资讯详情

MATLAB手写KMeans聚类:逐轮输出SSE收敛图与工程实现

📅 2026/9/14 13:40:23 | 华诺云谱 👁 阅读
MATLAB手写KMeans聚类:逐轮输出SSE收敛图与工程实现
简介聚类分析是数据挖掘与机器学习中的高频技术这套基于K均值算法的MATLAB 2021a仿真资源面向需要快速上手聚类实验的学生、科研人员与算法初学者覆盖从数据读取、聚类迭代到结果可视化的完整流程。压缩包共4个文件约27KB包含一个M脚本主程序、两个文本数据文件及说明文档分别承担算法实现、实验数据与使用指导的功能结构紧凑、便于对照学习。目前已有755人浏览学习适合作为理解K均值聚类原理并进行动手实践的入门样例。运行代码后可以清晰看到聚类点的划分结果与质心收敛曲线直观理解初始化、分配、更新、迭代这一算法闭环说明文档和数据文件还能帮助用户替换数据、验证不同场景从而从理论走向仿真实践尤其适合机器学习初学者的首个聚类项目。1. 用MATLAB 2021a把KMeans聚类过程“拆开看成图”手头这份「聚类分析基于kmeans聚类分析并输出收敛图」的仿真工程核心东西不多一个Kmeans.m、两个文本数据文件10.txt和fpgamatlab.txt加一份说明.docx但它把KMeans从“调包出结果”变成了“观察算法收敛”。多数人用MATLAB自带kmeans函数输入数据直接拿标签却看不到质心在第几轮停止移动、SSE如何逐轮下降。这份工程强制按初始化、分配、更新、判断收敛的原始流程实现并把聚类点和每一轮误差输出成图正是数据挖掘和机器学习课程设计里老师想看到的东西。适合做聚类课设、复现收敛图或者想稳定输出聚类仿真结果的人。下面所有实现和参数都以MATLAB 2021a为基准能在当前版本直接跑通。2. KMeans目标函数与MATLAB函数骨架的迭代实现2.1 分配与更新KMeans为什么能收敛KMeans把聚类问题写成最小化目标函数给定n个样本和类别数k找到k个质心使所有样本到它所属质心的距离平方和最小。距离通常取欧氏距离公式写作SSE Σ ||x_i - c_label(i)||²这个公式不是摆设。KMeans的收敛性正是基于SSE的单调不增固定质心时把每个样本分给最近的质心只可能减小或保持SSE固定簇划分时把质心更新为簇内样本均值根据均值的最小平方误差性质同样只会让SSE变小。两类操作交替执行SSE被限制在一个递减序列里再加上有限样本的离散性迭代一定会在某个容差内停下来。收敛图的意义就是把这条递减曲线画出来验证过程确实在推进。工程上收敛判断常用两种情况相邻两轮SSE差值小于容差或者质心位移的最大值小于容差。我建议优先看SSE差值因为质心位移小不代表标签稳定尤其在特征尺度差异大的数据上某一维的微小位移可能已经改变了样本归属。项目要求输出收敛图所以代码里必须把每一轮分配步骤算出的最小距离平方和记录下来而不是只留最终质心。2.2 自己写一个可输出迭代轨迹的Kmeans.m核心函数下面这版函数结构与项目场景匹配在MATLAB 2021a下可直接运行也保留了画收敛图所需的中间量。项目里的Kmeans.m大概率就是按这个思路组织循环的。function [idx, centroids, sse] my_kmeans(X, k, max_iter, tol) % X: n×p 矩阵n个样本p个特征 % idx: n×1 聚类标签 % centroids: k×p 最终质心 % sse: 每一轮迭代的误差序列供收敛图使用 [n, p] size(X); rng(42); % 固定随机种子保证可复现 init_pos randperm(n, k); % 无放回抽取k个样本作为初始质心 centroids X(init_pos, :); prev_sse inf; sse zeros(max_iter, 1); for iter 1:max_iter dist_mat zeros(n, k); for j 1:k diff X - centroids(j, :); dist_mat(:, j) sum(diff .^ 2, 2); % 欧氏距离平方 end [min_dist, idx] min(dist_mat, [], 2); % 分配步骤取最近质心 for j 1:k if any(idx j) centroids(j, :) mean(X(idx j, :), 1); % 更新步骤 end end sse(iter) sum(min_dist); if abs(prev_sse - sse(iter)) tol % 收敛判断 sse sse(1:iter); % 只保留实际迭代轮数 break; end prev_sse sse(iter); end end几个关键参数和逻辑说明randperm(n, k)做无放回抽样避免两个初始质心落在同一个样本点上这是自写KMeans最容易忽略的细节。diff X - centroids(j, :)利用MATLAB的广播特性得到n×p差值矩阵。手写平方距离可以避免依赖pdist2减少统计工具箱依赖。min(dist_mat, [], 2)返回每个样本到最近质心的距离平方min_distsum(min_dist)就是本轮SSE必须在这里取值不能放到质心更新之后。空簇保护用any(idx j)判断。某个簇在更新时没有样本就保留上一轮质心位置避免mean([])产生NaN。收敛后sse sse(1:iter)截断数组画图时横轴长度就是实际迭代轮数。数据量较大时max_iter设到200以内足够。2.3 自写循环与MATLAB内置kmeans函数的选型取舍MATLAB自带kmeans并不是不能得到迭代信息通过statset(MaxIter, 100)设置选项配合Display,iter能打印日志但拿不到每一轮的质心坐标和SSE原始序列。这个项目要画收敛图和聚类点分布自写循环是更合理的选择。实现方式可观测性计算速度适用场景内置kmeans日志有限快C内核优化只关心最终标签自写循环实现每轮质心、SSE均可记录中等纯MATLAB循环需要收敛图、教学演示向量化pdist2可记录质心路径较快特征维度低的中等数据我一般这样处理先用自写循环把算法行为确认正确再换内置kmeans做大规模对比验证。直接拿内置函数出图后续想改初始质心策略时没有抓手等于绕过了项目最有价值的教学点。3. 文本数据读取与Kmeans.m的数据预处理流程3.1 拿到10.txt和fpgamatlab.txt先看格式项目压缩包里有两个文本文件初学者总是一上来就load(10.txt)结果报错或者读进一堆NaN。正确做法是先看前几行判断数据是纯数字还是带了表头。fid fopen(fpgamatlab.txt, r); headLines cell(5, 1); for i 1:5 headLines{i} fgetl(fid); end fclose(fid); disp(headLines);如果前五行是空格分隔的数字用readmatrix最省事data readmatrix(10.txt); data data(all(isfinite(data), 2), :); % 过滤NaN和Inf行readmatrix能自动识别空格、逗号和制表符分隔MATLAB 2021a里比textscan更省心。若文件开头有注释行读进来的第一列是NaN过滤逻辑会把它们整体删除。注意过滤顺序先用any(isnan(...), 2)删行再处理缺失值不要用rmmissing一次性删它会同时删行和删列破坏特征维度。fpgamatlab.txt这个文件名暗示了另一种可能内容未必是二维散点也可能是FPGA仿真导出的多维中间结果。对这类未知格式我习惯读取后马上检查矩阵规模列数是2还是3决定后面直接画散点还是先降维。3.2 标准化量纲不一致时距离计算会失真KMeans依赖欧氏距离特征量纲不一致时距离主导权会完全交给数值大的维度。比如一个维度是0到1的归一化值另一个是0到1000的原始读数聚类结果几乎只由后者决定。项目里数据来源不确定建议默认做标准化X zscore(data); % 每列零均值、单位方差zscore按列计算对二维表是标准做法。如果各特征单位一致比如都是电压幅值跳过这步也可以但画图时要注意坐标含义已经变成原始物理量。我实际项目中一般保留原始数据一份、标准化数据一份聚类在标准化数据上做出聚类点时把质心反变换回原始尺度再标到散点图上。3.3 完整调用流程与k、max_iter、tol的设定标准调用流程如下data readmatrix(10.txt); data(all(isfinite(data), 2), :) []; X zscore(data); k 3; % 类别数先用肘部法找初值 max_iter 100; % 迭代上限防止死循环 tol 1e-6; % SSE绝对变化阈值 [idx, C, sse] my_kmeans(X, k, max_iter, tol);参数具体怎么定取决于数据规模样本量几百时max_iter50足够上万时建议100以上tol取1e-6已经比多数课程设计严格再小只会增加无意义迭代。k的选取没有银弹常见做法是跑一个k2:6循环记录每个k的最终SSE画肘部曲线找拐点。注意KMeans对初始质心敏感同一个k重跑几次最终SSE可能不同应选SSE最小的一次作为该k的代表值再做肘部判断。4. 聚类散点图与SSE收敛图的MATLAB可视化4.1 聚类点和质心的分布图聚类结果拿到后第一件事是画点和质心。figure; gscatter(X(:,1), X(:,2), idx); hold on; plot(C(:,1), C(:,2), kx, MarkerSize, 14, LineWidth, 2); legend(unique(idx), Location, best); title(KMeans聚类点分布); xlabel(feature 1); ylabel(feature 2); grid on;gscatter按idx给每个类别分配不同颜色黑色叉标记质心。如果聚类前做了zscore图上坐标是标准化空间这个细节要在说明文档里写清楚否则别人会误以为坐标是原始值。特征维度大于3时没法直接画二维散点一般先对标准化数据做PCA取前两个主成分再把质心投影到同一主成分空间后叠加。legend(unique(idx))的写法能自动适配类别数不需要手工列类别名。4.2 SSE收敛曲线的本质与解读收敛图本身只是把sse序列画出来难点在解读。figure; plot(1:numel(sse), sse, -o, MarkerFaceColor, b); xlabel(迭代次数); ylabel(SSE); title(KMeans收敛曲线); grid on;健康的收敛图应当单调不增第一轮SSE最高第二轮快速下降之后下降幅度急剧收窄最终进入平台。如果画出来的曲线上下波动说明更新步骤和分配步骤的顺序错了最常见的问题是把质心更新前的距离当成当轮SSE或者sse(iter)在更新步骤之后才计算。收敛图横轴越短说明初始质心选得越准横轴长且末端仍有明显下降趋势说明max_iter可能限制了收敛。4.3 异常曲线形态与排查方向实际跑项目时收敛图会暴露比报错更多的问题。曲线形态可能原因检查点SSE在中间某轮回升更新步骤在SSE计算前执行确认min_dist来自本轮分配步骤SSE完全不变初始质心抽样有放回检查randperm是否无放回曲线收敛但SSE偏高陷入局部最优换成kmeans初始化或增加重复次数收敛轮数为0数据中重复样本过多查看X是否有大量完全相同的行还有一个常被忽略的做法把质心轨迹连成线画出来。在my_kmeans里增加一个centroid_path输出每轮把centroids追加到三维数组最后按轮次连线。质心路径交叉不代表算法错说明不同簇的质心在迭代中交换了归属方式这本身就是很好的教学素材。对聚类分析这种偏可视化的仿真项目多加一条质心轨迹线比单纯画标签图更有说服力。5. 初始质心敏感性修复与fpgamatlab文件验证5.1 用kmeans初始化降低收敛波动KMeans项目里最容易被低估的坑是初始质心。随机初始化在不同批次运行下可能得到完全不同的聚类点图初学调试时常常被误导以为是算法写错了。kmeans的思路是第一个质心随机之后每个质心按“离已有质心越远的点被选中概率越大”来抽样。距离平方加权的实现如下centroids zeros(k, p); centroids(1, :) X(randi(n), :); for j 2:k d2 min(pdist2(X, centroids(1:j-1, :)).^2, [], 2); prob d2 / sum(d2); centroids(j, :) X(randsample(n, 1, true, prob), :); endpdist2来自统计工具箱若环境里没有可以复用2.2节的手写距离计算。randsample(n, 1, true, prob)按权重抽样距离越大的点被选中概率越高。把这段替换掉randperm后收敛曲线前几轮的SSE起点通常更低不同批次之间结果更稳定。项目里的Kmeans.m若没有这步我建议加上这是数据挖掘课设里最明显的加分改动。5.2 fpgamatlab.txt作为第二份验证数据fpgamatlab.txt如果与10.txt格式不同不要直接复用同一条读取路径。先判断每列代表的物理量如果是FPGA仿真导出的时间序列可能含重复时间戳或局部缺失这些都要先清洗。验证时应把同一套my_kmeans跑在标准化后的数据上对比两份数据的收敛轮数轮数偏多通常说明簇间重叠严重轮数极少说明簇结构清晰这是判断数据可聚类性的快捷方式。在MATLAB 2021a环境里数据量提升到十万行以上时距离矩阵计算会成为明显瓶颈。FPGA方向通常就是把距离计算做成流水线并行单元但硬件加速前先用这份仿真把收敛图和聚类点图固化成可对比的基准结果后续更换实现才有参照系。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。