极致梯度提升树 | eXtreme Gradient Boosting (XGBoost)
XGBoost 是大规模并行 boosting tree 的工具,它是目前最快最好的开源 boosting tree 工具包,比常见的工具包快 10 倍以上。Xgboost 和 GBDT 两者都是 boosting 方法,除了工程实现、解决问题上的一些差异外,最大的不同就是目标函数的定义。
本文不对XGBoost原理进行推导和探讨,具体内容可以参考文末推荐文章
一、对比原算法GBDT的提升优化
一是算法本身的优化
- 弱学习器:GBDT只支持决策树,XGBoost不仅支持 CART 还支持线性分类器(使用线性分类器的 XGBoost 相当于带 L1 和 L2 正则化项的逻辑斯蒂回归(分类问题)或者线性回归(回归问题))
- 损失函数:XGBoost除了本身的损失,还加上了正则化部分。
- 优化方式:GBDT的损失函数只对误差部分做负梯度(一阶泰勒)展开,而XGBoost损失函数对误差部分做二阶泰勒展开,更加准确。
二是算法运行效率的优化
- 对每个弱学习器做并行选择,找到合适的子树分裂特征和特征值
- 在并行选择之前,先对所有的特征的值进行排序分组,方便加快并行选择
- 对分组的特征,选择合适的分组大小,使用CPU缓存进行读取加速
三是算法健壮性的优化
- 对于缺失值的特征,通过枚举所有缺失值在当前节点是进入左子树还是右子树来决定缺失值的处理方式
- 算法本身加入了L1和L2正则化项,可以防止过拟合,泛化能力更强
二、XGBoost搭建模型
2.1、实现方式
XGBoost模型搭建可以通过两种方式来实现,一种是XGBoost的原生接口,另一种是S-Klearn风格的接口;以下分别对两种实现方式做介绍。
2.1.1、XGBoost原生接口
import xgboost as xgb X_train,X_test,y_train,y_test = train_test_split(X,y,test_size=0.2,random_state=12343) dtrain = xgb.DMatrix(X_train,y_train) # 将数据转换为xgb所需的特殊数据矩阵,能大幅加快模型训练 xgb_model = xgb.train(params,dtrain,num_rounds) # 原生接口训练,具体是回归模型还是分类模型需要通过params中的objective参数值决定 dtest = xgb.DMatrix(X_test) y_pred = xgb_model.predict(dtest) # 预测,注:如果objective选择的是“binary:logistic”,则返回概率值
2.1.2、S-Klearn风格接口
from xgboost import XGBClassifier from xgboost import XGBRegressor X_train,X_test,y_train,y_test = train_test_split(X,y,test_size=0.2,random_state=12343) xgb_clf_model = XGBClassifier() # 分类模型 xgb_clf_model.fit(X_train,y_train) # 训练 xgb_clf_model.predict(X_test) # 返回预测值 xgb_clf_model.predict_proba(X_test) # 返回各个样本属于各个类别的概率 xgb_res_model = XGBRegressor() # 回归模型 xgb_res_model.fit(X_train,y_train) # 训练 xgb_res_model.predict(X_test) # 返回预测值
2.2、参数详解
两种风格的参数大同小异,只是在不同的接口中,参数名称会有细微差异,具体参数如下
XGBoost的参数我总结为模型参数和训练参数,XGBoost原生接口中的模型参数都在params中,可以通过params中的objective参数值决定具体是分类模型还是回归模型。
2.2.1、模型参数
模型参数如下:
XGBoost原生接口 | SKlearn风格接口 | 参数默认值 | 推荐候选值 | 参数说明 |
objective | objective | “reg:linear” | “binary:logistic” | 用于指定学习任务及相应的学习目标:
reg:linear,线性回归(默认值)。
reg:logistic,逻辑回归。
binary:logistic,二分类的逻辑回归问题,输出为概率。
binary:logitraw,二分类逻辑回归,输出的结果为wTx
count:poisson,计数问题的poisson回归,输出结果为poisson分布。在poisson回归中,max_delta_step的缺省值为0.7 (used to safeguard optimization)
multi:softmax – 设置 XGBoost 使用softmax目标函数做多分类,需要设置参数num_class(类别个数)
multi:softprob – 如同softmax,但是输出结果为ndata*nclass的向量,其中的值是每个数据分为每个类的概率。 |
num_class | num_class | ㅤ | ㅤ | 用于设置多分类问题的类别个数。需和上个参数联合使用 |
booster | booster | ‘gbtree’ | ‘gbtree’ | 用于指定弱学习器的类型;‘gbtree’,表示使用基于树的模型作为弱学习器;‘gblinear’ 表示使用线性模型作为弱学习器。 |
eta | learning_rate | 0.3 | [0.01, 0.015, 0.025, 0.05, 0.1]
在0.01到0.2之间 | "Shrinkage"的思想,即不完全信任每个弱学习器学到的残差值,设置较小的 eta 就可以多学习几个弱学习器来弥补不足的残差。 |
num_boost_round | n_estimators | 100 | [1, ∞] | ㅤ |
max_depth | max_depth | 6 | [3, 5, 6, 7, 9, 12, 15, 17, 25]
[1, ∞],经验值推荐:3-10 | 指定树的最大深度,合理的设置可以防止过拟合。 |
subsample | subsample | 1 | [0.6, 0.7, 0.8, 0.9, 1]
常在0.5到1之间选择 | 指定采样出 subsample * n_samples 个样本用于训练弱学习器。不放回抽样。取值在(0, 1)之间,设置为1表示使用所有数据训练弱学习器。如果取值小于1,则只有一部分样本会去做GBDT的决策树拟合。选择小于1的比例可以减少方差,即防止过拟合,但是会增加样本拟合的偏差,因此取值不能太低。 |
colsample_bytree | colsample_bytree | 1 | [0.6, 0.7, 0.8, 0.9, 1]
常在0.5到1之间选择 | 用来控制每棵随机采样的列数的占比(每一列是一个特征) |
colsample_bylevel | colsample_bylevel | 1 | ㅤ | 每棵树每次节点分裂的时候列采样的比例 |
alpha | reg_alpha | ㅤ | [0, 0.01~0.1, 1] | L1正则化权重项,增加此值将使模型更加保守。 |
lambda | reg_lambda | ㅤ | [0, 0.1, 0.5, 1] | L2正则化权重项,增加此值将使模型更加保守。 |
gamma | gamma | 0 | [0, 0.05 ~ 0.1, 0.3, 0.5, 0.7, 0.9, 1] | 指定叶节点进行分支所需的损失减少的最小值,设置的值越大,模型就越保守。 |
min_child_weight | min_child_weight | 1 | [1, 3, 5, 7] | 指定孩子节点中最小的样本权重和,如果一个叶子节点的样本权重和小于min_child_weight则拆分过程结束 |
eval_metric | eval_metric | ㅤ | ㅤ | 用于指定评估指标,可以传递各种评估方法组成的list。
rmse: 均方根误差
mae: 平均绝对值误差
logloss: negative log-likelihood
error: 二分类错误率。其值通过错误分类数目与全部分类数目比值得到。对于预测,预测值大于0.5被认为是正类,其它归为负类。 error@t: 不同的划分阈值可以通过 ‘t’进行设置
merror: 多分类错误率,计算公式为(wrong cases)/(all cases)
mlogloss: 多分类log损失
auc: 曲线下的面积
ndcg: Normalized Discounted Cumulative Gain
map: 平均正确率 |
early_stopping_rounds | early_stopping_rounds | None | [1, ∞],50 | 提前停止轮数,在 fit 函数中 |
eval_set | eval_set | None | ㅤ | 评估集,在 fit 函数中 |
silent | silent | 0 | 1 | 数值型,表示是否输出运行过程的信息,默认值为0,表示打印信息。设置为1时,不输出任何信息。 |
nthread | ㅤ | ㅤ | -1 | 使用线程数 |
seed | random_state | random_state | ㅤ | 指定随机数种子。 |
2.2.2、训练参数
XGBoost原生接口训练参数如下:
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)
参数名称 | 参数默认值 | 参数说明 |
params | ㅤ | 接受字典类型,存储模型参数。例如:{‘booster’:‘gbtree’,‘eta’:0.1} |
dtrain | ㅤ | 训练数据,这里的数据必须要通过xgb.DMatrix()转换:
dtrain = xgb.DMatrix(data, label=label) |
num_boost_round | 10 | 指定最大迭代次数 |
evals | ㅤ | 列表类型,用于指定训练过程中用于评估的数据及数据的名称[(dtrain,‘train’),(dval,‘val’)] |
obj | ㅤ | 可以指定二阶可导的自定义目标函数 |
feval | ㅤ | 自定义评估函数。 |
maximize | False | 是否对评估函数最大化,默认值为False。 |
early_stopping_rounds | ㅤ | 指定迭代多少次没有得到优化则停止训练,默认值为None,表示不提前停止训练。
如果设置了此参数,则模型会生成三个属性:
best_score
best_iteration
best_ntree_limit
evals 必须非空才能生效,如果有多个数据集,则以最后一个数据集为准。 |
verbose_eval | ㅤ | 可以是bool类型,也可以是整数类型。如果设置为整数,则每间隔verbose_eval次迭代就输出一次信息。 |
xgb_model | ㅤ | 加载之前训练好的 xgb 模型,用于增量训练 |
S-Klearn风格接口训练参数如下:
xgb_model.fit( X, # array, DataFrame 类型 y, # array, Series 类型 eval_set=None, # 用于评估的数据集,例如:[(X_train, y_train), (X_test, y_test)] eval_metric=None, # 评估函数,字符串类型,例如:'mlogloss' early_stopping_rounds=None, verbose=True, # 间隔多少次迭代输出一次信息 xgb_model=None )
2.3、模型调参
2.3.1、可优化参数
- max_depth: 决定树的最大深度,比较重要 常用值4-6 ,深度越深越容易过拟合。
- n_estimators: 构建多少颗数 ,树越多越容易过拟合。
- learning_rate/eta : 学习率 ,范围0-1 。默认值 为0.3 , 常用值0.01-0.2
- subsample: 每次迭代用多少数据集 。
- colsample_bytree: 每次用多少特征 。可以控制过拟合。
- min_child_weight :树的最小权重 ,越小越容易过拟合。
- gamma:如果分裂能够使loss函数减小的值大于gamma,则这个节点才分裂。gamma设置了这个减小的最低阈值。如果gamma设置为0,表示只要使得loss函数减少,就分裂。
- alpha:l1正则,默认为0
- lambda :l2正则,默认为1
2.3.2、网格搜索
parameters = { 'max_depth': [5, 10, 15, 20, 25], 'learning_rate': [0.01, 0.02, 0.05, 0.1, 0.15], 'n_estimators': [50, 100, 200, 300, 500], 'min_child_weight': [0, 2, 5, 10, 20], 'max_delta_step': [0, 0.2, 0.6, 1, 2], 'subsample': [0.6, 0.7, 0.8, 0.85, 0.95], 'colsample_bytree': [0.5, 0.6, 0.7, 0.8, 0.9], 'reg_alpha': [0, 0.25, 0.5, 0.75, 1], 'reg_lambda': [0.2, 0.4, 0.6, 0.8, 1], 'scale_pos_weight': [0.2, 0.4, 0.6, 0.8, 1] } xlf = xgb.XGBClassifier(silent=True,objective='binary:logistic',nthread=-1,seed=1440) gsearch = GridSearchCV(xlf, param_grid=parameters, scoring='accuracy', cv=3) gsearch.fit(train_x, train_y)
2.4、其他事项
2.4.1、保存加载模型
import xgboost as xgb xgb_model.save_model('./xgb_model.model') xgb_model = xgb.Booster(model_file=('./xgb_model.model') xgb_model = xgb.Booster().load_model('./xgb_model.model') # 以下方法待验证,具体出处详见:[XGBoost如何保存和读取模型? - 知乎](https://zhuanlan.zhihu.com/p/370761158) from xgboost import XGBClassifier,Booster clf.save_model('./xgb_model.model') clf = XGBClassifier() clf._Booster = Booster().load_model('./xgb_model.model')
2.4.2、特征重要性
1、查看特征重要性
只有当选择决策树模型作为基础学习器(booster=gbtree)时,才能定义特征的重要性。它没有为其他基本学习者类型定义,例如线性学习者(booster=gblinear)。
# 方法一: xgb_model.get_score(importance_type=importance_type) # importance_type有以下几种: # 'weight', 'gain', 'cover', 'total_gain', 'total_cover' # weight - 该特征在所有树中被用作分割样本的特征的次数。 # gain - 在所有树中的平均增益。 # cover - 在树中使用该特征时的平均覆盖范围。 # 方法二: from xgboost import plot_importance plot_importance(xgb_model) plt.rcParams['figure.figsize'] = [3, 3] # 调整画布大小 plt.show() # 方法三: xgb_model.feature_importances_
打印树import matplotlib.pyplot as plt xgb.plot_tree(xgb_model,num_trees=0) plt.rcParams['figure.figsize'] = [80, 60] plt.show()
参考:
2、根据特征重要性选择特征
#fit model using each importance as a threshold thresholds = np.sort(model.feature_importances_) for thresh in thresholds: # select features using threshold selection = SelectFromModel(model,threshold=thresh,prefit=True ) select_X_train = selection.transform(X_train) # train model selection_model = XGBClassifier() selection_model.fit(select_X_train, y_train) # eval model select_X_test = selection.transform(X_test) y_pred = selection_model.predict(select_X_test) predictions = [round(value) for value in y_pred] accuracy = accuracy_score(y_test,predictions) print("Thresh=%.3f, n=%d, Accuracy: %.2f%%" % (thresh, select_X_train.shape[1], accuracy * 100.0))
2.4.3、xgb.DMatrix()数据工具
1、数据列名问题
xgb.DMatrix()在处理数据的时候,可以接收Dataframe带列名的数据,也能直接接收没有列名的Series数据,但是训练数据和测试数据在转换是需要保持统一,也就是要么都有列名,要么都没有,否则会报错。
2、直接读取libsvm格式数据
xgb.DMatrix()直接读取libsvm格式数据生成
DMatrix
格式,然后直接进行训练。libsvm格式数据如下:[label_1] [index_1]:[value_1_1] [index_2]:[value_1_2] … [index_n]:[value_1_n] [label_2] [index_1]:[value_2_1] [index_2]:[value_2_2] … [index_n]:[value_2_n] … [label_m] [index_1]:[value_m_1] [index_2]:[value_m_2] … [index_n]:[value_m_n]
label 目标值,就是说class(属于哪一类),就是你要分类的种类,通常是一些整数。index 是有顺序的索引,通常是连续的整数。就是指特征编号,必须按照升序排列value 就是特征值,用来train的数据,通常是一堆实数组成。
dtrain = xgb.DMatrix('./data/agaricus.txt.train') dtest = xgb.DMatrix('./data/agaricus.txt.test') xgb_model = xgboost.train(param, dtrain, num_round, watchlist)
2.4.4、使用经验
1、报错
(1)、特征名称错误,是因为预测数据在转换成DMetrics矩阵时没有把特证名称去掉,这一步需要ndarray类型的数据
2、负数
使用 XGboost 进行建模时,要求数据中不能有负数,如果存在负数需要进行处理,通常使用 min-max 归一化可以处理掉,或者删除或者替换等操作
三、XGBoost优缺点
优点
- GBDT 只用到一阶泰勒展开,而 XGBoost 对损失函数进行了二阶泰勒展开,提升了精准度。
- GBDT 以 CART 作为基分类器,XGBoost 不仅支持 CART 还支持线性分类器,(使用线性分类器的 XGBoost 相当于带 L1 和 L2 正则化项的逻辑斯蒂回归(分类问题)或者线性回归(回归问题))
- XGBoost 在目标函数中加入了正则项,用于控制模型的复杂度。
- XGBoost 借鉴了随机森林的做法,支持列抽样,不仅能降低过拟合,还能减少计算;
- XGBoost 采用的稀疏感知算法极大的加快了节点分裂的速度;
缺点
- 虽然利用预排序和近似算法可以降低寻找最佳分裂点的计算量,但在节点分裂过程中仍需要遍历数据集;
- 预排序过程的空间复杂度过高,不仅需要存储特征值,还需要存储特征对应样本的梯度统计值的索引,相当于消耗了两倍的内存。