☰
矩阵谱如何决定优化算法性能:条件数、特征值与收敛速度
2026/10/19 11:18:08 网站建设 项目流程

实话讲,我整理 18.065 的 note3(第19到24讲)时,一开始挺不适应的——前面十几讲都在教我怎么用 SVD、主成分分析、图拉普拉斯这些矩阵工具去“看”数据,突然话锋一转,开始讲梯度下降、随机梯度下降、动量法,感觉像误入了另一门优化课。但等我啃完这六讲,才发现它哪里是换了领域,它是把前面所有线性代数的储备,一次性用到了“如何把最小化问题算出来”这个终极问题上。

这份笔记适合谁?我觉得特别适合三类人:第一类是看过很多优化器博客,知道 momentum、Adam 的公式,却不理解它们为什么长那样;第二类是学完线性代数但不知道特征值、奇异值除了做题还能干嘛的同学;第三类是在实际训练模型中被“学习率怎么调、要不要开动量”折磨过的人。如果你在这三类里,第19-24讲会给你一层底层解释——优化算法的快慢和稳定性,几乎全由矩阵的谱决定。

1. 为什么这门矩阵方法课,最后讲起了优化算法

1.1 从 SVD、图拉普拉斯到“找最小值”的跳跃

前十几讲的主线很明确:数据分析里的矩阵工具。从 SVD 讲到主成分分析,再到随机矩阵、图拉普拉斯、傅里叶,最后落到神经网络里矩阵扮演的角色。但到了第19讲,课上的问题突然变成了:“现在给你一个目标函数,你怎么找到它的最小值?”

第一反应确实会问:这不应该是凸优化课的内容吗?等我把接下来的内容顺着听下来,才明白它的逻辑:优化算法自己就是一个迭代矩阵过程。每一次迭代相当于在反复乘同一个矩阵,只是这个矩阵会随着当前位置变化而变化。所以梯度的模长怎么衰减、会不会振荡、能不能加速,背后全是矩阵特征值和奇异值在指挥。

换个角度想,这门课前面的所有章节都在做同一件事:把数据变成矩阵,再从矩阵里提取结构。而优化部分处理的对象,恰好是“损失函数的海森矩阵”或“梯度协方差矩阵”。也就是说,优化算法并不属于另一个世界,它是矩阵方法在“求解”这一环节的自然延伸。

1.2 条件数才是隐藏主角

这里有个关键线索:这门课讲 SVD 时反复强调过条件数,也就是最大奇异值除最小奇异值。图像压缩里,它决定保留多少奇异值;PCA 里,它决定主成分的能量分布;谱聚类里,它和图连通性绑定。到了优化部分,条件数依然是主角,只是换成了海森矩阵或数据协方差矩阵的谱。

普通梯度下降在病态二次型上的收敛速度,几乎只由条件数决定;条件数越大,收敛越慢。而后面讲的各种“加速技巧”,比如预处理、动量法、批归一化,本质上都在做同一件事:压缩条件数。所以这一整段笔记的主线非常清楚,我甚至给它起了个名字,叫“条件数的复仇”——前面你学过的所有矩阵工具,都在这里等着派上用场。

2. 梯度下降的收敛速度,其实是一张矩阵谱的图

2.1 二次型目标函数:优化算法的最小实验台

要看清梯度下降的收敛行为,最好别一上来就看深度学习的复杂损失,先落到最简单但又能体现谱影响的函数上:

f(x) = 1/2 · xᵀAx - bᵀx

其中 A 是对称正定矩阵。梯度就是 ∇f = Ax - b,最优点 x* = A⁻¹b。梯度下降写成迭代式:

x_{k+1} = x_k - t · (A x_k - b)

令误差 e_k = x_k - x*,就可以消掉 b,得到最关键的关系:

e_{k+1} = (I - tA) e_k

这一步是整个分析的钥匙。你把“梯度下降算法”看成一种操作,迭代过程就变成了纯矩阵作用。后面所有关于收敛的分析,都在围绕这个小矩阵展开:它必须有小于 1 的谱半径才收敛,谱半径越小收敛越快。

2.2 特征值差距大时,为什么最速下降会走锯齿

“最速下降”这个名字挺误导人的:每一步走的是局部最陡方向,整体却并不快。一个经典的二维例子是 A = [[1,0],[0,100]],等高线是又扁又长的椭圆。梯度方向基本垂直等高线,但椭圆长短轴方向的特征值相差 100 倍,于是从某个点出发,会一路以锯齿状路径跌跌撞撞走向中心。

