【MNIST】データ拡張で「汎化性能」UPっぷ【認識率UP!】

# ❶はじめに
AIの`'Hello World'`と呼ばれる`MNIST(AIによる数字認識)`について、今日もゆるりと試行錯誤していきたいと思います。
![AI入学式.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405376/0b96715f-efaa-869d-a755-ed942c3b7a7a.png)

## 自分で書いた数字を認識させてみた
[『`0からはじめる「Python AIプログラミング」for Google Colab`](https://qiita.com/PoodleMaster/items/f6b7205c4c20948e9c0d)』で実施した通り、MNISTの認識精度は99.1%以上となり、**ほぼ完成**しているように見えました。

``` :0からはじめる「PythonAIプログラミング」for&nbsp;Google&nbsp;Colab&nbsp;の結果
Test loss     : 0.024685267359018326
Test accuracy : 0.991599977016449
```
しかし、『[`【MNIST】自分の数字をAIに認識させよう`](https://qiita.com/PoodleMaster/items/33dae7a3b10dc117fbed)』で自分の手書き数字が正しく認識されるか試してみたところ、**意外と誤認識が多い**ことが分かりました。
![before.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405376/8029b718-ae32-5b29-0fdf-6ea098784121.png)

> これはつまり、**MNISTが用意したテストデータでの認識精度が99.1%以上になったに過ぎない**ということですね。
> →考えてみたら、そりゃそーなんだけどね😅

## 誤認識サンプル
例えばですが、『[`0からはじめる「Python AIプログラミング」for Google Colab`](https://qiita.com/PoodleMaster/items/33dae7a3b10dc117fbed)』で作ったモデルを使用しての、誤認識サンプルです。
### (1) 誤認識：1を6と勘違い
結構まっとうに書いたんだけどダメか…
<img src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405376/57e4c047-c68c-2214-38c6-6f521e1b395f.png' width=400>

### (2) 誤認識：2を7と勘違い
回転しているとダメか…
<img src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405376/b087b965-d28f-9fdd-dc0d-037443f2587a.png' width=400>

### (3) 誤認識：7を2と勘違い
縮小して端に書くとダメか…
<img src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405376/aa372c10-060a-0341-41dd-2bcadb80c001.png' width=400>


# ➋今回やること
ということで、『[`0からはじめる「Python AIプログラミング」for Google Colab`](https://qiita.com/PoodleMaster/items/f6b7205c4c20948e9c0d)』で作ったモデルでは、**汎化性能が低い**ことが分かったので、**学習データを回転、拡大・縮小、移動**などさせて、もっと多種多様な入力にも対応できるモデルを作れるかを試してみたいと思います。
名付けて、**汎化性能UPっぷ大作戦！**

> 「ツール作るのメンドクセ」という方はこちらからすぐに遊べます。
> <font color="DeepPink"><strong>HEROKUへデプロイしました。すぐに遊べます😊！</strong></font> 
> → https://mnister-web.herokuapp.com/

# ➌ImageDataGeneratorの利用
MNISTの学習データを汎化するため、多種多様な学習データを用意する必要があります。しかし、新規にデータを用意したりラベル付けする事は、とても大変です。そこで、kerasライブラリに用意されている`ImageDataGenerator`というリアルタイムに`データ拡張(Data Augmentation)`ができる関数を使い、学習させたいと思います。

> ■Keras Documentation
> https://keras.io/ja/preprocessing/image/#imagedatagenerator

`ImageDataGenerator`の設定値ですが、今回は数字なので水平反転、垂直反転はしないようにしました。また、回転角度を90度にしてしまうと数字の6と9を混同してしまう恐れがあるため、回転角度は-50～50度としました。テストデータを見ると数字の一部しかないような悪質な画像もありますが、今回はなるべく画像ははみ出さないよう、左右、上下のスライド幅も調整してします。

``` python:
# ImageDataGenerator設定
GenTrain = ImageDataGenerator(
    featurewise_center=False,               # データセット全体で,入力の平均を0に調整
    samplewise_center=False,                # 各サンプルの平均を0に調整
    featurewise_std_normalization=False,    # 入力をデータセットの標準偏差で正規化
    samplewise_std_normalization=False,     # 各入力をその標準偏差で正規化
    zca_whitening=False,                    # ZCA白色化のイプシロン
    rotation_range=50,                      # 回転角度(-50～50度)
    width_shift_range=0.3,                  # 左右のスライド幅
    height_shift_range=0.2,                 # 上下のスライド幅
    zoom_range=[1.0,1.5],                   # 拡大・縮小率
    horizontal_flip=False,                  # 水平反転しない
    vertical_flip=False)                    # 垂直反転しない
```

### ImageDataGeneratorが吐き出すサンプル画像
上記の設定において`ImageDataGenerator`が吐き出す画像を確認したところ、以下のように回転、拡大・縮小、左右・上下スライドなどができています。数字がハミ出ていないし、文字潰れも少ないことが確認できたので、これらを使って学習していきます！

<img src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405376/157ff730-41fa-e1ff-68be-ffbbe37569bb.png' width=120><img src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405376/3289bf5a-a213-a89d-a0bf-d06dde8f087a.png' width=120><img src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405376/cb288d5f-f2ca-d7f2-71fa-6cab4745353f.png' width=120><img src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405376/ed367b1b-be95-0fe0-3e81-22b7d0309940.png' width=120><img src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405376/f9253456-2525-d603-c708-9f2d9907bf29.png' width=120>

# ➍MNISTデータ整理
MNISTで共有されているデータは、学習データ60,000個、テストデータ10,000個です。
今回は学習データの中からバリデーション用に2割を分別したいと思います。

```
・学習データ　(60,000)─┬─学習データ         (48,000)
　　　　　　　　　　　　│
　　　　　　　　　　　　└─バリデーションデータ(12,000)
・テストデータ(10,000)
```


# ➎ネットワーク定義
今回は学習データのバリエーションをもっと増やすので、複雑なものも扱えるようにネットワーク定義を少し調整してみました。

```
Model: "sequential"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
conv2d (Conv2D)              (None, 28, 28, 64)        640       
_________________________________________________________________
activation (Activation)      (None, 28, 28, 64)        0         
_________________________________________________________________
conv2d_1 (Conv2D)            (None, 28, 28, 64)        36928     
_________________________________________________________________
activation_1 (Activation)    (None, 28, 28, 64)        0         
_________________________________________________________________
max_pooling2d (MaxPooling2D) (None, 14, 14, 64)        0         
_________________________________________________________________
dropout (Dropout)            (None, 14, 14, 64)        0         
_________________________________________________________________
conv2d_2 (Conv2D)            (None, 14, 14, 64)        36928     
_________________________________________________________________
activation_2 (Activation)    (None, 14, 14, 64)        0         
_________________________________________________________________
conv2d_3 (Conv2D)            (None, 14, 14, 64)        36928     
_________________________________________________________________
activation_3 (Activation)    (None, 14, 14, 64)        0         
_________________________________________________________________
max_pooling2d_1 (MaxPooling2 (None, 7, 7, 64)          0         
_________________________________________________________________
dropout_1 (Dropout)          (None, 7, 7, 64)          0         
_________________________________________________________________
conv2d_4 (Conv2D)            (None, 7, 7, 128)         73856     
_________________________________________________________________
activation_4 (Activation)    (None, 7, 7, 128)         0         
_________________________________________________________________
flatten (Flatten)            (None, 6272)              0         
_________________________________________________________________
dense (Dense)                (None, 64)                401472    
_________________________________________________________________
dense_1 (Dense)              (None, 10)                650       
=================================================================
Total params: 587,402
Trainable params: 587,402
Non-trainable params: 0
_________________________________________________________________
```

# ➏学習結果
学習データ48,000個、イテレーション数100回、バッチ数480個、epoc数200回(200回繰り返し)として学習しました。

## AccutuaryとLossのグラフ
今回入力データを複雑になったのでepoc数も200と大幅に増加させていますが、少し過学習傾向になっていました。
<img width=400 src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405376/dbfa3000-d857-e169-baa6-a82fb5edb561.png'> <img  width=400 src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405376/1d0d4cd9-cc69-8965-15b3-28c045881056.png'>

## MNISTのTESTデータで検証した結果
`ImageDataGenerator`を用いて汎化学習させたあと、MNISTのTESTデータを使用して評価(Evaluate)を行いました。結果は、以下の通りとなり、汎化学習させた結果としては、なかなか良いのではないでしょうか。

``` :【MNIST】ImageDataGeneratorで汎化性能UPっぷ!の結果&nbsp;&nbsp;(MNISTのTESTデータをそのまま使用)
Test loss    : 0.026212161406874657
Test accuracy: 0.9926000237464905
```

## Confusion Matrixのヒートマップ
MNISTが用意した10,000個のテストデータを推測させ、ヒートマップを作成しました。ほぼ正解なので、左上から右下へ向かってのみ色がついています。ここの数字の意味合いとしては、例えば、「5と推測し、本当に5でした」というのが885個、「2と推測したけど、本当は7でした」というのが6個、「8と推測したけど、本当は9でした」というのが8個あったというのが分かります。

> ・縦軸：正解ラベル
> ・横軸：推測ラベル

![HeatMap.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405376/d3050630-ff4e-51f6-fddd-84fc3e9d30e3.png)


## 誤認識データ一覧
MNISTが用意した10,000個のテストデータを推測させ、誤りのみを抽出しました。
誤りの結果を見てみると、人間でも判断が分かれそうなものが結構ありますね😅

> ・左上の黄色数字：正解ラベル
> ・右下の赤色数字：AIが推測したラベル

NG count :  74個 / 10,000個
![NG.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405376/af0af295-7e10-f93b-a922-7af7a1ea7165.png)

> 誤認識した結果を見ると、もう少し頑張れる余地はあるものの、MNISTのテストデータで、**認識率100%**を取ろうというのは**ナンセンス**だということが分かりました。

# ➐改善効果確認
ということで、まだまだ改善の余地はありそうですが、本番はここからです！
さっそく先程誤認識したものが改善しているか、**ImageDataGenerator**を利用して学習させたモデルを使用して、**汎化性能がUPっぷ**したのかを確認していきます。

## 改善効果サンプル
### (1) 正しく認識：6と勘違いしていたものが、正しく1と認識
問題ないッスね！
<img src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405376/1bc9a4ae-26df-e5f2-d519-3ba4b2279ce2.png' width=400>

### (2) 正しく認識：7と勘違いしていたものが、正しく2と認識
回転もかなり強くなりました！!

<img src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405376/726627fb-4071-e5a1-9685-18ff2071cd27.png' width=400>


### (3) 正しく認識：2と勘違いしていたものが、正しく7と認識
縮小や位置移動などにもかなり強くなりました！
<img src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405376/1a7759ba-a282-93ae-dd09-fb0df2f2677f.png' width=400>

## 結果
結果は、先程のもの程度なら**バッチリ改善**されました！
他にも色々と手書き文字で試しましたが、今回の`ImageDataGenerator`により、かなり**汎化性能がUPっぷ**したことが分かりました😄
![after.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405376/fcd8eccf-4693-7c56-75f4-45618b607fb5.png)

# ➑ソースコード
### (1) 今回のソースコード (MNIST_ImageDataGenerator)
今回の`ImageDataGenerator`を利用したモデル作成のソースコードは、以下の**GitHub**で公開しております。ご参考ください。
> <img width=64 src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405376/3a342531-d030-6a66-678e-45f328de072f.png'><img width=120 src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405376/7e2c1f12-f920-922c-81f2-312da1406ad0.png'>
> Github : https://github.com/PoodleMaster/MNIST_ImageDataGenerator

### (2) mnisterツール
Browserで手書きの文字を入力できるツール(低認識のモデル付き)です。モデルを差し替えて遊んでください。こちらは『[【MNIST】自分の数字をAIに認識させよう](https://qiita.com/PoodleMaster/items/33dae7a3b10dc117fbed)』の記事で公開しておりますので、よろしければご利用ください。ソースコードは、以下の**GitHub**で公開しております。

> <img width=64 src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405376/3a342531-d030-6a66-678e-45f328de072f.png'><img width=120 src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405376/7e2c1f12-f920-922c-81f2-312da1406ad0.png'>
> Github : https://github.com/PoodleMaster/mnister

![mnister](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405376/1b39532a-e5f7-1106-8c10-9d8b10a54d3d.gif)

# ➒以上
自分で書いた数字をきちんと認識してくれるとなんだか嬉しくなりますよね。
お疲れ様でした！