现在位置: 首页 > PyTorch 教程 > 正文

PyTorch 学习率调度器

学习率(Learning Rate)是神经网络训练中最重要的超参数之一。学习率过大,训练震荡甚至发散;学习率过小,收敛极慢,容易陷入局部最优。

学习率调度器(LR Scheduler) 通过在训练过程中动态调整学习率,兼顾训练初期的快速收敛与后期的精细调优。


1. 基础概念与使用模式

标准使用流程

实例

import torch
import torch.nn as nn
import torch.optim as optim

model     = nn.Linear(10, 1)
optimizer = optim.SGD(model.parameters(), lr=0.1)

# 1. 创建调度器,传入 optimizer
scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=30, gamma=0.1)

for epoch in range(100):
    # 2. 训练
    train(model, optimizer)

    # 3. 每个 epoch 结束后调用 scheduler.step()
    scheduler.step()

    # 4. 查看当前学习率
    current_lr = scheduler.get_last_lr()[0]
    print(f"Epoch {epoch+1}, LR: {current_lr:.6f}")

step() 的调用时机

实例

# 正确:optimizer.step() 在前,scheduler.step() 在后
optimizer.step()
scheduler.step()

# 错误:scheduler.step() 在 optimizer.step() 之前
# PyTorch 1.1.0 之后会产生警告,部分调度器行为异常
scheduler.step()
optimizer.step()

注意:optimizer.step() 必须在 scheduler.step() 之前调用。

查看和保存学习率状态

实例

# 获取当前学习率
current_lr = optimizer.param_groups[0]['lr']
current_lr = scheduler.get_last_lr()[0]   # 上一次 step() 后的 LR

# 保存检查点(必须同时保存 scheduler 状态)
torch.save({
    'epoch':          epoch,
    'model':          model.state_dict(),
    'optimizer':      optimizer.state_dict(),
    'scheduler':      scheduler.state_dict(),   # 别漏掉这个
}, 'checkpoint.pth')

# 恢复检查点
ckpt = torch.load('checkpoint.pth')
model.load_state_dict(ckpt['model'])
optimizer.load_state_dict(ckpt['optimizer'])
scheduler.load_state_dict(ckpt['scheduler'])

2. 固定衰减调度器

2.1 StepLR 按步衰减

每隔固定 step_size 个 epoch,将学习率乘以 gamma。是最简单、最常用的调度器之一。

公式: lr = lr_base * gamma^(floor(step / step_size))

实例

optimizer = optim.SGD(model.parameters(), lr=0.1)
scheduler = optim.lr_scheduler.StepLR(
    optimizer,
    step_size=30,   # 每 30 个 epoch 衰减一次
    gamma=0.1       # 每次乘以 0.1(即缩小为原来的 1/10)
)

# LR 变化:
# Epoch  0-29:  0.1
# Epoch 30-59:  0.01
# Epoch 60-89:  0.001
# Epoch 90+:    0.0001

适用场景:训练节奏固定、阶段清晰的任务,如 ResNet 在 ImageNet 上的训练(90 epoch,在第 30、60 epoch 衰减)。


2.2 MultiStepLR 多里程碑衰减

在指定的若干个 epoch(里程碑)处衰减学习率,比 StepLR 更灵活。

实例

optimizer = optim.SGD(model.parameters(), lr=0.1)
scheduler = optim.lr_scheduler.MultiStepLR(
    optimizer,
    milestones=[30, 60, 80],   # 在第 30、60、80 epoch 衰减
    gamma=0.1
)

# LR 变化:
# Epoch  0-29:  0.1
# Epoch 30-59:  0.01
# Epoch 60-79:  0.001
# Epoch 80+:    0.0001

适用场景:已知在哪些 epoch 模型需要精细调整,如分类模型中后期精细收敛阶段。


2.3 ExponentialLR 指数衰减

每个 epoch 都衰减,学习率以指数形式持续下降,衰减更平滑。

公式: lr = lr_base * gamma^epoch

实例

