Exp-normalize和LogSumExp技巧

本文介绍了在softmax模型中如何处理指数运算可能导致的上溢和下溢问题,以及如何使用exp-normalize和LogSumExp技巧确保数值稳定性。通过调整计算方式,可以避免在计算概率分布和交叉熵损失时出现溢出和下溢,从而确保模型训练的正确性和效率。
摘要由CSDN通过智能技术生成

softmax模型中,会计算出模型输出然后将此输出送入交叉熵损失。
对较大值进行指数运算时,可能会出现上溢 (overflow) 或下溢 (underflow) 问题。

Exp-normalize技巧

使用softmax预测概率分布时,
y ^ j = exp ⁡ ( x j ) ∑ k = 1 n exp ⁡ ( x k ) \hat{y}_{j}=\frac{\exp \left(x_{j}\right)}{\sum^{n}_{k=1} \exp \left(x_{k}\right)} y^j=k=1nexp(xk)exp(xj)
其中 y ^ j \hat{y}_j y^j是预测的概率分布,由向量 x ∈ R n \boldsymbol{x} \in \mathbb{R}^{n} xRn决定。如果 x k x_k xk中的一些数值非常大, e x p ( x k ) exp(x_k) exp(xk)可能会大于数据类型容许的最大数字,造成上溢 (overflow),这将使这将使分母或分子变为 i n f inf inf (无穷大),最后得到的是0、 i n f inf inf n a n nan nan (不是数字) 的 y ^ j \hat{y}_{j} y^j。 在这些情况下,我们无法得到一个明确定义的交叉熵值。这种情况下可以使用exp-normalize技巧避免数值上溢。
y ^ j = exp ⁡ ( x i − b ) exp ⁡ ( b ) ∑ k = 1 n exp ⁡ ( x k − b ) exp ⁡ ( b ) = exp ⁡ ( x i − b ) ∑ k = 1 n exp ⁡ ( x k − b ) \hat{y}_{j}=\frac{\exp \left(x_{i}-b\right) \exp (b)}{\sum_{k=1}^{n} \exp \left(x_{k}-b\right) \exp (b)}=\frac{\exp \left(x_{i}-b\right)}{\sum_{k=1}^{n} \exp \left(x_{k}-b\right)} y^j=k=1nexp(xkb)exp(b)exp(xib)exp(b)=k=1nexp(xkb)exp(xib)
可以选择 b = max ⁡ i = 1 n x i b=\max _{i=1}^{n} x_{i} b=maxi=1nxi,这种情况下, y ^ j \hat{y}_{j} y^j移位不变,不可能出现上溢,因为此时最大的指数为 max ⁡ i = 1 n x i − b = 0 \max _{i=1}^{n} x_{i}-b=0 maxi=1nxib=0

例子:

x = np.array([1, -10, 1000])
np.exp(x) / np.exp(x).sum()
RuntimeWarning: overflow encountered in exp
RuntimeWarning: invalid value encountered in true_divide
array([ 0.,  0., nan])

使用exp-normalize后:

def exp_normalize(x):
    b = x.max()
    y = np.exp(x - b)
    return y / y.sum()

exp_normalize(x)
array([0., 0., 1.])

上溢问题得到解决。

使用exp-normalize技巧的sigmoid函数

sigmoid 函数可以使用exp-normalize技巧进行计算,避免数值溢出。
s i g m o i d ( x ) = 1 1 + exp ⁡ ( − x ) = exp ⁡ ( x ) 1 + exp ⁡ ( x ) sigmoid(x)=\frac{1}{1+\exp (-x)}=\frac{\exp (x)}{1+\exp (x)} sigmoid(x)=1+exp(x)1=1+exp(x)exp(x)
如果x很大, e x p ( x ) exp(x) exp(x)会出现上溢;如果x很小, e x p ( − x ) exp(-x) exp(x)可能会出现上溢,为了避免这种情况,在 x > = 0 x>=0 x>=0时使用第一种表示, x < 0 x<0 x<0时使用第二种。

def sigmoid(x):
    "Numerically stable sigmoid function."
    if x >= 0:
        z = exp(-x)
        return 1 / (1 + z)
    else:
        # if x is less than zero then z will be small, denom can't be
        # zero because it's 1+z.
        z = exp(x)
        return z / (1 + z)

LogSumExp技巧

经过减法和规范化步骤之后,可能有些 x k − b x_k-b xkb具有较大的负值,由于精度受限, e x p ( x k − b ) exp(x_k-b) exp(xkb)将有接近0的值,即下溢 (underflow) ,这些值可能会四舍五入为零,使 y ^ j \hat y_j y^j为零,并且使得 log ⁡ ( y ^ j ) \log(\hat y_j) log(y^j)的值为-inf。反向传播几步后,我们可能会发现自己面对一屏幕可怕的nan结果。
尽管我们要计算指数函数,但在后续计算交叉熵损失函数的过程中需要取他们的对数, 通过将softmax和交叉熵结合在一起,可以避免反向传播过程中可能会困扰我们的数值稳定性问题。

