PyTorch 学习率实战:从零理解衰减策略与调度器

PyTorch 学习率实战:从零理解衰减策略与调度器

副标题:手把手带你排掉"学习率定死"的坑------用下山类比讲透过大过小的判断,StepLR / 余弦退火 / Warmup 怎么选,以及 scheduler.step() 到底该放哪

你有没有过这种抓狂:模型换了优化器、加了正则化、连 batch_size 都调了,loss 还是不理想。最后发现------问题出在那个最不起眼的旋钮:学习率 。太大,loss 在天上飞、甚至直接 NaN;太小,loss 趴着纹丝不动,跑了一晚上跟没跑一样。

学习率,是训练里最敏感、也最常被误用 的超参数。前面第 12 篇的梯度下降公式 θ = θ − α ∇ L \theta = \theta - \alpha \nabla L θ=θ−α∇L 里,那个 α \alpha α 就是它。这一课,我们专门把 α \alpha α 怎么设、训练中怎么"变",一次讲透。我会用"下山找谷底"的比喻串起全程。


一、学习率是什么:下山时的"步子大小"

把训练想象成蒙眼下山找最低点(loss 最小处) 。你每走一步,靠脚下的坡度(梯度)判断往哪走,而学习率就是这一步迈多大

  • 步子太大:一脚跨过谷底,在坡两边来回蹦,永远落不准最低点;再大点,直接飞出山崖(loss 爆炸 / NaN)。
  • 步子太小:安全是安全,但一小时才挪一厘米,天黑都下不到山脚(loss 几乎不动)。
  • 步子刚好:稳稳朝着谷底走,最后在最低处精修到位。

于是聪明人会这么干:一开始离谷底远,大步快走;快到了,换成小碎步精修。这就是"学习率衰减"的全部哲学。

💡 一句话定位:学习率控制每次参数更新的步子大小。前期大步快走探路,后期小步精修收敛------这就是所有衰减策略的核心逻辑。


二、学习率过大 vs 过小:看 loss 曲线说话

判断学习率合不合适,最直观的办法就是看 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
python 复制代码
import torch

# ❌ 太大:loss 爆炸
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)

🕳️ 直觉补充:学习率没有"绝对正确值",只有"相对合适区间"。它和 batch_size、模型规模都耦合------batch 越大、模型越深,通常能承受更大的 lr。所以经验值只是起点,最终还得用 loss 曲线来裁定。


三、为什么需要衰减:训练的两个阶段

训练天然分两段:

  • 前期 :参数离最优解还远,需要大步快走 → 大学习率
  • 后期 :参数已经接近最优解,需要小步精修 → 小学习率

如果全程用大学习率,后期参数会在谷底附近来回震荡 ,永远精确不下来(就像你大步跨过最低点,左右横跳)。如果全程用小学习率,前期又慢得让人想砸电脑。所以必须衰减------前期大、后期小,平滑过渡。


四、常见衰减策略(附代码与适用场景)

PyTorch 把衰减逻辑封装在 torch.optim.lr_scheduler 里。记住一点:调度器是优化器的"副驾驶",它只负责改 optimizer 里的 lr,不负责更新参数。下面逐个看。

1. StepLR:等间隔衰减(最直白)

每隔固定 step_size 个 epoch,学习率乘一次 gamma

python 复制代码
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:0.1 → 0.05 → 0.025 ...

for epoch in range(30):
    train(...)
    scheduler.step()    # 每个 epoch 结束后调用一次
参数 含义
step_size 每隔多少 epoch 衰减一次
gamma 衰减系数(0.5 表示减半)

适用:不知道该何时衰减、只想简单定期砍一刀的场景。缺点是"砍"得太生硬,曲线不连续。

2. MultiStepLR:指定里程碑衰减(更可控)

只在你指定的 milestones 处衰减,其余时间不变。

python 复制代码
from torch.optim.lr_scheduler import MultiStepLR

scheduler = MultiStepLR(optimizer, milestones=[50, 130, 200], gamma=0.1)
# 在第 50、130、200 个 epoch 各乘以 0.1

适用:你心里大概知道"在哪些节点该降"(比如 ImageNet 训练常在第 30、60、90 轮降),比 StepLR 更贴合人工经验。

3. ExponentialLR:指数衰减(最平滑但降得快)

