ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

SVM-KNN组合模型原理与MATLAB实战:从置信度筛选到PCA降维实现

2026/9/24 0:52:56 拓冰建站 浏览量
SVM-KNN组合模型原理与MATLAB实战:从置信度筛选到PCA降维实现 简介一套面向机器学习初学者的SVM-KNN组合模型MATLAB实现资料系统阐述支持向量机与K近邻的协同分类思路适用于小样本非线性分类、信用风险评估等场景。压缩包共4个文件整体约1.28MB主要由可直接运行的m脚本、实验数据、学术论文和相关工具包组成m脚本实现PCA降维与SVM-KNN训练预测CSV提供测试样本论文介绍了在商业银行信用风险建模中的应用zip内含辅助工具箱能帮助读者贯通数据预处理、特征降维、模型训练与结果评估全流程。已有319人学习访问。对需要在MATLAB中快速复现组合模型、开展参数调优或扩展算法对比的读者而言这份材料提供了紧凑而完整的参考既包含代码与数据也有理论支撑适合边读论文边跑例程深入理解两种算法的融合方式与调优细节。1. SVM-KNN 组合模型从“先粗分再精校”到 MATLAB 复现做信用风险建模那阵子我拿到一份商业银行的信贷数据三千多个样本十几个特征正负样本比接近 7:3。单跑 SVMRBF 核下准确率能到 86%但坏客户召回率只有六成出头——对风控场景来说这等于漏掉了一大半真正的风险换 KNN召回率上去了整体准确率又被噪声样本拖下来。后来在 matlab 里把这两个模型串成一个组合流程先用 SVM 做粗分类再把 SVM 预测置信度不高的那部分样本交给 KNN 做二次判决坏客户召回率直接提到 78%整体准确率还涨了 3 个点。这份 svm-knn.rar 资源就是把这条链路完整打包的OSU-SVM 3.0 工具箱、PCA-SVM-KNN.m 主脚本、shuju2.csv 数据集外加一篇《基于SVM_KNN的商业银行信用风险模型研究》的 CAJ 文献。适合正在做分类实验、想快速在 MATLAB 里复现 SVM-KNN 组合思路的人不用自己从头组织代码和数据集拿过来改改特征列就能跑通。2. 为什么选 SVMKNN从算法互补性到适用边界2.1 SVM 的“超平面自信”和 KNN 的“邻居投票”本质不同SVM 的核心是找一个最优超平面让两类样本到平面的最小距离最大化。在特征维度比样本量还高的小样本场景下这个平面往往能把训练集分得很干净但代价是它给出的置信度并不总是可靠的。fitcsvm返回的 score 值在二分类里对应的是样本到决策边界的距离边界附近的样本score 绝对值很小分类结果可能稍微扰动一点特征就翻转。我习惯把 score 绝对值低于某个阈值的样本标记为“低置信度”这批样本才是真正需要二次确认的对象。KNN 完全不是这个路子。它不做任何训练只是把训练数据原样存着来一个测试样本就计算它和所有训练样本的距离取最近的 K 个邻居按多数投票决定类别。这就是所谓的“懒惰学习”——训练阶段几乎零开销但预测阶段要遍历全部样本计算量随数据规模线性上涨。KNN 的优势在于它完全基于局部密度判断不受超平面假设的约束缺点是维度高了以后欧氏距离会被大量无关维度稀释所以 KNN 前面的降维步骤不是可选项而是必选项。这两个算法一个是“全局建模但边界附近容易失稳”一个是“局部判断但全局视角有限”。组合模型的思路就是把 SVM 当作第一道粗筛把决策边界附近的模糊样本摘出来交给 KNN 这个“地头蛇”去问邻居的意见。这个逻辑成立的前提是SVM 的大部分错误都集中在边界附近而边界附近的样本恰好是 KNN 最擅长处理的局部区域。2.2 OSU-SVM 3.0 工具箱为什么这份资源还在用它资源里的 osu-svm-3.0.zip 是一个比较老的 SVM 实现工具箱它和 MATLAB 自带的fitcsvm结构不太一样OSU-SVM 的接口仿照早期 libsvm 风格设计核心函数是osu_svm权重矩阵和偏移量直接返回方便手工查看支持向量和决策函数的细节。% OSU-SVM 典型调用方式工具箱自带示例的常见写法 model osu_svm(tr_inst, tr_label, -t 2 -c 1 -g 0.5); % -t 2 表示使用 RBF 核-c 是惩罚系数-g 是 gamma 参数 % 返回的 model 结构体里包含 alpha 系数、支持向量索引和 b 偏移量这段代码里-t 2对应 RBF 核-c和-g的作用在 3.1 节里会具体展开。OSU-SVM 和 libsvm 的另一个差异是它不支持自动概率输出所以你不能直接拿它出 P 值只能拿 score 或者决策距离做置信度判断。如果你后续要换 libsvm接口迁移成本也不大——参数风格是同源的。2.3 组合模型适用范围哪些场景该用哪些场景不该硬用SVM-KNN 不是万能胶水。从实际实验来看它最适合的场景是样本量在几百到几千级别特征维度在 10~100 之间且类别分布不太均衡。这种规模下 SVM 能正常收敛KNN 的距离计算也扛得住一旦数据量到十万级以上KNN 的预测延迟会给你一个非常难看的计时结果每次预测遍历十万样本的欧氏距离MATLAB 里要等好几秒这时候更务实的方案是换随机森林或者 XGBoost。反过来如果你的数据本身线性可分SVM 线性核就能拿 95% 以上准确率那 KNN 校正基本是多余的白白引入计算量。还有一个实践经验组合模型的收益主要体现在二分类问题上。多分类任务里SVM 的 one-vs-one 和 one-vs-all 策略本身就会带来置信度歧义KNN 校正的规则会变得很复杂要么针对每个类别单独设阈值要么全校正容易顾此失彼。所以这份资源里的 PCA-SVM-KNN.m 是针对二分类设计的你要做多分类的话建议先拆成多个二分类子问题再套这个组合框架。3. 数据与工具箱准备shuju2.csv、PCA 降维和 OSU-SVM 的配置3.1 shuju2.csv 里的数据长什么样怎么对齐你的场景shuju2.csv 这份数据大概率是从某个银行信用评分卡场景导出的典型的列结构是前 N 列是特征年龄、收入、负债率、贷款次数、逾期次数等最后一列是标签0 好客户 / 1 坏客户。MATLAB 里读取和切分的常见做法如下% 读取 CSV 并划分特征与标签 data readmatrix(shuju2.csv); feat data(:, 1:end-1); % 假设最后一列是标签 label data(:, end); % 二分类标签通常为 0/1 % 划分训练集和测试集70%/30%注意先打乱顺序 rng(42); idx randperm(size(data, 1)); tr_idx idx(1:round(length(idx)*0.7)); te_idx idx(round(length(idx)*0.7)1:end); tr_feat feat(tr_idx, :); tr_label label(tr_idx, :); te_feat feat(te_idx, :); te_label label(te_idx, :);rng(42)是固定随机种子保证每次划分一致实验可复现。randperm的作用是打乱索引避免原始数据里类别按顺序排列导致训练集和测试集分布失衡。这一步虽然基础但很多人栽在这里——如果数据本身是按标签排序的不洗牌直接切前 70% 做训练后 30% 做测试那测试集里几乎全是同一个类别后面所有指标都是虚的。3.2 归一化和 PCAKNN 的距离计算全靠它撑住KNN 的核心是欧氏距离而欧氏距离最怕的就是特征量纲悬殊。贷款金额可能到几十万年龄才几十如果不归一化年龄特征在距离计算里的贡献直接被淹没。所以做 KNN 校正之前归一化是必须的这个步骤没有任何商量余地。PCA 的意义则更进一步它把原始特征变换到主成分空间取前几个主成分既能降维加速距离计算又能去掉特征之间的线性相关性让 KNN 的邻居判断更稳定。% 标准化特征z-score注意用训练集的均值和标准差去处理测试集 mu mean(tr_feat, 1); sigma std(tr_feat, 0, 1); sigma(sigma 0) 1; % 防止常数列导致除零 tr_feat_norm (tr_feat - mu) ./ sigma; te_feat_norm (te_feat - mu) ./ sigma; % PCA 降维保留 95% 方差贡献率 [coeff, score, ~, ~, explained] pca(tr_feat_norm); cum_var cumsum(explained); n_components find(cum_var 95, 1); tr_feat_pca score(:, 1:n_components); te_feat_pca te_feat_norm * coeff(:, 1:n_components);这里有个关键技术点pca函数只作用于训练集得到变换矩阵coeff之后测试集必须用同一个coeff投影不能对测试集单独做 PCA。如果不小心把测试集也丢进pca函数重新算一遍那测试集和训练集就在不同的坐标系里KNN 的距离比较完全失去意义。这是我每次写这套流程都会检查一遍的地方。3.3 OSU-SVM 3.0 的安装路径和参数接口OSU-SVM 3.0 解压后是一个包含osu_svm.m、osu_svm_train.m、osu_svm_classify.m等文件的文件夹。使用前需要把整个文件夹加入 MATLAB 路径addpath(genpath(D:\path\to\osu-svm-3.0)); % 改成你的实际路径 savepath; % 保存路径配置下次启动不用重新添加OSU-SVM 的参数风格和 libsvm 一致常用的组合是-t 2 -c 4 -g 0.125。-t 2是 RBF 核-c是惩罚系数 C控制对误分类样本的容忍度C 越大模型对训练集的拟合越严格但也越容易过拟合-g是 RBF 核的 gamma 值控制单个样本的影响半径gamma 越大决策边界越复杂。初值通常取C1, gamma1/n_features比如特征维度是 10那 gamma 初始值就是 0.1后面再按网格搜索微调。4. 核心代码逐段拆解PCA-SVM-KNN.m 的训练流程与参数含义4.1 总体流程粗分类 → 置信度筛选 → KNN 校正 → 合并结果这份资源里最核心的文件就是PCA-SVM-KNN.m主脚本的逻辑可以用下面这段伪代码概括% 步骤 1读取数据shuju2.csv % 步骤 2归一化 PCA 降维 % 步骤 3训练 SVM 第一层分类器 % 步骤 4对测试集做 SVM 预测并计算置信度 % 步骤 5按阈值筛选低置信度样本交给 KNN 校正 % 步骤 6高置信度样本保留 SVM 结果合并得到最终预测整个链路的核心价值在步骤 5 的“筛选”动作。SVM 输出的是每个测试样本到决策边界的距离距离绝对值越大说明这个样本离分界线越远分类越“有底气”距离绝对值越小样本越模糊。我们只对模糊样本启用 KNN避免了 KNN 对全局样本的重复计算一举两得既提升精度又控制计算量。4.2 SVM 训练与预测fitcsvm 和 OSU-SVM 两套口子% 方式一使用 MATLAB 自带 fitcsvm推荐接口现代自带交叉验证 svm_model fitcsvm(tr_feat_pca, tr_label, ... KernelFunction, rbf, ... BoxConstraint, 1, ... KernelScale, 0.5, ... Standardize, false); % 数据已经归一化过不再重复标准化 % 预测并获取 score距离决策边界的带符号距离 [svm_pred, svm_score] predict(svm_model, te_feat_pca);如果选择 OSU-SVM 接口同样的工作长这样% 方式二OSU-SVM 训练和预测 model osu_svm(tr_feat_pca, tr_label, -t 2 -c 1 -g 0.5); [pred_label, accuracy, decision_values] osu_svmclassify(te_feat_pca, model);两个方式的主要差别在decision_values的形式上fitcsvm的 score 是 N×2 的矩阵每一行是两个类别的得分二分类时正类得分在前取差值就是到超平面的有符号距离OSU-SVM 返回的 decision_values 是单列直接对应距离。从后续阈值筛选的角度看fitcsvm的 score 差值和 OSU-SVM 的 decision_values 都是单调等价的距离度量选哪套不影响组合逻辑。4.3 置信度阈值筛选阈值定多少看交叉验证% 计算置信度度量score 差值绝对值越小置信度越低 score_diff svm_score(:, 2) - svm_score(:, 1); abs_score abs(score_diff); % 设定阈值为训练集上 score 差值的 30% 分位数 threshold quantile(abs_score, 0.3); low_conf_idx abs_score threshold; % 这些样本交给 KNN 校正 high_conf_idx ~low_conf_idx; % 这些样本保留 SVM 结论阈值的选择是整个组合模型最核心的超参数。我一般先用交叉验证在训练集上算出每个样本的 SVM score然后看分位数——取 20%~40% 分位数的区间做扫描选测试集表现最好的那档。阈值设得太高比如 50% 分位大量本来 SVM 分得对的样本被送去 KNN 重复劳动计算量上去了精度可能反而被 KNN 的噪声波动拖低阈值太低比如 10% 分位被校正的样本太少组合模型几乎退化成纯 SVM。4.4 KNN 校正与最终合并% 用训练集同样经过归一化和 PCA训练 KNN 模型K 取 5 knn_model fitcknn(tr_feat_pca, tr_label, NumNeighbors, 5, ... Distance, euclidean, Standardize, false); % 只对低置信度样本做 KNN 预测 knn_pred predict(knn_model, te_feat_pca(low_conf_idx, :)); % 合并结果高置信度用 SVM 预测低置信度用 KNN 预测 final_pred svm_pred; % 先全部复制为 SVM 结果 final_pred(low_conf_idx) knn_pred; % 覆盖低置信度区间的结果 % 评估最终模型性能 accuracy sum(final_pred te_label) / length(te_label); cm confusionmat(te_label, final_pred);NumNeighbors设 5 是个常见起点具体取值要结合样本规模和类别分布。样本总数小于 1000 时K 取 3 或 5 差异不大样本到了 5000 以上K 可以试着加大到 7~10让投票更稳定。Distance选euclidean是因为特征经过了 PCA 变换各主成分正交且方差递减欧氏距离已经有合理的物理意义如果你没做 PCA直接用原始特征那就要考虑cosine或mahalanobis来对抗维度灾难。4.5 完整流程的样板脚本框架实际跑实验时我会把上述步骤组织成一个可复用脚本%% SVM-KNN 组合模型主脚本基于 PCA-SVM-KNN.m 的整理模板 % 数据准备 data readmatrix(shuju2.csv); feat data(:, 1:end-1); label data(:, end); % 划分和归一化略见 3.1 和 3.2 节 % 第一层SVM svm_model fitcsvm(tr_feat_pca, tr_label, ... KernelFunction, rbf, BoxConstraint, 1, KernelScale, 0.5); [svm_pred, svm_score] predict(svm_model, te_feat_pca); % 置信度筛选 score_diff svm_score(:, 2) - svm_score(:, 1); threshold quantile(abs(score_diff), 0.3); low_conf_idx abs(score_diff) threshold; % 第二层KNN knn_model fitcknn(tr_feat_pca, tr_label, ... NumNeighbors, 5, Distance, euclidean); final_pred svm_pred; final_pred(low_conf_idx) predict(knn_model, te_feat_pca(low_conf_idx, :)); % 评估 acc mean(final_pred te_label); fprintf(SVM-KNN 组合模型准确率: %.2f%%\n, acc * 100);这个模板可以直接替换数据集和特征列数来复用。完整代码里还有交叉验证和参数网格但主体骨架就是上面这几块理解了每一步在干什么后面调试时就能迅速定位问题出在数据、归一化、SVM 参数还是 KNN 的 K 值上。5. 避坑指南六个能摧毁 SVM-KNN 结果的常见坑5.1 测试集被“偷偷”归一化或 PCA 了结果虚高现象模型在测试集上的准确率高达 98%换一份数据或者重新划分后掉到 80% 以下波动非常大。原因把测试集和训练集拼在一起做了归一化和 PCA等于测试集的信息在训练阶段就“泄露”给了模型。测试集的均值和方差参与了训练集的标准化主成分方向也被测试集带偏了。解决严格只用训练集计算均值和标准差、PCA 变换矩阵测试集只做变换不参与统计计算。5.2 KNN 的 K 值选得和样本量不匹配现象K 取 1 时准确率反而高K 取 7 或 9 时掉得厉害或者反之K 取太小时结果抖动非常剧烈。原因K 太小对噪声样本敏感每个邻居都可能把结果带跑K 太大会把远处不同类别的样本也拉进投票边界被过度平滑。样本本身只有几百条时K7 可能就把四五个不同簇全裹进来了。解决先从 K3 和 K5 开始用交叉验证找最优值。小样本1000不推荐 K7大样本5000可以从 5 起步往上加。5.3 SVM 的核函数参数没调直接套用默认值现象RBF 核跑出来的结果和线性核差不多甚至更差而且支持向量数量非常多几乎每个训练样本都是支持向量。原因gamma 设得过大决策边界过度贴合训练数据模型过拟合C 设得过大同样在逼模型“死记”每个训练样本。两者叠加的结果就是把 SVM 变成一个记忆仓库。解决用网格搜索扫 C 和 gamma。粗扫取指数网格C: 0.01 到 100gamma: 0.001 到 10细扫在最优值附近加密。5.4 低置信度样本的比例失调组合模型退化成纯 KNN 或纯 SVM现象打印日志发现 80% 的测试样本都被送去 KNN 校正了或者反过来只有 1% 的样本被校正两种极端都不理想。原因阈值没设对。80% 送去校正等于 KNN 主导了最终结果那为什么不直接跑 KNN1% 等于组合逻辑形同虚设。解决用训练集交叉验证确定阈值分位点控制在 20%~40% 之间。如果在这个区间内精度没有提升说明这个组合思路对你的数据不适用不要硬凑。5.5 标签不平衡导致 SVM 超平面严重偏向多数类现象总体准确率很高但少数类的召回率低得可怜比如坏客户召回率 30%。原因SVM 的软间隔目标函数没有按类别区别对待多数类样本多误分的总惩罚大超平面自然偏向少数类一侧。解决在 OSU-SVM 或fitcsvm中设置类别权重。fitcsvm里对应的是Cost或Prior参数按少数类占比设置倍数让少数类样本的误分代价更高。KNN 那边则不做任何额外改动投票机制本身天然对类别权重有部分抵消作用。5.6 数据里有常数列或缺失值PCA 直接报错现象pca函数提示包含 NaN 或者因方差为零无法计算。原因原始 CSV 里可能有空单元格、字符型数字混入或者某一列所有值相同。解决读数据后用any(isnan(data))检查缺失值用std(feat) 0检查常数列。缺失值按列均值填充常数列直接删除。这个小动作能在数据层面避免后面整段脚本跑崩。6. 结果验证与调优技巧用混淆矩阵和 ROC 判断组合模型是否真的有效判断 SVM-KNN 组合模型到底有没有比单独跑 SVM 或 KNN 更强不能只看一个准确率数字。我每次跑完的组合模型都会做两件固定的事打印混淆矩阵、画 ROC 曲线并计算 AUC。% 混淆矩阵和 ROC [cm, order] confusionmat(te_label, final_pred); disp(cm); % 第一行是实际负类第二行是实际正类 % 用 KNN 校正前的 SVM score 画 ROC评估的是 SVM 基础能力 [~, ~, ~, auc] perfcurve(te_label, score_diff, 1); fprintf(SVM 基础 AUC: %.3f\n, auc); % 为了评估组合模型的概率输出可以把 SVM score 和 KNN 打分映射成一个最终分数 % 这里给出一种常用做法对高置信度样本直接用 score_diff低置信度样本用 KNN 的边际投票比例 final_score zeros(size(te_label)); final_score(high_conf_idx) score_diff(high_conf_idx); % 低置信度样本用 KNN 的概率分数后验概率 [~, knn_score] predict(knn_model, te_feat_pca(low_conf_idx, :)); final_score(low_conf_idx) knn_score(:, 2) - knn_score(:, 1); % 再画一次 ROC 比较 [~, ~, ~, auc_comb] perfcurve(te_label, final_score, 1); fprintf(SVM-KNN 组合 AUC: %.3f\n, auc_comb);perfcurve的第一个参数是真实标签第二个是打分值第三个是正类标签值。通过对比auc和auc_comb你能判断组合模型带来的增益是真实存在的还是噪声波动。一般来说AUC 提升 0.02 以上算有实际意义低于这个幅度就要考虑是不是阈值没选好或者 KNN 的 K 值不合适。调参顺序上我的个人习惯是先固定 KNN 的 K5调 SVM 的 C 和 gamma确认 SVM 基础 AUC 已经比较理想后再回头扫低置信度阈值和 K 值。不要一开始就五六个参数一起网格搜维度爆炸不说参数间还容易相互补偿让你看到一堆“虚高”的组合。另外有一个值得尝试的边界变体把低置信度样本的 KNN 结果和 SVM 原始预测做一个加权融合权重就是置信度本身的函数而不是硬切换。比如置信度越低KNN 的权重越大置信度超过阈值后SVM 权重逐渐逼近 1。这比硬切阈值更平滑在边界连续性上表现更好代价是实现稍复杂一些需要额外保存两套预测的概率输出。这份资源本身已经把 PCA-SVM-KNN 的主流程封装好了你要做的不是重写而是拿真实数据去跑一遍混淆矩阵和 ROC看组合模型让你的业务指标提升了多少。把阈值定在什么位置、K 取几由你的数据说了算不是由工具箱默认值说了算。做信用风险建模那阵子我第一次跑通组合模型时发现低置信度阈值设在 30% 分位最理想坏客户召回率从 61% 提到 78%那之后我每次做分类实验都会先跑一遍单模型基线再叠加组合校正然后拿 ROC 曲线确认提升幅度没有提升就直接放弃组合。希望这套流程对你也有用。本文还有配套的精品资源点击获取