K-means 聚类算法学习笔记
K-means 是一种非常流行且简单易懂的聚类算法,它的核心思想是“物以类聚,人以群分”。
K-means 是什么?
简单来说,K-means 的目标就是把一堆数据点分成 K 个“小团体”(也就是“簇”或“聚类”),让每个小团体内部的成员都尽可能地相似,而不同小团体之间的成员则尽可能地不同。
它判断“相似”的标准很简单:离得近就是一伙的!具体来说,就是离某个“中心点”(我们叫它“质心”)最近的数据点,就属于这个质心代表的团体。
一个好懂的比喻
想象一下,你是一家大型商场的负责人,现在想在商场里设置 K 个服务台,方便顾客咨询和休息。你会怎么做呢?
初步放置: 你可能会先随机选 K 个地方放上服务台。
顾客选择: 顾客来了之后,他们自然会选择离自己最近的那个服务台去咨询。
服务台调整: 过了一段时间,你发现每个服务台周围都聚集了一群顾客。为了让服务更高效,你会把每个服务台的位置调整到它所服务的这群顾客的“平均位置”(也就是这群顾客的中心点)。
重复调整: 顾客会根据新的服务台位置重新选择最近的服务台,你再根据新的顾客分布调整服务台位置……
这样几轮下来,每个服务台就会“守住”一片区域,这些区域就是我们说的“簇”,而服务台的最终位置就是每个簇的“质心”。
K-means 的“数学目标”
K-means 算法在背后做的事情,就是努力让一个叫做“目标函数”的值变得最小。这个目标函数长这样:
$$ \sum{i=1}^{n} \min{1 \le j \le K} |x_i - \mu_j|^2 $$
别被公式吓到,它的意思其实很简单:
x_i
代表每一个数据点。μ_j
代表第 j 个簇的质心(也就是那个“中心点”)。||x_i - μ_j||^2
表示数据点x_i
到它所属簇的质心μ_j
之间的距离的平方。min
表示每个点都选择离它最近的那个质心。∑
表示把所有数据点到它们各自质心的距离平方加起来。
所以,整个公式的意思就是:所有数据点到它们各自所属簇的质心的距离的平方和,要尽可能地小。
为什么用平方距离?
平方距离会放大那些离质心较远的点(“离群点”)的影响,让算法更关注这些点,努力把它们也拉进合适的簇里。
它默认使用的是欧氏距离,这要求你的数据特征(比如身高、体重)是数值型的,并且它们的单位(量纲)最好是可比较的,或者经过了标准化处理。
算法步骤:两步走,直到稳定
K-means 算法是一个迭代过程,主要就两步,不断重复直到收敛(也就是结果不再有明显变化):
分配(Assignment Step): 对于每一个数据点,计算它到所有 K 个质心的距离,然后把它分配给距离最近的那个质心所在的簇。
更新(Update Step): 对于每一个簇,重新计算它的质心。新的质心就是这个簇里面所有数据点的平均值。
什么时候停止?
质心的位置不再发生变化。
目标函数(所有点到质心距离平方和)的下降幅度变得非常小,几乎不变了。
达到了你预设的最大迭代次数。
算法复杂度: 大致是 O(n × K × d × iter)
n
:数据点的数量。K
:你想要分的簇的数量。d
:每个数据点的特征维度(比如一个人的身高、体重、年龄,维度就是3)。iter
:算法迭代的次数。
为什么 K-means 会收敛?
这是一个很棒的问题!简单来说,K-means 每次迭代都不会让它的“数学目标”变差:
分配步骤: 把每个点分给最近的质心,这肯定不会让总的距离平方和变大,只会变小或不变。
更新步骤: 用簇内所有点的平均值作为新的质心,这是在当前簇分配下,能让簇内点到质心距离平方和最小化的最佳选择,所以也不会让总的距离平方和变大。
由于目标函数有一个下限(距离平方和不可能小于0),而且每次迭代都会让它变小或不变,所以算法最终一定会停下来,达到一个局部最优解(不一定是全局最优,但已经是一个不错的解了)。
初始化的重要性:K-means++
K-means 算法对最初选择的 K 个质心位置非常敏感。如果初始质心选得不好,算法可能会陷入一个“局部最优解”,导致聚类效果不理想。
为了解决这个问题,人们发明了 K-means++ 这种更好的初始化方法:
第一个质心: 随机从所有数据点中选择一个作为第一个质心。
后续质心: 接下来选择质心时,会“偏爱”那些离现有质心比较远的点。具体来说,一个点离现有质心越远,它被选为下一个质心的概率就越大。
这样做的好处是,初始质心会尽可能地分散开,覆盖到数据的不同区域,从而让算法更稳定,也更快地收敛到更好的结果。
小技巧: 除了 K-means++,实际应用中,我们还经常会多次随机运行 K-means 算法(每次用不同的初始质心),然后选择其中目标函数值最小(也就是聚类效果最好)的那次结果。
何时 K-means 好用?何时要慎用?
✅ 适合用 K-means 的场景:
簇的形状: 你的数据簇大致是球形或凸形的,而且大小和密度都比较接近。
特征类型: 你的数据特征是连续的数值型,并且各个特征的单位(量纲)相近,或者已经经过了标准化处理。
聚类类型: 你需要一个快速、可扩展的“硬聚类”结果(每个数据点明确地只属于一个簇)。
❌ 慎用或不适合用 K-means 的场景:
簇的形状: 簇的形状是拉长的、弯曲的、非球形的,或者簇之间有“桥梁”连接。K-means 会强行把它们切成球状,效果会很差。
离群点: 数据中存在明显的“离群点”(异常值)。由于 K-means 使用平方距离,离群点会被“狠狠放大”,严重拉偏质心的位置,导致聚类不准确。
特征类型: 数据中包含大量的类别型变量(比如“颜色:红/绿/蓝”)。欧氏距离不适合衡量这类特征的相似度。
簇的差异: 不同簇之间的大小或密度差异非常大。
实战三件套:数据预处理、选 K、稳收敛
1. 数据预处理:让数据“听话”
标准化: 这是最重要的一步!如果你的数据特征单位不同(比如身高是米,体重是公斤),数值大的特征会在距离计算中占据主导地位。通过标准化(比如
z-score
标准化,让数据变成均值为0,标准差为1),可以消除这种影响,让所有特征“一视同仁”。处理离群点: 识别并处理数据中的极端离群点。你可以选择删除它们,或者使用对离群点不那么敏感的替代算法(比如 K-medoids)。
2. 如何选择合适的 K 值?
选择 K(也就是你想分成多少个簇)是 K-means 聚类中最关键也最头疼的问题之一。以下是几种常用方法:
肘部法则(Elbow Method):
怎么做: 分别用 K=1, 2, 3... 等不同的 K 值运行 K-means 算法,并记录每次的总簇内平方和(SSE,也就是前面提到的目标函数值)。然后画一张图,横轴是 K 值,纵轴是 SSE。
怎么看: 随着 K 的增加,SSE 会逐渐减小。你会发现曲线会有一个明显的“拐点”,就像人的肘部一样。这个拐点对应的 K 值通常被认为是比较合适的,因为它意味着再增加 K 值,SSE 的下降幅度就不那么明显了,收益不大了。
轮廓系数(Silhouette Score):
怎么做: 同样是尝试不同的 K 值。对于每个 K 值,计算所有数据点的平均轮廓系数。轮廓系数的取值范围是 [-1, 1]。
怎么看: 轮廓系数越高越好。它衡量了一个点与它自己簇的相似度(应该高),以及与相邻簇的相异度(应该高)。平均轮廓系数最高的 K 值通常是最佳选择。
Gap Statistic:
怎么做: 将你的数据集的 SSE 与随机生成的数据集的 SSE 进行比较。目标是找到一个 K 值,使得你的数据集的 SSE 与随机数据集的 SSE 之间的差距最大。
怎么看: 差距越大,说明你的聚类结构越明显,这个 K 值越可能代表了数据的真实聚类数量。
3. 提升 K-means 的稳定性
使用 K-means++ 初始化: 前面已经提到了,这是最常用的方法,能有效避免陷入糟糕的局部最优。
多次重启取最优: 多次运行 K-means(每次随机初始化),然后选择目标函数值最小(SSE最小)的那次结果。
设置最大迭代数与收敛阈值: 即使算法没有完全收敛,也可以在达到最大迭代次数后停止,或者当目标函数下降幅度小于某个很小的阈值时停止,防止无限循环。
处理空簇: 偶尔会出现某个簇在迭代过程中变得没有数据点的情况。可以把这个空簇的质心重新放置到离当前所有质心最远的点上,或者放置到当前 SSE 最大的那个点上,以避免算法中断。
常见“坑”与应对策略
常见问题 | 应对策略 |
---|---|
特征尺度不一致 | 标准化/归一化数据。 |
离群点拉偏质心 | 去噪;使用 K-medoids(质心是真实样本点,更抗噪);或使用截断/鲁棒尺度(对距离计算进行调整,减少离群点影响)。 |
非球形簇 | 考虑使用其他聚类算法,如 GMM(高斯混合模型,可拟合椭球形簇,是“软聚类”)、DBSCAN/HDBSCAN(基于密度,能发现任意形状的簇,且不用预设 K 值)、或谱聚类。 |
K 值选择不当 | 综合使用肘部法则、轮廓系数、Gap Statistic 进行评估。 |
数据维度太高 | 先进行降维处理(如 PCA 主成分分析、UMAP),再进行聚类;或者只在关键的特征子空间进行聚类。 |
和“亲戚们”的对比
K-means 并不是唯一的聚类算法,它还有一些“亲戚”,各有优缺点:
K-means:
特点: 硬分配(每个点明确属于一个簇)、倾向于发现球形簇、速度快、对离群点敏感。
何时用: 数据量大,簇形状大致为球形,需要快速得到聚类结果。
K-medoids (PAM):
特点: 质心是簇中真实存在的数据点(称为“代表点”),而不是平均值。因此,它对离群点不那么敏感,更鲁棒。
何时用: 数据中可能存在离群点,但计算速度通常比 K-means 慢,不适合超大数据集。
GMM (高斯混合模型,基于 EM 算法):
特点: 软分配(每个点以概率属于多个簇)、可以拟合不同形状(椭球形)和大小的簇。
何时用: 簇的边界模糊,或者簇的形状不是简单的球形,需要更灵活的聚类。
DBSCAN / HDBSCAN:
特点: 基于密度,不需要预先指定 K 值,能够发现任意形状的簇,并且能识别出噪声点(不属于任何簇的点)。
何时用: 簇的形状不规则,数据中存在噪声,且不确定簇的数量。但参数选择可能比较敏感,对密度变化大的数据集效果不稳。
典型应用场景
K-means 在实际中应用非常广泛:
图像量化/压缩: 把一张图片中成千上万种颜色聚类成 K 种主要颜色,从而实现图片压缩,生成“K 色图”。
用户分群: 根据用户的购买行为、浏览历史、人口统计学特征等,将用户分成不同的群体(比如“高价值用户”、“新用户”、“流失风险用户”),以便进行精准营销或个性化推荐。
文档/向量聚类: 将大量的文本(经过词向量或嵌入表示后)或任何高维数据向量进行聚类,可以快速将相似的文档或数据归类,加速信息检索或推荐系统中的“召回”环节。
迷你“心法”清单
数值特征: 在使用 K-means 之前,务必先对数据进行标准化(比如
z-score
或Min-Max
归一化)。初始化: 总是使用 K-means++ 来初始化质心,并且最好多次运行 K-means 算法,选择其中最好的结果。
选择 K: 先尝试
K
值在2
到10
之间,然后结合肘部法则和轮廓系数来选择 1-2 个最合适的候选K
值,最后根据你的业务理解和可解释性来最终确定。检查簇形状: 如果聚类结果中簇的形状明显不是球形,或者簇之间的大小/密度差异很大,那么请考虑使用 GMM 或 DBSCAN 等更高级的算法。
大数据量: 如果你的数据量非常大(比如百万级甚至更大),可以考虑使用 Mini-Batch K-means,它是一种 K-means 的变体,通过使用数据子集来更新质心,从而大大加快计算速度,同时保持近似的聚类效果。
评估: 除了 SSE 和轮廓系数这些内部评估指标,更重要的是结合业务指标(比如用户转化率、留存率等)来评估聚类结果的实际价值。