一、模型介紹
上一篇文章介紹了一個梯度提升決策樹模型 XGBoost,這篇文章我們繼續(xù)學習一下 GBDT 模型的另一個進化版本:LightGBM。LigthGBM 是 boosting 集合模型中的新成員,由微軟提供,它和 XGBoost 一樣是對 GBDT 的高效實現,原理上它和 GBDT 及 XGBoost 類似。不過GBDT 在每一次迭代的時候,都需要遍歷整個訓練數據多次。如果把整個訓練數據裝進內存則會限制訓練數據的大小;如果不裝進內存,反復地讀寫訓練數據又會消耗非常大的時間。尤其面對工業(yè)級海量的數據,普通的 GBDT 算法是不能滿足其需求的。
LightGBM 提出的主要原因就是為了解決 GBDT 在海量數據遇到的問題,讓 GBDT 可以更好更快地用于工業(yè)實踐。LightGBM 在很多方面會比 XGBoost 表現的更為優(yōu)秀。這也是我們這篇文章要介紹的重點內容。
二、模型原理
LightGBM 的模型原理和 XGBoost 的模型原理基本一致,具體可參考 XGBoost。不過值得一提的是其中使用的兩個算法: GOSS 和 EFB 。
LightGBM 中的 GOSS 和 EFB 算法
提升樹是利用加模型與前向分布算法實現學習的優(yōu)化過程,它有一些高效實現,如 XGBoost,GBDT(Gradient Boosting Decision Tree) 等。其中 GBDT 采用負梯度作為劃分的指標(信息增益),XGBoost 則利用到二階導數。他們共同的不足是,計算信息增益需要掃描所有樣本,從而找到最優(yōu)劃分點。在面對大量數據或者特征維度很高時,它們的效率和擴展性很難使人滿意。解決這個問題的直接方法就是減少特征量和數據量而且不影響精確度,有部分工作根據數據權重采樣來加速 booisting 的過程,但由于 GBDT 沒有樣本權重不能應用。
LightGBM(基于GBDT的)則很好的解決這些問題,它主要包含兩個算法:
(1) 單邊梯度采樣,Gradient-based One-Side Sampling(GOSS)
GOSS(從減少樣本角度):排除大部分小梯度的樣本,僅用剩下的樣本計算信息增益,它是一種在減少數據量和保證精度上平衡的算法。
AdaBoost 中,樣本權重是數據重要性的指標。然而在 GBDT 中沒有原始樣本權重,不能應用權重采樣。幸運的是,我們觀察到 GBDT 中每個數據都有不同的梯度值,對采樣十分有用。即梯度小的樣本,訓練誤差也比較小,說明數據已經被模型學習得很好了,直接想法就是丟掉這部分梯度小的數據。然而這樣做會改變數據的分布,將會影響訓練模型的精確度,為了避免此問題,提出了 GOSS 算法。
GOSS 是一個樣本的采樣算法,目的是丟棄一些對計算信息增益沒有幫助的樣本留下有幫助的。根據計算信息增益的定義,梯度大的樣本對信息增益有更大的影響。因此,GOSS 在進行數據采樣的時候只保留了梯度較大的數據,但是如果直接將所有梯度較小的數據都丟棄掉勢必會影響數據的總體分布。所以,GOSS 首先將要進行分裂的特征的所有取值按照絕對值大小降序排序(XGBoost一樣也進行了排序,但是 LightGBM 不用保存排序后的結果),選取 top a 個實例。然后在剩余的數據中隨機采樣 b 個實例。接著計算信息增益時為采樣出的小梯度數據乘以 (1-a)/b,這樣算法就會更關注訓練不足的實例,而不會過多改變原數據集的分布。我們證明此措施在相同的采樣率下比隨機采樣獲得更準確的結果,尤其是在信息增益范圍較大時。
(2) 互斥特征綁定,Exclusive Feature Bundling(EFB)
EFB(從減少特征角度):EFB是通過特征捆綁的方式減少特征維度(其實是降維技術)的方式,來提升計算效率。
通常真是應用中,雖然特征量比較多,但是由于特征空間十分稀疏,是否可以設計一種無損的方法來減少有效特征呢?特別在稀疏特征空間上,許多特征幾乎是互斥的(例如許多特征不會同時為非零值,像 one-hot),我們可以捆綁互斥的特征。通常被捆綁的特征都是互斥的(一個特征值為零,一個特征值不為零),這樣兩個特征捆綁起來才不會丟失信息。如果兩個特征并不是完全互斥(部分情況下兩個特征都是非零值),可以用一個指標對特征不互斥程度進行衡量,稱之為沖突比率,當這個值較小時,我們可以選擇把不完全互斥的兩個特征捆綁,而不影響最后的精度。最后,我們將捆綁問題歸約到圖著色問題,通過貪心算法求得近似解。
EBF的算法步驟如下:
- 將特征按照非零值的個數進行排序。
- 計算不同特征之間的沖突比率。
- 遍歷每個特征并嘗試合并特征,使沖突比率最小化。
當然,這里面有兩個非常重要的問題:
- 怎么判定那些特征應該綁在一起(build bundled)?
- 怎么把特征綁為一個(merge feature)?
由于這兩個問題過于復雜,這里只作簡單的思路描述。
bundle(什么樣的特征被綁定)算法流程:
- 建立一個圖,每個點代表特征,每個邊有權重,其權重和特征之間總體沖突相關。
- 按照降序排列圖中的度數來排序特征。
- 檢查排序之后的每個特征,對他進行特征綁定或者建立新的綁定使得操作之后的總體沖突最小。
merging features(特征合并):
如何合并同一個 bundle 的特征來降低訓練時間復雜度。關鍵在于原始特征值可以從 bundle 中區(qū)分出來。鑒于直方圖算法存儲離散值而不是連續(xù)特征值,我們通過將互斥特征放在不同的箱中來構建 bundle。這可以通過將偏移量添加到特征原始值中實現,例如,假設 bundle 中有兩個特征,原始特征 A 取值 [0, 10], B 取值 [0, 20]。我們添加偏移量 10 到 B 中,因此 B 取值 [10, 30]。通過這種做法,就可以安全地將 A、B 特征合并,使用一個取值 [0, 30] 的特征取代 AB。
EFB 算法能夠將許多互斥的特征變?yōu)榈途S稠密的特征,就能夠有效的避免不必要 0 值特征的計算。實際,通過用表記錄數據中的非零值,來忽略零值特征,達到優(yōu)化基礎的直方圖算法。通過掃描表中的數據,建直方圖的時間復雜度將從 O(#data) 降到 O(#non_zero_data)。當然,這種方法在構建樹過程中需要而額外的內存和計算開銷來維持預特征表。我們在 LightGBM 中將此優(yōu)化作為基本函數,因為當 bundles 是稀疏的時候,這個優(yōu)化與 EFB 不沖突(可以用于 EFB)。
三、模型細節(jié)
1. XGBoost 的缺點
(1) 精確貪心算法
每輪迭代時,都需要遍歷整個訓練數據多次。如果把整個訓練數據裝進內存則會限制訓練數據的大?。蝗绻谎b進內存,反復地讀寫訓練數據又會消耗非常大的時間。
- 優(yōu)點:可以找到精確的劃分條件。
- 缺點:計算量巨大,內存占用巨大,易產生過擬合。
(2) 預排序方法(pre-sorted)
首先,空間消耗大。這樣的算法需要保存數據的特征值,還保存了特征排序的結果(例如排序后的索引,為了后續(xù)快速的計算分割點),這里需要消耗訓練數據兩倍的內存。其次時間上也有較大的開銷,在遍歷每一個分割點的時候,都需要進行分裂增益的計算,消耗的代價大。
- 優(yōu)點:可以使用多線程,可以加速精確貪心算法
- 缺點:效率低下,可能產生不必要的葉結點
(3) level-wise
生成決策樹是 level-wise 級別的,也就是預先設置好樹的深度之后,每一顆樹都需要生長到設置的那個深度,這樣有些樹在某一次分裂之后效果甚至沒有提升但仍然會繼續(xù)劃分樹枝,然后再次劃分....之后就是無用功了,耗時。
(4) 對 cache 優(yōu)化不友好
在預排序后,特征對梯度的訪問是一種隨機訪問,并且不同的特征訪問的順序不一樣,無法對 cache 進行優(yōu)化。同時,在每一層長樹的時候,需要隨機訪問一個行索引到葉子索引的數組,并且不同特征訪問的順序也不一樣,也會造成較大的 cache miss。
2. LightGBM對Xgboost的優(yōu)化
- 基于Histogram的決策樹算法
- 直方圖做差加速
- 直接支持類別特征 (Categorical Feature)
- 基于直方圖的稀疏特征優(yōu)化
- 帶深度限制的Leaf-wise的葉子生長策略
- Cache命中率優(yōu)化
- 多線程優(yōu)化
(1) Histogram算法
Histogram algorithm 應該翻譯為直方圖算法,直方圖算法的思想也很簡單,首先將連續(xù)的浮點數據轉換為 bin 數據,具體過程是首先確定對于每一個特征需要多少的桶 bin,然后均分,將屬于該桶的樣本數據更新為 bin 的值,最后用直方圖表示。將連續(xù)的浮點特征離散成 k 個離散值,并構造寬度為 k 的 Histogram。然后遍歷訓練數據,統(tǒng)計每個離散值在直方圖中的累計統(tǒng)計量。在進行特征選擇時,只需要根據直方圖的離散值,遍歷尋找最優(yōu)的分割點。

使用直方圖算法有很多優(yōu)點。首先最明顯就是內存消耗的降低,直方圖算法不僅不需要額外存儲預排序的結果,而且可以只保存特征離散化后的值,而這個值一般用 8 位整型存儲就足夠了,內存消耗可以降低為原來的 1/8。
然后在計算上的代價也大幅降低,預排序算法每遍歷一個特征值就需要計算一次分裂的增益,而直方圖算法只需要計算 k 次(k可以認為是常數),時間復雜度從 O(#data#feature) 優(yōu)化到 O(k#features)。
Histogram 算法并不是完美的。由于特征被離散化后,找到的并不是很精確的分割點,所以會對結果產生影響。但在實際的數據集上表明,離散化的分裂點對最終的精度影響并不大,甚至會好一些。原因在于 decision tree 本身就是一個弱學習器,采用 Histogram 算法會起到正則化的效果,有效地防止模型的過擬合。時間上的開銷由原來的 O(#data * #features) 降到 O(k * #features)。由于離散化,#bin 遠小于 #data,因此時間上有很大的提升。
Histogram 算法有幾個需要注意的地方:
- 使用 bin 替代原始數據相當于增加了正則化。
- 使用 bin 意味著很多數據的細節(jié)特征被放棄了,相似的數據可能被劃分到相同的桶中,這樣的數據之間的差異就消失了。
- bin 數量選擇決定了正則化的程度,bin 越少懲罰越嚴重,欠擬合風險越高。
- 構建直方圖時不需要對數據進行排序(比XGBoost快),因為預先設定了bin的范圍。
- 直方圖除了保存劃分閾值和當前 bin 內樣本數以外還保存了當前 bin 內所有樣本的一階梯度和(一階梯度和的平方的均值等價于均方損失)。
- 閾值的選取是按照直方圖從小到大遍歷,使用了上面的一階梯度和,目的是得到劃分之后 △loss 最大的特征及閾值。
Histogram 算法的優(yōu)缺點:
Histogram 算法并不是完美的。由于特征被離散化后,找到的并不是很精確的分割點,所以會對結果產生影響。但在實際的數據集上表明,離散化的分裂點對最終的精度影響并不大,甚至會好一些。原因在于 decision tree 本身就是一個弱學習器,采用 Histogram 算法會起到正則化的效果,有效地防止模型的過擬合。時間上的開銷由原來的 O(#data * #features) 降到 O(k * #features) 。由于離散化,#bin 遠小于 #data,因此時間上有很大的提升。
(2) 直方圖差加速:
LightGBM 另一個優(yōu)化是 Histogram(直方圖)做差加速。一個容易觀察到的現象:一個葉子的直方圖可以由它的父親節(jié)點的直方圖與它兄弟的直方圖做差得到。通常構造直方圖,需要遍歷該葉子上的所有數據,但直方圖做差僅需遍歷直方圖的k個桶。利用這個方法,LightGBM 可以在構造一個葉子的直方圖后,可以用非常微小的代價得到它兄弟葉子的直方圖,在速度上可以提升一倍。
(3) 直接支持類別特征:
實際上大多數機器學習工具都無法直接支持類別特征,一般需要把類別特征,轉化 one-hot 特征,降低了空間和時間的效率。而類別特征的使用是在實踐中很常用的?;谶@個考慮,LightGBM 優(yōu)化了對類別特征的支持,可以直接輸入類別特征,不需要額外的 0/1 展開。并在決策樹算法上增加了類別特征的決策規(guī)則。
one-hot 編碼是處理類別特征的一個通用方法,然而在樹模型中,這可能并不一定是一個好的方法,尤其當類別特征中類別個數很多的情況下。主要的問題是:
- 可能無法在這個類別特征上進行切分(即浪費了這個特征)。使用 one-hot 編碼,意味著在每一個決策節(jié)點上只能使用 one vs other 的切分方式。當類別值很多時,每個類別上的數據可能會比較少,這時候切分會產生不平衡,這意味著切分增益也會很?。ū容^直觀的理解是,不平衡的切分和不切分沒有區(qū)別)。
-
會影響決策樹的學習。因為就算可以在這個類別特征進行切分,也會把數據切分到很多零碎的小空間上。如下圖左邊所示,而決策樹學習時利用的是統(tǒng)計信息,在這些數據量小的空間上,統(tǒng)計信息不準確,學習會變差。但如果使用下圖右邊的分裂方式,數據會被切分到兩個比較大的空間,進一步的學習也會更好。
image.png
為了解決 one-hot 編碼處理類別特征的不足。LightGBM 采用了 many vs many 的切分方式,實現了類別特征的最優(yōu)切分。用 LightGBM 可以直接輸入類別特征,并產生上圖右邊的效果。在 1 個 k 維的類別特征中尋找最優(yōu)切分,樸素的枚舉算法的復雜度是 O(2^k),而LightGBM采用了如 On Grouping For Maximum Homogeneity 的方法實現了 O(k log k) 的算法。
算法流程下圖所示:在枚舉分割點之前,先把直方圖按每個類別的均值進行排序;然后按照均值的結果依次枚舉最優(yōu)分割點。從下圖可以看到,Sum(y)/Count(y) 為類別的均值。當然,這個方法很容易過擬合,所以在 LightGBM 中加入了很多對這個方法的約束和正則化。

(4) 基于直方圖的稀疏特征優(yōu)化
(5) 帶深度限制的 Leaf-wise 的葉子生長策略
在 Histogram 算法之上,LightGBM 進行進一步的優(yōu)化。首先它拋棄了大多數 GBDT 工具使用的按層生長 (level-wise) 的決策樹生長策略,而使用了帶有深度限制的按葉子生長 (leaf-wise) 算法。
XGBoost 采用的是按層生長 level-wise 生長策略,能夠同時分裂同一層的葉子,從而進行多線程優(yōu)化,不容易過擬合;但不加區(qū)分的對待同一層的葉子,帶來了很多沒必要的開銷。因為實際上很多葉子的分裂增益較低,沒必要進行搜索和分裂。
LightGBM 采用 leaf-wise 生長策略,每次從當前所有葉子中找到分裂增益最大(一般也是數據量最大)的一個葉子,然后分裂,如此循環(huán)。因此同 Level-wise相比,在分裂次數相同的情況下,Leaf-wise 可以降低更多的誤差,得到更好的精度。Leaf-wise 的缺點是可能會長出比較深的決策樹,產生過擬合。因此 LightGBM 在 Leaf-wise 之上增加了一個最大深度的限制,在保證高效率的同時防止過擬合。
(6) Cache 命中率優(yōu)化
(7) 線程優(yōu)化
直接支持高效并行。LightGBM 原生支持并行學習,目前支持特征并行和數據并行的兩種。特征并行的主要思想是在不同機器在不同的特征集合上分別尋找最優(yōu)的分割點,然后在機器間同步最優(yōu)的分割點。數據并行則是讓不同的機器先在本地構造直方圖,然后進行全局的合并,最后在合并的直方圖上面尋找最優(yōu)分割點。

LightGBM 針對這兩種并行方法都做了優(yōu)化,在特征并行算法中,通過在本地保存全部數據避免對數據切分結果的通信;在數據并行中使用分散規(guī)約 (Reduce scatter) 把直方圖合并的任務分攤到不同的機器,降低通信和計算,并利用直方圖做差,進一步減少了一半的通信量。

基于投票的數據并行則進一步優(yōu)化數據并行中的通信代價,使通信代價變成常數級別。在數據量很大的時候,使用投票并行可以得到非常好的加速效果。

更具體的內容可以看NIPS2016的文章:A Communication-Efficient Parallel Algorithm for Decision Tree
四、模型優(yōu)缺點
五、模型使用
import lightgbm as lgb
model = lgb.LGBMClassifier(boosting_type='gbdt', num_leaves=31, max_depth=-1,
learning_rate=0.1, n_estimators=100,
subsample_for_bin=200000, objective=None, class_weight=None,
min_split_gain=0., min_child_weight=1e-3, min_child_samples=20,
subsample=1., subsample_freq=0, colsample_bytree=1.,
reg_alpha=0., reg_lambda=0., random_state=None,
n_jobs=-1, silent=True, importance_type='split')
model.fit(X_train, y_train, eval_metric='l2',
eval_set=[(X_train, y_train), (X_eval, y_eval)],
eval_names=['train', 'evals'],
early_stopping_rounds=50,
verbose=500,
categorical_feature=categorical_feature)
model.predict(test_x)
model.predict_proba(test_x)
