如何在 TensorFlow 中调试 NaN 值?

Posted

技术标签:

【中文标题】如何在 TensorFlow 中调试 NaN 值?【英文标题】:How does one debug NaN values in TensorFlow? 【发布时间】:2016-12-13 02:52:50 【问题描述】:

我正在运行 TensorFlow,我碰巧有一些东西会产生 NaN。我想知道它是什么,但我不知道该怎么做。主要问题是,在“正常”的程序程序中,我只会在执行操作之前编写一个打印语句。 TensorFlow 的问题是我不能这样做,因为我首先声明(或定义)图形,因此将打印语句添加到图形定义中没有帮助。是否有任何规则、建议、启发式方法或任何东西来追踪可能导致 NaN 的原因?


在这种情况下,我更准确地知道要看哪一行,因为我有以下内容:

Delta_tilde = 2.0*tf.matmul(x,W) - tf.add(WW, XX) #note this quantity should always be positive because its pair-wise euclidian distance
Z = tf.sqrt(Delta_tilde)
Z = Transform(Z) # potentially some transform, currently I have it to return Z for debugging (the identity)
Z = tf.pow(Z, 2.0)
A = tf.exp(Z) 

当这一行出现时,我认为它返回 NaN,正如我的摘要作者所声明的那样。为什么是这样?有没有办法至少探索 Z 平方根后的值?


对于我发布的具体示例,我尝试了tf.Print(0,Z),但没有成功,它什么也没打印。如:

Delta_tilde = 2.0*tf.matmul(x,W) - tf.add(WW, XX) #note this quantity should always be positive because its pair-wise euclidian distance
Z = tf.sqrt(Delta_tilde)
tf.Print(0,[Z]) # <-------- TF PRINT STATMENT
Z = Transform(Z) # potentially some transform, currently I have it to return Z for debugging (the identity)
Z = tf.pow(Z, 2.0)
A = tf.exp(Z) 

我实际上不明白tf.Print 应该做什么。为什么需要两个参数?如果我想打印 1 张量,为什么需要通过 2?对我来说似乎很奇怪。


我正在查看函数tf.add_check_numerics_ops(),但它没有说明如何使用它(而且文档似乎不是很有帮助)。有谁知道这个怎么用?


因为我有 cmets 处理数据可能不好,所以我使用标准 MNIST。但是,我正在计算一个正数(成对的欧几里德距离),然后对其求平方根。因此,我看不出数据具体会成为什么问题。

【问题讨论】:

【参考方案1】:

在前向过程中出现的 NAN 是一回事,而在后向过程中出现的 NAN 又是另一回事。

步骤 0:数据

确保使用 NumPy 工具准备的数据集中没有极端输入,例如 NAN 输入或负标签,例如:assert not np.any(np.isnan(x))

第一步:前进

切换到 CPU 环境以获得更详细的回溯,并在计算梯度之前仅通过 loss = tf.stop_gradient(loss) 测试前向传递,看看您是否可以运行多个批次而没有错误。如果发生错误,有几种潜在的错误和方法:

    日志中的交叉熵损失函数为0(请参考this answer) 0/0 问题 here 发布的课外问题。 在一些可疑的地方尝试tensor = tf.check_numerics(tensor, 'tensor')。 尝试tf_debug,如this answer 中所写。

第二步:后退

如果一切顺利,删除loss = tf.stop_gradient(loss)

    尝试非常小的学习率 通过简单的计算(如全连接)替换复杂的代码块,输入和输出形状相同,以放大错误所在的位置。您可能会遇到像this 这样的落后错误。

顺便说一句,确保每个张量的形状都符合要求总是有帮助的。您可以尝试输入固定大小的批次(删除剩余部分)并重塑特征张量(图形从数据集接收数据的位置),如您所期望的那样(否则第一个维度有时会是无),然后打印形状图中具有固定数字的张量。

【讨论】:

【参考方案2】:

对于 TensorFlow 2,在您的代码中注入一些 x=tf.debugging.check_numerics(x,'x is nan')。如果 x 有任何不是数字 (NaN) 或无穷大 (Inf) 的值,它们将抛出 InvalidArgument 错误。

哦,对于下一个在寻找 TF2 NaN 问题时发现此问题的人,我的情况证明是一个爆炸梯度。梯度本身达到 1e+20,这还不是 NaN,但是将其添加到变量中结果变得太大了。我做的诊断是

gradients = tape.gradient(loss, training_variables)
for g,v in zip(gradients, training_variables):
  tf.print(v.name, tf.reduce_max(g))
optimizer.apply_gradients(zip(gradients, training_variables))

这揭示了过大的数字。在 CPU 上运行完全相同的网络运行良好,但在我的工作站中的 GTX 1080 TI 上运行失败,因此可能导致 CUDA 数值稳定性问题成为根本原因。但由于它只是偶尔发生,所以我用胶带把整个事情录下来:

gradients = tape.gradient(loss, training_variables)
gradients = [tf.clip_by_norm(g, 10.0) for g in gradients]
optimizer.apply_gradients(zip(gradients, training_variables))

这只会将爆炸渐变剪辑到一个合理的值。对于梯度总是很高的网络,这无济于事,但由于幅度只是偶尔高,这解决了问题,现在网络在 GPU 上也能很好地训练。

【讨论】:

check_numerics() 是否在培训期间工作?文档中的示例将其包装成一个 try-catch。这是在图形模式下工作吗?另外,你为什么要分配x = check_numerics(x)【参考方案3】:

我能够通过摆脱网络模型中的所有 dropout 层来解决我的 NaN 问题。我怀疑可能由于某种原因,网络中的一个单元(神经元?)丢失了太多的输入连接(所以它在丢失后为零),所以当信息通过时,它的值为 NaN。我看不出这种情况会如何一遍又一遍地发生,dropout=0.8 在每个有一百多个单位的层上,所以问题可能是由于不同的原因而得到解决的。不管怎样,注释掉 dropout 层解决了我的问题。

编辑:哎呀!我意识到我在由三个单元组成的最终输出层之后添加了一个 dropout 层。现在这更有意义了。所以,不要那样做!

【讨论】:

【参考方案4】:

tfdbg.has_inf_or_nan 的当前实现似乎不会在遇到任何包含 NaN 的张量时立即中断。当它停止时,显示的大量张量列表按其执行顺序排序。 找到Nans 的第一次出现的一种可能的方法是将所有张量转储到一个临时目录并随后进行检查。 这是一个快速而肮脏的example 来做到这一点。 (假设NaNs 出现在前几次运行中)

【讨论】:

【参考方案5】:

我曾经发现确定 nans 和 infs 可能发生的位置比修复错误要困难得多。作为@scai 回答的补充,我想在这里补充几点:

调试模块,可以通过以下方式导入:

from tensorflow.python import debug as tf_debug

比任何打印或断言都要好得多。

您可以通过更改会话包装器来添加调试功能:

sess = tf_debug.LocalCLIDebugWrapperSession(sess)
sess.add_tensor_filter("has_inf_or_nan", tf_debug.has_inf_or_nan)

你会提示一个命令行界面,然后你输入: run -f has_inf_or_nanlt -f has_inf_or_nan 查找 nans 或 infs 的位置。第一个是灾难发生的第一个地方。通过变量名,您可以跟踪代码中的来源。

参考:https://developers.googleblog.com/2017/02/debug-tensorflow-models-with-tfdbg.html

【讨论】:

你有没有在使用这个 tf_debug 插件进行调试时让你的程序超慢的实验。另外,我不能通过终端命令运行tf_debug模式,只能通过pycharm调试模式运行这个调试设置。 此外,我需要将ui_type="readline" 参数添加到LocalCLIDebugWrapperSession 以使其工作。 sess = tf_debug.LocalCLIDebugWrapperSession(sess, ui_type="readline") 参考:***.com/questions/52747655/…【参考方案6】:

从 0.12 版开始,TensorFlow 附带一个名为 tfdbg 的内置调试器。它优化了调试此类错误数值问题(如infnan)的工作流程。文档位于: https://www.tensorflow.org/programmers_guide/debugger

【讨论】:

【参考方案7】:

首先,您需要检查您输入的数据是否正确。在大多数情况下,这就是原因。当然,并非总是如此。

我通常使用 Tensorboard 来查看训练时发生的情况。因此,您可以使用

查看每个步骤的值
Z = tf.pow(Z, 2.0)    
summary_z = tf.scalar_summary('z', Z) 
#etc..
summary_merge = tf.merge_all_summaries()
#on each desired step save: 
    summary_str = sess.run(summary_merge)
    summary_writer.add_summary(summary_str, i)

您也可以简单地评估并打印当前值:

 print(sess.run(Z))

【讨论】:

问题是它获取了 NaN 值,所以我的摘要编写器实际上退出了我的脚本,所以我看不到它。您是否建议在可能导致 NaN 的操作之前写入值? (可能在 sqrt 之前)另外,这是网络的一部分,所以我在一些火车操作上调用 sess.run。不幸的是,我不能只 sess.run Z(或者我不知道该怎么做)。 你可以通过op1_answer, op2_answer, opN_answer = sess.run([op1, op2, opN], feed_dict = etc..)运行一些操作 谢谢!我的输入数据有空行...您的回答解决了我的问题。【参考方案8】:

您可以获得 NaN 结果的原因有很多,通常是因为学习率太高,但也可能有很多其他原因,例如输入队列中的数据损坏或计算为 0 的日志.

无论如何,使用您描述的打印进行调试不能通过简单的打印来完成(因为这只会导致打印图表内的张量信息而不打印任何实际值)。

但是,如果您使用 tf.print 作为构建图形的操作 (tf.print),那么当图形被执行时,您将得到打印的实际值(观察这些值以进行调试和了解您的网络的行为)。

但是,您使用打印语句的方式并不完全正确。这是一个操作,因此您需要向它传递一个张量并请求一个结果张量,您稍后需要在执行图中使用该结果张量。否则该操作将不会被执行并且不会发生打印。试试这个:

Z = tf.sqrt(Delta_tilde)
Z = tf.Print(Z,[Z], message="my Z-values:") # <-------- TF PRINT STATMENT
Z = Transform(Z) # potentially some transform, currently I have it to return Z for debugging (the identity)
Z = tf.pow(Z, 2.0)

【讨论】:

如果第二个 Z 是数据,为什么必须通过第一个 Z?从本质上讲,tf.Print 的 API 令人困惑。为什么我们需要两个输入参数来打印一件事? 在评估第一个张量 Z 时打印张量列表 [Z]。有时可能想要打印出不同的东西。 这是一个我觉得对一些张量有用的小片段 x: DEBUGGING = False x = x if not DEBUGGING else tf.Print(x, [x], 'Value of x: ') 【参考方案9】:

看起来你可以在完成图表后调用它。

check = tf.add_check_numerics_ops()

我认为这将添加对所有浮点运算的检查。然后在会话运行函数中添加检查操作。

sess.run([check, ...])

【讨论】:

仅供参考,这会在使用优化器时遗漏一些操作 -- github.com/tensorflow/tensorflow/issues/2288

以上是关于如何在 TensorFlow 中调试 NaN 值?的主要内容,如果未能解决你的问题,请参考以下文章

tensorflow 调试模块tfdbg

在 TensorFlow 中使用 SSIM 损失返回 NaN 值

深度学习中损失值(loss值)为nan(以tensorflow为例)

TensorFlow NaN 错误?

学习笔记TF063:TensorFlow Debugger

为啥我的 tensorflow 模型输出在 x 个时期后变为 NaN?