机器学习实战笔记6—AdaBoost

注:此系列文章里的部分算法和深度学习笔记系列里的内容有重合的地方,深度学习笔记里是看教学视频做的笔记,此处文章是看《机器学习实战》这本书所做的笔记,虽然算法相同,但示例代码有所不同,多敲一遍没有坏处,哈哈。(里面用到的数据集、代码可以到网上搜索,很容易找到。)。Python版本3.6

机器学习十大算法系列文章:

机器学习实战笔记1—k-近邻算法

机器学习实战笔记2—决策树

机器学习实战笔记3—朴素贝叶斯

机器学习实战笔记4—Logistic回归

机器学习实战笔记5—支持向量机

机器学习实战笔记6—AdaBoost

机器学习实战笔记7—K-Means

机器学习实战笔记8—随机森林

机器学习实战笔记9—人工神经网络

此系列源码在我的GitHub里:https://github.com/yeyujujishou19/Machine-Learning-In-Action-Codes

一,算法原理:

A) Boosting提升算法

AdaBoost是典型的Boosting算法,属于Boosting家族的一员。在说AdaBoost之前,先说说Boosting提升算法。Boosting算法是将“弱学习算法“提升为“强学习算法”的过程,主要思想是“三个臭皮匠顶个诸葛亮”。一般来说,找到弱学习算法要相对容易一些,然后通过反复学习得到一系列弱分类器,组合这些弱分类器得到一个强分类器。Boosting算法要涉及到两个部分,加法模型和前向分步算法。加法模型就是说强分类器由一系列弱分类器线性相加而成。一般组合形式如下:

其中,h(x;a_{m}) 就是一个个的弱分类器,a_{m}是弱分类器学习到的最优参数,\ss _{m}就是弱学习在强分类器中所占比重,P是所有a_{m}\ss _{m}的组合。这些弱分类器线性相加组成强分类器。前向分步就是说在训练过程中,下一轮迭代产生的分类器是在上一轮的基础上训练得来的。也就是可以写成这样的形式:

由于采用的损失函数不同,Boosting算法也因此有了不同的类型,AdaBoost就是损失函数为指数损失的Boosting算法。

B) AdaBoost原理理解

基于Boosting的理解,对于AdaBoost,我们要搞清楚两点:

  1. 每一次迭代的弱学习h(x;a_{m})有何不一样,如何学习?
  2. 弱分类器权值\ss _{m}如何确定?

对于第一个问题,AdaBoost改变了训练数据的权值,也就是样本的概率分布,其思想是将关注点放在被错误分类的样本上,减小上一轮被正确分类的样本权值,提高那些被错误分类的样本权值。然后,再根据所采用的一些基本机器学习算法进行学习,比如逻辑回归。

对于第二个问题,AdaBoost采用加权多数表决的方法,加大分类误差率小的弱分类器的权重,减小分类误差率大的弱分类器的权重。这个很好理解,正确率高分得好的弱分类器在强分类器中当然应该有较大的发言权。

 C) 举例

为了加深理解,我们来举一个例子。有如下的训练样本,我们需要构建强分类器对其进行分类。x是特征,y是标签。

序号12345678910
x0123456789
y111-1-1-1111-1

令权值分布D1=(w_{1,1},w_{1,2},…,w_{1,10}),
并假设一开始的权值分布是均匀分布:w_{1,i}=0.1,i=1,2,…,10
现在开始训练第一个弱分类器。我们发现阈值取2.5时分类误差率最低,得到弱分类器为:

当然,也可以用别的弱分类器,只要误差率最低即可。这里为了方便,用了分段函数。得到了分类误差率e1=0.3。
第二步计算G_{1}(x)在强分类器中的系数\alpha _{1}= 1/2log((1-e1)/e1)=0.4236,这个公式先放在这里,下面再做推导。
第三步更新样本的权值分布,用于下一轮迭代训练。由公式: 

其中z_{1}计算公式,将下列公式中的m换成1带入即可,(m表示第m次迭代)

 

 

可以看出,被分类正确的样本权值减小了,被错误分类的样本权值提高了。
第四步得到第一轮迭代的强分类器:

以此类推,经过第二轮……第N轮,迭代多次直至得到最终的强分类器。迭代范围可以自己定义,比如限定收敛阈值,分类误差率小于某一个值就停止迭代,比如限定迭代次数,迭代1000次停止。这里数据简单,在第3轮迭代时,得到强分类器:

 的分类误差率为0,结束迭代。
F(x)=sign(F_{3}(x))就是最终的强分类器。

 

二,算法的优缺点:

优点:泛华错误率低,易编码,可以应用在大部分分类器上,无参数调整。

