模型调优

XGBoost有一些参数可以显著影响模型的准确性和训练速度。 应该了解的第一个参数是:n_estimators and early_stopping_roundsn_estimators 指定训练循环次数。

early_stopping_rounds 提供了一种自动查找理想值的方法。 early_stopping_rounds会导致模型在validation score停止改善时停止迭代,即使迭代次数还没有到n_estimators。为n_estimators设置一个高值然后使用early_stopping_rounds来找到停止迭代的最佳时间是明智的。

存在随机的情况有时会导致validation score无法改善,因此需要指定一个数字,以确定在停止前允许多少轮退化。early_stopping_rounds = 5是一个合理的值。 因此,在五轮validation score无法改善之后训练将停止。 以下是early_stopping的代码:

my_model = XGBRegressor(n_estimators=1000)
my_model.fit(train_X, train_y, early_stopping_rounds=5, 
             eval_set=[(test_X, test_y)], verbose=False)
predictions = my_model.predict(test_X)

from sklearn.metrics import mean_absolute_error
print("Mean Absolute Error : " + str(mean_absolute_error(predictions, test_y)))

当使用early_stopping_rounds时,需要留出一些数据来检查要使用的轮数。 如果以后想要使所有数据拟合模型,请将n_estimators设置为在早期停止运行时发现的最佳值。

learning_rate

对于更好的XGBoost模型,这是一个微妙但重要的技巧:

XGBoost模型不是通过简单地将每个组件模型中的预测相加来获得预测,而是在将它们添加之前将每个模型的预测乘以一个小数字。这意味着我们添加到集合中的每个树都不会对最后结果有决定性的影响。在实践中,这降低了模型过度拟合的倾向。

因此,使用一个较大的n_estimators值并不会造成过拟合。如果使用early_stopping_rounds,树的数量会被设置成一个合适的值。

通常,较小的learning rate(以及大量的estimators)将产生更准确的XGBoost模型,但是由于它在整个循环中进行更多迭代,因此也将使模型更长时间进行训练。 包含学习率的代码如下:

my_model = XGBRegressor(n_estimators=1000, learning_rate=0.05)
my_model.fit(train_X, train_y, early_stopping_rounds=5, 
             eval_set=[(test_X, test_y)], verbose=False)
predictions = my_model.predict(test_X)

from sklearn.metrics import mean_absolute_error
print("Mean Absolute Error : " + str(mean_absolute_error(predictions, test_y)))

实战

实战基于数据集 AllstateClaimsSeverity (Kaggle2016竞赛) :

官网:https://www.kaggle.com/c/allstate-claims-severity/overview

基于给出的数据预测保险赔偿。给出的训练数据是116列(cat1-cat116)的离散数据和14列(con1-con14)的连续数据。

import pandas as pd 
import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import roc_auc_score as AUC
from sklearn.metrics import mean_absolute_error
from sklearn.decomposition import PCA
from sklearn.preprocessing import LabelEncoder,LabelBinarizer
from sklearn.model_selection  import cross_val_score

from scipy import stats
import seaborn as sns
from copy import deepcopy

%matplotlib inline

%config InlineBackend.figure_format = 'retina'

加载数据

train = pd.read_csv('allstate-claims-severity/train.csv') 
test = pd.read_csv('allstate-claims-severity/test.csv') 

观察数据,看看数据是啥样的

train.shape # (188318, 132)

输出训练数据,查看数据内容

train

图片说明

print('First 20 columns:',list(train.columns[:20]))
print('Last 20 columns:',list(train.columns[-20:]))

First 20 columns: ['id', 'cat1', 'cat2', 'cat3', 'cat4', 'cat5', 'cat6', 'cat7', 'cat8', 'cat9', 'cat10', 'cat11', 'cat12', 'cat13', 'cat14', 'cat15', 'cat16', 'cat17', 'cat18', 'cat19']
Last 20 columns: ['cat112', 'cat113', 'cat114', 'cat115', 'cat116', 'cont1', 'cont2', 'cont3', 'cont4', 'cont5', 'cont6', 'cont7', 'cont8', 'cont9', 'cont10', 'cont11', 'cont12', 'cont13', 'cont14', 'loss']

观察得到:一共有 object 类型属性 116 个,float64 属性15个,int64 属性 1 个,其中 id 是int64,loss 赔偿是 float64.

train.describe()

图片说明

查看缺失值

大多情况,我们都需要对数据进行缺失值处理。

pd.isnull(train).values.any()# False 表示没有缺失值
连续值与离散值

train.info()

#类型以及数量:float64(15), int64(1), object(116)

<class 'pandas.core.frame.DataFrame'>
RangeIndex: 188318 entries, 0 to 188317
Columns: 132 entries, id to loss
dtypes: float64(15), int64(1), object(116)
memory usage: 189.7+ MB

查看离散特征和连续特征个数

