一、GBDT类库弱学习器参数

参数分为三类 
第一类:Miscellaneous Parameters: Other parameters for overall functioning. 没啥用 
第二类:Boosting Parameters: These affect the boosting operation in the model. 
n_estimators 最大弱学习器的个数,太小欠拟合,太大过拟合 
learning_rate 学习率,太大过拟合,一般很小0.1,和n_estimators一起调 
subsample 子采样,防止过拟合,太小欠拟合。GBDT中是不放回采样 
第三类:Tree-Specific Parameters: These affect each individual tree in the model. 
max_features 最大特征数 
max_depth 最大树深,太大过拟合 
min_samples_split 内部节点再划分所需最小样本数,越大越防过拟合 
min_weight_fraction_leaf 叶子节点最小的样本权重和。如果存在较多样本有缺失值,或者分类树样本的分布类别偏差很大,就会引入样本权重,这时我们就要注意这个值了。越大越防过拟合 
max_leaf_nodes:最大叶子节点数 ,太大过拟合 
min_impurity_split:节点划分最小不纯度 
presort:是否对数据进行预分类,以加快拟合中最佳分裂点的发现。默认False,适用于大数据集。小数据集使用True,可以加快训练。是否预排序,预排序可以加速查找最佳分裂点,对于稀疏数据不管用,Bool,auto:非稀疏数据则预排序,若稀疏数据则不预排序

接下来把调参的整个过程整理一下:

  • 首先使用默认的参数,进行数据拟合;
  • 从步长(learning rate)和迭代次数(n_estimators)入手;一般来说,开始选择一个较小的步长来网格搜索最好的迭代次数。这里,可以将步长初始值设置为0.1。对于迭代次数进行网格搜索;
  • 接下来对决策树的参数进行寻优
  • 首先我们对决策树最大深度max_depth和内部节点再划分所需最小样本数min_samples_split进行网格搜索。【min_samples_split暂时不能一起定下来,因为这个还和决策树其他的参数存在关联】
  • 接着再对内部节点再划分所需最小样本数min_samples_split和叶子节点最少样本数min_samples_leaf一起调参;做到这里,min_samples_split要做两次网格寻优,一次是树的最大深度max_depth,一次是叶子节点最少样本数min_samples_leaf。 【具体观察min_samples_split的值是否落在边界上,如果是可以进一步寻优】
  • 继续对最大特征数max_features进行网格搜索。做完这一步可以看看寻找出的最优参数组合给出的分类器的效果。
  • 可以进一步考虑对子采样的比例进行网格搜索,得到subsample的寻优参数
  • 回归到第2步调整设定的步长(learning rate)和迭代次数(n_estimators),注意两者的乘积保持不变,这里可以分析得到:通过减小步长可以提高泛化能力,但是步长设定过小,也会导致拟合效果反而变差,也就是说,步长不能设置的过小。

二、回归

数据集:已知用户的30个特征,预测用户的信用值

from sklearn.ensemble import GradientBoostingRegressor
from sklearn.grid_search import GridSearchCV #用平均值填补缺失值
gbdt_train_label = train_data['信用分'] gbdt_train_data = train_data[columns_]
gbdt_test_data = test_data[columns_] gbdt_train_data = gbdt_train_data.fillna(gbdt_train_data.mean())
gbdt_test_data = gbdt_test_data.fillna(gbdt_test_data.mean())
#填补6个月平均占比总费用为其他列的平均值
all_rows = list(gbdt_train_data.index)
inf_rows = list(gbdt_train_data.loc[gbdt_train_data['6个月平均占比总费用'] == float('inf')].index)
inf_mean = gbdt_train_data.ix[list(filter(lambda x: x not in inf_rows,all_rows))].mean() gbdt_train_data['6个月平均占比总费用'][gbdt_train_data['6个月平均占比总费用'] == float('inf')] = inf_mean['6个月平均占比总费用']

1. 找树的数量最佳值

