NLP(三十九):用tf-idf+xgboost进行文本分类

一、xgboost类库实用小结

 在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原理篇里面的HtjHtj:

Htj=xiRtjhtiHtj=∑xi∈Rtjhti

 

    这个值需要网格搜索寻找最优值,在sklearn GBDT里面,没有完全对应的参数,不过min_samples_split从另一个角度起到了阈值限制。

    3) gamma: XGBoost的决策树分裂所带来的损失减小阈值。也就是我们在尝试树结构分裂时,会尝试最大数下式:

max12G2LHL+λ+12G2RHR+λ12(GL+GR)2HL+HR+λγmax12GL2HL+λ+12GR2HR+λ−12(GL+GR)2HL+HR+λ−γ

 

    这个最大化后的值需要大于我们的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是L2正则化系数,在原理篇里我们讨论了XGBoost的正则化损失项部分:

Ω(ht)=γJ+λ2j=1Jw2tjΩ(ht)=γJ+λ2∑j=1Jwtj2

 

    上面这些参数都是需要调参的,不过一般先调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解决实际问题的朋友们。

二、xgboost类库重要参数

数据比赛Kaggle,天池中最常见的就是XGBoost和LightGBM。

模型是在数据比赛中尤为重要的,但是实际上,在比赛的过程中,大部分朋友在模型上花的时间却是相对较少的,大家都倾向于将宝贵的时间留在特征提取与模型融合这些方面。在实战中,我们会先做一个baseline的demo,尽可能快尽可能多的挖掘出模型的潜力,以便后期将精力花在特征和模型融合上。这里就需要一些调参功底。

本文从这两种模型的一共百余参数中选取重要的十余个进行探讨研究。并给大家展示快速轻量级的调参方式。当然,有更高一步要求的朋友,还是得戳LightGBMXGBoost这两个官方文档链接。

XGBoost 的重要参数

 

XGBoost的参数一共分为三类:

  1. 通用参数:宏观函数控制。

  2. Booster参数:控制每一步的booster(tree/regression)。booster参数一般可以调控模型的效果和计算代价。我们所说的调参,很这是大程度上都是在调整booster参数。

  3. 学习目标参数:控制训练目标的表现。我们对于问题的划分主要体现在学习目标参数上。比如我们要做分类还是回归,做二分类还是多分类,这都是目标参数所提供的。

完整参数请戳官方文档

通用参数

  1. booster:我们有两种参数选择,gbtreegblinear。gbtree是采用树的结构来运行数据,而gblinear是基于线性模型。

  2. silent:静默模式,为1时模型运行不输出。

  3. nthread: 使用线程数,一般我们设置成-1,使用所有线程。如果有需要,我们设置成多少就是用多少线程。

