Lesson 13
K-means 聚类:无标签下的自动分群
聚类是最典型的无监督学习任务。本章从 K-means 的迭代步骤讲起,到如何选 k、如何用轮廓系数评估、最后用图像颜色量化做实战。
本章细分学习地图
K-means 是聚类的入门算法。它简单高效,但对初始化和簇形状有假设。每个模块帮你建立一个判断能力。
| 模块 | 拆细学习单元 | 完成后应产出 | 掌握标准 |
|---|---|---|---|
| 算法步骤 | 随机初始化 → 分配 → 更新质心 → 收敛判断。 | 手动模拟一轮迭代。 | 能画出两步迭代过程。 |
| 选择 k | 肘部法(inertia)、轮廓系数。 | k-inertia 折线图。 | 能解释肘部拐点和轮廓系数含义。 |
| sklearn 实战 | KMeans 训练、标签分配、质心查看。 | 分群可运行代码。 | 能解读 labels_ 和 cluster_centers_。 |
| 图像颜色量化 | 用 KMeans 把图像颜色压缩到 k 种。 | 颜色量化代码。 | 理解像素聚类的实际意义。 |
学习目标
K-means 迭代流程
本章完成度
已完成 0/4
聚类没有"标准答案"。评估聚类好坏不是看准确率,而是看簇内紧凑、簇间分离,以及结果是否对业务有可解释的意义。
K-means 算法步骤
K-means 通过反复执行两个步骤来找到 k 个簇:
随机选 k 个点作为初始质心(sklearn 默认用 k-means++ 智能初始化)。
把每个样本分配给距离最近的质心所在的簇。
重新计算每个簇内所有样本的均值,作为新质心。
重复步骤 2-3,直到质心不再变化或达到最大迭代次数。
K-means 优化的目标是 inertia:所有样本到其所属质心距离的平方和。inertia 越小,簇内越紧凑。
如何选择 k
K-means 需要预先指定 k。两种常用方法帮助选择合理的 k 值:
肘部法 Elbow Method
画 k vs inertia 折线图,找"肘部"拐点——inertia 下降速度明显减缓的位置。
轮廓系数 Silhouette Score
衡量每个样本与自身簇的紧凑度 vs 与最近其他簇的分离度。范围 [-1, 1],越高越好。
from sklearn.cluster import KMeans
from sklearn.datasets import make_blobs
from sklearn.metrics import silhouette_score
X, _ = make_blobs(n_samples=300, centers=4, random_state=42)
for k in range(2, 8):
km = KMeans(n_clusters=k, n_init=10, random_state=42)
labels = km.fit_predict(X)
sil = silhouette_score(X, labels)
print(f"k={k} inertia={km.inertia_:.0f} 轮廓系数={sil:.3f}")
sklearn 实战:客户分群
用 KMeans 对鸢尾花数据做无监督分群(假装不知道标签),然后与真实标签对比。
from sklearn.datasets import load_iris
from sklearn.cluster import KMeans
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import silhouette_score
import numpy as np
X, y_true = load_iris(return_X_y=True)
X_scaled = StandardScaler().fit_transform(X)
km = KMeans(n_clusters=3, n_init=10, random_state=42)
labels = km.fit_predict(X_scaled)
print(f"轮廓系数: {silhouette_score(X_scaled, labels):.3f}")
print(f"质心形状: {km.cluster_centers_.shape}") # (3, 4)
# 对比真实标签(注意:聚类标签编号和真实标签不一定对应)
from collections import Counter
for cluster_id in range(3):
mask = labels == cluster_id
true_labels = y_true[mask]
print(f"簇 {cluster_id}: {Counter(true_labels)}")
KMeans 输出的标签 0/1/2 只是编号,和真实类别的编号没有对应关系。对比时需要手动检查每个簇的组成。
图像颜色量化
把图像的每个像素看成 RGB 三维空间中的一个点,用 KMeans 聚成 k 个簇,把每个像素替换为其质心颜色,实现颜色压缩。
import numpy as np
from sklearn.cluster import KMeans
# 模拟一张 100x100 的 RGB 图像(随机像素)
np.random.seed(42)
image = np.random.randint(0, 256, size=(100, 100, 3), dtype=np.uint8)
# 展平成 (10000, 3) 的像素矩阵
pixels = image.reshape(-1, 3).astype(float)
# 聚类到 8 种颜色
km = KMeans(n_clusters=8, n_init=10, random_state=42)
labels = km.fit_predict(pixels)
# 用质心颜色替换每个像素
compressed = km.cluster_centers_[labels].reshape(image.shape).astype(np.uint8)
print(f"原始颜色数: {len(np.unique(pixels, axis=0))}")
print(f"压缩后颜色数: {len(np.unique(compressed.reshape(-1, 3), axis=0))}") # 8
| K-means 的优势 | K-means 的局限 |
|---|---|
| 算法简单高效,大数据集可用 MiniBatchKMeans。 | 需要预设 k,不能自动发现簇数。 |
| 适合球形/凸形簇。 | 对初始化敏感(用 k-means++ 缓解)。 |
| 结果易解释(质心即簇的代表)。 | 不适合椭圆形、环形、密度不均的簇。 |
练习题
第 1 题:K-means 的"M 步"做了什么?如果某个簇没有任何样本会怎样?
参考思路:M 步重新计算每个簇的均值作为新质心。空簇通常会被重新初始化(sklearn 默认行为),或导致警告。
第 2 题:轮廓系数为 -0.1 说明什么?
参考思路:轮廓系数为负说明部分样本被分到了错误的簇——它们离邻近簇的质心比自身簇更近。应重新选 k 或检查特征质量。
第 3 题:用 make_blobs 生成 4 个簇的数据,分别对 k=2,3,4,5,6 做 KMeans,画出 k vs 轮廓系数,找出最佳 k。
参考思路:轮廓系数最高的 k 通常与真实簇数一致(k=4)。代码参见本章"选择 k"部分。
检查点
离开本章前,请确认:
- 能画出 K-means 的两步迭代过程(分配 + 更新质心)。
- 能用肘部法和轮廓系数选择合理的 k 值。
- 能用 KMeans 完成分群并解读
labels_和cluster_centers_。 - 能说出 K-means 对初始化、簇形状和预设 k 的局限。