SL Scikit-Learn Learn机器学习教程

Lesson 13

K-means 聚类:无标签下的自动分群

聚类是最典型的无监督学习任务。本章从 K-means 的迭代步骤讲起,到如何选 k、如何用轮廓系数评估、最后用图像颜色量化做实战。

  • 预计时长40 分钟
  • 难度中等
  • 前置章节第 01 章

本章细分学习地图

K-means 是聚类的入门算法。它简单高效,但对初始化和簇形状有假设。每个模块帮你建立一个判断能力。

模块拆细学习单元完成后应产出掌握标准
算法步骤随机初始化 → 分配 → 更新质心 → 收敛判断。手动模拟一轮迭代。能画出两步迭代过程。
选择 k肘部法(inertia)、轮廓系数。k-inertia 折线图。能解释肘部拐点和轮廓系数含义。
sklearn 实战KMeans 训练、标签分配、质心查看。分群可运行代码。能解读 labels_cluster_centers_
图像颜色量化用 KMeans 把图像颜色压缩到 k 种。颜色量化代码。理解像素聚类的实际意义。

学习目标

K-means 迭代流程

1初始化 k 质心
2分配最近质心
3更新质心位置
4重复至收敛

本章完成度

已完成 0/4

本章核心判断

聚类没有"标准答案"。评估聚类好坏不是看准确率,而是看簇内紧凑、簇间分离,以及结果是否对业务有可解释的意义。

K-means 算法步骤

K-means 通过反复执行两个步骤来找到 k 个簇:

01
初始化

随机选 k 个点作为初始质心(sklearn 默认用 k-means++ 智能初始化)。

02
分配(E 步)

把每个样本分配给距离最近的质心所在的簇。

03
更新(M 步)

重新计算每个簇内所有样本的均值,作为新质心。

04
收敛

重复步骤 2-3,直到质心不再变化或达到最大迭代次数。

inertia(惯性)

K-means 优化的目标是 inertia:所有样本到其所属质心距离的平方和。inertia 越小,簇内越紧凑。

Inertia = Σi=1n ‖xi − μc(i)‖² μc(i) 是样本 xi 所属簇的质心

如何选择 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 的局限。