#找树的数量最佳值
param_gbdt = {'n_estimators':list(range(100,600,50))}
gbdt_search = GridSearchCV(estimator=GradientBoostingRegressor(learning_rate=0.1,min_samples_split=300,min_samples_leaf=20,
max_depth=8,max_features='sqrt',subsample=0.8,random_state=75),
param_grid=param_gbdt,scoring='neg_mean_squared_error',iid=False,cv=3)
gbdt_search.fit(gbdt_train_data,gbdt_train_label)
print(gbdt_search.grid_scores_)
print(gbdt_search.best_params_)
print(gbdt_search.best_score_)

2.  找树的深度最佳值

#找树的深度最佳值
param_gbdt1 = {'max_depth':[6,7,8,9,10]}
gbdt_search1 = GridSearchCV(estimator=GradientBoostingRegressor(learning_rate=0.1,n_estimators = 200,min_samples_split=300,
min_samples_leaf=20,max_features='sqrt',subsample=0.8,random_state=75),
param_grid=param_gbdt1,scoring='neg_mean_squared_error',iid=False,cv=5)
gbdt_search1.fit(gbdt_train_data,gbdt_train_label)
print(gbdt_search1.grid_scores_)
print(gbdt_search1.best_params_)
print(gbdt_search1.best_score_)

3. 找min_samples_split,min_samples_leaf最佳值

#找min_samples_split,min_samples_leaf最佳值
param_gbdt2 = {'min_samples_split':[500,700,900,1100],
'min_samples_leaf':[30,50,70,90]}
gbdt_search2 = GridSearchCV(estimator=GradientBoostingRegressor(learning_rate=0.1,n_estimators = 100,max_depth=6,
max_features='sqrt',subsample=0.8,random_state=75),n_jobs=3,
param_grid=param_gbdt2,scoring='neg_mean_squared_error',iid=False,cv=5)
gbdt_search2.fit(gbdt_train_data,gbdt_train_label)
print(gbdt_search2.grid_scores_)
print(gbdt_search2.best_params_)
print(gbdt_search2.best_score_)

4. 粗调完成,得到了上述参数的最佳大致范围,接下来,可以进行细调,就是在上述最佳参数附近范围进行搜索,这里就只对n_estimators和学习率进行调整

param_gbdt3 = {'learning_rate':[0.06,0.08,0.1],
'n_estimators':[100,150,200,250]}
gbdt_search3 = GridSearchCV(estimator=GradientBoostingRegressor(min_samples_split=700,min_samples_leaf=70,
max_depth=9,max_features='sqrt',subsample=0.8,random_state=75),n_jobs=3,
param_grid=param_gbdt3,scoring='neg_mean_squared_error',iid=False,cv=5)
gbdt_search3.fit(gbdt_train_data,gbdt_train_label)
print(gbdt_search3.grid_scores_)
print(gbdt_search3.best_params_)
print(gbdt_search3.best_score_)

可以看到,最佳学习率为 0.06,提升树数量为250 ;此时r2提升到0.7907 。  

三、分类

前提:假设数据已经处理完毕,现在开始训练。

modelfit函数包含训练,测试,交叉验证,输出accuracy和AUC,画出重要特征的功能

def modelfit(alg, dtrain, dtest, predictors, performCV=True, printFeatureImportance=True, cv_folds=5):
'''
alg是分类器,dtrain是训练集(包括label),dtes是测试集,predictors是除了label和ID的其他特征,performCV指是否要交叉验证
'''
#训练训练集
alg.fit(dtrain[predictors], dtrain['Disbursed']) #predictors是除了待预测的其他特征,Distursed是目标特征 #预测测试集
dtrain_predictions = alg.predict(dtrain[predictors])
dtrain_predprob = alg.predict_proba(dtrain[predictors])[:,1] #交叉验证
if performCV:
cv_score = cross_validation.cross_val_score(alg, dtrain[predictors], dtrain['Disbursed'], cv=cv_folds, scoring='roc_auc') #输出模型的accuracy、AUC
print("\nModel Report")
print("Accuracy : %.4g" % metrics.accuracy_score(dtrain['Disbursed'].values, dtrain_predictions))
print("AUC Score (Train): %f" % metrics.roc_auc_score(dtrain['Disbursed'], dtrain_predprob)) if performCV:#如果选了要交叉验证,就输出交叉验证的平均值,标准差,最小值,最大值
print("CV Score : Mean - %.7g | Std - %.7g | Min - %.7g | Max - %.7g" % (np.mean(cv_score),np.std(cv_score),np.min(cv_score),np.max(cv_score))) #画出重要特征图像
if printFeatureImportance:
feat_imp = pd.Series(alg.feature_importances_, predictors).sort_values(ascending=False)
feat_imp.plot(kind='bar', title='Feature Importances')
plt.ylabel('Feature Importance Score')

  

