Kerasで始める機械学習（MNIST編）

## はじめに
機械学習におけるHelloWorld的位置にあるMNIST(手書き文字)の認識をKerasを用いて行います。
想定読者は機械学習、DeepLearningについて概念を学んだのでKerasで簡単な実装を行ないたい方です。

## 実行環境
macOS Mojave
Python 3.6.8 
Keras 2.2.4

## MNISTデータセットのLOAD

keasと必要なライブラリをImportし、MNISTデータセットをロードします。
MNISTデータはkerasに実装されている関数load_dataを利用し取得できます。
[load_dataの詳細については公式サイト](https://keras.io/ja/datasets/≈)を参照ください。

```python3:main.py
import keras
import numpy

from keras.datasets import mnist

# MNISTデータのロード
train, test = mnist.load_data()
X_train, y_train = train
X_test, y_test = test

# サイズを変更(画像数、解像度(縦、横)、白黒)
X_train = X_train.reshape(60000,28,28,1)
X_test = X_test.reshape(10000,28,28,1)

```

## MNISTデータを確認する

ロードしたMNISTデータをmatplotlibを使って確認してみます。
参考：[matplotlib.pyplot.imshow](https://matplotlib.org/3.1.1/api/_as_gen/matplotlib.pyplot.imshow.html)

```python3:show_img
import matplotlib.pyplot as plt

def show_img(img, figsize=(2,2)):
    fig = plt.figure(figsize=figsize,dpi=100)
    plt.imshow(img, cmap = 'gray', interpolation = 'bicubic')
    plt.xticks([]), plt.yticks([])  # to hide tick values on X and Y axis
    plt.show()

# X_trainの一番目の画像を表示
show_img(X_train[0])
```

### 出力結果
数字の”５”と思われる画像が確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/204989/bbf15c5a-9cba-c772-e9ce-d3eec5305c26.png)


## One-Hot encoding
教師ラベルをOne-Hot Encodingします。
[KerasではOne-hot encodingする関数としてto_categorical](https://keras.io/ja/utils/#to_categorical)が用意されています。

One-hot とは1つだけHigh(1)であり、他はLow(0)であるようなビット列のことです。([wikipedia](https://ja.wikipedia.org/wiki/One-hot))
MNISTは0~9の教師ラベルを持つ手書き画像なので例えば教師ラベルの[５]は[0,0,0,0,0,1,0,0,0,0]のように６番目のみ１が立つようにEncodeされます。

```python3:to_categorical
from keras.utils.np_utils import to_categorical

y_trn_cgl = to_categorical(y_train)
y_test_cgl = to_categorical(y_test)

print(f"train_labels.shape:{y_train.shape}, y_trn.shape:{y_test.shape}")
print(f"y_trn_cgl:{y_trn_cgl}") 
print(f"y_test_cgl:{y_test_cgl}")
```

## CNNモデルの構築

モデルの構造は (2D Conv -> Relu -> MaxPooling)*2 -> 全結合層 -> Softmax
となるシンプルなニューラルネットを構築します。
各レイヤの仕様について公式サイトのリンクを貼っておきます。

・[Conv2D](https://keras.io/ja/backend/#conv2d)：２次元畳み込み層
・[Activation](https://keras.io/ja/layers/core/#activation)：活性化関数(今回はReluを使用)
・[MaxPooling2D](https://keras.io/ja/layers/pooling/)：Maxプーリング層
・[Dense](https://keras.io/ja/layers/core/#dense)：全結合層


```python3:Model
from keras.models import Model
from keras.layers import Input, Conv2D, MaxPooling2D, Dropout, Flatten

def create_cnn_model(input_size):
    # 入力データサイズを指定
    X_input = Input((input_size[0], input_size[1], 1))
    # 2次元畳み込み層。
    X = Conv2D(filters=64, kernel_size=(5,5), padding="valid")(X_input)
    # 活性化関数はReluを使用
    X = Activation('relu')(X)
    # MaxPooling層
    X = MaxPooling2D(pool_size = (2,2))(X)

    X = Conv2D(filters=128, kernel_size=(5,5), padding="valid")(X)
    X = Activation('relu')(X)
    X = MaxPooling2D(pool_size = (2,2))(X)

    X = Flatten()(X)
    # 全結合層。MNISTでは10クラスへ分類するため出力10で指定
    X = Dense(10, activation="softmax")(X)

    model = Model(inputs=X_input, outputs=X)

    return model
```

## CNNモデルの学習
MNISTデータの準備とCNNモデルの準備ができましたので、
モデルの学習を行なっていきます。

```python
from keras import optimizers
# セッションの初期化
K.clear_session() 

# 入力画像サイズを指定
input_size =  (28, 28) 
# モデルを作成
cnn_model = create_cnn_model(input_size) 

# 最適化アルゴリズムを指定(lr=学習率)
opt = optimizers.Adagrad(lr=1e-4)
# 多クラス分類用の損失関数の指定
loss = "categorical_crossentropy" 
metrics = ['accuracy']

# 設定を反映
cnn_model.compile(optimizer=opt, loss=loss, metrics=metrics) 

#学習を実行
# 学習経過をhistory_cnnに格納してます。
history_cnn = cnn_model.fit(X_train[0:6000], y_trn_cgl[0:6000], epochs=10) 
```

## テストデータの予測
学習したモデルでテストデータを予測します。


```python
predict = cnn_model.predict(X_test)

eval_loss, eval_acc = cnn_model.evaluate(X_test, y_test_cgl)
print("eval_loss:{0}\neval_acc{1}".format(eval_loss, eval_acc))

def show_history(history):
    fig, ax = plt.subplots(1, 2, figsize=(15,5))
    ax[0].set_title('loss')
    ax[0].plot(history.epoch, history.history["loss"], label="Train loss")
    #ax[0].plot(history.epoch, history.history["val_loss"], label="Validation loss")
    ax[1].set_title('categorical_accuracy')
    ax[1].plot(history.epoch, history.history["acc"], label="Train accuracy")
    #ax[1].plot(history.epoch, history.history["val_acc"], label="Validation accuracy")
    ax[0].legend()
    ax[1].legend()
    plt.show()
    
show_history(history_cnn)

```

## 実行結果

左の図がLossを、右の図が正答率(categorical accuracy)を示しています。
横軸はEpoch数、つまり学習回数です。

学習回数を重ねるにつれLossが下がり学習が進み、それに伴い正答率が向上していることがわかります。
MNISTは比較的簡単な画像分類のため10回の学習で85%近い正答率が出せることがわかります。
特に工夫はしていないCNNモデルでも学習回数を増やせば90%近い正答率は出せると思います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/204989/bce1d429-37fc-531f-f1cc-2c3d9fe85d1b.png)

## 参考リンク
・[Keras Documentaion](https://keras.io/ja/)