ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

聚类分析核心算法与实战:K-means、层次聚类、DBSCAN

聚类分析核心算法与实战:K-means、层次聚类、DBSCAN

上周接到一个没有标签的用户行为数据集,老板只丢下一句话:“你先把这些用户分成几类看看。”没有标准答案、没有历史规律可抄,我脑子里跳出来的第一个名字就是聚类分析。在数据挖掘里,聚类分析属于无监督学习,核心任务就一条:在没有标注的情况下,把相似的对象放进同一组,让组内尽量紧凑、组间尽量疏远。这类需求在用户分群、图像分割、文本主题归纳、异常检测里都极其常见。这篇内容相当于把聚类分析里的基本概念和主流算法——距离度量、K-means、层次聚类、DBSCAN、结果评估——从头到尾梳一遍,结合我自己实际跑数据时踩过的坑和常用套路展开,适合正要开始做聚类任务、或者学完理论不知道怎么落地的朋友对照着用。

1. 先搞清楚聚类在解决什么问题

1.1 聚类和无监督学习到底做了什么

先说一个经常被误解的点:聚类不是一个“预测”任务,而是一个“发现结构”的任务。监督学习里有标签,模型要学的是“x到y的映射”;聚类里没有标签,算法要回答的是“这些点天然长成几堆、每堆有什么共同特征”。

举个生活化的例子。你去逛超市,货架上的商品如果完全不分类,你找一瓶酱油得在几万个SKU里翻半天。超市把商品分成饮料区、调料区、日化区,这就是一次聚类的过程——分完类之后,每个区域的商品内部相关性高,区域之间的差异明显,你的购物效率就上来了。数据分析里的聚类,本质就是自动完成这种“货架划分”,只不过输入变成了多维特征,输出是每个样本的簇编号。

所以聚类分析在数据挖掘流程里通常不是终点,而是中间环节。它经常用于:

  • 探索性分析:数据拿到手先聚类,看看是否存在天然分组;
  • 特征工程:把簇编号作为新特征喂给下游模型;
  • 预处理:用聚类结果给部分未标注数据打伪标签;
  • 异常检测:聚不进去的样本往往就是异常点。

这个定位决定了我们评估聚类结果时,不能只盯着数学指标,还要看业务上是否可解释。这一点后面专门讲。

1.2 三个绕不开的业务场景

聚类不是理论玩具,我实际接触到的需求基本都能归到以下三类。

第一类是用户分群。某电商项目想做精细化运营,但用户没有填写任何画像信息。我们把用户的购买频次、客单价、浏览时长、最近一次购买时间等特征标准化之后做聚类,结果分成四群:高价值活跃用户、价格敏感型用户、沉睡用户、新用户。每一群对应不同的运营策略,比如沉睡用户发召回券,高价值用户进专属客服群。这个场景里聚类产出的不是一个“预测分值”,而是一组可以命名、可以行动的用户群。

第二类是异常检测。交易数据里绝大多数样本是正常的,少量样本行为模式明显不同。用聚类的视角看,正常的样本会聚成密集的大簇,异常样本要么落在稀疏区域,要么自己单独成簇。DBSCAN这类密度聚类天然会把这些点标记为噪声,做风控前筛一轮非常顺手。

第三类是结构归纳。比如对商品标题做文本向量化之后聚类,能够自动把“无线耳机”“蓝牙耳机”“运动耳机”归到相近的位置,方便后续做类目拆分。图像领域也有类似用法,对像素颜色聚类可以做简单的图像分割。

看这三个场景你会发现共同点:没有现成的标签,又想快速理解数据的内在结构。这就是聚类在数据挖掘里不可被替代的原因。

2. 距离度量:把“像不像”变成数字之前,先想清楚这四件事

2.1 为什么距离度量比选算法更关键

很多人一上来就纠结“用K-means还是DBSCAN”,其实更该先回答一个问题:你准备怎么定义两个样本之间的相似度?

