softmax的loss和gradient推导过程

softmax的loss和gradient推导过程

  相信搞deeplearning的各位大牛都很熟悉softmax了,用来对得分矩阵做归一化得到概率的一种分类手段,我这两天在做cs231n的作业,新手上路,只作为自己的学习足迹记录,还望各位大佬多多包涵。

  • 简单介绍
  • Softmax的loss计算
  • Softmax的grad计算
  • naive loop
  • vectorization

简单介绍

每个样本的loss

  这个公式是大家非常熟悉的,其实就是对于神经网络最后一层的结果进行指数概率的归一化,其中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

总结

这是第一次写数学公式加代码过程,博主发现自己的数学还是很渣的,希望所有初学者能和博主一起努力,共勉吧。以后学到的东西都更新到这里。