梯度与反向传播:跟着一只猫走三个来回
先把三个动作分开
「反向传播」和「梯度下降」常被当成一回事,实际是两件事:
- 反向传播是算梯度的算法。它回答「每个参数该往哪个方向调、调多少」,算完就交差。
- 梯度下降是用梯度的策略。拿到梯度之后,沿负梯度方向把参数挪一步。
一轮训练是三个动作串起来的:正向算出代价 → 反向算出梯度 → 更新参数。反向传播只占中间那一步。
下面这个动画把这三个动作拆成了 22 步,可以一步一步走。图上任何一个数字都能点开看它是怎么算出来的。
网络很小:两个输入(耳朵 0.9、眼睛 0.8),两个隐藏神经元,一个输出,一共 9 个参数。目标 y = 1.0(这是一只猫)。
正向:z 只是中转
每个神经元的两步计算(上一篇讲过,这里只作参照):
z = w₁·x₁ + w₂·x₂ + ba = σ(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.85。z 只是中转量,它存在的意义是等下反向传播时要用到它的斜率。
三层算完,网络输出 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:
z | 0 | 1 | 2 | 4 |
|---|---|---|---|---|
σ′(z) | 0.250 | 0.197 | 0.105 | 0.018 |
所以往回每穿过一个神经元,至少要乘一次不超过 0.25 的数。这是 sigmoid 的形状决定的。
「学得慢」是什么意思
回到更新公式:新参数 = 旧参数 − η × 梯度。梯度就是步长。
- 输出层梯度
0.18→ 参数这一步挪0.18 - 隐藏层梯度
0.027→ 只挪0.027
同一轮训练,后面的层已经调了一大截,前面的层几乎没动。不是前面的层不需要学,是它收不到足够强的信号去学。
层数一多,就不是「慢」而是「停」
按每层 1/7 复利往回算:
| 往回传的层数 | 剩下的信号 |
|---|---|
| 2 | 1/46 |
| 5 | 1/14,638 |
| 10 | 1/2.1 亿 |
| 20 | 1/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.1498 | 0.0949 | 0.0661 | 0.0495 |
| 降幅 | −0.0549 | −0.0288 | −0.0166 | |
| 「是猫」的把握 | 0.613 | 0.692 | 0.743 | 0.778 |
降幅越来越小。 不是因为学习率变了(η 一直是 1),而是越接近谷底梯度本身越小,步长 η·∇C 跟着自动收窄。
所以训练的终点不是「代价等于 0」—— sigmoid 只有输入趋于无穷时才输出 1,完美预测在数学上够不着。真正的终点是「梯度约等于 0,再走也没意义」。实践中用早停:代价几乎不再下降就停手。
这就是训练
三轮的动作完全一样,变的只有参数。真实网络无非是把这个循环重复几万次、参数从 9 个变成上亿个 —— 每一步做的事和这里一模一样。
不过上面这个网络有个特殊之处:输出层只有一个神经元。所以每个隐藏神经元只有一个下游,反向传播时不需要做任何合并。
输出层变成两个之后,会多出一步。下一篇讲那个。