Kmeans聚类算法深度解析:原理、Python实现与K值确定实战
2026/9/15 17:20:47 网站建设 项目流程

聚类分析这个方向,我前前后后折腾了不少年。从最早用 SPSS 点菜单跑数据,到后来拿 MATLAB 调工具箱,再到现在主力用 Python 手撸算法,算是把 Kmeans 这条线整个摸了一遍。今天就把这个最经典、也最容易被忽视细节的算法,从头到尾拆开揉碎讲清楚。这篇是系列第一篇,重点放在原理和思路,让零基础的人也能完全吃透。

1. 内容整体设计与思路拆解

1.1 聚类分析到底在解决什么问题

要理解 Kmeans,先要把聚类分析放在整个数据分析的版图里看清楚位置。我习惯用一个最直白的问题切入:给你一堆数据,没有标签,没有答案,你怎么从里面找到规律?这就是聚类分析的核心场景,它是无监督学习里最主流的一类方法。请注意“无监督”这三个字的含义,模型没有正确答案可以比对,只能依靠数据本身的分布结构去发现内在分组关系。

拿实际业务场景来说,一个电商平台拿到用户的历史购买记录,用户没有被打上“高价值”“低价值”这类标签,但平台希望把用户分成几类,对不同类别做不同的运营策略。这时候聚类分析就派上用场了。再比如做图像压缩、文档归类和异常检测,底层也经常能看到聚类的影子。

聚类分析的本质,是在没有先验标签的前提下,把相似的样本归到同一个簇(Cluster)里,让同一个簇内部的样本尽可能相似,不同簇之间的样本尽可能不同。这句话听起来简单,但落到具体实现上,就需要定义“相似”的标准,这就是距离度量的问题。

我见过不少刚入门的同学,一上来就急着写代码,结果连“为什么要选欧氏距离、为什么有时候要用余弦相似度”都说不清楚。这样做项目是会踩坑的。所以在讲 Kmeans 具体步骤之前,我强烈建议先建立一个整体认知:聚类的核心三要素是距离度量、聚类准则和算法策略,任何一个变化,都会衍生出不同的聚类算法。

1.2 Kmeans 算法的历史定位与适用边界

Kmeans 之所以能在大半个世纪之后依然是使用频率最高的聚类算法之一,主要是因为它足够简单、计算速度够快、思路也足够直观。它最早由 Hugo Steinhaus 在 1956 年提出,后来 James MacQueen 在 1967 年正式使用了“Kmeans”这个名字。虽然之后出现了 DBSCAN、层次聚类、高斯混合模型等一堆新方法,但 Kmeans 在很多场景下依然是首选方案。这不是因为它最强,而是因为它最适合作为第一版基线模型。

理解 Kmeans 的适用边界,比会调库更重要。我在实际项目里对 Kmeans 的定位是:当数据量大、维度不太高、簇形状近似球形或者凸形的时候,Kmeans 往往能以极低的计算成本给出可解释的分组结果。反过来,如果数据中存在明显的长条形簇、环形簇,或者簇与簇之间严重重叠,Kmeans 的效果就会比较糟糕。这一点我会在后面的章节里专门展开讲,因为它直接决定了你该不该在项目里用它。

另外要强调的是,Kmeans 做的是硬聚类,每个样本只会被分到一个簇,不会像高斯混合模型那样给出样本属于各个簇的后验概率。这个特性在很多需要软决策的场景下,就显得有些力不从心了。所以,不是 Kmeans 不够好,而是你得知道在什么场景下请它出场最合适。

2. Kmeans 算法核心原理深度解析

2.1 算法目标函数与迭代思想

Kmeans 的本质是一个最优化问题。它试图找到一种簇划分方式,使得所有样本到其所属簇中心的距离平方和最小。这个指标在文献里通常叫 SSE(Sum of Squared Errors,误差平方和),也叫惯性(Inertia)。公式写出来是:

[ SSE = \sum_{i=1}^{K} \sum_{x \in C_i} |x - \mu_i|^2 ]

其中 (K) 是簇的数量,(C_i) 是第 (i) 个簇的样本集合,(\mu_i) 是第 (i) 个簇的质心(Centroid)。质心在 Kmeans 里的定义并非数据点本身,而是簇内所有样本在各维度上的均值,所以算法才叫“K-均值”。

