优化器与学习率调度
优化器利用梯度历史更新参数,调度器改变后续更新学习率。
学习目标
- 把 SGD 更新与 StepLR 顺序对齐
- 解冻骨干后为什么可能仍不更新?
- 说明:调度频率由计划定义;不能把所有 scheduler 都放在每个 batch 后调用。
为什么需要这个概念
优化器保存参数引用及更新状态;学习率调度器修改优化器参数组中的学习率。调度轴可以是 epoch、参数更新数或验证指标,必须按具体调度器约定使用。
学习前需要掌握
背景与问题
优化器决定怎样把梯度转为参数变化,调度器决定学习率怎样随训练推进。二者职责不同,更新频率不一致会使实际训练配方偏离记录。
概念定义
优化器保存参数引用及更新状态;学习率调度器修改优化器参数组中的学习率。调度轴可以是 epoch、参数更新数或验证指标,必须按具体调度器约定使用。
直观理解
优化器决定怎么迈步,调度器决定今天迈多大步;数了多少批数据并不总等于迈了多少次步。
核心原理
SGD 使用当前梯度或带动量的历史方向,AdamW 使用每参数矩估计并可施加解耦衰减。建立优化器后新增或解冻参数,不会凭空让它们进入旧参数组,必须检查实际优化参数集合。
调度器通常在 optimizer.step 后推进;StepLR 常按 epoch,OneCycleLR 常按更新,ReduceLROnPlateau 接收验证指标。调用次数是数学计划的一部分,不是随意放在哪里都一样。
多个参数组可以有不同初始学习率,但恢复训练时需要同时恢复优化器和调度器状态。若重新创建调度器却从旧 epoch 接着记日志,打印的轮次并不能保证学习率轨迹接上。
数学表达
核心公式
优化器保存参数引用及更新状态;学习率调度器修改优化器参数组中的学习率。调度轴可以是 epoch、参数更新数或验证指标,必须按具体调度器约定使用。
变量含义
η_t当前学习率g_t当前梯度
计算步骤
- 1第一步用 0.1 更新到 0.9,再衰减。
- 2第二步用 0.05 更新到 0.85,再衰减。
- 3第三步用 0.025 更新到 0.825。
代码对应位置
示例 1把 SGD 更新与 StepLR 顺序对齐的最小验证:使用完整构造输入核对把 SGD 更新与 StepLR 顺序对齐,输出只表示本例计算。
变量解释
| 变量 | 含义 |
|---|---|
η_t | 当前学习率 |
g_t | 当前梯度 |
完整数值示例
把 SGD 更新与 StepLR 顺序对齐
已知条件
- 参数初值 1,每次梯度为 1
- 初始学习率 0.1,每步后乘 0.5
计算步骤
- 第一步用 0.1 更新到 0.9,再衰减。
- 第二步用 0.05 更新到 0.85,再衰减。
- 第三步用 0.025 更新到 0.825。
计算结果
实际使用的学习率依次为 0.1、0.05、0.025。
处理前后对比
- 调度频率由计划定义;不能把所有 scheduler 都放在每个 batch 后调用。
把 SGD 更新与 StepLR 顺序对齐
代码在更新前读取 used_lr,更新后才调用 scheduler.step,因而日志明确区分“这一步用的学习率”与“下一步的学习率”。
若在每个微批都推进,而两微批才优化一次,计划就被加速两倍。遇到 AMP 因非有限梯度跳过更新时,同样要考虑是否推进按更新计数的计划。
计算与实现步骤
- 第一步用 0.1 更新到 0.9,再衰减。
- 第二步用 0.05 更新到 0.85,再衰减。
- 第三步用 0.025 更新到 0.825。
代码实现
示例 1
把 SGD 更新与 StepLR 顺序对齐的最小验证
example_01.py用途:使用完整构造输入核对把 SGD 更新与 StepLR 顺序对齐,输出只表示本例计算。
import torch
p = torch.nn.Parameter(torch.tensor(1.))
opt = torch.optim.SGD([p], lr=0.1)
sched = torch.optim.lr_scheduler.StepLR(opt, step_size=1, gamma=0.5)
for step in range(1, 4):
opt.zero_grad(set_to_none=True)
p.grad = torch.ones_like(p)
used = opt.param_groups[0]["lr"]
opt.step()
sched.step()
print(f"step={step} used_lr={used:.3f} parameter={p.item():.3f}")代码解析
解析始终位于完整代码下方,并按实际代码段逐项对应。
输入数据与任务
使用完整构造输入核对把 SGD 更新与 StepLR 顺序对齐,输出只表示本例计算。
Step 1 · 1–11 行
导入当前步骤需要的数值计算、预处理、模型或评价工具。依赖集中写在代码开头,便于复现。
import torch
p = torch.nn.Parameter(torch.tensor(1.))
opt = torch.optim.SGD([p], lr=0.1)
sched = torch.optim.lr_scheduler.StepLR(opt, step_size=1, gamma=0.5)
for step in range(1, 4):
opt.zero_grad(set_to_none=True)
p.grad = torch.ones_like(p)
used = opt.param_groups[0]["lr"]
opt.step()
sched.step()
print(f"step={step} used_lr={used:.3f} parameter={p.item():.3f}")预期输出或运行结果
step=1 used_lr=0.100 parameter=0.900 step=2 used_lr=0.050 parameter=0.850 step=3 used_lr=0.025 parameter=0.825
常见错误 · 4 条
- 每 epoch 重建 optimizer
- scheduler 频率错
- 参数组重复
- 只恢复权重
实际应用
- 对齐优化预算和学习率曲线
- 微调不同模块时使用参数组
常见错误
- 每 epoch 重建 optimizer
- scheduler 频率错
- 参数组重复
- 只恢复权重
常见问题
解冻骨干后为什么可能仍不更新?
requires_grad=True 只允许求导,优化器还必须持有这些参数;若创建时只传任务头,需要新增参数组或按配方重建。
这个示例能说明什么边界?
调度频率由计划定义;不能把所有 scheduler 都放在每个 batch 后调用。