反向传播算法的详细推导

本文由 AI 自动从英文版翻译

概述

计算多层感知机(MLP)的反向传播(BP)有几种方法。我们的目标是计算损失相对于每一层权重矩阵的梯度,这表示一个标量对矩阵的导数。我们可以使用以下方法:

  • 直接使用矩阵对矩阵的梯度计算 LW(l)\frac{\partial \mathcal{L}}{\partial \mathbf{W}^{(l)}}。(我们不会使用这种方法,因为矩阵对矩阵的梯度难以计算)
  • 直接计算 LW(l)\frac{\partial \mathcal{L}}{\partial \mathbf{W}^{(l)}},同时避免向量对矩阵的梯度。(我们也不会使用这种方法,因为它仍然相当具有挑战性)
  • 计算每个元素的 LWi,j(l)\frac{\partial \mathcal{L}}{\partial \mathbf{W}^{(l)}_{i,j}},然后将它们组装成矩阵。(这是我们将采用的方法)

对于我们选择的方法,虽然计算 LWi,j(l)\frac{\partial \mathcal{L}}{\partial \mathbf{W}^{(l)}_{i,j}} 足以确定所有梯度,你可以写一个 for 循环来完成更新,但这种方法效率不高。现代加速器(如 GPU)可以显著加速矩阵乘法运算。因此,我们仍然需要将标量结果组装成矩阵形式。

我们将首先检查单个样本的梯度计算。由于 SGD 算法需要一批样本,然后我们将结果扩展到处理批量数据。

预备知识

为了使我们的计算更加直接,我们首先介绍三个你应该理解的关键概念:分母布局、多元链式法则和矩阵组装。

分母布局:在深度学习社区中,默认使用分母布局计算导数。这意味着标量对矩阵的梯度将产生与原始矩阵相同形状的矩阵。如果结果是原始矩阵的转置,那么我们使用的是分母布局。你可以从这篇维基百科文章了解更多关于分母布局的信息。

多元链式法则:我们需要理解多元链式法则。如果 x=x(t)x=x(t)y=y(t)y=y(t),且 z=f(x,y)z=f(x,y),那么我们有 zt=fxxt+fyyt\frac{\partial z}{\partial t}=\frac{\partial f}{\partial x}\frac{\partial x}{\partial t}+\frac{\partial f}{\partial y}\frac{\partial y}{\partial t}。这里有一篇关于多元链式法则的文章。由于我们以标量方式计算梯度,接受向量或矩阵的函数变成多元函数。例如,f(x)f(\mathbf{x}) 变成 f(x0,x1,,xn)f(\mathbf{x}_0, \mathbf{x}_1, \cdots, \mathbf{x}_n)。因此,在以下推导中,我们将始终使用多元链式法则。

矩阵组装:最后,我们需要理解如何组装向量或矩阵。对于矩阵 W\mathbf{W},我们可以将其写为 W=[Wi,j]i,j\mathbf{W}=[\mathbf{W}_{i,j}]_{i,j},其中索引 iijj 表示我们需要遍历每一行和每一列。类似地,对于向量,我们有 v=[vi]i\mathbf{v}=[\mathbf{v}_i]_i。组装可以看作是矩阵乘法的逆过程。例如,两个列向量的乘法 xy=[xiyj]i,j\mathbf{xy}^\top=[\mathbf{x}_i\mathbf{y}_j]_{i,j}。因此,如果我们得到标量结果,其中 Wi,j=xiyj\mathbf{W}_{i,j}=\mathbf{x}_i\mathbf{y}_j,我们可以将其组装成矩阵 W=xyT\mathbf{W}=\mathbf{x}\mathbf{y}^T

定义