这个地方有个初学者容易混淆的点:Kmeans 的 K 指的是簇的数量,而不是样本的特征维度。我自己刚开始学的时候就搞错过,以为 K 代表特征数量,结果怎么调都不对。

整个迭代优化的思路可以概括成一个循环:先固定簇划分,更新质心;再固定质心,重新划分样本。交替进行,直到目标函数不再明显下降。这个策略在优化领域有一个通用的名字,叫坐标下降法或者块坐标下降法。理解这一点很重要,因为它解释了为什么 Kmeans 只能保证收敛到局部最优解,而不是全局最优解。这个特性会直接影响后面要讲的多重随机初始化策略。

2.2 四步迭代流程拆解

Kmeans 的标准流程可以拆成四个步骤来记忆。第一步是参数初始化,需要确定簇的数量 (K) 和初始质心的位置。通常是随机从样本中抽取 (K) 个点作为初始质心,也可以用 Kmeans++ 策略优化初始位置。

第二步是分配样本。遍历所有样本,计算每个样本到 (K) 个质心的距离,将它分配到距离最近的质心所代表的簇。这一步所用的距离度量默认为欧氏距离,也可以根据业务场景替换为曼哈顿距离或余弦距离。

第三步是更新质心。重新计算每个簇内所有样本的均值,用这个均值向量作为新的质心位置。因为这个均值很可能不是数据集里实际存在的点,所以叫虚拟质心。

第四步是收敛检查。重复第二步和第三步,直到质心不再发生显著变化,或者样本分配结果趋于稳定,或者达到了预设的最大迭代次数。整个过程的计算复杂度大约是 (O(n \cdot K \cdot d \cdot t)),其中 (n) 是样本数,(d) 是维度数,(t) 是迭代次数。所以 Kmeans 面对百万级样本时依然可以做到可接受的计算开销。

2.3 距离度量选型的深层逻辑

距离度量是 Kmeans 里最容易被低估的一个环节。很多教材默认用欧氏距离,但实际项目中我却经常需要换度量。这背后的逻辑在于:欧氏距离的几何意义是直线距离,对特征各维度的尺度变化很敏感。如果特征之间量纲差异巨大,比如一个是身高,一个是收入,收入维度会主导整个距离计算,结果就是聚类结果被收入这一个维度支配了。

解决这个问题的标准方案有两种。第一种是对特征做标准化,用 Z-score 或 Min-Max 归一化把量纲拉到同一尺度。第二种是更换距离度量,比如在文本数据中常用余弦相似度来消除文档长度的影响。需要注意的是,如果你选的不是欧氏距离,那么第三步“更新质心”就不能简单地用均值操作了。比如 K-medoids 算法更新的是簇内离其他样本最近的实际样本点,就是因为中位数对离群点更鲁棒。

我个人的实战经验是:除非你对数据分布非常有把握,否则第一步永远是做标准化,再尝试不同距离度量做对比。这种对比实验的成本很低,但对最终聚类质量的影响可能是决定性的。

3. Python 手写 Kmeans 从零实现与调库操作

3.1 从零手写 Kmeans 的完整代码

纸上谈兵讲一堆原理,不如直接上手写代码。我建议所有初学者都至少手写一次 Kmeans,不要一上来就调 sklearn,否则对算法的理解永远停留在“玄学”层面。下面这份代码是我在项目中使用过的精简版,保留了核心逻辑,同时做了适当的工程化处理。

