Tensorflow中softmax的问题

6
我有一个Tensorflow多分类器,使用tf.nn.softmax计算概率时会出现nan或inf。如下所示的代码段(logits的形状为batch_size x 6,因为我有6个类别且输出是one-hot编码)。batch_size是1024。
logits = tf.debugging.check_numerics(logits, message='bad logits', name=None)
probabilities = tf.nn.softmax(logits=logits, name='Softmax')
probabilities = tf.debugging.check_numerics(probabilities, message='bad probabilities', name=None)

分类器在最后一个语句中失败,因为它在probabilities中发现了nan或inf。logits很干净,否则第一个语句也会失败。 从我所读到的关于tf.nn.softmax的内容来看,它可以处理logits中非常大和非常小的值。我已经在交互模式下验证过了。
>>> with tf.Session() as s:
...   a = tf.constant([[1000, 10], [-100, -200], [3, 4.0]])
...   sm = tf.nn.softmax(logits=a, name='Softmax')
...   print(a.eval())
...   print(sm.eval())
...
[[1000.   10.]
 [-100. -200.]
 [   3.    4.]]
[[1.         0.        ]
 [1.         0.        ]
 [0.26894143 0.7310586 ]]

我尝试将logits中的值修剪,整个过程现在可以工作了。请参见下面修改后的片段。

logits = tf.debugging.check_numerics(logits, message='logits', name=None)
safe_logits = tf.clip_by_value(logits, -15.0, 15.0)
probabilities = tf.nn.softmax(logits=safe_logits, name='Softmax')
probabilities = tf.debugging.check_numerics(probabilities, message='bad probabilities', name=None)

在第二个语句中,我将`logits`中的值剪切到-15和15之间,这在某种程度上防止了softmax计算中出现nan/inf。因此,我能够解决手头的问题。
然而,我仍然不明白为什么这种剪辑起作用?(我应该提到,在-20和20之间剪辑无效,并且模型在probabilities中出现naninf时失败)。
有人可以帮助我理解为什么会这样吗?
我正在使用tensorflow 1.15.0,在64位实例上运行。

你如何计算“logits”? - rvinas
logits是前一层的输出(就在头部之前)。 - Nik
我尝试了您的代码,使用tensorflow 2.0,没有出现您所说的错误。 - lazy
很难在样本上重现这个错误。在此发生之前,作业需要运行100K步。 - Nik
1个回答

3

首先要查看的是数值本身,这一点您已经完成了。其次要查看的是梯度。即使该值似乎合理,如果梯度非常陡峭,反向传播将最终爆炸梯度和值。

例如,如果logits由类似于log(x)的函数生成,那么x为0.001将生成-6.9。看起来相当温和。但梯度为1000!在反向传播/前向传播期间,这会很快导致梯度和值爆炸。

# Pretend this is the source value that is fed to a function that generates the logit. 
>>> x = tf.Variable(0.001)

# Let's operate on the source value to generate the logit. 
>>> with tf.GradientTape() as tape:
...   y = tf.math.log(x)
... 

# The logit looks okay... -6.9. 
>>> y
<tf.Tensor: shape=(), dtype=float32, numpy=-6.9077554>

# But the gradient is exploding. 
>>> tape.gradient(y,x)
<tf.Tensor: shape=(), dtype=float32, numpy=999.99994>
>>> 

对Logit进行剪裁似乎可以集中产生较小的值以供给softmax,但这可能不是为什么它有所帮助(实际上,softmax可以轻松处理值为tf.float32.max的logit,因此logit的值很可能不是问题)。真正可能发生的是,当您将其剪裁到15时,当Logit本应为20且具有爆炸性梯度时,您也将梯度设置为零。因此,剪切值还引入了被截断的梯度。

# This is same source variable as above. 
>>> x = tf.Variable(0.001)

# Now let's operate with clipping. 
>>> with tf.GradientTape() as tape:
...   y = tf.clip_by_value(tf.math.log(x), -1., 1.)
... 

# The clipped logit still looks okay... 
>>> y
<tf.Tensor: shape=(), dtype=float32, numpy=-1.0>

# What may be more important is that the clipping has also zeroed out the gradient
>>> tape.gradient(y,x)
<tf.Tensor: shape=(), dtype=float32, numpy=0.0>

您的NaN/inf是在model.fit期间还是在model.predict / model.evaluate / model.__call__期间发生的? - Yaoshiang
是的,这会支持我假设的想法。虽然有时您可以打印logit并将它们视为正常,在反向传播期间,梯度在某些时候变得非常大,比如1000,然后爆炸成inf/nan。可能不是logit触发了这个问题,它可能是上游的问题。当这种情况发生时,我通常尝试降低LR,并有时截断梯度,这两种方法都比较便宜,可以解决问题。添加批量规范化可以帮助解决该问题。然后我添加一个检查点回调,以便我可以使模型返回到即将爆炸的状态。 - Yaoshiang
如果你真的想深入了解它,你可以使用tf.GradientTape构建自定义训练循环,并直接记录梯度。https://keras.io/guides/writing_a_training_loop_from_scratch/#using-the-gradienttape-a-first-endtoend-example - Yaoshiang
哦,是的,我会做的第一件事就是在各个地方添加tf.debugging.check_numerics。这将帮助您隔离实际触发nan的操作。 - Yaoshiang
谢谢。我会尝试你提到的一些建议。 - Nik
显示剩余2条评论

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