Boost

  Adaboost(adaptive boosting)是一种集成学习算法,基于boosting的框架,因此首先简单说一下boosting算法。boosting算法主要的目的是将“弱分类器”提升为“强分类器”。其工作机制如下:

  1. 在原始训练数据上训练出一个基分类器;
  2. for t=2,3,…,T:
    根据前一个分类器的分类结果调整样本的分布,使得被分错的样本受到更多的关注;
    从调整后的训练样本训练下一个基分类器;
  3. 得到T个基分类器后对他们进行加权得到强分类器。

Adaboost原理

  Adaboost是boost算法中最常用也是最著名的一个,其通过对基分类器进行线性组合得到最终的强分类器。Adaboost的实现流程如下图所示:



adaboost回归模型例子_权重


adaboost回归模型例子_权重_02adaboost回归模型例子_adaboost回归模型例子_03

adaboost回归模型例子_决策树_04 就是第 adaboost回归模型例子_集成学习_05 个分类器在训练样本上的错误率,根据这个错误率,分类器的权重通过以下式子计算:
adaboost回归模型例子_boost_06得到第 adaboost回归模型例子_集成学习_05 个分类器的权重之后,再根据分类结果就可以更新每个训练样本的权重。如图中第7步所示,如果某个样本 adaboost回归模型例子_集成学习_08 被第 adaboost回归模型例子_集成学习_05 个分类器正确分类,那么在训练第 adaboost回归模型例子_集成学习_10 个分类器的时候,其权重变为:
adaboost回归模型例子_权重_11
否则,若样本被错误分类,则:
adaboost回归模型例子_boost_12 式(2)和(3)中,adaboost回归模型例子_adaboost回归模型例子_13 为规范化因子,在更新了所有样本的权重之后,adaboost回归模型例子_adaboost回归模型例子_13 可以这样计算:
adaboost回归模型例子_boost_15 得到所有的 adaboost回归模型例子_boost_16 个基分类器后,最终的分类器可以表示为下面这样:
adaboost回归模型例子_决策树_17 显然,它是所有的 adaboost回归模型例子_boost_16 个基分类器的线性组合。
  还有一点,在每次训练出新的分类器之后都需要检查训练出的分类器的是否符合条件。图中基分类器的错误率 adaboost回归模型例子_集成学习_19

