☰
MATLAB kmeans聚类实战:从数据准备到结果验证的完整指南
2026/10/1 8:56:05 网站建设 项目流程

简介:这份资源是面向本科及以上学习者、科研人员与工程实践者的MATLAB聚类分析工具包,围绕k-means算法解决数据分组与模式识别问题,适合课程作业、论文实验及项目原型开发等场景。压缩包共10个文件,约378KB,包含2个.m主程序脚本、1个.xls与1个.xlsx数据表格,以及6张jpg结果示意图,代码完整且附有注释,数据齐全,便于直接运行与后续扩展。资源已有232人学习下载,说明其在教学与实践中具有一定参考价值。读者可获得一套可复用的k-means实现流程,包括数据读取、聚类计算与结果可视化,配合示例数据与运行截图,能快速理解算法参数设置与效果评估,并在此基础上修改距离度量、聚类数目或替换数据集,完成创新性扩展。若运行中遇到疑问,也可联系作者获取支持。

1. 从一份能跑的 kmeans 聚类 MATLAB 代码说起:数据齐全到底意味着什么

很多人第一次接触聚类,是在一堆没有标签的样本面前发懵:分类模型用不了,因为没人告诉你哪条数据属于哪一类。kmeans 聚类分析就是干这个的——它不需要标签,只靠样本之间的距离,把相似的东西自动归堆。MATLAB 做这件事有天然优势,矩阵运算快、可视化顺手、统计与机器学习工具箱里现成的函数拿来就能用。但真正卡住新手的往往不是算法本身,而是「代码完整、数据齐全」这六个字背后的东西:数据长什么样、维度怎么统一、聚类数 K 怎么定、结果怎么验证。这篇笔记就围绕一份可直接复现的 kmeans 聚类 MATLAB 代码,把数据准备、参数设置、结果评估和踩坑记录一次讲透,适合刚上手聚类、手里有数据但不知道怎么落地的人。

2. kmeans 在 MATLAB 里到底怎么算:原理、选型与最小可跑代码

2.1 算法核心与 MATLAB 的实现路径

kmeans 的目标很朴素:把 N 个样本分成 K 个簇,让每个样本到它所属簇中心的距离平方和最小。数学上就是最小化簇内平方误差和,这个目标函数没有解析解,所以用迭代逼近。标准流程是四步:初始化 K 个中心、把每个样本分配给最近的中心、重新计算每个簇的中心、重复分配和更新直到中心不再明显移动或达到迭代上限。

MATLAB 里实现 kmeans 有两条路。一条是统计与机器学习工具箱的kmeans函数,一行调用就能出结果,支持距离度量、重复次数、并行等参数;另一条是自己写循环,适合理解算法细节或做教学演示。实际项目里我一般先用内置函数跑通基线,确认数据没问题、K 值合理,再考虑要不要手写改造。内置函数底层用的是 Lloyd 算法,配合 kmeans++ 初始化,能显著降低陷入局部最优的概率。

选型上要注意:如果你的数据维度很高(比如上百维),欧氏距离会失效,这时候要么先降维(PCA、t-SNE),要么换余弦距离。如果簇的形状不是球形,kmeans 本身就不合适,得考虑 DBSCAN 或谱聚类。这些边界在动手前就要想清楚,否则跑出来的结果看着有模有样,实际没法用。

2.2 数据准备:从原始表格到聚类矩阵

「数据齐全」不是指文件多,而是指数据能直接喂进算法。kmeans 要求输入是一个 N×D 的数值矩阵,每行一个样本,每列一个特征。常见的数据问题有三类:缺失值、量纲不统一、类别型字段没编码。

缺失值处理上,我一般先看缺失比例。低于 5% 的,用列均值或中位数填补;高于 20% 的,考虑直接删列或换特征。量纲问题更隐蔽,比如一个特征是年龄(0-100),另一个是年收入(0-1000000),不标准化的话收入会完全主导距离计算,年龄等于白给。标准做法是 z-score 标准化,让每个特征均值为 0、标准差为 1。

