ConSinGAN : 1枚の画像から生成できるGANを使ってみた

# はじめに
ディープラーニングによって既存の画像から新しい画像を生成するGAN(Genarative Adversarial Network)という技術は、私がディープラーニングで最も感動した技術ですが、通常学習には膨大な訓練データと時間が必要なので、なかなか個人では試せていませんでした。そんな折、たった1枚の訓練画像から生成することのできるGANを開発したという下記の論文を目にし、調べてみました。

>[Improved Techniques for Training Single-Image GANs](https://arxiv.org/abs/2003.11512)

こちらは今年の3月に投稿された論文で、去年投稿された
>[SinGAN: Learning a Generative Model from a Single Natural Image](https://arxiv.org/abs/1905.01164)

で提案されたSinGAN(Single-Image-GAN)の改善のようです。

# GAN
GAN(敵対的生成ネットワーク)自体については知名度も高く、多くのサイトで説明されているので詳細は省きます。[こちら](https://elix-tech.github.io/ja/2017/02/06/gan.html)などを参照ください。
GANとは訓練データの分布を学習してそっくりなデータを生成する「生成モデル」の一つで、偽物のデータを生成するGeneratorとそれを本物と区別するDiscriminatorを作り、これらを交互に学習させることで最終的に本物と区別できないようなデータを生成するものです。これはよく偽札作りに例えられます。偽札を作る偽造者(Generator)は警察(Discriminator)を騙すように学習していき、警察は偽札を見破れるように学習していきます。
ここで訓練データを$\boldsymbol{x}$,ノイズを$z$,確率分布を$p(\boldsymbol{x})$,$p(z)$,Generatorを$G$,Discriminatorを$D$,Discriminatorが$\boldsymbol{x}$を正しく識別する確率を$D(\boldsymbol{x})$,Generatorの$z$からの生成データを$G(z)$とすると目的関数は以下のようになります。

```math
{\rm min}_{G}{\rm max}_{D}V(D,G)=\mathbb{E}_{\boldsymbol{x}\sim p_{data}(\boldsymbol{x})}[{\rm log}D(\boldsymbol{x})]+\mathbb{E}_{z\sim p_{z}(z)}[{\rm log}(1-D(G(z)))]
```

Generatorは本物を偽物と間違え($D(\boldsymbol{x})\to 0$)、偽物を本物と間違える($D(G(z))\to1$)ようにするため、$V(D,G)$が小さくなるように学習し、Discriminatorはその逆で$V(D,G)$が大きくなるように学習します。
GANにはたくさんの派生があり、画像変換(線画⇔写真、夏の写真⇔冬の写真)や実在しない人間の画像生成など驚くべき技術がたくさん生み出されています。(詳しくは[こちら](https://qiita.com/shionhonda/items/330c9fdf78e62db3402b#%E7%94%BB%E5%83%8F%E5%A4%89%E6%8F%9B))

# SinGAN
従来のGANでは学習に大量の訓練データを必要としますが、SinGAN(Single-Image-GAN)はその名の通り1枚の画像から学習することのできるGANです。SinGANのタスクとしてUnconditional Image Generation、Image Harmonizationなどがあります。(後述)

### 学習方法

SinGANでは図([論文Short Summary](https://www.tobiashinz.com/2020/03/24/improved-techniques-for-training-single-image-gans.html)より引用)のように複数のGeneratorを用い、各Generatorは前のGeneratorの出力画像を入力として受け取ります。また、Generatorはそれぞれ個別に学習し、学習時に前のGeneratorの重みは固定します。(始めにG0を学習し、G0を固定してG1を学習、G0,G1を固定してG2を学習…)
ここで重要な点として、Discriminatorは画像を全体としてでなくパッチとして見るようにし、一部分を見れば本物そっくりだが全体として見ると異なっているような画像を作ることができます。(図の分裂したコロッセオ)

<img width=600 src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/570054/04107ab9-a6ea-d3b0-0de2-d89518b778a2.png>

# ConSinGAN
上記の手法ではGenerator間の相互作用を制限してしまうことが発覚したということで、新しく提案された手法がGeneratorを固定せずに同時に(Concurrently)学習するConSinGAN(Concurrently-Single-Image-GAN)です。

### 学習方法
全てのGeneratorを同時に学習するとオーバーフィッティングしてしまうので、論文では次の2点を取り入れています。
1. 後半3つのGeneratorを同時に学習する。
2. 前のGeneratorに行く度に学習率を1/10倍に小さくする。
これは図([論文Short Summary](https://www.tobiashinz.com/2020/03/24/improved-techniques-for-training-single-image-gans.html)より引用)のようになります。
最適化手法はAdamです。

<img width=600 src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/570054/922e4df2-c4fb-b665-428b-22c2b14c4c79.png>

ここで学習率を小さくすることは生成画像の多様性を作れる分訓練画像の再現性は失われ、トレードオフの関係にあります。

今回このConSinGANの学習方法でGenerator数を減らす事で、SinGANと比べて学習時間が約1/6となり、しかもより高いパフォーマンスをあげたとのことです。

#### モデルアーキテクチャ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/570054/b25e1ddc-08fa-be49-4c7d-29441c1b4418.png)

Genarator,Discriminatorとも図([論文](https://arxiv.org/pdf/2003.11512.pdf)より引用)のように畳み込み層を複数層重ねた構造をしています。ここで次のGeneratorへの入力となる特徴マップはupsamplingした後多様性のためにノイズを加え、さらに出力が大きくずれないようにresidual connectionで繋いでいます。

#### 目的関数
ステージ$n$での目的関数は下記のようになります。

```math
{\rm min}_{G_{n}}{\rm max}_{D_{n}}L_{\rm adv}(G_n,D_n)+\alpha L_{\rm rec}(G_n)
```

ここで$L_{\rm adv}(G_n,D_n)$は目的関数にWasserstein距離を用いた[WGAN-GP](https://qiita.com/triwave33/items/5c95db572b0e4d0df4f0)([論文](https://arxiv.org/abs/1704.00028))の目的関数で識別の精度を表す項で、$L_{\rm rec}(G_n)$は生成画像と訓練画像の距離で学習の安定性を表す項です。

```math
L_{\rm adv}(G_n,D_n)=\mathbb{E}_{z\sim p_{z}(z)}[D(G(z))]-\mathbb{E}_{{\boldsymbol x}\sim p_{data}}[D({\boldsymbol x})]+\lambda \mathbb{E}_{{\hat{\boldsymbol x}}\sim p_{{\hat{\boldsymbol x}}}}[(||\nabla_{{\hat{\boldsymbol x}}} D({\hat{\boldsymbol x}})||_2-1)^2]\\

L_{\rm rec}(G_n)=||G_n(x_0)-x_n||_2^2
```
$\alpha$はデフォルト10の定数で、${\hat{\boldsymbol x}}$は訓練データと生成データを結んだ直線上の点、$\lambda$は定数、$x_n$は訓練画像、$x_0$は$G_n$への入力画像です。
ただし$L_{\rm adv}(G_n,D_n)$の入力はタスクによって異なり、例えばUnconditional Image Generationではノイズが与えられますがImage Harmonizationでは訓練画像をAugment(一部を切り取ったり色を変えるなどのノイズを加える)した画像を与えた方が良い結果になったそうです。


## タスク
今回はUnconditional Image GenerationとImage Harmonizationを使ってみました。他にも興味深いタスクが述べられているので興味のある方は論文をご覧ください。

### Unconditional Image Generation
図([論文Short Summary](https://www.tobiashinz.com/2020/03/24/improved-techniques-for-training-single-image-gans.html)より引用)のように訓練画像にランダムにノイズ$z$を加え、大域的な構造を保ちながらリアルな実在しない画像を作ることができます。(画像サイズを変えても調整しているのが分かります。)

<img width=600 src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/570054/da2c7c74-5b61-df14-bfd4-9b986e424ab8.png alt=a>

### Image Harmonization
図([論文Short Summary](https://www.tobiashinz.com/2020/03/24/improved-techniques-for-training-single-image-gans.html)より引用)のように訓練画像(絵画など)を学習し、加えたオブジェクトを学習した画像のスタイルで調和させます。

<img width=600 src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/570054/5b4fe539-a6ee-7c7c-f8f4-d68d68ea07b1.png>

#### ファインチューニング
Image Harmonizationでは、さらに学習済みのモデルにnaive画像(上図のNaive)を加えて訓練し直すことでより良い結果を得ることができます。(上図のFine-tune)

# 実装
公開されている[GitHub](https://github.com/tohinz/ConSinGAN)をcloneして、無料でGPUが使用できるGoogle Colaboratory上で動かしました。

```python:Setup
#リポジトリクローン
!git clone https://github.com/tohinz/ConSinGAN.git
#使用ライブラリインストール
pip install -r requirements.txt
```
途中でColaboratoryのツールのバージョンが違うという内容のエラーが出ましたが動作は問題ありませんでした。

## Unconditional Generation
Images/Generation/に訓練画像を置きます。以前ヴェネツィアで撮影したリアルト橋(rialto.jpg 1867×1400ピクセル)を使ってみます。

<img width=300 src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/570054/3f9a0756-1d46-3d6a-d029-7476127d8dd0.jpeg>

```pyrhon:Unconditional_Generation
!python main_train.py --gpu 0 --train_mode generation --input_name Images/Generation/rialto.jpg
```
--gpuで使用するGPU(デフォルトで0)、--train_modeでタスクの指定(generation, harmonizationなど)、--input_nameで訓練画像のpathを指定します。なお、今回は変更していませんが任意で学習率(--lr_scale)やGenerator数(--train_stage)なども変えることができます。
デフォルトではGenerator数5, それぞれ2000イテレーションで学習しています。
サイズが大きいからか訓練には69分かかりました。結果はTrainedModels/rialto/yyyy_mm_dd_hh_mm_ss_generation_train_depth_3_lr_scale_0.1_act_lrelu_0.05というところにできました。生成サンプルをいくつか載せます。

![gen_sample_4.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/570054/ebafab8d-c927-610f-acc1-53547628a2e7.jpeg)![gen_sample_8.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/570054/f71a3add-95ec-8893-25fa-c5e9a59e838d.jpeg)![gen_sample_23.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/570054/aed0d9f0-15ad-a891-27b7-4faa15a15e1f.jpeg)

少しノイズが強いですがグニョグニョした橋ができました。

## Image Harmonization

###[絵画](http://gahag.net/tag/%E3%83%95%E3%83%AA%E3%83%BC%E7%B5%B5%E7%94%BB/page/2/)×猫

絵画(干し草作り)に猫の写真を合わせてみます。
左が訓練画像(300×300ピクセル hoshikusa.jpg)、右がnaive画像(142×130ピクセル hoshikusa_naive.jpg)です。これらをImage/Harmonization/に配置します。
なお本来は追加するオブジェクトの部分を切り取ったマスク画像を配置するようですが作れなかったのでマスクなしで作成しています。

<img width=300 src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/570054/30014ec5-a9fa-464a-0853-50d03f524957.jpeg><img width=300 src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/570054/e7d86b75-da34-35e1-8d75-6f9d2eef9a83.jpeg>

```python:訓練
!python main_train.py --gpu 0 --train_mode harmonization --train_stages 3 --min_size 120 --lrelu_alpha 0.3 --niter 1000 --batch_norm --input_name Images/Harmonization/hoshikusa.jpg 
```
訓練時間は15分でした。続いて訓練モデルにnaive画像を適用します。

```python:評価
!python evaluate_model.py --gpu 0 --model_dir TrainedModels/hoshikusa/yyyy_mm_dd_hh_mm_ss_harmonization_train_depth_3_lr_scale_0.1_BN_act_lrelu_0.3 --naive_img Images/Harmonization/hoshikusa_naive.jpg
```
結果はTrainedModels/hoshikusa/yyyy_mm_dd_hh_mm_ss_harmonization_train_depth_3_lr_scale_0.1_BN_act_lrelu_0.3/Evaluation/にできます。

<img width=300 src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/570054/7616c986-4c0f-4582-63ee-6747899c70d6.jpeg>

分解能が悪いですが写真の猫の色が変わりました。
さらにファインチューニングしてみます。

```python:ファインチューニング
!python main_train.py --gpu 0 --train_mode harmonization --input_name Images/Harmonization/hoshikusa.jpg --naive_img Images/Harmonization/hoshikusa_naive.jpg --fine_tune --model_dir TrainedModels/hoshikusa/yyyy_mm_dd_hh_mm_ss_harmonization_train_depth_3_lr_scale_0.1_BN_act_lrelu_0.3
```

イテレーション数をデフォルトの2000回(11分)やると左のように背景の色と一体化してオーバーフィッティングしてしまいました。論文では500回で十分と述べられています。(右)

<img width=300 src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/570054/4a7b6b4b-dc54-853c-5233-607f340f65c2.jpeg><img width=300 src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/570054/b7bb42d2-d81e-00dc-3657-00057490affc.jpeg>




### [白黒背景](https://github.com/tohinz/ConSinGAN)×ラーメン

写真を漫画風に変換してみます。
左が訓練画像(pen_building.jpg 600×337ピクセル)、右がnaive画像(pen_building_naive.jpg 283×213ピクセル)です。

<img width=300 src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/570054/87b7451b-d730-e402-7c01-967632d8b6d1.jpeg><img width=300 src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/570054/43e24828-6f86-5958-5191-3bd49be4ee0e.jpeg>


同様に実行して結果は次のようになりました(訓練時間9分)。左が通常評価、右がファインチューニング(100イテレーション)です。

<img width=300 src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/570054/c6a391d5-86b5-88cc-3256-e919269b326c.jpeg><img width=300 src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/570054/90d67a6c-6aaf-1407-cbae-07e8bb998260.jpeg>

ラーメンを漫画風にできました。このタスクはファインチューニングしなくても十分のようです。


# まとめ
今回は個人的に興味を持っていたGANのモデルで、1枚の訓練画像で生成できるSinGANを改善したConSinGANについての論文を読み、動かしてみました。画像生成技術はたくさんのモデルが出ていて感動するような技術が多く、結果も分かりやすいので楽しいですね。ただ、著作権の問題で自分の好きなアニメや漫画の画像を使って作ったものを公開できないのは残念でした。