Kerasのコールバック。各トレーニングエポックでの予測値の保存と可視化

KerasはTensorflowライブラリと一緒に使われる高水準APIで、多くの人にとっての参入障壁を下げ、Deep Learningモデルやシステムの作成を民主化しました。

使い始めの頃は、内部の仕組みのほとんどを抽象化した高レベルのAPIが、基本を理解し、直感的に操作できるようにするのに役立ちます。

しかし、実務家は、実用的な洞察を得たり、モデルがどのように学習するかをより深く理解したりするために、ボンネットの下で何が起こっているかをより強く直感したいと思うのが自然でしょう。

ということです。

多くの場合、Deep Neural Networkの学習プロセスを見て、学習エポックごとに値を予測する方法をテストし、その値を保存することが有用です。

この保存された値は、MatplotlibやSeabornなどのライブラリを使用して予測値を可視化したり、ログに保存してスマートシステムでさらに分析したり、単に人間が分析したりするのに利用することができます。

しかし、学習曲線は時間経過による平均損失を反映しており、学習が終了するまでモデルがどのように動作するかを見ることはできません。

Kerasにはコールバックという素晴らしい機能があり、これは学習中に呼び出されるコードの断片で、学習プロセスをカスタマイズするために使用することができます。

通常、コールバックを使って、モデルのパフォーマンスが良ければ保存し、オーバーフィットしていれば学習を停止し、そうでなければ学習プロセスのステップに反応したり影響を与えたりすることができます。

このため、各バッチやエポックで予測を実行し、結果を保存するためにコールバックは自然な選択となります。

このガイドでは、Kerasの各トレーニングエポックで、テストセット上で予測を実行し、結果を視覚化し、画像として保存する方法について見ていきます。

注意:これからKerasを使って簡単なDeep Learningモデルを構築していきますが、実装やデータセットにはあまり重点を置きません。

これは回帰モデルを構築するためのガイドではありませんが、コールバックがどのように動作するかを適切に紹介するためにモデルが必要です。

>
> これらのモデルを構築する方法と、単に正確なのではなく、高い精度を得る方法についてもっと読みたいという方は、広範囲で詳細なハンズオン住宅価格予測 – Pythonによる機械学習!を読んでみてください。

Kerasによるディープラーニングモデルの構築と評価

説明のために、簡単なKerasモデルを構築してみましょう。

このセクションでは、最小限の焦点と注意でスピードアップします – これは回帰モデルの構築に関するガイドではありません。

Scikit-Learnの datasets モジュールを通して入手した、カリフォルニア住宅データセット(California Housing Dataset)を使って作業します。

それでは、これから使うライブラリと静的メソッドをインポートしましょう。

import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd
import tensorflow as tf
from tensorflow import keras
from sklearn.datasets import fetch_california_housing
from sklearn.model_selection import train_test_split


では、データセットをロードして、トレーニングセットとテストセットに分割し(後で検証セットを分割します)、家の位置を可視化して、データが正しくロードされたかどうかを確認しましょう。

X, y = fetch_california_housing(as_frame=True, return_X_y=True)
x_train, x_test, y_train, y_test = train_test_split(x, y)


plt.figure(figsize=(12, 8))
sns.scatterplot(data=x, x='Longitude', y='Latitude', size=y, alpha=0.5, hue=y, palette='magma')
plt.show()


カリフォルニアのようですね。

データが正しく読み込まれたので、単純なシーケンシャルなKerasモデルを定義することができます。

checkpoint = keras.callbacks.ModelCheckpoint("california.h5", save_best_only=True)


model = keras.Sequential([
    keras.layers.Dense(64, activation='relu', kernel_initializer='normal', kernel_regularizer="l2", input_shape=[x_train.shape[1]]),
    keras.layers.Dropout(0.2),
    keras.layers.BatchNormalization(),

    keras.layers.Dense(64, activation='relu', kernel_initializer='normal', kernel_regularizer="l2"),
    keras.layers.Dropout(0.2),
    keras.layers.BatchNormalization(),

    keras.layers.Dense(1)
])


model.compile(loss='mae',
              optimizer=keras.optimizers.RMSprop(learning_rate=1e-2, decay=0.1),
              metrics=['mae'])

history = model.fit(
    x_train, y_train,
    epochs=150,
    batch_size=64,
    validation_split=0.2,
    callbacks=[checkpoint]
)


ここでは、オーバーフィッティングを防ぐためにドロップアウトとバッチ正規化を行い、RMSpropオプティマイザとMean Absolute Error Lossで最適化されたシンプルなMLPを使用しています。

このモデルを150エポック、検証分割数「0.2」でフィッティングし、「ModelCheckpoint」コールバックで重みをファイルに保存しました。

これを実行すると、次のような結果になります。