缺点:对离群点敏感。

适用数据类型:

三,实例代码:

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

建立AdaBoost算法之前,我们必须先建立弱分类器,并保存样本的权重。弱分类器使用单层决策树(decision stump),也称决策树桩,它是一种简单的决策树,通过给定的阈值,进行分类。

A1) 数据集可视化

为了训练单层决策树,我们需要创建一个训练集,编写代码如下:

'''
Created on Nov 11, 2018
Adaboost is short for Adaptive Boosting
@author: SXL
'''
# -*-coding:utf-8 -*-
from numpy import *
import numpy as np
import matplotlib.pyplot as plt

def loadSimpData():
    datMat = 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

"""
数据可视化
dataMat - 数据矩阵
labelMat - 数据标签
Returns:无
"""
def showDataSet(dataMat, labelMat):
    data_plus = []                                  #正样本
    data_minus = []                                 #负样本
    for i in range(len(dataMat)):
        if labelMat[i] > 0:
            data_plus.append(dataMat[i])
        else:
            data_minus.append(dataMat[i])
    data_plus_np = np.array(data_plus)                                              #转换为numpy矩阵
    data_minus_np = np.array(data_minus)                                            #转换为numpy矩阵
    plt.scatter(np.transpose(data_plus_np)[0], np.transpose(data_plus_np)[1])       #正样本散点图
    plt.scatter(np.transpose(data_minus_np)[0], np.transpose(data_minus_np)[1])     #负样本散点图
    plt.show()

if __name__ == '__main__':
    dataArr,classLabels = loadSimpData()
    showDataSet(dataArr,classLabels)

代码结果:

可以看到,如果想要试着从某个坐标轴上选择一个值(即选择一条与坐标轴平行的直线)来将所有的蓝色圆点和橘色圆点分开,这显然是不可能的。这就是单层决策树难以处理的一个著名问题。通过使用多颗单层决策树,我们可以构建出一个能够对该数据集完全正确分类的分类器。

A2) 构建单层决策树

我们设置一个分类阈值,比如我横向切分,如下图所示:

蓝横线上边的是一个类别,蓝横线下边是一个类别。显然,此时有一个蓝点分类错误,计算此时的分类误差,误差为1/5 = 0.2。这个横线与坐标轴的y轴的交点,就是我们设置的阈值,通过不断改变阈值的大小,找到使单层决策树的分类误差最小的阈值。同理,竖线也是如此,找到最佳分类的阈值,就找到了最佳单层决策树,编写代码如下:
 

"""
单层决策树分类函数
dataMatrix - 数据矩阵
dimen - 第dimen列,也就是第几个特征
threshVal - 阈值
threshIneq - 标志
Returns:retArray - 分类结果
"""
def stumpClassify(dataMatrix,dimen,threshVal,threshIneq):
    retArray = np.ones((np.shape(dataMatrix)[0],1))                #初始化retArray为1
    if threshIneq == 'lt':  #lt:less than,
        retArray[dataMatrix[:,dimen] <= threshVal] = -1.0         #如果小于阈值,则赋值为-1
    else:
        retArray[dataMatrix[:,dimen] > threshVal] = -1.0         #如果大于阈值,则赋值为-1
    return retArray

"""
找到数据集上最佳的单层决策树
dataArr - 数据矩阵
classLabels - 数据标签
D - 样本权重
Returns:
    bestStump - 最佳单层决策树信息
    minError - 最小误差
    bestClasEst - 最佳的分类结果
"""
def buildStump(dataArr,classLabels,D):
    dataMatrix = np.mat(dataArr); labelMat = np.mat(classLabels).T          #转化成mat矩阵
    m,n = np.shape(dataMatrix)                    #获取数据集的维度信息
    numSteps = 10.0; bestStump = {}; bestClasEst = np.mat(np.zeros((m,1)))   #bestStump最佳单层决策树信息  bestClasEst - 最佳的分类结果
    minError = float('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):  #第二层循环,对每个步长
            for inequal in ['lt', 'gt']:     #第三层循环,大于和小于的情况,均遍历。lt:less than,gt:greater than
                threshVal = (rangeMin + float(j) * stepSize)                     #计算阈值
                predictedVals = stumpClassify(dataMatrix, i, threshVal, inequal)#计算分类结果
                errArr = np.mat(np.ones((m,1)))                                 #初始化误差矩阵
                errArr[predictedVals == labelMat] = 0                             #分类正确的,赋值为0
                weightedError = D.T * errArr                                      #计算误差
                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

if __name__ == '__main__':
    dataArr,classLabels = loadSimpData() #加载数据
    D = np.mat(np.ones((5, 1)) / 5)  #D是数据集中每个特征的权重向量
    bestStump,minError,bestClasEst = buildStump(dataArr,classLabels,D)
    print('bestStump:\n', bestStump)
    print('minError:\n', minError)
    print('bestClasEst:\n', bestClasEst)

 代码结果:

 代码不难理解,就是通过遍历,改变不同的阈值,计算最终的分类误差,找到分类误差最小的分类方式,即为我们要找的最佳单层决策树。这里lt表示less than,表示分类方式,对于小于阈值的样本点赋值为-1,gt表示greater than,也是表示分类方式,对于大于阈值的样本点赋值为-1。经过遍历,我们找到,训练好的最佳单层决策树的最小分类误差为0.2,就是对于该数据集,无论用什么样的单层决策树,分类误差最小就是0.2。这就是我们训练好的弱分类器。接下来,使用AdaBoost算法提升分类器性能,将分类误差缩短到0,看下AdaBoost算法是如何实现的。
五 使用AdaBoost提升分类器性能

B)使用AdaBoost提升分类器性能