Booster参数

  1. n_estimator: 也作num_boosting_rounds

    这是生成的最大树的数目,也是最大的迭代次数。

  2. learning_rate: 有时也叫作eta,系统默认值为0.3,。

    每一步迭代的步长,很重要。太大了运行准确率不高,太小了运行速度慢。我们一般使用比默认值小一点,0.1左右就很好。

  3. gamma:系统默认为0,我们也常用0

    在节点分裂时,只有分裂后损失函数的值下降了,才会分裂这个节点。gamma指定了节点分裂所需的最小损失函数下降值。 这个参数的值越大,算法越保守。因为gamma值越大的时候,损失函数下降更多才可以分裂节点。所以树生成的时候更不容易分裂节点。范围: [0,∞]

  4. subsample:系统默认为1

    这个参数控制对于每棵树,随机采样的比例。减小这个参数的值,算法会更加保守,避免过拟合。但是,如果这个值设置得过小,它可能会导致欠拟合。 典型值:0.5-10.5代表平均采样,防止过拟合. 范围: (0,1]注意不可取0

  5. colsample_bytree:系统默认值为1。我们一般设置成0.8左右。

    用来控制每棵随机采样的列数的占比(每一列是一个特征)。 典型值:0.5-1范围: (0,1]

  6. colsample_bylevel:默认为1,我们也设置为1.

    这个就相比于前一个更加细致了,它指的是每棵树每次节点分裂的时候列采样的比例

  7. max_depth: 系统默认值为6

    我们常用3-10之间的数字。这个值为树的最大深度。这个值是用来控制过拟合的。max_depth越大,模型学习的更加具体。设置为0代表没有限制,范围: [0,∞]

  8. max_delta_step:默认0,我们常用0.

    这个参数限制了每棵树权重改变的最大步长,如果这个参数的值为0,则意味着没有约束。如果他被赋予了某一个正值,则是这个算法更加保守。通常,这个参数我们不需要设置,但是当个类别的样本极不平衡的时候,这个参数对逻辑回归优化器是很有帮助的。

  9. lambda:也称reg_lambda,默认值为0

    权重的L2正则化项。(和Ridge regression类似)。这个参数是用来控制XGBoost的正则化部分的。这个参数在减少过拟合上很有帮助。

  10. alpha:也称reg_alpha默认为0,

    权重的L1正则化项。(和Lasso regression类似)。 可以应用在很高维度的情况下,使得算法的速度更快。

  11. scale_pos_weight:默认为1

    在各类别样本十分不平衡时,把这个参数设定为一个正值,可以使算法更快收敛。通常可以将其设置为负样本的数目与正样本数目的比值。

学习目标参数

objective [缺省值=reg:linear]

  • 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的向量,其中的值是每个数据分为每个类的概率。

eval_metric [缺省值=通过目标函数选择]

  • 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: 平均正确率

一般来说,我们都会使用xgboost.train(params, dtrain)函数来训练我们的模型。这里的params指的是booster参数。

两种基本的实例

我们要注意的是,在xgboost中想要进行二分类处理的时候,我们仅仅在 objective中设置成 binary,会发现输出仍然是一堆连续的值。这是因为它输出的是模型预测的所有概率中最大的那个值。我们可以后续对这些概率进行条件处理得到最终类别,或者直接调用xgboost中的XGBClassifier()类,但这两种函数的写法不太一样。大家看我下面的例子。

复制代码
from numpy import loadtxt
from xgboost import XGBClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
​
# 导入数据
dataset = loadtxt('pima-indians-diabetes.csv', delimiter=",")
# split data into X and y
X = dataset[:, 0:8]
Y = dataset[:, 8]
# split data into train and test sets
seed = 7
test_size = 0.33
X_train, X_test, y_train, y_test = train_test_split(X, Y, test_size=test_size, random_state=seed)
# 设置参数
model = XGBClassifier(max_depth=15,
                      learning_rate=0.1,
                      n_estimators=2000,
                      min_child_weight=5,
                      max_delta_step=0,
                      subsample=0.8,
                      colsample_bytree=0.7,
                      reg_alpha=0,
                      reg_lambda=0.4,
                      scale_pos_weight=0.8,
                      silent=True,
                      objective='binary:logistic',
                      missing=None,
                      eval_metric='auc',
                      seed=1440,
                      gamma=0)
model.fit(X_train, y_train)
# 进行预测
y_pred = model.predict(X_test)
predictions = [round(value) for value in y_pred]
# 查看准确率
accuracy = accuracy_score(y_test, predictions)
print("Accuracy: %.2f%%" % (accuracy * 100.0))
复制代码

 

以上是xgboost.train()写法,这是xgboost最原始的封装函数。这样训练我们预测输出的是一串连续值,是xgboost在这几个类别上概率最大的概率值。我们如果想要得到我们的分类结果,还需要进行其他操作。

幸运的是,xgboost为了贴合sklearn的使用,比如gridsearch这些实用工具,又开发了XGBoostClassifier()XGBoostRegression()两个函数。可以更加简单快捷的进行分类和回归处理。注意xgboost的sklearn包没有 feature_importance 这个量度,但是get_fscore()函数有相同的功能。当然,为了和sklearn保持一致,写法也发生变化,具体请看下面代码:

复制代码
import xgboost as xgb
from sklearn.model_selection import train_test_split
from sklearn.metrics import roc_auc_score
from sklearn.datasets import load_breast_cancer
# 二分类解决乳腺癌
cancer = load_breast_cancer()
x = cancer.data
y = cancer.target
train_x, valid_x, train_y, valid_y = train_test_split(x, y, test_size=0.333, random_state=0)  # 分训练集和验证集
# 这里不需要Dmatrix
​
xlf = xgb.XGBClassifier(max_depth=10,
                        learning_rate=0.01,
                        n_estimators=2000,
                        silent=True,
                        objective='binary:logistic',
                        nthread=-1,
                        gamma=0,
                        min_child_weight=1,
                        max_delta_step=0,
                        subsample=0.85,
                        colsample_bytree=0.7,
                        colsample_bylevel=1,
                        reg_alpha=0,
                        reg_lambda=1,
                        scale_pos_weight=1,
                        seed=1440,
                        missing=None)
xlf.fit(train_x, train_y, eval_metric='error', verbose=True, eval_set=[(valid_x, valid_y)], early_stopping_rounds=30)
# 这个verbose主要是调节系统输出的,如果设置成10,便是每迭代10次就有输出。
# 注意我们这里eval_metric=‘error’便是准确率。这里面并没有accuracy命名的函数,网上大多例子为auc,我这里特意放了个error。
y_pred = xlf.predict(valid_x, ntree_limit=xlf.best_ntree_limit)
auc_score = roc_auc_score(valid_y, y_pred)
y_pred = xlf.predict(valid_x, ntree_limit=xlf.best_ntree_limit)
# xgboost没有直接使用效果最好的树作为模型的机制,这里采用最大树深限制的方法,目的是获取刚刚early_stopping效果最好的,实测性能可以
auc_score = roc_auc_score(valid_y, y_pred)  # 算一下预测结果的roc值
复制代码

 

那么我们介绍了这么多,重点就来了:如何又快又好的调参?首先我们需要了解grid search是个什么原理。

GridSearch 简介

这是一种调参手段;穷举搜索:在所有候选的参数选择中,通过循环遍历,尝试每一种可能性,表现最好的参数就是最终的结果。其原理就像是在数组里找最大值。(为什么叫网格搜索?以有两个参数的模型为例,参数a有3种可能,参数b有4种可能,把所有可能性列出来,可以表示成一个3*4的表格,其中每个cell就是一个网格,循环过程就像是在每个网格里遍历、搜索,所以叫grid search)

其实这个就跟我们常用的遍历是一样的。建议大家使用sklearn里面的GridSearch函数,简洁速度快。

复制代码
import xgboost as xgb
from sklearn.model_selection import train_test_split
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import GridSearchCV
​
cancer = load_breast_cancer()
x = cancer.data[:50]
y = cancer.target[:50]
train_x, valid_x, train_y, valid_y = train_test_split(x, y, test_size=0.333, random_state=0)  # 分训练集和验证集
# 这里不需要Dmatrix
​
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(max_depth=10,
                        learning_rate=0.01,
                        n_estimators=2000,
                        silent=True,
                        objective='binary:logistic',
                        nthread=-1,
                        gamma=0,
                        min_child_weight=1,
                        max_delta_step=0,
                        subsample=0.85,
                        colsample_bytree=0.7,
                        colsample_bylevel=1,
                        reg_alpha=0,
                        reg_lambda=1,
                        scale_pos_weight=1,
                        seed=1440,
                        missing=None)
​
# 有了gridsearch我们便不需要fit函数
gsearch = GridSearchCV(xlf, param_grid=parameters, scoring='accuracy', cv=3)
gsearch.fit(train_x, train_y)
​
print("Best score: %0.3f" % gsearch.best_score_)
print("Best parameters set:")
best_parameters = gsearch.best_estimator_.get_params()
for param_name in sorted(parameters.keys()):
    print("\t%s: %r" % (param_name, best_parameters[param_name]))
    #极其耗费时间,电脑没执行完
复制代码

 

