集成方法之AdaBoost

AdaBoost学习笔记


AdaBoost (adaptive boosting:自适应 boosting)概述

      

       首先我们就来说一下什么是AdaBoost,AdaBoost其实是boosting算法的一个典型的版本,它其实是一种弱分类器的集成方法,而为什么要采用这样子的方法呢?其实,在实际开发中,我们可以发现,找一个强分类器,有时是一件很困难的事情,就比如说构建一棵决策树,但在有很多数据和特征的时候,通常这颗树的深度会非常的深,而且还要经过非常多次迭代才能够把这棵树建好,并且好不容易才建好的这棵可能分类性能还不是很理想,这时我们就可以采取上面所提及AdaBoost算法了。

       所谓的AdaBoost的核心思想其实是,既然找一个强分类器不容易,那么我们干脆就不找了吧!我们可以去找多个弱分类器,这是比较容易实现的一件事情,然后再集成这些弱分类器就有可能达到强分类器的效果了,其中这里的弱分类器真的是很弱,你只需要构建一个比瞎猜的效果好一点点的分类器就可以了,下面就开始说一下adboost的算法步骤吧!

说在步骤之前:

        在写算法步骤的时候,我想我们应该要明白我们的目的,就是我们实现一个AdBoost算法,我们究竟是要做什么呢?

很简单,就是做三件事:

(1)找出一个构建弱分类器的方法;

(2)更新数据的权重矩阵;

(3)计算这些弱分类器的权重。

    先看看工作示意图吧!

                                     

AdaBoost算法步骤:

1.   计算数据Data的规模,假设有m条数据,n个特征;

2.   进行初始化工作,给m条数据赋予相同的权值,而每个权值都设为1/m,这些权值可以构成权重矩阵D

3.   把数据Data和权重矩阵D导入弱分类器构建算法里面(这里暂且先不说怎么构建弱分类器),这时就可以求得第一个弱分类器了,并且还可以得到用该分类器来给数据Data分类的分类结果,也能计算该分类器的错误率,然后进行权值调整,对分类错误的数据对应的权值做增大处理,分类正确的数据的权值做减小处理,更新D;(听网上有人说关于权重的更新是这个意思:其实权重矩阵就是一个概率分布,更新的规则就是,减小弱分类器分类效果较好的数据的概率,增大弱分类器分类效果较差的数据的概率。最终的分类器是个弱分类器的加权平均。

下面是相关的计算公式(公式并不是很难理解):

4.  接着再把数据Data和更新之后的权重矩阵D放进弱分类器构建算法里面构建下一个弱分类器,接下来的操作就和第三部一模一样了,然后不断地重复这一步,直到训练错误率为0或者分类器的数目达到了用户制定的最大数目,则算法停止,即可得到AdBoost分类器了;

5.  模型的使用:假设测试数据是testData,我们只需把testData代入每一个弱分类器里面,得到一组分类结果,然后在这组分类结果里面的每一个分类结果都乘以分类器对应的权重alpha,接着对这组数据做连加操作,然后再把最后的结果放入sign符号函数里面转为1和-1,那么此时已经分好类了,就是1和-1类,也就可以得到testData的预测类别了;

注意:AdBoost最原始的样子只是一个二分类器,不过我们可以使用类似把SVM转为多分类器的方法来把它也转为多分类器,这里就不做过多的说明了。

  如何构建弱分类器?

这里所说的弱分类器,可以有很多种,不过我在这里选从单层普通决策树,也就是一个树桩:

注意:同样在开始之前我还是要说一下我们要做一些什么:

(1)构建一棵只有一个根节点和两个叶子节点的树;

(2)那么我们就需要求三样东西,第一样是这个根节点是什么,也就是使用哪个属性进行分类,第二样是分类的阈值是多少,也就是要找一个值来划分数据变成两类,第三样是选着当大于或是小于阈值的时候,需要分类到-1(一开始默认全是1类,根据阈值,满足条件的就改为-1类,剩下的不变)

下面就开始正式的步骤吧!

o   首先对所有特征进行遍历,因为不知道那个特征用来分类比较好;

o   在每个该特征所对应的那列数据中,每隔一段步长设立一个阈值,对阈值进行遍历,因为不知道最好的阈值在哪里;

o   在阈值下面再对大于和小于号进行遍历,因为不知是大于号还是小于号分为类别-1的效果比较好

o   然后比较每次迭代的错误率,如果该次错误率比最小错误率小的话,就把它的错误率赋给最小错误率,它对应的分类器赋给最好分类器

o   最后,返回最小错误率和最好分类器,还有估计的分类结果。

这里面说得可能不是很清楚,那么结合下面的代码来看可能好一点:

#构建单层普通决策树,其实就是要找一个属性来分类,然后再求它的阈值,并且还需知道是大于还是小于阈值的时候类别变成-1
def buildStump(dataArr, classLabels, D):
    dataMatrix = mat(dataArr)
    labelMat = mat(classLabels).T
    m, n = shape(dataMatrix)
    numSteps = 10.0    #把数据分为均分十份,下面遍历阈值的时候用到
    bestStump = {}     #初始化最好分类器为一个空字典,后面用它做容器
    bestClassEst = mat(zeros((m, 1))) #初始化最终的最好分类结果全部都为1
    minError = 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):
            #对大于小于号进行迭代,因为不知道是大于还是小于阈值的时候类别才需要改为-1,
            #其实就是有些属性和结果成正比,有些成反比,有些没啥关系,不好确定
            for inequal in ['lt', 'gt']:
                threshVal = (rangeMin + float(j) * stepSize) #这个变量代表阈值,其中j始于-1止于10
                predictedVals = stumpClassify(dataMatrix, i, threshVal, inequal)
                errArr = mat(ones((m, 1))) #初始化错误矩阵为全一矩阵
                errArr[predictedVals == labelMat] = 0 #分类正确的那些,改错误矩阵中对应的值为0
                weightedError = D.T * errArr          #用权重矩阵乘以错误矩阵,获得加权错误
                if weightedError < minError:          #用加权错误和最小错误比较大小
                    minError = weightedError          #如果加权错误小于最小错误,则把加权错误的值赋给最小错误
                    bestClassEst = predictedVals.copy()#此时对应的分类结果copy到最好分类结果里
                    #单层普通决策树的内容要有,特征变量,阈值,还有是大于还是小于阈值的时候分类变成-1
                    #更新决策树
                    bestStump['dim'] = i
                    bestStump['thresh'] = threshVal
                    bestStump['ineq'] = inequal
    #把所得的最好决策树,最小错误率,最好分类结果返回去
    return bestStump, minError, bestClassEst

