gbdt源码解读

最近看了一些gbdt方面的内容,发现网上的一些文章都语焉不详,在git上找了份源码

gbdt算法要点

  • for i in range(self.maxTreeNum)
  • gbdt采用bosting方法,就是一棵棵树逐步的降低残差
  • gbdt的基分类器是二叉树,二叉树的某个节点分裂方式是
        for i in range(dim):
            #遍历所有的数据
            for j in range(size):
                #令 x_train[j,i]为分裂点    
                splitNum = x_train[j,i]
                leftSubTree=[]
                rightSubTree=[]
                #以splitNum 为分裂点,对于第i个feature ,将数据分成两类,
                for k in range(size):
                    tmpNum=x_train[k,i]
                    if tmpNum<=splitNum:
                        leftSubTree.append(residualGradient[k])
                    else:
                        rightSubTree.append(residualGradient[k])
                sumLoss=0.0
                #分别计算左右子树的loss,再求和,通过最小化loss,来决定分裂的feature和分裂的值
                sumLoss+=self.calculateSquareLoss(np.array(leftSubTree))
                sumLoss+=self.calculateSquareLoss(np.array(rightSubTree))
                if sumLoss<minLossValue:
                    
                    bestSplitPointDim=i
                    bestSplitPointValue=splitNum
                    minLossValue=sumLoss
                    print(treeHeight,bestSplitPointDim)
        '''
  • 一棵二叉树的计算复杂度是,树的深度*特征的个数*(样本个数)的平方

  • gbdt的算法复杂度=树的个数*树的深度*特征的个数*(样本个数)的平方

  • gbdt的每棵树在分裂是穷举所有的feature,穷举这个feature下所有的点,分别计算loss,最小的那个loss对应的那个feature 就是最优feature,对应的那个value 就是分裂点

  • xgboost在穷举所有的feature 时,采用并行计算,能够提高计算效率

  • gbdt在算法本质上与xgboost相同,xgboost 采用二阶导数,并且加了L1,L2正则,

  • 在现如今,gbdt已经过时,他的优势是可解释性,与深度学习相比,gbdt的输入只是一位的向量,他孤立的遍历这个向量每个元素的值来实现分类回归,这不免有些浅薄,模型如果要获得更好的结果,他一定要有泛化能力,泛化能力来源于高维特征.

  • 在深度学习上,也有类似的做法,比如对同样的数据,建立多个深度学习模型,然后提取模型的某一层feature,比如将一个矩阵通多个模型过映射成多个向量,再把这些向量,拼接起来,之后嘛,你可用dnn,也可以用gbdt,随所愿你

from GBDTReg import GBDT
class Config(object):
    learningRate=0.1
    maxTreeLength=4
    maxLeafCount=30
    maxTreeNum=4

首先是遍历range(self.maxTreeNum)树的数量, residualGradient=(y-y0)然后开始分裂树,

    def splitTree(self,x_train,residualGradient,treeHeight):
        """

        :param x_train:训练数据
        :param residualGradient:当前需要拟合的梯度残差值
        :param treeHeight:树的高度
        :return:建好的GBDT树
        """
        size = len(x_train)
        dim = len(x_train[0])
        #约定:左子树是小于等于,右子树是大于
        bestSplitPointDim=-1
        bestSplitPointValue=-1
        curLoss = self.calculateSquareLoss(residualGradient)
        minLossValue=curLoss
        if treeHeight==self.maxTreeLength:
            return curLoss
        tree=dict([])
        for i in range(dim):
            for j in range(size):
                splitNum = x_train[j,i]
                leftSubTree=[]
                rightSubTree=[]
                for k in range(size):
                    tmpNum=x_train[k,i]
                    if tmpNum<=splitNum:
                        leftSubTree.append(residualGradient[k])
                    else:
                        rightSubTree.append(residualGradient[k])
                sumLoss=0.0

                sumLoss+=self.calculateSquareLoss(np.array(leftSubTree))
                sumLoss+=self.calculateSquareLoss(np.array(rightSubTree))
                if sumLoss<minLossValue:
                    bestSplitPointDim=i
                    bestSplitPointValue=splitNum
                    minLossValue=sumLoss
        #如果损失值没有变小,则不作任何改变,也就是下面的归位一个Node
        if minLossValue==curLoss:
            return np.mean(residualGradient)
        else:

            leftSplit=[(x_train[i],residualGradient[i]) for i in range(size) if x_train[i,bestSplitPointDim]<=bestSplitPointValue ]
            rightSplit=[(x_train[i],residualGradient[i]) for i in range(size) if x_train[i,bestSplitPointDim]>bestSplitPointValue ]
             
#            print(leftSplit)
            newLeftSubTree = list(zip(*leftSplit))[0]
            newLeftResidual = list(zip(*leftSplit))[1]
            leftTree = self.splitTree(np.array(newLeftSubTree),newLeftResidual,treeHeight+1)

            newRightSubTree = list(zip(*rightSplit))[0]
            newRightResidual =list(zip(*rightSplit))[1]
            rightTree = self.splitTree(np.array(newRightSubTree),newRightResidual,treeHeight+1)

            tree[(bestSplitPointDim,bestSplitPointValue)]=[leftTree,rightTree]
            return tree

通过对split方法的解读,可以看出所谓梯度提升树枝,就是递归的方式更新梯度的同时,划分左子树和右子树。一直到loss不在降低。如果本棵树loss不再降低,self.tree.append(curTree),最后的模型是多棵树的叠加。

源码链接

我面试候选人时必问的一个问题GBDT中的梯度是什么对什么的梯度?
给一个有m个样本,n维特征的数据集,如果用LR算法,那么梯度是几维?
同样的m*n数据集,如果用GBDT,那么梯度是几维?m维?n维?m*n维?或者是与树的深度有关?或者与树的叶子节点的个数有关?
1 当前y对之前所有轮次的tree的和的gradient
2 n维
3 m维,还有就是gbdt在一阶泰勒展开后需要添加正则化以防止naive solution,而xgb展开到二阶后,如果cost function的二阶导数大于0,相当于某种意义上的自带正则
        for i in range(self.maxTreeNum):
            print("the tree %i-th"%i)

            # 之前所有轮次的tree的和的gradient
            residualGradient = -1*self.learningRate*(curValue-y_train) #梯度维度与样本数量有关
            curTree = self.splitTree(x_train,residualGradient,1)

gbdt细节

posted @   luoganttcc  阅读(8)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 如何调用 DeepSeek 的自然语言处理 API 接口并集成到在线客服系统
· 【译】Visual Studio 中新的强大生产力特性
· 2025年我用 Compose 写了一个 Todo App
点击右上角即可分享
微信分享提示