9
聚类:K-Means / DBSCAN / 层次聚类
Clustering: KMeans, DBSCAN, Hierarchical
无监督学习的代表:没有标签,让算法自己把"相似的"凑成一堆。客户分群、异常检测、图片压缩都用得到。
K-Means
论四步迭代
① 随机选 K 个中心点;② 每个样本归到最近中心;③ 重新算每簇中心(均值);④ 重复 ②③ 直到中心不动。问题:随机初始化可能卡局部最优,所以用 K-Means++(初始中心尽量散开)+ 多次运行。K 怎么选?肘部法则(Elbow Method):画 SSE 随 K 变化的曲线,找"拐点"。
from sklearn.cluster import KMeans
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import silhouette_score
# 假设 X 是客户特征(消费、频次、客单价)
scaler = StandardScaler()
X_s = scaler.fit_transform(X)
# K-Means++ + 跑 10 次取最好
kmeans = KMeans(n_clusters=4, init="k-means++", n_init=10,
random_state=42)
labels = kmeans.fit_predict(X_s)
# 轮廓系数:-1~1,越大越好(>0.5 不错)
score = silhouette_score(X_s, labels)
print(f"轮廓系数: {score:.3f}")
# 轮廓系数: 0.512
# 每簇大小
import numpy as np
for k in range(4):
print(f"簇 {k}: {(labels==k).sum()} 人")
DBSCAN:基于密度
论能发现任意形状的簇
K-Means 假设簇是"球形",遇到月牙形、环形就抓瞎。DBSCAN 看密度:核心点(半径 ε 内至少有 minPts 个邻居)、边界点(在核心点邻居里但自己邻居不够)、噪声点(谁也不是)。不用指定 K,自动识别噪声。缺点:ε 和 minPts 难调,密度不均匀时效果差。
from sklearn.cluster import DBSCAN
db = DBSCAN(eps=0.5, min_samples=5, metric="euclidean")
labels = db.fit_predict(X_s)
# label=-1 是噪声点
print(f"噪声点:{(labels==-1).sum()}")
层次聚类
Agglomerative(自底向上):开始每个点自己一簇,每次合并最近的两个,直到满意。画成树状图(Dendrogram)直观。适合小数据。
from sklearn.cluster import AgglomerativeClustering
agg = AgglomerativeClustering(n_clusters=4, linkage="ward")
labels = agg.fit_predict(X_s)
肘部法则选 K
# 画 SSE 随 K 变化曲线,找拐点
import matplotlib.pyplot as plt
sse = []
for k in range(1, 11):
km = KMeans(n_clusters=k, n_init=10, random_state=42)
km.fit(X_s)
sse.append(km.inertia_) # 簇内平方和
plt.plot(range(1, 11), sse, "o-")
plt.xlabel("K")
plt.ylabel("SSE")
plt.title("肘部法则:找拐点")
plt.show()
# 拐点通常在 K=3~5 附近
聚类对比表
| 维度 | K-Means | DBSCAN | 层次聚类 |
| 要指定 K? | 是 | 否 | 事后定 |
| 簇形状 | 球形 | 任意形状 | 任意 |
| 噪声点 | 不识别 | 自动识别 | 不识别 |
| 大数据 | 快 | 慢 | 很慢 O(n²) |
| 必须标准化 | 是 | 是 | 是 |
DBSCAN 参数怎么调
| 参数 | 说明 |
| eps(ε) | 邻域半径。太大所有点成一类,太大多类变噪声。常用 k-distance 图选拐点。 |
| min_samples | 核心点需要的邻居数。越大越严格,噪声越多。 |
| metric | 距离度量,默认欧氏。高维数据用余弦。 |
GMM 高斯混合模型:带"软归属"的聚类
K-Means 有个毛病:每个点必须"非黑即白"地判给某一类。但现实里一个用户可能 60% 像购物狂、40% 像游客。GMM(Gaussian Mixture Model)不硬判,而是告诉你"这个点属于每一类的概率是多少"——软聚类。
论大白话:K-Means 是"站队",GMM 是"打比例"
GMM 假设数据是由 K 个高斯分布(正态分布)混在一起生成的,每个分布就是一个"团"。它用 EM 算法同时学出:① 每个高斯的中心和形状;② 每个点属于每个高斯的概率。K-Means 其实是 GMM 在"高斯形状圆且等大、概率非 0 即 1"时的特例。
sklearn 跑 GMM
from sklearn.mixture import GaussianMixture
from sklearn.datasets import make_blobs
X, _ = make_blobs(n_samples=300, centers=3, random_state=0)
gmm = GaussianMixture(n_components=3, random_state=0)
gmm.fit(X)
# 硬归属:每个点最可能属于哪一类(和 K-Means 一样)
labels = gmm.predict(X)
# 软归属:关键!每个点属于各类的概率
proba = gmm.predict_proba(X)
print(proba[0])
# [9.9e-01 1.0e-08 1.2e-06] 第0个点 99% 属于第0类,但不是100%
# 选 K:用 BIC / AIC,越小越好(自动找合理的簇数)
for k in range(1, 6):
m = GaussianMixture(n_components=k, random_state=0).fit(X)
print(k, f"BIC={m.bic(X):.0f}")
应用场景:① 需要"打分"而非"贴标签"时(客户分层、异常检测——一个点对所有簇概率都低就是异常);② 簇的形状是椭圆而非圆形时,GMM 比 K-Means 拟合得好。
防坑:GMM 假设簇是高斯形
GMM 假设每个簇近似高斯分布。如果真实簇是弯的、环形的(像两个月牙),GMM 和 K-Means 都会切错,这种情况交给 DBSCAN 才对。另外它对初始值也敏感,记得多跑几次或用 n_init。
练习:GMM vs K-Means(点开对答案)
问:什么时候宁可慢也要用 GMM 而不是 K-Means?
答:当你需要"点属于每类的概率"(软聚类),或簇的形状是椭圆、大小不一的时候。只需要一个硬标签、簇近似球形且要快,K-Means 更简单直接。
本章面试题
面试 · 聚类
Q1. K-Means 为什么对初始中心敏感?
查看答案
目标函数非凸,不同初始中心可能收敛到不同局部最优。K-Means++ 让初始中心尽量分散,n_init 跑多次取最优缓解。
Q2. 怎么选 K?
查看答案
肘部法则看 SSE 拐点;轮廓系数看"簇内紧、簇间松";业务上也要看分出来能不能解释。
Q3. DBSCAN 相比 K-Means 优势?
查看答案
不用指定 K;能发现任意形状;能识别噪声点。
Q4. 聚类为什么必须标准化?
查看答案
聚类靠距离,量纲大的特征会主导距离。