softmax函数
softmax函数如下
f
(
x
)
i
=
e
x
i
∑
j
=
1
n
e
x
j
,
j
=
1
,
2
,
…
,
n
f(x)_{i}=\frac{e^{x_{i}}}{\sum_{j=1}^{n} e^{x_{j}}}, j=1,2, \ldots, n
f(x)i=∑j=1nexjexi,j=1,2,…,n
softmax上溢出(overflow)和下溢出(underflow)问题
- c 极其大,导致分子计算 e c e^c ec时上溢出。
- c 为负数,且 |c|很大,此时分母是一个极小的正数,有可能四舍五入为0,导致下溢出。
解决方案
令$ M = max(x_i), i =1,2,3,….,n , 即 M 为 所 有 ,即M为所有 ,即M为所有x_i 中 最 大 的 值 , 那 么 我 们 只 需 要 计 算 中最大的值,那么我们只需要计算 中最大的值,那么我们只需要计算f(x_i - M) 的 值 , 就 可 以 解 决 上 溢 出 、 下 溢 出 的 问 题 了 , 并 且 , 计 算 结 果 理 论 上 仍 然 和 的值,就可以解决上溢出、下溢出的问题了,并且,计算结果理论上仍然和 的值,就可以解决上溢出、下溢出的问题了,并且,计算结果理论上仍然和f(x_i)$保持一致。

其中M=3是z1,z2,z3中的最大值。可见计算结果并未改变。
通过这样的变换,对任何一个
x
i
x_i
xi,减去M之后,e的指数的最大值为0,所以不会发生上溢出;同时,分母中也至少会包含一个值为1的项,所以分母也不会下溢出(四舍五入为0)。
分子下溢出
仍然有一个问题:如果softmax函数中的分子发生下溢出,也就是前面所说的 c 为负数,且 |c|很大,此时分母是一个极小的正数,有可能四舍五入为0的情况,此时,如果我们把softmax函数的计算结果再拿去计算 log,即 log softmax,其实就相当于计算 log(0),所以会得到 −∞,但这实际上是错误的,因为它是由舍入误差造成的计算错误。
l
e
f
t
log
[
f
(
x
i
)
]
=
log
(
e
x
i
e
x
1
+
e
x
2
+
⋯
e
x
n
)
=
log
(
e
x
i
e
M
e
x
1
e
M
+
e
x
2
e
M
+
⋯
e
x
n
e
M
)
=
log
(
e
(
x
i
−
M
)
∑
j
n
e
(
x
j
−
M
)
)
=
log
(
e
(
x
i
−
M
)
)
−
log
(
∑
j
n
e
(
x
j
−
M
)
)
{left} \log \left[f\left(x_{i}\right)\right]=\log \left(\frac{e^{x_{i}}}{e^{x_{1}}+e^{x_{2}}+\cdots e^{x_{n}}}\right)\\ =\log \left(\frac{\frac{e^{x_{i}}}{e^{M}}}{\frac{e^{x_{1}}}{e^{M}}+\frac{e^{x_{2}}}{e^{M}}+\cdots \frac{e^{x_{n}}}{e^{M}}}\right)= \log \left( \frac{e^{\left(x_{i}-M\right)}}{\sum_{j}^{n} e^{\left(x_{j}-M\right)}}\right) \\=\log \left(e^{\left(x_{i}-M\right)}\right)-\log \left(\sum_{j}^{n} e^{\left(x_{j}-M\right)}\right)
leftlog[f(xi)]=log(ex1+ex2+⋯exnexi)=log(eMex1+eMex2+⋯eMexneMexi)=log(∑jne(xj−M)e(xi−M))=log(e(xi−M))−log(j∑ne(xj−M)) {left}
会产生下溢出的因素已经被消除掉了——求和项中,至少有一项的值为1,这使得log后面的值不会下溢出,也就不会发生计算 log(0) 的悲剧。
nn.log_softmax
对于单标签多分类问题,直接经过softmax求出概率分布,然后把这个概率分布用crossentropy做一个似然估计误差。但是softmax求出来的概率分布,每一个概率都是(0,1)的,这就会导致有些概率过小,导致下溢。
考虑到这个概率分布总归是要经过crossentropy的,而crossentropy的计算是把概率分布外面套一个-log 来似然,那么直接在计算概率分布的时候加上log,把概率从(0,1)变为(-∞,0),这样就防止中间会有下溢出。
所以log_softmax说白了就是将本来应该由crossentropy做的套log的工作提到预测概率分布来,跳过了中间的存储步骤,防止中间数值会有下溢出,使得数据更加稳定。
正是由于把log这一步从计算误差提到前面,所以用log_softmax之后,下游的计算误差的function就应该变成NLLLoss(它没有套log这一步,直接将输入取反,然后计算和label的乘积求和平均)