Image Style Transfer Using Convolutional Nerual Networks
最后更新于
最后更新于
tags: Nueral Style Transfer
Leon A.Gatys是最早使用CNN做图像风格迁移的先驱之一,这篇文章还有另外一个版本,应该是它投到CVPR之前的预印版,两篇文章内容基本相同。
我们知道在训练CNN分类器时,接近输入层的Feature Map包含更多的图像的纹理等细节信息,而接近输出层的Feature Map则包含更多的内容信息。这个特征的原理可以通过我们在残差网络中介绍的数据处理不等式(DPI)解释:越接近输入层的Feature Map经过的处理(卷积和池化)越少,则这时候损失的图像信息还不会很多。随着网络层数的加深,图像经过的处理也会增多,根据DPI中每次处理信息会减少的原理,靠后的Feature Map则包含的输入图像的信息是不会多余其之前的Feature Map的;同理当我们使用标签值进行参数更新时,越接近损失层的Feature Map则会包含越多的图像标签(内容)信息,越远则包含越少的内容信息。这篇论文正是利用了CNN的天然特征实现的图像风格迁移的。
具体的讲,当我们要在图片(content)的内容之上应用图片(style)的风格时,我们会使用梯度下降等算法更新目标图像(target)的内容,使其在较浅的层有和图片类似的响应值,同时在较深的层和也有类似的响应,这样就保证了和有类似的风格而且和有类似的内容,这样生成的图片就是我们要得到的风格迁移的图片。如图1所示。
在Keras官方源码中,作者提供了神经风格迁移的源码,这里对算法的讲解将结合源码进行分析。
图1:图像风格迁移效果图
IST的原理基于上面提到的网络的不同层会响应不同的类型特征的特点实现的。给定一个训练好的网络,源码中使用的是VGG19 ,下面是源码第142-143行,因此在运行该源码时如果你之前没有下载过训练好的VGG19模型文件,第一次运行会有下载该文件的过程,文件名为'vgg19_weights_tf_dim_ordering_tf_kernels_notop.h5'。
论文中有两点在源码中并没有体现,一个是对权值进行了归一化,使用的方法是我们之前介绍的Weight Normalization,另外一个是使用平均池化代替最大池化,使用了这两点的话会有更快的收敛速度。
图2有三个部分,最左侧的输入是风格图片,将其输入到训练好的VGG19中,会得到一批它对应的Feature Map;最右侧则是内容图片,它也会输入到这个网络中得到它对应的Feature Map;中间是目标图片,它的初始值是白噪音图片,它的值会通过SGD进行更新,SGD的损失函数时通过在这个网络中得到的Feature Map和的Feature Map以及的Feature Map计算得到的。图2中所有的细节会在后面的章节中进行介绍。
图2:图像风格迁移算法流程图
传统的深度学习方法是根据输入数据更新网络的权值。而IST的算法是固定网络的参数,更新输入的数据。固定权值更新数据还有几个经典案例,例如材质学习,卷积核可视化等。
通过对142行的model
的遍历我们可以得到每一层的Feature Map的名字以及内容,然后将其保存在字典中,见147行。
上式中的content_weight
是内容损失函数的比重,源码中给出的值是0.025,内容损失函数的定义见185-186行:
另外一点和内容表示不同的是,风格表示使用了每个block的第一个卷积来计算损失函数,作者认为这种方式得到的纹理特征更为光滑,因为仅仅使用底层Feature Map得到的图像较为精细但是比较粗糙,而高层得到的图像则含有更多的内容信息,损失了一些纹理信息,但他的材质更为光滑。所以,综合了所有层的样式表示的损失函数为:
上面的更新同样使用SGD。
下面我们继续来学习源码,从源码的214-223行我们可以看出样式表示使用了5个block的Feature Map:
从上面的代码中我们可以看出,样式表示使用了feature_layers
中所包含的Feature Map,并且最后loss的计算把它们进行了相加。第221行的style_loss
的定义见源码的171-178行:
从174-175行我们可以看出损失函数的计算使用的是两个Feature Map的Gram矩阵,Gram矩阵的定义见155-162行:
第158或者160行的batch_flatten
验证了Feature Map要先展开成向量,第161行则是Gram矩阵的计算公式。
还有一些超餐在配置文件中进行了指定,style_weight
和total_variation_weight
的默认值都是1。
下面继续学习这一部分的源码。在第287-288行的fmin_l_bfgs_b
说明了计算梯度使用了L-BFGS算法,它是scipy提供:
fmin_l_bfgs_b
是scipy包中一个函数。第一个参数是定义的损失函数,第二个参数是输入数据,fprime
通常用于计算第一个损失函数的梯度,maxfun
是函数执行的次数。它的第一个返回值是更新之后的x的值,这里使用了递归的方式反复更新x,第二个返回值是损失值。
287行的损失函数定义在264-269行:
其中最重要的函数是eval_loss_and_grads()
函数,它定义在了237-248行:
图像风格迁移是一个非常好玩但是无法对齐效果量化的算法,我们可以得到和一些著名画家风格看起来非常类似的画作,但是很难从数学的角度去衡量一个画作的风格,得出的结论是非常主观的。但是算法的设计动机是出于CNN的底层Feature Map接近图像纹理而高层Feature Map接近图像内容的天然特性,也是对神经网络这个黑盒子从另外一个角度给与了解释。IST产生的结果非常有趣,由此诞生了一批商用的软件,例如Prisma等。
IST如果能迁移到音频领域也许会有帮助,例如在TTS中如果可以将合成的语音的内容应用到真实人类语音的风格上,这样也许可以得到更为平滑的语音。或者如果我们将音频内容应用到某个人说话的风格中,也许我们可以得到和这个人说话风格非常类似的音频输出。
算法另外一个缺点是对噪音比较敏感,尤其是当参与合成的风格图片和内容图片都是真实照片的时候。
内容表示是图2中右侧的两个分支所示的过程。我们先看最右侧,输入VGG19中,我们提取其在第四个block中第二层的Feature Map,表示为conv42(源码中提取的是conv5_2)。假设其层数为,是Feature Map的数量,也就是通道数,是Feature Map的像素点的个数。那么我们得到Feature Map 可以表示为,$$F^l{ij}lij\vec{x}P^l$$。
如果的和的非常接近,那么我们可以认为和在内容上比较接近,因为越接近输出的层包含有越多的内容信息。这里我们可以定义IST的内容损失函数为:
下面我们来看一下源码,上面142行的input_tensor
的是由一次拼接而成的,见136-138行。
这样我们可以根据关键字提取我们想要的Feature Map,例如我们提取两个图像在conv5_2处的Feature Map (源码中的base_image_features
)和源码中的combination_features
),然后使用这两个Feature Map计算损失值,见208-212行:
有了损失函数的定义之后,我们便可以根据损失函数的值计算其关于的梯度值,从而实现从后向前的梯度更新。
如果损失函数只包含内容损失,当模型收敛时,我们得到的应该非常接近的内容。但是它很难还原到和一模一样,因为即使损失值为0时,我们得到的值也有多种的形式。
为什么说具有的内容呢,因为当经过VGG19的处理后,它的conv5_2层的输出了几乎一样,而较深的层具有较高的内容信息,这也就说明了和具有非常类似的内容信息。
风格表示的计算过程是图2的左侧和中间两个分支。和计算相同,我们将输入到模型中便可得到它对应的Feature Map 。不同于内容表示的直接运算,风格表示使用的是Feature Map展开成1维向量的Gram矩阵的形式。使用Gram矩阵的原因是因为考虑到纹理特征是和图像的具体位置没有关系的,所以通过打乱纹理的位置信息来保证这个特征,Gram矩阵的定义如下:
其中是的Gram矩阵和的Gram矩阵的均方误差:
它关于的梯度的计算方式为:
明白了如何计算内容损失函数和风格损失函数之后,整个风格迁移任务的损失函数就是两个损失值得加权和:
其中和就是我们在1.2节和1.3节介绍的content_weight
和total_variation_weight
。通过调整这两个超参数的值我们可以设置生成的图像更偏向于的内容还是的风格。的值用来更新输入图像的内容,作者推荐使用L-BFGS更新梯度。
另外对于的初始化,论文中推荐使用白噪音进行初始化,这样虽然计算的时间要更长一些,但是得到的图像的样式具有更强的随机性。而论文使用的是使用初始化,这样得到的生成图像更加稳定。
其中x
的初始化使用的是内容图片:
其中f_outputs()
是实例化的Keras函数,作用是使用梯度更新的内容,见226-234行: