深度学习笔记 - Softmax反向传播梯度计算推导过程

这里主要参考了深肚学习 - 超详细的softmax的反向传播梯度计算推导并扩展为n分类的情况。

对于深度学习多分类问题,神经网络最后一层常用softmax处理多项分布,这里我们设定n分类任务,推导其正向传播和反向传播中最后一层的相关计算公式。

正向传播

对于n分类任务,这里我们设定输出层的相关信息如下:

  • 输入:
    • 输出层神经元的输入是Z=[z1,z2,,zn]Z = [z_1,z_2,\cdot, z_n]
    • 分类真实标签是Y=[y1,y2,,yn]Y = [y_1, y_2, \cdot, y_n],其中YYone-shot分类标签,只有真实类别yk=1y_k=1,其余均为0。
  • 输出:
    • Y^=softmax(Z)=[y^1,y^2,,y^n]\hat{Y} = \text{softmax}(Z) = \left[\hat{y}_1, \hat{y}_2, \dots, \hat{y}_n\right],其中y^i\hat{y}_i代表属于第i类的条件概率P(y^iX)P(\hat{y}_i|X)

则对于n分类问题,其Softmax表达式为:

y^i=ezij=1nezj\hat{y}_i = \frac{e^{z_i}}{\sum_{j=1}^{n} e^{z_j}}

注意:这里为了后续计算方便,我们令S=j=1nezjS = \sum_{j=1}^{n} e^{z_j}

对应的Cost function

J=i=1nyilny^iJ = - \sum_{i=1}^{n}y_i \ln \hat{y}_i

这里代价函数函数选择了使用交叉熵,关于交叉熵的解释,请参考一文读懂信息熵、交叉熵和相对熵(KJ散度)

反向传播

这里我们假设第k个神经元对应的是正确的标签,也就是yk=1y_k=1,且注意YYone-shot分类标签。

第一步:求取JJy^i\hat{y}_i的偏导数

Jy^i=yiy^i\frac{\partial J}{\partial \hat{y}_i} = -\frac{y_i}{\hat{y}_i}

第二步:求取JJziz_i的偏导数

Jzi=j=1nJy^j×y^jzi=j=1n(yjy^j×y^jzi)\frac{\partial{J}}{\partial{z_i}} = \sum_{j=1}^{n} \frac{\partial{J}}{\partial{\hat{y}_j}} \times \frac{\partial{\hat{y}_j}}{\partial{z_i}} = \sum_{j=1}^{n}(-\frac{y_j}{\hat{y}_j} \times \frac{\partial{\hat{y}_j}}{\partial{z_i}})

这里之所以要使用j=1n\sum_{j=1}^{n},是因为在上述y^i\hat{y}_iSoftmax表达式中,能清晰看到其分母的求和中一定含有ezie^{z_i}这一项。

又因为只有yk=1y_k=1,其余的yi=0(ik)y_i=0(i \neq k),所以又可以写为如下表达式:

Jzi=yky^k×y^kzi=1y^k×y^kzi\frac{\partial{J}}{\partial{z_i}} = - \frac{y_k}{\hat{y}_k} \times \frac{\partial{\hat{y}_k}}{\partial{z_i}} = - \frac{1}{\hat{y}_k} \times \frac{\partial{\hat{y}_k}}{\partial{z_i}}

第三步:求取y^kzi\frac{\partial{\hat{y}_k}}{\partial{z_i}},这里需要分类讨论

  • 第一种情况:i=ki=k

y^kzi=y^kzk=ezk×S(ezk)2S2=ezkS(ezkS)2=y^k×(1y^k)\frac{\partial{\hat{y}_k}}{\partial{z_i}} = \frac{\partial{\hat{y}_k}}{\partial{z_k}} = \frac{e^{z_k}\times S - (e^{z_k})^{2}}{S^2} = \frac{e^{z_k}}{S} - (\frac{e^{z_k}}{S})^2 = \hat{y}_k \times (1 - \hat{y}_k)

  • 第二种情况:iki \neq k

y^kzi=0ezk×eziS2=y^k×y^i\frac{\partial{\hat{y}_k}}{\partial{z_i}} = \frac{0 - e^{z_k}\times e^{z_i}}{S^2} = - \hat{y}_k \times \hat{y}_i

第四步:直接代入整个Jz\frac{\partial{J}}{\partial{z}}表示

JZ=[Jz1JzkJzn]=[i=1nJy^iy^iz1i=1nJy^iy^izki=1nJy^iy^izn]=[Jy^ky^kz1Jy^ky^kzkJy^ky^kzn]=[1y^k(y^ky^1)1y^ky^k(1y^k)1y^k(y^ky^n)]=[y^1y^k1y^n]=Y^Y\frac{\partial J}{\partial Z} = \begin{bmatrix} \frac{\partial J}{\partial z_1} \\ \vdots \\ \frac{\partial J}{\partial z_k} \\ \vdots \\ \frac{\partial J}{\partial z_n} \end{bmatrix} = \begin{bmatrix} \sum_{i=1}^{n} \frac{\partial J}{\partial \hat{y}_i} \cdot \frac{\partial \hat{y}_i}{\partial z_1} \\ \vdots \\ \sum_{i=1}^{n} \frac{\partial J}{\partial \hat{y}_i} \cdot \frac{\partial \hat{y}_i}{\partial z_k} \\ \vdots \\ \sum_{i=1}^{n} \frac{\partial J}{\partial \hat{y}_i} \cdot \frac{\partial \hat{y}_i}{\partial z_n} \end{bmatrix} = \begin{bmatrix} \frac{\partial J}{\partial \hat{y}_k} \cdot \frac{\partial \hat{y}_k}{\partial z_1} \\ \vdots \\ \frac{\partial J}{\partial \hat{y}_k} \cdot \frac{\partial \hat{y}_k}{\partial z_k} \\ \vdots \\ \frac{\partial J}{\partial \hat{y}_k} \cdot \frac{\partial \hat{y}_k}{\partial z_n} \end{bmatrix} = \begin{bmatrix} -\frac{1}{\hat{y}_k} \cdot \left(-\hat{y}_k \hat{y}_1\right) \\ \vdots \\ -\frac{1}{\hat{y}_k} \cdot \hat{y}_k \left(1 - \hat{y}_k\right) \\ \vdots \\ -\frac{1}{\hat{y}_k} \cdot \left(-\hat{y}_k \hat{y}_n\right) \end{bmatrix} = \begin{bmatrix} \hat{y}_1 \\ \vdots \\ \hat{y}_k - 1 \\ \vdots \\ \hat{y}_n \end{bmatrix} = \hat{Y} - Y

简单验证代码(PyTorch)

1
2
3
4
5
6
7
8
9
10
11
import torch

x = torch.randn(4, requires_grad=True) # logits
t = torch.tensor([0, 0, 1, 0.], dtype=torch.float) # one-hot

y = torch.softmax(x, dim=0)
loss = -(t * torch.log(y)).sum()

loss.backward()
print("y:", y.detach().numpy())
print("grad z = y - t:", x.grad.numpy()) # 应等于 y - t

运行后可看到 x.grad 与公式yty - t一致,即为推导结果的直接验证。