前向分步算法与提升树模型

本篇博客主要来说明前向分步算法以及通过前向分步算法构造的提升树模型。

首先,我们假设某一模型公式具有如下形式:

f(x)=\sum_{m=1}^{M}\beta_mb(x;\gamma_m)

其中我们称b(x;\gamma_m)为基函数,\beta_m为此基函数的系数,而\gamma_m为基函数的相关参数,我们称这样的模型f(x)为加法模型;我们通过使用这样一个模型来进行回归于分类任务。

然而,这样一个模型改如何来构造呢?我们采用一种叫做前向分步算法的方法来构造这么一个模型。

首先我们给定损失函数如下所示:

L(y, f(x))

那我们的任务就是优化的损失函数如下所示:

\underset{\beta_m,\gamma_m}{min}\sum_{i=1}^{N}L(y_i, \sum_{m=1}^{M}\beta_mb(x_i;\gamma_m))

然而这是一个复杂的优化问题,对其直接求解往往过于复杂,因此这里采用前向分步算法从前向后逐个求出基函数b(x;\gamma_m)及其系数\beta_m,也就是说我们每次只需优化如下损失函数即可求出一组\betab(x;\gamma)

\underset{\beta,\gamma}{min}\sum_{i=1}^{N}L(y_i, \beta b(x_i;\gamma))

李航的统计学习方法书中写到这里用y_i,但是个人认为这里的y_i并非是样本真实标记,其只代表一个数值,表明了每次只需优化有这这种形式的损失函数,到后面可以看出,这个y_i实际上代表残差,即样本真实值和当前模型预测值之差。

接下来给出前向分步算法的具体操作步骤:

假设存在训练数据集T=\left\{(x_1, y_1), (x_2, y_2), ..., (x_N, y_N) \right \}x_i\in \chi \subseteq R^ny_i\in \left\{+1, -1\right\};损失函数为L(y, f(x));并且我们还知道基函数集\left\{b(x, \gamma)\right\},我们不知道每一个基函数的参数,我们需要通过前向分步算法将参数求解出来

第一步:初始化模型f_0(x)=0

第二步:对于m=1, 2, ..., M:

  • 2.1 极小化如下损失:(\beta_m, \gamma_m)=arg\;\underset{\beta_m,\gamma_m}{min}\sum_{i=1}^{N}L(y_i, f_{m-1}(x_i) + \beta_m b(x_i;\gamma_m)),其中f_{m-1}(x_i)=\sum_{j=1}^{m-1}\beta_jb(x_i;\gamma_j)为当前模型对于样本点x_i的预测值,我们记做\widehat{y}_{m-1}^i,也就是对(\beta_m, \gamma_m)=arg\;\underset{\beta_m,\gamma_m}{min}\sum_{i=1}^{N}L(y_i, \widehat{y}_{m-1}^i + \beta_m b(x_i;\gamma_m))进行优化;实际上我们是要让\widehat{y}_{m-1}^i + \beta_m b(x_i;\gamma_m)逼近真实值y_i,即\widehat{y}_{m-1}^i + \beta_m b(x_i;\gamma_m)\rightarrow y_i,而\widehat{y}_{m-1}^i为已知量,因此将这个逼近问题转化为\beta_m b(x_i;\gamma_m)\rightarrow y_i-\widehat{y}_{m-1}^i,所以\beta_m b(x_i;\gamma_m)是对当前模型预测残差的一个拟合
  • 2.2 更新当前模型:f_{m}(x)=f_{m-1}(x)+\beta_mb(x;\gamma_m)
  • 循环执行2.1和2.2知道构造完所有的M个基函数

第三步:构造最终分类器:f(x)=f_M(x)=\sum_{m=1}^{M}\beta_mb(x;\gamma_m)

以上便是前向分步算法的具体步骤及公式

前向分步算法中的损失函数如果是针对分类问题我们往往采取指数损失L(y, f(x))=exp(-yf(x));而如果是回归问题,我们通常采用平方误差损失L(x, f(x))=(y-f(x))^2;其实Adaboost算法得到的模型实质上与前向分步算法采用指数损失得到的加法模型等价。

下面我们介绍提升树模型:

以决策树为基函数,进行前向分步算法得到的模型就是提升树模型,这种以决策树为基函数的提升方法成为提升树(boosting tree)。如果我们最终要构造的时分类模型,我们采用二叉分类树作为基函数;如果要构造回归模型,我们采用二叉回归树作为基函数。

提升树模型如果用来解决分类问题,我们只需将前向分布算法中的损失函数用指数损失,基函数用二叉分类树即可得到提升树模型;实质上我们在Adaboost算法中将基分类器限制为二叉分类树也可以得到提升树模型,与前者是等价的,因此提升树算法是Adaboost算法的一种特殊情况。

