一、写在前面
本文是《统计学习方法》第8章提升方法的笔记,整合了《机器学习实战》中的提升树Python代码。《方法》重理论,但不易理解,《实战》重实践,但缺乏理论基础,特别是AdaBoost算法的解释、提升树与加法模型的关系等。两相结合,应该能获得较为全面的知识。本文是第八章,往后翻了翻,《方法》还有十四章。路漫漫其修远兮,年轻人骑猪仔不能放弃。
二、提升方法
提升方法是从弱学习算法出发,反复学习,得到一系列弱分类器(又称为基本分类器),然后组合这些弱分类器,构成一个强分类器。大多数的提升方法都是改变训练数据的概率分布(训练数据的权值分布),针对不同的训练数据分布调用弱学习算法学习的一系列弱分类器。
对于提升学习中的Adaboost算法,选择提高那些被前一轮弱分类器错误分类的样本的权值,而降低那些被正确分类的样本的权值。这样一来,那些没有得到正确分类的数据,由于其权值加大而受到后一轮的弱分类器的更大关注。于是,分类问题被一系列弱分类器“分而治之”。至于弱分类器的组合,Adaboost采用加权多数表决的方法。具体地,加大分类误差率小的弱分类器的权值,使其在表决中起较大的作用;减小分类误差率大的弱分类器的权值,使其在表决中起较小的作用。
算法流程:
输入:训练数据集、弱学习算法;
输出:最终分类器G(x)G(x)G(x)
(1)初始化训练数据的权值分布:

每个w的下标由两部分构成,前一个数表示当前迭代次数,与D的下标保持一致,后一个数表示第几个权值,与位置保持一致。初始情况下,每个权值都是均等的。
(2)
(a)使用具有权值分布的训练数据集学习,得到基本分类器
(b)计算G(x)G(x)G(x)在训练数据集上的分类误差率:

(c)计算分类器的系数:

这里的对数是自然对数,计算的是该分类器的权值系数。分类误差率越小,分类器权重越大。为什么一定要用这个式子呢?这与前向分步算法的推导有关,在后面的章节会介绍。
(d)更新样本权重:

对于上式的理解:在上一轮中被正确分类的样本,将按一定尺度削弱它下一轮的权重;在上一轮中被错误分类的样本,将按一定尺度加强它下一轮的权重;且当前分类器的误分率越低、权重越大,那么这个尺度也越大。
ZmZ_mZm是规范化因子:

(3)构建基本分类器的线性组合

这里,分类器权重之和并不为1.
得到最终分类器:

Adaboost具有适应性,即它能适应弱分类器各自的训练误差率,这也是它名称的由来,Ada是Adaptive的简写。
三、提升树
提升树是以分类树或回归树为基本分类器的提升方法。提升树被认为是统计学习中性能最好的方法之一。
提升树算法采用前向分步算法。首先确定初始提升树,然后第m歩的模型是:

其中:fm−1(x)f_{m-1}(x)fm−1(x)是前一轮的弱分类器。通过经验风险极小化确定下一棵决策树的参数θ\thetaθ。

