十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

递归特征消除优化:突破贪心局限,提升模型性能

递归特征消除优化:突破贪心局限,提升模型性能 1. 项目缘起当特征选择遇上“维度诅咒”做机器学习或者数据分析的朋友肯定都遇到过这个经典难题手头的数据集有几十上百个特征但你知道其中肯定有不少是冗余的、不相关的甚至是噪声。一股脑儿全扔进模型里不仅训练慢如蜗牛模型还容易过拟合泛化能力一塌糊涂。这就是所谓的“维度诅咒”。特征选择就成了我们模型优化流程里绕不开的一环。常见的特征选择方法比如基于统计检验的过滤法Filter、基于模型权重的包裹法Wrapper比如经典的递归特征消除RFE或者嵌入法Embedded如Lasso大家或多或少都用过。但不知道你有没有遇到过这种情况当你使用像递归特征消除RFE这类包裹法时它确实能给你一个特征重要性排序告诉你“从全量特征开始每次剔除最不重要的那个直到达到目标特征数”。这个过程听起来很合理但实际操作中尤其是特征间存在复杂共线性时这个“每次剔除一个”的贪婪策略可能会陷入局部最优。它可能早早地就把一些单独看效果不突出、但组合起来威力巨大的特征给扔掉了最终得到的特征子集可能并不是全局最优的那个“黄金组合”。最近在优化一个风控模型时我就被这个问题卡住了。用传统RFE跑出来的特征集AUC指标总是差那么一点点调来调去都不满意。于是我开始琢磨有没有办法对RFE这个过程本身进行“优化”不是优化RFE里的基模型参数而是优化它“递归消除”这个搜索策略。这就是今天想跟大家聊的“递归消除法优化”。它不是发明一个新算法而是针对现有RFE框架的痼疾通过引入更灵活的搜索策略和评估机制让我们有更大机会找到那个真正优秀的特征子集。这就像给一把好刀开了刃让它用起来更顺手、更高效。2. 递归特征消除RFE的“阿喀琉斯之踵”在谈优化之前我们得先彻底理解标准RFE是怎么工作的以及它的痛点在哪里。这样优化起来才能有的放矢。2.1 标准RFE流程拆解标准的递归特征消除其核心思想可以概括为“反向淘汰”。假设我们初始有N个特征。全模型训练用所有N个特征训练一个基模型比如SVM、随机森林、逻辑回归等。这个模型需要能输出特征的重要性度量例如线性模型的系数绝对值、树模型的特征重要性feature_importances_、或者基于模型性能的排列重要性。重要性排序根据上一步得到的特征重要性对所有特征进行排序。剔除最不重要特征剔除掉排名最靠后的一个或一小批特征。递归迭代用剩下的N-1个特征重复步骤1-3。终止与输出重复上述过程直到剩余特征数量达到我们预设的目标值k。最终我们会得到一系列嵌套的特征子集包含N个特征的集合、N-1个特征的集合……直到k个特征的集合。通常我们会通过交叉验证从这一系列子集中选择在验证集上性能最好的那个作为最终输出。这个过程听起来很清晰但问题就隐藏在步骤2和3的“贪婪性”里。2.2 贪婪策略的局限与共线性陷阱RFE的“每次剔除最不重要”是一个典型的贪心算法。贪心算法的特点是每一步都选择当前看来最优的选项期望以此导向全局最优。但在特征选择这个高维、非线性、特征间相互关联的复杂空间里贪心策略很容易走错路。一个简单的思想实验假设我们有三个特征X1, X2, X3。其中X1和X2高度相关共线性它们俩携带的信息几乎一样X3则提供独立的信息。一个理想的模型可能只需要X1或X2加上X3就够了。标准RFE可能这样走第一轮训练由于X1和X2高度共线模型可能会“平均”分配重要性给它们比如在线性模型中它们的系数可能会变得不稳定且值较小而X3的重要性很清晰。于是RFE可能判定X1或X2是“最不重要”的并将其剔除。结果我们剩下了{X2, X3}或{X1, X3}。看起来没问题但如果X1和X2的共线性导致模型在第一轮就错误地低估了它们而实际上{X1, X2}这个组合在后续与其他特征交互时可能有奇效呢RFE没有机会验证这一点因为它在第一轮就把其中一个“误杀”了。更普遍的问题是特征重要性是依赖于当前特征子集的上下文的。当一个特征被剔除后剩余特征的重要性排名会立刻发生变化。某个特征在包含所有特征时看起来不重要但在某个较小的子集里可能是关键先生。标准RFE这种“一次定生死”的剔除方式没有给特征“第二次机会”。此外**剔除的“粒度”**也可能是个问题。每次只剔除一个特征固然精细但计算成本极高尤其当特征数量很多时。而如果为了加速每次剔除一批比如最不重要的10%又可能因为“批量误杀”而错过优质特征组合。3. 优化策略一引入“回溯”与“宽搜”机制既然问题是贪心策略可能错过全局最优解那么最直接的优化思路就是引入一定的“探索”能力减少“贪心”的短视。这里我分享两种在实践中比较有效的策略。3.1 带回溯的递归消除RFE with Backtracking这个想法借鉴了图搜索算法中的回溯思想。我们不完全信任单次排序就决定特征的“死刑”而是允许在后续迭代中重新考虑之前被剔除的特征。基本操作流程如下运行标准RFE记录下整个剔除路径比如路径是 [剔除A] - [剔除B] - [剔除C] - ... - 得到特征集S_k。从最终的特征集S_k开始尝试将最近几轮被剔除的特征比如最后剔除的m个特征例如C, B, A逐个或组合地重新加入S_k。每加入一个特征都用交叉验证评估新特征子集的性能。如果加入某个特征后验证性能有显著提升超过预设的阈值如AUC提升0.005则保留这个特征更新当前最优子集。可以迭代进行多轮回溯不仅回溯最后一步还可以回溯到更早的步骤。Python实现示意from sklearn.feature_selection import RFE from sklearn.model_selection import cross_val_score from sklearn.ensemble import RandomForestClassifier import numpy as np def rfe_with_backtracking(estimator, X, y, n_features_to_select, backtrack_steps3, cv5): 带回溯的RFE estimator: 基模型 X, y: 特征和标签 n_features_to_select: 目标特征数 backtrack_steps: 回溯步数 cv: 交叉验证折数 # 1. 首先运行标准RFE记录剔除顺序 rfe RFE(estimator, n_features_to_select1, step1) # step1确保每次剔除一个便于记录 rfe.fit(X, y) # RFE的ranking_属性中排名为1的是最后被选中的排名越大越先被剔除。 # 我们需要按剔除顺序排列特征索引从先剔除到后剔除 ranking rfe.ranking_ feature_indices np.arange(X.shape[1]) # 按ranking降序排列得到剔除顺序从最早剔除到最晚剔除 elimination_order feature_indices[np.argsort(-ranking)] # 最终选中的特征索引 (ranking 1) current_selected feature_indices[ranking 1] # 2. 回溯阶段 # 获取最后被剔除的 backtrack_steps 个特征的索引 last_eliminated elimination_order[:backtrack_steps] best_score cross_val_score(estimator, X[:, current_selected], y, cvcv, scoringroc_auc).mean() best_set current_selected.copy() for feature_idx in last_eliminated: # 尝试加入这个被剔除的特征 candidate_set np.sort(np.append(best_set, feature_idx)) score cross_val_score(estimator, X[:, candidate_set], y, cvcv, scoringroc_auc).mean() if score best_score 0.001: # 设置一个最小提升阈值 print(f回溯发现特征 {feature_idx} 有益性能从 {best_score:.4f} 提升至 {score:.4f}) best_score score best_set candidate_set return best_set, best_score # 使用示例 # estimator RandomForestClassifier(n_estimators100, random_state42) # best_features, best_auc rfe_with_backtracking(estimator, X_train, y_train, n_features_to_select20, backtrack_steps5)为什么有效它缓解了标准RFE“一次剔除永无翻身”的问题。特别是当特征间存在复杂依赖或共线性时某个特征在“大部队”里表现平平但在一个精干的“小分队”里可能至关重要。回溯机制给了这些特征一个“复活赛”的机会。3.2 随机递归特征消除Randomized RFE这是另一种增加探索性的方法灵感来自随机森林本身的随机性。我们在每一轮剔除特征时不总是剔除“最不重要”的那个而是以一定的概率从“不重要特征池”中随机选择一个剔除。操作流程训练基模型得到所有特征的重要性分数。将特征按重要性排序但不像标准RFE那样直接剔除最后一名。将排名后p%的特征例如后20%放入一个“候选剔除池”。从这个池子里随机选择一个特征进行剔除。可以给排名越靠后的特征越高的被剔除概率例如按排名加权随机。重复迭代。Python实现核心逻辑import numpy as np from sklearn.utils import check_random_state def randomized_rfe(estimator, X, y, n_features_to_select, step1, prob_pool_ratio0.2, random_stateNone): 随机化RFE prob_pool_ratio: 每一轮从重要性排名后 prob_pool_ratio 比例的特征中随机选择剔除。 rng check_random_state(random_state) n_features X.shape[1] support_ np.ones(n_features, dtypebool) # 当前支持的特征掩码 ranking_ np.ones(n_features, dtypeint) * (n_features 1) # 记录剔除轮次 current_step 1 while np.sum(support_) n_features_to_select: # 用当前特征子集训练模型 estimator.fit(X[:, support_], y) # 获取重要性 (这里以feature_importances_为例需根据模型调整) if hasattr(estimator, feature_importances_): importances estimator.feature_importances_ elif hasattr(estimator, coef_): importances np.abs(estimator.coef_.ravel()) else: raise ValueError(Estimator does not have feature_importances_ or coef_ attribute) # 获取当前支持特征的重要性 curr_feat_indices np.where(support_)[0] curr_importances importances # 对当前特征按重要性排序升序重要性小的在前 sorted_indices_relative np.argsort(curr_importances) sorted_indices_global curr_feat_indices[sorted_indices_relative] # 确定候选剔除池排名靠后的 prob_pool_ratio 比例特征 pool_size max(1, int(len(sorted_indices_global) * prob_pool_ratio)) candidate_pool sorted_indices_global[:pool_size] # 注意sorted_indices_relative是升序所以前面的是重要性小的 # 随机从池中选择一个特征剔除 if len(candidate_pool) 0: to_eliminate rng.choice(candidate_pool) support_[to_eliminate] False ranking_[to_eliminate] current_step current_step 1 else: break # 设置最终选中特征的排名为1 ranking_[support_] 1 return support_, ranking_ # 使用示例最终 support_ 为布尔数组表示特征是否被选中为什么有效它通过引入随机性打破了贪心算法严格的局部最优路径依赖。多次运行随机化RFE可能会得到不同的特征子集然后我们可以取这些子集的并集或者选择平均交叉验证性能最好的那个。这实际上是在用计算资源换取更大的搜索空间覆盖率更有可能逼近全局最优解。4. 优化策略二重构评估与终止准则标准RFE的另一个潜在问题是它的评估和终止准则相对僵化。通常我们要么预设要保留的特征数k要么观察交叉验证得分曲线来选择一个k。但我们可以做得更精细。4.1 基于统计显著性的提前终止在递归剔除过程中我们每一轮都有一个性能得分比如交叉验证的AUC均值。标准做法是等所有轮次跑完选分数最高的。但我们可以更早地发现“性能平台期”或“下降点”从而提前停止节省大量计算。方法使用统计检验来判断性能下降是否显著。例如记录一个滑动窗口内的性能得分比如最近5轮。当新的一轮得分与这个窗口的历史得分相比其下降程度通过了统计显著性检验如配对t检验p值0.05我们就可以认为性能出现了显著恶化从而停止剔除并选择性能下降前的最优子集。from scipy import stats def rfe_early_stopping(estimator, X, y, min_features5, window_size5, pval_threshold0.05, cv5): n_features X.shape[1] support_ np.ones(n_features, dtypebool) scores_history [] # 记录每一轮剔除后的CV得分 while np.sum(support_) min_features: # 计算当前特征子集的性能 score cross_val_score(estimator, X[:, support_], y, cvcv, scoringroc_auc).mean() scores_history.append(score) # 检查是否满足提前停止条件 if len(scores_history) window_size 1: recent_scores scores_history[-(window_size1):] # 包含当前轮次 # 将当前轮次得分与之前窗口期的平均得分进行比较配对检验需同一折的结果这里简化处理 # 更严谨的做法是保存每一折的得分进行配对t检验 window_avg_before np.mean(scores_history[-(window_size1):-1]) current_score scores_history[-1] # 简单使用阈值判断如果当前得分低于窗口期平均得分一定阈值则停止 # 更复杂的可以模拟一个检验这里用简单阈值示例 performance_drop window_avg_before - current_score if performance_drop 0.005: # 假设AUC下降超过0.005认为显著 print(f提前停止在{np.sum(support_)}个特征时性能下降{performance_drop:.4f}) # 回退到上一轮的特征集即scores_history中得分最高的对应的特征集 # 注意这里需要记录每一轮对应的support_代码略复杂仅示意逻辑 break # 执行下一轮剔除这里需要实现特征重要性计算和剔除逻辑略 # ... 找到最不重要特征并更新 support_ ... # 返回历史最佳得分对应的特征子集 return best_support, best_score这个方法的核心价值在于效率。对于特征数很多的场景可能早在剔除到一半时性能就稳定或开始下降了后续的剔除都是无用功。提前终止能节省大量计算时间。4.2 多指标融合评估我们通常只用一个指标如AUC、准确率来评估特征子集。但有时单一指标可能无法全面反映子集的质量。例如AUC可能持平但模型的校准度Calibration或在不同阈值下的业务效用如捕获率可能发生了变化。优化点设计一个复合评估函数。比如综合得分 w1 * AUC w2 * (1 - Brier Score) w3 * 业务效用分数在每一轮递归中我们用这个综合得分来评估特征子集并据此决定剔除哪个特征或何时停止。权w1, w2, w3需要根据具体业务目标来设定。这要求基模型不仅能输出预测还能输出预测概率并且我们需要定义清晰的业务效用函数。虽然实现起来更复杂但对于业务导向强的项目如风控、营销这种优化能让特征选择直接对齐最终的业务KPI。5. 优化策略三基模型与重要性度量的选择艺术RFE的性能极度依赖于基模型及其提供的特征重要性度量。选错了基模型优化策略再好也是事倍功半。5.1 基模型的选择稳定性优先不是所有模型都适合做RFE的基模型。理想的基础模型应该满足能提供可靠的特征重要性度量树模型随机森林、梯度提升树的feature_importances_基于不纯度减少或覆盖度相对稳定。线性模型Lasso、逻辑回归的系数coef_直观但对共线性敏感。训练速度较快因为RFE要反复训练模型几十上百次。对超参数不太敏感我们不希望因为基模型超参数没调好导致重要性排序失真。我的经验是中小规模数据特征数1000随机森林是首选。它本身对共线性不敏感提供的重要性度量相对稳健训练速度尚可。注意设置足够的n_estimators如500和min_samples_leaf如5来增加稳定性。高维稀疏数据如文本特征线性SVMLinearSVC或逻辑回归带L1或L2正则可能更合适。它们处理高维稀疏数据效率高系数可以解释为重要性。使用L1正则化本身就有特征选择作用与RFE结合效果可能更好。需要捕捉复杂非线性关系梯度提升树如XGBoost, LightGBM非常强大。但要注意GBDT模型更容易过拟合如果数据量不大在RFE的每一轮都使用GBDT可能会导致重要性评估方差较大。此时可以考虑使用交叉验证下的特征重要性例如在每一轮用5折交叉验证训练5个模型取重要性分数的平均值来增加评估的稳定性。5.2 重要性度量的“陷阱”与改进即使选对了模型重要性度量本身也有坑。树模型的“偏见”树模型如随机森林倾向于给高基数取值多的连续特征或分类特征分配更高的重要性。这不一定代表该特征预测能力更强可能只是因为它有更多分裂机会。共线性下的“波动”对于高度相关的特征线性模型的系数会非常不稳定重要性排序也就不可靠。树模型虽然好一些但相关特征的重要性会被“稀释”。优化方法使用排列重要性Permutation Importance这是更稳健的重要性度量方法。它对模型本身没有假设通过随机打乱某个特征的值观察模型性能下降的程度来衡量其重要性。虽然计算成本高但在RFE的关键轮次比如最后几十个特征的精筛阶段使用可以大大提高选择质量。Scikit-learn提供了sklearn.inspection.permutation_importance。SHAP值SHAP值基于博弈论能提供一致且可解释的特征贡献度。它可以精确到每一个样本给出每一个特征的贡献值。我们可以用所有样本上某个特征SHAP值的平均绝对值作为全局重要性度量。SHAP值能更好地处理特征交互比传统的重要性度量更可靠。当然计算成本也是最高的。实操建议可以采用“混合策略”。在RFE前期特征很多时使用快速的、基于模型内置的重要性如随机森林的feature_importances_进行粗筛。当特征数减少到一定范围比如50个以下时切换到更稳健但更耗时的排列重要性或SHAP值进行最后几轮的精细剔除和排序。这样在精度和效率之间取得平衡。6. 实战案例优化风控模型特征选择最后分享一个我最近在信贷风控模型中的实战案例把上面几种优化思路串起来用。背景原始特征约300个包括用户画像、行为数据、第三方数据等。存在大量共线性和噪声特征。目标是用逻辑回归模型构建一个申请评分卡要求特征数控制在15-20个以内便于解释和部署。初始尝试标准RFE基模型逻辑回归L2正则。结果最终选出的20个特征子集在测试集上AUC为0.725。但业务人员反馈一些他们认为重要的强规则特征没有被选入。分析问题逻辑回归对共线性敏感在标准RFE的贪心剔除中可能把一些业务强相关但统计上共线的特征过早剔除。优化方案实施第一轮随机森林粗筛 回溯基模型换为随机森林n_estimators200,max_depth10利用其抗共线性能力。采用随机化RFE(prob_pool_ratio0.3)运行5次得到5个不同的特征子集特征数约50个左右。对这5个子集取并集得到约80个候选特征。这一步的目的是“广撒网”避免早期贪心剔除造成的遗漏。第二轮逻辑回归精筛 提前终止在80个候选特征上使用逻辑回归作为基模型运行带回溯的RFE(backtrack_steps5)。评估指标采用复合指标综合得分 0.7 * AUC 0.3 * KSKS值在风控中也很关键。设置基于显著性的提前终止如果连续3轮综合得分下降且平均下降幅度超过0.003则停止。最终算法在保留22个特征时自动停止。回溯机制在此轮中成功“复活”了2个在第一轮随机RFE中被误剔除的业务关键特征。第三轮SHAP值验证与微调用这22个特征训练一个XGBoost模型不用于最终生产仅用于分析。计算该模型的SHAP值观察每个特征的全局重要性及其与目标的关系方向是否与业务逻辑一致。发现其中一个特征SHAP值很高但与业务逻辑相悖显示该特征值越高风险越低但业务经验认为应越高风险越高。经数据核查发现是该特征在数据预处理时编码错误。修正后重新运行第二轮的精筛过程。最终结果得到一组20个特征的子集。测试集AUC提升至0.738KS值也有改善。更重要的是最终的特征集不仅统计指标好而且每一个特征都能得到业务专家的合理解释模型的可解释性和业务接受度大大提升。核心心得不要迷信单一方法标准RFE是一个好工具但并非万能。将其视为一个基础框架根据你的数据特性和业务目标进行定制化优化效果天差地别。分层筛选是高效策略用快而糙的方法如随机森林RFE做大量特征的初筛再用慢而精的方法如逻辑回归RFE回溯高级评估在小范围候选集上做精筛。这是平衡效果与效率的黄金法则。业务逻辑是最终检验无论指标多好看特征选择的结果一定要拉上业务方一起Review。一个违背业务直觉的特征被选中很可能意味着数据、模型或流程中存在隐藏问题。SHAP这类可解释性工具是连接数据科学与业务理解的桥梁。优化是永无止境的本文提到的回溯、随机化、提前终止、多指标评估都是可以灵活组合的“插件”。你可以根据具体场景像搭积木一样构建最适合自己的特征选择流水线。记住目标不是找到数学上的绝对最优解那通常是NP难问题而是在有限的计算资源和时间内找到业务上可接受、性能上足够优秀的“满意解”。
返回列表