Keras / Tensorflowで始めるディープラーニング入門

# 概要
Keras(Tensorflowバックグラウンド)を用いた画像認識の入門として、[MNIST](https://keras.io/datasets/#mnist-database-of-handwritten-digits)(手書き数字の画像データセット)で手書き文字の予測を行いました。

実装したコード(iPython Notebook)は[こちら(Github)](https://github.com/yampy/machine-learning/tree/master/keras/keras-introduction)をご確認下さい。

- MNISTデータセット サンプル:

![mnist.png](https://qiita-image-store.s3.amazonaws.com/0/172399/d83ca830-3ab8-6778-ac78-ff24524caa8d.png)

- 実装した
![png.png](https://qiita-image-store.s3.amazonaws.com/0/172399/478f5e49-8c68-de8f-a672-bbb80a05aa10.png)

# 前提
## Kerasとは
[Keras](https://keras.io/)とは、Pythonで書かれたニューラルネットワークのライブラリです。
バックエンドとして[TensorFlow](https://www.tensorflow.org/)、[CNTK](https://www.microsoft.com/en-us/cognitive-toolkit/)、[Theano](http://www.deeplearning.net/software/theano/)のライブラリを選択することができます。
人間にとって分かりやすい設計の為、初心者でも簡単に迅速なプロトタイピングが可能となっています。

[日本語ドキュメント](https://keras.io/ja/)も充実しています。インストール方法は[公式HP](https://keras.io/ja/#_2)をご参照下さい。


### インストール
Kerasに加え、今回バックエンドとして利用するTensorflowもインストールします。

```
pip install --upgrade tensorflow
pip install keras
```

## 2つのモデル記述方法
kerasでは、ネットワークの層（レイヤー）を記述する方法が2つあります。

### Sequentialモデル
ネットワークを1列に積み重ねていく、シンプルな方法です。

- サンプルコード：

```
from keras.models import Sequential
from keras.layers import Dense, Activation

# モデルの作成
model = Sequential()

# モデルにレイヤーを積み上げていく
model.add(Dense(units=64, input_dim=100))
model.add(Activation('relu'))
model.add(Dense(units=10))
model.add(Activation('softmax'))

# 訓練プロセスの定義
model.compile(loss='categorical_crossentropy',
              optimizer='sgd',
              metrics=['accuracy'])

# 訓練の実行
# (x_train, y_trainはNumpy行列の学習データ)
model.fit(x_train, y_train, epochs=5, batch_size=32)

# 予測の実行
classes = model.predict(x_test, batch_size=128)

```

### Functional API
複雑なネットワークの記述が可能な方法です。
Sequentialモデルと異なり、複数の出力や共有レイヤーの定義等が可能となっています。

- サンプルコード：

```
from keras.layers import Input, Dense
from keras.models import Model

# インプットの定義
inputs = Input(shape=(784,))

# レイヤーの定義
nw = Dense(64, activation='relu')(inputs)
nw = Dense(64, activation='relu')(x)
predictions = Dense(10, activation='softmax')(x)

# モデルの定義(インプットとレイヤーを指定)
model = Model(inputs=inputs, outputs=predictions)

# 訓練プロセスの定義
model.compile(optimizer='rmsprop',
              loss='categorical_crossentropy',
              metrics=['accuracy'])

#  訓練の実行
model.fit(data, labels)

# 予測の実行
classes = model.predict(x_test, batch_size=128)

```
# 実装
今回は、2種類のモデルを*Functional API*を用いて実装してみます。
1つは単純な全結合のみのモデル、もう1つはCNN(Convolution Neural Network:畳み込みニューラルネットワーク)のモデルです。

## 手書き文字の認識(1): 全結合層のみ
冗長な部分は省略して主要な部分のみ解説していきます。
ソースコードは[こちら](https://github.com/yampy/machine-learning/blob/master/keras/keras-introduction/keras_mnist_pixeldata.ipynb)。


### データセットのダウンロード
教師ラベル付けされた手書き文字をダウンロードします。

```keras_mnist_simpleLayers.ipynb
from keras.datasets import mnist
(x_train, y_train), (x_test, y_test) = mnist.load_data()
```
内容は10種類の数字の白黒画像(28x28)です。訓練データが60000枚、テストデータが10000枚となっております。

Kerasでは、MINIST以外にも[いくつかのデータセット](https://keras.io/ja/datasets/)が提供されています。

### 画像データの前処理
ネットワークで扱う為に、訓練データを1次元配列に変換して、値を0から1の間に変換します。
また、教師データについても*one-hot表現*(正解の要素を1として、他の要素を0にする表現)に変換します。

```keras_mnist_simpleLayers.ipynb
x_train = x_train.reshape(60000, 784).astype('float32') /255
x_test = x_test.reshape(10000, 784).astype('float32') /255

y_train = keras.utils.np_utils.to_categorical(y_train.astype('int32'),10)
y_test = keras.utils.np_utils.to_categorical(y_test.astype('int32'),10)

```

### モデルの作成
全結合層3層のみのシンプルなモデルです。
Dropoutは、訓練時に一定確率で中間層を無視する関数です。過学習を防ぐために使用します。

```keras_mnist_simpleLayers.ipynb
inputs = Input(shape=(784,))

nw = Dense(512, activation='relu')(inputs)
nw = Dropout(.5)(nw)
nw = Dense(512, activation='relu')(nw)
nw = Dropout(.5)(nw)
predictions = Dense(10, activation='softmax')(nw)
```

### 訓練
epoch(学習の繰り返し単位)20回で訓練を行います。

```keras_mnist_simpleLayers.ipynb
history = model.fit(x_train, y_train, batch_size=128, epochs=20, verbose=1, validation_data=(x_test, y_test))
```

kerasでは、訓練を行うと自動でログを出力をしてくれます。

```
Train on 60000 samples, validate on 10000 samples
Epoch 1/20
60000/60000 [==============================] - 7s - loss: 0.3319 - acc: 0.8974 - val_loss: 0.1267 - val_acc: 0.9607
Epoch 2/20
60000/60000 [==============================] - 6s - loss: 0.1621 - acc: 0.9524 - val_loss: 0.0921 - val_acc: 0.9733
Epoch 3/20
60000/60000 [==============================] - 6s - loss: 0.1318 - acc: 0.9610 - val_loss: 0.0874 - val_acc: 0.9750
(中略)
Epoch 20/20
60000/60000 [==============================] - 6s - loss: 0.0722 - acc: 0.9823 - val_loss: 0.1083 - val_acc: 0.9817
```

### 結果の表示

-正答率:

```keras_mnist_simpleLayers.ipynb
plt.plot(history.history['acc'])
plt.plot(history.history['val_acc'])
plt.title('model accuracy')
plt.ylabel('accuracy')
plt.xlabel('epoch')
plt.legend(['train', 'test'], loc='upper left')
plt.show()
```

![accuracy.png](https://qiita-image-store.s3.amazonaws.com/0/172399/9d416b05-9842-6ef6-4b54-e225fd9ea1bc.png)

epochが2回目では95.24%、20回目では98.23%と高い認識精度が出ています。

- ロス関数:

```keras_mnist_simpleLayers.ipynb
plt.plot(history.history['loss'])
plt.plot(history.history['val_loss'])
plt.title('model loss')
plt.ylabel('loss')
plt.xlabel('epoch')
plt.legend(['train', 'test'], loc='upper left')
plt.show()
```

![loss.png](https://qiita-image-store.s3.amazonaws.com/0/172399/12fe4083-359f-6968-903e-ebde1426ab16.png)

epochが10回以上の時点で過学習(overfitting)が起きていることが分かります。


## 手書き文字の認識(2): CNNモデル
同じデータに対して、CNNで画像認識を行います。
ソースコードは[こちら](https://github.com/yampy/machine-learning/blob/master/keras/keras-introduction/keras_mnist_cnn.ipynb)。

### 画像データの前処理
kerasのバックエンドで使用するライブラリにより入力チャネルの順番が異なる為、条件分岐させて前処理を定義します。

```keras_mnist_cnn.ipynb
if backend.image_data_format() == 'channels_first':  
    inputs = Input(shape=(1, 28, 28))  
    x_train = x_train.reshape(x_train.shape[0], 1, 28, 28).astype('float32') / 255
    x_test = x_test.reshape(x_test.shape[0], 1, 28, 28).astype('float32') / 255

else:
    inputs = Input(shape=(28, 28, 1))
    x_train = x_train.reshape(x_train.shape[0], 28, 28, 1).astype('float32') / 255
    x_test = x_test.reshape(x_test.shape[0], 28, 28, 1).astype('float32') / 255
```

### モデルの作成

```keras_mnist_cnn.ipynb
# 2つの畳み込みレイヤー
conv1_1 = Conv2D(32, (3, 3), activation='relu', name='conv1_1')(inputs)
conv1_2 = Conv2D(64, (3, 3), activation='relu', name='conv1_2')(conv1_1)

# MaxPooling2D: pool_sizeの範囲で最も大きい値を次の層に渡す
pool1 = MaxPooling2D(pool_size=(2, 2), name='pool1')(conv1_2)

# Dropout: 訓練時に一定確率で中間層を無視する
nw = Dropout(.25)(pool1)

# Flatten: 2次元データを1次元に変換
nw = Flatten()(nw)
nw = Dense(128, activation='relu')(nw)
nw = Dropout(.5)(nw)

# 最終レイヤー(10クラスに分類)
predictions = Dense(10, activation='softmax')(nw)

# モデルの作成
model = Model(inputs=inputs, outputs=predictions)
model.compile(loss=keras.losses.categorical_crossentropy, optimizer=keras.optimizers.Adadelta(), metrics=['accuracy'])

```

# コールバックの定義
kerasでは、訓練中に実行可能な処理を[コールバック](https://keras.io/ja/callbacks/)として定義できます。
今回は、以下のコールバックを定義します。

- ModelCheckpoint: epoch毎にweightデータを出力する処理
- TensorBoard: モデル等の可視化を行う[Tensorboard](https://www.tensorflow.org/get_started/summaries_and_tensorboard)用のログ出力


```keras_mnist_cnn.ipynb
cb_cp = keras.callbacks.ModelCheckpoint('./out/checkpoints/weights.{epoch:02d}-{val_loss:.2f}.hdf5', verbose=1, save_weights_only=True)
cb_tf = keras.callbacks.TensorBoard(log_dir='./out/tensorBoard', histogram_freq=0)
```

# 訓練の実施
epoch数10で実施します。

```keras_mnist_cnn.ipynb
history = model.fit(x_train, y_train, batch_size=128, epochs=10, verbose=1, callbacks=[cb_cp, cb_tf], validation_data=(x_test, y_test))
```

### 結果の表示

```
Train on 60000 samples, validate on 10000 samples
Epoch 1/10
59904/60000 [============================>.] - ETA: 0s - loss: 0.3267 - acc: 0.9009Epoch 00000: saving model to ./out/checkpoints/weights.00-0.08.hdf5
60000/60000 [==============================] - 135s - loss: 0.3263 - acc: 0.9011 - val_loss: 0.0785 - val_acc: 0.9749
Epoch 2/10
59904/60000 [============================>.] - ETA: 0s - loss: 0.1132 - acc: 0.9671Epoch 00001: saving model to ./out/checkpoints/weights.01-0.06.hdf5
60000/60000 [==============================] - 131s - loss: 0.1131 - acc: 0.9671 - val_loss: 0.0580 - val_acc: 0.9811
Epoch 3/10
59904/60000 [============================>.] - ETA: 0s - loss: 0.0871 - acc: 0.9735Epoch 00002: saving model to ./out/checkpoints/weights.02-0.05.hdf5
60000/60000 [==============================] - 135s - loss: 0.0870 - acc: 0.9735 - val_loss: 0.0469 - val_acc: 0.9850
(中略)
Epoch 10/10
59904/60000 [============================>.] - ETA: 0s - loss: 0.0410 - acc: 0.9876Epoch 00009: saving model to ./out/checkpoints/weights.09-0.03.hdf5
60000/60000 [==============================] - 140s - loss: 0.0410 - acc: 0.9876 - val_loss: 0.0295 - val_acc: 0.9902
```

- 正答率:
epoch 1回目でも90.09%と高い精度が出ています。
![accuracy.png](https://qiita-image-store.s3.amazonaws.com/0/172399/71d50e46-f537-8b98-d015-baa6e56004fd.png)


- ロス関数:
汎化性能も、全結合層のみのモデルより良い結果となっています。
![loss.png](https://qiita-image-store.s3.amazonaws.com/0/172399/9f4fe969-368a-98d4-c4a0-832e11d82992.png)



### モデルの可視化
今回は、モデルの可視化に[Tensorboard](https://www.tensorflow.org/get_started/summaries_and_tensorboard)を用います。

以下のコマンドで起動します。
コールバック処理にて出力したログを指定します。

```
tensorboard --logdir=./out/
```

以下のようにモデルの可視化ができました。

![png.png](https://qiita-image-store.s3.amazonaws.com/0/172399/478f5e49-8c68-de8f-a672-bbb80a05aa10.png)

# 感想
今回はMNISTの手書き文字認識という比較的難易度の低い課題でしたので、シンプルなネットワークでも十分な精度で予測できました。
しかし、実際に物体検出などのより難易度の高い課題を解く場合、特徴検出を行う為にVGG16等の[学習済モデル](https://keras.io/ja/applications/)を利用することで、最初から精度の高い特徴検出を行うのが一般的となっています。
(実際に以前紹介した[YOLO](http://qiita.com/yampy/items/7a705acf4c6899bc11c7)や[SSD](http://qiita.com/yampy/items/37c607fdf77a919cda5d)でもVGG16が使用されています)

次は、学習済モデルによるファインチューニングを行い独自課題の認識にもチャレンジしてみたいと思います。また、Tensorboardももっと色々な機能があるので使っていきたいと思います。


# 参考
- [Keras Documentation](https://keras.io/ja/)
- [ディープラーニング実践入門 〜 Kerasライブラリで画像認識をはじめよう！](https://employment.en-japan.com/engineerhub/entry/2017/04/28/110000#functional-API%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%9F%E3%83%A2%E3%83%87%E3%83%AB%E3%81%AE%E6%9B%B8%E3%81%8D%E6%96%B9)