实现

  接下来以基于决策树桩的Adaboost分类器为例实现马疝病数据集上的预测。决策树桩(decision stump是最简单的分类器,只通过一个特征来做决策,换句话说,决策树只有一次分类过程,包含两个用于决策的叶子节点。
  马疝病数据集包含两类样本,为了后面实现方便,在加载数据集的时候将原本的类别标签0和1转化成-1和1,加载数据集代码如下:

def loadDataSet(path):
    dataSet = np.loadtxt(path)
    X = dataSet[:, :-1]
    y = 2 * dataSet[:, -1] - 1   # 注意将类标签换成-1, 1
    return X, y

决策树桩分类器的代码如下:

def stumpClassify(X, dimen, threshVal, inqual):
    '''根据阈值和当前维度(dimen)的特征值返回样本分类结果'''
    returnArr = np.ones(X.shape[0])
    if inqual == 'lt':
        returnArr[X[:, dimen] <= threshVal] = -1.0
    else:
        returnArr[X[:, dimen] >= threshVal] = -1.0
    return returnArr

def buildStump(X, y, D):
    '''
    根据数据集构建一个决策树桩
    :param X: 训练集的特征
    :param y: 训练集的类别标签
    :param D: 样本的权重向量
    :return: 训练出的决策树桩,误差,分类结果
    '''
    m, n = X.shape
    numStep = 10
    bestStump = {}  # 训练出的决策树桩用字典存储
    bestClassEst = np.zeros(m)  # 样本的类别估计值
    minError = np.inf
    for i in range(n):  # 遍历特征
        rangeMin = np.min(X[:, i])   # 特征取值的区间范围
        rangeMax = np.max(X[:, i])
        stepSize = (rangeMax - rangeMin) / numStep   # 根据步数和范围求步长
        for j in range(-1, numStep+1):
            for inequal in ['lt', 'gt']:
                threshVal = rangeMin + (j * stepSize)  # 第j个区间的上边界,用来作为当前分类的阈值
                predictedVals = stumpClassify(X, i, threshVal, inequal)
                error = np.ones(m)
                error[predictedVals == y] = 0  # 通过错误分类计算当前决策树桩的分类错误率
                weightedError = np.dot(D, error)  # 加权错误率
                if weightedError < minError:     # 记录误差最小的决策树桩
                    minError = weightedError
                    bestClassEst = predictedVals
                    bestStump['dim'] = i
                    bestStump['thresh'] = threshVal
                    bestStump['ineq'] = inequal
    return  bestStump, minError, bestClassEst

由于数据集的特征是连续的,因此程序中通过区间化的方式对特征值做了离散化处理。通过遍历数据集每个特征属性的不同分割阈值,得到最好的决策树桩分类器,最后返回的参数中bestStump为存储决策树桩的字典,包括了用来分类的特征索引、分割阈值和分割方式(lt or gt)、决策树桩的分类误差、分类结果向量。

Adaboost分类器的构建过程如下,过程和前面图中的伪代码基本一致。

def adaBoostTrainDS(X, y, numIt=40):
    '''
    :param X: 训练集的特征
    :param y: 训练集的类别标签
    :param numIt: 最大迭代次数,且每次迭代会生成一个弱分类器
    :return: 弱分类器序列
    '''
    weakClassArr = []
    m = X.shape[0]  # 训练样本数目
    D = np.ones(m) / m   # 初始每个样本权重相同
    aggClassEst = np.zeros(m)  # 样本的类别估计值
    for i in range(numIt):
        # 训练一个决策树桩分类器
        bestStump, error, classEst = buildStump(X, y, D)
        # print("样本权重: ", D.T)
        alpha = 0.5 * np.log((1 - error) / max(error, 1e-16))  # 根据误差为当前分类器分配权重
        bestStump['alpha'] = alpha
        weakClassArr.append(bestStump)  # 当前的决策树桩加入弱分类器列表
        # print("样本类别估计值:", classEst)
        D *= np.exp(-1 * alpha * y * classEst)   # 更新每个样本的权重
        D /= np.sum(D)
        aggClassEst += alpha * classEst  # 当前集成分类器的分类结果
        # print("分类结果:", aggClassEst)
        errorRate = np.dot(np.sign(aggClassEst) != y, np.ones(m)) / m
        print("累加错误率:%f" % errorRate)
        if abs(errorRate - 0.0) < 1e-10:
            break
    return weakClassArr

函数返回了所有的决策树桩分类器以及其对应的权重,也就是最终的分类器。接下来,对于需要预测的样本,将其传入分类器,即可根据各个决策树桩的分类结果的线性组合得到最终的分类结果。预测函数的代码如下:

def adaClassify(X, classifierArr):
    '''
    分类函数
    :param X: 待分类样本
    :param classifierArr: 弱分类器列表
    :return: 分类结果
    '''
    m = X.shape[0]
    aggClassEst = np.zeros(m)
    for i in range(len(classifierArr)):
        classEst = stumpClassify(X, classifierArr[i]['dim'], classifierArr[i]['thresh'], classifierArr[i]['ineq'])
        aggClassEst += classifierArr[i]['alpha'] * classEst
    return np.sign(aggClassEst)

数据集及完整的实现代码可以从我的github下载。

总结

  从偏差和方差的角度理解,Adaboost算法主要侧重于降低偏差,其每个基分类器的泛化性能较弱,但通过集成最终可以构建出性能很好的分类器。
  与boosting很相似的另一种集成方法是bagging算法,随机森林就是其中一个典型的例子。后面再慢慢总结。

参考资料

《机器学习》(周志华)
《机器学习实战》(Peter Harrington)