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

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

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

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

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

因爲 XGBoost(一般被稱爲 GBM 殺手)已經在機器學習領域出現了好久,現在有很是多詳細論述它的文章,因此本文將重點討論 CatBoost 和 LGBM,在下文咱們將談到:網絡

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

LightGBM 和 XGBoost 的結構差別

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

首先讓咱們理解預分類算法如何工做:機器學習

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

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


爲何 GOSS 方法如此高效?

在 Adaboost 中,樣本權重是展現樣本重要性的很好的指標。但在梯度提高決策樹(GBDT)中,並無自然的樣本權重,所以 Adaboost 所使用的採樣方法在這裏就不能直接使用了,這時咱們就須要基於梯度的採樣方法。學習

梯度表徵損失函數切線的傾斜程度,因此天然推理到,若是在某些意義上數據點的梯度很是大,那麼這些樣本對於求解最優分割點而言就很是重要,由於算其損失更高。測試

GOSS 保留全部的大梯度樣例,並在小梯度樣例上採起隨機抽樣。好比,假若有 50 萬行數據,其中 1 萬行數據的梯度較大,那麼個人算法就會選擇(這 1 萬行梯度很大的數據+x% 從剩餘 49 萬行中隨機抽取的結果)。若是 x 取 10%,那麼最後選取的結果就是經過肯定分割值獲得的,從 50 萬行中抽取的 5.9 萬行。ui

在這裏有一個基本假設:若是訓練集中的訓練樣例梯度很小,那麼算法在這個訓練集上的訓練偏差就會很小,由於訓練已經完成了。編碼

爲了使用相同的數據分佈,在計算信息增益時,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, time
from sklearn.model_selection import train_test_split

data = 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)*1

cols = ["AIRLINE","FLIGHT_NUMBER","DESTINATION_AIRPORT","ORIGIN_AIRPORT"]
for item in cols:
    data[item] = data[item].astype("category").cat.codes +1

train, 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 xgb
from sklearn import metrics

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]))

# Parameter Tuning
model = 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 lgb
from sklearn import metrics

def 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 Features
model2 = lgb.train(params, d_train)
auc2(model2, train, test)

#With Catgeorical Features
cate_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 cb
cat_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 features
clf = 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 features
clf = 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 都比另外兩個算法慢。

因此,你更喜歡哪一個算法呢?

相關文章
相關標籤/搜索