深度学习与艺术 - 神经风格迁移
在本次作业中,你将学习神经风格迁移。该算法由Gatys等人在2015年创建(https://arxiv.org/abs/1508.06576%E3%80%82))。
在此作业中,你将:
- 实现神经风格迁移算法
- 使用算法生成新颖的艺术图像
目前你研究的大多数算法都会优化损失函数以获得一组参数值。而在神经样式转换中,你将学习优化损失函数以获得像素值!
import os
import sys
import scipy.io
import scipy.misc
import matplotlib.pyplot as plt
from matplotlib.pyplot import imshow
from PIL import Image
from nst_utils import *
import numpy as np
import tensorflow as tf
%matplotlib inline
1 问题陈述
神经风格迁移(NST)是深度学习中最有趣的技术之一。如下所示,它将“内容”图像(Content)和“风格”图像(Style)合并在一起,以创建“生成”图像(Generated)。生成的图像G将图像C的“内容”与图像S的“风格”组合在一起。
在此示例中,你将巴黎卢浮宫博物馆的图像(内容图像C)与印象派运动的领导者克劳德·莫奈的作品(风格图像S)混合在一起以生成新的图像。
让我们看看如何做到这一点。
2 迁移学习
神经风格迁移(NST)使用以前训练过的卷积网络,并以此为基础。将之前经过不同任务训练的网络应用于新任务的想法叫做迁移学习。
遵循原始的NST论文,我们将使用VGG网络。具体来说,我们将使用VGG-19,这是VGG网络的19层版本。该模型已经在非常大的ImageNet数据库上进行了训练,因此已经学会了识别各种低层特征和高层特征。
model = load_vgg_model("pretrained-model/imagenet-vgg-verydeep-19.mat")
print(model)
{'input': <tf.Variable 'Variable:0' shape=(1, 300, 400, 3) dtype=float32_ref>, 'conv1_1': <tf.Tensor 'Relu:0' shape=(1, 300, 400, 64) dtype=float32>, 'conv1_2': <tf.Tensor 'Relu_1:0' shape=(1, 300, 400, 64) dtype=float32>, 'avgpool1': <tf.Tensor 'AvgPool:0' shape=(1, 150, 200, 64) dtype=float32>, 'conv2_1': <tf.Tensor 'Relu_2:0' shape=(1, 150, 200, 128) dtype=float32>, 'conv2_2': <tf.Tensor 'Relu_3:0' shape=(1, 150, 200, 128) dtype=float32>, 'avgpool2': <tf.Tensor 'AvgPool_1:0' shape=(1, 75, 100, 128) dtype=float32>, 'conv3_1': <tf.Tensor 'Relu_4:0' shape=(1, 75, 100, 256) dtype=float32>, 'conv3_2': <tf.Tensor 'Relu_5:0' shape=(1, 75, 100, 256) dtype=float32>, 'conv3_3': <tf.Tensor 'Relu_6:0' shape=(1, 75, 100, 256) dtype=float32>, 'conv3_4': <tf.Tensor 'Relu_7:0' shape=(1, 75, 100, 256) dtype=float32>, 'avgpool3': <tf.Tensor 'AvgPool_2:0' shape=(1, 38, 50, 256) dtype=float32>, 'conv4_1': <tf.Tensor 'Relu_8:0' shape=(1, 38, 50, 512) dtype=float32>, 'conv4_2': <tf.Tensor 'Relu_9:0' shape=(1, 38, 50, 512) dtype=float32>, 'conv4_3': <tf.Tensor 'Relu_10:0' shape=(1, 38, 50, 512) dtype=float32>, 'conv4_4': <tf.Tensor 'Relu_11:0' shape=(1, 38, 50, 512) dtype=float32>, 'avgpool4': <tf.Tensor 'AvgPool_3:0' shape=(1, 19, 25, 512) dtype=float32>, 'conv5_1': <tf.Tensor 'Relu_12:0' shape=(1, 19, 25, 512) dtype=float32>, 'conv5_2': <tf.Tensor 'Relu_13:0' shape=(1, 19, 25, 512) dtype=float32>, 'conv5_3': <tf.Tensor 'Relu_14:0' shape=(1, 19, 25, 512) dtype=float32>, 'conv5_4': <tf.Tensor 'Relu_15:0' shape=(1, 19, 25, 512) dtype=float32>, 'avgpool5': <tf.Tensor 'AvgPool_4:0' shape=(1, 10, 13, 512) dtype=float32>}
该模型存储在python字典中,其中每个变量名称都是键,而对应的值是包含该变量值的张量。要通过此网络测试图像,只需要将图像提供给模型。在TensorFlow中,你可以使用tf.assign函数执行此操作。特别地,你将使用如下的assign函数:
model["input"].assign(image)
这会将图像分配为模型的输入。此后,如果要访问特定层的激活函数,例如当网络在此图像上运行时是 4_2 层,则可以在正确的张量conv4_2上运行TensorFlow会话,如下所示:
sess.run(model["conv4_2"])
3 神经风格迁移
我们将分三步构建NST算法:
- 建立内容损失函数 J c o n t e n t ( C , G ) J_{content}(C,G) Jcontent(C,G)。
- 建立风格损失函数 J s t y l e ( S , G ) J_{style}(S,G) Jstyle(S,G)。
- 放在一起得出 J ( G ) = α J c o n t e n t ( C , G ) + β J s t y l e ( S , G ) J(G) = \alpha J_{content}(C,G) + \beta J_{style}(S,G) J(G)=αJcontent(C,G)+βJstyle(S,G)。
3.1 计算内容损失
在我们的运行示例中,内容图像C是巴黎卢浮宫博物馆的图片。运行下面的代码以查看卢浮宫的图片。
content_image = scipy.misc.imread("images/louvre.jpg")
imshow(content_image)
d:\vr\virtual_environment\lib\site-packages\ipykernel_launcher.py:1: DeprecationWarning: `imread` is deprecated!
`imread` is deprecated in SciPy 1.0.0, and will be removed in 1.2.0.
Use ``imageio.imread`` instead.
"""Entry point for launching an IPython kernel.
<matplotlib.image.AxesImage at 0x26905cddb38>
内容图像(C)显示了卢浮宫博物馆的金字塔,周围是古老的巴黎建筑,在晴朗的天空下只有几层云。
3.1.1 如何确保生成的图像G与图像C的内容匹配?
正如我们在课程中所讲述的,ConvNet的底层倾向于检测诸如边缘和简单纹理之类的低层特征,而深层则倾向于检测诸如纹理之类的更复杂的高层特征。
我们希望生成的图像G具有与输入图像C相似的内容。假设你已选择某些层的激活来表示图像的内容。在实践中,如果在网络中间选择一个层,既不会太浅也不会太深,你将获得视觉上令人满意的结果。(完成本练习后,请随时返回并尝试使用不同的图层,以查看结果变化。)
因此,假设你选择了一个特定的隐藏层使用。现在,将图像C设置为预训练的VGG网络的输入,并进行正向传播。假设 a ( C ) a^{(C)} a(C)是你选择的层中的隐藏层激活。(在课程中,我们将其写为 a [ l ] ( C ) a^{[l](C)} a[l](C),但在这里我们将删除上标 [ l ] [l] [l]以简化表示手法。)这将是张量 n H × n W × n C n_H \times n_W \times n_C nH×nW×nC。对图像G重复此过程:将G设置为输入,然后进行正向传播。令 a ( G ) a^{(G)} a(G)为相应的隐藏层激活。我们将内容损失函数定义为:
J c o n t e n t ( C , G ) = 1 4 × n H × n W × n C ∑ all entries ( a ( C ) − a ( G ) ) 2 (1) J_{content}(C,G) = \frac{1}{4 \times n_H \times n_W \times n_C}\sum _{ \text{all entries}} (a^{(C)} - a^{(G)})^2\tag{1} Jcontent(C,G)=4×nH×nW×nC1all entries∑(a(C)−a(G))2(1)
在这里,
n
H
,
n
W
n_H,n_W
nH,nW和
n
C
n_C
nC是你选择的隐藏层的高度,宽度和通道数,并以损失的归一化术语显示。请注意
a
(
C
)
a^{(C)}
a(C)和
a
(
G
)
a^{(G)}
a(G)是与隐藏层的激活对应的。为了计算损失
J
c
o
n
t
e
n
t
(
C
,
G
)
J_{content}(C,G)
Jcontent(C,G),将这些3D体积展开为2D矩阵更方便,如下所示。(从技术上讲,此展开步骤不需要计算
J
c
o
n
t
e
n
t
J_{content}
Jcontent,但是对于之后需要进行类似操作以计算样式
J
s
t
y
l
e
J_{style}
Jstyle常数的情况来说,这将是一个很好的实践。)
练习:使用TensorFlow计算“内容损失”。
说明:实现此函数包含3个步骤:
- 从a_G检索尺寸:
- 要从张量X检索尺寸,请使用: X.get_shape().as_list()
- 如上图所示展开a_C和a_G
- 计算内容损失:
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维
a_C_unrolled = tf.transpose(tf.reshape(a_C, [n_H * n_W, n_C]))
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)))
J_content = 1 / (4 * n_H * n_W * n_C) * tf.reduce_sum(tf.square(tf.subtract(a_C_unrolled, a_G_unrolled)))
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()
J_content = 6.7655935
你应该记住:
- 内容损失需要对神经网络进行隐藏层激活,并计算 a ( C ) a^{(C)} a(C)和 a ( G ) a^{(G)} a(G)之间的差异。
- 当我们在最小化内容损失时,这将有助于确保 G G G具有与 C C C类似的内容。
3.2 计算风格损失
我们将使用以下样式图像作为示例运行:
style_image = scipy.misc.imread("images/monet_800600.jpg")
imshow(style_image)
d:\vr\virtual_environment\lib\site-packages\ipykernel_launcher.py:1: DeprecationWarning: `imread` is deprecated!
`imread` is deprecated in SciPy 1.0.0, and will be removed in 1.2.0.
Use ``imageio.imread`` instead.
"""Entry point for launching an IPython kernel.
<matplotlib.image.AxesImage at 0x269080c23c8>
这幅画以印象派的风格绘制。
让我们看看如何定义“风格”常数函数 J s t y l e ( S , G ) J_{style}(S,G) Jstyle(S,G)。
3.2.1 风格矩阵
风格矩阵也称为“语法矩阵”。在线性代数中,向量 ( v 1 , … , v n ) (v_{1},\dots ,v_{n}) (v1,…,vn)的集合的Gram矩阵G是点积的矩阵,其项是 G i j = v i T v j = n p . d o t ( v i , v j ) {\displaystyle G_{ij} = v_{i}^T v_{j} = np.dot(v_{i}, v_{j}) } Gij=viTvj=np.dot(vi,vj)。换句话说, G i j G_{ij} Gij比较 v i v_i vi与 v j v_j vj的相似度:如果它们非常相似,则它们会具有较大的点积,因此 G i j G_{ij} Gij也会较大。
请注意,此处使用的变量名称存在冲突。我们遵循文献中使用的通用术语,但是 G G G用于表示风格矩阵(或Gram矩阵)也表示生成的图像。我们将从上下文中确保清楚 G G G的指代。
在NST中,可以通过将“展开的”滤波器矩阵与其转置相乘来计算风格矩阵:
结果是维度为 ( n C , n C ) (n_C,n_C) (nC,nC)的矩阵,其中 n C n_C nC是滤波器的数量。值 G i j G_{ij} Gij衡量滤波器 i i i的激活与滤波器 j j j的激活的相似度。
语法矩阵的一个重要部分是对角元素(例如 G i i G_{ii} Gii)也可以衡量滤波器 i i i的活跃程度。例如,假设滤波器 i i i正在检测图像中的垂直纹理。然后 G i i G_{ii} Gii衡量整个图像中垂直纹理的普遍程度:如果 G i i G_{ii} Gii大,则意味着图像具有很多垂直纹理。
通过捕获不同类型特征的普遍性( G i i G_{ii} Gii)以及一起出现多少不同特征( G i i G_{ii} Gii),样式矩阵可以衡量图像的样式。
练习:
使用TensorFlow实现一个计算矩阵A的语法矩阵的函数。公式为:A的语法矩阵为
G
A
=
A
A
T
G_A = AA^T
GA=AAT。如果遇到问题,请查看Hint 1 和 Hint 2。
def gram_matrix(A):
"""
参数:
A -- 矩阵的shape为(n_C, n_H * n_W)
返回:
GA -- A的Gram矩阵,形状为(n_C, n_C)
"""
GA = tf.matmul(A,tf.transpose(A))
return GA
tf.reset_default_graph()
with tf.Session() as test:
tf.set_random_seed(1)
A = tf.random_normal([3, 2*1], mean=1, stddev=4)
GA = gram_matrix(A)
print("GA = " + str(GA.eval()))
GA = [[ 6.422305 -4.429122 -2.096682]
[-4.429122 19.465837 19.563871]
[-2.096682 19.563871 20.686462]]
3.2.2 风格损失
生成风格矩阵(Gram矩阵)后,你的目标是使"style"图像S的Gram矩阵和生成的图像G的Gram矩阵之间的距离最小。现在,我们仅使用单个隐藏层 A [ L ] A^{[L]} A[L],该层的相应的风格损失定义为:
J s t y l e [ l ] ( S , G ) = 1 4 × n C 2 × ( n H × n W ) 2 ∑ i = 1 n C ∑ j = 1 n C ( G i j ( S ) − G i j ( G ) ) 2 (2) J_{style}^{[l]}(S,G) = \frac{1}{4 \times {n_C}^2 \times (n_H \times n_W)^2} \sum _{i=1}^{n_C}\sum_{j=1}^{n_C}(G^{(S)}_{ij} - G^{(G)}_{ij})^2\tag{2} Jstyle[l](S,G)=4×nC2×(nH×nW)21i=1∑nCj=1∑nC(Gij(S)−Gij(G))2(2)
其中 G ( S ) G^{(S)} G(S)和 G ( G ) G^{(G)} G(G)分别是“风格”图像和“生成”图像的语法矩阵,使用针对网络中特定的隐藏层的激活来计算。
练习:计算单层的风格损失。
说明:实现此函数的步骤是:
- 从隐藏层激活a_G中检索尺寸:
- 要从张量X检索尺寸,请使用:
X.get_shape().as_list()
- 要从张量X检索尺寸,请使用:
- 如上图所示,将隐藏层激活a_S和a_G展开为2D矩阵。
- 计算图像S和G的风格矩阵。(使用以前编写的函数)
- 计算风格损失:
def compute_layer_style_cost(a_S, a_G):
# 从a_G中检索维度
m, n_H, n_W, n_C = a_G.get_shape().as_list()
# 重新塑造图像的形状(n_C, n_H * n_W)
a_S = tf.reshape(a_S,shape=(n_H * n_W,n_C))
a_G = tf.reshape(a_G,shape=(n_H * n_W,n_C))
# 计算图像S和G的gram_矩阵
GS = gram_matrix(tf.transpose(a_S))
GG = gram_matrix(tf.transpose(a_G))
# 计算损失
J_style_layer = tf.reduce_sum(tf.square(tf.subtract(GS,GG))) / (4 * (n_C * n_C) * (n_W * n_H) * (n_W * n_H))
return J_style_layer
tf.reset_default_graph()
with tf.Session() as test:
tf.set_random_seed(1)
a_S = 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_style_layer = compute_layer_style_cost(a_S, a_G)
print("J_style_layer = " + str(J_style_layer.eval()))
J_style_layer = 9.190278
3.2.3 风格权重
到目前为止,你仅从一层捕获了风格特征。如果我们从几个不同的层次“合并”风格损失,我们将获得更好的结果。完成此练习后,请随时返回并尝试不同的权重,以查看它如何更改生成的图像 G G G。但现在而言,这是一个合理的默认值:
STYLE_LAYERS = [
('conv1_1', 0.2),
('conv2_1', 0.2),
('conv3_1', 0.2),
('conv4_1', 0.2),
('conv5_1', 0.2)]
你可以如下组合不同层的风格损失:
J
s
t
y
l
e
(
S
,
G
)
=
∑
l
λ
[
l
]
J
s
t
y
l
e
[
l
]
(
S
,
G
)
J_{style}(S,G) = \sum_{l} \lambda^{[l]} J^{[l]}_{style}(S,G)
Jstyle(S,G)=l∑λ[l]Jstyle[l](S,G)
λ
[
l
]
\lambda^{[l]}
λ[l]的值在STYLE_LAYERS中给出。
我们已经实现了compute_style_cost(...)
函数。它只是简单地多次调用你的compute_layer_style_cost(...)
,并使用STYLE_LAYERS中的值对结果进行加权。请仔细阅读以确保你了解它在做什么。
2.从STYLE_LAYERS循环(layer_name,coeff):
a. 选择当前层的输出张量 例如,要从层"conv1_1"中调用张量,你可以这样做:out = model["conv1_1"]
b. 通过在张量"out"上运行会话,从当前层获取style图像的风格
c. 获取一个表示当前层生成的图像风格的张量。 这只是"out"。
d. 现在,你拥有两种风格。使用上面实现的函数计算当前层的style_cost
e. 将当前层的(style_cost x coeff)添加到整体风格损失(J_style)中
3.返回J_style,它现在应该是每层的(style_cost x coeff)之和。
def compute_style_cost(model, STYLE_LAYERS):
"""
计算从几个选择的层的总体样式成本
参数:
model -- 我们的tensorflow模型
STYLE_LAYERS -- 一个python列表,包含:
- 我们想从中提取样式的层的名称
- 每一个都有一个系数
返回:
J_style -- 表示标量值的张量,由式(2)定义的样式代价
"""
# 初始化整体样式开销
J_style = 0
for layer_name, coeff in STYLE_LAYERS:
# 选择当前所选层的输出张量
out = model[layer_name]
# 通过out运行会话,将a_S设置为所选层的隐藏层激活
a_S = sess.run(out)
# 将a_G设置为来自同一层的隐藏层激活。在这里,a_G引用model[layer_name],并且还没有计算。在后面的代码中,我们将指定图像G作为模型输入,这样当我们运行会话时,这将是从适当的层绘制的激活,G作为输入。
a_G = out
# 计算当前层的style_cost
J_style_layer = compute_layer_style_cost(a_S, a_G)
# 将该图层的coeff * J_style_layer添加到整体样式开销中
J_style += coeff * J_style_layer
return J_style
注意:在上述for循环的内部循环中,a_G是张量,尚未进行求值。当我们在下面的model_nn()中运行TensorFlow计算图时,它将在每次迭代时进行评估和更新。
你如何选择每一层的系数?较深的层捕获更复杂的特征,并且较深的层中的特征在图像中相对于彼此而言定位较少。因此,如果希望生成的图像柔和地跟随风格图像,请尝试为较深的层选择较大的权重,为第一层选择较小的权重。相反,如果希望生成的图像强烈遵循风格图像,请尝试为较低的层选择较小的权重,为第一层选择较大的权重。
你应该记住:
- 可以使用隐藏层激活的Gram矩阵表示图像的风格。但是,结合多个不同层的语法矩阵表示,我们可以获得更好的结果。这与内容表示法相反,后者通常仅使用一个隐藏层就足够了。
- 最小化风格损失将导致图像 G G G遵循图像 S S S的风格。
3.3 定义优化的总损失
最后,让我们创建一个损失函数,以最小化风格和内容损失。公式为:
J ( G ) = α J c o n t e n t ( C , G ) + β J s t y l e ( S , G ) J(G) = \alpha J_{content}(C,G) + \beta J_{style}(S,G) J(G)=αJcontent(C,G)+βJstyle(S,G)
练习:实现总损失函数,其中包括内容损失和风格损失。
def total_cost(J_content, J_style, alpha = 10, beta = 40):
J = alpha * J_content + beta * J_style
return J
tf.reset_default_graph()
with tf.Session() as test:
np.random.seed(3)
J_content = np.random.randn()
J_style = np.random.randn()
J = total_cost(J_content, J_style)
print("J = " + str(J))
J = 35.34667875478276
你应该记住:
- 总损失是内容损失 J c o n t e n t ( C , G ) J_{content}(C,G) Jcontent(C,G)和风格损失 J s t y l e ( S , G ) J_{style}(S,G) Jstyle(S,G)的线性组合
- α \alpha α和 β \beta β是控制内容和风格之间相对权重的超参数
4 解决优化问题
最后,让我们将所有内容组合在一起以实现神经风格迁移!
该程序必须执行以下操作:
- 创建一个交互式会话
- 加载内容图像
- 加载风格图像
- 随机初始化要生成的图像
- 加载VGG16模型
- 构建TensorFlow计算图:
- 通过VGG16模型运行内容图像并计算内容损失
- 通过VGG16模板运行风格图像并计算风格损失
- 计算总损失
- 定义优化器和学习率
- 初始化TensorFlow图,并运行大量迭代,然后再每个步骤更新生成的图像。
让我们详细介绍各个步骤。
你之前已经实现了总损失 J ( G ) J(G) J(G),我们现在将设置TensorFlow来针对 G G G进行优化。为此,你的程序必须重置计算图并使用"Interactive Session"。与常规会话不同,交互式会话将启动自身作为默认会话以构建计算图。这使你可以运行变量而无需经常引用会话对象,从而简化了代码。
让我们开始交互式会话。
# 重置图
tf.reset_default_graph()
# 启动交互式会话
sess = tf.InteractiveSession()
让我们加载,重塑和标准化我们的“内容”图像(卢浮宫博物馆图片):
content_image = scipy.misc.imread("images/louvre_small.jpg")
content_image = reshape_and_normalize_image(content_image)
d:\vr\virtual_environment\lib\site-packages\ipykernel_launcher.py:1: DeprecationWarning: `imread` is deprecated!
`imread` is deprecated in SciPy 1.0.0, and will be removed in 1.2.0.
Use ``imageio.imread`` instead.
"""Entry point for launching an IPython kernel.
加载,重塑和标准化我们的“风格”图像(克劳德·莫奈的画):
style_image = scipy.misc.imread("images/monet.jpg")
style_image = reshape_and_normalize_image(style_image)
d:\vr\virtual_environment\lib\site-packages\ipykernel_launcher.py:1: DeprecationWarning: `imread` is deprecated!
`imread` is deprecated in SciPy 1.0.0, and will be removed in 1.2.0.
Use ``imageio.imread`` instead.
"""Entry point for launching an IPython kernel.
现在,我们将“生成的”图像初始化作为从content_image创建的噪声图像。通过将生成图像的像素初始化为主要是噪声但仍与内容图像稍微相关的像素,这将有助于生成的图像的内容更快速地匹配“内容”图像的内容。(可以在nst_utils.py
中查看generate_noise_image(...)
的详细信息;为此,请在此Jupyter笔记本的左上角单击"File–>Open…")
generated_image = generate_noise_image(content_image)
imshow(generated_image[0])
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
<matplotlib.image.AxesImage at 0x26905ec3d30>
接下来,如第(2)部分所述,让我们加载VGG16模型。
model = load_vgg_model("pretrained-model/imagenet-vgg-verydeep-19.mat")
为了获得计算内容损失的程序,我们现在将a_C和a_G分配为适当的隐藏层激活。我们将使用conv4_2层来计算内容损失。下面的代码执行以下操作:
- 将内容图像分配为VGG模型的输入。
- 将a_C设置为张量,为层"conv4_2"提供隐藏层激活。
- 设置a_G为张量,为同一层提供隐藏层激活。
- 使用a_C和a_G计算内容损失。
# 将内容图像指定为VGG模型的输入。
sess.run(model['input'].assign(content_image))
# 选择conv4_2层的输出张量
out = model['conv4_2']
# 设置a_C为我们选择的图层的隐藏层激活
a_C = sess.run(out)
# 将a_G设置为来自同一层的隐藏层激活。这里,a_G引用了model['conv4_2'],还没有计算。在后面的代码中,我们将指定图像G作为模型输入,这样当我们运行会话时,这将是从适当的层绘制的激活,G作为输入。
a_G = out
# 计算内容成本
J_content = compute_content_cost(a_C, a_G)
注意:此时,a_G是张量,尚未验证。当我们在下面的model_nn()中运行Tensorflow计算图时,它将在每次迭代时进行确认和更新。
# 将模型的输入指定为“style”图像
sess.run(model['input'].assign(style_image))
# 计算样式成本
J_style = compute_style_cost(model, STYLE_LAYERS)
练习:现在你有了J_content和J_style,通过调用total_cost()
计算总损失J。 使用alpha = 10
和 beta = 40
。
J = total_cost(J_content, J_style, alpha = 10, beta = 40)
你之前已经学习了如何在TensorFlow中设置Adam优化器。我们在这里使用2.0的学习率。See reference
optimizer = tf.train.AdamOptimizer(2.0)
train_step = optimizer.minimize(J)
练习:实现model_nn()函数,该函数初始化tensorflow计算图的变量,将输入图像(初始生成的图像)作为VGG16模型的输入,并运行train_step进行训练步骤。
def model_nn(sess, input_image, num_iterations = 200):
# 初始化全局变量(需要在初始化器上运行会话)
sess.run(tf.global_variables_initializer())
# 通过模型运行带噪声的输入图像(初始生成的图像)。使用assign()。
generated_image=sess.run(model['input'].assign(input_image))
for i in range(num_iterations):
# 在train_step上运行会话以最小化总成本
sess.run(train_step)
# 通过在当前model['input']上运行会话来计算生成的图像
generated_image = sess.run(model['input'])
# 每20次打印一次。
if i%20 == 0:
Jt, Jc, Js = sess.run([J, J_content, J_style])
print("Iteration " + str(i) + " :")
print("total cost = " + str(Jt))
print("content cost = " + str(Jc))
print("style cost = " + str(Js))
# 保存当前生成的图像到“/output”目录
save_image("output/" + str(i) + ".png", generated_image)
# 保存最后生成的图像
save_image('output/generated_image.jpg', generated_image)
return generated_image
运行以下单元格以生成艺术图像。每运行20次迭代在CPU上大约需要3分钟,但是在大约140次迭代后你开始观察到好的结果。通常使用GPU训练神经风格迁移。
model_nn(sess, generated_image)
Iteration 0 :
total cost = 4936893000.0
content cost = 7881.85
style cost = 123420350.0
Iteration 20 :
total cost = 931792700.0
content cost = 15150.729
style cost = 23291030.0
Iteration 40 :
total cost = 476977900.0
content cost = 16802.03
style cost = 11920246.0
Iteration 60 :
total cost = 306887600.0
content cost = 17398.729
style cost = 7667841.0
Iteration 80 :
total cost = 224318640.0
content cost = 17652.709
style cost = 5603553.0
Iteration 100 :
total cost = 177715900.0
content cost = 17879.422
style cost = 4438427.5
Iteration 120 :
total cost = 147169620.0
content cost = 18050.78
style cost = 3674727.5
Iteration 140 :
total cost = 125411320.0
content cost = 18213.465
style cost = 3130729.5
Iteration 160 :
total cost = 108912420.0
content cost = 18361.072
style cost = 2718220.2
Iteration 180 :
total cost = 96001230.0
content cost = 18497.363
style cost = 2395406.5
array([[[[ -45.12358 , -72.19441 , 51.746346 ],
[ -24.75327 , -44.2964 , 29.886335 ],
[ -39.64303 , -31.56099 , 13.718143 ],
...,
[ -24.76011 , -10.788876 , 14.371523 ],
[ -28.816984 , -5.280051 , 23.481634 ],
[ -40.314568 , -6.0927963, 49.9986 ]],
[[ -58.39488 , -53.06708 , 26.432343 ],
[ -32.944817 , -32.69456 , -1.5097439],
[ -26.427433 , -31.894102 , 15.655808 ],
...,
[ -25.357164 , -9.746335 , 24.563683 ],
[ -20.002506 , -20.456278 , 12.591088 ],
[ -38.10473 , -10.029796 , 10.0670185]],
[[ -50.200523 , -50.721996 , 15.647173 ],
[ -37.31482 , -42.209923 , -6.276647 ],
[ -33.782967 , -25.933123 , 5.7868314],
...,
[ -11.978632 , -41.413166 , 10.257919 ],
[ -13.608108 , -24.304035 , 14.8848295],
[ -23.316372 , -21.182524 , 12.9698 ]],
...,
[[ -45.040543 , -44.788315 , -27.18235 ],
[ -90.703514 , -68.318245 , -255.0205 ],
[ -65.162224 , -61.23871 , -127.02678 ],
...,
[ -62.159573 , -74.07849 , -31.950476 ],
[ -75.70332 , -98.683426 , -27.93272 ],
[ 3.5887198, -34.177395 , 23.60415 ]],
[[ -19.461033 , -72.949875 , 11.1744175],
[-165.36955 , -96.42717 , -28.43835 ],
[ 18.556105 , -60.75096 , -17.065166 ],
...,
[ -91.97378 , -86.639656 , -49.875256 ],
[-101.63406 , -109.606384 , -63.365128 ],
[ -69.75303 , -100.961685 , -3.9610627]],
[[ 40.353714 , -34.501305 , 46.080757 ],
[ 28.722298 , -80.35448 , 24.79451 ],
[ 33.369385 , -26.925333 , 19.59569 ],
...,
[-117.021164 , -103.20966 , -19.439291 ],
[-147.96053 , -143.07509 , -31.319807 ],
[ -25.229664 , -101.636154 , 23.11785 ]]]], dtype=float32)
你完成了!运行此命令后,在笔记本计算机的上方栏中单击"File",然后单击"Open"。转到"/output"目录以查看所有已保存的图像。打开"generated_image"以查看生成的图像!
你应该看到下面显示的图像:
我们不想让你等待太久才能看到初始结果,因此已相应地设置了超参数。为了获得最佳效果,较长的优化算法(可能以较小的学习率)运行效果更好。完成并提交此作业后,我们建议你返回并使用此笔记本进行更多操作,看看是否可以生成外观更好的图像。
以下是一些其他示例:
-
梵高(星空)风格的波斯波利斯(伊朗)古城的美丽废墟
-
伊斯帕汗陶瓷风格的居鲁士大帝之墓
-
具有抽象蓝色液体绘画风格的湍流科学研究。
5 使用你自己的图像进行测试
最后,你还可以在自己的图像上重新运行算法!
为此,请回到第4部分,并使用你自己的图片更改内容图像和风格图像。以下是你应该执行的操作:
- 单击笔记本上部选项卡中的"File -> Open"
- 转到"/images"并上传图像(要求:(WIDTH = 300, HEIGHT = 225)),例如将其重命名为"my_content.png"和"my_style.png"
- 从以下位置更改部分(3.4)中的代码:
content_image = scipy.misc.imread("images/louvre.jpg")
style_image = scipy.misc.imread("images/claude-monet.jpg")
到:
content_image = scipy.misc.imread("images/my_content.jpg")
style_image = scipy.misc.imread("images/my_style.jpg")
重新运行单元(你可能需要重新启动笔记本计算机上部选项卡中的Kernel )。
你还可以试着调整一下超参数:
- 哪一层负责表示风格? STYLE_LAYERS
- 你要运行算法多少次迭代? num_iterations
- 内容和风格之间的相对权重是多少? alpha / beta
6 总结
恭喜你出色地完成这项任务!现在,你可以使用“神经风格迁移”生成艺术图像。这也是你第一次建立模型,在该模型中,优化算法将更新像素值而不是神经网络的参数。深度学习有许多不同类型的模型,这只是其中之一!
你应该记住:
- 神经风格迁移是一种算法,给定内容图像C和风格图像S可以生成艺术图像
- 它使用基于预训练的ConvNet的特征(隐藏层激活)。
- 使用一个隐藏层的激活来计算内容损失函数。
- 使用该层激活的Gram矩阵计算一层的风格损失函数。使用几个隐藏层可以获得整体风格损失函数。
- 优化总损失函数以合成新图像。