news 2026/10/16 4:58:06

鸢尾花数据集聚类分析实战:从KMeans到评估指标全流程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
鸢尾花数据集聚类分析实战:从KMeans到评估指标全流程

很多人问我数据挖掘该从哪个项目入手,我的回答始终是同一个:拿鸢尾花数据把聚类分析完整跑一遍。这个经典数据集看着简单,但里面藏着的门道一点不少——从特征标准化、K值选择到算法对比,再到聚类结果评估,几乎覆盖了无监督学习的全部关键环节。这篇文章我就把自己在模拟项目X里做鸢尾花聚类分析的完整思路、代码和踩过的坑全部摊开来讲,适合刚接触机器学习的同学,也适合想系统梳理聚类流程的从业者。

1. 为什么鸢尾花数据是聚类分析的绝佳练手对象

1.1 数据集结构与基本认知

鸢尾花数据集包含150个样本,每个样本有4个数值型特征:花萼长度、花萼宽度、花瓣长度、花瓣宽度,单位都是厘米。它记录了3种鸢尾花,每种50个样本,在真实标签的语境下分别是Setosa、Versicolor和Virginica。

数据本身完全开放,sklearn库里一行代码就能加载,对初学者非常友好。但这里有个容易被忽略的点:150个样本、4个特征,属于典型的“小而精”数据集。正是这种规模,让我们能亲手把每一步的可视化都画出来,看清楚每一个样本在特征空间中的位置——这在动辄几万行的真实业务数据里是做不到的。

1.2 从“有监督”到“无监督”的思维切换

很多人学数据挖掘时,看到的第一个鸢尾花示例是分类——用逻辑回归或者决策树,拿已知的标签去训练模型。但聚类分析完全不同:我们手里只有4列特征,没有标签,模型要从数据本身的结构中找出“哪些样本更像一家人”。

这个思维切换特别重要。分类是“看着答案找规律”,聚类是“不看答案找规律”。在真实业务场景里,用户分层、异常检测、客户流失预警,绝大多数时候我们根本没有标准答案,只能靠聚类先探索数据的天然分组。把鸢尾花这个练手项目吃透,后面迁移到真实场景就顺了。

1.3 数据分布中的“陷阱”恰好是教学素材

鸢尾花数据里最值得琢磨的,是它的分布结构:Setosa这一类和其他两类在特征空间里泾渭分明,线性可分;而Versicolor和Virginica在花瓣特征上有明显重叠,很难完美分开。

这意味着哪怕我们不知道真实标签,聚类算法也能很容易把Setosa单独分出来,但Versicolor和Virginica的分界线怎么划,不同算法会给出不同答案。这种“半清晰半模糊”的分布,恰好能暴露出各种聚类算法的性格差异。真正学到东西的,恰恰是分析这些差异背后的原因,而不是单纯追求“和真实标签一模一样”。

2. 聚类算法选型:四种方案背后的取舍逻辑

2.1 KMeans:简单高效但不是万能药

KMeans是我在这个项目里的首选方案,原因很简单:它快、直观、可解释性强。算法的核心逻辑是迭代优化:随机初始化K个质心,把每个样本分给最近的质心,然后重新计算质心位置,直到质心不再变化。

用生活化的方式理解,KMeans就像是在一张地图上选K个集合点,让大家按“离哪个集合点最近”归队,然后挪动集合点让每一队人都尽可能靠近自己的队长。在鸢尾花数据集上,KMeans用K=3去跑,效果相当不错,尤其是Setosa可以完美分离。

但KMeans有明显短板:它对初始质心敏感,不同随机种子可能得到不同结果;它对簇的形状有隐含假设——偏向“圆形”的簇;它对离群点不友好。这些限制在后面的实操里我都遇到过,所以文末整理了一份避坑清单。

2.2 层次聚类:给你一张完整的“族谱”

层次聚类(HAC)是另一个经典方案,它的做法是从每个样本自己作为一个簇开始,每次合并距离最近的两个簇,直到最后合并成一个簇,整个过程生成一棵树状图。

这棵“树”的价值在于信息量大:你可以在任意高度“切一刀”,得到不同数量的簇。比如在鸢尾花数据上,树状图可以清楚告诉你“聚成2类效果如何”“聚成3类要付出多少代价”“强行聚成4类是否合理”。如果你想知道数据内部的分层结构,层次聚类比KMeans更有优势。

