gbdt源码解读
最近看了一些gbdt方面的內容,發現網上的一些文章都語焉不詳,在git上找了份源碼
gbdt算法要點
- for i in range(self.maxTreeNum)
- gbdt采用bosting方法,就是一棵棵樹逐步的降低殘差
- gbdt的基分類器是二叉樹,二叉樹的某個節點分裂方式是
-
一棵二叉樹的計算復雜度是,樹的深度*特征的個數*(樣本個數)的平方
-
gbdt的算法復雜度=樹的個數*樹的深度*特征的個數*(樣本個數)的平方
-
gbdt的每棵樹在分裂是窮舉所有的feature,窮舉這個feature下所有的點,分別計算loss,最小的那個loss對應的那個feature 就是最優feature,對應的那個value 就是分裂點
-
xgboost在窮舉所有的feature 時,采用并行計算,能夠提高計算效率
-
gbdt在算法本質上與xgboost相同,xgboost 采用二階導數,并且加了L1,L2正則,
-
在現如今,gbdt已經過時,他的優勢是可解釋性,與深度學習相比,gbdt的輸入只是一位的向量,他孤立的遍歷這個向量每個元素的值來實現分類回歸,這不免有些淺薄,模型如果要獲得更好的結果,他一定要有泛化能力,泛化能力來源于高維特征.
-
在深度學習上,也有類似的做法,比如對同樣的數據,建立多個深度學習模型,然后提取模型的某一層feature,比如將一個矩陣通多個模型過映射成多個向量,再把這些向量,拼接起來,之后嘛,你可用dnn,也可以用gbdt,隨所愿你
首先是遍歷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=-1bestSplitPointValue=-1curLoss = self.calculateSquareLoss(residualGradient)minLossValue=curLossif treeHeight==self.maxTreeLength:return curLosstree=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.0sumLoss+=self.calculateSquareLoss(np.array(leftSubTree))sumLoss+=self.calculateSquareLoss(np.array(rightSubTree))if sumLoss<minLossValue:bestSplitPointDim=ibestSplitPointValue=splitNumminLossValue=sumLoss#如果損失值沒有變小,則不作任何改變,也就是下面的歸位一個Nodeif 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的和的gradientresidualGradient = -1*self.learningRate*(curValue-y_train) #梯度維度與樣本數量有關curTree = self.splitTree(x_train,residualGradient,1)gbdt細節
總結
- 上一篇: python 二叉树遍历
- 下一篇: N叉树的深度 python实现