上手机器学习系列-第4篇-聊聊GBDT

本文介绍了GBDT的基本概念,它是基于决策树的Boosting方法,广泛应用于机器学习。通过sklearn库展示了如何实现GBDT,并探讨了n_estimators、learning_rate和max_depth等关键参数对模型性能的影响,强调了它们之间的平衡调整对于优化模型的重要性。
摘要由CSDN通过智能技术生成

前言

本期我们来聊GBDT,这是一种被广泛使用的集成学习方法。当今在kaggle竞赛中被广泛使用的XGBoost、LightGBM正是以它为基础。因此掌握好该基础算法的原理与编码技巧还是有必要的。

我们知道,集成学习大致可分为Bagging、Boosting两种方法,其对于集成中每个单独学习器的训练过程如下简图所示:
在这里插入图片描述

GBDT顾名思义,也是一种Boosting方法。我们不妨再把它的名字拆开说一下,G表示Gradient(梯度,是一种最优化损失函数的思想),B就是Boosting了,DT表示Decison Tree,代表这种集成学习方法的基学习器是一个决策树。决策树本身可以用于分类以及回归,那么GBDT当然也适用于两个场景了。

接下来我们结合sklearn来实践一下GBDT。

Sklearn + GBDT

在sklearn的官方文档中,GBDT又名Gradient Tree Boosting,它分别有用于分类的类(GradientBoostingClassifier)、用于回归的类(GradientBoostingRegressor)。这里着重来聊分类。我们的方法会是结合代码的使用过程,把相关的理论知识点也带出来。

本节我们继续使用前面讲logistic回归时使用过的一个二分类数据集:
(img)

先把完整的代码贴出来:

from sklearn.datasets import load_breast_cancer
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.model_selection import train_test_split

#导入数据
X,y = load_breast_cancer(return_X_y=True)

#拆分训练集与测试集
X_train,X_test,y_train,y_test = train_test_split(X,y,test_size=0.2,random_state = 0) #test_ratio指定测试集数据量的占比,random_state是为了保证实验可复现,如果不指定数值,下一次运行随机拆分的数据未必就是本次的结果了

#创建一个分类器的实例, 注意用到了哪些参数
clf = GradientBoostingClassifier(n_estimators=50,learning_rate=1.0, max_depth=1, random_state=0)

#拟合数据
clf = clf.fit(X_train, y_train)

#在测试集上预测,并输出误差率
clf.score(X_test, y_test)   # 执行后可得到准确率为98.246%

#还可以看一下F1-score
from sklearn.metrics import f1_score
y_predict = clf.predict(X_test)
print("Micro-F1: %f" % f1_score(y_test,y_predict, average='micro'))
print("Macro-F1: %f" % f1_score(y_test,y_predict, average='macro'))

# 执行后可得到:
# Micro-F1: 0.982456
# Macro-F1: 0.981777

可见短短的几行代码,未经仔细调较的参数,就可以得到98%以上的分类准确率与F1-score,还是相当不错的。下面我们展开来聊聊上面的代码。

假设我们并不清楚GradientBoostingClassifier里面每个参数的理论意义,不妨碍我们做一些实验来看看这些参数对最终结果的影响是什么。

n_estimators代表了该集成学习方法使用了多少个弱学习器,事实上,无论用于分类还是回归场景,GBDT方法使用的弱学习器都是回归树。我们让n_estimators从10过渡到200,同时保持其它参数不同,看一下模型分类准确率的变化:

# 将训练过程封装到一个函数中,便于迭代实验
def tryClassify(n_estimators = 10):
    clf = GradientBoostingClassifier(n_estimators=n_estimators,learning_rate=1.0, max_depth=1, random_state=0)
    clf = clf.fit(X_train, y_train)
    return clf.score(X_test, y_test)

# 多次实验,保存数据
x = [i * 10 for i in range(1,20)]
y = [tryClassify(i) for i in x]

# 画图
import seaborn as sns
sns.lineplot(x,y)

在这里插入图片描述

可见当弱学习器的数量从30过渡到40时,准确率有了明显的提升,但再继续增长时,就稳定在97.5%上下波动了。至少我们可以了解到弱学习器的数量还是不能太少的。

再来看learning_rate,使用类似的代码,可以其它参数不变的情况下,得到下面的图形:
(img)
这就比较有趣了,当学习速率超过1.0后开始出现剧烈波动,超3.0以后稳定在60%(仅仅比随机猜测好一些,实践来说已经不可接受了)。这就有必要了解下这个学习速率的参数到底是怎么参与到训练过程了。

