画像認識以外のディープラーニングを試してみた

# はじめに
この記事は [NTTテクノクロス Advent Calendar 2018](https://qiita.com/advent-calendar/2018/ntt-tx) の16日目です。

はじめまして。NTTテクノクロスの福田(@fuku-ys) です。

普段は、不揮発性メモリに関する研究開発支援や[分散ストレージの検証・運用・サポート](https://www.ntt-tx.co.jp/products/osscloud/?utm_source=qiita&utm_medium=ac2018&utm_campaign=16) などに携わっています。
それに加え、今年度新たにディープラーニングに関係する仕事に携わることになり、ディープラーニングの世界に足を踏み入れたところです！

とは言っても、ディープラーニング基盤のハードウェア周りの調査や、ノード間の集団通信やGPU間のメモリコピー転送の高速化などに取り組んできましたので、実のところディープラーニング自体は学習処理を動かすための題材として付き合ってきました:laughing:


# 今回やってみたこと
ディープラーニングといえば、顔認証や自動車の自動運転などに応用されている画像認識のモデルをイメージする方が多いのではないでしょうか。ディープラーニング界の"hello world"と言われているMNISTもこの画像認識のモデルになります。

モデルとしては他にも、Google翻訳など文章の構造を扱う自然言語処理や、音声データを扱う音声認識など様々な種類がありますが、今回は個人的に興味があった画像生成のモデルを実際に動かして試してみることにしました。（何となく凄そうで楽しそうですよね:satisfied:）


# 画像生成モデルとは？
画像生成モデルは、学習した画像に似た新たな画像を生成するモデルです。
その実現のため、GAN(Generative Adversarial Networks)や、GANのアルゴリズムを改良したDCGAN(Deep Convolutional Generative Adversarial Networks)が利用されます。

GANやDCGANは、ノイズを元に新たな画像を生成するGeneratorと、入力された画像がGeneratorによって作られた偽者なのか本物なのかを判定するDiscriminatorの2つのネットワークを使います。
Discriminatorは判定の正解率が上がるようにネットワークの勾配を更新していき、GeneratorはDiscriminatorが判定を誤る向きへ勾配を更新していきます。
お互いが競い合うように学習をすすめることから、偽札の製造者(:hurtrealbad:)と、偽札を見破る警察(:cop:)の関係に例えられることが多いようです。 

詳しいアルゴリズムについては、有識者の方がQiitaの記事としても解説されていますのでここでは割愛します。(ぜひ検索してみてください)


# DCGANを実行してみた
## 環境情報
今回利用した環境の概要は以下です。

* OS: Ubuntu16.04
* GPU: Tesla K80
* CUDA: 9.2
* Chainer: 4.5.0(pipコマンドでインストール)


## 前準備
pipコマンドでインストールしたChainerにはサンプルプログラムが含まれませんので、githubからサンプルプログラムを含むChainerのソースコード一式をgit cloneコマンドで持ってきます。

```Bash
# git clone https://github.com/chainer/chainer.git
```
git cloneコマンドで持ってきたソースコードはmasterブランチのものになりますので、pipでインストールしたChainer 4.5.0のバージョンに合わせるため、対応するタグをチェックアウトします。

```Bash
# cd chainer/
# git checkout -b v4.5.0 refs/tags/v4.5.0
Switched to a new branch 'v4.5.0'
```

DCGANのサンプルプログラムは以下のディレクトリにあります。

```Bash
# ls -al examples/dcgan
total 280
drwxr-xr-x  2 root root   4096 Nov 28 10:59 .
drwxr-xr-x 17 root root   4096 Nov 28 10:59 ..
-rw-r--r--  1 root root    616 Nov 28 10:57 README.md    # READMEファイル
-rw-r--r--  1 root root 252276 Nov 28 10:57 example_image.png    # README内で使われる画像ファイル 
-rw-r--r--  1 root root   3632 Nov 28 10:59 net.py    # ニューラルネットワークの実装
-rw-r--r--  1 root root   4567 Nov 28 10:59 train_dcgan.py    # 本体
-rw-r--r--  1 root root   1459 Nov 28 10:59 updater.py    # 勾配更新処理の実装
-rw-r--r--  1 root root   1095 Nov 28 10:59 visualize.py    # 作成した画像をフォルダに出力する処理の実装
```

DCGANサンプルプログラムの実行に必要なパッケージをインストールします。

```Bash
# pip install pillow
```

以上でDCGAN実行のための準備は完了です。

## 実行1: CIFAR-10データセットで実行
まずはサンプルプログラムをそのまま実行してみます。

サンプルプログラムの実装を見てみると、Chainerで用意されているCIFAR-10データセットを取得するAPIを利用して、ラベルなし、RGBの各要素の明度を0から255の値として取得する(人間が見た時に自然に見えると理解)引数で実行しているようです。
CIFAR-10は、全10種でラベル分けされた32*32ピクセルのRGB画像で、計6万枚(学習用5万枚、検証用1万枚)からなるデータセットです。

```Python:examples/dcgan/train_dcgan.py
    if args.dataset == '':
        # Load the CIFAR10 dataset if args.dataset is not specified
        train, _ = chainer.datasets.get_cifar10(withlabel=False, scale=255.)
```

では、実行してみます。

```Bash
# python examples/dcgan/train_dcgan.py --gpu 0
```

デフォルトでは、1000イテレータごとにカレントディレクトリのresultディレクトリ配下に学習済みデータを圧縮したnpyファイルと、その配下のpreviewフォルダに生成した画像を100枚並べた画像サンプルが出力されます。

* 1000イテレータ目で生成した画像サンプル
<img width="1280" alt="image00001000.png" src="https://qiita-image-store.s3.amazonaws.com/0/318642/fa33350e-2f53-fa23-f6cc-b6ebd96e29a1.png">

* 25000イテレータ目で生成した画像サンプル
<img width="1280" alt="image00025000.png" src="https://qiita-image-store.s3.amazonaws.com/0/318642/e3aa3837-34fd-c929-428d-d1941c279f59.png">

* 50000イテレータ目で生成した画像サンプル
<img width="1280" alt="image00050000.png" src="https://qiita-image-store.s3.amazonaws.com/0/318642/0426436a-2797-9162-24b0-dfa565869371.png">

最初はノイズだらけですが、学習が進むについて鮮明になっていきました。
ただ、10種の画像がごちゃまぜになっていて、車っぽいけどちょっと違ったり、動物っぽいけど何なのか分からないような画像が生成されてしまいました。


## 実行2: CIFAR-10データセットのうち猫画像だけを抽出して実行
CIFAR-10の全10種あるすべてのラベルの画像を使用すると、生成される画像もごちゃまぜなものなってしまったので、次は猫に絞ってやってみました。猫にしたのはなんとなくウケが良さそうだからです:smirk:

CIFAR-10データセットの中身を調べた結果、猫のラベルは3番でした。

* label=0: airplane(飛行機)
* label=1: automobile(自動車)
* label=2: bird(鳥)
* label=3: cat(猫)
* label=4: deer(鹿)
* label=5: dog(犬)
* label=6: frog(カエル)
* label=7: horse(馬)
* label=8: ship(船)
* label=9: truck(トラック)

以下のようにサンプルプログラムを修正し、ラベル3番のみを格納したnumpy配列を新たに生成してデータセットとして使用するようにします。

```Python:examples/dcgan/train_dcgan.py
    if args.dataset == '':
        # Load the CIFAR10 dataset if args.dataset is not specified
#        train, _ = chainer.datasets.get_cifar10(withlabel=False, scale=255.)    # コメントアウトし、以下を追加
        train, _ = chainer.datasets.get_cifar10(withlabel=True, scale=255.)
        cat_images = np.empty(0, dtype=np.float32)
        count = 0
        for i in train:
            if i[1] == 3:    # 猫のラベルである3のみをnumpy配列に格納
                cat_images = np.append(cat_images, i[0])
                count += 1
        train = np.reshape(cat_images, (count, 3, 32, 32))
```

では、実行してみます。

```Bash
# python examples/dcgan/train_dcgan.py --gpu 0
```

* 1000イテレータ目で生成した画像サンプル
<img width="1280" alt="image00001000.png" src="https://qiita-image-store.s3.amazonaws.com/0/318642/872092ae-8763-0291-708a-5ee3c2b44119.png">

* 25000イテレータ目で生成した画像サンプル
<img width="1280" alt="image00025000.png" src="https://qiita-image-store.s3.amazonaws.com/0/318642/cb1c5e48-43e0-561f-1e93-6a58964c92a4.png">

* 50000イテレータ目で生成した画像サンプル
<img width="1280" alt="image00050000.png" src="https://qiita-image-store.s3.amazonaws.com/0/318642/8cf5626c-5415-e2de-8993-e8a4b1c76fc5.png">


うーん。かわいい猫たちを期待したのですが、猫には見えない微妙な画像がたくさん生成されてしまいました。


## 実行3: ラーメン画像を使用して実行
CIFAR-10ではあまりよい結果にならなかったのですが、他のデータセットで試したくなりました。
そこでダメ元で[PostgreSQL芸人](https://qiita.com/nuko_yokohama/items/085b75ee4c0938936ab9)であり、またラーメン通である ぬこ＠横浜さん(@nuko_yokohama) にお願いしたところ、快くコレクションの中から約1000枚を提供いただけました！(ありがとうございます)

こんな感じで、1枚1枚ファイル名に日付と店名が入っています。す、すごい。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/318642/2091355a-cefe-f9c8-f55d-88efa30a0642.png)

これをデータセットとして使用してみます。

画像は横長のものでしたので、正方形になるよう編集します。今回サイズは128*128 ピクセルにしました。
以下はその際に使用したコマンドです。

```Bash
# 無慈悲にもファイル名を連番になるようリネーム
ls * | awk '{ printf "mv %s %04d.jpg\n", $0, NR }' | sh

# 縦横の比率そのままで縦サイズを128ピクセルにリサイズ
mogrify -resize x128 *.jpg

# 横サイズを中心を起点に128ピクセル残してリサイズ
mogrify -resize x128 -gravity center -crop 128x128+0+0! *.jpg
```

この後、餃子、チャーハンの画像が混ざっていることに気がつきましたので手動で除外しました。

また、サンプルプログラムのニューラルネットワークはCIFAR-10の32*32ピクセルに合うよう実装されていますので、今回使用する128*128ピクセルに合わせて以下のように修正しました。

```Bash:examples/dcgan/net.py
class Generator(chainer.Chain):

#    def __init__(self, n_hidden, bottom_width=4, ch=512, wscale=0.02):    # コメントアウトし、以下を追加
    def __init__(self, n_hidden, bottom_width=16, ch=512, wscale=0.02):

(snip)

class Discriminator(chainer.Chain):

#    def __init__(self, bottom_width=4, ch=512, wscale=0.02):    # コメントアウトし、以下を追加
    def __init__(self, bottom_width=16, ch=512, wscale=0.02):
```

iオプションでラーメン画像を格納したディレクトリを指定して、実行してみます。

```Bash
# python examples/dcgan/train_dcgan.py --gpu 0 -i /root/train_nuko/
```

* 1000イテレータ目で生成した画像サンプル
![image00001000.png](https://qiita-image-store.s3.amazonaws.com/0/318642/14a7f9b2-dd26-02cc-559d-7a4390e536e3.png)

* 25000イテレータ目で生成した画像サンプル
![image00025000.png](https://qiita-image-store.s3.amazonaws.com/0/318642/aab8a527-bac0-cebf-d372-438949a6fc67.png)

* 50000イテレータ目で生成した画像サンプル
![image00050000.png](https://qiita-image-store.s3.amazonaws.com/0/318642/f85816ac-131c-5466-3341-e963bdbc4d44.png)

なんと、思ったよりもラーメンっぽいラーメン画像が生成されました！
データセットとして、画像の構図が揃っている(器を真上から写したもの)ことが影響しているんでしょうか。


# さいごに
今回DCGANを3つのデータセットで試してみて、データセットによっては割と良い画像が生成できることが分かりました。

このDCGANの技術をどうビジネスに活かせるのだろうという観点で調べてみたところ、送電線の保守点検のためのディープラーニングで活用されている[事例](https://www.toshiba-sol.co.jp/tech/sat/case/1710_1.htm)がありました。
データセットを大量に用意することが困難なケースで、DCGANを使い少ない画像データから類似した画像を生成し、画像認識のモデルに学習させることで画像認識の精度向上を実現しているようで、DCGANの可能性を感じました。

Chainerにはまた異なるモデルのサンプルプログラムが用意されてるので、色々試してディープラーニングの世界をもっと見て回りたいと思います。