#这个函数的功能就是把原来全部初始化为1的类别,根据阈值来调整
def stumpClassify(dataMatrix, dimen, threshVal, threshIneq):
    retArray = ones((shape(dataMatrix)[0], 1))  #类别返回矩阵,初始化为全1
    if threshIneq == 'lt':
        retArray[dataMatrix[:, dimen] <= threshVal] = -1.0
    else:
        retArray[dataMatrix[:, dimen] > threshVal] = -1.0
    return retArray

既然已经已经建好了弱分类器,那么我们就可以根据adboost算法的步骤来构建adboost分类器算法了,代码如下:

def adaBoostTrainDs(dataArr, classLabels, numIt=40):
    weakClassArr = []#用来存储每一次迭代生成的弱分类器,作为最终的分类器,结构是一个列表里面包含若干个字典
    m = shape(dataArr)[0]
    D = mat(ones((m, 1)) / m)
    aggClassEst = mat(zeros((m, 1)))#一个全零矩阵,用来保存运行时的类别估计值
    for i in range(numIt):
        bestStump, error, classEst = buildStump(dataArr, classLabels, D)
        alpha = float(0.5 * log((1.0 - error) / max(error, 1e-16)))#计算alpha值,max(error, 1e-16)为了防止没有错误时,分母为零
        bestStump['alpha'] = alpha#把alpha值存进对应的单层决策树中
        weakClassArr.append(bestStump)#把单层决策树加入决策树组里
        #更新权值矩阵D
        expon = multiply(-1 * alpha * mat(classLabels).T, classEst)#1*1=1,1*(-1)=-1
        D = multiply(D, exp(expon))
        D = D / D.sum()
        #下面过程主要计算运行时的累积错误率
        #计算累积估计值
        aggClassEst += alpha * classEst
        #sign是一个符号函数就是大于0的返回1.0,小于0的返回-1.0,等于0的返回0.0
        #计算估计错误的数据个数,sign(aggClassEst)可以把估计值变成类别
        aggErrors = multiply(sign(aggClassEst) != mat(classLabels).T, ones((m, 1)))
        errorRate = aggErrors.sum() / m #计算此时的累积错误率
        print("total error: ", errorRate,'\n')
        #如果累计错误率已经是0,那就跳出函数
        if errorRate == 0:
            break
    return weakClassArr, aggClassEst

有了上面所写的代码之后,我们就可以建好adaboost分类器了,上面return的那个weakClassArr就是我们所想要的分类

