聚类
聚类(Clustering)
from sklearn.cluster import KMeans
🔤 逐词翻译:
sklearn:scikit-learn,Python 最流行的机器学习库cluster(聚类):一种无监督学习任务,目标是把相似的数据点“分组”KMeans(K均值):最经典、最常用的聚类算法
✅ 所以这行代码的意思是:
“从 sklearn 的聚类模块中,导入 KMeans 算法”
🌟 什么是 KMeans?(大白话版)
想象你有一堆没标签的小球,随机摆在纸上(每个球有各自的位置),你想把它们按“位置远近”自动分成 3 堆。
KMeans 就像一个“自动分拣机器人”,它会:
先随便选 3 个小球位置当中心点(叫 聚类中心 / centroids)
把每个小球分配给离自己最近的中心
重新计算每堆的新中心(取该堆所有球位置的平均坐标)
重复步骤 2~3,直到中心不再怎么移动
✅ 最终,你就得到了 K 个簇(clusters),每簇内的球彼此位置近(相似度高),不同簇之间的球位置远(差异大)。
💡 K 是你提前指定的簇的数量(比如 K=3)
💻 基本用法示例:
from sklearn.cluster import KMeans
import numpy as np
# 明确数据含义:每行 = [用户年龄, 月消费金额(元)],共6个用户
X = np.array([
[18, 200], # 用户1:18岁,月消费200元
[20, 400], # 用户2:20岁,月消费400元
[19, 150], # 用户3:19岁,月消费150元
[35, 220], # 用户4:35岁,月消费220元
[38, 450], # 用户5:38岁,月消费450元
[36, 180] # 用户6:36岁,月消费180元
])
# 创建KMeans模型:分成2类(年轻人/中年人),固定random_state确保结果可复现
kmeans = KMeans(n_clusters=2, random_state=42, n_init=10) # n_init=10避免初始中心选得太差
# 训练模型(无监督学习,不需要标签y)
kmeans.fit(X)
# 1. 查看每个用户的聚类标签(0和1只是类别代号,无大小意义!)
print("每个用户的聚类标签(0=年轻人,1=中年人):")
print(kmeans.labels_)
# 固定输出:[0 0 0 1 1 1](因为random_state=42,标签顺序不会乱)
# 👉 关键说明:如果把random_state改成其他数,可能输出[1 1 1 0 0 0],但类别完全一样,只是代号互换
# 2. 查看两个聚类中心(= 每类用户的「平均年龄」和「平均消费金额」)
print("\n两个聚类中心([平均年龄, 平均月消费]):")
cluster_centers = kmeans.cluster_centers_
print("年轻人聚类中心:", cluster_centers[0].round(1)) # 保留1位小数,更直观
print("中年人聚类中心:", cluster_centers[1].round(1))
# 输出:
# 年轻人聚类中心: [19. 250. ] (平均19岁,月均消费250元)
# 中年人聚类中心: [36.3 283.3] (平均36.3岁,月均消费283.3元)
"""
KMeans 的第一步是 “随便选 K 个中心点”(比如 K=2 时选 2 个点),这个 “随便” 其实是计算机的 “伪随机”—— 如果不固定random_state,每次运行程序都会选不同的初始中心点;如果固定了random_state,每次运行都会选相同的初始中心点。
"""
⚙️ 常用参数解释:
| 参数 | 中文 | 说明 |
|---|---|---|
n_clusters=8 |
聚类数量 | 默认分 8 类,你通常要自己指定(比如 2、3、5) |
random_state |
随机种子 | 保证结果可复现(就像“固定随机数”) |
init='k-means++' |
初始化方法 | 默认用 k-means++(比随机选中心更稳) |
max_iter=300 |
最大迭代次数 | 防止无限循环 |
✅ KMeans 的优缺点
| 优点 | 缺点 |
|---|---|
| ✔️ 简单、快速、易用 | ❌ 必须提前知道 K 是多少(可用“肘部法则”或轮廓系数辅助) |
| ✔️ 对球形簇效果很好 | ❌ 对非球形簇(如月牙形)效果差 |
| ✔️ 可扩展到大数据(有优化版) | ❌ 对异常值敏感(因为用“均值”) |
📈 如何选择 K?—— 肘部法则(Elbow Method)
import matplotlib.pyplot as plt
from sklearn.cluster import KMeans
# 计算不同 K 值下的误差平方和(inertia)
inertias = []
K_range = range(1, 10)
for k in K_range:
kmeans = KMeans(n_clusters=k)
kmeans.fit(X)
inertias.append(kmeans.inertia_)
# 画图
plt.plot(K_range, inertias, 'bo-')
plt.xlabel('K (聚类数量)')
plt.ylabel('簇内误差平方和 (Inertia)')
plt.title('肘部法则')
plt.show()
👉 找“拐点”(elbow point)——之后下降变缓的地方,就是合适的 K。
✅ 总结:
KMeans= 自动分组工具
给一堆没标签的数据,它能帮你找出隐藏的类别结构,广泛用于:
- 客户分群(高价值 vs 低活跃)
- 图像压缩(把相近颜色合并)
- 文档聚类(自动归类新闻主题)
当然可以!我们用大白话 + 图解思维 + 例子,彻底搞懂 轮廓系数(Silhouette Score) 是什么、为什么重要、怎么用。
轮廓系数(Silhouette Score)
🌟 一句话总结:
轮廓系数 = 衡量聚类结果“好不好”的打分卡,分数越接近 1 越好,越接近 -1 越差。
它能告诉你:
✅ 每个样本是不是被分到了“对的簇”?
✅ 整体聚类结构是否清晰?
🔍 为什么需要它?
KMeans 需要你提前指定 K(聚类数量),但你怎么知道 K=3 比 K=4 更好呢?
→ 轮廓系数就是帮你选 K 的“黄金指标”之一!
🧠 核心思想(超通俗版)
对每一个样本点,计算两个距离:
-
a = 到自己簇内其他点的平均距离
→ 越小越好(说明“我和自己人很近”) -
b = 到最近的其他簇的所有点的平均距离
→ 越大越好(说明“我和外人很远”)
然后定义这个点的轮廓系数 s:
[
s = \frac{b - a}{\max(a, b)}
]
- 如果 s ≈ 1:说明 a 很小、b 很大 → 分得非常合理!
- 如果 s ≈ 0:说明 a ≈ b → 在两个簇边界上,模棱两可
- 如果 s < 0:说明 a > b → 你可能被分错簇了!
✅ 所有点的 s 取平均,就是整体轮廓系数(范围:[-1, 1])
🖼️ 示意图(文字版)
簇 A 簇 B
● ● ● ○ ○ ○
● ○
↑
这个点离簇A近(a小),离簇B远(b大)→ s ≈ 1 ✅
边界点:
● ● | ○ ○
↑
这个点离两边差不多(a≈b)→ s ≈ 0 ⚠️
错误点:
● ● ○ ← 这个○被分到●簇
↑
它离自己簇(●)远(a大),离○簇近(b小)→ s < 0 ❌
💻 Python 怎么算?
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score
from sklearn.datasets import make_blobs
import matplotlib.pyplot as plt
# 生成示例数据(3个自然簇)
X, _ = make_blobs(n_samples=300, centers=3, cluster_std=0.6, random_state=42)
# 尝试不同 K 值
K_range = range(2, 10)
scores = []
for k in K_range:
kmeans = KMeans(n_clusters=k, random_state=42)
labels = kmeans.fit_predict(X)
score = silhouette_score(X, labels)
scores.append(score)
print(f"K={k}, 轮廓系数 = {score:.3f}")
# 画图找最高分
plt.plot(K_range, scores, 'bo-')
plt.xlabel('聚类数量 K')
plt.ylabel('轮廓系数')
plt.title('选择最优 K:轮廓系数越大越好')
plt.grid(True)
plt.show()
输出示例:
K=2, 轮廓系数 = 0.582
K=3, 轮廓系数 = 0.721 ← 最高!
K=4, 轮廓系数 = 0.598
...
✅ 所以最优 K = 3(和真实情况一致!)
✅ 轮廓系数 vs 肘部法则(Inertia)
| 方法 | 优点 | 缺点 |
|---|---|---|
| 肘部法则(Inertia) | 计算快 | 主观(“拐点”不好判断) |
| 轮廓系数 | 客观打分,有明确范围 [-1,1] | 计算稍慢(需算所有点对距离) |
💡 推荐组合使用:先用肘部法缩小范围,再用轮廓系数精确定 K。
⚠️ 注意事项
- 不适用于密度不均的簇(比如 DBSCAN 更适合月牙形数据)
- 默认用欧氏距离,如果特征尺度差异大,记得先标准化(
StandardScaler) - K=1 时无法计算(因为没有“其他簇”)
✅ 总结:
| 概念 | 含义 |
|---|---|
| 轮廓系数(Silhouette Score) | 聚类质量的客观评分(-1 ~ 1) |
| s ≈ 1 | 样本在正确簇中,且簇间分离清晰 |
| s ≈ 0 | 样本在簇边界,分类模糊 |
| s < 0 | 样本很可能被分错簇 |
| 用途 | 选择最优聚类数量 K,评估聚类效果 |
✅ 它就像给每个样本发一张“满意度调查表”:
“你对自己所在的群满意吗?和隔壁群比呢?”
最后取全班平均分!
可视化单个样本的轮廓图
轮廓图(Silhouette Plot) 是一种非常直观的可视化方法,能让你一眼看出:
- 每个簇的内部紧密程度
- 是否有分错的样本(轮廓系数为负)
- 聚类数量 K 是否合适
下面我给你一个 完整、可运行的 Python 示例,并用手写数字数据集演示如何画出漂亮的轮廓图!
✅ 完整代码:可视化单个样本的轮廓图
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import load_digits
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_samples, silhouette_score
from sklearn.preprocessing import StandardScaler
# ----------------------------
# 1. 加载数据
# ----------------------------
digits = load_digits()
X = digits.data
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
# ----------------------------
# 2. 设置聚类数量 K(尝试 K=8, 9, 10, 11 看效果)
# ----------------------------
K = 10 # 你可以改成 8, 9, 11 对比
kmeans = KMeans(n_clusters=K, random_state=42)
cluster_labels = kmeans.fit_predict(X_scaled)
# ----------------------------
# 3. 计算轮廓系数(每个样本 + 整体)
# ----------------------------
silhouette_avg = silhouette_score(X_scaled, cluster_labels)
sample_silhouette_values = silhouette_samples(X_scaled, cluster_labels)
print(f"✅ K = {K} 时,平均轮廓系数 = {silhouette_avg:.3f}")
# ----------------------------
# 4. 画轮廓图
# ----------------------------
plt.figure(figsize=(10, 8))
y_lower = 10 # 起始 y 坐标(留白)
colors = plt.cm.tab10.colors # 使用 10 种颜色(足够 K≤10)
for i in range(K):
# 获取第 i 个簇的所有样本的轮廓系数
ith_cluster_silhouette_values = sample_silhouette_values[cluster_labels == i]
ith_cluster_silhouette_values.sort() # 排序,让图形更整齐
size_cluster_i = ith_cluster_silhouette_values.shape[0]
y_upper = y_lower + size_cluster_i
# 填充颜色区域
color = colors[i % len(colors)]
plt.fill_betweenx(
np.arange(y_lower, y_upper),
0,
ith_cluster_silhouette_values,
facecolor=color,
edgecolor=color,
alpha=0.7
)
# 在图上标注簇编号
plt.text(-0.05, y_lower + 0.5 * size_cluster_i, str(i))
# 更新下一起始位置
y_lower = y_upper + 10 # 每个簇之间留一点空隙
# 画平均轮廓系数的竖线
plt.axvline(x=silhouette_avg, color="red", linestyle="--", linewidth=2, label=f'平均轮廓系数 = {silhouette_avg:.3f}')
# 设置坐标轴
plt.xlim([-0.1, 1])
plt.ylim([0, y_lower])
plt.xlabel('轮廓系数值')
plt.ylabel('簇标签')
plt.title(f'K={K} 的轮廓图(Silhouette Plot)')
plt.legend(loc='upper right')
plt.tight_layout()
plt.show()
🔍 图形解读(关键!)
📌 X 轴:轮廓系数 s ∈ [-1, 1]
- 越靠右越好(接近 1)
- 红线:所有样本的平均轮廓系数
📌 Y 轴:每个簇的样本(按簇分组)
- 每个彩色块 = 一个簇
- 块的高度 = 该簇的样本数量
- 块的宽度 = 该簇样本的轮廓系数分布
✅ 好的聚类应满足:
- 所有块都 > 0(没有负值)
- 各块宽度相近(说明各簇质量均衡)
- 红线靠右(平均分高)
- 各块高度差不多(说明没有特别小的“垃圾簇”)
❌ 坏的信号:
- 某个块延伸到负值区 → 有样本被分错
- 某个块非常窄 → 这个簇内部松散
- 某个块特别矮 → 可能是噪声或 K 设多了
🖼️ 示例对比(想象图)
| K=10(合理) | K=12(可能过聚类) |
|---|---|
| 所有块在 0.0~0.4 之间,无负值 | 有几个块很窄,甚至部分样本 s < 0 |
| 平均 s ≈ 0.13 | 平均 s 反而下降 |
💡 手写数字本身不是“球形簇”,所以轮廓系数不会很高(通常 0.1~0.2),但相对比较仍有效!
🧪 小实验:试试不同 K
把 K = 10 改成 K = 8 或 K = 12,运行几次,观察:
- 平均轮廓系数如何变化?
- 是否出现负值区域?
- 哪个 K 的图看起来最“整齐”?
你会发现:K=10 通常是最合理的(因为真实有 10 个数字)!
✅ 总结:轮廓图的作用
| 功能 | 说明 |
|---|---|
| 评估单个样本 | 看它是否被合理分配 |
| 诊断问题簇 | 找出松散或错误的簇 |
| 辅助选 K | 选平均轮廓系数最高、且无负值的 K |
| 可视化聚类质量 | 比单一数字更直观 |
这个图在论文、报告中非常专业,而且一行代码不能搞定,必须手动绘制——你现在已经掌握它了!🎉
一个完整、可运行的 Python 案例
下面是一个完整、可运行的 Python 案例,使用 KMeans 对手写数字(load_digits)进行无监督聚类 —— 即使没有标签,我们也能看看 KMeans 能不能自动把 0~9 分成 10 类!
我们会:
- 加载数据(不使用标签)
- 用 KMeans 聚类(K=10)
- 用真实标签评估聚类效果(虽然训练时没用它)
- 可视化每个簇的“代表图像”
- 计算轮廓系数 & 调整兰德指数(ARI)
✅ 完整代码(带详细中文注释)
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import load_digits
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score, adjusted_rand_score
from sklearn.preprocessing import StandardScaler
# ==============================
# 1. 加载手写数字数据集(只用图像,不用标签)
# ==============================
digits = load_digits()
X = digits.data # shape: (1797, 64) —— 每张图是 8x8=64 维向量
y_true = digits.target # 真实标签(仅用于后续评估,KMeans 不会看到它!)
print(f"数据形状: {X.shape}")
print("注意:KMeans 是无监督学习,训练时完全不知道 y_true!")
# ==============================
# 2. (可选)标准化特征 —— 对 KMeans 很重要!
# ==============================
# 因为 KMeans 基于欧氏距离,如果某些像素值范围大,会主导结果
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
# ==============================
# 3. 使用 KMeans 聚类(假设我们知道有 10 个数字)
# ==============================
kmeans = KMeans(n_clusters=10, random_state=42, n_init=10)
y_pred = kmeans.fit_predict(X_scaled) # 得到每个样本的聚类标签
# ==============================
# 4. 评估聚类效果(用真实标签做“事后检验”)
# ==============================
# (a) 轮廓系数(不需要真实标签)
sil_score = silhouette_score(X_scaled, y_pred)
print(f"\n🎯 轮廓系数: {sil_score:.3f}(越接近1越好)")
# (b) 调整兰德指数 ARI(需要真实标签,衡量聚类与真实标签的一致性)
ari_score = adjusted_rand_score(y_true, y_pred)
print(f"🎯 调整兰德指数 (ARI): {ari_score:.3f}(越接近1越好,0表示随机)")
# ==============================
# 5. 可视化:每个簇的“中心图像”(即聚类中心 reshape 成 8x8)
# ==============================
fig, axes = plt.subplots(2, 5, figsize=(12, 6))
centers = kmeans.cluster_centers_ # shape: (10, 64)
# 注意:因为用了 StandardScaler,中心值不是原始像素,需反变换回近似原始尺度
centers_original_scale = scaler.inverse_transform(centers)
for i in range(10):
ax = axes[i // 5, i % 5]
# 将 64 维向量转为 8x8 图像,并限制在 [0, 16](原始 digits 数据范围)
img = centers_original_scale[i].reshape(8, 8)
img = np.clip(img, 0, 16) # 防止负值或过大值
ax.imshow(img, cmap='gray_r')
ax.set_title(f'簇 {i}', fontsize=14)
ax.set_axis_off()
plt.suptitle('KMeans 聚类中心(每个簇的“平均数字”)', fontsize=16)
plt.tight_layout()
plt.show()
# ==============================
# 6. (进阶)查看每个簇中真实的数字分布
# ==============================
from sklearn.metrics import confusion_matrix
import seaborn as sns
# 画混淆矩阵:行=真实标签,列=聚类标签
cm = confusion_matrix(y_true, y_pred)
plt.figure(figsize=(10, 8))
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues')
plt.xlabel('KMeans 聚类标签')
plt.ylabel('真实数字标签')
plt.title('聚类结果 vs 真实标签(看是否对齐)')
plt.show()
🔍 关键点解释
1. 为什么能聚类手写数字?
- 虽然 KMeans 不知道“这是 3 还是 8”,但它发现:
- 所有“圈多”的图像彼此相似 → 自动聚成一类(可能是 8 或 9)
- 所有“竖线”的图像彼此相似 → 聚成另一类(可能是 1)
- 前提是:同类数字的图像在像素空间中确实聚集在一起
2. 为什么要标准化(StandardScaler)?
- 原始像素值范围是 0~16,但不同位置的像素方差不同
- 标准化让每个像素“平等参与距离计算”,避免某些位置主导结果
3. 调整兰德指数(ARI)是什么?
- 衡量两个标签分配的一致性(即使标签编号不同)
- 比如:聚类把“0”标成“5”,但所有 0 都在一个簇 → ARI 仍高
- ARI = 1:完美匹配;ARI ≈ 0:相当于随机分
4. 聚类中心图像是怎么来的?
- KMeans 的
cluster_centers_是 64 维向量 - 把它 reshape 成 8×8,就得到“这个簇的平均数字长什么样”
📊 典型输出示例
数据形状: (1797, 64)
注意:KMeans 是无监督学习,训练时完全不知道 y_true!
🎯 轮廓系数: 0.132
🎯 调整兰德指数 (ARI): 0.678
- 轮廓系数偏低(~0.13):因为手写数字簇不是球形,且有些数字相似(如 4/9, 3/8)
- ARI 较高(~0.68):说明聚类结果和真实标签有较强对应关系!
💡 即使没有标签,KMeans 也能大致把数字分开!
🎯 总结
| 步骤 | 目的 |
|---|---|
加载 load_digits().data |
获取无标签图像数据 |
| 标准化 | 提升 KMeans 效果 |
KMeans(n_clusters=10) |
自动分 10 类 |
| 轮廓系数 | 评估簇内紧密度 & 簇间分离度 |
| ARI + 混淆矩阵 | 用真实标签“事后验证”效果 |
| 可视化中心 | 看每个簇学到了什么数字 |
✅ 这就是无监督学习的魅力:
即使没人告诉你“这是什么数字”,算法也能从数据中发现结构!

浙公网安备 33010602011771号