简介一套基于鸢尾花数据集的三种聚类算法 Python 代码包面向机器学习初学者与数据分析人员用于掌握无监督学习中的 K-Means、合并聚类和 DBSCAN并通过同一份数据直观对比不同算法的聚类效果。资源既包含三种算法的核心实现代码也给出在鸢尾花数据集上的可直接运行示例同时附有 Word 版说明文档系统讲解各算法的原理步骤与关键差异例如 K-Means 需要预置簇数且受初始中心影响合并聚类通过相似度逐层聚合DBSCAN 则依赖邻域半径与最小样本数识别任意形状簇。压缩包共 6 个文件以 .py 源码和 .doc 说明文档为主整体仅 433KB轻量便携下载解压后即可对照运行或二次改进适合课堂教学、课程设计和自学实践。目前已有 2320 人学习下载代码注释清晰、输出结果易于追踪是快速上手并落地聚类算法的实用资源。1. 三种聚类算法在同一份鸢尾花数据上做对照这份代码到底在验证什么做聚类实验的人十有八九第一份数据就是鸢尾花Iris。150 条样本、4 个特征、3 个类别小到能逐行检查又刚好能暴露不同聚类算法之间的本质差异。k均值、合并聚类和 DBSCAN 对同一份 Iris 数据集聚类表面上只是三个脚本并排跑实际上回答的是三个完全不同的问题数据里有没有球形簇、簇能否按层次结构合并、密度不均时边界在哪里。这篇笔记把三类算法的代码、参数和评估口径拆开讲适合正在赶机器学习作业、或者想把手头无标签数据快速跑通一遍的从业者。看完你能直接得到三张聚类图、一套评估指标并且知道每个结果怎么解释、哪里最容易翻车。2. 鸢尾花数据集与算法选型为什么三套逻辑差异这么大的算法都拿它当试金石2.1 Iris 数据集的结构与“有标签却做无监督”的实验边界鸢尾花数据集由 Fisher 在 1936 年整理sklearn里直接load_iris()就能拿到。150 条样本分成 setosa、versicolor、virginica 三类每类 50 条每条有花萼长度、花萼宽度、花瓣长度、花瓣宽度四个数值特征。这里有个容易混淆的点在无监督聚类任务里这 150 个真实标签是被“封存”的算法在训练阶段看不到类别标签只在最后评估时拿出来做对照。from sklearn.datasets import load_iris import pandas as pd iris load_iris() X iris.data # 特征矩阵150x4单位是厘米 y iris.target # 真实标签0/1/2仅用于事后评估 feature_names iris.feature_names df pd.DataFrame(X, columnsfeature_names) df[label] y print(df.describe())逻辑说明load_iris()返回一个 Bunch 对象data是特征矩阵target是整数标签。上面把数据转成 DataFrame 是为了先看分布——花萼长度在 4.3 到 7.9 之间花瓣长度在 0.1 到 6.9 之间意味着花瓣特征的方差远大于花萼特征。这个量纲差异直接决定后面的 DBSCAN 必须做标准化否则 eps 参数会完全失效。实验边界在于我们拿着有标签的数据做无监督目的是对比算法在“不知道答案”时的表现。真实标签不能参与任何训练参数的选择比如不能因为知道有三类就把 K-Means 的 K 硬定为 3然后说模型很准——这在教学演示里常见但实际落地时你的数据往往根本没有标签K 要靠轮廓系数等内部指标来选。2.2 三种算法的划分逻辑从“先定 K”到“密度可达”K-Means 的思路是预先指定 K 个簇随机初始化 K 个中心然后迭代两步——把每个样本分给最近的中心再重新计算中心位置直到中心不再变化。它对簇形状的默认假设是“凸的、近似等方差的球形簇”这也是它处理 Iris 时天然吃亏的地方Iris 里 versicolor 和 virginica 两类在特征空间中有重叠实心球假设会让边界处的样本被随机劈开。合并聚类Agglomerative Hierarchical Clustering走的是另一条路一开始每个样本自己是一个簇然后按距离不断合并最近的两个簇直到剩下一个簇。整个过程形成一棵树你可以在任意高度“切一刀”得到任意数量的簇。它的优势是不用提前定 K而是看树状图决定在哪里切缺点是计算复杂度高150 条样本没问题几万条样本就要考虑用linkageward配合距离矩阵的优化实现。DBSCAN 完全抛弃了“中心”和“树”的概念。它把样本分成核心点、边界点和噪声点三类如果一个点的 eps 邻域内样本数不少于 min_samples它就是核心点核心点之间通过“密度可达”连成簇落单的样本就是噪声。DBSCAN 不需要指定簇个数能发现任意形状的簇还能显式标出离群点。代价是两个参数 eps 和 min_samples 对结果极其敏感而且在高维数据上“密度”这个概念会退化。三种算法在 Iris 上的对照本质上是在问同一个问题这份数据的簇结构到底更像球形、层次型还是密度型Iris 的正确答案是“近似球形但有部分重叠”所以 K-Means 和 ward 合并聚类通常表现接近DBSCAN 则要看参数配得怎么样。这个判断对实际项目很有参考价值——拿到新数据时先想清楚簇的形状假设再选算法比逐个试错快得多。2.3 预期表现与核心难点setosa 好分另两类才是真正的分水岭把三种算法放在同一份数据上本质是做一次受控对照实验。Iris 的第一个类别 setosa 和其他两类线性可分所以几乎所有算法都能把它干净地分出来真正的难点在 versicolor 和 virginica这两类在花瓣特征上有明显重叠区域。预先知道这个结构对调参很有帮助如果聚类结果连 setosa 都分错了说明代码有 bug 或者特征没有标准化如果只是 versicolor 和 virginica 混在一起那是数据本身重叠导致的贝叶斯误差不是算法实现的问题。我一般建议先用seaborn.pairplot或散点矩阵看一眼原始特征分布再跑算法否则你连“这个结果对不对”都判断不了。比如花瓣长度和花瓣宽度两个特征就能把三类分得七七八八而花萼宽度单独拿出来几乎没有区分度。这意味着如果你用全部 4 个特征做聚类花萼宽度反而会引入噪声让重叠变得更严重。实践里不少人只取花瓣长度和花瓣宽度两个特征跑聚类效果往往比四维全量更好这并不是偷懒而是特征选择本身就嵌在聚类流程里。3. 用 Python 跑通三类算法的核心代码参数怎么设、结果怎么看3.1 K-MeansK 值选择、随机初始化与聚类中心解读K-Means 在 sklearn 里的调用非常成熟但有两个新手最容易忽略的参数n_init和random_state。n_init表示用多少组不同的随机中心去跑最后保留代价函数最小的那组结果默认是 10random_state固定随机种子让结果可复现。后面避坑章节会专门讲这个问题这里先跑通。from sklearn.cluster import KMeans from sklearn.preprocessing import StandardScaler scaler StandardScaler() X_scaled scaler.fit_transform(X) kmeans KMeans(n_clusters3, initk-means, n_init10, random_state42, max_iter300) kmeans.fit(X_scaled) print(聚类标签:, kmeans.labels_[:20]) print(聚类中心:, kmeans.cluster_centers_)逻辑说明先标准化再聚类是这里的关键一步。K-Means 用欧氏距离衡量样本相似度如果花瓣长度方差大和花萼宽度方差小不做标准化前者会在距离计算里占据绝对主导相当于你只用了两个特征。initk-means是默认也推荐的初始化方式它让初始中心尽量分散减少落到局部最优的概率。参数说明n_clusters3在本实验里是为了和真实三类做对照实际无标签场景应该换用轮廓系数或其他内部指标来确定 K后面第四章会讲max_iter300是单次迭代上限Iris 这种小数据几十次迭代就收敛了不需要动random_state42保证每次跑出来完全一致。运行完可以看两个东西一是kmeans.labels_和真实标签的对应关系注意聚类标签的编号是任意的0/1/2 和真实类别的对应可能要映射直接数一致数会误以为准确率很低二是聚类中心在标准化空间里的坐标如果配合scaler.inverse_transform把它还原成原始单位能得到“每个簇的平均花萼长度、平均花瓣长度”这类可解释的结论。3.2 合并聚类三种 linkage 策略在 Iris 上的差异合并聚类的核心参数是linkage它决定“两个簇之间的距离”怎么定义。常见的三种ward用合并后簇内方差增量倾向生成大小相近的球状簇complete用两个簇最远样本的距离对噪声敏感average用所有样本对距离的平均介于两者之间。Iris 上建议直接用ward它和 K-Means 的假设最接近也是实际项目中默认首选。from sklearn.cluster import AgglomerativeClustering from scipy.cluster.hierarchy import dendrogram, linkage import matplotlib.pyplot as plt # 直接聚类 agg AgglomerativeClustering(n_clusters3, linkageward) agg_labels agg.fit_predict(X_scaled) # 画树状图需要先用 scipy 重新算 linkage 矩阵 Z linkage(X_scaled, methodward) plt.figure(figsize(10, 6)) dendrogram(Z, truncate_modelevel, p5) plt.title(Ward 合并聚类的树状图截断显示) plt.ylabel(簇间距离) plt.show()逻辑说明AgglomerativeClustering适合直接拿聚类结果但它不保留树状图的中间过程要看树得用scipy.cluster.hierarchy.linkage单独算一次 Z 矩阵。dendrogram画出来的横轴是样本索引纵轴是合并时的距离距离越大说明两个簇越晚被合并、差异越大。参数说明truncate_modelevel, p5表示只显示最上面 5 层合并因为 150 个样本的完整树状图横轴会挤成一团。树状图在 Iris 上的典型特征是setosa 那一支在很低的距离就独立成形versicolor 和 virginica 在高得多的距离才分开这个距离差就是后面判断“该切几刀”的依据。一个常见误用直接用AgglomerativeClustering的n_clusters3而不看树状图等于放弃了合并聚类最大的优势。正确的做法是先画树状图看最大的几个“高度落差”在哪里再回到n_clusters那一刀。Iris 上通常在高度约 5 到 8 的位置有明显的分叉切在那里得到 3 簇左右最合理。3.3 DBSCANeps 和 min_samples 的参数配对方法DBSCAN 的两个参数比 K-Means 的 K 难调得多。eps是邻域半径min_samples是成为核心点的最少样本数。经验上min_samples先取2 * 特征维度这里特征维度是 4所以先取 8 左右eps则要画 k-distance 图来确定——计算每个样本到它第min_samples个最近邻居的距离排序后找拐点。from sklearn.cluster import DBSCAN from sklearn.neighbors import NearestNeighbors import numpy as np # 方法一直接跑先给一组保守参数 db DBSCAN(eps0.5, min_samples8).fit(X_scaled) print(簇标签-1 表示噪声:, db.labels_) print(簇个数:, len(set(db.labels_)) - (1 if -1 in db.labels_ else 0)) # 方法二k-distance 图辅助定 eps nn NearestNeighbors(n_neighbors8).fit(X_scaled) distances, _ nn.kneighbors(X_scaled) k_dist np.sort(distances[:, -1]) # 每个样本到第 8 近邻居的距离 plt.plot(k_dist) plt.xlabel(样本序号按距离排序) plt.ylabel(第 8 近邻距离) plt.title(k-distance 图拐点处对应合适 eps) plt.show()逻辑说明k-distance 图的原理是簇内样本的第 8 近邻距离普遍较小噪声样本的距离会突然变大图上会出现一个明显的“肘部”这个肘部对应的纵坐标就是合理的 eps。Iris 标准化之后肘部通常在 0.4 到 0.8 之间所以上面先用 0.5 试跑。参数说明eps0.5对标准化后的 Iris 通常能分出 2 到 3 个簇但很容易把 versicolor 和 virginica 的一部分标成噪声min_samples8偏大时噪声点变多偏小时会把边界样本也变成核心点簇的边界变得破碎。DBSCAN 另一个特点是它的标签里-1是噪声统计簇个数时必须先排除-1这个细节很多人头一次跑都会漏。DBSCAN 在 Iris 上的表现通常弱于 K-Means 和 ward 合并聚类原因是 Iris 的类分布更接近“球形加部分重叠”而不是“密度不均的任意形状”。这不是 DBSCAN 的缺陷而是它的适用场景不同——如果你的数据有大量离群点、簇形状是长条形或月牙形DBSCAN 才会明显胜出。3.4 一张图跑通三种算法聚类结果并排可视化的完整脚本把上面三段捏成一个脚本输出三张子图是最常见的交付形态。可视化时注意两点一是用 PCA 降维后的轴而不是原始特征轴Iris 四个特征里取前两个 PCA 分量通常能解释 95% 以上的方差画出来更干净二是给每个子图标注算法名和簇个数不要把三个图混在一起。from sklearn.decomposition import PCA pca PCA(n_components2, random_state42) X_pca pca.fit_transform(X_scaled) fig, axes plt.subplots(1, 3, figsize(15, 4)) results [ (K-Means, kmeans.labels_), (合并聚类(ward), agg_labels), (DBSCAN, db.labels_) ] for ax, (name, labels) in zip(axes, results): scatter ax.scatter(X_pca[:, 0], X_pca[:, 1], clabels, cmapviridis, s20, alpha0.8) ax.set_title(name) ax.set_xlabel(PC1) ax.set_ylabel(PC2) ax.legend(*scatter.legend_elements(), title簇) plt.tight_layout() plt.show()逻辑说明PCA(n_components2)把四维标准化特征压成两维X_pca只是用于画图聚类本身仍然在四维的X_scaled上完成顺序不要搞反。三个算法都使用同一个X_scaled保证对比公平。参数说明cmapviridis是色盲友好的配色s20控制点大小alpha0.8让重叠的点稍微透明。如果 DBSCAN 的图里出现大片灰色-1 噪声说明 eps 太小或 min_samples 太大回到 k-distance 图重新取值。跑完这张图你就能直观看到 K-Means 和 ward 的结果几乎相同DBSCAN 则多出若干噪声点——这个对比本身就是结论。4. 聚类效果评估轮廓系数、ARI 与 NMI 怎么搭配使用4.1 无监督内部指标轮廓系数能告诉你什么没有真实标签时只能用内部指标评估聚类质量。轮廓系数Silhouette Coefficient是最常用的一种对每个样本计算它到同簇其他样本的平均距离 a以及到最近其他簇样本的平均距离 b轮廓系数就是 (b - a) / max(a, b)。取值范围在 -1 到 1 之间越接近 1 说明簇内紧致、簇间分离。from sklearn.metrics import silhouette_score, calinski_harabasz_score, davies_bouldin_score for name, labels in [(K-Means, kmeans.labels_), (合并聚类, agg_labels), (DBSCAN(含噪声), db.labels_)]: # DBSCAN 的 -1 噪声样本需要剔除否则指标会失真 if -1 in labels: mask labels ! -1 sc silhouette_score(X_scaled[mask], labels[mask]) else: sc silhouette_score(X_scaled, labels) print(f{name}: 轮廓系数{sc:.3f})逻辑说明轮廓系数的输入是特征矩阵和聚类标签不需要真实标签。上面代码处理了一个隐蔽问题DBSCAN 的噪声点-1会被误认为一个簇导致轮廓系数计算失真所以先mask掉。实际报结果时DBSCAN 要同时报告“剔除噪声后的轮廓系数”和“噪声占比”两个数。参数说明calinski_harabasz_score也叫方差比准则值越大越好davies_bouldin_score值越小越好。这三个内部指标在 Iris 上通常给出一致的排序K-Means 和 ward 合并聚类接近DBSCAN 剔除噪声后三者接近但 DBSCAN 的噪声率会拖累它的工程可用性。轮廓系数的局限也要讲清楚它只衡量几何紧致度完全不看真实标签。一个极端例子是把 Iris 按花瓣长度中位数一分为二轮廓系数可能很高但这两半里都混着三个真实类别——所以内部指标只能用来筛参数和诊断不能用来证明“聚类找到了真实结构”。4.2 有监督对照指标ARI 与 NMI 的正确解读因为 Iris 有真实标签我们还能用外部指标做“开卷考试”。调整兰德指数ARI和归一化互信息NMI都考虑了随机划分的基线取值范围最大为 1接近 0 表示和随机划分差不多。ARI 对簇结构更敏感NMI 对类别分布更鲁棒实践里两个都报。from sklearn.metrics import adjusted_rand_score, normalized_mutual_info_score for name, labels in [(K-Means, kmeans.labels_), (合并聚类, agg_labels), (DBSCAN, db.labels_)]: if -1 in labels: mask labels ! -1 ari adjusted_rand_score(y[mask], labels[mask]) nmi normalized_mutual_info_score(y[mask], labels[mask]) print(f{name}: ARI{ari:.3f}, NMI{nmi:.3f} (噪声样本 {np.sum(labels -1)} 个已剔除)) else: ari adjusted_rand_score(y, labels) nmi normalized_mutual_info_score(y, labels) print(f{name}: ARI{ari:.3f}, NMI{nmi:.3f})逻辑说明adjusted_rand_score把聚类结果和真实标签做排列匹配它自动处理了标签编号不一致的问题——聚类把 setosa 标成 0 还是 2 都不影响得分。在 Iris 上 K-Means 的 ARI 通常在 0.7 左右不是 0.99因为 versicolor 和 virginica 的重叠区域样本无论怎么分都会被判错这是数据本身的贝叶斯误差。参数说明NMI 的默认平均方式是arithmetic样本不平衡时可以考虑geometric这两个指标对 DBSCAN 噪声的处理必须显式说明剔除噪声样本后再算否则等于默认把噪声当作一个簇得分会虚高或虚低。这里要顺便纠正一个常见误解聚类“准确率”不是随便算的。如果直接把聚类标签和真实标签逐位对比因为标签编号错位准确率可能只有 30%但这不代表算法差。ARI 已经隐式做了标签匹配所以报告里优先用 ARI 而不是手工匹配后的准确率。周志华《机器学习》里对聚类性能度量也是分外部指标和内部指标两类讲这个分类本身就是行业通行的口径。4.3 三个指标怎么组合使用一套实用的判读顺序指标要用组合拳不能单看一个。我一般按这个顺序判读先看轮廓系数低于 0.4 说明聚类结构本身弱先回头检查标准化和特征选择再看 ARI它告诉你和真实标签的吻合度在 Iris 这种有标准答案的数据上最有说服力最后看噪声占比DBSCAN 超过 20% 的噪声就基本不可用了。这套判读顺序在无标签数据上要稍作调整没有 ARI 就用轮廓系数加簇个数一起看再结合业务方对“簇的语义”的判断。比如一个簇如果同时包含高价值和低价值客户即使几何上很紧致业务上也没有意义——聚类最终要服务于解释不是服务于分数。算法建议主指标辅助指标关注点K-Means轮廓系数、ARI簇内平方和(SSE)K 值是否合理是否陷入局部最优合并聚类树状图高度差、ARI轮廓系数在哪个高度切linkage 选对没有DBSCAN噪声占比、ARI簇个数稳定性eps 是否在平台期噪声是否可解释上面这个表是我做实验报告时的固定格式。每个算法两到三个指标不多报因为每多一个数字就要多解释一层报告反而难读。评估还有一个实际作用参数调优。比如 DBSCAN 的 eps 在 0.2 到 1.5 之间扫一遍每个值跑一次聚类、算一次轮廓系数和 ARI画出曲线就能看到参数变化的敏感区间。这个“参数—指标”曲线比单点调参可靠得多第六节会给具体代码。5. 避坑指南鸢尾花聚类最容易翻车的六个细节5.1 现象K-Means 每次运行结果完全不一样第一次跑 K-Means 没设random_state第二次跑簇标签变了甚至某个样本的归属在两个簇之间横跳。原因是 K-Means 对初始中心敏感不同随机种子可能收敛到不同的局部最优。原因n_init虽然会跑多组初始中心但随机种子不同多组中心的组合也不同最终选出的最优解可能有微差。在 Iris 这种有重叠的数据上边界样本的归属本来就模糊随机性就会被放大。解决固定random_state42或其他任意整数让实验可复现如果要做多种子实验也要固定一组种子列表并记录每个种子的代价函数值。正式交付的代码里不设随机种子的 K-Means 是不允许上线的因为你没法向别人解释“为什么昨天跑的和今天跑的不一样”。5.2 现象DBSCAN 画出来要么全是噪声要么只有一个大簇eps0.2跑出来满屏灰色噪声eps2.0跑出来所有样本合为一类中间值怎么调都不对。原因特征没有标准化。原始特征里花瓣长度的方差远大于花萼宽度欧氏距离被花瓣长度主导eps 的尺度完全错乱。这是 DBSCAN 最常见的失败方式比 K-Means 严重得多因为 K-Means 好歹会用 k-means 缓解一下DBSCAN 对距离尺度没有任何补救。解决先StandardScaler标准化再用 k-distance 图找拐点。标准化后 eps 的合理区间一般在 0.3 到 1.5 之间扫参时优先在这个区间画曲线。记住一个经验DBSCAN 调参前先问自己一句“所有特征是否已经缩放到同一量纲”这一步能省掉一半的调参时间。5.3 现象合并聚类的树状图横轴挤成一团根本看不清150 个样本的完整树状图横轴密密麻麻标签叠在一起无法判断该在哪里切。原因完整树状图在样本量大于 50 时横轴就会拥挤这是可视化问题不是算法问题。很多人第一次看到这团“黑毛线”就以为代码写错了其实只是没做截断显示。解决用truncate_modelevel, p5只显示顶层合并或者把dendrogram的orientation改为横放。更重要的是不要靠肉眼数树状图的分支而是看合并高度差量化判断——相邻合并的高度差最大的地方就是最佳切割点。可以顺手把Z矩阵里的高度列打印出来用np.diff找最大落差的位置。5.4 现象轮廓系数很高但 ARI 很低结果自相矛盾K-Means 跑出轮廓系数 0.6结果 ARI 只有 0.5感觉算法“高分低能”。原因轮廓系数衡量的是簇的几何紧致度不关心簇是否匹配真实类别。如果真实类别有重叠算法可以切出几何上非常漂亮的球形簇但把两个真实类别的边缘样本归错了——几何指标照样给高分。解决有标签就用 ARI/NMI 做主指标轮廓系数做诊断辅助无标签时承认轮廓系数有局限结合簇个数和业务可解释性判断。不要拿几何指标证明“聚类很准”这就像说一个盒子排列整齐所以里面装的东西一定是对的逻辑上站不住。5.5 现象DBSCAN 结果里噪声点特别多簇却只有一个有时 eps 怎么调DBSCAN 都倾向于把一大部分样本标为噪声剩下的粘成一个簇看不到“多簇”结构。原因Iris 标准化后密度分布比较均匀没有明显的密度分离DBSCAN 在这种数据上本就表现一般。它在“密度有高低起伏”的数据上才发挥优势Iris 不是它的主场。解决换用 K-Means 或 ward 合并聚类做主打DBSCAN 只作为“离群点检测器”使用看它标出的噪声是否对应真实的异常样本。如果业务上确实要 DBSCAN 出多簇考虑先降维或改用 HDBSCAN但这是另一个方向了。强行让 DBSCAN 在 Iris 上出三个簇等于逼它做不擅长的事结果一定别扭。5.6 现象三维散点图看不出聚类效果颜色混在一起用matplotlib的Axes3D画了三维散点图转半天角度也看不清簇边界。原因三维可视化把四个特征压成三个重叠区域的视觉遮蔽反而比二维更严重旋转带来的信息量不足以抵消透视变形。而且三维图在纸面和屏幕上展示时深度方向的遮挡会让读者误判簇的分离程度。解决Iris 这种低维数据用 PCA 降到二维画图比三维图清晰得多如果非要用三维固定一个视角并分别画簇的轮廓不要指望单张图说明问题。真正的簇结构判断交给指标图片只是给读者看的证据——这也是为什么第四章的指标表比任何一张散点图都重要。6. 进阶把三类算法变成一份可复现、可交付的聚类比对报告6.1 封装一个统一的聚类流程函数散落的脚本只能自己跑交付给团队或写进实验记录就需要封装。我习惯把“标准化 → 聚类 → 评估 → 可视化”包成一个函数传入算法名和参数返回指标字典和图像。这样换数据集、换参数都只改一行。def run_clustering(name, model, X, y, pca_components2): from sklearn.preprocessing import StandardScaler scaler StandardScaler() Xs scaler.fit_transform(X) labels model.fit_predict(Xs) noise_mask labels ! -1 if -1 in labels else None # 统一指标计算入口 metrics {} if noise_mask is not None: metrics[noise_ratio] (labels -1).mean() eff_labels labels[noise_mask] eff_X Xs[noise_mask] eff_y y[noise_mask] else: eff_labels, eff_X, eff_y labels, Xs, y metrics[silhouette] silhouette_score(eff_X, eff_labels) metrics[ari] adjusted_rand_score(eff_y, eff_labels) metrics[nmi] normalized_mutual_info_score(eff_y, eff_labels) metrics[n_clusters] len(set(eff_labels)) # PCA 投影用于画图 pca PCA(n_componentspca_components, random_state42) Xp pca.fit_transform(Xs) return metrics, Xp, labels逻辑说明函数内部先标准化再聚类保证调用方不会忘记缩放noise_mask把 DBSCAN 的特殊情况统一处理掉噪声占比作为额外指标返回。pca_components默认 2画图用聚类始终在标准化后的原始维度上做PCA 只影响可视化。参数说明model是已经配好参数的 sklearn 估计器对象调用fit_predict时保持接口一致。这个函数的输出是(metrics, Xp, labels)三元组便于在循环里攒成表格。调用方可以这样用model KMeans(n_clusters3, random_state42)一行就能拿到指标和绘图数据。6.2 参数网格扫描用一张折线图定 DBSCAN 的 eps与其一次次改参数重跑不如把 eps 的候选值列出来一次跑完同时记录轮廓系数和 ARI你会直观看到参数敏感区间。这个做法同样适用于 K-Means 扫 K 值——把n_clusters从 2 到 8 各跑一遍画一条“K—轮廓系数”曲线取拐点处的 K。candidates np.linspace(0.2, 1.5, 20) sil_scores, ari_scores, n_clusters_list [], [], [] for eps in candidates: db DBSCAN(epseps, min_samples8) labels db.fit_predict(X_scaled) mask labels ! -1 if len(set(labels[mask])) 2 or mask.sum() 10: sil_scores.append(np.nan) ari_scores.append(np.nan) else: sil_scores.append(silhouette_score(X_scaled[mask], labels[mask])) ari_scores.append(adjusted_rand_score(y[mask], labels[mask])) n_clusters_list.append(len(set(labels)) - (1 if -1 in labels else 0)) # 画双轴折线图 fig, ax1 plt.subplots() ax1.plot(candidates, sil_scores, o-, label轮廓系数) ax1.plot(candidates, ari_scores, s--, labelARI) ax1.set_xlabel(eps) ax1.set_ylabel(得分) ax2 ax1.twinx() ax2.bar(candidates, n_clusters_list, alpha0.2, label簇个数) ax2.set_ylabel(簇个数) plt.legend() plt.show()逻辑说明代码里加了两个保护条件——有效样本少于 10 个或者只有 1 个簇时指标没有意义直接记为 NaN。twinx()生成双 y 轴左边是得分右边是簇个数一眼就能看出“eps 多大时簇个数稳定、得分最高”。参数说明np.linspace(0.2, 1.5, 20)把 eps 在 0.2 到 1.5 之间均匀取 20 个值这一步把离散的调参变成连续观察min_samples8保持之前定好的值。实际跑出来的典型结果是eps 在 0.4 附近 ARI 出现峰值之后簇个数快速下降这个“肩部”就是最优区间不需要追求唯一精确值。6.3 用一份表格把结果落成报告以及三个值得再试的方向聚类实验做完交付物不是图而是一张汇总表。我通常按算法一行、指标一列组织簇个数、轮廓系数、ARI、NMI、噪声占比。Iris 这份数据跑完典型结果是 K-Means 和 ward 的 ARI 都在 0.7 左右DBSCAN 调好参数后 ARI 接近但噪声占比 5% 左右。这张表写进实验记录谁都能复现——这才是“聚类代码.zip”真正该有的价值。如果做完这份对照还想往深走我给你三个方向。第一把特征维度从 4 个缩减到 2 个只留花瓣长宽重新跑一遍三套算法你会看到 ARI 普遍提升这能帮你理解特征选择对聚类的真实影响。第二给数据人为加 10 个离群点再看 DBSCAN 和 K-Means 的表现差异——DBSCAN 会把这 10 个点全部标成噪声K-Means 则会硬把它们塞进最近的簇里这个对比比任何理论都直观。第三换一份形状完全不同的数据集比如 sklearn 的make_moons你会看到 DBSCAN 和 K-Means 的排名直接反转。做聚类这份数据多年我最深的体会是三种算法跑在同一份 Iris 上比的不是谁更准而是谁更适合问题的形状假设。K-Means 适合球形簇加快速迭代合并聚类适合需要解释层次关系的场景DBSCAN 适合密度不均和噪声明确的数据。调参之前先问一句数据长什么样比任何参数搜索都管用。每次拿到一份新的无标签数据我都会先画分布、定形状假设、再选算法这套流程已经成了反射动作。希望这些踩过的坑和封装好的流程能帮到你让你拿到一份聚类代码时不是把它当黑匣子跑完就扔而是能拆开、能调参、能解释、能复现。本文还有配套的精品资源点击获取
