本节你会学到
- 说清楚「学习率决定什么:衰减策略与训练曲线」解决的核心问题
- 知道它在「PyTorch 深度学习基础(43~58)」中的位置
- 把 DataLoader、Linear 层、Tensor 这些关键词联系起来
学习率是训练里最敏感的超参数——太大 loss 爆炸,太小 loss 不动。这篇讲清怎么判断学习率过大过小,以及 StepLR、CosineAnnealing 等衰减策略怎么选。
学习路线:PyTorch 深度学习基础(43~58) · 第 55 课
在前 20 篇 PyTorch 入门上继续深化,补齐张量操作、自动微分、优化器家族和训练排错能力。
学完本阶段你能做到:独立搭建一个 PyTorch 训练脚本,正确处理 Shape、device、梯度清空、优化器选择和学习率策略,并能根据 loss/accuracy 曲线诊断训练问题。
推荐读法:当前更新主线,建议跟更;每篇都要配合代码自检。
查看完整阶段 · 16 篇先看目标,再带着问题读正文。读完后用练习确认自己真的理解了。
先检查模型参数、输入 Tensor、标签 Tensor 是否在同一个设备上。常见修正是把它们统一 `.to(device)`。
因为训练是否正确经常取决于 shape、dtype、device 和梯度流,API 名字只能告诉你工具,不会保证数据契约正确。
`输入数据 → forward → loss → backward → optimizer.step()`,并知道本文主题位于这条链路的哪一环。
你换了优化器、加了正则化、调了 batch_size,loss 还是不理想。最后发现是学习率没调对——太大,loss 在天上飞;太小,loss 趴着不动。
学习率是训练里最敏感的旋钮。这篇讲清怎么判断它大了还是小了,以及训练中后期的衰减策略。
第 52-54 篇我们讲了 SGD、AdaGrad、RMSProp、Adam 四种优化器,每个都有 lr 参数。第 12 篇梯度下降公式 $\theta = \theta - \alpha \nabla L$ 里的 $\alpha$ 就是学习率。今天我们专门讲这个 $\alpha$ 怎么设、怎么变。
学习率控制每次参数更新的步子大小。前期大步快走,后期小步精修——这就是学习率衰减策略的核心逻辑。
看 loss 曲线是最直接的方法。
| 症状 | 可能原因 | 处理 |
|---|---|---|
| loss 剧烈震荡 | 学习率太大 | 降 10 倍 |
| loss 变 NaN | 学习率太大 | 降 10 倍 + 梯度裁剪 |
| loss 先降后升 | 学习率偏大 | 降 2-5 倍 |
| loss 几乎不动 | 学习率太小 | 升 10 倍 |
| loss 平稳下降 | 合适 | 保持 |
| 优化器 | 入门起点 | 常用范围 |
|---|---|---|
| SGD | 0.01 | 0.001 ~ 0.1 |
| Adam | 0.001 | 0.0001 ~ 0.01 |
| AdamW(大模型微调) | 0.00002 | 0.00001 ~ 0.0001 |
# 太大
optimizer = torch.optim.Adam(model.parameters(), lr=0.1) # loss 爆炸
# 合适
optimizer = torch.optim.Adam(model.parameters(), lr=0.001) # loss 平稳下降
# 太小
optimizer = torch.optim.Adam(model.parameters(), lr=0.00001) # loss 不动
训练分两个阶段:
如果全程用大学习率,后期会在最优解附近来回震荡,无法精确收敛。如果全程用小学习率,前期太慢。所以需要衰减——前期大、后期小。
每隔固定 epoch,学习率乘以一个系数。
from torch.optim.lr_scheduler import StepLR
optimizer = torch.optim.SGD(model.parameters(), lr=0.1)
scheduler = StepLR(optimizer, step_size=10, gamma=0.5)
# 每 10 个 epoch,lr = lr * 0.5
for epoch in range(30):
train(...)
scheduler.step() # 每个 epoch 结束后调用
| 参数 | 含义 |
|---|---|
step_size |
每隔多少 epoch 衰减一次 |
gamma |
衰减系数(0.5 表示减半) |
在指定的 epoch 处衰减。
from torch.optim.lr_scheduler import MultiStepLR
scheduler = MultiStepLR(optimizer, milestones=[50, 130, 200], gamma=0.1)
# 在第 50、130、200 个 epoch 各乘以 0.1
适合“我知道大概在哪些节点该降”的场景。
每个 epoch 都衰减一点点。
from torch.optim.lr_scheduler import ExponentialLR
scheduler = ExponentialLR(optimizer, gamma=0.99)
# 每个 epoch: lr = lr * 0.99
衰减最平滑,但下降较快。
按余弦曲线平滑下降,前期慢、中期快、后期又慢。
from torch.optim.lr_scheduler import CosineAnnealingLR
scheduler = CosineAnnealingLR(optimizer, T_max=50, eta_min=0)
# T_max 是周期长度,eta_min 是最小学习率
推荐:CosineAnnealingLR 是现代深度学习最常用的衰减策略,效果通常比 StepLR 好。
import torch
import torch.nn as nn
from torch.optim.lr_scheduler import CosineAnnealingLR
model = nn.Sequential(nn.Linear(100, 50), nn.ReLU(), nn.Linear(50, 10))
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
scheduler = CosineAnnealingLR(optimizer, T_max=50)
for epoch in range(50):
model.train()
for x, y in train_loader:
optimizer.zero_grad()
loss = criterion(model(x), y)
loss.backward()
optimizer.step()
scheduler.step() # ⚠️ 放在 epoch 循环里,不是 batch 循环里
# 打印当前学习率
current_lr = optimizer.param_groups[0]['lr']
print(f"Epoch {epoch+1}, lr={current_lr:.6f}, loss={loss.item():.4f}")
关键:scheduler.step() 放在每个 epoch 训练结束后,不是每个 batch 后。
大模型训练(如 BERT、GPT)常用 warmup:前几个 epoch 学习率从 0 线性升到目标值,再开始衰减。
为什么?大模型初期参数没稳定,直接用大学习率会破坏初始化。先小步热身,再大步走。
from torch.optim.lr_scheduler import LinearLR, SequentialLR
# Warmup + Cosine 组合
warmup = LinearLR(optimizer, start_factor=0.1, total_iters=5)
cosine = CosineAnnealingLR(optimizer, T_max=45)
scheduler = SequentialLR(optimizer, [warmup, cosine], milestones=[5])
入门项目不需要 warmup,但知道大模型训练有这回事。
# ❌ 放在 batch 循环里,学习率衰减太快
for epoch in range(50):
for x, y in train_loader:
...
optimizer.step()
scheduler.step() # 每个 batch 都调!
# ✅ 放在 epoch 循环里
for epoch in range(50):
for x, y in train_loader:
...
optimizer.step()
scheduler.step() # 每个 epoch 调一次
盲目调 lr 没意义。训练时记录 loss,画出来看趋势。
losses = []
for epoch in range(50):
for x, y in train_loader:
...
losses.append(loss.item())
import matplotlib.pyplot as plt
plt.plot(losses)
plt.xlabel('step')
plt.ylabel('loss')
plt.show()
前 10 个 epoch loss 还在快速下降,就开始衰减学习率,导致训练提前停滞。一般在前期 loss 平台期再开始衰减。
练习 1:StepLR(step_size=10, gamma=0.1),初始 lr=0.1,第 25 个 epoch 时学习率是多少?
练习 2:训练时 loss 先正常下降,到 epoch 30 突然变成 NaN,最可能是什么原因?怎么排查?
练习 3:写一个训练配置:Adam 优化器,lr=0.001,前 5 个 epoch warmup,之后用余弦退火到 0,总共 50 个 epoch。
练习 1:第 10、20 个 epoch 分别衰减一次。第 25 个 epoch 时已经衰减了 2 次:0.1 → 0.01 → 0.001。学习率是 0.001。
练习 2:最可能是学习率偏大,到训练中期梯度累积导致参数飞走。排查:① 检查 loss 曲线看是不是突然飙升。② 加梯度裁剪 clip_grad_norm_。③ 降低学习率重试。④ 检查数据有没有异常值(如除以 0)。
练习 3:
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
warmup = LinearLR(optimizer, start_factor=0.01, total_iters=5)
cosine = CosineAnnealingLR(optimizer, T_max=45, eta_min=0)
scheduler = SequentialLR(optimizer, [warmup, cosine], milestones=[5])
scheduler.step() 放在 epoch 循环里,不是 batch 循环里下一篇解决训练的另一个基础设施——GPU 训练时模型和数据怎么放到同一设备。
留言讨论