The Optimization of the Adaboost and Gradient Boosted Decision Tree

The Optimization of the Adaboost

1.对于Adaboost error function的推导

再回到咱们上篇文章讲到的Adaboost算法,咱们要从Adaboost算法推导出GBDT。首先回顾一下上篇文章的Adaboost,主要思想就是把弱分类器集中起来获得一个强的分类器。首先第一次建造树的时候每个样本的权值都是同样的,以后的每一次训练只要有错误,那么这个错误就会被放大,而正确的权值就会被缩小,以后会获得每个模型的α,根据每个树的α把结果结合起来就获得须要的结果。
node

10624272-852c5746735815f0.png

在Adaboost里面,Ein表达式:
10624272-66b23acac2234e82.png

每个错误的点乘权值相加求平均,咱们想把这一个特征结合到decision tree里面,那么就须要咱们在决策树的每个分支下面加上权值,这样很麻烦。为了保持决策树的封闭性和稳定性,咱们不改变结构,只是改变数据的结果,喂给的数据乘上权值,其余的不改变,把决策树当成是一个黑盒子。
权值u其实就是这个数据用boostrap抽样抽到的几率,能够作一个带权抽样,也就是带了u的sample,这样抽出来的数据没一个样本和u的比例应该是差很少一致的,因此带权sampling也叫boostrap的反操做。这种方法就是对数据自己进行改变而对于算法自己的数据结构不变。
10624272-7e3876db9e4e66d0.png

10624272-0b31edf509777fce.png

上面的步骤就是获得gt,接下来就是求α了,α就是每个model的重要性,上节课咱们是讲到过怎么求的,先获得错误率ξ,而后:
10624272-c90fe9ec1ffe375b.png

这个东西很重要,以后咱们会对这公式作推导。

①为何Adaboost要弱类型的分类器?
分类器讲道理,应该是越强越好的,可是Adaboost相反,他只要弱的,强的不行。来看一下α,若是你的分类器很强,ξ基本就是equal 0了,这样的α是无穷大的,其余的分类器就没有意义了,这样就又回到了单分类器,而单分类器是作不到aggregation model的好处的,祥见aggregation model这篇文章。
git

10624272-a7c46938ed3ace36.png

咱们把这种称为autocracy,独裁。针对这两个缘由,咱们能够作剪枝,限制树的大小等等的操做,好比只使用一部分样本,这在sampling的操做中已经起到这类做用,由于必然有些样本没有被采样到。
10624272-41f9cda4a3167df3.png

因此,综上缘由,Adaboost经常使用的模型就是decision stump,一层的决策树。
10624272-c87b542c40b61f4a.png

10624272-04af26ed55e573bf.png

事实上,若是树高为1时,一般较难遇到ξ=0的状况,且通常不采用sampling的操做,而是直接将权重u代入到算法中。这是由于此时的AdaBoost-DTree就至关因而AdaBoost-Stump,而AdaBoost-Stump就是直接使用u来优化模型的。前面的算法实现也是基于这些理论实现的算法。

回到正题,继续从optimization的角度讨论Adaboost,Adaboost权重计算以下:
github

10624272-848666cd16af45bf.png

把上面两张形式结合一下,获得下面综合形式。第一层的u是1/N,每一次递推要乘上
10624272-edc8225ba9e0d040.png

因而,u(t+1):
10624272-8d82b41e684f0c7f.png

而在这里面
10624272-66b2bccd3a6d7cad.png
称为是voting score,也就是集合投票的分数,而u(t+1)是和voting score成正比的:
10624272-091a3539f6be95c5.png

仔细看一下,voting score是由许多的g(x)经过不一样的α进行线性组合而成的,换一个角度,把g(x)看作是特征转换的φ(x),α就是权重w,这样一来再和SVM对比一下:
10624272-397decae570354fa.png