符号

  • 标量:x,y,x, y, \cdots
  • 向量:x,y,\mathbf{x}, \mathbf{y}, \cdots
  • 矩阵:X,Y,\mathbf{X}, \mathbf{Y}, \cdots
  • 下标符号:xi\mathbf{x}_i 表示向量 x\mathbf{x} 的第 ii 个元素,这是一个标量。
  • 指示函数:1ij\mathbf{1}_{ij} 如果 i=ji=j 则等于 1,否则为 0。

网络架构

  • 输入:x\mathbf{x},形状为 [n,1][n, 1]
  • 标签:y\mathbf{y},形状为 [c,1][c, 1](独热编码)
  • 层数:LL
  • 类别数:cc
  • 线性变换:Wx+b\mathbf{Wx+b}
  • ll 层的权重矩阵:W(l)\mathbf{W}^{(l)}

权重矩阵形状

  • 第一层:W(1)\mathbf{W}^{(1)},形状为 [m(1),m(0)=n][m^{(1)}, m^{(0)}=n]
  • 隐藏层(从 2 到 L1L-1):W(l)\mathbf{W}^{(l)},形状为 [m(l),m(l1)][m^{(l)}, m^{(l-1)}]
  • 最后一层:W(L)\mathbf{W}^{(L)},形状为 [c,m(L1)][c, m^{(L-1)}]

激活函数和激活值

  • 激活函数:ff
  • ll 层的激活值:a(l)\mathbf{a}^{(l)}

激活值形状

  • 输入:a(0)\mathbf{a}^{(0)},形状为 [n,1][n, 1]
  • 隐藏层激活值(从 1 到 L1L-1):a(l)\mathbf{a}^{(l)},形状为 [m(l),1][m^{(l)}, 1]
  • 输出 logits(最后一层):a(L)\mathbf{a}^{(L)},形状为 [c,1][c, 1]

前向传播

a(0)=xz(1)=W(1)a(0)+b(1)a(1)=f(z(1))z(l)=W(l)a(l1)+b(l)a(l)=f(z(l1))z(L)=W(L)a(L1)+b(L)a(L)=softmax(z(L1))L=CE(a(L),y)\begin{align*} \mathbf{a}^{(0)} &= \mathbf{x} \\ \mathbf{z}^{(1)} &= \mathbf{W}^{(1)}\mathbf{a}^{(0)} + \mathbf{b}^{(1)} \\ \mathbf{a}^{(1)} &= f(\mathbf{z}^{(1)}) \\ \vdots & \quad\vdots\\ \mathbf{z}^{(l)} &= \mathbf{W}^{(l)}\mathbf{a}^{(l-1)} + \mathbf{b}^{(l)} \\ \mathbf{a}^{(l)} &= f(\mathbf{z}^{(l-1)}) \\ \vdots & \quad\vdots\\ \mathbf{z}^{(L)} &= \mathbf{W}^{(L)}\mathbf{a}^{(L-1)} + \mathbf{b}^{(L)} \\ \mathbf{a}^{(L)} &= \text{softmax}(\mathbf{z}^{(L-1)}) \\ \mathcal{L} &=\text{CE}(\mathbf{a}^{(L)},y) \\ \end{align*}

其中交叉熵损失为 L=CE(a(L),y)=i=1cyilog(ai(L))\mathcal{L}=\text{CE}(\mathbf{a}^{(L)},y)=-\sum_{i=1}^c \mathbf{y}_i\log(\mathbf{a}_i^{(L)})

我们的目标是计算 L\mathcal{L} 相对于 W(l)\mathbf{W}^{(l)}b(l)\mathbf{b}^{(l)} 的梯度。在这个推导中,我们将专注于计算相对于 W(l)\mathbf{W}^{(l)} 的梯度。相对于 b(l)\mathbf{b}^{(l)} 的梯度遵循类似的模式。

最后一层

由于最后一层与其他层不同(它使用 softmax 而不是常规激活函数),我们将单独计算其梯度。

