优化问题#

目标#

找到使损失函数最小的参数:

$$ \theta^* = \arg\min L(\theta) $$

挑战#

  1. 非凸优化:可能存在多个局部最优
  2. 高维空间:参数数量巨大
  3. 数据量大:无法使用全部数据
  4. 梯度问题:梯度消失/爆炸

梯度下降变体#

1. 批量梯度下降(BGD)#

使用全部训练数据计算梯度:

$$ \theta \leftarrow \theta - \eta \cdot \frac{1}{m} \sum_{i=1}^{m} \nabla L(\theta; x_i, y_i) $$

特点

  • 梯度准确
  • 计算慢
  • 内存需求大

2. 随机梯度下降(SGD)#

每次使用一个样本:

$$ \theta \leftarrow \theta - \eta \cdot \nabla L(\theta; x_i, y_i) $$

特点

  • 计算快
  • 梯度噪声大
  • 收敛不稳定

3. 小批量梯度下降(Mini-batch SGD)#

使用小批量数据(最常用):

$$ \theta \leftarrow \theta - \eta \cdot \frac{1}{\text{batch\_size}} \sum_{i \in \text{batch}} \nabla L(\theta; x_i, y_i) $$

特点

  • 平衡速度和稳定性
  • 充分利用GPU并行
  • 默认选择

4. 带动量的SGD(Momentum)#

累积历史梯度:

$$ v_t = \beta \cdot v_{t-1} + \eta \cdot \nabla L(\theta_t) $$ $$ \theta_{t+1} = \theta_t - v_t $$

特点

  • 加速收敛
  • 减少震荡
  • 超参数:$\beta$ (通常0.9)

5. Nesterov加速梯度(NAG)#

在预测位置计算梯度:

$$ v_t = \beta \cdot v_{t-1} + \eta \cdot \nabla L(\theta_t - \beta \cdot v_{t-1}) $$ $$ \theta_{t+1} = \theta_t - v_t $$

特点

  • 比Momentum更快收敛
  • 减少过冲

自适应学习率算法#

1. AdaGrad#

根据历史梯度调整学习率:

$$ G_t = G_{t-1} + (\nabla L_t)^2 $$ $$ \theta_{t+1} = \theta_t - \frac{\eta}{\sqrt{G_t + \varepsilon}} \cdot \nabla L_t $$

特点

  • 自动降低学习率
  • 适合稀疏梯度
  • 问题:学习率可能过小

2. RMSProp#

使用指数移动平均:

$$ G_t = \beta \cdot G_{t-1} + (1-\beta) \cdot (\nabla L_t)^2 $$ $$ \theta_{t+1} = \theta_t - \frac{\eta}{\sqrt{G_t + \varepsilon}} \cdot \nabla L_t $$

特点

  • 解决AdaGrad学习率衰减过快
  • 适合非平稳目标

3. Adam(Adaptive Moment Estimation)#

结合动量和自适应学习率:

$$ m_t = \beta_1 \cdot m_{t-1} + (1-\beta_1) \cdot \nabla L_t \quad \text{(一阶矩估计)} $$ $$ v_t = \beta_2 \cdot v_{t-1} + (1-\beta_2) \cdot (\nabla L_t)^2 \quad \text{(二阶矩估计)} $$

偏差修正

$$ \hat{m}_t = \frac{m_t}{1 - \beta_1^t}, \quad \hat{v}_t = \frac{v_t}{1 - \beta_2^t} $$

更新参数

$$ \theta_{t+1} = \theta_t - \frac{\eta}{\sqrt{\hat{v}_t + \varepsilon}} \cdot \hat{m}_t $$

超参数

  • $\beta_1 = 0.9$ :一阶矩衰减率
  • $\beta_2 = 0.999$ :二阶矩衰减率
  • $\varepsilon = 10^{-8}$ :防止除零

特点

  • 最常用 的优化器
  • 自适应学习率
  • 对超参数不敏感

4. AdamW#

Adam的改进版本,解耦权重衰减:

$$ \theta_{t+1} = \theta_t - \eta \cdot \left(\frac{\hat{m}_t}{\sqrt{\hat{v}_t + \varepsilon}} + \lambda \cdot \theta_t\right) $$

特点

  • 更好的泛化性能
  • 权重衰减独立于梯度

5. 其他变体#

  • AdaDelta:无需学习率
  • Adamax:Adam的无穷范数版本
  • Nadam:结合NAG和Adam

学习率调度#

1. 固定学习率#

最简单,但可能不是最优。

2. 阶梯衰减(Step Decay)#

每隔固定epoch降低学习率:

scheduler = StepLR(optimizer, step_size=30, gamma=0.1)
# 每30个epoch,学习率乘以0.1

3. 指数衰减#

