监督学习集成模型——对比与调参

一、三大Boosting算法对比

XGBoost、LightGBM和CatBoost都是目前经典的SOTA(state of the art)Boosting算法,都可以归入梯度提升决策树算法系列。这三个模型都是以决策树为支撑的集成学习框架,其中XGBoost是对原始版本GBDT算法的改进,而LightGBM和CatBoost则是在XGBoost基础上做了进一步的优化,在精度和速度上各有所长。

三大模型之间的区别主要体现在两个方面:

模型树的构造方式有所不同,XGBoost使用按层生长(level-wise)的决策树构建策略,LightGBM则使用按叶子生长(leaf-wise)的构建策略,而CatBoost使用对称树结构,其决策树都是完全二叉树。

对于类别特征的处理有较大区别,XGBoost本身不具备自动处理类别特征的能力,对于数据中的类别特征,需要我们手动处理变换成数值后才能输入到模型中;LightGBM中则需要指定类别特征名称,算法即可自动对其进行处理;CatBoost以处理类别特征而闻名,通过目标变量统计等特征编码方式也能实现高效处理类别特征。

下面以Kaggle 2015年的flights数据集为例,分别用XGBoost、LightGBM、CatBoost模型进行实验。

https://www.kaggle.com/datasets/usdot/flight-delays

1.1 数据预处理

该数据集共有500多万条航班数据记录,特征有31个。仅演示用的情况下,我们采用抽样的方式从原始数据集中抽样1%的数据,并筛选11个特征,经预处理后重新构建训练集,目标是构建对航班是否有延误的二分类模型。数据读取和简单预处理代码如下:

import numpy as np
import pandas as pd
import time
from sklearn.model_selection import train_test_split

data = pd.read_csv("flights.csv")
# 数据抽样1%
data = data.sample(frac=0.01, random_state=10)
# 特征抽样,获取指定的11个特征
data = data[["MONTH","DAY","DAY_OF_WEEK","AIRLINE","FLIGHT_NUMBER","DESTINATION_AIRPORT",
                 "ORIGIN_AIRPORT","AIR_TIME", "DEPARTURE_TIME","DISTANCE","ARRIVAL_DELAY"]]

# 重新设置索引
data = data.reset_index(drop=True)

# 对标签进行离散化,延误10分钟以上才算延误
data["ARRIVAL_DELAY"] = (data["ARRIVAL_DELAY"]>10)*1
# 类别特征
cols = ["AIRLINE","FLIGHT_NUMBER","DESTINATION_AIRPORT","ORIGIN_AIRPORT"]
# 类别特征编码
for item in cols:
    data[item] = data[item].astype("category").cat.codes +1

# 划分数据集
X_train, X_test, y_train, y_test = train_test_split(data.drop(["ARRIVAL_DELAY"], axis=1), data["ARRIVAL_DELAY"],
                                                random_state=10, test_size=0.3)

# 打印划分后数据集大小
print(X_train.shape, y_train.shape, X_test.shape, y_test.shape)

(40733, 10) (40733,) (17458, 10) (17458,)

得到40733条训练样本,17458条测试样本,查看了正例反例的分布情况。

y_train.value_counts()

0 32125
1 8608
Name: ARRIVAL_DELAY, dtype: int64

y_test.value_counts()

0 13691
1 3767
Name: ARRIVAL_DELAY, dtype: int64

1.2 XGBoost测试

import xgboost as xgb
from sklearn.metrics import f1_score
from sklearn.metrics import accuracy_score, roc_auc_score

# 设置模型参数
params = {
    'booster': 'gbtree',
    'objective': 'binary:logistic',   
    'gamma': 0.1,
    'max_depth': 8,
    'lambda': 2,
    'subsample': 0.7,
    'colsample_bytree': 0.7,
    'min_child_weight': 3,
    'eta': 0.001,
    'seed': 1000,
    'nthread': 4,
}
t0 = time.time()
# plst = params.items()
num_rounds = 500
dtrain = xgb.DMatrix(X_train, y_train)
model_xgb = xgb.train(params, dtrain, num_rounds)
print('training spend {} seconds'.format(time.time()-t0))
# 对测试集进行预测
t1 = time.time()
dtest = xgb.DMatrix(X_test)
y_pred = model_xgb.predict(dtest)
print('testing spend {} seconds'.format(time.time()-t1))
y_pred_train = model_xgb.predict(dtrain)
print(roc_auc_score(y_train, y_pred_train))
print(roc_auc_score(y_test, y_pred))

