监督学习集成模型——GBDT

一、梯度提升树

提升是一类将弱学习器提升为强学习器的算法总称。

提升树(boosting tree)就是弱学习器为决策树的提升方法。针对提升树模型,加性模型和前向分步算法的组合是典型的求解方式。当损失函数为平方损失和指数损失时,前向分步算法(通过一轮轮的弱学习器学习,利用前一个弱学习器的结果来更新后一个弱学习器的训练集权重。)的每一步迭代都较为容易求解,但如果是一般的损失函数,前向分步算法的每一步迭代并不容易。

使用损失函数的负梯度在当前模型的值来求解更为一般的提升树模型。这种基于负梯度求解提升树前向分步迭代过程的方法也叫梯度提升树(gradient boosting tree)

在AdaBoost中,我们可以使用任意单模型作为弱分类器,但提升树的弱分类器只能是决策树模型。

GBDT的全称为梯度提升决策树(gradient boosting decision tree),其基模型(弱学习器)为CART决策树,针对分类问题基模型为二叉分类树,对应梯度提升模型就叫GBDT;针对回归问题基模型为二叉回归树,对应的梯度提升模型叫作GBRT(gradient boosting regression tree)。

GBDT近年来在一些数据竞赛上大杀四方,并不断衍生出像XGBoost和LightGBM等更强大的版本。

从名字上看,GBDT是由决策树(Regression Decision Tree)、提升模型(Boosting)和梯度下降(Gradient )一起构成的。所以,要搞清楚GBDT的基本原理,就必须对这三者及其相互作用有一个深入的理解。

二、GBDT基本原理

决策树的基本原理我们已经很清楚了,就是依据信息增益等原则不断选择特征构建树模型的过程,具体可参考https://www.cnblogs.com/wkfvawl/p/16587436.html。

Boosting则是一种集成学习模式,通过将多个单个决策树(弱学习器)进行线性组合构成一个强学习器的过程,Boosting以一个单模型作为作为弱分类器,GBDT中使用CART作为这种弱学习器(基模型)。而融入了梯度下降对Boosting树模型进行优化之后就有了梯度提升树模型。

我们先来用一个通俗的说法来理解GBDT。假设某位同学月薪10k,先用一个树模型拟合了6k,发现有4k的损失,然后再用一棵树模型拟合了2k,这样持续拟合下去,拟合值和目标值之间的残差会越来越小,而我们将每一轮迭代,也就是每一棵树的预测值加起来就是模型最终的预测结果。不停的使用单棵决策树组合就是Boosting的过程,使用梯度下降对Boosting树模型进行优化的过程就是Gradient Boosting。

  • GBDT是回归树,不是分类树。对于分类树而言,其值加减无意义(如性别),而对于回归树而言,其值加减才是有意义的(如说年龄)。GBDT 的核心在于累加所有树的结果作为最终结果,所以 GBDT 中的树都是回归树,不是分类树,这一点相当重要。
  • GBDT的核心在于累加所有树的结果作为最终结果
  • GBDT的关键点就是利用损失函数的负梯度去模拟(代替)残差,这样对于一般的损失函数,只要其一阶可导就行。

公式推导

一个提升树模型的数学表达为:

\(f_M(x)=∑_{m=1}^M T(x│Θ_m)\)

其中\(T(x;Θ_m)\)为决策树表示的基模型(决策树不会很深),\(Θ_m\)为决策树参数,\(M\)为决策树棵数。

当确定初始提升树模型\(f_0(x)=0\),第m的模型表示为:

\(f_m(x)=f_{m−1}(x)+T(x;Θ_m)\)

其中\(f_{m−1}(x)\)为当前迭代模型,根据前向分步算法,可以使用经验风险最小化来确定下一棵决策树的参数\(Θ_m\)

\(\widehat{\Theta}_{m}=\underset{\theta_{m}}{\operatorname{argmin}} \sum_{i=1}^{N} L\left(y_{i}, f_{m-1}\left(x_{i}\right)+T\left(x_{i} ; \Theta_{m}\right)\right)\)

以梯度提升回归树为例,一棵回归树可以表示为:

\(T(x;Θ)=∑_{k=1}^K c_k I(x∈R_j)\)

根据加性模型,第0步、第m步和最终模型可以表示为:

\(f_0(x)=0\)

\(f_m(x)=f_{m−1}(x)+T(x;Θ_m)\)

\(f_M(x)=∑_{m=1}^MT(x;Θ_m)\)

