反向传播:误差为什么必须从后往前传

1. 一个两层小网络跑一遍:前向到底算出了什么

先来看一个比神经网络更简单的电路。取表达式 f(x, y, z) = (x + y) * z,输入 x = -2, y = 5, z = -4。算第一遍:

q = x + y = 3
f = q * z = -12

这只是普通的算术。但把它画成一张电路图,前后两个方向都能跑:

图注:电路图上每个端口都有两个数字:绿色是前向算出的数值(x=-2, y=5, z=-4 走到 q=3 再走到 f=-12),红色是从输出端往回传的梯度(最后那个 1 是起点)。前向(黑箭头)沿 wire 算数值,反向(红字)沿同一批 wire 回算梯度——同一张图,两个方向。

这张图是 Stanford CS231n 课程笔记里的标准电路教学图,它的结构比真实神经网络简单得多,但前向数值与反向梯度这两个方向完全对应你以后在任何神经网络里看到的东西CS231n。

现在把上面这个电路升级成"两层小网络"。结构如下:

  • 第一层:把输入向量 x 做一次线性变换 z^(1) = W^(1) x + b^(1),再过一个激活函数 a^(1) = σ(z^(1))。
  • 第二层:再做一次 z^(2) = W^(2) a^(1) + b^(2),a^(2) = σ(z^(2)) 就是预测值 ŷ。
  • 最后算损失 L,比如二元交叉熵(BCE)。

激活函数 σ 这里用 sigmoid,理由是它的导数有漂亮化简 σ'(z) = σ(z) * (1 - σ(z)),后面反向传播时可以直接代入CS231n。

把变量名整理一下,下面六章都会反复用到这张表:

符号含义
x输入向量
W^(l), b^(l)第 l 层的权重矩阵与偏置向量
z^(l)第 l 层加权输入(线性部分)
a^(l)第 l 层激活值(过完激活函数),特例 a^(L) = ŷ
L损失(loss),目标就是把它降下来

前向到底算出了什么? 输入 x 沿 Wx + b → σ 一路变换到最后一层吐出一个预测 ŷ,再和标准答案 y 比一比压成一个损失 L。这个 L 不是凭空冒出来的数字——它是前向一路算到底、在终点处被定义的那个量。这就是我们要"往回传"的对象:误差不是一个抽象名词,它就是 L 本身,下一章就要回答"为什么这个数必须从输出端往输入端回传"。

最后留一条时间注释:反向传播不是 1986 年才冒出来的新东西。1974 年,Werbos 在博士论文中以 dynamic feedback 描述了沿有序运算表回算导数的方法Werbos 1974;1986 年 Rumelhart、Hinton、Williams 在 Nature 上把这件事带回主流研究,1986 年论文后来成为反向传播传播史上的经典时间锚点Rumelhart et al. 1986。在 PyTorch 等常见框架中,loss.backward() 是启动自动反向计算的一种标准入口;其底层是沿计算图应用链式法则的 reverse-mode 自动微分D2L §2.5。

2. 为什么误差必须从后往前传:责任分摊与一次回传的高效性

上一章我们让网络跑了一遍前向,得到了一个具体的损失 L。接下来要问:训练网络的目的是降低 L,怎么降?梯度下降要求对每个权重 w 算出 ∂L/∂w("动一下这个权重,损失会变多少")。问题来了:这个梯度为什么必须从输出端往输入端回传,而不是从前往后推?

直觉一:责任分摊。预测错了,每个权重各该分多少责?靠近输出的权重直接连着预测值,责大;远离输出的权重只是中间环节,责小但还要靠它上一层的接力才能算出来。这种"责任从输出端一层一层往前分摊"的画面,正是反向传播算法想表达的事。

直觉二:计算效率。如果不走反向,而是给每个权重单独做一次数值微分(∂L/∂w ≈ (L(w+ε) - L(w)) / ε),网络有几万个权重就要跑几万次前向。Reverse-mode autodiff(反向模式自动微分)只需要一次前向 + 一次回传就能拿到损失对所有参数的梯度,这就是神经网络训练里默认用 reverse-mode 的根本原因Olah 2015。

下面用一张具体计算图把"责任分摊"这件事讲透。考虑表达式 e = (a + b) * (b + 1),输入 a = 2, b = 1。前向算出 c = a + b = 3、d = b + 1 = 2、e = c * d = 6。

