TensorBoard - 在同一张图上绘制训练和验证损失?

Posted

技术标签:

【中文标题】TensorBoard - 在同一张图上绘制训练和验证损失?【英文标题】:TensorBoard - Plot training and validation losses on the same graph? 【发布时间】:2016-09-05 21:48:59 【问题描述】:

有没有办法在相同图上同时绘制训练损失和验证损失?

很容易为它们中的每一个单独创建两个单独的标量摘要,但这会将它们放在单独的图表上。如果两者都显示在同一个图表中,则更容易看出它们之间的差距以及它们是否由于过度拟合而开始发散。

有没有内置的方法来做到这一点?如果没有,解决方法?非常感谢!

【问题讨论】:

目前 (5/24) 没有官方支持的方式来执行此操作。但我们正在考虑添加一个更通用的系统,用于将不同的数据源绑定在一起进行可视化,该系统将支持这一点。 @dandelion 这个目前还不受支持吗? here is a solution using keras 这是我们构建的一个工具,用于记录和比较许多实验的大量指标(包括同一图表和单独图表上的训练、验证和测试损失)。 Github:github.com/aimhubio/aim 【参考方案1】:

我一直在做的解决方法是使用两个具有不同日志目录的SummaryWriter 分别用于训练集和交叉验证集。你会看到这样的东西:

【讨论】:

谢谢!我曾认为这种方法可能有效,但还没有尝试过。当然,这使得比较运行和验证更加乏味/混乱,但至少它是一种选择。不过,我暂时将这个问题悬而未决,希望我们能找到一个解决方案,在不放弃其他东西的情况下解决它。 当然,等待您的好消息 :) 顺便说一句,至于运行之间的比较以及验证,我认为这不是问题,因为您可以将它们保存到“run1/ train”、“run2/train”、“run1/validation”等。比较时检查你想要的曲线。 如何在同一张图上运行 2 次。当我尝试创建 2 个不同作者但名称相同的摘要时,我得到了 LossLoss_1 那我应该运行哪个目录tensorboard,即--logdir参数是什么? 黄玉恒,放在同一个父目录下。然后在 --logdir 中指定父目录。我通常会创建一个目录 ./summaries/ 并将每个子目录放在那里。【参考方案2】:

您可以将验证损失和训练损失之间的差异绘制为自己的标量摘要来跟踪差异,而不是单独显示两条线。

这并没有提供关于单个图的那么多信息(与添加两个摘要相比),但它有助于能够比较多个运行(而不是每次运行添加多个摘要)。

【讨论】:

【参考方案3】:

非常感谢 niko 提供有关自定义标量的提示。

我被官方custom_scalar_demo.py弄糊涂了,因为事情太多了,我研究了很长时间才弄明白它是如何工作的。

为了准确显示为现有模型创建自定义标量图需要做什么,我整理了以下完整示例:

# + <
# We need these to make a custom protocol buffer to display custom scalars.
# See https://developers.google.com/protocol-buffers/
from tensorboard.plugins.custom_scalar import layout_pb2
from tensorboard.summary.v1 import custom_scalar_pb
#   > 
import tensorflow as tf
from time import time
import re

# Initial values
(x0, y0) = (-1, 1)

# This is useful only when re-running code (e.g. Jupyter).
tf.reset_default_graph()  

# Set up variables.
x = tf.Variable(x0, name="X", dtype=tf.float64)
y = tf.Variable(y0, name="Y", dtype=tf.float64)

# Define loss function and give it a name.
loss = tf.square(x - 3*y) + tf.square(x+y)
loss = tf.identity(loss, name='my_loss')

# Define the op for performing gradient descent.
minimize_step_op = tf.train.GradientDescentOptimizer(0.092).minimize(loss)

# List quantities to summarize in a dictionary 
# with (key, value) = (name, Tensor).
to_summarize = dict(
    X = x,
    Y_plus_2 = y + 2,
)

# Build scalar summaries corresponding to to_summarize.
# This should be done in a separate name scope to avoid name collisions
# between summaries and their respective tensors. The name scope also
# gives a title to a group of scalars in TensorBoard.
with tf.name_scope('scalar_summaries'):
    my_var_summary_op = tf.summary.merge(
        [tf.summary.scalar(name, var) 
            for name, var in to_summarize.items()
        ]
    )

# + <
# This constructs the layout for the custom scalar, and specifies
# which scalars to plot.
layout_summary = custom_scalar_pb(
    layout_pb2.Layout(category=[
        layout_pb2.Category(
            title='Custom scalar summary group',
            chart=[
                layout_pb2.Chart(
                    title='Custom scalar summary chart',
                    multiline=layout_pb2.MultilineChartContent(
                        # regex to select only summaries which 
                        # are in "scalar_summaries" name scope:
                        tag=[r'^scalar_summaries\/']
                    )
                )
            ])
    ])
)
#   >

# Create session.
with tf.Session() as sess:

    # Initialize session.
    sess.run(tf.global_variables_initializer())

    # Create writer.
    with tf.summary.FileWriter(f'./logs/session_int(time())') as writer:

        # Write the session graph.
        writer.add_graph(sess.graph) # (not necessary for scalars)