聚类的所有算法,本质上都在做同一件事:把距离近的样本分到一起。距离定义换了,聚类结果天差地别。同一个数据集,用欧氏距离和用余弦相似度,出来的簇可能是完全不同的故事。所以距离度量不是算法后面的小细节,它决定了“相似”这个词的具体含义。

我把常用的距离度量整理成一张表,方便对照:

度量方式计算公式适合场景注意事项
欧氏距离sqrt(Σ(xi-yi)²)数值型、特征量纲相近、各维度独立对量纲极敏感,必须先标准化
曼哈顿距离Σ|xi-yi|高维稀疏数据、特征间不相关比欧氏距离更稳健,受异常值影响小
闵可夫斯基距离(Σ|xi-yi|^p)^(1/p)欧氏和曼哈顿的推广p=2是欧氏,p=1是曼哈顿
余弦相似度(x·y)/(|x||y|)文本向量、Embedding等方向敏感场景只关心方向不关心模长

选距离时不要默认。文本向量几乎不会用欧氏距离,因为文本向量的模长跟文档长度强相关,用欧氏距离会把“长度相似”误当成“内容相似”;而余弦相似度只看方向,更能体现语义上的接近。

2.2 量纲标准化:这一步不做,聚类结果会被单个特征绑架

我见过太多聚类结果看起来奇怪,根因不是算法选错,而是没做标准化。

举个例子,一份用户数据里有“年龄”和“年收入”两个特征。年龄在20到60之间波动,收入可能从3万到100万。如果用欧氏距离,年收入这个维度的差值完全碾压年龄,聚类结果几乎只由收入决定,“年龄”参与了个寂寞。更麻烦的是,收入如果有个别年入千万的样本点,距离矩阵会被极端值拉偏。

解决办法是先标准化再算距离。常用的两种:

  • Z-score标准化:x' = (x - μ) / σ,适合数据近似正态、没有太多极端异常值的情况;
  • Min-Max归一化:x' = (x - min) / (max - min),把数据压到[0,1]区间,但对异常值非常敏感,一个极端值会把其他所有值压到接近0。

实际操作里,如果已经发现某些特征有明显长尾,可以先做对数变换再标准化。这属于经验操作,不是教科书必写的内容,但往往很管用。

2.3 分类变量和混合类型特征怎么处理

真实数据不会全是数值型。用户画像里有性别、城市、设备类型,商品数据里有类目、品牌。直接把分类变量编码成0和1参与欧氏距离计算,语义上很难解释,“男”和“女”之间的距离等于1,但“北京”和“上海”的编码差也可能等于1,这没有可比性。

我的经验分两种情况:

  • 分类变量是低基数的,比如性别、是否认证,可以用独热编码之后继续用欧氏距离或余弦距离,问题不大;
  • 分类变量基数高,比如城市有几百个取值,独热编码之后特征维度爆炸,距离计算被稀释得很厉害。

这时候可以考虑Gower距离。它允许不同特征使用不同的距离定义,数值型用曼哈顿距离,分类型用“相同为0、不同为1”,最后加权汇总。配合层次聚类或者K-medoids这类算法使用,效果比强行用欧氏距离靠谱得多。

3. K-means:上手最快但坑最多的聚类算法

3.1 核心逻辑与伪代码

K-means应该是最普及的聚类算法了,普及到很多人把它当成聚类的代名词。它的思路非常朴素:

  1. 随机选K个点作为初始簇中心;
  2. 每个样本分到离它最近的中心点所在的簇;
  3. 重新计算每个簇的中心(所有样本点的均值);
  4. 重复第2、3步,直到中心点不再变化或达到最大迭代次数。

写成伪代码是这样:

输入:样本集 X,簇数 K,最大迭代次数 T 输出:每个样本的簇标签 随机初始化 K 个中心 μ1, μ2, ..., μK for t = 1 to T: for 每个样本 x_i: 计算 x_i 到所有中心 μ_k 的距离 将 x_i 分到距离最小的簇 C_k for 每个簇 C_k: 重新计算中心 μ_k = mean(C_k) 如果所有中心的变化量小于阈值 ε,停止迭代 返回样本标签

