共计 2513 个字符,预计需要花费 7 分钟才能阅读完成。
神经网络训练中的优化难题
在训练深度神经网络时,我们常常会遇到以下几个典型问题:

- 梯度消失 / 爆炸:深层网络中梯度在反向传播时可能指数级减小或增大,导致参数更新失效
- 震荡收敛:在损失函数的陡峭区域,优化过程容易出现剧烈震荡
- 局部最优陷阱:模型可能陷入局部最优解而无法找到全局最优
- 超参数敏感:传统优化方法对学习率等超参数的选择非常敏感
这些问题的存在使得训练深度神经网络变得困难且耗时。传统优化方法如 SGD(随机梯度下降)虽然简单,但在复杂场景下表现往往不尽如人意。
Adam 优化器原理详解
Adam(Adaptive Moment Estimation)是一种自适应学习率的优化算法,它结合了 Momentum 和 RMSProp 两种方法的优点。其核心思想是为每个参数维护两个状态变量:
- 一阶矩估计(均值):$m_t = \beta_1 m_{t-1} + (1-\beta_1)g_t$
- 二阶矩估计(方差):$v_t = \beta_2 v_{t-1} + (1-\beta_2)g_t^2$
其中 $g_t$ 是当前时间步的梯度,$\beta_1$ 和 $\beta_2$ 是衰减率超参数(通常设为 0.9 和 0.999)。
为了避免在训练初期出现偏差,Adam 还引入了偏差校正项:
$\hat{m}_t = \frac{m_t}{1-\beta_1^t}$
$\hat{v}_t = \frac{v_t}{1-\beta_2^t}$
最终的参数更新公式为:
$\theta_t = \theta_{t-1} – \eta \cdot \frac{\hat{m}_t}{\sqrt{\hat{v}_t} + \epsilon}$
几何上可以理解为:Adam 为每个参数维护了一个 ” 自适应学习率 ”,在梯度方向变化剧烈的地方减小步长,在梯度方向稳定的地方增大步长。
PyTorch 实战示例
下面我们用一个完整的 PyTorch 示例来演示 Adam 优化器的使用:
import torch
import torch.nn as nn
import torch.optim as optim
from torchvision import datasets, transforms
# 1. 数据加载
transform = transforms.Compose([transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,))
])
train_dataset = datasets.MNIST('../data', train=True, download=True, transform=transform)
train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=64, shuffle=True)
# 2. 模型定义
class SimpleNN(nn.Module):
def __init__(self):
super(SimpleNN, self).__init__()
self.fc1 = nn.Linear(784, 128)
self.fc2 = nn.Linear(128, 10)
def forward(self, x):
x = x.view(-1, 784)
x = torch.relu(self.fc1(x))
x = self.fc2(x)
return x
model = SimpleNN()
# 3. 优化器初始化
optimizer = optim.Adam(model.parameters(),
lr=0.001,
betas=(0.9, 0.999),
eps=1e-08,
weight_decay=0)
criterion = nn.CrossEntropyLoss()
# 4. 训练循环
for epoch in range(10):
for batch_idx, (data, target) in enumerate(train_loader):
# 梯度清零
optimizer.zero_grad()
# 前向传播
output = model(data)
loss = criterion(output, target)
# 反向传播
loss.backward()
# 参数更新
optimizer.step()
if batch_idx % 100 == 0:
print(f'Epoch: {epoch} | Batch: {batch_idx} | Loss: {loss.item():.4f}')
在这个示例中,有几个关键点需要注意:
betas参数控制一阶和二阶矩估计的衰减率eps是一个很小的数,用于防止除以零- 每次迭代前必须调用
zero_grad()清除梯度 step()方法执行实际的参数更新
调参经验指南
根据实践经验,Adam 优化器的参数设置有以下建议:
- 学习率(lr):通常设置为 0.001,这是一个很好的起点。对于更大的模型可能需要更小的学习率
- 批大小(batch size):一般选择 32-256 之间,需要考虑显存限制
- $\beta_1$:保持默认值 0.9 即可
- $\beta_2$:保持默认值 0.999 即可
- $\epsilon$:除非有特殊需求,否则保持默认值 1e-8
- 权重衰减(weight decay):如果需要正则化,可以设置为 1e- 4 左右
常见错误场景
在实践中,使用 Adam 时容易犯以下几个错误:
- 忽略偏差校正:在实现 Adam 时忘记应用偏差校正项,导致训练初期表现不佳
- 误用权重衰减:将 L2 正则化直接加到损失函数中而不是通过 Adam 的 weight_decay 参数实现
- 学习率过大:虽然 Adam 对学习率不敏感,但过大的学习率仍会导致训练不稳定
性能对比实验
我们在 MNIST 数据集上对比了 Adam 和 SGD 的表现:
| 优化器 | 测试准确率(%) | 收敛所需 epoch 数 |
|---|---|---|
| SGD | 98.1 | 15 |
| Adam | 98.5 | 8 |
从结果可以看出,Adam 在更少的训练周期内达到了更高的准确率。
总结与思考
Adam 优化器因其自适应学习率的特性,在大多数深度学习任务中都能取得不错的效果。它减少了超参数调优的工作量,使训练过程更加稳定。然而,Adam 并不总是最佳选择:
- 对于某些特定的网络结构(如 RNN),可能需要其他优化方法
- 在资源受限的环境中,SGD 可能更节省内存
思考题:在什么情况下 Adam 优化器可能不如 SGD 表现好?为什么?
