特征選擇是特征工程中的重要一環,其主要目的是從所有特征中選出相關特征 (relevant feature),或者說在不引起重要信息丟失的前提下去除掉無關特征 (irrelevant feature) 和冗余特征 (redundant feature)。進行特征選擇的好處主要有以下幾種:
- 降低過擬合風險,提升模型效果
- 提高訓練速度,降低運算開銷
- 更少的特征通常意味着更好的可解釋性
不同的模型對於無關特征的容忍度不同,下圖來自《 Applied Predictive Modeling 》 (P489),顯示了逐漸增加無關特征后不同模型的RMSE的變化。樹模型普遍表現較好,而神經網絡因其模型的復雜性則很容易過擬合。Lasso 因其可以產生稀疏特征因而也有較好的表現。

特征選擇的方法主要分為三大類:過濾式方法 (Filter Methods),包裹式方法 (Wrapper Methods) 和嵌入式方法 (Embedded Methods)。
- 過濾式方法運用統計指標來為每個特征打分並篩選特征,其聚焦於數據本身的特點。其優點是計算快,不依賴於具體的模型,缺點是選擇的統計指標不是為特定模型定制的,因而最后的准確率可能不高。而且因為進行的是單變量統計檢驗,沒有考慮特征間的相互關系。
- 包裹式方法使用模型來篩選特征,通過不斷地增加或刪除特征,在驗證集上測試模型准確率,尋找最優的特征子集。包裹式方法因為有模型的直接參與,因而通常准確性較高,但是因為每變動一個特征都要重新訓練模型,因而計算開銷大,其另一個缺點是容易過擬合。
- 嵌入式方法利用了模型本身的特性,將特征選擇嵌入到模型的構建過程中。典型的如 Lasso 和樹模型等。准確率較高,計算復雜度介於過濾式和包裹式方法之間,但缺點是只有部分模型有這個功能。
下面這張圖總結地更加全面,來自 《A review of feature selection techniques in bioinformatics 》

本文接下來主要考察過濾式方法中常用的幾個方法:卡方檢驗、F 檢驗和互信息,並探討它們用於特征選擇的內在機理。
卡方檢驗
既然特征選擇的目的是去除無關特征,那么什么是無關特征? 對於分類問題,在過濾式方法中一般假設與標簽獨立的特征為無關特征,而卡方檢驗恰好可以進行獨立性檢驗,所以其適用於特征選擇。如果檢驗結果是某個特征與標簽獨立,則可以去除該特征。說到卡方檢驗自然會用到卡方分布,其定義如下:
設隨機變量 \(x_1, x_2 ... x_n \,,\quad \text{i.i.d} \sim N(0,1)\) ,即獨立同分布於標准正態分布,那么這 \(n\) 個隨機變量的平方和:
\[X = \sum\limits_{i=1}^n x_i^2 \]構成一個新的隨機變量,其服從自由度為 \(n\) 的卡方分布 ( \(\chi^2\) 分布) ,記為 \(X \sim \chi^2_n\) 。
下圖顯示不同自由度下卡方分布的概率密度曲線,可以看到自由度越大,卡方分布就越接近正態分布:

下面舉個例子看卡方檢驗的一般流程:
假設我想檢驗一個男人有特殊着裝癖好與其變態與否的關系,如果檢驗的結果是二者不獨立,那下次在街上看見女裝大佬我可能就要繞着走了。。。 所以該獨立性檢驗的假設如下:
零假設 ($H_0$):着裝偏好與變態傾向獨立 備選假設 ($H_1$) :着裝偏好與變態傾向不獨立
卡方檢驗一般需要先建立列聯表,表中每個格子是觀察頻數,表示實際觀測到的同時滿足兩個條件的數量:

同時需要計算每個格子的期望頻數,因為零假設是兩個變量獨立,因此依獨立性的定義:\(P(A,B) = P(A)\, P(B)\),於是上表中每個格子的期望頻數為 \(N \times P(A,B) = N \times P(A) \times P(B)\) ,其中 \(N\) 為總數量,那么第一個格子的期望頻數為 \(3100 \times \frac{750}{3100} \times \frac{500}{3100} = 121\) 。因此總體期望頻數表為:

有了這兩個列聯表后,就可以計算檢驗統計量 \(\chi^2\) ( \(\chi^2\) 表示卡方值) ,\(\chi^2\) 越大,表示觀測值和理論值相差越大,當 \(\chi^2\) 大於某一個臨界值時,就能獲得統計顯著性的結論:
其中 \(O_{ij}\) 為觀測頻數表中單元格的數值,\(E_{ij}\) 為期望頻數表中單元格的數值,\(r\) 為行數,\(c\) 為列數,自由度 \(df\) 為 \((2-1)\times(3-1) = 2\) ,\(\chi^2\) 服從卡方分布,則查卡方分布表:

得 \(P(\chi^2 > 13.82) < 0.001\) ,而實際計算出的 \(\chi^2\) 為 26.99,顯著性很高,意味着零假設成立的情況下樣本結果出現的概率小於 \(0.1\%\),因而可以拒絕零假設,接受備選假設。這意味着男性的特殊着裝偏好與變態傾向具有相關性。當然這里得說明兩點:
- 本數據純屬虛構。
- 相關性不代表因果性,完全可能是第三個變量 (如:國籍) 同時導致了女裝和變態,致使這兩個變量產生了某種相關性。
再回到特征選擇的問題,從嚴格的統計學角度來看,使用卡方檢驗進行特征選擇可能會產生一些問題。假設選擇的顯著性水平 \(\alpha\) 為 0.05,這說明犯第一類錯誤 (\(\text{type} \, \text{I} \, \text{error}\),兩個變量實際獨立卻被判為相關) 的概率為 5%,若進行了 1000 次卡方檢驗,則平均有 \(1000 \times 0.05 = 50\) 次會選擇與標簽不相關的特征。機器學習問題中動輒就有幾千至上百萬的特征,那么這里面漏過的特征可能會相當多。不過好在搞機器學習並不是在搞統計,我們實際上比較關心的是特征的相對重要性。依上面的卡方分布表,檢驗統計量 \(\chi^2\)越大,越有信心拒絕零假設,接受兩個變量不獨立 的事實,因而可以按每個特征 \(\chi^2\) 值的大小進行排序,去除 \(\chi^2\) 值小的特征。
以上就是卡方檢驗用於特征選擇的一般流程,而我看到在大部分資料中舉的例子都是離散型特征的,如下圖:

這其中有幾個值得注意的點:
(1) 上面舉的卡方檢驗例子是判別 着裝癖好
與 變態傾向
具有相關性,然而 着裝癖好
是離散型特征,而大部分機器學習模型是無法直接處理離散型特征的,如果按通常的作法進行 one-hot 轉換 (如下圖),就不能確定其中單個的特征 (如 着裝癖好_女裝
) 是否仍與 變態傾向
有相關性。

(2) 上面這一點也可以反過來看,假設卡方檢驗的結果是 着裝癖好
與 變態傾向
獨立,也並不代表單個的特征 (如着裝癖好_不定裝
)與變態傾向
獨立。所以綜合這兩點,應該先將離散型特征進行轉換,再對每個特征進行卡方檢驗,而不是像這些資料中那樣直接對一個離散型特征作檢驗。
(3) 如果是對 one-hot 轉換后的每個特征構建列聯表進行卡方檢驗,那將會是個巨大的工程,因為one-hot 轉換通常會使特征維數成倍增加。因此我們需要一個快速計算 \(\chi^2\) 的方法,而不是繁瑣地對每個特征計算列聯表頻數,所幸 scikit-learn
中就提供了這樣的快捷方法,同時也將看到這個方法也為連續型變量的應用打開了一扇大門。下面看 feature_selection.chi2
的源碼 (有省略):
def chi2(X, y):
Y = LabelBinarizer().fit_transform(y) # (1)
if Y.shape[1] == 1:
Y = np.append(1 - Y, Y, axis=1)
observed = safe_sparse_dot(Y.T, X) # (2)
feature_count = X.sum(axis=0).reshape(1, -1) # (3)
class_prob = Y.mean(axis=0).reshape(1, -1) # (4)
expected = np.dot(class_prob.T, feature_count) # (5)
return _chisquare(observed, expected)
def _chisquare(f_obs, f_exp):
f_obs = np.asarray(f_obs, dtype=np.float64)
k = len(f_obs)
chisq = f_obs
chisq -= f_exp
chisq **= 2
with np.errstate(invalid="ignore"):
chisq /= f_exp
chisq = chisq.sum(axis=0)
return chisq, special.chdtrc(k - 1, chisq)
這個實現並不是傳統意義上的通過計算頻數構建列聯表,而是將屬於每一個標簽類別的特征取值總和作為列聯表單元格的觀測值,即第 (2) 步 (需要先在第 (1) 步將標簽離散化)。而對於列聯表單元格的期望值的計算,則是基於這樣的假設:如果標簽與特征獨立,則每個標簽類別為均勻分布,即第 (4) 步中的 \(\rm{class\_prob} \Longrightarrow p\),則第 (5) 步中每個單元格期望值的計算就與傳統意義上期望值類似了: \(\mathbb{E}[x] = \sum_i p_i x_i\) 。接下來的_chisuqare()
方法則是按照公式 \((1)\) 計算 \(\chi^2\) 值。
這樣實現的一大好處是可以通過矩陣相乘快速得出所有特征的觀測值和期望值,在計算出各特征的 \(\chi^2\) 值后,如上文所述,可以按每個特征的 \(\chi^2\) 值大小進行排序,方便地進行特征選擇。另一個好處是擴大了 chi2
的適用范圍,觀察上面的代碼,對於原始特征的唯一處理就是第 (3) 步中的 sum
,而不是原來的計算頻數,這樣一些連續型特征也可以使用該方法進行特征選擇了。
F 檢驗
F 檢驗是一類建立在 F 分布基礎上的假設檢驗方法,服從 F 分布的隨機變量與上文中卡方分布的關系如下:
\[F = \frac{X_1 / d_1}{X_2 / d_2} \tag{2} \]其中 \(X_1\) 和 \(X_2\) 分別服從自由度為 \(d_1\) 和 \(d_2\) 的卡方分布,即 \(X_1 \sim \chi^2(d_1), \;X_2 \sim \chi^2(d_2)\) ,且 \(X_1\) 與 \(X_2\) 獨立,則隨機變量 \(F\) 服從自由度為 \((d_1, d_2)\) 的F分布,記為 \(F \sim \text{F}(d_1, d_2)\) 。
下圖顯示不同自由度下F分布的概率密度曲線:

scikit-learn
中提供了兩種F檢驗方法 —— 適用於分類的 f_classif
和適用於回歸的 f_regression
,分別對應單因素方差分析和線性相關分析,下面分別介紹。
(1) 方差分析
在卡方檢驗中我們要測試的是被檢驗的特征與類別是否獨立,若拒絕零假設,則特征與類別相關。而在方差分析中則采用了不同的思路: 按照不同的標簽類別將特征划分為不同的總體,我們想要檢驗的是不同總體之間均值是否相同 (或者是否有顯著性差異)。下面承接上文的例子,類別為變態與否,因為方差分析只適用於連續型特征,所以這里采用了 “身高” 這個特征:

上圖中紅框和籃框中的特征值對應於兩個類別區分出的兩個不同的總體。方差分析用於特征選擇的邏輯是這樣: 如果樣本中是變態的平均身高為 1.7 米,非變態的平均身高也為 1.7 米,憑身高無法判定一個人變態與否,那么說明身高這個特征對於類別沒有區分度,則可以去除。反之,若前者的平均身高為 1.6 米,后者的平均身高為 1.9 米,那么我們有理由認為身高很能區分變態與否。因此將問題形式化為假設檢驗問題:
零假設 ($H_0$): $\mu_1 = \mu_2 = \cdots = \mu_k$ 備選假設 ($H_1$) : $k$ 個總體的均值不全相等
下面闡述方差分析的原理。設共有 \(k\) 個類別,總樣本數為 \(n\) ,第 \(j\) 個類別的樣本數為 \(n_j\) ,\(x_{ij}\) 表示第 \(j\) 個類別的第 \(i\) 個樣本,\(\bar{x_j}\) 表示第 \(j\) 個類別的樣本均值,即 \(\bar{x_j} = \frac{\sum_{i=1}^{n_j} x_{ij}}{n_j}\) ,\(\bar{x}\) 為總樣本均值 \(\bar{x} = \frac{\sum_{j=1}^k \sum_{i=1}^{n_j}x_{ij}}{n}\) ,那么樣本的總體變異為:
\(SST\) 可以分解為兩部分 —— 類別內差異 \(SSE\) 和類別間差異 \(SSB\) :
\(SSE\) 衡量每個類別內部樣本之間的差異,可以認為是隨機誤差。\(SSB\) 則衡量不同類別之間的差異。方差分析的基本思想是將不同類別之間的變異與隨機誤差作比較,如果二者之比大於某一臨界值,則可拒絕零假設接受備選假設,即不同類別間樣本均值不全相等,這也意味着樣本特征對於類別有一定的區分度。
而對於如何確定臨界值,則終於要用到傳說中的 F 分布了。在 \((2)\) 式中已經定義了服從F分布的隨機變量,注意到分子分母都要除以自由度,而 \(SSE\) 和 \(SSB\) 的自由度分別為 \(k-1\) 和 \(n-k\) ,因而統計檢驗量 \(F\) :
服從分子自由度為 \(k-1\),分母自由度為 \(n-k\) 為的 F 分布,即 \(\frac{MSB}{MSE} \sim F(k-1, \,n-k)\) 。看到這里,敏感的同學可能已經注意到了,方差分析的思想和線性判別分析 (Linear Discriminant Analysis,LDA) 非常類似 ( LDA 的思想可大致概括為 “投影后類內方差最小,類間方差最大”)。沒錯~,這兩個方法都是由英國大統計學家和生物學家 Ronald Fisher 爵士所創立。
於是按假設檢驗的套路,零假設成立的情況下算出 F 值,查 F 分布表,若p值小於0.05 (或0.01),則拒絕零假設接受備選假設,不同類別間均值不相等。在現實中使用軟件包可以方便地輸出方差分析表,這里使用 python 里的統計包 statsmodels
:
import statsmodels
import statsmodels.api as sm
from statsmodels.formula.api import ols
lm = ols('標簽 ~ 身高', data=data).fit()
table = sm.stats.anova_lm(lm, typ=1)
print(table)
#######################################################
df sum_sq mean_sq F P(>F)
身高 1.0 0.188034 0.188034 0.622642 0.460102
Residual 6.0 1.811966 0.301994 NaN NaN
從上表可以看出 \(p\) 值為0.46,所以不能拒絕零假設,即身高這個特征無法區分變態與否。
方差分析可用於控制一個或多個自變量來檢驗其與因變量的關系,進而檢測某種實驗效果,因而與實驗設計有着千絲萬縷的關系,不過這里面的水頗深。。。 甚至有很多專著,如 《 Design and Analysis of Experiments 》 等。 就一般的特征選擇問題而言,和卡方檢驗一樣,我們依然比較關心的是特征的相對重要性,所以可以按每個特征 F 值的大小進行排序,去除F值小的特征。
上面的例子中檢驗身高與標簽之間的關系,因為只有身高一個因素,所以被稱為單因素方差分析。當然其他還有雙因素方差分析,可以同時檢驗兩個特征與標簽的關系,以及兩個特征之間的相互關系,缺點是計算繁瑣,復雜度比單因素高。
單因素方差分析 (F檢驗) 與統計學中另一大假設檢驗方法 —— \(t\) 檢驗也頗有淵源,檢驗統計量 F 與 t 檢驗中的檢驗統計量 t 的關系為: \(F = t^2\) ,所以對於只有兩個類別來說,F 檢驗和 t 檢驗會得出相同的結論,但對於多個類別的情況,t檢驗只能兩兩進行比較,這會帶來一些問題:
- 多個類別之間兩兩比較,計算復雜度較高,如果有10個類別,則有 \(C_{10}^2 = 45\) 種組合。
- 對原始資料的利用率低,每次只能用到全部實驗數據的幾分之一。
- 會增大假陽性 (即第一類錯誤) 的概率,假設顯著性水平 \(\alpha = 0.05\) ,則犯第一類錯誤的概率為0.05,那么不犯第一類錯誤的概率為 \(1-0.05=0.95\)。對於有5個類別,10個組合的兩兩比較問題,至少犯一次第一類錯誤的概率上升到 \(1-0.95^{10} \approx 0.4\) ,這樣就降低了統計推斷的可靠性。
所以對於多個類別的比較,方差分析是首選,其相當於是 t 檢驗對於多類別的擴展,我想 scikit-learn
的特征選擇模塊中使用 F 檢驗而不是 t 檢驗是有這方面考慮的。
(2) 線性相關分析
對於特征和標簽皆為連續值的回歸問題,要檢測二者的相關性,最直接的做法就是求相關系數 \(r_{xy}\):
但 scikit-learn
中的 f_regression
采用的是先計算相關系數,然后轉化為F值。這又是個神奇的操作,究竟是如何轉換的?在線性回歸中常使用判定系數 \(R^2\) 作為回歸方程擬合數據點的程度,或者說是因變量的總體方差能被自變量解釋的比例。\(R^2\) 的定義以及和相關系數 \(r_{xy}\) 的關系如下:
其中 \(SSE\) 為誤差平方和:\(SSE = \sum_{i=1}^n (y_i - \hat{y}_i)^2\) ,\(SSR\) 為回歸平方和:\(SSR = \sum_{i=1}^n (\hat{y}_i - \bar{y})^2\) ,\(SST\) 為總體平方和:\(SST = \sum_{i=1}^n (y_i - \bar{y})^2\) 。可以看到這些式子與方差分析中的式子非常類似,不過注意這里計算的是都是標簽值 \(y\),而不是方差分析中的 \(x\) 。這其中的原理都是相通的,我們同樣可以用 \(SSR\) 和 \(SSE\) 來計算檢驗統計量 \(F\) (\(SSR\) 和 \(SSE\) 的自由度分別為1和 n-2 ):
即 \(\frac{MSR}{MSE} \sim F(1, \,n-2)\) 。這樣就可以方便地將相關系數轉化為 F 值了,接下來的步驟與之前的假設檢驗一樣。該方法的缺點是只能檢測線性相關關系,但不相關不代表獨立,可能是非線性相關關系。
互信息
互信息 (mutual information) 用於特征選擇,可以從兩個角度進行解釋:(1)、基於 KL 散度和 (2)、基於信息增益。對於離散型隨機變量 \(X, \,Y\),互信息的計算公式如下:
對於連續型變量:
可以看到連續型變量互信息的需要計算積分比較麻煩,通常先要進行離散化,所以這里主要討論離散型變量的情況。互信息可以方便地轉換為 KL 散度的形式:
我們知道 KL 散度可以用來衡量兩個概率分布之間的差異,而如果 \(x\) 和 \(y\) 是相互獨立的隨機變量,則 \(p(x,y) = p(x)\,p(y)\) ,那么 \((3.3)\) 式為 \(\huge{0}\)。因此若 \(I(X;Y)\) 越大,則表示兩個變量相關性越大,於是就可以用互信息來篩選特征。
而從信息增益的角度來看,互信息表示由於 \(X\) 的引入而使 \(Y\) 的不確定性減少的量。信息增益越大,意味着特征 \(X\) 包含的有助於將 \(Y\) 分類的信息越多 (即 \(Y\) 的不確定性越小)。決策樹就是一個典型的應用例子,其學習的主要過程就是利用信息增益來選擇最優划分特征,表示由於特征 \(A\) 而使得對數據集 \(D\) 的分類不確定性減少的程度,信息增益大的特征具有更強的分類能力。其計算公式為:
\((3.4)\) 式中 \(\mathcal{V}\) 表示特征 \(A\) 有 \(\mathcal{V}\) 個可能的取值,\(|D^v|\) 表示第 \(v\) 個取值上的樣本數量。 \((3.5)\) 式中設總共有 \(\mathcal{K}\) 個類別,\(|C_k|\) 表示屬於第 \(k\) 類的樣本數量,\(\sum_{k=1}^\mathcal{K}|C_k| = |D|\)。 \(|D_k^v|\) 表示特征 \(A\) 的取值為 \(v\) 且類別為 \(k\) 的樣本數量。
互信息和信息增益,二者是等價的,下面我們來看表示互信息的 \((3.1)\) 式是如何推導出表示信息增益的 \((3.4)\) 和 \((3.5)\) 式的:
上面的 \((a)\) 式就對應着 \((3.5)\) 式,而 \((b)\) 式對應 \((3.4)\) 式, \(p(y) \simeq \frac{|C_k|}{|D|}\;,\; p(x) \simeq \frac{|D^v|}{|D|}\;,\; p(y|x) \simeq \frac{|D_{k}^v|}{|D^v|}\) 。由此可以看到決策樹的學習過程也是一種依賴於訓練數據的極大似然估計。
再來探究下 \((b)\) 式,\(H(Y)\) 為熵,表示隨機變量 \(Y\) 的不確定性。\(H(Y|X)=\sum\limits_{x}p(x) H(Y|X=x)\) 為條件熵 (conditional entropy),表示在隨機變量 \(X\) 已知的情況下隨機變量 \(Y\) 的不確定性。那么二者的差 \(I(X;Y) = H(Y) - H(Y|X)\) 就表示由於 \(X\) 的引入而使 \(Y\) 的不確定性減少的量,維基里有一張形象的圖:

放在特征選擇的語境下,我們希望 \(Y\) 的不確定越小越好,這樣越有助於分類,那么互信息越大,則特征 \(X\) 使得 \(Y\) 的不確定性減少地也越多,即 \(X\) 中包含的關於 \(Y\) 的信息越多。因而策略還是和上文一樣,計算每個特征與類別的互信息值,排序后去除互信息小的特征。
互信息的一大優點是其能檢測出多種變量之間的關系,而相較而言 F 檢驗只能表示線性相關關系。Scikit-learn
的這個例子 (Comparison of F-test and mutual information) 中顯示了這一點,互信息能很好展現 \(x\) 和 \(y\) 之間的非線性關系:

/