接着就要创建一个基线模型(baseline model)。这里我们用AUC来作为衡量标准,所以用常数的话AUC就是0.5。一般来说用默认参数设置的GBM模型就是一个很好的基线模型,我们来看看这个模型的输出和特征重要性:

#Choose all predictors except target & IDcols
predictors = [x for x in train.columns if x not in [target, IDcol]]
gbm0 = GradientBoostingClassifier(random_state=10)
modelfit(gbm0, train, test, predictors,printOOB=False)

从图上看出,CV的平均值是0.8319,后面调整的模型会做得比这个更好

GBDT主要有两种类型的参数:

Tree-specific parameters:

  • min_samples_split
  • min_samples_leaf
  • max_depth
  • min_leaf_nodes
  • max_features
  • loss function

Boosting specific paramters:

  • n_estimators
  • learning_rate
  • subsample

参数调节的一般方法:树参数和boosting参数。learning rate没有什么特别的调节方法,因为只要我们训练的树足够多learning rate总是小值来得好。

虽然随着决定树的增多GBM并不会明显得过度拟合,高learing rate还是会导致这个问题,但如果我们一味地减小learning rate、增多树,计算就会非常昂贵而且需要运行很长时间。了解了这些问题,我们决定采取以下方法调参:

(1)选择一个相对来说稍微高一点的learning rate。一般默认的值是0.1,不过针对不同的问题,0.05到0.2之间都可以

(2)决定当前learning rate下最优的决定树数量。它的值应该在40-70之间。记得选择一个你的电脑还能快速运行的值,因为之后这些树会用来做很多测试和调参。

(3)接着调节树参数来调整learning rate和树的数量。我们可以选择不同的参数来定义一个决定树,后面会有这方面的例子

(4)降低learning rate,同时会增加相应的决定树数量使得模型更加稳健

1. 固定 learning rate和需要估测的决定树数量

  为了决定boosting参数,我们得先设定一些参数的初始值,可以像下面这样:

  • min_ samples_ split=500: 这个值应该在总样本数的0.5-1%之间,由于我们研究的是不均等分类问题,我们可以取这个区间里一个比较小的数,500。
  • min_ samples_ leaf=50: 可以凭感觉选一个合适的数,只要不会造成过度拟合。同样因为不均等分类的原因,这里我们选择一个比较小的值。
  • max_ depth=8: 根据观察数和自变量数,这个值应该在5-8之间。这里我们的数据有87000行,49列,所以我们先选深度为8。
  • max_ features=’sqrt’: 经验上一般都选择平方根。
  • subsample=0.8: 开始的时候一般就用0.8

  注意我们目前定的都是初始值,最终这些参数的值应该是多少还要靠调参决定。现在我们可以根据learning rate的默认值0.1来找到所需要的最佳的决定树数量,可以利用网格搜索(grid search)实现,以10个数递增,从20测到80。

#Choose all predictors except target & IDcols
predictors = [x for x in train.columns if x not in [target, IDcol]]
param_test1 = {'n_estimators':range(20,81,10)}
gsearch1 = GridSearchCV(estimator = GradientBoostingClassifier(learning_rate=0.1, min_samples_split=500,
min_samples_leaf=50,max_depth=8,max_features='sqrt', subsample=0.8,random_state=10),
param_grid = param_test1, scoring='roc_auc',n_jobs=4,iid=False, cv=5)
gsearch1.fit(train[predictors],train[target])

