TensorFlow 实战(七)(1)

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: TensorFlow 实战(七)

第十四章:TensorBoard:TensorFlow 的大兄弟

本章内容包括

  • 在 TensorBoard 上运行和可视化图像数据
  • 实时监测模型性能和行为
  • 使用 TensorBoard 进行性能分析模型建模
  • 使用 tf.summary 在自定义模型训练过程中记录自定义指标
  • 在 TensorBoard 上可视化和分析词向量

到目前为止,我们已经重点关注了各种模型。我们讨论了全连接模型(例如自动编码器)、卷积神经网络和循环神经网络(例如 LSTM、GRU)。在第十三章,我们谈到了 Transformer,这是一类强大的深度学习模型,为语言理解领域的最新最优性能打下了基础。此外,受到自然语言处理领域的成就启发,Transformer 在计算机视觉领域也引起了轰动。我们已经完成了建模步骤,但还需要通过其他几个步骤来最终收获成果。其中一个步骤是确保向模型提供的数据/特征是正确的,并且模型按预期工作。

在本章中,我们将探索机器学习的一个新方面:利用可视化工具包来可视化高维数据(例如图像、词向量等),以及跟踪和监控模型性能。让我们了解为什么这是一个关键需求。由于机器学习在许多不同领域的成功示范,机器学习已经深深扎根于许多行业和研究领域。因此,这意味着我们需要更快速地训练新模型,并在数据科学工作流程的各个步骤中减少摩擦(例如了解数据、模型训练、模型评估等)。TensorBoard 是迈出这一步的方向。它可以轻松跟踪和可视化数据、模型性能,甚至对模型进行性能分析,了解数据在哪里花费了最多的时间。

通常,您会将数据和模型度量值以及其他要可视化的内容写入日志目录。写入日志目录的内容通常被组织成子文件夹,文件夹的命名包含了日期、时间以及对实验的简要描述。这将有助于在 TensorBoard 中快速识别一个实验。TensorBoard 会不断搜索指定的日志目录以查找更改,并在仪表板上进行可视化。您将在接下来的章节中了解到这些步骤的具体细节。

14.1 使用 TensorBoard 可视化数据

假设您是一家时尚公司的数据科学家。他们要求您评估构建一个可以在给定照片中识别服装的模型的可行性。为此,您选择了 Fashion-MNIST 数据集,该数据集包含黑白服装图像,属于 10 个类别之一。一些类别包括 T 恤、裤子和连衣裙。您将首先加载数据并通过 TensorBoard 进行分析,TensorBoard 是一种可视化工具,用于可视化数据和模型。在这里,您将可视化一些图像,并确保在加载到内存后正确分配了类标签。

首先,我们将下载 Fashion-MNIST 数据集。Fashion-MNIST 是一个带有各种服装图片和相应标签/类别的标记数据集。Fashion-MNIST 主要受到了 MNIST 数据集的启发。为了恢复我们的记忆,MNIST 数据集由 28×28 大小的 0-9 数字图像和相应的数字标签构成。由于任务的容易性,许多人建议不再将 MNIST 作为性能基准数据集,因此 Fashion-MNIST 应运而生。与 MNIST 相比,Fashion-MNIST 被认为是一项更具挑战性的任务。

下载 Fashion-MNIST 数据集非常容易,因为它可通过 tensorflow_datasets 库获取:

import tensorflow_datasets as tfds
# Construct a tf.data.Dataset
fashion_ds = tfds.load('fashion_mnist')

现在,让我们打印来查看数据的格式:

print(fashion_ds)

这将返回

{'test': <PrefetchDataset shapes: {image: (28, 28, 1), label: ()}, types: 
➥ {image: tf.uint8, label: tf.int64}>, 'train': <PrefetchDataset shapes: 
➥ {image: (28, 28, 1), label: ()}, types: {image: tf.uint8, label: 
➥ tf.int64}>}

数据集包含两个部分,一个训练数据集和一个测试数据集。训练集有两个项:图像(每个图像尺寸为 28×28×1)和一个整数标签。测试集上也有同样的项。接下来,我们将创建三个 tf.data 数据集:训练集、验证集和测试集。我们将原始训练数据集分为两部分,一个训练集和一个验证集,然后将测试集保持不变(参见下一个代码清单)。

代码清单 14.1 生成训练、验证和测试数据集

def get_train_valid_test_datasets(fashion_ds, batch_size, 
➥ flatten_images=False):
    train_ds = fashion_ds["train"].shuffle(batch_size*20).map(
        lambda xy: (xy["image"], tf.reshape(xy["label"], [-1]))         ❶
    )
    test_ds = fashion_ds["test"].map(
        lambda xy: (xy["image"], tf.reshape(xy["label"], [-1]))         ❷
    )
    if flatten_images:
        train_ds = train_ds.map(lambda x,y: (tf.reshape(x, [-1]), y))   ❸
        test_ds = test_ds.map(lambda x,y: (tf.reshape(x, [-1]), y))     ❸
    valid_ds = train_ds.take(10000).batch(batch_size)                   ❹
    train_ds = train_ds.skip(10000).batch(batch_size)                   ❺
    return train_ds, valid_ds, test_ds