节点是变量(或运算结果),有向边是依赖关系,边上的小数字是"局部导数"——只看这一小段、上游动一下下游跟着变多少。现在问 ∂e/∂b 是多少?b 影响 e 有两条路径:b → c → e 与 b → d → e。多元链式法则说:把所有路径上的局部导数乘起来,再加起来Olah 2015。

b → c → e:  1 * 2 = 2
b → d → e:  1 * 3 = 3
合计: ∂e/∂b = 2 + 3 = 5

这就是"责任分摊"的算术形态。如果 e 的梯度从 1 开始往回传,经过 e → c 与 e → d 时分别乘上局部导数 d=2 与 c=3,得到两条 wire 上的梯度 -2 与 -3;再往回,b 同时连着 c 与 d,两条梯度累加得到 5——正是多元链式法则的结论。代码里要写成 grad += ... 而不是 grad = ...,就是因为分支节点的梯度要累加Karpathy 2015。

那么,为什么必须从后往前传?看一张对照表:

维度Forward-mode(前向模式)Reverse-mode(反向模式)
起点某个输入某个输出(这里是损失 L)
传播方向沿 wire 往输出端沿 wire 往输入端
一次扫描得到一个输入对所有节点的影响所有节点对一个输出的影响
适合的形状输入少、输出多输入多、输出少

神经网络里"参数极多、损失是一个标量",正好是 reverse-mode 的主场Olah 2015。一次从 L=1 出发回传,所有参数的梯度一次性拿到。如果反过来用 forward-mode 一个个输入地扫,要扫到地老天荒。这就是为什么"从后往前传"不是约定俗成,而是计算代价决定的硬规则。

为了把这个抽象落到数字上,回到第 1 章那个 (x+y)z 电路,x=-2, y=5, z=-4,前向 q=3, f=-12。反向从 df=1 出发:

步骤这一步做了什么局部导数 × 上游梯度结果
f = q*z反向到 q 与 z∂f/∂q = z = -4、∂f/∂z = q = 3dq = -4、dz = 3
q = x+y反向到 x 与 y∂q/∂x = 1、∂q/∂y = 1dx = 1 * (-4) = -4、dy = 1 * (-4) = -4

每一步都是同一个动作的重复:梯度 = 局部导数 × 上游梯度,整条规则就这一句话Karpathy 2015。

最后说一句注释:今天所有框架(PyTorch、TensorFlow、JAX 等)内部都是 reverse-mode 自动微分引擎,调用一行 loss.backward(),框架就在后台把这张计算图沿 wire 反向走一遍、把梯度填进每个 .gradPyTorch autograd。下面这张图就是 PyTorch 真实运行时的内部数据结构——计算图不是比喻,是真存在的有向无环图:

图注:PyTorch 教程里给出的真实计算图:顶部两个蓝色矩形叶子节点(shape (2)),下方三个灰色 grad_fn 节点(PowBackward0、MulBackward0、PowBackward0),最底部一个绿色 SubBackward0 接收它们传上来的信号。这不是示意图,是 .backward() 真正在跑的数据结构。

到这里,方向感已经清楚了:梯度从后往前传,是因为 reverse-mode 在"参数极多、损失一个标量"的形状下,能一次回传就拿到所有参数的梯度,比正向逐个参数做数值微分高效得多。本质上它在做的事是"责任从输出端一层一层往前分摊"。下一章把这个方向上的规则写成四条具体公式。

3. 链式法则怎么一层层套上去:BP1–BP4 四条公式

上一章我们用 (x+y)z 这个极小电路看见了"梯度 = 局部导数 × 上游梯度"的微观规则。现在把它从一层推向多层网络,把规则写成四条全局公式(教材里统称 BP1–BP4)Nielsen 在线书 Ch.2。

先看一张依赖图,把"局部导数相乘"这件事从图里带出来:

图注:七个变量 w^(L)(蓝)、a^(L-1)(黑)、b^(L)(粉)、z^(L)(绿)、a^(L)、y(黄)、C₀(红)之间的依赖关系。灰色连线显示谁喂给谁,每个变量旁的小数轴标出当前值。这是 3Blue1Brown 教程里的标准依赖图,用来给链式法则铺路3Blue1Brown。

链式法则说的是:损失 C₀ 对权重 w^(L) 的导数,等于沿这条因果链上每段局部导数相乘:

∂C₀/∂w^(L) = (∂z^(L)/∂w^(L)) · (∂a^(L)/∂z^(L)) · (∂C₀/∂a^(L))

