列车快速风格转移网络
这个例子展示了如何训练一个网络来将一个图像的风格转移到另一个图像。它基于[1]中定义的体系结构。
这个例子类似于使用深度学习的神经风格迁移这是因为,要获得风格化的图像Y,你只需要将输入图像X向前传递给网络。
下面是训练算法的高级图表。这使用三个图像来计算损失:输入图像X,转换图像Y和风格图像S。
注意,损失函数使用预训练的网络vg -16从图像中提取特征。的实现和数学定义风格转移损失部分的示例。
负荷训练数据
下载并提取COCO 2014列车图片和说明文字https://cocodataset.org/#download点击“2014年列车图片”。将数据保存在指定的文件夹中imageFolder
.将图像提取到imageFolder
.2014年的COCO由可可财团.
创建目录来存储COCO数据集。
imageFolder = fullfile(tempdir,“可可”);如果~存在(imageFolder“dir”mkdir (imageFolder);结束
创建一个包含COCO映像的映像数据存储。
imds = imageDatastore(imageFolder,“IncludeSubfolders”,真正的);
跑步训练可能需要很长时间。如果您希望以牺牲结果网络的准确性为代价来减少训练时间,则通过设置选择图像数据存储的子集分数
到一个更小的值。
分数= 1;numObservations = numel(imds.Files);imds =子集(imds,1:地板(numObservations*分数));
要调整图像大小并将它们全部转换为RGB,请创建一个增强图像数据存储。
augimds = augmentedImageDatastore([256 256],imds,“ColorPreprocessing”,“gray2rgb”);
阅读样式图像。
styleImage = imread(“starryNight.jpg”);styleImage = imresize(styleImage,[256 256]);
显示所选样式图像。
图imshow(style)“风格形象”)
定义图像转换器网络
定义图像转换器网络。这是一个图像到图像的网络。网络由三部分组成:
网络的第一部分将大小为[256x256x3]的RGB图像作为输入,并将其下采样到大小为[64x64x128]的特征映射。
网络的第二部分由支持函数中定义的五个相同的剩余块组成金宝app
residualBlock。
网络的第三部分也是最后一部分将特征映射上采样到图像的原始大小,并返回转换后的图像。最后一部分使用
upsampleLayer
,它是附加到这个示例的自定义层,作为支持文件。金宝app
层= [第一部分。imageInputLayer([256 256 3],归一化=“没有”)卷积2dlayer([9 9],32,填充=“相同”) groupNormalizationLayer (“channel-wise”) relullayer convolution2dLayer([3 3],64,Stride=2,Padding=“相同”) groupNormalizationLayer (“channel-wise”) relullayer convolution2dLayer([3 3],128,Stride=2,Padding=“相同”) groupNormalizationLayer (“channel-wise”) reluLayer (Name =“relu_3”)第二部分。residualBlock (“1”) residualBlock (“2”) residualBlock (“3”) residualBlock (“4”) residualBlock (“5”)第三部分。upsampleLayer convolution2dLayer([3 3],64,Padding=“相同”) groupNormalizationLayer (“channel-wise”) reluLayer upsampleLayer convolution2dLayer([3 3],32,Padding=“相同”) groupNormalizationLayer (“channel-wise”) reluLayer卷积2dlayer(9,3,填充=“相同”));lgraph = layerGraph(图层);
在剩余块中添加缺失的连接。
lgraph = connectLayers(“relu_3”,“add_1 / in2”);lgraph = connectLayers(“add_1”,“add_2 / in2”);lgraph = connectLayers(“add_2”,“add_3 / in2”);lgraph = connectLayers(“add_3”,“add_4 / in2”);lgraph = connectLayers(“add_4”,“add_5 / in2”);
在图中可视化图像转换器网络。
图表(lgraph)标题(“变换网络”)
创建一个dlnetwork
对象从图层图。
netTransform = dlnetwork(lgraph);
风格丢失网络
本例使用预先训练好的vg -16深度神经网络来提取不同层次的内容和风格图像的特征。这些多层特征用于计算各自的内容和样式损失。
要获得预先训练好的VGG-16网络,请使用vgg16
函数。如果您没有安装所需的支持包,则该软件提供下载链接。金宝app
netLoss = vgg16;
为了提取计算损失所需的特征,您只需要前24层。提取并转换为层图。
lossLayers = netLoss.Layers(1:24);lgraph = layerGraph(lossLayers);
转换为dlnetwork
.
netLoss = dlnetwork(lgraph);
定义模型损失函数
创建函数modelLoss
,列于模型损失函数部分的示例。该函数以损失网络、图像转换网络、一小批输入图像、包含风格图像Gram矩阵的数组、与内容损失相关的权重和与风格损失相关的权重作为输入。该函数返回总损耗、与内容相关的损耗和与样式相关的损耗、总损耗相对于图像转换器的可学习参数的梯度、图像转换器网络的状态以及转换后的图像。
指定培训项目
像[1]中那样,在2个epoch中使用迷你批量大小为4的训练。
numEpochs = 2;miniBatchSize = 4;
将增强映像数据存储的读大小设置为迷你批处理大小。
augimds。MiniBatchSize = MiniBatchSize;
指定ADAM优化的选项。指定学习速率为0.001,梯度衰减因子为0.01,梯度衰减因子的平方为0.999。
learnRate = 0.001;gradientDecayFactor = 0.9;squaredGradientDecayFactor = 0.999;
在计算总损失时,指定样式损失的权重和内容损失的权重。
请注意,为了在内容和风格损失之间找到一个良好的平衡,您可能需要尝试不同的权重组合。
weightContent = 1e-4;weightStyle = 3e-8;
选择训练进度的plot frequency。这指定了每次绘图更新之间有多少次迭代。
plotFrequency = 10;
火车模型
为了能够计算训练期间的损失,计算样式图像的Gram矩阵。
将样式图像转换为dlarray
.
S = dlarray(single(styleImage),“SSC”);
为了计算Gram矩阵,将风格图像输入到VGG-16网络,并提取四个不同层的激活。
[SActivations1,SActivations2,SActivations3,SActivations4] = forward(netLoss,S,...输出= [“relu1_2”“relu2_2”“relu3_3”“relu4_3”]);
使用支持函数计算每组激活的Gram矩阵金宝appcreateGramMatrix
.
SGram{1} = createGramMatrix(SActivations1);SGram{2} = createGramMatrix(SActivations2);SGram{3} = createGramMatrix(SActivations3);SGram{4} = createGramMatrix(SActivations4);
训练图由两个人物组成:
显示训练期间损失情况的图表
包含图像转换器网络的输入图像和输出图像的图形
初始化训练图。您可以在支持函数中查看初始化的详细信息金宝appinitializeFigures。
这个函数返回:轴ax₁
在轴上画损失ax2
你在哪里绘制验证图像,动画线lineLossContent
其中包含内容损失,动画线lineLossStyle
其中包含风格损失和动画线lineLossTotal
其中包括全部损失。
[ax1,ax2,lineLossContent,lineLossStyle,lineLossTotal] = initializeStyleTransferPlots;
初始化ADAM优化器的平均梯度和平均梯度平方超参数。
averageGrad = [];averageSqGrad = [];
计算训练迭代的总次数。
numIterations = floor(augimds.NumObservations*numEpochs/miniBatchSize);
训练前初始化迭代次数和定时器。
迭代= 0;开始= tic;
训练模型。如果有GPU,可以在GPU上进行训练。使用GPU需要并行计算工具箱™和受支持的GPU设备。金宝app有关受支持设备的信息,请参见金宝appGPU计算要求(并行计算工具箱).这可能要花很长时间。
%遍历epoch。为i = 1:numEpochs重置和洗牌数据存储。重置(augimds);Augimds = shuffle(Augimds);在小批上循环。而Hasdata (augimds)迭代=迭代+ 1;读取小批数据。Data = read(augimds);忽略纪元的最后一个部分小批。如果size(data,1) < miniBatchSize继续结束将图像从数据存储中提取到单元格数组中。图片=数据{:,1};沿着第4维连接图像。X = cat(4,images{:});X =单(X);将小批量数据转换为dlarray并指定尺寸标签%“SSCB”(空间,空间,通道,批)。X = dlarray(X,“SSCB”);如果在GPU上训练,则将数据转换为gpuArray。如果canUseGPU X = gpuArray(X);结束评估模型损失,梯度和网络状态使用的末尾列出的modelLoss函数%的例子。[loss,lossContent,lossStyle,gradients,state,Y] = dlfeval(@modelLoss,...netLoss netTransform X, SGram、weightContent weightStyle);netTransform。年代tate = state;更新网络参数。[netTransform, averageGrad averageSqGrad] =...adamupdate (netTransform、渐变averageGrad averageSqGrad,迭代,...learnRate, gradientDecayFactor, squaredGradientDecayFactor);%每个plot频率迭代,绘制训练进度。如果mod(迭代,plotFrequency) == 0 addpoints(lineLossTotal,迭代,double(loss)) addpoints(lineLossContent,迭代,double(lossContent)) addpoints(lineLossStyle,迭代,double(lossStyle))使用小批的第一个图像作为验证图像。Xv = x (:,:,:,1);使用先前计算的转换后的验证映像。Yv = y (:,:,:,1);要使用imshow函数,请转换为uint8。validationImage = uint8(gather(extractdata(XV)));transformedValidationImage = uint8(gather(extractdata(YV)));绘制输入图像和输出图像并增加大小imshow (imtile ({validationImage, transformedValidationImage}),父= ax2);结束%显示训练开始后所经过的时间和训练完成百分比。D = duration(0,0,toc(start),Format=“hh: mm: ss”);completionPercentage = round(迭代/numIterations*100,2);标题(ax₁,”时代:“+ I +,迭代:"+迭代+“的”+ numIterations +"("+ completionPercentage +“%)”+,消失:"+字符串(D))现在绘制结束结束
为图像设置风格
训练完成后,您可以在选择的任何图像上使用图像转换器。
加载要转换的图像。
imFilename =“peppers.png”;im = imread(imFilename);
将输入图像调整为图像转换器的输入尺寸。
Im = imresize(Im,[256,256]);
将其转换为dlarray。
X = dlarray(single(im),“SSCB”);
使用GPU转换为gpuArray
如果有的话。
如果canUseGPU X = gpuArray(X);结束
要将样式应用于图像,请使用函数将其正向传递给图像转换器预测。
Y = predict(netTransform,X);
将图像重新缩放到范围[0 255]。首先,使用函数双曲正切
要重新调节Y
到范围[-1 1]。然后,shift和缩放输出,重新缩放到[0 255]范围。
Y = 255*(tanh(Y)+1)/2;
准备Y
策划。使用函数extractdata
来提取数据dlarray。
使用函数gather将Y从GPU传输到本地工作区。
Y = uint8(gather(extractdata(Y)));
将输入图像(左)显示在程式化图像(右)旁边。
图m = imtile({im,Y});imshow (m)
模型损失函数
这个函数modelLoss
以损失网络作为输入netLoss
,图像转换器网络netTransform
,一个小批量的输入图像X
,一个包含样式图像的Gram矩阵的数组SGram
,与内容物损失相关的重量contentWeight
和体重有关的风格损失styleWeight
.该函数返回与内容相关的总损耗lossContent
以及与风格相关的损失lossStyle
,总损耗相对于图像变压器的可学习参数的梯度梯度
,图像变压器网络的状态状态
,以及变换后的图像Y
.
函数(损失、lossContent lossStyle、渐变状态,Y] =...modelLoss(netLoss,netTransform,X,SGram,contentWeight,styleWeight) [Y,state] = forward(netTransform,X);Y = 255*(tanh(Y)+1)/2;[loss,lossContent,lossStyle] = styleTransferLoss(netLoss,Y,X,SGram,contentWeight,styleWeight);gradients = dlgradient(loss,netTransform.Learnables);结束
风格转移损失
这个函数styleTransferLoss
以损失网络作为输入netLoss
,一个小批量的输入图像X,
一小批转换后的图像Y
,一个包含样式图像的Gram矩阵的数组SGram
,即与内容和样式相关联的权重contentWeight
而且styleWeight,
分别。它返还全部损失损失
单独的组成部分:内容损失lossContent
风格的丧失lossStyle。
内容损失是衡量输入图像之间的空间结构有多大差异的指标X
输出图像Y
.
另一方面,风格损失告诉你风格形象之间在风格外观上有多大的差异年代
输出图像Y
.
下面的图表解释了算法styleTransferLoss
实现了总损失的计算。
首先,函数传递输入图像X
,变换后的图像Y
以及样式图像年代
到预训练网络vg -16。这个预先训练好的网络从这些图像中提取出几个特征。然后利用输入图像X和输出图像Y的空间特征计算内容损失,利用输出图像Y和风格图像s的风格特征计算风格损失,最后将内容损失和风格损失相加得到总损失。
内容丢失
对于小批中的每张图像,内容损失函数比较原始图像和该层输出的转换图像的特征relu3_3
.特别是,它计算激活之间的均方误差,并返回小批的平均损失:
在哪里
包含输入图像,
包含转换后的图像,
是小批量尺寸,和
表示在层中提取的激活relu3_3。
风格的损失
为了计算样式损失,对于mini-batch中的每一张图像:
提取各层的激活
relu1_2
,relu2_2
,relu3_3
而且relu4_3
.对于每一个激活 计算克矩阵 .
计算对应的Gram矩阵之间的平方差。
将每一层的四个输出相加 从前面的步骤。
为了获得整个小批的样式损失,计算每个图像的样式损失的平均值 在小批量中:
在哪里 是层的索引,和 为克拉姆矩阵。
全部损失
函数[loss,lossContent,lossStyle] = styleTransferLoss(netLoss,Y,X,...SGram、weightContent weightStyle)提取激活。yactivation = cell(1,4);YActivations [YActivations {1}, {2}, YActivations {3}, YActivations {4}] =...转发(netLoss Y“输出”,[“relu1_2”“relu2_2”“relu3_3”“relu4_3”]);XActivations = forward(netLoss,X,“输出”,“relu3_3”);计算激活之间的均方误差。lossContent = mean((YActivations{3} - XActivations).^2,“所有”);将所有四次激活的损失加起来。lossStyle = 0;为j = 1:4 G = createGramMatrix(YActivations{j});lossStyle = lossStyle + sum((G - SGram{j}).^2,“所有”);结束小批量的平均损耗。miniBatchSize = size(X,4);lossStyle = lossStyle/miniBatchSize;应用权重。lossContent = weightContent * lossContent;lossStyle = weightStyle * lossStyle;计算总损失。loss = lossContent + lossStyle;结束
剩余块
的residualBlock
函数返回一个包含六层的数组。它由卷积层、实例归一化层、ReLu层和加法层组成。请注意,groupNormalizationLayer(“channel-wise”)
是一个简单的实例规范化层。
函数layers = residualBlock(name) layers = [convolution2dLayer([3 3], 128,Padding=“相同”、名称=“convRes_”+名称+“_1”) groupNormalizationLayer (“channel-wise”、名称=“normRes_”+名称+“_1”) reluLayer (Name =“reluRes_”+名称+“_1”)卷积2dlayer([3 3],128,填充=“相同”、名称=“convRes_”+名称+“_2”) groupNormalizationLayer (“channel-wise”、名称=“normRes_”+名称+“_2”) additionLayer (Name =“加”+名字)];结束
格拉姆矩阵
这个函数createGramMatrix
将单个图层的激活作为输入,并返回一个小批处理中每个图像的风格表示.
输入大小为[H, W, C, N]的特征图,其中H为高度,W为宽度,C为通道数,N为mini-batch大小。该函数输出一个数组G
大小[C,C,N]。每个子数组G (:,:, k)
克拉姆矩阵是否对应于
图像在迷你批处理。每个条目
Gram矩阵的,表示通道之间的相关性
而且
,因为每个入口都在通道中
将通道中相应位置的条目相乘
:
在哪里 激活是 图像在迷你批处理。
Gram矩阵包含哪些特征一起被激活的信息,但没有关于特征在图像中出现的位置的信息。这是因为高度和宽度的总和丢失了关于空间结构的信息。损失函数使用这个矩阵作为图像的风格表示。
函数G = createGramMatrix(激活)[h,w,numChannels] = size(激活,1:3);features =重塑(激活,h*w,numChannels,[]);featrest = permute(特征,[2 1 3]);G = dlmtimes(featest,features) / (h*w*numChannels);结束
参考文献
约翰逊,贾斯汀,亚历山大·阿拉希和李飞飞。“实时风格转换和超分辨率的感知损失。”计算机视觉欧洲会议.施普林格,Cham, 2016。
另请参阅
dlnetwork
|向前
|预测
|dlarray
|dlgradient
|dlfeval
|adamupdate