1. 聚类解决什么问题
给定没有标签的数据集 \(X=\{x_1,x_2,\ldots,x_n\},\quad x_i\in\mathbb R^d,\) 聚类希望找到一个分组函数 $z_i\in{1,\ldots,K}$,或者更一般的层次结构、密度区域与软归属概率,使得同一组内相似、不同组间有差异。这句话看似直观,却隐藏了三个必须先回答的问题:
- “相似”用什么度量?欧氏距离、余弦距离、马氏距离,还是图上的连通性?
- “组”是什么形状?球状、椭圆、任意密度连通,还是流形上的局部邻域?
- 结果如何验证?无监督场景没有唯一答案,指标只能回答某一种假设下的好坏。
因此聚类不是一个单一算法,而是一族带有不同归纳偏置(inductive bias)的模型。K-Means 假设簇近似球形且方差相近;GMM 假设数据由多个高斯分布混合生成;DBSCAN 假设簇是高密度区域并允许噪声;谱聚类则把数据看成相似度图上的划分问题。算法没有脱离假设的“最好”,只有和数据生成机制更匹配的选择1。
1.1 硬聚类、软聚类与重叠聚类
- 硬聚类:每个样本只属于一个簇,例如 K-Means 的标签。
- 软聚类:输出 $p(z_i=k\mid x_i)$,一个样本可同时对多个簇有较高概率,例如 GMM。
- 重叠/模糊聚类:输出隶属度 $u_{ik}\in[0,1]$,且不一定满足概率归一,便于表达边界模糊的群组。
- 层次聚类:不预先固定 $K$,输出一棵树(dendrogram),用户在不同高度切割得到不同粒度。
- 密度聚类:簇由高密度区域定义,孤立点被标记为噪声。
1.2 统一视角:目标函数与归纳偏置
大多数算法都可写成“在假设空间中优化某个目标”:
\[\hat\theta=\arg\min_\theta \mathcal L(X;\theta) \quad\text{或}\quad \hat\theta=\arg\max_\theta \log p(X;\theta).\]区别在于:
- K-Means 最小化簇内平方误差(SSE)。
- GMM 最大化观测数据似然。
- 谱聚类最小化图割目标的松弛形式。
- DBSCAN 不显式优化全局目标,而是按密度可达关系构造连通分量。
- 深度聚类同时学习表示 $f_\phi(x)$ 与簇结构。
理解目标函数比记 API 更重要:当结果异常时,你可以回到目标函数判断是尺度、距离、初始化还是模型假设导致的。
2. 开始前的数学工具箱
2.1 距离与相似度
欧氏距离
\(d_2(x,y)=\sqrt{\sum_{j=1}^{d}(x_j-y_j)^2}.\) 平方欧氏距离去掉了开方,排序关系不变,适合 K-Means 与高斯模型。它对量纲极其敏感:年龄(0–100)和年收入(0–1,000,000)直接相加没有意义。
曼哈顿距离
\(d_1(x,y)=\sum_j|x_j-y_j|.\) 在高维稀疏或含异常值的数据上通常比欧氏距离更稳健,但对应的几何等值面是菱形,和 K-Means 的平方损失不匹配。
余弦相似度
\(\operatorname{cos}(x,y)=\frac{x^\top y}{\|x\|_2\|y\|_2}.\) 文本 TF-IDF、归一化 embedding 常用余弦距离 $1-\operatorname{cos}$。若向量方向比长度更重要,应先 L2 归一化再使用 K-Means;归一化后欧氏距离与余弦距离单调等价。
马氏距离
\(d_M(x,y)=\sqrt{(x-y)^\top\Sigma^{-1}(x-y)}.\) 它用协方差校正特征相关性。样本少、维度高时 $\Sigma$ 不可逆,需使用收缩估计 $\Sigma_\lambda=(1-\lambda)\Sigma+\lambda I$ 或 PCA 降维。
2.2 标准化、缺失值与异常值
常见预处理:
import numpy as np
def standardize(x: np.ndarray, eps: float = 1e-8) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
"""按列做 z-score 标准化。
Args:
x: 形状为 (n_samples, n_features) 的数值矩阵。
eps: 防止常数列除零的稳定项。
Returns:
返回标准化矩阵、列均值和列标准差,便于线上复用训练期统计量。
"""
mean = np.nanmean(x, axis=0)
std = np.nanstd(x, axis=0)
std = np.maximum(std, eps)
return (x - mean) / std, mean, std
标准化统计量只能在训练集上计算,否则会产生数据泄漏。缺失值可以用中位数、KNN 或模型插补;对异常值,先区分“真正稀有样本”和“测量错误”,因为 DBSCAN 可能把前者正确标成噪声,而 K-Means 会被其质心拖动。
2.3 高维距离退化
维度增大时,最近邻和最远邻的距离相对差异会变小,称为距离集中(concentration)。直觉上,所有点都“差不多远”,基于距离的聚类会失去分辨率。应对方法包括:
- 删除业务上无意义的特征;
- PCA/随机投影/自编码器降维;
- 对文本和图像使用预训练 embedding;
- 采用余弦、相关系数或学习到的度量;
- 在原空间与低维空间分别聚类并比较稳定性。
3. K-Means:最重要的基线
3.1 目标函数与 Lloyd 算法
帮助理解:K-Means 可以理解为“先猜 K 个代表点,再把每个样本交给最近的代表点,然后把代表点移动到组内平均位置”。反复执行后,得到的是硬标签:每个样本只能属于一个簇。
K-Means 最小化簇内平方和:
\[\min_{\{\mu_k\},z}\;J=\sum_{i=1}^n\|x_i-\mu_{z_i}\|_2^2.\]Lloyd 算法交替执行两步:
- 分配(assignment):$z_i\leftarrow\arg\min_k|x_i-\mu_k|^2$。
- 更新(update):$\mu_k\leftarrow\frac{1}{|C_k|}\sum_{i:z_i=k}x_i$。
每一步都不会增加 $J$,因此有限步后收敛到局部最优;但问题本身是 NP-hard,局部最优不保证全局最优2。
3.2 NumPy 手写实现
import numpy as np
def kmeans_numpy(
x: np.ndarray,
n_clusters: int,
n_init: int = 10,
max_iter: int = 300,
tol: float = 1e-4,
seed: int = 0,
) -> tuple[np.ndarray, np.ndarray, float]:
"""使用 NumPy 实现多次随机初始化的 K-Means。
Args:
x: 输入数据,形状为 (n_samples, n_features)。
n_clusters: 簇数量 K,必须小于等于样本数。
n_init: 独立初始化次数,取目标函数最小的一次。
max_iter: 单次运行的最大迭代次数。
tol: 质心移动的平方范数阈值。
seed: 随机数种子。
Returns:
labels: 每个样本的簇标签。
centers: 形状为 (K, d) 的质心。
inertia: 最终 SSE。
Raises:
ValueError: 输入维度、K 或 n_init 不合法时抛出。
"""
if x.ndim != 2 or n_clusters < 1 or n_clusters > len(x) or n_init < 1:
raise ValueError("x 必须为二维矩阵,且 1 <= n_clusters <= n_samples")
rng = np.random.default_rng(seed)
# 多次随机初始化,最后选择 SSE 最小的一次,降低局部最优风险。
best = None
for _ in range(n_init):
centers = x[rng.choice(len(x), size=n_clusters, replace=False)].copy()
for _ in range(max_iter):
# 广播计算“每个样本到每个质心”的平方距离矩阵。
distances = ((x[:, None, :] - centers[None, :, :]) ** 2).sum(axis=2)
labels = distances.argmin(axis=1)
# 先复制旧质心,逐簇写入均值,避免更新顺序影响本轮分配。
new_centers = centers.copy()
for k in range(n_clusters):
members = x[labels == k]
if len(members) > 0:
new_centers[k] = members.mean(axis=0)
else:
# 空簇重置为当前误差最大的样本,避免 NaN 传播。
worst = distances.min(axis=1).argmax()
new_centers[k] = x[worst]
shift = ((new_centers - centers) ** 2).sum()
centers = new_centers
if shift <= tol:
break
# 质心收敛后重新分配一次,确保返回的标签与最终质心一致。
distances = ((x[:, None, :] - centers[None, :, :]) ** 2).sum(axis=2)
labels = distances.argmin(axis=1)
final_dist = distances[np.arange(len(x)), labels]
inertia = float(final_dist.sum())
if best is None or inertia < best[2]:
best = (labels.copy(), centers.copy(), inertia)
return best
复杂度约为 $O(nKdT)$,其中 $T$ 是迭代次数;内存中的距离矩阵是 $O(nK)$。当 $n$ 或 $K$ 很大时,应使用分块计算或 Mini-Batch K-Means。
3.3 K-Means++ 初始化
随机初始化可能把多个质心放在同一簇。K-Means++ 先随机选一个质心,之后按样本到最近质心的平方距离 $D(x)^2$ 作为抽样权重,重复选点。该策略在期望意义上给出了 $O(\log K)$ 近似保证,并显著减少坏局部最优3。
def kmeans_plus_plus(x: np.ndarray, k: int, rng: np.random.Generator) -> np.ndarray:
"""按 K-Means++ 分布选择初始质心。"""
centers = [x[rng.integers(len(x))]]
closest_sq = ((x - centers[0]) ** 2).sum(axis=1)
for _ in range(1, k):
total = closest_sq.sum()
if total <= 0:
centers.append(x[rng.integers(len(x))])
continue
index = rng.choice(len(x), p=closest_sq / total)
centers.append(x[index])
closest_sq = np.minimum(closest_sq, ((x - x[index]) ** 2).sum(axis=1))
return np.asarray(centers)
工程默认应优先 k-means++ 加少量 n_init,并固定随机种子记录可复现结果。n_init 不是越大越好:它线性增加成本,应该和数据规模及结果不稳定程度匹配。
3.4 Mini-Batch K-Means 与 PyTorch Tensor
帮助理解:Mini-Batch K-Means 是 K-Means 的“省内存版”:每次只看一小批样本,马上更新质心。它速度快、适合大数据,但结果会有更多随机波动。
Mini-Batch K-Means 每次只取一个小批次,用指数或计数加权更新质心。它把每轮成本从 $O(nKd)$ 降到 $O(bKd)$,适合流式数据,但会引入随机噪声,需要学习率衰减和多轮遍历。
import torch
def minibatch_kmeans_tensor(
x: torch.Tensor,
k: int,
batch_size: int = 1024,
steps: int = 2000,
seed: int = 0,
) -> tuple[torch.Tensor, torch.Tensor]:
"""在 PyTorch Tensor 上运行 Mini-Batch K-Means。
Args:
x: 浮点 Tensor,形状为 (n_samples, n_features),可在 CPU 或 GPU 上。
k: 簇数量。
batch_size: 每步随机抽取的样本数。
steps: 更新步数。
seed: PyTorch 随机种子。
Returns:
返回整型标签和最终质心;标签在函数末尾对全量样本计算。
"""
if x.ndim != 2 or k < 1 or k > x.shape[0]:
raise ValueError("x 必须为二维 Tensor,且 k 合法")
generator = torch.Generator(device=x.device).manual_seed(seed)
initial = torch.randperm(x.shape[0], generator=generator, device=x.device)[:k]
centers = x[initial].clone()
counts = torch.zeros(k, device=x.device, dtype=x.dtype)
for _ in range(steps):
# 随机抽取一个小批次,只用它近似全量数据的更新方向。
indices = torch.randint(x.shape[0], (min(batch_size, x.shape[0]),), generator=generator, device=x.device)
batch = x[indices]
# 计算批次样本到当前质心的平方距离。
dist = torch.cdist(batch, centers).square()
labels = dist.argmin(dim=1)
for cluster in range(k):
points = batch[labels == cluster]
if points.numel() == 0:
continue
count = points.shape[0]
old = counts[cluster]
counts[cluster] += count
rate = count / counts[cluster]
centers[cluster] = (1 - rate) * centers[cluster] + rate * points.mean(dim=0)
labels = torch.cdist(x, centers).square().argmin(dim=1)
return labels, centers
3.5 K-Means 的结构性盲点
- 非球形簇:两个同心环会被切成扇区。
- 尺度不同:未标准化时大尺度特征支配距离。
- 簇大小/密度不同:大簇可能吞并小簇。
- 离群点敏感:平方损失放大极端值。
- K 必须预先给定:需要指标、领域知识或模型选择。
不要只凭二维散点图判断“看起来分得开”。高维中可分离性、业务可解释性和线上稳定性必须单独验证。
4. 如何选择 K 与评估聚类
4.1 内部指标
SSE 与肘部法
\(\operatorname{SSE}(K)=\sum_i\|x_i-\mu_{z_i}\|^2.\) 随着 K 增大 SSE 必然下降,肘部点只能提供启发式建议,不能当成统计检验。
轮廓系数
对样本 $i$,令 $a(i)$ 为同簇平均距离,$b(i)$ 为最近其他簇平均距离:
\(s(i)=\frac{b(i)-a(i)}{\max(a(i),b(i))}.\)
取值范围为 [-1,1]。它偏好凸、均匀的簇,对密度变化和非球形结构不一定公平;实现时可直接使用 silhouette_score,但要确保距离度量与训练聚类一致45。
Calinski-Harabasz 与 Davies-Bouldin
CH 比较簇间离散度和簇内离散度;DB 计算每个簇与最相似簇的平均相似度,越低越好,具体公式和实现细节可参考评估工具文档5。指标之间冲突时,不要简单平均,应该回到业务目标与数据假设。
4.2 有标签时的外部指标
如果事后拥有真实标签,可以使用 Adjusted Rand Index(ARI)、Normalized Mutual Information(NMI)、V-measure、Fowlkes-Mallows 等。ARI 对随机一致性做校正,但标签编号置换不影响结果。外部指标只评价和给定标签的一致性,不代表聚类揭示了更有用的业务结构。
4.3 稳定性与可复现性
对样本重采样、随机种子、时间窗口和特征子集重复聚类,比较标签的一致性(ARI/NMI)或共识矩阵。一个轮廓系数高但重采样后完全改变的结果,通常不适合生产。应记录:数据版本、预处理统计量、距离、随机种子、算法版本、超参数、指标和可视化。
5. Gaussian Mixture Model:从硬标签到概率模型
5.1 生成式假设
帮助理解:GMM 不强迫样本做“非黑即白”的选择,而是认为数据由多个高斯分布混合生成,并给出“属于每个组件的概率”。椭圆形、重叠的簇通常更适合它。
GMM 假设每个样本先按混合权重 $\pi_k$ 选择组件,再从高斯分布采样:
\[p(x)=\sum_{k=1}^{K}\pi_k\mathcal N(x\mid\mu_k,\Sigma_k),\quad \pi_k\ge0,\ \sum_k\pi_k=1.\]后验责任度(responsibility)为:
\[\gamma_{ik}=p(z_i=k\mid x_i)=\frac{\pi_k\mathcal N(x_i\mid\mu_k,\Sigma_k)}{\sum_j\pi_j\mathcal N(x_i\mid\mu_j,\Sigma_j)}.\]5.2 EM 推导
引入隐变量 $z$ 后,直接最大化似然困难。EM 交替执行:
- E 步:固定参数,计算 $\gamma_{ik}$。
- M 步:固定责任度,更新 \(N_k=\sum_i\gamma_{ik},\quad \mu_k=\frac{1}{N_k}\sum_i\gamma_{ik}x_i,\) \(\Sigma_k=\frac{1}{N_k}\sum_i\gamma_{ik}(x_i-\mu_k)(x_i-\mu_k)^\top,\quad \pi_k=N_k/n.\)
EM 保证观测对数似然不下降,但仍可能陷入局部最优。协方差矩阵接近奇异时需加 reg_covar;高维全协方差参数量为 $O(Kd^2)$,可选 diagonal、tied 或 spherical 结构。
5.3 PyTorch Tensor 的对数域实现
import torch
def gmm_log_likelihood(x: torch.Tensor, means: torch.Tensor, log_vars: torch.Tensor, logits: torch.Tensor) -> torch.Tensor:
"""计算对角协方差 GMM 的逐样本对数似然。
Args:
x: (n, d) 输入样本。
means: (k, d) 组件均值。
log_vars: (k, d) 对角方差的对数,避免直接优化负方差。
logits: (k,) 未归一化混合权重。
Returns:
(n,) 每个样本的 log p(x),使用 logsumexp 保持数值稳定。
"""
# 在对数方差参数化下保证方差为正,并避免极小值导致数值爆炸。
var = log_vars.exp().clamp_min(1e-6)
diff = x[:, None, :] - means[None, :, :]
log_prob = -0.5 * ((diff.square() / var) + log_vars + torch.log(torch.tensor(2 * torch.pi, device=x.device))).sum(dim=-1)
# logsumexp 聚合各组件,避免先转回概率后发生下溢。
return torch.logsumexp(log_prob + torch.log_softmax(logits, dim=0), dim=1)
与 K-Means 相比,GMM 能表示椭圆簇并给出不确定性;但它对高斯假设、协方差估计和初始化更敏感。组件数可用 BIC/AIC 选择:
\(\operatorname{BIC}=-2\log L+p\log n,\) 其中 $p$ 是自由参数数量。BIC 惩罚复杂模型更强,适合在候选模型中做相对比较。
6. 层次聚类:从相似度树观察多尺度结构
6.1 凝聚式算法
凝聚式(agglomerative)从每个样本一个簇开始,每次合并最相近的两个簇,直到只剩一个簇。簇间距离由 linkage 定义:
- single:两簇最近点距离,容易出现“链式效应”。
- complete:两簇最远点距离,倾向紧凑簇。
- average:所有跨簇点对平均距离。
- Ward:合并后 SSE 增量最小,和欧氏空间 K-Means 目标一致。
帮助理解:层次聚类像是在“搭积木”:开始时每个样本都是独立小组,算法不断合并最相近的两组,最后形成一棵树。你可以在树的不同高度切一刀,得到不同数量的簇。
结果是一棵 dendrogram,切割高度即可得到不同 K。朴素实现需要维护 $O(n^2)$ 距离矩阵,样本数很大时内存先成为瓶颈。
import numpy as np
def ward_merge_cost(a: np.ndarray, b: np.ndarray) -> float:
"""返回 Ward 合并两个簇带来的 SSE 增量。"""
return len(a) * len(b) / (len(a) + len(b)) * float(((a.mean(0) - b.mean(0)) ** 2).sum())
层次聚类的优点是无需预先固定 K、可解释多粒度关系;缺点是早期错误合并不可撤销,噪声会污染树,且大样本复杂度高。若需要大规模层次结构,可先用 BIRCH 或抽样,再对子簇中心做层次聚类。
7. 密度聚类:DBSCAN、OPTICS 与 HDBSCAN
7.1 DBSCAN 的定义
帮助理解:DBSCAN 先找“足够拥挤”的区域,再把相互连通的拥挤区域合并成簇;落在稀疏区域的点会被标记为噪声,因此不需要预先指定簇数量。
给定半径 $\varepsilon$ 和最小点数 min_samples:
- 核心点:$\varepsilon$-邻域内至少有
min_samples个点。 - 边界点:自身不是核心点,但在某个核心点邻域内。
- 噪声点:既非核心点也不从属于任何核心点。
从任一核心点出发,按密度可达关系扩展,即得到一个簇。DBSCAN 不需要 K,能发现任意形状并显式识别噪声6。
$\varepsilon$ 的选择可用 k-distance 图:计算每个点到第 min_samples 个近邻的距离并排序,在曲线拐点附近取值。特征尺度和距离度量必须先处理好,否则参数没有可迁移性。
7.2 OPTICS 与 HDBSCAN
DBSCAN 使用单一全局 $\varepsilon$,在不同密度的簇上表现受限。OPTICS 输出有序样本及可达距离,从一次运行中得到多个密度层次7。HDBSCAN 对层次密度树做凝聚和稳定性选择,能自动选择簇并为样本给出成员概率,通常比 DBSCAN 更适合密度差异明显的数据8。
实战建议:
- 低维、噪声明显且密度近似均匀:DBSCAN。
- 想探索多个密度阈值:OPTICS。
- 簇密度不同、需要软成员概率:HDBSCAN。
- 高维原始特征:先学习或压缩表示,再做密度聚类;直接在几百维上用欧氏距离通常不可靠。
8. 谱聚类:把聚类变成图切分
8.1 相似度图与拉普拉斯矩阵
帮助理解:谱聚类把样本看成一张图:相似的样本之间连一条权重较大的边。它先在图上找容易切开的区域,再对新的坐标做 K-Means,所以能处理月牙、同心圆等非凸形状。
构造相似度矩阵 $W$,例如 k-nearest-neighbor 图或高斯核:
\[W_{ij}=\exp(-\|x_i-x_j\|^2/(2\sigma^2)).\]度矩阵 $D_{ii}=\sum_jW_{ij}$,未归一化拉普拉斯为 $L=D-W$,归一化版本为 $L_{sym}=I-D^{-1/2}WD^{-1/2}$。取最小的前 K 个特征向量组成矩阵 $U$,对 U 的行再运行 K-Means。这个“先嵌入后聚类”能把同心圆、月牙等非凸簇变成线性可分结构9。
8.2 PyTorch 计算特征向量
import torch
def spectral_embedding(x: torch.Tensor, k_neighbors: int = 10, n_components: int = 2) -> torch.Tensor:
"""构造 kNN 图并返回归一化拉普拉斯的低频特征向量。"""
# 先得到全体样本的两两距离;大数据时应改用稀疏近邻搜索。
distances = torch.cdist(x, x).square()
knn = distances.topk(k_neighbors + 1, largest=False).indices[:, 1:]
# 用邻居权重填充相似度图,其余边保持为 0。
w = torch.zeros_like(distances)
sigma = distances.gather(1, knn).median().clamp_min(1e-6)
weights = torch.exp(-distances.gather(1, knn) / (2 * sigma))
rows = torch.arange(x.shape[0], device=x.device)[:, None].expand_as(knn)
w[rows, knn] = weights
w = torch.maximum(w, w.T)
degree = w.sum(dim=1).clamp_min(1e-8)
inv_sqrt = degree.rsqrt()
laplacian = torch.eye(x.shape[0], device=x.device) - inv_sqrt[:, None] * w * inv_sqrt[None, :]
# 取拉普拉斯矩阵最小特征值对应的低频方向作为新表示。
eigenvalues, eigenvectors = torch.linalg.eigh(laplacian)
return eigenvectors[:, :n_components]
复杂度主要来自 $n\times n$ 相似度矩阵和特征分解,内存为 $O(n^2)$,不适合超大样本。可用 Nyström 近似、稀疏图、近似近邻和随机特征分解扩展规模。谱聚类对图构造超参数敏感,kNN 太小会断图,太大则抹平局部结构。
9. Mean Shift、Affinity Propagation 与 BIRCH
9.1 Mean Shift
帮助理解:Mean Shift 不先猜簇数,而是把每个点反复推向“周围最拥挤的地方”。最后落到同一个密度峰的点被分到一起;带宽越大,看到的结构越粗。
Mean Shift 在核密度估计的梯度方向上迭代:
\(m(x)=\frac{\sum_i K((x-x_i)/h)x_i}{\sum_i K((x-x_i)/h)}-x.\) 所有点向密度峰移动,收敛到同一峰的点归为一簇。它无需指定 K,能发现任意形状,但每次迭代都要访问大量样本,带宽 $h$ 决定簇粒度,样本量大时成本很高。
9.2 Affinity Propagation
算法在样本间传递 responsibility 和 availability 两类消息,自动选出 exemplars(代表样本)10。相似度矩阵为 $O(n^2)$,不适合大数据;preference 参数控制簇数,通常需要通过分位数或验证集调节。
9.3 BIRCH
BIRCH 用聚类特征(CF)树压缩大规模数据:每个节点保存样本数 N、线性和 LS、平方和 SS,从而可快速计算质心和半径11。先构建 CF 树,再对叶节点子簇运行 K-Means 或层次聚类。它适合内存受限的流式场景,但依赖欧氏空间,阈值半径对结果影响很大。
10. 模糊 C 均值与不确定性
帮助理解:模糊 C 均值允许一个样本同时属于多个簇,例如“更像 A,也有一点像 B”。隶属度越平均,说明样本越接近边界,决策时应保留不确定性。
模糊 C 均值把硬标签放宽为隶属度矩阵 U,目标为:
\(J_m=\sum_{i=1}^n\sum_{k=1}^K u_{ik}^m\|x_i-c_k\|^2, \quad \sum_k u_{ik}=1.\) 更新公式:
\[u_{ik}=\left[\sum_j\left(\frac{\|x_i-c_k\|}{\|x_i-c_j\|}\right)^{2/(m-1)}\right]^{-1}.\]m 越大,边界越模糊。它适合一个样本可能同时属于多个状态的场景,如医学表型,但仍继承球状簇和欧氏距离假设,并且对异常点敏感。生产中应同时报告最大隶属度与熵:
\(H_i=-\sum_k u_{ik}\log u_{ik},\) 高熵样本应进入人工复核或后续决策的“不确定”分支。
11. 深度聚类:同时学习表示与簇
帮助理解:深度聚类先学习“什么样的表示更适合分组”,再在表示空间里聚类。它能处理图像、文本等原始距离不代表语义的高维数据,但训练不稳定和塌缩风险也更高。
经典算法通常在固定特征空间中聚类。图像、语音、文本的原始空间距离未必表达语义,因此深度聚类把编码器 $z=f_\phi(x)$ 与聚类目标联合优化。
11.1 自编码器 + K-Means 基线
先训练自编码器重建输入,再取瓶颈向量做 K-Means:
import torch
from torch import nn
class AutoEncoder(nn.Module):
"""用于聚类前表示学习的最小自编码器。"""
def __init__(self, input_dim: int, latent_dim: int) -> None:
super().__init__()
self.encoder = nn.Sequential(nn.Linear(input_dim, 128), nn.ReLU(), nn.Linear(128, latent_dim))
self.decoder = nn.Sequential(nn.Linear(latent_dim, 128), nn.ReLU(), nn.Linear(128, input_dim))
def forward(self, x: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]:
"""返回潜变量和重建结果。"""
# 编码器压缩输入,潜变量将作为后续聚类的特征。
latent = self.encoder(x)
return latent, self.decoder(latent)
只优化重建损失会保留与聚类无关的细节;只优化聚类损失又可能学到退化表示。因此通常采用预训练 + 聚类微调,并监控重建、聚类稳定性和下游任务三类信号。
11.2 DEC:自训练的软分配
Deep Embedded Clustering(DEC)在潜空间引入 Student-t 核得到软分配:
\[q_{ij}=\frac{(1+\|z_i-\mu_j\|^2/\alpha)^{-(\alpha+1)/2}}{\sum_{j'}(1+\|z_i-\mu_{j'}\|^2/\alpha)^{-(\alpha+1)/2}}.\]再构造强化高置信度样本的目标分布:
\(p_{ij}=\frac{q_{ij}^2/f_j}{\sum_{j'}q_{ij'}^2/f_{j'}},\quad f_j=\sum_iq_{ij},\) 最小化 KL 散度 $\sum_i\sum_j p_{ij}\log(p_{ij}/q_{ij})$12。平方会放大高置信度分配,但也可能造成确认偏差;应使用目标分布更新间隔、早停和稳定性检查。
def dec_kl(q: torch.Tensor, eps: float = 1e-8) -> torch.Tensor:
"""根据 DEC 公式计算 KL(p || q) 聚类损失。"""
# 统计每个簇当前吸收了多少样本,用于校正大簇偏置。
frequency = q.sum(dim=0, keepdim=True)
# 平方会强化高置信度分配,再按簇频率做归一化。
target = (q.square() / frequency.clamp_min(eps))
target = target / target.sum(dim=1, keepdim=True).clamp_min(eps)
return (target * (target.add(eps).log() - q.clamp_min(eps).log())).sum(dim=1).mean()
11.3 对比学习与深度聚类的现代组合
近年的方法通常把以下组件组合起来:
- 增强一致性:同一样本的两种增强应得到相近表示;不同样本通过 InfoNCE 或 supervised contrastive loss 分离。
- 原型/中心学习:维护可学习原型,使用 Sinkhorn-Knopp 或均衡约束避免所有样本塌缩到一个簇。
- 伪标签自训练:高置信度样本生成伪标签,低置信度样本延迟决策。
- 图结构正则:在 kNN 图上约束邻居表示或标签一致。
- 最优传输(OT):给定簇容量先验,求样本到原型的软匹配,缓解簇不平衡与塌缩。
一个可训练的联合损失可以写成:
\[\mathcal L=\lambda_{rec}\mathcal L_{rec}+\lambda_{con}\mathcal L_{InfoNCE}+\lambda_{clu}\mathcal L_{KL}+\lambda_{bal}\mathcal L_{balance}.\]权重不是越多越好;先固定表示学习和聚类的最小闭环,再逐项加入正则并做消融。对于文本/图像,优先使用领域预训练模型的 embedding,再比较简单 K-Means、GMM、HDBSCAN 与深度微调,深度方法只有在表示确实不足时才值得承担复杂度。
11.4 现代深度聚类方法谱系
“深度聚类”不是单一模型,而是几条逐渐融合的路线:
- 生成式路线:VaDE 将 VAE 的先验设为高斯混合,在重建与 KL 正则之间学习可采样的簇结构;适合需要生成和不确定性估计的场景,但对先验和解码器容量敏感。
- 重建增强路线:IDEC 在 DEC 的 KL 目标之外保留重建损失,减少微调阶段表示漂移;当原始特征细节对簇有用时通常更稳。
- 自监督原型路线:DeepCluster/DeepCluster-v2 交替执行表示网络训练与 K-Means 伪标签更新;SwAV 用在线聚类和最优传输在不同增强视图间交换分配,避免显式构造所有负样本。
- 邻域一致路线:SCAN 先用自监督表示构建近邻,再最大化邻居预测一致性,同时用熵正则避免塌缩;适合类别边界由局部邻域决定的图像数据。
- 图融合路线:SDCN、DCRN 等把自编码器与 GCN/图正则联合,利用样本关系传播聚类信号;必须防止图构造中的标签泄漏和过平滑。
这些方法的共同难点是簇塌缩、伪标签确认偏差和目标冲突。实验时至少记录每个 epoch 的簇占比、潜变量协方差的有效秩、原型间最小距离和不同随机种子的 ARI;只看训练损失无法发现退化解。现代模型带来的提升通常首先来自更好的表示和增强,而不是更复杂的聚类头,故应把“冻结预训练 embedding + 简单聚类”作为不可省略的对照组。
11.5 图神经网络与多视图聚类
当样本天然构成图(社交网络、分子、知识图谱)时,可用 GCN/GAT 编码邻居信息,再在节点 embedding 上聚类。多视图聚类则为同一对象的文本、图像、行为分别编码,通过协同正则或共享潜空间融合。关键风险是图泄漏:如果边包含未来信息,离线聚类会高估效果;时间切分和边遮蔽必须与下游任务一致。
12. 统一的工程选型流程
12.1 先做数据诊断
- 统计样本数、维度、稀疏度、缺失率、异常值比例。
- 明确特征的尺度、单位、时间切分和业务语义。
- 抽样计算距离分布,比较欧氏、余弦、曼哈顿的集中程度。
- 用 PCA/UMAP 仅作可视化,不把二维图当成真实结构证明。
12.2 建立可比较的基线
推荐顺序:
- 标准化 + K-Means++(多个 K)。
- 标准化 + GMM(多个协方差类型,用 BIC 比较)。
- kNN 图 + 谱聚类(样本量允许时)。
- PCA/embedding + HDBSCAN(需要噪声与任意形状时)。
所有候选使用同一数据切分、同一预处理和同一评估协议。不要拿原空间 K-Means 的轮廓系数和 embedding 空间 HDBSCAN 的业务指标直接混为一谈。
12.3 选择算法的快速决策表
| 数据特征/目标 | 首选 | 备选与注意事项 |
|---|---|---|
| 大规模、球状、需快速分群 | Mini-Batch K-Means | BIRCH;先标准化与抽样估计 K |
| 椭圆簇、需要概率与不确定性 | GMM | 协方差正则、BIC、组件退化检查 |
| 任意形状且有噪声 | DBSCAN/HDBSCAN | 先处理尺度;检查密度差异 |
| 多尺度层次关系 | 凝聚式层次聚类 | Ward 适合欧氏;大样本先压缩 |
| 非凸簇、相似度图可靠 | 谱聚类 | 稀疏图、近似特征分解 |
| 无法给 K、密度峰明显 | Mean Shift | 带宽决定粒度,成本较高 |
| 高维语义数据 | 预训练 embedding + K-Means/HDBSCAN | 深度微调需防塌缩与伪标签偏差 |
| 图或多视图对象 | GNN/多视图深度聚类 | 防止时间泄漏,做视图消融 |
13. 常见失败模式与排查顺序
- 所有点挤在一个簇:检查特征是否未标准化、距离是否溢出、深度模型是否塌缩;查看每簇样本数和中心间距离。
- K 改一点结果就完全不同:增加初始化次数,做重采样稳定性;确认数据中是否没有清晰簇。
- DBSCAN 全是噪声:检查 $\varepsilon$ 与尺度,绘制 k-distance;高维先降维或换度量。
- GMM 出现极大似然但簇无意义:协方差奇异导致单点组件;增大正则、限制最小方差或改用 diagonal/tied。
- 深度聚类训练损失下降但标签退化:监控簇占比、表示方差、原型间距离;加入均衡约束和早停。
- 指标很好但业务不可用:轮廓系数只反映几何分离,必须做画像、稳定性、下游 uplift 和人工抽检。
- 线上漂移:记录新样本到中心/密度模型的距离分布、簇占比、未知/噪声比例;设定重训和回滚门槛。
14. 从实验到生产的最小闭环
一个可审计的聚类流水线至少包含:
数据快照 -> 训练期预处理 -> 表示学习/降维 -> 聚类模型 -> 版本化标签
-> 内部指标与稳定性 -> 业务画像与抽检 -> 线上漂移监控 -> 重训/回滚
保存对象不仅是模型参数,还包括特征顺序、均值方差、距离定义、K 或密度参数、随机种子、训练数据指纹和评估报告。标签编号本身没有语义,模型更新后簇 0 不一定仍然代表原来的群体;需要用中心相似度、最优匹配或业务规则对齐新旧簇。
在推荐、风控、营销等高风险场景,聚类不应直接成为自动决策唯一依据。应把簇作为特征或候选分层,并检查不同群体的误差、覆盖率和资源分配是否产生不公平结果。无监督不等于无责任:没有标签只意味着验证更困难。
15. 一页纸总结
- 聚类结果由距离/相似度 + 模型假设 + 超参数 + 随机性共同决定。
- K-Means 是最好的第一基线,但不是通用答案;K-Means++、多次初始化和标准化是最低配置。
- GMM 提供概率与椭圆簇,DBSCAN/HDBSCAN 处理噪声与任意形状,层次聚类揭示多尺度结构,谱聚类利用图关系。
- 深度聚类的关键不在“网络更深”,而在于学习一个适合聚类的表示,并防止塌缩、确认偏差和数据泄漏。
- 轮廓系数、BIC、ARI 只能回答局部问题;稳定性、业务解释、下游收益和线上漂移才决定能否落地。
- 最可靠的工程路径是:先诊断数据,再建立简单基线,逐项增加模型能力,每一步都保留可复现的验证证据。
参考文献
-
Rui Xu, Donald Wunsch. Survey of Clustering Algorithms. IEEE Transactions on Neural Networks, 2005. ↩
-
Stuart Lloyd. Least Squares Quantization in PCM. IEEE Transactions on Information Theory, 1982. ↩
-
David Arthur, Sergei Vassilvitskii. k-means++: The Advantages of Careful Seeding. SODA, 2007. ↩
-
Jean-Bastien Rousseeuw. Silhouettes: A Graphical Aid to the Interpretation and Validation of Cluster Analysis. Journal of Computational and Applied Mathematics, 1987. ↩
-
MathWorks/Scikit-learn documentation. Clustering performance evaluation and estimator guides. 2024. ↩ ↩2
-
Martin Ester, Hans-Peter Kriegel, Jörg Sander, Xiaowei Xu. A Density-Based Algorithm for Discovering Clusters in Large Spatial Databases with Noise. KDD, 1996. ↩
-
Mihael Ankerst, Markus Breunig, Jörg Sander, Hans-Peter Kriegel. OPTICS: Ordering Points To Identify the Clustering Structure. SIGMOD, 1999. ↩
-
Ricardo J. G. Campello, Davoud Moulavi, Jörg Sander. Density-Based Clustering Based on Hierarchical Density Estimates. PAKDD, 2013. ↩
-
Andrew Y. Ng, Michael I. Jordan, Yair Weiss. On Spectral Clustering: Analysis and an Algorithm. NIPS, 2002. ↩
-
Brendan J. Frey, Delbert Dueck. Clustering by Passing Messages Between Data Points. Science, 2007. ↩
-
Tian Zhang, Raghu Ramakrishnan, Miron Livny. BIRCH: An Efficient Data Clustering Method for Very Large Databases. SIGMOD, 1996. ↩
-
Junjie Xie, Ross Girshick, Ali Farhadi. Unsupervised Deep Embedding for Clustering Analysis. ICML, 2016. ↩