Tensorflow-2-Tensorboard使用

1c8b: 概述

机器学习如此复杂,训练模型的时候,摸不清背后到底是如何运行的。自己设置的参数和关键变量,如果能看到在训练时的变化情况,可以为后面的参数调优阶段提供很大的便利。

Tensorboard就是这样一个工具。

它刻意将模型抽象成图像,tensor每一步是如何流动的,一目了然。

通过适当的代码设置,还能将指定的关键变量在训练时的变化情况绘制成曲线图,以便训练完成后观察变量的变化情况,来更加准确定位问题。

这篇文章简单介绍一下tensorboard的基本用法。


1c8c: Tensorboard使用

tensorboard(以下简称tb)的操作,从创建一个FileWriter开始。

在接下来的代码中,我参照CS231N课程的数据集例子,用tensorflow(以下简称tf)写了一个Logistic Regression,并以此来说明tb的基本用法。

用到的notebook我的github上可以找到。使用之前,请确保执行

conda env create -f environment_tf.yml

来创建和我一样的运行环境。如有问题,可以留言或者ISSUE。

创建好环境之后,运行

source activate tb-lab

激活conda环境,然后运行

jupyter notebook

来使用本例中的notebook

下面的示例程序用tf做了一个两层的分类网络。将下图中的数据集分类。

待分类的数据集

最终分类效果是这样的。

分类完成之后

tb的使用,大致归纳为三步:

  • 调用tf中的FileWriter将自己关注的数据写到磁盘
  • 在命令行使用tensorboard --logdir /path/to/log启动tbweb app
  • 然后在本地浏览器输入localhost:6006来使用tb

下面具体看一下怎么使用。

1.生成模型图

生成模型图只需要一句话就行。比如说,现在已经初始化好了变量,处理好了数据,部分代码如下:

# 数据初始化
N = 100 # number of points per class
D = 2 # dimensionality
K = 3 # number of classes
X = np.zeros((N * K, D)) # data matrix (each row = single example)
y = np.zeros(N * K, dtype='uint8') # class labels
for j in range(K):
    ix = range(N * j, N * (j + 1))
    r = np.linspace(0.0, 1, N) # radius
    t = np.linspace(j * 4, (j + 1) * 4, N) + np.random.randn(N) * 0.2 # theta
    X[ix] = np.c_[r * np.sin(t), r * np.cos(t)]
    y[ix] = j
# lets visualize the data:
plt.scatter(X[:, 0], X[:, 1], c=y, s=40, cmap=plt.cm.Spectral)
plt.show()

# 输入数据
inputs = tf.placeholder(tf.float32, [N * K, D])

# 数据标签
targets = tf.placeholder(tf.float32, [None, K])

softmax_w_1 = tf.Variable(tf.truncated_normal((D, h), stddev=0.1), dtype=tf.float32, name='weight_1')

softmax_b_1 = tf.Variable(tf.zeros((1, h)), dtype=tf.float32)

softmax_w_2 = tf.Variable(tf.truncated_normal((h, K), stddev=0.1), dtype=tf.float32, name='weight_2')

softmax_b_2 = tf.Variable(tf.zeros((1, K)), dtype=tf.float32)

省略部分代码......

prediction = tf.nn.softmax(logits_2)

准备开始训练的时候,加上一句

tf.summary.FileWriter('./logs/summary', session.graph)

例如,在session开始的时候添加就好。

with tf.Session() as session:
    session.run(init)
    
    merged = tf.summary.merge_all()
    writer = tf.summary.FileWriter('./logs/summary', session.graph)

我们要看整个模型的图像,因此传入session.graph对象。

这时,来到命令行,切换到notebook所在的目录,然后执行

tensorboard --logdir logs/summary

logs/summary就是代码中定义的目录路径。tb启动后会提示到浏览器用localhost:6006去打开tb应用。

在浏览器打开后,切换到GRAPH标签,看到的模型图是这样的。

这个时候其他标签还没有内容,因为还没有在代码中进行添加。

上面的图片,就是现在这个模型的原始图像,没有进行任何分组和加工。看起来很乱,有一些标签,例如slice什么的,都不知道是什么意思。

