本文是《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 个二维的点,一团拉长、倾斜的云。想把它压成一维(每个点只留一个数),该往哪个方向投影?

左:中心化后的点云与两个互相垂直的箭头——红色 PC1 沿着云的长轴、橙色 PC2 沿短轴;中:把每个点投影到 PC1 上(红点排成一条斜线),投影后方差 3.44;右:投影到竖直方向(灰点排成一条竖线),方差 1.34

两个主成分方向 [-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];方向;各主成分的解释方差比
  1. ① 减掉均值——PCA 找的是围绕均值的方差方向,不减均值第一主成分会指向均值本身;
  2. ② 对中心化后的 \(n \times d\) 数据矩阵做 SVD,\(V^T\) 的行就是主成分方向、已按方差从大到小排好。四个点的例子:svd 返回的奇异值是 \(4, 2\),\(V^T\) 的两行是 \((1, 1)/\sqrt 2\) 与 \((1, -1)/\sqrt 2\)(可能差一个负号);
  3. ③ 奇异值的平方除以 \(n - 1\) 就是该方向上的方差(第四章证明;四个点用 \(n\) 而不是 \(n - 1\) 除:\(4^2 / 4 = 4\)、\(2^2 / 4 = 1\),正是上一章手算的两个方差);
  4. ④ 投影 = 数据矩阵乘前 \(k\) 个方向。
前 10 维投影坐标与 sklearn 的最大差 3.2e-13(符号对齐后)

sklearn.decomposition.PCA 逐元素相差 \(10^{-13}\)(”符号对齐”:一个方向乘 −1 仍是同一个方向,两个实现可能选相反的符号)。

2. 解释方差:手写数字

1797 张 8×8 的手写数字,每张 64 个像素 = 64 维:

左:64 个主成分各自解释的方差比柱状图,第一个 14.9%、快速衰减,第 30 个之后接近零;右:累计曲线,29 维处到 95%(虚线),64 维到 100%

前 \(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 个方向

六张 8×8 的图:原图是一个 7;k = 1 时只是一团模糊、重建误差 24.6;k = 2 隐约有形;k = 5 能认出是 7;k = 10 清楚了、误差 7.3;k = 30 与原图几乎一样、误差 1.7

\(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),与一个同尺寸、同标准差的随机高斯矩阵,各算奇异值:

横轴奇异值序号 1 到 896,纵轴归一化后的奇异值(对数轴):真实权重(红)从 1 开始持续下降到约 0.01;随机矩阵(灰)在前 600 个几乎平坦、末尾才掉下来

解释 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 维画在纸上,最直接的是取前两个主成分:

左:手写数字 PCA 前两维的散点,0(深蓝)与 1(橙)各在一角分得开,中间 3、5、8、9 几种颜色混成一片;右:PCA 30 维再 t-SNE 到 2 维,十种颜色各成一团、团与团分开

前两维只解释了 28.5% 的方差,所以图上能分开的只有差别最大的几类(0 和 1),形状相近的 3 / 5 / 8 混在一起。要在二维上把更多结构分开,用非线性降维:t-SNEUMAP。它们不找方向,而是直接在二维上安排点的位置,让原空间里的近邻在二维上也是近邻。量化一下”同类点有多聚在一起”——在二维坐标上做 KNN 分类:

二维坐标来自 KNN 准确率(5 折)
PCA 2 维 0.603
t-SNE 2 维 0.976
PCA 30 维 → t-SNE 2 维 0.977

2. 二维图能信什么

t-SNE / UMAP 的图漂亮得多,代价是:

  1. 只保留局部结构:同一团里谁挨着谁是可信的;两团之间的距离没有意义,团的大小也没有意义(算法会把密的团摊开、稀的团收紧);
  2. 不可逆:从二维坐标不能还原原向量,也不能把新的点”投影”进已有的图(UMAP 部分可以);
  3. 有随机性:换个 seed 图就变;
  4. :\(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.5–1.0 之间、大量重叠;减均值后,不同主题对集中在 −0.4 到 0.2、同主题对集中在 0.3 到 0.9,两个分布明显分开

  全部对的余弦均值 同主题对 不同主题对 两者之差
原始 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 上一分钟内跑完。

九、自测

  1. 为什么 PCA 前要减均值?不减会怎样?

    答案

    PCA 找的是围绕均值的方差方向;不减均值,第一主成分会指向均值本身而不是数据的变化方向。

  2. 中心化数据矩阵的奇异值是 \(\sigma_1 = 30\),\(n = 101\)。第一主成分方向上的方差是多少?

    答案

    \(\sigma_1^2 / (n - 1) = 900 / 100 = 9\)。

  3. 64 维数据 29 维解释 95% 方差,说明什么?这与 LoRA 有什么关系、又有什么区别?

    答案

    数据近似躺在 29 维子空间里,有效维度远少于 64;LoRA 假设微调的改动 \(\Delta W\) 只在少数方向上——同一种低秩直觉,但对象是 \(\Delta W\) 而不是数据或 \(W\) 本身(真实 \(W\) 的 90% 能量要 299/896 维)。

  4. t-SNE 图上两个团离得很远、另两个团挨得很近。能得出什么结论?

    答案

    什么也不能——t-SNE 只保留局部结构,团与团之间的距离和团的大小没有意义;能信的只是”同一团里的点在原空间是近邻”。

  5. 两个 embedding 的余弦相似度 0.85,但这个模型的所有向量对余弦都在 0.75 以上。0.85 说明什么?该怎么处理?

    答案

    几乎没有信息——各向异性让所有对都高;减均值后再算(本文 0.14 的差距拉开到 0.64),或看它在全部对里的百分位。

  6. 怎么用 PCA 一眼判断一批 embedding 有没有各向异性?

    答案

    看第一主成分的解释方差比:异常大(本文 26%,正常应远小于此)说明有一个巨大的公共方向,所有向量挤在窄锥里。

下一篇

下一篇讲无监督学习在数据工程里最重要的一个应用:万亿 token 的近似去重——Jaccard、MinHash 的无偏估计、LSH 的 S 曲线,以及”Jaccard 大于 0.7 视为重复”是怎么定出来的。

  1. 它找的是投影后方差最大、等价地重建误差最小的方向(勾股定理:投影长度² + 残差² = 常数)。对中心化数据 \(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。详见第二章第四章。 

  2. 先用 PCA 的解释方差看有效维度(手写数字 64 维里 29 维解释 95%),画图时先 PCA 到 30–50 维再 t-SNE / UMAP 到 2 维。PCA 前两维只能分开差别最大的类(KNN 0.60),t-SNE 能把十类各成一团(0.98),但它只保局部结构:同一团里谁挨着谁可信,团与团的距离和团的大小没有意义,且不可逆、有随机性。详见第六章。 

  3. 各向异性:所有向量挤在一个窄锥里,有一个巨大的公共方向(对 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 会按你的通知设置发邮件,不用守在这里。

×