一、为什么需要学习率调度?

学习率控制着参数更新的步长。如果把模型训练比作下山:

  • 学习率过大:步子迈得太大,在山谷里来回跳跃,甚至越跳越远(发散)。
  • 学习率过小:步子太小,下山极慢,还容易卡在半山腰的小坑里(局部最优)。
  • 理想策略:开始大步走(快速接近山谷),越接近谷底步子越小(精细搜索最优点)。

学习率调度器(LR Scheduler)就是自动实现这个"由大到小"或"动态调整"过程的工具。

在这里插入图片描述

训练循环中的调度器用法

在 PyTorch 中,调度器的使用模式非常统一:

import torch.optim as optim

optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.9)
scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=30, gamma=0.1)

for epoch in range(num_epochs):
    train(model, optimizer)
    val_loss = validate(model)
    scheduler.step()  # 每个 epoch 结束后调用

注意 scheduler.step() 应在 optimizer.step() 之后、每个 epoch 结束时调用(PyTorch 1.1+ 的约定)。

二、八大调度器详解

2.1 StepLR —— 阶梯衰减

核心思想:每隔固定步数(step_size),将学习率乘以衰减因子 gamma。

数学公式

lrepoch=lrinit×γ⌊epoch/step_size⌋ \text{lr}_{\text{epoch}} = \text{lr}_{\text{init}} \times \gamma^{\lfloor \text{epoch} / \text{step\_size} \rfloor} lrepoch=lrinit×γepoch/step_size

其中 γ\gammaγ 通常取 0.1,step_size 通常为总 epoch 数的 1/3。

请添加图片描述

PyTorch 实现

scheduler = optim.lr_scheduler.StepLR(
    optimizer,
    step_size=30,   # 每 30 个 epoch 衰减一次
    gamma=0.1       # 每次乘以 0.1
)

适用场景:CNN 图像分类(ResNet 原论文即采用此策略)。

优点:实现简单,行为可预测,易于调试。

缺点:阶梯式跳变可能导致训练不稳定;衰减时机固定,缺乏自适应能力。


2.2 MultiStepLR —— 多阶梯衰减

StepLR 的灵活版本,允许在指定的 epoch 列表处进行衰减。

数学公式

lrepoch=lrinit×γ∣{m∈milestones:m≤epoch}∣ \text{lr}_{\text{epoch}} = \text{lr}_{\text{init}} \times \gamma^{|\{m \in \text{milestones} : m \leq \text{epoch}\}|} lrepoch=lrinit×γ{mmilestones:mepoch}

PyTorch 实现

scheduler = optim.lr_scheduler.MultiStepLR(
    optimizer,
    milestones=[60, 80, 90],  # 在第 60、80、90 epoch 衰减
    gamma=0.1
)

适用场景:当你通过实验观察到 loss 在某些特定 epoch 出现平台期时,可以手动指定衰减点。

2.3 ExponentialLR —— 指数衰减

核心思想:每个 epoch 将学习率乘以 gamma(略小于 1),实现连续平滑的指数下降。

数学公式

lrepoch=lrinit×γepoch \text{lr}_{\text{epoch}} = \text{lr}_{\text{init}} \times \gamma^{\text{epoch}} lrepoch=lrinit×γepoch

gamma 通常取 0.9 ~ 0.99。

请添加图片描述

PyTorch 实现

scheduler = optim.lr_scheduler.ExponentialLR(
    optimizer, gamma=0.95
)
# gamma=0.95 → 100 个 epoch 后 lr 降为初始值的 0.6%
# gamma=0.99 → 100 个 epoch 后 lr 降为初始值的 36.6%

适用场景:RNN、语音识别等需要持续细化的任务。

优点:曲线平滑无突变,只有一个超参。

缺点:后期学习率可能衰减到极小值导致训练停滞;gamma 选择比较敏感。

2.4 CosineAnnealingLR —— 余弦退火

核心思想:学习率按余弦函数从初始值平滑下降到最小值。SGDR(Stochastic Gradient Descent with Warm Restarts)论文推广了带热重启的版本。

数学公式

lrt=ηmin⁡+12(ηmax⁡−ηmin⁡)(1+cos⁡(tTmax⁡π)) \text{lr}_t = \eta_{\min} + \frac{1}{2}(\eta_{\max} - \eta_{\min})\left(1 + \cos\left(\frac{t}{T_{\max}} \pi\right)\right) lrt=ηmin+21(ηmaxηmin)(1+cos(Tmaxtπ))

其中 Tmax⁡T_{\max}Tmax 是半周期长度,ηmin⁡\eta_{\min}ηmin 是最小学习率。
请添加图片描述

PyTorch 实现

# 单周期余弦退火
scheduler = optim.lr_scheduler.CosineAnnealingLR(
    optimizer,
    T_max=50,       # 半周期长度
    eta_min=1e-5    # 最小学习率
)

