机器学习笔记(二十):TensorFlow实战十二(TensorBoard可视化)

1 - 引言

前面已经介绍到TensorFlow可以实现许多非常常用的神经网络结构,有的网络结构十分复杂,里面的参数关系更是难以管理。因此,TensorFlow提供了一个可视化工具TensorBoard。可以有效的展示运行过程中的计算图、各种指标随着时间的变化趋势以及训练中使用到的图像等信息。

2 - TensorBoard简介

TensorBoard是TensorFlow的可视化工具,可以呈现当前TensorFlow程序运行的最新状态。以下代码展示了一个简单的TensorFlow程序,在这个程序中完成了TensorBoard日志输出的功能

import tensorflow as tf

input1 = tf.constant([1.0, 2.0, 3.0], name="input1")

input2 = tf.Variable(tf.random_uniform([3], name="input2"))

output = tf.add_n([input1, input2], name="add")

writer = tf.summary.FileWriter("/path/to/log", tf.get_default_graph())

writer.close()

在Anaconda Prompt输入以下代码生成TensorBoard网址
(首先激活tensorflow环境,再cd进存放Log的目录)
在这里插入图片描述
在浏览器中输入网址,得到计算图的可视化界面
在这里插入图片描述

3 - TensorFlow计算图可视化

下面将更加详细的介绍如何利用TensorBoard将计算图上的各个数据可视化

3.1 - 命名空间与TensorBoard图上节点

为了很好的组织可视化效果图中的计算节点,让可视化结果更加便于理解,TensorBoard支持通过TensorFlow命名空间来整理可视化效果图上的节点。在TensorBoard的默认视图中,TensorFlow计算图中同一个命名空间下的所有节点会被缩略成一个节点,只有顶层命名空间的节点才会被显示在TensorBoard可视化效果图上。

下面给出一个示例将一个神经网络结构图可视化出来。

import os
import time
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data

#加载mnist_inference.py中定义的常量和前向传播的函数。
import mnist_inference

BATCH_SIZE = 100
LEARNING_RATE_BASE = 0.8 # 最开始的学习率
LEARNING_RATE_DECAY = 0.99 # 在指数衰减学习率的过程中用到
REGULARIZATION_RATE = 0.0001 # 描述模型复杂度的正则化项在损失函数中的系数
TRAINING_STEPS = 30000 # 训练轮数,注意,训练一个Batch就是一个step
MOVING_AVERAGE_DECAY = 0.99 # 滑动平均模型的衰减率,最后我会讲解滑动平均模型


def train(mnist):
    #将处理输入数据的计算都放在名字为“input"的命名空间下
    with tf.name_scope('input'):
        # 定义输入输出placeholder。
        x = tf.placeholder(tf.float32, [None, mnist_inference.INPUT_NODE], name='x-input')
        y_ = tf.placeholder(tf.float32, [None, mnist_inference.OUTPUT_NODE], name='y-input')

    regularizer = tf.contrib.layers.l2_regularizer(REGULARIZATION_RATE)
    # 直接使用mnist_inference.py中定义的前向传播过程
    y = mnist_inference.inference(x, regularizer)
    global_step = tf.Variable(0, trainable=False)
    #将处理滑动平均相关的计算都放在名为moving_average的命名空间下
    with tf.name_scope('moving_average'):

        # 定义损失函数、学习率、滑动平均操作以及训练过程
        variable_averages = tf.train.ExponentialMovingAverage(
            MOVING_AVERAGE_DECAY, global_step
        )
        variable_averages_op = variable_averages.apply(
            tf.trainable_variables()
        )
    #将损失函数相关的计算都放在loss_function的命名空间下
    with tf.name_scope('loss_function'):
        cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(
            logits=y, labels=tf.argmax(y_, 1)
        )
        cross_entropy_mean = tf.reduce_mean(cross_entropy)
        loss = cross_entropy_mean + tf.add_n(tf.get_collection('losses'))
    #将定义学习率、优化方法以及每一轮训练需要执行的操作都放在名字为“train_step”的命名空间下
    with tf.name_scope("train_step"):
        learning_rate = tf.train.exponential_decay(
            LEARNING_RATE_BASE,
            global_step,
            mnist.train.num_examples / BATCH_SIZE,
            LEARNING_RATE_DECAY
        )
        train_step = tf.train.GradientDescentOptimizer(learning_rate)\
                       .minimize(loss, global_step=global_step)
    with tf.control_dependencies([train_step, variable_averages_op]):
        train_op = tf.no_op(name='train')


    startTime = time.time()

    with tf.Session() as sess:
        tf.global_variables_initializer().run()

        # 在训练过程中不再测试模型在验证数据上的表现,验证和测试的过程将会有一个独
        # 立的程序来完成。
        for i in range(TRAINING_STEPS):
            xs, ys = mnist.train.next_batch(BATCH_SIZE)
            _, loss_value, step = sess.run([train_op, loss, global_step],
                                           feed_dict={x: xs, y_: ys})
            # 每1000轮保存一次模型
            if i % 1000 == 0:
                # 输出当前的训练情况。这里只输出了模型在当前训练batch上的损失
                # 函数大小。通过损失函数的大小可以大概了解训练的情况。在验证数
                # 据集上正确率的信息会有一个单独的程序来生成
                print("After %d training step(s), loss on training "
                      "batch is %g." % (step, loss_value))

        print("Time taken: %f" % (time.time() - startTime))
        writer = tf.summary.FileWriter("/path/to/log", tf.get_default_graph())
        writer.close()
