在XGBoost算法原理小结中,我们讨论了XGBoost的算法原理,这一片我们讨论如何使用XGBoost的Python类库,以及一些重要参数的意义和调参思路。
本文主要参考了XGBoost的Python文档 和 XGBoost的参数文档。
1. XGBoost类库概述
XGBoost除了支持Python外,也支持R,Java等语言。本文关注于Python的XGBoost类库,安装使用"pip install xgboost"即可,目前使用的是XGBoost的0.90版本。XGBoost类库除了支持决策树作为弱学习器外,还支持线性分类器,以及带DropOut的决策树DART,不过通常情况下,我们使用默认的决策树弱学习器即可,本文也只会讨论使用默认决策树弱学习器的XGBoost。
XGBoost有2种Python接口风格。一种是XGBoost自带的原生Python API接口,另一种是sklearn风格的API接口,两者的实现是基本一样的,仅仅有细微的API使用的不同,主要体现在参数命名上,以及数据集的初始化上面。
2. XGBoost类库的基本使用方式
完整示例参见我的Github代码。
2.1 使用原生Python API接口
XGBoost的类库的2种接口风格,我们先来看看原生Python API接口如何使用。
原生XGBoost需要先把数据集按输入特征部分,输出部分分开,然后放到一个DMatrix数据结构里面,这个DMatrix我们不需要关心里面的细节,使用我们的训练集X和y初始化即可。
import pandas as pd import numpy as np import xgboost as xgb import matplotlib.pylab as plt %matplotlib inline from sklearn.model_selection import GridSearchCV from sklearn.model_selection import train_test_split
from sklearn.datasets.samples_generator import make_classification # X为样本特征,y为样本类别输出, 共10000个样本,每个样本20个特征,输出有2个类别,没有冗余特征,每个类别一个簇 X, y = make_classification(n_samples=10000, n_features=20, n_redundant=0, n_clusters_per_class=1, n_classes=2, flip_y=0.1)
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=1)
dtrain = xgb.DMatrix(X_train,y_train)
dtest = xgb.DMatrix(X_test,y_test)
上面的代码中,我们随机初始化了一个二分类的数据集,然后分成了训练集和验证集。使用训练集和验证集分别初始化了一个DMatrix,有了DMatrix,就可以做训练和预测了。简单的示例代码如下:
param = {'max_depth':5, 'eta':0.5, 'verbosity':1, 'objective':'binary:logistic'} raw_model = xgb.train(param, dtrain, num_boost_round=20)
from sklearn.metrics import accuracy_score pred_train_raw = raw_model.predict(dtrain) for i in range(len(pred_train_raw)): if pred_train_raw[i] > 0.5: pred_train_raw[i]=1 else: pred_train_raw[i]=0 print (accuracy_score(dtrain.get_label(), pred_train_raw))
训练集的准确率我这里输出是0.9664。再看看验证集的表现:
pred_test_raw = raw_model.predict(dtest) for i in range(len(pred_test_raw)): if pred_test_raw[i] > 0.5: pred_test_raw[i]=1 else: pred_test_raw[i]=0 print (accuracy_score(dtest.get_label(), pred_test_raw))
验证集的准确率我这里的输出是0.9408,已经很高了。
不过对于我这样用惯sklearn风格API的,还是不太喜欢原生Python API接口,既然有sklearn的wrapper,那么就尽量使用sklearn风格的接口吧。
2.2 使用sklearn风格接口,使用原生参数
对于sklearn风格的接口,主要有2个类可以使用,一个是分类用的XGBClassifier,另一个是回归用的XGBRegressor。在使用这2个类的使用,对于算法的参数输入也有2种方式,第一种就是仍然使用和原始API一样的参数命名集合,另一种是使用sklearn风格的参数命名。我们这里先看看如何使用和原始API一样的参数命名集合。
其实就是使用XGBClassifier/XGBRegressor的**kwargs参数,把上面原生参数的params集合放进去,代码如下:
sklearn_model_raw = xgb.XGBClassifier(**param) sklearn_model_raw.fit(X_train, y_train, early_stopping_rounds=10, eval_metric="error", eval_set=[(X_test, y_test)])
里面的param其实就是2.1节里面定义的:
param = {'max_depth':5, 'eta':0.5, 'verbosity':1, 'objective':'binary:logistic'}
使用sklearn风格的接口,却使用原始的参数名定义,感觉还是有点怪,所以我一般还是习惯使用另一种风格接口,sklearn风格的参数命名。
2.3 使用sklearn风格接口,使用sklearn风格参数
使用sklearn风格的接口,并使用sklearn风格的参数,是我推荐的方式,主要是这样做和GBDT之类的sklearn库使用起来没有什么两样了,也可以使用sklearn的网格搜索。
不过这样做的话,参数定义命名和2.1与2.2节就有些不同了。具体的参数意义我们后面讲,我们看看分类的算法初始化,训练与调用的简单过程:
sklearn_model_new = xgb.XGBClassifier(max_depth=5,learning_rate= 0.5, verbosity=1, objective='binary:logistic',random_state=1)
可以看到,参数定义直接放在了XGBClassifier的类参数里,和sklearn类似。大家可以看到之前两节我们定义的步长eta,这里变成了另一个名字learning_rate。
在初始化后,训练和预测的方法就和2.2节没有区别了。
sklearn_model_new.fit(X_train, y_train, early_stopping_rounds=10, eval_metric="error", eval_set=[(X_test, y_test)])
3. XGBoost类库参数
在第二节我们已经尝试使用XGBoost类库了,但是对于XGBoost的类库参数并没有过多讨论。这里我们就详细讨论下,主要以2.3节的sklearn风格参数为主来进行讨论。这些参数我会和之前讲的scikit-learn 梯度提升树(GBDT)调参小结中的参数定义对应,这样如果大家对GBDT的调参很熟悉了,那么XGBoost的调参也就掌握90%了。
XGBoost的类库参数主要包括boosting框架参数,弱学习器参数以及其他参数。
3.1 XGBoost框架参数
对于XGBoost的框架参数,最重要的是3个参数: booster,n_estimators和objectve。
1) booster决定了XGBoost使用的弱学习器类型,可以是默认的gbtree, 也就是CART决策树,还可以是线性弱学习器gblinear以及DART。一般来说,我们使用gbtree就可以了,不需要调参。
2) n_estimators则是非常重要的要调的参数,它关系到我们XGBoost模型的复杂度,因为它代表了我们决策树弱学习器的个数。这个参数对应sklearn GBDT的n_estimators。n_estimators太小,容易欠拟合,n_estimators太大,又容易过拟合,一般需要调参选择一个适中的数值。
3) objective代表了我们要解决的问题是分类还是回归,或其他问题,以及对应的损失函数。具体可以取的值很多,一般我们只关心在分类和回归的时候使用的参数。
在回归问题objective一般使用reg:squarederror ,即MSE均方误差。二分类问题一般使用binary:logistic, 多分类问题一般使用multi:softmax。
3.2 XGBoost 弱学习器参数
这里我们只讨论使用gbtree默认弱学习器的参数。 要调参的参数主要是决策树的相关参数如下:
1) max_depth: 控制树结构的深度,数据少或者特征少的时候可以不管这个值。如果模型样本量多,特征也多的情况下,需要限制这个最大深度,具体的取值一般要网格搜索调参。这个参数对应sklearn GBDT的max_depth。
2) min_child_weight: 最小的子节点权重阈值,如果某个树节点的权重小于这个阈值,则不会再分裂子树,即这个树节点就是叶子节点。这里树节点的权重使用的是该节点所有样本的二阶导数的和,即XGBoost原理篇里面的$H_{tj}$:$$H_{tj} = \sum\limits_{x_i \in R_{tj}}h_{ti}$$
这个值需要网格搜索寻找最优值,在sklearn GBDT里面,没有完全对应的参数,不过min_samples_split从另一个角度起到了阈值限制。
3) gamma: XGBoost的决策树分裂所带来的损失减小阈值。也就是我们在尝试树结构分裂时,会尝试最大数下式:$$ \max \frac{1}{2}\frac{G_L^2}{H_L + \lambda} + \frac{1}{2}\frac{G_R^2}{H_R+\lambda} - \frac{1}{2}\frac{(G_L+G_R)^2}{H_L+H_R+ \lambda} - \gamma$$
这个最大化后的值需要大于我们的gamma,才能继续分裂子树。这个值也需要网格搜索寻找最优值。
4) subsample: 子采样参数,这个也是不放回抽样,和sklearn GBDT的subsample作用一样。选择小于1的比例可以减少方差,即防止过拟合,但是会增加样本拟合的偏差,因此取值不能太低。初期可以取值1,如果发现过拟合后可以网格搜索调参找一个相对小一些的值。
5) colsample_bytree/colsample_bylevel/colsample_bynode: 这三个参数都是用于特征采样的,默认都是不做采样,即使用所有的特征建立决策树。colsample_bytree控制整棵树的特征采样比例,colsample_bylevel控制某一层的特征采样比例,而colsample_bynode控制某一个树节点的特征采样比例。比如我们一共64个特征,则假设colsample_bytree,colsample_bylevel和colsample_bynode都是0.5,则某一个树节点分裂时会随机采样8个特征来尝试分裂子树。
6) reg_alpha/reg_lambda: 这2个是XGBoost的正则化参数。reg_alpha是L1正则化系数,reg_lambda是L1正则化系数,在原理篇里我们讨论了XGBoost的正则化损失项部分:$$\Omega(h_t) = \gamma J + \frac{\lambda}{2}\sum\limits_{j=1}^Jw_{tj}^2$$
上面这些参数都是需要调参的,不过一般先调max_depth,min_child_weight和gamma。如果发现有过拟合的情况下,再尝试调后面几个参数。
3.3 XGBoost 其他参数
XGBoost还有一些其他的参数需要注意,主要是learning_rate。
learning_rate控制每个弱学习器的权重缩减系数,和sklearn GBDT的learning_rate类似,较小的learning_rate意味着我们需要更多的弱学习器的迭代次数。通常我们用步长和迭代最大次数一起来决定算法的拟合效果。所以这两个参数n_estimators和learning_rate要一起调参才有效果。当然也可以先固定一个learning_rate ,然后调完n_estimators,再调完其他所有参数后,最后再来调learning_rate和n_estimators。
此外,n_jobs控制算法的并发线程数, scale_pos_weight用于类别不平衡的时候,负例和正例的比例。类似于sklearn中的class_weight。importance_type则可以查询各个特征的重要性程度。可以选择“gain”, “weight”, “cover”, “total_gain” 或者 “total_cover”。最后可以通过调用booster的get_score方法获取对应的特征权重。“weight”通过特征被选中作为分裂特征的计数来计算重要性,“gain”和“total_gain”则通过分别计算特征被选中做分裂特征时带来的平均增益和总增益来计算重要性。“cover”和 “total_cover”通过计算特征被选中做分裂时的平均样本覆盖度和总体样本覆盖度来来计算重要性。
4. XGBoost网格搜索调参
XGBoost可以和sklearn的网格搜索类GridSeachCV结合使用来调参,使用时和普通sklearn分类回归算法没有区别。具体的流程的一个示例如下:
gsCv = GridSearchCV(sklearn_model_new, {'max_depth': [4,5,6], 'n_estimators': [5,10,20]}) gsCv.fit(X_train,y_train)
print(gsCv.best_score_) print(gsCv.best_params_)
我这里的输出是:
0.9533333333333334
{'max_depth': 4, 'n_estimators': 10}
接着尝试在上面搜索的基础上调learning_rate :
sklearn_model_new2 = xgb.XGBClassifier(max_depth=4,n_estimators=10,verbosity=1, objective='binary:logistic',random_state=1) gsCv2 = GridSearchCV(sklearn_model_new2, {'learning_rate ': [0.3,0.5,0.7]}) gsCv2.fit(X_train,y_train)
print(gsCv2.best_score_) print(gsCv2.best_params_)
我这里的输出是:
0.9516
{'learning_rate ': 0.3}
当然实际情况这里需要继续调参,这里假设我们已经调参完毕,我们尝试用验证集看看效果:
sklearn_model_new2 = xgb.XGBClassifier(max_depth=4,learning_rate= 0.3, verbosity=1, objective='binary:logistic',n_estimators=10) sklearn_model_new2.fit(X_train, y_train, early_stopping_rounds=10, eval_metric="error", eval_set=[(X_test, y_test)])
最后的输出是:
[9] validation_0-error:0.0588
也就是验证集的准确率是94.12%。
我们可以通过验证集的准确率来判断我们前面网格搜索调参是否起到了效果。实际处理的时候需要反复搜索参数并验证。
以上就是XGBoost的类库使用总结了,希望可以帮到要用XGBoost解决实际问题的朋友们。
(欢迎转载,转载请注明出处。欢迎沟通交流: [email protected])