标准(或批量)梯度下降使用整个数据集计算梯度以进行每次参数更新。这提供了梯度的精确估计,但对于深度学习中常见的大数据集而言,计算成本高昂且速度缓慢。随机梯度下降(SGD)则走向另一个极端,仅在单个训练样本上评估梯度后更新参数。每次更新速度快很多,并有助于摆离浅层局部最小值,但更新非常嘈杂,可能导致不稳定的收敛路径。是否存在一个折中方案?是的,它是实践中最常用的方法:小批量梯度下降。小批量梯度下降在批量梯度下降(Batch GD)的可靠性与随机梯度下降(SGD)的效率之间取得平衡。它不使用整个数据集或仅一个样本,而是根据训练数据的一个小部分(称为小批量)计算梯度并更新参数。小批量梯度下降的工作原理核心思路直接明了:选择小批量大小: 选择一个数字$b$,即小批量大小,通常为2的幂(例如,32、64、128、256),以提升硬件效率。这个$b$远小于训练样本总数$N$,但大于1。打乱数据: 在每个周期(对训练数据的一次完整处理)开始时,随机打乱数据集。这可以避免模型在每个周期都遇到相同的小批量序列,并有助于确保梯度估计具有代表性。遍历小批量: 将打乱后的数据集划分为大小为$b$的小批量。计算梯度并更新: 对于每个小批量$\mathcal{B} = { (\mathbf{x}^{(i)}, y^{(i)}) }_{i=1}^b$:计算小批量的平均损失: $$ L(\mathbf{W}; \mathcal{B}) = \frac{1}{b} \sum_{i=1}^b L(\mathbf{W}; \mathbf{x}^{(i)}, y^{(i)}) $$计算此平均损失相对于参数$\mathbf{W}$的梯度: $$ \nabla_{\mathbf{W}} L(\mathbf{W}; \mathcal{B}) = \frac{1}{b} \sum_{i=1}^b \nabla_{\mathbf{W}} L(\mathbf{W}; \mathbf{x}^{(i)}, y^{(i)}) $$使用此小批量梯度和学习率$\eta$更新参数: $$ \mathbf{W} \leftarrow \mathbf{W} - \eta \nabla_{\mathbf{W}} L(\mathbf{W}; \mathcal{B}) $$一旦算法处理完覆盖整个训练数据集的所有小批量,一个周期就完成了。训练通常需要多个周期。小批量梯度下降的优势这种方法提供了几个显著优势:效率: 在小批量上计算梯度比使用整个数据集快得多。重要的是,它使我们能够运用现代硬件(如GPU)上高度优化的矩阵运算。同时处理$b$个样本通常比逐个处理$b$个样本效率高得多。更平稳的收敛: 通过对$b$个样本的梯度进行平均,参数更新的方差相对于SGD减小了。这带来了更稳定的收敛路径,并且通常允许使用稍高的学习率。频繁更新: 与批量梯度下降(每个周期仅更新一次)不同,小批量梯度下降在每个周期更新参数多次(具体而言,是$N/b$次)。这通常会使收敛到良好解决方案的速度更快。以下可视化图提供了这些不同梯度下降变体在损失曲面上可能采取的优化路径的比较。{ "layout": { "xaxis": { "title": "参数 1", "range": [-0.5, 4.8], "showgrid": false, "zeroline": false }, "yaxis": { "title": "参数 2", "range": [-0.5, 4], "showgrid": false, "zeroline": false }, "title": "优化路径", "showlegend": true, "width": 600, "height": 450, "legend": { "yanchor": "top", "y": 0.99, "xanchor": "left", "x": 0.01 } }, "data": [ { "x": [0, 0.5, 1, 1.5, 2, 2.5, 3, 3.5, 4], "y": [0, 0.4, 0.8, 1.2, 1.6, 2.0, 2.4, 2.8, 3.2], "mode": "lines+markers", "name": "批量梯度下降", "line": { "color": "#1c7ed6", "width": 3 }, "marker": { "size": 5 } }, { "x": [0, 0.8, 0.5, 1.5, 1.2, 2.5, 2, 3.5, 2.8, 4.2, 3.8, 4.5], "y": [0, 0.5, 1, 0.8, 1.8, 1.5, 2.5, 2, 3, 2.5, 3.5, 3], "mode": "lines+markers", "name": "随机梯度下降", "line": { "color": "#fa5252", "width": 1.5, "dash": "dot" }, "marker": { "size": 4 } }, { "x": [0, 0.6, 1.2, 1.8, 2.4, 3.0, 3.6, 4.1], "y": [0, 0.3, 0.7, 1.1, 1.5, 1.9, 2.3, 2.6], "mode": "lines+markers", "name": "小批量梯度下降", "line": { "color": "#40c057", "width": 2 }, "marker": { "size": 5 } } ] }优化路径:批量梯度下降(平滑、直接)、随机梯度下降(嘈杂)、小批量梯度下降(中等噪声、更频繁的更新),向最小值收敛(未显示)。考量点尽管小批量梯度下降是深度学习优化中的主力方法,仍有一些要点需要注意:小批量大小($b$): 这是一个需要选择的新超参数。较小批量大小(例如,32)会带来更多噪声,这有时可以帮助优化器摆脱不佳的局部最小值(起到一种正则化的作用),但收敛速度可能较慢或需要更小的学习率。较大批量大小(例如,256、512)提供更准确的梯度估计,带来更平滑的收敛,并可能允许更高的学习率。然而,它们需要更多内存,并可能收敛到更尖锐的最小值,这有时泛化能力较差。它们在计算速度方面也存在收益递减,受限于硬件并行性。2的幂通常受到偏爱,因为它们与GPU中的内存架构良好匹配。梯度噪声: 从小批量计算的梯度仍然是一个估计值,而非整个数据集上的真实梯度。虽然噪声小于SGD,但这种噪声意味着优化路径仍非完全平滑。动量等技术(我们将在下一节讨论)常与小批量梯度下降配合使用,以帮助平滑这些更新。在实践中,小批量梯度下降(在许多深度学习库中常被简称为SGD,尽管它使用了小批量)是当前大多数优化算法的根本。PyTorch和TensorFlow等深度学习框架提供了方便的DataLoader工具,它们可以在训练期间自动为你处理数据打乱和创建小批量。# PyTorch 使用小批量的训练循环 import torch from torch.utils.data import TensorDataset, DataLoader # 假设 X_train, y_train 是张量 # 假设 model 和 criterion (损失函数) 已定义 # 假设 optimizer 已初始化 (例如, torch.optim.SGD) batch_size = 64 train_dataset = TensorDataset(X_train, y_train) train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True) num_epochs = 10 for epoch in range(num_epochs): model.train() # 设置模型为训练模式 running_loss = 0.0 for inputs, labels in train_loader: # inputs 和 labels 构成一个小批量 # 将参数梯度归零 optimizer.zero_grad() # 前向传播 outputs = model(inputs) loss = criterion(outputs, labels) # 反向传播并优化 loss.backward() optimizer.step() running_loss += loss.item() print(f"Epoch {epoch+1}, Loss: {running_loss / len(train_loader)}") # 训练后,在验证集上评估这种效率与合理精确的梯度估计的结合,使得小批量梯度下降成为训练深度神经网络的一种高效且被广泛采用的方法。然而,基本的小批量方法仍会遇到挑战,例如在损失中应对沟壑或鞍点,这促使了动量和自适应方法等更先进技术的发展。