在 2015 年,Gatys 及其小伙伴们提出一个算法:A Neural Algorithm of Artistic Style,利用它,我们可以将某张艺术作品的风格迁移另外一张图片中。
在这片论文之后,陆续出现不少算法对其进行补充/优化,又或是以另一种思路达到更优结果,文末会对这些算法稍有提及。本文主要针对 Gatys 算法中的主要思路及部分细节做些介绍,后续系列文章会以此为扩展,介绍其他算法,以及填上本文中遗留的坑。
0. 简介
文首提到 NST 可以将某艺术作品风格迁移另外一张图片中。从这句如此简单的描述里面,我们可以提炼出这几个关键点:
- 给定某艺术作品,如何定义/概括其风格?
- 如何控制合成图中艺术痕迹深浅?
- 如何得到合成图?
以上这三点将成为贯穿全文的主线脉络。下面先来看看第一点:
1. 艺术风格(Style)
给定一幅艺术作品,要如何定义它的「艺术风格」?或者先来回忆一下,我们通常如何评价某幅作品的「风格」?
上面两张图是 NST 中常用的 Style Image 之二。当我们眼睛扫过这两张作品的瞬间,大脑便会对这两张作品形成大致的印象。我们可能会觉得“Starry Night”是迷幻的,绚丽的;而“Water Lilies”是朦胧的。那为什么“Starry Night”会让人觉得它是迷幻的呢?
如果我来回答,我可能会说:“因为图里面使用了大量的螺旋,曲线并且还用了比较强烈的明暗色调对比”。这么一来,似乎 Style 的定义就自然而然流露出来了。现在我们可以说:艺术作品的风格主要由作品中的显著特征 (Feature) 共同塑造出来。换而言之,我们可以认为 Style 就是一些 Feature 的集合。那怎样用数学表示呢?
1.1 ConvNet
为了用数学的方式来定义图片的 Style,我们需要利用「卷积神经网络」(Convolutional Neural Network,或 ConvNet)。ConvNet 背后的数学原理其实并不复杂,可从此讲解视频中跟随 Andrew Ng 的讲解学习,也推荐他在 Coursera 上开设的相关课程。
举一个 ConvNet 作为例子,它的前两层为 Convolutional Layer,所使用的 Filter 为 3x3,那么这两层的 Forward Propagation 过程如下图所示(为了简便这里先省略了 Channel):
从图中可以得到对 ConvNet 的一个直觉上的认知:
- Layer 1 的 Filter 只「看到」原图中 3x3 大小的区域;
- 通过 Layer 1,Layer 2 的 Filter 可以间接地「看到」原图中 5x5 大小的区域;
如果把它稍作推广,我们可以知道:
- Hidden Layer 中每个 Filter(或者 Unit)只能「看到」图像的局部;
- 层级越深,该层的 Filter 能「看到」区域越大;
上面用了若干个「看到」来描述某个 Filter 的操作所涉及到的原图区域大小。那么 ConvNet 在经过训练迭代,完成学习之后,这些处于各个不同 Layer 中的 Filter,从他们看到的东西中,学习到了什么呢?
1.2 Visualizing ConvNets
以 VGG-19 为例,假设我们输入一张图片,接着选中第一层的某个 Filter,找到此 Filter 输出的 Activation 中最大的 9 个值所对应的图片切片,它们可能是如下图红框部分所示:
而类似地,同一层中其他 Filter 所对应的图片切片,可能如上图其他部分所示。看起来这一层的 Filter 所做的工作是识别不同朝向的边缘。接下来,对于第二层的 Filter,对应的图片切片则可能如下:
看起来比第一层复杂了一点点,识别出了某种纹理,像是多种边缘的组合。然后是第三层:
至此我们很容易得到一种猜测:层级越深,Filter 所识别的内容(特征,Feature)越复杂。而实际上这也正是 ConvNets 内部不同层级的 Filter 所具有的规律。这个结论也与前一部分中「层级越深,该层的 Filter 能看到区域越大」此直觉认知相呼应。更详细的论证以及实现过程可以参考这篇论文以及这篇博客,还没来得及手动实现一遍,之后会把这个坑填上。
1.3 Gram Matrix
稍微整理一下,到了这里,我们已经有下面的结论:
- 艺术风格由作品中的 Feature 集合共同塑造出来,也就是说 Feature 之间具有关联性;
- ConvNets 中的 Filter 能识别出图像中的 Feature,Feature 与 Filter 的模式越匹配则 Activation 越高;
- ConvNets 中层级越深,Filter 看到的区域越大,识别的 Featrue 越复杂;
于是,我们可以考虑利用 ConvNets 中某些层的输出(Activation)来代表作品中具有的显著 Feature,然后利用某种数学方式表示作品中 Feature 的关联性,便能得到对这幅作品 Style 的数学定义。由此,我们引入概念 Gram Matrix,用以表达 Feature 的关联性。
先选中某层的 Activation 输出,并且把它 Flatten,得到一个 $(n_H \times n_W) \times n_C$ 的矩阵,Flatten 后的矩阵可以看作是一系列向量的集合 $(v_1, \dots, v_n)$,其中每个向量都是某个 Filter 的 Activation:
接着我们计算矩阵 $G$,其中 $G_{ij} = v_i^T v_j$ 得到 Gram Matrix 如下图:
于是,矩阵中的元素 $G_{ij}$ 便体现着不同 Filter 对应的 Feature 之间的关联性。例如:假如图像中同时具有上图中绿色以及蓝色两个 Filter 对应的 Feature,那么 $G_{01}$ 的值就会较大,也就是说他们关联性较高;反之 $G_{01}$ 的值就会较小。
至此,我们得到了 Style 的数学表达方式:使用 ConvNets 中某些层的 Gram Matrix 来代表此图像的 Style。
2. Cost
Cost 可以被看作是一个指标,稍后会给出这个它的数学定义。Cost 将被用于衡量合成图与原图以及风格图之间的相似度。为了定义此 Cost,我们下面将引入 Style Cost 以及 Content Cost 两个概念。
2.1 Style Cost
在上文我们得到了 Style 的数学定义(Gram Matrix)之后,下一步就是利用它,来计算一个指标,用以衡量两张图像之间风格的远近。假设现在我们有风格图 $S$ 以及合成图 $G$,并且我们选用某一层 $l$ 的 Activation 来代表整个图像,那么我们可以定义一个函数 $J_{style}^{[l]}$ 来表示这个指标:
$$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 $$
我们把这个指标称为 Style Cost。上边公式分为两部分,前部分是一个用于 Normalize 的常量,后半部分则是计算两个 GM 之间的方差和,也就是它们的「距离」。注意此处出现了命名冲突,GM 以及合成图 G 都用了 $G$ 来表示,需要注意根据上下文分辨。
上面我们得到的是针对网络中某一层所计算得到的 Style Cost,也就是说只涉及到该层中的 Feature。实际上我们可以考虑组合使用其他层的 Feature,并为它们赋予不同的权重,从而得到一个更具代表性的 Style Cost:
$$J_{style}(S,G) = \sum_{l} \lambda^{[l]} J^{[l]}_{style}(S,G)$$
其中 $\lambda^{[l]}$ 就是不同层的 Style Cost 对应的权重,调节不同的 $\lambda$ 组合会影响最终生成的图片的风格,一般来说保证 $\sum_{l}\lambda^{[l]} = 1$ 即可。
从直觉上的认知,当我们在训练的过程中最小化 Style Cost 时,就像是在告诉合成图:你必须具有 Style Image 中的这些纹理,但我不管它们出现在哪里。
2.2 Content Cost
与 Style Cost 类似,Content Cost 也可以看作是一个指标,而这个指标用于衡量两张图片的内容的相似度。选定 ConvNets 中的某一层,假设输入原图 $C$ 以及合成图 $G$,在选定层所得到的 Activation 分别记为 $a^{(C)}$ 和 $a^{(G)}$,那么 Content Cost 将由以下公式表示:
$$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 $$
与 $J_{style}$ 类似,$J_{content}$ 也由两部分组成:前部分是一个用于 Normalize 的常量,后半部分则是计算两个 Activation 之间的方差和,也就是它们的「距离」。
注意,这里我们只选择了一层的 Activation 来计算 Content Cost。前面提到,层级越低,识别到的 Feature 越基础(如边缘识别),层级越高识别到的 Feature 越高级(例如眼睛),所以:
- 假如我们选择了较浅的层,比如说第一层,那么在 Content Cost 的束缚下,合成图将跟原图在像素级别上相似,简单来说就是合成的过程将没有「发展空间」;
- 假如我们选择了较深的层,可能 Contnet Cost 只保证合成图里面有眼睛,但不保证眼睛是不是在脸上;
显然,我们需要选择网络中处于比较中间的一层,来计算 Content Cost,以避免上面提及的问题。
2.3 Total Cost
现在我们有了 Content Cost 来衡量合成图与原图之间的内容相似度,以及 Style Cost 来衡量合成图与风格图之间的风格相似度。接下来,我们把 Content Cost 以及 Style Cost 组合起来,得到一个新的指标
$$J(G) = \alpha J_{content}(C,G) + \beta J_{style}(S,G)$$
公式中的 $\alpha$ 以及 $\beta$ 控制合成图的整体偏向。简单来说,如果 $\alpha$ 更高,合成图将更偏向原图;如果 $\beta$ 更高,合成图将更偏向风格图。
3. 合成
合成图 $G$ 的生成方法是是这个算法中比较有趣的地方,$G$ 并不是作为 ConvNet 的输出,而是作为 ConvNet 的输入(同时也是 ConvNet 的参数)然后进行训练迭代,经过一定次数的迭代后,我们便得到了想要的合成图。
根据原作者的 Paper,我们将使用 VGG-19 这个图像识别 Model,它是在 ImageNet 这个相当庞大的数据库之上被训练出来的,因此它可以识别出大量基础以及高级的图像特征(Feature)。
3.1 Training
要进行训练,首先我们需要定下一个目标,它是构建整个训练算法的基础。而在这里,我们的训练目标是:使 $J(G)$ 最小化。这意味着合成图在内容上需要贴合原图,在风格上需要贴合风格图。
训练的过程大致上可以拆分为 3 个部分:
- 迭代: 一个外层循环,循环中做的事情为如下两点;
- Forward Propagation: 给 VGG-19 输入我们的初始合成图 $G$,计算得到 $J(G)$;
- Back Propagation: 通过 BP(即 Backpropagation) 来更新输入的合成图 $G$;
> 关于 BP,可以把它简单地看作是一种**用于优化网络中的参数**的算法,它利用了导数的链式法则以及偏导数。在本次迭代中计算得到 $J(G)$ 之后,通过 BP 来更新网络中的参数,使得下一次迭代计算到的 $J(G)$ 减少。
最后,在此次训练中,Content Cost 以及 Style Cost 的计算所取用的 Layer 如下图所示:
另外,编码实现时也有一些趣的地方:
- 加载模型时没有必要加载 Fully Connect Layer,因为完全不需要使用;
- 用
sess.run
来运行某一层来得到输出; - 计算时可以复用所构建的 Computation Graph,只需要 assign 不同的 imput image;
4. 小结
可以尝试调整算法的一些地方来改变得到的合成图:
- Style Cost 中的所选用的 Layer 已经对应的 Weight;
- Content Cost 中的所选用的 Layer;
- Total Cost 中的 $\alpha$ 以及 $\beta$;
此算法最大的优点就是所需数据量少,从头到尾所需要的数据仅仅是我们我们的 Content Image 以及 Style Image。而缺点则是时间久,因为每次合成图片都需要重新进行训练迭代。
4.1 其他算法
这篇文章里面提到了另一种实现的思路,可以实现即时的合成,不需要重新训练。主要思想是采集大量的图片数据,来训练一个图片转换网络(Image Transformation Network, ITN)。训练所用的 Cost Function 仍然借用 VGG-19,由此构成了一个 Loss Network。进行 BP 时不是更新合成图,而是更新 ITN 的参数。训练
文章 最后还提到另一种更高级的玩法,可以对于任意给定 Style Image 以及 Content Image 进行合成。主要思想是复用了大部分已经训练好的参数,每次合成只针对 ITN 某些重要的参数进行重新训练。
End
本文所用算法的源代码已经放到Github上,任意取阅。
参考资料
- 论文 Visualizing and Understanding Convolutional Networks;
- 论文 A Neural Algorithm of Artistic Style;
- 博客 How convolutional neural networks see the world;
- 博客 Neural Artistic Style Transfer: A Comprehensive Look
- 课程 Courses by DeepLearning.ai in Coursera;
- 视频 Visualizing Convolutional Filters from a CNN;
- 模型 imagenet-vgg-verydeep-19;