# + <
        # Define the layout for creating custom scalars in terms
        # of the scalars.
        writer.add_summary(layout_summary)
#   >

        # Main iteration loop.
        for i in range(50):
            current_summary = sess.run(my_var_summary_op)
            writer.add_summary(current_summary, global_step=i)
            writer.flush()
            sess.run(minimize_step_op)   

上面包含一个“原始模型”,由三个代码块增强

# + <
        [code to add custom scalars goes here]
#   >

我的“原始模型”有这些标量:

还有这张图:

我修改后的模型具有相同的标量和图形,以及以下自定义标量:

这个自定义的标量图只是一个简单的布局,结合了原始的两个标量图。

不幸的是,结果图很难阅读,因为两个值具有相同的颜色。 (它们仅通过标记来区分。)然而,这与 TensorBoard 的每个日志具有一种颜色的约定一致。

说明

思路如下。您有一组变量要绘制在单个图表中。作为先决条件,TensorBoard 应该在“SCALARS”标题下单独绘制每个变量。 (这是通过为每个变量创建一个标量摘要,然后将这些摘要写入日志来完成的。这里没有什么新内容。)

为了在同一张图表中绘制多个变量,我们告诉 TensorBoard 将这些摘要中的哪一个组合在一起。然后将指定的摘要组合到“CUSTOM SCALARS”标题下的单个图表中。我们通过在日志开头编写一次“布局”来实现这一点。一旦 TensorBoard 收到布局,它会在更新普通的“SCALARS”时自动在“CUSTOM SCALARS”下生成一个组合图表。

假设您的“原始模型”已经将变量(作为标量摘要)发送到 TensorBoard,唯一需要的修改是在主迭代循环开始之前注入布局。每个自定义标量图表通过正则表达式选择要绘制的摘要。因此,对于要一起绘制的每组变量,将变量各自的摘要放在单独的名称范围中会很有用。 (这样您的正则表达式可以简单地选择该名称范围内的所有摘要。)

重要提示:生成变量摘要的操作与变量本身不同。例如,如果我有一个变量ns1/my_var,我可以创建一个摘要ns2/summary_op_for_myvar。自定义标量图表布局只关心摘要操作,而不是原始变量的名称或范围。

【讨论】:

【参考方案4】:

这里是一个例子,创建两个共享同一个根目录的tf.summary.FileWriters。创建由两个tf.summary.FileWriters 共享的tf.summary.scalar。在每个时间步,获取summary 并更新每个tf.summary.FileWriter

import os

import tqdm
import tensorflow as tf


def tb_test():
    sess = tf.Session()

    x = tf.placeholder(dtype=tf.float32)
    summary = tf.summary.scalar('Values', x)
    merged = tf.summary.merge_all()

    sess.run(tf.global_variables_initializer())

    writer_1 = tf.summary.FileWriter(os.path.join('tb_summary', 'train'))
    writer_2 = tf.summary.FileWriter(os.path.join('tb_summary', 'eval'))

    for i in tqdm.tqdm(range(200)):
        # train
        summary_1 = sess.run(merged, feed_dict=x: i-10)
        writer_1.add_summary(summary_1, i)
        # eval
        summary_2 = sess.run(merged, feed_dict=x: i+10)            
        writer_2.add_summary(summary_2, i)

    writer_1.close()
    writer_2.close()


if __name__ == '__main__':
    tb_test()

结果如下:

橙色线表示评估阶段的结果,蓝色线表示训练阶段的数据。

另外,TF 团队有一个非常有用的post,你可以参考一下。

【讨论】:

【参考方案5】:

仅适用于通过搜索遇到此问题的任何人:当前实现此目标的最佳做法是仅使用 torch.utils.tensorboard 中的 SummaryWriter.add_scalars 方法。来自docs:

from torch.utils.tensorboard import SummaryWriter
writer = SummaryWriter()
r = 5
for i in range(100):
  writer.add_scalars('run_14h', 'xsinx':i*np.sin(i/r),
                                'xcosx':i*np.cos(i/r),
                                'tanx': np.tan(i/r), i)
writer.close()
# This call adds three values to the same scalar plot with the tag
# 'run_14h' in TensorBoard's scalar section.

预期结果:

expected result image

【讨论】:

最佳简洁答案!谢谢【参考方案6】:

为了完整起见,从 tensorboard 1.5.0 开始,这现在是可能的。

您可以使用自定义标量插件。为此,您需要首先进行 tensorboard 布局配置并将其写入事件文件。来自张量板示例:

import tensorflow as tf
from tensorboard import summary
from tensorboard.plugins.custom_scalar import layout_pb2

# The layout has to be specified and written only once, not at every step