cat_features = list(train.select_dtypes(include=['object']))
print('离散特征Categorical: {} features'.format(len(cat_features)))
离散特征Categorical: 116 features
cont_features = [cont for cont in list(train.select_dtypes(include=['float64','int64'])) if cont not in ['loss','id']]
print('连续特征Continuous: {} features'.format(len(cont_features)))

连续特征Continuous: 14 features

id_col = list(train.select_dtypes(include=['int64']))
print('A column of int64:{}'.format(id_col))

类别值中属性的个数
#统计类别属性中不同类别的个数
cat_uniques=[]
for cat in cat_features:
cat_uniques.append(len(train[cat].unique()))
uniq_values_in_categories = pd.DataFrame.from_dict([('cat_name',cat_features),('unique_values',cat_uniques)])

uniq_values_in_categories.head()

赔偿值

plt.figure(figsize=(16,8))
plt.plot(train['id'],train['loss'])
print('train[\'id\']个数:',len(train['id']))
plt.title('Loss values per id')
plt.xlabel('id')
plt.ylabel('loss')
plt.legend()
plt.show()

图片说明

如上图所示,损失值有几个显著的峰值,表示严重事故。这样的数据分布,使得这个功能非常扭曲导致回归表现不佳。

基本上,偏度 度量了实值随机变量的均值分布的不对称性,下面让我们来计算一下loss的偏度:

#scipy.stats 统计指标。
stats.mstats.skew(train['loss']).data
#输出:array(3.79492815)
偏度值比1大,说明数据是倾斜的。不利于数据建模。我们利用对数变换np.log,使倾斜降低。

stats.mstats.skew(np.log(train['loss'])).data
#输出:array(0.0929738)

两种 loss 分布对比:

fig, (ax1, ax2) = plt.subplots(1,2)
fig.set_size_inches(16,5)
ax1.hist(train['loss'], bins=50)
ax1.set_title('Train Loss target histogram')
ax1.grid(True)
ax2.hist(np.log(train['loss']), bins=50, color='g')
ax2.set_title("Train Log Loss target histogram")
ax2.grid(True)
plt.show()

图片说明

特征之间的相关性

plt.subplots(figsize=(16,9))
correlation_mat = train[cont_features].corr()
sns.heatmap(correlation_mat,annot=True)

图片说明

XGBoost 调参策略

导入依赖

import pandas as pd 
import numpy as np
import xgboost as xgb
import pickle
import sys
import matplotlib.pyplot as plt
from sklearn.metrics import make_scorer
from sklearn.metrics import mean_absolute_error
from sklearn.preprocessing import LabelEncoder,LabelBinarizer
from sklearn.model_selection  import cross_val_score
from sklearn.model_selection import KFold,train_test_split

from xgboost import XGBRegressor

import warnings
warnings.filterwarnings('ignore')
%matplotlib inline

%config InlineBackend.figure_format = 'retina'

数据预处理

train = pd.read_csv('allstate-claims-severity/train.csv')
train['log_loss']=np.log(train['loss'])

features = [x for x in train.columns if x not in ['id','loss','log_loss']]

cat_features =[x for x in train.select_dtypes(include = ['object']) if x not in ['id','loss','log_loss']]

num_features =[x for x in train.select_dtypes(exclude = ['object']) if x not in ['id','loss','log_loss']]           

print('离散特征Categorical: {} features'.format(len(cat_features)))
print('Numerical:{} features'.format(len(num_features)))
features

离散特征 Categorical: 116 features
Numerical:14 features
['cat1',
 'cat2',
 'cat3',
 'cat4',
 'cat5',
 'cat6',
 'cat7',
 'cat8',
 'cat9',
 'cat10',
 'cat11',
 'cat12',
 'cat13',
 'cat14',
 'cat15',
 'cat16',
 'cat17',
 'cat18',
 'cat19',
 'cat20',
 'cat21',
 'cat22',
 'cat23',
 'cat24',
 'cat25',
 'cat26',
 'cat27',
 'cat28',
 'cat29',
 'cat30',
 'cat31',
 'cat32',
 'cat33',
 'cat34',
 'cat35',
 'cat36',
 'cat37',
 'cat38',
 'cat39',
 'cat40',
 'cat41',
 'cat42',
 'cat43',
 'cat44',
 'cat45',
 'cat46',
 'cat47',
 'cat48',
 'cat49',
 'cat50',
 'cat51',
 'cat52',
 'cat53',
 'cat54',
 'cat55',
 'cat56',
 'cat57',
 'cat58',
 'cat59',
 'cat60',
 'cat61',
 'cat62',
 'cat63',
 'cat64',
 'cat65',
 'cat66',
 'cat67',
 'cat68',
 'cat69',
 'cat70',
 'cat71',
 'cat72',
 'cat73',
 'cat74',
 'cat75',
 'cat76',
 'cat77',
 'cat78',
 'cat79',
 'cat80',
 'cat81',
 'cat82',
 'cat83',
 'cat84',
 'cat85',
 'cat86',
 'cat87',
 'cat88',
 'cat89',
 'cat90',
 'cat91',
 'cat92',
 'cat93',
 'cat94',
 'cat95',
 'cat96',
 'cat97',
 'cat98',
 'cat99',
 'cat100',
 'cat101',
 'cat102',
 'cat103',
 'cat104',
 'cat105',
 'cat106',
 'cat107',
 'cat108',
 'cat109',
 'cat110',
 'cat111',
 'cat112',
 'cat113',
 'cat114',
 'cat115',
 'cat116',
 'cont1',
 'cont2',
 'cont3',
 'cont4',
 'cont5',
 'cont6',
 'cont7',
 'cont8',
 'cont9',
 'cont10',
 'cont11',
 'cont12',
 'cont13',
 'cont14']