这一行是后面所有 BP 公式的母版。把它一层层往下推、写到每一层,就得到 BP1–BP4。

先把一个核心记号钉死:误差项 δ^l。第 l 层每个神经元 j 上定义

δ^l_j ≡ ∂C / ∂z^l_j

它说的是"这一层的加权输入该为损失负多少责"。为什么选 z 而不是 a?纯粹是代数上更简洁——选 a 也能算,但多绕一步Nielsen 在线书 Ch.2。

BP1:输出层误差。最后一层(第 L 层)的 δ 直接由损失对激活的梯度乘上激活函数导数得到:

δ^L = ∇_a C ⊙ σ'(z^L)

这里的 ⊙ 是逐元素乘(Hadamard 积),不是矩阵乘。两边向量同维度,逐位相乘Nielsen 在线书 Ch.2。

BP2:把 δ 往上一层传。如果第 l+1 层的 δ 已知,第 l 层的 δ 怎么算?

δ^l = ((W^(l+1))^T · δ^(l+1)) ⊙ σ'(z^l)

W^T 在这里出现是有具体原因的,不是代数巧合。δ^(l+1) 的长度等于第 l+1 层神经元数,W^(l+1) 的形状是 (l+1 层神经元数) × (l 层神经元数),要乘出一个长度等于第 l 层神经元数的向量才能与左边的 δ^l 形状对齐,必须把 W^(l+1) 转置过来Nielsen 在线书 Ch.2。注意这里是矩阵乘,不是 Hadamard;乘完再用 ⊙ σ'(z^l) 逐元素折算敏感度。

BP3:偏置的梯度。这一层的偏置为损失负多少责,等于这一层的 δ:

∂C/∂b^l = δ^l

BP4:权重的梯度。这一层每一个权重的梯度,等于"输入侧激活"乘以"输出侧 δ":

∂C/∂w^l = a_in × δ_out

这里 a_in 是这个权重输入端的激活值,δ_out 是输出端的误差项。直观上是把"上一层的火有多旺"和"下一层喊回来的责任"乘起来,看这个权重在两者之间该负多少责Nielsen 在线书 Ch.2。下面这张图就是 BP4 公式 ∂C/∂w = a_in × δ_out 的最小可视化锚:

图注:左右两个圆形节点(神经元)由一条水平短线段(权重)相连,线上方写着 ∂C/∂w =,左圆旁标 a_in,中右位置标 × δ_out。每个权重的梯度 = 输入侧激活 × 输出侧 δ——这张图就是这条公式的图像版Nielsen 在线书 Ch.2。

把四条公式放在同一张总览里:

编号公式一句话中文翻译它回答的问题
BP1δ^L = ∇_a C ⊙ σ'(z^L)输出与目标的差距有多刺眼,乘上激活在这一点的敏感度输出层怎么算 δ?
BP2δ^l = ((W^(l+1))^T · δ^(l+1)) ⊙ σ'(z^l)后面一层喊回来的责任,乘上我对这一层 z 的敏感度隐藏层怎么算 δ?
BP3∂C/∂b^l = δ^l这一层偏置为损失负多少责偏置怎么更新?
BP4∂C/∂w^l = a_in × δ_out这个权重为损失负多少责权重怎么更新?

BP1–BP4 不是凭空发明的公式,它们就是"链式法则一层一层拆成局部导数相乘"的具象化。输出层先算 δ^L;每往上一层就用 BP2 把 δ 乘 W^T 传上去、乘 σ'(z^l) 折算敏感度;最后用 BP3/BP4 把责任拆给偏置和每个权重。下一章把这四条公式代入一组具体数字,从 δ^L 一路算到 δ^1 再到每一个权重。

4. 跟着一个具体数字走完全程:2-2-1 网络的回传

公式是抽象的,下面把它落到一组具体数字上。考虑一个 2-2-1 网络:2 个输入、2 个隐藏神经元(用 sigmoid)、1 个输出神经元;共 9 个可训练参数Medium worked example。

图注:六阶段走完一遍前向 + 反向 + 一次更新的两层网络结构图。同一骨架、六张状态:前向 → 损失 → 输出 δ → 隐藏 δ → 梯度 → 更新。每个阶段只换数字、不换骨架,让读者清楚看见"我们要算的就是这张图"。

初始参数:

输入:    x = [1, 0]
第一层:  W^(1) = [[0.5, -0.6], [0.3, 0.8]],   b^(1) = [0.1, -0.2]
第二层:  W^(2) = [0.7, 0.9],                  b^(2) = -0.3
目标:    y = 1

第一步:前向(fwd)

z^(1) = W^(1) x + b^(1) = [0.6, 0.1]
a^(1) = σ(z^(1))         ≈ [0.6457, 0.5250]
z^(2) = W^(2) a^(1) + b^(2) ≈ 0.6245
ŷ = σ(z^(2))              ≈ 0.6513
L = -log(ŷ)              ≈ 0.4288     (y=1 时 BCE 化简)

到这里前向结束,损失 L ≈ 0.4288 算出来了。第二步:输出层 δ(用 BP1)

对 sigmoid + BCE 这一对常用搭配,∂L/∂z^(2) 在 BP1 下下有一个漂亮闭环:

δ^(2) = ∂L/∂z^(2) = ŷ - y ≈ 0.6513 - 1 = -0.3487

不用单独乘 σ'(z^(2)) 与 (1/ŷ),两个东西合并起来恰好等于 ŷ - y。这一行是输出层特例下的简化结果,值得单独点破Medium worked example。

第三步:隐藏层 δ(用 BP2)

把输出层的 δ 通过 W^(2) 的转置传回去,再乘上隐藏层激活函数在 z^(1) 处的导数 σ'(z) = σ(z)(1 - σ(z)):

σ'(z_1^(1)) = 0.6457 · (1 - 0.6457) ≈ 0.2288
σ'(z_2^(1)) = 0.5250 · (1 - 0.5250) ≈ 0.2494
δ^(1) = (W^(2))^T · δ^(2) ⊙ σ'(z^(1))
     = [0.7, 0.9] · (-0.3487) ⊙ [0.2288, 0.2494]
     = [-0.2441, -0.3138] ⊙ [0.2288, 0.2494]
     ≈ [-0.0559, -0.0783]

第四步:每个权重和偏置的梯度(用 BP3/BP4)

∂L/∂b^(2) = δ^(2)                       ≈ -0.3487
∂L/∂W^(2) = δ^(2) · a^(1)                ≈ [-0.2252, -0.1831]
∂L/∂b^(1) = δ^(1)                       ≈ [-0.0559, -0.0783]
∂L/∂W^(1) = δ^(1) · x^T (外积)          ≈ [[-0.0559, 0],
                                            [-0.0783, 0]]

注意第二列整列为 0:这是因为输入 x₂ = 0,∂L/∂W^(1) 第二列就是 δ^(1) · x₂,而 x₂=0 让整列梯度归零。这是一个常被忽略、但跑过网络就一定会遇上的细节Medium worked example。

第五步:一次梯度下降更新(学习率 η=0.1)

b^(2):  -0.3000 → -0.3000 - 0.1·(-0.3487) = -0.2651
W^(2):  [0.7, 0.9] → [0.7225, 0.9183]
b^(1):  [0.1, -0.2] → [0.1056, -0.1922]
W^(1):  [[0.5, -0.6], [0.3, 0.8]]
       → [[0.5056, -0.6], [0.3078, 0.8]]

把所有步骤并排展示,复习一下"每一行对应 BP1–BP4 哪一步":

步骤算的是什么对应 BP 公式数值
前向z^(1) → a^(1) → z^(2) → ŷ → L—L ≈ 0.4288
输出层 δ∂L/∂z^(2)BP1 特例化简δ^(2) ≈ -0.3487
输出层权重梯度δ^(2) · a^(1)BP4[-0.2252, -0.1831]
输出层偏置梯度δ^(2)BP3-0.3487
隐藏层 δ(W^(2))^T δ^(2) ⊙ σ'(z^(1))BP2[-0.0559, -0.0783]
隐藏层权重梯度δ^(1) · x^TBP4[[-0.0559, 0], [-0.0783, 0]]
隐藏层偏置梯度δ^(1)BP3[-0.0559, -0.0783]
一次更新w ← w − η · ∂L/∂w—见上

把 BP1–BP4 四条公式代入一组数字,输出层 δ 在 sigmoid + BCE 下化简为 ŷ − y 是一个漂亮闭环;隐藏层 δ 通过 W^T 把责任传上去、乘 σ'(z) 折算敏感度;每个权重梯度 = 输入侧激活 × 输出侧 δ;x₂ = 0 会让对应列权重整列梯度归零。下一章把这一串公式翻译成大白话——搞清楚"这一步究竟在改什么"。