# 主程序入口
def main(argv=None):
    # 声明处理MNIST数据集的类,这个类在初始化时会自动下载数据。
    mnist = input_data.read_data_sets("/path/to/MNIST_data", one_hot=True)
    train(mnist)

# TensorFlow提供的一个主程序入口,tf.app.run会调用上面定义的main函数
if __name__ == "__main__":

    tf.app.run()

运行之后我们会得到这样一个计算图
在这里插入图片描述

3.2 - 节点信息

除了展示计算图的结构外,还可以将节点的基本信息以及运行时消耗的时间和空间都显示出来,这可以帮助我们有针对性的优化我们的神经网络模型。我们还是同样以MNIST神经网络为例,将计算节点的运行时间和消耗的内存写入TenosrBoard的日志文件中。

import os
import time
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data

#加载mnist_inference.py中定义的常量和前向传播的函数。
import mnist_inference

BATCH_SIZE = 100
LEARNING_RATE_BASE = 0.8 # 最开始的学习率
LEARNING_RATE_DECAY = 0.99 # 在指数衰减学习率的过程中用到
REGULARIZATION_RATE = 0.0001 # 描述模型复杂度的正则化项在损失函数中的系数
TRAINING_STEPS = 30000 # 训练轮数,注意,训练一个Batch就是一个step
MOVING_AVERAGE_DECAY = 0.99 # 滑动平均模型的衰减率,最后我会讲解滑动平均模型
log_dir='/path/to/log'

def train(mnist):
    #将处理输入数据的计算都放在名字为“input"的命名空间下
    with tf.name_scope('input'):
        # 定义输入输出placeholder。
        x = tf.placeholder(tf.float32, [None, mnist_inference.INPUT_NODE], name='x-input')
        y_ = tf.placeholder(tf.float32, [None, mnist_inference.OUTPUT_NODE], name='y-input')

    regularizer = tf.contrib.layers.l2_regularizer(REGULARIZATION_RATE)
    # 直接使用mnist_inference.py中定义的前向传播过程
    y = mnist_inference.inference(x, regularizer)
    global_step = tf.Variable(0, trainable=False)
    #将处理滑动平均相关的计算都放在名为moving_average的命名空间下
    with tf.name_scope('moving_average'):

        # 定义损失函数、学习率、滑动平均操作以及训练过程
        variable_averages = tf.train.ExponentialMovingAverage(
            MOVING_AVERAGE_DECAY, global_step
        )
        variable_averages_op = variable_averages.apply(
            tf.trainable_variables()
        )
    #将损失函数相关的计算都放在loss_function的命名空间下
    with tf.name_scope('loss_function'):
        cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(
            logits=y, labels=tf.argmax(y_, 1)
        )
        cross_entropy_mean = tf.reduce_mean(cross_entropy)
        loss = cross_entropy_mean + tf.add_n(tf.get_collection('losses'))
    #将定义学习率、优化方法以及每一轮训练需要执行的操作都放在名字为“train_step”的命名空间下
    with tf.name_scope("train_step"):
        learning_rate = tf.train.exponential_decay(
            LEARNING_RATE_BASE,
            global_step,
            mnist.train.num_examples / BATCH_SIZE,
            LEARNING_RATE_DECAY
        )
        train_step = tf.train.GradientDescentOptimizer(learning_rate)\
                       .minimize(loss, global_step=global_step)
    with tf.control_dependencies([train_step, variable_averages_op]):
        train_op = tf.no_op(name='train')


    startTime = time.time()

    with tf.Session() as sess:
        tf.global_variables_initializer().run()
        train_writer = tf.summary.FileWriter(log_dir + '/train', sess.graph)
        # 在训练过程中不再测试模型在验证数据上的表现,验证和测试的过程将会有一个独
        # 立的程序来完成。
        for i in range(TRAINING_STEPS):
            xs, ys = mnist.train.next_batch(BATCH_SIZE)
            _, loss_value, step = sess.run([train_op, loss, global_step],
                                           feed_dict={x: xs, y_: ys})
            # 每1000轮保存一次模型
            if i % 1000 == 0:
                #配置运行时需要记录的信息
                run_options = tf.RunOptions(trace_level=tf.RunOptions.FULL_TRACE)
                #运行时记录运行信息的proto
                run_metadata = tf.RunMetadata()
                #将配置信息和记录运行信息的proto传入运行的过程,从而记录运行时每一个节点的时间、空间开销信息
                _,loss_value,step = sess.run(
                    [train_op,loss,global_step],feed_dict={x:xs,y_:ys},
                    options=run_options,run_metadata=run_metadata)
                #将节点在运行时的信息写入日志文件
                train_writer.add_run_metadata(run_metadata,'setp%03d'%i)
                # 输出当前的训练情况。这里只输出了模型在当前训练batch上的损失
                # 函数大小。通过损失函数的大小可以大概了解训练的情况。在验证数
                # 据集上正确率的信息会有一个单独的程序来生成
                print("After %d training step(s), loss on training "
                      "batch is %g." % (step, loss_value))
            else:
                _, loss_value, step = sess.run([train_op, loss, global_step], feed_dict={x: xs, y_: ys})
        print("Time taken: %f" % (time.time() - startTime))
        writer = tf.summary.FileWriter("/path/to/log", tf.get_default_graph())
        writer.close()