我们需要注意的是,Grid Search 需要交叉验证支持的。这里的cv=3,是个int数,就代表3-折验证。实际上cv可以是一个对象,也可以是其他类型。分别代表不同的方式验证。具体的大家可看下面这段表述。

Possible inputs for cv are:

  • None, to use the default 3-fold cross-validation,

  • integer, to specify the number of folds.

  • An object to be used as a cross-validation generator.

  • An iterable yielding train/test splits.

    cv的可能输入包括:

  • None,使用默认的3倍交叉验证,

  • 整数,用来指定折叠的次数。

  • 用作交叉验证生成器的对象。

  • 一个可迭代的训练/测试序列进行分割。

三、通过early-stop实现过拟合

本文翻译自Avoid Overfitting By Early Stopping With XGBoost In Python,讲述如何在使用XGBoost建模时通过Early Stop手段来避免过拟合。全文系作者原创,仅供学习参考使用,转载授权请私信联系,否则将视为侵权行为。码字不易,感谢支持。以下为全文内容:


过拟合问题是在使用复杂的非线性学习算法时会经常碰到的,比如gradient boosting算法。

在这篇博客中你将发现如何通过Early Stop方法使得我们在使用Python中的XGBoost模型时可以尽可能地避免过拟合问题:

读完这篇博客后,你将学到:

  • Early Stop可以减少训练集上的过拟合
  • 在使用XGBoost模型时如何监控训练过程中模型的表现,如何绘制学习曲线
  • 如何使用Early Stop方法在模型表现最好的时候停止训练

让我们开始吧。

使用Early Stop避免过拟合

Early Stop是训练复杂机器学习模型以避免其过拟合的一种方法。

它通过监控模型在一个额外的测试集上的表现来工作,当模型在测试集上的表现在连续的若干次(提前指定好的)迭代中都不再提升时它将终止训练过程。

它通过尝试自动选择拐点来避免过拟合,在拐点处,测试数据集的性能开始下降,而训练数据集的性能随着模型开始过拟合而继续改善。

性能的度量可以是训练模型时正在使用的损失函数(例如对数损失),或通常意义上用户感兴趣的外部度量(例如分类精度)。

在XGBoost中监控模型的表现

XGBoost模型在训练时可以计算并输入在某个指定的测试数据集的性能表现。

在调用model.fit()函数时,可以指定测试数据集和评价指标,同时设置verbose参数为True,这样就可以在训练过程中输出模型在测试集的表现。

例如,我们可以通过下面的方法在使用XGBoost训练二分类任务时输出分类错误率(通过“error”指定):

eval_set = [(X_test, y_test)]
model.fit(X_train, y_train, eval_metric="error", eval_set=eval_set, verbose=True)

XGBoost提供了一系列的模型评价指标,包括但不限于:

  • “rmse” 代表均方根误差
  • “mae” 代表平均绝对误差
  • “logloss” 代表二元对数损失
  • “mlogloss” 代表m-元对数损失
  • “error” 代表分类错误率
  • “auc” 代表ROC曲线下面积

完整的列表见XGBoost文档中的“Learning Task Parameters””章节。

例如,我们可以演示如何监控使用UCI机器学习存储库(更新:从这里下载)的关于Pima糖尿病发病数据集的XGBoost模型在训练过程中的性能指标。

完整代码清单如下:

# monitor training performance
from numpy import loadtxt
from xgboost import XGBClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
# load data
dataset = loadtxt('pima-indians-diabetes.csv', delimiter=",")
# split data into X and y
X = dataset[:,0:8]
Y = dataset[:,8]
# split data into train and test sets
X_train, X_test, y_train, y_test = train_test_split(X, Y, test_size=0.33, random_state=7)
# fit model no training data
model = XGBClassifier()
eval_set = [(X_test, y_test)]
model.fit(X_train, y_train, eval_metric="error", eval_set=eval_set, verbose=True)
# make predictions for test data
y_pred = model.predict(X_test)
predictions = [round(value) for value in y_pred]
# evaluate predictions
accuracy = accuracy_score(y_test, predictions)
print("Accuracy: %.2f%%" % (accuracy * 100.0))

