行莫
行莫
发布于 2026-01-05 / 6 阅读
0
0

批量、随机、小批量梯度下降法

批量、随机、小批量梯度下降法

三种梯度下降方法概述

1. 批量梯度下降(Batch Gradient Descent, BGD)

原理:

  • 使用全部训练样本计算梯度
  • 每次迭代更新参数时,需要遍历所有样本
  • 梯度计算:$\nabla J(\theta) = \frac{1}{m}\sum_{i=1}^{m}\nabla J_i(\theta)$

特点:

  • ✅ 梯度方向准确,收敛稳定
  • ✅ 可以保证收敛到全局最优(凸函数)或局部最优(非凸函数)
  • ❌ 计算量大,内存占用高
  • ❌ 不适合大规模数据集
  • ❌ 无法在线学习(需要所有数据)

2. 随机梯度下降(Stochastic Gradient Descent, SGD)

原理:

  • 每次迭代随机选择一个样本计算梯度
  • 每次更新只使用一个样本的信息
  • 梯度计算:$\nabla J(\theta) = \nabla J_i(\theta)$,其中 $i$ 是随机选择的样本索引

特点:

  • ✅ 计算速度快,内存占用小
  • ✅ 适合大规模数据集
  • ✅ 可以在线学习
  • ✅ 可能跳出局部最优
  • ❌ 梯度噪声大,收敛不稳定
  • ❌ 需要调整学习率(通常使用学习率衰减)
  • ❌ 可能无法收敛到最优解

3. 小批量梯度下降(Mini-batch Gradient Descent, MBGD)

原理:

  • 每次迭代使用一小批样本(batch)计算梯度
  • 是 BGD 和 SGD 的折中方案
  • 梯度计算:$\nabla J(\theta) = \frac{1}{b}\sum_{i=k}^{k+b-1}\nabla J_i(\theta)$,其中 $b$ 是批次大小

特点:

  • ✅ 平衡了计算效率和收敛稳定性
  • ✅ 可以利用向量化加速(GPU友好)
  • ✅ 梯度噪声适中
  • ✅ 适合大规模数据集
  • ❌ 需要选择合适的批次大小
  • ❌ 批次大小是超参数,需要调优

4. 对比

方法样本数梯度准确性收敛速度内存占用适用场景
BGD全部小数据集
SGD1大规模在线学习
MBGD小批量大规模数据集(最常用)

对比

准备数据集

构造一个线性回归数据集,用于对比三种梯度下降方法,样本数 1000 , 特征 10 .

# 设置随机种子,保证结果可复现
np.random.seed(42)

# 生成数据集
n_samples = 1000  # 样本数量
n_features = 10   # 特征数量

# 生成特征矩阵 X
X = np.random.randn(n_samples, n_features)

# 生成真实的权重和偏置
true_weights = np.random.randn(n_features)
true_bias = 2.5

# 生成目标值(添加噪声)
y = X @ true_weights + true_bias + np.random.randn(n_samples) * 0.1

# 标准化特征(对梯度下降很重要)
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

实现三种梯度下降方法

class LinearRegression:
    """线性回归基类"""
    def __init__(self, learning_rate=0.01, max_iter=1000, tol=1e-6):
        self.learning_rate = learning_rate
        self.max_iter = max_iter
        self.tol = tol
        self.weights = None
        self.bias = None
        self.loss_history = []
        
    def _compute_loss(self, X, y, weights, bias):
        """计算均方误差损失"""
        predictions = X @ weights + bias
        loss = np.mean((predictions - y) ** 2)
        return loss
    
    def _compute_gradient(self, X, y, weights, bias):
        """计算梯度"""
        predictions = X @ weights + bias
        error = predictions - y
        grad_weights = X.T @ error / len(y)
        grad_bias = np.mean(error)
        return grad_weights, grad_bias
    
    def predict(self, X):
        """预测"""
        if self.weights is None:
            raise ValueError("模型尚未训练")
        return X @ self.weights + self.bias


class BatchGradientDescent(LinearRegression):
    """批量梯度下降(BGD)"""
    def fit(self, X, y):
        n_samples, n_features = X.shape
        self.weights = np.random.randn(n_features) * 0.01
        self.bias = 0.0
        self.loss_history = []
        
        for i in range(self.max_iter):
            # 使用全部样本计算梯度
            grad_weights, grad_bias = self._compute_gradient(X, y, self.weights, self.bias)
            
            # 更新参数
            self.weights -= self.learning_rate * grad_weights
            self.bias -= self.learning_rate * grad_bias
            
            # 记录损失
            loss = self._compute_loss(X, y, self.weights, self.bias)
            self.loss_history.append(loss)
            
            # 检查收敛
            if i > 0 and abs(self.loss_history[-2] - self.loss_history[-1]) < self.tol:
                print(f"BGD 在第 {i+1} 次迭代后收敛")
                break
        
        return self


