ペンギン分類器をつくってみた（後編）

# はじめに
この記事は[SRA Advent Calendar 2018](https://qiita.com/advent-calendar/2018/sra) の23日目の記事です。

# やったこと
## 下準備
[前編記事](https://qiita.com/hirosemi/items/f4ca703948ff014f2dd5)のとおりです。
128x128サイズ（足りない部分は黒でパディング済み）のペンギン6種（エンペラーペンギン、キングペンギン、アデリーペンギン、ヒゲペンギン、ジェンツーペンギン、イワトビペンギン）各500枚の写真を用意しています。これをトレーニングデータ、検証用データにして、Keras(backendはtensorflow)で学習させます。
さらに、各60枚のテストデータで、作成した学習モデルの正解率を計算します。

ディレクトリ構成は下記の通り

    |
    |-  penguin_classfications_class.ipynb   : jupyter notebook (4層CNN)
    |-  penguin_classfications_class_imagenetbased.ipynb  : jupyter notebook(VGG16 ImageNet based)
    |- train　　(train+validation用画像　各500枚)
        |- adelie_penguin      : アデリーペンギン
        |- chinstrap_penguin   : ヒゲペンギン
        |- emperor_penguin     : エンペラーペンギン
        |- gentoo_penguin      : ジェンツーペンギン
        |- king_penguin        : キングペンギン
        |- rockhopper_penguin  : イワトビペンギン
    |- test     (test用画像　各60枚)
        |- adelie_penguin
        |- chinstrap_penguin
        |- emperor_penguin
        |- gentoo_penguin
        |- king_penguin
        |- rockhopper_penguin

コード（jupyter notebook)はgithubにあげました。

- [4層CNN](https://github.com/hirosemi/penguins_classfications/blob/master/penguin_classfications_class.ipynb)
- [VGG16 ImageNet based](https://github.com/hirosemi/penguins_classfications/blob/master/penguin_classfications_class_imagenetbased.ipynb)


# 作成したモデル
今回のモデル作成はおもに「[PythonとKerasによるディープラーニング](https://www.amazon.co.jp/dp/B07D498RJK/)」 の第5章を参考にしています。
あと、コード実装では [小さなデータセットで良い分類器を学習させるとき](https://qiita.com/daisukelab/items/381099590f22e4f9ab1f) を参考にさせていただきました。

データの読み込みはImageDataGenerator.flow_from_directory() を使っています。ImageDataGeneratorのvalidation_splitを指定することで、トレーニングデータと検証用データにわけています。validation_split=0.2としました。

flow_from_directoryメソッドでsubset='training' を指定するとトレーニング用のサブセット、subset='validation'を指定すると検証用サブセットがとれるようなので、これを使いました。[本家のドキュメントには書いてあるんですが](https://keras.io/preprocessing/image/#flow_from_directory) 、 日本語版のドキュメントに入ってないのでわりと新しいオプションなんでしょうか。

## 4層CNN、データ拡張なし
以下のような単純な畳み込み層とプーリング層が交互に4層重ねたネットワークを使いました。

```python
def network(input_shape, num_classes):
    model = models.Sequential()
    model.add(layers.Conv2D(32, (3, 3), activation='relu', input_shape=input_shape))
    model.add(layers.MaxPooling2D(2, 2))
    model.add(layers.Conv2D(64, (3, 3), activation='relu'))
    model.add(layers.MaxPooling2D(2, 2))
    model.add(layers.Conv2D(128, (3, 3), activation='relu'))
    model.add(layers.MaxPooling2D(2, 2))
    model.add(layers.Conv2D(128, (3, 3), activation='relu'))
    model.add(layers.MaxPooling2D(2, 2))
    model.add(layers.Flatten())
    model.add(layers.Dense(512, activation='relu'))
    model.add(layers.Dense(num_classes, activation='softmax'))
    return model
```
手始めにデータ拡張なしで試したところ、

![image.png](https://qiita-image-store.s3.amazonaws.com/0/143174/fd649494-08a6-bdf9-63e9-0685af86983f.png)

![image.png](https://qiita-image-store.s3.amazonaws.com/0/143174/d69ddd00-1ca1-dfcf-fdf1-3a87c623f5bb.png)

15epoch くらいでもう過学習に突入しているようです。validation accも63くらい。
テスト用検証データで確認したところ、正解率は63.6%でした。

### CAMで可視化
CAM(Class Activation Map)で4層目の畳み込み層出力特徴マップと入力画像を重ねて、画像のどこが判断の決め手になったのかを確認します。

これはキングペンギンなのですが、エンペラーペンギンと判定しています。しかも特徴として強く効いているのが、サイズ合わせの上下のパディング部分。これはおかしい。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/143174/dc79939c-454a-cbf7-5857-c5bb7ac8f538.png)

こちらはキングペンギンをキングペンギンと判定しています。正解はしていますが、どうも強く効いた部分がペンギン本体からはみ出して背景の雪の部分に入っています。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/143174/09d7db65-03cc-0e38-b587-e8d531c240cf.png)

結構当てずっぽうのようです。trainデータが各500枚(x0.8)しかないので無理もないです。

## 4層CNN、データ拡張あり
データが少ないときはデータ拡張をすると効くはず。そこでKerasのImageDataGeneratorの機能でデータ拡張して同じ4層CNNで学習させました。
今度は良い感じにval accが上がっていったので200epoch回しています。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/143174/a663fd3a-34a0-b570-8ae3-7bad34fac623.png)

![image.png](https://qiita-image-store.s3.amazonaws.com/0/143174/c4c7f811-5718-3b9f-46cf-dd73a996ed41.png)

テストデータに対しての正解率は87.5%までいきました。

### CAM

まずは失敗したほうからです。
キングペンギンをエンペラーペンギンと誤認識していますが、判定に使ったのはペンギンの頭の下部のところでまあ、悪くはないです。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/143174/7e39d6ff-709b-aff2-093b-ce58f0abb2ad.png)

こちらが正解したほう。イワトビペンギンを正しく判定しました。イワトビペンギンの特徴である頭部分が効いています。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/143174/a1f32634-8c10-94e0-2a99-684a0401f64d.png)

他のCAMを見てみても、このモデルがかなりよくペンギンの特徴を捉えて判定しているのが見て取れました。

## VGG16 ImageNetのファインチューニング（データ拡張なし）
小さなデータセットを用いたディープラーニングの効果的アプローチは、学習済みネットワークを使用すること、とKeras本にありましたので、ImageNetで訓練したVGG16の最後の畳み込みブロック5のみ解凍して再学習したモデルを作ってみます。
まずはデータ拡張なしでやったところ、

![image.png](https://qiita-image-store.s3.amazonaws.com/0/143174/1b8fd7b6-7f2f-e566-5b4d-1c62f4cf9818.png)

![image.png](https://qiita-image-store.s3.amazonaws.com/0/143174/e5618db7-6913-6ded-ac50-cc5c5005cb05.png)

この前に30epochsのVGG16ベースネットワーク凍結での学習をしていますが、正解率は0.8を少し上回っていました。データ拡張なしの4層ネットワークと比べると、かなり上がりました。
テストデータに対する正解率は82.2%でした。

### CAM
これは正解の例です。イワトビペンギンと認識したのですが、判定に使っているのが、ペンギンの背中や足元の部分、背景の雪の部分で当てずっぽう感が拭えません。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/143174/df63a57e-a3a0-ed81-d90f-21fdcd37b730.png)


## VGG16 ImageNetのファインチューニング（データ拡張あり）
では、データ拡張ありでファインチューニングをやってみたらどうでしょう。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/143174/418bd130-412c-e7e7-ba84-655d83665009.png)

![image.png](https://qiita-image-store.s3.amazonaws.com/0/143174/b654d27a-39ed-23fb-1921-c26c596321c3.png)


やはり、この前に30epochsのVGG16ベースネットワーク凍結での学習をしています。
検証データの正解率は当然データ拡張なしよりは良いですが、単純な4層データ拡張ありとそれほど変わりません。

テストデータに対しての正解率は86.9%で残念ながら4層+データ拡張に及びませんでした。

### CAM

このようなキングペンギンの正解はいい感じに特徴である首の下を見ていますが、

![image.png](https://qiita-image-store.s3.amazonaws.com/0/143174/e2c3eed3-70da-9c62-caeb-5a67ae13af36.png)


ジェンツーペンギンを足元だけで判断していたりします。わたしだって足を見ただけでペンギン判断できませんが。。。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/143174/626b6399-4381-8328-972f-4bd4850f6a07.png)

一般的には転移学習したほうが性能向上が見られるはずなので、この結果はどこかが間違っているのではないかなあと思っています。しかし、間違っている箇所が現状不明。。。

# できなかったこと

- MixUpもやりたかったのですがまたの機会に。
- 画像検索でキングペンギンをエンペラーペンギンと間違えている例が多かったので、学習モデルはキングペンギンとエンペラーペンギンをどれくらい間違えるのか調べたかったのですが時間切れ。


# おわりに
画像を集めるところからはじめて分類器を作成しました。

- モデル作成の前のラベルづけは人力だから大変。
- KerasでCNNを用いた画像分類器は、サンプルコード見よう見まねで簡単につくれる。
- 正解率をあげたければ、画像の水増しは必須。
- CAMで可視化してどこが効いているかをチェックし、モデルの正当性検証したほうがよい。


# 参考文献

- [PythonとKerasによるディープラーニング](https://www.amazon.co.jp/dp/B07D498RJK/)
-  [小さなデータセットで良い分類器を学習させるとき](https://qiita.com/daisukelab/items/381099590f22e4f9ab1f)