1、卷积核
卷积神经网络的每一层参数都是一组卷积核即权重。
2、特征层
在CNN输出结果之前,有一个高维的特征问量,该层离最终输出层最近、也是最能代图像特征的一层。这一层的特征,除了可以直接通过交叉熵损失做分类,还经常会用于进行图像比对和搜索。得到了多张图片的高维向量之即可通过计算向量距离的方法得到与某张图片最像的图片。
(1)反向池化:记录了每个池化区域内最大值的位置信息。反池化前特征层的数值与池化后特征层数值不一样
(2)激活
标注卷积单元的激活函数使用的是非线性的 ReLU,在反向操作中同样也是使用该函数
(3)反向卷积
卷积计算实际上是输人特征值和卷积核(kernel)的点积运算。既然要实现“逆”卷积那么这里取训练好的权重矩阵的逆即可。需要注意的是,这里的反向池化、激活以及反向卷积操作的目的都是为了将特征层的信息还原,所有信息都是利用训练好的神经网络已有的信息。
首先,每个卷积层单元都对应一个“逆”卷积单元,并与其相连接,输入一张测试图片,使其通过正向发租神经网络,当想要测试某个通道的激活情况时,就将其他所有通道置为零,并将测试图经过仅由该通道激活后的特征图输人到“逆卷积模块,即反池化、激活以及逆向卷积操作来重构特征图。接下来“逆”卷积模块需要将特征不断下传重构,直到重构成原图的大小。
3、图片风格化(神经网络图像风格转化)
from __future__ import print_function import torch import torch.nn as nn import torch.nn.functional as F import torch.optim as optim #引入优化方法 #下面两个引用用来载入/展示图片 from PIL import Image import matplotlib.pyplot as plt import torchvision.transforms as transforms #将PIL图片格式转换为向量(tensor)格式 import torchvision.models as models #训练/载入预训练模型 import copy #用来拷贝模型 device = torch.device("cuda" if torch.cuda.is_available() else "cpu") #如果有cuda则使用gpu,否则使用cpu #图片预处理:原始PIL图片像素值的范围为0-255,在向量处理过程中需要先将这些值归一化到0-1。另外,图片需要缩放到相同的维度。 imsize = 512 if torch.cuda.is_available() else 128 # 如果没有gpu则使用小图 loader = transforms.Compose([ transforms.Resize(imsize), # 将图片进行缩放,需要缩放到相同的尺度再输入到神经网络 transforms.ToTensor()]) # 将图片转为pytorch可接受的向量(tensor)格式 def image_loader(image_name): image = Image.open(image_name) image = loader(image).unsqueeze(0) return image.to(device, torch.float) style_img = image_loader("images/neural-style/style_sketch.jpg") #载入1张风格图片 content_img = image_loader("images/neural-style/content_person.jpg") #载入1张内容图片 assert style_img.size() == content_img.size(), \ "we need to import style and content images of the same size"
#将 PyTorch 中 tensor 格式的数据转成 PIL格式的飘片用于展示 unloader = transforms .ToPILImage( ) plt.ion() def imshow(tensor, title=None): image = tensor.cpu().clone() image = image.squeeze(0) image = unloader(image) plt.imshow(image) if title is not None: # 定义一个专门用于展示图片的函数 #为了不改变 tensor 的内容这里先备份一- # 去掉这里面没有用的 batch 这个维度 plt.title(title) plt.pause(0.001)# pause a bit so that plots are updated plt.figure() imshow(style_img, title='Style Image') plt.figure() imshow(content_img,title='Content Image')
风格图片和内容图片
unloader = transforms.ToPILImage() # 将pytorch中tensor格式的数据转成PIL格式的图片用于展示 plt.ion() def imshow(tensor, title=None): #定义一个专门用于展示图片的函数 image = tensor.cpu().clone() # 为了不改变tensor的内容这里先克隆一下 image = image.squeeze(0) # 去掉这里面没有用的batch这个维度 image = unloader(image) plt.imshow(image) if title is not None: plt.title(title) plt.pause(0.001) # pause a bit so that plots are updated plt.figure() imshow(style_img, title='Style Image') plt.figure() imshow(content_img, title='Content Image')
定义损失函数
# 定义内容学习的损失函数 class ContentLoss(nn.Module): def __init__(self, target,): super(ContentLoss, self).__init__() self.target = target.detach() def forward(self, input): self.loss = F.mse_loss(input, self.target) #输入内容图片和目标图片的均方差 return input # 定义风格学习的损失函数前需要先定义格雷姆矩阵的计算方法 def gram_matrix(input): a, b, c, d = input.size() # a为batch中图片的个数(1) # b为feature map的个数 # (c,d)feature map的维度 (N=c*d) features = input.view(a * b, c * d) # resise F_XL into \hat F_XL G = torch.mm(features, features.t()) # 计算得出格雷姆矩阵(内积) return G.div(a * b * c * d) #对格雷姆矩阵的数值进行归一化 # 定义风格学习的损失函数 class StyleLoss(nn.Module): def __init__(self, target_feature): super(StyleLoss, self).__init__() self.target = gram_matrix(target_feature).detach() def forward(self, input): G = gram_matrix(input) self.loss = F.mse_loss(G, self.target) #艺术图片的格雷姆矩阵和目标图片格雷姆矩阵的均方差 return input
定义网络结构
#载入用ImageNet预训练好的VGG19模型,并只使用features模块 #注:pytorch将vgg模型分为2个模块,features模块和classifier模块,其中features模块包含卷积和池化层,classifier模块包含全连接和分类层。 # 一些层在训练和预测(评估)时网络的行为(参数)不同,注意这里需要使用eval() cnn = models.vgg19(pretrained=True).features.to(device).eval() # VGG网络是用均值[0.485, 0.456, 0.406],方差[0.229, 0.224, 0.225]对图片进行归一化之后进行训练的,所以这里也需要对图片进行归一化 cnn_normalization_mean = torch.tensor([0.485, 0.456, 0.406]).to(device) cnn_normalization_std = torch.tensor([0.229, 0.224, 0.225]).to(device) class Normalization(nn.Module): def __init__(self, mean, std): super(Normalization, self).__init__() #下面两个操作将数据转成[BatchSize x Channel x Hight x Weight]格式 self.mean = torch.tensor(mean).view(-1, 1, 1) self.std = torch.tensor(std).view(-1, 1, 1) def forward(self, img): # normalize img return (img - self.mean) / self.std # vgg19.features中包含(Conv2d, ReLU, MaxPool2d, Conv2d, ReLU…)等,为了实现图片风格转换,需要将内容损失层(content loss)和风格损失层(style loss)加到vgg19.features后面 content_layers_default = ['conv_4'] style_layers_default = ['conv_1', 'conv_2', 'conv_3', 'conv_4', 'conv_5'] def get_style_model_and_losses(cnn, normalization_mean, normalization_std, style_img, content_img, content_layers=content_layers_default, style_layers=style_layers_default): cnn = copy.deepcopy(cnn) normalization = Normalization(normalization_mean, normalization_std).to(device) #归一化模块 content_losses = [] style_losses = [] model = nn.Sequential(normalization) #可以一个新的nn.Sequential,顺序地激活 i = 0 # 每看到一个卷积便加1 for layer in cnn.children(): #遍历当前cnn结构 #判断当前遍历的是cnn中的卷积层 or ReLU层 or 池化层 or BatchNorm层 if isinstance(layer, nn.Conv2d): i += 1 name = 'conv_{}'.format(i) elif isinstance(layer, nn.ReLU): name = 'relu_{}'.format(i) layer = nn.ReLU(inplace=False) #由于实验过程中发现in-place在ContentLoss和StyleLoss上表现的不好,因此置为False elif isinstance(layer, nn.MaxPool2d): name = 'pool_{}'.format(i) elif isinstance(layer, nn.BatchNorm2d): name = 'bn_{}'.format(i) else: raise RuntimeError('Unrecognized layer: {}'.format(layer.__class__.__name__)) model.add_module(name, layer) if name in content_layers: #向网络中加入content loss target = model(content_img).detach() content_loss = ContentLoss(target) model.add_module("content_loss_{}".format(i), content_loss) content_losses.append(content_loss) if name in style_layers: #向网络中加入style loss target_feature = model(style_img).detach() style_loss = StyleLoss(target_feature) model.add_module("style_loss_{}".format(i), style_loss) style_losses.append(style_loss) # now we trim off the layers after the last content and style losses for i in range(len(model) - 1, -1, -1): if isinstance(model[i], ContentLoss) or isinstance(model[i], StyleLoss): break model = model[:(i + 1)] return model, style_losses, content_losses
定义优化函数
def get_input_optimizer(input_img): #使用LBFGS方法进行梯度下降(不是常用的随机梯度下降,但不论是LBFGS还是随机梯度下降都是在空间中寻找最优解的优化方法) optimizer = optim.LBFGS([input_img.requires_grad_()]) return optimizer # 定义整个风格化的学习过程 def run_style_transfer(cnn, normalization_mean, normalization_std, content_img, style_img, input_img, num_steps=300, style_weight=1000000, content_weight=1): """Run the style transfer.""" print('Building the style transfer model..') model, style_losses, content_losses = get_style_model_and_losses(cnn, normalization_mean, normalization_std, style_img, content_img) optimizer = get_input_optimizer(input_img) print('Optimizing..') run = [0] while run[0] <= num_steps: def closure(): #用来评估并返回当前loss的函数 input_img.data.clamp_(0, 1) #将更新后的输入修正到0-1 optimizer.zero_grad() model(input_img) style_score = 0 content_score = 0 for sl in style_losses: style_score += sl.loss for cl in content_losses: content_score += cl.loss style_score *= style_weight content_score *= content_weight loss = style_score + content_score loss.backward() run[0] += 1 if run[0] % 50 == 0: print("run {}:".format(run)) print('Style Loss : {:4f} Content Loss: {:4f}'.format( style_score.item(), content_score.item())) print() return style_score + content_score optimizer.step(closure) input_img.data.clamp_(0, 1) #最后一次修正 return input_img # 最终运行算法的一行代码 output = run_style_transfer(cnn, cnn_normalization_mean, cnn_normalization_std, content_img, style_img, input_img)
展示风格化的图片
plt.figure() imshow(output,title='outputImage')#画出风格化图片 plt.ioff() plt.show()
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)