首页 / 资讯中心 / 文章详情

Relief特征选择算法原理与MATLAB实现:从权重更新到实战避坑

Relief特征选择算法原理与MATLAB实现:从权重更新到实战避坑 ★ FEATURED ARTICLE
简介面向高维数据特征筛选与模型效率优化需求此压缩包提供Relief特征选择算法的MATLAB完整实现适合机器学习、数据挖掘方向的学生、研究人员以及需要做特征预处理的技术人员。Relief算法通过反复比较查询样本与同类、异类近邻的特征差异来更新特征权重能够有效处理多类别分类问题尤其适用于生物信息学、医学诊断等高维小样本场景。压缩包共19个文件约1.32MB文件类型以.m脚本为主涵盖特征标准化、权重更新、距离计算、去冗余等核心环节同时附带.mat中间数据、.xls胃癌样本集、.fig结果图及数据说明文档便于对照学习和二次开发。整体目录清晰可直接在MATLAB中按流程运行。目前已有734人学习下载。借助配套的胃癌症例数据与中间结果文件读者可以完整复现从数据预处理、特征权重迭代更新到重要特征排序输出的全过程既能用于理解Relief算法原理也能迁移到自己的特征选择任务中是一份兼顾教学与实战的可靠参考资料。1. Relief 特征选择一个不算新的算法为什么还能当主力工具做特征选择的老手都知道拿到两百个特征先别急着调参先用 Relief 跑一遍比什么都快。Relief 是过滤式特征选择里少见的“不训练模型也能打分”的算法按每个特征在同类近邻和异类近邻上的差异自动排出一个重要性顺序。它不吃样本量维度再高也能在几分钟内出结果非常适合故障诊断、生物信息这类“样本少、特征多”的项目前期筛查。下面把 Relief 的原理、MATLAB 实现、参数设置和踩过的坑一次讲透新手能照着复现熟手也能在边界条件上少走弯路。2. 先吃透 Relief 的原理一个近邻的直觉为什么要写成权重2.1 同类该像、异类该不像Relief 的核心直觉Relief 的出发点非常朴素。假设你有 200 个特征、100 个健康样本和 100 个故障样本现在想知道哪些特征真正能把两类分开。Relief 会随机抽一个锚点样本在特征空间里找到离它最近的同类样本再找到离它最近的异类样本。如果某个特征上同类邻居很近、异类邻居很远说明这个特征对区分类别有贡献应该加分反过来如果某个特征在同类邻居之间也忽大忽小那它多半是噪声应该减分。这个思路不需要训练任何分类器也不假设特征服从正态分布或线性可分。它只看“局部邻居关系”所以能捕捉到一些线性方法看不出来的判别信息。放在特征工程之特征选择的框架里Relief 属于典型的过滤式方法先排序、再筛选完全不依赖你后面用 SVM 还是随机森林。这也是它和相关系数法、方差法的本质区别——那些方法只单独看每个特征和标签的统计关系而 Relief 看的是特征在样本邻域中的表现。2.2 权重更新公式和它背后的取舍整个算法浓缩成一行权重更新式W W - (x_i - x_hit).^2 (x_i - x_miss).^2其中 x_i 是锚点样本x_hit 是同类的最近邻x_miss 是异类的最近邻。平方项对差异大的特征更敏感一个特征如果在异类邻居上差出两个数量级它获得的加分远大于那些差异较小的特征。这个设计让 Relief 天然关注“判别力强”的特征而不是“有点区别”的特征。原始 Relief 有两个很硬的限制只能处理二分类近邻个数固定为 1。每次随机抽一个锚点重复 m 次后把所有更新量取平均得到的就是每个特征的最终权重。这里有个工程上的取舍为什么不直接拿全部样本两两计算距离因为 n 个样本的全量距离矩阵是 n×n当 n 到万级、特征到几百维时内存和时间都受不了。Relief 用随机抽样换效率m 足够大时统计意义上权重会收敛到稳定值。2.3 MATLAB 里为什么这套算法特别好写我自己在 MATLAB 里实现 Relief 的主要原因有三个。第一向量化写起来顺手找最近邻只需要sum((X - xi).^2, 2)一行min取索引find按类别分组全是基础函数不依赖任何商业工具箱也能跑。第二后续验证闭环短Relief 选完特征直接在同一工作空间里接fitcknn做交叉验证特征矩阵不用导出成文件再导回来。第三可视化方便权重排序后bar一画拐点在哪、噪声特征在哪一眼就能看出来。很多人在 MATLAB 里做特征选择会陷入一个误区先装一堆工具箱再找现成函数。实际上 Relief 的核心逻辑就三十行左右自己写反而更可控因为你能精确知道每个参数在干什么。这也是我推荐从最小实现入手的原因而不是直接去找封装好的黑匣子。3. 用 MATLAB 写一个能跑的 Relief最小代码和两个必调参数3.1 最小可运行版本二分类、近邻 k1下面这个函数只做一件事输入特征矩阵 X、标签 y、抽样次数 m输出每个特征的重要性权重。function w relief_min(X, y, m) % 最小版 Relief只支持二分类近邻个数固定为 1 % X: n x d 特征矩阵 % y: n x 1 标签只能取 0 或 1 % m: 随机抽样的迭代次数一般取样本数 n % 返回 w: 1 x d 权重向量越大代表该特征越重要 X (X - mean(X, 1)) ./ max(std(X, 1), eps); % z-score 标准化 n size(X, 1); d size(X, 2); w zeros(1, d); rng(0); % 固定随机种子保证结果可复现 for t 1:m i randi(n); % 随机抽一个锚点 same find(y y(i)); % 同类样本索引 diff find(y ~ y(i)); % 异类样本索引 same(same i) []; % 去掉锚点自身 ds sum((X(same, :) - X(i, :)).^2, 2); % 到同类样本的平方距离 dd sum((X(diff, :) - X(i, :)).^2, 2); % 到异类样本的平方距离 [~, p] min(ds); % 同类最近邻 [~, q] min(dd); % 异类最近邻 nh same(p); nm diff(q); % 权重更新同类差异减分异类差异加分 w w - (X(i, :) - X(nh, :)).^2 ... (X(i, :) - X(nm, :)).^2; end w w / m; % 取平均 end代码逻辑上sum((X(same,:) - X(i,:)).^2, 2)用矩阵广播一次性算出锚点到所有同类样本的距离比 for 循环快很多。min返回最近邻索引same(p)和diff(q)再映射回原始样本编号。权重更新的加减顺序是核心减去同类距离的平方加上异类距离的平方二者都在同一个特征维度上逐个计算。调用示例也用合成数据说明rng(0); X randn(500, 50); % 500 个样本50 个特征 y double(sum(X(:, 1:10), 2) 0); % 只有前 10 个特征与标签相关 w relief_min(X, y, 500); [~, idx] sort(w, descend); idx(1:10) % 查看权重最高的 10 个特征编号运行之后你会发现前 10 个特征里大概率出现大部分真实相关特征1 到 10其余位置被随机噪声特征占据。这就是 Relief 在干的事不保证每次排序完美但能让真实特征稳定地浮到最前面。提示MATLAB 里写中文注释建议把文件编码存成 UTF-8否则换一台机器打开就是乱码。这个问题在 R2023 之后的版本里依然会遇到存文件时手动选 UTF-8 是最稳的做法。3.2 两个核心参数怎么定m 和 k最小版里只有一个迭代次数 m但实际用的往往是 ReliefF它多了一个近邻数 k。这两个参数直接决定结果质量。参数作用常用设置m随机抽样次数决定权重稳定性等于样本数 n样本上万时取 5000 也够k每类取几个近邻参与权重更新5 到 10样本少时取 3 到 5距离度量如何定义“近邻”标准化后的欧氏距离离散特征多时用曼哈顿距离m 太小权重波动大随机抽到几个离群点就可能带偏排序m 取到 n 以上后边际收益急剧下降纯属浪费计算时间。k 则是 Relief 和 ReliefF 拉开差距的地方k1 就是原始 Relief对局部噪声敏感k 增大相当于把“最近的一个邻居”换成“一类邻居的平均意见”更稳健但也会抹掉边界样本的判别信号。我的习惯是默认 k10如果样本量少于 200降到 5。3.3 权重排序怎么变成特征名单拿到权重向量 w 之后第一步永远是画图而不是直接卡阈值。[sorted_w, idx] sort(w, descend); figure; bar(sorted_w); xlabel(按重要性排序的特征序号); ylabel(Relief 权重);画完图你会看到一个典型形态前几个特征权重明显高后面断崖式下跌再往后进入一条平缓的尾巴。特征数量可以按三种方式确定一是看曲线的“肘部”也就是斜率突变的位置二是累计权重达到总权重 80% 时的特征数三是结合业务经验直接取前 20 或前 50。但我必须提醒一点Relief 的权重只是相对排序不是“这个特征贡献了 15%”这种可解释的占比别拿去做特征重要性的定量解释。4. 从 Relief 到 ReliefF 和 RReliefF多分类、回归场景的落地写法4.1 超过两个类别时原始 Relief 为什么失效如果标签不是 0/1而是四种故障类型原始 Relief 的更新公式就有问题了。它把所有“异类”混在一起找最近邻相当于默认只有一个决策边界。四分类时类 A 和类 B 的区别可能由特征 1、2 决定类 A 和类 C 的区别由特征 3、4 决定混在一起之后权重会被多个边界的平均值拉平结果往往是谁都突出不了。ReliefF 的解决办法是按类别分开处理抽到锚点后同类里找 k 个近邻更新“减分项”其他每个类别各自找 k 个近邻乘上该类的先验概率再累加到“加分项”。这样不同类别的判别特征可以同时得到高分互不压制。4.2 ReliefF 的 MATLAB 核心循环function w relieff_custom(X, y, m, k) % 多分类 ReliefFy 从 1 到 C % 每个锚点同类取 k 近邻其他每个类别各取 k 近邻按类别先验加权 X (X - mean(X, 1)) ./ max(std(X, 1), eps); n size(X, 1); C max(y); w zeros(1, size(X, 2)); rng(0); for t 1:m i randi(n); same find(y y(i)); same(same i) []; if isempty(same), continue; end d_same sum((X(same, :) - X(i, :)).^2, 2); [~, ord] sort(d_same); hit_idx same(ord(1:min(k, numel(ord)))); % 同类 k 近邻 for c 1:C if c y(i), continue; end % 跳过锚点自身类别 diff_c find(y c); d_diff sum((X(diff_c, :) - X(i, :)).^2, 2); [~, ord] sort(d_diff); miss_idx diff_c(ord(1:min(k, numel(ord)))); % 该类 k 近邻 p_c numel(diff_c) / n; % 类别先验概率 w w - sum((X(i, :) - X(hit_idx, :)).^2, 1) ... p_c * sum((X(i, :) - X(miss_idx, :)).^2, 1); end end w w / (m * k); end这里有个细节值得说明标准 ReliefF 在加权时有的写法用 p_c有的写法用 p_c / (1 - p_yi)区别在于要不要把锚点自身类别的概率从分母里去掉。数据均衡时两者几乎一样类别不平衡时后半种写法对少数类更友好。我给的版本按 p_c 加权代码更直观排序结果在大多数情况下够用。w / (m * k)是平均操作因为每轮累加了 1 个 hit 项和 C-1 个 miss 项每项内部又包含 k 个近邻的求和所以要除以总近邻数才是一个无量纲的可比权重。4.3 RReliefF标签是连续值时的变体有时候你要选的不是故障类别而是某个连续指标比如剩余寿命、温度、能耗。RReliefF 就是针对回归场景的版本。它不再区分“同类”和“异类”而是直接取锚点的 k 个近邻计算近邻和锚点之间的标签差异用这个差异给特征距离加权。分量ReliefF分类RReliefF回归标签 y离散类别连续数值近邻划分同类一组、异类按类别分组统一取 k 个近邻更新依据同/异类特征差近邻标签差乘以特征差归一化除以迭代次数和 k除以所有近邻的总权重实现时核心循环比 ReliefF 简单抽锚点、取 k 近邻对每个近邻算标签差 tau再算特征差与 tau 的乘积并累加。这个值越大说明“标签变化剧烈时这个特征也变化剧烈”也就是该特征对预测标签有信息量。4.4 和互信息特征选择对比结果不一致时听谁的很多人会把 Relief 和互信息特征选择放在一起比较。两者都是过滤式都不训练模型但互信息衡量的是特征和标签之间的概率依存关系需要把连续特征离散化bins 取多少直接决定结果Relief 走的是近邻路线不需要设定 bins对连续特征更自然。实际项目中我常用的搭配是先用 ReliefF 从两三百个特征里筛出前五十再用互信息对这五十个做二次排序两个方法都认可的特征直接进模型有分歧的靠交叉验证裁决。互信息在 MATLAB 里实现要自己写离散化和信息熵计算比 Relief 多一层调参成本所以我不建议它做第一步的粗筛。5. 避坑Relief 特征选择最常见的 5 个翻车现场这一章写的都是我实际调试中踩过的坑每一条都按现象、原因、解决的顺序拆开讲。5.1 同样的数据跑两次特征权重完全不一样现象同一份 X 和 y上午跑出特征 A 排第一下午重跑变成特征 B 排第一结果没法交代。原因Relief 基于随机抽样randi每次生成不同的锚点序列。权重本身带有抽样噪声m 不够大时波动尤其明显。解决在调用 Relief 之前固定随机种子函数开头写rng(0)或者把种子作为参数传入。发布结论前最好连续跑五次观察排序的稳定性如果 top10 的特征在五次里经常变说明 m 太小加大迭代次数而不是换算法。5.2 不标准化大尺度特征直接霸榜现象某个特征取值在几千伏、几万转这种量级其它特征在 0 到 1 之间。Relief 跑完大尺度特征全部排在前面和领域经验严重不符。原因距离计算用的是欧氏距离大数值特征在距离中的占比天然就大。即使别的特征判别力更强也会被淹没在数值尺度里。解决进入算法前先做 z-score 标准化。注意要在 Relief 内部做而不是在外部改了数据再传进来这样能保证每次抽样迭代看到的都是同一套尺度。特征量纲差距极大或存在离群点时考虑换成曼哈顿距离它对尺度变化的敏感度更低。5.3 k 从 5 改成 20排名大变不知道听谁的现象k5 时特征 X 排第三k20 时直接掉到二十名开外两个结果看起来不像同一个算法跑出来的。原因k 控制的是“近邻视野”。小 k 只看局部能抓住边界附近的判别特征大 k 覆盖更大范围更容易受类别内部分布的影响。两套设置看到的数据形态不同排序自然不同。解决把 k 当作超参数来对待不要当作固定的默认值。小样本n300直接取 k5样本充足时取 kfloor(sqrt(n))然后跑 k5、10、20 三组取三组结果的 top20 特征交集作为最终候选集。这个交集通常比任何单组设置都稳。5.4 类别不平衡时少数类的判别特征被淹没现象1000 个样本里类别 1 有 950 个类别 2 只有 50 个。理论上类别 2 有很强的一组判别特征但 Relief 跑完这组特征排名普遍靠后。原因锚点是从全体样本里按均匀分布抽的抽样到类别 2 的概率只有 5%。它参与权重更新的次数太少贡献被类别 1 的样本淹没了。解决把抽样改成分层抽样。先按类别把样本拆开再保证每个类别至少被抽到相同次数。代码上做一点小改动idx1 randsample(find(y 0), 200, true); idx2 randsample(find(y 1), 200, true); anchor_idx [idx1; idx2];然后用anchor_idx(t)替代原来的randi(n)。这样每个类别都贡献相同数量的锚点少数类的判别特征就不会再吃亏了。5.5 按权重删掉“不重要的”特征模型反而变差现象Relief 排序出来最后 20 个特征权重接近 0删掉它们之后模型准确率不升反降。原因Relief 是过滤式方法它评价的是单个特征的独立判别力不识别冗余也不识别交互。某两个特征单独看都平淡无奇组合起来却能把类别分得很开这种情况 Relief 会误杀。解决Relief 只适合做初筛不要直接用它做最终特征集。正确姿势是先用 Relief 把上千维压到几十维再结合随机森林重要性或 LASSO 这类考虑交互和冗余的方法做二次筛选最后用交叉验证决定保留多少个。这也说明了为什么我不建议把 Relief 当成“一个函数调完就出结果”的黑匣子。6. 验证选出的特征行不行三个不依赖测试集的量化技巧6.1 权重衰减曲线帮你把特征数量卡在拐点特征数量到底取多少看权重图的肘部是最快的办法。把排序后的权重做一阶差分差值最大的位置就是曲线最陡的地方通常对应信息量的分水岭。[sorted_w, idx] sort(w, descend); grad diff(sorted_w); [~, knee] max(abs(grad)); feature_count knee 1;这个方法适合在项目第一天快速估算特征规模但它只看了权重曲线的形状没有验证模型效果所以定位是“快速估算”不是最终依据。6.2 用交叉验证对比特征子集选完特征最终要服务模型。一个很笨但可靠的做法是从 top1 开始逐个把特征加进分类器观察交叉验证精度在哪里到顶。这比任何理论公式都直接。rng(0); acc zeros(20, 1); for topk 1:20 Xsub X(:, idx(1:topk)); mdl fitcknn(Xsub, y, NumNeighbors, 5); acc(topk) 1 - kfoldLoss(crossval(mdl), LossFun, ClassifError); end plot(1:20, acc);这段代码需要 Statistics and Machine Learning Toolbox。精度曲线通常会快速上升然后在某个位置陷入平台期甚至下降——下降点就是特征数量的上界。注意数据量小的时候交叉验证本身也有方差多跑几次看趋势比盯单次数值更靠谱。6.3 置换检验算显著性Relief 给了一个权重值但你不知道这个值是不是“随机碰巧”跑出来的。置换检验的思路是把标签随机打乱重跑一遍 Relief得到一组纯噪声下的权重分布。如果原始权重远高于噪声分布说明它是真的。perm_w zeros(200, size(X, 2)); for b 1:200 yp y(randperm(numel(y))); perm_w(b, :) relieff_custom(X, yp, 100, 10); end pval mean(max(perm_w, [], 2) max(w));这个 p 值是启发式的别拿去论文里当严格显著性检验但用来快速发现“这个权重高得可疑还是真货”很够用。我在实际项目里把这套流程固定成脚本模板固定种子、记录 k 和 m、保存每次的权重排序最后用交叉验证曲线收尾。否则三个月后回看单一张权重图根本说不清这组特征从哪来、参数是什么。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站