尧图精选

递归特征消除配合SVM:MATLAB特征选择实战与特征排序

🕒 发布时间:2026/10/1 23:13:23 📁 来源:尧图网络
简介面向机器学习与数据挖掘实践者的MATLAB资源包聚焦基于支持向量机SVM的递归特征消除RFE特征选择方法。RFE通过反复训练模型并剔除最不重要的特征帮助降低高维数据中的冗余与过拟合风险尤其适合特征维度较高、需要提升模型泛化能力的场景。包体非常精简共3个文件含2个MATLAB脚本与1个dat格式数据集整体仅3KB脚本分别承担数据加载预处理与RFE迭代删减核心流程数据文件则提供了肝脏疾病诊断样本可直接用于演练完整的特征选择过程。已有447人学习下载适合初学者观摩SVM-RFE的完整实现思路与流程。通过该资源可快速掌握特征重要性排序、逐步剔除冗余特征、比较不同特征子集下的模型性能为后续高维建模提供可靠的预处理依据。1. 递归特征消除配 SVM把“特征选择”从玄学变成可复现实验做机器学习建模的人几乎都经历过这种尴尬模型在训练集上漂亮得不像话一换数据就原形毕露。问题往往不在模型而在特征——冗余特征太多SVM 被噪声牵着鼻子走。这份digui.rar资源包里实现的是递归特征消除RFE配合 SVM 的完整 MATLAB 流程配套一份肝脏疾病诊断数据liver.dat跑一遍就能拿到“哪个特征对分类贡献最大”的排序结果。它适合两类人一是刚接触特征工程的建模新手需要一个能运行的代码模板二是想给高维数据降维、又不想牺牲可解释性的熟手。资源不大三个文件没有花哨包装核心就是digui.m这个递归消除函数。2. 拆解源码包三个文件的分工以及 RFE 为什么总爱配 SVM2.1 递归特征消除的运作逻辑“删掉最不重要的一个”这句话的执行细节递归特征消除听起来玄乎拆开看其实是一个贪心策略先用当前全部特征训练一个模型根据模型给出的特征重要性排序删掉最不重要的那个特征然后用剩下的特征重新训练再删一个循环往复直到特征数量达到预设值。关键点是“每次只删一个”而不是一次砍掉一批。因为特征之间可能存在交互一次性删掉多个很可能把单独看权重小、组合起来却有区分力的特征误伤掉。在 SVM 场景里特征的“重要性”由谁说了算线性核 SVM 训练完成后会得到一个权重向量w每个特征对应一个权重分量。权重的绝对值越大说明该特征对超平面方向的贡献越大也就是对分类决策越重要。RFE 就拿着这个w做“排序 → 删除 → 重训”的循环。这里的隐含假设是特征贡献与权重绝对值单调相关。这个假设在特征经过标准化之后基本成立。RFE 的停止条件有两种常见设置一是指定最终保留的特征数比如保留到 3 个就停二是观察模型性能指标当删除某个特征后性能下降明显就不再继续删。资源包里的digui.m采用的是第一种预设最少保留特征数逻辑更直接也方便做对比实验。2.2 liver.dat、liver_data.m、digui.m三个文件各自扮演什么角色解压digui.rar后有三个文件先搞清楚它们的分工再动手不然容易跑出莫名其妙的结果。文件名类型职责liver.dat数据文件肝脏疾病诊断数据集每行一个样本前若干列为特征最后一列为类别标签liver_data.mMATLAB 脚本负责加载数据、标签转换、归一化预处理为主流程准备输入digui.mMATLAB 函数实现 RFE 核心迭代逻辑输入特征矩阵和标签输出特征重要性排序liver.dat是典型的二分类医学数据。特征列是生化检验指标数值范围各不相同有的指标可能只有 0 到 1有的可能到几百如果不做归一化SVM 的权重向量会被数值量级大的特征带偏得出的特征排序就没有参考意义。所以liver_data.m里除了加载数据还会做标准化处理。digui.m是核心它不关心数据本身是什么领域只认“特征矩阵 标签”这对输入换任何数据集都能复用这是这份资源最值得拿走的模块。2.3 为什么是 SVM而不是 Lasso 或互信息特征选择方法很多Lasso 通过 L1 正则化让一部分特征的系数变成 0也能选出特征子集互信息特征选择则是完全不依赖模型纯粹靠计算特征与标签之间的相关性来打分。那为什么 RFE 要配 SVM主要原因是 SVM 在高维小样本场景下泛化能力相对稳而且线性核 SVM 天然产出权重向量RFE 可以直接拿它当特征打分器不需要额外训练一个代理模型。Lasso 的系数路径对特征间的共线性敏感两个高度相关的特征会被随机地“二选一”排序稳定性差互信息只看单变量关系忽略了特征组合的交互效应。相比之下RFE 是“带着模型一起选”每轮删除都考虑了当前特征子集下的真实判别贡献更贴近最终分类任务的目标。说到边界RFE 也不是万能的。如果样本量远小于特征维度每一轮 SVM 训练本身就在过拟合排序结果的可靠性会打折扣。这种情况我会先做一轮粗筛比如用互信息排序砍掉明显无关的特征再交给 RFE 细选。资源包里没有这一层但你可以照着这个思路自己加。3. MATLAB 实操复现从加载 liver.dat 到拿到特征排序3.1 第一步加载数据并做标准化打开 MATLAB把解压后的三个文件放进当前工作目录。先运行liver_data.m里的加载逻辑你也可以直接在命令行粘贴执行% liver_data.m 核心片段 % 1. 加载 liver.dat文件与脚本同目录 data load(liver.dat); % 2. 分离特征矩阵和标签最后一列是类别标签 X data(:, 1:end-1); y data(:, end); % 3. 归一化每一列减去均值再除以标准差 X zscore(X); % 4. 标签转成 SVM 需要的 1 / -1 形式 y(y 1) 1; y(y 2) -1;这段代码做了三件事。load把文本数据读成 MATLAB 矩阵data(:, 1:end-1)取除最后一列以外的所有列作为特征矩阵X最后一列作为标签yzscore是 MATLAB 内置的标准化函数按列计算均值和标准差。最后一步标签转换很关键fitcsvm默认要求二分类标签是1和-1如果原数据里类别是 1 和 2不转换会直接报错或者训练出错误的分类器。标准化这一步别省略。liver.dat里各特征的量纲相差很大如果不做zscoredigui.m里算出来的权重w会被大数值特征主导特征排序结果基本失真。这是跑这个资源最容易踩的坑之一后面避坑章节会展开说。3.2 第二步digui.m 核心函数——每一轮迭代做了什么digui.m是整个资源包的核心先看它的完整实现% digui.m —— 递归特征消除核心函数 % 输入: % X 标准化后的特征矩阵 (n_samples x n_features) % y 1 / -1 标签向量 % keep_min 最少保留特征数默认保留到 1 个 % 输出: % ranking 特征索引排序从最不重要到最重要 function [ranking, history] digui(X, y, keep_min) if nargin 3 keep_min 1; end [~, n_features] size(X); remaining 1:n_features; % 当前还保留的特征索引集合 ranking []; % 按“先淘汰”的顺序收集特征 history {}; % 记录每一轮剩余特征方便后续复盘 while length(remaining) keep_min % 用当前剩余特征训练线性 SVM model fitcsvm(X(:, remaining), y, ... KernelFunction, linear, ... Standardize, false); % 线性核 SVM 的 Beta 就是特征权重 w model.Beta; % 找到权重绝对值最小的特征判定为“最不重要” [~, rm_idx] min(abs(w)); % rm_idx 是在 remaining 局部数组里的下标要映射回原始特征编号 removed_feature remaining(rm_idx); % 记录被淘汰的特征并从保留集合中删除 ranking [ranking, removed_feature]; remaining(rm_idx) []; % 存一份当前剩余特征后面做性能曲线要用 history{end 1} remaining; % 命令行打印进度 fprintf(第 %d 轮删除特征 %d剩余 %d 个特征\n, ... length(ranking), removed_feature, length(remaining)); end % 最后剩下的特征最“重要”接在 ranking 末尾 ranking [ranking, remaining]; end这段代码里有两个细节值得注意。第一个是remaining(rm_idx) []MATLAB 支持用下标删除数组元素删除后remaining自动变短下一次循环的fitcsvm自然就只用剩余特征。第二个是ranking的拼接顺序每轮被淘汰的特征按先后顺序放进ranking前面迭代结束后把最终保留的特征补在末尾所以ranking是从“最不重要”到“最重要”排列的。要看最重要的特征取ranking最后几位即可。参数说明fitcsvm里KernelFunction设为linear是为了能拿到model.Beta作为特征权重如果换用rbf核Beta字段不存在特征排序就得另想近似方案具体在避坑章节讨论。Standardize设为false是因为我们已经在liver_data.m里做过zscore不需要让模型再做一遍。第三方参数如BoxConstraint默认取 1对小数据集够用不需要动。3.3 第三步运行完整流程输出特征重要性排序主流程脚本把数据加载和 RFE 函数串起来% 完整主流程加载数据 - 标准化 - RFE - 输出排序 data load(liver.dat); X data(:, 1:end-1); y data(:, end); X zscore(X); y(y 1) 1; y(y 2) -1; % 调用 digui.m至少保留 2 个特征 [ranking, history] digui(X, y, 2); % 打印特征重要性从最重要到最不重要 fprintf(\n 特征重要性排序最重要在前\n); for i length(ranking):-1:1 fprintf(第 %d 重要 - 原始特征列 %d\n, length(ranking) - i 1, ranking(i)); end % 用最重要的前 3 个特征重新训练 SVM对比全特征效果 top3 fliplr(ranking(end-2:end)); model_full fitcsvm(X, y, KernelFunction, linear, Standardize, false); model_top3 fitcsvm(X(:, top3), y, KernelFunction, linear, Standardize, false); % 计算训练集准确率作为快速参考 acc_full 1 - loss(model_full, X, y); acc_top3 1 - loss(model_top3, X(:, top3), y); fprintf(\n全特征 SVM 训练集准确率: %.2f%%\n, acc_full * 100); fprintf(Top3 特征 SVM 训练集准确率: %.2f%%\n, acc_top3 * 100);fliplr的作用是把ranking倒过来因为ranking末尾是最后保留下来的特征也就是最重要的特征倒序后正好从头到尾按重要性排列。这里用训练集准确率做快速参考只是为了验证流程通不通真正的模型评估要用交叉验证下一章会专门写。运行完这段你会在命令行看到类似这样的输出每一轮删掉了哪个特征、剩余多少特征、以及最终的特征重要性排序。liver.dat的特征数不多所以迭代很快几秒钟就能跑完。这个结果就是你后续所有分析的起点。4. 特征选择避坑指南三个翻车现场和修复办法4.1 现象先对全量数据做 zscore再划分训练集测试集准确率虚高跑通流程的人十个里面有八个犯过这个错。我一开始也是这样——把X zscore(X)放在数据划分之前整个数据集一起做标准化然后随机抽 70% 训练、30% 测试。测试准确率漂亮得惊人但换到真实场景就崩。原因zscore用的是全量数据的均值和标准差测试集的信息在标准化阶段就被“偷看”了这叫数据泄漏。正确做法是先划分训练集和测试集只对训练集计算均值和标准差再用训练集的统计量去标准化测试集。zscore函数不保存均值和标准差所以要么手动算要么用[Z, mu, sigma] zscore(X_train)的第二种返回形式。修复代码% 先划分再标准化 rng(42); % 固定随机种子结果可复现 idx randperm(size(X, 1)); train_idx idx(1:floor(size(X, 1) * 0.7)); test_idx idx(floor(size(X, 1) * 0.7) 1:end); % 只用训练集计算均值和标准差 [Z_train, mu, sigma] zscore(X(train_idx, :)); % 测试集用同一组统计量处理 Z_test (X(test_idx, :) - mu) ./ sigma;从那以后我每次跑特征选择都强制先划分再标准化宁可在心里默念三遍“测试集不能碰训练集”。4.2 现象一次删掉多个特征最优特征子集被跳过有段时间我嫌 RFE 跑得慢改成了每轮删 35 个特征结果得到的特征组合表现还不如随机选的。原因在于特征之间存在交互效应单独看权重都很低的两个特征放在一起可能对分类有强判别力。RFE 每次只删一个正是为了避免这种误伤——删掉一个特征后剩余特征的权重会重新分配原本被压抑的特征可能变得重要。一次删多个等于是用旧模型的视角去评判新子集当然会翻车。解决老老实实每次删一个。如果嫌迭代次数多可以先观察history里每一轮的准确率变化在性能开始明显下滑的前两轮停下来而不是盲目加速。4.3 现象把核函数换成 RBF 之后RFE 结果乱跳fitcsvm默认核函数是rbf如果不显式指定KernelFunction, linearmodel.Beta会是空数组min(abs(w))直接报错。就算用脚本强行近似RBF 核的 SVM 权重不具有“每个特征一个系数”的结构没法直接对应到原始特征排序结果也不稳定重跑两次排名可能都不一样。解决用这份资源做 RFE核函数必须固定为linear。如果你确实想用非线性核需要换成基于分类准确率的特征打分策略——每轮删除一个特征用交叉验证算删除前后的准确率差差得小就说明该特征可有可无。这种做法的缺点是计算量成倍增加liver.dat这种小数据集能承受上到几百维特征就吃力了。4.4 现象迭代结束后保留的特征数量不同排序结果对不上digui.m的ranking输出是“从最不重要到最重要”的顺序而history里存的是每一轮“剩下的特征索引”。如果你在循环外面直接拿history{end}当最终保留特征和ranking末尾对不上——因为ranking末尾包含的是最后一次迭代后剩余的keep_min个特征而history{end}也是这次的剩余特征两者应该一致。不一致往往是下标映射出了问题比如在删除remaining(rm_idx)之后又用了原始下标去访问X。解决删除前先把removed_feature remaining(rm_idx)记下来所有对特征列的操作统一走remaining映射别直接拿循环内局部下标当全局下标用。5. 性能验证与调参用交叉验证确定该保留几个特征5.1 不同特征数的交叉验证对比从 6 个特征看到 1 个特征RFE 给的是排序但“保留几个特征”这个问题排序本身回答不了。常见做法是拿排序结果做消融实验从最重要的 1 个特征开始逐步增加特征数量每一档都做一次交叉验证画出准确率随特征数变化的曲线取曲线峰值对应的特征数。% 基于 digui.m 排序结果做交叉验证消融实验 rng(42); [ranking, history] digui(X, y, 1); % ranking 从最不重要到最重要翻转后从最重要开始 important fliplr(ranking); cv_acc zeros(length(important), 1); for k 1:length(important) % 取前 k 个最重要的特征 X_sub X(:, important(1:k)); % 5 折交叉验证 mdl fitcsvm(X_sub, y, KernelFunction, linear, ... Standardize, false, CrossVal, on, KFold, 5); cv_acc(k) 1 - kfoldLoss(mdl); fprintf(保留 %d 个特征, 5 折交叉验证准确率: %.2f%%\n, ... k, cv_acc(k) * 100); end % 画出特征数与准确率的关系 figure; plot(1:length(important), cv_acc * 100, -o); xlabel(保留特征数量); ylabel(5 折交叉验证准确率 (%)); grid on;KFold设为 5在 345 个样本左右的小数据集上足够稳定。跑完之后你会看到一个典型趋势从 1 个特征到 3 个特征准确率快速上升3 到 5 个趋于平缓到 6 个时可能略微下降因为冗余特征开始引入噪声。不同核参数下曲线形态会变但“中间存在一个最优区间”是常见的。5.2 与 Lasso、互信息特征选择做对照判断 RFE 是否值得用只跑 RFE 不够说服力最强的实验是和主流特征选择方法做对照。MATLAB 里做互信息特征选择可以调用fscmrmr最小冗余最大相关性算法Lasso 用lassoglm或者fitrlinear带 L1 正则。% 方法一互信息特征选择MRMR [idx_mrmr, scores_mrmr] fscmrmr(X, y); % idx_mrmr 按重要性排序取前 k 个即可 % 方法二Lasso 特征选择 [B, FitInfo] lasso(X, y, CV, 5); % 取 Lambda1SE 对应的非零系数特征 non_zero_idx find(B(:, FitInfo.Index1SE) ~ 0);对照实验跑完通常能看到这样的规律互信息选出的特征单变量判别力强但组合起来可能冗余Lasso 选出的特征少而精但遇到强共线性时特征选择不稳定每次重跑选的列可能不一样RFE 在稳定性和组合效果之间比较均衡而且它的输出是完整的排序比另外两种更直观。下表是liver.dat上我跑出来的典型对比随着你改随机种子会有波动但趋势基本一致方法选出的特征数5 折 CV 准确率重跑稳定性RFE-SVM线性核3最高高排序稳定互信息MRMR4略低高但冗余偏多LassoLambda1SE2略低低共线性时波动这组对比的价值是告诉你选特征没有银弹RFE 的优势在于“结合模型看贡献”在模型可解释性这个诉求上它的排序结果比系数绝对值更直观。如果你的项目里特征数以百计建议先用互信息做一轮粗筛砍到 50 维以下再跑 RFE这是我在实际项目中验证过的效率方案。6. 进阶把这份代码改造成你自己的特征选择工具箱6.1 把“一次运行”变成“一次可复现实验”封装、固定种子、输出图表digui.m现在是一个手写风格的函数要变成能反复调用的工具箱建议做三件事固定随机种子、把交叉验证封装成独立函数、把特征数-准确率曲线图和数据一起存盘。固定种子的重要性前面避坑章节提过SVM 训练本身不依赖随机数但交叉验证的数据划分依赖不固定种子两次实验的准确率差异会干扰你的判断。% 封装一份通用的 rfe_svm_demo.m参数化输入 function [ranking, cv_table] rfe_svm_demo(X, y, kfold, plot_flag) rng(42); [ranking, ~] digui(X, y, 1); important fliplr(ranking); cv_acc zeros(length(important), 1); for k 1:length(important) mdl fitcsvm(X(:, important(1:k)), y, ... KernelFunction, linear, CrossVal, on, ... KFold, kfold); cv_acc(k) 1 - kfoldLoss(mdl); end if plot_flag plot(1:length(important), cv_acc * 100, -o); end cv_table table((1:length(important)), cv_acc * 100, ... VariableNames, {FeatureCount, CVPctAcc}); end这样写的好处是换数据集时只需要保证输入X是标准化后的数值矩阵、y是 1/-1 标签其他逻辑完全不用动。6.2 泛化到自己的数据两个必须重写的点和一个必须保留的底线换到自己项目的数据时要重写的是两部分数据加载和预处理。加载逻辑因文件格式而异这没什么好说预处理的底线是“测试集的统计量必须来自训练集”这条我在第四章跌过一次以后就刻进肌肉记忆了。digui.m本身不需要改动这是它设计得精简的好处。我个人的习惯是把它变成一个更大流程的子函数先跑 MRMR 粗筛再跑digui.m细选最后用rfe_svm_demo输出曲线。从那以后我每次做特征选择都强制走一遍“先划分 → 训练集标准化 → RFE 排序 → 交叉验证消融 → 对照实验”这五步不再凭所谓经验拍脑袋定特征。这套流程适用不了所有场景高维稀疏数据可能更适合直接上嵌入法但对大部分中小规模表格型数据它至少能给你一个不翻车的起点。希望帮到你。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联 返回资讯列表 →