Skip to content

理论学习:为什么你的网络不收敛 ​

第 2 章已经把梯度算对了,「方向」有了。可把同一段代码接到真实网络上,SGD 学习率开到 1.0,第 1 轮 loss 就变成 NaN;退回 1e−3,loss 又停在 2.3016 一动不动。方向对了,步子怎么迈,才是训练工程要回答的问题。

这一篇不堆技巧,只把四种典型失败拆开:激活饱和、初始化方差、归一化位置、优化器与正则。每一种都先给现象,再给机制,最后给本章实测的数字。

![四种失败形态](/ai-four-kings/第 3 章:训练工程/assets/ch3_four_failures.svg)

<!-- 配图:四宫格:平线、NaN、爬升、过拟合,各自带一条曲线和一行结论 -->

一、四种失败形态:先看曲线,再猜原因 ​

训练不收敛不是一个问题,是四类问题。它们的曲线形状完全不同,第一眼就能分开。

形态现象第一嫌疑本章对应
平线loss 十轮停在 2.3014,acc 11.2%初始化、梯度是否为 0第三节
爆炸第 1 轮 loss 变 NaN,acc 10.2%学习率过大第五节
爬升loss 从 1.55 升到 2.13,acc 34.1% → 16.9%学习率过大、优化器不匹配第五节
过拟合train 0.0114,val 0.1753,两者越拉越开容量过大、没有正则第六节

图里四格都来自真实运行。② 甚至没留下曲线:SGD 学习率 1.0 时,第一个 epoch 内 loss 就不是有限值,训练循环直接退出。这就是为什么要先看 loss 的数值范围,再看准确率。

参数更新只有一行:

text
θ ← θ − η ∇L(θ)

η 是学习率,∇L 是梯度。失败可以粗分成两类:梯度本身尺度不对(太大、太小、全零),或者步长与梯度不匹配(方向对,步子太大)。前者主要来自激活、初始化、归一化;后者主要来自优化器和学习率。

二、激活函数:饱和把梯度压没了 ​

先看激活函数的导数上限。

![激活函数与饱和区](/ai-four-kings/第 3 章:训练工程/assets/ch3_activation_saturation.svg)

<!-- 配图:Sigmoid / Tanh / ReLU / LeakyReLU 四条曲线,两侧红色饱和区,右侧导数上页面板 -->

Sigmoid 的导数:

text
σ(z) = 1 / (1 + e^{−z})
σ′(z) = σ(z)(1 − σ(z)) ≤ 0.25

最大值在 z = 0 处,只有 0.25。|z| 一大,导数就趋近 0:输入落在「饱和区」,这一层几乎不传梯度。Tanh 好一些,导数上限是 1;ReLU 在正半轴导数恒为 1,负半轴恒为 0。

单层 0.25 不可怕,可怕的是连乘。一个 8 层 Sigmoid 网络,梯度从输出传到第 1 层,每层最多乘 0.25:

![八层 Sigmoid 的梯度连乘](/ai-four-kings/第 3 章:训练工程/assets/ch3_grad_chain.svg)

<!-- 配图:九个方块从 1.000 依次乘 0.25 降到 1.53e−5;下方 ReLU 与 Sigmoid 的首末层梯度实测 -->

text
0.25⁸ = 1.53e−5

第 1 层只剩十万分之十五。这不是比喻,是乘法。

实测也一致。同一个 784→128→64→10 网络、同一份 He 初始化,只看第一个 batch 的首层梯度范数:

![四种激活的首层梯度](/ai-four-kings/第 3 章:训练工程/assets/ch3_saturation_measured.svg)

<!-- 配图:ReLU 4.57、LeakyReLU 4.59、Sigmoid 0.42、Tanh 2.79 四根柱,底部注明十轮后结果 -->

激活首层梯度范数首层 / 末层初始零输出比例
ReLU4.572.3750.98%
LeakyReLU 0.014.592.4450.98%
Sigmoid0.420.500.22%
Tanh2.791.830.00%

Sigmoid 的 0.42 比 ReLU 的 4.57 小 10.9 倍。但这里有一个容易误读的地方:10 轮之后,四种激活的验证准确率都在 97.1–97.3%。ReLU 97.23%、LeakyReLU 97.23%、Sigmoid 97.28%、Tanh 97.11%。

