sklearn实现GBDT算法(分类)

阿喽哈~小天才们,今天我们聊一聊GBDT

上一篇文章我们详细地说了GBDT算法原理,包括为什么拟合负梯度、负梯度为何可以替代残差、二分类GBDT算法公式和实例演算,感兴趣的童鞋请移步GBDT算法详解&算法实例(分类算法)

具体算法公式啥的这里就不赘述啦,大家就自行学习理解叭,我们今天主要是说如何使用sklearn包来实现GBDT以及简单的调参演示,话不多说上代码~

1、导入各种包

import pandas as pd
import seaborn as sns

from sklearn.model_selection import train_test_split
from sklearn.model_selection import cross_val_score
from sklearn.model_selection import GridSearchCV
from sklearn.preprocessing import OrdinalEncoder
from sklearn.metrics import roc_curve, auc 

from sklearn import tree 
from sklearn.ensemble import AdaBoostClassifier
from sklearn.ensemble import GradientBoostingClassifier

import matplotlib.pyplot as plt
%matplotlib inline
plt.rcParams['font.size'] = 24

2、数据读取

为了方便大家代码复现,本次使用的是python自带的泰坦尼克号数据集,共891个样本,特征涉及性别、年龄、船票价格、是否有同伴等等,标签列有两个,分别是‘survived’和‘alive’,都表示该乘客是否生还,所以我们取一列就可以了

data = sns.load_dataset('titanic')  # 导入泰坦尼克号生还数据
data

3、数据预处理

 首先进行数据的预处理,首先可以看到‘deck’列存在缺失值,我们可以看看总体数据缺失的情况

data.replace(to_replace=r'^\s*$', value=np.nan, regex=True, inplace=True)   # 把各类缺失类型统一改为NaN的形式
data.isnull().mean()

共4列数据存在缺失值,‘deck’缺失率超过70%,予以删除,剩余特征的缺失值使用其均值或是众数进行填补

细心地童鞋可能发现了有好几列重复的特征,‘embarked’和‘embark_town’都表示出发港口,‘sex’、‘who’、‘adult_male’都表示性别,‘pclass’和‘class’都是船票类型,‘sibsp’和‘alone’都表示是否有同伴,对于这几个特征,所以我们保留其中一个就可以了

del data['deck']   # 删除‘deck’列
del data['who']
del data['adult_male']
del data['class']
del data['alone']

data['age'].fillna(np.mean(data.age), inplace=True)   # 年龄特征使用均值对缺失值进行填补
data['embarked'].fillna(data['embarked'].mode(dropna=False)[0], inplace=True)   # 文本型特征视同众数进行缺失值填补

x = data.drop(['alive', 'survived', 'embark_town'], axis=1)   # 取出用于建模的特征列X
label = data['survived']   # 取出标签列Y

sklean中的Adaboost算法是无法进行字符串的处理的,所以要先进行数据编码,这里我们就使用最简单的特征编码,转化完毕后特征全部变为数值型

oe = OrdinalEncoder()   # 定义特征转化函数
 # 把需要转化的特征都写进去
x[['sex', 'embarked']] = oe.fit_transform(x[['sex', 'embarked']])  
x.head()

  

# 划分训练集、测试集
xtrain, xtest, ytrain, ytest = train_test_split(x, label, test_size=0.3)

4、训练模型

"""
sklearn中GBDT封装的所有超参数
class sklearn.ensemble.GradientBoostingClassifier(loss='log_loss', learning_rate=0.1, n_estimators=100, subsample=1.0, 
                                                 criterion='friedman_mse', min_samples_split=2, min_samples_leaf=1, 
                                                 min_weight_fraction_leaf=0.0, max_depth=3, min_impurity_decrease=0.0, init=None, 
                                                 random_state=None, max_features=None, verbose=0, max_leaf_nodes=None, warm_start=False, 
                                                 validation_fraction=0.1, n_iter_no_change=None, tol=0.0001, ccp_alpha=0.0)
"""

 这里同时训练了Adaboost分类模型和GBDT分类模型,用于对比模型效果

abc = AdaBoostClassifier(random_state=37)  # adaboost
abc = abc.fit(xtrain, ytrain)  # 拟合训练集
score_a = abc.score(xtest, ytest)  # 输出测试集准确率

gbdt = GradientBoostingClassifier(random_state=37)  # gbdt
gbdt = gbdt.fit(xtrain, ytrain)  # 拟合训练集
score_g = gbdt.score(xtest, ytest)  # 输出测试集准确率

print("adaboost:{}".format(score_a), " gbdt:{} \n".format(score_g))

从准确率这个指标来看,GBDT略胜一筹,但毕竟是分类问题,我们再使用AUC来评估模型,看一看是否还是GBDT的效果更好

