机器学习数学基础专题【左扬精讲】—— 学习率调度器全解: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 级

本文目录

  1. 一、为什么需要学习率调度 — What & Why
  2. 二、Warmup:训练初期的"安全起步"
  3. 三、LinearLR:线性衰减
  4. 四、CosineAnnealingLR:余弦退火
  5. 五、CosineAnnealingWarmRestarts:余弦重启
  6. 六、PolynomialLR:多项式衰减
  7. 七、ReduceLROnPlateau:指标驱动的自适应调度
  8. 八、调度器选择指南
  9. 九、常见问题 FAQ(20 组)
  10. 十、Roadmap 后续预告

一、为什么需要学习率调度 — What & Why

What — 学习率调度器在做什么?

学习率调度器根据训练进度动态调整优化器的学习率。训练初期用较大学习率快速下降损失,后期逐渐减小学习率,在最优解附近精细微调。

Why — 为什么固定学习率通常不是最优选择?

问题一:固定大学习率 → 后期震荡

大学习率在最优解附近会导致参数反复跳过去又跳回来,无法收敛到最低点。

问题二:固定小学习率 → 前期收敛极慢

小学习率在前期会浪费大量计算在平坦区域爬行,训练时间翻倍都不止。

问题三:大模型对 warmup 极其敏感

Transformer 类大模型初始化时,注意力输出的方差较大。如果一开始就用目标学习率,第一步 loss 就可能爆炸。

没有学习率调度会发生什么?

  • 固定大学习率:后期在最优解附近震荡,验证指标反复波动。
  • 固定小学习率:前期收敛极慢,训练时间被拉长数倍。
  • 没有 warmup:大模型前几步 loss 直接 NaN,训练崩溃。
How — 调度器的 step() 调用时机

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 被噪声污染,后续训练不稳定。
  • 模型可能收敛到更差的局部最优。
How — Warmup + Cosine 的标准组合

大模型训练最经典的组合是 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 — 线性衰减适合什么场景?

线性衰减的优点是简单可控,学习率曲线平滑,没有余弦退火在后期"平坦尾巴"的问题。但它没有重启机制,在非凸优化中容易卡在局部最优。

How — 纯线性衰减
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 — 为什么余弦退火比线性衰减更受欢迎?

余弦曲线的特点是:前期下降快、后期下降慢。这意味着模型能在前期快速跳过平坦区域,在后期缓慢逼近最优解。而线性衰减是匀速下降,后期步长仍然偏大。

没有余弦退火会发生什么?

  • 线性衰减在后期步长仍较大,模型在最优解附近来回跳动。
  • 余弦退火的"缓出"特性让模型有更多时间精细收敛。
How — CosineAnnealingLR 的使用
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 — 为什么重启有助于跳出局部最优?

非凸优化中,模型容易卡在局部最优或高原区域。重启时学习率突然增大,给模型足够的动力跳出当前区域,重新探索更优的解。多次重启的平均效果通常优于单次长时间训练。

How — SGDR 的使用
scheduler = torch.optim.lr_scheduler.CosineAnnealingWarmRestarts(
    optimizer,
    T_0=1000,            # 第一次重启前的步数
    T_mult=2,            # 每次重启后周期翻倍
    eta_min=1e-6,
)

T_0=1000T_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。它比余弦退火更早进入低学习率区域,有助于模型快速稳定。

How — PolynomialLR 的使用
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,但对不同任务的合适值差异巨大。
How — ReduceLROnPlateau 的使用
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 级。

八、调度器选择指南

How — 场景化选择速查表
# 场景 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
posted @ 2026-07-13 19:34  左扬  阅读(42)  评论(0)    收藏  举报