gbdt算法_集成学习Boosting—GBDT回归和分类算法

e3234ca4785b43fc9da3d009ce77c0c1.png

GBDT即GradientBoostingDecisionTree,梯度提升决策树,以回归树为弱学习器,跟AdaBoost一样都可以用以下公式表示的集成学习算法

d76924e6e64cfe357a0d2e40a6c1bb77.png

为什么用到的都是CART回归树,因为GBDT每次迭代要拟合的是梯度值,为连续值。下面公式会推导出GBDT的为什么拟合的是梯度值

b4361b6db6792f9f40a2a292ed7148d2.png

算法流程

1,初始化学习器F0(x)

2,计算拟合后的负梯度

3,用计算后的负梯度作为本轮弱学习器(CART回归树)的拟合目标,fk(x)

4,循环2-3步,达到退出条件,得到提升树

b4361b6db6792f9f40a2a292ed7148d2.png    

拟合目标推导

接下来推导下为什么GBDT拟合的目标的负梯度值

1,损失函数,算法根据不同的应用场景可以定义不同的损失函数,后面会列举回归场景下和二分类场景下代入后的推导结果

6821a7c97653f27099e6743389a67d6d.png

2,根据前向分布,可以得到第k个强学习器为

850354e4a27d6e0da276506f0459e996.png

将2代入1得到,并根据泰勒一阶展开可以得到

d0321f9cfd8bc36603032b7749685e74.png

为了减少损失函数,即本轮损失函数<=上一轮损失函数,需要让上一轮损失函数减去一个>=0的数,则让

46a9b4bd0e294143c4bbde7ebc67d0ff.png

4cb71c7ba127d86da7692815d238b917.png

得到弱学习器f(x)拟合的是上一轮强学习器损失函数对预测结果的导数,即负梯度

b4361b6db6792f9f40a2a292ed7148d2.png

回归算法

一,算法推导

在回归问题下,损失函数一般为平方误差

1772c8f549bdecf2aa8129d8d2c63c1f.png

代入弱学习器目标即负梯度的公式得到

ee3f918b73a5a9a108877dc63ce2f5b1.png

可以得到每一轮弱学习器拟合的目标是截至上一轮强学习器拟合结果和目标之间的残差

二,举例应用

例子特征为x,目标为y

53e53392991a2630986bedaade7114fc.png

1,初始化学习器F0(x)为样本目标的平均值

F0(x)=(1+3+6+8)/4=4.5

2,计算第一轮弱学习器的拟合目标并进行Cart回归树分列

f1(x)=Y-F0(x)

得到

abacd26bb9c599ac5757c9951c45d9ee.png

Cart回归树根据均方差最小进行分裂,第一棵树如下

40df2666c022d18b5ec640e57783f951.png

第一轮学习结果如下:根据Shrinkage的思想,为了防止每轮学习过拟合,我们对每一轮弱学习器的学习结果乘上一个学习率learing_rate,更新下一轮学习器的拟合目标

f2(x)=Y-F0(x)-f1(x)*0.1

fea5a74dafdd6d83479edf7ffd8d2dbb.png

重复上面的学习过程,第二棵树的分裂结果如下:

acd87667f848c5f31fdfbec42f61f05e.png

第二轮的学习结果:

83f619cf771e8ddd8f132ce15950742f.png

一直重复上面的学习可以得到接下来每一轮的学习结果

3,假设我们设定学习次数为10次,则最终得到强学习器

ee0892cf62443913710b048b006926ca.png

学习结果如下:

def926c8400b1b05eb838916c433258f.png

b4361b6db6792f9f40a2a292ed7148d2.png

二分类算法

一,算法推导

在二分类问题,损失函数一般为二元交叉熵损失函数

f230782a884eeb0270b503265839018c.png

我们知道

cd29cb97ad7d6c099ec80de4cc4f0e69.png

代入损失函数后得到

f3d6e7f455a3be145bd5eab20a30d567.png

每一轮弱学习器拟合的目标即损失函数的负梯度为

4e33d5344eae422a14b46c7d20d7397e.png

但因为分类问题存在F(x)到预测概率的sign转换问题,Cart回归树拟合出来的值为概率的残差值,而我们需要得到F(x)的学习器,再通过sign函数转换概率,所以我们需要将弱学习器的拟合结果做个转换,将其转化为F(x)项,假设预测叶子节点的值为c

0d441fcaf13678a57e6c952c06599864.png

对上面的损失函数求导并使导数为0,试试能不能求出c

cff1af431c8559e28a1d0cb547f7e5e3.png

