资讯动态

MATLAB实现MRMR与ReliefF特征选择:原理、验证与踩坑

发布时间:2026/10/1 18:11:44 来源:尧图企业网站定制
简介MATLAB环境下实现MRMR与ReliefF两种经典特征选择算法的完整代码包面向需要剔除冗余特征、提升模型性能的机器学习学习者、竞赛选手及科研人员。压缩包共21个文件含7个m脚本、2个cpp源码、2个h头文件以及针对Windowsmexw64/dll、Linuxmexa64/mexglx、Macmexmac等平台预编译的mex动态库可在对应环境直接调用整体仅101KB轻量便携。MRMR基于互信息衡量特征与目标变量的相关性及特征间冗余度ReliefF则通过近邻样本权重评估特征区分能力两种方法互补适用于不同规模的数据集。目前已有264人浏览学习。代码中的fs_sup_mrmr、fsReliefF.m提供完整主流程mutualinfo.m、estpab.cpp等底层文件便于深究算法原理可直接嵌入毕业设计或竞赛项目。1. 特征选择翻车现场MRMR和ReliefF为什么值得放进你的MATLAB工具箱一份训练数据拉出来六七十个特征大多数人第一反应是直接丢进模型。结果往往是在训练集上慢慢涨、在验证集上原地踏步模型复杂度上去了可解释性却烂了。我调过的一个流程原始特征68个随机森林跑完准确率82%用MRMR和ReliefF把特征压到15个后再跑准确率到了91%训练时间少了三分之二。MRMR最小冗余最大相关性和ReliefF都属于过滤式特征选择方法不依赖下游分类器计算开销可控适合在建模前做一轮粗筛。这篇笔记按“原理、命令、验证、踩坑”的顺序写新手能照着跑通熟手能拿到参数边界和调整思路。2. MRMR原理与MATLAB实现从互信息到最小冗余最大相关性的落地路径2.1 MRMR的数学直觉为什么“最大相关”不够用互信息特征选择是最直接的做法逐个计算每个特征与标签的互信息I(x_i, y)按大小排序取前k个。它的缺陷是只看单特征不考虑特征之间的重叠。比如两个强相关但信息高度重复的特征各自与标签的互信息都很高按互信息排序它们都会入选实际等于把同一份信息买了两回。MRMR把选择标准改为“与标签的相关性尽量大、与被选特征之间的冗余尽量小”。定义特征子集S的评分相关项 V_S (1/|S|)·Σ_{i∈S} I(x_i, y)冗余项 W_S (1/|S|²)·Σ_{i,j∈S} I(x_i, x_j)目标是在满足|S|k的子集里最大化V_S - W_S。这是一个组合优化问题直接穷举做不到所以实际用的是贪心前向选择。MATLAB内置的fsrmrmr做的就是这件事每一步从未选特征里挑一个能使V_S - W_S增量最大的特征加进去。理解这一点很重要后面解读scores和调试结果都靠它。注意这里的互信息既出现在特征与标签之间也出现在特征与特征之间所以特征量纲和分布形态会直接影响评分。2.2 用fsrmrmr在MATLAB里跑通MRMR最小命令与输出解读% X: n行p列每行一个样本每列一个特征 % Y: n行1列类别标签必须是数值、逻辑或分类数组 [idx, scores] fsrmrmr(X, Y); % 看前10个特征的排序索引 idx(1:10)代码说明idx是特征索引的排序向量idx(1)是MRMR判据下最重要的特征idx(2)次之。scores与原始特征位置一一对应表示该特征在MRMR准则下的重要性评分数值越大越靠前。这里有个容易误会的地方scores不是“单特征与标签的互信息”而是贪心过程中加入该特征时对“相关性减冗余”目标函数的贡献所以第二个特征之后的分数通常会明显低于第一个这很正常。控制入选特征数% 只需要前5个特征时加名值对参数 [idx5, scores5] fsrmrmr(X, Y, NumFeaturesToSelect, 5); % 打印入选序号和对应评分 [idx5, scores5(idx5)]参数说明NumFeaturesToSelect让函数只返回前5个特征的索引和评分后续做对比实验时省一次截断。若你的特征里有类别型变量比如设备ID、班组编号版本支持时可以声明这些特征是类别特征避免把它当连续值参与互信息的联合分布估计。我每次跑之前都会先 help fsrmrmr 扫一眼当前版本的参数列表因为MATLAB各版本对名值对的支持不完全一致这是基本功不是小题大做。2.3 从零手写MRMR分箱互信息与贪心选择内置函数够用但有些场景必须自己写想换互信息估计器、想塞进更复杂的搜索策略、或者想确认scores到底怎么来的。下面是一个可运行的最小实现。function mi mutual_information(x, y, nbins) % 分箱法估计两个连续变量间的互信息 edges_x linspace(min(x), max(x), nbins1); edges_y linspace(min(y), max(y), nbins1); [c, ~, ~] histcounts2(x, y, edges_x, edges_y); pxy c / sum(c(:)); px sum(pxy, 2); py sum(pxy, 1); pxy(pxy 0) eps; px(px 0) eps; py(py 0) eps; mi sum(pxy .* log(pxy ./ (px * py)), all); end逻辑说明先把x和y各自分到nbins个箱子里用histcounts2统计联合直方图再按互信息的概率形式MI Σ p(x,y) log(p(x,y)/(p(x)p(y)))求和。pxy置eps是防止联合分布里有空箱子导致log(0)。nbins是关键参数太小把信息抹平太大把噪声当信号实数特征我一般取sqrt(n)附近的值n是样本量。function [idx, scores] mrmr_greedy(X, Y, k, nbins) % 贪心前向选择每步最大化 I(xj,Y) - 平均冗余 [~, p] size(X); n numel(Y); selected false(p, 1); idx zeros(k, 1); scores zeros(p, 1); Ixy zeros(p, 1); if nargin 4, nbins max(5, round(sqrt(n))); end for j 1:p Ixy(j) mutual_information(X(:,j), Y, nbins); end for t 1:k bestScore -inf; bestFeat 0; for j 1:p if selected(j), continue; end redun 0; for sIdx 1:t-1 if selected(sIdx) redun redun mutual_information(X(:,j), X(:,sIdx), nbins); end end if t 1, redun redun / (t - 1); end cand Ixy(j) - redun; if cand bestScore bestScore cand; bestFeat j; end end selected(bestFeat) true; idx(t) bestFeat; scores(bestFeat) bestScore; end end逻辑说明外层循环选k个特征。对每个候选特征Ixy(j)是它与标签的相关性冗余项是它与所有已选特征互信息的平均。cand 相关性 - 冗余取最大者入选。这个实现与fsrmrmr在数值上的差异主要来自互信息估计方式分箱法对离散特征友好对连续特征会损失精度MATLAB内置实现用的估计方式更细致。参数说明nbins默认取sqrt(n)附近样本少时下限设为5k是目标特征数。跑的时候注意时间复杂度是O(k·p)迭代里反复调mutual_information特征超过几百个时建议先把特征两两互信息算好存进矩阵而不是现算。另一个常见做法是把K近邻熵估计Kraskov估计器换成内置替代但那个实现起来更长分箱法在初筛阶段够用。2.4 MRMR的边界XOR型分布和标签噪声下为什么失效MRMR解决不了的问题主要有两类。一是强非线性关系且依赖特征组合比如XOR型分布里单个特征与标签的互信息都是0MRMR选不出任何特征只能转投嵌入式或包裹式方法。二是标签噪声大标签本身的错误会被互信息忠实记录下来选出来的特征也会偏向噪声方向。我遇到过一份标注错位的数据MRMR选出的特征全是和时间戳相关的列后来发现标签列在复制粘贴时整体下移了一行。遇到这类情况不要急着在特征选择方法上加复杂度先用相关性热图和数据清洗把基础打牢。这也是为什么我总把MRMR定位在初筛环节而不是最终拍板的那一步。3. ReliefF的MATLAB实现样本权重迭代与近邻选择3.1 ReliefF和MRMR的本质差异两类过滤式方法的路线之争在特征工程这个大类里特征选择是离建模最近的一步MRMR和ReliefF常被放在一起比较但它们的哲学完全不同。MRMR站在“特征集”的视角用互信息度量相关性是典型的信息论派ReliefF站在“样本”的视角通过反复随机抽样本、对比同类近邻和异类近邻来估计每个特征的价值属于距离度量派。这个差异带来两个直接后果第一ReliefF能感知特征之间的交互因为它每次打分都发生在真实样本的邻域上MRMR的贪心过程则更看重集合层面的冗余控制第二ReliefF对特征尺度敏感必须先做标准化MRMR用分箱互信息尺度的绝对值影响不大。两种方法的定位可以放一张表对比对比维度MRMR (fsrmrmr)ReliefF (relieff)核心度量互信息 / 信息论样本邻域距离特征交互感知通过冗余项间接感知在邻域上直接感知是否需要标准化不必须分箱不敏感必须距离计算敏感类别不平衡受先验影响较小少数类近邻波动大建议分层采样输出形态特征排序索引 评分排序索引 权重典型使用时机特征多、共线严重时做初筛想保留交互与局部判别信息时3.2 用内置relieff跑通特征排序与权重% X先做z-score标准化避免量纲差异主导距离 Xz zscore(X); % k是近邻个数默认10这里显式指定 [idx, weights] relieff(Xz, Y, 10); % 按权重从高到低看特征排名 [~, rankIdx] sort(weights, descend); rankIdx(1:10)代码说明第一个返回值idx是特征重要性排序索引第二个weights是与原始特征位置一一对应的权重向量。权重越接近0的特征区分能力越弱权重为负偶尔出现多见于噪声特征不用过度解读。k近邻数对结果有影响k偏小打分对局部样本敏感排序结果容易随样本变化而波动k偏大分数被平均得太狠细小的特征差异被磨平。分类问题我一般从k10开始试样本上千以后在10到20之间调。注意relieff距离计算的前提是标准化zscore之后再看排序结果否则连续量纲大的特征必然霸榜。如果样本量特别大relieff的全样本更新很慢近邻搜索是主要开销。先抽样2000个样本快速摸底是常见做法方向确认后再决定要不要上全量。内置relieff的参数表里通常还有先验概率、类别特征声明等选项每个版本支持情况不同动手前看文档。3.3 手写ReliefF核心循环权重更新公式与代码内置relieff的输出对初学者像个黑匣子想验证它对每个特征打分是否合理或者想改距离度量时需要看懂核心循环。function w relieff_manual(X, Y, k, m) % X: 已标准化的特征矩阵Y: 标签向量 % k: 近邻数m: 随机抽样迭代次数 [n, p] size(X); w zeros(p, 1); classes unique(Y(:)); D pdist2(X, X); rng(42); % 固定随机种子方便复现 for t 1:m i randi(n); same find(Y Y(i)); same(same i) []; if isempty(same), continue; end [~, os] sort(D(i, same)); hitIdx same(os(1:min(k, numel(same)))); missIdx []; for c 1:numel(classes) if classes(c) Y(i), continue; end diffC find(Y classes(c)); [~, oc] sort(D(i, diffC)); missIdx [missIdx; diffC(oc(1:min(k, numel(diffC))))]; end for j 1:p lo min(X(:,j)); hi max(X(:,j)); denom hi - lo; if denom 0, continue; end diffHit mean(abs(X(i,j) - X(hitIdx,j))) / denom; diffMiss mean(abs(X(i,j) - X(missIdx,j))) / denom; w(j) w(j) - diffHit / (m*k) diffMiss / (m*k); end end end逻辑说明每一步随机抽一个样本i找出它同类的k个近邻hit和每个异类里最近的k个近邻miss。对每个特征j计算该样本与hit和miss在特征j上的平均绝对差除以该特征的取值范围做归一化。权重更新公式的核心是三句话特征在同类间差异小权重下降特征在异类间差异大权重上升m次迭代后取平均。参数说明m即更新次数手写版建议取100到样本数之间。k1时就是原始Relief算法。这里为演示做了两点简化miss类没有按先验比例加权距离计算也没有复用当前权重向量这两点会让手写结果与内置函数有数值偏移但排序趋势一致。要缩小差距可以在每次迭代后把当前w归一化后重新计算距离矩阵代价是运行时间翻倍。3.4 ReliefF的两个关键前提标准化的必要性与类别不平衡的代价ReliefF的距离在原始特征空间上计算特征取值大的一方会主导距离打分自然偏向它。所以标准化不是可选项是必选项。类别不平衡时ReliefF对多数类的近邻统计更充分少数类的异类近邻经常远得离谱——距离排序里那些远邻帮不上忙。我的处理办法是分层采样更新迭代时先从每个类别按比例抽样本再进入近邻计算让少数类有足够发言权。另外遇到离群样本时ReliefF的权重会被单个极端样本拉偏建议预处理阶段用分位数截断或MAD剔除异常值后再跑。4. 特征选择结果验证把MRMR和ReliefF的排序变成可信的特征子集4.1 用什么评估特征子集准确率、AUC与一致性排序结果本身不解决“选几个特征”的问题。MRMR给了你从1到p的完整排名但真正训练时选前5个还是前20个完全取决于下游模型的表现。我见过两种典型的翻车只看权重绝对值把权重低于某个阈值的特征全删掉结果删掉了那些有冗余但仍有增量信息的特征另一种是迷信“前10名一定好”不做验证直接进建模。特征选择的产出不是那个排序表而是“在给定分类器下top-k的表现曲线”。二分类问题建议同时看准确率和AUC多分类以宏平均F1为主。单看准确率在类别不均衡时会被多数类带偏AUC和F1会诚实得多。要特别警惕验证集上“某个特征数下准确率突高”的假象小样本下交叉验证的方差很大一次峰值不能说明问题要看曲线趋势。4.2 用学习曲线确定特征数从top-1到top-N逐级累加% 假设idx是上一步特征选择得到的排序索引 maxK 20; accList zeros(maxK, 1); aucList zeros(maxK, 1); classes unique(Y(:)); rng(42); for nFeat 1:maxK Xs X(:, idx(1:nFeat)); model fitcsvm(Xs, Y, KernelFunction, linear, ... Standardize, true, CrossVal, on, KFold, 5); accList(nFeat) 1 - kfoldLoss(model); [~, score] kfoldPredict(model); [~, ~, ~, aucList(nFeat)] ... perfcurve(Y, score(:, 2), classes(2)); end plot(1:maxK, accList, -o, 1:maxK, aucList, -x); legend({Accuracy, AUC}); xlabel(特征数); ylabel(指标);代码说明fitcsvm的CrossVal,on,KFold,5直接做5折交叉验证kfoldLoss返回错误率accList就是1减去它。kfoldPredict返回每个样本在交叉验证下的预测分数perfcurve用分数与真实标签算AUC。这里classes(2)只是示例实际要换成你关心的正类标签值。跑出来的曲线通常“先快速上升、到达平台、然后缓慢下降”下降段就是特征数太多开始过拟合的区域平台段的起点是合适的特征数。参数说明KFold设5或10都行样本少用5样本多可以加。线性SVM只是验证工具换成决策树、KNN都可以关键是“同一个分类器、同一个交叉验证方案”下去比较不同特征数才有可比性。固定随机种子rng(42)是为了让每次重跑结果一致否则平台段每次都不同没法判断。4.3 特征排序的一致性验证bootstrap重采样与频率统计除了准确率还要看排序本身的可信程度。交叉验证只能说明“这个特征子集在今天这份数据上有效”不能保证换一批数据还是这组特征。一个便宜的做法是bootstrap重采样每次有放回地抽80%样本重跑特征选择记录每个特征进入前k名的次数。nRep 30; k 15; count zeros(size(X, 2), 1); rng(42); for r 1:nRep idx_r randsample(n, round(0.8 * n), true); [idx_sel, ~] fsrmrmr(X(idx_r, :), Y(idx_r), NumFeaturesToSelect, k); count(idx_sel) count(idx_sel) 1; end bar(count);代码说明randsample的第三个参数true表示有放回抽样每次抽80%样本重跑fsrmrmr统计每个特征被选入前15名的次数。count的柱状图里高的特征是“每次都上榜的稳健强特征”忽高忽低的是“靠运气上榜的特征”。同样的脚本换成relieff再跑一遍可以交叉确认。这一步不花多少时间但对交付很有价值——合作方问“凭什么是这15个特征”时你拿得出30次重采样的频率表而不是一句“算法算的”。4.4 两种方法的结果怎么用并集、交集还是加权如果MRMR和ReliefF的排序差异大不要急着分对错。常见做法是各取前20个做并集用4.2的学习曲线在这个并集上再选k。如果并集候选在验证集上还不如单一方法多半是标签噪声大或者特征间共线严重先回去看数据处理而不是在方法上继续加码。有一个更省事的交叉验证思路把两个排序当作两种特征生成器直接用fitcsvm在各自前5、10、15、20个特征上各跑一遍比较四个点的AUC谁高用谁。我压过最狠的一回两份排序几乎不重叠排查下来是数据里有一条记录把标签和特征列错位了。特征选择方法之间的“玄学”冲突根子多半在数据本身。5. MRMR与ReliefF踩坑实录数据预处理、类别编码与性能问题的排查清单5.1 陷阱一Y是字符串或cell函数直接报错现象调用fsrmrmr(X, Y)或relieff(X, Y, 10)报错提示Y必须是数值、逻辑或分类数组。 原因从Excel读进来的标签经常是字符串组成的cell这两个内置函数不接受这种格式。 解决用grp2idx做一次编码转换。[Ynum, Ynames] grp2idx(Y); % cell标签编码为1,2,3... [idx, scores] fsrmrmr(X, Ynum);补充编码后Ynames保存原始标签名后续画混淆矩阵或者做结果解释时还需要它。如果报错提示里出现“Y must be a numeric vector”这类字样基本就是标签类型问题。注意grp2idx输出的类别编号顺序是按首次出现顺序排列的不是字典序后续建模时要保持同一个映射。5.2 陷阱二特征或标签里有NaN结果全是NaN或直接中断现象scores或weights返回全NaN或函数直接提示不支持缺失值。 原因互信息分箱和距离计算都无法处理缺失值内置函数默认不插补。 解决在特征选择之前做缺失值处理。数值特征用中位数填补类别特征用众数填补缺失率过高的列直接删除并且顺序必须是“先删后补”。% 删除缺失率超过30%的列再对剩余列补中位数 keepCol sum(isnan(X)) / size(X, 1) 0.3; X fillmissing(X(:, keepCol), median);注意缺失值填补必须发生在特征选择之前且尽量在交叉验证的每一折内单独计算避免测试集信息泄漏。顺序做反了选出来的特征子集会虚高到新数据上就缩水。5.3 陷阱三离散特征和连续特征混在一起互信息被高估现象MRMR选出来的特征清一色是离散特征连续特征排名普遍靠后。 原因分箱法对取值少的离散特征天然友好联合分布估计更准互信息偏高连续特征分箱后信息被压缩评分吃亏。 解决把特征分成连续组和离散组分别排序合并候选再用交叉验证统一评估。relieff那边的问题刚好相反离散特征在距离计算里被当作连续量需要预先做独热编码或声明为类别特征。contCols [1 2 5:8]; % 按实际列号指定 discCols setdiff(1:size(X,2), contCols); [idx_cont, ~] fsrmrmr(X(:, contCols), Y); [idx_disc, ~] fsrmrmr(X(:, discCols), Y); % 合并两组候选后再走验证 cand unique([idx_cont(1:15), idx_disc(1:10)]);补充独热编码会把一个5个取值的离散特征拆成5列特征维数膨胀而且每列与标签的互信息被摊薄MRMR之后需要做组级合并。另一个做法是连续特征也用quantile分箱后再估计互信息两边都在同一个量级上比排名更公平。5.4 陷阱四大样本下relieff跑起来像蜗牛现象一万个样本、三百个特征relieff跑一次要几分钟甚至更久。 原因relieff每次更新都要在全样本里找近邻默认更新次数接近样本数复杂度近似O(n²·p)。 解决先抽样2000个样本跑快速版本确认方向再决定是否全量同时把k近邻数调小减少每次排序开销。% 随机抽2000个样本快速摸底 rng(42); sampleIdx randsample(n, 2000, false); idx_fast relieff(Xz(sampleIdx, :), Y(sampleIdx), 5);补充可以先算一下pdist2的距离矩阵规模万行样本的距离矩阵就有上亿个元素在MATLAB里直接占掉几百MB内存所以小步摸底的收益远大于硬跑全量。如果你有并行计算工具箱可以把第4章重采样验证那段循环用parfor拆开跑MRMR和ReliefF本身不可并行但30次重采样是可以并行的。5.5 陷阱五MRMR和ReliefF排序互相矛盾怀疑人生现象MRMR的top特征在ReliefF里排倒数两个方法的结果完全不重叠。 原因正常现象两种方法回答的问题不同。MRMR偏好独立性强、单特征信号明显的变量ReliefF偏好局部邻域区分度强、且常与其他特征交互的变量。 解决不要对比单个排名各取前20个并集后走交叉验证定夺。如果并集表现不如任何单一方法回数据层排查先检查特征列和标签是否错位、是否有重复样本、标签是否编码错误。记住一条血泪经验方法冲突往往是数据问题的信号不是特征选择本身有问题。6. 把特征选择封装成可复用脚本一致性验证与结果落地的进阶习惯如果你打算长期做特征工程而不是只看一次排序建议把第2到第5章的内容收进一个统一入口输入(X, Y, 方法名, k)输出特征排序、重采样频率和选中的特征列表。我自己的做法是把fsrmrmr、relieff、手写互信息估计器放在同一个目录里每次新数据进来先跑一遍汇总脚本产出三张图方法排序对比表、bootstrap频率柱状图、不同特征数下的交叉验证曲线。三张图一起看基本能回答“选哪几个特征”和“排序是否可信”。脚本里我习惯加一个验证动作把选出的特征子集在原始标签和打乱标签的数据上各跑一遍。打乱标签后特征选择结果应该回归随机水平如果打乱后依然能“选出”看似重要的特征说明特征选择存在严重的偏向多半是预处理把标签信息带进了特征矩阵。% 打乱标签后的对照实验结果应接近随机 rng(42); Yperm Y(randperm(numel(Y))); [idx_perm, ~] fsrmrmr(X, Yperm); % 对比原始标签下的排序计算前20个特征的重叠度 overlap numel(intersect(idx(1:20), idx_perm(1:20)));逻辑说明overlap如果很高说明特征矩阵里藏有和标签强相关的信息检查一下是不是预处理时按标签做过归一化、或者特征里混入了标签衍生的统计量。这是我在交付前必跑的净化测试不花多少时间但救过我很多次。回到标题本身MRMR和ReliefF在MATLAB里都是十几行命令能跑完的工具真正花时间的是数据预处理、参数调整和结果验证。内置函数帮你省掉了互信息估计和近邻搜索的细节但省不掉你对数据做判断的那一步。把每次实验的参数和结果记录下来养成“先验证、再交付”的习惯比换更强的算法更管用。希望这份笔记能帮你少踩几个我已经踩过的坑。本文还有配套的精品资源点击获取

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

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

免费获取报价 →
↑