xgboost是各种比赛中最常使用的方法,网上介绍非常多,但是大部分看起来都比较费劲,这篇文章我将通俗的讲一下xgboost是在干什么,是怎么实现的,每一步的细节中要注意什么问题,达到理解—应用的程度,想了解具体理论的各位看官请移步其他文章。
1.xgboost原理
说起xgboost,我们不得不提一下GBDT,也就是梯度提升决策树,这是一种基于树的集成算法,至于什么方法构成了GBDT的决策树,无非就是ID3、C4.5、C5.0、CART,最常用的就是那个CART,多棵树的集合就构成了GBDT。其实GBDT是对残差的拟合,什么意思呢?假设目标函数是9,第一棵树预测为5,剩下4的误差,那么下一颗树继续拟合4,直至误差满足我们的要求,这和xgboost的思路是一样的。那么问题来了,有了GBDT为啥还要用xgboost呢,有一种说法是,GBDT的每一步优化都依赖于上一步的误差,当大数据量的时候就太慢了,xgboost通过改变目标函数来避免了这个问题。
GBDT的目标函数是预测值和真实值差的累加,也就是误差累加,可以看出每一步计算都依赖于上面所有步的误差,效率比较低。xgboost自然要做出改进,怎么优化呢,一步步分解来看。
形式上,在目标函数后面加一个惩罚项:
xgboost目标函数
目标函数简化,采用高数中的泰勒展开,回忆一下泰勒展开是什么:
泰勒展开模式
好了怎么应用到上面的目标函数中呢,先来确认f(x)是啥,这个f(x)是上面说的那个损失函数l(yi,),也就是我们只泰勒展开这个l(yi,)就行了,那么扎展开的时候我们需要知道这个
是什么,来看损失函数的形式,
是个常量我们上一步求的,可以当f(x)来看,其中x自然是变量
,求导也要对它求。f(xi)是这把我们求的,是导致函数值变化的原因,巧了这个
也表示变化的原因,那么他两是一个东西。
现在能展开了吧,请看下式:
损失函数的泰勒展开
这不还是坑人吗,怎么变得这么简洁的,其实吧gi和hi是下面这样的:
好了前面那个l,我们上一步不是求出来了吗,把它忽略掉吧,损失函数变成这样了:
损失函数的简化形式
到了这一步,目标函数已经和上一步的损失无关了,也就是xgboost摆脱了GBDT的缺点,可以并行计算了,核心做法是做了个泰勒展开,但你以为xgboost就这样了吗,那你就太年轻了,继续来看。
树复杂度表达式展开
怎么还有个Ω没给我解释呢,别急啊,来了,这个Ω是表示树的复杂度的,你想啊树的复杂度能怎么表示,首先你得考虑叶子节点个数吧,然后xgboost人家考虑的更多一些,还把叶子节点的得分给考虑进去了,并且为了防止叶子节点太多影响你计算,给你加了个L2正则化,啥不懂L2正则化,自己去百度吧。同时为了平衡这两个指标,人家还给加了两个权重,是不是很贴心。
树的复杂度的表示方法
等一下啊,道理我都明白,但是你能不能告诉我叶子节点得分是怎么算的。这个吧,其实对于单颗决策树是很好理解的,如果是分类问题那么这个数就是0-1之间的一个数,代表的是样本分到这个节点上的得分值,如果是回归问题,那么这个值就是样本的平均值。但是对于xgboost,我们都把决策树的目标给改了,这个东西你不得算出来啊,计算方法在后边说,就是一个最优目标函数对应一个最优得分值。
目标函数的整合,目标函数的两个部分都知道咋回事了吧,把它放到一起吧,这样的话我们才能看怎么去优化,合并以后的公式是这样子的:
整合以后的目标函数形式
这个w好像能合并到一起啊,合到一起试试。
进一步整合目标函数
看上去头晕眼花的,什么东西啊,能不能再简单点,当然能。先这样:
然后再这样:
目标函数的最终形式
目标函数的优化,都到这一步了可以优化了吧,这里边变量就剩叶子节点的得分了,我们来求导,得到下面这个东西:
目标函数的优化
等一下啊,这个w不就是前面说的叶子节点得分值,原来是这么算的,求这个其实就是为了求目标函数值。好了有了目标函数值,接下来干嘛。回头想想我们的目标,我们现在知道要拟合的残差,我们有特征,接下来我们要找一颗最优的树进行样本划分啊,咋找,决策树找最优划分点不是算增益最大吗,那么我们拿着这个目标函数去找最大的增益不就是现在这棵树的目标了吗。好了怎么算呢,看下面式子:
树结构划分标准
其实划分算法太多了,有很多是为了提高效率的,这个就交给包内部去解决吧,我们知道他是要划分树了就好了。
如此循环往复,一个xgboost过程就完成了,其实大概就是拿到值先找到一颗差不多的树,一看不行啊还有误差啊,拿着这个误差再去找一棵树继续拟合,等到拟合的差不多了,这些个树就集成到一起进行划分。那么xgboost是怎么预测的,其实一句话就可以理解,就是每棵树叶子节点得分的加和,想想你的指标在每棵树上都有满足条件的划分,如果划分到那一类就把叶子节点的得分加起来。其实xgboost还有缩减和列取样的问题,意思是防止过拟合,就是说每一步叶子节点的权重都要乘上一个系数,这样的话使每棵树叶子节点权重对结果影响不会太大,剩下更多的空间交给模型去拟合,还有一个列取样其实是为了计算简便,每次计算时从特征空间中选出几个来构建树。
2.python导入相关包
python简直太友好了,可以直接调用xgboost包跑程序,我们要做的就是提取指标和调参,提取指标这个事我教不了你,得根据业务来,调参倒是有套路可寻。首先你要实现一个xgboost,你要知道你用什么工具去实现,python里有两个包,一个就叫xgboost,另外一个是xgboost的sklearn接口,叫XGBClassifer。另外要调参还有一个并行的网格搜索包,sklearn里的GridSearchCV。其他还有一些包需要引入就是打打辅助。
import pandas as pd
import numpy as np
import xgboost as xgb
from xgboost.sklearn import XGBClassifier
from sklearn import cross_validation, metrics #交叉验证和效果评估,其他模型也很常用。
from sklearn.grid_search import GridSearchCV #并行搜索,加快速度。
3.xgboost调参
准备工作已经做好了,接下来开始调参工作了,调参之前大家记得要做两件件事,就是先把数据整理好,把模型搭建好。
3.1 搭建模型
简单的很,已经引入了XGBClassifier,直接把相关默认参数设置好就可以了。
clf1 = XGBClassifier(learning_rate =0.1,
n_estimators=1000,
max_depth=5,
min_child_weight=1,
gamma=0,
subsample=0.8,
colsample_bytree=0.8,
objective= 'binary:logistic',
nthread=4,
scale_pos_weight=1,
seed=27)
3.2 参数解释
我们看到在建立xgboost的模型时,有很多参数,这些参数是什么意思呢,我们来看一下。
一般参数
这些参数用来控制XGBoost的整体功能,是一些通用的设置,有的时候都不用调整。
(1)booster[默认gbtree]
每次迭代的模型选择,有两个gbtree:基于树的模型和gbliner:线性模型,显然我们一般都会选择gbtree。
(2)silent[默认0]
是否开启静默模式,0为不开启,1为开启,开启后不输出任何信息,显然这不利于我们调参,默认选0就好了。
(3)nthread[默认取最大线程数]
这个参数用来控制最大并行的线程数,如果你希望取得所有CPU的核,那么你就不用管它。
booster参数或者说树的参数
这些参数是要重点调整的,比较重要,主要是用来控制每一步树的生成。
(1)eta [default=0.3]
学习率参数,就是原理中说的缩减,保证每一颗树对于结果的影响不太大,从而保证模型的效果。更新叶子节点权重时,乘以该系数,避免步长过大。参数值越大,越可能无法收敛。把学习率 eta 设置的小一些,小学习率可以使得后面的学习更加仔细。 典型值为0.01-0.2。
(2)min_child_weight [default=1]
大家对他的解释是决定最小叶子节点样本权重和,不太好理解。看了一些解释的文章,这个值可以理解为H值,还记得H值吗,就是损失函数对y(t-1)的二阶导数和,那么如果损失函数是平方函数(回归问题),这个就是1,如果是对数损失函数(分类问题),导数是a(1-a)的形式,a代表sigmoid函数,这样的话当y预测值非常大的时候,这个式子的值接近于0,这当然是不好的,因此你要给他设定一个阈值,小于这个阈值就不分裂了。现在可以解释了,这个值代表所有样本二阶导数的和,和上边说的叶子得分不是一个事,如果是回归问题实际代表样本个数,如果是分类问题实际代表a(1-a)所有样本计算值的加和。
明白这个参数是啥以后,来看他是干嘛的,这个参数用于避免过拟合,当它的值较大时,可以避免模型学习到局部的特殊样本。举个栗子来说,对正负样本不均衡时的 0-1 分类而言,假设 h 在 0.01 附近,min_child_weight 为 1 意味着叶子节点中最少需要包含 100 个样本,实际是通过控制样本数来控制过拟合的。你们应该看出来这个值越小越容易过拟合,需要通过cv进行调整优化。
(3)max_depth [default=6]
这个没啥好说的,每棵树的最大深度,也是用来避免过拟合的,max_depth越大,模型会学到更具体更局部的样本,典型值3-10,要用cv调优。
(4)max_leaf_nodes
树上最大节点的数量,和上面的那个参数一样,如果定义了这个参数就会忽略掉max_depth参数,我们调优还是以max_depth为主吧。
(5)gamma[default=0]
一听这种希腊字母就知道是个系数,在树的叶子节点上作进一步分区所需的最小损失减少。越大,算法越保守。取值在[0,∞] 。通俗点讲就是,这个节点还划不划分,先看看损失减不减少了再说。同样需要cv调优。
(6)max_delta_step [default=0]
这参数限制每棵树权重改变的最大步长。如果这个参数的值为0,那就意味着没有约束。如果它被赋予了某个正值,那么它会让这个算法更加保守。通常,这个参数不需要设置。但是当各类别的样本十分不平衡时,它对逻辑回归是很有帮助的。也就是说这个参数不用管啊。
(7)subsample [default=1]
样本采样用的,减小这个参数的值,算法会更加保守,避免过拟合,但是如果这个值设置得过小,它可能会导致欠拟合。典型值:0.5-1。既然有个范围,给他个面子cv调优一把吧。
(8)colsample_bytree [default=1]
列采样,就是选择生成树的特征,前面介绍过了,和设置缩减率一样是为了干嘛来着,是为了防止过拟合的,一般设置为: 0.5-1 ,也要用cv拟合。
(9)colsample_bylevel[default=1]
等一下哈,这个怎么和上面参数这么像,哦,它是在上面树的基础上,对每一级进行分裂时对列(就是特征)进行采样,大神们都说这个参数不用用了,用上面那个就行了。
(10)lambda [default=1]
又是个系数,这个是控制L2正则的,就是目标函数里的那个叶子节点得分前边的系数,用不用看你自己了。
(11)alpha [default=0]
想必你也想到了吧,有L2就有L1,用不用全凭自己了。
(12) scale_pos_weight [default=1]
这个是控制样本均衡与否的,如果是不均衡样本,设置一个正数可以保证快速收敛,具体为什么,也没人解释,先留着吧。
(13)tree_method[default=’auto’]
还记得我说过树的生成有很多方法吧,他们介绍的老复杂了,别看了,人家自动给我们打包好了,有三个可选的值, {‘auto’, ‘exact’, ‘approx’} ,分别对应 贪心算法(小数据集)/近似算法(大数据集) 。
大概就这么多吧,如果看到了别的,我再补充。
学习目标参数
这个是最后一类参数了,跟目标函数有关。
(1)objective [ default=reg:linear ]
这是返回目标函数值,这个东西包含的函数还挺多,默认是线形的。此外你还可以选择:binary:logistic 二分类的逻辑回归,返回预测的概率(不是类别)。multi:softmax 使用softmax的多分类器,返回预测的类别(不是概率)。在这种情况下,你还需要多设一个参数:num_class(类别数目)。multi:softprob 和multi:softmax参数一样,但是返回的是每个数据属于各个类别的概率。
(2)eval_metric[默认值取决于objective参数的取值]
也就是说怎么计算目标函数值,根据你目标函数的形式来,对于回归问题,默认值是rmse,对于分类问题,默认值是error。比较典型的有(我直接截图了懒得打字):
损失函数计算方法
你应该知道吧目标函数值和实际值之间的差距,就是这个参数要计算的重点。
(3)seed(default=0)
这个叫随机数种子,还记得random包里那个seed吗,这个参数就是为了可以使结果复现。
命令行参数
n_estimators 这个参数叫迭代次数,也就是说生成树的个数。
更多参数
更多参数请移步官方文档:xgboost官方文档
知道了这些参数,想要弄明白调参是干啥,调整什么就很容易了,下面会结合例子来说明。
3.3 调参方法
先来推荐一个大哥的博客,这个链接里https://blog.csdn.net/u014465639/article/details/74351982写了好几种模型的调参方法,想要了解的不妨去看看。
开始之前,给大家介绍一个新朋友,这个东西可以很好的帮助我们进行调参,它是sklearn里的一个包,[sklearn.model_selection.GridSearchCV],我们调参就是基于这个包的。那么这个包怎么实现调参呢,来看一眼它的常用参数。
(1)estimator:优化器,也就是你建立的模型,这里自然就是xgboost模型,如果要对其他集成算法调优,那就写其他模型了,注意模型需要初始化哦。
(2)param_grid:字典或者列表,一般用字典,请在这里输入你要优化的参数值。
(3)scoring :用啥子估计误差,如果不设置,那我没办法了,就用estimator的误差来衡量吧。
有了上面的方法,我们还会怕调参数吗,当然不会了,先设置好常用的参数,然后再依次调参。请注意,调参是有顺序的,按照下面这个来。
1.n_estimators
叫迭代次数,也就是生成树的个数。
cv_params = {'n_estimators': [400, 500, 600, 700, 800]}
other_params = {'learning_rate': 0.1, 'n_estimators': 500, 'max_depth': 5, 'min_child_weight': 1, 'seed': 0,
'subsample': 0.8, 'colsample_bytree': 0.8, 'gamma': 0, 'reg_alpha': 0, 'reg_lambda': 1}
咦,这里边有n_estimators的值了,其实就是以他为基准进行挑选啊,首先给个基础值进去,给谁啊,当然是建立的初始模型了。
model = xgb.XGBRegressor(**other_params)
然后跑一下模型,这个我最后统一说,反正最后给出一个最优值,但是我们设置的粒度太粗了,不能直接用,接下来细调,同样方法跑一遍。
cv_params = {'n_estimators': [550, 575, 600, 650, 675]}
2.min_child_weight和max_depth
这两个参数是控制树生成的,树的结构对于最终的结果影响还是很大的,所以这个放到第二个调整批次是应当的,两个参数可以一起调。
cv_params = {'max_depth': [3, 4, 5, 6, 7, 8, 9, 10], 'min_child_weight': [1, 2, 3, 4, 5, 6]}
other_params = {'learning_rate': 0.1, 'n_estimators': 550, 'max_depth': 5, 'min_child_weight': 1,
'seed': 0,'subsample': 0.8, 'colsample_bytree': 0.8, 'gamma': 0, 'reg_alpha': 0, 'reg_lambda': 1}
3.gamma
控制节点分裂标准的。
cv_params = {'gamma': [0.1, 0.2, 0.3, 0.4, 0.5, 0.6]}
4.subsample和colsample_bytree
采样的放一起,都是比例。
cv_params = {'subsample': [0.6, 0.7, 0.8, 0.9], 'colsample_bytree': [0.6, 0.7, 0.8, 0.9]}
reg_alpha和reg_lambda
正则化的指标,这里有一点需要注意你看这个写法和上面原理好像不一样啊,其实就是直接XGB包和XGB包的sklearn接口的区别,一般都调用sklearn接口,所以就写成符合sklearn习惯的样子了。
cv_params = {'reg_alpha': [0.05, 0.1, 1, 2, 3], 'reg_lambda': [0.05, 0.1, 1, 2, 3]}
6.learning_rate
学习率,每一叶子节点得分需要乘上这个数,一般比较小,从小的数字调起。
cv_params = {'learning_rate': [0.01, 0.05, 0.07, 0.1, 0.2]}
基本调参就告一段落了,这里需要特别指出一点就是调参可以提高模型性能,但是更重要的还是特征选择,数据清洗,特征融合等工作,大家注意把基础工作做好。
4.完整代码
基础数据啥的我就不列了啊,大概说一下概要吧,和网上的不一样,我也是按照自己的想法做了一些省略。
#1.建立一个初步的模型,看一下效果怎么样。
xgb1 = XGBClassifier(
learning_rate =0.1,
n_estimators=1000,
max_depth=5,
min_child_weight=1,
gamma=0,
subsample=0.8,
colsample_bytree=0.8,
objective= 'binary:logistic',
nthread=4,
scale_pos_weight=1,
seed=27)#经验值
xgb_param = xgb1.get_xgb_params()#得到模型的参数
xgtrain = xgb.DMatrix(dtrain[predictors].values, label=dtrain[target].values)#转换成原生xgboost需要的数据格式。
cvresult = xgb.cv(xgb_param, xgtrain, num_boost_round=alg.get_params()['n_estimators'], nfold=cv_folds,
metrics='auc', early_stopping_rounds=early_stopping_rounds, show_progress=False)#注意啊这是原生的模型自带的,你只需传参,cv_folds=5,表示5%的数据用于交叉验证。这个cvresults返回训练集和测试集的误差,行数就是最大迭代的次数。
xgb1.set_params(n_estimators=cvresult.shape[0])
xgb1.fit(dtrain[predictors], dtrain['Disbursed'],eval_metric='auc')
dtrain_predictions = alg.predict(dtrain[predictors])#算准确率用的
dtrain_predprob = alg.predict_proba(dtrain[predictors])[:,1]#算auc用的
feat_imp = pd.Series(alg.booster().get_fscore()).sort_values(ascending=False)#算重要度的指标
#‘weight’ - the number of times a feature is used to split the data across all trees.‘gain’ - the average gain of the feature when it is used in trees.‘cover’ - the average coverage of the feature when it is used in trees.
#weight - 该特征在所有树中被用作分割样本的特征的次数。gain - 在所有树中的平均增益。cover - 在树中使用该特征时的平均覆盖范围。
#
#2开始按步骤调参用的XGBclassifer。
#第一步调优
params_test1 = {'n_estimators': [400, 500, 600, 700, 800]}
other_params = {'learning_rate': 0.1, 'n_estimators': 500, 'max_depth': 5, 'min_child_weight': 1, 'seed': 27,
'subsample': 0.8, 'colsample_bytree': 0.8, 'gamma': 0, 'reg_alpha': 0, 'reg_lambda': 1}
model = XGBClassfoer(**other_params)
optimized_XGB 1= GridSearchCV(estimator=model, param_grid=cv_params, scoring='r2', cv=5, verbose=1, n_jobs=4)
optimized_XGB.fit(X_train, y_train)
evalute_result = optimized_GBM.grid_scores_
##第二步调优
param_test2 = { 'max_depth':range(3,10,2), 'min_child_weight':range(1,6,2)}
optimized_XGB2= GridSearchCV(estimator = XGBClassifier( learning_rate =0.1, n_estimators=140, max_depth=5,
min_child_weight=1, gamma=0, subsample=0.8, colsample_bytree=0.8,
objective= 'binary:logistic', nthread=4, scale_pos_weight=1, seed=27), param_grid = param_test2, scoring='roc_auc',n_jobs=4,iid=False, cv=5)
optimized_XGB2.fit(train[predictors],train[target])
optimized_XGB2.grid_scores_, optimized_XGB2.best_params_, optimized_XGB2.best_score_
#依次类推,得到最后的最优参数集合,再建立模型用于预测
#3.最终模型
model = XGBClassifer(learning_rate=0.1, n_estimators=550, max_depth=4, min_child_weight=5, seed=27,
subsample=0.7, colsample_bytree=0.7, gamma=0.1, reg_alpha=1, reg_lambda=1)
model.fit(X_train, y_train)
ans = model.predict(X_test)
以上就是本篇的所有内容,很多东西就是讲思路,让大家都知道怎么回事,准确性可能差点,如果你想有更深的理解,还是结合其他人的文章看看。