原文地址:https://www.tensorflow.org/programmers_guide/debugger
TensorFlow调试器是TensorFlow专门的调试器。它提供运行的TensorFlow的图其内部的结构和状态的可见性。从这种可见性中获得的洞察力有利于调试各种模型在训练和推断中出现的错误。
这个教程将展现tfdbg的命令行界面的功能,并聚焦于如何调试在TensorFLow的模型开发中经常发生的一种错误:错误数值(nan和inf)导致的训练失败。
为了观察这个问题,在没有调试器的情况下,运行下列代码:
python -mtensorflow.python.debug.examples.debug_mnist
这个代码训练了一个简单的神经网络用来识别MNIST数字图片。请注意,准确率在第一次训练后,微微上升,但是接着停滞在了一个比较低(近机会)的水平:
抓抓脑袋,你怀疑肯定是在训练中,图中的一些节点产生了比如inf和nan这样的坏的数值。TensorFlow的计算图模型使得其不用用类似于Python的pdb等多用途的调试器来调试例如模型内部状态。tfdbg专门用来诊断这中类型的问题,并查明问题首先暴露出来的那个确切的节点。
用tfdbg包装TensorFlow会话
为了在我们的样例中添加tfdbg的支持,我们只需要添加下列三行代码,这三行代码会在提供了—debug标识的时候,用一个调试器包装器来包装会话对象:
这个包装器有会话对象相同的接口,因此启动调试不需要对于代码做其他的修改。但是包装器提供其他的功能,包括:
l在每次run()调用之前或者之后,提供一个基于终端的用户接口,让你控制运行,并检查图的内部状态
l让你可以为张量的数值注册特殊的过滤器,以此来方便问题的诊断。
在这个例子中,我们将注册一个称作tfdbg.has_inf_or_nan的张量过滤器,它仅仅确定了图中的任何一个中间张量,是否存在任何的nan或者inf数值。(这个过滤器是一个普遍的样例,我们使用debug_data模块来运行它)
defhas_inf_or_nan(datum,tensor):
returnnp.any(np.isnan(tensor))ornp.any(np.isinf(tensor))
建议:你可以写你自己定义的过滤器。参见DebugDumpDir.find()的API文档获取额外的信息。
用tfdbg调试模型训练
让我们在开启调试的情况下,再次训练模型。运行上面提到的指令,这次增加—debug标志:
python-m tensorflow.python.debug.examples.debug_mnist--debug
调试包装器会话会在将要运行第一次run()调用的时候,弹出给你,并有关于获取的张量和供给字典的信息显示在屏幕上。
这就是我们所提到的运行-启动用户接口。如果屏幕尺寸太小,不足以显示消息的整个内容,你可以调整它的大小,或者用PageUp/PageDown/Home/End键来浏览屏幕上的输出。
正如屏幕所显示的,第一次调用run()使用测试集计算准确率,也就是一个图上的前向传递。你可以输入run(或者它的简写r)来启动run()的调用。在终端上同样支持鼠标事件,你可以只点击屏幕左上角的带下划线的run来运行。
这会在run()调用刚结束的时候启动另外一个屏幕,它会显示所有这次运行中被转储的中间张量。(这些张量也可以通过在你执行run之后,运行命令lt来获取。)这被称作运行-结束用户接口:
tfdbg CLI常用指令
在tfdbg>弹出界面尝试下列命令(参考代码tensorflow/python/debug/examples/debug_mnist.py)
在第一调用run()的时候,没有有问题的数据值。你可以使用run命令或者他的缩写r,来接着运行下一个run。
建议:如果你重复的输入run或者r,你将会让run()调用进入序列模式。
你也可以使用-t参数来指定一次运行run()的次数,例如
tfdbg>run-t10
除了重复的输入run,并在每次run()之后,手动的在运行-结束用户界面搜索nan和inf,你还可以使用下列命令让调试器不用再运行前和运行后停止并弹出,并重复的执行run()调用知道nan或者inf第一次出现在图中。这类似于一些程序语言调试器的条件中断:
tfdbg>run-f has_inf_or_nan
注意:这个能成功是因为我们之前为nan和inf注册了一个名为has_inf_or_nan的过滤器(如前所述)。如果你注册了其他的过滤器,那么你也可以让tfdbg运行直到任何张量被传递给过滤器,比如:
# In python code:
sess.add_tensor_filter('my_filter',my_filter_callable)
# Run at tfdbg run-start prompt:
tfdbg>run-f my_filter
在你输入run –f has_inf_or_nan之后,你会看到如下显示,红色标题行意味着tfdbg在一个run()调用后立即停止了,因为这个run调用产生了中间张量,传递给了指定的过滤器has_inf_or_nan:
如屏幕显示所示,has_inf_or_nan过滤器在第四次运行run()的时候,第一次被传值:一个Adam优化器前馈训练在图中传递了这个值。在这次运行中,36个(总共95个)中间张量包含nan或者inf值。这些张量按照时间先后顺序被列出,并且左边显示了他们的时间戳。在列表顶部,你可以看到首先出现坏的数据值的第一个张量:cross_entropy/Log:0
查看张量的数值,点击下划线的张量的名字cross_entropy/Log:0,或者输入相同的指令
tfdbg>pt cross_entropy/Log:0
向下滚动一点,你会注意到一些分散的inf数值。如果inf和nan的例子很难用肉眼看出,你可以使用下列指令运行正则表达式搜索,并且高亮输出:
tfdbg>/inf
或者:
tfdbg>/(inf|nan)
为什么出现了这些无穷大?为了更进一步的调试,显示更多关于节点cross_entropy/Log的信息,可以通过点击顶部下划线的node_info菜单选项或者输出相同的命令:
tfdbg>ni cross_entropy/Log
你可以看到这个节点由个操作类型为Log,并且它的输入节点是softmax/Softmax。运行下列指令来更进一步的查看输入张量:
tfdbg>pt softmax/Softmax:0
检查输入张量的值,并搜索检查其是否有零:
tfdbg>/0\.000
确实有零的存在。现在清楚了,坏的数据值的源头是节点cross_entropy/Log计算零的对数值。为了找到相关的Python源码,使用-t标志的ni参数命令来追溯节点的构建:
tfdbg>ni-t cross_entropy/Log
如果你使用在屏幕顶部的可点击的node_info菜单项目,那么-t标志是默认使用的。
从追溯中可以看到,操作是在代码debug_mnist.py:105-106行创建的:
diff=y_*tf.log(y)
*tfdbg的功能使得追溯张亮和操作到Python源文件中每行变得容易。它可以用操作或者张量注释创建它们的Python文件的每行。为了使用这个功能,只需点击ni –t 指令的堆栈输出中的有下划线的数字,或者使用ps(或者print_source)命令,比如ps /path/to/source.py。下面的屏幕截图就是一个ps输出的例子:
对于tf.log的输入运用一个数值剪切可以解决这个问题:
diff=y_*tf.log(tf.clip_by_value(y,1e-8,1.0))
现在,再次尝试训练,并使用--debug:
python-m tensorflow.python.debug.examples.debug_mnist--debug
输入在弹出的tfdbg>界面,输入run –f has_inf_or_nan,从而确定没有张量被标记为含有nan或者inf的数值,这样准确率就不再停滞不前了。成功!
调试tf-learn评估器
对于在tfdbg上调试tf.contrib.learn评估器和实验的文档,请参考How to Use TensorFlow
Debugger (tfdbg) with tf.contrib.learn.
离线调试远程运行的会话
有时候,你的模型运行在远程的机器或者进程上,你无法通过终端接触到。为了在这种情况下运行模型调试,你可以使用tfdbg的offline_analyzer。它运行在转储的数据字典上。如果你运行的进程是用Python写的,你可以使用tf.dbg.watch_graph方法,在调用Session.run()的时候,配置RunOption的原型。这会导致,在Session.run()被调用时,中间的张量和运行时的图被转储到你选择的一个共享存储位置上。例如:
fromtensorflow.python.debugimportdebug_utils
# ... Code where your session and graph are set up...
run_options=tf.RunOptions()
debug_utils.watch_graph(
run_options,
session.graph,
debug_urls=["file:///shared/storage/location/tfdbg_dumps_1"])
# Be sure to use different directories for different run() calls.
session.run(fetches,feed_dict=feeds,options=run_options)
晚些时候,你可以用终端接触的环境中,你可以载入并且查看这些使用tfdbg的offline_analyzer存储在共享存储空间的转储字典上的数据。例如:
python-m tensorflow.python.debug.cli.offline_analyzer\
--dump_dir=/shared/storage/location/tfdbg_dumps_1
会话包装器
DumpingDebugWrapperSession提供一个更为容易并且灵活的方式在文件系统中,来产生可以用来离线分析的转储数据。为了使用这个,仅需要做:
# Let your BUILD target depend on "//tensorflow/python/debug:debug_py
# (You don't need to worry about the BUILD dependency if you are using a pip
# install of open-source TensorFlow.)
fromtensorflow.python.debugimportdebug_utils
sess=tf_debug.DumpingDebugWrapperSession(
sess,"/shared/storage/location/tfdbg_dumps_1/",watch_fn=my_watch_fn)
watch_fn=my_watch_fn是可调用的,它允许你在不同的Session.run()调用中,配置查看的张量,作为对于run()调用和其他状态的获取以及feed_dict的函数。参考the
API doc of DumpingDebugWrapperSession获得更多详情。
如果你的模型是用C++或者其他语言写的,你也可以修改RunOptions的debug_options域来产生可以离线查看的调试转储文件。参考the
proto definition获得更多细节。
tfdbg命令行界面的其他功能
l使用上下键浏览命令历史记录。支持基于前缀的导航。
l使用prev或者next的命令,或者点击屏幕顶部的下划线的<--和-->链接,来浏览屏幕输出的历史记录
lTab补全命令和一些命令的参数
l使用bash风格的重定位将屏幕输出写入文件。例如:
tfdbg>pt cross_entropy/Log:0[:,0:10]>/tmp/xent_value_slices.txt
常见问题
Q:在lt命令输出的左侧的时间戳反应了非调试模式的真实性能吗?
A:没有。调试器在图中插入了额外的特殊目的的调试节点,来记录中间的张量的数值。这些节点肯定会减缓图的运行。如果你对剖析你的模型感兴趣,查看TensorFlow的tfprof和其他剖析工具。
Q:我怎样把tfdbg和我在Bazel里的会话连接起来?为什么我看到一个错误:ImportError:cannot
import name debug?
A:在你构建的规则中(BUILD rule),声明依赖:"//tensorflow:tensorflow_py"和"//tensorflow/python/debug:debug_py"。你包含的第一个依赖是使用没有即使调试器支持的TensorFlow;第二个开启调试器。然后在你的Python文件中,增加:
fromtensorflow.pythonimportdebugastf_debug
# Then wrap your TensorFlow Session with the local-CLI wrapper.
sess=tf_debug.LocalCLIDebugWrapperSession(sess)
Q:tfdbg能帮助调试运行时的,类似于形状不匹配的错误吗?
A:可以。tfdbg在运行时拦截由操作产生的错误,并且在命令行界面呈现这些错误以及一些调试指引。查看例子:
# Debugging shape mismatch during matrix multiplication.
python-m tensorflow.python.debug.examples.debug_errors\
--error shape_mismatch--debug
# Debugging uninitialized variable.
python-m tensorflow.python.debug.examples.debug_errors\
--error uninitialized_variable--debug
Q:怎么才能使我的tfdbg包装的会话或者钩子仅在主线程中运行调试模式?
A:这是一个常见的使用例子,例子中的Session对象被同时的用于多个线程。典型地,子线程看管类似于运行入队操作的后台任务。你时常仅仅需要调试主线程(或者稍不频繁地,仅仅一个子线程)。你可以使用LocalCLIDebugWrapperSession的thread_name_filter关键字参数来获取这种类型的选择线程的调试。例如,你想要只调试主线程,你可以用:
sess=tf_debug.LocalCLIDebugWrapperSession(sess,thread_name_filter="MainThread$"
上面的例子依赖于一个事实,在Python中的主线程有个默认名为MainThread。
Q:我正在调试的模型很大。被tfdbg转储的数据占满了我硬盘的空闲空间。我该怎么办?
A:对于巨大的模型,比如有很多中间的张量的模型,有个别中间张量有巨大尺寸的模型和/或者图中在任何tf.while_loops中有很多迭代,这种磁盘空间问题都会发生。
有三种可能的变通方案或者解决办法:
1.LocalCLIDebugWrapperSession和LocalCLIDebugHook的构造器提供一个关键字参数dump_root,有了这个参数你可以指定tfdbg转储调试数据的路径。例如:
```python # For LocalCLIDebugWrapperSession sess =tf_debug.LocalCLIDebugWrapperSession(dump_root="/with/lots/of/space")
#
For LocalCLIDebugHook hooks =
[tf_debug.LocalCLIDebugHook(dump_root="/with/lots/of/space")]``
确保dump_root指向的目录是空的或者不存在的。tfdbg会在退出前清空这个目录。
2.在运行时,减少使用的批处理的大小。
3.使用tfbdg的过滤器选项运行命令,仅查看图中特殊的节点。例如:
tfdbg> run --node_name_filter .*hidden.*
tfdbg> run --op_type_filter Variable.* tfdbg> run --tensor_dtype_filter int.*
Q:为什么我不能再tfdbg命令行界面选择文本?
A:这是因为tfdbg命令行界面在终端中默认开启了鼠标事件。这个鼠标-任务模式重载了默认的控制台交互,其中包括文本选择。你可以使用命令mouse off或者m off来重新开启文本选择。
Q:开源的TensorFlow中的tfdbg命令行界面对于特殊平台系统的要求是什么?
A:在Mac OS X,需要ncurses库。它可以用brew install homebrew/dupes/ncurses安装。在Windows下,需要pyreadline库。如果你使用Anaconda3你可以使用比如C:\Program Files\Anaconda3\Scripts\pip.exe的指令安装pyreadline。