import numpy as np def initialize_centroids(data, k, seed=42): """随机选择k个样本作为初始质心""" rng = np.random.default_rng(seed) indices = rng.choice(data.shape[0], size=k, replace=False) return data[indices] def assign_clusters(data, centroids): """将每个样本分配到最近的质心""" distances = np.sqrt(((data[:, np.newaxis] - centroids)**2).sum(axis=2)) return np.argmin(distances, axis=1) def update_centroids(data, labels, k): """根据簇内样本均值更新质心""" new_centroids = np.zeros((k, data.shape[1])) for i in range(k): points = data[labels == i] if len(points) > 0: new_centroids[i] = points.mean(axis=0) else: # 空簇处理:随机重新初始化一个质心 new_centroids[i] = data[np.random.choice(data.shape[0])] return new_centroids def kmeans_manual(data, k, max_iter=100, tol=1e-4, seed=42): """完整Kmeans迭代过程""" centroids = initialize_centroids(data, k, seed) for iteration in range(max_iter): labels = assign_clusters(data, centroids) new_centroids = update_centroids(data, labels, k) shift = np.linalg.norm(new_centroids - centroids) centroids = new_centroids if shift < tol: print(f"第{iteration+1}次迭代后收敛") break return labels, centroids

这段代码里我最想强调的有两个工程细节。第一个是向量化计算距离矩阵,用data[:, np.newaxis] - centroids一次算出所有样本到所有质心的差值,而不是写 for 循环逐样本计算,这样在数据量上来之后速度差距会非常明显。第二个是空簇处理,如果某个簇在一次迭代后没有分配到任何样本,直接报错会让你很被动,我的做法是随机重新初始化该质心,保证迭代能继续进行。

3.2 用 sklearn 快速完成聚类任务

手写代码是为了理解原理,实际工作中还是要用成熟的库。sklearn 的 KMeans 接口把大量细节都封装好了,但有几个参数你必须搞清楚,否则容易用出问题。

from sklearn.cluster import KMeans from sklearn.preprocessing import StandardScaler import pandas as pd # 假设df是已经清洗好的数据,包含数值型特征 features = df[['feature1', 'feature2', 'feature3']] # 标准化是Kmeans里几乎必做的一步 scaler = StandardScaler() features_scaled = scaler.fit_transform(features) # 初始化Kmeans模型 km = KMeans(n_clusters=4, init='k-means++', n_init=10, max_iter=300, random_state=42) # 训练并获取聚类结果 labels = km.fit_predict(features_scaled) # 将标签还原到原始数据 df['cluster'] = labels

这里面的init='k-means++'n_init=10是两个绝对不能忽略的参数。k-means++是优化的初始化策略,它让初始质心尽量分散,大大降低陷入糟糕局部最优解的概率。n_init=10表示算法会独立运行 10 次,每次用不同的随机初始质心,最后保留 SSE 最小的那次结果。这两个参数组合起来,基本可以保证 Kmeans 在大多数常规数据上都表现稳定。

3.3 标准化操作的理由与实操细节

聚类之前做标准化,这个动作看起来朴素,背后却藏着实打实的数学逻辑。Kmeans 在数学上是以欧氏距离为核心的模型,而欧氏距离对量纲极其敏感。如果你的数据里一个特征是“年龄”,取值范围大概在 0 到 100 之间;另一个特征是“年收入”,取值范围在几万到几百万之间,那么在计算距离时,年龄那个维度几乎起不到任何作用,年收入完全主导了整个聚类的走势。这不是假设,而是我在真实业务数据上反复验证过的现象。

标准化的方法有很多,最常用的是 Z-score 标准化,公式是 ((x - \mu) / \sigma),让每个特征都变成均值为 0、标准差为 1 的分布。sklearn 里的StandardScaler就是干这个事的。也有用 Min-Max 归一化的,把数据压缩到 [0, 1] 区间,在特征分布近似均匀的情况下效果也还可以。我的个人习惯是默认先试 Z-score,因为它对离群点的容忍度比 Min-Max 稍好。

标准化之后还有个隐性收益:它让不同批次采集的数据在特征尺度上具备可比性。比如你上周拿到的用户数据和这周新增的数据,如果在建模前统一走同一个标准化流程,后续做模型迁移和对比就方便很多。

4. K 值确定与质心初始化的实战方法

4.1 肘部法则的实现与判读技巧

Kmeans 最让人头疼的问题,大概就是确定 (K) 值到底取多少。业务上如果有强先验知识,比如明确知道要分成几个用户群,那直接用就行。但更多时候是没有先验的,这时候最常用的方法就是肘部法则。