而提升树模型如果用来解决回归问题,我们需要将前向分布算法中的损失函数设置为平方误差损失,基函数使用二叉回归树即可得到解决回归问题的提升树模型。

因为解决分类问题就是Adaboost算法,只不过基分类器采用了二叉分类树,Adaboost前面已经介绍过了,所以这里就不介绍了,这里我们只介绍用于解决回归问题的提升树模型,这里还是采用前向分步算法:

第一步:确定初始提升树:f_0(x)=0

第二步:确定第m颗二叉回归树T(x;\Theta_m),其中\Theta_m表示这棵树的参数:

首先可以确定的是,通过前向分步算法我们可以通过解决这样一个优化问题\Theta_m=arg\;\underset{\Theta_m}{min}\sum_{i=1}^{N}L(y_i, f_{m-1}(x_i)+T(x_i;\Theta_m))来求出第m颗树。但我们进行进一步分析,由于我们这里采用的是平方误差损失,故L(y_i, f_{m-1}(x_i)+T(x_i;\Theta_m))=(y_i- f_{m-1}(x_i)-T(x_i;\Theta_m))^2,而y_i- f_{m-1}(x_i)实际上就是当前模型f_{m-1}(x)对于第i个样本预测值与真实值的残差,我们记作r_{m-1}^i=y_i- f_{m-1}(x_i),因此损失函数便成为了L(y_i, f_{m-1}(x_i)+T(x_i;\Theta_m))=(r_{m-1}^i-T(x_i;\Theta_m))^2,可以看出实际上T(x;\Theta_m)是用来拟合当前模型的残差r_{m-1}^i=y_i- f_{m-1}(x_i)的。

所以第m颗树的求取我们可以通过:

  • 2.1 计算残差r_{m-1}^i=y_i- f_{m-1}(x_i)
  • 2.2 优化:\Theta_m=arg\;\underset{\Theta_m}{min}\sum_{i=1}^{N}L(r_{m-1}^i, T(x_i;\Theta_m))=arg\;\underset{\Theta_m}{min}\sum_{i=1}^{N}(r_{m-1}^i- T(x_i;\Theta_m))^2
  • 得到第m颗树T(x;\Theta_m),更新f_m(x)=f_{m-1}(x)+T(x;\Theta_m)

第三步:得到回归问题的提升树模型:f_M(x)=\sum_{m=1}^{M}T(x;\Theta_m)

到这里提升树以及前向分步算法页介绍完毕了,下面附上提升树相关程序,数据为波士顿房价数据:

from sklearn.preprocessing import StandardScaler
import pandas as pd
import numpy as np
from numpy import random as rd
from sklearn.datasets import load_boston
from copy import copy
np.set_printoptions(linewidth=1000, suppress=True)


class Foward(object):
    
    def __init__(self, n):
        # self.base_funcs用于存放基函数
        self.base_funcs = []
        x = load_boston()["data"]
        y = load_boston()["target"]
        self.x_train, self.x_test, self.y_train, self.y_test = train_test_split(x, y, test_size=0.3, random_state=3)
        stand_transfer = StandardScaler()
        self.x_train = stand_transfer.fit_transform(self.x_train)
        self.x_test = stand_transfer.transform(self.x_test)
        self.n = n
        # 假设初始残差为self.residual与训练标记相同
        self.residual = copy(self.y_train)
    
    def train(self):
        # acum_residual用于累加预测的残差
        acum_residual = np.zeros(self.y_train.shape)
        for i in range(self.n):
            tree = DecisionTreeRegressor()
            param = {"max_depth": np.arange(1, 5), "min_samples_leaf": np.arange(1, 5), "min_samples_split": np.arange(2, 5)}
            clsf = GridSearchCV(estimator=tree, param_grid=param, cv=10)
            clsf.fit(self.x_train, self.residual)
            residual_pred = clsf.predict(self.x_train)
            self.base_funcs.append(clsf.best_estimator_)
            acum_residual += residual_pred
            # self.residual为真实残差,计算方式为y的真实值减去预测残差的累加就是真实残差,下一次再用base_func拟合这个残差
            self.residual = copy(self.y_train - acum_residual)
    
    def test(self):
        y_test_predict = np.zeros(self.y_test.shape)
        for model in self.base_funcs:
            y_test_predict += model.predict(self.x_test)
        print("===================前向分步算法================")
        print("测试集真实值:", self.y_test)
        print("测试集预测值:", y_test_predict)
        print("测试集均方误差为:", mean_squared_error(y_pred=y_test_predict, y_true=self.y_test))
            
    def run(self):
        self.train()
        self.test()
        
        
def main():
    f = Foward(30)
    f.run()

    
if __name__ == "__main__":
    main()

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值