K均值、合并聚类与DBSCAN在鸢尾花数据集上的聚类实践对比

发布时间:2026/9/14 9:12:31
K均值、合并聚类与DBSCAN在鸢尾花数据集上的聚类实践对比 简介面向机器学习初学者与聚类分析实践者的算法教学代码包聚焦鸢尾花数据集上的无监督聚类任务。资源共6个文件压缩包大小433KB包含5个Python脚本和1个Word文档脚本分别实现和演示K-Means、凝聚式层次聚类Agglomerative Clustering与DBSCAN三种算法在鸢尾花数据上的聚类流程Word文档则对算法原理、参数选择和结果分析做系统梳理。已有2320人学习下载。通过对照代码可直观理解K-Means对初始化质心的敏感性、合并聚类的层级合并过程、DBSCAN中epsilon与minPts的调参影响同时也能学习数据标准化、距离计算、聚类结果可视化的典型写法适合课程实验、期末复习或入门机器学习时快速跑通三种主流聚类方法。1. 鸢尾花数据集上k均值、合并聚类和DBSCAN聚类代码的取舍下载“k均值、合并聚类和DBSCAN聚类算法对鸢尾花数据集聚类代码”这类压缩包的人多数是想把三种算法放在同一份数据上跑一遍好直观感受差异。跑完第一眼通常感到意外k均值分成三类合并聚类分成三类DBSCAN却可能只分出一类剩下样本全被标成噪声。这不是代码写错了而是三种算法对“簇”的定义不同。k均值假设簇是凸的近似球状合并聚类通过距离递归合并或切割层次DBSCAN完全依赖局部密度密度不连续处就被判为噪声。鸢尾花数据集共150个样本、4个特征、3个真实类别其中setosa在特征空间和其他两类完全分开versicolor与virginica却又存在特征重叠正好能把三种算法的偏好暴露得清清楚楚。这篇博文从算法假设讲起给出pandas读取鸢尾花数据集、特征标准化、三种聚类建模调参、到轮廓系数与ARI评估的完整聚类代码路径适合刚接触无监督学习的读者也适合要快速拿聚类结果做基线比对的工程师。2. 三种聚类算法的核心逻辑与选型理由2.1 距离度量三种算法共享的底层标尺k均值、合并聚类、DBSCAN在scikit-learn里默认都用欧氏距离。欧氏距离有一个容易被忽视的前提参与运算的特征必须大致处于同一量纲否则数值范围大的特征会在距离公式里占据支配地位让聚类结果被那个特征牵着走。鸢尾花数据四个特征单位都是厘米量级看起来一致实际方差差异不小特征最小值最大值标准差sepal length (cm)4.37.90.83sepal width (cm)2.04.40.43petal length (cm)1.06.91.76petal width (cm)0.12.50.76petal length的标准差大约是sepal width的四倍如果直接拿原始特征算距离petal length就成了最大的权重维度。无论选哪种算法我一般先把这步验证一遍感受量级差距import numpy as np from sklearn.datasets import load_iris X load_iris().data # 两个样本在原始特征空间里的欧氏距离 dist_raw np.linalg.norm(X[0] - X[1]) print(原始特征欧氏距离:, dist_raw)这段代码用numpy的linalg.norm计算两个样本向量差的L2范数也就是欧氏距离。样本量级固定后后续标准化的必要性会更直观如果特征标准差从0.43到1.76不等距离计算已经隐含了特征加权。这就是为什么三种算法在同一个数据集上出现不同结果第一步排查要先看输入数据而不是算法参数。2.2 k均值球状簇假设下的迭代优化k均值的工作流程是随机初始化k个质心把每个样本分配到距离最近的质心再计算每个簇的均值作为新质心反复迭代直到质心移动量小于阈值。优化目标是簇内样本到质心的距离平方和sklearn里记为inertia_。这个目标隐含两个假设簇近似球形、规模相近。如果真实簇是狭长形或嵌套结构k均值会强行用中垂面把它们切开。在鸢尾花数据上setosa和另外两类距离足够远k均值能干净分离versicolor和virginica存在特征重叠k均值在重叠区用一个平面硬切分界面和真实物种边界不一定重合。这就是为什么k均值在鸢尾花上通常能达到90%上下的准确率但很难再往上走错分样本基本都落在versicolor和virginica的交界区域。如果业务数据是这种形态优先考虑密度类算法或混合模型而不是纠结k均值调参。2.3 合并聚类从叶子到根的层次合并合并聚类AgglomerativeClustering初始把每个样本看成一簇然后反复合并距离最近的两簇。簇间距离由linkage参数决定linkage计算方式适用形态ward合并后簇内方差增量最小球形簇结果接近k均值complete两簇间最远样本对的距离受离群点影响较大average两簇间所有样本对平均距离折中方案计算量略大single两簇间最近样本对的距离容易产生链式粘连ward在鸢尾花数据上的结果和k均值重合度很高因为两者都在做“簇内离差最小化”。complete和average的差异主要出现在重叠区域重叠样本可能被分到不同簇。single则经常出现一条细长链把两个真实类串在一起看到树状图上某两簇被一个样本逐个连接就是典型的链式效应。遇到这种结构不要用single做最终结论可以先借树状图观察样本间的亲疏关系。2.4 DBSCAN当密度定义一切DBSCAN用eps和min_samples两个参数定义密度eps是邻域半径min_samples是半径内需要达到的最少样本数。满足条件的是核心点落在核心点半径内但自身不满足条件的是边界点两者都不满足的是噪声点。这个机制适合三类问题簇形状不规则的数据、密度差异明显的数据、明确需要过滤离群点的业务数据。鸢尾花数据集样本只有150条versicolor与virginica重叠区没有明显的密度凹陷DBSCAN很容易把这两个类别看成同一个密度连通域。尤其在标准化后的空间里重叠区的eps会像桥一样把两簇接起来。因此DBSCAN在鸢尾花上往往表现不如k均值这是数据形态与算法定位不匹配的结果。如果业务场景里存在簇形状复杂或离群点较多的需求优先考虑DBSCAN其余情况先用k均值和合并聚类做基线再根据轮廓系数判断是否换算法。3. 用pandas读取鸢尾花数据集iris并完成特征标准化3.1 最小依赖四个库就能跑完全部聚类代码三种聚类算法完整跑下来只需要scikit-learn、pandas、scipy、matplotlib四个库。scikit-learn提供KMeans、AgglomerativeClustering、DBSCAN和评估指标pandas负责读取表格数据scipy的cluster.hierarchy模块用来画树状图matplotlib出图。pip install scikit-learn pandas scipy matplotlib安装完成后先打印版本确认依赖没有装到过老的版本import sklearn import pandas import scipy import matplotlib print(sklearn:, sklearn.__version__) print(pandas:, pandas.__version__) print(scipy:, scipy.__version__)如果四行版本号能被正常打印环境就可以支撑下面的代码。需要留意的兼容点是sklearn的AgglomerativeClustering在较新版本里参数名从affinity改成了metric老教程里的代码直接复制会报unexpected keyword。这类差异在升级sklearn时经常遇到先跑一段最小代码验证环境比对着文档查配置更省时间。3.2 用pandas读取鸢尾花数据集iris并检查数据直接从sklearn的数据集模块加载省去手动下载csv的步骤import pandas as pd from sklearn.datasets import load_iris iris load_iris(as_frameTrue) df iris.frame df[species] iris.target_names[iris.target] print(df.head()) print(df.shape) print(df.isnull().sum())as_frameTrue让sklearn返回带列名的DataFrame省掉把numpy数组转DataFrame那行代码。head()查看前5行结构shape确认行数和列数isnull().sum()判断是否存在缺失值。鸢尾花数据集在pandas里的结构很干净检查项结果样本行数150特征列数4类别数3setosa / versicolor / virginica每类样本数50 / 50 / 50缺失值0数据越干净三种聚类算法的差异就越纯粹地来自算法本身。如果用真实的业务数据做同样对比缺失值、量纲差异、类别不均衡都会干扰判断所以这类示例数据适合用来建立算法选择的直觉。3.3 z-score标准化聚类前必做的预处理检查数据后把特征矩阵和标签分开。标签用来做外部评估但聚类过程中不使用。from sklearn.preprocessing import StandardScaler X df[[sepal length (cm), sepal width (cm), petal length (cm), petal width (cm)]].values y df[target].values scaler StandardScaler() X_scaled scaler.fit_transform(X) print(标准化前 petal width 标准差: {:.4f}.format(X[:, 3].std())) print(标准化后 petal width 标准差: {:.4f}.format(X_scaled[:, 3].std()))StandardScaler对每个特征列做z-score变换输出均值为0、方差为1。标准化之后四个特征在欧氏距离中的贡献一致三种聚类算法的输入基准完全相同。k均值对标准化尤其敏感因为inertia直接由距离平方和决定DBSCAN的eps是一个绝对半径特征缩放后eps的含义会彻底改变合并聚类中的ward基于方差增量同样受量纲影响。自己写聚类代码时标准化这一步建议固定为流程的一部分而不是临时看情况补上。注意fit_transform同时完成“估计均值和方差”以及“执行变换”两步。聚类场景中这不会引入信息泄露但如果后续要用同样的scaler处理新样本必须先保存scaler对象再调用transform。4. k均值、合并聚类和DBSCAN在鸢尾花数据集上的建模与调参4.1 k均值用肘部法则先确定k再固定随机种子k均值必须指定k值。鸢尾花数据集有真实标签三分类但无监督场景下不应该直接使用这个外部信息。最基础的做法是画肘部图观察inertia随k值的变化import matplotlib.pyplot as plt from sklearn.cluster import KMeans inertia [] k_range range(1, 9) for k in k_range: model KMeans(n_clustersk, random_state42, n_init10) model.fit(X_scaled) inertia.append(model.inertia_) plt.plot(list(k_range), inertia, markero) plt.xlabel(k) plt.ylabel(Inertia) plt.title(鸢尾花数据集KMeans肘部图) plt.show()inertia等于样本到质心的距离平方和k越大inertia必然单调下降。实际调试中看的是曲线的弯曲点k从1增加到2、从2增加到3时下降幅度明显k3之后下降变得平缓这个拐点就是合理的k。固定random_state42保证结果可复现n_init10让每次初始化跑10轮取最优避免单个坏质心把结果拖入局部最优。确定k后建立正式模型kmeans KMeans(n_clusters3, random_state42, n_init10) kmeans_labels kmeans.fit_predict(X_scaled)fit_predict在训练的同时返回每个样本的簇标签和先fit再predict的效果一致。k均值调参的关键就三个k的选择、n_init是否足够、random_state是否固定。很多代码里省略n_init在sklearn 1.2之后默认行为会根据数据量自动选择但对150样本的小数据显式指定10次最稳妥。4.2 合并聚类linkage参数决定树形态合并聚类把n_clusters当作树状图的切割层数调参重点是linkage和metricfrom sklearn.cluster import AgglomerativeClustering hc AgglomerativeClustering( n_clusters3, linkageward, metriceuclidean ) hc_labels hc.fit_predict(X_scaled)linkageward按合并后方差增量最小选择簇对metriceuclidean计算样本间距离。ward只支持欧氏距离其他linkage可选manhattan、cosine等。想观察合并过程用scipy画树状图from scipy.cluster.hierarchy import dendrogram, linkage Z linkage(X_scaled, methodward) plt.figure(figsize(9, 5)) dendrogram(Z, truncate_modelevel, p5) plt.title(鸢尾花数据集合并聚类树状图) plt.show()dendrogram的truncate_modelevel表示只显示顶层5层p5控制树的纵向深度。小数据集可以直接展示完整树数据量上万时截断是更实用的做法。对照4.1节ward的结果和k均值接近本来就是同样的方差最小化逻辑想测试别的簇间距离定义把linkage换成average或complete再跑一遍即可。4.3 DBSCAN先看k-distance曲线再网格搜索DBSCAN的两个参数直接决定簇的数量和噪声比例。最常用的参数起点是k-distance曲线计算每个样本到第k个最近邻的距离按升序排列后画折线曲线上拐点对应的距离就是eps的参考值。from sklearn.neighbors import NearestNeighbors neigh NearestNeighbors(n_neighbors5) neigh.fit(X_scaled) distances, _ neigh.kneighbors(X_scaled) k_dist np.sort(distances[:, -1]) plt.plot(k_dist) plt.title(k-distance曲线第5近邻距离排序) plt.show()n_neighbors5对应min_samples5distances[:, -1]取的是“第5个最近邻”的距离因为kneighbors返回的距离矩阵包括自己第0列是自己到自己的0距离最后一列才是有效值。排序后纵坐标快速抬升的位置就是拐点。拿到eps参考值后再做参数网格扫描from sklearn.cluster import DBSCAN for eps in [0.3, 0.4, 0.5, 0.6]: for min_samples in [3, 5, 8]: db DBSCAN(epseps, min_samplesmin_samples) labels_tmp db.fit_predict(X_scaled) n_clusters_tmp len(set(labels_tmp) - {-1}) n_noise_tmp sum(labels_tmp -1) print(feps{eps:.1f}, min_samples{min_samples}, f簇数{n_clusters_tmp}, 噪声{n_noise_tmp})输出中如果某个组合得到3个簇、且噪声数量很少这个参数就是相对合理的。注意set(labels_tmp)里会包含-1这个噪声标签统计簇数前必须把它去掉。EPS和min_samples对DBSCAN的影响方向恰好相反eps过大合并簇过小制造噪声min_samples过大时核心点减少过小时一个孤立点也可能成为核心点。4.4 聚类的参数对照调参时先看结果往哪个方向偏三种算法调参时面对的约束不同整理成对照表方便记忆参数算法作用调参方向n_clustersk均值 / 合并聚类期望簇数肘部点或业务确定n_initk均值初始化次数设为10以上减少局部最优random_statek均值固定随机种子复现和分析稳定性时固定linkage合并聚类簇间距离定义ward优先观察链式结构用singleepsDBSCAN邻域半径从k-distance曲线的拐点起步min_samplesDBSCAN核心点最小邻域样本数小数据集取3到5如果网格搜索里没有一个DBSCAN参数组合能同时满足“3个簇”和“低噪点率”不要为了凑数而继续上调eps。把eps调大到所有点都聚成一类DBSCAN就退化成一种密度版的合并聚类失去了噪声识别的意义。报告结果时应该记录最优组合是什么以及它在噪声率和簇数之间的取舍而不是强行追求和真实标签一致。5. 轮廓系数、ARI与PCA三种聚类结果的评估与对比5.1 内部评估轮廓系数衡量紧致与分离聚类没有标签时最常用的内部评估指标是轮廓系数。对每个样本计算a到自身簇内样本的平均距离和b到最近邻簇样本的平均距离硅胶系数为(b - a) / max(a, b)最终取所有样本的平均值。from sklearn.metrics import silhouette_score results { k均值(k3): kmeans_labels, 合并聚类(ward): hc_labels, DBSCAN(eps0.5,min_samples5): dbscan_labels } for name, labels in results.items(): s silhouette_score(X_scaled, labels) print(f{name}: 轮廓系数 {s:.4f})轮廓系数的取值范围在-1到1之间越接近1说明簇越紧致且分离度越高。在鸢尾花数据集上k均值和合并聚类的轮廓系数通常能到0.5上下说明这三类在标准化后的空间里确实存在可分离结构DBSCAN的得分取决于参数选择噪声点比例高时轮廓系数会明显下降。但轮廓系数只回答“聚类内部结构是否自洽”它不回答“聚类结果是否符合真实类别分布”。两个类别交错但被算法切成几个小圆球轮廓系数反而可能很高。所以内部评估只能过滤明显差的结果不能单独作为最终结论。5.2 外部评估用真实标签计算ARI和NMI鸢尾花数据集的优势是拥有真实标签可以做外部评估。调整兰德指数ARI对样本对的聚类一致程度进行计数并校正随机分组的影响取值1表示与真实分类完全一致0表示随机水平。标准化互信息NMI衡量两个划分的互信息归一化值对类别数量不均衡的数据更稳健。from sklearn.metrics import adjusted_rand_score, normalized_mutual_info_score for name, labels in results.items(): ari adjusted_rand_score(y, labels) nmi normalized_mutual_info_score(y, labels) print(f{name}: ARI {ari:.4f}, NMI {nmi:.4f})在鸢尾花数据集中常见参考范围如下算法轮廓系数ARINMIk均值k30.52 ~ 0.560.85 ~ 0.900.74 ~ 0.78合并聚类wardk30.48 ~ 0.520.80 ~ 0.850.70 ~ 0.75DBSCAN良好参数下0.35 ~ 0.550.60 ~ 0.900.60 ~ 0.85以上是多次运行中常见的参考区间具体值和sklearn版本、随机种子有关。k均值错分的样本集中在versicolor与virginica交界处ARI到0.9附近就封顶了DBSCAN参数好时能接近k均值参数差时降到0.6以下。如果一次运行连0.8都到不了先检查是否忘了标准化、k值是否选错、eps是否偏离了k-distance曲线的拐点。5.3 PCA降维可视化能看趋势别当精确边界把聚类标签画到二维平面最常用的降维方式是PCA。PCA把数据投影到方差最大的方向上保留的信息量能覆盖原始数据的大部分方差。from sklearn.decomposition import PCA pca PCA(n_components2) X_pca pca.fit_transform(X_scaled) fig, axes plt.subplots(1, 3, figsize(14, 4)) for ax, (name, labels) in zip(axes, results.items()): ax.scatter(X_pca[:, 0], X_pca[:, 1], clabels, cmapviridis, edgecolork, s35) ax.set_title(f{name} 聚类结果) ax.set_xlabel(PC1) ax.set_ylabel(PC2) plt.tight_layout() plt.show()在PC1和PC2的投影平面上setosa位于左下方且与其他两类间距明显versicolor和virginica在右上方有重叠区域。对比三张图时重叠区域的着色差异就是算法分歧的直观来源。但要注意PCA是线性投影高维空间里两个点在这个投影下可能贴上在其他方向上却相隔很远可视化只能作为辅助验证最终结论必须回到量化指标。6. 聚类代码落地时的三个验证细节6.1 用两个随机种子验证k均值的稳定性k均值对初始质心敏感哪怕n_init10不同random_state之间也可能出现完全不同的簇划分。验证方式是把两个种子的结果做一次ARI对比from sklearn.cluster import KMeans from sklearn.metrics import adjusted_rand_score labels_seed42 KMeans(n_clusters3, random_state42, n_init10).fit_predict(X_scaled) labels_seed0 KMeans(n_clusters3, random_state0, n_init10).fit_predict(X_scaled) print(两个种子的ARI:, adjusted_rand_score(labels_seed42, labels_seed0))ARI等于1.0表示两个种子给出的簇划分完全一致说明数据结构的信号足够强如果明显低于1.0说明聚类结果依赖随机初始化这时把n_init提升到50甚至100让算法挑选inertia最小的解稳定性会好很多。6.2 从k-distance曲线读取eps而不是拍脑袋DBSCAN调参最常见的错误是直接从0.1开始试要么噪声点一大堆要么全并成一类。正确顺序是先确定min_samples常用5画出k-distance曲线找到拐点再以拐点附近的值作为eps基准。如果拐点不明显说明数据没有明显的密度断裂带DBSCAN可能根本不是合适的选择。这个判断本身就是调参过程中最有价值的输出。6.3 把簇中心反标准化回原始单位k均值聚类得到的cluster_centers_位于标准化后的坐标空间直接输出很难解读。用scaler的反变换还原到原始量纲才能看出每个簇在各维度上的实际均值centers_original scaler.inverse_transform(kmeans.cluster_centers_) center_df pd.DataFrame(centers_original, columnsiris.feature_names) print(center_df.round(2))输出的是每个簇在sepal length、sepal width、petal length、petal width四个维度上的均值比如簇0的花瓣长度均值是1.46簇1是4.26簇2是5.55。这组数值可以直接和业务方确认簇0是“小花瓣”簇1是“中等花瓣”簇2是“大花瓣”。聚类结果的反向解释往往比图表更贴近实际决策因为数值回到了原始单位方便直接写入报告或对接后续流程。本文还有配套的精品资源点击获取