优化问题#
目标#
找到使损失函数最小的参数:
$$ \theta^* = \arg\min L(\theta) $$挑战#
- 非凸优化:可能存在多个局部最优
- 高维空间:参数数量巨大
- 数据量大:无法使用全部数据
- 梯度问题:梯度消失/爆炸
梯度下降变体#
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.13. 指数衰减#
按指数降低学习率:
scheduler = ExponentialLR(optimizer, gamma=0.95)
# 每个epoch,学习率乘以0.954. 余弦退火(Cosine Annealing)#
按余弦函数降低学习率:
scheduler = CosineAnnealingLR(optimizer, T_max=100)
# 学习率按余弦函数从最大值降到05. 自适应调度#
根据验证损失调整:
scheduler = ReduceLROnPlateau(optimizer, mode='min', factor=0.5, patience=10)
# 验证损失10个epoch不下降,学习率乘以0.56. Warm-up#
训练初期逐渐增加学习率:
scheduler = LambdaLR(optimizer, lr_lambda=lambda epoch: epoch / 10)
# 前10个epoch线性增加学习率梯度问题处理#
梯度消失#
问题:深层网络中梯度变得非常小。
解决方案:
- 使用ReLU等激活函数
- 残差连接(ResNet)
- 批量归一化
- 梯度裁剪
梯度爆炸#
问题:梯度变得非常大。
解决方案:
- 梯度裁剪:
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)- 权重初始化(Xavier、He)
- 批量归一化
梯度检查#
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总结#
- 梯度下降:BGD、SGD、Mini-batch SGD
- 动量:加速收敛,减少震荡
- 自适应算法:Adam最常用,自动调整学习率
- 学习率调度:阶梯衰减、余弦退火等
- 梯度问题:梯度裁剪、权重初始化
关键要点:
- Mini-batch SGD是基础
- Adam是最常用的优化器
- 学习率调度很重要
- 注意梯度消失和爆炸问题