🏠 总目录📚 本教程 05c · SVD 与实践 ← →
📑 本页目录(点开跳转)

05c · SVD 与 PCA 的实践

⏱ 38 分钟 | ⭐⭐ 推导是对的,但没人照着算


🎯 一句话

上一章证明了「主成分 = 协方差矩阵的特征向量」—— 而没有一个正经实现会照着这句话去算。

真实的 PCA 走的是 SVD:更稳、更省,还能直接处理稀疏矩阵。 这一章讲三件事:SVD 和特征分解是什么关系、主成分该留几个、PCA 什么时候会翻车。

📌 上一章的三个结论,本章会一直用到(忘了也不用回头,用到时都会重述一遍): ① 协方差矩阵 Σ = (1/n)·Xcᵀ Xc(Xc = 中心化后的数据),迹 = 总方差 = 特征值之和 ② Σw = λw —— 主成分只能是 Σ 的特征向量,⭐ λ 就是那个方向上的方差 ③ 手算例子:5 个点 → Σ = [[2, 1.8], [1.8, 2]],λ = 3.8 / 0.2,解释方差比 95% / 5%


🧮 一、和 SVD 的关系:为什么实践中没人真去分解协方差矩阵

上一章那两条推导是对的 —— 但没有一个正经实现会照着算。真实的 PCA 走的是 SVD。

两者的关系:一行代数

对中心化后的数据矩阵 $X_c$(n×d)做奇异值分解:

$$X_c = U S V^\top$$

其中 U(n×k)和 V(d×k)的列都是单位正交向量,S 是对角矩阵,对角元 $s_1\ge s_2\ge\dots\ge 0$ 叫奇异值。代进去:

$$\Sigma = \frac1n X_c^\top X_c = \frac1n (USV^\top)^\top(USV^\top) = \frac1n V S^\top U^\top U S V^\top = V\Big(\frac{S^2}{n}\Big)V^\top$$

(中间用了 $U^\top U = I$。)而特征分解写出来是 $\Sigma = W\Lambda W^\top$。两式一对照:

特征分解的东西 就是 SVD 的什么
主成分方向 W ⭐ 右奇异向量 V
特征值 λᵢ(第 i 个主成分的方差) ⭐ $s_i^2 / n$
主成分得分 Z = Xc·W ⭐ $U S$(连乘法都省了)

用上一章那个手算例子对一下(5 个点,Σ=[[2, 1.8], [1.8, 2]],λ=3.8 / 0.2):$s = [4.359,\ 1.0]$, 则 $s_1^2/n = 19/5 = 3.8$、$s_2^2/n = 1/5 = 0.2$ —— 和特征分解一模一样。

⭐ 四个理由:为什么必须走 SVD

理由 ① 数值稳定(最硬的一条)

算 $X_c^\top X_c$ 这一步会把条件数平方:$\kappa(X^\top X) = \kappa(X)^2$。 条件数平方,意味着你的有效精度直接砍掉一半。

import numpy as np

rng = np.random.RandomState(0)
Q, _ = np.linalg.qr(rng.randn(200, 5))
V, _ = np.linalg.qr(rng.randn(5, 5))
X = Q @ np.diag([1.0, 1e-2, 1e-4, 1e-6, 1e-8]) @ V.T   # ⭐ 人造一个病态数据
Xc = X - X.mean(axis=0)

print("cond(X)      = %.2e" % np.linalg.cond(Xc))          # 9.96e+07
print("cond(X.T@X)  = %.2e" % np.linalg.cond(Xc.T @ Xc))   # 6.79e+15  ⭐ 平方了

lam_eig = np.linalg.eigvalsh(Xc.T @ Xc / len(X))[::-1]     # 走协方差特征分解
lam_svd = np.linalg.svd(Xc, compute_uv=False) ** 2 / len(X)  # ⭐ 走 SVD
print("最小主成分方差 · 特征分解 =", lam_eig[-1])           # 2.82e-19
print("最小主成分方差 · SVD      =", lam_svd[-1])           # 4.99e-19  ← 这个才对

真值是 $(10^{-8})^2/200 = 5\times10^{-19}$。 SVD 给出 4.99e−19(对),协方差特征分解给出 2.82e−19 —— 错了 44%。 病态再严重一点,特征分解甚至会吐出负的方差(数学上不可能,是浮点误差)。

理由 ② 不用把 d×d 那个矩阵造出来

