GBDT学习笔记

Gradient Boost Decision Tree

梯度增强决策树

定义


图片来源知乎
GBDT的含义就是用Gradient Boosting的策略训练出来的DT模型。模型的结果是一组回归分类树组合(CART Tree Ensemble):\(T_1...T_K\) 。其中 \(T_j\) 学习的是之前 \(j-1\)棵树预测结果的残差,这种思想就像准备考试前的复习,先做一遍习题册,然后把做错的题目挑出来,在做一次,然后把做错的题目挑出来在做一次,经过反复多轮训练,取得最好的成绩。知乎

目前我的理解就是:先随机抽取一些样本进行训练,得到一个基分类器,然后再次训练拟合模型的残差。
残差的定义:\(y_{真实}-y_{预测}\),前一个基分类器未能拟合的部分也就是残差,于是新分类器继续拟合,直到残差达到指定的阈值。

基于残差的gradient

gradient是梯度的意思,也可以说是一阶导数
平方损失函数MSE:\(\frac{1}{2} \sum_{0}^{n}\left(y_{i}-F\left(x_{i}\right)\right)^{2}\)
熟悉其他算法的原理应该知道,这个损失函数主要针对回归类型的问题,分类则是用熵值类的损失函数。具体到平方损失函数的式子,你可能已经发现它的一阶导其实就是残差的形式,所以基于残差的GBDT是一种特殊的GBDT模型,它的损失函数是平方损失函数,常用来处理回归类的问题。具体形式可以如下表示:
损失函数:\(L(y, F(x))=\frac{1}{2}(y-F(X))^{2}\)
因此求最小化的\(J=\frac{1}{2}(y-F(X))^{2}\)
哈哈此使可以求一阶导数了
损失函数的一阶导数(梯度):\(\frac{\partial J}{\partial F\left(x_{i}\right)}=\frac{\partial \sum_{i} L\left(y_{i}, F\left(x_{i}\right)\right)}{\partial F\left(x_{i}\right)}=\frac{\partial L\left(y_{i}, F\left(x_{i}\right)\right)}{\partial F\left(x_{i}\right)}=F\left(x_{i}\right)-y_{i}\)
而参数就是负的梯度:\(y_{i}-F\left(x_{i}\right)=-\frac{\partial J}{\partial F\left(x_{i}\right)}\)

评价

基于残差的GBDT在解决回归问题上不算是一个好的选择,一个比较明显的缺点就是对异常值过于敏感。
当存在一个异常值的时候,就会导致残差灰常之大。。自行理解

boosting

gbdt模型可以认为是是由k个基模型组成的一个加法运算式

\(\hat{y}_{i}=\sum_{k=1}^{K} f_{k}\left(x_{i}\right), f_{k} \in F\)

其中F是指所有基模型组成的函数空间
那么一般化的损失函数是预测值 \(\hat{y}_{i}\) 与 真实值\(y_{i}\) 之间的关系,如我们前面的平方损失函数,那么对于n个样本来说,则可以写成
\(L=\sum_{i=1}^{n} l\left(y_{i}, \hat{y}_{i}\right)\)

更一般的,我们知道一个好的模型,在偏差和方差上有一个较好的平衡,而算法的损失函数正是代表了模型的偏差面,最小化损失函数,就相当于最小化模型的偏差,但同时我们也需要兼顾模型的方差,所以目标函数还包括抑制模型复杂度的正则项,因此目标函数可以写成
\(O b j=\sum_{i=1}^{n} l\left(y_{i}, \hat{y}_{i}\right)+\sum_{k=1}^{K} \Omega\left(f_{k}\right)\)
其中 \(\Omega\) 代表了基模型的复杂度,若基模型是树模型,则树的深度、叶子节点数等指标可以反应树的复杂程度。

贪心算法

对于Boosting来说,它采用的是前向优化算法,即从前往后,逐渐建立基模型来优化逼近目标函数,具体过程如下:

\(\hat{y}_{i}^{0}=0\)
\(\hat{y}_{i}^{1}=f_{1}\left(x_{i}\right)=\hat{y}_{i}^{0}+f_{1}\left(x_{i}\right)\)
\(\hat{y}_{i}^{2}=f_{1}\left(x_{i}\right)+f_{2}\left(x_{i}\right)=\hat{y}_{i}^{1}+f_{2}\left(x_{i}\right)\)
\(\cdots\)
\(\hat{y}_{i}^{t}=\sum_{k=1}^{t} f_{k}\left(x_{i}\right)=\hat{y}_{i}^{t-1}+f_{t}\left(x_{i}\right)\)

如何学习一个新模型

