本文為數據茶水間群友原創,經授權在本公眾號發表。
關於作者:JunLiang,一個熱愛挖掘的數據從業者,勤學好問、動手達人,期待與大家一起交流探討機器學習相關內容~
0x00 前言
我們在《特征工程系列:特征篩選的原理與實現(上)》中介紹了特征選擇的分類,並詳細介紹了過濾式特征篩選的原理與實現。本篇繼續介紹封裝式和嵌入式特征篩選的原理與實現。
0x01 特征選擇實現方法三:線性模型與正則化
1.主要思想
當所有特征在相同尺度上時,最重要的特征應該在模型中具有最高系數,而與輸出變量不相關的特征應該具有接近零的系數值。即使使用簡單的線性回歸模型,當數據不是很嘈雜(或者有大量數據與特征數量相比)並且特征(相對)獨立時,這種方法也能很好地工作。
2.正則化模型
正則化就是把額外的約束或者懲罰項加到已有模型(損失函數)上,以防止過擬合並提高泛化能力。損失函數由原來的E(X,Y)變為E(X,Y)+alpha||w||,w是模型系數組成的向量(有些地方也叫參數parameter,coefficients),||·||一般是L1或者L2范數,alpha是一個可調的參數,控制着正則化的強度。當用在線性模型上時,L1正則化和L2正則化也稱為Lasso和Ridge。
1)L1正則化/Lasso regression
L1正則化將系數w的l1范數作為懲罰項加到損失函數上,由於正則項非零,這就迫使那些弱的特征所對應的系數變成0。因此L1正則化往往會使學到的模型很稀疏(系數w經常為0),這個特性使得L1正則化成為一種很好的特征選擇方法。
Lasso能夠挑出一些優質特征,同時讓其他特征的系數趨於0。當如需要減少特征數的時候它很有用,但是對於數據理解來說不是很好用。
2)L2正則化/Ridge regression
L2正則化將系數向量的L2范數添加到了損失函數中。
-
由於L2懲罰項中系數是二次方的,這使得L2和L1有着諸多差異,最明顯的一點就是,L2正則化會讓系數的取值變得平均。
-
對於關聯特征,這意味着他們能夠獲得更相近的對應系數。
-
Ridge將回歸系數均勻的分攤到各個關聯變量上。
L2正則化對於特征選擇來說一種穩定的模型,不像L1正則化那樣,系數會因為細微的數據變化而波動。所以L2正則化和L1正則化提供的價值是不同的,L2正則化對於特征理解來說更加有用:表示能力強的特征對應的系數是非零。
3.原理介紹
多元線性回歸,具有n個特征值,預測公式如下。
多元線性回歸方程演變成求θ。
每個特征都有對應的權重系數coef,特征的權重系數的正負值代表特征與目標值是正相關還是負相關,特征的權重系數的絕對值代表重要性。
sklearn中 中LinearRegression的fit()方法就是通過訓練集求出θ,LinearRegression的兩個屬性intercept和coef分別對應θ0和θ1-θn。
4.代碼實現
1)普通線性模型
#獲取boston數據
boston=datasets.load_boston()
x=boston.data
y=boston.target
#過濾掉異常值
x=x[y<50]
y=y[y<50]
reg=LinearRegression()
reg.fit(x,y)
#求排序后的coef
coefSort=reg.coef_.argsort()
#featureNameSort: 按對標記值的影響,從小到大的各特征值名稱
#featureCoefSore:按對標記值的影響,從小到大的coef_
featureNameSort=boston.feature_names[coefSort]
featureCoefSore=reg.coef_[coefSort]
print("featureNameSort:", featureNameSort)
print("featureCoefSore:", featureCoefSore)
# 輸出:featureNameSort: ['NOX' 'DIS' 'PTRATIO' 'LSTAT' 'CRIM' 'INDUS' 'AGE' 'TAX' 'B' 'ZN' 'RAD' 'CHAS' 'RM']
featureCoefSore: [-1.24268073e+01 -1.21088069e+00 -8.38888137e-01 -3.50952134e-01
-1.05574295e-01 -4.35179251e-02 -2.36116881e-02 -1.37702943e-02 7.93577159e-03
3.52748549e-02 2.50740082e-01 4.55405227e-01 3.75411229e+00]
結果分析:
-
正相關影響系數最大的特征值是”RM”:房間的平均數量,系數值為3.75。
-
負相關影響系數最大的特征值是”NOX”:一氧化氮濃度,系數值為-1.24。
2)L1正則化線性模型
#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)
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: {}".format(
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。L1正則化回歸的穩定性與非正則化線性模型類似,這意味着當數據中存在相關特征時,系數(以及特征等級)即使在小數據變化時也會發生顯着變化。
3)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 {}".format(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(0, 1, size)
X = np.array([X1, X2, X3]).T
lr = LinearRegression()
lr.fit(X,Y)
print("Linear model: {}".format(pretty_print_linear(lr.coef_)))
ridge = Ridge(alpha=10)
ridge.fit(X,Y)
print("Ridge model: {}".format(pretty_print_linear(ridge.coef_)))
# 輸出
Random seed 0
Linear model: 0.728 * X0 + 2.309 * X1 + -0.082 * X2
Ridge model: 0.938 * X0 + 1.059 * X1 + 0.877 * X2
Random seed 1
Linear model: 1.152 * X0 + 2.366 * X1 + -0.599 * X2
Ridge model: 0.984 * X0 + 1.068 * X1 + 0.759 * X2
Random seed 2
Linear model: 0.697 * X0 + 0.322 * X1 + 2.086 * X2
Ridge model: 0.972 * X0 + 0.943 * X1 + 1.085 * X2
Random seed 3
Linear model: 0.287 * X0 + 1.254 * X1 + 1.491 * X2
Ridge model: 0.919 * X0 + 1.005 * X1 + 1.033 * X2
Random seed 4
Linear model: 0.187 * X0 + 0.772 * X1 + 2.189 * X2
Ridge model: 0.964 * X0 + 0.982 * X1 + 1.098 * X2
Random seed 5
Linear model: -1.291 * X0 + 1.591 * X1 + 2.747 * X2
Ridge model: 0.758 * X0 + 1.011 * X1 + 1.139 * X2
Random seed 6
Linear model: 1.199 * X0 + -0.031 * X1 + 1.915 * X2
Ridge model: 1.016 * X0 + 0.89 * X1 + 1.091 * X2
Random seed 7
Linear model: 1.474 * X0 + 1.762 * X1 + -0.151 * X2
Ridge model: 1.018 * X0 + 1.039 * X1 + 0.901 * X2
Random seed 8
Linear model: 0.084 * X0 + 1.88 * X1 + 1.107 * X2
Ridge model: 0.907 * X0 + 1.071 * X1 + 1.008 * X2
Random seed 9
Linear model: 0.714 * X0 + 0.776 * X1 + 1.364 * X2
Ridge model: 0.896 * X0 + 0.903 * X1 + 0.98 * X2
從示例中可以看出,線性回歸的系數變化很大,具體取決於生成的數據。然而,對於L2正則化模型,系數非常穩定並且密切反映數據的生成方式(所有系數接近1)。
0x02 特征選擇實現方法四:隨機森林選擇
隨機森林具有准確率高、魯棒性好、易於使用等優點,這使得它成為了目前最流行的機器學習算法之一。隨機森林提供了兩種特征選擇的方法:mean decrease impurity和mean decrease accuracy。
1.平均不純度減少(mean decrease impurity)
1)原理介紹
-
隨機森林由多顆CART決策樹構成,決策樹中的每一個節點都是關於某個特征的條件,為的是將數據集按照不同的響應變量一分為二。
-
CART利用不純度可以確定節點(最優條件),對於分類問題,通常采用基尼不純度,對於回歸問題,通常采用的是方差或者最小二乘擬合。
-
當訓練決策樹的時候,可以計算出每個特征減少了多少樹的不純度。對於一個決策樹森林來說,可以算出每個特征平均減少了多少不純度,並把它平均減少的不純度作為特征選擇的標准。
-
隨機森林基於不純度的排序結果非常鮮明,在得分最高的幾個特征之后的特征,得分急劇的下降。
2)代碼實現
from sklearn.datasets import load_boston
from sklearn.ensemble import RandomForestRegressor
import numpy as np
#Load boston housing dataset as an example
boston = load_boston()
X = boston["data"]
Y = boston["target"]
names = boston["feature_names"]
# 訓練隨機森林模型,並通過feature_importances_屬性獲取每個特征的重要性分數。rf = RandomForestRegressor()
rf.fit(X, Y)
print("Features sorted by their score:")
print(sorted(zip(map(lambda x: round(x, 4), rf.feature_importances_), names),
reverse=True))
2.平均精確度減少(mean decrease accuracy)
1)原理介紹
-
通過直接度量每個特征對模型精確率的影響來進行特征選擇。
-
主要思路是打亂每個特征的特征值順序,並且度量順序變動對模型的精確率的影響。
-
對於不重要的變量來說,打亂順序對模型的精確率影響不會太大。
-
對於重要的變量來說,打亂順序就會降低模型的精確率。
2)代碼實現
from sklearn.cross_validation import ShuffleSplit
from sklearn.metrics import r2_score
from collections import defaultdict
X = boston["data"]
Y = boston["target"]
rf = RandomForestRegressor()
scores = defaultdict(list)
#crossvalidate the scores on a number of different random splits of the data
for train_idx, test_idx in ShuffleSplit(len(X), 100, .3):
X_train, X_test = X[train_idx], X[test_idx]
Y_train, Y_test = Y[train_idx], Y[test_idx]
# 使用修改前的原始特征訓練模型,其acc作為后續混洗特征值后的對比標准。r = rf.fit(X_train, Y_train)
acc = r2_score(Y_test, rf.predict(X_test))
# 遍歷每一列特征
for i in range(X.shape[1]):
X_t = X_test.copy()
# 對這一列特征進行混洗,交互了一列特征內部的值的順序
np.random.shuffle(X_t[:, i])
shuff_acc = r2_score(Y_test, rf.predict(X_t))
# 混洗某個特征值后,計算平均精確度減少程度。scores[names[i]].append((acc-shuff_acc)/acc)
print("Features sorted by their score:")
print(sorted([(round(np.mean(score), 4), feat) for feat, score in scores.items()], reverse=True))
0x03 特征選擇實現方法五:頂層特征選擇
頂層特征選擇發建立在基於模型的特征選擇方法基礎之上的,例如線性回歸和SVM等,在不同的子集上建立模型,然后匯總最終確定特征得分。
1.穩定性選擇(Stability selection)
穩定性選擇常常是一種既能夠有助於理解數據又能夠挑出優質特征的這種選擇。
1)原理介紹
-
穩定性選擇是一種基於二次抽樣和選擇算法相結合較新的方法,選擇算法可以是回歸、SVM或其他類似的方法。
-
它的主要思想是在不同的數據子集和特征子集上運行特征選擇算法,不斷的重復,最終匯總特征選擇結果。比如可以統計某個特征被認為是重要特征的頻率(被選為重要特征的次數除以它所在的子集被測試的次數)。
-
理想情況下,重要特征的得分會接近100%。稍微弱一點的特征得分會是非0的數,而最無用的特征得分將會接近於0。
2)代碼實現
from sklearn.linear_model import RandomizedLasso
from sklearn.datasets import load_boston
boston = load_boston()
#using the Boston housing data.
#Data gets scaled automatically by sklearn's implementation
X = boston["data"]
Y = boston["target"]
names = boston["feature_names"]
rlasso = RandomizedLasso(alpha=0.025)
rlasso.fit(X, Y)
print("Features sorted by their score:")
print(sorted(zip(map(lambda x: round(x, 4), rlasso.scores_), names),
reverse=True))
2.遞歸特征消除(Recursive feature elimination,RFE)
1)原理介紹
-
遞歸特征消除的主要思想是反復的構建模型(如SVM或者回歸模型)然后選出最好的(或者最差的)的特征(可以根據系數來選),把選出來的特征放到一遍,然后在剩余的特征上重復這個過程,直到所有特征都遍歷了。
-
這個過程中特征被消除的次序就是特征的排序。因此,這是一種尋找最優特征子集的貪心算法。
-
RFE的穩定性很大程度上取決於在迭代的時候底層用哪種模型。
-
假如RFE采用的普通的回歸,沒有經過正則化的回歸是不穩定的,那么RFE就是不穩定的。
-
假如RFE采用的是Ridge,而用Ridge正則化的回歸是穩定的,那么RFE就是穩定的。
2)代碼實現
from sklearn.feature_selection import RFE
from sklearn.linear_model import LinearRegression
boston = load_boston()
X = boston["data"]
Y = boston["target"]
names = boston["feature_names"]
#use linear regression as the model
lr = LinearRegression()
#rank all features, i.e continue the elimination until the last one
rfe = RFE(lr, n_features_to_select=1)
rfe.fit(X,Y)
print("Features sorted by their rank:")
print(sorted(zip(map(lambda x: round(x, 4), rfe.ranking_), names)))
結果輸出
Features sorted by their rank:
[(1, 'NOX'), (2, 'RM'), (3, 'CHAS'), (4, 'PTRATIO'), (5, 'DIS'),
(6, 'LSTAT'), (7, 'RAD'), (8, 'CRIM'), (9, 'INDUS'), (10, 'ZN'),
(11, 'TAX'), (12, 'B'), (13, 'AGE')]
0xFF 總結
-
單變量特征選擇可以用於理解數據、數據的結構、特點,也可以用於排除不相關特征,但是它不能發現冗余特征。
-
正則化的線性模型可用於特征理解和特征選擇。相比起L1正則化,L2正則化的表現更加穩定,L2正則化對於數據的理解來說很合適。由於響應變量和特征之間往往是非線性關系,可以采用basis expansion的方式將特征轉換到一個更加合適的空間當中,在此基礎上再考慮運用簡單的線性模型。
-
隨機森林是一種非常流行的特征選擇方法,它易於使用。但它有兩個主要問題:
-
重要的特征有可能得分很低(關聯特征問題)
-
這種方法對特征變量類別多的特征越有利(偏向問題)
-
特征選擇在很多機器學習和數據挖掘場景中都是非常有用的。在使用的時候要弄清楚自己的目標是什么,然后找到哪種方法適用於自己的任務。
-
當選擇最優特征以提升模型性能的時候,可以采用交叉驗證的方法來驗證某種方法是否比其他方法要好。
-
當用特征選擇的方法來理解數據的時候要留心,特征選擇模型的穩定性非常重要,穩定性差的模型很容易就會導致錯誤的結論。
-
對數據進行二次采樣然后在子集上運行特征選擇算法能夠有所幫助,如果在各個子集上的結果是一致的,那就可以說在這個數據集上得出來的結論是可信的,可以用這種特征選擇模型的結果來理解數據。
-
關於訓練模型的特征篩選,個人建議的實施流程 :
-
數據預處理后,先排除取值變化很小的特征。如果機器資源充足,並且希望盡量保留所有信息,可以把閾值設置得比較高,或者只過濾離散型特征只有一個取值的特征。
-
如果數據量過大,計算資源不足(內存不足以使用所有數據進行訓練、計算速度過慢),可以使用單特征選擇法排除部分特征。這些被排除的特征並不一定完全被排除不再使用,在后續的特征構造時也可以作為原始特征使用。
-
如果此時特征量依然非常大,或者是如果特征比較稀疏時,可以使用PCA(主成分分析)和LDA(線性判別)等方法進行特征降維。
-
經過樣本采樣和特征預篩選后,訓練樣本可以用於訓練模型。但是可能由於特征數量比較大而導致訓練速度慢,或者想進一步篩選有效特征或排除無效特征(或噪音),我們可以使用正則化線性模型選擇法、隨機森林選擇法或者頂層特征選擇法進一步進行特征篩選。
最后,特征篩選是為了理解數據或更好地訓練模型,我們應該根據自己的目標來選擇適合的方法。為了更好/更容易地訓練模型而進行的特征篩選,如果計算資源充足,應盡量避免過度篩選特征,因為特征篩選很容易丟失有用的信息。如果只是為了減少無效特征的影響,為了避免過擬合,可以選擇隨機森林和XGBoost等集成模型來避免對特征過擬合。
參考文獻:
-
[1] Feature selection – Part I: univariate selection. http://blog.datadive.net/selecting-good-features-part-i-univariate-selection/
-
[2] Selecting good features – Part II: linear models and regularization. http://blog.datadive.net/selecting-good-features-part-ii-linear-models-and-regularization/
-
[3] Feature selection. https://scikit-learn.org/stable/modules/feature_selection.html#univariate-feature-selection
-
[4] https://gist.github.com/satra/aa3d19a12b74e9ab7941
-
[5] 結合Scikit-learn介紹幾種常用的特征選擇方法. https://www.cnblogs.com/hhh5460/p/5186226.html