本文是《LLM 时代的经典机器学习:只讲它在哪里重现》系列的第 4 篇(共六篇)。上一篇:分类器一家——从朴素贝叶斯到梯度提升;下一篇:去重——MinHash 与 LSH 的概率

前两篇的模型都需要标签。无监督学习没有标签,只有 \(x\),要回答的问题是”这批数据里有什么结构”。在 LLM 的数据工程里它回答两个具体问题:这个语料里有哪些主题、各占多少(聚类),以及4096 维的 embedding 怎么看(降维)。这一篇讲三个工具——K-Means、DBSCAN、PCA——各自的算法、假设与失效方式,并把 PCA 与 L0 第三篇的 SVD 对上。

全篇的核心问题是:

怎么知道一个语料里有什么主题?4096 维的 embedding 怎么”看”?

一、总览

1. 三个工具

K-Means    分成 k 簇、每簇一个中心,反复"分配 → 更新中心"        假设簇是球形等大的;k 要自己选
DBSCAN     按密度聚:密集处连成簇、稀疏处是噪声                    不用指定 k、能找任意形状、能标离群点
PCA        找方差最大的正交方向,投影到前几维                      = 协方差矩阵的特征分解 = 数据矩阵的 SVD

2. 本文的章节安排

主题 内容
K-Means 算法、\(k\) 怎么选(肘部、轮廓系数)、它的假设
DBSCAN 按密度聚;两个月牙上的对比
聚类在数据工程里 语料的主题分布、按簇配比、找垃圾簇、SFT 数据的多样性
PCA 算法、解释方差、与 SVD 的关系、低秩直觉
可视化 二维图;t-SNE / UMAP;embedding 的各向异性
自测 五道题
本文小结  

配套脚本:04_unsupervised.py

二、K-Means

1. 算法

把 \(n\) 个点分成 \(k\) 簇,每簇一个中心,目标是让每个点到自己簇中心的距离平方和(簇内平方和,inertia)最小。算法只有两步反复:

初始化 k 个中心(随机挑 k 个点,或 k-means++ 挑得分散些)
重复直到不变:
    分配:每个点归到最近的中心
    更新:每个中心移到自己簇内所有点的均值

每一步都不会让簇内平方和变大,所以一定收敛——但收敛到局部最优,结果依赖初始化,实践中跑多次(n_init=10)取最好的。

2. \(k\) 怎么选

\(k\) 是超参数,算法不告诉你。两个常用的判据,在一份真实有 6 簇的数据上:

  k     簇内平方和    轮廓系数
  2        42880     0.665
  3        26439     0.591
  4        16412     0.532
  5         8274     0.609
  6         7281     0.518   ← 肘部
  7         6773     0.473
  8         6226     0.468
 10         5313     0.343
k=6 时与真实标签的 ARI 0.761;每簇样本数 [569, 500, 504, 500, 459, 468]
  • 肘部法:簇内平方和随 \(k\) 单调下降(\(k = n\) 时为零),但下降速度在真实簇数处突然变缓——从 5 到 6 降了 1000,从 6 到 7 只降 500。曲线的”肘部”就是 \(k\)。
  • 轮廓系数:每个点”离自己簇有多近、离最近的别的簇有多远”的综合,取值 \([-1, 1]\),越大越好。这里它在 \(k = 2\) 最高——因为 6 个簇两两靠近形成了 3 个大组,轮廓系数偏好粗的划分。两个判据不一致时,按业务定:你想看 3 个大主题还是 6 个细主题。

ARI(adjusted Rand index)是有真实标签时衡量聚类与标签一致程度的指标,1 是完全一致;0.761 说明 6 簇里有两对靠得近的被部分混了。真实数据没有标签,ARI 用不上,靠上面两个判据加人眼看。

3. 假设

K-Means 假设簇是球形的、大小差不多的(因为它用欧氏距离到中心的远近来分配)。簇是长条形、月牙形、密度不同、大小差很多时,它会切错——下一章。

三、DBSCAN

1. 按密度聚

DBSCAN 不预设簇的形状与个数。两个参数:邻域半径 \(\varepsilon\) 与最少点数 min_samples。半径内有足够多点的是核心点,核心点与它半径内的点连成一簇,连不上任何核心点的是噪声

2. 两个月牙