layout_summary = summary.custom_scalar_pb(layout_pb2.Layout(
  category=[
    layout_pb2.Category(
      title='losses',
      chart=[
          layout_pb2.Chart(
              title='losses',
              multiline=layout_pb2.MultilineChartContent(
                tag=[r'loss.*'],
              )),
          layout_pb2.Chart(
              title='baz',
              margin=layout_pb2.MarginChartContent(
                series=[
                  layout_pb2.MarginChartContent.Series(
                    value='loss/baz/scalar_summary',
                    lower='baz_lower/baz/scalar_summary',
                    upper='baz_upper/baz/scalar_summary'),
                ],
              )), 
      ]),
    layout_pb2.Category(
      title='trig functions',
      chart=[
          layout_pb2.Chart(
              title='wave trig functions',
              multiline=layout_pb2.MultilineChartContent(
                tag=[r'trigFunctions/cosine', r'trigFunctions/sine'],
              )),
          # The range of tangent is different. Let's give it its own chart.
          layout_pb2.Chart(
              title='tan',
              multiline=layout_pb2.MultilineChartContent(
                tag=[r'trigFunctions/tangent'],
              )),
      ],
      # This category we care less about. Let's make it initially closed.
      closed=True),
  ]))

writer = tf.summary.FileWriter(".")
writer.add_summary(layout_summary)
# ...
# Add any summary data you want to the file
# ...
writer.close()

Category 是一组 Charts。每个Chart 对应一个单独的图,该图同时显示多个标量。 Chart 可以绘制简单的标量(MultilineChartContent)或填充区域(MarginChartContent,例如,当您想要绘制某个值的偏差时)。 MultilineChartContenttag 成员必须是与要在图表中分组的标量的 tags 匹配的正则表达式列表。有关更多详细信息,请查看 https://github.com/tensorflow/tensorboard/blob/master/tensorboard/plugins/custom_scalar/layout.proto 中对象的原型定义。请注意,如果您有多个FileWriters 写入同一目录,则只需在其中一个文件中写入布局。将其写入单独的文件也可以。

要查看 TensorBoard 中的数据,您需要打开“自定义标量”选项卡。这是一个示例图片,说明https://user-images.githubusercontent.com/4221553/32865784-840edf52-ca19-11e7-88bc-1806b1243e0d.png

【讨论】:

【参考方案7】:

PyTorch 1.5 中两位作者的解决方案:

import os
from torch.utils.tensorboard import SummaryWriter

LOG_DIR = "experiment_dir"
train_writer = SummaryWriter(os.path.join(LOG_DIR, "train"))
val_writer = SummaryWriter(os.path.join(LOG_DIR, "val"))

# while in the training loop
for k, v in train_losses.items()
    train_writer.add_scalar(k, v, global_step)

# in the validation loop
for k, v in val_losses.items()
    val_writer.add_scalar(k, v, global_step)

# at the end
train_writer.close()
val_writer.close()

train_losses dict 中的键必须与 val_losses 中的键匹配,才能在同一个图上分组。

【讨论】:

【参考方案8】:

Tensorboard 是一个非常好的工具,但由于它的声明性,它很难让它完全按照你的意愿去做。

我建议您查看 Losswise (https://losswise.com) 来绘制和跟踪损失函数,以替代 Tensorboard。使用 Losswise,您可以准确指定应该一起绘制的图形:

import losswise

losswise.set_api_key("project api key")
session = losswise.Session(tag='my_special_lstm', max_iter=10)
loss_graph = session.graph('loss', kind='min')

# train an iteration of your model...
loss_graph.append(x, 'train_loss': train_loss, 'validation_loss': validation_loss)
# keep training model...

session.done()

然后你会得到类似的东西:

注意数据是如何通过loss_graph.append 调用显式馈送到特定图表的,然后该数据将显示在项目的仪表板中。

此外,对于上面的示例,Losswise 会自动生成一个包含min(training_loss)min(validation_loss) 列的表格,因此您可以轻松地比较实验中的汇总统计信息。对于比较大量实验的结果非常有用。

【讨论】:

【参考方案9】:

请让我在 @Lifu Huang 给出的答案中提供一些代码示例。首先从here 下载loger.py 然后:

from logger import Logger
def train_model(parameters...):
    N_EPOCHS = 15
    # Set the logger
    train_logger = Logger('./summaries/train_logs')
    test_logger = Logger('./summaries/test_logs')
    for epoch in range(N_EPOCHS):

        # Code to get train_loss and test_loss

        # ============ TensorBoard logging ============#
        # Log the scalar values
        train_info = 
            'loss': train_loss,
        
        test_info = 
            'loss': test_loss,
        

        for tag, value in train_info.items():
            train_logger.scalar_summary(tag, value, step=epoch)
        for tag, value in test_info.items():
            test_logger.scalar_summary(tag, value, step=epoch)

最后你运行tensorboard --logdir=summaries/ --port=6006你得到:

【讨论】:

以上是关于TensorBoard - 在同一张图上绘制训练和验证损失?的主要内容,如果未能解决你的问题,请参考以下文章

TensorBoard:将两张图合并为一张图

组在张量板的同一张图上运行

遍历数据框中的列并在同一张图上绘图(Python)

matplotlib:同一张图上有2个不同的图例

为啥张量板图弯曲得很奇怪?

python 将多个数据图绘制到一张图上