用Keras实现图片风格迁移的算法详解

【摘要】详细分析了基于Keras实现图片风格迁移的生成算法,提供了GUI实现工具;同时,比较了生成式深度学习跟常规深度学习的差异,拓展对神经网络的认识。

 

一. 图片风格迁移生成算法详解

该实现算法及代码,主要取自Keras 之父写的 《Python 深度学习》,有推荐价值。在此基础上,本人开发了一个用tkinter写的实用小工具,通过设置目标图片,风格图片,按开始转换,即可生成风格迁移后的新图片。

用Keras实现图片风格迁移的算法详解_第1张图片

源代码可通过以下Github地址获取

https://github.com/EdwinZhang1970/Python/tree/master/Netural%20Style%20Transfer

1. 算法要达到的目标

  1. 提供目标图片风格图片,产生一个新的生成图片
  2. 生成图片需要尽量保留目标图片的内容;同时应用风格图片的风格

2. 如何定义和获得图片的内容特征值,风格特征值

  1. 根据对深度学习神经网络模型的研究分析,模型网络更靠底部(靠近输入端)的层激活包含了图片的局部,具体信息;更靠顶部(靠近输出端)的层激活,包含了图片的全局,抽象信息。本例算法采用的是VGG19模型,其模型结构是

用Keras实现图片风格迁移的算法详解_第2张图片

2. 取图片在模型中的更靠顶部的层激活信息,作为图片的内容特征值,因为是全局的,因此,可以只取一层就可以了。本例算法取的是conv5_2, 代码中为block5_conv2

content_layer = 'block5_conv2'

3. 取图片在模型中的更靠底部的层激活信息,作为图片的风格特征值,因为是局部的,因此,需要多取几层,以获得更全面的风格特征信息。本例中取的是 conv1_1, conv2_1, conv3_1, conv4_1, conv5_1, 代码中为block1_conv1, block2_conv1, block3_conv1, block4_conv1, block5_conv1

style_layers = ['block1_conv1',
                'block2_conv1',
                'block3_conv1',
                'block4_conv1',
                'block5_conv1']

 

3. 如何让生成图片跟目标图片保持内容相似,跟风格图片保持风格相似

  1. 让生成图片和目标图片,在靠近顶部的所选层的激活信息(内容信息)保持相似,从而做到生成图片和目标图片的内容保持相识。定义的内容损失函数(损失值越小,相似度越高)。直接用层激活的内容进行比较,获得差异损失值
def content_loss(base, combination):
    return K.sum(K.square(combination - base))

    2. 让生成图片和风格图片,在靠近底部依次向上所选的多个层激活(风格信息),让它们在这几个层保持类似的同层相互关系(不是层的具体内容,而是该层元素之间的相互关系),从而做到生成图片与风格图片的风格一致. 

   通过 gram_matrix()函数,可计算获得输入矩阵的格拉姆矩阵,格拉姆矩阵是原始矩阵的相互关系的映射

def gram_matrix(x):
    features = K.batch_flatten(K.permute_dimensions(x, (2, 0, 1)))
    gram = K.dot(features, K.transpose(features))
    return gram

然后以矩阵相互关系,图片的尺寸信息为参数,计算损失值(图片尺寸越大,向量值越多,合计值越大, 除以尺寸信息维度,就有可比性了)

def style_loss(style, combination, img_height, img_width):
    S = gram_matrix(style)
    C = gram_matrix(combination)
    channels = 3
    size = img_height * img_width
    return K.sum(K.square(S - C)) / (4. * (channels ** 2) * (size ** 2))

3. 提高生成图片的空间连续性,让生成图片本身更像完整的图片,而不是单个像素点的随意堆积

def total_variation_loss(x, img_height, img_width):
    a = K.square(
        x[:, :img_height - 1, :img_width - 1, :] - x[:, 1:, :img_width - 1, :])
    b = K.square(
        x[:, :img_height - 1, :img_width - 1, :] - x[:, :img_height - 1, 1:, :])
    return K.sum(K.pow(a + b, 1.25))

这段代码咋看很复杂,细看还是很简单的。简化a, 只需抽取变量a中有变化的第1维进行分析

K.square(x[:img_height-1] – x[1:])
= (x[0]-x[1])**2 +(x[1]-x[2])**2 + … + (x[img_height-1]-x[img_height])**2

可以看出,公式就是图片中,所有点与其相邻点的距离和(不包括最后一条边)。使相邻点的之间的差异损失越小,就达到了使图片像素点越连续的目的。

 4. 总的损失值,是以上三种损失的加权合计结果。本例设置的缺省权重系数为

total_variation_weight = 1e-4
content_weight = 0.025
style_weight = 1.

loss += content_weight * content_loss(target_image_features, combination_features)

for layer_name in style_layers:
    ... ...
    sl = style_loss(style_reference_features, combination_features, img_height, img_width)
    loss += (style_weight / len(style_layers)) * sl

loss += total_variation_weight * total_variation_loss(combination_image, img_height, img_width)

其中,style_weigth表示的是总的风格权重,它将会被平均分配到各个风格层参与计算。

