Adaboost是一种集成学习的方法,当采用基于简单模型的单个分类器对样本进行分类的效果不理想时,人们希望能够通过构建并整合多个分类器来提高最终的分类性能。
Boosting方法并不是简单地对多个分类器的输出进行投票决策,而是通过一种迭代过程对分类器的输入和输出进行加权处理。在不同应用中可以采用不同类型的弱分类器,在每次迭代过程中,根据分类的情况对各个样本进行加权,而不仅仅是简单的重采样。
弱分类器:人们常称不理想的单个分类器称为弱分类器
Boosting算法的训练过程:
目前,最为广泛使用的Boosting方法是提出的AdaBoost算法。这里对这个算法做一个简单的介绍。
设给定 N N N个训练样本 { x 1 , x 2 , . . . . x N } {\{x_1,x_2,....x_N}\} {x1,x2,....xN},用 G m ( x ) ∈ { − 1 , 1 } ( m = 1 , . . . , M ) {G_m}\left( x \right) \in \left\{ { - 1,1} \right\}\left( {m = 1,...,M} \right) Gm(x)∈{−1,1}(m=1,...,M)表示 M M M个弱分类器在样本 x x x上的输出,通过AdaBoost算法构造这 M M M个分类器并进行决策的具体过程如下:
1. 初始化训练样本 { x 1 , x 2 , . . . . x N } {\{x_1,x_2,....x_N}\} {x1,x2,....xN}的权重 w i = 1 / N , i = 1 , . . . . N {w_i} = 1/N,{\kern 1pt} {\kern 1pt} {\kern 1pt} {\kern 1pt} {\kern 1pt} {\kern 1pt} {\kern 1pt} i = 1,....N wi=1/N,i=1,....N
2. 对 m = 1 → M m = 1 \to M m=1→M,重复使用以下过程:
(1)利用 { w i } \{w_i\} {wi}加权后的训练样本构造分类器 G m ( x ) ∈ { − 1 , 1 } {G_m}\left( x \right) \in \left\{ { - 1,1} \right\} Gm(x)∈{−1,1}。(注意构造弱分类器的具体算法可以不同,例如使用线性分类器和决策树等。)
(2)计算样本用 { w i } \{w_i\} {wi}加权后的分类错误率 e m e_m em,并令 c m = 1 / 2 ∗ log ( ( 1 − e m ) / e m ) {c_m} = 1/2*\log \left( {\left( {1 - {e_m}} \right)/{e_m}} \right) cm=1/2∗log((1−em)/em)
(3)更新训练数据集的权重分布:
Z m = ∑ i = 1 N w m i exp ( − c m y i G m ( x ) ) {Z_m} = \sum\limits_{i = 1}^N {{w_{mi}}} \exp \left( { - {c_m}{y_i}{G_m}\left( x \right)} \right) Zm=i=1∑Nwmiexp(−cmyiGm(x)), Z Z Z是规范化因子。
w m + 1 , i = w m i ⋅ exp ( − c m y i G m ( x ) ) Z m , i = 1 , . . . N {w_{m + 1,i}} = \frac{{{w_{mi}} \cdot \exp \left( { - {c_m}{y_i}{G_m}\left( x \right)} \right)}}{{{Z_m}}},i=1,...N wm+1,i=Zmwmi⋅exp(−cmyiGm(x)),i=1,...N
D m + 1 = ( w m + 1 , 1 , w m + 1 , 2 , . . . . . . . w m + 1 , N ) D_{m+1}=({w_{m+1,1},w_{m+1,2},.......w_{m+1,N}}) Dm+1=(wm+1,1,wm+1,2,.......wm+1,N) 新的权值分布。
3. 构造基本分类器的线性组合
f ( x ) = ∑ m = 1 M c m G m ( x ) f\left( x \right) = \sum\limits_{m = 1}^M {{c_m}} {G_m}\left( x \right) f(x)=m=1∑McmGm(x)
对于待分类样本 x x x,分类器的输出为 s i g n [ ∑ m = 1 M c m G m ( x ) ] sign[\sum\limits_{m = 1}^M {{c_m}} {G_m}\left( x \right)] sign[m=1∑McmGm(x)]
这里补充说明几点:
(1) M M M表示设置迭代的次数,也是AdaBoost算法构造弱分类器的最大个数。
(2)错误率 e m e_m em小于一个最小值时停止循环,或者是 e m e_m em==0停止循环也行。 e m e_m em在 c m {c_m} cm中作为分母,设计程序时需要避开 e m = 0 e_m=0 em=0的情况。
接下来,只训练一个弱分类器。
(1)数据集:
{1,2.1,1
1.5,1.6,1
1.3,1,-1
1,1,-1
2,1,1}
(2)图
从图中发现,训练一个单层决策树的弱分类器,无论怎么划分,都存在一个点误分类。
(3)模型测试
# 找出数据集上最佳的单层决策树
def get_Stump(xMat, yMat, D):
"""
参数说明:
xMat:特征矩阵
yMat:标签矩阵
D:样本权重
返回:
bestStump:最佳单层决策树信息
minE:最小误差
bestClass:最佳的分类结果
"""
m, n = xMat.shape #m为样本的个数,n为特征数
Step = 10 # 初始化一个步数
bestStump = {} # 用字典形式来存储树桩信息
bestClass = np.mat(np.zeros((m, 1))) # 初始化分类结果为1
minE = np.inf # 最小误差初始化为正无穷大
for i in range(n): # 遍历所有特征值
min = xMat[:, i].min() # 找到特征的最小值
max = xMat[:, i].max() # 找到特征的最大值
stepSize = (max - min)/ Step # 计算步长
for j in range(-1, int(Step)+1):
for S in ['lt', 'gt']: # 大于和小于的情况,均遍历
Q = (min + j * stepSize) # 计算阈值
re = Classify0(xMat, i, Q, S) # 计算分类结果
err = np.mat(np.ones((m,1))) # 初始化误差矩阵
err[re == yMat] = 0 # 分类正确的,赋值为0
eca = D.T * err # 计算误差
if eca < minE: # 找到误差最小的分类方式
minE = eca
bestClass = re.copy()
bestStump['特征值'] = i
bestStump['阈值'] = Q
bestStump['标志'] = S
return bestStump, minE, bestClass
(4)训练结果
bestStump:{'特征值': 0, '阈值': 1.3, '标志': 'lt'}
minE:[[0.2]]
bestClass:[[-1.]
[ 1.]
[-1.]
[-1.]
[ 1.]]
单层决策树程序设计的传入参数有
xMat:特征矩阵
yMat:标签矩阵
D:样本权重
在上述讲解的算法步骤中,第二步的第三小步,更新训练数据集的权重分布,将更新的权值作为参数D重新传入到单层决策树中,返回误差minE通过公式 c m = 1 / 2 ∗ log ( ( 1 − e m ) / e m ) {c_m} = 1/2*\log \left( {\left( {1 - {e_m}} \right)/{e_m}} \right) cm=1/2∗log((1−em)/em)得到的 c m {c_m} cm作为新增分类器的权重。
Adaboost集成训练的程序:
# 基于单层决策树的Adaboost训练过程
def Ada_train(xMat, yMat, maxC=40):
"""
函数功能:基于单层决策树的Adaboost训练过程
参数说明:
xMat:特征矩阵
yMat:标签矩阵
maxC:最大迭代次数
返回:
weakClass:弱分类器信息
aggClass:类别估值(更改标签估值)
"""
weakClass = []
m = xMat.shape[0]
D = np.mat(np.ones((m, 1))/m) # 初始化权重
aggClass = np.mat(np.zeros((m,1)))
for i in range(maxC):
Stump, error, bestClass = get_Stump(xMat, yMat, D) # 构造单层决策树
alpha = float(0.5*np.log((1-error)/max(error, 1e-6))) # 计算弱分类器权重alpha
Stump['alpha'] = np.round(alpha, 2)
weakClass.append(Stump) # 存储单层决策树
expon = np.multiply(-1*alpha*yMat, bestClass) # 计算e的指数项
D = np.multiply(D, np.exp(expon))
D = D / D.sum() # 更新权重
aggClass += alpha * bestClass # 计算类别估值(更改标签估值)
aggErr = np.multiply(np.sign(aggClass) != yMat, np.ones((m, 1))) # 统计误分类样本数
errRate = aggErr.sum()/m
print("total error: ", errRate)
if errRate == 0:
break
return weakClass, aggClass
完整程序:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Date : 2020/6/30 15:46
"""
1.实现单层决策树,找出数据集上最佳的单层决策树
2.实现多层的分类(adaboost)
"""
import numpy as np
import matplotlib.pyplot as plt
# 加载文件
def loadDataSet(path = 'horseColicTraining2.txt'):
data = list()
labels = list()
with open(path) as f:
lines = f.readlines()
for line in lines:
line = line.rstrip().split('\t')
lineArr = []
for i in range(len(line)-1):
lineArr.append(float(line[i]))
data.append(lineArr)
labels.append(float(line[-1]))
xMat = np.array(data)
yMat = np.array(labels).reshape(-1, 1)
return xMat, yMat
# 按照阈值分类结果
def Classify0(xMat, i, Q, S):
"""
xMat:数据矩阵
Q:阈值
S:标志
"""
re = np.ones((xMat.shape[0], 1))
if S == 'lt':
re[xMat[:, i] <= Q] = -1 # 如果小于阈值,则赋值为-1
else:
re[xMat[:, i] > Q] = 1 # 如果大于阈值,则赋值为1
return re
# 找出数据集上最佳的单层决策树
def get_Stump(xMat, yMat, D):
"""
参数说明:
xMat:特征矩阵
yMat:标签矩阵
D:样本权重
返回:
bestStump:最佳单层决策树信息
minE:最小误差
bestClass:最佳的分类结果
"""
m, n = xMat.shape #m为样本的个数,n为特征数
Step = 10 # 初始化一个步数
bestStump = {} # 用字典形式来存储树桩信息
bestClass = np.mat(np.zeros((m, 1))) # 初始化分类结果为1
minE = np.inf # 最小误差初始化为正无穷大
for i in range(n): # 遍历所有特征值
min = xMat[:, i].min() # 找到特征的最小值
max = xMat[:, i].max() # 找到特征的最大值
stepSize = (max - min)/ Step # 计算步长
for j in range(-1, int(Step)+1):
for S in ['lt', 'gt']: # 大于和小于的情况,均遍历
Q = (min + j * stepSize) # 计算阈值
re = Classify0(xMat, i, Q, S) # 计算分类结果
err = np.mat(np.ones((m,1))) # 初始化误差矩阵
err[re == yMat] = 0 # 分类正确的,赋值为0
eca = D.T * err # 计算误差
if eca < minE: # 找到误差最小的分类方式
minE = eca
bestClass = re.copy()
bestStump['特征值'] = i
bestStump['阈值'] = Q
bestStump['标志'] = S
return bestStump, minE, bestClass
# 基于单层决策树的Adaboost训练过程
def Ada_train(xMat, yMat, maxC=40):
"""
函数功能:基于单层决策树的Adaboost训练过程
参数说明:
xMat:特征矩阵
yMat:标签矩阵
maxC:最大迭代次数
返回:
weakClass:弱分类器信息
aggClass:类别估值(更改标签估值)
"""
weakClass = []
m = xMat.shape[0]
D = np.mat(np.ones((m, 1))/m) # 初始化权重
aggClass = np.mat(np.zeros((m,1)))
for i in range(maxC):
Stump, error, bestClass = get_Stump(xMat, yMat, D) # 构造单层决策树
alpha = float(0.5*np.log((1-error)/max(error, 1e-6))) # 计算弱分类器权重alpha
Stump['alpha'] = np.round(alpha, 2)
weakClass.append(Stump) # 存储单层决策树
expon = np.multiply(-1*alpha*yMat, bestClass) # 计算e的指数项
D = np.multiply(D, np.exp(expon))
D = D / D.sum() # 更新权重
aggClass += alpha * bestClass # 计算类别估值(更改标签估值)
aggErr = np.multiply(np.sign(aggClass) != yMat, np.ones((m, 1))) # 统计误分类样本数
errRate = aggErr.sum()/m
print("total error: ", errRate)
if errRate == 0:
break
return weakClass, aggClass
# 开始对待预测的数据进行分类
def AdaClassify(xMat, weakClass):
m = xMat.shape[0] # 待分类数据集的长度
aggClass = np.mat(np.zeros((m, 1)))
for i in range(len(weakClass)): # 遍历所有分类器进行分类
classEst = Classify0(xMat,
weakClass[i]['特征值'],
weakClass[i]['阈值'],
weakClass[i]['标志'],
)
aggClass += classEst * weakClass[i]['alpha']
return np.sign(aggClass)
# ROC图像
def plotROC(predStrengths, classLabels):
"""
输入:
predStrengths : Adaboost预测的结果(行)
classLabels : 原本训练数据的标签(列)
"""
cur = (1.0, 1.0) # 光标
ySum = 0.0 # 变量来计算AUC
numPosClas = sum(np.array(classLabels) == 1.0)
yStep = 1 / float(numPosClas) # 向上的步长
xStep = 1 / float(len(classLabels) - numPosClas) # 向右的步长
sortedIndicies = predStrengths.argsort() # 得到排序索引,它是反向的
fig = plt.figure()
fig.clf()
ax = plt.subplot(111)
# 循环所有的值,在每个点上画一条线段
for index in sortedIndicies.tolist()[0]:
if classLabels[index] == 1.0:
delX = 0
delY = yStep
else:
delX = xStep
delY = 0
ySum += cur[1]
# draw line from cur to (cur[0]-delX,cur[1]-delY)
ax.plot([cur[0], cur[0] - delX], [cur[1], cur[1] - delY], c='b') # 逐渐加入曲线变化的一条直线
cur = (cur[0] - delX, cur[1] - delY) # 重新更新cur的起始点
ax.plot([0, 1], [0, 1], 'b--')
plt.xlabel('False positive rate')
plt.ylabel('True positive rate')
plt.title('ROC curve for AdaBoost horse colic detection system')
ax.axis([0, 1, 0, 1])
plt.show()
print("the Area Under the Curve is: ", ySum * xStep)
if __name__ == '__main__':
xMat, yMat = loadDataSet(path='horseColicTraining2.txt') # 训练数据
# 测试单层决策树
m = xMat.shape[0]
D = np.mat(np.ones((m, 1))/m)
bestStump, minE, bestClass = get_Stump(xMat, yMat, D)
# 测试单层决策树的Adaboost训练过程
weakClass, aggClass = Ada_train(xMat, yMat, maxC=10) # 返回弱分类器的集合,以及弱分类的标签值
print('分类器的个数:', len(weakClass))
testArr, testLabelArr = loadDataSet(path='horseColicTest2.txt') # 测试数据
pre = AdaClassify(testArr, weakClass) # 返回预测值
# 计算准确度
errArr = np.mat(np.ones((len(pre), 1))) # 一共有m个预测样本
cnt = errArr[pre != testLabelArr].sum()
print('误分类点在总体预测样本中的比例为:', cnt / len(pre))
# 绘画出ROC图
plotROC(aggClass.T, yMat)
结果(1):
分类器为:
[{'特征值': 2, '阈值': 36.72, '标志': 'lt', 'alpha': 0.27},
{'特征值': 0, '阈值': 1.0, '标志': 'lt', 'alpha': 0.15},
{'特征值': 18, '阈值': 0.0, '标志': 'lt', 'alpha': 0.14},
{'特征值': 18, '阈值': 53.400000000000006, '标志': 'lt', 'alpha': 0.16},
{'特征值': 0, '阈值': 0.9, '标志': 'lt', 'alpha': 0.06},
{'特征值': 12, '阈值': 1.2, '标志': 'lt', 'alpha': 0.05},
{'特征值': 0, '阈值': 1.0, '标志': 'lt', 'alpha': 0.04},
{'特征值': 18, '阈值': 53.400000000000006, '标志': 'lt', 'alpha': 0.03},
{'特征值': 0, '阈值': 0.9, '标志': 'lt', 'alpha': 0.04},
{'特征值': 4, '阈值': 57.599999999999994, '标志': 'lt', 'alpha': 0.04}]
结果(3):
误分类点在总体预测样本中的比例为: 0.3283582089552239
the Area Under the Curve is: 0.6757359086266125
参考资料:
1 https://www.bilibili.com/video/BV1it411q7wy?from=search&seid=15656450157548541974
2 《统计学习方法》- 李航