-y为实际目标值,取值为{0,1},c取无穷大或无穷小都无法使导数等于0。为了使损失函数最小化,我们对损失函数进行泰勒二次展开

ab1f16a818f0615ea8af81d78da1bfbb.png

根据二元一次方程求损失函数的极值

89367dbc3e1a2139c539b5a77b0be5a6.png

因此每一轮弱学习器的叶子节点取值需要用上公式转换为F(x)的加和项,才能通过sign函数转换得到预测概率

二,举例应用

例子特征为x,目标为y

d3baebb5b0437099614880dda95bd0f0.png

1,初始化学习器F0(x)

F0(x)=ln(P/(1-P))=ln(0.6/(1-0.6))=0.405

2,计算第一轮弱学习器的拟合目标并进行Cart回归树分列

bde54fb86758a249841f3e0d5c1e65a3.png

得到

f153e8e79952d59246056ed0710fbb1f.png

Cart回归树根据均方差最小进行分裂,得到第一轮学习结果如下

e4ad4ac5f58403ddae61b8090a9defbf.png

但是f1(x)_hat为目标值和概率值之间的残差预测值,我们需要用到下面公式转换得到每个叶子F(x)的弱学习项,才能通过sign公式计算得到下一轮的概率残差

c3c87d63f28de085f4b16181d5a485c1.png

r即f1(x),y为实际目标值,x<=2的叶子节点转换过程如下:

57829ca7ec74536b88386e5ae0490285.png

同理转换2

2f2122f91f29987c2386d07cb709551c.png

根据Shrinkage的思想,为了防止每轮学习过拟合,我们对每一轮弱学习器的学习结果乘上一个学习率learing_rate,更新下一轮学习器的拟合目标

85deae19dc3ea39c64b3ae9dd80c7bad.png

重复上面的学习过程,第二棵树的分裂结果如下:

ac8f70145425cbcc33bd872fdca88980.png

3,假设我们设定学习次数为10次,则最终得到强学习器

c82fcc3614e2113a2c8830ed6e8c385a.png

1e92f8b96a83399f76dc7f8cd99823fe.png

b4361b6db6792f9f40a2a292ed7148d2.png

手撕原码

创建一个简单的GBDTOfBike的类,涵盖回归场景和二分类场景的学习模型,输出参数包括

  • learning_rate:学习率,默认为0.1;

  • n_estimators:学习器个数,默认为5;

  • min_samples_leaf:弱学习叶子最小样本数,默认为1;

  • error:弱学习器拟合误差阈值,默认为0

  • FinalError:强学习器拟合残差阈值,回归场景下使用,默认为0;

  • Classcify:是否调用分类算法,默认为True

封装几个算法中需要用到的方法:

  • SplitData:弱学习器进行学习时,根据每一个分裂条件将数据集分成左右两个子数据集

  • GetBestfeat: 根据回归树的分裂原则,均方差最小找到每一次分裂最好的变量和分裂值

  • CartRegressionTree:调用SplitData和GetBestfeat生成回归树,输出回归树的分裂变量,分裂点级每个叶子的取值

  • TreeLabel:根据CartRegressionTree输出的回归树的参数对样本求预测值

  • GBDTRegression:回归算法主程序;

  • GBDTClassification:二分类算法主程序;

  • 根据设定的学习次数和学习率,调用CartRegressionTree方法进行学习,输出强学习器参数和每个弱学习器的参数

  • Predict:应用训练完的最终强学习器,预测输入样本结果

算法代码:

import pandas as pdimport numpy as npclass GBDTOfBike(object):    def __init__(self,learning_rate=0.1,n_estimators=3,min_samples_leaf=1,error=0,FinalError=0,Classcify=True):        self.learning_rate=learning_rate        self.n_estimators=n_estimators        self.min_samples_leaf=min_samples_leaf        self.error=error        self.FinalError=FinalError        self.Classcify=Classcify    def LoadDataSet(self,TrainData,TrainLabel):        self.TrainData=TrainData.copy()        self.TrainLabel=pd.DataFrame(TrainLabel)         self.TrainLabel.columns=["Y"]                    #根据最好的分裂特征和阈值划分数据    def SplitData(self,TrainData,TrainLabel,leaf):        LeftTrainData=TrainData[TrainData.loc[:,leaf['Feat']]<=leaf['Value']]        LeftTrainLabel=TrainLabel[TrainData.loc[:,leaf['Feat']]<=leaf['Value']]        RightTrainData=TrainData[TrainData.loc[:,leaf['Feat']]>leaf['Value']]        RightTrainLabel=TrainLabel[TrainData.loc[:,leaf['Feat']]>leaf['Value']]        return LeftTrainData,LeftTrainLabel,RightTrainData,RightTrainLabel    def GetBestfeat(self,TrainData,fx,Y):        '''        TrainData:训练的特征变量        fx:弱学习器的拟合目标,即负梯度        Y:样本目标变量        '''        m,n=TrainData.shape        #程序终止条件1:数据集记录数小于等于设定阈值        if m<=self.min_samples_leaf:            if self.Classcify==True:                return fx.mean()*fx.count()/((Y-fx.mean())*(1-Y+fx.mean())).sum()            return round(fx.mean(),4)        #程序终止条件2:目标值只有一种取值        if len(set(fx))==1:            if  self.Classcify==True:                return fx.mean()*fx.count()/((Y-fx.mean())*(1-Y+fx.mean())).sum()            return round(fx.mean(),4)            R2  = fx.var()*m         for i in range(n):            splitlist=set(TrainData.iloc[:,i])            for idx,j in enumerate(splitlist):                Left=round(fx[TrainData.iloc[:,i]<=j].mean(),4)                Left_n=fx[TrainData.iloc[:,i]<=j].count()                Right=round(fx[TrainData.iloc[:,i]>j].mean(),4)                Right_n=fx[TrainData.iloc[:,i]>j].count()                L_value=pow(fx[TrainData.iloc[:,i]<=j]-Left,2).sum()+pow(fx[TrainData.iloc[:,i]>j]-Right,2).sum()                if L_valueand Left_n>=                    R2=L_value                    if self.Classcify==True:                        Left=Left*Left_n/((Y[TrainData.iloc[:,i]<=j]-Left)*(1-Y[TrainData.iloc[:,i]<=j]+Left)).sum()                        Right=Right*Right_n/((Y[TrainData.iloc[:,i]>j]-Right)*(1-Y[TrainData.iloc[:,i]>j]+Right)).sum()                                           leaf={'Feat':TrainData.columns[i],'Value':j,'lLabel':Left,'rLabel':Right}        #程序终止条件3:继续分列无法得到误差下降的阈值,退出        if L_value-R2<self.error:            if  self.Classcify==True:                return fx.mean()*fx.count()/((Y-fx.mean())*(1-Y+fx.mean())).sum()            return round(fx.mean(),4)        return leaf                    #根据参数进行弱学习器训练                def CartRegressionTree(self,TrainData,TrainLabel,fx,Y):        Tree=self.GetBestfeat(TrainData,fx,Y)        if isinstance(Tree,float) :            return Tree                ReTree=Tree.copy()        LeftTrainData,LeftTrainLabel,RightTrainData,RightTrainLabel=self.SplitData(TrainData,TrainLabel,Tree)            ReTree['lLabel']=self.CartRegressionTree(LeftTrainData,LeftTrainLabel,LeftTrainLabel['fx'],LeftTrainLabel['Y'])        ReTree['rLabel']=self.CartRegressionTree(RightTrainData,RightTrainLabel,RightTrainLabel['fx'],RightTrainLabel['Y'])        return ReTree     #根据训练的弱学习器模型参数对样本计算预测的负梯度    def TreeLabel(self,Data,ReTree):             x=Data[ReTree['Feat']]        if x<=ReTree['Value']:            lTree=ReTree['lLabel']            if isinstance(lTree,float):                return lTree             return self.TreeLabel(Data,lTree)                else:            rTree=ReTree['rLabel']            if isinstance(ReTree['rLabel'],float):                return ReTree['rLabel']            return self.TreeLabel(Data,rTree)    #GBDT的回归算法主程序    def GBDTRegression(self,TrainData,TrainLabel):                GBDTRegression={'n_estimators':self.n_estimators,                        'learning_rate':self.learning_rate}                #初始化弱学习器        TrainLabel['Fx']=TrainLabel['Y'].mean()        GBDTRegression['F0']=TrainLabel['Y'].mean()               for n in range(self.n_estimators):            y_hat=[]            #计算负梯度:本轮弱学习器的拟合目标            TrainLabel['fx']=TrainLabel['Y']-TrainLabel['Fx']            if sum(TrainLabel['fx']**2)<=self.FinalError:                print('第%d次迭代达到设定均方差阈值'%(n+1))                GBDTRegression['n_estimators']=n                break                        ReTree=self.CartRegressionTree(TrainData,TrainLabel,TrainLabel['fx'],TrainLabel['Y'])             GBDTRegression['%d'%n]=ReTree            for idx,row in TrainData.iterrows():                    y_hat.append(self.TreeLabel(row,ReTree))            TrainLabel['fx_hat']=pd.DataFrame(y_hat,index=TrainLabel.index)            TrainLabel['Fx']+=TrainLabel['fx_hat']*self.learning_rate             return GBDTRegression,TrainLabel[['Y','Fx']]                def sign(self,x):        return 1/(1+np.exp(-x))        #GBDT的分类算法主程序    def GBDTClassification(self,TrainData,TrainLabel):              GBDTClassification={'n_estimators':self.n_estimators,                        'learning_rate':self.learning_rate}        #初始化弱学习器        P=TrainLabel['Y'].mean()        TrainLabel['Fx']=np.log(P/(1-P))        GBDTClassification['F0']=np.log(P/(1-P))                for n in range(self.n_estimators):            y_hat=[]            #计算负梯度:本轮弱学习器的拟合目标            TrainLabel['fx']=TrainLabel['Y']-self.sign(TrainLabel['Fx'])                  ReTree= self.CartRegressionTree(TrainData,TrainLabel,TrainLabel['fx'],TrainLabel['Y'])                           GBDTClassification['%d'%n]=ReTree                        for idx,row in TrainData.iterrows():                    y_hat.append(self.TreeLabel(row,ReTree))                TrainLabel['fx_hat']=pd.DataFrame(y_hat,index=TrainLabel.index)            TrainLabel['Fx']+=TrainLabel['fx_hat']*self.learning_rate          TrainLabel['Predict_Prob']=self.sign(TrainLabel['Fx'])          TrainLabel['Predict_Y']=TrainLabel['Predict_Prob'].map(lambda x:1 if x>0.5 else 0)        return GBDTClassification,TrainLabel[['Y','Predict_Y','Predict_Prob']]        def train(self):        if self.Classcify==True:            self.GBDT,self.TrainLabel=self.GBDTClassification(self.TrainData,self.TrainLabel)        else:            self.GBDT,self.TrainLabel=self.GBDTRegression(self.TrainData,self.TrainLabel)                    return self.GBDT,self.TrainLabel              def Predict(self,TestData,GBDT):        TestData['Fx']=GBDT['F0']        for ReTree in GBDT.values():            if isinstance(ReTree,dict):                y_hat=[]                for idx,row in TestData.iterrows():                     y_hat.append(self.TreeLabel(row,ReTree))                                    TestData['fx_hat']=pd.DataFrame(y_hat,index=TestData.index)                TestData['Fx']+=TestData['fx_hat']*GBDT['learning_rate']            if self.Classcify==True:            TestData['Predict_Prob']=self.sign(TestData['Fx'])              TestData['Predict_Y']=TestData['Predict_Prob'].map(lambda x:1 if x>0.5 else 0)            return TestData[['Predict_Prob','Predict_Y']]         return TestData['Fx']

