Coursera之deeplearning.ai:CNN-Art Generation with Neural Style Transfer
Deep Learning & Art: Neural Style Transfer
该算法基于Gatys et al.(2015)的研究成果(https://arxiv.org/abs/1508.06576).
在本实验中, 可以实现神经风格迁移算法;并用于生成艺术图像。
一些算法通过优化一个成本函数来获取一组参数值,而在神经风格转移中,则通过优化一个成本函数来确定像素值的数值。
1-问题陈述
属于深度学习领域的一个有趣的技术手段,在神经艺术风格转移中被广泛应用。该技术通过融合不同类型的图片——其中一个是内容图像(C),另一个是风格图像(S),从而生成一个新的图片(G)。生成后的图片(G)包含了图片C的内容元素以及图片S所具有的艺术风格特征。例如,在将卢浮宫(Paris)的照片与 Claude Monet 的印象派作品混合时

2-迁移学习
Neural Style Transfer (NST)基于预先训练好的卷积神经网络构建而成。迁移学习的概念在于利用在不同领域经过训练的网络并将其应用于新的领域任务中。
model = load_vgg_model("pretrained-model/imagenet-vgg-verydeep-19.mat")
print(model)
代码解释
该模型以Python字典的形式存储,并将每个变量名作为键存在对应的张量数据。当需要对网络进行图像处理时,请将图像输入到网络中进行计算操作。为了实现这一过程,在TensorFlow框架中可以调用tf.assign功能用于设置模型输入。具体而言,
model["input"].assign(image)
这一行代码即可将输入图像赋值给模型中的input层节点。此外,
model["input"].assign(image)
也可以被视为一种特殊的输入处理方式。如果目标是在特定层级(如conv4_2)获取激活信息,则可以在正确配置好张量数据后启动TensorFlow计算图:
tf.Session().run(conv4_2_tensor)
sess.run(model["conv4_2"])
3-Neural Style Transfer
NST算法分三步:
- 计算内容成本函数J_{content}(C,G)
- 计算风格成本函数J_{style}(C,G)
- 计算总成本函数J(G)=\alpha J_{content}(C,G)+\beta J_{style}(C,G)
3.1-计算内容成本
运行实例中,内容图像C是巴黎卢浮宫的图片,运行代码查看卢浮宫图片:
content_image = scipy.misc.imread("images/louvre.jpg")
imshow(content_image)
代码解释

3.1.1 如何确保生成图像G与输入图像C具有相似内容
较浅层的卷积神经网络(ConvNet)主要擅长检测基础特征,如边缘和简单的纹理模式;而较深层则能够捕捉到更为复杂的特征组合与细节信息。
为了使生成图像G与输入图像C具有相似的内容组成,在实践中我们通常会选择某一层级的激活来反映图像内容特性。具体而言,在实验中发现若选择的是中间层级,则会获得最佳视觉效果。(建议在完成练习后可尝试更换不同层级以观察结果的变化)
假设已经确定了一个特定隐藏层级用于后续计算。现在将输入图像C设置为经过预先训练好的VGG网络处理后的输入数据,并执行前向传播操作。令a^{C}表示所选层级处的隐藏层激活(hidden layer activations),其维度为n_{H}\times n_{W}\times n_{C} tensor形式;同样地将生成图像G作为输入并进行前向传播运算,则对应的隐藏层激活为a^{G}。在此基础上我们定义的内容损失函数为:
在这里, n_{H}, n_{W}, n_{C}分别代表选择的隐藏层的高度、宽度以及数量, 并且它们都是基于成本标准化后的体现. 其中, 是一些volumes分别对应于各个隐藏层的激活状态. 为了计算 , 需要将这些3D volumes展平为2D矩阵. (从技术上讲, 这种unrolling操作相比无需计算J_{content}的情况更为复杂, 但当你未来需要进行类似的操作来计算样式常量J_{style}时, 这确实是一个值得借鉴的好方法.)

完成内容损失计算任务:按照以下步骤操作:第一步,请通过读取 a_{G}的维度信息完成从 a_{G}中获取所需数据;第二步,请根据获取的数据构建一个相似度矩阵;第三步,请基于上述矩阵更新生成器模型参数
- 从tensor X获取其形状信息。
- 展开卷积核参数a_C和a_G如上文所述。
- 评估内容的成本指标。
# GRADED FUNCTION: compute_content_cost
def compute_content_cost(a_C, a_G):
"""
Computes the content cost
Arguments:
a_C -- tensor of dimension (1, n_H, n_W, n_C), hidden layer activations representing content of the image C
a_G -- tensor of dimension (1, n_H, n_W, n_C), hidden layer activations representing content of the image G
Returns:
J_content -- scalar that you compute using equation 1 above.
"""
### START CODE HERE ###
# Retrieve dimensions from a_G (≈1 line)
m, n_H, n_W, n_C = a_G.get_shape().as_list()
# Reshape a_C and a_G (≈2 lines)
a_C_unrolled = tf.reshape(a_C,shape=[m, n_H*n_W, n_C])
a_G_unrolled = tf.reshape(a_G,shape=[m, n_H*n_W, n_C])
# compute the cost with tensorflow (≈1 line)
J_content = tf.reduce_sum(tf.square(a_C_unrolled - a_G_unrolled))/(4 * n_H * n_W * n_C)
### END CODE HERE ###
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()))
代码解释
预计的结果将是:J_content = 6.76559
3.2-计算风格成本
针对我们当前的研究项目,则计划采用以下风格图来辅助分析。
style_image = scipy.misc.imread("images/monet_800600.jpg")
imshow(style_image)
代码解释