...
Epoch 150/150
387/387 [==============================] - 3s 7ms/step - loss: 0.6279 - mae: 0.5976 - val_loss: 0.6346 - val_mae: 0.6042


学習カーブを可視化することで、学習がどのように行われたかを知ることができます。

しかし、学習カーブだけでは全体像がわかりません。

model_history = pd.DataFrame(history.history)
model_history['epoch'] = history.epoch


fig, ax = plt.subplots(1, figsize=(8,6))
num_epochs = model_history.shape[0]


ax.plot(np.arange(0, num_epochs), model_history["mae"], 
        label="Training MAE")
ax.plot(np.arange(0, num_epochs), model_history["val_mae"], 
        label="Validation MAE")
ax.legend()


plt.tight_layout()
plt.show()


この結果

そして、我々のモデルを評価することができます。

model.evaluate(x_test, y_test)


162/162 [==============================] - 0s 2ms/step - loss: 0.5695 - mae: 0.5451 - mape: 32.2959


ターゲット変数が$100.000の倍数で測定されるので、我々のネットワークは$54.000まで価格を見逃すことを意味し、これは~32%の平均絶対誤差となります。

ランダムフォレスト回帰のような伝統的な機械学習手法の多くは、このデータセットに対してより広範なデータ前処理を行った後でも、ハイパーパラメータを調整することで約52,000ドルを達成しています。

ここでのポイントは、特に正確なモデルを構築することではありませんが、モデルがあまり早く収束しないようなデータセットを選択したため、ターゲット変数の周りでモデルが踊る様子を観察することができます。

モデルがどのように機能しているかを評価する、より分かりやすい方法は、平均絶対誤差と平均絶対パーセント誤差の集計を完全に捨て、実際の価格に対する予測価格の散布図をプロットすることです。

もし両者が等しければ、プロットされたマーカーは斜めにまっすぐな軌跡を描くことになります。

参考までに、対角線をプロットし、各マーカーがその線にどれだけ近いかを評価することもできます。

test_predictions = model.predict(x_test)
test_labels = y_test


fig, ax = plt.subplots(figsize=(8,4))
plt.scatter(test_labels, test_predictions, alpha=0.6, 
            color='#FF0000', lw=1, ec='black')
lims = [0, 5]


plt.plot(lims, lims, lw=1, color='#0000FF')
plt.ticklabel_format(useOffset=False, style='plain')
plt.xticks(fontsize=18)
plt.yticks(fontsize=18)
plt.xlim(lims)
plt.ylim(lims)


plt.tight_layout()
plt.show()


このコードを実行すると、次のようになります。

このネットワークは安い家を高く、高い家を低く見積もり、そして見積もりはかなり余裕のある範囲を持っています(右側のいくつかの予測は完全に範囲外ですが、これはデータセットをクリーニングしていないため、多くの家の価格が輸入時にその値に制限されているために起こります)。

これは学習曲線から得られる洞察ではなく、逆の効果、つまり安い家をアンダープライスし、高い家をオーバープライスするネットワークは、同じMAEとMAPEを持っていても全く異なる振る舞いをする可能性があるのです。

また、モデルがどのようにしてここに到達したのか、そしてこれらの予測が時間や学習プロセスを通じてどのように変化したのかにも興味があります。

これは学習プロセスの終点に過ぎず、ここに到達するまでにかなりの学習が行われました。

このコールバックは各エポックにおいてテストセット上で予測を実行し、予測を視覚化し、画像として保存します。

プロットによるカスタム予測Kerasコールバック

ちょうど ModelCheckpoint コールバックを使って、各エポックにおいてモデルがベストパフォーマンス状態であるかをチェックし、それを .h5 ファイルに保存して持続させたように、予測を実行して視覚化し、ディスクに画像を保存するカスタムコールバックを書くことができます。

カスタムコールバックを作成するには、Callbackクラスを拡張して、そのクラスが提供するメソッドをオーバーライドします。

class PerformancePlotCallback(keras.callbacks.Callback):

    def on_train_end(self, logs=None):
      ...
    def on_epoch_begin(self, epoch, logs=None):
      ...
    def on_epoch_end(self, epoch, logs=None):
      ...
    def on_test_begin(self, logs=None):
      ...
    def on_test_end(self, logs=None):
      ...
    # Etc.


トレーニング中のモデルを使っていつ予測したいかに応じて、適切なメソッドを選択することになります。

学習の進捗を測るにはエポックが必要なので、各トレーニングエポックの終了時に、テストセットでモデルをテストします。

これは外部データなので、コールバックにテストセットを提供する方法が必要です。

最も簡単な方法は、テストセットを受け取るコンストラクタを定義し、その上で現在のモデルを評価し、一貫した結果を得ることです。

