[机器学习] - 提升方法AdaBoost

Adaboost是一种集成学习的方法,当采用基于简单模型的单个分类器对样本进行分类的效果不理想时,人们希望能够通过构建并整合多个分类器来提高最终的分类性能。

Boosting方法并不是简单地对多个分类器的输出进行投票决策,而是通过一种迭代过程对分类器的输入和输出进行加权处理。在不同应用中可以采用不同类型的弱分类器,在每次迭代过程中,根据分类的情况对各个样本进行加权,而不仅仅是简单的重采样。

弱分类器:人们常称不理想的单个分类器称为弱分类器

1 Adaboost算法流程

Boosting算法的训练过程:
        
  
  目前,最为广泛使用的Boosting方法是提出的AdaBoost算法。这里对这个算法做一个简单的介绍。
  设给定 N N N个训练样本 { x 1 , x 2 , . . . . x N } {\{x_1,x_2,....x_N}\} { x1,x2,....xN},用 G m ( x ) ∈ { − 1 , 1 } ( m = 1 , . . . , M ) {G_m}\left( x \right) \in \left\{ { - 1,1} \right\}\left( {m = 1,...,M} \right) Gm(x){ 1,1}(m=1,...,M)表示 M M M个弱分类器在样本 x x x上的输出,通过AdaBoost算法构造这 M M M个分类器并进行决策的具体过程如下:
  1. 初始化训练样本 { x 1 , x 2 , . . . . x N } {\{x_1,x_2,....x_N}\} { x1,x2,....xN}的权重 w i = 1 / N , i = 1 , . . . . N {w_i} = 1/N,{\kern 1pt} {\kern 1pt} {\kern 1pt} {\kern 1pt} {\kern 1pt} {\kern 1pt} {\kern 1pt} i = 1,....N wi=1/N,i=1,....N
  
  2. 对 m = 1 → M m = 1 \to M m=1M,重复使用以下过程:
  (1)利用 { w i } \{w_i\} { wi}加权后的训练样本构造分类器 G m ( x ) ∈ { − 1 , 1 } {G_m}\left( x \right) \in \left\{ { - 1,1} \right\} Gm(x){ 1,1}。(注意构造弱分类器的具体算法可以不同,例如使用线性分类器和决策树等。)
  (2)计算样本用 { w i } \{w_i\} { wi}加权后的分类错误率 e m e_m em,并令 c m = 1 / 2 ∗ log ⁡ ( ( 1 − e m ) / e m ) {c_m} = 1/2*\log \left( {\left( {1 - {e_m}} \right)/{e_m}} \right) cm=1/2log((1em)/em)
  (3)更新训练数据集的权重分布:
     Z m = ∑ i = 1 N w m i exp ⁡ ( − c m y i G m ( x ) ) {Z_m} = \sum\limits_{i = 1}^N { {w_{mi}}} \exp \left( { - {c_m}{y_i}{G_m}\left( x \right)} \right) Zm=i=1Nwmiexp(cmyiGm(x)) Z Z Z是规范化因子。
     w m + 1 , i = w m i ⋅ exp ⁡ ( − c m y i G m ( x ) ) Z m , i = 1 , . . . N {w_{m + 1,i}} = \frac{ { {w_{mi}} \cdot \exp \left( { - {c_m}{y_i}{G_m}\left( x \right)} \right)}}{ { {Z_m}}},i=1,...N wm+1,i=Zmwmiexp(cmyiGm(x))i=1,...N
     D m + 1 = ( w m + 1 , 1 , w m + 1 , 2 , . . . . . . . w m + 1 , N ) D_{m+1}=({w_{m+1,1},w_{m+1,2},.......w_{m+1,N}}) Dm+1=(wm+1,1,wm+1,2,.......wm+1,N) 新的权值分布。
  
  3. 构造基本分类器的线性组合
     f ( x ) = ∑ m = 1 M c m G m ( x ) f\left( x \right) = \sum\limits_{m = 1}^M { {c_m}} {G_m}\left( x \right) f(x)=m=1McmGm(x)
   对于待分类样本 x x x,分类器的输出为 s i g n [ ∑ m = 1 M c m G m ( x ) ] sign[\sum\limits_{m = 1}^M { {c_m}} {G_m}\left( x \right)] sign[m=1McmGm(x)]

这里补充说明几点:
(1) M M M表示设置迭代的次数,也是AdaBoost算法构造弱分类器的最大个数。
(2)错误率 e m e_m em小于一个最小值时停止循环,或者是 e m e_m em==0停止循环也行。 e m e_m em c m {c_m} cm中作为分母,设计程序时需要避开 e m = 0 e_m=0 em=0的情况。

