跳到主要内容

优化器进化史与 GPU 加速 — 谁在决定「这一步挪多大」

这一章讲三件事: 最朴素的梯度下降会在哪几种地形上失败; 一路加到 Adam 的每一步各补了什么缺陷;以及 GPU 为什么快、多卡怎么拆数据。 第 03 章你已经在训练循环里见过 optimizer.step(),本章拆的就是这一行背后的事。

1. 顶层全景:一条补丁叠补丁的进化链

θ ← θ − λ·g 裸梯度下降(λ=学习率,g=梯度)
│ 死法:λ 太小爬不动/太大跳过极值;鞍点卡住;平坦区误判早停

+ 动量(历史梯度的累积) 冲过平坦区,压住振荡
│ 不足:λ 仍要手工调

+ 逐参数自适应 λ AdaGrad → RMSProp → Adam
│ (谁梯度小就给谁大步子)

组合拳:先 Adam 后 SGD 前期省心,后期泛化更好

改进的切入点多半是两个:书里原话是「影响优化的无非两个因素:一个是梯度方向,一个是学习率」——动量改方向的使用方式,自适应算法改学习率本身1

2. 裸梯度下降的三种死法

更新规则本身简单到一行:参数沿梯度的方向挪 λ 那么远(梯度指向损失上升最快的方向,所以要走反面)。它简洁,对凸问题能收敛到最优点,但有三种典型死法2:

  • 学习率过敏。 λ 太小,收敛慢到没有实用价值;太大,一步跨过极值点,在谷底两侧来回弹3
  • 鞍点卡住。 鞍点(某些方向向上、某些方向向下的「马鞍中心」)处梯度为零,固定步长的算法会停在原地不动4
  • 平坦区误判。 长缓坡上梯度接近 0,算法误以为已到极值,提前结束迭代——其实离目标还远5

死法归因清楚,补丁的思路就顺了:平坦区梯度不可靠,那就借历史梯度的惯性;学习率难调,那就让每个参数自己调自己的步长

3. 动量与 NAG:给下山的人装惯性

动量(Momentum)借物理直觉:运动的物体有惯性,不会因为某一步梯度小就立刻停下。实现上,每步的更新量 = 历史梯度的累积 + 当前梯度6。效果:振幅变小、更快到达极值点——平坦区里历史梯度还在推它走,谷底两侧的正反梯度互相抵消,振荡被压平7

NAG(Nesterov Accelerated Gradient)是动量的精细版,思路一句话:既然每步都要合并历史与当前梯度,不如先按历史惯性往前走半步,再在「前方位置」看梯度来修正——像跑步者提前看脚下的路而不是只看脚下8。防大幅振荡、不冲过头,代价只是每步多算一次前方梯度。

4. 自适应学习率:AdaGrad → RMSProp → Adam

动量没解决「学习率难调」——λ 还是一个全局手调的数。自适应算法把这一维也交给算法:

算法核心动作补的缺陷新的坑
AdaGrad给每个参数记「历史梯度平方和」r,步长 = λ/√r梯度一直大的参数自动小步,稀疏(大多时候不出现)特征能大步更新r 只增不减,学习率过早衰减到不走9
RMSProp把平方和换成指数加权移动平均(只记最近一段)治 AdaGrad 的过早衰减;新超参 ρ 控制记忆长度仍无动量10
AdamRMSProp + 动量,再加偏置校正前期估计不准的问题被校正;每步学习率有确定范围,参数更新平稳超参变多(两个动量系数)11

三步进化的主线只有一条:从「所有参数共享一个学习率」到「每个参数按自己的梯度历史定步长」。书里的结论也直白:自适应优化器因鲁棒(皮实、不怕折腾)性和泛化能力受欢迎,而用 SGD 必须手工选学习率并随时间衰减12

5. 主走查:先 Adam 后 SGD 的组合拳

本章的主走查是一个实战决策,而不是一个公式。 场景:同一个模型,优化器怎么选?

书里的方案分两段:前期用 Adam——训练早期 SGD 对参数初始化非常敏感,Adam 不挑初始化、省调参时间,先把参数带进「像样的区域」;后期切 SGD+动量——想冲最好性能时,SGD 的泛化常更好。书里的实验曲线显示,迭代约 150 轮之后,SGD 的测试误差反超 Adam13

把这个决策拆成步骤:

1) 用 Adam(lr=0.001 级别)跑到损失平台期 —— 不挑初始化,快
2) 记下参数,换 SGD+momentum(lr 重调,动量 0.9 左右)
3) 后半程 SGD 慢慢走,泛化上限更高
(为什么后期 SGD 更好,书里归因于 SGD 对参数的精细调整;
这属于经验结论,书未给出理论证明。)

