本篇博客的大纲:

  1. 导入相关的库/模块
  2. 初始化xgb.XGBRegressor模型的默认参数
  3. 按照顺序,每次选取一个或多个相关参数,给出取值区间,进行GridSearch
  4. 针对网格搜索给出的最佳参数,再划定更详细的区间,将最优参数精细化
  5. 找到所有属性的最佳参数,与原始模型进行结果对比,验证是否提高了模型的精度

一、相关的库/模块

import pandas as pd
import numpy as np
from sklearn.model_selection import KFold, cross_val_score
import xgboost as xgb
from sklearn.model_selection import GridSearchCV

二、xgboost调参开始(注意调参顺序)

  1. 初始化模型默认参数
other_params = {'eta': 0.3, 'n_estimators': 500, 'gamma': 0, 'max_depth': 6, 'min_child_weight': 1,
                'colsample_bytree': 1, 'colsample_bylevel': 1, 'subsample': 1, 'reg_lambda': 1, 'reg_alpha': 0,
                'seed': 33}

    xgboost参数的详细说明,详见官网,我这里简单介绍一下,上面几个参数的含义。

1. eta : 默认是0.3,别名是 leanring_rate,更新过程中用到的收缩步长,在每次提升计算之后,算法会直接获得新特征的权重。 eta通过缩减特征的权重使提升计算过程更加保守;[0,1]
2. gamma:默认是0,别名是 min_split_loss,在节点分裂时,只有在分裂后损失函数的值下降了(达到gamma指定的阈值),才会分裂这个节点。gamma值越大,算法越保守(越不容易过拟合);[0,∞]
3. max_depth:默认是6,树的最大深度,值越大,越容易过拟合;[0,∞]
4. min_child_weight:默认是1,决定最小叶子节点样本权重和,加权和低于这个值时,就不再分裂产生新的叶子节点。当它的值较大时,可以避免模型学习到局部的特殊样本。但如果这个值过高,会导致欠拟合。[0,∞]
5. max_delta_step:默认是0,这参数限制每颗树权重改变的最大步长。如果是 0 意味着没有约束。如果是正值那么这个算法会更保守,通常不需要设置。[0,∞]
6. subsample:默认是1,这个参数控制对于每棵树,随机采样的比例。减小这个参数的值算法会更加保守,避免过拟合。但是这个值设置的过小,它可能会导致欠拟合。 (0,1]
7. colsample_bytree:默认是1,用来控制每颗树随机采样的列数的占比; (0,1]
8. colsample_bylevel:默认是1,用来控制的每一级的每一次分裂,对列数的采样的占比; (0,1]
9. lambda:默认是1,别名是reg_lambda,L2 正则化项的权重系数,越大模型越保守;
10. alpha:默认是0,别名是reg_alpha,L1 正则化项的权重系数,越大模型越保守;
11. seed:随机数种子,相同的种子可以复现随机结果,用于调参!
12. n_estimators:弱学习器的数量
  1. 寻找最佳的 n_estimators
cv_params = {'n_estimators': np.linspace(100, 1000, 10, dtype=int)}
regress_model = xgb.XGBRegressor(**other_params)  # 注意这里的两个 * 号!
gs = GridSearchCV(regress_model, cv_params, verbose=2, refit=True, cv=5, n_jobs=-1)
gs.fit(X, y)  # X为训练数据的特征值,y为训练数据的label
# 性能测评
print("参数的最佳取值::", gs.best_params_)
print("最佳模型得分:", gs.best_score_)

我们这里传入的参数列表是等差数列,[100,200,300,…,1000],输出结果如下:

XGBRegressor 使用 xgbregressor调参_GridSearch


最佳取值是100,那我们进一步查找最佳参数值,再用一个等差数列,[50,60,70,…,150]

cv_params = {'n_estimators': np.linspace(50, 150, 11, dtype=int)}

输出结果如下:

XGBRegressor 使用 xgbregressor调参_xgboost调参_02


那么我们可以考虑决定 n_estimators 的取值为 140,然后修改other_params里n_estimators的值为140,开始调整下一参数;

注意:每次调完一个参数,要把 other_params对应的参数更新为最优值!

  1. 寻找最佳的 max_depth
cv_params = {'max_depth': np.linspace(1, 10, 10, dtype=int)}

传入的参数列表为等差数列[1,2,3,…,10],其他部分不变,

输出结果:

XGBRegressor 使用 xgbregressor调参_回归模型_03


可以看到,max_depth为2,而不是默认的6时,模型的得分有了明显的提高,修改other_params里的max_depth值,继续调参;

  1. 寻找最佳的 min_child_weight
cv_params = {'min_child_weight': np.linspace(1, 10, 10, dtype=int)}