关键还是在于GBDT的目标函数上,即新模型的加入总是以优化目标函数为目的的。

以第t步的模型拟合为例,在这一步,模型对第 \(i\)个样本 \(x_i\) 的预测为:
\(\hat{y}_{i}^{t}=\hat{y}_{i}^{t-1}+f_{t}\left(x_{i}\right)\)

其中 \(f_{t}\left(x_{i}\right)\) 就是我们这次需要加入的新模型,即需要拟合的模型,此时,目标函数就可以写成:

\(\begin{aligned} O b j^{(t)} &=\sum_{i=1}^{n} l\left(y_{i}, \hat{y}_{i}^{t}\right)+\sum_{i=i}^{t} \Omega\left(f_{i}\right) \\ &=\sum_{i=1}^{n} l\left(y_{i}, \hat{y}_{i}^{t-1}+f_{t}\left(x_{i}\right)\right)+\Omega\left(f_{t}\right)+\text { constant } \end{aligned}\) (1)
因此当求出最优目标函数的时候也就相当于求出了\(f_{t}\left(x_{i}\right)\)

GBDT的目标函数

这部分也是推导XGBoost的过程

我们知道泰勒公式中,若\(\Delta x\) 很小时,我们只保留二阶导是合理的(GBDT是一阶导,XGBoost是二阶导,我们以二阶导为例,一阶导可以自己去推,因为更简单)或许也可以说我们更希望将优化问题转化为一个凸优化问题,因此而引入二阶泰特展开式,即:
\(f(x+\Delta x) \approx f(x)+f^{\prime}(x) \Delta x+\frac{1}{2} f^{\prime \prime}(x) \Delta x^{2}\) (2)

那么在等式(1)中,我们把 \(\hat{y}_{i}^{t-1}\) 看成是等式(2)中的x, \(f_{t}\left(x_{i}\right)\) 看成是 \(\Delta x\) ,因此等式(1)可以写成:

\(O b j^{(t)}=\sum_{i=1}^{n}\left[l\left(y_{i}, \hat{y}_{i}^{t-1}\right)+g_{i} f_{t}\left(x_{i}\right)+\frac{1}{2} h_{i} f_{t}^{2}\left(x_{i}\right)\right]+\Omega\left(f_{t}\right)+\) constant (3)

其中 \(g_{i}\) 为损失函数的一阶导, \(h_i\) 为损失函数的二阶导,注意这里的导是对 \(\hat{y}_{i}^{t-1}\) 求导。我们以 平方损失函数为例\(\sum_{i=1}^{n}\left(y_{i}-\left(\hat{y}_{i}^{t-1}+f_{t}\left(x_{i}\right)\right)\right)^{2}\) ,则分别给出\(g_i\),\(h_i\)

\(g_{i}=\partial_{\hat{y}^{t-1}}\left(\hat{y}^{t-1}-y_{i}\right)^{2}=2\left(\hat{y}^{t-1}-y_{i}\right), \quad h_{i}=\partial_{\hat{y}^{t-1}}^{2}\left(\hat{y}^{t-1}-y_{i}\right)^{2}=2\)

由于在第t步 \(\hat{y}_{i}^{t-1}\) 其实是一个已知的值,所以 \(l\left(y_{i}, \hat{y}_{i}^{t-1}\right)\) 是一个常数,其对函数优化不会产生影响,因此,等式(3)可以写成:
\(O b j^{(t)} \approx \sum_{i=1}^{n}\left[g_{i} f_{t}\left(x_{i}\right)+\frac{1}{2} h_{i} f_{t}^{2}\left(x_{i}\right)\right]+\Omega\left(f_{t}\right)\) (4)

所以我么只要求出每一步损失函数的一阶和二阶导的值(由于前一步的 \(\hat{y}_{i}^{t-1}\) 是已知的,所以这两个值就是常数)代入等式4,然后最优化目标函数,就可以得到每一步的 \(f(x)\) ,最后根据加法模型得到一个整体模型

如何使用决策树表示目标函数

假设我们boosting的基模型用决策树来实现,则一颗生成好的决策树,即结构确定,也就是说树的叶子结点其实是确定了的。假设这棵树的叶子结点有 \(T\) 片叶子,而每片叶子对应的值 \(w \in R^{T}\) 。熟悉决策树的同学应该清楚,每一片叶子结点中样本的预测值都会是一样的,在分类问题中是某一类在回归问题中,是某一个值(在GBDT中都是回归树,即分类问题转化成对概率的回归了),那么肯定存在这样一个函数\(q:R^d->{1,2,...T}\),即将 \(f_{t}(x)\) 中的每个样本映射到每一个叶子结点上,当然 \(f_{t}(x)\)和 q 我们都是不知道的,但我们也不关心,这里只是说明一下决策树表达数据结构的方法是怎么样的,不理解也没有问题。

