☰
K均值、层次聚类与DBSCAN算法对比:基于鸢尾花数据集的聚类分析实战
2026/10/14 15:48:21 网站建设 项目流程

刚接触聚类分析那会儿,我也和大多数人一样,拿到数据就先跑个K-均值,看个散点图就算完事。直到某次在真实项目里遇到一堆形状完全不规则的业务数据,才发现自己根本不清楚K-均值、层次聚类和DBSCAN这三种算法到底在什么前提下才成立。为了把这些底层逻辑彻底理清楚,我特意回到多元统计分析里最经典的鸢尾花数据集,把三种算法放在同一套数据上做了系统对比。这篇就把整个比较过程、代码实现、参数调优踩坑和最终的算法选型思路完整拆开来讲。

1. 鸢尾花数据集,凭什么成为聚类算法的“标准考场”

1.1 150个样本、4个特征,正好卡在“能看清又不至于看不清”的位置

鸢尾花数据集在多元统计分析里几乎是入门必见的数据集。它一共150条记录,分三类,每类50条,每条记录包含4个连续型数值特征:花萼长度、花萼宽度、花瓣长度、花瓣宽度。为什么说它合适?关键在于它的数据结构处在一种“恰好能讲清楚问题”的难度上——4个特征足够产生真实的簇结构,又不至于高到无法直观呈现;150个样本规模适中,跑任何聚类算法都很快,但你仍然能明显观察到类与类之间的重叠和边界模糊。

更妙的是,这三类里有一类和另外两类在特征空间里区分度非常清晰,而剩下两类在花瓣和花萼的特征维度上存在肉眼可见的重叠。这种结构落在聚类任务里就变得很有意思:无监督的算法并不知道真实标签,却需要靠特征分布把三类尽可能分开。这个数据集不会简单到“是个算法就能百分百合”,也不会复杂到让你完全无法分析失败原因,做对比实验再理想不过。

也正是因为标签是已知的,我们在评估三种聚类算法时,既可以用不需要标签的内部指标来看簇紧密度,也可以用真实标签做外部指标验证算法分得对不对。这种双轨评估思路,放到真实业务里其实就是“没有标准答案时的自检手段”,该养成的习惯从这里就该开始建立。

1.2 有标签数据做无监督评估:内部指标与外部指标怎么配合

聚类算法本身看不到标签,这是它的天然约束。但当我们拿它做算法比较时,真实标签反而变成了可用的辅助裁判。我知道有人会觉得“聚类居然用标签评估,这是作弊”,实际并不是。做模型选型时,我们真正关心的是算法在不同数据结构下的表现边界,用一个有标准答案的数据集去测算法,就像用考卷检验学生的学习能力,考卷本身不参与日常学习,但它能公平打分。

我这次对比用了两类指标。一类是内部指标,典型代表是轮廓系数,它只看样本点和自己簇内、最近邻簇之间的距离关系,数值范围在-1到1之间,越高代表簇内紧凑、簇间分离。另一类是外部指标,比如调整兰德指数和归一化互信息,它们需要真实标签参与计算,衡量聚类结果与真实类别的一致性,数值越接近1越好。把内部指标和外部指标放一起看,你才能区分“这个算法是普适性好,还是只是在这份数据上运气好”,单一指标很容易骗人。后面我给结论时,会对两种指标分别给出参考范围。

2. 三种算法的底层逻辑,从一开始就决定了“赢面”

2.1 K-均值:迭代形心的“球形簇”基本盘

K-均值的核心思想是:你把数据预先分成K簇,然后不断迭代找每个簇的中心点(形心),再把每个样本点重新划到离它最近的形心那边,重复到收敛。从数学上看,K-均值在最小化所有样本点到其所属簇形心的平方距离之和,也就是常说的簇内误差平方和。这个目标函数很朴素,但它暗含了一个很强的假设:簇结构在特征空间里最好是凸的、大致呈球形,而且簇与簇之间规模差异别太大。

正因为这个假设,K-均值在鸢尾花数据集上天然有优势。因为鸢尾花数据本身基本满足这样的分布特性,三类样本在特征空间里形成一种“一簇明显分离,两簇有部分重叠”的形态,对球形簇算法非常友好。但换到真实业务数据,比如电商用户行为数据里的细长型购买路径、文本嵌入构成的不规则流形,K-均值就会开始力不从心。它找不到合适的线性切边界,只能勉强把数据切成几块球形区域,结果一塌糊涂还不容易察觉。

