资讯动态

Matlab实现Kmeans聚类:从数据预处理到结果评估完整指南

发布时间:2026/9/11 18:42:39 来源:尧图企业网站定制
实验室一个小师弟抱着电脑来找我说课程作业要用Matlab做Kmeans聚类可代码跑出来一团乱。我帮他理了一遍发现十有八九的人都会卡在同样的几个地方数据没标准化、K值随意拍脑袋、可视化只画了张散点图却不看簇中心。今天就把这套流程完整写出来从数据导入到代码实现、从K值选择到结果评估把该注意的坑一个个填平。如果你也是刚接触聚类、或者实验室里被指定用Matlab做数据分析这篇应该能帮你少走不少弯路。Kmeans是原型聚类里的经典算法也常被写作k-means。很多机器学习课程和实训平台比如头歌里的原型聚类算法kmeans训练题都会拿它当入门题Matlab的Statistics and Machine Learning Toolbox里也内置了现成函数。但会用函数和真正理解算法是两码事所以下面我不光写怎么调用还会把底层逻辑和手写实现一并讲透。1. 动手之前先搞懂Kmeans到底在做什么很多人一上来就敲kmeans(X, 3)跑出结果就以为完事了。其实Kmeans的核心思想特别朴素但朴素的算法背后藏着不少容易翻车的细节。先花几分钟把原理和方案选型理清楚比急着写代码划算得多。1.1 Kmeans的直观理解Kmeans要做的事情一句话概括就是把n个样本划分成K个簇让每个样本到它所属簇中心的距离平方和最小。整个过程可以看作在交替做两件事第一根据当前的簇中心把每个样本分给离它最近的那个中心第二根据新分到的样本重新计算每个簇的中心位置。这两个步骤反复交替直到簇中心不再明显变化。拿生活里的场景打比方。假设一个城市里有若干个快递站点每个站点有个负责人。刚开始站点位置是随便选的第一步每个居民去找离自己最近的站点第二步每个站点的负责人统计归属自己的居民然后把站点搬到这些居民位置的几何中心。搬完之后有些居民发现自己离另一个站点更近了于是重新换站点负责人又跟着调整位置。如此反复最终每个站点和它服务的居民区域就稳定下来。这里的“站点”就是簇中心“居民”就是样本点。数学上用目标函数表达更清楚。给定数据集 X {x1, x2, ..., xn}Kmeans要最小化SSE Σ(i1..n) Σ(k1..K) r(i,k) · ||x_i - μ_k||²其中r(i,k)表示样本i是否属于簇kμ_k是第k个簇的中心。这个公式也叫簇内误差平方和SSE越小说明簇内样本越紧凑。Kmeans本质就是一个求解这个优化问题的迭代算法只不过它找到的通常是局部最优而不是全局最优这也是后面要讲Replicates参数的原因。1.2 为什么选Matlab而不是Python我平时Python和Matlab都在用各有各的顺手场景。如果只是单纯算个聚类Python的sklearn确实生态更丰富模型切换也方便。但Matlab在特定场景下优势非常明显尤其是以下三类情况你的数据处理链路本来就基于Matlab比如信号处理、图像处理的项目数据已经在工作区里了没必要再倒腾到Python学校课程或实验室项目明确要求Matlab实现比如很多头歌的机器学习实训题就是用Matlab或类似环境考核聚类算法需要快速可视化、出论文级图表Matlab的绘图交互体验在科研圈积累很深改坐标轴、调字体、导矢量图都很顺手。另外Matlab内置的kmeans函数背后是经过高度优化的实现默认采用k-means初始化配合Replicates参数多次重复运行能有效缓解局部最优问题。对大多数中等规模数据几万行以内来说计算速度和稳定性完全够用。1.3 什么时候不适合用KmeansKmeans虽然好用但它有与生俱来的假设簇的形状偏向凸形球形而且对噪声和离群点敏感。如果你的数据分布是月牙形、环形、或者簇与簇之间严重重叠Kmeans的效果大概率不理想。遇到这种情况建议考虑层次聚类比如AGNES或密度聚类比如DBSCAN。Matlab里层次聚类有linkage和cluster函数密度聚类也有现成实现这些是Kmeans之外的合理替代方案。判断数据适不适合Kmeans最直接的办法是先用PCA降维到二维或三维人眼扫一遍分布形态心里就有数了。2. 数据准备聚类是否靠谱七成看这里我见过太多人把精力全花在调K值和选参数上结果忽视了数据预处理。事实上聚类结果的质量受数据预处理的影响远大于算法参数的影响。Kmeans用的是距离度量数据一旦量纲不一致距离计算就会完全跑偏。2.1 用readtable快速导入CSV和Excel数据Matlab读取表格数据最推荐的是readtable。它能把CSV、Excel、TXT等格式的数据读成table类型表头、列名、数据类型都帮你自动识别好非常方便。% 读取带表头的CSV文件 T readtable(data.csv); % 查看前几行 head(T) % 读取Excel文件(xlsx/xls) T2 readtable(data.xlsx, Sheet, Sheet1);如果数据文件只有纯数值、没有表头用readmatrix会更省事它直接返回double数组X readmatrix(numeric_data.csv);顺带说一句有人问“怎么把CSV导入Matlab做FFT仿真”这类问题的核心是先搞清楚数据是哪几列、采样率是多少再用readmatrix或readtable读进来后续处理也就是对相应列做运算而已。导入以后第一时间用whos查看变量维度用summary(T)查看每列的统计信息确认数据量级和缺失情况这个习惯能帮你提前发现很多问题。2.2 标准化不同量纲是Kmeans最大的坑这里要反复强调Kmeans是基于欧氏距离默认情况的算法特征之间量纲差距过大会让距离被大数值特征主导。举个很常见的例子用户数据里“年龄”范围是20到60“年收入”范围是5万到100万。如果不做处理计算距离时年龄的贡献几乎可以忽略最后聚类结果基本只反映收入差异“年龄”这个维度等于白选了。解决办法是对每个特征做标准化。最常用的是z-score标准化让每个特征变成均值为0、标准差为1的分布。Matlab里一行搞定X_norm zscore(X);也可以手动实现本质就是 (x - mean(x)) / std(x)。标准化之后各个特征在距离计算中拥有平等的发言权。如果你需要保留原始数据的解释性可以把聚类结果映射回原尺度后面我会讲到这个技巧。2.3 脏数据清理与类型转换真实数据几乎不可能干干净净。常见的脏数据包括缺失值、异常值、非数值型列这些对Kmeans都是灾难。缺失值会导致距离计算出现NaN很多函数直接报错或者返回垃圾结果。处理方式有两种直接删除包含缺失值的行T rmmissing(T);适合缺失比例不高的情况填充缺失值T fillmissing(T, linear);适合时间序列或连续特征。有些table里会有字符串或分类列比如“城市”“性别”。这类categorical特征不能直接放进Kmeans因为它们无法参与数值距离计算。要么用categorical转成序号编码比如男1女2要么直接删除不参与聚类。在实际项目中我通常先画个相关性热图把明显无关的ID列、日期列去掉再对选定的特征做标准化。还有一个特别容易踩的坑kmeans函数要求输入是double类型的数值矩阵不是table。所以用readtable读进来之后需要把数值列提取出来转成矩阵% 假设T中前4列是数值特征 X table2array(T(:, 1:4));这一步漏掉的话运行kmeans时Matlab会报错提示输入必须是数值矩阵。很多新手卡在这一步其实只要打印class(X)检查一下类型问题立刻就清楚了。3. 核心实现官方函数和手写版本双管齐下这一节是全文的重头戏。我会先讲怎么调用Matlab内置函数快速实现再贴一个手写版Kmeans的代码帮助你彻底理解算法迭代逻辑。两条路都走通才算真正掌握了Kmeans。3.1 最省心上手直接调用kmeans函数Matlab的kmeans函数位于Statistics and Machine Learning Toolbox。最基础的调用方式是idx kmeans(X, k);其中X是n×p的样本矩阵k是你要分的簇数返回的idx是n×1的向量每个元素表示对应样本被分到了哪个簇1到K。更完整的调用会同时返回簇中心和更多统计信息[idx, C, sumd, D] kmeans(X, k, Replicates, 10, MaxIter, 500, Display, final);各返回值含义如下idx每个样本的簇归属CK×p的簇中心矩阵sumd每个簇内样本到中心的距离平方和D每个样本到每个中心的全距离矩阵。推荐在实际使用中加上Replicates参数。这个参数的含义是“用不同的初始中心重复跑N次最后返回SSE最小的一次结果”。因为Kmeans对初始中心敏感不同的随机起点可能收敛到不同局部最优多跑几次能显著提高稳定性。我一般设10数据量大时可以适当减少。完整示例代码如下用Matlab自带的鸢尾花数据集fisheririsload fisheriris; % 加载内置数据集 X meas; % 150×4的数值特征 X_norm zscore(X); % 标准化 k 3; % 鸢尾花分为3类 rng(42); % 固定随机种子保证结果可复现 [idx, C, sumd, D] kmeans(X_norm, k, Replicates, 10); % 查看每类的样本数 tabulate(idx);这里我习惯先固定随机种子rng(42)原因后面讲“每次结果不一样”时会展开。tabulate可以快速看每个簇的样本量检查有没有某个簇太小有的话说明K值可能选大了或者数据分布不均衡。3.2 自己动手写一个Kmeans彻底理解迭代逻辑官方函数好用但如果只看它你可能永远不知道黑盒里发生了什么。很多课程和实训比如头歌里的原型聚类算法kmeans就是要求学生自己实现。手写一个简化版Kmeans其实不难核心就三步初始化中心、分配样本、更新中心然后循环。function [idx, C] mykmeans(X, k, maxIter) % 手写Kmeans简化版 % 输入X为n×p样本矩阵k为簇数maxIter为最大迭代次数 % 输出idx为n×1簇标签C为k×p簇中心 [n, p] size(X); rng(42); % 1. 随机选k个样本作为初始中心 init_idx randperm(n, k); C X(init_idx, :); for iter 1:maxIter % 2. 计算每个样本到所有中心的距离并分配最近的中心 D pdist2(X, C); % n×k距离矩阵 [~, idx] min(D, [], 2); % 每个样本最近的簇编号 % 3. 更新中心每个簇内样本的均值 C_new zeros(k, p); for j 1:k if any(idx j) % 防止空簇 C_new(j, :) mean(X(idx j, :), 1); else C_new(j, :) C(j, :); % 空簇保持原中心 end end % 4. 收敛判断中心变化足够小则停止 if norm(C_new - C, fro) 1e-6 break; end C C_new; end end调用方式和官方函数一样传入数据、K值和最大迭代次数即可mydata zscore(meas); [idx_mine, C_mine] mykmeans(mydata, 3, 200);这个版本省略了很多工程细节比如k-means初始化、多次重复选择最优、加速计算等但它完整保留了Kmeans的骨架。你可以在每次迭代后画一下当前中心的位置直观看到中心从随机点逐步“移动”到数据密集区域的过程理解效率会非常高。如果有空你还可以在这个版本上自己加上k-means初始化做法是先随机选第一个中心之后每个新中心以“距离现有中心越远的点被选中概率越大”的原则从剩余样本中选取。这个改动虽然只有十几行但对结果稳定性的提升非常明显。3.3 K值怎么选肘部法则轮廓系数K是Kmeans里最让人头疼的参数。没有绝对正确的K只有相对合适的K。工程上最常用的两个工具是肘部法则Elbow Method和轮廓系数Silhouette Coefficient。肘部法则的思路随着K增大SSE必然减小因为簇越多每个簇内部越紧凑。但SSE的下降速度会越来越慢。画一条“K-SSE”曲线找到拐点类似手肘的位置这个K就是比较自然的取值。代码K_list 1:8; SSE zeros(size(K_list)); sil_avg zeros(size(K_list)); for i 1:length(K_list) k K_list(i); rng(42); [idx, C, sumd] kmeans(X_norm, k, Replicates, 10); SSE(i) sum(sumd); if k 1 sil_avg(i) mean(silhouette(X_norm, idx)); end end % 画肘部图 figure; plot(K_list, SSE, o-, LineWidth, 2); xlabel(K); ylabel(SSE); title(肘部法则确定K值);轮廓系数则是另一个视角它综合衡量每个样本与自己簇内其他样本的紧密度、以及与其他簇样本的分离度取值范围在-1到1之间。值越接近1说明聚类效果越好接近-1说明样本可能被分错了簇。Matlab直接有silhouette函数返回每个样本的轮廓值取平均就是整体聚类质量的指标。实操时我的习惯是先用肘部法则找出K的候选区间再算几个候选K的平均轮廓系数取轮廓系数最大的那个K。两者结合比单看一条曲线可靠得多。如果K2和K3的平均轮廓系数非常接近那就选更符合业务理解的那个算法指标最终要服务于业务解释。4. 聚类结果可视化与效果评估聚类做完不算完你得能让人一眼看出聚类效果好不好。可视化在聚类分析里既是沟通工具也是自我检查工具。很多奇怪的结果画图之后立刻暴露。4.1 散点图叠加簇中心如果原始数据只有两个特征直接用gscatter按簇标签画二维散点图figure; gscatter(X_norm(:,1), X_norm(:,2), idx); hold on; plot(C(:,1), C(:,2), kx, MarkerSize, 12, LineWidth, 3); hold off; legend(Cluster 1, Cluster 2, Cluster 3, Centroids); xlabel(Feature 1 (标准化)); ylabel(Feature 2 (标准化)); title(Kmeans聚类结果 (k3));gscatter会根据idx里的类别自动分配不同颜色和点形比手动scatter加循环省事得多。叠加簇中心用黑色叉号一眼就能看出中心是否落在对应簇的“几何中心”位置。如果数据维度超过二维直接看原始特征不现实。我的做法是先做主成分分析PCA把高维特征压缩到前两个主成分再做散点图。虽然会损失少量信息但通常能保留数据最主要的分布结构[coeff, score, latent] pca(X_norm); figure; gscatter(score(:,1), score(:,2), idx); xlabel([PC1 (, num2str(round(latent(1)/sum(latent)*100)), %)]); ylabel([PC2 (, num2str(round(latent(2)/sum(latent)*100)), %)]); title(PCA降维后的聚类可视化);PC1和PC2后面的百分比表示该主成分解释的方差比例放到坐标轴标签里比只写“PC1”“PC2”更有信息量。4.2 轮廓图判断聚类质量散点图能看出大致分离程度但确定“每个样本分得对不对”还得靠轮廓图。Matlab的silhouette函数非常直观figure; silhouette(X_norm, idx); title(聚类轮廓图);输出是一张按簇排列的条形图每个样本对应一个水平条。条形越长越接近1说明这个样本和它所在簇很匹配如果出现很多负值的长条说明这些样本大概率分错了簇或者K值不合适。整体上条形整体偏长、没有明显负数长条就说明聚类结构比较清晰。我自己的经验阈值是这样的平均轮廓系数超过0.5说明聚类结构中等偏上超过0.7属于明显可分低于0.25则说明数据基本没有可聚类的结构或者这个K值根本不对。当然这些经验值仅供参考不同领域的数据特征差异很大。4.3 聚完类之后一定要看的“分簇画像”这个步骤很多人会忽略但在我看来它才是聚类的价值所在。聚类不是终点业务分析才是。所谓“分簇画像”就是算一下每个簇在每个特征上的均值、中位数、标准差看看不同簇之间的差异是否具有业务意义。% 按簇分组统计原始特征均值 clust_stats grpstats(T, idx, {mean, std}); disp(clust_stats);举个例子如果是对电商用户聚类分完簇后你要能回答类似“簇1是高消费高频次人群”“簇2是低消费低频次人群”这种结论。光说“分成了3个簇”没有任何价值能解释每个簇的特征画像才说明聚类落地了。如果发现某些簇的画像几乎一样说明K值选多了或者特征选得不好需要回去调整。5. 常见问题与排查经验这部分是我真正花时间踩坑踩出来的。很多问题不看一眼现场很难想到原因我把高频问题按“现象-原因-方案”整理出来你遇到类似情况可以直接对照排查。5.1 为什么每次跑出来的结果都不一样Kmeans使用随机初始化即使是k-means也带随机性所以同一份数据跑两次kmeans结果可能有细微差异严重时甚至簇标签顺序都会变比如第一次的“簇1”在第二次变成了“簇2”。解决办法有两个固定随机种子在调用前加rng(42);这样每次运行结果完全一致方便复现实验设置Replicates参数让函数多次运行取最优能减小随机性带来的波动。固定随机种子不是什么“作弊”而是工程上的必备习惯。做实验、写报告、演示给老师看都需要结果可复现。5.2 聚类结果看着很乱问题出在哪一图乱成粥先别怪算法。按顺序排查这几个地方是否标准化了没有标准化的话结果很可能被大数值特征主导是否包含无关特征ID列、日期列、唯一标识这类特征会严重干扰距离计算K值是否合理K太大容易把完整簇切碎K太小会把不同群体硬塞一起数据是否适合Kmeans如果数据形状是环形、月牙形、极度不平衡Kmeans天然不擅长换层次聚类或DBSCAN。我的建议是先可视化原始数据分布再做聚类。数据本身分布一团糟算法再强也白搭。5.3 数据量大、跑得慢怎么办Kmeans的时间复杂度大约是O(n·K·d·iter)样本量越大迭代次数越多耗时越线性上升。几万行数据在Matlab里通常没问题但如果跑到百万级就会明显感到卡顿。常用手段包括抽样如果数据量很大先随机抽1万到5万条跑通流程确定K值和预处理方案后再全量计算降维先做PCA把特征维度压缩到20维以内距离计算量会大幅下降调整MaxIter如果数据本身收敛快可以适当调小迭代上限加快运行速度关闭显示Display, off避免迭代过程刷屏能稍微提速。5.4 版本和工具箱相关的坑kmeans函数依赖Statistics and Machine Learning Toolbox。如果运行时报“未定义函数或变量‘kmeans’”先用ver命令检查工具箱是否安装ver(stats)如果没安装解决办法是安装该工具箱或者改用其他方式比如手写Kmeans反而完全不需要额外工具箱。另外不同Matlab版本中kmeans函数的默认参数有差异。较老版本默认使用随机初始化而新版本默认使用k-means这意味着同一段代码在不同版本上跑出来的结果可能不完全一致。遇到这类问题多看对应版本的官方文档不要拿旧教程的参数在新版本上硬套。5.5 常见问题速查表现象可能原因解决方案每次跑结果不同随机初始化、局部最优rng固定种子Replicates设置10以上聚类效果差、簇重叠严重未标准化、特征含噪声、K不合适zscore标准化、清洗特征、用肘部法轮廓系数选K报错“输入必须是数值矩阵”传入的是table或cell用table2array转为double数组某个簇只有一两个样本K偏大或存在离群点减小K检查并剔除离群点中心点落在异常位置离群点干扰均值计算标准化、去除离群点或用k-medoids不同版本结果不一致默认初始化策略不同固定随机种子明确距离度量和参数5.6 反标准化把簇中心映射回真实值最后分享一个非常实用的小技巧。如果你先对数据做了zscore标准化聚类后得到的簇中心C是标准化空间里的坐标直接拿出来给人看很难解释。要还原真实值只需要把每个特征的标准化和均值信息存下来再反变换回去mu mean(X); sigma std(X); C_real C .* sigma mu; % 逐列还原这里的. *是逐元素乘法因为C的每一列对应一个特征要和对应特征的均值和标准差匹配。还原之后的簇中心就是真实业务含义下的“典型代表”比如“收入中位数8.5万、年龄35岁”这样可以直接读懂的数字。这个细节在写报告或给业务方汇报时非常加分但很多人不知道。我自己的体会是Kmeans算法本身并不困难真正拉开差距的是数据预处理和对结果的解释能力。代码能跑通只是第一步能解释清楚每个簇的差异、能根据业务或课程要求合理选K、能稳定复现实验结果这些才是实际使用中最花时间也最有价值的部分。希望这篇能帮你把Kmeans从“跑通”推进到“用明白”的阶段。

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

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

免费获取报价