判断(我们的,不是书里的): 「先 Adam 后 SGD」这个配方今天依然在用,但默认选择已经变了:大多数现代视觉与语言模型的训练从头到尾用 Adam 的改进版(如 AdamW),因为它对学习率 schedules 更友好;「换 SGD 冲上限」更多出现在竞赛和特定视觉任务里。读这本书时,把它当「可选武器」而不是「标准流程」。 如果错,会错在: 如果某类任务(如大规模语言模型预训练)对优化器二阶信息不敏感、纯 SGD 系即可,那「Adam 前期省心」的前提就不普遍成立。

6. GPU:不是更快的 CPU,是另一台机器

深度学习的计算以矩阵乘加为主,这类运算可以整批并行。CPU 是几个强核心的串行机器,GPU 是几千个流处理器的众核机器——矩阵运算摊给几千个核心同时做,单卡对单核 CPU 的提升常见几十倍到上千倍;一块合理优化的 GPU 卡可能相当于数十上百台 CPU 主机14。这就是 GPU 成为训练首选的原因15

PyTorch 侧的用法只有两句话:数据 .to(device)、模型 .to(device),把张量与网络搬进显存;torch.cuda.is_available() 探测、device_count() 数卡16

多卡训练的主流做法是按批拆发。 nn.DataParallel 把一个批次复制 N 份分到 N 张卡,各自前向,梯度汇总回主卡。书里跑了一个可见的实验:批次 128、两张卡,打印出每张卡内部各收到 64 条——「In Model: input size [64, 13]」×2,「Outside: input size [128, 13]」×117

两个书里点到的实践细节:想要只用指定的几张卡,设环境变量(系统级开关)CUDA_VISIBLE_DEVICES;DistributedDataParallel(DDP)配置繁琐一点,但速度和效果更好18。后者今天已是绝对主流——PyTorch 源码在 DataParallel 的文档里明确警告「建议改用 DistributedDataParallel,哪怕单机单卡」(补充(不在书里,依据我们的 frontier 书架):依据: shelf=ai-frontier-reference/pytorch@src:torch/nn/parallel/data_parallel.py:67 事实=官方源码写明 "It is recommended to use DistributedDataParallel, instead of this class")。

GPU 不是万金油,书里给了三条反向经验:卡数尽量取偶数(奇数卡可能异常中断);数据量小时 GPU 可能不如单卡甚至不如 CPU(搬运数据的开销吃掉收益);内存吃紧时把 pin_memory 关掉、或用低精度数据类型19

7. 作者的判断与证据

给了证据的: 学习率三死法、动量与 NAG 的振荡对比、Adam/SGD 的 150 轮分界,书里都配了图;多卡拆批的 64+64 打印是实际运行输出。

转述共识的: 「RMSProp 在实践中已被证明有效」20、自适应优化器更鲁棒,属当时的社区共识,书未附实验。

作者的立场: 「先 Adam 后 SGD」作为可选策略给出,且诚实地用了「有时可以考虑」「有时能达到很好的效果」这类留有余地的措辞——没有把它写成普适法则。

8. 边界与局限

  • 各算法的伪代码书里以截图给出。 动量/NAG/AdaGrad/RMSProp/Adam 的伪代码在原书中是图片,不便转录成公式——要看公式的读者需另查原文或原论文(补充(不在书里,来自通用知识):Adam 原论文为 Kingma & Ba, 2015)。

  • 没有成套的学习率调度(训练中自动调步幅)。「先热身再缓缓降」这类今天默认的操作不在书内;「随时间衰减学习率」只作为 SGD 的配套提了一句。

  • DDP 只给了一行配置,进程组、数据切分 sampler 等实战必需品全没讲;真要写分布式训练(多机多卡一起训),本书只能当入口。

  • 混合精度训练(书只在 Caffe2 一章提过 float16)在训练侧完全未覆盖——这是 2020 年后 GPU 训练的最大增量之一。

9. 可带走的

  1. 裸梯度下降的死法清单:学习率过敏、鞍点卡住、平坦区早停——见到「训练停在半路」,先对号入座;
  2. 动量=历史梯度的惯性,治平坦区与振荡;NAG=先走半步再看路;
  3. 自适应学习率的进化逻辑:平方和会过早衰减→换指数加权平均(RMSProp)→再加动量与偏置校正(Adam);
  4. 不想调参,默认 Adam;要冲泛化上限,试 SGD+动量;「先 Adam 后 SGD」是书里给的中间路线(约 150 轮分界);
  5. GPU 快在「几千核心并行算矩阵」,不在「单核更快」;数据量小时可能反而慢;
  6. 多卡默认按批拆发:批 128 两卡即各 64;新代码用 DDP,DataParallel 已被官方劝退;
  7. GPU 数取偶;pin_memory 在内存紧张时可以关。

10. 原文地图