class PerformancePlotCallback(keras.callbacks.Callback):
    def __init__(self, x_test, y_test):
        self.x_test = x_test
        self.y_test = y_test

    def on_epoch_end(self, epoch, logs=None):
        print('Evaluating Model...')
        print('Model Evaluation: ', self.model.evaluate(self.x_test))


この単純なコールバックは、家屋のテストセットと関連するターゲット変数を受け取り、各エポックで自分自身を評価し、通常のKeras出力と一緒に、コンソールに結果を出力します。

このコールバックをインスタンス化してモデルに追加し、再度 fit() した場合、以前とは異なる結果が表示されます。

performance_simple = PerformancePlotCallback(x_test, y_test)


# Model definition and compilation...


history = model.fit(
    x_train, y_train,
    epochs=150,
    validation_split=0.2,
    callbacks=[performance_simple]
)


という結果になります。

Epoch 1/150
387/387 [==============================] - 3s 7ms/step - loss: 1.0785 - mae: 1.0140 - val_loss: 0.9455 - val_mae: 0.8927
Evaluating Model...
162/162 [==============================] - 0s 1ms/step - loss: 0.0528 - mae: 0.0000e+00
Model Evaluation:  [0.05277165770530701, 0.0]
Epoch 2/150
387/387 [==============================] - 3s 7ms/step - loss: 0.9048 - mae: 0.8553 - val_loss: 0.8547 - val_mae: 0.8077
Evaluating Model...
162/162 [==============================] - 0s 1ms/step - loss: 0.0471 - mae: 0.0000e+00
Model Evaluation:  [0.04705655574798584, 0.0]
...


すごい! このモデルは、エポックごとに、コールバックに渡されたデータに基づいて自己評価しています。

では、コールバックを修正して、すでに散乱している出力に予測値を表示する代わりに、予測値を可視化するようにしましょう。

単純化するために、コールバックは画像をフォルダに保存するようにし、後でそれらをビデオやGIFにつなぎ合わせることができるようにします。

また、コンストラクタに model_name を含めることで、画像とそのファイル名を生成する際にモデルを区別できるようにします。

class PerformancePlotCallback(keras.callbacks.Callback):
    def __init__(self, x_test, y_test, model_name):
        self.x_test = x_test
        self.y_test = y_test
        self.model_name = model_name

    def on_epoch_end(self, epoch, logs={}):
        y_pred = self.model.predict(self.x_test)
        fig, ax = plt.subplots(figsize=(8,4))
        plt.scatter(y_test, y_pred, alpha=0.6, 
            color='#FF0000', lw=1, ec='black')

        lims = [0, 5]


plt.plot(lims, lims, lw=1, color='#0000FF')
        plt.ticklabel_format(useOffset=False, style='plain')
        plt.xticks(fontsize=18)
        plt.yticks(fontsize=18)
        plt.xlim(lims)
        plt.ylim(lims)


plt.tight_layout()
        plt.title(f'Prediction Visualization Keras Callback - Epoch: {epoch}')
        plt.savefig('model_train_images/'+self.model_name+"_"+str(epoch))
        plt.close()


ここでは、各エポックについて Matplotlib の図を作成し、実際の価格に対して予測された価格の散布図をプロットします。

さらに、対角線の基準線を追加しました。

散布図のマーカーが対角線に近いほど、我々のモデルの予測はより正確であることを意味します。

散布図はモデル名とエポック数、そして学習中のモデルがどのエポックにいるかを示すタイトルと共に plt.savefig() で保存されます。

それでは、再びこのカスタムコールバックを使用して、x_testy_test セットに加え、モデル名を指定してみましょう。

checkpoint = keras.callbacks.ModelCheckpoint("california.h5", save_best_only=True)
performance = PerformancePlotCallback(x_test, y_test, "california_model")


# Model definition and compilation...

history = model.fit(
    x_train, y_train,
    epochs=150,
    validation_split=0.2,
    callbacks=[checkpoint, performance]
)


PerformancePlotCallbackが本格的に稼動し、指定されたフォルダに各エポックでのパフォーマンスの画像を生成します。

model_train_images フォルダは 150 個のプロットでいっぱいになりました。

お気に入りのツールを使って画像をつなぎ合わせてビデオやGifファイルにすることもできますし、単に手動で閲覧することもできます。

このデータで学習させたモデルのGIFです。

結論

このガイドでは、California Housing Datasetの家の価格をまあまあの精度で予測する簡単なモデルを構築しました。

そして、Deep LearningモデルのパフォーマンスをテストするためのカスタムKerasコールバックの書き方と、トレーニング中の各エポックでの可視化について見てきました。

これらの画像をディスクに保存し、そこからGifを作成することで、モデルの学習曲線の分析から得られるものとは異なる、学習プロセスに対する視点を得られるように進めました。

タイトルとURLをコピーしました