AdaBoost Meta-algorithm(Adaptive Boosting Meta-algorithm, 自适应 boosting 元算法) 属于监督学习算法, 它是迭代算法, 其核心思想是针对同一个训练集训练不同的分类器, 即弱分类器, 然后把这些弱分类器集合起来, 构造一个更强的最终分类器.
优点 | 泛化错误率低, 易编码, 可以应用在大部分分类器上, 无参数调整 |
缺点 | 对离群点敏感 |
适用数据类型 | 数值型, 标称型 |
基础概念
1. bagging
bootstrap aggregating(自举汇聚法), 也称bagging方法, 是从原始数据集选择 S 次后得到 S 个新数据集的一种技术. bagging 方法有以下特征:
(1) 新数据集和原数据集样本数相等
(2) 每个数据集都是通过在原始数据集中随机选择一个样本进行替换得到的
(3) 新数据集中可以有重复的值, 而原始数据集中的某些值在新集合中不再出现
(4) 选一个学习算法应用于 S 个数据集, 可得到 S 个分类器
(5) 分类时, 应用这 S 个分类器进行分类, 选择 S 个分类中最多的类别作为分类结果
2. boosting
boosting 与 bagging 类似, 它们在 S 个数据集上都是使用同一个算法进行分类, 即得到的 S 个分类器的类型是一致的. 两者的不同在于:
(1) boosting 的分类器是通过串行训练获得的, 即新分类器根据已训练出的分类器进行训练
(2) boosting 通过关注被已有分类器错分的数据来获取新分类器
(3) bagging 中的分类器权重是相等的, 而 boosting 中的分类器权重不相等
(4) boosting 每个分类器权重代理的是其对应分类器在上一轮迭代中的成功度
3. 分类器错误率
4. 分类器权重 α
5. 训练集样本权重 D
算法对训练集中的每个样本都赋于一个权重, 这些权重构成向量D. 在训练时, 每次迭代完成后, 都会调速各个样本的权重, 分对的样本权重降低, 分错的样本权重升高. 下图依次为样本正确划分和错误划分后的D的计算式:
正确划分: 错误划分:
6. sign 函数
符号函数, 用于取某个数的正负号.
算法描述
AdaBoost分为两部分, 对数据集训练出弱分类器和把多个弱分类器集合成强分类器, 以下先给出一个总图, 再分别描述这两个部分.
1. 弱分类器训练(本文使用的是单层决策树)
(1) 对样本的每个属性(列), 都按固定步数, 固定步数【(最大值-最小值)/步数】进行划分
(2) 每次划分时, 都把小于当前阈值【步长*当前步数】的划到 -1 类, 大于当前阈值的划到 1 类
(3) 计算加权错误率
(4) 保存权错误率最低时的 dim, thresh, ineq 作为当前决策树的划分规则(代码 62-69行)
2. 弱分类器合并成强分类器
弱分类器的训练是串行进行的, 前面分类器的训练结果会影响后面的分类器
(1) 训练集中的每个样本都赋一个权重, 这些权重合成向量 D;
(2) D 全部初化为 1/m, m为样本数
(3) 以 D 为参数对训练集进行训练, 得到一个弱分类器 (包含最佳划分规则, 最低错误率等)
(4) 根据错误率计算出分类器权重 α, 再根据 α 调整 D
(5) 循环 (3)->(4), 直至最低错误率为 0, 或达到设定的迭代次数
(6) 分类时, 由于每个分类器都有自己的分类结果, 及权重 α, 按以下公式得到新样本所属分类(代码 109-117行)
算法流程图
1. 弱分类器训练(本文使用的是单层决策树)
2. 弱分类器合并成强分类器
代码
# -*- coding: utf-8 -*
from numpy import *
# 加载简单数据
def loadSimpData():
datMat = matrix([[1. , 2.1],
[2. , 1.1],
[1.3, 1. ],
[1. , 1. ],
[2. , 1. ]])
# 依次表示 datMat 各个点所属分类
classLabels = [1.0, 1.0, -1.0, -1.0, 1.0]
return datMat,classLabels
# 1. 通过阈值比较对数据分类, 小于阈值的归到 -1 类, 大于阈值的归到 1 类
# 2. lt 表示需要设置的是 less than
# 3. retArray[dataMatrix[:,dimen] <= threshVal] = -1.0 表示:
# 假设 dataMatrix[i, dimen] <= threshVal, 则 retArray[i] = -1
def stumpClassify(dataMatrix,dimen,threshVal,threshIneq):#just classify the data
# 先全部初始化为 1, 后面只要对需要设置 -1 的部分进行设置即可
retArray = ones((shape(dataMatrix)[0],1))
if threshIneq == 'lt':
retArray[dataMatrix[:,dimen] <= threshVal] = -1.0
else:
retArray[dataMatrix[:,dimen] > threshVal] = -1.0
return retArray
# 1. 构建单层决策树(因为树只有一层, 所以称为 树桩(stump))
# 2. 本函数将会得到一个弱分类器, 即在当前 D 下, 对哪一列(属性, dim), 什么阈值(thresh),
# 是取大于还是小于(ineq)此阈值时, 能得于最小的加权错误率
# 3. 本文对节点的划分不同于第3章的决策树按最大增益值(熵)来进行,
# 而是基于权重 D, 最小的加权错误率来构建分类器的
def buildStump(dataArr,classLabels,D):
dataMatrix = mat(dataArr);
labelMat = mat(classLabels).T # 转置
m,n = shape(dataMatrix)
numSteps = 10.0; bestStump = {}; bestClasEst = mat(zeros((m,1)))
minError = inf # 最小错误率初始化为正无穷
# 遍历所有列, 即所有属性值
for i in range(n):
rangeMin = dataMatrix[:,i].min(); # 取第 i 列的最小值
rangeMax = dataMatrix[:,i].max(); # 取第 i 列的最大值
stepSize = (rangeMax-rangeMin)/numSteps # 计算步长
# 对当前列(属性值), 逐个步长进行遍历
for j in range(-1,int(numSteps)+1):
# 大于和小于当前阈值都要进行考虑
# 'lt' 表示 less than, 'gt' 表示 greater than
for inequal in ['lt', 'gt']:
threshVal = (rangeMin + float(j) * stepSize) # 当前阈值
# 根据当前阈值划分(按大于/小于), 满足(大于/小于) 的就置为 1
predictedVals = stumpClassify(dataMatrix,i,threshVal,inequal)
# 与真正类别检签值相一致的设为 1, 不一致的设为 0
errArr = mat(ones((m,1)))
errArr[predictedVals == labelMat] = 0
# 计算加权错误率, 并保存错误率最小时的 dim, thresh, ineq
weightedError = D.T*errArr #calc total error multiplied by D
if weightedError < minError:
minError = weightedError
bestClasEst = predictedVals.copy()
bestStump['dim'] = i
bestStump['thresh'] = threshVal
bestStump['ineq'] = inequal
return bestStump,minError,bestClasEst
# 将多个由 单层决策树(Decision Stump, DS) 弱分类器合并成一个强分类器
def adaBoostTrainDS(dataArr,classLabels,numIt=40):
weakClassArr = []
m = shape(dataArr)[0]
# D 初始化为相等的值, 由于D是概率分布向量, 要保证其所有元素和为 1, 所以初始化为 1/m
D = mat(ones((m,1))/m)
aggClassEst = mat(zeros((m,1)))
for i in range(numIt):
bestStump,error,classEst = buildStump(dataArr,classLabels,D)#build Stump
# 计算 alpha 值
alpha = float(0.5*log((1.0-error)/max(error,1e-16)))
bestStump['alpha'] = alpha
weakClassArr.append(bestStump) # 保存当前弱分类器的信息
# 按公式计算 D 值, 新的 D 值会被用来进行下一次单层决策树的构建
expon = multiply(-1*alpha*mat(classLabels).T,classEst)
D = multiply(D,exp(expon))
D = D/D.sum()
# 计算总错误率, 当错误率为 0, 或达到循环次数时退出
aggClassEst += alpha*classEst
aggErrors = multiply(sign(aggClassEst) != mat(classLabels).T,ones((m,1)))
errorRate = aggErrors.sum()/m
if errorRate == 0.0: break
return weakClassArr, aggClassEst
# 分类
def adaClassify(datToClass,classifierArr):
dataMatrix = mat(datToClass)
m = shape(dataMatrix)[0]
aggClassEst = mat(zeros((m,1)))
# 取每个弱分类器的分类结果, 乘以其 alpha 值, 最后相加, 再取 sign 函数
for i in range(len(classifierArr)):
classEst = stumpClassify(dataMatrix,classifierArr[i]['dim'],\
classifierArr[i]['thresh'],\
classifierArr[i]['ineq'])#call stump classify
aggClassEst += classifierArr[i]['alpha']*classEst
# sign 为符号函数, 即 aggClassEst 为正数, 则返回 1, 否则返回 -1
return sign(aggClassEst)
if __name__ == "__main__":
dataArr, labelArr = loadSimpData()
classifierArr, aggClassEst = adaBoostTrainDS(dataArr, labelArr, 30)
print adaClassify([0, 0],classifierArr) # 分到 -1 类
print adaClassify([2, 2],classifierArr) # 分到 1 类
待补充
1. 非均衡分类问题
说明
本文为《Machine Leaning in Action》第七章(Improving classification with the AdaBoost meta-algorithm)读书笔记, 代码稍作修改及注释.
好文参考
1.《Boosting算法简介》
2.《浅谈Adaboost算法》
3.《理解Adaboost算法》