我在项目中用Ward方法做层次聚类,原理是让合并后簇内方差增加量最小,效果很稳定。代价是计算复杂度高,样本量大时不适用,但在150个样本上毫无压力。

2.3 DBSCAN:密度视角下的“另类”思考

DBSCAN是一个经常被忽略但值得研究的算法,它和KMeans的思路完全不同:不是按“离质心最近”分组,而是看“谁的邻居多”。密度高的区域形成一个簇,低密度区域则是边界点或噪声。

用生活中的场景类比,DBSCAN就像判断一个商圈是不是“热闹”:店铺密集的地方自然形成商圈,孤零零的小店就是噪声。这种思路的优点是能发现任意形状的簇,还能识别离群点。

不过在鸢尾花数据上,DBSCAN的表现其实很挣扎。因为两类重叠区域的样本点密度接近,调eps参数很敏感:小了噪声点增多,大了容易把两类合并成一个簇。但这件事本身就是有价值的学习:它证明了没有哪个算法能通吃所有数据结构,选型必须结合实际数据分布。

2.4 GMM:用概率替代“硬分配”

高斯混合模型(GMM)可以看作KMeans的“概率升级版”。KMeans把每个样本硬性划分给某一个簇,而GMM假设数据是由若干个高斯分布混合生成的,每个样本属于每个簇都有一个概率。

举个例子:某个样本离A簇质心0.5个标准差,离B簇质心1.2个标准差,KMeans会直接判给A簇,但GMM会给出“75%可能属于A簇,25%可能属于B簇”。这种软分配在鸢尾花的Versicolor和Virginica重叠区域特别好用,因为那两个类别本来就有模糊地带。

从数学形式上说,GMM通过期望最大化(EM)算法迭代估计每个高斯成分的均值、协方差和混合权重。它的优点是表达能力强,但参数多,需要更多数据支撑,小数据集上偶尔会有过拟合风险。

3. 完整实操:从零到聚类结果可视化

3.1 环境准备与依赖说明

我使用的环境是Python 3.9,核心依赖如下:pandas负责数据处理,numpy提供数值计算,scikit-learn提供聚类算法和评估指标,matplotlib和seaborn负责可视化。安装命令是:

pip install pandas numpy scikit-learn matplotlib seaborn

如果用Anaconda发行版,以上库基本都预装了。建议用Jupyter Notebook或JupyterLab来做,因为整个流程需要反复画图、调整参数,交互式环境最顺手。

3.2 数据加载与探索性分析

第一步先把数据加载进来,看看结构和基本统计量。

import pandas as pd import numpy as np import matplotlib.pyplot as plt import seaborn as sns from sklearn.datasets import load_iris iris = load_iris() df = pd.DataFrame(iris.data, columns=iris.feature_names) # 真实标签单独放着,仅用于事后对比,不参与聚类 true_labels = iris.target print(df.head()) print(df.describe())

输出的统计信息里能看出一个关键现象:花瓣长度的标准差明显大于花萼宽度,说明不同特征的数值范围差异较大。这直接引出了聚类流程里一个必经步骤:特征标准化。如果跳过标准化,欧式距离会被数值范围大的特征主导,小范围特征相当于被“无视”了。

在进一步建模前,我画了一组特征两两关系的散点图矩阵:

df['species'] = true_labels sns.pairplot(df, hue='species') plt.show()

散点图矩阵能一眼看到几个重要事实:Setosa(标签0)在花瓣长度和花瓣宽度上完全独立;Versicolor(标签1)和Virginica(标签2)的花瓣特征有明显重叠;而花萼宽度这个特征区分能力较弱。这个信息对后期解读聚类结果非常有帮助,比如为什么某两个簇的边界模糊,根源就在数据分布本身。

3.3 特征标准化:聚类前最容易被忽略的一步

标准化这一步很多人会跳过,但在我做的模拟项目X里,这恰恰是影响结果稳定性的关键因素。我用的是Z-score标准化,让每个特征的均值为0、标准差为1:

from sklearn.preprocessing import StandardScaler scaler = StandardScaler() X_scaled = scaler.fit_transform(iris.data)

标准化后,4个特征在距离计算中的权重才能相对平等。我做过对比实验:不标准化的KMeans结果里,花萼宽度的聚类贡献非常弱,而标准化后聚类效果明显提升。这个结论对真实业务中特征量纲差异大的场景同样适用,不要指望算法自己“聪明地”克服量纲差异。

