来源:https://blog.csdn.net/zt_1995/article/details/62227603
其实整个推导,上面这个图片已经介绍得十分清楚了,但是仍有很多小步骤被省略掉了,我会补上详细的softmax求导的过程:
(1)softmax函数
首先再来明确一下softmax函数,一般softmax函数是用来做分类任务的输出层。softmax的形式为:
Si=∑kezkezi 其中 Si表示的是第i个神经元的输出,接下来我们定义一个有多个输入,一个输出的神经元。神经元的输出为 zi=∑ijxij+b,其中 wij是第 i个神经元的第 j个权重,b是偏移值. zi表示网络的第 i个输出。给这个输出加上一个softmax函数,可以写成: ai=∑kezkezi, 其中 ai表示softmax函数的第i个输出值。这个过程可以用下图表示:
(2)损失函数
softmax的损失函数一般是选择交叉熵损失函数,交叉熵函数形式为 C=−∑iyilnai,其中y_i表示真实的标签值
(3)需要用到的高数的求导公式
c'=0(c为常数)
(x^a)'=ax^(a-1),a为常数且a≠0
(a^x)'=a^xlna
(e^x)'=e^x
(logax)'=1/(xlna),a>0且 a≠1
(lnx)'=1/x
(sinx)'=cosx
(cosx)'=-sinx
(tanx)'=(secx)^2
(secx)'=secxtanx
(cotx)'=-(cscx)^2
(cscx)'=-csxcotx
(arcsinx)'=1/√(1-x^2)
(arccosx)'=-1/√(1-x^2)
(arctanx)'=1/(1+x^2)
(arccotx)'=-1/(1+x^2)
(shx)'=chx
(chx)'=shx
(uv)'=uv'+u'v
(u+v)'=u'+v'
(u/)'=(u'v-uv')/^2
(3)进行推导
我们需要求的是loss对于神经元输出 zi的梯度,求出梯度后才可以反向传播,即是求:
∂zi∂C, 根据链式法则(也就是复合函数求导法则) ∂aj∂C∂zi∂aj,初学的时候这个公式理解了很久,为什么这里是 aj而不是 ai呢?这里我们回忆一下softmax的公示,分母部分包含了所有神经元的输出,所以对于所有输出非i的输出中也包含了 zi,所以所有的a都要参与计算,之后我们会看到计算需要分为 i=j和 i̸=j两种情况分别求导数。
首先来求前半部分:
∂aj∂C=∂aj−∑jyilnaj=−∑jyjaj1,接下来求第二部分的导数,如果 i=j, ∂zi∂ai=∂zi∂(∑kezkezi)=(∑kezk)2∑kezkezi−(ezi)2=(∑kezkeiz)(1−∑kezkezi)=ai(1−ai),如果 i̸=j, ∂zi∂ai=∂zi∂∑kezkezj=−ezj(∑kekz1)2ezi=−aiaj,接下来把上面的组合之后得到 ∂zi∂C=(−∑jyjaj1)∂zi∂aj=−aiyiai(1−ai)+∑j̸=iajyjaiaj=−yi+yiai+∑j̸=iayji=−yi+ai∑jyj,推导完成!
(4)对于分类问题来说,我们给定的结果 yi最终只有一个类别是1,其他是0,因此对于分类问题,梯度等于:
∂zi∂C=ai−yi
最后放一份CS231N的代码实现,帮助进一步理解:
#coding=utf-8
import numpy as np
def softmax_loss_native(W, X, y, reg):
'''
Softmax_loss的暴力实现,利用了for循环
输入的维度是D,有C个分类类别,并且我们在有N个例子的batch上进行操作
输入:
- W: 一个numpy array,形状是(D, C),代表权重
- X: 一个形状为(N, D)为numpy array,代表输入数据
- y: 一个形状为(N,)的numpy array,代表类别标签
- reg: (float)正则化参数
f返回:
- 一个浮点数代表Loss
- 和W形状一样的梯度
'''
loss = 0.0
dW = np.zeros_like(W) #dW代表W反向传播的梯度
num_classes = W.shape[1]
num_train = X.shape[0]
for i in xrange(num_train):
scores = X[i].dot(W)
shift_scores = scores - max(scores) #防止数值不稳定
loss_i = -shift_scores[y[i]] + np.log(sum(np.exp(shift_scores)))
loss += loss_i
for j in xrange(num_classes):
softmax_output = np.exp(shift_scores[j]) / sum(np.exp(shift_scores))
if j == y[i]:
dW[:, j] += (-1 + softmax_output) * X[i]
else:
dW[:, j] += softmax_output * X[i]
loss /= num_train
loss += 0.5 * reg * np.sum(W * W)
dW = dW / num_train + reg * W
return loss, dW
def softmax_loss_vectorized(W, X, y, reg):
loss = 0.0
dW = np.zeros_like(W)
num_class = W.shape[1]
num_train = X.shape[0]
scores = X.dot(W)
shift_scores = scores - np.max(scores, axis=1).reshape(-1, 1)
softmax_output = np.exp(shift_scores) / np.sum(np.exp(shift_scores), axis=1).reshape(-1, 1)
loss = -np.sum(np.log(softmax_output[range(num_train), list(y)]))
loss /= num_train
loss += 0.5 * reg * np.sum(W * W)
dS = softmax_output.copy()
dS[range(num_train), list(y)] += -1
dW = (x.T).dot(dS)
dW = dW/num_train + reg*W
return loss, dW