HelloAI
L2 第 6 篇 🐣 难度 🕒 10 分钟

K-Means 聚类:最经典的无监督算法

没标签也能学。K-Means 是用户分群、图像压缩、市场分析的瑞士军刀——20 行代码就能写。

阿莱
2026/6/29

L2-01 我们看过:无监督学习的三大任务是聚类、降维、异常检测

K-Means 是聚类里最知名、最常用的算法——简单到 20 行就能自己写,但用对了能做很大的事。

它解决什么

输入:一堆没标签的点。 输出:把它们分成 K 类,相似的归一类。

“K” 是你预先设定的——比如把客户分 5 类、把图像像素分 16 类。

经典应用:

  • 客户分群:把 100 万用户分成 5 类(高价值新用户、低频流失等)
  • 图像压缩:1600 万种颜色压成 16 种主要颜色,文件小 90%
  • 文档聚类:自动把 1000 篇新闻分进若干主题
  • 异常检测:和所有”簇”都远的点 = 异常

算法本身(4 步)

K-Means 简洁得让人意外——只有 4 步:

  1. 随机选 K 个点作为初始”中心”(centroid)
  2. 每个样本归到离自己最近的中心
  3. 更新每个中心 = 它的成员的平均位置
  4. 重复 2-3 直到中心不再变化

就这么简单。

用图理解

初始:
●●  · ●  ●
   · ●  · ●●
●  ·  ·  ●

▲ ← K=2,随机两个中心

Step 1: 把每个点归到最近的中心
(左边的点都归▲₁,右边的点都归▲₂)

Step 2: 更新中心 = 各组的平均
▲₁ 移动到左侧点群的中心
▲₂ 移动到右侧点群的中心

Step 3: 再分配(这一轮可能没人换组)
Step 4: 中心不动 → 算法收敛

用 sklearn 跑一遍

from sklearn.cluster import KMeans
from sklearn.datasets import make_blobs
import numpy as np
import matplotlib.pyplot as plt

# 生成 4 个明显聚团的假数据
X, _ = make_blobs(n_samples=300, centers=4, cluster_std=0.6, random_state=42)

# K-Means
kmeans = KMeans(n_clusters=4, random_state=42, n_init=10)
kmeans.fit(X)

# 每个样本的聚类标签
labels = kmeans.labels_
print(labels[:10])   # [3 1 2 0 0 1 ...]

# 每个簇的中心
centers = kmeans.cluster_centers_
print(centers.shape)   # (4, 2)

# 可视化
plt.scatter(X[:, 0], X[:, 1], c=labels, cmap='viridis', s=20)
plt.scatter(centers[:, 0], centers[:, 1], c='red', marker='X', s=200)
plt.title('K-Means 结果')
plt.show()

3 行训练,几秒收敛,30 万样本毫无压力。这就是 K-Means 的工业适用性。

“K” 怎么选

最难的问题:你不知道有几类

3 种常用方法:

1. 肘部法(Elbow Method)

跑 K=1, 2, 3, …, 10,画出”每个 K 对应的总簇内距离平方和(inertia)”。

inertias = []
for k in range(1, 11):
    kmeans = KMeans(n_clusters=k, n_init=10).fit(X)
    inertias.append(kmeans.inertia_)

plt.plot(range(1, 11), inertias, 'bo-')
plt.xlabel('K'); plt.ylabel('Inertia')

曲线会有一个”肘部”(拐点)——通常那个 K 就是最合适的。

2. 轮廓系数(Silhouette Score)

衡量”每个样本和它所属簇的相似度 vs 和其他簇的距离”。

from sklearn.metrics import silhouette_score
for k in range(2, 8):
    kmeans = KMeans(n_clusters=k, n_init=10).fit(X)
    score = silhouette_score(X, kmeans.labels_)
    print(f"K={k}: silhouette={score:.3f}")

轮廓系数越接近 1 越好。

3. 业务知识

最实用的方法。 “我们运营团队认为客户大致就 4-5 类”——那 K=4 或 5。

现实:算法给的”最优 K” 经常和业务直觉冲突。这时候选业务直觉——聚类的价值在于”业务能用”。

K-Means 的弱点

必须预设 K:你需要业务知识或试错。

对初始中心敏感:不同的随机初始化可能得到不同结果。sklearn 的 n_init=10 是跑 10 次取最好的。

