简 介: 对于在深度学习中的两个常见的函数SoftMax,交叉熵进行的探讨。在利用paddle平台中的反向求微分进行验证的过程中,发现结果 与数学定义有差别。具体原因还需要之后进行查找。
关键词
: 交叉熵,SoftMax
在深度学习网络中,SoftMax 以及Cross Entropy(交叉熵)是最常见到神经网络输出级的数据处理以及计算损失函数。
§01 定 义
1.1 函数
SoftMax函数往往使用在分类神经网络的最后一层的处理中,它将分类神经网络的输出转换成网络对于输入样本所属类别的概率。
1.1.1 类别概率
如果神经网络 N ( φ ; X ) N\left( {\varphi ;X} \right) N(φ;X)在输入样品 x i x_i xi的作用下,输出输出向量为 [ z 1 , z 2 , ⋯ , z M ] \left[ {z_1 ,z_2 , \cdots ,z_M } \right] [z1,z2,⋯,zM]。分别表示该样本属于 1 ~ M 个类别的概率。但属于概率必须满足:
- z i ≥ 0 , i = 1 , 2 , ⋯ , M z_i \ge 0,\,\,i = 1,2, \cdots ,M zi≥0,i=1,2,⋯,M
- ∑ i = 1 M z i = 1 \sum\limits_{i = 1}^M {z_i } = 1 i=1∑Mzi=1
为了达到概率条件,需要进行处理。SoftMax处理方式是其中一种。
▲ 图1.1.1 神经网络输入输出示意图
1.1.2 SoftMax
在 详解softmax函数以及相关求导过程 中,给出了SoftMax的定义:
y i = e z i ∑ i = 1 M e z i y_i = {{e^{z_i } } \over {\sum\limits_{i = 1}^M {e^{z_i } } }} yi=i=1∑Meziezi
数据计算过程中的流程图参见下图:
▲ 图1.1.2 SoftMax结构示意图
可以看到输出的 y i , i = 1 , 2 , ⋯ , M y_i ,i = 1,2, \cdots ,M yi,i=1,2,⋯,M满足:
- y i ≥ 0 , i = 1 , 2 , ⋯ , M y_i \ge 0,\,\,i = 1,2, \cdots ,M yi≥0,i=1,2,⋯,M
- ∑ i = 1 M y i = 1 \sum\limits_{i = 1}^M {y_i } = 1 i=1∑Myi=1
(1)举例
a = [0.1,0.2,0.3,-1,5]
b = sum([exp(aa) for aa in a])
c = [exp(aa)/b for aa in a]
print("a: {}".format(a), "b: {:0.2f}".format(b), "c: {}".format(c))
a: [0.1, 0.2, 0.3, -1, 5]
b: 152.46
c: [0.007249044016189045, 0.008011432630542426, 0.008854002355397775, 0.0024129971374439265, 0.9734725238604267]
1.2 交叉熵
根据 一文搞懂熵(Entropy),交叉熵(Cross-Entropy)中的定义,对于一组样本所出现的概率分布:
p
i
,
i
=
1
,
2
,
⋯
,
M
p_i ,\,\,i = 1,2, \cdots ,M
pi,i=1,2,⋯,M,所对应的信息熵为:
E
e
n
t
r
o
p
y
(
P
)
=
E
(
P
)
=
−
∑
i
=
1
M
p
i
log
(
p
i
)
E_{entropy} \left( P \right) = E\left( P \right) = - \sum\limits_{i = 1}^M {p_i \log \left( {p_i } \right)}
Eentropy(P)=E(P)=−i=1∑Mpilog(pi)
1.2.1 交叉熵定义
如果有两个相同长度的概率分布:
P
=
{
p
1
,
p
2
,
⋯
,
p
M
}
P = \left\{ {p_1 ,p_2 , \cdots ,p_M } \right\}
P={p1,p2,⋯,pM}
Q
=
{
q
1
,
q
2
,
⋯
,
q
M
}
Q = \left\{ {q_1 ,q_2 , \cdots ,q_M } \right\}
Q={q1,q2,⋯,qM}。他们两者之间的交叉熵有两种定义:
在神经网络应用中,P,Q两个概率通常情况下:一个是网络输出SoftMax对应的样本类别概率;另外一个是样本期望类别概率。
从上面定义来看,对于两个概率分布,这两个交叉熵的取值是不同的,除非两个概率分布是相同的,或者相差一个系数。
1.2.2 举例
比如对于MNIST分类问题,期望值为:
P: [0, 0, 1, 0, 0, 0, 0, 0, 0, 0]
网络输出为:
Q: [0.29213776 0.03122202 0.03394387 0.01968607 0.13676823 0.00576233
0.32953807 0.02688782 0.07919548 0.04485835]
H ( P , Q ) H\left( {P,Q} \right) H(P,Q)对应的交叉熵为:
H ( P , Q ) = − ∑ i = 0 9 p ( i ) ⋅ log [ q ( i ) ] = − log ( 0.03394 ) = 3.383 H\left( {P,Q} \right) = - \sum\limits_{i = 0}^9 {p\left( i \right) \cdot \log \left[ {q\left( i \right)} \right] = - \log \left( {0.03394} \right) = 3.383} H(P,Q)=−i=0∑9p(i)⋅log[q(i)]=−log(0.03394)=3.383
从这里可以看到,由于 log 函数要求输入 x 必须是大于0,所以在计算上面的交叉熵的时候,往往使用 H ( Q , P ) H\left( {Q,P} \right) H(Q,P)的定义,而不是反过来。
这个交叉熵只是反应了网络输出中,对应样本正确类别的神经元输出距离“1”还相差多少,至于其他网络输出对于交叉熵是没有影响的。
§02 求 导
通常情况下,对于用于分类神经网络的最后一层的输出往往采用线性传递函数,再加上SoftMax,通过交叉熵计算损失函数Loss。在进行损失函数反向传播的过程中,需要计算反向传播的梯度方向。
▲ 图2.2 网络输出对应的交叉熵
L o s s ( z i , t i ) = − ∑ i = M t i log ( e z i ∑ j = M M e z j ) Loss\left( {z_i ,t_i } \right) = - \sum\limits_{i = }^M {t_i \log \left( {{{e^{z_i } } \over {\sum\limits_{j = M}^M {e^{z_j } } }}} \right)} Loss(zi,ti)=−i=∑Mtilog⎝⎜⎜⎜⎛j=M∑Mezjezi⎠⎟⎟⎟⎞
2.1 梯度求解
为了方便梯度求解,假设样本属于第 c 个种类,也就是:
t
C
=
1
,
t
m
=
0
,
m
≠
c
t_C = 1,\,\,t_m = 0,m \ne c
tC=1,tm=0,m=c。那么此时交叉熵为:
L
o
s
s
(
z
i
,
t
i
)
=
−
log
e
z
c
∑
j
=
1
M
e
z
j
=
log
(
∑
j
=
1
M
e
z
j
)
−
z
c
Loss\left( {z_i ,t_i } \right) = - \log {{e^{z_c } } \over {\sum\limits_{j = 1}^M {e^{z_j } } }} = \log \left( {\sum\limits_{j = 1}^M {e^{z_j } } } \right) - z_c
Loss(zi,ti)=−logj=1∑Mezjezc=log(j=1∑Mezj)−zc
求此时损失函数 L o s s Loss Loss对于网络输出 z i z_i zi 的梯度下降方向:
− ∂ L o s s ∂ z i = d z c d z i − 1 ∑ j = 1 M e z j ⋅ e z i = d z c d z i − y i - {{\partial Loss} \over {\partial z_i }} = {{dz_c } \over {dz_i }} - {1 \over {\sum\limits_{j = 1}^M {e^{z_j } } }} \cdot e^{z_i } = {{dz_c } \over {dz_i }} - y_i −∂zi∂Loss=dzidzc−j=1∑Mezj1⋅ezi=dzidzc−yi
情况分为两种:
第一种情况: i = c i = c i=c
− ∂ L o s s ∂ z c = 1 − y c -{{\partial Loss} \over {\partial z_c }} = 1 - y_c −∂zc∂Loss=1−yc
第二种情况:
i
≠
c
i \ne c
i=c
−
∂
L
o
s
s
∂
z
i
=
−
y
i
-{{\partial Loss} \over {\partial z_i }} = - y_i
−∂zi∂Loss=−yi
2.2 对比MSE
如果损失函数使用MSE,即:
L o s s = 1 M ∑ i = 1 M ( t i − z i ) 2 Loss = {1 \over M}\sum\limits_{i = 1}^M {\left( {t_i - z_i } \right)^2 } Loss=M1i=1∑M(ti−zi)2
那么它针对于所有输出的负梯度方向:
− ∂ L o s s ∂ z i = ( t i − z i ) M - {{\partial Loss} \over {\partial z_i }} = {{\left( {t_i - z_i } \right)} \over M} −∂zi∂Loss=M(ti−zi)
对比前面对于SoftMax以及交叉熵求解来看,除了相差一个常量系数 1 / M 1/M 1/M之外,梯度的方向是相同的。
▲ 图2.2.1 交叉熵在分类问题
2.3 对比Sigmoid
如果网络最后一层传递函数采用Sigmoid函数:
f ( x ) = 1 1 + e − x f\left( x \right) = {1 \over {1 + e^{ - x} }} f(x)=1+e−x1
Sigmoid函数曲线:
▲ 图2.3.1 Sigmoid函数曲线
x = linspace(-10, 10, 200)
smt = 1/(1+exp(-x))
plt.figure(figsize=(10,5))
plt.plot(t, smt)
plt.xlabel("x")
plt.ylabel("sigmoid(x)")
plt.grid(True)
plt.title("Sigmoid Active Function")
plt.tight_layout()
plt.show()
这个函数虽然不能够保证网络的输出 z i , i = 1 , 2 , ⋯ , M z_i ,i = 1,2, \cdots ,M zi,i=1,2,⋯,M满足概率分布归一化要求,但可以保证输出值 z i > 0 , i = 1 , 2 , ⋯ , M z_i > 0,\,\,i = 1,2, \cdots ,M zi>0,i=1,2,⋯,M。这种情况也称为 logistic回归。
▲ 图2.3.2 最后一级采用Sigmoid函数输出
如果在此基础上仍然利用交叉熵作为损失函数,那么交叉熵对于 z i , i = 1 , 2 , ⋯ , M z_i ,i = 1,2, \cdots ,M zi,i=1,2,⋯,M的负梯度为:
− ∂ L o s s ∂ z i = − ∑ i = 1 M t i log ( 1 1 + e − z i ) - {{\partial Loss} \over {\partial z_i }} = - \sum\limits_{i = 1}^M {t_i \log \left( {{1 \over {1 + e^{ - z_i } }}} \right)} −∂zi∂Loss=−i=1∑Mtilog(1+e−zi1)
同样,假设当前样本对应第 c c c类,也就是: t c = 1 , t i = 0 , i ≠ c t_c = 1,\,\,t_i = 0,\,i \ne c tc=1,ti=0,i=c。
第一种情况: i = c i = c i=c
− ∂ L o s s ∂ z c = ∂ ∂ z c log ( 1 + e − z i ) = − e − z c 1 + e − z c = ( 1 − y c ) - {{\partial Loss} \over {\partial z_c }} = {\partial \over {\partial z_c }}\log \left( {1 + e^{ - z_i } } \right) = {{ - e^{ - z_c } } \over {1 + e^{ - z_c } }} = \left( {1 - y_c } \right) −∂zc∂Loss=∂zc∂log(1+e−zi)=1+e−zc−e−zc=(1−yc)
第二种情况: i ≠ c i \ne c i=c
− ∂ L o s s ∂ z i = 0 - {{\partial Loss} \over {\partial z_i }} = 0 −∂zi∂Loss=0
通过上面对比,可以看到使用Sigmoid函数作为网络的最后一级输出的传递函数,然后使用交叉熵来作为损失函数的时候存在的问题:
- 它只对于该样本对应的类别所属的神经元反向有梯度,对于其它输出神经元反向没有梯度;
- 当神经元输出接近于1的时候,反向梯度数值会下降;
上面会使得网络在收敛的时候速度变慢。
§03 实验验证
3.1 对比SoftMax与MSE
根据前面推导,可以看到在网络输出传递函数为线性函数的时候,通过SoftMax以及交叉熵对应的损失函数,与MSE对应的损失函数所对网络输出节点的损失函数负梯度数值是一样的。下面在paddle平台下,测试一下这方面的内容。
3.1.1 建立softmax网络
搭建一个仅仅由softmax组成的网络。
class netmodel(paddle.nn.Layer):
def __init__(self, ):
super(netmodel, self).__init__()
def forward(self, x):
x = paddle.nn.functional.softmax(x)
return x
3.1.2 求解SoftMax梯度
(1)求SoftMax输出
Ⅰ.使用net求解SoftMax输出
定义一个向量z,代表网络的实际输出。然后经过上面定义的SoftMax,获得实际输出y。
net = netmodel()
z = TT(array([0,0.1,-2,3])[newaxis,:], dtype='float64')
z.stop_gradient = False
y = paddle.nn.functional.softmax(z)
print("z: {}".format(z), "y: {}".format(y))
z: Tensor(shape=[1, 4], dtype=float64, place=CPUPlace, stop_gradient=False,
[[ 0. , 0.10000000, -2. , 3. ]])
y: Tensor(shape=[1, 4], dtype=float64, place=CPUPlace, stop_gradient=False,
[[0.04479074, 0.04950142, 0.00606177, 0.89964607]])
Ⅱ.使用numpy求SoftMax
通过Numpy函数计算SoftMax,对比paddle中的 SoftMax函数数值:
zz = z.numpy()
yy = exp(zz)/sum(exp(zz))
print("zz: {}".format(zz), "yy: {}".format(yy))
zz: [[ 0. 0.1 -2. 3. ]]
yy: [[0.04479074 0.04950142 0.00606177 0.89964607]]
对比一下,上面的数值是一样的。
(2)反向梯度计算
上面利用paddle中的loss求解机制,自动完成对z的梯度求解。
net = netmodel()
optimizer = paddle.optimizer.SGD(learning_rate=0.1, parameters=net.parameters())
z = TT(array([0,0.1,-2,3])[newaxis,:], dtype='float64')
z.stop_gradient = False
y = paddle.nn.functional.softmax(z)
t = TT(array([[1]]))
loss = paddle.nn.functional.cross_entropy(y, t)
loss.backward()
print("loss.numpy(): {}".format(loss.numpy()), "y: {}".format(y), "y.grad: {}".format(y.grad), "z.grad: {}".format(z.grad))
loss.numpy(): [1.66634288]
y: Tensor(shape=[1, 4], dtype=float64, place=CPUPlace, stop_gradient=False,
[[0.04479074, 0.04950142, 0.00606177, 0.89964607]])
y.grad: Tensor(shape=[1, 4], dtype=float64, place=CPUPlace, stop_gradient=False,
[[ 0.18804884, -0.81106323, 0.18090513, 0.44210926]])
z.grad: Tensor(shape=[1, 4], dtype=float64, place=CPUPlace, stop_gradient=False,
[[-0.00802040, -0.05832138, -0.00112875, 0.06747052]])
观察 z 变量的反向梯度,下面进行验证一下。
首先,关于 Loss的计算数值,它应该等于:
L o s s = − log ( y 1 ) = − log ( 0.0495 ) = 3.006 Loss = - \log \left( {y_1 } \right) = - \log \left( {0.0495} \right) = 3.006 Loss=−log(y1)=−log(0.0495)=3.006
但上面给出的数值:
print("loss.numpy(): {}".format(loss.numpy()))
loss.numpy(): [1.66634288]
这与定义不同?那么问题出现在哪里了呢?
再检查y[1]的梯度:
print(1+log(0.0495)*y[0][1])
Tensor(shape=[1], dtype=float64, place=CPUPlace, stop_gradient=False,
[0.85120948])
可以看到这与上面计算出的数值也不一样。
※ 总 结 ※
对于在深度学习中的两个常见的函数SoftMax,交叉熵进行的探讨。在利用paddle平台中的反向求微分进行验证的过程中,发现结果 与数学定义有差别。具体原因还需要之后进行查找。
■ 相关文献链接:
- 详解softmax函数以及相关求导过程
- 一文搞懂熵(Entropy),交叉熵(Cross-Entropy)
● 相关图表链接:
- 图1.1.1 神经网络输入输出示意图
- 图1.1.2 SoftMax结构示意图
- 图2.2 网络输出对应的交叉熵
- 图2.2.1 交叉熵在分类问题
- 图2.3.1 Sigmoid函数曲线
- 图2.3.2 最后一级采用Sigmoid函数输出