算一算

d = 50,000 个特征(文本 TF-IDF 很常见)

→ Σ 是 50000 × 50000 = 25 亿个数,float64 下 20 GB,存都存不下

→ 而 SVD 直接吃 Xc,从来不构造 Σ

理由 ③ n < d 时协方差矩阵一定是奇异的

100 个样本、5000 个特征(组学数据、小样本图像的典型形态):$\Sigma$ 的秩最多是 99, 剩下 4901 个特征值理论上是 0、实际算出来是一堆 ±1e−15 的噪声。 SVD 只会给出 99 个非零奇异值,干干净净。

理由 ④ 只想要前 k 个的时候,有专门的算法

方法 干什么 / 什么时候用
截断 SVD 只求前 k 个奇异值与向量,k 远小于 d 时用
随机化 SVD 先随机投影再分解,约 O(n·d·k)。⭐ sklearn 在 d 大时的默认(svd_solver="randomized")
TruncatedSVD 不做中心化,所以能直接吃稀疏矩阵。⭐ 文本 TF-IDF 必用(中心化会把稀疏矩阵变稠密,直接爆内存)—— 这就是 LSA

🔑 所以 sklearn.decomposition.PCA 内部就是 SVD,svd_solver 参数在选具体走哪种。 你以为你在做"特征分解",实际上一次都没做过。

⚠️ 两个会让你对不上数的小坑

坑 说明
主成分的符号是任意的 ⭐ w 和 −w 都满足 $\Sigma w=\lambda w$,都合法。不同库、不同版本都可能给出相反的符号 —— 别把符号当成稳定语义("PC1 高 = 更富有"换台机器可能就反了)
除以 n 还是 n−1 手算除以 n;sklearn 的 explained_variance_ 除以 n−1。绝对值差 n/(n−1) 倍,但 explained_variance_ratio_ 完全一样

🔍 二、主成分留几个

主力工具:解释方差比

$$\text{第 }i\text{ 个主成分的解释方差比} = \frac{\lambda_i}{\sum_j \lambda_j} = \frac{\lambda_i}{\mathrm{tr}(\Sigma)}$$

分母就是上一章那条「迹 = 总方差 = 特征值之和」。上一章的手算例子里是 95% / 5%。

五种选法,按靠谱程度排

方法 怎么做 评价
累计阈值 取到累计解释方差 ≥ 90%(或 95%、80%)为止 最常用,但 90% 这个数字是拍脑袋的,没有理论依据
肘部法(碎石图) 画 λ 随 i 下降的曲线,找"拐弯"处 直观,但很多真实数据根本没有明显的肘,全靠人眼
Kaiser 准则 留 λ > 1 的 ⚠️ 只在用相关矩阵(即先标准化)时有意义——那时平均特征值恰好是 1。用协方差矩阵时这条毫无道理
平行分析 同形状的随机数据也跑一遍 PCA,只留 λ 超过随机基线的 比 Kaiser 严谨,但要多跑几十次
⭐ 下游任务交叉验证 把 k 当成一个普通超参数,在验证集上按最终指标选 最靠谱的一条。 你降维不是为了降维,是为了下游模型 —— 那就让下游指标说话

🔑 实践建议:先画一次累计解释方差曲线建立量级感(5 个成分就够,还是要 200 个?), 再在那个量级附近拿 3~4 个候选值做交叉验证。别在"90% 还是 95%"上纠结。


⚠️ 三、PCA 的前提与失效场景

六条,前三条是真会翻车的。

① 它只能找线性子空间

PCA 找的是一个平的子空间。数据要是弯的,它就废了。

信息关系

经典反例:瑞士卷(Swiss roll)
数据本质上是一张【卷起来的二维纸】,内在维度就是 2
但 PCA 只能拿平面去切它→两端卷在一起的点被投到同一个位置

🔗 出路是非线性降维:核 PCA(第 6 章 · 核技巧那套把戏原样搬过来, 把内积换成核函数就行)、t-SNE / UMAP(基础教程第 6 章, 只能用来看一眼,不能拿去当特征)、自编码器。

② ⭐ 必须先标准化(除非所有特征本来就同量纲)

⭐ 一句话结论:同一批数据,把收入的单位从"元"换成"万元",第一主成分就从收入变成了年龄。

关键信息

③ 💀 方差 ≠ 信息(最容易忽略的一条)

PCA 是无监督的,它压根不看 y。

