特征篩選的方法主要包括:Filter(過濾法)、Wrapper(封裝法)、Embedded(嵌入法)
filter: 過濾法
特征選擇方法一:去掉取值變化小的特征(Removing features with low variance)
方法雖然簡單但是不太好用,可以把它作為特征選擇的預處理,先去掉那些取值變化小的特征
如果機器資源充足,並且希望盡量保留所有信息,可以把閾值設置得比較高,或者只過濾離散型特征只有一個取值的特征。
離散型變量:95%的實例的該特征取值都是1,那就可以認為這個特征作用不大。 如果100%都是1,那這個特征就沒意義了。
連續型變量:拋棄那些方差小於某個閾值的特征。
from sklearn.feature_selection import VarianceThreshold X = [[0, 0, 1], [0, 1, 0], [1, 0, 0], [0, 1, 1], [0, 1, 0], [0, 1, 1]] sel = VarianceThreshold(threshold=(.8 * (1 - .8))) sel.fit_transform(X)
特征選擇實現方法二:單變量特征選擇
單變量特征選擇方法獨立的衡量每個特征與響應變量之間的關系,單變量特征選擇能夠對每一個特征進行測試,
衡量該特征和響應變量之間的關系,根據得分扔掉不好的特征。該方法簡單,易於運行,易於理解,
通常對於理解數據有較好的效果(但對特征優化、提高泛化能力來說不一定有效)
單變量特征選擇可以用於理解數據、數據的結構、特點,也可以用於排除不相關特征,但是它不能發現冗余特征。
1.Pearson相關系數(Pearson Correlation) -- 主要用於連續型特征的篩選,只對線性關系敏感
import numpy as np from scipy.stats import pearsonr np.random.seed(2019) size=1000 x = np.random.normal(0, 1, size) # 計算兩變量間的相關系數 print("Lower noise {}".format(pearsonr(x, x + np.random.normal(0, 1, size)))) print("Higher noise {}".format(pearsonr(x, x + np.random.normal(0, 10, size))))
2.互信息和最大信息系數(Mutual information and maximal information coefficient)
熵H(Y)與條件熵H(Y|X)之間的差稱為互信息 -- 只能用於離散型特征的選擇,對離散化的方式很敏感
由於互信息法並不方便直接用於特征選擇,因此引入了最大信息系數。最大信息數據首先尋找一種最優的離散方式,然后把互信息取值轉換成一種度量方式,取值區間為[0,1]。
x = np.random.normal(0,10,300) z = x *x pearsonr(x,z) # 輸出-0.1 from minepy import MINE m = MINE() m.compute_score(x, z) print(m.mic())
3.距離相關系數(Distance correlation) -- 為了克服Pearson相關系數的弱點而生的。
from scipy.spatial.distance import pdist, squareform import numpy as np from numbapro import jit, float32 def distcorr(X, Y): """ Compute the distance correlation function >>> a = [1,2,3,4,5] >>> b = np.array([1,2,9,4,4]) >>> distcorr(a, b) 0.762676242417 """ X = np.atleast_1d(X) Y = np.atleast_1d(Y) if np.prod(X.shape) == len(X): X = X[:, None] if np.prod(Y.shape) == len(Y): Y = Y[:, None] X = np.atleast_2d(X) Y = np.atleast_2d(Y) n = X.shape[0] if Y.shape[0] != X.shape[0]: raise ValueError('Number of samples must match') a = squareform(pdist(X)) b = squareform(pdist(Y)) A = a - a.mean(axis=0)[None, :] - a.mean(axis=1)[:, None] + a.mean() B = b - b.mean(axis=0)[None, :] - b.mean(axis=1)[:, None] + b.mean() dcov2_xy = (A * B).sum()/float(n * n) dcov2_xx = (A * A).sum()/float(n * n) dcov2_yy = (B * B).sum()/float(n * n) dcor = np.sqrt(dcov2_xy)/np.sqrt(np.sqrt(dcov2_xx) * np.sqrt(dcov2_yy)) return dcor
距離相關系數的另一種算法,鏈接
import numpy as np def dist(x, y): #1d only return np.abs(x[:, None] - y) def d_n(x): d = dist(x, x) dn = d - d.mean(0) - d.mean(1)[:,None] + d.mean() return dn def dcov_all(x, y): dnx = d_n(x) dny = d_n(y) denom = np.product(dnx.shape) dc = (dnx * dny).sum() / denom dvx = (dnx**2).sum() / denom dvy = (dny**2).sum() / denom dr = dc / (np.sqrt(dvx) * np.sqrt(dvy)) return dc, dr, dvx, dvy import matplotlib.pyplot as plt fig = plt.figure() for case in range(1,5): np.random.seed(9854673) x = np.linspace(-1,1, 501) if case == 1: y = - x**2 + 0.2 * np.random.rand(len(x)) elif case == 2: y = np.cos(x*2*np.pi) + 0.1 * np.random.rand(len(x)) elif case == 3: x = np.sin(x*2*np.pi) + 0.0 * np.random.rand(len(x)) #circle elif case == 4: x = np.sin(x*1.5*np.pi) + 0.1 * np.random.rand(len(x)) #bretzel dc, dr, dvx, dvy = dcov_all(x, y) print dc, dr, dvx, dvy ax = fig.add_subplot(2,2, case) #ax.set_xlim(-1, 1) ax.plot(x, y, '.') yl = ax.get_ylim() ax.text(-0.95, yl[0] + 0.9 * np.diff(yl), 'dr=%4.2f' % dr) plt.show()
4.基於學習模型的特征排序(Model based ranking)
這種方法的思路是直接使用你要用的機器學習算法,針對每個單獨的特征和響應變量建立預測模型。如果特征與響應變量之間的關系是非線性的,則有許多替代方案,例如基於樹的方法(決策樹,隨機森林)、或者擴展的線性模型等。基於樹的方法是最簡單的方法之一,因為他們可以很好地模擬非線性關系,不需要太多的調整。
但是要避免的主要是過度擬合,因此樹的深度應該相對較小,並且應該應用交叉驗證。
from sklearn.datasets import load_boston from sklearn.model_selection import train_test_split, cross_val_score, ShuffleSplit from sklearn.preprocessing import StandardScaler from sklearn.ensemble import RandomForestRegressor #from sklearn.metrics import r2_score, mean_squared_error, mean_absolute_error #Load boston housing dataset as an example boston = load_boston() #print(boston.DESCR) #x_train,x_test,y_train,y_test = train_test_split(boston.data,boston.target,random_state=33,test_size=0.25) 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)) from sklearn.svm import SVR l_svr = SVR(kernel='linear') #poly, rbf l_svr.fit(x_train,y_train) l_svr.score(x_test,y_test) from sklearn.neighbors import KNeighborsRegressor knn = KNeighborsRegressor(weights="uniform") knn.fit(x_train,y_train) knn.score(x_test,y_test) from sklearn.tree import DecisionTreeRegressor dt = DecisionTreeRegressor() dt.fit(x_train,y_train) dt.score(x_test,y_test) from sklearn.ensemble import RandomForestRegressor rfr = RandomForestRegressor() rfr.fit(x_train,y_train) rfr.score(x_test,y_test) from sklearn.ensemble import ExtraTreesRegressor etr = ExtraTreesRegressor() etr.fit(x_train,y_train) etr.score(x_test,y_test) from sklearn.ensemble import GradientBoostingRegressor gbr = GradientBoostingRegressor() gbr.fit(x_train,y_train) gbr.score(x_test,y_test)
5.卡方檢驗 -- 只適用於分類問題中離散型特征篩選
卡方值描述兩個事件的獨立性或者描述實際觀察值與期望值的偏離程度。卡方值越大,表名實際觀察值與期望值偏離越大,也說明兩個事件的相互獨立性越弱。
#導入sklearn庫中的SelectKBest和chi2 from sklearn.feature_selection import SelectKBest ,chi2 #選擇相關性最高的前5個特征 X_chi2 = SelectKBest(chi2, k=5).fit_transform(X, y) X_chi2.shape
特征選擇實現方法三:線性模型與正則化
當所有特征在相同尺度上時,最重要的特征應該在模型中具有最高系數,而與輸出變量不相關的特征應該具有接近零的系數值。即使使用簡單的線性回歸模型,當數據不是很嘈雜(或者有大量數據與特征數量相比)並且特征(相對)獨立時,這種方法也能很好地工作。
正則化就是把額外的約束或者懲罰項加到已有模型(損失函數)上,以防止過擬合並提高泛化能力。
當用在線性模型上時,L1正則化和L2正則化也稱為Lasso和Ridge。
Lasso能夠挑出一些優質特征,同時讓其他特征的系數趨於0。當如需要減少特征數的時候它很有用,但是對於數據理解來說不是很好用。
Ridge將回歸系數均勻的分攤到各個關聯變量上,L2正則化對於特征理解來說更加有用
多元線性回歸方程演變成求θ。每個特征都有對應的權重系數coef,特征的權重系數的正負值代表特征與目標值是正相關還是負相關,特征的權重系數的絕對值代表重要性。
#獲取boston數據 from sklearn.linear_model import LinearRegression boston=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) #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) # lasso回歸 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))) # 嶺回歸 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_)))
特征選擇實現方法四:隨機森林選擇
1.平均不純度減少(mean decrease impurity)
當訓練決策樹的時候,可以計算出每個特征減少了多少樹的不純度。對於一個決策樹森林來說,可以算出每個特征平均減少了多少不純度,並把它平均減少的不純度作為特征選擇的標准。
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)
通過直接度量每個特征對模型精確率的影響來進行特征選擇。
主要思路是打亂每個特征的特征值順序,並且度量順序變動對模型的精確率的影響。
對於不重要的變量來說,打亂順序對模型的精確率影響不會太大。
對於重要的變量來說,打亂順序就會降低模型的精確率。
from sklearn.model_selection 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))
特征選擇實現方法五:頂層特征選擇
1.穩定性選擇(Stability selection)
它的主要思想是在不同的數據子集和特征子集上運行特征選擇算法,不斷的重復,最終匯總特征選擇結果。比如可以統計某個特征被認為是重要特征的頻率(被選為重要特征的次數除以它所在的子集被測試的次數)。
理想情況下,重要特征的得分會接近100%。稍微弱一點的特征得分會是非0的數,而最無用的特征得分將會接近於0。
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)
遞歸特征消除的主要思想是反復的構建模型(如SVM或者回歸模型)然后選出最好的(或者最差的)的特征(可以根據系數來選),把選出來的特征放到一遍,然后在剩余的特征上重復這個過程,直到所有特征都遍歷了。
這個過程中特征被消除的次序就是特征的排序。因此,這是一種尋找最優特征子集的貪心算法。
RFE的穩定性很大程度上取決於在迭代的時候底層用哪種模型。
假如RFE采用的普通的回歸,沒有經過正則化的回歸是不穩定的,那么RFE就是不穩定的。
假如RFE采用的是Ridge,而用Ridge正則化的回歸是穩定的,那么RFE就是穩定的。
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)))
正則化的線性模型可用於特征理解和特征選擇。相比起L1正則化,L2正則化的表現更加穩定,L2正則化對於數據的理解來說很合適。
由於響應變量和特征之間往往是非線性關系,可以采用basis expansion的方式將特征轉換到一個更加合適的空間當中,在此基礎上再考慮運用簡單的線性模型。
隨機森林是一種非常流行的特征選擇方法,它易於使用。但它有兩個主要問題:
- 重要的特征有可能得分很低(關聯特征問題)
- 這種方法對特征變量類別多的特征越有利(偏向問題)
當選擇最優特征以提升模型性能的時候,可以采用交叉驗證的方法來驗證某種方法是否比其他方法要好。
當用特征選擇的方法來理解數據的時候要留心,特征選擇模型的穩定性非常重要,穩定性差的模型很容易就會導致錯誤的結論。
參考資料: