『教程』TensorFlow代码调试模块tfdbg

转载自公众号机器之心

 

Debugging 是程序员必备技能,TensorFlow 程序员也不例外。然而 TensorFlow 的运行模式是先构造一张 graph,再执行 session.run(),这就为调试带来一些困难。普通调试工具如 pdb 只能看到 graph 外部的变量和控制流程,无法深入 graph 内部一探究竟。

 

TensorFlow debugger(简称 tfdbg)是 TensorFlow 的专用调试器,它可以让你看到运行 TensorFlow graph 时的内部结构体和状态。有了这个神器就如同调试普通 Python 程序一样调试 TensorFlow 程序了!

 

这里看个简单例子(lms.py),使用 LMS 算法估计线性滤波器权值。LMS 是数字信号处理中最基本的自适应滤波算法,结构简单,计算量低,便于硬件实现,适用于实时信号处理场景,但学习速率选取不当时容易造成发散,出现 Inf 和 NaN 值。以下程序就存在这个问题:

import numpy as np
import tensorflow as tf
from tensorflow.python import debug as tfdbg

H = 128    # 滤波器长度
L = 10240  # 输入信号长度
miu = 1    # 学习速率

# 载入滤波器权值
filter = np.random.randn(H)
# 载入信号值
sig_in = np.random.randn(L)

weights = tf.Variable(tf.zeros([H], dtype=tf.float32))  # 训练权值
input_vec = tf.placeholder(tf.float32, shape=(H))       # 输入信号窗口
prod = tf.reduce_sum(tf.multiply(input_vec, weights))   # 输出信号
desired = tf.reduce_sum(tf.multiply(input_vec, filter)) # 期望信号
err = tf.nn.l2_loss(desired - prod)                     # 误差信号
opt = tf.train.GradientDescentOptimizer(miu).minimize(err) # 梯度下降优化器
mse = tf.nn.l2_loss(weights - filter)                   # 训练权值与预期权值的 MSE

with tf.Session() as sess:
  sess = tfdbg.LocalCLIDebugWrapperSession(sess)                      # 被调试器封装的会话
  sess.add_tensor_filter("has_inf_or_nan", tfdbg.has_inf_or_nan)      # 调试器添加过滤规则
  tf.global_variables_initializer().run()                             # 变量初始化
  print('Initialized!')  for step in xrange(L - H):
    sess.run(opt, feed_dict={input_vec:sig_in[step:(step+H)]})        # 输入信号窗口每次滑动一个单位
    print sess.run(mse)                                               # 打印输出,观察训练权值是否收敛到与预期权值一致

使用 TensorFlow 1.3.0 运行上述程序。

 

进入交互式调试界面,这里可以输入调试命令,常用的有:

tfdbg> run

运行一次 session.run(),并将所有内部 tensor 导出,界面如下:

这时可以用鼠标点击“Tensor name”,查看相应的 tensor 详情(等价为输入命令:pt tensor_name)。下图为点击“Mul_1/y:0”之后的界面:

可以按键盘上“Page Up/Page Down”按键来翻页显示,tfdbg> 后输入“@数字”可以直接跳到对应的元素位置,如 “@125” 后,界面跳转如下:

这时可以查看 Mu_1/y:0[125] 元素的值。

 

我们如果对 graph 中某个 node 感兴趣,可以使用 ni 命令查看:

上图显示 Mul:0 节点对应 Op 为 Mul,运行时使用 gpu:0 设备,有两个输入,一个为 placeholder,一个为 Variable;下一个节点为 Sum。通过这些信息可以辨识调试器中某个特定节点对应程序中代码位置。为了提高辨识度,在程序中使用 tf api 构建 graph 时,最好加入 name 参数。

 

通过 ls 命令可以查看创建节点的框架源码:

从上图看到源码和创建的节点统计值,鼠标点击源码则自动跳转到创建代码处,适合跟踪框架代码。

 

前面手动查找 Tensor 值的方式比较低效,还可以使用过滤器帮助查找异常值。在代码中我们已经创建了一个名为“has_inf_or_nan” 的过滤器,在运行时指定参数:run -f has_inf_or_nan 则使用该过滤器,检查运行时的 tensor 出现 NaN 或 Inf 的情况。

调试器运行到第 30 轮时发现 L2Loss_1:0 tensor 中出现了 NaN/Inf,程序中断,我们可以输入“pt L2Loss_1:0” 打印这个 tensor:

它的值为 Inf,为什么会变为 Inf 呢?我们继续跟踪它前一级 tensor(通过命令 “ni L2Loss_1:0” 得知前一个 tensor 为 sub_1,再使用 “pt sub_1” 命令打印 sub_1 的值)

发现这些值都非常大,再计算 L2Loss 会超过 float32 能表示的最大值,所以生成了 Inf。

继续向前跟踪 tensor,可以找到这些异常大的值来源为 Variable,即训练的权值。这些值为什么会变得这么大?一定是迭代更新的步子太大造成发散。找到了原因,解决起来就很简单了,我们将代码第 7 行学习速率(miu = 1)调小一些,比如 0.01,保存后再次运行(先 python lms.py 进入 tfdbg> ,再运行 run -f has_inf_or_nan),这时会发现程序一直运行到结束,不会中断,说明没有 NaN/Inf 产生,bug 已经消除。

 

通过上述例子,相信童鞋们可以掌握 tfdbg 基本用法,并可以举一反三调试自己程序中的 bug。

 

 

最近 TensorFlow 又有一大利好,开始支持 Eager Execution 模式,可以直接执行 tf api,无需使用 session.run(),这样方便了程序调试。不过该功能尚在开发阶段,不可避免会踩到一些坑。愿意尝鲜的童鞋可以体验下:

 

Docker 安装:docker pull tensorflow/tensorflow:nightly

PIP 安装:pip install tf-nightly

测试程序:

import tensorflow as tf
import tensorflow.contrib.eager as tfe
tfe.enable_eager_execution()
x = tf.add(1, 1)
y = tf.constant([1, 2, 3])
z = x + y
print(z)
 

参考

【1】Debugging TensorFlow Programs, https://www.tensorflow.org/programmers_guide/debugger

【2】TensorFlow Eager Execution, https://github.com/tensorflow/tensorflow/blob/master/tensorflow/contrib/eager/python/g3doc/guide.md

 

posted @ 2017-11-10 00:07  叠加态的猫  阅读(9060)  评论(0编辑  收藏  举报