本篇博客承接上文,主要介绍softmax的求导和为啥在线性分类器的self.loss函数中,求导部分是那样写的。
原理
softmax公式:
a
i
=
e
i
∑
j
e
j
a_i = \frac{e^i}{ \sum\limits_{j} e^j}
ai=j∑ejei
交叉熵公式:
l
o
s
s
=
−
∑
k
y
k
×
l
o
g
(
a
k
)
loss = -\sum\limits_{k} y_k \times log(a_k)
loss=−k∑yk×log(ak)
现在有一个线性分类器:
z
=
x
×
W
+
b
z = x \times W + b
z=x×W+b
x是单样本的reshape之后的向量,这里的y则是one hot编码。
经过softmax:
a
=
s
o
f
t
m
a
x
(
z
)
a = softmax(z)
a=softmax(z)
最后计算loss:
l
o
s
s
=
−
∑
k
y
k
∗
l
o
g
(
a
k
)
loss = -\sum_k y_k * log(a_k)
loss=−k∑yk∗log(ak)
我们想计算W的导数,用梯度下降法更新w,根据链式法则:
∂
l
o
s
s
∂
w
=
∂
l
o
s
s
∂
a
×
∂
a
∂
z
×
∂
z
∂
x
\frac{\partial loss}{\partial w} = \frac{\partial loss}{\partial a} \times \frac{\partial a}{\partial z} \times \frac{\partial z}{\partial x}
∂w∂loss=∂a∂loss×∂z∂a×∂x∂z
现在假想我们要处理的情况是单样本,y是one hot编码的label。
∂
l
o
s
s
∂
z
i
=
∑
k
∂
l
o
s
s
∂
a
k
×
∂
a
k
∂
z
i
\frac{\partial loss}{\partial z_i}=\sum_k \frac{\partial loss}{\partial a_k} \times \frac{\partial a_k}{ \partial z_i}
∂zi∂loss=k∑∂ak∂loss×∂zi∂ak
k的范围是从1到类别数M,i是z的每一个单元输出,自然也是从1到M变化。
之所以对z的第i个单元的求导,会涉及到所有的a,是因为在softmax中,分母就是所有值的指数求和,因此
a
a
a的所有值都对
a
i
a_i
ai有作用,自然,
z
i
z_i
zi的导数就应该包含loss对所有a的导数。
∂
l
o
s
s
∂
a
k
=
−
y
k
a
k
\frac{\partial loss}{\partial a_k}=-\frac{y_k}{a_k}
∂ak∂loss=−akyk
接下来就是求:
∂
a
k
∂
z
i
\frac{\partial a_k}{\partial z_i}
∂zi∂ak
分两种情况,
i
=
k
i =k
i=k或者
i
̸
=
k
i \not= k
i̸=k (i不等于在markdown中怎么是这么显示的?)
当i=k:
∂
a
i
∂
z
i
=
∂
(
e
z
i
∑
j
e
z
j
∂
z
i
)
\frac{\partial a_i}{\partial z_i} = \frac{\partial(\frac{e^{z_i}}{\sum_j e^{z_j}}}{\partial z_i})
∂zi∂ai=∂zi∂(∑jezjezi)
注意因为j里面是有i的,分子分母都有
z
i
z_i
zi。
∂
a
i
∂
z
i
=
∂
(
e
z
i
∑
j
e
z
j
∂
z
i
)
=
e
z
i
(
∑
j
e
z
j
)
−
e
z
i
e
z
j
(
∑
j
e
z
j
)
2
=
e
z
i
∑
j
e
z
j
×
(
1
−
e
z
i
∑
j
e
z
j
)
=
a
i
(
1
−
a
i
)
\frac{\partial a_i}{\partial z_i} = \frac{\partial(\frac{e^{z_i}}{\sum_j e^{z_j}}}{\partial z_i}) =\frac{e^{z_i} (\sum_j e^{z_j}) - e^{z_i} e^{z_j}}{(\sum_j e^{z_j})^2}=\frac{e^{z_i}}{\sum_j e^{z_j}} \times (1-\frac{e^{z_i}}{\sum_j e^{z_j}}) \\ =a_i(1-a_i)
∂zi∂ai=∂zi∂(∑jezjezi)=(∑jezj)2ezi(∑jezj)−eziezj=∑jezjezi×(1−∑jezjezi)=ai(1−ai)
当 i != k时,分子中就没有和
z
i
z_i
zi相关的项了。
∂
a
k
∂
z
i
=
∂
e
z
k
∑
j
e
z
j
∂
z
i
=
−
e
z
i
e
z
k
(
∑
j
e
z
j
)
2
=
−
a
i
a
k
\frac{\partial a_k}{\partial z_i}=\frac{\partial \frac{e^{z_k}}{\sum_j e^{z_j}}}{\partial z_i} = - \frac{e^{z_i} e^{z_k}}{(\sum_j e^{z_j})^2} = -a_i a_k
∂zi∂ak=∂zi∂∑jezjezk=−(∑jezj)2eziezk=−aiak
汇总之后:
∂ l o s s ∂ z i = ∑ k ∂ l o s s ∂ a k × ∂ a k ∂ z i = ∑ i = k ∂ l o s s ∂ a k ∂ a k ∂ z i + ∑ i ≠ k ∂ l o s s ∂ a k ∂ a k ∂ z i = y i ( a i − 1 ) + a i ∑ i ≠ k y k = a i ∑ j y j − y k \frac{\partial loss}{\partial z_i}=\sum_k \frac{\partial loss}{\partial a_k} \times \frac{\partial a_k}{ \partial z_i} = \sum_{i=k} \frac{\partial loss}{\partial a_k}\frac{\partial a_k}{\partial z_i}+ \sum_{i \neq k} \frac{\partial loss}{\partial a_k}\frac{\partial a_k}{\partial z_i}\\ =y_i(a_i-1) + a_i \sum_{i \neq k} y_k= a_i\sum_j y_j - y_k ∂zi∂loss=k∑∂ak∂loss×∂zi∂ak=i=k∑∂ak∂loss∂zi∂ak+i̸=k∑∂ak∂loss∂zi∂ak=yi(ai−1)+aii̸=k∑yk=aij∑yj−yk
因为y是one hot 编码,所以
∑
j
y
j
=
1
\sum_j y_j=1
∑jyj=1,则:
∂
l
o
s
s
∂
z
i
=
a
i
−
y
i
\frac{\partial loss}{\partial z_i}=a_i - y_i
∂zi∂loss=ai−yi
这就是softmax的导数了。
原来就是概率值a 减去 在对应的类别的标签。即,如果
z
i
z_i
zi对应是正例,则loss对它的导数就是softmax之后的值减1。
!!!这个时候在回顾上一篇博客求导数的位置。
# 求导
# softmax的求导是经过softmax得到的矩阵,在每个正例位置减一
# z = x*w +b
# a = softmax(z)
# loss = -y*log(a)
grad_z = softmax_scores
grad_z[np.arange(batch_size),y] -= 1
grad_w = x.T.dot(grad_z)
grad_w /= batch_size # 梯度求平均
grad_w += reg*self.W
grad_z就是求loss对z的求导,它等于softmax_scores,即公式里面的a,然后在正例的位置减1.
w的导数按照链式法则,乘上去就行了。根据链式法则,代码这里:
grad_w = x.T.dot(grad_z)
按照正常顺序,应该写为
grad_z * x
只因为我们推到是考虑单样本的情况,如果是多样本的情况,grad_z实质上是 a T a^T aT
a T × x = x T × a a^T \times x = x^T \times a aT×x=xT×a