機器學習之特征選擇(Feature Selection)


1 引言

  特征提取和特征選擇作為機器學習的重點內容,可以將原始數據轉換為更能代表預測模型的潛在問題和特征的過程,可以通過挑選最相關的特征,提取特征和創造特征來實現。要想學習特征選擇必然要了解什么是特征提取和特征創造,得到數據的特征之后對特征進行精煉,這時候就要用到特征選擇。本文主要介紹特征選擇的三種方法:過濾法(filter)、包裝法(wrapper)和嵌入法(embedded)。

特征提取(Feature Extraction):從文字,圖像,聲音等其他非結構化數據中提取新信息作為特征。比如說,從淘寶寶貝的名稱中提取出產品類別,產品顏色,是否是網紅產品等等。

 

特征創造(Feature Creation):把現有特征進行組合,或互相計算,得到新的特征。比如說,我們有一列特征是速度,一列特征是距離,我們就可以通過讓兩列相處,創造新的特征:通過距離所花的時間。

 

特征選擇(Feature Selection):從所有的特征中,選擇出有意義,對模型有幫助的特征,以避免必須將所有特征都導入模型去訓練的情況。

2 Filter過濾法

  過濾法可以理解為在機器學習算法之前的預處理,過濾法特征選擇的過程完全獨立與任何機器學習算法。根據對特征經過統計檢驗之后得到的分數,來篩選掉一些相對來說無用的特征,從而優化特征集。

 

過濾法適用場景:在需要遍歷特征或升維的算法之前,對特征進行過濾。

過濾法的目的:在維持算法表現的前提下,幫助算法降低計算成本。

2.1 方差過濾

  Variance Threshold是通過特征本身方差來篩選特征的類。比如一個特征本身的方差很小,就表示樣本在這個特征上基本沒有差異,可能特征中的大多數值都一樣,甚至整個特征的取值都相同,那這個特征對於樣本區分沒有什么作用。所以無論接下來的特征工程要做什么,都要優先消除方差為0的特征。VarianceThreshold有重要參數threshold,表示方差的閾值,表示舍棄所有方差小於threshold的特征,不填默認為0,即刪除所有的記錄都相同的特征。下面代碼簡單的實現了方差過濾:

import pandas as pd
from sklearn.feature_selection import VarianceThreshold
data = pd.read_csv(r"./train.csv") x = data.iloc[:,1:] y = data.iloc[:,0] print(x.shape) selector = VarianceThreshold() x_var0 = selector.fit_transform(x) print(x_var0.shape)

   從結果中可以看到原本數據中有784個特征,經過閾值為 0 的方差過濾之后,剩下708個特征,也就是說之前有76個特征的方差都為0。剩下的708個特征還是比較多的,並不能滿足我們的需求,此時我們還需要進一步的特征選擇。由於單純調整閾值比較抽象,我們並不知道特定閾值下會留下多少個特征,留下特征過多或者過少都對我們的結果不利,所以我們可以留下指定數量的特征,比如留下一半的特征,找到特征方差的中位數,再將這個中位數作為 threshold 的值就可以讓特征總數減半,代碼如下:

import pandas as pd
import numpy as np
from sklearn.feature_selection import VarianceThreshold

data = pd.read_csv(r"./train.csv")
x = data.iloc[:,1:]
y = data.iloc[:,0]
print(x.shape)
selector = VarianceThreshold(np.median(x.var().values))
x_feature_selection = selector.fit_transform(x)
print(x_feature_selection.shape)

   如果特征是二分類,特征的取值就是伯努利隨機變量,這些變量的方差計算公式為:Var[X] = p (1 - p),其中 X 為特征矩陣,p為二分類特征中的一類在這個特征中所占的概率。那么假設 p = 0.8,即二分類中某種分類占到80%以上的時候刪除特征。代碼如下