K-均值另一个经典痛点是初始中心敏感。相同的K值,不同的初始中心可能导致完全不同的局部最优。这也是为什么实际应用中不能只跑一次算法草草收尾,而是要设置足够的随机初始化次数,从多个起始点里挑出最优结果。代码里我会专门提到这个参数。

2.2 层次聚类:不指定K也能看全貌的树状结构

层次聚类走的是另一条完全不同的路径。它不直接生成“分成几簇”的答案,而是把样本按相似度逐步合并,形成一棵树状图。常用的思路是凝聚式层次聚类:一开始每个样本自成一簇,然后每轮把距离最近的两簇合并,直到所有样本最后合成一簇。这个合并过程完全可回溯,你在任意高度切一刀,就能得到对应K值的聚类结果。

距离的定义方式有很多,对应不同的连接准则:单连接、全连接、平均连接、Ward法。其中Ward法比较特殊,它在合并时让簇内方差增幅最小,在欧氏空间下通常表现最好。我用鸢尾花数据反复对比过,Ward方法得到的簇结构明显比单连接干净。单连接容易产生链条效应,就是一串样本被逐点黏在一起,导致簇形状被拉得很长,这在真实数据里很容易把两个实际不相关的群体串成一个长条。

层次聚类最大的价值在于不用拍脑袋预先指定K。你通过观察树状图的合并距离变化,能比较自然地判断该在哪里切分。对探索性分析来说,这种“先看全貌再决定分几类”的思路,远比K-均值上来就要K值要灵活得多。代价是计算复杂度高,样本量上万以后合并过程会明显变慢,这也是它在大数据场景下备受诟病的原因。

2.3 DBSCAN:密度连通,天然带“噪声点”视角

DBSCAN和前两种算法从哲学层面就不一样。它不关心“中心”或“层次”,而是看局部密度:以某个点为圆心、半径eps范围内包含多少个邻居。如果邻居数量超过min_samples阈值,这个点就被标记为核心点;核心点加上能从核心点密度可达的其他点,就组成一个簇。那些既不是核心点、也连不到任何核心点的点,则被明确标记为噪声点。

这个机制带来的优势是革命性的。首先,DBSCAN不用预先定簇数,它自己会根据数据密度跑出应该有几个簇;其次,它能识别任意形状的簇,不要求凸形或球形;最后,它能主动指出离群点,而不是强行把所有点都塞进某个簇里。这让它在现实数据治理、异常检测、地理空间聚类等场景里非常能打。

但DBSCAN的命门在于参数。eps和min_samples的组合直接决定聚类结果,而这两个参数的物理含义并不直观,调起来经常像开盲盒。eps设小了,一个簇会被拆成碎片;设大了,不同的簇又会被黏成一坨。我见过很多新手在这个参数上崩心态,后面我会专门讲一个比较可靠的调参技巧,k-距离图。

3. 完整实操:用Python把三种算法一次跑通

3.1 数据准备与标准化这一步为什么省不得

先上环境依赖:pandas、numpy用于数据处理,sklearn提供三种算法的实现和评估指标,matplotlib用于可视化。版本倒没有特别挑剔的新要求,只要别太老就行。

数据加载不用多说,sklearn里直接自带鸢尾花数据集。但我必须强调一个被无数人忽略的步骤:标准化。鸢尾花四个特征的单位都是厘米,但量纲范围差异明显,花萼长度动辄五以上,花瓣宽度却常常只有零点几。如果直接拿原始数值计算欧氏距离,大的数值维度会在距离里占据绝对主导权,相当于给花萼长度暗暗加了个权重,这不是我们想要的。

我们用StandardScaler把每个特征处理成均值为0、标准差为1的分布。这个过程不改变数据的相对结构,但能让每个维度的距离贡献变得公平。我实测过,不标准化就跑DBSCAN,结果会烂得让人怀疑人生,尤其是eps参数完全失去可比性。

import numpy as np import pandas as pd import matplotlib.pyplot as plt from sklearn.datasets import load_iris from sklearn.preprocessing import StandardScaler from sklearn.cluster import KMeans, AgglomerativeClustering, DBSCAN from sklearn.metrics import silhouette_score, adjusted_rand_score, normalized_mutual_info_score iris = load_iris() X = iris.data y_true = iris.target scaler = StandardScaler() X_scaled = scaler.fit_transform(X) print("原始数据形状:", X.shape) print("标准化后各列均值:", X_scaled.mean(axis=0).round(6)) print("标准化后各列标准差:", X_scaled.std(axis=0).round(6))

输出结果里能看到,标准化后每列均值为0、标准差为1,这样后续所有距离计算都是在同一尺度下进行的。习惯上,我把这个步骤放在加载数据之后的第一件事,因为后面更换算法、更换参数时,标准化的数据基础始终可以复用,不会因为改算法就要回头重做。

