荷兰后印象派画家——梵高是表现主义的先驱,并深深影响了二十世纪的绘画艺术,尤其是野兽派与德国表现主义。梵高的作品,如《星夜》、《向日葵》与《麦田群鸦》等,现已跻身于全球最著名、广为人知与昂贵的艺术作品的行列。今天,让我们用神经算法把你的图片赋上你心怡的艺术色彩!
在这里要讨论的是利用 Wolfram 语言来进行艺术风格的转换。在下面的文档中也能找到这个范例:
NetTrain >> 应用 >> 计算机视觉 >> 风格转移(http://reference.wolfram.com/language/ref/NetTrain.html.en)
创建一幅新图像,内容来自一幅给定的图像,风格则来自另一幅给定的图像。这是根据 Gatys 等在《艺术风格的神经算法》(A Neural Algorithm of Artistic Style) 中描述的方法来实现的。内容和风格图像如下:
为了生成这两幅图像的混合品,先获取预先训练过的图像分类网络:
vggNet = NetModel["VGG-16 Trained on ImageNet Competition Data"];
获取将被用作风格和内容图像的特征提取器的子网络:
featureNet = Take[vggNet, {1, "relu4_1"}]
其中用到了三个损失函数。第一个损失函数确保合成图像中的 "content" 与内容图像的相似:
contentLoss = NetGraph[{MeanSquaredLossLayer[]}, {1 -> NetPort["LossContent"]}]
第二个损失函数确保合成图像中的 "style" 与风格图像的相似。风格相似性被定义为输入和输出的 Gram 矩阵间的均方差:
gramMatrix = NetGraph[{FlattenLayer[-1], TransposeLayer[1 -> 2], DotLayer[]}, {1 -> 3, 1 -> 2 -> 3}];
styleLoss = NetGraph[{gramMatrix, gramMatrix, MeanSquaredLossLayer[]},{NetPort["Input"] -> 1, NetPort["Target"] -> 2, {1, 2} -> 3, 3 -> NetPort["LossStyle"]}]
第三个损失函数确保合成图像中相邻像素间亮度的幅值变化为较小的值。这使得合成图像看起来更自然:
l2Loss = NetGraph[{ThreadingLayer[(#1 - #2)^2 &], SummationLayer[]}, {{NetPort["Input"], NetPort["Target"]} -> 1 -> 2}];tvLoss = NetGraph[<|
"dx1" -> PaddingLayer[{{0, 0}, {1, 0}, {0, 0}}, "Padding" -> "Fixed" ],
"dx2" -> PaddingLayer[{{0, 0}, {0, 1}, {0, 0}}, "Padding" -> "Fixed"],
"dy1" -> PaddingLayer[{{0, 0}, {0, 0}, {1, 0}}, "Padding" -> "Fixed" ],
"dy2" -> PaddingLayer[{{0, 0}, {0, 0}, {0, 1}}, "Padding" -> "Fixed"],
"lossx" -> l2Loss, "lossy" -> l2Loss, "tot" -> TotalLayer[]|>,
{{"dx1", "dx2"} -> "lossx", {"dy1", "dy2"} -> "lossy",
{"lossx", "lossy"} -> "tot" -> NetPort["LossTV"]}]
定义一个函数,为任意内容和风格的图像创建最终的训练网络。该函数同时生成了一个随机初始图像:
createTransferNet[net_, content_Image, styleFeatSize_] := Module[{dims = Prepend[3]@Reverse@ImageDimensions[content]},NetGraph[<|"Image" -> ConstantArrayLayer["Array" -> RandomReal[{-0.1, 0.1}, dims]],"imageFeat" -> NetReplacePart[net, "Input" -> dims],"content" -> contentLoss,"style" -> styleLoss,"tv" -> tvLoss|>,{"Image" -> "imageFeat",{"imageFeat", NetPort["ContentFeature"]} -> "content",{"imageFeat", NetPort["StyleFeature"]} -> "style","Image" -> "tv"},"StyleFeature" -> styleFeatSize ] ]
定义一个 NetDecoder 来可视化预测的图像:
meanIm = NetExtract[featureNet, "Input"][["MeanImage"]]
{0.48502, 0.457957, 0.407604}
decoder = NetDecoder[{"Image", "MeanImage" -> meanIm}]
训练数据由从内容和风格图像中提取的特征组成。定义特征提取函数:
extractFeatures[img_] := NetReplacePart[featureNet, "Input" ->NetEncoder[{"Image", ImageDimensions[img],
"MeanImage" ->meanIm}]][img];
创建由单个内容和风格特征实例组成的训练集:
trainingdata = <|
"ContentFeature" -> {extractFeatures[contentImg]},
"StyleFeature" -> {extractFeatures[styleImg]}
|>
创建训练集,其输入维度对应于内容和人风格图像的维度:
net = createTransferNet[featureNet, contentImg,
Dimensions@First@trainingdata["StyleFeature"]];
在训练时,对三个损失函数进行加权,以确定内容和风格的相对重要性。对于不同内容和风格的图像,这些值可能需要更改。创建损失规范,定义由这三个损失组合而成的最终损失:
perPixel = 1/(3*Apply[Times, ImageDimensions[contentImg]]);lossSpec = {"LossContent" -> Scaled[6.*10^-5],
"LossStyle" -> Scaled[0.5*10^-14],
"LossTV" -> Scaled[20.*perPixel]};
用 NetTrain 优化图像,用 LearningRateMultipliers 冻结网络中除 ConstantArrayLayer 之外的所有参数。最好是在 GPU 上进行训练,因为用 CPU 进行训练可能要花一个小时才能得到好结果。可以用 计算->放弃计算 在任何时候终止训练:
trainedNet = NetTrain[net,
trainingdata, lossSpec,
LearningRateMultipliers -> {"Image" -> 1, _ -> None},
TrainingProgressReporting ->
Function[decoder[#Weights[{"Image", "Array"}]]],
MaxTrainingRounds -> 300, BatchSize -> 1,
Method -> {"ADAM", "InitialLearningRate" -> 0.05},
TargetDevice -> "GPU"
]
从训练过的网络中的 ConstantArrayLayer 提取最终图像:
decoder[NetExtract[trainedNet, {"Image", "Array"}]]
现代版《星夜》出现了......
你可以尝试其他美图噢!更多细节请点击“阅读原文”参见【Wolfram社区】的原文。
+
=