AdaBoost算法其实很精炼,算法流程也好理解,但是看了算法的解释版本之后,什么前向分布算法,什么指数损失函数之后有点迷糊了。抛开这些理论性的推导不谈(其实是因为能力有限),通过例子直观的了解AdaBoost算法的计算过程。
简要叙述一下AdaBoost算法的主要过程:
AdaBoost为每个数据样本分配权重,权重符合概率分布,初始权重符合均匀分布,串行训练M个模型,依据每轮训练的模型的错误率(被误分类样本的权重之和)确定当前模型在最终模型中的权重,以及更新训练样本的权重,误分类样本权重升高,分类正确的样本权重降低。
下图的算法流程来自于《统计学习方法》。
下面通过具体的实例来理解AdaBoost算法的流程,例子来自于《统计学习方法》。
第一轮迭代:
此时得到的组合模型中只有一个 ,此时 的分类结果就是最终模型的分类结果。第一轮迭代中6,7,8(6,7,8指的是x的值,不是指的序号)被误分类。此时得到的组合模型在训练数样本上的预测结果如下:
X |
y |
分类结果 |
|||
0 |
1 |
0.4236 |
0.4236 |
1 |
正确 |
1 |
1 |
0.4236 |
0.4236 |
1 |
正确 |
2 |
1 |
0.4236 |
0.4236 |
1 |
正确 |
3 |
-1 |
-0.4236 |
-0.4236 |
-1 |
正确 |
4 |
-1 |
-0.4236 |
-0.4236 |
-1 |
正确 |
5 |
-1 |
-0.4236 |
-0.4236 |
-1 |
正确 |
6 |
1 |
-0.4236 |
-0.4236 |
-1 |
错误 |
7 |
1 |
-0.4236 |
-0.4236 |
-1 |
错误 |
8 |
1 |
-0.4236 |
-0.4236 |
-1 |
错误 |
9 |
-1 |
-0.4236 |
-0.4236 |
-1 |
正确 |
其中sign符号函数如下:
第二轮迭代:
第二轮迭代中3,4,5被误分类,此时得到的最终模型是前两轮模型的线性组合。那么在当前的组合条件下 的分类结果是怎样的?
X |
y |
分类结果 |
||||
0 |
1 |
0.4236 |
0.6496 |
1.0732 |
1 |
正确 |
1 |
1 |
0.4236 |
0.6496 |
1.0732 |
1 |
正确 |
2 |
1 |
0.4236 |
0.6496 |
1.0732 |
1 |
正确 |
3 |
-1 |
-0.4236 |
0.6496 |
0.226 |
1 |
错误 |
4 |
-1 |
-0.4236 |
0.6496 |
0.226 |
1 |
错误 |
5 |
-1 |
-0.4236 |
0.6496 |
0.226 |
1 |
错误 |
6 |
1 |
-0.4236 |
0.6496 |
0.226 |
1 |
正确 |
7 |
1 |
-0.4236 |
0.6496 |
0.226 |
1 |
正确 |
8 |
1 |
-0.4236 |
0.6496 |
0.226 |
1 |
正确 |
9 |
-1 |
-0.4236 |
-0.6496 |
-1.0732 |
-1 |
正确 |
第三轮迭代:
第三轮迭代中0,1,2,9被误分类,此时得到的最终模型是前三轮模型的线性组合。那么在当前的组合条件下 的分类结果是怎样的?
X |
y |
分类结果 |
|||||
0 |
1 |
0.4236 |
0.6496 |
-0.7514 |
0.3218 |
1 |
正确 |
1 |
1 |
0.4236 |
0.6496 |
-0.7514 |
0.3218 |
1 |
正确 |
2 |
1 |
0.4236 |
0.6496 |
-0.7514 |
0.3218 |
1 |
正确 |
3 |
-1 |
-0.4236 |
0.6496 |
-0.7514 |
-0.5254 |
-1 |
正确 |
4 |
-1 |
-0.4236 |
0.6496 |
-0.7514 |
-0.5254 |
-1 |
正确 |
5 |
-1 |
-0.4236 |
0.6496 |
-0.7514 |
-0.5254 |
-1 |
正确 |
6 |
1 |
-0.4236 |
0.6496 |
0.7514 |
0.9774 |
1 |
正确 |
7 |
1 |
-0.4236 |
0.6496 |
0.7514 |
0.9774 |
1 |
正确 |
8 |
1 |
-0.4236 |
0.6496 |
0.7514 |
0.9774 |
1 |
正确 |
9 |
-1 |
-0.4236 |
-0.6496 |
0.7514 |
-0.3218 |
-1 |
正确 |
经过三轮迭代之后,在训练集上的错误率为0。
python实现的AdaBoost算法如下,代码来源于《机器学习实战》。
# coding:utf-8
import numpy as np
def loadSimpData():
dataMat = [
[1.0, 2.1],
[2.0, 1.1],
[1.3, 1.0],
[1.0, 1.0],
[2.0, 1.0]
]
classLabels = [1.0, 1.0, -1.0, -1.0, 1.0]
return dataMat, classLabels
def stumpClassify(dataMatrix, dimen, threshval, threshIneq):
"""
单层决策树分类决策函数,根据指定特征,指定特征的划分阈值,指定特征的划分条件(大于或者小于)构建决策树
:param dataMatrix: 数据矩阵
:param dimen: 分支特征索引编号,按照每个特征进行节点分支
:param threshval: 分支阈值
:param threshIneq: 分支类别(大于或者小于)
:return: 返回分类结果
"""
retArray = np.ones(shape=(dataMatrix.shape[0], 1)) # 初始化所有样本的分类结果为+1
if threshIneq == 'lt':
retArray[dataMatrix[:, dimen] <= threshval] = -1.0 # 将第dimen个特征小于threshval的样本标记为-1
else:
retArray[dataMatrix[:, dimen] > threshval] = -1.0 # 将第dimen个特征大于threshval的样本标记为+1
return retArray
def buildStump(dataArr, classLabels, D):
"""
遍历决策树的所有特征,所有特征的划分阈值,所有特征的划分条件,寻找最佳单层决策树桩(只有一层的决策树)
:param dataArr: 数据样本
:param classLabels: 数据样本标签
:param D: 数据样本权重
:return: 最佳单层决策树分类信息、最低错误率、最优分类结果
"""
dataMatrix = np.mat(dataArr)
labelMat = np.mat(classLabels).T
m, n = dataMatrix.shape
numSteps = 10.0 # 为了寻找最优的划分点,总共尝试的次数
bestStump = {}
bestClasEst = np.mat(np.zeros(shape=(m, 1)))
minError = np.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) # 计算决策树的分支阈值,当j=-1或j=numSteps + 1时,是单分支决策树
predictedVals = stumpClassify(dataMatrix, i, threshVal, inequal) # 计算在当前分支阈值条件下,决策树的分类结果
errArr = np.mat(np.ones(shape=(m, 1))) # errArr矩阵用于保存决策树的预测结果
errArr[predictedVals == labelMat] = 0 # 将errArr矩阵中被当前决策树分类正确的样本对应位置的值置为0
weightedError = D.T * errArr # 计算分类错误率(按位相乘并求和),错误率=所有分类错误样本的权重求和
# print("split: dimension %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
def adaBoostingTrainDS(dataArr, classLabels, numIter=40):
"""
训练AdaBoost集成模型
:param dataArr: 输入样本数据
:param classLabels: 样本数据标签
:param numIter: 训练迭代次数
:return: 每轮迭代的最佳弱决策树信息(特征索引编号、分类阈值、分类条件、分类器权重alpha)
"""
dataArr = np.mat(dataArr)
weakClassArr = [] # 用于记录各个弱分类器的信息
m = dataArr.shape[0] # 训练样本数量
D = np.mat(np.ones(shape=(m, 1)) / m) # 向量D用来保存样本权重,初始权重相等
aggClassEst = np.mat(np.zeros(shape=(m, 1))) # 最终得到的分类函数
for i in range(numIter):
bestStump, error, classEst = buildStump(dataArr, classLabels, D) # 最佳决策树信息(特征编号,阈值,分支条件)、错误率、分类结果
# print("D: ", D.T)
alpha = float(0.5 * np.log((1 - error) / max(error, 1e-16))) # 基于当前弱分类器的分类错误率计算该分类器的最终决策权重
bestStump['alpha'] = alpha
weakClassArr.append(bestStump)
# print("classEst: ", classEst.T)
# 对应统计学习方法中公式8.3、8.4、8.5
expon = np.multiply(-1 * alpha * np.mat(classLabels).T, classEst) # 更新样本权重,分类错误的样本权重增加,分类正确的样本权重减少
D = np.multiply(D, np.exp(expon))
D = D / D.sum() # 将D归一化为一个概率分布,D中元素的和为1
aggClassEst += alpha * classEst # 根据权重整合弱分类器,就是将每个样本对应弱分类器的分类结果乘以对应的alpha然后求和,然后使用sign进行符号化
# print("aggClassEst: ", aggClassEst.T)
# print("np.sign -> ", np.sign(aggClassEst) != np.mat(classLabels).T)
aggErrors = np.multiply(np.sign(aggClassEst) != np.mat(classLabels).T, np.ones(shape=(m, 1))) # 统计集成后的模型预测错误的样本数量
errorRate = aggErrors.sum() / m # 计算错误率
print("total error: ", errorRate)
if errorRate == 0.0: # 直到aggClassEst对全部样本预测正确为止
break
return weakClassArr, aggClassEst
def adaBoostingClassify(dataToClassify, classifierArr):
"""
使用训练好的AdaBoost集成模型进行预测
:param dataToClassify: 数据样本
:param classifierArr: 训练得到的AdaBoost弱模型以及模型对应的权重alpha
:return: 返回数据集的分类结果
"""
dataMatrix = np.mat(dataToClassify) # 待预测的输入数据
m = dataMatrix.shape[0]
aggClassEst = np.mat(np.zeros(shape=(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: ", aggClassEst)
return np.sign(aggClassEst) # 输出最终分类结果,累加结果符号化
if __name__ == '__main__':
dataMat, labelMat = loadSimpData()
D = np.mat(np.ones(shape=(5, 1)) / 5)
# print(D)
# 计算每轮迭代的最佳决策树分类方法
print("#" * 80)
bestStump, minError, bestClasEst = buildStump(dataMat, labelMat, D)
print(bestStump)
print(minError)
print(bestClasEst)
# 训练得到的一些列弱分类器信息
print("#" * 80)
classifyArr, aggClassEst = adaBoostingTrainDS(dataMat, labelMat, numIter=9)
print(classifyArr)
print(aggClassEst)
# 测试adaBoosting算法
print("#" * 80)
testSet = np.array(
[
[0.5, 1.0],
[1.5, 2.0],
[0.5, 1.1],
[0.0, 1.0],
[1.0, 2.0],
[0.0, 0.0]
]
)
classifierResult = adaBoostingClassify(testSet, classifyArr)
print(classifierResult)