class StochasticGradientDescent(LinearRegression):
    """随机梯度下降(SGD)"""
    def fit(self, X, y):
        n_samples, n_features = X.shape
        self.weights = np.random.randn(n_features) * 0.01
        self.bias = 0.0
        self.loss_history = []
        
        for i in range(self.max_iter):
            # 随机选择一个样本
            idx = np.random.randint(0, n_samples)
            X_sample = X[idx:idx+1]  # 保持2D形状
            y_sample = y[idx:idx+1]
            
            # 使用单个样本计算梯度
            grad_weights, grad_bias = self._compute_gradient(X_sample, y_sample, 
                                                              self.weights, self.bias)
            
            # 更新参数
            self.weights -= self.learning_rate * grad_weights
            self.bias -= self.learning_rate * grad_bias
            
            # 每10次迭代记录一次损失(使用全部样本)
            if i % 10 == 0:
                loss = self._compute_loss(X, y, self.weights, self.bias)
                self.loss_history.append(loss)
        
        return self


class MiniBatchGradientDescent(LinearRegression):
    """小批量梯度下降(MBGD)"""
    def __init__(self, learning_rate=0.01, max_iter=1000, tol=1e-6, batch_size=32):
        super().__init__(learning_rate, max_iter, tol)
        self.batch_size = batch_size
    
    def fit(self, X, y):
        n_samples, n_features = X.shape
        self.weights = np.random.randn(n_features) * 0.01
        self.bias = 0.0
        self.loss_history = []
        
        for i in range(self.max_iter):
            # 随机选择一个小批量
            indices = np.random.choice(n_samples, size=self.batch_size, replace=False)
            X_batch = X[indices]
            y_batch = y[indices]
            
            # 使用小批量计算梯度
            grad_weights, grad_bias = self._compute_gradient(X_batch, y_batch, 
                                                              self.weights, self.bias)
            
            # 更新参数
            self.weights -= self.learning_rate * grad_weights
            self.bias -= self.learning_rate * grad_bias
            
            # 每10次迭代记录一次损失(使用全部样本)
            if i % 10 == 0:
                loss = self._compute_loss(X, y, self.weights, self.bias)
                self.loss_history.append(loss)
        
        return self

模型训练

# 设置相同的初始条件
learning_rate = 0.01
max_iter = 1000

# 训练三种模型
print("=" * 60)
print("开始训练模型...")
print("=" * 60)

# 批量梯度下降
print("\n1. 批量梯度下降 (BGD)...")
bgd_model = BatchGradientDescent(learning_rate=learning_rate, max_iter=max_iter)
start_time = time.time()
bgd_model.fit(X_scaled, y)
bgd_time = time.time() - start_time
print(f"   训练时间: {bgd_time:.4f} 秒")
print(f"   最终损失: {bgd_model.loss_history[-1]:.6f}")
print(f"   迭代次数: {len(bgd_model.loss_history)}")

# 随机梯度下降
print("\n2. 随机梯度下降 (SGD)...")
sgd_model = StochasticGradientDescent(learning_rate=learning_rate, max_iter=max_iter)
start_time = time.time()
sgd_model.fit(X_scaled, y)
sgd_time = time.time() - start_time
print(f"   训练时间: {sgd_time:.4f} 秒")
print(f"   最终损失: {sgd_model.loss_history[-1]:.6f}")
print(f"   迭代次数: {len(sgd_model.loss_history)}")

# 小批量梯度下降
print("\n3. 小批量梯度下降 (MBGD)...")
mbgd_model = MiniBatchGradientDescent(learning_rate=learning_rate, max_iter=max_iter, batch_size=32)
start_time = time.time()
mbgd_model.fit(X_scaled, y)
mbgd_time = time.time() - start_time
print(f"   训练时间: {mbgd_time:.4f} 秒")
print(f"   最终损失: {mbgd_model.loss_history[-1]:.6f}")
print(f"   迭代次数: {len(mbgd_model.loss_history)}")

print("\n" + "=" * 60)
print("训练完成!")
print("=" * 60)

对比结果

损失曲线和性能对比

性能指标对比

对比三种梯度下降方法(BGD、SGD、MBGD)在相同数据集上的性能表现。

我们使用以下四个指标来评估模型的性能:

  1. MSE (Mean Squared Error,均方误差)

    • 公式:$MSE = \frac{1}{n}\sum_{i=1}^{n}(y_i - \hat{y}_i)^2$
    • 含义:预测值与真实值差的平方的平均值
    • 特点:对大误差敏感,值越小越好
    • 单位:与目标变量的平方单位相同
  2. RMSE (Root Mean Squared Error,均方根误差)

    • 公式:$RMSE = \sqrt{MSE}$
    • 含义:MSE 的平方根
    • 特点:与目标变量单位相同,更直观
    • 单位:与目标变量单位相同
    • 值越小越好
  3. MAE (Mean Absolute Error,平均绝对误差)

    • 公式:$MAE = \frac{1}{n}\sum_{i=1}^{n}|y_i - \hat{y}_i|$
    • 含义:预测值与真实值差的绝对值的平均值
    • 特点:对所有误差同等对待,不受异常值影响
    • 单位:与目标变量单位相同
    • 值越小越好
  4. R² (R-squared,决定系数)

    • 公式:$R^2 = 1 - \frac{\sum_{i=1}^{n}(y_i - \hat{y}i)^2}{\sum{i=1}^{n}(y_i - \bar{y})^2}$
    • 含义:模型解释的方差占总方差的比例
    • 特点:取值范围通常在 [0, 1],越接近 1 越好
    • 解释:
      • $R^2 = 1$:完美拟合
      • $R^2 = 0$:模型不优于简单平均值
      • $R^2 < 0$:模型表现比简单平均值还差