肘部法则的思路非常直观:随着 (K) 增大,SSE 单调递减,因为更多的簇意味着每个簇可以更紧凑。但它的下降速度会越来越慢。当增加的簇带来的 SSE 下降幅度骤减时,这个转折点就像手臂的肘部一样,被认为是最佳的 (K) 值。实际操作中,我会对 (K=1) 到 (K=10) 分别建模,记录每个 (K) 的 SSE,然后画出折线图。

import matplotlib.pyplot as plt sse = [] k_values = range(1, 11) for k in k_values: km = KMeans(n_clusters=k, init='k-means++', n_init=10, random_state=42) km.fit(features_scaled) sse.append(km.inertia_) plt.figure(figsize=(8, 5)) plt.plot(list(k_values), sse, marker='o') plt.xlabel('K值') plt.ylabel('SSE') plt.title('肘部法则:K与SSE的关系') plt.show()

这里我提醒两点。第一,km.inertia_就是 SSE,sklearn 直接算好了,不用自己写距离求和。第二,肘部法则得到的可能是一个范围而非一个精确的整数,画完图之后还要结合业务解释力去判断。我在一个用户分群项目里,肘部出现在 (K=3) 和 (K=5) 之间的平缓过渡区,最终结合业务访谈结果选了 4,因为第 4 类的特征画像最清晰。

4.2 轮廓系数与其他辅助指标

肘部法则在某些数据上并不好使,因为 SSE 曲线可能没有明显的拐点,而是平滑下降。这时候就应该上轮廓系数(Silhouette Coefficient)。轮廓系数的思想是同时衡量簇内凝聚度和簇间分离度,取值在 [-1, 1] 之间,越接近 1 表示聚类效果越好。

单个样本的轮廓系数计算公式是 ((b - a) / \max(a, b)),其中 (a) 是样本到同簇其他样本的平均距离,(b) 是样本到最近的其他簇中所有样本的平均距离。把所有样本的轮廓系数取平均,就是整体轮廓系数。sklearn 提供了现成的silhouette_score,一行代码就能算出来。

我的实际操作习惯是:先看肘部图圈定候选 (K) 值范围,再算这些候选值对应的轮廓系数,取最高的那个做参考,最后还要结合业务可行性来判断。这两个方法相互印证,能显著降低选错 (K) 的概率。另外,还有 Calinski-Harabasz 指数和 Davies-Bouldin 指数,可以作为辅助参考,但实践下来我觉得轮廓系数最直观、最容易解释。

4.3 不同初始化策略的效果对比

初始化策略对 Kmeans 结果的影响之大,远超很多人的直觉。经典的随机初始化方法是一把双刃剑:它能保证算法从任意状态启动,但也可能让算法收敛到恶心的局部最优解。你可能会问,Kmeans 不是有迭代优化吗?局部最优解不是最终收敛目标吗?问题在于,局部最优解之间的质量差异可以非常大,有的很合理,有的则是灾难性的。

5. 常见问题与排查技巧实录

5.1 收敛不稳定与局部最优问题

Kmeans 的收敛结果是依赖初始质心的,可能第一次运行和第二次运行得到不同的簇划分。这不是代码写错了,而是算法的内在特性。要解决这个问题,最标准的策略是多重随机初始化,也就是我在前面提到的n_init参数。sklearn 默认n_init=10,它会用 10 组不同的初始质心分别跑完整迭代,最终保留 SSE 最小的那组结果。

如果你手写的代码也想要这个功能,方法很简单。在外面套一层循环,记录多次运行中 SSE 最小的结果即可。这里我要特别提醒:SSE 最小并不总是代表最好的聚类,因为它在某些数据分布下可能偏爱球形簇。但作为一个自动筛选标准,它在绝大多数场景下都是可靠且客观的。

还有一种更强的初始化方法叫 K-means++,它的核心思路是先随机选第一个质心,然后后续质心的选择概率与样本到已有质心的距离平方成正比。这样避免了初始质心扎堆的现象。sklearn 里已经把 K-means++ 设为默认的初始化方式,这也是我推荐直接用 sklearn 的原因之一。自己实现 K-means++ 虽然不难,但没必要重复造轮子。

5.2 特征量纲与数据类型的坑