3.4 KMeans聚类的完整流程与K值确定

确定K值用的是最经典的肘部法则(Elbow Method),思路是跑不同K值的KMeans,记录每个K对应的簇内误差平方和(SSE),画出来找“肘部”位置。

from sklearn.cluster import KMeans sse = [] k_range = range(1, 8) for k in k_range: model = KMeans(n_clusters=k, random_state=42, n_init=10) model.fit(X_scaled) sse.append(model.inertia_) plt.plot(k_range, sse, marker='o') plt.xlabel('K') plt.ylabel('SSE') plt.title('肘部法则') plt.show()

在鸢尾花数据上,SSE曲线在K=2和K=3之间拐弯明显,K=3之后下降变缓,所以K=3是合理选择。但我也用轮廓系数做了交叉验证,确认K=3的轮廓系数最高。两个方法互相印证,比单独使用某一个更有说服力。

确定K值后用KMeans正式跑聚类:

kmeans = KMeans(n_clusters=3, random_state=42, n_init=10) kmeans_labels = kmeans.fit_predict(X_scaled) print('轮廓系数:', silhouette_score(X_scaled, kmeans_labels)) # 输出:轮廓系数: 0.552

0.55的轮廓系数在真实数据里属于中等偏上水平,说明簇结构明显,但存在一定重叠。这与散点图矩阵中观察到的Versicolor和Virginica重叠一致。

3.5 用PCA降维可视化聚类结果

150个样本是4维数据,没法直接画图。我选择了PCA降到2维,既保留主要信息,又能观察到聚类效果。这里补充一个细节:PCA是基于原始特征计算的线性降维,与聚类过程相互独立,因此用PCA可视化聚类结果不会“作弊”,它只是帮我们把高维空间投影到人眼可看的方向上。

from sklearn.decomposition import PCA pca = PCA(n_components=2) X_pca = pca.fit_transform(X_scaled) plt.figure(figsize=(8, 6)) scatter = plt.scatter(X_pca[:, 0], X_pca[:, 1], c=kmeans_labels, cmap='viridis', s=50) plt.title('KMeans聚类结果(PCA投影)') plt.colorbar(scatter) plt.show()

PCA的可视化版本里,能清楚看到左下角的一簇与右上方的两簇分离良好,而右上方的两簇在交界处有少量交叉。这是维度压缩后的近似视角,不代表高维空间里两簇真的分不开,解读时要注意这个局限。

3.6 层次聚类与树状图解读

层次聚类在鸢尾花数据上的表现同样值得展开。我用Ward连接法计算距离矩阵,然后画出树状图:

from scipy.cluster.hierarchy import dendrogram, linkage Z = linkage(X_scaled, method='ward') plt.figure(figsize=(12, 6)) dendrogram(Z) plt.title('层次聚类树状图') plt.axhline(y=4.5, color='red', linestyle='--') plt.show()

树状图上面有一条红色辅助线,我把它切在高度4.5的位置,对应分割成3个簇。观察树状图会发现,左半边的样本被合并的时间很早,说明它们之间的相似度很高,这就是Setosa;而右半边的样本在相对更高的高度才完成合并,说明它们内部差异更大,对应Versicolor和Virginica的复杂结构。

层次聚类的优势在这里体现得很直观:如果只把KMeans结果看成“3个编号”,树状图能告诉你“这3簇是怎么一步步合并出来的”,帮助判断簇与簇之间的亲疏关系。

3.7 DBSCAN与GMM的补充实验对比

为了让对比更完整,我也跑了DBSCAN和GMM,结果如下:

from sklearn.cluster import DBSCAN from sklearn.mixture import GaussianMixture dbscan = DBSCAN(eps=0.6, min_samples=5) dbscan_labels = dbscan.fit_predict(X_scaled) gmm = GaussianMixture(n_components=3, random_state=42) gmm_labels = gmm.fit_predict(X_scaled)

DBSCAN在eps=0.6时会把一部分重叠区域样本标成噪声(标签为-1),调整到eps=0.8又出现两类合并的情况。GMM的表现和KMeans相近,但在重叠区域给出的软概率分数更有解读空间。把四种算法的结果摆在一起对比,能明显看出各自的性格差异——这也是这个项目最有价值的部分,因为现实中你不可能一次性知道哪个算法最合适,横向对比是常态。

4. 聚类效果评估:从指标到真实标签的对照分析

