AdaBoost 元算法
AdaBoost 元算法是基于某一个前面介绍过的算法(比如 kNN、决策树、SVM)的,以决策树为例,我们的目标不是直接训练完整的决策树,寻求最优的参数设置,而是用很多不完美的决策树,将它们的计算结果经过加权求和,作为预测的依据。简单的说就是“三个臭皮匠,赛过一个诸葛亮”。
算法从一个初始的权重向量 D 开始,训练得到决策树 A (可能是很简单的预测方式,并且预测准确率不高),根据 A 的预测情况计算决策树 A 的权重 alpha(A),并更新样本的权重向量 D(A):
根据 D(A) 训练下一个决策树 B ,再根据 B 的预测情况计算 alpha(B) 和 D(B);如此不断迭代和修正,最终就得到了完整模型。
AdaBoost 元算法很容易理解,算法过程用到的公式也比较简单,不过其背后的“分治”思想很值得深入体会和挖掘。
基于单层决策树的 AdaBoost 元算法
from numpy import *
# 构建简易数据集
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
# 辅助函数:通过阈值比较对数据进行分类
# dataMatrix:特征集
# dimen:要处理的特征在特征集中的下标
# threshVal:阈值
# threshIneq:正负标签划分方式
def stumpClassify(dataMatrix, dimen, threshVal, threshIneq):
retArray = ones((shape(dataMatrix)[0], 1))
if(threshIneq == 'lt'):
retArray[dataMatrix[:, dimen] <= threshVal] = -1.0
else:
retArray[dataMatrix[:, dimen] > threshVal] = -1.0
return retArray
# 单层决策树(决策树的一个简化版本)
# 遍历所有的特征、各特征下的多种取值、大于或小于两种方式
# 找到在该权重向量 D 下的最优的单层决策树
# dataArr:特征集
# classLabels:标签集
# 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()
rangeMax = dataMatrix[:, i].max()
stepSize = (rangeMax - rangeMin) / numSteps
# 遍历所有步长限定的所有可取阈值
for j in range(-1, int(numSteps) + 1):
# 遍历 > 和 < 两种判断方式
for inequal in ['lt', 'gt']:
# 计算分类结果
threshVal = (rangeMin + float(j) * stepSize)
predictedVals = stumpClassify(dataMatrix, i, threshVal, inequal)
# 统计分类的错误情况
errArr = mat(ones((m, 1)))
errArr[predictedVals == labelMat] = 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
# 基于单层决策树 DS 的训练过程
# dataArr、classLabels:数据集
# numIt:迭代次数
def adaBoostTrainDS(dataArr, classLabels, numIt = 40):
# 存储所有弱分类器的 DS 数组
weakClassArr = []
# 样本数
m = shape(dataArr)[0]
# 初始化权重向量 D
D = mat(ones((m,1)) / m)
# 记录每个数据点的类别估计的累计值
aggClassEst = mat(zeros((m,1)))
for i in range(numIt):
# 根据当前的权重向量 D 生成一个 DS
bestStump,error,classEst = buildStump(dataArr,classLabels,D)
# 计算 alpha (当 error 为 0,用 1e-16 替代,避免除零溢出)
alpha = float(0.5 * log((1.0 - error) / max(error, 1e-16)))
# 将 alpha 存入这个 DS 的字典
bestStump['alpha'] = alpha
# 将该 DS 加入 DS 数组
weakClassArr.append(bestStump)
# 计算用于下一轮迭代的权重向量 D
expon = multiply(-1 * alpha * mat(classLabels).T, classEst)
D = multiply(D, exp(expon))
D = D / D.sum()
# 记录每个数据点的类别估计的累计值
aggClassEst += alpha * classEst
# 计算当前累计值下的分类错误情况
aggErrors = multiply(sign(aggClassEst) != mat(classLabels).T,ones((m,1)))
errorRate = aggErrors.sum() / m
print("total error: ",errorRate)
# 如果已经全部正确分类,则退出循环
if(errorRate == 0.0):
break
return weakClassArr,aggClassEst
# AdaBoost分类函数
# datToClass:测试数据的特征集
# classifierArr:训练好的多个 DS 字典组成的列表
def adaClassify(datToClass, classifierArr):
# 测试数据的特征集
dataMatrix = mat(datToClass)
# 测试样本数
m = shape(dataMatrix)[0]
# 记录每个数据点的类别估计的累计值
aggClassEst = mat(zeros((m,1)))
# 遍历每个 DS
for i in range(len(classifierArr)):
# 计算当前 DS 的分类结果
classEst = stumpClassify(dataMatrix,classifierArr[i]['dim'],classifierArr[i]['thresh'],classifierArr[i]['ineq'])
# 通过 alpha 加权
aggClassEst += classifierArr[i]['alpha'] * classEst
print(aggClassEst)
return sign(aggClassEst)
dataArr, classLabels = loadSimpData()
weakClassArr, aggClassEst = adaBoostTrainDS(dataArr, classLabels)
classResult = adaClassify([[0,0],[5,5]], weakClassArr)
print(classResult)
运行结果:
[[-0.69314718]
[ 0.69314718]]
[[-1.66610226]
[ 1.66610226]]
[[-2.56198199]
[ 2.56198199]]
[[-1.]
[ 1.]]
Process finished with exit code 0