机器学习笔记(十二)——集成学习方法之AdaBoost

集成学习方法

本文参考于《机器学习实战》和《机器学习》

在此以前一共介绍了五种分类算法,分别为KNN、决策树、朴素贝叶斯、逻辑回归、支持向量机,能够看到每一种算法都有各自的优缺点,以及适合的数据集。集成学习方法能够将不一样分类算法构建的分类器组合在一块儿,更加高效准确的分类。python

使用集成学习方法时能够有多种形式:能够是不一样算法的集成,也能够是同一算法在不一样设置下的集成,还能够是数据集不一样部分分配给不一样分类器以后的集成。算法

对于集成方法中分类器的组合也能够将集成分为“同质”和“异质”。例如“决策树集成”中全部的分类器都为决策树,同质集成中的个体分类器能够称做“基分类器”;而“异质”就是集成中包含不一样分类算法,异质集成中的个体分类器常称为“组件分类器”。app

集成学习经过将若干个弱学习器组合成一个强分类器,这个强分类器能够得到比单一学习器更加显著的准确率。弱分类器常指准确率略优于随机猜想的学习器,好比在二分类问题上准确率略高于50%的分类器,在同质集成学习中基分类器有时也能够直接称为弱分类器。less

集成学习方法大体可分为Bagging和Boosting两种方法,这两种方法的思想都是将多种分类和回归算法经过某种方式组合在一块儿,从而获得一个强分类器。下面分别介绍这两种方法,比较两种方法的异同。机器学习

Bagging

能够将Bagging方法的主要原理总结成如下几点:分布式

  • 抽样:在原始数据中有放回的抽取N次获得一个新的采样数据集。新数据集与原始数据集大小相等,因为是有放回抽样,因此容许采样数据集中有重复值。
  • 训练:总共选取M个采样数据集,每个数据集对应一种学习算法,从而能够获得M个分类器,利用这些分类器对数据进行分类。
  • 票选:M个分类器会获得M个分类结果,因此最后经过投票将分类结果中最多的类别做为最终的分类结果。

随机森林就是Bagging方法的典型表明,它是由不少棵决策树与Bagging方法思想相结合而成。函数

Boosting

Boosting方法与Bagging方法很相似,但前者主要是经过将权重与串行迭代结合的方式进行学习,Boosting更加关注每次分类结束后被分错的样本,在下次分类以前,Boosting会增大分错样本的权重,这样下一个分类器在训练时就会更加注重对分错样本的训练。学习

在决定最终的分类时,因为每一轮分类权重都会发生改变,因此Boosting方法经过加权求和的方式获得分类结果。Boosting方法的典型表明为Adaboost,它是由不少棵决策树与Boosting方法思想相结合而成,也被称做提高树,除此以外还有GBDT和XGBoost。下文会给出以AdaBoost的流程图,其原理就是Boosting的主要思想。测试

两种方法区别

两种算法虽然思想上都是经过将若干个弱分类器组合成一个强分类器,让最终的分类准确率获得提高,可是两者也是有很大区别的,具体以下:ui

  • Bagging在原始数据集中以随机放回抽样的方式获得新采样集;而Boosting每一轮的训练集都为原始数据集,只是训练集中每一个样本的权重在发生变化。
  • Bagging是采用并行的方式学习,每一个分类器学习时互不影响;而Boosting是采用串行的方式学习,上一次训练结果会影响下一次训练集中样本的权重。
  • Bagging因为每一个分类器权重都一致,因此在断定分类最终结果时采用投票式;而Boosting每一个分类器都有相应的权重,因此在断定分类最终结果时采用加权求和的方式。

AdaBoost算法介绍

算法原理

