机器学习数学基础专题【左扬精讲】—— 学习率调度器全解:Warmup、Cosine、Linear、Polynomial、ReduceLROnPlateau
机器学习数学基础专题【左扬精讲】—— 学习率调度器全解:Warmup、Cosine、Linear、Polynomial、ReduceLROnPlateau
学习率是优化器的核心超参,但 "固定学习率" 几乎从来不是最优选择。学习率调度器(Learning Rate Scheduler)根据训练步数、epoch 或验证指标动态调整学习率,在训练初期用大学习率快速下降,在后期用小学习率精细微调。
本文系统讲解 PyTorch 中最常用的 5 种调度策略的设计动机、数学原理和代码实现。
本文所有代码片段均为通用 PyTorch 2.x 写法,可直接在 PyTorch 2.0+ 环境中运行验证 。
本文涉及的核心 PyTorch 模块(PyTorch 2.x):
torch.optim.lr_scheduler.LinearLR ← 线性衰减
torch.optim.lr_scheduler.CosineAnnealingLR ← 余弦退火
torch.optim.lr_scheduler.CosineAnnealingWarmRestarts ← 余弦重启
torch.optim.lr_scheduler.PolynomialLR ← 多项式衰减
torch.optim.lr_scheduler.ReduceLROnPlateau ← 指标驱动的自适应调度
torch.optim.lr_scheduler.SequentialLR ← 组合多个调度器(如 Warmup+Cosine)
学习率调度 Warmup Cosine Annealing Linear Decay Polynomial Decay ReduceLROnPlateau 学习率退火 warmup 调度器 AdamW
学习重点提示
★ 必须掌握
- 必须掌握:为什么需要学习率调度 — 固定学习率无法兼顾"快收敛"和"精细微调"
- 必须掌握:Warmup 的必要性 — 大模型训练初期梯度噪声大,大学习率直接 NaN
- 必须掌握:CosineAnnealingLR 的数学直觉 — 余弦曲线从 lr_max 平滑降到 lr_min
- 必须掌握:ReduceLROnPlateau 是唯一"根据验证指标"做决策的调度器
★ 建议掌握
- 建议掌握:SequentialLR 如何组合 Warmup + Cosine
- 建议掌握:PolynomialDecay 的 power 参数对收敛的影响
- 建议掌握:scheduler.step() 放在 batch 级还是 epoch 级
本文目录
- 一、为什么需要学习率调度 — What & Why
- 二、Warmup:训练初期的"安全起步"
- 三、LinearLR:线性衰减
- 四、CosineAnnealingLR:余弦退火
- 五、CosineAnnealingWarmRestarts:余弦重启
- 六、PolynomialLR:多项式衰减
- 七、ReduceLROnPlateau:指标驱动的自适应调度
- 八、调度器选择指南
- 九、常见问题 FAQ(20 组)
- 十、Roadmap 后续预告
一、为什么需要学习率调度 — What & Why
What — 学习率调度器在做什么?
学习率调度器根据训练进度动态调整优化器的学习率。训练初期用较大学习率快速下降损失,后期逐渐减小学习率,在最优解附近精细微调。
Why — 为什么固定学习率通常不是最优选择?
问题一:固定大学习率 → 后期震荡
大学习率在最优解附近会导致参数反复跳过去又跳回来,无法收敛到最低点。
问题二:固定小学习率 → 前期收敛极慢
小学习率在前期会浪费大量计算在平坦区域爬行,训练时间翻倍都不止。
问题三:大模型对 warmup 极其敏感
Transformer 类大模型初始化时,注意力输出的方差较大。如果一开始就用目标学习率,第一步 loss 就可能爆炸。
没有学习率调度会发生什么?
- 固定大学习率:后期在最优解附近震荡,验证指标反复波动。
- 固定小学习率:前期收敛极慢,训练时间被拉长数倍。
- 没有 warmup:大模型前几步 loss 直接 NaN,训练崩溃。
PyTorch 调度器的 step() 可以放在 batch 级或 epoch 级:
# 方式 1: batch 级(更精细,推荐)
for step, batch in enumerate(dataloader):
train_step(...)
scheduler.step() # 每个 batch 调整一次
# 方式 2: epoch 级(粗粒度)
for epoch in range(num_epochs):
train_epoch(...)
scheduler.step() # 每个 epoch 调整一次
原则:如果调度器的 total_iters 是步数级别的(如 warmup 1000 steps),就必须 batch 级调用;如果 T_max 是 epoch 级别的,epoch 级调用即可 。
二、Warmup:训练初期的"安全起步"
What — Warmup 是什么?
Warmup 是指在训练的前 N 步内,学习率从接近 0 线性(或非线性)增长到目标值。它不单独是一个调度器,而是与其他调度器组合使用。
PyTorch 中通过 torch.optim.lr_scheduler.LinearLR 实现 warmup 阶段。
Why — 为什么大模型必须用 warmup?
问题一:初始化后的梯度方差大
大模型权重初始化后,各层的激活值分布不稳定。前几步的梯度估计噪声极大,如果直接用目标学习率,参数会被大幅度的噪声更新带偏。
问题二:BatchNorm 等统计量需要 warmup
训练初期的 running mean/var 估计极不准确,大步长更新会把这些噪声固化进统计量中。
没有 warmup 会发生什么?
- 前几步 loss 直接爆炸到 NaN。
- running statistics 被噪声污染,后续训练不稳定。
- 模型可能收敛到更差的局部最优。
大模型训练最经典的组合是 SequentialLR 把 LinearLR(warmup)和 CosineAnnealingLR 拼接:
from torch.optim.lr_scheduler import LinearLR, CosineAnnealingLR, SequentialLR
scheduler = SequentialLR(
optimizer,
schedulers=[
LinearLR(optimizer, start_factor=0.1, end_factor=1.0, total_iters=1000),
CosineAnnealingLR(optimizer, T_max=total_steps - 1000),
],
milestones=[1000],
)
start_factor=0.1 表示从目标学习率的 10% 开始;end_factor=1.0 表示 warmup 结束时达到 100%。
三、LinearLR:线性衰减
What — 线性衰减是什么?
线性衰减让学习率从初始值线性下降到结束值。数学上:lr = end_factor + (start_factor - end_factor) * (step / total_iters)。
Why — 线性衰减适合什么场景?
线性衰减的优点是简单可控,学习率曲线平滑,没有余弦退火在后期"平坦尾巴"的问题。但它没有重启机制,在非凸优化中容易卡在局部最优。
scheduler = torch.optim.lr_scheduler.LinearLR(
optimizer,
start_factor=1.0,
end_factor=0.01,
total_iters=10000,
)
从 lr=3e-5 线性降到 lr=3e-7,适合需要稳定衰减的场景 。
四、CosineAnnealingLR:余弦退火
What — 余弦退火是什么?
余弦退火让学习率按照余弦曲线从 lr_max 平滑下降到 lr_min。数学上:lr = lr_min + 0.5 * (lr_max - lr_min) * (1 + cos(pi * step / T_max))。
Why — 为什么余弦退火比线性衰减更受欢迎?
余弦曲线的特点是:前期下降快、后期下降慢。这意味着模型能在前期快速跳过平坦区域,在后期缓慢逼近最优解。而线性衰减是匀速下降,后期步长仍然偏大。
没有余弦退火会发生什么?
- 线性衰减在后期步长仍较大,模型在最优解附近来回跳动。
- 余弦退火的"缓出"特性让模型有更多时间精细收敛。
scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(
optimizer,
T_max=total_steps, # 半个余弦周期的步数
eta_min=1e-6, # 最小学习率(余弦曲线的最低点)
)
T_max 控制余弦周期长度。通常设为总步数的一半或全部。如果 T_max = total_steps,学习率从 lr 降到 eta_min 只经历半个周期,不会回升 。
五、CosineAnnealingWarmRestarts:余弦重启
What — 余弦重启是什么?
余弦重启(SGDR,Stochastic Gradient Descent with Warm Restarts)在余弦退火的基础上增加了"重启"机制:学习率降到最低后,重新从 lr_max 开始新一轮余弦退火。
Why — 为什么重启有助于跳出局部最优?
非凸优化中,模型容易卡在局部最优或高原区域。重启时学习率突然增大,给模型足够的动力跳出当前区域,重新探索更优的解。多次重启的平均效果通常优于单次长时间训练。
scheduler = torch.optim.lr_scheduler.CosineAnnealingWarmRestarts(
optimizer,
T_0=1000, # 第一次重启前的步数
T_mult=2, # 每次重启后周期翻倍
eta_min=1e-6,
)
T_0=1000,T_mult=2 意味着重启周期依次为 1000、2000、4000、8000...步。这种"越来越长的重启间隔"策略在实践中表现良好 。
六、PolynomialLR:多项式衰减
What — 多项式衰减是什么?
多项式衰减让学习率按照多项式函数下降:lr = (1 - step/total_iters)^power * lr_max。当 power=1 时退化为线性衰减,power=2 时是平方衰减。
Why — 多项式衰减的优势?
多项式衰减的一个变体(power > 1)在前期下降更快、后期更平缓。BERT 原始论文使用的正是 polynomial decay with power=2.0。它比余弦退火更早进入低学习率区域,有助于模型快速稳定。
scheduler = torch.optim.lr_scheduler.PolynomialLR(
optimizer,
total_iters=total_steps,
power=2.0, # 2.0 是 BERT 论文常用值
)
当 power=2.0 时,学习率曲线是一个开口向下的抛物线。BERT 原始论文 (Devlin et al., 2018) 使用 power=2.0 进行学习率衰减 。
七、ReduceLROnPlateau:指标驱动的自适应调度
What — ReduceLROnPlateau 是什么?
ReduceLROnPlateau 是唯一一个不依赖步数、而是根据验证集指标来调整学习率的调度器。当指标在若干个 epoch 内没有提升时,它自动将学习率乘以一个衰减因子。
Why — 为什么"指标驱动"很有价值?
问题一:步数驱动的调度不知道模型是否已经收敛
CosineAnnealingLR 会在预设步数后把学习率降到很低,但如果模型其实还需要更多训练,它不会给你机会。
问题二:指标驱动是真正的"自适应"
如果验证 loss 还在下降,说明模型还在学,学习率应该保持;如果验证 loss 停滞了,说明需要降低学习率继续精细微调。
没有 ReduceLROnPlateau 会发生什么?
- 步数驱动的调度可能在模型已经收敛后继续浪费计算。
- 需要手动设置 T_max,但对不同任务的合适值差异巨大。
scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(
optimizer,
mode="min", # 监控验证 loss(越小越好)
factor=0.5, # 学习率减半
patience=3, # 3 个 epoch 不改善就降 lr
threshold=1e-4, # 改善幅度阈值
min_lr=1e-7, # 学习率下限
)
# 注意:step() 需要传入当前 epoch 的验证指标
scheduler.step(val_loss)
关键注意点:ReduceLROnPlateau.step() 必须在验证循环之后调用,并传入验证指标值。它通常放在 epoch 级而非 batch 级。
八、调度器选择指南
# 场景 1: BERT/GPT 大模型预训练
scheduler = SequentialLR(optimizer,
schedulers=[
LinearLR(optimizer, start_factor=0.1, end_factor=1.0, total_iters=1000),
CosineAnnealingLR(optimizer, T_max=total_steps - 1000, eta_min=1e-6),
], milestones=[1000])
# 场景 2: 图像分类(ResNet)
scheduler = CosineAnnealingLR(optimizer, T_max=num_epochs, eta_min=0)
# 场景 3: 通用 fine-tuning
scheduler = LinearLR(optimizer, start_factor=1.0, end_factor=0.01, total_iters=num_epochs)
# 场景 4: 验证集指标不稳定时的自适应调度
scheduler = ReduceLROnPlateau(optimizer, mode="min", factor=0.5, patience=3)
九、常见问题 FAQ(20 组)
FAQ 常见问题解答
Q1. 什么是学习率调度器?
学习率调度器根据训练进度动态调整学习率。它让训练初期用大学习率快速下降,后期用小学习率精细微调,比固定学习率更高效。
Q2. 为什么需要 warmup?
大模型初始化后梯度噪声大,直接用目标学习率可能导致 loss 爆炸。Warmup 让学习率从 0 逐步增长到目标值,给模型一个"热身"阶段 。
Q3. Warmup 一般设置多少步?
通常占总训练步数的 1%~5%。BERT 用 10000 步 warmup(总步数约 1M),小模型 fine-tuning 可能只用 100~500 步 。
Q4. CosineAnnealingLR 和线性衰减哪个更好?
余弦退火更受欢迎。它的前期下降快、后期下降慢的曲线特性更适合非凸优化。线性衰减是匀速的,后期步长仍然偏大 。
Q5. CosineAnnealingWarmRestarts 中的 T_mult 是什么意思?
T_mult 控制每次重启后周期的倍增倍数。T_mult=2 表示每次重启后周期翻倍(1000→2000→4000...),T_mult=1 表示周期不变。
Q6. scheduler.step() 应该放在 batch 级还是 epoch 级?
取决于调度器的 total_iters/T_max 单位。如果单位是步数(如 LinearLR total_iters=1000),放在 batch 级;如果单位是 epoch(如 CosineAnnealingLR T_max=num_epochs),放在 epoch 级。
Q7. ReduceLROnPlateau 和 Cosine 的区别?
ReduceLROnPlateau 根据验证指标决策,Cosine 根据步数决策。ReduceLROnPlateau 更自适应但需要验证集,Cosine 更可预测但不感知模型状态。
Q8. 学习率衰减到多少合适?
通常衰减到初始学习率的 1%~10%。Cosine 的 eta_min 常用 1e-6 到 1e-7。衰减太小导致训练后期无变化,衰减太大导致欠拟合 。
Q9. 可以同时使用 warmup 和 ReduceLROnPlateau 吗?
可以,用 SequentialLR 组合。先用 LinearLR 做 warmup,再用 ReduceLROnPlateau 做指标驱动的调度。
Q10. PolynomialLR 的 power 参数怎么选?
BERT 论文用 power=2.0。power=1.0 等价于线性衰减,power=2.0 前期衰减更快。更大的 power 值前期衰减更剧烈 。
Q11. 学习率太高会怎样?
Loss 震荡、发散甚至 NaN。大学习率可能让参数跳过最优解,在非凸曲面中无法收敛。
Q12. 学习率太低会怎样?
收敛极慢,可能卡在局部最优。小学习率虽然稳定,但需要极长的训练时间才能到达最优解附近。
Q13. 什么是学习率预热(warmup)的反面?
cooldown(冷却期)。某些调度器在训练末尾会进入一个更保守的衰减阶段,进一步降低学习率以稳定最终参数。这不是 PyTorch 内置的,但可以通过组合调度器实现。
Q14. OneCycleLR 是什么?
一种在一个周期内先升后降的学习率策略。学习率从低到高(warmup)再降到更低(annealing),配合动量从高到低。由 Leslie Smith 在 "A disciplined approach to neural network hyper-parameters" 中提出。
Q15. 不同的层可以用不同的学习率吗?
可以,通过 param_groups。常见做法是 backbone 用较小学习率,head 用较大学习率(如微调时 lr_backbone=1e-5, lr_head=1e-4)。
Q16. 为什么余弦退火在大模型上表现好?
大模型训练步数多,余弦曲线的平滑衰减特性有助于稳定收敛。配合 warmup,可以在前期快速下降、后期精细微调,是当前大模型的事实标准组合 。
Q17. 学习率调度器会影响 BatchNorm 吗?
间接影响。学习率影响参数更新幅度,进而影响 BatchNorm 的 running mean/var 估计。大学习率会导致 running statistics 波动更大。
Q18. 训练 loss 不降但 lr 已经很小了怎么办?
可能是模型容量不足、数据问题或初始化不当。降低学习率只能解决"步长太大"的问题,无法解决模型架构本身的局限。
Q19. 验证 loss 上升但训练 loss 下降是调度器的问题吗?
不是调度器的问题,是过拟合的信号。训练 loss 下降说明模型还在学训练数据,验证 loss 上升说明泛化能力在退化。应该用早停(early stopping)而非调整调度器。
Q20. 可以中途修改学习率吗?
可以,直接修改 optimizer.param_groups[i]["lr"]。但建议通过调度器来管理,手动修改容易导致调度器状态不一致。
全篇总纲
- 1 个目标:让学习率从"大"平滑降到"小"
- 1 个必经之路:Warmup(前 N 步从 0 升到目标 lr)
- 5 种衰减策略:Linear / Cosine / CosineRestart / Polynomial / Plateau
- 1 个事实标准:大模型 = Warmup + CosineAnnealingLR
- 2 个选择原则:步数级调度看总步数,指标级调度看验证集
十、Roadmap 后续预告
后续预告
- 第 9 篇:混合精度与分布式训练 — 从单机单卡到 DDP、FSDP、DeepSpeed
- 第 10 篇:大模型推理与部署 — 从 PyTorch 到 ONNX / TensorRT / vLLM

浙公网安备 33010602011771号