
简介面向MATLAB研究者的FSLib_v7.0.1_2020_2特征选择库专为高维数据预处理与机器学习建模前的降维需求而设计汇集过滤法、包裹法、嵌入法等多种经典与现代特征选择算法。过滤法可基于相关性或统计量快速评分包裹法通过搜索特征子集直接评估模型性能嵌入法则在模型训练过程中自动筛选重要特征适用于不同规模与类型的数据集。压缩包共含363个文件其中321个m脚本是算法主体覆盖实现代码、调用接口与示例程序另有10个fig图形文件辅助可视化以及若干C/C源文件与mex接口文件便于二次开发或编译加速。整体仅600KB轻量且便于部署。目前已有258人学习下载。使用该库可一站式完成特征评估、子集搜索与结果对比也能参考源码理解算法细节非常适合在MATLAB环境下开展特征选择实验、复现论文方法或处理真实数据集前的特征工程。对于高维稀疏场景合理选择特征还可显著减少冗余、缩短训练时间并提升模型可解释性。1. 特征选择库 FSLib为什么有人专门为 MATLAB 做一个包一份 2000 行、800 列的数据摆在面前先别急着丢进模型。这个阶段真正花时间的不是调参而是从几百个候选特征里找出和标签跑得动的那一小组。FSLib 就是干这个的工具包它把几十种特征选择算法收进同一个 MATLAB 工具箱装好后只需按统一格式传特征矩阵和标签就能在几分钟内对比 Filter、Wrapper、Embedded 好几条路线。适合手里有表格数据、正在做分类或回归预处理的人也适合嫌自己反复写相关系数、卡方、relief 的人。FSLib_v7.0.1_2020_2 这个压缩包名里7.0.1 是库版本2020-2 可以理解为维护者标记的 2020 年第二次更新批次很多 MATLAB 工具包都习惯用这种“库版本年份序号”的双层标记本质是在文件层面做发布管理。下面按使用习惯展开先讲怎么装、数据要塞成什么形状再跑通一个最小流程接着讨论算法选型和最常见的参数坑最后用交叉验证和稳定性检查确认这组特征值不值得进模型。2. FSLib_v7.0.1_2020_2 的安装、目录结构与数据格式2.1 把压缩包放进 MATLAB 搜索路径FSLib 不是一个.mlappinstall安装包也不是需要图形化安装的 App。它本质是一堆.m文件组成的函数库所以解压之后唯一要做的是把目录加进 MATLAB 搜索路径。我一般这样处理unzip(FSLib_v7.0.1_2020_2.zip); fslibPath fullfile(pwd, FSLib_v7.0.1_2020_2); addpath(genpath(fslibPath)); savepath第一行解压到当前工作目录第二行拼出绝对路径第三行用genpath把该目录下所有子目录一次性加进去第四行把当前搜索路径保存到pathdef.m。如果不执行savepath下次启动 MATLAB 还得重新addpath所以我建议在确认目录没有移动的情况下直接保存。savepath偶尔会报路径权限错误常见场景是 MATLAB 安装在C:\Program Files\MATLAB\...当前用户没有写权限。解决办法有两个一是把 FSLib 解压到userpath里比如C:\Users\你的用户名\Documents\MATLAB二是在 MATLAB 用户目录下的startup.m文件里写一行addpath(genpath(你的FSLib路径))每次启动自动加载。这两种方式都比去修改 MATLAB 安装目录权限省事。2.2 先看 examples再进 methods解压后的库通常按功能分成三类目录不同打包习惯会有差异但大方向一致目录或文件常见内容使用建议methods/每个特征选择算法一个.m文件用help查签名不要随便改源码utils/归一化、排序、交叉验证等辅助函数发生同名冲突时优先到这里确认examples/演示脚本和说明文档先完整跑通再替换成自己的 X 和 yREADME或Contents.m版本记录、更新日志安装后第一时间打开不要用run一口气跑完 examples 里所有内容我看过不少人是直接全选 F9结果变量名互相覆盖最后根本说不清是哪个算法选出来的特征。正确做法是逐段执行关注每段在做什么处理以及变量区里 X、y、k、score 的形状变化。查看实际文件列表ls(fullfile(fslibPath, methods)) ls(fullfile(fslibPath, examples))看到方法文件名之后用help确认函数签名。FSLib 中大部分方法都遵循“特征矩阵 X、标签 y、要选的特征个数 k”这个位置参数顺序返回值通常只有两个选中特征下标和对应评分。但不同版本的函数命名不一定一样同样的 mRMR 实现可能叫mrmr、mMRR或mrmr_mid所以help才是最终依据。2.3 数据形状样本在行特征在列FSLib 里的方法默认输入是二维数值矩阵X是 n×pn 是样本数p 是特征数y是 n×1 的列向量。分类任务的标签如果是字符串或 cell 数组需要先转成整数索引。从 CSV 读入时我习惯这样写T readtable(train_data.csv); X T{:, 2:end}; % 第一列是标签或样本ID yRaw T{:, 1}; if iscell(yRaw) || isstring(yRaw) y grp2idx(yRaw); else y double(yRaw(:)); endT{:, 2:end}用花括号取出 table 里的数值数据出来是 double 矩阵。yRaw(:)的意义是统一成列向量很多算法会对size(y, 1)和size(X, 1)做一致性检查行向量容易在边界处翻车。grp2idx会把字符串类别映射成整数同时返回一个类别列表后续画图或解释模型时可以用。FSLib 多数算法不会替你做缺失值处理所以进入任何选择方法之前先做两个断言assert(all(isfinite(X(:))), X 里存在 NaN 或 Inf); assert(size(X, 1) numel(y), 样本数与标签数不一致);第一句把矩阵展成一维向量检查所有元素第二句核对样本数。遇到少量缺失我会先用fillmissing或直接rmmissing按行删除避免特征选择阶段出现“某列只有 3 个有效值却拿到很高分数”的假象。2.4 2020-2 后缀到底是什么意思压缩包名里的2020_2不是算法代号它更像维护者在版本 7.0.1 基础上加的一个发布批次标记表示 2020 年的第二次发布。很多 MATLAB 工具包会在 File Exchange 页面写一个正式版本号压缩包内部再带年份序号方便本地归档和追溯哪天拿到一个 2020-2 的包和 2020-1 对比就知道更新了什么。拿到包之后先打开README或Contents.m看一眼更新日志。这个方法虽然很笨但能避免很多“代码明明照着别人教程抄的为什么报错”的问题因为对方的版本可能是 2020-1方法是旧签名。3. 用 FSLib 跑通第一个特征选择最小流程从原始矩阵到选中特征3.1 准备一个真实规模的输入起步数据不一定要多大但最好具备“特征数明显多于样本数”或“特征间存在冗余”这两个特点之一否则特征选择的意义不大。MATLAB 自带的ovariancancer数据就很合适216 个样本、4000 个特征而且标签就是两类load ovariancancer X obs; y grp2idx(grp); fprintf(样本数 %d特征数 %d\n, size(X, 1), size(X, 2));obs是 216×4000 的基因表达量矩阵grp是 216×1 的 cell 数组。如果你的 MATLAB 没有这个数据集或者压根不用 Statistics Toolbox也可以直接用任意一份自己的表格数据Section 2.3 的读入代码可以直接复用。重点是让 X 和 y 进入工作区时形状正确这一步花 5 分钟确认后面能省 50 分钟排错。提示如果你不确定当前工作区的 X 和 y 是否符合 FSLib 要求先不要调用算法直接whos X y看尺寸。3.2 跑 mRMR 选 20 个特征最小可运行流程我通常用 mRMR 开头原因是它同时考虑特征和标签的相关性以及特征之间的冗余度在高维数据上得到的特征集合比单独跑相关系数更均衡。代码就三行k 20; [selected, score] mrmr(X, y, k); disp(selected);selected是选中的特征下标比如[128 301 45 19 776 ...]这些下标直接对应X的列。score是每个选中特征进入序列时的得分通常不是最终全局排序分数值本身跨版本可能不可比但大小关系仍然能反映特征被选入的先后。由于 FSLib_v7.0.1_2020_2 里 mRMR 的函数命名可能在不同电脑上不完全一致调用前先做一次身份确认which mrmr -all如果 MATLAB 报 “mrmr not found”就在 methods 目录里找以mrmr开头的文件把函数名换成实际文件名再调用。如果which显示多个结果说明发生了同名冲突这是后面会专门处理的路径污染问题。拿到评分后建议直接画出来看分曲线figure; stem(score, filled); xlabel(Feature index); ylabel(FSLib score); title(mRMR feature scores);绝大多数真实数据里前面几个特征分数明显高后面会出现一个类似肘部的平台平台之后的特征对模型的边际贡献开始变小。平台出现的位置就是 k 的第一个参考值。这里不是说 k 必须取在肘部而是让用户对自己的数据特征分布有一个直观判断避免闭着眼睛设 k100。3.3 把选中的特征写回原始表格如果 X 是从 table 里拆出来的选完后还需要把特征名对应回去。假设原始表第一列是标签后面每列是一个特征varNames T.Properties.VariableNames; selectedNames varNames(selected 1); % 第一列是标签特征列从第 2 列开始 TSelected T(:, [1, selected 1]); writetable(TSelected, selected_features.csv);这里最容易错的是列偏移selected是从 X 的列号算的而 X 对应原表T{:, 2:end}也就是 X 的第 1 列是原表的第 2 列所以写回时一定要做1。如果漏了选中的特征名会整体错位一列最后写进 CSV 的是完全不同的特征。3.4 怎么判断这 20 个选得对不对先别急着追求最优精度我一般看三件事第一选出的特征和业务直觉是否一致比如做信贷评分时如果前 20 个特征里全是同一个渠道衍生变量就要怀疑冗余处理力度不够第二score 曲线是否在 k 附近出现明显下降第三后续模型在同样交叉验证条件下是否稳定。第三点放到最后一章专门做。如果 mRMR 的结果不满意还可以直接用评分中位数做阈值不固定 km median(score); selectedByMedian find(score m);这种做法的好处是不需要提前拍脑袋定 k坏处是遇到 score 分布扁平的场景时选出的特征数量可能超过预期。固定 k 和阈值法没有绝对优劣关键是把选择依据写进脚本注释后面回头审计时知道当时为什么这么设。4. FSLib 特征选择算法的选型Filter、Wrapper 和 Embedded4.1 三种路线到底在选什么很多刚开始用 FSLib 的人会把“特征选择”理解成一种操作实际它是一族差异很大的算法。FSLib 把算法按搜索策略和评价方式分了三大类Filter、Wrapper、Embedded。Filter 思路是独立地评估每个特征和标签之间的关系再按分数排序截取前 k 个。计算量最小也最适合特征数上千的场景。缺点是不考虑特征之间的冗余两个高度相关的特征可能同时排进前 20造成信息重复。Wrapper 把“特征子集”当成搜索对象用分类器或回归器的效果给每个子集打分典型的包括 SVM-RFE 和贪心前向选择。这类方法能更直接地反映最终模型需求但计算复杂度随特征数增长非常快4000 个特征跑完整 wrapper 在普通笔记本上可能要按小时计。Embedded 介于两者之间把特征选择嵌入模型训练过程比如带 L1 惩罚的逻辑回归、决策树分裂时的特征重要性。FSLib 里这类方法通常会要求指定一个基础模型或核函数参数使用门槛高一些但效率和效果通常比纯 wrapper 更均衡。算法族是否考虑特征间冗余计算成本适合场景Filter通常不考虑mRMR 是例外低高维数据粗筛保留前 100~500Wrapper由模型间接考虑高样本量中等特征数几百以内Embedded在训练中考虑中特征和标签关系明显非线性4.2 FSLib 里怎么快速对比多种方法FSLib 没有强制统一的调用入口但大多数方法的位置参数相似所以我会写一个薄封装函数把当前项目里常用的算法全部收敛到一个接口下function [idx, score] runFSLib(method, X, y, k) switch lower(method) case mrmr [idx, score] mrmr(X, y, k); case fisher [idx, score] fisher(X, y, k); case relieff [idx, score] relieff(X, y, k); otherwise error(未识别的算法: %s, method); end end这段代码本身没有任何智能逻辑它的价值是把方法名、参数格式、返回值统一成同一种形状这样后续循环对比、结果缓存、交叉验证都只需要处理一个函数签名。注意fisher和relieff的名字要以本地which结果为准如果你的版本里叫fisher_score或reliefF直接改 case 后面的字符串和对应函数调用即可。调用方式methods {mrmr, fisher, relieff}; selectedMap containers.Map(); for i 1:numel(methods) [idx, score] runFSLib(methods{i}, X, y, 20); selectedMap(methods{i}) idx; fprintf(%-8s 前3个特征: %s\n, methods{i}, mat2str(idx(1:min(3, end)))); endcontainers.Map在同一个循环里把每个方法选出的下标存起来避免后面还得重新跑一遍。mat2str把行向量转成可打印字符串min(3, end)防止 k 小于 3 时索引越界。循环结束后可以用intersect看两组特征重叠度比如 mRMR 和 Fisher 只重叠了 4 个这就提示数据里相关结构比较复杂单靠一个 Filter 方法不够需要进入 wrapper 验证。4.3 特征类型不匹配时的预处理FSLib 里大量方法基于欧氏距离、方差或协方差计算默认输入是连续数值。如果原表里有离散特征比如“学历等级”或“设备型号”直接用原始整数编码通常不合理因为整数编码会强行给类别制造排序关系。我一般用onehotencode做展开Tcat T(:, catCols); % catCols 是离散特征列号 Tenc onehotencode(Tcat, 2, Categories, all); Tnum T(:, setdiff(2:width(T), catCols)); X2 [Tnum{:,:}, Tenc];onehotencode第二个参数 2 表示沿列方向展开返回的Tenc是单列 table展开后每个类别占一列。数值列继续保留原始值最后拼成新的特征矩阵 X2。这里有一个容易被忽略的问题离散列里某个类别只出现了 1 次时one-hot 展开后那一列绝大多数是 0FSLib 的 Filter 方法可能把它当成“高分特异特征”实际只是一个离群点。遇到这种情况我会先做频次过滤出现次数小于 5 的类别先合并成“其他”。5. FSLib 的高频参数调整、运行时报错与结果不一致问题5.1 必调参数表FSLib 虽然把算法封装得接近“黑盒”但有几个参数几乎每个项目都要碰。我在不同数据集上反复用下来优先级最高的是下面这些参数常见取值对结果的影响判断方式k5100直接决定最终特征数量看 score 曲线的肘部位置距离度量euclidean、cosine影响 ReliefF、KNN 类算法同一数据集换距离对比稳定性归一化zscore、minmax量纲不一致时距离类算法结果失真特征标准差相差 3 个数量级时必做标签类型分类整数、连续值Filter 方法分回归和分类两套公式先用class确认 y 的类型不要把 k 设成一个固定值就完事。曲线下降平缓时k 多 10 个特征通常只会带来 1% 的精度变化但模型复杂度会明显增加。反过来说如果 score 曲线在 k15 处断崖式下跌那就没必要硬选 50 个。5.2 特征尺度与归一化基于距离的特征选择方法对尺度极其敏感。基因表达量可能是 0~10000年龄是 20~80体重是 40~120直接算欧氏距离时表达量的波动会完全淹没年龄和体重的影响。我先做一次 zscore 归一化X normalize(X, zscore);normalize按列计算均值和标准差每一列变成零均值、单位方差。注意如果某一列是常数zscore 会得到 NaN因为标准差为 0 时做了除法。后面任何 FSLib 方法碰到 NaN 都可能直接报错或返回空结果。稳妥写法X(:, var(X) eps) [];删除近似常数特征。这个操作要放在归一化之前否则标准差为 0 的列已经变成 NaN 了。5.3 路径污染和函数名冲突FSLib 踩坑率最高的地方不是算法参数而是 MATLAB 工具箱自带函数和 FSLib 里的同名函数打架。最典型的是relieffMATLAB 统计和机器学习工具箱自带一个relieffFSLib 里如果也放了一个同名文件which会显示多个which relieff -all出现多个结果时MATLAB 默认执行搜索路径里排在前面的那个而 FSLib 的路径是后添加的可能根本轮不到它执行。解决办法是把 FSLib 路径用-begin选项放到搜索路径最前面addpath(genpath(fslibPath), -begin);这样 FSLib 里的同名函数会优先被调用。这条命令会改变当前会话的搜索路径但不会永久影响 MATLAB 自带函数只要不savepath重启后恢复原样。如果你已经保存过旧路径配置先别急着restoredefaultpath那会把用户自己的路径也清掉我一般只在干净环境里这么做。5.4 常见报错对照FSLib 的报错信息经常不适合直接搜索因为报错点通常在内部函数而不是你的入口。最常见的几个原因和解法报错信息特征真实原因处理方式Index exceeds the number of array elementsk 大于特征列数k min(k, size(X, 2))Input must be a numeric matrixX 是 table 或 cellX table2array(X)Labels must be a column vectory 是行向量或列方向不对y y(:)NaN or Inf数据里有缺失或无穷值用isfinite检查并清洗我在所有 FSLib 调用之前都会放一段防御代码assert(ismatrix(X) isnumeric(X), X 必须是数值矩阵); k min(k, size(X, 2)); y y(:); assert(numel(y) size(X, 1), 样本数与标签数不一致);这四行不能提升算法精度但能把一半以上的低级问题拦在入口处让后面的报错信息真正指向算法参数。5.5 结果不一致随机性来源在哪FSLib 里不少方法带有随机性ReliefF 的邻居采样、wrapper 的初始子集、以及部分优化求解都会依赖随机数。每次运行选出的特征不完全一样这不是库坏了而是算法本身的随机机制。要让自己能复现结果至少在每个脚本开头固定随机数rng(42);固定种子只能保证同一台机器、同一版本 MATLAB 下可复现。不同平台或不同 MATLAB 版本之间随机数生成器的算法细节可能导致结果仍然不一致。这类不一致不能靠 rng 完全消灭只能接受并用多次运行统计重叠率来判断特征子集是否稳定。6. 特征选择结果验证交叉验证与特征稳定性6.1 用 5 折交叉验证对比“全部特征”和“FSLib 选中特征”特征选择是否有效最终要看模型泛化性能而不是算法打分多高。我会用分层 5 折交叉验证固定随机种子保证全特征和选中特征在完全相同的训练测试划分下对比rng(42); cvp cvpartition(y, KFold, 5); accFull zeros(cvp.NumTestSets, 1); accSel zeros(cvp.NumTestSets, 1); for fold 1:cvp.NumTestSets tr cvp.training(fold); te cvp.test(fold); mdlFull fitcdiscr(X(tr, :), y(tr), DiscrimType, pseudoLinear); accFull(fold) 1 - loss(mdlFull, X(te, :), y(te)); mdlSel fitcdiscr(X(tr, selected), y(tr), DiscrimType, pseudoLinear); accSel(fold) 1 - loss(mdlSel, X(te, selected), y(te)); end fprintf(全部特征准确率: %.2f%%\n, mean(accFull) * 100); fprintf(选中特征准确率: %.2f%%\n, mean(accSel) * 100);cvpartition的分层会尽量让每一折里的类别比例接近原始数据避免某个 fold 恰好没有少数类样本。fitcdiscr在特征数多于样本数时必须指定pseudoLinear否则协方差矩阵不可逆会直接报错。loss返回的是分类损失用1 - loss得到准确率。如果选中特征只有 20 个却能在 4000 个特征里拿到接近或者更高的准确率说明 FSLib 选择的特征是有效信号。如果反而大幅下降可能不是特征选择算法的问题而是基模型在低维空间需要重新调参比如逻辑回归或 LDA 在特征减少之后决策边界简单了原来的惩罚项可能突然过强。6.2 特征稳定性检查换个样本子集还是不是同一批特征交叉验证能说明“这组特征在这个数据集上是否有效”但回答不了“这组特征换成一批新数据还稳不稳”。做法是多次子采样再统计选中特征的重叠率rng(2020); overlap zeros(10, 1); numSel numel(selected); for r 1:10 idxSample randsample(size(X, 1), round(0.8 * size(X, 1))); [idxRep, ~] mrmr(X(idxSample, :), y(idxSample), numSel); overlap(r) numel(intersect(idxRep, selected)) / numSel; end fprintf(平均重叠率: %.2f\n, mean(overlap));randsample每次抽取 80% 样本mrmr重新选择同样数量的特征intersect计算两次选择的共同特征数量。平均重叠率在 0.8 以上说明这组特征对样本扰动不敏感可以放心交给下游模型在 0.5 以下时先别急着加大 k那往往不是特征数不够而是候选特征之间存在高度相关的“同义特征组”算法只是随机从组里挑了一个代表。重叠率持续低于 0.5 时先对选中特征算相关系数矩阵做层次聚类把强相关特征分到同一组再从每组挑代表特征重新进入交叉验证直接加大 k 只会让模型复杂度上升而稳定性没有实质改善。本文还有配套的精品资源点击获取