1. 梯度下降与随机梯度下降的核心区别

在深度学习中,梯度下降(Gradient Descent)和随机梯度下降(Stochastic Gradient Descent, SGD)是最基础的优化算法。它们的目标都是通过迭代调整模型参数,使得损失函数的值最小化。但两者的工作方式有着本质区别。

梯度下降在每次参数更新时,会计算整个训练集上的平均梯度。这意味着每次更新都需要遍历所有样本,计算量大但更新方向稳定。而随机梯度下降每次只随机选取一个样本计算梯度并更新参数,计算效率高但更新方向波动较大。

举个例子,假设我们有3个样本数据:

x_data = [1.0, 2.0, 3.0]
y_data = [2.0, 4.0, 6.0]

在梯度下降中,每次更新权重w时:

grad = 0
for x, y in zip(x_data, y_data):
    grad += 2*x*(x*w - y)  # 对每个样本计算梯度
w -= 0.01 * (grad / len(x_data))  # 使用平均梯度更新

而在随机梯度下降中:

for x, y in zip(x_data, y_data):
    grad = 2*x*(x*w - y)  # 只计算当前样本梯度
    w -= 0.01 * grad  # 立即更新

实际应用中,我们通常会采用折中的小批量梯度下降(Mini-batch Gradient Descent),既保证了计算效率,又使更新方向相对稳定。

2. PyTorch实现梯度下降

在PyTorch中实现梯度下降非常直观。首先我们需要明确几个关键点:

  1. 前向传播计算预测值
  2. 计算损失函数
  3. 反向传播计算梯度
  4. 手动更新参数

下面是一个完整的PyTorch实现示例:

import torch
import matplotlib.pyplot as plt

# 准备数据
x_data = torch.tensor([[1.0], [2.0], [3.0]])
y_data = torch.tensor([[2.0], [4.0], [6.0]])

# 初始化权重
w = torch.tensor([[1.0]], requires_grad=True)

# 学习率
learning_rate = 0.01

# 存储训练过程
loss_list = []

for epoch in range(100):
    # 前向传播
    y_pred = x_data * w
    
    # 计算损失(MSE)
    loss = ((y_pred - y_data) ** 2).mean()
    loss_list.append(loss.item())
    
    # 反向传播
    loss.backward()
    
    # 手动更新权重,不自动计算梯度
    with torch.no_grad():
        w -= learning_rate * w.grad
        
        # 梯度清零
        w.grad.zero_()

# 绘制损失曲线
plt.plot(loss_list)
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.show()

这里有几个关键细节需要注意:

  1. requires_grad=True告诉PyTorch需要跟踪该张量的梯度
  2. loss.backward()会自动计算所有需要梯度的张量的梯度
  3. 更新参数时要使用torch.no_grad()上下文,避免影响梯度计算
  4. 每次更新后必须手动清零梯度,否则梯度会累积

3. 随机梯度下降的PyTorch实现

随机梯度下降的实现与批量梯度下降类似,主要区别在于数据的处理方式。下面是SGD的完整实现:

import torch
import matplotlib.pyplot as plt

# 准备数据
x_data = torch.tensor([[1.0], [2.0], [3.0]])
y_data = torch.tensor([[2.0], [4.0], [6.0]])

# 初始化权重
w = torch.tensor([[1.0]], requires_grad=True)

# 学习率
learning_rate = 0.01

# 存储训练过程
loss_list = []

for epoch in range(100):
    total_loss = 0
    for x, y in zip(x_data, y_data):
        # 前向传播
        y_pred = x * w
        
        # 计算损失
        loss = (y_pred - y) ** 2
        total_loss += loss.item()
        
        # 反向传播
        loss.backward()
        
        # 更新权重
        with torch.no_grad():
            w -= learning_rate * w.grad
            w.grad.zero_()
    
    loss_list.append(total_loss / len(x_data))

# 绘制损失曲线
plt.plot(loss_list)
plt.xlabel('Epoch')
plt.ylabel('Average Loss')
plt.show()

随机梯度下降的特点:

  1. 每个epoch内遍历所有样本,但每次只用一个样本更新
  2. 梯度更新更频繁(样本数×epoch次)
  3. 损失曲线波动较大,但通常能更快收敛
  4. 对噪声数据更鲁棒,不容易陷入局部最优

4. 关键参数与调优技巧

在实际应用中,有几个关键参数会影响梯度下降算法的表现:

4.1 学习率选择

学习率是梯度下降中最重要的超参数之一。太小的学习率会导致收敛过慢,太大的学习率可能导致无法收敛。经验法则:

  • 常见初始值:0.1, 0.01, 0.001
  • 可以使用学习率查找器(LR Finder)确定合适范围

PyTorch实现学习率测试:

learning_rates = [1, 0.1, 0.01, 0.001, 0.0001]
for lr in learning_rates:
    w = torch.tensor([[1.0]], requires_grad=True)
    for epoch in range(100):
        y_pred = x_data * w
        loss = ((y_pred - y_data) ** 2).mean()
        loss.backward()
        with torch.no_grad():
            w -= lr * w.grad
            w.grad.zero_()
    print(f"LR: {lr}, Final loss: {loss.item()}")

4.2 动量(Momentum)

动量方法可以帮助加速SGD在相关方向上的收敛,抑制震荡。PyTorch中实现带动量的SGD:

optimizer = torch.optim.SGD([w], lr=0.01, momentum=0.9)

动量原理:

v = momentum * v - learning_rate * gradient
w = w + v

4.3 批量大小选择

小批量梯度下降的批量大小也是一个重要参数:

  • 批量太小:更新方向噪声大,收敛不稳定
  • 批量太大:每次更新计算量大,可能陷入局部最优
  • 常见选择:32, 64, 128, 256等2的幂次

PyTorch实现小批量梯度下降:

from torch.utils.data import DataLoader

dataset = torch.utils.data.TensorDataset(x_data, y_data)
dataloader = DataLoader(dataset, batch_size=2, shuffle=True)

for epoch in range(100):
    for batch_x, batch_y in dataloader:
        # 前向传播和反向传播
        ...

5. 实际应用中的注意事项

在真实项目中使用梯度下降算法时,有几个常见问题需要注意:

  1. 梯度消失/爆炸:特别是深层网络中,梯度可能变得极小或极大。解决方法包括:

    • 梯度裁剪(torch.nn.utils.clip_grad_norm_
    • 使用ReLU等合适的激活函数
    • 批归一化(BatchNorm)
  2. 局部最优:神经网络损失函数通常有很多局部最优点。解决方法:

    • 使用随机梯度下降增加随机性
    • 尝试不同的初始化方法
    • 使用模拟退火等技术
  3. 过拟合:模型在训练集上表现太好,泛化能力差。解决方法:

    • 增加正则化(L1/L2)
    • 使用Dropout
    • 早停(Early Stopping)
  4. 学习率衰减:随着训练进行,逐渐减小学习率可以提升模型性能:

    scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=30, gamma=0.1)
    
  5. 数值稳定性:注意浮点数精度问题,特别是当学习率很小时:

    • 使用双精度(dtype=torch.float64
    • 检查梯度值是否合理

在实际项目中,我通常会先用小批量数据进行快速实验,确定合适的学习率和批量大小,然后再用全量数据进行训练。同时,使用TensorBoard或Weights & Biases等工具监控训练过程也非常有帮助。

更多推荐