原因是网络只有两个隐层,Adam 又对每个参数做了归一化,把梯度尺度差异抹平了。第 2 章那个 8 层实验里,Sigmoid 的首末层比值是 5.50e−5,ReLU 仍有 2.25——深度一上来,差距就藏不住了。

设计取舍:ReLU 还是 Sigmoid ​

方案收益代价
A:ReLU / LeakyReLU正半轴梯度恒为 1,首层梯度 4.57负半轴直接归零;本实验最终模型 75.4% 的 ReLU 输出为 0
B:Sigmoid输出有界、平滑,适合概率型输出导数上限 0.25,深层连乘消失

本章选 A。代价是「死单元」:学习率太大或初始化太偏时,一批单元永久落在负半轴,梯度永远是 0。LeakyReLU 用 0.01 的负斜率留了一条细缝,本章实测它和 ReLU 几乎同分。

三、初始化:方差与对称性 ​

激活决定单层导数,初始化决定信号能不能一层层传下去。

![初始化方差逐层传播](/ai-four-kings/第 3 章:训练工程/assets/ch3_init_variance.svg)

<!-- 配图:四种初始化在输入 / 第一层 / 第二层的激活标准差折线;零初始化贴 0,小随机掉到 0.0092 -->

全连接层的预激活方差近似:

text
Var(z) = fan_in · Var(w) · Var(a)

要让 Var(z) 和 Var(a) 同量级,权重的方差就必须约为 1 / fan_in。

实测(784→128→64→10,ReLU,输入标准化):

初始化第 1 层激活 std第 2 层激活 std10 轮验证 acc
零0.0000.00011.20%
小随机 0.010.1310.009297.62%
Xavier0.6140.43997.38%
He0.6620.58097.23%

小随机 0.01 从 0.131 掉到 0.0092,两层衰减 14.3 倍;He 从 0.662 到 0.580,基本持平。这就是「方差保持」四个字的实际含义。

比方差更严重的是对称性。

![零初始化的对称性](/ai-four-kings/第 3 章:训练工程/assets/ch3_zero_symmetry.svg)

<!-- 配图:128 个相同隐单元复制同一输出;右侧三个后果;底部指标表 -->

如果所有权重都是 0,128 个隐单元的前向输出完全相同,反向梯度也完全相同,更新之后仍然完全相同。网络实际上只有 1 个有效特征,被复制了 128 份。

实测结果是:

指标数值含义
验证准确率11.2%只会猜先验(每类 10%)
验证 loss2.3016= ln(10),输出均匀分布
首层梯度范数0.0没有任何更新
单元余弦相似度1.000128 个方向完全重合

踩坑记录

  • 现象:第一次跑零初始化,10 轮验证 acc 卡在 11.20%,loss 2.3014,一条水平线。
  • 原因:权重全 0 → 所有隐单元输出相同 → 梯度相同 → 更新后仍相同,对称性从未被打破。
  • 修复:改成随机初始化,方差按 fan_in 缩放。He 初始化后单元余弦相似度降到 0.028,10 轮 acc 97.23%。

设计取舍:Xavier 还是 He ​

![Xavier 与 He 的公式](/ai-four-kings/第 3 章:训练工程/assets/ch3_xavier_he.svg)

<!-- 配图:fan_in / fan_out 示意;Xavier 2/(fan_in+fan_out);He 2/fan_in -->

方案公式适用代价
A:XavierVar(w) = 2 / (fan_in + fan_out)tanh / sigmoid,导数上限接近 1对 ReLU 偏小;本实验第 2 层 std 0.439,比 He 低 24%
B:HeVar(w) = 2 / fan_inReLU / LeakyReLU,负半轴砍掉一半激活换成 Sigmoid 时容易把预激活推进饱和区

本章固定用 B,因为主线激活是 ReLU。两者在两层网络上最终只差 0.15 个百分点(97.38% vs 97.23%),但第 2 层的信号强度差了 24%;网络一深,这个差距会被逐层放大。

四、归一化:输入一次,还是每层一次 ​

输入标准化的统计量来自训练集:mean = 0.130612,std = 0.308064。验证集和测试集直接复用这两个数,不重新计算。

