1. 引言

在前两篇文章中,我们分别探索了支持向量机(SVM)随机森林(Random Forest) 这两个监督学习的典范。它们如同拥有“老师”指导,通过已知的标签(鸢尾花的类别)学习预测规则。然而,现实世界中,大量数据是没有标签的——我们手头只有海量的用户行为数据、基因表达数据或文本内容,却不知道它们该被分成哪几类。

这时,无监督学习便登场了。而K-Means正是其中最经典、最直观的聚类算法之一。它的目标不再是“预测”,而是“发现”:将没有标签的数据,根据其内在的相似性,自动划分为K个簇(Cluster),使得簇内的数据点尽可能相似,簇间的数据点尽可能不同。

本文将从监督学习转向无监督学习,与前文形成完美互补:

  • SVM/Random Forest“给定标签,学习边界”
  • K-Means“没有标签,发现群组”

我们将通过本文,不仅掌握K-Means的原理与实现,更会看到无监督聚类如何为监督学习提供洞见,从而将两种范式联系起来。

本文将带你:

  1. 理解K-Means的核心思想、算法步骤与评估指标。
  2. 使用scikit-learn,在同一个鸢尾花数据集上实现K-Means聚类。
  3. 通过**“肘部法则”** 和**“轮廓系数”** 确定最佳的K值。
  4. 关键联系:将K-Means的聚类结果与真实标签进行对比分析,并探索如何利用聚类结果辅助监督学习

2. 目录

3. 环境与数据准备

环境配置与之前一致。我们将继续使用鸢尾花数据集,以便进行跨文章的对比分析。

pip install numpy pandas matplotlib scikit-learn
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn import datasets
from sklearn.cluster import KMeans
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import silhouette_score, silhouette_samples, adjusted_rand_score, normalized_mutual_info_score
from sklearn.decomposition import PCA
import warnings
warnings.filterwarnings(‘ignore’)

# 加载数据集
iris = datasets.load_iris()
X = iris.data
y = iris.target # 真实标签,仅用于后续评估,聚类时不会使用

# 特征标准化(对K-Means至关重要!因为它基于距离度量)
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

# 为了二维可视化,使用PCA降至2维
pca = PCA(n_components=2)
X_pca = pca.fit_transform(X_scaled)

# 创建DataFrame方便操作
df = pd.DataFrame(X_scaled, columns=iris.feature_names)
df[‘pca_1’] = X_pca[:, 0]
df[‘pca_2’] = X_pca[:, 1]
df[‘true_label’] = y

print(“数据准备完成。”)
print(f”标准化后特征维度:{X_scaled.shape})
print(f”PCA降维后维度:{X_pca.shape})

4. K-Means核心原理简述

4.1 目标与直觉:最小化簇内距离

K-Means的优化目标非常直观:将n个样本划分为K个簇 C = { C 1 , C 2 , . . . , C K } C = \{C_1, C_2, ..., C_K\} C={C1,C2,...,CK},使得每个样本到其所属簇的质心(Centroid,即簇内所有点的均值)的欧氏距离平方和最小。 这个总和被称为簇内平方和(Within-Cluster Sum of Squares, WCSS),也叫惯性(Inertia)

数学表达为:
Minimize J = ∑ k = 1 K ∑ x i ∈ C k ∣ ∣ x i − μ k ∣ ∣ 2 \text{Minimize} \quad J = \sum_{k=1}^{K} \sum_{x_i \in C_k} ||x_i - \mu_k||^2 MinimizeJ=k=1KxiCk∣∣xiμk2
其中 μ k \mu_k μk 是第 k k k 个簇的质心。

4.2 算法步骤:迭代优化

K-Means通过以下迭代过程来逼近最优解(这是一种贪心算法,不保证找到全局最优,但通常效果很好):

  1. 初始化:随机选择K个样本点作为初始质心。
  2. 分配步骤:将每个数据点分配到距离最近的质心所代表的簇。
  3. 更新步骤:重新计算每个簇的质心(取簇内所有点的平均值)。
  4. 重复:重复步骤2和3,直到质心不再发生显著变化(收敛),或达到最大迭代次数。

