前几篇介绍了使用方差阈值相关性分析以及统计检验等方式来过滤特征,这些方法都有一个共同点,就是由人来决定如何打分。

今天介绍一种让模型自己给特征“打分”的方式,也就是让模型自己判断哪些特征重要,哪些特征是无关紧要的。

与其我自己猜,不如让模型告诉我。

核心思路

这种特征选择方式的核心思想总结起来就一句话: 三个臭皮匠,顶个诸葛亮

也就是用多个模型分别给特征打分,然后综合排名,选出最重要的特征。

模型是怎么给特征"打分"的?

不同类型的模型,给特征打分的方式完全不同。我觉得这个特别有意思,就像不同的老师批改作文,评分标准都不一样。

下面有 3 种利用模型给特征打分的方式。

  1. 对于树模型

比如,随机森林、梯度提升树这些树模型,在训练过程中会自然地统计一件事:每个特征在分裂节点的时候,帮模型减少了多少"不确定性"(不纯度)。

减少得越多,说明这个特征越重要。

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 个不同的取值,它就更容易被选为分裂点,得分虚高。这一点我后来才知道,被坑过。

  1. 对于线性模型

比如带 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 当成一个"自动特征筛选器"来用。

  1. 此外,还可以通过置换重要性,看删掉某个特征,模型会不会变差。

这个方法我觉得最"暴力",但也最直觉:

  1. 先把模型训练好
  2. 然后把某一个特征的列值随机打乱
  3. 看模型的性能(比如准确率)下降了多少

下降越多 → 这个特征越重要。

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的
默认 选高于平均水平的 自动计算均值,选超过均值的

代码实现

完整的代码和模拟数据的测试用例(信用卡违约预测)共享在:

https://url11.ctfile.com/d/45455611-166997307-d6d609?p=6872 (访问密码: 6872)

相关文件是:model_based_selector.pytest_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 种打分方式混合一起使用)的方式。

毕竟多训练几个模型也就多花几分钟,但结果会靠谱很多。

选特征和选队友其实差不多——让多几个人一起看,总比一个人拍板要强。


原文地址: http://www.cveoy.top/t/topic/qHh1 著作权归作者所有。请勿转载和采集!

免费AI点我,无需注册和登录