# 勤奋的凯尔森同学10000h刻意练习Day15:SVD与矩阵分解 —— 从几何直觉到推荐系统的数学引擎
所属专栏: 凯尔森的10000h刻意练习
Slogan: 一个36岁程序员, 研究生(学硕),前世界500强top20软件开发Java工程师,用10000小时深耕AI, 目标Top10 AI资深工程师, 全程日更记录。
一张1000×1000的图,凭什么能压缩到原来1/10还不失真?Netflix凭什么靠一个算法预测你没看过的电影评分?
Day10的PCA用协方差矩阵的特征值分解找主成分。今天讲它的"升级版"——奇异值分解(SVD)。PCA只能处理方阵(协方差矩阵),SVD能分解任意矩阵,适用范围更广。更重要的是,SVD是推荐系统的数学引擎:把一个巨大的"用户-物品"评分矩阵分解成低秩的"用户偏好"和"物品特征"矩阵,就能预测用户没看过的物品评分——Netflix百万美金大赛的核心思路。
SVD和PCA的关系是"一个硬币的两面":对中心化数据矩阵做SVD,等价于做PCA。但SVD的几何直觉更直接——任何矩阵变换都是"旋转→缩放→旋转"三步。理解SVD,就理解了线性代数在机器学习中最实用的部分。
# 1.1 SVD是什么:一个公式,三步变换
# 1.1.1 核心公式
任意矩阵都可以分解为:
不用记字母,只要记住三步:先旋转(),再缩放(),再旋转()。其中:
- 和是正交矩阵(只旋转,不改变长度)
- 是对角矩阵(沿各坐标轴独立缩放)
- 对角线上的叫奇异值,是各方向的"拉伸强度"
关键性质:奇异值按降序排列,前几个通常远大于后面的——这就是后面"截断"能成立的根本原因。
# 1.1.2 几何直觉:旋转→缩放→旋转
把矩阵理解为一个线性变换(把一个向量变成另一个向量),SVD告诉你:任何变换,无论多复杂,都能拆成三步——
- :旋转一下(正交矩阵只改变方向,不改变长度)
- :沿坐标轴拉伸或压缩(对角矩阵,各方向独立)
- :再旋转一下
打个比方:你手里有一团橡皮泥,先转个方向,然后沿x/y轴拉伸成椭圆,再转一下摆正——任何线性变换都是这三步的组合。奇异值就是各方向的缩放因子:大奇异值方向"拉伸得多"(数据的主要结构),小奇异值方向"压缩得多"(接近噪声)。
# 1.1.3 与特征值分解的关系
特征值分解只对方阵有效,且不一定正交。SVD对任意矩阵有效,且都是正交矩阵。
两者怎么联系?如果对算(这就变成方阵了),再做特征分解,它的特征向量恰好就是的右奇异向量,特征值恰好是奇异值的平方。为什么?因为,所以:
中间(正交矩阵的性质),所以直接化简。这条关系是SVD和PCA等价性的桥梁:PCA的协方差矩阵,其特征分解就是SVD的副产品。
# 1.2 截断SVD:丢掉小奇异值就是最优压缩
# 1.2.1 为什么要截断
奇异值是按降序排的,前几个大、后面小。如果只保留前个最大的奇异值,把其余置零,就得到截断SVD:
是秩矩阵,只用个奇异值就近似了原矩阵。直觉上:大奇异值方向是数据的主要结构(信号),小奇异值方向是噪声——保留大的、丢掉小的,就是去噪和压缩。
# 1.2.2 Eckart-Young定理:截断就是最优的
截断SVD不是随便近似,而是理论最优的低秩近似。Eckart-Young定理说:
即近似误差(在Frobenius范数下)恰好等于丢掉的奇异值的平方和开根。而且任何秩矩阵的近似误差——没有任何秩矩阵能比截断SVD更好。这就是为什么SVD是压缩和降维的黄金标准。
# 1.2.3 代码验证:分解、截断、误差一把跑
下面用4×2矩阵跑一遍完整流程:SVD分解→截断→验证误差恰好等于→和PCA对比确认等价。
import numpy as np
# === SVD分解与截断 ===
A = np.array([[1, 2],
[3, 4],
[5, 6],
[7, 8]])
# full_matrices=False,得到的是经济型SVD/reduced SVD
# U:4×2
# Σ:2×2
# V^T:2×2
U, S, Vt = np.linalg.svd(A, full_matrices=False)
print("奇异值:", np.round(S, 4))
# 完整重建(验证分解正确)
# np.diag(S)就是把[S1, S2]变成Σ
A_reconstructed = U @ np.diag(S) @ Vt
# 计算max∣A−A_reconstructed∣
print("重建误差:", np.round(np.max(np.abs(A - A_reconstructed)), 10))
# 截断 k=1
# U[:, :1]取 U 的所有行,但是只取第 1 列。
# S[:1]只取第一个奇异值[σ1]
# Vt[:1, :]取 (V^T) 的第一行。
A_k1 = U[:, :1] @ np.diag(S[:1]) @ Vt[:1, :]
print("\n截断 k=1 近似:")
print("原始:", A.tolist())
print("近似:", np.round(A_k1, 2).tolist())
frob_error = np.sqrt(np.sum((A - A_k1)**2))
print(f"Frobenius误差: {frob_error:.4f}")
print(f"理论值 sigma_2: {S[1]:.4f}")
print(f"误差 == sigma_2? {np.isclose(frob_error, S[1])}")
# === 和PCA对比:确认等价 ===
X = np.array([[1, 2], [2, 1], [3, 4], [4, 3], [5, 6]], dtype=float)
X_c = X - X.mean(axis=0)
# 计算协方差矩阵
C = np.cov(X_c, rowvar=False)
# 得到协方差矩阵(C)的特征值
eigvals, _ = np.linalg.eigh(C)
# 把特征值从大到小排序
idx = np.argsort(eigvals)[::-1]
U2, S2, _ = np.linalg.svd(X_c, full_matrices=False)
print("\nPCA 特征值:", np.round(eigvals[idx], 4))
print("SVD S^2/(m-1):", np.round(S2**2 / (X.shape[0]-1), 4))
print("两者匹配:", np.allclose(eigvals[idx], S2**2 / (X.shape[0]-1)))
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
奇异值: [14.2691 0.6268]
重建误差: 0.0
截断 k=1 近似:
原始: [[1, 2], [3, 4], [5, 6], [7, 8]]
近似: [[1.4, 1.67], [3.2, 3.83], [5.01, 5.99], [6.82, 8.15]]
Frobenius误差: 0.6268
理论值 sigma_2: 0.6268
误差 == sigma_2? True
PCA 特征值: [5.671 0.529]
SVD S^2/(m-1): [5.671 0.529]
两者匹配: True
2
3
4
5
6
7
8
9
10
11
12
13
三个关键结论一眼可见:① 奇异值14.27远大于0.63(第一个方向占主导);② 截断的误差恰好等于(Eckart-Young定理验证);③ SVD算出的和PCA的特征值完全一致(等价性验证)。
# 1.2.4 "能量"与去噪
矩阵的Frobenius范数,即所有奇异值平方和。截断后保留的"能量比":
比如奇异值,保留时能量保留率——丢了98%的数据维度,只损失0.2%的能量。截断小奇异值 = 去噪,这正是PCA保留大方差方向的原理——PCA和截断SVD的去噪逻辑完全一致。
# 1.3 SVD与PCA:一个硬币的两面
# 1.3.1 对比表
| PCA | SVD | |
|---|---|---|
| 分解对象 | 协方差矩阵 | 数据矩阵本身 |
| 分解方式 | (特征分解) | |
| 主成分方向 | 的列向量 | 的列向量(完全相同) |
| 方差(特征值) | ||
| 投影 |
对中心化数据做SVD,等价于做PCA;两者的完全一样,方差。
# 1.3.2 为什么实践中用SVD而非特征分解
- 数值稳定性:SVD直接分解数据矩阵,不需要先算(计算会丢失小特征值的精度)
- 适用范围:SVD对任意矩阵有效,特征分解只对方阵有效
- 计算效率:sklearn的PCA底层就是用SVD(
svd_solver='full'),而不是特征分解
Day10讲PCA降维时说"找方差最大方向",今天从SVD视角看就是"保留大奇异值方向"——同一个操作,两种表述。
# 1.4 推荐系统:矩阵分解
# 1.4.1 问题:评分矩阵
推荐系统的核心数据是用户-物品评分矩阵(用户 × 物品),其中大量位置为0(未评分)。目标:预测这些未评分位置的值,据此推荐。
# 1.4.2 矩阵分解的直觉
先想一个问题:你为什么喜欢某部电影?你记住的不是"电影编号7231",而是它喜剧成分足、动作场面多——口味由少数几个维度决定。矩阵分解就建立在这个假设上:每个用户、每件物品都能用个"潜在因子"描述。
拿(喜剧、动作)举例:
- 用户1爱喜剧、不爱动作,偏好向量(第1维是喜剧偏好,第2维是动作偏好)
- 电影A是纯喜剧,属性向量;电影B是纯动作,
预测评分 = 两个向量的内积(逐维相乘再求和),数值大小刚好落在1~5的评分范围:
内积的直觉是匹配度:用户口味和电影属性在每个维度上对齐得越好,评分越高。用户1爱喜剧,给喜剧片A打4.25分、动作片B打2分——合理。
把所有用户的向量按行堆成矩阵(),所有物品的向量按行堆成(),全部预测一次算出:。这就是低秩矩阵分解:表面看有个格子,实际由和共个参数决定——每个用户、每件物品都被压缩进同一个维空间,通常取10~100,远小于和。
这些因子维度不需要人工指定含义("喜剧""动作"只是方便理解的说法),算法从评分数据里自动学出的维度可能对应任何叫不出名字的模式——只要它能解释评分。学出和后,里的空格直接用填充,这就是推荐的来源。
# 1.4.3 直接SVD与梯度下降
直接SVD:对做SVD,取前个奇异值,就能填充未评分位置:
R = np.array([
[5, 3, 0, 1, 0],
[4, 0, 0, 1, 0],
[1, 1, 0, 5, 4],
[0, 1, 0, 4, 5],
], dtype=float)
U, S, Vt = np.linalg.svd(R, full_matrices=False)
k = 2
R_approx = U[:, :k] @ np.diag(S[:k]) @ Vt[:k, :]
print("秩2近似:")
print(np.round(R_approx, 2))
print("\n预测未评分位置:")
for i in range(R.shape[0]):
for j in range(R.shape[1]):
if R[i, j] == 0:
print(f" 用户{i+1} -> 物品{j+1}: {R_approx[i,j]:.2f}")
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
秩2近似:
[[ 5.31 2.06 0. 1.23 -0.05]
[ 3.52 1.36 0. 0.8 -0.05]
[ 1.04 1.2 0. 4.53 4.42]
[-0.04 0.81 0. 4.45 4.6 ]]
预测未评分位置:
用户1 -> 物品3: 0.00
用户1 -> 物品5: -0.05
用户2 -> 物品2: 1.36
用户2 -> 物品3: 0.00
用户2 -> 物品5: -0.05
用户3 -> 物品3: 0.00
用户4 -> 物品1: -0.04
用户4 -> 物品3: 0.00
2
3
4
5
6
7
8
9
10
11
12
13
14
15
但直接SVD有两个问题:① 把未评分位置(0)当成"真的打了0分",引入偏差;② 预测值可能出现负数(评分应该>0)。
实际推荐系统改用梯度下降学习和:只在有评分的位置算损失,加上L2正则防止过拟合,迭代优化。这样未评分位置不参与训练,避免了直接SVD的偏差——这就是Netflix大赛的核心算法,也是今天推荐系统矩阵分解方法的鼻祖。
# 1.5 SVD的工程应用
# 1.5.1 数据压缩
图像是矩阵,做SVD后丢弃小奇异值就能压缩。一张的图(100万个数),用的截断SVD只需存()+(个)+()共约10万个数,压缩90%——而且Eckart-Young定理保证这是最优的秩50近似。
# 1.5.2 自然语言处理:LSA
把"文档-词"矩阵做SVD,保留前个奇异值,就是潜在语义分析(LSA)。它把"文档"和"词"映射到同一个低维"潜在语义空间",能发现同义词(在不同文档中共现的词会落在相近方向)。这是Day7(感知机/最大熵)、Day14(HMM与CRF)之外的另一条NLP路线。
# 1.6 传统机器学习总结
走完Day6~Day15,我们用10天走过了从线性回归到SVD的完整传统机器学习路线:线性回归(Day6)、逻辑回归含感知机与最大熵(Day7)、决策树与随机森林(Day8)、SVM(Day9)、无监督学习(Day10)、朴素贝叶斯与KNN(Day11)、XGBoost/LightGBM(Day12)、模型评估与端到端项目(Day13)、HMM与CRF(Day14)、SVD与矩阵分解(Day15)。这一节把这些模型的画像一次性拉齐,再附上踩过的坑和后续学习路径。
# 1.6.1 监督学习模型对比
下表聚焦监督学习模型(Day6~Day13),HMM与CRF(Day14)、SVD(Day15)属于序列与矩阵分解的独立脉络,不在表中(感知机与最大熵已并入Day7逻辑回归):
| 模型 | 适用场景 | 优点 | 缺点 | 训练速度 | 是否需缩放 |
|---|---|---|---|---|---|
| 线性回归 | 回归(连续值) | 简单、可解释、快 | 只能拟合线性关系、对异常值敏感 | 快 | 是 |
| 逻辑回归 | 二分类/多分类 | 输出概率、可解释、快 | 线性决策边界 | 快 | 是 |
| KNN | 分类/回归 | 简单、无需训练、直观 | 预测慢、对维度和缩放敏感、要存全量数据 | 训练快,预测慢 | 是 |
| 决策树 | 分类/回归 | 可解释、无需缩放、能处理类别特征 | 容易过拟合、不稳定、对小变动敏感 | 快 | 否 |
| 随机森林 | 分类/回归 | 精度高、抗过拟合、能输出特征重要性 | 模型大、解释性差 | 中(可并行) | 否 |
| GBDT | 分类/回归 | 精度高、能捕捉非线性 | 串行训练慢、调参复杂、易过拟合 | 慢 | 否 |
| XGBoost | 分类/回归 | 高效、正则化、精度高、工业级 | 参数多、需调参 | 中 | 否 |
| SVM | 分类/回归(小数据) | 核技巧处理非线性、小数据表现好 | 大数据训练慢、调参难、对缺失值敏感 | 慢 | 是 |
| 朴素贝叶斯 | 分类(文本/高维稀疏) | 训练快、适合高维稀疏数据、少参数 | 特征独立假设强、精度一般 | 快 | 否(文本常做归一化) |
怎么按问题选模型:
- 按数据规模:小样本(<1k)优先SVM、决策树、KNN;中等规模(1k~10w)随机森林、GBDT、XGBoost、CatBoost;大样本(>10w)优先LightGBM、线性模型、随机森林(需调参)
- 按任务类型:分类首选逻辑回归当基线,再上随机森林、XGBoost;回归首选线性回归当基线,再上Ridge/Lasso、梯度提升回归
- 按可解释性:要求高选线性模型、决策树、KNN;不要求选集成模型、神经网络
- 按训练时间:有限选线性模型、朴素贝叶斯;充裕可尝试复杂集成
# 1.6.2 常见坑与经验
| 坑 | 症状 | 解决 |
|---|---|---|
| 数据泄露 | 测试集表现好,上线崩 | 缩放/编码只fit训练集;时间序列不能随机切分 |
| 类别不平衡 | 准确率高但召回率低 | class_weight / SMOTE / 阈值调整 |
| 特征量纲不一 | 基于距离的模型效果差 | StandardScaler |
| 过拟合 | 训练好测试差 | 正则化、加数据、早停、集成 |
| 欠拟合 | 训练测试都差 | 更复杂模型、加特征、减正则 |
| 调参只在测试集 | 性能虚高 | 用验证集调参,测试集只看一次 |
# 1.6.3 后续学习路径
机器学习基础篇(Day6~Day15)至此收官——10天走完了从线性回归、逻辑回归到树模型、集成学习、序列模型、矩阵分解的完整路线。接下来往三个方向继续深入:
- 深度学习:PyTorch/TensorFlow,神经网络、CNN、RNN、Transformer,从表格数据转向图像和文本
- 大模型:LLM原理、Prompt Engineering、RAG、微调
- 工程化:MLflow实验跟踪、Docker部署、Kubeflow流水线、模型监控
这三个方向正好对应项目里的"AI大模型开发(Python)"和"AI智能应用开发(Java)"专栏——底子已经打牢,后面的路会越走越稳。
# 小结
| 概念 | 公式 | 直觉 |
|---|---|---|
| SVD | 旋转→缩放→旋转 | |
| 奇异值 | 各方向的"拉伸强度" | |
| 截断SVD | 最优低秩近似 | |
| Eckart-Young | 截断误差 = 丢掉奇异值平方和开根 | |
| PCA = SVD | 协方差分解 = 数据分解的副产品 | |
| 推荐系统 | 低秩矩阵分解预测评分 |
SVD是线性代数在机器学习中最美的应用之一。它把"任意矩阵"统一分解为旋转+缩放,给出最优低秩近似的理论保证,还是PCA、LSA、推荐系统的共同引擎。Day10讲PCA降维时说"找方差最大方向",今天从SVD视角看就是"保留大奇异值方向"——同一个操作,两种表述。配合1.6节的模型对比表、常见坑和后续学习路径,传统机器学习的工具箱和路线图就此完整。
# 从感知机到MLP:传统ML到深度学习的衔接
回到Day7开篇那个最朴素的线性分类器——感知机,它是整个传统机器学习到深度学习的起点。1969年Minsky在《Perceptrons》里证明单层感知机连XOR都分不开(两条直线切不开异或的四点),这直接导致了AI第一次寒冬。出路在同一年就被提出:再叠一层隐藏层,让先把经过一层变换出新特征,再在上面做线性分类——这就是多层感知机(MLP):
把感知机的sign换成sigmoid、把多层堆叠起来、用反向传播训练——就是今天神经网络的基本结构。回头看这10天的路线:感知机(误分类驱动)→逻辑回归(概率输出+交叉熵)→最大熵(信息论解释),三条线在Day7汇合成同一个模型;决策树/集成学习给出非线性;SVM给最大间隔;HMM/CRF处理序列;SVD处理矩阵分解。这些工具各自解决了"线性可分""非线性""序列""矩阵"四类问题,但都受限于"人工设计特征"——MLP的真正价值在于让模型自己学特征,这正是深度学习的入场券。接下来就该正式踏入深度学习的世界了。