称霸kaggle的XGBoost究竟是啥?

一、前言:kaggle神器XGBoost

相信入了机器学习这扇门的小伙伴们一定听过XGBoost这个名字,这个看起来朴实无华的boosting算法近年来可算是炙手可热,别的不说,但是大家所熟知的kaggle比赛来看,说XGBoost是“一统天下”都不为过。业界将其冠名“机器学习竞赛的胜利女神”,当然,相信很多小伙伴也看过很多文章称其为“超级女王”。那么问题来了,为啥是女的?(滑稽~)
XGBoost全称eXtreme Gradient Boosting,极限梯度提升算法,由陈天奇(一个致力于将算法的工程效益发挥到极致的大牛)设计。它强就强在既有超强的建模效果,又有非常快的运算速度,这一点是非常难得的,因为众所周知,建模能力越强的算法往往就越复杂,运算速度也会变慢,比如常常跑的我们崩溃的SVM和各种深度学习的神经网络。
本文依然采用重实战轻推理的观点来看一看XGBoost算法。采用的仍然是二分类的乳腺癌数据集。(XGBoost的回归是其一大特色,建议小伙伴去其他地方了解一下)

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

# 解决坐标轴刻度负号乱码
plt.rcParams['axes.unicode_minus'] = False
# 解决中文乱码问题
plt.rcParams['font.sans-serif'] = ['Simhei']

%matplotlib inline
# 读入癌症数据集
from sklearn import datasets
cancer=datasets.load_breast_cancer()
X=cancer.data
y=cancer.target

# 划分训练集和测试集
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,首先要来看另一个算法,GBDT。

二、梯度提升树GBDT

2.1 GBDT原理

GBDT,全称Gradient Boosting Decision Tree,译为“梯度提升树”,看名字也就知道了,XGBoost一定是和GBDT有关联的。其实XGBoost就是陈天奇基于GBDT写的。
当然,首先,GBDT本身也是一个boosting:每次给都前一棵树学习错误的样本一个更大的权重再放回,让这些错误样本下一次被抽中的可能更大,也就是让学错的样本多学几次。就像学生做习题一样,第一次从100到题里抽取70道题做,发现做错了10道,把这10道标上记号;过一段时间再回来做这本习题集,同样,也随便做70道,但是尽可能把上次做错的那10道再做一遍,这次发现其中5道做对了,5道仍然做错了,其他的60道里又错了3道,这次我们将做错的8道题再次标上记号···以此反复,我们复习这本练习题集很多遍,就能提高我们的解题能力了。
GBDT的基分类器是CART,集成方式为Gradient Boosting。



这是boosting算法的基本思想,而不同的构建下一个加强学习弱分类(在习题的比方中可能就是你如何确定下一次选那些题能更好地进行提升学习)的方法,就是不同boosting算法的区别。GBDT的构建方法就是梯度提升,这其实可以类比逻辑回归中的梯度下降。因为GBDT本身基分类器是CART回归树,所以很好理解GBDT的目标函数是最小化残差(真实值与预测值的差)。


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]                            # 预测1类的概率
y_pred=gbc.predict(X_test)                                       # 模型对测试集的预测结果
fpr_gbc,tpr_gbc,threshold_gbc=metrics.roc_curve(y_test,y_prob)   # 获取真阳率、伪阳率、阈值  
auc_gbc=metrics.auc(fpr_gbc,tpr_gbc)                             # AUC得分
score_gbc=metrics.accuracy_score(y_test,y_pred)                  # 模型准确率
print([score_gbc,auc_gbc])

准确率0.9650,AUC值为0.9982。
可以看一下模型的默认参数。

gbc

2.3 GBDT调参

官方:https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.GradientBoostingClassifier.html
网友:https://www.cnblogs.com/DjangoBlog/p/6201663.html

2.3.1 择优:n_estimators & learning_rate

我们第一步要调参的就是弱分类器个数和学习率。先分别来看看这两个参数。

