競賽常用集成學習框架Boosting算法總結(XGBoost、LightGBM)(附代碼)

Boosting類算法簡介

Boosting是一類可將弱學習器提升爲強學習器的算法,這類算法的工作機制如下:先從初始訓練集訓練出一個基學習器,再根據基學習器的表現對訓練樣本分佈進行調整,使得先前基學習器做錯的訓練樣本在後續受到更多關注,然後基於調整後的樣本分佈來訓練下一個基學習器;重複進行這一操作,最終將各個基學習器得到的結果加權結合。常見的Boosting算法包括GBDT、XGBoost和LightGBM等。

XGBoost

XGBoost原理

XGBoost是由K個基模型組成的一個加法運算式,其基模型爲CART迴歸樹(二叉樹)。訓練完成得到K棵樹,當預測一個樣本的分數時,根據這個樣本的特徵,在每棵樹中會落到對應的一個葉子節點,每個葉子節點對應一個分數,將每棵樹對應的分數加起來就是該樣本的預測值。

$$\hat{y}_{i}=\sum_{k=1}^{K} f_{k}\left(x_{i}\right)$$

如下圖例子,訓練兩棵決策樹,小孩的預測分數就是兩棵樹中小孩所落到的結點的分數相加。爺爺的預測分數同理。

XGBoost的目標函數定義爲:

O b j=\sum_{i=1}^{n} l\left(y_{i}, \hat{y}_{i}\right)+\sum_{k=1}^{K} \Omega\left(f_{k}\right)    其中   \Omega\left(f_{t}\right)=\gamma T+\frac{1}{2} \lambda \sum_{j=1}^{T} w_{j}^{2} 

目標函數由兩部分構成,第一部分用來衡量預測分數和真實分數的差距,第二部分爲正則化項。正則化項同樣包含兩部分,T表示葉子結點的個數,w表示葉子節點的分數。超參數γ可以控制葉子結點的個數,λ可以控制葉子節點的分數不會過大,防止樹過於複雜而造成過擬合。

boosting 模型是前向加法,第t步的模型擬合上次預測的殘差,即:

\hat{y}_{i}^{t}=\hat{y}_{i}^{t-1}+f_{t}\left(x_{i}\right)

此時,目標函數可改寫爲:

O b j^{(t)}=\sum_{i=1}^{n} l\left(y_{i}, \hat{y}_{i}^{(t-1)}+f_{t}\left(x_{i}\right)\right)+\Omega\left(f_{t}\right)

以平方誤差爲例,將上式進行二階泰勒展開得:

O b j^{(t)} \simeq \sum_{i=1}^{n}\left[l\left(y_{i}, \hat{y}_{i}^{(t-1)}\right)+g_{i} f_{t}\left(x_{i}\right)+\frac{1}{2} h_{i} f_{t}^{2}\left(x_{i}\right)\right]+\Omega\left(f_{t}\right)

其中g_{i}=\partial_{\hat{y}^{(t-1)}}\left(\hat{y}^{(t-1)}-y_{i}\right)^{2}=2\left(\hat{y}^{(t-1)}-y_{i}\right) \quad  h_{i}=\partial_{\hat{y}^{(t-1)}}^{2}\left(y_{i}-\hat{y}^{(t-1)}\right)^{2}=2

目標函數第一項爲常數,對優化不影響,可以直接去掉,此時,目標函數簡化爲:

O b j^{(t)}=\sum_{i=1}^{n}\left[g_{i} f_{t}\left(x_{i}\right)+\frac{1}{2} h_{i} f_{t}^{2}\left(x_{i}\right)\right]+\Omega\left(f_{t}\right)

我們知道,每個樣本都最終會落到一個葉子結點中,所以可將f_{t}\left(x_{i}\right)替換爲其落入的葉子節點的分數w,將樹的正則化一併整理到式中得到:

O b j^{(t)}=\sum_{j=1}^{T}\left[\left(\sum_{i \in I_{j}} g_{i}\right) w_{j}+\frac{1}{2}\left(\sum_{i \in I_{j}} h_{i}+\lambda\right) w_{j}^{2}\right]+\gamma T

此時目標函數是關於葉子結點分數w的一個一元二次函數,求解最優的w和和其對應的最優目標函數值如下:

w_{j}^{*}=-\frac{G_{j}}{H_{j}+\lambda}    O b j=-\frac{1}{2} \sum_{j=1}^{T} \frac{G_{j}^{2}}{H_{j}+\lambda}+\gamma T

根據下圖舉例說明葉子節點的分數w如何計算。每個葉子節點的G和H將落入其中的樣本求和,再根據上文給出的公式求得分數。

在上面的推導中,我們知道了如果我們一棵樹的結構確定了,如何求得每個葉子結點的分數。 下面介紹如何確定樹的結構。

XGBoost使用了和CART迴歸樹一樣的想法,可利用貪婪算法,遍歷所有特徵的內部的所有劃分點,不同的是使用上式目標函數值作爲評價函數。可利用近似算法,對於連續型特徵,根據分位數對特徵進行分桶,即找到m個劃分點,可以在生成一顆樹之前對特徵做好劃分(全局),也可以在分裂葉子節點時計算劃分(局部)。根據目標函數值計算節點分裂的增益,使用增益最大的特徵及劃分點進行分裂。同時爲了限制樹生長過深,加入閾值(gamma)限制,只有當增益大於該閾值才進行分裂。當特徵值缺失時,XGBoost的思想是將該樣本分別劃分到左結點和右結點,然後計算其增益,哪個大就劃分到哪邊。

此外,XGBoost使用了Shrinkage方法,相當於學習速率(learning_rate,eta)。XGBoost 在進行完一次迭代後,會將葉子節點的權重乘上該係數,主要是爲了削弱每棵樹的影響,讓後面有更大的學習空間;

\hat{y}_{i}^{t}=\hat{y}_{i}^{t-1}+\eta f_{t}\left(x_{i}\right) 

XGBoost基模型爲迴歸樹,是如何適用於分類問題的呢?它將每個樣本計算得到的分數經過一個變化轉化爲該樣本屬於該類的概率,那麼每輪擬合的殘差即爲輸入樣本對應某類的真實概率(1或0)- 前面k-1棵樹組合計算的對應該類的概率值。對於多分類問題,爲每個類別分別建樹,也就是說,對於一個k分類問題,總共建樹數量爲k*n_estimator。

XGBoost是如何並行訓練的?XGBoost的並行,並不是說每棵樹可以並行訓練,XGB本質上仍然採用boosting思想,每棵樹訓練前需要等前面的樹訓練完成才能開始訓練。XGBoost的並行,指的是特徵維度的並行:決策樹的學習最耗時的一個步驟就是在每次尋找最佳分裂點是都需要對特徵的值進行排序。而 XGBoost 在訓練之前對根據特徵對數據進行了排序,然後保存到塊結構中。在對節點進行分裂時需要選擇增益最大的特徵作爲分裂,這時各個特徵的增益計算可以同時進行。

XGBoost使用特徵重要性進行特徵選擇

使用XGBoost訓練模型可以自動地獲取特徵的重要性,從而有效地進行特徵的篩選。特徵的重要性表示這個特徵在構建提升樹的作用。去除一些不重要的特徵可能會使模型準確率提高,因爲這些特徵可能是噪聲。

xgboost內置了get_fscore函數獲取特徵重要性,API如下:

get_score(fmap=''importance_type='weight')

獲取每個特徵的特徵重要性,有以下幾種度量方式:

  • ‘weight’: 特徵作爲分裂節點的次數

  • ‘total_gain’: 某特徵在每次分裂節點時帶來的總增益

  • ‘total_cover’: 某特徵在每次分裂節點時處理的樣本的數量之和

  • ‘gain’: total_gain/weight

  • ‘cover’: total_cover/weight

此外,通過XGBoost model參數也可指定特徵重要性度量方式(importance_type:默認爲'gain'),通過model屬性feature_impoerance_獲得特徵重要性。通過對特徵重要性進行排序,逐個剔除不重要的特徵,查看剔除特徵後模型效果的提升情況來選擇留下哪些特徵。本文以多分類問題爲例,給出使用交叉驗證進行特徵選擇的代碼。

import numpy as np 
import pandas as pd 

import xgboost as xgb
from xgboost.sklearn import XGBClassifier
from sklearn.model_selection import GridSearchCV
from sklearn.metrics import cohen_kappa_score, make_scorer
from sklearn.feature_selection import SelectFromModel

new_train = pd.read_csv('/kaggle/input/dataset/train_feature.csv')

train_x = new_train.drop(['accuracy_group'],axis=1).values
train_y = new_train['accuracy_group'].values

model_xgb = XGBClassifier(
        learning_rate =0.1,  # 學習率
        n_estimators=1000,   # 樹的個數
        max_depth=5,         # 樹的最大深度
        min_child_weight=1,  # 葉子節點樣本權重加和最小值sum(H)
        gamma=0,             # 節點分裂所需的最小損失函數下降值
        subsample=0.8,       # 樣本隨機採樣作爲訓練集的比例
        colsample_bytree=0.8, # 使用特徵比例
        objective= 'multi:softmax', # 損失函數(這裏爲多分類)
        num_class=4,         # 多分類問題類別數
        scale_pos_weight=1,  # 類別樣本不平衡
        seed=1)

# 使用默認參數訓練一個模型以得到特徵重要性
model_xgb.fit(train_x,train_y)

thresholds = np.sort(model_xgb.feature_importances_)
xgb_param = model_xgb.get_xgb_params()
for thresh in thresholds:
    # select features using threshold
    selection = SelectFromModel(model_xgb, threshold=thresh, prefit=True)
    select_train_x = selection.transform(train_x)
    # 5折交叉驗證
    xgtrain = xgb.DMatrix(select_train_x, label=train_y)
    cvresult = xgb.cv(xgb_param,xgtrain,num_boost_round=xgb_param['n_estimators'],             
                       nfold=5,metrics='mlogloss', early_stopping_rounds=50)
    score = cvresult['test-mlogloss-mean'].iloc[-1]
    print("Thresh=%.3f, n=%d, Accuracy: %.2f%%" % (thresh, select_train_x.shape[1], score))

XGBoost使用K折交叉驗證 + 網格搜索(Grid Search)調參

本文參考XGBoost參數調優完全指南(附Python代碼),使用grid search對模型參數分組調優。由於XGBoost的參數過多,所有參數一起調網格搜索空間太大,因此將參數分成幾組分別調優。關於XGBoost的參數在鏈接文章中已經給出了詳細的描述,故本文不再贅述。只是修改了上文中的部分代碼以適應XGBoost版本更新帶來的變化,並將參數調節的幾個步驟整理在一起,實現自動調參。

# step1:初始使用一個較大的學習速率,其他參數使用初始估計值,確定當前最佳決策樹數量
xgb_param = model_xgb.get_xgb_params()
xgtrain = xgb.DMatrix(train_x, label=train_y)
cvresult = xgb.cv(xgb_param, xgtrain, num_boost_round=xgb_param['n_estimators'], nfold=5,
                metrics='mlogloss', early_stopping_rounds=50)
# 根據交叉驗證結果修改決策樹的個數
model_xgb.set_params(n_estimators=cvresult.shape[0])

# step2:max_depth 和 min_weight 粗調優
param_test = {
 'max_depth':range(3,10,2),
 'min_child_weight':range(1,8,2)
}
gsearch = GridSearchCV(estimator=model_xgb,param_grid=param_test,scoring='accuracy',n_jobs=-1,cv=5)
gsearch.fit(train_x,train_y)
print(gsearch.best_score_)
curr_max_depth = gsearch.best_params_['max_depth']
curr_min_child_weight = gsearch.best_params_['min_child_weight']

# step3: 根據上一步的結果對max_depth 和 min_weight 精調優 在step2最優值上下範圍各擴展1
param_test = {
 'max_depth':[curr_max_depth-1,curr_max_depth,curr_max_depth+1],
 'min_child_weight':[curr_min_child_weight-1,curr_min_child_weight,curr_min_child_weight+1]
}
gsearch = GridSearchCV(estimator=model_xgb,param_grid=param_test,scoring='accuracy',n_jobs=-1,cv=5)
gsearch.fit(train_x,train_y)
print(gsearch.best_score_)
model_xgb.set_params(max_depth=gsearch.best_params_['max_depth'])
model_xgb.set_params(min_child_weight=gsearch.best_params_['min_child_weight'])

# step4: gamma調優
param_test = {
 'gamma':[i/10.0 for i in range(0,5)]
}
gsearch = GridSearchCV(estimator=model_xgb,param_grid=param_test,scoring='accuracy',n_jobs=-1,cv=5)
gsearch.fit(train_x,train_y)
print(gsearch.best_score_)
model_xgb.set_params(gamma=gsearch.best_params_['gamma'])

# step5: 調整subsample 和 colsample_bytree 參數
param_test = {
 'subsample':[i/10.0 for i in range(6,10)],
 'colsample_bytree':[i/10.0 for i in range(6,10)]
}
gsearch = GridSearchCV(estimator=model_xgb,param_grid=param_test,scoring='accuracy',n_jobs=-1,cv=5)
gsearch.fit(train_x,train_y)
print(gsearch.best_score_)
model_xgb.set_params(subsample=gsearch.best_params_['subsample'])
model_xgb.set_params(colsample_bytree=gsearch.best_params_['colsample_bytree'])

# step6: 正則化參數以及其他未給出初始定義的參數調優,要注意加入這些參數後模型效果是否變好

# step7: 使用上述調優後的參數 降低學習率並調優增加決策樹個數
model_xgb.set_params(learning_rate=0.01)
cvresult = xgb.cv(model_xgb.get_xgb_params(), xgtrain, num_boost_round=1000, nfold=5,
                metrics='mlogloss', early_stopping_rounds=50)
# 根據交叉驗證結果修改決策樹的個數
model_xgb.set_params(n_estimators=cvresult.shape[0])

XGBoost原理部分參考內容:

【機器學習】決策樹(下)——XGBoost、LightGBM(非常詳細)

一文讀懂機器學習大殺器XGBoost原理

陳天奇 xgboost ppt    密碼:v3y6

LightGBM

LightGBM原理

LightGBM 由微軟提出,主要用於解決 GDBT 在海量數據中遇到的問題。GBDT在每一次迭代的時候,都需要遍歷整個訓練數據多次。如果把整個訓練數據裝進內存則會限制訓練數據的大小;如果不裝進內存,反覆地讀寫訓練數據又會消耗非常大的時間。尤其面對工業級海量的數據,普通的GBDT算法是不能滿足其需求的。LightGBM相對XGBoost 具有訓練速度快、內存佔用低的特點。爲了達到這兩點,LightGBM提出了以下幾點解決方案。包括1-2兩個大的優化和剩餘的一些小的優化。

  • 單邊梯度抽樣算法(Gradient-based One-Side Sampling, GOSS);
  • 互斥特徵捆綁算法(Exclusive Feature Bundling, EFB);
  • 直方圖算法;
  • 基於最大深度的 Leaf-wise 的垂直生長算法;
  • 類別特徵最優分割;

單邊梯度抽樣算法

GBDT 算法的梯度大小可以反應樣本的權重,梯度越小說明模型擬合的越好,單邊梯度抽樣算法(Gradient-based One-Side Sampling, GOSS)利用這一信息對樣本進行抽樣,減少了大量梯度小的樣本,在接下來的計算中只需關注梯度高的樣本,極大的減少了計算量。用梯度描述可能不夠直觀,我們知道boosting模型是前向加法,第t步的模型擬合上次預測的殘差,在GDBT中殘差其實是最小均方損失函數關於預測值的反向梯度,也就是說,預測值和實際值的殘差與損失函數的負梯度相同。我們可以近似的理解爲,在接下來的計算中更多關注預測值與實際值偏差較大的樣本。

GOSS算法保留了梯度大的樣本,將更多的注意力放在訓練不足的樣本上;而爲了不改變樣本的數據分佈,對梯度小的樣本進行隨機抽樣,並引入一個常數進行平衡。GOSS首先根據數據的梯度絕對值排序,選取a%個實例。然後在剩餘的數據中隨機採樣b%個實例。接着計算信息增益時爲採樣出的小梯度數據乘以(1-a)/b進行放大。

互斥特徵捆綁算法

這一算法用來減少特徵的數量。高維特徵往往是稀疏的,而且特徵間可能是相互排斥的(如兩個特徵不同時取非零值),如果兩個特徵並不完全互斥(如只有一部分情況下是不同時取非零值),可以用互斥率表示互斥程度。互斥特徵捆綁算法(Exclusive Feature Bundling, EFB)指出如果將一些特徵進行融合綁定,則可以降低特徵數量。

1. 哪些特徵可以一起綁定?

EFB 算法利用特徵和特徵間的關係構造一個加權無向圖,頂點是特徵,邊是兩個特徵間互斥程度;

根據節點的度進行降序排序,度越大,與其他特徵的衝突越大;

檢查排序之後的每個特徵,對他進行特徵綁定bundle或者建立新的綁定bundle使得操作之後的總體衝突最小。

2. 特徵綁定後,特徵值如何確定?

如何合併同一個bundle的特徵來降低訓練時間複雜度。關鍵在於原始特徵值可以從bundle中區分出來。假設 Bundle 中有兩個特徵值,A 取值爲 [0, 10]、B 取值爲 [0, 20],爲了保證特徵 A、B 的互斥性,我們可以給特徵 B 添加一個偏移量轉換爲 [10, 30],Bundle 後的特徵其取值爲 [0, 30],這樣便實現了特徵合併。

直方圖算法

直方圖算法的基本思想是將連續的特徵離散化爲 k 個離散特徵,同時構造一個寬度爲 k 的直方圖用於統計信息(含有 k 個 bin)。利用直方圖算法我們無需遍歷數據,只需要遍歷 k 個 bin 即可找到最佳分裂點。這個思想和XGBoost中分裂樹節點使用的近似算法類似,但 XGBoost在每一層都動態的重新構建直方圖,而LightGBM中對每個特徵都有一個直方圖,所以構建一次直方圖就夠了。

基於最大深度的 Leaf-wise 的垂直生長算法

在建樹的過程中有兩種策略:

  • Level-wise:基於層進行生長,直到達到停止條件;
  • Leaf-wise:每次分裂增益最大的葉子節點,直到達到停止條件。

XGBoost 採用 Level-wise 的增長策略,方便並行計算每一層的分裂節點,提高了訓練速度,但同時也因爲節點增益過小增加了很多不必要的分裂,增加了計算量;LightGBM 採用 Leaf-wise 的增長策略減少了計算量,配合最大深度的限制防止過擬合,由於每次都需要計算增益最大的節點,所以無法並行分裂。

類別特徵最優分割

大部分的機器學習算法都不能直接支持類別特徵(如XGBoost),一般都會對類別特徵進行編碼,然後再輸入到模型中。常見的處理類別特徵的方法爲 one-hot 編碼。這不僅降低了空間和時間的效率而且可能會影響決策樹學習。所以LightGBM優化了對類別特徵的支持,可以直接輸入類別特徵,不需要將其轉化爲數值特徵。

LightGBM使用K折交叉驗證 + 網格搜索(Grid Search)調參

LightGBM調參仍然使用與XGBoost一樣的思路,但LightGBM加入了一些參數,且有些參數不同名但代表同一個意義。另外LightGBM中加入了類別變量,訓練的時候可選擇。

model_lgb = lgb.LGBMClassifier(
            learning_rate=0.1,   # 學習率
            n_estimators=1000,    # 樹的個數
            max_depth=10,         # 樹的最大深度
            num_leaves=31,        # 葉子節點個數 'leaf-wise'
            min_split_gain=0,    # 節點分裂所需的最小損失函數下降值
            objective='multiclass', # 多分類
            metric='multiclass',  # 評價函數
            num_class=4,          # 多分類問題類別數
            subsample=0.8,        # 樣本隨機採樣作爲訓練集的比例
            colsample_bytree=0.8, # 使用特徵比例
            seed=1)

# lightgbm 5折交叉驗證 + 網格搜索(Grid Search)調參
def lgb_auto_para_tuning(model_lgb,train_x,train_y,categorical_feature):
    # step1:初始使用一個較大的學習速率,其他參數使用初始估計值,確定當前最佳決策樹數量
    lgb_param = model_lgb.get_params()
    data_train = lgb.Dataset(train_x,train_y,categorical_feature=categorical_feature)
    cvresult = lgb.cv(lgb_param, data_train, categorical_feature=categorical_feature,num_boost_round=xgb_param['n_estimators'], nfold=5,
                    metrics='mlogloss', early_stopping_rounds=50)
    # 根據交叉驗證結果修改決策樹的個數
    model_lgb.set_params(n_estimators=cvresult.shape[0])
    print(model_lgb.get_params()['n_estimators'])
    
    # 類別變量
    train_x[categorical_feature] = train_x[categorical_feature].astype('category')
    # step2:max_depth 和 num_leaves 粗調優
    param_test = {
     'max_depth':range(3,10,2),
     'num_leaves':range(31,128,32)
    }
    gsearch = GridSearchCV(estimator=model_lgb,param_grid=param_test,scoring='accuracy',n_jobs=-1,cv=5)
    gsearch.fit(train_x,train_y)
    print(gsearch.best_params_)     
    print(gsearch.best_score_)
    curr_max_depth = gsearch.best_params_['max_depth']
    curr_num_leaves = gsearch.best_params_['num_leaves']

    # step3: 根據上一步的結果對max_depth 和 num_leaves 精調優 在step2最優值上下範圍各擴展1
    param_test = {
     'max_depth':[curr_max_depth-1,curr_max_depth,curr_max_depth+1],
     'num_leaves':[curr_num_leaves-8,curr_num_leaves,curr_num_leaves+8]
    }
    gsearch = GridSearchCV(estimator=model_lgb,param_grid=param_test,scoring='accuracy',n_jobs=-1,cv=5)
    gsearch.fit(train_x,train_y)
    print(gsearch.best_params_)     
    print(gsearch.best_score_)
    model_xgb.set_params(max_depth=gsearch.best_params_['max_depth'])
    model_xgb.set_params(num_leaves=gsearch.best_params_['num_leaves'])

    # step4: min_split_gain調優
    param_test = {
     'min_split_gain':[i/10.0 for i in range(0,5)]
    }
    gsearch = GridSearchCV(estimator=model_lgb,param_grid=param_test,scoring='accuracy',n_jobs=-1,cv=5)
    gsearch.fit(train_x,train_y)
    print(gsearch.best_params_)     
    print(gsearch.best_score_)
    model_xgb.set_params(min_split_gain=gsearch.best_params_['min_split_gain'])

    # step5: 調整subsample 和 colsample_bytree 參數
    param_test = {
     'subsample':[i/10.0 for i in range(6,10)],
     'colsample_bytree':[i/10.0 for i in range(6,10)]
    }
    gsearch = GridSearchCV(estimator=model_lgb,param_grid=param_test,scoring='accuracy',n_jobs=-1,cv=5)
    gsearch.fit(train_x,train_y)
    print(gsearch.best_params_)     
    print(gsearch.best_score_)
    model_xgb.set_params(subsample=gsearch.best_params_['subsample'])
    model_xgb.set_params(colsample_bytree=gsearch.best_params_['colsample_bytree'])

    # step6: 正則化參數以及其他未給出初始定義的參數調優,要注意加入這些參數後模型效果是否變好

    # step7: 使用上述調優後的參數 降低學習率並調優增加決策樹個數
    model_lgb.set_params(learning_rate=0.01)
    cvresult = lgb.cv(model_lgb.get_xgb_params(), data_train,categorical_feature=categorical_feature,num_boost_round=1000, nfold=5,
                    metrics='mlogloss', early_stopping_rounds=50)
    # 根據交叉驗證結果修改決策樹的個數
    model_lgb.set_params(n_estimators=cvresult.shape[0])
    print(model_xgb.get_params()['n_estimators'])

 

LightGBM原理部分參考內容:

【機器學習】決策樹(下)——XGBoost、LightGBM(非常詳細)

『 論文閱讀』LightGBM原理-LightGBM: A Highly Efficient Gradient Boosting Decision Tree

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章