由于树的线性组合可以很好地拟合训练数据,即使数据中的输入与输出之间的关系很复杂也是如此,所以提升树是一个髙功能的学习算法。
不同问题有大同小异的提升树学习算法,其主要区别在于使用的损失函数不同。包括用平方误差损失函数的回归问题,用指数损失函数的分类问题,以及用一般损失函数的一般决策问题。
对于二类分类问题,提升树算法只需将AdaBoost算法中的基本分类器限制为二类分类树即可,可以说这时的提升树算法是AdaBoost算法的特殊情况,接下来通过《机器学习实战》中的代码学习其应用。
四、python实现二分类Adaboost算法
测试数据;
def loadSimpData():
"""
加载简单数据集
:return:
"""
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
可视化一下:
def plotData(datMat, classLabels):
xcord0 = []
ycord0 = []
xcord1 = []
ycord1 = []
for i in range(len(classLabels)):
if classLabels[i]==1.0:
xcord1.append(datMat[i,0]), ycord1.append(datMat[i,1])
else:
xcord0.append(datMat[i,0]), ycord0.append(datMat[i,1])
fig = plt.figure()
ax = fig.add_subplot(111)
ax.scatter(xcord0,ycord0, marker='s', s=90)
ax.scatter(xcord1,ycord1, marker='o', s=50, c='red')
plt.title('decision stump test data')
plt.show()
dataMat,classLabels=loadSimpData()
plotData(dataMat,classLabels)
调用:
dataMat,classLabels=loadSimpData()
plotData(dataMat,classLabels)
单层决策树分类:单层决策树分类就是一个树桩,传入数据和分裂点,输出分类结果。
def stumpClassify(dataMatrix, dimen, threshVal, threshIneq): # just classify the data
"""
用只有一层的树桩决策树对数据进行分类
:param dataMatrix: 数据
:param dimen: 特征的下标
:param threshVal: 阈值
:param threshIneq: 大于或小于
:return: 分类结果
"""
retArray = ones((shape(dataMatrix)[0], 1))
if threshIneq == 'lt':
retArray[dataMatrix[:, dimen] <= threshVal] = -1.0
else:
retArray[dataMatrix[:, dimen] > threshVal] = -1.0
return retArray
构建决策树桩:上个函数到底取什么参数才好呢?给定训练数据集的权重向量,利用该向量计算错误率加权和,取最低的参数作为训练结果。下列函数返回的是最优树桩、误差率和最优分类结果。
def buildStump(dataArr,classLabels,D):
dataMatrix=mat(dataArr)
labelMat=mat(classLabels).T
m,n=shape(dataMatrix)
numStep=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)/numStep
for j in range(-1,int(numStep)+1):
for inequal in ['lt','gt']:
threshVal=(rangeMin+float(j)*stepSize)
predicteVals=stumpClassify(dataMatrix,i,threshVal,inequal)
errArr=mat(ones(((m,1))))
errArr[predicteVals==labelMat]=0
weightedError=D.T*errArr
if weightedError<minError:
minError=weightedError
bestClasEst=predicteVals.copy()
bestStump['dim']=i
bestStump['thresh']=threshVal
bestStump['ineq']=inequal
return bestStump,minError,bestClasEst
AdaBoost训练:有了上面的基础,就不难看懂完整的训练代码了。
def adaBoostTrainDS(dataArr, classLabels, numIt=40):
"""
基于单层决策树的ada训练
:param dataArr: 样本特征矩阵
:param classLabels: 样本分类向量
:param numIt: 迭代次数
:return: 一系列弱分类器及其权重,样本分类结果
"""
weakClassArr = []
m = shape(dataArr)[0]
D = mat(ones((m, 1)) / m) # 将每个样本的权重初始化为均等
aggClassEst = mat(zeros((m, 1)))
for i in range(numIt):
bestStump, error, classEst = buildStump(dataArr, classLabels, D) # 构建树桩决策树,这是一个若分类器,只能利用一个维度做决策
# print "D:",D.T
alpha = float(
0.5 * log((1.0 - error) / max(error, 1e-16))) # 计算 alpha, 防止发生除零错误
bestStump['alpha'] = alpha
weakClassArr.append(bestStump) # 保存树桩决策树
# print "classEst: ",classEst.T
expon = multiply(-1 * alpha * mat(classLabels).T, classEst) # 每个样本对应的指数,当预测值等于y的时候,恰好为-alpha,否则为alpha
D = multiply(D, exp(expon)) # 计算下一个迭代的D向量
D = D / D.sum() # 归一化
# 计算所有分类器的误差,如果为0则终止训练
aggClassEst += alpha * classEst
# print "aggClassEst: ",aggClassEst.T
aggErrors = multiply(sign(aggClassEst) != mat(classLabels).T, ones((m, 1))) # aggClassEst每个元素的符号代表分类结果,如果与y不等则表示错误
errorRate = aggErrors.sum() / m
print("total error: ", errorRate)
if errorRate == 0.0: break
return weakClassArr, aggClassEst
上面的流程在理论部分已有详细解释,可以参照理论部分的公式对应着看。
下面是运用提升树进行分类:
def adaClassify(datToClass, classifierArr):
dataMatrix = mat(datToClass)
m = shape(dataMatrix)[0]
aggClassEst = mat(zeros((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)
return sign(aggClassEst)
datArr, labelArr = loadSimpData()
classifierArr, aggClassEst = adaBoostTrainDS(datArr, labelArr, 30)
print(adaClassify([1, 1], classifierArr))
输出:
total error: 0.2
total error: 0.2
total error: 0.0
[[-0.69314718]]
[[-1.66610226]]
[[-0.77022252]]
[[-1.]]
可以看到,分类过程中,随着加法模型的不断叠加,对于(1,1)这个点,其累加结果是“负”得越来越厉害的,最终取符号输出-1类别。而在训练集中,真实数据确实属于-1,可见adaboost算法是有效的。
本文结合《统计学习方法》与《机器学习实战》,详细介绍了Adaboost算法原理及提升树方法,并提供了Python实现代码。
717

被折叠的 条评论
为什么被折叠?