运行这段代码将会在67%的数据集上训练模型,并且在每一轮迭代中使用剩下的33%数据来评估模型的性能。

每次迭代都会输出分类错误,最终将会输出最后的分类准确率。

...
[89]    validation_0-error:0.204724
[90]    validation_0-error:0.208661
[91]    validation_0-error:0.208661
[92]    validation_0-error:0.208661
[93]    validation_0-error:0.208661
[94]    validation_0-error:0.208661
[95]    validation_0-error:0.212598
[96]    validation_0-error:0.204724
[97]    validation_0-error:0.212598
[98]    validation_0-error:0.216535
[99]    validation_0-error:0.220472
Accuracy: 77.95%

观察所有的输出,我们可以看到,在训练快要结束时测试集上的模型性能的变化是平缓的,甚至变得更差。

使用学习曲线来评估XGBoost模型

我们可以提取出模型在测试数据集上的表现并绘制成图案,从而更好地洞察到在整个训练过程中学习曲线是如何变化的。

在调用XGBoost模型时我们提供了一个数组,数组的每一项是一个X和y的配对。在测试集之外,我们同时将训练集也作为输入,从而观察在训练过程中模型在训练集和测试集上各自的表现。

例如:

eval_set = [(X_train, y_train), (X_test, y_test)]
model.fit(X_train, y_train, eval_metric="error", eval_set=eval_set, verbose=True)

模型在各个数据集上的表现可以在训练结束后通过model.evals_result()函数获取,这个函数返回一个dict包含了评估数据集的代码和对应的分数列表,例如:

results = model.evals_result()
print(results)

这将输出如下的结果:

{
    'validation_0': {'error': [0.259843, 0.26378, 0.26378, ...]},
    'validation_1': {'error': [0.22179, 0.202335, 0.196498, ...]}
}

“validation_0”和“validation_1”代表了在调用fit()函数时传给eval_set参数的数组中数据集的顺序。

一个特定的结果,比如第一个数据集上的分类错误率,可以通过如下方法获取:

results['validation_0']['error']

另外我们可以指定更多的评价指标,从而同时获取多种评价指标的变化情况。

接着我们可以使用收集到的数据绘制曲线,从而更直观地了解在整个训练过程中模型在训练集和测试集上的表现究竟如何。

下面是一段完整的代码,展示了如何将收集到的数据绘制成学习曲线:

# plot learning curve
from numpy import loadtxt
from xgboost import XGBClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
from matplotlib import pyplot
# load data
dataset = loadtxt('pima-indians-diabetes.csv', delimiter=",")
# split data into X and y
X = dataset[:,0:8]
Y = dataset[:,8]
# split data into train and test sets
X_train, X_test, y_train, y_test = train_test_split(X, Y, test_size=0.33, random_state=7)
# fit model no training data
model = XGBClassifier()
eval_set = [(X_train, y_train), (X_test, y_test)]
model.fit(X_train, y_train, eval_metric=["error", "logloss"], eval_set=eval_set, verbose=True)
# make predictions for test data
y_pred = model.predict(X_test)
predictions = [round(value) for value in y_pred]
# evaluate predictions
accuracy = accuracy_score(y_test, predictions)
print("Accuracy: %.2f%%" % (accuracy * 100.0))
# retrieve performance metrics
results = model.evals_result()
epochs = len(results['validation_0']['error'])
x_axis = range(0, epochs)
# plot log loss
fig, ax = pyplot.subplots()
ax.plot(x_axis, results['validation_0']['logloss'], label='Train')
ax.plot(x_axis, results['validation_1']['logloss'], label='Test')
ax.legend()
pyplot.ylabel('Log Loss')
pyplot.title('XGBoost Log Loss')
pyplot.show()
# plot classification error
fig, ax = pyplot.subplots()
ax.plot(x_axis, results['validation_0']['error'], label='Train')
ax.plot(x_axis, results['validation_1']['error'], label='Test')
ax.legend()
pyplot.ylabel('Classification Error')
pyplot.title('XGBoost Classification Error')
pyplot.show()