对比上面实际上是很像的,乘上的y就是表名是在正确的一侧仍是错误的一侧,因此,这里的voting score也能够看作是没有正规化的距离。也就是没有除|w|的距离,因此从效果上看,voting score是要越大越好。再来看以前的乘上y的表达式,咱们要作的就是要使得分类正确,因此y的符号和voting score符号相同的数量会愈来愈多,这样也就保证了这个voting score是在不断增大,距离不断增大。这种距离要求大的在SVM里面咱们叫正则化——regularization,一样,这样就证实了①Adaboost的regularization。刚刚的g(x)看作是φ其实就是feature transform,因此也证实了②Adaboost的feature transform。这两个特性一个是踩刹车一个是加速,效果当然是比单分类器好的。
10624272-8d82b41e684f0c7f.png

因此根据上面的公式,能够获得u是在不断减少的:
10624272-805d3ee4001113e8.png

因此咱们的目标就是要求通过了(t+1)轮的迭代以后,u的数据集合要愈来愈小。有以下:
10624272-e486e1f644003a71.png

②Adaboost的error
上式中咱们把voting score看作是s。对于0/1error,若是ys >= 0,证实是分类正确的,不惩罚err0/1 = 0,;若是ys < 0,证实分类错误,惩罚err0/1 = 1。对于Adaboost,咱们能够用算法

10624272-d711b058e10f86a2.png

来表示,可能有些同窗以为这里不太对, 这里明明是u的表达式为何能够做为错误的衡量?这个表达式里面包含了y和s,正好能够用来表达Adaboost的error,是就使用了。
10624272-8631c494abbef1bc.png

正好也是减少的,和咱们须要的err是同样的,要注意这里Adaboost咱们是反推,已知到效果和方法反推表达式,使用要用上一切可使用的。
和error0/1对比:
10624272-a631633e11f5a8fa.png

10624272-a80cab580ebafcf1.png

因此,Adaboost的error function是能够代替error0/1的,而且效果更好。

2.对于Adaboost error function的一些简单处理

③首先要了解一些泰勒展开:
在通常的函数状况下,是有:
数据结构

10624272-c9523e5bfeb79da6.png

①:
10624272-360a6d482b2168ed.png

②:
10624272-f966f0fb83d9b860.png

①②式子代入上面:
10624272-d8865675acc17410.png
10624272-001912e89e2a8cbb.png

这就是一阶Taylor expansion。待会要利用他们来求解。

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

10624272-e79d545cdae2b057.png

这是Gradient descent的公式,咱们类比一下error function:
10624272-f7f1c185d27ab832.png

这里是使用exp(x) 的Taylor一阶展开,这里的方向是h(x),而Gradient descent是w才是方向,w是某一个点的方向,而h(x)也能够代入某一个点获得一个value,这个value就是方向,因此基本上和梯度降低是一致的:
10624272-d818384007f62c85.png
以上的对于error function的分解是在x = 0的点进行Taylor展开,能够看到前面的一项是常数,后面的一项才是变量,使用咱们须要优化的就是减少后面一项的值。因此咱们先要找到一个最合适的h(x)来优化这个函数,n先忽略:
10624272-c2a618c25b24e397.png

对于y和h(x)咱们均限定是{1,-1},对这个优化项作一些平移:
10624272-d6bfacdec6b3fb85.png

因此要优化的项最后又转变成了Ein,咱们的演算法一直都是在作减少Ein这件事,因此Adaboost的base algorithm——decision stump就是作的这件事。h(x)就能够解决了。
10624272-70b62671c722712d.png

解决了h(x)的问题接下来就是η的问题了:
10624272-65fd3e88b043ebb9.png

10624272-5c8fec73f6b5be73.png

因此最后的公式如上图所示。
上式中有几个状况能够考虑:
10624272-fb0cb1be993dce14.png

通过推导:
10624272-21bc1da660bb86ef.png

10624272-69700a643938189c.png