5. 把公式翻译成大白话:每一步究竟在改什么

公式能算,但只看到一串符号不知道"它到底在说一件什么事"。本章对照每一条 BP 公式,用一句话说出它在说什么。先看一张"链式法则三因子拆开读"的主图:

图注:横排链式法则等式 ∂C₀/∂w^(L) = (∂z^(L)/∂w^(L)) · (∂a^(L)/∂z^(L)) · (∂C₀/∂a^(L)),三个偏导因子被彩色方框框出,配三句英文因果问句:"How much does a nudge to w^(L) change z^(L)?" / "How much does that nudge to z^(L) change a^(L)?" / "How much does that nudge to a^(L) change C₀?" 颜色编码:w 蓝、z 绿、a 黑、C₀ 红3Blue1Brown。

这张图把 BP4 公式按"三段因果问句"拆开读了一遍:微调 w^(L),多大程度改 z^(L)?那个微调多大程度改 a^(L)?再那个微调多大程度改 C₀?三个回答相乘,就是 w^(L) 的梯度。回到第 3 章那张依赖图:这条因果链 nudge w → nudge z → nudge a → nudge C₀ 正是反向传播要传递的东西。

把 BP1–BP4 各翻成一句中文:

编号公式大白话翻译
BP1δ^L = ∇_a C ⊙ σ'(z^L)输出与目标的差距有多刺眼,乘上激活在这一点的敏感度。如果预测离答案很远(差距刺眼)且激活函数在这一段很陡(敏感度高),这一项就大。
BP2δ^l = ((W^(l+1))^T · δ^(l+1)) ⊙ σ'(z^l)后面一层喊回来的责任,乘上我对这一层 z 的敏感度。后面算出来 δ^(l+1) 之后,先用 W^T 把责任按权重分配回上一层(每个权重怎么连的就怎么分),再乘 σ'(z^l) 折算自己这一层对 z 的敏感度。
BP3∂C/∂b^l = δ^l这一层偏置该为损失负多少责——直接等于这一层的 δ,因为偏置直接加在 z 上。
BP4∂C/∂w^l = a_in × δ_out这个权重该为损失负多少责——输入侧激活越强、输出侧喊回来的责任越大,这个权重的梯度就越大。

回扣第 2 章的"责任分摊"直觉:BP4 公式 ∂C/∂w = a_in × δ_out 拆开看就是三因子链式法则的具体形态——微调 w 多大程度改 z、那个微调多大程度改 a、再那个微调多大程度改 C₀,三段因果相乘3Blue1Brown。

BP1 还有一条值得专门点出的解读:∂C/∂a^L 与 (a^L − y) 成正比,"实际输出与想要输出差得越远,a^L 的一个小微小就能造成 C₀ 的大变化"——输出越错,调整越狠3Blue1Brown。而 ∂z/∂w = a^(L-1) 这一项,对应的是"前一个神经元越活跃,这个权重的微调越能传到 z^(L)"——"一起激活的神经元会一起被调整",这是 Hebbian 类比在数学里的镜像3Blue1Brown。

到这里,BP1–BP4 不再只是四组公式,而是四条可读的中文陈述:输出错得多刺眼、上一层喊回来多少责、这一层偏置该负多少责、这个权重该负多少责。下一章用三道自测题把这一路走过的方向感、公式套用、记号对照收尾。

6. 自测题:从方向判断到链式法则套用

到这里整条链路走完了,下面用三组自测题让读者自己跟着走一遍。这是演练清单,不是考试——不评分、不计错。

自测题 1:方向判断

下面这张电路图包含四个输入 (x, y, z, w) 和四个 gate(× → max → + → ×2)。每个端口上绿色是前向算出的值,红色是反向梯度CS231n。试试先盖住红色数字,自己把每条线上的反向梯度推一遍,再揭开对比。

图注:四输入 (x=3.00, y=-4.00, z=2.00, w=-1.00),四个连续 gate:×、max、+、×2。同一张图展示三种反向流模式——add 等分分发、max 路由到较大者、mul 交换并乘。

判断依据就是三种 gate 的行为规则:

  • add gate:把上游梯度等分给所有输入(局部导数都是 1)。
  • max gate:把梯度路由给前向时较大的那个输入,另一个收到 0。
  • mul gate:局部导数 = 另一输入的值,交换并乘以上游梯度。