AdaBoost的全称为adaptive boosting(自适应boosting),能够将其运行过程归结为如下几点:

  • 训练数据集中的每一个样本都会被赋予一个相同的权重值,这些权重会构成一个向量,暂称为D。
  • 分类器在训练数据集以后会计算出该分类器的错误率,即分错样本的几率,而且依据错误率更新分类器的权重。
  • 在下一个分类器训练以前,会依据上一个分类器的权重调整每个样本的权重,上一次分对的样本权重下降,分错的样本权重提升。
  • 迭代次数是人为设置的,在达到迭代次数或者知足某个条件后,以加权求和的方式肯定最终的分类结果。

相关公式

一、样本权重

对于一个含有N个样本的数据集,在初始化训练数据的权重分布式,每一个样本的权重都应该是相等的。

二、错误率

若是咱们将基于不一样权重$D(M)$训练出的分类器称做$h_M(x)$,每一个样本实际对应类别为$f(x)$,则错误率$\epsilon$的定义以下:$$\epsilon=\frac{未正确分类的样本数目}{全部样本数目}$$ $$\epsilon_M=P(h_M(x)\neq f(x))$$

三、分类器权重

除样本权重外,每个分类器也会有一个权重$\alpha_M$: $$\alpha_M=\frac{1}{2}ln(\frac{1-\epsilon_M}{\epsilon_M})$$
能够看下面这张流程图:

左边是数据集,黑条的长度表明每一个样本的权重,每个分类器训练以后后,都会获得一个分类器权重$\alpha$,最后全部分类器加权结果(三角形)会在圆形中求和,以获得最终的分类结果。

四、更新权重D

在下次训练以前,为了使正确分类的样本权重下降而错分样本的权重提升,须要对权重向量D更新:

在两个式子合并时,这里通常针对二分类问题,即类别标签为+1和-1。当分类器预测结果和真实类别相同时,两者乘积为+1,不一样时,两者乘积为-1。其中$Z_t$称做规范化因子,它的做用就是确保$D_{M+1}$是一个几率分布。

五、最终分类器输出

能够利用每一个弱分类器的线性组合达到加权求和的目的,而且经过符号函数肯定最终的分类结果。$$H(x)=sign(\sum_{m=1}^M\alpha_m h_m(x))$$

结合策略

能够看到AdaBoost的流程图中有一个“结合策略”,顾名思义,就是经过某种策略将全部的弱分类器的分类结果结合在一块儿,继而判断出最终的分类结果。结合策略可分为三类:平均法、投票法和学习法。

  • 平均法

对于数值型的回归预测问题,最多见的结合策略是使用平均法,平均法又能够分为简单平均法和加权平均法。

假设有n个弱分类器$(h_1(x),h_2(x),...,h_n(x))$,简单平均法公式以下: $$H(x)=\frac{1}{n}\sum_{i=1}^nh_i(x)$$

加权平均法公式以下:$$H(x)=\sum_{i=1}^nw_ih_i(x)$$
其中$w_i$是弱分类器$h_i(x)$的权重,一般要求$w_i\geq0$,$\sum_{i=1}^nw_i=1$。能够看出简单平均法是加权平均法令$w_i=\frac{1}{n}$的特例。

  • 投票法

对分类的预测,一般使用的是投票法,投票法能够分为相对多数投票法、绝对多数投票法和加权投票法。

相对多数投票法就是常说起的少数服从多数,在预测结果中选择出现次数最多的类别做为最终的分类类别,若是不止一个类别得到最高票数,则在其中随机选取一个作最终类别。

绝对多数投票法就是在相对多数投票法的基础上进行改进,不光要求要得到最高票数,而且票数还要超过50%,不然拒绝预测。

加权投票法与加权平均法相似,即每一个若分类器获得的分类票数要乘以一个权重,最终将各个类别的票数加权求和,最大值对应类别做为最终分类类别。

  • 学习法

学习法相对于前两种方法较为复杂,但获得的效果可能会更优,学习法的表明是Stacking,其总体思想是在弱分类器以后再加上一个分类器,将弱分类器的输出当成输入再次训练。

