淺談 Tree model 的 feature importance
本文想回答的問題:
- 不同套件中,使用了哪些 feature importance 與其原理
- 各種 feature importance 有什麼優缺點與限制
- 關鍵字: feature importance, lightGBM, random forest, gradient boosting decision tree
- 先備知識: decision tree 的基本概念 、 entropy 、 gini-index
0. 前言:
在處理傳統表格式的資料時, tree-based model 仍舊是目前 Machine learning 演算法的主流…吧?而 feature importance 則是訓練完一定會拿來看的重要資訊。所以我在當面試官時,常常會問這樣的問題:「tree model 的 feature importance 如何計算?如何解讀?」一問就知道面試者對 tree model 的理論理解程度,以及是否有真的拿資料 fit 過的實務經驗。
但 feature importance 到底怎麼算的 ? 在 scikit-learn, lightGBM, XGBoost 三個常用套件中,各自有不同的作法,今天就來一次講清楚,當一個有溫度的套件俠^ ^。
1. 不同套件支援的 feature importance:
直接上表格給大家參考
2. Scikit-learn 支援的 feature importance:
scikit-learn 的 random forest 與 GBDT 有兩種 feature importance 可以使用,分別為 impurity-based feature importance 與 permutation importance。
a. impurity-based feature importance
一句話解釋的話,就是計算每個特徵對於 loss 下降的貢獻。但貢獻度要如何計算?
tree model 都是透過特徵將資料分組 (切分枝),達到降低 entropy (或 gini index) 的目的。因此,以單顆 decision tree 來說,我們可以去找樹上每一個分枝,看他用的是哪個特徵,以及切了之後的 entropy 變化,就可以當作該特徵在這個節點上對模型的貢獻。
某特徵在某節點的貢獻 = 切之前節點的_entropy — 切之後的兩個節點的_entropy_加總
接著,找出所有節點,把不同特徵的貢獻個別累加起來,就可以算出一棵樹中,每個特徵的貢獻度,即是 feature importance。不過,每個節點資料的數量都不相同,直接拿每個節點上 entropy 相減的值來比其實不太公平。所以 scikit learn 的做法其實是這樣的:
importance_data[node.feature] += (
node.weighted_n_node_samples * node.impurity —
left.weighted_n_node_samples * left.impurity —
right.weighted_n_node_samples * right.impurity
)
(source: 程式碼)
簡單來說,就是乘上節點上的資料數量,一個權重的概念。
這邊隨便舉個例子。參考下圖,第二層左邊的藍色節點,用 sepal_length 作爲切點後將資料切為兩堆,各有 1 個與 36 個樣本,且可以完美分類。該節點上 sepal_length 的 feature importance 為:
(0.053 * 37) - (0 * 1) - (0 * 36) = 1.961
算出每顆樹的 feature importance 後,因為我們是 random forest 嘛,接著就把每顆樹上的 feature importance 取平均,就可以得到最終的 feature importance 值。
附帶一提, scikit-learn 在計算 feature importance 過程中,共經過兩次的標準化 (normalize),第一次是在 decision tree 時,讓每顆樹的所有特徵 feature importance 的加總是一,第二次則是在 random forest 中,將每個特徵在每顆樹中 importance 取平均,使得最後輸出的 feature importance 加總為一。
impurity-based feature importance 的限制
其實官方的範例都講得很清楚了,可以直接看。總結一下, impurity-based 算出來的 feature importance 只能反應訓練資料上的重要性,因而易受 overfitting 影響。另外,它會過度放大 high cardinality 特徵的重要性 (一個二元特徵切過之後,其下游分枝就不能再切了,但連續型特徵卻可以一直切一直爽)。
b. permutation importance
要理解其概念,可以試想下面情境:我們有了一個訓練好的模型後,接著就會去評估(evaluate)模型在訓練或測試集上的準確度。假設今天有個特徵很重要,我們卻耍白目把它在資料集上的數值打亂(random shuffle),那理論上模型的預測結果應該會因此大幅失準。
permutation importance 的概念大概就是這樣,針對每個特徵,我們依序隨機打散其中的數值,並計算模型準確度,而準確度下降的幅度即代表特徵的重要性。如果一個特徵被打亂後,準確度根本不受影響,那合理判斷這個特徵根本可有可無。
雖然上面都說模型準確度,但 permutation importance 其實可以吃不同的指標(recall, F1 score 等等),也能自定義函數作為指標。另外也可以在不同的資料集上計算 permutation_importance ,也就是說我們可以在訓練集上,也可以在測試集上做。另外, scikit-learn 實作的 permutation importance 也不限定於 tree-model 上,只要是分類器都可以使用!
那什麼時候可考慮 permutation importance 呢? 首先,他可以在 validation set 上做,減少了 biased 的問題,也沒有 impurity-based 方法會偏好 high cardinality features 的問題。
缺點呢,他要跑很久!因為要對每個 feature shuffle n 次,所以若有 k 個 feature,就要做 n * k 次的預測。另外,若兩個特徵相關性很高,也會使兩個特徵的 importance 都不高,類似迴歸的共線性問題。
此外,自己覺得 permutation importance 的數值滿難解釋的。它的數值呈現的是每個特徵被打亂後,對應的指標下降數值,以準確度來說,一個掉 5% ,一個掉 2% ,怎樣算多?看起來實在沒什麼感覺。而且,如果用不同參數訓練出兩個模型,在不同準確度下,其數值也很難拿來比較。另外,要用這個東西,還要試著跟業務端解釋其概念,他們會買單嗎?
相較之下 impurity-based 的概念很好理解,又可以換算成比例,可說是非常親切。而且大家都愛用,業務端大概也有相關經驗,就不用多費唇舌解釋一遍。
特徵 A 可以解釋 60% 的模型預測力!業務端:哦哦哦哦哦!!
3. LightGBM 支援的 feature importance
lightGBM 是我個人最常使用的 boosting tree 演算法,它實作了 gain 與 split-count 兩種方法:
a. gain:
基本上與 impurity-based feature importances 概念相同,只是 lightGBM 與 XGBoost 都能吃客製化的 objective function,所以實際上要看 objective function 長怎樣,但基本上就是看 objective function 減少的幅度。另外,注意 lightGBM 沒有做標準化,所以 feature importance 加總不為 1。
b. split count:
計算特徵被選為切點的次數,簡單直白。是預設的 feature importance 算法。一樣沒有做標準化,所以 feature importance 加總不為 1。根據自己做的實驗, split 比 gain 更容易受噪音影響(參考最下面的附錄)。
c. scikit-learn permutation importance:
What? 眾所皆知,lightGBM 有實作 scikit-learn API,所以可以用這種方式直接去接 scikit-learn 寫好的功能。
4. XGBoost 支援的 feature importance
xgboost 有五種給你選,直接上表格。
gain 與 weight 基本上與 lightGBM 相同,但是多了一個 cover:
a. cover:
cover 代表特徵作為切點時該節點的資料量。 decision tree 在越下游的分枝,其資料量會越來越少,直覺來想當然是越上游的切點越重要,所以一個特徵越常在上游被當切點,其 cover 就會越大。要注意的是,同一棵樹中特徵可能被重複拿來當切點,可能因此造成該特徵的平均 cover 變小(weight 很大,但 cover 很小)。
除了平均 ,xgboost 還很友善的提供加總的 gain 與 cover 供大家參考(不過其實 total_gain 就是 weight * gain 啦)。
XGBoost 給你五種選擇,不過這些 feature importance 算出來通常不太相同,有時反而讓人眼花撩亂。
下面用 scikit-learn 的 wine dataset 來比較各種 feature importance , 因為 feature importance 通常是拿來做特徵篩選,這邊就直接比排名 (rank),並計算 rank_avg ,代表五種 feature importance 的 rank 平均。 lgb_rank 則是我額外 train 了一個 lightGBM 模型,它的 gain importance (rank)。
可以發現各種 importance 變動不小,建議大家可以多加比較再做判斷,另外算出來的 importance 與 lightGBM 也差滿多的。
5. 結語
這次簡介了不同套件實作的 feature importance 。 feature importance 除了用來解釋模型,也很常用於特徵篩選(過濾掉不需要的特徵),因此,我也針對噪音對 feature importance 跑了一些實驗,就留到下次(哪次?)分享囉^^。
附錄:比較噪音對 gain 與 split 的影響:
這邊我針對兩種方法,做了一個簡單的實驗:在 wine dataset 中加入不同數量的噪音變數(random normal),看看兩種方法受噪音影響的程度。
參考下表, index 代表加入的噪音數量(原始有 13 個特徵, index 13 代表加入 13 個噪音變數,所以總共有 26 個變數建模),對應數值則為建模後這些噪音變數的 feature importance 加總。這邊都經過 normalize ,所以假設數值為 0.2 ,即代表噪音變數分走了 20% 的 feature importance,以 gain 來解釋的話,代表 20% 的 loss 下降都是透過噪音變數亂切的結果。
可以發現, 使用 LightGBM 時, split 明顯比 gain 易受噪音影響。而比較 random forest 與 boosting tree,又可以發現 random forest 較易受噪音影響。這個結果也告訴我們,如果你的模型中存在很多噪音或不重要的變數,的確是會影響模型準確度,必須要小心的進行特徵篩選,或設定相關的超參數(例如樹的深度、隨機抽取特徵的比例等)。