学习率调度器理论
学习率调度器(Learning Rate Schedule)是深度学习优化的核心组件之一。通过精心设计的学习率调整策略,我们可以显著加速收敛、提升泛化性能,并训练更大规模的模型。12
1. 学习率预热(Warmup)理论
1.1 Warmup的动机
早期训练的不稳定性是引入Warmup的主要原因。在训练初期,模型面临以下挑战:
| 问题 | 描述 | 影响 |
|---|---|---|
| 梯度方差大 | 随机初始化导致早期梯度方向不稳定 | 参数更新方向混乱 |
| 参数尺度不一致 | 不同层的参数处于不同尺度 | 激活值尺度波动 |
| 自适应优化器问题 | Adam等动量估计未收敛 | 自适应学习率估计不准 |
| 过参数化问题 | 早期模型表达能力过强 | 容易过拟合训练数据 |
Transformer训练的特殊挑战:
Transformer架构中的自注意力机制对学习率极为敏感。Vaswani等人3指出:
- 注意力机制的softmax输出在早期可能接近均匀分布
- 梯度在token位置间波动剧烈
- 需要足够的warmup步数使注意力模式稳定
1.2 线性Warmup的数学描述
线性Warmup是最常用的预热策略:
其中:
- :初始学习率(通常很小,如 或 )
- :预热结束后的最大学习率
- :预热步数
- :当前步数
Transformer标准设置:
def get_linear_lr(step, d_model, warmup_steps=4000, base_lr=0.0, max_lr=2.5e-4):
"""
Transformer原始论文的学习率调度
lr = d_model^(-0.5) * min(step^(-0.5), step * warmup^(-1.5))
"""
return d_model ** (-0.5) * min(
(step + 1) ** (-0.5),
(step + 1) * warmup_steps ** (-1.5)
)
# 等价于:先线性warmup,然后按 step^(-0.5) 衰减1.3 余弦Warmup
余弦Warmup结合了warmup的稳定性和余弦衰减的平滑性:
其中 为衰减周期长度。
简化形式:
def cosine_warmup_schedule(step, warmup_steps, max_steps, max_lr=2.5e-4, min_lr=0.0):
"""余弦Warmup调度"""
if step < warmup_steps:
# 线性warmup
return max_lr * step / warmup_steps
else:
# 余弦衰减
progress = (step - warmup_steps) / (max_steps - warmup_steps)
return min_lr + 0.5 * (max_lr - min_lr) * (1 + np.cos(np.pi * progress))1.4 Warmup的理论保证
收敛性分析:
Warmup的核心理论支撑来自于优化器的收敛性分析。Reddi等人4指出Adam在早期可能不收敛,需要warmup来稳定二阶矩估计。
梯度范数演化:
经验观察表明,经过warmup后,梯度范数趋于稳定:
这使得后续的自适应学习率调整更加可靠。
自适应学习率稳定性:
对于Adam,优化器状态满足:
经过 步warmup后:
当 时, 收敛到真实方差的无偏估计。
2. 余弦退火(Cosine Annealing)理论
2.1 数学形式
Loshchilov & Hutter5提出的余弦退火形式:
其中:
- :最小学习率(通常为 )
- :最大学习率
- :总训练步数或衰减周期
- :当前步数()
可视化:
η
η_max ┤ ╭──────────────╮
│ ╱ ╲
│ ╱ ╲
│ ╱ ╲
│ ╱ ╲
η_min ┤───────────────╱───────────────────────╲───────
0 T/2 T step
2.2 收敛性分析
与固定学习率的对比:
考虑一个 -强凸函数 。使用学习率 的SGD更新为:
固定学习率 的收敛界:
其中 是梯度方差, 为最优解。
余弦退火的渐近行为:
当 时,余弦退火的学习率均值为:
这意味着有效学习率约为最大学习率的一半,与 “learning rate = 1/√(d)” 的经典设置有关。
2.3 Warmup-Restart策略(SGDR)
SGDR(Stochastic Gradient Descent with Warm Restarts)5通过周期性重启来探索不同的局部最小值:
其中 是第 个周期的结束时间。
多周期设置:
class CosineAnnealingWarmRestarts:
def __init__(self, max_lr, min_lr, T_0, T_mult=1):
self.max_lr = max_lr
self.min_lr = min_lr
self.T_0 = T_0
self.T_mult = T_mult
self.T_i = T_0
def get_lr(self, step, T_cur):
"""T_cur: 当前周期已完成的步数"""
return self.min_lr + 0.5 * (self.max_lr - self.min_lr) * \
(1 + np.cos(np.pi * T_cur / self.T_i))
def step(self, step):
"""更新周期计数"""
while step >= self.T_i:
step -= self.T_i
self.T_i *= self.T_mult # 周期长度递增
return self.get_lr(step, 0)2.4 余弦退火与泛化:Sharp/Flat Minima
关键假设:余弦退火倾向于收敛到平坦最小值(Flat Minimum),这与更好的泛化性能相关。
理论分析:
Hessian特征值与泛化的关系:
| 最小值类型 | Hessian | 泛化性能 | 余弦退火倾向 |
|---|---|---|---|
| Sharp | 大 | 较差 | 否 |
| Flat | 小 | 较好 | 是 |
直觉解释:
- 高学习率阶段:模型在损失曲面上快速移动,探索更多区域
- 低学习率阶段:模型精细调整,进入平坦谷底
- 周期性重启:允许模型跳出sharp minima的邻域
实验证据:
Izmailov等人6证明,使用SWA(Stochastic Weight Averaging)和余弦退火可以获得更平坦的最小值:
class SWA:
def __init__(self, model, swa_start, swa_end):
self.model = model
self.swa_start = swa_start
self.swa_end = swa_end
self.swa_weights = None
self.count = 0
def update(self, step):
if self.swa_start <= step <= self.swa_end:
if self.swa_weights is None:
self.swa_weights = {k: v.clone()
for k, v in self.model.named_parameters()}
else:
for k, v in self.model.named_parameters():
self.swa_weights[k] += v
self.count += 1
def average(self):
for k, v in self.swa_weights.items():
self.model.state_dict()[k] = v / self.count3. 阶梯式衰减与指数衰减
3.1 阶梯衰减(Step Decay)
数学形式:
其中 是衰减系数, 是衰减间隔。
常见设置:
def step_decay_schedule(step, initial_lr=0.1, step_size=30, gamma=0.1):
"""阶梯衰减:每30个epoch学习率衰减为原来的1/10"""
num_decays = step // step_size
return initial_lr * (gamma ** num_decays)
# 等效PyTorch实现
scheduler = torch.optim.lr_scheduler.StepLR(
optimizer,
step_size=30, # 每30个epoch
gamma=0.1 # 学习率乘以0.1
)衰减时机分析:
| 衰减时机 | 效果 | 适用场景 |
|---|---|---|
| 每N步 | 简单稳定 | 大多数场景 |
| 每N个epoch | 与batch size对齐 | 小规模训练 |
| 验证集恶化时 | 自适应 | 防止过拟合 |
3.2 指数衰减(Exponential Decay)
数学形式:
其中 是衰减率。
连续时间极限:
当步长 时:
与阶梯衰减的关系:
阶梯衰减是指数衰减的离散近似。当 且 时,两者在对数尺度上几乎重合。
3.3 多项式衰减
数学形式:
其中 控制衰减曲线的形状。
| 值 | 行为 | 特点 |
|---|---|---|
| 线性 | 平稳下降 | |
| 缓衰减 | 早期下降慢 | |
| 快衰减 | 早期下降快 |
def polynomial_decay_schedule(step, total_steps, initial_lr, alpha=1.0):
"""多项式衰减"""
progress = min(step / total_steps, 1.0)
return initial_lr * (1 - progress) ** alpha3.4 Step-wise vs Continuous衰减对比
| 特性 | Step-wise衰减 | Continuous衰减 |
|---|---|---|
| 形式 | 离散跳跃 | 连续平滑 |
| 收敛速度 | 较快(后期可能震荡) | 稳定 |
| 泛化性能 | 可能陷入局部最优 | 更平滑 |
| 实现复杂度 | 简单 | 中等 |
| 典型应用 | 传统CNN | Transformer/LLM |
理论分析:
连续衰减(如余弦)在损失曲面上产生更规则的收敛轨迹:
这对应于阻尼振荡形式的学习率变化,有助于跳出局部最优。
4. 周期性学习率理论
4.1 Cyclic Learning Rate (CLR)
Smith提出的CLR7通过周期性振荡学习率来加速训练:
其中 是周期长度, 是周期索引。
三角CLR(最常用形式):
def triangular_lr(step, step_size, max_lr, min_lr):
"""
三角学习率调度
step_size: 半周期长度
"""
cycle = np.floor(1 + step / (2 * step_size))
x = np.abs(step / step_size - 2 * cycle + 1)
return min_lr + (max_lr - min_lr) * np.maximum(0, 1 - x)
# 等效PyTorch实现
scheduler = torch.optim.lr_scheduler.CyclicLR(
optimizer,
base_lr=1e-6,
max_lr=1e-3,
step_size_up=2000,
mode='triangular'
)4.2 周期性退火与跳过的联系
退火周期与跳过(Skip)的类比:
周期性学习率可以被视为在优化轨迹上人为引入”跳过”(jump)操作:
其中 是由学习率突变引起的额外位移。
Langevin动力学的类比:
在物理启发的优化中,学习率周期类似于热噪声周期:
其中 模拟温度,周期性降低 类似于周期性降低学习率。
4.3 周期性学习率对损失曲面的探索分析
损失曲面与CLR:
CLR通过以下机制探索损失曲面:
- 高学习率阶段:允许参数跨越能量壁垒
- 低学习率阶段:精细收敛到局部最优
- 周期切换:重新定位,探索新区域
理论框架:
考虑梯度下降的连续近似:
当 周期性变化时,有效的梯度方向被周期性缩放,这等价于在参数空间中引入周期性扰动。
收敛保证:
对于凸函数,CLR满足:
其中 是平均学习率, 是梯度方差。
5. 逆缩放与缩放定律
5.1 学习率随模型规模的缩放规律
经典结果:
对于宽度为 的神经网络,经典理论建议:
Transformer的缩放规则:
GPT-3论文8使用的缩放规则:
其中 (某些设置下 )。
5.2 的理论推导
从Edge of Stability角度:
在训练动力学:Edge of Stability理论中,最优学习率满足:
其中 是Hessian的最大特征值。
Hessian特征值与模型规模:
对于过参数化网络,Hessian的最大特征值满足:
其中 是参数数量, 是数据维度。
推导:
- 随机初始化的权重矩阵谱范数:
- 梯度方差缩放:
- Hessian特征值缩放:
因此:
当 与 有固定比例关系时(如Transformer),简化为:
5.3 计算最优训练中的学习率调度
Chinchilla缩放定律9:
在计算最优训练中,模型规模 与训练token数 满足:
这意味着更大的模型需要更多token,但增加的比例小于线性。
学习率与训练进度的协调:
计算最优训练要求学习率调度适应训练进程:
def compute_optimal_schedule(step, model_size, total_steps, base_lr=1e-3):
"""
基于计算最优原则的学习率调度
大模型在早期需要更保守的学习率,
但在训练后期可以保持较高的学习率
"""
# 基础缩放
lr_scale = (model_size / 1e9) ** (-0.5)
# 训练阶段调整
progress = step / total_steps
if progress < 0.1:
# Warmup阶段:更保守
warmup_factor = progress / 0.1
return base_lr * lr_scale * warmup_factor * 0.5
else:
# 主训练阶段:余弦衰减
decay_progress = (progress - 0.1) / 0.9
cosine_factor = 0.5 * (1 + np.cos(np.pi * decay_progress))
return base_lr * lr_scale * (cosine_factor + 0.1)6. 实践指南
6.1 PyTorch实现代码
完整的调度器实现:
import numpy as np
import torch
from torch.optim.lr_scheduler import _LRScheduler
class WarmupCosineScheduler(_LRScheduler):
"""
带有线性warmup的余弦退火调度器
学习率曲线:
- 0 ~ warmup_steps: 线性warmup
- warmup_steps ~ total_steps: 余弦退火
"""
def __init__(
self,
optimizer: torch.optim.Optimizer,
warmup_steps: int,
total_steps: int,
max_lr: float = 2.5e-4,
min_lr: float = 0.0,
last_epoch: int = -1
):
self.warmup_steps = warmup_steps
self.total_steps = total_steps
self.max_lr = max_lr
self.min_lr = min_lr
super().__init__(optimizer, last_epoch)
def get_lr(self):
if self.last_epoch < self.warmup_steps:
# 线性warmup阶段
warmup_factor = self.last_epoch / self.warmup_steps
return [self.max_lr * warmup_factor for _ in self.base_lrs]
else:
# 余弦退火阶段
progress = (self.last_epoch - self.warmup_steps) / \
(self.total_steps - self.warmup_steps)
cosine_factor = 0.5 * (1 + np.cos(np.pi * progress))
return [self.min_lr + (self.max_lr - self.min_lr) * cosine_factor
for _ in self.base_lrs]
class WarmupLinearDecayScheduler(_LRScheduler):
"""
带有线性warmup的线性衰减调度器(GPT风格)
适用于GPT系列模型的训练
"""
def __init__(
self,
optimizer: torch.optim.Optimizer,
warmup_steps: int,
total_steps: int,
max_lr: float = 6e-4,
min_lr: float = 0.0,
last_epoch: int = -1
):
self.warmup_steps = warmup_steps
self.total_steps = total_steps
self.max_lr = max_lr
self.min_lr = min_lr
super().__init__(optimizer, last_epoch)
def get_lr(self):
if self.last_epoch < self.warmup_steps:
# Warmup阶段
warmup_factor = self.last_epoch / self.warmup_steps
return [self.max_lr * warmup_factor for _ in self.base_lrs]
else:
# 线性衰减阶段
progress = (self.last_epoch - self.warmup_steps) / \
(self.total_steps - self.warmup_steps)
decay_factor = 1 - progress
return [self.min_lr + (self.max_lr - self.min_lr) * decay_factor
for _ in self.base_lrs]
class PolynomialDecayScheduler(_LRScheduler):
"""
多项式衰减调度器(LLaMA风格)
特点:
- 更长的warmup
- 余弦阶段后可接多项式衰减
"""
def __init__(
self,
optimizer: torch.optim.Optimizer,
warmup_steps: int,
total_steps: int,
max_lr: float,
min_lr: float = 0.0,
power: float = 1.0, # 多项式指数
lr_decay_steps: int = None,
last_epoch: int = -1
):
self.warmup_steps = warmup_steps
self.total_steps = total_steps
self.max_lr = max_lr
self.min_lr = min_lr
self.power = power
self.lr_decay_steps = lr_decay_steps or total_steps
super().__init__(optimizer, last_epoch)
def get_lr(self):
if self.last_epoch < self.warmup_steps:
# Warmup阶段
warmup_factor = self.last_epoch / self.warmup_steps
return [self.max_lr * warmup_factor for _ in self.base_lrs]
elif self.last_epoch < self.total_steps:
# 多项式衰减阶段
decay_steps = self.last_epoch - self.warmup_steps
total_decay_steps = self.total_steps - self.warmup_steps
progress = decay_steps / total_decay_steps
decay_factor = (1 - progress) ** self.power
return [self.min_lr + (self.max_lr - self.min_lr) * decay_factor
for _ in self.base_lrs]
else:
return [self.min_lr for _ in self.base_lrs]使用示例:
import torch
import torch.nn as nn
# 模型和优化器
model = nn.Sequential(
nn.Linear(768, 3072),
nn.GELU(),
nn.Linear(3072, 768)
)
optimizer = torch.optim.AdamW(
model.parameters(),
lr=1e-4,
weight_decay=0.1
)
# 调度器配置
total_steps = 100000
warmup_steps = 2000
# 方法1:使用自定义调度器
scheduler = WarmupCosineScheduler(
optimizer,
warmup_steps=warmup_steps,
total_steps=total_steps,
max_lr=2e-4,
min_lr=1e-5
)
# 方法2:使用PyTorch内置调度器组合
# Step 1: Warmup
warmup_scheduler = torch.optim.lr_scheduler.LinearLR(
optimizer,
start_factor=0.1, # 从10%的lr开始
end_factor=1.0,
total_iters=warmup_steps
)
# Step 2: 余弦退火
cosine_scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(
optimizer,
T_max=total_steps - warmup_steps,
eta_min=1e-5
)
# Step 3: 链接调度器
scheduler = torch.optim.lr_scheduler.SequentialLR(
optimizer,
schedulers=[warmup_scheduler, cosine_scheduler],
milestones=[warmup_steps]
)
# 训练循环
for step in range(total_steps):
# 前向传播
loss = model(inputs)
# 反向传播
loss.backward()
# 优化器更新
optimizer.step()
scheduler.step()
optimizer.zero_grad()6.2 调度策略选择建议
| 场景 | 推荐调度策略 | 原因 |
|---|---|---|
| 小数据集 (< 100K样本) | Step Decay | 简单有效,易调参 |
| 图像分类 (ResNet) | Cosine Annealing | 平滑收敛,泛化好 |
| Transformer预训练 | Warmup + Cosine | 标准配置,稳定训练 |
| LLM微调 | Linear Warmup + Decay | 保留预训练知识 |
| 强化学习 | Cyclic LR | 探索-利用平衡 |
| 大批量训练 | Layer-wise LR Decay | 底层学习率更低 |
| 高效微调 (LoRA) | 常数 LR 或 Cosine | 避免过大更新 |
Layer-wise Learning Rate Decay:
def layer_wise_lr_decay(optimizer, model, decay_rate=0.9):
"""
底层使用更小的学习率,顶层使用更大的学习率
适用于预训练模型微调
"""
no_decay = ["bias", "LayerNorm.weight", "layernorm.weight"]
optimizer_grouped_parameters = []
for name, param in model.named_parameters():
if not param.requires_grad:
continue
# 判断层数
if "embeddings" in name:
layer_num = 0
else:
layer_num = int(re.search(r'layer\.(\d+)', name).group(1))
depth = model.config.num_hidden_layers - layer_num
lr_scale = decay_rate ** depth
if any(nd in name for nd in no_decay):
optimizer_grouped_parameters.append({
"params": [param],
"lr": optimizer.defaults['lr'] * lr_scale,
"weight_decay": 0.0
})
else:
optimizer_grouped_parameters.append({
"params": [param],
"lr": optimizer.defaults['lr'] * lr_scale,
"weight_decay": optimizer.defaults.get('weight_decay', 0.0)
})
return optimizer.__class__(optimizer_grouped_parameters, **optimizer.defaults)6.3 与优化器的组合策略
AdamW + Cosine Annealing:
这是Transformer训练的标准组合:
# LLaMA 2 的配置
optimizer = torch.optim.AdamW(
model.parameters(),
lr=3e-4,
betas=(0.9, 0.95),
eps=1e-8,
weight_decay=0.1
)
scheduler = {
"type": "CosineAnnealingLR",
"params": {
"T_max": num_training_steps,
"eta_min": 3e-5,
"last_epoch": -1
}
}SGD + Step Decay:
适合传统CNN训练:
optimizer = torch.optim.SGD(
model.parameters(),
lr=0.1,
momentum=0.9,
weight_decay=5e-4
)
scheduler = torch.optim.lr_scheduler.MultiStepLR(
optimizer,
milestones=[30, 60, 90], # 第30, 60, 90个epoch衰减
gamma=0.1
)Lion + Cosine:
Lion优化器10使用更简单的动量形式:
# Lion优化器需要特殊的学习率调度
# 推荐使用 constant 或 cyclic LR
optimizer = Lion(
model.parameters(),
lr=1e-4,
betas=(0.9, 0.99)
)
# Lion不需要warmup,可以直接使用余弦衰减
scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(
optimizer,
T_max=num_training_steps
)6.4 调度器调试技巧
import matplotlib.pyplot as plt
def plot_lr_schedule(scheduler, total_steps, title="Learning Rate Schedule"):
"""可视化学习率调度曲线"""
lrs = []
for step in range(total_steps):
scheduler.last_epoch = step
lrs.append(scheduler.get_lr()[0])
plt.figure(figsize=(12, 4))
plt.plot(range(total_steps), lrs)
plt.xlabel("Step")
plt.ylabel("Learning Rate")
plt.title(title)
plt.grid(True)
plt.show()
# 使用示例
optimizer = torch.optim.AdamW(model.parameters(), lr=1e-4)
scheduler = WarmupCosineScheduler(
optimizer,
warmup_steps=2000,
total_steps=100000,
max_lr=2e-4
)
plot_lr_schedule(scheduler, 100000)核心公式速查
| 调度策略 | 公式 |
|---|---|
| 线性Warmup | |
| 余弦退火 | |
| 阶梯衰减 | |
| 指数衰减 | |
| 多项式衰减 | |
| 三角CLR | $\eta_t = \eta_{\min} + (\eta_{\max} - \eta_{\min})\max(0, 1 - \frac{ |
| 缩放定律 |
参考
相关阅读
- 自适应学习率优化器理论 — Adam等优化器与学习率调度的交互
- 训练动力学:Edge of Stability — 学习率与Hessian特征值的关系
- Transformer缩放定律 — 模型规模与学习率的关系
- 缩放定律起源理论 — 缩放定律的深层机制
Footnotes
-
Goodfellow, I., Bengio, Y., & Courville, A. (2016). Deep Learning. MIT Press. ↩
-
Ruder, S. (2016). An overview of gradient descent optimization algorithms. arXiv:1609.04747. ↩
-
Vaswani, A., et al. (2017). Attention is All You Need. NeurIPS 2017. ↩
-
Reddi, S. J., et al. (2018). On the Convergence of Adam and Beyond. ICLR 2018. ↩
-
Loshchilov, I., & Hutter, F. (2017). SGDR: Stochastic Gradient Descent with Warm Restarts. ICLR 2017. ↩ ↩2
-
Izmailov, P., et al. (2018). Averaging Weights Leads to Wider Optima and Better Generalization. UAI 2018. ↩
-
Smith, L. N. (2017). Cyclical Learning Rates for Training Neural Networks. WACV 2017. ↩
-
Brown, T. B., et al. (2020). Language Models are Few-Shot Learners. NeurIPS 2020. ↩
-
Hoffmann, J., et al. (2022). Training Compute-Optimal Large Language Models. NeurIPS 2022. ↩
-
Chen, S., et al. (2024). Symbolic Discovery of Optimization Algorithms. ICML 2024. ↩