出现锯齿的原因很简单:步长 t 只能取一个全局值。对 λ = 1 的方向合适的步长,对 λ = 100 的方向而言太小;对 λ = 100 合适的步长,对 λ = 1 的方向又超过 2/λ 导致发散。于是只能折中,两边都凑合,结果就是整体收敛率非常差。

这个收敛因子可以精确算出来:((κ-1)/(κ+1))²,其中 κ = λ_max / λ_min。κ = 100 时,收敛因子大约是 0.96,每一步只能把误差缩小约 4%,慢到让人怀疑程序出了 bug。

2.3 步长的选择,本质上是谱半径最小化问题

把步长选择当成一个优化问题,思路会清楚很多。对固定的 t,矩阵 I - tA 的特征值是 1 - t·λ_i,它们绝对值的最大值叫谱半径:

ρ = max_i |1 - t·λ_i|

只要 ρ < 1,迭代就收敛;ρ 越接近 0,收敛越快。如果知道 A 的特征值范围 [λ_min, λ_max],最优 t 会把最大和最小特征方向的 |1 - tλ| 拉平,也就是:

t* = 2 / (λ_min + λ_max)

对应的最优谱半径是:

ρ* = (κ - 1) / (κ + 1)

从这个公式能直接解释“学习率太大直接发散”:当 t > 2/λ_max 时,最大特征方向满足 |1 - t·λ_max| > 1,误差在那个方向被持续放大,发散是必然结果。所以调学习率不是玄学,它按定义就是一个谱半径最小化问题。

3. 随机梯度下降:噪声不是坏事,谱里藏着原因

3.1 小批量梯度的无偏估计与协方差矩阵

理解 SGD 的关键,是把目标函数看成每个样本损失的平均。真实梯度是全体样本梯度的平均,而 SGD 用小批量样本的梯度均值代替它。这个估计是无偏的:抽样均匀时,小批量梯度的期望等于全量梯度。

但无偏不意味着没方差。它围绕真实梯度有一圈波动,这个波动的大小用梯度向量的协方差矩阵 C 来衡量:

C = E[(g_i - g)(g_i - g)ᵀ]

为什么这是线性代数内容?因为 C 经常正比于数据协方差矩阵或其低秩近似,而数据协方差的谱,又正好是数据矩阵奇异值的平方。也就是说,前面 SVD 章节里学的奇异值谱,在这里直接决定了 SGD 的噪声水平。

3.2 从鞍点上“蹦出去”:谱层面的解释

很多资料说“SGD 的噪声能帮助逃离鞍点”,听起来有点玄。其实从谱的角度看并不复杂:把损失函数在鞍点附近做二阶展开,海森矩阵有正特征方向也有负特征方向。在鞍点附近,梯度模长很小,确定性梯度下降基本在原地踏步,每一步只能走一点点。

但 SGD 的梯度估计含随机噪声,相当于每一步叠加了一个随机位移。即使目标函数在某个方向上是平的,噪声也有能量,能让你“漂移”到非零梯度的区域。海森矩阵越平的方向,梯度噪声越容易起主导作用。所以噪声的作用有点像在冰面上被风一直吹着移动——你未必知道风往哪吹,但时间足够长时,总能滑出这片平台。

3.3 学习率衰减的线性代数依据

SGD 想收敛,学习率不能固定。直觉上,固定学习率会在极小值附近保持一个与步长相当的振荡半径,稳态误差归不了零。理论上要求:

  • ∑ t_k = ∞,保证算法在无穷时间上能到达目标附近;
  • ∑ t_k² < ∞,保证随机噪声的累积不爆炸。

经典的方案是 t_k = t0 / (1 + γk)。放到矩阵谱的语境里,衰减速度应该匹配目标附近海森矩阵的特征值大小。如果主要特征值很大,前期的 t0 必须小,否则直接发散;如果谱非常分散,只用单一 t0 很难兼顾,这时常见做法是先用 warmup 平滑上升,再走余弦衰减到接近零。

我在实际调参时,一般先跑几十个 iteration 观察损失是否下降,把 t0 的量级定下来,再套用 cosine schedule,而不是一开始就调衰减系数。

4. 动量法与 Nesterov 加速:二阶差分方程把收敛率拉上一个台阶

4.1 从一阶迭代到二阶迭代:引入速度变量

普通梯度下降的误差递推是一阶差分方程:

e_{k+1} = (I - tA) e_k