答案就在图上红色数字里。关键是**:分支节点的梯度要累加**,如果某条线被多个 gate 共同消费,它的梯度是多个来源的 +=,而不是覆盖。

自测题 2:链式法则套用

参照 BP3,写出下面这个三层网络第 2 层 δ^l 的完整公式,包含 W^T、σ'(z^l)、⊙ 三件套:

第 3 层(输出层):δ^(L) = ∇_a C ⊙ σ'(z^(L))
第 2 层(中间层):δ^l = ?

答案:把 δ^(L) 沿第 3 层权重矩阵 W^(L) 的转置传回第 2 层,再乘上第 2 层 z^(l) 处 sigmoid 的导数,逐元素相乘:

δ^l = ((W^(L))^T · δ^(L)) ⊙ σ'(z^l)

这就是 BP3 的矩阵形式,结构上与 BP2 完全一致。读者可以再加一层验证:如果网络有 4 层而非 3 层,δ^(l-1) 又该怎么写?答案只是再套一次同样的模板——矩阵转置 + 激活导数逐元素乘——一直往回走就是了Nielsen 在线书 Ch.2。

自测题 3:记号对照

下表中的几种写法在不同教材里都出现过。它们到底指同一个东西吗?

写法出处(典型)含义是否指同一个量
δ^l_jNielsen 在线书第 l 层神经元 j 上 ∂C/∂z^l_j 的简记是
∂C/∂z^l_jNielsen 在线书(展开形式)损失对第 l 层加权输入的偏导是
e^l(或 e_j)一些入门博客与 δ^l_j 等价,但省略了下标是
gradient of C w.r.t. activation at layer l通用英语表述损失对某层激活的偏导 ∂C/∂a^l_j不是——注意这是 ∂C/∂a,不是 ∂C/∂z

最后一行是个易混点:∂C/∂z^l_j(δ 定义用)与 ∂C/∂a^l_j(BP1 里 ∇_a C 用)只差一个激活函数。如果选 z 作锚点,公式更短;如果选 a,要在 BP1/BP2 末尾多乘一次 σ'(z^l)。两种写法数学上等价,但要分清楚现在指的是哪一种Nielsen 在线书 Ch.2。

能独立完成这三道自测题,说明反向传播的两个卡点——方向感(为什么从后往前传)和逐层套用(链式法则怎么一层一层叠上去)——已经从"听懂"变成"能做"。下一节是参考文献。

参考文献

本专题引用的核心资料如下,建议深入学习时阅读原文:

  1. Rumelhart, Hinton & Williams (1986). Learning representations by back-propagating errors. Nature 323:533–536. — https://www.nature.com/articles/323533a0
  2. Christopher Olah (2015). Calculus on Computational Graphs: Backpropagation. — https://colah.github.io/posts/2015-08-Backprop/
  3. Michael A. Nielsen (2015). Neural Networks and Deep Learning, Ch.2: How the backpropagation algorithm works. — http://neuralnetworksanddeeplearning.com/chap2.html
  4. 3Blue1Brown / Grant Sanderson (2017). Backpropagation calculus. — https://www.3blue1brown.com/lessons/backpropagation-calculus/
  5. Andrej Karpathy / Stanford CS231n (2017). Backpropagation, Intuitions. — https://cs231n.github.io/optimization-2/
  6. Andrej Karpathy (2015). Hacker's guide to Neural Networks. — http://karpathy.github.io/neuralnets/
  7. PyTorch Contributors (2025). A Gentle Introduction to torch.autograd. Last Updated Oct 01, 2025. — https://docs.pytorch.org/tutorials/beginner/blitz/autograd_tutorial.html
  8. Dive into Deep Learning (2024). §2.5 Automatic Differentiation. — https://d2l.ai/chapter_preliminaries/autograd.html
  9. Muhammad Fakharul Hasnain (2026). The Math of Neural Networks Part 2: Backpropagation, Building One From Scratch. Medium. — https://medium.com/@muhammadfakharulhasnain6/the-math-of-neural-networks-part-2-backpropagation-building-one-from-scratch-9000f5a65c4b
  10. Paul J. Werbos (1974). Beyond Regression: New Tools for Prediction and Analysis in the Behavioral Sciences. Harvard University PhD thesis, August 1974. — https://gwern.net/doc/ai/nn/1974-werbos.pdf
反向传播:误差为什么必须从后往前传