
简介一套MATLAB实现的特征选择工具包面向机器学习和数据挖掘学习者解决高维数据中冗余特征筛选问题。内含MRMR与ReliefF两种经典算法前者基于互信息最大化相关并最小化冗余适合处理大规模特征集后者通过近邻距离评估特征权重计算效率较高两者均以函数形式封装可直接调用。包内共二十一个文件主体为m脚本便于阅读和修改同时附带C源码及头文件以及适用于Windows、Linux、Mac等平台的多种MEX编译文件确保跨环境运行压缩包整体仅约一百零一千字节。已有二百六十五人参与学习下载。通过源码可深入理解两种特征的评分机制并在分类、回归任务中动手实践特征筛选同时可作为毕设或竞赛的基础工具帮助提升模型性能与编程能力。1. 特征选择先于建模MRMR 和 ReliefF 到底在解决什么问题做分类建模时最怕的不是模型选错而是特征太多。几百上千维的数据直接丢进分类器训练慢是一回事冗余特征还会把模型带偏测试集上的泛化能力直线下降。特征选择就是在训练前把真正有用的维度挑出来这一步做对了后面模型调参都能省一半功夫。这份「matlab MRMR和relieff特征选择方法」资源包把两种主流特征选择算法封装成了可直接运行的 MATLAB 函数MRMR 用互信息平衡相关性与冗余ReliefF 用近邻差异给特征打分选特征的同时还自带加速用的 mex 文件。适合正在做毕设、竞赛特征工程或者论文里需要对比多种特征选择方法的从业者。下面我会从原理、调用、参数设置一路拆到踩坑记录照着跑就能用。2. 拆 MRMR 实现互信息计算、增量选择与 fsMRMR 调用2.1 先认识一下 MRMR 在算什么MRMRMaximal Relevance and Minimal Redundancy的核心目标一句话就能说清选出来的特征既要跟目标变量 y 高度相关彼此之间又别太像。两个「相关性」都用互信息Mutual Information, MI来度量定义是I(X;Y) ΣΣ p(x,y) log [ p(x,y) / (p(x)p(y)) ]互信息衡量的是一个变量的取值在多大程度上携带了另一个变量的信息。它比线性相关系数强的地方在于线性相关系数只捕捉线性关系而互信息对非线性关系同样敏感。比如一个特征跟标签之间是正弦曲线的关系皮尔逊系数可能算出来接近 0但互信息能给出明显的非零值。所以 MRMR 在处理真实表格数据时比单纯按方差或卡方筛选要可靠不少。MRMR 的搜索策略是贪心增量不是暴力枚举所有特征子集。第一步先选与 y 互信息最大的那个特征之后每一步从剩余特征里挑「与 y 互信息大、与已选特征平均互信息小」的特征。这里面有两种折中方式MIDMutual Information Differencescore rel - red直接相减MIQMutual Information Quotientscore rel / red直接相除两种打分各有边界。MID 实现简单、数值稳定大多数场景下够用MIQ 对相关性弱的特征更友好但 red 接近 0 时得分会爆炸实际使用要加个极小常数保护。资源包里的 fsMRMR.m 核心就是这个增量循环后面我会把循环逻辑拆开看。2.2 文件包里哪些文件是 MRMR 的解压后fs_sup_mrmr目录下的文件可以分成三组。第一组是算法入口文件作用fsMRMR.m增量选择主入口调用底层互信息函数完成任务fsMRMR_info.m用信息熵直接估计互信息的变体fsMRMR_parson.m用 Parzen 窗估计概率密度的变体对分布不规整的特征更稳第二组是互信息计算的底层支撑文件作用mutualinfo.m互信息的 MATLAB 封装入口estpab.cpp联合概率分布表估计的 C 源码estmutualinfo.cpp互信息最终计算的 C 源码miinclude.h、elementmexheader.h编译所需的头文件makeosmex.m重新编译 mex 二进制的脚本第三组是已经编译好的 mex 二进制后缀区分平台.mexw64是 Windows 64 位.mexa64是 Linux 64 位.mexglx是老版 Linux 32 位.mexmac是老版 Mac.dll是老版 Windows。看到这么一堆后缀就该有心理准备这个包跨了多个平台拿到手后第一步要确认当前 MATLAB 平台跟 mex 后缀匹配不匹配就得重新编译。这个坑我在第 4 章专门展开。2.3 最小可跑通的调用代码先写一个最简单能跑的调用用随机数据验证流程addpath(fs_sup_mrmr); % 把 MRMR 目录加进搜索路径 X randn(120, 60); % 120 个样本、60 维特征 y randi([0 1], 120, 1); % 二分类标签注意保持列向量 k 15; % 期望选出的特征数量 [selected, score] fsMRMR(X, y, k); disp(selected); % 按增量选入顺序排列的特征下标 disp(score); % 每个特征被选中时的 MRMR 得分这里有个容易混的点X 必须是每行一个样本每列一个特征和 sklearn 的惯例一致。y 必须是列向量如果传成行向量函数内部按列处理时维度就对不上。selected里的顺序不代表特征重要性排序而是增量选择被选入的先后顺序——第一个元素是单变量互信息最大的特征后面的元素是在冗余惩罚下选出来的。想看最终的优先级排名需要自己根据业务需求重新排。如果调用fsMRMR_info或fsMRMR_parson输入输出格式和fsMRMR保持一致只是互信息估计方式不同。我一般先把三个都跑一遍对比选定特征集合的重合度重合度高的说明结果稳定重合度低就要想想数据里的噪声是不是太大。2.4 从 mex 到 MATLAB 层互信息是怎么算出来的互信息计算是整个 MRMR 的性能瓶颈也是这个包选择用 C 写底层的原因。纯 MATLAB 循环遍历所有样本做直方图统计特征一多就慢得没法用。mutualinfo.m是封装入口内部会调用编译好的estpab先估计联合概率分布表再由estmutualinfo汇总出互信息值。单独验证一对特征的互信息可以这样调% 手动计算两个特征之间的互信息 mi mutualinfo(X(:, 3), X(:, 7), 10); % 10 是直方图分箱数第三个参数n_bins控制直方图的分箱数量直接影响互信息估计的精度。样本量在 100 到 500 之间时我一般取 8 到 12样本量超过 1000 可以放宽到 16 到 20。分箱数太小概率分布被压得太粗互信息会被低估分箱数太大每个箱子里样本太少统计噪声又会上来。这个参数值得用交叉验证试一轮它对 MRMR 选出的特征集影响不小。2.5 增量选择的循环逻辑MID 与 MIQ 两种打分fsMRMR.m 的核心是一个双重循环。外层循环要选 k 个特征内层循环遍历所有剩余特征计算每个候选特征的 rel 和 red 值。核心逻辑示意如下% 增量选择的示意fsMRMR.m 内部做的就是这个事 selected []; for t 1:k bestscore -Inf; best 0; for i 1:size(X, 2) if any(selected i) continue; % 已选特征跳过 end rel mutualinfo(X(:, i), y); % 与目标的互信息 if isempty(selected) red 0; % 第一个特征没有冗余 else red 0; for j selected red red mutualinfo(X(:, i), X(:, j)); end red red / numel(selected); % 与已选特征的平均冗余 end s rel - red; % MID 打分MIQ 用 rel / (red eps) if s bestscore bestscore s; best i; end end selected(end 1) best; end这段代码的复杂度特征很明显每选一个新特征都要重新计算剩余特征与目标、与所有已选特征的互信息。特征数 p 越大内层循环越长总开销接近 O(k·p²)。这也是为什么高维数据上 MRMR 跑得慢——不是 MATLAB 慢是算法本身就是这个量级。如果想用 MIQ把s rel - red换成s rel / (red eps)就行eps是浮点极小值防止除以零。3. 拆 ReliefF 实现近邻遍历、权值更新与交叉验证版3.1 ReliefF 的权值更新逻辑ReliefF 的思路和 MRMR 完全不同它不看信息论而是从样本的局部结构出发。核心假设是一个好的特征应该让同类样本在这个特征上取值相近让异类样本在这个特征上取值差异明显。基于这个假设算法通过反复抽样样本、查找近邻、累加权值来给每个特征打分。形式化的更新规则是重复 m 次每次随机抽一个样本 R_i在样本集中找它的 k 个同类近邻 H_j 和 k 个异类近邻 M_j。对每个特征 A按下面的规则更新权值W(A) W(A) - Σ_j diff(A, R_i, H_j) / (m·k) Σ_j diff(A, R_i, M_j) / (m·k)diff 是特征 A 在两个样本之间的归一化差异。连续特征直接做|x1 - x2| / (max - min)离散特征则取值相同记 0、不同记 1。累加到最后W(A) 为正且越大说明这个特征在区分不同类别上越有贡献W(A) 为负说明这个特征在同类别样本内部跳动得厉害反而干扰分类。这个算法的好处是直观输出天然就是每个特征的权值排名不像 MRMR 要自己解析增量顺序。但它有一个隐性约束依赖「同类近邻」和「异类近邻」的概念所以它是为分类任务设计的。回归任务没有类别标签ReliefF 直接不适用这一点在选型时要记牢。3.2 调用 fsReliefF拿到排名与权值fs_sup_relieff目录下的主函数是fsReliefF.m最小调用代码如下addpath(fs_sup_relieff); % 加入 ReliefF 目录 [ranked, weight] fsReliefF(X, y, 10); % 第三个参数是近邻数输出两个变量ranked是按权值降序排列的特征下标ranked(1)就是最有区分度的特征weight是每个特征对应的权值与ranked一一对应。第三个参数 10 是近邻数 k不是要选的特征数——这个跟 fsMRMR 的第三个参数含义完全不一样用的时候千万别混。选多少个特征由你自己根据ranked截取比如取前 15 个top_features ranked(1:15);ReliefF 对连续特征和离散特征都可以处理但连续特征不同量纲对 diff 的影响很大。如果特征里既有年龄0-100又有收入0-100000收入维度会主导近邻距离计算选出来的特征排名基本被量纲大的特征霸占。我一般喂数据之前先做一遍 zscore 标准化让每个连续特征都在同一尺度上再跑 ReliefF。3.3 近邻数 k 与迭代次数 m 怎么调fsReliefF的调用参数看起来简单真正要调的参数藏在函数内部。近邻数 k 和迭代次数 m 是影响结果的两个关键量参数位置建议值近邻数 k调用时的第三个参数默认 10样本量小于 200 时降到 3~5特征噪声大时可以试 15~20迭代次数 m函数内部实现原始实现默认遍历全体样本样本量几千时建议改到 300~500 次随机抽样k 太小近邻对噪声敏感单次抽样带来的权值波动大k 太大近邻里混入距离较远的样本同类近邻和异类近邻的区别被模糊化。m 的值直接影响稳定性和耗时的平衡。样本量大时跑全量重采样很慢我一般会把 m 改成固定次数比如 500结果和全量采样差别不大但速度能快好几倍。类别不平衡是另一个容易被忽略的坑。ReliefF 抽样时从多数类抽到的概率天然更高少数类的信息在权值里会被稀释。常见做法是先用datasample做分层采样让各类别样本量大致持平再喂给fsReliefF这样多数类不会主导权值更新。3.4 用 fsReliefF_cv_sv 做交叉验证评估fs_sup_relieff目录里还有一个fsReliefF_cv_sv.m从文件名可以看出它是 ReliefF 和交叉验证、支持向量机封装的组合。它的价值在于不是只看特征排名而是直接把「选完特征 → 训练分类器 → 交叉验证」这个流程串起来输出一个分类精度指标用来判断特征子集的真实效果。我拿到手的第一件事是先看函数头确认签名help fsReliefF_cv_sv % 确认输入输出格式再传参这个函数内部封装的是 SVM使用时注意两点。第一如果数据是多分类要确认源文件里的 SVM 是否支持多分类不支持就得自己改造成一对一策略。第二交叉验证的折数要跟数据量匹配几百样本做 5 折几千样本做 10 折折数太多单折样本过少评估方差会变大。4. 绕开三处暗坑mex 文件不匹配、编译失败与数据格式问题4.1 mex 文件平台不匹配最常见的翻车点现象运行 fsMRMR 报Invalid MEX-file estmutualinfo.mexw64或者报Undefined function estmutualinfo for input arguments of type double。第一次遇到这种报错很容易懵以为代码有问题其实问题出在二进制文件上。原因这个包同时带了.mexw64、.mexa64、.mexglx、.mexmac、.dll多种平台的编译产物MATLAB 只会加载与当前系统匹配的那一个后缀。比如 Windows 64 位应该用.mexw64但如果你从压缩包里拷贝文件时手抖或者杀毒软件隔离了部分文件MATLAB 找不到对应平台的 mex就会用这个含糊的报错提示你。解决先确认当前平台再重新编译。disp(computer); % 输出如 PCWIN64 / GLNXA64 / MACI64确认平台后把不匹配后缀的 mex 文件从 MATLAB 路径里移走然后运行makeosmex.m重新编译生成当前平台的二进制文件。注意杀毒软件有时会把.mexw64当可疑文件拦截解压时放行一次就好。4.2 编译失败先mex -setup现象运行makeosmex时直接报错No supported compiler was found或者编译到一半报一堆 C 头文件的语法错误。原因MATLAB 的 mex 编译依赖外部 C 编译器。新版 MATLAB 在 Windows 上常用 MinGW-w64老版本需要 Visual Studio 或者 lcc。系统里没装这些编译器或者装了但 MATLAB 没识别到makeosmex就跑不起来。解决编译之前先手动指定编译器。mex -setup C % 弹出编译器列表选中已安装的那个 makeosmex % 再跑编译脚本编译成功的标志是当前目录出现新的.mexw64或.mexa64文件时间戳是刚刚生成的。如果编译栈报的是 C 语法错误优先怀疑编译器版本太老——C11 的基本语法在老编译器上过不去换个新版本 MinGW-w64 基本能解决。4.3 数据格式问题行向量、NaN 与逻辑型标签现象fsReliefF能跑完但weight全为 NaN或者fsMRMR报错行数不一致。原因本质是输入数据格式不满足函数的隐性约定。常见三种情况y 传成了行向量函数内部按列处理时维数错位X 里混入了 NaN 或 Inf直方图统计和距离计算直接失效y 是逻辑型 true/falsediff 计算时被当成数值但边界行为异常。解决数据进函数之前先做统一清洗这是我每次跑特征选择的习惯动作X double(X); X(isnan(X)) 0; X(isinf(X)) 0; y double(y(:)); % 强制转成 double 列向量这套操作放在脚本最前面能省掉大量定位问题的时间。特别是从表格文件读进来的数据NaN 几乎是常态不处理就跑特征选择结果里出现 NaN 一点都不奇怪。4.4 特征数 k 越界与高维下的运行效率现象设置 k 大于特征总数时报Index exceeds matrix dimensions特征上千维时 MRMR 跑几分钟不出结果。原因k 越界是没做边界保护MRMR 的增量循环访问到不存在的特征下标高维慢是因为 MRMR 的互信息对计算是 O(k·p²) 量级p 越大开销增长越猛。解决传参前加保护高维数据先粗筛再精筛k min(k, size(X, 2)); % 防止越界 v var(X); X X(:, v 1e-6); % 先剔除零方差特征零方差特征对分类没有任何信息量留着只会拖慢互信息计算。粗筛之后再跑 MRMR耗时可能掉一个数量级而选出来的特征集几乎不受影响。如果粗筛后特征还是上千考虑先用卡方检验或方差选择砍到几百再交给 MRMR 精筛这是高维表格数据的标准操作。5. 两种方法怎么选从数据规模、特征冗余到最终模型评估5.1 一张参数对比表把两种方法放在同一张表里对比选型时会清晰得多维度MRMRReliefF打分依据互信息相关性 冗余惩罚近邻距离差异的权值累计输出形式按增量顺序的特征下标按权值降序的完整排名冗余控制显式惩罚与已选特征高互信息的特征隐式通过近邻距离间接体现非线性关系互信息可直接捕捉近邻距离间接捕捉回归任务适合互信息不依赖类别不适合依赖同类/异类近邻计算开销高特征多时近似平方增长较低主要开销在近邻搜索适用规模高维、冗余强的结构化数据小到中等规模、需要快速排名这里最值得记住的是最后两行。MRMR 的显式冗余机制在特征高度相关的场景里优势明显但代价是计算量ReliefF 胜在快和直观但它的结构性弱点在特征维度极高时会被放大——因为近邻距离是在全体特征上计算的无关维度太多会把近邻关系扯歪。5.2 三种典型场景的选择建议第一种场景特征几百维、彼此高度相关比如图像特征、基因表达数据。这种数据冗余是主要矛盾优先上 MRMR它能主动避开和已选特征高度相似的候选选出来的特征集更精简。第二种场景样本两三千、特征几十维目标只是快速拿到一个可解释排名。这种规模下 ReliefF 几十秒就能出结果而且权值直接代表区分度解释起来比 MRMR 的增量得分更直观。第三种场景回归任务。MRMR 的互信息计算根本不依赖类别标签直接用ReliefF 的同类/异类近邻概念在回归里没有定义不要硬上。5.3 两套方法同场对比的评估流程实际项目里我一般不会只信一种方法而是两个都跑再用同一个分类器做交叉验证对比。这样选型结论才有数据支撑rng(42); % 固定随机种子让两次对比的折划分一致 addpath(fs_sup_mrmr); addpath(fs_sup_relieff); [idx_m, ~] fsMRMR(X, y, 30); [idx_r, ~] fsReliefF(X, y, 10); nsel 15; % 两边都取前 15 个特征 acc_m crossval((xtr,ytr,xte,yte) ... mean(fitcknn(xtr,ytr,NumNeighbors,5).predict(xte) yte), ... X(:, idx_m(1:nsel)), y, KFold, 5); acc_r crossval((xtr,ytr,xte,yte) ... mean(fitcknn(xtr,ytr,NumNeighbors,5).predict(xte) yte), ... X(:, idx_r(1:nsel)), y, KFold, 5); fprintf(MRMR acc: %.3f, ReliefF acc: %.3f\n, acc_m, acc_r);关键细节是rng(42)要放在交叉验证之前这样两次对比的折划分完全一样唯一的变量就是特征子集。如果忘了固定随机种子两次精度差异可能只是折划分不同带来的噪声对比就不公平了。5.4 特征数 k 的取值经验拐点图与 log2 经验值特征数量选多少这个问题的答案不能拍脑袋。两个经验可以结合使用。第一个经验是信息论粗略估计k ceil(log2(p))p 是特征总数以这个为起点再上下搜索。比如 500 维特征log2 约 9那就在 8 到 15 之间试一轮。第二个经验是看权值分布的拐点。ReliefF 跑完之后把权值降序画出来如果曲线从某个位置开始明显变得平缓说明后面的特征边际收益很小[~, w] fsReliefF(X, y, 10); plot(sort(w, descend));找到拐点后在拐点前后各取几个 k 值做交叉验证选精度最高的那一个。这个「曲线找拐点 交叉验证精调」的组合方式比我之前直接套固定 k 值稳定得多。6. 把特征选择嵌进建模流程一条可以复用的完整链路6.1 一个可以直接改的完整脚本前面的原理和参数都过了一遍最后给一条完整链路把清洗、粗筛、双方法对比、交叉验证串成一个函数拿到自己的数据上改一行就能用function best_feat run_fs_pipeline(X, y, k_top) rng(42); % 固定随机种子保证可复现 X double(X); X(isnan(X)) 0; % 清洗 NaN X X(:, var(X) 1e-6); % 粗筛零方差特征 y double(y(:)); % 强制列向量 addpath(fs_sup_mrmr); addpath(fs_sup_relieff); k min(k_top, size(X, 2)); % 边界保护 [idx_m, ~] fsMRMR(X, y, k); [idx_r, ~] fsReliefF(X, y, 10); % 用 KNN 交叉验证对比两组特征集 acc_m crossval((xtr,ytr,xte,yte) ... mean(fitcknn(xtr,ytr,NumNeighbors,5).predict(xte) yte), ... X(:, idx_m), y, KFold, 5); acc_r crossval((xtr,ytr,xte,yte) ... mean(fitcknn(xtr,ytr,NumNeighbors,5).predict(xte) yte), ... X(:, idx_r), y, KFold, 5); if acc_m acc_r best_feat idx_m; fprintf(选择 MRMRacc%.3f\n, acc_m); else best_feat idx_r; fprintf(选择 ReliefFacc%.3f\n, acc_r); end end这段代码把前几章的坑都堵上了固定随机种子保证对比公平数据清洗提前做掉k 加边界保护粗筛砍掉零方差特征。拿真实数据跑的时候把 X 和 y 换成自己的变量就行。6.2 验证这一步千万别省特征选择本身不会提升模型精度它的作用是去掉干扰、缩短训练时间、提升可解释性。所以最后一定要做一组对比全特征训练一个模型选中特征训练一个模型比较精度和训练时间。如果精度持平但时间降了一半说明特征选择起作用了如果精度反而明显下降可能是 k 值设得太小把有区分度的特征砍掉了回去把 k 调大再跑一轮。刚拿到这套代码时我图省事直接把.mexa64文件拷到 Windows 机器上就想跑结果 MATLAB 报了一下午的Invalid MEX-file最后发现是平台后缀不匹配。从那以后我每次拿到任何带 mex 的 MATLAB 工具包第一件事永远是disp(computer)确认平台再跑一遍makeosmex重新编译绝不在这上面省时间。这个习惯帮我避开了后面好几次类似的问题希望帮到你。本文还有配套的精品资源点击获取