资讯详情 Matlab实现KNN与K-means:从手写代码到参数调优避坑指南
📅 2026/10/12 13:15:07
简介一份基于MATLAB实现的KNN与K-means结合学习资源面向机器学习入门者及需理解监督与无监督算法配合使用的开发者。KNN依据“近朱者赤”原则进行样本分类K-means则先以无监督聚类方式确定簇中心作为距离度量参考缓解高维空间“维度灾难”对分类精度的影响。代码按数据预处理、K值选择、欧氏距离计算、最近邻筛选、多数表决的经典流程组织并示范利用MATLAB内置函数完成矩阵运算与距离计算便于将理论公式转化为可运行程序。压缩包仅1个m文件大小621B小巧精炼适合逐步调试理解算法细节。已有221人学习查看。读者可从中获得可直接运行的KNN.m示例厘清K-means与KNN的协同关系同时积累数据标准化与参数调优的实践技巧。通过断点调试观察样本距离矩阵的生成过程能直观理解最近邻选择逻辑并可迁移至其他分类任务适合课程设计或算法对比参考。1. 从 KNN.rar 说起一个 Matlab 包里两条算法线这个以 KNN.rar 命名的 Matlab 资源网盘里经常跟着 K-means、KNN、knn matlab、knn算法 一组关键词一起出现。不管它最初是课程作业目录打包还是实验报告的附件命名里已经藏了两条路线K近邻分类KNN是监督学习K均值聚类K-means是无监督学习。对正在赶 Matlab 课设、做图像处理大作业或者准备复现对比实验的人来说这种包的价值不在于“能跑出图”而是同一份数据上既能用 KNN 判分类准确率又能用 K-means 看聚类结构。接下来的内容全部围绕这个标题展开手写两种算法、换内置函数、参数怎么定、哪里最容易翻车。2. 在 Matlab 里跑通 KNN 分类手写距离计算与 fitcknn 两条路2.1 造一份带标签的二维样本固定随机种子保证每次结果一样很多人第一次上手 KNN 算法是在实训平台的“算法应用案例”关卡给的往往是一张几百行的成绩表或鸢尾花数据集。在 Matlab 里复现时我不建议直接读那个表格开跑而是先用几行代码造一份二维数据把决策边界画出来确认逻辑没写错再换真实数据。原因很简单二维数据错了你能一眼看出来高维数据错了你根本不知道是特征问题还是代码问题。rng(42); % 固定随机种子保证每次运行生成同样的样本 X1 mvnrnd([0 0], [0.4 0; 0 0.4], 50); % 第一类以(0,0)为中心的50个点 X2 mvnrnd([2 2], [0.4 0; 0 0.4], 50); % 第二类以(2,2)为中心的50个点 X [X1; X2]; % 100x2 特征矩阵 Y [ones(50, 1); ones(50, 1) * 2]; % 标签1类和2类mvnrnd 的第一个参数是均值向量第二个是协方差矩阵第三个是生成点个数。这里两类中心相距 2标准差 0.4类间有重叠但不多适合演示 KNN。Y 用 1 和 2 两个整数做标签方便后面的 mode 投票。rng(42) 是血泪经验换来的习惯随机过程不锁种子跑十次九个结果论文表格里没法交代。接下来把数据按行随机拆成训练集和测试集常见做法是取 70% 训练、30% 测试n size(X, 1); idx randperm(n); train_idx idx(1:round(n * 0.7)); test_idx idx(round(n * 0.7) 1:end); X_train X(train_idx, :); Y_train Y(train_idx); X_test X(test_idx, :); Y_test Y(test_idx);randperm 生成 1 到 100 的随机排列取出前 70 个行号作为训练集避免手工挑样时把两类分得不均匀。训练测试比例不是死的数据量大以后可以放到 8:2 或 9:1。2.2 手写 KNN 分类器距离排序、邻居投票、准确率KNN 的核心计算就三句话算距离、取邻居、投票。把这三句话封装成一个独立函数是课设里最清晰的写法function pred knnClassify(X_train, Y_train, X_test, K) nTest size(X_test, 1); pred zeros(nTest, 1); for i 1:nTest % 计算当前测试点到所有训练点的欧氏距离 dist sqrt(sum((X_train - X_test(i, :)).^2, 2)); % 距离升序排序取前K个邻居的下标 [~, idx] sort(dist); neighborLabels Y_train(idx(1:K)); % 这K个邻居里出现次数最多的标签作为预测结果 pred(i) mode(neighborLabels); end end逻辑就四行X_train - X_test(i, :) 是矩阵每一行减去当前测试点平方后按行求和再开方得到列向量 dist。sort 默认升序idx(1:K) 是最近的 K 个训练样本行号。mode 取众数就是投票结果。这个循环写法慢但对理解 KNN 最直观100 个点毫无压力。参数说明K 取奇数在两类问题上更稳比如 3、5、7。K1 是最近邻过拟合严重决策边界会把单个噪声点都圈进来K 太大又把远处的样本拉进投票边界过于平滑。真实项目里距离计算可以直接用 pdist2 替代手写循环语义不变。主脚本里这样调用K 5; pred knnClassify(X_train, Y_train, X_test, K); acc mean(pred Y_test); fprintf(准确率%.2f%%\n, acc * 100);mean(pred Y_test) 先做逐元素比较得到 0/1 向量再求平均就是正确率。第一次跑通时建议把预测结果和真实标签挨个打印出来对着看有时候准确率看着高仔细一看全是某一类那是训练集划分出了问题不是算法问题。2.3 内置 fitcknn 的三个必调参数K、距离、标准化Matlab 自带 fitcknn生产环境不需要手写循环。新版 Matlab 上用法完全一致只要 Statistics and Machine Learning Toolbox 装了就支持mdl fitcknn(X_train, Y_train, ... NumNeighbors, 5, ... % 邻居数 K Distance, euclidean, ...% 距离度量 Standardize, true); % 是否标准化特征 pred predict(mdl, X_test); acc mean(pred Y_test);参数作用设置经验NumNeighbors邻居数 K先用 5 试跑再用交叉验证找一个 3~15 之间的值Distance距离度量euclidean 最常用文本或高维稀疏特征用 cityblock 或 cosineStandardize特征标准化特征量纲差异大时必须开 true否则距离被大数值维主导fitcknn 返回一个 ClassificationKNN 对象predict 对 X_test 逐行预测。Standardize 是这里最容易忽略的参数它只计算训练集的均值和标准差预测时自动用同一组统计量处理测试集。这一点内置函数比手写版干净不会造成数据泄露。选 K 不要靠肉眼猜用交叉验证Ks 1:2:15; err zeros(size(Ks)); for i 1:numel(Ks) mdl fitcknn(X_train, Y_train, ... NumNeighbors, Ks(i), Standardize, true); cvmdl crossval(mdl, KFold, 10); err(i) kfoldLoss(cvmdl); end [~, bestIdx] min(err); fprintf(最优K%d\n, Ks(bestIdx));crossval 包一层 10 折交叉验证kfoldLoss 返回预测错误的平均占比所以要选最小 loss 对应的 K。我一般会把 err 画出来看曲线是不是先下降再回升只挑单个点容易选到过拟合位置。两个细节提醒如果提示 fitcknn 未定义多半是工具箱没装kmeans、crossval、kfoldLoss 都在同一个工具箱里。如果你用手写版和 fitcknn 在完全相同的训练测试划分下准确率对不上先检查是不是 fitcknn 开了 Standardize 而手写版没开这个原因占九成。3. K-means 在 Matlab 里的落地手写迭代、内置 kmeans 与肘部选 K3.1 手写 K-means 迭代分配、更新、收敛三步走K-means 是原理简单、实现却容易在细节上翻车的算法。目标是把 n 个样本分到 K 个簇让簇内平方和 SSE 最小。迭代只有两步把每个点分给最近的中心再让中心移到簇内点的均值上。function [labels, centroids, sse] myKmeans(X, K, maxIter) [n, ~] size(X); % 初始化从样本里随机挑 K 个点当初始中心 initIdx randperm(n, K); centroids X(initIdx, :); for iter 1:maxIter % 分配计算每个样本到 K 个中心的距离取最近的中心 D pdist2(X, centroids); [~, labels] min(D, [], 2); % 更新每个簇的样本均值作为新中心 newCentroids zeros(K, size(X, 2)); for k 1:K clusterPts X(labels k, :); newCentroids(k, :) mean(clusterPts, 1); end % 收敛判断中心变化小于阈值就停 if norm(newCentroids - centroids, fro) 1e-6 centroids newCentroids; break; end centroids newCentroids; end % 计算最终 SSE sse 0; for k 1:K clusterPts X(labels k, :); sse sse sum(sum((clusterPts - centroids(k, :)).^2)); end endpdist2(X, centroids) 返回 n 行 K 列矩阵每一行是样本到 K 个中心各自的欧氏距离min(D, [], 2) 沿列方向取最小值得到最近中心的序号。norm(..., fro) 是 Frobenius 范数对矩阵求整体距离中心变化小于 1e-6 就认为收敛。maxIter 我一般给 50~200。这里有一个必须处理的情况某个簇样本数为 0 时mean 返回 NaN中心变成 NaN后面整个矩阵就崩了。课设里遇到这个问题最简单的做法是重新随机挑一个样本当作该簇中心if isempty(clusterPts) randPick randi(n); newCentroids(k, :) X(randPick, :); end空簇一般出现在初始中心落在离大部队很远的地方。与其在 for 循环里打补丁正规做法是直接用内置 kmeans它的初始化策略对空簇做了更完整的处理。3.2 内置 kmeans 参数Replicates、Start 与固定复现内置函数把随机初始化和空簇处理都封装好了常见做法是rng(42); [labels, centroids, sumd] kmeans(X, 2, ... Replicates, 10, ... % 用10个不同初始中心跑取SSE最小的结果 MaxIter, 300, ... Start, plus); % 用 k-means 初始化减少坏初始点参数作用设置经验Replicates重复次数数据量不大时给 5~10越大越稳但越慢Start初始中心选择plus 即 k-means默认会用纯随机效果差时开它MaxIter最大迭代次数默认 100样本多给 300注意看是否触发迭代上限没收敛sumd 返回每个簇内点距离簇心的平方和把 sumd 加起来就是总 SSE。kmeans 输出的标签受初始中心影响固定 rng 后才能复现。我在做对比实验时习惯先把 rng 设死再跑 kmeans不然论文表格里的数字每次运行都不一样答辩时没法解释。这里有个容易踩的隐性坑内置 kmeans 对簇标签的编号顺序不固定。同一份数据跑两次原本叫 1 的簇可能变成 2。做结果对比时要先把每簇中心排序再按中心大小重新映射标签否则两张图看起来像完全不同。3.3 肘部法则把 K-means 的 K 画出来再定K-means 的 K 不像 KNN 的 K 可以由交叉验证损失来选因为无监督没有标签算准确率。最常见的做法是肘部法则Kmax 8; sseArr zeros(Kmax, 1); for K 1:Kmax [~, ~, sumd] kmeans(X, K, Replicates, 5, MaxIter, 300); sseArr(K) sum(sumd); end plot(1:Kmax, sseArr, -o); xlabel(K); ylabel(SSE);K 越小 SSE 越大随着 K 增大 SSE 一定下降。如果数据存在自然分组曲线在某个 K 之后下降明显变平形成“肘部”这个 K 就是合适的聚类数。我一般会连续跑两遍肘部曲线如果两次肘部位移很大说明数据结构模糊聚类数本身没有稳定答案。注意SSE 曲线没有明显肘部不代表代码错了更常见的是数据接近连续分布。这种时候强行用 K-means 选 K 就像用尺子量重量方向就错了。可以考虑换高斯混合模型或者回到特征设计上重新提取更分离的特征。4. KNN 与 K-means 的常见问题现象、原因、解决这里面的坑我基本都在课设和项目里踩过挑影响最大、复现率最高的五条写出来按“现象→原因→解决”讲。4.1 量纲差异让 KNN 的距离计算全线崩溃现象二维演示数据上准确率 90% 以上换成真实数据比如身高 168cm、体重 65kg、年龄 24 岁这种三列数据准确率暴跌。分类结果基本等于只看身高体重和年龄没起什么作用。原因欧氏距离把每一维平方加和数值大的特征在总距离里占的权重天然更大。KNN 本身不做特征重要性建模默认所有维度等权重量纲差异等于手动给身高加了权重其他特征全被淹没。解决用 fitcknn 时开 Standardize, true或手写标准化mu mean(X_train, 1); % 1 表示按列计算均值 sigma std(X_train, 0, 1); % 0 表示除以 n-1 的无偏标准差 X_train_std (X_train - mu) ./ sigma; X_test_std (X_test - mu) ./ sigma;注意必须用训练集的 mu 和 sigma 去标准化测试集。把测试数据混进来统一标准化就是下一节要说的数据泄露。K-means 也有同样问题聚类前不标准化距离也被某一大数值列主导画出来的簇其实是按那一列切出来的。4.2 偶数 K 的投票打平与 K-means 空簇两个随机性翻车现场现象手写 KNN 时取 K4最近 4 个邻居标签是 [1 1 2 2]mode 在 Matlab 里返回 1表面出了结果实际投票打平。另一边 K-means 跑着跑着输出 NaNplot 曲线直接断裂。原因偶数 K 在两类问题上天然可能平分Matlab 的 mode 对平分返回数值较小的标签不是随机所以你会得到一个“看似稳定但实际没道理”的结果。K-means 空簇是初始中心落在稀疏区域导致的RNG 初始化时刚好抽到离群点这个簇从头到尾分不到样本。解决KNN 在二分类上直接用奇数 K3、5、7 都行多分类同样选奇数更稳。K-means 空簇用内置 kmeans 开 Replicates, 10 和 Start, plus距离加权初始化会大幅降低空簇概率。特征再标准化一下稀疏区域的情况还会进一步减少。4.3 标准化放进了全量数据隐蔽的数据泄露现象用 fitcknn 开 Standardize 没问题但自己先手写 zscore 对 X 全量标准化再划分训练测试交叉验证准确率 96%改成先划分、再只用训练集统计量标准化准确率降到 84%。人一下子慌了。原因zscore 在全量数据上算的均值和方差等于让测试集的信息混进了训练数据的坐标系。KNN 不做统计建模这种信息泄漏不像回归那么致命但在数据量小、测试分布偏的时候它会让距离计算“提前见过”测试集所在的坐标系准确率虚高。解决先 split 后标准化统计量只从训练集算上一小节的代码就是标准写法。fitcknn 的 Standardize 严格按照训练集计算并记住统计量所以不会有这个数据泄露问题。手写版把 mu、sigma 作为参数传进预测流程不要在预测时重新算。4.4 高维图像特征让 KNN 和 K-means 同时失效现象做 Matlab 图像处理大作业把每张图片的像素拉成 10000 维向量直接用 KNN 预测准确率不到 50%K-means 聚类也把颜色相近的图分到一起而不是内容相近的图。原因维度升高后欧氏距离趋近于所有样本距离都差不多最近邻居不再代表语义相似。像素级特征受背景、光照直接干扰这不是算法错了是特征表达错了。所谓维度灾难在这里有最直观的体现10000 维空间里最近邻居和随机挑选几乎没有区别。解决先从图像里提取高层特征颜色直方图、HOG、灰度共生矩阵都可以或者简单用 PCA 把 10000 维像素降到 50 维左右再跑 KNN。在做这类高维数据时降维后准确率往往比原特征高出一截因为保留了主要差异、去掉了冗余噪声。K-means 也一样先降维再聚类簇的语义才有意义。5. 用 silhouette 给聚类结果打分K-means 选 K 的第二个判据肘部法则在数据没有明显分组时很难判断K 选 2 还是选 3 全凭感觉。这时我会多走一步算轮廓系数 silhouette。它取值范围是 [-1, 1]接近 1 表示样本和所属簇很紧、和其他簇距离很远接近 0 表示样本正好在两个簇边界负值则说明这个样本更该分到别的簇。rng(42); [labels, centroids] kmeans(X, 2, Replicates, 10, Start, plus); [s, h] silhouette(X, labels); mean_s mean(s); fprintf(平均轮廓系数%.3f\n, mean_s);s 是每个样本的轮廓值h 是轮廓图句柄。平均轮廓系数大于 0.5 说明聚类结构明显0.3~0.5 能接受低于 0.3 就建议换 K 或换特征。实际操作是把 K 从 2 跑到 6画出 K 与平均轮廓系数的折线峰值对应的 K 更值得信。如果同时在做 KNN 和 K-means把两个结果放一起判断KNN 交叉验证准确率用来衡量数据可分性K-means 轮廓系数用来衡量可分组性。两个都好说明数据的类别和簇结构都真实存在KNN 好但轮廓系数低说明存在可分边界但聚类中心假设不成立反之说明数据有明显簇结构却没有稳定可用的标签规则。这套判断逻辑在项目里决定该上监督分类还是无监督聚类比单看准确率靠谱得多。我在做图像处理对比实验时习惯先跑 KNN 看一眼交叉验证准确率再跑 K-means 看轮廓系数两个都通过后才把结论写进报告。这个习惯帮我免掉不少答辩时被追问的尴尬也避开了很多“看起来能跑、实际不可靠”的结果。希望帮到你。本文还有配套的精品资源点击获取