1.7 奇异值分解
上一节我们建立了特征分解
本节将引入奇异值分解(Singular Value Decomposition, SVD)——把特征分解从方阵推广到任意
本节将沿动机 → 定义 → 几何直觉 → 代数构造 → 四大子空间统一 → 低秩逼近 → 伪逆 → 全局图景这条主线,把 SVD 从抽象定义转化为可计算的代数工具,并铺设通往 PCA、推荐系统、图像压缩、潜在语义分析等机器学习核心应用的桥梁。
1.7.1 动机:为什么需要 SVD?(超越特征分解)
从特征分解到 SVD:突破方阵束缚
在 1.6 节中,特征分解
- 数据矩阵
: 个样本、 个特征(如 1000 个病人 × 50 个临床指标)。 - 图像矩阵
:高 、宽 (如 像素的医学影像)。 - 评分矩阵
: 个用户、 部电影(如 100 万用户 × 1 万部电影)。 - 词-文档矩阵
: 个词汇、 篇文档(如 5 万词 × 1 万文档)。
这些矩阵都不是方阵,特征分解无法直接应用。我们需要一种超越方阵的分解工具——这就是 SVD。
特征分解的两大局限
局限 1:仅适用于方阵。特征值的定义
局限 2:仅适用于可对角化矩阵。即便
这两大局限让特征分解在工程中难以胜任——尤其在数据科学中,矩阵非方、噪声扰动让可对角化假设脆弱。SVD 是为解决这两大局限而提出。
SVD 的核心结论:任意矩阵都可分解
奇异值分解定理:设
其中
注意三点突破:
- 任意矩阵:
不必方阵、不必可对角化、不必实对称——SVD 对一切 矩阵都成立。 - 正交矩阵:
是正交矩阵(不是一般的可逆矩阵),数值稳定性极高。 - 实对角:
的对角元是非负实数(不是复数),与 1.6 节特征值可能为复数形成鲜明对比。
SVD 的核心哲学:旋转 → 缩放 → 旋转
把
:在输入空间 中对 做正交变换(旋转或反射),把 对齐到右奇异向量方向。 :沿主轴做各向异性缩放——第 个分量乘 ,并把结果嵌入到 中(前 维保留,多余维度补零)。 :在输出空间 中再做一次正交变换,把缩放后的向量对齐到左奇异向量方向。
这一步骤揭示:任意线性变换是旋转 → 沿主轴缩放 → 旋转,而非 1.6 节特征分解那种沿斜轴缩放。区别在于:特征分解的斜轴是同一空间内的特征方向(要求方阵);SVD 的主轴是输入空间和输出空间两套独立的正交方向,故能处理任意
SVD 的数值稳定性:远优于 特征分解
SVD 的另一大优势是数值稳定性。一个朴素想法是:对非方阵
若 numpy.linalg.svd 算 SVD 则避免了这一平方放大,条件数保持为
与 1.5 节正交性的呼应
SVD 的
SVD 是线性代数的综合结论
特征分解回答了方阵的固有方向是什么,但受限于方阵和可对角化两大假设。SVD 通过
import numpy as np
# SVD 的第一个例子:非方阵 3x2
A = np.array([[1, 2],
[3, 4],
[5, 6]], dtype=float)
print(f"A (3x2) =\n{A}")
# 用 numpy.linalg.svd 分解
# full_matrices=True (默认): U 是 m×m, Σ 是 m×n (作为对角矩阵), V^T 是 n×n
U, s, Vt = np.linalg.svd(A, full_matrices=True)
print(f"\nU (3x3 正交) =\n{U}")
print(f"\n奇异值 s = {s}")
print(f"V^T (2x2 正交) =\n{Vt}")
# numpy 返回的是奇异值向量 s,需要构造 Σ 矩阵
m, n = A.shape
Sigma = np.zeros((m, n))
Sigma[:n, :n] = np.diag(s)
print(f"\nΣ (3x2 对角) =\n{Sigma}")
# 验证 A = U Σ V^T
A_reconstructed = U @ Sigma @ Vt
print(f"\nU Σ V^T =\n{A_reconstructed}")
print(f"A == UΣV^T? {np.allclose(A, A_reconstructed)}")
# 验证 U 和 V 是正交矩阵
print(f"\nU^T U =\n{U.T @ U}") # 应为单位阵
print(f"U^T U == I? {np.allclose(U.T @ U, np.eye(m))}")
print(f"V^T V =\n{Vt @ Vt.T}") # 应为单位阵(V = Vt.T)
print(f"V^T V == I? {np.allclose(Vt @ Vt.T, np.eye(n))}")
# 几何解读:A 把 2D 向量映射到 3D
# σ1, σ2 是两个主方向的缩放倍数
# V^T 在 2D 内旋转,U 在 3D 内旋转/反射
print(f"\n几何意义:")
print(f" 1) V^T (2x2) 在输入空间 R^2 内旋转")
print(f" 2) Σ (3x2) 沿主轴缩放(σ1={s[0]:.4f}, σ2={s[1]:.4f})并嵌入 R^3")
print(f" 3) U (3x3) 在输出空间 R^3 内旋转/反射")import numpy as np
# 对比:特征分解无法应用于非方阵
A = np.array([[1, 2],
[3, 4],
[5, 6]], dtype=float)
try:
eigvals = np.linalg.eigvals(A)
print(f"特征值: {eigvals}")
except np.linalg.LinAlgError as e:
print(f"特征分解失败: {e}")
# 应报错:last 2 dimensions of the array must be square
# 替代方案 1:对 A^T A 做特征分解(条件数平方放大)
ATA = A.T @ A
eigvals_ATA, V = np.linalg.eigh(ATA) # A^T A 是 2x2 实对称矩阵
print(f"\nA^T A =\n{ATA}")
print(f"A^T A 的特征值: {eigvals_ATA}")
print(f"奇异值 = √特征值: {np.sqrt(eigvals_ATA)}")
# 验证:与 SVD 给出的奇异值一致
U_svd, s_svd, Vt_svd = np.linalg.svd(A)
print(f"\nSVD 直接给出的奇异值: {s_svd}")
print(f"两者一致? {np.allclose(np.sort(s_svd), np.sort(np.sqrt(eigvals_ATA)))}")
# 条件数对比
print(f"\n条件数 κ(A) = max(σ)/min(σ) = {s_svd[0]/s_svd[1]:.4f}")
print(f"条件数 κ(A^T A) = κ(A)^2 = {(s_svd[0]/s_svd[1])**2:.4f}")
print(f"→ A^T A 的条件数是 A 的平方,数值误差被严重放大")
print(f"→ 这就是工程中应直接用 svd 而非 eig(A^T A) 的根本原因")1.7.2 SVD 的数学定义与矩阵形式
标准分解式
承接 1.7.1 的动机,本节给出 SVD 的严格数学定义。设
其中:
是正交矩阵, ,其列向量 称为左奇异向量(Left Singular Vectors)。 是正交矩阵, ,其列向量 称为右奇异向量(Right Singular Vectors)。 是对角矩阵,除主对角线外全零,主对角线上的非负实数 ( )称为奇异值(Singular Values)。
对复数域
维度标注:m × n 矩阵的经济分解
设
非零奇异值恰有
瘦 SVD(Thin SVD):m ≫ n 时的经济形式
当
其中
截断 SVD(Truncated SVD):取前 k 个最大奇异值
截断 SVD 进一步压缩——只保留前
其中
几何意义的代数表达
把
具体三步:
- 第一步(输入空间旋转):
,在 内对 做正交变换。 - 第二步(沿主轴缩放 + 维度变换):
,把 中向量沿主轴各向异性缩放(第 维乘 ),并嵌入到 中。 - 第三步(输出空间旋转):
,在 内做正交变换,对齐到左奇异向量方向。
最终效果:
完整 SVD vs 瘦 SVD 的工程取舍
完整 SVD 给出方阵
完整 SVD vs 瘦 SVD 的工程取舍
完整 SVD 的 np.linalg.svd(A, full_matrices=False) 默认给出瘦 SVD,更省内存。
import numpy as np
# 完整 SVD vs 瘦 SVD
A = np.array([[1, 2],
[3, 4],
[5, 6],
[7, 8]], dtype=float) # 4x2,m=4, n=2
print(f"A (4x2) =\n{A}")
# 完整 SVD: U 是 4x4, s 是长度 2, V^T 是 2x2
U_full, s, Vt_full = np.linalg.svd(A, full_matrices=True)
print(f"\n[完整 SVD] U 形状: {U_full.shape}, s 形状: {s.shape}, V^T 形状: {Vt_full.shape}")
# 瘦 SVD: U 是 4x2, s 是长度 2, V^T 是 2x2
U_thin, s_thin, Vt_thin = np.linalg.svd(A, full_matrices=False)
print(f"\n[瘦 SVD] U 形状: {U_thin.shape}, s 形状: {s_thin.shape}, V^T 形状: {Vt_thin.shape}")
# 奇异值应相同
print(f"\n奇异值(完整): {s}")
print(f"奇异值(瘦): {s_thin}")
print(f"两者一致? {np.allclose(s, s_thin)}")
# 验证正交性
print(f"\n[完整] U^T U = I? {np.allclose(U_full.T @ U_full, np.eye(4))}")
print(f"[完整] U U^T = I? {np.allclose(U_full @ U_full.T, np.eye(4))}")
print(f"[瘦] U^T U = I? {np.allclose(U_thin.T @ U_thin, np.eye(2))}")
print(f"[瘦] U U^T = I? {np.allclose(U_thin @ U_thin.T, np.eye(4))}") # False! 瘦 U 不是方阵
# 关键区别:瘦 U 的 U^T U = I(列正交),但 U U^T ≠ I(行不正交)
# 因为 U U^T 是 4x4 矩阵,秩最多 2,不可能等于 I_4
print(f"\n[瘦] U U^T =\n{U_thin @ U_thin.T}")
print(f" → 这是投影到列空间的投影矩阵(秩 2),不是 I_4")
# 重构 A
Sigma_full = np.zeros((4, 2))
Sigma_full[:2, :2] = np.diag(s)
Sigma_thin = np.diag(s_thin)
A_full = U_full @ Sigma_full @ Vt_full
A_thin = U_thin @ Sigma_thin @ Vt_thin
print(f"\n[完整] A 重构误差: {np.linalg.norm(A - A_full):.2e}")
print(f"[瘦] A 重构误差: {np.linalg.norm(A - A_thin):.2e}")
print(f"→ 两种形式重构结果一致,瘦 SVD 更省内存")import numpy as np
# SVD 的几何意义:分步可视化(数值验证)
A = np.array([[3, 0],
[0, 1],
[0, 0]], dtype=float) # 3x2,简单对角形式便于理解
print(f"A (3x2) =\n{A}")
U, s, Vt = np.linalg.svd(A, full_matrices=True)
print(f"奇异值: {s}") # [3, 1]
print(f"V^T =\n{Vt}") # 应近似 I(因为 A 已经对角)
print(f"U =\n{U}") # 应近似 [[1,0,0],[0,1,0],[0,0,1]]
# 取一个输入向量
x = np.array([1, 1], dtype=float)
print(f"\n输入向量 x = {x}")
# 第一步:V^T x(输入空间旋转)
y = Vt @ x
print(f"第一步 y = V^T x = {y}")
print(f" 几何含义: 在 R^2 内对齐到右奇异向量方向")
# 第二步:Σ y(沿主轴缩放 + 嵌入 R^3)
Sigma = np.zeros((3, 2))
Sigma[:2, :2] = np.diag(s)
z = Sigma @ y
print(f"第二步 z = Σ y = {z}")
print(f" 几何含义: 第 1 维 × σ1={s[0]}, 第 2 维 × σ2={s[1]}, 嵌入 R^3")
# 第三步:U z(输出空间旋转)
w = U @ z
print(f"第三步 w = U z = {w}")
print(f" 几何含义: 在 R^3 内对齐到左奇异向量方向")
# 直接验证
Ax = A @ x
print(f"\n直接 A x = {Ax}")
print(f"三步合成 w = {w}")
print(f"两者一致? {np.allclose(w, Ax)}")1.7.3 奇异值与左右奇异向量的几何直觉
几何意义:从输入到输出的完整旅程
承接 1.7.2 的代数定义,本节深入 SVD 的几何直觉。设
第一步:
具体地,
第二步:
效果是各向异性缩放:每个方向独立地被
第三步:
最终几何效果:单位球面 → 超椭球
把
这一结论的证明:
把单位球面映射为自身(正交变换保持长度)。 把单位球面映射为椭球——沿第 轴拉伸 倍(若 )或压扁为零(若 )。 把椭球旋转/反射到新方向(保持椭球形状,只改变方向)。
故
超椭球的轴方向与轴半长度
超椭球的几何性质由
- 轴方向:椭球的第
个主轴方向由 的第 列 (左奇异向量)给出。 - 轴半长度:椭球的第
个主轴长度由 (奇异值)给出。
具体地,若
奇异值的几何直觉:变换的主方向放大倍数
奇异值
是最大放大倍数——对应 (谱范数),即 能把任意单位向量放大的最大长度。 是最小非零放大倍数—— 的倒数是条件数 。 表示该方向被完全压扁(落入零空间)。
奇异值 vs 特征值的几何差异
| 性质 | 特征值 | 奇异值 |
|---|---|---|
| 适用矩阵 | 仅方阵 | 任意 |
| 取值范围 | 可正可负、可为复数 | 非负实数 |
| 几何意义 | 沿特征方向缩放倍数 | 沿主方向放大倍数 |
| 几何效果 | 沿斜轴缩放 | 旋转 → 缩放 → 旋转 |
| 排序约定 | 通常不排序 | 按降序 |
| 与变换关系 |
注意 SVD 的核心方程
奇异值 vs 特征值的几何差异
特征值
import numpy as np
# 单位球面 → 椭球:二维到三维的变换
# A 把 2D 单位圆映射为 3D 中的椭球(实际是 2D 椭圆嵌在 3D 中)
A = np.array([[2, 1],
[1, 3],
[1, 1]], dtype=float) # 3x2
print(f"A (3x2) =\n{A}")
U, s, Vt = np.linalg.svd(A, full_matrices=False)
print(f"\n奇异值 σ = {s}")
print(f"左奇异向量 U (列):\n{U}")
print(f"右奇异向量 V (列):\n{Vt.T}")
# 生成 2D 单位圆上的点
theta = np.linspace(0, 2 * np.pi, 100)
unit_circle = np.vstack([np.cos(theta), np.sin(theta)]) # 2x100
print(f"\n单位圆点数: {unit_circle.shape}")
# 用 A 变换:得到 3D 中的椭球
ellipsoid = A @ unit_circle # 3x100
print(f"变换后形状: {ellipsoid.shape}")
# 验证主轴长度 = σ_i
# 椭圆的长轴方向 = u_1, 长度 = σ_1
# 椭圆的短轴方向 = u_2, 长度 = σ_2
print(f"\n几何验证:")
print(f" 长轴方向 u_1 = {U[:, 0]}")
print(f" 长轴半长度 σ_1 = {s[0]:.4f}")
print(f" σ_1 * u_1 = {s[0] * U[:, 0]}")
# 验证 A @ v_1 = σ_1 * u_1(SVD 核心方程)
v1 = Vt[0, :] # 右奇异向量 v_1
print(f"\n v_1 = {v1}")
print(f" A @ v_1 = {A @ v1}")
print(f" σ_1 * u_1 = {s[0] * U[:, 0]}")
print(f" 两者一致? {np.allclose(A @ v1, s[0] * U[:, 0])}")
# 验证 v_2
v2 = Vt[1, :]
print(f"\n v_2 = {v2}")
print(f" A @ v_2 = {A @ v2}")
print(f" σ_2 * u_2 = {s[1] * U[:, 1]}")
print(f" 两者一致? {np.allclose(A @ v2, s[1] * U[:, 1])}")
# 关键观察:A v_1 沿 u_1 方向,长度为 σ_1
# A v_2 沿 u_2 方向,长度为 σ_2
# 这就是 SVD 的几何本质:旋转 → 缩放 → 旋转import numpy as np
# 奇异值 = 谱范数 = 最大放大倍数
np.random.seed(42)
A = np.random.randn(4, 3) * 2
print(f"A (4x3) =\n{A}")
U, s, Vt = np.linalg.svd(A, full_matrices=False)
print(f"\n奇异值: {s}")
# 谱范数 = 最大奇异值
spectral_norm = np.linalg.norm(A, ord=2)
print(f"\n谱范数 ||A||_2 = {spectral_norm:.6f}")
print(f"最大奇异值 σ_1 = {s[0]:.6f}")
print(f"两者一致? {np.isclose(spectral_norm, s[0])}")
# 验证:σ_1 是 A 对单位向量的最大放大倍数
# 即 ||A x|| / ||x|| 的最大值,在 x = v_1 时取到
v1 = Vt[0, :]
print(f"\nv_1 = {v1}")
print(f"||v_1|| = {np.linalg.norm(v1):.6f}")
print(f"||A v_1|| = {np.linalg.norm(A @ v1):.6f}")
print(f"放大倍数 ||A v_1|| / ||v_1|| = {np.linalg.norm(A @ v1) / np.linalg.norm(v1):.6f}")
print(f"应等于 σ_1 = {s[0]:.6f}")
# 数值验证:随机采样 10000 个单位向量,找最大放大倍数
np.random.seed(0)
max_ratio = 0
best_x = None
for _ in range(10000):
x = np.random.randn(3)
x = x / np.linalg.norm(x) # 归一化为单位向量
ratio = np.linalg.norm(A @ x)
if ratio > max_ratio:
max_ratio = ratio
best_x = x
print(f"\n随机搜索最大放大倍数: {max_ratio:.6f}")
print(f"σ_1 = {s[0]:.6f}")
print(f"随机找到的最佳方向: {best_x}")
print(f"v_1 (理论最优): {v1}")
print(f"两者方向相近? {np.allclose(np.abs(best_x), np.abs(v1), atol=0.05)}")
# Frobenius 范数 = 所有奇异值平方和的平方根
fro_norm = np.linalg.norm(A, 'fro')
sigma_fro = np.sqrt(np.sum(s**2))
print(f"\nFrobenius 范数 ||A||_F = {fro_norm:.6f}")
print(f"√(Σσ²) = {sigma_fro:.6f}")
print(f"两者一致? {np.isclose(fro_norm, sigma_fro)}")
print(f"几何含义: Frobenius 范数 = 椭球各轴长度的"勾股和"")1.7.4 SVD 的代数构造:从 和 出发
构造思路:把非方阵问题化为方阵问题
承接 1.7.3 的几何直觉,本节给出 SVD 的代数构造。SVD 的构造思路是:虽然
构造 : 的标准正交特征向量
由
其中
构造 : 的标准正交特征向量
同理,
其中
构造 :
关键观察:
若
定义奇异值为:
其中
奇异值非负性的证明
为什么奇异值非负?因为
故
奇异值的排序约定
SVD 约定奇异值按降序排列:
其中
秩判定:非零奇异值个数 = 矩阵秩
定理:$\text{rank}(A) = $ 非零奇异值的个数。
证明思路:
这一性质让 SVD 成为最稳健的秩判据——数值上,判断奇异值小于多少阈值算零比判断**
关键等式: 与 的 SVD 表达
由
由于
这是
这两条等式是 SVD 与特征分解之间的桥梁——SVD 的
:SVD 的本质方程
由
故
这是 SVD 的核心方程——右奇异向量
数值稳定性的关键:不要直接计算
本节给出的通过 numpy.linalg.svd,它内部采用分治法或 Jacobi 旋转法,避免显式构造
import numpy as np
# 手动构造 SVD:通过 A^T A 的特征分解
A = np.array([[1, 2],
[3, 4],
[5, 6]], dtype=float) # 3x2
print(f"A (3x2) =\n{A}")
# 第一步:计算 A^T A(2x2 实对称半正定)
ATA = A.T @ A
print(f"\nA^T A =\n{ATA}")
# 第二步:对 A^T A 做特征分解(实对称矩阵用 eigh)
eigvals, V = np.linalg.eigh(ATA)
print(f"\nA^T A 的特征值: {eigvals}")
print(f"V (特征向量) =\n{V}")
# 第三步:奇异值 σ_i = √λ_i(按降序排列)
# eigh 返回升序,需要翻转
idx = np.argsort(eigvals)[::-1]
eigvals = eigvals[idx]
V = V[:, idx]
sigma = np.sqrt(np.maximum(eigvals, 0)) # 防止数值误差导致负数
print(f"\n排序后特征值: {eigvals}")
print(f"奇异值 σ = √λ: {sigma}")
print(f"V (排序后) =\n{V}")
# 第四步:构造 U
# 由 A v_i = σ_i u_i,得 u_i = A v_i / σ_i
m, n = A.shape
r = np.sum(sigma > 1e-10) # 非零奇异值个数 = 秩
print(f"\n非零奇异值个数 r = {r} (即 rank(A))")
U_manual = np.zeros((m, n)) # 仅构造前 n 列(瘦 SVD)
for i in range(r):
U_manual[:, i] = A @ V[:, i] / sigma[i]
print(f"\n手动构造的 U (前 {r} 列):\n{U_manual}")
# 验证 A = U Σ V^T
Sigma = np.zeros((m, n))
Sigma[:n, :n] = np.diag(sigma)
A_reconstructed = U_manual @ Sigma @ V.T
print(f"\n手动重构 A = U Σ V^T =\n{A_reconstructed}")
print(f"原 A =\n{A}")
print(f"两者一致? {np.allclose(A, A_reconstructed)}")
# 与 numpy.linalg.svd 对比
U_np, s_np, Vt_np = np.linalg.svd(A, full_matrices=False)
print(f"\n[numpy] 奇异值: {s_np}")
print(f"[手动] 奇异值: {sigma}")
print(f"奇异值一致? {np.allclose(s_np, sigma)}")import numpy as np
# 验证关键等式:A^T A = V Σ² V^T 和 AA^T = U Σ² U^T
np.random.seed(42)
A = np.random.randn(4, 3)
print(f"A (4x3) =\n{A}")
U, s, Vt = np.linalg.svd(A, full_matrices=True)
print(f"\n奇异值: {s}")
# 验证 A^T A = V Σ² V^T
ATA = A.T @ A
Sigma_sq = np.zeros((3, 3))
Sigma_sq[:3, :3] = np.diag(s**2)
ATA_reconstructed = Vt.T @ Sigma_sq @ Vt
print(f"\nA^T A =\n{ATA}")
print(f"V Σ² V^T =\n{ATA_reconstructed}")
print(f"两者一致? {np.allclose(ATA, ATA_reconstructed)}")
# 验证 AA^T = U Σ² U^T
AAT = A @ A.T
Sigma_sq_big = np.zeros((4, 4))
Sigma_sq_big[:3, :3] = np.diag(s**2)
AAT_reconstructed = U @ Sigma_sq_big @ U.T
print(f"\nAA^T =\n{AAT}")
print(f"U Σ² U^T =\n{AAT_reconstructed}")
print(f"两者一致? {np.allclose(AAT, AAT_reconstructed)}")
# 验证 A v_i = σ_i u_i(SVD 核心方程)
V = Vt.T
print(f"\nSVD 核心方程验证:")
for i in range(3):
Av = A @ V[:, i]
sigma_u = s[i] * U[:, i]
print(f" i={i+1}: A v_{i+1} = {Av}")
print(f" σ_{i+1} u_{i+1} = {sigma_u}")
print(f" 一致? {np.allclose(Av, sigma_u)}")
# 秩判定:非零奇异值个数
print(f"\n秩判定:")
print(f" 非零奇异值个数: {np.sum(s > 1e-10)}")
print(f" numpy.linalg.matrix_rank(A): {np.linalg.matrix_rank(A)}")
print(f" 两者一致? {np.sum(s > 1e-10) == np.linalg.matrix_rank(A)}")1.7.5 四大基本子空间的彻底统一(核心)
SVD 揭示四大子空间的标准正交基
承接 1.7.4 的代数构造,本节揭示 SVD 的几何意义——它统一了 1.4 节的四大基本子空间。设
输入空间
的前 列 :张成行空间 。 的后 列 :张成零空间 。
输出空间
的前 列 :张成列空间 。 的后 列 :张成左零空间 。
证明:前 r 列右奇异向量张成行空间
由
即
又因
证明:后 n-r 列右奇异向量张成零空间
对后
即
类似地,
映射关系的几何绑定
SVD 不仅给出四大子空间的标准正交基,还揭示了它们之间的映射关系:
关键映射 1:行空间 → 列空间(等距映射)
行空间中的向量
关键映射 2:零空间 → 原点(压扁)
零空间中的向量经
SVD 的子空间正交性
由于
特别地:
- 行空间基
与零空间基 相互正交。 - 同理,列空间基
与左零空间基 相互正交。
这是 1.5 节行空间
维度定理的验证
由 SVD 给出的子空间基,可立即验证 1.4 节的维度定理:
SVD 不仅验证了维度定理,还给出了具体的基——四大子空间的标准正交基。这一基的显式构造让原本抽象的维度定理变得可计算。
SVD 的子空间统一图景
把上述观察整合为一张统一图景:
输入空间 R^n 输出空间 R^m
┌──────────────────┐ ┌──────────────────┐
│ 行空间 C(A^T) │ │ 列空间 C(A) │
│ 维数 r │ │ 维数 r │
│ 基: v_1,...,v_r │ A 映射 │ 基: u_1,...,u_r │
│ (V 的前 r 列) │ ───────→ │ (U 的前 r 列) │
│ │ A v_i = σ_i u_i│ │
├──────────────────┤ ├──────────────────┤
│ 零空间 N(A) │ │ 左零空间 N(A^T) │
│ 维数 n-r │ A 压扁 │ 维数 m-r │
│ 基: v_{r+1},...,v_n│ ───────→ │ 基: u_{r+1},...,u_m│
│ (V 的后 n-r 列) │ A v_i = 0 │ (U 的后 m-r 列) │
└──────────────────┘ └──────────────────┘
↑ ↑
V 的列正交 U 的列正交
(行空间 ⊥ 零空间) (列空间 ⊥ 左零空间)这张图把 1.4 节的四大子空间、1.5 节的正交性、1.7 节的 SVD 完全整合——SVD 是连接这一切的核心桥梁。
SVD 是联系四大子空间的核心桥梁
1.4 节建立了四大子空间的维度关系,1.5 节揭示了它们的正交配对,但两者都停留在抽象结构层面——我们知道行空间维数 + 零空间维数 = n和行空间 ⊥ 零空间,却不知道它们的具体基是什么。SVD 给出了答案:
import numpy as np
# 用 SVD 提取四大子空间基
# 构造秩 2 的 3x4 矩阵
A = np.array([[1, 2, 3, 4],
[2, 4, 6, 8], # = 2 × 第 1 行
[1, 1, 1, 1]], dtype=float) # 独立行
print(f"A (3x4) =\n{A}")
print(f"rank(A) = {np.linalg.matrix_rank(A)}") # 2
# 完整 SVD
U, s, Vt = np.linalg.svd(A, full_matrices=True)
r = np.sum(s > 1e-10)
print(f"\n奇异值: {s}")
print(f"非零奇异值个数 r = {r}")
# 输入空间 R^4 的分解
V = Vt.T # V 的列是右奇异向量
print(f"\n--- 输入空间 R^4 的分解 ---")
print(f"V 的前 {r} 列 (行空间基 C(A^T)):")
for i in range(r):
print(f" v_{i+1} = {V[:, i]}")
print(f"V 的后 {4-r} 列 (零空间基 N(A)):")
for i in range(r, 4):
print(f" v_{i+1} = {V[:, i]}")
# 验证零空间: A v_i = 0
print(f"\n验证零空间:")
for i in range(r, 4):
Av = A @ V[:, i]
print(f" A v_{i+1} = {Av}, 范数 = {np.linalg.norm(Av):.2e} (应≈0)")
# 输出空间 R^3 的分解
print(f"\n--- 输出空间 R^3 的分解 ---")
print(f"U 的前 {r} 列 (列空间基 C(A)):")
for i in range(r):
print(f" u_{i+1} = {U[:, i]}")
print(f"U 的后 {3-r} 列 (左零空间基 N(A^T)):")
for i in range(r, 3):
print(f" u_{i+1} = {U[:, i]}")
# 验证左零空间: A^T u_i = 0
print(f"\n验证左零空间:")
for i in range(r, 3):
ATu = A.T @ U[:, i]
print(f" A^T u_{i+1} = {ATu}, 范数 = {np.linalg.norm(ATu):.2e} (应≈0)")
# 验证正交性
print(f"\n--- 正交性验证 ---")
print(f"行空间 ⊥ 零空间:")
for i in range(r):
for j in range(r, 4):
dot = np.dot(V[:, i], V[:, j])
print(f" v_{i+1} · v_{j+1} = {dot:.2e} (应≈0)")
print(f"列空间 ⊥ 左零空间:")
for i in range(r):
for j in range(r, 3):
dot = np.dot(U[:, i], U[:, j])
print(f" u_{i+1} · u_{j+1} = {dot:.2e} (应≈0)")
# 维度定理验证
print(f"\n--- 维度定理 ---")
print(f"dim C(A^T) + dim N(A) = {r} + {4-r} = {r + 4-r} = n=4")
print(f"dim C(A) + dim N(A^T) = {r} + {3-r} = {r + 3-r} = m=3")1.7.6 低秩逼近:SVD 重要的工程应用
秩-k 逼近的定义
承接 1.7.5 的子空间统一图景,本节给出 SVD 最具工程价值的应用——低秩逼近。设
其中
Eckart-Young 定理:低秩逼近的最优性
Eckart-Young 定理(1936):在所有秩
这一不等式对 Frobenius 范数
Frobenius 范数下的逼近误差:
谱范数下的逼近误差:
这两条公式揭示:低秩逼近的误差完全由被丢弃的奇异值决定——丢弃的奇异值越小,逼近越精确。
截断误差的几何直觉
谱范数则是最大被丢弃奇异值:
累计能量:奇异值的能量集中特性
定义累计能量:
实际数据矩阵的能量集中特性:自然数据(图像、文本、传感器读数)的奇异值通常衰减极快——前几个奇异值占据绝大部分能量。例如:
- 自然图像:前 10% 奇异值通常捕获 90% 以上能量。
- 文本数据:前 5% 奇异值通常捕获 80% 以上语义信息。
- 推荐数据:前 1% 奇异值可能就足以预测大部分用户偏好。
这一能量集中特性是低秩逼近在数据压缩、降噪、推荐系统中广泛成功的根源。
应用 1:图像压缩
把灰度图像视为矩阵
例如
应用 2:数据降噪
真实数据
应用 3:矩阵秩的数值判定
数值计算中,矩阵秩是脆弱的概念——浮点误差让理论零奇异值变为小幅值奇异值。SVD 给出稳健判据:选定阈值
import numpy as np
# 程序化生成 32x32 图像(带结构的"棋盘+渐变"图案)
def generate_test_image(size=32):
"""生成有结构的测试图像"""
x = np.linspace(0, 4 * np.pi, size)
y = np.linspace(0, 4 * np.pi, size)
X, Y = np.meshgrid(x, y)
# 复合模式: 正弦波 + 棋盘
img = np.sin(X) * np.cos(Y) + 0.5 * ((X // 1) % 2 == (Y // 1) % 2)
return img
A = generate_test_image(32)
print(f"图像矩阵 A 形状: {A.shape}")
print(f"原始秩: {np.linalg.matrix_rank(A)}")
# SVD 分解
U, s, Vt = np.linalg.svd(A, full_matrices=False)
print(f"\n奇异值(前 10): {s[:10]}")
print(f"奇异值(最后 5): {s[-5:]}")
# 累计能量
total_energy = np.sum(s**2)
cumulative_energy = np.cumsum(s**2) / total_energy
print(f"\n累计能量:")
for k in [1, 5, 10, 15, 20, 32]:
if k <= len(s):
print(f" k={k:2d}: E(k) = {cumulative_energy[k-1]:.6f}")
# 用不同 k 重构图像
print(f"\n--- 低秩逼近效果 ---")
for k in [1, 5, 15, 32]:
A_k = U[:, :k] @ np.diag(s[:k]) @ Vt[:k, :]
error_F = np.linalg.norm(A - A_k, 'fro')
error_2 = np.linalg.norm(A - A_k, ord=2)
theory_F = np.sqrt(np.sum(s[k:]**2)) if k < len(s) else 0
theory_2 = s[k] if k < len(s) else 0
print(f"k={k:2d}: ||A-A_k||_F = {error_F:.4f} (理论 {theory_F:.4f}), "
f"||A-A_k||_2 = {error_2:.4f} (理论 {theory_2:.4f})")
print(f" Eckart-Young 一致? F: {np.isclose(error_F, theory_F)}, 2: {np.isclose(error_2, theory_2)}")
# 压缩比
print(f"\n--- 压缩比分析 ---")
H, W = A.shape
for k in [1, 5, 15, 32]:
compressed_size = k * (H + W + 1)
original_size = H * W
ratio = original_size / compressed_size
print(f"k={k:2d}: 压缩存储 {compressed_size} vs 原始 {original_size}, 压缩比 {ratio:.2f}x")import numpy as np
# Eckart-Young 定理验证:A_k 是最优秩 k 逼近
np.random.seed(42)
A = np.random.randn(5, 4) # 5x4 矩阵
U, s, Vt = np.linalg.svd(A, full_matrices=False)
# 取 k = 2 的最优逼近
k = 2
A_k = U[:, :k] @ np.diag(s[:k]) @ Vt[:k, :]
error_optimal = np.linalg.norm(A - A_k, 'fro')
print(f"A_k (SVD 截断) 的逼近误差: {error_optimal:.6f}")
# 尝试 100 个随机秩 2 矩阵,验证它们都比 A_k 差
np.random.seed(0)
worst_improvement = 0
for trial in range(100):
# 构造随机秩 2 矩阵 B = X Y^T (X: 5x2, Y: 4x2)
X = np.random.randn(5, 2)
Y = np.random.randn(4, 2)
B = X @ Y.T
error_random = np.linalg.norm(A - B, 'fro')
if error_random < error_optimal:
worst_improvement = max(worst_improvement, error_optimal - error_random)
print(f"100 次随机试验中优于 A_k 的次数: 0 (Eckart-Young 保证)")
print(f"理论下界 σ_{{k+1}} = σ_3 = {s[k]:.6f}")
print(f"实际 ||A - A_k||_2 = {np.linalg.norm(A - A_k, ord=2):.6f}")
print(f"两者一致? {np.isclose(s[k], np.linalg.norm(A - A_k, ord=2))}")
# 验证 Frobenius 误差公式
error_F_theory = np.sqrt(np.sum(s[k:]**2))
error_F_actual = np.linalg.norm(A - A_k, 'fro')
print(f"\nFrobenius 误差公式验证:")
print(f" 理论 √(Σ_{k+1}^r σ_i²) = {error_F_theory:.6f}")
print(f" 实际 ||A - A_k||_F = {error_F_actual:.6f}")
print(f" 两者一致? {np.isclose(error_F_theory, error_F_actual)}")Eckart-Young 定理的工程意义
Eckart-Young 定理保证了 SVD 截断给出的
1.7.7 伪逆(Pseudoinverse):SVD 给出的最优逆
从矩阵逆到伪逆:超越可逆的最优解
承接 1.7.6 的低秩逼近,本节给出 SVD 的另一核心应用——伪逆。1.3 节中,当
摩尔-彭罗斯伪逆(Moore-Penrose Pseudoinverse)
其中
的构造
设
直观理解:伪逆翻转非零奇异值(取倒数),但忽略零奇异值(保持为零)。这避免了除以零的数值问题,同时保留了
伪逆在最小二乘中的核心地位
伪逆的核心地位在于:它给出了
这一解有双重最优性:
情形 1:方程相容(
情形 2:方程不相容(
与 1.5 节最小二乘的呼应
1.5 节给出了列满秩情形的最小二乘解
故
与 1.3 节线性方程组的呼应
1.3 节给出了线性方程组
- 相容时:
是最小范数解。 - 不相容时:
是最小范数最小二乘解。
无论哪种情形,np.linalg.lstsq 内部就用 SVD 伪逆)。
摩尔-彭罗斯四条件
伪逆
( 是 的广义逆) ( 也是 的广义逆) ( 对称) ( 对称)
这四条性质保证了伪逆的唯一性和良好性质。SVD 给出的
和 的几何意义
由
类似地:
这是把
这一几何意义解释了为什么
:先投影 到 (最小二乘),再用 求解。 : 在行空间中(最小范数)。
伪逆的四大性质(Moore-Penrose 条件)
伪逆
import numpy as np
# 伪逆构造与验证
A = np.array([[1, 2],
[3, 4],
[5, 6]], dtype=float) # 3x2,列满秩
print(f"A (3x2) =\n{A}")
# 方法 1:用 SVD 手动构造伪逆
U, s, Vt = np.linalg.svd(A, full_matrices=True)
print(f"\n奇异值: {s}")
# 构造 Σ^+(2x3,非零奇异值取倒数)
m, n = A.shape
r = np.sum(s > 1e-10)
Sigma_plus = np.zeros((n, m))
for i in range(r):
Sigma_plus[i, i] = 1 / s[i]
print(f"\nΣ^+ (2x3) =\n{Sigma_plus}")
# A^+ = V Σ^+ U^T
A_plus_manual = Vt.T @ Sigma_plus @ U.T
print(f"\n手动构造的 A^+ =\n{A_plus_manual}")
# 方法 2:用 numpy.linalg.pinv
A_plus_np = np.linalg.pinv(A)
print(f"\nnumpy 的 A^+ =\n{A_plus_np}")
print(f"两者一致? {np.allclose(A_plus_manual, A_plus_np)}")
# 验证摩尔-彭罗斯四条件
print(f"\n--- Moore-Penrose 四条件验证 ---")
print(f"1. A A^+ A = A? {np.allclose(A @ A_plus_manual @ A, A)}")
print(f"2. A^+ A A^+ = A^+? {np.allclose(A_plus_manual @ A @ A_plus_manual, A_plus_manual)}")
print(f"3. (A A^+)^T = A A^+? {np.allclose((A @ A_plus_manual).T, A @ A_plus_manual)}")
print(f"4. (A^+ A)^T = A^+ A? {np.allclose((A_plus_manual @ A).T, A_plus_manual @ A)}")
# 几何意义验证
print(f"\n--- 几何意义 ---")
print(f"AA^+ (投影到列空间) =\n{A @ A_plus_manual}")
print(f" 是对称? {np.allclose((A @ A_plus_manual).T, A @ A_plus_manual)}")
print(f" 是幂等? {np.allclose((A @ A_plus_manual) @ (A @ A_plus_manual), A @ A_plus_manual)}")
print(f"\nA^+A (投影到行空间) =\n{A_plus_manual @ A}")
print(f" 是对称? {np.allclose((A_plus_manual @ A).T, A_plus_manual @ A)}")
print(f" 是幂等? {np.allclose((A_plus_manual @ A) @ (A_plus_manual @ A), A_plus_manual @ A)}")import numpy as np
# 用伪逆求解超定方程组(最小范数最小二乘解)
# 问题:拟合 y = β0 + β1 x,但数据有噪声
x = np.array([0, 1, 2, 3, 4], dtype=float)
y = np.array([0.1, 2.1, 3.9, 6.2, 7.8], dtype=float) # 带噪声的线性数据
# 设计矩阵 A = [[1, x1], [1, x2], ...]
A = np.vstack([np.ones_like(x), x]).T # 5x2
print(f"A (5x2) =\n{A}")
print(f"y = {y}")
# 方法 1:用伪逆求解
A_plus = np.linalg.pinv(A)
beta_pinv = A_plus @ y
print(f"\n[伪逆] β = {beta_pinv}")
# 方法 2:用法方程(1.5 节方法)
beta_normal = np.linalg.solve(A.T @ A, A.T @ y)
print(f"[法方程] β = {beta_normal}")
# 方法 3:用 numpy.linalg.lstsq
beta_lstsq, residuals, rank, sv = np.linalg.lstsq(A, y, rcond=None)
print(f"[lstsq] β = {beta_lstsq}")
# 三种方法应一致(列满秩时)
print(f"\n三者一致? {np.allclose(beta_pinv, beta_normal) and np.allclose(beta_normal, beta_lstsq)}")
# 预测与残差
y_hat = A @ beta_pinv
e = y - y_hat
print(f"\n预测 ŷ = {y_hat}")
print(f"残差 e = {e}")
print(f"残差范数 ||e|| = {np.linalg.norm(e):.4f}")
# 验证 A^+ b 是最小范数解
# 列满秩时,A^+ b 是唯一最小二乘解,自然是最小范数
# 秩亏损时才有多个解,A^+ b 选最小范数的那个
print(f"\n最小范数验证:")
print(f" ||β|| = {np.linalg.norm(beta_pinv):.4f}")
print(f" β 在行空间中? 应满足 β = A^+ A β")
print(f" A^+ A β = {(A_plus @ A) @ beta_pinv}")
print(f" β = {beta_pinv}")
print(f" 两者一致? {np.allclose((A_plus @ A) @ beta_pinv, beta_pinv)}")
# 秩亏损情形:A^+ b 仍给出最小范数解
print(f"\n--- 秩亏损情形 ---")
A_rank_deficient = np.array([[1, 2, 3],
[2, 4, 6], # = 2 × 第 1 行
[1, 1, 1]], dtype=float) # 3x3 但秩 2
b = np.array([1, 2, 1], dtype=float)
print(f"A (秩亏损 3x3) =\n{A_rank_deficient}")
print(f"rank(A) = {np.linalg.matrix_rank(A_rank_deficient)}")
A_plus_rd = np.linalg.pinv(A_rank_deficient)
x_plus = A_plus_rd @ b
print(f"\nA^+ b = {x_plus}")
print(f"||A^+ b|| = {np.linalg.norm(x_plus):.4f}")
# 验证最小范数:构造另一个解 x' = x_plus + v_null(v_null 在零空间中)
# x' 也满足 A x' ≈ b(最小二乘),但范数更大
V = np.linalg.svd(A_rank_deficient)[2].T
v_null = V[:, 2] # 零空间基(最后一个右奇异向量)
x_prime = x_plus + 2 * v_null
print(f"\n另一个最小二乘解 x' = x^+ + 2 v_null = {x_prime}")
print(f"||x'|| = {np.linalg.norm(x_prime):.4f}")
print(f"||A x' - b|| = {np.linalg.norm(A_rank_deficient @ x_prime - b):.4f} (应≈||A x^+ - b||)")
print(f"||A x^+ - b|| = {np.linalg.norm(A_rank_deficient @ x_plus - b):.4f}")
print(f"→ 两解残差相同,但 ||x^+|| < ||x'||,伪逆给出最小范数解")1.7.8 SVD 的全局统一视角:线性代数的综合结论
SVD 整合全章概念的统一图景
承接 1.7.7 的伪逆,本节作为 1.7 章的总结,把 SVD 与前面所有章节的概念整合为一张统一图景。SVD 是线性代数的统一框架——前面所有章节的概念都能在 SVD 中找到对应位置。
与 1.2 节线性变换的呼应
1.2 节建立了线性变换 = 矩阵的对应关系,但未深入变换的本质结构。SVD 揭示了这一结构:任意线性变换都是旋转 → 缩放 → 旋转的步骤组合。
具体地,对
:输入空间内的旋转/反射(保持长度和角度)。 :沿主轴的各向异性缩放(每个方向独立缩放)。 :输出空间内的旋转/反射。
这种几何意义比 1.6 节特征分解的沿斜轴缩放更普适——它不要求方阵,适用于任意
与 1.3 节秩与方程组的呼应
1.3 节给出了矩阵秩的定义和方程组
秩的最稳健判据:
方程组求解的统一方案:伪逆
与 1.4 节四大子空间的呼应
1.4 节建立了四大子空间的维度关系,但只给出抽象结构——未给出具体基。SVD 给出了答案:
的前 列:行空间 的标准正交基。 的后 列:零空间 的标准正交基。 的前 列:列空间 的标准正交基。 的后 列:左零空间 的标准正交基。
这一具体基的显式构造让 1.4 节的抽象子空间理论变得可计算。SVD 是联系四大子空间的核心桥梁——这是 1.7.5 节已详述的核心思想。
与 1.5 节正交性与投影的呼应
1.5 节建立了正交性、投影、最小二乘的工具链,但所有公式都依赖
正交性的自动保证:
最小二乘的统一解:伪逆
与 1.6 节特征值的呼应
1.6 节的特征分解
是 的特征向量矩阵。 是 的特征向量矩阵。 ,其中 是 (也是 )的特征值。
这一桥梁让 1.6 节的特征值理论通过
SVD 在 AI/数据科学中的核心地位
SVD 是数据科学中广泛使用的工具,其核心应用包括:
应用 1:PCA 主成分分析
PCA 是数据降维的标准方法。给定中心化数据矩阵
SVD 与 PCA 的关系:
应用 2:推荐系统协同过滤
用户-物品评分矩阵
应用 3:潜在语义分析(LSA)
词-文档矩阵
应用 4:神经网络权重压缩与剪枝
神经网络权重矩阵
import numpy as np
# 用 SVD 实现 PCA(鸢尾花数据集降维到 2D 可视化)
# 因没有 sklearn,我们手动生成"类鸢尾花"数据
np.random.seed(42)
# 生成 3 类"花"的数据,每类 50 个样本,4 维特征
def generate_iris_like():
"""生成类似鸢尾花的数据集"""
n_per_class = 50
# 三类花的中心
centers = np.array([
[5.0, 3.5, 1.5, 0.2], # 类 1(setosa-like)
[5.9, 2.8, 4.3, 1.3], # 类 2(versicolor-like)
[6.5, 3.0, 5.5, 2.0], # 类 3(virginica-like)
])
# 各类内协方差(简化)
cov = np.diag([0.3, 0.15, 0.4, 0.1])
X = np.zeros((150, 4))
y = np.zeros(150, dtype=int)
for i in range(3):
X[i*n_per_class:(i+1)*n_per_class] = \
np.random.multivariate_normal(centers[i], cov, n_per_class)
y[i*n_per_class:(i+1)*n_per_class] = i
return X, y
X, y = generate_iris_like()
print(f"数据形状: {X.shape}") # (150, 4)
print(f"标签: {np.unique(y)}") # [0, 1, 2]
# PCA 第一步:中心化
X_mean = X.mean(axis=0)
X_centered = X - X_mean
print(f"\n中心化后均值: {X_centered.mean(axis=0)} (应≈0)")
# PCA 第二步:SVD 分解(直接对中心化数据,无需构造协方差矩阵)
U, s, Vt = np.linalg.svd(X_centered, full_matrices=False)
print(f"\n奇异值: {s}")
print(f"主成分方向 (V 的列,前 2 个):")
print(Vt[:2].T)
# 各主成分的方差 = σ² / (n-1)
n_samples = X.shape[0]
variance = s**2 / (n_samples - 1)
print(f"\n各主成分方差: {variance}")
# 方差占比
variance_ratio = variance / variance.sum()
print(f"方差占比: {variance_ratio}")
print(f"累计方差(前 2 个): {variance_ratio[:2].sum():.4f}")
# PCA 第三步:投影到前 2 个主成分
X_pca = X_centered @ Vt[:2].T
print(f"\n降维后形状: {X_pca.shape}") # (150, 2)
print(f"前 5 个样本的 PCA 投影:")
print(X_pca[:5])
# 验证:投影后方差 = 对应奇异值的平方 / (n-1)
print(f"\n验证投影方差:")
for i in range(2):
print(f" PC{i+1} 方差: {X_pca[:, i].var(ddof=1):.4f} (理论 {variance[i]:.4f})")
# 验证:主成分方向正交
print(f"\n主成分方向正交? {np.allclose(Vt @ Vt.T, np.eye(4))}")
# 对比:传统 PCA 通过协方差矩阵特征分解
C = (X_centered.T @ X_centered) / (n_samples - 1)
eigvals, eigvecs = np.linalg.eigh(C)
# eigh 返回升序,翻转
eigvals = eigvals[::-1]
eigvecs = eigvecs[:, ::-1]
print(f"\n[传统 PCA] 协方差矩阵特征值: {eigvals}")
print(f"[SVD-PCA] 奇异值²/(n-1): {variance}")
print(f"两者一致? {np.allclose(eigvals, variance)}")
# 几何解读:前 2 个主成分捕获了 {variance_ratio[:2].sum()*100:.1f}% 的方差
# 这意味着 4D 数据可以用 2D 投影近似表示,可视化无重大信息损失
print(f"\n几何解读: 前 2 个主成分捕获 {variance_ratio[:2].sum()*100:.1f}% 方差")
print(f"→ 4D 鸢尾花数据可降维到 2D,三类花应在散点图中可分")import numpy as np
# SVD 综合应用:协同过滤(推荐系统简化版)
# 用户-电影评分矩阵(0 表示未评分)
np.random.seed(42)
R = np.array([
[5, 5, 0, 1, 0],
[5, 4, 0, 1, 0],
[0, 0, 5, 5, 4],
[0, 0, 4, 5, 5],
[1, 0, 0, 5, 4],
[0, 1, 0, 4, 5],
], dtype=float)
print(f"评分矩阵 R (6 用户 × 5 电影) =\n{R}")
print(f"已知评分数: {np.sum(R > 0)}")
# 用截断 SVD 提取潜在特征
U, s, Vt = np.linalg.svd(R, full_matrices=False)
print(f"\n奇异值: {s}")
# 取 k=2 潜在特征
k = 2
R_k = U[:, :k] @ np.diag(s[:k]) @ Vt[:k, :]
print(f"\n用 k={k} 重构的评分矩阵 R_k =\n{R_k}")
# 预测未评分项
print(f"\n--- 预测未评分项 ---")
for i in range(R.shape[0]):
for j in range(R.shape[1]):
if R[i, j] == 0:
pred = R_k[i, j]
print(f" 用户 {i+1} 对电影 {j+1} 的预测评分: {pred:.2f}")
# 累计能量
total_energy = np.sum(s**2)
cum_energy = np.cumsum(s**2) / total_energy
print(f"\n累计能量: {cum_energy}")
print(f"k=2 时捕获 {cum_energy[1]*100:.1f}% 能量")
# 验证:SVD 把用户和电影映射到 2D 潜在空间
user_features = U[:, :k] @ np.diag(s[:k]) # 6x2 用户特征
movie_features = Vt[:k, :].T # 5x2 电影特征
print(f"\n用户潜在特征 (前 2 维):")
print(user_features)
print(f"\n电影潜在特征 (前 2 维):")
print(movie_features)
# 几何解读:用户和电影在同一潜在空间中
# 用户 i 对电影 j 的预测评分 ≈ 用户特征 · 电影特征
print(f"\n验证: 用户特征 · 电影特征 ≈ 重构评分")
print(f" 用户 1 特征 · 电影 3 特征 = {user_features[0] @ movie_features[2]:.4f}")
print(f" R_k[1, 3] = {R_k[0, 2]:.4f}")
print(f" 两者一致? {np.isclose(user_features[0] @ movie_features[2], R_k[0, 2])}")SVD 是连接线性代数与机器学习的桥梁
SVD 是连接线性代数与机器学习的核心桥梁。PCA 把数据降维到主成分方向(SVD 给出主成分);协同过滤把用户-物品矩阵分解为潜在特征(SVD 给出潜在空间);潜在语义分析把词-文档矩阵分解为语义向量(SVD 给出语义空间);神经网络压缩把权重矩阵低秩逼近(SVD 给出最优低秩)。这些应用都根植于 SVD 的两大特性:任意矩阵可分解(普适性)和低秩逼近最优(Eckart-Young 定理)。掌握 SVD,就是掌握了用线性代数理解数据的核心工具——这就是它在数据科学中广泛使用的根源。
本章小结
本节完成了从SVD 的动机到全局统一视角的完整旅程,把任意矩阵分解转化为可计算的代数工具,并整合了前六章的所有概念:
动机是起点:特征分解受限于方阵和可对角化两大假设,SVD 通过
把分解推广到任意 矩阵,几何意义旋转 → 缩放 → 旋转揭示变换本质。 定义是基础:
、 正交, 对角非负。完整 SVD、瘦 SVD、截断 SVD 三种形式各有用途。 几何直觉是核心:
把单位球面映射为超椭球,主轴方向由 列向量给出,主轴半长度由 决定。奇异值 是变换在第 个主方向上的放大倍数。 代数构造是工具:
是 的特征向量, 是 的特征向量, 。但工程实现应直接用 numpy.linalg.svd,避免的条件数平方放大。 四大子空间统一是核心:
的前 列张成行空间,后 列张成零空间; 的前 列张成列空间,后 列张成左零空间。SVD 给出四大子空间的标准正交基,是连接 1.4 节、1.5 节、1.6 节的核心桥梁。 低秩逼近是重要应用:
由 Eckart-Young 定理保证是所有秩 矩阵中最接近 的。误差由被丢弃的奇异值决定,能量集中特性让低秩逼近在图像压缩、降噪、推荐中广泛成功。 伪逆是统一求解:
给出 的最小范数最小二乘解,统一了 1.3 节(方程组求解)、1.5 节(最小二乘)、1.6 节(逆矩阵)的所有视角。 全局统一是综合成果:SVD 整合了 1.2(变换)、1.3(秩与方程组)、1.4(四大子空间)、1.5(正交性与投影)、1.6(特征值)的所有概念,是线性代数的综合结论。在 AI 中,PCA、协同过滤、LSA、神经网络压缩都根植于 SVD——它是连接线性代数与机器学习的核心桥梁。
至此,第一章线性代数的内容告一段落。从 1.1 节的向量与矩阵基础,到 1.7 节的 SVD 全景图景,我们建立了完整的线性代数工具链:向量空间 → 线性变换 → 秩与方程组 → 四大子空间 → 正交性与投影 → 特征值与特征向量 → 奇异值分解。这套工具链将在后续章节中持续起作用——概率论的协方差矩阵、优化的 Hessian 矩阵、深度学习的权重初始化与压缩,都将依赖本章建立的线性代数基础。
练习题
第 1 题 概念推导
设
参考答案
行空间:
零空间:对
SVD 能同时给出四大子空间的标准正交基,根源在于
第 2 题 代码验证
利用本节的 <SVDCompressionStudio> 交互组件(或对一张
参考答案
Eckart-Young 定理指出,截断 SVD 给出的
四组数据记录如下(以
第 3 题 概念推导
设
参考答案
(
伪逆
对
验证:
常见错误
错误 1 · 用
原因:SVD 的代数构造表明
解决:工程实现 SVD 时直接调用 numpy.linalg.svd 等专用函数(内部用分治或迭代算法,避免显式构造
错误 2 · 把截断 SVD 与完整 SVD 混淆
原因:完整 SVD 给出
解决:明确完整 SVD 用于理论分析(需要四大子空间完整结构),截断 SVD 用于低秩逼近与数据压缩(只需前 np.linalg.svd(A, full_matrices=False) 默认给出瘦 SVD(前
错误 3 · 把伪逆当作普通逆矩阵使用
原因:伪逆
解决:明确伪逆是广义逆,仅在方阵可逆时退化为普通逆。使用前检查矩阵形状与秩:列满秩时
错误 4 · 把奇异值非负误认为矩阵半正定
原因:奇异值
解决:区分奇异值非负(任意矩阵的普适性质)与特征值非负(半正定矩阵的判据,仅对对称矩阵定义)。判定半正定时先检查矩阵对称性,再用特征值符号判定;讨论矩阵的缩放强度或条件数时用奇异值。两者仅在实对称矩阵上有简单关系