深度学习篇---Adam优化器
Adam 优化器是 PyTorch 中最常用的优化器之一,它结合了 SGD+Momentum 和 RMSprop 的优点,自带自适应学习率调整功能,对初学者非常友好。下面用通俗易懂的方式讲解其原理和用法,并提供详细代码示例。
为什么 Adam 更受欢迎?
可以把各种优化器比作不同的下山方式:
- 普通 SGD 是 "步行",需要手动控制步长
- SGD+Momentum 是 "骑车",有惯性但仍需手动控速
- Adam 是 "自动驾驶汽车":能自动根据路况(梯度变化)调整速度和方向,既快又稳
Adam 的核心优势:
- 不需要手动精细调整学习率
- 收敛速度比 SGD 快
- 对噪声数据更稳健
- 适合大多数模型(CNN、RNN、Transformer 等)
完整代码示例(对比 Adam 和 SGD)
下面通过图像分类任务,直观展示 Adam 的优势:
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader
from torchvision import datasets, transforms
import matplotlib.pyplot as plt
import numpy as np# 设置随机种子,保证结果可复现
torch.manual_seed(42)
np.random.seed(42)# 1. 准备数据(使用MNIST手写数字数据集)
# 数据预处理:转为张量并标准化
transform = transforms.Compose([transforms.ToTensor(),transforms.Normalize((0.1307,), (0.3081,)) # MNIST数据集的均值和标准差
])# 加载训练集和测试集
train_dataset = datasets.MNIST(root='./data', train=True, download=True, transform=transform
)
test_dataset = datasets.MNIST(root='./data', train=False, download=True, transform=transform
)# 创建数据加载器
train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=1000, shuffle=False)# 2. 定义模型(简单CNN)
class SimpleCNN(nn.Module):def __init__(self):super().__init__()self.conv1 = nn.Conv2d(1, 32, kernel_size=3, stride=1, padding=1)self.conv2 = nn.Conv2d(32, 64, kernel_size=3, stride=1, padding=1)self.pool = nn.MaxPool2d(2, 2) # 池化层,缩小特征图尺寸self.fc1 = nn.Linear(64 * 7 * 7, 128) # 全连接层self.fc2 = nn.Linear(128, 10) # 输出层(10个类别)self.relu = nn.ReLU()def forward(self, x):x = self.pool(self.relu(self.conv1(x))) # 卷积+激活+池化x = self.pool(self.relu(self.conv2(x)))x = x.view(-1, 64 * 7 * 7) # 展平特征图x = self.relu(self.fc1(x))x = self.fc2(x)return x# 创建两个相同的模型,分别用Adam和SGD训练
model_adam = SimpleCNN()
model_sgd = SimpleCNN() # 结构相同,参数独立# 3. 定义损失函数(交叉熵损失,适合分类任务)
criterion = nn.CrossEntropyLoss()# 4. 初始化优化器
# Adam优化器(推荐参数)
optimizer_adam = optim.Adam(model_adam.parameters(),lr=0.001, # 学习率:Adam默认0.001,通常无需修改betas=(0.9, 0.999),# 动量参数:控制一阶矩和二阶矩的指数衰减率weight_decay=1e-5 # 权重衰减:防止过拟合
)# SGD优化器(作为对比)
optimizer_sgd = optim.SGD(model_sgd.parameters(),lr=0.01, # SGD通常需要更大的学习率momentum=0.9,weight_decay=1e-5
)# 5. 训练模型
def train(model, optimizer, train_loader, criterion, epochs=10):model.train() # 切换到训练模式train_losses = []train_accuracies = []for epoch in range(epochs):running_loss = 0.0correct = 0total = 0for i, (inputs, labels) in enumerate(train_loader):# 前向传播outputs = model(inputs)loss = criterion(outputs, labels)# 反向传播和优化optimizer.zero_grad() # 清空梯度loss.backward() # 计算梯度optimizer.step() # 更新参数# 统计损失和准确率running_loss += loss.item()_, predicted = torch.max(outputs.data, 1)total += labels.size(0)correct += (predicted == labels).sum().item()# 每100批次打印一次信息if i % 100 == 99:print(f'[{epoch+1}, {i+1}] loss: {running_loss/100:.3f}')running_loss = 0.0# 计算本轮的平均损失和准确率epoch_loss = running_loss / len(train_loader)epoch_acc = 100 * correct / totaltrain_losses.append(epoch_loss)train_accuracies.append(epoch_acc)print(f'Epoch {epoch+1} - Loss: {epoch_loss:.4f}, Accuracy: {epoch_acc:.2f}%')return train_losses, train_accuracies# 分别训练两个模型(5轮即可看出差异)
print("===== 用Adam训练 =====")
adam_losses, adam_accs = train(model_adam, optimizer_adam, train_loader, criterion, epochs=5)print("\n===== 用SGD训练 =====")
sgd_losses, sgd_accs = train(model_sgd, optimizer_sgd, train_loader, criterion, epochs=5)# 6. 可视化结果
plt.figure(figsize=(14, 6))# 左图:损失对比
plt.subplot(1, 2, 1)
plt.plot(range(1, 6), adam_losses, 'r-', marker='o', label='Adam')
plt.plot(range(1, 6), sgd_losses, 'b-', marker='s', label='SGD+Momentum')
plt.title('训练损失对比')
plt.xlabel('轮次')
plt.ylabel('损失值')
plt.legend()# 右图:准确率对比
plt.subplot(1, 2, 2)
plt.plot(range(1, 6), adam_accs, 'r-', marker='o', label='Adam')
plt.plot(range(1, 6), sgd_accs, 'b-', marker='s', label='SGD+Momentum')
plt.title('训练准确率对比')
plt.xlabel('轮次')
plt.ylabel('准确率 (%)')
plt.legend()plt.tight_layout()
plt.show()
关键知识点解析
1. Adam 优化器的核心参数
Adam 的参数比 SGD 多,但大部分情况下用默认值即可:
lr
(学习率):- 默认值
0.001
,这是 Adam 最推荐的初始值 - 调整逻辑:若损失下降慢,可增大到
0.003
;若震荡大,可减小到0.0001
- 注意:Adam 对学习率的敏感度比 SGD 低,不用频繁调整
- 默认值
betas
:- 是一个元组
(beta1, beta2)
,默认(0.9, 0.999)
beta1
:一阶矩(动量)的指数衰减率,类似 SGD 的 momentumbeta2
:二阶矩的指数衰减率,控制学习率的自适应程度- 几乎不需要修改,这组默认值在绝大多数场景都表现优秀
- 是一个元组
weight_decay
:- 权重衰减(L2 正则化),默认
0
- 用法和 SGD 相同:过拟合时增大(
1e-5 ~ 1e-3
),欠拟合时减小
- 权重衰减(L2 正则化),默认
2. Adam 的工作原理(通俗解释)
Adam 的名字来自 "Adaptive Moment Estimation"(自适应矩估计),它做了两件聪明事:
- 动量(Momentum):记住过去的梯度方向,类似滚动的小球,加速收敛
- 自适应学习率:对更新频繁的参数(如高频特征)用小学习率,对更新少的参数用大学习率
简单说,Adam 会根据参数的 "历史表现" 动态调整学习策略,不需要人工干预。
3. 代码运行结果分析
从对比图中能明显看到 Adam 的优势:
- 损失下降更快:前 2 轮就显著低于 SGD
- 准确率提升更快:5 轮后通常比 SGD 高 5%~10%
- 训练更稳定:损失曲线波动更小
4. 实际使用技巧
- 新手首选:不知道用什么优化器时,直接用
Adam(lr=0.001)
,80% 的任务都能跑通 - 学习率调整:若要调参,优先调整
lr
,范围通常在1e-4 ~ 1e-3
之间 - 配合调度器:虽然 Adam 自带自适应,但后期用
ReduceLROnPlateau
调度器仍能提升效果 - 大模型训练:当训练 BERT 等大模型时,建议用
AdamW
(Adam 的改进版,权重衰减更合理)
5. 适用场景
Adam 几乎适用于所有深度学习任务,尤其推荐:
- 初学者入门(调参简单)
- 复杂模型(如 CNN、RNN、Transformer)
- 数据量中等或较小的场景
- 希望快速看到训练效果的场景
通过这个示例,你可以看到 Adam 相比 SGD 的明显优势。在实际项目中,建议优先尝试 Adam 优化器,尤其是当你对调参不太熟悉时,它能帮你节省大量调试时间。