在已知\(f_{m−1}(x)\)的情况下,求解前述优化式可得到当前迭代步的模型参数。假设回归树的损失函数为平方损失

\(L(y,f(x))=(y−f(x))^2\)

对应到GBRT中,损失可推导为:

\(L(y,f_{m−1}(x)+T(x;Θ_m))=[y−f_{m−1}(x)−T(x;Θ_m)]^2\)

  • \(y\)真实值
  • \(f_{m−1}(x)\)截止到m-1的预测值
  • T(x;Θ_m)第m棵树的输出

令:\(r=y−f_{m−1}(x)\):表示训练第M棵树时,截止到当前的残差值

有:\(L(y,f_{m−1}(x)+T(x│Θ_m))=[r−T(x│Θ_m)]^2\)

梯度提升树以梯度下降的方法,使用损失函数的负梯度在当前模型的值作为回归提升树中残差的近似值:

\[r_{m i}=-\left[\frac{\partial L\left(y_{i} f\left(x_{i}\right)\right)}{\partial f\left(x_{i}\right)}\right]_{f(x)=f_{m-1}(x)} \]

综合提升树模型、前向分步算法和梯度提升,给定训练数据集\(D={(x_1,y_1),(x_2,y_2),⋯,(x_N,y_N)},x_i∈χ,y_i∈Y⊆R^n\),GBDT算法的一般流程可归纳为如下步骤。

(1)初始化提升树模型:\(f_{0}(x)=\underset{c}{\operatorname{argmin}} \sum_{i=1}^{N} L\left(y_{i}, c\right)\)

(2)对\(m=1, 2,⋯,M\),有:

  • (a)对每个样本\(i=1, 2,⋯,N\),计算负梯度拟合的残差:$$r_{m i}=-\left[\frac{\partial L\left(y_{i} f\left(x_{i}\right)\right)}{\partial f\left(x_{i}\right)}\right]{f(x)=f_{m-1}(x)}$$

  • (b)将上一步得到的残差作为样本新的真实值,并将数据\((x_i,r_mi),i=1, 2,⋯,N\)作为下一棵树的训练数据,得到一棵新的回归树\(f_m(x)\),其对应的叶子结点区域为\(R_{mj},j=1, 2,⋯,J\)。其中J为回归树T的叶子结点的个数。

  • (c)对叶子区域\(j=1, 2,⋯,J\)计算最佳拟合值:\(c_{m j}=\underset{c}{\operatorname{argmin}} \sum_{x_{i} \in R_{m j}} L\left(y_{i}, f_{m-1}\left(x_{i}\right)+c\right)\)

  • (d)更新提升树模型:\(f_m(x)=f_{m−1}(x)+∑_{j=1}^J c_{mj}I(x∈R_{mj})\)

(3)得到最终的梯度提升树:$f(x)=f_M(x)=∑_{m=1}^M ∑_{j=1}^J c_{mj}I(x∈R_{mj}) $

GBDT与 Adaboost 的对比

相同点:都是 Boosting 家族成员,使用弱分类器;都使用前向分布算法;

不同点:

迭代思路不同:Adaboost 是通过提升错分数据点的权重来弥补模型的不足(利用错分样本),而 GBDT 是通过算梯度来弥补模型的不足(利用残差)

损失函数不同:AdaBoost 采用的是指数损失,GBDT 使用的是绝对损失或者 Huber 损失函数;

三、GBDT算法实现

GBDT的基模型为CART,所以定义决策树结点和构建CART树至关重要,CART算法在前面决策树部分已经进行了初步实现。

当基模型构建好后,即可根据GBDT算法流程搭建GBDT和GBRT。除此之外,一些辅助函数的定义(最大熵/Gini指数计算),损失函数定义和模型可视化方法等辅助功能也应该一应俱全。

