趋近智
随机梯度下降 (SGD)、小批量梯度下降和动量更新的实际表现如何?通过在一个具体任务上观察它们的表现,有助于对它们的优缺点形成直观认识。
我们将设置一个简单的回归问题,并使用这些不同的优化策略训练一个基础的线性模型。我们的目的是观察收敛速度和学习过程平滑度的差异。
首先,让我们定义一个简单的合成数据集。我们将生成遵循线性关系并带有一定噪声的数据,这是一种常见的情况。
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import TensorDataset, DataLoader
import numpy as np
# 生成合成数据:y = 2x + 1 + 噪声
np.random.seed(42)
X_numpy = np.random.rand(100, 1) * 10
y_numpy = 2 * X_numpy + 1 + np.random.randn(100, 1) * 2 # 添加一些噪声
# 转换为PyTorch张量
X = torch.tensor(X_numpy, dtype=torch.float32)
y = torch.tensor(y_numpy, dtype=torch.float32)
# 定义一个简单的线性模型
model = nn.Linear(1, 1)
# 定义损失函数(均方误差)
criterion = nn.MSELoss()
# 创建数据集
dataset = TensorDataset(X, y)
我们有100个数据点,其中 y 大约等于 2x+1。我们的 nn.Linear(1, 1) 模型尝试学习权重(斜率,目标值为2)和偏置(截距,目标值为1)。我们将使用均方误差 (MSE) 作为损失函数。
我们将比较三种优化方法:
SGD 优化器,搭配标准学习率。每个实验我们需要独立的模型实例和优化器,以确保公平比较。
# 学习率
learning_rate = 0.001
momentum_factor = 0.9
num_epochs = 50
# --- 优化器 ---
# 1. 小批量梯度下降(批量大小16)
model_minibatch = nn.Linear(1, 1)
optimizer_minibatch = optim.SGD(model_minibatch.parameters(), lr=learning_rate)
dataloader_minibatch = DataLoader(dataset, batch_size=16, shuffle=True)
# 2. 随机梯度下降(批量大小1)
model_sgd = nn.Linear(1, 1)
optimizer_sgd = optim.SGD(model_sgd.parameters(), lr=learning_rate)
dataloader_sgd = DataLoader(dataset, batch_size=1, shuffle=True)
# 3. 带动量的SGD(批量大小16)
model_momentum = nn.Linear(1, 1)
optimizer_momentum = optim.SGD(model_momentum.parameters(), lr=learning_rate, momentum=momentum_factor)
# 为了公平比较,我们使用与小批量梯度下降相同的数据加载器
dataloader_momentum = DataLoader(dataset, batch_size=16, shuffle=True)
注意,对于纯粹的SGD,我们将 batch_size 设置为1。对于小批量和动量法,我们使用 batch_size=16。动量优化器与小批量优化器相同,只是多了一个 momentum=0.9 参数。
所有优化器的训练循环结构类似。我们遍历各个周期(epoch),在每个周期内,我们遍历由 DataLoader 提供的数据批次。
def train_model(model, optimizer, dataloader, criterion, epochs):
"""辅助函数,用于训练模型并记录每个周期的损失。"""
epoch_losses = []
for epoch in range(epochs):
epoch_loss = 0.0
num_batches = 0
for inputs, targets in dataloader:
# 将参数梯度归零
optimizer.zero_grad()
# 前向传播
outputs = model(inputs)
loss = criterion(outputs, targets)
# 反向传播并优化
loss.backward()
optimizer.step()
epoch_loss += loss.item()
num_batches += 1
avg_epoch_loss = epoch_loss / num_batches
epoch_losses.append(avg_epoch_loss)
# 可选:打印进度
# if (epoch + 1) % 10 == 0:
# print(f'Epoch [{epoch+1}/{epochs}], Loss: {avg_epoch_loss:.4f}')
return epoch_losses
# 训练每个模型
losses_minibatch = train_model(model_minibatch, optimizer_minibatch, dataloader_minibatch, criterion, num_epochs)
losses_sgd = train_model(model_sgd, optimizer_sgd, dataloader_sgd, criterion, num_epochs)
losses_momentum = train_model(model_momentum, optimizer_momentum, dataloader_momentum, criterion, num_epochs)
print("训练完成。")
比较这些优化器最直接的方法是绘制它们在不同周期(epoch)上的训练损失。损失越低通常表示模型拟合越好,下降越快则表示收敛越迅速。
在合成线性回归任务中,小批量梯度下降(批量大小16)、随机梯度下降(批量大小1)和带动量的SGD(批量大小16)的每个周期的平均训练损失。请注意y轴采用对数刻度。
让我们分析这张图表(请记住,由于随机初始化和数据混洗,结果可能略有不同):
这种实践比较突出了我们之前讨论的特点:
这个练习表明,即使是核心算法也有其不同的表现。尽管小批量SGD是一种主力方法,动量法通常能提供显著的加速,为我们将在下一章中考察的自适应方法铺平道路。请记住,最佳选择通常取决于具体问题、数据集和模型架构。实验是经常需要的。
这部分内容有帮助吗?
© 2026 ApX Machine Learning用心打造