传入的参数列表为等差数列[1,2,3,…,10],其他部分不变,

输出结果:

XGBRegressor 使用 xgbregressor调参_取值_04


发现模型的得分进一步提高,修改other_params里的min_child_weight值,继续调参;

  1. 寻找最佳的gamma值
cv_params = {'gamma': np.linspace(0, 1, 10)}

传入的参数列表为等差数列[0,0.1,0.2,…,1.0],其他部分不变,


输出的最优值是模型的默认值,那么我们进一步细分gamma取值区间,

cv_params = {'gamma': np.linspace(0, 0.1, 11)}

输出结果:

XGBRegressor 使用 xgbregressor调参_xgboost调参_05


可以发现,gamma取0.01时,模型得分进一步提高,如果想要再提高,可以进一步缩小区间,不过我这里怕出现过拟合,还是取0.01即可;

  1. 寻找最佳的subsample值
cv_params = {'subsample': np.linspace(0, 1, 11)}

传入的参数列表为等差数列[0,0.1,0.2,…,1.0],其他部分不变,

输出结果:

XGBRegressor 使用 xgbregressor调参_XGBRegressor 使用_06


输出的最优值是模型的默认值,那我们进一步细分subsample的取值区间:

cv_params = {'subsample': np.linspace(0.9, 1, 11)}

输出结果:

XGBRegressor 使用 xgbregressor调参_XGBRegressor 使用_07


参数最佳取值依旧未变,那我们就用subsample = 1.0 作为最优参数,继续调参;

  1. 寻找最佳的colsample_bytree值
cv_params = {'colsample_bytree': np.linspace(0, 1, 11)[1:]}

colsample_bytree的取值区间为( 0,1],所以不能取0;

输出结果:

XGBRegressor 使用 xgbregressor调参_xgboost调参_08


经验证,参数最佳取值依旧未变,那我们就用默认值作为最优参数,继续调参;

  1. 寻找最佳的reg_lambda
cv_params = {'reg_lambda': np.linspace(0, 100, 11)}

正则项系数 reg_lambda 的值越大,模型越保守!

输出结果:

XGBRegressor 使用 xgbregressor调参_取值_09


进一步细分:

cv_params = {'reg_lambda': np.linspace(40, 60, 11)}

输出结果:

XGBRegressor 使用 xgbregressor调参_XGBRegressor 使用_10


发现模型的得分进一步提高,修改other_params里的 reg_lambda值,继续调参;

  1. 寻找最佳的reg_alpha
cv_params = {'reg_alpha': np.linspace(0, 10, 11)}

输出结果:

XGBRegressor 使用 xgbregressor调参_取值_11


输出最优的reg_alpha为默认值,继续细分:

cv_params = {'reg_alpha': np.linspace(0, 1, 11)}

输出结果:

XGBRegressor 使用 xgbregressor调参_XGBRegressor 使用_12


输出值仍未改变,不妨就取默认值作为最优值,继续调参;

  1. 寻找最佳的 eta 值
    一般这时候要调小学习率来测试,这里用的是一个等比数列;
cv_params = {'eta': np.logspace(-2, 0, 10)}

输出结果:

XGBRegressor 使用 xgbregressor调参_取值_13


这里的,模型得分没有变化,可能是训练数据较少的缘故,暂且用0.01作为学习率,这样一来,我们要调的关键参数基本调试完毕;

三、对比原始模型结果

  1. 定义评分函数
def rmsle_cv(model):
    rmse = np.sqrt(-cross_val_score(model, X, y,
                                    scoring="neg_mean_squared_error", cv=5))
    return rmse

利用sklearn模块自带的评分函数cross_val_score,进行五折交叉验证,开根号取得标准差;

  1. 初始化默认模型和调参之后的模型
# 默认参数
default_xgb = xgb.XGBRegressor()
score = rmsle_cv(default_xgb)
print("default score: {:.4f} ({:.4f})\n".format(score.mean(), score.std()))
# 调参之后
tuned_gbr = xgb.XGBRegressor(learning_rate=0.3, n_estimators=140,
                             max_depth=2, min_child_weight=2,
                             subsample=1, colsample_bytree=1, gamma=0.02, reg_lambda=48)
score = rmsle_cv(tuned_gbr)
print("tuned score: {:.4f} ({:.4f})\n".format(score.mean(), score.std()))
  1. 输出结果对比

    可以发现,误差有了一定的下降;

THE END

有一句话说的很好,“数据和特征决定了机器学习的上限,而模型和算法只是逼近这个上限而已”,xgboost的调参过程基本就是这样,我们想要进一步提升预测的精度,对于数据和特征可能还需要进一步的处理。

参考资料:
XGboost数据比赛实战之调参篇(完整流程)