質問があります? TensorFlowフォーラム訪問フォーラムでコミュニティとつながる

TensorBoard のスカラー: Keras でトレーニングメトリックをログする

TensorFlow.org で表示 Google Colab で実行 GitHub でソースを表示

概要

機械学習には、損失などの主要なメトリック、そしてトレーニングが進むにつれそれらがどのように変化するかを理解することが必ず伴います。こういったメトリックは、過適合が発生していないか、不要に長くトレーニングしていないかなどを理解する上で役立ちます。そのため、モデルのデバッグや改善を行いやすくするために、トレーニングの実行から得られるメトリックを比較すると良いでしょう。

TensorBoard の Scalars ダッシュボードでは、単純な API を使用して簡単にメトリックを視覚化することができます。このチュートリアルでは、非常に基本的な例を使って、Keras モデルを開発する際に TensorBoard でこれらの API を使用する方法を説明します。Keras TensorBoard コールバックと TensorFlow Summary API を使用してデフォルトとカスタムのスカラーを視覚化する方法を学習します。

セットアップ

# Load the TensorBoard notebook extension.
%load_ext tensorboard
from datetime import datetime
from packaging import version

import tensorflow as tf
from tensorflow import keras

import numpy as np

print("TensorFlow version: ", tf.__version__)
assert version.parse(tf.__version__).release[0] >= 2, \
    "This notebook requires TensorFlow 2.0 or above."
TensorFlow version:  2.2

簡単な回帰用データをセットアップする

これから Keras を使用して回帰を計算し、データセットペアにおける最適な適合線を見つけたいと思います。(この種の問題でニューラルネットワークと勾配降下を使用するのはやり過ぎではありますが、例を理解しやすくなります。)

TensorBoard を使用して、エポック間でトレーニングとテストの損失がどのように変化するのかを観測します。時間の経過とともにトレーニングとテストの損失が下降して安定化する様子を確認してください。

まず、おおまかに線 y = 0.5x + 2 に沿って 1000 個のデータポイントを生成し、それらをトレーニングセットとテストセットに分割します。ここでは、ニューラルネットワークがこの関係を学習することを目標とします。

data_size = 1000
# 80% of the data is for training.
train_pct = 0.8

train_size = int(data_size * train_pct)

# Create some input data between -1 and 1 and randomize it.
x = np.linspace(-1, 1, data_size)
np.random.shuffle(x)

# Generate the output data.
# y = 0.5x + 2 + noise
y = 0.5 * x + 2 + np.random.normal(0, 0.05, (data_size, ))

# Split into test and train pairs.
x_train, y_train = x[:train_size], y[:train_size]
x_test, y_test = x[train_size:], y[train_size:]

モデルをトレーニングして損失をログする

モデルの定義、トレーニング、および評価の準備が整いました。

トレーニングしながら損失スカラーをログするには、次のように行います。

  1. Keras TensorBoard コールバックを作成します。
  2. ログディレクトリを指定します。
  3. TensorBoard コールバックを Keras の Model.fit() に渡します。

TensorBoard は、ログディレクトリ階層からログデータを読み取ります。このノートブックでは、ルートログディレクトリは logs/scalars で、その後にタイムスタンプ付きのサブディレクトリが続きます。タイムスタンプがサブディレクトリに付加されることで、TensorBoard を使用してモデルでイテレーションを行う際に、トレーニングセットを簡単に識別して選択することができます。

logdir = "logs/scalars/" + datetime.now().strftime("%Y%m%d-%H%M%S")
tensorboard_callback = keras.callbacks.TensorBoard(log_dir=logdir)

model = keras.models.Sequential([
    keras.layers.Dense(16, input_dim=1),
    keras.layers.Dense(1),
])

model.compile(
    loss='mse', # keras.losses.mean_squared_error
    optimizer=keras.optimizers.SGD(lr=0.2),
)

print("Training ... With default parameters, this takes less than 10 seconds.")
training_history = model.fit(
    x_train, # input
    y_train, # output
    batch_size=train_size,
    verbose=0, # Suppress chatty output; use Tensorboard instead
    epochs=100,
    validation_data=(x_test, y_test),
    callbacks=[tensorboard_callback],
)

print("Average test loss: ", np.average(training_history.history['loss']))
Training ... With default parameters, this takes less than 10 seconds.
Average test loss:  0.05271831926424056

TensorBoard を使って損失を調べる

では、上記で使用したルートログディレクトリを指定して、TensorBoard を起動しましょう。

TensorBoard の UI が読み込まれるまで数秒ほどかかります。

%tensorboard --logdir logs/scalars

