3. 学习率调度策略:让模型找到最佳步长

学习率,说白了就是模型每次更新参数时迈的步子大小。步子太大容易扯着蛋——loss 震荡不收敛;步子太小又像蚂蚁搬家——训到天荒地老。所以我们需要一套动态调整学习率的策略,让模型在训练的不同阶段用不同的步长。

我个人习惯把学习率调度比作开车:刚开始路况不明,油门踩大点快速探索;等接近目的地了,就得收油慢行,精确定位。下面这四种策略,是我在实际项目中用得最多的。

3.1 Step Decay:分段降速

Step Decay 是最直观的策略。每隔固定轮数,学习率就乘以一个衰减因子。比如每 30 个 epoch 学习率减半。

# PyTorch 实现
import torch.optim.lr_scheduler as scheduler

optimizer = torch.optim.SGD(model.parameters(), lr=0.1)
step_scheduler = scheduler.StepLR(
    optimizer,
    step_size=30,      # 每 30 个 epoch
    gamma=0.5          # 学习率乘以 0.5
)

for epoch in range(100):
    train_one_epoch()
    step_scheduler.step()
    current_lr = optimizer.param_groups[0]['lr']
    print(f'Epoch {epoch}: lr = {current_lr:.5f}')
输出效果: lr 从 0.1 → 0.05 (epoch 30) → 0.025 (epoch 60) → 0.0125 (epoch 90)

我在项目中遇到过一个问题:step_size 设得太小,学习率降得太快,模型还没学到足够特征就被迫「慢走」了。后来我一般把 step_size 设为总 epoch 数的 1/3 左右,gamma 用 0.1 或 0.5。

我的经验: 如果你不确定 step_size 怎么设,先跑一个短实验看 loss 曲线。loss 下降变平缓的地方,就是该降学习率的位置。

3.2 Exponential Decay:平滑衰减

Exponential Decay 不像 Step Decay 那样跳变,而是每个 epoch 都平滑地乘一个衰减因子。公式很简单:lr = initial_lr * gamma^epoch。

exp_scheduler = scheduler.ExponentialLR(
    optimizer,
    gamma=0.95         # 每个 epoch 乘 0.95
)

for epoch in range(100):
    train_one_epoch()
    exp_scheduler.step()

你想想看,gamma=0.95 的话,100 个 epoch 后学习率会降到初始值的 0.95^100 ≈ 0.006 倍。衰减速度其实挺快的。我个人习惯用 gamma=0.97~0.99,让衰减更温和一些。

注意: Exponential Decay 的衰减曲线是指数级的,前期降得快,后期降得慢。如果你的模型需要长时间精细调优,这个策略可能不太合适——后期学习率太小,模型几乎不动了。

3.3 Cosine Annealing:余弦退火

Cosine Annealing 是我个人最喜欢的策略。它模拟余弦函数的形状:学习率从初始值平滑下降到最小值,然后「跳」回初始值重新开始。这种周期性重启能帮助模型跳出局部最优。

cosine_scheduler = scheduler.CosineAnnealingLR(
    optimizer,
    T_max=50,          # 半个周期的 epoch 数
    eta_min=1e-6       # 最小学习率
)

for epoch in range(100):
    train_one_epoch()
    cosine_scheduler.step()

为什么余弦退火效果好?我举个例子:模型在某个局部极小点卡住了,学习率降到最低时它「困」在那里。但下一个周期学习率突然跳回初始值,模型就有机会跳出这个坑,去探索更好的区域。

关键参数:
  • T_max: 半个周期的长度。一般设为总 epoch 数的 1/2 或 1/4
  • eta_min: 最小学习率。我通常设为初始学习率的 0.01~0.001 倍

我曾经在一个图像分类项目里,用 Step Decay 训到 80 个 epoch 就 plateau 了,换成 Cosine Annealing 后,同样的模型在 120 个 epoch 时又降了 2 个点的 loss。嗯,这就是周期性重启的威力。

3.4 ReduceLROnPlateau:自适应降速

前面三种策略都是「无脑」按 epoch 降学习率,不管模型当前状态如何。ReduceLROnPlateau 则聪明得多——它监控验证集指标,只有当指标不再提升时才降学习率。

plateau_scheduler = scheduler.ReduceLROnPlateau(
    optimizer,
    mode='min',        # 监控 loss,越小越好
    factor=0.5,        # 降为原来的一半
    patience=5,        # 连续 5 个 epoch 没改善就降
    threshold=1e-4,    # 改善阈值
    cooldown=2,        # 降完后冷却 2 个 epoch
    min_lr=1e-7        # 最低学习率
)

for epoch in range(100):
    train_one_epoch()
    val_loss = validate()
    plateau_scheduler.step(val_loss)  # 传入监控指标
避坑指南: 我曾经把 patience 设成 2,结果验证 loss 稍微波动一下就降学习率,模型还没稳定就被打断了。后来我改成 patience=5~10,给模型足够的耐心。

ReduceLROnPlateau 的四个核心参数:

参数 作用 推荐值
factor 每次降多少 0.1~0.5
patience 等几个 epoch 再降 5~10
threshold 多大变化算「改善」 1e-4~1e-3
cooldown 降完后休息几轮 2~5

3.5 四种策略对比

下面这张表帮你快速决策:

策略 适用场景 优点 缺点
Step Decay 训练曲线有明显阶段 简单直观,容易调参 跳变可能破坏稳定性
Exponential Decay 需要平滑衰减 连续平滑,无跳变 后期学习率过小
Cosine Annealing 需要跳出局部最优 周期性重启,探索性强 参数 T_max 敏感
ReduceLROnPlateau 验证集指标波动大 自适应,省心 可能过早降速

3.6 知识结构图

下面这张 SVG 图帮你理清四种策略的核心逻辑:

学习率调度策略核心逻辑 学习率调度策略 Step Decay Exponential Decay Cosine Annealing ReduceLROnPlateau 关键选择原则: • 训练曲线有明显阶段 → Step Decay • 需要平滑衰减 → Exponential Decay • 需要跳出局部最优 → Cosine Annealing • 验证集波动大 → ReduceLROnPlateau 四种策略各有适用场景,建议根据训练曲线动态调整

3.7 实战建议

说了这么多,到底该用哪个?我的建议是:

  1. 新手首选 ReduceLROnPlateau——它最省心,自动帮你判断什么时候降学习率
  2. 追求极致性能用 Cosine Annealing——周期性重启能帮你找到更好的局部最优
  3. 训练时间短用 Step Decay——简单粗暴,效果稳定
  4. 需要平滑衰减用 Exponential Decay——但注意后期学习率别太小
重要提醒: 学习率调度策略不是银弹。如果你的模型本身就不收敛,换再好的调度策略也没用。先确保模型能正常训练,再考虑调优学习率。

嗯,以上就是四种主流学习率调度策略的详解。记住一点:没有最好的策略,只有最适合你当前任务的策略。多跑实验,多看 loss 曲线,你自然能找到感觉。