在前向传播中,ai(L)=ezik=1cezk\mathbf{a}^{(L)}_i=\frac{e^{\mathbf{z}_i}}{\sum_{k=1}^c e^{\mathbf{z}_k}} 表示 softmax 层输出的归一化概率。softmax 梯度的直接计算得到 ai(L)zj(L)=ai(L)(1ijaj(L))\frac{\partial \mathbf{a}^{(L)}_i}{\partial \mathbf{z}^{(L)}_j}=\mathbf{a}^{(L)}_i(\mathbf{1}_{ij}-\mathbf{a}^{(L)}_j)

为了计算梯度 Lzj(L)\frac{\partial \mathcal{L}}{\partial \mathbf{z}_j^{(L)}},我们通过引入激活值 a\mathbf{a} 使用链式法则。这里的一个重要点是,当我们在标量形式下工作时,我们遇到的大多数函数都是多元函数,因此我们需要使用多元链式法则。例如,L\mathcal{L} 是应用于 a0(L),a1(L),,ac1(L)\mathbf{a}_0^{(L)}, \mathbf{a}_1^{(L)}, \dots, \mathbf{a}_{c-1}^{(L)} 的交叉熵函数的结果。根据多元链式法则,我们有 Lzj(L)=i=1cLai(L)ai(L)zj(L)\frac{\partial \mathcal{L}}{\partial \mathbf{z}_j^{(L)}}=\sum_{i=1}^c\frac{\partial \mathcal{L}}{\partial \mathbf{a}_i^{(L)}}\frac{\partial \mathbf{a}_i^{(L)}}{\partial \mathbf{z}_j^{(L)}}

因此,我们有

Lzj(L)=i=1cLai(L)ai(L)zj(L)=i=1c(yiai(L))ai(L)zj(L)=i=1cyi(1ijaj(L))=i=1cyi1ij+aj(L)i=1cyi=aj(L)yj\begin{align*} \frac{\partial \mathcal{L}}{\partial \mathbf{z}_j^{(L)}} &= \sum_{i=1}^c\frac{\partial \mathcal{L}}{\partial \mathbf{a}_i^{(L)}}\frac{\partial \mathbf{a}_i^{(L)}}{\partial \mathbf{z}_j^{(L)}} \\ & =\sum_{i=1}^c(\frac{\mathbf{y}_i}{\mathbf{a}_i^{(L)}})\frac{\partial \mathbf{a}_i^{(L)}}{\partial \mathbf{z}_j^{(L)}} \\ &=-\sum_{i=1}^c\mathbf{y}_i(\mathbf{1}_{ij}-\mathbf{a}^{(L)}_j) \\ &=-\sum_{i=1}^c\mathbf{y}_i\mathbf{1}_{ij}+\mathbf{a}_j^{(L)}\sum_{i=1}^c\mathbf{y}_i \\ &=\mathbf{a}^{(L)}_j-\mathbf{y}_j \end{align*}

现在我们将 Lzj(L)\frac{\partial \mathcal{L}}{\partial \mathbf{z}_j^{(L)}} 组装成向量:Lz(L)=a(L)y\frac{\partial \mathcal{L}}{\partial \mathbf{z}^{(L)}}=\mathbf{a}^{(L)}-\mathbf{y}。详细的组装过程是 Lz(L)=[Lzj(L)]j=1c=[aj(L)yj]j=1c=[aj(L)]j=1c[yj]j=1c=a(L)y\frac{\partial \mathcal{L}}{\partial \mathbf{z}^{(L)}}=[\frac{\partial \mathcal{L}}{\partial \mathbf{z}_j^{(L)}}]_{j=1}^{c}=[\mathbf{a}^{(L)}_j-\mathbf{y}_j]_{j=1}^{c}=[\mathbf{a}^{(L)}_j]_{j=1}^{c}-[\mathbf{y}_j]_{j=1}^{c}=\mathbf{a}^{(L)}-\mathbf{y}

权重梯度

