批量、随机、小批量梯度下降法
三种梯度下降方法概述
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 | 全部 | 高 | 慢 | 高 | 小数据集 |
| SGD | 1 | 低 | 快 | 低 | 大规模在线学习 |
| 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)在相同数据集上的性能表现。
我们使用以下四个指标来评估模型的性能:
-
MSE (Mean Squared Error,均方误差)
- 公式:$MSE = \frac{1}{n}\sum_{i=1}^{n}(y_i - \hat{y}_i)^2$
- 含义:预测值与真实值差的平方的平均值
- 特点:对大误差敏感,值越小越好
- 单位:与目标变量的平方单位相同
-
RMSE (Root Mean Squared Error,均方根误差)
- 公式:$RMSE = \sqrt{MSE}$
- 含义:MSE 的平方根
- 特点:与目标变量单位相同,更直观
- 单位:与目标变量单位相同
- 值越小越好
-
MAE (Mean Absolute Error,平均绝对误差)
- 公式:$MAE = \frac{1}{n}\sum_{i=1}^{n}|y_i - \hat{y}_i|$
- 含义:预测值与真实值差的绝对值的平均值
- 特点:对所有误差同等对待,不受异常值影响
- 单位:与目标变量单位相同
- 值越小越好
-
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加速训练
批次大小选择建议
-
小批次(8-32):
- 梯度噪声较大,但可能有助于跳出局部最优
- 适合非凸优化问题
- 训练速度快
-
中等批次(32-128):
- 平衡了稳定性和速度
- 最常用的选择
- 适合大多数场景
-
大批次(128-512):
- 梯度更稳定,但可能陷入局部最优
- 适合凸优化问题
- 可以利用更好的并行化
-
全批次(BGD):
- 梯度最准确
- 适合小数据集
- 计算成本高
实际应用建议
- 默认选择:小批量梯度下降(MBGD),批次大小 32-128
- 大规模数据:使用 SGD 或小批次 MBGD
- 小规模数据:可以使用 BGD
- 深度学习:通常使用 MBGD,批次大小根据 GPU 内存调整
- 在线学习:使用 SGD
学习率调整
- BGD:可以使用较大的学习率(0.01-0.1)
- SGD:需要使用较小的学习率(0.001-0.01),并配合学习率衰减
- MBGD:中等学习率(0.01),可以配合学习率衰减
性能对比总结
从我们的实验结果可以看出:
- BGD 收敛最稳定,但训练时间最长
- SGD 训练最快,但损失曲线波动大
- MBGD 在速度和稳定性之间取得了良好的平衡
在实际应用中,小批量梯度下降(MBGD) 是最常用的方法,因为它平衡了计算效率、收敛稳定性和最终精度。比如:scikit-learn 的 SGDRegressor 默认使用小批量梯度下降,我们可以通过设置参数来模拟不同的行为。