如何在TensorFlow中调试NaN值?

59

我正在运行TensorFlow,我的代码中出现了一个NaN值。我想知道是什么导致了这个问题,但我不知道该如何解决。在“普通”的过程式程序中,我通常会在操作执行之前写一个print语句以便找到问题所在。但在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(),但它没有说明如何使用(加上文档似乎并没有很有帮助)。有人知道如何使用吗?


因为有评论指出数据可能存在问题,所以我正在使用标准的MNIST。但是,我正在计算一个正值(成对欧几里得距离),然后取其平方根。因此,我不认为数据具体会是问题所在。

9个回答

27

有几个原因会导致你得到NaN结果,通常是由于学习率过高,但还有其他很多可能原因,比如输入队列中的数据损坏或计算出0的日志。

无论如何,像你描述的那样使用print进行调试是行不通的(因为这只会打印图中张量信息而不打印任何实际值)。

然而,如果在构建图时使用tf.print作为一个op(tf.print),当执行图时,你将获得实际输出的值(并且观察这些值以调试和理解你的神经网络的行为是一个好的练习)。

然而,你并没有完全正确地使用print语句。这是一个op,所以你需要传递给它一个张量,并请求一个结果张量,你需要在后面执行图时使用。否则,op不会被执行,也不会发生任何打印。试试这个:

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)

8
为什么在第二个Z是数据的情况下,还需要通过第一个Z?实质上,“tf.Print”API很困惑。为什么我们需要两个输入参数才能打印一个单一的东西? - Charlie Parker
当第一个张量Z被评估时,张量[Z]的列表将被打印出来。有时候,人们可能想要打印出不同的东西。 - holdenlee
3
这是一个针对某个张量 x 的有用的代码片段: DEBUGGING = False x = x if not DEBUGGING else tf.Print(x, [x], 'Value of x: ')如果 DEBUGGING 为假,则 x 不会被改变。否则,将使用 TensorFlow 的 tf.Print 函数打印出 x 的值。 - Toke Faurby

17

以前,我发现定位nans和infs出现的位置比修复错误要困难得多。作为对@scai答案的补充,我想在这里添加一些要点:

使用debug模块,可以通过导入来实现:

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 来查找NaN或Inf出现的位置。第一个位置就是导致灾难的地方。通过变量名称,你可以追踪代码中的起源。

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


你是否遇到了使用tf_debug插件进行调试时程序超级缓慢的问题?此外,我无法通过终端命令运行tf_debug模式,只能通过pycharm调试模式来运行此调试设置。 - Moonlight Knight
此外,我需要在 LocalCLIDebugWrapperSession 中添加 ui_type="readline" 参数以使其正常工作。sess = tf_debug.LocalCLIDebugWrapperSession(sess, ui_type="readline") 参考:https://stackoverflow.com/questions/52747655/why-do-i-get-curses-error-cbreak-returned-err-when-using-tensorflow-cli-de - Moonlight Knight

11

9

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

check = tf.add_check_numerics_ops()

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

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


1
请注意,当使用优化器时,此处遗漏了一些操作-- https://github.com/tensorflow/tensorflow/issues/2288 - Yaroslav Bulatov

5
首先,你需要正确检查输入数据。在大多数情况下,这是问题的原因。但当然并不总是如此。
我通常使用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。不幸的是,我不能只运行Z.sess(或者我不知道如何运行)。 - Charlie Parker
你可以通过以下方式运行一些操作:op1_answer,op2_answer,opN_answer = sess.run([op1,op2,opN],feed_dict = {etc..}) - Alex Joz
谢谢!我的输入数据有空行……你的回答解决了我的问题。 - CyberPlayerOne

5

对于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) - Stefan Falk

4

前向过程中出现的NAN是一件事情,而后向过程中出现的NAN是另一件事情。

步骤0:数据

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

步骤1:前向

切换到CPU环境以获取更详细的回溯信息,并在计算梯度之前通过 loss = tf.stop_gradient(loss) 测试仅前向传递,看看是否可以连续运行多个批次而不出现错误。如果出现错误,则可能存在以下几种类型的错误和解决方法:

  1. 交叉熵损失函数记录了0(请参见该答案
  2. 0/0问题
  3. 超出类别问题,如在此处发布的问题
  4. 在某些可疑位置尝试 tensor = tf.check_numerics(tensor, 'tensor')
  5. 按照此答案编写的尝试 tf_debug

步骤2:后向

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

  1. 尝试非常小的学习速率
  2. 通过简单计算(如全连接)替换复杂的代码块,使输入和输出具有相同的形状,以放大错误所在的位置。您可能会遇到类似于此问题的反向错误。

顺便说一句,始终确保每个张量的形状是需要的。您可以尝试输入固定大小的批次(删除余数),并将特征张量(其中图形从数据集接收数据)重塑为您希望它们成为的形状(否则第一维有时会是None),然后打印图形中非常张量的形状与固定数字。

Andréj Karpathy的神经网络训练/调试配方是一个关于训练/调试神经网络的好文章。


1
我通过删除网络模型中所有的辍学层来解决了NaN问题。我怀疑网络中的某个单元(神经元?)失去了太多的输入连接(因此在辍学后为零),因此当信息被传递时,它的值为NaN。我不明白为什么在每个具有超过一百个单元的层上使用dropout=0.8会反复发生这种情况,所以问题可能是由于不同的原因而得到解决。无论如何,注释掉辍学层解决了我的问题。

编辑:糟糕!我意识到我在最后输出层之后添加了一个辍学层,该层包含三个单元。现在这更有意义了。所以,不要那样做!


1

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接