学习率调度器理论

学习率调度器(Learning Rate Schedule)是深度学习优化的核心组件之一。通过精心设计的学习率调整策略,我们可以显著加速收敛、提升泛化性能,并训练更大规模的模型。12


1. 学习率预热(Warmup)理论

1.1 Warmup的动机

早期训练的不稳定性是引入Warmup的主要原因。在训练初期,模型面临以下挑战:

问题描述影响
梯度方差大随机初始化导致早期梯度方向不稳定参数更新方向混乱
参数尺度不一致不同层的参数处于不同尺度激活值尺度波动
自适应优化器问题Adam等动量估计未收敛自适应学习率估计不准
过参数化问题早期模型表达能力过强容易过拟合训练数据

Transformer训练的特殊挑战

Transformer架构中的自注意力机制对学习率极为敏感。Vaswani等人3指出:

  • 注意力机制的softmax输出在早期可能接近均匀分布
  • 梯度在token位置间波动剧烈
  • 需要足够的warmup步数使注意力模式稳定

1.2 线性Warmup的数学描述

线性Warmup是最常用的预热策略:

其中:

  • :初始学习率(通常很小,如
  • :预热结束后的最大学习率
  • :预热步数
  • :当前步数

Transformer标准设置

def get_linear_lr(step, d_model, warmup_steps=4000, base_lr=0.0, max_lr=2.5e-4):
    """
    Transformer原始论文的学习率调度
    
    lr = d_model^(-0.5) * min(step^(-0.5), step * warmup^(-1.5))
    """
    return d_model ** (-0.5) * min(
        (step + 1) ** (-0.5),
        (step + 1) * warmup_steps ** (-1.5)
    )
 
# 等价于:先线性warmup,然后按 step^(-0.5) 衰减

1.3 余弦Warmup

余弦Warmup结合了warmup的稳定性和余弦衰减的平滑性:

其中 为衰减周期长度。

简化形式

def cosine_warmup_schedule(step, warmup_steps, max_steps, max_lr=2.5e-4, min_lr=0.0):
    """余弦Warmup调度"""
    if step < warmup_steps:
        # 线性warmup
        return max_lr * step / warmup_steps
    else:
        # 余弦衰减
        progress = (step - warmup_steps) / (max_steps - warmup_steps)
        return min_lr + 0.5 * (max_lr - min_lr) * (1 + np.cos(np.pi * progress))

1.4 Warmup的理论保证

收敛性分析

Warmup的核心理论支撑来自于优化器的收敛性分析。Reddi等人4指出Adam在早期可能不收敛,需要warmup来稳定二阶矩估计。

梯度范数演化

经验观察表明,经过warmup后,梯度范数趋于稳定:

这使得后续的自适应学习率调整更加可靠。

自适应学习率稳定性

对于Adam,优化器状态满足:

经过 步warmup后:

时, 收敛到真实方差的无偏估计。


2. 余弦退火(Cosine Annealing)理论

2.1 数学形式

Loshchilov & Hutter5提出的余弦退火形式:

其中:

  • :最小学习率(通常为
  • :最大学习率
  • :总训练步数或衰减周期
  • :当前步数(

可视化

η
η_max ┤                    ╭──────────────╮
      │                   ╱              ╲
      │                  ╱                ╲
      │                 ╱                  ╲
      │                ╱                    ╲
η_min ┤───────────────╱───────────────────────╲───────
      0              T/2                     T       step

2.2 收敛性分析

与固定学习率的对比

考虑一个 -强凸函数 。使用学习率 的SGD更新为:

固定学习率 的收敛界

其中 是梯度方差, 为最优解。

余弦退火的渐近行为

时,余弦退火的学习率均值为:

这意味着有效学习率约为最大学习率的一半,与 “learning rate = 1/√(d)” 的经典设置有关。

2.3 Warmup-Restart策略(SGDR)

SGDR(Stochastic Gradient Descent with Warm Restarts)5通过周期性重启来探索不同的局部最小值:

其中 是第 个周期的结束时间。

多周期设置

class CosineAnnealingWarmRestarts:
    def __init__(self, max_lr, min_lr, T_0, T_mult=1):
        self.max_lr = max_lr
        self.min_lr = min_lr
        self.T_0 = T_0
        self.T_mult = T_mult
        self.T_i = T_0
        
    def get_lr(self, step, T_cur):
        """T_cur: 当前周期已完成的步数"""
        return self.min_lr + 0.5 * (self.max_lr - self.min_lr) * \
               (1 + np.cos(np.pi * T_cur / self.T_i))
    
    def step(self, step):
        """更新周期计数"""
        while step >= self.T_i:
            step -= self.T_i
            self.T_i *= self.T_mult  # 周期长度递增
        return self.get_lr(step, 0)

2.4 余弦退火与泛化:Sharp/Flat Minima

关键假设:余弦退火倾向于收敛到平坦最小值(Flat Minimum),这与更好的泛化性能相关。

理论分析

Hessian特征值与泛化的关系:

最小值类型Hessian 泛化性能余弦退火倾向
Sharp较差
Flat较好

直觉解释

  1. 高学习率阶段:模型在损失曲面上快速移动,探索更多区域
  2. 低学习率阶段:模型精细调整,进入平坦谷底
  3. 周期性重启:允许模型跳出sharp minima的邻域

实验证据

Izmailov等人6证明,使用SWA(Stochastic Weight Averaging)和余弦退火可以获得更平坦的最小值:

class SWA:
    def __init__(self, model, swa_start, swa_end):
        self.model = model
        self.swa_start = swa_start
        self.swa_end = swa_end
        self.swa_weights = None
        self.count = 0
        
    def update(self, step):
        if self.swa_start <= step <= self.swa_end:
            if self.swa_weights is None:
                self.swa_weights = {k: v.clone() 
                                   for k, v in self.model.named_parameters()}
            else:
                for k, v in self.model.named_parameters():
                    self.swa_weights[k] += v
            self.count += 1
    
    def average(self):
        for k, v in self.swa_weights.items():
            self.model.state_dict()[k] = v / self.count

3. 阶梯式衰减与指数衰减

3.1 阶梯衰减(Step Decay)

数学形式

其中 是衰减系数, 是衰减间隔。

常见设置

def step_decay_schedule(step, initial_lr=0.1, step_size=30, gamma=0.1):
    """阶梯衰减:每30个epoch学习率衰减为原来的1/10"""
    num_decays = step // step_size
    return initial_lr * (gamma ** num_decays)
 
# 等效PyTorch实现
scheduler = torch.optim.lr_scheduler.StepLR(
    optimizer, 
    step_size=30,      # 每30个epoch
    gamma=0.1          # 学习率乘以0.1
)

衰减时机分析

衰减时机效果适用场景
每N步简单稳定大多数场景
每N个epoch与batch size对齐小规模训练
验证集恶化时自适应防止过拟合

3.2 指数衰减(Exponential Decay)

数学形式

其中 是衰减率。

连续时间极限

当步长 时:

与阶梯衰减的关系

阶梯衰减是指数衰减的离散近似。当 时,两者在对数尺度上几乎重合。

3.3 多项式衰减

数学形式

其中 控制衰减曲线的形状。

行为特点
线性平稳下降
缓衰减早期下降慢
快衰减早期下降快
def polynomial_decay_schedule(step, total_steps, initial_lr, alpha=1.0):
    """多项式衰减"""
    progress = min(step / total_steps, 1.0)
    return initial_lr * (1 - progress) ** alpha

3.4 Step-wise vs Continuous衰减对比

特性Step-wise衰减Continuous衰减
形式离散跳跃连续平滑
收敛速度较快(后期可能震荡)稳定
泛化性能可能陷入局部最优更平滑
实现复杂度简单中等
典型应用传统CNNTransformer/LLM

理论分析

连续衰减(如余弦)在损失曲面上产生更规则的收敛轨迹:

这对应于阻尼振荡形式的学习率变化,有助于跳出局部最优。


4. 周期性学习率理论

4.1 Cyclic Learning Rate (CLR)

Smith提出的CLR7通过周期性振荡学习率来加速训练:

其中 是周期长度, 是周期索引。

三角CLR(最常用形式):

def triangular_lr(step, step_size, max_lr, min_lr):
    """
    三角学习率调度
    step_size: 半周期长度
    """
    cycle = np.floor(1 + step / (2 * step_size))
    x = np.abs(step / step_size - 2 * cycle + 1)
    return min_lr + (max_lr - min_lr) * np.maximum(0, 1 - x)
 
# 等效PyTorch实现
scheduler = torch.optim.lr_scheduler.CyclicLR(
    optimizer,
    base_lr=1e-6,
    max_lr=1e-3,
    step_size_up=2000,
    mode='triangular'
)

4.2 周期性退火与跳过的联系

退火周期与跳过(Skip)的类比

周期性学习率可以被视为在优化轨迹上人为引入”跳过”(jump)操作:

其中 是由学习率突变引起的额外位移。

Langevin动力学的类比

在物理启发的优化中,学习率周期类似于热噪声周期:

其中 模拟温度,周期性降低 类似于周期性降低学习率。

4.3 周期性学习率对损失曲面的探索分析

损失曲面与CLR

CLR通过以下机制探索损失曲面:

  1. 高学习率阶段:允许参数跨越能量壁垒
  2. 低学习率阶段:精细收敛到局部最优
  3. 周期切换:重新定位,探索新区域

理论框架

考虑梯度下降的连续近似:

周期性变化时,有效的梯度方向被周期性缩放,这等价于在参数空间中引入周期性扰动。

收敛保证

对于凸函数,CLR满足:

其中 是平均学习率, 是梯度方差。


5. 逆缩放与缩放定律

5.1 学习率随模型规模的缩放规律

经典结果

对于宽度为 的神经网络,经典理论建议:

Transformer的缩放规则

GPT-3论文8使用的缩放规则:

其中 (某些设置下 )。

5.2 的理论推导

从Edge of Stability角度

训练动力学:Edge of Stability理论中,最优学习率满足:

其中 是Hessian的最大特征值。

Hessian特征值与模型规模

对于过参数化网络,Hessian的最大特征值满足:

其中 是参数数量, 是数据维度。

推导

  1. 随机初始化的权重矩阵谱范数:
  2. 梯度方差缩放:
  3. Hessian特征值缩放:

因此:

有固定比例关系时(如Transformer),简化为:

5.3 计算最优训练中的学习率调度

Chinchilla缩放定律9

在计算最优训练中,模型规模 与训练token数 满足:

这意味着更大的模型需要更多token,但增加的比例小于线性。

学习率与训练进度的协调

计算最优训练要求学习率调度适应训练进程:

def compute_optimal_schedule(step, model_size, total_steps, base_lr=1e-3):
    """
    基于计算最优原则的学习率调度
    
    大模型在早期需要更保守的学习率,
    但在训练后期可以保持较高的学习率
    """
    # 基础缩放
    lr_scale = (model_size / 1e9) ** (-0.5)
    
    # 训练阶段调整
    progress = step / total_steps
    
    if progress < 0.1:
        # Warmup阶段:更保守
        warmup_factor = progress / 0.1
        return base_lr * lr_scale * warmup_factor * 0.5
    else:
        # 主训练阶段:余弦衰减
        decay_progress = (progress - 0.1) / 0.9
        cosine_factor = 0.5 * (1 + np.cos(np.pi * decay_progress))
        return base_lr * lr_scale * (cosine_factor + 0.1)

6. 实践指南

6.1 PyTorch实现代码

完整的调度器实现

import numpy as np
import torch
from torch.optim.lr_scheduler import _LRScheduler
 
class WarmupCosineScheduler(_LRScheduler):
    """
    带有线性warmup的余弦退火调度器
    
    学习率曲线:
    - 0 ~ warmup_steps: 线性warmup
    - warmup_steps ~ total_steps: 余弦退火
    """
    def __init__(
        self,
        optimizer: torch.optim.Optimizer,
        warmup_steps: int,
        total_steps: int,
        max_lr: float = 2.5e-4,
        min_lr: float = 0.0,
        last_epoch: int = -1
    ):
        self.warmup_steps = warmup_steps
        self.total_steps = total_steps
        self.max_lr = max_lr
        self.min_lr = min_lr
        super().__init__(optimizer, last_epoch)
    
    def get_lr(self):
        if self.last_epoch < self.warmup_steps:
            # 线性warmup阶段
            warmup_factor = self.last_epoch / self.warmup_steps
            return [self.max_lr * warmup_factor for _ in self.base_lrs]
        else:
            # 余弦退火阶段
            progress = (self.last_epoch - self.warmup_steps) / \
                       (self.total_steps - self.warmup_steps)
            cosine_factor = 0.5 * (1 + np.cos(np.pi * progress))
            return [self.min_lr + (self.max_lr - self.min_lr) * cosine_factor
                    for _ in self.base_lrs]
 
 
class WarmupLinearDecayScheduler(_LRScheduler):
    """
    带有线性warmup的线性衰减调度器(GPT风格)
    
    适用于GPT系列模型的训练
    """
    def __init__(
        self,
        optimizer: torch.optim.Optimizer,
        warmup_steps: int,
        total_steps: int,
        max_lr: float = 6e-4,
        min_lr: float = 0.0,
        last_epoch: int = -1
    ):
        self.warmup_steps = warmup_steps
        self.total_steps = total_steps
        self.max_lr = max_lr
        self.min_lr = min_lr
        super().__init__(optimizer, last_epoch)
    
    def get_lr(self):
        if self.last_epoch < self.warmup_steps:
            # Warmup阶段
            warmup_factor = self.last_epoch / self.warmup_steps
            return [self.max_lr * warmup_factor for _ in self.base_lrs]
        else:
            # 线性衰减阶段
            progress = (self.last_epoch - self.warmup_steps) / \
                       (self.total_steps - self.warmup_steps)
            decay_factor = 1 - progress
            return [self.min_lr + (self.max_lr - self.min_lr) * decay_factor
                    for _ in self.base_lrs]
 
 
class PolynomialDecayScheduler(_LRScheduler):
    """
    多项式衰减调度器(LLaMA风格)
    
    特点:
    - 更长的warmup
    - 余弦阶段后可接多项式衰减
    """
    def __init__(
        self,
        optimizer: torch.optim.Optimizer,
        warmup_steps: int,
        total_steps: int,
        max_lr: float,
        min_lr: float = 0.0,
        power: float = 1.0,  # 多项式指数
        lr_decay_steps: int = None,
        last_epoch: int = -1
    ):
        self.warmup_steps = warmup_steps
        self.total_steps = total_steps
        self.max_lr = max_lr
        self.min_lr = min_lr
        self.power = power
        self.lr_decay_steps = lr_decay_steps or total_steps
        super().__init__(optimizer, last_epoch)
    
    def get_lr(self):
        if self.last_epoch < self.warmup_steps:
            # Warmup阶段
            warmup_factor = self.last_epoch / self.warmup_steps
            return [self.max_lr * warmup_factor for _ in self.base_lrs]
        elif self.last_epoch < self.total_steps:
            # 多项式衰减阶段
            decay_steps = self.last_epoch - self.warmup_steps
            total_decay_steps = self.total_steps - self.warmup_steps
            progress = decay_steps / total_decay_steps
            decay_factor = (1 - progress) ** self.power
            return [self.min_lr + (self.max_lr - self.min_lr) * decay_factor
                    for _ in self.base_lrs]
        else:
            return [self.min_lr for _ in self.base_lrs]

使用示例

import torch
import torch.nn as nn
 
# 模型和优化器
model = nn.Sequential(
    nn.Linear(768, 3072),
    nn.GELU(),
    nn.Linear(3072, 768)
)
 
optimizer = torch.optim.AdamW(
    model.parameters(),
    lr=1e-4,
    weight_decay=0.1
)
 
# 调度器配置
total_steps = 100000
warmup_steps = 2000
 
# 方法1:使用自定义调度器
scheduler = WarmupCosineScheduler(
    optimizer,
    warmup_steps=warmup_steps,
    total_steps=total_steps,
    max_lr=2e-4,
    min_lr=1e-5
)
 
# 方法2:使用PyTorch内置调度器组合
# Step 1: Warmup
warmup_scheduler = torch.optim.lr_scheduler.LinearLR(
    optimizer,
    start_factor=0.1,  # 从10%的lr开始
    end_factor=1.0,
    total_iters=warmup_steps
)
 
# Step 2: 余弦退火
cosine_scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(
    optimizer,
    T_max=total_steps - warmup_steps,
    eta_min=1e-5
)
 
# Step 3: 链接调度器
scheduler = torch.optim.lr_scheduler.SequentialLR(
    optimizer,
    schedulers=[warmup_scheduler, cosine_scheduler],
    milestones=[warmup_steps]
)
 
# 训练循环
for step in range(total_steps):
    # 前向传播
    loss = model(inputs)
    
    # 反向传播
    loss.backward()
    
    # 优化器更新
    optimizer.step()
    scheduler.step()
    
    optimizer.zero_grad()

6.2 调度策略选择建议

场景推荐调度策略原因
小数据集 (< 100K样本)Step Decay简单有效,易调参
图像分类 (ResNet)Cosine Annealing平滑收敛,泛化好
Transformer预训练Warmup + Cosine标准配置,稳定训练
LLM微调Linear Warmup + Decay保留预训练知识
强化学习Cyclic LR探索-利用平衡
大批量训练Layer-wise LR Decay底层学习率更低
高效微调 (LoRA)常数 LR 或 Cosine避免过大更新

Layer-wise Learning Rate Decay

def layer_wise_lr_decay(optimizer, model, decay_rate=0.9):
    """
    底层使用更小的学习率,顶层使用更大的学习率
    
    适用于预训练模型微调
    """
    no_decay = ["bias", "LayerNorm.weight", "layernorm.weight"]
    
    optimizer_grouped_parameters = []
    for name, param in model.named_parameters():
        if not param.requires_grad:
            continue
            
        # 判断层数
        if "embeddings" in name:
            layer_num = 0
        else:
            layer_num = int(re.search(r'layer\.(\d+)', name).group(1))
        
        depth = model.config.num_hidden_layers - layer_num
        lr_scale = decay_rate ** depth
        
        if any(nd in name for nd in no_decay):
            optimizer_grouped_parameters.append({
                "params": [param],
                "lr": optimizer.defaults['lr'] * lr_scale,
                "weight_decay": 0.0
            })
        else:
            optimizer_grouped_parameters.append({
                "params": [param],
                "lr": optimizer.defaults['lr'] * lr_scale,
                "weight_decay": optimizer.defaults.get('weight_decay', 0.0)
            })
    
    return optimizer.__class__(optimizer_grouped_parameters, **optimizer.defaults)

6.3 与优化器的组合策略

AdamW + Cosine Annealing

这是Transformer训练的标准组合:

# LLaMA 2 的配置
optimizer = torch.optim.AdamW(
    model.parameters(),
    lr=3e-4,
    betas=(0.9, 0.95),
    eps=1e-8,
    weight_decay=0.1
)
 
scheduler = {
    "type": "CosineAnnealingLR",
    "params": {
        "T_max": num_training_steps,
        "eta_min": 3e-5,
        "last_epoch": -1
    }
}

SGD + Step Decay

适合传统CNN训练:

optimizer = torch.optim.SGD(
    model.parameters(),
    lr=0.1,
    momentum=0.9,
    weight_decay=5e-4
)
 
scheduler = torch.optim.lr_scheduler.MultiStepLR(
    optimizer,
    milestones=[30, 60, 90],  # 第30, 60, 90个epoch衰减
    gamma=0.1
)

Lion + Cosine

Lion优化器10使用更简单的动量形式:

# Lion优化器需要特殊的学习率调度
# 推荐使用 constant 或 cyclic LR
 
optimizer = Lion(
    model.parameters(),
    lr=1e-4,
    betas=(0.9, 0.99)
)
 
# Lion不需要warmup,可以直接使用余弦衰减
scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(
    optimizer,
    T_max=num_training_steps
)

6.4 调度器调试技巧

import matplotlib.pyplot as plt
 
def plot_lr_schedule(scheduler, total_steps, title="Learning Rate Schedule"):
    """可视化学习率调度曲线"""
    lrs = []
    for step in range(total_steps):
        scheduler.last_epoch = step
        lrs.append(scheduler.get_lr()[0])
    
    plt.figure(figsize=(12, 4))
    plt.plot(range(total_steps), lrs)
    plt.xlabel("Step")
    plt.ylabel("Learning Rate")
    plt.title(title)
    plt.grid(True)
    plt.show()
 
# 使用示例
optimizer = torch.optim.AdamW(model.parameters(), lr=1e-4)
scheduler = WarmupCosineScheduler(
    optimizer,
    warmup_steps=2000,
    total_steps=100000,
    max_lr=2e-4
)
plot_lr_schedule(scheduler, 100000)

核心公式速查

调度策略公式
线性Warmup
余弦退火
阶梯衰减
指数衰减
多项式衰减
三角CLR$\eta_t = \eta_{\min} + (\eta_{\max} - \eta_{\min})\max(0, 1 - \frac{
缩放定律

参考

相关阅读

Footnotes

  1. Goodfellow, I., Bengio, Y., & Courville, A. (2016). Deep Learning. MIT Press.

  2. Ruder, S. (2016). An overview of gradient descent optimization algorithms. arXiv:1609.04747.

  3. Vaswani, A., et al. (2017). Attention is All You Need. NeurIPS 2017.

  4. Reddi, S. J., et al. (2018). On the Convergence of Adam and Beyond. ICLR 2018.

  5. Loshchilov, I., & Hutter, F. (2017). SGDR: Stochastic Gradient Descent with Warm Restarts. ICLR 2017. 2

  6. Izmailov, P., et al. (2018). Averaging Weights Leads to Wider Optima and Better Generalization. UAI 2018.

  7. Smith, L. N. (2017). Cyclical Learning Rates for Training Neural Networks. WACV 2017.

  8. Brown, T. B., et al. (2020). Language Models are Few-Shot Learners. NeurIPS 2020.

  9. Hoffmann, J., et al. (2022). Training Compute-Optimal Large Language Models. NeurIPS 2022.

  10. Chen, S., et al. (2024). Symbolic Discovery of Optimization Algorithms. ICML 2024.