![归一化的三种位置](/ai-four-kings/第 3 章:训练工程/assets/ch3_norm_positions.svg)

<!-- 配图:原始像素 / 输入标准化 / 标准化+BN 三列流程,底部到 97% 的轮次 -->

输入处理到 97% 的轮次8 轮验证 acc
原始像素 0–1797.29%
输入标准化497.41%
输入标准化 + BN397.82%

BN 放在 Linear 之后、激活之前。它做的是同一件事,但对象不是输入,而是每一层的预激活:

![BatchNorm 的前向与两种模式](/ai-four-kings/第 3 章:训练工程/assets/ch3_bn_forward.svg)

<!-- 配图:训练模式用 batch 均值方差并累计 running stats;推理模式用 running stats;右侧为什么 BN 好训 -->

text
μ = mean(z)          σ² = var(z)
ẑ = (z − μ) / √(σ² + ε)
y = γ · ẑ + β

γ、β 是可学习参数。训练时用当前 batch 的统计量,同时把均值方差累计到 running_mean / running_var;推理时改用累计值。这就是 model.train() 与 model.eval() 对 BN 的真实差别——不是「关掉 BN」,而是「换一套统计量」。

设计取舍:输入标准化还是 BN ​

方案收益代价
A:只做输入标准化一次计算,零额外参数,不依赖 batch只管输入;层间分布漂移照旧,到 97% 要 4 轮
B:标准化 + BN每层重算统计量,到 97% 只要 3 轮每个 BN 层多 2×宽度 个参数;batch 太小时统计量不稳;训练/推理两套模式

本章主线选 B:BN 让 30 轮训练里学习率更宽容,也让第 2 层不必看第 1 层的尺度脸色。代价是参数从 235,914 变成 235,934——多出来的 20 个,来自输出层也建了一个 10 维 BN。这不是笔误,后文会解释。

五、优化器与学习率:方向对,步子也要对 ​

同样一个峡谷,三种走法。

![三种优化器的路径](/ai-four-kings/第 3 章:训练工程/assets/ch3_optimizer_paths.svg)

<!-- 配图:SGD 来回横跳、Momentum 抵消震荡、Adam 每维缩放;右侧三个更新式与实测表 -->

text
SGD:       θ ← θ − η∇L
Momentum:  v ← βv − η∇L,  θ ← θ + v
Adam:      m ← β₁m + (1−β₁)g
           v ← β₂v + (1−β₂)g²
           θ ← θ − η m̂ / (√v̂ + ε)

SGD 对所有参数用同一个步长;Momentum 把历史方向带上,窄方向上的来回震荡会相互抵消;Adam 对每个参数分别缩放,步长大约固定在 η 附近,和梯度大小无关。

6 轮实测:

优化器最佳验证 acc最佳学习率学习率过大时
SGD97.13%0.1lr=1.0 → 第 1 轮 NaN
Momentum97.17%0.01lr=0.1 → 94.07%
Adam97.02%1e−3lr=0.1 → 16.94%

三者的最佳值只差 0.15 个百分点,但它们要的学习率差两个数量级。SGD 在 1e−4 时只有 72.53%,学习率放大 1000 倍到 0.1 才到 97.13%;Adam 在 1e−3 就到位,放大 100 倍到 0.1 直接崩到 16.94%。

学习率本身的地形也值得单独看。

![学习率扫描](/ai-four-kings/第 3 章:训练工程/assets/ch3_lr_regimes.svg)

<!-- 配图:对数横轴,1e−4 到 1e−1 八个点,右侧红色悬崖区,标出平台与 3e−2 起的崩塌 -->

学习率1e−43e−41e−32e−33e−31e−23e−21e−1
6 轮验证 acc96.0397.0097.0295.9796.2394.7289.7216.94

平台很宽:3e−4 到 1e−3 都在 97% 附近。悬崖很陡:再大 10 倍到 1e−2 还有 94.72%,到 1e−1 只剩 16.94%。所以调学习率的顺序是「先找悬崖,再找平台」:从大到小降,直到 loss 不再发散,再在平台里细挑。

设计取舍:Adam 还是 Momentum ​