training spend 7.261053085327148 seconds
testing spend 0.0786886215209961 seconds
0.7520467070718759
0.6965243744827456

我们测试了XGBoost在flights数据集上的表现,导入相关模块并设置模型超参数,基于训练集进行XGBoost 模型拟合,最后将训练好的模型用于测试集预测,得到测试集AUC为0.69

1.3 LightGBM测试

import lightgbm as lgb

d_train = lgb.Dataset(X_train, label=y_train)
params = {"max_depth": 5, "learning_rate" : 0.05, "num_leaves": 500,  "n_estimators": 300}

#With Catgeorical Features
cate_features_name = ["MONTH","DAY","DAY_OF_WEEK","AIRLINE","DESTINATION_AIRPORT",
                 "ORIGIN_AIRPORT"]
t0 = time.time()
model_lgb = lgb.train(params, d_train, categorical_feature = cate_features_name)
print('training spend {} seconds'.format(time.time()-t0))
t1 = time.time()
y_pred = model_lgb.predict(X_test)
print('testing spend {} seconds'.format(time.time()-t1))
y_pred_train = model_lgb.predict(X_train)
print(roc_auc_score(y_train, y_pred_train))
print(roc_auc_score(y_test, y_pred))

training spend 0.41118717193603516 seconds
testing spend 0.06251025199890137 seconds
0.8864882165535997
0.7026932971667874

LightGBM得到测试集AUC为0.69,与XGBoost表现差不多

1.4 CatBoost测试

import catboost as cb

cat_features_index = [0,1,2,3,4,5,6]
t0 = time.time()
model_cb = cb.CatBoostClassifier(eval_metric="AUC", one_hot_max_size=50, 
                            depth=6, iterations=300, l2_leaf_reg=1, learning_rate=0.1)
model_cb.fit(X_train,y_train, cat_features= cat_features_index)
print('training spend {} seconds'.format(time.time()-t0))
t1 = time.time()
y_pred = model_cb.predict(X_test)
print('testing spend {} seconds'.format(time.time()-t1))
y_pred_train = model_cb.predict(X_train)
print(roc_auc_score(y_train, y_pred_train))
print(roc_auc_score(y_test, y_pred))

training spend 18.903005361557007 seconds
testing spend 0.04068255424499512 seconds
0.5656212355170469
0.5455274098689694

CatBoost得到测试集AUC为0.55,相比XGBoost和LightGBM表现要差很多。

LightGBM无论是精度还是在速度上,都要优于XGBoost和CatBoost。当然我们只是在数据集上直接用三个模型做了比较,没有做进一步的数据特征工程和超参数调优。

二、超参数调优方法

机器学习模型中有大量参数需要事先人为设定,比如神经网络训练的batch-size、XGBoost等集成学习模型的树相关参数,我们将这类不是经过模型训练得到的参数叫作超参数(hyperparameter)。人为调整超参数的过程就是我们熟知的调参。机器学习中常用的调参方法包括网格搜索法(grid search)随机搜索法(random search)贝叶斯优化(bayesian optimization)

2.1 网格搜索法

网格搜索是一种常用的超参数调优方法,常用于优化三个或者更少数量的超参数,本质上是一种穷举法。对于每个超参数,使用者选择一个较小的有限集去探索,然后这些超参数笛卡儿乘积得到若干组超参数。网格搜索使用每组超参数训练模型,挑选验证集误差最小的超参数作为最优超参数。

sklearn中通过model_selection模块下的GridSearchCV来实现网格搜索调参。