我们可以将误差项定义为: δ(l)=Lz(l)\mathbf{\delta}^{(l)}=\frac{\partial \mathcal{L}}{\partial \mathbf{z}^{(l)}} 这是一个列向量,简化了我们的链式法则计算。对于最后一层,δ(L)=a(L)y\mathbf{\delta}^{(L)}=\mathbf{a}^{(L)}-\mathbf{y}

如果我们能够计算所有层 llδ(l)\mathbf{\delta}^{(l)},那么我们可以计算所有层的 LW(l)\frac{\partial \mathcal{L}}{\partial \mathbf{W}^{(l)}}。这个计算使用多元链式法则。由于我们想使用链式法则通过 z\mathbf{z} 连接 L\mathcal{L}W\mathbf{W},我们需要使用多元链式法则:LWi,j(l)=kLzk(l)zk(l)Wi,j(l)\frac{\partial \mathcal{L}}{\partial \mathbf{W}^{(l)}_{i,j}}=\sum_{k}\frac{\partial \mathcal{L}}{\partial \mathbf{z}_k^{(l)}}\frac{\partial \mathbf{z}_k^{(l)}}{\partial \mathbf{W}^{(l)}_{i,j}}

让我们回顾一下 z(l)=W(l)a(l1)+b(l)\mathbf{z}^{(l)}= \mathbf{W}^{(l)}\mathbf{a}^{(l-1)} + \mathbf{b}^{(l)} 中的矩阵乘法。我们有 zi(l)=j=1nWi,j(l)aj(l1)+bi(l)\mathbf{z}^{(l)}_i=\sum_{j=1}^n \mathbf{W}^{(l)}_{i,j}\mathbf{a}^{(l-1)}_j+\mathbf{b}^{(l)}_i。由此,我们可以看到 zi(l)Wi,j(l)=aj(l1)\frac{\partial \mathbf{z}^{(l)}_i}{\partial \mathbf{W}^{(l)}_{i,j}}=\mathbf{a}^{(l-1)}_j,对于 iki\neq kzk(l)Wi,j(l)=0\frac{\partial \mathbf{z}^{(l)}_k}{\partial \mathbf{W}^{(l)}_{i,j}}=0(即 Wi,j(l)\mathbf{W}_{i,j}^{(l)} 只影响 zi(l)\mathbf{z}_i^{(l)} 的计算)。因此,我们有:

LWi,j(l)=Lzi(l)zi(l)Wi,j(l)=δi(l)aj(l1)\frac{\partial \mathcal{L}}{\partial \mathbf{W}^{(l)}_{i,j}}=\frac{\partial \mathcal{L}}{\partial \mathbf{z}_i^{(l)}}\frac{\partial \mathbf{z}_i^{(l)}}{\partial \mathbf{W}^{(l)}_{i,j}}=\mathbf{\delta}_i^{(l)}\mathbf{a}_j^{(l-1)}

现在让我们将结果组装成矩阵形式。我们有 LW(l)=[LWi,j(l)]i,j=[δi(l)aj(l1)]i,j=δ(l)a(l1)\frac{\partial \mathcal{L}}{\partial \mathbf{W}^{(l)}}=[\frac{\partial \mathcal{L}}{\partial \mathbf{W}^{(l)}_{i,j}}]_{i,j}=[\mathbf{\delta}_i^{(l)}\mathbf{a}_j^{(l-1)}]_{i,j}=\mathbf{\delta}^{(l)}\mathbf{a}^{(l-1)\top}

特别是对于最后一层,我们有 LWi,j(L)=(ai(L)yi)aj(L1)\frac{\partial \mathcal{L}}{\partial \mathbf{W}^{(L)}_{i,j}}=(\mathbf{a}^{(L)}_i-\mathbf{y}_i)\mathbf{a}_j^{(L-1)},以及 LW(L)=(a(L)y)a(L1)\frac{\partial \mathcal{L}}{\partial \mathbf{W}^{(L)}}=(\mathbf{a}^{(L)}-\mathbf{y})\mathbf{a}^{(L-1)\top}

