什么是优化器?

在深度学习中,优化器就像一位“智能向导”,帮助模型从大量参数中找到让损失函数(比如模型预测与真实标签的差距)最小化的方向。想象你在爬山,想从山顶(高损失)走到山谷(低损失),优化器就是决定你每一步往哪走、走多远的“导航系统”。它的核心任务是更新模型参数,让模型在训练数据上的表现越来越好。

为什么需要不同的优化器?

不同的优化器针对不同的问题设计,各有优缺点:

  • SGD(随机梯度下降):最基础的优化器,每次用一个样本的梯度更新参数。
  • 优点:简单、内存占用小。
  • 缺点:收敛慢,对学习率敏感(学习率太大易震荡,太小收敛慢)。
  • 改进:加入“动量”(Momentum)可加速收敛,类似物理中的“惯性”,让参数更新更平滑。

  • Adam(自适应矩估计):目前最流行的优化器,结合了动量和自适应学习率。

  • 优点:默认参数表现好,收敛快,对学习率不敏感,适合大多数场景。
  • 特点:会根据参数的历史梯度自动调整学习率,让不同参数有不同的更新速度。

  • AdamW:Adam的改进版,加入了权重衰减(L2正则化),能有效防止过拟合。

PyTorch中的优化器概览

PyTorch的torch.optim模块提供了多种优化器,以下是初学者最常用的几种:

优化器 核心特点 适用场景
SGD 基础随机梯度下降,需手动调学习率和动量 简单模型、需要严格控制参数时
SGD+Momentum 加入动量,加速收敛,减少震荡 训练波动大的模型(如RNN)
Adam 自适应学习率+动量,默认参数效果优异 大多数深度学习任务(CNN、Transformer等)
AdamW Adam+权重衰减,避免过拟合 数据量小或模型复杂时

实战:用PyTorch实现优化器对比

下面我们用一个简单的线性回归模型,对比SGD和Adam的优化效果。目标是让模型学习到y = 2x + 3的线性关系(加入噪声模拟真实数据)。

步骤1:准备数据

生成带噪声的线性数据:

import torch
import torch.nn as nn
import torch.optim as optim
import numpy as np
import matplotlib.pyplot as plt

# 设置随机种子,保证结果可复现
torch.manual_seed(42)

# 生成100个样本,每个样本1个特征,标签为 y = 2x + 3 + 噪声
x = torch.randn(100, 1) * 10  # 输入特征
y = 2 * x + 3 + torch.randn(100, 1) * 1.5  # 真实关系+噪声

步骤2:定义模型

用PyTorch的nn.Module定义简单线性模型:

class LinearRegression(nn.Module):
    def __init__(self):
        super().__init__()
        self.linear = nn.Linear(in_features=1, out_features=1)  # 输入1维,输出1维

# 初始化两个模型,分别用SGD和Adam优化
model_sgd = LinearRegression()
model_adam = LinearRegression()

步骤3:定义损失函数和优化器

损失函数用均方误差(MSE),优化器分别用SGD和Adam:

# 损失函数:均方误差
criterion = nn.MSELoss()

# SGD优化器:学习率设为0.01,需手动控制学习率
optimizer_sgd = optim.SGD(model_sgd.parameters(), lr=0.01)

# Adam优化器:默认参数,无需手动调参
optimizer_adam = optim.Adam(model_adam.parameters(), lr=0.01)

步骤4:训练模型

训练循环:前向传播→计算损失→反向传播→更新参数

def train(optimizer, model, x, y, epochs=1000):
    losses = []
    for epoch in range(epochs):
        # 前向传播:模型预测
        pred = model(x)
        loss = criterion(pred, y)
        losses.append(loss.item())

        # 反向传播+参数更新
        optimizer.zero_grad()  # 清空梯度(避免累积)
        loss.backward()        # 计算梯度
        optimizer.step()       # 更新参数

        # 每100轮打印一次损失
        if (epoch + 1) % 100 == 0:
            print(f'Epoch {epoch+1}, Loss: {loss.item():.4f}')
    return losses

# 分别训练两个模型
losses_sgd = train(optimizer_sgd, model_sgd, x, y)
losses_adam = train(optimizer_adam, model_adam, x, y)

步骤5:对比结果

训练后,我们可以通过损失曲线最终参数观察效果:

  1. 损失曲线
   plt.figure(figsize=(10, 5))
   plt.plot(losses_sgd, label='SGD')
   plt.plot(losses_adam, label='Adam')
   plt.xlabel('Epoch')
   plt.ylabel('Loss')
   plt.title('Loss Curve Comparison')
   plt.legend()
   plt.show()

(直观感受:Adam收敛更快,损失下降更平稳;SGD可能震荡且收敛慢)

  1. 最终参数
   print("SGD训练后的参数:")
   print(f"权重: {model_sgd.linear.weight.item():.2f}, 偏置: {model_sgd.linear.bias.item():.2f}")

   print("\nAdam训练后的参数:")
   print(f"权重: {model_adam.linear.weight.item():.2f}, 偏置: {model_adam.linear.bias.item():.2f}")

(输出应为接近真实值 权重≈2,偏置≈3,Adam参数更稳定)

总结与建议

  • 初学者首选Adam:默认参数几乎适用于所有场景,收敛快且稳定性高,不用手动调学习率。
  • SGD的适用场景:若需要严格控制参数(如小数据集),或想尝试动量、学习率调整等技巧。
  • 关键技巧:训练时若损失不下降,尝试增大学习率(lr=0.1)或换AdamW(防过拟合)。

通过实战,你可以发现:优化器就像工具,没有绝对“最好”的,只有“最适合”的。先从Adam开始,再根据任务需求尝试其他优化器吧!

小夜