### GBDT定义
class GBDT(object):
    def __init__(self, n_estimators, learning_rate, min_samples_split,
                 min_gini_impurity, max_depth, regression):
        ### 常用超参数
        # 树的棵树
        self.n_estimators = n_estimators
        # 学习率
        self.learning_rate = learning_rate
        # 结点最小分裂样本数
        self.min_samples_split = min_samples_split
        # 结点最小基尼不纯度
        self.min_gini_impurity = min_gini_impurity
        # 最大深度
        self.max_depth = max_depth
        # 默认为回归树
        self.regression = regression
        # 损失为平方损失
        self.loss = SquareLoss()
        # 如果是分类树,需要定义分类树损失函数
        # 这里省略,如需使用,需自定义分类损失函数
        if not self.regression:
            self.loss = None
        # 多棵树叠加
        self.estimators = []
        for i in range(self.n_estimators):
            self.estimators.append(RegressionTree(min_samples_split=self.min_samples_split,
                                             min_gini_impurity=self.min_gini_impurity,
                                             max_depth=self.max_depth))
    # 拟合方法
    def fit(self, X, y):
        # 前向分步模型初始化,第一棵树
        self.estimators[0].fit(X, y)
        # 第一棵树的预测结果
        y_pred = self.estimators[0].predict(X)
        # 前向分步迭代训练
        for i in range(1, self.n_estimators):
            gradient = self.loss.gradient(y, y_pred)
            self.estimators[i].fit(X, gradient)
            y_pred -= np.multiply(self.learning_rate, self.estimators[i].predict(X))
            
    # 预测方法
    def predict(self, X):
        # 回归树预测
        y_pred = self.estimators[0].predict(X)
        for i in range(1, self.n_estimators):
            y_pred -= np.multiply(self.learning_rate, self.estimators[i].predict(X))
        # 分类树预测
        if not self.regression:
            # 将预测值转化为概率
            y_pred = np.exp(y_pred) / np.expand_dims(np.sum(np.exp(y_pred), axis=1), axis=1)
            # 转化为预测标签
            y_pred = np.argmax(y_pred, axis=1)
        return y_pred

GBDT分类树与回归树

### GBDT分类树
class GBDTClassifier(GBDT):
      def __init__(self, n_estimators=200, learning_rate=.5, min_samples_split=2,
                 min_info_gain=1e-6, max_depth=2):
            super(GBDTClassifier, self).__init__(n_estimators=n_estimators,
                                             learning_rate=learning_rate,
                                             min_samples_split=min_samples_split,
                                             min_gini_impurity=min_info_gain,
                                             max_depth=max_depth,
                                             regression=False)
      # 拟合方法
      def fit(self, X, y):
            super(GBDTClassifier, self).fit(X, y)
        
### GBDT回归树
class GBDTRegressor(GBDT):
      def __init__(self, n_estimators=300, learning_rate=0.1, min_samples_split=2,
                 min_var_reduction=1e-6, max_depth=3):
        super(GBDTRegressor, self).__init__(n_estimators=n_estimators,
                                            learning_rate=learning_rate,
                                            min_samples_split=min_samples_split,
                                            min_gini_impurity=min_var_reduction,
                                            max_depth=max_depth,
                                            regression=True)

定义回归树的平方损失

class SquareLoss():
    # 定义平方损失
    def loss(self, y, y_pred):
        return 0.5 * np.power((y - y_pred), 2)
    # 定义平方损失的梯度
    def gradient(self, y, y_pred):
        return -(y - y_pred)

对波士顿房价数据集进行预测,最后计算评估回归模型的均方误差

# 导入sklearn数据集模块
from sklearn import datasets
# 导入波士顿房价数据集
boston = datasets.load_boston()
# 打乱数据集
X, y = data_shuffle(boston.data, boston.target, seed=13)
X = X.astype(np.float32)
offset = int(X.shape[0] * 0.9)
# 划分数据集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3)
# 创建GBRT实例
model = GBDTRegressor()
# 模型训练
model.fit(X_train, y_train)
# 模型预测
y_pred = model.predict(X_test)
# 计算模型预测的均方误差
mse = mean_squared_error(y_test, y_pred)
print ("Mean Squared Error of NumPy GBRT:", mse)

Mean Squared Error of NumPy GBRT: 84.29078032628252

使用sklearn提供的GBDT算法实现

# 导入sklearn GBDT模块
from sklearn.ensemble import GradientBoostingRegressor
# 创建模型实例
reg = GradientBoostingRegressor(n_estimators=200, learning_rate=0.5,
                                 max_depth=4, random_state=0)
# 模型拟合
reg.fit(X_train, y_train)
# 模型预测
y_pred = reg.predict(X_test)
# 计算模型预测的均方误差
mse = mean_squared_error(y_test, y_pred)
print ("Mean Squared Error of sklearn GBRT:", mse)

Mean Squared Error of sklearn GBRT: 14.885053466425939

posted @ 2022-08-18 21:11  王陸  阅读(211)  评论(0编辑  收藏  举报