层间反向传播

现在唯一剩下的任务是计算所有层 llδ(l)\mathbf{\delta}^{(l)}。我们可以使用链式法则计算所有层的 δ(l)\mathbf{\delta}^{(l)}

δj(l1)=Lzj(l1)=kLzk(l)zk(l)zj(l1)=kLzk(l)zk(l)ak(l1)ak(l1)zj(l1)\begin{align*} \mathbf{\delta}_j^{(l-1)} &= \frac{\partial \mathcal{L}}{\partial \mathbf{z}_j^{(l-1)}} \\ &= \sum_{k}\frac{\partial \mathcal{L}}{\partial \mathbf{z}_k^{(l)}}\frac{\partial \mathbf{z}_k^{(l)}}{\partial \mathbf{z}_j^{(l-1)}} \\ &= \sum_{k}\frac{\partial \mathcal{L}}{\partial \mathbf{z}_k^{(l)}}\frac{\partial \mathbf{z}_k^{(l)}}{\partial \mathbf{a}_k^{(l-1)}}\frac{\partial \mathbf{a}_k^{(l-1)}}{\partial \mathbf{z}_j^{(l-1)}} \\ \end{align*}

第二步成立是因为 zj(l1)\mathbf{z}_j^{(l-1)} 通过线性变换贡献给每个 zk(l)\mathbf{z}_k^{(l)}。第三步是因为 zj(l1)\mathbf{z}_j^{(l-1)} 只通过非线性变换影响 aj(l1)\mathbf{a}_j^{(l-1)}

由于 aj(l1)=f(zj(l1))\mathbf{a}_j^{(l-1)}=f(\mathbf{z}_j^{(l-1)}),我们有 aj(l1)zj(l1)=f(zj(l1))\frac{\partial \mathbf{a}_j^{(l-1)}}{\partial \mathbf{z}_j^{(l-1)}}=f'(\mathbf{z}_j^{(l-1)})。由于 z(l)=W(l)a(l1)+b(l)\mathbf{z}^{(l)}=\mathbf{W}^{(l)}\mathbf{a}^{(l-1)}+\mathbf{b}^{(l)},从矩阵乘法,我们有 zk(l)aj(l1)=Wk,j(l)\frac{\partial \mathbf{z}_k^{(l)}}{\partial \mathbf{a}_j^{(l-1)}}=\mathbf{W}^{(l)}_{k,j}。因此,我们有:

δj(l1)=kδk(l)Wk,j(l)f(zj(l1))\mathbf{\delta}_j^{(l-1)}=\sum_{k}\mathbf{\delta}_k^{(l)}\mathbf{W}^{(l)}_{k,j}f'(\mathbf{z}_j^{(l-1)})

现在让我们将结果组装成向量形式。我们有:

δ(l1)=[δj(l1)]j=1m=[kδk(l)Wk,j(l)f(zj(l1))]j=1m=[kδk(l)Wk,j(l)]j=1m[f(zj(l1))]j=1m=[W:,j(l)δ(l)]j=1mf(z(l1))=W(l)δ(l)f(z(l1))\begin{align*} \mathbf{\delta}^{(l-1)} &=[\mathbf{\delta}_j^{(l-1)}]_{j=1}^m=[\sum_{k}\mathbf{\delta}_k^{(l)}\mathbf{W}^{(l)}_{k,j}f'(\mathbf{z}_j^{(l-1)})]_{j=1}^m \\ &=[\sum_{k}\mathbf{\delta}_k^{(l)}\mathbf{W}^{(l)}_{k,j}]_{j=1}^m\odot[f'(\mathbf{z}_j^{(l-1)})]_{j=1}^m \\ &=[\mathbf{W}^{(l)\top}_{:,j} \mathbf{\delta}^{(l)}]_{j=1}^m\odot f'(\mathbf{z}^{(l-1)}) \\ &=\mathbf{W}^{(l)\top}\mathbf{\delta}^{(l)}\odot f'(\mathbf{z}^{(l-1)}) \end{align*}

