[神经风格迁移]用卷积神经网络将艺术照进现实(Keras版本,每行代码都解析的那种噢)
神经风格迁移是生成式深度学习中较为奇妙的项目之一,可以通过卷积神经网络模型中某一层(或多层)所学习到的输出(学习到的特征)进行整合实现。如果想更深入的了解卷积神经网络每层的输出的内容,可以看看我的上一篇博客(内容通俗易懂)“看得见的”卷积神经网络(图文并茂+代码解读)(卷积神经网络可视化)_新手村霸的博客-CSDN博客。接下来就让我们走进卷积神经网络的艺术世界吧
目录
1.了解构造损失函数的思路(就等于了解了大致流程):
1.1内容损失:
1.2 风格损失:
阶段总结:
2用Keras实现神经风格迁移:
2.1创建一个网络,他能同时计算(获得)风格参考图像, 目标图像,生成图像的VGG19对应的层激活:
2.1.1定义初始化变量
2.1.2两个处理数据的辅助函数:
2.1.3加载预训练的VGG19模型,输入三张准备好的图片:
2.2我们用(1)中获得的激活来定义损失函数(损失函数的大致内容就和上面描述的一样)。我们的目标就是将其最小化:
2.2.1内容损失:
2.2.2风格损失:
2.2.3总变差损失(选作):
2.2.4将所有的损失合并:
2.3设置梯度下降来将损失函数最小化(快要结束啦):
2.4主程序,总流程(拼接代码,没有难点):
神经风格迁移:将参考图像的风格应用于目标图像,同时保留目标图像的内容。简单的一句话,进行这篇博客的总结。如下图所示(相信我,你做出来的会好看很多哈哈哈):
生成的图片(最右侧)中保留了第一张图片的内容和第二张图片的风格。在这里的风格所指的就是图片中的纹理、颜色、和视觉图案,内容则是指图像中的高级宏伟结构。接下来就让我们看看如何通过卷积神经网络实现这一奇妙的创作吧。
1.了解构造损失函数的思路(就等于了解了大致流程):
其实神经风格迁移的背后的相关概念和深度学习的核心算法是一样的,我们需要定义一个损失函数,让后将其最小化(通过梯度下降的方式,如果想深入了解梯度下降可以参考我的这篇博客梯度下降法原理解析(大白话+公式推理)_新手村霸的博客-CSDN博客)。我们的目标是保存原始图像的内容和参考风格图像中的风格。那么通过目标我们就能得出如下损失函数的基本概念:
loss = distance(style(reference_image) - style(generated_image)) + distance(content(original_image) - content(generated_image))
简单解释就是 损失 = 风格(参考风格图片—生成图片) + 内容(原始图片— 生成图片)。
1.1内容损失:
我们知道在网络中更靠近顶层的输出(激活)包含图像更加全局、更加抽象的信息(如羽毛,眼睛,房屋等)。这也就对应上了原始图片上的内容信息。因此,内容损失很好的选择就是原始图像和生成图像在网络中较为顶层的输出(激活)的L2范数(也就是用原始图像在较为顶层中的输出和生成图像在较为顶层中的输出进行比较)。
1.2 风格损失:
相反,由纹理、颜色、视觉图案所表示的风格就在网络中较为底层的输出中。但是风格在网络中分布的较为广泛(风格是多尺度的),我们单单选择一层是不够的(内容损失就可以选择一层在稍微顶层的层能实现)。对于风格损失,在这里我们将使用层激活的格拉姆矩阵,即特征图的内积(如果不了解也没关系,后面代码会用大白话解析,这里混个耳熟即可)。这个内积可以被理解为一层中特征图之间的映射关系,也就是抓住了图片特征的规律(就是我们想索取的风格),我们可以在它身上找到我们想要的纹理外观。
阶段总结:
在网络的较顶层中找到要保留的原始图片中的内容,在较低层和较高层(主要是低层)找寻我们要保留的风格图像中的风格。
2用Keras实现神经风格迁移:
我们使用VGG19(notop版)来简单实现神经风格迁移的操作。基本思路如下,这也是接下来文章的主要结构:
(1)创建一个网络,他能同时计算(获得)风格参考图像, 目标图像,生成图像的VGG19对应的层激活。
(2)我们用(1)中获得的激活来定义损失函数(损失函数的大致内容就和上面描述的一样)。我们的目标就是将其最小化。
(3)设置梯度下降来将损失函数最小化。
2.1创建一个网络,他能同时计算(获得)风格参考图像, 目标图像,生成图像的VGG19对应的层激活:
2.1.1定义初始化变量:
代码如下:
from keras.preprocessing.image import load_img, img_to_arraytarget_image_path = '/media/hjl/Ubuntu 20.0/zzzz.jpg' # 原始图像路径(内容来源)
style_reference_image_path = '/media/hjl/Ubuntu 20.0/lll.jpg' # 参考风格图像width, height = load_img(target_image_path).size
img_height = 400 # 固定(统一)图像高
img_width = int(width * img_height / height) # 通过固定的高按比例缩放宽
2.1.2两个处理数据的辅助函数:
在导入VGG19网络之前我们来定义一些辅助函数,用于对输入VGG19网络的图片数据作预处理(图像变张量),和VGG19输出的数据进行后处理(张量变图像)代码如下:
import numpy as np
from keras.applications import vgg19# 将图像转化为VGG19可以识别的数据张量:
def preprocess_image(image_path):# 导入图片img = load_img(image_path, target_size=(img_height, img_width)) # 将图片变为张量数据img = img_to_array(img) # 因为Keras中VGG19处理图片数据是4维张量,所以我们要多加一个维度在最前面,也就是输入数据的批量img = np.expand_dims(img,axis=0) # 这里经过两个处理,1.将数据的RGB转变为BGR。2.每一层减去像素(BGR)自身的平均数mean(为了方便处理)img = vgg19.preprocess_input(img) return img# 将输出数据转化为图片(和上面的操作相反即可)
def deprocess_image(x):# 以下三步是分别加回像素(BGR)的平均值x[:, :, 0] += 103.939 x[:, :, 1] += 116.779x[:, :, 2] += 123.68# 这一步就是将BGR转换回RGBx = x[:, :, ::-1] # 控制数值在0-255之间x = np.clip(x, 0, 255).astype('uint8') return x
2.1.3加载预训练的VGG19模型,输入三张准备好的图片:
这里先做个简单的说明,因为输入的目标图像(内容来源)和风格参考图像(风格来源)在网络中就像常量一样,我们只负责提取,不用对他们作出改变。所以存放在K.constant()中,而生成的图片(艺术照)则是在梯度下降的过程中不断改善,是变化的。所以我们将它存放在K.placeholder()中(一个可变的生成占位符)。代码如下:
from keras import backend as K
# 存放目标图片
target_image = K.constant(preprocess_image(target_image_path)) # 存放风格图片
style_reference_image = K.constant(preprocess_image(style_reference_image_path))# 存放生成图片(艺术照)
combination_image = K.placeholder((1, img_height, img_width, 3))# 我们将三组张量组成的批量放在一起输入VGG19网络,注意放入的顺序,后面会用到哈。
input_tensor = K.concatenate([target_image,style_reference_image,combination_image], axis=0)model = vgg19.VGG19(input_tensor=input_tensor,weights='imagenet',include_top=False)print('model.loaded.')
这样我们就完成了风格迁移的第一大步啦!都是比较常规的操作。
2.2我们用(1)中获得的激活来定义损失函数(损失函数的大致内容就和上面描述的一样)。我们的目标就是将其最小化:
2.2.1内容损失:
这种方式只是一种输入损失函数比较好的表示方式(不用纠结为什么这么做,是比较通用的表示方式)。
def content_loss(base, combination):return K.sum(K.square(combination - base)) # 将张量对应的元素求平方再加起来
2.2.2风格损失:
这里就会用到刚才提到的格拉姆矩阵了,这里提前解释一下哈(大白话), 先了解以下这三个函数(很好理解)
(1)K.permute_dimensions(x, (2, 0, 1)):假设x是一个张量,那么(2, 0,1)对应的就是这个张量要改变成的索引位置,例如:x的张量形状为(14,14,512)就变为(512, 14, 14),将索引为2的放到最前面。
(2)K.batch_flatten(x):这里就是将x扁平化的操作,将x拍扁成2阶张量。比如x的形状为(512,14,14),就变成(512,14*14)。
(3)K.transpose(x):求x张量的转置
如果了解了上面的函数,就知道格拉姆矩阵的求法啦~,接下来上代码:
# 这个就是格拉姆矩阵的求法啦,是不是看完上面的函数就明白啦
def gram_maxtrix(x):features = K.batch_flatten(K.permute_dimensions(x, (2, 0, 1)))gram = K.dot(features, K.transpose(features))return gramdef style_loss(style, combination):# 风格图像的格拉姆矩阵S = gram_maxtrix(style)# 生成艺术照的格拉姆矩阵C = gram_maxtrix(combination)# RGB三色通道channels = 3size = img_height * img_width# 这个返回值是这篇论文内结论,没有给出推倒,有可能是论文作者的经验或者尝试得到的最佳风格损失返回值return K.sum(K.square(S - C)) / (4. * (channels ** 2) * (size ** 2))
2.2.3总变差损失(选作):
这个损失在之前没有提到,因为这个损失的目的是促使生成的艺术照更具有空间连续性(也就是更丝滑),算是锦上添花的操作吧。可以简单了解一下哈,也不复杂,一张辅助图片就看懂啦。
def total_variation_loss(x):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))
也就是将输入的张量x 进行上图的运算。这里其实就是相邻像素之间的相减发现了吗?只要将这个损失减小,那么相邻两个像素之间的变化就减小,那么也就变化的不会那么突兀啦。
2.2.4将所有的损失合并:
代码比较长,但是很好理解。我会每一步都做解析的。
# 首先将每一层的名字和对应的输出组成字典的形式
outputs_dict = dict([(layer.name, layer.output) for layer in model.layers])# 这是内容损失选择的层,较为顶层
content_layer = 'block5_conv2'# 这是风格损失选择的层
style_layers = ['block1_conv1','block2_conv1','block3_conv1','block4_conv1','block5_conv1']# 刚才选作的总变差损失权重
total_variation_weight = 1e-4
# 风格损失权重
style_weight = 1.
# 内容损失权重
content_weight = 0.025# 初始化loss为0
loss = K.variable(0.)# 以下几行代码是将内容损失添加到loss之中
layer_features = outputs_dict[content_layer]
# 这里的[0, :, :, :]就是之前上文说要注意放入输入数据顺序的地方,0号索引对应的就是target_image的内容
target_image_features = layer_features[0, :, :, :]
# 同理
combination_features = layer_features[2, :, :, :]
loss = loss + content_weight * content_loss(target_image_features, combination_features)# 接下来几行代码就是将风格损失放入loss中,过程和上面的内容损失是一样的
for layer_name in style_layers:layer_features = outputs_dict[layer_name]style_reference_features = layer_features[1, :, :, :]combination_features = layer_features[2, :, :, :]sl = style_loss(style_reference_features, combination_features)loss = loss + (style_weight / len(style_layers)) * sl# 这里是将总变差损失放入loss中
loss = loss + total_variation_loss(combination_image) * total_variation_weight
终于完成第二大步啦各位,希望各位能耐心看。通过代码解读后是很好理解的啦。
2.3设置梯度下降来将损失函数最小化(快要结束啦):
这里我们使用L-BFGS算法进行优化(论文中就是用这个)。但是它有两个小小的约束:
(1)它需要将损失函数值和梯度值作为两个单独的函数传入(但是按照Keras习惯性操作是K.function(x,[loss, grads])。)
(2)它只能用于展平的向量,但是我们的数据是三维的图像数组。
这两个问题我们逐一解决。
(1)用flatten()函数展平输入数据解决第一个问题啦。
(2)我们将自己创建一个python类,取名为Evaluator。这个类能实现同时计算loss和grad,然后就能一起传入L-BFGS算法进行优化啦
接下来我们创建自己的Evaluator类,代码如下:
import tensorflow as tf
# 这行代码是因为我直接使用gradients函数会报错,如果各位也会再加上吧
tf.compat.v1.disable_eager_execution()# 创建grads梯度值
grads = K.gradients(loss, combination_image)[0]# 将损失值和梯度值相关联并创建函数(输入combination_image,返回loss, grads)
fetch_loss_and_grads = K.function([combination_image], [loss, grads])# 创建类
class Evaluator(object):def __init__(self):self.loss_value = Noneself. grad_values = None# 计算loss和graddef loss(self, x):assert self.loss_value is None# 先将展平的数据还原,用于计算loss和gradsx = x.reshape((1, img_height, img_width, 3))outs = fetch_loss_and_grads([x])loss_value = outs[0]#将grad展平,因为输入数据x是三维所以梯度也是三维的,要展平才能输入L-BFGSgrad_values = outs[1].flatten().astype('float64')self.loss_value = loss_valueself.grad_values = grad_valuesreturn self.loss_value# 这里是缓存这一轮的grad,下面使用时就明白了def grads(self, x):assert self.loss_value is not Nonegrad_values = np.copy(self.grad_values)# 这里改为none说明只记录当前轮次self.loss_value = Noneself.grad_values = Nonereturn grad_values#创建实例
evaluator = Evaluator()
最后一步啦,写主程序,就是之前的代码进行拼接。也是体现流程的地方啦
2.4主程序,总流程(拼接代码,没有难点):
这里我们每轮迭代会进行20次梯度上升,所以最后会生成20张艺术照。
# 导入L-BFGS
from scipy.optimize import fmin_l_bfgs_b
# 这里我使用SciPy会报错,所以我使用这个库
import imageio
import time# 这个是生成艺术照的名字的开头
result_prefix = 'My_result'
# 进行20次梯度上升
iterations = 20# 图片预处理,这个就是之前创建的辅助函数之一啦
x = preprocess_image(target_image_path)# 展平,为了输入L-BFGS中
x = x.flatten()for i in range(iterations):print('Start of iterations', i)start_time = time.time()# 这里就用到我们创建的类啦,看到这里也就明白为什么要缓存grad了x, min_val, info = fmin_l_bfgs_b(evaluator.loss, x, fprime=evaluator.grads,maxfun = 20)print('Current loss value:', min_val)# 将输出数据变换为图片形状的张量img = x.copy().reshape((img_height, img_width, 3))#这里又是我们的辅助函数啦img = deprocess_image(img)fname = result_prefix + '%d_.png' % iimageio.imwrite(fname, img)end_time = time.time()print(i, end_time - start_time)
好啦,到这里本篇博客就结束啦。希望看到这里的你能亲自动手实验一下哈。图片选的好得到的艺术照片也就越好看哈哈哈。你可以尝试改变我选取的风格的网络对应的层,也就是style_layers,会得到不同的效果。这里比较建议使用GPU(CPU的话大概要1个小时了都,生成20张图片的话)。
希望这篇博客能给你带来收获哈,期待你的一键三连哈~
[神经风格迁移]用卷积神经网络将艺术照进现实(Keras版本,每行代码都解析的那种噢)相关推荐
- pythonturtle艺术字_字体风格迁移,卷积神经网络打败艺术字,生成最美汉字
曾几何时,小学的我们上机课时最喜欢摆弄的就是 word 的艺术字,丑陋的效果并不能阻挡我们在每个角落塞进七彩的字体....... "七彩"的艺术字 但是时代不同了,我们现在已经有了 ...
- python图片风格迁移毕设_神经风格迁移是如何运作的概述及Python实现
神经风格迁移是如何运作的概述及Python实现 作者:PHPYuan 时间:2019-03-26 03:40:37 深度学习可以捕获一个图像的内容并将其与另一个图像的风格相结合,这种技术称为神经风格迁 ...
- Stanford CS230深度学习(六)目标检测、人脸识别和神经风格迁移
在CS230的lecture 6中主要吴恩达老师讲述了一些关于机器学习和深度学习的tips,用一个触发词台灯的例子教我们如何快速的解决实际中遇到的问题,这节课主要是偏思维上的了解,还是要实际问题实际分 ...
- 4.6 什么是神经风格迁移-深度学习第四课《卷积神经网络》-Stanford吴恩达教授
←上一篇 ↓↑ 下一篇→ 4.5 面部验证与二分类 回到目录 4.7 什么是深度卷积网络? 什么是神经风格迁移 (What is neural style transfer?) 最近,卷积神经网络最有 ...
- Coursera吴恩达《卷积神经网络》课程笔记(4)-- 人脸识别与神经风格迁移
红色石头的个人网站:redstonewill.com <Convolutional Neural Networks>是Andrw Ng深度学习专项课程中的第四门课.这门课主要介绍卷积神经网 ...
- 吴恩达老师深度学习视频课笔记:神经风格迁移(neural style transfer)
什么是神经风格迁移(neural style transfer):如下图,Content为原始拍摄的图像,Style为一种风格图像.如果用Style来重新创造Content照片,神经风 ...
- python神经结构二层_《python深度学习》笔记---8.3、神经风格迁移
<python深度学习>笔记---8.3.神经风格迁移 一.总结 一句话总结: 神经风格迁移是指将参考图像的风格应用于目标图像,同时保留目标图像的内容. 1."神经风格迁移是指将 ...
- 成为梵高、毕加索?你最喜欢的人脸识别与神经风格迁移来啦!
AI有道 不可错过的AI技术公众号 关注 1 What Is Face Recognition 首先简单介绍一下人脸验证(face verification)和人脸识别(face recognitio ...
- 深度学习笔记(47) 神经风格迁移
深度学习笔记(47) 神经风格迁移 1. 神经风格迁移 2. 代价函数 1. 神经风格迁移 近,卷积神经网络最有趣的应用是神经风格迁移 来看几个例子,比如这张照片,照片是在斯坦福大学拍摄的 如果想利用 ...
最新文章
- Python可变序列中的一些坑
- 详解GAN的谱归一化(Spectral Normalization)
- Jquery操作Cookie取值错误的解决方法
- Freemarker中通过request获得contextPath
- codelite linux 安装,CodeLite + MinGW环境安装
- 《面向对象程序设计》第六次作业(图形化界面)
- Java基础String类
- 绿色版电子书PDF转换Word转换器
- C++中的RECT类
- 电商十二、pinyougou02.sql的内容③
- 生态版图 | 10月份YashanDB获信创产品认证,并与3款产品完成互认证
- 桌面的文件不见了怎么找出来
- 你愿意给应届生200万年薪吗?
- matlab中字符串和变量一起显示输出eval()函数用法
- RESTful服务 安全
- 放不下的原理_想要彻底忘记一个人,明白“洛克定律”的真实原理就可以
- 挣值如何计算?(转载)
- 软件腐化的七个特征之僵化性和脆弱性(设计模式原则的反面) (《敏捷软件开发》读书总结第一篇)
- CA-MKD:置信多教师知识蒸馏
- Matter Test-Harness自动化测试系统