Deep Learning with Python
这篇文章是我学习《Deep Learning with Python》(第二版,François Chollet 著) 时写的系列笔记之一。文章的内容是从 Jupyter notebooks 转成 Markdown 的,你可以去 GitHub 或 Gitee 找到原始的 .ipynb
笔记本。
你可以去这个网站在线阅读这本书的正版原文(英文)。这本书的作者也给出了配套的 Jupyter notebooks。
本文为 第8章 生成式深度学习 (Chapter 8. Generative deep learning) 的笔记之一。
8.3 Neural style transfer
神经风格迁移
神经风格迁移(neural style transfer),基于深度学习的神经网络,将参考图像的风格应用于目标图像,同时保留目标图像的内容,创造出新的图像。
神经风格迁移的思想很简单:定义一个损失函数来指定要实现的目标,然后将这个损失最小化。这里的目标就是保存原始图像的内容,同时采用参考图像的风格。
假设有函数 content 和 style 分别可以计算出输入图像的内容和风格,以及有范式函数 distance,则神经风格迁移的损失可以表达为:
loss = distance(content(original_image) - content(generated_image)) +
distance(style(reference_image) - style(generated_image))
事实上,利用深度卷积神经网络,是可以从数学上定义 style 和 content 函数的。
损失定义
- 内容损失
卷积神经网络靠底部(前面)的层激活包含关于图像的局部信息,靠近顶部(后面)的层则包含更加全局、抽象的信息。内容就是图像的全局、抽象的信息,所以可以用卷积神经网络靠顶部的层激活来表示图像的内容。
因此,给定一个预训练的卷积神经网络,选定一个靠顶部的层,内容损失可以使用「该层在目标图像上的激活」和「该层在生成图像上的激活」之间的 L2 范数。
- 风格损失
不同于内容只用一个层即可表达,风格需要多个层才能定义。风格是多种方面的,比如笔触、线条、纹理、颜色等等,这些内容会出现在不同的抽象程度上。所以风格的表达就需要捕捉所有空间尺度上提取的外观,而不仅仅是在单一尺度上。
在这种思想下,风格损失的表达,可以借助于层激活的 Gram 矩阵。这个 Gram 矩阵就是某一层的各个特征图的内积,表达了层的特征间相互关系(correlation)的映射,它就对应于这个尺度上找到的纹理(texture)的外观。而在不同的层激活内保存相似的内部相互关系,就可以认为是“风格”了。
那么,我们就可以用生成图像和风格参考图像在不同层上保持的纹理,来定义风格损失了。
神经风格迁移的 Keras 实现
神经风格迁移可以用任何预训练卷积神经网络来实现,这里选用 VGG19。
神经风格迁移的步骤如下:
- 创建一个网络,同时计算风格参考图像、目标图像和生成图像的 VGG19 层激活;
- 使用这三张图像上计算的层激活来定义之前所述的损失函数;
- 梯度下降来将这个损失函数最小化。
在开始构建网络前,先定义风格参考图像和目标图像的路径。如果图像尺寸差异很大,风格迁移会比较困难,所以这里我们还统一定义一下尺寸:
# 不使用及时执行模式
import tensorflow as tf
tf.compat.v1.disable_eager_execution()
# 定义初始变量
from tensorflow.keras.preprocessing.image import load_img, img_to_array
target_image_path = 'img/portrait.jpg'
style_referencce_image_path = 'img/transfer_style_reference.jpg'
width, height = load_img(target_image_path).size
img_height = 400
img_width = width * img_height // height
这里图片我选择了:
- transfer_style_reference: 文森特·梵高《麦田里的丝柏树》(A Wheatfield, with Cypresses),1889年,收藏于纽约大都会博物馆。
- portrait: 保罗·高更《不列塔尼牧人》(The Swineherd, Brittany),1888年,收藏于美国加州洛杉矶郡立美术馆。
接下来,我们需要一些辅助函数,用于图像的加载、处理。
# 辅助函数
import numpy as np
from tensorflow.keras.applications import vgg19
def preprocess_image(image_path):
img = load_img(image_path, target_size=(img_height, img_width))
img = img_to_array(img)
img = np.expand_dims(img, axis=0)
img = vgg19.preprocess_input(img)
return img
def deprocess_image(x):
# vgg19.p