1. 项目概述:为什么特征选择是数据挖掘的“定海神针”
刚入行做数据挖掘那会儿,我总觉得模型效果不好,第一反应就是算法不够高级、参数没调好,或者数据量不够大。后来踩了无数坑才明白,很多时候问题出在起点上——你喂给模型的数据“原料”本身就有问题。这里说的“原料”问题,不是指数据脏,而是指你从成百上千个原始特征里,一股脑儿全塞进模型。这就像做一道菜,你把厨房里所有能找到的调料,盐、糖、酱油、醋、花椒、八角、甚至芥末,都按差不多的量倒进去,结果可想而知。特征选择,就是那个帮你从“调料架”里精准挑出最合适、最有效的那几味调料的过程。在Python数据挖掘的分析与清洗流程中,特征选择,或者说特征筛选,是决定模型性能上限、提升训练效率、甚至增强模型可解释性的关键一步,其重要性怎么强调都不为过。
很多人,尤其是新手,容易陷入一个误区:特征越多,模型能学到的信息就越多,效果应该越好。理论上似乎没错,但现实很骨感。无关特征和冗余特征就像噪音,会干扰模型捕捉真正的信号,导致模型变得复杂、脆弱且难以理解,这就是所谓的“维度灾难”。一个经过精心筛选的特征子集,往往能用一个更简单、更快速的模型,达到甚至超越使用全部特征时的效果。今天,我就结合自己多年的实战经验,为你系统性地梳理和汇总Python中那些真正好用、必须掌握的特征选择方法。我们会从原理出发,深入到每种方法的适用场景、Python实现细节以及那些只有踩过坑才知道的注意事项,目标是让你看完就能在自己的项目里用起来,避开我当年走过的弯路。
2. 特征选择的核心逻辑与三大方法论
在动手写代码之前,我们必须先理清特征选择背后的核心逻辑。它不是玄学,而是有严谨的数学和统计基础支撑的。所有的特征选择方法,归根结底都是为了回答一个问题:这个特征对预测目标变量到底有没有用,以及有多大用?根据评价特征“有用性”的方式以及与后续建模过程的耦合程度,业界通常将特征选择方法分为三大类:过滤法、包裹法和嵌入法。理解这三者的区别,是你正确选型的第一步。
2.1 过滤法:快刀斩乱麻的初筛
过滤法是最独立、最快速的一类方法。它的核心思想是:在训练模型之前,基于数据本身的统计特性,对每个特征进行评分和排序,然后按分数高低选择特征。它完全独立于任何机器学习算法,只关心特征与目标变量之间的相关性或关联强度。
为什么首选过滤法?因为它的计算成本最低。你只需要计算一些统计指标(如相关系数、卡方值、互信息),就能对特征有个初步的、宏观的了解。在项目初期,面对成百上千个特征,用过滤法进行一轮粗筛,可以迅速剔除掉大量明显无关或冗余的特征,将特征数量降低到一个可管理的范围,为后续更精细的包裹法或嵌入法节省大量时间。它就像淘金前的“筛沙”,先把大块的石头和明显的泥沙去掉。
常用过滤法指标详解:
- 方差选择法:这是最简单粗暴的方法。如果一个特征在所有样本上的取值几乎不变(方差接近于0),那它显然无法提供任何区分样本的信息。使用
sklearn.feature_selection.VarianceThreshold可以轻松实现。但这里有个大坑:方差受量纲影响极大。一个年龄特征(取值20-60)的方差,和一个年薪特征(取值200000-600000)的方差完全不在一个数量级。直接使用方差阈值,年薪特征几乎肯定会被保留,而年龄特征可能被误删。因此,在使用方差选择法前,必须对数据进行标准化或归一化处理,消除量纲影响。 - 相关系数法:用于衡量连续型特征与连续型目标之间的线性相关程度。常用皮尔逊相关系数。
pandas的.corr()方法或scipy.stats.pearsonr函数可以计算。绝对值越接近1,线性相关性越强。但它的局限也很明显:只能检测线性关系。如果特征与目标之间存在复杂的非线性关系,皮尔逊相关系数可能会很低,从而误判该特征无用。 - 卡方检验:专用于衡量分类特征与分类目标之间的关联性。它检验的是“特征取值与目标取值是否独立”的假设。卡方值越大,独立性假设越不成立,即特征与目标关联越强。
sklearn.feature_selection.chi2可以直接计算。切记,卡方检验要求特征和目标都是非负的,通常需要先对特征进行分箱处理,将其离散化。 - 互信息法:这是过滤法中的“瑞士军刀”,非常强大。互信息衡量的是两个变量之间的相互依赖程度,它不仅能捕捉线性关系,还能捕捉任何形式的非线性关系。对于特征X和目标Y,互信息I(X;Y)表示知道了X的值后,Y的不确定性减少了多少。
sklearn.feature_selection.mutual_info_classif(用于分类)和mutual_info_regression(用于回归)是利器。它的优点是无参数、能捕捉非线性,缺点是计算量比相关系数和卡方检验要大一些。
实操心得:我通常的流程是,先做标准化,然后用方差法去掉“僵尸特征”(方差为0或极小)。接着,根据问题是分类还是回归,选用卡方或互信息对所有剩余特征进行评分排序。我会保留排名前K个的特征,或者保留所有分数超过某个阈值的特征。这个K或阈值没有黄金标准,我一般会画一个“特征分数-排名”的折线图,寻找那个分数开始急剧下降的“拐点”,作为初步筛选的界限。
2.2 包裹法:让模型自己当裁判
如果说过滤法是“纸上谈兵”,那么包裹法就是“实战演练”。它的核心思想是:将特征子集的选择过程与最终的机器学习模型训练过程捆绑在一起。通过不断尝试不同的特征组合,并用模型在验证集上的性能(如准确率、AUC)作为评价标准,来寻找最优的特征子集。
为什么需要包裹法?因为过滤法有个致命弱点:它评价的是单个特征的好坏,而模型最终使用的是特征组合。一个单独看与目标相关性不强的特征,可能与其他特征组合起来会产生强大的交互效应,对模型提升巨大。反之,两个单独看都很强的特征,可能因为高度共线性,同时放入模型反而会降低稳定性。包裹法通过模型的真实表现来评价特征子集,理论上能找到全局更优解。
经典包裹法:递归特征消除递归特征消除是包裹法中最常用、最实用的策略,在sklearn中对应RFE和RFECV类。它的工作流程像一个淘汰赛:
- 用所有特征训练一个模型(这个模型必须能提供特征重要性度量,如线性模型的系数、树模型的
feature_importances_)。 - 根据模型给出的特征重要性进行排序,淘汰掉最不重要的一个或几个特征。
- 用剩下的特征重复步骤1和2,直到达到指定的特征数量。
RFECV(带交叉验证的RFE)会更进一步,它通过交叉验证自动确定最优的特征数量,是我更推荐的做法。
RFE/RFECV的选型陷阱:最大的坑在于基模型的选择。RFE依赖基模型来评价特征重要性,如果基模型本身就不适合你的数据,或者对特征尺度敏感,那么它给出的重要性排名可能就是错的,导致淘汰过程南辕北辙。例如,对于线性数据,用逻辑回归或SVM做RFE是合适的;对于非线性数据,则应该选择随机森林、梯度提升树这类模型。我的经验是,先用一个你认为最适合解决当前问题的强模型(如LightGBM)作为RFE的基模型,这样选出来的特征子集,对于同类型的模型泛化性最好。
踩坑记录:我曾在一个项目中,用线性回归作为RFE的基模型去筛选特征,然后用筛选后的特征去训练一个复杂的神经网络,结果效果很差。后来才明白,线性模型认为重要的线性特征,对于能捕捉非线性的神经网络来说未必是最优的。这就是“裁判”和“运动员”不匹配导致的问题。后来我改用随机森林作为RFE的基模型,筛选出的特征子集再喂给神经网络,效果显著提升。
2.3 嵌入法:浑然一体的优雅方案
嵌入法可以看作是过滤法和包裹法的“私生子”,它结合了两者的优点。它的核心思想是:在模型训练的过程中自动进行特征选择。某些机器学习算法本身在训练时,就会产生一个稀疏的解,或者能够输出特征的重要性,这个过程本身就隐含了特征选择。
嵌入法的典型代表:
- 基于L1正则化的模型:如Lasso回归(用于回归)和L1正则化的逻辑回归(用于分类)。L1正则化的特性是它倾向于产生稀疏的权重系数,即会将许多不重要的特征的系数压缩为0。这些系数为0的特征,就可以被认为是被模型自动筛选掉了。这种方法非常高效,特征选择与模型训练一步到位。
- 树模型的特征重要性:像随机森林、梯度提升树(如XGBoost, LightGBM, CatBoost)这类集成树模型,在训练完成后可以直接通过
feature_importances_属性获取特征重要性。这个重要性通常基于特征在树中被用于分裂节点的次数或带来的不纯度减少总量。你可以根据重要性排序,选择Top-K的特征。
嵌入法的优势与注意事项:嵌入法最大的优点是高效且与模型高度协同,选择出的特征是为当前模型“量身定做”的。但它也有局限:其选择结果与所使用的特定模型强相关。用Lasso选出来的特征子集,可能只对线性模型友好;用随机森林选出来的,可能更适用于树模型。此外,树模型的特征重要性计算方式有多种(如“基尼重要性”、“分裂次数”、“覆盖样本数”),不同计算方式下的排名可能有差异,需要结合业务理解进行判断。
3. 实战演练:用Python代码打通特征选择全流程
理论说得再多,不如一行代码。下面,我将用一个模拟的分类数据集,带你走完从过滤法到包裹法再到嵌入法的完整Python实战流程。我们会使用scikit-learn、pandas、numpy和matplotlib这些标准库。
3.1 数据准备与基线模型建立
首先,我们创建一个包含一些相关特征、无关特征和冗余特征的数据集,以便观察不同方法的筛选效果。
import numpy as np import pandas as pd from sklearn.datasets import make_classification from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import accuracy_score, roc_auc_score import matplotlib.pyplot as plt # 1. 生成模拟数据 # 生成1000个样本,20个特征,其中只有5个是真正有信息的特征 X, y = make_classification(n_samples=1000, n_features=20, n_informative=5, n_redundant=5, n_repeated=0, n_clusters_per_class=2, random_state=42) # 转换为DataFrame,方便查看 feature_names = [f'feature_{i}' for i in range(X.shape[1])] df = pd.DataFrame(X, columns=feature_names) df['target'] = y print(f"数据集形状: {df.shape}") print(f"特征示例:\n{df[feature_names[:5]].head()}") # 2. 划分训练集和测试集(注意:特征选择只能在训练集上进行!) X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y) # 3. 数据标准化(为方差过滤和某些模型准备) scaler = StandardScaler() X_train_scaled = scaler.fit_transform(X_train) X_test_scaled = scaler.transform(X_test) # 重要!使用训练集的scaler来转换测试集 # 4. 建立一个基线模型(使用所有特征) base_model = RandomForestClassifier(n_estimators=100, random_state=42) base_model.fit(X_train_scaled, y_train) y_pred_base = base_model.predict(X_test_scaled) y_proba_base = base_model.predict_proba(X_test_scaled)[:, 1] base_accuracy = accuracy_score(y_test, y_pred_base) base_auc = roc_auc_score(y_test, y_proba_base) print(f"\n【基线模型】使用全部20个特征") print(f"测试集准确率: {base_accuracy:.4f}") print(f"测试集AUC: {base_auc:.4f}")3.2 过滤法实战:方差过滤与互信息筛选
我们先进行过滤法筛选。这里组合使用方差过滤和互信息法。
from sklearn.feature_selection import VarianceThreshold, SelectKBest, mutual_info_classif # 1. 方差过滤 (移除低方差特征) # 设定一个极低的阈值,比如0.01,目的是移除那些在所有样本上几乎不变的“常量特征” var_selector = VarianceThreshold(threshold=0.01) X_train_var = var_selector.fit_transform(X_train_scaled) # 获取被保留的特征索引 var_support = var_selector.get_support() var_selected_features = [feature_names[i] for i, support in enumerate(var_support) if support] print(f"\n方差过滤后保留的特征数: {len(var_selected_features)}") print(f"保留的特征: {var_selected_features}") # 2. 互信息法筛选 (在方差过滤后的数据上进行) # 我们计划从剩下的特征中,用互信息选出最重要的10个 k_best = 10 mi_selector = SelectKBest(score_func=mutual_info_classif, k=k_best) # 注意:这里使用方差过滤后的训练数据 X_train_var X_train_mi = mi_selector.fit_transform(X_train_var, y_train) mi_support = mi_selector.get_support() # 需要将mi_support映射回原始特征索引,有点绕 # 先得到方差过滤后的特征索引 var_indices = np.where(var_support)[0] # 再从这些索引中,找出被互信息选中的 mi_selected_indices = var_indices[mi_support] mi_selected_features = [feature_names[i] for i in mi_selected_indices] print(f"\n互信息法筛选后保留的特征数: {k_best}") print(f"保留的特征: {mi_selected_features}") # 获取互信息分数,用于可视化 mi_scores = mi_selector.scores_ # 为所有原始特征创建分数表,未被方差过滤的分数设为NaN all_mi_scores = np.full(len(feature_names), np.nan) all_mi_scores[var_indices] = mi_scores[:len(var_indices)] # 只填充有分数的部分 # 可视化特征重要性(互信息分数) plt.figure(figsize=(12, 6)) sorted_idx = np.argsort(all_mi_scores)[::-1] # 按分数降序排序 plt.bar(range(len(sorted_idx)), all_mi_scores[sorted_idx]) plt.xticks(range(len(sorted_idx)), np.array(feature_names)[sorted_idx], rotation=90) plt.xlabel('Features') plt.ylabel('Mutual Information Score') plt.title('Feature Importance based on Mutual Information (Filtered by Variance)') plt.tight_layout() plt.show()3.3 包裹法实战:递归特征消除与交叉验证
接下来,我们使用更精细的包裹法——带交叉验证的递归特征消除。
from sklearn.feature_selection import RFECV from sklearn.svm import SVC # 使用SVM作为RFE的基模型,适用于我们的缩放数据 # 初始化RFECV对象 # estimator: 基模型,这里用线性SVC,因为它能提供coef_作为重要性度量 # step: 每次迭代淘汰的特征数 # cv: 交叉验证折数 # scoring: 评估指标 # n_jobs: 并行数 rfecv_selector = RFECV(estimator=SVC(kernel='linear', random_state=42), step=1, cv=5, # 5折交叉验证 scoring='accuracy', n_jobs=-1, min_features_to_select=5) # 至少保留5个特征 # 在标准化后的完整训练集上拟合RFECV rfecv_selector.fit(X_train_scaled, y_train) # 输出结果 print(f"\n【RFECV包裹法】") print(f"最优特征数量: {rfecv_selector.n_features_}") print(f"所有特征是否被选中: {rfecv_selector.support_}") rfecv_selected_features = [feature_names[i] for i, support in enumerate(rfecv_selector.support_) if support] print(f"被选中的特征: {rfecv_selected_features}") # 绘制交叉验证分数随特征数量的变化图 plt.figure(figsize=(10, 6)) plt.xlabel("Number of features selected") plt.ylabel("Cross validation score (accuracy)") plt.plot(range(1, len(rfecv_selector.cv_results_['mean_test_score']) + 1), rfecv_selector.cv_results_['mean_test_score']) plt.fill_between(range(1, len(rfecv_selector.cv_results_['mean_test_score']) + 1), rfecv_selector.cv_results_['mean_test_score'] - rfecv_selector.cv_results_['std_test_score'], rfecv_selector.cv_results_['mean_test_score'] + rfecv_selector.cv_results_['std_test_score'], alpha=0.2) plt.axvline(x=rfecv_selector.n_features_, color='r', linestyle='--', label=f'Optimal: {rfecv_selector.n_features_} features') plt.legend() plt.title('RFECV: Optimal Number of Features') plt.tight_layout() plt.show()3.4 嵌入法实战:L1正则化与树模型重要性
最后,我们看看嵌入法的两种实现。
from sklearn.linear_model import LogisticRegression from sklearn.feature_selection import SelectFromModel # 方法一:基于L1正则化的逻辑回归 print("\n【嵌入法 - L1正则化逻辑回归】") l1_model = LogisticRegression(penalty='l1', solver='liblinear', C=0.1, random_state=42, max_iter=1000) l1_model.fit(X_train_scaled, y_train) # 查看系数,很多特征的系数被压缩为0 coef_df = pd.DataFrame({'feature': feature_names, 'coefficient': l1_model.coef_[0]}) print("特征系数(L1正则化后):") print(coef_df.sort_values(by='coefficient', key=abs, ascending=False).head(10)) # 使用SelectFromModel自动选择非零系数特征 l1_selector = SelectFromModel(l1_model, prefit=True, threshold=-np.inf, max_features=10) # 选择系数绝对值最大的10个 l1_support = l1_selector.get_support() l1_selected_features = [feature_names[i] for i, support in enumerate(l1_support) if support] print(f"\nL1正则化选中的特征 ({len(l1_selected_features)}个): {l1_selected_features}") # 方法二:基于树模型的特征重要性 print("\n【嵌入法 - 随机森林特征重要性】") # 我们重用之前训练的基线随机森林模型 rf_model = base_model # 已经在标准化数据上训练过 # 获取特征重要性 importances = rf_model.feature_importances_ indices = np.argsort(importances)[::-1] # 打印重要性排名 print("特征重要性排名:") for i, idx in enumerate(indices[:10]): # 只看前10 print(f"{i+1:2d}. {feature_names[idx]:15s} {importances[idx]:.4f}") # 可视化 plt.figure(figsize=(12, 6)) plt.title('Random Forest Feature Importances') plt.bar(range(X_train_scaled.shape[1]), importances[indices], align='center') plt.xticks(range(X_train_scaled.shape[1]), np.array(feature_names)[indices], rotation=90) plt.xlim([-1, X_train_scaled.shape[1]]) plt.tight_layout() plt.show() # 使用SelectFromModel根据重要性选择特征(例如选择重要性大于中位数的特征) rf_selector = SelectFromModel(rf_model, prefit=True, threshold='median') rf_support = rf_selector.get_support() rf_selected_features = [feature_names[i] for i, support in enumerate(rf_support) if support] print(f"\n随机森林重要性选中的特征 ({len(rf_selected_features)}个): {rf_selected_features}")3.5 方法对比与最终模型评估
现在,我们用测试集来评估,使用不同方法筛选出的特征子集,训练同一个随机森林模型,效果到底如何。
# 定义一个函数,用于使用选定的特征训练并评估模型 def evaluate_feature_set(feature_indices, method_name): X_train_selected = X_train_scaled[:, feature_indices] X_test_selected = X_test_scaled[:, feature_indices] model = RandomForestClassifier(n_estimators=100, random_state=42) model.fit(X_train_selected, y_train) y_pred = model.predict(X_test_selected) y_proba = model.predict_proba(X_test_selected)[:, 1] acc = accuracy_score(y_test, y_pred) auc = roc_auc_score(y_test, y_proba) return acc, auc, len(feature_indices) # 收集不同方法选出的特征索引 methods = { 'All Features': list(range(len(feature_names))), 'Filter (MI Top10)': [feature_names.index(f) for f in mi_selected_features], 'Wrapper (RFECV)': [feature_names.index(f) for f in rfecv_selected_features], 'Embedded (L1)': [feature_names.index(f) for f in l1_selected_features], 'Embedded (RF Importance)': [feature_names.index(f) for f in rf_selected_features] } # 评估并汇总结果 results = [] for name, indices in methods.items(): acc, auc, n_feat = evaluate_feature_set(indices, name) results.append([name, n_feat, acc, auc]) results_df = pd.DataFrame(results, columns=['Method', 'Num_Features', 'Accuracy', 'AUC']) print("\n=== 不同特征选择方法效果对比 ===") print(results_df.to_string(index=False)) # 可视化对比 fig, axes = plt.subplots(1, 2, figsize=(14, 5)) x = range(len(results_df)) axes[0].bar(x, results_df['Accuracy']) axes[0].set_xticks(x) axes[0].set_xticklabels(results_df['Method'], rotation=45) axes[0].set_ylabel('Accuracy') axes[0].set_title('Model Accuracy with Different Feature Sets') for i, v in enumerate(results_df['Accuracy']): axes[0].text(i, v+0.005, f'{v:.3f}', ha='center') axes[1].bar(x, results_df['AUC']) axes[1].set_xticks(x) axes[1].set_xticklabels(results_df['Method'], rotation=45) axes[1].set_ylabel('AUC') axes[1].set_title('Model AUC with Different Feature Sets') for i, v in enumerate(results_df['AUC']): axes[1].text(i, v+0.005, f'{v:.3f}', ha='center') plt.tight_layout() plt.show()运行完以上代码,你会得到一个清晰的对比表格和图表。通常你会发现,使用特征选择方法后,模型在测试集上的性能(Accuracy和AUC)与使用全部特征时持平甚至略有提升,但特征数量却大幅减少,模型训练和预测速度更快,且更易于理解和解释。这正是特征选择的价值所在。
4. 高级话题与避坑指南:从理论到工业级实践
掌握了基础方法后,我们需要深入一些高级话题和实战中必然会遇到的“坑”,这些是教科书里很少讲,但却能决定项目成败的关键。
4.1 特征稳定性分析与数据泄露陷阱
特征稳定性:你通过训练集选出了一组最优特征,但换一批数据(比如下个月的数据),这组“最优特征”还最优吗?如果特征选择结果波动很大,说明你的模型可能过拟合了训练集的特征分布,泛化能力存疑。评估特征稳定性的一个简单方法是多次随机划分训练/验证集,重复特征选择过程,观察被选中特征的频率。频率越高,特征越稳定。
数据泄露的巨坑:这是特征选择中最容易犯且后果最严重的错误。切记:任何需要用到目标变量y的计算(如互信息、卡方、模型拟合),都只能在训练集上进行!常见的错误包括:
- 在全集上做特征选择:先用全部数据(训练+测试)计算特征重要性或进行筛选,然后再划分训练测试集。这相当于让测试集信息“泄露”到了训练阶段,会严重高估模型性能。
- 在特征选择中使用了未来信息:在时间序列问题中,如果用t时刻的特征去预测t时刻的目标,是合理的。但如果你不小心在特征工程中混入了t+1时刻的信息(比如用全局均值填充缺失值,而这个均值包含了未来数据),就会造成数据泄露。
- 在交叉验证中处理不当:当使用
RFECV或任何在交叉验证中需要拟合的筛选器时,sklearn的Pipeline结合GridSearchCV是标准做法,它能确保在每一折交叉验证中,特征选择只使用该折的训练部分,从而避免泄露。
避坑指南:我养成的一个强制习惯是,在项目开始就严格划分出训练集、验证集和测试集(或使用时间序列的滚动窗口)。测试集绝对隔离,只在最终评估时使用。所有特征工程和选择步骤,都封装在一个
sklearn.Pipeline里,并将这个Pipeline作为整体进行交叉验证。这样可以最大程度避免无心之失导致的数据泄露。
4.2 高维数据与特征选择的组合策略
当特征数量成千上万(例如基因数据、文本TF-IDF向量)时,直接使用包裹法(如RFE)计算开销会变得无法承受。这时需要采用分阶段、组合式的策略:
- 第一阶段:粗暴过滤。使用方差过滤(去掉零方差特征)和简单的单变量过滤法(如互信息),快速将特征数量从万级降到千级。可以设置一个较高的K值(如保留Top 1000)。
- 第二阶段:模型初筛。使用一个计算效率高的嵌入法模型,如L1正则化的线性模型(Lasso/Logistic Regression with L1),或者基于树模型的
SelectFromModel,将特征数量从千级降到百级。线性模型速度快,树模型能捕捉非线性。 - 第三阶段:精细包裹。在百级特征上,再使用计算成本较高的包裹法(如RFECV)或更复杂的集成特征选择方法,最终确定几十个核心特征。
对于超高维数据,还可以考虑专门的方法,如稳定性选择。它不是给出一个固定的特征子集,而是通过多次子采样(如Bootstrap)并拟合一个稀疏模型(如Lasso),计算每个特征被选中的概率。概率越高的特征越稳定、越重要。sklearn没有直接实现,但可以用LassoCV配合循环自行实现。
4.3 分类与回归问题的选型差异
虽然很多方法(如方差过滤、互信息)经过调整可以同时适用于分类和回归,但最佳实践仍有差异:
- 分类问题:卡方检验、互信息(
mutual_info_classif)是天然的过滤法选择。ANOVA F-value(f_classif)也是常用选项,它检验的是不同类别间特征均值的差异是否显著。包裹法和嵌入法则没有太大限制。 - 回归问题:皮尔逊相关系数、互信息(
mutual_info_regression)是首选的过滤法。F-value(f_regression)用于检验特征与目标的线性相关性。需要特别注意,对于回归问题,特征与目标、特征与特征之间的多重共线性会严重影响线性模型和某些特征选择方法。在筛选前,计算特征间的方差膨胀因子(VIF)或使用相关矩阵热图检查共线性,必要时先进行降维(如PCA)或剔除高相关特征之一。
4.4 业务理解与特征选择的结合
特征选择不能完全交给算法。业务理解应该贯穿始终。算法认为不重要的特征,在业务上可能具有关键的解释意义。例如,在金融风控中,“用户年龄”这个特征在模型中的重要性可能排不进前十,但出于合规和可解释性要求,你必须保留它。反之,算法认为重要的特征,你需要能解释其业务含义。如果一个特征重要性很高但业务上无法理解,你需要警惕是否是数据泄露或巧合造成的虚假关联。
我的做法是,将算法筛选出的特征列表,与业务专家进行讨论。创建一个特征重要性/选择原因的表,包含三列:特征名、算法评分/排名、业务含义解读。通过这种对话,往往能发现数据中的潜在问题,或者激发出新的、更有价值的特征构造思路。特征选择的终点,不是一个冰冷的数字列表,而是一个在预测性能、计算效率和业务可解释性之间取得最佳平衡的特征子集。这个过程没有唯一正确答案,需要你根据项目目标反复权衡和迭代。