跳到主要内容

优化器进化链 — 梯度下降怎么一步步变成 Adam

这一章讲三件事: 「训练」到底在优化什么(答案会让很多人意外); 梯度下降怎么走(拿一组真实数字走一遍);以及从梯度下降到 Adam 的完整进化链—— 每一代优化器都在修上一代的一种病。 读完你能回答「为什么大家的默认优化器都是 Adam」这类问题。

1. 先把「训练」说准

第 04 章说训练就是「调权重」。按什么方向调、调多大幅度,就是优化(optimization)的活。 原书 3.3 开篇的框架句:训练就是找一组让损失函数最小的参数值—— 损失函数(loss)把「预测与真值的差距」变成一个数,优化器负责在超高维的参数空间里往低处走1

但这里埋着全书第 3 章最重要的一句提醒,值得单独成段:

优化算法只会最小化训练误差;深度学习要的是低泛化误差—— 在没见过的数据上的表现。这两件事不是一回事2

「训练误差降到零」完全可能只是背题(第 03 章的过拟合)。所以原书开出的组合拳是: 正则化(L1/L2)、dropout(训练时随机掐掉一部分神经元,逼网络不依赖任何单个神经元)、 早停(验证集成绩开始变差就停)、数据增强(旋转翻转造新样本)3优化器管左半边,泛化手段管右半边,别指望优化器自己解决过拟合。

2. 主走查:梯度下降,一组真实数字

梯度下降(gradient descent)的机制一句话:沿「往低处走最陡」的方向挪一小步,重复4。 「最陡的方向」就是负梯度——梯度是「每个参数该往哪边调、调多少最见效」的信号。

原书 3.6 有一组可以直接复算的实测:造 100 条数据,真实规律是 y = 3·x₁ + 2·x₂ + 噪声,让网络自己把三个参数(两个权重+一个偏置)学回来。 学习率(每步挪多大)拍成 0.01。

迭代(反复重做)100 个来回。

每个来回,行话叫轮——一轮就是完整过一遍训练数据5:

变体每步用多少数据学到的参数(书里打印值)对照真实值
batch(全量)全部 100 条[−0.052, 3.111, 1.945][0, 3, 2]
mini-batch(小批:每步只喂 10 条)每步 10 条[0.029, 3.062, 1.948][0, 3, 2]

两个变体都把 3 和 2 学了回来(误差百分之几,因为数据里有噪声)。 差别在节奏:全量每一步都用完整数据算梯度,方向准、走得稳但每步贵; mini-batch 每步只用 10 条,梯度带噪声、方向晃,但每步便宜几十倍—— 同样时间能走几十步,总量上反而先到6

至于SGD(随机梯度下降,每步只用 1 条数据),原书概念讲对了 (噪声大、震荡多,但噪声反而能帮它跳出浅坑)7, 注意:它的代码在小节里贴错了(SGD 一节贴的是 mini-batch 的代码, mini-batch 一节贴的是 batch 的代码)——概念看正文,代码别对号入座8。 实践中,「mini-batch」就是行业默认,深度学习里说 SGD 常泛指这一族9

3. 进化链:五步,每步修一个病

下面是本章的主脊。每一代优化器回答同一个问题:上一步为什么走不好?

第 1 步:病 = 狭窄的峡谷走不动 → 药 = 动量

损失面上常见一种地形:一个方向很陡、另一个方向很平(峡谷)。 纯梯度下降在谷底来回撞墙、沿谷方向挪得极慢。 动量(momentum)的修法:给优化器加「惯性」——不再只信当前梯度, 而是维护一个速度 v,每步 v = β·v + 学习率·梯度,然后参数减去 v(β 典型取 0.9)10

为什么有效: 同方向的梯度在速度里累积(越走越快), 来回震荡的梯度在速度里互相抵消(撞墙不再用力)—— 陡方向震荡被抹平,平方向加速,原书说这正是它能「冲过平原、逃出浅坑」的原因11

第 2 步:病 = 动量冲过头 → 药 = Nesterov 先看一眼

