在PyTorch的模型训练流程中,学习率是最敏感的超参数之一。静态学习率往往导致训练初期权重更新剧烈、后期却在最优解附近反复震荡。通过torch.optim.lr_scheduler中提供的各类调度器,我们可以在每一个训练阶段按数学规则调整优化器的学习率,从而兼顾收敛速度与最终精度。

为什么需要动态调整学习率
神经网络优化本质是在损失曲面上寻找极小值。训练刚开始时,参数离最优区域较远,较大的学习率能加快下降;但随着损失曲面趋于平缓,过大的步长会跳过极小值。如果一直使用固定学习率,要么前期太慢,要么后期不稳定。动态衰减策略通过逐步缩小步长,让模型在后期进行精细微调。
从数学角度看,学习率衰减相当于在梯度下降中引入了一个随时间递减的缩放因子。PyTorch并没有修改优化器内部的梯度计算,而是在每个step调用时重写了优化器.param_groups中的lr字段。这意味着调度器与优化器是解耦的,你可以随意组合不同的优化器和调度逻辑。
torch.optim.lr_scheduler基础用法
所有调度器都接收优化器作为第一个参数,并在每个epoch或iteration后调用step方法。最常见的错误是把step放在batch循环内却配置成epoch级调度,导致学习率被过早降到零。下面的示例展示了一个标准训练循环:
import torch
import torch.nn as nn
from torch.optim import Adam
from torch.optim.lr_scheduler import StepLR
model = nn.Linear(10, 2)
optimizer = Adam(model.parameters(), lr=0.01)
scheduler = StepLR(optimizer, step_size=5, gamma=0.5)
for epoch in range(20):
for x, y in data_loader: # 假设已定义data_loader
optimizer.zero_grad()
loss = nn.CrossEntropyLoss()(model(x), y)
loss.backward()
optimizer.step()
scheduler.step() # 每个epoch结束时更新学习率
print(epoch, scheduler.get_last_lr())
上面的StepLR表示每5个epoch将学习率乘以0.5。step_size和gamma是两个核心参数,前者控制衰减频率,后者控制衰减幅度。在调试时可以使用get_last_lr方法打印当前实际学习率,确认调度器是否按预期工作。
StepLR与MultiStepLR的差异
StepLR的衰减点是等间隔的,而MultiStepLR允许手动指定衰减节点,例如在第10和15个epoch衰减。对于已知训练会经历明显阶段的任务,MultiStepLR更灵活。其底层只是判断当前epoch是否在milestones列表中,然后连续乘以gamma。
从工程实践看,等间隔衰减在简单任务上够用,但复杂任务常在特定阶段才需要降压。如果把衰减点设在验证集精度开始平缓的时刻,往往能打破停滞,继续下降一点损失。
指数与余弦衰减策略
ExponentialLR在每个step将学习率乘以gamma,实现平滑指数下降。它适合希望学习率连续缓降、不希望出现阶跃变化的场景。对应公式为lr = lr * gamma ** epoch。
from torch.optim.lr_scheduler import ExponentialLR scheduler = ExponentialLR(optimizer, gamma=0.95) # 每epoch调用scheduler.step()
CosineAnnealingLR则模拟余弦曲线,从初始值平滑降到eta_min,再周期性重置。它在图像分类训练中非常流行,因为能在训练末期给出极小学习率做精细收敛。其更新公式为:
from torch.optim.lr_scheduler import CosineAnnealingLR scheduler = CosineAnnealingLR(optimizer, T_max=20, eta_min=1e-5) # T_max为一个余弦周期对应的epoch数
余弦策略相比阶梯衰减,避免了突然降压带来的训练抖动,很多论文证明它在相同epoch下能提升零点几个百分点的精度。但如果训练意外中断,重启后周期错乱,需要配合检查点保存epoch数来恢复状态。
组合调度与warmup避坑
实际项目中常先使用warmup线性升温,再接余弦衰减。PyTorch提供了LambdaLR或SequentialLR来实现组合。SequentialLR通过milestones切分不同阶段的调度器:
from torch.optim.lr_scheduler import SequentialLR, LinearLR, CosineAnnealingLR warmup = LinearLR(optimizer, start_factor=0.1, total_iters=5) cosine = CosineAnnealingLR(optimizer, T_max=15, eta_min=1e-5) scheduler = SequentialLR(optimizer, schedulers=[warmup, cosine], milestones=[5])
一个典型误区是warmup阶段手动改了optimizer的lr,然后又调用调度器step,导致两者覆盖冲突。正确做法是完全交由调度器管理lr字段,不要在外部直接写optimizer.param_groups[0]['lr'] = xxx。另外,保存和加载检查点时,除了模型权重,也要保存scheduler.state_dict,否则重启后学习率会回到初始值。
如何选择合适的衰减策略
对于短平快的实验,StepLR加早停就能满足;对于追求精度的基准训练,余弦退火几乎是默认选项;当数据量少、易过拟合时,指数衰减配合权重衰减能缓和过拟合。下表列出常见调度器特点:
| 调度器 | 衰减形态 | 适用场景 |
|---|---|---|
| StepLR | 阶跃 | 简单任务快速验证 |
| ExponentialLR | 连续指数 | 希望平滑降压 |
| CosineAnnealingLR | 余弦 | 图像分类等精度敏感任务 |
| MultiStepLR | 手动阶跃 | 已知训练阶段划分 |
需要强调的是,调度器只是辅助手段。如果数据管道有瓶颈、模型容量不足,换再巧妙的衰减策略也救不了训练曲线。建议先固定调度器跑通流程,再针对验证集表现微调step_size或T_max。
小结与调试技巧
使用lr_scheduler时,最核心的原则是明确step的调用频率与调度器设计频率一致。可以在训练脚本里加一行日志,把每个epoch的lr和loss画成曲线,直观看到衰减是否带来loss平台突破。遇到学习率变成0或不变的情况,优先检查是否忘了调用step,或优化器被重新创建导致调度器引用了旧对象。
当多个调度器嵌套时,用SequentialLR而非手动切换,能减少状态不一致bug。只要理解调度器本质只是修改param_groups里的lr,你就能灵活写出符合业务节奏的衰减逻辑,让PyTorch训练更加省心。
PyTorchlr_scheduler学习率衰减修改时间:2026-08-05 20:42:31