y_test_proba_abc = abc.predict_proba(xtest)
false_positive_rate_abc, recall_abc, thresholds_abc = roc_curve(ytest, y_test_proba_abc[:, 1])  
roc_auc_abc = auc(false_positive_rate_abc, recall_abc)     # adaboost AUC指标

y_test_proba_gbdt = gbdt.predict_proba(xtest)
false_positive_rate_gbdt, recall_gbdt, thresholds_gbdt = roc_curve(ytest, y_test_proba_gbdt[:, 1])  
roc_auc_gbdt = auc(false_positive_rate_gbdt, recall_gbdt)     # gbdt AUC指标

# 画出俩模型对应的ROC曲线
plt.plot(false_positive_rate_abc, recall_abc, color='blue', label='AUC_abc=%0.3f' % roc_auc_abc)
plt.plot(false_positive_rate_gbdt, recall_gbdt, color='orange', label='AUC_gbdt=%0.3f' % roc_auc_gbdt)  
plt.legend(loc='best', fontsize=15, frameon=False)  
plt.plot([0, 1], [0, 1], 'r--')  
plt.xlim([0.0, 1.0])  
plt.ylim([0.0, 1.0])  
plt.ylabel('Recall')  
plt.xlabel('Fall-out')  
plt.show()

  

蓝色实线为adaboost的ROC曲线,黄色实线为gbdt的ROC曲线,从AUC指标评估模型的话,gbdt效果是要更好的,而且AUC指标对于分类模型的评估还是很公平有效的

5、调参

网格搜索类似枚举法,把要调整的参数和参数范围设置完毕之后,它可以进行参数组合,找到模型效果最佳的模型组合,当需要调整的参数较多或参数范围很广时,网格搜索就会非常慢,有利有弊叭,所以一般的调参可以先手动调一调,找到参数大致的最优范围,再通过网格搜索去准确定位最优参数值

这里调参的顺序为:

  • n_estimators
  •  max_depth、min_samples_split
  • min_samples_leaf、min_samples_split
  • max_features、subsample
  • learning_rate

 (1)n_estimators 基学习器数量

首先设定初始的参数值,这里不用过于纠结数值设置地是否科学,从参数一般取值范围里选取就可以了,比如学习率learning_rate的范围一般在[0.01, 0.3]之间,那么我们从这个范围内选取初始学习率,一般是没有问题的

# 通过网格搜索法选择合理的gbdt算法参数
params1 = {'n_estimators': [i for i in range(10, 60, 5)]}
gbdt = GridSearchCV(estimator = GradientBoostingClassifier(learning_rate=0.1, min_samples_split=8, min_samples_leaf=10, max_depth=8, 
                                                           max_features='sqrt', subsample=0.8, random_state=37), 
                    param_grid= params1, scoring = 'roc_auc', cv = 5, n_jobs = 10, verbose = 1)
gbdt.fit(xtrain, ytrain)

print('best_params_:', gbdt.best_params_) # 返回参数的最佳组合和对应AUC值
print('best_score_:', gbdt.best_score_)

为了避免数据划分带来的偶然性,这里使用网格搜索 + 5折交叉验证进行调参,结果显示n_estimators = 30的时候,模型效果最好,5折交叉后的5个模型,auc均值约为0.855 

(2)max_depth 树深、min_samples_split 内部节点再划分所需最小样本数

# 通过网格搜索法选择合理的gbdt算法参数
params2 = {'max_depth': [i for i in range(1, 10, 1)], 
           'min_samples_split': [i for i in range(4, 20, 2)]}
gbdt = GridSearchCV(estimator = GradientBoostingClassifier(learning_rate=0.1, n_estimators=30, min_samples_leaf=10, max_features='sqrt',
                                                           subsample=0.8, random_state=37), 
                    param_grid= params2, scoring = 'roc_auc', cv = 5, n_jobs = 10, verbose = 1)
gbdt.fit(xtrain, ytrain)

print('best_params_:', gbdt.best_params_) # 返回参数的最佳组合和对应AUC值
print('best_score_:', gbdt.best_score_)

 

max_depth = 3、min_samples_split = 4的时候,模型效果最好,5折交叉后的5个模型,auc均值约为0.866,较上一步上升了0.012

(3)min_samples_leaf 叶子节点最少样本数、min_samples_split 内部节点再划分所需最小样本数

细心地同学应该已经发现了,min_samples_split这个超参数上一步已经进行调整了,为什么这里又需要调参。因为上一步是为了选出合适的树深,所以把min_samples_split和max_depth一起调整更合理,同时min_samples_leaf和min_samples_split又息息相关,所以这一步把他俩放在一起进行调参

# 通过网格搜索法选择合理的gbdt算法参数
params2 = {'min_samples_leaf': [i for i in range(1, 50, 2)], 
           'min_samples_split': [i for i in range(4, 20, 2)]}