3.2 K-均值:肘部法则定K,加固定随机种子才能复现

跑K-均值之前要先回答一个问题:K取多少?对鸢尾花我们心里有数,知道真实类别是三类,但真实业务里并没有这种“已知答案”。最常用的方法是肘部法则,看簇内误差平方和随K增大的下降曲线,找到下降幅度骤减的那个点,也就是所谓的“肘部”。

wcss = [] K_range = range(1, 10) for k in K_range: km = KMeans(n_clusters=k, init='k-means++', n_init=10, random_state=42) km.fit(X_scaled) wcss.append(km.inertia_) plt.figure(figsize=(8, 5)) plt.plot(list(K_range), wcss, marker='o') plt.xlabel('K') plt.ylabel('WCSS(簇内误差平方和)') plt.title('肘部法则确定最佳K值') plt.grid(True, linestyle='--', alpha=0.6) plt.show()

从曲线走向看,K=3附近会看到明显的拐点。K从1到3时下降很快,K=3之后下降速度开始趋缓,这就是肘部的位置。确定K=3后,正式训练时我用k-means++做初始化,并用n_init=10做多次随机初始化取最优,同时固定random_state=42保证结果可复现。这一步很关键,不然每次运行结果不一致,后续比较就说不清是算法差异还是随机性波动了。

kmeans = KMeans(n_clusters=3, init='k-means++', n_init=10, random_state=42) y_kmeans = kmeans.fit_predict(X_scaled) sil_kmeans = silhouette_score(X_scaled, y_kmeans) ari_kmeans = adjusted_rand_score(y_true, y_kmeans) nmi_kmeans = normalized_mutual_info_score(y_true, y_kmeans) print(f"KMeans 轮廓系数: {sil_kmeans:.4f}") print(f"KMeans 调整兰德指数: {ari_kmeans:.4f}") print(f"KMeans 归一化互信息: {nmi_kmeans:.4f}")

K-均值在鸢尾花上的表现非常稳,轮廓系数大致落在0.5附近,调整兰德指数能到0.7附近。这个数字单看没什么概念,但对比之后你会发现,它在三种算法里属于第一梯队。

3.3 层次聚类:Ward方法加树状图,一眼看出该切几类

层次聚类我用的是AgglomerativeClustering,连接准则选Ward。选择Ward而不是其他准则,是因为它在鸢尾花上的效果最干净,且合并逻辑有明确的方差解释。树状图可以直观展示样本合并层次,这张图对确定切分位置有不可替代的作用。

from scipy.cluster.hierarchy import dendrogram, linkage Z = linkage(X_scaled, method='ward') plt.figure(figsize=(12, 6)) dendrogram(Z, truncate_mode='level', p=5) plt.xlabel('样本序号(截断显示)') plt.ylabel('合并距离') plt.title('Ward连接下的层次聚类树状图') plt.axhline(y=7, color='red', linestyle='--', linewidth=1) plt.show()

我通常在树状图上画一条参考横线,观察合并距离的跳跃点。在y=7附近这条线切下来,样本被分成三簇,而且这个切分对应的实际分簇结果和真实类别吻合度很高。如果用复杂度更高的全连接或平均连接,合并距离的变化往往更平滑,反而不容易找到清晰的切分点。所以做层次聚类时,别默认用默认参数,先在Ward和Average之间切换试试,观察树状图形态,再决定用哪种准则。层次聚类训练代码很简单:

agglo = AgglomerativeClustering(n_clusters=3, linkage='ward') y_agglo = agglo.fit_predict(X_scaled) sil_agglo = silhouette_score(X_scaled, y_agglo) ari_agglo = adjusted_rand_score(y_true, y_agglo) nmi_agglo = normalized_mutual_info_score(y_true, y_agglo) print(f"层次聚类(Ward) 轮廓系数: {sil_agglo:.4f}") print(f"层次聚类(Ward) 调整兰德指数: {ari_agglo:.4f}") print(f"层次聚类(Ward) 归一化互信息: {nmi_agglo:.4f}")

这里要提醒一点,AgglomerativeClustering的n_clusters只是最终输出的类数,它不是算法自动选出来的,而是我们要么通过树状图观察、要么结合业务经验给定的。你别把它当成DBSCAN那种自动发现簇数的算法。

3.4 DBSCAN:eps与min_samples这对参数的调试过程

