递归特征添加#

RecursiveFeatureAddition 实现了递归特征添加(RFA),这是一种前向特征选择过程。

此方法首先使用所有变量训练一个机器学习模型,然后从该模型中得出特征重要性。特征重要性由线性模型的系数(coef_ 属性)或基于决策树模型的特征重要性(feature_importances_ 属性)给出。

在下一步中,递归特征添加 仅使用重要性最高的特征训练模型,并存储此模型的性能。

然后,RecursiveFeatureAddition 添加第二重要的特征,训练一个新的机器学习模型,并确定其性能。如果性能超过一个阈值(与仅包含一个特征的先前模型相比),则认为第二个特征是重要的并会被保留。否则,它将被移除。

递归特征添加 通过将下一个最重要的特征添加到特征集中,训练一个新的机器学习模型,获取其性能,确定性能变化,依此类推,直到所有特征都被评估。

需要注意的是,从初始机器学习模型中得出的特征重要性仅用于对特征进行排序,从而确定特征添加的顺序。但是否保留某个特征是根据特征添加后模型性能的提升来决定的。

参数#

RecursiveFeatureAddition 有两个参数需要用户在一定程度上任意确定:第一个是将被评估性能的机器学习模型。第二个是性能提升的阈值,需要达到该阈值才能保留一个特征。

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

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

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 RecursiveFeatureAddition

# 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

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

# initialize linear regression estimator
linear_model = LinearRegression()

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

通过 fit(),模型找到最有用的特征,即那些在添加后导致模型性能增加超过0.01的特征。通过 transform(),转换器从数据集中移除这些特征。

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

通过递归特征增加和线性回归,只有4个特征被认为重要:

        bmi        bp        s1        s5
0  0.061696  0.021872 -0.044223  0.019907
1 -0.051474 -0.026328 -0.008449 -0.068332
2  0.044451 -0.005670 -0.045599  0.002861
3 -0.011595 -0.036656  0.012191  0.022688
4 -0.036385  0.021872  0.003935 -0.031988

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

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

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

0.488702767247119

评估特征重要性#

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

tr.feature_importances_

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

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

特征重要性是通过交叉验证获得的,因此 RecursiveFeatureAddition 还存储了特征重要性的标准差:

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_

在以下输出中,我们可以看到通过添加每个功能返回的性能变化:

{'s1': 0,
 's5': 0.28371458794131676,
 'bmi': 0.1377714799388745,
 's2': 0.0023327265047610735,
 'bp': 0.018759914615172735,
 'sex': 0.0027996354657459643,
 's4': 0.002695149440021638,
 's3': 0.002683934134630306,
 's6': 0.000304067408860742,
 'age': -0.007387230783454768}

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

# Get the performance drift of each feature
tr.performance_drifts_std_

在以下输出中,我们看到了通过添加每个功能返回的性能变化的标准差:

{'s1': 0,
 's5': 0.029336910701570382,
 'bmi': 0.01752426732750277,
 's2': 0.020525965661877265,
 'bp': 0.017326401244547558,
 'sex': 0.00867675077259389,
 's4': 0.024234566449074676,
 's3': 0.023391851139598106,
 's6': 0.016865740401721313,
 'age': 0.02042081611218045}

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

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/rfa_perf_drifts.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

我们看到两个图表都显示 s1s5 是最重要的特征。然而,需要注意的是,从特征重要性图表中,我们可能会认为 s2bp 是重要的(它们的系数值相对较大),但是,将它们添加到一个已经包含 s1s5bmi 的模型中,并不会导致模型性能的提升。这表明 s2bp 可能与一些最重要的特征(s1s5bmi)之间存在相关性。

查看已淘汰的功能#

RecursiveFeatureAddition 存储了基于给定阈值将被丢弃的特征:

# the features to drop
tr.features_to_drop_

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

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

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

tr.get_support()

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

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

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

其他资源#

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

../../_images/fsml.png

机器学习的特征选择#











或者阅读我们的书:

../../_images/fsmlbook.png

机器学习中的特征选择#















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