递归特征消除#

RecursiveFeatureElimination 实现了递归特征消除。递归特征消除(RFE)是一个反向特征选择过程。

在 Feature-engine 的 RFE 实现中,一个特征将被保留或移除,基于该特征添加到机器学习模型后对模型性能的影响。这与 Scikit-learn 的 RFE 实现不同,后者根据机器学习模型通过其系数参数或 ‘feature_importances_’ 属性得出的特征重要性来决定特征的保留或移除。

Feature-engine 的 RFE 实现首先在整个变量集上训练一个模型,并存储其性能值。基于同一模型,RecursiveFeatureElimination 通过 coef_feature_importances_ 属性推导出特征重要性,这取决于它是一个线性模型还是基于树的算法。这些特征重要性值用于按性能递增排序特征,以确定特征将被递归移除的顺序。最不重要的特征首先被移除。

在下一步中,RecursiveFeatureElimination 移除最不重要的特征,并使用剩余的变量训练一个新的机器学习模型。如果这个模型的性能比前一个模型的性能差,那么,该特征被保留(因为移除该特征导致了模型性能的下降),否则,它被移除。

RecursiveFeatureElimination 现在移除第二不重要的特征,训练一个新模型,将其性能与之前的模型进行比较,决定是否移除或保留该特征,并继续处理下一个变量,直到评估数据集中的所有特征。

需要注意的是,在 Feature-engine 的 RFE 实现中,特征重要性仅用于对特征进行排序,从而确定特征被剔除的顺序。但是否保留某个特征是基于特征剔除后模型性能的下降来决定的。

通过递归地消除特征,RFE 试图消除模型中可能存在的依赖性和共线性。

参数#

递归特征消除 有两个参数需要用户在一定程度上任意确定:第一个是将被评估性能的机器学习模型。第二个是性能下降的阈值,需要达到该阈值才能移除一个特征。

RFE 不是与机器学习模型无关的,这意味着特征选择取决于模型,不同的模型可能会有不同的最优特征子集。因此,建议您使用最终打算构建的机器学习模型。

关于阈值,这个参数需要一些手动调整。较高的阈值将返回较少的特征。

Python 示例#

让我们看看如何使用这个转换器与Scikit-learn中的糖尿病数据集。首先,我们加载数据:

import matplotlib.pyplot as plt
import pandas as pd
from sklearn.datasets import load_diabetes
from sklearn.linear_model import LinearRegression
from feature_engine.selection import RecursiveFeatureElimination

# load dataset
X, y = load_diabetes(return_X_y=True, as_frame=True)

print(X.head())

在以下输出中,我们看到了糖尿病数据集:

        age       sex       bmi        bp        s1        s2        s3  \
0  0.038076  0.050680  0.061696  0.021872 -0.044223 -0.034821 -0.043401
1 -0.001882 -0.044642 -0.051474 -0.026328 -0.008449 -0.019163  0.074412
2  0.085299  0.050680  0.044451 -0.005670 -0.045599 -0.034194 -0.032356
3 -0.089063 -0.044642 -0.011595 -0.036656  0.012191  0.024991 -0.036038
4  0.005383 -0.044642 -0.036385  0.021872  0.003935  0.015596  0.008142

         s4        s5        s6
0 -0.002592  0.019907 -0.017646
1 -0.039493 -0.068332 -0.092204
2 -0.002592  0.002861 -0.025930
3  0.034309  0.022688 -0.009362
4 -0.002592 -0.031988 -0.046641

现在,我们设置 RecursiveFeatureElimination 以基于线性回归模型返回的 r2 选择特征,使用 3 折交叉验证。在这种情况下,我们将参数 threshold 保留为默认值 0.01。

# initialize linear regresion estimator
linear_model = LinearRegression()

# initialize feature selector
tr = RecursiveFeatureElimination(estimator=linear_model, scoring="r2", cv=3)

使用 fit() 方法,模型会找到最有用的特征,即那些在移除后导致模型性能下降超过 0.01 的特征。使用 transform() 方法,转换器会从数据集中移除这些特征。

Xt = tr.fit_transform(X, y)
print(Xt.head())

通过线性回归的递归特征消除,确定了六个重要的特征:

        sex       bmi        bp        s1        s2        s5
0  0.050680  0.061696  0.021872 -0.044223 -0.034821  0.019907
1 -0.044642 -0.051474 -0.026328 -0.008449 -0.019163 -0.068332
2  0.050680  0.044451 -0.005670 -0.045599 -0.034194  0.002861
3 -0.044642 -0.011595 -0.036656  0.012191  0.024991  0.022688
4 -0.044642 -0.036385  0.021872  0.003935  0.015596 -0.031988

RecursiveFeatureElimination 在其属性中存储了使用所有特征训练的模型的性能:

# get the initial linear model performance, using all features
tr.initial_model_performance_

