用 torch.optim.lr_scheduler 比手动改 param_groups 更安全、易复现,除非需分层学习率或响应特定指标;StepLR 适合节奏明确场景,MultiStepLR 更灵活;ReduceLROnPlateau 依赖验证信号;OneCycleLR 加速收敛但参数强耦合;手动修改仅适用于分层学习率或梯度响应式调整。

直接结论:用 torch.optim.lr_scheduler 比手动改 optimizer.param_groups 更安全、更易复现,除非你要做分层学习率或响应特定指标(比如验证 loss 突然上升)。
StepLR 和 MultiStepLR 怎么选?看训练阶段是否固定
StepLR 适合训练节奏明确、loss 下降平滑的场景,比如 baseline 实验或小数据集微调。它每 step_size 个 epoch 就乘一次 gamma,简单粗暴但容易过早衰减——如果 step_size=10 而模型在第 12 epoch 才开始明显下降,那前两轮就白浪费了。
MultiStepLR 更贴近实际需求:你可以把关键节点写死,比如 milestones=[30, 60, 90],对应“初步收敛→精细调整→最后打磨”三个阶段。注意 milestones 是 epoch 编号(从 0 开始计还是从 1 开始?PyTorch 里是训练轮次序号,即第 30 次 scheduler.step() 触发衰减),不是百分比。
- 别把
milestones设成 [10, 20, 30] 后又只训 25 epoch——最后一个衰减永远不触发 -
gamma别设成 0.99 这种“微调式”值,它本意是阶跃下降,0.5 或 0.1 更常见 - 两者都必须在
optimizer.step()之后调用scheduler.step(),顺序反了会跳过第一次更新
ReduceLROnPlateau 为什么常被忽略?因为它依赖验证信号
它不按 epoch 数,而按验证 loss 是否“卡住”来决定是否衰减,特别适合 loss 曲线抖动大、收敛节奏难预测的任务(如小样本分类或生成模型)。但它有个硬性前提:你得有靠谱的验证集和稳定的 val_loss 计算逻辑。
立即学习“Python免费学习笔记(深入)”;
常见坑是没传 mode='min'(默认是 min,但如果你监控的是 accuracy 就得改成 mode='max'),或者 patience 设太小——比如设成 2,结果 val loss 因 batch noise 波动两次就被误判为停滞,导致学习率过早崩塌。
- 必须每次调用
scheduler.step(val_loss),而不是无参的scheduler.step() -
threshold控制“多大波动才算没改善”,默认 1e-4,对 float32 loss 值偏敏感,可酌情放大到 1e-3 - 它不会自动重置 patience 计数器,一旦触发衰减,就得等下一轮真正改善才能恢复
OneCycleLR 为什么能加速收敛?它本质是“先冲再稳”
它在一个 epoch 内完成“升—顶—降”全过程,配合 momentum 反向调节,让模型快速穿过平坦区、避开鞍点。实测中,哪怕只训 10 个 epoch,OneCycleLR 的最终精度也常高于 StepLR 训 50 个 epoch 的结果。
但它的参数强耦合:总 epoch 数 total_steps 必须和训练循环完全一致;max_lr 不能拍脑袋设,建议先跑 torch.optim.lr_scheduler.LearningRateFinder(或手动扫 1e-5 到 1e-1);div_factor(初始 lr 除数)设 25 是经验起点,但若你 max_lr 是 0.01,那起始 lr 就是 4e-4,太小可能启动不了。
-
final_div_factor决定结尾 learning rate 多低,设 1e4 意味着结尾 lr = max_lr / 1e4,对 Adam 来说常低于 1e-7,基本不动了 - 它内部会覆盖 optimizer 的原始
lr,所以初始化时optimizer的 lr 值其实无关紧要 - 别在 resume 训练时直接复用 OneCycleLR——它的周期是绑定 total_steps 的,断点续训需重新计算剩余步数
手动修改 optimizer.param_groups 的唯一合理场景
只有两类情况值得绕开 scheduler:一是给 backbone 和 head 设置不同学习率(迁移学习常见),二是根据训练 loss 的绝对值或梯度 norm 做响应式调整(比如 if grad_norm > 10: lr *= 0.8)。
关键点在于:必须遍历所有 param_group 并单独赋值,因为有些组可能含 weight_decay 但不用调 lr;且绝不能重建 optimizer(如 optimizer = Adam(...)),否则动量、二阶矩等状态全丢。
- 正确写法:
for param_group in optimizer.param_groups: param_group['lr'] *= 0.9 - 错误写法:
optimizer = torch.optim.Adam(model.parameters(), lr=new_lr)—— 动量清零,训练大概率崩溃 - 如果用了多个参数组(比如 bias 不加 weight_decay),记得确认每个组的
'lr'键都存在,否则报KeyError
最易被忽略的细节:所有 scheduler 的 step() 调用时机必须和训练逻辑对齐。例如在 epoch 循环里,scheduler.step() 放在 optimizer.step() 后;但在 batch 级调度(如 OneCycleLR)中,它得放在每个 batch 的 optimizer.step() 之后。错一次,整个学习率曲线就偏移了。


















