K-means聚类实战:用Python给鸢尾花(Iris)数据集自动分个类
鸢尾花数据集是机器学习领域的经典入门案例,包含150个样本,每个样本有4个特征(萼片长度、萼片宽度、花瓣长度、花瓣宽度)和对应的品种标签。我们将使用K-means这一无监督学习算法,仅基于花的特征自动将其分成若干类别,并与真实品种进行对比分析。
1. 环境准备与数据探索
首先导入必要的Python库并加载数据:
import numpy as np import pandas as pd import matplotlib.pyplot as plt from sklearn.datasets import load_iris from sklearn.preprocessing import StandardScaler # 加载数据集 iris = load_iris() X = iris.data y = iris.target feature_names = iris.feature_names target_names = iris.target_names查看数据的基本统计信息:
df = pd.DataFrame(X, columns=feature_names) print(df.describe())输出结果将显示四个特征的统计量:
| 特征 | 均值 | 标准差 | 最小值 | 25%分位 | 中位数 | 75%分位 | 最大值 |
|---|---|---|---|---|---|---|---|
| 萼片长度 | 5.84 | 0.83 | 4.30 | 5.10 | 5.80 | 6.40 | 7.90 |
| 萼片宽度 | 3.06 | 0.44 | 2.00 | 2.80 | 3.00 | 3.30 | 4.40 |
| 花瓣长度 | 3.76 | 1.77 | 1.00 | 1.60 | 4.35 | 5.10 | 6.90 |
| 花瓣宽度 | 1.20 | 0.76 | 0.10 | 0.30 | 1.30 | 1.80 | 2.50 |
注意:不同特征的数值范围差异较大,建议进行标准化处理以提高聚类效果。
2. 数据预处理与特征工程
良好的数据预处理能显著提升聚类效果:
# 特征标准化 scaler = StandardScaler() X_scaled = scaler.fit_transform(X) # 可视化特征分布 plt.figure(figsize=(12, 6)) for i in range(4): plt.subplot(2, 2, i+1) plt.hist(X_scaled[:, i], bins=20) plt.title(feature_names[i]) plt.tight_layout() plt.show()特征相关性分析:
import seaborn as sns corr_matrix = df.corr() sns.heatmap(corr_matrix, annot=True, cmap='coolwarm') plt.title('特征相关性矩阵') plt.show()关键发现:
- 花瓣长度与花瓣宽度高度相关(0.96)
- 萼片宽度与其他特征相关性较弱
- 考虑使用PCA降维可能有助于可视化
3. 确定最佳聚类数量
K-means需要预先指定聚类数量K,我们使用肘部法则和轮廓系数来确定最佳K值:
from sklearn.cluster import KMeans from sklearn.metrics import silhouette_score # 尝试不同的K值 inertia = [] silhouette_scores = [] K_range = range(2, 8) for k in K_range: kmeans = KMeans(n_clusters=k, random_state=42) kmeans.fit(X_scaled) inertia.append(kmeans.inertia_) silhouette_scores.append(silhouette_score(X_scaled, kmeans.labels_)) # 绘制肘部法则图 plt.figure(figsize=(12, 5)) plt.subplot(1, 2, 1) plt.plot(K_range, inertia, 'bo-') plt.xlabel('K值') plt.ylabel('簇内平方和(Inertia)') plt.title('肘部法则') plt.subplot(1, 2, 2) plt.plot(K_range, silhouette_scores, 'ro-') plt.xlabel('K值') plt.ylabel('轮廓系数') plt.title('轮廓系数分析') plt.tight_layout() plt.show()分析结果:
- 肘部法则建议K=3(与实际品种数一致)
- 轮廓系数在K=3时达到峰值
- 最终选择K=3进行建模
4. 构建K-means模型与结果分析
训练最终模型并分析结果:
# 训练K-means模型 kmeans = KMeans(n_clusters=3, random_state=42) clusters = kmeans.fit_predict(X_scaled) # 将聚类结果与真实标签对比 results = pd.DataFrame({ '真实品种': y, '预测聚类': clusters, '品种名称': target_names[y] }) print(results.groupby(['真实品种', '预测聚类']).size().unstack())输出混淆矩阵:
| 真实\预测 | 0 | 1 | 2 |
|---|---|---|---|
| setosa | 50 | 0 | 0 |
| versicolor | 0 | 47 | 3 |
| virginica | 0 | 14 | 36 |
可视化聚类结果:
from sklearn.decomposition import PCA # 使用PCA降维到2D便于可视化 pca = PCA(n_components=2) X_pca = pca.fit_transform(X_scaled) plt.figure(figsize=(12, 6)) plt.subplot(1, 2, 1) plt.scatter(X_pca[:, 0], X_pca[:, 1], c=y, cmap='viridis') plt.title('真实品种分布') plt.colorbar(ticks=[0, 1, 2], label='品种') plt.subplot(1, 2, 2) plt.scatter(X_pca[:, 0], X_pca[:, 1], c=clusters, cmap='viridis') plt.scatter(kmeans.cluster_centers_[:, 0], kmeans.cluster_centers_[:, 1], s=200, marker='X', c='red') plt.title('K-means聚类结果') plt.colorbar(ticks=[0, 1, 2], label='聚类') plt.tight_layout() plt.show()模型评估指标:
from sklearn.metrics import adjusted_rand_score, homogeneity_score print(f"调整兰德指数: {adjusted_rand_score(y, clusters):.3f}") print(f"同质性分数: {homogeneity_score(y, clusters):.3f}")典型输出结果:
- 调整兰德指数: 0.730
- 同质性分数: 0.751
5. 模型优化与高级技巧
提升K-means性能的几种实用方法:
- 特征选择优化:
# 只使用花瓣特征 X_petal = X[:, 2:4] kmeans_petal = KMeans(n_clusters=3, random_state=42).fit(X_petal) print(f"仅用花瓣特征的调整兰德指数: {adjusted_rand_score(y, kmeans_petal.labels_):.3f}")- 初始化方法改进:
# 使用k-means++初始化 kmeans_plus = KMeans(n_clusters=3, init='k-means++', random_state=42).fit(X_scaled) print(f"k-means++初始化的调整兰德指数: {adjusted_rand_score(y, kmeans_plus.labels_):.3f}")- 多次运行取最优:
best_score = -1 for _ in range(10): kmeans = KMeans(n_clusters=3, random_state=None).fit(X_scaled) current_score = adjusted_rand_score(y, kmeans.labels_) if current_score > best_score: best_score = current_score print(f"多次运行最佳调整兰德指数: {best_score:.3f}")- 不同距离度量尝试:
from sklearn.metrics import pairwise_distances # 自定义距离函数 def manhattan_distance(x, y): return np.sum(np.abs(x - y)) # 使用预计算距离矩阵 dist_matrix = pairwise_distances(X_scaled, metric=manhattan_distance) kmeans_manhattan = KMeans(n_clusters=3, random_state=42, init='k-means++', n_init=1, precompute_distances=True).fit(dist_matrix)6. 业务解读与决策建议
基于聚类结果的实用洞察:
品种区分度分析:
- setosa品种完全被正确分类(簇0)
- versicolor和virginica存在部分重叠(簇1和簇2)
特征重要性排序:
# 计算每个特征对聚类的贡献度 feature_importance = np.std(kmeans.cluster_centers_, axis=0) plt.barh(feature_names, feature_importance) plt.title('特征对聚类的贡献度') plt.show()- 异常点检测:
# 计算每个样本到其簇中心的距离 distances = np.min(kmeans.transform(X_scaled), axis=1) outliers = np.where(distances > np.percentile(distances, 95))[0] print(f"检测到的潜在异常样本索引: {outliers}")实际应用建议:
- 对于园艺分类,可优先关注花瓣特征
- 在质量检测中,距离簇中心过远的样本可能需要人工复核
- 当引入新品种时,需要重新评估K值选择