softmax的loss和gradient推导过程
softmax的loss和gradient推导过程
相信搞deeplearning的各位大牛都很熟悉softmax了,用来对得分矩阵做归一化得到概率的一种分类手段,我这两天在做cs231n的作业,新手上路,只作为自己的学习足迹记录,还望各位大佬多多包涵。
- 简单介绍
- Softmax的loss计算
- Softmax的grad计算
- naive loop
- vectorization
简单介绍

这个公式是大家非常熟悉的,其实就是对于神经网络最后一层的结果进行指数概率的归一化,其中Li求得的是对于每个样本而言,它在所有类别中,被分类位正确的概率,syi代表样本被正确分类的评分,sj代表样本被分类为j的评分。 其实对于loss的求解很简单,主要的难度就在于对于梯度的求解。
loss的计算
根据上面的定义其实很容易计算loss,把所有样本i的Li加起来就是最终的结果,需要注意的是L的维度,和分数矩阵相同,最后再计算的时候用numpy.sum函数求矩阵的所有元素之和即可。另外需要考虑的就是归一化和正则化,归一化除以训练样本数就行。做作业的时候说的是用L2正则化,也就是把regularization_rate*W*W加在loss后面即可。

梯度grad的计算

以上这个式子的第一步其实是我们熟悉的链式求导法则,第二步是矩阵的求导法则,s是x与w的点乘结果,如果对某一个求导,结果就是另一个的转置。清楚了以上这个式子就可以进行正常的推导了。

单纯求导的结果会是这样,我们可以看出和s有关的有两个参数sj和syi,分别对应着正确分类的分数和其他所有分类的分数。
终于到了激动人心的推导过程。上面说了要分为正确的Syi和错误的Sj两种情况来计算,也就是j=yi和j≠yi两种。首先看j≠yi。这个时候我们是对sj来求导,那syi就是一个常数,分子后面的被减数的求和项只剩下sj的指数,这样求到之后得出的是probability(j)。

接下来用同样的思想,对syi来求导数,sj就是未知数。可以看出结果是probability(i)-1。

代码块
关于以上的求解,算法分为两种,一种是对于矩阵每个元素的细致计算,通过循环来实现,也叫做naive算法,这种算法直接按照公式去做,还是很简单的直接上代码:
def softmax_loss_naive(W, X, y, reg):
"""
Softmax loss function, naive implementation (with loops)
Inputs have dimension D, there are C classes, and we operate on minibatches
of N examples.
Inputs:
- W: A numpy array of shape (D, C) containing weights.
- X: A numpy array of shape (N, D) containing a minibatch of data.
- y: A numpy array of shape (N,) containing training labels; y[i] = c means
that X[i] has label c, where 0 <= c < C.
- reg: (float) regularization strength
Returns a tuple of:
- loss as single float
- gradient with respect to weights W; an array of same shape as W
"""
# Initialize the loss and gradient to zero.
loss = 0.0
dW = np.zeros_like(W)
for i in range(X.shape[0]):
scores = np.dot(X[i],W)
scores -= np.max(scores) #规范化数据
exp_scores = np.exp(scores)
s_y_i = np.exp(scores[y[i]])
sum_exp_scores_i = np.sum(exp_scores)
L_i = -np.log(s_y_i/sum_exp_scores_i) #计算Li
for j in range(W.shape[1]):
if j == y[i]: #正确列的修正
dW[:,y[i]] += (-1+s_y_i/sum_exp_scores_i)*X[i].T
else:
dW[:,j] += (np.exp(scores[j])/sum_exp_scores_i)*X[i].T #错误列的修正
loss += L_i
loss /= X.shape[0]
dW /= X.shape[0]
loss += 0.5*reg*np.sum(W*W) #regularization
dW += reg*W
return loss, dW
def softmax_loss_vectorized_modify(W, X, y, reg):
"""
Softmax loss function, vectorized version.
Inputs and outputs are the same as softmax_loss_naive.
"""
# Initialize the loss and gradient to zero.
loss = 0.0
scores = X.dot(W)
dW = np.zeros_like(scores)
s_max = np.reshape(np.max(scores,axis=1),(X.shape[0],1))
scores -= s_max #对于每一行样本规范化数据
prob = np.exp(scores)/np.sum(np.exp(scores),axis=1,keepdims=True)
correct = np.zeros_like(prob)
correct[range(X.shape[0]),y] = 1.0 #把每个样本的正确分类位置为1
loss += -np.sum(correct*np.log(prob))/X.shape[0] + 0.5*reg*np.sum(W*W) #loss就是按照公式计算
dW += (prob-correct)/X.shape[0] + 0.5*reg*np.sum(W*W) #correct矩阵只有在正确类才为1,错误类为0,也符合上面的推导
return loss, dW
总结
这是第一次写数学公式加代码过程,博主发现自己的数学还是很渣的,希望所有初学者能和博主一起努力,共勉吧。以后学到的东西都更新到这里。