调整权重,可以获得不同的生成结果。比如,加大content_weight, 将会使生成图看起来更像目标图。

 

4. 如何进行学习训练

1. 获得目标图片,风格图片的常量信息(因此整个处理过程,赋值后不会再改变)

target_image = K.constant(preprocess_image(target_image_path, img_height, img_width))

style_reference_image = K.constant(preprocess_image(style_reference_image_path, img_height, img_width))

2. 设置生成图片的占位符。(整个处理过程,都在不断地调整特征值),初始值为生成图片的数据

combination_image = K.placeholder((1, img_height, img_width, 3))


3. 装载预训练的VGG19模型及在imagenet数据集上获得的权重系数

model = vgg19.VGG19(input_tensor=input_tensor,
                    weights='imagenet',
                    include_top=False)

4. 利用预训练模型,提取目标图片,风格图片,生成图片的各层激活值,并根据损失函数的定义,计算损失值和梯度


5. 利用优化函数fmin_l_bfgs_b()进行优化,获得优化结果,优化结果中包括生成图的特征数据。对生成图特征数据进行图片还原处理,即可获得可显示的图片效果。每一轮优化计算都会有一个结果,本文提供的工具将实时显示每轮优化的图片效果。
最核心的大功臣是L-BFGS-B优化算法,感谢科学家设计了各种优化算法,感谢Scipy的大师们实现了这些算法。

 

二. 生成式深度学习跟常规深度学习的比较

1.    应用场景
a)     常规:根据单一样本的特征值,预测其自身的标签值,获得回归,分类信息。实现对现有模式和规则的提取和使用,完成对已生成的样本数据的分析。
b)    此例:综合分析多个相关样本的特征值,生成新的样本特征值。在现有模式和规则的基础上,进行融合,获得新的模式和规则,生成创新的样本数据。


2.    样本特征值的获取
a)    常规:对获得的文本,图片,音频,视频,测量,传感等原始数据,进行清理,数值化,浮点化,归一化等处理,只需要获得数据的基本特征值,然后转成可输入计算模型的向量数据,输入模型进行计算。
b)    此例:提取原始向量数据在预训练模型中的不同层激活输出,并对这些层激活进行建模处理,获得样本的内容特征,纹理风格特征,然后以这些提取的高级特征值为基础,完成接下来的模型优化算法运算。


3.    损失函数的设计
a)    常规:统计样本的计算标签值与实际标签值的差异,若标签值为回归值,可用mse, mae;若标签为单标签二分类 多标签多分类,可用 binary_crossentropy;  若标签值为单标签多分类,可用categorical_crossentropy
b)    此例:损失函数 = 生成图片跟目标图片的内容损失 * 权重 + 生成图片跟风格图片的风格损失 * 权重  +  生成图片本身的像素化损失 * 权重。 完全是特定场景下的独有损失函数。


4.    优化算法的设计
a)    常规:基于梯度下降的算法及改良算法:SGD, RMSProp, Adagrad, Adam , 
b)    此例:L-BFGS-B算法,适应于有约束的多元函数优化算法。注:scipy中的optimize子包中提供了常用的最优化算法函数实现


5.    模型及学习成果的设计
a)    常规:利用CNN提取样本空间信息,利用RNN提取样本时序信息,利用RPN推荐样本区域位置信息,再利用DNN进行特征值与标签值的线性回归映射等,学习获得使损失函数值最小的模型权重系数值
b)    此例:利用现有的预训练模型提取特征值,并进行建模处理,学习获得使损失函数值最小的样本数据特征值


6.    批量喂入模型的目的(小技巧)
a)    常规:一个批次中的样本是独立的,序号随机,相互没有关系,在训练时,大家共同为减少损失函数值做贡献;在预测时,各顾各计算自己的标签值
b)    此例:借用批次概念,将3个相关样本数据,按规定的次序,放在一个批次中同时计算(而不是分3次分别计算),然后再根据规定的序号取出相应的输出值,用于后续的处理。


7.    计算能力的影响(小感触)
a)    常规:现代神经网络技术的应用和发展,得益于算法改进,计算机算能提高,大数据获得。因此,人工智能企业的价值评估是:研发团队的人才论文情况,看算法能力;GPU服务器的超算集群情况,看计算能力;标注数据的投入情况,看数据能力。
b)    此例:我用64G内存的服务器,一个计算轮次为3~4秒;用8G内存的笔记本,一个计算轮次是800多秒。没有好的计算机,看来不行。

小结:
        深度学习框架提供了大量常规算法模块,使我们可以像搭积木一样,快速搭建神经网络来满足一些常用场景的应用。而更多的实际场景,将需要根据具体的原始业务数据,提取特定的高级样本特征,设计各种特定的损失函数,优化算法,以获得业务所需要的标签数据或特征数据,满足特定场景的业务需要和性能需要。

其中的每个步骤,都是算法在起主导作用。利用常用算法搭积木,设计解决方案,是我们学习的基础。同时,更多的应用场景,要求我们根据具体数据和需求,选用,设计和创建特定算法,以满足产业级应用的需要。

 

 

 

你可能感兴趣的:(Python,深度学习,生成式深度学习)