批量处理

我们可以将上述计算扩展到处理批量数据。在我们之前的讨论中,每个样本被表示为列向量,但在深度学习编程中,我们通常将样本表示为矩阵中的行。我们有 X=[x1,x2,,xb]\mathbf{X}=[\mathbf{x}_1^\top,\mathbf{x}_2^\top,\cdots,\mathbf{x}_b^\top],形状为 [b,n][b, n],其中 xi\mathbf{x}_i 是列向量。类似地,我们有 Y=[y1,y2,,yb]\mathbf{Y}=[\mathbf{y}_1,\mathbf{y}_2,\cdots,\mathbf{y}_b],形状为 [b,c][b, c],其中 yi\mathbf{y}_i 是列向量。

总损失为 L=1bi=1bL(xi,yi)\mathcal{L}=\frac{1}{b}\sum_{i=1}^b\mathcal{L}(\mathbf{x}_i,\mathbf{y}_i)。向量 a(l),z(l),δ(l)\mathbf{a}^{(l)},\mathbf{z}^{(l)},\mathbf{\delta}^{(l)} 分别变成矩阵 A(l),Z(l),Δ(l)\mathbf{A}^{(l)},\mathbf{Z}^{(l)},\mathbf{\Delta}^{(l)},形状均为 [b,m(l)][b, m^{(l)}]。对于线性变换,我们有:

Z(l)=A(l1)W(l)+B(l)\mathbf{Z}^{(l)}=\mathbf{A}^{(l-1)}\mathbf{W}^{(l)\top}+\mathbf{B}^{(l)}

其中 B(l)\mathbf{B}^{(l)} 是通过在所有样本上堆叠偏置向量 b(l)\mathbf{b}^{(l)} 形成的。对于非线性变换,我们有 A(l)=f(Z(l))\mathbf{A}^{(l)}=f(\mathbf{Z}^{(l)})。(或者,你可以将权重矩阵 W\mathbf{W} 定义为我们原始定义的转置,以避免线性变换中的转置)。

注意,由于我们之前对单个向量的讨论是按元素进行的,对于批量数据的矩阵情况的推导遵循类似的模式。

损失和 Softmax

对于损失和 softmax,LZi,j(L)=1b(Ai,j(L)Yi,j)\frac{\partial \mathcal{L}}{\partial \mathbf{Z}^{(L)}_{i,j}}=\frac{1}{b}(\mathbf{A}^{(L)}_{i,j}-\mathbf{Y}_{i,j})。组装过程很简单,得到 LZ(L)=1b(A(L)Y)\frac{\partial \mathcal{L}}{\partial \mathbf{Z}^{(L)}}=\frac{1}{b}(\mathbf{A}^{(L)}-\mathbf{Y})

权重梯度

对于权重的梯度,更新等价于 Z(l)=W(l)A(l1)+B(l)\mathbf{Z}^{(l)\top}=\mathbf{W}^{(l)}\mathbf{A}^{(l-1)\top}+\mathbf{B}^{(l)\top},这与向量形式更相似。由于权重参与每个样本的计算,使用多元链式法则,我们有:

LWi,j(l)=k=1bLZi,k(l)Zi,k(l)Wi,j(l)=k=1bΔi,k(l)Ak,j(l1)\frac{\partial\mathcal{L}}{\partial \mathbf{W}_{i,j}^{(l)}}=\sum_{k=1}^{b}\frac{\partial \mathcal{L}}{\partial \mathbf{Z}_{i,k}^{(l)}}\frac{\partial \mathbf{Z}_{i,k}^{(l)}}{\partial \mathbf{W}_{i,j}^{(l)}}=\sum_{k=1}^b\mathbf{\Delta}_{i,k}^{(l)}\mathbf{A}_{k,j}^{(l-1)}