import pandas as pd
import numpy as np
from sklearn.feature_selection import VarianceThreshold
data = pd.read_csv(r"./train.csv")
x = data.iloc[:,1:]
y = data.iloc[:,0]
print(x.shape)
selector = VarianceThreshold(0.8 *(1-0.8))
x_feature_selection = selector.fit_transform(x)
print(x_feature_selection.shape)

   K-近鄰算法(KNN)是一種比較簡單的分類算法,其原理是利用每個樣本到其他樣本點的距離來判斷每個樣本點的相似度,然后對樣本進行分類。KNN必須遍歷每個特征和樣本,因而特征越多,KNN所需要的計算力也就越大。

  隨機森林或隨機決策森林是用於分類,回歸和其他任務的集成學習方法,其通過在訓練時構建多個決策樹並輸出作為類的模式(分類)或平均預測(回歸)的類來操作。個別樹木。隨機決策森林糾正決策樹過度擬合其訓練集的習慣。隨機森林隨機的選取特征進行分值,本身的運算非常迅速。

  實驗證明,對特征進行方差過濾之后,KNN的准確率稍有提升,運行時間降低了三分之一。隨機森林的准確率略低於KNN,但是花費的算力非常少,不到KNN計算時間的百分之 1 。另外隨機森林的准確率略微上升,運行時間並沒與什么變化。因此方差過濾並不是適用於所有的算法,因為過濾之后模型可能變好也可能變性能下降。我們就需要針對數據集去進行嘗試,也就是調參,選出最優的參數,畫學習曲線就可以找到比較好的參數點。但是現實中一般不會花費太多時間在方差過濾的調參上,而是使用閾值為 0 或者閾值很小的方差進行過濾,消除一些明顯用不到的特征然后選取其他的特征選擇方法繼續削減特征數量。

2.2 相關性過濾

  一般情況下特征如果和標簽的相關性比較大的話,這樣的特征能夠為我們提供大量的信息。如果特征與標簽無關,只會白白浪費我們的算力,還可能給模型帶來噪聲。在 sklearn 中有三種常用的方法來評判特征和標簽之間的相關性:卡方、F檢驗和互信息。

卡方過濾

  卡方過濾是專門針對離散型標簽(即分類問題)的相關性過濾。卡方檢驗類feature_selection.chi2計算每個非負特征和標簽之間的卡方統計量,並依照卡方統計量由高到低為特征排名。再結合feature_selection.SelectKBest這個可以輸入”評分標准“來選出前K個分數最高的特征的類,我們可以借此除去最可能獨立於標簽,與我們分類目的無關的特征。下面代碼簡單實現了卡方過濾:

from sklearn.ensemble import RandomForestClassifier as RFC
from sklearn.model_selection import cross_val_score
from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import chi2

#留下300個特征
X_fschi = SelectKBest(chi2, k=300).fit_transform(X_fsvar, y)
X_fschi.shape
#驗證模型效果
cross_val_score(RFC(n_estimators=10,random_state=0),X_fschi,y,cv=5).mean()

  K值變化時,模型的評分也會跟着變化,手動去調整一個一個參數的話效率非常低,我們可以使用學習曲線來獲得一個最優的超參數K。代碼如下:

import pandas as pd
import numpy as np
from sklearn.feature_selection import VarianceThreshold
from sklearn.ensemble import RandomForestClassifier as RFC
from sklearn.model_selection import cross_val_score
from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import chi2
import matplotlib.pyplot as plt
data = pd.read_csv(r"./train.csv")
x = data.iloc[:,1:]
y = data.iloc[:,0]
# print(x.shape)
selector = VarianceThreshold(np.median(x.var().values))
x_fsvar = selector.fit_transform(x)
score = []
for i in range(390,200,-10):
    x_fschi = SelectKBest(chi2,k = i).fit_transform(x_fsvar,y)
    once = cross_val_score(RFC(n_estimators=10,random_state=0),x_fschi,y,cv=5).mean()
    score.append(once)
plt.plot(range(390,200,-10),score)
plt.show()

  從曲線中我們可以看到,隨着K值不斷的在呢個價模型的表現不斷上升,這說明K越大越好,數據中所有的特征都是和標簽相關的。但是這個程序運行時間比較長,我們可以用另一種更好的方法選擇 K :看 p 值選K。

  卡方阿金艷的本質是推測數據之間的差異,卡方檢驗返回卡方值和 P 值兩個統計量,其中卡方值很難界定有效的范圍,而 p 值我們一般使用 0.01 或 0.05 作為顯著性水平,即p值判斷的邊界。

