K-Means 聚类:最经典的无监督算法
没标签也能学。K-Means 是用户分群、图像压缩、市场分析的瑞士军刀——20 行代码就能写。
L2-01 我们看过:无监督学习的三大任务是聚类、降维、异常检测。
K-Means 是聚类里最知名、最常用的算法——简单到 20 行就能自己写,但用对了能做很大的事。
它解决什么
输入:一堆没标签的点。 输出:把它们分成 K 类,相似的归一类。
“K” 是你预先设定的——比如把客户分 5 类、把图像像素分 16 类。
经典应用:
- 客户分群:把 100 万用户分成 5 类(高价值新用户、低频流失等)
- 图像压缩:1600 万种颜色压成 16 种主要颜色,文件小 90%
- 文档聚类:自动把 1000 篇新闻分进若干主题
- 异常检测:和所有”簇”都远的点 = 异常
算法本身(4 步)
K-Means 简洁得让人意外——只有 4 步:
- 随机选 K 个点作为初始”中心”(centroid)
- 每个样本归到离自己最近的中心
- 更新每个中心 = 它的成员的平均位置
- 重复 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) | 图结构 / 非凸簇 |
| HDBSCAN | DBSCAN 改进版,参数更少 |
K-Means 仍然是默认选项——80% 业务场景它够用。其它算法是特定问题的备选。
一个有趣的应用:图像压缩
K-Means 还能做图像压缩:
- 把一张图的每个像素看作 3D 点(R, G, B)
- K-Means 把所有像素聚成 K 个颜色簇(比如 16 种)
- 把每个像素替换成它所属簇的中心颜色
- 结果:图片从 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 的)。
- 必须标准化:StandardScaler 是默认
- n_init >= 10:别只跑一次
- 选 K 时业务优先:算法只给参考
- 30 万样本以下:sklearn 默认实现没问题;更大用 MiniBatchKMeans
- 结果可视化:能讲给非技术同事听比算法精确更重要
下一篇:《评估指标 + 过拟合 + 正则化》 —— 让模型不犯傻的工程实战。
读到这里说明你认真在学 🎯
订阅每周精选 —— 下一篇新文章 / 新可视化第一时间送到邮箱。
讨论区
· 用 GitHub 账号登录评论src/components/Comments.astro 顶部填入
仓库 ID 和分类 ID(见组件注释里的配置步骤)。