在上面,我们构建了一个基于加权输入值进行决策的分类器。现在我们拥有了实现一个完整AdaBoost算法所需要的所有信息。

整个实现的伪代码如下:

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

实现代码:

#使用AdaBoost提升分类器性能
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)     #构建单层决策树
        print("D:",D.T)
        alpha = float(0.5 * np.log((1.0 - error) / max(error, 1e-16)))         #计算弱学习算法权重alpha,使error不等于0,因为分母不能为0
        bestStump['alpha'] = alpha     #存储弱学习算法权重
        weakClassArr.append(bestStump)        #存储单层决策树
        print("classEst: ", classEst.T)   # classEst最佳的分类结果
        expon = np.multiply(-1 * alpha * np.mat(classLabels).T, classEst)     #np.multiply元素乘法,计算e的指数项
        D = np.multiply(D, np.exp(expon))   #np.exp返回e的幂次方,e是一个常数为2.71828
        D = D / D.sum()                                                        #根据样本权重公式,更新样本权重
        #计算AdaBoost误差,当误差为0的时候,退出循环
        aggClassEst += alpha * classEst
        print("aggClassEst: ", aggClassEst.T)
        aggErrors = np.multiply(np.sign(aggClassEst) != np.mat(classLabels).T, np.ones((m,1)))     #计算误差  np.sign([-5., 4.5])-》array([-1.,  1.])
        errorRate = aggErrors.sum() / m
        print("total error: ", errorRate)
        if errorRate == 0.0: break                                             #误差为0,退出循环
    return weakClassArr, aggClassEst

if __name__ == '__main__':
    dataArr,classLabels = loadSimpData()  #加载数据
    weakClassArr, aggClassEst = adaBoostTrainDS(dataArr, classLabels)  #adaboost训练分类器
    print("弱分类器数组",weakClassArr)
    print("强分类器",aggClassEst)

代码结果:

在第一轮迭代中,D中的所有值都相等。于是,只有第一个数据点被错分了。因此在第二轮迭代中,D向量给第一个数据点0.5的权重。这就可以通过变量aggClassEst的符号来了解总的类别。第二次迭代之后,我们就会发现第一个数据点已经正确分类了,但此时最后一个数据点却是错分了。D向量中的最后一个元素变为0.5,而D向量中的其他值都变得非常小。最后,第三次迭代之后aggClassEst所有值的符号和真是类别标签都完全吻合,那么训练错误率为0,程序终止运行。

最后训练结果包含了三个弱分类器,其中包含了分类所需要的所有信息。一共迭代了3次,所以训练了3个弱分类器构成一个使用AdaBoost算法优化过的分类器,分类器的错误率为0。

一旦拥有了多个弱分类器以及其对应的alpha值,进行测试就变得想当容易了。编写代码如下:

"""
AdaBoost分类函数
datToClass - 待分类样例
classifierArr - 训练好的分类器
Returns:分类结果
"""
def adaClassify(datToClass,classifierArr):
    dataMatrix = np.mat(datToClass)
    m = np.shape(dataMatrix)[0]
    aggClassEst = np.mat(np.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)
    return np.sign(aggClassEst)

if __name__ == '__main__':
    dataArr,classLabels = loadSimpData()
    weakClassArr, aggClassEst = adaBoostTrainDS(dataArr, classLabels)
    print("分类结果:\n",adaClassify([[0,0],[5,5]], weakClassArr))  #[[0,0],[5,5]] 两个测试点

代码结果:

代码很简单,在之前代码的基础上,添加adaClassify()函数,该函数遍历所有训练得到的弱分类器,利用单层决策树,输出的类别估计值乘以该单层决策树的分类器权重alpha,然后累加到aggClassEst上,最后通过sign函数最终的结果。可以看到,分类没有问题,(5,5)属于正类,(0,0)属于负类。

C)在一个难数据集上应用AdaBoost

机器学习实战笔记4—Logistic回归文章中,我们使用Logistic回归方法训练马疝病数据集,预测病马死亡率。当时的训练结果如下图所示:

这个是使用Sklearn的LogisticRegression()训练的分类器,可以看到,错误率约为34.9254%。可以看到错误率还是蛮高的,现在我们使用AdaBoost算法,训练出一个更强的分类器,这里的数据集有所变化,之前的标签是0和1,现在将标签改为+1和-1,其他数据不变。

使用自己用Python写的AbaBoost算法进行训练,添加loadDataSet函数用于加载数据集。编写代码如下:

def loadDataSet(fileName):      #general function to parse tab -delimited floats
    numFeat = len(open(fileName).readline().split('\t')) #get number of fields
    dataMat = []; labelMat = []
    fr = open(fileName)
    for line in fr.readlines():
        lineArr =[]
        curLine = line.strip().split('\t')
        for i in range(numFeat-1):
            lineArr.append(float(curLine[i]))
        dataMat.append(lineArr)
        labelMat.append(float(curLine[-1]))
    return dataMat,labelMat

if __name__ == '__main__':
    dataArr, LabelArr = loadDataSet('horseColicTraining2.txt')
    weakClassArr, aggClassEst = adaBoostTrainDS(dataArr, LabelArr)
    testArr, testLabelArr = loadDataSet('horseColicTest2.txt')
    print(weakClassArr)
    predictions = adaClassify(dataArr, weakClassArr)
    errArr = np.mat(np.ones((len(dataArr), 1)))
    print('训练集的错误率:%.3f%%' % float(errArr[predictions != np.mat(LabelArr).T].sum() / len(dataArr) * 100))
    predictions = adaClassify(testArr, weakClassArr)
    errArr = np.mat(np.ones((len(testArr), 1)))
    print('测试集的错误率:%.3f%%' % float(errArr[predictions != np.mat(testLabelArr).T].sum() / len(testArr) * 100))

代码结果:

这里输出了AdaBoost算法训练好的分类器的组合,我们只迭代了40次,也就是训练了40个弱分类器。最终,训练集的错误率为19.732%,测试集的错误率为19.403%,可以看到相对于Sklearn的罗辑回归方法,错误率降低了很多。这个仅仅是我们训练40个弱分类器的结果,如果训练更多弱分类器,效果会更好。但是当弱分类器数量过多的时候,你会发现训练集错误率降低很多,但是测试集错误率提升了很多,这种现象就是过拟合(overfitting)。分类器对训练集的拟合效果好,但是缺失了普适性,只对训练集的分类效果好,这是我们不希望看到的。


D)使用Sklearn的AdaBoost

sklearn.ensemble模块提供了很多集成方法,AdaBoost、Bagging、随机森林等。本文使用的是AdaBoostClassifier。

# -*-coding:utf-8 -*-
import numpy as np
from sklearn.ensemble import AdaBoostClassifier
from sklearn.tree import DecisionTreeClassifier

def loadDataSet(fileName):
    numFeat = len((open(fileName).readline().split('\t')))
    dataMat = []; labelMat = []
    fr = open(fileName)
    for line in fr.readlines():
        lineArr = []
        curLine = line.strip().split('\t')
        for i in range(numFeat - 1):
            lineArr.append(float(curLine[i]))
        dataMat.append(lineArr)
        labelMat.append(float(curLine[-1]))
    return dataMat, labelMat

if __name__ == '__main__':
    dataArr, classLabels = loadDataSet('horseColicTraining2.txt')
    testArr, testLabelArr = loadDataSet('horseColicTest2.txt')
    bdt = AdaBoostClassifier(DecisionTreeClassifier(max_depth = 2), algorithm = "SAMME", n_estimators = 10)
    bdt.fit(dataArr, classLabels)
    predictions = bdt.predict(dataArr)
    errArr = np.mat(np.ones((len(dataArr), 1)))
    print('训练集的错误率:%.3f%%' % float(errArr[predictions != classLabels].sum() / len(dataArr) * 100))
    predictions = bdt.predict(testArr)
    errArr = np.mat(np.ones((len(testArr), 1)))
    print('测试集的错误率:%.3f%%' % float(errArr[predictions != testLabelArr].sum() / len(testArr) * 100))

代码结果:

 

欢迎扫码关注我的微信公众号

 

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值