拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

批量梯度下降BGD详解:原理、实现与调参避坑指南

批量梯度下降BGD详解:原理、实现与调参避坑指南

很多人第一次接触深度学习,都是从梯度下降这四个字开始的。但说实话,真正要把 Batch Gradient Descent(BGD)讲透,讲明白它为什么是深度学习优化的地基,而不是简单背一个公式,其实并不容易。这篇文章我打算换个角度,不只讲 BGD 是什么,更想讲清楚它为什么长这样、它到底在解决什么问题、以及你在实际写代码和调模型时,哪些地方最容易因为没理解 BGD 而踩坑。

这篇文章适合刚看完深度学习理论、正准备动手写第一个模型的人,也适合已经用框架调过模型但感觉原理模糊、想回头把优化器这块补扎实的同学。我会从数学直觉讲到 Python 手写实现,再到学习率、特征缩放、收敛判断这些实操细节,尽量让每个概念都能落地。

1. 为什么模型训练绕不开梯度下降:从“解方程”到“找最小值”

1.1 深度学习训练的本质是一个优化问题

先问你一个问题:训练一个神经网络,到底是在做什么?

表面上看,我们在做的是“喂数据、算损失、反向传播、更新参数”。但把这一整套动作抽象出来,本质上只有一个目标——调整网络里的每一组权重和偏置,让损失函数的值尽可能小。

损失函数衡量的是模型预测值和真实值之间的差距。比如你做房价预测,模型估了个 300 万,实际成交价是 280 万,误差就体现在损失函数里。训练过程就是不断修改参数,让这个差距一路降下去。

如果你只面对一个线性回归,而且特征维度不多,理论上真的可以不用“训练”——直接对损失函数求导、令导数等于零,解出参数解析解。这就是最小二乘法做的事情。但神经网络不是这样:层数一深、参数量一上来,从几万到几百万甚至更多,损失函数就变成一个极度复杂的非线性高维曲面。你根本不可能写出它的导数表达式,更不可能解方程。这时候,唯一可行的方法就是迭代式地逼近最小值。

BGD 就是最经典、最基础的一种迭代优化算法。它的思路非常朴素:每次沿着损失函数下降最快的方向走一小步,不停重复,直到走到一个足够低的地方。

1.2 一个生活化的类比:摸黑下山

我特别喜欢用“摸黑下山”来类比梯度下降。

想象你在大雾天站在一座山上,能见度不高,看不到山脚在哪里。你唯一能感知的,是自己脚下这块地面的坡度——往哪个方向走,地势会明显向下。梯度下降做的事情就是:在当前这个位置,用你的脚感知一下最陡的下坡方向,然后跨出一步;到了新位置,再感知、再跨步,反复这个过程。

这里有几个关键点,正好对应 BGD 的核心要素:

  • 你感知“坡度”的方式,对应计算损失函数对参数的偏导数,梯度向量的方向就是上升最快的方向,所以我们要取它的反方向来走。
  • 跨出一步的大小,对应学习率(Learning Rate)。步子太大,可能一步跨过谷底甚至滚到对面山坡;步子太小,走到天黑都到不了山脚。
  • 你脚下的山体形状,对应损失函数的曲面。神经网络这个曲面非常崎岖,到处都是局部低洼处,BGD 能不能找到全局最低点,和初始位置、步长、曲面形状都有关。

这个类比虽然简单,但能帮你在心里建立一个图像的框架。以后看到任何梯度下降变体(SGD、Momentum、Adam),你都可以回到这个“下山”场景里理解:它们本质都是在解决“怎么迈步更聪明”的问题。

2. BGD 的数学原理:梯度、方向与步长的拆解

2.1 梯度是什么:多维导数的集合

先从一个参数的情况说起。假设你只有一个参数 w,损失函数是 L(w),它画出来是一条二维曲线。导数 L'(w) 表示曲线在当前 w 处的斜率。导数为正,说明向右走损失变大,所以你该向左;导数为负,说明向右走损失变小,你该向右。

