前几篇介绍了使用方差阈值,相关性分析以及统计检验等方式来过滤特征,这些方法都有一个共同点,就是由人来决定如何打分。
今天介绍一种让模型自己给特征“打分”的方式,也就是让模型自己判断哪些特征重要,哪些特征是无关紧要的。
与其我自己猜,不如让模型告诉我。
核心思路
这种特征选择方式的核心思想总结起来就一句话: 三个臭皮匠,顶个诸葛亮。
也就是用多个模型分别给特征打分,然后综合排名,选出最重要的特征。
模型是怎么给特征"打分"的?
不同类型的模型,给特征打分的方式完全不同。我觉得这个特别有意思,就像不同的老师批改作文,评分标准都不一样。
下面有 3 种利用模型给特征打分的方式。
- 对于树模型:
比如,随机森林、梯度提升树这些树模型,在训练过程中会自然地统计一件事:每个特征在分裂节点的时候,帮模型减少了多少"不确定性"(不纯度)。
减少得越多,说明这个特征越重要。
from sklearn.ensemble import RandomForestClassifier
model = RandomForestClassifier(n_estimators=100, random_state=42)
model.fit(X_train, y_train)
# 每个特征的重要性分数,加起来等于 1
importances = model.feature_importances_
这个方法的优点是快——训练完模型顺手就拿到了,不需要额外计算。而且它能捕捉非线性关系,比单纯看相关性靠谱多了。
但它有个坑:它偏爱"类别多"的特征。比如一个特征有 100 个不同的取值,它就更容易被选为分裂点,得分虚高。这一点我后来才知道,被坑过。
- 对于线性模型:
比如带 L1 正则化的逻辑回归或者 Lasso 回归,思路更直接——系数的绝对值越大,特征越重要。
但这里有个关键前提:你得先把特征标准化。
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X_train)
model = LogisticRegression(penalty='l1', solver='liblinear', C=1.0)
model.fit(X_scaled, y_train)
# 取系数的绝对值
importances = np.abs(model.coef_).flatten()
为什么一定要标准化?因为如果一个特征的单位是"元"(取值范围几万到几十万),另一个是"百分比"(取值范围 0~1),那它们的系数根本没法直接比。
标准化之后大家都在同一个尺度上,才公平。
L1 正则化还有个好处:它会自动把不重要特征的系数压到 0。
所以我有时候把 Lasso 当成一个"自动特征筛选器"来用。
- 此外,还可以通过置换重要性,看删掉某个特征,模型会不会变差。
这个方法我觉得最"暴力",但也最直觉:
- 先把模型训练好
- 然后把某一个特征的列值随机打乱
- 看模型的性能(比如准确率)下降了多少
下降越多 → 这个特征越重要。
from sklearn.inspection import permutation_importance
result = permutation_importance(
model, X_test, y_test,
n_repeats=10,
random_state=42
)
# 每个特征的平均重要性
importances = result.importances_mean
它的优点是什么模型都能用,不限于树模型或线性模型。
缺点就是慢——每个特征都要打乱多次、推理多次,特征多的时候要等好一会儿。
为什么要让多个模型"投票"?
上面三种方法各有所长,也各有盲区。这就出现了一个问题:
如果只用一个模型来评分,万一这个模型恰好对某些特征有偏见怎么办?
比如树模型偏好高基数特征,线性模型只能捕捉线性关系。如果我的数据恰好同时有这两种情况,单一模型的评分结果就不可靠了。
这就像招聘面试只让一个面试官拍板——他可能有自己的偏好。
**多个面试官一起打分,取平均,结果通常更客观。**所以,能不能让好几种模型都来打分,然后取个平均值?
当然可以,这就是这篇文章介绍的过滤特征的原理。
代码设计
整体流程很简单(3 步走):
数据输入 → 多模型打分 → 融合排名 → 选出Top特征
代码中核心的 class 就是EnsembleFeatureSelector。其核心功能设计如下:
初始化参数
top_k=10 # 我要前10个最重要的特征
min_score=0.5 # 或者:得分≥0.5的特征都要
enable_permutation=False # 是否用"置换法"评估(更准但更慢)
model_weights=None # 给不同模型分配权重(比如随机森林权重高一点)
构建模型池
根据任务类型(分类/回归)自动选择不同的模型组合:
| 任务类型 | 模型池 |
|---|---|
| 分类 | 随机森林 + 梯度提升树 + L1逻辑回归 |
| 回归 | 随机森林 + 梯度提升树 + Lasso回归 |
为什么选这3个?
- 树模型(随机森林、梯度提升树):能自动捕捉非线性关系,抗过拟合
- 线性模型(L1正则):简单高效,L1正则会自动把无用特征系数压到0
三种打分方式
方式A:树模型内置重要性
原理:树在分裂节点时,会计算每个特征让"不纯度"减少了多少
减少越多 → 该特征越重要
比喻:就像分班考试,哪个科目最能区分学生水平,哪个科目就最重要
方式B:线性模型系数法
原理:先标准化数据(消除量纲差异),然后看模型系数的绝对值
系数越大 → 该特征对结果影响越大
比喻:就像投票,谁的"声音"(系数)最大,谁就越重要
方式C:置换重要性
原理:把某个特征的列随机打乱,看模型准确率下降多少
下降越多 → 该特征越重要
比喻:就像考试时把某道题的答案随机填,如果总分下降很多,说明这道题很关键
融合评分
# 假设3个模型给出的分数:
特征A: [0.8, 0.6, 0.7] → 集成均值 = 0.7
特征B: [0.3, 0.4, 0.2] → 集成均值 = 0.3
# 然后归一化到 [0, 1],排名
加权平均(如果指定了 model_weights):
# 比如你觉得随机森林更靠谱,给它权重2,其他给1
{"随机森林": 2, "梯度提升树": 1, "L1逻辑回归": 1}
筛选特征
三种策略,优先级从高到低:
| 策略 | 说明 | 示例 |
|---|---|---|
top_k | 精确选前K个 | top_k=10 → 选排名前10的特征 |
min_score | 选归一化得分≥阈值的 | min_score=0.5 → 选得分≥0.5的 |
| 默认 | 选高于平均水平的 | 自动计算均值,选超过均值的 |
代码实现
完整的代码和模拟数据的测试用例(信用卡违约预测)共享在:
url11.ctfile.com/d/45455611-… (访问密码: 6872)
相关文件是:model_based_selector.py 和 test_model_based_selector.py。
感兴趣的朋友欢迎下载了自己运行试试看,运行过程中有什么问题也欢迎交流。
模拟数据的测试结果:
======================================================================
信用卡违约预测 —— 特征选择效果对比实验
======================================================================
数据集信息:
样本总数:5000
特征总数:30(5 个有效特征 + 25 个噪声特征)
违约比例:5.6%
训练集大小:3500,测试集大小:1500
──────────────────────────────────────────────────────────────────────
方案 A:使用全部 30 个特征
──────────────────────────────────────────────────────────────────────
准确率:0.9447
AUC: 0.6920
──────────────────────────────────────────────────────────────────────
方案 B:使用 EnsembleFeatureSelector 筛选特征
──────────────────────────────────────────────────────────────────────
特征重要性排名报告(Top 15):
被选中的特征:['月收入', '负债收入比', '信用评分', '信用卡额度使用率', '历史逾期次数', '月消费金额_交通', '基金持有
金额', '客服咨询次数']
筛选后特征数:8
准确率:0.9447
AUC: 0.7298
──────────────────────────────────────────────────────────────────────
方案 C:使用 EnsembleFeatureSelector 激进筛选(top_k=5)
──────────────────────────────────────────────────────────────────────
特征重要性排名报告(Top 5):
被选中的特征:['月收入', '负债收入比', '信用评分', '信用卡额度使用率', '历史逾期次数']
筛选后特征数:5
准确率:0.9433
AUC: 0.7483
======================================================================
汇总对比
======================================================================
方案 特征数 准确率 AUC
──────────────────────────────────────────────────────────────
A: 全部特征 30 0.9447 0.6920
B: 筛选后(top 8) 8 0.9447 0.7298
C: 筛选后(top 5) 5 0.9433 0.7483
======================================================================
注意事项
归一化这事儿不能偷懒
如果忘了做归一化,直接把三个模型的原始分数取平均。
那么有可能线性模型的系数特别大(因为某个特征的量纲特别大),直接把树模型的评分"淹没"了。
最终选出来的特征,几乎完全取决于线性模型的结果,集成毫无意义。
不同来源的分数,一定要先统一量纲再融合。
相关特征会让置换重要性"失灵"
如果数据里有两个特征高度相关(相关系数 0.95+)。
那么,可能用置换重要性评估时,两个特征得分都很低。
原因是:打乱其中一个特征,模型还能靠另一个"替补",所以性能没怎么下降。模型就以为这两个都不重要。
高度相关的特征,最好先做一轮去相关处理,或者直接用树模型的内置重要性。
训练集和测试集的重要性可能不一样
置换重要性最好在测试集上算,而不是训练集。
因为在训练集上算,模型可能过拟合了,给出的重要性分数不能反映真实情况。
# 推荐做法:在测试集上评估
result = permutation_importance(model, X_test, y_test, n_repeats=10)
特征选择也是一种"学习",要警惕过拟合。
总结
基于模型的特征排序,本质上是在回答一个问题:在这个数据上,哪些特征对预测目标最有帮助?
如果时间允许,我倾向于用多模型集成(也就是上面提到的 3 种打分方式混合一起使用)的方式。
毕竟多训练几个模型也就多花几分钟,但结果会靠谱很多。
选特征和选队友其实差不多——让多几个人一起看,总比一个人拍板要强。