gsearch1.grid_scores_, gsearch1.best_params_, gsearch1.best_score_

  可以看出对于0.1的learning rate, 60个树是最佳的,而且60也是一个合理的决定树数量,所以我们就直接用60。但在一些情况下上面这段代码给出的结果可能不是我们想要的,比如:

  • 如果给出的输出是20,可能就要降低我们的learning rate到0.05,然后再搜索一遍。
  • 如果输出值太高,比如100,因为调节其他参数需要很长时间,这时候可以把learniing rate稍微调高一点。

2. 调节树参数

  树参数可以按照这些步骤调节:

  • 调节max_depth和 num_samples_split
  • 调节min_samples_leaf
  • 调节max_features

  需要注意一下调参顺序,对结果影响最大的参数应该优先调节,就像max_depthnum_samples_split

  重要提示:接着我会做比较久的网格搜索(grid search),可能会花上15-30分钟。你在自己尝试的时候应该根据电脑情况适当调整需要测试的值。

  max_depth可以相隔两个数从5测到15,而min_samples_split可以按相隔200从200测到1000。这些完全凭经验和直觉,如果先测更大的范围再用迭代去缩小范围也是可行的。

#Grid seach on subsample and max_features
param_test2 = {'max_depth':range(5,16,2), 'min_samples_split':range(200,1001,200)}
gsearch2 = GridSearchCV(estimator = GradientBoostingClassifier(learning_rate=0.1, n_estimators=60,
max_features='sqrt', subsample=0.8, random_state=10),
param_grid = param_test2, scoring='roc_auc',n_jobs=4,iid=False, cv=5)
gsearch2.fit(train[predictors],train[target])

gsearch2.grid_scores_, gsearch2.best_params_, gsearch2.best_score_

  从结果可以看出,我们从30种组合中找出最佳的max_depth是9,而最佳的min_smaples_split是1000。1000是我们设定的范围里的最大值,有可能真正的最佳值比1000还要大,所以我们还要继续增加min_smaples_split。树深就用9。接着就来调节min_samples_leaf,可以测30,40,50,60,70这五个值,同时我们也试着调大min_samples_split的值。

#Grid seach on subsample and max_features
param_test3 = {'min_samples_split':range(1000,2100,200), 'min_samples_leaf':range(30,71,10)}
gsearch3 = GridSearchCV(estimator = GradientBoostingClassifier(learning_rate=0.1, n_estimators=60,max_depth=9,
max_features='sqrt', subsample=0.8, random_state=10),
param_grid = param_test3, scoring='roc_auc',n_jobs=4,iid=False, cv=5)
gsearch3.fit(train[predictors],train[target])

  

gsearch3.grid_scores_, gsearch3.best_params_, gsearch3.best_score_

 

这样min_samples_split的最佳值是1200,而min_samples_leaf的最佳值是60。注意现在CV值增加到了0.8396。现在我们就根据这个结果来重新建模,并再次评估特征的重要性。

modelfit(gsearch3.best_estimator_, train, test, predictors)

  比较之前的基线模型结果可以看出,现在我们的模型用了更多的特征,并且基线模型里少数特征的重要性评估值过高,分布偏斜明显,现在分布得更加均匀了。

  接下来就剩下最后的树参数max_features了,可以每隔两个数从7测到19。

#Grid seach on subsample and max_features
param_test4 = {'max_features':range(7,20,2)}
gsearch4 = GridSearchCV(estimator = GradientBoostingClassifier(learning_rate=0.1, n_estimators=60,max_depth=9,
min_samples_split=1200, min_samples_leaf=60, subsample=0.8, random_state=10),
param_grid = param_test4, scoring='roc_auc',n_jobs=4,iid=False, cv=5)
gsearch4.fit(train[predictors],train[target])

gsearch4.grid_scores_, gsearch4.best_params_, gsearch4.best_score_

最佳的结果是7,正好就是我们设定的初始值(平方根)。当然你可能还想测测小于7的值,鼓励这么做。而按照我们的设定,现在的树参数是这样的:

  • min_samples_split: 1200
  • min_samples_leaf: 60
  • max_depth: 9
  • max_features: 7

3. 调节子样本比例来降低learning rate

接下来就可以调节子样本占总样本的比例,我准备尝试这些值:0.6,0.7,0.75,0.8,0.85,0.9。