但神经网络里参数成千上万,损失函数就是一个多元函数 L(w₁, w₂, ..., wₙ)。这时候导数升级为偏导数——固定其他参数不变,只对某一个参数求导,看这个方向上损失怎么变化。把所有偏导数拼成一个向量,就是梯度:

∇L = (∂L/∂w₁, ∂L/∂w₂, ..., ∂L/∂wₙ)

梯度的核心意义在于:它指向的是损失函数上升最快的方向。所以要让损失下降,我们就往梯度的反方向走。这正是参数更新公式的由来:

w ← w - η · ∇L

其中 η 是学习率。注意这里矩阵/向量的写法表示所有参数同时更新,不是逐个来。

2.2 学习率:决定每一步迈多大

学习率可能是整个 BGD 里最需要反复调的一个超参数。它直接决定收敛速度和最终精度,甚至决定你能不能收敛。

如果 η 太大,可能出现的情况是:一步跨过了谷底,反而到了损失更高的位置;再跨一步又弹回来。损失曲线就像震荡的弹簧,怎么都平稳不下来。极端情况下,损失会直接爆炸成 NaN。

如果 η 太小,模型倒是稳定了,但每一步只挪一点点,训练几个 epoch 损失还在高位徘徊。这时候你通常会怀疑是不是模型结构写错了,但其实只是学习率压得太保守。

比较稳妥的做法是先用一个 log 尺度的范围做几次小实验,比如 0.1、0.01、0.001、0.0001,每档跑几十个 batch(BGD 里就对应几十轮 epoch),看损失曲线下降速度,再锁定量级。我自己的经验是:与其在 0.01 和 0.008 之间反复横跳,不如先在 0.1 和 0.001 之间感受一下损失曲线的“脾气”,再精细调。

2.3 损失函数曲面:凸与非凸的差别

如果你跑的是线性回归这种凸优化问题,损失函数曲面像一个碗,只有一个全局最低点,BGD 无论从哪开始,只要学习率合适,基本都能收敛到全局最优。

但神经网络的损失曲面是非凸的,有大量局部极小值和鞍点。局部极小值是周围都比它高,你被困在里面出不去;鞍点则是某个方向是下坡、另一个方向是上坡,梯度在鞍点附近接近零,BGD 走到这里容易停步不前。

这是 BGD 的天然短板,也是后来动量(Momentum)等优化器要解决的问题。但作为基础,你先要知道:BGD 不保证找到全局最优,甚至不保证找到好的局部最优。实际训练中,人们通常接受“找到一个足够好的局部极小值即可”,因为神经网络参数极多,损失函数曲面维度极高,不同局部极小点之间的性能差异往往远小于随机初始化带来的差异。

3. BGD 的完整算法流程:从一次迭代到完整训练周期

3.1 算法步骤拆解

把 BGD 写成伪代码,非常简洁:

初始化参数 w(随机或固定策略) 设置学习率 η、最大迭代轮数 T for epoch = 1 to T: 1. 计算整个训练集上的损失:L = (1/N) Σ L(f(x_i; w), y_i) 2. 计算损失对所有参数的梯度:g = ∇w L = (1/N) Σ ∇w L(f(x_i; w), y_i) 3. 更新参数:w = w - η · g 4. (可选)记录损失值、计算验证集表现,判断是否提前停止

第 1 和第 2 步可以一次性前向传播 + 反向传播完成,不需要分开做两次。框架里你调用 loss.backward() 后,梯度就已经存在每个参数的 .grad 属性里了。但你以为 BGD 每个 epoch 只对每个样本算一次梯度吗?对,这就是它最大的特点,也是最大的代价。

3.2 梯度是一个平均值:BGD 的准确性来源

注意公式里那个 1/N。BGD 用整个训练集来计算梯度,得到的是损失曲面在当前点的真实梯度方向(如果能严格遍历所有样本)。这是因为:

