TensorFlow2 + Keras による画像分類に挑戦9 ～モデルの学習、セーブ＆ロードをしてみる～

# はじめに
TensorFlow2 + Keras を利用した画像分類（Google Colaboratory 環境）についての勉強メモ（第9弾）です。題材は、ド定番である手書き数字画像（MNIST）の分類です。

- TensorFlow2 + Keras による画像分類に挑戦 シリーズ
  - [1. とりあえず動かす](https://qiita.com/code0327/items/7d3c7bd3327ff049243a)
  - [2. 入力データを詳しくみてみる](https://qiita.com/code0327/items/2c969ca4675d5a3691ef)
  - [3. MNISTデータを可視化してみる](https://qiita.com/code0327/items/a6edd5b27256429c3819)
  - [4. 学習済みモデルで予測させてみる](https://qiita.com/code0327/items/1047adc050ab6d75ad5c)
  - [5. 分類に失敗する画像を観察してみる](https://qiita.com/code0327/items/5dfc1b2ed143c1f9bd2b)
  - [6. 自分で用意した画像の前処理・分類をしてみる](https://qiita.com/code0327/items/d1a6b63915871a17020a)
  - [7. 層タイプ・活性化関数について理解する](https://qiita.com/code0327/items/68dd7beb1196320c8c15)
  - [8. 最適化アルゴリズムと損失関数を選択する](https://qiita.com/code0327/items/4f8f656bed23140b8962)
  - [9. モデルの学習、セーブ＆ロードをしてみる](https://qiita.com/code0327/items/951f418c81dbbd65c1fc)


[前回](https://qiita.com/code0327/items/4f8f656bed23140b8962)は、チュートリアルのプログラムの `model.compile(...)` のところ（**最適化アルゴリズム**や**損失関数**の設定）について取り上げました。

今回は、モデルの学習 `model.fit(...)` の設定（エポック数、バッチ数、バリデーション用データ設定など）について、理解していきたいと思います。また、学習済みモデルのセーブとロードについても扱っていきたいと思います。

# モデルのトレーニング（学習）

トレーニングは、`fit()` メソッドで行ないます（モデルをトレーニングデータにフィット（適合）させるという意味で fit ）。

- `model.fit()` のリファレンスは[こちら](https://www.tensorflow.org/api_docs/python/tf/keras/Model?version=stable#fit)を参照

「[初心者のための TensorFlow 2.0 入門](https://www.tensorflow.org/tutorials/quickstart/beginner?hl=ja)」のチュートリアルのなかでは、次のような3つの引数を与えてトレーニングを行なっています。

```py
model.fit(x_train, y_train, epochs=5)
```

第一引数はトレーニング用の**入力データのNumpy配列**（バッチ）、第二引数はトレーニング用の**正解データのNumpy配列**（バッチ）、第三引数 `epochs` は「入力データのNumpy配列全体」を1単位と数えた学習の反復回数を示す**エポック数**（整数値）になります。

エポック数（`epochs`）が大きいほど、よりトレーニングデータに適合したモデルとなっていく一方で、過学習に陥る可能性が高くなり、また、学習にかかる時間も長くなっていきます。

1エポックの終了ごとに、次のようにトレーニング用に対する評価が行なわれたログが出力されます。

>Train on 60000 samples
>Epoch 1/5
>60000/60000 [==//====] - 5s 83us/sample - loss: 0.2978 - accuracy: 0.9138
>Epoch 2/5
>60000/60000 [==//====] - 4s 75us/sample - loss: 0.1454 - accuracy: 0.9566


上記の `loss: 0.2978` は（当該エポック完了時点でのモデルを使って）トレーニングデータに対する損失関数値（loss）を評価した結果、`accuracy: 0.9138` は正解率を評価した結果になります。基本的に、`loss` と `accuracy` はエポック数が大きくなればなるほど改善していきます。

## バリデーションデータ（検証用データ）の指定

上記で出力される `loss` と `accuracy` のモニタリングでは、**過学習に陥っていないか（汎化性能が失われていっていないか）を判断することはできません**。

そこで、トレーニングデータの一部をバリエーションデータ（検証、Validation）として切り分け、それを使ってエポック完了毎に評価を行なうことができます（**バリエーション用として切り分けらたデータは、トレーニングには一切使用されません**）。

具体的には、次のようにして、バリエーション用として使用するデータ割合（`validation_split`）を指定できます。

```py
model.fit(x_train, y_train, validation_split=0.2, epochs=5)
```

実行時のログは、次のようになります。

>Train on 48000 samples, validate on 12000 samples
>Epoch 1/5
>48000/48000 [==============================] - 4s 92us/sample - loss: 0.3273 - accuracy: 0.9036 - val_loss: 0.1515 - val_accuracy: 0.9572
>Epoch 2/5
>48000/48000 [==============================] - 4s 85us/sample - loss: 0.1619 - accuracy: 0.9521 - val_loss: 0.1233 - val_accuracy: 0.9657

`validation_split`を指定しなかったときは「`Train on 60000 samples`」でしたが、これが「`Train on 48000 samples, validate on 12000 samples`」に変わっていることが確認できます。トレーニングデータ 60,000 件の 20% にあたる **12,000 件を検証用に割り当てている**ということです。

また、エポック毎に、バリデーションデータを使ったモデルの評価 `val_loss` と `val_accuracy` も出力されていることが分かります。エポックが進んでいくなかで、`loss` と `val_loss` の両方が下がっていけば順調に学習できていることを意味します。一方で、`loss` は下がるものの `val_loss` が上がってくれば過学習の状態になっていると考えられます。

`validation_split` オプションでは、トレーニングデータから一定割合をバリデーションデータとして使用しましたが、別途用意したデータをバリデーションデータに割り当てることもできます。前回や前々回では、ハイパーパラメータの効果を調べるために、次のようにして、テストデータをそのままバリデーションデータとして割り当てました。

```py
model.fit(x_train, y_train, validation_data=(x_test,y_test), epochs=5)
```

## バッチサイズの指定

`batch_size` オプションで、**トレーニングにおけるバッチサイズ**を指定することができます。省略した場合は、batch_size=32` と指定した場合と同じになります。

トレーニングでは、**ミニバッチ学習**という方法を採用しており、例えば 60,000 件のトレーニングデータを与えても、それを一気に全部使って学習はしません。バッチサイズで指定された32件のデータを 60,000 件からランダムに取り出して学習（重み更新）、次に 59,968 件からランダムに32件を取り出して学習（重み更新）・・・を繰り返します。バッチサイズが小さいほど、重み更新頻度が上がって計算時間は長くなります。

一般に、バッチサイズには、32、64、128などが採用されるようです。

```py
model.fit(x_train, y_train, batch_size=64, epochs=5)
```

## `fit()` の戻値

`fit()` を実行すると、Historyオブジェクトが戻値として得られます。`History.history` で、次のようにエポック毎の `loss`、`val_loss`、`accuracy`、`val_accuracy` をリストとして得ることができます。前回、前々回では、このデータを使ってグラフを描きました。

```py
tmp = model.fit(x_train, y_train, validation_split=0.2, epochs=5)
print(tmp.history)
```
>{'loss': [0.32421727414925894, 0.15691129270382226, 0.11842467025667429, 0.09661550550659498, 0.07930808525610095], 
> 'accuracy': [0.9054792, 0.95329165, 0.9648542, 0.97077084, 0.9762083], 
> 'val_loss': [0.15114309609557192, 0.1147864429069062, 0.09423549160702775, 0.09074506457825192, 0.08207530307924996], 
> 'val_accuracy': [0.95933336, 0.967, 0.97216666, 0.97291666, 0.97525]}


# モデルをファイルに保存

チュートリアルで扱っているような小規模NNモデルの場合、トレーニングに要する時間は大したことありません。しかし、モデル規模が大きくなったり、トレーニングのエポック数を大きく設定したりすると、そのトレーニング実行時間はとても長いものになります。

そのため、トレーニング結果である学習済みモデルは、ファイルに保存して必要に応じて呼び出せるようにしておくと便利です。


特に Google Colab. では **アイドル状態（セルを実行していない状態）が90分間つづくとインスタンスが落とされてしまい**、再接続したときには**メモリがクリア**されてしまいます（変数の内容が消えてしまいます）。

よって、「トレーニングを実行」→「時間がかかるので席を離れる」→「トレーニングが終了（学習済みモデルが完成）」→「90分アイドルがつづく」→「インスタンスが落とされ学習済みモデルが消えてしまう」→「**席に戻って愕然**」ということが起きてしまいます（PCがスリープ状態になっても同様のことが起きます）。

これを避けるためにも、トレーニングの直後には「モデルの保存」のコードを入れておくと安心です。

通常、変数内容（オブジェクト）のシリアライズ・保存には pickle を利用します。しかし、pickle でモデルを保存しようとすると `TypeError: can't pickle _thread.RLock objects` のように**失敗**します。

そのため、モデルの保存には専用の `model.save(...)` を利用します。引数としてファイルパスを指定するだけです。

```py:学習済みモデルの保存
model.save('model-01.h5') # 一時領域に保存
# model.save('/content/model-01.h5') # 絶対パスで一時領域に保存
```

Google Colab. 環境では、GoogleDriveをマウントして、そこに保存することをお勧めします。GoogleDriveのマウント方法については、[こちら](https://qiita.com/code0327/items/4e5c4dbf1185e617a3e3) の後半を参照ください。

なお、モデルのコンパイル前に保存しようとすると `WARNING:tensorflow:No training configuration found in save file: the model was *not* compiled. Compile it manually.` のような警告ができます。

# モデルの読込み

保存したモデルの読込みは次のようにします。

```py:学習済みモデルの読込み
import tensorflow as tf
model = tf.keras.models.load_model('model-01.h5')
```

# 参考資料

- [機械学習／ディープラーニングにおけるバッチサイズ、イテレーション数、エポック数の決め方](https://qiita.com/kenta1984/items/bad75a37d552510e4682)
- [Google Colaboratory で Chainer を触ってみるvol.5 ~エポック数, バッチサイズを理解する~](https://qiita.com/4no3/items/cc2322d654e279d55596)