操作步骤

💀 一个真实会发生的事故形态:
二分类任务,特征里有一个方差极小、但和标签几乎完美相关的变量
(比如某个传感器的微小读数偏移,量级 0.001,却正好区分两类)
PCA 保留 95% 方差→这个方向排在最后,被当成噪声丢掉
降维后模型 AUC 从 0.94 掉到 0.71
排查时看不出来:解释方差比 95%,"信息只丢了 5%",报表上一切正常 ⚠️
⭐ 症结:那被丢掉的 5% 方差里,装着 100% 的判别信息

🔑 该补什么:① 降维前后都在验证集上跑一次下游指标,别只看解释方差比; ② 需要"对分类有用的方向"就用 LDA(有监督,最大化类间/类内方差比), 而不是 PCA(基础教程第 6 章有两者的对照)。

④ 对离群点敏感

方差是平方量,一个离得很远的点对协方差矩阵的贡献是平方级的, 足以把 PC1 整个拽过去。这和第 1 章里「一个离群点拽歪最小二乘」是同一个机理 —— 它们背后都是高斯假设那条薄尾巴。缓解:先做稳健的离群点处理,或用 Robust PCA。

⑤ 主成分不可解释

PC1 是「0.31×年龄 − 0.52×收入 + 0.44×点击数 + …」这种所有原始特征的线性组合。 要向业务解释"哪个特征重要"时 PCA 帮不上忙 —— 那种场景该做特征选择而不是特征压缩 (第 5 章出路②:「大多数维度是噪声,删掉它们比压缩更好」)。

⑥ 💀 泄漏:PCA 必须 fit 在训练集上

结果对照

❌ pca.fit_transform(全部数据) 再切分训练/测试
测试集参与了均值和主成分方向的计算→离线分数虚高,上线暴跌
✅ pca.fit(X_train)→分别 transform;最省事是塞进 Pipeline,
让交叉验证自动只在折内 fit

🔗 基础教程第 6 章的原话就是 「PCA 是最容易泄漏的预处理之一」;五类泄漏的完整清单在基础教程第 5 章。

💡 顺带:白化(whitening)

把每个主成分得分再除以 $\sqrt{\lambda_i}$,让所有方向的方差都变成 1。

信息关系

PCA→去掉相关性(各主成分之间协方差为 0)
PCA + 白化→去掉相关性【并且】拉平尺度
⚠️ 代价:最小的那几个主成分被放大了 1/√λ 倍
—— 而它们往往正是噪声。白化会把噪声一起放大 ⭐

🔗 这一章连到哪里

去哪 为什么
05b · PCA 的推导 本章一直在用的 Σ = (1/n)XcᵀXc、Σw = λw、λ = 那个方向上的方差,全都是在那里推出来的
ML基础 06 · 无监督:聚类与降维 ⭐ 对面是工程视角:TruncatedSVD 什么时候用、PCA 与 LDA 怎么选、泄漏怎么防。本章第三节那六条失效场景,正是那些告诫的来历
第 6 章 · 核技巧 第三节 ① 说 PCA 只能找线性子空间;核 PCA 就是把推导里的内积换成核函数,一步升级成非线性降维
第 1 章 · 最大似然 ⭐ 第三节 ④「PCA 对离群点敏感」和那一章「一个离群点拽歪最小二乘」是同一条高斯薄尾巴
ML基础 05 · 评估与过拟合 · 数据这一关 14 第三节 ⑥ 那条泄漏事故:前者给五类泄漏的完整清单,后者给实测数字(预处理先于切分,0.984 → 0.849)
推荐算法 05 · 矩阵分解 ⭐ 同一套 SVD 换个场景:那里分解的是用户 × 物品交互矩阵,拿到的"隐因子"就是这里的主成分
AI基础设施 18c · 剪枝与稀疏化 低秩近似的工程版:把权重矩阵做截断 SVD 来压模型,靠的正是「前 k 个奇异值装了大部分能量」这条

✅ 检查点

  1. SVD 的 V、S 分别对应特征分解里的什么?主成分得分怎么直接从 SVD 里拿?
  2. 为什么实践中不直接对协方差矩阵做特征分解?(说出至少三个理由)
  3. 主成分留几个,最靠谱的选法是哪个?为什么不是"累计 95%"?
  4. 有一个方差极小、但和标签几乎完美相关的特征,PCA 会怎么对待它?该怎么避免?
  5. 什么时候必须标准化,什么时候反而不该?
  6. 白化在做什么?代价是什么?