动量靠惯性,惯性会在坡顶冲过头。Nesterov 加速梯度(NAG)的修法只改一处: 先按惯性挪到「预计位置」,在那个位置算梯度,再决定怎么落步—— 先前瞻,再修正。原书公式:gradient = grad_fn(x − momentum × velocity)12。 用大白话说:普通动量是「闭眼踩油门」,NAG 是「探出头看一眼再打方向盘」。

第 3 步:病 = 一个学习率伺候不了所有参数 → 药 = AdaGrad

网络里参数的命运极不均匀:有的参数梯度常来(常见特征),有的参数梯度稀少(稀有特征)。 固定学习率下,常来的学过头,稀少的学不动。 AdaGrad 的修法:给每个参数记一本账——历史梯度平方的累加和 G; 学习率除以 √G。梯度来得多的人账厚、步子自动缩小;来得少的人账薄、步子相对大13

为什么有效: 稀有特征的参数终于得到像样的更新,这正是稀疏数据(很多特征只在少数样本出现)需要的。

第 4 步:病 = AdaGrad 的账只增不减 → 药 = RMSprop

AdaGrad 致命缺陷:G 只加不减,走了足够多步之后 √G 巨大, 学习率被压到趋近零——模型越学越慢,最后瘫痪。原书原话:学习率会变得过小、阻碍收敛(收敛=越走越接近定点、变化越来越小)14RMSprop 的修法:把「累加」改成「指数衰减平均」—— cache = 0.9·cache + 0.1·新梯度²(0.9 是书里例子用的衰减率)15。 旧账按比例折旧,recent 的梯度说了算。步子不再单调萎缩。

第 5 步:病 = RMSprop 没有动量 → 药 = Adam 合体

Adam(adaptive moment estimation)把两支汇合: 一阶矩 m(动量那套:梯度的指数衰减平均)+ 二阶矩 v(RMSprop 那套:梯度平方的指数衰减平均), 再加一道偏差修正:前几步 m、v 都偏小(从零起算),按 1/(1−βᵗ) 放大回来16。 默认超参数:β1 = 0.9,β2 = 0.999,ε = 1e-817——你今天在任何框架里看到的默认值就是它们。

优化器修的病一句话
动量峡谷震荡给梯度加惯性
NAG冲过头先前瞻再落步
AdaGrad学习率一刀切按历史梯度给每个参数定步幅
RMSpropAdaGrad 账本只增不减历史折旧,近年梯度说了算
AdamRMSprop 缺动量动量 + 自适应步幅 + 偏差修正

这就是「为什么默认都用 Adam」的答案:它不是单项冠军,是毛病最少的那一个。

4. 学习率:比选哪个优化器更要紧的旋钮

本章开头的实验里,学习率 0.01 是拍定的。原书 3.13 把这门学问归成四招18:

  • 阶梯衰减:每过固定轮数把学习率乘个系数往下砍;
  • 指数退火:随时间连续衰减;
  • 看平台降:验证成绩连续几轮不涨,就降学习率再试;
  • warmup:训练初期先用小步热身,稳住再加速。

书里给了这套流程的实测结果:一个 LeNet 卷积网络在 Fashion-MNIST (6 万张灰度服饰图、10 类)上,测试集准确率 84.12%——这是全书少有的端到端跑通的完整数字19但必须如实标注:这一节的标题是学习率调度(随训练进度调步子大小),代码里却固定 lr=0.001、没有任何调度器, 训练循环还有缩进错乱——概念部分正确,代码和实验没有兑现标题的许诺20

5. 二阶方法:另一种思路(L-BFGS)

一阶方法只用「坡度」信息。L-BFGS 属于拟牛顿法:试图再利用「坡度怎么变」(曲率)信息, 相当于不只看坡有多陡,还猜坡的形状,一步迈得更准。完整曲率(行话叫 Hessian 矩阵) 在深度网络的参数量下根本存不下,所以 L-BFGS 用最近若干步的历史近似它, 原书定位很准:适合目标平滑、参数量中等的问题——大网络训练场上仍是一阶方法的天下21