❶ 获取训练数据集,对其进行洗牌,并输出一个(image, label)元组。

❷ 获取测试数据集,并输出一个(image, label)元组。

❸ 将图像扁平化为 1D 向量,用于全连接网络。

❹ 将验证数据集设置为前 10,000 个数据点。

❺ 将训练数据集设置为其余数据。

这是一个简单的数据流程。原始数据集中的每个记录都以字典形式提供,包含两个键:image 和 label。首先,我们使用 tf.data.Dataset.map()函数将基于字典的记录转换为(image, label)的元组。然后,如果数据集要用于全连接网络,则可选择性地将 2D 图像扁平化为 1D 向量。换句话说,28 × 28 的图像将变为 784 大小的向量。最后,我们将前 10,000 个数据点(经过洗牌)作为验证集,其余数据作为训练集。

在 TensorBoard 上可视化数据的方式是通过将信息记录到预定义的日志目录中,通过 tf.summary.SummaryWriter()。这个写入器将以 TensorBoard 理解的特殊格式写入我们感兴趣的数据。接下来,您启动一个 TensorBoard 的实例,将其指向日志目录。使用这种方法,让我们使用 TensorBoard 可视化一些训练数据。首先,我们定义一个从标签 ID 到字符串标签的映射:

id2label_map = {
    0: "T-shirt/top",
    1: "Trouser",
    2:"Pullover",
    3: "Dress",
    4: "Coat",
    5: "Sandal",
    6: "Shirt",
    7: "Sneaker",
    8: "Bag",
    9: "Ankle boot"
}

这些映射是从 mng.bz/DgdR 中获得的。然后我们将定义日志目录。我们将使用日期时间戳来生成不同运行的唯一标识符,如下所示:

log_datetimestamp_format = "%Y%m%d%H%M%S"
log_datetimestamp = datetime.strftime(datetime.now(), 
➥ log_datetimestamp_format)
image_logdir = "./logs/data_{}/train".format(log_datetimestamp)

如果你对 log_datetimestamp_format 变量中的奇怪格式感到困惑,那么它是 Python 的 datetime 库使用的标准格式,用于定义日期和时间的格式(mng.bz/lxM2),如果你在你的代码中使用它们。具体来说,我们将会得到运行时间(由 datetime.now() 给出)作为一个没有分隔符的数字字符串。我们将得到一天中给定时间的年份(%Y)、月份(%m)、日期(%d)、24 小时制的小时(%H)、分钟(%M)和秒(%S)。然后,我们将数字字符串附加到日志目录中,以根据运行时间创建一个唯一标识符。接下来,我们通过调用以下函数定义一个 tf.summary.SummaryWriter(),并将日志目录作为参数传递。有了这个,我们使用这个摘要写入器所做的任何写入都将被记录在定义的目录中:

image_writer = tf.summary.create_file_writer(image_logdir)

接下来,我们打开已定义的摘要写入器作为默认写入器,使用一个 with 子句。一旦摘要写入器打开,任何 tf.summary.<数据类型> 对象都将将该信息记录到日志目录中。在这里,我们使用了一个 tf.summary.image 对象。你可以使用几种不同的对象来记录(www.tensorflow.org/api_docs/python/tf/summary):

  • tf.summary.audio—用于记录音频文件并在 TensorBoard 上听取的对象类型
  • tf.summary.histogram—用于记录直方图并在 TensorBoard 上查看的对象类型
  • tf.summary.image—用于记录图像并在 TensorBoard 上查看的对象类型
  • tf.summary.scalar—用于记录标量值(例如,在几个周期内计算的模型损失)并在 TensorBoard 上显示的对象类型
  • tf.summary.text—用于记录原始文本数据并在 TensorBoard 上显示的对象类型

在这里,我们将使用 tf.summary.image() 来编写并在 TensorBoard 上显示图像。 tf.summary.image() 接受几个参数:

  • name—摘要的描述。这将在在 TensorBoard 上显示图像时用作标签。
  • data—大小为[b, h, w, c]的图像批次,其中 b 是批次大小,h 是图像高度,w 是图像宽度,c 是通道数(例如,RGB)。
  • step—一个整数,可用于显示属于不同批次/迭代的图像(默认为 None)。
  • max_outputs—在给定时间内显示的最大输出数量。如果数据中的图片数量超过 max_outputs,则将显示前 max_outputs 个图片,其余图片将被静默丢弃(默认为三)。
  • description—摘要的详细描述(默认为 None)

我们将以两种方式编写两组图像数据:

  • 首先,我们将从训练数据集中逐个取出 10 张图像,并带有其类标签标记地写入它们。然后,具有相同类标签(即,类别)的图像将被嵌套在 TensorBoard 上的同一部分中。
  • 最后,我们将一次写入一批 20 张图像。
with image_writer.as_default():
    for data in fashion_ds["train"].batch(1).take(10):
        tf.summary.image(
            id2label_map[int(data["label"].numpy())], 
            data["image"], 
            max_outputs=10, 
            step=0
        )