2 最佳的单层决策树

接下来,只训练一个弱分类器。

(1)数据集:

{
    
    1,2.1,1
1.5,1.6,1
1.3,1,-1
1,1,-1
2,1,1}

(2)图
        
从图中发现,训练一个单层决策树的弱分类器,无论怎么划分,都存在一个点误分类。

(3)模型测试

# 找出数据集上最佳的单层决策树
def get_Stump(xMat, yMat, D):
    """
    参数说明:
        xMat:特征矩阵
        yMat:标签矩阵
        D:样本权重
    返回:
        bestStump:最佳单层决策树信息
        minE:最小误差
        bestClass:最佳的分类结果
    """
    m, n = xMat.shape  #m为样本的个数,n为特征数
    Step = 10  # 初始化一个步数
    bestStump = {
    
    }  # 用字典形式来存储树桩信息
    bestClass = np.mat(np.zeros((m, 1)))  # 初始化分类结果为1
    minE = np.inf  # 最小误差初始化为正无穷大
    for i in range(n):  # 遍历所有特征值
        min = xMat[:, i].min()  # 找到特征的最小值
        max = xMat[:, i].max()  # 找到特征的最大值
        stepSize = (max - min)/ Step  # 计算步长
        for j in range(-1, int(Step)+1):
            for S in ['lt', 'gt']:  # 大于和小于的情况,均遍历
                Q = (min + j * stepSize)  # 计算阈值
                re = Classify0(xMat, i, Q, S)  # 计算分类结果
                err = np.mat(np.ones((m,1)))  # 初始化误差矩阵
                err[re == yMat] = 0  # 分类正确的,赋值为0
                eca = D.T * err  # 计算误差
                if eca < minE:  # 找到误差最小的分类方式
                    minE = eca
                    bestClass = re.copy()
                    bestStump['特征值'] = i
                    bestStump['阈值'] = Q
                    bestStump['标志'] = S
    return bestStump, minE, bestClass

(4)训练结果

bestStump:{
    
    '特征值': 0, '阈值': 1.3, '标志': 'lt'}

minE:[[0.2]]

bestClass:[[-1.]
            [ 1.]
            [-1.]
            [-1.]
            [ 1.]]

3 基于单层决策树的Adaboost集成训练

单层决策树程序设计的传入参数有

扫描二维码关注公众号,回复: 12958838 查看本文章

xMat:特征矩阵
yMat:标签矩阵
D:样本权重

在上述讲解的算法步骤中,第二步的第三小步,更新训练数据集的权重分布,将更新的权值作为参数D重新传入到单层决策树中,返回误差minE通过公式 c m = 1 / 2 ∗ log ⁡ ( ( 1 − e m ) / e m ) {c_m} = 1/2*\log \left( {\left( {1 - {e_m}} \right)/{e_m}} \right) cm=1/2log((1em)/em)得到的 c m {c_m} cm作为新增分类器的权重。

Adaboost集成训练的程序:

# 基于单层决策树的Adaboost训练过程
def Ada_train(xMat, yMat, maxC=40):
    """
    函数功能:基于单层决策树的Adaboost训练过程
    参数说明:
        xMat:特征矩阵
        yMat:标签矩阵
        maxC:最大迭代次数
    返回:
        weakClass:弱分类器信息
        aggClass:类别估值(更改标签估值)
    """
    weakClass = []
    m = xMat.shape[0]
    D = np.mat(np.ones((m, 1))/m)  # 初始化权重
    aggClass = np.mat(np.zeros((m,1)))
    for i in range(maxC):
        Stump, error, bestClass = get_Stump(xMat, yMat, D)  # 构造单层决策树
        alpha = float(0.5*np.log((1-error)/max(error, 1e-6)))  # 计算弱分类器权重alpha
        Stump['alpha'] = np.round(alpha, 2)
        weakClass.append(Stump)  # 存储单层决策树
        expon = np.multiply(-1*alpha*yMat, bestClass)  # 计算e的指数项
        D = np.multiply(D, np.exp(expon))
        D = D / D.sum()  # 更新权重
        aggClass += alpha * bestClass  # 计算类别估值(更改标签估值)
        aggErr = np.multiply(np.sign(aggClass) != yMat, np.ones((m, 1)))  # 统计误分类样本数
        errRate = aggErr.sum()/m
        print("total error: ", errRate)
        if errRate == 0:
            break
    return weakClass, aggClass

4 病马训练集的测试