按指数降低学习率:

scheduler = ExponentialLR(optimizer, gamma=0.95)
# 每个epoch,学习率乘以0.95

4. 余弦退火(Cosine Annealing)#

按余弦函数降低学习率:

scheduler = CosineAnnealingLR(optimizer, T_max=100)
# 学习率按余弦函数从最大值降到0

5. 自适应调度#

根据验证损失调整:

scheduler = ReduceLROnPlateau(optimizer, mode='min', factor=0.5, patience=10)
# 验证损失10个epoch不下降,学习率乘以0.5

6. Warm-up#

训练初期逐渐增加学习率:

scheduler = LambdaLR(optimizer, lr_lambda=lambda epoch: epoch / 10)
# 前10个epoch线性增加学习率

梯度问题处理#

梯度消失#

问题:深层网络中梯度变得非常小。

解决方案

  1. 使用ReLU等激活函数
  2. 残差连接(ResNet)
  3. 批量归一化
  4. 梯度裁剪

梯度爆炸#

问题:梯度变得非常大。

解决方案

  1. 梯度裁剪
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
  1. 权重初始化(Xavier、He)
  2. 批量归一化

梯度检查#

def check_gradients(model):
    """检查梯度是否正常"""
    for name, param in model.named_parameters():
        if param.grad is not None:
            grad_norm = param.grad.norm().item()
            if grad_norm > 100:
                print(f"Warning: Large gradient in {name}: {grad_norm}")
            elif grad_norm < 1e-6:
                print(f"Warning: Small gradient in {name}: {grad_norm}")

代码实现#

基础优化器#

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

model = nn.Sequential(
    nn.Linear(784, 128),
    nn.ReLU(),
    nn.Linear(128, 10)
)

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

# Adam
optimizer = optim.Adam(model.parameters(), lr=0.001, betas=(0.9, 0.999))

# AdamW
optimizer = optim.AdamW(model.parameters(), lr=0.001, weight_decay=0.01)

学习率调度器#

# 阶梯衰减
scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=30, gamma=0.1)

# 余弦退火
scheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=100)

# 自适应
scheduler = optim.lr_scheduler.ReduceLROnPlateau(
    optimizer, mode='min', factor=0.5, patience=10
)

# 训练循环
for epoch in range(epochs):
    train_loss = train_one_epoch()
    val_loss = validate()
    
    # 更新学习率
    scheduler.step()  # 或 scheduler.step(val_loss) 对于ReduceLROnPlateau
    
    print(f"Epoch {epoch}, LR: {scheduler.get_last_lr()[0]:.6f}")

Warm-up实现#

class WarmupScheduler:
    def __init__(self, optimizer, warmup_epochs, base_lr):
        self.optimizer = optimizer
        self.warmup_epochs = warmup_epochs
        self.base_lr = base_lr
        self.current_epoch = 0
    
    def step(self):
        self.current_epoch += 1
        if self.current_epoch <= self.warmup_epochs:
            lr = self.base_lr * (self.current_epoch / self.warmup_epochs)
            for param_group in self.optimizer.param_groups:
                param_group['lr'] = lr

梯度裁剪#

# 方法1:按范数裁剪
max_norm = 1.0
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm)

# 方法2:按值裁剪
clip_value = 1.0
torch.nn.utils.clip_grad_value_(model.parameters(), clip_value)

# 完整训练循环
for epoch in range(epochs):
    for batch in dataloader:
        optimizer.zero_grad()
        loss = compute_loss(batch)
        loss.backward()
        
        # 梯度裁剪
        torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
        
        optimizer.step()

优化器比较#

def compare_optimizers(model_class, train_data, epochs=10):
    optimizers = {
        'SGD': optim.SGD(model.parameters(), lr=0.01),
        'SGD+Momentum': optim.SGD(model.parameters(), lr=0.01, momentum=0.9),
        'Adam': optim.Adam(model.parameters(), lr=0.001),
        'AdamW': optim.AdamW(model.parameters(), lr=0.001),
    }
    
    results = {}
    for name, optimizer in optimizers.items():
        model = model_class()
        losses = []
        for epoch in range(epochs):
            loss = train_one_epoch(model, optimizer, train_data)
            losses.append(loss)
        results[name] = losses
    
    return results

总结#

  1. 梯度下降:BGD、SGD、Mini-batch SGD
  2. 动量:加速收敛,减少震荡
  3. 自适应算法:Adam最常用,自动调整学习率
  4. 学习率调度:阶梯衰减、余弦退火等
  5. 梯度问题:梯度裁剪、权重初始化

关键要点

  • Mini-batch SGD是基础
  • Adam是最常用的优化器
  • 学习率调度很重要
  • 注意梯度消失和爆炸问题