∇w L_all = (1/N) Σᵢ ∇w L(xᵢ, w)

它把所有样本的梯度平均起来,相当于对每个样本的“建议方向”做了一个综合裁决。个别样本的噪声和异常值会被平均掉,梯度方向比只用一小批样本更稳定、更准。

这一点既是优点也是缺点。优点是每次更新方向靠谱,损失曲线通常平滑下降,不会剧烈震荡;缺点是你每走一步都得遍历整个数据集,如果数据有十万条、百万条,一次 epoch 的计算量就非常惊人。训练一轮就要等很久,更别提多个 epoch。

3.3 什么时候选择 BGD:小数据集与凸问题

在深度学习的大规模场景下,BGD 因为算力成本太高,几乎不会作为默认选择。但在下面几种情况里,它依然很合适,甚至是最佳选择:

  • 数据集很小:几千条甚至几百条样本,遍历全部样本的代价完全可以接受。
  • 凸优化问题:比如线性回归、逻辑回归,损失函数是凸的,BGD 能稳定收敛到全局最优,还能避免 SGD 的震荡。
  • 需要精确梯度来调试:当你想验证自己手写的反向传播对不对,或者测试一个优化器的行为,BGD 的确定性最好(不引入随机采样),方便复现和排查。

我自己最早手写神经网络的时候,就是用 BGD 在一个几十条样本的 toy dataset 上做验证。每一步行为都可以预期,调参也直观,比一上来就用 Adam 更容易建立直觉。

4. BGD 手写实现:用 Python 从零搭一个完整例子

4.1 生成一份带噪声的线性回归数据

为了让你能直接跑起来看效果,我用最简单的线性回归来演示。虽然线性回归有解析解,但拿它来理解 BGD 刚刚好。

import numpy as np import matplotlib.pyplot as plt # 固定随机种子,保证结果可复现 np.random.seed(42) # 生成 100 个样本,特征一维,真实关系 y = 2x + 3 + 噪声 X = np.random.rand(100, 1) * 4 - 2 # x 范围约 [-2, 2] true_w = 2.0 true_b = 3.0 y = true_w * X + true_b + np.random.randn(100, 1) * 0.5 # 可视化一下数据分布 plt.scatter(X, y, alpha=0.7) plt.xlabel("x") plt.ylabel("y") plt.title("Synthetic Data for BGD Demo") plt.show()

这里的噪声是均值为 0、标准差 0.5 的高斯噪声。有了噪声,损失函数的最小值就不再是精确的 2 和 3,而是落在它们附近。BGD 的目标就是尽量逼近。

4.2 定义损失函数与梯度计算

线性回归里我们通常用均方误差(MSE)作为损失:

L(w, b) = (1/N) Σᵢ (w·xᵢ + b - yᵢ)²

对 w 求偏导:

∂L/∂w = (2/N) Σᵢ (w·xᵢ + b - yᵢ) · xᵢ

对 b 求偏导:

∂L/∂b = (2/N) Σᵢ (w·xᵢ + b - yᵢ)

这里我为了代码清晰,没有把 2 约掉。实践上你完全可以把它吸收进学习率,因为常数因子最会被 η 缩放吸收。关键是你前后保持一致即可。

写成代码:

def mse_loss(w, b, X, y): N = len(X) pred = w * X + b return np.mean((pred - y) ** 2) def compute_gradient(w, b, X, y): N = len(X) pred = w * X + b grad_w = (2 / N) * np.sum((pred - y) * X) grad_b = (2 / N) * np.sum(pred - y) return grad_w, grad_b

注意到这里我用了np.mean来计算损失,而梯度里除以 N,两者是配套的。很多入门教程在损失里用np.sum,梯度里又除以 N,结果虽然只差一个常数倍数,但初学者对照公式时容易懵,建议全程统一。

4.3 训练循环:参数更新与轨迹记录

核心训练循环如下:

# 初始化参数 w = 0.0 b = 0.0 # 超参数 learning_rate = 0.05 epochs = 300 # 记录历史,方便可视化 loss_history = [] w_history = [] b_history = [] for epoch in range(epochs): grad_w, grad_b = compute_gradient(w, b, X, y) # BGD 参数更新:所有参数同时更新 w -= learning_rate * grad_w b -= learning_rate * grad_b # 每 10 轮记录一次 if epoch % 10 == 0: loss = mse_loss(w, b, X, y) loss_history.append(loss) w_history.append(w) b_history.append(b) print(f"epoch {epoch:3d}: w = {w:.4f}, b = {b:.4f}, loss = {loss:.6f}")

跑完 300 轮,你会看到 w 逐渐从 0 逼近 2 左右,b 逼近 3 左右。以下是某一次运行的典型中间输出(你的结果因为随机种子不同可能略有偏差):

epoch 0: w = 0.1780, b = 0.0600, loss = 10.216190 epoch 10: w = 1.3572, b = 1.5230, loss = 1.896358 epoch 30: w = 1.8903, b = 2.7242, loss = 0.285412 epoch 50: w = 1.9830, b = 2.9239, loss = 0.224301 epoch 100: w = 2.0147, b = 3.0190, loss = 0.220120 epoch 200: w = 2.0176, b = 3.0299, loss = 0.219577 epoch 300: w = 2.0176, b = 3.0302, loss = 0.219569

你可能会注意到,到第 50 轮左右损失就降得差不多了,后面 100 到 300 轮几乎看不出变化。这就是 BGD 的特性:梯度方向稳定,到接近最优点附近时梯度幅值很小,更新步长也随之变小,收敛变得非常平缓。不用担心,这个表现是正常的。

4.4 把损失曲线和参数轨迹画出来

训练完成后,画一下损失曲线和参数轨迹,你会对“收敛”有非常直观的感受。

plt.figure(figsize=(12, 4)) plt.subplot(1, 2, 1) plt.plot(range(len(loss_history)), loss_history, marker=".") plt.xlabel("epoch (x10)") plt.ylabel("MSE loss") plt.title("Loss Curve") plt.subplot(1, 2, 2) plt.plot(w_history, b_history, marker=".", alpha=0.7) plt.scatter([true_w], [true_b], color="red", label="true params") plt.xlabel("w") plt.ylabel("b") plt.title("Parameter Trajectory") plt.legend() plt.tight_layout() plt.show()

参数轨迹图上,你会看到一条从 (0,0) 出发、弯弯曲曲最终停在 (2.02, 3.03) 附近的线。这个轨迹其实就是下山路径在地图上的投影。BGD 的轨迹通常比 SGD 平滑很多,因为每一步都是全量样本的平均方向,极少突然跑偏。

5. 学习率与特征缩放:BGD 收敛快慢的两个决定性因素

5.1 学习率调试:这个参数怎么试

我知道很多人上来就直接设 learning_rate = 0.01 或者 0.001,然后开着训练去看 loss 曲线。如果曲线不降,就开始怀疑人生。学习率的选择其实有一些经验性的方法:

  • log 坐标扫描:在 {1, 0.1, 0.01, 0.001, 0.0001} 里各跑一小段时间,画 loss 曲线。观察哪一档能让 loss 稳步下降又不震荡。
  • 观察初始几步的 loss 变化:如果第一步 loss 反而暴增(比如从 10 跳到 1000),多半是学习率太大。如果 loss 变化很慢,说明太小,可以按 10 倍往上加。
  • 用完一个 epoch 后 loss 变化小于 1%:可以考虑把学习率调大一档,加速收敛。

以下是我常用的一组对比实验(同样的数据和初始化,不同学习率下的表现):

学习率初始阶段表现最终效果
1.0loss 剧烈震荡,可能发散无法收敛
0.1下降很快,但接近最优点后仍有小幅度震荡收敛较快,最终精度略差
0.01平稳下降,速度适中收敛到非常接近最优点
0.001下降太慢,300 轮可能还没到最优附近需要更多轮数

