资讯动态

KNN算法Matlab实现:模式识别分类基线、距离度量与交叉验证实践

发布时间:2026/10/24 10:09:18 来源:尧图企业网站定制
简介这是一份面向模式识别与机器学习初学者的KNN算法Matlab实现学习资料聚焦于用Matlab完成最近邻分类的完整流程。内容从距离度量与K值选取讲起涉及欧氏距离计算、交叉验证选参、类别不平衡处理等要点并延伸到剪辑近邻法的阈值筛选思路帮助读者在分类任务中减少远距离邻居带来的干扰。包内共3个文件均为m脚本压缩包约3KB体量轻便分别对应分类主流程、距离计算与最近邻筛选、以及参数调试运行的环节便于直接打开阅读与改造。目前已有4444人学习下载说明该实现思路在课程作业与小规模实验中具有参考价值。读者可据此理解KNN从数据归一化、近邻搜索到投票预测的完整链路并借助kd树等加速手段的思路将其迁移到自己的数据集与作业任务中。1. 上手 KNN 之前先想清楚它到底在解决什么问题KNN 算法的 Matlab 实现这类资源在模式识别课程作业里出现的频率很高但真正能让读者拿来就改的并不多。前阵子帮某个实验室的朋友看一份分类任务特征矩阵才 12 维、样本不到 800 条对方先用了一套需要调一堆超参的模型训练慢、解释性差换成 KNN 之后精度几乎没掉单次预测从几百毫秒降到十几毫秒。这事说明一个道理KNN 的价值不在“先进”而在于它对数据分布几乎没有假设非线性边界也能直接吃下来。这份资源适合三类人正在做模式识别课程的初学者、想拿一个完整多分类基线做对照的实验者、以及要给自己的特征工程找一把“尺子”的开发者。2. 项目骨架与数据流从一份特征矩阵到一张判决表先把整份代码的骨架摸清楚比一上来就抠公式有用得多。KNN 的实现看着简单但真正决定它好不好用的是数据怎么摆、距离怎么算、K 怎么选这三件事而这三件事分别落在哪些文件里必须先心里有数。2.1 目录结构与文件职责这个项目的组织方式属于典型的“主脚本 函数库”结构入口只有一个其余全是可单独调用的函数。常见做法是把算法核心和实验脚本分开这样你换数据集时只需要动入口不用碰算法本体。下面是整理出来的文件清单文件 / 目录职责是否常改main.m入口脚本串起加载、切分、训练、评估、可视化是config/params.m集中管理 K 值、距离度量、权重模式等参数是data/存放特征与标签文件.mat或.csv是load_data.m读取原始数据统一成“样本 × 特征”矩阵偶尔normalize_data.m归一化 / 标准化防止量纲主导距离偶尔split_data.m训练测试切分支持分层抽样偶尔knn_classify.m预测主函数输入距离矩阵和训练标签否utils/pairwise_dist.m向量化距离矩阵计算否utils/knn_predict.m单次批量预测 加权投票否utils/scan_k.m交叉验证扫描最优 K否utils/plot_result.m混淆矩阵、决策边界可视化偶尔这张表最值得记住的一点是knn_classify.m和utils/下的三个函数属于“算法本体”除非你要改算法行为否则不该动它们。绝大多数二次开发需求——换数据集、换距离、加类别——都发生在main.m和params.m这两个文件里。这种划分的好处是调试时你能快速定位问题是在数据侧还是算法侧。2.2 数据组织约定一行一个样本一列一个特征KNN 对数据格式的容忍度比神经网络低得多因为它直接拿特征向量做减法。如果矩阵方向搞反了代码不一定报错但距离全错、精度掉到随机水平这是新手最容易翻车的地方之一。function [X, y] load_data(filePath) % 读取数据并统一成 n x d 的特征矩阵与 n x 1 的标签向量 % filePath: 数据文件路径支持 .csv 与 .mat % X: n 行样本、d 列特征的 double 矩阵 % y: n 行 1 列的类别标签整数或字符串 [~, ~, ext] fileparts(filePath); switch lower(ext) case .mat S load(filePath); X double(S.X); y S.y; case .csv raw readmatrix(filePath); % 约定最后一列是标签前面所有列是特征 X raw(:, 1:end-1); y raw(:, end); otherwise error(load_data:badFormat, 不支持的文件类型: %s, ext); end % 方向校验样本数必须远大于特征数否则大概率是矩阵转置了 if size(X, 2) size(X, 1) warning(load_data:maybeTransposed, ... 特征维度 %d 大于样本数 %d请确认矩阵方向, size(X, 2), size(X, 1)); end % 标签统一成列向量避免后续索引维度不匹配 y y(:); end这段代码有三个地方值得看。第一switch lower(ext)把格式分支显式写出来比用if exist(...)试探要干净出错时报的错也明确。第二size(X,2) size(X,1)那个警告是血泪经验——真实数据集里样本数几乎总是远大于特征数一旦反了基本就是转置问题让代码主动喊一声能省半小时排查。第三y y(:)看似多余但 Matlab 里行向量和列向量参与accumarray、逻辑索引时行为不一致养成统一成列向量的习惯能躲掉一堆维度报错。参数上filePath是唯一入参函数不负责路径拼接路径由main.m用fullfile拼好传进来这样换系统Windows 反斜杠 / Linux 斜杠不会出问题。2.3 归一化距离度量的前置动作KNN 里所有样本平等地参与距离计算所以量纲差异会被直接放大。一个以“米”为单位的特征取值范围 02另一个以“毫米”为单位的取值范围 02000后者会完全主导欧氏距离前面那个特征相当于白给。这不是玄学是每次用真实数据都会撞上的问题。function [Xnorm, mu, sigma] normalize_data(X, mode) % 特征归一化 % mode zscore : (x - mu) / sigma适合特征近似高斯分布 % mode minmax : (x - min) / (max - min)适合有明确上下界的特征 if nargin 2 mode zscore; end switch mode case zscore mu mean(X, 1); sigma std(X, 0, 1); sigma(sigma 0) 1; % 常数列保护避免除零产生 NaN Xnorm (X - mu) ./ sigma; case minmax mu min(X, [], 1); sigma max(X, [], 1) - mu; sigma(sigma 0) 1; Xnorm (X - mu) ./ sigma; otherwise error(normalize_data:badMode, 未知归一化模式: %s, mode); end end关键在sigma(sigma 0) 1这一行。真实数据里常出现某个特征在所有样本上取值一样比如采集设备故障导致的常量列标准差为 0不做保护就会整列变NaN而NaN参与sort时的排序行为非常反直觉最终表现是预测结果莫名其妙全部偏向某一类。这个坑不写出来很多人会卡很久。还有一条容易忽略的mu和sigma必须由训练集计算然后原样应用到测试集上。如果用全量数据算统计量再切分测试集信息就泄漏进训练过程了交叉验证的精度会虚高几个百分点。常见做法是在main.m里先切分再对Xtrain调normalize_data拿到mu/sigma然后用同一个mu/sigma手工标准化Xtest。2.4 主流程走读串起来看一遍main.m是整个项目的阅读入口把它读通了剩下的函数都是按需下钻。它的结构应该清晰到一眼能看出实验的五个阶段。% main.m —— KNN 分类实验入口 clear; clc; close all; params config_params(); % 1. 载入参数 [X, y] load_data(fullfile(data, params.dataFile)); idx split_data(y, params.testRatio, params.seed); % 2. 分层切分 Xtrain X(idx.train, :); ytrain y(idx.train); Xtest X(idx.test, :); ytest y(idx.test); [Xtrain, mu, sigma] normalize_data(Xtrain, params.normMode); % 3. 标准化 Xtest (Xtest - mu) ./ sigma; kBest scan_k(Xtrain, ytrain, params.kList, params.nFold); % 4. 交叉验证选 K fprintf(交叉验证选出的 K %d\n, kBest); D pairwise_dist(Xtest, Xtrain); % 5. 预测与评估 ypred knn_predict(D, ytrain, kBest, params.weightMode); acc mean(ypred ytest); fprintf(测试集精度: %.4f\n, acc); plot_result(ytest, ypred, params.classNames); % 6. 可视化顺序上有个细节scan_k在训练集内部做交叉验证测试集从头到尾只被用一次就是最后算精度那一下。很多人图省事直接在测试集上扫 K选出来的 K 一定是乐观偏置的写论文或者做对比实验时这个数字站不住脚。pairwise_dist(Xtest, Xtrain)返回的是nTest × nTrain的矩阵第i行第j列表示第i个测试样本到第j个训练样本的距离。这个矩阵的形状决定了后面knn_predict的索引方式是整条链路里最需要记牢的一个约定。3. 核心模块拆解距离、近邻与投票的三段式KNN 的算法本体可以切成三段算距离、挑近邻、做决策。三段之间靠一个距离矩阵衔接。这一段把每段的实现细节和常见变体都摊开讲因为二次开发时你八成只会改这三段里的某一段。3.1 距离度量默认欧氏但不是唯一选择欧氏距离是最常见的默认值适合特征已经标准化、各维重要性接近的场景。但如果你的特征是文本 TF-IDF、直方图或者 embedding 向量余弦距离往往更合适因为它只关心方向不关心模长。function D pairwise_dist(X, Y, metric) % 批量计算 X 中每个样本到 Y 中每个样本的距离 % X: n x dY: m x d % metric: euclidean | cityblock | cosine % D: n x m 距离矩阵 if nargin 3 metric euclidean; end switch metric case euclidean xn sum(X.^2, 2); % n x 1 ym sum(Y.^2, 2); % 1 x m D2 xn ym - 2 * (X * Y);% 利用 ||a-b||^2 ||a||^2||b||^2-2a·b D2(D2 0) 0; % 浮点误差可能产生极小负数 D sqrt(D2); case cityblock D zeros(size(X,1), size(Y,1)); for j 1:size(Y,1) D(:, j) sum(abs(X - Y(j, :)), 2); end case cosine Xn X ./ max(vecnorm(X, 2, 2), 1e-12); Yn Y ./ max(vecnorm(Y, 2, 2), 1e-12); D 1 - Xn * Yn; % 余弦相似度转距离 end end欧氏那一支是这段代码的核心。如果老老实实写双重循环n×m次sqrt(sum((x-y).^2))在 5000×5000 的规模下 Matlab 要跑好几十秒用矩阵乘法展开成||a||² ||b||² - 2a·b一次X*Y就能出结果耗时通常能压到一秒以内。D2(D2 0) 0是必须的因为两个几乎相同的向量做减法后理论值为 0浮点运算可能给出-1e-16sqrt一下就成复数了后面sort直接崩。cityblock那一支保留循环是因为曼哈顿距离没法用同样的矩阵乘法展开强行向量化要占用n×m×d的三维数组内存爆炸不如老老实实循环。参数metric建议放在params.m里不要硬编码在函数内部这样换度量方式只改一处。另外注意换距离度量往往要重新扫 K。余弦距离的取值范围是 02欧氏距离的量纲随特征尺度变化同一个 K 在两种度量下的表现可能差好几个百分点。3.2 K 近邻选取排序 vs 部分排序拿到距离矩阵后每一行要挑出最小的 K 个。sort是最直观的写法复杂度O(m log m)其中 m 是训练样本数。在几千到几万的规模上sort完全够用不要过早优化。真正需要换mink的场景是训练集上百万条、并且你要在线实时预测——那时排序的常数开销会体现出来。function [nnIdx, nnDist] find_neighbors(Drow, k) % 从一行距离中找出 k 个最近邻的下标与距离 % Drow: 1 x m 距离行向量 % k: 近邻数量 % nnIdx: k x 1 训练样本下标按距离升序 % nnDist: k x 1 对应距离 m numel(Drow); k min(k, m); % 防御K 不能超过训练样本数 if k m / 4 % K 远小于总数时用部分排序避免全排 [nnDist, nnIdx] mink(Drow, k); nnDist nnDist(:); nnIdx nnIdx(:); else [nnDist, nnIdx] sort(Drow, ascend); nnDist nnDist(1:k); nnIdx nnIdx(1:k); end endk min(k, m)这行防御很重要。K 大于训练样本数时sort不会报错但会返回越界索引报错信息往往指向后面的yTrain(nn)越界排查起来要绕一圈。mink是较新版本 Matlab 才有的函数如果你的环境版本较老替换成sort分支即可行为一致。关于 K 值的语义K 必须是正整数实际使用中一般取奇数避免二分类时平票。多分类下平票概率低一些但仍然存在后面投票环节会用“距离加权”来天然打破平局。3.3 加权投票让近的样本说话更响最朴素的 KNN 是 K 个近邻一人一票多数决。这在样本分布均匀时没问题但当某一类样本密集、另一类稀疏时密集类会天然占优。距离加权投票越近权重越大能缓解这个问题代价是对噪声点更敏感。function [ypred, scores] knn_predict(D, ytrain, k, weightMode, classes) % 批量 KNN 预测 % D: nTest x nTrain 距离矩阵 % ytrain: nTrain x 1 训练标签 % k: 近邻数 % weightMode: uniform | distance | rank % classes: 全部类别列向量用于固定输出顺序 if nargin 5 classes unique(ytrain); end nC numel(classes); nTest size(D, 1); ypred zeros(nTest, 1); scores zeros(nTest, nC); for i 1:nTest [d, idx] find_neighbors(D(i, :), k); lab ytrain(idx); switch weightMode case uniform w ones(k, 1); case distance w 1 ./ max(d, 1e-12); % 距离为 0 时用极小值兜底 case rank w (k:-1:1); % 名次权重线性递减 otherwise error(knn_predict:badWeight, 未知权重模式: %s, weightMode); end % 按类别累加权重lab 必须映射到 1..nC 的连续整数 [~, labIdx] ismember(lab, classes); s accumarray(labIdx, w, [nC, 1], sum); scores(i, :) s; [~, mi] max(s); ypred(i) classes(mi); end end三个权重模式对应三种场景。uniform是教科书版本适合类别均衡、样本干净。distance是最常用的改进1/d的写法要注意d可能是 0测试样本与训练样本完全重合在切分逻辑有 bug 时会出现max(d, 1e-12)是兜底。rank只看名次不看绝对距离当你怀疑距离尺度不可靠比如特征还没标准化好时可以拿它做对照。accumarray(labIdx, w, [nC,1], sum)这一句是性能关键。等价写法是for c 1:nC, s(c) sum(w(labclasses(c))); end但要跑 nC 次逻辑索引accumarray一次搞定。前提是labIdx必须是1..nC的正整数所以ismember那步不能省——如果标签是字符串或者非连续整数直接传进accumarray会报错。另外scores返回的是各类别的累计权重做后续的 ROC 曲线、置信度分析时用得上不要只留ypred。3.4 交叉验证扫描 K 值K 是 KNN 唯一真正需要调的参数。扫一遍候选值用 K 折交叉验证挑平均精度最高的那个是最稳妥的做法。function [kBest, accCurve] scan_k(X, y, kList, nFold) % K 折交叉验证扫描最优 K % X, y: 训练集 % kList: 候选 K 值向量如 1:2:21 % nFold: 折数常用 5 或 10 if nargin 4, nFold 5; end cv cvpartition(y, KFold, nFold); % 分层每折类别比例与全集一致 accCurve zeros(numel(kList), 1); for a 1:numel(kList) k kList(a); for f 1:nFold trIdx training(cv, f); teIdx test(cv, f); % 每折内部单独标准化杜绝信息泄漏 [Xtr, mu, sg] normalize_data(X(trIdx, :), zscore); Xte (X(teIdx, :) - mu) ./ sg; D pairwise_dist(Xte, Xtr, euclidean); yp knn_predict(D, y(trIdx), k, distance); accCurve(a) accCurve(a) mean(yp y(teIdx)); end accCurve(a) accCurve(a) / nFold; end [~, bi] max(accCurve); kBest kList(bi); endcvpartition(y, KFold, nFold)里的y参数不能省——带上标签才做分层保证每折里各类别比例与全集接近。如果不分层小类别可能在某一折里一条样本都没有那一折的精度就没意义了最终平均会被拉偏。标准化写在折内是另一个关键点。把normalize_data挪到折外、对整个X做一次看起来只是代码位置不同实际是把验证折的统计量泄漏进了训练accCurve会整体偏高选出的 K 也会偏小小 K 更容易过拟合虚高的验证精度会偏爱它。kList一般取1:2:21或者1:2:min(31, floor(sqrt(n)))。上界跟样本量有关经验上不超过训练样本数的平方根再大就退化成一个全局先验了。4. 跑通全流程环境、参数与结果解读代码看懂之后要能真正跑起来并看懂输出。这一段按操作顺序走卡在哪一步就对应看哪一小节。4.1 环境准备与首次运行Matlab 版本上mink、vecnorm、readmatrix这几个函数需要 R2017b 之后的版本。如果你的环境更老把mink换成sort分支、readmatrix换成csvread或readtable即可逻辑不变。首次运行的步骤把项目根目录设为当前工作目录或者addpath(genpath(pwd))把子目录一次性加进搜索路径。后者更省事避免函数找不到的报错。确认data/下有数据文件文件名与params.dataFile一致。命令行执行main。% 一次性把项目所有子目录加入路径在项目根目录执行 addpath(genpath(pwd)); savepath; % 可选持久化下次打开 Matlab 不用再加genpath会递归包含所有子目录包括.git之类的隐藏目录一般无伤大雅。如果项目里有多个同名函数比如你自己也写了个normalize_data.m路径顺序会影响调用结果用which normalize_data确认一下实际命中的是哪一个。4.2 参数配置真正需要动的就这几个参数集中放在config_params.m里返回一个结构体。这种写法比散落的全局变量好维护也方便做批量实验时在外层循环改字段。参数典型取值影响调整建议kList1:2:21决定候选 K 范围样本多时上界可放大到 31weightModedistance投票策略类别不均衡时优先用它metriceuclidean距离度量特征已标准化后仍效果差试cosinenormModezscore标准化方式特征有硬边界时改minmaxtestRatio0.3测试集占比小样本300建议降到 0.2nFold5交叉验证折数样本少时用 10方差更小但更慢seed42随机种子做对比实验时固定保证可复现function p config_params() % 所有可调参数集中在此便于批量实验时统一修改 p.dataFile features.csv; p.testRatio 0.3; p.seed 42; p.normMode zscore; p.metric euclidean; p.kList 1:2:21; p.nFold 5; p.weightMode distance; p.classNames {类别A, 类别B, 类别C}; endseed这个字段很多人会忽略。split_data里用rng(seed)固定随机流之后同一份数据每次切分结果一致你改一个参数再跑精度变化才能归因到那个参数上不固定的话两次运行的差异里混着切分噪声根本判断不了改动有没有用。4.3 分层切分与可复现性function idx split_data(y, testRatio, seed) % 分层切分保证训练集与测试集的类别比例与全集一致 % 返回 idx.train / idx.test 两组下标 rng(seed); % 固定随机流 classes unique(y); trainIdx []; testIdx []; for c 1:numel(classes) pos find(y classes(c)); pos pos(randperm(numel(pos))); % 类内打乱 nTest max(1, round(numel(pos) * testRatio)); testIdx [testIdx; pos(1:nTest)]; %#okAGROW trainIdx [trainIdx; pos(nTest1:end)];%#okAGROW end idx.train trainIdx; idx.test testIdx; end按类别循环切分而不是整体randperm是为了处理不均衡数据。假设三类样本比例 90:8:2整体随机切 30% 做测试小类很可能一条都没进测试集混淆矩阵上那一行全是零精度数字看着很高但完全不能反映真实能力。max(1, ...)保证每个类别至少有一条进测试集。%#okAGROW是抑制 Matlab 的数组增长警告这里循环次数等于类别数通常个位数性能影响可以忽略。4.4 结果解读别只看一个精度数字跑完之后屏幕上通常只有一行精度这个数字信息量很低。真正要看的是混淆矩阵和决策边界图。function plot_result(ytest, ypred, classNames) % 绘制混淆矩阵与每类召回率 figure(Name, KNN 分类结果); cm confusionmat(ytest, ypred); confusionchart(cm, classNames); recall diag(cm) ./ max(sum(cm, 2), 1); fprintf(\n各类别召回率\n); for c 1:numel(classNames) fprintf( %-8s %.4f\n, classNames{c}, recall(c)); end endconfusionchart直接把矩阵画成热力图一眼能看出哪两类被混得最厉害。diag(cm) ./ sum(cm,2)算的是召回率每类被正确找出的比例跟总体精度是两回事总体精度 92% 但某一类召回只有 60%说明这个类别的特征区分度不够需要回去看特征工程而不是继续调 K。如果特征只有二维或三维可以再画一张决策边界图在特征空间铺一张网格每个格点当作测试样本预测一次用contourf画出色块。这张图能直观看出 K 取 1 时边界如何贴着噪声点走、K 取 15 时边界如何被平滑成一条粗略的分界。这是理解 K 值作用最快的方式比看精度曲线直观得多。5. 避坑与排查五条踩过的记录KNN 的代码短出错时的现象却常常指向错误的方向。下面五条都是实际调试中反复出现的按“现象 → 原因 → 解决”记下来。5.1 精度一直是随机水平现象不管 K 取多少测试精度稳定在1/类别数附近混淆矩阵看起来像均匀分布。原因矩阵方向搞反了。X被读成了d × npairwise_dist算出来的“距离”实际上是特征维度之间的差跟样本相似度毫无关系。这种情况不会报错因为矩阵乘法形状恰好兼容。解决在load_data里加方向断言或者在main.m里fprintf(样本数 %d特征数 %d\n, size(X,1), size(X,2))打印一次。养成打印矩阵尺寸的习惯比事后猜要快得多。5.2 预测结果里出现 NaN 或复数现象ypred里混着NaN或者sqrt报“输入必须为非负”的错。原因两个来源。一是特征里有常量列标准化时sigma 0除法产生NaN二是欧氏距离用矩阵乘法展开时浮点误差给出极小负数sqrt后变复数。解决normalize_data里加sigma(sigma0)1pairwise_dist里加D2(D20)0。这两行防御性代码看着不起眼但能消掉一大半莫名其妙的报错。5.3 交叉验证精度很高上线就崩现象scan_k报出的验证精度 0.96换一份新数据预测只有 0.7 出头。原因标准化或特征选择做在了切分之前。整份数据先标准化再切分测试折的均值和方差信息已经渗进训练过程特征选择如果在全量数据上做同理。这类泄漏在样本量小的时候尤其明显。解决把normalize_data挪进交叉验证的内层循环用训练折的mu/sigma去变换验证折。可以用一个简单的检查手段把scan_k里折内标准化的代码临时注释掉如果精度明显上升基本可以确认之前存在泄漏。5.4 样本量一大就跑不动现象训练集超过 2 万条时main.m跑一次要好几分钟内存占用飙升。原因pairwise_dist返回nTest × nTrain的稠密矩阵。2 万 × 2 万就是 4 亿个 double约 3.2 GB超出普通机器的可用内存系统开始换页速度断崖式下跌。解决分块计算。把测试集按 500 条一批切开每次只算500 × nTrain的距离矩阵预测完再拼起来峰值内存降到原来的四十分之一。如果连训练集都装不下就该考虑换近似最近邻结构KD 树、球树而不是继续用暴力搜索。5.5 类别不均衡时小类几乎全错现象总体精度 0.9但小类的召回率只有 0.1 左右混淆矩阵里小类那一行几乎全跑到大类去了。原因uniform投票下K 个近邻里只要大类样本占多数小类就永远赢不了。这是投票机制的问题不是特征的问题。解决先换weightMode distance让距离近的样本权重大一些再不行就在knn_predict里给每个类别的累计权重乘一个与类别频率成反比的系数也就是代价敏感学习。注意改完之后要重新扫 K因为加权会改变最优 K 的位置。6. 二次开发从能跑的教学代码到能用的实验基线教学版 KNN 和实验基线之间差的东西不多但每一件都影响结论可靠性。我一般按下面这个顺序做改造改一步验一次不一次性堆完。改造点改哪个文件验证方式换距离度量pairwise_dist.mparams.m固定 K对比两种度量的召回率加类别代价权重knn_predict.m看小类召回率是否上升、总体精度下降多少分块预测新增knn_predict_block.m分块与不分块的结果必须完全一致换评估口径plot_result.m增加宏平均 F1不只看总体精度第一件是换距离度量。这个改动最便宜收益却常常最大。做法是在pairwise_dist里加一支cosine然后把params.m里的metric改成可配置跑两组对照实验。判断标准不是总体精度而是小类召回率——如果换度量后小类召回明显提升、总体精度只掉零点几个点这通常意味着新度量更能捕捉类间差异值得保留。第二件是加代价权重。在knn_predict的accumarray之后加一行按类别频率的倒数做缩放% 代价敏感投票类别越稀有票越重 freq accumarray(ismember(ytrain, classes(:)) .* (1:numel(classes)), 1, ... [numel(classes), 1]); cw 1 ./ max(freq, 1); cw cw / mean(cw); % 归一化避免整体量级变化影响平局判定 s s .* cw;这段代码需要在函数内预先算好freq和cw放进循环外别在每次预测时重复计算。改完之后必须重新scan_k因为权重变化会移动最优 K 的位置沿用旧 K 得到的对比结论是不成立的。第三件是分块预测。写成knn_predict_block(D_func, Xtest, Xtrain, ...)这种传函数句柄的形式可以做到分块与整块结果逐位相同。验证方法很简单同一份数据分别走两条路径用isequal(yp1, yp2)断言不通过就是分块边界或索引拼装写错了。从那以后我每次改完 KNN 的投票逻辑都会强制走一遍“固定 seed → 跑交叉验证 → 打印各类召回率 → 与上一版逐类对比”的流程光看总体精度真的会漏掉小类退化这种最要命的问题。希望帮到你。本文还有配套的精品资源点击获取

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

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

免费获取报价 →
↑