TensorFlow自带的一个强大的可视化工具,TensorBoard 涉及到的运算,通常是在训练庞大的深度神经网络中出现的复杂而又难以理解的运算。为了更方便 TensorFlow 程序的理解、调试与优化,你可以用 TensorBoard 来展现你的 TensorFlow 图像,绘制图像生成的定量指标图以及附加数据。

功能

这是TensorFlow在MNIST实验数据上得到Tensorboard结果

  • Event: 展示训练过程中的统计数据(最值,均值等)变化情况
  • Image: 展示训练过程中记录的图像
  • Audio: 展示训练过程中记录的音频
  • Histogram: 展示训练过程中记录的数据的分布图

原理

  • 在运行过程中,记录结构化的数据
  • 运行一个本地服务器,监听6006端口
  • 请求时,分析记录的数据,绘制

当 TensorBoard 设置完成后是这样子的:

数据序列化 

TensorBoard 通过读取 TensorFlow 的事件文件来运行。TensorFlow 的事件文件包括了你会在 TensorFlow 运行中涉及到的主要数据。下面是 TensorBoard 中汇总数据(Summary data)的大体生命周期。

首先,创建你想汇总数据的 TensorFlow 图,然后再选择你想在哪个节点进行汇总(summary)操作。

比如,假设你正在训练一个卷积神经网络,用于识别 MNISt 标签。你可能希望记录学习速度(learning rate)的如何变化,以及目标函数如何变化。通过向节点附加scalar_summary操作来分别输出学习速度和期望误差。然后你可以给每个 scalary_summary 分配一个有意义的 标签,比如 'learning rate' 和 'loss function'

或者你还希望显示一个特殊层中激活的分布,或者梯度权重的分布。可以通过分别附加 histogram_summary运算来收集权重变量和梯度输出。

在TensorFlow中,所有的操作只有当你执行,或者另一个操作依赖于它的输出时才会运行。我们刚才创建的这些节点(summary nodes)都围绕着你的图像:没有任何操作依赖于它们的结果。因此,为了生成汇总信息,我们需要运行所有这些节点。这样的手动工作是很乏味的,因此可以使用tf.merge_all_summaries来将他们合并为一个操作。

然后你可以执行合并命令,它会依据特点步骤将所有数据生成一个序列化的Summary protobuf对象。最后,为了将汇总数据写入磁盘,需要将汇总的protobuf对象传递给tf.train.Summarywriter

SummaryWriter 的构造函数中包含了参数 logdir。这个 logdir 非常重要,所有事件都会写到它所指的目录下。此外,SummaryWriter 中还包含了一个可选择的参数 GraphDef。如果输入了该参数,那么 TensorBoard 也会显示你的图像。

现在已经修改了你的图,也有了 SummaryWriter,现在就可以运行你的神经网络了!如果你愿意的话,你可以每一步执行一次合并汇总,这样你会得到一大堆训练数据。这很有可能超过了你想要的数据量。你也可以每一百步执行一次合并汇总,或者如下面代码里示范的这样。

merged_summary_op = tf.merge_all_summaries()
summary_writer = tf.train.SummaryWriter('/tmp/mnist_logs', sess.graph)
total_step = 0
while training:
  total_step += 1
  session.run(training_op)
  if total_step % 100 == 0:
    summary_str = session.run(merged_summary_op)
    summary_writer.add_summary(summary_str, total_step)

启动TensorBoard 

输入下面的指令来启动TensorBoard

python tensorflow/tensorboard/tensorboard.py --logdir=path/to/log-directory

这里的参数 logdir 指向 SummaryWriter 序列化数据的存储路径。如果logdir目录的子目录中包含另一次运行时的数据,那么 TensorBoard 会展示所有运行的数据。一旦 TensorBoard 开始运行,你可以通过在浏览器中输入 localhost:6006 来查看 TensorBoard。

如果你已经通过pip安装了 TensorBoard,你可以通过执行更为简单地命令来访问 TensorBoard

