ライブラリを使ってディープラーニングを構築する　～Keras + TensorFlow編

# **■0.はじめに**

[ディープラーニングを基本から学ぶ](https://qiita.com/nvtomo1029/items/551a28f40d092265047c)

こちらで、ゼロからディープラーニングを実装することを学びました。
実務的にはライブラリを使ってとなるのが現実的かと思い、ライブラリを使ったディープラーニングを構築してみようと考えました。

[scikit-learn編](https://qiita.com/nvtomo1029/items/200dbfe5928c4a0cd625)、[chainer編](https://qiita.com/nvtomo1029/items/18c3901052b2ee3dd355)、[TensorFlow編](https://qiita.com/nvtomo1029/items/16338153fa5e93e1ba46)に続き、第四弾はKeras + TensorFlowです。

# **■1.Keras**

>Kerasは、Pythonで書かれたオープンソースニューラルネットワークライブラリである。MXNet、Deeplearning4j、TensorFlow、CNTK、Theanoの上部で動作することができる。ディープニューラルネットワークを用いた迅速な実験を可能にするよう設計され、最小限、モジュール式、拡張可能であることに重点が置かれている。プロジェクトONEIROS (Open-ended Neuro-Electronic Intelligent Robot Operating System) の研究の一部として開発された。中心的な開発者、メンテナはGoogleのエンジニアのFrançois Cholletである。
>
>2017年、GoogleのTensorFlowチームは、TensorFlowのコアライブラリにおいてKerasをサポートすることを決定した。Cholletは、Kerasはタスク全体を担う機械学習ライブラリよりむしろインターフェースとして着想された、と説明した。Kerasはバックエンドの科学計算ライブラリにかかわらず、ニューラルネットワークの設定を容易に行うことができる、より高いレベルでより直感的な一連の抽象化を提供している。MicrosoftはKerasにCNTKバックエンドを追加する作業を行っている。
>[Wikipedia](https://ja.wikipedia.org/wiki/Keras)

# **■2.環境**

**・Windows 10**
**・Anaconda 4.4.1**
**・Python 3.6.1**
**・TensorFlow 1.4.0**
**・Keras 2.1.2**

# **■3.利用データ**

**MNIST**
・28x28ピクセルからなる手書き数字(0～9)のデータセット
・機械学習のサンプルデータとしてよく使われる
・データ数は70,000枚

# **■4.実装**

## **▼4.1.データ準備**

これまで同様、MNISTのデータを取得します。
Kerasではkeras.datasets.mnistにMNISTを取得するI/Fが用意されてました。

[MNIST 手書き数字データベース](https://keras.io/ja/datasets/#mnist)

```py3
# MNISTデータのダウンロード
# x_train, x_test: shape (num_samples, 28, 28) の白黒画像データのuint8配列．
# y_train, y_test: shape (num_samples,) のカテゴリラベル(0-9のinteger)のuint8配列．
(x_train, y_train), (x_test, y_test) = mnist.load_data()
# 成形(3次元→2次元)
x_train = x_train.reshape(x_train.shape[0], -1)
x_test = x_test.reshape(x_test.shape[0], -1)
# 正規化
x_train = x_train.astype('float32') / 255
x_test = x_test.astype('float32') / 255

print('x_train.shape:', x_train.shape)
print('y_train.shape:', y_train.shape)
print('x_test.shape:', x_test.shape)
print('y_test.shape:', y_test.shape)
```

画像データはニューラルネットワークで読み込めるよう2次元に変換します。
さらに正規化することで学習の精度を上げます。

```py3
x_train.shape: (60000, 784)
y_train.shape: (60000,)
x_test.shape: (10000, 784)
y_test.shape: (10000,)
```

学習データ、テストデータの次元は上記のようになります。

## **▼4.2.ニューラルネットワークの定義**

続いてニューラルネットワークの定義。
Kerasでは、Sequentialモデルを使って層を積み重ねていくことで定義できます。

```py3
# モデルの定義
model = Sequential([
    Dense(500, activation='relu', input_shape=(784, )),
    Dense(300, activation='relu'),
    Dense(10, activation='softmax'),
])
```

3層構造で活性化関数にReLUを使用しています。
これまでのライブラリと比べると、実装が相当シンプルになりますね。

```py3
# モデルの要約を出力
model.summary()
```

定義したモデルの要約を出力できます。
出力イメージです。

```py3
Layer (type)                 Output Shape              Param #
=================================================================
dense_1 (Dense)              (None, 500)               392500
_________________________________________________________________
dense_2 (Dense)              (None, 300)               150300
_________________________________________________________________
dense_3 (Dense)              (None, 10)                3010
=================================================================
Total params: 545,810
Trainable params: 545,810
Non-trainable params: 0
_________________________________________________________________
```

続いてはモデルのコンパイル。
具体的には、最適化手法、損失関数、評価関数の定義を行います。

```py3
# 最適化手法の設定
opt = optimizers.Adam()

# モデルのコンパイル
model.compile(optimizer = opt,                          # 最適化手法
              loss = 'sparse_categorical_crossentropy', # 損失関数
              metrics = ['accuracy'])                   # 評価関数
```

設定できる最適化手法、損失関数は下記にまとまってあります。
(日本語なのがいいですね！)

[最適化手法](https://keras.io/ja/optimizers/)
[損失関数](https://keras.io/ja/losses/)

設定できる評価関数は下記に書いてあるんですが、、、

[評価関数](https://keras.io/ja/metrics/)

metrics = ['accuracy']

と指定した場合の説明はないですね...
(推測ですが)損失関数と同じのが使われるんじゃないかと。
今回の場合は、sparse_categorical_accuracy が使われているように思います。

## **▼4.3.学習と評価**

続いて学習と評価を実行します。

```py3
csv_logger = CSVLogger('training.log')
# 学習
history = model.fit(x_train, y_train, 
                    epochs=10, 
                    batch_size=100, 
                    validation_data=(x_test, y_test), 
                    callbacks=[csv_logger])
```

fit関数で学習を行います。
fit関数、、、scikit-learnのようですね。

validation_dataを設定すると、各エポックの最後に評価を行ってくれます。
学習時に何か関数を呼び出したいときは、callbacksを設定します。

[コールバック](https://keras.io/ja/callbacks/)

主目的は、学習中のモデル内部の状態と統計量を可視化することですが、自前のコールバック関数を定義することもできます。
今回はCSVLoggerをコールバックに設定して、各エポックの結果をファイルに保存しています。
また、fit関数の戻り値には学習時に得られた情報が入っています。
後述する可視化のために取得しておきます。

```py3
Train on 60000 samples, validate on 10000 samples
Epoch 1/10
2018-01-18 16:40:05.861538: I C:\tf_jenkins\home\workspace\rel-win\M\windows\PY\36\tensorflow\core\platform\cpu_feature_guard.cc:137] Your CPU supports instructions that this TensorFlow binary was not compiled to use: AVX AVX2
60000/60000 [==============================] - 6s 93us/step - loss: 0.2141 - acc: 0.9366 - val_loss: 0.1099 - val_acc: 0.9653
Epoch 2/10
60000/60000 [==============================] - 5s 87us/step - loss: 0.0795 - acc: 0.9753 - val_loss: 0.0896 - val_acc: 0.9721
Epoch 3/10
60000/60000 [==============================] - 5s 87us/step - loss: 0.0526 - acc: 0.9834 - val_loss: 0.0636 - val_acc: 0.9798
Epoch 4/10
60000/60000 [==============================] - 5s 86us/step - loss: 0.0378 - acc: 0.9876 - val_loss: 0.1079 - val_acc: 0.9707
Epoch 5/10
60000/60000 [==============================] - 5s 87us/step - loss: 0.0295 - acc: 0.9908 - val_loss: 0.0843 - val_acc: 0.9764
Epoch 6/10
60000/60000 [==============================] - 5s 86us/step - loss: 0.0233 - acc: 0.9919 - val_loss: 0.0616 - val_acc: 0.9831
Epoch 7/10
60000/60000 [==============================] - 5s 86us/step - loss: 0.0205 - acc: 0.9933 - val_loss: 0.0681 - val_acc: 0.9812
Epoch 8/10
60000/60000 [==============================] - 5s 86us/step - loss: 0.0183 - acc: 0.9937 - val_loss: 0.0773 - val_acc: 0.9816
Epoch 9/10
60000/60000 [==============================] - 5s 86us/step - loss: 0.0141 - acc: 0.9954 - val_loss: 0.0801 - val_acc: 0.9810
Epoch 10/10
60000/60000 [==============================] - 5s 86us/step - loss: 0.0123 - acc: 0.9961 - val_loss: 0.0782 - val_acc: 0.9805
10000/10000 [==============================] - 0s 37us/step
```

学習の経過は上記のように出力されます。

```py3
# 評価
score = model.evaluate(x_test, y_test, batch_size=32)

print('score[loss, accuracy]:', score)
```

テストデータを使用して学習させたモデルの評価を行います。

## **▼4.4.認識精度、損失値の推移を可視化する**

学習時に取得した認識精度、損失値を使って、推移を可視化します。

```py3
#Accuracyの推移
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.savefig('accuracy.png')
plt.close()

# Lossの推移
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.savefig('loss.png')
```

結果はこんな感じ。
過学習もしてないようで、いい感じです。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/70897/e4cc91be-9132-629f-2124-2ff475404615.png)

![image.png](https://qiita-image-store.s3.amazonaws.com/0/70897/11254277-39f4-6e06-3c8c-68d87f2c15b1.png)

## **▼4.5.全実装**

```py3
# coding: utf-8
import matplotlib.pyplot as plt
from keras.datasets import mnist
from keras.models import Sequential
from keras.layers import Dense
from keras import optimizers
from keras.callbacks import CSVLogger

# MNISTデータのダウンロード
# x_train, x_test: shape (num_samples, 28, 28) の白黒画像データのuint8配列．
# y_train, y_test: shape (num_samples,) のカテゴリラベル(0-9のinteger)のuint8配列．
(x_train, y_train), (x_test, y_test) = mnist.load_data()
# 成形(3次元→2次元)
x_train = x_train.reshape(x_train.shape[0], -1)
x_test = x_test.reshape(x_test.shape[0], -1)
# 正規化
x_train = x_train.astype('float32') / 255
x_test = x_test.astype('float32') / 255

print('x_train.shape:', x_train.shape)
print('y_train.shape:', y_train.shape)
print('x_test.shape:', x_test.shape)
print('y_test.shape:', y_test.shape)

# モデルの定義
model = Sequential([
    Dense(500, activation='relu', input_shape=(784, )),
    Dense(300, activation='relu'),
    Dense(10, activation='softmax'),
])

# モデルの要約を出力
model.summary()

# 最適化手法の設定
opt = optimizers.Adam()

# モデルのコンパイル
model.compile(optimizer = opt,                          # 最適化手法
              loss = 'sparse_categorical_crossentropy', # 損失関数
              metrics = ['accuracy'])                   # 評価関数

csv_logger = CSVLogger('training.log')
# 学習
history = model.fit(x_train, y_train, 
                    epochs=10, 
                    batch_size=100, 
                    validation_data=(x_test, y_test), 
                    callbacks=[csv_logger])
# 評価
score = model.evaluate(x_test, y_test, batch_size=32)

print('score[loss, accuracy]:', score)

#Accuracyの推移
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.savefig('accuracy.png')
plt.close()

# Lossの推移
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.savefig('loss.png')
```

# **■5.実装：畳み込みニューラルネットワーク**

最後に畳み込みニューラルネットワークの実装を試してみました。
まずは全実装を示し、全結合の実装との相違点を説明していきます。

## **▼5.1.全実装**

```py3
# coding: utf-8
import matplotlib.pyplot as plt
from keras.datasets import mnist
from keras.models import Sequential
from keras.layers import Dense, Conv2D, MaxPooling2D, Flatten
from keras import optimizers
from keras.callbacks import CSVLogger
from keras import metrics

# MNISTデータのダウンロード
# x_train, x_test: shape (num_samples, 28, 28) の白黒画像データのuint8配列．
# train, y_test: shape (num_samples,) のカテゴリラベル(0-9のinteger)のuint8配列．
(x_train, y_train), (x_test, y_test) = mnist.load_data()
# 成形(3次元→4次元)
x_train = x_train.reshape(x_train.shape[0], x_train.shape[1], x_train.shape[2], 1)
x_test = x_test.reshape(x_test.shape[0], x_test.shape[1], x_test.shape[2], 1)
# 正規化
x_train = x_train.astype('float32') / 255
x_test = x_test.astype('float32') / 255

print('x_train.shape:', x_train.shape)
print('y_train.shape:', y_train.shape)
print('x_test.shape:', x_test.shape)
print('y_test.shape:', y_test.shape)

# モデルの定義
model = Sequential([
    Conv2D(32, kernel_size=(3, 3), activation='relu', input_shape=(28, 28, 1)),
    MaxPooling2D(),
    Conv2D(64, kernel_size=(3, 3), activation='relu', input_shape=(28, 28, 1)),
    MaxPooling2D(),
    Conv2D(64, kernel_size=(3, 3), activation='relu', input_shape=(28, 28, 1)),
    Flatten(),
    Dense(100, activation='relu'),
    Dense(10, activation='softmax'),
])

# モデルの要約を出力
model.summary()

# 最適化手法の設定
opt = optimizers.Adam()

# モデルのコンパイル
model.compile(optimizer = opt,                          # 最適化手法
              loss = 'sparse_categorical_crossentropy', # 損失関数
              metrics = ['accuracy'])                   # 評価関数

csv_logger = CSVLogger('training.log')
# 学習
history = model.fit(x_train, y_train, 
                    epochs=10, 
                    batch_size=100, 
                    validation_data=(x_test, y_test), 
                    callbacks=[csv_logger])
# 評価
score = model.evaluate(x_test, y_test, batch_size=32)

print('score[loss, accuracy]:', score)

#Accuracyの推移
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.savefig('accuracy.png')
plt.close()

# Lossの推移
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.savefig('loss.png')
```

## **▼5.2.変更点1：入力データの成形**

全結合層のニューラルネットワークとの比較で、入力データの形状を維持したまま処理を行えることができるのが畳み込みニューラルネットワークの大きなアドバンテージと言われています。
そのため、全結合層ではニューラルネットワークに処理させるのに、入力データの形状をフラットにしていますが、

```py3:全結合層ニューラルネットワークの場合
# 成形(3次元→2次元)
x_train = x_train.reshape(x_train.shape[0], -1)
x_test = x_test.reshape(x_test.shape[0], -1)
```

畳み込みニューラルネットワークでは入力データの形状を変える必要はありません。

```py3:畳み込みニューラルネットワークの場合
# 成形(3次元→4次元)
x_train = x_train.reshape(x_train.shape[0], x_train.shape[1], x_train.shape[2], 1)
x_test = x_test.reshape(x_test.shape[0], x_test.shape[1], x_test.shape[2], 1)
```

4次元目はチャンネル数で、チャンネル方向のデータが複数ある場合、例えばRGBなら3が入りますが、今回使用しているMNISTはモノクロですので1固定となります。

## **▼5.3.変更点2：モデルの定義**

畳み込みニューラルネットワークでは、全結合層では用いない畳み込み層、プーリング層が入ってきますので当然モデルの定義も変わってきます。

```py3:全結合層ニューラルネットワークの場合
# モデルの定義
model = Sequential([
    Dense(500, activation='relu', input_shape=(784, )),
    Dense(300, activation='relu'),
    Dense(10, activation='softmax'),
])
```

こちらは全結合層の3層ニューラルネットワークの定義。

```py3:畳み込みニューラルネットワークの場合
# モデルの定義
model = Sequential([
    Conv2D(32, kernel_size=(3, 3), activation='relu', input_shape=(28, 28, 1)),
    MaxPooling2D(),
    Conv2D(64, kernel_size=(3, 3), activation='relu', input_shape=(28, 28, 1)),
    MaxPooling2D(),
    Conv2D(64, kernel_size=(3, 3), activation='relu', input_shape=(28, 28, 1)),
    Flatten(),
    Dense(100, activation='relu'),
    Dense(10, activation='softmax'),
])
```

こちらは畳み込みニューラルネットワークの定義。
畳み込み層 - プーリング層 - 畳み込み層 - プーリング層 - 畳み込み層 - 全結合層 - 全結合層
となっています。
畳み込み層から全結合層にするときにFlatten()を使い、入力を平滑化しているのがポイント。

[Flatten](https://keras.io/ja/layers/core/#flatten)

これら2つの変更のみで全結合層のモデルを畳み込み層を含むモデルに変えることができました。