👀 答案
  1. V 的列 = 主成分方向 W;λᵢ = sᵢ²/n;得分 Z = Xc·W = U·S(连那次矩阵乘法都省了)。用上一章的例子验证:s = [4.359, 1.0] → 19/5 = 3.8、1/5 = 0.2 ✅
  2. ①数值:算 XᵀX 会把条件数平方(κ(XᵀX)=κ(X)²),有效精度砍半——实测最小主成分方差,特征分解算成 2.82e−19、SVD 给 4.99e−19,真值 5e−19,特征分解错 44%;更病态时甚至会吐出负方差。②不用构造 d×d(d=50000 → 25 亿个数、20 GB)。③n < d 时 Σ 必然奇异,多出来的特征值全是浮点噪声。④只要前 k 个时有截断 / 随机化 SVD,且 TruncatedSVD 不中心化,能直接吃稀疏矩阵。sklearn 的 PCA 内部就是 SVD。
  3. 把 k 当成普通超参数,用下游任务在验证集上的指标来选。"95%"那个数字是拍脑袋的,没有理论依据;而且方差多少和下游有没有用是两回事(见下一题)。
  4. PCA 会把它排在最后、当噪声丢掉——因为 PCA 无监督,压根不看 y。事故形态:保留 95% 方差,AUC 从 0.94 掉到 0.71,而报表上"只丢了 5% 信息"完全看不出问题——那 5% 方差里装着 100% 的判别信息。避免:①降维前后都跑一次下游指标 ②要判别方向就用 LDA(有监督)。
  5. 方差不可比时必须标准化:收入(元)+年龄(岁) 方差差 2500 万倍,第一主成分基本就是收入;把收入换成万元,第一主成分立刻变成年龄——同一批数据换个单位答案就反了。本来同量纲时不该标准化:同图的 784 个像素都是 0~255,此时方差差异是真信息(边缘像素方差小,本来就该被降权)。
  6. 把每个主成分得分再除以 √λᵢ,让所有方向的方差都变成 1 —— PCA 去掉相关性,白化在此之上再拉平尺度。⚠️ 代价:最小的那几个主成分被放大了 1/√λ 倍,而它们往往正是噪声。

🛑 可以停在这里

⚡ 走神救援

⭐ PCA 和 SVD 的关系:对中心化后的数据做 SVD,右奇异向量就是主成分方向、奇异值的平方按样本数缩一下就是特征值、左边乘上奇异值就是得分——两条路给出同一件东西。

⭐⭐ 为什么实践里全走 SVD,四条理由里第一条最硬:先算协方差矩阵会把条件数平方,有效精度直接砍半——实测最小那个主成分的方差,特征分解错了将近一半,而 SVD 基本准确。另外三条:不用构造那个高维方阵(维度一大就是几十 GB)、样本数小于维度时协方差必然奇异、以及截断和随机化版本只求前几个、还能吃稀疏矩阵。

⚠️ 两个小坑:主成分的符号是任意的(别把符号当语义);除以样本数还是自由度只影响绝对值、不影响比值。

⭐ k 怎么选:把它当超参数,让下游指标说话——⚠️ 「保留 95% 方差」没有理论依据。

六条失效场景里最该记的三条:⭐ 必须标准化,判据是「方差可不可比」——单位一换,第一主成分就换了人;而同一张图的像素之间方差本来就可比,那就不该标准化。 💀 ⭐ 方差不等于信息:PCA 根本不看标签,一个方差极小却和标签几乎完美相关的特征会被当噪声丢掉——⚠️ 而报表上只写着「丢了 5% 的方差」,完全看不出来指标已经塌了。要用标签就该换有监督的方法。 💀 必须只在训练集上拟合:在全量数据上拟合再切分就是泄漏。

💡 白化是再按尺度拉平,⚠️ 代价是把最小的那几个主成分(往往正是噪声)成倍放大。

下一节 👉 06-核技巧.md

去那里的理由:本章第三节 ① 说 PCA 只能找平的子空间,数据要是弯的它就废了。 第 6 章那套「不去高维空间、却拿到在那里计算的结果」的把戏, 原样套到 PCA 的推导上就是核 PCA —— 之所以能这么套,是因为那两条推导全程只用到内积。

打卡记录保存在你的浏览器里,首页能看到总进度