optimizer = optim.Adam(model.parameters(), lr=0.01)
scheduler = optim.lr_scheduler.ExponentialLR(
    optimizer,
    gamma=0.95   # 每个 epoch 乘以 0.95
)

# LR 变化(前 5 个 epoch):
# Epoch 1: 0.0100
# Epoch 2: 0.0095
# Epoch 3: 0.0090
# Epoch 4: 0.0086
# Epoch 5: 0.0081

gamma 设置得太小(如 0.5)会导致学习率迅速趋近于零,通常设在 0.9~0.99 之间。


3. 自适应调度器

3.1 ReduceLROnPlateau 监控指标衰减

最智能的调度器之一:监控某个指标(如验证集 loss),当该指标停止改善时自动降低学习率。不需要提前知道在哪个 epoch 衰减。

实例

optimizer = optim.Adam(model.parameters(), lr=0.01)
scheduler = optim.lr_scheduler.ReduceLROnPlateau(
    optimizer,
    mode='min',       # 'min': 监控值越小越好(loss);'max': 越大越好(accuracy)
    factor=0.1,       # 触发时 lr = lr × factor
    patience=10,      # 允许指标停滞的 epoch 数,超过则衰减
    threshold=1e-4,   # 改善幅度小于该值视为未改善
    min_lr=1e-6,      # 学习率下限,不会低于此值
    verbose=True      # 打印衰减信息
)

for epoch in range(100):
    train_loss = train(model, optimizer)
    val_loss   = evaluate(model)

    # 与其他调度器不同,这里传入监控指标
    scheduler.step(val_loss)

监控 Accuracy 的写法:

实例

scheduler = optim.lr_scheduler.ReduceLROnPlateau(
    optimizer,
    mode='max',       # accuracy 越大越好
    factor=0.5,
    patience=5,
)

scheduler.step(val_accuracy)

适用场景:几乎所有任务的默认首选,尤其是不确定训练多少 epoch、或训练不稳定时。


3.2 CosineAnnealingLR 余弦退火

学习率按余弦曲线从初始值平滑下降到最小值(eta_min),避免了阶梯式衰减的突变。