下面来正式推导:

\(f_{t}(x)\)可以转化为\(w_{q(x)}\),其中\(q(x)\) 代表了每个样本在哪个叶子结点上,而 \(w_q\) 则代表了哪个叶子结点取什么 \(w\) 值,所以 \(w_{q(x)}\) 就代表了每个样本的取值\(w\) (即预测值.

如果决策树的复杂度可以由正则项来定义 \(\Omega\left(f_{t}\right)=\gamma T+\frac{1}{2} \lambda \sum_{j=1}^{T} w_{j}^{2}\) ,即决策树模型的复杂度由生成的树的叶子节点数量和叶子节点对应的值向量的L2范数决定。

我们假设 \(I_{j}=\left\{i | q\left(x_{i}\right)=j\right\}\) 为第 j 个叶子节点的样本集合,则等式4根据上面的一些变换可以写成:

\(\begin{aligned} O b j^{(t)} & \approx \sum_{i=1}^{n}\left[g_{i} f_{t}\left(x_{i}\right)+\frac{1}{2} h_{i} f_{t}^{2}\left(x_{i}\right)\right]+\Omega\left(f_{t}\right) \\ &=\sum_{i=1}^{n}\left[g_{i} w_{q\left(x_{i}\right)}+\frac{1}{2} h_{i} w_{q\left(x_{i}\right)}^{2}\right]+\gamma T+\frac{1}{2} \lambda \sum_{j=1}^{T} w_{j}^{2} \\ &=\sum_{j=1}^{T}\left[\left(\sum_{i \in I_{j}} g_{i}\right) w_{j}+\frac{1}{2}\left(\sum_{i \in I_{j}} h_{i}+\lambda\right) w_{j}^{2}\right]+\gamma T \end{aligned}\) (5)

即我们之前样本的集合,现在都改写成叶子结点的集合,由于一个叶子结点有多个样本存在,因此

如何优化目标函数

那么对于单棵决策树,一种理想的优化状态就是枚举所有可能的树结构,因此过程如下:

a、首先枚举所有可能的树结构,即 q;

b、计算每种树结构下的目标函数值,即等式7的值;

c、取目标函数最小(大)值为最佳的数结构,根据等式6求得每个叶子节点的 \(w\) 取值,即样本的预测值。

但上面的方法肯定是不可行的,因为树的结构千千万,所以一般用贪心策略来优化:

a、从深度为0的树开始,对每个叶节点枚举所有的可用特征

b、 针对每个特征,把属于该节点的训练样本根据该特征值升序排列,通过线性扫描的方式来决定该特征的最佳分裂点,并记录该特征的最大收益(采用最佳分裂点时的收益)

c、 选择收益最大的特征作为分裂特征,用该特征的最佳分裂点作为分裂位置,把该节点生长出左右两个新的叶节点,并为每个新节点关联对应的样本集

d、回到第1步,递归执行到满足特定条件为止

那么如何计算上面的收益呢,很简单,仍然紧扣目标函数就可以了。假设我们在某一节点上二分裂成两个节点,分别是左(L)右(R),则分列前的目标函数是:\(-\frac{1}{2}\left[\frac{\left(G_{L}+G_{R}\right)^{2}}{H_{L}+H_{R}+\lambda}\right]+\gamma\),分裂后\(-\frac{1}{2}\left[\frac{G_{L}^{2}}{H_{L}+\lambda}+\frac{G_{R}^{2}}{H_{R}+\lambda}\right]+2 \gamma\),则对于目标函数来说,分裂后的收益是(这里假设是最小化目标函数,所以用分裂前-分裂后)
Gain \(=\frac{1}{2}\left[\frac{G_{L}^{2}}{H_{L}+\lambda}+\frac{G_{R}^{2}}{H_{R}+\lambda}-\frac{\left(G_{L}+G_{R}\right)^{2}}{H_{L}+H_{R}+\lambda}\right]-\gamma\)

总结

a、算法在拟合的每一步都新生成一颗决策树;

b、在拟合这棵树之前,需要计算损失函数在每个样本上的一阶导和二阶导,即 \(g_i\)\(h_i\)

c、通过上面的贪心策略生成一颗树,计算每个叶子结点的的 \(G_j\)\(H_j\) ,利用等式6计算预测值 \(w\)

d、把新生成的决策树 \(f_{t}(x)\) 加入 \(\hat{y}_{i}^{t}=\hat{y}_{i}^{t-1}+\epsilon f_{t}\left(x_{i}\right)\) ,其中\(\epsilon\) 为学习率,主要为了抑制模型的过拟合。

【参考知乎机器学习-一文理解GBDT的原理-20171001】
这篇文章的推导思路很清晰,建议多看几遍,虽然很多但是没有废话,慢一点可以理解的。

复现

 1) n_estimators: 也就是弱学习器的最大迭代次数,或者说最大的弱学习器的个数。一般来说n_estimators太小,容易欠拟合,n_estimators太大,又容易过拟合,一般选择一个适中的数值。默认是100。在实际调参的过程中,我们常常将n_estimators和下面介绍的参数learning_rate一起考虑。

 2) learning_rate: 即每个弱学习器的权重缩减系数ν,也称作步长,在原理篇的正则化章节我们也讲到了,加上了正则化项,我们的强学习器的迭代公式为fk(x)=fk−1(x)+νhk(x)。ν的取值范围为0<ν≤1。对于同样的训练集拟合效果,较小的ν意味着我们需要更多的弱学习器的迭代次数。通常我们用步长和迭代最大次数一起来决定算法的拟合效果。所以这两个参数n_estimators和learning_rate要一起调参。一般来说,可以从一个小一点的ν开始调参,默认是1。

 3) subsample: 即我们在原理篇的正则化章节讲到的子采样,取值为(0,1]。注意这里的子采样和随机森林不一样,随机森林使用的是放回抽样,而这里是不放回抽样。如果取值为1,则全部样本都使用,等于没有使用子采样。如果取值小于1,则只有一部分样本会去做GBDT的决策树拟合。选择小于1的比例可以减少方差,即防止过拟合,但是会增加样本拟合的偏差,因此取值不能太低。推荐在[0.5, 0.8]之间,默认是1.0,即不使用子采样。

 4) init: 即我们的初始化的时候的弱学习器,拟合对应原理篇里面的f0(x),如果不输入,则用训练集样本来做样本集的初始化分类回归预测。否则用init参数提供的学习器做初始化分类回归预测。一般用在我们对数据有先验知识,或者之前做过一些拟合的时候,如果没有的话就不用管这个参数了。

 5) loss: 即我们GBDT算法中的损失函数。分类模型和回归模型的损失函数是不一样的。

      对于分类模型,有对数似然损失函数"deviance"和指数损失函数"exponential"两者输入选择。默认是对数似然损失函数"deviance"。在原理篇中对这些分类损失函数有详细的介绍。一般来说,推荐使用默认的"deviance"。它对二元分离和多元分类各自都有比较好的优化。而指数损失函数等于把我们带到了Adaboost算法。

      对于回归模型,有均方差"ls", 绝对损失"lad", Huber损失"huber"和分位数损失“quantile”。默认是均方差"ls"。一般来说,如果数据的噪音点不多,用默认的均方差"ls"比较好。如果是噪音点较多,则推荐用抗噪音的损失函数"huber"。而如果我们需要对训练集进行分段预测的时候,则采用“quantile”。

 6) alpha:这个参数只有GradientBoostingRegressor有,当我们使用Huber损失"huber"和分位数损失“quantile”时,需要指定分位数的值。默认是0.9,如果噪音点较多,可以适当降低这个分位数的值。