p值  <=0.05 或0.01 >0.05 或 0.01
數據差異 差異不是自然形成的 這些差異是很自然的樣本誤差
相關性 兩組數據是相關的 兩組數據是相互獨立的
原假設  拒絕原假設,接受備擇假設 接受原假設

  卡方值大,p值小於0.05的特征是和標簽相關聯的特征。調用 SelectKBest,可以直接從chi實例化后的模型中獲取各個特征所對應的卡方值和 p 值。我們只需要算出來p值大於0.05 的特征有幾個,這個個數就是我們想要得到的K值。這里得到的 p 值全為 0,也就是說對於該數據集,方差過濾已經把所有和標簽無關的特征都剔除了。 

chivalue, pvalues_chi = chi2(X_fsvar,y)
print(chivalue)
print(pvalues_chi) #k取多少?我們想要消除所有p值大於設定值,比如0.05或0.01的特征: k = chivalue.shape[0] - (pvalues_chi > 0.05).sum() #X_fschi = SelectKBest(chi2, k=填寫具體的k).fit_transform(X_fsvar, y) #cross_val_score(RFC(n_estimators=10,random_state=0),X_fschi,y,cv=5).mean()

F檢驗

  F檢驗,又稱ANOVA,方差齊性檢驗,是用來捕捉每個特征與標簽之間的線性關系的過濾方法。它即可以做回歸也可以做分類,因此包含feature_selection.f_classif(F檢驗分類)和feature_selection.f_regression(F檢驗回歸)兩個類。其中F檢驗分類用於標簽是離散型變量的數據,而F檢驗回歸用於標簽是連續型變量的數據。

  和卡方檢驗一樣,這兩個類需要和類SelectKBest連用,並且我們也可以直接通過輸出的統計量來判斷我們到底要設置一個什么樣的K。需要注意的是,F檢驗在數據服從正態分布時效果會非常穩定,因此如果使用F檢驗過濾,我們會先將數據轉換成服從正態分布的方式。

  F檢驗的本質是尋找兩組數據之間的線性關系,其原假設是”數據不存在顯著的線性關系“。它返回F值和p值兩個統計量。和卡方過濾一樣,我們希望選取p值小於 0.05 或 0.01 的特征,這些特征與標簽時顯著線性相關的,而p值大於0.05或0.01的特征則被我們認為是和標簽沒有顯著線性關系的特征,應該被刪除。以F檢驗的分類為例,我們繼續在數字數據集上來進行特征選擇:

chivalue, pvalues_chi = chi2(X_fsvar,y)
chivalue
pvalues_chi
#k取多少?我們想要消除所有p值大於設定值,比如0.05或0.01的特征:
k = chivalue.shape[0] - (pvalues_chi > 0.05).sum()
#X_fschi = SelectKBest(chi2, k=填寫具體的k).fit_transform(X_fsvar, y)
#cross_val_score(RFC(n_estimators=10,random_state=0),X_fschi,y,cv=5).mean()
from sklearn.feature_selection import f_classif
F, pvalues_f = f_classif(X_fsvar,y)
F
pvalues_f
k = F.shape[0] - (pvalues_f > 0.05).sum()
#X_fsF = SelectKBest(f_classif, k=填寫具體的k).fit_transform(X_fsvar, y)
#cross_val_score(RFC(n_estimators=10,random_state=0),X_fsF,y,cv=5).mean()

 

  得到的結論和我們用卡方過濾得到的結論一模一樣:沒有任何特征的p值大於0.01,所有的特征都是和標簽相關,因此不需要進行相關性過濾。

互信息法

  互信息法是用來捕捉每個特征與標簽之間的任意關系(包括線性和非線性關系)的過濾方法。和F檢驗相似,它既可以做回歸也可以做分類,並且包含兩個類feature_selection.mutual_info_classif(互信息分類)和feature_selection.mutual_info_regression(互信息回歸)。這兩個類的用法和參數都和F檢驗一模一樣,不過互信息法比F檢驗更加強大,F檢驗只能夠找出線性關系,而互信息法可以找出任意關系。

  互信息法不返回 p 值或 F 值類似的統計量,它返回“每個特征與目標之間的互信息量的估計”,這個估計量在[0,1]之間取值,為0則表示兩個變量獨立,為1則表示兩個變量完全相關。以互信息分類為例的代碼如下