# Write a batch of 20 images at once
with image_writer.as_default():
    for data in fashion_ds["train"].batch(20).take(1):
        pass
    tf.summary.image("A training data batch", data["image"], max_outputs=20, step=0)

然后,我们就可以开始可视化 TensorBoard 了。在 Jupyter 笔记本代码单元格中只需运行以下命令即可简单地初始化和加载 TensorBoard。您现在已经知道,Jupyter 笔记本由单元格组成,您可以在其中输入文本/代码。单元格可以是代码单元格、Markdown 单元格等:

%load_ext tensorboard
%tensorboard --logdir ./logs --port 6006

图 14.1 显示了代码在笔记本单元格中的外观。


图 14.1 在笔记本单元格中的 Jupyter 魔术命令

您可能会注意到,这不是典型的 Python 语法。以 % 符号开头的命令被称为 Jupyter 魔术命令。请记住,Jupyter 是生成笔记本的 Python 库的名称。您可以在 mng.bz/BMd1 查看此类命令的列表。第一个命令加载 TensorBoard Jupyter 笔记本扩展程序。第二个命令使用提供的日志目录(–logdir)参数和端口(–port)参数实例化 TensorBoard。如果不指定端口,则 TensorBoard 默认在 6006(或大于 6006 的第一个未使用的端口)上运行。图 14.2 显示了可视化图像的 TensorBoard 的外观。


图 14.2 TensorBoard 可视化记录的图像,以内联方式在 Jupyter 笔记本中显示。您可以对图像执行各种操作,例如调整亮度或对比度。此外,数据被记录到的子目录显示在左侧面板上,让您可以轻松地显示/隐藏不同的子目录以便进行更容易的比较。

或者,您还可以将 TensorBoard 可视化为 Jupyter 笔记本之外的浏览器选项卡。在浏览器中运行这两个命令后,打开 http://localhost:6006,将显示 TensorBoard,如图 14.2 所示。在下一节中,我们将看到在模型训练过程中如何使用 TensorBoard 来跟踪和监视模型性能。

练习 1

您有一个名为 step_image_batches 的变量中包含五批图像的列表。列表中的每个项目对应于训练的前五个步骤。您希望在 TensorBoard 中显示这些批次,每个批次都具有正确的步骤值。您可以将每个批次命名为 batch_0、batch_1 等等。您该如何做?

14.2 使用 TensorBoard 跟踪和监视模型

通过对 Fashion-MNIST 数据集中的数据有很好的理解,您将使用神经网络在此数据上训练模型,以衡量您能够对不同类型的服装进行多么准确的分类。您计划使用密集网络和卷积神经网络。您将在相同的条件下训练这两个模型(例如,数据集),并在 TensorBoard 上可视化模型的准确性和损失。

TensorBoard 的主要作用是能够在模型训练时可视化模型的性能。深度神经网络以其长时间的训练时间而闻名。毫无疑问,尽早识别模型中的问题是非常值得的。TensorBoard 在其中发挥着至关重要的作用。您可以将模型性能(通过评估指标)导入到 TensorBoard 中实时显示。因此,您可以在花费过多时间之前快速发现模型中的任何异常行为并采取必要的行动。

在本节中,我们将比较全连接网络和卷积神经网络在 Fashion-MNIST 数据集上的性能。让我们将一个小型全连接模型定义为我们想要使用该数据集测试的第一个模型。它将有三层:

  • 一个具有 512 个神经元和 ReLU 激活的层,该层接收来自 Fashion-MNIST 数据集的平坦图像
  • 一个具有 256 个神经元和 ReLU 激活的层,该层接收前一层的输出
  • 一个具有 softmax 激活的具有 10 个输出的层(表示类别)

该模型使用稀疏分类交叉熵损失和 Adam 优化器进行编译。由于我们对模型准确性感兴趣,因此我们将其添加到要跟踪的指标列表中:

from tensorflow.keras import layers, models
dense_model = models.Sequential([
    layers.Dense(512, activation='relu', input_shape=(784,)),
    layers.Dense(256, activation='relu'),
    layers.Dense(10, activation='softmax')
])
dense_model.compile(loss="sparse_categorical_crossentropy", optimizer='adam', metrics=['accuracy'])

模型完全定义后,我们对训练数据进行训练,并在验证数据集上进行评估。首先,让我们定义全连接模型的日志目录:

log_datetimestamp_format = "%Y%m%d%H%M%S"
log_datetimestamp = datetime.strftime(
    datetime.now(), log_datetimestamp_format
)
dense_log_dir = os.path.join("logs","dense_{}".format(log_datetimestamp))

与以往一样,您可以看到我们不仅将写入子目录而不是普通的平面目录,而且还使用了基于运行时间的唯一标识符。这些子目录中的每一个代表了 TensorBoard 术语中所谓的一个 run

在 TensorBoard 中组织运行

通常,用户在通过 TensorBoard 可视化时利用某种运行组织。除了为多个算法创建多个运行之外,常见的做法是向运行添加日期时间戳,以区分同一算法在不同场合运行的不同运行。