公式: lr_t = eta_min + 0.5 * (eta_max - eta_min) * (1 + cos(t * pi / T_max)

实例

optimizer = optim.SGD(model.parameters(), lr=0.1)
scheduler = optim.lr_scheduler.CosineAnnealingLR(
    optimizer,
    T_max=100,     # 半个周期的长度(通常设为总 epoch 数)
    eta_min=1e-6   # 学习率最小值(默认 0)
)

# LR 变化轨迹(T_max=10 时的示意):
# 0.1 -> 0.095 -> 0.079 -> 0.055 -> 0.026 -> 0.001
#              (余弦曲线平滑下降)

适用场景:训练 epoch 数固定的场景,在 Vision Transformer、ResNet 等论文中广泛使用,收敛质量好。


3.3 CosineAnnealingWarmRestarts

余弦退火的升级版,支持周期性重启(Warm Restarts):每个周期结束后学习率重置回初始值,开始新一轮余弦衰减。允许模型跳出局部最优。

实例

optimizer = optim.SGD(model.parameters(), lr=0.1)
scheduler = optim.lr_scheduler.CosineAnnealingWarmRestarts(
    optimizer,
    T_0=10,       # 第一个周期的长度(epoch 数)
    T_mult=2,     # 每次重启后周期长度的倍数(1=等长,2=逐渐加长)
    eta_min=1e-6
)

# T_mult=2 时的周期长度:10 -> 20 -> 40 -> 80 ...
# LR 变化(T_0=10, T_mult=1):
# 0.1 -> ... -> 0 -> 0.1 -> ... -> 0 -> 0.1(每 10 个 epoch 重启一次)

适用场景:训练大模型、或希望模型从多个收敛点中选最优时,配合 Snapshot Ensemble 使用效果显著。


4. 预热调度器

Warmup(预热)是指训练开始的若干步内,学习率从一个极小值逐渐升高到目标值。大 batch size 训练、Transformer 类模型几乎都需要预热,否则初期梯度更新过猛,模型难以稳定。

4.1 LinearLR 线性调度

在指定 epoch 内线性改变学习率(可用于线性预热或线性衰减)。

实例

optimizer = optim.Adam(model.parameters(), lr=0.01)

# 线性预热:前 5 个 epoch 内,lr 从 0.01×0.1=0.001 线性增长到 0.01
warmup_scheduler = optim.lr_scheduler.LinearLR(
    optimizer,
    start_factor=0.1,   # 初始 lr = base_lr × start_factor
    end_factor=1.0,     # 结束 lr = base_lr × end_factor
    total_iters=5       # 经过 5 个 epoch 完成
)

# LR 变化:0.001 -> 0.003 -> 0.005 -> 0.007 -> 0.009 -> 0.01

4.2 ConstantLR 常数阶段

在指定 epoch 数内,将学习率固定为 base_lr × factor,之后恢复原值。

实例

# 前 5 个 epoch 使用 base_lr × 0.5,之后恢复正常
scheduler = optim.lr_scheduler.ConstantLR(
    optimizer,
    factor=0.5,
    total_iters=5
)

4.3 SequentialLR 组合调度

将多个调度器按顺序串联,是实现"预热 + 衰减"组合策略的标准做法。

实例

optimizer = optim.Adam(model.parameters(), lr=0.01)

# 阶段一:预热(前 5 个 epoch,LR 从 0.001 线性增长到 0.01)
warmup = optim.lr_scheduler.LinearLR(
    optimizer, start_factor=0.1, end_factor=1.0, total_iters=5
)

# 阶段二:余弦退火(剩余 95 个 epoch)
cosine = optim.lr_scheduler.CosineAnnealingLR(
    optimizer, T_max=95, eta_min=1e-6
)

# 组合:先执行 warmup,第 5 个 epoch 后切换到 cosine
scheduler = optim.lr_scheduler.SequentialLR(
    optimizer,
    schedulers=[warmup, cosine],
    milestones=[5]          # 在第 5 个 epoch 切换
)

# 使用方式与普通调度器完全一致
for epoch in range(100):
    train(...)
    scheduler.step()

适用场景:Transformer 训练的标配(预热 + 余弦退火),BERT、GPT、ViT 的预训练均采用此策略。


5. 循环调度器

5.1 CyclicLR 循环学习率

学习率在 base_lrmax_lr 之间周期性循环,可帮助模型探索更广的参数空间、跳出鞍点。

实例

optimizer = optim.SGD(model.parameters(), lr=0.01)
scheduler = optim.lr_scheduler.CyclicLR(
    optimizer,
    base_lr=0.001,          # 学习率下界
    max_lr=0.01,            # 学习率上界
    step_size_up=2000,      # 从 base_lr 上升到 max_lr 的迭代步数
    step_size_down=2000,    # 从 max_lr 下降到 base_lr 的迭代步数(默认等于 step_size_up)
    mode='triangular',      # 三角形循环(等幅)
    # mode='triangular2'    # 每个周期振幅减半
    # mode='exp_range'      # 振幅指数衰减
)

# CyclicLR 按 step(batch)调用,不是按 epoch
for epoch in range(num_epochs):
    for inputs, labels in train_loader:
        optimizer.zero_grad()
        loss = criterion(model(inputs), labels)
        loss.backward()
        optimizer.step()
        scheduler.step()    # 每个 batch 后调用

三种模式对比:

mode振幅变化特点
triangular不变稳定探索,适合初期
triangular2每周期减半先探索后收敛
exp_range指数衰减最终平稳收敛

5.2 OneCycleLR 单周期策略

性能最优的调度器之一,由 fastai 提出的 1-Cycle Policy。整个训练只有一个周期:学习率先升后降,动量(momentum)反向变化。

训练速度更快,通常只需传统训练的 1/5~1/10 的 epoch 数就能达到相同精度。

实例

optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.9)

