3. 固定学习率与衰减策略:步长衰减、指数衰减、分段衰减的实现与调试
说到学习率,你在 FRL 训练里肯定纠结过:设大了训练不稳,设小了半天不动。固定学习率最直接,但我个人很少直接用它跑到底——太容易陷进局部最优了。衰减策略才是让模型「先探索、后收敛」的关键。下面我把三种常用策略掰开揉碎讲清楚,顺便分享我踩过的一些坑。
3.1 固定学习率的陷阱与适用场景
固定学习率就是把 learning_rate 设成一个常数,全程不变。你想想看,这么做的好处是简单:参数少,调试省心。但坏处也很明显——
- 太大会震荡:训练后期 loss 在高位反复跳,永远降不下去。
- 太小会卡住:前期探索不够,模型很快停在某个鞍点。
我记得有一次做连续控制任务,lr 设了 1e-4 就一直不动,后来改成 1e-3 才开始下降。固定学习率其实最适合 调试阶段:先用它跑 50~100 回合,摸清 loss 的大致范围,再决定要不要上衰减。
3.2 步长衰减(Step Decay)
步长衰减是最朴素的衰减方式:每训练 N 步,把学习率乘一个因子 gamma。说白了就是「每隔一段距离砍一刀」,砍的幅度和频率都由你定。
# 伪代码示例
initial_lr = 1e-3
step_size = 2000 # 每 2000 步衰减一次
gamma = 0.5 # 衰减因子
for global_step in range(total_steps):
# 计算当前学习率
current_lr = initial_lr * (gamma ** (global_step // step_size))
# 用 current_lr 更新模型
optimizer.learning_rate.assign(current_lr)
我在项目中遇到过这样一个坑:步长设得太小(比如每 500 步衰减一次),结果学习率下降太快,训练中期就收敛不动了。后来把步长调大到 3000,效果明显改善。所以 步长要匹配一个 epoch 的步数,一般设为一个 epoch 的 1~3 倍左右。
3.3 指数衰减(Exponential Decay)
指数衰减比步长衰减更平滑。它的公式是:
lr = initial_lr × decay_rate (global_step / decay_steps)
其中 decay_steps 控制衰减速度,decay_rate 通常取 0.9~0.99。你想想看,指数衰减的好处是每个 step 都衰减一点点,曲线连续可微,对优化器比较友好。在 RL 里尤其常见——Pytorch 的 ExponentialLR 就是这个原理。
# 用 PyTorch 实现
scheduler = torch.optim.lr_scheduler.ExponentialLR(optimizer, gamma=0.95)
for epoch in range(num_epochs):
train_one_epoch()
scheduler.step()
我记得有一次做 FRL 训练,用指数衰减配合 Adam,loss 曲线特别漂亮——前期快速下降,中期平稳过渡,后期几乎贴地。但要注意:指数衰减 下降速度固定,如果训练步数很长,后期学习率会变得极小(比如 1e-6 量级),导致模型几乎不再更新。所以一般要设一个 min_lr 做下限。
3.4 分段衰减(Piecewise Decay)
分段衰减就是自己画一条学习率曲线——在几个关键节点手动切换值。比如第 0~10k step 用 1e-3,第 10k~20k step 用 5e-4,之后用 2e-4。这种方式自由度最高,但也最依赖经验。
# 分段衰减示例(TensorFlow 风格)
boundaries = [10000, 20000] # 切换点
values = [1e-3, 5e-4, 2e-4] # 对应区间的 lr
def piecewise_lr(global_step):
# 依次判断当前 step 落在哪个区间
for i, bound in enumerate(boundaries):
if global_step < bound:
return values[i]
return values[-1]
我曾经掉进一个大坑:boundaries 和 values 长度没对齐,少写了一个值,结果学习率在第 20000 step 之后直接跳到了 NaN。后来我写了个自动化检查——
assert len(values) == len(boundaries) + 1, "values 数量必须比 boundaries 多一个"
分段衰减在 调参经验丰富 的场景下特别高效。如果你已经知道训练到什么时候需要降低探索率,那直接写分段表比任何自动衰减都更可控。
3.5 三种策略对比与选择指南
| 策略 | 衰减方式 | 收敛速度 | 优点 | 缺点 |
|---|---|---|---|---|
| 步长衰减 | 阶梯跳变 | 中 | 直观易调,每段独立 | 跳变时可能震荡 |
| 指数衰减 | 连续指数下降 | 快(前期) | 平滑,自动,广泛适用 | 后期过小,需设下限 |
| 分段衰减 | 人工分段常数 | 依赖分段点 | 最灵活,可精细控制 | 调参工作量大,易出错 |
我个人的选型逻辑很直白:
- 新手/快速实验 → 指数衰减 + 设 min_lr = 1e-6
- 中期调优 → 步长衰减,gamma=0.8~0.9,step_size = 2~3 个 epoch
- 精准控制 → 分段衰减,先看 loss 曲线确定边界
3.6 调试中的关键检查点
调试时别只看最终 loss,一定要 监控学习率的变化轨迹。我习惯在每个 epoch 记下当前学习率,画一条曲线叠在 loss 曲线下方——如果 loss 突然上升的同时学习率也在跳变,那就是衰减策略有问题。
- 检查衰减是否生效:打印每个 step 的学习率,确认曲线形状对得上预设。
- 监控 loss 抖动:步长衰减的阶梯跳变处,loss 抖动超过 15% 就调小 gamma 或调大步长。
- 设置学习率下限:指数衰减尤其需要,否则后期 lr 趋近于 0,模型直接「死掉」。
- 分段衰减对齐边界:用 assert 检查 values 长度 = boundaries 长度 + 1,我吃过这个亏。