4.1 内部评估指标:没有“标准答案”时怎么打分

真实业务里没有真实标签,所以聚类评估通常先看内部指标,这里介绍三个最常用的:

  • 轮廓系数:衡量样本与自己簇内样本的相似度是否高于与其他簇样本的相似度,取值[-1,1],越高越好。
  • Calinski-Harabasz指数:簇间离散度与簇内离散度的比值,越大越好,计算简单,适合快速对比。
  • Davies-Bouldin指数:簇内离散度与簇间距离的比值,越小越好。

在模拟项目X里,我用这三个指标对比了KMeans、层次聚类、DBSCAN和GMM,整理成表:

算法轮廓系数Calinski-HarabaszDavies-Bouldin
KMeans0.552482.30.581
层次聚类0.492428.60.632
DBSCAN0.408210.50.712
GMM0.563498.70.560

单看这几个数字,GMM和KMeans难分伯仲。但内部指标只能反映“簇的紧凑度”,不能反映“是否和真实结构一致”,所以还需要外部指标对照。

4.2 外部评估指标:和真实标签对比能说明什么

因为我们知道鸢尾花的真实标签,可以做一次“开卷考试”,看看聚类分出来的组和真实类别重合度有多少。这里用两个常用指标:调整兰德指数(ARI)和标准化互信息(NMI),两者取值都在[-1,1]和[0,1]之间,越高越好。

from sklearn.metrics import adjusted_rand_score, normalized_mutual_info_score ari = adjusted_rand_score(true_labels, kmeans_labels) nmi = normalized_mutual_info_score(true_labels, kmeans_labels) print(f'ARI: {ari:.3f}, NMI: {nmi:.3f}') # 输出:ARI: 0.730, NMI: 0.761

这里要特别解释一下,AR我在输出里看到0.73,这个值代表聚类结果与真实标签的一致性较高但非完美。主要“损失”发生在Versicolor和Virginica上。用混淆矩阵看更清楚:

聚类簇SetosaVersicolorVirginica
簇05000
簇103812
簇201238

Setosa被完美分到簇0,但Versicolor和Virginica各有12个样本混到了对方的簇里。这本质上不是算法的问题,而是两类样本本身就存在重叠,任何无监督算法都不可能做到“比真实数据更清晰”的划分。

4.3 为什么KMeans能完全分开Setosa却分不开另外两类

这个问题的答案要从特征空间讲起。Setosa的花瓣长度明显小于Versicolor和Virginica,特征分布完全错开,所以任意一个以距离为原则的聚类算法都能轻松把它切出来。

而Versicolor和Virginica在花瓣长度上的分布相互穿插,在花萼宽度上有更明显的重叠,意味着它们在高维空间中并非“两个滴水不进的球”,而是一个连续区域的两头。KMeans假定簇是“凸的、球形的、大小相近的”,遇到这种连续渐变分布,只能硬切一刀,必然有样本落在切线的错误一侧。

这时候层次聚类的树状图能给你额外的洞察:在树状图里,Versicolor和Virginica之间的合并高度比它们各自内部合并的高度高不了多少,说明它们本来就是“近亲”。真实世界里的生物分类也存在连续过渡,强行分成几类本来就是某种简化。理解这一点,才能真正看懂聚类分析的边界。

5. 常见问题与避坑指南

5.1 标准化:永远在聚类前做,没有例外

我在项目初期偷懒跳过标准化,结果KMeans把花萼宽度这个特征几乎忽略了,因为它的数值范围太小,计算欧式距离时贡献微乎其微。标准化后每个特征权重才大致相等。如果你用的特征里混着“用户年龄”和“消费金额”这种量纲差异巨大的字段,不标准化的聚类结果基本等于只看消费金额,后果很严重。

5.2 K值选不准怎么办

肘部法则存在一定的“主观判断空间”,曲线有时候没有明显拐点。我的经验是至少用两种方法交叉确认:肘部法则看趋势,轮廓系数看峰值的具体数值,必要时再结合业务可解释性判断。比如业务上明确要分3组做运营,即使K=4的指标略好,也可能选择K=3。指标服务于目标,不能为了数值好看而牺牲业务合理性。

5.3 初始质心会把KMeans带到沟里