方案收益代价
A:Adam 1e−3前几轮最快,6 轮 97.02%,无需精细调 lr对每个参数单独缩放,噪声也被放大;lr=0.1 时只剩 16.94%
B:Momentum 1e−26 轮 97.17%,略高;梯度噪声被动量平滑需要手动选 lr;lr=0.1 时 94.07%,比 Adam 的同档好,但仍不如自己的最佳

本章主线选 A:30 轮、有 BN 和 Dropout 的网络,Adam 的稳定性比那 0.15 个百分点更值钱。代价是要接受它对学习率上界更敏感——这也是为什么后文最终配置把 lr 固定在 1e−3,而不是 0.1。

六、正则与早停:别把欠拟合当成收敛 ​

![三种正则](/ai-four-kings/第 3 章:训练工程/assets/ch3_regularization.svg)

<!-- 配图:L2 / Dropout / Early Stopping 三面板,各自公式、示意与实测数字 -->

三种常用手段解决的是同一个问题:模型把训练集背下来了。

text
L2 / Weight Decay:  L = L_data + λ‖W‖²
Dropout:            训练时按概率 p 把激活置 0,推理时整体缩放
Early Stopping:     验证指标连续 patience 轮不改善就停

一个 784→512→256→10、535,818 个参数的大网络,30 轮实测:

配置验证 loss验证 acc测试 acc
无正则0.175397.87%97.96%
Dropout 0.30.084098.18%98.27%
Weight Decay 1e−20.144896.03%96.37%

Dropout 0.3 把测试 acc 从 97.96% 提到 98.27%,验证 loss 从 0.1753 压到 0.0840。Weight Decay 1e−2 反而把测试 acc 压到 96.37%——正则不是越强越好,强度过头就是欠拟合。

早停也有一个容易忽略的选择:盯 loss 还是盯 acc。

大网络无正则时,验证 loss 在第 4 轮见底(0.1015),按 patience=4 在第 8 轮停,测试 96.86%。但如果换成一个 10,000 张的小训练集,同样的网络:

选择规则选中轮次val lossval acctest acc
按 val loss 最低ep40.170595.33%95.40%
按 val acc 最高ep300.206296.69%96.71%

验证 loss 上升了 0.0357(+20.9%),验证 acc 却涨了 1.36 个百分点,测试 acc 涨了 1.31 个百分点。原因在 loss 和 acc 量的不是同一件事:loss 由正确类的概率决定,acc 只看最大值是不是正确类。模型后期把正确类从 0.95 推到 0.98,acc 不变甚至小涨;但错误样本也被推得更自信,把 loss 拉高了。

踩坑记录

  • 现象:按 val loss 早停,测试只有 95.40%;同一份数据按 val acc 跑到第 30 轮,测试 96.71%。
  • 原因:loss 对置信度敏感,acc 对置信度不敏感;小数据上 loss 会更早见底,早停因此过早。
  • 修复:先明确验收指标。验收看 acc 就按 acc 选模型;如果要输出概率,loss 和可靠性图要一起看。

七、这一章没解决的问题 ​

上面所有手段——换激活、调初始化、加 BN、换优化器、上正则——改的都是「怎么把 784 维像素向量拟合得更好」。它们能修掉平线、爆炸、爬升、过拟合,却修不掉一类错误:最终模型测试 98.20%,180 个错误里,9→4 有 13 张、7→2 有 10 张、3→5 有 9 张。这些数字的形状只差一笔,全连接层把 28×28 拉平成 784 维之后,相邻像素的关系就没了。

下一个问题只能换网络结构来回答:卷积为什么能看见图像?

自检 ​

合上笔记,试着回答:

  • 四种失败形态各是什么曲线形状?第一嫌疑分别是什么?
  • Sigmoid 的导数上限是多少?8 层连乘后第 1 层还剩多少?
  • 零初始化为什么会「训多久都分不开」?用哪两个指标能确认?
  • Xavier 和 He 的公式差在哪?为什么 ReLU 要用 He?
  • BN 在 train() 和 eval() 下分别用哪套统计量?忘记切换会发生什么?
  • 学习率 1e−3 与 1e−1 在同一个 Adam 上差多少?先找悬崖还是先找平台?
  • 早停按 loss 和按 acc,哪个更容易过早停?为什么?