LogSumExp (LSE) 操作可以被表示为:
LSE ⁡ ( x 1 , … , x N ) = log ⁡ ( ∑ n = 1 N exp ⁡ ( x n ) ) \operatorname{LSE}\left(x_{1}, \ldots, x_{N}\right)=\log \left(\sum_{n=1}^{N} \exp \left(x_{n}\right)\right) LSE(x1,,xN)=log(n=1Nexp(xn))
max ⁡ { x 1 , … , x n } < LSE ⁡ ( x 1 , … , x n ) ≤ max ⁡ { x 1 , … , x n } + log ⁡ ( n ) \max \left\{x_{1}, \ldots, x_{n}\right\}<\operatorname{LSE}\left(x_{1}, \ldots, x_{n}\right) \leq \max \left\{x_{1}, \ldots, x_{n}\right\}+\log (n) max{x1,,xn}<LSE(x1,,xn)max{x1,,xn}+log(n)

在softmax中使用这种技巧,将交叉熵和softmax结合在一起,对原式取对数得:
log ⁡ ( y ^ j ) = log ⁡ exp ⁡ ( x j ) ∑ k = 1 n exp ⁡ ( x k ) = x j − log ⁡ ∑ k = 1 n exp ⁡ ( x k ) \begin{aligned} \log \left(\hat{y}_{j}\right) &=\log \frac{\exp \left(x_{j}\right)}{\sum_{k=1}^{n} \exp \left(x_{k}\right)} \\ &=x_{j}-\log \sum_{k=1}^{n} \exp \left(x_{k}\right) \end{aligned} log(y^j)=logk=1nexp(xk)exp(xj)=xjlogk=1nexp(xk)
此时 log ⁡ ∑ k = 1 n exp ⁡ ( x k ) \log \sum_{k=1}^{n} \exp \left(x_{k}\right) logk=1nexp(xk)仍有可能出现上溢,故令
L o g S u m M a x = log ⁡ ∑ k = 1 n exp ⁡ ( x k ) = log ⁡ ∑ k = 1 n exp ⁡ ( x k − b ) exp ⁡ ( b ) = b + log ⁡ ∑ k = 1 n exp ⁡ ( x k − b ) \begin{aligned} LogSumMax=\log \sum_{k=1}^{n} \exp \left(x_{k}\right) &=\log \sum_{k=1}^{n} \exp \left(x_{k}-b\right)\exp(b) \\ &= b+\log \sum_{k=1}^{n} \exp \left(x_{k}-b\right) \end{aligned} LogSumMax=logk=1nexp(xk)=logk=1nexp(xkb)exp(b)=b+logk=1nexp(xkb)
此时 log ⁡ ( s o f t m a x ) \log(softmax) log(softmax)函数可表示为:
log ⁡ ( y ^ j ) = x j − L o g S u m M a x = x j − b − log ⁡ ∑ k = 1 n exp ⁡ ( x k − b ) \begin{aligned} \log \left(\hat{y}_{j}\right) &=x_j-LogSumMax\\ &=x_j-b-\log \sum_{k=1}^{n} \exp \left(x_{k}-b\right) \end{aligned} log(y^j)=xjLogSumMax=xjblogk=1nexp(xkb)
y ^ j = exp ⁡ ( x j − L o g S u m M a x ) \hat{y}_{j} =\exp \left( x_j-LogSumMax\right) y^j=exp(xjLogSumMax)
对LogSumMax求导即可得到exp-normalize:
∂ ( b + log ⁡ ∑ k = 1 n exp ⁡ ( x k − b ) ) ∂ x j = exp ⁡ ( x j − b ) ∑ k = 1 n exp ⁡ ( x k − b ) \frac{\partial \left( b+ \log \sum_{k=1}^{n} \exp \left(x_{k}-b\right)\right)}{\partial x_{j}}=\frac{\exp \left(x_{j}-b\right)}{\sum_{k=1}^{n} \exp \left(x_{k}-b\right)} xj(b+logk=1nexp(xkb))=k=1nexp(xkb)exp(xjb)

如果需要对概率分布求log(如使用交叉熵损失的softmax)则使用LogSumMax,如果只求概率分布,使用exp-normalize即可。由于exp-normalize使用exp更少,在数值上更稳定。

例子:

def logsumexp(x):
    c = x.max()
    return c + np.log(np.sum(np.exp(x - c)))  
>>> x = np.array([1000, 1000, 1000])
>>> np.exp(x)
array([inf, inf, inf])

>>> logsumexp(x)
1001.0986122886682
>>> np.exp(x - logsumexp(x))
array([0.33333333, 0.33333333, 0.33333333])
>>> x = np.array([-1000, -1000, -1000])
>>> np.exp(x)
array([0., 0., 0.])
>>> np.exp(x - logsumexp(x))
array([0.33333333, 0.33333333, 0.33333333])

>>> x = np.array([-1000, -1000, 1000])
>>> np.exp(x - logsumexp(x))
array([0., 0., 1.])

exp-normalize和log-sum-max有时都被叫做"softmax",但其实exp-normalize更接近"soft argmax",LSE函数才是真正意义上的softmax函数。也就是说我们常常在神经网络中用的softmax其实是soft argmax。

Reference

  1. Exp-normalize trick
  2. The Log-Sum-Exp Trick
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值