尧图精选

MATLAB特征选择实战:MRMR与ReliefF算法详解及代码实现

🕒 发布时间:2026/10/1 22:54:00 📁 来源:尧图网络
简介这套MATLAB特征选择工具包面向机器学习学习者与科研人员提供MRMR最大相关最小冗余与ReliefF两种经典算法的完整实现。MRMR基于互信息衡量特征与目标的相关性及特征间冗余度ReliefF通过近邻距离为特征赋权两者可应用于分类、回归等场景的特征筛选帮助提升模型精度并降低计算成本。压缩包共21个文件核心为7个m脚本另附带cpp源文件、h头文件及编译好的mexa64/mexw64/mexglx/mexmac和dll动态库便于在Windows/Linux/Mac平台直接调用包体仅101KB。已有264人学习浏览。通过研读代码与实际运行读者能深入理解两种算法的计算流程与适用差异也可直接迁移到毕业设计或竞赛项目中是特征选择入门与实战的高性价比资料。1. 特征选择为什么值得单独折腾MRMR与ReliefF不是同一类算法做MATLAB实验的人多数都经历过这种时刻数据集里塞了几百个特征模型精度死活上不去查了半天发现有一半特征是冗余的。特征选择不是锦上添花而是直接影响模型能不能收敛、过拟合严不严重的关键一步。MRMR和ReliefF是两种最常被拿来对比的特征选择方法但很多初学者误以为它们只是两套打分公式的差别——实际上MRMR基于信息论用互信息同时衡量相关性与冗余度ReliefF基于近邻距离迭代地为每个特征打分。这套MATLAB实现把两个算法封装成了可直接调用的函数带了MEX加速文件还附了交叉验证脚本。适合做毕业设计、参加数据竞赛或者实际项目中需要快速筛选特征的MATLAB使用者。2. 先看MRMR互信息算相关性冗余度靠特征间MI来平衡MRMR 的全称是 Maximal Relevance and Minimal Redundancy翻译过来就是“最大相关、最小冗余”。它跟常见的 filter 方法最大的区别在于评价一个特征好不好不光看它跟标签的关联强不强还要看它跟已经选中的特征重不重复。如果你只想选 20 个特征而这 20 个特征彼此高度相关那实际上有效信息量可能只有五六个特征那么多后面的十几个都是在重复表达。2.1 为什么选互信息而不是皮尔逊相关系数先澄清一个容易混淆的点MRMR 里用的“相关性”不是皮尔逊相关系数而是互信息。皮尔逊相关只能捕捉线性关系两个特征之间的关系如果是非线性的比如一个单调但弯曲的曲线皮尔逊系数可能趋近于 0但它们之间明明有强关联。互信息没有这个限制它的本质是“知道一个变量之后对另一个变量的不确定性减少了多少”所以线性和非线性关系都能反映出来。MRMR 的目标函数在数学上写成两部分相减的形式max V(S) (1/|S|) Σ I(x_i; y) − (1/|S|²) Σ I(x_i; x_j)左边是特征与标签的互信息均值代表相关性的平均强度右边是特征两两之间互信息的均值代表冗余度的平均大小。算法按这个标准逐步往集合里加特征每一步挑一个让 V(S) 增量最大的特征放进去。这样一来新加入的特征一方面要跟标签够相关另一方面又不能跟已有的特征“重复信息”。互信息本身是定义在离散变量上的连续特征必须先离散化。常见的做法是等距分箱也就是把特征取值范围切成若干个区间然后用落在每个区间的样本频率去估计概率分布。这个包里的 estmutualinfo.cpp 实现的就是这套计算。离散化的箱数会直接影响结果箱数太多每个箱子里样本太少估计不稳定箱数太少信息损失又太大。2.2 estmutualinfo 与 makeosmex.m互信息计算的加速组件互信息的计算看着简单但实际上要遍历所有特征对当特征数上千时纯 MATLAB 写循环会非常慢。这个包里把互信息计算的核心部分写成了 C通过 MEX 编译后在 MATLAB 里直接调用就是文件列表里那一堆 estmutualinfo.mexw64、estpab.mexa64、estmutualinfo.mexglx。文件名里的后缀对应不同平台mexw64 是 Windows 64 位mexa64 是 Linux 64 位mexglx 是 Linux 32 位mexmac 是老的 Mac 平台。正常用的时候MATLAB 会自动匹配与当前系统匹配的那个文件但如果你换了 MATLAB 版本或者操作系统后缀名对不上就会报 “Invalid MEX file”。遇到这种情况不需要手动改代码直接跑包里的 makeosmex.m 重新编译% 先把当前目录切换到源码文件所在文件夹 % 然后执行编译脚本会自动调用 mex 命令编译 estmutualinfo.cpp 和 estpab.cpp makeosmex;makeosmex 脚本做的事情就是调用 mex 命令把 estmutualinfo.cpp 和 estpab.cpp 编译成当前平台对应的 MEX 文件。编译前确认 MATLAB 有可用的编译器就行Windows 下一般装一个 MinGW-w64 或者 Visual Studio 的 C 编译器Linux 下用系统自带的 gcc 即可。编译完成后会在当前目录生成新的 .mexw64 或者 .mexa64 文件以后调用 fsMRMR 使用的就是这份新编译的版本。还有一点值得注意箱数nbins是 estmutualinfo 的关键参数默认值通常在 5 到 10 之间。数据量大的时候可以适当提高数据量小就保持默认。我一般先用默认值跑一遍再试一次 nbins10对比两次选出的特征序号差异大不大。如果差异很大说明估计不够稳定需要回头看数据质量而不是继续调箱数。2.3 fsMRMR.m 的调用方式与输出含义fsMRMR.m 是这个包里 MRMR 算法的主入口。我一般会把调用封装成固定模板换数据集的时候只需要改数据路径和几个参数% 读取数据 % X: n x d 矩阵n 为样本数d 为特征数 % y: n x 1 列向量类别标签必须从 1 开始的正整数 load(data.mat, X, y); % 检查数据基本状态 assert(~any(isnan(X(:))), 特征矩阵存在 NaN需要先清洗); assert(all(ismember(unique(y), 1:max(y))), 标签必须是从 1 开始连续的正整数); % 希望最终选出的特征个数 k 20; % 调用 MRMR 特征选择返回特征编号与得分 [idx, score] fsMRMR(X, y, k); % 查看前 10 个被选中的特征 disp(选中的特征索引按重要度降序:); disp(idx(1:10)); % 把选出的特征子集保存下来供后续建模 X_selected X(:, idx(1:k)); save(selected_data.mat, X_selected, y, idx);这里 fsMRMR 的三个输入参数是固定的X 是特征矩阵y 是标签向量k 是想要的特征个数。返回的第一个值 idx 是长度为 k 的向量里面是按重要度从高到低排好的特征在原始矩阵中的列号后面的建模直接取 X(:, idx) 就行。第二个返回值 score 对应每个特征被选中时的 MRMR 得分可以用它观察选到第几个特征后得分开始明显下降那个拐点往往就是合理的特征数上限。有一点需要强调MRMR 是逐步贪心算法它的结果依赖于前一步已经选中的特征。同一个数据集如果两次运行中某一步分数相同产生随机取舍结果可能会有轻微差异。所以正式实验之前先固定随机种子保证结果可以复现。2.4 fsMRMR_info.m 与 fsMRMR_parson.m辅助函数的作用包里还有两个跟 MRMR 相关的辅助文件很多人会忽略它们其实在调试时很有用。fsMRMR_info.m 从命名看应该是输出每个特征与标签的互信息值也就是只算“相关性”那半边不做冗余惩罚。这个文件很适合在跑完整 MRMR 之前先看一眼哪些特征单打独斗能力强快速定位一批候选特征。fsMRMR_parson.m看名字里的 parson 就知道它应该是用 Parzen 窗核密度估计来估计互信息的版本。跟默认的等距分箱相比Parzen 窗的估计更平滑对小样本更友好代价是计算量更大。如果你的样本量只有几百个用默认版本跑出来的结果不太稳定可以换成这个版本试试通常能拿到更平稳的得分曲线。3. ReliefF 特征加权靠近邻距离迭代打分样本量决定稳定性ReliefF 是 Relief 算法的多分类扩展版。它的思路跟 MRMR 完全没有交集不计算信息量而是直接在样本空间里做距离度量。如果一个特征能让同类样本靠近、让异类样本远离这个特征就该得高分。这种思路在实践里非常直观也容易被非技术背景的合作者理解——你解释“谁的区分能力强谁分高”比解释互信息容易得多。3.1 ReliefF 打分的核心逻辑命中、未命中与权值更新算法每次从样本集里随机挑一个样本 R然后在所有样本中找 R 的 k 个同类近邻称为 near hit和 k 个异类近邻near miss。对每个特征计算 R 与这些近邻在该特征上的差量如果同类近邻在这个特征上跟 R 很像说明这个特征能把同类聚在一起权值就增加如果异类近邻在这个特征上跟 R 差异很大说明这个特征能把类别分开权值也增加。反过来异类近邻在这个特征上跟 R 很像说明它区分不了类别权值会降低。写成迭代式就是W W − Σ diff(R, H) / (m · k) Σ [P(C) / (1 − P(class(R)))] · Σ diff(R, M) / (m · k)其中 m 是随机抽样的次数H 是同类近邻集合M 是异类近邻集合P(C) 是某个异类类别的先验概率。公式里的权重项说明了一个容易被忽略的细节不同类别的样本量不均衡时稀有类别的贡献会被放大防止大类别把特征权值带偏。ReliefF 的优点在于计算开销跟特征维度近似线性相关不像 MRMR 那样要算特征两两之间的互信息。它的缺点是对近邻参数 k 和样本密度敏感小样本下 k 选得不好结果波动会很大。这也是为什么我每次用 ReliefF 都会顺手跑几次不同随机种子对比结果稳定性。3.2 fsReliefF.m 的调用方式与输出解释fsReliefF.m 是主函数调用格式跟 fsMRMR 类似但第三个参数不是特征数量而是近邻数 k% 固定随机种子避免每次运行结果波动 rng(2024); % 调用 ReliefFk5 表示使用 5 个近邻样本 % 返回值 weight 是每个特征的权值向量 % ranked_idx 是按权值降序排列的特征编号 [weight, ranked_idx] fsReliefF(X, y, 5); % 查看权值分布确认整体打分情况 figure; bar(weight); xlabel(特征编号); ylabel(ReliefF 权值); % 取权值最高的前 20 个特征 k_features 20; selected_idx ranked_idx(1:k_features); disp(selected_idx);关于近邻数 k 的取值我一般在样本量上百时取 5 到 10样本量上千时可以取 10 到 20。太小了权值方差大太大了会把边界区域的细节磨平。如果两个 k 值选出的特征集合差异明显说明特征之间的区分度本身不稳定这种情况下的结论需要谨慎看待不能直接拿一个 k 的结果就下判断。ReliefF 还有一个特点它对特征做了归一化之后再计算距离所以输入数据的量纲不同不影响排序结果。但要注意归一化是在函数内部做的你自己不需要提前归一化如果提前做了也不会出错只是多一步没必要的操作。倒是标签格式要注意ReliefF 内部是按标签索引近邻的传 0/1 或者非连续整数都可能出问题具体在第 4 章展开说。3.3 fsReliefF_cv_sv.m交叉验证脚本怎么理解这个文件命名里的 cv 是 cross-validation交叉验证sv 我理解是 support vector 的缩写意味着这个脚本会用支持向量机之类的分类器在 ReliefF 选出的特征子集上做交叉验证返回分类准确率。它的存在说明原包的作者不只是给了一个打分函数还给了评估特征子集有效性的标准流程。% 调用交叉验证评估 % 内部会对特征排序然后在不同特征数下评估准确率 acc fsReliefF_cv_sv(X, y, 5); % 观察不同特征数量下准确率的变化 plot(acc); xlabel(使用前 k 个特征); ylabel(交叉验证准确率);从使用经验看交叉验证脚本最有用的地方是帮你找到“准确率开始持平或下降”的那个特征数。MRMR 的特点是选出来的特征逐个都有独立信息所以准确率曲线往往单调上升然后走平ReliefF 选出的特征可能有重叠信息曲线到了某个点之后反而会掉头向下这时候就说明再往后加的特征已经开始引入噪声了。因此用 ReliefF 时不要盲目取权值排序的前 N 个一定要结合这条曲线确定最终的特征数。4. 踩坑记录MEX 编译、数据格式与计算资源四类问题MRMR 和 ReliefF 的算法本身不算复杂真正让人翻车的往往是环境问题和数据格式问题。这套代码我在 Windows 和 Linux 上都跑过下面这几条是出现频率最高的。4.1 现象调用 fsMRMR 报 “Invalid MEX file” 或直接提示找不到函数原因MEX 文件是编译过的二进制跟操作系统、MATLAB 版本强绑定。包里默认带的 estmutualinfo.mexw64 只适用于 Windows 64 位如果你用的是 32 位 MATLAB、换到了 Linux或者 MATLAB 版本太新导致二进制接口不兼容都会报这个错。解决先确认路径已经加进去用 addpath 把 fs_sup_mrmr 目录加入搜索路径。然后看报错是不是 MEX 文件的问题是的话不要纠结直接在源码目录跑 makeosmex.m 重新编译编译成功后后续调用就都正常了。需要提醒的是编译前确认当前目录确实是源码所在目录否则 mex 命令找不到 cpp 文件会报“无法打开源文件”的错误这个跟 MATLAB 版本无关是路径问题。4.2 现象跑了半天返回的 MRMR 得分里全是 NaN原因特征矩阵里存在 NaN 或者 Inf。互信息计算要对数据分箱统计频数NaN 会被当成一个奇怪的取值Inf 会让分箱边界失效最终概率估计崩掉得分自然就是 NaN。这个坑最常见因为很多人从数据库或者 Excel 导入数据时缺失值会自动变成 NaN完全没有提示。解决调用前先做清洗用 isnan 和 isinf 找出问题特征要么删除对应列要么用该列的均值或中位数填充。我的习惯是先打印一下每个特征的缺失率缺失率超过 30% 的特征直接删掉剩下的用中位数填充。这样对后续 MRMR 的影响最小因为中位数对异常值不敏感不会像均值那样被极端值拉偏。4.3 现象ReliefF 运行报错提示标签索引无效原因ReliefF 在找同类近邻和异类近邻时需要用标签值作为索引所以标签必须是 1、2、3 这样从 1 开始的连续正整数。如果你把二分类标签写成 0 和 1或者用了逻辑值 true/false或者类别编号是 1 和 3中间跳过了 2都有可能在索引时出错。MRMR 那边对标签的容忍度稍高一点但 ReliefF 对格式很敏感。解决在调用之前统一做一次标签映射把原始标签映射到 1 到 c 的连续整数范围% 将任意标签值映射为从 1 开始的连续正整数 [unique_labels, ~, y_mapped] unique(y); % 此时 y_mapped 是 1 到 c 的整数列向量 % unique_labels 保存了原始标签与新编号的对应关系代码说明unique 返回三个输出第三个输出 y_mapped 会把每个样本映射到它在 unique_labels 里的位置因此总是 1 到类别数之间的连续整数。这是我切换多个特征选择函数之前都会先做的一步避免每个函数对标签格式的要求不一致导致反复调试。4.4 现象MRMR 在特征数上千时跑得极慢甚至卡死不动原因MRMR 每选一个特征都要计算新特征与已选特征之间的互信息整体复杂度接近 O(d²)。d 是几百时还能等到了几千纯 MATLAB 循环配合 MEX 单次调用还是要等很久。尤其是用了 Parzen 窗版本 fsMRMR_parson.m 时计算量还会进一步上涨。解决先降维再精挑。常见的做法是先用 ReliefF 粗筛一遍把特征数从几千降到几百再用 MRMR 精挑。我做过对比实验粗筛后再精挑的结果跟直接在全部特征上跑 MRMR 相比分类准确率差别在 1% 以内但时间可以从几小时降到几分钟。另外可以适当调低互信息估计的箱数减少每一次互信息计算的开销但注意箱数不要低于 4否则信息损失太大选出来的特征会明显变差。5. 两个算法怎么配合先粗筛再精挑拿交叉验证结果对拍我最早用这套代码的时候是单独用 MRMR 选一组特征、单独用 ReliefF 选一组特征然后凭感觉挑个数。后来发现这俩算法选出的特征集合重叠度往往只有一半左右如果只看某一个算法很容易漏掉另半边有效特征。现在的固定流程是先用 ReliefF 快速筛掉明显没用的特征再用 MRMR 在剩下的特征里精挑最后用交叉验证对拍两个结果。% 先 ReliefF 粗筛取前 300 个特征 [~, ranked] fsReliefF(X, y, 5); X_reduced X(:, ranked(1:300)); % 再 MRMR 精挑取 30 个 [idx_mrmr, ~] fsMRMR(X_reduced, y, 30); % 两者对拍不同特征数下分别用 5 折交叉验证看准确率 % 这里用 fitcknn 做示范实际可以换成任意分类器 for k 10:10:50 acc_m(k/10) crossval_acc(X_reduced(:, idx_mrmr(1:k)), y); acc_r(k/10) crossval_acc(X(:, ranked(1:k)), y); end plot(10:10:50, acc_m, o-, 10:10:50, acc_r, s-); legend(MRMR 精挑, ReliefF 粗筛);其中 crossval_acc 是随手写的辅助函数内部用 fitcknn 配合 CrossVal, on 做五折交叉验证返回平均分类准确率。对拍图表上两条曲线的交叉点往往就是特征数的合理下限而开始走平的位置就是上限超过之后加特征只会增加过拟合风险不会带来精度提升。从那以后我每次做特征选择都强制走一遍这个流程先看数据有没有 NaN再映射标签然后 ReliefF 粗筛、MRMR 精挑最后交叉验证定数量。这套顺序帮我避开了绝大部分玄学调参的弯路希望帮到你。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联 返回资讯列表 →