数值稳定的softmax
Posted
技术标签:
【中文标题】数值稳定的softmax【英文标题】:Numercially stable softmax 【发布时间】:2017-07-24 18:44:08 【问题描述】:下面有计算softmax函数的数值稳定方法吗? 我得到的值在神经网络代码中变成了 Nans。
np.exp(x)/np.sum(np.exp(y))
【问题讨论】:
这里的答案显示了计算 softmax 的更好方法:***.com/questions/34968722/softmax-function-python @ajcr 此链接上接受的答案实际上是糟糕的建议。 Abhishek,即使他们最初似乎不明白为什么是正确的做法,OP 所做的事情也是如此。除了溢出之外,softmax 中没有数值上的困难步骤。因此,在数学上等价的同时将所有输入向左移动,消除了溢出的可能性,因此在数值上是一种改进。 是的,尽管该接受答案的作者在 cmets 中承认,减去最大值不会引入“必要项”,但实际上会提高数值稳定性(也许应该编辑答案......)。无论如何,数值稳定性的问题在其他几个答案中得到了解决。 @AbhishekBhatia:您认为该链接是否令人满意地回答了您的问题,或者这里的新答案是否有益? 【参考方案1】:softmax exp(x)/sum(exp(x)) 实际上在数值上表现良好。它只有正项,所以我们不用担心失去显着性,而且分母至少和分子一样大,所以结果保证在 0 和 1 之间。
唯一可能发生的意外是指数中的溢出或不足。 x 的单个元素上溢或所有元素下溢都会使输出或多或少无用。
但是很容易通过使用对任何标量 c 成立的恒等式 softmax(x) = softmax(x + c) 来防范:减去 max(来自 x 的 x) 留下一个只有非正数条目的向量,排除溢出和至少一个为零的元素排除消失的分母(在某些情况下为下溢但并非所有条目都是无害的)。
脚注:理论上,灾难性事故总和是可能的,但您需要荒谬个术语。例如,即使使用只能解析 3 个小数的 16 位浮点数——与“正常”64 位浮点数的 15 个小数相比——我们需要介于 2^1431 (~6 x 10^431) 和 2 ^1432 得到 off by a factor of two 的总和。
【讨论】:
【参考方案2】:Softmax 函数容易出现两个问题:overflow和underflow
溢出:当非常大的数字近似为infinity
下溢:当非常小的数字(在数轴上接近零)近似(即四舍五入)为zero
为了在进行 softmax 计算时解决这些问题,一个常见的技巧是通过 从所有元素中减去其中的最大元素来移动输入向量。对于输入向量x
,定义z
,这样:
z = x-max(x)
然后取新的(稳定的)向量z
的softmax
例子:
def stable_softmax(x):
z = x - max(x)
numerator = np.exp(z)
denominator = np.sum(numerator)
softmax = numerator/denominator
return softmax
# input vector
In [267]: vec = np.array([1, 2, 3, 4, 5])
In [268]: stable_softmax(vec)
Out[268]: array([ 0.01165623, 0.03168492, 0.08612854, 0.23412166, 0.63640865])
# input vector with really large number, prone to overflow issue
In [269]: vec = np.array([12345, 67890, 99999999])
In [270]: stable_softmax(vec)
Out[270]: array([ 0., 0., 1.])
在上述情况下,我们通过使用stable_softmax()
有关详细信息,请参阅deep learning一书中的Numerical Computation一章。
【讨论】:
我不确定减去最大值是否是处理下溢的最佳方法。但正如保罗的回答所暗示的,下溢不是问题。【参考方案3】:扩展 @kmario23 的答案以支持 1 或 2 维 numpy 数组或列表(如果您通过 softmax 函数传递一批结果,这很常见):
import numpy as np
def stable_softmax(x):
z = x - np.max(x, axis=-1, keepdims=True)
numerator = np.exp(z)
denominator = np.sum(numerator, axis=-1, keepdims=True)
softmax = numerator / denominator
return softmax
test1 = np.array([12345, 67890, 99999999]) # 1D
test2 = np.array([[12345, 67890, 99999999], [123, 678, 88888888]]) # 2D
test3 = [12345, 67890, 999999999]
test4 = [[12345, 67890, 999999999]]
print(stable_softmax(test1))
print(stable_softmax(test2))
print(stable_softmax(test3))
print(stable_softmax(test4))
[0. 0. 1.]
[[0. 0. 1.]
[0. 0. 1.]]
[0. 0. 1.]
[[0. 0. 1.]]
【讨论】:
这对我来说仍然是下溢 我已经使用它很长一段时间了,没有任何问题。你确定你没有输入 NaN 或 Infs 吗? 我现在明白了 -np.seterr(all='raise')
会抱怨大值的下溢即使函数正常工作。这确实是最好的解决方案。【参考方案4】:
感谢Paul Panzer's 的解释,但我想知道为什么我们需要减去max(x)。因此,我找到了更详细的信息,希望对与我有相同问题的人有所帮助。 请参阅以下链接文章中的“最大减法是怎么回事?”部分。
https://nolanbconaway.github.io/blog/2017/softmax-numpy
【讨论】:
【参考方案5】:在您的情况下计算 softmax 函数没有任何问题。问题似乎来自爆炸梯度或您的训练方法的此类问题。通过“裁剪值”或“选择正确的初始权重分布”来关注那些问题。
【讨论】:
“按照你的情况计算 softmax 函数没有任何问题。” 尝试用它计算softmax(800)
。
在那个规模下做任何事情都会导致“inf”如果你试图在那个规模下工作,python 中的任何事情都是不稳定的。以上是关于数值稳定的softmax的主要内容,如果未能解决你的问题,请参考以下文章
为python中的列表定义数值稳定的sigmoid函数的最佳方法