K-Means k=2:  ARI 0.255(球形假设不成立,一刀切在中间)
DBSCAN:       ARI 1.000,找到 2 簇 + 0 个噪声点(不用指定 k,能标出离群点)

两个交错的月牙,K-Means 用一条直线切成左右两半(每半各含两个月牙的一部分),DBSCAN 沿着密度把两个月牙完整地分开。它的代价:\(\varepsilon\) 不好选(密度不均匀的数据没有一个全局合适的半径),大数据上比 K-Means 慢。

选择的原则:簇大致球形、想指定个数、数据量大 → K-Means;形状不规则、想自动发现个数、想标出离群点 → DBSCAN(或层次聚类)。

四、聚类在数据工程里

1. “这批数据里有什么”

预训练语料几十 T、SFT 数据几十万条,没人能读完。聚类是回答”这批数据里有什么”的探索工具:

文本 ──embedding 模型──► 向量(几百到几千维)──K-Means k=50 或 200──► 每簇抽 5–10 条人读

每簇读几条就知道这一簇是什么主题(新闻、代码、论坛、广告、乱码……),簇的大小就是主题的占比。这是 L4 预训练系列第三篇”数据配比”决策的第一步——先知道有什么,再决定要多少

2. 三个具体用法

  • 按簇配比:某个主题(比如 SEO 垃圾、模板页)占了 20%,按簇采样把它压到 2%;某个想要的主题太少,定向补。
  • 找垃圾簇:爬虫抓到的乱码、导航栏、cookie 提示常常自成一簇——比写规则更快地发现它们。
  • SFT 数据的多样性:几万条指令按簇采样,保证各类任务都有、没有一类占太多;也用来发现”这 500 条其实是同一个模板”的近重复(下一篇用 MinHash 做精细版)。

三个用法都不需要聚类”正确”——\(k\) 取 50 还是 200、边界切在哪,对”看看有什么”影响不大。这是无监督学习在工程里最常见的角色:探索,而不是预测

五、PCA

1. 算法

主成分分析(PCA)找数据方差最大的方向。步骤:把数据减去均值;算协方差矩阵 \(C = \frac{1}{n-1} X^T X\)(\(d \times d\));做特征分解(L0 第三篇第七章),特征向量按特征值从大到小排——第一个特征向量是方差最大的方向(第一主成分),第二个是与它垂直的方向里方差最大的,依此类推;把数据投影到前 \(k\) 个特征向量上就是降到 \(k\) 维。

2. 解释方差

每个主成分的特征值就是数据在那个方向上的方差;前 \(k\) 个的和占总方差的比例叫解释方差比。手写数字(1797 张 8×8 的图,64 维):

前  1 个主成分解释  14.9% 的方差
前  2 个主成分解释  28.5%
前  5 个主成分解释  54.5%
前 10 个主成分解释  73.8%
前 20 个主成分解释  89.4%
前 30 个主成分解释  95.9%
解释 95% 需要 29 维(原 64 维)

64 维的数据,29 维就解释了 95% 的方差——数据”基本上”是低维的:64 个像素之间高度相关,真正的自由度比 64 少得多。这是 L0 第三篇”低秩”的第一个真实实例:如果前 \(r\) 个主成分解释了绝大部分方差,数据就近似躺在一个 \(r\) 维子空间里。LoRA 对 \(\Delta W\) 低秩的假设是同一种直觉——微调的改动只在少数几个方向上。

3. 与 SVD 的关系

PCA 就是数据矩阵的 SVD。对中心化后的 \(X = U \Sigma V^T\)(L0 第三篇),\(V\) 的列就是主成分方向,协方差矩阵的特征值等于奇异值的平方除以 \(n - 1\):

协方差矩阵的特征值 = 奇异值² / (n−1):前 3 个 [179.  163.7 141.8] vs [179.  163.7 141.8]

两种算法给出相同的数字。实际实现都走 SVD(数值上更稳),”PCA”与”截断 SVD”在中心化数据上是同一件事。

六、可视化

1. 二维图

把前两个主成分画成散点图,按类别上色:

二维投影图已保存 out/digits_pca.png(0 与 1 分得开,3/5/8 混在一起——线性投影只能到这个程度)

前两维只解释了 28.5% 的方差,所以图上能分开的只有差别最大的几类(0 和 1),形状相近的 3 / 5 / 8 混在一起。

