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}')
我在项目中遇到过一个问题:step_size 设得太小,学习率降得太快,模型还没学到足够特征就被迫「慢走」了。后来我一般把 step_size 设为总 epoch 数的 1/3 左右,gamma 用 0.1 或 0.5。
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,让衰减更温和一些。
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) # 传入监控指标
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 图帮你理清四种策略的核心逻辑:
3.7 实战建议
说了这么多,到底该用哪个?我的建议是:
- 新手首选 ReduceLROnPlateau——它最省心,自动帮你判断什么时候降学习率
- 追求极致性能用 Cosine Annealing——周期性重启能帮你找到更好的局部最优
- 训练时间短用 Step Decay——简单粗暴,效果稳定
- 需要平滑衰减用 Exponential Decay——但注意后期学习率别太小
嗯,以上就是四种主流学习率调度策略的详解。记住一点:没有最好的策略,只有最适合你当前任务的策略。多跑实验,多看 loss 曲线,你自然能找到感觉。