tensorboard --logdir=/path/to/log-directory

进入 TensorBoard 的界面时,你会在右上角看到导航选项卡,每一个选项卡将展现一组可视化的序列化数据集 。对于你查看的每一个选项卡,如果 TensorBoard 中没有数据与这个选项卡相关的话,则会显示一条提示信息指示你如何序列化相关数据。

TensorFlow 图表计算强大而又复杂,图表可视化在理解和调试时显得非常有帮助。 下面是一个运作时的可式化例子。

为了显示自己的图表,需将 TensorBoard 指向此工作的日志目录并运行,点击图表顶部窗格的标签页,然后在左上角的菜单中选择合适的运行。

名称域(Name scoping)和节点(Node)

典型的 TensorFlow 可以有数以千计的节点,为变量名划定范围,并且可视化把该信息用于在图表中的节点上定义一个层级。默认情况下, 只有顶层节点会显示。下面这个例子使用tf.name_scopehidden命名域下定义了三个操作:

import tensorflow as tf

with tf.name_scope('hidden') as scope:
  a = tf.constant(5, name='alpha')
  W = tf.Variable(tf.random_uniform([1, 2], -1.0, 1.0), name='weights')
  b = tf.Variable(tf.zeros([1]), name='biases')

结果是得到了下面三个操作名:

  • hidden/alpha
  • hidden/weights
  • hidden/biases

默认地,三个操作名会折叠为一个节点并标注为hidden。其额外细节并没有丢失,你可以双击,或点击右上方橙色的+来展开节点,然后就会看到三个子节点alphaweightsbiases了。

这有一个生动的例子,例中有一个更复杂的节点,节点处于其初始和展开状态。

通过名称域把节点分组来得到可读性高的图表很关键的。如果你在构建一个模型,名称域就可以用来控制可视化结果。你的名称域越好,可视性就越好。

TensorFlow 图表有两种连接关系:数据依赖和控制依赖

  • 数据依赖显示两个操作之间的tensor流程,用实心箭头指示;
  • 控制依赖用点线表示。在已展开的视图(上面的右图)中,除了用点线连接的CheckNumericscontrol_dependency之外,所有连接都是数据依赖的。

还有一种手段用来简化布局。大多数 TensorFlow 图表有一部分节点,这部分节点和其他节点之间有很多连接。比如,许多节点在初始化阶段可能会有一个控制依赖,而绘制所有init节点的边缘和其依赖可能会创造出一个混乱的视图。为了减少混乱,可视化把所有 high-degree 节点分离到右边的一个从属区域, 而不会绘制线条来表示他们的边缘。线条也不用来表示连接了,我们绘制了小节点图标来指示这些连接关系。分离出从属节点通常不会把关键信息删除掉,因为这些节点和内构功能是相关的。

序列折叠(series collapsing)

序列基序(Sequential motifs)是拥有相同结构并且其名称结尾的数字不同的节点,它们被折叠进一个单独的节点块(stack)中。对长序列网络来说,序列折叠极大地简化了视图,对于已层叠的节点,双击会展开序列。

最后,针对易读性的最后一点要说到的是,可视化为常节点和摘要节点使用了特别的图标,总结起来有下面这些节点符号:

交互

 

详情卡片展示conv2名称域的详细信息,名称域中操作节点的输入和输出被结合在一起,适用于不显示属性的名称域。

详情卡片展示DecodeRaw操作节点,除了输入和输出,卡片也会展示与当前节点相关的设备和属性。

 

下图是一张真实图表的图解:

结构视图:灰色节点的结构是唯一的。橙色的conv1conv2节点有相同的结构, 其他颜色的节点也类似。

设备视图:名称域根据其中的操作节点的设备片件来着色,在此紫色代表GPU,绿色代表CPU。

实现

在构建graph的过程中,记录你想要追踪的Tensor

with tf.name_scope('output_act'):
    hidden = tf.nn.relu6(tf.matmul(reshape, output_weights[0]) + output_biases)
    tf.histogram_summary('output_act', hidden)