GBDT类库弱学习器参数

    这里我们再对GBDT的类库弱学习器的重要参数做一个总结。由于GBDT使用了CART回归决策树,因此它的参数基本来源于决策树类,也就是说,和DecisionTreeClassifier和DecisionTreeRegressor的参数基本类似。如果你已经很熟悉决策树算法的调参,那么这一节基本可以跳过。不熟悉的朋友可以继续看下去。

 1) 划分时考虑的最大特征数max_features: 可以使用很多种类型的值,默认是"None",意味着划分时考虑所有的特征数;如果是"log2"意味着划分时最多考虑log2N个特征;如果是"sqrt"或者"auto"意味着划分时最多考虑N−−√个特征。如果是整数,代表考虑的特征绝对数。如果是浮点数,代表考虑特征百分比,即考虑(百分比xN)取整后的特征数。其中N为样本总特征数。一般来说,如果样本特征数不多,比如小于50,我们用默认的"None"就可以了,如果特征数非常多,我们可以灵活使用刚才描述的其他取值来控制划分时考虑的最大特征数,以控制决策树的生成时间。

 2) 决策树最大深度max_depth: 默认可以不输入,如果不输入的话,默认值是3。一般来说,数据少或者特征少的时候可以不管这个值。如果模型样本量多,特征也多的情况下,推荐限制这个最大深度,具体的取值取决于数据的分布。常用的可以取值10-100之间。

 3) 内部节点再划分所需最小样本数min_samples_split: 这个值限制了子树继续划分的条件,如果某节点的样本数少于min_samples_split,则不会继续再尝试选择最优特征来进行划分。 默认是2.如果样本量不大,不需要管这个值。如果样本量数量级非常大,则推荐增大这个值。

 4) 叶子节点最少样本数min_samples_leaf: 这个值限制了叶子节点最少的样本数,如果某叶子节点数目小于样本数,则会和兄弟节点一起被剪枝。 默认是1,可以输入最少的样本数的整数,或者最少样本数占样本总数的百分比。如果样本量不大,不需要管这个值。如果样本量数量级非常大,则推荐增大这个值。

 5)叶子节点最小的样本权重和min_weight_fraction_leaf:这个值限制了叶子节点所有样本权重和的最小值,如果小于这个值,则会和兄弟节点一起被剪枝。 默认是0,就是不考虑权重问题。一般来说,如果我们有较多样本有缺失值,或者分类树样本的分布类别偏差很大,就会引入样本权重,这时我们就要注意这个值了。

 6) 最大叶子节点数max_leaf_nodes: 通过限制最大叶子节点数,可以防止过拟合,默认是"None”,即不限制最大的叶子节点数。如果加了限制,算法会建立在最大叶子节点数内最优的决策树。如果特征不多,可以不考虑这个值,但是如果特征分成多的话,可以加以限制,具体的值可以通过交叉验证得到。

 7) 节点划分最小不纯度min_impurity_split: 这个值限制了决策树的增长,如果某节点的不纯度(基于基尼系数,均方差)小于这个阈值,则该节点不再生成子节点。即为叶子节点 。一般不推荐改动默认值1e-7。