#Grid seach on subsample and max_features
param_test5 = {'subsample':[0.6,0.7,0.75,0.8,0.85,0.9]}
gsearch5 = GridSearchCV(estimator = GradientBoostingClassifier(learning_rate=0.1, n_estimators=60,max_depth=9,
min_samples_split=1200, min_samples_leaf=60, subsample=0.8, random_state=10, max_features=7),
param_grid = param_test5, scoring='roc_auc',n_jobs=4,iid=False, cv=5)
gsearch5.fit(train[predictors],train[target])

gsearch5.grid_scores_, gsearch5.best_params_, gsearch5.best_score_

  给出的结果是0.85。这样所有的参数都设定好了,现在我们要做的就是进一步减少learning rate,就相应地增加了树的数量。需要注意的是树的个数是被动改变的,可能不是最佳的,但也很合适。随着树个数的增加,找到最佳值和CV的计算量也会加大,为了看出模型执行效率,我还提供了我每个模型在比赛的排行分数(leaderboard score),怎么得到这个数据不是公开的,你很难重现这个数字,它只是为了更好地帮助我们理解模型表现。

  现在我们先把learning rate降一半,至0.05,这样树的个数就相应地加倍到120。

#Choose all predictors except target & IDcols
predictors = [x for x in train.columns if x not in [target, IDcol]]
gbm_tuned_1 = GradientBoostingClassifier(learning_rate=0.05, n_estimators=120,max_depth=9, min_samples_split=1200,
min_samples_leaf=60, subsample=0.85, random_state=10, max_features=7)
modelfit(gbm_tuned_1, train, test, predictors)

  接下来我们把learning rate进一步减小到原值的十分之一,即0.01,相应地,树的个数变为600。

#Choose all predictors except target & IDcols
predictors = [x for x in train.columns if x not in [target, IDcol]]
gbm_tuned_2 = GradientBoostingClassifier(learning_rate=0.01, n_estimators=600,max_depth=9, min_samples_split=1200,
min_samples_leaf=60, subsample=0.85, random_state=10, max_features=7)
modelfit(gbm_tuned_2, train, test, predictors)

  继续把learning rate缩小至二十分之一,即0.005,这时候我们有1200个树。

#Choose all predictors except target & IDcols
predictors = [x for x in train.columns if x not in [target, IDcol]]
gbm_tuned_3 = GradientBoostingClassifier(learning_rate=0.005, n_estimators=1200,max_depth=9, min_samples_split=1200,
min_samples_leaf=60, subsample=0.85, random_state=10, max_features=7,
warm_start=True)
modelfit(gbm_tuned_3, train, test, predictors, performCV=False)

  排行得分稍微降低了,我们停止减少learning rate,只单方面增加树的个数,试试1500个树

#Choose all predictors except target & IDcols
predictors = [x for x in train.columns if x not in [target, IDcol]]
gbm_tuned_4 = GradientBoostingClassifier(learning_rate=0.005, n_estimators=1500,max_depth=9, min_samples_split=1200,
min_samples_leaf=60, subsample=0.85, random_state=10, max_features=7,
warm_start=True)
modelfit(gbm_tuned_4, train, test, predictors, performCV=False)

还有一个技巧就是用“warm_start”选项。这样每次用不同个数的树都不用重新开始

参考文献:

【1】scikit-learn 梯度提升树(GBDT)调参小结

【2】Gradient Boosting Machine(GBM)调参方法详解

【3】https://github.com/aarshayj/Analytics_Vidhya/tree/master/Articles