批次对比

测试不同批次大小的影响:
SGD (batch=1) - MSE: 0.009575, R²: 0.998414, 时间: 0.0250s
MBGD (batch=8) - MSE: 0.009412, R²: 0.998442, 时间: 0.0660s
MBGD (batch=16) - MSE: 0.009387, R²: 0.998446, 时间: 0.0660s
MBGD (batch=32) - MSE: 0.009366, R²: 0.998449, 时间: 0.0666s
MBGD (batch=64) - MSE: 0.009370, R²: 0.998448, 时间: 0.0667s
MBGD (batch=128) - MSE: 0.009368, R²: 0.998449, 时间: 0.0680s
MBGD (batch=256) - MSE: 0.009363, R²: 0.998450, 时间: 0.0736s
MBGD (batch=512) - MSE: 0.009363, R²: 0.998450, 时间: 0.1030s
BGD 在第 627 次迭代后收敛
BGD (batch=全部) - MSE: 0.009413, R²: 0.998441, 时间: 0.0370s

损失曲线详细对比

总结与建议

三种方法的优缺点总结

批量梯度下降 (BGD)

优点:

  • 梯度方向准确,收敛稳定
  • 可以保证收敛到全局最优(凸函数)
  • 实现简单,易于理解

缺点:

  • 计算量大,每次迭代需要遍历所有样本
  • 内存占用高
  • 不适合大规模数据集
  • 无法在线学习

适用场景:

  • 小规模数据集(< 10,000 样本)
  • 需要精确解的情况
  • 计算资源充足

随机梯度下降 (SGD)

优点:

  • 计算速度快,每次迭代只处理一个样本
  • 内存占用小
  • 适合大规模数据集
  • 可以在线学习
  • 可能跳出局部最优

缺点:

  • 梯度噪声大,收敛不稳定
  • 需要调整学习率(通常使用学习率衰减)
  • 可能无法收敛到最优解
  • 损失曲线波动大

适用场景:

  • 大规模数据集(> 100,000 样本)
  • 在线学习场景
  • 需要快速迭代
  • 对最终精度要求不是特别高

小批量梯度下降 (MBGD)

优点:

  • 平衡了计算效率和收敛稳定性
  • 可以利用向量化加速(GPU友好)
  • 梯度噪声适中
  • 适合大规模数据集
  • 最常用的方法

缺点:

  • 需要选择合适的批次大小(超参数)
  • 批次大小需要调优

适用场景:

  • 大规模数据集(最常用)
  • 深度学习(默认方法)
  • 需要平衡速度和精度
  • GPU加速训练

批次大小选择建议

  1. 小批次(8-32)

    • 梯度噪声较大,但可能有助于跳出局部最优
    • 适合非凸优化问题
    • 训练速度快
  2. 中等批次(32-128)

    • 平衡了稳定性和速度
    • 最常用的选择
    • 适合大多数场景
  3. 大批次(128-512)

    • 梯度更稳定,但可能陷入局部最优
    • 适合凸优化问题
    • 可以利用更好的并行化
  4. 全批次(BGD)

    • 梯度最准确
    • 适合小数据集
    • 计算成本高

实际应用建议

  1. 默认选择:小批量梯度下降(MBGD),批次大小 32-128
  2. 大规模数据:使用 SGD 或小批次 MBGD
  3. 小规模数据:可以使用 BGD
  4. 深度学习:通常使用 MBGD,批次大小根据 GPU 内存调整
  5. 在线学习:使用 SGD

学习率调整

  • BGD:可以使用较大的学习率(0.01-0.1)
  • SGD:需要使用较小的学习率(0.001-0.01),并配合学习率衰减
  • MBGD:中等学习率(0.01),可以配合学习率衰减

性能对比总结

从我们的实验结果可以看出:

  1. BGD 收敛最稳定,但训练时间最长
  2. SGD 训练最快,但损失曲线波动大
  3. MBGD 在速度和稳定性之间取得了良好的平衡

在实际应用中,小批量梯度下降(MBGD) 是最常用的方法,因为它平衡了计算效率、收敛稳定性和最终精度。比如:scikit-learn 的 SGDRegressor 默认使用小批量梯度下降,我们可以通过设置参数来模拟不同的行为。


评论