# 勤奋的凯尔森同学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告诉你:任何变换,无论多复杂,都能拆成三步——

  1. :旋转一下(正交矩阵只改变方向,不改变长度)
  2. :沿坐标轴拉伸或压缩(对角矩阵,各方向独立)
  3. :再旋转一下

打个比方:你手里有一团橡皮泥,先转个方向,然后沿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)))
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
1
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}")
1
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
1
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天走完了从线性回归、逻辑回归到树模型、集成学习、序列模型、矩阵分解的完整路线。接下来往三个方向继续深入:

  1. 深度学习:PyTorch/TensorFlow,神经网络、CNN、RNN、Transformer,从表格数据转向图像和文本
  2. 大模型:LLM原理、Prompt Engineering、RAG、微调
  3. 工程化: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的真正价值在于让模型自己学特征,这正是深度学习的入场券。接下来就该正式踏入深度学习的世界了。

最近更新: 2026/9/30
 |  Copyright © -Present Kelsen All Rights Reserved. | Author KelsenJing