例如,您可能会测试相同的算法与不同的超参数(例如,层数、学习率、优化器等),并且可能希望将它们都放在一个地方查看。假设您想测试具有不同学习率(0.01、0.001 和 0.0005)的全连接层。您将在主日志目录中具有以下目录结构:

./logs/dense/run_2021-05-27-03-14-21_lr=0.01
./logs/dense/run_2021-05-27-09-02-52_lr=0.001
./logs/dense/run_2021-05-27-10-12-09_lr=0.001
./logs/dense/run_2021-05-27-14-43-12_lr=0.0005

或者您甚至可以使用更嵌套的目录结构:

./logs/dense/lr=0.01/2021-05-27-03-14-21
./logs/dense/lr=0.001/2021-05-27-09-02-52
./logs/dense/lr=0.001/2021-05-27-10-12-09
./logs/dense/lr=0.0005/2021-05-27-14-43-12

我想强调时间戳您的运行很重要,如侧边栏中所述。这样,您将为每次运行都有一个唯一的文件夹,并且可以随时返回到以前的运行以进行比较。接下来,让我们使用 get_train_valid_test()函数生成训练/验证/测试数据集。请确保设置 flatten_images=True:

batch_size = 64
tr_ds, v_ds, ts_ds = get_train_valid_test_datasets(
    fashion_ds, batch_size=batch_size, flatten_images=True
)

将模型指标传递给 TensorBoard 非常简单。在模型训练/评估期间,可以传递一个特殊的 TensorBoard 回调函数:

tb_callback = tf.keras.callbacks.TensorBoard(
    log_dir=dense_log_dir, profile_batch=0
)

让我们讨论一些您可以传递给 TensorBoard 回调函数的关键参数。默认的 TensorBoard 回调函数如下所示:

tf.keras.callbacks.TensorBoard(
    log_dir='logs', histogram_freq=0, write_graph=True,
    write_images=False, write_steps_per_second=False, update_freq='epoch',
    profile_batch=2, embeddings_freq=0, embeddings_metadata=None, 
)