这里没有绝对“正确”的学习率,它取决于你的损失函数尺度。同一个 0.01,在 MSE 损失上是合理的,在交叉熵损失上可能就很保守或过于激进。所以每次换损失函数,学习率都得重新试。

5.2 特征缩放:梯度方向被拉歪了怎么办

这是一个特别容易被忽略、但对 BGD 影响巨大的细节。

如果输入特征 X 的两个维度尺度差异很大,比如特征 A 取值在 0~1,特征 B 取值在几千到几万,那么损失函数的等高线会被拉成非常扁的椭圆形。这时候梯度方向并不是直指椭圆中心,而会沿着长轴方向反复来回曲折,形成“之”字形路径。你可以理解为下山时不是朝山脚走直线,而是顺着一个很窄的峡谷左冲右突,走得极其缓慢。

解决办法就是特征标准化:

def standardize(X): mean = np.mean(X, axis=0) std = np.std(X, axis=0) return (X - mean) / std

标准化之后,不同特征的尺度都落在均值为 0、标准差为 1 的范围,损失函数的等高线更接近圆形,梯度方向能更直接地指向最优点。不仅收敛速度大幅提升,你选择学习率时也会更容易。

拿我自己的例子来说,做一个多特征线性回归时,一开始没注意特征缩放,BGD 跑了 2000 轮才收敛到一个普通精度;标准化之后,200 轮就达到甚至超过之前的精度。差别就是这么大。

5.3 参数初始化:起点也会影响收敛

BGD 对初始参数敏感吗?在凸问题里不敏感,因为全局最优只有一个,从哪出发最终都一样。但在神经网络里非常敏感,因为非凸损失曲面上有大量局部极小值。

常见的初始化策略大致是:

  • 线性回归、逻辑回归:可以全零初始化,凸问题没关系。
  • 神经网络:使用随机初始化,每个权重从均值为 0、标准差合适的小范围内采样(Xavier/He 初始化都是这么设计的),目的是让每一层输出的方差保持在合理范围,避免梯度消失或爆炸。

实操中,你看到的许多模型训练结果差异大,一部分就来自随机初始化。这也就是为什么你跑同一个训练脚本两次,结果不完全一样。

6. BGD、SGD 与 Mini-batch GD:三种梯度下降该怎么选

6.1 从全量到单个样本的极端对比

梯度下降这个家族里,最著名的三个成员就是 BGD、SGD 和 Mini-batch GD。它们的唯一区别就在于:每一次参数更新,用多少个样本来计算梯度。

  • BGD:用全部 N 个样本,1 个 epoch 只更新 1 次,梯度最稳定,计算最慢。
  • SGD:用 1 个样本,1 个 epoch 更新 N 次,速度快但有剧烈震荡,每次梯度方向噪声大。
  • Mini-batch GD:用一小批样本(比如 32/64/128 条),在效率和稳定性之间取折中。这是深度学习训练实际最常用的方案。

我建议你把三者画在一个对比表里,方便记忆:

方法每次更新样本数每 epoch 更新次数梯度稳定性收敛速度(每 epoch)典型场景
BGDN(全部)1最高慢小数据集、凸优化
SGD1N最低,噪声大快但震荡大规模数据,配合退火学习率
Mini-batchm(如 32/64)N/m中等快且较稳深度学习标准选择

6.2 为什么深度学习默认选 Mini-batch 而不是 BGD

深层神经网络参数多、数据量大,BGD 每更新一次都要跑完整个数据集,训练一轮就要数小时甚至数天,完全没法接受。SGD 虽然每轮次数多,但每次只算一个样本,实际也不划算——它没法利用 GPU 的并行计算能力,矩阵运算一次只喂一条数据,浪费算力。