这里最核心的迭代思路,其实是在最小化一个目标函数——所有样本到它所属簇中心的距离平方和,也就是SSE(Sum of Squared Errors)。K-means每次迭代都在朝着让SSE降低的方向调整,但因为问题本身是非凸的,它不保证找到全局最优,只能找到局部最优。

3.2 K值到底怎么选

K-means最大的坑之一就是K需要提前指定。现实里你连数据有几类都不知道,怎么可能提前知道K?

我常用的方法有三板斧,按顺序来:

第一,肘部法则。跑K从1到10的K-means,记录每个K对应的SSE,画出折线图。SSE会随着K增大持续下降,但下降速度会在某个点之后明显变慢,这个转折点就是所谓的“肘部”,对应的K就是候选值。问题在于“明显变慢”很主观,有时候没有清晰的肘,所以这个方法只能作为参考。

第二,轮廓系数。轮廓系数综合了簇内紧密性和簇间分离度,取值范围在-1到1之间,越大说明聚类效果越好。分别计算K=2到K=10的轮廓系数,取最高的K。这个方法比肘部法则要客观一些,但计算量大,数据量一大就慢。

第三,业务约束。如果分完簇是用来做运营策略的,K一般不会太大,3到8个比较合适,因为运营团队不可能维护几十套差异化策略。很多时候我会先算肘部和轮廓系数,再结合业务可解释性做微调。

3.3 初始中心点选择与局部最优问题

K-means另一个知名的问题是对初始中心点敏感。随机初始化运气不好,可能一开始有两个中心落在同一个真实簇里,另外一个真实簇完全没有中心,算法最终收敛到一个很差的局部最优。

解决方式之一是使用k-means++初始化策略。它的核心思想是让初始中心点尽可能分散:第一个中心随机选,后续每个新中心在选的时候,离已有中心越远的点被选中的概率越大。这样能显著降低陷入差局部最优的概率。另一个更简单的工程化做法是:用多个随机种子跑若干次K-means,挑SSE最小的那次结果。

我在实践里通常两个方法叠加:先用k-means++,再并行跑10次取SSE最小结果。效果很稳定。

3.4 K-means的适用边界,别拿它硬扛所有形状

K-means天生假设簇是凸的、大小差不多的球形簇。为什么?因为它是基于均值定义簇中心,均值天然适合各向同性的分布。如果数据是月牙形、环形、带长尾的条状,K-means结果会非常离谱。

举一个经典的例子:两个同心环形的数据,理论上应该是两类,但K-means强行按距离划分,会把内环和外环各切一半,得到两组完全不符合直觉的簇。这是因为环形数据不满足欧氏空间里的“靠近中心”假设。

所以遇到非凸形状的数据,要么换密度聚类(DBSCAN),要么先做特征变换,要么用核方法把数据映射到高维空间再做K-means。不要硬刚。

4. 层次聚类:从树状图看懂数据的内在层次

4.1 凝聚式层次聚类的基本过程

层次聚类是另一种主流的聚类思路,最大的特点是它不直接给出一个“K个簇”的答案,而是生成一棵完整的层次树,你可以根据需要从任意高度切开。

最常用的凝聚式层次聚类(AGNES)算法,逻辑是自底向上的:

  1. 初始时每个样本自己是一个簇;
  2. 计算所有簇之间的距离,找到最近的两个簇;
  3. 合并它们;
  4. 重新计算新簇与其他簇之间的距离;
  5. 重复第2到4步,直到所有样本合并成一个簇。

这个过程中需要定义“簇与簇之间的距离”,也就是链接准则。不同准则会得到完全不同的聚类结构,甚至比算法选择影响更大。

