资讯动态

K-means聚类算法Matlab实现:从原理、参数调到K值选择与轮廓验证

发布时间:2026/10/19 2:24:14 来源:尧图企业网站定制
简介K-means聚类算法的Matlab实现详解文档面向机器学习初学者、MATLAB用户及数据分析人员帮助读者快速掌握用MATLAB完成K-means聚类分析的方法。文档从算法原理入手说明K-means是基于距离度量的无监督学习方法通过迭代将样本划分为K个簇使簇内相似度最高、簇间差异最大随后逐条拆解初始化、分配、更新、重复四个基本步骤。核心部分展示了MATLAB内置kmeans函数的实际用法包括输入参数设置、idx与centroids返回值含义并配合可直接运行的二维数据示例用scatter函数完成分类着色和质心标记的可视化便于对照代码理解执行逻辑。文档还总结了K-means需预先指定K值、对异常值敏感、初始化影响结果等局限性并简述K-means、二分K-means、核K-means等改进思路兼顾基础与进阶。资源为单个docx文档压缩包约15KB内容结构清晰、篇幅精炼现有442人学习下载适合自学或作为教学辅助材料。1. K-means聚类算法Matlab实现从7个数据点看这份代码能用在哪做客户分群时拿到一份 K-means 聚类算法的 MATLAB 代码7 个二维坐标点、两行核心调用跑完直接把散点图轮廓和质心画了出来。K-means 聚类算法在 MATLAB 里并不需要自己写迭代循环内置的kmeans函数把距离计算、样本分配、质心更新全封装好了。这份代码的价值在于它演示了从构造data矩阵开始到idx和centroids两个输出再到scatter可视化的最短流程。如果你正在处理测点分类、特征归并或者数据分桶这类任务数据已经准备好但还没想好怎么下手这份代码正好可以当扩展底座。熟练之后把它接到真实数据上十分钟就能出一版聚类原型。2. 原理与选型为什么直接信任kmeans函数K-means 可能是无监督学习里最容易上手的算法但容易上手不等于随便跑跑就行。拿到这份代码后我建议先花十分钟把它的迭代逻辑过一遍不然遇到 K 值选择、空簇、结果不稳定这些问题时你会以为是自己数据的问题其实是算法的边界情况。2.1 四步迭代背后初始化、分配、更新、收敛K-means 的目标很直白把n个样本分成K个簇让簇内样本尽量紧凑。衡量紧凑的指标是簇内平方和也就是每个样本到它所属质心的欧氏距离平方之和。算法本身是一个四步循环第一初始化。随机从数据里挑K个点作为初始质心。这一步的结果直接决定后面收敛到哪个局部最优所以它也是整个算法里最玄学的地方。第二分配。计算每个样本到K个质心的距离把它归给最近的那个质心形成K个簇。第三更新。对每个簇计算所有样本的均值用这个均值作为新的质心坐标。第四重复第二和第三步直到质心坐标的变化小于阈值或者达到最大迭代次数。对照到代码上data是 7 行 2 列的样本矩阵K 2表示分成两个簇。kmeans(data, K)内部做的就是上面这套循环最后返回两个结果idx是 7 行 1 列的向量每个元素是 1 到K之间的整数表示对应数据点属于哪个簇centroids是K行 2 列的矩阵每一行是一个质心的坐标。有个细节值得注意默认距离度量是平方欧氏距离也就是在Distance参数里填sqeuclidean。如果你的数据维度很高或者特征稀疏可以考虑换cityblock曼哈顿距离它对异常值的敏感度低一些。但换之前要清楚一件事——K-means 的核心逻辑是均值更新换了距离度量之后均值可能不再是这个距离下的最优中心收敛性质会变化。大多数场景下先用默认的sqeuclidean跑通流程再根据业务判断要不要换。2.2 手写循环与内置函数选kmeans而不是自己造轮子我自己早期做聚类的时候也尝试过手写 K-means 的迭代循环后来发现纯属浪费时间。手写代码看起来只有十几行但你要额外处理三件麻烦事终止条件的判定质心变化多少算收敛最大迭代次数设多少、空簇的处理某个簇一个样本都没分到怎么办、以及多维数据下按列计算均值的索引问题。这些边界情况看似不起眼真正跑起来全是坑。MATLAB 的kmeans函数把这些都封装好了而且支持几个对结果影响很大的扩展参数。比如Replicates参数可以让函数从多组随机初始质心出发各跑一遍最后返回总簇内平方和最小的一次结果Start参数可以指定初始化方式默认在新版本里已经是 k-means让初始质心互相远离极大降低空簇概率。你要是手写循环这些改进全部要自己实现工作量直接翻倍。所以我的建议是除非你是在上课或做算法对比实验必须展示迭代过程否则直接用内置kmeans。这份示例代码走的就是这个思路。你可以把它扩展成更稳健的调用形式[idx, centroids, sumd] kmeans(data, K, ... Distance, sqeuclidean, ... MaxIter, 200, ... Replicates, 5, ... Start, plus);这段代码里额外做了三件事MaxIter, 200限制最大迭代次数防止数据量大时算法长时间不收敛Replicates, 5让函数重复 5 次聚类挑总距离最小的一次减少初始化随机性带来的波动Start, plus显式指定 k-means 初始化。第三个输出sumd是K行 1 列的向量每个元素是该簇内所有样本到质心的距离平方和后面做手肘法选 K 值时要用到它。3. 跑通示例代码数据格式与可视化输出拆解这一章把示例代码从头到尾拆开讲。你不需要额外装任何工具箱MATLAB 基础环境里的 Statistics and Machine Learning Toolbox 就自带kmeans函数。脚本本身只有十几行重点在于理解数据矩阵的组织方式和两个输出变量的用法。3.1 data矩阵的组织一行一个样本每列一个特征示例里的data是一个 7 行 2 列的矩阵data [1 1; 1.5 2; 3 4; 5 7; 3.5 5; 4.5 5; 3.5 4.5];这里每一行是一个数据点第一列是 x 坐标第二列是 y 坐标。分号在 MATLAB 矩阵里表示换行所以1 1是第一行1.5 2是第二行依此类推。这 7 个点分布在二维平面上肉眼扫一遍就能看出左下角两个点靠得近右上角五个点相对集中天然适合分成两组。真实场景下数据一般不会手动敲进矩阵而是从 CSV、Excel 或者数据库里读进来。常见做法是用readmatrix直接读取表格文件data readmatrix(data.csv);读进来之后先确认一下尺寸。size(data)应该返回[样本数, 特征数]每一行是一个样本每一列是一个特征。很多新手在这里栽跟头读进来的表格如果行列转置了kmeans会把每个特征当成一个样本去聚类结果完全不对。还要注意data里不能有NaN或Inf。kmeans遇到缺失值会直接报错而且不会告诉你哪一行出了问题。我的习惯是在聚类前加一行检查assert(~any(isnan(data(:))), data contains NaN values);isnan(data(:))把整个矩阵展成一列再逐元素判断是否为 NaN只要有任意一个是assert就会中断并打印提示。这一步在数据量大时尤其值得做因为几万行数据里藏几个空值很容易漏掉。3.2 idx与centroids聚类结果的两个核心输出核心调用只有一行[idx, centroids] kmeans(data, K);idx是 7 行 1 列的向量元素值是 1 或 2。比如运行结果可能是[1; 1; 2; 2; 2; 2; 2]表示前两个点属于簇 1后五个点属于簇 2。centroids是 2 行 2 列的矩阵第一行是簇 1 的质心坐标第二行是簇 2 的质心坐标。如果你把centroids打印出来会发现簇 1 的质心大概在(1.25, 1.5)附近簇 2 的质心大概在(3.9, 5.1)附近——正好是两个簇的重心。接下来是可视化部分这段代码值得逐行看figure; hold on; scatter(data(:, 1), data(:, 2), [], idx, filled); scatter(centroids(:, 1), centroids(:, 2), 100, k, filled, Marker, x); legend(Cluster 1, Cluster 2, Centroids); hold off;第一个scatter的完整签名是scatter(x, y, sz, c, filled)。data(:, 1)取所有点的 x 坐标data(:, 2)取所有点的 y 坐标。sz传空数组[]表示使用默认点大小c传idx这个向量——MATLAB 会把 1 和 2 自动映射到两种不同颜色这就是为什么两个簇的散点会显示成不同颜色。最后加filled让点是实心的不加的话默认是空心圆圈视觉上不够清晰。第二个scatter画质心。100是点的大小k是黑色Marker, x把标记形状改成交叉。legend里的三个标签依次对应第一个散点图、第二个散点图和第二个散点图的 x 标记。注意这里的legend是按绘图对象的创建顺序匹配的所以Cluster 1和Cluster 2对应的是第一个scatter里的两种颜色。运行这段代码后你会看到左下角两个点和右上角五个点被分开染色两个黑色 x 分别落在各自簇的中心位置。如果K 3你会看到三个簇三种颜色质心也是三个。可视化这一套代码可以完整保留后面换任何数据集都只需要改data和K。4. 参数调整与K值选择从K2扩展到真实场景的三个改动示例里K 2是写死的但真实数据不会告诉你该分几类。我见过不少同学把 K 值随手填成 3 或 5跑出来效果不好就怪算法不行其实问题出在这个数据集根本不该分成这么多簇。这一章讲清楚 K 值怎么定、初始化怎么改、数据要不要标准化。4.1 K值怎么选手肘法看到拐点再定手肘法Elbow Method是选 K 最常用的手段思路很简单对 K 从 1 到某个上限分别跑聚类记录每次的总簇内平方和 SSE然后画折线图。K 增加时 SSE 必然下降因为簇越多每个簇越紧凑。但下降速度会在某个 K 之后明显变缓折线图上出现一个像手肘的拐点这个拐点对应的 K 就是比较合理的取值。实现代码不需要额外工具箱直接用kmeans的第三个输出sumd累加rng(42); data [1 1; 1.5 2; 3 4; 5 7; 3.5 5; 4.5 5; 3.5 4.5]; K_max 6; sse zeros(K_max, 1); for K 1:K_max [~, ~, sumd] kmeans(data, K, MaxIter, 500, Replicates, 5); sse(K) sum(sumd); end plot(1:K_max, sse, -o); xlabel(K); ylabel(SSE);[~, ~, sumd]里的~表示跳过不需要的前两个输出。sumd是 K 行 1 列的向量sum(sumd)把所有簇的距离平方和加总就是当前 K 下的总 SSE。Replicates, 5在这里很重要——每个 K 值都从 5 组随机初始质心出发跑一遍取最优的一次避免某个 K 恰好收敛到差解导致 SSE 异常。跑完这段代码你会看到 SSE 在 K1 时最高K2 时大幅下降K3 及以后下降变缓。折线图上的肘部就在 K2 附近说明这份数据分成两类是合理的。这个小例子的结论很干净真实数据往往没有这么明显的拐点这时候就结合业务经验来定分多少类业务上解释得通每一类的人数或样本量够不够支撑后续分析手肘法给的是数学上的参考业务约束才是最终决策依据。4.2 初始化与可复现性kmeans和rng的双保险K-means 的初始化敏感是个老话题。不同初始质心可能收敛到不同局部最优解这在数学上无法避免。较新版本的 MATLAB 默认使用 k-means 初始化这个策略刻意让初始质心互相远离能大幅降低收敛到差解的概率。但降低不等于消除随机性依然存在。要让实验结果可复现最直接的办法是固定随机种子。在调用kmeans之前加一行rng(42)这样只要数据不变每次运行得到的结果就完全一致。这里的数字 42 只是习惯换成任何整数都行。rng(42); [idx, centroids] kmeans(data, K, Replicates, 10, Start, plus);Start, plus显式指定 k-means。虽然新版本默认就是它但显式写出来有两个好处一是代码的意图更明确阅读者不用去查文档二是兼容老版本 MATLAB——旧版本默认是随机选择初始质心不写这个参数结果可能差很多。Replicates, 10让函数从 10 组不同初始质心出发各跑一次自动选择最优结果。代价是运行时间约等于原来的 10 倍对这份只有 7 个点的数据没什么影响但如果是几十万行数据这个参数值就要慎重。还有一类坑是数据量纲。假设特征里同时有年龄和年收入年龄几十、收入几十万欧氏距离几乎完全被收入主导聚类结果相当于只按收入分群。解决办法是先标准化再聚类data_norm zscore(data); [idx, centroids] kmeans(data_norm, K);标准化之后的质心坐标都在零均值、单位方差的空间里画图时如果还想展示原始坐标下的质心要手动做逆变换centroids_orig centroids .* std(data, [], 1) mean(data, 1);这行代码做的事很直观zscore是(数据 - 均值) / 标准差逆变换就是乘标准差再加均值。std(data, [], 1)计算每列标准差mean(data, 1)计算每列均值。我在实际项目里画图前一定会做这一步不然图上质心位置和原始数据对不上。5. 避坑指南MATLAB运行K-means的四个典型问题这一章全是实际跑脚本时翻车翻出来的经验。每一条都按现象 → 原因 → 解决的顺序写你可以直接对照排查。5.1 每次运行结果都不一样现象同一份数据、同一个 K连续跑两次kmeansidx和centroids变了有时候连画出来的簇边界都不同。原因kmeans依赖随机初始质心不同起点可能收敛到不同的局部最优解。这不是 MATLAB 的 bug是 K-means 算法的固有特性。解决在脚本开头加rng(42)固定随机种子输出就可以复现。如果对稳定性有更高要求用Replicates, 5多跑几遍让 MATLAB 自动选择总 SSE 最小的一次结果。我自己的习惯是调试阶段固定种子确认算法逻辑没问题后、正式跑批量数据时再放开随机性用多次运行取最优的方式保证结果质量。5.2 聚类结果出现空簇现象K 设成 3结果画出来的图只有两簇点有一簇是空的或者centroids里有一行坐标明显不在任何数据点附近。原因初始质心选得不好。比如两个质心都落在同一片密集区域另一个区域没有质心吸引样本分配阶段结束后那个簇一个点都没分到。这是 K-means 初始化敏感的典型表现。解决换用 k-means 初始化或者增加Replicates次数。k-means 的核心改进就是让初始质心尽量互相远离空簇概率大幅下降。我在做客户分群时 K 超过 5 就习惯性加Replicates, 10基本没再遇到过空簇。5.3 数据没做标准化聚类结果被大数值特征带偏现象数据里有年龄和年收入两类特征收入动辄几十万年龄只有几十聚类结果几乎只按收入区分年龄信息完全被淹没。原因K-means 用欧氏距离衡量相似度量纲大的特征在距离计算里权重被放大量纲小的特征对距离的贡献可以忽略不计。解决聚类前先做zscore标准化让每个特征变成零均值、单位方差。标准化后的特征在距离计算里地位平等聚类结果才真正反映多维空间中的近邻关系。注意画图时要按前面说的逆变换把质心映射回原始量纲否则图上的坐标轴还是标准化空间业务方看不懂。5.4 legend显示混乱或者所有点一个颜色现象K4 时设置legend(Cluster 1, Cluster 2, Cluster 3, Cluster 4, Centroids)图例标签和实际颜色对不上或者所有点显示成同一种颜色。原因当idx作为颜色向量传给scatter时MATLAB 会按数值大小自动映射颜色但legend是按绘图对象匹配的。如果连续调用多个scatter而不显式管理句柄图例标签的顺序和数量就容易错位。解决循环绘制每个簇的数据点同时把绘图句柄收集起来再统一传给legendfigure; hold on; h gobjects(K1, 1); for k 1:K h(k) scatter(data(idx k, 1), data(idx k, 2), 60, filled); end h(K1) scatter(centroids(:, 1), centroids(:, 2), 100, kx, LineWidth, 2); legend(h, [cellstr(Cluster (1:K)), {Centroids}]);gobjects(K1, 1)预分配一个图形对象数组scatter返回的句柄按顺序存进去。idx k是逻辑索引把属于第 k 个簇的点全部筛出来。cellstr(Cluster (1:K))动态生成Cluster 1、Cluster 2这些标签K 改成多少都不用手动改图例。这个方法在 K 值变大以后比单行scatter可靠得多。6. 进阶验证用silhouette指标给聚类结果把关K-means 运行结束不等于聚类质量过关。我拿到一份聚类结果交付前会先看轮廓系数silhouette coefficient。这个指标衡量每个样本与自己所在簇的紧密度和与邻近簇的分离度值域从 -1 到 1。接近 1 说明样本离自己簇的近邻远、离其他簇的点远聚类结构清晰接近 0 说明样本在两个簇的边界附近为负值说明这个样本可能被分错了簇。6.1 轮廓系数怎么看从柱状图到平均轮廓值MATLAB 里一行代码就能拿到轮廓图figure; silhouette(data, idx);s silhouette函数接收原始数据和聚类标签idx输出每个样本的轮廓值柱状图。柱子的颜色按簇区分长度越接近 1 越好。如果某个簇里有不少负值柱子说明这个簇内部结构松散样本在归属上摇摆不定。光看图不够还要算一个平均轮廓值来量化K_candidate 2:4; avg_silh zeros(length(K_candidate), 1); for i 1:length(K_candidate) [idx_i, ~] kmeans(data, K_candidate(i), Replicates, 5); s silhouette(data, idx_i); avg_silh(i) mean(s); end这段代码对候选的 K 值分别聚类、分别算平均轮廓值最后比较哪个 K 最大。注意silhouette的第一个参数data要和调用kmeans时传入的数据一致。如果你对标准化后的数据聚类轮廓验证也要用标准化之后的数据否则量纲差异会污染距离计算轮廓值失真。6.2 把轮廓验证嵌进交付流程手肘法定 K 的范围轮廓系数定最终的 K这是我跑聚类实验的标准流程。手肘法先看 SSE 折线图的拐点排除明显不合理的 K 值轮廓系数再对剩余的候选 K 逐一手动验证选平均轮廓值最高的那个。两个指标配合使用比单看任何一个都稳。我用这个习惯吃过亏。有一次给某跨平台系统的数据模块做特征归并K3 时平均轮廓值 0.62K4 时 0.71我当时直接选了 4后来被业务方质疑第四类里混了几条噪声数据。回头排查发现是数据没有标准化轮廓值被两个大数值特征主导看起来数值高其实是假象。从那以后我每次交付聚类结果前都强制走一遍先zscore再跑kmeans再silhouette最后画图。这套流程不复杂但能挡住大部分翻车。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