奇异值分解(SVD):线性代数的瑞士军刀 本节摘要:SVD 是线性代数的瑞士军刀——每个矩阵都有一个,每个数据科学家都需要一个。特征分解只对方阵且要求满秩特征向量,而 SVD 对任意形状、任意秩的矩阵都成立,无任何附加条件。它把矩阵拆成三个因子,揭示矩阵对空间做的几何动作: ——Vᵀ 旋转输入、Σ 沿轴缩放、U 旋转到输出。本节从几何直觉出发,讲清左奇异向量(输出方向)、奇异值(缩放因子)、右奇异向量(输入方向),以及 的坐标级图像;给出外积形式与 Eckart-Young 定理(截断 SVD 是最优低秩近似);用从零的幂迭代实现 SVD;演示 SVD 在图像压缩(800×600 图存成 k×1401 个值)、推荐系统(Netflix 大奖的潜在因子)、NLP
本节摘要:SVD 是线性代数的瑞士军刀——每个矩阵都有一个,每个数据科学家都需要一个。特征分解只对方阵且要求满秩特征向量,而 SVD 对任意形状、任意秩的矩阵都成立,无任何附加条件。它把矩阵拆成三个因子,揭示矩阵对空间做的几何动作:
A = U·Σ·Vᵀ——Vᵀ 旋转输入、Σ 沿轴缩放、U 旋转到输出。本节从几何直觉出发,讲清左奇异向量(输出方向)、奇异值(缩放因子)、右奇异向量(输入方向),以及A·vᵢ = σᵢ·uᵢ的坐标级图像;给出外积形式与 Eckart-Young 定理(截断 SVD 是最优低秩近似);用从零的幂迭代实现 SVD;演示 SVD 在图像压缩(800×600 图存成 k×1401 个值)、推荐系统(Netflix 大奖的潜在因子)、NLP 潜在语义分析、去噪、伪逆求解最小二乘上的真实威力;最后点明 PCA 本质就是 SVD——sklearn 的 PCA 内部就用 SVD 而非协方差特征分解,因为后者平方条件数、损失精度。
对应原课程:Phase 01 · Lesson 11 ·
singular-value-decomposition(原英文phases/01-math-foundations/11-singular-value-decomposition/docs/en.md)。前置:第 1、2、3 节(线性代数)。
阅读完本节,你应当能够:
你有一个 1000×2000 的矩阵(用户-电影评分、文档-词频、图像像素),要压缩它、去噪、找隐藏结构或解最小二乘。特征分解只对方阵且要求满秩特征向量;SVD 对任意矩阵、任意形状、任意秩都成立,无任何条件。它把矩阵拆成三个因子,揭示矩阵对空间做了什么。它是线性代数里最通用、最有用的分解。
每个矩阵,无论形状,都依次做三件事:旋转、缩放、旋转。SVD 让这分解显式:
A = U · Σ · Vᵀ m×n m×m m×n n×n (任意) (旋转) (缩放) (旋转)
想象:SVD 接过一个矩阵,告诉你「它把一个输入球面先按 Vᵀ 旋转、再用 Σ 拉成椭球、最后用 U 旋转到输出」。奇异值就是椭球各轴的长度。
对 m×n 矩阵 A:
A = U·Σ·Vᵀ,其中: U m×m,正交(UᵀU = I) Σ m×n,对角(奇异值在对角) V n×n,正交(VᵀV = I) 奇异值 σ₁ ≥ σ₂ ≥ … ≥ σᵣ > 0,r = rank(A)
U 的列是左奇异向量,V 的列是右奇异向量,Σ 对角元是奇异值(非负、降序)。
关系:A·vᵢ = σᵢ·uᵢ——矩阵把第 i 个右奇异向量按 σᵢ 缩放,映到第 i 个左奇异向量。这给了「矩阵做什么」的逐坐标图像。
SVD 可写成秩 1 矩阵之和:
A = σ₁·u₁·v₁ᵀ + σ₂·u₂·v₂ᵀ + … + σᵣ·uᵣ·vᵣᵀ
每一项 σᵢ·uᵢ·vᵢᵀ 是秩 1 矩阵(外积)。第一项捕获最重要的单一模式,第二项次之,依此类推。截断求和即任意秩的最优近似(Eckart-Young 定理)。
A 的奇异值/向量直接来自 AᵀA 与 AAᵀ 的特征值/向量:
AᵀA 的特征向量,σᵢ² 是 AᵀA 的特征值;AAᵀ 的特征向量,其特征值也是 σᵢ²。三个推论:
AᵀA 特征分解算 SVD,但这平方了条件数、损失精度,专用 SVD 算法避免之。Eckart-Young-Mirsky 定理:对 A 的最佳秩 k 近似(无论 Frobenius 还是谱范数),就是保留前 k 个奇异值及对应向量:
A_k = U_k · Σ_k · V_kᵀ 近似误差(谱范数) = σ_{k+1} 近似误差(Frobenius) = √(σ_{k+1}² + … + σᵣ²)
这不仅是「一个好的」近似,而是可证最优的秩 k 近似——没有别的秩 k 矩阵比它更接近 A。若奇异值衰减快,小 k 就能捕获大部分矩阵;衰减慢则无低秩结构。
灰度图是像素强度矩阵。800×600 图有 48 万值。SVD 用远少于此近似它:
原始:800×600 = 480000 值 秩 k 近似:U_k(800×k) + Σ_k(k) + V_k(600×k) = k·(800+600+1) = k·1401 值 k=10: 14010 值 (2.9%) k=50: 70050 值 (14.6%) k=100: 140100 值 (29.2%)
关键洞见:自然图像奇异值衰减快——前几个捕获大结构(形状、梯度),后面的捕获细节与噪声。秩 50 截断常能给出肉眼几乎无差、却省 85% 存储的图。
Netflix 大奖让 SVD 出名。用户-电影评分矩阵大部分项缺失,但该矩阵有低秩——用户口味并不完全独立,有少数潜在因子(动作 vs 文艺、老 vs 新、烧脑 vs 直观)解释大部分偏好。SVD 把(填充后的)评分矩阵分解为 U(用户在潜在因子空间的画像)、Σ(各因子重要性)、Vᵀ(电影画像)。预测评分 = 用户画像与电影画像的点积(按奇异值加权),低秩近似填上缺失项。实战用 Simon Funk 的增量 SVD 或 ALS(交替最小二乘)直接处理缺失,但核心都是 SVD 潜在因子分解。
LSA(或 LSI)把 SVD 用于「词-文档」矩阵。秩 k=2 截断后:每个文档变成 2D「概念空间」里的点,每个词也变成同空间里的点;同主题文档聚拢,同义词聚拢(因它们出现在相似文档里,SVD 把它们归到同一潜在维度)。LSA 是最早从原始文本捕获语义相似度的成功方法之一,现代词嵌入(Word2Vec、GloVe)可视为其思想后裔。
带噪数据的信号集中在前几个奇异值,噪声分散在所有奇异值上。截断去掉噪声底。当奇异值有明显「间隙」时,间隙之上是信号(保留)、之下是噪声(丢弃)。这用于信号处理、科学测量、数据清洗——任何被加性噪声污染的矩阵,截断 SVD 都是分离信噪的原则性方法。
Moore-Penrose 伪逆 A⁺ 把矩阵求逆推广到非方阵与奇异矩阵,SVD 让它变得平凡:
若 A = U·Σ·Vᵀ,则 A⁺ = V·Σ⁺·Uᵀ 其中 Σ⁺:转置 Σ,把每个非零对角元 σᵢ 换成 1/σᵢ,零保持零。
伪逆解最小二乘:若 Ax = b 无精确解(超定系统),则 x = A⁺b 是最小二乘解(最小化 ‖Ax−b‖)。结果与正规方程 (AᵀA)⁻¹Aᵀb 相同,但数值更稳。
经 AᵀA 算特征分解会平方奇异值(AᵀA 特征值是 σᵢ²),从而平方条件数、放大误差:
A 奇异值 [1000, 1, 0.001],条件数 10⁶ AᵀA 特征值 [10⁶, 1, 10⁻⁶],条件数 10¹² → 多丢 6 位精度
现代 SVD 算法(Golub-Kahan 双对角化)直接在 A 上算,从不形成 AᵀA。这就是为什么总该用 np.linalg.svd(A) 而非 np.linalg.eig(A.T @ A)。
PCA 本质就是 SVD 在中心化数据上的应用——这不是类比,是字面意义上的同一计算。
中心化数据矩阵 X(n_samples × n_features): 协方差 C = (1/(n−1))·XᵀX X = U·Σ·Vᵀ(SVD) XᵀX = V·Σ²·Vᵀ C = (1/(n−1))·V·Σ²·Vᵀ 故主成分恰是右奇异向量 V,每成分解释方差 = σᵢ²/(n−1)。 sklearn 的 PCA 用 SVD 而非特征分解实现——更快更稳。
第 10 节学的降维,全是 SVD 在底下。PCA 是 SVD 在 ML 里最常见的应用。
完整源码见 phases/01-math-foundations/11-singular-value-decomposition/code/(Python svd.py,Julia svd.jl)。
思路:对 AᵀA 用幂迭代找最大奇异值与向量,再「缩减」矩阵,重复找下一个。
def power_iteration(M, num_iters=100): v = np.random.randn(M.shape[1]); v /= np.linalg.norm(v) for _ in range(num_iters): v = M @ v; v /= np.linalg.norm(v) eigenvalue = v @ M @ v return eigenvalue, v def svd_from_scratch(A, k=None): m, n = A.shape k = k or min(m, n) sigmas, us, vs = [], [], [] A_residual = A.copy().astype(float) for _ in range(k): eigenvalue, v = power_iteration(A_residual.T @ A_residual, num_iters=200) if eigenvalue < 1e-10: break # 剩余方向已被压扁 sigma = np.sqrt(eigenvalue) u = A_residual @ v / sigma sigmas.append(sigma); us.append(u); vs.append(v) A_residual -= sigma * np.outer(u, v) # 缩减,找下一个 return np.column_stack(us), np.array(sigmas), np.column_stack(vs)
设计要点:幂迭代对最大特征向量线性收敛;每轮「缩减排掉已找到的秩 1 成分」,等价于找剩余矩阵的最大奇异方向。教育性强但远慢于 LAPACK 的 Golub-Kahan。
def compress_image_svd(image, k): U, S, Vt = np.linalg.svd(image, full_matrices=False) return U[:, :k] @ np.diag(S[:k]) @ Vt[:k, :] for k in [1, 5, 10, 20, 50]: compressed = compress_image_svd(image, k) error = np.linalg.norm(image - compressed) / np.linalg.norm(image) ratio = k * (rows + cols + 1) / (rows * cols) print(f"k={k} error={error:.4f} storage={ratio:.1%}")
U, S, Vt = np.linalg.svd(A, full_matrices=False) A_pinv = Vt.T @ np.diag(1.0 / S) @ U.T x_svd = A_pinv @ b # = 最小二乘解
NumPy/LAPACK 的 SVD 是工业级实现:
U, S, Vt = np.linalg.svd(A, full_matrices=False) # 直接在 A 上,不形成 AᵀA
full_matrices=False(瘦 SVD)在 m≫n 或 n≫m 时省内存。sklearn 的 PCA 内部正是 svd(X_centered, full_matrices=False)。
💡 永远用
np.linalg.svd(A)而非eig(A.T @ A)——后者平方条件数,高条件数矩阵会丢多位精度。
outputs/skill-svd.md:一份关于「何时、如何在实际项目里用 SVD」的技能说明。code/svd.jl 演示同样概念,用 Julia 原生 svd() 与 LinearAlgebra。源码见 phases/01-math-foundations/11-singular-value-decomposition/code/。
AᵀA 特征分解从零实现完整 SVD(V 与奇异值来自特征分解,U = AVΣ⁻¹),与幂迭代版、NumPy 比较精度。A = U·Σ·Vᵀ = 旋转(Vᵀ)+ 缩放(Σ)+ 旋转(U);奇异值是椭球轴长。A·vᵢ = σᵢ·uᵢ:右奇异向量是输入方向,奇异值是缩放因子,左奇异向量是输出落点。AᵀA 特征分解平方条件数、损精度。下一节,我们把矩阵推广到任意维——张量运算:深度学习里
[batch, channel, height, width]这样的多维数组如何 reshape、广播、einsum,以及它们在 PyTorch/JAX 里的高效实现。