# 主程序入口
def main(argv=None):
    # 声明处理MNIST数据集的类,这个类在初始化时会自动下载数据。
    mnist = input_data.read_data_sets("/path/to/MNIST_data", one_hot=True)
    train(mnist)

# TensorFlow提供的一个主程序入口,tf.app.run会调用上面定义的main函数
if __name__ == "__main__":

    tf.app.run()

在这里插入图片描述

3.3 - 监控指标可视化

TensorBoard除了可以可视化计算图,还可以可视化运行过程各种有助于了解程序运行状态的监控指标。
下面介绍SCALARS 、IMAGES、AUDIO、DISTRIBUTIONS、HISTOGRAMS这四个栏目并且添加进监控指标中

import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data


# ####  1. 生成变量监控信息并定义生成监控信息日志的操作。

SUMMARY_DIR = "/path/to/log"
BATCH_SIZE = 100
TRAIN_STEPS = 30000


# var给出了需要记录的张量,name给出了在可视化结果中显示的图表名称,这个名称一般和变量名一致
def variable_summaries(var, name):
    # 将生成监控信息的操作放在同一个命名空间下
    with tf.name_scope('summaries'):
        # 通过tf.histogram_summary函数记录张量中元素的取值分布
        # tf.summary.histogram函数会生成一个Summary protocol buffer.
        # 将Summary 写入TensorBoard 门志文件后,在HISTOGRAMS 栏,和
        # DISTRIBUTION 栏下都会出现对应名称的图表。和TensorFlow 中其他操作类似,
        # tf.summary.histogram 函数不会立刻被执行,只有当sess.run 函数明确调用这个操作时, TensorFlow
        # 才会具正生成并输出Summary protocol buffer.

        tf.summary.histogram(name, var)

        # 计算变量的平均值,并定义生成平均值信息日志的操作,记录变量平均值信息的日志标签名
        # 为'mean/'+name,其中mean为命名空间,/是命名空间的分隔符
        # 在相同命名空间中的监控指标会被整合到同一栏中,name则给出了当前监控指标属于哪一个变量

        mean = tf.reduce_mean(var)
        tf.summary.scalar('mean/' + name, mean)

        # 计算变量的标准差,并定义生成其日志文件的操作
        stddev = tf.sqrt(tf.reduce_mean(tf.square(var - mean)))
        tf.summary.scalar('stddev/' + name, stddev)


# #### 2. 生成一层全链接的神经网络。
def nn_layer(input_tensor, input_dim, output_dim, layer_name, act=tf.nn.relu):
    # 将同一层神经网络放在一个统一的命名空间下
    with tf.name_scope(layer_name):
        # 声明神经网络边上的权值,并调用权重监控信息日志的函数
        with tf.name_scope('weights'):
            weights = tf.Variable(tf.truncated_normal([input_dim, output_dim], stddev=0.1))
            variable_summaries(weights, layer_name + '/weights')

        # 声明神经网络边上的偏置,并调用偏置监控信息日志的函数
        with tf.name_scope('biases'):
            biases = tf.Variable(tf.constant(0.0, shape=[output_dim]))
            variable_summaries(biases, layer_name + '/biases')
        with tf.name_scope('Wx_plus_b'):
            preactivate = tf.matmul(input_tensor, weights) + biases
            # 记录神经网络节点输出在经过激活函数之前的分布
            tf.summary.histogram(layer_name + '/pre_activations', preactivate)
        activations = act(preactivate, name='activation')

        # 记录神经网络节点输出在经过激活函数之后的分布。

        """
        对于layerl ,因为使用了ReLU函数作为激活函数,所以所有小于0的值部被设为了0。于是在激活后
        的layerl/activations 图上所有的值都是大于0的。而对于layer2 ,因为没有使用激活函数,
        所以layer2/activations 和layer2/pre_activations 一样。
        """
        tf.summary.histogram(layer_name + '/activations', activations)
        return activations


def main(_):
    mnist = input_data.read_data_sets("/path/to/MNIST_data", one_hot=True)

    with tf.name_scope('input'):
        x = tf.placeholder(tf.float32, [None, 784], name='x-input')
        y_ = tf.placeholder(tf.float32, [None, 10], name='y-input')

    with tf.name_scope('input_reshape'):
        image_shaped_input = tf.reshape(x, [-1, 28, 28, 1])
        tf.summary.image('input', image_shaped_input, 10)
        # 将输入变量还原成图片的像素矩阵,并通过tf.iamge_summary函数定义将当前的图片信息写入日志的操作

    hidden1 = nn_layer(x, 784, 500, 'layer1')
    y = nn_layer(hidden1, 500, 10, 'layer2', act=tf.identity)

    # 计算交叉熵并定义生成交叉熵监控日志的操作。
    with tf.name_scope('cross_entropy'):
        cross_entropy = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=y, labels=y_))
        tf.summary.scalar('cross_entropy', cross_entropy)

    with tf.name_scope('train'):
        train_step = tf.train.AdamOptimizer(0.001).minimize(cross_entropy)

    """
    计算模型在当前给定数据上的正确率,并定义生成正确率监控日志的操作。如果在sess.run()
    时给定的数据是训练batch,那么得到的正确率就是在这个训练batch上的正确率;如果
    给定的数据为验证或者测试数据,那么得到的正确率就是在当前模型在验证或者测试数据上
    的正确率。
    """
    with tf.name_scope('accuracy'):
        with tf.name_scope('correct_prediction'):
            correct_prediction = tf.equal(tf.argmax(y, 1), tf.argmax(y_, 1))
        with tf.name_scope('accuracy'):
            accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
        tf.summary.scalar('accuracy', accuracy)

    # tf.scalar_summary,tf.histogram_summary,tf.image_summary函数都不会立即执行,需要通过sess.run来调用这些函数
    # 因为程序重定义的写日志的操作非常多,一一调用非常麻烦,所以Tensorflow提供了tf.merge_all_summaries函数来整理所有的日志生成操作。
    # 在Tensorflow程序执行的过程中只需要运行这个操作就可以将代码中定义的所有日志生成操作全部执行一次,从而将所有日志文件写入文件。

    merged = tf.summary.merge_all()

    with tf.Session() as sess:
        # 初始化写日志的writer,并将当前的Tensorflow计算图写入日志
        summary_writer = tf.summary.FileWriter(SUMMARY_DIR, sess.graph)
        tf.global_variables_initializer().run()

        for i in range(TRAIN_STEPS):
            if i % 1000 == 0:
                print(i)
            xs, ys = mnist.train.next_batch(BATCH_SIZE)
            # 运行训练步骤以及所有的日志生成操作,得到这次运行的日志。
            summary, _ = sess.run([merged, train_step], feed_dict={x: xs, y_: ys})
            # 将得到的所有日志写入日志文件,这样TensorBoard程序就可以拿到这次运行所对应的
            # 运行信息。
            summary_writer.add_summary(summary, i)

    summary_writer.close()


if __name__ == '__main__':
    tf.app.run()

(1)SCALARS
展示的是标量的信息,我程序中用tf.summary.scalars()定义的信息都会在这个窗口。
在这里插入图片描述

(2)IMAGES
在程序中我们设置了一处保存了图像信息,就是在转变了输入特征的shape,然后记录到了image中,于是在tensorflow中就会还原出原始的图片了:
在这里插入图片描述
(3)AUDIO
这里展示的是声音的信息,但本案例中没有涉及到声音的。
(5)DISTRIBUTIONS
这里查看的是神经元输出的分布,有激活函数之前的分布,激活函数之后的分布等。
在这里插入图片描述
(6)HISTOGRAMS
也可以看以上数据的直方图
在这里插入图片描述

4 - 总结

利用TensorBoard可以很好的帮助我们可视化神经网络的结构、参数信息、计算代价等我们需要了解的信息。进而进一步的帮助我们改善神经网络。

猜你喜欢

转载自blog.csdn.net/HHH_ANS/article/details/84547816