假设簇是”球形”:不适合月牙形、环形分布。对策:用 DBSCAN、谱聚类、层次聚类。

对异常值敏感:单个离群点能严重歪曲中心。对策:用 K-Medoids(用中位数代替均值)。

特征缩放敏感:量纲大的特征会主导。对策:先 StandardScaler。

关键预处理

from sklearn.preprocessing import StandardScaler

# 标准化(K-Means 必备)
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

# 现在再聚类
kmeans = KMeans(n_clusters=4, n_init=10).fit(X_scaled)

不标准化会怎样:如果”收入”以”万”为单位(数值 5-50),“年龄”以”年”为单位(20-60),收入的范围大很多——K-Means 完全按收入分群,年龄被忽略。几乎肯定不是你想要的

一个真实业务例子

任务:电商客户分群(这是 K-Means 最经典的用法之一)。

import pandas as pd
from sklearn.preprocessing import StandardScaler
from sklearn.cluster import KMeans
import matplotlib.pyplot as plt

# 假设你拿到了用户特征
df = pd.read_csv('customers.csv')
features = ['lifetime_value', 'days_since_last_order', 'order_frequency',
            'avg_order_value', 'product_categories_purchased']

X = df[features]

# 1. 标准化
X_scaled = StandardScaler().fit_transform(X)

# 2. 找最佳 K
inertias = []
for k in range(2, 11):
    inertias.append(KMeans(n_clusters=k, n_init=10).fit(X_scaled).inertia_)
plt.plot(range(2, 11), inertias, 'o-')

# 3. 假设肘部在 K=5
kmeans = KMeans(n_clusters=5, n_init=10, random_state=42).fit(X_scaled)
df['segment'] = kmeans.labels_

# 4. 看每个簇的特征
profile = df.groupby('segment')[features].mean()
print(profile)
# 这能让你定义簇——"高价值流失"、"新用户"、"沉睡用户" 等

这就是产品/运营/CRM 团队天天用 ML 的方式——不需要深度学习,K-Means 够了。

其它聚类算法(简介)

K-Means 不是万能。要知道它的”亲戚们”:

算法适用场景
DBSCAN任意形状的簇 + 自动确定 K(基于密度)
层次聚类(Hierarchical)输出”聚类树”,可视化好;K 可后定
GMM(高斯混合模型)软聚类(一个点属于某簇的”概率”)
谱聚类(Spectral)图结构 / 非凸簇
HDBSCANDBSCAN 改进版,参数更少

K-Means 仍然是默认选项——80% 业务场景它够用。其它算法是特定问题的备选。

一个有趣的应用:图像压缩

K-Means 还能做图像压缩

  1. 把一张图的每个像素看作 3D 点(R, G, B)
  2. K-Means 把所有像素聚成 K 个颜色簇(比如 16 种)
  3. 把每个像素替换成它所属簇的中心颜色
  4. 结果:图片从 16 万色变 16 色,文件大幅缩小
from sklearn.cluster import KMeans
from PIL import Image
import numpy as np

img = np.array(Image.open('photo.jpg'))
pixels = img.reshape(-1, 3)

# 聚成 16 色
kmeans = KMeans(n_clusters=16, n_init=3, random_state=42).fit(pixels)
compressed = kmeans.cluster_centers_[kmeans.labels_].astype(np.uint8)
Image.fromarray(compressed.reshape(img.shape)).save('compressed.jpg')

16 色就够看大致内容——这就是 GIF 文件的工作原理(也是 K-Means 的)。

💡 K-Means 工业实战要点
  • 必须标准化:StandardScaler 是默认
  • n_init >= 10:别只跑一次
  • 选 K 时业务优先:算法只给参考
  • 30 万样本以下:sklearn 默认实现没问题;更大用 MiniBatchKMeans
  • 结果可视化:能讲给非技术同事听比算法精确更重要

下一篇:《评估指标 + 过拟合 + 正则化》 —— 让模型不犯傻的工程实战。

📬

读到这里说明你认真在学 🎯

订阅每周精选 —— 下一篇新文章 / 新可视化第一时间送到邮箱。

💬

讨论区

· 用 GitHub 账号登录评论
⚠️ Giscus 评论未配置 —— 在 src/components/Comments.astro 顶部填入 仓库 ID 和分类 ID(见组件注释里的配置步骤)。