深度学习篇---Adam优化器

发布于:2025-09-05 ⋅ 阅读:(16) ⋅ 点赞:(0)

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.0
        correct = 0
        total = 0
        
        for 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 / total
        train_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 的 momentum
    • beta2:二阶矩的指数衰减率,控制学习率的自适应程度
    • 几乎不需要修改,这组默认值在绝大多数场景都表现优秀
  • weight_decay

    • 权重衰减(L2 正则化),默认0
    • 用法和 SGD 相同:过拟合时增大(1e-5 ~ 1e-3),欠拟合时减小
2. Adam 的工作原理(通俗解释)

Adam 的名字来自 "Adaptive Moment Estimation"(自适应矩估计),它做了两件聪明事:

  1. 动量(Momentum):记住过去的梯度方向,类似滚动的小球,加速收敛
  2. 自适应学习率:对更新频繁的参数(如高频特征)用小学习率,对更新少的参数用大学习率

简单说,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 优化器,尤其是当你对调参不太熟悉时,它能帮你节省大量调试时间。