美文网首页
(十六)梯度提升树--回归和分类的算法(gbdt))

(十六)梯度提升树--回归和分类的算法(gbdt))

作者: 羽天驿 | 来源:发表于2020-04-07 08:35 被阅读0次

    一、GBDT

    算法中有两个值,一个预测值,一个真实值,梯度提升树,减小残差,使梯度减小。
    梯度提升回归树,裂分条件是:

    • MSE
    • 均方误差
    • \text{MSE}(y, \hat{y}) = \frac{1}{n_\text{samples}} \sum\limits_{i=0}^{n_\text{samples} - 1} (y_i - \hat{y}_i)^2.
    • y_i 是真实值,\hat{y_i} 预测值
    • <font color = red>梯度提升回归树,划分指标mse算法示例</font>
      • mse.png
      • for循环,计算所有的裂分方式的mse,找变化最大的,作为裂分条件!!!

      • 为什么变化最大,最好的裂分条件???

      • 因为,变化大,我们将相似的数据划归到相同的组中。


    梯度提升树--gradient Boosting DecisionTree ----> GBDT


    ensemble.png

    GBDT也是集成学习Boosting家族的成员,但是却和传统的Adaboost有很大的不同。回顾下Adaboost,我们是利用前一轮迭代弱学习器的误差率来更新训练集的权重,这样一轮轮的迭代下去。GBDT也是迭代,使用了前向分布算法,但是弱学习器限定了只能使用CART回归树模型,同时迭代思路和Adaboost也有所不同。

    在GBDT的迭代中,假设我们前一轮迭代得到的强学习器是ft−1(x)ft−1(x), 损失函数是L(y,ft−1(x))L(y,ft−1(x)), 我们本轮迭代的目标是找到一个CART回归树模型的弱学习器ht(x)ht(x),让本轮的损失损失L(y,ft(x))=L(y,ft−1(x)+ht(x))L(y,ft(x))=L(y,ft−1(x)+ht(x))最小。也就是说,本轮迭代找到决策树,要让样本的损失尽量变得更小。

    GBDT的思想可以用一个通俗的例子解释,假如有个人30岁,我们首先用20岁去拟合,发现损失有10岁,这时我们用6岁去拟合剩下的损失,发现差距还有4岁,第三轮我们用3岁拟合剩下的差距,差距就只有一岁了。如果我们的迭代轮数还没有完,可以继续迭代下面,每一轮迭代,拟合的岁数误差都会减小。

    二、代码算例(回归)

    import numpy as np
    
    from sklearn.ensemble import GradientBoostingRegressor
    
    import matplotlib.pyplot as plt
    
    from sklearn import tree
    
    ### 实际问题,年龄预测,回归问题
    # 简单的数据,算法原理,无论简单数据,还是复杂数据,都一样
    X = np.array([[600,0.8],[800,1.2],[1500,10],[2500,3]])
    y = np.array([14,16,24,26])
    
    # loss  = ls 最小二乘法
    gbdt = GradientBoostingRegressor(n_estimators=3,loss = 'ls',
                                     learning_rate=0.1)#learning_rate 学习率
    gbdt.fit(X,y)#训练
    y_ = gbdt.predict(X)#预测
    y_
    
    array([18.374, 18.916, 21.084, 21.626])
    
    # 目标值,真实值,算法,希望,预测,越接近真实,模型越好!!!
    print(y)
    # 求平均,这个平均值就是算法第一次预测的基准,初始值
    print(y.mean())
    
    [14 16 24 26]
    20.0
    
    # 残差:真实值,和预测值之间的差
    residual = y - y.mean()
    residual
    # 残差,越小越好
    # 如果残差是0,算法完全准确的把数值预测出来!
    
    array([-6., -4.,  4.,  6.])
    
    # 第一颗树,分叉时,friedman-mse (就是均方误差)= 26
    # print('均方误差:',((y - y.mean())**2).mean())
    plt.figure(figsize=(8,8))
    _ = tree.plot_tree(gbdt[0,0],filled=True)
    
    output_5_0.png
    # 梯度下降,降低残差
    residual = residual - learning_rate*residual
    residual
    
    array([-5.4, -3.6,  3.6,  5.4])
    
    # 第二颗树
    plt.figure(figsize=(8,8))
    _ = tree.plot_tree(gbdt[1,0],filled=True)
    
    output_7_0.png
    # 梯度下降,降低残差
    residual = residual - learning_rate*residual
    residual
    
    array([-4.86, -3.24,  3.24,  4.86])
    
    # 第三颗树
    plt.figure(figsize=(8,8))
    _ = tree.plot_tree(gbdt[2,0],filled=True)
    
    output_9_0.png
    # 第三颗树,还要进行梯度下降
    # 梯度下降,降低残差
    residual = residual - learning_rate*residual
    residual
    
    array([-4.374, -2.916,  2.916,  4.374])
    
    # 使用残差一步步,计算的结果
    y_ = y - residual
    y_
    
    array([18.374, 18.916, 21.084, 21.626])
    
    # 使用算法,预测
    gbdt.predict(X)
    
    array([18.374, 18.916, 21.084, 21.626])
    

    三、代码算例(分类)

    import numpy as np
    
    from sklearn.ensemble import GradientBoostingClassifier
    
    
    '''loss函数:Log-loss;
    回归树的分裂准则:MSE;
    树的深度:1(即决策树桩)
    学习率:0.1;'''
    
    X = np.arange(1,11).reshape(-1,1)
    X
    
    array([[ 1],
           [ 2],
           [ 3],
           [ 4],
           [ 5],
           [ 6],
           [ 7],
           [ 8],
           [ 9],
           [10]])
    
    y = np.array([0,0,0,1,1]*2)
    y
    
    array([0, 0, 0, 1, 1, 0, 0, 0, 1, 1])
    
    clf = GradientBoostingClassifier(n_estimators=2,max_depth=1)
    clf.fit(X,y)
    clf.predict(X)
    
    array([0, 0, 0, 0, 0, 0, 0, 0, 1, 1])
    
    # 梯度提升分类树中,只有一颗树
    # 这一颗树预测值是
    clf[0,0].predict(X)
    
    array([-0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625,
            2.5  ,  2.5  ])
    
    from sklearn import tree
    
    _ = tree.plot_tree(clf[0,0],filled=True)
    
    output_7_0.png
    clf[0,0].predict(X)
    
    array([-0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625,
            2.5  ,  2.5  ])
    
    clf[1,0].predict(X)
    
    array([-0.57052111, -0.57052111, -0.57052111, -0.57052111, -0.57052111,
           -0.57052111, -0.57052111, -0.57052111,  2.16820117,  2.16820117])
    
    _ = tree.plot_tree(clf[1,0],filled=True)
    
    output_10_0.png

    根据算法原理,进行计算

    初始化算法,计算F0

    F_0 = np.log(4/6)
    F_0
    
    -0.40546510810816444
    

    计算\widetilde{y} 负梯度

    y_d0 = y - 1/(1 + np.exp(-F_0))
    y_d0
    
    array([-0.4, -0.4, -0.4,  0.6,  0.6, -0.4, -0.4, -0.4,  0.6,  0.6])
    

    拟合第一棵树

    # 分裂标准 mse
    for i in range(1,11):
        if i ==10:
            mse = ((y_d0 - y_d0.mean())**2).mean()
        else:
            left_mse = ((y_d0[:i] - y_d0[:i].mean())**2).mean()
            right_mse = ((y_d0[i:] - y_d0[i:].mean())**2).mean()
            mse = left_mse*i/10 + right_mse*(10-i)/10
        print('从第%d个进行切分'%(i),mse)
    
    从第1个进行切分 0.22222222222222224
    从第2个进行切分 0.2
    从第3个进行切分 0.17142857142857143
    从第4个进行切分 0.22499999999999998
    从第5个进行切分 0.23999999999999994
    从第6个进行切分 0.23333333333333336
    从第7个进行切分 0.20952380952380956
    从第8个进行切分 0.15
    从第9个进行切分 0.2
    从第10个进行切分 0.24
    

    计算左、右两侧叶子的预测值,由公式:

    \gamma_{mj} = \frac{\sum_{x_i \in R_{mj}}\widetilde{y_i}}{\sum_{x_i \in R_{mj}}(y_i - \widetilde{y_i})(1 - y_i + \widetilde{y_i})}

    # 前八个是一类
    # 后两个是一类
    # 分子
    f1 = y_d0[:8].sum()
    # print(f1)
    # 分母
    f2 = ((y[:8] - y_d0[:8])*(1 - y[:8] + y_d0[:8])).sum()
    f2
    gamma1 = np.round(f1/f2,3)
    print('左边决策树分支,预测值:',gamma1)
    
    # 右边分支
    gamma2 =np.round(y_d0[8:].sum()/((y[8:] - y_d0[8:])*(1 - y[8:] + y_d0[8:])).sum(),3)
    print('右边决策树分支,预测值:',gamma2)
    
    左边决策树分支,预测值: -0.625
    右边决策树分支,预测值: 2.5
    
    gamma = np.array([-0.625]*8 + [2.5]*2)
    gamma
    
    array([-0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625,
            2.5  ,  2.5  ])
    
    F_1 =np.round( F_0 + gamma*0.1,4)
    F_1
    
    array([-0.468 , -0.468 , -0.468 , -0.468 , -0.468 , -0.468 , -0.468 ,
           -0.468 , -0.1555, -0.1555])
    

    四、梯度提升树--原理

    import numpy as np
    
    from sklearn.ensemble import GradientBoostingClassifier
    
    from sklearn import tree
    

    以下是树参数

    '''loss函数:Log-loss;
    回归树的分裂准则:MSE;
    树的深度:1(即决策树桩)
    学习率:0.1;'''
    
    'loss函数:Log-loss;\n回归树的分裂准则:MSE;\n树的深度:1(即决策树桩)\n学习率:0.1;'
    

    构造数据

    X = np.arange(1,11).reshape(-1,1)
    y = np.array([0,0,0,1,1]*2)
    display(X,y)
    
    array([[ 1],
           [ 2],
           [ 3],
           [ 4],
           [ 5],
           [ 6],
           [ 7],
           [ 8],
           [ 9],
           [10]])
    
    
    
    array([0, 0, 0, 1, 1, 0, 0, 0, 1, 1])
    

    声明算法,进行训练和预测

    # 默认情况下,损失函数就是Log-loss == 交叉熵!
    clf = GradientBoostingClassifier(n_estimators=100,learning_rate=0.1,max_depth=1)
    clf.fit(X,y)
    y_ = clf.predict(X)
    print('真实的类别:',y)
    print('算法的预测:',y_)
    
    真实的类别: [0 0 0 1 1 0 0 0 1 1]
    算法的预测: [0 0 0 1 1 0 0 0 1 1]
    

    第一颗树

    # 梯度提升分类树中,只有一颗树
    # 这一颗树预测值是
    _ = tree.plot_tree(clf[0,0],filled=True)
    
    output_8_0.png

    第二颗树

    _ = tree.plot_tree(clf[1,0],filled=True)
    
    output_10_0.png

    第三颗树

    _ = tree.plot_tree(clf[2,0],filled=True)
    
    output_12_0.png

    第100颗

    _ = tree.plot_tree(clf[99,0],filled=True)
    
    output_14_0.png

    <font color =red>根据算法原理,进行计算</font>

    步骤一,初始化算法

    初始化算法,计算\rho = F_0(x)

    \rho =F_0(x)= log\frac{\sum\limits_{i=1}^Ny_i}{\sum\limits_{i=1}^N(1 -y_i)}

    # 二分类问题,类别 :0,1
    # [0 0 0 1 1 0 0 0 1 1]
    F0 = np.log(4/6)
    F0
    
    -0.40546510810816444
    

    \widetilde{y} = y - \frac{1}{1+exp(-F(x))} = -\psi'(y,F(x))

    计算\widetilde{y} 导数就是梯度,负梯度

    \psi'(y,F(x)) = -y + \frac{1}{1 + exp(-F(x))}

    # 函数F(X) 初始值F0的负梯度
    yderivative0 = y - 1/(1 + np.exp(-F0))
    yderivative0
    
    array([-0.4, -0.4, -0.4,  0.6,  0.6, -0.4, -0.4, -0.4,  0.6,  0.6])
    

    步骤二,梯度提升树

    拟合第一棵树

    # 分裂标准 mse
    for i in range(1,11):
        if i ==10:
            mse = ((yderivative0 - yderivative0.mean())**2).mean()
        else:
            left_mse = ((yderivative0[:i] - yderivative0[:i].mean())**2).mean()
            right_mse = ((yderivative0[i:] - yderivative0[i:].mean())**2).mean()
            mse = left_mse*i/10 + right_mse*(10-i)/10
        print('从第%d个进行切分'%(i),np.round(mse,4))
    # 从第八个样本这里进行分类,最优的选择,和算法第一颗画图的结果一致
    
    从第1个进行切分 0.2222
    从第2个进行切分 0.2
    从第3个进行切分 0.1714
    从第4个进行切分 0.225
    从第5个进行切分 0.24
    从第6个进行切分 0.2333
    从第7个进行切分 0.2095
    从第8个进行切分 0.15
    从第9个进行切分 0.2
    从第10个进行切分 0.24
    

    计算左、右两侧叶子的预测值,由公式:

    \gamma_{mj} = \frac{\sum_{x_i \in R_{mj}}\widetilde{y_i}}{\sum_{x_i \in R_{mj}}(y_i - \widetilde{y_i})(1 - y_i + \widetilde{y_i})}

    # 前八个是一类
    # 后两个是一类
    # 左边分支
    gamma1 = np.round(yderivative0[:8].sum()/((y[:8] - yderivative0[:8])*(1 - y[:8] + yderivative0[:8])).sum(),3)
    print('左边决策树分支,预测值:',gamma1)
    
    # 右边分支
    gamma2 =np.round(yderivative0[8:].sum()/((y[8:] - yderivative0[8:])*(1 - y[8:] + yderivative0[8:])).sum(),3)
    print('右边决策树分支,预测值:',gamma2)
    
    左边决策树分支,预测值: -0.625
    右边决策树分支,预测值: 2.5
    

    一颗树就拟合完成了,自己计算的,和算法中的第一颗树(画图显示),完全一样

    ---------------------------------------------------------------------------------------------------------------------------------------------------------------

    拟合第二颗树

    # 第一颗预测的结果
    gamma = np.array([-0.625]*8 + [2.5]*2)
    gamma
    
    array([-0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625,
            2.5  ,  2.5  ])
    
    # F(x) 随着梯度提升树,提升,发生变化
    learning_rate = 0.1
    F1 =np.round( F0 + gamma*learning_rate,4) #保留4位小数
    F1
    
    array([-0.468 , -0.468 , -0.468 , -0.468 , -0.468 , -0.468 , -0.468 ,
           -0.468 , -0.1555, -0.1555])
    
    # 计算F1(x)的负梯度
    yderivative1 = np.round(y - 1/(1 + np.exp(-F1)),4)
    yderivative1
    
    array([-0.3851, -0.3851, -0.3851,  0.6149,  0.6149, -0.3851, -0.3851,
           -0.3851,  0.5388,  0.5388])
    
    # 第二颗树分裂标准 mse
    for i in range(1,11):
        if i ==10:
            mse = ((yderivative1 - yderivative1.mean())**2).mean()
        else:
            left_mse = ((yderivative1[:i] - yderivative1[:i].mean())**2).mean()
            right_mse = ((yderivative1[i:] - yderivative1[i:].mean())**2).mean()
            mse = left_mse*i/10 + right_mse*(10-i)/10
        print('从第%d个进行切分'%(i),np.round(mse,4))
    # 第二棵树也是从第八个样本这里进行分类,最优的选择,和算法第二颗画图的结果一致
    
    从第1个进行切分 0.2062
    从第2个进行切分 0.1856
    从第3个进行切分 0.1592
    从第4个进行切分 0.2106
    从第5个进行切分 0.2224
    从第6个进行切分 0.2187
    从第7个进行切分 0.1998
    从第8个进行切分 0.15
    从第9个进行切分 0.1904
    从第10个进行切分 0.2227
    
    # 计算第二颗树的预测值
    # 前八个是一类
    # 后两个是一类
    # 左边分支
    gamma1 = np.round(yderivative1[:8].sum()/((y[:8] - yderivative1[:8])*(1 - y[:8] + yderivative1[:8])).sum(),3)
    print('第二棵树左边决策树分支,预测值:',gamma1)
    
    # 右边分支
    gamma2 =np.round(yderivative1[8:].sum()/((y[8:] - yderivative1[8:])*(1 - y[8:] + yderivative1[8:])).sum(),3)
    print('第二棵树右边决策树分支,预测值:',gamma2)
    
    第二棵树左边决策树分支,预测值: -0.571
    第二棵树右边决策树分支,预测值: 2.168
    

    拟合第三颗树,第二棵树的基础上进行了提升

    # 第二棵树预测值
    gamma = np.array([-0.571]*8 + [2.168]*2)
    gamma
    
    array([-0.571, -0.571, -0.571, -0.571, -0.571, -0.571, -0.571, -0.571,
            2.168,  2.168])
    
    # F(x) 随着梯度提升树,提升,发生变化
    learning_rate = 0.1
    F2 =np.round( F1 + gamma*learning_rate,4) #保留4位小数
    F2
    
    array([-0.5251, -0.5251, -0.5251, -0.5251, -0.5251, -0.5251, -0.5251,
           -0.5251,  0.0613,  0.0613])
    
    # 计算F2(x)的负梯度
    yderivative2 = np.round(y - 1/(1 + np.exp(-F2)),4)
    yderivative2
    
    array([-0.3717, -0.3717, -0.3717,  0.6283,  0.6283, -0.3717, -0.3717,
           -0.3717,  0.4847,  0.4847])
    
    # 第三颗树分裂标准 mse
    for i in range(1,11):
        if i ==10:
            mse = ((yderivative2 - yderivative2.mean())**2).mean()
        else:
            left_mse = ((yderivative2[:i] - yderivative2[:i].mean())**2).mean()
            right_mse = ((yderivative2[i:] - yderivative2[i:].mean())**2).mean()
            mse = left_mse*i/10 + right_mse*(10-i)/10
        print('从第%d个进行切分'%(i),np.round(mse,4))
    # 第三棵树从第三个样本这里进行裂分,最优的选择,和算法第三颗画图的结果一致
    
    从第1个进行切分 0.1935
    从第2个进行切分 0.1744
    从第3个进行切分 0.1498
    从第4个进行切分 0.199
    从第5个进行切分 0.208
    从第6个进行切分 0.2067
    从第7个进行切分 0.1918
    从第8个进行切分 0.15
    从第9个进行切分 0.1827
    从第10个进行切分 0.2088
    
    #  计算第三颗树的预测值
    # 前三个是一类
    # 后七个是一类
    # 左边分支
    gamma1 = np.round(yderivative2[:3].sum()/((y[:3] - yderivative2[:3])*(1 - y[:3] + yderivative2[:3])).sum(),3)
    print('第三棵树左边决策树分支,预测值:',gamma1)
    
    # 右边分支
    gamma2 =np.round(yderivative2[3:].sum()/((y[3:] - yderivative2[3:])*(1 - y[3:] + yderivative2[3:])).sum(),3)
    print('第三棵树右边决策树分支,预测值:',gamma2)
    
    第三棵树左边决策树分支,预测值: -1.592
    第三棵树右边决策树分支,预测值: 0.666
    

    <font color = red>三棵树,已然构造出来了,进行概率计算</font>

    p = \frac{1}{1 + exp(-F(x))} = sigmoid

    # 计算第三颗的F3(x)
    # 第三颗树预测值
    gamma = np.array([-1.592]*3 + [0.666]*7)
    gamma
    
    array([-1.592, -1.592, -1.592,  0.666,  0.666,  0.666,  0.666,  0.666,
            0.666,  0.666])
    
    # F(x) 随着梯度提升树,提升,发生变化
    learning_rate = 0.1
    F3 =np.round( F2 + gamma*learning_rate,4) #保留4位小数
    F3
    
    array([-0.6843, -0.6843, -0.6843, -0.4585, -0.4585, -0.4585, -0.4585,
           -0.4585,  0.1279,  0.1279])
    
    proba = 1/(1 + np.exp(-F3))
    # 类别:0,1,如果这个概率大于等于0.5类别1,小于0.5类别0
    (proba >= 0.5).astype(np.int8)
    
    array([0, 0, 0, 0, 0, 0, 0, 0, 1, 1], dtype=int8)
    
    clf.predict(X)
    
    array([0, 0, 0, 1, 1, 0, 0, 0, 1, 1])
    

    相关文章

      网友评论

          本文标题:(十六)梯度提升树--回归和分类的算法(gbdt))

          本文链接:https://www.haomeiwen.com/subject/wmuruhtx.html