steps_per_epoch = len(train_loader)

scheduler = optim.lr_scheduler.OneCycleLR(
    optimizer,
    max_lr=0.1,                              # 学习率最高点
    steps_per_epoch=steps_per_epoch,         # 每个 epoch 的 step 数
    epochs=10,                               # 总 epoch 数
    pct_start=0.3,                           # 前 30% 用于预热上升
    anneal_strategy='cos',                   # 衰减策略('cos' 或 'linear')
    div_factor=25,                           # 初始 lr = max_lr / div_factor
    final_div_factor=1e4                     # 最终 lr = max_lr / final_div_factor
)

# 初始 lr = 0.1 / 25 = 0.004
# 峰值 lr = 0.1(在 30% 处)
# 最终 lr = 0.1 / 10000 = 0.00001

# 同样按 batch 调用
for epoch in range(10):
    for inputs, labels in train_loader:
        optimizer.zero_grad()
        loss = criterion(model(inputs), labels)
        loss.backward()
        optimizer.step()
        scheduler.step()    # 每个 batch 后调用

适用场景:在计算资源有限、需要快速验证想法时首选;max_lr 建议通过 LR Finder 工具确定最优值。


6. 自定义调度器

6.1 LambdaLR 函数式自定义

通过传入一个 Lambda 函数(接受 epoch 数,返回学习率乘数)来完全自定义调度策略。

实例

optimizer = optim.Adam(model.parameters(), lr=0.01)

# 示例一:Transformer 经典预热策略
# lr ∝ min(step^-0.5, step × warmup_steps^-1.5)
def transformer_lr(epoch, warmup_epochs=10, d_model=512):
    if epoch == 0:
        return 1e-7 / 0.01          # 避免除零
    step = epoch + 1
    warmup = warmup_epochs
    return (d_model ** -0.5) * min(step ** -0.5, step * warmup ** -1.5) / 0.01

scheduler = optim.lr_scheduler.LambdaLR(optimizer, lr_lambda=transformer_lr)


# 示例二:多项式衰减(lr 从初始值线性/多项式降到 0)
def polynomial_decay(epoch, total_epochs=100, power=1.0):
    return max((1 - epoch / total_epochs) ** power, 0.0)

scheduler = optim.lr_scheduler.LambdaLR(optimizer, lr_lambda=polynomial_decay)


# 示例三:分组学习率(不同参数组不同策略)
optimizer = optim.Adam([
    {'params': model.backbone.parameters(), 'lr': 1e-4},
    {'params': model.head.parameters(),     'lr': 1e-3},
])

# 每个参数组一个 lambda 函数
scheduler = optim.lr_scheduler.LambdaLR(
    optimizer,
    lr_lambda=[
        lambda epoch: 0.95 ** epoch,    # backbone:慢衰减
        lambda epoch: 0.85 ** epoch,    # head:快衰减
    ]
)

6.2 继承 LRScheduler

需要更复杂的逻辑时,继承 LRScheduler(PyTorch >= 2.0,旧版为 _LRScheduler)实现完全自定义:

实例

from torch.optim.lr_scheduler import LRScheduler
import math

class WarmupCosineScheduler(LRScheduler):
    """
    预热 + 余弦退火组合调度器(手动实现版)
    - 前 warmup_epochs 个 epoch 线性预热
    - 之后余弦退火到 min_lr
    """

    def __init__(self, optimizer, warmup_epochs, total_epochs,
                 min_lr=1e-6, last_epoch=-1):
        self.warmup_epochs = warmup_epochs
        self.total_epochs  = total_epochs
        self.min_lr        = min_lr
        super().__init__(optimizer, last_epoch)

    def get_lr(self):
        epoch = self.last_epoch

        # 预热阶段:线性增大
        if epoch < self.warmup_epochs:
            warmup_factor = (epoch + 1) / self.warmup_epochs
            return [base_lr * warmup_factor for base_lr in self.base_lrs]

        # 余弦退火阶段
        progress = (epoch - self.warmup_epochs) / (
            self.total_epochs - self.warmup_epochs
        )
        cosine_factor = 0.5 * (1 + math.cos(math.pi * progress))
        return [
            self.min_lr + (base_lr - self.min_lr) * cosine_factor
            for base_lr in self.base_lrs
        ]