6. 作者的判断与证据

  • 有实测数字可引的: 第 2 节那组 [3.062, 1.948] 与 [3.111, 1.945] 是书里打印的真实输出; LeNet 的 84.12% 同样是书里报告的实测(尽管代码有瑕疵)22
  • 有完整推导的: Adam 的五条更新式(含偏差修正)在书里写全了,与学界标准一致23
  • 本书的硬伤(第 01 章地图的展开): 3.4 凸性、3.5 约束两节与深度学习主线几乎无关 (是标准最优化教科书内容),且 3.5 的两个例子的打印输出违反了各自代码里写的约束—— linprog 例子输出 B 项目 4950 小时而正文限定总工时 100 小时;cvxpy 例子在 「两数之和 ≤1」的约束下打印出解 [≈0, 3.0]24这两节的输出一律不可引。
  • 另一个坏掉的演示: Adam 一节的示例把「梯度」也用随机数生成了一次、循环内从不更新, 优化器实际上在对着一个固定的随机向量「优化」,与损失函数完全脱钩—— 代码能跑,但什么也没演示25

判断(我们的,不是书里的): 原书第 3 章把优化器按「目录」平铺 (每个算法一节,各自公式各自代码),读完容易记住一堆名字而记不住差别。 本章改成「进化链」讲法——每代修上一代的病——不是我们发明的组织方式, 但它是理解这族算法唯一省力的方式:记住「病」就记住了「药」。 如果错,会错在: 如果某一步的「病」在特定任务里恰好是优点 (比如 AdaGrad 的步幅单调萎缩在极稀疏数据上反而稳), 「修病」的叙事会让读者低估它的适用场景——进化链是记忆术,不是价值排序。

7. 边界与局限

  • 原书没有反向传播。 「梯度怎么算出来」(误差从输出层逐层往回推的链式过程) 原书只在第 1 章历史部分提过名字,第 3 章直接假设梯度已到手。 这是本书最大的结构性缺口之一:没有反向传播,「训练」只讲了后半截。
  • 原书没讲的学习率实践: 网格/随机搜索、warmup 的具体时长经验、以及 「Adam + 阶梯衰减」这类混合用法。
  • 优化器之外的两件套,原书只点名: 权重初始化(原书说「好初始化能避开糟糕局部极小」, 没讲 Xavier/He 的公式)与批归一化(第 09 章会在 GAN 那一章补它的动机)26

8. 可带走的

  1. 优化器最小化的是训练误差;泛化靠正则化/dropout/早停/数据增强——两套工具别混为一谈;
  2. 梯度下降 = 沿负梯度挪小步;batch 准而贵,mini-batch 晃而便宜,行业默认是 mini-batch;
  3. 主走查一组数:真实规律 y=3x₁+2x₂,mini-batch 学回 [3.062, 1.948]——「学规律,不背答案」的实证;
  4. 进化链五步记法:动量治震荡,NAG 治冲动,AdaGrad 治一刀切,RMSprop 治账本膨胀,Adam 全都要;
  5. Adam 默认超参 β1=0.9、β2=0.999、ε=1e-8——看到「Adam(lr=3e-4)」就知道在拧哪个旋钮;
  6. 学习率是比优化器选择更要紧的旋钮;四招:阶梯衰减、指数退火、看平台降、warmup;
  7. L-BFGS 用曲率信息、参数量中等时好用;大数据集仍是一阶方法的天下;
  8. 原书 3.4-3.5 两节的打印输出与自身约束矛盾,不可引用;Adam 的示例演示是坏的;
  9. 全书缺口:反向传播一次没讲——「梯度从哪来」要去别的书补。

9. 原文地图