from sklearn.feature_selection import mutual_info_classif as MIC
result = MIC(X_fsvar,y)
k = result.shape[0] - sum(result <= 0)
#X_fsmic = SelectKBest(MIC, k=填寫具體的k).fit_transform(X_fsvar, y)
#cross_val_score(RFC(n_estimators=10,random_state=0),X_fsmic,y,cv=5).mean() 

  最終得到的互信息量都大於 0,表明所有特征都和標簽相關。

3 Embedded嵌入法

  嵌入法是一種算法自己決定使用哪些特征的方法,即特征選擇和算法訓練同時進行。在使用嵌入法時,我們先使用某些機器學習的算法和模型進行訓練,得到各個特征的權值系數,根據權值系數從大到小選擇特征。這些權值系數往往代表了特征對於模型的某種貢獻或某種重要性,比如決策樹和樹的集成模型中的feature_importances_屬性,可以列出各個特征對樹的建立的貢獻,我們就可以基於這種貢獻的評估,找出對模型建立最有用的特征。因此相比於過濾法,嵌入法的結果會更加精確到模型的效用本身,對於提高模型效力有更好的效果。並且,由於考慮特征對模型的貢獻,因此無關的特征(需要相關性過濾的特征)和無區分度的特征(需要方差過濾的特征)都會因為缺乏對模型的貢獻而被刪除掉,可謂是過濾法的進化版。

  由於嵌入法時算法自身決定哪些特征可以使用,很難去界定一個標准來判斷特征是否是有效的,所以並不會像過濾法那樣根據統計知識和指標來對特征進行衡量(如p值應當低於顯著水平 0.05)。針對不同的算法,模型的權值系數也會不同,所以不同模型的嵌入法使用起來也是不一樣的。此外,由於嵌入法選取特征時是根據算法來決定的,也就是說特征選擇的過程伴隨着算法的訓練過程,那么整個過程非常耗時耗力,這也是嵌入法的一個缺陷。下面我們會以隨機森林和決策樹模型來學習一下嵌入法。

 feature_selection.SelectFromModel

class sklearn.feature_selection.SelectFromModel (estimator, threshold=None, prefit=False, norm_order=1,max_features=None)

參數 說明
estimator

使用的模型評估器,只要是帶feature_importances或者coef_屬性,或帶有

l1 和 l2 懲罰項的模型都可以使用。

threshold 特征重要性的閾值,重要性低於這個閾值的特征都將被刪除
prefit

默認False,后的模型直接傳遞給構造函數。如果為True,則必須直接調用

fit和transform,不能使用fit_transform,並且SelectFromModel不能與

cross_val_score,GridSearchCV和克隆估計器的類似實用程序一起使用。

norm_order

K可輸入非零整數,正無窮,負無窮,默認值為1。載頻鼓起的coef_屬性高

於一維的情況下,用於過濾低於閾值的系數的向量的番薯的階數。

max_features

在閾值設定下,要選擇的最大特征數。要禁用閾值並僅根據max_features

選擇,請設置threshold = -np.inf

  前兩個參數 estimator 和 threshold在實際應用過程中比較重要,需要我們重點學習。以隨機森林為例,借助學習曲線幫助我們尋找最佳特征值。

from sklearn.feature_selection import SelectFromModel
from sklearn.ensemble import RandomForestClassifier as RFC
import numpy as np
import matplotlib.pyplot as plt
RFC_ = RFC(n_estimators =10,random_state=0) print(X_embedded.shape) #模型的維度明顯被降低了 #畫學習曲線來找最佳閾值 RFC_.fit(X,y).feature_importances_ threshold = np.linspace(0,(RFC_.fit(X,y).feature_importances_).max(),20) score = [] for i in threshold: X_embedded = SelectFromModel(RFC_,threshold=i).fit_transform(X,y) once = cross_val_score(RFC_,X_embedded,y,cv=5).mean() score.append(once) plt.plot(threshold,score) plt.show() X_embedded = SelectFromModel(RFC_,threshold=0.00067).fit_transform(X,y) X_embedded.shape print(cross_val_score(RFC_,X_embedded,y,cv=5).mean())

  通過學習曲線我們可以得到一個最佳的閾值,使得模型的分數達到 96%以上。因此嵌入法可以實現特征的選擇。

