共计 9102 个字符,预计需要花费 23 分钟才能阅读完成。
太多的特色会减少模型的复杂性和过拟合,而太少的特色会导致模型的拟合有余。将模型优化为足够简单以使其性能可推广,但又足够简略易于训练、保护和解释是特征选择的次要工作。
“特征选择”意味着能够保留一些特色并放弃其余一些特色。本文的目标是概述一些特征选择策略:
- 删除未应用的列
- 删除具备缺失值的列
- 不相干的特色
- 低方差特色
- 多重共线性
- 特色系数
- p 值
- 方差收缩因子 (VIF)
- 基于特色重要性的特征选择
- 应用 sci-kit learn 进行主动特征选择
- 主成分剖析 (PCA)
该演示的数据集在 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')
当初让咱们深入研究特征选择的 11 种策略。
删除未应用的列
当然,最简略的策略是你的直觉。尽管是直觉,但有时很有用的,某些列在最终模型中不会以任何模式应用(例如“ID”、“FirstName”、“LastName”等列)。如果您晓得某个特定列将不会被应用,请随时将其删除。在咱们的数据中,没有一列有这样的问题所以,我在此步骤中不删除任何列。
删除具备缺失值的列
缺失值在机器学习中是不可承受的,因而咱们会采纳不同的策略来清理缺失数据(例如插补)。然而如果列中短少大量数据,那么齐全删除它是十分好的办法。
# 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
不相干的特色
无论算法是回归(预测数字)还是分类(预测类别),特色都必须与指标相干。如果一个特色没有体现出相关性,它就是一个次要的打消指标。能够别离测试数值和分类特色的相关性。
数值变量
# correlation between target and features
(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)
柴油车的中位价高于汽油车。这意味着这个分类变量能够解释汽车价格,所以应放弃它。能够像这样独自查看每个分类列。
低方差特色
检查一下咱们的特色的差别:
import numpy as np
# variance of numeric features
(df
.select_dtypes(include=np.number)
.var()
.astype('str'))
这里的“bore”具备极低的方差,尽管这是删除的候选者。在这个非凡的例子中,我不违心删除它,因为它的值在 2.54 和 3.94 之间,因而方差很低:
df['bore'].describe()
多重共线性
当任何两个特色之间存在相关性时,就会呈现多重共线性。在机器学习中,冀望每个特色都应该独立于其余特色,即它们之间没有共线性。高马力车辆往往具备高发动机尺寸。所以你可能想打消其中一个,让另一个决定指标变量——价格。
咱们能够别离测试数字和分类特色的多重共线性:
数值变量
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 值、自由度和预期频率数组。
p 值 <0.05,因而咱们能够回绝特色之间没有关联的原假如,即两个特色之间存在统计上显着的关系。
因为这两个特色之间存在关联,咱们能够抉择删除其中一个。
到目前为止,我曾经展现了在实现模型之前利用的特征选择策略。这些策略在第一轮特征选择以建设初始模型时很有用。然而一旦构建了模型,就能够取得无关模型性能中每个特色的适应度的更多信息。依据这些新信息,能够进一步确定要保留哪些性能。
上面咱们应用最简略的线性模型展现其中的一些办法。
# drop columns with missing values
df = df.dropna()
from sklearn.model_selection import train_test_split
# get dummies for categorical features
df = pd.get_dummies(df, drop_first=True)
# X features
X = df.drop('price', axis=1)
# y target
y = df['price']
# split data into training and testing set
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
# scaling
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_train = scaler.fit_transform(X_train)
X_test = scaler.fit_transform(X_test)
# convert back to dataframe
X_train = pd.DataFrame(X_train, columns = X.columns.to_list())
X_test = pd.DataFrame(X_test, columns = X.columns.to_list())
# instantiate model
model = LinearRegression()# fit
model.fit(X_train, y_train)
当初咱们曾经拟合了模型,让咱们进行另一轮特征选择。
特色系数
如果正在运行回归工作,则特色适应度的一个要害指标是回归系数(所谓的 beta 系数),它显示了模型中特色的绝对奉献。有了这些信息,能够删除奉献很小或没有奉献的性能。
# feature coefficients
coeffs = model.coef_
# visualizing coefficients
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]
p 值
在回归中,p 值通知咱们预测变量和指标之间的关系是否具备统计显著性。statsmodels 库提供了带有特色系数和相干 p 值的回归输入的函数。
如果某些特色不显著,能够将它们一个一个移除,而后每次从新运行模型,直到找到一组具备显着 p 值的特色,并通过更高的调整 R2 进步性能。
import statsmodels.api as sm
ols = sm.OLS(y, X).fit()
print(ols.summary())
方差收缩因子 (VIF)
方差收缩因子 (VIF) 是掂量多重共线性的另一种办法。它被测量为整体模型方差与每个独立特色的方差的比率。一个特色的高 VIF 表明它与一个或多个其余特色相干。依据教训:
- VIF = 1 示意无相关性
- VIF = 1-5 中等相关性
- VIF >5 高相干
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]
基于特色重要性抉择
决策树 / 随机森林应用一个特色来宰割数据,该特色最大水平地缩小了杂质 (以基尼系数杂质或信息增益掂量)。找到最佳特色是算法如何在分类工作中工作的要害局部。咱们能够通过 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 棵决策树),能够用置信区间计算绝对重要性的估计值。
# calculate standard deviation of feature importances
std = np.std([i.feature_importances_ for i in model.estimators_], axis=0)
# visualization
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")
当初咱们晓得了每个特色的重要性,能够手动(或以编程形式)确定保留哪些特色以及删除哪些特色。
应用 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)
# number of best features
X_best.shape[1]
>> 10
如果有大量特色,能够指定要保留的特色百分比。假如咱们想要保留 75% 的特色并抛弃残余的 25%:
# keep 75% top features
X_top = SelectPercentile(chi2, percentile = 75).fit_transform(X,y)
# number of best features
X_top.shape[1]
>> 36
正则化
正则化缩小了过拟合。如果你有太多的特色,正则化管制它们的成果,或者通过放大特色系数(称为 L2 正则化)或将一些特色系数设置为零(称为 L1 正则化)。
一些模型具备内置的 L1/L2 正则化作为超参数来惩办特色。能够应用转换器 SelectFromModel 打消这些性能。
让咱们实现一个带有惩办 = ‘l1’ 的 LinearSVC 算法。而后应用 SelectFromModel 删除一些性能。
# implement algorithm
from sklearn.svm import LinearSVC
model = LinearSVC(penalty= 'l1', C = 0.002, dual=False)
model.fit(X,y)
# select features using the meta transformer
selector = SelectFromModel(estimator = model, prefit=True)
X_new = selector.transform(X)
X_new.shape[1]
>> 2
# names of selected features
feature_names = np.array(X.columns)
feature_names[selector.get_support()]
>> array(['wheel-base', 'horsepower'], dtype=object)
序贯法
序贯法是一种经典的统计技术。在这种状况下一次增加 / 删除一个性能并查看模型性能,直到它针对需要进行优化。
序贯法有两种变体。前向抉择技术从 0 特色开始,而后增加一个最大水平地缩小谬误的特色;而后增加另一个特色,依此类推。
向后抉择在相同的方向上起作用。模型从蕴含的所有特色开始并计算误差;而后它打消了一个能够进一步缩小误差的特色。反复该过程,直到保留所需数量的特色。
# instantiate model
model = RandomForestClassifier(n_estimators=100, random_state=0)
# select features
selector = SequentialFeatureSelector(estimator=model, n_features_to_select=10, direction='backward', cv=2)
selector.fit_transform(X,y)
# check names of features selected
feature_names = np.array(X.columns)
feature_names[selector.get_support()]
>> array(['bore', 'make_mitsubishi', 'make_nissan', 'make_saab',
'aspiration_turbo', 'num-of-doors_two', 'body style_hatchback', 'engine-type_ohc', 'num-of-cylinders_twelve', 'fuel-system_spdi'], dtype=object)
主成分剖析 (PCA)
PCA 的次要目标是升高高维特色空间的维数。原始特色被从新投影到新的维度(即主成分)。最终目标是找到最能解释数据方差的特色数量。
# import PCA module
from sklearn.decomposition import PCA
# scaling data
X_scaled = scaler.fit_transform(X)
# fit PCA to data
pca = PCA()
pca.fit(X_scaled)
evr = pca.explained_variance_ratio_
# visualizing the variance explained by each principal components
plt.figure(figsize=(12, 5))
plt.plot(range(0, len(evr)), evr.cumsum(), marker="o", linestyle="--")
plt.xlabel("Number of components")
plt.ylabel("Cumulative explained variance")
20 个主成分解释了超过 80% 的方差,因而能够将模型拟合到这 20 个成分(特色)。能够预先确定方差阈值并抉择所需的主成分数量。
总结
这是对可利用于特征选择的各种技术的有用指南。在拟合模型之前利用了一些技术,例如删除具备缺失值的列、不相干的列、具备多重共线性的列以及应用 PCA 进行降维,而在根本模型实现之后利用其余技术,例如特色系数、p 值、VIF 等。尽管不会在一个我的项目中齐全应用所有策略,这些策略都是咱们进行测试的方向。
本文代码:https://www.overfit.cn/post/6d872f2534f2460493caa108cc546c15
作者:Mahbubul Alam