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
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参数的详细说明,详见官网,我这里简单介绍一下,上面几个参数的含义。
- eta : 默认是0.3,别名是 leanring_rate,更新过程中用到的收缩步长,在每次提升计算之后,算法会直接获得新特征的权重。 eta通过缩减特征的权重使提升计算过程更加保守;[0,1]
- gamma:默认是0,别名是 min_split_loss,在节点分裂时,只有在分裂后损失函数的值下降了(达到gamma指定的阈值),才会分裂这个节点。gamma值越大,算法越保守(越不容易过拟合);[0,∞]
- max_depth:默认是6,树的最大深度,值越大,越容易过拟合;[0,∞]
- min_child_weight:默认是1,决定最小叶子节点样本权重和,加权和低于这个值时,就不再分裂产生新的叶子节点。当它的值较大时,可以避免模型学习到局部的特殊样本。但如果这个值过高,会导致欠拟合。[0,∞]
- max_delta_step:默认是0,这参数限制每颗树权重改变的最大步长。如果是 0 意味着没有约束。如果是正值那么这个算法会更保守,通常不需要设置。[0,∞]
- subsample:默认是1,这个参数控制对于每棵树,随机采样的比例。减小这个参数的值算法会更加保守,避免过拟合。但是这个值设置的过小,它可能会导致欠拟合。 (0,1]
- colsample_bytree:默认是1,用来控制每颗树随机采样的列数的占比; (0,1]
- colsample_bylevel:默认是1,用来控制的每一级的每一次分裂,对列数的采样的占比; (0,1]
- lambda:默认是1,别名是reg_lambda,L2 正则化项的权重系数,越大模型越保守;
- alpha:默认是0,别名是reg_alpha,L1 正则化项的权重系数,越大模型越保守;
- seed:随机数种子,相同的种子可以复现随机结果,用于调参!
- 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],输出结果如下:
最佳取值是100,那我们进一步查找最佳参数值,再用一个等差数列,[50,60,70,…,150]
cv_params = {'n_estimators': np.linspace(50, 150, 11, dtype=int)}
输出结果如下:
那么我们可以考虑决定 n_estimators 的取值为 140,然后修改other_params里n_estimators的值为140,开始调整下一参数;
注意:每次调完一个参数,要把 other_params对应的参数更新为最优值!
cv_params = {'max_depth': np.linspace(1, 10, 10, dtype=int)}
传入的参数列表为等差数列[1,2,3,…,10],其他部分不变,
输出结果:
可以看到,max_depth为2,而不是默认的6时,模型的得分有了明显的提高,修改other_params里的max_depth值,继续调参;
cv_params = {'min_child_weight': np.linspace(1, 10, 10, dtype=int)}
传入的参数列表为等差数列[1,2,3,…,10],其他部分不变,
输出结果:
发现模型的得分进一步提高,修改other_params里的min_child_weight值,继续调参;
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)}
输出结果:
可以发现,gamma取0.01时,模型得分进一步提高,如果想要再提高,可以进一步缩小区间,不过我这里怕出现过拟合,还是取0.01即可;
cv_params = {'subsample': np.linspace(0, 1, 11)}
传入的参数列表为等差数列[0,0.1,0.2,…,1.0],其他部分不变,
输出结果:
输出的最优值是模型的默认值,那我们进一步细分subsample的取值区间:
cv_params = {'subsample': np.linspace(0.9, 1, 11)}
输出结果:
参数最佳取值依旧未变,那我们就用subsample = 1.0 作为最优参数,继续调参;
cv_params = {'colsample_bytree': np.linspace(0, 1, 11)[1:]}
colsample_bytree的取值区间为( 0,1],所以不能取0;
输出结果:
经验证,参数最佳取值依旧未变,那我们就用默认值作为最优参数,继续调参;
cv_params = {'reg_lambda': np.linspace(0, 100, 11)}
正则项系数 reg_lambda 的值越大,模型越保守!
输出结果:
进一步细分:
cv_params = {'reg_lambda': np.linspace(40, 60, 11)}
输出结果:
发现模型的得分进一步提高,修改other_params里的 reg_lambda值,继续调参;
cv_params = {'reg_alpha': np.linspace(0, 10, 11)}
输出结果:
输出最优的reg_alpha为默认值,继续细分:
cv_params = {'reg_alpha': np.linspace(0, 1, 11)}
输出结果:
输出值仍未改变,不妨就取默认值作为最优值,继续调参;
cv_params = {'eta': np.logspace(-2, 0, 10)}
输出结果:
这里的,模型得分没有变化,可能是训练数据较少的缘故,暂且用0.01作为学习率,这样一来,我们要调的关键参数基本调试完毕;
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,进行五折交叉验证,开根号取得标准差;
# 默认参数
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()))
有一句话说的很好,“数据和特征决定了机器学习的上限,而模型和算法只是逼近这个上限而已”,xgboost的调参过程基本就是这样,我们想要进一步提升预测的精度,对于数据和特征可能还需要进一步的处理。
参考资料:
XGboost数据比赛实战之调参篇(完整流程)