点击上方
“
小白学视觉
”,选择加"
星标
"或“
置顶
”
重磅干货,第一时间送达![](http://mmbiz.qpic.cn/mmbiz_jpg/ow6przZuPIENb0m5iawutIf90N2Ub3dcPuP2KXHJvaR1Fv2FnicTuOy3KcHuIEJbd9lUyOibeXqW8tEhoJGL98qOw/640?wx_fmt=other&wxfrom=5&wx_lazy=1&wx_co=1&tp=webp)
来源:DeepHub IMBA,编辑:数据派THU
本文与你分享可应用于特征选择的各种技术的有用指南。
太多的特征会增加模型的复杂性和过拟合,而太少的特征会导致模型的拟合不足。将模型优化为足够复杂以使其性能可推广,但又足够简单易于训练、维护和解释是特征选择的主要工作。
“特征选择”意味着可以保留一些特征并放弃其他一些特征。本文的目的是概述一些特征选择策略:
-
-
-
-
-
-
-
-
-
-
使用 sci-kit learn 进行自动特征选择
-
该演示的数据集在 MIT 许可下发布,来自 PyCaret——一个开源的低代码机器学习库。
数据集相当干净,但我做了一些预处理。请注意,我使用此数据集来演示不同的特征选择策略如何工作,而不是构建最终模型,因此模型性能无关紧要。
import pandas as
pddata = 'https://raw.githubusercontent.com/pycaret/pycaret/master/datasets/automobile.csv'
df = pd.read_csv(data)
df.sample(5)
该数据集包含 202 行和 26 列——每行代表一个汽车实例,每列代表其特征和相应的价格。这些列包括:
df.columns
>> Index(['symboling', 'normalized-losses', 'make', 'fuel-type', 'aspiration', 'num-of-doors', 'body-style', 'drive-wheels', 'engine-location','wheel-base', 'length', 'width', 'height', 'curb-weight', 'engine-type', 'num-of-cylinders', 'engine-size', 'fuel-system', 'bore', 'stroke', 'compression-ratio', 'horsepower', 'peak-rpm', 'city-mpg', 'highway-mpg', 'price'], dtype='object')
1.删除未使用的列
当然,最简单的策略是你的直觉。虽然是直觉,但有时很有用的,某些列在最终模型中不会以任何形式使用(例如“ID”、“FirstName”、“LastName”等列)。如果您知道某个特定列将不会被使用,请随时将其删除。在我们的数据中,没有一列有这样的问题所以,我在此步骤中不删除任何列。
2.删除具有缺失值的列
缺失值在机器学习中是不可接受的,因此我们会采用不同的策略来清理缺失数据(例如插补)。但是如果列中缺少大量数据,那么完全删除它是非常好的方法。
# total null values per column
df.isnull().sum()
>>
symboling 0
normalized-losses 35
make 0
fuel-type 0
aspiration 0
num-of-doors 2
body-style 0
drive-wheels 0
engine-location 0
wheel-base 0
length 0
width 0
height 0
curb-weight 0
engine-type 0
num-of-cylinders 0
engine-size 0
fuel-system 0
bore 0
stroke 0
compression-ratio 0
horsepower 0
peak-rpm 0
city-mpg 0
highway-mpg 0
price 0
dtype: int64
3.不相关的特征
无论算法是回归(预测数字)还是分类(预测类别),特征都必须与目标相关。如果一个特征没有表现出相关性,它就是一个主要的消除目标。可以分别测试数值和分类特征的相关性。
(df.corr().loc['price']
.plot(kind='barh', figsize=(4,10)))
在此示例中,peak-rpm, compression-ratio, stroke, bore, height , symboling 等特征与价格几乎没有相关性,因此我们可以删除它们。
可以手动删除列,但我更喜欢使用相关阈值(在本例中为 0.2)以编程方式进行:
# drop uncorrelated numeric features (threshold <0.2)
corr = abs(df.corr().loc['price'])
corr = corr[corr<0.2]
cols_to_drop = corr.index.to_list()
df = df.drop(cols_to_drop, axis=1)
import seaborn as sns
sns.boxplot(y = 'price', x = 'fuel-type', data=df)
柴油车的中位价高于汽油车。这意味着这个分类变量可以解释汽车价格,所以应放弃它。可以像这样单独检查每个分类列。
4.低方差特征
import numpy as np
(df
.select_dtypes(include=np.number)
.var()
.astype('str'))
这里的“bore”具有极低的方差,虽然这是删除的候选者。在这个特殊的例子中,我不愿意删除它,因为它的值在2.54和3.94之间,因此方差很低:
5.多重共线性
当任何两个特征之间存在相关性时,就会出现多重共线性。在机器学习中,期望每个特征都应该独立于其他特征,即它们之间没有共线性。高马力车辆往往具有高发动机尺寸。所以你可能想消除其中一个,让另一个决定目标变量——价格。
Heatmap 是检查和寻找相关特征的最简单方法。
import matplotlib.pyplot as plt
sns.set(rc={'figure.figsize':(16,10)})
sns.heatmap(df.corr(),
annot=True,
linewidths=.5,
center=0,
cbar=False,
cmap="PiYG")
plt.show()
大多数特征在某种程度上相互关联,但有些特征具有非常高的相关性,例如长度与轴距以及发动机尺寸与马力。
可以根据相关阈值手动或以编程方式删除这些功能。我将手动删除具有 0.80 共线性阈值的特征。
# drop correlated features
df = df.drop(['length', 'width', 'curb-weight', 'engine-size', 'city-mpg'], axis=1)
还可以使用称为方差膨胀因子 (VIF) 的方法来确定多重共线性并根据高 VIF 值删除特征。我稍后会展示这个例子。
与数值特征类似,也可以检查分类变量之间的共线性。诸如独立性卡方检验之类的统计检验非常适合它。
让我们检查一下数据集中的两个分类列——燃料类型和车身风格——是独立的还是相关的。
df_cat = df[['fuel-type', 'body-style']]
df_cat.sample(5)
然后我们将在每一列中创建一个类别的交叉表/列联表。
crosstab = pd.crosstab(df_cat['fuel-type'], df_cat['body-style'])
crosstab
最后,我们将在交叉表上运行卡方检验,这将告诉我们这两个特征是否独立。
from scipy.stats import chi2_contingency
chi2_contingency(crosstab)
p 值 <0.05,因此我们可以拒绝特征之间没有关联的原假设,即两个特征之间存在统计上显着的关系。
由于这两个特征之间存在关联,我们可以选择删除其中一个。
到目前为止,我已经展示了在实现模型之前应用的特征选择策略。这些策略在第一轮特征选择以建立初始模型时很有用。但是一旦构建了模型,就可以获得有关模型性能中每个特征的适应度的更多信息。根据这些新信息,可以进一步确定要保留哪些功能。
df = df.dropna()
from sklearn.model_selection import train_test_split
df = pd.get_dummies(df, drop_first=True)
X = df.drop('price', axis=1)
y = df['price']
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)
from sklearn.linear_model import LinearRegression
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_train = scaler.fit_transform(X_train)
X_test = scaler.fit_transform(X_test)
X_train = pd.DataFrame(X_train, columns = X.columns.to_list())
X_test = pd.DataFrame(X_test, columns = X.columns.to_list())
model = LinearRegression()
model.fit(X_train, y_train)
现在我们已经拟合了模型,让我们进行另一轮特征选择。
6.特征系数
如果正在运行回归任务,则特征适应度的一个关键指标是回归系数(所谓的 beta 系数),它显示了模型中特征的相对贡献。有了这些信息,可以删除贡献很小或没有贡献的功能。
coeffs = model.coef_
index = X_train.columns.tolist()
(pd.DataFrame(coeffs, index = index, columns = ['coeff']).sort_values(by = 'coeff')
.plot(kind='barh', figsize=(4,10)))
某些特征beta 系数很小,对汽车价格的预测贡献不大。可以过滤掉这些特征:
# filter variables near zero coefficient value
temp = pd.DataFrame(coeffs, index = index, columns = ['coeff']).sort_values(by = 'coeff')
temp = temp[(temp['coeff']>1) | (temp['coeff']< -1)]
# drop those features
cols_coeff = temp.index.to_list()
X_train = X_train[cols_coeff]
X_test = X_test[cols_coeff]
7.p 值
在回归中,p 值告诉我们预测变量和目标之间的关系是否具有统计显著性。statsmodels 库提供了带有特征系数和相关 p 值的回归输出的函数。
如果某些特征不显著,可以将它们一个一个移除,然后每次重新运行模型,直到找到一组具有显着 p 值的特征,并通过更高的调整 R2 提高性能。
import statsmodels.api as sm
ols = sm.OLS(y, X).fit()
print(ols.summary())
8.方差膨胀因子 (VIF)
方差膨胀因子 (VIF) 是衡量多重共线性的另一种方法。它被测量为整体模型方差与每个独立特征的方差的比率。一个特征的高 VIF 表明它与一个或多个其他特征相关。根据经验:
VIF 是一种消除多重共线性特征的有用技术。对于我们的演示,将所有 VIF 高于10的删除。
from statsmodels.stats.outliers_influence import variance_inflation_factor
# calculate VIF
vif = pd.Series([variance_inflation_factor(X.values, i) for i in range(X.shape[1])], index=X.columns)
# display VIFs in a table
index = X_train.columns.tolist()
vif_df = pd.DataFrame(vif, index = index, columns = ['vif']).sort_values(by = 'vif', ascending=False)
vif_df[vif_df['vif']<10]
9.基于特征重要性选择
决策树/随机森林使用一个特征来分割数据,该特征最大程度地减少了杂质(以基尼系数杂质或信息增益衡量)。找到最佳特征是算法如何在分类任务中工作的关键部分。我们可以通过 feature_importances_ 属性访问最好的特征。
让我们在我们的数据集上实现一个随机森林模型并过滤一些特征。
from sklearn.ensemble import RandomForestClassifier
# instantiate model
model = RandomForestClassifier(n_estimators=200, random_state=0)
# fit model
model.fit(X,y)
# feature importance
importances = model.feature_importances_
# visualization
cols = X.columns
(pd.DataFrame(importances, cols, columns = ['importance'])
.sort_values(by='importance', ascending=True)
.plot(kind='barh', figsize=(4,10)))
上面的输出显示了每个特征在减少每个节点/拆分处的重要性。
由于随机森林分类器有很多估计量(例如上面例子中的 200 棵决策树),可以用置信区间计算相对重要性的估计值。
std = np.std([i.feature_importances_ for i in model.estimators_], axis=0)
feat_with_importance = pd.Series(importances, X.columns)
fig, ax = plt.subplots(figsize=(12,5))
feat_with_importance.plot.bar(yerr=std, ax=ax)
ax.set_title("Feature importances")
ax.set_ylabel("Mean decrease in impurity")
现在我们知道了每个特征的重要性,可以手动(或以编程方式)确定保留哪些特征以及删除哪些特征。
10.使用 Scikit Learn 自动选择特征
sklearn 库中有一个完整的模块,只需几行代码即可处理特征选择。
sklearn 中有许多自动化流程,但这里我只展示一些:
# import modules
from sklearn.feature_selection import (SelectKBest, chi2, SelectPercentile, SelectFromModel, SequentialFeatureSelector, SequentialFeatureSelector)
基于卡方的技术根据一些预定义的分数选择特定数量的用户定义特征 (k)。这些分数是通过计算 X(独立)和 y(因)变量之间的卡方统计量来确定的。在 sklearn 中,需要做的就是确定要保留多少特征。如果想保留 10 个功能,实现将如下所示:
# select K best features
X_best = SelectKBest(chi2, k=10).fit_transform(X,y)