Pytorch深入浅出(八)之学习率调整(Scheduler)
虽然官方 API 很全,但有时我们需要特殊的策略(比如前 5 个 Epoch 线性增长做 Warmup,后面再衰减)。我们可以通过自定义函数来实现。
引言
如果说 Optimizer 是登山者的步伐,那么 Learning Rate (LR) 就是步幅的大小。Momentum 就是:方向的惯性
Learning Rate:步幅的大小
- 训练初期:我们需要大步流星(大 LR),快速接近山谷(最优解区域)。
- 训练后期:我们需要小步挪动(小 LR),精细调整,防止在最低点附近来回震荡,最终收敛到精确的最优解。
LR Scheduler 就是那个负责在训练过程中动态调整步幅的“变速箱”。
Momentum:方向的惯性
如果只有 Learning Rate,登山者每一步都会只看脚下的坡度(当前梯度):
坡度一变小,脚步立刻变慢,一遇到小坑(局部极小值 / 鞍点),就容易原地打转甚至停住。
⬇
Momentum 引入了“惯性”这一概念。
⬇
想象一个有重量的球沿着山坡滚动:
每一时刻的“推力”来自当前坡度(当前梯度),但球并不会立刻改变方向,而是会保留之前滚动的趋势。
⬇
- 在平坦区域,它能靠惯性继续前进
- 在震荡区域,它能过滤掉来回摆动的噪声
- 在正确方向上,它会越滚越快
公式
v t = m × v t − 1 + g t v_{t} = m \times v_{t-1} + g_{t} vt=m×vt−1+gt
w t = w t − 1 − l r × v t w_{t} = w_{t-1} - lr \times v_{t} wt=wt−1−lr×vt
当前的更新方向,并不是只由“这一刻的梯度”决定,而是由历史惯性 + 当前梯度共同决定。
g t g_t gt:当前时刻的梯度(脚下的坡)
v t − 1 v_{t-1} vt−1:过去积累的运动趋势(滚动惯性)
m m m:动量系数(惯性有多强)
m m m (momentum系数) 通常设为 0.9,意味着 90% 依赖惯性,10% 依赖当前梯度。
一、为什么需要 Scheduler?
在训练模型时,我们常遇到这种情况:Loss 下降到一定程度后就不再下降了,或者准确率在某个值附近剧烈震荡。这通常意味着当前的学习率对于精细调优来说太大了。
此时,我们需要一个策略:随着训练 Epoch 的增加,逐渐减小学习率。 在 PyTorch 中,这个机制由 torch.optim.lr_scheduler 模块提供。
⚠️ 注意 (版本 1.1.0+) 在 PyTorch 代码中,scheduler.step() 必须放在 optimizer.step() 之后!
- 正确顺序:
Forward->Backward->Optimizer.step()->Scheduler.step() - 错误后果:如果顺序反了,第一个 Epoch 的学习率会被跳过,导致与预期不符。
二、基类_LRScheduler详解
在Pytorch中,这些scheduler都是继承自_LRScheduler类,官方API文档:torch.optim.lr_scheduler
1. 核心属性
optimizer:该调度器关联的优化器(Scheduler 是寄生在 Optimizer 上的)。base_lrs:记录初始学习率(即 Optimizer 中设置的lr)。last_epoch:记录当前训练到了第几个 Epoch。
2. 核心方法
step():执行更新。通常在每个 Epoch 结束时调用,计算并更新下一个 Epoch 的 LR。get_lr():计算逻辑。根据具体的策略(如线性衰减、余弦退火)计算新的 LR。
Scheduler 本质上只做一件事:在合适的时机,修改 Optimizer 中 param_groups 的 lr 值
三、学习率调整策略
PyTorch 提供了十几种策略,但常用的主要分为三类:有序衰减、周期性变化、自适应调整。
1. 有序衰减
随着训练进行,学习率呈阶梯状或线性下降。
StepLR(阶梯衰减)- 原理:每隔固定的
step_size个 Epoch,LR 乘以gamma。 - 场景:最经典的策略,适合大部分基准模型。
- 原理:每隔固定的
MultiStepLR(指定步长衰减)- 原理:在指定的里程碑(如第 30, 80 Epoch)时,LR 乘以
gamma。 - 场景:当你明确知道模型在哪些 Epoch 需要“变挡”时使用。
- 原理:在指定的里程碑(如第 30, 80 Epoch)时,LR 乘以
ExponentialLR(指数衰减)- 原理:每个 Epoch 都乘以
gamma,当gamma较小时衰减得非常快。
- 原理:每个 Epoch 都乘以
2. 周期/平滑调整 (Cyclic / Smooth)
学习率不再是单调递减,而是平滑变化,目前 SOTA 模型常用。
CosineAnnealingLR(余弦退火) 🔥 (推荐)- 原理:LR 按照余弦函数(Cosine)的形状,从最大值平滑下降到最小值。
- 优势:下降过程平滑,相比阶梯下降,更容易找到更优解。
- 场景:训练 CNN、Transformer 等现代模型的首选。
3. 自适应调整 (Adaptive)
只有当模型“学不动”了,才降低学习率。
ReduceLROnPlateau(基于指标调整) 🔥 (实战常用)- 原理:监控某个指标(如
val_loss)。如果该指标在patience个 Epoch 内没有优化,就将 LR 乘以factor。 - 区别:它的
step()需要传入监控的指标值,如scheduler.step(val_loss)。
- 原理:监控某个指标(如
官方API
| 学习率调整策略 | 简要说明 |
|---|---|
lr_scheduler.LRScheduler |
在优化过程中调整学习率。 |
lr_scheduler.LambdaLR |
设置初始学习率。 |
lr_scheduler.MultiplicativeLR |
通过指定函数中的因子来乘以每个参数组的学习率。 |
lr_scheduler.StepLR |
每 step_size 个 epoch,将每个参数组的学习率按 gamma 衰减。 |
lr_scheduler.MultiStepLR |
当 epoch 数量达到 milestones 之一时,将每个参数组的学习率按 gamma 衰减一次。 |
lr_scheduler.ConstantLR |
将每个参数组的学习率乘以一个小的常数因子。 |
lr_scheduler.LinearLR |
通过线性改变小的乘法因子来衰减每个参数组的学习率。 |
lr_scheduler.ExponentialLR |
每 epoch,将每个参数组的学习率按 gamma 衰减。 |
lr_scheduler.PolynomialLR |
使用给定 total_iters 中的多项式函数来衰减每个参数组的学习率。 |
lr_scheduler.CosineAnnealingLR |
使用余弦退火调度设置每个参数组的学习率。 |
lr_scheduler.ChainedScheduler |
链接一系列学习率调度器。 |
lr_scheduler.SequentialLR |
包含一系列调度器,这些调度器预计在优化过程中按顺序调用。 |
lr_scheduler.ReduceLROnPlateau |
当某个度量停止改进时,降低学习率。 |
lr_scheduler.CyclicLR |
根据循环学习率策略(CLR)设置每个参数组的学习率。 |
lr_scheduler.OneCycleLR |
根据 1cycle 学习率策略设置每个参数组的学习率。 |
lr_scheduler.CosineAnnealingWarmRestarts |
使用余弦退火调度设置每个参数组的学习率。 |
四、自定义scheduler
虽然官方 API 很全,但有时我们需要特殊的策略(比如前 5 个 Epoch 线性增长做 Warmup,后面再衰减)。我们可以通过自定义函数来实现。
1.简单实现
实现原理:手动修改 optimizer.param_groups 中的 lr 属性。
def adjust_learning_rate(optimizer, epoch, init_lr):
"""
自定义策略:每 30 个 Epoch,学习率变为原来的 0.1
"""
lr = init_lr * (0.1 ** (epoch // 30))
# 核心:修改 optimizer 中的 param_groups
for param_group in optimizer.param_groups:
param_group['lr'] = lr
return lr
2.继承 _LRScheduler
继承 _LRScheduler 的方式,适用于:
- 想和 PyTorch 原生 Scheduler 完全一致的行为
- 需要
state_dict()/last_epoch/ resume 训练 - 想写成一个“可复用组件”
_LRScheduler 本质上帮我们做了三件事:
- 记录初始学习率 base_lrs
- 维护当前 epoch / step 计数 last_epoch
- 统一管理 step() 与 state_dict()
我们只需要关心一件事:“在第 n 个 epoch,我希望 lr 变成多少?
import torch
from torch.optim.lr_scheduler import _LRScheduler
class CustomStepLR(_LRScheduler):
def __init__(self, optimizer, step_size, gamma=0.1, last_epoch=-1):
self.step_size = step_size
self.gamma = gamma
super().__init__(optimizer, last_epoch)
def get_lr(self):
# 当前 epoch 不需要衰减
if self.last_epoch < self.step_size:
return self.base_lrs
# 计算衰减次数
factor = self.gamma ** (self.last_epoch // self.step_size)
return [base_lr * factor for base_lr in self.base_lrs]
# 使用方式
optimizer = torch.optim.SGD(model.parameters(), lr=0.1)
scheduler = CustomStepLR(optimizer, step_size=30, gamma=0.1)
for epoch in range(num_epochs):
train(...)
optimizer.step()
scheduler.step()
代码实战部分在上一篇优化器笔记的末尾代码实战部分有涉及!
更多推荐



所有评论(0)