稱霸kaggle的XGBoost究竟是啥?

一、前言:kaggle神器XGBoost

相信入了機(jī)器學(xué)習(xí)這扇門(mén)的小伙伴們一定聽(tīng)過(guò)XGBoost這個(gè)名字,這個(gè)看起來(lái)樸實(shí)無(wú)華的boosting算法近年來(lái)可算是炙手可熱,別的不說(shuō),但是大家所熟知的kaggle比賽來(lái)看,說(shuō)XGBoost是“一統(tǒng)天下”都不為過(guò)。業(yè)界將其冠名“機(jī)器學(xué)習(xí)競(jìng)賽的勝利女神”,當(dāng)然,相信很多小伙伴也看過(guò)很多文章稱其為“超級(jí)女王”。那么問(wèn)題來(lái)了,為啥是女的?(滑稽~)
XGBoost全稱eXtreme Gradient Boosting,極限梯度提升算法,由陳天奇(一個(gè)致力于將算法的工程效益發(fā)揮到極致的大牛)設(shè)計(jì)。它強(qiáng)就強(qiáng)在既有超強(qiáng)的建模效果,又有非常快的運(yùn)算速度,這一點(diǎn)是非常難得的,因?yàn)楸娝苤?,建模能力越?qiáng)的算法往往就越復(fù)雜,運(yùn)算速度也會(huì)變慢,比如常常跑的我們崩潰的SVM和各種深度學(xué)習(xí)的神經(jīng)網(wǎng)絡(luò)。
本文依然采用重實(shí)戰(zhàn)輕推理的觀點(diǎn)來(lái)看一看XGBoost算法。采用的仍然是二分類的乳腺癌數(shù)據(jù)集。(XGBoost的回歸是其一大特色,建議小伙伴去其他地方了解一下)

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

# 解決坐標(biāo)軸刻度負(fù)號(hào)亂碼
plt.rcParams['axes.unicode_minus'] = False
# 解決中文亂碼問(wèn)題
plt.rcParams['font.sans-serif'] = ['Simhei']

%matplotlib inline
# 讀入癌癥數(shù)據(jù)集
from sklearn import datasets
cancer=datasets.load_breast_cancer()
X=cancer.data
y=cancer.target

# 劃分訓(xùn)練集和測(cè)試集
from sklearn.model_selection import train_test_split
X_train,X_test,y_train,y_test=train_test_split(X,y,test_size-0.3,random_state=13)

要理解XGBoost,首先要來(lái)看另一個(gè)算法,GBDT。

二、梯度提升樹(shù)GBDT

2.1 GBDT原理

GBDT,全稱Gradient Boosting Decision Tree,譯為“梯度提升樹(shù)”,看名字也就知道了,XGBoost一定是和GBDT有關(guān)聯(lián)的。其實(shí)XGBoost就是陳天奇基于GBDT寫(xiě)的。
當(dāng)然,首先,GBDT本身也是一個(gè)boosting:每次給都前一棵樹(shù)學(xué)習(xí)錯(cuò)誤的樣本一個(gè)更大的權(quán)重再放回,讓這些錯(cuò)誤樣本下一次被抽中的可能更大,也就是讓學(xué)錯(cuò)的樣本多學(xué)幾次。就像學(xué)生做習(xí)題一樣,第一次從100到題里抽取70道題做,發(fā)現(xiàn)做錯(cuò)了10道,把這10道標(biāo)上記號(hào);過(guò)一段時(shí)間再回來(lái)做這本習(xí)題集,同樣,也隨便做70道,但是盡可能把上次做錯(cuò)的那10道再做一遍,這次發(fā)現(xiàn)其中5道做對(duì)了,5道仍然做錯(cuò)了,其他的60道里又錯(cuò)了3道,這次我們將做錯(cuò)的8道題再次標(biāo)上記號(hào)···以此反復(fù),我們復(fù)習(xí)這本練習(xí)題集很多遍,就能提高我們的解題能力了。
GBDT的基分類器是CART,集成方式為Gradient Boosting。



這是boosting算法的基本思想,而不同的構(gòu)建下一個(gè)加強(qiáng)學(xué)習(xí)弱分類(在習(xí)題的比方中可能就是你如何確定下一次選那些題能更好地進(jìn)行提升學(xué)習(xí))的方法,就是不同boosting算法的區(qū)別。GBDT的構(gòu)建方法就是梯度提升,這其實(shí)可以類比邏輯回歸中的梯度下降。因?yàn)镚BDT本身基分類器是CART回歸樹(shù),所以很好理解GBDT的目標(biāo)函數(shù)是最小化殘差(真實(shí)值與預(yù)測(cè)值的差)。


2.2 GBDT建模

from sklearn.ensemble import GradientBoostingClassifier as GBC
from sklearn import metrics 

# 建立GBDT模型
gbc = GBC().fit(X_train,y_train)

y_prob=gbc.predict_proba(X_test)[:,1]                            # 預(yù)測(cè)1類的概率
y_pred=gbc.predict(X_test)                                       # 模型對(duì)測(cè)試集的預(yù)測(cè)結(jié)果
fpr_gbc,tpr_gbc,threshold_gbc=metrics.roc_curve(y_test,y_prob)   # 獲取真陽(yáng)率、偽陽(yáng)率、閾值  
auc_gbc=metrics.auc(fpr_gbc,tpr_gbc)                             # AUC得分
score_gbc=metrics.accuracy_score(y_test,y_pred)                  # 模型準(zhǔn)確率
print([score_gbc,auc_gbc])

準(zhǔn)確率0.9650,AUC值為0.9982。
可以看一下模型的默認(rèn)參數(shù)。

gbc

2.3 GBDT調(diào)參

官方:https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.GradientBoostingClassifier.html
網(wǎng)友:https://www.cnblogs.com/DjangoBlog/p/6201663.html

2.3.1 擇優(yōu):n_estimators & learning_rate

我們第一步要調(diào)參的就是弱分類器個(gè)數(shù)和學(xué)習(xí)率。先分別來(lái)看看這兩個(gè)參數(shù)。

# 改進(jìn)學(xué)習(xí)曲線
n_param=range(1,101)
score=[]  # 記錄偏差
var=[]   # 記錄方差
ge=[]    # 記錄泛化誤差

for i in n_param:
    gbc=GBC(n_estimators=i)
    cv = KFold(n_splits=5, shuffle = True, random_state=13) #交叉驗(yàn)證模式
    CVresult=CVS(gbc,X_train,y_train,cv=cv)
    score.append(CVresult.mean())
    var.append(CVresult.var())
    ge.append((1-CVresult.mean())**2 + CVresult.var())

plt.figure(figsize=(20, 5))
plt.style.use('bmh')
plt.plot(range(1,101),ge,color='red',label='泛化誤差')
plt.legend(loc='upper right')
plt.show()

模型默認(rèn)的參數(shù)n_estimators=100,可以看到,由于這個(gè)數(shù)據(jù)集樣本少且較易分類,不需要建100棵樹(shù)。

print('n_estimators={}時(shí)偏差最小,最佳得分為{}'.format(n_param[score.index(max(score))],max(score)))
print('n_estimators={}時(shí)方差最小,最小方差為{}'.format(n_param[var.index(min(var))],min(var)))
print('n_estimators={}時(shí)泛化誤差最小,最小泛化誤差為{}'.format(n_param[ge.index(min(ge))],min(ge)))

我們會(huì)發(fā)現(xiàn),數(shù)據(jù)集樣本過(guò)少,在測(cè)試集上的表現(xiàn)很不穩(wěn)定,在這里,我們就選擇默認(rèn)參數(shù)n_estimators=100。

eta_param=np.arange(0.05,1,0.05)
score=[]  # 記錄偏差
var=[]   # 記錄方差
ge=[]    # 記錄泛化誤差

for i in eta_param:
    gbc=GBC(learning_rate=i)
    cv = KFold(n_splits=5, shuffle = True, random_state=13) #交叉驗(yàn)證模式
    CVresult=CVS(gbc,X_train,y_train,cv=cv)
    score.append(CVresult.mean())
    var.append(CVresult.var())
    ge.append((1-CVresult.mean())**2 + CVresult.var())
    
print('learning_rate={}時(shí)偏差最小,最佳得分為{}'.format(eta_param[score.index(max(score))],max(score)))
print('learning_rate={}時(shí)方差最小,最小方差為{}'.format(eta_param[var.index(min(var))],min(var)))
print('learning_rate={}時(shí)泛化誤差最小,最小方差為{}'.format(eta_param[ge.index(min(ge))],min(ge)))

learning_rate就選0.7。

2.3.2 剪枝:max_depth

max_depth是樹(shù)類模型的剪枝神器。默認(rèn)不限制樹(shù)深度。我們可以在前面看到,乳腺癌數(shù)據(jù)集樹(shù)深度max_depth=3。當(dāng)然,我們這個(gè)數(shù)據(jù)集過(guò)于簡(jiǎn)單,沒(méi)有調(diào)max_depth的必要。

depth_param=[1,2,3]
score=[]

for i in depth_param:
    gbc=GBC(max_depth=i,random_state=404).fit(X_train,y_train)
    score.append(metrics.accuracy_score(y_test,lr.predict(X_test)))

score

除了max_depth外,其他的樹(shù)剪枝的參數(shù)min_samples_split、min_samples_leaf、min_weight_fraction_leaf、max_leaf_nodes等等,大家都可以試著去調(diào)一調(diào)。
GBDT的參數(shù)調(diào)整這里不過(guò)多說(shuō),上面貼出了非常詳細(xì)的參數(shù),旨在讓大家了解GBDT,接下來(lái)看XGBoost。

三、XGBoost

陳天奇大神覺(jué)得GBDT很好用,就想能不能提高其工程效率。XGBoost里為了性能優(yōu)化,既提供了單機(jī)多線程并行加速,也支持多機(jī)分布式加速,這是其加冕為王的一個(gè)重要原因。
大家可以去下載陳大佬原版本論文閱讀。當(dāng)然,已經(jīng)有不少網(wǎng)友都出了對(duì)論文的解讀:
http://www.itdecent.cn/p/5e6c5b616114
XGBoost在GBDT的基礎(chǔ)上有了一些改進(jìn):
1、GBDT每次迭代擬合的是一階梯度,而XGBoost最大的特點(diǎn)是對(duì)目標(biāo)函數(shù)做了二階泰勒展開(kāi),每片葉子里的一階梯度記作g,二階梯度記作h。
2、XGBoost作為boosting雖然在每棵樹(shù)樹(shù)的生成上是串行的,但在構(gòu)建每棵樹(shù)分裂節(jié)點(diǎn)的時(shí)候是支持并行的。
3、XGBoost的的目標(biāo)函數(shù)很巧妙,不僅設(shè)置了誤差項(xiàng),還加入了模型復(fù)雜度作為一個(gè)對(duì)模型的懲罰項(xiàng),可以理解為,前部分為偏差,后部分為方差,即XGBoost的目標(biāo)函數(shù)希望平衡偏差和方差得到一個(gè)最優(yōu)解:

3.1 默認(rèn)參數(shù)下的XGBoost實(shí)戰(zhàn)

import xgboost as xgb
from sklearn import metrics

# 讀入訓(xùn)練數(shù)據(jù)集和測(cè)試集
dtrain=xgb.DMatrix(X_train,y_train)
dtest=xgb.DMatrix(X_test)

# 設(shè)置xgboost建模參數(shù)
params={'objective': 'binary:logistic','silent':True}

# 訓(xùn)練模型
bst=xgb.train(params,dtrain,num_boost_round=100)

# 輸入預(yù)測(cè)為正類的概率值
y_prob=bst.predict(dtest)
# 設(shè)置閾值為0.5,得到測(cè)試集的預(yù)測(cè)結(jié)果
y_pred = (y_prob >= 0.5)*1
# 獲取真陽(yáng)率、偽陽(yáng)率、閾值
fpr_xgb,tpr_xgb,threshold_xgb = metrics.roc_curve(y_test,y_prob)   
auc_xgb = metrics.auc(fpr_xgb,tpr_xgb)                             # AUC得分
score_xgb = metrics.accuracy_score(y_test,y_pred)                  # 模型準(zhǔn)確率
print([score_xgb,auc_xgb])

得到默認(rèn)參數(shù)下,模型準(zhǔn)確率為95.91%,AUC值為0.9931。
這里有幾點(diǎn)需要說(shuō)明一下,XGBoost建模有兩種方式,一種是xgboost提供的sklearn接口,它的使用方式就和調(diào)用sklearn建其他模型一樣,比如我們下面的語(yǔ)句就是分布建一個(gè)xgboost的分類器和回歸模型:

from xgboost import XGBClassifier
from xgboost import XGBRegressor

xgbc=XGBClassifier().fit(X_train,y_train)
xgbr=XGBRegressor().fit(X_train,y_train)

另一種方式呢,就是我們?cè)谏厦娼▁gboost的方法,直接調(diào)用xgboost這個(gè)類,這種方式會(huì)運(yùn)算更快,但是對(duì)于用慣了sklearn建模調(diào)包的人來(lái)說(shuō),會(huì)有些不習(xí)慣,這種方式需要先用xgb.DMatrix()的方式將訓(xùn)練集打包成類能夠接受的格式,訓(xùn)練模型的方式不是先實(shí)例化再fit,而是直接用類里面的一個(gè)方法xgb.train()。另外,模型參數(shù)必須單獨(dú)先寫(xiě)成一個(gè)params的字典。我們注意到,上邊建模時(shí)設(shè)置了objective為binary:logistic,意思是使用二分類的目標(biāo)函數(shù),而silent設(shè)置為T(mén)rue意思是不要打印出建立沒(méi)棵樹(shù)的過(guò)程,這個(gè)參數(shù)默認(rèn)是False,這里我們并不需要打印出過(guò)程。
我們這里使用xgboost自身這個(gè)類來(lái)演示主要是因?yàn)樾“孜以诓殚喐鞣N資料的時(shí)候發(fā)現(xiàn)大多數(shù)的文給出的實(shí)例都是使用的sklearn接口的方式,因?yàn)檫@種方式對(duì)于用慣了sklearn的人來(lái)說(shuō)會(huì)更加順手,還有很重要的一點(diǎn)就是這種方式方便使用網(wǎng)格搜索進(jìn)行調(diào)參。小白想演示一種不太常見(jiàn)到的用法,所以在默認(rèn)建模時(shí)直接使用的xgboost類,但在后面調(diào)參過(guò)程中我會(huì)使用sklearn接口的方式。這兩種建立xgboost模型的方法,它們的參數(shù)名字也會(huì)有所不同,默認(rèn)參數(shù)也會(huì)有一點(diǎn)點(diǎn)的區(qū)別,大家在使用的時(shí)候要多加區(qū)分。
xgboost的模型相對(duì)來(lái)說(shuō)會(huì)比較復(fù)雜一點(diǎn),因此模型可調(diào)節(jié)優(yōu)化的參數(shù)也會(huì)相對(duì)來(lái)說(shuō)比價(jià)多,小白在這里只會(huì)給大家演示一下自己比較常用的一些參數(shù)調(diào)節(jié)。

3.2 第一步:max_depth 和 min_child_weight

一般來(lái)說(shuō),集成模型會(huì)先選定弱分類器個(gè)數(shù)n_estimators,但以我的經(jīng)驗(yàn),復(fù)雜一點(diǎn)的模型數(shù)據(jù)量一大跑起來(lái)會(huì)很慢,所以我一般并不會(huì)基于去增加n_estimators,而是先用一個(gè)不太大的n_estimators進(jìn)行其他參數(shù)的調(diào)節(jié),再增加樹(shù)的棵數(shù)。這里,先默認(rèn)n_estimators=100。
樹(shù)的剪枝神器max_depth(默認(rèn)為6)自不必多說(shuō),min_child_weight(默認(rèn)為1)是xgboost獨(dú)特的一個(gè)參數(shù),對(duì)模型復(fù)雜度有比較大的影響,不知道大家還記得我們上面說(shuō)過(guò)的xgboost是二階泰勒展開(kāi)式嗎?min_child_weight這個(gè)參數(shù)就是用來(lái)控制葉子節(jié)點(diǎn)中二階導(dǎo)h之和的最小值,這個(gè)值越小,模型越容易過(guò)擬合。

from xgboost import XGBClassifier
from sklearn.model_selection import GridSearchCV

param_grid = {'max_depth': list(range(4,9)), 'min_child_weight': list((1,3,6))}

xgbc=XGBClassifier(n_estimators=100)
grid=GridSearchCV(xgbc,param_grid=param_grid, cv=5)
grid.fit(X_train, y_train)

print('最優(yōu)參數(shù):{}'.format(grid.best_params_))
print('最佳得分:{:.3f}'.format(grid.score(X_test,y_test)))

3.3 第二步:gamma

這個(gè)參數(shù)也是xgboost特有的,要詳細(xì)解釋這個(gè)參數(shù)可能就會(huì)涉及到xgboost目標(biāo)函數(shù)的數(shù)學(xué)求解過(guò)程,我們這里只需要理解,gamma這個(gè)值是控制樹(shù)分支所需要的最小信息增益的,也就是說(shuō)gamma設(shè)置了一個(gè)閾值,當(dāng)每次分枝時(shí)gini值的減少若是小于gamma,則不再進(jìn)行分枝了,這也是一個(gè)限制樹(shù)的復(fù)雜度防止過(guò)擬合的參數(shù)。

from xgboost import XGBClassifier
from sklearn.model_selection import GridSearchCV

param_grid = {'gamma': [ 0.1 * i for i in range(0,5)]}

xgbc=XGBClassifier(n_estimators=100,max_depth=5,min_child_weight=1)
grid=GridSearchCV(xgbc,param_grid=param_grid, cv=5)
grid.fit(X_train, y_train)

print('最優(yōu)參數(shù):{}'.format(grid.best_params_))
print('最佳得分:{:.3f}'.format(grid.score(X_test,y_test)))

3.4 第三步:subsample & colsample_bytree

這兩次參數(shù)都是控制采樣比例的,subsample表示建立每棵樹(shù)時(shí)選取多少比例的樣本數(shù)據(jù),colsample_bytree表示對(duì)特征的抽樣。這些都是樹(shù)模型常用的。

from xgboost import XGBClassifier
from sklearn.model_selection import GridSearchCV

param_grid = {'subsample':[ 0.1 * i for i in range(6,9)],
              'colsample_bytree':[ 0.1 * i for i in range(6,9)]}

xgbc=XGBClassifier(n_estimators=100
                   ,max_depth=5
                   ,min_child_weight=1
                   ,gamma=0)
grid=GridSearchCV(xgbc,param_grid=param_grid, cv=5)
grid.fit(X_train, y_train)

print('最優(yōu)參數(shù):{}'.format(grid.best_params_))
print('最佳得分:{:.3f}'.format(grid.score(X_test,y_test)))

可以看到,經(jīng)過(guò)抽樣,我們的分?jǐn)?shù)又提升了一點(diǎn)。

3.2 第四步:n_estimators & learning_rate

最后,終于我們可以來(lái)調(diào)節(jié)樹(shù)的課樹(shù)以及學(xué)習(xí)率了。這個(gè)數(shù)據(jù)集比較簡(jiǎn)單,但在真正的實(shí)際案例中,我們都會(huì)增加n_estimators的個(gè)數(shù)以提升模型效果。learning_rate默認(rèn)為0.3,在實(shí)際中我們都會(huì)讓它更小一點(diǎn),防止不收斂。我們之間已經(jīng)看過(guò)對(duì)于乳腺癌數(shù)據(jù)集n_estimators=100已經(jīng)完全足夠了。這里,我們?nèi)匀皇褂胣_estimators=100,來(lái)調(diào)節(jié)learning_rate。

from xgboost import XGBClassifier
from sklearn.model_selection import GridSearchCV

param_grid = {'eta':[0.5,0.4,0.3,0.2,0.1,0.075,0.05,0.04,0.03]}

xgbc=XGBClassifier(n_estimators=100
                   ,max_depth=5
                   ,min_child_weight=1
                   ,gamma=0
                   ,subsample=0.6
                   ,colsample_bytree=0.6)
grid=GridSearchCV(xgbc,param_grid=param_grid, cv=5)
grid.fit(X_train, y_train)

print('最優(yōu)參數(shù):{}'.format(grid.best_params_))
print('最佳得分:{:.3f}'.format(grid.score(X_test,y_test)))


以上就是小白習(xí)慣的xgboost調(diào)參四步走,當(dāng)然,根據(jù)實(shí)際情況,還有非常多的可以調(diào)整的參數(shù),等著小伙伴們自己去嘗試。
最后,再提到一個(gè)算法,叫做lightgbm,在xgboost上又做了一些改進(jìn),感興趣的小伙伴可以去自行了解一下:
https://www.cnblogs.com/mata123/p/7440774.html

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容