ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Relief算法特征权重选择:MATLAB工程落地完整拆解

Relief算法特征权重选择:MATLAB工程落地完整拆解 简介这份MATLAB资料包聚焦Relief算法的特征权重选择面向具备一定MATLAB编程基础、正在做毕业设计或数据挖掘竞赛的学生与研究人员帮助解决高维数据中冗余特征多、模型解释性差的问题。包内共22个文件以m脚本、mat数据、xls表格为主辅以asv备份、txt说明与fig图形文件压缩包约693KB涵盖算法实现、数据集、主调用脚本与结果可视化等模块。Relief通过迭代比较同类与异类近邻在各特征上的差异来更新权重高权重特征对分类贡献更大可据此筛选特征、降低过拟合风险并减少计算开销。目前已有158人学习下载。读者可据此理解算法原理与实现流程掌握特征排序、权重分析及结果展示思路并将其迁移到基因表达、医学数据等实际场景中。1. Relief 算法做特征权重选择一份 MATLAB 工程落地的完整拆解拿到一份数据几十上百个特征列直接丢进分类器训练结果往往不是过拟合就是训练慢得离谱。这时候需要有人告诉你哪些特征真正在起作用哪些纯属噪声。Relief 算法就是干这个的——它给每个特征打一个权重分分数越高说明这个特征对区分样本越有用。MATLAB 环境下实现 Relief 做特征权重选择核心工作分三块数据预处理、权重迭代计算、按权重排序做特征子集筛选。这套流程适合做分类任务前的特征工程尤其是特征维度在 20 到 200 之间、样本量几千到几万的中等规模数据集。特征数太少没必要跑太多的话 Relief 的计算开销也会上来但相比包裹法Wrapper动辄要反复训练模型Relief 作为过滤法Filter的效率优势非常明显。下面从原理到代码到踩坑一步步拆开讲。2. Relief 算法的权重机制与 MATLAB 实现路径2.1 Relief 凭什么给特征打分近邻差值的直觉Relief 的核心思想可以用一句话概括一个好的特征应该让同类样本靠得近让不同类样本离得远。具体怎么做对每一个训练样本算法找到它的两个邻居——同类最近邻Near Hit和异类最近邻Near Miss。然后比较每个特征在这两个邻居上的差异如果某个特征在同类近邻上的差值小同类样本该特征值接近在异类近邻上的差值大不同类样本该特征值差距大这个特征就被认为有区分力权重加分。反过来如果某个特征在同类近邻上差异大、在异类近邻上差异小权重就减分。这个加减的过程在每个样本上重复一遍最终每个特征累积出一个权重值。原始 Relief 只适用于二分类后来扩展的 ReliefF 支持多分类做法是对每个异类都找 k 个近邻按类别概率加权。MATLAB 中没有内置的 ReliefF 函数relieff在 Statistics and Machine Learning Toolbox 里有但很多人拿不到这个工具箱所以实际工程中经常需要自己写。自己写的好处是可控距离度量方式、近邻数量 k、迭代次数 m、特征归一化策略全部可以按数据特点调。坏处是要处理一些边界情况后面避坑章节会细说。2.2 手写 ReliefF 的 MATLAB 代码框架下面这份代码是一个可运行的 ReliefF 实现输入是特征矩阵和标签向量输出是每个特征的权重。代码里对多分类、归一化、近邻搜索都做了处理。function [weights, ranked_idx] reliefF_weights(X, Y, k, m) % reliefF_weights 基于 ReliefF 算法计算特征权重 % 输入: % X - 特征矩阵, 大小为 n_samples x n_features % Y - 标签向量, 大小为 n_samples x 1, 可以是数值或 categorical % k - 近邻数量, 通常取 5 到 10 % m - 迭代次数(抽样次数), 通常取样本数或略少 % 输出: % weights - 1 x n_features 的权重向量 % ranked_idx - 按权重降序排列的特征索引 [n_samples, n_features] size(X); % 标签统一转为 categorical, 方便处理多分类 if ~iscategorical(Y) Y categorical(Y); end classes categories(Y); n_classes numel(classes); % 特征归一化: ReliefF 对量纲敏感, 必须做 min-max 归一化 X_norm (X - min(X)) ./ (max(X) - min(X) eps); % 初始化权重 weights zeros(1, n_features); % 预计算距离矩阵(样本量不大时可行, 大了要改用 knnsearch) % 这里用欧氏距离 D pdist2(X_norm, X_norm, euclidean); for iter 1:m % 随机选一个样本 idx randi(n_samples); label_i Y(idx); % 找同类近邻 (Near Hit) same_class_idx find(Y label_i); same_class_idx(same_class_idx idx) []; % 排除自己 if isempty(same_class_idx) continue; end [~, sorted_same] sort(D(idx, same_class_idx)); k_same min(k, length(sorted_same)); near_hit same_class_idx(sorted_same(1:k_same)); % 找异类近邻 (Near Miss), 对每个异类分别找 near_miss_all {}; for c 1:n_classes if classes{c} label_i continue; end diff_class_idx find(Y classes{c}); if isempty(diff_class_idx) continue; end [~, sorted_diff] sort(D(idx, diff_class_idx)); k_diff min(k, length(sorted_diff)); near_miss_all{end1} diff_class_idx(sorted_diff(1:k_diff)); end % 更新权重 % Near Hit 贡献: 减去特征差异 for j 1:n_features diff_hit abs(X_norm(idx, j) - X_norm(near_hit, j)); weights(j) weights(j) - sum(diff_hit) / k_same; end % Near Miss 贡献: 加上特征差异(按类别概率加权) for c 1:length(near_miss_all) nm near_miss_all{c}; % 该异类样本占总异类样本的比例 prob_c length(nm) / (n_samples - length(same_class_idx) - 1); for j 1:n_features diff_miss abs(X_norm(idx, j) - X_norm(nm, j)); weights(j) weights(j) prob_c * sum(diff_miss) / length(nm); end end end % 归一化权重到 [-1, 1] 区间(可选, 方便比较) if max(weights) ~ min(weights) weights (weights - min(weights)) / (max(weights) - min(weights)) * 2 - 1; end % 按权重降序排列 [~, ranked_idx] sort(weights, descend); end这段代码的逻辑说明先做 min-max 归一化因为 ReliefF 用欧氏距离找近邻如果某个特征量纲是几千而另一个是零点几距离计算会被大量纲特征主导。然后对每次迭代随机抽一个样本分别找同类和异类近邻按公式更新权重。最后把权重归一化到 [-1, 1] 方便观察。参数方面k控制近邻数量太小对噪声敏感太大则局部性减弱我一般从 5 开始试数据噪声大就加到 10。m是迭代次数理论上越大越稳定但计算量线性增长实践中取样本数的 1 到 2 倍通常够用。如果样本量超过一万pdist2算全距离矩阵会吃内存这时候要改用knnsearch逐次查询。2.3 用 relieff 内置函数做交叉验证如果你的 MATLAB 装了 Statistics and Machine Learning Toolbox可以直接用relieff省去手写的麻烦而且它内部做了更多优化。调用方式如下% 假设 X 是 n x p 特征矩阵, Y 是标签向量 % k 为近邻数, 通常取 10 [k_weights, k_ranked] relieff(X, Y, 10); % 查看权重排序 disp(特征权重排序:); disp(table(k_ranked, k_weights(k_ranked), ... VariableNames, {FeatureIndex, Weight})); % 选前 15 个特征做后续建模 top_n 15; selected_features k_ranked(1:top_n); X_selected X(:, selected_features);relieff的第三个参数是 k第四个参数可以指定method为classification或regression。做分类时它会自动处理多分类的权重更新。注意relieff返回的权重已经做了归一化直接比较大小即可。内置函数和手写版本的区别在于内置函数对大数据做了分块处理内存占用更可控手写版本则方便你改距离度量比如换成曼哈顿距离或余弦距离。如果数据特征维度特别高比如上千维建议用内置版本因为手写版的pdist2在千维以上会非常慢。3. 从权重到特征子集筛选策略与验证方法3.1 权重排序后怎么定阈值三种实用策略拿到权重向量之后下一个问题就是选多少个特征这不是拍脑袋决定的有三种常用策略。第一种是固定数量法。按权重降序排列取前 N 个。N 怎么定可以看权重曲线的拐点——把权重从大到小画出来找下降明显变缓的位置。比如前 10 个特征权重都在 0.5 以上第 11 个突然掉到 0.1那 10 就是一个自然截断点。第二种是阈值法。设定一个权重阈值比如只保留权重为正的特征。因为 ReliefF 的权重含义是“该特征对区分的贡献”负权重意味着这个特征可能引入噪声。但要注意归一化之后权重为负不一定就是坏事得结合数据看。第三种是递增验证法。从 top-1 开始每次增加一个特征训练一个简单分类器比如 KNN 或决策树看验证集准确率的变化。当准确率不再提升甚至下降时就停止增加。这个方法最稳但计算量也最大。% 递增验证法示例 max_features min(30, size(X, 2)); accuracies zeros(max_features, 1); cv cvpartition(Y, KFold, 5); % 5 折交叉验证 for n 1:max_features feat_idx k_ranked(1:n); X_sub X(:, feat_idx); % 用 KNN 做快速评估 mdl fitcknn(X_sub, Y, NumNeighbors, 5, ... CVPartition, cv); accuracies(n) 1 - kfoldLoss(mdl); end % 找最佳特征数 [best_acc, best_n] max(accuracies); fprintf(最佳特征数: %d, 交叉验证准确率: %.4f\n, best_n, best_acc); % 画图观察 figure; plot(1:max_features, accuracies, -o, LineWidth, 1.5); xlabel(特征数量); ylabel(交叉验证准确率); title(递增特征选择验证曲线); grid on;这段代码的核心是cvpartition做交叉验证划分保证每次评估用的是相同的折避免随机波动干扰判断。fitcknn的CVPartition参数直接接受划分对象省去手动循环。跑完之后看accuracies曲线如果准确率在某个点之后趋于平缓那个点就是比较合适的特征数。3.2 用分类器验证特征子集的实际效果选好特征子集之后必须用独立的测试集验证效果。这里给一个完整的对比流程原始全特征 vs ReliefF 筛选后特征用同一个分类器做对比。% 划分训练集和测试集 rng(42); % 固定随机种子, 保证可复现 cv_holdout cvpartition(Y, HoldOut, 0.3); X_train X(training(cv_holdout), :); Y_train Y(training(cv_holdout)); X_test X(test(cv_holdout), :); Y_test Y(test(cv_holdout)); % 在训练集上计算 ReliefF 权重 [k_w, k_r] relieff(X_train, Y_train, 10); % 全特征模型 mdl_full fitcknn(X_train, Y_train, NumNeighbors, 5); pred_full predict(mdl_full, X_test); acc_full sum(pred_full Y_test) / length(Y_test); % 筛选后特征模型(取前 15 个) top_n 15; X_train_sel X_train(:, k_r(1:top_n)); X_test_sel X_test(:, k_r(1:top_n)); mdl_sel fitcknn(X_train_sel, Y_train, NumNeighbors, 5); pred_sel predict(mdl_sel, X_test_sel); acc_sel sum(pred_sel Y_test) / length(Y_test); fprintf(全特征准确率: %.4f (%d 维)\n, acc_full, size(X, 2)); fprintf(筛选后准确率: %.4f (%d 维)\n, acc_sel, top_n);注意这里 ReliefF 权重是在训练集上算的不能在测试集上算——否则就是数据泄露。这个细节很多人会忽略导致验证结果虚高。另外rng(42)固定随机种子是为了让结果可复现实际项目中可以多跑几次取平均。如果筛选后准确率比全特征还高说明去掉的特征确实在引入噪声如果准确率略降但维度大幅减少那要看你的目标是什么——如果后续要部署到资源受限的环境降维带来的收益可能比那一点准确率损失更值。4. 避坑与排查ReliefF 在 MATLAB 里的五个血泪教训4.1 现象权重全是负数或全接近零原因特征没有做归一化或者归一化方式不对。ReliefF 用距离找近邻如果某个特征数值范围是 0 到 10000另一个是 0 到 1距离计算完全被大量纲特征主导其他特征的权重更新几乎为零。另外如果数据里有大量重复值或常数特征归一化时分母接近零也会出问题。解决在调用 ReliefF 之前对所有特征做 min-max 归一化或 z-score 标准化。常数特征直接删掉。检查代码里归一化那一步有没有加eps防止除零。4.2 现象每次运行权重排序都不一样原因ReliefF 每次迭代随机抽一个样本如果不固定随机种子结果会有波动。样本量越小波动越明显。解决在调用前加rng(固定值)。如果数据量小建议把m设大一些比如样本数的 3 到 5 倍让随机抽样的覆盖更充分。另外可以多次运行取权重平均值这样更稳定。4.3 现象某个重要特征权重却很低原因ReliefF 只考虑特征单独与标签的关系不考虑特征之间的交互。如果两个特征高度相关它们会分摊权重单独看每个都不高。另外如果重要特征和标签的关系是非线性的而近邻距离用的是欧氏距离也可能捕捉不到。解决对高度相关的特征做相关性分析如果两个特征相关系数超过 0.9可以只保留权重较高的那个。对于非线性关系可以尝试对特征做变换比如取对数、平方后再跑 ReliefF或者换用基于互信息的特征选择方法做交叉验证。4.4 现象多分类时某些类别的特征权重异常原因ReliefF 在多分类时对每个异类找近邻如果某个类别的样本数极少它的近邻质量很差权重更新会被这些不可靠的近邻带偏。类别不平衡是常见诱因。解决对少数类做过采样或者在计算 Near Miss 时按类别样本数加权代码里prob_c那一步就是做这个的但权重设计可以再调。另外可以检查一下每个类别的样本数如果某个类少于 10 个样本考虑合并类别或收集更多数据。4.5 现象跑得特别慢内存爆了原因手写版本用pdist2算全距离矩阵样本量一万时矩阵是 10000x10000double 类型占 800MB样本量再大直接崩。另外每次迭代都重新排序找近邻重复计算严重。解决改用knnsearch逐次查询近邻不存全矩阵。或者用 MATLAB 的KDTreeSearcher对象加速。如果样本量超过五万建议先随机抽样一部分样本算权重再在全量数据上验证。内置relieff函数对大数据的处理更好能用就用内置的。5. 权重稳定性检验与增量更新技巧ReliefF 算出来的权重到底可不可信一个实用的检验方法是把数据随机分成两半各自独立跑 ReliefF然后看两组权重的排序相关性。如果 Spearman 相关系数在 0.8 以上说明权重稳定低于 0.5 就要警惕了可能是样本量不够或者特征噪声太大。% 权重稳定性检验: 分半验证 n size(X, 1); half floor(n / 2); idx_perm randperm(n); X1 X(idx_perm(1:half), :); Y1 Y(idx_perm(1:half)); X2 X(idx_perm(half1:end), :); Y2 Y(idx_perm(half1:end)); w1 relieff(X1, Y1, 10); w2 relieff(X2, Y2, 10); % Spearman 秩相关系数 rho corr(w1(:), w2(:), Type, Spearman); fprintf(分半权重 Spearman 相关系数: %.4f\n, rho); if rho 0.8 disp(权重稳定, 可以信任排序结果); elseif rho 0.5 disp(权重中等稳定, 建议增加样本量或增大 k); else disp(权重不稳定, 检查数据质量或改用其他特征选择方法); end这个检验花不了多少时间但能帮你避免在一个不稳定的权重排序上做决策。我现在的习惯是任何特征选择方法跑完之后都做一次分半稳定性检验过不了这一关就不往下走。另一个实用技巧是增量更新。实际项目中数据是分批来的每次新来一批数据就重新跑全量 ReliefF 太浪费。可以维护一个权重累积向量新数据来了只在新样本上跑迭代然后按样本量加权平均到已有权重上。这样既能跟上数据分布的变化又不用每次从头算。% 增量更新权重(伪代码思路) % weights_cum 是累积权重, n_cum 是已处理样本数 % X_new, Y_new 是新来的批次 w_new reliefF_weights(X_new, Y_new, k, size(X_new, 1)); n_new size(X_new, 1); % 加权平均更新 weights_cum (weights_cum * n_cum w_new * n_new) / (n_cum n_new); n_cum n_cum n_new;注意增量更新有个前提新旧数据的分布不能差太远。如果数据分布发生了明显漂移累积权重会滞后这时候还是得全量重算。判断分布漂移可以用简单的均值方差对比或者跑一个 KS 检验。最后说一个我自己的习惯每次做完特征选择不管用什么方法都会把选出来的特征子集和原始特征列表并排打出来人工过一遍。有时候算法觉得不重要的特征从业务逻辑上看其实很关键——这种时候我会把它强行保留然后在后续验证中观察它到底有没有用。算法是辅助不是替代判断。希望帮到你。本文还有配套的精品资源点击获取
返回列表