问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

SGD 为什么叫"随机"梯度下降?深入剖析其真正含义!【代码实战】

创作时间:
作者:
@小白创作中心

SGD 为什么叫"随机"梯度下降?深入剖析其真正含义!【代码实战】

引用
CSDN
1.
https://blog.csdn.net/qq_22841387/article/details/146239732

随机梯度下降(Stochastic Gradient Descent,SGD)是机器学习和深度学习中一种非常重要的优化方法。但为什么它被称为"随机"梯度下降?本文将深入剖析这个问题,并通过Python代码实现帮助读者理解SGD的核心原理。

经典梯度下降(GD):完整数据集的梯度计算

在传统的批量梯度下降(Batch Gradient Descent,BGD)方法中,每次参数更新都需要计算整个训练集的梯度,公式如下:

θ = θ − η ⋅ ∇ J ( θ ) \theta = \theta - \eta \cdot \nabla J(\theta)θ=θ−η⋅∇J(θ)

其中:

  • θ \thetaθ是待优化的参数,
  • η \etaη是学习率,
  • ∇ J ( θ ) \nabla J(\theta)∇J(θ)是对整个数据集计算的梯度:

∇ J ( θ ) = 1 m ∑ i = 1 m ∇ J i ( θ ) \nabla J(\theta) = \frac{1}{m} \sum_{i=1}^{m} \nabla J_i(\theta)∇J(θ)=m1 i=1∑m ∇Ji (θ)

其中m mm是数据集的样本数,J i ( θ ) J_i(\theta)Ji (θ)是单个样本的损失。

问题:当数据集很大时,每次计算完整梯度的成本很高,训练速度非常慢,尤其是在深度学习任务中。

随机梯度下降(SGD):为什么是"随机"梯度下降?

(1) 随机抽取数据点进行更新

SGD采用了一种更高效的策略:每次参数更新时,仅使用一个样本点的梯度,而不是整个数据集,计算公式如下:

θ = θ − η ⋅ ∇ J i ( θ ) \theta = \theta - \eta \cdot \nabla J_i(\theta)θ=θ−η⋅∇Ji (θ)

其中i ii是当前随机选取的样本索引。

这样,SGD每次迭代不需要计算所有数据的梯度,而是仅使用一个样本进行参数更新,从而显著降低计算开销,提高训练速度。

(2) 随机打乱数据集,避免顺序影响

在实际实现SGD时,通常会在每个epoch之前对数据集进行随机打乱(shuffle),确保:

  • 数据不会按照固定顺序训练,避免模型陷入局部最优。
  • 训练的样本是无序的,使得更新方向不会受到数据排序的影响。

这就是"随机"梯度下降的关键:样本点是随机选择的,而不是按照顺序逐个更新。

SGD相比批量梯度下降的优缺点

方法
每次计算的梯度
计算开销
更新速度
是否震荡
批量梯度下降(BGD)
所有样本的梯度
无震荡
随机梯度下降(SGD)
一个样本的梯度
有较大震荡
小批量梯度下降(MBGD)
一小批样本的梯度
适中
适中
震荡较小

总结:

  • BGD计算精确,但计算量大,不适合大规模数据。
  • SGD计算快,但梯度波动大,容易震荡。
  • 小批量梯度下降(MBGD)在计算效率和稳定性之间取得平衡,最常用。

解决SGD震荡问题的优化策略

由于SGD仅基于单个样本进行更新,因此梯度的方向会有较大的随机波动,容易导致参数来回震荡。为了缓解这个问题,通常会使用以下优化策略:

  1. 动量方法(Momentum):通过引入"惯性",在更新时保留一部分之前的梯度方向,使得更新更平滑。
  2. 自适应学习率方法(Adam、RMSProp):自动调整学习率,使得收敛更加稳定。
  3. 学习率衰减(Learning Rate Decay):随着训练进行逐步降低学习率,让模型在训练后期更稳定地收敛。

总结

SGD为什么被称为"随机"梯度下降?

  • 关键原因:SGD在每次参数更新时,只随机选择一个样本点来计算梯度,而不是使用整个数据集。
  • 进一步优化:为了防止数据的固定顺序影响模型,SGD还会对训练数据进行随机打乱(shuffle),确保更新方向的随机性。
  • 优缺点:
  • 优点:计算高效,适合大规模数据。
  • 缺点:梯度波动大,容易震荡,但可以通过优化方法改善。

在实际应用中,SGD的随机性不仅加快了训练速度,还能帮助模型跳出局部最优,使其成为深度学习优化的核心方法之一。

代码实现:逐步理解SGD

为了更直观地理解随机梯度下降(SGD),我们将用Python从零开始实现批量梯度下降(BGD)和随机梯度下降(SGD),并对比它们的收敛效果。我们以一个简单的线性回归问题为例:

生成数据集

首先,我们创建一个简单的线性数据集:

y = 3 x + 2 + ϵ y = 3x + 2 + \epsilony=3x+2+ϵ

其中ϵ \epsilonϵ是一些随机噪声。

import numpy as np
import matplotlib.pyplot as plt

# 生成 100 个数据点
np.random.seed(42)
X = 2 * np.random.rand(100, 1)  # 生成随机输入 x
y = 3 * X + 2 + np.random.randn(100, 1) * 0.5  # 线性关系 y = 3x + 2 + 噪声

# 绘制数据分布
plt.scatter(X, y, color='blue', label="Training data")
plt.xlabel("X")
plt.ylabel("y")
plt.legend()
plt.show()

批量梯度下降(BGD)