3.2.1-风格矩阵

该方法的结果生成了一个n_{C} \times n_{C}维数目的矩阵,并且n_{C}代表的是过滤器的数量。该过程旨在量化不同过滤器之间的激活相似性水平。
对角线元素G_{ii}衡量了一个特定滤波器的有效性强度(effectiveness)。举例而言,在图像识别任务中,
假设某个滤波器能够检测出垂直纹理特征,则G_{ii}反映了在整个图像范围内这种垂直纹理特征出现频率的整体水平。
具体而言,
较大的G_{ii}值表明该图像包含了大量垂直纹理特征实例。
此外,
通过分析不同滤波器之间的相关性以及共同捕捉到的各种特征模式,
风格矩阵G能够有效地表征图像的整体风格特性。
最后,
作为一个练习题:
请使用TensorFlow框架来计算给定矩阵A的克里姆斯(Gram)矩阵。
根据公式:
G_A = A A^T
其中,
克里姆斯矩阵G_A反映了向量集合\{a_i\}_{i=1}^{m}之间的内积关系,
每个元素(i,j)表示向量a_i与a_j之间的点积值。
# GRADED FUNCTION: gram_matrix
def gram_matrix(A):
"""
Argument:
A -- matrix of shape (n_C, n_H*n_W)
Returns:
GA -- Gram matrix of A, of shape (n_C, n_C)
"""
### START CODE HERE ### (≈1 line)
GA = tf.matmul(A, tf.transpose(A))
### END CODE HERE ###
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.42230511 -4.42912197 -2.09668207]
[ -4.42912197 19.46583748 19.56387138]
[ -2.09668207 19.56387138 20.6864624 ]]
代码解释
3.2.2-风格损失
计算生成图像的风格矩阵(Gram matrix)G_s之后, 目的是最小化这两个Gram矩阵之间的距离. 现在仅考虑网络中某一层a^{[l]}的特征, 其对应的风格损失定义为
G^{S} 和 G^{G} 分别代表风格图像与生成图像的Gram矩阵,在网络中某个指定隐藏层的激活计算结果上应用。练习题:请计算单个图层的风格成本。步骤:实现功能分为三个步骤。
在隐藏层激活a_G时进行检索维度:通过tensor X获取其形状信息:X.get_shape().as_list()。
将隐藏层激活a_S 和 s_G 展平为二维矩阵。
通过调用之前编写好的函数来计算图像S和G的风格矩阵。
基于上述计算结果来确定风格损失值。
# GRADED FUNCTION: compute_layer_style_cost
def compute_layer_style_cost(a_S, a_G):
"""
Arguments:
a_S -- tensor of dimension (1, n_H, n_W, n_C), hidden layer activations representing style of the image S
a_G -- tensor of dimension (1, n_H, n_W, n_C), hidden layer activations representing style of the image G
Returns:
J_style_layer -- tensor representing a scalar value, style cost defined above by equation (2)
"""
### START CODE HERE ###
# Retrieve dimensions from a_G (≈1 line)
m, n_H, n_W, n_C = a_G.get_shape().as_list()
# Reshape the images to have them of shape (n_C, n_H*n_W) (≈2 lines)
a_S = tf.reshape(a_S, [n_H*n_W, n_C])
a_G = tf.reshape(a_G, [n_H*n_W, n_C])
# Computing gram_matrices for both images S and G (≈2 lines)
GS = gram_matrix(tf.transpose(a_S))
GG = gram_matrix(tf.transpose(a_G))
# Computing the loss (≈1 line)
J_style_layer = tf.reduce_sum(tf.square(GS-GG))/(4 * n_C**2 * (n_W*n_H)**2)
### END CODE HERE ###
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.19028
(这里注意,题目提示:Reshape the images to have them of shape (n_C, n_Hn_W),但是这里需要reshape为(n_Hn_W, n_C),不然结果不对)
3.2.3风格权重
现在,已经从一个层捕捉了样式,需要从不同的层合并样式成本,会得到更好的结果。完成训练后,可以自由地返回并尝试不同地权重,看看它是如何改变生成图像G的。就目前为止,这是一个合理的默认值:
STYLE_LAYERS = [
('conv1_1', 0.2),
('conv2_1', 0.2),
('conv3_1', 0.2),
('conv4_1', 0.2),
('conv5_1', 0.2)]
代码解释
可以将不同层的样式成本合并在一起:
这里, \lambda^{[l]}的值在 STYLE_LAYERS 给出.
def compute_style_cost(model, STYLE_LAYERS):
"""
Computes the overall style cost from several chosen layers
Arguments:
model -- our tensorflow model
STYLE_LAYERS -- A python list containing:
- the names of the layers we would like to extract style from
- a coefficient for each of them
Returns:
J_style -- tensor representing a scalar value, style cost defined above by equation (2)
"""
# initialize the overall style cost
J_style = 0
for layer_name, coeff in STYLE_LAYERS:
# Select the output tensor of the currently selected layer
out = model[layer_name]
# Set a_S to be the hidden layer activation from the layer we have selected, by running the session on out
a_S = sess.run(out)
# Set a_G to be the hidden layer activation from same layer. Here, a_G references model[layer_name]
# and isn't evaluated yet. Later in the code, we'll assign the image G as the model input, so that
# when we run the session, this will be the activations drawn from the appropriate layer, with G as input.
a_G = out
# Compute style_cost for the current layer
J_style_layer = compute_layer_style_cost(a_S, a_G)
# Add coeff * J_style_layer of this layer to overall style cost
J_style += coeff * J_style_layer
return J_style
代码解释
在for循环内部的循环中(即a_G是一个张量)尚未完成计算评估工作。当在model_nn()函数运行时(即每次迭代),TensorFlow图会被重新构建并评估更新该张量的值。
3.3-确定用于优化的总成本
练习:实现包含内容成本和风格成本的总成本函数。
GRADED FUNCTION: total_cost
def total_cost(J_content, J_style, alpha = 10, beta = 40):
"""
Computes the total cost function
Arguments:
J_content -- content cost coded above
J_style -- style cost coded above
alpha -- hyperparameter weighting the importance of the content cost
beta -- hyperparameter weighting the importance of the style cost
Returns:
J -- total cost as defined by the formula above.
"""
### START CODE HERE ### (≈1 line)
J = alpha * J_content + beta * J_style
### END CODE HERE ###
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_{style}(S,G)进行线性组合而成
其中\alpha 和\beta 分别代表调节内容与风格之间相对重要程度的关键参数
4-解决优化问题
最后,请将所有物品集中统一应用于实现Neural Style Transfer技术。
-
生成一个Interactive Session
-
导入内容图像
-
导入风格图像
-
随机初始化生成的图像
-
调用预训练的VGG16模型
-
利用TensorFlow图进行操作:
利用VGG模型对输入的内容图像进行处理以计算内容成本;
利用VGG模型对输入的风格图像进行处理以计算风格成本;
计算总成本;
然后定义优化器并设置学习率。- 初始化TensorFlow graph并运行大量的迭代,在每一步更新生成的图像。
我们已经实现了关于J(G)的整体成本计算。下一步的任务是通过TensorFlow优化函数图中的节点G。为了实现这一目标,在当前版本中我们需要重新配置计算图并启用交互式会话机制。与传统静态session不同的是,在这种设计模式下,默认情况下交互式session充当了构建计算图时的启动角色。这样一来,在运行过程中就无需维护或引用session对象的相关信息内容
# Reset the graph
tf.reset_default_graph()
# Start interactive session
sess = tf.InteractiveSession()
代码解释
加载、重塑和规范我们的“内容”形象(卢浮宫的图片):
content_image = scipy.misc.imread("images/louvre_small.jpg")
content_image = reshape_and_normalize_image(content_image)
代码解释
加载、重塑和规范我们的“风格”形象(克劳德莫奈的画):
style_image = scipy.misc.imread("images/monet.jpg")
style_image = reshape_and_normalize_image(style_image)
代码解释
现在,在nst_utils.py中调用generate_noise_image(…)函数来设置'生成'目标图像是基于内容图象而产生的噪声版本。在初始化过程中使用的像素主要来自噪声成分,并与原始内容成分存在一定关联度。这种设计有助于'生成'目标图的内容迅速模仿'内容'目标图的特点。(可以查看nst_utils.py中generate_noise_image(…);点击 'File–>Open…' 在笔记本左上角)
generated_image = generate_noise_image(content_image)
imshow(generated_image[0])
代码解释

