一个弱分类器的误差率只比随机猜测好一些,提升的目的就是连续对反复修改的数据应用弱分类算法,由此产生一个弱分类器序列 Gm(x) , m=1,2,3...M ,然后通过一个加权的多数表决来合并全部预测,以产生最终预测
G(x)=sign(∑Mm=1αmGm(x))
这里, αm 由提升方法来计算,它们的作用是对序列中较精确的分类器给予较高的影响。
在每个提升步,数据修改就是对每一训练观测 (xi,yi) 实施加权 wi ,开始,所有权都置成 wi=1/N ,以便第一步能够简单地用通常的方式在数据上训练分类。对每一个相继的迭代 m=2,3...M ,观测的权值被分别修改,并且分类算法被再次应用于加权观测。在第 m 步,那些被前一步导出的分类器 Gm−1(x) 误分类的观测权值提高,而被正确分类的观测权值降低。这样,随着迭代的进行,那些很难正确分类的观测受到了不断增长的影响。因此,每个后继分类器被强制关注被前面的分类器误分类的训练观测。
算法流程如下:
1.初始化观测权值 wi=1/N,i=1,2...N
2.对于 m=1 到 M :
(a)使用权值 wi ,用分类器 Gm(x) 拟合数据
(b)计算 Gm(x) 在训练数据集上的分类误差率
em=P(Gm(xi)!=yi)=∑Ni=1wmiI(Gm(xi)!=yi)
(c)计算 Gm(x) 的系数
αm=12∗log1−emem
(d)更新训练数据的权值分布
Dm+1=(wm+1,1,...wm+1,N)
wm+1,i=wmiZmexp(−αmyiGm(x)),i=1,2...N
这里,Z_m是规范化因子
Zm=∑Ni=1wmiexp(−αmyiGm(x))
它使得 Dm+1 成为一个概率分布
3.构建基本分类器的线性组合
f(x)=∑Mm=1αmGm(x)
得到最终分类器
G(x)=sign(f(x))
由 αm=12∗log1−emem 可以看出,分类误差率越小的基本分类器在最终分类器中的作用越大
在每一次迭代过程中,训练数据的权值更改
AdBoost算法等价于使用以指数函数为损失函数的加法模型,AdaBoost算法是前向分步加法算法的特例。
提升方法实际采用加法模型与前向分步算法,以决策树为基函数的提升方法成为提升树。
对于二类分类问题,提升树算法只需将AdaBoost算法中的基本分类器限制为二类分类树即可。以一个结点的决策树为例,即决策树桩为例,
决策树桩由三部分组成,特征,特征值,不等号的方向,代码见最后
采用平方损失的回归树算法流程如下:
1.初始化 f0(x)=0
2.对 m=1,2,3...M
(a)计算残差 rmi=yi−fm−1(xi),i=1,2...N
(b)拟合残差 rmi 学习一个回归树,得到 T(x;Θ)
(c)更新 fm(x)=fm−1(x)+T(x;Θ)
3.得到回归问题提升树
fM(x)=∑Mm=1T(x;Θ)
上面限定了损失函数并限定了加法模型为树之和,考虑任意损失函数L(x)和任意加法模型f(x)
损失为
L(f)=∑Ni=1L(yi,f(xi)) (A)
对上式进行极小化可以看做是一种数值优化
f^=argminfL(f) (B)
其中,“参数” f∈IRN 是逼近函数 f(xi) 在N个数据点上的函数值:
f={f(x1),f(x2)...f(xN)}
数值优化的过程函数求解(B)式作为分量向量的和
fM=∑Mm=0hm,hm∈IRN
其中 f0=h0 是初始猜测,每个后继的 fm 都是根据当前的参数向量 fm−1 得到的,而 fm−1 是前面导出的向量之和,数值优化方法在计算每个增量向量 hm 上是不同的。
最速下降选择的是 hm=−ρmgm ,其中 ρm 是一个标量,而 gm∈IRN 是在 f=fm−1 处计算的 L(f) 的梯度。梯度 gim 的分量是
gim=[∂L(yi,f(xi)∂f(xi)]f(xi)=fm−1(xi)
步长 ρm 是下式的解
ρm=argminρ L(fm−1−ρgm)
接着,当前解被更新为
fm=fm−1−ρgm
并在下一次迭代过程中重复该过程。最速下降法可以看做是一个非常贪心的策略,因为 −gm 是 IRN 中的一个局部方向,在此方向上, L(f) 在 f=fm−1 上下降最快。
逐步前向提升也是一种非常贪心的策略。在每一步,给定当前的模型 fm−1 和它的拟合 fm−1(xi) ,解树是下式的解
Θ^m=argminΘm∑Ni=1 L(yi,fm−1+T(xi;Θm)) (C)
这样,树预测 T(xi;Θm) 类似于负梯度的分量。两者主要的不同在于树分量不是独立的。他们被限制为一个决策树的预测,而负梯度是无约束的最大下降方向。
如果极小化训练数据上的损失是唯一目的,则最速下降将是最可取的策略。对于任意可微的损失函数,梯度的计算是平凡的,而直接求解式(C)是困难的。然而,梯度仅定义在训练数据点 xi 上,而最终的目标是将 fM(x) 拓广到不在训练集中出现的新数据。
解决这个问题的可能方法是在第m次迭代时引进一棵树 T(xi;Θm) ,使得它的预测 tm 与负梯度尽可能接近。使用平方误差来度量接近程度,即
Θ^=argminΘ∑Ni=1(−gim−T(xi;Θ))2
即我们通过最小平方误差将树T拟合到负梯度值。这里就可以用上面说到的损失函数为平方误差的树回归算法了,负梯度称作广义或伪残差r。
from numpy import *
# adaBoost算法
# 决策树桩仅分裂一次,也就是说只选择一次特征
# 决策树桩由三部分组成,特征,特征值,不等号的方向
# 读入数据
def loadData():
dataMat = matrix([[1. , 2.1],
[2. , 1.1],
[1.3, 1. ],
[1. , 1. ],
[2. , 1. ]])
classLabels = matrix([1.0, 1.0,-1.0, -1.0, 1.0]).T
return dataMat, classLabels
# 根据当前树桩对数据进行分类
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为数据权重向量
# numStep是步数,据此可算出步长, bestClassEst是最优情况下的分类情况
def buildStump(dataMatrix, classLabels, D):
m, n = shape(dataMatrix)
numStep = 10.0
bestStump = {}
minError = inf
bestClasEst = mat(ones((m, 1)))
for i in range(n):
rangeMin = dataMatrix[:, i].min()
rangeMax = dataMatrix[:, i].max()
stepSize = (rangeMax-rangeMin)/numStep
for j in range(-1, int(numStep)+1):
threshVal = rangeMin + j*stepSize
for inequal in ['lt', 'rt']:
predictVals = stumpClassify(dataMatrix, i, threshVal, inequal)
errArr = mat(ones((m, 1)))
errArr[predictVals == classLabels] = 0
weightedError = D.T*errArr
if weightedError < minError:
minError = weightedError
bestClassEst = predictVals
bestStump['dim'] = i
bestStump['thresh'] = threshVal
bestStump['ineq'] = inequal
return bestStump, minError, bestClassEst
# adaBoost训练过程
def adaBoostTrain(dataMatrix, classLabels, numIter=40):
weakClassArr = []
m = shape(dataMatrix)[0]
D = mat(ones((m, 1))/m)
aggClassEst = mat(zeros((m, 1)))
for i in range(numIter):
bestStump, error, classEst = buildStump(dataMatrix, classLabels, D)
# 防止除以零溢出
alpha = float(0.5*log((1-error)/max(error, 1e-16)))
bestStump['alpha'] = alpha
weakClassArr.append(bestStump)
expon = multiply(-1*alpha*classLabels, classEst)
D = multiply(D, exp(expon))
D /= D.sum()
aggClassEst += alpha*classEst
# 比较两个矩阵的值得出预测正误情况
aggError = sign(aggClassEst) != classLabels
errorRate = aggError.sum()/m
if errorRate == 0.0:
break
return weakClassArr
def adaClassify(dataMatrix, classifierArr):
m = shape(dataMatrix)[0]
aggClassEst = mat(zeros((m, 1)))
for i in range(len(classifierArr)):
stump = classifierArr[i]
classEst = stumpClassify(dataMatrix, stump['dim'], stump['thresh'], stump['ineq'])
aggClassEst += stump['alpha']*classEst
return sign(aggClassEst)
dataMat, classLabels = loadData()
classifiers = adaBoostTrain(dataMat, classLabels, 9)
labels = adaClassify(mat([0, 0]), classifiers)
print(labels)