TensorBoard 純量:在 Keras 中記錄訓練指標

在 TensorFlow.org 上檢視 在 Google Colab 中執行 在 GitHub 上檢視原始碼

總覽

機器學習總是牽涉到理解關鍵指標,例如損失,以及這些指標如何隨著訓練進度而變化。這些指標可協助您瞭解自己是否過度擬合,或者是否進行了不必要的過長時間訓練。您可能想要比較不同訓練回合中的這些指標,以協助偵錯及改進模型。

TensorBoard 的「時間序列儀表板」可讓您透過簡單的 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
from keras import backend as K

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.8.2
# Clear any logs from previous runs
rm -rf ./logs/

為簡單迴歸設定資料

您現在將使用 Keras 來計算迴歸,亦即找出成對資料集的最佳擬合線。(雖然使用神經網路和梯度下降對於這類問題來說是小題大作,但它確實構成了一個非常容易理解的範例。)

您將使用 TensorBoard 來觀察訓練和測試損失在不同 epoch 間的變化。希望您會看到訓練和測試損失隨著時間推移而減少,然後保持穩定。

首先,沿著線 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(learning_rate=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.042797307365108284

使用 TensorBoard 檢查損失

現在,啟動 TensorBoard,指定您在上方使用的根記錄目錄。

請稍候幾秒鐘,讓 TensorBoard 的 UI 啟動。

%tensorboard --logdir logs/scalars

您可能會看到 TensorBoard 顯示訊息「目前資料集沒有作用中的儀表板」。這是因為初始記錄資料尚未儲存。隨著訓練進度,Keras 模型將開始記錄資料。TensorBoard 將定期重新整理並向您顯示純量指標。如果您沒有耐心,可以點擊右上角的「重新整理」箭頭。

當您觀看訓練進度時,請注意訓練和驗證損失如何快速減少,然後保持穩定。事實上,您可以在 25 個 epoch 後停止訓練,因為在那之後訓練沒有太大改善。

將滑鼠游標懸停在圖表上以查看特定資料點。您也可以嘗試用滑鼠放大,或選取其中一部分以查看更多詳細資訊。

請注意左側的「回合」選取器。「回合」代表來自一輪訓練的一組記錄,在本例中為 Model.fit() 的結果。開發人員通常會有非常多的回合,因為他們會隨著時間推移進行實驗並開發模型。

使用「回合」選取器來選擇特定回合,或僅從訓練或驗證中選擇。比較回合將協助您評估哪個版本的程式碼能更好地解決您的問題。

好的,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.148884 ]
 [14.562463 ]
 [ 3.0056725]]

還不錯!

記錄自訂純量

如果您想要記錄自訂值,例如動態學習率,該怎麼辦?若要執行此操作,您需要使用 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

使用左側的「回合」選取器,請注意您有一個 <timestamp>/metrics 回合。選取此回合會顯示「學習率」圖表,可讓您驗證此回合期間學習率的進展情況。

您也可以將此回合的訓練和驗證損失曲線與先前的回合進行比較。您也可能會注意到學習率排程會傳回離散值 (取決於 epoch),但學習率圖可能看起來很平滑。TensorBoard 有一個平滑參數,您可能需要將其調低至零才能看到未平滑的值。

此模型的表現如何?

print(model.predict([60, 25, 2]))
# True values to compare predictions against: 
# [[32.0]
#  [14.5]
#  [ 3.0]]
[[31.958094 ]
 [14.482997 ]
 [ 2.9993598]]

批次層級記錄

首先,讓我們載入 MNIST 資料集、正規化資料,並撰寫一個函式來建立簡單的 Keras 模型,以將圖片分類為 10 個類別。

mnist = tf.keras.datasets.mnist

(x_train, y_train),(x_test, y_test) = mnist.load_data()
x_train, x_test = x_train / 255.0, x_test / 255.0

def create_model():
  return tf.keras.models.Sequential([
    tf.keras.layers.Flatten(input_shape=(28, 28)),
    tf.keras.layers.Dense(512, activation='relu'),
    tf.keras.layers.Dropout(0.2),
    tf.keras.layers.Dense(10, activation='softmax')
  ])
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/mnist.npz
11493376/11490434 [==============================] - 0s 0us/step
11501568/11490434 [==============================] - 0s 0us/step

即時批次層級記錄

即時記錄批次層級的指標可以顯示我們每個 epoch 訓練中批次之間的波動程度,這對於偵錯可能很有用。

設定摘要寫入器到不同的記錄目錄

log_dir = 'logs/batch_level/' + datetime.now().strftime("%Y%m%d-%H%M%S") + '/train'
train_writer = tf.summary.create_file_writer(log_dir)

若要啟用批次層級記錄,自訂 tf.summary 指標應透過覆寫 Model 類別定義中的 train_step() 並封閉在摘要寫入器內容中來定義。這可以簡單地合併到子類別化的 Model 定義中,也可以擴充以編輯我們先前的 Functional API Model,如下所示

class MyModel(tf.keras.Model):
  def __init__(self, model):
    super().__init__()
    self.model = model

  def train_step(self, data):
    x, y = data
    with tf.GradientTape() as tape:
      y_pred = self.model(x, training=True)
      loss = self.compiled_loss(y, y_pred)
      mse = tf.keras.losses.mean_squared_error(y, K.max(y_pred, axis=-1))
    self.optimizer.minimize(loss, self.trainable_variables, tape=tape)
    with train_writer.as_default(step=self._train_counter):
      tf.summary.scalar('batch_loss', loss)
      tf.summary.scalar('batch_mse', mse)
    return self.compute_metrics(x, y, y_pred, None)

  def call(self, x):
    x = self.model(x)
    return x