每一步只由前一步决定。动量法引入速度变量 v,用来累积此前的梯度方向:

v_{k+1} = β·v_k + ∇f(x_k) x_{k+1} = x_k - t·v_{k+1}

从状态空间看,它把一阶迭代变成了二阶差分方程,系统有了“惯性”。这个惯性在特征值分散的问题里很有用:当某个方向梯度符号不断变化、剧烈振荡时,速度可以抵消往复的抖动,保留一致的前进方向。更准确地说,动量迭代对应的矩阵会拥有复数特征值,让轨迹带有旋转和阻尼。参数选得好时,这些复数特征值全部落在单位圆内,而且总和半径更小。

4.2 Chebyshev 多项式:为什么加速理论上限那么漂亮

对二次型目标,动量法不是随便走走。经过几步递推,误差可以写成:

e_k = P_k(A) · e_0

其中 P_k 是次数不超过 k 的多项式,且满足 P_k(0) = 1。我们希望这个多项式在 A 的所有特征值上都尽量小,而恰好这个问题有精确的最优解,需要用 Chebyshev 多项式来逼近。

Chebyshev 多项式 T_k(z) 在 z ∈ [-1,1] 区间内就是 cos(k·arccos z),区间外会用双曲余弦快速放大。把 [-1,1] 映射到 A 的特征值区间,再截断最大项,就能导出动量的最优 β 和步长组合。最终的有效收缩率大约对应 1/√κ,而普通梯度是 1/κ。

举个具体例子:κ = 100 时,普通梯度每步只能缩小约 4%,而动量法可以把每步误差的几何平均降到 0.8 倍附近,差距一个数量级。Nesterov 加速方法在凸问题上能达到 O(1/k²) 的收敛率,根源就在这里。课上推导了很大篇幅,最后核心直觉倒是很简单:二阶迭代允许你使用 Chebyshev 多项式指示的振荡节奏,从而显著压缩谱的影响。

4.3 稳定性边界:β 过大会螺旋发散的谱判据

动量法不是越猛越好。把 (x_k, v_k) 合成一个增广状态,迭代矩阵会变成一个 2×2 的分块矩阵。要稳定,这个增广矩阵所有特征值的模都必须小于 1。

β 太小时退化成普通梯度;β 太大时,某些方向会以螺旋方式放大,损失曲线先降后升,甚至一步直接变成 NaN。我自己在二维二次型上试过:A 取 [[1,0],[0,100]],β = 0.9 没问题;拉到 0.99 左右,轨迹开始旋转收缩,看起来还算正常;一旦超过某个阈值,轨迹从椭圆内侧向外螺旋,损失很快飞掉。

这个阈值可以直接由 β、步长 t 与特征值的组合算出。所以调试时如果碰到“加了动量反而崩了”,先别怪算法,多半是 β 和学习率的组合越过了谱边界。

5. 把 19-24 讲串成一张网:谱估计、预处理器与你的模型

5.1 数据协方差谱与深度学习优化问题的病态性

到了这六讲的后半段,课程内容会重新拉回“真实数据”。深层网络的海森矩阵到底长什么样,很多数值实验给出了大致结论:特征值分布极其分散,有一大批接近零的特征值,也有一小部分明显的尖峰。

这种结构与数据协方差矩阵的谱有直接联系。随机矩阵理论里有一个描述谱形状的经典公式:当样本数和维度成固定比例时,协方差矩阵的特征值会落在一个有界区间内,并呈现某种密度分布形态。它意味着深度学习优化天然病态,但一阶方法在实际任务里出奇地好用。

原因之一,是海森矩阵很大一部分谱对应“平坦方向”,这些方向不需要极高精度;随机梯度本身的噪声反而像一种隐式正则。另一个原因是小批量计算让每一步代价小很多,同样的时间可以迭代更多步。有时“算得快”比“收敛快”更重要。

5.2 预处理:相似变换思想在优化中的落地

病态条件数怎么治?最直接的办法是换坐标系。如果能找到矩阵 M,使 M⁻¹A 的特征值集中在 1 附近,梯度下降的收敛率就会大幅改善,这就是预处理器(preconditioner)的思想。

从相似变换的角度看,它没有改变问题的本质,只是引入 M 的平方根做一次坐标变换,把椭球等高线“掰”成圆。经典的 Jacobi 预处理只取对角元素;更精细的不完全 Cholesky 预处理,则近似模拟 A 的逆。

