第 6 章 模型拟合(Fitting models)
本章一句话:训练一个模型,就是不断沿着损失函数的“下坡方向”调参,直到损失最小;本章介绍这套迭代算法的家族——从最朴素的梯度下降,到加了噪声的 SGD、加了惯性的动量,再到能自适应步长的 Adam。
读这章的收获:建立“在损失曲面上走下坡”的几何直觉;理解为什么高维非凸曲面会有局部极小和鞍点;搞清 SGD 的噪声为什么反而有用;掌握动量、Nesterov、Adam 三种主流优化器的核心公式与动机;认识学习率、批大小等训练超参数。
本章速览
- 训练 = 最小化损失:第 3、4 章给出模型(一族分段线性函数),第 5 章给出损失(一个衡量预测与真值差距的数)。本章要做的,就是找出让损失最小的参数 \(\hat\phi\)。
- 梯度下降两步走:① 算出损失对每个参数的导数(梯度,指向上坡方向);② 沿反方向(下坡)走一小步。反复迭代,直到梯度趋近于零。
- 凸 vs. 非凸:线性回归的损失是“一个碗”(凸),从哪开始都能走到全局最小;但神经网络的损失是非凸的,遍布局部极小和鞍点,普通梯度下降很容易卡住。
- SGD 用噪声破局:每步只用一小批(minibatch)数据算梯度,给轨迹加上噪声,使算法有机会“翻山”跳出坏的山谷,还更省算力、更容易泛化。
- 动量加惯性:把本步梯度和上一步方向加权混合,抑制在山谷里来回震荡,加速收敛;Nesterov 则“先按惯性走一步再测梯度”,让校正更准。
- Adam 自适应步长:对每个参数按其梯度大小自动归一化步长,并对一阶矩(梯度)和二阶矩(梯度平方)都做动量平滑,使各方向、各层都能稳定且快速地前进。
- 超参数是手艺活:学习率、批大小、学习率调度、动量系数都不是模型参数,需要靠搜索和经验来定。
6.1 梯度下降(Gradient descent)
要拟合模型,需要一个训练集 \(\{x_i,y_i\}\)(输入/输出对)。我们希望模型 \(f[x_i,\phi]\) 尽量把输入 \(x_i\) 映射到对应的 \(y_i\)。为此定义损失函数 \(L[\phi]\),它返回一个数,量化这种映射的不匹配程度。优化算法的目标,就是找到让损失最小的参数:
\[ \hat\phi = \operatorname*{argmin}_{\phi}\, L[\phi]. \]
训练神经网络的标准方法都是迭代式的:先启发式地初始化参数,再反复调整使损失下降。这一类里最简单的就是梯度下降。它从初始参数 \(\phi=[\phi_0,\phi_1,\dots,\phi_N]^\top\) 出发,循环两步:
第 1 步——算梯度:计算损失对每个参数的偏导,组成梯度向量
\[ \frac{\partial L}{\partial\phi}= \begin{bmatrix} \partial L/\partial\phi_0\\ \partial L/\partial\phi_1\\ \vdots\\ \partial L/\partial\phi_N \end{bmatrix}. \]
第 2 步——更新参数:按下式走一步
\[ \phi \;\leftarrow\; \phi - \alpha\cdot\frac{\partial L}{\partial\phi}, \]
其中正标量 \(\alpha\) 决定步子大小。
几何直觉:第 1 步算出当前位置的梯度,它指向损失上坡最陡的方向;第 2 步往相反方向(下坡)走一小段距离(所以有负号)。\(\alpha\) 可以固定(这时叫学习率),也可以做线搜索——试几个 \(\alpha\),挑下降最多的那个。
走到损失最小处时,曲面是平的(否则还能继续下坡改进),此时梯度为零、参数不再变化。实践中我们监控梯度的大小,当它足够小就终止算法。
图示直觉:把损失曲面想象成一片山地,用亮度画成热力图——越暗损失越低。从某点出发,每次都朝最陡的下坡方向迈一步,几步之后就逼近谷底。对应到模型上:初始参数(最浅的线)拟合得很糟,每迭代一次线就更贴合数据,几步后已是一条像样的拟合曲线。
6.1.1 线性回归这个例子
拿第 2 章的一维线性回归来跑梯度下降。模型把标量 \(x\) 映到标量 \(y\),参数 \(\phi=[\phi_0,\phi_1]^\top\) 分别是截距和斜率:
\[ y=f[x,\phi]=\phi_0+\phi_1 x. \]
用最小二乘损失:
\[ L[\phi]=\sum_{i=1}^{I}\big(\phi_0+\phi_1 x_i-y_i\big)^2 . \]
它对参数的导数可以拆成每个样本贡献之和,\(\partial L/\partial\phi=\sum_i \partial\ell_i/\partial\phi\),其中
\[ \frac{\partial\ell_i}{\partial\phi}= \begin{bmatrix} 2(\phi_0+\phi_1 x_i-y_i)\\ 2x_i(\phi_0+\phi_1 x_i-y_i) \end{bmatrix}. \]
反复按上面的更新式迭代,参数就会稳步走向谷底(若每步都用线搜索找最佳 \(\alpha\),往往四步就很接近最小值了)。
6.1.2 Gabor 模型这个例子
线性回归的损失曲面永远只有一个明确的全局最小——更正式地说,它是凸的:任意两点之间的弦(连线)都落在曲面之上、不与曲面相交。凸性意味着无论从哪初始化,只要一直往下走就一定能到最小值,训练不会“失败”。
可惜,大多数非线性模型(包括浅层和深层网络)的损失都是非凸的。神经网络参数太多,曲面没法直接可视化,所以先用一个只有两个参数的简单非线性模型来体会非凸曲面的性质——Gabor 模型:
\[ f[x,\phi]=\sin[\phi_0+0.06\cdot\phi_1 x]\cdot\exp\!\left(-\frac{(\phi_0+0.06\cdot\phi_1 x)^2}{32.0}\right). \]
它是一个正弦波(产生振荡)乘上一个负指数包络(让振幅随远离中心而衰减)。参数 \(\phi_0\in\mathbb{R}\) 控制函数中心位置(\(\phi_0\) 增大时函数向左移),\(\phi_1\in\mathbb{R}^+\) 沿 \(x\) 轴拉伸或压缩它(\(\phi_1\) 增大时函数变窄)。给定 \(I\) 个样本,同样用最小二乘损失 \(L[\phi]=\sum_i (f[x_i,\phi]-y_i)^2\),目标仍是找最小化它的 \(\hat\phi\)。
6.1.3 局部极小与鞍点(Local minima and saddle points)
Gabor 模型的损失曲面上有许多局部极小(local minima):这些点梯度为零、朝任意方向移动损失都增加,但它们并非整体最低点。损失最低的那个点才是全局最小(global minimum)。
关键警告:从随机位置开始做梯度下降,不保证会走到全局最小。算法很可能(甚至更可能)停在某个局部极小里,而且你无从知道别处是否还有更优解。最终落点完全由起点决定——这是普通梯度下降的根本毛病。
曲面上还有鞍点(saddle points):梯度同样为零,但函数在某些方向上升、在另一些方向下降(像马鞍)。如果当前参数不是恰好落在鞍点上,梯度下降还能沿下坡方向逃离;但鞍点附近很平坦,很难判断训练是否真的收敛——如果一看到梯度变小就停,可能会误停在鞍点附近。
6.2 随机梯度下降(Stochastic gradient descent, SGD)
Gabor 只有两个参数,还能靠“穷举参数空间”或“从不同起点多跑几次取最优”来找全局最小。但神经网络动辄上百万参数,这两招都不现实。问题的根源在于:普通梯度下降的最终落点完全由起点决定。
随机梯度下降(SGD)的思路是:每步给梯度加一点噪声。这样解平均而言仍朝下坡走,但任一步的方向不一定是最陡下坡,甚至可能根本不是下坡。正因如此,SGD 有可能暂时上坡,从损失曲面的一个“山谷”跳到另一个山谷——从而有机会逃出坏的局部极小。
6.2.1 批与轮次(Batches and epochs)
制造随机性的机制很简单:每次迭代只从训练集里随机抽一小批样本,仅用这批数据算梯度。这一小批叫 minibatch / batch。第 \(t\) 步的更新式为:
\[ \phi_{t+1} \;\leftarrow\; \phi_t - \alpha\cdot\sum_{i\in\mathcal{B}_t}\frac{\partial\ell_i[\phi_t]}{\partial\phi}, \]
其中 \(\mathcal{B}_t\) 是当前批所含样本的索引集合,\(\alpha\) 是学习率。学习率在开始时就定好,不随函数的局部性质变化。
- 批通常无放回抽取:算法逐批用完整个训练集后,再从完整数据集重新开始抽。
- 一轮(epoch)指完整地过一遍训练集。
- 批可以小到 1 个样本,也可以大到整个数据集——后者称为全批梯度下降,等同于普通(非随机)梯度下降。
另一种理解:SGD 相当于每一步都在对一个不断变化的损失函数做确定性梯度下降——因为损失既依赖模型又依赖数据,每个随机批对应的损失都不同。尽管如此,任一点处的期望损失和期望梯度与普通梯度下降是一样的。
图示直觉:在整张数据集的损失曲面上,每步都有一个“可能的参数变化”的概率分布(对应不同的批选择)。某个批的损失曲面会让你往这边走,另一个批让你往那边走;有时算法相对于批损失是下坡,但相对于全局损失却是上坡——这正是 SGD 能逃离局部极小的机制。
6.2.2 SGD 的好处
- 虽然给轨迹加了噪声,但每步仍在改进对某子集数据的拟合,所以更新即便不最优也大体合理。
- 无放回抽样并遍历数据集,所有样本仍贡献均等。
- 只用一部分数据算梯度,计算更便宜。
- 原则上能逃离局部极小。
- 减少卡在鞍点附近的概率——曲面上任一点,总有些批会给出显著的梯度。
- 有证据表明 SGD 找到的参数往往泛化更好(详见第 9.2 节)。
SGD 不一定按传统意义“收敛”。但我们期望:接近全局最小时,所有数据点都被模型描述得很好,于是无论选哪个批梯度都很小,参数就基本不再变化。实践中常配合学习率调度(learning rate schedule):\(\alpha\) 起初取较大值,每 \(N\) 轮按固定因子衰减。逻辑是——训练早期希望算法到处探索、在山谷间跳跃以找到合理区域;后期已大致到位,更关心精调,于是减小 \(\alpha\) 做更细的调整。
6.3 动量(Momentum)
对 SGD 的一个常见改进是加上动量项:用“当前批梯度”和“上一步移动方向”的加权组合来更新参数:
\[ m_{t+1} \;\leftarrow\; \beta\cdot m_t + (1-\beta)\sum_{i\in\mathcal{B}_t}\frac{\partial\ell_i[\phi_t]}{\partial\phi}, \]
\[ \phi_{t+1} \;\leftarrow\; \phi_t - \alpha\cdot m_{t+1}, \]
其中 \(m_t\) 是动量(驱动第 \(t\) 步的更新),\(\beta\in[0,1)\) 控制梯度被随时间平滑的程度,\(\alpha\) 是学习率。
这个递归式意味着每步的更新其实是所有历史梯度的无穷加权和,越久远的权重越小(像“惯性”一样记住过去的走向)。效果是:
- 当多步梯度方向一致时,有效学习率会增大(顺势加速);
- 当梯度方向反复横跳时,正负项相互抵消,有效学习率减小。
总体上轨迹更平滑,抑制了在山谷里来回震荡的低效行为。
图示直觉:普通 SGD 下山的路像“之”字形来回摆动、非常绕;加了动量后,由于本步变化是“上一步变化 + 当前梯度”的加权和,方向被平滑掉,路径更直、收敛更快。
6.3.1 Nesterov 加速动量(Nesterov accelerated momentum)
动量项可以看成对“算法下一步会去哪”的一个粗略预测。Nesterov 加速动量的巧思是:先按动量走到那个预测点,再在预测点处测梯度(而不是在当前点测):
\[ m_{t+1} \;\leftarrow\; \beta\cdot m_t + (1-\beta)\sum_{i\in\mathcal{B}_t}\frac{\partial\ell_i[\phi_t-\alpha\beta\cdot m_t]}{\partial\phi}, \]
\[ \phi_{t+1} \;\leftarrow\; \phi_t - \alpha\cdot m_{t+1}, \]
这里梯度是在 \(\phi_t-\alpha\beta\cdot m_t\)(预测点)处求的。可以理解为:梯度项现在的作用是修正“仅靠动量”给出的路径——先迈出惯性的一步,再用那里的梯度把方向纠正过来,通常更准。
6.4 Adam
固定步长的梯度下降有个讨厌的毛病:对梯度大的参数做大调整(其实这里也许该谨慎些),对梯度小的参数做小调整(其实这里也许该多探索)。当损失曲面在一个方向特别陡、另一个方向特别缓时,很难选到一个既能在两个方向都有进展、又稳定的学习率——学习率小了缓方向太慢,大了陡方向就来回过冲、发散。
一个朴素的解法:把梯度归一化,让每个方向都走固定距离(由学习率决定)。先测出梯度 \(m_{t+1}\) 和逐元素的平方梯度 \(v_{t+1}\):
\[ m_{t+1}\leftarrow\frac{\partial L[\phi_t]}{\partial\phi},\qquad v_{t+1}\leftarrow\left(\frac{\partial L[\phi_t]}{\partial\phi}\right)^2 , \]
再按下式更新(开方与除法都是逐元素的):
\[ \phi_{t+1}\leftarrow\phi_t-\alpha\cdot\frac{m_{t+1}}{\sqrt{v_{t+1}}+\epsilon}, \]
其中 \(\epsilon\) 是防止除零的小常数。用平方梯度的正平方根去归一化梯度本身,结果是每个坐标方向上只剩下符号——于是算法沿每个坐标都走固定距离 \(\alpha\),方向由“哪边是下坡”决定。这样两个方向都能稳步推进,但除非正好落到最小值,否则不会精确收敛,而是会在最小值附近来回弹跳。
Adam(自适应矩估计,Adaptive moment estimation)把这个想法再加上动量——对梯度和平方梯度都做时间平滑:
\[ m_{t+1}\leftarrow\beta\cdot m_t+(1-\beta)\frac{\partial L[\phi_t]}{\partial\phi},\qquad v_{t+1}\leftarrow\gamma\cdot v_t+(1-\gamma)\left(\frac{\partial L[\phi_t]}{\partial\phi}\right)^2 , \]
其中 \(\beta,\gamma\) 是两个统计量各自的动量系数。
用动量等于对每个统计量的历史做加权平均。但训练刚开始时,先前的测量值都近似为零,会导致估计偏小(不真实)。为此做偏差校正:
\[ \tilde m_{t+1}\leftarrow\frac{m_{t+1}}{1-\beta^{\,t+1}},\qquad \tilde v_{t+1}\leftarrow\frac{v_{t+1}}{1-\gamma^{\,t+1}}. \]
由于 \(\beta,\gamma\in[0,1)\),带指数 \(t+1\) 的项随步数变小、分母趋近于 1,所以这个校正的影响随训练推进而逐渐消失。最后用校正后的项更新参数:
\[ \phi_{t+1}\leftarrow\phi_t-\alpha\cdot\frac{\tilde m_{t+1}}{\sqrt{\tilde v_{t+1}}+\epsilon}. \]
这样得到的算法既能收敛到整体最小,又能在参数空间的每个方向都取得良好进展。Adam 通常用在随机场景下,梯度及其平方都从 minibatch 算出:
\[ m_{t+1}\leftarrow\beta\cdot m_t+(1-\beta)\sum_{i\in\mathcal{B}_t}\frac{\partial\ell_i[\phi_t]}{\partial\phi},\qquad v_{t+1}\leftarrow\gamma\cdot v_t+(1-\gamma)\left(\sum_{i\in\mathcal{B}_t}\frac{\partial\ell_i[\phi_t]}{\partial\phi}\right)^2 , \]
所以实际轨迹是带噪声的。
图示直觉:损失曲面在竖直方向变化快、水平方向变化慢。(a) 学习率照顾竖直方向 → 水平方向爬得太久;(b) 学习率照顾水平方向 → 竖直方向过冲、发散;(c) 只保留符号、每轴走固定距离 → 两个方向都下坡,但会在最小值附近来回弹;(d) Adam 对梯度和归一化项都加动量 → 路径更平滑,能真正收敛。
如第 7 章会讲到,神经网络不同深度的参数,其梯度量级可能差别很大。Adam 能补偿这种倾向,平衡各层之间的更新。实践中 Adam 还有个优点:对初始学习率不那么敏感(因为它避开了上图 a、b 那种困境),所以不太需要复杂的学习率调度。
6.5 训练算法的超参数(Training algorithm hyperparameters)
学习算法的选择、批大小、学习率调度、动量系数,这些统称训练算法的超参数(hyperparameters)。它们直接影响最终模型性能,但与模型参数(即 \(\phi\))截然不同——模型参数靠训练学出来,超参数则要人来定。
怎么选超参数更像艺术而非科学:常见做法是用不同超参数训练许多个模型,再挑表现最好的那个,这叫超参数搜索(hyperparameter search)。第 8 章会再深入讨论这个问题。
本章小结
本章讨论模型训练,把它框定为“寻找使损失 \(L[\phi]\) 最小的参数 \(\phi\)”。
- 梯度下降:测当前参数处损失的梯度(即参数微变时损失如何变化),再朝损失下降最快的方向移动参数,反复直到收敛。
- 非凸的麻烦:非线性模型的损失曲面同时存在局部极小(梯度下降会被困住)和鞍点(看似收敛实则没有)。
- SGD 缓解之道:每步用不同的随机数据子集(一个批)算梯度,给过程加噪声,帮算法不被困在次优区域;每步还更省算力。
- 动量让收敛更高效(抑制震荡、顺势加速),Nesterov 进一步在预测点测梯度做校正。
- Adam 自适应地为每个参数归一化步长,并对一阶/二阶矩做动量平滑,在各方向、各层都稳定快速。
- 超参数(学习率、批大小、调度、动量系数)需靠搜索与经验确定。
本章的思想适用于优化任何模型。下一章处理两个专属于神经网络的训练环节:① 如何用著名的反向传播算法计算损失对网络参数的梯度;② 如何在优化开始前初始化参数——否则梯度可能变得极大或极小,阻碍训练。
关键术语对照
| 中文 | English | 一句话含义 |
|---|---|---|
| 梯度下降 | Gradient descent | 沿损失下坡方向迭代调参 |
| 学习率 | Learning rate (\(\alpha\)) | 每步移动距离的缩放系数 |
| 线搜索 | Line search | 沿下降方向试多个步长取最优 |
| 凸 / 非凸 | Convex / Non-convex | 损失是“一个碗” / 遍布坑与鞍 |
| 局部极小 / 全局最小 | Local / Global minimum | 区域最低点 / 整体最低点 |
| 鞍点 | Saddle point | 梯度为零但一升一降的点 |
| 随机梯度下降 | Stochastic gradient descent (SGD) | 每步只用一小批数据算梯度 |
| 小批 / 轮次 | Minibatch / Epoch | 一次抽取的样本子集 / 过完一遍数据 |
| 全批梯度下降 | Full-batch gradient descent | 用全部数据,等于普通梯度下降 |
| 学习率调度 | Learning rate schedule | 训练中按计划衰减学习率 |
| 动量 | Momentum (\(\beta\)) | 混入历史方向,平滑轨迹、加速收敛 |
| Nesterov 加速动量 | Nesterov accelerated momentum | 先按惯性走再测梯度做校正 |
| Adam | Adam | 自适应步长 + 一阶/二阶矩动量 |
| 一阶矩 / 二阶矩 | First / Second moment | 梯度的平滑均值 / 平方梯度的平滑均值 |
| 偏差校正 | Bias correction | 修正训练初期估计偏小的问题 |
| 超参数 | Hyperparameter | 影响性能、需人工设定的训练设置 |
返回目录 | 上一章:第 5 章 损失函数 | 下一章:第 7 章 梯度与初始化