运行这段代码将会在每一次训练迭代中输出模型在训练集和测试集上的分类错误率。我们可以通过设置verbose=False来关闭输出。

我们绘制了两张图,第一张图表示的是模型在每一轮迭代中在两个数据集上的对数损失:

 
XGBoost Learning Curve Log Loss

第二张图表示分类错误率:

 
XGBoost Learning Curve Classification Error

从第一张图来看,似乎有机会可以进行Early Stop,大约在20到40轮迭代时比较合适。

从第二张图可以得到相似的结果,大概在40轮迭代时效果比较理想。

在XGBoost中进行Early Stop

XGBoost提供了在指定轮数完成后提前停止训练的功能。

除了提供用于评估每轮迭代中的评价指标和数据集之外,还需要指定一个窗口大小,意味着连续这么多轮迭代中模型的效果没有提升。这是通过early_stopping_rounds参数来设置的。

例如,我们可以像下面这样设置连续10轮中对数损失都没有提升:

eval_set = [(X_test, y_test)]
model.fit(X_train, y_train, early_stopping_rounds=10, eval_metric="logloss", eval_set=eval_set, verbose=True)

如果同时指定了多个评估数据集和多个评价指标,early_stopping_rounds将会使用数组中的最后一个作为依据。

下面提供了一个使用early_stopping_rounds的详细例子:

# early stopping
from numpy import loadtxt
from xgboost import XGBClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
# load data
dataset = loadtxt('pima-indians-diabetes.csv', delimiter=",")
# split data into X and y
X = dataset[:,0:8]
Y = dataset[:,8]
# split data into train and test sets
seed = 7
test_size = 0.33
X_train, X_test, y_train, y_test = train_test_split(X, Y, test_size=test_size, random_state=seed)
# fit model no training data
model = XGBClassifier()
eval_set = [(X_test, y_test)]
model.fit(X_train, y_train, early_stopping_rounds=10, eval_metric="logloss", eval_set=eval_set, verbose=True)
# make predictions for test data
y_pred = model.predict(X_test)
predictions = [round(value) for value in y_pred]
# evaluate predictions
accuracy = accuracy_score(y_test, predictions)
print("Accuracy: %.2f%%" % (accuracy * 100.0))

运行这段代码将得到如下的输出(部分):

...
[35]    validation_0-logloss:0.487962
[36]    validation_0-logloss:0.488218
[37]    validation_0-logloss:0.489582
[38]    validation_0-logloss:0.489334
[39]    validation_0-logloss:0.490969
[40]    validation_0-logloss:0.48978
[41]    validation_0-logloss:0.490704
[42]    validation_0-logloss:0.492369
Stopping. Best iteration:
[32]    validation_0-logloss:0.487297

我们可以看到模型在迭代到42轮时停止了训练,在32轮迭代后观察到了最好的效果。

通常将early_stopping_rounds设置为一个与总训练轮数相关的函数(本例中是10%),或者通过观察学习曲线来设置使得训练过程包含拐点,这两种方法都是不错的选择。

总结

在这篇博客中你发现了如何监控模型的表现以及怎么做Early Stop。

你学会了:

  • 使用Early Stop手段在模型过拟合之前停止训练
  • 在使用XGBoost模型时如何监控模型的表现并绘制出模型的学习曲线
  • 在训练XGBoost模型时如何设置Early Stop参数

关于Early Stop或者这篇博客你还有什么想问的问题吗?欢迎在下方的评论区留言,我将尽我最大的努力来解答。


以上就是本文的全部内容,如果您喜欢这篇文章,欢迎将它分享给朋友们。

感谢您的阅读,祝您生活愉快!

四、代码

1、数据