optimizer = optim.Adam(model.parameters(), lr=0.01)
scheduler = WarmupCosineScheduler(
    optimizer,
    warmup_epochs=10,
    total_epochs=100,
    min_lr=1e-6
)

7. 调度器可视化对比

以下代码可以绘制各调度器的 LR 变化曲线,方便直观对比:

实例

import torch
import torch.optim as optim
import matplotlib.pyplot as plt

def simulate_lr(scheduler_fn, epochs=100, steps_per_epoch=None):
    """模拟并记录调度器的学习率变化"""
    model     = torch.nn.Linear(1, 1)
    optimizer = optim.SGD(model.parameters(), lr=0.1)
    scheduler = scheduler_fn(optimizer)
    lrs = []

    if steps_per_epoch:
        # 按 step 调用的调度器
        for _ in range(epochs):
            for _ in range(steps_per_epoch):
                optimizer.step()
                scheduler.step()
                lrs.append(optimizer.param_groups[0]['lr'])
    else:
        # 按 epoch 调用的调度器
        for _ in range(epochs):
            optimizer.step()
            scheduler.step()
            lrs.append(optimizer.param_groups[0]['lr'])
    return lrs

schedulers = {
    'StepLR(step=30, gamma=0.1)':
        lambda opt: optim.lr_scheduler.StepLR(opt, 30, 0.1),
    'CosineAnnealingLR':
        lambda opt: optim.lr_scheduler.CosineAnnealingLR(opt, T_max=100),
    'ExponentialLR(gamma=0.95)':
        lambda opt: optim.lr_scheduler.ExponentialLR(opt, 0.95),
    'CosineWarmRestarts(T0=25)':
        lambda opt: optim.lr_scheduler.CosineAnnealingWarmRestarts(opt, T_0=25),
}

plt.figure(figsize=(12, 5))
for name, fn in schedulers.items():
    plt.plot(simulate_lr(fn), label=name)

plt.xlabel('Epoch')
plt.ylabel('Learning Rate')
plt.title('PyTorch LR Scheduler Comparison')
plt.legend()
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('lr_schedulers.png', dpi=150)
plt.show()

8. 完整训练模板

集成了预热 + 余弦退火、模型保存与恢复的生产级训练模板:

实例

import torch
import torch.nn as nn
import torch.optim as optim
from torch.optim.lr_scheduler import SequentialLR, LinearLR, CosineAnnealingLR

# 超参数
EPOCHS       = 100
WARMUP_EPOCHS = 5
BASE_LR      = 1e-3
MIN_LR       = 1e-6
SAVE_PATH    = 'best_model.pth'

device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

# 模型 / 优化器 / 调度器
model     = MyModel().to(device)
optimizer = optim.AdamW(model.parameters(), lr=BASE_LR, weight_decay=1e-4)
criterion = nn.CrossEntropyLoss(label_smoothing=0.1)

# 预热 5 epoch + 余弦退火 95 epoch
warmup_sched = LinearLR(optimizer, start_factor=0.1, total_iters=WARMUP_EPOCHS)
cosine_sched = CosineAnnealingLR(optimizer, T_max=EPOCHS - WARMUP_EPOCHS, eta_min=MIN_LR)
scheduler    = SequentialLR(optimizer, [warmup_sched, cosine_sched], milestones=[WARMUP_EPOCHS])

# 恢复检查点
start_epoch = 0
best_acc    = 0.0

try:
    ckpt = torch.load(SAVE_PATH, map_location=device)
    model.load_state_dict(ckpt['model'])
    optimizer.load_state_dict(ckpt['optimizer'])
    scheduler.load_state_dict(ckpt['scheduler'])
    start_epoch = ckpt['epoch'] + 1
    best_acc    = ckpt['best_acc']
    print(f"恢复自 Epoch {start_epoch},最佳准确率 {best_acc:.4f}")
