MNISTで文字判定プログラムを作ってCNNの基本を知る

# 概要

- 手書きの0~9の数字を判定するプログラムを実装して、画像分類するCNNモデルの概略をつかむ
- データセットはmnistを使う
- CNNモデルはkerasで作成する

### 補足

- 勉強中の知識のアウトプットなので間違いがあるかもしれません。ご承知おきください。

# 環境等

- Google Colab
- keras version: 2.15.0

## 実装

- 学習して数字を判定するまでの大まかな流れ
    - step1 : 学習に使うデータの準備
    - step2 : 学習
    - step3 : 推論(学習で得られたモデルで判定する)

### step1 学習に使うデータの準備

#### mnistからデータセットをダウンロードする

- `keras.load_data`でmnistのデータセットをダウンロードする
- ダウンロードしたデータセットは トレーニング用とバリデーション用に分かれている
- データセットは 画像データ(x)と 正解ラベル(y)を持つ。正解ラベルは画像が示す数字である。

```python
import keras
import keras.datasets import mnist

# mnist からトレーニングデータセットとバリデーションデータセットをダウンロードする 
# xは画像データ、yは画像の判定ラベル
# x_trainデータは28*28ピクセル画像が60000枚 (60000, 28, 28)
# x_testデータは28*28ピクセル画像が10000枚 (10000, 28, 28)
(x_train, y_train), (x_test, y_test) = mnist.load_data()
```

#### load_dataの中身
- x_train[0]
<img width="200" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2851499/9728fd76-1fa1-99d6-7dae-e7ab304f0477.png">

- この画像の正解ラベル y_train[0] は `1`

#### データの前処理

- 正解ラベル(y)を 10個の確率データ の形式に変換する。今の正解ラベルは 1~9のラベルになっており、学習モデルの出力形式である10個の確率データに合わないため。
    - 例: [0, 1, ..., 0]。左から数字1,2, ... 9の確率
- 画像データ(x)は扱いやすいように 0~1の間の数値にする。今は min:0(白) ~ max:255(黒)を表す。

```python
from keras.utils import to_categorical

# mnistの画像データはグレースケール画像であり、min:0(白)~max:255(黒)を表す。入力を0~1の範囲にした方が都合がよいので255で割る。
X_train = x_train.reshape(60000, 28,28,1)/255
X_test = x_test.reshape(10000, 28,28,1)/255
# 学習モデルの出力が 10個の確率データ(例: [0, 1, ..., 0])になるので、今の形式(画像が0~9のうちのどの数字か)はかみ合わない。そこで形式をそろえるために変換する
Y_train = to_categorical(y_tr, 10)
Y_test = to_categorical(y_te, 10)
```

### step2 学習

#### 学習モデルを用意する

- kerasの`Sequential`モデルを使って学習モデルを作成する
    - `Sequential`モデルはニューラルネットワークを構築するための1つの方法
    - ニューラルネットワークは複数の層（レイヤー）で構成される。`Sequential`では層を1つずつ積み重ねて構築していく。

```python
from keras.models import Sequential
from keras.layers import Dense , Activation, Convolution2D , MaxPooling2D, Flatten

model = Sequential()
model.add(Convolution2D(10, (5,5), input_shape=(28,28,1), activation="relu")) # 2D畳み込み層
model.add(MaxPooling2D(2,2)) # Maxプーリング層
model.add(Convolution2D(10, (5,5), padding="same"))
model.add(MaxPooling2D(2,2))
model.add(Convolution2D(10, (5,5), padding="same"))
model.add(MaxPooling2D(2,2))
model.add(Flatten()) # 平坦化層。入力画像を１次元配列に変換して出力する。全結合層に接続する前に使われる。
model.add(Dense(10, activation="softmax")) # 全結合層
```

#### 畳み込み層(Convolution)とは

- 画像の特徴点を見つけること
- 画像にカーネルを充てることで特徴点(特徴マップ)を得る
- カーネルの数字によって抽出できる特徴が変わる。
- CNNにおいてカーネルが重みであり、よいCNNモデルとは最適なカーネルを持ったモデルのことを言う。

<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2851499/1d08842b-c803-51b0-be15-fbb5d305bc2e.png">

#### プーリング層(Pooling)とは

- 識別に不必要な特徴量をそぎ落とす。識別に必要な特徴量を抽出する処理

<img width="200" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2851499/a908c73d-7e51-b17d-3526-825ff3bc8621.png">

#### 全結合層(Dense)とは

- 畳み込み層も、プーリング層も、出力は画像のまま(特徴マップ)である。
- 最終的には画像を与えると、1~9のように識別したいので、出力を１次元にしたい。そのために全結合層がある。
- 全結合層の前にFlatten層で平坦化する

#### Sequentialモデルを使って学習済みモデルを作成する

- 前段で作ったSequentialモデルにデータセットを渡して学習を開始する
- 学習モデルができたら評価してみる
    - lossはモデルの出力値と、教師データの結果の誤差。0に近いほど良い。
    - accuracyはモデルの出力値の正答率。1に近いほどよい。

```python
from keras.callbacks import EarlyStopping

model.compile(optimizer='adam', loss='categorical_crossentropy', metrics="accuracy")

# 学習
model.fit(X_train, Y_train, epochs=1, batch_size=100, validation_data=(X_test, Y_test), callbacks=[EarlyStopping()])

# モデル評価
score = model.evaluate(X_test, Y_test, verbose=0)
print("loss:", score[0])
print("accuracy:", score[1])

"""出力
loss: 0.10847385227680206
accuracy: 0.9653000235557556
"""
```

### step3 推論してみる

```python
from keras.models import load_model

# 学習済みモデルを保存しておく
model.save('MNIST_NN.h5')
MNIST_NN = load_model('MNIST_NN.h5')

# 学習済みモデルに、mnistのバリデーション画像を渡して推論してみる
predicted = MNIST_NN.predict(x_test[0].reshape(1, 28, 28, 1))
print("判定結果=" np.argmax(predicted)) # 最も確率の高い数字を出力する
plt.imshow(x_test[0], cmap=plt.cm.gray_r)
plt.show

```

#### 結果

- 結果は`1`
- 判定できた。
- 何度か試したが時々間違える

<img width="200" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2851499/c937559a-0c4b-c016-3867-20c0332b914c.png">


## 感想など

- kerasのsequentialを使ってCNNモデルを構築。手書き文字の判定プログラムを作った。
- sequentialで層やその他のオプションを調整することで、判定精度などあげられるかもしれない。
- 今回は判定にmnistのバリデーションデータを使ったが、mnist以外の手書き文字を渡して判定してみたい
- mnistの文字判定はチュートリアルレベルの内容なので、他の画像を使ったCNNモデルを組んでみたい
- Google colabでAPIサーバを立てられる方法があるみたいなので、webアプリ上で手書き文字の入力と、結果の出力とかしてみたい