图中,

  • 每条曲线代表tensor的流向
  • 每个椭圆代表一个操作,如addmatmul
  • 每个圆角矩形代表一组操作,可以双击放大,看到这个组里面的细节
  • 整张图片可以放大缩小,随意拖动;点开每个节点,右上角都会有这个节点的详细信息

下面来加工一下,为模型图分组,让图像更加清晰有条理。

2.使用name_scope分组

调用tf.name_scope()方法来为graph分组。

我想清楚看到

  • 输入Inputs
  • 标签Targets
  • 两组Weightbias变量
  • 两个隐藏层的输出Logits_1Logits_2
  • 损失函数loss
  • 训练准确率Accuracy以及
  • 整个训练过程Train

示例代码如下。

输入Inputs

with tf.name_scope('Inputs') as scope:
    inputs = tf.placeholder(tf.float32, [N * K, D], name='inputs')

标签Targets

with tf.name_scope('Targets') as scope:
    targets = tf.placeholder(tf.float32, [None, K], name='targets')

两组Weight和bias变量

# 创建第一层的weights和bias
with tf.name_scope('Weight_Set_1') as scope:
    softmax_w_1 = tf.Variable(tf.truncated_normal((D, h), stddev=0.1), dtype=tf.float32, name='weight_1')
    softmax_b_1 = tf.Variable(tf.zeros((1, h)), dtype=tf.float32, name='bias_1')

# 创建第二层的weights和bias
with tf.name_scope('Weight_Set_2') as scope:
    softmax_w_2 = tf.Variable(tf.truncated_normal((h, K), stddev=0.1), dtype=tf.float32, name='weight_2')
    softmax_b_2 = tf.Variable(tf.zeros((1, K)), dtype=tf.float32, name='bias_2')

两个隐藏曾的输出Logits_1和Logits_2

with tf.name_scope('Logits_1') as scope:
    # 第一层的输出logits_1,使用ReLU作为activation function
    logits_1 = tf.nn.relu(tf.add(tf.matmul(X, softmax_w_1), softmax_b_1), name='logits_1')

with tf.name_scope('Logits_2') as scope:
    logits_2 = tf.add(tf.matmul(logits_1, softmax_w_2), softmax_b_2, name='logits_2')

    # 计算最终的预测分数,使用softmax计算最后的分数
    prediction = tf.nn.softmax(logits_2, name='prediction')

损失函数loss

with tf.name_scope('Loss') as scope:
    loss = tf.reduce_mean(
          tf.nn.softmax_cross_entropy_with_logits(labels=y_, logits=prediction, name='loss'))

训练准确率Accuracy

with tf.name_scope('Accuracy') as scope:
    # Determine if the predictions are correct
    is_correct_prediction = tf.equal(tf.argmax(prediction, 1), tf.argmax(y_, 1))
    # Calculate the accuracy of the predictions
    accuracy = tf.reduce_mean(tf.cast(is_correct_prediction, tf.float32), name='accuracy')

训练过程Train

with tf.name_scope('Train') as scope:
    # 使用adam最为optimizer
    optimizer = tf.train.AdamOptimizer(learning_rate).minimize(loss)

设置好这些代码之后,就可以跟第一步中一样,使用

with tf.Session() as session:
    session.run(init)

    writer = tf.summary.FileWriter('./logs/summary', session.graph)
    
......

来生成模型图。

训练完成后,再次执行tensorboard --logdir logs/summary,在浏览器中看到的模型图就是这样的了。

跟刚才相比,是不是清晰了很多,我所需的所有关注点,这个图上都有了。

这时如何为图中的节点分组。

3.添加变量详情

添加变量详情,刻意在训练结束后,直接看到变量的变化情况。上面两步中,只有GRAPH标签下面有内容,其他标签下面都没有。这一步就是往其他标签下面添加内容。

添加变量详情,使用

tf.summary.histogram()
tf.summary.scalar()

这两个方法。

比如,我想关注:

  • 两组Weight和bias的变化情况
  • 每一次的预测结果的变化情况
  • 训练过程中loss的变化情况
  • 训练过程中准确率的变化情况

我可以通过上述两个方法来添加代码。

注意在使用这两个方法之前,要为变量都加上name关键字参数。

看上面的代码中,有

softmax_w_1 = tf.Variable(tf.truncated_normal((D, h), stddev=0.1), dtype=tf.float32, name='weight_1')

