3. 学习率策略:固定学习率 vs 学习率衰减 vs 循环学习率,如何选择与调参
学习率,说白了就是模型每次更新参数时迈的步子大小。步子太大容易扯着蛋——loss 震荡不收敛;步子太小又像老太太散步——训到天荒地老也不见底。
我见过太多同学一上来就设个 0.01,然后盯着 loss 曲线发呆。嗯,今天咱们就把三种主流策略掰开揉碎,看看它们各自适合什么场景。
3.1 固定学习率:简单但危险
固定学习率是最原始的做法。整个训练过程用一个恒定值,比如 0.01 或 0.001。
适用场景:
- 小数据集(几千样本以内)
- 浅层网络(3-5 层)
- 快速原型验证
但我在项目中踩过坑。有一次做图像分类,用 ResNet-50 训 CIFAR-10,固定学习率 0.01,loss 前 20 个 epoch 降得挺好,后面直接震荡成心电图。为什么?因为随着训练进行,最优解附近的地形越来越陡峭,固定步长就变成了「过冲」。
避坑指南:我曾经在工业级推荐系统上试过固定学习率,训了 3 天 loss 死活不降。后来发现是学习率设大了,参数在最优解附近来回跳。换成衰减策略后,半天就收敛了。
3.2 学习率衰减:最常用的策略
学习率衰减的核心思想很简单:前期大步探索,后期小步精调。常见的衰减方式有四种:
| 衰减方式 | 公式/策略 | 典型参数 | 我的使用频率 |
|---|---|---|---|
| Step Decay | 每 N 个 epoch 乘以 gamma | gamma=0.1, step=30 | ⭐⭐⭐⭐ |
| Exponential Decay | lr = lr₀ × exp(-k × epoch) | k=0.01 | ⭐⭐⭐ |
| Cosine Annealing | 余弦曲线从 lr₀ 降到 0 | T_max=50 | ⭐⭐⭐⭐⭐ |
| ReduceLROnPlateau | loss 不降时自动衰减 | patience=5, factor=0.5 | ⭐⭐⭐⭐ |
我个人最常用的是 Cosine Annealing。为什么?因为它衰减得平滑,不会像 Step Decay 那样突然掉一截导致 loss 反弹。你想想看,训练到后期,模型已经在最优解附近了,突然把学习率砍掉 90%,参数更新直接卡死——这种事我遇到过好几次。
# PyTorch 实现 Cosine Annealing
import torch.optim.lr_scheduler as lr_scheduler
optimizer = torch.optim.SGD(model.parameters(), lr=0.1)
scheduler = lr_scheduler.CosineAnnealingLR(optimizer, T_max=50, eta_min=1e-6)
for epoch in range(100):
train_one_epoch()
scheduler.step()
print(f"Epoch {epoch}: lr = {scheduler.get_last_lr()[0]:.6f}")
调参技巧:我习惯把初始学习率设成 0.1 或 0.01,然后观察前 5 个 epoch 的 loss 下降速度。如果 loss 下降太快(每 epoch 降 50% 以上),说明学习率偏大;如果下降太慢(每 epoch 降不到 5%),说明偏小。调整到每 epoch 降 10%-20% 左右,基本就对了。
3.3 循环学习率:跳出局部最优的利器
循环学习率(Cyclical Learning Rate, CLR)是我最近两年越来越喜欢用的策略。它让学习率在上下界之间周期性变化,而不是单调递减。
为什么有效?因为训练过程中,模型可能会陷入局部最优或鞍点。这时候如果学习率突然变大,就能「跳」出来。我曾在 NLP 任务上对比过:用 Cosine Annealing 训到 80 个 epoch 卡住了,换成 CLR 后,第 90 个 epoch 突然又降了一大截。
CLR 有三种常见模式:
- triangular:线性上升再线性下降,最简单
- triangular2:每次周期峰值减半,更稳定
- exp_range:峰值指数衰减,兼顾探索和收敛
# PyTorch 实现 CLR
from torch.optim.lr_scheduler import CyclicLR
optimizer = torch.optim.SGD(model.parameters(), lr=0.001)
scheduler = CyclicLR(
optimizer,
base_lr=0.0001, # 下界
max_lr=0.01, # 上界
step_size_up=2000, # 上升步数
mode='triangular2' # 模式
)
for batch in dataloader:
optimizer.zero_grad()
loss = criterion(model(batch), batch)
loss.backward()
optimizer.step()
scheduler.step()
关键参数速查表:
| 参数 | 含义 | 推荐范围 |
|---|---|---|
| base_lr | 学习率下界 | max_lr 的 1/10 ~ 1/3 |
| max_lr | 学习率上界 | 最优固定 lr 的 2~10 倍 |
| step_size_up | 上升步数(通常 = 每个周期的 1/2) | 2~8 个 epoch 的步数 |
| mode | 循环模式 | triangular / triangular2 / exp_range |
3.4 三种策略对比与选择
我整理了一张对比表,方便你快速决策:
| 维度 | 固定学习率 | 学习率衰减 | 循环学习率 |
|---|---|---|---|
| 收敛速度 | 慢(需手动调优) | 中 | 快(自动探索) |
| 最终精度 | 低(易震荡) | 高 | 高(可跳出局部最优) |
| 调参难度 | 低(1 个参数) | 中(2-3 个参数) | 高(4-5 个参数) |
| 适用数据量 | 小(<1 万) | 中(1 万-100 万) | 大(>10 万) |
| 适用模型深度 | 浅层(<10 层) | 中深层(10-100 层) | 深层(>50 层) |
| 我的推荐指数 | ⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
我的建议是:
- 新手或快速实验:先用 Cosine Annealing,初始 lr=0.01,T_max=50
- 追求极致精度:上 CLR,用 triangular2 模式,base_lr=0.0001,max_lr=0.01
- 资源有限(只能训 1-2 次):用 ReduceLROnPlateau,patience=5,factor=0.5
注意:CLR 不是万能药。我曾在只有 5000 样本的小数据集上试过,结果 loss 震荡得更厉害。因为数据太少,每次学习率变化都会导致模型「失忆」。小数据集老老实实用衰减策略就好。
3.5 核心知识图谱
下面这张图总结了三种策略的适用场景和选择逻辑:
3.6 实战调参流程
最后,我分享一个自己常用的调参流程,你直接照着做就行:
- 第一步:跑 5 个 epoch 找初始 lr
- 用学习率范围测试(LR Range Test),从 1e-6 到 1.0 指数增长
- 找到 loss 下降最快的区间,取中间值作为初始 lr
- 第二步:用 Cosine Annealing 训 50 个 epoch
- 初始 lr = 上一步找到的值
- T_max = 50,eta_min = 1e-6
- 第三步:观察 loss 曲线
- 如果最后 10 个 epoch loss 还在降 → 增加 epoch 数或换 CLR
- 如果 loss 震荡 → 降低初始 lr 或换 ReduceLROnPlateau
- 如果 loss 卡住不动 → 换 CLR 尝试跳出
- 第四步:最终调优
- 用 CLR 的 triangular2 模式,base_lr = 初始 lr / 10,max_lr = 初始 lr × 3
- 周期长度设为 4-8 个 epoch
我的经验:80% 的情况下,Cosine Annealing 就能拿到不错的结果。只有当你发现 loss 在最后 20% 的训练时间里完全不动了,才值得花时间去调 CLR。别一上来就搞复杂策略——先跑通,再优化。