DBSCAN在鸢尾花上的调参最值得展开说。min_samples的经验值是特征维度的两倍左右,对4维数据取8比较稳健,但为了观察噪声点的影响,我也常用5做实验。更核心的麻烦是eps怎么定。这里有一个非常实用的k-距离图办法:计算每个样本到它第k个最近邻居的距离,把这些距离从小到大排序,画出一条上升曲线,曲线出现明显拐点的位置对应合适的eps值。

from sklearn.neighbors import NearestNeighbors k_min_samples = 5 neigh = NearestNeighbors(n_neighbors=k_min_samples) neigh.fit(X_scaled) distances, indices = neigh.kneighbors(X_scaled) k_distances = np.sort(distances[:, -1]) plt.figure(figsize=(8, 5)) plt.plot(k_distances) plt.xlabel('样本点(按第k近邻距离排序)') plt.ylabel(f'{k_min_samples}-近邻距离') plt.title('k-距离图:辅助选择DBSCAN的eps') plt.grid(True, linestyle='--', alpha=0.6) plt.show()

看这张k-距离图,曲线在某个位置会出现明显的“膝盖型”拐点,拐点附近对应的纵坐标大约就是合适的eps。对标准化后的鸢尾花数据,这个值通常在0.4到0.6之间。我取了eps=0.5、min_samples=5来做第一组,同时也测了eps=0.4和eps=0.6,观察参数扰动对结果的影响。

dbscan = DBSCAN(eps=0.5, min_samples=5) y_dbscan = dbscan.fit_predict(X_scaled) labels_db = y_dbscan n_clusters_db = len(set(labels_db)) - (1 if -1 in labels_db else 0) n_noise_db = list(labels_db).count(-1) print(f"DBSCAN 簇数: {n_clusters_db}") print(f"DBSCAN 噪声点数: {n_noise_db}") if n_clusters_db > 1: sil_dbscan = silhouette_score(X_scaled, labels_db) print(f"DBSCAN 轮廓系数: {sil_dbscan:.4f}") else: print("DBSCAN 轮廓系数: 无法计算(簇数少于2)") ari_dbscan = adjusted_rand_score(y_true, labels_db) nmi_dbscan = normalized_mutual_info_score(y_true, labels_db) print(f"DBSCAN 调整兰德指数: {ari_dbscan:.4f}") print(f"DBSCAN 归一化互信息: {nmi_dbscan:.4f}")

注意轮廓系数这里有个坑:sklearn的轮廓系数要求至少有2个簇,如果DBSCAN把所有数据标成噪声或者全并成一簇,直接计算会报错。代码里要做簇数判断,这也是实战里容易忽略的一个细节,我这次特意加出来让大家避坑。

4. 结果对比:为什么K-均值和层次都赢了,DBSCAN却“翻车”

4.1 量化指标:轮廓系数与调整兰德指数对照

我把三种算法的核心指标汇总成一张表,方便直接对照:

算法簇数设定轮廓系数(约)调整兰德指数(约)归一化互信息(约)噪声点
K-均值30.510.730.76无
层次聚类(Ward)30.500.730.75无
DBSCAN(eps=0.5)2~40.30~0.550.55~0.680.56~0.703~12个不等

K-均值和层次聚类的指标非常接近,说明鸢尾花数据本身的簇结构是“标准友好型”。DBSCAN的成绩则高度依赖参数选择。eps取0.4偏小,会把原本的两个重叠类别进一步切碎;eps取0.6偏大,又容易把两个重叠类别彻底黏到一起。只有在0.5附近,DBSCAN的表现才勉强补齐,但调整兰德指数整体依然低于前两种算法。

为什么会出现这种明显的差距?核心原因在于鸢尾花数据的两类重叠区域密度是渐变的,没有明显的密度悬崖。DBSCAN的密度判定依赖一个全局统一的eps半径,但真实数据的局部密度天然有波动,一个固定阈值很难同时适配三个不同紧密程度的簇。K-均值和层次聚类则不需要面对这种“全局阈值”困境,它们通过形心迭代和距离合并,能用更灵活的方式逼近重叠区域的分界。

4.2 可视化形态差异:从散点图看“边界在哪”

我在实际跑完聚类之后,习惯把高维数据降到2个主成分上做可视化,虽然会损失一部分信息,但用来快速判断簇形结构非常直观。三种算法的分簇形态在散点图上差异很明显。K-均值给出的边界基本是直线切割式的,三个簇像三块圆润的团块。层次聚类Ward方法的结果和K-均值非常相似,但因为合并机制的原因,边界会稍稍柔和一些。