器,那么我们下面再添加一个函数,我们只需往该函数里面添加测试数据和分类器,就可以获得预测的分类结果了,代码

如下:

#用来给测试数据分类,datToClass是待分类数据,classifierArr是分类器
def adaClassify(datToClass, classifierArr):
    dataMatrix = mat(datToClass)
    m = shape(dataMatrix)[0]
    aggClassEst = mat(zeros((m, 1)))#这个是类别估计值,全零矩阵
    #对所有的弱分类器进行遍历
    for i in range(len(classifierArr)):
        #预测分类
        classEst = stumpClassify(dataMatrix, classifierArr[i]['dim'], classifierArr[i]['thresh'],
                                 classifierArr[i]['ineq'])
        #加权,累积
        aggClassEst += classifierArr[i]['alpha'] * classEst
        print('aggClassEst:\n',aggClassEst,'\n')
    return sign(aggClassEst)#把估计值变成类别


为了方便调用我这里把全部代码都贴一份出来吧!(请使用python3以上版本运行该代码)

这份代码保存的文件名是adaboost.py:

__author__ = 'Administrator'
from cmath import log
from numpy.core.umath import sign
from numpy.matlib import zeros

from numpy import *

#这个用来构建一个虚拟数据
def loadSimpData():
    datMat = matrix([[1.0, 2.1],
                     [2.0, 1.1],
                     [1.3, 1.0],
                     [1.0, 1.0],
                     [2.0, 1.0]])
    classLabels = [1.0, 1.0, -1.0, -1.0, 1.0]
    return datMat, classLabels

#这个函数的功能就是把原来全部初始化为1的类别,根据阈值来调整
def stumpClassify(dataMatrix, dimen, threshVal, threshIneq):
    retArray = ones((shape(dataMatrix)[0], 1))  #类别返回矩阵,初始化为全1
    if threshIneq == 'lt':
        retArray[dataMatrix[:, dimen] <= threshVal] = -1.0
    else:
        retArray[dataMatrix[:, dimen] > threshVal] = -1.0
    return retArray

#构建单层普通决策树,其实就是要找一个属性来分类,然后再求它的阈值,并且还需知道是大于还是小于阈值的时候类别变成-1
def buildStump(dataArr, classLabels, D):
    dataMatrix = mat(dataArr)
    labelMat = mat(classLabels).T
    m, n = shape(dataMatrix)
    numSteps = 10.0    #把数据分为均分十份,下面遍历阈值的时候用到
    bestStump = {}     #初始化最好分类器为一个空字典,后面用它做容器
    bestClassEst = mat(zeros((m, 1))) #初始化最终的最好分类结果全部都为1
    minError = 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):
            #对大于小于号进行迭代,因为不知道是大于还是小于阈值的时候类别才需要改为-1,
            #其实就是有些属性和结果成正比,有些成反比,有些没啥关系,不好确定
            for inequal in ['lt', 'gt']:
                threshVal = (rangeMin + float(j) * stepSize) #这个变量代表阈值,其中j始于-1止于10
                predictedVals = stumpClassify(dataMatrix, i, threshVal, inequal)
                errArr = mat(ones((m, 1))) #初始化错误矩阵为全一矩阵
                errArr[predictedVals == labelMat] = 0 #分类正确的那些,改错误矩阵中对应的值为0
                weightedError = D.T * errArr          #用权重矩阵乘以错误矩阵,获得加权错误
                if weightedError < minError:          #用加权错误和最小错误比较大小
                    minError = weightedError          #如果加权错误小于最小错误,则把加权错误的值赋给最小错误
                    bestClassEst = predictedVals.copy()#此时对应的分类结果copy到最好分类结果里
                    #单层普通决策树的内容要有,特征变量,阈值,还有是大于还是小于阈值的时候分类变成-1
                    #更新决策树
                    bestStump['dim'] = i
                    bestStump['thresh'] = threshVal
                    bestStump['ineq'] = inequal
    #把所得的最好决策树,最小错误率,最好分类结果返回去
    return bestStump, minError, bestClassEst