深度学习里也有预处理形态:BatchNorm 就是对网络内部激活做了一次近似白化。它让后续层面对的损失地形更接近球面,这也是为什么加了 BatchNorm 后可以用更大的学习率。前面讲过的谱聚类、PCA、低秩近似,本质上都是“换一个让数据谱更清晰的坐标系”,和预处理是同一件事。

5.3 回到自己的模型:先测谱再选优化器

如果这份 note3 只留下一句话,我会写:先了解你问题的谱,再决定优化器。

在实际项目中,我不会一上来就盲选 Adam。小模型上可以用自动微分近似计算损失海森的最大特征值,或者干脆把训练集抽一小块,计算梯度协方差矩阵的谱,得到“病态程度”的数量级。

  • 条件数接近 1 或只有几十:普通 SGD 加适当动量完全够用;
  • 条件数上千上万:Adam 这类自适应方法更稳,因为它隐式地对每个方向做了缩放;
  • 条件数极大且还需要收敛证明:再考虑真正的二阶方法或复杂预处理。

这个选择顺序在 19-24 讲里被反复暗示,是整份笔记最值得带走的方法论。

6. 我整理 note3 期间的笔记方法、复现实验与踩坑记录

6.1 用“递推-谱-收敛率”卡片记录每个算法

我在整理这批内容时建立了一个固定模板:每个算法一行公式,下面接三块内容——递推关系、谱分析、最小实验。整理完以后,六个 lecture 的核心内容变成几张卡片,复习时扫一眼就能回忆起全貌。

算法误差递推核心谱/收敛结论最小实验
普通梯度e_{k+1} = (I - tA)e_kρ = (κ-1)/(κ+1)二维二次型,锯齿路径
动量法二阶差分,增广块矩阵有效收敛率约 1/√κ同一问题上轨迹更平滑
Nesterov外推点处计算梯度凸问题 O(1/k²)同一问题上收敛更快
SGD梯度估计含噪声受梯度协方差谱影响调大噪声观察“跳出平坦区”

每个算法配一个能独立运行的最小 python 实验,比光看课本深刻得多。

6.2 一个用 numpy 就能复现的二次型对照实验

建议在 notebook 里做这样一组实验:A 取 np.diag([1.0, 100.0]),目标函数 f(x) = 0.5·xᵀAx,从 x0 = (1.5, 0.5) 出发。分别用普通梯度、动量、Adam,在同一总迭代数下记录损失曲线和参数轨迹。

import numpy as np A = np.diag([1.0, 100.0]) x = np.array([1.5, 0.5]) lr = 2.0 / (A[0, 0] + A[1, 1]) # 理论最优步长 trace = [x.copy()] for _ in range(50): grad = A @ x x = x - lr * grad trace.append(x.copy())

你会看到普通梯度在山谷两侧来回反弹,动量法明显平滑,Adam 在前期更激进、后期更稳。然后把 A 改成 np.diag([1.0, 4.0]),同一套代码再跑一遍,会发现普通梯度的表现立刻变好。这就是条件数对收敛快慢的直观展示。整个实验不超过 40 行代码,不需要 GPU,两分钟就能跑完。

6.3 实操中容易踩的坑

最后记几个我踩过的坑。

第一个:曾经把“最速下降”当成“最快下降”,在病态问题里浪费了大量时间。现在只要看到收敛很慢,我第一反应是怀疑条件数,而不是盲目加大迭代次数。

第二个:动量的 β 和学习率是一对联合参数,不能只调其中一个。单独调大学习率很容易让增广矩阵的谱半径超过 1,表现为损失先降后飞。调试时如果发现“加了动量反而崩”,先检查 β 与学习率的组合是否越过稳定边界。

第三个:批量大小变化后要重新确认学习率。我踩过换 batch size 之后整个训练崩掉的坑。虽然有“线性缩放规则”这个经验公式,但大批量时收敛本身会变差,不一定直接按比例换学习率就完事。

第四个:做谱分析时别把左奇异向量和右奇异向量搞混。打印特征向量时经常看到方向对不上,本质上是两套坐标系的问题。特别注意 SVD 的 V 和 U 分别对应行空间和列空间的方向。

整理完这套 note3,我最深的体会是:优化器不是一堆孤立算法的堆叠,离了矩阵谱,很多结论都只是死记。如今我遇到训练收敛问题,第一反应不再是“再加个 trick”,而是做一次小小的谱侦察——大致摸清问题的病态程度,再决定要不要上动量、要不要开大学习率、要不要换自适应方法。这种看问题的方式,就是 18.065 最后这几讲留给我的最大收获。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询