其中,

  • histogram_summary用于生成分布图,也可以用scalar_summary记录存数值
  • 使用scalar_summary的时候,tag和tensor的shape要一致
  • name_scope可以不写,但是当你需要在Graph中体现tensor之间的包含关系时,就要写了,像下面这样:
with tf.name_scope('input_cnn_filter'):
    with tf.name_scope('input_weight'):
        input_weights = tf.Variable(tf.truncated_normal(
            [patch_size, patch_size, num_channels, depth], stddev=0.1), name='input_weight')
        variable_summaries(input_weights, 'input_cnn_filter/input_weight')
    with tf.name_scope('input_biases'):
        input_biases = tf.Variable(tf.zeros([depth]), name='input_biases')
        variable_summaries(input_weights, 'input_cnn_filter/input_biases')
  • 在Graph中会体现为一个input_cnn_filter,可以点开,里面有weight和biases
  • 用summary系列函数记录后,Tensorboard会根据graph中的依赖关系在Graph标签中展示对应的图结构
  • 官网封装了一个函数,可以调用来记录很多跟某个Tensor相关的数据:
def variable_summaries(var, name):
    """Attach a lot of summaries to a Tensor."""
    with tf.name_scope('summaries'):
        mean = tf.reduce_mean(var)
        tf.scalar_summary('mean/' + name, mean)
        with tf.name_scope('stddev'):
            stddev = tf.sqrt(tf.reduce_sum(tf.square(var - mean)))
        tf.scalar_summary('sttdev/' + name, stddev)
        tf.scalar_summary('max/' + name, tf.reduce_max(var))
        tf.scalar_summary('min/' + name, tf.reduce_min(var))
        tf.histogram_summary(name, var)
  • 只有这样记录国max和min的Tensor才会出现在Event里面
  • Graph的最后要写一句这个,给session回调
merged = tf.merge_all_summaries()

Session 中调用 

  • 构造两个writer,分别在train和valid的时候写数据:
train_writer = tf.train.SummaryWriter(summary_dir + '/train',session.graph)
valid_writer = tf.train.SummaryWriter(summary_dir + '/valid')
  • 这里的summary_dir存放了运行过程中记录的数据,等下启动服务器要用到
  • 构造run_option和run_meta,在每个step运行session时进行设置:
summary, _, l, predictions = 
    session.run([merged, optimizer, loss, train_prediction], options=run_options, feed_dict=feed_dict)
  • 注意要把merged拿回来,并且设置options
  • 在每次训练时,记一次:
train_writer.add_summary(summary, step)
train_writer.add_summary(summary, step)
  • 在每次验证时,记一次:
valid_writer.add_summary(summary, step)
valid_writer.add_summary(summary, step)
  • 达到一定训练次数后,记一次meta做一下标记
train_writer.add_run_metadata(run_metadata, 'step%03d' % step)
train_writer.add_run_metadata(run_metadata, 'step%03d' % step)

查看可视化结果

  • 启动TensorBoard服务器:
python安装路径/python TensorFlow安装路径/tensorflow/tensorboard/tensorboard.py --logdir=path/to/log-directory
注意这个python必须是安装了TensorFlow的python,tensorboard.py必须制定路径才能被python找到,logdir必须是前面创建两个writer时使用的路径

强迫症踩坑后记

  • 之前我的cnn代码里有valid_prediction,所以画出来的graph有两条分支,不太清晰,所以只留了train一个分支
  • 多用with,进行包裹,这样才好看,正如官网说的,你的summary代码决定了你的图结构
  • 不是所有的tensor都有必要记录,但是Variable和placeholder最好都用summary记录一下,也是为了好看
  • 由于有了gradient的计算,所以与gradient计算相关的都会被拎出来,下次试一下用其他optimizer

我的CNN TensorBoard代码:cnn_board.py

注:

tensorboard 出现Fatal error in launcher: Unable to create process using '"'

解决方法:

python -m tensorflow.tensorboard --logdir=