class sklearn.model_selection.GridSearchCV(estimator, param_grid, scoring=None, fit_params=None,n_jobs=1, iid=True, refit=True, cv=None, verbose=0, pre_dispatch=‘2*n_jobs’, error_score=’raise’, return_train_score=’warn’)

GridSearchCV()的参数含义:

  1. estimator
    选择使用的分类器,并且传入除需要确定最佳的参数之外的其他参数。
    每一个分类器都需要一个scoring参数,或者score方法:

  2. param_grid
    需要最优化的参数的取值,值为字典或者列表.

  3. scoring=None
    模型评价标准,默认None,这时需要使用score函数;或者如scoring='roc_auc',
    根据所选模型不同,评价准则不同。字符串(函数名),或是可调用对象,
    需要其函数签名形如:scorer(estimator, X, y);如果是None,则使用estimator的误差估计函数。

  4. n_jobs=1
    n_jobs: 并行数,int:个数,-1:跟CPU核数一致, 1:默认值

  5. cv=None

    交叉验证参数,默认None,使用三折交叉验证。指定fold数量,默认为3,也可以是yield产生训练/测试数据的生成器。

  6. verbose=0, scoring=None
    verbose:日志冗长度,int:冗长度,0:不输出训练过程,1:偶尔输出,>1:对每个子模型都输出。

  7. pre_dispatch=‘2*n_jobs’
    指定总共分发的并行任务数。当n_jobs大于1时,数据将在每个运行点进行复制,这可能导致OOM,
    而设置pre_dispatch参数,则可以预先划分总共的job数量,使数据最多被复制pre_dispatch次

  8. return_train_score=’warn’
    如果“False”,cv_results_属性将不包括训练分数。

  9. refit :默认为True,程序将会以交叉验证训练集得到的最佳参数,重新对所有可用的训练集与开发集进行,
    作为最终用于性能评估的最佳模型参数。即在搜索参数结束后,用最佳参数结果再次fit一遍全部数据集。

  10. iid:默认True,为True时,默认为各个样本fold概率分布一致,误差估计为所有样本之和,而非各个fold的平均。

下面使用网络搜索法对XGBoost模型进行最优参数搜索

### GridSearch
from sklearn.model_selection import GridSearchCV
model = xgb.XGBClassifier()
# 待搜索参数列表空间
param_lst = {"max_depth": [3,5,7],
              "min_child_weight" : [1,3,6],
              "n_estimators": [100,200,300],
              "learning_rate": [0.01,0.05,0.1]
             }
t0 = time.time()
grid_search = GridSearchCV(model, param_grid=param_lst, cv=3, 
                                   verbose=10, n_jobs=-1)
grid_search.fit(X_train, y_train)
print('gridsearch for xgb spend', time.time()-t0, 'seconds.')

Fitting 3 folds for each of 81 candidates, totalling 243 fits
gridsearch for xgb spend 101.64500069618225 seconds.

print(grid_search.best_estimator_)

XGBClassifier(base_score=0.5, booster='gbtree', callbacks=None,
colsample_bylevel=1, colsample_bynode=1, colsample_bytree=1,
early_stopping_rounds=None, enable_categorical=False,
eval_metric=None, gamma=0, gpu_id=-1, grow_policy='depthwise',
importance_type=None, interaction_constraints='',
learning_rate=0.1, max_bin=256, max_cat_to_onehot=4,
max_delta_step=0, max_depth=5, max_leaves=0, min_child_weight=6,
missing=nan, monotone_constraints='()', n_estimators=300,
n_jobs=0, num_parallel_tree=1, predictor='auto', random_state=0,
reg_alpha=0, reg_lambda=1, ...)

可以看出,当树最大深度为5,最小子树权重为6,且树的棵树为300时,模型能达到相对最优的效果。

2.2 随机搜索

随机搜索,即在指定超参数范围内或者分布上随机搜寻最优超参数。相较于网格搜索方法,给定超参数分布,并不是所有超参数都会进行尝试,而是会从给定分布中抽样固定数量的参数,实际仅对这些抽样到的超参数进行实验。相较于网格搜索,随机搜索有时候会是更高效的调参方法。