ntrain = train.shape[0]
#ntrain = 188318

train_x = train[features]
train_y = train['log_loss']

for c in range(len(cat_features)):
    train_x[cat_features[c]] = train_x[cat_features[c]].astype('category').cat.codes
print('Xtrain:',train_x.shape) # Xtrain: (188318, 130)
print('ytrain:',train_y.shape) # ytrain: (188318,)

train_x

图片说明

注:.cat.codes

我有一个名为language的数据框
         lang          level
0      english         intermediate
1      spanish         intermediate
2      spanish         basic
3      english         basic
4      english         advanced
5      spanish         intermediate
6      spanish         basic
7      spanish         advanced
我使用
language.lang.astype('category').cat.codes
以及
language.level.astype('category').cat.codes
分别是。获取以下数据帧:
      lang   level
0      0       1
1      1       1
2      1       0
3      0       0
4      0       2
5      1       1
6      1       0
7      1       2

Simple XGBoost Model

首先,我们训练一个基本的xgboost模型,然后进行参数调节通过交叉验证来观察结果的变换,使用平均绝对误差衡量 mean_absolute_error(np.exp(y),np.exp(yhat))。

xgboost 自定义一个数据矩阵类 DMatrix,会在训练开始时,进行一边预处理,从而提高之后每次迭代的效率。

结果衡量方法

#评估策略,e的次幂,用来评估。
#结果衡量方法:使用平均绝对误差来衡量
#mean_absolute_error(np.exp(y), np.exp(yhat))。
#定义计算损失值的函数
def xg_eval_mae(yhat,dtrain):
    y = dtrain.get_label()
    return 'mae',mean_absolute_error(np.exp(y),np.exp(yhat))

XGBoost 参数调节

Step 1: 选择一组初始参数 Step 2: 改变 max_depth 和 min_child_weight. Step 3: 调节 gamma 降低模型过拟合风险. Step 4: 调节 subsample 和 colsample_bytree 改变数据采样策略. Step 5: 调节学习率 eta.

class XGBoostRegressor(object):
    def __init__(self, **kwargs):
        self.params = kwargs
        if 'num_boost_round' in self.params:
            self.num_boost_round = self.params['num_boost_round']
        self.params.update({'silent': 1, 'objective': 'reg:linear', 'seed': 0})#默认参数

    def fit(self, x_train, y_train):
        '''
        #数据类型转换,#用参数去训练xgboost模型
        '''
        dtrain = xgb.DMatrix(x_train, y_train) 
        self.bst = xgb.train(params=self.params, dtrain=dtrain, num_boost_round=self.num_boost_round,
                             feval=xg_eval_mae, maximize=False)

    def predict(self, x_pred):
        dpred = xgb.DMatrix(x_pred)
        self.bst = xgb.train(params=self.params, dtrain=dtrain, num_boost_round=self.num_boost_round,
                             feval=xg_eval_mae, maximize=False)
        return self.bst.predict(dpred)

    def kfold(self, x_train, y_train, nfold=5):
        dtrain = xgb.DMatrix(x_train, y_train)
        cv_rounds = xgb.cv(params=self.params, dtrain=dtrain, num_boost_round=self.num_boost_round,
                           nfold=nfold, feval=xg_eval_mae, maximize=False, early_stopping_rounds=10)
        return cv_rounds.iloc[-1,:]

    def plot_feature_importances(self):
        feat_imp = pd.Series(self.bst.get_fscore()).sort_values(ascending=False)
        feat_imp.plot(title='Feature Importances')
        plt.ylabel('Feature Importance Score')

    def get_params(self, deep=True):
        return self.params

    def set_params(self, **params):
        self.params.update(params)
        return self 

#衡量标准
def mae_score(y_true, y_pred):
    return mean_absolute_error(np.exp(y_true), np.exp(y_pred))

mae_scorer = make_scorer(mae_score, greater_is_better=False)

bst = XGBoostRegressor(eta=0.1, colsample_bytree=0.5, subsample=0.5, 
                       max_depth=5, min_child_weight=3, num_boost_round=50)

bst.kfold(train_x, train_y, nfold=5)

train-rmse-mean       0.558938
train-rmse-std        0.001005
test-rmse-mean        0.562665
test-rmse-std         0.002445
train-mae-mean     1209.707324
train-mae-std         3.004207
test-mae-mean      1218.884204
test-mae-std          8.982969
Name: 49, dtype: float64