草庐IT

sklearn实现GBDT算法(分类)

入锅的小麻圆 2023-04-08 原文

阿喽哈~小天才们,今天我们聊一聊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!感恩的心~

有关sklearn实现GBDT算法(分类)的更多相关文章

  1. ruby - 如何根据特征实现 FactoryGirl 的条件行为 - 2

    我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden

  2. 区块链之加解密算法&数字证书 - 2

    目录一.加解密算法数字签名对称加密DES(DataEncryptionStandard)3DES(TripleDES)AES(AdvancedEncryptionStandard)RSA加密法DSA(DigitalSignatureAlgorithm)ECC(EllipticCurvesCryptography)非对称加密签名与加密过程非对称加密的应用对称加密与非对称加密的结合二.数字证书图解一.加解密算法加密简单而言就是通过一种算法将明文信息转换成密文信息,信息的的接收方能够通过密钥对密文信息进行解密获得明文信息的过程。根据加解密的密钥是否相同,算法可以分为对称加密、非对称加密、对称加密和非

  3. 华为OD机试用Python实现 -【明明的随机数】 2023Q1A - 2

    华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o

  4. 基于C#实现简易绘图工具【100010177】 - 2

    C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.

  5. MIMO-OFDM无线通信技术及MATLAB实现(1)无线信道:传播和衰落 - 2

     MIMO技术的优缺点优点通过下面三个增益来总体概括:阵列增益。阵列增益是指由于接收机通过对接收信号的相干合并而活得的平均SNR的提高。在发射机不知道信道信息的情况下,MIMO系统可以获得的阵列增益与接收天线数成正比复用增益。在采用空间复用方案的MIMO系统中,可以获得复用增益,即信道容量成倍增加。信道容量的增加与min(Nt,Nr)成正比分集增益。在采用空间分集方案的MIMO系统中,可以获得分集增益,即可靠性性能的改善。分集增益用独立衰落支路数来描述,即分集指数。在使用了空时编码的MIMO系统中,由于接收天线或发射天线之间的间距较远,可认为它们各自的大尺度衰落是相互独立的,因此分布式MIMO

  6. 【Java入门】使用Java实现文件夹的遍历 - 2

    遍历文件夹我们通常是使用递归进行操作,这种方式比较简单,也比较容易理解。本文为大家介绍另一种不使用递归的方式,由于没有使用递归,只用到了循环和集合,所以效率更高一些!一、使用递归遍历文件夹整体思路1、使用File封装初始目录,2、打印这个目录3、获取这个目录下所有的子文件和子目录的数组。4、遍历这个数组,取出每个File对象4-1、如果File是否是一个文件,打印4-2、否则就是一个目录,递归调用代码实现publicclassSearchFile{publicstaticvoidmain(String[]args){//初始目录Filedir=newFile("d:/Dev");Datebeg

  7. ruby - Arrays Sets 和 SortedSets 在 Ruby 中是如何实现的 - 2

    通常,数组被实现为内存块,集合被实现为HashMap,有序集合被实现为跳跃列表。在Ruby中也是如此吗?我正在尝试从性能和内存占用方面评估Ruby中不同容器的使用情况 最佳答案 数组是Ruby核心库的一部分。每个Ruby实现都有自己的数组实现。Ruby语言规范只规定了Ruby数组的行为,并没有规定任何特定的实现策略。它甚至没有指定任何会强制或至少建议特定实现策略的性能约束。然而,大多数Rubyist对数组的性能特征有一些期望,这会迫使不符合它们的实现变得默默无闻,因为实际上没有人会使用它:插入、前置或追加以及删除元素的最坏情况步骤复

  8. ruby - "public/protected/private"方法是如何实现的,我该如何模拟它? - 2

    在ruby中,你可以这样做:classThingpublicdeff1puts"f1"endprivatedeff2puts"f2"endpublicdeff3puts"f3"endprivatedeff4puts"f4"endend现在f1和f3是公共(public)的,f2和f4是私有(private)的。内部发生了什么,允许您调用一个类方法,然后更改方法定义?我怎样才能实现相同的功能(表面上是创建我自己的java之类的注释)例如...classThingfundeff1puts"hey"endnotfundeff2puts"hey"endendfun和notfun将更改以下函数定

  9. ruby - 实现k最近邻需要哪些数据? - 2

    我目前有一个reddit克隆类型的网站。我正在尝试根据我的用户之前喜欢的帖子推荐帖子。看起来K最近邻或k均值是执行此操作的最佳方法。我似乎无法理解如何实际实现它。我看过一些数学公式(例如k表示维基百科页面),但它们对我来说并没有真正意义。有人可以推荐一些伪代码,或者可以查看的地方,以便我更好地了解如何执行此操作吗? 最佳答案 K最近邻(又名KNN)是一种分类算法。基本上,您采用包含N个项目的训练组并对它们进行分类。如何对它们进行分类完全取决于您的数据,以及您认为该数据的重要分类特征是什么。在您的示例中,这可能是帖子类别、谁发布了该项

  10. ruby-on-rails - 使用 Ruby 正确处理 Stripe 错误和异常以实现一次性收费 - 2

    我查看了Stripedocumentationonerrors,但我仍然无法正确处理/重定向这些错误。基本上无论发生什么,我都希望他们返回到edit操作(通过edit_profile_path)并向他们显示一条消息(无论成功与否)。我在edit操作上有一个表单,它可以POST到update操作。使用有效的信用卡可以正常工作(费用在Stripe仪表板中)。我正在使用Stripe.js。classExtrasController5000,#amountincents:currency=>"usd",:card=>token,:description=>current_user.email)

随机推荐