现在我们将查看所提供的参数:

  • log_dir—用于日志记录的目录。一旦使用该目录(或该目录的父目录)启动了 TensorBoard,可以在 TensorBoard 上可视化信息(默认为“logs”)。
  • histogram_freq—在各个层中创建激活分布的直方图(稍后详细讨论)。此选项指定要多频繁(以 epochs 为单位)记录这些直方图(默认值为 0,即禁用)。
  • write_graph—确定是否将模型以图形的形式写入 TensorBoard 以进行可视化(默认为 True)。
  • write_image—确定是否将模型权重写为图像(即热图)以在 TensorBoard 上可视化权重(默认为 False)。
  • write_steps_per_second—确定是否将每秒执行的步骤数写入 TensorBoard(默认为 False)。
  • update_freq(‘batch’、'epoch’或整数)—确定是否每个批次(如果值设置为 batch)或每个 epoch(如果值设置为 epoch)向 TensorBoard 写入更新。传递一个整数,TensorFlow 将解释为“每 x 个批次写入 TensorBoard”。默认情况下,将每个 epoch 写入更新。写入磁盘很昂贵,因此过于频繁地写入将会降低训练速度。
  • profile_batch(整数或两个数字的列表)—确定要用于对模型进行分析的批次。分析计算模型的计算和内存使用情况(稍后详细讨论)。如果传递一个整数,它将分析一个批次。如果传递一个范围(即一个包含两个数字的列表),它将分析该范围内的批次。如果设置为零,则不进行分析(默认为 2)。
  • embedding_freq—如果模型具有嵌入层,则此参数指定可视化嵌入层的间隔(以 epochs 为单位)。如果设置为零,则禁用此功能(默认为 0)。
  • embedding_metadata—一个将嵌入层名称映射到文件名的字典。该文件应包含与嵌入矩阵中每行对应的标记(按顺序排列;默认为 None)。

最后,我们将像以前一样训练模型。唯一的区别是将 tb_callback 作为回调参数传递给模型:

dense_model.fit(tr_ds, validation_data=v_ds, epochs=10, callbacks=[tb_callback])

模型应该达到约 85%的验证准确率。现在打开 TensorBoard,访问 http:/ /localhost:6006。它将显示类似于图 14.3 的仪表板。随着日志目录中出现新数据,仪表板将自动刷新。


图 14.3 显示了 TensorBoard 上如何显示跟踪的指标。您可以看到训练和验证准确率以及损失值被绘制为折线图。此外,还有各种控件,例如最大化图形,切换到对数刻度 y 轴等等。

TensorBoard 仪表板具有许多控件,可以帮助用户通过记录的指标深入了解他们的模型。您可以打开或关闭不同的运行,具体取决于您要分析的内容。例如,如果您只想查看验证指标,则可以关闭 dense/train 运行,并反之亦然。Data/train 运行不会影响此面板,因为它包含我们从训练数据中记录的图像。要查看它们,可以单击 IMAGES 面板。

接下来,您可以更改平滑参数以控制曲线的平滑程度。通过使用曲线的平滑版本,有助于消除指标中的局部小变化,聚焦于整体趋势。图 14.4 描述了平滑参数对折线图的影响。


图 14.4 展示了平滑参数如何改变折线图。在这里,我们显示了使用不同平滑参数的相同折线图。您可以看到,随着平滑参数的增加,线条变得更平滑。原始线条以淡色显示。

此外,您还可以进行其他控制,例如切换到对数刻度 y 轴而不是线性。如果指标随时间观察到大幅变化,则这非常有用。在对数刻度下,这些大变化将变得更小。如果您需要更详细地检查图表,还可以在标准大小和全尺寸图之间切换。图 14.3 突出显示了这些控件。

之后,我们将定义一个简单的卷积神经网络,并执行相同的操作。也就是说,我们将首先定义网络,然后在使用回调函数到 TensorBoard 的同时训练模型。

让我们定义下一个我们将与全连接网络进行比较的模型:卷积神经网络(CNN)。同样,我们正在定义一个非常简单的 CNN,它包括

  • 一个 2D 卷积层,具有 32 个过滤器,5×5 内核,2×2 步幅和 ReLU 激活,该层接受来自 Fashion-MNIST 数据集的 2D 28×28 大小的图像
  • 一个具有 16 个过滤器的 2D 卷积层,具有 3×3 内核,1×1 步幅和 ReLU 激活,该层接受先前层的输出
  • 一个扁平化层,将卷积输出压成适用于密集层的 1D 向量
  • 具有 10 个输出(表示类别)并具有 softmax 激活的层
conv_model = models.Sequential([
    layers.Conv2D(
       filters=32, 
       kernel_size=(5,5), 
       strides=(2,2), 
       padding='same', 
       activation='relu', 
       input_shape=(28,28,1)
    ),
    layers.Conv2D(
        filters=16, 
        kernel_size=(3,3), 
        strides=(1,1), 
        padding='same', 
        activation='relu'
    ),
    layers.Flatten(),
    layers.Dense(10, activation='softmax')
])
conv_model.compile(
    loss="sparse_categorical_crossentropy", optimizer='adam', 
➥ metrics=['accuracy']
)
conv_model.summary()

接下来,我们将 CNN 相关的指标记录到一个名为./logs/conv_{datetimestamp}的单独目录中。这样,我们可以分别绘制完全连接的网络和 CNN 的评估指标。我们将生成训练和验证数据集以及一个 TensorBoard 回调,就像之前做的那样。然后,在调用 fit()方法训练模型时将它们传递给模型:

log_datetimestamp_format = "%Y%m%d%H%M%S"
log_datetimestamp = datetime.strftime(
    datetime.now(), log_datetimestamp_format
)
conv_log_dir = os.path.join("logs","conv_{}".format(log_datetimestamp))
batch_size = 64
tr_ds, v_ds, ts_ds = get_train_valid_test_datasets(
    fashion_ds, batch_size=batch_size, flatten_images=False
)
tb_callback = tf.keras.callbacks.TensorBoard(
    log_dir=conv_log_dir, histogram_freq=2, profile_batch=0
)
conv_model.fit(
    tr_ds, validation_data=v_ds, epochs=10, callbacks=[tb_callback]
)

注意我们在训练 CNN 时所做的更改。首先,我们不像训练完全连接的网络时那样将图像展平(即,在 get_train_valid_test_datasets()函数中设置 flatten_ images=False)。接下来,我们向 TensorBoard 回调引入了一个新参数。我们将使用 histogram_freq 参数来记录模型在训练过程中的层激活直方图。我们将很快更深入地讨论层激活直方图。这将在同一张图中显示两个模型(即密集模型和卷积模型)的准确度和损失指标,以便它们可以轻松比较(图 14.5)。


图 14.5 查看密集模型和卷积模型的指标。你可以根据需要比较不同的运行状态。

让我们再次回到激活直方图。激活直方图让我们可以可视化不同层的神经元激活分布随着训练的进行而变化的情况。这是一个重要的检查,它可以让你看到模型在优化过程中是否正在收敛,从而提供关于模型训练或数据质量的问题的见解。

让我们更深入地看一下这些直方图显示了什么。图 14.6 说明了我们训练的 CNN 生成的直方图。我们每两个时代绘制一次直方图。以直方图表示的权重堆叠在一起,这样我们就可以很容易地了解它们随时间的变化情况。直方图中的每个切片显示了给定层和给定时代中的权重分布。换句话说,它将提供诸如“有 x 个输出,其值为 y,大约为 z”的信息。


图 14.6 由 TensorBoard 显示的激活直方图。这些图表显示了给定层的激活分布随时间的变化情况(较浅的图表表示更近期的时代)。

通常,如果你有一个值的向量,创建直方图就相当简单。例如,假设值为[0.1, 0.3, 0.35, 0.5, 0.6, 0.61, 0.63],并且假设你有四个箱子:0.0, 0.2),[0.2, 0.4),[0.4, 0.6),和[0.6, 0.8)。你将得到图 14.7 所示的直方图。如果你看一下连接各条的中点的线,它类似于你在仪表板中看到的内容。