每个 epoch 都乘一次 gamma,连续平滑下降。

python 复制代码
from torch.optim.lr_scheduler import ExponentialLR

scheduler = ExponentialLR(optimizer, gamma=0.99)
# 每个 epoch: lr = lr * 0.99

适用 :想要平滑衰减、且不在乎前期降得偏快的情况。注意 gamma 要接近 1(如 0.99),否则几十步就归零了。

4. CosineAnnealingLR:余弦退火(现代最爱)

余弦曲线平滑下降,前期慢、中期快、后期又慢,像正弦的一半。

核心公式(学习率随 epoch 变化):

η t = η min ⁡ + 1 2 ( η max ⁡ − η min ⁡ ) ( 1 + cos ⁡ ( t T max ⁡ π ) ) \eta_t = \eta_{\min} + \frac{1}{2}(\eta_{\max} - \eta_{\min})\left(1 + \cos\left(\frac{t}{T_{\max}}\pi\right)\right) ηt=ηmin+21(ηmax−ηmin)(1+cos(Tmaxtπ))

其中 η max ⁡ \eta_{\max} ηmax 是初始 lr, η min ⁡ \eta_{\min} ηmin 是 eta_min, T max ⁡ T_{\max} Tmax 是周期长度。

python 复制代码
from torch.optim.lr_scheduler import CosineAnnealingLR

scheduler = CosineAnnealingLR(optimizer, T_max=50, eta_min=0)
# T_max:整个余弦周期长度(常设成总 epoch 数)
# eta_min:最小学习率(一般设 0 或很小的值)

⚠️ 为什么余弦退火这么流行? 它的下降节奏和人类调参的直觉高度吻合:前期 lr 还高时慢慢降(给你时间大步探路),中期快速滑落(加速收敛),后期贴近 0 时又放缓(精修不震荡)。现代深度学习(尤其是 Transformer 训练)几乎默认用它。

推荐:CosineAnnealingLR 是现代深度学习的首选衰减策略,实践效果通常优于生硬的 StepLR。


五、标准使用模板(含打印当前 lr)

python 复制代码
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)   # 周期 = 总 epoch 数

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 调一次)

    # 打印当前学习率(注意从 param_groups 里取,别硬编码)
    current_lr = optimizer.param_groups[0]['lr']
    print(f"Epoch {epoch+1:02d}, lr={current_lr:.6f}, loss={loss.item():.4f}")

🎮 👉 点击在线体验此交互组件

关键scheduler.step() 放在每个 epoch 训练结束后,不是每个 batch 后(详见第七节错误 1)。


六、Warmup:大模型训练的"先热身"

大模型训练(BERT、GPT 等)常加 Warmup :前几个 epoch 学习率从 0(或很小)线性升到目标值,再开始衰减。

为什么? 大模型初期参数还没稳定,直接用大学习率会一脚把精心初始化好的权重踢飞。先小步热身、让训练"进入状态",再大步走、最后衰减精修------三段式最稳。

python 复制代码
from torch.optim.lr_scheduler import LinearLR, SequentialLR

# Warmup(前 5 个 epoch 线性升到目标 lr)+ Cosine(之后退火到 0)
warmup = LinearLR(optimizer, start_factor=0.1, total_iters=5)
cosine = CosineAnnealingLR(optimizer, T_max=45)
scheduler = SequentialLR(optimizer, [warmup, cosine], milestones=[5])
# milestones=[5]:第 5 个 epoch 前用 warmup,之后切到 cosine

💡 入门提示 :普通小项目不需要 Warmup,它主要是为了解决"大模型初始不稳"的问题。但知道业界大模型训练有这回事,对你读论文、看开源代码很有帮助。


七、三个高频错误(收藏备用)

错误 1:scheduler.step() 放错位置

python 复制代码
# ❌ 放在 batch 循环里:一个 epoch 里被调用几百次,学习率瞬间衰减没
for epoch in range(50):
    for x, y in train_loader:
        ...
        optimizer.step()
        scheduler.step()   # 每 batch 都调!学习率崩了

# ✅ 放在 epoch 循环里:每个 epoch 只调一次
for epoch in range(50):
    for x, y in train_loader:
        ...
        optimizer.step()
    scheduler.step()       # 每个 epoch 调一次

