随机梯度下降(SGD)基础:从数学原理到Python实现

1次阅读
没有评论

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

image.webp

为什么需要随机梯度下降

在机器学习中,梯度下降是最常用的优化算法之一。传统的批量梯度下降 (BGD) 每次迭代都要计算整个数据集的梯度,当数据集很大时(比如百万级样本),这会带来巨大的计算开销。

随机梯度下降 (SGD) 基础:从数学原理到 Python 实现

想象一下,每次更新参数都要遍历全部数据,就像每次调整方向都要问遍所有路人,效率实在太低了。这就是我们需要随机梯度下降 (SGD) 的原因 – 它每次只随机选取一个样本计算梯度,大大提高了迭代速度。

数学原理

SGD 的参数更新规则很简单:

$$\theta_{t+1} = \theta_t – \eta \cdot \nabla_\theta J(\theta_t; x^{(i)}, y^{(i)})$$

其中:
– $\theta$ 是模型参数
– $\eta$ 是学习率
– $(x^{(i)}, y^{(i)})$ 是随机选取的一个训练样本

与 BGD 相比,SGD 有两大优势:
1. 计算量小,每次迭代只需处理一个样本
2. 随机性可以帮助跳出局部最优解

Python 实现

下面我们用一个简单的线性回归任务来演示 SGD 的实现。我们将:
1. 生成二维线性数据
2. 实现 SGD 优化
3. 可视化优化过程

import numpy as np
import matplotlib.pyplot as plt

# 1. 生成数据
np.random.seed(42)
X = 2 * np.random.rand(100, 1)  # 100 个 0 - 2 之间的随机数
y = 4 + 3 * X + np.random.randn(100, 1)  # 添加噪声

# 2. SGD 实现
def sgd(X, y, n_epochs=50, eta0=0.1):
    m = len(X)
    theta = np.random.randn(2, 1)  # 随机初始化参数

    # 记录损失和参数变化
    losses = []
    thetas = [theta.copy()]

    for epoch in range(n_epochs):
        # 学习率衰减
        eta = eta0 / (epoch + 1)

        for i in range(m):
            random_index = np.random.randint(m)
            xi = np.c_[1, X[random_index]]  # 添加偏置项
            yi = y[random_index]

            # 计算梯度
            gradients = 2 * xi.T.dot(xi.dot(theta) - yi)

            # 更新参数
            theta = theta - eta * gradients
            thetas.append(theta.copy())

        # 计算当前 epoch 的损失
        y_pred = np.c_[np.ones((m,1)), X].dot(theta)
        loss = np.mean((y_pred - y)**2)
        losses.append(loss)

    return theta, losses, thetas

# 运行 SGD
theta_sgd, losses_sgd, thetas_sgd = sgd(X, y)

对比实验

我们可以将 SGD 与 BGD 的收敛过程进行对比:

# BGD 实现
def bgd(X, y, n_epochs=50, eta=0.1):
    m = len(X)
    theta = np.random.randn(2, 1)
    losses = []

    for epoch in range(n_epochs):
        # 计算整个数据集的梯度
        gradients = 2/m * np.c_[np.ones((m,1)), X].T.dot(np.c_[np.ones((m,1)), X].dot(theta) - y)

        # 更新参数
        theta = theta - eta * gradients

        # 计算损失
        y_pred = np.c_[np.ones((m,1)), X].dot(theta)
        loss = np.mean((y_pred - y)**2)
        losses.append(loss)

    return theta, losses

# 运行 BGD
theta_bgd, losses_bgd = bgd(X, y)

# 绘制收敛曲线
plt.plot(losses_bgd, 'b-', label='BGD')
plt.plot(losses_sgd, 'r-', label='SGD')
plt.legend()
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.title('BGD vs SGD Convergence')
plt.show()

避坑指南

在实践 SGD 时,新手常会遇到以下问题:

  1. 学习率设置不当
  2. 学习率太大会导致震荡不收敛
  3. 学习率太小收敛速度太慢
  4. 建议使用学习率衰减策略

  5. 特征未归一化

  6. 不同特征尺度差异大会导致收敛困难
  7. 务必对特征进行标准化处理

  8. 随机性的收敛判断

  9. SGD 的损失曲线会有较大波动
  10. 建议观察移动平均损失而非单个 epoch

延伸思考

掌握了 SGD 的基本原理后,你可以尝试:

  1. 用 SGD 实现逻辑回归
  2. 在神经网络中应用 SGD
  3. 实现更高级的优化器如 Momentum 或 Adam

SGD 虽然简单,但它是深度学习优化的基础。理解它的工作原理对后续学习更复杂的优化算法很有帮助。

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