The Optimization of the Adaboost and Gradient Boosted Decision Tree

#The Optimization of the Adaboost ###1.對於Adaboost error function的推導 再回到咱們上篇文章講到的Adaboost算法,咱們要從Adaboost算法推導出GBDT。首先回顧一下上篇文章的Adaboost,主要思想就是把弱分類器集中起來獲得一個強的分類器。首先第一次建造樹的時候每個樣本的權值都是同樣的,以後的每一次訓練只要有錯誤,那麼這個錯誤就會被放大,而正確的權值就會被縮小,以後會獲得每個模型的α,根據每個樹的α把結果結合起來就獲得須要的結果。 node

在Adaboost裏面,Ein表達式:
每個錯誤的點乘權值相加求平均,咱們想把這一個特徵結合到decision tree裏面,那麼就須要咱們在決策樹的每個分支下面加上權值,這樣很麻煩。爲了保持決策樹的封閉性和穩定性,咱們不改變結構,只是改變數據的結果,餵給的數據乘上權值,其餘的不改變,把決策樹當成是一個黑盒子。 權值u其實就是這個數據用boostrap抽樣抽到的機率,能夠作一個帶權抽樣,也就是帶了u的sample,這樣抽出來的數據沒一個樣本和u的比例應該是差很少一致的,因此帶權sampling也叫boostrap的反操做。這種方法就是對數據自己進行改變而對於算法自己的數據結構不變。
上面的步驟就是獲得gt,接下來就是求α了,α就是每個model的重要性,上節課咱們是講到過怎麼求的,先獲得錯誤率ξ,而後:
這個東西很重要,以後咱們會對這公式作推導。

①爲何Adaboost要弱類型的分類器? 分類器講道理,應該是越強越好的,可是Adaboost相反,他只要弱的,強的不行。來看一下α,若是你的分類器很強,ξ基本就是equal 0了,這樣的α是無窮大的,其餘的分類器就沒有意義了,這樣就又回到了單分類器,而單分類器是作不到aggregation model的好處的,祥見aggregation model這篇文章。 git

咱們把這種稱爲autocracy,獨裁。針對這兩個緣由,咱們能夠作剪枝,限制樹的大小等等的操做,好比只使用一部分樣本,這在sampling的操做中已經起到這類做用,由於必然有些樣本沒有被採樣到。
因此,綜上緣由,Adaboost經常使用的模型就是decision stump,一層的決策樹。
事實上,若是樹高爲1時,一般較難遇到ξ=0的狀況,且通常不採用sampling的操做,而是直接將權重u代入到算法中。這是由於此時的AdaBoost-DTree就至關因而AdaBoost-Stump,而AdaBoost-Stump就是直接使用u來優化模型的。前面的算法實現也是基於這些理論實現的算法。

回到正題,繼續從optimization的角度討論Adaboost,Adaboost權重計算以下: github

把上面兩張形式結合一下,獲得下面綜合形式。第一層的u是1/N,每一次遞推要乘上
因而,u(t+1):
而在這裏面
稱爲是voting score,也就是集合投票的分數,而u(t+1)是和voting score成正比的:
仔細看一下,voting score是由許多的g(x)經過不一樣的α進行線性組合而成的,換一個角度,把g(x)看作是特徵轉換的φ(x),α就是權重w,這樣一來再和SVM對比一下:
對比上面實際上是很像的,乘上的y就是表名是在正確的一側仍是錯誤的一側,因此,這裏的voting score也能夠看作是沒有正規化的距離。也就是沒有除|w|的距離,因此從效果上看,voting score是要越大越好。再來看以前的乘上y的表達式,咱們要作的就是要使得分類正確,因此y的符號和voting score符號相同的數量會愈來愈多,這樣也就保證了這個voting score是在不斷增大,距離不斷增大。這種距離要求大的在SVM裏面咱們叫正則化——regularization,一樣,這樣就證實了①Adaboost的regularization。剛剛的g(x)看作是φ其實就是feature transform,因此也證實了②Adaboost的feature transform。這兩個特性一個是踩剎車一個是加速,效果當然是比單分類器好的。
因此根據上面的公式,能夠獲得u是在不斷減少的:
因此咱們的目標就是要求通過了(t+1)輪的迭代以後,u的數據集合要愈來愈小。有以下:

②Adaboost的error 上式中咱們把voting score看作是s。對於0/1error,若是ys >= 0,證實是分類正確的,不懲罰err0/1 = 0,;若是ys < 0,證實分類錯誤,懲罰err0/1 = 1。對於Adaboost,咱們能夠用算法

來表示,可能有些同窗以爲這裏不太對, 這裏明明是u的表達式爲何能夠做爲錯誤的衡量?這個表達式裏面包含了y和s,正好能夠用來表達Adaboost的error,是就使用了。
正好也是減少的,和咱們須要的err是同樣的,要注意這裏Adaboost咱們是反推,已知到效果和方法反推表達式,使用要用上一切可使用的。 和error0/1對比:
因此,Adaboost的error function是能夠代替error0/1的,而且效果更好。

###2.對於Adaboost error function的一些簡單處理bash

③首先要了解一些泰勒展開: 在通常的函數狀況下,是有: 數據結構

①:
②:
①②式子代入上面:
這就是一階Taylor expansion。待會要利用他們來求解。

用Taylor expansion處理一下Adaboost error function: app

這是Gradient descent的公式,咱們類比一下error function:
這裏是使用exp(x) 的Taylor一階展開,這裏的方向是h(x),而Gradient descent是w纔是方向,w是某一個點的方向,而h(x)也能夠代入某一個點獲得一個value,這個value就是方向,因此基本上和梯度降低是一致的:
以上的對於error function的分解是在x = 0的點進行Taylor展開,能夠看到前面的一項是常數,後面的一項纔是變量,使用咱們須要優化的就是減少後面一項的值。因此咱們先要找到一個最合適的h(x)來優化這個函數,n先忽略:
對於y和h(x)咱們均限定是{1,-1},對這個優化項作一些平移:
因此要優化的項最後又轉變成了Ein,咱們的演算法一直都是在作減少Ein這件事,因此Adaboost的base algorithm——decision stump就是作的這件事。h(x)就能夠解決了。
解決了h(x)的問題接下來就是η的問題了:
因此最後的公式如上圖所示。 上式中有幾個狀況能夠考慮:
通過推導:
求η最小值天然就是求導了:
就獲得:
而η = α,這樣就推導出了α的表達式了。 Adaboost實際上就是在尋找最塊降低方向和最快降低步長的過程當中優化,而α至關於最大的步長,h(x)至關於最快的方向。因此,Adaboost就是在Gradient descent上尋找最快的方向和最快的步長。

#Gradient Boosted Decision Tree 推導完了Adaboost,咱們接着推導Gradient Boosted Decision Tree,其實看名字就知道只不過是error function不太同樣而已。前面Adaboost的推導總的能夠歸納爲:less

這種exp(-ys)function是Adaboost專有的,咱們能不能換成其餘的?好比logistics或者linear regression的。
使用Gradient descent的就是這種形式,雖然形式變了,可是最終的結果都是求解最快的方向和最長的步長。
這裏使用均方差替代error。使用一階泰勒展開:
constant咱們不須要管,咱們只須要關心最後的一項。使得這一項最小,那隻須要h(x)和2(s - y)互爲相反數,而且h(x)很大很大就行了,h(x)不作限制,很明顯這樣是求不出來的。有一個簡單的作法,收了regularization的啓發,咱們能夠在後面加上懲罰項,使用L2範式,L2範式會使他們很小但不會爲0,可是L1範式會使得他們集中到邊角上,係數矩陣,使用這裏使用L2範式,另外一方面,也是對於化簡的方便作了準備。
因此,優化的目標:
y-s咱們稱爲殘差,咱們要作的就是使得h(x)和(y - s)接近,也就是作一個擬合的過程,也就是作regression。
擬合出來獲得的h(x)就是咱們要的gt(x)了。以後就是求η了。 ③:
獲得了g(x)以後就是求η步長了。注意上面的③纔是咱們要求的公式,
這一個知識爲了化簡方便的。
總結一下,以上就是GBDT的流程了。值得注意的是,sn的初始值通常均設爲0,即s1=s2=⋯=sN=0。每輪迭代中,方向函數gt經過C&RT算法作regression,進行求解;步進長度η經過簡單的單參數線性迴歸進行求解;而後每輪更新sn的值,即sn←sn+αtgt(xn)。T輪迭代結束後,最終獲得
。值得一提的是,本節課第一部分介紹的AdaBoost-DTree是解決binary classification問題,而此處介紹的GBDT是解決regression問題。兩者具備必定的類似性,能夠說GBDT就是AdaBoost-DTree的regression版本。

