PyTorch Optimizer 与 Scheduler 指南
📑 目录
- 背景与动机
- 核心概念与定义
- [Optimizer 深入理解](#Optimizer 深入理解)
- [Scheduler 深入理解](#Scheduler 深入理解)
- [Optimizer 与 Scheduler 的关系](#Optimizer 与 Scheduler 的关系)
- [常用 Scheduler 详解](#常用 Scheduler 详解)
- 训练循环集成
- [PyTorch Lightning 集成](#PyTorch Lightning 集成)
- 高级技巧与策略
- 常见问题与最佳实践
- 扩展阅读与进阶方向
1. 背景与动机
1.1 为什么需要学习率调度?
在深度学习训练中,学习率(Learning Rate) 是最重要的超参数之一。
问题场景
固定学习率的局限:
python
# 使用固定学习率训练
optimizer = torch.optim.SGD(model.parameters(), lr=0.1)
# 问题:
# 1. 训练初期:学习率太大 → 震荡、不稳定
# 2. 训练后期:学习率太大 → 难以收敛到最优点
# 3. 全程使用:无法适应训练的不同阶段
训练曲线示意:
Loss
│
│ 固定学习率(lr=0.1)
│ ╱╲ ╱╲ ╱╲ ╱╲
│ ╱ ╲╱ ╲╱ ╲╱ ╲ ← 后期震荡,难以收敛
│ ╱
│ ╱
│╱
└────────────────────> Epochs
│ 动态学习率(使用 Scheduler)
│ ╱╲
│ ╱ ╲___________ ← 后期学习率降低,平滑收敛
│╱
└────────────────────> Epochs
1.2 Scheduler 的核心价值
| 优势 | 说明 |
|---|---|
| 加速收敛 | 初期大学习率快速下降 loss |
| 提升性能 | 后期小学习率精细调优 |
| 避免震荡 | 动态调整避免参数更新过大 |
| 泛化能力 | 特定策略(如余弦退火)提升泛化 |
| 自动化 | 无需手动调整学习率 |
1.3 实际案例
ImageNet 训练典型策略:
python
# ResNet 训练方案(论文标准)
optimizer = SGD(model.parameters(), lr=0.1, momentum=0.9)
scheduler = MultiStepLR(optimizer, milestones=[30, 60, 90], gamma=0.1)
# 学习率变化:
# Epoch 0-29: lr = 0.1
# Epoch 30-59: lr = 0.01 (降低 10 倍)
# Epoch 60-89: lr = 0.001 (再降低 10 倍)
# Epoch 90+: lr = 0.0001
2. 核心概念与定义
2.1 什么是 Optimizer?
Optimizer(优化器) 是根据损失函数的梯度来更新模型参数的算法。
核心任务:
参数更新公式(简化版):
θ_new = θ_old - lr × gradient
其中:
- θ: 模型参数
- lr: 学习率
- gradient: 梯度(损失函数对参数的偏导数)
常见 Optimizer:
- SGD: 随机梯度下降
- Adam: 自适应学习率优化器
- AdamW: 修正权重衰减的 Adam
- RMSprop: 自适应学习率方法
2.2 什么是 Scheduler?
Scheduler(学习率调度器) 是动态调整 Optimizer 学习率的策略。
核心任务:
在训练过程中,根据预定规则调整学习率:
lr(epoch) = f(initial_lr, epoch, ...)
常见策略:
- 阶梯式衰减(StepLR)
- 指数衰减(ExponentialLR)
- 余弦退火(CosineAnnealingLR)
- 基于指标(ReduceLROnPlateau)
2.3 两者的关系
关系模型:
┌─────────────────────────────────────┐
│ Training Loop │
│ │
│ ┌──────────────┐ │
│ │ Model │ │
│ └──────┬───────┘ │
│ │ │
│ ▼ │
│ ┌──────────────┐ │
│ │ Loss & Grad │ │
│ └──────┬───────┘ │
│ │ │
│ ▼ │
│ ┌──────────────┐ │
│ │ Optimizer │ ◄─────┐ │
│ │ (更新参数) │ │ │
│ └──────┬───────┘ │ │
│ │ 读取/修改 lr │
│ │ │ │
│ ┌──────▼──────────────┴──┐ │
│ │ Scheduler │ │
│ │ (调整学习率) │ │
│ └────────────────────────┘ │
│ │
└─────────────────────────────────────┘
关键点:
1. Scheduler 不直接更新参数
2. Scheduler 通过修改 Optimizer 的学习率来影响训练
3. 一个 Optimizer 可以配合多个 Scheduler(链式调度)
3. Optimizer 深入理解
3.1 Optimizer 的内部结构
python
import torch.optim as optim
# 创建 Optimizer
optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.9)
# 查看内部结构
print(optimizer.state_dict())
# 输出:
# {
# 'state': {}, # 优化器状态(如动量)
# 'param_groups': [
# {
# 'lr': 0.01,
# 'momentum': 0.9,
# 'params': [参数ID列表],
# ...
# }
# ]
# }
关键组成部分:
| 组件 | 作用 | 示例 |
|---|---|---|
| param_groups | 参数组列表,每组有独立的超参数 | 不同层使用不同学习率 |
| lr | 学习率 | 0.01 |
| state | 优化器内部状态 | Adam 的一阶、二阶动量 |
| params | 要优化的参数 | model.parameters() |
3.2 param_groups 详解
param_groups 是 Scheduler 修改学习率的核心:
python
# 单个参数组
optimizer = optim.Adam(model.parameters(), lr=0.001)
print(len(optimizer.param_groups)) # 输出: 1
# 访问学习率
current_lr = optimizer.param_groups[0]['lr']
print(f"当前学习率: {current_lr}")
# 手动修改学习率(Scheduler 就是这样做的)
optimizer.param_groups[0]['lr'] = 0.0001
多参数组示例(不同层不同学习率):
python
optimizer = optim.SGD([
{'params': model.base.parameters(), 'lr': 1e-3}, # 参数组 0
{'params': model.classifier.parameters(), 'lr': 1e-2} # 参数组 1
], momentum=0.9)
print(len(optimizer.param_groups)) # 输出: 2
# Scheduler 会同时调整两个参数组的学习率
3.3 常用 Optimizer 对比
| Optimizer | 特点 | 适用场景 | 学习率敏感度 |
|---|---|---|---|
| SGD | 简单、稳定 | CV 任务、需要最佳性能 | 高(需精细调参) |
| SGD + Momentum | 加速收敛、减少震荡 | 大部分任务 | 高 |
| Adam | 自适应学习率、快速收敛 | NLP、快速原型 | 中 |
| AdamW | 修正权重衰减 | Transformer 模型 | 中 |
| RMSprop | 适应不同梯度尺度 | RNN、强化学习 | 中 |
4. Scheduler 深入理解
4.1 Scheduler 的工作原理
核心机制 :通过修改 optimizer.param_groups[i]['lr'] 来调整学习率。
python
# Scheduler 的简化实现
class SimpleScheduler:
def __init__(self, optimizer, gamma=0.1):
self.optimizer = optimizer
self.gamma = gamma
def step(self):
"""每次调用时降低学习率"""
for param_group in self.optimizer.param_groups:
param_group['lr'] *= self.gamma
print(f"学习率更新为: {param_group['lr']}")
# 使用
optimizer = optim.SGD(model.parameters(), lr=0.1)
scheduler = SimpleScheduler(optimizer, gamma=0.1)
scheduler.step() # lr: 0.1 → 0.01
scheduler.step() # lr: 0.01 → 0.001
4.2 Scheduler 的分类
Scheduler 分类
│
├─ 基于 Epoch 的调度
│ ├─ StepLR (阶梯式)
│ ├─ MultiStepLR (多阶梯)
│ ├─ ExponentialLR (指数衰减)
│ ├─ CosineAnnealingLR (余弦退火)
│ └─ PolynomialLR (多项式)
│
├─ 基于指标的调度
│ └─ ReduceLROnPlateau (性能停滞时降低)
│
├─ 循环调度
│ ├─ CyclicLR (周期性变化)
│ └─ OneCycleLR (单周期策略)
│
└─ 链式调度
└─ ChainedScheduler (组合多个 Scheduler)
4.3 调用时机
| Scheduler 类型 | 调用位置 | 调用时机 | 示例 |
|---|---|---|---|
| 基于 Epoch | epoch 结束后 | scheduler.step() |
StepLR, CosineAnnealingLR |
| 基于 Step | 每个 batch 后 | scheduler.step() |
OneCycleLR |
| 基于指标 | validation 后 | scheduler.step(metric) |
ReduceLROnPlateau |
5. Optimizer 与 Scheduler 的关系
5.1 依赖关系
python
# Scheduler 依赖于 Optimizer
optimizer = optim.Adam(model.parameters(), lr=0.001)
scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=10, gamma=0.1)
# ↑ 必须传入 optimizer
# Scheduler 保存了对 Optimizer 的引用
print(scheduler.optimizer is optimizer) # True
5.2 协作流程
python
# 完整训练循环
for epoch in range(num_epochs):
# 1. 训练阶段
for batch in train_loader:
optimizer.zero_grad() # 清零梯度
output = model(batch)
loss = criterion(output, target)
loss.backward() # 计算梯度
optimizer.step() # 更新参数(使用当前 lr)
# 2. 验证阶段
val_loss = validate(model, val_loader)
# 3. 学习率调度
scheduler.step() # 调整学习率(for next epoch)
# 4. 日志记录
current_lr = optimizer.param_groups[0]['lr']
print(f"Epoch {epoch}, LR: {current_lr}, Val Loss: {val_loss}")
5.3 数据流向
训练迭代中的数据流:
Epoch N:
Train: optimizer.step() with lr=0.01
Val: compute val_loss
Update: scheduler.step() → lr 变为 0.001
Epoch N+1:
Train: optimizer.step() with lr=0.001 ← 使用新学习率
...
5.4 关键原理
Scheduler 如何修改学习率:
python
# 查看 PyTorch 源码(简化版)
class StepLR:
def __init__(self, optimizer, step_size, gamma=0.1):
self.optimizer = optimizer
self.step_size = step_size
self.gamma = gamma
self.last_epoch = 0
# 保存初始学习率
self.base_lrs = [group['lr'] for group in optimizer.param_groups]
def step(self):
self.last_epoch += 1
# 计算新学习率
if self.last_epoch % self.step_size == 0:
for i, param_group in enumerate(self.optimizer.param_groups):
# 直接修改 optimizer 的 lr
param_group['lr'] = self.base_lrs[i] * (self.gamma ** (self.last_epoch // self.step_size))
6. 常用 Scheduler 详解
6.1 StepLR - 阶梯式衰减
原理:每隔固定 epoch 数,学习率乘以 gamma。
python
from torch.optim.lr_scheduler import StepLR
optimizer = optim.SGD(model.parameters(), lr=0.1)
scheduler = StepLR(
optimizer,
step_size=10, # 每 10 个 epoch 降低一次
gamma=0.1 # 每次降为原来的 0.1 倍
)
# 学习率变化:
# Epoch 0-9: lr = 0.1
# Epoch 10-19: lr = 0.01
# Epoch 20-29: lr = 0.001
# Epoch 30+: lr = 0.0001
可视化:
lr
0.1 │████████████
│
0.01│ ████████████
│
0.001 │ ████████████
└────────────┴────────────┴────────────> Epoch
10 20 30
适用场景:
- 训练周期较长的任务
- 需要明确的学习率下降节点
6.2 MultiStepLR - 多阶梯衰减
原理:在指定的 epoch 降低学习率。
python
from torch.optim.lr_scheduler import MultiStepLR
scheduler = MultiStepLR(
optimizer,
milestones=[30, 60, 90], # 在这些 epoch 降低
gamma=0.1
)
# 学习率变化:
# Epoch 0-29: lr = 0.1
# Epoch 30-59: lr = 0.01
# Epoch 60-89: lr = 0.001
# Epoch 90+: lr = 0.0001
适用场景:
- 复现论文配置(如 ResNet 训练)
- 需要灵活控制降低时机
6.3 ExponentialLR - 指数衰减
原理:每个 epoch 学习率乘以 gamma。
python
from torch.optim.lr_scheduler import ExponentialLR
scheduler = ExponentialLR(optimizer, gamma=0.95)
# 学习率变化:
# Epoch 0: lr = 0.1
# Epoch 1: lr = 0.1 × 0.95 = 0.095
# Epoch 2: lr = 0.095 × 0.95 = 0.09025
# ...
# Epoch n: lr = 0.1 × (0.95^n)
可视化:
lr
0.1 │╲
│ ╲___
│ ╲___
│ ╲___
0.0 └─────────────────> Epoch
适用场景:
- 需要平滑衰减
- 长期训练任务
6.4 CosineAnnealingLR - 余弦退火
原理:学习率按余弦函数变化。
python
from torch.optim.lr_scheduler import CosineAnnealingLR
scheduler = CosineAnnealingLR(
optimizer,
T_max=100, # 周期长度(epoch 数)
eta_min=1e-6 # 最小学习率
)
# 学习率公式:
# lr(epoch) = eta_min + (lr_initial - eta_min) * (1 + cos(π × epoch / T_max)) / 2
可视化:
lr
0.1 │╲ ╱
│ ╲ ╱
│ ╲ ╱
│ ╲ ╱
0.0 └────▼─────────> Epoch
T_max
适用场景:
- Transformer 训练(BERT、GPT)
- 需要在训练末期探索更好的局部最优
- 周期性任务
优势:
- 平滑过渡
- 后期缓慢下降利于收敛
6.5 ReduceLROnPlateau - 基于指标自适应
原理:当监控指标停滞时降低学习率。
python
from torch.optim.lr_scheduler import ReduceLROnPlateau
scheduler = ReduceLROnPlateau(
optimizer,
mode='min', # 'min' 或 'max'
factor=0.1, # 降低倍数
patience=10, # 容忍多少 epoch 不改善
threshold=1e-4, # 改善的最小阈值
verbose=True
)
# 使用方式不同:需要传入指标
for epoch in range(num_epochs):
train(...)
val_loss = validate(...)
scheduler.step(val_loss) # ← 传入验证指标
工作逻辑:
Epoch 1-10: val_loss 持续下降 → lr 保持 0.1
Epoch 11-20: val_loss 停滞不降 → 等待(patience)
Epoch 21: 超过 patience → lr 降为 0.01
...
适用场景:
- 不确定何时降低学习率
- 基于验证集性能自适应
- 调试阶段
6.6 OneCycleLR - 单周期策略
原理:学习率先增后减,配合动量变化。
python
from torch.optim.lr_scheduler import OneCycleLR
scheduler = OneCycleLR(
optimizer,
max_lr=0.1, # 最大学习率
total_steps=len(train_loader) * num_epochs, # 总步数
pct_start=0.3, # 增长阶段占比(30%)
anneal_strategy='cos', # 'cos' 或 'linear'
div_factor=25, # 初始 lr = max_lr / div_factor
final_div_factor=1e4 # 最终 lr = max_lr / final_div_factor
)
# 在每个 batch 后调用
for epoch in range(num_epochs):
for batch in train_loader:
optimizer.zero_grad()
loss = ...
loss.backward()
optimizer.step()
scheduler.step() # ← 每个 step 调用
学习率曲线:
lr
│ ╱╲
0.1 │ ╱ ╲
│ ╱ ╲
│ ╱ ╲___
│ ╱ ╲___
│ ╱ ╲___
0.0 └────────────────────────> Steps
增长 峰值 衰减
30% 点 70%
适用场景:
- 快速收敛(fastai 推荐)
- 训练周期较短的任务
- 配合较大 batch size
6.7 CyclicLR - 周期性学习率
原理:学习率在 base_lr 和 max_lr 之间周期性变化。
python
from torch.optim.lr_scheduler import CyclicLR
scheduler = CyclicLR(
optimizer,
base_lr=0.001,
max_lr=0.1,
step_size_up=2000, # 上升阶段步数
mode='triangular', # 'triangular', 'triangular2', 'exp_range'
)
# 每个 batch 后调用
for batch in train_loader:
...
scheduler.step()
模式对比:
triangular (三角波):
╱╲ ╱╲ ╱╲
╱ ╲╱ ╲╱ ╲
triangular2 (递减三角):
╱╲ ╱╲ ╱
╱ ╲╱ ▼
exp_range (指数):
╱╲ ╱╲╱
╱ ▼
适用场景:
- 避免陷入局部最优
- 探索不同学习率区间
6.8 Scheduler 对比表
| Scheduler | 调用时机 | 主要参数 | 适用场景 | 学习率变化 |
|---|---|---|---|---|
| StepLR | epoch 结束 | step_size, gamma | 简单任务 | 阶梯式 |
| MultiStepLR | epoch 结束 | milestones, gamma | 论文复现 | 多阶梯 |
| ExponentialLR | epoch 结束 | gamma | 长期训练 | 指数衰减 |
| CosineAnnealingLR | epoch 结束 | T_max, eta_min | Transformer | 余弦曲线 |
| ReduceLROnPlateau | validation 后 | patience, factor | 自适应场景 | 基于指标 |
| OneCycleLR | 每个 batch | max_lr, total_steps | 快速收敛 | 单周期 |
| CyclicLR | 每个 batch | base_lr, max_lr | 探索最优 | 周期性 |
7. 训练循环集成
7.1 基础训练循环(基于 Epoch 的 Scheduler)
python
import torch
import torch.nn as nn
import torch.optim as optim
from torch.optim.lr_scheduler import CosineAnnealingLR
# 1. 准备模型、优化器、调度器
model = MyModel()
optimizer = optim.Adam(model.parameters(), lr=0.001)
scheduler = CosineAnnealingLR(optimizer, T_max=100)
criterion = nn.CrossEntropyLoss()
# 2. 训练循环
num_epochs = 100
for epoch in range(num_epochs):
# ========== 训练阶段 ==========
model.train()
train_loss = 0.0
for batch_idx, (data, target) in enumerate(train_loader):
# 前向传播
output = model(data)
loss = criterion(output, target)
# 反向传播
optimizer.zero_grad()
loss.backward()
optimizer.step()
train_loss += loss.item()
avg_train_loss = train_loss / len(train_loader)
# ========== 验证阶段 ==========
model.eval()
val_loss = 0.0
with torch.no_grad():
for data, target in val_loader:
output = model(data)
loss = criterion(output, target)
val_loss += loss.item()
avg_val_loss = val_loss / len(val_loader)
# ========== 学习率调度 ==========
scheduler.step() # 调整学习率
# ========== 日志记录 ==========
current_lr = optimizer.param_groups[0]['lr']
print(f"Epoch {epoch+1}/{num_epochs}")
print(f" Train Loss: {avg_train_loss:.4f}")
print(f" Val Loss: {avg_val_loss:.4f}")
print(f" Learning Rate: {current_lr:.6f}")
7.2 使用 ReduceLROnPlateau
python
from torch.optim.lr_scheduler import ReduceLROnPlateau
optimizer = optim.Adam(model.parameters(), lr=0.001)
scheduler = ReduceLROnPlateau(
optimizer,
mode='min',
factor=0.5,
patience=5,
verbose=True
)
for epoch in range(num_epochs):
# 训练
train_loss = train_epoch(model, train_loader, optimizer, criterion)
# 验证
val_loss = validate(model, val_loader, criterion)
# 学习率调度(传入验证指标)
scheduler.step(val_loss) # ← 关键:传入 metric
# 日志
current_lr = optimizer.param_groups[0]['lr']
print(f"Epoch {epoch}, Val Loss: {val_loss:.4f}, LR: {current_lr:.6f}")
7.3 使用 OneCycleLR(基于 Step)
python
from torch.optim.lr_scheduler import OneCycleLR
# 计算总步数
total_steps = len(train_loader) * num_epochs
optimizer = optim.SGD(model.parameters(), lr=0.1, momentum=0.9)
scheduler = OneCycleLR(
optimizer,
max_lr=0.1,
total_steps=total_steps,
pct_start=0.3
)
for epoch in range(num_epochs):
model.train()
for batch_idx, (data, target) in enumerate(train_loader):
optimizer.zero_grad()
output = model(data)
loss = criterion(output, target)
loss.backward()
optimizer.step()
# 每个 batch 后调用
scheduler.step() # ← 关键:每步调用
# 可选:打印当前学习率
if batch_idx % 100 == 0:
current_lr = optimizer.param_groups[0]['lr']
print(f"Epoch {epoch}, Batch {batch_idx}, LR: {current_lr:.6f}")
7.4 链式 Scheduler(组合多个策略)
python
from torch.optim.lr_scheduler import SequentialLR, LinearLR, CosineAnnealingLR
# 前 10 个 epoch 线性增长(warmup),之后余弦退火
scheduler1 = LinearLR(optimizer, start_factor=0.1, total_iters=10)
scheduler2 = CosineAnnealingLR(optimizer, T_max=90)
scheduler = SequentialLR(
optimizer,
schedulers=[scheduler1, scheduler2],
milestones=[10] # 在 epoch 10 切换
)
for epoch in range(100):
train(...)
scheduler.step()
7.5 完整示例:ResNet 训练配置
python
import torch
import torch.nn as nn
import torch.optim as optim
from torch.optim.lr_scheduler import MultiStepLR
from torchvision.models import resnet50
# 1. 模型和数据
model = resnet50(pretrained=False, num_classes=10)
train_loader = ... # 你的数据加载器
val_loader = ...
# 2. 优化器配置(ImageNet 标准)
optimizer = optim.SGD(
model.parameters(),
lr=0.1,
momentum=0.9,
weight_decay=1e-4
)
# 3. 学习率调度器(ImageNet 标准)
scheduler = MultiStepLR(
optimizer,
milestones=[30, 60, 90],
gamma=0.1
)
# 4. 损失函数
criterion = nn.CrossEntropyLoss()
# 5. 训练循环
num_epochs = 100
best_acc = 0.0
for epoch in range(num_epochs):
# 训练
model.train()
for data, target in train_loader:
optimizer.zero_grad()
output = model(data)
loss = criterion(output, target)
loss.backward()
optimizer.step()
# 验证
model.eval()
correct = 0
total = 0
with torch.no_grad():
for data, target in val_loader:
output = model(data)
_, predicted = output.max(1)
total += target.size(0)
correct += predicted.eq(target).sum().item()
acc = 100. * correct / total
# 学习率调度
scheduler.step()
# 保存最佳模型
if acc > best_acc:
best_acc = acc
torch.save(model.state_dict(), 'best_model.pth')
# 日志
current_lr = optimizer.param_groups[0]['lr']
print(f"Epoch {epoch+1}/{num_epochs}")
print(f" Accuracy: {acc:.2f}%")
print(f" Best Accuracy: {best_acc:.2f}%")
print(f" Learning Rate: {current_lr}")
8. PyTorch Lightning 集成
8.1 基础配置方法
在 PyTorch Lightning 中,通过 configure_optimizers() 方法返回 Optimizer 和 Scheduler。
python
import pytorch_lightning as pl
import torch.optim as optim
from torch.optim.lr_scheduler import CosineAnnealingLR
class MyLightningModel(pl.LightningModule):
def __init__(self, lr=0.001):
super().__init__()
self.lr = lr
self.model = ... # 你的模型
def configure_optimizers(self):
# 方式1:仅返回 optimizer
optimizer = optim.Adam(self.parameters(), lr=self.lr)
return optimizer
# 其他方法:training_step, validation_step, etc.
8.2 配置 Optimizer + Scheduler
python
class MyLightningModel(pl.LightningModule):
def configure_optimizers(self):
optimizer = optim.Adam(self.parameters(), lr=0.001)
scheduler = CosineAnnealingLR(optimizer, T_max=100)
# 返回字典格式
return {
'optimizer': optimizer,
'lr_scheduler': {
'scheduler': scheduler,
'interval': 'epoch', # 'epoch' 或 'step'
'frequency': 1, # 每隔多少 interval 调用一次
'monitor': 'val_loss', # 用于 ReduceLROnPlateau
}
}
8.3 完整配置选项
python
def configure_optimizers(self):
optimizer = optim.SGD(self.parameters(), lr=0.1, momentum=0.9)
scheduler = MultiStepLR(optimizer, milestones=[30, 60], gamma=0.1)
return {
'optimizer': optimizer,
'lr_scheduler': {
'scheduler': scheduler,
# 调用时机
'interval': 'epoch', # 'epoch' 或 'step'
'frequency': 1, # 每 N 个 interval 调用一次
# 用于 ReduceLROnPlateau
'monitor': 'val_loss', # 监控的指标
'strict': True, # 如果 monitor 不存在是否报错
# 其他
'name': 'my_scheduler', # TensorBoard 中显示的名称
}
}
8.4 使用 ReduceLROnPlateau
python
from torch.optim.lr_scheduler import ReduceLROnPlateau
class MyModel(pl.LightningModule):
def configure_optimizers(self):
optimizer = optim.Adam(self.parameters(), lr=0.001)
scheduler = ReduceLROnPlateau(
optimizer,
mode='min',
factor=0.5,
patience=10
)
return {
'optimizer': optimizer,
'lr_scheduler': {
'scheduler': scheduler,
'monitor': 'val_loss', # 关键:必须指定 monitor
'interval': 'epoch',
}
}
def validation_step(self, batch, batch_idx):
# 确保记录了 val_loss
loss = ...
self.log('val_loss', loss) # ← 必须记录
return loss
8.5 使用 OneCycleLR
python
from torch.optim.lr_scheduler import OneCycleLR
class MyModel(pl.LightningModule):
def __init__(self, total_steps):
super().__init__()
self.total_steps = total_steps
def configure_optimizers(self):
optimizer = optim.SGD(self.parameters(), lr=0.1, momentum=0.9)
scheduler = OneCycleLR(
optimizer,
max_lr=0.1,
total_steps=self.total_steps,
)
return {
'optimizer': optimizer,
'lr_scheduler': {
'scheduler': scheduler,
'interval': 'step', # 关键:每个 step 调用
}
}
# 使用时计算 total_steps
total_steps = len(train_loader) * num_epochs
model = MyModel(total_steps=total_steps)
trainer = pl.Trainer(max_epochs=num_epochs)
8.6 多个 Optimizer 和 Scheduler
python
class MyGAN(pl.LightningModule):
def configure_optimizers(self):
# 生成器优化器
opt_g = optim.Adam(self.generator.parameters(), lr=0.0002)
# 判别器优化器
opt_d = optim.Adam(self.discriminator.parameters(), lr=0.0002)
# 生成器调度器
sch_g = CosineAnnealingLR(opt_g, T_max=100)
# 判别器调度器
sch_d = CosineAnnealingLR(opt_d, T_max=100)
return [opt_g, opt_d], [sch_g, sch_d]
8.7 自动记录学习率
PyTorch Lightning 会自动记录学习率到日志(需要配合 LearningRateMonitor):
python
from pytorch_lightning.callbacks import LearningRateMonitor
# 创建 Callback
lr_monitor = LearningRateMonitor(logging_interval='epoch')
# 训练
trainer = pl.Trainer(
max_epochs=100,
callbacks=[lr_monitor]
)
trainer.fit(model)
# 学习率会自动记录到 TensorBoard/WandB
8.8 完整 Lightning 示例
python
import pytorch_lightning as pl
import torch
import torch.nn as nn
import torch.optim as optim
from torch.optim.lr_scheduler import CosineAnnealingLR
from pytorch_lightning.callbacks import ModelCheckpoint, LearningRateMonitor
class MyClassifier(pl.LightningModule):
def __init__(self, input_dim, num_classes, lr=0.001):
super().__init__()
self.save_hyperparameters()
self.model = nn.Sequential(
nn.Linear(input_dim, 128),
nn.ReLU(),
nn.Linear(128, num_classes)
)
self.criterion = nn.CrossEntropyLoss()
def forward(self, x):
return self.model(x)
def training_step(self, batch, batch_idx):
x, y = batch
y_hat = self(x)
loss = self.criterion(y_hat, y)
# 记录指标
self.log('train_loss', loss, on_step=False, on_epoch=True)
return loss
def validation_step(self, batch, batch_idx):
x, y = batch
y_hat = self(x)
loss = self.criterion(y_hat, y)
acc = (y_hat.argmax(dim=1) == y).float().mean()
# 记录指标
self.log('val_loss', loss, prog_bar=True)
self.log('val_acc', acc, prog_bar=True)
def configure_optimizers(self):
optimizer = optim.Adam(self.parameters(), lr=self.hparams.lr)
scheduler = CosineAnnealingLR(optimizer, T_max=100)
return {
'optimizer': optimizer,
'lr_scheduler': {
'scheduler': scheduler,
'interval': 'epoch',
}
}
# 使用
model = MyClassifier(input_dim=784, num_classes=10, lr=0.001)
trainer = pl.Trainer(
max_epochs=100,
callbacks=[
ModelCheckpoint(monitor='val_loss', mode='min'),
LearningRateMonitor(logging_interval='epoch'),
],
)
trainer.fit(model, train_loader, val_loader)
9. 高级技巧与策略
9.1 Warmup 策略
概念:训练初期使用较小的学习率,逐步增加到目标值,避免初期梯度过大导致不稳定。
方法 1:使用 LinearLR + SequentialLR
python
from torch.optim.lr_scheduler import LinearLR, CosineAnnealingLR, SequentialLR
optimizer = optim.Adam(model.parameters(), lr=0.001)
# Warmup: 前 5 个 epoch 从 0.0001 线性增长到 0.001
warmup_scheduler = LinearLR(
optimizer,
start_factor=0.1, # 初始 lr = 0.001 × 0.1 = 0.0001
total_iters=5 # 5 个 epoch
)
# 主调度器
main_scheduler = CosineAnnealingLR(optimizer, T_max=95)
# 组合
scheduler = SequentialLR(
optimizer,
schedulers=[warmup_scheduler, main_scheduler],
milestones=[5] # 在 epoch 5 切换
)
# 使用
for epoch in range(100):
train(...)
scheduler.step()
方法 2:自定义 Warmup Scheduler
python
class WarmupScheduler:
"""带 Warmup 的调度器"""
def __init__(self, optimizer, warmup_epochs, base_scheduler):
self.optimizer = optimizer
self.warmup_epochs = warmup_epochs
self.base_scheduler = base_scheduler
self.current_epoch = 0
# 保存初始学习率
self.base_lrs = [group['lr'] for group in optimizer.param_groups]
def step(self):
if self.current_epoch < self.warmup_epochs:
# Warmup 阶段:线性增长
warmup_factor = (self.current_epoch + 1) / self.warmup_epochs
for i, param_group in enumerate(self.optimizer.param_groups):
param_group['lr'] = self.base_lrs[i] * warmup_factor
else:
# 主调度阶段
self.base_scheduler.step()
self.current_epoch += 1
# 使用
optimizer = optim.Adam(model.parameters(), lr=0.001)
base_scheduler = CosineAnnealingLR(optimizer, T_max=95)
scheduler = WarmupScheduler(optimizer, warmup_epochs=5, base_scheduler=base_scheduler)
for epoch in range(100):
train(...)
scheduler.step()
PyTorch Lightning 中的 Warmup
python
from torch.optim.lr_scheduler import LambdaLR
class MyModel(pl.LightningModule):
def configure_optimizers(self):
optimizer = optim.Adam(self.parameters(), lr=0.001)
# 使用 LambdaLR 实现 Warmup + Cosine
def lr_lambda(epoch):
warmup_epochs = 5
max_epochs = 100
if epoch < warmup_epochs:
# Warmup
return (epoch + 1) / warmup_epochs
else:
# Cosine Annealing
progress = (epoch - warmup_epochs) / (max_epochs - warmup_epochs)
return 0.5 * (1 + math.cos(math.pi * progress))
scheduler = LambdaLR(optimizer, lr_lambda)
return {'optimizer': optimizer, 'lr_scheduler': scheduler}
9.2 不同层不同学习率
概念:对模型不同部分使用不同的学习率(常用于迁移学习)。
python
# 场景:微调预训练模型
import torchvision.models as models
model = models.resnet50(pretrained=True)
# 冻结早期层,仅微调后期层
for param in model.layer1.parameters():
param.requires_grad = False
for param in model.layer2.parameters():
param.requires_grad = False
# 不同层使用不同学习率
optimizer = optim.SGD([
{'params': model.layer3.parameters(), 'lr': 1e-4}, # 小学习率
{'params': model.layer4.parameters(), 'lr': 1e-3}, # 中等学习率
{'params': model.fc.parameters(), 'lr': 1e-2} # 大学习率
], momentum=0.9)
# Scheduler 会同时调整所有参数组的学习率
scheduler = StepLR(optimizer, step_size=10, gamma=0.1)
# 查看各组学习率
for i, param_group in enumerate(optimizer.param_groups):
print(f"参数组 {i} 学习率: {param_group['lr']}")
PyTorch Lightning 中的实现:
python
class FineTuneModel(pl.LightningModule):
def __init__(self):
super().__init__()
self.backbone = models.resnet50(pretrained=True)
self.classifier = nn.Linear(2048, 10)
def configure_optimizers(self):
# 不同部分使用不同学习率
optimizer = optim.Adam([
{'params': self.backbone.parameters(), 'lr': 1e-4},
{'params': self.classifier.parameters(), 'lr': 1e-3}
])
scheduler = CosineAnnealingLR(optimizer, T_max=100)
return {'optimizer': optimizer, 'lr_scheduler': scheduler}
9.3 梯度累积与学习率
当使用梯度累积时,可能需要调整学习率:
python
# 等效 batch size = batch_size × accumulate_grad_batches
# 建议按比例调整学习率
accumulate_grad_batches = 4
base_lr = 0.001
# 调整学习率(线性缩放规则)
adjusted_lr = base_lr * accumulate_grad_batches
optimizer = optim.Adam(model.parameters(), lr=adjusted_lr)
在 PyTorch Lightning 中:
python
trainer = pl.Trainer(
accumulate_grad_batches=4, # 每 4 个 batch 更新一次
)
# 模型中相应调整学习率
class MyModel(pl.LightningModule):
def configure_optimizers(self):
# 考虑梯度累积的学习率
accumulate = self.trainer.accumulate_grad_batches
adjusted_lr = self.hparams.lr * accumulate
optimizer = optim.Adam(self.parameters(), lr=adjusted_lr)
# ...
9.4 学习率查找器(LR Finder)
概念:通过逐步增加学习率找到最优初始值。
python
from torch_lr_finder import LRFinder
# 创建模型和优化器
model = MyModel()
optimizer = optim.Adam(model.parameters(), lr=1e-7)
criterion = nn.CrossEntropyLoss()
# LR Finder
lr_finder = LRFinder(model, optimizer, criterion)
lr_finder.range_test(train_loader, start_lr=1e-7, end_lr=10, num_iter=100)
# 绘制曲线
lr_finder.plot() # 查看 loss vs lr 曲线
# 选择最优学习率(通常选择下降最快的点)
best_lr = lr_finder.history['lr'][lr_finder.history['loss'].index(min(lr_finder.history['loss']))]
print(f"建议学习率: {best_lr}")
# 重置模型
lr_finder.reset()
在 PyTorch Lightning 中:
python
from pytorch_lightning.tuner import Tuner
model = MyModel()
trainer = pl.Trainer()
# 自动查找学习率
tuner = Tuner(trainer)
lr_finder = tuner.lr_find(model, train_loader)
# 查看结果
fig = lr_finder.plot(suggest=True)
fig.show()
# 更新模型学习率
model.hparams.lr = lr_finder.suggestion()
9.5 周期性重启(Cosine Annealing with Warm Restarts)
python
from torch.optim.lr_scheduler import CosineAnnealingWarmRestarts
optimizer = optim.SGD(model.parameters(), lr=0.1)
scheduler = CosineAnnealingWarmRestarts(
optimizer,
T_0=10, # 第一个周期的长度
T_mult=2, # 每次重启后周期长度乘以的倍数
eta_min=1e-6 # 最小学习率
)
# 学习率变化:
# Epoch 0-9: 第一个周期(余弦下降)
# Epoch 10-29: 第二个周期(长度 × 2 = 20)
# Epoch 30-69: 第三个周期(长度 × 2 = 40)
学习率曲线:
lr
│╲ ╱╲ ╱╲
0.1 │ ╲ ╱ ╲ ╱ ╲
│ ╲╱ ╲ ╱ ╲
│ ▼ ╲ ╱ ╲___
│ ╲╱
0.0 └────────────────────────────> Epoch
10 30 70
↑ ↑ ↑
重启 重启 重启
10. 常见问题与最佳实践
10.1 常见错误
错误 1:Scheduler 调用时机错误
python
# ❌ 错误:在 optimizer.step() 之前调用
optimizer.step()
scheduler.step() # 正确位置
scheduler.step() # ❌ 错误位置
optimizer.step()
# ✅ 正确顺序
for epoch in range(num_epochs):
for batch in train_loader:
optimizer.zero_grad()
loss.backward()
optimizer.step() # 先更新参数
scheduler.step() # 再调整学习率
错误 2:忘记调用 scheduler.step()
python
# ❌ 错误:创建了 Scheduler 但忘记调用
scheduler = StepLR(optimizer, step_size=10)
for epoch in range(num_epochs):
train(...)
# 忘记调用 scheduler.step()
# 结果:学习率始终不变
错误 3:ReduceLROnPlateau 没有传入 metric
python
# ❌ 错误
scheduler = ReduceLROnPlateau(optimizer, mode='min')
scheduler.step() # 缺少参数
# ✅ 正确
val_loss = validate(...)
scheduler.step(val_loss)
错误 4:OneCycleLR 使用错误的调用频率
python
# ❌ 错误:OneCycleLR 应该每个 batch 调用,而非每个 epoch
scheduler = OneCycleLR(optimizer, max_lr=0.1, total_steps=total_steps)
for epoch in range(num_epochs):
train(...)
scheduler.step() # ❌ 错误:应该在 batch 循环内
# ✅ 正确
for epoch in range(num_epochs):
for batch in train_loader:
...
optimizer.step()
scheduler.step() # ✅ 每个 batch 调用
错误 5:修改学习率后没有更新 Scheduler
python
# ❌ 错误:手动修改学习率会导致 Scheduler 失效
optimizer.param_groups[0]['lr'] = 0.0001 # 手动修改
scheduler.step() # Scheduler 基于旧的 base_lr 计算
# ✅ 正确:重新创建 Scheduler
optimizer.param_groups[0]['lr'] = 0.0001
scheduler = StepLR(optimizer, step_size=10)
10.2 调试技巧
技巧 1:打印学习率变化
python
class LRLogger:
"""记录学习率变化"""
def __init__(self, optimizer):
self.optimizer = optimizer
self.lr_history = []
def log(self, epoch):
lr = self.optimizer.param_groups[0]['lr']
self.lr_history.append(lr)
print(f"Epoch {epoch}, LR: {lr:.6f}")
def plot(self):
import matplotlib.pyplot as plt
plt.plot(self.lr_history)
plt.xlabel('Epoch')
plt.ylabel('Learning Rate')
plt.title('Learning Rate Schedule')
plt.show()
# 使用
logger = LRLogger(optimizer)
for epoch in range(num_epochs):
train(...)
scheduler.step()
logger.log(epoch)
logger.plot()
技巧 2:验证 Scheduler 配置
python
def verify_scheduler(optimizer, scheduler, num_epochs=20):
"""验证 Scheduler 行为"""
print("验证学习率调度:")
print("-" * 50)
for epoch in range(num_epochs):
lr = optimizer.param_groups[0]['lr']
print(f"Epoch {epoch:3d}: LR = {lr:.6f}")
scheduler.step()
# 使用
optimizer = optim.SGD(model.parameters(), lr=0.1)
scheduler = StepLR(optimizer, step_size=5, gamma=0.1)
verify_scheduler(optimizer, scheduler)
技巧 3:使用 TensorBoard 可视化
python
from torch.utils.tensorboard import SummaryWriter
writer = SummaryWriter('runs/lr_schedule')
for epoch in range(num_epochs):
train(...)
# 记录学习率
lr = optimizer.param_groups[0]['lr']
writer.add_scalar('Learning Rate', lr, epoch)
scheduler.step()
writer.close()
10.3 最佳实践
✅ 推荐做法
1. 明确调用时机
python
# 基于 Epoch 的 Scheduler
for epoch in range(num_epochs):
train_one_epoch(...)
validate(...)
scheduler.step() # epoch 结束后
# 基于 Step 的 Scheduler
for epoch in range(num_epochs):
for batch in train_loader:
optimizer.step()
scheduler.step() # 每个 batch 后
2. 记录学习率到日志
python
# PyTorch Lightning 自动记录
from pytorch_lightning.callbacks import LearningRateMonitor
trainer = pl.Trainer(callbacks=[LearningRateMonitor()])
# 手动记录
self.log('learning_rate', optimizer.param_groups[0]['lr'])
3. 保存和恢复 Scheduler 状态
python
# 保存
checkpoint = {
'model': model.state_dict(),
'optimizer': optimizer.state_dict(),
'scheduler': scheduler.state_dict(), # 保存 Scheduler 状态
'epoch': epoch,
}
torch.save(checkpoint, 'checkpoint.pth')
# 加载
checkpoint = torch.load('checkpoint.pth')
model.load_state_dict(checkpoint['model'])
optimizer.load_state_dict(checkpoint['optimizer'])
scheduler.load_state_dict(checkpoint['scheduler'])
4. 使用 Warmup
python
# 大模型训练建议使用 Warmup
warmup_scheduler = LinearLR(optimizer, start_factor=0.1, total_iters=5)
main_scheduler = CosineAnnealingLR(optimizer, T_max=95)
scheduler = SequentialLR(optimizer, [warmup_scheduler, main_scheduler], milestones=[5])
5. 文档化配置
python
# 在代码中明确注释 Scheduler 配置
optimizer = optim.Adam(model.parameters(), lr=0.001)
scheduler = CosineAnnealingLR(
optimizer,
T_max=100, # 训练 100 个 epoch
eta_min=1e-6 # 最小学习率
)
# 学习率从 0.001 余弦退火到 1e-6
❌ 避免的做法
1. 混用不兼容的 Scheduler
python
# ❌ 错误:同时使用多个 Scheduler(未使用 ChainedScheduler)
scheduler1 = StepLR(optimizer, step_size=10)
scheduler2 = ExponentialLR(optimizer, gamma=0.9)
for epoch in range(num_epochs):
scheduler1.step()
scheduler2.step() # 会相互干扰
2. 在错误的地方修改学习率
python
# ❌ 错误:在训练循环内手动修改
for epoch in range(num_epochs):
for batch in train_loader:
optimizer.param_groups[0]['lr'] *= 0.99 # 每个 batch 都修改
3. 忽略 Scheduler 的返回值
python
# 某些 Scheduler 可能返回有用信息
# 虽然大多数情况下可以忽略,但了解返回值有助于调试
10.4 性能优化
技巧 1:避免频繁创建 Scheduler
python
# ❌ 低效
for epoch in range(num_epochs):
scheduler = StepLR(optimizer, step_size=10) # 每次都创建
scheduler.step()
# ✅ 高效
scheduler = StepLR(optimizer, step_size=10) # 创建一次
for epoch in range(num_epochs):
scheduler.step()
技巧 2:使用 Scheduler 的 state_dict 减少内存
python
# 如果只保存检查点,不需要完整 Scheduler 对象
scheduler_state = scheduler.state_dict() # 仅状态字典
10.5 常见场景推荐
| 场景 | 推荐 Scheduler | 配置建议 |
|---|---|---|
| CV 分类(ResNet 风格) | MultiStepLR | milestones=[30, 60, 90], gamma=0.1 |
| Transformer 训练 | CosineAnnealingLR + Warmup | T_max=max_epochs, warmup=5-10% |
| 迁移学习/微调 | ReduceLROnPlateau | patience=5-10, factor=0.5 |
| 快速原型开发 | OneCycleLR | max_lr=1e-3, pct_start=0.3 |
| 长期训练(>100 epochs) | CosineAnnealingWarmRestarts | T_0=10, T_mult=2 |
| 调试阶段 | StepLR | step_size=5, gamma=0.1(简单可控) |
11. 扩展阅读与进阶方向
11.1 官方文档
-
PyTorch Optimizer 文档 :
-
PyTorch LR Scheduler 文档 :
https://pytorch.org/docs/stable/optim.html#how-to-adjust-learning-rate
-
PyTorch Lightning Optimizer 文档 :
https://lightning.ai/docs/pytorch/stable/common/optimization.html
11.2 经典论文
学习率调度相关论文
-
Cyclical Learning Rates for Training Neural Networks
- 作者: Leslie N. Smith
- 介绍: CyclicLR 的原理和应用
-
SGDR: Stochastic Gradient Descent with Warm Restarts
- 作者: Ilya Loshchilov, Frank Hutter
- 介绍: CosineAnnealingWarmRestarts
-
Super-Convergence: Very Fast Training of Neural Networks Using Large Learning Rates
- 作者: Leslie N. Smith, Nicholay Topin
- 介绍: OneCycleLR(1cycle policy)
-
Decoupled Weight Decay Regularization
- 作者: Ilya Loshchilov, Frank Hutter
- 介绍: AdamW 优化器
11.3 高级主题
11.3.1 自定义 Scheduler
python
from torch.optim.lr_scheduler import _LRScheduler
class CustomScheduler(_LRScheduler):
"""自定义学习率调度器"""
def __init__(self, optimizer, max_epochs, last_epoch=-1):
self.max_epochs = max_epochs
super().__init__(optimizer, last_epoch)
def get_lr(self):
"""计算当前学习率"""
# 自定义调度逻辑
progress = self.last_epoch / self.max_epochs
# 示例:平方根衰减
factor = (1 - progress) ** 0.5
return [base_lr * factor for base_lr in self.base_lrs]
# 使用
optimizer = optim.Adam(model.parameters(), lr=0.001)
scheduler = CustomScheduler(optimizer, max_epochs=100)
11.3.2 集成外部库
使用 transformers 的 Scheduler:
python
from transformers import get_cosine_schedule_with_warmup
optimizer = optim.AdamW(model.parameters(), lr=5e-5)
scheduler = get_cosine_schedule_with_warmup(
optimizer,
num_warmup_steps=500,
num_training_steps=10000
)
使用 timm 的 Scheduler:
python
from timm.scheduler import CosineLRScheduler
scheduler = CosineLRScheduler(
optimizer,
t_initial=100,
warmup_t=5,
warmup_lr_init=1e-6,
)
11.4 实战项目推荐
- 复现 ImageNet 训练:使用标准的 MultiStepLR 配置
- 训练 BERT/GPT:实现 Warmup + Cosine Annealing
- 对比实验:在同一任务上对比不同 Scheduler 的效果
- 超参数搜索:使用 Optuna/Ray Tune 自动搜索最优 Scheduler 配置
11.5 相关工具
- TensorBoard: 可视化学习率曲线
- WandB: 实验追踪,对比不同 Scheduler
- Optuna: 超参数优化,包括 Scheduler 参数
- Ray Tune: 分布式超参数搜索
📌 总结
核心要点回顾
-
Optimizer 与 Scheduler 的关系:
- Optimizer 负责更新参数
- Scheduler 负责调整 Optimizer 的学习率
- Scheduler 通过修改
optimizer.param_groups[i]['lr']实现
-
关键概念:
- param_groups: Optimizer 的参数组,存储学习率等超参数
- 调用时机: 基于 epoch/step/metric 的不同调度策略
- Warmup: 训练初期逐步增加学习率的策略
-
常用 Scheduler:
- StepLR/MultiStepLR: 阶梯式衰减(CV 常用)
- CosineAnnealingLR: 余弦退火(Transformer 常用)
- ReduceLROnPlateau: 自适应调整(调试友好)
- OneCycleLR: 快速收敛(fastai 推荐)
-
PyTorch Lightning 集成:
- 通过
configure_optimizers()返回配置 - 使用
LearningRateMonitor自动记录
- 通过
-
最佳实践:
- 明确调用时机(epoch vs step)
- 使用 Warmup 提升稳定性
- 记录学习率到日志
- 保存 Scheduler 状态到检查点
Optimizer + Scheduler 快速参考
基础模板:
python
optimizer = optim.Adam(model.parameters(), lr=0.001)
scheduler = CosineAnnealingLR(optimizer, T_max=100)
for epoch in range(num_epochs):
train_one_epoch(model, train_loader, optimizer)
validate(model, val_loader)
scheduler.step()
PyTorch Lightning 模板:
python
def configure_optimizers(self):
optimizer = optim.Adam(self.parameters(), lr=0.001)
scheduler = CosineAnnealingLR(optimizer, T_max=100)
return {'optimizer': optimizer, 'lr_scheduler': scheduler}
常用配置:
python
# CV 任务
optimizer = optim.SGD(model.parameters(), lr=0.1, momentum=0.9)
scheduler = MultiStepLR(optimizer, milestones=[30, 60, 90], gamma=0.1)
# Transformer
optimizer = optim.AdamW(model.parameters(), lr=5e-5)
scheduler = CosineAnnealingLR(optimizer, T_max=100)
# 快速收敛
optimizer = optim.SGD(model.parameters(), lr=0.1, momentum=0.9)
scheduler = OneCycleLR(optimizer, max_lr=0.1, total_steps=total_steps)