GBDT调参总结的更多相关文章

  1. scikit-learn 梯度提升树(GBDT)调参小结

    在梯度提升树(GBDT)原理小结中,我们对GBDT的原理做了总结,本文我们就从scikit-learn里GBDT的类库使用方法作一个总结,主要会关注调参中的一些要点. 1. scikit-learn ...

  2. scikit-learn 梯度提升树(GBDT)调参笔记

    在梯度提升树(GBDT)原理小结中,我们对GBDT的原理做了总结,本文我们就从scikit-learn里GBDT的类库使用方法作一个总结,主要会关注调参中的一些要点. 1. scikit-learn ...

  3. gbdt调参的小结

    关键部分转自http://www.cnblogs.com/pinard/p/6143927.html 第一次知道网格搜索这个方法,不知道在工业中是不是用这种方式 1.首先从步长和迭代次数入手,选择一个 ...

  4. GBDT调参

    gbm算法流程图: gbdt 参数:参考scikit-learn The overall parameters can be divided into 3 categories: Tree-Speci ...

  5. scikit-learn随机森林调参小结

    在Bagging与随机森林算法原理小结中,我们对随机森林(Random Forest, 以下简称RF)的原理做了总结.本文就从实践的角度对RF做一个总结.重点讲述scikit-learn中RF的调参注 ...

  6. rf调参小结

    转自http://www.cnblogs.com/pinard/p/6160412.html 1. scikit-learn随机森林类库概述 在scikit-learn中,RF的分类类是RandomF ...

  7. sklearn-GBDT 调参

    1. scikit-learn GBDT类库概述 在sacikit-learn中,GradientBoostingClassifier为GBDT的分类类, 而GradientBoostingRegre ...

  8. xgboost/gbdt在调参时为什么树的深度很少就能达到很高的精度?

    问题: 用xgboost/gbdt在在调参的时候把树的最大深度调成6就有很高的精度了.但是用DecisionTree/RandomForest的时候需要把树的深度调到15或更高.用RandomFore ...

  9. LightGBM 调参方法(具体操作)

     sklearn实战-乳腺癌细胞数据挖掘(博主亲自录制视频) https://study.163.com/course/introduction.htm?courseId=1005269003& ...

随机推荐

  1. ABP之事件总线(4)

    在上一篇的随笔中,我们已经初步完成了EventBus,但是EventBus中还有诸多的问题存在,那么到底有什么问题呢,接下来我们需要看一看ABP中的源码是如何定义EventBus的. 1.第一个点 在 ...

  2. AJAX返回总是ERROR或是没有数据的问题

    如果总是到ERROR,是因为async没有定义为false,设置为同步,数据类型要设置为text,不要用json. 示例: if (IDcard != "") { $.ajax({ ...

  3. java整形数据和浮点型数据

    在定义数据的时候,如果有不是整形的数据(单精度,双精度之类的),那么运算的时候要在其所在式子里的数据后面加上.0,否则会出现(1/2=0.1.0/2=0.5)的现象,比如如下代码 public cla ...

  4. [No0000190]vim8安装教程和vim中文帮助文档Vimcdoc安装方法-Vim使用技巧(5)

    Vim8.0是近十年来的一次大更新,支持任务.异步I/O.Channels和JSON以及异步计时器.Lambdas 和 Closures等,还包括对GTK + 3的支持.由于ubuntu默认安装的Vi ...

  5. URL地址中的#

    1.#的涵义 代表网页中的一个位置.井号后面的字符,就是该位置的标识符.比如, http://www.baidu.com/index.html#one 就代表网页index.html的one位置.浏览 ...

  6. [Day4]Switch语句、数组、二维数组

    1.选择结构switch (1)格式 switch (表达式){ case 目标值1: 执行语句1; break; case 目标值2: 执行语句2; break; ...... case 目标值n: ...

  7. [https][openssl] OpenSSL 公钥、私钥以及自签名证书

    转自:https://www.zybuluo.com/muyanfeixiang/note/392079 简介 公钥私钥用来互相加解密的一对密钥,一般是采用RSA非对称算法.公钥加密的私钥能解密,私钥 ...

  8. python发送邮件 大全汇总

    https://blog.csdn.net/bmxwm/article/details/79007871 参考菜鸟教程发送只有文字的邮件 1 2 3 4 5 6 7 8 9 10 11 12 13 1 ...

  9. delphi string.split 按照任意字符串分割语句

    delphi string.split 按照任意字符串分割语句 1.就是把一个指定的字符串用指定的分割符号分割成多个子串,放入一个 TStringList 中 function ExtractStri ...

  10. EF-CodeFirst-数据迁移

    数据迁移 之前说到Code-First中有不同的数据库初始化策略,如CreateDatabaseIfNotExists,DropCreateDatabaseIfModelChanges和DropCre ...