4.3 评估:我们如何知道K选对了?

由于K-Means需要预先指定簇数K,这是该算法最大的挑战。我们使用以下两种主要方法来评估不同K值的好坏:

  1. 肘部法则(Elbow Method)
    绘制不同K值对应的WCSS(惯性)。随着K增加,WCSS会下降(因为簇更多,点离质心更近)。我们寻找WCSS下降速度突然变缓的“肘部”,这个点通常被认为是合适的K值。

  2. 轮廓系数(Silhouette Score)
    这是一个更全面的指标。对于单个样本,其轮廓系数定义为:
    s ( i ) = b ( i ) − a ( i ) max ⁡ ( a ( i ) , b ( i ) ) s(i) = \frac{b(i) - a(i)}{\max(a(i), b(i))} s(i)=max(a(i),b(i))b(i)a(i)

    • a ( i ) a(i) a(i):样本 i i i同一簇内所有其他点的平均距离(簇内不相似度)。
    • b ( i ) b(i) b(i):样本 i i i最近邻簇内所有点的平均距离(簇间不相似度)。
    • 轮廓系数范围为[-1, 1]。越接近1,说明样本聚类合理;接近0表示样本在簇边界上;负值可能表示样本被分错了簇。
    • 整体轮廓系数是所有样本轮廓系数的均值,用于评估整个聚类结果的质量。我们通常选择使整体轮廓系数最大的K。

5. 代码实现详解

5.1 数据加载与可视化(与监督学习相同的起点)

在没有标签的情况下,我们先对PCA降维后的数据进行可视化,直观感受数据的分布。

# 可视化原始数据(PCA降维后,颜色使用真实标签以便观察)
plt.figure(figsize=(10, 7))
scatter = plt.scatter(X_pca[:, 0], X_pca[:, 1], c=y, cmap=‘viridis’, edgecolor=‘k’, s=60, alpha=0.8)
plt.xlabel(‘主成分 1)
plt.ylabel(‘主成分 2)
plt.title(‘鸢尾花数据集 PCA降维 (颜色=真实类别))
plt.colorbar(scatter, label=‘真实类别’)
plt.grid(True, alpha=0.3)
plt.show()

图表解读:上图是知道真实答案的情况下数据的分布。但在实际无监督任务中,我们看不到颜色区分,只能看到一团数据点。K-Means的任务就是找出这些点的内在分组。

5.2 确定最佳簇数K:肘部法则与轮廓系数

我们让算法自动帮我们选择K。

# 定义要评估的K值范围
K_range = range(2, 11)

# 存储不同K值对应的WCSS(惯性)和轮廓系数
wcss = []
silhouette_scores = []

for k in K_range:
    kmeans = KMeans(n_clusters=k, init=‘k-means++, n_init=10, random_state=42)
    kmeans.fit(X_scaled) # 在标准化后的全特征上聚类
    wcss.append(kmeans.inertia_)
    silhouette_scores.append(silhouette_score(X_scaled, kmeans.labels_))

# 绘制肘部法则图
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 5))

ax1.plot(K_range, wcss, ‘bo-)
ax1.set_xlabel(‘簇数 (K))
ax1.set_ylabel(‘簇内平方和 (WCSS / Inertia))
ax1.set_title(‘肘部法则 (Elbow Method))
ax1.grid(True)

# 绘制轮廓系数图
ax2.plot(K_range, silhouette_scores, ‘ro-)
ax2.set_xlabel(‘簇数 (K))
ax2.set_ylabel(‘轮廓系数 (Silhouette Score))
ax2.set_title(‘轮廓系数 (Silhouette Score))
ax2.grid(True)

plt.tight_layout()
plt.show()

# 找出轮廓系数最大对应的K
best_k = K_range[np.argmax(silhouette_scores)]
print(f”根据轮廓系数,最佳K值为:{best_k})

