这个例子展示了如何训练一个网络来将一个图像的样式传递给另一个图像。它基于[1]中定义的体系结构。
这个例子类似于基于深度学习的神经风格迁移这是因为,要获得样式化的图像Y,只需要将输入图像X向前传递给网络。
下面是训练算法的高级图表。它使用三幅图像来计算损失:输入图像X、转换后的图像Y和样式图像S。
需要注意的是,损失函数使用预先训练的网络VGG-16从图像中提取特征。中可以找到它的实现和数学定义风格传递损失这个例子的一部分。
下载并提取COCO 2014火车图片和说明https://cocodataset.org/#download点击“2014列车图片”。将数据保存到指定的文件夹中imageFolder
.将图像提取为imageFolder
.2014年COCO被收藏可可财团.
创建用于存储COCO数据集的目录。
imageFolder = fullfile (tempdir,“可可”);如果~存在(imageFolder“dir”mkdir (imageFolder);结束
创建一个包含COCO映像的映像数据存储。
imd = imageDatastore (imageFolder,“IncludeSubfolders”,真正的);
跑步训练需要很长时间。如果您希望以降低生成网络的准确性为代价来减少训练时间,那么可以通过设置来选择图像数据存储的子集分数
到一个更小的值。
分数= 1;numObservations =元素个数(imds.Files);imd =子集(imd, 1:地板(numObservations *分数);
要调整图像的大小并将它们全部转换为RGB,创建一个增强的图像数据存储。
augimds = augmentedImageDatastore([256 256],imds,“ColorPreprocessing”,“gray2rgb”);
阅读样式图像。
styleImage = imread (“starryNight.jpg”);style = imresize(styleImage,[256 256]);
显示所选的样式图像。
图imshow (styleImage)标题(“风格形象”)
定义图像变压器网络。这是一个图像到图像的网络。网络由3部分组成:
网络的第一部分以大小为[256x256x3]的RGB图像为输入,向下采样为大小为[64x64x128]的feature map。
网络的第二部分由支持函数中定义的五个相同的残差块组成金宝appresidualBlock。
网络的第三部分,也是最后一部分,将特征映射上采样到图像的原始大小,并返回变换后的图像。最后一部分使用了upsampleLayer
,它是作为支持文件附加到本示例中的自定义层。金宝app
层= [%第一部分。imageInputLayer ([256 256 3),“名字”,“输入”,“归一化”,“没有”[9 9], 32,“填充”,“相同”,“名字”,“conv1”) groupNormalizationLayer (“channel-wise”,“名字”,“norm1”) reluLayer (“名字”,“relu1”卷积2dlayer ([3 3], 64,“步”2,“填充”,“相同”,“名字”,“conv2”) groupNormalizationLayer (“channel-wise”,“名字”,“norm2”) reluLayer (“名字”,“relu2”)卷积2dlayer ([3 3], 128,“步”2,“填充”,“相同”,“名字”,“conv3”) groupNormalizationLayer (“channel-wise”,“名字”,“norm3”) reluLayer (“名字”,“relu3”)%第二部分。residualBlock (“1”) residualBlock (“2”) residualBlock (“3”) residualBlock (“4”) residualBlock (“5”)%第三部分。upsampleLayer (“up1”卷积2dlayer ([3 3], 64,“步”, 1“填充”,“相同”,“名字”,“upconv1”) groupNormalizationLayer (“channel-wise”,“名字”,“norm6”) reluLayer (“名字”,“relu5”) upsampleLayer (“呼吁”)卷积2dlayer ([3 3], 32,“步”, 1“填充”,“相同”,“名字”,“upconv2”) groupNormalizationLayer (“channel-wise”,“名字”,“norm7”) reluLayer (“名字”,“relu6”3) convolution2dLayer(9日,“填充”,“相同”,“名字”,“conv_out”));lgraph = layerGraph(层);
在剩余块中添加缺失的连接。
lgraph = connectLayers (lgraph,“relu3”,“add1 / in2”);lgraph = connectLayers (lgraph,“add1”,“add2 / in2”);lgraph = connectLayers (lgraph,“add2”,“add3 / in2”);lgraph = connectLayers (lgraph,“add3”,“add4 / in2”);lgraph = connectLayers (lgraph,“add4”,“折扣/ in2”);
在一个情节中想象变压器网络的形象。
图绘制(lgraph)标题(“变换网络”)
创建一个dlnetwork
对象从层图。
dlnetTransform = dlnetwork (lgraph);
本例使用预训练的VGG-16深度神经网络提取不同层的内容和风格图像的特征。这些多层特征用于计算各自的内容和样式损失。
要得到预先训练过的VGG-16网络,使用vgg16
函数。如果您没有安装所需的支持包,那么该软件将提供下载链接。金宝app
netLoss = vgg16;
为了提取计算损失所需的特征,你只需要前24层。提取和转换为一层图。
lossLayers = netLoss.Layers(桥);lgraph = layerGraph (lossLayers);
转换为一个dlnetwork
.
dlnetLoss = dlnetwork (lgraph);
创建styleTransferLoss
函数中定义的风格传递损失这个例子的一部分。
这个函数styleTransferLoss
以损耗网络为输入dlnetLoss
,输入的一小批经过变换的图像dlX,
一小批转换后的图像海底
,该数组包含样式图像的Gram矩阵dlSGram
,与内容损失相关的重量contentWeight
体重和风格的减少有关styleWeight
.该函数返回总损失损失
而个体成分:内容损失lossContent
还有风格的丧失lossStyle。
的styleTransferLoss
函数使用支持函数金宝appcreateGramMatrix
在计算样式损失时。
的createGramMatrix
函数将损失网络提取的特征作为输入,并返回迷你批处理中每个图像的样式表示。你可以在这一节中找到Gram矩阵的实现和数学定义格拉姆矩阵.
创建函数modelGradients
,列于模型梯度函数这个例子的一部分。该函数以损耗网络作为输入dlnetLoss
,图像变压器网络dlnetTransform
,一小批输入图像dlX
,该数组包含样式图像的Gram矩阵dlSGram
,与内容损失相关的重量contentWeight
体重和风格的减少有关styleWeight
.函数返回梯度
图像变压器的可学习参数的损耗,图像变压器网络的状态,变换后的图像海底,
总损失损失
,与内容相关的损失lossContent
以及与风格相关的损失lossStyle。
在[1]中训练2个时期的4个小批量。
numEpochs = 2;miniBatchSize = 4;
将增强后的图像数据存储的读大小设置为迷你批处理大小。
augimds。MiniBatchSize = MiniBatchSize;
指定ADAM优化的选项。指定学习率为0.001,梯度衰减系数为0.01,梯度衰减系数的平方为0.999。
learnRate = 0.001;gradientDecayFactor = 0.9;squaredGradientDecayFactor = 0.999;
在可用的GPU上进行训练。使用GPU需要并行计算工具箱™和支持的GPU设备。金宝app有关支持的设备的信息,请参见金宝appGPU支金宝app持情况(并行计算工具箱).
executionEnvironment =“汽车”;
在计算总损失时,注明样式损失的权重和内容损失的权重。
请注意,为了在内容和样式损失之间找到良好的平衡,您可能需要试验不同的权重组合。
weightContent = 1的军医;weightStyle = 3 e-8;
选择训练进度的情节频率。这指定在每个情节更新之间有多少次迭代。
plotFrequency = 10;
为了能够计算训练期间的损失,计算风格图像的Gram矩阵。
将样式图像转换为dlarray
.
dlS = dlarray(单(styleImage),SSC的);
为了计算Gram矩阵,将风格图像输入到VGG-16网络中,提取四个不同层的激活。
[dlSActivations1, dlSActivations2 dlSActivations3 dlSActivations4] =前进(dlnetLoss dlS,...“输出”,[“relu1_2”“relu2_2”“relu3_3”“relu4_3”]);
使用支持函数计算每组激活的格拉姆矩阵金宝appcreateGramMatrix
.
dlSGram {1} = createGramMatrix (dlSActivations1);dlSGram {2} = createGramMatrix (dlSActivations2);dlSGram {3} = createGramMatrix (dlSActivations3);dlSGram {4} = createGramMatrix (dlSActivations4);
训练图由两个数字组成:
一个显示训练中损失的图表
一种包含图像变压器网络的输入和输出图像的图形
初始化训练图。您可以在支持函数中查看初始化的详细信息金宝appinitializeFigures。
这个函数返回:轴ax₁
你把损失标在哪里,坐标轴吗ax2
在哪里绘制验证图像,动画线lineLossContent
哪个包含内容丢失,动画行lineLossStyle
其中包含了风格的损失和动画线lineLossTotal
包括全部损失。
(ax₁,ax2 lineLossContent、lineLossStyle lineLossTotal] = initializeStyleTransferPlots ();
初始化ADAM优化器的平均梯度和平均平方梯度超参数。
averageGrad = [];averageSqGrad = [];
计算训练迭代的总数。
numIterations =地板(augimds.NumObservations * numEpochs / miniBatchSize);
在训练前初始化迭代数和计时器。
迭代= 0;开始=抽搐;
火车模型。这可能需要很长时间才能运行。
%循环纪元。为我= 1:numEpochs重置和洗牌数据存储。重置(augimds);augimds = shuffle (augimds);%循环小批。而Hasdata (augimds) iteration = iteration + 1;%读取小批数据。data =阅读(augimds);%忽略epoch的最后一部分小批。如果(数据,1)< miniBatchSize大小继续结束%从数据存储中提取图像到单元格数组中。数据图像= {:1};沿着第四维度连接图像。X =猫(4、图像{:});X =单(X);%将小批数据转换为dlarray并指定尺寸标签% 'SSCB'(空间、空间、通道、批处理)。dlX = dlarray (X,“SSCB”);%如果在GPU上训练,则将数据转换为gpuArray。如果(executionEnvironment = =“汽车”&& canUseGPU) || executionEnvironment ==“图形”dlX = gpuArray (dlX);结束%评估模型梯度和网络状态使用的% dlfeval和模型梯度函数%的例子。(梯度、州、海底、失去、lossContent lossStyle] = dlfeval (@modelGradients,...dlnetLoss、dlnetTransform dlX、dlSGram weightContent, weightStyle);dlnetTransform。=状态;%更新网络参数。[dlnetTransform, averageGrad averageSqGrad] =...adamupdate (dlnetTransform、渐变averageGrad averageSqGrad,迭代,...learnRate、gradientDecayFactor squaredGradientDecayFactor);%每一次plotfrequency迭代,绘制训练进度。如果mod(iteration,plotFrequency) == 0 addpoints(lineLossTotal,iteration,double(gather(extractdata(loss)))) addpoints(lineLossContent,iteration,double(gather(extractdata(lossStyle))))))%使用迷你批处理的第一个映像作为验证映像。dlV = dlX (:,:,: 1);%使用先前计算的转换后的验证图像。dlVY =海底(:,:,:1);%使用函数imshow,转换为uint8。validationImage = uint8(收集(extractdata (dlV)));transformedValidationImage = uint8(收集(extractdata (dlVY)));%绘制输入图像和输出图像,并增加大小imshow (imtile ({validationImage, transformedValidationImage}),“父”, ax2);结束%显示训练开始后经过的时间和训练完成的百分比。D =持续时间(0,0,toc(开始),“格式”,“hh: mm: ss”);completionPercentage =圆(迭代/ numIterations * 100, 2);标题(ax₁,”时代:“+我+”,迭代:“+迭代+“的”+ numIterations +”(“+ completionPercentage +“%)”+”,过去:“+ drawnow字符串(D))结束结束
一旦训练完成,您可以在您选择的任何图像上使用图像转换器。
加载你想要转换的图像。
imFilename =“peppers.png”;我= imread (imFilename);
将输入图像调整为图像转换器的输入尺寸。
[256256] im = imresize (im);
把它转换成dlarray。
dlX = dlarray(单(im),“SSCB”);
使用GPU转换为gpuArray
如果有的话。
如果canUseGPU dlX = gpuArray(dlX);结束
要将样式应用到图像,请使用该函数将其转发给图像转换器预测。
海底=预测(dlnetTransform dlX);
重新缩放图像到范围[0 255]。首先,使用函数双曲正切
要重新调节海底
到范围[-1 1]。然后,移动和缩放输出,重新缩放到[0 255]范围。
Y = 255 *(双曲正切(海底)+ 1)/ 2;
准备Y
策划。使用的函数extractdata
提取数据dlarray。
使用gather函数将Y从GPU转移到本地工作空间。
Y = uint8(收集(extractdata (Y)));
将输入图像(左)显示在风格化图像(右)旁边。
图m = imtile({im,Y});imshow (m)
这个函数modelGradients
以损耗网络为输入dlnetLoss
,图像变压器网络dlnetTransform
,一小批输入图像dlX
,该数组包含样式图像的Gram矩阵dlSGram
,与内容损失相关的重量contentWeight
体重和风格的减少有关styleWeight
.它将返回梯度
图像变压器的可学习参数的损耗,图像变压器网络的状态,变换后的图像海底
,全损损失
,与内容相关的损失lossContent
以及与风格相关的损失lossStyle。
函数(梯度、州、海底、失去、lossContent lossStyle] =...modelGradients(dlnetLoss,dlnetTransform,dlX, dlgram,contentWeight,styleWeight) [dlY,state] = forward(dlnetTransform,dlX);海底= 255 *(双曲正切(海底)+ 1)/ 2;[损失,lossContent, lossStyle] = styleTransferLoss (dlnetLoss,海底,dlX dlSGram, contentWeight, styleWeight);梯度= dlgradient(损失、dlnetTransform.Learnables);结束
这个函数styleTransferLoss
以损耗网络为输入dlnetLoss
,一小批输入图像dlX,
一小批转换后的图像海底
,该数组包含样式图像的Gram矩阵dlSGram
,与内容和样式相关的权重contentWeight
和styleWeight,
分别。它返回全部损失损失
而个体成分:内容损失lossContent
还有风格的丧失lossStyle。
内容损失是对输入图像之间空间结构差异的度量X
输出图像Y
.
另一方面,样式丢失告诉您样式图像之间的外观有多大的差异年代
输出图像Y
.
下图解释了算法styleTransferLoss
实现计算总损失。
首先,该函数传递输入图像X
,转换后的图像Y
以及样式图像年代
到预先训练的网络VGG-16。这个预先训练的网络从这些图像中提取几个特征。然后算法计算损失的内容使用的空间特性输出图像的输入图像X和Y .此外,计算风格损失通过输出图像Y的文体特征和风格的形象s .最后,它获得的总损失增加的内容和风格的损失。
对于小批处理中的每一幅图像,内容损失函数将该层输出的原始图像和变换后的图像的特征进行比较relu_3_3
.特别地,它计算激活之间的均方误差,并返回小批的平均损失:
在哪里
包含输入图像,
包含转换后的图像,
是小批量,和
表示层上提取的激活relu_3_3。
计算风格损失,对于每一个单一的图像在小批:
提取激活层relu1_2
,relu2_2
,relu3_3
和relu4_3
.
对于这四种激活的每一种 计算格拉姆矩阵 .
计算相应的格拉姆矩阵之间的平方差。
将每一层的四个输出相加 从上一步。
为了获得整个小批的风格损失,计算每个图像的风格损失的平均值 mini-batch:
在哪里
是指数层,和
是格拉姆矩阵。
函数[损失,lossContent, lossStyle] = styleTransferLoss (dlX dlnetLoss,海底,...dlSGram、weightContent weightStyle)%提取激活。dlYActivations =细胞(1、4);dlYActivations [dlYActivations {1}, {2}, dlYActivations {3}, dlYActivations {4}] =...转发(dlnetLoss,海底,“输出”,[“relu1_2”“relu2_2”“relu3_3”“relu4_3”]);dlX dlXActivations =前进(dlnetLoss,“输出”,“relu3_3”);%计算激活之间的均方误差。lossContent = mean((dlYActivations{3} - dlXActivations).^2,“所有”);把所有四种激活的损失加起来。lossStyle = 0;为j = 1:4 G = createGramMatrix(dlYActivations{j});lossStyle = lossStyle + sum((G - dlSGram{j}).^2,“所有”);结束在小批量中平均损失。miniBatchSize =大小(dlX 4);lossStyle = lossStyle / miniBatchSize;%应用权重。lossContent = weightContent * lossContent;lossStyle = weightStyle * lossStyle;计算总损失。loss = lossContent + lossStyle;结束
的residualBlock
函数返回一个包含6个层的数组。它由卷积层、实例规范化层、ReLu层和加法层组成。请注意,groupNormalizationLayer(“channel-wise”)
是一个简单的实例规范化层。
函数layers =[卷积2dlayer ([3 3], 128,])“步”, 1“填充”,“相同”,“名字”,“convRes”+名称+“_1”) groupNormalizationLayer (“channel-wise”,“名字”,“normRes”+名称+“_1”) reluLayer (“名字”,“reluRes”+名称+“_1”)卷积2dlayer ([3 3], 128,“步”, 1“填充”,“相同”,“名字”,“convRes”+名称+“_2”) groupNormalizationLayer (“channel-wise”,“名字”,“normRes”+名称+“_2”) additionLayer (2“名字”,“添加”+名字)];结束
这个函数createGramMatrix
将单个层的激活作为输入,并返回迷你批处理中每个图像的样式表示.
输入是一个大小为[H, W, C, N]的feature map,其中H为高度,W为宽度,C为通道数,N为mini-batch size。函数输出一个数组G
的大小(C, C, N)。每个子数组G (:,:, k)
格拉姆矩阵是否对应于
迷你批处理中的图像。每个条目
表示通道之间的相关性
和
,因为每个条目在通道中
将通道中相应位置的条目相乘
:
在哪里 是激活的吗 迷你批处理中的图像。
格拉姆矩阵包含关于哪些特征一起激活的信息,但没有关于特征在图像中出现的位置的信息。这是因为高度和宽度的总和失去了关于空间结构的信息。损失函数使用这个矩阵作为图像的风格表示。
函数G = createGramMatrix(activations) [h,w,numChannels] = size(activations,1:3);特点=重塑(激活,h * w, numChannels, []);featuresT = permute(features,[2 1 3]);G = dlmtimes(featuresT,features) / (h*w*numChannels);结束
约翰逊,贾斯汀,亚历山大·阿拉希和李飞飞。"实时风格转换和超分辨率的感知损失"计算机视觉欧洲会议.施普林格可汗,2016。
dlnetwork
|向前
|预测
|dlarray
|dlgradient
|dlfeval
|adamupdate