# Adds custom batch-level metrics to our previous Functional API model
model = MyModel(create_model())
model.compile(optimizer='adam',
              loss='sparse_categorical_crossentropy',
              metrics=['accuracy'])

定義我們的 TensorBoard 回呼,以將 epoch 層級和批次層級指標記錄到我們的記錄目錄,並使用我們選取的 batch_size 呼叫 model.fit()

tensorboard_callback = tf.keras.callbacks.TensorBoard(log_dir=log_dir)

model.fit(x=x_train, 
          y=y_train,
          epochs=5,
          batch_size=500, 
          validation_data=(x_test, y_test), 
          callbacks=[tensorboard_callback])
Epoch 1/5
120/120 [==============================] - 5s 36ms/step - loss: 0.4379 - accuracy: 0.8788 - val_loss: 0.2041 - val_accuracy: 0.9430
Epoch 2/5
120/120 [==============================] - 4s 31ms/step - loss: 0.1875 - accuracy: 0.9471 - val_loss: 0.1462 - val_accuracy: 0.9591
Epoch 3/5
120/120 [==============================] - 3s 27ms/step - loss: 0.1355 - accuracy: 0.9613 - val_loss: 0.1170 - val_accuracy: 0.9670
Epoch 4/5
120/120 [==============================] - 3s 27ms/step - loss: 0.1058 - accuracy: 0.9694 - val_loss: 0.0954 - val_accuracy: 0.9723
Epoch 5/5
120/120 [==============================] - 3s 27ms/step - loss: 0.0872 - accuracy: 0.9752 - val_loss: 0.0843 - val_accuracy: 0.9749
<keras.callbacks.History at 0x7fce165a2fd0>

使用新的記錄目錄開啟 TensorBoard,並查看 epoch 層級和批次層級指標

%tensorboard --logdir logs/batch_level

累積批次層級記錄

批次層級記錄也可以累積方式實作,將每個批次的指標與先前批次的指標平均,並在記錄批次層級指標時產生更平滑的訓練曲線。

設定摘要寫入器到不同的記錄目錄

log_dir = 'logs/batch_avg/' + datetime.now().strftime("%Y%m%d-%H%M%S") + '/train'
train_writer = tf.summary.create_file_writer(log_dir)

建立可依批次記錄的具狀態指標

batch_loss = tf.keras.metrics.Mean('batch_loss', dtype=tf.float32)
batch_accuracy = tf.keras.metrics.SparseCategoricalAccuracy('batch_accuracy')

與之前一樣,在覆寫的 train_step 方法中新增自訂 tf.summary 指標。若要使批次層級記錄成為累積式,請使用我們定義的具狀態指標來計算給定每個訓練步驟資料的累積結果。

class MyModel(tf.keras.Model):
  def __init__(self, model):
    super().__init__()
    self.model = model

  def train_step(self, data):
    x, y = data
    with tf.GradientTape() as tape:
      y_pred = self.model(x, training=True)
      loss = self.compiled_loss(y, y_pred)
    self.optimizer.minimize(loss, self.trainable_variables, tape=tape)
    batch_loss(loss)
    batch_accuracy(y, y_pred)
    with train_writer.as_default(step=self._train_counter):
      tf.summary.scalar('batch_loss', batch_loss.result())
      tf.summary.scalar('batch_accuracy', batch_accuracy.result())
    return self.compute_metrics(x, y, y_pred, None)

  def call(self, x):
    x = self.model(x)
    return x

# Adds custom batch-level metrics to our previous Functional API model
model = MyModel(create_model())
model.compile(optimizer='adam',
              loss='sparse_categorical_crossentropy',
              metrics=['accuracy'])

與之前一樣,定義我們的 TensorBoard 回呼,並使用我們選取的 batch_size 呼叫 model.fit()

tensorboard_callback = tf.keras.callbacks.TensorBoard(log_dir=log_dir)

model.fit(x=x_train, 
          y=y_train,
          epochs=5,
          batch_size=500, 
          validation_data=(x_test, y_test), 
          callbacks=[tensorboard_callback])
Epoch 1/5
120/120 [==============================] - 4s 27ms/step - loss: 0.4266 - accuracy: 0.8813 - val_loss: 0.2055 - val_accuracy: 0.9415
Epoch 2/5
120/120 [==============================] - 3s 26ms/step - loss: 0.1864 - accuracy: 0.9476 - val_loss: 0.1417 - val_accuracy: 0.9613
Epoch 3/5
120/120 [==============================] - 3s 27ms/step - loss: 0.1352 - accuracy: 0.9614 - val_loss: 0.1148 - val_accuracy: 0.9665
Epoch 4/5
120/120 [==============================] - 3s 26ms/step - loss: 0.1066 - accuracy: 0.9702 - val_loss: 0.0932 - val_accuracy: 0.9716
Epoch 5/5
120/120 [==============================] - 3s 27ms/step - loss: 0.0859 - accuracy: 0.9749 - val_loss: 0.0844 - val_accuracy: 0.9754
<keras.callbacks.History at 0x7fce15c39f50>

使用新的記錄目錄開啟 TensorBoard,並查看 epoch 層級和批次層級指標

%tensorboard --logdir logs/batch_avg

就這樣!您現在知道如何在 TensorBoard 中為各種使用案例建立自訂訓練指標。