简介:这份资源面向机器学习初学者与需要巩固无监督学习基础的开发者,围绕鸢尾花数据集给出三种经典聚类算法的完整实现,帮助读者理解不同聚类思路的差异与适用场景。压缩包共6个文件,以5个Python脚本和1份doc说明文档为主,整体约433KB,脚本分别对应k均值、合并聚类与DBSCAN的实现及在鸢尾花数据上的调用示例,文档则对算法原理与流程做补充讲解。内容涵盖k均值质心迭代、凝聚式层次合并、DBSCAN密度可达与参数设置等核心知识点,便于读者对照代码观察聚类效果并比较各算法优缺点。目前已有2321人学习下载,适合希望快速上手聚类实践、为后续项目选型积累经验的学习者参考。
1. 三套聚类代码跑同一份鸢尾花:为什么结果差这么多
鸢尾花数据集只有 150 行、4 个特征,很多人第一次做聚类就拿它练手,跑完 k 均值发现准确率还行,换成 DBSCAN 直接崩了,合并聚类又不知道参数怎么设。问题不在数据,在于三种算法对“簇”的定义根本不同:k 均值假设簇是球形且大小相近,合并聚类按距离阈值逐步合并,DBSCAN 靠密度连通性找任意形状的簇。同一份鸢尾花,k 均值在标准化后能拿到 0.9 左右的调整兰德指数,DBSCAN 在默认参数下可能把大部分点判成噪声,合并聚类则对链接方式极度敏感。这篇笔记把三套代码的落地路径拆开,从数据标准化、参数搜索到结果评估,每一步都给可复现的命令和参数说明,适合已经会用 sklearn 但被聚类结果不稳定困扰的从业者。
2. 数据准备与标准化:鸢尾花聚类的第一道分水岭
2.1 加载鸢尾花并确认特征量纲
鸢尾花数据集在 sklearn 里直接可用,但很多人忽略了一个事实:四个特征的单位都是厘米,量纲一致,所以不做标准化也能跑。但 DBSCAN 和合并聚类对距离敏感,标准化后结果更稳。我一般先加载数据,打印描述性统计,确认没有缺失值。
from sklearn.datasets import load_iris import pandas as pd iris = load_iris() X = iris.data y = iris.target df = pd.DataFrame(X, columns=iris.feature_names) print(df.describe()) print("标签分布:", pd.Series(y).value_counts().to_dict())这段代码加载数据后输出统计量。load_iris()返回的data是 150×4 的数组,target是 0/1/2 三分类标签。describe()看均值和标准差,如果某个特征标准差远大于其他,标准化就有必要。鸢尾花四个特征的标准差都在 0.2 到 0.8 之间,量纲差异不大,但花瓣宽度和萼片长度的均值差了近 10 倍,标准化后距离计算更均衡。
2.2 标准化与不标准化的对比实验
标准化用StandardScaler,把每个特征变成均值 0、方差 1。这一步对 k 均值影响不大,但对 DBSCAN 的eps参数影响很大——不标准化时eps要设到 0.5 以上,标准化后 0.3 到 0.5 就够。
from sklearn.preprocessing import StandardScaler scaler = StandardScaler() X_scaled = scaler.fit_transform(X) print("标准化前均值:", X.mean(axis=0).round(2)) print("标准化后均值:", X_scaled.mean(axis=0).round(2)) print("标准化后标准差:", X_scaled.std(axis=0).round(2))fit_transform先算均值和方差再变换。标准化后每列均值接近 0,标准差接近 1。注意:聚类是无监督任务,标准化要用全部数据拟合,不能像分类那样分训练测试集。如果后续要评估,用调整兰德指数(ARI)对比真实标签,但标准化必须用全量数据。
提示:如果鸢尾花数据是从 xlsx 文件读的,列名可能带空格或中文,先
df.columns = df.columns.str.strip()再取.values,否则 sklearn 会报特征名不匹配。
3. k 均值聚类:簇数怎么定、初始化怎么选
3.1 k 均值的核心参数与肘部法
k 均值只有两个关键参数:n_clusters和n_init。n_clusters决定簇数,n_init是不同初始化跑几次取最优。sklearn 从 1.4 开始n_init默认是'auto',等价于 1 次,但实际用的时候我建议显式设成 10,避免陷入局部最优。
from sklearn.cluster import KMeans import matplotlib.pyplot as plt inertias = [] K_range = range(1, 11) for k in K_range: km = KMeans(n_clusters=k, n_init=10, random_state=42) km.fit(X_scaled) inertias.append(km.inertia_) plt.plot(K_range, inertias, marker='o') plt.xlabel('簇数 k') plt.ylabel('簇内平方和') plt.title('肘部法确定 k') plt.show()inertia_是每个点到其簇中心的距离平方和。k 增大时 inertia 单调下降,拐点就是候选 k。鸢尾花真实有 3 类,肘部法通常在 k=3 附近出现明显拐弯。random_state=42保证每次初始化一致,方便复现。n_init=10表示跑 10 次不同初始中心,取 inertia 最小的那次。
3.2 用轮廓系数验证 k 值
肘部法靠肉眼,轮廓系数更客观。轮廓系数范围 -1 到 1,越接近 1 说明簇内越紧、簇间越远。
from sklearn.metrics import silhouette_score for k in range(2, 7): km = KMeans(n_clusters=k, n_init=10, random_state=42) labels = km.fit_predict(X_scaled) score = silhouette_score(X_scaled, labels) print(f"k={k}, 轮廓系数={score:.4f}")fit_predict返回每个样本的簇标签。silhouette_score计算全量样本的平均轮廓系数。鸢尾花在 k=2 时轮廓系数往往最高,因为其中两类在特征空间有重叠,k 均值强行分 3 类会拉低分数。这时候要结合业务判断:如果已知有三类,就选 k=3,轮廓系数只作参考。
3.3 k 均值结果评估与混淆矩阵
聚类没有标签,但鸢尾花有真实标签,可以用 ARI 和混淆矩阵看聚类效果。
from sklearn.metrics import adjusted_rand_score, confusion_matrix km = KMeans(n_clusters=3, n_init=10, random_state=42) labels = km.fit_predict(X_scaled) ari = adjusted_rand_score(y, labels) print("调整兰德指数:", round(ari, 4)) print("混淆矩阵:") print(confusion_matrix(y, labels))ARI 取值 -1 到 1,0 表示随机,1 表示完全一致。鸢尾花上 k 均值标准化后 ARI 通常在 0.6 到 0.75 之间。混淆矩阵看哪些类被混了——通常 versicolor 和 virginica 互相错分,因为这两个类在花瓣长度和宽度上重叠。confusion_matrix的行是真实标签,列是聚类标签,聚类标签顺序和真实标签顺序不一定对应,看的时候要手动对齐。
4. 合并聚类:链接方式决定成败
4.1 凝聚层次聚类的三种链接准则
合并聚类(凝聚层次聚类)从每个点一个簇开始,每次合并距离最近的两个簇。关键参数是linkage:ward最小化合并后的方差,complete用两组间最远点距离,average用平均距离。鸢尾花上ward通常最好,因为它在欧氏距离下倾向于产生大小相近的球形簇。
from sklearn.cluster import AgglomerativeClustering for linkage in ['ward', 'complete', 'average']: ac = AgglomerativeClustering(n_clusters=3, linkage=linkage) labels = ac.fit_predict(X_scaled) ari = adjusted_rand_score(y, labels) print(f"linkage={linkage}, ARI={ari:.4f}")AgglomerativeClustering不需要指定n_init,因为它是确定性算法。ward只能配合欧氏距离,complete和average可以配合其他距离但 sklearn 只支持欧氏。跑完对比 ARI,ward在鸢尾花上一般能到 0.7 以上,average可能只有 0.5 左右。
4.2 树状图与距离阈值
合并聚类可以画树状图看合并过程,用 scipy 的dendrogram。
from scipy.cluster.hierarchy import dendrogram, linkage import matplotlib.pyplot as plt Z = linkage(X_scaled, method='ward') plt.figure(figsize=(10, 6)) dendrogram(Z, truncate_mode='lastp', p=20) plt.title('ward 链接树状图') plt.xlabel('样本索引或簇大小') plt.ylabel('合并距离') plt.show()linkage返回一个 4 列的矩阵,每行记录一次合并。dendrogram画树状图,truncate_mode='lastp'只显示最后 20 个合并,避免 150 个叶子挤在一起。看树状图找最大的垂直距离,在那个位置切一刀就是簇数。鸢尾花在距离 10 左右切能得到 3 簇。
4.3 合并聚类的计算复杂度与适用边界
合并聚类的时间复杂度是 O(n³)(朴素实现)或 O(n² log n)(优化后),空间 O(n²)。150 行数据无所谓,但上万行就吃不消。我一般只在数据量小于 5000 时用合并聚类,再大就换 k 均值或 DBSCAN。另外合并聚类一旦合并就不能撤销,早期错误合并无法修正,这是它和 k 均值最大的区别。
注意:
AgglomerativeClustering的distance_threshold和n_clusters只能设一个。如果设distance_threshold,n_clusters必须为None,否则报错。
5. DBSCAN:密度聚类在鸢尾花上的翻车与调参
5.1 DBSCAN 的两个核心参数:eps 和 min_samples
DBSCAN 靠eps(邻域半径)和min_samples(邻域内最少点数)定义核心点。核心点的 eps 邻域内至少有 min_samples 个点,密度可达的点连成簇,不满足的判为噪声。鸢尾花上默认eps=0.5、min_samples=5经常把大量点判成噪声,因为标准化后点间距离集中在 0.5 到 1.5 之间。
from sklearn.cluster import DBSCAN from sklearn.neighbors import NearestNeighbors import numpy as np neighbors = NearestNeighbors(n_neighbors=5) neighbors.fit(X_scaled) distances, indices = neighbors.kneighbors(X_scaled) distances = np.sort(distances[:, 4], axis=0) plt.plot(distances) plt.xlabel('样本排序') plt.ylabel('第 5 近邻距离') plt.title('k-距离图确定 eps') plt.show()NearestNeighbors(n_neighbors=5)找每个点的 5 个最近邻,kneighbors返回距离和索引。取第 5 列(索引 4)排序后画图,拐点就是eps候选值。鸢尾花标准化后拐点通常在 0.6 到 0.8 之间。min_samples一般设成特征数加 1,鸢尾花 4 个特征就设 5。
5.2 用网格搜索找 DBSCAN 最优参数
手动调参太慢,写个循环遍历 eps 和 min_samples。
best_ari = -1 best_params = {} for eps in np.arange(0.3, 1.2, 0.1): for min_samples in [3, 5, 7, 10]: db = DBSCAN(eps=eps, min_samples=min_samples) labels = db.fit_predict(X_scaled) n_clusters = len(set(labels)) - (1 if -1 in labels else 0) if n_clusters < 2: continue ari = adjusted_rand_score(y, labels) if ari > best_ari: best_ari = ari best_params = {'eps': round(eps, 2), 'min_samples': min_samples} print("最优参数:", best_params) print("最优 ARI:", round(best_ari, 4))fit_predict返回标签,-1 表示噪声。len(set(labels))算簇数时要排除 -1。如果簇数小于 2 就跳过,因为 ARI 对单簇无意义。鸢尾花上最优 eps 通常在 0.6 到 0.8,min_samples 在 5 到 7,ARI 能到 0.5 到 0.6,比 k 均值低,因为 DBSCAN 会把边界点判成噪声。
5.3 DBSCAN 噪声点处理与结果解读
DBSCAN 的噪声点不是错误,是密度不够的点。鸢尾花里 versicolor 和 virginica 交界处的点经常被标为 -1。
db = DBSCAN(eps=0.7, min_samples=5) labels = db.fit_predict(X_scaled) n_noise = list(labels).count(-1) print(f"噪声点数: {n_noise}, 占比: {n_noise/len(labels):.2%}") print("各簇样本数:", pd.Series(labels).value_counts().to_dict())list(labels).count(-1)数噪声。如果噪声占比超过 30%,说明 eps 太小或 min_samples 太大。鸢尾花上合理噪声占比在 5% 到 15%。噪声点可以单独拿出来看,往往就是两类重叠区域的样本。实际业务里噪声点可能代表异常样本,不要直接丢掉,先分析来源。
6. 避坑与排查:三种聚类算法最容易翻车的五个地方
6.1 标准化做了但没对齐特征顺序
现象:从 xlsx 读的鸢尾花数据,列顺序和 sklearn 不一致,聚类结果完全乱套。原因:xlsx 里列可能是“萼片长、萼片宽、花瓣长、花瓣宽”,但 sklearn 是“萼片长、萼片宽、花瓣长、花瓣宽”——顺序一样,但有人手动改过列名或删过列。解决:读入后打印df.columns和iris.feature_names对比,用df = df[iris.feature_names]强制对齐,再.values转数组。
6.2 k 均值 n_init 用默认值导致结果不稳定
现象:同样的数据和 k,跑两次结果不一样,ARI 差 0.1 以上。原因:sklearn 1.4 之前n_init默认 10,之后改成'auto'只跑 1 次,初始化不好就陷局部最优。解决:显式写n_init=10或n_init=20,并固定random_state。如果数据量大,用init='k-means++'加速收敛。
6.3 DBSCAN 的 eps 用默认 0.5 导致全判噪声
现象:DBSCAN 跑完所有点都是 -1,或者只有一个簇加一堆噪声。原因:标准化后点间距离分布变了,默认 eps=0.5 太小,核心点条件不满足。解决:画 k-距离图找拐点,或者网格搜索 eps 从 0.3 到 1.2。min_samples 从 3 开始试,不要一上来设 10。
6.4 合并聚类 ward 链接配合非欧距离报错
现象:AgglomerativeClustering(linkage='ward', affinity='manhattan')直接抛异常。原因:ward 链接的数学推导基于欧氏距离,sklearn 限制只能配affinity='euclidean'。解决:要用曼哈顿距离就换linkage='average'或'complete',但效果可能下降。鸢尾花上老老实实用 ward + euclidean。
6.5 用 ARI 评估时聚类标签和真实标签顺序不对应
现象:混淆矩阵看起来全错,但 ARI 很高。原因:聚类标签是任意分配的,簇 0 可能对应真实类 2,簇 1 对应真实类 0。解决:用confusion_matrix看对应关系,或者用匈牙利算法对齐标签后再算准确率。ARI 本身对标签置换不变,所以 ARI 高就说明聚类结构对,不用管标签顺序。
7. 三套代码怎么选:一个快速决策表和调参习惯
三种算法没有绝对优劣,看数据形状和业务目标。我整理了一个决策表,按数据量、簇形状、噪声容忍度来选。
| 场景 | 推荐算法 | 关键参数 | 鸢尾花上预期 ARI |
|---|---|---|---|
| 数据量小于 5000,簇近似球形 | k 均值 | n_clusters=3, n_init=10 | 0.65~0.75 |
| 数据量小于 5000,不想预设簇数 | 合并聚类 ward | n_clusters=3, linkage='ward' | 0.70~0.78 |
| 数据有噪声,簇形状不规则 | DBSCAN | eps=0.7, min_samples=5 | 0.50~0.60 |
| 数据量大于 10000 | MiniBatchKMeans | n_clusters=k, batch_size=1024 | 略低于 k 均值 |
调参习惯上,我一般先跑 k 均值定基准,再用合并聚类看层次结构,最后用 DBSCAN 找噪声。如果三种算法给出的簇结构一致,说明数据本身分离度好;如果差异大,就回到特征工程,看是不是某些特征在干扰距离计算。鸢尾花上花瓣长度和花瓣宽度是最有区分度的两个特征,只用这两个特征跑 k 均值,ARI 能到 0.85 以上,比用全部四个特征还高——这也是我踩过的坑:特征不是越多越好,冗余特征会稀释距离信号。
最后一个技巧:把三种算法的标签拼成一个 DataFrame,看哪些样本在所有算法里都被分到同一簇,这些是“铁杆核心点”;哪些样本在不同算法间跳来跳去,这些是边界点,值得单独分析。这个习惯帮我省了很多调参时间,也让我对数据的理解从“跑通代码”变成“看懂结构”。希望帮到你。
本文还有配套的精品资源,点击获取