except FileNotFoundError:
    print("从头开始训练")

# 训练循环
history = {'train_loss': [], 'val_acc': [], 'lr': []}

for epoch in range(start_epoch, EPOCHS):
    # 训练
    model.train()
    total_loss = 0.0
    for inputs, labels in train_loader:
        inputs, labels = inputs.to(device), labels.to(device)
        optimizer.zero_grad()
        loss = criterion(model(inputs), labels)
        loss.backward()
        torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)  # 梯度裁剪
        optimizer.step()
        total_loss += loss.item()

    # 验证
    model.eval()
    correct = 0
    with torch.no_grad():
        for inputs, labels in val_loader:
            inputs, labels = inputs.to(device), labels.to(device)
            correct += (model(inputs).argmax(1) == labels).sum().item()

    avg_loss = total_loss / len(train_loader)
    val_acc  = correct / len(val_loader.dataset)
    cur_lr   = scheduler.get_last_lr()[0]

    # 记录 & 打印
    history['train_loss'].append(avg_loss)
    history['val_acc'].append(val_acc)
    history['lr'].append(cur_lr)
    print(f"Epoch {epoch+1:3d}/{EPOCHS} | "
          f"Loss: {avg_loss:.4f} | Acc: {val_acc:.4f} | LR: {cur_lr:.2e}")

    # 调度器更新
    scheduler.step()

    # 保存最优模型
    if val_acc > best_acc:
        best_acc = val_acc
        torch.save({
            'epoch':      epoch,
            'model':      model.state_dict(),
            'optimizer':  optimizer.state_dict(),
            'scheduler':  scheduler.state_dict(),
            'best_acc':   best_acc,
        }, SAVE_PATH)
        print(f"  保存最优模型,Acc: {best_acc:.4f}")

print(f"\n训练完成,最佳验证准确率: {best_acc:.4f}")

9. 调度器选择指南

按任务类型推荐

场景推荐调度器理由
快速实验 / 原型验证ReduceLROnPlateau无需调超参,自适应衰减
图像分类(固定 epoch)CosineAnnealingLR平滑衰减,收敛质量好
Transformer / BERTLinearLR + CosineAnnealingLR预热必不可少
资源有限,快速训练OneCycleLR1/5 时间达同等精度
大模型精调(fine-tune)LinearLR(预热) + ConstantLR低学习率稳定微调
不确定 epoch 数ReduceLROnPlateau自动响应,不依赖固定节奏
目标检测(YOLO/Faster-RCNN)MultiStepLR / OneCycleLR有明确衰减点或追求速度
希望跳出局部最优CosineAnnealingWarmRestarts周期重启探索参数空间

常见配置组合

实例

# 组合一:Warmup + 余弦退火(Transformer 标配)
SequentialLR([LinearLR(5 epochs), CosineAnnealingLR(95 epochs)])

# 组合二:监控验证集 + 自动衰减(通用首选)
ReduceLROnPlateau(mode='min', patience=5, factor=0.5)

# 组合三:OneCycleLR(快速训练首选)
OneCycleLR(max_lr=0.1, total_steps=total_steps, pct_start=0.3)

# 组合四:传统阶梯衰减(CV 经典)
MultiStepLR(milestones=[30, 60, 90], gamma=0.1)

注意事项速查

问题解决方法
调度器没有生效检查 scheduler.step() 是否在 optimizer.step() 之后 调用
CyclicLR / OneCycleLR 没变化这两个按 batch 调用,不是按 epoch
ReduceLROnPlateau 不触发检查 mode 是否设对(loss 用 'min',accuracy 用 'max'
恢复训练后 LR 不对检查是否保存并加载了 scheduler.state_dict()
多参数组 LR 设置使用 LambdaLR 传入列表,每个参数组一个 lambda