在批量梯度下降(Batch Gradient Descent)中,我们使用所有样本计算梯度,然后更新参数。

公式如下:

θ = θ − η ⋅ 1 m ∑ i = 1 m ∇ J i ( θ ) \theta = \theta - \eta \cdot \frac{1}{m} \sum_{i=1}^{m} \nabla J_i(\theta)θ=θ−η⋅m1 i=1∑m ∇Ji (θ)

其中θ \thetaθ是待优化参数,η \etaη是学习率,m mm是样本数。

# 批量梯度下降(BGD)实现
def batch_gradient_descent(X, y, learning_rate=0.1, n_iters=1000):
    m = len(X)
    theta = np.random.randn(2, 1)  # 初始化参数 [w, b]
    
    X_b = np.c_[np.ones((m, 1)), X]  # 添加偏置项
    loss_history = []
    for iteration in range(n_iters):
        gradients = (2 / m) * X_b.T @ (X_b @ theta - y)  # 计算全局梯度
        theta -= learning_rate * gradients  # 更新参数
        loss = np.mean((X_b @ theta - y) ** 2)  # 计算损失
        loss_history.append(loss)
    return theta, loss_history

theta_bgd, loss_bgd = batch_gradient_descent(X, y)
print(f"BGD 训练结果: theta = {theta_bgd.ravel()}")

在你的批量梯度下降(BGD)代码中,

theta

是线性回归模型的参数,包括权重

w

和偏置

b

,最终的

theta

由梯度下降迭代计算得出。

theta

具体代表什么?

代码中初始化了:

theta = np.random.randn(2, 1)  # 初始化参数 [w, b]

这意味着

theta

是一个2×1 的列向量,其中:

theta[0]

代表偏置项

b

-

theta[1]

代表权重

w

(即特征

X

的系数)

batch_gradient_descent

过程中,每次迭代都会基于均方误差(MSE)计算梯度,并更新

theta

,最终收敛到一个最优解。

如何查看

theta

的最终值?

代码在最终打印:

print(f"BGD 训练结果: theta = {theta_bgd.ravel()}")

theta_bgd.ravel()

theta

拉平成一维数组,假设结果如下:

BGD 训练结果: theta =  [2.10754808 2.88505669]

则模型的最终方程为:

y ^ = 2.88 X + 2.10 \hat{y} = 2.88 X + 2.10y^ =2.88X+2.10

这表示:

b ≈ 2.10

,即当

X=0

时,预测值

y

约为 2.10。

w ≈ 2.88

,即

X

每增加 1,预测

y

增加 2.88。

随机梯度下降(SGD)

在 SGD 中,我们随机选取一个样本计算梯度,并更新参数:

θ = θ − η ⋅ ∇ J i ( θ ) \theta = \theta - \eta \cdot \nabla J_i(\theta)θ=θ−η⋅∇Ji (θ)

我们还需要对数据进行随机打乱,避免数据的固定顺序影响模型的收敛。

# 随机梯度下降(SGD)实现
def stochastic_gradient_descent(X, y, learning_rate=0.1, n_epochs=1000):
    m = len(X)
    theta = np.random.randn(2, 1)  # 初始化参数 [w, b]
    
    X_b = np.c_[np.ones((m, 1)), X]  # 添加偏置项
    loss_history = []
    for epoch in range(n_epochs):
        indices = np.random.permutation(m)  # 随机打乱数据
        for i in indices:
            xi = X_b[i:i+1]  # 取单个样本
            yi = y[i:i+1]
            gradients = 2 * xi.T @ (xi @ theta - yi)  # 计算梯度
            theta -= learning_rate * gradients  # 更新参数
            
        loss = np.mean((X_b @ theta - y) ** 2)  # 计算损失
        loss_history.append(loss)
    return theta, loss_history

theta_sgd, loss_sgd = stochastic_gradient_descent(X, y)
print(f"SGD 训练结果: theta = {theta_sgd.ravel()}")

模型的最终方程为:

y ^ = 2.78 X + 2.30 \hat{y} = 2.78 X + 2.30y^ =2.78X+2.30

这表示:

b ≈ 2.30

,即当

X=0

时,预测值

y

约为 2.30。

w ≈ 2.78

,即

X

每增加 1,预测

y

增加 2.78。

训练过程对比

我们绘制 BGD 和 SGD 在训练过程中的损失下降情况:

plt.plot(loss_bgd, label="BGD Loss")
plt.plot(loss_sgd, label="SGD Loss")
plt.xlabel("Iteration (Epoch for SGD)")
plt.ylabel("Loss")
plt.legend()
plt.title("BGD vs SGD Loss Curve")
plt.show()

观察 SGD 的随机性

我们多次运行 SGD,并观察每次拟合出的参数值是否一致:

for i in range(5):
    theta_sgd, _ = stochastic_gradient_descent(X, y)
    print(f"Run {i+1}: SGD theta = {theta_sgd.ravel()}")

你会发现,由于 SGD 每次更新都基于随机样本,最终拟合出的参数会有细微差异,这就是SGD 的随机性所在!

总结

  • 批量梯度下降(BGD)使用所有数据计算梯度,更新稳定,但计算量大。
  • 随机梯度下降(SGD)每次使用单个样本计算梯度,计算高效,但参数更新方向会有较大波动。
  • SGD 的"随机"性来源于:
  1. 每次迭代随机选择样本进行梯度计算。
  2. 在每个 epoch 前对数据随机打乱,避免样本顺序影响学习过程。
  • SGD 适用于大规模数据集,收敛更快,但需要优化方法(如动量、Adam)来减少震荡。
© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号