Tensorboard
下图是来自官网的截图:
- 可视化模型图(操作和层)
- 查看权重(weights)、偏差(bias)或其他张量随时间变化的直方图
- 将嵌入投射到较低的维度空间 这个完全不懂
- 显示图片、文字和音频数据
- 剖析TensorFlow程序
从上图可以看出来:
我的这个确实是默认进入了SCALARS
, 然后我用到的面板SCALARS
,GRAPHS
会在顶部导航栏直接显示,而其他用不到的(我代码里没有相关代码)则会收起在INACTIVE
中。我以为我是有HISTOGRAMS
的,但是实际上并没有,当我点击HISTOGRAMS
时候我得到的界面如下。
从上面可以输出Loss相关图像,所以我们可以看出,Tensorboard can find my event files
. 所以就是我没有把histogram
数据写入到我的event file
中.
把Histogram写入到Event
文件中
1.目的:
查看一个张量在训练过程中值的分布
情况 可以通过tf.summary.histogram
将其分布情况以直方图的形式
在TensorBoard
直方图仪表板上显示。
应该把这个图像直方图称作为图像的亮度直方图.
也就是横坐标
从左到右代表像素的亮度值(0-255这种). 所以,左侧代表纯黑或者较暗的区域, 右侧代表较亮,纯白的区域。纵坐标
代表像素的数量, 哪个像素值上分布的像素多,哪个bar就高一些。
2. 目的:
tf.summary.histogram
输出一个直方图summary
的protocol buffer(是一种序列化数据结构的协议,对于透过管道或者存储数据进行通信的程序开发上是很有用的).
这个protocol buffer方法包含一个接口描述语言,描述一些数据结构,并提供程序工具根据这些描述产生代码,用于将这些数据产生或者解析数据流
输出一个直方图summary的数据,并告诉怎么来解析这些数据。
- name: A name for this summary. 对一个要观测的Summary起个名字。 然后,由任意active name scopes作为前缀, 前缀加名字就会作为这个summary的summary tag used for TensorBoard.
- values: 任意形状的一个张量。用于构建直方图的值。
tf.summary.histogram()将输入的一个任意大小和形状的张量压缩成一个由宽度和数量组成的直方图数据结构. 这里的宽度
和数量
分别都是什么?
假设输入 [0.5, 1.1, 1.3, 2.2, 2.9, 2.99],则可以创建三个bin,分别包含0-1之间/1-2之间/2-3之间的所有元素,即三个bin中的元素分别为[0.5]/[1.1,1.3]/[2.2,2.9,2.99].
这样,通过可视化张量在不同时间点的直方图来显示某些分布随时间变化的情况.
3. 如何把我的histogram添加到event?
# 将该分布记录到直方图汇总中
import tensorflow as tf
k = tf.placeholder(tf.float32)
# # 创建一个均值变化的正态分布(由0到5左右) 变量名字叫做均值-变化-正态分布
mean_moving_normal = tf.random_normal(shape=[1000], mean=(5*k), stddev=1)
tf.summary.histogram("normal/moving_mean", mean_moving_normal)
# 用于构建直方图的值就是用的: mean_moving_normal
因为这个相当于没有name_scope
, 所以,tag就是name
也就是normal/moving_mean
.
从上面的代码可以看出来,此时还只是把均值变化正态分布记录到直方图汇总中tf.summary.histogram("normal/moving_mean", mean_moving_normal)
,而这一切在没有Session的情况下,也还没真正进行。
sess = tf.Session() # 这才是要真正进行 但还没进行
writer = tf.summary.FileWriter("/tmp/histogram_example")
summaries = tf.summary.merge_all()
# 设置一个循环并将摘要写入磁盘
N = 400
for step in range(N):
k_val = step/float(N)
summ = sess.run(summaries, feed_dict={k: k_val})
writer.add_summary(summ, global_step=step) # 到底是add_summary起作用,前提是得获得summ.
然而我的facenet程序的代码组织是分里
和外
的。那就只能从逻辑上推理确定了,另外就是跑完以后根据tensorboard显示的结果来检验。
facenet的情形如下:
在外面:
summary_op = tf.summary.merge_all() # Merges all summaries collected in the default graph.
summary_writer = tf.summary.FileWriter(log_dir, sess.graph)
但是那到底是怎么实现把histogram添加到event里面的呢?
因为我之前训练的东西没有打开log_histograms
的FLAGs. 我打开一个然后试试看看.
第一个:原程序 只多打开log_histograms
第二个: 添加监控variables_norm
和gradients_norm
,然后打开log_histograms
开关,看看变量和梯度的范数,然后判断下梯度弥散或者梯度爆炸。
从新训练完事的结果可以看出来,当打开了log_histograms
开关后,我们知道:确实在TensorBoard上面的面板上会多出来HISTOGRAMS
, 如图所示.
这里有一个疑问,
这个刚开始的黄线Loss怎么是个0呢?Loss刚开始也不可能是0啊。
蓝线刚开始Loss下降有点快,
这就得看,看看到底cross_entropy_1
和cross_entropy_raw
什么区别?
然后,另外的话,就是看看这次添加了直方图
的上述两个cross_entropy_1
和cross_entropy_raw
有没有什么变化,如果没有变化的话,就是确定softmax cross entropy
是百分之百没错的。然后把他的tensorboard显示作为参照。
这个的话就牵扯到TensorFlow中的ExponentialMovingAverage
我有专门的博客来描写ExponentialMovingAverage
.
既然是滑动平均,而且有说过averaged parameters有时候会产生比final trained values更好的result. 所以我们也监控了一个滑动平均的值,也就是cross_entropy_1
. 然而这个滑动平均的版本这么变化是不是伴随其他问题? 或者说,干脆不看这个,而是应该看raw.
先看raw
吧,记住,看cosface时候也是先看raw
那个的趋势有没有和当前CE一样好?或者,找个别人不带raw
下方截图:
下方截图为普通原始的和滑动平均的两个版本:
1. 目的:
当我们训练深度神经网络的时候,一个很棘手的问题就是对于各种网络结构和超参数的选择产生的影响缺乏理解。例如,如果我们忽视权重初始化方式的选择,可能会导致权重之间存在非常大的方差,模型的训练就有可能迅速偏离正轨。另一方面,即使我们在参数选择的时候很小心,也免不了会出问题。比如学习率的选择,过大或过小的学习率会导致模型发散或者掉入局部最优点。然而并没有永远合适的学习率,这些都需要根据观察训练过程来作出合适的选择。
一个快速定位模型问题的方法就是通过观察模型训练过程的变化,这也是TensorBoard应用的场景。我们可以决定在TensorBoard中展示哪些值(这取决于你写的代码),这些值随训练时间的历史变化就会被记录下来,供给我们分析判断,帮助我们把复杂的神经网络训练过程进行可视化,使得我们可以更好地理解,调试,并优化程序。
2. TensorBoard的用法:
如何启动TensorBoard?
Linux系统下命令行执行tensorBoard
tensorboard --logdir summaries 注意: logdir后面没有等号。
# --logdir后面是tensorflow的summary所在的目录,这里面包含event文件,保存了TensorBoard展示需要的数据,而这些数据就是TensorFlow中的summary数据。
所有数据都被保存在如下event文件中:
==这些数据在程序上解读的话,就是对应代码里放进tensorflow summary中的数据。
tf.summary
,
如果想要从TensorBoard观察某个变量的情况,就必须将它封装为tf.summary对象
。
启动后,终端会输出查看TensorBoard的url, 在Web浏览器中打开该url就可以看到TensorBoard的面板了。
TensorFlow中有多种类型的summary. 我们可以利用tf.name_scope对不同的summary进行分组,那些拥有相同name scope的变量summary将会在TensorBoard中展示为同一行,方便我们查看.
- scalar类型的summary:
tf.summary.scalar
1. Scalar类型的summary
以下是例子,来自
# 这个例子是给要记录的scalar都创建一个placeholder.
#summary having the same name_scope will be displayed on the same row
with tf.name_scope('performance'):
# summaries need to be displayed.
# Whenever you need to record the loss, feed the mean loss to this placeholder
tf_loss_ph = tf.placeholder(tf.float32,shape=None,name='loss_summary')
# Create a scalar summary object for the loss so it can be displayed
tf_loss_summary = tf.summary.scalar('loss', tf_loss_ph)
# 这种用performance的方法和david facenet的作用是一样的:
# 用david方式改写如下:
tf_loss_summary = tf.summary.scalar('/performance'+'loss', tf_loss_ph)
从上面的图可以看出,这个不单单是tf.summary.scalar('loss', tf_loss_ph)
,而是把这个再次赋值给一个新的变量,叫做tf_loss_summary
.
回头补充一个可以直接把图中的变量封装为Summary的例子,也就是不用placeholder,而是直接用session.run的例子
然后,我facenet程序的代码如下:
# 确实也定义了一个placeholder如下:
learning_rate_placeholder = tf.placeholder(tf.float32, name='learning_rate')
# learning_rate是经过指数衰减后的learning_rate_placeholder
learning_rate = tf.train.exponential_decay(learning_rate_placeholder, global_step, args.learning_rate_decay_epochs*args.epoch_size, args.learning_rate_decay_factor, staircase=True)
# 然后把它放到tf.summary里,但是不额外定义变量
tf.summary.scalar('learning_rate', learning_rate) # 如果这么定义的话,回头tensorboard web界面里有没有learning_rate的信息啊?
但是,经过检查facenet的代码我发现,他们的代码把loss也添加到了tf.summary里,但是不同的是,他们专门定义个函数干这个事情
在facenet.py
文件的train
函数里,
def train(total_loss, global_step, optimizer, learning_rate, moving_average_decay, update_gradient_vars, log_histograms=True):
# Generate moving averages of all losses and associated summaries.
loss_averages_op = _add_loss_summaries(total_loss)
_add_loss_summaries
的定义:
def _add_loss_summaries(total_loss):
"""
Generates moving average for all losses and associated summaries for
visualizing the performance of the network.
Args:
total_loss: Total loss from loss().
Returns:
loss_averages_op: op for generating moving averages of losses.
# 为什么要generating moving average of losses?
# 之前对学习率有一个指数衰减,这边对Loss有个指数移动平均
"""
# Compute the moving average of all individual losses and the total loss.
loss_averages = tf.train.ExponentialMovingAverage(0.9, name='avg') # 好像是得到了loss的平均值,也可能只是指定用多大的程度来移动平均.
"""
{ExponentialMovingAverage}<tensorflow.python.training.moving_averages.ExponentialMovingAverage object at 0x7faa929d0898> 这说明loss_averages是一个ExponentialMovingAverage object,然后这个对象就应该有自己的方法。下面的.apply就是他的方法。
"""
# 之前,在train_softmax.py里有这么一句:
tf.add_to_collection('losses', specific_loss)
# 然后facenet train()函数里有一个:
losses = tf.get_collection('losses') # 这么一get就把刚才的添加进去的cross_entropy拿到了。
"""
losses={list:1}[<tf.Tensor 'cross_entropy:0' shape=() dtype=float32>]
"""
# 定义这个操作,但是还没真正执行操作
loss_averages_op = loss_averages.apply(losses + [total_loss])
"""
{Operation}
name: "avg"
op: "NoOp"
input: "^avg/AssignMovingAvg"
input: "^avg/AssignMovingAvg_1"
"""
##############################################################
"""
然后才真正开始在这个函数里,进行将loss保存到tf.summary的操作, 如下所示:
"""
# Attach a scalar summmary to all individual losses and the total loss;
# do the same for the averaged version of the losses.
for l in losses + [total_loss]:
# Name each loss as '(raw)' and name the moving average version of the loss as the original loss name
tf.summary.scalar(l.op.name +' (raw)', l)
# l.op进入到下面后确实有个name.所以l.op.name = 'cross_entropy'
# 所以,没平均的是,cross_entropy_raw
tf.summary.scalar(l.op.name, loss_averages.average(l))
# 平均后的loss_averages.average(l)是cross_entropy.
所以,_add_loss_summaries
的作用就是把loss
乃至loss
的平均都给添加到tf.summary.scalar
里。但是,这里和tf_loss_summary = tf.summary.scalar('loss', tf_loss_ph)
这种的不同,因为我们没再新增一个变量tf_loss_summary
.
但是,我们确实把cross_entropy
以及total_loss(加上正则化loss的那个)
都添加到tf.summary.scalar
里面了
2.1 可视化scalar(标量)数据类型:
这块可视化的就是:scalar值,可以看作是Shape为(1,1)的值,不对吧,应该是形状为(,)的值。反正就是标量数据,就比如: 分类准确率(accuracy), 那loss应该也是在这看的,对不对?
- 观察神经网络最后一层梯度的L2范数
- 观察training loss
- 观察validation loss
第一点:观察NN最后一层梯度的L2范数
梯度的范数是判断权重是否被正常更新的一个重要指标。它的值很小,说明存在梯度消失问题,过大则暗示梯度爆炸发生的潜在风险。
我想把这个加到我的train_softmax
里,然后看看权重被正常更新时候,这个梯度范数的规律,再看看出现NaN等不正常时候,这个范数的规律。
我们先看看,之前的作者怎么来把这个添加到tf.summary
里面的.
# Gradient norm summary
for g, v in grads_and_vars:
第二、三点:
if the validation loss going to increase that means overfitting. You should set the number of epochs as high as possible and avoid the overfitting. 一旦validation loss开始增加,就说明在训练集上已经过拟合了,也就是太过适应训练集,但是却不够泛化,在validation set上就出事了,出事的表现就是validation loss增加。
2.2 利用Histogram来可视化Vector或者数据集合 记录数据的直方图
在facenet.py的程序里,我们是这么利用histogram的:
if log_histograms:
for var in tf.trainable_variables():
# 以第一个var为例:
# <tf.Variable 'InceptionResnetV1/Conv2d_1a_3x3/weights:0'
# shape=(3, 3, 3, 32) dtype=float32_ref>
# 第二个:
# {RefVariable}
# <tf.Variable 'InceptionResnetV1/Conv2d_1a_3x3/BatchNorm/beta:0'
# shape=(32,) dtype=float32_ref>
# 这个的name是:var.op.name
#'InceptionResnetV1/Conv2d_1a_3x3/BatchNorm/beta'
tf.summary.histogram(var.op.name, var)
"""
有了这个后,然后,所有的可训练变量的直方图都会被记录
# 不要把鼠标放在空白处,然后再点击run to cursor.
#
那样的话,容易让程序误人为是要跑完整个程序。起不到debug的效果了该。
"""
# 还有另外一个,要保存的grad,应该可以通过观察grad,然后来确定梯度是不是爆炸或者弥散。
if log_histograms:
for grad, var in grads: # 注意到这块取到的grad,不是每次都有值。有的时候确实就是None.
if grad is not None:
tf.summary.histogram(var.op.name + '/gradients', grad)
#第一个有值的grad
#Tensor("gradients/AddN_493:0", shape=(3, 3, 3, 32), dtype=float32)
#第二个有值的grad
#Tensor("gradients/AddN_492:0", shape=(32,), dtype=float32)
2.3 通过Histogram来比较不同的权重初始化对于神经网络训练时权重更新的影响
其他
summary_op = tf.summary.merge_all()
# 从TensorFlow官网:
# Merges all summaries collected in the default graph.
# 从下面这个截图可以看出:
输入args: key---代表的是:GraphKey used to collect the summaries,用来收集summaries的GraphKey. 注意到默认的是: Defaults to GraphKeys.SUMMARIES.
"""
应该就是之前往Summary里添加了什么,这会把那些summaries都合并到一起.
"""
返回的东西:(Returns)
If no summaries were collected, returns None. Otherwise returns a scalar Tensor of type string containing the serialized Summary protocol buffer resulting from the merging. 是字符串描述的一系列Summary protocol buffer(描述了具体怎么合并,合并了哪些个!).
2.4 如何检测variables和gradients?
怎么在Tensorboard里面可视化变量和梯度的norms
one really useful visualisation you can do while training a network is visualise the norms of the variables and gradients.
how are they useful? some random things that immediately come to mind include the fact that(你可能会立刻想到一些随机事物包括以下事实)…
- diverging norms of variables might mean you haven’t got enough regularisation
发散的变量范数可能意味着你没有足够的regularization. - zero norm gradient means learning has somehow stopped.
零范数梯度意味着学习已经以某种方式停止了 - exploding gradient norms means learning is unstable and you might need to clip
爆炸的梯度范数意味着学习不稳定,你可能需要削减。
可视化Summary数据
再打开一个之前train_softmax.py
的event
文件,然后看看我对Summary部分都改变了什么?