XGBoost類庫使用小結

劉建平Pinard發表於2019-07-01

    在XGBoost演算法原理小結中,我們討論了XGBoost的演算法原理,這一片我們討論如何使用XGBoost的Python類庫,以及一些重要引數的意義和調參思路。

    本文主要參考了XGBoost的Python文件 和 XGBoost的引數文件

1. XGBoost類庫概述

    XGBoost除了支援Python外,也支援R,Java等語言。本文關注於Python的XGBoost類庫,安裝使用"pip install xgboost"即可,目前使用的是XGBoost的0.90版本。XGBoost類庫除了支援決策樹作為弱學習器外,還支援線性分類器,以及帶DropOut的決策樹DART,不過通常情況下,我們使用預設的決策樹弱學習器即可,本文也只會討論使用預設決策樹弱學習器的XGBoost。

    XGBoost有2種Python介面風格。一種是XGBoost自帶的原生Python API介面,另一種是sklearn風格的API介面,兩者的實現是基本一樣的,僅僅有細微的API使用的不同,主要體現在引數命名上,以及資料集的初始化上面。

2. XGBoost類庫的基本使用方式

    完整示例參見我的Github程式碼

2.1 使用原生Python API介面

    XGBoost的類庫的2種介面風格,我們先來看看原生Python API介面如何使用。

    原生XGBoost需要先把資料集按輸入特徵部分,輸出部分分開,然後放到一個DMatrix資料結構裡面,這個DMatrix我們不需要關心裡面的細節,使用我們的訓練集X和y初始化即可。

import pandas as pd
import numpy as np
import xgboost as xgb
import matplotlib.pylab as plt
%matplotlib inline

from sklearn.model_selection import GridSearchCV
from sklearn.model_selection import train_test_split
from sklearn.datasets.samples_generator import make_classification
# X為樣本特徵,y為樣本類別輸出, 共10000個樣本,每個樣本20個特徵,輸出有2個類別,沒有冗餘特徵,每個類別一個簇
X, y = make_classification(n_samples=10000, n_features=20, n_redundant=0,
                             n_clusters_per_class=1, n_classes=2, flip_y=0.1)
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=1)
dtrain = xgb.DMatrix(X_train,y_train)
dtest = xgb.DMatrix(X_test,y_test)

    上面的程式碼中,我們隨機初始化了一個二分類的資料集,然後分成了訓練集和驗證集。使用訓練集和驗證集分別初始化了一個DMatrix,有了DMatrix,就可以做訓練和預測了。簡單的示例程式碼如下:

param = {'max_depth':5, 'eta':0.5, 'verbosity':1, 'objective':'binary:logistic'}
raw_model = xgb.train(param, dtrain, num_boost_round=20)
from sklearn.metrics import accuracy_score
pred_train_raw = raw_model.predict(dtrain)
for i in range(len(pred_train_raw)):
    if pred_train_raw[i] > 0.5:
         pred_train_raw[i]=1
    else:
        pred_train_raw[i]=0               
print (accuracy_score(dtrain.get_label(), pred_train_raw))

    訓練集的準確率我這裡輸出是0.9664。再看看驗證集的表現:

pred_test_raw = raw_model.predict(dtest)
for i in range(len(pred_test_raw)):
    if pred_test_raw[i] > 0.5:
         pred_test_raw[i]=1
    else:
        pred_test_raw[i]=0               
print (accuracy_score(dtest.get_label(), pred_test_raw))

    驗證集的準確率我這裡的輸出是0.9408,已經很高了。

     不過對於我這樣用慣sklearn風格API的,還是不太喜歡原生Python API介面,既然有sklearn的wrapper,那麼就儘量使用sklearn風格的介面吧。

2.2 使用sklearn風格介面,使用原生引數

    對於sklearn風格的介面,主要有2個類可以使用,一個是分類用的XGBClassifier,另一個是迴歸用的XGBRegressor。在使用這2個類的使用,對於演算法的引數輸入也有2種方式,第一種就是仍然使用和原始API一樣的引數命名集合,另一種是使用sklearn風格的引數命名。我們這裡先看看如何使用和原始API一樣的引數命名集合。

    其實就是使用XGBClassifier/XGBRegressor的**kwargs引數,把上面原生引數的params集合放進去,程式碼如下:

sklearn_model_raw = xgb.XGBClassifier(**param)
sklearn_model_raw.fit(X_train, y_train, early_stopping_rounds=10, eval_metric="error",
        eval_set=[(X_test, y_test)])

    裡面的param其實就是2.1節裡面定義的:

param = {'max_depth':5, 'eta':0.5, 'verbosity':1, 'objective':'binary:logistic'}

    使用sklearn風格的介面,卻使用原始的引數名定義,感覺還是有點怪,所以我一般還是習慣使用另一種風格介面,sklearn風格的引數命名。

2.3 使用sklearn風格介面,使用sklearn風格引數

    使用sklearn風格的介面,並使用sklearn風格的引數,是我推薦的方式,主要是這樣做和GBDT之類的sklearn庫使用起來沒有什麼兩樣了,也可以使用sklearn的網格搜尋。

    不過這樣做的話,引數定義命名和2.1與2.2節就有些不同了。具體的引數意義我們後面講,我們看看分類的演算法初始化,訓練與呼叫的簡單過程:

sklearn_model_new = xgb.XGBClassifier(max_depth=5,learning_rate= 0.5, verbosity=1, objective='binary:logistic',random_state=1)

    可以看到,引數定義直接放在了XGBClassifier的類引數裡,和sklearn類似。大家可以看到之前兩節我們定義的步長eta,這裡變成了另一個名字learning_rate。

    在初始化後,訓練和預測的方法就和2.2節沒有區別了。

sklearn_model_new.fit(X_train, y_train, early_stopping_rounds=10, eval_metric="error",
        eval_set=[(X_test, y_test)])

3. XGBoost類庫引數

    在第二節我們已經嘗試使用XGBoost類庫了,但是對於XGBoost的類庫引數並沒有過多討論。這裡我們就詳細討論下,主要以2.3節的sklearn風格引數為主來進行討論。這些引數我會和之前講的scikit-learn 梯度提升樹(GBDT)調參小結中的引數定義對應,這樣如果大家對GBDT的調參很熟悉了,那麼XGBoost的調參也就掌握90%了。

    XGBoost的類庫引數主要包括boosting框架引數,弱學習器引數以及其他引數。

3.1  XGBoost框架引數 

    對於XGBoost的框架引數,最重要的是3個引數: booster,n_estimators和objectve。

    1) booster決定了XGBoost使用的弱學習器型別,可以是預設的gbtree, 也就是CART決策樹,還可以是線性弱學習器gblinear以及DART。一般來說,我們使用gbtree就可以了,不需要調參。

    2) n_estimators則是非常重要的要調的引數,它關係到我們XGBoost模型的複雜度,因為它代表了我們決策樹弱學習器的個數。這個引數對應sklearn GBDT的n_estimators。n_estimators太小,容易欠擬合,n_estimators太大,又容易過擬合,一般需要調參選擇一個適中的數值。

    3) objective代表了我們要解決的問題是分類還是迴歸,或其他問題,以及對應的損失函式。具體可以取的值很多,一般我們只關心在分類和迴歸的時候使用的引數。

    在迴歸問題objective一般使用reg:squarederror ,即MSE均方誤差。二分類問題一般使用binary:logistic, 多分類問題一般使用multi:softmax。

 3.2  XGBoost 弱學習器引數   

    這裡我們只討論使用gbtree預設弱學習器的引數。  要調參的引數主要是決策樹的相關引數如下: 

    1)   max_depth: 控制樹結構的深度,資料少或者特徵少的時候可以不管這個值。如果模型樣本量多,特徵也多的情況下,需要限制這個最大深度,具體的取值一般要網格搜尋調參。這個引數對應sklearn GBDT的max_depth。

    2) min_child_weight: 最小的子節點權重閾值,如果某個樹節點的權重小於這個閾值,則不會再分裂子樹,即這個樹節點就是葉子節點。這裡樹節點的權重使用的是該節點所有樣本的二階導數的和,即XGBoost原理篇裡面的$H_{tj}$:$$H_{tj} =  \sum\limits_{x_i \in R_{tj}}h_{ti}$$

    這個值需要網格搜尋尋找最優值,在sklearn GBDT裡面,沒有完全對應的引數,不過min_samples_split從另一個角度起到了閾值限制。

    3) gamma: XGBoost的決策樹分裂所帶來的損失減小閾值。也就是我們在嘗試樹結構分裂時,會嘗試最大數下式:$$ \max \frac{1}{2}\frac{G_L^2}{H_L + \lambda} + \frac{1}{2}\frac{G_R^2}{H_R+\lambda}  - \frac{1}{2}\frac{(G_L+G_R)^2}{H_L+H_R+ \lambda} - \gamma$$

    這個最大化後的值需要大於我們的gamma,才能繼續分裂子樹。這個值也需要網格搜尋尋找最優值。

    4) subsample: 子取樣引數,這個也是不放回抽樣,和sklearn GBDT的subsample作用一樣。選擇小於1的比例可以減少方差,即防止過擬合,但是會增加樣本擬合的偏差,因此取值不能太低。初期可以取值1,如果發現過擬合後可以網格搜尋調參找一個相對小一些的值。

    5) colsample_bytree/colsample_bylevel/colsample_bynode: 這三個引數都是用於特徵取樣的,預設都是不做取樣,即使用所有的特徵建立決策樹。colsample_bytree控制整棵樹的特徵取樣比例,colsample_bylevel控制某一層的特徵取樣比例,而colsample_bynode控制某一個樹節點的特徵取樣比例。比如我們一共64個特徵,則假設colsample_bytree,colsample_bylevel和colsample_bynode都是0.5,則某一個樹節點分裂時會隨機取樣8個特徵來嘗試分裂子樹。

    6) reg_alpha/reg_lambda: 這2個是XGBoost的正則化引數。reg_alpha是L1正則化係數,reg_lambda是L1正則化係數,在原理篇裡我們討論了XGBoost的正則化損失項部分:$$\Omega(h_t) = \gamma J + \frac{\lambda}{2}\sum\limits_{j=1}^Jw_{tj}^2$$

    上面這些引數都是需要調參的,不過一般先調max_depth,min_child_weight和gamma。如果發現有過擬合的情況下,再嘗試調後面幾個引數。