# 带热重启的版本(SGDR)
scheduler = optim.lr_scheduler.CosineAnnealingWarmRestarts(
    optimizer,
    T_0=10,         # 初始周期长度
    T_mult=2        # 每次重启后周期翻倍
)

适用场景:ImageNet 训练、各类视觉任务。

优点:曲线平滑,理论上优于阶梯衰减;热重启版本可逃离局部最优。

缺点:需要提前知道总训练长度;周期设置不当效果下降明显。

2.5 Warmup + Cosine —— 预热 + 余弦退火

核心思想:分两阶段——先线性预热,学习率从接近 0 增长到目标值;再切换为余弦退火逐渐降低。预热阶段让参数先稳定下来,避免初始梯度过大导致训练崩溃。

这是几乎所有 Transformer 模型(BERT、GPT、ViT、LLaMA 等)的标配策略。

数学公式

预热阶段(t<Twarmupt < T_{\text{warmup}}t<Twarmup):

lrt=lrinit×t+1Twarmup \text{lr}_t = \text{lr}_{\text{init}} \times \frac{t + 1}{T_{\text{warmup}}} lrt=lrinit×Twarmupt+1

余弦阶段(t≥Twarmupt \geq T_{\text{warmup}}tTwarmup):

lrt=ηmin⁡+12(lrinit−ηmin⁡)(1+cos⁡(π⋅t−TwarmupTtotal−Twarmup)) \text{lr}_t = \eta_{\min} + \frac{1}{2}(\text{lr}_{\text{init}} - \eta_{\min})\left(1 + \cos\left(\pi \cdot \frac{t - T_{\text{warmup}}}{T_{\text{total}} - T_{\text{warmup}}}\right)\right) lrt=ηmin+21(lrinitηmin)(1+cos(πTtotalTwarmuptTwarmup))

请添加图片描述

PyTorch 实现

# 方法 1:LambdaLR 自定义
import math

def lr_lambda(epoch):
    warmup_epochs = 10
    total_epochs = 100
    if epoch < warmup_epochs:
        return (epoch + 1) / warmup_epochs
    progress = (epoch - warmup_epochs) / (total_epochs - warmup_epochs)
    return 0.5 * (1 + math.cos(math.pi * progress))

scheduler = optim.lr_scheduler.LambdaLR(optimizer, lr_lambda)

# 方法 2:Hugging Face transformers 库(推荐)
from transformers import get_cosine_schedule_with_warmup

scheduler = get_cosine_schedule_with_warmup(
    optimizer,
    num_warmup_steps=1000,
    num_training_steps=10000
)

适用场景:Transformer 训练(NLP、CV 均适用),几乎是现代大模型训练的事实标准。

优点:避免初期梯度爆炸,兼顾训练稳定性与最终精度。

缺点:需要设置预热步数,实现稍复杂。

2.6 ReduceLROnPlateau —— 自适应衰减

核心思想:监控验证集指标(loss 或 accuracy),当连续 patience 个 epoch 没有改善时,自动将学习率乘以 factor。这是唯一一种"响应式"策略。

衰减规则

若连续 patience 个 epoch 指标未改善:lrnew=lrcurrent×factor \text{若连续 patience 个 epoch 指标未改善:} \quad \text{lr}_{\text{new}} = \text{lr}_{\text{current}} \times \text{factor} 若连续 patience  epoch 指标未改善:lrnew=lrcurrent×factor

请添加图片描述

PyTorch 实现

scheduler = optim.lr_scheduler.ReduceLROnPlateau(
    optimizer,
    mode='min',       # 'min' 监控 loss,'max' 监控 accuracy
    factor=0.5,       # 每次衰减为原来的 50%
    patience=10,      # 容忍 10 个 epoch 无改善
    min_lr=1e-6,      # 学习率下限
    verbose=True      # 打印衰减日志
)

for epoch in range(num_epochs):
    train(model, optimizer)
    val_loss = validate(model)
    scheduler.step(val_loss)  # 注意:必须传入监控指标

适用场景:微调预训练模型、训练长度不确定的探索性实验。

优点:完全自适应,无需预设衰减时间表。

缺点:依赖验证集指标的可靠性;patience 设置需要经验。

注意:与其他调度器不同,ReduceLROnPlateau 的 step() 方法需要传入监控指标(如 val_loss)。

2.7 OneCycleLR —— 超级收敛

核心思想:由 Leslie Smith 在"Super-Convergence"论文中提出。训练分为三段:

  1. 上升阶段(约占 30%):学习率从 max_lr / div_factor 线性上升到 max_lr
  2. 下降阶段(约占 70%):学习率从 max_lr 余弦下降到初始值。
  3. 退火尾声:学习率继续降到极小值 max_lr / (div_factor × final_div_factor)

