
说句实在话做数据这行当久了聚类算法都快成条件反射了。拿到一批没有标签的数据先跑个聚类看看形态几乎成了常规动作。但问题也出在这——很多人一上来就KMeans(n_clusters3)跑完画个散点图就算交差结果换批数据就翻车。聚类算法不是这么用的它背后的选型逻辑、参数含义、评估方式每一项都值得掰开揉碎讲清楚。这篇内容聚焦聚类算法本身重点覆盖 K-Means、层次聚类、DBSCAN 和 GMM 这几类主流方案会讲到它们各自的适用场景、为什么这么选、以及实际跑数据时最容易踩的坑。适合刚接触无监督学习的人也适合那些已经在用但总感觉哪里不对劲的同行。不管你是做用户分群、异常检测还是数据预处理聚类这套东西都绕不开不如一次理顺。1. 聚类算法到底在解决什么问题1.1 一句话理解聚类聚类的目标说起来很简单把相似的数据点归到一起让组内尽量紧凑、组间尽量疏远。但这句话落到实际数据上就有无数种相似的定义方式。欧氏距离算相似还是余弦相似度或者干脆不靠距离靠密度不同的定义方式催生了不同的算法也决定了每种算法的适用边界。我习惯把聚类比喻成收拾房间。K-Means 像是按东西大小分类先大致划几个区域然后反复微调层次聚类像是从每一个单件物品开始逐步把相近的合并进同一个抽屉DBSCAN 则更像按使用频率判断——经常一起用的放一起偶尔出现的孤品自己待着。没有哪种方式绝对正确只有合不合适。1.2 什么时候该用聚类聚类不是万能的但它解决的问题足够典型。最常见的有三类探索性分析数据刚到手没有任何标签想先看看整体结构。比如电商用户的行为数据跑一遍聚类可能自然分出高活跃付费型周末冲动型沉睡用户几类后续精细化运营就有抓手了。异常检测的前置手段密度类聚类算法能天然识别离群点DBSCAN 里那些不属于任何簇的点往往就是异常样本。我在处理交易风控数据时就经常用这个方法先粗筛一遍。特征工程与数据压缩将几千个原始特征聚成几十个簇再用簇中心或簇编号作为新特征喂给下游模型既能降维又能保留一定的非线性结构信息。有一点必须提前泼冷水聚类是无监督的它只能告诉你数据里有几坨、每坨长什么样不会告诉你这些坨叫什么名字。给每个簇打业务标签永远是人的工作算法替代不了。2. 主流聚类算法横向对比2.1 K-Means最快上手的聚类方案K-Means 是绝大多数人接触的第一个聚类算法也是我工作中跑得最多的。它的核心逻辑很朴素随机初始化 K 个中心点计算每个样本到各中心的距离归属到最近的中心然后重新计算每个簇的中心位置反复迭代直到中心不再明显移动。这个算法的优点极其突出快、简单、可解释性强。样本量十万级别的数据几秒钟就能跑完。但它的短板也藏得很深必须预先指定 K。这是 K-Means 最让人头疼的问题后面会专门讲怎么定 K。对初始中心敏感。不同的随机种子可能得到不同的结果所以实际使用中通常要跑多次取最优。倾向于凸形簇。K-Means 用欧氏距离划分边界本质是 Voronoi 划分对月牙形环形这类非凸形状的簇几乎无能为力。对离群点敏感。均值本身不具有稳健性一个极端点就能把整个簇中心拉偏。我见过太多人在 K-Means 上翻车不是算法不行而是没搞明白它的假设前提它假设每个簇是各向同性的高斯分布即球形簇。数据长得不像球就别怪 K-Means 不给力。2.2 层次聚类结果自带树状结构层次聚类分两种自底向上的凝聚法和自顶向下的分裂法。实际中用得最多的是凝聚法先把每个样本当成一个独立的簇然后每次合并距离最近的两个簇直到所有样本都合并成一个簇为止。合并的过程可以用树状图dendrogram完整记录下来。这个算法的价值在于聚类的过程完全透明。你可以从树状图上看到数据从细到粗的整个层级关系然后根据需要的粒度去切一刀得到想要的簇数。不像 K-Means 那样必须提前拍脑袋定 K层次聚类给了你一个后验的选择空间。但层次聚类有个致命弱点时间复杂度高。经典实现是 O(n²) 甚至 O(n³)数据量超过一万条就会明显变慢。而且合并操作不可逆一旦两个簇被合并后面就没有回头路。所以它在小样本、对可解释性要求高的场景里更有优势比如物种分类、文档主题层级归纳。另外层次聚类里簇间距离的定义方式也有讲究。单连接最小距离容易产生细长的链状簇全连接最大距离偏向紧凑的球形簇平均连接居中Ward 法则通过最小化合并后的方差增量来合并通常效果最稳。我个人默认用 Ward。2.3 DBSCAN处理任意形状簇的利器热搜里专门提到 dbscan聚类算法这确实是个值得认真对待的家伙。DBSCAN 的全称是 Density-Based Spatial Clustering of Applications with Noise基于密度的空间聚类。它跟 K-Means 的底层逻辑完全不同——它不关心中心只关心密度连通。核心思想就两句话在半径 eps 范围内如果邻居数超过 min_samples这个点就是核心点核心点的邻居不断往外扩展密度相连的点组成一个簇那些既不是核心点、也够不着任何核心点的样本被标记为噪声。这个机制带来几个 K-Means 给不了的能力可以识别任意形状的簇。环形的、月牙形的、S 形的只要密度连续DBSCAN 都能给你揪出来。天然处理离群点。噪声点被单独标出来这在对脏数据比较敏感的场景里太宝贵了。不需要预先指定簇数。簇的数量由数据密度分布自动决定。但 DBSCAN 的痛点同样明显。eps 和 min_samples 这对参数对结果影响极大而数据密度不均匀时一个全局的 eps 根本不够用——密度高的区域连成一片密度低的区域全被当成噪声。遇到这种情况后续要单独说怎么应对。2.4 高斯混合模型GMMGMM 可以理解为 K-Means 的概率化升级版。K-Means 是硬聚类每个样本非此即彼GMM 假设每个簇服从一个高斯分布然后通过 EM 算法迭代估计每个分布的均值、方差和权重最终给出每个样本属于每个簇的概率。这个概率输出在某些场景下太关键了。比如做用户分群时一个用户可能 60% 像高价值用户、40% 像潜在流失用户这种软归属信息比硬分类丰富得多。而且 GMM 允许不同簇有不同的大小和形状比 K-Means 的球形假设灵活不少。代价是计算量更大、参数更多而且容易陷入局部最优。EM 算法的初始值同样重要需要多跑几次对比。把这几个算法放到一起看选型逻辑就清晰了。算法簇形状簇数指定噪声处理数据规模主要短板K-Means凸形/球形必须指定不友好大对形状和离群点敏感层次聚类较灵活可通过树状图后定一般小计算复杂度高DBSCAN任意形状自动天然支持中密度不均时易失效GMM椭圆形必须指定一般中计算量大易局部最优3. 实操从数据到聚类结果的完整流程3.1 数据准备与预处理很多聚类翻车案例问题不是出在算法而是出在数据进入算法之前。预处理这一步我踩过的坑最深先说三个关键点量纲归一化不是可选项是必选项。K-Means 和 DBSCAN 都依赖距离计算如果特征 A 的取值范围是 0.1 到 0.9特征 B 是 1000 到 90000那 B 几乎会完全主导距离A 相当于白给。我用 StandardScaler 把每个特征标准化到均值为 0、方差为 1这是最常规也最稳的做法。高维数据先降维再看效果。聚类在低维空间的表现通常比高维直观得多。我习惯先用 PCA 降到二维或三维跑一遍聚类后把结果可视化确认簇的分离度再回到原始维度去做正式建模。这不只是为了画图更重要的是能提前发现数据形态问题。离散特征处理要谨慎。类别型特征如果用 one-hot 编码会让特征维度暴涨而且距离计算的意义会变得模糊。经验是如果类别特征很多先考虑是否有必要纳入聚类真要纳入可以用适当的编码方式如 target encoding 或 MDS 降维后的向量替代裸 one-hot。3.2 代码实现三种算法跑一遍直接用 scikit-learn 就能完成绝大多数聚类需求。我把三种算法的核心调用方法写出来顺便标注最容易犯错的地方。import numpy as np import pandas as pd import matplotlib.pyplot as plt from sklearn.datasets import make_blobs, make_moons from sklearn.preprocessing import StandardScaler from sklearn.cluster import KMeans, AgglomerativeClustering, DBSCAN from sklearn.mixture import GaussianMixture # 构造一个人造数据集两个弧形簇 少量噪声 X, y_true make_moons(n_samples500, noise0.08, random_state42) rng np.random.RandomState(42) noise rng.uniform(-1.5, 2.5, size(50, 2)) X np.vstack([X, noise]) X StandardScaler().fit_transform(X)# K-Means kmeans KMeans(n_clusters2, random_state42, n_init10) labels_kmeans kmeans.fit_predict(X) # 层次聚类Ward 连接 hier AgglomerativeClustering(n_clusters2, linkageward) labels_hier hier.fit_predict(X) # DBSCAN dbscan DBSCAN(eps0.3, min_samples5) labels_dbscan dbscan.fit_predict(X) # DBSCAN 中标签为 -1 的样本是噪声# GMM gmm GaussianMixture(n_components2, covariance_typefull, random_state42) labels_gmm gmm.fit_predict(X) proba_gmm gmm.predict_proba(X) # 软归属概率光看调用代码没啥感觉真正有价值的是放在同一张图上对比。以我构造的这个弧形簇 噪声数据为例K-Means 会强行把两个弧形从上到下切成两半层次聚类Ward也好不到哪去但 DBSCAN 能干净地把两个弧形分开并把散布的噪声单独标记出来。这就是直观感受算法差异最好的方式。n_init10是 K-Means 需要特别留意的参数。旧版本默认只跑一次很容易撞上局部最优新版本默认是 10 次取最优。如果你还在用老代码建议显式指定n_init10不要吃默认值的亏。3.3 如何评估聚类效果聚类没有标准答案但这不代表没法评估。分两种情况有真实标签用外部指标最常用的是调整兰德指数ARI和归一化互信息NMI。这两个指标都校正了随机分组的影响1 表示完全一致0 表示接近随机。直接用sklearn.metrics.adjusted_rand_score(y_true, labels)就能算。没有真实标签用内部指标。轮廓系数Silhouette Score是最常用的对每个样本计算它到同簇其他样本的平均距离 a以及它到最近的其他簇样本平均距离 b轮廓系数 (b-a)/max(a,b)。取值在 -1 到 1 之间越接近 1 说明簇内紧凑且簇间分离。from sklearn.metrics import silhouette_score, adjusted_rand_score # 有标签的评估 print(ARI - KMeans:, adjusted_rand_score(y_true, labels_kmeans)) print(ARI - DBSCAN:, adjusted_rand_score(y_true, labels_dbscan)) # 无标签的评估剔除 DBSCAN 的噪声点 mask labels_dbscan ! -1 print(轮廓系数 - DBSCAN:, silhouette_score(X[mask], labels_dbscan[mask]))注意一个细节轮廓系数只在 DBSCAN 剔除噪声后再计算。噪声点的存在会严重拉低轮廓值不剔除的话评估结果没有参考意义。4. 常见问题与调参避坑4.1 K 值到底怎么定这是 K-Means 和 GMM 绕不过去的问题。我的做法是三个方法交叉验证肘部法则把 K 从 1 试到 10记录每个 K 对应的簇内误差平方和SSE画折线图。SSE 随 K 增大必然下降但下降速度会在某个 K 处明显放缓这个肘部就是推荐值。SSE 可以直接用kmeans.inertia_取到。轮廓系数法对每个 K 计算整体轮廓系数取最大值对应的 K。不过轮廓系数偏向小的 K所以我会结合肘部图一起看。业务合理性这是最重要的一条。算出来的 K 再好如果分出来的簇在业务上没法解释、没法落地就得调。比如用户聚类 K3 时每类都有清晰的运营动作K5 时有两类怎么解释都牵强那就用 3。4.2 DBSCAN 的 eps 和 min_samples 怎么调DBSCAN 调参让人头大的点在于eps 是关键参数而它的合适值高度依赖数据尺度和密度分布。这里有两个实用方法K-距离图法对每个样本计算它到第 k 个最近邻居的距离k 通常取 min_samples把这些距离从小到大排序画折线图。曲线会发生突变的位置对应的距离就是合适的 eps。这个方法的原理是簇内的第 k 近邻距离小边缘点和噪声的第 k 近邻距离大突变点就是密度与非密度的分界线。from sklearn.neighbors import NearestNeighbors k 5 nn NearestNeighbors(n_neighborsk).fit(X) distances, _ nn.kneighbors(X) k_dist np.sort(distances[:, -1]) # 每个样本到第 k 近邻的距离 plt.plot(k_dist)曲线出现明显拐弯的地方读取对应的 y 轴值作为 eps 的初始值。然后再往两边微调结合轮廓系数和簇数合理性做最终确定。min_samples 的经验值min_samples 通常取数据维度数的两倍或更大。比如二维数据就取 4~5高维数据可以取 10~20。这个参数影响簇的平滑程度取太小簇会很碎取太大又容易把细密的簇合并或全部标成噪声。4.3 数据密度不均匀怎么办这是 DBSCAN 最大的软肋。一个全局 eps 对高密度区域来说是局部范围太大对低密度区域来说又什么都够不着。我实际遇到过的场景是用户行为数据中头部用户行为密集长尾用户行为稀疏DBSCAN 跑出来头部一片噪声、尾部也一片噪声中间勉强有一两个簇毫无业务价值。应对方案有几种分层处理先用粗粒度的方式进行划分比如按业务维度拆成不同子集每个子集单独调参。这个做法最直接也最容易解释给业务方。密度自适应算法换用 OPTICS 算法它本质上是 DBSCAN 的推广不需要全局 eps而是生成一个可达距离图可以看作对聚类结构的多尺度扫描。sklearn 里有sklearn.cluster.OPTICS代价是更慢。先降噪再聚类先用孤立森林等异常检测方法把明显的异常点去掉再用 DBSCAN 跑剩余数据这样密度分布会更均匀一些。4.4 聚类结果不稳定怎么办K-Means 和 GMM 都有随机初始化的问题同一份数据跑两次结果可能不同这会让业务方质疑你的稳定性。我的经验是固定随机种子random_state42保证结果可复现这在项目交付时是底线。对多个随机种子跑多次计算簇标签的一致性。如果两个随机种子下的结果差异很大说明数据本身的簇结构不清晰这时候要回头审视特征选择和数据质量而不是加个种子硬扛。用层次聚类或 DBSCAN 这类确定性算法做交叉验证。如果 K-Means 的结果和数据密度关系有明显出入通常说明 K 或者预处理方式有问题。5. 什么场景选什么算法想清楚场景再决定算法顺序不能反。我把这些年的选型经验整理成一张速查逻辑数据量大、簇是凸形、业务方就要一个可解释的分群结果K-Means没得说。配合 PCA 可视化调 K 值一套组合拳下来又快又稳。样本量小、需要完整层次关系层次聚类 Ward 连接。比如给一组用户标签做层级归类写成树状图给业务方看比任何其他算法的输出都直观。数据形状不规则、有噪声、不知道分几类DBSCAN 优先。我处理地理位置数据、轨迹数据这类簇形状天知道长什么样的数据时默认先跑 DBSCAN。需要概率归属、簇的大小差异大GMM。比如支付行为数据不同簇的分布范围差几个数量级GMM 的协方差矩阵能更好地刻画这种异质性。数据密度极不均匀先用 OPTICS 或分层处理别指望一个全局 eps 解决所有问题。选型时还有一个容易被忽略的因素解释成本。你用的算法越复杂给业务方讲清楚的难度就越大。在大部分商业场景下K-Means 把用户分成三类均值分别是……这种朴素结论的接受度远高于GMM 拟合出三个高斯分布的贝叶斯后验概率。算法是为决策服务的别为了炫技牺牲可解释性。最后再分享一个小习惯跑聚类之前永远先画图看数据。二维三维能直接看高维就先用 PCA 或 t-SNE 降维再看。数据长什么样该用什么算法心里就有数了。我见过太多人拿着几万字写好的调参策略结果数据本身一团乱麻怎么调都白搭。记住这条原则聚类的路会顺很多。