Mini-batch 每次喂几十条或几百条样本,既能并行计算,又不至于像 BGD 那样等太久。梯度方向介于两者之间:小的 mini-batch 天然带有噪声,这种噪声有时候反而是好事——它帮助损失函数跳出浅的局部极小值。BGD 的梯度太“准”了,反而容易陷在局部极小点里不动弹。

有一句话可以总结这个折中思想:全量梯度看着最聪明,但每一步都太贵;单样本梯度看着很笨,但胜在便宜且能意外“逃出生天”;小批量梯度既不贵也不笨,是现实中的最佳平衡。

6.3 BGD 在真实团队项目里的位置

我在实际做项目时,BGD 一般出现在四类地方:

  1. 教学和验证代码:手写神经网络 demo,用来验证反向传播公式的推导是否正确。
  2. 小规模基线模型:数据量在几千条以内、特征也少的线性模型,BGD 足够,连优化器都不用引。
  3. 联邦学习中的全量本地更新:某些客户端本地数据量小,在本地做一次全量梯度计算反而简单可控。
  4. 对比实验的对照组:当你想量化 Mini-batch 带来的加速或效果提升时,BGD 是最自然的基准线。

但如果是上千上万特征、百万级样本的工业场景,我会直接选 Mini-batch + Adam/带动量的 SGD,BGD 只会作为教学概念存在。

7. 在 PyTorch 里 BGD 到底长什么样:框架视角的对应关系

到这里,你可能会有个疑问:我在 PyTorch 里写optimizer = torch.optim.SGD(model.parameters(), lr=0.01),这是 BGD 还是 SGD?

答案是:取决于你喂给它的数据量。PyTorch 的SGD优化器本身只是一个参数更新规则,它不关心你用多少数据算出的梯度。你可以在每个 epoch 里遍历全部数据、累加或平均梯度后再调 optimizer,这就是 BGD;你也可以每个 batch 调一次 optimizer,那就是 Mini-batch/SGD。

一个标准的 PyTorch 训练循环通常长这样:

import torch import torch.nn as nn import torch.optim as optim model = nn.Linear(1, 1) loss_fn = nn.MSELoss() optimizer = optim.SGD(model.parameters(), lr=0.05) X_t = torch.tensor(X, dtype=torch.float32) y_t = torch.tensor(y, dtype=torch.float32) epochs = 300 for epoch in range(epochs): # 前向传播 + 损失计算 pred = model(X_t) loss = loss_fn(pred, y_t) # 梯度清零,反向传播,更新参数 optimizer.zero_grad() loss.backward() optimizer.step() if epoch % 10 == 0: print(f"epoch {epoch:3d}, loss = {loss.item():.6f}")

注意:这里每个 epoch 我只在全量数据上算了一次 loss 和一次梯度,然后调一次optimizer.step(),所以它等价于 BGD。

如果想明确使用 Mini-batch,你就需要把数据分成多个 batch,每个 batch 内调用一次optimizer.step(),一个 epoch 会更新多次。这个区别非常本质,很多人刚学时容易把优化器和数据加载混为一谈。

另外提醒一个细节:PyTorch 里如果你用了 batch 训练,一般会在每个 batch 开始前调用optimizer.zero_grad()清空上一 batch 的梯度。如果你忘了清零,梯度会累加到上一个 batch 的梯度上,这其实就变成了一个累加梯度版本,行为会和你预期的 BGD/Mini-batch 完全不同。

8. BGD 的常见坑与调试方法:真实踩过的三个问题

8.1 损失曲线不降反升

这是所有梯度下降问题里最经典的“翻车现场”。如果你看到 loss 曲线从一开始就往上走,或者先降后暴增,第一嫌疑是学习率太大,第二嫌疑是梯度计算错了,第三嫌疑是数据没归一化。

