组合分类方法之AdaBoost算法实战(单层决策树原理代码详解)---机器学习

版权声明:转载请注明出处: https://blog.csdn.net/weixin_42398658/article/details/83445447

先简单的回顾一下理论(下面的理论来源机器学习实战):

AdaBoost

  AdaBoost的一般流程如下所示:

(1)收集数据

(2)准备数据:依赖于所用的基分类器的类型,这里的是单层决策树,即树桩,该类型决策树可以处理任何类型的数据。

(3)分析数据

(4)训练算法:利用提供的数据集训练分类器

(5)测试算法:利用提供的测试数据集计算分类的错误率

(6)使用算法:算法的相关推广,满足实际的需要

接下来,具体阐述adaBoost分类算法

1 训练算法:基于错误提升分类器的性能

  上面所述的基分类器,或者说弱分类器,意味着分类器的性能不会太好,可能要比随机猜测要好一些,一般而言,在二类分类情况下,弱分类器的分类错误率达到甚至超过50%,显然也只是比随机猜测略好。但是,强分类器的分类错误率相对而言就要小很多,adaBoost算法就是易于这些弱分类器的组合最终来完成分类预测的。

  adaBoost的运行过程:训练数据的每一个样本,并赋予其一个权重,这些权值构成权重向量D,维度等于数据集样本个数。开始时,这些权重都是相等的,首先在训练数据集上训练出一个弱分类器并计算该分类器的错误率,然后在同一数据集上再次训练弱分类器,但是在第二次训练时,将会根据分类器的错误率,对数据集中样本的各个权重进行调整,分类正确的样本的权重降低,而分类错的样本权重则上升,但这些权重的总和保持不变为1.

  并且,最终的分类器会基于这些训练的弱分类器的分类错误率,分配不同的决定系数alpha,错误率低的分类器获得更高的决定系数,从而在对数据进行预测时起关键作用。alpha的计算根据错误率得来:

  alpha=0.5*ln(1-ε/max(ε,1e-16))

其中,ε=为正确分类的样本数目/样本总数,max(ε,1e-16)是为了防止错误率为而造成分母为0的情况发生

  计算出alpha之后,就可以对权重向量进行更新了,使得分类错误的样本获得更高的权重,而分类正确的样本获得更低的权重。D的计算公式如下:

  如果某个样本被正确分类,那么权重更新为:
  D(m+1,i)=D(m,i)*exp(-alpha)/sum(D)

  如果某个样本被错误分类,那么权重更新为:
  D(m+1,i)=D(m,i)*exp(alpha)/sum(D)

其中,m为迭代的次数,即训练的第m个分类器,i为权重向量的第i个分量,i<=数据集样本数量

  当我们更新完各个样本的权重之后,就可以进行下一次的迭代训练。adaBoost算法会不断重复训练和调整权重,直至达到迭代次数,或者训练错误率为0。

2 基于单层决策树构建弱分类器

  单层决策树是一种简单的决策树,也称为决策树桩。这里的需要强调的是,和前面学的决策树有点不同,最大的不同之处在于他的决策依据不同了,我们前面学的是通过熵进行决策,而这里的决策只是通过一个阈值进行决策,即数据如果大于这个值或者小于这个值就会分类为1或者-1,就这么简单,但是这个阈值怎么找才能错误率最低呢?这里使用遍历的方法,怎么遍历呢?很简单,首先给定的数据中肯定有一个最大值和最小值,我们先设定待选的阈值有几个,假定有10个,那么我就通过最大值和最小值的差除以10的结果就是步长了,第一个阈值从最小值开始即把阈值设置为最小值,然后进行分类并计算分类的错误的个数,然后阈值向前步进一个步长然后继续计算分类错误率,计算完所有的阈值以后,然后选择一个最小的分类错误率的阈值为这个决策树的阈值。

    原理就是这样,如果刚开始不理解可能会搞晕的,所以大家需要多看看,下面把代码贴上,注释很详细:

#!/usr/bin/env/python
# -*- coding: utf-8 -*-
# Author: 赵守风
# File name: adaboost.py
# Time:2018/10/27
# Email:[email protected]
import numpy as np

# 建立数据,用于后面的训练
def loadsimdata():
    datmat = np.matrix([[1, 2.1],
                        [2., 1.1],
                        [1.3, 1],
                        [1, 1],
                        [2., 1.]])
    classlabels = [1.0, 1.0, -1.0, -1.0, 1.0]

    return datmat, classlabels


# 单层决策树生成函数
# 这个函数作用是比较数据的某个特征的值和阈值的大小,通过这样进行分类,但是只看这个函数不好理解,需要
# 结合下面的函数进行理解
def stumpclassify(datamatrix, dimen, threshval, threshineq):
    '''
    :param datamatrix: 输入待分类的数据
    :param dimen: 输入数据的某个特征
    :param threshval: 设定的阈值
    :param threshineq: 阈值比较
    :return: 返回分类的结果
    '''
    retarray = np.ones((np.shape(datamatrix)[0], 1))  # 先默认分类都为1
    if threshineq == 'lt':  # 这个是为了找到最优的决策,因此两种情况都讨论了,即大于阈值和小于阈值
        retarray[datamatrix[:, dimen] <= threshval] = -1.0  # 当数据小于阈值时为-1,因为默认为1了,为了准确率,需要考虑大于阈值的情况
    else:
        retarray[datamatrix[:, dimen] > threshval] = -1.0  # 如果考虑大于阈值的情况则也是为-1,这里大家可能会有疑问,这是两种情况,调用这个函数的
        # 函数需要知道错误率最小的决策及阈值,因此他把两种情况都考虑了,即每次前进一步阈值都会更新,每次更新都计算大这个阈值或者小于这个阈值的情况
    return retarray


def buildstump(dataarr, classlabels, D):
    '''
    :param dataarr: 输入数据
    :param classlabels:  数据的真实分类标签
    :param  D: 数据的权值向量
    :return: beststump, minerror, bestclasest 即决策树,最小误差,预测值
    '''
    datamatrix = np.matrix(dataarr)  # 把数据转换为矩阵数据
    labelsmat = np.mat(classlabels).T  # 同理把标签数据转换为矩阵
    m, n = np.shape(datamatrix)  # 得到数据的维度即m行n列
    numsteps = 10.0  # 设置步数,目的是在步数以内找到最优的决策树
    beststump = {}  # 先建立一个空的字典,用作后面存储决策树
    bestclasest = np.mat(np.zeros((m, 1)))  # 预测分类空矩阵
    minerror = np.inf  # 错误率先设置为最大

    for i in range(n):  # 先遍历数据的所有特征
        rangemin = datamatrix[:, i].min()  # 寻找该特征下的最小值
        rangemax = datamatrix[:, i].max()  # 寻找该特征下的最大值
        stepsize = (rangemax - rangemin)/numsteps  # 通过上面来计算步长,为了找到最优的决策
        for j in range(-1, int(numsteps)+1):  # 上面说计算是在numsteps以内找到最优的,因此这个循环是步数
            for inequal in ['lt', 'gt']:  # 遍历大于或者小于两种情况,lt= less than  , gt = great than
                threshval = (rangemin + float(j)*stepsize)  # 通过设置的步和步长计算该步的阈值(他把每一步的值用作阈值,这样找到最优的)
                predictedvals = stumpclassify(datamatrix, i, threshval, inequal)  # 调用函数进行预测所有数据,现在再回去看看这个函数
                errarr = np.mat(np.ones((m, 1)))  # 准备计算错误率类,默认全为1
                errarr[predictedvals == labelsmat] = 0  # 如果相同和真正类别相同则为0,则剩下为1则为分类错误的
                weightederror = D.T*errarr # 保留分错的数据, 分类正确的数据直接为0
                print("split: dim %d, thresh %.2f, thresh ineqal: %s, the weighted error is %.3f"\
                       % (i, threshval, inequal, weightederror))
                if weightederror < minerror:  # 如果误差比之前的还小则更新新返回值,反之继续循环直达循环结束,返回
                    minerror = weightederror
                    bestclasest = predictedvals.copy()
                    beststump['dim'] = i
                    beststump['thresh'] = threshval
                    beststump['ineq'] = inequal

    return beststump, minerror, bestclasest