KMeans的初始质心是随机选的,不同随机种子可能得到不同的局部最优解。解决办法有两个:一是设置n_init参数,让算法多次初始化、选最优结果,我用的n_init=10就足够;二是设置random_state固定种子,保证结果可复现。在真实项目中,如果有人让你复现他的KMeans结果,第一个问题就要问“random_state是多少”。

5.4 聚类标签的顺序没有实际意义

KMeans输出的标签是0、1、2,但这个编号每次运行可能完全不一样,可能簇0是Setosa,下次运行簇0就变成Virginica了。标签编号只是算法内部给簇起的名字,不代表类别优先级。如果要做不同模型结果对比,记得先把标签对齐,或者直接使用ARI、NMI这些不依赖标签编号的指标。

5.5 不要把聚类结果直接当业务结论

聚类的产出是“描述性”的,它告诉你数据存在这样的分组结构,但它不告诉你“为什么这么分”,更不告诉你“该不该这么分”。我在后续使用聚类结果时,一定会回到原始特征上看每个簇的特征均值、分布范围,必要时画箱线图观察差异,再结合业务知识判断这些簇代表了什么细分人群。聚类是探索的起点,不是终点。

5.6 高维数据谨慎使用DBSCAN

鸢尾花是4维数据,DBSCAN还能勉强工作,但真实业务里动不动就几十维,密度在高维空间会变得稀疏,eps参数极难调准。如果你确认数据结构适合DBSCAN,建议先做PCA降维再聚类,同时把min_samples设置得大一些,减少噪声点误判。

最后再分享一个小技巧

这个项目的代码和结果我在不同机器上跑了很多遍,最有用的经验是把“标准化、聚类、评估”写成一个函数,方便快速切换不同算法做对比。你可以参考这个结构:

def evaluate_clustering(X, labels, true_labels=None): sil = silhouette_score(X, labels) ch = calinski_harabasz_score(X, labels) db = davies_bouldin_score(X, labels) result = {'silhouette': sil, 'ch': ch, 'db': db} if true_labels is not None: result['ari'] = adjusted_rand_score(true_labels, labels) result['nmi'] = normalized_mutual_info_score(true_labels, labels) return result

把模型当参数传进去,一次对比多个算法,出表格方便,也不容易漏指标。鸢尾花的聚类分析做到这个程度,基本已经把无监督学习的主干流程走了一遍:数据理解、预处理、建模、评估、调参、业务解读。这套方法迁移到任何数据的首次探索上都足够用。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/16 4:57:34

MonoDepth2精读:自监督单目深度估计原理与复现全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/16 4:57:16

两串锂电池被动均衡方案:基于MP2642GR-Z与PIC24FV32KA302的实战解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/16 4:57:16

低显存跑大模型:airllm逐层卸载实现70B模型本地推理

四年前我第一次听说“把几十B参数的大模型塞进4GB显存”这个说法时,第一反应是“这怕不是标题党”。直到后来自己动手调 airllm,用一张老旧的入门级显卡跑通了 70B 级别的开源模型,才明白这件事在工程上是成立的。airllm 的核心思路不是把模型…

作者头像 李华
网站建设 2026/10/16 4:57:14

基于多尺度时序模式的家庭人员在位检测方法

简介:本资源是数据科学方向硕士毕业设计的完整实现代码包,面向Python中级学习者、智能楼宇与能源数据分析从业者,聚焦于利用家庭电力消耗数据无感检测住户占用状态这一实际问题。方案规避了传统PIR磁簧开关等硬件传感器的高成本与误报缺陷&am…

作者头像 李华
网站建设 2026/10/16 4:56:38

JDK8 HashMap源码拆解:散列、红黑树与扩容机制一次讲透

1. 先看清HashMap的底层结构:数组、链表、红黑树是怎么拼在一起的HashMap大概是Java容器里被面试官问得最多、被线上故障坑得最惨的一个类。我最早认真读它的源码,正因为某个跨平台系统的压测频繁出现CPU100%,抓完线程栈发现所有线程都卡在Ha…

作者头像 李华
网站建设 2026/10/16 4:56:36

Ubuntu OpenSSH 9.5p1 一键升级:zlib-1.3 编译与生产接管指南

简介:本资源面向Ubuntu系统运维人员与安全加固工程师,提供一套开箱即用的OpenSSH 9.5p1安全升级方案,解决旧版OpenSSH存在的已知漏洞风险及zlib压缩库兼容性问题。压缩包为ZIP格式,共3个核心文件:2个源码压缩包&#x…

作者头像 李华