算法调用

#回归算法训练测试Trainx=pd.DataFrame(np.arange(4),columns=['x'])Trainy=pd.DataFrame([1,3,6,8],columns=['Y']) Testx=Trainx.copy()GBDT=GBDTOfBike(learning_rate=0.1,n_estimators=10,min_samples_leaf=1,error=0,FinalError=0,Classcify=False)GBDT.LoadDataSet(Trainx,Trainy['Y'])Regression,Trainy=GBDT.train() Testy= GBDT.Predict(Testx,Regression) #分类算法训练测试   Trainx=pd.DataFrame(np.arange(10),columns=['x'])Trainy=pd.DataFrame([1,1,1,0,0,0,1,1,1,0],columns=['Y']) Testx=Trainx.copy()GBDT=GBDTOfBike(learning_rate=0.1,n_estimators=10,min_samples_leaf=1,error=0,FinalError=0,Classcify=True)GBDT.LoadDataSet(Trainx,Trainy['Y'])Classcify,Trainy=GBDT.train() Testy= GBDT.Predict(Testx,Classcify)

代码结果:

用代码计算上面例子的结果

A,回归场景

  1. 拟合结果

    da90b5907c85a3f66913ae606ee0ee1f.png

  2. 模型训练结果

    1d4d6ff4c7518ae9586d4d058727dc98.png

B,分类场景

  1. 拟合结果

    cf08b667ff4de7b421c2f90bb1711743.png

  2. 模型训练结果

    cf39f6115a49dfd0e2b5f9f9e8ac7931.png

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值