我见过太多人在 Kmeans 结果不理想时,第一反应是换算法,却不知道问题出在数据本身。最常见的一个坑就是没有标准化。前面说过,不同的特征量纲会把聚类结果带偏。但更隐蔽的问题是存在非数值型特征。Kmeans 是纯数值算法,它对字符串是完全没有概念的,所以必须先把类别型变量做编码。

这里有个技巧:类别型变量编码之后,要不要做标准化?我的经验是,如果只做 Label Encoding,类别之间的相对顺序可能会误导聚类,比如把“红、绿、蓝”编成 1、2、3,算法就会认为绿色与蓝色的距离比它与红色的距离更近,这完全是人为制造出来的假象。更好的做法是使用 One-Hot 编码,然后用标准化处理所有数值特征。这样类别特征会在距离计算中变成一个等距的几何维度,而不是一个有顺序关系的数值轴。

5.3 高维数据与稀疏向量的特殊处理

在高维数据上,Kmeans 会遭遇所谓的“维度灾难”。维度升高之后,样本之间在欧氏距离上的差距会逐渐趋同,也就是说,距离度量的分辨力大幅下降。这个问题没有完美解法,但有几个缓解手段。最常见的是先用主成分分析(PCA)做降维,再把降维后的数据放进 Kmeans。相当于先用线性变换提取主要结构,再在低维空间里做聚类。

对于文本数据,经过 TF-IDF 处理后得到的通常是高维稀疏向量。在这个场景下,很多从业者会在 Kmeans 内部改用余弦距离,因为余弦相似度对文本长度差异不敏感。不过要留意,sklearn 的 KMeans 默认使用欧氏距离,不支持直接传自定义距离函数。如果你确实想用余弦距离,有两个办法:一是对向量做 L2 归一化,让每个向量缩放到单位长度,在单位超球面上欧氏距离和余弦距离单调等价;二是直接换用 sklearn 的 MiniBatchKMeans 或改用手写的距离逻辑。

5.4 空簇与离群点的处理实践

迭代过程中某个簇可能突然没有任何样本分配进来,这就是空簇。空闲的质心如果不处理,后续迭代会继续空转,白白浪费计算资源。我在手写实现里的做法是重新随机初始化一个质心,或者用距离该质心最远的样本点作为新的质心,重新参与迭代。后者的好处是能让质心快速跳到数据分布稀疏的区域,增加找到新结构的概率。

离群点对 Kmeans 的影响也很明显。因为 SSE 是平方误差,离群点会把质心往自己的方向拉拽,导致整个簇的划分出现偏移。我在真实项目中通常会在聚类之前用 IQR(四分位距)或者 DBSCAN 做一个离群点检测,把它们单独剔除或者单独作为一个簇标记。这样 Kmeans 在主体数据上的表现会稳定很多。

5.5 常见报错与解决方案速查表

报错信息或异常现象可能原因排查思路解决方案
Number of distinct clusters (n) found is less than n_clusters数据特征不够明显,簇内样本太少检查样本量是否太少,特征是否有方差增大样本量,检查数据是否标准化,降低 (K) 值
聚类结果每次运行都不一样初始质心不同导致局部最优解不同确认是否合理设置随机种子设置random_state固定种子,增加n_init次数
inertia_很大且无法下降数据量纲差异大或者特征包含缺失值检查特征分布和缺失情况做标准化,处理缺失值,考虑是否降维
聚类效果差但无报错距离度量不适合当前数据分布画出样本分布观察换距离度量,或改换 DBSCAN、层次聚类等方法
迭代次数过多,算法跑得慢数据量过大,特征维度高查看样本量和维度规模用 MiniBatchKMeans,或先降维再聚类

5.6 评估聚类结果好坏的几个角度

聚类没有标准答案,因为无监督任务没有一个真正的标签可以做交叉验证。但我们可以用几个间接指标来做质量评估。第一是稳定性验证:对同一份数据用不同种子跑多次,如果簇划分结果基本一致,说明聚类结果可靠。第二是业务可解释性:把每个簇的特征均值拿出来,人工判断各个簇是否能对应到一个说得通的业务群体。如果一个簇的特征均值与你对业务的理解相矛盾,那多半是聚类出了问题。

