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}
x∈Rn决定。如果
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(xk−b)exp(b)exp(xi−b)exp(b)=∑k=1nexp(xk−b)exp(xi−b)
可以选择
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=1nxi−b=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
xk−b具有较大的负值,由于精度受限,
e
x
p
(
x
k
−
b
)
exp(x_k-b)
exp(xk−b)将有接近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=1∑Nexp(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)=log∑k=1nexp(xk)exp(xj)=xj−logk=1∑nexp(xk)
此时
log
∑
k
=
1
n
exp
(
x
k
)
\log \sum_{k=1}^{n} \exp \left(x_{k}\right)
log∑k=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=1∑nexp(xk)=logk=1∑nexp(xk−b)exp(b)=b+logk=1∑nexp(xk−b)
此时
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)=xj−LogSumMax=xj−b−logk=1∑nexp(xk−b)
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(xj−LogSumMax)
对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+log∑k=1nexp(xk−b))=∑k=1nexp(xk−b)exp(xj−b)
如果需要对概率分布求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。