下面是一段数据准备代码,假设原始数据存在 Excel 里,包含数值列和几个类别列:

% 读取原始数据,第一行为表头 rawData = readtable('raw_data.xlsx'); % 查看前几行,确认列名和数据类型 head(rawData); % 分离数值特征和类别特征 numFeatures = rawData(:, {'Age', 'Income', 'Score', 'Spend'}); catFeatures = rawData(:, {'Gender', 'City'}); % 类别特征做独热编码,转成数值 catEncoded = onehotencode(catFeatures, 1:width(catFeatures)); % 合并成完整特征矩阵 featureMatrix = [table2array(numFeatures), catEncoded]; % 缺失值用列中位数填补 featureMatrix = fillmissing(featureMatrix, 'constant', ... median(featureMatrix, 1, 'omitnan')); % z-score 标准化,每列减均值除标准差 featureMatrix = zscore(featureMatrix); % 确认最终矩阵尺寸 fprintf('样本数: %d, 特征数: %d\n', size(featureMatrix, 1), size(featureMatrix, 2));

这段代码的逻辑是:先读表,把数值列和类别列分开处理,类别列用独热编码变成 0/1 向量,再拼回一个大矩阵。fillmissing用列中位数填补,比均值更抗异常值。zscore是标准化关键,少了这一步后面聚类结果基本不可信。参数上,onehotencode的第二个参数指定对哪些列编码,fillmissing的'constant'配合中位数是常见组合。跑完看输出尺寸,如果特征数和你预期对不上,多半是独热编码把某一列拆成了多列。

2.3 最小可跑的 kmeans 调用与参数含义

数据准备好之后,核心调用就一行。但这一行里的参数决定了结果好坏,不能随便填:

% 设定聚类数 K,先用肘部法粗定一个范围 K = 4; % 调用 kmeans,关键参数逐个说明 [idx, C, sumd, D] = kmeans(featureMatrix, K, ... 'Distance', 'sqeuclidean', ... % 距离度量,默认平方欧氏 'Replicates', 10, ... % 重复 10 次取最优,降低局部最优风险 'Start', 'plus', ... % kmeans++ 初始化 'MaxIter', 500, ... % 单次迭代上限 'Display', 'final'); % 只输出最终结果,避免刷屏 % idx 是每个样本的簇编号,C 是 K 个簇中心,sumd 是簇内距离和 fprintf('各簇样本数: '); disp(histcounts(idx, 1:K+1));

idx是 N×1 的簇标签,C是 K×D 的中心矩阵,sumd是每个簇内样本到中心的距离平方和,D是每个样本到所有中心的距离。Replicates设 10 是经验值,数据量大或 K 大时可以加到 20,代价是时间线性增长。Start用'plus'就是 kmeans++,比默认的均匀采样稳。MaxIter一般 300 到 500 够用,设太小可能没收敛就停了。跑完用histcounts看各簇样本数,如果某一簇只有个位数样本,要么是 K 设大了,要么是数据里有离群点。

3. 聚类数 K 怎么定:肘部法、轮廓系数与业务约束的三方博弈

3.1 肘部法的计算与读图

K 是 kmeans 唯一需要人为指定的关键参数,也是最容易拍脑袋的地方。肘部法的思路是:随着 K 增大,簇内距离和必然下降,但下降速度会在某个点明显变缓,那个拐点就是候选 K。实现上就是循环跑不同 K,记录sumd总和:

% 测试 K 从 1 到 10 的簇内距离和 K_range = 1:10; wss = zeros(length(K_range), 1); for i = 1:length(K_range) [~, ~, sumd] = kmeans(featureMatrix, K_range(i), ... 'Replicates', 5, 'Start', 'plus', 'Display', 'off'); wss(i) = sum(sumd); end % 画肘部图 figure; plot(K_range, wss, '-o', 'LineWidth', 1.5); xlabel('聚类数 K'); ylabel('簇内距离和'); title('肘部法确定 K'); grid on; % 计算相邻点的下降率,辅助判断拐点 dropRate = -diff(wss) ./ wss(1:end-1); disp(table(K_range(2:end)', dropRate, 'VariableNames', {'K', 'DropRate'}));

wss是 within-cluster sum of squares,随 K 单调下降。看图时找下降率突然变小的位置,比如从 K=3 到 4 降了 30%,从 4 到 5 只降了 8%,那 4 就是候选。代码里额外算了dropRate,比肉眼读图更客观。注意Replicates这里设 5 就够,因为只是比较趋势,不需要每个 K 都跑到最优。

3.2 轮廓系数:比肘部法更硬的指标

肘部法主观性强,轮廓系数(silhouette)能给出每个样本的聚类质量分数,范围 -1 到 1,越接近 1 说明样本离本簇近、离其他簇远。MATLAB 里silhouette函数直接算:

% 对候选 K 计算平均轮廓系数 K_candidates = 2:8; silScores = zeros(length(K_candidates), 1); for i = 1:length(K_candidates) idx = kmeans(featureMatrix, K_candidates(i), ... 'Replicates', 10, 'Start', 'plus', 'Display', 'off'); silScores(i) = mean(silhouette(featureMatrix, idx)); end % 输出对比表 disp(table(K_candidates', silScores, 'VariableNames', {'K', 'Silhouette'})); % 找最高分对应的 K [bestScore, bestIdx] = max(silScores); fprintf('最佳 K = %d, 轮廓系数 = %.4f\n', K_candidates(bestIdx), bestScore);

轮廓系数对距离度量敏感,如果前面没做标准化,这里分数会普遍偏低且不可比。一般平均轮廓系数高于 0.5 算结构清晰,0.3 到 0.5 算可接受,低于 0.25 就要怀疑数据本身没有明显簇结构。注意轮廓系数在 K=2 时往往偏高,这是它的已知偏向,所以不能只看分数,要结合肘部法和业务含义。

3.3 业务约束下的 K 选择

纯数学指标给的是候选,最终定 K 还要看业务能不能用。比如做用户分群,分成 3 群和 5 群对应的运营策略完全不同,5 群可能细到没法针对性投放。我一般会做一张对照表,把不同 K 下的簇大小、中心特征、轮廓系数列出来,和业务方一起过一遍。如果某个 K 下出现一个超大簇加几个极小簇,通常说明 K 偏大或者数据里有离群点没处理。这一步没有代码能替代,但前面算出的C和idx就是讨论的素材。

4. 结果可视化与簇特征解读:让聚类结果能讲出人话

4.1 二维和三维散点图的画法

聚类结果如果只给一堆标签,没人看得懂。可视化是让结果落地的关键一步。高维数据没法直接画,常规做法是先用 PCA 降到 2 维或 3 维,再按簇标签上色:

% PCA 降到二维用于可视化 [coeff, score, ~, ~, explained] = pca(featureMatrix); score2d = score(:, 1:2); % 按簇标签画散点图 figure; gscatter(score2d(:,1), score2d(:,2), idx, lines(K), '.', 12); xlabel(sprintf('PC1 (%.1f%%)', explained(1))); ylabel(sprintf('PC2 (%.1f%%)', explained(2))); title('kmeans 聚类结果(PCA 二维投影)'); grid on; % 叠加簇中心在 PCA 空间的投影 hold on; center2d = (C - mean(featureMatrix)) * coeff(:, 1:2); plot(center2d(:,1), center2d(:,2), 'kx', 'MarkerSize', 14, 'LineWidth', 2); hold off;

gscatter按idx分组上色,比手动循环scatter省事。explained告诉你前两个主成分解释了多少方差,如果加起来不到 50%,说明二维投影丢失信息太多,图只能当参考,不能下结论。中心点投影那一步是把原始空间的C通过同样的均值和coeff变换到 PCA 空间,这样中心点和样本点在同一坐标系里,方便看簇的紧致程度。

4.2 簇中心反标准化与特征画像

PCA 图看的是整体分布,要解释每个簇是什么,得回到原始特征空间看中心。但前面做了 z-score,中心值是标准化的,得反变换回去:

% 保存标准化参数(在 zscore 那一步之后) mu = mean(featureMatrix_raw, 1); sigma = std(featureMatrix_raw, 0, 1); % 反标准化簇中心 C_original = C .* sigma + mu; % 把中心转成表格,方便对照列名 centerTable = array2table(C_original, ... 'VariableNames', featureMatrix_colnames); disp(centerTable); % 对每个簇,找出中心值最高的三个特征 for k = 1:K [~, topIdx] = maxk(C_original(k,:), 3); fprintf('簇 %d 主导特征: %s\n', k, strjoin(featureMatrix_colnames(topIdx), ', ')); end

这里的关键是mu和sigma必须在标准化之前从原始矩阵算出来并保存,否则反变换对不上。C_original的每一行是一个簇在原始量纲下的中心,比如年龄 35、收入 8000 这样,业务方一看就懂。maxk找每个簇最突出的特征,快速生成画像描述。如果某个簇在多个特征上都偏高,说明这个簇的特征不单一,可能需要拆或者合并。

4.3 用轮廓图定位问题样本

整体轮廓系数是平均值,掩盖了个体差异。轮廓图能把每个样本的分数画出来,一眼看出哪些样本分错了:

figure; [silVals, ~] = silhouette(featureMatrix, idx, 'sqeuclidean'); title('各样本轮廓系数'); % 找出轮廓系数为负的样本,这些是可能分错的 negIdx = find(silVals < 0); fprintf('轮廓系数为负的样本数: %d (占比 %.1f%%)\n', ... length(negIdx), 100*length(negIdx)/length(silVals)); % 输出这些样本的原始索引和当前簇标签 if ~isempty(negIdx) disp(table(negIdx, idx(negIdx), silVals(negIdx), ... 'VariableNames', {'SampleIndex', 'Cluster', 'Silhouette'})); end

轮廓系数为负意味着样本到其他簇的平均距离比到本簇还近,基本可以判定分错了。占比低于 5% 可以接受,高于 10% 就要回头检查 K 是否合理、特征是否够区分。这些负分样本往往是边界情况,业务上可能正好是需要单独关注的那批人。

5. 避坑与排查:kmeans 聚类 MATLAB 实现里最容易翻车的五件事

5.1 没标准化导致某列特征独大

现象:聚类结果里某一簇的样本在某个特征上高度一致,其他特征完全随机,看起来像按单一维度分的。原因:不同特征量纲差异大,距离计算被大量纲特征主导。解决:聚类前对所有数值特征做 z-score 或 min-max 标准化,并在反标准化解读中心时用对应的均值和标准差还原。判断方法很简单,看簇中心表里各特征的数值范围,如果某一列数值比其他列大几个数量级,基本就是这个问题。

5.2 K 值拍脑袋定,结果没法解释

现象:跑出来的簇大小严重不均,或者业务方问「为什么是 4 类不是 3 类」时答不上来。原因:只跑了一次 kmeans,没做 K 的扫描和对比。解决:至少用肘部法和轮廓系数各扫一遍 K 的范围,把不同 K 下的簇大小、轮廓系数、中心特征列成表,选数学指标和业务含义都说得通的那个。我一般会把 2 到 8 的结果都留着,业务讨论时随时调出来看。

5.3 忽略 Replicates 导致结果每次不一样

现象:同样的数据和 K,两次运行得到的簇标签和中心不同。原因:kmeans 对初始中心敏感,单次运行容易陷入局部最优。解决:Replicates设 10 以上,Start用'plus'。如果数据量特别大导致重复太慢,可以先用'sample'初始化跑一次看大概,再用'plus'加Replicates精跑。另外注意,即使这样,簇的编号也可能不同,比较两次结果时要看中心而不是看标签数字。

5.4 缺失值没处理直接进 kmeans

现象:代码报错NaN相关,或者结果里某些样本的簇标签异常。原因:kmeans不接受含 NaN 的输入矩阵。解决:进 kmeans 之前必须fillmissing或rmmissing。填补方法上,数值列用中位数比均值稳,类别列用众数。如果某列缺失超过 30%,我倾向于直接删掉这列,因为填补引入的偏差可能比丢掉这列更大。

5.5 把聚类结果当分类标签用

现象:拿 kmeans 的idx去训练一个分类器,然后在新数据上预测,发现效果很差。原因:kmeans 给出的簇编号没有跨数据集的一致性,新数据跑一遍 kmeans 得到的编号和旧数据对不上。解决:如果要做预测,应该用聚类中心训练一个分类器(比如最近邻或 SVM),把中心作为「伪标签」的来源,而不是直接用编号。或者用knnsearch把新样本分配到最近的已有中心。这个坑很隐蔽,因为在自己数据集上验证时看着没问题,一上生产就露馅。

6. 从能跑到好用:kmeans 结果稳定性验证与增量分配的一个实用技巧

代码能跑通只是起点,真正投入使用前,我会做一件事:验证聚类结果的稳定性。方法不复杂,把数据随机分成两半,各自跑 kmeans,然后比较两半得到的簇中心是否接近。如果中心差异很大,说明数据本身没有稳定结构,或者 K 选得不对。MATLAB 里可以用pdist2算两组中心的距离矩阵,看最小距离是否在可接受范围内。

% 随机对半切分 n = size(featureMatrix, 1); halfIdx = randperm(n, floor(n/2)); dataA = featureMatrix(halfIdx, :); dataB = featureMatrix(setdiff(1:n, halfIdx), :); % 各自跑 kmeans K = 4; [~, CA] = kmeans(dataA, K, 'Replicates', 10, 'Start', 'plus', 'Display', 'off'); [~, CB] = kmeans(dataB, K, 'Replicates', 10, 'Start', 'plus', 'Display', 'off'); % 计算两组中心的两两距离 centerDist = pdist2(CA, CB); minDist = min(centerDist, [], 2); fprintf('各中心到另一组最近中心的距离: '); disp(minDist'); % 如果每个中心都能在另一组找到距离小于阈值的对应中心,认为稳定 threshold = 0.5; % 标准化空间下的经验阈值 stable = all(minDist < threshold); fprintf('聚类结果稳定: %s\n', string(stable));

这段代码的核心是pdist2算两组中心的距离矩阵,minDist是每个 A 组中心到 B 组最近中心的距离。阈值 0.5 是在标准化空间下的经验值,因为标准化后特征标准差为 1,中心距离小于半个标准差算接近。如果某个中心的最小距离超过 1,说明这一簇在两半数据里位置差异大,要么是样本太少不稳定,要么是 K 偏大。

另一个实用技巧是增量分配:当有新样本进来时,不需要重新跑整个 kmeans,直接用已有的中心做最近邻分配。knnsearch或者手动算距离都行:

% 新样本标准化(用训练时的 mu 和 sigma) newSample = (newRaw - mu) ./ sigma; % 分配到最近的中心 [d, assignedCluster] = min(pdist2(newSample, C), [], 2); fprintf('新样本分配到簇 %d,距离 %.4f\n', assignedCluster, d);

这样做的前提是新样本的分布和训练数据一致,如果业务发生突变(比如用户行为模式整体偏移),增量分配会失效,这时候需要重新聚类。我一般会监控新样本到最近中心的平均距离,如果持续上升,就是重新训练的触发信号。

这套流程跑下来,从数据准备到结果验证大概两三百行代码,覆盖了 kmeans 聚类分析在 MATLAB 里落地的完整链路。我自己踩过最深的坑是早期不做标准化直接跑,结果对着簇中心表看了半天没看出规律,后来才发现是收入那一列把距离全吃掉了。希望帮到你。

本文还有配套的精品资源,点击获取

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

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

立即咨询