聚类分析从入门到实战:K-Means/层次聚类/DBSCAN详解

发布时间:2026/10/16 10:03:39
聚类分析从入门到实战:K-Means/层次聚类/DBSCAN详解
聚类分析是我在数据挖掘项目里用得最多的工具之一。做用户分群、异常检测、文档归类甚至分析基因表达谱本质上都在做同一件事把相似的东西放到一起把不相似的东西分开。这个需求太日常了日常到你可能没意识到“物以类聚”这四个字背后其实藏着一整套严谨的数学逻辑。这一章的内容就是把这套逻辑拆开揉碎从基本概念讲到核心算法让你看完之后不仅知道K-Means怎么跑更知道它为什么有效、什么时候失效、出了问题时该往哪个方向排查。如果你是刚接触数据挖掘的新手或者已经在业务里做过一些数据分群但总觉得“黑箱感”很强这篇文章都适合你。我会从距离度量这种最底层的定义讲起把K-Means、层次聚类、DBSCAN这类主流算法的原理、优缺点和适用场景全部过一遍然后用一份模拟数据走完整条实操链路最后整理几个我实际踩过的问题和排查思路。整篇都是干货不看亏了。1. 聚类分析的整体设计思路与核心逻辑1.1 聚类的核心目标与应用场景聚类分析本质上是“无监督学习”的一种就是数据没有标签没有人告诉你“这个样本属于A类那个样本属于B类”你得靠数据自身的结构去发现规律。它和分类最大的区别就在这里分类是先有规则再分聚类是先看分布再找规则。举一个特别常见的例子。电商平台做用户运营时经常需要分群有的用户高频高客单价有的用户低频但客单价极高还有一批“羊毛党”只在大促时来一次。运营团队想针对不同人群做差异化策略但没有现成的标签体系。这时候拿用户的购买频次、客单价、最近一次购买间隔这些字段做聚类人群结构自己就浮现出来了。这就是聚类最核心的用途在标签缺失的情况下用数据的自然分布帮你发现结构。除此之外聚类还可以做异常检测、数据压缩预处理、图像分割、文本主题归纳等等。我自己在做一个推荐系统项目时就用聚类做过用户兴趣的粗粒度划分先把几百万用户聚成几十个兴趣簇再做精细的协同过滤计算量直接下降了一个量级效果还不掉。这种“先用聚类粗筛、再用精细模型精排”的思路在工业界非常常见。聚类分析的价值不在于它本身能直接给出答案而在于它能把高维、模糊、无序的数据整理成低维、清晰、有结构的信息。后续无论是打标、建模还是人工解读都从这个结构出发效率完全不一样。1.2 “距离”才是聚类的灵魂很多人觉得聚类的关键在于“用什么算法”我一开始也这么认为直到做了几个真实项目才发现算法的选择其实只占一小部分真正决定聚类效果的是你对“相似”的定义。什么叫相似数学上就是距离。距离小说明相似度高距离大说明相似度低。但同样是两个用户用欧氏距离算是一种结果用曼哈顿距离算是另一种结果用余弦相似度算又是一种结果。你选哪个取决于业务语义。欧氏距离是最直觉的两点之间的直线距离适合数值型特征、量纲相近的场景。但它的短板很明显——对量纲极度敏感。比如用户年龄范围是20到60年消费金额范围是1000到100000这两个字段放在一起算欧氏距离年龄的贡献几乎可以忽略不计。所以用欧氏距离之前或者标准化或者做加权否则结果会被量纲大的一方完全主导。曼哈顿距离算的是两个点在坐标系上沿轴方向的距离总和对异常值没有那么敏感适合特征之间存在独立维度的场景比如一个用户访问了几个页面、下单了几次这种“次数型”特征用曼哈顿距离往往比欧氏距离更稳。余弦相似度在文本场景里是主流。它只看方向不看长度对“一篇5000字文章和一篇200字摘要聊的是同一个话题”这种情况特别友好。文本向量化之后向量的模长往往和文本长度强相关这时候用欧氏距离会把长短文档分到不同簇里而用余弦相似度则能把同主题的文档聚在一起。我见过很多新手在这里踩坑拿TF-IDF向量直接算欧氏距离结果聚类结果完全被文档长度主导。换成余弦相似度之后效果立刻好了很多。所以做聚类之前第一个要想清楚的不是用哪个算法而是用什么方式度量“相似”。这个选择决定了你把什么样的数据往一起凑也决定了后续所有步骤的方向。1.3 数据结构与聚类类型聚类处理的输入通常是一个二维矩阵行是样本列是特征。但不同算法对数据结构的假设差异很大这一点经常被忽略。有些算法要求样本点是数值型向量比如K-Means直接算欧氏距离。有些算法则只需要一个“相似度矩阵”就行两两样本之间的距离后面所有计算都基于这个矩阵进行比如层次聚类和谱聚类。这个区别在实操中的意义是如果你的数据是用户对商品的评分矩阵每个用户是一个高维稀疏向量做K-Means前需要先降维或做矩阵分解如果换成层次聚类直接拿相似度矩阵开始跑就行省了很多预处理。从聚类的输出形式来看大致可以分成两类硬聚类和软聚类。硬聚类就是每个样本必须属于一个簇边界分明。软聚类则是每个样本以概率的形式属于多个簇比如高斯混合模型。大部分业务场景里硬聚类够用了但如果你的数据本身存在模糊地带——比如一个人既是“母婴人群”又是“美妆人群”——软聚类会更贴近真实情况。我做过一个跨品类兴趣分析的活用软聚类比硬聚类效果好得多因为用户兴趣本来就是多标签的强行打单标签反而失真。聚类类型的多样性还体现在层次上。有的场景只需要一层分组有的场景则希望看到“大簇套小簇”的树形结构。前者用划分聚类后者用层次聚类。理解了这些维度上的差异你选算法的时候就不会被“哪个更高级”这种问题带偏了。2. 核心算法逐个拆解与选型逻辑2.1 K-Means为什么它是永远的首选基础算法K-Means可以说是聚类算法里的“罗马柱”简单、快速、可解释性强绝大多数聚类需求都可以先用它试水。它的核心逻辑用一句话说就是你事先指定要分成K个簇算法随机选K个中心点然后循环做两件事——把每个样本分配到最近的中心点所属的簇再重新计算每个簇的中心即簇内所有样本的均值。不断重复直到中心点不再变化或变化很小为止。这里每一轮都像一个迭代优化的过程本质上是在最小化所有样本到其所属簇中心的距离平方和。这个指标叫SSESum of Squared ErrorsSSE越低理论上说明簇内越紧凑但这并不意味着SSE越低越好因为K越大SSE天然就越低极端情况下K等于样本数SSE就是0那没有任何意义。实操层面K-Means有几点需要注意。第一它对初始中心点选择非常敏感选不好可能陷入局部最优。所以工程上常用K-Means初始化策略即让初始中心点尽量相互远离这样比纯随机初始化稳定很多。第二它对异常值敏感一个异常点被划进某个簇这个簇的中心就被拽偏了。用之前最好做一步异常值检测或者用K-Medoids这类基于中心样本而不是均值的改进方案。第三它只能在凸形的簇结构上表现良好如果数据分布是“月牙形”或者“环形”K-Means会强行把不挨着的区域也切到一起这时候需要考虑密度聚类。我实际用K-Means做项目时的体会是它适合快速拿一个baseline适合你的数据分布很规整也适合大规模数据场景。它的时间复杂度接近线性sklearn里对十万级样本做K-Means也就几秒钟的事。你可以在跑复杂模型之前先拿K-Means的结果去看看分布能省大量时间。2.2 层次聚类需要全貌时的选择层次聚类和K-Means的思路完全不同。它不考虑“我到底要分成几类”而是先算所有样本两两之间的距离然后一层层往上合并。具体做法有自底向上凝聚和自顶向下分裂两种。实际用得多的是凝聚法先把每个样本单独当成一个簇然后每次找距离最近的两个簇合并直到最终所有样本都在一个簇里。这个合并的过程会构成一棵二叉树叫树状图。你随时可以在这棵树的任意高度“切一刀”得到你想要的任何数量的簇。这是层次聚类最大的优势——你不需要提前决定K可以先看全貌再选粒度。但层次聚类的缺点也很明显计算复杂度高。两层循环算距离矩阵需要O(n²)的时间和空间样本量上到几万就非常吃力了。所以我一般只在样本量不大的场景一两千以内用层次聚类或者在业务上非常需要看树状结构时用。层次聚类里还有一个细节很关键簇与簇之间的距离怎么定义。最常用的是平均距离average linkage即两个簇内所有样本两两距离的平均值和在k-Means里的均值思想最接近整体效果也算稳。最小距离single linkage会把两个簇的边界拉得很长容易产生链状效果。最大距离complete linkage则倾向于生成紧凑的球形簇对异常值相对敏感。这个选择直接影响聚类形态没有绝对优劣得看业务上你希望簇是什么形状。2.3 DBSCAN不规则形状与噪声场景的最优解DBSCAN是密度聚类里最经典的代表它的思路和K-Means完全不一样K-Means找的是“球状簇”DBSCAN找的是“密集区域”。它有两个核心参数εEps是邻域半径minPts是一个样本的邻域内至少包含多少个点才算核心点。算法从一个点出发如果在它半径ε内的点数超过minPts就认为这里是一个密集区域然后把区域内所有点都拉进同一个簇再顺着这些点继续扩展。最后那些“既不是核心点、又不在任何核心点邻域内”的点就被标记为噪声。DBSCAN的优点是根本不需要预先指定簇的数量还能自动识别噪声点而且可以处理任意形状的簇。我之前遇到过一种场景地图上分析门店覆盖区域用户分布呈现出“几个密集圈加一片稀疏地带”的特征K-Means跑出来结果一塌糊涂因为稀疏地带的点也被强行分配到了某个簇里。换DBSCAN之后圈的归属清晰稀疏点也被正确识别为低覆盖区结果非常符合业务直觉。代价是参数调起来比较费劲。ε和minPts的设置对结果影响很大而且当数据密度差异悬殊时一个全局的ε很难同时适配所有区域。如果数据中有的簇很密、有的簇很疏DBSCAN常常会把疏一点的簇当成噪声丢掉。这种情况可以试试OPTICS算法它相当于DBSCAN的扩展版能处理不同密度的簇但计算量更大。另外DBSCAN在高维数据里表现一般因为高维空间里样本距离普遍偏大而且差异变小ε很难找到一个合适的值。2.4 算法选型对比与实战决策路径每次做聚类项目我都会先拿一张对比清单过一遍再做决定。这里我把主流几个算法的关键特点整理成对照表方便大家一眼看清差异。算法是否需指定K能否处理噪声对簇形状的要求主要瓶颈适合场景K-Means需要弱凸形对初始值和异常值敏感大规模数据快速分群层次聚类不需要弱任意形状计算复杂度高小样本、需要树状图DBSCAN不需要强任意形状参数敏感、密度不均时效果差噪声多、形状不规则的场景高斯混合模型需要弱椭圆形簇需要概率解释软聚类、簇间有重叠谱聚类需要中任意形状需构图计算量大、构图方式敏感非凸结构、图数据选型的路径我一般是这样的先看数据规模上万条就优先排除层次聚类。再看不规则程度如果数据的分布形状完全没有先验先跑一遍DBSCAN看看能不能分出结构如果DBSCAN的噪声太多再考虑K-Means。最后看业务需求需要概率输出就用GMM需要“大群套小群”的层级就用层次聚类。还有一点不得不提聚类效果的好坏与特征质量强相关甚至比算法选择更重要。同样的K-Means特征不做处理和做了标准化、降维之后结果可能是天壤之别。所以选型之前先花精力把特征工程做好。3. 实操全流程从数据清洗到聚类结果解读3.1 数据预处理聚类成败的前置关口很多人拿到数据直接就跑聚类这是最大的坑。聚类算法对输入数据的要求很高哪怕你只是少做了一步标准化聚类结果都可能面目全非。第一步是处理缺失值。K-Means和层次聚类都不能直接处理NaN常见做法是删除相应样本或特征、用均值/中位数填充、或者用最简单的KNN插补。如果缺失比例大比如某个特征有40%以上缺失直接丢掉这个特征往往比填充更稳妥。我在实际项目中遇到过客户表里“职业”字段缺失率超过50%强行填充后反而引入了伪结构聚类结果完全不可解释最后还是删掉重新做了。第二步是标准化。几乎所有基于距离的聚类算法都要求特征在同一尺度上否则量纲大的特征会主导距离计算。最常用的方法是Z-Score标准化让每个特征的均值为0、方差为1或者用Min-Max归一化把特征缩放到[0,1]区间。这一步不是可选项对欧氏距离系的算法来说是强制的。我用过一个营销活动数据集“消费金额”字段的方差是“点击次数”的几百倍不标准化时聚类结果基本只跟着金额走标准化之后其他特征才开始起作用。第三步是降维。如果特征数量很大比如文本TF-IDF向量动辄上万维距离计算会变得极不稳定这就是“维度灾难”。常用做法是先用PCA砍到几十维再用聚类算法。我自己做文本聚类的时候基本流程都是TF-IDF转向量SVD降维到50维然后接K-Means效果比直接在高维上聚类稳定得多。也可以用UMAP这类非线性降维方法它在保留局部结构方面更强聚类前的降维效果也很好。处理完这些数据才能进入聚类环节。这一步不能省不能靠算法去“硬扛”。3.2 K值选择肘部法则与轮廓系数搭配使用K-Means需要先指定K这也是新手问得最多的问题K到底设几最经典的方法是肘部法则。你把K从1取到某个上限比如10对每个K运行K-Means并记录SSE然后画出K与SSE的关系曲线。随着K增大SSE不断减小但在某个K之后下降速度会明显变缓曲线出现一个“肘部”这个拐点就是相对合适的K值。这个方法的原理很直观当K还小于真实簇数时每多一个簇都能显著降低误差当K超过真实簇数后再增加簇只是把已经很紧凑的簇再切分收益就会骤减。但肘部法则的问题在于很多真实数据的曲线并没有明显的肘部曲线就是平滑下降的。这时候我会叠加轮廓系数来综合判断。轮廓系数同时衡量了簇内紧密度和簇间分离度取值范围在-1到1之间越接近1说明簇内越紧凑、簇间越分离。对每个K都算一次平均轮廓系数取最高值对应的K。另外一个思路是结合业务来定。做用户分群的时候运营团队通常希望是3到5个群因为太多群没法运营太少群又没有区分度。这个时候K不是从数据里“发现”出来的而是从业务约束里“定”出来的。我一般会算一个“数据建议值”再结合业务可行性做微调最后用一个双方都能接受的K。3.3 聚类实现一份可直接跑的Python示例这里我用一份模拟的电商用户数据走一遍K-Means聚类的完整流程。数据里包含三个特征年消费金额、年购买次数、距上次购买天数。模拟数据可能有人觉得不“真实”但流程本身是通用的换到任何数据集都成立。import numpy as np import pandas as pd import matplotlib.pyplot as plt from sklearn.preprocessing import StandardScaler from sklearn.cluster import KMeans from sklearn.metrics import silhouette_score # 1. 构造模拟数据 rng np.random.RandomState(42) n_samples 500 # 三个簇的中心大致不同 center1 [12000, 35, 7] center2 [3000, 10, 60] center3 [600, 5, 180] data np.vstack([ rng.normal(loccenter1, scale[1500, 5, 3], size(200, 3)), rng.normal(loccenter2, scale[500, 3, 8], size(200, 3)), rng.normal(loccenter3, scale[150, 2, 20], size(100, 3)) ]) df pd.DataFrame(data, columns[年消费金额, 年购买次数, 距上次购买天数]) # 2. 标准化 scaler StandardScaler() X_scaled scaler.fit_transform(df) # 3. 用肘部法则辅助选K sse_list [] for k in range(1, 11): km KMeans(n_clustersk, initk-means, n_init10, random_state42) km.fit(X_scaled) sse_list.append(km.inertia_) plt.figure(figsize(8, 5)) plt.plot(range(1, 11), sse_list, markero) plt.xlabel(K) plt.ylabel(SSE) plt.title(Elbow Curve) plt.show() # 4. 最终聚类 best_k 3 km_final KMeans(n_clustersbest_k, initk-means, n_init10, random_state42) df[cluster] km_final.fit_predict(X_scaled) # 5. 评估 sil silhouette_score(X_scaled, df[cluster]) print(fSilhouette Score: {sil:.3f}) # 6. 查看各簇的均值画像 print(df.groupby(cluster).mean())说一下几个容易被忽略的点。n_init10表示算法会从10个不同的随机初始中心运行10次取最好的一次结果。random_state用来固定随机种子保证结果可复现。这两个参数建议每次都写上。跑完之后最关键的一步是解读结果。把每个簇的特征均值打印出来对比簇与簇之间的差异。比如簇0可能是“年消费高、频次高、最近很活跃”这是典型的高价值活跃用户簇2可能是“消费低、频次低、很久没来”这是流失用户。每个簇都要能讲出业务故事来否则这个聚类就是失败的。3.4 聚类结果评估不只是看轮廓系数评估聚类质量是一件比想象中复杂的事。因为聚类没有“标准答案”你很难说一个聚类结果“对”还是“错”只能从多个角度验证它是否合理。轮廓系数是被用得最多的量化指标但它的局限在于只适合评估凸形簇。如果数据本身形状不规则即使聚类结构非常合理轮廓系数也可能很低。所以轮廓系数只能作为参考不能当成唯一标准。更有业务价值的评估方法是看“簇的可解释性”。把每个簇的特征分布画出来看看是不是有明显的分布差异把簇的样本数、均值、方差做成画像表格让业务同事看是否能给出合理命名。我在做用户分群项目时很多时候业务方一句“这群人能对得上我们的VIP客户”比任何指标都管用。还有一种场景是有外部标签可以参考的这时候可以用调整兰德指数ARI或互信息NMI来做对比。比如你已经知道数据里有一部分人工标注的类别虽然聚类不需要标签但你完全可以拿标签来验证聚类结果的一致性。不过要记住无监督学习本就不该依赖标签这个指标只是用来“参考”模型行为的。可视化的评估也很重要。如果原始数据能降到两维我习惯把聚类结果画成散点图颜色代表簇标签人眼扫一眼就能看出簇之间是否分得开。很多时候图形上已经能发现问题的结果指标可能依然显示“正常”所以“图表指标”组合验证才是稳妥的做法。4. 常见问题与排查技巧实录4.1 K-Means结果不稳定初始值敏感的排查做过聚类的人应该都有过这种经历同一份数据同一个K相同的代码这次跑出来的簇和上次跑出来的不一样甚至某个簇直接被分歪了。这通常是K-Means对初始中心点太敏感导致的。我排查这个问题的第一步是检查n_init参数如果设的是默认值1几乎可以肯定会不稳定至少在10以上。第二步看数据是否存在明显的极值点一个极端异常值可能把某个初始中心吸过去导致最终结果局部最优。第三步如果数据特征数量较大几十维以上可以试试先降维再聚类高维空间的距离计算不稳定是局部最优容易出现的温床。如果改完还不稳定就要考虑算法本身是否适合这份数据了。密度形状不规则的数据用K-Means反复横跳是正常的换DBSCAN跑一下看看结果稳定多了这个问题往往就消失了。4.2 聚类结果“无意义”量纲与数据质量排查“聚类跑出来了但每个簇的画像都差不多看不出任何差异”这也是高频问题。我见过很多人的第一反应是换算法其实大概率是数据预处理没做到位。先看是不是没有标准化。有金额、有次数、有年龄的数据不标准化直接丢进K-Means大数值特征直接主导距离其他特征全部沦为摆设簇与簇的差异只体现在一两个维度上其他维度完全相同看起来自然“没意义”。标准化之后往往就能看到不同簇在不同特征上的分化了。再看特征选择。如果特征本身就乱七八糟比如把用户ID也当成特征丢进去了距离计算会被ID这种纯随机变量完全打乱。聚类前一定要做特征筛选去掉无信息量的字段、去掉高度相关的冗余字段。还有一个常见原因是数据里混入了太多噪声样本。比如你分析的是全部用户但其中大量是压根没激活的僵尸账号它们在多个特征上都是0这些点会被聚成一堆簇的特征均值被严重拉低。这种情况可以先做一个过滤把激活用户和从未使用的用户拆开处理再对活跃用户做聚类。4.3 DBSCAN参数调不出来的处理思路DBSCAN的参数调节比K-Means的调K要难得多因为它两个参数联动而且没有一个清晰的“肘部”可以参考。我调参的一个实用步骤是这样的先把minPts设成特征维度数的两倍比如特征维度是5minPts设为10这是一个业界经验值。然后在sklearn.neighbors.NearestNeighbors里计算每个样本的k近邻距离k取minPts把这些距离排序后画出来。曲线拐弯的地方就是合适的ε。这是一个经典做法效果比瞎试好得多。如果数据密度差异太大全局ε搞不定我的建议是先做聚类前降维很多时候降维之后不同簇的密度差异会变小。再不行就用OPTICS替代DBSCAN它不再需要一个全局ε能从数据里自适应地发现不同密度的簇。虽然计算量大一些但省下来的调参时间完全值回票价。4.4 特征太多、维度过高导致的聚类失效维度灾难是聚类项目里非常隐蔽的杀手。特征数量一多样本间的距离会越来越接近高维空间中所有点几乎都处于“均匀稀疏”的状态聚类的结构被稀释掉了。这也是为什么高维聚类永远没有低维聚类看起来“清晰”。我的习惯是当特征超过30个时先做PCA或UMAP降维到5~20维再跑聚类。PCA的缺点是完全丢失特征的可解释性但如果你后续需要用聚出来的簇做预测或匹配这无所谓。如果需要保留业务上的可解释性可以考虑特征选择去冗余比如通过相关性矩阵把高度相关的特征去掉留下每个业务维度内最有代表性的变量。文本数据上这个思路更极端。我做过一个文本聚类项目原始TF-IDF向量有3万多维不做降维直接聚类簇与簇之间几乎分不开。降到50维之后同一主题的文档清晰地聚到了一起轮廓系数从0.2升到了0.4以上。高维是聚类的大敌尽早降维是降低成本又可以提升效果的稳定手段。4.5 聚类结果不稳定但方向一致时的业务处理有一种情况很有意思聚类结果每次跑都不一样但簇的中心大致在同一个区域只是边界样本的归属在摇摆。这种不稳定在真实数据里太常见了因为簇与簇之间本来就是渐变的边界样本很难有唯一的归属。这种情况下我不会强行追求稳定而是给边界样本加一个“置信度”的概念。比如连续跑多次聚类统计每个样本被分到同一个簇的比例如果某个样本经常在多个簇之间跳来跳去我给它的归属打上一个“边缘样本”的标记业务上可以做二次校验或者暂时不用它做任何自动化决策。聚类不是一次性的工作它是一个探索性过程。结果稳定只是表象核心还是看这个结构在业务上是否能被持续使用和验证。我建议把聚类分析做成一个周期性任务每次用同样的参数跑一遍观察簇结构是否发生变化。如果在几个月内簇结构没有大范围漂移说明这个聚类结果是稳定的、可用的但如果簇结构在频繁变化说明业务本质上是流动的这时你更应该关注变化的方向而不是执着于一次性的聚类结果。结尾做数据挖掘这几年聚类分析是我觉得最需要“手艺感”的算法之一。它不像分类模型那样有一个明确的准确率可以验算也不像回归模型那样有一条可以直接解释的曲线。你面对的是一个没有标准答案的结构发现过程所有的判断都依赖于你对数据的理解、对业务的感知、对距离和相似度的选择。这些恰恰是文档里不会写、课堂上不太讲、但真实项目里决定成败的东西。我自己也是踩了无数次坑才慢慢形成了一套“先理解数据、再设计距离、再选算法、最后反复验证”的稳定路径。希望这篇内容能帮你跳过那些我已经踩过的坑让你少花一些时间在尝试和纠错上多花一些时间在真正挖掘数据价值上。如果你在实操中也遇到了上面没提到的问题欢迎带着你的数据分布情况来聊我们互相印证一下经验。