神经风格转换
神经风格转换(Neural Style Transfer,NST)是深学习中最有趣的技术之一。如下图所示,它合并两个图像,即“内容”图像(CContent)和“风格”图像(SStyle),以创建“生成的”图像(GGenerated)。生成的图像G将图像C的“内容”与图像S的“风格”相结合。
在这个例子中,你将生成一个巴黎卢浮宫博物馆(内容图像C)与一个领袖印象派运动克劳德·莫奈的画(风格图像S)混合起来的绘画。
神经风格转换(NST)使用先前训练好了的卷积网络,并在此基础之上进行构建。使用在不同任务上训练的网络并将其应用于新任务的想法称为迁移学习。
根据原始的NST论文(https://arxiv.org/abs/1508.06576 ),我们将使用VGG网络,具体地说,我们将使用VGG-19,这是VGG网络的19层版本。这个模型已经在非常大的ImageNet数据库上进行了训练,因此学会了识别各种低级特征(浅层)和高级特征(深层)。
参考博客:
1.https://blog.csdn.net/qq_26907755/article/details/82259946
2.https://blog.csdn.net/u013733326/article/details/80767079
1.导入包
项目开始第一步导入我们要用到的工具包
import time
import os
import sys
import scipy.io#https://blog.csdn.net/q583501947/article/details/76735870
import scipy.misc
import matplotlib.pyplot as plt
from matplotlib.pyplot import imshow
from PIL import Image
import nst_utils
import numpy as np
import tensorflow as tf
Scipy是一个高级的科学计算库,它和Numpy联系很密切,Scipy一般都是操控Numpy数组来进行科学计算,所以可以说是基于Numpy之上了。Scipy有很多子模块可以应对不同的应用,例如插值运算,优化算法、图像处理、数学统计等
Scipy可以用来对mat文件进行操作
import scipy.io 数据输入输出
import scipy.misc 数据和图片之间的一个操作
import os /import sys 可参考:https://blog.csdn.net/zengxiantao1994/article/details/58188527
2.加载VGG模型
运行以下代码从VGG模型加载参数。这可能需要几秒钟的时间
model = nst_utils.load_vgg_model("pretrained-model/imagenet-vgg-verydeep-19.mat")
print(model)
结果:略
该模型存储在一个python字典中,其中每个变量名都是键,相应的值是一个包含该变量值的张量,要通过此网络运行图像,只需将图像提供给模型。 在TensorFlow中,你可以使用tf.assign函数来做到这一点:
#tf.assign函数用法
model["input"].assign(image)
注:model是一个字典,我们将其展开,可以看到如下形式:
这将图像作为输入给模型,在此之后,如果想要访问某个特定层的激活,比如4_2,请这样做:
#访问 4_2 层的激活
sess.run(model["conv4_2"])
3.搭建神经风格算法
我们可以使用下面3个步骤来构建神经风格转换(Neural Style Transfer,NST)算法:
1.构建内容损失函数 Jcontent(C,G)
2.构建风格损失函数 Jstyle(S,G)
3.把它放在一起得到J(G)=αJcontent(C,G)+βJstyle(S,G)
首先,我们可以读取一下内容函数看一看:
content_image = scipy.misc.imread("images/7.jpg")
imshow(content_image)
plt.show()#注意 这个地方不要忘记了,不然图片显示不出来
结果:(之前在上海拍摄的一张夜景图)
正如我们在吴恩达老师视频中看到的,浅层的一个卷积网络往往检测到较低层次的特征,如边缘和简单的纹理,更深层往往检测更高层次的特征,如更复杂的纹理以及对象分类等。
我们希望“生成的”图像G具有与输入图像C相似的内容。假设我们选择了一些层的激活来表示图像的内容,在实践中,如果你在网络中间选择一个层——既不太浅也不太深,你会得到最好的的视觉结果。(当你完成了这个练习后,你可以用不同的图层进行实验,看看结果是如何变化的。)
假设你选择了一个特殊的隐藏层,现在,将图像C作为已经训练好的VGG网络的输入,然后进行前向传播。让aC成为你选择的层中的隐藏层激活(在视频中吴恩达老师写作a^l_C,但在这里我们将去掉上标[l]以简化符号),激活值为nH×nW×nC的张量。然后用图像G重复这个过程:将G设置为输入数据,并进行前向传播,让a(G)成为相应的隐层激活,我们将把内容成本函数定义为:
这里nH,nW,nC分别代表了你选择的隐藏层的高度、宽度、通道数,并出现在成本的归一化项中,为了使得方便理解,需要注意的是a©与 a(G) 是与隐藏层激活相对应的卷积值,为了计算成本Jcontent(C,G),可以方便地将这些3D卷积展开为2D矩阵,如下所示。(从技术上讲,不需要这个展开步骤来计算Jcontent,但是当您以后需要执行类似的操作来计算Jstyle时,这将是一个很好的实践。)
1.开始计算内容损失值:
def compute_content_cost(a_C, a_G):
"""
计算内容代价的函数
参数:
a_C -- tensor类型,维度为(1, n_H, n_W, n_C),表示隐藏层中图像C的内容的激活值。
a_G -- tensor类型,维度为(1, n_H, n_W, n_C),表示隐藏层中图像G的内容的激活值。
返回:
J_content -- 实数,用上面的公式1计算的值。
"""
#获取a_G的维度信息
m, n_H, n_W, n_C = a_G.get_shape().as_list()
#对a_C与a_G从3维降到2维
#reshape函数 把张量a_C变成维度是 [n_H * n_W, n_C]的矩阵
#def reshape(tensor, shape, name=None)
#第1个参数为被调整维度的张量。
#第2个参数为要调整为的形状。
#返回一个shape形状的新tensor
#注意shape里最多有一个维度的值可以填写为 - 1,表示自动计算此维度。
a_C_unrolled = tf.transpose(tf.reshape(a_C, [n_H * n_W, n_C]))
#tf.transpose(input, [dimension_1, dimenaion_2,…,dimension_n]):
# 这个函数主要适用于交换输入张量的不同维度用的,如果输入张量是二维,就相当是转置。
# dimension_n是整数,如果张量是三维,就是用0,1,2来表示。这个列表里的每个数对应相应的维度。如果是[2,1,0],就把输入张量的第三维度和第一维度交换。
a_G_unrolled = tf.transpose(tf.reshape(a_G, [n_H * n_W, n_C]))
#计算内容代价
#J_content = (1 / (4 * n_H * n_W * n_C)) * tf.reduce_sum(tf.square(tf.subtract(a_C_unrolled, a_G_unrolled)))
#tf.subtract函数是一个算术运算符,用于表示减法,返回x-y的元素,tf.subtract函数中的x是一个Tensor
#对x内的所有元素进行平方操作 x必须为张量
J_content = 1/(4*n_H*n_W*n_C)*tf.reduce_sum(tf.square(tf.subtract(a_C_unrolled, a_G_unrolled)))
#https://www.w3cschool.cn/tensorflow_python/tensorflow_python-5y4d2i2n.html tf.reduce_sum所有元素求和
return J_content
tf.reset_default_graph()
# with tf.Session() as test:
# tf.set_random_seed(1)
# a_C = tf.random_normal([1, 4, 4, 3], mean=1, stddev=4)
# a_G = tf.random_normal([1, 4, 4, 3], mean=1, stddev=4)
# J_content = compute_content_cost(a_C, a_G)
# print("J_content = " + str(J_content.eval()))
#
# test.close()
结果
总结几个函数:
1.a_G.get_shape().as_list()
2.tf.reshape
3.tf.transpose
4.tf.subtract
5.tf.square
6.tf.reduce_sum
7.tf.reset_default_graph() 这玩意是干嘛的??tf.reset_default_graph函数用于清除默认图形堆栈并重置全局默认图形. 不太理解
需要记住的是:
内容成本采用神经网络的隐层激活,并测量a( C) 与 a(G)的区别。
当我们以后最小化内容成本时,这将有助于确保G的内容与C相似。
2.计算风格损失值
先看一下风格图片:
style_image = scipy.misc.imread("images/18.jpg")
imshow(style_image)
plt.show()
梵高的星空
注意 :内容图和风格图的大小要一致
计算风格矩阵:
风格矩阵又名“格拉姆矩阵”,在线性代数中,一组向量(v1,…,v2)的格拉姆矩阵G是点乘的矩阵,计算细节是Gij=viTvj=np.dot(vi,vj),换句话说,Gij比较了vi与vj的相似之处,如果他们非常相似,那么它们的点积就会很大,所以Gij就很大。
就是通道和通道之间的相关性
计算后的结果是维度为(nC,nC)的矩阵,其中nC是过滤器的数量,Gij测量了过滤器i的激活与过滤器j的激活具有多大的相似度。
风格矩阵Gii的一个重要的部分是对角线的元素,它测量了有效的过滤器i的多少。举个例子,假设过滤器i检测的是图像中的垂直的纹理,那么Gii测量的是图像整体中常见的垂直纹理,如果Gii很大,这意味着图像有很多垂直纹理。
通过捕捉不同类型的特征(Gii)的多少,以及总共出现了多少不同的特征(Gij),那么风格矩阵GG就测量的是整个图片的风格。
我们现在来使用tensorflow实现计算矩阵A的风格矩阵,计算公式是这样的:GA=AAT
接着开始计算风格损失:
在生成了风格矩阵(Gram matrix / Style matrix)之后,我们的目标是最小化风格图像的S与生成的图像G之间的距离。现在我们只使用单个隐藏层a[l],该层的相应的风格成本定义如下:
def gram_matrix(A):
"""
计算矩阵A的风格矩阵
参数:
A -- 矩阵,维度为(n_C, n_H * n_W)
返回:
GA -- A的风格矩阵,维度为(n_C, n_C)
"""
#https://www.w3cschool.cn/tensorflow_python/tensorflow_python-l5x72feg.html transpose_b:如果 True,b 在乘法之前转置.
#即 A*A'
GA = tf.matmul(A, A, transpose_b = True)
return GA
def compute_layer_style_cost(a_S, a_G):
"""
计算单隐藏层的风格损失
参数:
a_S -- tensor类型,维度为(1, n_H, n_W, n_C),表示隐藏层中图像S的风格的激活值。
a_G -- tensor类型,维度为(1, n_H, n_W, n_C),表示隐藏层中图像G的风格的激活值。
返回:
J_content -- 实数,用上面的公式2计算的值。
"""
#第1步:从a_G中获取维度信息
m, n_H, n_W, n_C = a_G.get_shape().as_list()
#第2步,将a_S与a_G的维度重构为(n_C, n_H * n_W)
a_S = tf.transpose(tf.reshape(a_S, [n_H * n_W, n_C]))
a_G = tf.transpose(tf.reshape(a_G, [n_H * n_W, n_C]))
#第3步,计算S与G的风格矩阵
GS = gram_matrix(a_S)
GG = gram_matrix(a_G)
#第4步:计算风格损失
#J_style_layer = (1/(4 * np.square(n_C) * np.square(n_H * n_W))) * (tf.reduce_sum(tf.square(tf.subtract(GS, GG))))
J_style_layer = 1/(4*n_C*n_C*n_H*n_H*n_W*n_W)*tf.reduce_sum(tf.square(tf.subtract(GS, GG)))
return J_style_layer
4. 风格权值
到目前为止,我们只从一个层使用了风格,如果我们从不同的层“合并”风格成本,我们会得到更好的结果。完成这个练习后,你可以回到这里,用不同的权重进行实验,看看它是如何改变生成的图像G的。
STYLE_LAYERS = [
('conv1_1', 0.2),
('conv2_1', 0.2),
('conv3_1', 0.2),
('conv4_1', 0.2),
('conv5_1', 0.2)]
我们使用如下公式:
def compute_style_cost(model, STYLE_LAYERS):
"""
计算几个选定层的总体风格成本
参数:
model -- 加载了的tensorflow模型
STYLE_LAYERS -- 字典,包含了:
- 我们希望从中提取风格的层的名称
- 每一层的系数(coeff)
返回:
J_style - tensor类型,实数,由公式(2)定义的成本计算方式来计算的值。
"""
# 初始化所有的成本值
J_style = 0
for layer_name, coeff in STYLE_LAYERS:
#选择当前选定层的输出
out = model[layer_name]
#运行会话,将a_S设置为我们选择的隐藏层的激活值
#sess.run实际上是 启动一个计算
#在tensorflow中,eval和run都是获取当前结点的值的一种方式。
#在使用eval时,若有一个 t 是Tensor对象,调用t.eval()相当于调用sess.run(t)
#注意:在上面for循环的内循环中,a_G是一个张量,尚未进行评估。 当我们在下面 的model_nn()中运行TensorFlow图时,它将在每次迭代中被评估和更新。
a_S = sess.run(out)
# 将a_G设置为来自同一图层的隐藏层激活,这里a_G引用model[layer_name],并且 还没有计算,
# 在后面的代码中,我们将图像G指定为模型输入,这样当我们运行会话时,
# 这将是以图像G作为输入,从隐藏层中获取的激活值。
a_G = out
#计算当前层的风格成本
J_style_layer = compute_layer_style_cost(a_S,a_G)
# 计算总风格成本,同时考虑到系数。
J_style += coeff * J_style_layer
return J_style
这里有点问题 a_G为什么没有计算??
接着计算总的:
def total_cost(J_content, J_style, alpha = 10, beta = 40):
"""
计算总成本
参数:
J_content -- 内容成本函数的输出
J_style -- 风格成本函数的输出
alpha -- 超参数,内容成本的权值
beta -- 超参数,风格成本的权值
"""
J = alpha * J_content + beta * J_style
return J
5.解决优化问题
最后,让我们一起来实现神经风格转换,它由以下几步构成:
创建交互会话
加载内容图像
加载风格图像
随机初始化生成的图像
加载VGG16模型
构建TensorFlow图:
使用VGG16模型来运行内容图并计算内容成本
使用VGG16模型来运行风格图并计算风格成本
计算总成本
定义优化器与学习速率
初始化TensorFlow图,进行多次迭代,每次迭代更新生成的图像。
让我们来一步步的走:
我们已经实现了总成本计算函数J(G)J(G),现在我们要设置TensorFlow来优化关于GG的内容,为此,程序必须重置图表并使用交互式会话(”Interactive Session”),与常规会话不同,“交互式会话”将自身设置为默认会话来构建图表。这允许我们在不需要引用会话对象的情况下运行变量,从而简化了代码,现在我们来开始运行交互式会话:
#重设图
tf.reset_default_graph()
#第1步:创建交互会话
#tf.InteractiveSession():它能让你在运行图的时候,插入一些计算图,
# 这些计算图是由某些操作(operations)构成的。这对于工作在交互式环境中的人们来说非常便利,比如使用IPython。
#tf.Session():需要在启动session之前构建整个计算图,然后启动该计算图。
#意思就是在我们使用tf.InteractiveSession()来构建会话的时候,
# 我们可以先构建一个session然后再定义操作(operation),
# 如果我们使用tf.Session()来构建会话我们需要在会话构建之前定义好全部的操作(operation)然后再构建会话。
#参考这个 :https://www.cnblogs.com/wangxuanran/p/10189325.html
sess = tf.InteractiveSession()
#第2步:加载内容图像(卢浮宫博物馆图片),并归一化图像
content_image = scipy.misc.imread("images/9.jpg")
content_image = nst_utils.reshape_and_normalize_image(content_image)
#第3步:加载风格图像(印象派的风格),并归一化图像
style_image = scipy.misc.imread("images/8.jpg")
style_image = nst_utils.reshape_and_normalize_image(style_image)
#第4步:随机初始化生成的图像,通过在内容图像中添加随机噪声来产生噪声图像
generated_image = nst_utils.generate_noise_image(content_image)
imshow(generated_image[0])
#第5步:加载VGG16模型
model = nst_utils.load_vgg_model("pretrained-model/imagenet-vgg-verydeep-19.mat")
#第6步:构建TensorFlow图:
##将内容图像作为VGG模型的输入。tf.assign(A, new_number): 这个函数的功能主要是把A的值变为new_number
#就是把内容图像作为模型的输入 这里的model实际上是一个字典 包含了 从输入到各层
sess.run(model["input"].assign(content_image))
## 获取conv4_2层的输出
out = model["conv4_2"]
## 将a_C设置为“conv4_2”隐藏层的激活值。
#https://blog.csdn.net/lcczzu/article/details/91449731
a_C = sess.run(out)
## 将a_G设置为来自同一图层的隐藏层激活,这里a_G引用model["conv4_2"],并且还没有计算,并没有run 所以是a_G是个tensor 后期更新 而a_C已经run了 是有值的
## 在后面的代码中,我们将图像G指定为模型输入,这样当我们运行会话时,
## 这将是以图像G作为输入,从隐藏层中获取的激活值。
#注意 一个run 只执行一次
a_G = out
## 计算内容成本
J_content = compute_content_cost(a_C, a_G)
## 将风格图像作为VGG模型的输入
sess.run(model["input"].assign(style_image))
## 计算风格成本
J_style = compute_style_cost(model, STYLE_LAYERS)
## 计算总成本
J = total_cost(J_content, J_style, alpha = 10, beta = 40)
## 定义优化器,设置学习率为2.0
optimizer = tf.train.AdamOptimizer(2.0)
## 定义学习目标:最小化成本
train_step = optimizer.minimize(J)
# 第7步:初始化TensorFlow图,进行多次迭代,每次迭代更新生成的图像。
def model_nn(sess, input_image, num_iterations = 200, is_print_info = True,
is_plot = True, is_save_process_image = True,
save_last_image_to = "output/generated_image.jpg"):
#初始化全局变量
sess.run(tf.global_variables_initializer())
#运行带噪声的输入图像 这里run 就是把生成图像作为输入 去更新上面的tensor
sess.run(model["input"].assign(input_image))
for i in range(num_iterations):
#运行最小化的目标:
sess.run(train_step)
#产生把数据输入模型后生成的图像
generated_image = sess.run(model["input"])
if is_print_info and i % 20 == 0:
Jt, Jc, Js = sess.run([J, J_content, J_style])
print("第 " + str(i) + "轮训练," +
" 总成本为:"+ str(Jt) +
" 内容成本为:" + str(Jc) +
" 风格成本为:" + str(Js))
if is_save_process_image:
nst_utils.save_image("output/" + str(i) + ".png", generated_image)
nst_utils.save_image(save_last_image_to, generated_image)
return generated_image
#开始时间
start_time = time.clock()
#非GPU版本,约25-30min
generated_image = model_nn(sess, generated_image)
#使用GPU,约1-2min
# with tf.device("/gpu:0"):
# generated_image = model_nn(sess, generated_image)
#结束时间
end_time = time.clock()
#计算时差
minium = end_time - start_time
print("执行了:" + str(int(minium / 60)) + "分" + str(int(minium%60)) + "秒")
结果: