可视化是深度学习神经网络开发、调试、应用中极为重要的手段。Tensorboard是Tensorflow提供的一个可视化工具,本文通过实际代码实验的方式说明使用TensorBoard实现记录变量,实现可视化调试的目的。
我的GitHub中TF_Graph项目, singleNerualNode.py
为了简化情况,实验中使用单层神经网络,网络结构定义见笔者前一篇文章 [TensorFlow]理解Tensorboard Graph“案例1:单个神经元”
merged = tf.summary.merge_all()
log_path = "tf_writer"
writer = tf.summary.FileWriter(log_path, sess.graph)
for iterIdx in range(iterationNumber):
sess.run(train_step, feed_dict={inputTensor: inputData, labelTensor:labels})
summary = sess.run(merged, feed_dict={inputTensor: inputData, labelTensor:labels})
writer.add_summary(summary, iterIdx)
#flush to disk every 50 iteration
if iterIdx % 50 == 0:
writer.flush()
writer.close()
启动TensorBoard,在shell输入如下命令
tensorboard –logdir tf_writer
注:’tf_writer’是我的log_path, 实际使用中根据存储Log的位置调整
从浏览器中访问
打开浏览器访问http://192.168.1.100:6006/,其中’192.168.1.100’是运行Tensorboard的计算机IP地址,可以是本机,也可以是网络上的服务器,能访问到就行。
区别于矩阵和向量,标量可以认为只有1个维度的数值量。
在神经网络中,损失函数的值、模型预测准确率等都是典型的标量。如下是计算损失函数和模型预测准确率的代码。
with tf.name_scope('evaluation'):
loss = tf.nn.l2_loss(a - labels, name='L2Loss') / batchSize
threshold = 0.5
binary_outputs = a >= threshold
binary_labels = labels >= threshold
correct_item = tf.equal(binary_outputs, binary_labels)
accuracy = tf.reduce_mean(tf.cast(correct_item, tf.float32))
对于这些变量,我们最典型的需求是知道每一轮模型训练迭代中,其朝着哪个方向变化,是否达到收敛状态。使用Tensorboard,只要添加以下代码就可以实现
tf.summary.scalar('L2Loss',loss)
tf.summary.scalar('Accuracy', accuracy)
在Tensorboard中看到的结果如下,Accuracy和L2 Loss分别记录在两张图表中,每张图的横坐标表示迭代序号(第几次训练),纵坐标就是我们添加的标量结果
Tensorboard会记录每个点的名称、平滑后取值、原始值、Step(迭代序号)、时间戳和相对于训练开始过了多长时间。这些信息对于可视化的观察模型收敛情况非常有帮助。
标量(scalar)在Tensor Flow的神经网络中只占很少一部分,大部分变量是多维张量,即Tensor。由于一个Tensor有多个维度,无法像标量一样直接输出成曲线,在可视化时可以有以下几种方法:
第一种,在TensorBoard的官方教程中给出了清晰的示意代码。给定一个Tensor,如下函数从均值、标准差、最大值、最小值等多个角度转化为标量,从而可视化。由于标量的可视化效果前一节已经展示过了,这里不在重复。
def variable_summaries(var):
"""Attach a lot of summaries to a Tensor (for TensorBoard visualization)."""
with tf.name_scope('summaries'):
mean = tf.reduce_mean(var)
tf.summary.scalar('mean', mean)
with tf.name_scope('stddev'):
stddev = tf.sqrt(tf.reduce_mean(tf.square(var - mean)))
tf.summary.scalar('stddev', stddev)
tf.summary.scalar('max', tf.reduce_max(var))
tf.summary.scalar('min', tf.reduce_min(var))
第二种,以直方图输出,输出方法非常简单,按照下列代码的方式调用tf.summary.histogram即可
with tf.name_scope('Nerual_Node'):
W = tf.Variable(tf.random_normal([numberOfInputDims, 1]), name='weights')
tf.summary.histogram('weights', W)
b = tf.Variable(tf.zeros([1]), name='biases')
tf.summary.histogram('biases', b)
这张图垂直于屏幕的方向,即高亮的一行显示822则个数据所在的坐标轴,是迭代次数序号。随着训练迭代次数由0到最大迭代次数(这里设定为1000)训练,bias的分布由远及近画出来。
关键问题是“bias的分布”指的是什么,根据代码bias本身定义为1维Tensor:b = tf.Variable(tf.zeros([1]), name=’biases’),一个Batch定义为1000,那么b可以视为具有1000个元素的一维数组,上图每个横截面给出的是这1000个元素的概率分布。
带着这个结论,接下来考察更复杂的多维Tensor的直方图:weight
weight本身定义维2*1 Tensor,每个batch定义为1000个样本
W = tf.Variable(tf.random_normal([numberOfInputDims, 1]), name='weights')
则直方图代表的是总数为2*1*1000个取值的分布。
我们将W设计维2*1,是为了保存两个输入节点分别的权重:w_1和w_2。在上述直方图分布中,确实可以清晰的看到W的分布呈现双峰分布,这两个峰分别对应w_1和w_2。随着训练次数的增加,w_1和w_2相距越来越远,这和训练目标是实现线性分类是吻合的。
第三种,以图形形式输出,在TensorFlow API中有详细描述,由于这次我们选择的简单神经网络中不具备这样格式的Tensor,暂时无法通过例子说明。API文档链接如下
https://www.tensorflow.org/versions/master/api_docs/python/tf/summary/image
除了前文描述的标量和Tensor输出之外,可视化还包括观察大量数据的分布。这里大量数据可以是输入、输出数据,也可以是网络中的任意参数。
Google对此有一篇专门的论文,其中Introduction对于Embedding的定义和作用有清晰的描述,笔者尝试翻译如下
embedding是从输入数据点到欧式空间中点的映射,为了理解模型的行为特征,机器学习的研发人员经常需要探索某个特定的Embedding。例如做音乐推荐系统的工程师创建了一个歌曲的embedding,他可能需要验证”Stairway to Heaven”这支歌曲最近的邻居包括“Whole Lotta Love”但不包括”Let it Go”. 对于这样的用户,从Embedding的几何结构获得理解就很关键。
具体到我们的案例,假定我想知道输入数据的几何分布,使用如下代码实现。
from tensorflow.contrib.tensorboard.plugins import projector
....
embedding_var = tf.Variable(inputData, 'data_embeding')
config = projector.ProjectorConfig()
embedding = config.embeddings.add()
embedding.tensor_name = embedding_var.name
embedding.metadata_path = 'label.csv'
projector.visualize_embeddings(tf.summary.FileWriter(log_path), config)
代码中inputData是1000*2的Tensor,表示1000个二维平面上的点。label.csv是包含了这1000个数据标签的文件,格式见如下文件
https://github.com/wangyaobupt/TF_Graph/blob/master/label.csv
Embedding还必须依赖于模型的保存,在定义网络结构中,需要定义saver如下
all_vars = tf.global_variables()
saver = tf.train.Saver(all_vars)
在模型训练中,必须保存训练结果
for iterIdx in range(iterationNumber):
sess.run(train_step, feed_dict={inputTensor: inputData, labelTensor:labels})
.....
saver.save(sess, model_path)
从图形左侧逐一解释,首先左上部分要选择数据和颜色,我们整个网络中只添加了一个Embedding Tensor,因此不用特殊选定,颜色算则Label即可,即根据label文件不同的标签标识颜色。
左下部有三张选项卡,s_TNE/PCA/Custom,默认为PCA。这里使用PCA是为了把高维Tensor降低到3维,以方便可视化。但我们此次实验的数据,扣除表示batch的维度,只有2维,因此PCA的输出就是数据的X、Y坐标,同时不需要显示Z轴。
右侧是实际图形,图中可以清晰的看到训练数据的线性可分性质。
由于Tensorflow在Embedding的文档本身较少,笔者写作这一节内容时参考了大量网络资料,特别感谢以下这Link的回答者Ehsan和Albert X.W.