随机梯度下降(SGD)基础:从原理到实战优化指南

1次阅读
没有评论

共计 2261 个字符,预计需要花费 6 分钟才能阅读完成。

image.webp

核心概念

随机梯度下降(Stochastic Gradient Descent, SGD)是深度学习中最基础的优化算法之一。它的核心思想是通过迭代更新模型参数,最小化损失函数。与批量梯度下降(Batch Gradient Descent)不同,SGD 每次只使用一个样本(或一小批样本)来计算梯度,从而大大提高了计算效率。

随机梯度下降 (SGD) 基础:从原理到实战优化指南

数学上,SGD 的权重更新过程可以表示为:

$$w_{t+1} = w_t – \eta \nabla J(w_t; x_i, y_i)$$

其中:
– $w_t$ 是第 $t$ 次迭代的权重
– $\eta$ 是学习率
– $\nabla J(w_t; x_i, y_i)$ 是损失函数 $J$ 关于权重 $w_t$ 在样本 $(x_i, y_i)$ 上的梯度

与批量梯度下降相比,SGD 的计算量更小,但梯度估计的方差较大,可能导致收敛过程不稳定。

痛点分析

虽然 SGD 简单高效,但在实际应用中常面临以下问题:

  1. 学习率敏感:学习率过大可能导致震荡甚至发散,过小则收敛速度慢。
  2. 震荡收敛:由于每次更新基于单个或少量样本,梯度估计噪声大,导致收敛路径曲折。
  3. 局部最优:在高维非凸优化问题中,容易陷入局部最优或鞍点。
  4. 梯度消失 / 爆炸:在深层网络中,梯度可能指数级衰减或增长,导致训练困难。

技术方案

为了解决上述问题,研究者提出了多种 SGD 变种算法:

  1. 动量法(Momentum):引入动量项来加速收敛并减少震荡。更新公式为:

$$v_{t+1} = \gamma v_t + \eta \nabla J(w_t)$$
$$w_{t+1} = w_t – v_{t+1}$$

其中 $\gamma$ 是动量系数,通常设为 0.9。

  1. Adagrad:自适应调整学习率,对频繁更新的参数使用较小学习率,反之亦然。

  2. Adam:结合动量和自适应学习率,是目前最常用的优化器之一。

代码示例

以下是用 PyTorch 实现带学习率衰减的 SGD 的完整代码:

import torch
import torch.nn as nn
import torch.optim as optim
from torchvision import datasets, transforms
from torch.utils.data import DataLoader

# 数据加载
transform = transforms.Compose([transforms.ToTensor(),
    transforms.Normalize((0.1307,), (0.3081,))
])

train_dataset = datasets.MNIST('./data', train=True, download=True, transform=transform)
train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True)

# 模型定义
class SimpleNet(nn.Module):
    def __init__(self):
        super(SimpleNet, 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 = SimpleNet()

# 优化器设置
optimizer = optim.SGD(model.parameters(), 
                     lr=0.01, 
                     momentum=0.9,  # 动量系数
                     weight_decay=1e-4)  # L2 正则化

# 学习率调度器
scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=5, gamma=0.1)

# 训练循环
for epoch in range(10):
    model.train()
    for batch_idx, (data, target) in enumerate(train_loader):
        optimizer.zero_grad()
        output = model(data)
        loss = nn.functional.cross_entropy(output, target)
        loss.backward()
        optimizer.step()

    scheduler.step()  # 更新学习率
    print(f'Epoch {epoch}, LR: {scheduler.get_last_lr()[0]:.6f}')

性能测试

在 MNIST 数据集上,我们比较了不同优化器的表现:

  1. Vanilla SGD:收敛速度较慢,准确率提升平缓
  2. SGD with Momentum:收敛速度明显加快,震荡减小
  3. Adam:初期收敛最快,但最终准确率与带动量的 SGD 相当

避坑指南

  1. 学习率初始值选择
  2. 常见初始值为 0.1、0.01 或 0.001
  3. 可以通过学习率扫描(learning rate sweep)来确定最佳值

  4. 梯度问题诊断

  5. 梯度爆炸:损失值变为 NaN
  6. 梯度消失:模型参数更新量极小
  7. 解决方案:梯度裁剪、批归一化、调整初始化方法

  8. 小批量大小选择

  9. 太小:噪声大,收敛不稳定
  10. 太大:内存需求高,每次更新计算量大
  11. 经验值:32-256 之间

思考题

如何设计实验验证 SGD 在不同神经网络深度下的表现差异?可以考虑以下方面:

  1. 构建不同深度的网络(如 3 层、5 层、10 层)
  2. 固定其他超参数,仅改变网络深度
  3. 记录训练过程中的损失曲线和验证准确率
  4. 比较收敛速度和最终性能
  5. 分析梯度传播情况(如梯度范数随时间变化)

通过这样的实验,可以深入理解 SGD 在不同网络结构中的行为特点。

正文完
 0
评论(没有评论)