![14-07

图 14.7 生成的序列[0.1, 0.3, 0.35, 0.5, 0.6, 0.61, 0.63]的直方图

然而,当数据很大且稀疏(例如在权重矩阵中)时,计算直方图涉及更复杂的数据操作。例如,在 TensorBoard 中计算直方图涉及使用指数 bin 大小(与示例中的均匀 bin 大小相反),这在接近零时提供了更细粒度的 bin 和远离零时提供了更宽的 bin。然后,它将这些不均匀大小的 bin 重新采样为统一大小的 bin,以便更容易、更有意义地进行可视化。这些计算的具体细节超出了本书的范围。如果您想了解更多细节,请参考mng.bz/d26o

我们可以看到,在训练过程中,权重正在收敛于一个近似的正态分布。但是偏差收敛于一个多峰分布,并在许多不同的地方出现峰值。

本节阐述了如何使用 TensorBoard 进行一些主要数据可视化和模型性能跟踪。这些是您在数据科学项目中必须设置的核心检查点的重要组成部分。数据可视化需要在项目早期完成,以帮助您理解数据及其结构。模型性能跟踪非常重要,因为深度学习模型需要更长的训练时间,而您需要在有限的预算(时间和成本)内完成培训。在下一节中,我们将讨论如何记录自定义指标到 TensorBoard 并可视化它们。

练习 2

您有一个由 classif_model 表示的二元分类模型。您想在 TensorBoard 中跟踪此模型的精度和召回率。此外,您想在每个时期可视化激活直方图。您将如何使用 TensorBoard 回调编译模型,并使用 TensorBoard 回调拟合数据以实现此目的?TensorFlow 提供了 tf.keras.metrics.Precision()和 tf.keras.metrics.Recall()来分别计算精度和召回率。您可以假设您直接记录到./logs 目录。假设您已经提供了 tf.data.Dataset 对象的训练数据(tr_ds)和验证数据(v_ds)。

14.3 使用 tf.summary 在模型训练期间编写自定义度量

想象一下,您是一名博士研究批量归一化的影响。特别是,您需要分析给定层中的权重均值和标准偏差如何随时间变化,以及有无批量归一化。为此,您将使用一个全连接网络,并在 TensorBoard 上记录每个步骤的权重均值和标准偏差。由于这不是您可以使用 Keras 模型生成的典型指标,因此您将在自定义培训循环中记录每个步骤的模型训练期间的指标。

为了比较批量归一化的效果,我们需要定义两个不同的模型:一个没有批量归一化,一个有批量归一化。这两个模型将具有相同的规格,除了使用批量归一化。首先,让我们定义一个没有批量归一化的模型:

from tensorflow.keras import layers, models
import tensorflow.keras.backend as K
K.clear_session()
dense_model = models.Sequential([
    layers.Dense(512, activation='relu', input_shape=(784,)),    
    layers.Dense(256, activation='relu', name='log_layer'),    
    layers.Dense(10, activation='softmax')
])
dense_model.compile(loss="sparse_categorical_crossentropy", optimizer='adam', metrics=['accuracy'])

该模型非常简单,与我们之前定义的完全连接模型相同。它有三个层,分别有 512、256 和 10 个节点。前两层使用 ReLU 激活函数,而最后一层使用 softmax 激活函数。请注意,我们将第二个 Dense 层命名为 log_layer。我们将使用该层来计算我们感兴趣的指标。最后,该模型使用稀疏分类交叉熵损失、Adam 优化器和准确度作为指标进行编译。接下来,我们使用批量归一化定义相同的模型:

dense_model_bn = models.Sequential([
    layers.Dense(512, activation='relu', input_shape=(784,)),
    layers.BatchNormalization(),
    layers.Dense(256, activation='relu', name='log_layer_bn'),
    layers.BatchNormalization(),
    layers.Dense(10, activation='softmax')
])
dense_model_bn.compile(
    loss="sparse_categorical_crossentropy", optimizer='adam', 
➥ metrics=['accuracy']
)

引入批量归一化意味着在 Dense 层之间添加 tf.keras.layers.BatchNormalization()层。我们将第二个模型中感兴趣的层命名为 log_layer_bn,因为我们不能同时使用相同名称的两个层。

有了定义好的模型,我们的任务是在每一步计算权重的均值和标准差。为此,我们将观察两个网络的第二层的权重的均值和标准差(log_layer 和 log_layer_bn)。正如我们已经讨论过的,我们不能简单地传递一个 TensorBoard 回调并期望这些指标可用。由于我们感兴趣的指标不常用,我们必须费力确保这些指标在每一步都被记录。

我们将定义一个 train_model()函数,可以将定义的模型传递给它,并在数据上进行训练。在训练过程中,我们将计算每一步权重的均值和标准差,并将其记录到 TensorBoard 中(见下一个清单)。

清单 14.2 在自定义循环中训练模型时编写 tf.summary 对象

def train_model(model, dataset, log_dir, log_layer_name, epochs):    
    writer = tf.summary.create_file_writer(log_dir)                        ❶
    step = 0
    with writer.as_default():                                              ❷
        for e in range(epochs):
            print("Training epoch {}".format(e+1))
            for batch in tr_ds:
                model.train_on_batch(*batch)                               ❸
                weights = model.get_layer(log_layer_name).get_weights()[0] ❹
                tf.summary.scalar("mean_weights",np.mean(np.abs(weights)), ❺
➥ step=step)                                                              ❺
                tf.summary.scalar("std_weights", np.std(np.abs(weights)),  ❺
➥ step=step)                                                              ❺
                writer.flush()                                             ❻
                step += 1
            print('\tDone')
    print("Training completed\n")

❶ 定义写入器。

❷ 打开写入器。

❸ 用一个批次进行训练。

❹ 获取层的权重。它是一个数组列表[权重,偏差],顺序是这样的。因此,我们只取权重(索引 0)。

❺ 记录权重的均值和标准差(对于给定 epoch 是两个标量)。

❻ 从缓冲区刷新到磁盘。

注意我们如何打开一个 tf.summary.writer(),然后使用 tf.summary.scalar()调用在每一步记录指标。我们给指标起了有意义的名称,以确保在 TensorBoard 上可视化时知道哪个是哪个。有了函数定义,我们为我们编译的两个不同模型调用它:

batch_size = 64
tr_ds, _, _ = get_train_valid_test_datasets(
    fashion_ds, batch_size=batch_size, flatten_images=True
)
train_model(dense_model, tr_ds, exp_log_dir + '/standard', "log_layer", 5)
tr_ds, _, _ = get_train_valid_test_datasets(
    fashion_ds, batch_size=batch_size, flatten_images=True
)
train_model(dense_model_bn, tr_ds, exp_log_dir + '/bn', "log_layer_bn", 5)

请注意,我们指定不同的日志子目录,以确保出现的两个模型是不同的运行。运行后,您将看到两个新的附加部分,名为 mean_weights 和 std_weights(图 14.8)。似乎当使用批量归一化时,权重的均值和方差更加剧烈地变化。这可能是因为批量归一化在层之间引入了显式归一化,使得层的权重更自由地移动。


图 14.8 权重的均值和标准差在 TensorBoard 中绘制

接下来的部分详细介绍了如何使用 TensorBoard 来分析模型并深入分析模型执行时时间和内存消耗情况。

练习 3

你计划计算斐波那契数列(即 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55 等),其中第 n 个数字 x_n 由 x_n = x_{n - 1} + x_{n - 2} 给出。编写一个代码,计算 100 步的斐波那契数列并在 TensorBoard 中将其绘制为折线图。你可以将名字“fibonacci”作为指标名称。

14.4 对模型进行性能瓶颈检测

你现在作为一位数据科学家加入了一家正在识别濒临灭绝的花卉物种的生物技术公司。之前的一位数据科学家开发了一个模型,而你将继续这项工作。首先,你想确定是否存在任何性能瓶颈。为了分析这些问题,你计划使用 TensorBoard 分析器。你将使用一个较小的花卉数据集来训练模型,以便分析器可以捕获各种计算配置文件。

我们从列表 14.3 中的模型开始。这是一个具有四个卷积层的 CNN 模型,中间有池化层,包括三个完全连接的层,最后一个是具有 17 个输出类别的 softmax 层。

列表 14.3 你可用的 CNN 模型

def get_cnn_model():
    conv_model = models.Sequential([                                ❶
        layers.Conv2D(                                              ❷
            filters=64, 
            kernel_size=(5,5), 
            strides=(1,1), 
            padding='same', 
            activation='relu', 
            input_shape=(64,64,3)
        ),
        layers.BatchNormalization(),                                ❸
        layers.MaxPooling2D(pool_size=(3,3), strides=(2,2)),        ❹
        layers.Conv2D(                                              ❺
            filters=128, 
            kernel_size=(3,3), 
            strides=(1,1), 
            padding='same', 
            activation='relu'
        ),
        layers.BatchNormalization(),                                ❺
        layers.Conv2D(                                              ❺
            filters=256, 
            kernel_size=(3,3), 
            strides=(1,1), 
            padding='same', 
            activation='relu'
        ),
        layers.BatchNormalization(),                                ❺
        layers.Conv2D(                                              ❺
            filters=512, 
            kernel_size=(3,3), 
            strides=(1,1), 
            padding='same', 
            activation='relu'
        ),
        layers.BatchNormalization(),                                ❺
        layers.AveragePooling2D(pool_size=(2,2), strides=(2,2)),    ❻
        layers.Flatten(),                                           ❼
        layers.Dense(512),                                          ❽
        layers.LeakyReLU(),                                         ❽
        layers.LayerNormalization(),                                ❽
        layers.Dense(256),                                          ❽
        layers.LeakyReLU(),                                         ❽
        layers.LayerNormalization(),                                ❽
        layers.Dense(17),                                           ❽
        layers.Activation('softmax', dtype='float32')               ❽
    ])
    return conv_model

❶ 使用顺序 API 定义一个 Keras 模型。

❷ 定义一个接受大小为 64 × 64 × 3 的输入的第一个卷积层。

❸ 一个批量归一化层

❹ 一个最大池化层

❺ 一系列交替的卷积和批量归一化层

❻ 一个平均池化层,标志着卷积/池化层的结束

❼ 将最后一个池化层的输出展平。

❽ 一组稠密层(带有渗漏线性整流激活),接着是一个具有 softmax 激活的层

我们将使用的数据集是在www.robots.ox.ac.uk/~vgg/data/flowers找到的花卉数据集,具体来说,是 17 类别数据集。它有一个包含花朵图像的单独文件夹,每个图像文件名上都有一个数字。这些图像按照文件名排序时,前 80 个图像属于类别 0,接下来的 80 个图像属于类别 1,依此类推。你已经提供了下载数据集的代码,位于笔记本 Ch14/14.1_Tensorboard.ipynb 中,我们这里不会讨论。接下来,我们将编写一个简单的 tf.data 流水线,通过读取这些图像来创建数据批次:

def get_flower_datasets(image_dir, batch_size, flatten_images=False):
    # Get the training dataset, shuffle it, and output a tuple of (image, 
➥ label)
    dataset = tf.data.Dataset.list_files(
        os.path.join(image_dir,'*.jpg'), shuffle=False
    )
    def get_image_and_label(file_path):
        tokens = tf.strings.split(file_path, os.path.sep)
        label = (
            tf.strings.to_number(
                tf.strings.split(
                    tf.strings.split(tokens[-1],'.')[0], '_'
                )[-1]
            ) -1
        )//80
        # load the raw data from the file as a string
        img = tf.io.read_file(file_path)
        img = tf.image.decode_jpeg(img, channels=3)
        return tf.image.resize(img, [64, 64]), label
    dataset = dataset.map(get_image_and_label).shuffle(400)
    # Make the validation dataset the first 10000 data
    valid_ds = dataset.take(250).batch(batch_size)
    # Make training dataset the rest
    train_ds = dataset.skip(250).batch(batch_size)
    )
    return train_ds, valid_ds