2. t-SNE 与 UMAP

要在二维上把更多结构分开,用非线性降维:t-SNE、UMAP。它们在二维图上把簇分得漂亮得多,代价是:不可逆(不能从二维还原)、只保留局部结构(图上两个簇的距离没有意义)、有随机性、\(O(n^2)\) 或接近。所以标准做法是先 PCA 到 50 维再 UMAP 到 2 维——PCA 去掉噪声维度并加速,UMAP 负责好看。

3. embedding 空间的各向异性

把一个 embedding 模型的输出做 PCA,常见的现象是前几个主成分占了绝大部分方差——所有向量都挤在一个窄锥里,任意两段文本的余弦相似度都在 0.7 以上。这叫各向异性,是很多 embedding 模型的通病,会让”余弦相似度 0.8”这个数字失去含义。修法有减去均值、白化(把各主成分的方差拉平)、或在训练时加对比 loss。看到检索分数全都很高又分不开时,先做一次 PCA 看看解释方差。

七、自测

  1. K-Means 的簇内平方和为什么随 \(k\) 单调下降?\(k = n\) 时是多少?
  2. 一批数据里有一个很大的簇和三个很小的簇,K-Means \(k = 4\) 会怎样?该用什么?
  3. 64 维数据 29 维解释 95% 方差,说明什么?这与 LoRA 有什么关系?
  4. 为什么 PCA 前要减均值?不减会怎样?
  5. 两个 embedding 的余弦相似度 0.85,但这个模型的所有向量对余弦都在 0.8 以上。0.85 说明什么?该怎么处理?

答案要点:(1)多一个中心只会让点到最近中心的距离不增;\(k = n\) 时每点自成一簇,为 0。(2)K-Means 假设等大,会把大簇切开、把小簇合并;用 DBSCAN 或层次聚类,或先按密度分再细分。(3)数据近似躺在 29 维子空间里,真正的自由度远少于 64;LoRA 假设微调的改动矩阵同样只在少数方向上,\(r = 16\) 就够。(4)PCA 找的是围绕均值的方差方向;不减均值,第一主成分会指向均值本身而不是数据的变化方向。(5)几乎没有信息——各向异性让所有对都高;减均值 / 白化后再算,或看它在全部对里的百分位。

八、本文小结

  • K-Means:分配 → 更新中心反复,收敛到局部最优(n_init 多跑几次);\(k\) 用肘部法(簇内平方和的拐点)或轮廓系数选,两者不一致时按业务定;假设簇球形等大。
  • DBSCAN:按密度聚,不用指定 \(k\),能找任意形状、能标离群点;两个月牙上 K-Means ARI 0.26 vs DBSCAN 1.0;代价是 \(\varepsilon\) 难选、大数据慢。
  • 聚类在数据工程里是探索工具:语料 embedding 后聚类 → 每簇抽几条看主题 → 按簇配比 / 找垃圾簇 / SFT 数据保多样性;不需要聚类”正确”。
  • PCA = 协方差矩阵的特征分解 = 中心化数据的 SVD(特征值 = 奇异值² / (n−1),两者数字相同);解释方差比告诉你数据的有效维度——手写数字 64 维里 29 维解释 95%:数据基本上是低维的,LoRA 的低秩假设是同一种直觉。
  • 可视化:PCA 前两维只能分开差别最大的类;t-SNE / UMAP 非线性、不可逆、只保局部;标准做法先 PCA 到 50 维再 UMAP。embedding 的各向异性(前几个主成分占绝大部分方差、所有对余弦都高)让相似度分数失去含义,减均值 / 白化。

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

本文由 arganzheng 创作,采用 CC BY 4.0 许可协议。在保留原文作者、署名以及完整原文链接(https://arganzheng.life/unsupervised-learning-kmeans-pca-and-embedding-clusters.html)的前提下,欢迎各种形式的转载、翻译或商业引用。


COMMENTS

评论存放在 GitHub Discussions, 用 GitHub 账号登录即可发表,支持 Markdown。 想针对正文某句话说?选中那段文字,点浮出的「评论」即可划线评论;觉得哪里写错了,发表时勾上「同时提交 Issue」。 有人回复你时 GitHub 会按你的通知设置发邮件,不用守在这里。

×