啊你告诉他一声,好吧,因为他这边目前为止的话,从来也没有接听过银行的电话,因为这边的话多次的话发了短信,多次打的电话,包括邮件的话已经发送给他了。让他看一下手机中信银行盖章发的短信,然后的话让他赶在九点半之前,务必要把它中信银行的支付信用卡账户信息给您核实一下,好吗?    0
啊不是五号,您这个逾期的,这个最早一期逾期的这个部分是您今天要还上的。呃,剩余的这个欠款的。您可以就是讲稍后的话,您这边需要时间的话,我们都可以去给您记录下来,后面可以给您申请的。    0
哦那您现在这个两千两千两百五十块钱的一个资金,现在手上有吗?    0
嗯您这边的话如果说,呃,就是想要处理这个逾期金额的话,因为你之前的话也是一直都是处于处理的。这个一期最低,如果说你想要通过这个两千多块钱来解决这个问题的话,您务必的话就是说,呃,在赶在这个最迟十一点钟之前把它处理进来。我们这边把材料给你保留下来,然后剩下的款项可以给您申请一下可以吧。    0
没有其他事也已经让您申请,让你小时候还一部分了呀先生。    0
对是的啊,但是你不能说这个钱花出去的绿化做处理啊。现在只要你想换个一千三百九十块钱呀,先生。    0
三中信信用卡的业务该怎么处理呢?我该怎么处理?可能是银行信贷资金呀先生。    0
那你不是第一天知道你对信用卡逾期呀!    0

2、训练部分

import pandas as pd
import os
from config.root_path import root
from sklearn.feature_extraction.text import TfidfTransformer,TfidfVectorizer
import xgboost
import pickle
from sklearn import metrics
class XgbModel():
    def __init__(self, strategy, train_tf=False, train_x = False):
        self.train_path = os.path.join(root, "chinese_classification", "datas", strategy, "data", "train.txt")
        self.dev_path = os.path.join(root, "chinese_classification", "datas", strategy, "data", "dev.txt")
        self.test_path = os.path.join(root, "chinese_classification", "datas", strategy, "data", "test.txt")
        self.class_path = os.path.join(root, "chinese_classification", "datas", strategy, "data", "class.txt")
        self.param = {'silent': 0, 'eta': 0.3, 'max_depth': 6, 'objective': 'multi:softmax', 'num_class': 2,
                 'eval_metric': 'mlogloss'}
        self.tfidf_model = os.path.join(root, "xgboost_model", "chkpt", "tfidf.pkl")
        self._model = os.path.join(root, "xgboost_model", "chkpt",strategy, "xgboost.pkl")
        self.train_tf= train_tf
        self.train_x = train_x

    def clean_data(self):
        seg = lambda s: " ".join(s)
        cw = lambda s:int(s)
        train_df = pd.read_csv(self.train_path, sep="\t", header=None, names=["s", "l"])
        train_x = train_df["s"].apply(seg)
        train_y = train_df["l"].apply(cw)
        test_df = pd.read_csv(self.test_path, sep="\t", header=None, names=["s", "l"])
        test_x = test_df["s"].apply(seg)
        test_y = test_df["l"].apply(cw)
        dev_df = pd.read_csv(self.dev_path, sep="\t", header=None, names=["s", "l"])
        dev_x = dev_df["s"].apply(seg)
        dev_y = dev_df["l"].apply(cw)
        return train_x,train_y,dev_x,dev_y,test_x,test_y

    def train_tfidf(self, corpus):
        tfidf_vectorizer = TfidfVectorizer(max_features=5000, analyzer ="char",lowercase=False)
        feature = tfidf_vectorizer.fit_transform(corpus)
        with open(self.tfidf_model, 'wb') as f:
            pickle.dump(tfidf_vectorizer, f)

    def get_tfidf(self, corpus):
        with open(self.tfidf_model, 'rb') as f:
            tfidf_vectorizer = pickle.load(f)
        res = tfidf_vectorizer.transform(corpus).toarray()
        return res

    def train_xgb(self,train_weight,train_y,dev_weight,dev_y):
        dtrain = xgboost.DMatrix(train_weight, label=train_y)
        ddev = xgboost.DMatrix(dev_weight, label=dev_y)
        evallist = [(dtrain, 'train'),(ddev, 'dev')]
        num_round = 100  # 循环次数
        my_eval_result = {}
        xgb_model = xgboost.train(self.param, dtrain, num_round, evallist,
                                  early_stopping_rounds=10, evals_result=my_eval_result)
        xgb_model.save_model(self._model)
        print(my_eval_result)

    def xgb_predict(self, test_weight, test_y):
        xgb_model = xgboost.Booster({'nthread': 4})  # init model
        xgb_model.load_model(self._model)  # load data
        dtest = xgboost.DMatrix(test_weight, label=test_y)
        y_predict = xgb_model.predict(dtest, iteration_range=(0, xgb_model.best_iteration + 1))  # 模型预测
        label_all = []
        with open(self.class_path, "r", encoding="utf8") as f:
            for line in f.readlines():
                line = line.strip()
                label_all.append(line)
        confusion_mat = metrics.confusion_matrix(test_y, y_predict)
        df = pd.DataFrame(confusion_mat, columns=label_all)
        df.index = label_all
        print('准确率:', metrics.accuracy_score(test_y, y_predict))
        print('分类报告:', metrics.classification_report(test_y, y_predict))

    def train_tfidf_xgb(self):
        train_x,train_y,dev_x,dev_y,test_x,test_y = self.clean_data()
        if self.train_tf:
            self.train_tfidf(train_x)
        if self.train_x:
            train_weight = self.get_tfidf(train_x)
            dev_weight = self.get_tfidf(dev_x)
            self.train_xgb(train_weight,train_y,dev_weight,dev_y)
        test_weight = self.get_tfidf(test_x)
        self.xgb_predict(test_weight, test_y)