主题原书章原文位置
更新式 θ←θ−λg第5章 机器学习基础text/06-ch05.txt:547(搜「反方向」) · text/06-ch05.txt:550(搜「θ←θ-λg」)
学习率敏感第5章 机器学习基础text/06-ch05.txt:555(搜「越过」)
鞍点卡住第5章 机器学习基础text/06-ch05.txt:561(搜「鞍点」)
平坦区提前终止第5章 机器学习基础text/06-ch05.txt:565(搜「提前结束迭代」)
两个下手方向第5章 机器学习基础text/06-ch05.txt:569(搜「梯度方向」)
动量与惯性第5章 机器学习基础text/06-ch05.txt:586(搜「惯性」) · text/06-ch05.txt:591(搜「累积」)
NAG 思路第5章 机器学习基础text/06-ch05.txt:598(搜「Nesterov」)
AdaGrad 与稀疏第5章 机器学习基础text/06-ch05.txt:620(搜「稀疏参数」) · text/06-ch05.txt:622(搜「过早或过量的减少」)
RMSProp 移动平均第5章 机器学习基础text/06-ch05.txt:638(搜「移动平均」)
Adam 一阶二阶矩第5章 机器学习基础text/06-ch05.txt:648(搜「一阶矩估计」)
自适应 vs 手工第5章 机器学习基础text/06-ch05.txt:657(搜「自适应优化算法」)
先 Adam 后 SGD第5章 机器学习基础text/06-ch05.txt:660(搜「先使用Adam」) · text/06-ch05.txt:665(搜「150」)
GPU 众核与倍数第5章 机器学习基础text/06-ch05.txt:671(搜「众核体系结构」) · text/06-ch05.txt:675(搜「上千倍」)
GPU 成首选第5章 机器学习基础text/06-ch05.txt:681(搜「首选」)
to(device) 用法第5章 机器学习基础text/06-ch05.txt:700(搜「to(device)」) · text/06-ch05.txt:713(搜「model.to」)
DataParallel 按卡拆批第5章 机器学习基础text/06-ch05.txt:717(搜「DataParallel」) · text/06-ch05.txt:851(搜「拆分成两份」)
DDP 更快更好第5章 机器学习基础text/06-ch05.txt:863(搜「速度和效果更好」)
GPU 注意事项第5章 机器学习基础text/06-ch05.txt:880(搜「偶数」) · text/06-ch05.txt:882(搜「还不如CPU」)

Footnotes

  1. 出处:「第5章 机器学习基础」第 569 段(text/06-ch05.txt:569,搜「梯度方向」)。

  2. 出处:「第5章 机器学习基础」第 550 段(text/06-ch05.txt:550,搜「θ←θ-λg」)。

  3. 出处:「第5章 机器学习基础」第 555 段(text/06-ch05.txt:555,搜「越过」)。

  4. 出处:「第5章 机器学习基础」第 561 段(text/06-ch05.txt:561,搜「鞍点」)。

  5. 出处:「第5章 机器学习基础」第 565 段(text/06-ch05.txt:565,搜「提前结束迭代」)。

  6. 出处:「第5章 机器学习基础」第 591 段(text/06-ch05.txt:591,搜「累积」)。

  7. 出处:「第5章 机器学习基础」第 581 段(text/06-ch05.txt:581,搜「振幅较小」)。

  8. 出处:「第5章 机器学习基础」第 598 段(text/06-ch05.txt:598,搜「Nesterov」)。

  9. 出处:「第5章 机器学习基础」第 620 段(text/06-ch05.txt:620,搜「稀疏参数」)与第 622 段(text/06-ch05.txt:622,搜「过早或过量的减少」)。

  10. 出处:「第5章 机器学习基础」第 638 段(text/06-ch05.txt:638,搜「移动平均」)。

  11. 出处:「第5章 机器学习基础」第 648 段(text/06-ch05.txt:648,搜「一阶矩估计」)。

  12. 出处:「第5章 机器学习基础」第 657 段(text/06-ch05.txt:657,搜「自适应优化算法」)。

  13. 出处:「第5章 机器学习基础」第 660 段(text/06-ch05.txt:660,搜「先使用Adam」)与第 665 段(text/06-ch05.txt:665,搜「150」)。

  14. 出处:「第5章 机器学习基础」第 671 段(text/06-ch05.txt:671,搜「众核体系结构」)与第 675 段(text/06-ch05.txt:675,搜「上千倍」)。

  15. 出处:「第5章 机器学习基础」第 681 段(text/06-ch05.txt:681,搜「首选」)。

  16. 出处:「第5章 机器学习基础」第 692 段(text/06-ch05.txt:692,搜「device_count」)与第 700 段(text/06-ch05.txt:700,搜「to(device)」)。

  17. 出处:「第5章 机器学习基础」第 851 段(text/06-ch05.txt:851,搜「拆分成两份」)与第 844 段(text/06-ch05.txt:844,搜「In Model」)。

  18. 出处:「第5章 机器学习基础」第 863 段(text/06-ch05.txt:863,搜「速度和效果更好」)。

  19. 出处:「第5章 机器学习基础」第 880 段(text/06-ch05.txt:880,搜「偶数」)与第 882 段(text/06-ch05.txt:882,搜「还不如CPU」)。

  20. 出处:「第5章 机器学习基础」第 643 段(text/06-ch05.txt:643,搜「实践中已被证明」)。