#Summary of Aggregation Models 到這裏,aggregation model基本就完成了。主要有三個方面: ①uniform:把g(x)平均結合。 ②non-uniform:把g(x)線性組合。 ③conditional:根據不一樣條件作非線性組合。 uniform採用投票、求平均的形式更注重穩定性;而non-uniform和conditional追求的更復雜準確的模型,但存在過擬合的危險。 dom

剛剛所討論的model都是創建在g(x)已知的狀況下,若是g(x)不知道,咱們就可使用一下方法: ①Bagging:經過boostrap方法訓練模型平均結合結果。 ②Adaboost:經過boostrap方法訓練模型進行線性組合。 ③Decision Tree:數據分割獲得不一樣的g(x)進行線性組合。
除了以上的方法,咱們還能夠把Bagging和Decision Tree結合起來稱爲random forest,Adaboost和decision tree結合起來就是Adaboost-stump,Gradient Boosted和Adaboost結合起來就是GBDT了。
Aggregation的核心是將全部的gt結合起來,融合到一塊兒,也就是集體智慧的思想。這種作法可以獲得好的G的緣由,是由於aggregation具備兩個方面的優勢:cure underfitting和cure overfitting。 ①aggregation models有助於防止欠擬合(underfitting)。它把全部比較弱的gt結合起來,利用集體智慧來得到比較好的模型G。aggregation就至關因而feature transform,來得到複雜的學習模型。 ②aggregation models有助於防止過擬合(overfitting)。它把全部gt進行組合,容易獲得一個比較中庸的模型,相似於SVM的large margin同樣的效果,避免了一些過擬合的狀況發生。從這個角度來講,aggregation起到了regularization的效果。 因爲aggregation具備這兩個方面的優勢,因此在實際應用中aggregation models都有很好的表現。

#代碼實現 主要的作法就是用{(x, (y - s))}作擬合就行了。 因爲以前寫的決策樹結構設計的不太好,使用起來不方便因而從新寫了一個,這裏的CART樹是用方差來衡量impurity的。函數

def loadDataSet(filename):
    ''' load dataSet :param filename: the filename which you need to open :return: dataset in file '''
    dataMat = pd.read_csv(filename)
    for i in range(np.shape(dataMat)[0]):
        if dataMat.iloc[i, 2] == 0:
            dataMat.iloc[i, 2] = -1
    return dataMat
    pass

def split_data(data_array, col, value):
    '''split the data according to the feature'''
    array_1 = data_array.loc[data_array.iloc[:, col] >= value, :]
    array_2 = data_array.loc[data_array.iloc[:, col] < value, :]
    return array_1, array_2
    pass

def getErr(data_array):
    '''calculate the var '''
    return np.var(data_array.iloc[:, -1]) * data_array.shape[0]
    pass

def regLeaf(data_array):
    return np.mean(data_array.iloc[:, -1])

複製代碼

加載數據,分割數據,計算方差,計算葉子平均,其實就是計算擬合的類別了。

def get_best_split(data_array, ops = (1, 4)):
    '''the best point to split data'''
    tols = ops[0]
    toln = ops[1]
    if len(set(data_array.iloc[:, -1])) == 1:
        return None, regLeaf(data_array)
    m, n = data_array.shape
    best_S = np.inf
    best_col = 0
    best_value = 0
    S = getErr(data_array)
    for col in range(n - 1):
        values = set(data_array.iloc[:, col])
        for value in values:
            array_1, array_2 = split_data(data_array, col, value)
            if (array_1.shape[0] < toln) or (array_2.shape[0] < toln):
                continue
            totalError = getErr(array_1) + getErr(array_2)
            if totalError< best_S:
                best_col = col
                best_value = value
                best_S = totalError
    if (S - best_S) < tols:
        return None, regLeaf(data_array)
    array_1, array_2 = split_data(data_array, best_col, best_value)
    if (array_1.shape[0] < toln) or (array_2.shape[0] < toln):
        return None, regLeaf(data_array)

    return best_col, best_value

複製代碼

獲得最好的分類,這裏相比以前的決策樹加了一些條件限制,葉子數量不能少於4,和以前的同樣,計算方差對比看看哪一個小。