我的排查顺序是这样的:

  1. 先打印初始 loss,确认它不是 NaN 或无穷大。
  2. 把学习率除以 10,重跑几十轮。如果曲线从“暴增”变成“缓慢下降”,确认是学习率问题。
  3. 如果学习率已经很小很小(比如 1e-7)还是不降,那就要检查梯度公式了。可以自己用数值梯度法验证一下:(f(x+h) - f(x-h)) / 2h,和你的解析梯度对比,误差应该在 1e-5 以内。

数值梯度验证是调试反向传播最好的工具,没有之一。你不需要每次训练都做,但每次手写梯度代码改完之后,都应该跑一遍这个检查。

8.2 收敛到错误的最优值:初始化惹的祸

有一次我在一个多峰函数上测试 BGD,初始点选在了一个局部极小值附近,结果模型一直收敛不到全局最优。我一度以为是学习率问题,反复调参都没用。后来把初始值换了个位置,很快就找到了更优解。

这也提醒了我:BGD 一定意义上是一种“局部方法”,它的视野只有脚下这一小块。如果你知道问题有多峰,要么多试几个随机初始化,要么引入退火或动量,要么换其他优化器。

8.3 所有样本的梯度平均值掩盖了重要信息

BGD 的梯度是全量平均。好处是稳定,坏事是——如果数据集里存在类别不平衡,或者某些样本是“关键少数”(比如异常检测场景里的少数异常样本),全量平均很容易把少数样本的贡献稀释掉。

举个例子:100 个样本里只有 1 个正样本,99 个负样本。BGD 算出来的平均梯度几乎完全由负样本主导,正样本的梯度贡献微乎其微,模型就会倾向于把所有样本都预测为负类,哪怕这个正样本非常重要。这时候,要么换 Mini-batch 增加采样随机性,要么用加权损失,要么做数据重采样。不要指望 BGD 自动处理不平衡问题。

9. 理解 BGD 之后,再看 Adam、Momentum 就简单了

9.1 每种新优化器都是在解决一个 BGD 的痛点

BGD 的三大痛点:局部极小值困局、收敛速度慢、对学习率敏感。后来的优化器基本就是针对这三点逐一开刀:

  • Momentum(动量):参数更新时保留一部分历史速度方向,相当于下山时带了一股冲劲。遇到小的局部坑,可以凭惯性冲出去;在平坦区域也能加快步伐。
  • AdaGrad/RMSProp:按参数的历史梯度幅值调整每个参数的学习率,让稀疏特征和频繁更新的特征有各自合适的步长。
  • Adam:结合了动量和 RMSProp 的思路,带一阶动量估计和二阶梯度平方估计,是目前入门最常用的默认优化器。
优化器核心思想对应解决的 BGD 痛点
Momentum积累历史梯度方向局部极小值、慢收敛
AdaGrad按梯度历史幅值缩放学习率稀疏特征、学习率难调
Adam动量 + 自适应学习率上述所有痛点的综合方案

9.2 为什么学了 BGD,Adam 文档里的参数你就看得懂了

Adam 的参数里有一个 β₁(通常 0.9)和一个 β₂(通常 0.999)。它们分别控制“记住历史梯度方向”和“记住历史梯度平方”的衰减速度。如果你理解了下山类比里的“速度”和“坡度感知”,你就知道 β₁ 是惯性系数,β₂ 是“很早就走过的路也要缓慢计入”的滑动窗口。之后再用 PyTorch 的Adam调参时,就不再是盲调了。

从这个角度看,BGD 不是一个孤立的数学知识点,而是一个“基准坐标系”。你用它理解之后,任何优化器都是在回答同一个问题:每一步怎么走,才能更快、更稳、更准地到达山脚?

我是很鼓励学习者自己动手把 BGD 写一遍的,哪怕只是一个 10 行代码的线形回归。实际跑过之后,你对学习率、特征缩放、梯度方向这些概念会有完全不同的体感。以后不管换什么框架、什么优化器,你都知道它们在最底层都在做同一件事——沿着梯度反方向,小心翼翼地,一步一步往下走。

返回列表