恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
一文吃透聚类算法:KMeans原理与实战
首页
资讯中心
/
一文吃透聚类算法:KMeans原理与实战
一文吃透聚类算法:KMeans原理与实战
发布时间:2026/9/8 8:11:29
聚类算法这套东西说实话是很多做数据分析、机器学习入门的朋友绕不开的一道坎。我当年最开始接触的时候也是被一大堆名词绕得头晕什么KMeans、层次聚类、DBSCAN感觉都像在说同一件事又好像不是一回事。后来在实际项目里一个个用过去才对它们各自的脾气摸出点门道。这标题既然叫“一文吃透聚类算法”我就把自己从基础概念到KMeans实战这一段路怎么走的踩过的坑、总结的经验全部整理出来。这篇文章的核心就围绕两个关键词展开聚类算法以及最经典也最常用的KMeans。文章会从基本分类讲起逐步深入到KMeans的数学原理、完整实战流程以及和密度聚类、层次聚类这些热门算法的对比选型。适合刚入门想系统理解聚类的新手也适合对KMeans有一定了解、但想弄懂细节和调试技巧的从业者参考。1. 聚类算法的整体认知它到底在解决什么问题1.1 无监督学习的核心没有标准答案的分组聚类算法首先属于无监督学习的范畴。所谓无监督说到底就是数据没有标签没有标准答案。这一点和分类问题有本质区别分类是有监督的比如邮件垃圾识别样本已经有“是垃圾邮件”和“不是垃圾邮件”的标注模型的任务是学习区分边界然后把新样本归到已知类别里。而聚类面临的是另一番景象给你一堆没有任何标注的数据点让你自己发现结构把相似的归在一起。它解决的本质问题是在没有先验知识的情况下如何通过数据自身的分布特征找到内在的分组规律。用生活场景来类比一下你拿到一筐蔬菜没有人告诉你这筐里有哪几种蔬菜你需要根据外观特征颜色、长度、形状自动将它们分成几堆。这个“自动分堆”的过程就是聚类算法在做的事情。你会发现聚类在很多实际项目中并不是最终目的更多时候是数据探索的第一步。比如在用户画像分析里企业面对几百万条用户行为数据这些用户是谁、有几类人、每类人有什么偏好一切都是未知的这时候就需要聚类先跑一遍把用户分成几大族群后续的运营策略才能有针对性地制定。理解了这一点就明白了聚类在整套机器学习体系里的位置它是探索性数据分析的核心工具是从无序走向有序的起步阶段。1.2 三大聚类流派原型聚类、密度聚类、层次聚类说到聚类算法这个“家族”不同算法最核心的区别在于它们对“什么是簇”的定义方式不一样。我们闲聊技术的时候经常提的三类就是标题里热搜词反复出现的那几个原型聚类KMeans代表、密度聚类DBSCAN代表和层次聚类AGNES代表。原型聚类这类算法认为每个簇可以用一个“原型点”来代表比如KMeans里的质心。它的核心思路就是初始化一个质心位置然后迭代更新让质心不断逼近簇内所有样本的中心。它的特点是高效、可解释性强但应对不规则形状的簇比较吃力对初始点选择敏感。密度聚类以DBSCAN为代表的这类算法对“簇”的定义换了一种角度——簇是样本分布密度足够高的连通区域。它把样本分为核心点、边界点和噪声点通过半径参数和最小邻居数参数来发现任意形状的簇。好处是不需要预先指定要分几类还能自动识别离群点。层次聚类这类算法不直接给出一个划分结果而是构建一棵树状的聚类层次图树状图。AGNES是其中最常用的自底向上聚合的策略从每个样本各自为一类开始不断合并距离最近的两类最终可以按需在任意层次上切分出不同数量的簇。这三类算法没有绝对的谁好谁坏我在实际项目里的习惯是如果数据量很大、维度不算太高、簇的形状接近球形优先KMeans如果数据带有明显的不规则分布、噪声点很多DBSCAN往往更能打如果样本量不大并且想深入观察数据的嵌套结构那层次聚类是很好的选择。接下来的内容会把重心放在KMeans上因为它是原型聚类里最基础、最常用、也是理解其他聚类算法的最佳入口。2. KMeans核心机制从数学原理到实现逻辑2.1 目标函数与迭代收敛小化距离平方和KMeans的思想极其简洁简单到可以用一句话概括把样本划分成K个簇使得每个样本到其所属簇质心的距离平方和最小。这个“距离平方和”在学术界有个正式名字叫簇内误差平方和WCSSWithin-Cluster Sum of Squares它就是我们优化的目标函数。用数学公式表达就是 [ J \sum_{i1}^{K}\sum_{x \in C_i} ||x - \mu_i||^2 ] 其中K是预设的簇的数量C_i表示第i个簇μ_i是第i个簇的质心也就是簇内所有样本的均值向量x是样本点||x-μ_i||^2表示样本到质心距离的平方。这个目标函数的意义很好理解一个簇内部的样本应该尽量紧凑也就是彼此之间相似度高。如果某个样本被归到了错误的簇它到那个质心的距离就会偏大进而拉高整个目标函数的值。KMeans的整个迭代过程就是围绕最小化这个J值来进行的。它的算法流程听起来也很直白一共就四步随机初始化K个质心位置。计算每个样本到这K个质心的距离把每个样本分配给距离最近的质心所在的簇。对每个簇重新计算质心也就是簇内所有样本的位置平均值。重复第2、3步直到质心不再发生明显移动或者达到设置的迭代次数。这个循环之所以能够收敛是因为每轮迭代都在单调地减少目标函数值。每一步把样本分给更近的质心不会增加距离重新计算质心为簇内均值又是在当前划分下的最优解。然而需要说明的是这个算法并不能保证收敛到全局最优。它本质上是一个迭代优化的贪心策略最终的聚类结果很大程度上依赖初始质心的选择这也是为什么实际应用中需要对KMeans做好初始化策略的原因。2.2 K值的选择肘部法则与轮廓系数在使用KMeans的过程中最让人头疼的往往不是算法本身而是K值怎么确定。因为业务上我们常常只有一个模糊的预期比如“把用户分成几类比较合适”但这个“几”并没有标准答案。K取大了每个簇内部的样本同质性强但是最终得到的多个簇可能在实际业务上没有区分度K取小了簇之间又会过度聚合丢失了细粒度信息。应对这个问题行业里最常用的两种辅助手段是肘部法则和轮廓系数。肘部法则的核心判断依据是簇内误差平方和WCSS随K值的变化趋势。直观上来说K值从1开始逐渐增大每个簇内部的紧凑程度都会提升所以WCSS一定是单调递减的。但是随着K不断增大WCSS下降的幅度会逐渐放缓。当K值较小时新增一个簇能显著降低WCSS因为数据里的大结构正在被拆开当K值接近真实簇数时再增加簇就只能把已有的簇硬性切开带来的边际收益迅速变小。这个转折点画出来就像一个手肘所以叫肘部法则。实际操作的时候横轴是K值纵轴是WCSS找到曲线下降趋势变缓的那个拐点就是比较合理的K值。轮廓系数的思路则不走WCSS的路线而是综合考量每个样本的簇内凝聚度和簇间分离度。每个样本点的轮廓系数的计算方法是 [ s \frac{b - a}{\max(a, b)} ] 其中a是这个样本到同簇其他样本的平均距离代表簇内的凝聚度b是这个样本到最近的其他簇内所有样本的平均距离代表它与相邻簇的分离程度。s的取值范围在-1到1之间。s接近1说明这个样本距离自己所在的簇很近离相邻簇很远聚类效果很理想s接近0说明样本处于两个簇的边界位置s为负值说明样本可能被分配错了簇。把所有样本的轮廓系数取平均得到的就是该K值下聚类的整体质量评分评分越高说明聚类效果越好。这两种方法我实践下来的感受是肘部法则直观、计算快但有时候拐点并不明确轮廓系数更精细但计算量偏大特别是数据量很大的时候。两者结合使用仍然是我最推荐的方式——先用肘部法则圈定K的大致范围再在这个范围内用轮廓系数精确定位。2.3 距离度量方式欧式距离与KMeans的适用边界KMeans里默认的距离度量方式是欧式距离这几乎成了标配。欧式距离的公式是 [ d(x, y) \sqrt{\sum_{i1}^{n}(x_i - y_i)^2} ] 也就是把两个样本点放在n维空间里计算它们之间的直线距离。之所以KMeans选欧式距离是因为它和“质心”这个定义天然契合。质心是簇内所有样本的均值而这个均值点恰好使得欧式距离下的簇内距离平方和最小。这背后其实有数学上的对应关系。但是这里就必须敲个警钟了KMeans适用的边界在哪里答案非常明确——它更适合簇是凸形、大小相对均匀的情况。所谓凸形就是簇可以用一个质心一个半径来描述任意两个在簇内的点它们之间的连线段上的点也仍然在簇内。当数据分布像月牙形、环形、长条形这些非凸形状时KMeans就完全心有余而力不足了它会硬生生把本该属于同一个簇的点切到不同簇里去。另外KMeans对特征的尺度高度敏感。因为距离计算时每个维度的数值会被直接叠加如果一个特征的单位很大比如客户的年消费金额动辄几万另一个特征的单位很小比如年龄几十那距离计算几乎完全被消费金额这个维度主导年龄维度形同虚设。这个问题后面会专门讲处理方案。3. KMeans实战全流程从数据准备到结果解读3.1 数据预处理标准化不是可选项而是必选项这部分我们进入实战。我以经典的客户分群场景为例假设我们手头有一份包含客户年收入、年消费金额、消费频次三维特征的数据集总共1000条样本目标是把客户分成几类以便后续制定差异化的运营策略。这个场景是KMeans最典型的应用也是标题里“实战”二字最合适的落点。拿到数据之后第一步绝对不是直接塞进KMeans而是做数据检查。我们需要先看数据的缺失情况、量纲差异、是否有明显的异常值。如果特征之间存在严重的量纲差异比如年收入是几万到几十万的量级而消费频次是几到几十的数值直接跑KMeans得到的结果会完全倾斜。解决办法就是标准化。最常见的是Z-score标准化公式是 [ x_{new} \frac{x - \mu}{\sigma} ] 也就是把每个特征减去均值、除以标准差让处理后的特征均值为0、方差为1。这一步做完之后所有特征在这个空间里的尺度就统一了距离计算时每个维度才有平等的贡献。不夸张地说标准化这一步直接决定了KMeans结果的好坏是真正的高杠杆动作。还有一个实操细节如果数据里有明显的离群点我建议在标准化之后、聚类之前先用简单的可视化手段比如散点图或者IQR方法把它们识别出来。KMeans对离群点非常敏感因为它用均值作为质心一个极端值就足以把质心拉偏很远。遇到这种情况要么对离群点做单独处理要么考虑用对噪声更鲁棒的聚类算法比如DBSCAN。3.2 核心实现从零手写KMeans到调库实现数据准备好了接下来就是实现环节。我平时在学习的时候有个习惯遇到核心算法先自己手写一遍再把调库版本的输出和手写版本对比。这样做一遍之后对算法内部每个步骤的细节把握会深刻很多后面排查问题、调参数才会心里有数。下面这份代码是我用Python手写KMeans的实现为了演示清晰去掉了多余的边界校验把核心逻辑浓缩出来import numpy as np def kmeans_manual(X, k, max_iters100, tol1e-4): # 初始化质心随机从样本中选取k个点作为初始质心 n_samples, n_features X.shape rng np.random.default_rng(42) centroids X[rng.choice(n_samples, k, replaceFalse)] for i in range(max_iters): # 分配步骤计算每个样本到所有质心的距离归属最近质心 distances np.linalg.norm(X[:, np.newaxis, :] - centroids, axis2) labels np.argmin(distances, axis1) # 更新步骤基于当前划分重新计算质心为簇内均值 new_centroids np.array([ X[labels j].mean(axis0) if np.sum(labels j) 0 else centroids[j] for j in range(k) ]) # 检查质心变化量是否低于阈值如果是则提前终止 shift np.linalg.norm(new_centroids - centroids) centroids new_centroids if shift tol: print(f迭代在第 {i} 轮收敛) break return labels, centroids这段代码里有两个点在面试里经常会被问出来也是实际实现时需要留意的细节。一是初始化质心时我用了“从样本中随机选取”而不是在特征空间里完全随机生成点这样能避免质心落在没有样本覆盖的空白区域后续收敛速度会快很多二是更新质心时如果某个簇分到0个样本我就保留原来的质心防止程序报错这一步在工程实现里很重要。再看看调库版本用scikit-learn实现一点都不复杂from sklearn.cluster import KMeans from sklearn.preprocessing import StandardScaler # 数据标准化 scaler StandardScaler() X_scaled scaler.fit_transform(X) # 训练KMeans kmeans KMeans(n_clusters5, initk-means, n_init10, random_state42) labels kmeans.fit_predict(X_scaled) # 查看聚类中心 centroids kmeans.cluster_centers_注意我在调用KMeans时设置了几个关键参数它们是实际项目中必须理解的不能直接默认值不管。initk-means这是改进后的智能初始化方法它会尽量让初始质心彼此远离避免陷入局部最优。它和原始的完全随机初始化相比在绝大多数场景下都能拿到更好的结果。n_init10因为KMeans对初始化敏感所以库会在不同的随机初始化上跑10次最终返回目标函数值最小的一次结果。这就是为了对冲随机初始化的不确定性。random_state42固定随机种子保证结果可复现。这个参数在跨团队协作、代码评审、论文复现里非常重要项目里千万不要省。3.3 确定最优K值的完整过程肘部法则计算到轮廓系数验证我们接着往下走假设数据经过标准化之后已经准备好接下来直接用可视化手段来确定K值。绘制肘部法则曲线只需要一小段代码import matplotlib.pyplot as plt from sklearn.cluster import KMeans wcss [] for k in range(1, 11): kmeans KMeans(n_clustersk, initk-means, n_init10, random_state42) kmeans.fit(X_scaled) wcss.append(kmeans.inertia_) plt.plot(range(1, 11), wcss, markero) plt.xlabel(Number of clusters (K)) plt.ylabel(WCSS) plt.title(Elbow Method for Optimal K) plt.show()这里要留意kmeans.inertia_就是簇内误差平方和sklearn直接封装在训练完的模型对象里。画出来之后能常看到曲线在K5附近下降速度明显放缓形成了一个近似肘部的转折点因此K5是一个可能的候选值。但这还不够再用轮廓系数来精确验证一下K4、5、6哪个更优from sklearn.metrics import silhouette_score for k in [4, 5, 6]: kmeans KMeans(n_clustersk, initk-means, n_init10, random_state42) labels kmeans.fit_predict(X_scaled) score silhouette_score(X_scaled, labels) print(fK{k}, Silhouette Score{score:.4f})在我使用的这份模拟数据上跑出来的结果可能是K4时轮廓系数0.52K5时0.58K6时0.47。这样一对比就清晰了K5的轮廓系数明显更高说明它不但整体簇间分离更好簇内凝聚度也更为理想所以最终选定K5。这里也顺便提醒一句轮廓系数不是越高越好极高的分数可能说明每个簇都非常紧凑但过度细分会让簇之间没有业务上的差异化意义。最终选定K值的时候不能只看数据指标还要回到业务里问自己分出来这5类客户每一类是否有明显的、可以驱动决策的特征和标签如果某个簇的客户画像与另一个簇几乎重叠那就说明K值可能还是偏大了。3.4 聚类结果可视化与业务解读聚类完成之后最关键的就是把结果“翻译”成业务语言。高维数据没法直接可视化所以实用做法是选两个最有解释力的维度来做散点图给不同簇标上不同颜色。在我们这个场景里我通常选择“年收入”和“年消费金额”这两个维度因为它们是最容易对应到业务策略的。画完图之后再把每个簇的均值统计出来做成一张特征分析表。我用表格展示一下典型输出簇编号样本数年收入均值年消费均值消费频次均值业务标签021431,2002,3405.2低收入低频价格敏感型119862,8008,97015.8中收入中频品质关注型2176108,50021,40032.1高收入高频高价值型324645,3003,1206.4中等收入低频观望型416678,40016,50024.7中高收入高频潜力价值型到这一步聚类的价值才真正体现出来。你会发现它不只是一个技术动作而是把模糊的“用户是谁”这个问题用数据切成了几个可以行动的答案。高价值型客户有哪些特征、怎么服务观望型客户要不要定向唤醒这些问题都有了数据依据。4. 常见问题与选型比较避坑指南与工具推荐4.1 KMeans常见使用问题与排查思路我把自己在实际项目里和社区答疑时经常遇到的KMeans问题整理成了一个速查表每次遇到问题直接对照定位效率会高很多现象可能原因排查方式与解决方案聚类结果每次运行都不一样初始化质心随机性导致陷入不同局部最优增大n_init固定random_state尝试k-means初始化某个簇样本数极少甚至为1K值过大或离群点被孤立成簇降低K值检查离群点检查数据标准化是否到位聚类结果没有业务区分度K选择不合理或特征选取与业务逻辑脱节重新审视特征工程结合肘部法则和轮廓系数重新选K数据分布是月牙形、环形等非凸形状错误地选用了KMeans改用DBSCAN或层次聚类部分特征数值范围过大主导距离计算缺少标准化对所有特征统一做Z-score标准化或MinMax缩放迭代次数很多但不收敛初始质心选择太差或数据本身不适合KMeans使用k-means检查数据是否包含大量离群点从这张表能看出KMeans本身的代码实现已经足够成熟绝大多数问题其实出在数据质量、参数选择和对数据分布的认识上。这也就是为什么我一直强调要理解算法原理很多问题不用看报错信息光凭对算法的理解就能判断出原因。4.2 KMeans与DBSCAN、层次聚类的选型对比借着这个话题把KMeans和最近热搜里经常一起出现的DBSCAN、层次聚类AGNES放在一起做个对比。这三类算法的选择本质上取决于你对数据了解多少和你的业务目标是什么。KMeans的优势是计算快在大样本场景下几十万、上百万条依然能保持较高效率。它需要提前指定K对簇的假设比较强适合凸形簇不具备识别噪声的能力。如果你的业务里“分几类”是已知的或者可以用肘部法则快速确定的数据量又比较大那KMeans就是性价比最高的选择。DBSCAN的优势在于不需要指定簇数可以发现任意形状的簇还能自动识别噪声点。但它对两个参数邻域半径eps和最小样本数min_samples比较敏感参数调起来比KMeans更依赖经验和对数据的理解。另外当不同簇的密度差异很大时DBSCAN很难同时兼顾所有簇的识别效果这是它天然的短板。层次聚类的优势在于不需要提前指定簇数聚类过程会生成一棵完整的树状图你可以从中任意切分。它的可解释性极好还能展现出数据的嵌套结构。但它的计算复杂度相对较高一般适合处理几千到几万条以内的中小规模数据。我们的训练数据集如果不到一万条层次聚类完全是值得考虑的方案。我个人的选型策略整理成一句话先画图看数据分布如果明显有非凸形状或稠密噪声DBSCAN优先如果数据量大、簇形状接近球形、K值好确定KMeans就好如果样本量不大又想知道数据的层次结构层次聚类是首选。4.3 聚类效果的评估困境与经验分享聚类问题和分类问题一个很大的区别是分类可以用准确率、F1这些有标签的指标来衡量而聚类面对的是无标签数据评估起来天然就有难度。轮廓系数、Davies-Bouldin指数这些内部指标只是衡量了簇的紧凑性和分离程度它们不能替代业务判断。这一点我在实际项目里反复体会过有一次模型的轮廓系数很高但分出来的簇在业务汇报时完全讲不出故事最后只能重新调参再跑。我现在的习惯是采用“内部指标业务验证”双通道评估。内部指标用来快速筛选候选模型比如确定K值业务验证则用来最终拍板把聚类结果中的每个簇都统计特征、画图、起名字如果每个簇都能给出一个清晰的业务标签而且这几个标签组合在一起能覆盖完整的业务决策场景这个聚类结果才算真正落地。如果你是在学术或者技术研究的场景里还可以通过外部指标如ARI、NMI来对照有标注的数据做验证但纯业务场景下业务验证的优先级要更高。最后再分享一个调试中的小技巧使用KMeans的时候可以把最终的聚类中心打印出来和原始数据的特征含义一一对照确认每个维度的中心值是否符合业务直觉。如果某个维度的中心值明显不合理比如“消费频次”这个维度在所有质心上都是负数那几乎可以断定标准化或数据处理环节有疏漏。这种简单的“常识校验”往往比任何复杂评估指标都更能提前发现问题。