KerasでCallbackを自作する

# はじめに
最近ではKerasを使って初めてDeepLearningにトライしてみる人も多いと思います。やはり最初はMNISTを検討して、その結果をあれこれ見てみようと思うのだと思います。
KerasはDeepの構造を非常に簡単に書くことができますが、かゆいところに微妙に手が届きにくかったりします。その分簡単に書けたりするのですが・・

今回は自作のCallbackを用いて、学習の経過を表示してみましょう。

# KerasでMNIST
多分、普通にMNISTを学習しようと思うと、下記のようなコードだと思います。今回はシンプルにMLP(Multi Layer Perceptron）のネットワークアーキテクチャを用意しました。

```python:train.py
import keras
from keras.layers import Input, Dense
from keras.models import Model
from load_mnist import load_mnist
import matplotlib.pyplot as plt

class MLP:
    def __init__(self, epoch=20, batch_size=32):
        self.epoch = epoch
        self.batch_size = batch_size
        inputs = Input(shape=(784,))
        x = Dense(64, activation='relu')(inputs)
        x = Dense(64, activation='relu')(x)
        predictions = Dense(10, activation='softmax')(x)

        self.model = Model(inputs=inputs, outputs=predictions)
        self.model.compile(
            optimizer='rmsprop',
            loss='categorical_crossentropy',
            metrics=['accuracy'])

    def train(self, _x_train, _y_train, _x_test, _y_test):
        hist = self.model.fit(
            x=_x_train,
            y=_y_train,
            epochs=self.epoch,
            batch_size=self.batch_size,
            validation_split=0.2)
        return hist

if __name__ == '__main__':
    # MNISTをなんとか下記のような形で持ってくる。
    x_train, y_train, x_test, y_test = load_MNIST()

    # MLPのインスタンス生成
    model = MLP()

    # 学習
    hist = model.train(x_train, y_train, x_test, y_test)
```
このhistはdictionary形式で帰ってきますので、中身をプロットしてやれば学習の経過を見ることができます。

```python
import matplotib.pyplot as plt

# Accuracyのプロット
plt.figure()
plt.title('Accuracy')
plt.xlabel('epoch')
plt.ylabel('accuracy')
plt.plot(hist.history['acc'], label='train')
plt.plot(hist.history['val_acc'], label='validation')
plt.legend()

# Lossのプロット
plt.figure()
plt.title('CrossEntropy Loss')
plt.xlabel('epoch')
plt.ylabel('loss')
plt.plot(hist.history['loss'], label='train')
plt.plot(hist.history['val_loss'], label='validation')
plt.legend()
plt.show()
```
![Accuracy.png](https://qiita-image-store.s3.amazonaws.com/0/204712/8df1cd83-7b9a-fb27-5300-10aed7ae765e.png)
![Loss.png](https://qiita-image-store.s3.amazonaws.com/0/204712/ff52fe8d-b040-5fed-f83c-721c2417a35f.png)
過学習してるなー、とかこれでわかりますね。

# Callback関数
プログラムを書いてきた人ならCallback関数は馴染みがあると思います。
引数として関数を渡すものです。細かいことは、こちらによく書いてあります。
[コールバック関数とは](https://qiita.com/pocket8137/items/df5afba90b51e90587a5)

Kerasにおいては、fitをする際にCallback関数を設定して、学習ついでに色々な処理ができます。たとえばtensorboardを用いてLossを表示したり、ModelのSaveを行ったり、というものが紹介されています。
[Keras Callbacks](https://keras.io/callbacks/)

Documentationのいちばん下の方に、”Create a callback”というのがありますが、それを今回は紹介しようと思います。
既にコードのサンプルを書いたように、例えばLossのグラフを学習後に書くことはKerasでとても簡単にできます。しかし、時間のかかる学習の場合など、リアルタイムに現状のLossを見たいとか、現状のConfusionMatrixが見たいとかあると思います。そういう痒いところをCallbackで実現しようというものです。

# Callbackの作成
KerasのDocumentationではこんな風にCallbackを記述しろ、と書かれていますね。

```python
class LossHistory(keras.callbacks.Callback):
    def on_train_begin(self, logs={}):
        self.losses = []

    def on_batch_end(self, batch, logs={}):
        self.losses.append(logs.get('loss'))
```
これをfitの関数に引き渡すことで、trainingが始まるとon_train_beginが、batchが終わるとon_batch_endが走るわけです。
ではこれを応用して、epochの終了毎にLossを保存し、それを表示してみましょう。

```python:loss_history.py
import numpy as np
import matplotlib.pyplot as plt
import keras


class LossHistory(keras.callbacks.Callback):
    def __init__(self):
        # コンストラクタに保持用の配列を宣言しておく
        self.train_acc = []
        self.train_loss = []
        self.val_acc = []
        self.val_loss = []

    def on_epoch_end(self, epoch, logs={}):
        # 配列にEpochが終わるたびにAppendしていく
        self.train_acc.append(logs['acc'])
        self.val_acc.append(logs['val_acc'])
        self.train_loss.append(logs['loss'])
        self.val_loss.append(logs['val_loss'])

        # グラフ描画部
        plt.figure(num=1, clear=True)
        plt.title('accuracy')
        plt.xlabel('epoch')
        plt.ylabel('accuracy')
        plt.plot(self.train_acc, label='train')
        plt.plot(self.val_acc, label='validation')
        plt.legend()
        plt.pause(0.1)
```
```python:train.py
class MLP:
    """
        省略
    """

    def train(self, _x_train, _y_train, _x_test, _y_test, cb_my):
        hist = self.model.fit(
            x=_x_train,
            y=_y_train,
            epochs=self.epoch,
            batch_size=self.batch_size,
            validation_split=0.2,
            callback=[cb_my])
        return hist

if __name__ == '__main__':
    """
        省略
    """

    # Callbackのインスタンス作成
    cb_my = loss_history.LossHistory()

    # 作成したCallbackを渡して学習をする
    hist = model.train(x_train, y_train, x_test, y_test, cb_my)
```

上記のように、Callbackを定義し、fit関数を叩きます。
そうすると、下記のようなグラフが時間経過と共に表示されます。
![sample.gif](https://qiita-image-store.s3.amazonaws.com/0/204712/ff72a346-c34a-065b-e80e-a4490dba0205.gif)


LossやAccuracyだけで言えば、Tensorboardでも同等なことができますが、同様に自分でグラフを書くことで、途中経過でのヒストグラムとかConfusion Matrixなど、なんでも出力できます。callbackではそのときのmodelの状態を見ることもできるので、例えばWeightなどを表示させつつ、学習過程を覗くことも可能です。

# まとめ
KerasでのCallback関数の基本的な実装を紹介しました。学習過程で、色々なものをみながら、効率よく検討を進めていきましょう。