资讯动态

梯度下降三种变体详解:BGD、SGD与Mini-batch对比与调参实践

发布时间:2026/10/14 16:00:41 来源:尧图企业网站定制
1. 先搞清楚梯度下降到底在解决什么问题做模型训练的朋友应该都遇到过这样的场景损失函数曲线怎么都不降或者降一会儿就开始剧烈震荡甚至直接发散到 NaN。这时候大家的第一反应往往是是不是学习率太大了数据是不是没归一化但真正想定位问题还是得回到最基础的优化算法本身。梯度下降Gradient Descent就是绝大多数深度学习模型训练背后的那个引擎理解它的三种主流变体——批量梯度下降、随机梯度下降、小批量梯度下降——基本等于拿到了调参的第一把钥匙。先打个比方。假设你蒙着眼睛站在一座山上目标是走到山脚的最低点。你不知道整座山的地形只能靠脚底感受当前位置的坡度。如果坡度陡说明你离最低点可能还远那就大步走如果坡度缓说明可能快到了那就小步挪。这个感受坡度的过程就是计算梯度的过程而走多远则由学习率控制。梯度下降要做的就是不断重复感受坡度、迈出一步、再感受坡度这个循环直到确认自己已经站在某个低点。这里有几个关键点必须提前讲清楚否则后面看公式会犯迷糊。1.1 损失函数与下山的类比模型训练的本质是找到一组参数让损失函数的值尽可能小。损失函数通常记为 L(θ)其中 θ 代表模型的所有参数权重、偏置。比如线性回归的均方误差、分类任务的交叉熵都是损失函数的例子。损失函数的值越小说明模型的预测结果和真实标签越接近。在数学上这叫做无约束优化问题。而梯度下降之所以是主力方法是因为深度学习模型的参数动辄百万甚至上亿解析解根本不存在只能用迭代的方式一点点逼近。每次迭代我们都要计算损失函数对每个参数的偏导数——也就是梯度——然后沿着梯度的反方向更新参数。因为梯度指向的是损失函数上升最快的方向反方向就是下降最快的方向。1.2 学习率这一步迈多大学习率learning rate用 η 表示控制每一步更新的幅度。它是整个梯度下降算法里最敏感的超参数。学习率太大参数会在最优值附近来回横跳甚至发散学习率太小收敛速度慢训练半天也没进展。而且学习率并不是设个合理值就能一劳永逸训练过程中往往还需要动态调整。一个直观的理解方式梯度告诉你方向学习率告诉你步长。方向再正确步长不对也白搭。1.3 梯度不是值而是方向很多初学者会有个误解以为梯度是一个具体的数值其实梯度是一个向量它的每个分量对应一个参数在该点的偏导数。这个向量的模长表示损失函数变化的剧烈程度方向则表示上升最快的方向。更新参数时我们用的是负梯度方向也就是让损失下降最快的方向。搞清了这三点接下来就能顺理成章地引出三种梯度下降变体了。它们的核心区别只有一个——每次更新参数时用多少样本的梯度来估算整体的下山方向。2. 批量梯度下降每一步都看完全部数据批量梯度下降Batch Gradient Descent简称 BGD是梯度下降最原始、最符合直觉的形式。它的做法很简单每更新一次参数就遍历整个训练集把所有样本的损失累加起来求平均得到一个大方向的梯度然后沿这个方向走一步。2.1 数学原理与完整计算流程假设训练集有 N 个样本损失函数是 L(θ)那么批量梯度下降的更新公式是θ θ - η * (1/N) * Σ L_i(θ) 对 θ 求偏导这个公式里最关键的部分是求和符号 Σ它表示对全部 N 个样本的梯度求平均。正因为看了所有数据这个梯度是对真实最优方向的一个相当准确的估计几乎不含噪声。具体流程可以拆成四步初始化参数 θ随机初始化或置零视模型而定。在整个训练集上计算所有样本的损失总和再对每个参数求偏导得到总梯度。用学习率 η 乘以梯度更新参数θ ← θ - η * gradient。重复第 2 到第 3 步直到损失收敛或达到预设的迭代轮数。用伪代码来表达就是for epoch in range(num_epochs): gradient compute_gradient(X_all, y_all, theta) theta theta - learning_rate * gradient这里注意epoch的概念一个 epoch 指的是完整遍历一次训练集。批量梯度下降每走一步就是一个 epoch所以它每一步的计算量是 O(N)N 是训练集大小。2.2 收敛稳定但代价是什么批量梯度下降最大的优点是稳定。因为梯度来自全量数据的平均方向准确损失曲线通常是平滑下降的不会有太多震荡。从理论上说只要学习率设置得当它最终能够收敛到局部最优解在凸问题中就是全局最优解。但它的缺点也极其明显每一步的计算成本太高。想象一下训练集有一百万张图片模型有几百万参数每更新一次参数就要把所有一百万张图片都过一遍计算梯度这不仅慢而且对内存的占用也非常大。GPU 显存是有上限的全量数据往往根本放不进去。还有一个容易被忽略的问题批量梯度下降每一步都朝着同一个方向走一旦陷入局部最优在非凸问题里比如神经网络它就会停在那个位置缺乏跳出的能力。因为它每一步都是精确计算的方向没有随机性所以它是确定性算法而随机性是逃离局部最优的重要手段之一。2.3 什么场景还在用它在我的实际经验里纯粹的批量梯度下降现在很少出现在深度学习训练中多见于以下两种情况训练集非常小比如几千个样本甚至更少全量计算梯度也很快问题本身是凸优化问题比如线性回归、逻辑回归的某些简化形式用 BGD 收敛路径清晰且不需要依赖 GPU 的大规模并行。很多机器学习教材讲线性回归时用的梯度下降都是 BGD但那只适合教学演示。真到了大规模数据集上BGD 的算力开销基本就是灾难级。3. 随机梯度下降用单样本的噪声换速度随机梯度下降Stochastic Gradient Descent简称 SGD是解决 BGD 算力瓶颈的最直接方案。它的核心变化是每次更新参数时不再看全部数据而是随机从训练集里抽一个样本用它的梯度来近似整体梯度。3.1 采样方式与更新公式的变化SGD 的更新公式变成了θ θ - η * L_i(θ) 对 θ 求偏导注意这里没有了对全部样本求平均的 Σ只有一个样本 i。也就是说每一步的梯度是基于单个样本计算的而样本 i 是每次随机抽取的。采样方式细分下来通常有两种一种是有放回抽样同一个样本可能在多个轮次被抽中另一种是无放回抽样即每个 epoch 内把训练集打乱顺序然后一个接一个地使用。无论哪种方式核心思想都是一样的——用单个样本的梯度作为全量梯度的一个带噪声的估计。伪代码如下for epoch in range(num_epochs): for i in random_permutation(N): gradient compute_gradient(x_i, y_i, theta) theta theta - learning_rate * gradient从代码里能看出来SGD 一个 epoch 内要更新 N 次参数而 BGD 一个 epoch 只更新 1 次。看起来 SGD 的更新次数多但每一次的计算量是 O(1)所以整体速度反而快得多。3.2 噪声是坏事吗并不一定很多人第一次接触 SGD 会觉得困惑用单个样本的梯度去更新参数方向不是很不准确吗训练不会出问题吗其实噪声恰恰是 SGD 的一个隐藏优势。单个样本的梯度相对于全量梯度来说是有偏的、波动的这种波动会带来两方面的效果一方面它让损失曲线的下降路径变得抖动不像 BGD 那样平滑另一方面这种随机性恰恰帮助模型有机会跳出局部最优。比如在一个平坦的局部极小值区域SGD 的随机抖动可能恰好把它推出去进入另一个损失更低的区域。这有点像模拟退火的思想——适当的随机扰动有助于全局搜索。我用一个生活化的类比来解释BGD 像是拿着一份完整地图按最优路径走SGD 则像是闭着眼睛每走一步都靠猜。猜的方向不一定准但正因为这种不确定性你反而有可能误打误撞找到一条地图上没有标注的捷径。当然也可能撞墙。此外SGD 每一步看的样本极少理论上内存开销几乎为零在数据无法全部载入内存时比如流式数据场景SGD 几乎是唯一能工作的在线学习方案。3.3 经典SGD的收敛特性与调参直觉SGD 的收敛性在理论上依赖一个条件学习率需要随时间逐渐衰减到零。因为梯度本身是有噪声的如果学习率始终保持不变参数会在最优解附近持续震荡而无法收敛到一个足够精确的点。这也是为什么很多经典教材里会把学习率衰减作为 SGD 的标准配套。实际调参时SGD 的学习率往往要比 BGD 小一些因为单个样本的梯度有可能很大——某个离群样本的梯度方向完全相反如果你步长太大很可能直接飞出去。一个常见的做法是从 0.01 或 0.001 开始尝试观察损失曲线的下降速度再做调整。还需要提醒一点SGD 虽然计算快但训练过程中的 loss 曲线会非常毛糙这是正常现象不代表训练出了问题。我在早期做实验时看到 loss 上下乱跳就急着调小学习率结果模型反而学得很慢后来才明白这是 SGD 的固有特性。如果想看得更清楚可以对 loss 做滑动平均而不是要求每一步都稳定下降。4. 小批量梯度下降两者的折中也是实践中默认选项讲完了两个极端你应该已经猜到了第三种方案——小批量梯度下降Mini-batch Gradient Descent。它每次更新参数时随机抽取一小批样本比如 32 个、64 个、128 个用这一小批样本的平均梯度来更新参数。4.1 小批量的本质在估计方差和计算效率之间找个平衡小批量梯度下降的更新公式是θ θ - η * (1/m) * Σ L_j(θ) 对 θ 求偏导j 1...m其中 m 是批次大小batch size通常远小于训练集大小 N但又远大于 1。从统计学的角度看m 个样本的平均梯度是全量梯度的一个无偏估计而方差大约是单个样本梯度方差的 1/m。也就是说批次越大梯度估计越准确方向越接近 BGD批次越小噪声越大但计算越轻量。小批量的优势在于m 的大小可以灵活调节让你在梯度准确度和计算效率之间找到一个合适的落点。而且现代深度学习框架比如 PyTorch、TensorFlow的 GPU 并行计算几乎都是针对小批量数据设计的。32 张图片同时过网络并不比 1 张图片慢 32 倍——因为 GPU 擅长并行处理只要显存放得下小批量的额外开销很小。这也是为什么小批量训练几乎成了所有深度学习项目的默认选择。伪代码如下for epoch in range(num_epochs): for batch in make_batches(X, y, batch_sizem): gradient compute_gradient(batch, theta) theta theta - learning_rate * gradient4.2 批次大小的选择逻辑为什么常说2的幂次关于 batch size 的选择有一个非常实际的经验通常取 2 的幂次比如 16、32、64、128、256。这背后的原因主要有两个。第一个原因是硬件对齐。GPU 的显存读取、矩阵运算内部有很多对齐优化2 的幂次维度通常能获得更高效的内存访问模式。第二个原因是全局经验值行业内大量实验表明32 到 256 这个区间在大多数任务上都能取得稳定的效果不需要频繁调整。那么 batch size 太小和太大会带来什么问题batch size 太小比如 1就退化成纯 SGD梯度噪声大收敛不稳定而且 GPU 并行能力利用不足吞吐量反而上不去。batch size 太大比如 2048 甚至更大梯度估计非常准收敛光滑但每一步的计算时间变长而且研究反复验证过过大的 batch size 会导致模型泛化能力变差——具体原因目前学界还没有完全定论但主流解释是大批次梯度更容易收敛到尖峭的局部极小值而小批次引入的噪声反而有助于逃到平坦区域。还有一个现实约束显存装不下。batch size 乘以单样本的显存占用包括中间激活值不能超过 GPU 显存上限。所以我的建议是从 32 开始试验如果显存允许、损失曲线太震荡就往上加到 64 或 128如果数据集很大、想要更快的迭代速度也可以试试 256。不要一上来就追求大 batch size——它不是越大越好。4.3 一个容易忽略的问题数据shuffle小批量梯度下降里有一个细节经常被忽略但影响很大——每个 epoch 开始之前都要把数据重新打乱shuffle。如果不打乱数据训练集中原本按类别顺序排列的样本会被几个 batch 连续读到。比如某个 batch 里全是猫的图片下一个 batch 里又全是狗模型参数就会在猫方向和狗方向之间反复拉扯导致收敛极慢甚至不收敛。打乱数据能让每个 batch 都尽量接近整体分布梯度估计会更稳定。我见过不少新手在这里踩坑数据没打乱loss 曲线呈现周期性的剧烈波动还以为是模型结构出了问题调了半天才发现是数据顺序的锅。还有一个延伸问题shuffle 之后同一个样本在每个 epoch 中会被分到不同的 batch这实际上也为训练过程引入了一点额外的随机扰动和 SGD 的噪声类似对泛化有好处。5. 收敛诊断与学习率调节我踩过的几个坑原理讲完之后这部分才是真正能帮你省时间的部分。梯度下降变体本身并不复杂但实际训练中真正折磨人的是那些看起来对不上号的异常现象。我把自己反复踩过的坑整理一下。5.1 损失曲线震荡先别急着加数据看看学习率遇到 loss 曲线剧烈震荡很多人第一反应是数据有问题或者模型结构不对但我现在的排查顺序固定是先看学习率再看 batch size最后才去检查数据和模型。学习率过大是 loss 震荡的最常见原因。最小化损失函数本质上是在一个曲面上走下山路步长过大每一步都从山坡的一侧跳到另一侧自然会在山谷里来回横跳。这时候把学习率除以 10 试试多半立刻见效。如果学习率已经很小但 loss 还是震荡那就要考虑是不是 batch size 太小导致梯度噪声过大。解决办法很简单把 batch size 往上调。不过 batch size 增加后梯度估计更准学习率也可以适当调大一点。这里有一个我自己一直用的参考思路学习率和 batch size 之间存在一种权衡关系——增大 batch size 时可以同比例增大学习率减小 batch size 时也要把学习率降下来。很多框架自带的 learning rate scaling 规则就是这么推导出来的。5.2 学习率退火训练中后期怎么调固定学习率从头训到尾很少能拿到最佳效果。训练初期参数距离最优解还很远需要较大的步长快速接近训练后期参数已经接近最优解太大的步长反而会在最优点附近来回震荡无法进一步精修。所以实践中几乎都会用学习率衰减learning rate decay也叫退火。常见的策略有三种按轮次衰减每训练一定 epoch学习率乘以一个衰减系数比如每 10 个 epoch 乘以 0.5。按指标衰减当 loss 连续若干个 epoch 不再下降时学习率减半。余弦退火学习率按照余弦函数的形状从初始值平滑降到最小值这种策略在近年来的研究中使用非常频繁。对于新手我推荐从第二种做起也就是loss plateau 就减半。因为它最直观——模型学不动了就给它更小的步长让它更精细地探索。实测下来非常稳妥。5.3 动量与梯度裁剪给抖的更新加个阻尼即使用了小批量梯度下降梯度仍然是有噪声的。为了抑制这种噪声论文和框架里几乎标配了一个改进方案——动量Momentum。它的思想很直观更新参数时不要把当前这一轮的梯度单独作为唯一依据而是和上一轮、上上轮的更新方向做一个加权平均。动量的更新公式可以理解为v γ * v_prev η * gradient θ θ - v其中 γ通常取 0.9控制历史方向的保留程度。这样做的好处是梯度中那些方向不一致的噪声分量会被平均掉而方向一致的真实信号会被不断累加速。这就像一个物理小球下山它有惯性不会因为地面的微小起伏而立刻改变方向。和动量类似的还有梯度裁剪gradient clipping主要用于应对梯度爆炸。当某些样本的梯度特别大——大到更新一步就能把参数甩到无穷远——就需要把梯度的模长限制在一个预设范围内。这在实际训练中非常重要尤其是训练 NLP 模型或循环神经网络时。我个人的经验是动量必开很多框架默认就开了梯度裁剪在出现 NaN 或 loss 突然飙升时优先检查。5.4 一个实操诊断清单最后分享一个我自己每次训练遇到问题时都会走一遍的检查清单检查 loss 曲线是快速发散可能是学习率太大还是慢速不降可能是学习率太小或者特征没标准化。如果 loss 曲线呈现周期性波动先确认数据是否已正确 shuffle。如果出现 NaN优先排查是否有特征值特别大、是否需要梯度裁剪、是否是某些中间计算溢出。如果模型在训练集上已经收敛但验证集效果不佳这个阶段再考虑数据增强、正则化、调整模型结构而不是继续纠结梯度下降的变体。如果你想复现别人的实验结果但怎么都不对先对齐 batch size 和学习率这两个参数的影响往往超过模型结构细节。6. 三种变体的选型对比与最后的经验把这三种变体放在一张表里做对比看起来会更清楚特性批量梯度下降 (BGD)随机梯度下降 (SGD)小批量梯度下降 (Mini-batch)每次更新样本数全部训练集1 个样本m 个样本如 32、64更新频率每个 epoch 一次每个样本一次每个 batch 一次梯度准确性最准确最粗糙、噪声大介于两者之间收敛稳定性最稳定抖动剧烈较稳定可调节对局部最优的逃离能力弱较强中等计算开销极高极低可控GPU 并行利用差差好典型适用场景小数据、凸优化、教学演示在线学习、流式数据深度学习训练默认选择关于选型我的建议其实很简单如果做深度学习、数据量较大、手上有 GPU——用 Mini-batchbatch size 32 起步。如果做在线学习数据以流的形式持续到达——用 SGD配合学习率衰减。如果只是写作业、做 demo、数据小到可以全部算完——可以用 BGD但它并不比 Mini-batch 更优秀只是更容易理解。比较有意思的是虽然随机梯度下降在理论上有很多优雅的性质比如对噪声的鲁棒性但近年来的深度学习实践已经证明小批量梯度下降配合动量、学习率退火是精度和效率综合最优的路线。很多文章里写的 SGD optimizer其实在框架默认实现里往往包含了动量跟教科书里的纯 SGD 已经有区别了这一点需要留意。我自己刚接触这些东西的时候曾经以为梯度下降不就是个求导加更新参数的循环有什么好深究的。后来在真实的训练任务中被各种 loss 异常折磨过几轮才慢慢意识到优化算法是整个训练过程的底层调度器它的每一个超参数都直接影响最终模型的精度走向。参数初始化、数据预处理、模型结构这些当然重要但优化器这一步如果选得不对、调得不细前面一切努力都可能白费。最后分享一点个人经验刚开始调参不要贪心不要指望一上来就配出一套完美组合。我现在的做法是先固定一个合理默认值比如 batch size 64、初始学习率 0.01 或 0.001、动量 0.9跑通一个小规模的完整流程确认模型能正常收敛再逐个超参数地试。一次只动一个变量记录它的影响——这比同时改十个参数然后靠猜有效得多。梯度下降的三种变体只是这条调参之路的起点后面还有 Adam、RMSProp 这些自适应优化器等着你去探索。但把基础的三兄弟吃透你会发现自己读任何优化算法的论文、源码都会轻松不少。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