本节摘要:训练后期最常见的两种失稳——训练集满分、验证集回落(过拟合),以及损失在高原徘徊不降(步长不当)——各有对症开关:正则化(Dropout、权重衰减、早停)限制模型把容量花在背题上,学习率调度(余弦退火、warmup)让步子先稳后收。本节给出两者可直接复用的代码与一组可手算的退火数值。
过拟合的机理在 4.4 节见过:容量大、数据少,模型选择背题。正则化一族的做法是给"背题"加价:Dropout 随机掐断神经元,让网络不敢把赌注押在任何单条通路上;权重衰减给大权重加罚,逼模型保持平滑;早停盯着验证曲线,一回落就收手。学习率调度治的是另一头:训练初期参数乱、需要大步子探索,后期接近谷底需要小步精修,恒定学习率注定两头不讨好——调度器让步长按计划先热身后收缩。两道闸门一个管容量去向,一个管步子节奏,合起来覆盖训练后程的绝大多数疑难。
阅读完本节,你应当能够:
训练时每个神经元以概率 p 被临时关闭,存活的输出乘 1/(1−p) 补偿期望;推理时全部直通、不再缩放。期望不变是靠这笔预付的放大账:掐掉一半、存活者翻倍,输出总和的期望与不掐时相同。它顺带强制特征去冗余——任何单个神经元都可能缺席,网络必须把判据摊到许多神经元上。
import torch import torch.nn as nn torch.manual_seed(1) drop = nn.Dropout(p=0.5) x = torch.ones(1, 10) drop.train() y = drop(x) print(y.tolist()) # 存活位置为 2.0(等于 1 除以 1−p),其余为 0 print("存活个数:", int((y != 0).sum())) drop.eval() print(drop(x).tolist()) # 原样直通:推理不丢、不再缩放
放置惯例:Dropout 主要装在全连接层之间,卷积层更常用 4.4 节的增强与 4.5 节的 BN 代替(卷积核参数共享,本身较难过拟合;空间上随机丢还会破坏邻域结构)。比例从 0.5 起试,过拟合不明显就减到 0.2 甚至关掉——正则化是药,无病服药伤精度。
权重衰减在每步更新里给权重乘一个小于一的系数(等价于对权重的平方和加罚):大的权重被持续压小,网络倾向更平滑的解。实现上有个关键分岔:SGD 里惩罚直接并进梯度(耦合式);Adam 里若同样并进去,罚项会被自适应步长不均匀地扭曲,AdamW 把它从梯度里拆出来单独施加(解耦式)——用 Adam 系就该配 AdamW 的衰减,这个细节在视觉任务里能差出可观的精度。典型量级:SGD 配 5e-4 起步,AdamW 配 0.01 到 0.05。
早停最朴素也最难被取代:把验证指标最好那一轮的权重存下来,之后若干轮(耐心值,比如十轮)没有刷新就停。它几乎零成本,还顺手解决了"不知道该训多少轮"的问题。
调度器里性价比最高的组合是"短热身 + 余弦退火":前几轮从极小值线性爬到目标值(防初期大梯度把参数打飞),之后按余弦曲线从峰值平滑滑落到谷值。公式与一组手算如下(峰值 0.1、谷值 0.001、总轮次 100):
eta(t) = 谷值 + 0.5 × (峰值 − 谷值) × (1 + cos(pi × t ÷ 总轮次))
import math peak, floor, total = 0.1, 0.001, 100 for t in [0, 25, 50, 75, 100]: lr = floor + 0.5 * (peak - floor) * (1 + math.cos(math.pi * t / total)) print(f"第{t:>3} 轮学习率: {lr:.4f}") # 第 0 轮 0.1000(热身结束的峰值) # 第 25 轮 0.0855 # 第 50 轮 0.0505(中点恰为峰谷的均值) # 第 75 轮 0.0155 # 第 100 轮 0.0010(平稳落谷)
曲线前段缓、中段陡、尾段缓,退火末期的小学习率正好给模型做精修——这也是 SGD 长跑能追平 Adam 的关键外挂。实践配方一句话:AdamW 1e-3 加 5 轮热身加余弦退火,能作为绝大多数图像任务的默认起点;另有按里程碑折半的阶梯式(ResNet 原文所用),效果相近,选顺手的即可。
第一笔,阶梯式调度:初始 0.1,第 40 与第 80 轮各乘 0.1,则全程学习率序列是 0.1、0.01、0.001 三段——ResNet 原文的配方,简单可复现,缺点是折点前后损失常有台阶式跳动。第二笔,早停的耐心值怎么设:验证指标若连续十轮未刷新最优,就回滚到最优权重收工;十轮是常见起点,数据噪声大或调度末端本就平缓时,可以放宽到十五轮,别让一次抖动误杀好模型。
把本节的配方落成代码骨架,配上数值直觉:峰值 0.1、热身五轮、余弦退火五十轮,学习率先从 0.02 线性爬到 0.1,再按余弦滑到 0.001;验证指标每轮记录一次,最优权重随跑随存。真机训练前,先用这份配置空跑一个轮次,打印每步的学习率确认曲线形状——调度器配错(比如热身峰值写成了谷值)是新手案头最高频的事故,一次打印胜过十次猜测。
# sched = torch.optim.lr_scheduler.CosineAnnealingLR(opt, T_max=50) # warmup 简版:前 5 轮手动把 lr 从 0.02 线性乘到 1.0 倍基准 # for epoch in range(5): # factor = (epoch + 1) / 5 # for g in opt.param_groups: # g["lr"] = g["lr"] / max(factor, 1e-8) * factor # 逐轮放大到基准
💡 关键直觉:先问自己过拟合的证据在哪——训练与验证的差距。差距大,动正则化与增强;两者都高但不降,动调度与学习率。对症下药比堆技巧重要。
至此训练闭环的全部部件齐了,还差一个常见开局:没有大数据时怎么起步。下一节迁移学习,直接接手一条成熟的流水线。