4 Wrapper包裝法

  包裝法也是一個特征選擇和算法訓練同時進行的方法,與嵌入法十分相似,它也是依賴於算法自身的選擇,比如coef_屬性或feature_importances_屬性來完成特征選擇。但不同的是,我們往往使用一個目標函數作為黑盒來幫助我們選取特征,而不是自己輸入某個評估指標或統計量的閾值。包裝法在初始特征集上訓練評估器,並且通過coef_屬性或通過feature_importances_屬性獲得每個特征的重要性。然后,從當前的一組特征中修剪最不重要的特征。在修剪的集合上遞歸地重復該過程,直到最終到達所需數量的要選擇的特征。區別於過濾法和嵌入法的一次訓練解決所有問題,包裝法要使用特征子集進行多次訓練,因此它所需要的計算成本是最高的。

   圖中的算法值得並不是我們最終涌過來導入數據的分類和回歸算法(即不是隨機森林),而是專業的數據挖掘算法,即我們的目標函數。這些數據挖掘算法的核心功能就是選取最佳特征子集。

  最典型的目標函數是遞歸特征消除法(Recursive feature elimination,簡寫為RFE)。它是一種貪婪的優化算法,旨在找到性能最佳的特征子集。 它反復創建模型,並在每次迭代時保留最佳特征或剔除最差特征,下一次迭代時,它會使用上一次建模中沒有被選中的特征來構建下一個模型,直到所有特征都耗盡為止。 然后,它根據自己保留或剔除特征的順序來對特征進行排名,最終選出一個最佳子集。包裝法的效果是所有特征選擇方法中最利於提升模型表現的,它可以使用很少的特征達到很優秀的效果。除此之外,在特征數目相同時,包裝法和嵌入法的效果能夠匹敵,不過它比嵌入法算得更見緩慢,所以也不適用於太大型的數據。相比之下,包裝法是最能保證模型效果的特征選擇方法。

 feature_selection.RFE

class sklearn.feature_selection.RFE (estimator, n_features_to_select=None, step=1, verbose=0)

參數 說明
estimator 使用的模型評估器。
n_feature_to_select 所需特征數
step 每次迭代中希望移除的特征數

  RFE類中有兩個比較重要的屬性,.support_:返回所有的特征的是否最后被選中的布爾矩陣,以及.ranking_返回特征的按數次迭代中綜合重要性的排名。類feature_selection.RFECV會在交叉驗證循環中執行RFE以找到最佳數量的特征,增加參數cv,其他用法都和RFE一模一樣。

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.ensemble import RandomForestClassifier as RFC
from sklearn.model_selection import cross_val_score
from sklearn.feature_selection import RFE

data = pd.read_csv(r"./train.csv")
x = data.iloc[:,1:]
y = data.iloc[:,0]
# print(x.shape)
RFC_ = RFC(n_estimators=10,random_state=0)
score = []
for i in range(1,751,50):
    x_wrapper = RFE(RFC_,n_features_to_select=i,step=50).fit_transform(x,y)
    once = cross_val_score(RFC_,x_wrapper,y,cv=5).mean()
    score.append(once)
plt.figure(figsize=[20,5])
plt.plot(range(1,751,50),score)
plt.xticks(range(1,751,50))
plt.show()

  結果可以看到,使用包裝法之后,只需要 50 個特征,模型的表現就已經達到了 90% 以上,比嵌入法和過濾法得到的特征子集要好很多。

5 總結

  本文講了過濾法、嵌入法和包裝法三種特征選擇方法。三種方法中過濾法最為簡單快速,需要的計算時間也最短,但是也較為粗略,實際應用過程中,通常只作為數據的預處理,剔除掉部分明顯不需要的特征,然后使用其他方法進一步特征選擇。嵌入式和包裝法更為精確,更適合具體到算法中去調整。計算量也較大,相應的運行時間也比較長。當數據量比較大時,優先使用方差過濾和互信息法對數據進行預處理,然后在使用其他的特征選擇方法。使用邏輯回歸時,優先使用嵌入法。使用支持向量機時,優先使用包裝法。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM