PS:该系列数据都可以在图灵社区(点击此链接)中随书下载中下载(如下)

1 基于数据集多重抽样的分类器
我们可以将不同的分类器组合起来,而这种这结果则被称为集成方法(ensemble method) 或者元算法(meta-algorithm)。使用集成方法时会有多种形式:可以是不同算法的集成,也可以是同一算法在不同设置下的集成,还可以是数据集不同部分分配给不同分类器之后的集成。
AdaBoost
优点:泛化错误率低,易编码,可以应用在发部分分类器上,无参数调整。
缺点:对离群点敏感。
适用数据类型:数值型和标称型数据。
1.1 bagging:基于数据随机重抽样的分类器构建方法
自然汇聚法(bootstrap aggregating),也称为bagging方法,是从原始数据集选择S次后得到S个新数据集的一种技术。在S个数据集建好之后,将某个学习算法分别作用于每个数据集就得到了S个分类器。当我们要对新数据进行分类时,就可以应用这S个分类器进行分类。与此同时,选择分类器投票结果中最多的类别作为最后的分类结果。
1.2 boosting
boosting与bagging类似,即在两种中,所使用的多个分类器的类型是一致的。但是在boosting中,不同的分类器是通过串行训练而获得的,每个新分类器都根据已训练出的分类器的性能进行训练。boosting是通过集中关注被已有分类器错分的那些数据来获得新的分类器。
boosting分类的结果基于所有分类器的加权求和结果,bagging中的分类器权重是相等的,而boosting中的分类器权重并不相等,每个权重代表的是其对应分类器上一轮迭代中的成功度。
2 训练算法:基于错误提升分类器的性能
AdaBoost:使用弱分类器和多个实例构建一个强分类器。AdaBoost是adaptive boosting的缩写,其运行过程如下:训练数据中的每个样本,并赋予其一个权重,这些权重构成了向量
D
D
D。一开始,这些权重都初始化成相等值。首先在训练数据上训练出一个弱分类器并计算该分类器的错误率,然后在同一数据集上再次训练弱分类器。在分类器的第二次训练中,会重新调整每个样本的权重,其中第一次分对的样本的权重将会降低,而第一次分错的样本的权重会提高。为了从所有弱分类器中得到最终的分类结果,AdaBoost为每个分类器都分配了一个权重值alpha,这些alpha值是基于每个弱分类器的错误率进行计算的。其中,错误率
ε
\varepsilon
ε定义为:
ε
=
未正确分类的样本数目
所有样本的数目
\varepsilon=\frac{\text {未正确分类的样本数目}}{\text{所有样本的数目}}
ε=所有样本的数目未正确分类的样本数目
alpha计算公式:
α
=
1
2
l
n
(
1
−
ε
ε
)
\alpha=\frac{1}{2}ln(\frac{1-\varepsilon}{\varepsilon})
α=21ln(ε1−ε)
AdaBoost计算流程:

计算出alpha之后,对权重向量
D
D
D进行更新,以使得那些正确分类的样本的权重降低而错分样本的权重升高。
如果某个样本被正确分类,那么该样本的权重更改为:
D
i
(
t
+
1
)
=
D
i
(
t
)
e
−
α
Sum
(
D
)
D_{i}^{(t+1)}=\frac{D_{i}^{(t)} \mathrm{e}^{-\alpha}}{\operatorname{Sum}(D)}
Di(t+1)=Sum(D)Di(t)e−α
如果某个样本被错误分类,那么该样本的权重更改为:
D
i
(
t
+
1
)
=
D
i
(
t
)
e
α
Sum
(
D
)
D_{i}^{(t+1)}=\frac{D_{i}^{(t)} \mathrm{e}^{\alpha}}{\operatorname{Sum}(D)}
Di(t+1)=Sum(D)Di(t)eα
计算出D之后,AdaBoost又开始进入下一轮迭代。AdaBoost算法会不断地重复训练和调整权重的过程,直到训练错误率为0或者弱分类器的数目达到用户的指定值为止。
3 基于单层决策树构建弱分类器
单层决策树(decision stump,也称为决策树桩) 是一种简单的决策树,它仅基于单个特征来做决策。
创建adaboost.py文件,编写代码:
import numpy as np
import matplotlib.pyplot as plt
def loadSimpData():
datMat = np.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 showDataSet(dataMat, labelMat):
'''数据可视化'''
dataPlus = []; dataMinus = []
for i in range(len(dataMat)):
if labelMat[i] > 0:
dataPlus.append(dataMat[i])
else:
dataMinus.append(dataMat[i])
dataPlusArr = np.array(dataPlus)
dataMinusArr = np.array(dataMinus)
plt.scatter(np.transpose(dataPlusArr)[0], np.transpose(dataPlusArr)[1])
plt.scatter(np.transpose(dataMinusArr)[0], np.transpose(dataMinusArr)[1])
plt.show()
在python命令行进行测试:


由数据集示意图可以看出,从某个坐标轴上选择一个值(即选择一条与坐标轴平行的直线)将所有的蓝色点和橙色点分开是不可能的。单层决策树难以处理,所以通过使用多棵单层决策树,就可以构建出一个能够对该数据集完全正确分类的分类器。
伪代码:
将最小错误率minError设为+inf
对数据集中的每一个特征(第一层循环):
对每个步长(第二层循环):
对每个不等号(第三层循环):
建立一棵单层决策树并利用加权数据集对它进行测试
如果错误率低于minError,则将当前单层决策树设为最佳单层决策树
返回最佳单层决策树
向ababoost.py添加代码,保存重载该模块,并在python命令行进行测试:
def stumpClassify(dataMatrix, dimen, threshVal, threshIneq):
'''
通过阈值比较对数据进行分类
threshVal:阈值 threshIneq:标志
'''
retArray = np.ones((np.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 = np.mat(dataArr); labelMat = np.mat(classLabels).T
m, n = np.shape(dataMatrix)
numSteps = 10.0
#bestStump:最佳单层决策树信息 bestClassEst:最佳分类结果
bestStump = {}; bestClassEst = np.mat(np.zeros((m, 1)))
minError = float('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 #计算阈值
predictedVals = stumpClassify(dataMatrix, i, threshVal, inequal)
errArr = np.mat(np.ones((m, 1)))
errArr[predictedVals == labelMat] = 0
weightedError = D.T * errArr #加权错误率
#print("split: dim %d, thresh %.2f, thresh ineqal: %s, the weighted error is %.3f"
# % (i, threshVal, inequal, weightedError))
if weightedError < minError: #找到误差最小的分类方式
minError = weightedError
bestClassEst = predictedVals.copy()
bestStump['dim'] = i
bestStump['thresh'] = threshVal
bestStump['ineq'] = inequal
return bestStump, minError, bestClassEst

上述单层决策树的生成函数是决策树的一个简化版本。它就是所谓的弱学习器,即弱分类算法。
4 完整AdaBoost算法的实现
伪代码:
对每次迭代:
利用buildStump()函数找到最佳的单层决策树
将最佳的单层决策树加入到单层决策树数组
计算alpha
计算新的权重向量D
更新累计类别估计值
如果错误率等于0.0,则退出循环
def adaBoostTrainDS(dataArr, classLabels, numIt=40):
'''基于单层决策树的AdaBoost训练过程'''
weakClassArr = []
m = np.shape(dataArr)[0]
#D:每个数据点的权重,初始时赋予相同的权重
D = np.mat(np.ones((m,1)) / m)
#每个数据点的类别估计累计值
aggClassEst = np.mat(np.zeros((m,1)))
for i in range(numIt):
bestStump, error, classEst = buildStump(dataArr, classLabels, D)
#print("D: ", D.T)
alpha = float(0.5 * np.log((1.0 - error) / max(error, 1e-16))) #max保证不会有除0溢出
bestStump['alpha'] = alpha
weakClassArr.append(bestStump)
#print("classEst: ", classEst.T)
#根据样本是否被正确分类,修改样本的权重
expon = np.multiply(-1 * alpha * np.mat(classLabels).T, classEst)
D = np.multiply(D, np.exp(expon))
D = D / D.sum()
aggClassEst += alpha * classEst
#print("aggClassEst: ", aggClassEst.T)
#错误率累加计算
aggErrors = np.multiply(np.sign(aggClassEst) != np.mat(classLabels).T, np.ones((m, 1)))
errorRate = aggErrors.sum() / m
#print("total error: ", errorRate, "\n")
if errorRate == 0.0:
break
return weakClassArr

数据的类别标签为[1.0, 1.0, -1.0, -1.0, 1.0],在第一轮迭代中,D中的所有值都相等,于是只有第一个数据点被错分了。因此在第二轮迭代中,D向量给第一个点0.5的权重,迭代之后发现第一个数据点已经被正确分类,但此时最后一个数据点却是错分了。D向量中的最后一个元素变为0.5,而D向量中的其他值都变得非常小。最后,在第三次迭代之后aggClassEst所有值得符号和真实类别标签都完全吻合,那么训练错误率为0,程序就此退出。
5 测试算法:基于AdaBoost的分类
def adaClassify(datToClass, classifierArr):
'''
AdaBoost分类函数
datToClass:一个或多个待分类样例 classifierArr:多个弱分类器组成的数组
'''
dataMatrix = np.mat(datToClass)
m = np.shape(dataMatrix)[0]
aggClassEst = np.mat(np.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 np.sign(aggClassEst)

可以发现,随着迭代的进行,数据点[0, 0]和[5, 5]的分类结果越来越强。
6 在一个难数据集上应用AdaBoost
将之前用于Logistic回归来预测患有疝病的马是否能够存活的数据horseColicTraining2和horseColicTest2.txt文件拷贝到adaboost.py所在的文件夹中:
def loadDataSet(fileName):
numFeat = len(open(fileName).readline().split('\t'))
dataMat = []; labelMat = []
with open(fileName, 'r') as fileObject:
for line in fileObject.readlines():
lineArr = []
curLine = line.strip().split('\t')
for i in range(numFeat - 1):
lineArr.append(float(curLine[i]))
dataMat.append(lineArr)
labelMat.append(float(curLine[-1]))
return dataMat, labelMat

要得到错误率,只需将上述错分样例的个数除以67即可。
将弱分类器的数目设定为1到10000之间的几个不同数字,并运行上述过程。在该数据集上得到的错误率比较低,在同一数据集上采用Logistic回归得到的平均错误率为0.35。

发现测试错误率在到达了一个最小值之后又开始上升了,这类现象称之为过拟合(overfitting,也称过学习)。
7 非均衡分类问题
7.1 其他分类性能度量指标:正确率、召回率及ROC曲线
二类问题混淆矩阵(confusion matrix):

真正例(True Positive,TP,也称真阳)、真反例(True Negative,TN,也称真阴)、伪反例(False Negetive,FN,也称假阴)、伪正例(False Positive,FP,也称假阳)。
正确率(Precision):
T
P
/
(
T
P
+
F
P
)
TP/(TP+FP)
TP/(TP+FP),预测为正例的样本中的真正正例的比例。
召回率(Recall):
T
P
/
(
T
P
+
F
N
)
TP/(TP+FN)
TP/(TP+FN),预测为正例的真实正例占所有真实正例的比例。
ROC曲线(ROC curve),ROC代表接收者操作特征(receiver operating characteristic),如下图就是马疝病检测系统的ROC曲线:

ROC曲线中,横轴是伪正例的比例(
假
阳
率
=
F
P
/
(
F
P
+
T
N
)
假阳率=FP/(FP+TN)
假阳率=FP/(FP+TN)),纵轴是真正例的比例(
真
阳
率
=
T
P
/
(
T
P
+
F
N
)
真阳率=TP/(TP+FN)
真阳率=TP/(TP+FN))。ROC曲线给出的是当阈值变化时假阳率和真阳率的变化情况。左下角的点对应的是将所有样例判为反例的情况,而右上角的点是将所有样例判为正例的情况。虚线给出的是随机猜测的结果曲线
在理想的的情况下,最佳的分类器应该尽可能地处于左上角,这意味着分类器在假阳率很低的同时获得了很高的真阳率。例如在垃圾邮件的过滤中,这就相当于过滤了所有的垃圾邮件,但是没有将合法邮件误识为垃圾邮件而放入垃圾邮件的文件夹中。
曲线下的面积(Area Under the Curve,AUC):AUC给出的是分类器的平均性能值,当然它并不能完全代替对整条曲线的观察。一个完美分类器的AUC为1.0,而随机猜测的AUC为0.5。
def plotROC(predStrengths, classLabels):
'''
绘制ROC曲线及计算AUC
predStrengths:分类器的预测强度
'''
cur = (1.0, 1.0) #绘制光标的位置
ySum = 0.0 #用于计算AUC
numPosClass = sum(np.array(classLabels) == 1.0)
yStep = 1 / float(numPosClass)
xStep = 1 / float(len(classLabels) - numPosClass)
#获取排序之后的索引
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]
ax.plot([cur[0], cur[0] - delX], [cur[1], cur[1] - delY], c='b')
cur = (cur[0] - delX, cur[1]- delY)
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)
按照predStrengths(分离器预测强度,即上述的aggClassEst变量)排序,predStrengths小的(负数)被判为反例,大的(正数)被判为正例。那么开始点在(1,1),随着predStrengths的增加,如果样例是正例(FN),那么
真
阳
率
=
T
P
/
(
T
P
+
F
N
)
真阳率=TP/(TP+FN)
真阳率=TP/(TP+FN)就会下降,
假
阳
率
=
F
P
/
(
F
P
+
T
N
)
假阳率=FP/(FP+TN)
假阳率=FP/(FP+TN)保持不变;相反如果样例是反例(TN),那么
假
阳
率
=
F
P
/
(
F
P
+
T
N
)
假阳率=FP/(FP+TN)
假阳率=FP/(FP+TN)会下降,
真
阳
率
=
T
P
/
(
T
P
+
F
N
)
真阳率=TP/(TP+FN)
真阳率=TP/(TP+FN)保持不变。
为了了解实际运行结果。需要将adaboostTrainDS()的最后一行代码替换为:
return weakClassArr, aggClassEst
以得到aggClassEst的值。然后在python命令行进行测试,即得到上述的ROC曲线,得到AUC≈0.8583:

7.2 基于代价函数的分类器决策控制
代价敏感的学习(cost-sensitive learning):

第一张表的分类代价计算公式:
T
P
∗
0
+
F
N
∗
1
+
F
P
∗
1
+
T
N
∗
0
TP*0+FN*1+FP*1+TN*0
TP∗0+FN∗1+FP∗1+TN∗0
第二张表的分类代价计算公式:
T
P
∗
(
−
5
)
+
F
N
∗
1
+
F
P
∗
50
+
T
N
∗
0
TP*(-5)+FN*1+FP*50+TN*0
TP∗(−5)+FN∗1+FP∗50+TN∗0
7.3 处理非均衡问题的数据抽样方法
欠抽样(undersampling) 或者过抽样(oversampling):过抽样意味着复制样例,而欠抽样意味着删除样例。
8 小结
AdaBoost以若学习器作为基分类器,并且输入数据,使其通过权重向量进行加权。在第一次迭代中,所有数据同等权重。在后续的迭代中,前次得迭代中分错的数据的权重会增加。
这里使用单层决策树作为若学习器构建了AdaBoost分类器,实际上任意分类器均可,只要分类器能够处理加权数据。
非均衡分类问题是指在分类器训练时正例数目和反例数目不相等(相差很大),上述介绍了几种方法处理该问题。
本文深入解析AdaBoost算法,介绍其工作原理,包括基于数据集多重抽样的分类器、Bagging和Boosting方法,以及如何使用单层决策树构建弱分类器。文章还探讨了非均衡分类问题,并提供了在复杂数据集上的AdaBoost应用案例。
17万+

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