组装后,我们有:

LW(l)=Δ(l)A(l1)\frac{\partial \mathcal{L}}{\partial \mathbf{W}^{(l)}}=\mathbf{\Delta}^{(l)\top}\mathbf{A}^{(l-1)}

让我们进行快速形状检查以验证我们的结果。LW(l)\frac{\partial \mathcal{L}}{\partial \mathbf{W}^{(l)}} 应该与 W(l)\mathbf{W}^{(l)} 具有相同的形状,即 [m(l),m(l1)][m^{(l)}, m^{(l-1)}]Δ(l)\mathbf{\Delta}^{(l)\top} 的形状为 [m(l),b][m^{(l)}, b]A(l1)\mathbf{A}^{(l-1)} 的形状为 [b,m(l1)][b, m^{(l-1)}]。因此,Δ(l)A(l1)\mathbf{\Delta}^{(l)\top}\mathbf{A}^{(l-1)} 的形状为 [m(l),m(l1)][m^{(l)}, m^{(l-1)}],与 W(l)\mathbf{W}^{(l)} 匹配。

层间反向传播

最后,对于从 Δ(l)\mathbf{\Delta}^{(l)}Δ(l1)\mathbf{\Delta}^{(l-1)} 的更新,由于每个样本彼此独立,很容易看出 Δ(l1)=W(l)Δ(l)f(Z(l1))\mathbf{\Delta}^{(l-1)\top}=\mathbf{W}^{(l)\top}\mathbf{\Delta}^{(l)\top}\odot f'(\mathbf{Z}^{(l-1)\top}),这意味着 Δ(l1)=Δ(l)W(l)f(Z(l1))\mathbf{\Delta}^{(l-1)}=\mathbf{\Delta}^{(l)}\mathbf{W}^{(l)}\odot f'(\mathbf{Z}^{(l-1)})。这是从向量形式的直接扩展。

实现

现在我们有了反向传播算法的完整推导:

Δ(L)=1b(A(L)Y)Δ(l)=Δ(l+1)W(l+1)f(Z(l))LW(l)=Δ(l)A(l1)\begin{align*} \mathbf{\Delta}^{(L)} & = \frac{1}{b}(\mathbf{A}^{(L)}-\mathbf{Y}) \\ \mathbf{\Delta}^{(l)} & = \mathbf{\Delta}^{(l+1)}\mathbf{W}^{(l+1)}\odot f'(\mathbf{Z}^{(l)}) \\ \frac{\partial \mathcal{L}}{\partial \mathbf{W}^{(l)}} & = \mathbf{\Delta}^{(l)\top}\mathbf{A}^{(l-1)} \\ \end{align*}

现在我们可以轻松实现反向传播。没有偏置的多层感知机的伪代码如下。注意 W\mathbf{W} 矩阵严格遵循上述定义,并与 PyTorch 的 nn.Linear 一致。

# PyTorch-style API implementation
# W is a list of weight matrices
 
def forward_pass(X, W):
    L = len(W)
    A, Z = [], []
    for l in range(L-1):
        Z[l] = A[l-1] @ W[l].T
        A[l] = f(Z[l])
    Z[L-1] = A[L-2] @ W[L-1]
    A[L-1] = softmax(Z[L-1])
    L = -np.sum(Y * np.log(A[L-1]))
    return L, A, Z
 
def backward_pass(Y, A, Z, W):
    L = len(W)
    Delta, W_g = [], []
    Delta[L-1] = (A[L-1] - Y) / Y.shape[0]
    for l in range(L-2, 0, -1):
        Delta[l] = Delta[l+1] @ W[l+1] * d_f(Z[l])
        W_g[l] = Delta[l+1].T @ A[l-1]
    return W_g
 
L, A, Z = forward_pass(X, W)
W_g = backward_pass(Y, A, Z, W)  # Equivalent to PyTorch's L.backward()