第49课:K-Means 聚类算法
1. 学习目标
- 理解 K-Means 的核心思想:能够用通俗的语言解释 K-Means 算法是如何工作的。
- 掌握 K-Means 的算法步骤:清晰地复述算法的初始化、分配、更新、迭代四个阶段。
- 使用 Python 实现 K-Means:能利用
scikit-learn库对数据进行聚类分析并可视化结果。 - 理解 K 值选择的重要性:掌握“肘部法则”用于估计最佳簇数量。
- 分析 K-Means 的优缺点:知道该算法适用于什么场景,以及它在哪些情况下可能失效。
2. 核心概念
想象一下,你有一大堆积木,颜色、形状各不相同。你的任务是把它们分成几组(簇),但规则是:同组内的积木要尽可能相似,不同组的积木要尽可能不同。你没有提前定义“相似”的标准,而是希望通过观察数据本身来发现分组。
这就是聚类,一种无监督学习任务。K-Means 是其中最经典、最常用的算法之一。
K-Means 的核心思想可以简单概括为四个步骤,我们称之为 “物以类聚,人以群分”的迭代过程:
- 初始化:随机选择
K个数据点作为初始的“簇中心”(质心)。K是你事先设定的想要划分的簇的数量。 - 分配:计算数据集中每个点到这
K个簇中心的距离。将每个点分配给距离最近的那个簇中心,形成K个临时的簇。 - 更新:对刚刚形成的
K个簇,重新计算每个簇的质心(即簇内所有点的平均值)。 - 迭代:重复步骤2和3(分配和更新),直到簇的分配不再发生变化(或变化极小),此时我们认为算法收敛了。
关键点:K 的选择
K 值对结果影响巨大。我们通常使用肘部法则来辅助选择。其思路是:计算不同 K 值下的簇内误差平方和,然后绘图。随着 K 增大,误差会减小。寻找图中曲线从急剧下降到趋于平缓的“肘部”点,这个拐点通常被认为是较好的 K 值。
优点:简单、快速、可解释。
缺点:需要预先指定 K;对初始质心敏感(可能陷入局部最优);假设簇是凸形和各向同性的,对复杂形状的簇(如环形)效果不佳。
3. 代码示例
下面是一个完整的 Python 示例,我们将生成模拟数据,并用 K-Means 进行聚类。
# 导入必要的库
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import make_blobs
from sklearn.cluster import KMeans
from sklearn.preprocessing import StandardScaler
# 1. 生成模拟数据 (4个簇)
np.random.seed(42)
X, y_true = make_blobs(n_samples=300, centers=4, cluster_std=0.60, random_state=0)
# 2. 数据标准化 (K-Means基于距离,标准化很重要)
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
# 3. 使用“肘部法则”估计最佳K值
inertias = []
K_range = range(1, 10)
for k in K_range:
kmeans = KMeans(n_clusters=k, random_state=42, n_init=10)
kmeans.fit(X_scaled)
inertias.append(kmeans.inertia_) # inertia_ 即 SSE
# 绘制肘部图
plt.figure(figsize=(10, 5))
plt.plot(K_range, inertias, 'bo-')
plt.xlabel('簇的数量 (K)')
plt.ylabel('簇内误差平方和 (SSE)')
plt.title('使用肘部法则确定最佳K值')
plt.grid(True)
plt.show()
# 从图中我们可以看到 K=4 是一个明显的“肘点”
# 4. 使用K=4进行聚类
kmeans_final = KMeans(n_clusters=4, random_state=42, n_init=10)
kmeans_final.fit(X_scaled)
# 获取聚类结果和质心
labels = kmeans_final.labels_
centers = kmeans_final.cluster_centers_
# 5. 可视化聚类结果
plt.figure(figsize=(12, 5))
# 原始数据
plt.subplot(1, 2, 1)
plt.scatter(X_scaled[:, 0], X_scaled[:, 1], c='gray', s=40, alpha=0.6)
plt.title('原始数据 (标准化后)')
plt.xlabel('特征 1')
plt.ylabel('特征 2')
plt.grid(True)
# 聚类结果
plt.subplot(1, 2, 2)
# 为每个簇分配不同颜色
colors = ['red', 'blue', 'green', 'purple']
for i in range(4):
cluster_points = X_scaled[labels == i]
plt.scatter(cluster_points[:, 0], cluster_points[:, 1],
s=40, c=colors[i], alpha=0.6, label=f'簇 {i}')
# 绘制质心
plt.scatter(centers[:, 0], centers[:, 1], s=200, c='black', marker='X', linewidths=2, label='质心')
plt.title('K-Means 聚类结果 (K=4)')
plt.xlabel('特征 1')
plt.ylabel('特征 2')
plt.legend()
plt.grid(True)
plt.tight_layout()
plt.show()
# 6. 查看每个簇包含的样本数
print("每个簇的样本数量:")
for i in range(4):
print(f"簇 {i}: {np.sum(labels == i)} 个样本")
4. 实践练习
练习 1:改变 K 值
在上面的代码基础上,尝试将 K 值分别设置为 2, 3, 5, 6,并重新运行聚类和可视化部分。观察聚类结果和质心位置的变化,思考为什么 K=4 在本数据中是合适的。
练习 2:使用真实数据集
使用 sklearn 内置的鸢尾花数据集 (load_iris)。该数据集有 4 个特征和 3 个真实类别。
- 任务:忽略真实标签,只用特征数据 (X) 进行 K-Means 聚类。尝试用肘部法则选择 K,并将聚类结果 (
labels) 与真实标签 (y) 进行对比。你能发现什么? - 提示:你可能需要先用
pandas或print查看聚类标签和真实标签的对应关系,它们可能只是数字顺序不同(如簇0对应类别2)。
练习 3:手写简化版 K-Means (选做)
尝试不用 sklearn,自己实现一个最简单的 K-Means 函数。要求:
- 输入:数据
X, 簇数K, 最大迭代次数max_iters。 - 过程:实现初始化、分配、更新三个核心步骤的循环。
- 输出:返回每个点的簇标签
labels和最终的质心centers。 (提示:使用numpy的np.random.choice初始化质心,用np.linalg.norm计算距离,用np.argmin找到最近质心)。
5. 常见错误
- 忽略数据标准化:K-Means 是基于距离的算法(如欧氏距离)。如果特征之间尺度差异很大(例如,一个特征范围是 [0, 1],另一个是 [0, 10000]),那么大尺度的特征会主导距离计算,导致聚类结果失真。务必在聚类前对数据进行标准化(StandardScaler)或归一化。
K值选择过于随意:不要凭感觉选择K。一定要结合业务背景,并借助“肘部法则”等数学工具来辅助判断。选错K会导致要么把一个簇强行拆开(K过大),要么把不同的簇强行合并(K过小)。- 误判簇的形状:K-Means 天然假设簇是“圆形”的(在标准化后的空间里)。对于拉长形状、环形或不规则形状的簇,K-Means 的效果会很差。这时应考虑其他聚类算法,如 DBSCAN 或层次聚类。
- 结果不稳定:由于初始质心是随机选取的,K-Means 可能收敛到不同的局部最优解。
scikit-learn中的KMeans通过参数n_init(默认为10)多次运行并取最佳结果来缓解这个问题,但理解这个局限性很重要。
6. 小结
- K-Means 是什么:一种通过迭代优化来将数据划分为 K 个簇的无监督学习算法。其核心是“分配-更新”循环。
- 四步流程:初始化质心 -> 分配点到最近质心 -> 更新质心为簇内均值 -> 重复直至收敛。
- 关键参数
K:必须预先指定。使用肘部法则(绘制不同 K 值的 SSE 曲线)来寻找拐点,估计最佳 K 值。 - Python 实现:使用
sklearn.cluster.KMeans类。fit(X)训练模型,.labels_获取标签,.cluster_centers_获取质心。 - 注意事项:一定要对数据标准化;理解它对簇形状的限制;知道它可能对初始化敏感。
- 适用场景:数据量较大、簇形状相对规则(凸形)、且需要快速得到一个初步分组结果的场景,如客户细分、图像压缩(颜色量化)、文档主题聚类等。
K-Means 就像一把“聚类瑞士军刀”,简单实用。掌握它,你就拥有了探索无监督学习世界的第一把钥匙。下一课,我们将学习另一种强大的聚类算法——层次聚类,它从完全不同的角度来构建簇结构。