【AI初心者向け】mnist_mlp.pyを1行ずつ解説していく(KerasでMNISTを学習させる)

# はじめに
皆さんはじめまして。
この記事は[mnist_mlp.py](https://github.com/keras-team/keras/blob/master/examples/mnist_mlp.py)を1行ずつ解説していくだけの記事です。
AIに興味があるけどまだ触ったことはない人などが対象です。これを読めばディープラーニングの基本的な学習の流れが理解できるはず、と思って書いていきます。(もともとは社内で研修用に使おうと思って作成していた内容です)

全3回予定です。
1. 【AI初心者向け】mnist_mlp.pyを1行ずつ解説していく(KerasでMNISTを学習させる)
2. [【AI初心者向け】mnist_cnn.pyを1行ずつ解説していく(KerasでMNISTを学習させる)](https://qiita.com/nashineko/items/69cc529117c92ecb1190)
3. [【AI初心者向け】mnist_transfer_cnn.pyを1行ずつ解説していく(KerasでMNISTを学習させる)](https://qiita.com/nashineko/items/129835aa606f5e2042c0)

## 動作確認方法について
MNISTは画像なので、このコードを動かすにはGPUがあったほうがいいです（CPUだとちょっと辛いです）。
おすすめの方法は[Google Colaboratory](https://colab.research.google.com/notebooks/welcome.ipynb?hl=ja)を使う方法です。
![colab.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/351213/87ea5af7-1fa3-22a8-d520-427d53695f4b.gif)
やることは2つだけ。
・Python3の新しいノートブックを開く
・ランタイムからGPUを有効にする
これでGPUが使えるようになりました。
セルにコードを貼り付けて実行(ショートカットはCTRL+ENTER)するだけで動きます。



# mnistについて
手書き文字画像のデータセットで、機械学習のチュートリアルでよく使用されます。
内容：0～9の手書き文字
画像サイズ：28pix＊28pix
カラー：白黒
データサイズ：７万枚（訓練データ6万、テストデータ1万の画像とラベルが用意されています）

# mlpとは
Multilayer perceptron、多層パーセプトロンのことです。
mnistは画像データですが、画像データの形を(28, 28)から(784,)に変更することでmlpとして学習させることができます。（精度は第2回でやるCNNのほうが上です。）

# mnist_mlp.pyについて
mnistの手書き文字の判定を行うモデルをKerasとTensorFlowを使って作成するコードです。
0～9の10種類の手書き文字を入力として受け取り、0～9のいずれであるか10種類に分類するモデルを作成します。

# コードの解説
## 準備
```python
'''Trains a simple deep NN on the MNIST dataset.
Gets to 98.40% test accuracy after 20 epochs
(there is *a lot* of margin for parameter tuning).
2 seconds per epoch on a K520 GPU.
'''

# 特に必要ないコードです（Pythonのバージョンが3だが、コードがPython2で書かれている場合に必要になる）
from __future__ import print_function

# 必要なライブラリをインポートしていく
import keras
from keras.datasets import mnist
from keras.models import Sequential
from keras.layers import Dense, Dropout
from keras.optimizers import RMSprop

# 定数を最初にまとめて指定する
batch_size = 128  # バッチサイズ。1度に学習するデータサイズ
num_classes = 10  # 分類するラベル数。今回は手書き画像を0～9の10種類に分類する
epochs = 20       # エポック数。全データを何回学習するか
```

## データの前処理
```python
# mnistのデータを読み込み、訓練データ(6万件)とテストデータ(1万件)に分割する
(x_train, y_train), (x_test, y_test) = mnist.load_data()

'''mlpでインプットデータとして使用できるようにするため、reshapeしてデータの形式を合わせる
x_train：(60000, 28, 28) ->(60000, 784) 28pix*28pixの画像を1列にする
x_test：(10000, 28, 28) ->(10000, 784) 28pix*28pixの画像を1列にする'''
x_train = x_train.reshape(60000, 784)
x_test = x_test.reshape(10000, 784)

# 画像データは0～255の値をとるので255で割ることでデータを標準化する
# .astype('float32')でデータ型を変換する。(しないと割ったときにエラーが出るはず)
x_train = x_train.astype('float32')
x_test = x_test.astype('float32')
x_train /= 255
x_test /= 255

# データの数を出力して確認
print(x_train.shape[0], 'train samples')
print(x_test.shape[0], 'test samples')

# ラベルデータをone-hot-vector化する
'''one-hot-vectorのイメージはこんな感じ
label  0 1 2 3 4 5 6 7 8 9
0:    [1,0,0,0,0,0,0,0,0,0]
8:    [0,0,0,0,0,0,0,0,1,0]'''
y_train = keras.utils.to_categorical(y_train, num_classes)
y_test = keras.utils.to_categorical(y_test, num_classes)
```
標準化について：画像の各ピクセルの値は0~255になっています。これを0~1に変換するイメージです。画像で機械学習するときは大体この255で割る処理をして、値を標準化します。

one-hot-vectorについて：今回、ラベルは0~9の10種類があり、それぞれ0~9の数字で表しています。しかし、10種類に分類したいだけなので、ラベルの数字自体には意味がありません。そこで、one-hot-vectorすることにより0と1のみでどのラベルなのかを表せるように変換します。


## モデルの定義
```python
# Sequentialクラスをインスタンス化
model = Sequential()

# 中間層
# 全結合層（512ユニット、活性化関数：Relu、受け取る入力サイズ：784）を追加
model.add(Dense(512, activation='relu', input_shape=(784,)))  
# 0.2の確率でドロップアウト
model.add(Dropout(0.2))
# 全結合層（512ユニット、活性化関数：Relu、受け取る入力サイズは自動で判断）を追加
model.add(Dense(512, activation='relu'))
# 0.2の確率でドロップアウト
model.add(Dropout(0.2))

# 出力層
# 全結合層（10ユニット、活性化関数：SoftMax、受け取る入力サイズは自動で判断）を追加
model.add(Dense(num_classes, activation='softmax'))

# モデルの構造を可視化
model.summary()
```
SequentialモデルはDNNの層を積み重ねて作るモデルです。一番最初の層にだけ、input_shapeを指定してやる必要があります。
出力層の活性化関数は、今回は多値分類するモデルなのでsoftmaxを使います。

## 学習
```python
# 学習プロセスを設定する
model.compile(
              # 損失関数を設定。今回は分類なのでcategorical_crossentropy
              loss='categorical_crossentropy',
              # 最適化アルゴリズムを指定。学習率などをいじれる
              optimizer=RMSprop(),
              # 評価関数を指定
              metrics=['accuracy'])

# 学習させる
history = model.fit(
                    # 学習データ、ラベル
                    x_train, y_train, 
                    # バッチサイズ（128）
                    batch_size=batch_size,
                    # エポック数（20）
                    epochs=epochs,
                    # 学習の進捗をリアルタムに棒グラフで表示(0で非表示)
                    verbose=1,
                    # テストデータ(エポックごとにテストを行い誤差を計算するため)
                    validation_data=(x_test, y_test))
```
モデルの定義が終わったら、損失関数や最適化アルゴリズムを指定してコンパイルします。その後、モデルにデータを渡して学習させます。より良いモデルを作るためには、最適化アルゴリズムやバッチサイズ、エポック数などをいろいろ変更して試してやる必要があります。

## 評価
```python
# テストデータを渡す(verbose=0で進行状況メッセージを出さない)
score = model.evaluate(x_test, y_test, verbose=0)
# 汎化誤差を出力
print('Test loss:', score[0])
# 汎化性能を出力
print('Test accuracy:', score[1])
```
学習が終わったら、テストデータを使ってどの程度の性能になったのかを評価します。lossが低く、accuracyが高いほど良いモデルといえます。