简介:这是一套机器学习特征选择算法的完整实现资源,聚焦基于互信息的最大相关最小冗余(mRMR)方法,附源代码、文档说明、学习报告和实验数据集,面向从事相关研究的在校生、教师及企业开发者,可用于课程设计、毕业设计或算法对比实验。代码部分包含C++核心模块(6个.cpp与6个.h)、MATLAB脚本(4个.m,涉及PCA、PPCA和特征索引)、SVM相关程序,以及lung、NCI9两组CSV数据集和PDF学习报告,能完整体现特征选择的预处理、计算和验证流程。压缩包共24个文件,以源文件、脚本、数据集和报告文档为主,另附makefile、mrmr与fast-mrmr可执行文件,整体仅968KB,轻量紧凑,目录结构清晰。已有145人学习下载,项目经作者测试运行成功,答辩评审平均分达96分,适合快速上手并基于现有代码做功能扩展。下载解压后可参照说明文档或mrmr源文件了解算法细节,便于学习与二次开发。
1. 机器学习里的特征选择:为什么是 mRMR 而不是拍脑袋挑特征
做基因表达谱分类时,我遇到过最头疼的事不是分类器选哪个,而是特征太多。几千个基因列在那里,样本只有几十个,直接丢给 SVM,精度全靠运气。后来换成基于互信息的最大相关最小冗余(mRMR)做特征选择,先把和类别标签相关性最强、彼此之间冗余度最小的特征挑出来,再喂给分类器,精度从玄学变成可复现。这套资源就是完整的 mRMR 实现,包含 MATLAB 源代码、两份基因表达数据集、大作业报告和说明文档,适合做机器学习课设、毕设或者刚入门特征选择的人。跟着跑一遍,你就能把它换成自己的数据用。
2. 最大相关最小冗余:互信息公式与贪心搜索的取舍
2.1 互信息:能抓非线性关系的关联度量
特征选择的第一步是回答一个问题:凭什么说一个特征和类别标签"相关"?最常见的做法是算皮尔逊相关系数,但它只能捕捉线性关系。基因表达数据里大量存在的是剂量效应、阈值效应,两个变量关系很强但不是直线关系,皮尔逊系数算出来可能接近 0,你直接把这个特征丢掉了,血亏。
互信息(Mutual Information)没有这个限制。它衡量的是知道一个变量之后,另一个变量的不确定性减少了多少。定义式是:
I(X;Y) = ΣΣ p(x,y) * log( p(x,y) / (p(x) * p(y)) )当 X 和 Y 完全独立时,p(x,y) = p(x)p(y),log 里面等于 1,互信息为 0;只要存在任何统计依赖,互信息就大于 0。这个性质决定了它天然适合做特征和标签之间的相关性打分,尤其在高维小样本的组学数据上,比相关系数可靠得多。
代码实现上,连续变量需要先离散化。常见做法是把特征值归一化后分箱,用直方图估计 joint 概率和边缘概率。你打开包里的mRMR/src,能看到核心的互信息估计函数,基本原理就是我下面这段的样子:
function mi = mutual_info(x, y, n_bins) % 计算两个变量之间的互信息 % x, y: 长度相同的列向量 % n_bins: 连续变量分箱数量,默认 20 if nargin < 3 n_bins = 20; end % 归一化到 [0, 1],避免量纲差异影响分箱 x = (x - min(x)) / (max(x) - min(x) + eps); y = (y - min(y)) / (max(y) - min(y) + eps); % 落到分箱编号,1 ~ n_bins x_bin = min(floor(x * n_bins) + 1, n_bins); y_bin = min(floor(y * n_bins) + 1, n_bins); % 联合直方图和边缘直方图 joint_hist = accumarray([x_bin, y_bin], 1, [n_bins, n_bins]); n = numel(x); p_joint = joint_hist / n; px = sum(joint_hist, 2) / n; py = sum(joint_hist, 1) / n; % 扩展成矩阵,计算互信息 px_mat = repmat(px, 1, n_bins); py_mat = repmat(py, n_bins, 1); mask = p_joint > 0; mi = sum(p_joint(mask) .* log(p_joint(mask) ./ (px_mat(mask) .* py_mat(mask)))); end分箱数n_bins是唯一需要你拍板的参数。bin 太少,互信息被低估,区分度消失;bin 太多,每个箱子里样本太少,概率估计噪声很大。我一般先取 20 跑一遍,再试 10 和 30,看排名靠前的特征是否稳定,这也是后面避坑章里要重点说的事。
2.2 最大相关与最小冗余:两个目标怎么同时优化
如果只按互信息从大到小排,选出来的一定是一堆互相高度相关的特征,信息冗余严重。mRMR 的核心思想是同时优化两个目标:
- 最大相关:已选特征集合 S 中的每个特征与类别 c 的互信息平均最大,数学表达为
D = (1/|S|) * Σ I(x_i; c)。 - 最小冗余:已选特征两两之间的互信息平均最小,数学表达为
R = (1/|S|^2) * ΣΣ I(x_i; x_j)。
两个目标直接合并成一个增量搜索问题:每轮从未选特征里挑一个,让"与标签的相关性减去与已选特征的平均冗余"最大,也就是最大化I(x_j; c) - (1/|S|) * Σ I(x_j; x_i)。这个过程是贪心的,每一步只保证当下最优,不保证全局最优,但在高维特征空间里这是唯一现实的做法——穷举所有特征组合在计算上根本不可能。
包里的 mRMR 核心实现逻辑大致是这样:
function [feat_idx, score] = mrmr_select(X, y, K) % 基于互信息的最大相关最小冗余特征选择 % X: n×p 数据矩阵,n 个样本,p 个特征 % y: n×1 标签列向量,类别用整数编码 % K: 目标特征数量 % feat_idx: 选出的特征下标(按选择顺序排列) % score: 每个特征入选时的目标函数值 [n, p] = size(X); K = min(K, p); % 预计算每个特征与标签的互信息(最大相关项) mi_class = zeros(p, 1); for j = 1:p mi_class(j) = mutual_info(X(:, j), y, 20); end % 预计算特征两两之间的互信息(最小冗余项),用于后续查询 mi_feat = zeros(p, p); for i = 1:p for j = i+1:p v = mutual_info(X(:, i), X(:, j), 20); mi_feat(i, j) = v; mi_feat(j, i) = v; end end selected = false(p, 1); feat_idx = zeros(K, 1); score = zeros(K, 1); for t = 1:K best_j = -1; best_val = -inf; for j = 1:p if selected(j) continue; end if t == 1 % 第一个特征只看与标签的相关性 val = mi_class(j); else % 后续特征:相关性 - 与已选特征的冗余均值 redun = mean(mi_feat(j, feat_idx(1:t-1))); val = mi_class(j) - redun; end if val > best_val best_val = val; best_j = j; end end selected(best_j) = true; feat_idx(t) = best_j; score(t) = best_val; end end注意这里特征两两互信息矩阵是 O(p²) 的复杂度,p 上万时二重循环会非常慢。包里的实现做了优化,但如果你要处理的是超过两万维的数据,我建议先用方差过滤砍掉明显没信息的特征,再跑 mRMR,否则光是算两两互信息就要等到天亮。
2.3 为什么选 filter 而不是 wrapper 或 embedded
特征选择方法按和分类器的关系分成三类,mRMR 属于 filter 派,因为它只依赖统计量,不依赖任何具体分类器。这三类的差别直接决定你在大作业或者毕设里怎么交代选型理由:
| 类型 | 搜索方式 | 计算开销 | 与分类器关系 | 典型代表 | 适用场景 |
|---|---|---|---|---|---|
| filter | 统计量排序 | 小,一次算完 | 无关 | mRMR、卡方、方差选择 | 高维小样本,特征上万 |
| wrapper | 特征子集 + 分类器评估 | 大,反复训练 | 强依赖 | 递归特征消除 | 特征几百以内 |
| embedded | 训练过程内嵌选择 | 中等 | 依赖模型 | L1 正则、树模型 | 中高维,模型带正则 |
选择 mRMR 而不是另外两派,核心原因是你没有足够样本让 wrapper 类方法反复训练分类器。基因表达数据经常是几十个样本对几千个特征,跑一轮递归特征消除要训练几十次分类器,算完可能已经是第二天了。mRMR 一次把所有特征的排序算出来,之后你想用 SVM、随机森林还是朴素贝叶斯验证,都是同一份特征子集,可复现性也好。embedded 方法虽然效果通常更好,但它和具体模型绑死,换成另一个分类器整套结论就要重跑。
3. 源码拆解:test.m、getFeaturesWithIndex.m 与 svm 验证链路
3.1 test.m:整个项目的入口顺序
拿到压缩包解压之后,先别急着点运行。把文件结构捋一遍,你就知道作者是怎么组织一个完整特征选择实验的。test.m是主入口,它的职责是把整条链路串起来:读数据 -> 跑 mRMR 选特征 -> 用选出的特征训练 SVM -> 输出精度。后面的test_pca.m是另一条对比路径,用 PCA 降维后再做同样的事情,两份脚本对照着看,本身就是一份现成的实验设计模板。
我按包里的接口风格补一个等价的主流程示例,你打开test.m对照着看,结构应该是一致的:
% test.m 主流程:mRMR 特征选择 + SVM 验证 % 第一步:读取基因表达数据集 data = csvread('test_lung_s3.csv'); X = data(:, 1:end-1); % 前若干列为特征 y = data(:, end); % 最后一列为类别标签 % 第二步:运行 mRMR,选出 30 个特征 [feat_idx, score] = mrmr_select(X, y, 30); fprintf('选中特征索引: %s\n', mat2str(feat_idx')); % 第三步:抽取特征子集,训练 SVM 做 5 折交叉验证 X_sel = X(:, feat_idx); rng(42); cv = cvpartition(y, 'Kfold', 5); acc = zeros(cv.NumTestSets, 1); for i = 1:cv.NumTestSets tr = training(cv, i); te = test(cv, i); model = fitcsvm(X_sel(tr, :), y(tr), 'Standardize', true); pred = predict(model, X_sel(te, :)); acc(i) = sum(pred == y(te)) / numel(y(te)); end fprintf('5-Fold CV Accuracy: %.2f%%\n', mean(acc) * 100);这套流程里最关键的设计是先 mRMR 后交叉验证。如果你把 mRMR 放在整个数据集上跑完再划分训练测试集,特征选择过程已经偷看了测试集的信息,这叫数据泄漏,精度会虚高,答辩时被老师问一句就容易翻车。正确做法是特征选择只依赖当前折的训练数据,后面避坑章我再展开说。
3.2 mRMR 与 src:核心搜索算法放在哪个文件
mRMR文件夹下面是算法的全部家当,src子文件夹里是核心函数,data.mrmr是运行产生的中间文件,保存了特征排序结果。data.mrmr这个文件值得你注意,它意味着你跑过一次之后,下次想换分类器重新验证,可以不重跑特征选择,直接读这个文件拿特征索引,省掉最耗时的那一步。
包里的实际函数名以mRMR/src为准,但核心逻辑就是我上面写的增量搜索。你自己实现时建议保持三个独立函数:互信息估计、特征对互信息矩阵、贪心搜索,分开的好处是出 bug 容易定位。互信息估计错了,后面全错;而互信息估计最容易出错的地方是概率估计时用hist而不是histcounts,老版本 MATLAB 里hist的边界处理和histcounts不一致,同一个特征在不同机器上跑出来排名不同,属于典型的玄学 bug。
3.3 getFeaturesWithIndex.m:把索引变回特征
mRMR 输出的不是特征本身,而是特征下标。下标这个东西在调试时非常容易把人绕晕——索引算出来了,但到底是哪几列?getFeaturesWithIndex.m干的就是这件事:传入原始数据矩阵和特征索引,返回抽好的特征子集。函数逻辑极短,就是一次矩阵索引:
function features = getFeaturesWithIndex(data, index) % data: 原始特征矩阵,每行一个样本 % index: mRMR 输出的特征下标向量 % features: 按 index 抽取出的特征子集,列数等于 index 长度 features = data(:, index); end这个封装本身没有技术难度,但它是好习惯。直接写X(:, idx)谁都会,问题在于索引语义不清晰:idx 是从 1 开始的列号还是从 0 开始的?是线性索引还是逻辑索引?封装成函数之后,接口语义固定,后面换数据、换特征选择方法时不用回头猜。我自己的习惯是拿到任何特征选择代码,第一件事就是写一个这种映射函数,哪怕只有一行。
3.4 svm 与 ppca:验证链路怎么闭合
svm文件夹里是分类验证部分。包里的 SVM 代码可能是老式svmtrain/svmclassify接口,也可能是 libsvm 封装。如果你用的 MATLAB 版本比较新(R2014a 之后),svmtrain已经被fitcsvm取代,直接跑test.m会报Undefined function 'svmtrain'。这不是包的问题,是版本差异,把训练和预测的调用换成fitcsvm+predict即可,参数上等价于老接口的默认设置。
ppca.m是概率主成分分析的实现。PCA 加 mRMR 是一条常见对比实验路径:先用 ppca 把高维数据压到低维,再对降维结果做特征选择或直接分类,用来回答"线性降维之后信息还在不在"这个问题。test_pca.m就是这条路径的完整脚本,和test.m形成对照实验。大作业报告里如果写了两个方法对比,那这组代码就是你复现报告数据的关键。
4. 把 mRMR 跑在你自己的数据集上:两份基因表达数据与完整流程
4.1 数据集长什么样
包里有test_lung_s3.csv和test_nci9_s3.csv两份基因表达数据。它们都是典型的"高维小样本"结构:每一行是一个样本,每一列是一个特征(基因),最后一列是类别标签。第一份是肺癌相关的表达谱数据,适合做二分类实验;第二份是 NCI9 细胞系数据,类别数更多,适合用来对比 mRMR 在多分类任务下的表现。
| 文件 | 场景 | 格式 | 用途 |
|---|---|---|---|
| test_lung_s3.csv | 肺癌表达谱 | CSV,行为样本、列为特征、末列为标签 | 二分类特征选择 + SVM 验证 |
| test_nci9_s3.csv | NCI9 细胞系 | CSV,行为样本、列为特征、末列为标签 | 多分类特征选择 + 分类器对比 |
| data.mrmr | mRMR 中间结果 | 自定义文本 | 保存特征索引,避免重复计算 |
用csvread读这两份文件时注意一个坑:MATLAB 的csvread要求文件里全是数值,如果标签列是字符串(比如'cancer'和'normal'),这个函数直接报错。包里的数据是数值编码过的,所以能读。如果你换成自己的数据,标签是字符串,得先用readtable读,再把标签列映射成 1、2、3 的整数编码,否则csvread这一步就卡住了。
4.2 跑通全流程的完整步骤
拿到包之后,推荐的执行顺序是:先跑原来的test.m,确认环境没问题,再动数据。下面是我按包内风格整理的完整运行流程:
# 1. 解压并进入项目目录 unzip Feature-Selection-master.zip cd Feature-Selection-master # 2. 用 MATLAB 打开 test.m,直接运行 # 如果遇到 svmtrain 不存在,把 fitcsvm 替换进去 matlab -batch "run('test.m')"在 MATLAB 里逐步执行的核心代码段:
% 读取肺腺癌数据集 data = csvread('test_lung_s3.csv'); % 分离特征和标签 X = data(:, 1:end-1); y = data(:, end); % 查看数据维度,确认高维小样本结构 fprintf('样本数: %d, 特征数: %d\n', size(X, 1), size(X, 2)); % mRMR 选 20 个特征 [feat_idx, ~] = mrmr_select(X, y, 20); % 查看选中了哪些特征(基因) fprintf('选中的特征索引: '); fprintf('%d ', feat_idx); fprintf('\n'); % 用 getFeaturesWithIndex 抽特征子集 X_sel = getFeaturesWithIndex(X, feat_idx); % 检查抽出来的特征矩阵大小是否正确 assert(size(X_sel, 2) == 20, '特征子集维度不对');这段代码跑完后,你会看到选出来的是 20 个特征下标,而不是特征名。如果想看特征名,需要额外读一个基因名文件,包里没带,但这不影响实验——特征选择实验关注的是排序和精度,不是具体的基因名。
4.3 输出怎么解读:特征索引、score 与 SVM 精度
mRMR 返回的两个输出要分清:feat_idx是特征下标,按选择顺序排,越靠前越重要;score是每轮的目标函数值,也就是I(x_j;c) - mean(I(x_j;x_i))的数值。score 有可能出现负值——当某个特征与已选特征冗余度超过它和标签的相关性时,目标函数就是负的,这说明这个特征"信息被前面的特征覆盖了",选它反而有害。
SVM 交叉验证精度是最终的判断依据。但只看平均精度不够,还要看五折之间的波动。如果五折精度分别是 98%、60%、99%、55%、97%,均值看着还行,实际是模型在个别折上崩了,通常意味着某个折里的测试样本分布和训练集差异大,或者是样本量太小导致划分不稳定。遇到这种情况,改cvpartition(y, 'Kfold', 5)为'Leaveout'留一验证,每个样本单独当一次测试集,虽然慢,但结果稳定得多。
4.4 换到自己的数据:关键改动点
把包里的数据换成自己的,只改三处,其余不用动。第一处是读文件的地方,路径换成你自己的 CSV;第二处是标签编码,保证 y 是 1 到类别数的整数;第三处是特征选择数量 K,这个参数值后面单独讲怎么定。其他所有代码逻辑完全不用改。
% 换成自己的数据 data = csvread('my_data.csv'); X = data(:, 1:end-1); y = data(:, end); % 检查标签是不是连续整数,不是则重新编码 % unique(y) 应该返回 1、2、3... 这样的序列 [~, ~, y] = unique(y); % 跑 mRMR,K 先随便给 20,后面再优化 [feat_idx, score] = mrmr_select(X, y, 20); % 分类验证 X_sel = getFeaturesWithIndex(X, feat_idx);换数据后最容易出的问题:你的数据维度如果比包里的两份数据低很多,比如只有 50 个特征,那K=30会选出近三分之二的特征,mRMR 的"最小冗余"约束几乎不起作用。这种情况下把 K 降到 10 以内,或者直接减少候选特征数量再看效果。
5. mRMR 实战避坑:互信息估计、归一化与 SVM 参数
5.1 互信息算出来全是 0 或彼此几乎相同
现象:跑完mutual_info,所有特征的互信息值都接近 0,排序结果几乎是随机的,和直接随机选特征没有区别。
原因:连续变量直接拿去算互信息,没有做离散化,或者分箱数设得太小。另一个常见原因是原始数据量纲差异过大,比如某些特征取值在 0~1 之间,另一些在 0~10000 之间,跨特征的分箱边界不一致,概率估计完全失真。
解决:先对每个特征做 min-max 归一化,再送进互信息计算;分箱数从 20 起步,分别试 10、20、30,看选出的特征排名是否稳定。我在 2.1 节给的那个mutual_info函数里已经内置了归一化,但如果你自己实现,很容易漏这一步。归一化之后务必再检查一次特征值范围,确认在 [0,1] 内再继续。
5.2 SVM 精度波动大,同一个特征集两次结果差很远
现象:保持代码不变,连续跑两次test.m,SVM 精度差了 5 到 8 个百分点,你开始怀疑特征选择是不是有随机性。
原因:交叉验证的划分是随机的,没有固定随机种子。每次运行cvpartition都会重新划分数据,不同折上的样本分布不一样,精度自然不一样。mRMR 本体没有随机性,但验证环节的随机性让人误以为整个流程不稳定。
解决:在交叉验证前加rng(42),把随机种子固定下来。种子选什么数字不重要,重要的是固定。同时把随机种子设成和test.m里一致的值,这样你复现报告里的精度数据时能对上号。我处理毕设项目时习惯把种子写在脚本最顶部,注释标明"修改此值可得到不同划分,但提交版本固定 42"。
5.3 svmtrain 不存在导致整个流程跑不起来
现象:点击运行test.m,MATLAB 报错svmtrain is not found或者Undefined variable svmtrain,整个验证链路直接中断。
原因:包里用的是老版本 SVM 接口,而新版本 MATLAB 用fitcsvm替代了svmtrain。你装了新版本 MATLAB,接口对不上,不是代码问题。
解决:把svmtrain换成fitcsvm,把svmclassify换成predict。参数上,fitcsvm(..., 'Standardize', true)对应老接口里的-c和缩放设置。注意fitcsvm的输入要求特征矩阵在前、标签在后,和老接口的调用顺序完全一致。包里的 SVM 代码如果还有svmtype这类老参数,一并删掉,新版不支持。
5.4 K 随手设 50,精度反而比 K=20 低
现象:特征数从 20 加到 50,交叉验证精度不升反降,过拟合迹象明显。
原因:mRMR 的增量搜索里,越往后选的特征与标签的相关性越弱,同时与已选特征的冗余度越来越高。K 设得过大,后期入选的特征本身就没有多少判别力,纯粹是凑数。对高维小样本数据,特征数超过样本数的十分之一,过拟合风险就开始陡增。
解决:把 K 当成超参数调,而不是随手填。做法是画一条特征数-精度曲线,取曲线上升后首次进入平台期的拐点作为最终 K 值。具体代码在下一章的 6.2 节给出。至少记住一条经验法则:K 不超过 min(50, 样本数/5),数据只有 40 个样本时,K 超过 8 就要开始警惕。
5.5 在整份数据上跑完 mRMR 再划分交叉验证
现象:交叉验证精度高达 98%,换到独立测试集上崩到 60%,老师问起来答不上来。
原因:特征选择在全部样本上运行,相当于在划分训练测试集之前就让 mRMR"看过"测试集的分布。SVM 验证时,测试集里的信息和特征选择过程存在间接泄漏,精度虚高。这种错误在学生的毕设答辩里出现频率极高,属于典型的数据泄漏翻车现场。
解决:在每一折交叉验证内部重新跑 mRMR。外层循环划分数据,内层先对训练部分做特征选择,再用选出的特征训练分类器,最后在测试部分上评估。代价是每折都要重跑一遍 mRMR,慢,但结果可信。代码模板:
rng(42); cv = cvpartition(y, 'Kfold', 5); acc = zeros(cv.NumTestSets, 1); for i = 1:cv.NumTestSets tr_idx = training(cv, i); te_idx = test(cv, i); % 关键:只对训练部分做特征选择 [idx, ~] = mrmr_select(X(tr_idx, :), y(tr_idx), 20); model = fitcsvm(X(tr_idx, idx), y(tr_idx), 'Standardize', true); pred = predict(model, X(te_idx, idx)); acc(i) = sum(pred == y(te_idx)) / numel(y(te_idx)); end这样跑出来的精度才是实际泛化性能的可靠估计。代价是每次交叉验证要重算一次特征排序,但数据集维度在几千以内时,这个开销完全可接受。
6. 进阶:ppca 降维、特征数曲线与一套可复用的验证管线
6.1 先用 ppca 降维再跑 mRMR:两条路线的对比
包里同时给了ppca.m和test_pca.m,这是一条完整的对比实验路径。常见做法是先跑直接 mRMR 得到精度,再用 ppca 把数据压到低维空间,对降维后的特征跑同样的分类流程,最后对比两条路线的精度差异。这组对照能说明线性降维是否保留了关键判别信息,也是大作业报告里的核心实验内容。
% 方案 A:直接 mRMR [feat_a, ~] = mrmr_select(X, y, 20); model_a = fitcsvm(X(:, feat_a), y, 'Standardize', true); % 方案 B:先 ppca 降维再 mRMR d = 30; % 降到 30 维 [~, X_ppca] = ppca(X, d); % 具体接口以包内注释为准 [feat_b, ~] = mrmr_select(X_ppca, y, 20); model_b = fitcsvm(X_ppca(:, feat_b), y, 'Standardize', true);结果通常有几种情况:ppca 降维后再选特征,精度大致持平但计算快一个数量级;或者精度明显下降,说明原始特征空间里存在非线性结构,线性降维损伤了信息;还有可能 ppca 后精度反而更高,因为降维本身起到了去噪作用。不管哪种结果,你都能在报告里讲出一个合理的解读,这才是对比实验的价值。
6.2 特征数 K 的选择:画特征数-精度曲线找拐点
前面避坑章里说了 K 不能拍脑袋,现在给具体做法。把 K 设成一组候选值,对每个 K 跑一遍完整的交叉验证,画出精度曲线,选第一个进入平台期的 K:
ks = 5:5:50; acc_mean = zeros(size(ks)); acc_std = zeros(size(ks)); rng(42); for t = 1:numel(ks) cv = cvpartition(y, 'Kfold', 5); temp = zeros(cv.NumTestSets, 1); for i = 1:cv.NumTestSets tr = training(cv, i); te = test(cv, i); [idx, ~] = mrmr_select(X(tr, :), y(tr), ks(t)); model = fitcsvm(X(tr, idx), y(tr), 'Standardize', true); pred = predict(model, X(te, idx)); temp(i) = sum(pred == y(te)) / numel(y(te)); end acc_mean(t) = mean(temp); acc_std(t) = std(temp); end % 打印每个 K 对应的精度和标准差 for t = 1:numel(ks) fprintf('K=%d, Acc=%.2f±%.2f%%\n', ks(t), acc_mean(t)*100, acc_std(t)*100); endK 从 5 到 30 精度上升,30 到 50 进入平台期,就取 K=30。如果曲线一直上升不拐弯,说明数据本身信息量大或者样本量足够,可以继续加大 K 范围重跑。如果曲线上升后明显下降,那就是过拟合信号。精度曲线图贴到大作业报告里,比直接说"选了 30 个特征"有说服力得多。
6.3 把 mRMR 锻造成一套固定验证管线
最后的建议是把 mRMR 做成你所有高维小样本项目的标准前置环节,而不是一次性用完就扔的脚本。
我从自己做课设的经验里总结的固定流程是:引入数据 -> 归一化 -> 方差过滤粗筛 -> mRMR 精筛 -> 特征数曲线定 K -> 交叉验证内重新跑特征选择 -> SVM/随机森林对比。这套管线里每一步都有明确输出,跑到哪一步出了问题,你都能知道该查哪里。那次我用全特征直接跑 SVM 翻车之后,就强制自己每次遇到高维数据先走一遍 mRMR,再谈分类器的事。从那以后我每次做特征选择实验,都会强制先跑一遍mrmr_select看排名前二十的特征稳不稳定,再决定后续模型怎么搭。这份资源把整条链路都配齐了,照着跑一遍,再换自己的数据走通一次,比看十篇理论文章都有用。希望帮到你。
本文还有配套的精品资源,点击获取