特征选择(feature_selection)
特征選擇
當數據預處理完成后,我們就要開始進行特征工程了。
在做特征選擇之前,有三件非常重要的事:跟數據提供者開會!跟數據提供者開會!跟數據提供者開會!
一定要抓住給你提供數據的人,尤其是理解業務和數據含義的人,跟他們聊一段時間。技術能夠讓模型起飛,前提是你和業務人員一樣理解數據。所以特征選擇的第一步,其實是根據我們的目標,用業務常識來選擇特征。來看完整版泰坦尼克號數據中的這些特征:
其中是否存活是我們的標簽。很明顯,以判斷“是否存活”為目的,票號,登船的艙門,乘客編號明顯是無關特征,可以直接刪除。姓名,艙位等級,船艙編號,也基本可以判斷是相關性比較低的特征。性別,年齡,船上的親人數量,這些應該是相關性比較高的特征。
所以,特征工程的第一步是:理解業務。
當然了,在真正的數據應用領域,比如金融,醫療,電商,我們的數據不可能像泰坦尼克號數據的特征這樣少,這樣明顯,那如果遇見極端情況,我們無法依賴對業務的理解來選擇特征,該怎么辦呢?我們有四種方法可以用來選擇特征:過濾法,嵌入法,包裝法,和降維算法。
#導入數據,讓我們使用digit recognizor數據來一展身手 import pandas as pd data = pd.read_csv('/Users/zhucan/Desktop/digit recognizor.csv') X = data.iloc[:,1:] y = data.iloc[:,0] X.shape #(42000, 784) """ 這個數據量相對夸張,如果使用支持向量機和神經網絡,很可能會直接跑不出來。使用KNN跑一次大概需要半個小時。 用這個數據舉例,能更夠體現特征工程的重要性。 """Filter過濾法
過濾方法通常用作預處理步驟,特征選擇完全獨立于任何機器學習算法。它是根據各種統計檢驗中的分數以及相關性的各項指標來選擇特征。
方差過濾
VarianceThreshold
這是通過特征本身的方差來篩選特征的類。比如一個特征本身的方差很小,就表示樣本在這個特征上基本沒有差異,可能特征中的大多數值都一樣,甚至整個特征的取值都相同,那這個特征對于樣本區分沒有什么作用。所以無論接下來的特征工程要做什么,都要優先消除方差為0的特征。VarianceThreshold有重要參數threshold,表示方差的閾值,表示舍棄所有方差小于threshold的特征,不填默認為0,即刪除所有的記錄都相同的特征。
from sklearn.feature_selection import VarianceThreshold selector = VarianceThreshold() #實例化,不填參數默認方差為0 X_var0 = selector.fit_transform(X) #獲取刪除不合格特征之后的新特征矩陣 #也可以直接寫成 X = VairanceThreshold().fit_transform(X) X_var0.shape #(42000, 708)可以看見,我們已經刪除了方差為0的特征,但是依然剩下了708多個特征,明顯還需要進一步的特征選擇。然而,如果我們知道我們需要多少個特征,方差也可以幫助我們將特征選擇一步到位。比如說,我們希望留下一半的特征,那可以設定一個讓特征總數減半的方差閾值,只要找到特征方差的中位數,再將這個中位數作為參數threshold的值輸入就好了:
import numpy as np X_fsvar = VarianceThreshold(np.median(X.var().values)).fit_transform(X) X_fsvar.shape #(42000, 392)當特征是二分類時,特征的取值就是伯努利隨機變量,這些變量的方差可以計算為:
Var?[X]=p(1?p)\operatorname{Var}[X]=p(1-p) Var[X]=p(1?p)其中X是特征矩陣,p是二分類特征中的一類在這個特征中所占的概率。
方差過濾對模型的影響
我們這樣做了以后,對模型效果會有怎樣的影響呢?在這里,我為大家準備了KNN和隨機森林分別在方差過濾前和方差過濾后運行的效果和運行時間的對比。KNN是K近鄰算法中的分類算法,其原理非常簡單,是利用每個樣本到其他樣本點的距離來判斷每個樣本點的相似度,然后對樣本進行分類。KNN必須遍歷每個特征和每個樣本,因而特征越多,KNN的計算也就會越緩慢。由于這一段代碼對比運行時間過長,所以我為大家貼出了代碼和結果。
我們從模塊neighbors導入KNeighborsClassfier縮寫為KNN,導入隨機森林縮寫為RFC,然后導入交叉驗證模塊和numpy。其中未過濾的數據是X和y,使用中位數過濾后的數據是X_fsvar,都是我們之前已經運行過的代碼。
可以看出,對于KNN,過濾后的效果十分明顯:準確率稍有提升,但平均運行時間減少了10分鐘,特征選擇過后算法的效率上升了1/3。那隨機森林又如何呢?
首先可以觀察到的是,隨機森林的準確率略遜于KNN,但運行時間卻連KNN的1%都不到,只需要十幾秒鐘。其次,方差過濾后,隨機森林的準確率也微弱上升,但運行時間卻幾乎是沒什么變化,依然是11秒鐘。
**為什么隨機森林運行如此之快?為什么方差過濾對隨機森林沒很大的有影響?**這是由于兩種算法的原理中涉及到的計算量不同。最近鄰算法KNN,單棵決策樹,支持向量機SVM,神經網絡,回歸算法,都需要遍歷特征或升維來進行運算,所以他們本身的運算量就很大,需要的時間就很長,因此方差過濾這樣的特征選擇對他們來說就尤為重要。但對于不需要遍歷特征的算法,比如隨機森林,它隨機選取特征進行分枝,本身運算就非常快速,因此特征選擇對它來說效果平平。這其實很容易理解,無論過濾法如何降低特征的數量,隨機森林也只會選取固定數量的特征來建模;而最近鄰算法就不同了,特征越少,距離計算的維度就越少,模型明顯會隨著特征的減少變得輕量。因此,過濾法的主要對象是:需要遍歷特征或升維的算法們,而過濾法的主要目的是:在維持算法表現的前提下,幫助算法們降低計算成本。
對受影響的算法來說,我們可以將方差過濾的影響總結如下:
在我們的對比當中,我們使用的方差閾值是特征方差的中位數,因此屬于閾值比較大,過濾掉的特征比較多的情況。我們可以觀察到,無論是KNN還是隨機森林,在過濾掉一半特征之后,模型的精確度都上升了。這說明被我們過濾掉的特征在當前隨機模式(random_state = 0)下大部分是噪音。那我們就可以保留這個去掉了一半特征的數據,來為之后的特征選擇做準備。當然,如果過濾之后模型的效果反而變差了,我們就可以認為,被我們過濾掉的特征中有很多都有有效特征,那我們就放棄過濾,使用其他手段來進行特征選擇。
選取超參數threshold
我們怎樣知道,方差過濾掉的到底時噪音還是有效特征呢?過濾后模型到底會變好還是會變壞呢?答案是:每個數據集不一樣,只能自己去嘗試。這里的方差閾值,其實相當于是一個超參數,要選定最優的超參數,我們可以畫學習曲線,找模型效果最好的點。但現實中,我們往往不會這樣去做,因為這樣會耗費大量的時間。我們只會使用閾值為0或者閾值很小的方差過濾,來為我們優先消除一些明顯用不到的特征,然后我們會選擇更優的特征選擇方法繼續削減特征數量。
相關性過濾
方差挑選完畢之后,我們就要考慮下一個問題:相關性了。我們希望選出與標簽相關且有意義的特征,因為這樣的特征能夠為我們提供大量信息。如果特征與標簽無關,那只會白白浪費我們的計算內存,可能還會給模型帶來噪音。在sklearn當中,我們有三種常用的方法來評判特征與標簽之間的相關性:卡方,F檢驗,互信息。
卡方過濾
卡方過濾是專門針對離散型標簽(即分類問題)的相關性過濾。卡方檢驗類feature_selection. chi2計算每個非負特征和標簽之間的卡方統計量,并依照卡方統計量由高到低為特征排名。再結合feature_selection.SelectKBest這個可以輸入”評分標準“來選出前K個分數最高的特征的類,我們可以借此除去最可能獨立于標簽,與我們分類目的無關的特征。
另外,如果卡方檢驗檢測到某個特征中所有的值都相同,會提示我們使用方差先進行方差過濾。并且,剛才我們已經驗證過,當我們使用方差過濾篩選掉一半的特征后,模型的表現時提升的。因此在這里,我們使用threshold=中位數時完成的方差過濾的數據來做卡方檢驗(如果方差過濾后模型的表現反而降低了,那我們就不會使用方差過濾后的數據,而是使用原數據):
可以看出,模型的效果降低了,這說明我們在設定k=300的時候刪除了與模型相關且有效的特征,我們的K值設置得太小,要么我們需要調整K值,要么我們必須放棄相關性過濾。當然,如果模型的表現提升,則說明我們的相關性過濾是有效的,是過濾掉了模型的噪音的,這時候我們就保留相關性過濾的結果。
選取超參數K
那如何設置一個最佳的K值呢?在現實數據中,數據量很大,模型很復雜的時候,我們也許不能先去跑一遍模型看看效果,而是希望最開始就能夠選擇一個最優的超參數k。那第一個方法,就是我們之前提過的學習曲線:
通過這條曲線,我們可以觀察到,隨著K值的不斷增加,模型的表現不斷上升,這說明,K越大越好,數據中所有的特征都是與標簽相關的。但是運行這條曲線的時間同樣也是非常地長,接下來我們就來介紹一種更好的選擇k的方法:看p值選擇k。
卡方檢驗的本質是推測兩組數據之間的差異,其檢驗的原假設是”兩組數據是相互獨立的”。卡方檢驗返回卡方值和P值兩個統計量,其中卡方值很難界定有效的范圍,而p值,我們一般使用0.01或0.05作為顯著性水平,即p值判斷的邊界,具體我們可以這樣來看:
從特征工程的角度,我們希望選取卡方值很大,p值小于0.05的特征,即和標簽是相關聯的特征。而調用SelectKBest之前,我們可以直接從chi2實例化后的模型中獲得各個特征所對應的卡方值和P值。
可以觀察到,所有特征的p值都是0,這說明對于digit recognizor這個數據集來說,方差驗證已經把所有和標簽無關的特征都剔除了,或者這個數據集本身就不含與標簽無關的特征。在這種情況下,舍棄任何一個特征,都會舍棄對模型有用的信息,而使模型表現下降,因此在我們對計算速度感到滿意時,我們不需要使用相關性過濾來過濾我們的數據。如果我們認為運算速度太緩慢,那我們可以酌情刪除一些特征,但前提是,我們必須犧牲模型的表現。接下來,我們試試看用其他的相關性過濾方法驗證一下我們在這個數據集上的結論。
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檢驗的分類為例,我們繼續在數字數據集上來進行特征選擇:
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則表示兩個變量完全相關。以互信息分類為例的代碼如下:
所有特征的互信息量估計都大于0,因此所有特征都與標簽相關。
當然了,無論是F檢驗還是互信息法,大家也都可以使用學習曲線,只是使用統計量的方法會更加高效。當統計量判斷已經沒有特征可以刪除時,無論用學習曲線如何跑,刪除特征都只會降低模型的表現。當然了,如果數據量太龐大,模型太復雜,我們還是可以犧牲模型表現來提升模型速度,一切都看大家的具體需求。
Embedded嵌入法
嵌入法是一種讓算法自己決定使用哪些特征的方法,即特征選擇和算法訓練同時進行。在使用嵌入法時,我們先使用某些機器學習的算法和模型進行訓練,得到各個特征的權值系數,根據權值系數從大到小選擇特征。這些權值系數往往代表了特征對于模型的某種貢獻或某種重要性,比如決策樹和樹的集成模型中的feature_importances_屬性,可以列出各個特征對樹的建立的貢獻,我們就可以基于這種貢獻的評估,找出對模型建立最有用的特征。因此相比于過濾法,嵌入法的結果會更加精確到模型的效用本身,對于提高模型效力有更好的效果。并且,由于考慮特征對模型的貢獻,因此無關的特征(需要相關性過濾的特征)和無區分度的特征(需要方差過濾的特征)都會因為缺乏對模型的貢獻而被刪除掉,可謂是過濾法的進化版。
然而,嵌入法也不是沒有缺點。
過濾法中使用的統計量可以使用統計知識和常識來查找范圍(如p值應當低于顯著性水平0.05),而嵌入法中使用的權值系數卻沒有這樣的范圍可找——我們可以說,權值系數為0的特征對模型絲毫沒有作用,但當大量特征都對模型有貢獻且貢獻不一時,我們就很難去界定一個有效的臨界值。這種情況下,模型權值系數就是我們的超參數,我們或許需要學習曲線,或者根據模型本身的某些性質去判斷這個超參數的最佳值究竟應該是多少。在我們之后的學習當中,每次講解新的算法,我都會為大家提到這個算法中的特征工程是如何處理,包括具體到每個算法的嵌入法如何使用。在這堂課中,我們會為大家講解隨機森林和決策樹模型的嵌入法。
另外,嵌入法引入了算法來挑選特征,因此其計算速度也會和應用的算法有很大的關系。如果采用計算量很大,計算緩慢的算法,嵌入法本身也會非常耗時耗力。并且,在選擇完畢之后,我們還是需要自己來評估模型。
- feature_selection.SelectFromModel
class sklearn.feature_selection.SelectFromModel (estimator, threshold=None, prefit=False, norm_order=1,max_features=None)
SelectFromModel是一個元變換器,可以與任何在擬合后具有coef_,feature_importances_屬性或參數中可選懲罰項的評估器一起使用(比如隨機森林和樹模型就具有屬性feature_importances_,邏輯回歸就帶有l1和l2懲罰項,線性支持向量機也支持l2懲罰項)。
對于有feature_importances_的模型來說,若重要性低于提供的閾值參數,則認為這些特征不重要并被移除。feature_importances_的取值范圍是[0,1],如果設置閾值很小,比如0.001,就可以刪除那些對標簽預測完全沒貢獻的特征。如果設置得很接近1,可能只有一兩個特征能夠被留下。
我們重點要考慮的是前兩個參數。在這里,我們使用隨機森林為例,則需要學習曲線來幫助我們尋找最佳特征值。
從圖像上來看,隨著閾值越來越高,模型的效果逐漸變差,被刪除的特征越來越多,信息損失也逐漸變大。但是在0.00134之前,模型的效果都可以維持在0.93以上,因此我們可以從中挑選一個數值來驗證一下模型的效果。
可以看出,特征個數瞬間縮小到324多,這比我們在方差過濾的時候選擇中位數過濾出來的結果392列要小,并且交叉驗證分數0.9399高于方差過濾后的結果0.9388,這是由于嵌入法比方差過濾更具體到模型的表現的緣故,換一個算法,使用同樣的閾值,效果可能就沒有這么好了。
和其他調參一樣,我們可以在第一條學習曲線后選定一個范圍,使用細化的學習曲線來找到最佳值:
#======【TIME WARNING:10 mins】======# score2 = [] for i in np.linspace(0,0.00134,20):X_embedded = SelectFromModel(RFC_,threshold=i).fit_transform(X,y)once = cross_val_score(RFC_,X_embedded,y,cv=5).mean()score2.append(once) plt.figure(figsize=[20,5]) plt.plot(np.linspace(0,0.00134,20),score2) plt.xticks(np.linspace(0,0.00134,20)) plt.show()
查看結果,果然0.00067并不是最高點,真正的最高點0.000564已經將模型效果提升到了94%以上。我們使用0.000564來跑一跑我們的SelectFromModel:
得出的特征數目依然小于方差篩選,并且模型的表現也比沒有篩選之前更高,已經完全可以和計算一次半小時的KNN相匹敵(KNN的準確率是96.58%),接下來再對隨機森林進行調參,準確率應該還可以再升高不少。可見,在嵌入法下,我們很容易就能夠實現特征選擇的目標:減少計算量,提升模型表現。因此,比起要思考很多統計量的過濾法來說,嵌入法可能是更有效的一種方法。然而,在算法本身很復雜的時候,過濾法的計算遠遠比嵌入法要快,所以大型數據中,我們還是會優先考慮過濾法。
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_features_to_select是想要選擇的特征個數,step表示每次迭代中希望移除的特征個數。除此之外,RFE類有兩個很重要的屬性,.support_:返回所有的特征的是否最后被選中的布爾矩陣,以及.ranking_返回特征的按數次迭代中綜合重要性的排名。類feature_selection.RFECV會在交叉驗證循環中執行RFE以找到最佳數量的特征,增加參數cv,其他用法都和RFE一模一樣。
from sklearn.feature_selection import RFE RFC_ = RFC(n_estimators =10,random_state=0) selector = RFE(RFC_, n_features_to_select=340, step=50).fit(X, y)selector.support_.sum() #340 selector.ranking_ #array([10, 9, 8, 7, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, # 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 6, 6,. . . # 5, 6, 7, 7, 7, 6, 7, 8, 8, 8, 9, 9, 9, 9, 6, 8, 8, # 8, 7, 8, 8, 8, 7, 8, 8, 8, 8, 8, 7, 8, 8, 8, 8, 9, # 10, 7]) X_wrapper = selector.transform(X) cross_val_score(RFC_,X_wrapper,y,cv=5).mean()我們也可以對包裝法畫學習曲線:
#======【TIME WARNING: 15 mins】======# 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%以上,比嵌入法和過濾法都高效很多。我們可以放大圖像,尋找模型變得非常穩定的點來畫進一步的學習曲線(就像我們在嵌入法中做的那樣)。如果我們此時追求的是最大化降低模型的運行時間,我們甚至可以直接選擇50作為特征的數目,這是一個在縮減了94%的特征的基礎上,還能保證模型表現在90%以上的特征組合,不可謂不高效。
同時,我們提到過,在特征數目相同時,包裝法能夠在效果上匹敵嵌入法。試試看如果我們也使用340作為特征數目,運行一下,可以感受一下包裝法和嵌入法哪一個的速度更加快。由于包裝法效果和嵌入法相差不多,在更小的范圍內使用學習曲線,我們也可以將包裝法的效果調得很好,大家可以去試試看。
特征選擇總結
至此,我們講完了降維之外的所有特征選擇的方法。這些方法的代碼都不難,但是每種方法的原理都不同,并且都涉及到不同調整方法的超參數。經驗來說,過濾法更快速,但更粗糙。包裝法和嵌入法更精確,比較適合具體到算法去調整,但計算量比較大,運行時間長。當數據量很大的時候,優先使用方差過濾和互信息法調整,再上其他特征選擇方法。使用邏輯回歸時,優先使用嵌入法。使用支持向量機時,優先使用包裝法。迷茫的時候,從過濾法走起,看具體數據具體分析。
其實特征選擇只是特征工程中的第一步。真正的高手,往往使用特征創造或特征提取來尋找高級特征。在Kaggle之類的算法競賽中,很多高分團隊都是在高級特征上做文章,而這是比調參和特征選擇更難的,提升算法表現的高深方法。特征工程非常深奧,雖然我們日常可能用到不多,但其實它非常美妙。若大家感興趣,也可以自己去網上搜一搜,多讀多看多試多想,技術逐漸會成為你的囊中之物。
總結
以上是生活随笔為你收集整理的特征选择(feature_selection)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: MySQL—【加餐1】高效查询方法
- 下一篇: 菜菜sklearn——XGBoost(1