主题原书章原文位置
训练=最小化损失3.3 Optimization and Deep Learningtext/28-ch03-03-3-3-optimization-and-deep-learning.txt:6(搜「minimize the loss」)
训练误差≠泛化误差3.3 Optimization and Deep Learningtext/28-ch03-03-3-3-optimization-and-deep-learning.txt:49(搜「minimizing the training error」)
泛化四件套3.3 Optimization and Deep Learningtext/28-ch03-03-3-3-optimization-and-deep-learning.txt:57(搜「Regularization」) · text/28-ch03-03-3-3-optimization-and-deep-learning.txt:61(搜「Dropout」) · text/28-ch03-03-3-3-optimization-and-deep-learning.txt:65(搜「Early Stopping」) · text/28-ch03-03-3-3-optimization-and-deep-learning.txt:70(搜「Data Augmentation」)
梯度下降机制3.6 Gradient Descenttext/31-ch03-06-3-6-gradient-descent.txt:25(搜「steepest descent」)
学习率两难3.6 Gradient Descenttext/31-ch03-06-3-6-gradient-descent.txt:31(搜「learning rate」)
实测数据 y=3x+2x3.6 Gradient Descenttext/31-ch03-06-3-6-gradient-descent.txt:218(搜「3 * X」) · text/31-ch03-06-3-6-gradient-descent.txt:224(搜「0.01」)
mini-batch 实测输出3.6 Gradient Descenttext/31-ch03-06-3-6-gradient-descent.txt:242(搜「0.02922543」)
batch 实测输出3.6 Gradient Descenttext/31-ch03-06-3-6-gradient-descent.txt:359(搜「0.05168228」)
SGD 概念与代码错位3.6 Gradient Descenttext/31-ch03-06-3-6-gradient-descent.txt:126(搜「escape local minima」) · text/31-ch03-06-3-6-gradient-descent.txt:177(搜「mini_batch」)
mini-batch 是默认3.6 Gradient Descenttext/31-ch03-06-3-6-gradient-descent.txt:259(搜「most commonly used」)
动量公式3.7 Momentumtext/32-ch03-07-3-7-momentum.txt:23(搜「vt」) · text/32-ch03-07-3-7-momentum.txt:38(搜「accumulate」)
动量治震荡3.7 Momentumtext/32-ch03-07-3-7-momentum.txt:43(搜「oscillating」)
Nesterov 前瞻3.8 Nesterov Accelerated Gradienttext/33-ch03-08-3-8-nesterov-accelerated-gradient.txt:47(搜「grad_fn」) · text/33-ch03-08-3-8-nesterov-accelerated-gradient.txt:35(搜「look ahead」)
AdaGrad 机制3.9 AdaGradtext/34-ch03-09-3-9-adagrad.txt:7(搜「adapts the learning rate」) · text/34-ch03-09-3-9-adagrad.txt:11(搜「sum of squared gradients」)
AdaGrad 学步塌缩3.9 AdaGradtext/34-ch03-09-3-9-adagrad.txt:38(搜「too small over time」)
RMSprop 衰减平均3.10 RMSproptext/35-ch03-10-3-10-rmsprop.txt:16(搜「exponentially decaying」) · text/35-ch03-10-3-10-rmsprop.txt:60(搜「decay_rate」)
Adam 更新式3.11 Adamtext/36-ch03-11-3-11-adam.txt:16(搜「update rule」) · text/36-ch03-11-3-11-adam.txt:20(搜「Corrected」)
Adam 默认超参3.11 Adamtext/36-ch03-11-3-11-adam.txt:35(搜「0.9」) · text/36-ch03-11-3-11-adam.txt:36(搜「1e」)
Adam 演示坏掉3.11 Adamtext/36-ch03-11-3-11-adam.txt:106(搜「np.random.randn(1)」)
L-BFGS 定位3.12 L-BFGStext/37-ch03-12-3-12-l-bfgs.txt:9(搜「efficient approximation」) · text/37-ch03-12-3-12-l-bfgs.txt:28(搜「moderate」)
调度四招3.13 Learning Rate Schedulingtext/38-ch03-13-3-13-learning-rate-scheduling.txt:46(搜「Annealing」) · text/38-ch03-13-3-13-learning-rate-scheduling.txt:52(搜「Plateau」) · text/38-ch03-13-3-13-learning-rate-scheduling.txt:58(搜「Warmup」)
Fashion-MNIST 84.12%3.14 Summarytext/39-ch03-14-3-14-summary.txt:5(搜「84.12」)
调度节无调度器3.13 Learning Rate Schedulingtext/38-ch03-13-3-13-learning-rate-scheduling.txt:131(搜「lr=0.001」)
初始化与架构对策3.4 Convexitytext/29-ch03-04-3-4-convexity.txt:9(搜「Initialization Strategies」) · text/29-ch03-04-3-4-convexity.txt:21(搜「skip connections」)
linprog 输出违反约束3.5 Constraintstext/30-ch03-05-3-5-constraints.txt:203(搜「4,950」) · text/30-ch03-05-3-5-constraints.txt:157(搜「100 hours」)
cvxpy 输出违反约束3.5 Constraintstext/30-ch03-05-3-5-constraints.txt:264(搜「5.9999」) · text/30-ch03-05-3-5-constraints.txt:240(搜「sum(x) <= 1」)
凸性与 DL 的关系薄弱3.4 Convexitytext/29-ch03-04-3-4-convexity.txt:36(搜「convex-like」)

Footnotes

  1. 出处:「3.3 Optimization and Deep Learning」第 6 段(text/28-ch03-03-3-3-optimization-and-deep-learning.txt:6,搜「minimize the loss」)。原文:训练即找最优参数值以最小化损失函数;优化算法在高维参数空间里迭代走向最优。

  2. 出处:「3.3 Optimization and Deep Learning」第 49 段(text/28-ch03-03-3-3-optimization-and-deep-learning.txt:49,搜「minimizing the training error」)。原话:「While optimization algorithms focus on minimizing the training error, deep learning aims to achieve low generalization error」。

  3. 出处:「3.3 Optimization and Deep Learning」第 57 段(搜「Regularization」)、第 61 段(搜「Dropout」)、第 65 段(搜「Early Stopping」)、第 70 段(搜「Data Augmentation」)。dropout 的「随机掐掉神经元」机制在第 62 段。

  4. 出处:「3.6 Gradient Descent」第 25 段(text/31-ch03-06-3-6-gradient-descent.txt:25,搜「steepest descent」)。

  5. 出处:「3.6 Gradient Descent」第 218 段(text/31-ch03-06-3-6-gradient-descent.txt:218,搜「3 * X」)与第 224 段(搜「0.01」)。数据生成:y = 3 * X[:, 0] + 2 * X[:, 1] + np.random.randn(100);learning_rate=0.01,num_iterations=100;mini-batch 的 batch_size=10 在第 225 段。

  6. 出处:「3.6 Gradient Descent」第 242 段(text/31-ch03-06-3-6-gradient-descent.txt:242,搜「0.02922543」)与第 359 段(搜「0.05168228」)。mini-batch 输出 [0.02922543 3.06187484 1.94812191],batch 输出 [−0.05168228 3.11146125 1.94541002]。

  7. 出处:「3.6 Gradient Descent」第 126 段(text/31-ch03-06-3-6-gradient-descent.txt:126,搜「escape local minima」)。原文:SGD 梯度估计方差高、震荡多,但在噪声或非凸问题里有利于跳出局部极小。

  8. 出处:「3.6 Gradient Descent」第 177 段(text/31-ch03-06-3-6-gradient-descent.txt:177,搜「mini_batch」)与第 296 段(搜「batch_gradient_descent」)。3.6.2(SGD 节)贴的代码定义的是 mini_batch_gradient_descent;3.6.3(mini-batch 节)贴的是 batch_gradient_descent

  9. 出处:「3.6 Gradient Descent」第 259 段(text/31-ch03-06-3-6-gradient-descent.txt:259,搜「most commonly used」)。原文:mini-batch 是深度学习中最常用的变体,能利用现代硬件加速器的并行性。

  10. 出处:「3.7 Momentum」第 23 段(text/32-ch03-07-3-7-momentum.txt:23,搜「vt」)。更新式:v_t = β·v_{t−1} + 学习率·梯度;x_t = x_{t−1} − v_t;β 取 0 到 1 之间(第 30 段)。

  11. 出处:「3.7 Momentum」第 43 段(text/32-ch03-07-3-7-momentum.txt:43,搜「oscillating」)。原文:方向一致的梯度贡献更多、震荡的梯度影响被削弱;第 46 段补了「穿越峡谷、逃出局部极小、加速收敛」。

  12. 出处:「3.8 Nesterov Accelerated Gradient」第 47 段(text/33-ch03-08-3-8-nesterov-accelerated-gradient.txt:47,搜「grad_fn」)与第 35 段(搜「look ahead」)。原文:先按动量估出未来位置,在那个位置算梯度。

  13. 出处:「3.9 AdaGrad」第 7 段(text/34-ch03-09-3-9-adagrad.txt:7,搜「adapts the learning rate」)与第 11 段(搜「sum of squared gradients」)。原文:按参数历史梯度平方和缩放学习率,稀疏更新的参数得到更快的收敛。

  14. 出处:「3.9 AdaGrad」第 38 段(text/34-ch03-09-3-9-adagrad.txt:38,搜「too small over time」)。原文:AdaGrad 对初始学习率敏感,随时间学习率可能变得过小、阻碍收敛;这促成了加衰减项的 RMSprop。

  15. 出处:「3.10 RMSprop」第 60 段(text/35-ch03-10-3-10-rmsprop.txt:60,搜「decay_rate」)。代码:cache = decay_rate * cache + (1 - decay_rate) * gradient ** 2;decay_rate=0.9 在第 78 段。

  16. 出处:「3.11 Adam」第 16 段(text/36-ch03-11-3-11-adam.txt:16,搜「update rule」)与第 20 段(搜「Corrected」)。五条式:m、v 的指数衰减平均;m_hat、v_hat 的偏差修正;参数按 lr·m_hat/(√v_hat+ε) 更新。

  17. 出处:「3.11 Adam」第 35 段(text/36-ch03-11-3-11-adam.txt:35,搜「0.9」)与第 36 段(搜「1e」)。原文:β1、β2 典型值 0.9 与 0.999,ε 常 1e−8。

  18. 出处:「3.13 Learning Rate Scheduling」第 46 段(搜「Annealing」)、第 52 段(搜「Plateau」)、第 58 段(搜「Warmup」);阶梯衰减与指数衰减在第 48-49 段(搜「step decay」)。

  19. 出处:「3.14 Summary」第 5 段(text/39-ch03-14-3-14-summary.txt:5,搜「84.12」)。原文:「Accuracy on the test set: 84.12%」;Fashion-MNIST 数据集描述(6 万张、10 类、每类 6 千)在 text/38-ch03-13-3-13-learning-rate-scheduling.txt:74(搜「60,000」)。

  20. 出处:「3.13 Learning Rate Scheduling」第 131 段(text/38-ch03-13-3-13-learning-rate-scheduling.txt:131,搜「lr=0.001」)。代码 optim.SGD(model.parameters(), lr=0.001, momentum=0.9) 全程固定学习率,无任何调度器;训练循环中两重 for 嵌套错乱、Variable() 为废弃 API。

  21. 出处:「3.12 L-BFGS」第 9 段(text/37-ch03-12-3-12-l-bfgs.txt:9,搜「efficient approximation」)与第 28 段(搜「moderate」)。原文:L-BFGS 用有限内存近似 Hessian,适合目标平滑、参数量中等问题。

  22. 出处:「3.14 Summary」第 5 段(text/39-ch03-14-3-14-summary.txt:5,搜「84.12」)。

  23. 出处:「3.11 Adam」第 16-22 段(text/36-ch03-11-3-11-adam.txt:16,搜「update rule」)。

  24. 出处:「3.5 Constraints」第 203 段(text/30-ch03-05-3-5-constraints.txt:203,搜「4,950」)、第 157 段(搜「100 hours」)、第 264 段(搜「5.9999」)、第 240 段(搜「sum(x) <= 1」)。第 01 章 §5 有完整分析。

  25. 出处:「3.11 Adam」第 106 段(text/36-ch03-11-3-11-adam.txt:106,搜「np.random.randn(1)」)。gradients = {'w': np.random.randn(1), 'b': np.random.randn(1)} 只生成一次,adam_optimizer 循环内不重算梯度;compute_loss 定义了却与更新无关。

  26. 出处:「3.4 Convexity」第 9 段(text/29-ch03-04-3-4-convexity.txt:9,搜「Initialization Strategies」)与第 20 段(搜「skip connections」)。原文把初始化策略、自适应学习率、动量、正则化、残差连接/批归一化并列为应对优化挑战的手段;Xavier/He 公式全书未讲。