下面测试一下:

#!/usr/bin/env/python
# -*- coding: utf-8 -*-
# Author: 赵守风
# File name: test.py
# Time:2018/10/27
# Email:[email protected]
import matplotlib.pyplot as plt
import pandas as pd
import  numpy as np
import adaboost

datmat, classlabels = adaboost.loadsimdata()
print('datmat: ', datmat)
print('classlabels', classlabels)
D = np.mat(np.ones((5, 1))/5)
adaboost.buildstump(datmat, classlabels, D)

测试结果为(部分):

datmat:  [[1.  2.1]
 [2.  1.1]
 [1.3 1. ]
 [1.  1. ]
 [2.  1. ]]
classlabels [1.0, 1.0, -1.0, -1.0, 1.0]
split: dim 0, thresh 0.90, thresh ineqal: lt, the weighted error is 0.400
split: dim 0, thresh 0.90, thresh ineqal: gt, the weighted error is 0.600
split: dim 0, thresh 1.00, thresh ineqal: lt, the weighted error is 0.400
split: dim 0, thresh 1.00, thresh ineqal: gt, the weighted error is 0.600
split: dim 0, thresh 1.10, thresh ineqal: lt, the weighted error is 0.400
split: dim 0, thresh 1.10, thresh ineqal: gt, the weighted error is 0.600
split: dim 0, thresh 1.20, thresh ineqal: lt, the weighted error is 0.400
split: dim 0, thresh 1.20, thresh ineqal: gt, the weighted error is 0.600

下面给出基于单层决策树的完整Adaboost代码

先给出伪代码:

完整AdaBoost算法实现
算法实现伪代码
对每次迭代:
    利用buildStump()函数找到最佳的单层决策树
    将最佳单层决策树加入到单层决策树数组
    计算alpha
    计算新的权重向量D
    更新累计类别估计值
    如果错误率为等于0.0,退出循环

 代码如下,如果原理懂了,代码看起来还是很容易的。

def adaBoostTrainDS(dataArr,classLabels,numIt=40):
    weakClassArr = [] # 存储训练好的决策树使用的
    m = np.shape(dataArr)[0] # 取出数据的行
    D = np.mat(np.ones((m,1))/m)   #数据的权值初始相等
    aggClassEst = np.mat(np.zeros((m,1)))
    for i in range(numIt):
        bestStump,error,classEst = buildstump(dataArr,classLabels,D)# 建立第i个决策树
        print("D:",D.T)
        alpha = float(0.5*np.log((1.0-error)/max(error,1e-16)))#计算决策树权值
        bestStump['alpha'] = alpha # 更新权值
        weakClassArr.append(bestStump)  # 把第i个决策树添存储
        print("classEst: ", classEst.T)
        expon = np.multiply(-1*alpha*np.mat(classLabels).T,classEst) # 更新数据权值D
        D = np.multiply(D, np.exp(expon))  # 看不懂的建议吧原理搞懂再来看
        D = D/D.sum()
        # calc training error of all classifiers, if this is 0 quit for loop early (use break)
        aggClassEst += alpha*classEst
        print("aggClassEst: ",aggClassEst.T)
        aggErrors = np.multiply(np.sign(aggClassEst) != np.mat(classLabels).T,np.ones((m,1)))
        errorRate = aggErrors.sum()/m
        print("total error: ",errorRate)
        if errorRate == 0.0: break # 错误率为0时返回迭代
    return weakClassArr,aggClassEst

下面的测试就不贴了,很简单。

猜你喜欢

转载自blog.csdn.net/weixin_42398658/article/details/83445447