class node:
    '''tree node'''
    def __init__(self, col=-1, value=None, results=None, gb=None, lb=None):
        self.col = col
        self.value = value
        self.results = results
        self.gb = gb
        self.lb = lb
        pass
複製代碼

葉子節點,col列,val劃分的值,results結果,gb右子樹,lb左子樹。

def buildTree(data_array, ops = (1, 4)):
    col, val = get_best_split(data_array, ops)
    if col == None:
        return node(results=val)
    else:
        array_1, array_2 = split_data(data_array, col, val)
        greater_branch = buildTree(array_1, ops)
        less_branch = buildTree(array_2, ops)
        return node(col=col, value=val, gb=greater_branch, lb=less_branch)
    pass
複製代碼

創建一棵樹。

def treeCast(tree, inData):
    '''get the classification'''
    if tree.results != None:
        return tree.results
    if inData.iloc[tree.col] > tree.value:
        return treeCast(tree.gb, inData)
    else:
        return treeCast(tree.lb, inData)
    pass

def createForeCast(tree, testData):
    m = len(testData)
    yHat = np.mat(np.zeros((m, 1)))
    for i in range(m):
        yHat[i, 0] = treeCast(tree, testData.iloc[i])
    return yHat
複製代碼

建立分類。

def GBDT_model(data_array, num_iter, ops = (1, 4)):
    m, n = data_array.shape
    x = data_array.iloc[:, 0:-1]
    y = data_array.iloc[:, -1]
    y = np.mat(y).T
    list_trees = []
    yHat = None
    for i in range(num_iter):
        print('the ', i, ' tree')
        if i == 0:
            tree = buildTree(data_array, ops)
            list_trees.append(tree)
            yHat = createForeCast(tree, x)
        else:
            r = y - yHat
            data_array = np.hstack((x, r))
            data_array = pd.DataFrame(data_array)
            tree = buildTree(data_array, ops)
            list_trees.append(tree)
            rHat = createForeCast(tree, x)
            yHat = yHat + rHat
    return list_trees, yHat
複製代碼

這裏只是使用了迴歸問題的迴歸樹,x和(y - s)作擬合以後加入預測集便可。 接下來就是畫圖了:

def getwidth(tree):
    if tree.gb == None and tree.lb == None: return 1
    return getwidth(tree.gb) + getwidth(tree.lb)


def getdepth(tree):
    if tree.gb == None and tree.lb == None: return 0
    return max(getdepth(tree.gb), getdepth(tree.lb)) + 1


def drawtree(tree, jpeg='tree.jpg'):
    w = getwidth(tree) * 100
    h = getdepth(tree) * 100 + 120

    img = Image.new('RGB', (w, h), (255, 255, 255))
    draw = ImageDraw.Draw(img)

    drawnode(draw, tree, w / 2, 20)
    img.save(jpeg, 'JPEG')


def drawnode(draw, tree, x, y):
    if tree.results == None:
        # Get the width of each branch
        w1 = getwidth(tree.lb) * 100
        w2 = getwidth(tree.gb) * 100

        # Determine the total space required by this node
        left = x - (w1 + w2) / 2
        right = x + (w1 + w2) / 2

        # Draw the condition string
        draw.text((x - 20, y - 10), str(tree.col) + ':' + str(tree.value), (0, 0, 0))

        # Draw links to the branches
        draw.line((x, y, left + w1 / 2, y + 100), fill=(255, 0, 0))
        draw.line((x, y, right - w2 / 2, y + 100), fill=(255, 0, 0))

        # Draw the branch nodes
        drawnode(draw, tree.lb, left + w1 / 2, y + 100)
        drawnode(draw, tree.gb, right - w2 / 2, y + 100)
    else:
        txt = str(tree.results)
        draw.text((x - 20, y), txt, (0, 0, 0))
複製代碼

以後就是運行主函數了:

if __name__ == '__main__':
    data = loadDataSet('../Data/LogiReg_data.txt')
    tree = buildTree(data)
    drawtree(tree, jpeg='treeview_cart.jpg')
    gbdt_results, y = GBDT_model(data, 10)
    print(y)
    for i in range(len(y)):
        if y[i] > 0:
            print('1')
        elif y[i] < 0:
            print('0')
複製代碼

效果:
效果貌似仍是能夠的。aggregation model就到此爲止了,幾乎全部經常使用模型都講完了。

最後符上GitHub全部代碼: github.com/GreenArrow2…

相關文章
相關標籤/搜索