gbdt = GridSearchCV(estimator = GradientBoostingClassifier(learning_rate=0.1, n_estimators=30, max_depth=3, max_features='sqrt',
                                                           subsample=0.8, random_state=37), 
                    param_grid= params2, scoring = 'roc_auc', cv = 5, n_jobs = 10, verbose = 1)
gbdt.fit(xtrain, ytrain)

print('best_params_:', gbdt.best_params_) # 返回参数的最佳组合和对应AUC值
print('best_score_:', gbdt.best_score_)

 

min_samples_leaf = 5、min_samples_split = 18的时候,模型效果最好,5折交叉后的5个模型,auc均值约为0.868,较上一步上升了0.002

(4)max_features 划分时考虑的最大特征数、subsample 样本抽样比例

# 通过网格搜索法选择合理的gbdt算法参数
params2 = {'max_features': [i for i in range(1, 8, 1)],
           'subsample': [0.6,0.7,0.75,0.8,0.85,0.9]}
gbdt = GridSearchCV(estimator = GradientBoostingClassifier(learning_rate=0.1, n_estimators=30, max_depth=3, min_samples_leaf=5, min_samples_split=18,
                                                           random_state=37), 
                    param_grid= params2, scoring = 'roc_auc', cv = 5, n_jobs = 10, verbose = 1)
gbdt.fit(xtrain, ytrain)

print('best_params_:', gbdt.best_params_) # 返回参数的最佳组合和对应AUC值
print('best_score_:', gbdt.best_score_)

 

因为这里一共7个特征,所以设置最大特征数的时候范围需要注意下,max_features = 5、subsample = 0.8的时候,模型效果最好,5折交叉后的5个模型,auc均值约为0.869,较上一步上升了0.001

(5)learning_rate 学习率

# 通过网格搜索法选择合理的gbdt算法参数
params2 = {'learning_rate': [0.01, 0.05, 0.1, 0.15, 0.2]}
gbdt = GridSearchCV(estimator = GradientBoostingClassifier(n_estimators=30, max_depth=3, max_features=6, min_samples_leaf=5, subsample=0.8, 
                                                           min_samples_split=18, random_state=37), 
                    param_grid= params2, scoring = 'roc_auc', cv = 5, n_jobs = 10, verbose = 1)
gbdt.fit(xtrain, ytrain)

print('best_params_:', gbdt.best_params_) # 返回参数的最佳组合和对应AUC值
print('best_score_:', gbdt.best_score_)

 

 learning_rate = 0.1的时候,模型效果最好,这和我们自己设定的学习率一样,因此5折交叉后的5个模型,auc均值还是0.869

经过上面的调参过程,5折交叉验证的AUC值从原始的0.855提升到0.869,可以看出:

  1. 应该先调整对模型效果影响显著的参数,比如一些防止过拟合的超参数:基学习器数量、树深等;
  2. 随着调参的进行,每一步模型效果的提升是越来越弱的;
  3. 虽然调参可以提升模型效果,但在初始AUC的水平下,提升效果相对来说是微弱的,因此数据质量和特征工程对最终的模型效果仍然起决定性作用,调参只是略微提升了效果,同时减少了过拟合的风险,小伙伴们不能把希望全部寄托在调参上哦

6、最终模型及效果

gbdt = GradientBoostingClassifier(learning_rate=0.1, n_estimators=30, max_depth=6, min_samples_leaf=5, 
                                  subsample=0.8, min_samples_split=18, max_features=6, random_state=37)
gbdt = gbdt.fit(xtrain, ytrain)

y_test_proba_gbdt = gbdt.predict_proba(xtest)
false_positive_rate_gbdt, recall_gbdt, thresholds_gbdt = roc_curve(ytest, y_test_proba_gbdt[:, 1])  
auc(false_positive_rate_gbdt, recall_gbdt)

 

利用调整后的参数组合去拟合训练集,并验证测试集效果,AUC值达到 0.895(调参前为0.878)

7、查看模型各类属性

#特征重要性
feature_name = x.columns

gbdt.feature_importances_
[*zip(feature_name, gbdt.feature_importances_)]

可以看到“sex”的权重是最大的,也就是“游客最终是否生还”和“性别”有很大的关系,这和我们现实中的认知是一致的,因为当时泰坦尼克号遇难时,逃生策略就是让妇女和孩子先撤离,因此遇难的大多数年轻力壮的男士,说到这还是有些感伤o(╥﹏╥)o 

gbdt可以画出决策树,但只能一棵树一棵树地画

plt.figure(figsize = (100,30))  # 设置画图参数
_ = tree.plot_tree(gbdt[0][0])  # 画出第1个基学习器

gbdt.estimators_[0][0].feature_importances_   # 查看第1颗树对应的特征重要性

 

从树的构成可以看出来,第1颗树没有用到第4、第5这两个特征,所以其对应的特征重要性为0

本人才疏学浅,若有理解有误的地方,还请各路大佬批评指正♡♡♡

ok!感恩的心~

  • 17
    点赞
  • 62
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值