GBDT即GradientBoostingDecisionTree,梯度提升决策树,以回归树为弱学习器,跟AdaBoost一样都可以用以下公式表示的集成学习算法
为什么用到的都是CART回归树,因为GBDT每次迭代要拟合的是梯度值,为连续值。下面公式会推导出GBDT的为什么拟合的是梯度值
算法流程:
1,初始化学习器F0(x)
2,计算拟合后的负梯度
3,用计算后的负梯度作为本轮弱学习器(CART回归树)的拟合目标,fk(x)
4,循环2-3步,达到退出条件,得到提升树
拟合目标推导
接下来推导下为什么GBDT拟合的目标的负梯度值
1,损失函数,算法根据不同的应用场景可以定义不同的损失函数,后面会列举回归场景下和二分类场景下代入后的推导结果
2,根据前向分布,可以得到第k个强学习器为
将2代入1得到,并根据泰勒一阶展开可以得到
为了减少损失函数,即本轮损失函数<=上一轮损失函数,需要让上一轮损失函数减去一个>=0的数,则让
即
得到弱学习器f(x)拟合的是上一轮强学习器损失函数对预测结果的导数,即负梯度
回归算法
一,算法推导
在回归问题下,损失函数一般为平方误差
代入弱学习器目标即负梯度的公式得到
可以得到每一轮弱学习器拟合的目标是截至上一轮强学习器拟合结果和目标之间的残差
二,举例应用
例子特征为x,目标为y
1,初始化学习器F0(x)为样本目标的平均值
F0(x)=(1+3+6+8)/4=4.5
2,计算第一轮弱学习器的拟合目标并进行Cart回归树分列
f1(x)=Y-F0(x)
得到
Cart回归树根据均方差最小进行分裂,第一棵树如下
第一轮学习结果如下:根据Shrinkage的思想,为了防止每轮学习过拟合,我们对每一轮弱学习器的学习结果乘上一个学习率learing_rate,更新下一轮学习器的拟合目标
f2(x)=Y-F0(x)-f1(x)*0.1
重复上面的学习过程,第二棵树的分裂结果如下:
第二轮的学习结果:
一直重复上面的学习可以得到接下来每一轮的学习结果
3,假设我们设定学习次数为10次,则最终得到强学习器
学习结果如下:
二分类算法
一,算法推导
在二分类问题,损失函数一般为二元交叉熵损失函数
我们知道
代入损失函数后得到
每一轮弱学习器拟合的目标即损失函数的负梯度为
但因为分类问题存在F(x)到预测概率的sign转换问题,Cart回归树拟合出来的值为概率的残差值,而我们需要得到F(x)的学习器,再通过sign函数转换概率,所以我们需要将弱学习器的拟合结果做个转换,将其转化为F(x)项,假设预测叶子节点的值为c
对上面的损失函数求导并使导数为0,试试能不能求出c
-y为实际目标值,取值为{0,1},c取无穷大或无穷小都无法使导数等于0。为了使损失函数最小化,我们对损失函数进行泰勒二次展开
根据二元一次方程求损失函数的极值
因此每一轮弱学习器的叶子节点取值需要用上公式转换为F(x)的加和项,才能通过sign函数转换得到预测概率
二,举例应用
例子特征为x,目标为y
1,初始化学习器F0(x)
F0(x)=ln(P/(1-P))=ln(0.6/(1-0.6))=0.405
2,计算第一轮弱学习器的拟合目标并进行Cart回归树分列
得到
Cart回归树根据均方差最小进行分裂,得到第一轮学习结果如下
但是f1(x)_hat为目标值和概率值之间的残差预测值,我们需要用到下面公式转换得到每个叶子F(x)的弱学习项,才能通过sign公式计算得到下一轮的概率残差
r即f1(x),y为实际目标值,x<=2的叶子节点转换过程如下:
同理转换2
根据Shrinkage的思想,为了防止每轮学习过拟合,我们对每一轮弱学习器的学习结果乘上一个学习率learing_rate,更新下一轮学习器的拟合目标
重复上面的学习过程,第二棵树的分裂结果如下:
3,假设我们设定学习次数为10次,则最终得到强学习器
手撕原码
创建一个简单的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,回归场景
拟合结果
模型训练结果
B,分类场景
拟合结果
模型训练结果