让我们分析一下我们在这里所做的事情。首先,我们从给定文件夹中读取具有.jpg 扩展名的文件。然后我们有一个名为 get_image_and_label()的嵌套函数,它接受一个图像的文件路径,并通过从磁盘中读取该图像产生图像和标签。标签可以通过计算得到

  • 提取图像 ID
  • 减去 1(即将 ID 减 1,以使其成为从零开始的索引)并除以 80

之后,我们对数据进行洗牌,并将前 250 个数据作为验证数据,其余的作为训练数据。接下来,我们使用定义的这些函数并训练 CNN 模型,同时创建模型的各种计算性能分析。为了使性能分析工作,你需要两个主要的先决条件:

  • 安装 Python 包tensorboard_plugin_profile
  • 安装 libcupti,CUDA 性能分析工具包接口。

安装 CUDA 性能分析工具包接口(libcupti)

TensorBoard 需要 libcupti CUDA 库才能进行模型性能分析。安装此库需要根据所使用的操作系统的不同步骤。这假设您的计算机配备了 NVIDIA GPU。不幸的是,你将无法在 Mac 上执行此操作,因为 Mac 上没有可用于数据收集的性能分析 API。(查看developer.nvidia.com/cupti-ctk10_1u1中的需求部分。)

TensorFlow 实战(七)(2)https://developer.aliyun.com/article/1522941

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
2月前
|
机器学习/深度学习 自然语言处理 TensorFlow
TensorFlow 实战(六)(2)
TensorFlow 实战(六)
24 0
|
18天前
|
机器学习/深度学习 TensorFlow API
TensorFlow与Keras实战:构建深度学习模型
本文探讨了TensorFlow和其高级API Keras在深度学习中的应用。TensorFlow是Google开发的高性能开源框架,支持分布式计算,而Keras以其用户友好和模块化设计简化了神经网络构建。通过一个手写数字识别的实战案例,展示了如何使用Keras加载MNIST数据集、构建CNN模型、训练及评估模型,并进行预测。案例详述了数据预处理、模型构建、训练过程和预测新图像的步骤,为读者提供TensorFlow和Keras的基础实践指导。
153 59
|
2月前
|
机器学习/深度学习 自然语言处理 TensorFlow
TensorFlow 实战(五)(5)
TensorFlow 实战(五)
20 1
|
2月前
|
自然语言处理 算法 TensorFlow
TensorFlow 实战(六)(3)
TensorFlow 实战(六)
22 0
|
2月前
|
机器学习/深度学习 数据可视化 TensorFlow
TensorFlow 实战(六)(1)
TensorFlow 实战(六)
25 0
|
2月前
|
存储 自然语言处理 TensorFlow
TensorFlow 实战(五)(4)
TensorFlow 实战(五)
22 0
|
2月前
|
数据可视化 TensorFlow 算法框架/工具
TensorFlow 实战(八)(4)
TensorFlow 实战(八)
25 1
|
2月前
|
TensorFlow API 算法框架/工具
TensorFlow 实战(八)(3)
TensorFlow 实战(八)
26 1
|
2月前
|
机器学习/深度学习 自然语言处理 TensorFlow
TensorFlow 实战(八)(5)
TensorFlow 实战(八)
25 0
|
2月前
|
并行计算 TensorFlow 算法框架/工具
TensorFlow 实战(八)(2)
TensorFlow 实战(八)
22 0