最后这个name='weight_1'就是用来标识softmax_w_1的名称。

为了使结果更加准确,务必为分组中的变量都添加相应的名称标识。

名称都添加了之后,就可以使用下面的代码来添加我们想要的结果了。

# 两组Weight和bias的直方图,用histogram
w_1_hist = tf.summary.histogram('weight_1', softmax_w_1)
b_1_hist = tf.summary.histogram('bias_1', softmax_b_1)
w_2_hist = tf.summary.histogram('weight_2', softmax_w_2)
b_2_hist = tf.summary.histogram('bias_2', softmax_b_2)

# 每次预测值的直方图
logits = tf.summary.histogram('prediction', prediction)

# 损失loss和准确率accuracy的变化,这里用scalar就好,具体原因还没有深究
# 用histogram应该也没有问题的,用了scalar之后,scalar标签下面就有内容了
loss_hist = tf.summary.scalar('loss', loss)
acc_hist = tf.summary.scalar('accuracy', accuracy)

然后,在训练的时候,要调用merge_all(),并用session去执行merge,最后将merge_all()返回的对象(本例中叫summary)添加到FileWriter中才行。看着复杂,一步步看代码还是很清晰的。

# 初始化Variable
init = tf.global_variables_initializer()

# 打开session
with tf.Session() as session:
    # 运行初始化
    session.run(init)
    
    # 调用merge_all(),将前面添加的所有histogram和scalar合并到一起,方便观察
    merged = tf.summary.merge_all()
    # 同样写入到logs中
    writer = tf.summary.FileWriter('./logs/summary', session.graph)
    
    training_feed_dict = {inputs: X, targets: y_.eval()}
    
    for i in range(epochs):
        # 训练的时候,第一个传入merged对象,返回summary
        summary, _, l = session.run(
                [merged, optimizer, loss],
                feed_dict=training_feed_dict)

        if not i % 50:
            print('Epoch {}/{} '.format(i + 1, epochs), 
                  'Training loss: {:.4f}'.format(l))
            # 每50步,将summary对象添加到writer写入磁盘,最后来观察变化
            writer.add_summary(summary, i)

在这里,就可以通过观察这些变量的行为来找问题所在了。

训练完成后,同理打开tb

可以看到SCALAR标签下,就有刚才添加的lossaccuracy了。

看这两个变量的行为不错,loss一直降低,accuracy最后达到了%99左右。

这个图片时可以通过选定指定一个区域来放大的,放大之后如下图。

这是accuracy放大之后的效果图,可以更加清晰。

接下来到DISTRIBUTIONHISTOGRAM里看看。

上图是两组Weights在训练过程中的行为,这里分布均匀,挺正常。如果有问题的话,会保持不变,也就是说模型没有进行学习。

这里是两组bias的行为。这里可以看出点问题了,这个实例模型在训练的时候,虽然准确率能到%99,但是loss降到0.5左右就下不去了。

如果在notebook中多次尝试运行整个模型,会发现最后分类完成的那张图片,无法很精准的分类。

看上图,bias_2的分布有点可疑,我还不确定,但是这样异常的不均匀的情况可以给予一定的方向。我现在要做的就是就去看看什么影响了bias_2的正常更新,并且这个影响是不是引起了最终的loss瓶颈。

上图是HISTOGRAMWeights的分布,看起来还是比较正常的,分布很均匀。

再来看看bias的。

这是两个bias的分部情况,第一个看起来还不错。但是第二个就异常了。刻意点一下图像左下角那个按钮,就可以将图像放大。我放大了第二个bias的图像,如下。

简直是找不到规律,虽然不是很明白为什么会这样,但是感觉问题就出在这里了。

这里看到的是2D图像,点左边栏中的OFFSET,就能看到和Weights那一样的3D图像了。


1c8d: 总结

就像上面的例子,如果没有tb,我并不能准确知道到底是哪一个变量有异常。tb的可视化功能大大提高了训练网络的效率。

这里是tb的基本使用方式,还有很多有待研究。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,313评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,369评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,916评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,333评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,425评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,481评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,491评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,268评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,719评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,004评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,179评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,832评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,510评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,153评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,402评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,045评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,071评论 2 352

推荐阅读更多精彩内容