优化算法:让模型学得更好
本章来源:本文整理自《动手学深度学习》(zh.d2l.ai)第 11 章《优化算法》,原书作者 Aston Zhang、李沐、Zachary C. Lipton、Alexander J. Smola。
本章讲什么
前面反复提到"优化"——找到让损失函数最小的参数。这一章系统介绍优化算法:从最基础的梯度下降,到实践中几乎人人都在用的 Adam,再到学习率怎么调度。
一个重要的前提认知:优化和深度学习的追求不完全一致。优化关注最小化目标函数(降低训练误差),而深度学习真正关心的是在有限数据下找到泛化好的模型(降低泛化误差)。两者的最小值点往往不同——训练误差低不代表泛化好,就像考试题做得好不代表真学会了。
优化面临的三座大山
深度学习的目标函数有三个典型困难:
- 局部最小值:梯度为零但只是局部最优。高维问题中到处都是这样的点。
- 鞍点:所有梯度都消失,但既不是极大也不是极小。想象马鞍——一个方向是谷、另一个方向是脊,放个球上去,它既不是最低也不是最高。高维空间中鞍点比局部最小值更常见。
- 梯度消失:梯度趋近零导致训练停滞(比如 tanh 在输入较大处导数趋近 0)。这是 ReLU 被引入的原因。
有趣的是,小批量 SGD 的天然噪声反而是跳出局部最小值的有利特性。
凸性:一个理想的分析环境
凸函数在优化理论里是理想环境:凸函数的局部极小值就是全局极小值(怎么走都不会卡在半山腰的坑里),算法分析也容易得多。虽然深度学习问题大多非凸,但局部极小值附近往往表现出一定凸性。
詹森不等式是凸性的核心工具:凸函数的期望 ≥ 期望的凸函数,用它可以用简单表达式约束复杂表达式。
约束优化里还有几个概念:拉格朗日乘数(把约束并入目标函数)、惩罚(实践中直接加惩罚项,如权重衰减)、投影(把点映射到约束集里最近的点,如梯度截断)。
梯度下降:优化的起点
梯度下降的思想来自泰勒展开:函数在某点附近可用一阶近似,沿负梯度方向移动能使函数值下降。于是迭代更新:x ← x - η·梯度,直到梯度足够小。
学习率 η 极其关键:
- 太小:进展缓慢,走很多步还离最优很远。
- 太大:一阶近似失效,可能越过最优点甚至发散。
就像"蒙眼下山":梯度告诉你哪个方向最陡,每次朝最陡方向迈一步。步子(学习率)太大可能一步跨过谷底甚至滚下山(发散),太小则下得很慢。
更高级的牛顿法用二阶导数(Hessian 矩阵)一步确定最优步长,凸问题上收敛极快,但 Hessian 计算和存储代价太高,非凸时还可能上升,实践中很少直接用。
随机梯度下降(SGD)
梯度下降每次迭代要计算全部 n 个样本的梯度,代价 O(n),大数据集下不可行。SGD 每次只随机采样一个样本,用它的梯度更新参数,单步代价降到 O(1)。
关键性质:随机梯度是完整梯度的无偏估计——期望等于真实梯度,所以平均意义上它是良好估计,只是带噪声(轨迹比梯度下降嘈杂得多,接近最优点后仍会抖动)。
有噪声就带来新问题:常数学习率下永远收敛不到最优点,所以必须动态降低学习率。基本策略:分段常数、指数衰减(太激进)、多项式衰减(表现好)。
"用抽查代替全面调查":完整梯度像普查(准但贵),随机梯度像抽查一两个样本(快但有噪声),抽查多次平均下来结果也可靠。
小批量随机梯度下降:实用之选
梯度下降"数据高效但计算不高效",SGD"计算高效但数据不高效"。小批量随机梯度下降每次用一小批样本,是两者的折中。
计算效率的根源是向量化和缓存:一次性算矩阵乘法比逐元素循环快几个数量级。小批量的统计性质:梯度期望不变(无偏),方差显著降低——b 个独立梯度的平均使标准差降为 b 的平方根的倒数。
但批量大到一定程度后,方差的额外减少相比计算代价的线性增长就微不足道了。实践中选择"能填满 GPU 内存又足够大"的批量。
"点外卖凑单"的类比:一次只送一份(SGD)太浪费运费,一次全送(GD)又慢又占地方,凑个"刚好装满配送箱"的份量(小批量)最划算。
动量法
SGD 有两个问题:梯度噪声大,以及病态条件数问题(不同方向进展速度差异悬殊,比如"窄峡谷"型的目标函数——一个方向平缓、一个方向陡峭)。
动量法的核心思想是"泄漏平均值":用过去梯度的指数加权平均替代瞬时梯度做更新。历史梯度按指数衰减,较大的衰减系数相当于更长期的"记忆"。
好处:在噪声大的方向(梯度方向随机振荡),平均后相互抵消,步长变小;在方向一致的方向(如峡谷底部),平均后梯度对齐,步长加大——相当于"自动判断该冲还是该稳"。
"滚下山坡的小球":普通梯度下降像每一步都重新评估方向的谨慎步行者,动量法像滚动的球——球有惯性,沿峡谷方向越滚越快,横向抖动被惯性抵消。β 像摩擦力,越大惯性越强、越能冲过小坑。
AdaGrad:按坐标自适应
AdaGrad 解决"稀疏特征"问题:语言模型中不常出现的词需要更大的学习率才能学到足够信息,而常见特征需要学习率快点降下来避免过拟合。固定全局学习率无法兼顾两者。
AdaGrad 的思路:为每个坐标维护独立的、自适应的学习率——用历史梯度平方的累加作为缩放因子。梯度大的坐标学习率被压得小,梯度小的坐标学习率相对大,实现"逐坐标自适应"。
"按科目难度分配学习时间":不常考但难的知识点(稀疏特征)要多分配时间(大学习率),常考的知识点学得快、时间递减。
缺陷:累积值只增不减,学习率持续衰减,对凸问题够用,但深度学习的非凸问题上衰减可能过于剧烈,训练后期移动很慢。
RMSProp 与 AdaDelta
RMSProp 针对 AdaGrad 的缺陷做修复:把"梯度平方的累加"改成"梯度平方的指数加权平均"(泄漏平均值)。这样累积值不会无限增长,学习率可以独立控制、不随迭代自动衰减到零。
AdaDelta 是另一种变体,最大特点是没有学习率参数——用"参数本身变化量"的尺度来校准更新步长。它同时跟踪"过去步长多大"和"当前梯度多大",两者相除得到与梯度绝对尺度无关的相对步长。
"照自己过去的步伐走路":不看固定步幅表(学习率),而是参照"我上次迈了多大步"来调整当前步伐,再结合当前坡度决定走多远。
Adam:集大成者
Adam 把之前所有技巧合并进一个算法:SGD 的高效、小批量的向量化、动量法的历史梯度累积、AdaGrad 的逐坐标缩放、RMSProp 的学习率分离。
它用两个指数加权移动平均状态:一个估计梯度的一阶矩(动量,记住方向),一个估计梯度的二阶矩(方差,知道路况)。关键细节是偏差校正——状态从零初始化会产生初始偏差,要校正成无偏估计。
"既有方向感又有节奏感的司机":动量像记住要去的大方向(惯性),二阶矩像知道路况好坏(哪段路颠簸就开慢点),Adam 把两者结合——朝惯性方向开、路面差的地方自动减速,还带一个可调的基础油门(学习率)。
优点:对超参数不太敏感、通常开箱即用。Adam 是实践中应用最广泛的优化器之一。
学习率调度
调整学习率的调度策略与实际算法本身同等重要:
- 学习率太大发散、太小训练过慢或次优。
- 预热(warmup):初始参数随机,最初的更新方向可能无意义,一开始用大步长没好处——应先小步预热再加速。
- 常用策略:分段常数调度(在固定里程碑把学习率减半,让优化先跑到驻点再降学习率精修)、余弦调度(学习率按余弦曲线平滑下降)、单因子衰减(每步乘一个衰减因子)。
"炒菜火候管理":一开始大火(预热)让锅热起来,但一直大火会糊(发散),于是中间逐渐调小火(衰减),最后文火慢炖(小学习率精修)。
小结
- 优化与深度学习追求不同:前者降训练误差,后者要泛化好。
- 局部最小值、鞍点、梯度消失是深度优化的三大挑战。
- 梯度下降沿负梯度迭代,学习率过大发散、过小缓慢。
- SGD 用单个样本梯度(无偏但有噪声),需降低学习率。
- 小批量 SGD 兼顾计算效率和统计效率,是实用之选。
- 动量法用梯度指数加权平均,加速一致方向、抑制噪声。
- AdaGrad、RMSProp、AdaDelta 逐坐标自适应学习率。
- Adam 集大成,是实践中应用最广的优化器。
- 学习率调度(预热、分段常数、余弦)与算法同等重要。
关键术语
| 术语 | 一句话解释 |
|---|---|
| 局部最小值 / 鞍点 | 局部最优 / 梯度为零但非极值的点 |
| 凸函数 | 局部极小值即全局极小值的函数 |
| 詹森不等式 | 凸函数期望的不等式 |
| 梯度下降 | 沿负梯度方向迭代更新参数 |
| 学习率 | 控制每步更新幅度的超参数 |
| SGD | 用单个样本梯度更新参数 |
| 小批量 SGD | 每次用一小批样本更新参数 |
| 向量化 | 用矩阵运算批量处理数据 |
| 动量法 | 过去梯度的指数加权平均 |
| AdaGrad / RMSProp / AdaDelta | 逐坐标自适应学习率的算法家族 |
| Adam | 结合动量与自适应缩放的一阶优化算法 |
| 学习率调度 | 按迭代自动调整学习率的机制 |
| 预热 | 初始阶段学习率先升后降 |