本文是《LLM 时代的经典机器学习:只讲它在哪里重现》系列的第 8 篇(共十一篇)。上一篇:聚类——K-Means、DBSCAN 与「这批语料里有什么」;下一篇:去重——MinHash 与 LSH 的概率。
上一篇的句向量有 896 维,一个 LLM 的 hidden state 有 4096 维。人看不了 896 维的东西,但可以问两个问题:这些维度里真正有信息的有几个?能不能把它们画在纸上?降维回答这两个问题。主角是 PCA(主成分分析)——找数据方差最大的几个方向,把数据投影上去。它简单到四行代码,但连着三件后面反复出现的事:它就是 L0 第三篇的 SVD;它给出的”有效维度”是 LoRA 低秩假设的直觉来源;用它看一眼 embedding,会发现一个让”余弦相似度 0.8”失去含义的陷阱。
全篇的核心问题是:
PCA 的”方差最大的方向”到底在找什么,为什么它等于 SVD?1 4096 维的 embedding 怎么”看”,二维图上的距离能信吗?2 为什么一个 embedding 模型算出来任意两句话的余弦相似度都在 0.7 以上?3
一、总览
1. 从一个方向到一张图
本文按”PCA 是什么 → 它给出什么数字 → 怎么画图 → 在真实 embedding 上看到什么”组织:
| 概念 | 一句话 | 本文里的数字 |
|---|---|---|
| PCA 的几何 | 找投影后方差最大的方向 | 二维例子:PC1 方向方差 3.44,随手挑的方向 1.34 |
| 手写 PCA | 中心化 → SVD → 取前 \(k\) 行 | 与 scikit-learn 差 \(3 \times 10^{-13}\) |
| 解释方差 | 前 \(k\) 个主成分占总方差的比例 = 数据的有效维度 | 手写数字 64 维里 29 维解释 95% |
| 重建 | 均值 + 前 \(k\) 个坐标 × 方向 | \(k = 30\) 时重建误差 0.77,肉眼看不出差别 |
| PCA = SVD = 协方差特征分解 | 三种算法同一个答案 | 前 3 个特征值 179.0 / 163.7 / 141.8 三种算法全同 |
| t-SNE / UMAP | 非线性、只保局部、不可逆 | 二维图上 KNN 准确率:PCA 0.60,t-SNE 0.98 |
| 各向异性 | 所有向量挤在一个窄锥里 | 78 句真实句向量:任意两句余弦均值 0.79,减均值后 −0.01 |
2. 本文的章节安排
| 章 | 主题 | 内容 |
|---|---|---|
| 二 | PCA 的几何 | 投影是什么(一个点手算);四个点沿三个方向的方差;二维例子:哪个方向方差最大 |
| 三 | 手写 PCA | 四行 SVD;手写数字 64 维的解释方差曲线;用前 \(k\) 维重建图像 |
| 四 | 三个名字一件事 | 协方差矩阵特征分解 = 数据矩阵 SVD = PCA;数字对上 |
| 五 | 有效维度与低秩 | 29 维解释 95% 意味着什么;一个真实权重矩阵的奇异值谱;LoRA |
| 六 | 画在二维上 | PCA vs t-SNE;二维图能信什么、不能信什么 |
| 七 | embedding 的各向异性 | 真实句向量的余弦分布;减均值前后;检索例子 |
| 八 | 本文小结 | |
| 九 | 自测 | 六道题 |
二、PCA 的几何
1. 哪个方向方差最大
200 个二维的点,一团拉长、倾斜的云。想把它压成一维(每个点只留一个数),该往哪个方向投影?
两个主成分方向 [-0.825 -0.565], [ 0.565 -0.825](互相垂直)
解释方差比 [0.907 0.093]:第一主成分占 90.7%
投影到 PC1(方差最大的方向):投影后的方差 3.442
投影到随手挑的方向(竖直):投影后的方差 1.340
投影:把每个点沿垂直方向”压”到一条直线上,只保留它在这条线上的坐标。算一个:直线方向用一个长度为 1 的向量 \(u\) 表示,点 \(x\) 在这条线上的坐标就是点积 \(x^T u\)。取 \(u = (1, 1) / \sqrt 2 = (0.707, 0.707)\)(45° 方向),点 \(x = (3, 1)\) 的坐标是 \(3 \times 0.707 + 1 \times 0.707 = 2.83\);把这个坐标乘回方向 \(2.83 \cdot u = (2, 2)\) 就是”压到直线上之后的点”,它与原点 \((3, 1)\) 的差 \((1, -1)\) 垂直于直线,是被丢掉的部分。
压到不同的直线上,点散开的程度(方差)不同。四个已中心化的点 \((2, 2), (-2, -2), (1, -1), (-1, 1)\),试三个方向:
| 方向 \(u\) | 四个点的投影坐标 | 坐标的方差(平方的平均) |
|---|---|---|
| \((1, 1)/\sqrt 2\) | 2.83, −2.83, 0, 0 | 4.0 |
| \((1, 0)\)(横轴) | 2, −2, 1, −1 | 2.5 |
| \((1, -1)/\sqrt 2\) | 0, 0, 1.41, −1.41 | 1.0 |
沿 45° 方向投影,四个点散得最开(两个点在远端、两个点重合在原点);沿 −45° 方向,前两个点都压成了 0,散得最窄。PCA 找的是投影后方差最大的方向——方差最大意味着点之间的差别保留得最多、丢掉的信息最少。这个方向叫第一主成分(PC1)。第二主成分在与 PC1 垂直的方向里再找方差最大的,依此类推,\(d\) 维数据有 \(d\) 个互相垂直的主成分。四个点的例子里 PC1 就是 \((1, 1)/\sqrt 2\)、方差 4,PC2 是与它垂直的 \((1, -1)/\sqrt 2\)、方差 1;两者相加 5 是总方差(每个点到原点距离平方的平均:\((8 + 8 + 2 + 2)/4 = 5\)),PC1 占 \(4/5 = 80\%\)——这个比例叫解释方差比。上面 200 个点的云 90.7% 的方差在 PC1 上——压成一维只丢 9.3%。
2. 为什么是”方差”
换个说法:把点投到一条线上再”抬回”原空间,与原来的点有一个距离(重建误差)。让重建误差之和最小的直线,恰好就是方差最大的直线——两者是同一件事。上一节 \((3, 1)\) 的例子:点到原点距离² 是 \(9 + 1 = 10\),投影坐标² 是 \(2.83^2 = 8\),残差 \((1, -1)\) 的长度² 是 2,\(10 = 8 + 2\)——勾股定理。对每个点都有”距离² = 投影² + 残差²”,左边与方向无关(点在那里不动),所以让投影² 的总和最大,就是让残差² 的总和最小。第二篇最小二乘也是找一条线让残差最小,但那里的残差是竖直的(只看 \(y\) 的误差),这里是垂直于直线的(\(x\)、\(y\) 一起算),所以两条线不一样。
三、手写 PCA
1. 四行
def pca(X, k):
Xc = X - X.mean(0) # ① 中心化
U, S, Vt = np.linalg.svd(Xc, full_matrices=False) # ② 数据矩阵的 SVD:Vt 的每一行是一个主成分方向
explained = S ** 2 / (len(X) - 1) # ③ 每个主成分的方差 = 奇异值² / (n−1)
return Xc @ Vt[:k].T, Vt[:k], explained / explained.sum() # ④ 投影到前 k 个方向 [n, k];方向;各主成分的解释方差比
- ① 减掉均值——PCA 找的是围绕均值的方差方向,不减均值第一主成分会指向均值本身;
- ② 对中心化后的 \(n \times d\) 数据矩阵做 SVD,\(V^T\) 的行就是主成分方向、已按方差从大到小排好。四个点的例子:
svd返回的奇异值是 \(4, 2\),\(V^T\) 的两行是 \((1, 1)/\sqrt 2\) 与 \((1, -1)/\sqrt 2\)(可能差一个负号); - ③ 奇异值的平方除以 \(n - 1\) 就是该方向上的方差(第四章证明;四个点用 \(n\) 而不是 \(n - 1\) 除:\(4^2 / 4 = 4\)、\(2^2 / 4 = 1\),正是上一章手算的两个方差);
- ④ 投影 = 数据矩阵乘前 \(k\) 个方向。
前 10 维投影坐标与 sklearn 的最大差 3.2e-13(符号对齐后)
与 sklearn.decomposition.PCA 逐元素相差 \(10^{-13}\)(”符号对齐”:一个方向乘 −1 仍是同一个方向,两个实现可能选相反的符号)。
2. 解释方差:手写数字
1797 张 8×8 的手写数字,每张 64 个像素 = 64 维:
| 前 \(k\) 个主成分 | 1 | 2 | 5 | 10 | 20 | 30 |
|---|---|---|---|---|---|---|
| 累计解释方差 | 14.9% | 28.5% | 54.5% | 73.8% | 89.4% | 95.9% |
解释 95% 需要 29 维(原 64 维)。64 个像素之间高度相关(一个像素黑,它旁边的也大概率黑),真正独立变化的方向远少于 64。
3. 重建:前 \(k\) 维还原一张图
降维之后能还原吗?重建 = 均值 + 前 \(k\) 个坐标 × 前 \(k\) 个方向:
rec = mu + Z[i, :k] @ V[:k] # Z[i, :k]:这张图在前 k 个主成分上的坐标;V[:k]:前 k 个方向
| \(k\) | 1 | 2 | 5 | 10 | 30 |
|---|---|---|---|---|---|
| 全部 1797 张的平均重建 MSE | 15.98 | 13.42 | 8.54 | 4.91 | 0.77 |
用 10 个数(而不是 64 个)就能存一张认得出来的数字,30 个数就肉眼无差——这就是”64 维数据基本上是低维的”的直观含义。
四、三个名字一件事
教材里 PCA 有两种讲法——”协方差矩阵的特征分解”与”数据矩阵的 SVD”——它们是同一个算法:
- 协方差矩阵 \(C = \frac{1}{n-1} X_c^T X_c\)(\(d \times d\))描述各维度之间怎么一起变化:对角线是每个维度自己的方差,第 \((i, j)\) 格是维度 \(i\) 与 \(j\) 的协方差——两者同增同减为正、一增一减为负、无关为零。四个点的例子(用 \(n\) 除):\(C = \begin{pmatrix} 2.5 & 1.5 \\ 1.5 & 2.5 \end{pmatrix}\),两个坐标各自方差 2.5,协方差 1.5 为正——点云沿 45° 拉长。一个矩阵的特征向量是被它作用后方向不变、只被拉长 \(\lambda\) 倍的向量(\(Cv = \lambda v\)),\(\lambda\) 叫特征值。验算:\(C \begin{pmatrix} 1 \\ 1 \end{pmatrix} = \begin{pmatrix} 4 \\ 4 \end{pmatrix} = 4 \begin{pmatrix} 1 \\ 1 \end{pmatrix}\),\(C \begin{pmatrix} 1 \\ -1 \end{pmatrix} = 1 \cdot \begin{pmatrix} 1 \\ -1 \end{pmatrix}\)——特征向量正是两个主成分方向,特征值 4 与 1 正是投影后的方差。这不是巧合:沿方向 \(u\) 投影后的方差是 \(u^T C u\),让它最大的单位向量就是最大特征值对应的特征向量。
- 对中心化数据做 SVD:\(X_c = U \Sigma V^T\)。代进去:\(C = \frac{1}{n-1} V \Sigma U^T U \Sigma V^T = V \frac{\Sigma^2}{n-1} V^T\)——这正是 \(C\) 的特征分解:\(V\) 的列是特征向量,\(\sigma_i^2 / (n-1)\) 是特征值。
三种算法算同一份数据:
| 算法 | 前 3 个方差 |
|---|---|
协方差矩阵 eigh 的特征值 |
179.01, 163.72, 141.79 |
| 数据矩阵 SVD 的 \(\sigma^2 / (n-1)\) | 179.01, 163.72, 141.79 |
sklearn.PCA.explained_variance_ |
179.01, 163.72, 141.79 |
特征向量与 SVD 的 \(V\) 的前 3 列余弦 1.000(同一方向)。实际实现都走 SVD:不用先算 \(X^T X\)(它的条件数是 \(X\) 的平方,数值上更不稳)。”PCA”与”截断 SVD”在中心化数据上是同一件事。
五、有效维度与低秩
1. 29 维解释 95% 意味着什么
64 维的数据,29 维就解释了 95% 的方差——数据”基本上”躺在一个 29 维的子空间里,剩下 35 个方向几乎只有噪声。矩阵的秩名义上是 64(没有哪个奇异值精确为零),但后 35 个奇异值很小——”有效秩”只有 29 左右,这是”低秩”的第一个真实实例。它有直接的工程含义:存储、传输、检索都可以只用前 29 维,几乎不损失信息(上一节 \(k = 30\) 的重建)。
2. 一个真实权重矩阵的奇异值谱
同样的眼光看一个 LLM 的权重矩阵。Qwen2.5-0.5B 第 12 层的 \(W_Q\)(896 × 896),与一个同尺寸、同标准差的随机高斯矩阵,各算奇异值:
解释 90% 能量需要的秩:真实权重 299 / 896,随机矩阵 458 / 896
“能量”就是奇异值的平方和(对数据矩阵它是总方差);”解释 90% 能量需要的秩”是前多少个奇异值的平方加起来占到 90%——与手写数字那张累计曲线是同一个量,只是对象从数据矩阵换成了权重矩阵。
真实权重的谱比随机矩阵衰减得快——它不是随机的,有结构——但 \(W\) 本身远不是低秩的(90% 的能量要 299 维)。LoRA 的低秩假设说的不是 \(W\),而是微调的改动 \(\Delta W\):一次微调只教模型少数几件新事,改动集中在少数方向上,所以 \(\Delta W = BA\)、\(r = 8\)–64 就够。这是比”数据是低维的”更强的一个假设,已被实践反复验证(L5 后训练系列讲它的训练细节)。
六、画在二维上
1. PCA vs t-SNE
要把 64 维画在纸上,最直接的是取前两个主成分:
前两维只解释了 28.5% 的方差,所以图上能分开的只有差别最大的几类(0 和 1),形状相近的 3 / 5 / 8 混在一起。要在二维上把更多结构分开,用非线性降维:t-SNE、UMAP。它们不找方向,而是直接在二维上安排点的位置,让原空间里的近邻在二维上也是近邻。量化一下”同类点有多聚在一起”——在二维坐标上做 KNN 分类:
| 二维坐标来自 | KNN 准确率(5 折) |
|---|---|
| PCA 2 维 | 0.603 |
| t-SNE 2 维 | 0.976 |
| PCA 30 维 → t-SNE 2 维 | 0.977 |
2. 二维图能信什么
t-SNE / UMAP 的图漂亮得多,代价是:
- 只保留局部结构:同一团里谁挨着谁是可信的;两团之间的距离没有意义,团的大小也没有意义(算法会把密的团摊开、稀的团收紧);
- 不可逆:从二维坐标不能还原原向量,也不能把新的点”投影”进已有的图(UMAP 部分可以);
- 有随机性:换个 seed 图就变;
- 慢:\(O(n^2)\) 或接近,几万个点以上要先降维。
所以标准做法是先 PCA 到 30–50 维再 t-SNE / UMAP 到 2 维——PCA 去掉噪声维度并加速(上表最后一行与直接 t-SNE 一样好),t-SNE 负责好看。读图时记住:看团,不看团之间的距离。
七、embedding 的各向异性
1. 任意两句话都很像
回到上一篇那 78 句话的 896 维句向量(Qwen2.5-0.5B 最后一层 mean pooling)。算任意两句的余弦相似度:
| 全部对的余弦均值 | 同主题对 | 不同主题对 | 两者之差 | |
|---|---|---|---|---|
| 原始 | 0.790 | 0.911 | 0.771 | 0.140 |
| 减均值 | −0.011 | 0.539 | −0.098 | 0.637 |
| 减均值 + 去掉前 2 个主成分 | −0.012 | 0.297 | −0.061 | 0.358 |
原始向量上,”央行下调利率”与”先把洋葱炒到透明”的余弦也有 0.77——所有向量都挤在一个窄锥里,两两之间的夹角都很小。用本文的工具看一眼:对这 78 个向量做 PCA,第一主成分解释 25.8%、前 3 个 50.2%——一个巨大的公共方向(大致是”所有句子的均值”)主导了一切。这叫各向异性(anisotropy),是很多 embedding 模型(尤其是直接拿语言模型隐状态当 embedding)的通病,它让”余弦相似度 0.8”这个数字失去含义——0.8 可能只是”两句都是中文句子”。
2. 修法
最简单的修法是减均值:把公共方向扣掉。减均值后全部对的均值降到 −0.01,同主题与不同主题的差距从 0.14 拉开到 0.64(右图两个分布分开了)。再进一步是去掉前几个主成分(all-but-the-top)或白化(把各主成分的方差拉平)——但在这个只有 78 句、6 个主题的小语料上,前 2 个主成分恰好承载了主题信息,去掉它们反而让差距缩回 0.36;这些更激进的修法适合大语料,小数据上减均值就够。训练时的修法是加对比 loss(第五篇的间隔思想),专门的 embedding 模型都这么做。
3. 对检索的影响
用一句体育的句子去检索最相似的三句:
| top-1 | top-2 | top-3 | |
|---|---|---|---|
| 原始余弦 | 自行车赛…(0.93) | 守门员扑出点球…(0.92) | 羽毛球决赛…(0.91) |
| 减均值余弦 | 自行车赛…(0.56) | 新赛季的赛程表…(0.53) | 羽毛球决赛…(0.51) |
检索结果都对(体育句子排前面),但原始分数 0.93 / 0.92 / 0.91 与不同主题句子的 0.77 挤在一起,几乎没有区分度;减均值后 0.56 vs −0.10,阈值才有地方放。看到检索分数全都很高又分不开时,先做一次 PCA 看解释方差——第一主成分占比异常大就是各向异性,减均值再算。
八、本文小结
- PCA 找投影后方差最大(= 重建误差最小)的互相垂直的方向;二维例子 PC1 方差 3.44 vs 随手方向 1.34。手写四行(中心化 → SVD → 取前 \(k\) 行),与 scikit-learn 差 \(10^{-13}\)。
- 解释方差给出数据的有效维度:手写数字 64 维里 29 维解释 95%,10 个坐标就能重建一张认得出的图,30 个肉眼无差。
- 三个名字一件事:协方差矩阵特征分解 = 中心化数据 SVD = PCA,特征值 \(= \sigma^2 / (n-1)\),三种算法数字全同;实现走 SVD 更稳。
- 低秩:数据基本上是低维的;真实权重矩阵的谱比随机矩阵衰减得快(90% 能量 299 vs 458 维)但 \(W\) 本身不低秩——LoRA 的低秩假设是关于 \(\Delta W\) 的。
- 画图:PCA 前两维只能分开差别最大的类(KNN 0.60);t-SNE / UMAP 非线性、只保局部、不可逆、有随机性(0.98);标准做法先 PCA 到 30–50 维再 t-SNE;看团,不看团间距离。
- 各向异性:78 句真实句向量任意两句余弦均值 0.79、第一主成分占 26%——所有向量挤在窄锥里,余弦失去含义;减均值后同主题 / 不同主题的差距从 0.14 拉到 0.64;检索分数全高又分不开时先看 PCA。
配套代码:本文全部数字与图由 classical-ml/08_dimensionality_reduction.py 产生(geometry / pca / reconstruct / svd / tsne / anisotropy / spectrum 七个子实验;句向量复用上一篇的 out/sentence_embeddings.npz,权重谱读本地缓存的 Qwen2.5-0.5B),CPU 上一分钟内跑完。
九、自测
-
为什么 PCA 前要减均值?不减会怎样?
答案
PCA 找的是围绕均值的方差方向;不减均值,第一主成分会指向均值本身而不是数据的变化方向。
-
中心化数据矩阵的奇异值是 \(\sigma_1 = 30\),\(n = 101\)。第一主成分方向上的方差是多少?
答案
\(\sigma_1^2 / (n - 1) = 900 / 100 = 9\)。
-
64 维数据 29 维解释 95% 方差,说明什么?这与 LoRA 有什么关系、又有什么区别?
答案
数据近似躺在 29 维子空间里,有效维度远少于 64;LoRA 假设微调的改动 \(\Delta W\) 只在少数方向上——同一种低秩直觉,但对象是 \(\Delta W\) 而不是数据或 \(W\) 本身(真实 \(W\) 的 90% 能量要 299/896 维)。
-
t-SNE 图上两个团离得很远、另两个团挨得很近。能得出什么结论?
答案
什么也不能——t-SNE 只保留局部结构,团与团之间的距离和团的大小没有意义;能信的只是”同一团里的点在原空间是近邻”。
-
两个 embedding 的余弦相似度 0.85,但这个模型的所有向量对余弦都在 0.75 以上。0.85 说明什么?该怎么处理?
答案
几乎没有信息——各向异性让所有对都高;减均值后再算(本文 0.14 的差距拉开到 0.64),或看它在全部对里的百分位。
-
怎么用 PCA 一眼判断一批 embedding 有没有各向异性?
答案
看第一主成分的解释方差比:异常大(本文 26%,正常应远小于此)说明有一个巨大的公共方向,所有向量挤在窄锥里。
下一篇
下一篇讲无监督学习在数据工程里最重要的一个应用:万亿 token 的近似去重——Jaccard、MinHash 的无偏估计、LSH 的 S 曲线,以及”Jaccard 大于 0.7 视为重复”是怎么定出来的。
-
它找的是投影后方差最大、等价地重建误差最小的方向(勾股定理:投影长度² + 残差² = 常数)。对中心化数据 \(X_c = U\Sigma V^T\),协方差矩阵 \(C = X_c^T X_c / (n-1) = V \frac{\Sigma^2}{n-1} V^T\) 恰好是特征分解——\(V\) 的列是主成分方向、\(\sigma^2/(n-1)\) 是方差,所以 PCA 就是 SVD;三种算法前 3 个方差全是 179.01 / 163.72 / 141.79。详见第二章、第四章。 ↩
-
先用 PCA 的解释方差看有效维度(手写数字 64 维里 29 维解释 95%),画图时先 PCA 到 30–50 维再 t-SNE / UMAP 到 2 维。PCA 前两维只能分开差别最大的类(KNN 0.60),t-SNE 能把十类各成一团(0.98),但它只保局部结构:同一团里谁挨着谁可信,团与团的距离和团的大小没有意义,且不可逆、有随机性。详见第六章。 ↩
-
各向异性:所有向量挤在一个窄锥里,有一个巨大的公共方向(对 78 句真实句向量做 PCA,第一主成分占 26%),任意两句余弦均值 0.79,”央行下调利率”与”炒洋葱”也有 0.77。修法是减均值(同主题 / 不同主题的余弦差距从 0.14 拉到 0.64),大语料上再去掉前几个主成分或白化;训练时加对比 loss。详见第七章。 ↩
本文由 arganzheng 创作,采用 CC BY 4.0 许可协议。在保留原文作者、署名以及完整原文链接(https://arganzheng.life/dimensionality-reduction-pca-svd-tsne-and-umap.html)的前提下,欢迎各种形式的转载、翻译或商业引用。
COMMENTS
评论存放在 GitHub Discussions, 用 GitHub 账号登录即可发表,支持 Markdown。 想针对正文某句话说?选中那段文字,点浮出的「评论」即可划线评论;觉得哪里写错了,发表时勾上「同时提交 Issue」。 有人回复你时 GitHub 会按你的通知设置发邮件,不用守在这里。