# 改进学习曲线
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) #交叉验证模式
    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()

模型默认的参数n_estimators=100,可以看到,由于这个数据集样本少且较易分类,不需要建100棵树。

print('n_estimators={}时偏差最小,最佳得分为{}'.format(n_param[score.index(max(score))],max(score)))
print('n_estimators={}时方差最小,最小方差为{}'.format(n_param[var.index(min(var))],min(var)))
print('n_estimators={}时泛化误差最小,最小泛化误差为{}'.format(n_param[ge.index(min(ge))],min(ge)))

我们会发现,数据集样本过少,在测试集上的表现很不稳定,在这里,我们就选择默认参数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) #交叉验证模式
    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={}时偏差最小,最佳得分为{}'.format(eta_param[score.index(max(score))],max(score)))
print('learning_rate={}时方差最小,最小方差为{}'.format(eta_param[var.index(min(var))],min(var)))
print('learning_rate={}时泛化误差最小,最小方差为{}'.format(eta_param[ge.index(min(ge))],min(ge)))

learning_rate就选0.7。

2.3.2 剪枝:max_depth

max_depth是树类模型的剪枝神器。默认不限制树深度。我们可以在前面看到,乳腺癌数据集树深度max_depth=3。当然,我们这个数据集过于简单,没有调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外,其他的树剪枝的参数min_samples_split、min_samples_leaf、min_weight_fraction_leaf、max_leaf_nodes等等,大家都可以试着去调一调。
GBDT的参数调整这里不过多说,上面贴出了非常详细的参数,旨在让大家了解GBDT,接下来看XGBoost。

三、XGBoost

陈天奇大神觉得GBDT很好用,就想能不能提高其工程效率。XGBoost里为了性能优化,既提供了单机多线程并行加速,也支持多机分布式加速,这是其加冕为王的一个重要原因。
大家可以去下载陈大佬原版本论文阅读。当然,已经有不少网友都出了对论文的解读:
https://www.jianshu.com/p/5e6c5b616114
XGBoost在GBDT的基础上有了一些改进:
1、GBDT每次迭代拟合的是一阶梯度,而XGBoost最大的特点是对目标函数做了二阶泰勒展开,每片叶子里的一阶梯度记作g,二阶梯度记作h。
2、XGBoost作为boosting虽然在每棵树树的生成上是串行的,但在构建每棵树分裂节点的时候是支持并行的。
3、XGBoost的的目标函数很巧妙,不仅设置了误差项,还加入了模型复杂度作为一个对模型的惩罚项,可以理解为,前部分为偏差,后部分为方差,即XGBoost的目标函数希望平衡偏差和方差得到一个最优解:

3.1 默认参数下的XGBoost实战

import xgboost as xgb
from sklearn import metrics

# 读入训练数据集和测试集
dtrain=xgb.DMatrix(X_train,y_train)
dtest=xgb.DMatrix(X_test)

# 设置xgboost建模参数
params={'objective': 'binary:logistic','silent':True}

# 训练模型
bst=xgb.train(params,dtrain,num_boost_round=100)

# 输入预测为正类的概率值
y_prob=bst.predict(dtest)
# 设置阈值为0.5,得到测试集的预测结果
y_pred = (y_prob >= 0.5)*1
# 获取真阳率、伪阳率、阈值
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)                  # 模型准确率
print([score_xgb,auc_xgb])

得到默认参数下,模型准确率为95.91%,AUC值为0.9931。
这里有几点需要说明一下,XGBoost建模有两种方式,一种是xgboost提供的sklearn接口,它的使用方式就和调用sklearn建其他模型一样,比如我们下面的语句就是分布建一个xgboost的分类器和回归模型:

from xgboost import XGBClassifier
from xgboost import XGBRegressor

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

