總結了 11 種特徵選擇策略
太多的特徵會增加模型的複雜性和過擬合,而太少的特徵會導致模型的擬合不足。將模型優化爲足夠複雜以使其性能可推廣,但又足夠簡單易於訓練、維護和解釋是特徵選擇的主要工作。
“特徵選擇” 意味着可以保留一些特徵並放棄其他一些特徵。本文的目的是概述一些特徵選擇策略:
-
刪除未使用的列
-
刪除具有缺失值的列
-
不相關的特徵
-
低方差特徵
-
多重共線性
-
特徵係數
-
p 值
-
方差膨脹因子 (VIF)
-
基於特徵重要性的特徵選擇
-
使用 sci-kit learn 進行自動特徵選擇
-
主成分分析 (PCA)
該演示的數據集在 MIT 許可下發布,來自 PyCaret——一個開源的低代碼機器學習庫。點擊瞭解詳情👉一文徹底掌握自動機器學習 AutoML: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://github.com/mabalam/feature_selection
作者:Mahbubul Alam
轉自:DeepHub IMBA
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/crumndz38w4AwfbctUdVtA