深度学习被你忽略的细节系列篇——SoftmaxLogSumExp和Sigmoid
Posted AI蜗牛之家
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深度学习被你忽略的细节系列篇——SoftmaxLogSumExp和Sigmoid相关的知识,希望对你有一定的参考价值。
平时我们基本用pytorch或者tensorflow框架时,基本对特别底层的函数实现关注不多,仅限于知道公式的原理。但是很多大佬往往自己会实现一些源码(比如ListNet复现),在看这些源码时,经常出现各种有点难以理解的代码,本来很简单的东西,莫名其妙的各种转换,化简完之后可能感觉是一样的,这么费劲周折的折腾啥?殊不知还是对底层的实现原理了解少了,虽然有些源码不需要我们从底层造轮子(完全从底层造轮子也影响效率),但是能理解其原理在我们debug以及看一些源码时不至于太多疑惑(毕竟国外很多大佬都喜欢实现一些底层utils)。
今天我们来重新认识一下我们经常用的Softmax、LogSumExp和Sigmoid
1. 背景概要
我们知道编程语言中的数值都有一个表示范围的,如果数值过大,超过最大的范围,就是上溢;如果过小,超过最小的范围,就是下溢。
今天要讨论的Softmax、LogSumExp和Sigmoid,就面临着上述溢出的问题,下面的一些梳理也主要用来解决计算Softmax或CrossEntropy时出现的上溢(overflow)或下溢(underflow)问题。
2. Softmax
在机器学习中,计算概率输出基本都需要经过Softmax函数,它的公式应该很熟悉了吧
Softmax
(
x
i
)
=
exp
(
x
i
)
∑
j
=
1
n
exp
(
x
j
)
(1)
\\textSoftmax(x_i) = \\frac\\exp(x_i)\\sum_j=1^n \\exp(x_j) \\tag1
Softmax(xi)=∑j=1nexp(xj)exp(xi)(1)
但是Softmax存在上溢和下溢大问题。如果
x
i
x_i
xi太大,对应的指数函数也非常大,此时很容易就溢出,得到nan结果;如果
x
i
x_i
xi太小,或者说负的太多,就会导致出现下溢而变成0,如果分母变成0,就会出现除0的结果。
此时我们经常看到一个常见的做法是(其实用到的是指数归一化技巧, exp-normalize),先计算x中的最大值
b
=
max
i
=
1
n
x
i
b = \\max_i=1^n x_i
b=maxi=1nxi,然后根据
Softmax
(
x
i
)
=
exp
(
x
i
)
∑
j
=
1
n
exp
(
x
j
)
=
exp
(
x
i
−
b
)
⋅
exp
(
b
)
∑
j
=
1
n
(
exp
(
x
j
−
b
)
⋅
exp
(
b
)
)
=
exp
(
x
i
−
b
)
⋅
exp
(
b
)
exp
(
b
)
⋅
∑
j
=
1
n
exp
(
x
j
−
b
)
=
exp
(
x
i
−
b
)
∑
j
=
1
n
exp
(
x
j
−
b
)
=
Softmax
(
x
i
−
b
)
\\beginaligned \\textSoftmax(x_i) &= \\frac\\exp(x_i)\\sum_j=1^n \\exp(x_j) \\\\ &= \\frac\\exp(x_i - b) \\cdot \\exp(b)\\sum_j=1^n \\left (\\exp(x_j - b) \\cdot \\exp(b) \\right) \\\\ &= \\frac\\exp(x_i - b) \\cdot \\exp(b) \\exp(b) \\cdot \\sum_j=1^n \\exp(x_j - b) \\\\ &= \\frac\\exp(x_i - b)\\sum_j=1^n \\exp(x_j - b) \\\\ &= \\textSoftmax(x_i - b) \\endaligned
Softmax(xi)=∑j=1nexp(xj)exp(xi)=∑j=1n(exp(xj−b)⋅exp(b))exp(xi−b)⋅exp(b)=exp(b)⋅∑j=1nexp(xj−b)exp(xi−b)⋅exp(b)=∑j=1nexp(xj−b)exp(xi−b)=Softmax(xi−b)
这种转换是等价的,经过这一变换,就避免了上溢,最大值变成了
exp
(
0
)
=
1
\\exp(0)=1
exp(0)=1;同时分母中也会有一个1,就避免了下溢。
我们通过实例来理解一下。
def bad_softmax(x):
y = np.exp(x)
return y / y.sum()
x = np.array([1, -10, 1000])
print(bad_softmax(x))
#运行结果
#... RuntimeWarning: overflow encountered in exp
#... RuntimeWarning: invalid value encountered in true_divide
#array([ 0., 0., nan])
接下来进行上面的优化,并进行测试:
def softmax(x):
b = x.max()
y = np.exp(x - b)
return y / y.sum()
print(softmax(x))
# array([0., 0., 1.])
x = np.array([-800, -1000, -1000])
print(bad_softmax(x))
# array([nan, nan, nan])
print(softmax(x))
# array([1.00000000e+00, 3.72007598e-44, 3.72007598e-44])
关于softmax的另外实现,参加下文。
3. LogSumExp
什么是LSE?
LSE被定义为参数指数之和的对数:
LSE
(
x
1
,
⋯
,
x
n
)
=
log
∑
i
=
1
n
exp
(
x
i
)
=
log
(
exp
(
x
1
)
+
⋯
+
exp
(
x
n
)
)
\\textLSE(x_1,\\cdots,x_n) = \\log \\sum_i=1^n \\exp(x_i) =\\log \\left(\\exp(x_1) + \\cdots + \\exp(x_n) \\right)
LSE(x1,⋯,xn)=logi=1∑nexp(xi)=log(exp(x1)+⋯+exp(xn))
输入可以看成是一个n维的向量,输出是一个标量。
什么场景下要到LogSumExp呢? 以上是关于深度学习被你忽略的细节系列篇——SoftmaxLogSumExp和Sigmoid的主要内容,如果未能解决你的问题,请参考以下文章 点云深度学习系列博客: Point Transformer方法概述
交叉熵loss大家肯定不陌生,其中就有一项是log
p
p
p,比如多分类场景里面就需要在对softmax的结果做对数处理:
log
(
Softmax
(
x
i
)
)
=
log
exp
(
x
i
)
∑
j
=
1
n
exp
(
x
j
)
=
x
i
−
log
∑
j
=
1
n
exp
(
x
j
)
\\beginaligned \\log \\left( \\textSoftmax(x_i) \\right) &= \\log \\frac\\exp(x_i)\\sum_j=1^n \\exp(x_j) \\\\ &= x_i - \\log \\sum_j=1^n \\exp(x_j) \\\\ \\endaligned
log(Softmax(xi))=log∑j=1nexp(xj)exp(xi)=xi−logj=1∑nexp(