加载VGG模型:
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计算内容成本。
# Assign the content image to be the input of the VGG model.
sess.run(model['input'].assign(content_image))
# Select the output tensor of layer conv4_2
out = model['conv4_2']
# Set a_C to be the hidden layer activation from the layer we have selected
a_C = sess.run(out)
# Set a_G to be the hidden layer activation from same layer. Here, a_G references model['conv4_2']
# and isn't evaluated yet. Later in the code, we'll assign the image G as the model input, so that
# when we run the session, this will be the activations drawn from the appropriate layer, with G as input.
a_G = out
# Compute the content cost
J_content = compute_content_cost(a_C, a_G)
代码解释
请注意查看以下信息:a_G代表的是一个张量。请注意,在我们运行Tensorflow graph的过程中,在model_nn()函数内部,在每一次循环周期中进行评估与更新操作。
请注意查看以下信息:a_G代表的是一个张量。请注意,在我们运行Tensorflow graph的过程中,在model_nn()函数内部,在每一次循环周期中进行评估与更新操作。
# Assign the input of the model to be the "style" image
sess.run(model['input'].assign(style_image))
# Compute the style cost
J_style = compute_style_cost(model, STYLE_LAYERS)
代码解释
练习:利用和,通过调用totalcost()来计算总成本J,使用alpha=10和beta=40。
### START CODE HERE ### (1 line)
J = total_cost(J_content, J_style, alpha=10, beta=40)
### END CODE HERE ###
代码解释
在TensorFlow中设置 Adam optimizer ,学习速率为2.0。
# define optimizer (1 line)
optimizer = tf.train.AdamOptimizer(2.0)
# define train_step (1 line)
train_step = optimizer.minimize(J)
代码解释
练习:编写model_nn()函数;该函数负责设置tensorflow图中的变量;将起始生成的图像配置为VGG16模型的输入;并负责训练步骤的操作。
def model_nn(sess, input_image, num_iterations = 200):
# Initialize global variables (you need to run the session on the initializer)
### START CODE HERE ### (1 line)
sess.run(tf.global_variables_initializer())
### END CODE HERE ###
# Run the noisy input image (initial generated image) through the model. Use assign().
### START CODE HERE ### (1 line)
sess.run(model["input"].assign(input_image))
### END CODE HERE ###
for i in range(num_iterations):
# Run the session on the train_step to minimize the total cost
### START CODE HERE ### (1 line)
sess.run(train_step)
### END CODE HERE ###
# Compute the generated image by running the session on the current model['input']
### START CODE HERE ### (1 line)
generated_image = sess.run(model['input'])
### END CODE HERE ###
# Print every 20 iteration.
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))
# save current generated image in the "/output" directory
save_image("output/" + str(i) + ".png", generated_image)
# save last 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 = 5.05035e+09
content cost = 7877.67
style cost = 1.26257e+08
启动后,在笔记本顶部菜单栏选择并双击"文件"选项卡,随后切换至指定路径

完整的代码路径为:deeplearning.ai/Art Generation with Neural Style Transfer-v2.ipynb