求η最小值天然就是求导了:
10624272-3ab88b5f6737ade4.png

就获得:
10624272-e9850a420c2df85f.png

而η = α,这样就推导出了α的表达式了。
Adaboost实际上就是在寻找最块降低方向和最快降低步长的过程当中优化,而α至关于最大的步长,h(x)至关于最快的方向。因此,Adaboost就是在Gradient descent上寻找最快的方向和最快的步长。

Gradient Boosted Decision Tree

推导完了Adaboost,咱们接着推导Gradient Boosted Decision Tree,其实看名字就知道只不过是error function不太同样而已。前面Adaboost的推导总的能够归纳为:less

10624272-5b3e98e01725b24e.png

这种exp(-ys)function是Adaboost专有的,咱们能不能换成其余的?好比logistics或者linear regression的。
10624272-da2d376265b48494.png

使用Gradient descent的就是这种形式,虽然形式变了,可是最终的结果都是求解最快的方向和最长的步长。
10624272-dd0f7df75592dd1e.png

10624272-31ce06e367b1a11e.png

这里使用均方差替代error。使用一阶泰勒展开:
10624272-b61f2d732b2d2104.png

constant咱们不须要管,咱们只须要关心最后的一项。使得这一项最小,那只须要h(x)和2(s - y)互为相反数,而且h(x)很大很大就行了,h(x)不作限制,很明显这样是求不出来的。有一个简单的作法,收了regularization的启发,咱们能够在后面加上惩罚项,使用L2范式,L2范式会使他们很小但不会为0,可是L1范式会使得他们集中到边角上,系数矩阵,使用这里使用L2范式,另外一方面,也是对于化简的方便作了准备。
10624272-feda1b65abf61d52.png

因此,优化的目标:
10624272-4d9cf15805168451.png

y-s咱们称为残差,咱们要作的就是使得h(x)和(y - s)接近,也就是作一个拟合的过程,也就是作regression。
10624272-d90c1097cb35af89.png

10624272-f8f155085aaa6a49.png

拟合出来获得的h(x)就是咱们要的gt(x)了。以后就是求η了。
③:
10624272-df26cd4aa152a1d2.png

获得了g(x)以后就是求η步长了。注意上面的③才是咱们要求的公式,
10624272-c56baf185ae7cd80.png
这一个知识为了化简方便的。
10624272-fd9fffa628f16910.png

10624272-2365062725ba2d04.png

总结一下,以上就是GBDT的流程了。值得注意的是,sn的初始值通常均设为0,即s1=s2=⋯=sN=0。每轮迭代中,方向函数gt经过C&RT算法作regression,进行求解;步进长度η经过简单的单参数线性回归进行求解;而后每轮更新sn的值,即sn←sn+αtgt(xn)。T轮迭代结束后,最终获得
10624272-7e298f6da1fdbf79.png
。值得一提的是,本节课第一部分介绍的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

10624272-60b903221e94f19b.png

刚刚所讨论的model都是创建在g(x)已知的状况下,若是g(x)不知道,咱们就可使用一下方法:
①Bagging:经过boostrap方法训练模型平均结合结果。
②Adaboost:经过boostrap方法训练模型进行线性组合。
③Decision Tree:数据分割获得不一样的g(x)进行线性组合。

10624272-a866f3c0f29d0374.png

除了以上的方法,咱们还能够把Bagging和Decision Tree结合起来称为random forest,Adaboost和decision tree结合起来就是Adaboost-stump,Gradient Boosted和Adaboost结合起来就是GBDT了。
10624272-6eeeb01adfd801c5.png

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')
10624272-21b040afef2b98b1.png

效果:


10624272-c51ee23929e69689.png

效果貌似仍是能够的。aggregation model就到此为止了,几乎全部经常使用模型都讲完了。

最后符上GitHub全部代码:
https://github.com/GreenArrow2017/MachineLearning/tree/master/MachineLearning/GBDT