简介:这份实验报告面向学习数据挖掘与聚类分析的高校学生及算法入门者,围绕k-means聚类算法的原理、流程与MATLAB实现展开,帮助读者理解无监督划分方法并完成Iris数据集的聚类实践。资源包共1个doc文件,约126KB,内容涵盖实验目的、算法原理与流程图、k=3与k=4两种取值下的聚类效果分析、实验结论与心得体会,并附有完整的MATLAB源代码,便于对照复现与调试。报告重点讨论了初始类中心选择、数据输入顺序及类别数k对迭代次数和聚类结果的影响,也指出算法对离群点敏感、难以处理非凸簇等局限,并给出k值选取与初始中心优化的思路。目前已有2741人学习,适合作为课程实验参考或聚类算法的入门练手材料。
1. 一份能跑通的 k-means 实验报告,到底值不值得下
带过课程设计的人大概都遇到过这种场面:数据集给了、算法原理也背了,真到 MATLAB 里写循环,聚类结果要么全挤成一坨,要么每次跑出来的颜色分布都不一样,报告里那句“聚类效果良好”写得心虚。这份《k-means 数据聚类实验报告(MATLAB 详细代码)》解决的正是这个断层——它不是只给结论的文档,而是把 Iris 数据集、k-means 主程序、手写的 kmeans 子函数、k=3 与 k=4 的对比图、误差平方和准则、初始中心选取的讨论一并打包。适合两类人:一是要交实验报告、需要一份能逐行读懂并改参数的学生;二是想快速回顾划分式聚类实现细节、拿 MATLAB 做原型验证的从业者。下面按“原理怎么立住、代码怎么跑、坑在哪”拆开讲。
2. 先立住原理:k-means 的划分准则与收敛条件
2.1 平方误差准则为什么是这套代码的目标函数
k-means 的本质是找一个划分,让簇内紧凑、簇间分离。这份报告采用的是最经典的平方误差准则,目标函数写成对每个簇内点到簇中心的欧氏距离平方求和。用公式表达就是 E = Σ_{i=1}^{k} Σ_{p∈C_i} ||p − m_i||²,其中 m_i 是第 i 个簇的均值中心。代码里所有“距离”计算最终都落到这个式子上,理解这一点,后面看sum((repmat(x(i,:),k,1)-nc).^2,2)就不会觉得是黑匣子。
为什么选欧氏距离而不是曼哈顿距离?Iris 的四个特征都是连续数值、量纲接近,欧氏距离对球形簇的刻画最自然,而且均值中心在欧氏度量下正好是使平方误差最小的点——这是 k-means 能用“求平均”来更新中心的数学前提。换成其他距离,均值未必是最优中心,更新公式就得改。报告里提到“也可以用其他距离度量”,但代码没有展开,实际动手时如果换距离,mean那一步必须同步替换,否则收敛性没有保证。
收敛判据方面,报告写的是“新的平均值不再改变则结束”。严格说这是中心不再移动的判据,等价于划分不再变化。代码里用move标志位实现:一轮扫描中没有任何点改变归属,move保持 0,循环退出。这个判据比“目标函数变化小于阈值”更硬,好处是不会因为浮点误差提前停,坏处是数据量大时可能多跑几轮。我在自己的原型里一般会加一个最大迭代次数兜底,防止震荡。
2.2 初始中心怎么选:报告的做法与它的边界
报告里初始中心取的是x(bn,:)、x(2*bn,:)、x(3*bn,:),其中bn = round(n/k*rand),也就是在前 1/k 的数据范围内随机取一个起点,再等间隔往后取。这个做法的意图在正文里说得很直白:前 k 个数据差别较大,能提高效率和准确率。它比纯随机抽 k 个点稳一些,因为等间隔采样降低了抽到相邻近点的概率。
但这里有个容易被忽略的边界:bn是随机数,如果bn恰好很小,三个中心可能落在同一片密集区,迭代次数会明显上升,甚至陷入局部最优。报告自己也承认“初始类中心的选择对聚类准确度有较大影响”。所以这份代码适合用来观察“初值影响迭代次数”这个现象,但不适合当作生产级初始化方案。真要稳,常见做法是 k-means++ 或者多次随机重启取平方误差最小的那次,报告没写,但这是读代码时应该自己补上的一课。
2.3 两段式循环:先粗分再微调
代码里的kmeans.m子函数有两段 while。第一段maxgn=100,做的是标准 Lloyd 迭代:算距离、归簇、重算均值。第二段maxiter=2,做的是逐个点的“调整式”再判断,用dadj = nr./(nr+1).*dist'计算调整后距离,只有更优才移动点并即时更新该簇中心。这个第二段其实是增量式更新,能让结果对输入顺序不那么敏感,但也意味着它不再是严格的批量 k-means,收敛性分析要打折扣。
读这段代码时不要被maxiter=2迷惑,它不是“只迭代两次就收敛”,而是“最多做两轮微调”。实际聚类质量主要由第一段决定,第二段是锦上添花。报告正文的算法流程写的是标准六步,和代码的两段式并不完全一一对应,这是文档与实现之间的一个缝隙,后面避坑章节会专门说。
3. 把代码跑起来:数据组织、主程序与可视化
3.1 数据怎么进 MATLAB:Iris 的 150×4 矩阵
报告附录把 Iris 数据直接硬编码成一个 150 行 4 列的矩阵x,每行一个样本,四列对应四个特征。这样做的优点是复制粘贴就能跑,不依赖外部文件;缺点是数据被归一化过(数值大致落在 0 到 1 之间),和原始 Iris 的厘米量纲不同,如果拿这份矩阵去和别的实现对比,数值对不上是正常的。
如果你手上有ch7 iris.txt原文件,更规范的做法是读文件而不是硬编码。常见做法是用load或readmatrix,并确认分隔符和缺失值:
% 读取 Iris 数据集,假设为制表符或空格分隔的纯数值文件 raw = readmatrix('ch7 iris.txt'); % 若含表头,改用 readtable 后取数值列 x = raw(:, 1:4); % 前四列为特征,第五列若为标签则单独存 labels = raw(:, 5); % 标签仅用于事后评估,不参与聚类 [n, d] = size(x);这里readmatrix对纯数值文件最省事,遇到表头会返回 NaN 行,需要检查。labels单独拿出来是为了后面算准确率,k-means 本身是无监督的,训练时绝不能把标签喂进去,否则就是数据泄漏。硬编码矩阵和读文件两条路都行,前者适合快速复现报告图,后者适合换成自己的数据。
3.2 主程序逐段拆:k 值、初始中心、调用与绘图
主程序开头k=3;,然后定义x,接着算bn和nc,再调用[cid,nr,centers] = kmeans(x,k,nc)。cid是 1×n 的归属向量,nr是每簇样本数,centers是最终的 k×d 中心矩阵。这三个输出就是后续分析和绘图全部依赖的东西。
绘图部分用了一个 150 次的 for 循环,按cid(i)的值给每个点打红、蓝、绿星号。这段代码能跑,但写法很啰嗦,而且text(-4,-3.6,...)的坐标是写死的,换数据集就会跑到图外面。更清爽的写法是按簇索引一次性画:
% 按簇批量绘图,避免逐点 if-else colors = {'r*','b*','g*','k*'}; figure; hold on; for c = 1:k idx = (cid == c); plot(x(idx,1), x(idx,2), colors{c}, 'MarkerSize', 6); end plot(centers(:,1), centers(:,2), 'kx', 'MarkerSize', 12, 'LineWidth', 2); legend('簇1','簇2','簇3','中心'); hold off;注意这里只画了前两个特征,因为二维图能看。Iris 有四维,前两维的可分性其实一般,k=3 时图上会有交叠,这不是算法失败,而是降维可视化的信息损失。报告里的效果图也是二维的,看的时候要有这个预期。想更客观,就补一个混淆矩阵或者轮廓系数,别只凭图下结论。
3.3 换 k 值和换初值:两个必须亲手做的对照实验
报告给了 k=3 和 k=4 两组结果,结论是 k=4 出现过度分割。这个对照很有价值,但只跑一次说明不了初值敏感性。建议至少做两组实验:固定 k=3,改rand的种子跑五次,记录每次的迭代次数和最终平方误差;再固定种子,把 k 从 2 跑到 5,看平方误差随 k 单调下降但边际收益递减的现象。
% 初值敏感性对照:固定 k,换随机种子重复 k = 3; for s = 1:5 rng(s); % 固定种子,保证可复现 bn = round(n/k*rand); nc = [x(bn,:); x(2*bn,:); x(3*bn,:)]; [cid, nr, centers] = kmeans(x, k, nc); % 计算平方误差和 E = 0; for c = 1:k idx = (cid == c); E = E + sum(sum((x(idx,:) - centers(c,:)).^2)); end fprintf('seed=%d, E=%.4f, 各簇样本数=%s\n', s, E, mat2str(nr)); end这段代码的关键是rng(s),没有它每次结果都不同,对照就失去意义。E的计算直接对应第 2 章的目标函数,可以拿来验证“初值不同、E 略有差异”这个结论。如果某次 E 明显偏高,多半是初值撞到了坏点,这正是报告里“初始中心特别重要”的实证。
4. 避坑与排查:这份代码最容易翻车的五个地方
4.1 现象:每次运行颜色分布都不一样,报告没法复现
原因:bn = round(n/k*rand)里的rand没有固定种子,初始中心每次都在变。解决:在调用前加rng(42)之类的固定种子,并在报告里写明种子值。如果要求更稳,把初始化换成 k-means++ 或多次重启取最优,别依赖单次随机。
4.2 现象:k=4 时某个簇只有一两个点,图上一片稀疏
原因:Iris 本身只有三个真实类别,强行 k=4 必然把一个自然簇劈开,或者产生离群小簇。这不是代码 bug,是 k 值与数据结构不匹配。解决:用肘部法或轮廓系数辅助选 k,报告里把 k=4 作为“过度分割”的反例讲清楚,而不是当成失败案例藏起来。
4.3 现象:第二段微调循环几乎不生效,move一直是 0
原因:第一段 100 轮 Lloyd 迭代后划分已经稳定,第二段maxiter=2的增量调整没有空间。这在干净数据上是正常的。解决:不要为了看到“Some points were moved”而调大maxiter,那只会增加运行时间;如果确实想观察增量更新的作用,把第一段maxgn调小到 5 左右再对比。
4.4 现象:换自己的数据后,绘图点全挤在角落或跑出坐标轴
原因:绘图用了text(-4,-3.6,...)和固定的星号坐标逻辑,坐标范围是照着归一化后的 Iris 写死的。解决:去掉硬编码 text,改用axis auto或根据min/max动态设范围;绘图前先zscore标准化特征,避免量纲大的列主导距离。
4.5 现象:距离计算报维度不匹配或结果全为 NaN
原因:repmat(x(i,:),k,1)-nc要求nc是 k×d,如果手工改nc时少了一列,或者读文件时把标签列混进特征,维度就对不上。解决:在kmeans函数入口加一句assert(size(nc,2)==d),读数据后打印size(x)确认列数,标签列务必剔除。
5. 进阶用法:把实验代码改成可评估的小工具
报告给的是“能出图”的脚本,离“能判断聚类好不好”还差一步。我一般会在跑完之后补三样东西:平方误差随迭代次数的下降曲线、用真实标签算的聚类纯度、以及不同 k 下的肘部图。这三样不需要额外工具箱,几十行就能写完,却能把一份实验报告从“看起来对”变成“有据可查”。
先看迭代曲线。把kmeans.m里每轮结束时的 E 存进一个数组,主程序画出来,能直观看到前几轮陡降、后面趋平,收敛慢的那次往往就是初值不好。再看纯度:对每个簇,取该簇内出现最多的真实标签作为簇标签,正确数除以总数就是纯度。Iris 三类的纯度在 k=3 时通常能到 0.9 上下,低于这个值就该怀疑初始化或标准化。最后是肘部图,k 从 1 跑到 6,画 E 对 k 的曲线,拐点就是相对合理的 k。
| 评估项 | 计算方式 | 看什么 |
|---|---|---|
| 平方误差 E | 簇内点到中心距离平方和 | 随迭代下降、随 k 下降 |
| 聚类纯度 | 各簇多数标签正确数 / n | k=3 时应接近 0.9 |
| 肘部拐点 | E-k 曲线斜率突变处 | 辅助确定 k |
| 各簇样本数 | nr输出 | 是否出现极小簇 |
有一个细节值得单独说:报告里的数据是归一化过的,如果你换成原始量纲的 Iris,萼片长度约 4 到 8、花瓣宽度约 0.1 到 2.5,欧氏距离会被大数值特征主导,聚类结果会明显偏向萼片。所以无论用哪份数据,进kmeans之前先做zscore标准化,这一步在报告里没强调,但它是复现别人结果时最容易翻车的地方。从那以后我每次拿聚类代码改数据,都强制先打印各列均值和方差、再跑一次标准化前后的对照,确认结果不是被量纲带偏的。希望帮到你。
本文还有配套的精品资源,点击获取