一、前言: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