TensorBoard 标量:在 Keras 中记录训练指标

在 TensorFlow.org 上查看 在 Google Colab 中运行 在 GitHub 上查看源代码

概述

机器学习必然涉及了解关键指标,例如损失,以及它们在训练过程中如何变化。这些指标可以帮助您了解是否过度拟合,例如,或者是否不必要地训练了太长时间。您可能希望比较不同训练运行中的这些指标,以帮助调试和改进您的模型。

TensorBoard 的**时间序列仪表板**允许您使用简单的 API 可视化这些指标,而无需付出太多努力。本教程提供了一些非常基本的示例,以帮助您学习如何在开发 Keras 模型时使用这些 API 与 TensorBoard 协同工作。您将学习如何使用 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 来观察训练和测试**损失**在各个时期如何变化。希望您会看到训练和测试损失随着时间的推移而减少,然后保持稳定。

首先,生成 1000 个大致沿直线 *y = 0.5x + 2* 的数据点。将这些数据点分成训练集和测试集。您希望神经网络学习这种关系。

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 个时期后停止训练,因为训练在那之后没有太大改善。

将鼠标悬停在图表上以查看特定数据点。您也可以尝试使用鼠标放大或选择部分数据点以查看更多详细信息。

请注意左侧的“运行”选择器。“运行”表示一轮训练的日志集,在本例中是 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 运行。选择此运行将显示一个“学习率”图表,允许您验证此运行期间学习率的进展情况。

您还可以将此运行的训练和验证损失曲线与您之前的运行进行比较。您可能还会注意到,学习率计划返回离散值,具体取决于时期,但学习率图可能看起来很平滑。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 数据集,规范化数据并编写一个函数,该函数创建用于将图像分类为 10 个类别的简单 Keras 模型。

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

即时批次级日志记录

在批次级别即时记录指标可以向我们展示每个时期训练期间批次之间波动的程度,这对于调试很有用。

将摘要写入器设置为不同的日志目录

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 指标应通过覆盖模型类定义中的 train_step() 方法来定义,并包含在摘要写入器上下文中。这可以简单地组合到子类化的模型定义中,也可以扩展到编辑我们之前的函数式 API 模型,如下所示

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 回调,将时期级和批次级指标都记录到我们的日志目录,并使用我们选择的 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,查看时期级和批次级指标

%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,查看时期级和批次级指标

%tensorboard --logdir logs/batch_avg

就是这样!您现在知道如何在 TensorBoard 中为各种用例创建自定义训练指标。