递归特征添加#
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()
在下图中,我们可以看到向模型中添加每个功能所导致的性能变化:
为了比较,我们可以将线性回归得出的特征重要性与标准差一起绘制:
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()
在下图中,我们看到了由线性回归系数确定的功能重要性:
我们看到两个图表都显示 s1
和 s5
是最重要的特征。然而,需要注意的是,从特征重要性图表中,我们可能会认为 s2
和 bp
是重要的(它们的系数值相对较大),但是,将它们添加到一个已经包含 s1
、s5
和 bmi
的模型中,并不会导致模型性能的提升。这表明 s2
或 bp
可能与一些最重要的特征(s1
、s5
和 bmi
)之间存在相关性。
查看已淘汰的功能#
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]
就是这样!你现在知道如何通过递归地将特征添加到数据集中来选择特征了。
其他资源#
有关此功能选择方法及其他方法的更多详细信息,请查看以下资源:
或者阅读我们的书:
我们的书籍和课程都适合初学者和更高级的数据科学家。通过购买它们,您正在支持 Feature-engine 的主要开发者 Sole。