bobofamilyNIU CHUN
02/03

梯度与反向传播:跟着一只猫走三个来回

2026-08-286 分钟阅读

先把三个动作分开

「反向传播」和「梯度下降」常被当成一回事,实际是两件事:

  • 反向传播是算梯度的算法。它回答「每个参数该往哪个方向调、调多少」,算完就交差。
  • 梯度下降是用梯度的策略。拿到梯度之后,沿负梯度方向把参数挪一步。

一轮训练是三个动作串起来的:正向算出代价 → 反向算出梯度 → 更新参数。反向传播只占中间那一步。

下面这个动画把这三个动作拆成了 22 步,可以一步一步走。图上任何一个数字都能点开看它是怎么算出来的

交互动画 · 2→2→1 · 三轮 · η=1 · 可单步新窗口打开 ↗

网络很小:两个输入(耳朵 0.9、眼睛 0.8),两个隐藏神经元,一个输出,一共 9 个参数。目标 y = 1.0(这是一只猫)。

正向:z 只是中转

每个神经元的两步计算(上一篇讲过,这里只作参照):

z = w₁·x₁ + w₂·x₂ + b
a = σ(z) = 1/(1+e⁻ᶻ)

第一轮隐1 的计算是 0.5×0.9 + 0.5×0.8 + 0 = 0.85,然后 σ(0.85) = 0.7006

交给下一层的是 a,不是 z 输出层算 z2 时乘的是 0.7006(隐1 的 a),不是 0.85z 只是中转量,它存在的意义是等下反向传播时要用到它的斜率。

三层算完,网络输出 a2 = 0.6129,也就是「61.3% 把握是猫」。

代价只告诉你「有多差」

C = (a2 − y)² = (0.6129 − 1.0)² = 0.1498

正向传播到这里就结束了。但 C 只是一个数,它说明网络差得有多远,没有说该怎么改。 怎么改是下一步的事。

反向的起点:δ

掉头往回。第一个要算的是输出层的 δ,它由两项相乘得到:

∂C/∂a2 = 2(a2 − y) = 2 × (0.6129 − 1) = −0.7741 离目标还差多少
σ′(z2) = a2(1 − a2) = 0.6129 × 0.3871 = 0.2372 信号能不能穿过激活函数
δ_out = −0.7741 × 0.2372 = −0.1837

δ 的定义是 ∂C/∂z —— 代价对本层加权和的敏感度。算它出来是因为本层三个参数都要复用它,不用各算各的。

接力棒:往回传的是「要求」,不是梯度

往回传的时候有两种乘法,规则很简单:穿过一条边就乘这条边的权重,穿过一个神经元就乘它的斜率。

隐1 的接力棒:δ_out × w2_1 = −0.1837 × 0.7 = −0.1286
隐1 的 δ :−0.1286 × σ′(z1_1)=0.2098 = −0.0270

中间那个 −0.1286 就是 ∂C/∂a1_1它不是任何一个参数的梯度,而是「输出层对前一层提的要求」—— 输出层要求前一层的激活值往某个方向变,前一层据此计算本层参数该怎么调。

传到输入层就停了:像素值是给定的,不需要梯度。

一个权重值不值得调,看三项

Ch.4 里那个公式,拆开读其实是一句话:

∂C/∂w = a前一层 · σ′(z) · 2(a − y)
↑ ↑ ↑
前层信号 能不能穿过 离目标
有多强 激活函数 差多少

三项里任意一个接近 0,这个权重的梯度就接近 0,基本不用动它。

  • 前一层那个神经元没激活(a前 = 0)→ 这个权重怎么调都是乘 0,白调
  • 神经元被压到饱和(σ′ ≈ 0)→ 信号穿不过去,链子在这断了
  • 已经算对了(a = y)→ 没什么好调的

第二条就是梯度消失的源头。sigmoid 的斜率最大只有 0.25(在 z=0 处),两端几乎平到 0。

两个跑起来才看得见的现象

前面几节从公式就能推出来。下面这两个要把三轮完整跑一遍、看着真实数值才会显现。

一、误差信号穿过负权重会翻号