3.3  XGBoost 其他引數

    XGBoost還有一些其他的引數需要注意,主要是learning_rate。

    learning_rate控制每個弱學習器的權重縮減係數,和sklearn GBDT的learning_rate類似,較小的learning_rate意味著我們需要更多的弱學習器的迭代次數。通常我們用步長和迭代最大次數一起來決定演算法的擬合效果。所以這兩個引數n_estimators和learning_rate要一起調參才有效果。當然也可以先固定一個learning_rate ,然後調完n_estimators,再調完其他所有引數後,最後再來調learning_rate和n_estimators。

    此外,n_jobs控制演算法的併發執行緒數, scale_pos_weight用於類別不平衡的時候,負例和正例的比例。類似於sklearn中的class_weight。importance_type則可以查詢各個特徵的重要性程度。可以選擇“gain”, “weight”, “cover”, “total_gain” 或者 “total_cover”。最後可以通過呼叫booster的get_score方法獲取對應的特徵權重。“weight”通過特徵被選中作為分裂特徵的計數來計算重要性,“gain”和“total_gain”則通過分別計算特徵被選中做分裂特徵時帶來的平均增益和總增益來計算重要性。“cover”和 “total_cover”通過計算特徵被選中做分裂時的平均樣本覆蓋度和總體樣本覆蓋度來來計算重要性。

4. XGBoost網格搜尋調參

    XGBoost可以和sklearn的網格搜尋類GridSeachCV結合使用來調參,使用時和普通sklearn分類迴歸演算法沒有區別。具體的流程的一個示例如下:

gsCv = GridSearchCV(sklearn_model_new,
                   {'max_depth': [4,5,6],
                    'n_estimators': [5,10,20]})
gsCv.fit(X_train,y_train)
print(gsCv.best_score_)
print(gsCv.best_params_)

    我這裡的輸出是:

    0.9533333333333334

    {'max_depth': 4, 'n_estimators': 10}

    接著嘗試在上面搜尋的基礎上調learning_rate :

sklearn_model_new2 = xgb.XGBClassifier(max_depth=4,n_estimators=10,verbosity=1, objective='binary:logistic',random_state=1)
gsCv2 = GridSearchCV(sklearn_model_new2, 
                   {'learning_rate ': [0.3,0.5,0.7]})
gsCv2.fit(X_train,y_train)
print(gsCv2.best_score_)
print(gsCv2.best_params_)

    我這裡的輸出是:

    0.9516

    {'learning_rate ': 0.3}

    當然實際情況這裡需要繼續調參,這裡假設我們已經調參完畢,我們嘗試用驗證集看看效果:

sklearn_model_new2 = xgb.XGBClassifier(max_depth=4,learning_rate= 0.3, verbosity=1, objective='binary:logistic',n_estimators=10)
sklearn_model_new2.fit(X_train, y_train, early_stopping_rounds=10, eval_metric="error",
        eval_set=[(X_test, y_test)])

    最後的輸出是:

    [9]	validation_0-error:0.0588

    也就是驗證集的準確率是94.12%。

    我們可以通過驗證集的準確率來判斷我們前面網格搜尋調參是否起到了效果。實際處理的時候需要反覆搜尋引數並驗證。

    以上就是XGBoost的類庫使用總結了,希望可以幫到要用XGBoost解決實際問題的朋友們。

 

(歡迎轉載,轉載請註明出處。歡迎溝通交流: liujianping-ok@163.com)  

相關文章