XGBoost回归的sklearnAPI实现
不同于内嵌在sklearn框架中的其他算法,xgboost是独立的算法库,因此它有一套不同于sklearn代码的原生代码。大部分时候我们使用原生代码来运行xgboost,因为这套原生代码是完全为集成学习所设计的,不仅可以无缝使用交叉验证、默认输出指标为RMSE,还能够默认输出训练集上的结果帮我们监控模型。然而对于熟悉sklearn的我们来说,这一套代码略有难度,因此许多人也会倾向于使用xgboost自带的sklearn接口来实现算法。
XGBoost自带sklearn接口(sklearn API),通过这个接口,我们可以使用跟sklearn代码一样的方式来实现xgboost,即可以通过fit和predict等接口来执行训练预测过程,也可以调用属性比如coef_等。在XGBoost的sklearn API中,我们可以看到下面五个类:
类 |
说明 |
XGBRegressor() |
实现xgboost回归 |
XGBClassifier() |
实现xgboost分类 |
XGBRanker() |
实现xgboost排序 |
XGBRFClassifier() |
基于xgboost库实现随机森林分类 |
XGBRFRegressor() |
基于xgboost库实现随机森林回归 |
其中XGBRF的两个类是以XGBoost方式建树、但以bagging方式构建森林的类,通常只有在我们使用普通随机森林效果不佳、但又不希望使用Boosting的时候使用。这种使用XGBoost方式建树的森林在sklearn中已经开始了实验,不过还没有正式上线。
另外两个类就很容易理解了,一个是XGBoost的回归,一个是XGBoost的分类。这两个类的参数高度相似,我们可以以XGBoost回归为例查看:
class xgboost.XGBRegressor
(n_estimators, max_depth, learning_rate, verbosity, objective, booster, tree_method, n_jobs, gamma, min_child_weight, max_delta_step, subsample, colsample_bytree, colsample_bylevel, colsample_bynode, reg_alpha, reg_lambda, scale_pos_weight, base_score, random_state, missing, num_parallel_tree, monotone_constraints, interaction_constraints, importance_type, gpu_id, validate_parameters, predictor, enable_categorical, eval_metric, early_stopping_rounds, callbacks,**kwargs)
class xgboost.XGBClassifier
(n_estimators, use_label_encoder, max_depth, learning_rate, verbosity, objective, booster, tree_method, n_jobs, gamma, min_child_weight, max_delta_step, subsample, colsample_bytree, colsample_bylevel, colsample_bynode, reg_alpha, reg_lambda, scale_pos_weight, base_score, random_state, missing, num_parallel_tree, monotone_constraints, interaction_constraints, importance_type, gpu_id, validate_parameters, predictor, enable_categorical, **kwargs)
可以看到,两个类的参数两都很多,其中不乏一些我们非常熟悉的参数,例如n_estimators
,learning_rate
, max_depth
等。但大部分参数还是需要我们重新学习和认识,这与xgboost复杂的原理有很大的关系,但由于是sklearn API,所以所有这些参数都有相应的默认值。我们可以在不认识参数的情况下调用这个类。以回归类为例我们来看:
1 2 3 4 5
| from xgboost import XGBRegressor from sklearn.model_selection import cross_validate, KFold
from sklearn.model_selection import train_test_split
|
1
| data = pd.read_csv(r"D:\Pythonwork\2021ML\PART 2 Ensembles\datasets\House Price\train_encode.csv",index_col=0)
|
|
Id |
住宅类型 |
住宅区域 |
街道接触面积(英尺) |
住宅面积 |
街道路面状况 |
巷子路面状况 |
住宅形状(大概) |
住宅现状 |
水电气 |
... |
泳池面积 |
泳池质量 |
篱笆质量 |
其他配置 |
其他配置的价值 |
销售月份 |
销售年份 |
销售类型 |
销售状态 |
SalePrice |
0 |
0.0 |
5.0 |
3.0 |
36.0 |
327.0 |
1.0 |
0.0 |
3.0 |
3.0 |
0.0 |
... |
0.0 |
0.0 |
0.0 |
0.0 |
0.0 |
1.0 |
2.0 |
8.0 |
4.0 |
208500 |
1 |
1.0 |
0.0 |
3.0 |
51.0 |
498.0 |
1.0 |
0.0 |
3.0 |
3.0 |
0.0 |
... |
0.0 |
0.0 |
0.0 |
0.0 |
0.0 |
4.0 |
1.0 |
8.0 |
4.0 |
181500 |
2 |
2.0 |
5.0 |
3.0 |
39.0 |
702.0 |
1.0 |
0.0 |
0.0 |
3.0 |
0.0 |
... |
0.0 |
0.0 |
0.0 |
0.0 |
0.0 |
8.0 |
2.0 |
8.0 |
4.0 |
223500 |
3 |
3.0 |
6.0 |
3.0 |
31.0 |
489.0 |
1.0 |
0.0 |
0.0 |
3.0 |
0.0 |
... |
0.0 |
0.0 |
0.0 |
0.0 |
0.0 |
1.0 |
0.0 |
8.0 |
0.0 |
140000 |
4 |
4.0 |
5.0 |
3.0 |
55.0 |
925.0 |
1.0 |
0.0 |
0.0 |
3.0 |
0.0 |
... |
0.0 |
0.0 |
0.0 |
0.0 |
0.0 |
11.0 |
2.0 |
8.0 |
4.0 |
250000 |
5 rows × 81 columns
1 2 3
| X = data.iloc[:,:-1] y = data.iloc[:,-1]
|
(1460, 80)
count 1460.000000
mean 180921.195890
std 79442.502883
min 34900.000000
25% 129975.000000
50% 163000.000000
75% 214000.000000
max 755000.000000
Name: SalePrice, dtype: float64
在这个数据集上我们曾经达到过如下的分数:(TPE代表经过调参,其它的代表未调参)
算法 |
RF |
AdaBoost |
GBDT |
RF (TPE) |
AdaBoost (TPE) |
GBDT (TPE) 梯度提升树GBDT 贝叶斯优化TPE调参 |
5折验证 运行时间 |
1.29s |
0.28s |
0.49s |
0.22s |
0.27s |
1.54s(↑) |
测试最优分数 (RMSE) |
30571.267 |
35345.931 |
28783.954 |
28346.673 |
35169.730 |
26415.835(↓) |
1 2 3 4 5 6 7 8 9 10 11 12 13
|
Xtrain,Xtest,Ytrain,Ytest = train_test_split(X,y,test_size=0.3,random_state=1412)
xgb_sk = XGBRegressor(random_state=1412)
xgb_sk.fit(Xtrain,Ytrain) xgb_sk.score(Xtest,Ytest)
|
0.8707175563742298
1 2 3 4 5
|
xgb_sk = XGBRegressor(random_state=1412)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| cv = KFold(n_splits=5,shuffle=True,random_state=1412)
result_xgb_sk = cross_validate(xgb_sk,X,y,cv=cv ,scoring="neg_root_mean_squared_error" ,return_train_score=True ,verbose=True ,n_jobs=-1)
|
[Parallel(n_jobs=-1)]: Using backend LokyBackend with 16 concurrent workers.
[Parallel(n_jobs=-1)]: Done 5 out of 5 | elapsed: 1.5s finished
{'fit_time': array([0.23205304, 0.45810175, 0.46110272, 0.47810745, 0.47210622]),
'score_time': array([0.00499988, 0.00500083, 0.00700212, 0.00600123, 0.0080018 ]),
'test_score': array([-25398.06063039, -42892.11176772, -26426.91326917, -20676.41934632,
-33375.56869975]),
'train_score': array([ -903.74997856, -1106.45801425, -997.3799282 , -818.69215194,
-877.57892862])}
1 2 3 4
| def RMSE(result,name): return abs(result[name].mean())
|
1
| RMSE(result_xgb_sk,"train_score")
|
940.7718003131752
1
| RMSE(result_xgb_sk,"test_score")
|
29753.814742669765
可以看到,在默认参数下,xgboost模型极度不稳定,并且过拟合的情况非常严重,在训练集上的RMSE达到了前所未有的低点940.77,这说明XGBoost的学习能力的确强劲,现有数据量对xgboost来说可能有点不足。在没有调整任何参数的情况下,XGBoost的表现没能胜过梯度提升树,这可能是因为在默认参数下梯度提升树的过拟合程度较轻。我们可以尝试使用之前学过的知识,对XGBoost的参数略微进行调整,例如将最可能影响模型的参数之一:max_depth
设置为一个较小的值。
1 2
| xgb_sk = XGBRegressor(max_depth=5,random_state=1412)
|
1 2 3 4 5
| result_xgb_sk = cross_validate(xgb_sk,X,y,cv=cv ,scoring="neg_root_mean_squared_error" ,return_train_score=True ,verbose=True ,n_jobs=-1)
|
[Parallel(n_jobs=-1)]: Using backend LokyBackend with 16 concurrent workers.
[Parallel(n_jobs=-1)]: Done 5 out of 5 | elapsed: 0.3s finished
1
| RMSE(result_xgb_sk,"train_score")
|
2362.6596931022264
1
| RMSE(result_xgb_sk,"test_score")
|
28623.2199609373
过拟合程度立刻减轻了,这说明模型是有潜力的,经过精密的调参之后xgboost上应该能够获得不错的结果。
当sklearn API训练完毕之后,我们可以调用sklearn中常见的部分属性对训练后的模型进行查看,例如查看特征重要性的属性feature_importances_
,以及查看XGB下每一棵树的get_booster()
方法、查看总共有多少棵树的get_num_boosting_rounds()
方法、以及查看当前所有参数的方法get_params
。
1
| xgb_sk = XGBRegressor(max_depth=5,random_state=1412).fit(X,y)
|
1 2
| xgb_sk.feature_importances_
|
array([2.75380560e-04, 3.31971998e-04, 1.00358156e-02, 1.05883693e-03,
3.16664134e-03, 0.00000000e+00, 1.98506648e-04, 2.89293937e-03,
1.25273280e-02, 0.00000000e+00, 5.93377743e-04, 1.02778999e-02,
2.30982271e-03, 1.63636741e-03, 2.90878420e-03, 2.35304120e-03,
6.21278654e-04, 4.83804524e-01, 3.57081974e-03, 5.26868505e-03,
5.39273489e-03, 1.19193934e-03, 6.73988950e-04, 8.58596177e-04,
7.24220707e-04, 6.31435658e-04, 6.40772341e-04, 1.79228242e-02,
1.88877675e-04, 5.03550633e-04, 1.56989340e-02, 2.03612563e-03,
2.47432292e-03, 1.01661379e-03, 8.10322072e-03, 2.90135911e-04,
5.88890282e-04, 7.41500990e-04, 1.55724427e-02, 4.79830429e-04,
3.00964224e-04, 3.08048874e-02, 1.71841952e-04, 1.05094137e-02,
7.64262909e-03, 5.08715457e-04, 3.02572548e-02, 2.92710634e-03,
3.74272262e-04, 3.38621549e-02, 1.68845232e-03, 5.42165304e-04,
3.92313190e-02, 2.88939234e-02, 3.41931777e-03, 3.12080747e-03,
9.38584842e-03, 8.22769129e-04, 1.47235217e-02, 3.14127025e-03,
1.07205287e-03, 1.11840509e-01, 3.14075779e-03, 2.42510848e-02,
7.32478802e-05, 7.96243025e-04, 9.54234274e-04, 1.75007887e-03,
5.87832765e-04, 3.06713278e-04, 1.96228363e-03, 1.95614668e-03,
0.00000000e+00, 6.25234039e-04, 6.64470572e-05, 1.08636224e-04,
8.74570746e-04, 8.18299886e-04, 3.22782877e-03, 3.69613548e-03],
dtype=float32)
1 2
| xgb_sk.get_booster()[2]
|
<xgboost.core.Booster at 0x1f905eec130>
一棵树都是一个单独的Booster提升树,Booster就相当于sklearn中DecisionTreeRegressor,只不过是使用xgboost独有的建树规则进行计算。
1 2
| xgb_sk.get_num_boosting_rounds()
|
100
{'objective': 'reg:squarederror',
'base_score': 0.5,
'booster': 'gbtree',
'colsample_bylevel': 1,
'colsample_bynode': 1,
'colsample_bytree': 1,
'enable_categorical': False,
'gamma': 0,
'gpu_id': -1,
'importance_type': None,
'interaction_constraints': '',
'learning_rate': 0.300000012,
'max_delta_step': 0,
'max_depth': 5,
'min_child_weight': 1,
'missing': nan,
'monotone_constraints': '()',
'n_estimators': 100,
'n_jobs': 16,
'num_parallel_tree': 1,
'predictor': 'auto',
'random_state': 1412,
'reg_alpha': 0,
'reg_lambda': 1,
'scale_pos_weight': 1,
'subsample': 1,
'tree_method': 'exact',
'validate_parameters': 1,
'verbosity': None}
查看参数对xgboost来说很有意义,因为XGBRegressor的说明中没有注明默认参数,因此通过查看参数,我们可以了解到xgboost在sklearn API中都设置了怎样的参数,作为未来调参的参考。对于xgboost分类器,我们还可以调用predict_proba
这样的方法来输出概率值,除此之外我们一般不会再用到xgboost sklearn API中的其他功能。