请添加图片描述

PyTorch 实现

scheduler = optim.lr_scheduler.OneCycleLR(
    optimizer,
    max_lr=0.01,
    total_steps=100,
    pct_start=0.3,          # 上升阶段占 30%
    div_factor=25,          # 初始 lr = max_lr / 25
    final_div_factor=1e4,   # 最终 lr = max_lr / (25 × 1e4)
    anneal_strategy='cos'   # 下降阶段用余弦退火
)

# 注意:OneCycleLR 是按 step(batch)更新,不是按 epoch
for epoch in range(num_epochs):
    for batch in train_loader:
        train_step(model, optimizer, batch)
        scheduler.step()  # 每个 batch 后调用

适用场景:ResNet 快速训练、配合大 batch size 使用。

优点:能实现"超级收敛",大幅减少所需训练 epoch。

缺点:需预先确定总步数,对 max_lr 选择敏感。

2.8 PolynomialLR —— 多项式衰减

核心思想:学习率按多项式函数衰减,power 参数控制衰减曲线的形状。

数学公式

lrepoch=(lrinit−lrend)×(1−epochTtotal)power+lrend \text{lr}_{\text{epoch}} = (\text{lr}_{\text{init}} - \text{lr}_{\text{end}}) \times \left(1 - \frac{\text{epoch}}{T_{\text{total}}}\right)^{\text{power}} + \text{lr}_{\text{end}} lrepoch=(lrinitlrend)×(1Ttotalepoch)power+lrend

  • power = 1:线性衰减
  • power > 1:前期衰减慢,后期加速(如 power=2 为二次衰减)
  • power < 1:前期衰减快,后期趋于平缓

请添加图片描述

PyTorch 实现

# PyTorch 1.13+ 内置
scheduler = optim.lr_scheduler.PolynomialLR(
    optimizer,
    total_iters=100,
    power=2.0       # 二次衰减
)

# 旧版本手动实现
def poly_lr_lambda(epoch):
    total_epochs = 100
    power = 0.9
    return (1 - epoch / total_epochs) ** power

scheduler = optim.lr_scheduler.LambdaLR(optimizer, poly_lr_lambda)

适用场景:语义分割(DeepLab 系列)、目标检测。

优点:衰减形状灵活可调,power=1 时就是线性衰减。

缺点:需要预知总训练步数,power 最佳值依赖任务。

2.9 CyclicLR —— 周期性学习率

核心思想:学习率在 base_lr 和 max_lr 之间周期性波动。triangular2 模式下每个周期的振幅减半。理论依据是周期性提高学习率有助于跳过尖锐的局部最小值,倾向于收敛到更平坦、泛化更好的区域。

数学公式(triangular2 模式)

cycle=⌊epoch/(2×step_size)⌋ \text{cycle} = \lfloor \text{epoch} / (2 \times \text{step\_size}) \rfloor cycle=epoch/(2×step_size)⌋

x=∣epochstep_size−2×cycle−1∣ x = \left| \frac{\text{epoch}}{\text{step\_size}} - 2 \times \text{cycle} - 1 \right| x= step_sizeepoch2×cycle1

lr=base_lr+(max_lr−base_lr)×max⁡(0,1−x)×12cycle \text{lr} = \text{base\_lr} + (\text{max\_lr} - \text{base\_lr}) \times \max(0, 1 - x) \times \frac{1}{2^{\text{cycle}}} lr=base_lr+(max_lrbase_lr)×max(0,1x)×2cycle1

请添加图片描述

PyTorch 实现

scheduler = optim.lr_scheduler.CyclicLR(
    optimizer,
    base_lr=1e-4,         # 下界
    max_lr=0.01,          # 上界
    step_size_up=2000,    # 上升半周期(以 batch 为单位)
    mode='triangular2',   # 振幅逐周期减半
    cycle_momentum=False  # 搭配 Adam 时设为 False
)

适用场景:小数据集、快速实验、学习率范围探索。

优点:无需精确设置总训练长度,能跳出尖锐局部最优。

缺点:波动可能导致训练不稳定,上下界设置需要经验。

三、全部调度器对比

下图展示了在相同初始学习率(0.01)和总 epoch(100)下,8 种调度器的学习率变化曲线对比。

请添加图片描述

对比总结表

调度器 衰减方式 需要预设总步数 自适应 典型场景
StepLR 阶梯式 CNN 分类
MultiStepLR 多阶梯 自定义衰减节点
ExponentialLR 指数平滑 RNN / 语音
CosineAnnealingLR 余弦曲线 ImageNet 训练
Warmup + Cosine 预热 + 余弦 Transformer 全系列
ReduceLROnPlateau 响应式 微调 / 探索性实验
OneCycleLR 升 → 降 → 退火 快速训练 / 大 batch
PolynomialLR 多项式 语义分割
CyclicLR 周期波动 小数据 / 快速实验