在以下输出中,我们看到了在整个数据集上训练的线性回归的性能:

0.488702767247119

评估特征重要性#

线性回归的系数用于确定初始特征重要性评分,该评分用于在应用递归消除过程之前对特征进行排序。我们可以如下检查特征重要性:

tr.feature_importances_

在以下输出中,我们看到了从线性模型中得出的特征重要性:

age     41.418041
s6      64.768417
s3     113.965992
s4     182.174834
sex    238.619526
bp     322.091802
s2     436.671584
bmi    522.330165
s5     741.471337
s1     750.023872
dtype: float64

特征重要性是通过交叉验证获得的,因此 递归特征消除 也存储了特征重要性的标准差:

tr.feature_importances_std_

在以下输出中,我们看到了特征重要性的标准差:

age     18.217152
sex     68.354719
bmi     86.030698
bp      57.110383
s1     329.375819
s2     299.756998
s3      72.805496
s4      47.925822
s5     117.829949
s6      42.754774
dtype: float64

选择过程基于移除一个特征是否会降低模型的性能,相比于包含该特征的同一模型。我们可以如下检查性能变化:

# Get the performance drift of each feature
tr.performance_drifts_

在以下输出中,我们看到了通过移除每个特征返回的性能变化:

{'age': -0.0032800993162502845,
 's6': -0.00028194870232089997,
 's3': -0.0006751427734088544,
 's4': 0.00013890056776355575,
 'sex': 0.01195652626644067,
 'bp': 0.02863360798239445,
 's2': 0.012639242239088355,
 'bmi': 0.06630359039334816,
 's5': 0.10937354113435072,
 's1': 0.024318355833473526}

我们还可以检查性能漂移的标准差:

# Get the performance drift of each feature
tr.performance_drifts_std_

在以下输出中,我们看到了通过消除每个特征后返回的性能变化的标准差:

{'age': 0.013642261032787014,
 's6': 0.01678934235354838,
 's3': 0.01685859860738229,
 's4': 0.017977817100713972,
 'sex': 0.025202392033518706,
 'bp': 0.00841776123355417,
 's2': 0.008676750772593812,
 'bmi': 0.042463565656018436,
 's5': 0.046779680487815146,
 's1': 0.01621466049786452}

我们现在可以用标准差绘制性能变化,以识别重要特征:

r = pd.concat([
    pd.Series(tr.performance_drifts_),
    pd.Series(tr.performance_drifts_std_)
], axis=1
)
r.columns = ['mean', 'std']

r['mean'].plot.bar(yerr=[r['std'], r['std']], subplots=True)

plt.title("Performance drift elicited by adding features")
plt.ylabel('Mean performance drift')
plt.xlabel('Features')
plt.show()

在下图中,我们可以看到从模型中移除每个特征后性能的变化:

../../_images/rfe_perf_drift.png

为了比较,我们可以将线性回归得出的特征重要性与标准差一起绘制:

r = pd.concat([
    tr.feature_importances_,
    tr.feature_importances_std_,
], axis=1
)
r.columns = ['mean', 'std']

r['mean'].plot.bar(yerr=[r['std'], r['std']], subplots=True)

plt.title("Feature importance derived from the linear regression")
plt.ylabel('Coefficients value')
plt.xlabel('Features')
plt.show()

在下图中,我们看到了由线性回归系数确定的功能重要性:

../../_images/rfa_linreg_imp.png

通过比较两个图表中的表现,我们可以开始理解哪些特征是重要的,以及哪些特征可能与其他数据中的变量显示出某种相关性。如果一个特征具有相对较大的系数,但移除它并不会改变模型性能,那么它可能与数据中的另一个变量相关。

查看被淘汰的功能#

RecursiveFeatureElimination 还会存储基于给定阈值将被丢弃的特征。

# the features to remove
tr.features_to_drop_

这些功能未被RFE流程视为重要:

['age', 's3', 's4', 's6']

RecursiveFeatureElimination 也有 get_support() 方法,其工作方式与 Scikit-learn 的特征选择类完全相同:

tr.get_support()

输出中,选中的特性为 True,将被丢弃的特性为 False:

[False, True, True, True, True, True, False, False, True, False]

就是这样!你现在知道如何通过递归地移除特征来选择数据集中的特征了。

其他资源#

关于递归特征消除的更多细节,请参阅本文:

有关此方法和其他特征选择方法的更多详细信息,请查看以下资源:

有关此方法和其他特征选择方法的更多详细信息,请查看以下资源:

../../_images/fsml.png

机器学习的特征选择#











或者阅读我们的书:

../../_images/fsmlbook.png

机器学习中的特征选择#















我们的书籍和课程都适合初学者和更高级的数据科学家。通过购买它们,您正在支持 Feature-engine 的主要开发者 Sole。