我在实践中最常用的是一个土办法:把聚类结果带上原始数据做交叉统计。比如在用户分群场景里,如果第 1 簇用户的平均客单价比第 2 簇高出一大截,第 2 簇的复购率又比第 1 簇高,那这个聚类结果在业务上就有明显的区分度和可用性。反过来,如果两个簇在所有维度上的统计指标都差不多,那说明聚类本质上没有分开什么有意义的结构,你需要回到特征工程甚至数据收集层面去反思。

6. 工具选型:SPSS、MATLAB 与 Python 的选择建议

6.1 各工具的使用场景对比

我经常被问到“学聚类到底用 SPSS、MATLAB 还是 Python”。这个问题没有统一答案,关键看你在什么阶段、处理什么规模的数据。如果你的工作是偏学术方向,需要在论文里快速跑出一个可复现的结果,SPSS 的图形界面和菜单式操作非常友好。它内置了 Kmeans、系统聚类等多种方法,导出的结果自带厚厚的统计表格,对非编程背景的研究者特别友好。

MATLAB 的优势则在于矩阵运算能力非常强,而且它的工具箱里预装了很多聚类相关的函数。如果你做的是信号处理或者图像处理方向,数据本身就是矩阵或者多维数组,那用 MATLAB 处理聚类会比 Python 更顺手。它自带的可视化工具也能快速画出聚类后的散点图,调试体验不错。

Python 的优势则体现在整条数据管道的打通上。从数据读取、清洗、标准化、聚类到后期可视化和模型部署,全部可以在同一个生态里完成。在你需要把聚类结果落到线上系统,或者要处理百万级甚至更大规模的数据时,Python 的灵活性和生态优势就会完全碾压另外两个工具。

6.2 Python 生态里的其他聚类扩展

Kmeans 只是 Python 聚类生态的冰山一角。在 sklearn 里,围绕 Kmeans 还有几个重要变体值得了解。MiniBatchKMeans是 KMeans 的大数据版本,它每次随机抽取一小批样本来更新质心,训练速度大幅提高,但代价是聚类结果有轻微波动,适合超大规模数据的粗聚类。另外还有KMeans与特征选择的结合,比如先用聚类做标签,再训练分类模型,这种操作在伪标签半监督学习里很常见。

除了 sklearn,scipy.cluster里也有层次聚类相关的函数,处理小规模数据时效果很好。yellowbrick库可以用于可视化 Kmeans 的肘部图,画图部分能省不少代码。做高维聚类可视化时,umap-learn库经常被用来把高维样本映射到二维平面再画散点图,直观展示聚类效果。我还会在项目里用plotly做交互式三维散点图,方便跟业务方一起确认簇的分布效果。

6.3 从 MATLAB 迁移到 Python 的注意点

我知道很多学校的课程还在用 MATLAB 讲 Kmeans,学生出来工作后要转到 Python,多少会遇到一些概念对接上的障碍。比如 MATLAB 里kmeans(X, k)默认采用 k-means++ 初始化,你传进去的数据矩阵 X 是一行一个样本。Python 里 sklearn 的 KMeans 也是同样的布局,一行一个样本。但要注意,MATLAB 返回的idx是从 1 开始的索引,而 Python 是从 0 开始的,这个差异在数据对接和后续处理时容易让人栽跟头。

另一个差异在于对 NaN 的处理。MATLAB 的kmeans会自动忽略包含 NaN 的行,而 sklearn 的 KMeans 遇到 NaN 会直接报错。迁移代码时要先做好缺失值处理。还有一点,MATLAB 的kmeans支持直接传入距离度量参数,比如'cosine',而 sklearn 的 KMeans 不支持,需要先做向量归一化再训练。这几个差异点理清楚了,从 MATLAB 转 Python 基本可以无缝衔接。

7. 一个完整实战案例:电商用户价值分群

7.1 数据准备与特征构造

理论讲再多,都不如完整跑一个案例来得过瘾。我这里用一个经过脱敏的电商用户数据作为示例,数据包括用户的购买总金额、购买次数、最近一次购买距今天数、平均每次购买金额四个核心特征。目标是把用户分成不同价值的群体,为后续营销策略提供依据。