四、如何选择调度器?

按任务类型选

  • CNN 图像分类(ResNet、EfficientNet)→ StepLR 或 CosineAnnealingLR
  • Transformer 模型(BERT、GPT、ViT、LLaMA)→ Warmup + Cosine(几乎是唯一选择)
  • 语义分割(DeepLab、SegFormer)→ PolynomialLR(power=0.9)
  • 目标检测(YOLO、Faster R-CNN)→ StepLR、MultiStepLR 或 CosineAnnealingLR
  • 微调预训练模型(不确定训练多久)→ ReduceLROnPlateau
  • 快速训练 / 大 batch→ OneCycleLR

按经验法则选

  1. 不知道选什么? 用 CosineAnnealingLR,它在绝大多数场景都有不错的表现。
  2. 训练 Transformer? 用 Warmup + Cosine,不用犹豫。
  3. 不确定总 epoch 数? 用 ReduceLROnPlateau,让训练动态自己决定。
  4. 想要最快收敛? 试试 OneCycleLR + 大 batch size。
  5. 任务简单 / 赶时间? StepLR 足够了。

请添加图片描述

五、进阶技巧

5.1 组合调度器(ChainedScheduler / SequentialLR)

PyTorch 支持将多个调度器串联:

scheduler1 = optim.lr_scheduler.LinearLR(
    optimizer, start_factor=0.01, total_iters=10
)
scheduler2 = optim.lr_scheduler.CosineAnnealingLR(
    optimizer, T_max=90
)

scheduler = optim.lr_scheduler.SequentialLR(
    optimizer,
    schedulers=[scheduler1, scheduler2],
    milestones=[10]  # 第 10 epoch 从 scheduler1 切换到 scheduler2
)

5.2 学习率查找器(LR Finder)

在正式训练前,用 LR Range Test 找到最佳学习率范围:

# 使用 torch-lr-finder 库
from torch_lr_finder import LRFinder

finder = LRFinder(model, optimizer, criterion)
finder.range_test(train_loader, end_lr=10, num_iter=100)
finder.plot()  # 选择 loss 下降最快的区间
finder.reset()

找到的最佳学习率可以直接用作 OneCycleLR 的 max_lr 或 CyclicLR 的 max_lr

5.3 不同参数组使用不同学习率

微调预训练模型时,通常对骨干网络和新增层使用不同的学习率:

optimizer = optim.Adam([
    {'params': model.backbone.parameters(), 'lr': 1e-5},   # 骨干:小学习率
    {'params': model.head.parameters(), 'lr': 1e-3}        # 新增层:大学习率
])

# 调度器会同时作用于所有参数组
scheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=50)

5.4 监控学习率变化

训练时记录学习率有助于调试:

# 方法 1:获取当前学习率
current_lr = optimizer.param_groups[0]['lr']

# 方法 2:配合 TensorBoard
from torch.utils.tensorboard import SummaryWriter
writer = SummaryWriter()

for epoch in range(num_epochs):
    train(model, optimizer)
    scheduler.step()
    writer.add_scalar('lr', optimizer.param_groups[0]['lr'], epoch)

六、常见误区

  1. optimizer.step() 之前调用 scheduler.step():PyTorch 1.1+ 要求先执行优化器更新,再执行调度器更新,否则第一个 epoch 的学习率会被跳过。

  2. ReduceLROnPlateau 忘记传入指标:这个调度器的 step() 方法需要传入验证指标(如 val_loss),不能像其他调度器一样直接 scheduler.step()

  3. OneCycleLR 按 epoch 更新:OneCycleLR 设计上是按 batch(step)更新的,如果按 epoch 更新会导致学习率变化过慢。

  4. 混淆 T_max 和 total_epochs:CosineAnnealingLR 的 T_max 是半周期长度。如果你的总训练长度是 100 epoch 且只想做一个完整周期,应设 T_max=100

  5. Warmup 步数设置过大:预热阶段通常只占总训练的 5%~10%。过长的预热会浪费训练时间,过短则可能达不到稳定效果。

七、总结

学习率调度是深度学习训练中不可或缺的技术。核心要点:

  • 没有"万能"的调度器,选择取决于任务、模型架构和训练预算。
  • Warmup + Cosine 已成为现代深度学习(尤其是 Transformer)的事实标准。
  • CosineAnnealingLR 是最通用的选择,适合大多数场景。
  • ReduceLROnPlateau 是最"省心"的选择,适合不确定训练长度的场景。
  • 善用 LR Finder 可以大幅减少超参调优的时间。
  • 训练时务必监控和记录学习率变化曲线,它是排查训练问题的重要线索。
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