SVD 奇异值分解与低秩近似Singular Value Decomposition & Low-Rank Approximation
特征分解只对方阵有效,而且未必有实特征值;SVD 却对任何长方矩阵都成立 —— 它把任何变换统一成「旋转 → 沿轴缩放 → 再旋转」三步。奇异值的大小顺序告诉你哪些方向值得保留,于是图像压缩、去噪、PCA 的数值实现、乃至大模型的 LoRA 微调,全都落在同一句话上:留下能量最大的前 k 张卡片。
奇异值与低秩 · 交互演示
为什么学这步?
现实里的数据矩阵几乎从不铺满整个空间:一张照片的相邻像素高度相关,一批句子的词向量挤在少数几个方向上。SVD 给出的正是「数据到底活在几个方向上」的精确定量答案,而且它是唯一在误差意义下最优的降维方式 —— PCA 的工业实现(scikit-learn、numpy)几乎都用 SVD 而不是显式的协方差矩阵特征分解,正是为了避免把条件数平方。同样的数学还解释了大模型微调:LoRA 用两个细长的矩阵乘出增量的效果,参数量能少两个数量级。
📌 发生了什么
- 任何 m×n 矩阵都能写成 UΣVᵀ:旋转 → 沿轴缩放 → 再旋转
- 奇异值恒为非负数并按从大到小排列,它的平方等于 AᵀA 的特征值
- 只保留前 k 个奇异值得到的低秩矩阵,是所有秩不超过 k 的矩阵里误差最小的
⚠️ 常见陷阱
- 奇异值不能为负,也没有虚数:别拿 σ = −2 当例子(特征值才可以)
- 秩亏时 u = A v / σ 会数值爆炸,必须按容差把过小的 σ 直接取 0
- U 与 V 的列符号并不唯一((−U)Σ(−V)ᵀ 同样合法),几何上只有方向有意义
✅ 本章小结
- A = UΣVᵀ = Σ σᵢuᵢvᵢᵀ:把矩阵拆成按重要性排序的秩 1 卡片之和
- σᵢ = √λᵢ(AᵀA),而且永远非负、降序;最大奇异值就是「最长拉伸倍数」
- 截断 SVD 是最优低秩逼近:误差恰为尾部能量的平方根,这就是压缩、去噪与 LoRA 的共同依据
📐 为什么奇异值就是 AᵀA 特征值的算术平方根?
把 A = UΣVᵀ 代入 AᵀA。U 的列互相垂直(UᵀU 是单位矩阵),Σ 是对角矩阵,于是中间那一串会塌成 Σ²:
这正是对称矩阵的正交对角化:右奇异向量 vᵢ 是 AᵀA 的特征向量,σᵢ² 是对应的特征值,所以 σᵢ = √λᵢ(AᵀA)(算术平方根天然非负)。几何上,σ₁² 就是 A 把单位向量拉得最长的那条边的长度平方。但要注意:把这条式子当成算法是危险的 —— 形成 AᵀA 会把条件数平方,κ(AᵀA) = κ(A)²,量级很小的奇异值会先被舍入误差吃掉,这就是工业实现直接对 A 做 SVD 的原因。本章引擎用的是单侧 Jacobi:反复把 A 的两列旋转到互相垂直,转完之后列长就是奇异值,累积的旋转就是 V,全程不碰 AᵀA。
📉 Eckart-Young:为什么「砍掉尾部」是最优的?
A = Σ σᵢuᵢvᵢᵀ 说明矩阵是一叠秩 1 卡片,σᵢ 就是每张卡片的权重,而且已经按大小排好序。取前 k 张得到 A_k,误差正好是剩下卡片的能量:
右半边的不等式就是 Eckart-Young 定理:任何秩不超过 k 的矩阵都比 A_k 差,所以「砍掉最小的那些奇异值」不是一种启发式,而是最优策略。这也解释了去噪为什么有效:噪声的能量几乎均匀地摊在所有方向上(每张卡片都沾一点),而信号集中在最先的几张上 —— 砍掉尾部,丢掉的主要是噪声。反过来,如果数据本身就是高秩的(例如纯随机噪声图片),那就没有便宜的近似可用,这正是压缩前必须先看一眼能量谱的原因。
💻 单侧 Jacobi 与伪逆的核心
// 单侧 Jacobi:把 A 的每一对列旋转到互相垂直(节选自 svd-solver.ts)
function svdSweep(B, V, m, n, tol) {
var rotations = 0;
for (var p = 0; p < n - 1; p++) {
for (var q = p + 1; q < n; q++) {
var ap = colNorm2(B, m, n, p), aq = colNorm2(B, m, n, q);
var gamma = colDot(B, m, n, p, q); // 这一对列的内积
if (Math.abs(gamma) <= tol * Math.sqrt(ap) * Math.sqrt(aq)) continue;
var zeta = (aq - ap) / (2 * gamma); // 使内积归零的旋转角
var t = (zeta >= 0 ? 1 : -1) / (Math.abs(zeta) + Math.sqrt(1 + zeta * zeta));
var c = 1 / Math.sqrt(1 + t * t), s = c * t;
rotateColumns(B, V, m, n, p, q, c, s); // 同时累积到 V 上
rotations++;
}
}
return rotations; // 本趟 0 次旋转 = 已收敛
}
// 转完所有趟之后:列长就是奇异值,B 除以列长就是 U,V 就是右奇异向量。
// 截断伪逆:小奇异值不能除,只能丢
function pseudoInverse(S, tol) {
var cut = tol * S[0]; // 相对容差,而不是绝对阈值
return S.map(function (sig) { return sig <= cut ? 0 : 1 / sig; });
}
📚 参考文献与延伸阅读
- Strang, Introduction to Linear Algebra, Ch. 7「Singular Value Decomposition」 — 把 A = UΣVᵀ 讲成旋转、缩放、再旋转的经典几何视角
- Hastie, Tibshirani & Friedman (2009), The Elements of Statistical Learning, §14.5「Principal Components Analysis」 — 中心化数据矩阵的 SVD 与协方差矩阵特征分解的等价性
- Golub & Van Loan, Matrix Computations, Ch. 2 §2.4-2.6 与 Ch. 8 — SVD 的存在性证明、单侧 Jacobi 算法与数值精度分析
- Hu et al. (2021), LoRA: Low-Rank Adaptation of Large Language Models — 低秩更新在大模型微调中的工程落地;对照阅读 → ML 第 11 步 · PCA 降维、DL 第 5 步 · 自编码器
📝 课后练习
检验你的理解——答对为止