
文章目录
-
- [📖 课前导读](#📖 课前导读)
- 一、知识原理:学习率调度的数学直觉
-
- [1.1 为什么需要学习率衰减?](#1.1 为什么需要学习率衰减?)
- [1.2 学习率调度的常见模式](#1.2 学习率调度的常见模式)
- [1.3 学习率预热(Warmup)的原理](#1.3 学习率预热(Warmup)的原理)
- 二、环境搭建与准备
- 三、代码实战:PyTorch内置学习率调度器
-
- [3.1 StepLR:等间隔阶梯衰减](#3.1 StepLR:等间隔阶梯衰减)
- [3.2 MultiStepLR:自定义衰减里程碑](#3.2 MultiStepLR:自定义衰减里程碑)
- [3.3 ExponentialLR:指数衰减](#3.3 ExponentialLR:指数衰减)
- [3.4 CosineAnnealingLR:余弦退火](#3.4 CosineAnnealingLR:余弦退火)
- [3.5 CosineAnnealingWarmRestarts:带热重启的余弦退火](#3.5 CosineAnnealingWarmRestarts:带热重启的余弦退火)
- [3.6 ReduceLROnPlateau:基于验证指标的自适应调整](#3.6 ReduceLROnPlateau:基于验证指标的自适应调整)
- [3.7 学习率预热调度器](#3.7 学习率预热调度器)
- [3.8 组合调度器:SequentialLR 和 ChainedScheduler](#3.8 组合调度器:SequentialLR 和 ChainedScheduler)
- [3.9 自定义LambdaLR](#3.9 自定义LambdaLR)
- [3.10 从检查点恢复训练时的调度器状态](#3.10 从检查点恢复训练时的调度器状态)
- 四、实战案例:不同调度器在图像分类上的效果对比
- 五、学习率调度器与优化器的集成模式
-
- [5.1 标准训练循环模板](#5.1 标准训练循环模板)
- [5.2 注意事项:scheduler.step()的位置](#5.2 注意事项:scheduler.step()的位置)
- 六、可视化不同调度器的学习率曲线
- 七、高级技巧与最佳实践
-
- [7.1 学习率预热在Transformer中的应用](#7.1 学习率预热在Transformer中的应用)
- [7.2 使用`get_lr()`调试调度器](#7.2 使用
get_lr()调试调度器) - [7.3 为不同的参数组设置不同的调度策略](#7.3 为不同的参数组设置不同的调度策略)
- [7.4 学习率与Batch Size的关系](#7.4 学习率与Batch Size的关系)
- [7.5 何时不使用学习率调度器?](#7.5 何时不使用学习率调度器?)
- 八、难点解析:常见问题与陷阱
-
- [8.1 忘记了`optimizer.step()`和`scheduler.step()`的先后顺序](#8.1 忘记了
optimizer.step()和scheduler.step()的先后顺序) - [8.2 调度器状态没有随checkpoint保存](#8.2 调度器状态没有随checkpoint保存)
- [8.3 使用`ReduceLROnPlateau`时传入错误的指标模式](#8.3 使用
ReduceLROnPlateau时传入错误的指标模式) - [8.4 学习率下降过猛导致训练停滞](#8.4 学习率下降过猛导致训练停滞)
- [8.5 热身阶段过长导致前期浪费](#8.5 热身阶段过长导致前期浪费)
- [8.1 忘记了`optimizer.step()`和`scheduler.step()`的先后顺序](#8.1 忘记了
- 九、课后总结
- 十、课后作业
- 十一、下一课预告
- [🔗《精讲25课|PyTorch 从入门到精通》系列课程导航](#🔗《精讲25课|PyTorch 从入门到精通》系列课程导航)
📖 课前导读
为什么固定的学习率不是最优解?
在第9课中我们学过,学习率决定了参数更新的步长。如果使用固定学习率:
- 设置太大:初期可能快速下降,但后期会在最优值附近剧烈震荡,无法精确收敛。
- 设置太小:初期进展极其缓慢,浪费大量时间,且可能陷入局部极小点难以跳出。
理想的学习率变化曲线应该是:前期较大 ,快速接近最优区域;中期逐渐减小 ,开始精细探索;后期很小,在最优值附近微调。就像开车上高速:先猛踩油门加速到接近限速(大学习率),然后松开油门微调速度(中等学习率),最后遇到收费站轻轻点刹(小学习率)。
PyTorch的torch.optim.lr_scheduler提供了丰富的学习率调度策略,让你可以像设置导航一样规划学习率的"行程"。本课将带你掌握所有主流调度器,并学会为不同场景选择最合适的策略。
💡 核心认知:学习率调度器和优化器是"搭档"关系。优化器负责根据当前学习率更新参数,调度器负责在训练过程中修改优化器的学习率。两者配合得好,1+1>2。
学完这一课,你将能够:
- ✅ 理解学习率衰减的必要性和常见策略
- ✅ 使用PyTorch内置的StepLR、MultiStepLR、ExponentialLR
- ✅ 掌握余弦退火(CosineAnnealingLR)及其带重启的变体
- ✅ 实现学习率预热(Warmup)------大模型训练的标配
- ✅ 使用ReduceLROnPlateau根据验证集损失自适应调整
- ✅ 自定义学习率调度器(lambda函数或继承
_LRScheduler) - ✅ 可视化不同调度策略的学习率曲线
- ✅ 将调度器与优化器正确集成到训练循环中
一、知识原理:学习率调度的数学直觉
1.1 为什么需要学习率衰减?
从最优化理论的角度,使用固定学习率的SGD在强凸函数下可以收敛,但收敛速度受限于学习率的选择。如果学习率太大,最终的收敛半径(与最优解的距离)无法缩小;如果学习率太小,收敛速度线性变慢。因此,需要在训练过程中逐渐减小学习率,以同时获得快速的初始下降和精确的最终收敛。
1.2 学习率调度的常见模式
| 模式 | 数学形式 | 特点 |
|---|---|---|
| 阶梯衰减 | 每经过一定步数,学习率乘以γ | 简单有效,需要预设下降点 |
| 指数衰减 | lr = lr0 * γ^(epoch) |
平滑下降,下降速度先快后慢 |
| 多项式衰减 | lr = lr0 * (1 - t/T)^p |
从lr0降到0,p控制曲线形状 |
| 余弦退火 | lr = lr_min + 0.5*(lr0-lr_min)*(1+cos(π*t/T)) |
平滑下降,末期下降慢 |
| 带重启的余弦 | 周期性地重置学习率 | 跳出局部极小点 |
| 自适应衰减 | 当验证损失不再下降时,降低学习率 | 不需要预设步数 |
1.3 学习率预热(Warmup)的原理
在训练初期,模型参数是随机初始化的,梯度估计可能不稳定,直接用较大的学习率可能导致梯度爆炸。预热策略在前若干个epoch(或step)内,让学习率从0线性增长到预设值,帮助模型平稳启动。这对于Transformer、BERT等大模型训练至关重要。
二、环境搭建与准备
python
import torch
import torch.nn as nn
import torch.optim as optim
from torch.optim.lr_scheduler import (StepLR, MultiStepLR, ExponentialLR,
CosineAnnealingLR, CosineAnnealingWarmRestarts,
ReduceLROnPlateau, LinearLR, ConstantLR,
SequentialLR, ChainedScheduler, LambdaLR)
import matplotlib.pyplot as plt
import numpy as np
print(f"PyTorch版本: {torch.__version__}")
# 定义一个简单的模型用于演示(不需要真实数据)
model = nn.Linear(10, 2)
三、代码实战:PyTorch内置学习率调度器
3.1 StepLR:等间隔阶梯衰减
StepLR每经过step_size个epoch,学习率乘以gamma。
python
# 创建优化器
optimizer = optim.SGD(model.parameters(), lr=0.1)
# 调度器:每10个epoch,学习率乘以0.1
scheduler = StepLR(optimizer, step_size=10, gamma=0.1)
# 模拟训练20个epoch,记录学习率变化
lrs = []
for epoch in range(20):
lrs.append(optimizer.param_groups[0]['lr'])
# 训练一个epoch...(省略实际训练)
scheduler.step() # 每个epoch后调用
# 可视化
plt.plot(range(1, 21), lrs, marker='o')
plt.xlabel('Epoch')
plt.ylabel('Learning Rate')
plt.title('StepLR (step_size=10, gamma=0.1)')
plt.grid(True)
plt.show()
参数说明:
step_size:每隔多少个epoch调整一次gamma:衰减因子last_epoch:最后一个epoch索引,用于恢复训练(默认为-1)
3.2 MultiStepLR:自定义衰减里程碑
与StepLR类似,但你可以指定在哪些epoch进行衰减。
python
optimizer = optim.SGD(model.parameters(), lr=0.1)
scheduler = MultiStepLR(optimizer, milestones=[30, 60, 80], gamma=0.1)
# 在第30、60、80个epoch时,学习率乘以0.1
3.3 ExponentialLR:指数衰减
每个epoch学习率乘以gamma,呈指数级下降:lr = lr0 * gamma^epoch
python
optimizer = optim.SGD(model.parameters(), lr=0.1)
scheduler = ExponentialLR(optimizer, gamma=0.95) # 每个epoch下降5%
适用场景:需要平滑连续下降的任务,但注意指数衰减可能导致后期学习率过小。
3.4 CosineAnnealingLR:余弦退火
学习率按照余弦函数从初始值下降到eta_min(默认为0)。
公式:lr = eta_min + (lr0 - eta_min) * (1 + cos(π * epoch / T_max)) / 2
python
optimizer = optim.SGD(model.parameters(), lr=0.1)
scheduler = CosineAnnealingLR(optimizer, T_max=50, eta_min=0)
# T_max: 从lr0降到eta_min所需的epoch数(半个周期)
可视化:学习率在50个epoch内平滑地从0.1降到0,呈现余弦曲线。
3.5 CosineAnnealingWarmRestarts:带热重启的余弦退火
这是CosineAnnealing的增强版:每隔T_0个epoch,学习率重置为初始值(热重启),然后继续余弦下降。这有助于跳出局部极小点,常用于深度网络的长时间训练。
python
optimizer = optim.SGD(model.parameters(), lr=0.1)
scheduler = CosineAnnealingWarmRestarts(optimizer, T_0=20, T_mult=2, eta_min=1e-5)
# T_0: 第一次重启前的epoch数
# T_mult: 每次重启后周期翻倍(20 -> 40 -> 80 ...)
# eta_min: 最小学习率
python
# 模拟100个epoch
lrs = []
optimizer = optim.SGD(model.parameters(), lr=0.1)
scheduler = CosineAnnealingWarmRestarts(optimizer, T_0=20, T_mult=2)
for epoch in range(100):
lrs.append(optimizer.param_groups[0]['lr'])
scheduler.step()
plt.plot(range(1, 101), lrs)
plt.xlabel('Epoch')
plt.ylabel('Learning Rate')
plt.title('CosineAnnealingWarmRestarts (T_0=20, T_mult=2)')
plt.show()
3.6 ReduceLROnPlateau:基于验证指标的自适应调整
前面的调度器都是基于epoch的固定计划。ReduceLROnPlateau则根据某个指标(如验证损失)是否停止改善来动态降低学习率。这是一种"反馈式"调度。
python
optimizer = optim.SGD(model.parameters(), lr=0.1)
scheduler = ReduceLROnPlateau(optimizer, mode='min', factor=0.1, patience=5,
threshold=1e-4, min_lr=1e-6)
# 在训练循环中,每个epoch结束后传入验证损失
for epoch in range(100):
val_loss = ... # 计算验证损失
scheduler.step(val_loss) # 根据val_loss决定是否降低学习率
关键参数:
mode:'min'(指标下降时降低LR)或'max'(指标上升时降低LR)factor:每次降低的倍数(新LR = LR * factor)patience:容忍多少个epoch指标没有改善threshold:最小改善阈值min_lr:学习率下限
适用场景:验证集性能是唯一关注点,不需要预设衰减计划(如Kaggle比赛)。
3.7 学习率预热调度器
PyTorch 1.10+ 提供了专门的预热调度器。
LinearLR:线性预热
python
# 在前5个epoch内,学习率从0线性增加到0.1
warmup_scheduler = LinearLR(optimizer, start_factor=0.01, end_factor=1.0, total_iters=5)
# start_factor=0.01 表示起始学习率为初始lr的0.01倍
ConstantLR:常数预热
python
# 前5个epoch保持一个较小的学习率(初始lr的0.01倍),然后恢复到原lr
constant_scheduler = ConstantLR(optimizer, factor=0.01, total_iters=5)
3.8 组合调度器:SequentialLR 和 ChainedScheduler
有时候你需要先预热,再阶梯衰减,或者先余弦退火,再恒定。可以使用SequentialLR顺序执行多个调度器,或者ChainedScheduler串联它们(每个step调用所有调度器)。
python
# 顺序执行:先LinearLR预热5个epoch,再CosineAnnealingLR
optimizer = optim.SGD(model.parameters(), lr=0.1)
scheduler1 = LinearLR(optimizer, start_factor=0.01, end_factor=1.0, total_iters=5)
scheduler2 = CosineAnnealingLR(optimizer, T_max=45, eta_min=1e-5)
# 在5个epoch后切换到scheduler2
scheduler = SequentialLR(optimizer, schedulers=[scheduler1, scheduler2], milestones=[5])
# 链式:每个step同时更新所有调度器(通常用于多个调度器不冲突的情况)
# 例如:指数衰减 + 预热
# scheduler = ChainedScheduler([warmup_scheduler, exp_scheduler])
3.9 自定义LambdaLR
如果内置调度器都不满足需求,可以使用LambdaLR,通过lambda函数定义学习率与epoch的关系。
python
# 例1:多项式衰减 lr = lr0 * (1 - epoch/100)^2
lambda_func = lambda epoch: (1 - epoch/100) ** 2
scheduler = LambdaLR(optimizer, lr_lambda=lambda_func)
# 例2:分段函数(epoch<30时线性增长,30~80线性下降,80后恒定)
def custom_lambda(epoch):
if epoch < 30:
return epoch / 30 # 0 -> 1
elif epoch < 80:
return 1 - (epoch - 30) / 50 # 1 -> 0
else:
return 0.01 # 保持小学习率
scheduler = LambdaLR(optimizer, lr_lambda=custom_lambda)
# 例3:周期振荡(模拟SGDR)
lambda_osc = lambda epoch: 0.5 * (1 + np.cos(np.pi * (epoch % 20) / 20))
scheduler = LambdaLR(optimizer, lr_lambda=lambda_osc)
3.10 从检查点恢复训练时的调度器状态
当你保存模型checkpoint并想恢复训练时,必须同时保存和恢复调度器的状态(尤其是那些有内部状态的调度器,如CosineAnnealingWarmRestarts)。
python
# 保存
checkpoint = {
'epoch': epoch,
'model_state_dict': model.state_dict(),
'optimizer_state_dict': optimizer.state_dict(),
'scheduler_state_dict': scheduler.state_dict(),
}
torch.save(checkpoint, 'checkpoint.pth')
# 恢复
checkpoint = torch.load('checkpoint.pth')
model.load_state_dict(checkpoint['model_state_dict'])
optimizer.load_state_dict(checkpoint['optimizer_state_dict'])
scheduler.load_state_dict(checkpoint['scheduler_state_dict'])
epoch = checkpoint['epoch']
四、实战案例:不同调度器在图像分类上的效果对比
我们使用CIFAR-10和一个简单的CNN(不训练真实网络,只模拟学习率变化,但为了展示效果,可以训练少量epoch)。这里重点展示调度器的配置和集成。
python
import torch
import torchvision
import torchvision.transforms as transforms
from torch.utils.data import DataLoader
from torchvision.models import resnet18
# 数据准备
transform = transforms.Compose([transforms.ToTensor(), transforms.Normalize((0.5,0.5,0.5), (0.5,0.5,0.5))])
train_set = torchvision.datasets.CIFAR10(root='./data', train=True, download=True, transform=transform)
train_loader = DataLoader(train_set, batch_size=64, shuffle=True)
# 模型
model = resnet18(num_classes=10) # 使用ResNet18演示
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model.to(device)
# 定义训练函数,传入调度器,并记录学习率变化
def train_with_scheduler(scheduler, epochs=30, lr=0.1):
optimizer = optim.SGD(model.parameters(), lr=lr, momentum=0.9, weight_decay=5e-4)
criterion = nn.CrossEntropyLoss()
model.train()
lr_history = []
for epoch in range(epochs):
# 记录当前学习率
current_lr = optimizer.param_groups[0]['lr']
lr_history.append(current_lr)
# 一个epoch的训练
running_loss = 0.0
for inputs, labels in train_loader:
inputs, labels = inputs.to(device), labels.to(device)
optimizer.zero_grad()
outputs = model(inputs)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
running_loss += loss.item()
print(f"Epoch {epoch+1}: lr={current_lr:.6f}, loss={running_loss/len(train_loader):.4f}")
# 更新学习率
if isinstance(scheduler, ReduceLROnPlateau):
# 对于ReduceLROnPlateau,需要传入验证损失(这里简化用训练损失)
scheduler.step(running_loss)
else:
scheduler.step()
return lr_history
# 测试不同调度器(为了节省时间,只演示配置,不实际运行)
# 但下面给出代码模板,你可以自行运行比较
# 1. StepLR
optimizer = optim.SGD(model.parameters(), lr=0.1)
scheduler = StepLR(optimizer, step_size=10, gamma=0.1)
# lrs_step = train_with_scheduler(scheduler, epochs=30)
# 2. MultiStepLR
# scheduler = MultiStepLR(optimizer, milestones=[15, 25], gamma=0.1)
# 3. CosineAnnealingLR
# scheduler = CosineAnnealingLR(optimizer, T_max=30)
# 4. CosineAnnealingWarmRestarts
# scheduler = CosineAnnealingWarmRestarts(optimizer, T_0=10, T_mult=2)
# 5. ReduceLROnPlateau
# scheduler = ReduceLROnPlateau(optimizer, mode='min', factor=0.5, patience=5)
五、学习率调度器与优化器的集成模式
5.1 标准训练循环模板
python
def train_loop(model, train_loader, val_loader, optimizer, scheduler, epochs, device):
criterion = nn.CrossEntropyLoss()
best_acc = 0.0
for epoch in range(epochs):
# 训练
model.train()
train_loss = 0.0
for inputs, labels in train_loader:
inputs, labels = inputs.to(device), labels.to(device)
optimizer.zero_grad()
outputs = model(inputs)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
train_loss += loss.item()
# 验证
model.eval()
correct = 0
total = 0
with torch.no_grad():
for inputs, labels in val_loader:
inputs, labels = inputs.to(device), labels.to(device)
outputs = model(inputs)
_, predicted = torch.max(outputs, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
val_acc = correct / total
# 调度器更新
if isinstance(scheduler, ReduceLROnPlateau):
scheduler.step(val_acc if scheduler.mode == 'max' else train_loss)
else:
scheduler.step()
print(f"Epoch {epoch+1}: lr={optimizer.param_groups[0]['lr']:.6f}, val_acc={val_acc:.4f}")
5.2 注意事项:scheduler.step()的位置
- 基于epoch的调度器 (StepLR、CosineAnnealing等):通常在每个epoch结束后调用
scheduler.step() - 基于step的调度器(自定义或某些场景):可以在每个batch后调用,但要注意性能开销
- ReduceLROnPlateau:必须在知道指标值之后调用,通常也是epoch结束后
六、可视化不同调度器的学习率曲线
python
def plot_scheduler_lr(scheduler_class, kwargs, epochs=100, lr0=0.1, **sched_kwargs):
model = nn.Linear(10,2)
optimizer = optim.SGD(model.parameters(), lr=lr0)
scheduler = scheduler_class(optimizer, **kwargs)
lrs = []
for epoch in range(epochs):
lrs.append(optimizer.param_groups[0]['lr'])
scheduler.step()
plt.plot(range(1, epochs+1), lrs, label=scheduler_class.__name__)
return lrs
plt.figure(figsize=(12, 8))
# StepLR
plot_scheduler_lr(StepLR, {'step_size': 20, 'gamma': 0.3}, epochs=100)
# MultiStepLR
plot_scheduler_lr(MultiStepLR, {'milestones': [30, 60, 80], 'gamma': 0.2}, epochs=100)
# ExponentialLR
plot_scheduler_lr(ExponentialLR, {'gamma': 0.97}, epochs=100)
# CosineAnnealingLR
plot_scheduler_lr(CosineAnnealingLR, {'T_max': 50}, epochs=100)
# CosineAnnealingWarmRestarts
plot_scheduler_lr(CosineAnnealingWarmRestarts, {'T_0': 20, 'T_mult': 2}, epochs=100)
plt.xlabel('Epoch')
plt.ylabel('Learning Rate')
plt.title('不同学习率调度器对比 (初始lr=0.1)')
plt.legend()
plt.grid(True)
plt.ylim(0, 0.12)
plt.show()
观察结论:
- StepLR:阶梯式下降,有明显平台
- MultiStepLR:在指定点下降
- ExponentialLR:平滑指数下降,后期极低
- CosineAnnealingLR:余弦形平滑下降
- CosineAnnealingWarmRestarts:周期性重启,学习率多次"跃升"
七、高级技巧与最佳实践
7.1 学习率预热在Transformer中的应用
在训练BERT、GPT等大模型时,标准的做法是:前若干步(通常为总步数的10%或固定几千步)学习率线性增加,之后线性衰减或余弦衰减。
python
# 计算总训练步数
total_steps = len(train_loader) * num_epochs
warmup_steps = int(0.1 * total_steps) # 预热10%
# 使用LambdaLR实现warmup + 余弦衰减
def lr_lambda(step):
if step < warmup_steps:
return step / warmup_steps # 从0线性增加到1
else:
# 余弦衰减到0
progress = (step - warmup_steps) / (total_steps - warmup_steps)
return 0.5 * (1 + np.cos(np.pi * progress))
scheduler = LambdaLR(optimizer, lr_lambda=lr_lambda)
7.2 使用get_lr()调试调度器
调度器提供了get_lr()方法,可以查看下一次step后将会设定的学习率。
python
# 在step之前
print(scheduler.get_lr())
scheduler.step()
7.3 为不同的参数组设置不同的调度策略
如第9课所述,优化器可以有多个参数组。每个参数组可以独立设置学习率,也可以独立应用调度器(但需要使用LambdaLR的lr_lambda为每个组分别定义)。
python
optimizer = optim.SGD([
{'params': model.fc1.parameters(), 'lr': 0.01},
{'params': model.fc2.parameters(), 'lr': 0.001}
], lr=0.1) # 默认lr会被组内lr覆盖
# 为每个组定义不同的lambda函数
lambda1 = lambda epoch: 0.95 ** epoch
lambda2 = lambda epoch: 0.99 ** epoch
scheduler = LambdaLR(optimizer, lr_lambda=[lambda1, lambda2])
7.4 学习率与Batch Size的关系
当增加Batch Size时,可以相应增加学习率(线性缩放规则)。例如,Batch Size从256增加到512,学习率可从0.1增加到0.2。这一规则对SGD和AdamW都近似适用。
7.5 何时不使用学习率调度器?
- 小模型、小数据集、短时间训练:固定学习率可能就足够了。
- 使用Adam等自适应优化器时,调度器的收益可能不如SGD明显,但仍然有效(尤其对Transformer)。
- 使用1-cycle调度(如
OneCycleLR)时,它内部已经包含了学习率调度,不需要额外调度器。
八、难点解析:常见问题与陷阱
8.1 忘记了optimizer.step()和scheduler.step()的先后顺序
一般顺序:
python
optimizer.zero_grad()
loss.backward()
optimizer.step()
# 如果是基于step的调度器,在这里调用scheduler.step()
# 如果是基于epoch的调度器,在epoch结束后调用
如果基于epoch的调度器放在了optimizer.step()之前,当前epoch的学习率会使用下一epoch的值,导致学习率提前一个epoch下降。
8.2 调度器状态没有随checkpoint保存
恢复训练时如果不加载调度器状态,学习率会从初始值开始,破坏训练进度。
8.3 使用ReduceLROnPlateau时传入错误的指标模式
如果任务是准确率(越高越好),应设置mode='max';如果是损失(越低越好),设置mode='min'。传反了会导致学习率在指标改善时反而降低。
8.4 学习率下降过猛导致训练停滞
当学习率衰减因子gamma太小时(如0.1),学习率可能跳跃式下降,造成损失突然上升。可以尝试更平滑的衰减(如0.9)或使用余弦退火。
8.5 热身阶段过长导致前期浪费
预热步数通常为总步数的5%-10%。预热过长会使模型在低学习率下徘徊太久,影响收敛速度。
九、课后总结
调度器速查表
| 调度器 | 特点 | 关键参数 | 适用场景 |
|---|---|---|---|
| StepLR | 等间隔阶梯下降 | step_size, gamma | 经典CNN训练 |
| MultiStepLR | 自定义衰减点 | milestones, gamma | 已知损失下降点 |
| ExponentialLR | 指数平滑下降 | gamma | 需要连续变化 |
| CosineAnnealingLR | 余弦曲线下降 | T_max, eta_min | 快速收敛、SGD |
| CosineAnnealingWarmRestarts | 带重启的余弦 | T_0, T_mult | 跳出局部极小,长时间训练 |
| ReduceLROnPlateau | 自适应,基于指标 | mode, patience, factor | 验证集性能是关键 |
| LinearLR | 线性预热 | start_factor, total_iters | 大模型训练开头 |
| LambdaLR | 任意自定义函数 | lr_lambda | 任何特殊需求 |
| OneCycleLR | 1周期调度(上升+下降) | max_lr, total_steps | 训练时间固定,追求极速收敛 |
决策树
是否需要基于验证集性能动态调整?
├── 是 → ReduceLROnPlateau
└── 否 → 是否需要预热(Warmup)?
├── 是 → 结合LinearLR + 主调度器(如CosineAnnealingLR)
└── 否 → 是否有已知的损失下降点?
├── 是 → MultiStepLR
├── 否 → 训练时间是否固定?
├── 是 → CosineAnnealingLR / OneCycleLR
└── 否 → StepLR 或 ExponentialLR
检查清单
- 理解不同调度器的基本行为
- 能根据任务选择调度器(CV用StepLR/Cosine,NLP大模型用warmup+余弦衰减)
- 知道如何将调度器集成到训练循环中
- 会保存和恢复调度器状态
- 能使用
ReduceLROnPlateau并正确设置mode和patience - 能够通过
LambdaLR实现自定义调度策略 - 学习了学习率可视化的方法,用于调试
十、课后作业
作业1:调度器可视化
使用本课中的可视化代码,绘制以下调度器在200个epoch内的学习率变化曲线(初始lr=0.1):
- StepLR(step_size=40, gamma=0.5)
- MultiStepLR(milestones=50, 100, 150, gamma=0.3)
- ExponentialLR(gamma=0.98)
- CosineAnnealingLR(T_max=80)
- CosineAnnealingWarmRestarts(T_0=30, T_mult=1)
作业2:调度器对收敛性能的影响
在CIFAR-10上训练一个简单的CNN(如ResNet-18简化版),比较以下三种调度器下的验证准确率(训练50个epoch):
- 固定学习率0.01
- StepLR(step_size=20, gamma=0.1)
- CosineAnnealingLR(T_max=50)
记录每个epoch的验证准确率,画出曲线并分析。
作业3:实现自定义调度器
编写一个调度器,使得学习率按照lr = lr0 / (1 + decay_rate * epoch)的规律衰减。分别使用LambdaLR和继承_LRScheduler两种方式实现。
作业4:预热+余弦衰减的组合
实现一个warmup+余弦衰减的调度器:前5个epoch学习率从0线性增加到0.1,之后余弦衰减到0。使用SequentialLR或LambdaLR实现,并画出学习率曲线(总共50个epoch)。
作业5:学习率范围测试
编写一个函数,在训练开始前执行学习率范围测试(LR Range Test):从一个极小的学习率(如1e-7)开始,每个batch线性增加学习率直到一个很大的值(如1),记录每个batch的损失。然后画出学习率-损失曲线,找出损失下降最陡峭的区域对应的学习率作为初始学习率。提示:可以修改第9课的lr_range_test函数。
十一、下一课预告
第11课我们将进行第一个完整实战项目:手写数字识别。我们将整合前面10课的所有知识:
- 数据加载(Dataset、DataLoader)
- 网络定义(nn.Module)
- 损失函数、优化器、学习率调度器
- 完整的训练循环、验证评估、模型保存与加载
学完第11课,你将拥有自己的第一个可运行的PyTorch训练脚本,并理解深度学习项目的全流程。
附录:PyTorch学习率调度器API完整列表
| 调度器 | 初始化示例 |
|---|---|
StepLR |
StepLR(optimizer, step_size=10, gamma=0.1) |
MultiStepLR |
MultiStepLR(optimizer, milestones=[20,40], gamma=0.1) |
ExponentialLR |
ExponentialLR(optimizer, gamma=0.95) |
CosineAnnealingLR |
CosineAnnealingLR(optimizer, T_max=50) |
CosineAnnealingWarmRestarts |
CosineAnnealingWarmRestarts(optimizer, T_0=10, T_mult=2) |
ReduceLROnPlateau |
ReduceLROnPlateau(optimizer, mode='min', factor=0.1, patience=5) |
LinearLR |
LinearLR(optimizer, start_factor=0.01, total_iters=5) |
ConstantLR |
ConstantLR(optimizer, factor=0.01, total_iters=5) |
LambdaLR |
LambdaLR(optimizer, lr_lambda=lambda epoch: 0.95**epoch) |
OneCycleLR |
OneCycleLR(optimizer, max_lr=0.01, total_steps=1000) |
CyclicLR |
CyclicLR(optimizer, base_lr=0.001, max_lr=0.01, step_size_up=2000) |
🔗《精讲25课|PyTorch 从入门到精通》系列课程导航
🌟 感谢您耐心阅读到这里!
💡 如果本文对您有所启发欢迎:
👍 点赞📌 收藏 📤 分享给更多需要的伙伴。
🗣️ 期待在评论区看到您的想法, 共同进步。
🔔 关注我,持续获取更多干货内容~
🤗 我们下篇文章见~