其中最核心的,是DMtarix这个读取数据的类,以及train()这个用于训练的类。与sklearn把所有的参数都写在类中的方式不同,xgboost库中必须先使用字典设定参数集,再使用train来将参数及输入,然后进行训练。会这样设计的原因,是因为XGB所涉及到的参数实在太多,全部写在xgb.train()中太长也容易出错。在这里,我为大家准备了
params可能的取值以及xgboost.train的列表,给大家一个印象。
params {eta, gamma, max_depth, min_child_weight, max_delta_step, subsample, colsample_bytree,
colsample_bylevel, colsample_bynode, lambda, alpha, tree_method string, sketch_eps,
scale_pos_weight, updater,refresh_leaf, process_type, grow_policy, max_leaves,
max_bin, predictor, num_parallel_tree}
xgboost.train (params, dtrain, num_boost_round=10, evals=(), obj=None, feval=None,
maximize=False,early_stopping_rounds=None, evals_result=None, verbose_eval=True,
xgb_model=None, callbacks=None,learning_rates=None)
我们也可以选择第二种方法,使用xgboost库中的sklearn的API。这是说,我们可以调用如下的类,并用我们sklearn当中惯例的实例化,fit和predict的流程来运行XGB,并且也可以调用属性比如coef_等等。当然,这是我们回归的类,我们也有用于分类,用于排序的类。他们与回归的类非常相似,因此了解一个类即可。
class xgboost.XGBRegressor (max_depth=3, learning_rate=0.1, n_estimators=100, silent=True,
objective='reg:linear', booster='gbtree', n_jobs=1, nthread=None, gamma=0, min_child_weight=1,
max_delta_step=0,subsample=1, colsample_bytree=1, colsample_bylevel=1, reg_alpha=0,
reg_lambda=1, scale_pos_weight=1,base_score=0.5, random_state=0, seed=None,
missing=None, importance_type='gain', **kwargs)
调用xgboost.train和调用sklearnAPI中的类XGBRegressor,需要输入的参数是不同的,而且看起来相当的不同。但其实,这些参数只是写法不同,功能是相同的。比如说,我们的params字典中的第一个参数eta,其实就是我们XGBRegressor里面的参数learning_rate,他们的含义和实现的功能是一模一样的。只不过在sklearnAPI中,开发团队友好地帮助我们将参数的名称调节成了与sklearn中其他的算法类更相似的样子。所以对我们来说,使用xgboost中设定的建模流程来建模,和使用sklearnAPI中的类来建模,模型效果是比较相似的,但是xgboost库本身的运算速度(尤其是交叉验证)以及调参手段比sklearn要简单。
class xgboost.XGBRegressor (max_depth=3, learning_rate=0.1, n_estimators=100, silent=True,
objective='reg:linear', booster='gbtree', n_jobs=1, nthread=None, gamma=0, min_child_weight=1,
max_delta_step=0,subsample=1, colsample_bytree=1, colsample_bylevel=1, reg_alpha=0,
reg_lambda=1, scale_pos_weight=1,base_score=0.5, random_state=0, seed=None, missing=None,
importance_type='gain', **kwargs)
梯度提升(Gradient boosting)是构建预测模型的最强大技术之一,它是集成算法中提升法(Boosting)的代表算法。集成算法通过在数据上构建多个弱评估器,汇总所有弱评估器的建模结果,以获取比单个模型更好的回归或分类表现。弱评估器被定为是表现至少比随机猜测更好的模型,即预测准确率不低于50%的任意模型。
集成不同弱评估器的方法有很多种。有像我们曾经在随机森林的课中介绍的,一次性建立多个平行独立的弱评估器的装袋法。也有像我们今天要介绍的提升法这样,逐一构建弱评估器,经过多次迭代逐渐累积多个弱评估器的方法。提升法的中最著名的算法包括Adaboost和梯度提升树,XGBoost就是由梯度提升树发展而来的。梯度提升树中可以有回归树也可以有分类树,两者都以CART树算法作为主流,XGBoost背后也是CART树,这意味着XGBoost中所有的树都是二叉的
XGB vs GBDT 核心区别1:求解预测值 的方式不同:
GBDT中预测值是由所有弱分类器上的预测结果的加权求和,其中每个样本上的预测结果就是样本所在的叶子节点的均值。
而XGBT中的预测值是所有弱分类器上的叶子权重直接求和得到,计算叶子权重是一个复杂的过程。
参数含义 | xgb.train() | xgb.XGBRegressor() |
---|---|---|
集成中弱评估器的数量 | num_round,默认10 | n_estimators,默认100 |
训练中是否打印每次训练的结果 | slient,默认False | slient,默认True |
n_estimators参数对模型的影响:
- 首先,XGB中的树的数量决定了模型的学习能力,树的数量越多,模型的学习能力越强。只要XGB中树的数量足够了,即便只有很少的数据,模型也能够学到训练数据100%的信息,所以XGB也是天生过拟合的模型。但在这种情况下,模型会变得非常不稳定。
- 第二,XGB中树的数量很少的时候,对模型的影响较大,当树的数量已经很多的时候,对模型的影响比较小,只能有微弱的变化。当数据本身就处于过拟合的时候,再使用过多的树能达到的效果甚微,反而浪费计算资源。当唯一指标 R 2 R^{2} R2或者准确率给出的n_estimators看起来不太可靠的时候,我们可以改造学习曲线来帮助我们。
- 第三,树的数量提升对模型的影响有极限,最开始,模型的表现会随着XGB的树的数量一起提升,但到达某个点之后,树的数量越多,模型的效果会逐步下降,这也说明了暴力增加n_estimators不一定有效果。
这些都和随机森林中的参数n_estimators表现出一致的状态。在随机森林中我们总是先调整n_estimators,当n_estimators的极限已达到,我们才考虑其他参数,但XGB中的状况明显更加复杂,当数据集不太寻常的时候会更加复杂。这是我们要给出的第一个超参数,因此还是建议优先调整n_estimators,一般都不会建议一个太大的数目,300以下为佳。
subsample随机抽样的时候抽取的样本比例,数据量过大时,考虑使用
参数含义 | xgb.train() | xgb.XGBRegressor() |
---|---|---|
随机抽样的时候抽取的样本比例,范围(0,1] | subsample,默认1 | subsample,默认1 |
在逻辑回归中,我们自定义步长 α \alpha α 来干涉我们的迭代速率,在XGB中看起来却没有这样的设置,但其实不然。在XGB中,我们完整的迭代决策树的公式应该写作:
y ^ i k + 1 = y ^ i k + η f k + 1 ( x i ) \widehat{y}_{i}^{k+1}=\widehat{y}_{i}^{k}+\eta f_{k+1}\left( x_{i}\right) y ik+1=y ik+ηfk+1(xi)
其中 η \eta η 读作"eta",是迭代决策树时的步长(shrinkage),又叫做学习率(learning rate)。和逻辑回归中的 α \alpha α 类似, η \eta η 越大,迭代的速度越快,算法的极限很快被达到,有可能无法收敛到真正的最佳。 α \alpha α 越小,越有可能找到更精确的最佳值,更多的空间被留给了后面建立的树,但迭代速度会比较缓慢。
参数含义 | xgb.train() | xgb.XGBRegressor() |
---|---|---|
集成中的学习率,又称为步长 以控制迭代速率,常用于防止过拟合 eta | 默认0.3 取值范围[0,1] | 默认0.1 取值范围[0,1] |
结论:
通常, η \eta η (学习率)我们不调整 ,即便调整,一般它也会在[0.01,0.2]之间变动。如果我们希望模型的效果更好,更多的可能是从树本身的角度来说,对树进行剪枝,而不会寄希望于调整学习率
梯度提升算法中不只有梯度提升树,XGB作为梯度提升算法的进化,自然也不只有树模型一种弱评估器。在XGB中,除了树模型,我们还可以选用线性模型,比如线性回归,来进行集成。虽然主流的XGB依然是树模型,但我们也可以使用其他的模型。基于XGB的这种性质,我们有参数“booster"来控制我们究竟使用怎样的弱评估器。
xgb.train() & params | xgb.XGBRegressor() | |
---|---|---|
参数名称 | xgb_model | booster |
说明 | 使用哪种弱评估器。可以输入gbtree, gblinear或dart。输入的评估器不同,使用 的params参数也不同,每种评估器都有自 己的params列表。评估器必须于param参 数相匹配,否则报错。 | 使用哪种弱评估器。可以输入gbtree,gblinear或dart。 gbtree代表梯度提升树,dart是Dropouts meet Multiple Additive Regression Trees,可译为抛弃提升树,在建树的过 程中会抛弃一部分树,比梯度提升树有更好的防过拟合功能。 输入gblinear使用线性模型。 |
xgb.train() | xgb.XGBRegressor() | xgb.XGBClassifier() |
---|---|---|
参数obj:默认binary:logistic | objective:默认reg:linear | objective:默认binary:logistic |
常用的选择有:
输入 | 选用的损失函数 |
---|---|
reg:linear | 使用线性回归的损失函数,均方误差,回归时使用 |
binary:logistic | 使用逻辑回归的损失函数,对数损失log_loss,二分类时使用 |
binary:hinge | 使用支持向量机的损失函数,Hinge Loss,二分类时使用 |
multi:softmax | 使用softmax损失函数,多分类时使用 |
在xgboost中,我们被允许自定义损失函数,但通常我们还是使用类已经为我们设置好的损失函数。我们的回归类中本来使用的就是reg:linear,因此在这里无需做任何调整。注意:分类型的目标函数导入回归类中会直接报错。现在来试试看xgb自身的调用方式。
这个结构中有两部分内容,一部分是控制树结构的γT ,另一部分则是我们的正则项。叶子数量T 可以代表整个树结构,这是因为在XGBoost中所有的树都是CART树(二叉树),所以我们可以根据叶子的数量 判断出树的深度,而γ是我们自定的控制叶子数量的参数。
至于第二部分正则项,类比一下我们岭回归和Lasso的结构,参数 α \alpha α 和 λ \lambda λ的作用其实非常容易理解,他们都是控制正则化强度的参数,我们可以二选一使用,也可以一起使用加大正则化的力度。当 α \alpha α 和 λ \lambda λ都为0的时候,目标函数就是普通的梯度提升树的目标函数。
参数含义 | xgb.train() | xgb.XGBClassifier() |
---|---|---|
L1正则项的参数 α \alpha α | alpha,默认0,取值范围[0, +∞] | reg_alpha,默认0,取值范围[0, +∞] |
L2正则项的参数 γ \gamma γ | lambda,默认1,取值范围[0, +∞] | reg_lambda,默认1,取值范围[0, +∞] |
根据我们以往的经验,我们往往认为两种正则化达到的效果是相似的,只不过细节不同。比如在逻辑回归当中,两种正则化都会压缩 θ \theta θ参数的大小,只不过L1正则化会让 θ \theta θ 为0,而L2正则化不会。在XGB中也是如此。当 α \alpha α 和 λ \lambda λ越大,惩罚越重,正则项所占的比例就越大,在尽全力最小化目标函数的最优化方向下,叶子节点数量就会被压制,模型的复杂度就越来越低,所以对于天生过拟合的XGB来说,正则化可以一定程度上提升模型效果。
对于两种正则化如何选择的问题,从XGB的默认参数来看,我们优先选择的是L2正则化。当然,如果想尝试L1也不是不可。两种正则项还可以交互,因此这两个参数的使用其实比较复杂。在实际应用中,正则化参数往往不是我们调参的最优选择,如果真的希望控制模型复杂度,我们会调整 γ \gamma γ
而不是调整这两个正则化参数,因此大家不必过于在意这两个参数最终如何影响了我们的模型效果。对于树模型来说,还是剪枝参数地位更高更优先。大家只需要理解这两个参数从数学层面上如何影响我们的模型就足够了。如果我们希望调整 α \alpha α
和 λ \lambda λ ,我们往往会使用网格搜索来帮助我们。
目标函数:
比起最初的损失函数 + 复杂度的样子,我们的目标函数已经发生了巨大变化。我们的样本量 已经被归结到了每个叶子当中去,我们的目标函数是基于每个叶子节点,也就是树的结构来计算。所以,我们的目标函数又叫做“结构分数”(structure score),分数越低,树整体的结构越好。如此,我们就建立了树的结构(叶子)和模型效果的直接联系。
贪婪算法指的是控制局部最优来达到全局最优的算法,决策树算法本身就是一种使用贪婪算法的方法。XGB作为树的集成模型,自然也想到采用这样的方法来进行计算,所以我们认为,如果每片叶子都是最优,则整体生成的树结构就是最优,如此就可以避免去枚举所有可能的树结构。
回忆一下决策树中我们是如何进行计算:我们使用基尼系数或信息熵来衡量分枝之后叶子节点的不纯度,分枝前的信息熵与分治后的信息熵之差叫做信息增益,信息增益最大的特征上的分枝就被我们选中,当信息增益低于某个阈值时,就让树停止生长。在XGB中,我们使用的方式是类似的:我们首先使用目标函数来衡量树的结构的优劣,然后让树从深度0开始生长,每进行一次分枝,我们就计算目标函数减少了多少,当目标函数的降低低于我们设定的某个阈值时,就让树停止生长
分枝后的结构分数之差为:
在之前所有的推导过程中,我们都没有提到 γ \gamma γ 这个变量。从目标函数和结构分数之差Gain的式子中来看, γ \gamma γ是我们每增加一片叶子就会被剪去的惩罚项。增加的叶子越多,结构分数之差Gain会被惩罚越重,所以 γ \gamma γ又被称之为是“复杂性控制”(complexity control),所以 γ \gamma γ是我们用来防止过拟合的重要参数。实践证明, γ \gamma γ是对梯度提升树影响最大的参数之一,其效果丝毫不逊色于n_estimators和防止过拟合的神器max_depth。同时, γ \gamma γ 还是我们树停止生长的重要参数。
参数含义 | xgb.train() | xgb.XGBClassifier() |
---|---|---|
L1正则项的参数 α \alpha α | alpha,默认0,取值范围[0, +∞] | reg_alpha,默认0,取值范围[0, +∞] |
L2正则项的参数 γ \gamma γ | lambda,默认1,取值范围[0, +∞] | reg_lambda,默认1,取值范围[0, +∞] |
class xgboost.XGBRegressor (max_depth=3, learning_rate=0.1, n_estimators=100, silent=True,
objective='reg:linear', booster='gbtree', n_jobs=1, nthread=None, gamma=0, min_child_weight=1,
max_delta_step=0, subsample=1, colsample_bytree=1, colsample_bylevel=1, reg_alpha=0, reg_lambda=1,
scale_pos_weight=1, base_score=0.5, random_state=0, seed=None, missing=None, importance_type='gain', kwargs)
参数含义 | xgb.train() | xgb.XGBClassifier() |
---|---|---|
树的最大深度 | max_depth,默认6 | max_depth,默认6 |
每次生成树时随机抽样特征的比例 | colsample_bytree,默认1 | colsample_bytree,默认1 |
每次生成树的一层时 随机抽样特征的比例 | colsample_bylevel,默认1 | colsample_bylevel,默认1 |
每次生成一个叶子节点时,随机抽样特征的比例 | colsample_bynode,默认1 | N.A. |
一个叶子节点上所需要的最小hi,即叶子节点上的二阶导数之和 ,类似于样本权重 | min_child_weight, | 默认1 min_child_weight,默认1 |
这些参数中,树的最大深度是决策树中的剪枝法宝,算是最常用的剪枝参数,不过在XGBoost中,最大深度的功能与参数 γ \gamma γ相似,因此如果先调节了 γ \gamma γ,则最大深度可能无法展示出巨大的效果。当然,如果先调整了最大深度,则 γ \gamma γ也有可能无法显示明显的效果。通常来说,这两个参数中我们只使用一个,不过两个都试试也没有坏处。