咱们将弱分类器称做初级分类器,将用于结合的分类器称做次级分类器,利用学习法结合策略时,先经过初级分类器对数据集训练,而后再经过次级分类器在训练一次,才会获得最终的预测结果。

AdaBoost代码实现

单层决策树

单层决策树是一种简单的决策树,它仅仅基于单个特征来作决策,也就是获得一个信息增益以后会舍弃其余特征,因此单层决策树只有一次分裂过程,实际上也是一个树桩。

之因此用单层决策树来构建弱分类器,是由于在下面这份数据集中,单层决策树能更好的体现"弱"这个性质。

为何这么说呢?在某个坐标轴上选择一个值,若是要利用垂直坐标轴而且通过该值的一条直线将两类样本分隔开,这显然是不可能的。可是能够经过屡次划分,即经过使用多棵单层决策树构建出一个彻底正确分类的分类器。

咱们暂且将上图样本的分类规定为正类和负类,那么在划分数据集时候能够给定相应的阈值,以下图:

假如选取X1特征的阈值为1.4,能够规定小于这个阈值的样本为负类,相应大于这个阈值的样本为正类;但规定小于阈值的样本为正类,大于为负类也一样合理,因此对于一个特征有两种状况,这也是下面这个函数中惟一比较别扭的点。

def buildStump(dataMatrix, classLabels, D):
    labelMat = np.mat(classLabels).T # 标签列表矩阵化并转置
    m, n = np.shape(dataMatrix) # 获取输入数据矩阵行数和列数
    numSteps = 10.0 # 阈值改变次数
    bestStump = {} # 用于存树的空字典
    bestClasEst = np.mat(np.zeros((m, 1)))
    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): # 阈值改变次数
            # lt(less than)指小于该阈值,分类为-1
            # gt(greater than)指大于该阈值,分类为-1
            for inequal in ['lt', 'gt']: # 两种状况,都须要遍历
                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 # 利用权重计算出偏差
                if weightedError < minError: # 若是本轮偏差比上一次小,则替换
                    minError = weightedError
                    bestClasEst = predictedVals.copy()
                    # 将单层决策树存入字典
                    bestStump['dim'] = i
                    bestStump['thresh'] = threshVal
                    bestStump['ineq'] = inequal
    return bestStump, minError, bestClasEst

这部分代码中须要了解如下点:numSteps就是遍历某个特征全部取值的次数;bestStump这个字典用来存储最佳单层决策树的相关信息;minError为最小偏差,暂定正无穷大,每次循环都会更新。

三层for循环是关键部分,首先第一层是遍历全部特征,而且设置了每次阈值改变的长度;第二层是以步长为基础,遍历特征上全部可取值;第三层则是小于和大于之间切换不等式。

在第三层循环内调用了一个函数,基于给定变量,会返回分类预测结果。首先建立了一个全为1的矩阵,若是预测结果和真实结果一致则对应位置调整为0,而且利用这个矩阵和权重矩阵的转置相乘可获得一个新的偏差,接着会经过比较更新最小偏差,并储存最佳单层决策树的相关信息,即特征、阈值和(lt or gt)。

第三层循环调用函数以下:

def stumpClassify(dataMatrix, dimen, threshVal, threshIneq):
    # 建立一个与输入矩阵行数相同、列数为1的矩阵
    retArray = np.ones((np.shape(dataMatrix)[0], 1))
    # 若是小于阈值,分类为-1
    if threshIneq == 'lt':
        retArray[dataMatrix[:, dimen] <= threshVal] = -1.0
    # 若是大于阈值,分类为-1
    else:
        retArray[dataMatrix[:, dimen] > threshVal] = -1.0
    return retArray

这个函数很是简单,就是以前说起两种状况,小于阈值将其分类为-1或者大于阈值时将其分类为-1都是合理状况,因此须要遍历考虑这两种状况。

能够看到对于第一个特征来讲最小偏差为0.2,也就是说垂直于x轴划分,最优状况下会分错一个样本,下面利用AdaBoost方法将多个单侧决策树结合在一块儿,看一下样本是否能所有分类正确。