图表解读

  • 左图(肘部法则):寻找曲线拐点。可以看到K从2增加到3时,WCSS下降显著;K>3后,下降变得平缓,“肘部”出现在K=3附近
  • 右图(轮廓系数):寻找峰值。K=2时轮廓系数最高,但结合领域知识(我们知道有3种花),以及肘部法则的观察,K=3是更合理、更符合实际的选择。这也说明了评估聚类质量需要结合多种指标和先验知识。

5.3 模型训练与预测

使用确定的最佳K值(这里取K=3)来训练最终的K-Means模型。

# 使用最佳K值(K=3)训练模型
kmeans_final = KMeans(n_clusters=3, init=‘k-means++, n_init=10, random_state=42)
kmeans_final.fit(X_scaled)

# 获取聚类标签和质心
cluster_labels = kmeans_final.labels_
centroids = kmeans_final.cluster_centers_

print(f”最终模型训练完成,WCSS: {kmeans_final.inertia_:.2f})
print(f”聚类标签分布: {np.bincount(cluster_labels)})

5.4 聚类结果可视化与分析

将聚类结果投射到PCA空间进行可视化。

# 创建可视化用的DataFrame
df[‘cluster’] = cluster_labels

# 使用PCA降维后的数据进行可视化
plt.figure(figsize=(14, 6))

# 子图1:K-Means聚类结果
plt.subplot(1, 2, 1)
scatter1 = plt.scatter(X_pca[:, 0], X_pca[:, 1], c=cluster_labels, cmap=‘Set1’, edgecolor=‘k’, s=60, alpha=0.8)
# 绘制质心(需要将质心也转换到PCA空间)
centroids_pca = pca.transform(scaler.transform(centroids))
plt.scatter(centroids_pca[:, 0], centroids_pca[:, 1], c=‘black’, s=200, marker=‘X’, label=‘质心’)
plt.xlabel(‘主成分 1)
plt.ylabel(‘主成分 2)
plt.title(f’K-Means聚类结果 (K={best_k}))
plt.legend()
plt.grid(True, alpha=0.3)

# 子图2:真实标签(用于对比)
plt.subplot(1, 2, 2)
scatter2 = plt.scatter(X_pca[:, 0], X_pca[:, 1], c=y, cmap=‘viridis’, edgecolor=‘k’, s=60, alpha=0.8)
plt.xlabel(‘主成分 1)
plt.ylabel(‘主成分 2)
plt.title(‘真实类别标签’)
plt.colorbar(scatter2, label=‘真实类别’)
plt.grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

图表解读:对比左右两图。K-Means的聚类结果(左图)在很大程度上还原了数据的真实分组(右图),尤其是在类别分明的区域。这证明K-Means成功发现了数据的内在结构。

6. 关键联系:从聚类到分类的桥梁

这是本文与前面文章产生联系的核心部分。我们探讨两个问题:

6.1 聚类结果 vs. 真实标签:评估与解释

虽然K-Means是无监督的,但如果我们有真实标签,可以计算聚类结果与真实标签的相似度,以评估聚类质量。

# 计算聚类结果与真实标签的匹配度(注意:聚类标签是任意的,需要重新映射才能与真实标签比较)
# Adjusted Rand Index (ARI):衡量两个分配的相似度,校正了随机一致性。1为完美匹配,0为随机。
# Normalized Mutual Information (NMI):基于信息论,衡量互信息。1为完美匹配。
ari_score = adjusted_rand_score(y, cluster_labels)
nmi_score = normalized_mutual_info_score(y, cluster_labels)

print(f”聚类结果与真实标签的相似度评估:”)
print(f”调整兰德指数 (ARI): {ari_score:.4f})
print(f”标准化互信息 (NMI): {nmi_score:.4f})