def train_model():
    strategy_list = [
        "gaodiqiu",
        "gongqing",
        "huhui",
        "kuangjia",
        "mianzhi",
        "sanmingzhi",
        "zanmei",
        "zijiaren"
    ]
    for s_list in strategy_list:
        m = XgbModel(s_list, False, True)
        m.train_tfidf_xgb()
        break

if __name__ == '__main__':
    train_model()

3、预测部分

import xgboost
import pickle
import os
from config.root_path import root

class XgbPrediction(object):
    def __init__(self, strategy):
        self.tfidf_model = os.path.join(root, "xgboost_model", "chkpt", "tfidf.pkl")
        self._model = os.path.join(root, "xgboost_model", "chkpt",strategy, "xgboost.pkl")
        with open(self.tfidf_model, 'rb') as f:
            self.tfidf_vectorizer = pickle.load(f)
        self.xgb_model = xgboost.Booster({'nthread': 4})  # init model
        self.xgb_model.load_model(self._model)  # load data
        self.cat_list = []
        with open(os.path.join(root, "chinese_classification", "datas",strategy,"data", "class.txt"),
                  "r", encoding="utf8") as f:
            for line in f.readlines():
                self.cat_list.append(line.strip())
        self.id_label = dict(zip(range(len(self.cat_list)), self.cat_list))
        self.id_to_label = lambda x:self.id_label[x]

    def predict(self, sentence):
        content = [" ".join(sentence)]
        res = self.tfidf_vectorizer.transform(content).toarray()
        dtest = xgboost.DMatrix(res)
        y_predict = self.xgb_model.predict(dtest).astype("int")[0]  # 模型预测
        result = self.id_to_label(y_predict)
        return result

    def multi_predict(self, sentence_list):
        contents = [" ".join(sentence) for sentence in sentence_list]
        res = self.tfidf_vectorizer.transform(contents).toarray()
        dtest = xgboost.DMatrix(res)
        y_predict = self.xgb_model.predict(dtest).astype("int")
        result = list(map(self.id_to_label, y_predict))
        return result

if __name__ == '__main__':
    XgbPrediction("gongqing").predict("我爱中国")

 

posted @ 2021-11-15 10:39  jasonzhangxianrong  阅读(1787)  评论(0编辑  收藏  举报