sklearn中通过model_selection模块下的RandomizedSearchCV方法进行随机搜索。

### RandomSearch
from sklearn.model_selection import RandomizedSearchCV
# from scipy.stats import uniform
model = xgb.XGBClassifier()
param_lst = {'max_depth': [3,5,7], 
                 'min_child_weight': [1,3,6], 
                 'n_estimators': [100,200,300],
                 'learning_rate': [0.01, 0.05, 0.1]
                }
t0 = time.time()
random_search = RandomizedSearchCV(model, param_lst, random_state=0)
random_search.fit(X_train, y_train)
print(random_search.best_params_)
print('randomsearch for xgb spend', time.time()-t0, 'seconds.')

{'n_estimators': 300, 'min_child_weight': 6, 'max_depth': 5, 'learning_rate': 0.1}
randomsearch for xgb spend 66.17739844322205 seconds.

2.2 贝叶斯调参

贝叶斯优化是一种基于高斯过程(Gaussian process)和贝叶斯定理的参数优化方法,近年来广泛用于机器学习模型的超参数调优。贝叶斯优化其实跟其他优化方法一样,都是为了求目标函数取最大值时的参数值。作为序列优化问题,贝叶斯优化需要在每次迭代时选取一个最佳观测值,这是贝叶斯优化的关键问题,而这个关键问题正好被上述高斯过程完美解决。

关于贝叶斯优化的大量数学原理,包括高斯过程、采集函数、Upper Confidence Bound(UCB)和Expectation Improvements(EI)等概念原理,可参考相关论文表述。

贝叶斯调参可直接借用现成的第三方库BayesianOptimization来实现。

pip install bayesian-optimization
### Bayesian Opt
from bayes_opt import BayesianOptimization
from tqdm import tqdm
import random
# 定义目标优化函数
def xgb_evaluate(min_child_weight,colsample_bytree,max_depth,subsample,gamma,alpha):
    num_rounds = 3000
    random_state = 2021
    params = {
    'eta': 0.1,
    'silent': 1,
    'eval_metric': 'auc',
    'verbose_eval': True,
    'seed': random_state
    }
    params['min_child_weight'] = int(min_child_weight)
    params['cosample_bytree'] = max(min(colsample_bytree, 1), 0)
    params['max_depth'] = int(max_depth)
    params['subsample'] = max(min(subsample, 1), 0)
    params['gamma'] = max(gamma, 0)
    params['alpha'] = max(alpha, 0)

    # 定义xgb交叉验证结果
    cv_result = xgb.cv(params, dtrain, num_boost_round=num_rounds, nfold=5,
             seed=random_state)

    return cv_result['test-auc-mean'].values[-1]

创建贝叶斯优化实例,并设定参数搜索范围

num_iter = 25
init_points = 5

xgbBO = BayesianOptimization(xgb_evaluate, {'min_child_weight': (1, 10),
                                            'colsample_bytree': (0.1, 0.5),
                                            'max_depth': (1, 10),
                                            'subsample': (0.5, 1),
                                            'gamma': (0, 5),
                                            'alpha': (0, 8),
                                            })
# 执行调优过程
xgbBO.maximize(init_points=init_points, n_iter=num_iter)

可以看到,贝叶斯优化在第13次迭代时达到了最优,当alpha=0.7061,列抽样比为0.3001,gamma参数为1.903,树最大深度为9,最小子树权重为2.257,以及子抽样比例为0.9207时,测试集AUC达到最优的0.7205

iter target alpha colsam... gamma max_depth min_ch... subsample
4 0.7172 2.369 0.3095 2.263 8.802 4.177 0.663
7 0.7193 1.118 0.443 1.844 7.876 3.6 0.7404
13 0.7205 0.7061 0.3001 1.903 9.235 2.257 0.9207
posted @ 2022-08-26 19:38  王陸  阅读(244)  评论(0编辑  收藏  举报