特征选择方法:过滤式、包裹式、嵌入式方法概览
特征选择这件事,说白了就是帮模型挑出真正有用的特征。
做ADMET预测时,分子描述符动辄上千个。但真正对某个性质有贡献的,可能就几十个。我见过不少项目,模型精度上不去,不是算法不行,而是喂进去太多噪声特征。
今天就聊三种主流的特征选择方法:过滤式、包裹式、嵌入式。这三种策略,我在不同项目里都踩过坑,也攒了些经验。
为什么需要特征选择?
先想一个问题:模型的输入特征并不是越多越好。
- 维度灾难:特征数量增加,模型需要的数据量指数级增长
- 过拟合风险:无关特征会被模型误当成信号
- 计算开销:每个额外特征都意味着更多的训练时间
- 可解释性:特征越少,模型越好解释
我自己的经验是:把特征从1000个降到50个,模型性能往往不降反升。这就是特征选择的魅力。
三种方法的核心区别
| 方法 | 核心思想 | 是否依赖模型 | 计算成本 | 典型场景 |
|---|---|---|---|---|
| 过滤式 | 根据统计指标排序特征 | 否 | 低 | 数据量大的初步筛选 |
| 包裹式 | 用模型预测性能评估特征子集 | 是(训练模型) | 高 | 特征数量较少时 |
| 嵌入式 | 在模型训练过程中学习特征权重 | 是(利用模型内部机制) | 中等 | 需要同时训练和筛选特征 |
三种方法没有绝对的好坏,关键是看你的数据规模和计算资源。
我曾经在一个项目里直接上了包裹式方法,结果跑了三天三夜还没跑完。从那以后,我就学乖了,先过滤后包裹才是正道。
过滤式方法
过滤式方法不依赖任何模型。它直接计算每个特征与目标变量之间的统计量,按得分排序,然后选前K个。
常用的指标有这些:
- 方差分析(ANOVA):适用于连续特征、分类目标,看组间差异是否显著
- 互信息(Mutual Information):能捕捉非线性关系,比较灵活
- 卡方检验:适用于分类特征和分类目标
- 皮尔逊相关系数:只看线性关系,简单但容易漏掉非线性信号
代码实现很简单:
from sklearn.feature_selection import SelectKBest, f_regression
# 假设 X 是特征矩阵,y 是目标变量
selector = SelectKBest(score_func=f_regression, k=50)
X_selected = selector.fit_transform(X, y)
# 查看哪些特征被选中
selected_indices = selector.get_support(indices=True)
print(f"选中的特征索引: {selected_indices}")
优点:速度快,适合高维数据。
缺点:不考虑特征之间的交互作用。两个单独没什么用的特征,组合起来可能很强,但过滤式方法发现不了。
包裹式方法
包裹式方法用模型做"裁判"。它评估某个特征子集时,会训练一个模型,然后看模型的预测性能。
典型的包裹式方法有:
- 递归特征消除(RFE):反复训练模型,每次去掉最不重要的特征
- 前向搜索:从空集开始,每次添加一个能提升性能最多的特征
- 后向搜索:从全集开始,每次去掉一个影响最小的特征
RFE的代码示例:
from sklearn.feature_selection import RFE
from sklearn.ensemble import RandomForestRegressor
estimator = RandomForestRegressor(n_estimators=100)
selector = RFE(estimator, n_features_to_select=30, step=10)
selector.fit(X, y)
# 查看特征排名
print("特征排名:", selector.ranking_)
# 排名为1的表示被选中
优点:能找到特征之间的组合效果,筛选结果直接服务于模型。
缺点:计算量很大。特征多的时候,简直是一场灾难。
我建议:先用过滤式把特征压缩到100个以内,再用包裹式精细筛选。这样既保证质量,又控制时间。
嵌入式方法
嵌入式方法把特征选择过程嵌在模型训练里。模型在学参数的同时,也在给特征打分。
常见的嵌入式方法:
- Lasso回归(L1正则化):自动把不重要的特征系数压缩到0
- 树模型的特征重要性:随机森林、XGBoost都能输出特征重要度
- 决策树的分裂特征:每个节点分裂时选择的特征就是"被选中"的
Lasso回归示例:
from sklearn.linear_model import Lasso
lasso = Lasso(alpha=0.01)
lasso.fit(X, y)
# 查看特征系数,系数为0的特征被剔除
coef = lasso.coef_
important_features = [i for i, c in enumerate(coef) if c != 0]
print(f"非零系数的特征数: {len(important_features)}")
优点:一次训练就完成特征选择,效率比包裹式高。
缺点:特征选择结果受模型超参数影响大,调参要花功夫。
一张图看懂三种方法
怎么选?我的经验法则
面对一个实际的ADMET预测任务,怎么选方法?我总结了一个简单的决策流程:
- 先看特征数量:如果超过500个,务必先用过滤式压缩
- 再看计算预算:时间充裕想找最优子集 → 包裹式;求快 → 嵌入式
- 最后看模型类型:用树模型的话,直接取特征重要性(嵌入式);用线性模型,考虑Lasso
我记得有一个水溶性预测的项目,一开始2000多个分子描述符。我用过滤式选了前100个,然后用递归特征消除选到30个。最后的模型不仅速度快,准确率还提升了5%。
嗯,掌握了这三种方法的设计思想和适用场景,你就有了做特征选择的工程直觉。下个项目里,你至少知道该从哪个方向入手了。