结果解读:ARI和NMI的值通常介于0.5到0.8之间,表明K-Means的聚类与真实的类别有较强的一致性,但并非完全相同。这可能是因为:

  1. 簇的形状假设:K-Means假设簇是凸的、各向同性的(球形),而真实数据分布可能更复杂。
  2. 重叠区域:不同类别的样本在特征空间中有重叠,K-Means会根据距离将其硬分配到一个簇。

6.2 进阶应用:聚类特征工程

这是无监督学习辅助监督学习的强大范式。 我们可以将K-Means的聚类标签作为一个新的特征,输入给后续的监督学习模型(如SVM或随机森林)。

from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score

# 将聚类标签作为新特征,与原始特征合并
X_with_cluster = np.column_stack([X_scaled, cluster_labels]) # 增加一列聚类标签

# 划分训练测试集(使用相同的random_state确保可比性)
X_train, X_test, y_train, y_test = train_test_split(
    X_with_cluster, y, test_size=0.3, random_state=42)

# 训练随机森林分类器(使用全部特征+聚类特征)
rf_model = RandomForestClassifier(n_estimators=100, random_state=42)
rf_model.fit(X_train, y_train)
y_pred_with_cluster = rf_model.predict(X_test)
acc_with_cluster = accuracy_score(y_test, y_pred_with_cluster)

# 训练只使用原始特征的随机森林作为基准
X_train_orig, X_test_orig, _, _ = train_test_split(
    X_scaled, y, test_size=0.3, random_state=42)
rf_model_orig = RandomForestClassifier(n_estimators=100, random_state=42)
rf_model_orig.fit(X_train_orig, y_train)
y_pred_orig = rf_model_orig.predict(X_test_orig)
acc_orig = accuracy_score(y_test, y_pred_orig)

print(f”使用【原始特征】的随机森林准确率: {acc_orig:.4f})
print(f”使用【原始特征 + 聚类特征】的随机森林准确率: {acc_with_cluster:.4f})

结果解读:你会发现,加入聚类特征后,分类准确率可能略有提升或保持稳定。这证明聚类特征为模型提供了额外的信息视角。在更复杂的数据集(如图像、文本)上,这种“聚类特征工程”的效果可能更加显著。

7. 总结与完整代码获取

本文我们完成了从监督学习到无监督学习的跨越:

  1. 掌握了K-Means聚类的核心原理:通过迭代优化,最小化簇内距离。
  2. 学会了使用肘部法则和轮廓系数科学地确定最佳簇数K。
  3. 在鸢尾花数据集上完整实现了K-Means聚类流程,并进行了可视化分析。
  4. 建立了关键联系:通过将聚类结果与真实标签对比,以及将聚类特征作为输入,直观展示了无监督学习如何为监督学习提供价值。

K-Means虽简单,却是理解聚类、原型学习和数据降维思想的基石。它的局限性(如需预设K、假设球形簇)也催生了如DBSCAN、层次聚类、谱聚类等更高级的算法。

本文的完整代码已整合在上文各步骤中,你可以直接复制运行,并尝试修改K值或使用其他数据集(如make_blobs生成数据)进行实验。

**如果你希望继续探索更强大的聚类算法,或者对“聚类特征工程”在实战中的应用有更多想法,欢迎在评论区交流!

8. 参考文献

  1. MacQueen, J. (1967). Some methods for classification and analysis of multivariate observations. Proceedings of the fifth Berkeley symposium on mathematical statistics and probability, 1(14), 281-297.
  2. Scikit-learn官方文档 - KMeans: https://scikit-learn.org/stable/modules/generated/sklearn.cluster.KMeans.html
  3. Scikit-learn官方文档 - Clustering Evaluation: https://scikit-learn.org/stable/modules/clustering.html#clustering-evaluation
  4. Rousseeuw, P. J. (1987). Silhouettes: a graphical aid to the interpretation and validation of cluster analysis. Journal of computational and applied mathematics, 20, 53-65.
  5. Arthur, D., & Vassilvitskii, S. (2007). k-means++: The advantages of careful seeding. Proceedings of the eighteenth annual ACM-SIAM symposium on Discrete algorithms, 1027-1035.

更多推荐