softmax的上下溢问题
问题描述
在解决多分类问题时,常常使用softmax作为激活函数:\(softmax(x_i) = \frac {e^{x_i}}{\sum _{j=1}^{n}{e^x_j}}\)
一次计算过程基本如下:
def softmax(inputs):
length = len(inputs)
exps = []
sum = 0
for item in inputs:
exp_val = math.exp(item) # item就是x_i
sum = sum + exp_val # sum就是 $\sum (e^x_j)$
exps.append(exp_val)
return exps/sum # 返回一个list,list中每个元素对应于每个x_i计算后的值
计算过程当然是没什么问题,但是对于计算机而言,它能够计算、存储的数值大小、精度总要有个上下限:
- 当inputs中的数比较大时:可能出现上溢
如inputs = [1000, 2000, 3000]
,那么e^x_i 就会超过上限,从而报错:OverflowError,更别提后续的求和、求商。这就是上溢。 - 当inputs中的数比较小时:可能出现下溢
如inputs = [-1000, -2000. -3000]
,e^x_i 同样超过了计算机的精度范围,也就直接记为0,从而使得分母的值为0,那么整个计算结果在python中就会显示ZeroDivisionError,在numpy中标记为“nan"。这就是下溢。
对于上下溢问题(通常是下溢),一个典型表现就是对NN做训练时,准确率没有变化:因为经过softmax的输出并不是一个数,后续的损失、优化无法进行,参数得不到更新,那自然每次都是一样的结果。
解决:softmax的冗余性
根据上述softmax公式进行推导:
\(softmax(x_i+x)=\frac {e^{x_i+x}}{\sum _{j=1}^{n}{e^{x_j+x}}}
=\frac {e^x \cdot e^{x_i}} {\sum _{j=1}^{n} {(e^x \cdot e^x_j)}}
=\frac {e^x \cdot e^{x_i}} {e^x \cdot \sum _{j=1}^{n} {e^x_j}}
=\frac {e^{x_i}} {\sum _{j=1}^{n}{e^x_j}}
=softmax(x_i)\)
我们可以看到对于任意一个数a, x+a和x在softmax中的结果都是一样的,即\(softmax(x) = softmax(x+a)\),这个结论被称为softmax的冗余性。
我们令 \(x=x+a\),其中\(a=-max(x)\),则:
- \((x+a)\)的最大值等于0,避免了上溢的问题;
- 同时,因为一定有\(x+a=0\), 所以分母中的各个加数至少有一个为1,也就不可能为0,由此避免下溢的问题。