4.2 四种链接准则怎么选

  • 单链接(single):取两个簇中最近的样本对之间的距离。优点是能发现任意形状的簇,缺点是对噪声极敏感,容易出现“链式效应”,把不该相连的簇串成一条长链。
  • 全链接(complete):取两个簇中最远的样本对之间的距离。对噪声更稳健,但偏向生成紧凑的球形簇,非凸形状照样搞不定。
  • 平均链接(average):取两个簇中所有样本对距离的平均值。介于单链接和全链接之间,实用性强,但在距离计算上略贵。
  • Ward法(Ward's method):合并时选择让簇内SSE增加最少的两个簇。相当于层次版的K-means思想,实践中效果很好,我默认首选。

我的习惯是:数据量中等、簇形状大致均匀时,优先用Ward法或者平均链接。单链接只在我明确想找细长条或者链状结构时才用。

4.3 树状图怎么读,以及它真正的价值

层次聚类最好的产品是树状图。图中每个叶子是一个样本,合并的高度表示两个簇之间的距离。你拿着这张图,可以在不同高度上切一刀:

  • 切得低,得到细粒度的很多小簇;
  • 切得高,得到粗粒度的几个大簇。

这比K-means方便的地方在于,你不必一开始就拍板K,可以先看树状图的结构再决定。比如某产品关键词的层次聚类,树状图上能清楚看到“耳机”下面分出“无线耳机”“有线耳机”,“无线耳机”再分出“头戴式”“入耳式”,这种层次关系对类目体系建设非常有价值。

但层次聚类有个硬伤,就是计算复杂度高。传统的凝聚式层次聚类复杂度接近O(n³),哪怕优化过也在O(n² log n)左右,样本量过万就很吃力,过十万基本没法直接跑。实际工程里如果有大数据量的层次聚类需求,一般是先对样本做抽样,或者先用K-means粗聚类成几百个簇,再对簇中心做层次聚类。

树状图的另一个作用是检查数据是否存在明显层次结构。如果树状图上所有样本几乎是同一高度合并的,说明数据里没有自然的嵌套结构,这时候强行解释层级没有意义。

5. DBSCAN:用密度对抗任意形状和不均衡的噪声

5.1 核心概念:Eps、MinPts、核心点、边界点、噪声点

如果说K-means是“按距离划圈”,那DBSCAN就是“按密度长在一起”。它的基本思想是:一个簇是一块密度足够高的区域,簇与簇之间被低密度区域隔开。

DBSCAN有三个关键概念:

  • Eps(邻域半径):一个点的Eps邻域是指以它为中心、半径Eps范围内的所有点;
  • MinPts(最少邻居数):一个点的Eps邻域内至少要有MinPts个点,才有资格被称为核心点;
  • 核心点、边界点、噪声点:核心点是邻域内稠密的点;边界点是落在某个核心点邻域内,但自己不够密的点;噪声点是谁都不挨着的孤立点。

算法过程简单理解:

任选一个未访问的点 p 如果 p 的 Eps 邻域内包含至少 MinPts 个点,则 p 是核心点,生成一个新簇 把 p 邻域内所有点都加入这个簇,并递归扩展:对簇内每个核心点,把它的邻域点也拉进来 如果 p 不是核心点,先标记为噪声,但之后如果它落在某个核心点邻域内,则改为边界点 重复以上过程直到所有点都被访问过

用一句类比解释密度可达:你住在一个小区里,你的邻居,以及你邻居的邻居,只要这些“邻居关系”链足够连续,你们就算同一个社区的人。链断了的地方就是密度低谷,也就是簇的边界。

5.2 两个参数怎么调:k-距离图是核心方法

DBSCAN的参数比K-means多,而且对参数敏感。Eps太小,大部分点会变成噪声;Eps太大,所有点会被并成一个簇。MinPts太小,任何稀疏区域的点都可能成簇;MinPts太大,小簇直接被当成噪声。

调参的标准做法是用k-距离图:

  1. 先对所有特征做标准化;
  2. 取MinPts = 2 × 特征维度数(一个经验值),也可以结合数据量调整;
  3. 计算每个样本到它的第MinPts个最近邻的距离;
  4. 把这些距离从大到小排序并画出曲线;
  5. 曲线出现明显拐点的位置,对应的距离值就是Eps的候选值。

为什么是“第MinPts个最近邻的距离”?因为如果某个点要成为核心点,它到第MinPts个最近邻的距离必须小于等于Eps。排序曲线里拐点左侧的点通常是噪声点,拐点右侧是簇内点,所以拐点处的距离就是密度的分界线。

需要注意的是,这个图有时候没有那么明显的拐点,尤其是数据密度渐变时。这时可以结合业务对噪声的容忍度来定:宁可多分几个小簇,也不要把噪声强行并进大簇。

5.3 DBSCAN的优势和它搞不定的情况

DBSCAN的优点非常突出:

  • 不需要指定K,簇数由数据密度自然决定;
  • 能识别任意形状的簇,环形、月牙形、S形都行;
  • 自动识别噪声点,不需要单独做异常值剔除;
  • 对初始参数不敏感到“只要Eps和MinPts合理,结果就稳”。

但它也有很明确的软肋。首先,如果数据里不同簇的密度差异很大,一个Eps值就很难同时照顾稀疏簇和密集簇。密集簇的Eps应该小,稀疏簇的Eps应该大,一个固定值必然顾此失彼。这种情况下可以尝试OPTICS算法,它是对DBSCAN的改进,不要求一个全局固定的Eps。

其次,高维数据下密度概念本身会失效。因为高维空间里点与点之间距离差异变小,所谓的“第k近邻距离”都差不多,拐点就不存在了。所以DBSCAN在高维场景下通常要先做降维,在二维或三维投影空间里聚类。

最后,参数调起来确实费劲。k-距离图每次都要画,样本量一大,计算第k近邻距离的开销也不小。工程上建议先用小样本画图定参数,再全量跑。

6. 聚类质量评估:不是跑完就结束,怎么证明结果靠谱

6.1 内部指标:没有真实标签时看什么

聚类是无监督学习,没有“准确率”这种东西。跑完之后不能用“预测对了多少”来评价,因为根本没有正确答案。所以业界发展出两类评估指标。

内部指标只依赖聚类结果本身和原始数据,不需要外部标签。

  • SSE:所有样本到所属簇中心的距离平方和。K-means的目标函数,越小代表簇内越紧凑,但它会随着K增大单调下降,不能单独用来选K。
  • 轮廓系数:对每个样本,先算它到同簇其他样本的平均距离a,再算它到最近邻簇所有样本的平均距离b,轮廓系数s = (b - a) / max(a, b)。取值在-1到1之间,越接近1说明该样本被正确分到紧凑且分离的簇里;接近0说明它正好在两个簇的边界上;负数说明可能分错了。
  • Davies-Bouldin指数:簇内散度之和与簇间距离的比值,越小越好。

实际项目里,我最常用的是轮廓系数+SSE两个一起看。比如跑K从2到10的K-means,列一张表:

KSSE轮廓系数
232000.42
321000.55
417000.47
515000.38
614000.32

这个模拟结果里K=3的轮廓系数最高,说明3簇划分在“内部紧凑+外部分离”这个维度上最优。虽然SSE还在随K增加而下降,但轮廓系数的下降说明多分出来的簇其实是在强行切开本来紧密的区域。

6.2 外部指标:有标签的时候怎么对照

有些场景下数据其实是有真实标签的,只是聚类阶段故意不用,比如有一个已经人工标注好的类目数据集,你想验证聚类算法能不能复现这个结构。这时候就要用外部指标。

  • 纯度(Purity):每个簇中占比最大的真实类别所占的比例,所有簇加权平均。
  • 兰德指数(RI):判断样本对是否在同一簇/同一类别,统计一致和不一致的样本对比例。
  • F值:精确率和召回率的调和平均,在聚类评估中常用调整兰德指数(ARI)。

我的看法是,外部指标更多用于算法评测和论文验证,工程里很少依赖。因为如果已有标签,你直接用监督学习就好,没必要绕一圈做聚类。外部指标更大的意义在于回答“聚类结果和已知规律是否符合”,类似一种置信度检查。

6.3 业务验证才是最后一道关

所有数学指标都是辅助,真正要过的是业务解释这一关。一个簇最终会被运营、产品、业务同事使用,如果他们看不懂这个簇里的人有什么共同特征,数学上再漂亮的聚类都是废的。

我通常对每个聚类结果做三件事:

  1. 统计每个簇在各特征上的均值/中位数/分布,找出显著区别于其他簇的特征;
  2. 给每个簇取一个业务名字,比如“低频高价值用户”“高频低客单羊毛党”;
  3. 问自己一个问题:看到这个簇的人,能不能马上知道下一步该做什么动作?如果答案是不能,说明这个聚类结果缺乏行动价值,要么换特征,要么换算法。

这个步骤经常被忽略,但它才是数据挖掘项目里聚类分析真正产生价值的地方。

7. 算法选型与实操经验:数据长什么样,决定你该用谁

7.1 选型速查表

直接给结论,基于我自己在不同数据集上的实践整理:

场景特征推荐算法原因
数据量大(十万级以上)、簇大致是球形、K可预估、需要快速出结果K-means时间复杂度近似线性,工程实现成熟
数据量中等、簇数未知、希望看到层次结构层次聚类(Ward或平均链接)树状图可解释性好
簇形状不规则、存在明显噪声、不知道KDBSCAN自动识别噪声,任意形状
高维文本向量、稀疏数据K-means + 余弦距离,或球形K-means欧氏距离在稀疏高维空间语义失真
混合类型特征(数值+分类)Gower距离 + 层次聚类 / K-medoids能同时处理不同类型特征
不同簇密度差异大OPTICS局部密度自适应,比DBSCAN灵活

选型时最重要的不是比较算法名气,而是搞清楚数据的几何形状、规模、噪声程度和业务对可解释性的要求。我甚至遇到过数据量不到两千条,用DBSCAN反而不如层次聚类的情况——因为数据本身呈现清晰的树状结构,强行用密度聚类反而把层次关系抹平了。

7.2 实操流程:从拿到数据到交出结论,我的固定动作

踩过不少坑之后,我现在做聚类基本固定走五步:

  1. 数据清洗与特征工程:处理缺失值、异常值,能加的特征先加上。聚类对特征非常敏感,漏掉关键特征,簇结构就会变形。
  2. 标准化与距离选择:连续特征先标准化,再根据数据特性选距离度量。这一步直接决定后续所有结果的上限。
  3. 小样本快速试跑:先随机抽样几千条,同时跑K-means和DBSCAN,画散点图看簇形状,对比轮廓系数。小样本快,能快速验证方向。
  4. 全量训练与评估:方向定了再全量跑,多跑几个K或几组参数,结合内部指标和业务可解释性做最终选择。
  5. 簇画像与交付:输出每个簇的特征分布、业务命名和行动建议,而不是只交付一个簇标签字段。

7.3 最后几个值得记住的经验

第一,聚类结果无法解释时,优先怀疑特征和距离,而不是算法。我见过有人对着K-means调了半天初始中心,结果最后发现问题是一个特征没做标准化。数据准备花的时间占整个项目的七成,这话在聚类里尤为真实。

第二,不要只看聚类图就觉得结果对了。降维可视化的图很容易给人“看起来很分开”的错觉,但二维投影可能已经丢失了大量结构信息,必须配数量化指标一起看。

第三,对业务而言,簇的数量宁少勿多。五个解释得清楚的簇,永远好过十个边界模糊的簇。好的聚类不是把数据分得多细,而是分得让人能快速理解并行动。

如果你正准备做自己的第一个聚类项目,我的建议是:别急着调参,先画图,先想清楚“相似”的定义,再用小样本把算法跑通。方向对了,后面全是水到渠成的事。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进