def adaBoostTrainDs(dataArr, classLabels, numIt=40):
    weakClassArr = []#用来存储每一次迭代生成的弱分类器,作为最终的分类器,结构是一个列表里面包含若干个字典
    m = shape(dataArr)[0]
    D = mat(ones((m, 1)) / m)
    aggClassEst = mat(zeros((m, 1)))#一个全零矩阵,用来保存运行时的类别估计值
    for i in range(numIt):
        bestStump, error, classEst = buildStump(dataArr, classLabels, D)
        alpha = float(0.5 * log((1.0 - error) / max(error, 1e-16)))#计算alpha值,max(error, 1e-16)为了防止没有错误时,分母为零
        bestStump['alpha'] = alpha#把alpha值存进对应的单层决策树中
        weakClassArr.append(bestStump)#把单层决策树加入决策树组里
        #更新权值矩阵D
        expon = multiply(-1 * alpha * mat(classLabels).T, classEst)#1*1=1,1*(-1)=-1
        D = multiply(D, exp(expon))
        D = D / D.sum()
        #下面过程主要计算运行时的累积错误率
        #计算累积估计值
        aggClassEst += alpha * classEst
        #sign是一个符号函数就是大于0的返回1.0,小于0的返回-1.0,等于0的返回0.0
        #计算估计错误的数据个数,sign(aggClassEst)可以把估计值变成类别
        aggErrors = multiply(sign(aggClassEst) != mat(classLabels).T, ones((m, 1)))
        errorRate = aggErrors.sum() / m #计算此时的累积错误率
        print("total error: ", errorRate,'\n')
        #如果累计错误率已经是0,那就跳出函数
        if errorRate == 0:
            break
    return weakClassArr, aggClassEst

    
#用来给测试数据分类,datToClass是待分类数据,classifierArr是分类器
def adaClassify(datToClass, classifierArr):
    dataMatrix = mat(datToClass)
    m = shape(dataMatrix)[0]
    aggClassEst = mat(zeros((m, 1)))#这个是类别估计值,全零矩阵
    #对所有的弱分类器进行遍历
    for i in range(len(classifierArr)):
        #预测分类
        classEst = stumpClassify(dataMatrix, classifierArr[i]['dim'], classifierArr[i]['thresh'],
                                 classifierArr[i]['ineq'])
        #加权,累积
        aggClassEst += classifierArr[i]['alpha'] * classEst
        print('aggClassEst:\n',aggClassEst,'\n')
    return sign(aggClassEst)#把估计值变成类别

下面这一份是用来调用上面那一份代码的,名字随意,不过记得和上面那份代码放在同一个目录下,这份才是用来运行

的:

__author__ = 'Administrator'

import adaboost
from numpy import *
from matplotlib import pyplot as plt

datMat, classLabels = adaboost.loadSimpData()

#画图用的
for i in range(5):
    if(classLabels[i]==1):
        plt.plot(datMat[i,0],datMat[i,1],'or')
        continue
    plt.plot(datMat[i,0],datMat[i,1],'ob')
plt.show()
print('所加载的数据:\n',datMat,'\n')
print('上面数据的类别:\n',classLabels,'\n')

print('测试生成一个弱分类器:\n',adaboost.buildStump(datMat, classLabels, mat(ones((5, 1)) / 5)))

a, b = adaboost.adaBoostTrainDs(datMat, classLabels, 9)
print('最终的adboost分类器的样子:\n',a,'\n')

#序列化
#把模型保存下来,以供以后使用
import pickle
f=open('D:\\dump.txt','wb')
pickle.dump(a,f)
f.close
#看看写入的dump.txt文件,一堆乱七八糟的内容,这些都是Python保存的对象内部信息
#反序列化
#把模型从磁盘中读取出来
f=open('D:\\dump.txt','rb')
adb=pickle.load(f)
f.close
print('从磁盘中读取的adboost分类器的样子:\n',adb,'\n')
print('显然是一摸一样的\n')

c = adaboost.adaClassify([[5, 5], [0, 0]], a)
print('测试结果为:\n',c,'\n')

好了上面说了这么多东西,到了下面,也是时候来点好玩的了,其实当我们真正要用到这些算法的时候,大可不必这么麻

烦的去写一遍,我们其实可以直接调用sklearn库的现成的函数,下面就看一下用sklearn怎么实现吧!

#用sklearn库轻松实现
from sklearn.model_selection import cross_val_score
from sklearn.datasets import load_iris
from sklearn.ensemble import AdaBoostClassifier
iris=load_iris()
from sklearn.cross_validation import train_test_split
train_X,test_X,train_y,test_y=train_test_split(iris.data,iris.target,test_size=0.2,random_state=0)
print('提示信息:上面可能会有一个警告,是一个未来版本警告来的,目前不影响使用\n')
clf=AdaBoostClassifier(n_estimators=100)
clf.fit(train_X,train_y)
print('原来的类别:',test_y,'\n')
pred = clf.predict(test_X)
print('预测的类别:',pred)
print('\n'+'模型的平均正确率为:',clf.score(test_X,test_y))


它里面已经做多了很多东西了已经不仅仅是二分类器了,可以用它来多分类了。

参考资料:《机器学习实战》












  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值