AdaBoost算法理解与提升树原理及实现
从机器学习三要素理解AdaBoost算法
AdaBoost
- 算法模型:加法模型 f(x)=∑m=1MalphamGm(x) f ( x ) = ∑ m = 1 M a l p h a m G m ( x ) , 最终模型: 最 终 模 型 : G(x) = sign(f(x))$$
- 损失函数:指数函数 基分类器分类错误或正确时 对数据集权值的调整 更为关注误分类 wm+1,i=WmiZmexp−αmyiGm(xi) w m + 1 , i = W m i Z m exp − α m y i G m ( x i ) , Zm=∑i=1NWmiexp−αmyiGm(xi) Z m = ∑ i = 1 N W m i exp − α m y i G m ( x i )
- 算法:前向分步算法
前向分步算法
什么是前向分步算法
原理: 由于模型是加法模型, 如果从前向后, 每一步只学习上个基函数及其系数, 逐步逼近损失函数最小化函数,就可以简化优化的复杂度
从局部最优到全局最优
- 模型:
f(x)=∑m=1Mβmb(x;rm)
f
(
x
)
=
∑
m
=
1
M
β
m
b
(
x
;
r
m
)
其中: b(x;rm)是基函数,弱分类器,rm是基函数的参数 b ( x ; r m ) 是 基 函 数 , 弱 分 类 器 , r m 是 基 函 数 的 参 数 - 损失函数:
L(y,f(x))
L
(
y
,
f
(
x
)
)
结合损失函数及算法模型: 学习加法模型f(x)
经验风险极小化即损失函数极小化问题
极小化损失函数模型为: minβm,rm∑i=1NL(yi,∑m=1Mβmb(x;,rm) min β m , r m ∑ i = 1 N L ( y i , ∑ m = 1 M β m b ( x ; , r m )
根据前向分布算法将损失函数简化–求每步的损失函数的最小化 那么最终的损失函数就是最小
即:
minβ,r∑i=1NL(yi,βb(x;r))
min
β
,
r
∑
i
=
1
N
L
(
y
i
,
β
b
(
x
;
r
)
)
前向分步算法实现步骤
Input: dataSetT=(x1,y1),(x2,y2),....,(xN,yN) d a t a S e t T = ( x 1 , y 1 ) , ( x 2 , y 2 ) , . . . . , ( x N , y N ) 损失函数L(y_i, f(x)) 基函数集合{b(x;, r)}
Output: 加法模型f(x) 加 法 模 型 f ( x )
前向分步算法将: 将同时求解m=1,…,M的所有参数 βm,rm β m , r m 简化为求解各个参数 βm,rm β m , r m 的最优解
具体步骤:
- step1: 初始化f_0(x) = 0
step2: 对M个弱分类器 有m=1,2,…,M轮迭代
(a) 极小化损失函数
(\beta_m, r_m) = \arg\min\limits_{\beta, r}\sum\limits_{i=1}^{N}L(y_i, f_{m-1}(x_i) + \beta b(x_i;r)) 得到参数 得 到 参 数 \beta_m, r_m 分别表示基函数系数以及基函数参数(b)更新: 分 别 表 示 基 函 数 系 数 以 及 基 函 数 参 数 ( b ) 更 新 : f(x) = f_{m-1}(x) + \beta_m b(x;r_m)$step3: 得出最终模型
f(x)=fm(x)=∑m=1M=1βmb(x;rm) f ( x ) = f m ( x ) = ∑ m = 1 M = 1 β m b ( x ; r m )
前向分布算法与AdaBoost
定理: AdaBoost算法是前向分布加法算法的特例, 模型是由基本分类器组成的加法模型
损失函数为指数函数
前向分布算法学习的是加法模型 当基函数为基本分类器时, 该加法模型等价于AdaBoost的最终分类器
f(x)=∑m=1MαmGm(x)
f
(
x
)
=
∑
m
=
1
M
α
m
G
m
(
x
)
损失函数:
L(y,f(x))=exp[−yf(x)]
L
(
y
,
f
(
x
)
)
=
exp
[
−
y
f
(
x
)
]
扩展: 证明前向分布算法损失函数是指数损失函数
假设: m-1迭代,前向分布算法已经得到了
fm−1(x)
f
m
−
1
(
x
)
fm−1(x)=f(m−2)f(x)+αm−1Gm−1(x)=alpha1G1(x)+...+αm−1Gm−1f(x)
f
m
−
1
(
x
)
=
f
(
m
−
2
)
f
(
x
)
+
α
m
−
1
G
m
−
1
(
x
)
=
a
l
p
h
a
1
G
1
(
x
)
+
.
.
.
+
α
m
−
1
G
m
−
1
f
(
x
)
即:
fm(x)=f(m−1)(x)+αmGm(x)
f
m
(
x
)
=
f
(
m
−
1
)
(
x
)
+
α
m
G
m
(
x
)
目标函数:
(αm,Gm(x)=argminα,G∑i=1Nexp[−y(fm−1(xi)+αG(xi)]
(
α
m
,
G
m
(
x
)
=
arg
min
α
,
G
∑
i
=
1
N
exp
[
−
y
(
f
m
−
1
(
x
i
)
+
α
G
(
x
i
)
]
即为:(alpham,Gm(x))=argminα,G∑i=1NWmiexp[−yiαG(xi)]
即
为
:
(
a
l
p
h
a
m
,
G
m
(
x
)
)
=
arg
min
α
,
G
∑
i
=
1
N
W
m
i
exp
[
−
y
i
α
G
(
x
i
)
]
其中
Wmi=exp[−yifm−1(xi)]
W
m
i
=
exp
[
−
y
i
f
m
−
1
(
x
i
)
]
,
Wmi即不依赖α,也不依赖G,但是与fm−1(x)有关
W
m
i
即
不
依
赖
α
,
也
不
依
赖
G
,
但
是
与
f
m
−
1
(
x
)
有
关
接下来证明公式中达到的最小 αm∗和G∗m(x)就是AdaBoost算法所得到的αm和Gmx α m ∗ 和 G m ∗ ( x ) 就 是 A d a B o o s t 算 法 所 得 到 的 α m 和 G m x
- 1.先求:G_m^*(x),对任意\alpha>0
G∗m(x)=argminα,G∑i=1NWmiI(yi≠G(xi)) G m ∗ ( x ) = arg min α , G ∑ i = 1 N W m i I ( y i ≠ G ( x i ) )
其中: Wmi=exp[−yifm−1(xi)] W m i = exp [ − y i f m − 1 ( x i ) ] - 2.求 αm∗,∑i=1NWmiexp[−yiαG(xi)]=∑yi=Gm(xi)Wmiexp−α+∑yi≠Gm(xi)Wmiexpα(expα−exp−α)∑i=1NWmiI(yi≠G(xi))+exp−α∑i=1NWmi α m ∗ , ∑ i = 1 N W m i exp [ − y i α G ( x i ) ] = ∑ y i = G m ( x i ) W m i exp − α + ∑ y i ≠ G m ( x i ) W m i exp α ( exp α − exp − α ) ∑ i = 1 N W m i I ( y i ≠ G ( x i ) ) + exp − α ∑ i = 1 N W m i
将
Gmx代入,对α求导:α=12log1−emem
G
m
x
代
入
,
对
α
求
导
:
α
=
1
2
log
1
−
e
m
e
m
em是误分类率,em=∑i=1NWmiI(yi≠Gm(xi))
e
m
是
误
分
类
率
,
e
m
=
∑
i
=
1
N
W
m
i
I
(
y
i
≠
G
m
(
x
i
)
)
权值更新为:
Wmi+1,i=Wmiexp[−yiαmGm(xi)]
W
m
i
+
1
,
i
=
W
m
i
exp
[
−
y
i
α
m
G
m
(
x
i
)
]
提升树原理及实现
提升树原理及实现
提升树是以分类数或回归数为基本分类器的提升方法.提升树被认为是统计学习中性能最好的方法之一
提升树模型
提升数模型实际上采用加法模型(即基函数的线性组合)与前向分步算法,以决策树为基函数的提升方法称为提升树
该基函数模型仅仅略优于随机猜测,因此是使用单层决策树
单层决策树的原理及实现:
单层决策树:decision stump 决策树桩
原理: 仅仅基于单个特征来说决策 实质上只有一次分裂过程 就是个树桩
伪代码实现
将最小错误率minError设置为 正无穷大
对数据集中每一个特征(第一层循环):
对每个步长(第二层循环):
对每个不等号(第三层循环):
建立一颗单层决策树并利用加权数据对它进行测试(错误率)
如果错误率低于minError,则将当前单层决策树设置为最佳单层决策树
返回最佳单层决策树
import numpy as np
def loadSimpData():
""" 测试数据
Returns:
dataArr feature对应的数据集
labelArr feature对应的分类标签
"""
dataSet = np.array([[1., 2.1], [2., 1.1], [1.3, 1.], [1., 1.], [2., 1.]])
labelMat = [1.0, 1.0, -1.0, -1.0, 1.0]
return dataSet, labelMat
# 建立单层决策树
def stumpTree(dataSet, labelMat, D):
"""
建立单层决策树
:param dataSet: 训练数据集 特征值
:param labelMat: 训练数据集 类别标签
:param D: 权重值
:return
bestStumpTree 最佳单层决策树
"""
dataMat = np.mat(dataSet)
labelMat = np.mat(labelMat).T
m, n = np.shape(dataMat)
# print('m, n', m, n)
# print('dataMat:', dataMat)
# print('labelMat:', labelMat)
# step1 设置最小误差
minError = np.inf # 最小误差率设置为正无穷
numSteps = 10.0
bestStump = dict()
bestClasEst = np.mat(np.zeros((m, 1)))
# step2 对每一个特征进行循环 计算步长
# print('训练数据集特征', featureNums)
for index in range(n):
# 连续性数据 分类 需要计算步长
rangeMin = dataMat[:, index].min()
rangeMax = dataMat[:, index].max()
delta = (rangeMax - rangeMin) / numSteps
# step3 第二层循环
for j in range(-1, int(numSteps)+1):
# step4 第三层循环 建立一颗单层决策树并利用加权数据对它进行测试(错误率)
for inequal in ['lt', 'gt']:
threshVal = (rangeMin + j * delta)
predictedVals = stumpClassify(dataMat, index, threshVal, inequal)
# print('predictedVals 结果集', predictedVals) # matrix 形式的结果集
# 计算加权错误率 weightedError
errorMat = np.mat(np.ones((m, 1)))
errorMat[predictedVals == labelMat] = 0
weightedError = D.T * errorMat
# print('index', index)
if weightedError < minError:
minError = weightedError
bestClasEst = predictedVals.copy()
bestStump['dim'] = index
bestStump['thresh'] = threshVal
bestStump['ineq'] = inequal
return bestStump, minError, bestClasEst
def stumpClassify(dataMat, index, threshVal, threshIneq):
"""
单层决策树分类
在阈值一边的数据会分到类别-1
另一边的阈值分到类别1
首先将全部数组元素设置为1 然后将不满足不等式要求的元素设置为-1
:param dataMat: 训练数据集 输入空间
:param index: 训练数据集 特征index
:param threshVal: 阈值
:param threshIneq: 小于或大于
:return
retArray: 结果集
"""
# print('try', (np.shape(dataMat))[0])
dimen = (np.shape(dataMat))[0]
retArray = np.ones((((np.shape(dataMat))[0]), 1)) # 5x1
if threshIneq == 'lt':
retArray[dataMat[:, index] <= threshVal] = -1.0
else:
retArray[dataMat[:, index] > threshVal] = -1.0
return retArray
基于单层决策树的AdaBoost算法实现
伪代码
对每次迭代:
利用stumpTree()函数找到最佳的单层决策树
将最佳的单层决策树加入到决策树组
计算alpha
计算新的权重向量D
更新累计类别估计值
如果错误率==0, 退出循环
def adaBoostTrainDT(dataMat, labelMat, maxCycle):
"""
基于单层决策树的AdaBoost算法实现
:param dataMat: 训练数据集 输入空间
:param labelMat: 训练数据集 输出空间
:param maxCycle: 最大迭代次数
:return
strongDtree 决策数组
"""
strongDtree = list()
m, n = np.shape(dataMat)
# 初始化权重向量D
D = np.ones((m, 1)) / m
# print('初始化权重向量D', D)
upClassEst = np.mat(np.zeros((m, 1)))
for cycle in range(maxCycle):
bestStump, minError, bestClasEst = stumpTree(dataMat, labelMat, D)
# print('predictedVals 结果', bestClasEst) # 结果
# 计算 alpha 系数
alpha = float(0.5 * np.log((1-minError)/max(minError, 0.001)))
bestStump['alpha'] = alpha
strongDtree.append(bestStump)
# 更新D 权重向量
# 错误分类的加重权重 正确分类的减少权重
expon = np.multiply(-1*alpha*np.mat(labelMat).T, bestClasEst)
D = np.multiply(D, np.exp(expon))
D = D/D.sum()
# 更新累计类别估计值
upClassEst += alpha * bestClasEst
aggError = np.multiply(np.sign(upClassEst) != np.mat(labelMat).T, np.ones((m, 1)))
errorRate = aggError.sum() / m
print('errorRate', errorRate)
if errorRate == 0.0:
break
return strongDtree
测试算法及效果
# 测试算法: 基于AdaBoost的分类
def adaClassify(dataToClass, classifierArr):
"""
基于AdaBoost的强分类器的分类
:param dataToClass: 输入变量
:param classifierArr: 强分类器
:return
np.sign(aggClassEst) 分类结果
"""
# do stuff similar to last aggClassEst in adaBoostTrainDS
dataMat = np.mat(dataToClass)
m = np.shape(dataMat)[0]
aggClassEst = np.mat(np.zeros((m, 1)))
# 循环 多个分类器
for i in range(len(classifierArr)):
# 前提: 我们已经知道了最佳的分类器的实例
# 通过分类器来核算每一次的分类结果,然后通过alpha*每一次的结果 得到最后的权重加和的值。
classEst = stumpClassify(dataMat, classifierArr[i]['dim'], classifierArr[i]['thresh'],
classifierArr[i]['ineq'])
aggClassEst += classifierArr[i]['alpha'] * classEst
print(aggClassEst)
return np.sign(aggClassEst)
def main():
dataSet, labelMat = loadSimpData()
strongDtree = adaBoostTrainDT(dataSet, labelMat, maxCycle=40)
print('最终分类器:', strongDtree)
result = adaClassify([[5, 5], [0, 0]], strongDtree)
print('测试结果:', result)
if __name__ == '__main__':
main()
提升树-疝气病马预测
def loadDataSet(filename):
dataSet = list()
labelMat = list()
with open(filename, 'r') as f:
for lines in f.readlines():
lis = lines.split('\t')
lineArr = []
for i in lis[:-1]:
lineArr.append(float(i))
# print(lineArr)
# print(len(lineArr))
dataSet.append(lineArr)
labelMat.append(float(lis[-1]))
print(np.shape(dataSet), type(dataSet[0][0])) # (299, 21) <class 'float'>
print(np.shape(labelMat), type(dataSet[0][0])) # (299, ) <class 'float'>
return dataSet, labelMat
def main():
filename = 'horseColicTraining2.txt'
dataSet, labelMat = loadDataSet(filename)
strongDtree, aggClassEst = adaBoostTrainDT(dataSet, labelMat, maxCycle=10)
print('strongDtree', strongDtree)
print('aggClassEst', aggClassEst)
plotROC(aggClassEst.T, labelMat)
filename = 'horseColicTest2.txt'
testSet, testlabel = loadDataSet(filename)
predictList = adaClassify(testSet, strongDtree)
# 测试:计算总样本数,错误样本数,错误率
m = np.shape(testSet)[0]
error = np.mat(np.ones((m, 1)))
print('测试样本个数%s 错误个数%s 错误率%.2f%%' % (m, error[predictList != np.mat(testlabel).T].sum(), error[predictList != np.mat(testlabel).T].sum()/m *100))
参考文献
《统计学习方法》,李航著
《机器学习实战》