
简介面向机器学习初学者和数据分析课程学习者这是基于K-Means聚类算法处理经典鸢尾花数据集的Python实践资源能够帮助理解无监督学习中簇划分、中心点迭代等核心概念。压缩包共2个文件包含一个csv格式的鸢尾花数据文件和一个py格式的聚类脚本整体大小仅2KB轻量精炼方便直接下载查看。目前已有155人学习下载。脚本围绕K-Means完整流程展开涵盖数据读取与清洗、数值特征标准化、聚类模型构建、簇内误差平方和计算以及散点图可视化等典型步骤通过该脚本可清楚观察不同K值下聚类效果的变化理解肘部法则和轮廓系数等评价思路。资源还可作为基础模板迁移到其他无监督聚类任务适用于课程实验、期末项目或入门自学具有一定的二次开发价值。1. 为什么用 KMeans 跑 iris 是入门聚类最稳的一条路iris 鸢尾花数据集几乎是每个学机器学习的人都会撞上的名字150 条样本、4 个连续特征、3 个品种干净到不需要清洗。但大多数人把它当分类题做却忽略了一个事实——iris 同样是从 0 理解 KMeans 的最佳载体。因为 KMeans 是无监督算法训练时看不到标签而 iris 恰好自带标签于是你能用真实类别反过来检验聚类结果准不准这是很多真实业务数据给不了的条件。这篇文章就从「动手实现」的角度把 iris 上的 KMeans 聚类完整链路拆开数据怎么加载、要不要标准化、K 值怎么选、聚类质量怎么量化、结果怎么画出来。适合刚接触聚类的新手也适合想把无监督评估流程补全的工程师。所有代码基于 Python 3.8 和 scikit-learn 1.x按顺序复制就能跑通。2. iris 数据集结构与 KMeans 聚类的核心参数2.1 用代码看清 iris 的 4 个特征和 3 类分布第一件事是把数据真实加载出来看一眼不要上来就 fit。用 scikit-learn 内置接口加载 iris省去找 CSV 的麻烦。from sklearn.datasets import load_iris import pandas as pd iris load_iris() df pd.DataFrame(iris.data, columnsiris.feature_names) df[species] iris.target_names[iris.target] print(df.shape) # (150, 5) print(df.head()) print(df.groupby(species).size())加载后是 150 行 5 列species 列是真实品种名前 4 列是特征花萼长度、花萼宽度、花瓣长度、花瓣宽度单位都是厘米。为什么先打印 shape 和 groupby因为聚类最怕样本不均衡而 iris 三个品种各 50 条非常规整这是它能当基准数据集的核心原因。特征虽然同为厘米数值范围差别却不小花萼长度在 4.3 到 7.9 之间花瓣宽度只有 0.1 到 2.5。这个差异直接决定了后面必须做标准化否则欧氏距离会被数值大的特征主导这是 KMeans 的经典陷阱。2.2 KMeans 的收敛过程与 6 个必调参数KMeans 的核心逻辑很简单随机选 K 个质心把每个样本归到最近的质心重新计算每簇样本的均值作为新质心重复直到质心偏移小于阈值。整个流程对欧氏距离敏感所以特征尺度、初始质心位置都会直接影响最终簇的形状。在 scikit-learn 里KMeans 的常用参数集中在下面这张表我一般默认按这个思路设参数默认值对 iris 的建议n_clusters8显式设为 3别用默认值initk-means保持默认避免纯随机初始化n_init10保持 10多跑几次取最优max_iter300iris 上 100 次内就会收敛tol1e-4收敛阈值保持默认random_stateNone调试和复现时固定为 42 这类整数最容易踩的坑是把 n_clusters 当默认值放任不管。scikit-learn 的默认值 8 是通用场景预设拿它跑 iris 会得到 8 个没有语义的簇。另外random_state 不设的话每次运行结果都可能不同这在写文档、做演示、对比实验时非常致命。2.3 特征缩放为什么裸数据跑 KMeans 的分类边界是歪的KMeans 用欧氏距离衡量样本相似度四个特征量纲不一致时数值范围大的特征会在距离计算里「淹没」其他特征。比如花萼长度和花瓣宽度在距离中的权重天然差一个数量级聚类边界会向花萼长度倾斜。常见做法是用 StandardScaler 做 z-score 标准化把每个特征变成均值为 0、方差为 1 的分布。这里给一个直观对比from sklearn.preprocessing import StandardScaler from sklearn.cluster import KMeans scaler StandardScaler() X_scaled scaler.fit_transform(iris.data) kmeans_raw KMeans(n_clusters3, n_init10, random_state42).fit(iris.data) kmeans_scaled KMeans(n_clusters3, n_init10, random_state42).fit(X_scaled) print(raw inertia:, kmeans_raw.inertia_) print(scaled inertia:, kmeans_scaled.inertia_)inertia 是样本到所属簇质心的平方距离之和数值越小代表簇内越紧凑。单看数字标准化后的 inertia 通常比原始数据大这不是变差了而是量纲被统一后的正常表现。判断特征缩放是否有效不能只看 inertia要看聚类结果与真实标签的对齐程度这正是下一章要做的事。提示标准的做法是先 fit_transform 训练集再对后续新样本用同一个 scaler 去 transform不能重新 fit。后面预测部分会用到这个细节。3. 用 Python 完整实现 iris KMeans从训练到评估3.1 最小可用聚类代码与参数含义把前面的步骤合并成标准训练流程加载数据、提取特征、标准化、训练、查看簇中心。代码不长但每行都有作用。from sklearn.datasets import load_iris from sklearn.preprocessing import StandardScaler from sklearn.cluster import KMeans iris load_iris() X iris.data scaler StandardScaler() X_scaled scaler.fit_transform(X) kmeans KMeans(n_clusters3, initk-means, n_init10, max_iter300, random_state42) kmeans.fit(X_scaled) labels kmeans.labels_ centers kmeans.cluster_centers_ print(每个簇的样本数:, pd.Series(labels).value_counts().sort_index().values) print(簇中心矩阵形状:, centers.shape)fit 之后拉开三个结果看labels_ 是每个样本的簇编号cluster_centers_ 是 K 个质心的坐标此处是标准化后的坐标维度等于特征数inertia_ 是簇内误差平方和。注意这里没有再取原始 iris 的标签KMeans 从头到尾只用了 X_scaled聚类编号和真实品种编号也没有对应关系——第 0 簇可能对应 setosa也可能对应 versicolor。3.2 与真实标签对比用 ARI 和 NMI 量化聚类质量KMeans 是聚类算法不会给出「这个簇是 setosa」这类答案。想量化聚类效果可以把聚类结果和真实标签放在一起算外部指标。这里最常用的是 ARI调整兰德指数和 NMI标准化互信息。ARI 取值范围是 -1 到 11 代表聚类结果与真实标签完全一致0 等于随机划分负数代表比随机还差。NMI 范围是 0 到 1值越高说明聚类结果与真实类别共享的信息越多。iris 是均衡三类数据这两个指标都很可靠。from sklearn.metrics import adjusted_rand_score, normalized_mutual_info_score true_labels iris.target ari adjusted_rand_score(true_labels, kmeans.labels_) nmi normalized_mutual_info_score(true_labels, kmeans.labels_) print(fARI: {ari:.4f}) print(fNMI: {nmi:.4f})跑出来的 ARI 一般在 0.73 左右NMI 在 0.75 左右。注意调整过后的 ARI 不会虚高即便聚类编号和真实标签顺序完全不一样只要划分边界正确ARI 仍会接近 1。这也是它比 accuracy 更适合评估聚类的原因accuracy 要求编号一一对应聚类编号根本没有这种对应关系。3.3 用训练好的模型给新样本归类聚类模型训练完不能只停留在 iris 这 150 条上真实场景里会有新样本进来。predict 方法可以直接处理。import numpy as np new_sample np.array([[5.1, 3.5, 1.4, 0.2]]) new_sample_scaled scaler.transform(new_sample) pred_cluster kmeans.predict(new_sample_scaled) print(新样本所属簇:, pred_cluster[0])这里有两个不能省的细节。第一个是 transform 而不是 fit_transform新样本要沿用训练集 scaler 的均值和方差否则标准化口径对不上第二个是 predict 只把新样本分配到最近的已有质心不会更新质心位置。如果线上数据分布一直在变需要定期用全量数据重训模型而不是一直 predict 下去。4. 选 K 的 3 个判断方法与两类高频排错4.1 用肘部法则确定 iris 的簇数量实际业务里几乎没人告诉你「分成 3 类」K 要靠数据说话。最经典的方法是肘部法则对不同的 K 跑 KMeans记录 inertia画出 K-inertia 曲线找曲线由陡变缓的拐点。这个拐点就是「再加一个簇收益不大」的位置。import matplotlib.pyplot as plt inertias [] K_range range(1, 11) for k in K_range: km KMeans(n_clustersk, n_init10, random_state42).fit(X_scaled) inertias.append(km.inertia_) plt.plot(list(K_range), inertias, markero) plt.xlabel(n_clusters) plt.ylabel(inertia) plt.title(Elbow Method for iris) plt.show()运行后会看到 K2 到 K3 时曲线下降明显K3 之后下降幅度逐渐趋缓。iris 的肘部就在 K3 这个位置和真实类别数一致。需要注意肘部法则依赖人眼判断不够客观曲线特别平滑时很难选出唯一拐点。4.2 用轮廓系数交叉验证 K轮廓系数不需要真实标签属于内部评估指标。它综合衡量每个样本与自身簇的紧密度、与相邻簇的分离度取值范围 -1 到 1。值接近 1 代表样本离自己簇很近且远离其他簇接近 -1 代表可能分错了簇。from sklearn.metrics import silhouette_score for k in range(2, 6): km KMeans(n_clustersk, n_init10, random_state42).fit(X_scaled) score silhouette_score(X_scaled, km.labels_) print(fK{k}, silhouette{score:.4f})在 iris 上正常结果按轮廓系数看K2 通常最高K3 略低但仍不错K4、K5 明显下滑。这会出现一个有意思的情况——肘部法则说 3轮廓系数说 2到底听谁的我的观点是看业务目标只想区分离群结构选 2想要更细的语义边界选 3。iris 真实标签是 3 类所以取 3 更合理。4.3 n_init 和 random_state 不设好结果每次都不一样KMeans 的初始质心是随机选择的即使是 k-means也只是让初始质心更分散不改变其随机性。如果随机数种子没固定同一份代码连续跑两遍可能得到不同标签编号、不同 inertia。n_init 的作用是每个 K 值跑 N 次保留 inertia 最小的一次作为最终结果但这不解决「每次运行结果不同」的问题要可复现必须设 random_state。实践中我习惯把 random_state 提取成常量写在代码顶部RANDOM_STATE 42 kmeans KMeans(n_clusters3, n_init10, random_stateRANDOM_STATE)另一个高频坑是报错ConvergenceWarning提示达到 max_iter 上限但未收敛。iris 上很少遇到但数据量大时会把警告刷满终端。遇到时先看数据有没有做标准化再看 max_iter 是否需要增大不要一上来就关警告。4.4 聚类评估时最容易误用的两个细节第一点是不要把 accuracy 用在聚类结果上。聚类标签 0、1、2 和真实标签 0、1、2 没有语义对应直接算 accuracy 会得到极低分数这不是模型差是指标用错了。第二点是不要对 KMeans 结果直接做交叉验证交叉验证依赖有监督评估KMeans 是生成式模型更适合用稳定性分析或者外部指标验证。提示sns.pairplot 配合 hue聚类标签 能快速看出特征两两组合下的簇分布比单看数字直观得多。聚类结果好不好先画图再算分两个步骤都别省。5. 用 PCA 把四维聚类结果降到二维的可视化验证5.1 PCA 降维与聚类散点图代码iris 有 4 个特征没法直接画二维散点。常见做法是先用 PCA 降到 2 维保留方差最大的两个主成分再画散点图。注意 PCA 是无监督降维不会用到真实标签适合用来观察聚类边界。from sklearn.decomposition import PCA import matplotlib.pyplot as plt pca PCA(n_components2) X_pca pca.fit_transform(X_scaled) plt.figure(figsize(8, 6)) scatter plt.scatter(X_pca[:, 0], X_pca[:, 1], ckmeans.labels_, cmapviridis, s50) plt.scatter(kmeans.cluster_centers_[:, 0], kmeans.cluster_centers_[:, 1], markerX, s200, cred, labelcentroids) plt.xlabel(PC1) plt.ylabel(PC2) plt.title(KMeans Clustering on iris (PCA)) plt.colorbar(scatter) plt.legend() plt.show()这里有一点容易忽略簇中心是 4 维坐标画图前要先对簇中心做同样的 PCA 降维。上面的代码直接用cluster_centers_和X_pca一起画是错的。正确做法是把质心也放进 PCA 变换centers_pca pca.transform(kmeans.cluster_centers_) plt.scatter(centers_pca[:, 0], centers_pca[:, 1], markerX, s200, cred)5.2 从散点图判断聚类是否成功的 3 个观察点第一观察同一簇样本是否在二维空间中聚集为紧凑的团。KMeans 用欧氏距离聚类在 PCA 投影下正常应该看到 2 到 3 个边界分明的簇团。第二看 setosa 那一类是否完全独立于其他两类iris 数据集中 setosa 与其他两类在花瓣维度上天然分离画出来几乎不会有重叠。第三看重叠部分是否集中在 versicolor 和 virginica 之间这两个品种在真实特征空间中本来就有重叠KMeans 把它们分得不够开是数据本身的特性不是算法缺陷。PCA 的 explained_variance_ratio_ 值得顺手打印它告诉你前两个主成分保留了多少信息。iris 上前两维通常能解释 95% 以上的方差所以散点图有较高可信度。如果这个值低于 70%二维图可能误导判断看三维图或改用 t-SNE 更稳妥。本文还有配套的精品资源点击获取