完整程序:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Date  : 2020/6/30 15:46


"""
1.实现单层决策树,找出数据集上最佳的单层决策树
2.实现多层的分类(adaboost)
"""

import numpy as np
import matplotlib.pyplot as plt


# 加载文件
def loadDataSet(path = 'horseColicTraining2.txt'):
    data = list()
    labels = list()
    with open(path) as f:
        lines = f.readlines()
        for line in lines:
            line = line.rstrip().split('\t')
            lineArr = []
            for i in range(len(line)-1):
                lineArr.append(float(line[i]))
            data.append(lineArr)
            labels.append(float(line[-1]))
        xMat = np.array(data)
        yMat = np.array(labels).reshape(-1, 1)
    return xMat, yMat


# 按照阈值分类结果
def Classify0(xMat, i, Q, S):
    """
    xMat:数据矩阵
    Q:阈值
    S:标志
    """
    re = np.ones((xMat.shape[0], 1))
    if S == 'lt':
        re[xMat[:, i] <= Q] = -1  # 如果小于阈值,则赋值为-1
    else:
        re[xMat[:, i] > Q] = 1  # 如果大于阈值,则赋值为1
    return re


# 找出数据集上最佳的单层决策树
def get_Stump(xMat, yMat, D):
    """
    参数说明:
        xMat:特征矩阵
        yMat:标签矩阵
        D:样本权重
    返回:
        bestStump:最佳单层决策树信息
        minE:最小误差
        bestClass:最佳的分类结果
    """
    m, n = xMat.shape  #m为样本的个数,n为特征数
    Step = 10  # 初始化一个步数
    bestStump = {
    
    }  # 用字典形式来存储树桩信息
    bestClass = np.mat(np.zeros((m, 1)))  # 初始化分类结果为1
    minE = np.inf  # 最小误差初始化为正无穷大
    for i in range(n):  # 遍历所有特征值
        min = xMat[:, i].min()  # 找到特征的最小值
        max = xMat[:, i].max()  # 找到特征的最大值
        stepSize = (max - min)/ Step  # 计算步长
        for j in range(-1, int(Step)+1):
            for S in ['lt', 'gt']:  # 大于和小于的情况,均遍历
                Q = (min + j * stepSize)  # 计算阈值
                re = Classify0(xMat, i, Q, S)  # 计算分类结果
                err = np.mat(np.ones((m,1)))  # 初始化误差矩阵
                err[re == yMat] = 0  # 分类正确的,赋值为0
                eca = D.T * err  # 计算误差
                if eca < minE:  # 找到误差最小的分类方式
                    minE = eca
                    bestClass = re.copy()
                    bestStump['特征值'] = i
                    bestStump['阈值'] = Q
                    bestStump['标志'] = S
    return bestStump, minE, bestClass


# 基于单层决策树的Adaboost训练过程
def Ada_train(xMat, yMat, maxC=40):
    """
    函数功能:基于单层决策树的Adaboost训练过程
    参数说明:
        xMat:特征矩阵
        yMat:标签矩阵
        maxC:最大迭代次数
    返回:
        weakClass:弱分类器信息
        aggClass:类别估值(更改标签估值)
    """
    weakClass = []
    m = xMat.shape[0]
    D = np.mat(np.ones((m, 1))/m)  # 初始化权重
    aggClass = np.mat(np.zeros((m,1)))
    for i in range(maxC):
        Stump, error, bestClass = get_Stump(xMat, yMat, D)  # 构造单层决策树
        alpha = float(0.5*np.log((1-error)/max(error, 1e-6)))  # 计算弱分类器权重alpha
        Stump['alpha'] = np.round(alpha, 2)
        weakClass.append(Stump)  # 存储单层决策树
        expon = np.multiply(-1*alpha*yMat, bestClass)  # 计算e的指数项
        D = np.multiply(D, np.exp(expon))
        D = D / D.sum()  # 更新权重
        aggClass += alpha * bestClass  # 计算类别估值(更改标签估值)
        aggErr = np.multiply(np.sign(aggClass) != yMat, np.ones((m, 1)))  # 统计误分类样本数
        errRate = aggErr.sum()/m
        print("total error: ", errRate)
        if errRate == 0:
            break
    return weakClass, aggClass


# 开始对待预测的数据进行分类
def AdaClassify(xMat, weakClass):
    m = xMat.shape[0]  # 待分类数据集的长度
    aggClass = np.mat(np.zeros((m, 1)))

    for i in range(len(weakClass)):  # 遍历所有分类器进行分类
        classEst = Classify0(xMat,
                             weakClass[i]['特征值'],
                             weakClass[i]['阈值'],
                             weakClass[i]['标志'],
                             )
        aggClass += classEst * weakClass[i]['alpha']
    return np.sign(aggClass)


