训练刚开始的那几步,梯度方向还不稳定,一上来就用大学习率,容易把参数推到奇怪的地方。稳妥的做法是先小步走几步,等梯度方向稳了,再放开步长,这就是预热(warmup)。训练到后期,又希望用更平滑的曲线一点点接近最优解,甚至希望在降到低点后自动重启,再重新搜索一遍。
这些需求,简单的固定衰减满足不了。我们这篇文章介绍三组更灵活的调度器:预热与线性(ConstantLR、LinearLR)、余弦退火(CosineAnnealingLR、CosineAnnealingWarmRestarts)、组合策略(ChainedScheduler、SequentialLR)。每种都给出构造参数、变化规律、示例代码和适用场景。
1. 预热与线性
这一类解决的是训练开头的问题:训练最开始的那几步,梯度估计噪声大,一上来就用大学习率容易把参数推到奇怪的地方。先小步走几步(预热,warmup),再放开步长,是 Transformer 这类模型的标准做法。
这一族有两个工具:ConstantLR、LinearLR,逐个看。
ConstantLR 的思路最直白:前 total_iters 个 step,学习率固定在 初始学习率 × factor。走完这些 step 后,学习率恢复为优化器里设置的初始学习率,之后保持不变。相当于训练刚开始时踩一脚刹车。
ConstantLR(optimizer, factor=0.33, total_iters=5)
factor 是预热阶段学习率的缩放比例,total_iters 是预热持续多少 step。下面的例子初始学习率设 1、factor=0.1、total_iters=5,那么前 5 步学习率是 0.1,之后回到 1。注意示例里 learning_rates 的第一个值来自 scheduler.get_last_lr(),创建调度器后它已经按 factor 缩放好了。
import torch
import torch.optim as optim
import torch.nn as nn
import matplotlib.pyplot as plt
from torch.optim.lr_scheduler import ConstantLR
model_parameters = [nn.Parameter(torch.tensor([1, 2, 3], dtype=torch.float32))]
optimizer = optim.Adam(model_parameters, lr=1)
scheduler = ConstantLR(optimizer, factor=0.1, total_iters=5)
learning_rates = [scheduler.get_last_lr()[0]]
for _ in range(10):
optimizer.step()
scheduler.step()
learning_rates.append(scheduler.get_last_lr()[0])
print('%.5f' % scheduler.get_last_lr()[0])
plt.plot(range(len(learning_rates)), learning_rates, 'ro-')
plt.title('ConstantLR')
plt.grid()
plt.show()
LinearLR 在预热阶段给出了一个平滑的过渡:用 total_iters 个 step,把学习率从 初始学习率 × start_factor 线性地变化到 初始学习率 × end_factor,之后保持 end_factor 对应的值不变。
LinearLR(optimizer, start_factor=0.33, end_factor=1.0, total_iters=5)
最经典的用法是线性预热:start_factor 设得很小(比如 0.001),end_factor=1.0,学习率在 total_iters 步内从几乎为 0 线性升到初始学习率。也可以反过来,start_factor=1.0、end_factor=0.1,做一段线性下降再保持。
import torch
import torch.optim as optim
import torch.nn as nn
import matplotlib.pyplot as plt
from torch.optim.lr_scheduler import LinearLR
model_parameters = [nn.Parameter(torch.tensor([1, 2, 3], dtype=torch.float32))]
optimizer = optim.SGD(model_parameters, lr=0.1)
# 前 50 步从 0.0001 线性升到 0.01,之后保持 0.01
scheduler = LinearLR(optimizer, start_factor=0.001, end_factor=0.10, total_iters=50)
learning_rates = [scheduler.get_last_lr()[0]]
for _ in range(100):
optimizer.step()
scheduler.step()
learning_rates.append(scheduler.get_last_lr()[0])
plt.plot(range(len(learning_rates)), learning_rates)
plt.title('LinearLR')
plt.grid()
plt.show()
小贴士:warmup 往往和后面的衰减搭配使用,先用 LinearLR 预热,再用 SequentialLR 切到衰减策略,第 3 节会演示这个组合。
2. 余弦退火
余弦退火(Cosine Annealing)是一大类非常流行的策略:学习率沿余弦曲线从最大值平滑降到最小值。余弦曲线在两端变化慢、中间变化快,好处是下降初期保持较高学习率、后期又能平滑逼近最小值,比阶梯式衰减更温和。它还有个额外的价值:降到最低点后自动重启回升,重新用大学习率搜索。在陷入局部最优时,大学习率有助于跳出来,这也是它广受欢迎的原因。
这一族有两个变体:CosineAnnealingLR、CosineAnnealingWarmRestarts。
CosineAnnealingLR 用 T_max 控制周期:从优化器设置的初始学习率(最大值)到 eta_min(最小值),走完 T_max 个 step。到点后自动重启,重新从最大值往下走,循环往复。
CosineAnnealingLR(optimizer, T_max, eta_min=0)
下面例子最大学习率 0.1、最小值 0.01、T_max=20,学习率每 20 步完成一次从高到低的余弦下降然后重启。训练 100 步会看到约 5 个完整的周期。
import torch
import torch.optim as optim
import torch.nn as nn
import matplotlib.pyplot as plt
from torch.optim.lr_scheduler import CosineAnnealingLR
model_parameters = [nn.Parameter(torch.tensor([1, 2, 3], dtype=torch.float32))]
optimizer = optim.SGD(model_parameters, lr=0.1)
scheduler = CosineAnnealingLR(optimizer, T_max=20, eta_min=0.01)
learning_rates = [scheduler.get_last_lr()[0]]
for _ in range(100):
optimizer.step()
scheduler.step()
learning_rates.append(scheduler.get_last_lr()[0])
plt.plot(range(len(learning_rates)), learning_rates)
plt.title('CosineAnnealingLR')
plt.grid()
plt.show()
适用场景:图像分类、目标检测等 CV 训练的主流选择。重启机制带来的周期性大学习率,能帮助模型跳出局部最优。
CosineAnnealingWarmRestarts 是余弦退火加热重启(warm restarts):显式指定第一个周期多长(T_0),之后每个周期是上一个周期的 T_mult 倍,学习率在每个周期结束时回到最大值(或接近最大值)重新开始。
CosineAnnealingWarmRestarts(optimizer, T_0, T_mult=1, eta_min=0)
T_0 是第一个周期的步长,T_mult 是周期递增倍数。设 T_0=10、T_mult=2,周期长度依次是 10、20、40、80、160,也就是重启点分别在第 10、30、70、150 步。T_mult=1 时所有周期等长,和 CosineAnnealingLR 行为接近。
import torch
import torch.optim as optim
import torch.nn as nn
import matplotlib.pyplot as plt
from torch.optim.lr_scheduler import CosineAnnealingWarmRestarts
model_parameters = [nn.Parameter(torch.tensor([1, 2, 3], dtype=torch.float32))]
optimizer = optim.SGD(model_parameters, lr=0.1)
# T_0=10:第一个周期 10 步,T_mult=2:之后周期依次为 20、40、80
scheduler = CosineAnnealingWarmRestarts(optimizer, T_0=10, T_mult=2, eta_min=0.01)
learning_rates = [scheduler.get_last_lr()[0]]
for _ in range(100):
optimizer.step()
scheduler.step()
learning_rates.append(scheduler.get_last_lr()[0])
plt.plot(range(len(learning_rates)), learning_rates)
plt.title('CosineAnnealingWarmRestarts')
plt.grid()
plt.show()
适用场景:希望多次用大学习率重新搜索的探索型训练。周期倍增让后期搜索频率降低、更聚焦于精调。
3. 组合策略
单个策略表达不了复杂计划时,就需要把多个调度器组合起来。PyTorch 提供了两种组合方式,区别在于组合的语义。ChainedScheduler 是同时作用,每个 step 所有策略都执行,规则叠加。SequentialLR 是顺序切换,前一个策略跑完再交给下一个。
组合策略有两种实现方式:ChainedScheduler、SequentialLR。
ChainedScheduler 传入一个调度器列表,每个 step 都会依次调用列表中每个调度器的 step(),所有规则同时生效、互相叠加。比如把 StepLR(每 10 步乘 0.1)和 ExponentialLR(每步乘 0.9)组合,学习率同时受两种规则影响。
ChainedScheduler(schedulers)
import torch
import torch.optim as optim
import torch.nn as nn
import matplotlib.pyplot as plt
from torch.optim.lr_scheduler import ChainedScheduler, StepLR, ExponentialLR
model_parameters = [nn.Parameter(torch.tensor([1, 2, 3], dtype=torch.float32))]
optimizer = optim.SGD(model_parameters, lr=0.1)
scheduler1 = StepLR(optimizer, step_size=10, gamma=0.1)
scheduler2 = ExponentialLR(optimizer, gamma=0.9)
scheduler = ChainedScheduler([scheduler1, scheduler2])
learning_rates = [scheduler.get_last_lr()[0]]
for _ in range(100):
optimizer.step()
scheduler.step()
learning_rates.append(scheduler.get_last_lr()[0])
plt.plot(range(len(learning_rates)), learning_rates)
plt.title('ChainedScheduler')
plt.grid()
plt.show()
SequentialLR 让多个调度器按阶段接力:milestones 指定切换点,第一个调度器从第 0 步跑到第 milestones[0] 步,然后切换到第二个,直到最后一个调度器接管剩余训练。注意:切换时学习率会先回到优化器设置的初始学习率,再按新策略重新计算,最后一个调度器会一直工作到训练结束。
SequentialLR(optimizer, schedulers, milestones)
最典型的应用就是 warmup 加余弦衰减:先用 LinearLR 预热,训练到一定步数后切到 CosineAnnealingLR 做余弦下降。下面的例子前 20 步用 ExponentialLR 平滑衰减,之后切到 StepLR 阶梯下降。
import torch
import torch.optim as optim
import torch.nn as nn
import matplotlib.pyplot as plt
from torch.optim.lr_scheduler import SequentialLR, ExponentialLR, StepLR
model_parameters = [nn.Parameter(torch.tensor([1, 2, 3], dtype=torch.float32))]
optimizer = optim.SGD(model_parameters, lr=0.1)
scheduler1 = ExponentialLR(optimizer, gamma=0.9) # 第 0~19 步
scheduler2 = StepLR(optimizer, step_size=10, gamma=0.1) # 第 20 步起
scheduler = SequentialLR(optimizer, schedulers=[scheduler1, scheduler2], milestones=[20])
learning_rates = [scheduler.get_last_lr()[0]]
for _ in range(100):
optimizer.step()
scheduler.step()
learning_rates.append(scheduler.get_last_lr()[0])
plt.plot(range(len(learning_rates)), learning_rates)
plt.title('SequentialLR')
plt.grid()
plt.show()
这一篇讲了三组策略。预热解决训练开头的问题:先小步走几步,等梯度方向稳了再放开步长,ConstantLR 是恒定小步,LinearLR 是线性过渡。余弦退火负责训练后半段:沿余弦曲线平滑降到最低,还能自动重启,帮模型跳出局部最优。组合策略把不同阶段串起来:ChainedScheduler 多条规则同时作用,SequentialLR 按阶段接力。最经典的组合是 warmup 加余弦衰减,先预热,再按余弦曲线降,很多预训练模型的官方实现都是这个套路。

冀公网安备13050302001966号