文章目录
- lecture17:XGBoost(eXtreme Gradient Boosting)
- 目录
- 1. XGBoost的基本信息
- 2. XGBoost与GBDT的异同点
- 3. XGBoost的原理
- 3.1定义树的复杂度
- 3.2 分裂节点
- 3.3 自定义损失函数
- 4. XGBoost的使用
lecture17:XGBoost(eXtreme Gradient Boosting)
目录
1. XGBoost的基本信息
全称:eXtreme Gradient Boosting
作者:程天奇(华盛顿大学博士)
基础:GBDT
所属:boosting 迭代算法、树类算法
应用类型:分类和回归
优点:速度快,效果好,可以并行计算处理大量数据,支持多种语言,支持自定义损失函数,正则化,高度灵活性,缺失值处理,剪枝,内置交叉验证,在已有模型基础上继续训练等等
缺点:发布时间短,工业领域应用较少,需要检验
参考博客
2. XGBoost与GBDT的异同点
相同点:
xgboost是在GBDT的基础上对boosting算法进行的改进,内部决策树使用的是回归树,简单回顾GBDT如下:
注意:
XGBoost和GBDT本质的思想都是一种残差学习方式,即当前的弱分类器的主要目标是学习上一个弱分类器的误差;最后,通过将所有的弱分类器集成在一起,组成一个混合模型,从而降低模型的方差和偏差。
不同点:
如果不考虑工程实现、解决问题上的一些差异,xgboost与gbdt比较大的不同就是目标函数的定义。如下图是XGBoost目标函数的定义:
注意:从目标函数的表达式可以看出,XGBoost的目标函数主要分为:加入一颗新树后模型的误差,正则化项和一个常数项;其中模型误差项,可以将加入的新树看成∆X,然后对L函数求泰勒展开,就得到最下面的公式了,从最下面的公式可以看出,目标函数中的损失项包括:上一颗树所在模型的损失,当前加入新树后模型的一阶导数和加入新树后的二阶导数。XGBoost利用泰勒展开三项,做一个近似,我们可以很清晰地看到,最终的目标函数只依赖于每个数据点的在误差函数上的一阶导数和二阶导数。正则化项包括对当前新加入的树叶子结点个数的约束(L1正则化项)和当前新加入树的叶子结点的权重的约束(L2正则化项)。
3. XGBoost的原理
对于上面给出的目标函数,我们可以进一步化简
3.1定义树的复杂度
对于f的定义做一下细化,把树拆分成结构部分q和叶子权重部分w。下图是一个具体的例子。结构函数q把输入映射到叶子的索引号上面去,而w给定了每个索引号对应的叶子分数是什么
定义这个复杂度包含了一棵树里面节点的个数,以及每个树叶子节点上面输出分数的L2模平方。
当然这不是唯一的一种定义方式,不过这一定义方式学习出的树效果一般都比较不错。下图还给出了复杂度计算的一个例子
在这种新的定义下,我们可以把目标函数进行如下改写,其中I被定义为每个叶子上面样本集合
g是一阶导数,h是二阶导数
注意:
在上面的公式中,由于误差项中的第一项是上一个树所在模型的误差,与当前新加入的树无关,所以为一个常数,与目标函数的优化(优化的目标是找到一颗最佳的树(f(t)),将误差降低)无关,可以省略,于是误差项变成了一阶导数和二阶导数之和;由于最终的样本都会划分到每个叶子节点上,所以可以将最开始的样本的遍历换成叶子结点的遍历,从而可以和正则化项中的L2项进行合并,得到最终的公式如上图。
3.2 分裂节点
论文中给出了两种分裂节点的方法
(1)贪心法
每一次尝试去对已有的叶子加入一个分割,且每次分割的目标就是使得损失函数最小
注意:
XGBoost和决策树的构造准则不同,它不在基于gini系数或者熵,而是通过比较划分前和划分后对模型的损失(之前推到的那个目标函数)的影响,来选出最佳的树节点的划分。
对于每次扩展,我们还是要枚举所有可能的分割方案,如何高效地枚举所有的分割呢?我假设我们要枚举所有x < a 这样的条件,对于某个特定的分割a我们要计算a左边和右边的导数和
我们可以发现对于所有的a,我们只要做一遍从左到右的扫描就可以枚举出所有分割的梯度和GL和GR。然后用上面的公式计算每个分割方案的分数就可以了。
观察这个目标函数,大家会发现第二个值得注意的事情就是引入分割不一定会使得情况变好,因为我们有一个引入新叶子的惩罚项。优化这个目标对应了树的剪枝, 当引入的分割带来的增益小于一个阀值的时候,我们可以剪掉这个分割。大家可以发现,当我们正式地推导目标的时候,像计算分数和剪枝这样的策略都会自然地出现,而不再是一种因为heuristic(启发式)而进行的操作了。
**下面是论文中的算法 **
(2)近似算法:
主要针对数据太大,不能直接进行计算
3.3 自定义损失函数
(1)常用损失函数
(2)一阶导数和二阶导数的推到:
4. XGBoost的使用
(1)官方代码
#!/usr/bin/python
import numpy as np
import xgboost as xgb
###
# advanced: customized loss function
#
print ('start running example to used customized objective function')dtrain = xgb.DMatrix('../data/agaricus.txt.train')
dtest = xgb.DMatrix('../data/agaricus.txt.test')# note: for customized objective function, we leave objective as default
# note: what we are getting is margin value in prediction
# you must know what you are doing
param = {'max_depth': 2, 'eta': 1, 'silent': 1}
watchlist = [(dtest, 'eval'), (dtrain, 'train')]
num_round = 2# user define objective function, given prediction, return gradient and second order gradient
# this is log likelihood loss
def logregobj(preds, dtrain):labels = dtrain.get_label()preds = 1.0 / (1.0 + np.exp(-preds))grad = preds - labelshess = preds * (1.0-preds)return grad, hess# user defined evaluation function, return a pair metric_name, result
# NOTE: when you do customized loss function, the default prediction value is margin
# this may make builtin evaluation metric not function properly
# for example, we are doing logistic loss, the prediction is score before logistic transformation
# the builtin evaluation error assumes input is after logistic transformation
# Take this in mind when you use the customization, and maybe you need write customized evaluation function
def evalerror(preds, dtrain):labels = dtrain.get_label()# return a pair metric_name, result# since preds are margin(before logistic transformation, cutoff at 0)return 'error', float(sum(labels != (preds > 0.0))) / len(labels)# training with customized objective, we can also do step by step training
# simply look at xgboost.py's implementation of train
bst = xgb.train(param, dtrain, num_round, watchlist, logregobj, evalerror)
(2)XGBoost调参
通用参数
这些参数用来控制XGBoost的宏观功能。
- booster[默认gbtree]
- 选择每次迭代的模型,有两种选择:gbtree:基于树的模型 ; gbliner:线性模型
- silent[默认0]
- 当这个参数值为1时,静默模式开启,不会输出任何信息。
- 一般这个参数就保持默认的0,因为这样能帮我们更好地理解模型
- nthread[默认值为最大可能的线程数]
- 这个参数用来进行多线程控制,应当输入系统的核数。
- 如果你希望使用CPU全部的核,那就不要输入这个参数,算法会自动检测它。
还有两个参数,XGBoost会自动设置,目前你不用管它。接下来咱们一起看booster参数
booster参数
尽管有两种booster可供选择,我这里只介绍tree booster,因为它的表现远远胜过linear booster,所以linear booster很少用到
- eta[默认0.3]
- 和GBM中的 learning rate 参数类似。
- 通过减少每一步的权重,可以提高模型的鲁棒性。
- 典型值为0.01-0.2。
- min_child_weight[默认1]
- 决定最小叶子节点样本权重和。
- 和GBM的 min_child_leaf 参数类似,但不完全一样。XGBoost的这个参数是最小样本权重的和,而GBM参数是最小样本总数。
- 这个参数用于避免过拟合。当它的值较大时,可以避免模型学习到局部的特殊样本。
- 但是如果这个值过高,会导致欠拟合。这个参数需要使用CV来调整。
- max_depth[默认6]
- 和GBM中的参数相同,这个值为树的最大深度
- 这个值也是用来避免过拟合的。max_depth越大,模型会学到更具体更局部的样本。
- 需要使用CV函数来进行调优。
- 典型值:3-10
- max_leaf_nodes
- 树上最大的节点或叶子的数量。
- 可以替代max_depth的作用。因为如果生成的是二叉树,一个深度为n的树最多生成n^2个叶子
- 如果定义了这个参数,GBM会忽略max_depth参数。
- gamma[默认0]
- 在节点分裂时,只有分裂后损失函数的值下降了,才会分裂这个节点。Gamma指定了节点分裂所需的最小损失函数下降值。
- 这个参数的值越大,算法越保守。这个参数的值和损失函数息息相关,所以是需要调整的。
- max_delta_step[默认0]
- 这参数限制每棵树权重改变的最大步长。如果这个参数的值为0,那就意味着没有约束。如果它被赋予了某个正值,那么它会让这个算法更加保守。
- 通常,这个参数不需要设置。但是当各类别的样本十分不平衡时,它对逻辑回归是很有帮助的。
- 这个参数一般用不到,但是你可以挖掘出来它更多的用处
- subsample[默认1]
- 和GBM中的subsample参数一模一样。这个参数控制对于每棵树,随机采样的比例。
- 减小这个参数的值,算法会更加保守,避免过拟合。但是,如果这个值设置得过小,它可能会导致欠拟合。
- 典型值:0.5-1
- 典型值:0.5-1
- 和GBM里面的max_features参数类似。用来控制每棵随机采样的列数的占比(每一列是一个特征)。
- 典型值:0.5-1
- colsample_bylevel[默认1]
- 用来控制树的每一级的每一次分裂,对列数的采样的占比。
- 我个人一般不太用这个参数,因为subsample参数和colsample_bytree参数可以起到相同的作用。但是如果感兴趣,可以挖掘这个参数更多的用处。
- lambda[默认1]
- 权重的L2正则化项。(和Ridge regression类似)。
- 这个参数是用来控制XGBoost的正则化部分的。虽然大部分数据科学家很少用到这个参数,但是这个参数在减少过拟合上还是可以挖掘出更多用处的
- alpha[默认1]
- 权重的L1正则化项。(和Lasso regression类似)。
- 可以应用在很高维度的情况下,使得算法的速度更快。
- scale_pos_weight[默认1]
- 在各类别样本十分不平衡时,把这个参数设定为一个正值,可以使算法更快收敛。
学习目标参数
这个参数用来控制理想的优化目标和每一步结果的度量方法。
- objective[默认reg:linear]
这个参数定义需要被最小化的损失函数。最常用的值有:- binary:logistic 二分类的逻辑回归,返回预测的概率(不是类别)。
- multi:softmax 使用softmax的多分类器,返回预测的类别(不是概率)。
- 在这种情况下,你还需要多设一个参数:num_class(类别数目)。
- multi:softprob 和multi:softmax参数一样,但是返回的是每个数据属于各个类别的概率。
- eval_metric[默认值取决于objective参数的取值]
- 对于有效数据的度量方法。
- 对于回归问题,默认值是rmse,对于分类问题,默认值是error。
- 典型值有:
- seed(默认0)
- 随机数的种子
- 设置它可以复现随机数据的结果,也可以用于调整参数
(3)Python中对XGBoost的使用
- 任务:二分类,存在样本不均衡问题(scale_pos_weight参数可以加快训练速度)
def xgboost_predict():import xgboost as xgb#xgboost start heredtest = xgb.DMatrix(test_x)dval = xgb.DMatrix(val_x , label = val_y)dtrain = xgb.DMatrix(x,label = y)params = {'booster':'gbtree','silent':1 ,#设置成1则没有运行信息输出,最好是设置为0.#'nthread':7,# cpu 线程数 默认最大'eta': 0.007, # 如同学习率'min_child_weight':3, # 这个参数默认是 1,是每个叶子里面 h 的和至少是多少,对正负样本不均衡时的 0-1 分类而言#,假设 h 在 0.01 附近,min_child_weight 为 1 意味着叶子节点中最少需要包含 100 个样本。#这个参数非常影响结果,控制叶子节点中二阶导的和的最小值,该参数值越小,越容易 overfitting。'max_depth':6, # 构建树的深度,越大越容易过拟合'gamma':0.1, # 树的叶子节点上作进一步分区所需的最小损失减少,越大越保守,一般0.1、0.2这样子。'subsample':0.7, # 随机采样训练样本'colsample_bytree':0.7, # 生成树时进行的列采样 'lambda':2, # 控制模型复杂度的权重值的L2正则化项参数,参数越大,模型越不容易过拟合。#'alpha':0, # L1 正则项参数#'scale_pos_weight':1, #如果取值大于0的话,在类别样本不平衡的情况下有助于快速收敛。#'objective': 'multi:softmax', #多分类的问题#'num_class':10, # 类别数,多分类与 multisoftmax 并用'seed':1000, #随机种子#'eval_metric': 'auc'}watchlist = [(dval,"val"),(dtrain,"train")] xgboost_model = xgb.train(params , dtrain , num_boost_round = 3000 , evals = watchlist)#xgboost_model.save_model("./xgboost.model")#predict the test setxgboost_predict_y = xgboost_model.predict(dtest , ntree_limit = xgboost_model.best_ntree_limit)
- DART:将dropput思想引入XGBoost
import xgboost as xgb
# read in data
dtrain = xgb.DMatrix('demo/data/agaricus.txt.train')
dtest = xgb.DMatrix('demo/data/agaricus.txt.test')
# specify parameters via map
param = {'booster': 'dart','max_depth': 5, 'learning_rate': 0.1,'objective': 'binary:logistic', 'silent': True,'sample_type': 'uniform','normalize_type': 'tree','rate_drop': 0.1,'skip_drop': 0.5}
num_round = 50
bst = xgb.train(param, dtrain, num_round)
# make prediction
# ntree_limit must not be 0
preds = bst.predict(dtest, ntree_limit=num_round)
- XGBoost在sklearn中的例子:
from sklearn.model_selection import train_test_split
from sklearn import metrics
from sklearn.datasets import make_hastie_10_2
import xgboost as xgb
#记录程序运行时间
import time start_time = time.time()
X, y = make_hastie_10_2(random_state=0)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.5, random_state=0)##test_size测试集合所占比例#xgb矩阵赋值
xgb_train = xgb.DMatrix(X_train, label=y_train)
xgb_test = xgb.DMatrix(X_test,label=y_test)##参数
params={
'booster':'gbtree',
'silent':1 ,#设置成1则没有运行信息输出,最好是设置为0.
#'nthread':7,# cpu 线程数 默认最大
'eta': 0.007, # 如同学习率
'min_child_weight':3,
# 这个参数默认是 1,是每个叶子里面 h 的和至少是多少,对正负样本不均衡时的 0-1 分类而言
#,假设 h 在 0.01 附近,min_child_weight 为 1 意味着叶子节点中最少需要包含 100 个样本。
#这个参数非常影响结果,控制叶子节点中二阶导的和的最小值,该参数值越小,越容易 overfitting。
'max_depth':6, # 构建树的深度,越大越容易过拟合
'gamma':0.1, # 树的叶子节点上作进一步分区所需的最小损失减少,越大越保守,一般0.1、0.2这样子。
'subsample':0.7, # 随机采样训练样本
'colsample_bytree':0.7, # 生成树时进行的列采样
'lambda':2, # 控制模型复杂度的权重值的L2正则化项参数,参数越大,模型越不容易过拟合。
#'alpha':0, # L1 正则项参数
#'scale_pos_weight':1, #如果取值大于0的话,在类别样本不平衡的情况下有助于快速收敛。
#'objective': 'multi:softmax', #多分类的问题
#'num_class':10, # 类别数,多分类与 multisoftmax 并用
'seed':1000, #随机种子
#'eval_metric': 'auc'
}plst = list(params.items())
num_rounds = 100 # 迭代次数watchlist = [(xgb_train, 'train'),(xgb_test, 'val')]#训练模型并保存
# early_stopping_rounds 当设置的迭代次数较大时,early_stopping_rounds 可在一定的迭代次数内准确率没有提升就停止训练
model = xgb.train(plst, xgb_train, num_rounds, watchlist,early_stopping_rounds=100)#model.save_model('./model/xgb.model') # 用于存储训练出的模型
print("best best_ntree_limit",model.best_ntree_limit)y_pred = model.predict(xgb_test,ntree_limit=model.best_ntree_limit)
print('error=%f' % ( sum(1 for i in range(len(y_pred)) if int(y_pred[i]>0.5)!=y_test[i]) /float(len(y_pred))))
#输出运行时长
cost_time = time.time()-start_time
print("xgboost success!",'\n',"cost time:",cost_time,"(s)......")```