在艺术绘画的创做过程当中,人们经过将一张图片的内容和风格构成复杂的相互做用来产生独特的视觉体验。然而,所谓的艺术风格是一种抽象的难以定义的概念。所以,如何将一个图像的风格转换成另外一个图像的风格更是一个复杂抽象的问题。尤为是对于机器程序而言,解决一个定义模糊不清的问题是几乎不可行的。
在神经网络以前,图像风格迁移的程序采用的思路是:分析一种风格的图像,为这种风格创建一个数学统计模型;再改变要作迁移的图像使它的风格符合创建的模型。该种方法能够取得不一样的效果,可是有一个较大的缺陷:一个模型只可以实现一种图像风格的迁移。所以,基于传统方法的风格迁移的模型应用十分有限。
随着神经网络的发展,机器在某些视觉感知的关键领域,好比物体和人脸识别等有着接近于人类甚至超越人类的的表现。这里咱们要介绍一种基于深度神经网络的机器学习模型——卷积神经网络,它能够分离并结合任意图片的风格和内容,生成具备高感知品质的艺术图片。本文介绍一篇在2015年由 Gatys 等人发表的一篇文章_ A Neural Algorithm of Artistic Style_,该文章介绍了一种利用卷积神经网络进行图像风格迁移的算法。相比于传统的风格迁移的方法,该方法具备更好的普适性。
php
处理图像任务最有效的一种深度神经网络就是卷积神经网络。卷积神经网络由多个网络层组成的前馈神经网络,每一个网络层包含了许多用于处理视觉信息的计算单元(神经元)。每一层的计算单元能够被理解为一个图片过滤器的集合,每一层均可以提取图片的不一样的特定特征。咱们把给定层的输出称为特征图谱(Feature Map)——输入图像的不一样过滤版本。
当卷积神经网络用于物体识别时,随着网络的层次愈来愈深,网络层产生的物体特征信息愈来愈清晰。这意味着,沿着网络的层级结构,每个网络层的输出愈来愈关注于输入图片的实际内容而不是它具体的像素值。经过重构每一个网络层的特征图谱,咱们我能够可视化每一层所表达的关于输入图片的信息。从中能够看出,位于更高层的网络层可以根据物体及其在输入图像中的排列来捕获输入图像的高级内容而不包含具体的像素值信息。所以,咱们参考网络模型的高层结构做为图片的内容表示。
html
为了获取输入图片的风格表示,咱们使用一个被用来获取纹理特征的特征空间。该特征空间包含了特征图谱空间范围内不一样滤波器响应之间的相关性。经过多个层的特征相关性,咱们得到输入图像的静态多尺度表示,它可以捕获图片的纹理信息却不包含全局排列。
前端
本文的一个关键点是图片的内容表示和风格表示在卷积神经网络中是可分离的。也就是说,咱们能够独立地操纵这两种表示来产生新的有感知意义上的图片。为了证实这一观点,该论文展现了一些由不一样内容和风格的图片混合生成的合成图片,如图 2 所示。
这些图片是经过寻找一个同时匹配照片内容和对应的艺术风格的图片的方法而生成的。这些合成图片在保留原始照片的全局布置的同时,继承了各类艺术图片的不一样艺术风格。风格表示是一个多层次的表达,包括了神经网络结构的多个层次。当风格表示只包含了少许的低层结构,风格的就变得更加局部化,产生不一样的视觉效果。当风格表示由网络的高层结构表示时,图像的结构会在更大的范围内和这种风格匹配(图 2 最后一行),产生更加流畅持续的视觉体验。
python
实际上,图片的内容和风格是不可以被彻底分离的。当咱们合成图片时,咱们一般找不出一张可以匹配某个图片内容和另外一种图片风格的图片。在咱们合成图片的过程当中,咱们须要最小化的损失函数包含内容和风格,但它们是分开的。所以,咱们须要平滑地调整内容和风格的权重比例(图 3 的行坐标)。当损失函数分配在内容和风格的权重不一样时,合成产生的图片效果也彻底不同。咱们须要适当地调整内容表示和风格表示的权重比来产生具备视觉感染力的图片。是否可以找到合适的权重比是可否产生使人满意的图片的关键因素。
git
该论文中的实验结果,是以 VGG 网络为基础产生的。该实验使用的是由 16 个卷积层和 5 个池化层(VGG 19)组成的特征空间。因为该实验不要进行分类,咱们不须要使用全链接层。该模型是公开可用的,咱们能够在 Caffe 框架和 Keras 框架找到。该文做者实验发现使用平均池化比使用最大池化更容易获得使人满意的实验结果。github
在这里咱们参考后续的文章,使用 VGG 16 网络模型进行实验。该模型可以在不丢失图片精度的条件下,尽量地加快训练速度。同时,为了保留原始图片的结构细节(且让合成图片符合大多数人的审美),在这里咱们使用 'block2_conv2' 的输出做为图片的内容表示(原文采用的是'block4_conv2' )。而风格表示方面,咱们依旧采用论文中所述的纹理特征做为风格表示。
算法
在卷积神经网络(CNN)中,通常认为较低层的描述了图像的具体视觉特征(即纹理、颜色等),较高层的特征是较为抽象的图像内容描述。当要比较两幅图像的内容类似性的时候,咱们比较两幅图片在 CNN 网络中高层特征的类似性便可。本次实验中,咱们使用内容图片和合成图片对应网络层的特征图谱的欧氏距离来表示内容损失。
内容损失的计算公式**:**网络
参考代码:app
def content_loss(content, combination):
return backend.sum(backend.square(combination - content))
layer_features = layers['block2_conv2']
content_image_features = layer_features[0, :, :, :]
combination_features = layer_features[2, :, :, :]
loss += content_weight * content_loss(content_image_features,
combination_features)
复制代码
要比较两张图片的风格类似性,咱们须要比较它们在 CNN 网络中较低层特征的类似性。与内容损失不一样的是,咱们不能仅仅使用欧式距离来定义风格损失。CNN 的底层特征虽然在必定程度上包含了图像的风格特色,可是因为特征图谱的空间信息过于明显,直接计算欧氏距离会产生较大的偏差。所以,咱们须要在保留低层的视觉特征的同时消除空间信息,Gatys 提出了一个很是神奇的矩阵——Gram 矩阵。
Gram 矩阵:框架
# 定义 Gram 矩阵
def gram_matrix(x):
features = backend.batch_flatten(backend.permute_dimensions(x, (2, 0, 1)))
gram = backend.dot(features, backend.transpose(features))
return gram
# 计算总的风格损失
def style_loss(style, combination):
S = gram_matrix(style)
C = gram_matrix(combination)
channels = 3
size = height * width
return backend.sum(backend.square(S - C)) / (4. * (channels ** 2) * (size ** 2))
feature_layers = ['block1_conv2', 'block2_conv2',
'block3_conv3', 'block4_conv3',
'block5_conv3']
for layer_name in feature_layers:
layer_features = layers[layer_name]
style_features = layer_features[1, :, :, :]
combination_features = layer_features[2, :, :, :]
sl = style_loss(style_features, combination_features)
loss += (style_weight / len(feature_layers)) * sl
复制代码
为了获得使人满意的合成图片,咱们须要最小化上面定义的内容损失和风格损失。这里咱们定义了一个总损失函数,分别用
def total_variation_loss(x):
a = backend.square(x[:, :height-1, :width-1, :] - x[:, 1:, :width-1, :])
b = backend.square(x[:, :height-1, :width-1, :] - x[:, :height-1, 1:, :])
return backend.sum(backend.pow(a + b, 1.25))
loss += total_variation_weight * total_variation_loss(combination_image)
复制代码
实验中,咱们使用 'conv2_2' 层来计算内容损失而不是论文中用到的 'conv4_2';当咱们在计算图片的风格损失时,'conv1_1'、'conv2_1'、'conv3_1'、'conv4_1'、'conv5_1' 层的权重为
在本实验中,咱们使用 L-BFGS 算法来优化总损失函数。因为咱们使用的是梯度降低算法,咱们引入一个Evaluator 类——经过两个独立的函数 loss 和 grads,来计算损失和梯度。
参考代码:
# 定义梯度
grads = backend.gradients(loss, combination_image)
# 定义类
outputs = [loss],outputs += grads
f_outputs = backend.function([combination_image], outputs)
def eval_loss_and_grads(x):
x = x.reshape((1, height, width, 3))
outs = f_outputs([x]),loss_value = outs[0]
grad_values = outs[1].flatten().astype('float64')
return loss_value, grad_values
class Evaluator(object):
def __init__(self):
self.loss_value = None,self.grads_values = None
def loss(self, x):
assert self.loss_value is None
loss_value, grad_values = eval_loss_and_grads(x)
self.loss_value = loss_value,self.grad_values = grad_values
return self.loss_value
def grads(self, x):
assert self.loss_value is not None
grad_values = np.copy(self.grad_values)
self.loss_value = None,self.grad_values = None
return grad_values
evaluator = Evaluator()
复制代码
对于合成图片,咱们将其初始化为一个随机有效的像素的集合。最后经过 L-BFGS 算法来最小化损失函数。从实验结果能够看出,当迭代进行至 10 次后,损失就再也不显著减小。
参考代码:
x = np.random.uniform(0, 255, (1, height, width, 3)) - 128.
iterations = 10
for i in range(iterations):
print('Start of iteration', i)
start_time = time.time()
x, min_val, info = fmin_l_bfgs_b(evaluator.loss, x.flatten(),
fprime=evaluator.grads, maxfun=20)
print('Current loss value:', min_val)
end_time = time.time()
print('Iteration %d completed in %ds' % (i, end_time - start_time))
复制代码
最终咱们合成的图片效果展现以下:
本论文首次提出了运用神经网络模型来实现风格迁移:使用卷积神经网络将一张图片中的内容和风格进行了分离和提取;而且定义了如何来计算图片内容类似性和风格类似性,经过最小化内容损失和风格损失来获得使人满意的结果。
相比传统的风格迁移模型,利用卷积神经网络来提取图片的内容和风格具备重大的意义 ,它使得模型具备更加普遍的通用性,而不须要为每一种风格的图片创建一个数学模型。可是该方法也有一些不足之处:内容损失使用特征图谱的欧氏距离来表示的效果并非十分的理想,好比一个张图片通过一小段平移以后在视觉效果上与原图几乎没有差异,而此时使用像素点间的差值来计算损失会产生较大的偏差;同时该模型优化的是合成图片
Mo(网址:momodel.cn)是一个支持 Python 的人工智能在线建模平台,能帮助你快速开发、训练并部署模型。
Mo 人工智能俱乐部 是由网站的研发与产品设计团队发起、致力于下降人工智能开发与使用门槛的俱乐部。团队具有大数据处理分析、可视化与数据建模经验,已承担多领域智能项目,具有从底层到前端的全线设计开发能力。主要研究方向为大数据管理分析与人工智能技术,并以此来促进数据驱动的科学研究。
目前俱乐部每周六在杭州举办以机器学习为主题的线下技术沙龙活动,不按期进行论文分享与学术交流。但愿能汇聚来自各行各业对人工智能感兴趣的朋友,不断交流共同成长,推进人工智能民主化、应用普及化。