3. 学习率策略:从固定步长到自适应
学习率,说白了就是模型每次更新参数时迈的步子有多大。步子太小,训练慢得像蜗牛爬;步子太大,直接跨过最优点,Loss 震荡甚至发散。我见过太多新手一上来就设个 0.01,然后对着不收敛的曲线发呆。
这一节我们就把学习率的门道彻底讲透。从最基础的固定学习率开始,再到各种衰减策略,最后聊聊那些能自动调节的自适应方法。
3.1 固定学习率:简单但危险
固定学习率就是整个训练过程中学习率保持不变。听起来很省事,对吧?但实际用起来问题不少。
- 选值困难:0.1 可能发散,0.001 可能训到天荒地老
- 后期震荡:接近最优点时,固定步长会导致在最优值附近来回跳动
- 无法逃离鞍点:在梯度近乎为零的平坦区域,固定学习率会让模型卡死
我在项目中遇到过这样一个案例:用 ResNet-50 训练图像分类,固定学习率 0.01,Loss 降到 0.8 左右就再也不动了。换成衰减策略后,最终 Loss 降到了 0.3 以下。你看,问题就出在后期步长太大,参数一直在最优值附近震荡,进不去真正的谷底。
3.2 学习率衰减:让步子越迈越小
既然固定学习率后期有问题,那我们就让学习率随着训练进行逐渐减小。这就是学习率衰减的核心思想。
Step Decay(阶梯式衰减)
每隔固定的 epoch 数,把学习率乘以一个衰减因子。比如每 30 个 epoch 学习率减半。
# Step Decay 实现
def step_decay(epoch, initial_lr=0.1, drop=0.5, epochs_drop=30):
return initial_lr * (drop ** (epoch // epochs_drop))
# 使用 PyTorch 的调度器
scheduler = torch.optim.lr_scheduler.StepLR(
optimizer, step_size=30, gamma=0.5
)
我个人习惯在 CV 任务中先用 Step Decay。为什么?因为视觉任务的数据分布相对稳定,阶梯式下降能让人直观地看到每个阶段的收敛情况。我记得有一次训练检测模型,每 20 个 epoch 学习率降为 0.1 倍,Loss 曲线就像下楼梯一样,每降一次就往下跳一截,特别清晰。
Exponential Decay(指数衰减)
学习率按照指数函数连续衰减。公式是:lr = initial_lr * gamma^epoch。
# Exponential Decay
scheduler = torch.optim.lr_scheduler.ExponentialLR(
optimizer, gamma=0.95
)
指数衰减比 Step Decay 更平滑。你想想看,Step Decay 在衰减点处学习率会突然跳变,有时候会导致 Loss 也跟着跳一下。而指数衰减是连续变化的,训练过程更稳定。
| 策略 | 衰减方式 | 优点 | 缺点 |
|---|---|---|---|
| Step Decay | 阶梯式 | 直观可控,适合CV任务 | 衰减点处可能震荡 |
| Exponential Decay | 连续指数 | 平滑稳定 | 后期学习率过小 |
3.3 余弦退火:让学习率周期性重启
余弦退火是我个人非常喜欢的一种策略。它的学习率按照余弦函数周期性地从大到小变化,然后突然跳回大学习率,再重新衰减。
# 余弦退火调度器
scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(
optimizer, T_max=50, eta_min=1e-6
)
# 带重启的余弦退火
scheduler = torch.optim.lr_scheduler.CosineAnnealingWarmRestarts(
optimizer, T_0=20, T_mult=2, eta_min=1e-6
)
为什么要让学习率跳回去?嗯,这里有个关键点:大学习率能帮助模型跳出局部最优。你想想看,如果模型卡在某个局部谷底,突然来个大步长,说不定就跳出去了,然后在新区域找到更好的解。
3.4 自适应学习率:让优化器自己决定步长
前面几种策略都需要我们手动设定衰减规则。那有没有办法让优化器自己判断什么时候该迈大步、什么时候该迈小步?有,这就是自适应学习率方法。
RMSprop
RMSprop 的核心思想是:对每个参数单独调整学习率。梯度大的参数,学习率自动变小;梯度小的参数,学习率自动变大。
# RMSprop 优化器
optimizer = torch.optim.RMSprop(
model.parameters(), lr=0.01, alpha=0.99, eps=1e-8
)
我记得在训练 RNN 时,梯度爆炸是个老大难问题。RMSprop 的梯度裁剪机制天然适合这种场景。它通过维护梯度平方的滑动平均,自动把梯度大的参数的学习率压下来,有效防止了梯度爆炸。
Adam
Adam 可以说是目前最流行的优化器了。它结合了 Momentum(动量)和 RMSprop 两者的优点:既考虑了梯度的一阶矩(动量),又考虑了二阶矩(自适应学习率)。
# Adam 优化器(推荐参数)
optimizer = torch.optim.Adam(
model.parameters(), lr=0.001, betas=(0.9, 0.999), eps=1e-8
)
lr=0.001:经过大量实践验证的黄金初始值betas=(0.9, 0.999):一阶动量衰减 0.9,二阶动量衰减 0.999eps=1e-8:防止除零的小常数
Adam 几乎不需要调参,拿来就能用。但要注意,它也不是万能的。我曾经在某个生成对抗网络的任务中,Adam 训出来的模型质量就是不如 SGD + 余弦退火。所以我的建议是:
- CV 任务:先试 SGD + Momentum + 余弦退火
- NLP 任务:Adam 或 AdamW 是标配
- GAN 训练:Adam 通常更稳定
- 小数据集:RMSprop 有时表现更好
3.5 学习率策略全景图
下面这张图把各种学习率策略的关系和适用场景梳理清楚了。
3.6 实战建议:如何选择学习率策略
说了这么多,到底该用哪个?我总结了一套实用的选择流程:
- 先用 Adam 跑 baseline:lr=0.001,看能不能收敛
- 如果收敛但效果一般:换成 SGD + Momentum + 余弦退火
- 如果 Loss 震荡严重:试试 RMSprop 或降低初始学习率
- 如果训练后期 Loss 下不去:加入学习率衰减,Step Decay 或 Exponential
- 如果怀疑陷入局部最优:用余弦退火带重启的版本
学习率策略没有银弹。不同任务、不同模型、不同数据量,适合的策略都不一样。我的做法是:先跑一组对比实验,把 Step Decay、余弦退火、Adam 都试一遍,哪个好用哪个。毕竟,实践出真知嘛。