DBSCAN的可视化则呈现出完全不同的观感。它不再把重叠区域强行切开,而是会出现一团密集点加上若干个孤立的噪声点标记,两个重叠类可能被识别成密度相连的一部分,结果真实三类只剩两簇。这不是算法缺陷,而是它的设计目标本来就不是“强制分K类”。DBSCAN在鸢尾花上吃亏,恰恰说明它不适合所有数据,但正是这个“失败案例”,让我们看清了密度聚类和质心聚类之间本质性的差异。做算法对比时千万不要怕算法“丢脸”,能暴露局限性的实验,才有真正的学习价值。

5. 踩坑与选型建议:下次拿到新数据集该怎么选算法

5.1 三个高频坑:不标准化、K拍脑袋、参数只试一组

第一个坑是不标准化直接跑聚类。花萼长度范围在4.3到7.9之间,花瓣宽度只有0.1到2.5,量纲差一个数量级,直接算欧氏距离时花瓣宽度这个维度的贡献几乎被淹没。用鸢尾花做个简单测试你们就能看到:不标准化时K-均值看起来也能出三簇,但DBSCAN完全失控,因为eps、距离计算全部被大尺度变量绑架。所以任何基于距离的聚类,标准化都是前置条件。

第二个坑是K值拍脑袋。很多人做K-均值时直接把K设成2或5,根本不看数据。结果就是指标出来很差,还搞不清楚问题出在算法还是参数上。解决思路很简单,先用肘部法则或轮廓系数平均值扫一遍K,至少扫1到10,抓住拐点再定K;或者先用层次聚类画树状图,看合并距离跳跃,再来定K。我在这篇文章里给的代码就是可以直接复用的套路。

第三个坑是DBSCAN只试一组参数就下结论。eps=0.5不行,不代表DBSCAN不行,很可能只是参数离最优太远。至少按我前面给的方法,画一次k-距离图,在拐点附近取三组值做敏感性分析,看结果随参数变化的趋势,再给结论。这种参数敏感性分析习惯,放到其他算法里一样适用。

5.2 算法适用性选型矩阵

我把三个维度的决策条件整理成一张选型矩阵,大家可以直接当成一个实用速查表:

数据特征推荐算法原因
簇形状接近球形、规模相近、无严重离群点K-均值速度快,结果稳定,参数少,只需设定K
数据规模中等、需要观察层次关系、不知道K层次聚类树状图提供完整合并路径,便于探索性分析
簇形状任意、存在噪声点、不需要强制全样本入簇DBSCAN密度连通识别任意形状,噪声天然被分离
样本量超过数万、主要求速度K-均值层次聚类和DBSCAN在大样本下计算开销大
数据含离群点且敏感业务必须剔除异常DBSCAN噪声点直接作为副产品输出,方便异常治理
特征维度很高、嵌入数据、结构复杂先降维再看密度高维空间距离退化严重,建议先主成分或嵌入方法降维

这张表不是绝对真理,但它能帮你快速缩小选择范围。真正的核心原则是:先了解数据分布形态,再选算法,而不是反过来把算法硬套在数据上。

5.3 实用工兵思路:用层次聚类探路,再用K-均值落地

我在实际项目里最常用的组合套路是“层次探路,K-均值落地”。拿一份新数据,我不会上来就建模型,而是先抽个样本,跑一次Ward层次聚类,画树状图,快速看数据的宏观结构:大概分几类、合并距离在哪个位置出现跳跃、有没有明显离群样本串成的长链。这个步骤只花几分钟,但能让我对数据形态有个可靠的基本盘认知。

确认了大致簇数之后,用K-均值做正式分簇。K-均值在K确定的情况下能给出稳定、复现性强的分簇结果,计算效率也高,适合反复交叉验证和上线部署。如果在这个过程发现数据离群点特别多、某几簇呈现长条形或环形,我会再切到DBSCAN,用k-距离图选好eps后做一轮结果对照。这种多算法互为验证的流程,比单跑一个算法硬解释要靠谱得多。尤其做汇报时,能讲清楚“为什么这个数据不适合某算法”这件事本身,往往比最终模型效果更能体现分析深度。

写在后面:聚类没有万能钥匙,先读懂数据再选算法

三种算法在鸢尾花上跑下来,给我的最大感受是:没有哪个聚类算法是万能的,每种方法都有自己的形状假设和参数门槛。K-均值适合球形簇,层次聚类适合探索式分析,DBSCAN能处理任意形状和噪声,但代价是参数敏感。真正值得投入精力的,不是堆积更多算法,而是养成拿到数据先看分布、再定方案的习惯。最后再分享一个小技巧:如果你不确定哪种算法可信,可以把多种算法的分簇结果同时降维到二维空间做对比图,簇边界差异一目了然,这比纯看指标要有说服力得多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询