δ_out = −0.1837 是负的。传给隐1 得到 −0.0270,还是负的;传给隐2 却变成了 +0.0179

原因是 w2_2 = −0.4 是个负权重:

隐2 的接力棒:−0.1837 × (−0.4) = +0.0735 ← 负负得正
隐2 的 δ :+0.0735 × 0.2441 = +0.0179

所以同一个「输出偏低,要往上抬」的要求,传到两个隐藏神经元手里变成了相反的指令:隐1 该往大调,隐2 该往小调。这在单层网络里看不到,因为误差信号只走一站。

二、越靠前的层,梯度越小

第一轮九个梯度,按层分:

梯度
输出层−0.1287 −0.1060 −0.1837
隐藏层−0.0243 −0.0216 −0.0270 +0.0161 +0.0143 +0.0179

隐藏层比输出层小了约 7 倍。原因就在接力棒那一步:

δ_h1 = δ_out × w2_1 × σ′(z1_1)
= −0.1837 × 0.7 × 0.2098 = −0.0270
↑ ↑ ↑
上层信号 穿过边 穿过神经元
乘权重 乘斜率

每往回传一层,就乘两个小于 1 的数 —— 一个权重、一个斜率。这里是 0.7 × 0.2098 = 0.147,相当于每层缩到原来的 1/7。

其中 σ′ 是躲不掉的。sigmoid 的斜率最大只有 0.25(在 z = 0 处),越往两端越接近 0:

z0124
σ′(z)0.2500.1970.1050.018

所以往回每穿过一个神经元,至少要乘一次不超过 0.25 的数。这是 sigmoid 的形状决定的。

「学得慢」是什么意思

回到更新公式:新参数 = 旧参数 − η × 梯度梯度就是步长。

  • 输出层梯度 0.18 → 参数这一步挪 0.18
  • 隐藏层梯度 0.027 → 只挪 0.027

同一轮训练,后面的层已经调了一大截,前面的层几乎没动。不是前面的层不需要学,是它收不到足够强的信号去学

层数一多,就不是「慢」而是「停」

按每层 1/7 复利往回算:

往回传的层数剩下的信号
21/46
51/14,638
101/2.1 亿
201/4.6×10¹⁶

到十几层的时候,最前面那层拿到的梯度已经是 10⁻⁹ 量级。乘上学习率再加到参数上,浮点数下基本等于没加 —— 前几层被冻住了,训练再久也不动。

这就是梯度消失(vanishing gradient):信号不是被谁挡住的,是在传回去的路上被一层层乘没了。

这也是现在普遍用 ReLU 而不是 sigmoid 的主要原因。ReLU 在正区间的导数是 1,不是 ≤ 0.25 —— 乘 1 不缩水,信号能一路传回最前面。

更新:这一步才真的改变网络

反向传播交出九个梯度就结束了,网络还一点没变。真正动手的是这一步:

新参数 = 旧参数 − η × 梯度 (这里 η = 1)
w2_1 : 0.700 − (−0.1287) = 0.829

梯度都是负的,意味着「往大调能降低代价」,所以更新后参数都变大了。

三轮之后

第 1 轮第 2 轮第 3 轮第 4 轮起点
代价0.14980.09490.06610.0495
降幅−0.0549−0.0288−0.0166
「是猫」的把握0.6130.6920.7430.778

降幅越来越小。 不是因为学习率变了(η 一直是 1),而是越接近谷底梯度本身越小,步长 η·∇C 跟着自动收窄

所以训练的终点不是「代价等于 0」—— sigmoid 只有输入趋于无穷时才输出 1,完美预测在数学上够不着。真正的终点是「梯度约等于 0,再走也没意义」。实践中用早停:代价几乎不再下降就停手。

这就是训练

三轮的动作完全一样,变的只有参数。真实网络无非是把这个循环重复几万次、参数从 9 个变成上亿个 —— 每一步做的事和这里一模一样。


不过上面这个网络有个特殊之处:输出层只有一个神经元。所以每个隐藏神经元只有一个下游,反向传播时不需要做任何合并。

输出层变成两个之后,会多出一步。下一篇讲那个。