# Import GradientBoostingRegressor
from sklearn.ensemble import GradientBoostingRegressor

# Instantiate gb
gb = GradientBoostingRegressor(max_depth=4,
                               n_estimators=200,
                               random_state=2)

# Fit gb to the training set
gb.fit(X_train, y_train)

# Predict test set labels
y_pred = gb.predict(X_test)

# Import mean_squared_error as MSE
from sklearn.metrics import mean_squared_error as MSE

# Compute MSE
mse_test = MSE(y_test, y_pred)

# Compute RMSE
rmse_test = mse_test**(1/2)

# Print RMSE
print('Test set RMSE of gb: {:.3f}'.format(rmse_test))

<script.py> output:
    Test set RMSE of gb: 52.065

Stochastic Gradient Boosting

SGB

# Import GradientBoostingRegressor
from sklearn.ensemble import GradientBoostingRegressor

# Instantiate sgbr
sgbr = GradientBoostingRegressor(max_depth=4, 
                                 subsample=0.9,
                                 max_features=0.75,
                                 n_estimators=200,                                
                                 random_state=2)
# Fit sgbr to the training set
sgbr.fit(X_train, y_train)

# Predict test set labels
y_pred = sgbr.predict(X_test)

# Import mean_squared_error as MSE
from sklearn.metrics import mean_squared_error as MSE

# Compute test set MSE
mse_test = MSE(y_test, y_pred)

# Compute test set RMSE
rmse_test = mse_test**(1/2)

# Print rmse_test
print('Test set RMSE of sgbr: {:.3f}'.format(rmse_test))
<script.py> output:
    Test set RMSE of sgbr: 49.979

调参实例

# Define params_dt
params_dt = {
             'max_depth': [2, 3, 4],
             'min_samples_leaf': [0.12, 0.14, 0.16, 0.18]
            }
# Import GridSearchCV
from sklearn.model_selection import GridSearchCV

# Instantiate grid_dt
grid_dt = GridSearchCV(estimator=dt,
                       param_grid=params_dt,
                       scoring='roc_auc',
                       cv=5,
                       n_jobs=-1)
# Import roc_auc_score from sklearn.metrics 
from sklearn.metrics import roc_auc_score

# Extract the best estimator
best_model = grid_dt.best_estimator_

# Predict the test set probabilities of the positive class
y_pred_proba = best_model.predict_proba(X_test)[:,1]

# Compute test_roc_auc
test_roc_auc = roc_auc_score(y_test, y_pred_proba)

# Print test_roc_auc
print('Test set ROC AUC score: {:.3f}'.format(test_roc_auc))

<script.py> output:
    Test set ROC AUC score: 0.610
posted @ 2020-02-23 10:26  高文星星  阅读(668)  评论(0编辑  收藏  举报