資料特徵選擇
特徵選擇(排序)對於資料科學家、機器學習從業者來說非常重要。好的特徵選擇能夠提升模型的效能,更能幫助我們理解資料的特點、底層結構,這對進一步改善模型、演算法都有著重要作用。
特徵選擇主要有兩個功能:
- 減少特徵數量、降維,使模型泛化能力更強,減少過擬合
- 增強對特徵和特徵值之間的理解
拿到資料集,一個特徵選擇方法,往往很難同時完成這兩個目的。通常情況下,我們經常不管三七二十一,選擇一種自己最熟悉或者最方便的特徵選擇方法(往往目的是降維,而忽略了對特徵和資料理解的目的)。
在許多機器學習相關的書裡,很難找到關於特徵選擇的內容,因為特徵選擇要解決的問題往往被視為機器學習的一種副作用,一般不會單獨拿出來討論。
本文將結合 Scikit-learn提供的例子 介紹幾種常用的特徵選擇方法,它們各自的優缺點和問題。
1 去掉取值變化小的特徵 Removing features with low variance
這應該是最簡單的特徵選擇方法了:假設某特徵的特徵值只有0和1,並且在所有輸入樣本中,95%的例項的該特徵取值都是1,那就可以認為這個特徵作用不大。如果100%都是1,那這個特徵就沒意義了。當特徵值都是離散型變數的時候這種方法才能用,如果是連續型變數,就需要將連續變數離散化之後才能用,而且實際當中,一般不太會有95%以上都取某個值的特徵存在,所以這種方法雖然簡單但是不太好用。可以把它作為特徵選擇的預處理,先去掉那些取值變化小的特徵,然後再從接下來提到的的特徵選擇方法中選擇合適的進行進一步的特徵選擇。
2 單變數特徵選擇 Univariate feature selection
單變數特徵選擇能夠對每一個特徵進行測試,衡量該特徵和響應變數之間的關係,根據得分扔掉不好的特徵。對於迴歸和分類問題可以採用卡方檢驗等方式對特徵進行測試。
這種方法比較簡單,易於執行,易於理解,通常對於理解資料有較好的效果(但對特徵優化、提高泛化能力來說不一定有效);這種方法有許多改進的版本、變種。
2.1 Pearson相關係數 Pearson Correlation
皮爾森相關係數是一種最簡單的,能幫助理解特徵和響應變數之間關係的方法,該方法衡量的是變數之間的線性相關性,結果的取值區間為[-1,1],-1表示完全的負相關(這個變數下降,那個就會上升),+1表示完全的正相關,0表示沒有線性相關。
Pearson Correlation速度快、易於計算,經常在拿到資料(經過清洗和特徵提取之後的)之後第一時間就執行。Scipy的 pearsonr 方法能夠同時計算相關係數和p-value,
import numpy as np
from scipy.stats import pearsonr
np.random.seed(0)
size = 300
x = np.random.normal(0, 1, size)
print "Lower noise", pearsonr(x, x + np.random.normal(0, 1, size))
print "Higher noise", pearsonr(x, x + np.random.normal(0, 10, size))
Lower noise (0.71824836862138386, 7.3240173129992273e-49)Higher noise (0.057964292079338148, 0.31700993885324746)
這個例子中,我們比較了變數在加入噪音之前和之後的差異。當噪音比較小的時候,相關性很強,p-value很低。
Scikit-learn提供的 f_regrssion 方法能夠批量計算特徵的p-value,非常方便,參考sklearn的 pipeline
Pearson相關係數的一個明顯缺陷是,作為特徵排序機制,他只對線性關係敏感。如果關係是非線性的,即便兩個變數具有一一對應的關係,Pearson相關性也可能會接近0。
x = np.random.uniform(-1, 1, 100000) print pearsonr(x, x**2)[0]
-0.00230804707612
更多類似的例子參考 sample plots 。另外,如果僅僅根據相關係數這個值來判斷的話,有時候會具有很強的誤導性,如 Anscombe’s quartet ,最好把資料可視化出來,以免得出錯誤的結論。
2.2 互資訊和最大資訊係數 Mutual information and maximal information coefficient (MIC)
以上就是經典的互資訊公式了。想把互資訊直接用於特徵選擇其實不是太方便:1、它不屬於度量方式,也沒有辦法歸一化,在不同資料及上的結果無法做比較;2、對於連續變數的計算不是很方便(X和Y都是集合,x,y都是離散的取值),通常變數需要先離散化,而互資訊的結果對離散化的方式很敏感。
最大資訊係數克服了這兩個問題。它首先尋找一種最優的離散化方式,然後把互資訊取值轉換成一種度量方式,取值區間在[0,1]。 minepy 提供了MIC功能。
反過頭來看y=x^2這個例子,MIC算出來的互資訊值為1(最大的取值)。
from minepy import MINE
m = MINE()
x = np.random.uniform(-1, 1, 10000)
m.compute_score(x, x**2)
print m.mic()
1.0
MIC的統計能力遭到了 一些質疑 ,當零假設不成立時,MIC的統計就會受到影響。在有的資料集上不存在這個問題,但有的資料集上就存在這個問題。
2.3 距離相關係數 (Distance correlation)
距離相關係數是為了克服Pearson相關係數的弱點而生的。在x和x^2這個例子中,即便Pearson相關係數是0,我們也不能斷定這兩個變數是獨立的(有可能是非線性相關);但如果距離相關係數是0,那麼我們就可以說這兩個變數是獨立的。
R的 energy 包裡提供了距離相關係數的實現,另外這是 Python gist 的實現。
#R-code > x = runif (1000, -1, 1) > dcor(x, x**2) [1] 0.4943864
儘管有MIC和距離相關係數在了,但當變數之間的關係接近線性相關的時候,Pearson相關係數仍然是不可替代的。第一、Pearson相關係數計算速度快,這在處理大規模資料的時候很重要。第二、Pearson相關係數的取值區間是[-1,1],而MIC和距離相關係數都是[0,1]。這個特點使得Pearson相關係數能夠表徵更豐富的關係,符號表示關係的正負,絕對值能夠表示強度。當然,Pearson相關性有效的前提是兩個變數的變化關係是單調的。
2.4 基於學習模型的特徵排序 (Model based ranking)
這種方法的思路是直接使用你要用的機器學習演算法,針對每個單獨的特徵和響應變數建立預測模型。其實Pearson相關係數等價於線性迴歸裡的標準化迴歸係數。假如某個特徵和響應變數之間的關係是非線性的,可以用基於樹的方法(決策樹、隨機森林)、或者擴充套件的線性模型等。基於樹的方法比較易於使用,因為他們對非線性關係的建模比較好,並且不需要太多的除錯。但要注意過擬合問題,因此樹的深度最好不要太大,再就是運用交叉驗證。
在 波士頓房價資料集 上使用sklearn的 隨機森林迴歸 給出一個單變數選擇的例子:
from sklearn.cross_validation import cross_val_score, ShuffleSplit
from sklearn.datasets import load_boston
from sklearn.ensemble import RandomForestRegressor
#Load boston housing dataset as an example
boston = load_boston()
X = boston["data"]
Y = boston["target"]
names = boston["feature_names"]
rf = RandomForestRegressor(n_estimators=20, max_depth=4)
scores = []
for i in range(X.shape[1]):
score = cross_val_score(rf, X[:, i:i+1], Y, scoring="r2",
cv=ShuffleSplit(len(X), 3, .3))
scores.append((round(np.mean(score), 3), names[i]))
print sorted(scores, reverse=True)
[(0.636, ‘LSTAT’), (0.59, ‘RM’), (0.472, ‘NOX’), (0.369, ‘INDUS’), (0.311, ‘PTRATIO’), (0.24, ‘TAX’), (0.24, ‘CRIM’), (0.185, ‘RAD’), (0.16, ‘ZN’), (0.087, ‘B’), (0.062, ‘DIS’), (0.036, ‘CHAS’), (0.027, ‘AGE’)]
3 線性模型和正則化
單變數特徵選擇方法獨立的衡量每個特徵與響應變數之間的關係,另一種主流的特徵選擇方法是基於機器學習模型的方法。有些機器學習方法本身就具有對特徵進行打分的機制,或者很容易將其運用到特徵選擇任務中,例如迴歸模型,SVM,決策樹,隨機森林等等。說句題外話,這種方法好像在一些地方叫做wrapper型別,大概意思是說,特徵排序模型和機器學習模型是耦盒在一起的,對應的非wrapper型別的特徵選擇方法叫做filter型別。
下面將介紹如何用迴歸模型的係數來選擇特徵。越是重要的特徵在模型中對應的係數就會越大,而跟輸出變數越是無關的特徵對應的係數就會越接近於0。在噪音不多的資料上,或者是資料量遠遠大於特徵數的資料上,如果特徵之間相對來說是比較獨立的,那麼即便是運用最簡單的線性迴歸模型也一樣能取得非常好的效果。
from sklearn.linear_model import LinearRegression
import numpy as np
np.random.seed(0)
size = 5000
#A dataset with 3 features
X = np.random.normal(0, 1, (size, 3))
#Y = X0 + 2*X1 + noise
Y = X[:,0] + 2*X[:,1] + np.random.normal(0, 2, size)
lr = LinearRegression()
lr.fit(X, Y)
#A helper method for pretty-printing linear models
def pretty_print_linear(coefs, names = None, sort = False):
if names == None:
names = ["X%s" % x for x in range(len(coefs))]
lst = zip(coefs, names)
if sort:
lst = sorted(lst, key = lambda x:-np.abs(x[0]))
return " + ".join("%s * %s" % (round(coef, 3), name)
for coef, name in lst)
print "Linear model:", pretty_print_linear(lr.coef_)
Linear model: 0.984 * X0 + 1.995 * X1 + -0.041 * X2
在這個例子當中,儘管資料中存在一些噪音,但這種特徵選擇模型仍然能夠很好的體現出資料的底層結構。當然這也是因為例子中的這個問題非常適合用線性模型來解:特徵和響應變數之間全都是線性關係,並且特徵之間均是獨立的。
在很多實際的資料當中,往往存在多個互相關聯的特徵,這時候模型就會變得不穩定,資料中細微的變化就可能導致模型的巨大變化(模型的變化本質上是係數,或者叫引數,可以理解成W),這會讓模型的預測變得困難,這種現象也稱為多重共線性。例如,假設我們有個資料集,它的真實模型應該是Y=X1+X2,當我們觀察的時候,發現Y’=X1+X2+e,e是噪音。如果X1和X2之間存線上性關係,例如X1約等於X2,這個時候由於噪音e的存在,我們學到的模型可能就不是Y=X1+X2了,有可能是Y=2X1,或者Y=-X1+3X2。
下邊這個例子當中,在同一個資料上加入了一些噪音,用隨機森林演算法進行特徵選擇。
from sklearn.linear_model import LinearRegression
size = 100
np.random.seed(seed=5)
X_seed = np.random.normal(0, 1, size)
X1 = X_seed + np.random.normal(0, .1, size)
X2 = X_seed + np.random.normal(0, .1, size)
X3 = X_seed + np.random.normal(0, .1, size)
Y = X1 + X2 + X3 + np.random.normal(0,1, size)
X = np.array([X1, X2, X3]).T
lr = LinearRegression()
lr.fit(X,Y)
print "Linear model:", pretty_print_linear(lr.coef_)
Linear model: -1.291 * X0 + 1.591 * X1 + 2.747 * X2
係數之和接近3,基本上和上上個例子的結果一致,應該說學到的模型對於預測來說還是不錯的。但是,如果從係數的字面意思上去解釋特徵的重要性的話,X3對於輸出變數來說具有很強的正面影響,而X1具有負面影響,而實際上所有特徵與輸出變數之間的影響是均等的。
同樣的方法和套路可以用到類似的線性模型上,比如邏輯迴歸。
3.1 正則化模型
正則化就是把額外的約束或者懲罰項加到已有模型(損失函式)上,以防止過擬合併提高泛化能力。損失函式由原來的E(X,Y)變為E(X,Y)+alpha||w||,w是模型係數組成的向量(有些地方也叫引數parameter,coefficients),||·||一般是L1或者L2範數,alpha是一個可調的引數,控制著正則化的強度。當用線上性模型上時,L1正則化和L2正則化也稱為Lasso和Ridge。
3.2 L1正則化/Lasso
L1正則化將係數w的l1範數作為懲罰項加到損失函式上,由於正則項非零,這就迫使那些弱的特徵所對應的係數變成0。因此L1正則化往往會使學到的模型很稀疏(係數w經常為0),這個特性使得L1正則化成為一種很好的特徵選擇方法。
Scikit-learn為線性迴歸提供了Lasso,為分類提供了L1邏輯迴歸。
下面的例子在波士頓房價資料上運行了Lasso,其中引數alpha是通過grid search進行優化的。
from sklearn.linear_model import Lasso
from sklearn.preprocessing import StandardScaler
from sklearn.datasets import load_boston
boston = load_boston()
scaler = StandardScaler()
X = scaler.fit_transform(boston["data"])
Y = boston["target"]
names = boston["feature_names"]
lasso = Lasso(alpha=.3)
lasso.fit(X, Y)
print "Lasso model: ", pretty_print_linear(lasso.coef_, names, sort = True)
Lasso model: -3.707 * LSTAT + 2.992 * RM + -1.757 * PTRATIO + -1.081 * DIS + -0.7 * NOX + 0.631 * B + 0.54 * CHAS + -0.236 * CRIM + 0.081 * ZN + -0.0 * INDUS + -0.0 * AGE + 0.0 * RAD + -0.0 * TAX
可以看到,很多特徵的係數都是0。如果繼續增加alpha的值,得到的模型就會越來越稀疏,即越來越多的特徵係數會變成0。
然而,L1正則化像非正則化線性模型一樣也是不穩定的,如果特徵集合中具有相關聯的特徵,當資料發生細微變化時也有可能導致很大的模型差異。
3.3 L2正則化/Ridge regression
L2正則化將係數向量的L2範數新增到了損失函式中。由於L2懲罰項中係數是二次方的,這使得L2和L1有著諸多差異,最明顯的一點就是,L2正則化會讓係數的取值變得平均。對於關聯特徵,這意味著他們能夠獲得更相近的對應係數。還是以Y=X1+X2為例,假設X1和X2具有很強的關聯,如果用L1正則化,不論學到的模型是Y=X1+X2還是Y=2X1,懲罰都是一樣的,都是2 alpha。但是對於L2來說,第一個模型的懲罰項是2alpha,但第二個模型的是4*alpha。可以看出,係數之和為常數時,各系數相等時懲罰是最小的,所以才有了L2會讓各個係數趨於相同的特點。
可以看出,L2正則化對於特徵選擇來說一種穩定的模型,不像L1正則化那樣,係數會因為細微的資料變化而波動。所以L2正則化和L1正則化提供的價值是不同的,L2正則化對於特徵理解來說更加有用:表示能力強的特徵對應的係數是非零。
回過頭來看看3個互相關聯的特徵的例子,分別以10個不同的種子隨機初始化執行10次,來觀察L1和L2正則化的穩定性。
from sklearn.linear_model import Ridge
from sklearn.metrics import r2_score
size = 100
#We run the method 10 times with different random seeds
for i in range(10):
print "Random seed %s" % i
np.random.seed(seed=i)
X_seed = np.random.normal(0, 1, size)
X1 = X_seed + np.random.normal(0, .1, size)
X2 = X_seed + np.random.normal(0, .1, size)
X3 = X_seed + np.random.normal(0, .1, size)
Y = X1 + X2 + X3 + np.random.normal(