另一种方式呢,就是我们在上面建xgboost的方法,直接调用xgboost这个类,这种方式会运算更快,但是对于用惯了sklearn建模调包的人来说,会有些不习惯,这种方式需要先用xgb.DMatrix()的方式将训练集打包成类能够接受的格式,训练模型的方式不是先实例化再fit,而是直接用类里面的一个方法xgb.train()。另外,模型参数必须单独先写成一个params的字典。我们注意到,上边建模时设置了objective为binary:logistic,意思是使用二分类的目标函数,而silent设置为True意思是不要打印出建立没棵树的过程,这个参数默认是False,这里我们并不需要打印出过程。
我们这里使用xgboost自身这个类来演示主要是因为小白我在查阅各种资料的时候发现大多数的文给出的实例都是使用的sklearn接口的方式,因为这种方式对于用惯了sklearn的人来说会更加顺手,还有很重要的一点就是这种方式方便使用网格搜索进行调参。小白想演示一种不太常见到的用法,所以在默认建模时直接使用的xgboost类,但在后面调参过程中我会使用sklearn接口的方式。这两种建立xgboost模型的方法,它们的参数名字也会有所不同,默认参数也会有一点点的区别,大家在使用的时候要多加区分。
xgboost的模型相对来说会比较复杂一点,因此模型可调节优化的参数也会相对来说比价多,小白在这里只会给大家演示一下自己比较常用的一些参数调节。

3.2 第一步:max_depth 和 min_child_weight

一般来说,集成模型会先选定弱分类器个数n_estimators,但以我的经验,复杂一点的模型数据量一大跑起来会很慢,所以我一般并不会基于去增加n_estimators,而是先用一个不太大的n_estimators进行其他参数的调节,再增加树的棵数。这里,先默认n_estimators=100。
树的剪枝神器max_depth(默认为6)自不必多说,min_child_weight(默认为1)是xgboost独特的一个参数,对模型复杂度有比较大的影响,不知道大家还记得我们上面说过的xgboost是二阶泰勒展开式吗?min_child_weight这个参数就是用来控制叶子节点中二阶导h之和的最小值,这个值越小,模型越容易过拟合。

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('最优参数:{}'.format(grid.best_params_))
print('最佳得分:{:.3f}'.format(grid.score(X_test,y_test)))

3.3 第二步:gamma

这个参数也是xgboost特有的,要详细解释这个参数可能就会涉及到xgboost目标函数的数学求解过程,我们这里只需要理解,gamma这个值是控制树分支所需要的最小信息增益的,也就是说gamma设置了一个阈值,当每次分枝时gini值的减少若是小于gamma,则不再进行分枝了,这也是一个限制树的复杂度防止过拟合的参数。

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('最优参数:{}'.format(grid.best_params_))
print('最佳得分:{:.3f}'.format(grid.score(X_test,y_test)))

3.4 第三步:subsample & colsample_bytree

这两次参数都是控制采样比例的,subsample表示建立每棵树时选取多少比例的样本数据,colsample_bytree表示对特征的抽样。这些都是树模型常用的。

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('最优参数:{}'.format(grid.best_params_))
print('最佳得分:{:.3f}'.format(grid.score(X_test,y_test)))

可以看到,经过抽样,我们的分数又提升了一点。

3.2 第四步:n_estimators & learning_rate

最后,终于我们可以来调节树的课树以及学习率了。这个数据集比较简单,但在真正的实际案例中,我们都会增加n_estimators的个数以提升模型效果。learning_rate默认为0.3,在实际中我们都会让它更小一点,防止不收敛。我们之间已经看过对于乳腺癌数据集n_estimators=100已经完全足够了。这里,我们仍然使用n_estimators=100,来调节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('最优参数:{}'.format(grid.best_params_))
print('最佳得分:{:.3f}'.format(grid.score(X_test,y_test)))


以上就是小白习惯的xgboost调参四步走,当然,根据实际情况,还有非常多的可以调整的参数,等着小伙伴们自己去尝试。
最后,再提到一个算法,叫做lightgbm,在xgboost上又做了一些改进,感兴趣的小伙伴可以去自行了解一下:
https://www.cnblogs.com/mata123/p/7440774.html

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容