sklearn中GBDT的源代码参见:https://github.com/scikit-learn/scikit-learn/blob/master/sklearn/ensemble/_gb.py。我们看到关于参数的说明中有这样一句描述:

    learning_rate : float, default=0.1
        learning rate shrinks the contribution of each tree by `learning_rate`.
        There is a trade-off between learning_rate and n_estimators.

在一层一层找learning_rate后,可以看到最终在代码中的应用是:

raw_predictions[:, k] += **learning_rate** * tree.predict(X).ravel()

再结合一点理论知识,我们得知这个学习速率是相当于每生成一颗新的弱学习器时,以一定的系数来加到现有的模型中。

其实我们也可能从下图这样的物理方式近似地理解,学习速率就是在原来的预测值上加上一个新值去逼近目标值,一旦加的多了,就容易错过极值点,甚至有可能无法收敛,一直在极值附近动荡(我们可以认为上面准确率曲线随着学习速率上升出现大幅度波动也是这个原因)。同时,如果学习速率过小呢,可能就需要更多的迭代步骤来逼近真实值。这样我们也就能理解为啥说n_estimators与learning_rate是一对需要结合起来优化的参数。
在这里插入图片描述

再来看max_depth,代表了弱学习器(决策回归树)的深度,我们知道对于决策树,树的深度越大,越有可能造成过拟合,所以一般在实践中还需要进行剪纸。对于集成学习而言,弱学习器可以是非常弱的,不要求有很高的精度。从下图我们也看到,弱学习器越复杂,整体的准确率反而有下降。因此建立默认先采用深度为1的树来进行训练。
在这里插入图片描述

其实,我们也可以把树的深度逐渐增加时,训练数据上的准确率也画出来,如下图中的橙色线,可见确实当树的深度增加时,训练集上准确率达到了100%,产生了过拟合的情况。
在这里插入图片描述

random_state比较简单,就是提供了随机数种子,使得多次尝试时生成的随机数序列是可重现的。

我们再来看看源代码中还有哪些值得关注的。

class GradientBoostingClassifier(ClassifierMixin, BaseGradientBoosting)

GradientBoostingClassifier的定义表明它承继了两个父类,那么应该有不少功能是在父类中定义的。果然我们往下读其代码时会发现自身的定义中只有根据自变量X来进行预测,以及打印score值的方法函数。所以它的核心功能使用以下方法调用了父类的过程 super().__init__()。我们查看ClassifierMixin的源码发现它仅实现了一个输出score的功能,所以核心还在BaseGradientBoosting中。
继续看,发现一段核心逻辑:

 tree = DecisionTreeRegressor(
                criterion=self.criterion,
                splitter='best',
                max_depth=self.max_depth,
                min_samples_split=self.min_samples_split,
                min_samples_leaf=self.min_samples_leaf,
                min_weight_fraction_leaf=self.min_weight_fraction_leaf,
                min_impurity_decrease=self.min_impurity_decrease,
                min_impurity_split=self.min_impurity_split,
                max_features=self.max_features,
                max_leaf_nodes=self.max_leaf_nodes,
                random_state=random_state,
                ccp_alpha=self.ccp_alpha)

            if self.subsample < 1.0:
                # no inplace multiplication!
                sample_weight = sample_weight * sample_mask.astype(np.float64)

            X = X_csr if X_csr is not None else X
            tree.fit(X, residual, sample_weight=sample_weight,
                     check_input=False, X_idx_sorted=X_idx_sorted)

            # update tree leaves
            loss.update_terminal_regions(
                tree.tree_, X, y, residual, raw_predictions, sample_weight,
                sample_mask, learning_rate=self.learning_rate, k=k)

从这里可以看到弱学习器被建立了,它是一颗决策回归树。然后通过

residual = loss.negative_gradient(y, raw_predictions_copy, k=k,sample_weight=sample_weight)

来求得梯度反方向。

更多的代码细节可以自行去探索喽。

结语

本篇我们结合sklearn学习了GBDT模型的要点。掌握了这个基础之后,我们在接下来就会逐一聊聊在GBDT上成长起来的三种很又NB又实用的方法:XGBoost、LightGBM、CatBoost。敬请继续关注本公众号推送内容。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值