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
)

为什么要让学习率跳回去?嗯,这里有个关键点:大学习率能帮助模型跳出局部最优。你想想看,如果模型卡在某个局部谷底,突然来个大步长,说不定就跳出去了,然后在新区域找到更好的解。

我的经验:余弦退火在 NLP 和 Transformer 类模型上效果特别好。我曾经用 BERT 做微调,固定学习率训到 3 个 epoch 就过拟合了,换成余弦退火后训了 10 个 epoch 还在持续提升。

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
)
Adam 的默认参数为什么好用?
  • lr=0.001:经过大量实践验证的黄金初始值
  • betas=(0.9, 0.999):一阶动量衰减 0.9,二阶动量衰减 0.999
  • eps=1e-8:防止除零的小常数

Adam 几乎不需要调参,拿来就能用。但要注意,它也不是万能的。我曾经在某个生成对抗网络的任务中,Adam 训出来的模型质量就是不如 SGD + 余弦退火。所以我的建议是:

  • CV 任务:先试 SGD + Momentum + 余弦退火
  • NLP 任务:Adam 或 AdamW 是标配
  • GAN 训练:Adam 通常更稳定
  • 小数据集:RMSprop 有时表现更好

3.5 学习率策略全景图

下面这张图把各种学习率策略的关系和适用场景梳理清楚了。

学习率策略全景图 学习率策略 手动衰减策略 自适应学习率 周期性策略 Step Decay Exponential RMSprop Adam 余弦退火 各策略适用场景 Step Decay:CV 分类/检测任务,训练阶段清晰可见 Exponential:需要平滑衰减的场景,如语音模型 余弦退火:NLP/Transformer 微调,需要跳出局部最优 RMSprop:RNN/序列模型,梯度爆炸风险高 Adam:通用首选,NLP/GAN/多模态任务 💡 我的建议:新手从 Adam 开始,遇到收敛问题再换手动衰减或余弦退火

3.6 实战建议:如何选择学习率策略

说了这么多,到底该用哪个?我总结了一套实用的选择流程:

  1. 先用 Adam 跑 baseline:lr=0.001,看能不能收敛
  2. 如果收敛但效果一般:换成 SGD + Momentum + 余弦退火
  3. 如果 Loss 震荡严重:试试 RMSprop 或降低初始学习率
  4. 如果训练后期 Loss 下不去:加入学习率衰减,Step Decay 或 Exponential
  5. 如果怀疑陷入局部最优:用余弦退火带重启的版本
曾经踩过的坑:有一次我在训练推荐系统模型时,Adam 的 Loss 降到 0.5 就卡住了。我调了半天学习率、衰减策略都没用。后来发现是 Adam 的 epsilon 默认值 1e-8 太小,导致数值不稳定。改成 1e-7 后,Loss 直接降到了 0.2。所以,有时候问题不在策略本身,而在实现细节。

学习率策略没有银弹。不同任务、不同模型、不同数据量,适合的策略都不一样。我的做法是:先跑一组对比实验,把 Step Decay、余弦退火、Adam 都试一遍,哪个好用哪个。毕竟,实践出真知嘛。