⚠️ 记忆口诀optimizer.step() 在 batch 里(更新参数),scheduler.step() 在 epoch 后(调整 lr)。它俩频率差了一个数量级。

错误 2:只调学习率,不看 loss 曲线

盲目调 lr 没意义。训练时务必记录 loss、画出来看趋势------眼睛比直觉靠谱。

python 复制代码
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()

错误 3:学习率衰减太早

前 10 个 epoch loss 还在快速下降,你就急着衰减学习率,结果训练提前停滞------相当于还没走下山就蹲下了。一般要等前期 loss 进入"平台期"、下降变慢时,再开始衰减。


核心要点小结

  1. 学习率 = 下山步子大小:太大 → loss 震荡 / NaN;太小 → loss 不动;合适 → 平稳下降。
  2. 经验起点 :Adam 1e-3、SGD 0.01、大模型微调 1e-5 级,但最终用 loss 曲线裁定。
  3. 衰减逻辑:前期大步快走探路,后期小步精修收敛。
  4. 调度器是优化器的"副驾驶" :只改 lr,不更新参数;scheduler.step()epoch 循环里,不是 batch 循环里。
  5. CosineAnnealingLR 是现代首选:下降节奏(慢---快---慢)最贴合调参直觉;StepLR 生硬、ExponentialLR 降太快。
  6. 大模型训练加 Warmup:先小步热身(线性升 lr)再退火,避免初始大 lr 踢飞权重。
  7. 训练时画 loss 曲线:用眼睛判断学习率是否合适,别盲调。

动手思考题

  1. StepLR(step_size=10, gamma=0.1),初始 lr=0.1,第 25 个 epoch 时学习率是多少?(提示:数一数第 10、20 个 epoch 各衰减了几次)
  2. 训练时 loss 先正常下降,到 epoch 30 突然变成 NaN,最可能是什么原因?请列出你的排查步骤(至少 3 步)。
  3. 写一段训练配置:Adam 优化器,lr=0.001,前 5 个 epoch 做 Warmup,之后用余弦退火到 0,总共 50 个 epoch。(提示:用 LinearLR + CosineAnnealingLR + SequentialLR
  4. 小实验:把上面的配置跑一个小数据集,分别用 StepLR 和 CosineAnnealingLR,对比两者最终的 val_acc 和收敛平滑度,把你的曲线贴到评论区,我们一起看哪种衰减更香 🔥

下一课解决训练的另一个基础设施------GPU 训练时,模型和数据怎么放到同一块设备上 ,以及那个让人头大的 Expected all tensors to be on the same device 报错怎么破。


📚 关于本系列

本文是 「AI 学习路线 · 阶段四:PyTorch 深度学习基础」 系列中的一篇。所有文章在我的个人博客上都有 可交互动画 + 完整学习路线 版本,建议配合食用 👇

🔗 在博客上阅读本文原版(含可交互组件、公式动画)

👉 PyTorch 学习率实战:从零理解衰减策略与调度器

🗺️ 查看完整 AI 学习路线 (从 0 到进阶,持续更新)

👉 bestsdz.xyz

觉得有帮助的话,欢迎去博客点个收藏 ⭐,你的支持是我更新的最大动力!

相关推荐
nuoxin1141 小时前
BL-M8812CU3 无线 WiFi 模块-富利威
人工智能·嵌入式硬件·fpga开发·硬件工程·dsp开发
cxr8281 小时前
第四章 查询与推理能力
人工智能·架构·知识图谱·智能体
云端漫步19871 小时前
HarmonyOS NEXT AI 应用开发总结:30 篇之旅
人工智能·华为·harmonyos
布值倒区什么name1 小时前
python文件IO学习
python
confiself1 小时前
COVE:记忆-参数双通道协调自进化
人工智能
风途科技~1 小时前
土壤五参数测定仪:pH / 水分 / 温度 / 电导率 / 含盐量一体化土壤监测利器
人工智能
chanmama88881 小时前
品牌全域洞察怎么做?蝉妈妈拆解竞品策略
大数据·网络·人工智能·经验分享·社交电子
新新学长搞科研1 小时前
【人工智能会议推荐】2026人工智能、信息物理系统和智能计算国际学术会议(ICAICI 2026)
人工智能·智能计算
海盗12341 小时前
AI新闻日报_2026-08-06
人工智能