TensorFlowで画風変換を少し説明しつつ実装してみる

# 本記事に関して
本記事は、[TensorFlow Advent Calendar 2017](https://qiita.com/advent-calendar/2017/tensorflow)の 19日目の記事です。

また、**本記事では、コードを載せると冗長になると思いましたので、実行可能なコードはGitHubにて公開しております。参照しながらご覧ください。**
https://github.com/isboj/neuralart_tf_ja
コード自体は、JupyterNotebook形式で以下になります。
https://github.com/isboj/neuralart_tf_ja/blob/master/neuralart_tf_ja.ipynb


# 画風変換とは
コンテンツ画像とスタイル画像を用意して、コンテンツ画像にスタイルを適用して、新しい出力を得ようというものです。
以下のように、コンテンツとスタイルの二つの入力画像の特徴を持った画像を生成します。
![4-Figure2-1.png](https://qiita-image-store.s3.amazonaws.com/0/154325/6ebb618b-df39-e5c7-165b-fe6f287f8027.png)

この手法自体は、[参考論文](#参考論文)で発表されたもので、私もその論文の内容に沿って今回説明してみます。しかし、完全に理解できているか怪しい部分もありますので、何かありましたら、コメント欄よりご連絡ください。

# 今回の環境
Python 3.6
TensorFlow 1.2


# まずは、VGG-19 Networkを入手
今回紹介する画風変換手法のアイデアは、VGG-19 Networkという画像分類で利用するモデルを画風変換に利用しようというものです。VGG-19では高度に画像分類ができる、つまり、**高度に特徴を抽出できる能力**があります。そこで、コンテンツ画像とスタイル画像の特徴を抽出して、それらを上手く融合させれば、新しい画像が生成できるというものです。  
この、私では到底思いつかない発想を、実現したことに、この論文の素晴らしさがあります。　　
それでは、VGG-19 Networkをダウンロードします。
http://www.vlfeat.org/matconvnet/pretrained/
上のサイトより、VGG-VDモデルの**imagenet-vgg-verydeep-19**をダウンロードしてください。
今回は、ダウンロードしたファイルを
[カレントディレクトリ]>[models]
に保存しました。

# VGGネットワークを実装
**GitHubで公開しているJupyterNotebookファイルを参照しながらご覧ください。**
https://github.com/isboj/neuralart_tf_ja/blob/master/neuralart_tf_ja.ipynb

それでは、VGGネットワークを実装します。画像分類をするわけではなく、特徴抽出の仕組みを使いたいので、畳み込み層と、プーリング層の部分のみ実装します。
TensorFlowのネットワークを定義する関数は、以下になります。
`def build_net(ntype, nin, rwb=None):`
そして、先程ダウンロードした、imagenet-vgg-verydeep-19.matから最適化された、重みなどを取ってくる関数が以下になります。
`def get_weight_bias(vgg_layers, i):`
最後に、上の二つの関数を使いながら、ネットワークを組み立てる関数が以下になります。
`def build_vgg19(path):`

# 誤差関数の定義
VGGネットワークについては、上の関数を定義することで組み立てられました。つまり、画像の特徴を抽出できるようになりました。これを用いて、コンテンツ画像、スタイル画像双方の特徴を抽出できますが、問題は、どの様に特徴抽出した2つの画像を融合させるかにあります。
この、融合のさせ方により、生成される画像もスタイルが弱いものであったり、強いものであったりするわけですが、今回は、論文で紹介されているとおりにします。
ここで、どのように誤差を求めるのか考えます。出力される画像は、コンテンツとスタイルの特徴をバランスよく含んでいる必要があります。
そこで、最小にすべき誤差($\mathcal{L}_{total}$)は、

```math
\mathcal{L}_{total} = \alpha\mathcal{L}_{content} + \beta\mathcal{L}_{style}

```

ということになります。上の式では、**コンテンツと出力の誤差($\mathcal{L}_{content}$)**と**スタイルと出力の誤差($\mathcal{L}_{style}$)**を求めて足しているだけです。そして、$\alpha$と$\beta$を変えることで、コンテンツとスタイルの影響力を調整できます。それでは、それぞれの誤差をどの様に計算するのか説明します。

## コンテンツと出力の誤差
ここでは、簡単に言えば、コンテンツと出力の2乗誤差を取っているだけです。
$l$層におけるコンテンツ$\vec{p}$と出力$\vec{x}$の誤差は

```math
\mathcal{L}_{content}(\vec{p},\vec{x},l) = \frac{1}{2}\sum_{i,j}(F_{ij}^{l}-P_{ij}^{l})^2
```

となります。
$i , j$は畳み込みフィルタの位置を表します。
$F_{i, j}^{l}$は、$l$層における、フィルタ位置$i,j$でのフィルタの活性度の総和です。 
$P_{i, j}^{l}$は、元画像(コンテンツ画像の)特徴表現です。

>上の部分は、プログラムでは以下になります。
`def build_content_loss(p, x):`

## スタイルと出力の誤差
スタイルのについても、考え方は、コンテンツの誤差の場合と同様です。
しかし、スタイルは特徴を個々のフィルタ出力の相関で表現するため、グラム行列を用います。

```math
G_{ij}^{l} = \sum_{k}F_{ik}^{l}F_{jk}^{l}
```
$i , j$は畳み込みフィルタの位置を表します。

>上の部分は、プログラムでは以下になります。
`def gram_matrix(x, area, depth):`
また、スタイル自体もグラム行列に対応した形に変形するため、プログラムには以下の関数があります。
`def gram_matrix_val(x, area, depth):`

以上より、$l$層におけるスタイル$\vec{a}$と出力$\vec{x}$の誤差は

```math
E_{l} = \frac{1}{4N_l^2M_l^2}\sum_{i,j}(G_{ij}^{l}-A_{ij}^{l})^2
```
となります。
$N_l$はフィルタ数、$M_l$はフィルタサイズです。
$A_{ij}^{l}$はスタイル画像の特徴表現です。

>上の部分は、プログラムでは以下になります。
`def build_style_loss(a, x):`

そして、スタイルの誤差に関しては、複数の層の和を取るので、最終的に以下になります。


```math
\mathcal{L}_{style}(\vec{a},\vec{x}) = \sum_{l=0}^Lw_lE_l
```
>上の部分は、プログラムでは、関数として定義しておらず。誤差を求める際に和を取るようになっています。
`cost_style = sum(map(lambda l: l[1]*build_style_loss(sess.run(net[l[0]]) ,  net[l[0]]), STYLE_LAYERS))`
こうすることで、どの層から特徴を取るか、`STYLE_LAYERS`を変えることで柔軟に操作ができます。

>ちなみに、ソースコードを見て頂ければわかりますが、コンテンツと出力の誤差についても同様にしています。
`cost_content = sum(map(lambda l,: l[1]*build_content_loss(sess.run(net[l[0]]) ,  net[l[0]]), CONTENT_LAYERS))`
こうすることで、コンテンツについても、`CONTENT_LAYERS`を変えることで柔軟に操作ができます。




# 画風変換を実行
ここまでで、画風変換を実装するにあたり、最も重要である部分の説明を致しました。説明不足の部分はあるかと思いますが、GitHubで公開しているコードにもコメントを記入しておりますので、そちらも参照お願い致します。
それでは、今回は誤差関数の定義で説明致しました、$\alpha$と$\beta$を変えるとどの様に出力画像が変化するのかを見てみます。
**スタイル画像**
<img src="https://qiita-image-store.s3.amazonaws.com/0/154325/75aabb38-2d61-4be0-82d6-49cefd8b8d1c.jpeg" width=400px height= 250px>
  
**コンテンツ画像**
<img src="https://qiita-image-store.s3.amazonaws.com/0/154325/4761bdfc-5506-0508-2dac-1323f83c686a.jpeg" width=400px height=250px>

$\alpha/\beta=10^{-1}$(プログラムの、`STYLE_STRENGTH=10`)のとき
<img src="https://qiita-image-store.s3.amazonaws.com/0/154325/a2185bb7-75e1-8ead-a3fc-4fdbc9f39170.png" width=400px height= 250px>

$\alpha/\beta=10^{-2}$(プログラムの、`STYLE_STRENGTH=100`)のとき
<img src="https://qiita-image-store.s3.amazonaws.com/0/154325/2c819d54-8f69-c46a-201f-8fef51186533.png" width=400px height= 250px>

$\alpha/\beta=10^{-3}$(プログラムの、`STYLE_STRENGTH=1000`)のとき
<img src="https://qiita-image-store.s3.amazonaws.com/0/154325/c09c6eeb-b649-0593-0043-71b3dd18d8e2.png" width=400px height= 250px>

$\alpha/\beta=10^{-4}$(プログラムの、`STYLE_STRENGTH=10000`)のとき
<img src="https://qiita-image-store.s3.amazonaws.com/0/154325/c7c5e5d1-ef54-ed26-6e94-5f7fc1bb7528.png" width=400px height= 250px>


以上から分かりますように、$\alpha/\beta$の値が小さいほど、スタイルが強いので、コンテンツの特徴が失われていきます。上の例では、値を変えていくと瀬戸大橋がだんだんぼやけていることがお分かりになるかと思います。

# 写真のスタイル変換を実行
**GitHubで公開しているJupyterNotebookファイルに変更を加えたものです。**
https://github.com/isboj/neuralart_tf_ja/blob/master/neuralart_tf_ja.ipynb

このプログラムは、パラメータを変更することにより、画風変換以外にも利用することができます。論文では、写真のスタイル変換に利用されていました。
例えば、スタイル画像とコンテンツ画像を以下のようにします。
**スタイル画像(東京の夜景)**
<img src="https://qiita-image-store.s3.amazonaws.com/0/154325/0c1f964d-7a00-2e27-dfc5-c39fcb0a5678.jpeg" width=400px height=250px>
  
**コンテンツ画像(大阪の昼間)**
<img src="https://qiita-image-store.s3.amazonaws.com/0/154325/d300a701-d561-a318-53b2-0b23a910ca63.jpeg" width=400px height=250px>

そして、プログラム中のパラメータを以下のように変更します。

```python3

"""
画風変換の出力を調節するにはここを変更
"""
# 各種パラメータの設定
INI_NOISE_RATIO = 0.5 # ホワイトノイズの重み
STYLE_STRENGTH = 100 # スタイルの強さ
ITERATION = 5000 # 最適化回数

# コンテンツ画像と出力画像で誤差を取る層
CONTENT_LAYERS =[('conv4_2',1.)]
# スタイル画像と出力画像で誤差を取る層
STYLE_LAYERS=[('conv1_1',1.),('conv2_1',1.),('conv3_1',1.),('conv4_1',1.),('conv5_1',1.)]
```

プログラムを実行すると、以下の出力が得られます。
<img src="https://qiita-image-store.s3.amazonaws.com/0/154325/8c2babcd-d2f3-326c-3f38-e98cb2d4fedb.png" width=400px heiht=250px>
美しいとは言えない出力画像ですが、窓に明かりが灯っているなど、昼間の大阪が夜みたいになり、興味深い結果であることはお分かり頂けると思います。
当然、コンテンツと出力の誤差を取る層を変更するとさらに違った結果となります。良い結果が得られるまで、いろいろとパラメータを変えて試してみるとよいかもしれません。


# 最後に
画風変換を少し説明しつつ実装してみると言っておき、本当に、少ししか説明できなかった気もしますが、発表された研究自体は大変面白いものです。また、画風変換以外にもパラメータの設定の仕方によっては応用ができますので、色々と試してみると面白いかと思います。
そして、今回の手法では、画風変換に置いて、コンテンツ画像の色情報は失われてしまいます。しかし、色はコンテンツのままで、画風を変換する手法も提案されています。詳しくは、[参考論文](#参考論文)、[参考サイト](#参考サイト)よりご覧ください。
また、ご意見・ご質問・画風変換の説明の追記の要望等ございましたら、お気軽にコメント欄よりお寄せください。今後も、この記事をもっと内容の濃いものにすべく努めたいと思います。

ご覧頂きありがとうございました。

# 参考論文
**画風変換**
[Image style transfer using convolutional neural networks](https://www.cv-foundation.org/openaccess/content_cvpr_2016/html/Gatys_Image_Style_Transfer_CVPR_2016_paper.html)
Gatys, Leon A., Alexander S. Ecker, and Matthias Bethge.

**画風変換(プレプリント版)**
[A Neural Algorithm of Artistic Style](https://arxiv.org/abs/1508.06576)
Gatys, Leon A., Alexander S. Ecker, and Matthias Bethge.

**画風変換(色情報を保存)**
[Preserving Color in Neural Artistic Style Transfer](https://arxiv.org/abs/1606.05897)
Leon A. Gatys, Matthias Bethge, Aaron Hertzmann, Eli Shechtman, 

# 参考サイト
**TensorFlowでのA Neural Algorithm of Artistic Style / Image Style Transfer Using Convolutional Neural Networksの実装**
https://github.com/ckmarkoh/neuralart_tensorflow
https://github.com/gnperdue/neuralart_tf

**画風変換に関する技術説明**
https://elix-tech.github.io/ja/2016/08/22/art.html
https://research.preferred.jp/2015/09/chainer-gogh/
http://www.renom.jp/ja/notebooks/image_processing/neural-style-transfer/notebook.html

**TensorFlowでのPreserving Color in Neural Artistic Style Transferの実装(画風変換で色情報を保存)**
https://github.com/cysmith/neural-style-tf