TensorBoard に「No dashboards are active for the current data set(現在のデータセットにはアクティブなダッシュボードはありません)」というメッセージが表示されることがあります。これは、最初のログデータがまだ保存されていないためです。トレーニングが進むにつれ、Keras モデルはデータをログし始めるため、TensorBoard の再読み込みが定期的に行われ、スカラーメトリックが表示されるようになります。それまで待てない方は、右上にある再読み込み矢印をタップしてください。

トレーニングが進むにつれ、トレーニングと検証の損失が急速に下降し、安定する様子が見られます。実際、トレーニングが 25 エポックを終了したところであまり改善が見られなくなるため、その時点でトレーニングを停止することができます。

グラフをホバーし、特定のデータポイントを確認します。マウスを使用して拡大したり、部分的に選択して詳細を確認することも可能です。

左側に「Runs(実行)」セレクタがあります。1 つの「実行」は、1 ラウンドのトレーニングから得たログ一式を指します。このケースでは、これは Model.fit() の結果です。通常、長期間にわたってモデルの実験と開発が行われるため、実行の数は非常に多くなります。

Runs セレクタを使用して特定の実行を選択するか、トレーニングまたは検証のみから選択します。実行を比較すると、どのバージョンのコードで問題の解決が最適に行われているかを評価しやすくなります。

TensorBoard の損失グラフには、トレーニングと検証の両方で損失が一定して下降し、安定したことが示されています。つまり、モデルのメトリックは非常に良質である可能性が高いということです。では、このモデルが実際のデータでどのように動作するかを見てみましょう。

入力データ (60, 25, 2) がある場合、線 y = 0.5x + 2 は (32, 14.5, 3) となるはずです。モデルはこれと合致するでしょうか。

print(model.predict([60, 25, 2]))
# True values to compare predictions against: 
# [[32.0]
#  [14.5]
#  [ 3.0]]
[[32.234306 ]
 [14.5974245]
 [ 3.0074697]]

よくできました!

カスタムスカラーをログする

動的学習率などのカスタムの値をログする場合はどうでしょうか。この場合は、TensorFlow Summary API を使用する必要があります。

回帰モデルを維持したまま、カスタム学習率をログします。次のように行ってください。

  1. tf.summary.create_file_writer() を使ってファイルライターを作成します。
  2. カスタム学習率の関数を定義します。この関数は、Keras の LearningRateScheduler コールバックに渡されます。
  3. 学習率関数内に、カスタム学習率をログするための tf.summary.scalar() を使用します。
  4. LearningRateScheduler コールバックを Model.fit() に渡します。

一般的に、カスタムスカラーをログするには、tf.summary.scalar() をファイルライターとともに使用する必要があります。この実行のデータを特定のディレクトリに書き込むのはファイルライターであり、tf.summary.scalar() を使用する際にファイルライターが暗黙的に使用されるためです。

logdir = "logs/scalars/" + datetime.now().strftime("%Y%m%d-%H%M%S")
file_writer = tf.summary.create_file_writer(logdir + "/metrics")
file_writer.set_as_default()

def lr_schedule(epoch):
  """
  Returns a custom learning rate that decreases as epochs progress.
  """
  learning_rate = 0.2
  if epoch > 10:
    learning_rate = 0.02
  if epoch > 20:
    learning_rate = 0.01
  if epoch > 50:
    learning_rate = 0.005

  tf.summary.scalar('learning rate', data=learning_rate, step=epoch)
  return learning_rate

lr_callback = keras.callbacks.LearningRateScheduler(lr_schedule)
tensorboard_callback = keras.callbacks.TensorBoard(log_dir=logdir)

model = keras.models.Sequential([
    keras.layers.Dense(16, input_dim=1),
    keras.layers.Dense(1),
])

model.compile(
    loss='mse', # keras.losses.mean_squared_error
    optimizer=keras.optimizers.SGD(),
)

training_history = model.fit(
    x_train, # input
    y_train, # output
    batch_size=train_size,
    verbose=0, # Suppress chatty output; use Tensorboard instead
    epochs=100,
    validation_data=(x_test, y_test),
    callbacks=[tensorboard_callback, lr_callback],
)

もう一度 TensorBoard を確認しましょう。

%tensorboard --logdir logs/scalars

左側の「Runs」セレクタを使用すると、<timestamp>/metrics 実行が 1 つあります。この実行を選択すると「learning rate(学習率)」グラフが表示され、この実行における学習率の進行を確認することができます。

また、この実行のトレーニングと検証の損失曲線を、以前の実行のものと比較することも可能です。

このモデルの出来栄えはどうでしょうか。

print(model.predict([60, 25, 2]))
# True values to compare predictions against: 
# [[32.0]
#  [14.5]
#  [ 3.0]]
[[32.234013 ]
 [14.5973015]
 [ 3.0074618]]