本篇博客主要来说明前向分步算法以及通过前向分步算法构造的提升树模型。
首先,我们假设某一模型公式具有如下形式:
其中我们称为基函数,为此基函数的系数,而为基函数的相关参数,我们称这样的模型为加法模型;我们通过使用这样一个模型来进行回归于分类任务。
然而,这样一个模型改如何来构造呢?我们采用一种叫做前向分步算法的方法来构造这么一个模型。
首先我们给定损失函数如下所示:
那我们的任务就是优化的损失函数如下所示:
然而这是一个复杂的优化问题,对其直接求解往往过于复杂,因此这里采用前向分步算法从前向后逐个求出基函数及其系数,也就是说我们每次只需优化如下损失函数即可求出一组和:
李航的统计学习方法书中写到这里用,但是个人认为这里的并非是样本真实标记,其只代表一个数值,表明了每次只需优化有这这种形式的损失函数,到后面可以看出,这个实际上代表残差,即样本真实值和当前模型预测值之差。
接下来给出前向分步算法的具体操作步骤:
假设存在训练数据集,,;损失函数为;并且我们还知道基函数集,我们不知道每一个基函数的参数,我们需要通过前向分步算法将参数求解出来
第一步:初始化模型
第二步:对于:
- 2.1 极小化如下损失:,其中为当前模型对于样本点的预测值,我们记做,也就是对进行优化;实际上我们是要让逼近真实值,即,而为已知量,因此将这个逼近问题转化为,所以是对当前模型预测残差的一个拟合
- 2.2 更新当前模型:
- 循环执行2.1和2.2知道构造完所有的个基函数
第三步:构造最终分类器:
以上便是前向分步算法的具体步骤及公式
前向分步算法中的损失函数如果是针对分类问题我们往往采取指数损失;而如果是回归问题,我们通常采用平方误差损失;其实Adaboost算法得到的模型实质上与前向分步算法采用指数损失得到的加法模型等价。
下面我们介绍提升树模型:
以决策树为基函数,进行前向分步算法得到的模型就是提升树模型,这种以决策树为基函数的提升方法成为提升树(boosting tree)。如果我们最终要构造的时分类模型,我们采用二叉分类树作为基函数;如果要构造回归模型,我们采用二叉回归树作为基函数。
提升树模型如果用来解决分类问题,我们只需将前向分布算法中的损失函数用指数损失,基函数用二叉分类树即可得到提升树模型;实质上我们在Adaboost算法中将基分类器限制为二叉分类树也可以得到提升树模型,与前者是等价的,因此提升树算法是Adaboost算法的一种特殊情况。
而提升树模型如果用来解决回归问题,我们需要将前向分布算法中的损失函数设置为平方误差损失,基函数使用二叉回归树即可得到解决回归问题的提升树模型。
因为解决分类问题就是Adaboost算法,只不过基分类器采用了二叉分类树,Adaboost前面已经介绍过了,所以这里就不介绍了,这里我们只介绍用于解决回归问题的提升树模型,这里还是采用前向分步算法:
第一步:确定初始提升树:
第二步:确定第颗二叉回归树,其中表示这棵树的参数:
首先可以确定的是,通过前向分步算法我们可以通过解决这样一个优化问题来求出第颗树。但我们进行进一步分析,由于我们这里采用的是平方误差损失,故,而实际上就是当前模型对于第个样本预测值与真实值的残差,我们记作,因此损失函数便成为了,可以看出实际上是用来拟合当前模型的残差的。
所以第颗树的求取我们可以通过:
- 2.1 计算残差
- 2.2 优化:
- 得到第颗树,更新
第三步:得到回归问题的提升树模型:
到这里提升树以及前向分步算法页介绍完毕了,下面附上提升树相关程序,数据为波士顿房价数据:
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()