数值稳定的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(来自 xx) 留下一个只有非正数条目的向量,排除溢出和至少一个为零的元素排除消失的分母(在某些情况下为下溢但并非所有条目都是无害的)。

脚注:理论上,灾难性事故总和是可能的,但您需要荒谬个术语。例如,即使使用只能解析 3 个小数的 16 位浮点数——与“正常”64 位浮点数的 15 个小数相比——我们需要介于 2^1431 (~6 x 10^431) 和 2 ^1432 得到 off by a factor of two 的总和。

【讨论】:

【参考方案2】:

Softmax 函数容易出现两个问题:overflowunderflow

溢出:当非常大的数字近似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的主要内容,如果未能解决你的问题,请参考以下文章

动手深度学习16- 深度学习的数值稳定性和模型初始化

算法的数值稳定性

MatLab中的数值不稳定性卡尔曼滤波器

为python中的列表定义数值稳定的sigmoid函数的最佳方法

labview怎么实现下一个数和上一个数的比较,判定数值是否在一个稳定范围

数值稳定性 梯度爆炸 梯度消失 + 模型初始化和激活函数 动手学深度学习v2 pytorch