拿到数据的第一步绝不是直接跑聚类,而是先看数据质量。我会先检查缺失值、异常值和特征分布,然后用箱线图观察每个特征是否存在明显离群点。在本案例中,购买总金额和购买次数都呈现严重右偏分布,少数高价值用户远高于整体水平。直接把原始特征丢进 Kmeans 会造成高价值用户自成一簇,其他用户挤成一团,区分度很差。应对办法是对特征做对数变换压缩长尾,再进行 Z-score 标准化。

import pandas as pd import numpy as np from sklearn.preprocessing import StandardScaler from sklearn.cluster import KMeans from sklearn.metrics import silhouette_score # 原始数据 data = pd.DataFrame({ 'total_amount': [1200, 85, 40, 20000, 500, ...], 'purchase_count': [12, 3, 1, 68, 8, ...], 'recency_days': [5, 60, 120, 2, 15, ...], 'avg_basket': [100, 28, 40, 294, 62, ...] }) # 构造特征:对数变换压缩右偏 for col in ['total_amount', 'purchase_count', 'recency_days', 'avg_basket']: data[col + '_log'] = np.log1p(data[col]) # 选择特征并标准化 features = ['total_amount_log', 'purchase_count_log', 'recency_days_log', 'avg_basket_log'] scaler = StandardScaler() X_scaled = scaler.fit_transform(data[features])

7.2 确定 K 值并完成建模

对标准化后的数据分别计算 (K=2) 到 (K=6) 的轮廓系数,我得到的结果如下表所示:

K值轮廓系数SSE
20.485623
30.523920
40.563095
50.512541
60.472112

可以看到,(K=4) 时轮廓系数最高,说明四簇划分在簇内紧密度和簇间分离度上表现最好。虽然 (K=5) 和 (K=6) 的 SSE 更低,但轮廓系数下降说明它们已经开始把原本自然聚成一团的样本暴力切碎了,这种过细的分割在业务上也不好解释。

确定 (K=4) 之后,我用n_init=20做多重随机初始化,选出 SSE 最小的一次结果。得到的四个簇分别对应高价值高频用户、中价值活跃用户、低价值低频用户、沉睡流失用户。再回到原始数据做交叉统计,四类用户的核心指标差异明显,业务方认可度也很高。

7.3 结果可视化与业务落地

聚类完成之后,可视化是跟业务方沟通的关键一步。我用 PCA 把四维特征降到两个主成分,然后画出四个簇的散点图。在图片上可以看到四个簇分别占据平面上不同区域,没有严重的重叠交叉,说明聚类结构清晰。

Kmeans 跑完只是第一步,真正的价值在于落地。我给业务方的建议是:对高价值高频用户,投入高成本权益做重点运营;对中价值活跃用户,想办法提升客单价;对低价值用户,保持低成本触达;对沉睡流失用户,做唤醒活动。这套方案的基础就是聚类分析产生的用户分群。如果只用观察经验来分群,很难做到这样有数据支撑的精细化运营。

8. 个人实操心得

Kmeans 是我用过最多次的聚类算法,也是我向新人首推的第一个无监督算法。原因很简单,它的复杂度低、速度快、结果易解释,适合作为所有聚类任务的基线方案。回想我这些年踩过的坑,最想提醒大家的其实是两件事。

第一件事,Kmeans 的成败往往在数据准备阶段就已经决定了。特征有没有标准化,有没有截断异常值,有没有把类别变量处理清楚,这些前置动作的影响远超你后面调整 K 值或者切换初始化策略。我见过太多团队在算法参数上反复挣扎,最后发现问题出在一开始的数据处理上。这就像做饭,食材不新鲜,厨艺再好也救不回来。

第二件事,不要迷信单次运行的结果。Kmeans 自身是一个随机算法,随机种子的变化可能导致完全不同的分组结论。在关键决策场景下,一定要跑多次、换种子、看稳定性,还要结合业务语义去判断聚类结果是否合理。算法只是工具,最终决策靠的是人。

下一篇系列文章,我会重点讲 Kmeans 在大规模数据上的加速方案、Kmeans 与 DBSCAN 的对比选型,以及在文本聚类场景里的具体实践。希望这篇内容能帮你把 Kmeans 的基础打牢,我们下一篇再见。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询