结合AdaBoost方法

前文已经给出了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))) #初始化一个值为0的行向量
    for i in range(numIt): # 在给定次数内迭代
        bestStump, error, classEst = buildStump(dataArr, classLabels, D) # 构建单层决策树
        alpha = float(0.5 * np.log((1.0 - error) / max(error, 1e-16))) # 计算弱分类器的权重
        bestStump['alpha'] = alpha # 将弱分类器权重存入树中
        weakClassArr.append(bestStump) # 将最佳单层决策树存入一个列表
        # 更新样本权重
        expon = np.multiply(-1 * alpha * np.mat(classLabels).T, classEst)
        D = np.multiply(D, np.exp(expon))
        D = D / D.sum()
        # 计算AdaBoost的偏差
        aggClassEst += alpha * classEst                       
        aggErrors = np.multiply(np.sign(aggClassEst) != np.mat(classLabels).T, np.ones((m,1)))
        errorRate = aggErrors.sum() / m
        # 若是偏差为0,则退出循环
        if errorRate == 0.0:
            break
    return weakClassArr, aggClassEst

首先对全部样本初始化一个权重D,在设置的迭代次数内,获得存储最佳单层决策树相关信息的字典、最小偏差、预测结果。在最小偏差的基础上更新分类器权重alpha,依据公式当最小偏差为0时,程序会发生报错,即除数为0,利用max方法便可防止这种状况发生。

而后依据alpha更新样本的权重,而expon中的classEst也就是上文公式中弱分类器预测结果$h_M(x)$,classLabels也就是真实结果$f(x)$。

由于咱们没法猜得一个数据集须要几棵单层决策树才能彻底正确分类,若是迭代次数过多,则会发生过拟合的现象,分类反而不许了,因此上述代码最后这部分就是为了防止迭代次数过多,在偏差为0时即便未达到迭代次数也退出迭代。

这里运用了上文公式的$$H(x)=sign(\sum_{m=1}^M\alpha_m h_m(x))$$
先计算每一个弱分类器预测类别的估计值,累加后再利用符号函数进行分类。这里科普一下sign方法,当值大于0时返回1.0,当值小于0时返回-1.0,当值等于0时返回0,知道了sign方法,这部分代码就很容易理解。


最后AdaBoost方法经过三个最佳单层决策树实现了对数据集的彻底正确分类,好比下面这种切分方式:

可能表达的比较抽象,可是在理解的时候你要抛开一个刀就能划分的思想,咱们利用的是集成方法求解分类问题,这是三刀组合在一块儿才获得的彻底正确分类结果。

测试算法

算法模型已经建成了,因此须要对该模型进行测试,代码的思路比较简单,就是遍历全部训练获得的弱分类器,计算每一个弱分类器预测类别的估计值,累加后再经过符号函数对类别进行判断,代码以下:

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
    return np.sign(aggClassEst)

自己咱们的训练数据集就很是小,因此不会计算模型准确率,只是输入两个样本对其类别进行判断,这里样本选择[[5,5],[0,0]],运行截图以下:

能够看到,随着迭代次数的增长,类别的估计值的绝对值愈来愈大(离0愈来愈远),由于最后是利用符号函数对分类结果进行判断,因此这也表明着输入样本的分类结果愈来愈强。

总结

AdaBoost的优势就是分类的准确率高,与其余复杂算法相比更容易编码,而且能够应用在大部分分类器上,没有什么参数须要调整。缺点就是对离群点敏感,由于离群点通常都是错误数据,每每应该被舍弃,对于这类样本分类器很容易分错,而分错的样本的下次迭代时权重会增长,分类器对这类样本会更加剧视,就可能会致使一错再错。

关注公众号【奶糖猫】后台回复"AdaBoost"可获取源码供参考,感谢阅读。

相关文章
相关标签/搜索