從結構到性能,一文概述XGBoost、Light GBM和CatBoost的同與不同

文章選自Medium,機器之心編譯,原文點此跳轉。

儘管近年來神經網路復興並大為流行,但是 boosting 演算法在訓練樣本量有限、所需訓練時間較短、缺乏調參知識等場景依然有其不可或缺的優勢。本文從演算法結構差異、每個演算法的分類變數時的處理、演算法在數據集上的實現等多個方面對 3 種代表性的 boosting 演算法 CatBoost、Light GBM 和 XGBoost 進行了對比;雖然本文結論依據於特定的數據集,但通常情況下,XGBoost 都比另外兩個演算法慢。

最近,我參加了 kaggle 競賽 WIDS Datathon,並通過使用多種 boosting 演算法,最終排名前十。從那時開始,我就對這些演算法的內在工作原理非常好奇,包括調參及其優劣勢,所以有了這篇文章。儘管最近幾年神經網路復興,並變得流行起來,但我還是更加關注 boosting 演算法,因為在訓練樣本量有限、所需訓練時間較短、缺乏調參知識的場景中,它們依然擁有絕對優勢。

  • 2014 年 3 月,XGBOOST 最早作為研究項目,由陳天奇提出
  • 2017 年 1 月,微軟發布首個穩定版 LightGBM
  • 2017 年 4 月,俄羅斯頂尖技術公司 Yandex 開源 CatBoost

由於 XGBoost(通常被稱為 GBM 殺手)已經在機器學習領域出現了很久,如今有非常多詳細論述它的文章,所以本文將重點討論 CatBoost 和 LGBM,在下文我們將談到:

  • 演算法結構差異
  • 每個演算法的分類變數時的處理
  • 如何理解參數
  • 演算法在數據集上的實現
  • 每個演算法的表現

LightGBM 和 XGBoost 的結構差異

在過濾數據樣例尋找分割值時,LightGBM 使用的是全新的技術:基於梯度的單邊採樣(GOSS);而 XGBoost 則通過預分類演算法和直方圖演算法來確定最優分割。這裡的樣例(instance)表示觀測值/樣本。

首先讓我們理解預分類演算法如何工作:

  • 對於每個節點,遍歷所有特徵
  • 對於每個特徵,根據特徵值分類樣例
  • 進行線性掃描,根據當前特徵的基本信息增益,確定最優分割
  • 選取所有特徵分割結果中最好的一個

簡單說,直方圖演算法在某個特徵上將所有數據點劃分到離散區域,並通過使用這些離散區域來確定直方圖的分割值。雖然在計算速度上,和需要在預分類特徵值上遍歷所有可能的分割點的預分類演算法相比,直方圖演算法的效率更高,但和 GOSS 演算法相比,其速度仍然更慢。

為什麼 GOSS 方法如此高效?

在 Adaboost 中,樣本權重是展示樣本重要性的很好的指標。但在梯度提升決策樹(GBDT)中,並沒有天然的樣本權重,因此 Adaboost 所使用的採樣方法在這裡就不能直接使用了,這時我們就需要基於梯度的採樣方法。

梯度表徵損失函數切線的傾斜程度,所以自然推理到,如果在某些意義上數據點的梯度非常大,那麼這些樣本對於求解最優分割點而言就非常重要,因為算其損失更高。

GOSS 保留所有的大梯度樣例,並在小梯度樣例上採取隨機抽樣。比如,假如有 50 萬行數據,其中 1 萬行數據的梯度較大,那麼我的演算法就會選擇(這 1 萬行梯度很大的數據+x% 從剩餘 49 萬行中隨機抽取的結果)。如果 x 取 10%,那麼最後選取的結果就是通過確定分割值得到的,從 50 萬行中抽取的 5.9 萬行。

在這裡有一個基本假設:如果訓練集中的訓練樣例梯度很小,那麼演算法在這個訓練集上的訓練誤差就會很小,因為訓練已經完成了。

為了使用相同的數據分布,在計算信息增益時,GOSS 在小梯度數據樣例上引入一個常數因子。因此,GOSS 在減少數據樣例數量與保持已學習決策樹的準確度之間取得了很好的平衡。

高梯度/誤差的葉子,用於 LGBM 中的進一步增長

每個模型是如何處理屬性分類變數的?

CatBoost

CatBoost 可賦予分類變數指標,進而通過獨熱最大量得到獨熱編碼形式的結果(獨熱最大量:在所有特徵上,對小於等於某個給定參數值的不同的數使用獨熱編碼)。

如果在 CatBoost 語句中沒有設置「跳過」,CatBoost 就會將所有列當作數值變數處理。

注意,如果某一列數據中包含字元串值,CatBoost 演算法就會拋出錯誤。另外,帶有默認值的 int 型變數也會默認被當成數值數據處理。在 CatBoost 中,必須對變數進行聲明,才可以讓演算法將其作為分類變數處理。

對於可取值的數量比獨熱最大量還要大的分類變數,CatBoost 使用了一個非常有效的編碼方法,這種方法和均值編碼類似,但可以降低過擬合情況。它的具體實現方法如下:

1. 將輸入樣本集隨機排序,並生成多組隨機排列的情況。

2. 將浮點型或屬性值標記轉化為整數。

3. 將所有的分類特徵值結果都根據以下公式,轉化為數值結果。

其中 CountInClass 表示在當前分類特徵值中,有多少樣本的標記值是「1」;Prior 是分子的初始值,根據初始參數確定。TotalCount 是在所有樣本中(包含當前樣本),和當前樣本具有相同的分類特徵值的樣本數量。

可以用下面的數學公式表示:

LightGBM

和 CatBoost 類似,LighGBM 也可以通過使用特徵名稱的輸入來處理屬性數據;它沒有對數據進行獨熱編碼,因此速度比獨熱編碼快得多。LGBM 使用了一個特殊的演算法來確定屬性特徵的分割值。

注意,在建立適用於 LGBM 的數據集之前,需要將分類變數轉化為整型變數;此演算法不允許將字元串數據傳給分類變數參數。

XGBoost

和 CatBoost 以及 LGBM 演算法不同,XGBoost 本身無法處理分類變數,而是像隨機森林一樣,只接受數值數據。因此在將分類數據傳入 XGBoost 之前,必須通過各種編碼方式:例如標記編碼、均值編碼或獨熱編碼對數據進行處理。

超參數中的相似性

所有的這些模型都需要調節大量參數,但我們只談論其中重要的。以下是將不同演算法中的重要參數按照功能進行整理的表格。

實現

在這裡,我使用了 2015 年航班延誤的 Kaggle 數據集,其中同時包含分類變數和數值變數。這個數據集中一共有約 500 萬條記錄,因此很適合用來同時評估比較三種 boosting 演算法的訓練速度和準確度。我使用了 10% 的數據:50 萬行記錄。

以下是建模使用的特徵:

  • 月、日、星期:整型數據
  • 航線或航班號:整型數據
  • 出發、到達機場:數值數據
  • 出發時間:浮點數據
  • 到達延誤情況:這個特徵作為預測目標,並轉為二值變數:航班是否延誤超過 10 分鐘
  • 距離和飛行時間:浮點數據

import pandas as pd, numpy as np, timefrom sklearn.model_selection import train_test_splitdata = pd.read_csv("flights.csv")data = data.sample(frac = 0.1, random_state=10)data = data[["MONTH","DAY","DAY_OF_WEEK","AIRLINE","FLIGHT_NUMBER","DESTINATION_AIRPORT", "ORIGIN_AIRPORT","AIR_TIME", "DEPARTURE_TIME","DISTANCE","ARRIVAL_DELAY"]]data.dropna(inplace=True)data["ARRIVAL_DELAY"] = (data["ARRIVAL_DELAY"]>10)*1cols = ["AIRLINE","FLIGHT_NUMBER","DESTINATION_AIRPORT","ORIGIN_AIRPORT"]for item in cols: data[item] = data[item].astype("category").cat.codes +1train, test, y_train, y_test = train_test_split(data.drop(["ARRIVAL_DELAY"], axis=1), data["ARRIVAL_DELAY"], random_state=10, test_size=0.25)

XGBoost

import xgboost as xgbfrom sklearn import metricsdef auc(m, train, test): return (metrics.roc_auc_score(y_train,m.predict_proba(train)[:,1]), metrics.roc_auc_score(y_test,m.predict_proba(test)[:,1]))# Parameter Tuningmodel = xgb.XGBClassifier()param_dist = {"max_depth": [10,30,50], "min_child_weight" : [1,3,6], "n_estimators": [200], "learning_rate": [0.05, 0.1,0.16],}grid_search = GridSearchCV(model, param_grid=param_dist, cv = 3, verbose=10, n_jobs=-1)grid_search.fit(train, y_train)grid_search.best_estimator_model = xgb.XGBClassifier(max_depth=50, min_child_weight=1, n_estimators=200, n_jobs=-1 , verbose=1,learning_rate=0.16)model.fit(train,y_train)auc(model, train, test)

Light GBM

import lightgbm as lgbfrom sklearn import metricsdef auc2(m, train, test): return (metrics.roc_auc_score(y_train,m.predict(train)), metrics.roc_auc_score(y_test,m.predict(test)))lg = lgb.LGBMClassifier(silent=False)param_dist = {"max_depth": [25,50, 75], "learning_rate" : [0.01,0.05,0.1], "num_leaves": [300,900,1200], "n_estimators": [200] }grid_search = GridSearchCV(lg, n_jobs=-1, param_grid=param_dist, cv = 3, scoring="roc_auc", verbose=5)grid_search.fit(train,y_train)grid_search.best_estimator_d_train = lgb.Dataset(train, label=y_train)params = {"max_depth": 50, "learning_rate" : 0.1, "num_leaves": 900, "n_estimators": 300}# Without Categorical Featuresmodel2 = lgb.train(params, d_train)auc2(model2, train, test)#With Catgeorical Featurescate_features_name = ["MONTH","DAY","DAY_OF_WEEK","AIRLINE","DESTINATION_AIRPORT", "ORIGIN_AIRPORT"]model2 = lgb.train(params, d_train, categorical_feature = cate_features_name)auc2(model2, train, test)

CatBoost

在對 CatBoost 調參時,很難對分類特徵賦予指標。因此,我同時給出了不傳遞分類特徵時的調參結果,並評估了兩個模型:一個包含分類特徵,另一個不包含。我單獨調整了獨熱最大量,因為它並不會影響其他參數。

import catboost as cbcat_features_index = [0,1,2,3,4,5,6]def auc(m, train, test): return (metrics.roc_auc_score(y_train,m.predict_proba(train)[:,1]), metrics.roc_auc_score(y_test,m.predict_proba(test)[:,1]))params = {depth: [4, 7, 10], learning_rate : [0.03, 0.1, 0.15], l2_leaf_reg: [1,4,9], iterations: [300]}cb = cb.CatBoostClassifier()cb_model = GridSearchCV(cb, params, scoring="roc_auc", cv = 3)cb_model.fit(train, y_train)With Categorical featuresclf = cb.CatBoostClassifier(eval_metric="AUC", depth=10, iterations= 500, l2_leaf_reg= 9, learning_rate= 0.15)clf.fit(train,y_train)auc(clf, train, test)With Categorical featuresclf = cb.CatBoostClassifier(eval_metric="AUC",one_hot_max_size=31, depth=10, iterations= 500, l2_leaf_reg= 9, learning_rate= 0.15)clf.fit(train,y_train, cat_features= cat_features_index)auc(clf, train, test)

結語

為了評估模型,我們應該同時考慮模型的速度和準確度表現。

請記住,CatBoost 在測試集上表現得最好,測試集的準確度最高(0.816)、過擬合程度最小(在訓練集和測試集上的準確度很接近)以及最小的預測和調試時間。但這個表現僅僅在有分類特徵,而且調節了獨熱最大量時才會出現。如果不利用 CatBoost 演算法在這些特徵上的優勢,它的表現效果就會變成最差的:僅有 0.752 的準確度。因此我們認為,只有在數據中包含分類變數,同時我們適當地調節了這些變數時,CatBoost 才會表現很好。

第二個使用的是 XGBoost,它的表現也相當不錯。即使不考慮數據集包含有轉換成數值變數之後能使用的分類變數,它的準確率也和 CatBoost 非常接近了。但是,XGBoost 唯一的問題是:它太慢了。尤其是對它進行調參,非常令人崩潰(我用了 6 個小時來運行 GridSearchCV——太糟糕了)。更好的選擇是分別調參,而不是使用 GridSearchCV。

最後一個模型是 LightGBM,這裡需要注意的一點是,在使用 CatBoost 特徵時,LightGBM 在訓練速度和準確度上的表現都非常差。我認為這是因為它在分類數據中使用了一些修正的均值編碼方法,進而導致了過擬合(訓練集準確率非常高:0.999,尤其是和測試集準確率相比之下)。但如果我們像使用 XGBoost 一樣正常使用 LightGBM,它會比 XGBoost 更快地獲得相似的準確度,如果不是更高的話(LGBM—0.785, XGBoost—0.789)。

最後必須指出,這些結論在這個特定的數據集下成立,在其他數據集中,它們可能正確,也可能並不正確。但在大多數情況下,XGBoost 都比另外兩個演算法慢。

所以,你更喜歡哪個演算法呢?


推薦閱讀:

情緒大腦皮層和皮層下神經過程 植物性神經下丘腦和邊緣 杏仁核 海馬 扣帶回 大腦皮層 腦垂體腎上腺甲狀腺
項目筆記(一):實驗——用神經網路實現midi音樂旋律音軌的確定
先睹為快:神經網路頂會ICLR 2018論文接受結果速覽
CS224N Lecture1 筆記
GAN之父:讓機器擁有天賦 我還在對付利用AI作惡的人

TAG:xgboost | boosting | 神經網路 |