# ROC图像
def plotROC(predStrengths, classLabels):
    """
    输入:
        predStrengths : Adaboost预测的结果(行)
        classLabels : 原本训练数据的标签(列)
    """
    cur = (1.0, 1.0)  # 光标
    ySum = 0.0  # 变量来计算AUC
    numPosClas = sum(np.array(classLabels) == 1.0)
    yStep = 1 / float(numPosClas)  # 向上的步长
    xStep = 1 / float(len(classLabels) - numPosClas)  # 向右的步长
    sortedIndicies = predStrengths.argsort()  # 得到排序索引,它是反向的
    fig = plt.figure()
    fig.clf()
    ax = plt.subplot(111)
    # 循环所有的值,在每个点上画一条线段
    for index in sortedIndicies.tolist()[0]:
        if classLabels[index] == 1.0:
            delX = 0
            delY = yStep
        else:
            delX = xStep
            delY = 0
            ySum += cur[1]
        # draw line from cur to (cur[0]-delX,cur[1]-delY)
        ax.plot([cur[0], cur[0] - delX], [cur[1], cur[1] - delY], c='b')  # 逐渐加入曲线变化的一条直线
        cur = (cur[0] - delX, cur[1] - delY)  # 重新更新cur的起始点

    ax.plot([0, 1], [0, 1], 'b--')
    plt.xlabel('False positive rate')
    plt.ylabel('True positive rate')
    plt.title('ROC curve for AdaBoost horse colic detection system')
    ax.axis([0, 1, 0, 1])
    plt.show()
    print("the Area Under the Curve is: ", ySum * xStep)


if __name__ == '__main__':
    xMat, yMat = loadDataSet(path='horseColicTraining2.txt')  # 训练数据

    # 测试单层决策树
    m = xMat.shape[0]
    D = np.mat(np.ones((m, 1))/m)
    bestStump, minE, bestClass = get_Stump(xMat, yMat, D)

    # 测试单层决策树的Adaboost训练过程
    weakClass, aggClass = Ada_train(xMat, yMat, maxC=10)  # 返回弱分类器的集合,以及弱分类的标签值
    print('分类器的个数:', len(weakClass))
    testArr, testLabelArr = loadDataSet(path='horseColicTest2.txt')  # 测试数据
    pre = AdaClassify(testArr, weakClass)  # 返回预测值

    # 计算准确度
    errArr = np.mat(np.ones((len(pre), 1)))  # 一共有m个预测样本
    cnt = errArr[pre != testLabelArr].sum()
    print('误分类点在总体预测样本中的比例为:', cnt / len(pre))

    # 绘画出ROCplotROC(aggClass.T, yMat)

结果(1):

分类器为: 
[{
    
    '特征值': 2, '阈值': 36.72, '标志': 'lt', 'alpha': 0.27}, 
{
    
    '特征值': 0, '阈值': 1.0, '标志': 'lt', 'alpha': 0.15}, 
{
    
    '特征值': 18, '阈值': 0.0, '标志': 'lt', 'alpha': 0.14}, 
{
    
    '特征值': 18, '阈值': 53.400000000000006, '标志': 'lt', 'alpha': 0.16}, 
{
    
    '特征值': 0, '阈值': 0.9, '标志': 'lt', 'alpha': 0.06}, 
{
    
    '特征值': 12, '阈值': 1.2, '标志': 'lt', 'alpha': 0.05}, 
{
    
    '特征值': 0, '阈值': 1.0, '标志': 'lt', 'alpha': 0.04},
{
    
    '特征值': 18, '阈值': 53.400000000000006, '标志': 'lt', 'alpha': 0.03}, 
{
    
    '特征值': 0, '阈值': 0.9, '标志': 'lt', 'alpha': 0.04}, 
{
    
    '特征值': 4, '阈值': 57.599999999999994, '标志': 'lt', 'alpha': 0.04}]

结果(2):
ROC曲线图为:
        

结果(3):

误分类点在总体预测样本中的比例为: 0.3283582089552239
the Area Under the Curve is:  0.6757359086266125

参考资料
1 https://www.bilibili.com/video/BV1it411q7wy?from=search&seid=15656450157548541974
2 《统计学习方法》- 李航

猜你喜欢

转载自blog.csdn.net/qq_41709378/article/details/106879741
今日推荐