数值稳定的 softmax

Numerically stable softmax

提问人:Abhishek Bhatia 提问时间:3/5/2017 最后编辑:desertnautAbhishek Bhatia 更新时间:10/25/2022 访问量:51515

问:

下面有没有一种数值稳定的方法来计算 softmax 函数? 我得到的值在神经网络代码中成为 Nans。

np.exp(x)/np.sum(np.exp(y))
蟒蛇 numpy nan 科学计算 softmax

评论

3赞 Alex Riley 3/5/2017
这里的答案显示了计算softmax的更好方法:stackoverflow.com/questions/34968722/softmax-function-python
3赞 Paul Panzer 3/5/2017
@ajcr 此链接上接受的答案实际上是糟糕的建议。Abhishek,OP 所做的事,即使他们最初似乎不明白为什么是正确的做法。除了溢出之外,softmax 中没有数字困难的步骤。因此,将所有输入向左移动,同时在数学上是等效的,消除了溢出的可能性,因此在数值上也是一种改进。
0赞 Alex Riley 3/5/2017
是的,尽管该公认答案的作者在评论中承认减去最大值不会引入“必要项”,但实际上可以提高数值稳定性(也许应该编辑该答案......无论如何,数值稳定性的问题在其他几个答案中都得到了解决。@AbhishekBhatia:您认为该链接是否令人满意地回答了您的问题,或者这里的新答案会有所帮助?

答:

-5赞 amir hossein hajavi 3/5/2017 #1

计算 softmax 函数没有错,因为它是你的情况。问题似乎来自梯度爆炸或训练方法的此类问题。通过“裁剪值”或“选择正确的初始权重分布”来关注这些问题。

评论

9赞 Warren Weckesser 3/5/2017
“在你的情况下,计算softmax函数并没有错。”尝试用它来计算。softmax(800)
0赞 amir hossein hajavi 3/8/2017
如果你试图在这种规模下工作,在这种规模下做任何事情都会导致 python 中的任何事情都不稳定。
78赞 Paul Panzer 3/5/2017 #2

softmax exp(x)/sum(exp(x)) 实际上在数值上表现良好。它只有正项,所以我们不必担心失去意义,而且分母至少和分子一样大,所以结果保证在 0 到 1 之间。

唯一可能发生的事故是指数中的过度或流入不足。x 的单个元素的溢出或所有元素的下溢将使输出或多或少无用。

但是,通过使用恒等式 softmax(x) = softmax(x + c) 可以很容易地防止这种情况,该恒等式适用于任何标量 c:从 x 中减去 max(x) 会留下一个只有非正条目的向量,从而排除溢出和至少一个为零的元素排除消失的分母(某些但不是所有条目中的下溢是无害的)。

脚注:从理论上讲,灾难性事故的总和是可能的,但你需要一个荒谬的术语。例如,即使使用只能解析 3 位小数的 16 位浮点数---与“正常”64 位浮点数的 15 位小数相比---我们也需要在 2^1431 (~6 x 10^431) 和 2^1432 之间得到相差 2 倍的总和。

47赞 kmario23 3/11/2018 #3

Softmax 函数容易出现两个问题:溢和下溢

溢出:当非常大的数字近似infinity

下溢:当非常小的数字(数字行中接近零)近(即四舍五入)为zero

为了在进行softmax计算时解决这些问题,一个常见的技巧是通过从所有元素中减去其中的最大元素来移动输入向量。对于输入向量,定义如下:xz

z = x-max(x)

然后取新(稳定)向量的softmaxz


例:

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()


有关详细信息,请参阅深度学习中的数值计算一章。

评论

0赞 Adam Wilson 12/23/2021
我不确定减去最大值是否是处理下溢的最佳方法。但正如保罗的回答所暗示的那样,下溢不是一个问题。
9赞 David Parks 11/30/2019 #4

扩展 @kmario23 的答案以支持 1 维或 2 维 numpy 数组或列表。2D 张量(假设第一个维度是批量维度)在通过 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 numpy
test2 = np.array([[12345, 67890, 99999999], # 2D numpy
                  [123, 678, 88888888]])    #
test3 = [12345, 67890, 999999999]           # 1D list
test4 = [[12345, 67890, 999999999]]         # 2D list

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.]]

评论

1赞 Nihar Karve 5/4/2020
这对我来说仍然不足
0赞 David Parks 5/4/2020
我已经使用它很长一段时间没有问题。您确定没有 NaN 或 Infs 作为输入吗?
1赞 Nihar Karve 5/5/2020
我现在明白了 - 即使函数正常工作,也会抱怨大值的下溢。这确实是最好的解决方案。np.seterr(all='raise')
1赞 Allohvk 10/22/2022
这是一个黄金答案,就像一个魅力。请强烈注意这个解决方案,因为我尝试了六种以上的所谓softmax解决方案,这是唯一真正有效的解决方案