目标:系统掌握深度学习中核心学习率调度策略的设计动机、数学公式、实现方式与适用场景,建立"调度器工具箱"的完整认知。
前置要求:了解基本的神经网络训练流程(前向传播、反向传播、参数更新)和优化器基础(SGD、Adam)。
学习率是深度学习中最重要的超参数——没有之一。选对学习率调度策略,往往比换优化器更有效。如果说优化器决定"怎么走",学习率调度器决定"每一步走多远"。本文按演进脉络系统梳理各类调度策略,给出公式、曲线形状、代码实现和记忆小贴士。
1. 学习率调度的动机(Why Scheduling?)
在深入具体调度器之前,先理解"为什么学习率不能一直不变"。
1.1 固定学习率的困境
使用固定学习率训练时,面临一个根本矛盾:
- 学习率太大:训练初期收敛快,但后期在最优点附近震荡,无法精细收敛
- 学习率太小:训练后期收敛精细,但初期收敛极慢,浪费大量时间
数值示例:假设损失函数 $\mathcal{L} = (\theta - 3)^2$,初始 $\theta = 0$:
- $\eta = 0.5$(太大):$\theta_1 = 0 - 0.5 \times (-6) = 3$(一步到位!但实际中会跨过最优点来回震荡)
- $\eta = 0.001$(太小):$\theta_1 = 0 - 0.001 \times (-6) = 0.006$(走了 1000 步才到 $\theta \approx 2.60$,仍未收敛到 $\theta^* = 3$)
理想方案:初期用大学习率快速接近最优点,后期用小学习率精细收敛。
1.2 学习率调度的核心思想
在训练过程中动态调整学习率,让它随时间(epoch 或 step)按某种规则衰减:
$$ \eta_t = f(\eta_0, t, T, \ldots) $$其中 $\eta_0$ 是初始学习率,$t$ 是当前步数,$T$ 是总步数。
类比:开车下山。刚出发时可以踩油门加速(大学习率快速下降),接近山谷时要踩刹车减速(小学习率精细调整),否则会冲过头。
1.3 调度器的统一框架
所有调度器都遵循同一个框架:在每个 step(或 epoch)结束时,根据某种规则计算新的学习率,通知优化器更新。
flowchart LR
Train["训练循环"] --> Step["一个 step<br/>前向+反向+更新"]
Step --> Scheduler["调度器<br/>计算新 η_t"]
Scheduler --> Update["优化器<br/>使用新 η_t"]
Update --> Step
for epoch in range(num_epochs):
for batch in dataloader:
loss = model(batch)
loss.backward()
optimizer.step() # 用当前学习率更新参数
optimizer.zero_grad()
scheduler.step() # 每个 epoch 结束后更新学习率
2. 基于轮次的调度器(Epoch-based Schedulers)
最简单的调度策略——按固定规则在每个 epoch 结束后衰减学习率。
2.1 StepLR(阶梯衰减)
简介:每隔固定数量的 epoch,将学习率乘以一个衰减因子 $\gamma$。最经典、最常用的调度策略。
$$ \eta_t = \eta_0 \times \gamma^{\lfloor t / \text{step\_size} \rfloor} $$其中 $\gamma$ 是衰减因子(通常取 0.1),$\text{step\_size}$ 是衰减间隔(通常取 30)。
数值示例:$\eta_0 = 0.1$,$\gamma = 0.1$,$\text{step\_size} = 30$:
- epoch 0~29:$\eta = 0.1 \times 0.1^0 = 0.1$
- epoch 30~59:$\eta = 0.1 \times 0.1^1 = 0.01$
- epoch 60~89:$\eta = 0.1 \times 0.1^2 = 0.001$
- epoch 90~:$\eta = 0.1 \times 0.1^3 = 0.0001$
形状:阶梯形——在衰减点突然下降,其余时间保持不变。
类比:考试降级。每隔一段时间考核一次,不合格就降一级学习率。
记忆小贴士:StepLR = “Step"——每隔固定步数衰减一次。阶梯形。
import torch.optim as optim
from torch.optim.lr_scheduler import StepLR
optimizer = optim.SGD(model.parameters(), lr=0.1, momentum=0.9)
scheduler = StepLR(optimizer, step_size=30, gamma=0.1)
for epoch in range(90):
train(...)
scheduler.step()
适用场景:ResNet 等 CNN 训练的经典方案(90 epoch,每 30 epoch 衰减 10 倍)。
2.2 MultiStepLR(多里程碑衰减)
简介:StepLR 的泛化版本——不是每隔固定 epoch 衰减,而是在指定的 epoch 衰减。
$$ \eta_t = \eta_0 \times \gamma^{\sum_i \mathbb{1}[t \geq m_i]} $$其中 $m_i$ 是里程碑列表(如 $[60, 120, 160]$)。
数值示例:$\eta_0 = 0.1$,$\gamma = 0.2$,$\text{milestones} = [60, 120, 160]$:
- epoch 0~59:$\eta = 0.1$
- epoch 60~119:$\eta = 0.1 \times 0.2 = 0.02$
- epoch 120~159:$\eta = 0.02 \times 0.2 = 0.004$
- epoch 160~:$\eta = 0.004 \times 0.2 = 0.0008$
与 StepLR 的区别:StepLR 的衰减间隔固定(如每 30 epoch);MultiStepLR 的衰减点可以任意指定(如 $[60, 120, 160]$),更灵活。
from torch.optim.lr_scheduler import MultiStepLR
scheduler = MultiStepLR(optimizer, milestones=[60, 120, 160], gamma=0.2)
适用场景:需要在特定 epoch 衰减的场景(如目标检测中 YOLO 的训练方案)。
2.3 ExponentialLR(指数衰减)
简介:每个 epoch 都乘以衰减因子 $\gamma$,学习率呈指数下降。
$$ \eta_t = \eta_0 \times \gamma^t $$数值示例:$\eta_0 = 0.1$,$\gamma = 0.95$:
- epoch 0:$\eta = 0.1$
- epoch 10:$\eta = 0.1 \times 0.95^{10} = 0.0599$
- epoch 50:$\eta = 0.1 \times 0.95^{50} = 0.00769$
- epoch 100:$\eta = 0.1 \times 0.95^{100} = 0.000592$
形状:平滑的指数下降曲线。
类比:匀速刹车。不像 StepLR 那样"踩一脚松一脚”,而是持续均匀地减速。
记忆小贴士:ExponentialLR = “Exponential"——每步乘以 $\gamma$,指数下降。
from torch.optim.lr_scheduler import ExponentialLR
scheduler = ExponentialLR(optimizer, gamma=0.95)
适用场景:需要平滑衰减时的简单方案。缺点是后期学习率衰减太快,可能过早停止学习。
2.4 基于轮次的调度器对比
| 调度器 | 形状 | 灵活性 | 特点 |
|---|---|---|---|
| StepLR | 阶梯形 | 低 | 最经典,ResNet 标配 |
| MultiStepLR | 不规则阶梯 | 中 | 指定任意衰减点 |
| ExponentialLR | 指数曲线 | 低 | 平滑但衰减太快 |
3. 基于指标的调度器(Metric-based Schedulers)
基于轮次的调度器"盲目"衰减——不管模型是否还在进步,到了时间就衰减。基于指标的调度器"智能"衰减——只在模型不再进步时才衰减。
3.1 ReduceLROnPlateau(Plateau 检测)
简介:监控某个指标(通常是验证损失),当指标在连续若干个 epoch 内不再显著改善时,将学习率乘以衰减因子。
核心逻辑:
- 每个 epoch 结束后,计算验证损失 $\mathcal{L}_{\text{val}}$
- 如果 $\mathcal{L}_{\text{val}}$ 在最近
patience个 epoch 内没有改善超过threshold,则 $\eta \leftarrow \eta \times \text{factor}$
关键参数:
| 参数 | 含义 | 典型值 |
|---|---|---|
mode | 监控指标方向(min 越小越好,max 越大越好) | min |
patience | 等待多少个 epoch 无改善才衰减 | 10 |
factor | 衰减因子 | 0.1 |
threshold | 改善的最小幅度 | 1e-4 |
数值示例:假设 patience=3,factor=0.1,验证损失序列:
epoch 1: val_loss = 0.50 (best)
epoch 2: val_loss = 0.48 (best)
epoch 3: val_loss = 0.47 (best)
epoch 4: val_loss = 0.475 (no improve, count=1)
epoch 5: val_loss = 0.478 (no improve, count=2)
epoch 6: val_loss = 0.476 (no improve, count=3)
→ 触发衰减!η ← η × 0.1
epoch 7: val_loss = 0.46 (best,新学习率生效)
类比:耐心等公交。你等了 patience 分钟公交还没来(损失不再下降),就决定换一种交通方式(衰减学习率)。
记忆小贴士:ReduceLROnPlateau = “Reduce LR on Plateau"——在平台期减少学习率。智能但需要验证集。
from torch.optim.lr_scheduler import ReduceLROnPlateau
scheduler = ReduceLROnPlateau(optimizer, mode='min', factor=0.1, patience=10, threshold=1e-4)
for epoch in range(num_epochs):
train(...)
val_loss = validate(...)
scheduler.step(val_loss) # 注意:需要传入监控指标
适用场景:不确定何时该衰减时的"自适应"方案。缺点是需要验证集,且 patience 的选择需要经验。
4. 余弦族调度器(Cosine-based Schedulers)
余弦调度器用余弦函数平滑地衰减学习率——既不是阶梯形的突变,也不是指数形的过快衰减。是现代深度学习训练的主流选择。
4.1 CosineAnnealingLR(余弦退火)
简介:学习率按余弦函数从初始值平滑衰减到最小值。形状像日落——从高点自然渐暗到地平线。
$$ \eta_t = \eta_{\min} + \frac{1}{2}(\eta_{\max} - \eta_{\min})\left(1 + \cos\left(\frac{t}{T}\pi\right)\right) $$数值示例:$\eta_{\max} = 0.1$,$\eta_{\min} = 0$,$T = 100$:
- $t = 0$:$\eta = 0 + 0.5 \times 0.1 \times (1 + \cos(0)) = 0.5 \times 0.1 \times 2 = 0.1$
- $t = 25$:$\eta = 0.5 \times 0.1 \times (1 + \cos(0.25\pi)) = 0.05 \times (1 + 0.707) = 0.0854$
- $t = 50$:$\eta = 0.05 \times (1 + \cos(0.5\pi)) = 0.05 \times (1 + 0) = 0.05$
- $t = 75$:$\eta = 0.05 \times (1 + \cos(0.75\pi)) = 0.05 \times (1 - 0.707) = 0.0146$
- $t = 100$:$\eta = 0.05 \times (1 + \cos(\pi)) = 0.05 \times (1 - 1) = 0$
形状:平滑的余弦曲线,从 $\eta_{\max}$ 降到 $\eta_{\min}$。
类比:日落。学习率像太阳一样,从最高点(正午)平滑地"落"到地平线($\eta_{\min}$)——不是突然关灯,而是自然渐暗。
记忆小贴士:CosineAnnealing = “余弦退火”——像金属退火一样,温度(学习率)按余弦曲线平滑降低。
from torch.optim.lr_scheduler import CosineAnnealingLR
scheduler = CosineAnnealingLR(optimizer, T_max=100, eta_min=0)
适用场景:现代深度学习训练的主流选择。常与 Warmup 组合使用(见 §5)。
4.2 CosineAnnealingWarmRestarts(带热重启的余弦退火)
简介:在余弦退火的基础上,每隔 $T_0$ 个 epoch 重启学习率到初始值,然后重新按余弦衰减。每次重启后,周期长度乘以 $T_{\text{mult}}$。
$$ \eta_t = \eta_{\min} + \frac{1}{2}(\eta_{\max} - \eta_{\min})\left(1 + \cos\left(\frac{t' }{T_i}\pi\right)\right) $$其中 $t'$ 是当前周期内的步数,$T_i$ 是当前周期的长度。
周期长度变化($T_0 = 10$,$T_{\text{mult}} = 2$):
- 第 1 个周期:$T_1 = 10$(epoch 0~9)
- 第 2 个周期:$T_2 = 10 \times 2 = 20$(epoch 10~29)
- 第 3 个周期:$T_3 = 20 \times 2 = 40$(epoch 30~69)
形状:多个余弦半波,每个半波从 $\eta_{\max}$ 降到 $\eta_{\min}$,然后突然重启。
类比:间歇训练。像运动员做间歇训练——冲刺(高学习率)→ 减速(余弦衰减)→ 休息(低学习率)→ 重新冲刺(重启)。
记忆小贴士:WarmRestarts = “热重启"——学习率降到最低后,突然"加热"回最高点。
from torch.optim.lr_scheduler import CosineAnnealingWarmRestarts
scheduler = CosineAnnealingWarmRestarts(optimizer, T_0=10, T_mult=2)
适用场景:需要逃离局部最小值时(重启可以帮助跳出当前盆地)。SGDR(Stochastic Gradient Descent with Warm Restarts)论文的核心策略。
4.3 余弦族对比
| 调度器 | 形状 | 重启 | 特点 |
|---|---|---|---|
| CosineAnnealingLR | 单个余弦半波 | 无 | 平滑衰减,现代主流 |
| CosineAnnealingWarmRestarts | 多个余弦半波 | 有 | 周期性重启,逃离局部最小值 |
5. 预热策略(Warmup)
训练初期参数是随机初始化的,梯度方向不可靠。用大学习率更新可能导致训练发散。预热策略在训练初期用很小的学习率"试探”,等梯度方向稳定后再升到目标学习率。
5.1 线性预热(Linear Warmup)
简介:学习率从接近 0 线性增长到目标学习率,通常在前 5~10% 的训练步数内完成。
$$ \eta_t = \eta_{\text{target}} \cdot \frac{t}{T_{\text{warmup}}} \quad (t \leq T_{\text{warmup}}) $$数值示例:$\eta_{\text{target}} = 10^{-4}$,$T_{\text{warmup}} = 1000$:
- $t = 0$:$\eta = 0$
- $t = 250$:$\eta = 10^{-4} \times 250/1000 = 2.5 \times 10^{-5}$
- $t = 500$:$\eta = 5 \times 10^{-5}$
- $t = 1000$:$\eta = 10^{-4}$(达到目标)
为什么需要预热:训练初期,随机初始化的参数产生的梯度方向不可靠。如果直接用大学习率,可能一步跨到损失曲面的"悬崖"上。预热相当于"小心试探”——用小步幅走几步,等梯度方向稳定后再大步走。
类比:热车。冬天开车前需要先热车(预热),等发动机(模型参数)稳定后再加速(大学习率)。直接猛踩油门可能伤车(训练发散)。
记忆小贴士:Warmup = “预热”——像运动员热身一样,先小幅度活动,再进入正赛。
5.2 预热 + 余弦退火(现代 LLM 标配)
现代大语言模型的训练几乎都采用 Warmup + Cosine Annealing 的组合:先线性预热,再余弦衰减。
$$ \eta_t = \begin{cases} \eta_{\text{target}} \cdot \dfrac{t}{T_{\text{warmup}}} & \text{if } t \leq T_{\text{warmup}} \\[8pt] \eta_{\min} + \dfrac{1}{2}(\eta_{\text{target}} - \eta_{\min})\left(1 + \cos\left(\dfrac{t - T_{\text{warmup}}}{T - T_{\text{warmup}}}\pi\right)\right) & \text{if } t > T_{\text{warmup}} \end{cases} $$形状:先线性上升(预热),再余弦下降(退火)——呈"先升后降"的山峰形。
from torch.optim.lr_scheduler import CosineAnnealingLR, LinearLR, SequentialLR
optimizer = optim.AdamW(model.parameters(), lr=1e-4)
warmup = LinearLR(optimizer, start_factor=0.01, total_iters=1000)
cosine = CosineAnnealingLR(optimizer, T_max=9000, eta_min=1e-6)
scheduler = SequentialLR(optimizer, schedulers=[warmup, cosine], milestones=[1000])
for epoch in range(10000):
train(...)
scheduler.step()
适用场景:BERT、GPT、LLaMA 等 Transformer 模型预训练的标准方案。
6. 高级调度器(Advanced Schedulers)
以下调度器在特定场景下有独特优势。
6.1 OneCycleLR(超级收敛)
简介:Leslie Smith 2018 提出的"超级收敛"策略。整个训练过程中学习率呈单峰形状——先从低升到高,再从高降到极低。
$$ \eta_t = \begin{cases} \eta_{\min} + (\eta_{\max} - \eta_{\min}) \cdot \dfrac{t}{T_{\text{up}}} & \text{if } t \leq T_{\text{up}} \\[8pt] \eta_{\max} - (\eta_{\max} - \eta_{\min}) \cdot \dfrac{t - T_{\text{up}}}{T - T_{\text{up}}} & \text{if } t > T_{\text{up}} \end{cases} $$关键特性:学习率先升后降的同时,动量(momentum)先降后升——学习率高时动量低(探索),学习率低时动量高(精细收敛)。
形状:三角形——先线性上升,再线性下降。
类比:过山车。学习率像过山车一样——先爬升到最高点(最大学习率),再俯冲到最低点(最小学习率)。上升阶段快速探索,下降阶段精细收敛。
记忆小贴士:OneCycle = “一个周期"——整个训练只有一个"先升后降"的周期。Super-convergence 的核心。
from torch.optim.lr_scheduler import OneCycleLR
scheduler = OneCycleLR(optimizer, max_lr=0.01, total_steps=10000)
for epoch in range(num_epochs):
for batch in dataloader:
train_step(...)
scheduler.step() # 注意:OneCycleLR 每个 step 更新,不是每个 epoch
适用场景:需要快速训练时(fast.ai 推荐)。可以在更少的 epoch 内达到相同精度。
6.2 CyclicLR(循环学习率)
简介:Leslie Smith 2017 提出,学习率在 $[\eta_{\min}, \eta_{\max}]$ 之间周期性振荡。
三种模式:
- triangular:三角波形,线性升降
- triangular2:三角波形,振幅每周期减半
- exp_range:三角波形,振幅按指数衰减
类比:振荡搜索。像在山谷中来回走动——不是一路下坡,而是故意在一定范围内振荡,以探索更多区域、逃离局部最小值。
记忆小贴士:CyclicLR = “循环“学习率——在高低之间反复振荡。
from torch.optim.lr_scheduler import CyclicLR
scheduler = CyclicLR(optimizer, base_lr=1e-5, max_lr=1e-3, step_size_up=2000, mode='triangular')
适用场景:不确定最优学习率范围时,CyclicLR 可以自动在范围内探索。
6.3 LambdaLR(自定义调度)
简介:用一个 lambda 函数定义任意调度规则——最大的灵活性。
from torch.optim.lr_scheduler import LambdaLR
# 自定义:前 10% warmup,之后线性衰减
def lr_lambda(current_step):
warmup_steps = 1000
if current_step < warmup_steps:
return current_step / warmup_steps
return max(0.0, 1.0 - (current_step - warmup_steps) / (total_steps - warmup_steps))
scheduler = LambdaLR(optimizer, lr_lambda)
适用场景:标准调度器无法满足需求时的"终极方案”。
7. 调度器选择指南
| 场景 | 推荐调度器 | 核心原因 | 记忆关键词 |
|---|---|---|---|
| CNN 经典训练 | StepLR | 简单有效,ResNet 标配 | 阶梯衰减 |
| 不确定何时衰减 | ReduceLROnPlateau | 自适应,基于验证指标 | 平台期检测 |
| Transformer / LLM | Warmup + Cosine | 现代标配,训练稳定 | 先升后余弦降 |
| 快速训练 | OneCycleLR | 超级收敛,更少 epoch | 过山车 |
| 探索最优 LR | CyclicLR | 周期性振荡,自动探索 | 循环振荡 |
| 自定义需求 | LambdaLR | 最大灵活性 | 任意函数 |
| 不确定时 | CosineAnnealingLR | 平滑有效,万金油 | 余弦退火 |
8. 调度器演进脉络
flowchart TD
Fixed["固定学习率"] --> StepLR["StepLR"]
Fixed --> ExpLR["ExponentialLR"]
StepLR --> MultiStepLR["MultiStepLR"]
Fixed --> ReduceLR["ReduceLROnPlateau<br/>2012"]
Fixed --> Cosine["CosineAnnealingLR<br/>2016"]
Cosine --> WarmRestarts["CosineWarmRestarts<br/>2017"]
Fixed --> Cyclic["CyclicLR<br/>2017"]
Cyclic --> OneCycle["OneCycleLR<br/>2018"]
Cosine --> WarmupCosine["Warmup + Cosine"]
Fixed --> Lambda["LambdaLR"]
从上图可以看出,调度器的发展遵循一条清晰的脉络:
- 固定规则期:StepLR、ExponentialLR——简单但不灵活
- 自适应期:ReduceLROnPlateau——根据验证指标智能衰减
- 平滑衰减期:CosineAnnealingLR——余弦曲线平滑衰减,成为主流
- 组合策略期:Warmup + Cosine、OneCycleLR——多种策略组合,效果最优
理解了这条演进脉络,就掌握了调度器设计的核心哲学:在衰减的平滑性(避免突变)和自适应性(根据训练状态调整)之间寻找最佳平衡点。
延伸阅读:关于 Warmup 为什么对 Transformer 训练至关重要的理论分析(从梯度方差的角度),以及 OneCycleLR 的"超级收敛"现象的数学解释,可参考 大语言模型训练机制全解·第一篇 的相关章节。