目录
引言
一、 基于数据集多重抽样的分类器
1.1 bagging:基于数据随机重抽样的分类器构建方法
1.2 boosting
二、 训练算法
三、基于单层决策树构建弱分类器
四、完整AdaBoost算法的实现
五、测试算法:基于AdaBoost的分类
六、在一个难数据集上应用AdaBoost
七、非均衡分类问题
7.1 其他分类性能度量指标:正确率、召回率及ROC曲线
元算法是对其他算法进行组合的一种方式。AdaBoost是最流行的元算法。这一章学习讨论boosting方法及其代表分类器AdaBoost,建立单层决策树分类器。AdaBoost算法将应用在上述单层决策树分类器之上。
先前学习了KNN、决策树、朴素贝叶斯、logistic回归、支持向量机五种不同的分类算法,它们都拥有各自的优缺点。将不同的分类器组合起来的结果就称为是集成方法亦或者称为元算法。集成方法会有很多不同的形式,可以是不同算法的集成,也可以是同一个算法在不同设置下的集成,还可以是数据集不同部分分配给不同分类器之后的集成。
AdaBoost算法的优缺点:
优点:泛化错误率降低,易编码,可以应用在大部分分类器上,无参数调整
缺点:对离群点敏感
适用数据类型: 数值型和标称型数据
自举汇聚法即bagging法是在原始数据集选择S次后得到S个新数据集的一种技术新数据集和原数据集大小相等,每个数据集都是通过在原始数据集中随机选择一个样本来进行可替换而得到的。这里的替换就意味着可以多次选择同一个样本。这一个性质允许新数据集中可以有重复的值存在,而原本的数据集的某些值就不存在了。
建立好S个数据集后,将学习算法分别作用于每个数据集就得到了S个分类器。当对新数据进行分类是,可以应用S个分类器进行分类,选择分类器投票结果中得票数最多的类别作为分类结果。
boosting是一种与bagging很类似的技术。不论是在boosting还是在bagging中,所使用的多个分类器类型是一致的,前者的不同分类器是通过串行训练得到的,每个新分类器都根据已训练出的分类器的性能来进行训练。而boosting通过集中关注被已有分类器错分的那些数据来获得新的分类器。
bagging中分类器的权重是相等的,boosting中的分类器权重并不相等,每个权重代表的是其对应分类器在上一轮迭代中的成功度。
AdaBoosting的一般流程
1、收集数据:可以使用任意方法
2、准备数据:依赖于所使用的弱分类器类型,这里使用的是单层决策树,该分类器可以处理任何类型数据。也可以使用任意分类器作为弱分类器。
3、分析数据:可以使用任意方法
4、训练算法:AdaBoost的大部分时间都用在训练上,分类器将多次在同一个数据集上训练弱分类器
5、测试算法:计算分类的错误率
6、使用算法:同SVM一样,AdaBoost预测两个类别中的一个,将它应用到多个类别的场合,就要像多类SVM中的做法一样对AdaBoost进行修改。
AdaBoost的运行过程如下:训练数据中的每个样本,并赋予其一个权重,权重构成向量D。将这些权重都初始化成相等值。先在训练数据上训练一个弱分类器并计算该分类器的错误率,然后再同一数据集上再次训练弱分类器。再分类器的第二次训练中,将会重新调整每个样本的权重,其中第一次分对的样本的权重将会降低,第一次错误分的权重将会提高。
将错误率定义为:
alpha的计算公式为:
AdaBoost算法流程如下图:
图1 AdaBoost算法的示意图计算除alpha值后,对权重向量D进行更新,使得正确分类权重降低错误分类权重升高。D的计算方法如下:
某个样本的被正确分类,那么该样本的权重更改为:
如果是某个样本被错分,该样本的权重更改为:
在计算出D之后,AdaBoost就开始进入下一轮迭代。AdaBoost算法会不断的重复训练和调整权重的过程,直到训练错误率达到0或弱分类器的数目达到用户的指定值为止。
单层决策树是一种简单的决策树。构建AdaBoost代码时首先需要通过一个简单数据集来确保在算法实现上一切就绪。创建adaboost.py的新文件加入代码:
def loadSimpData():
datMat = matrix([[1.0, 2.1], # 构建矩阵
[2.0, 1.1],
[1.3, 1.0],
[1.0, 1.0],
[2.0, 1.0]])
classLabels = [1.0, 1.0, -1.0, -1.0, 1.0]
return datMat, classLabels
这里是创建数据集,将矩阵中的数据呈现在坐标系中:
import adaboost
from numpy import *
import matplotlib.pyplot as plt
dataMat, classLabels = adaboost.loadSimpData()
fig = plt.figure()
ax = fig.add_subplot(111)
ax.scatter(array(dataMat[:, 0]), array(dataMat[:, 1]).tolist())
plt.show()
:
建立单层决策数,令第一个函数用于测试是否有某个值小于或者大于我们正在测试的阈值。第二函数在一个加权数据集中集中循环,并找到具有最低错误率的单层决策树。
# 单层决策树分类函数
def stumpClassify(dataMatrix, dimen, threshVal, threshIneq):
retArray = ones((shape(dataMatrix)[0], 1)) # 创建以dataMatrix的行数为行,1为列的1矩阵
if threshIneq == 'lt':
retArray[dataMatrix[:, dimen] <= threshVal] = -1.0 # 如果小于阈值,则赋值为-1
else:
retArray[dataMatrix[:, dimen] > threshVal] = -1.0 # 如果大于阈值,则赋值为-1
return retArray
# 找到数据集上最佳的单层决策树
def buildStump(dataArr, classLabels, D):
dataMatrix = mat(dataArr); labelMat = mat(classLabels).T
m, n = shape(dataMatrix)
numSteps = 10.0; bestStump = {}; bestClasEst = mat(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']: # 大于和小于的情况,均遍历。lt:less than,gt:greater than
threshVal = (rangeMin + float(j) * stepSize) # 计算阈值
predictedVals = stumpClassify(dataMatrix, i, threshVal, inequal) # 计算分类结果
errArr = np.mat(np.ones((m,1))) # 初始化误差矩阵
errArr[predictedVals == labelMat] = 0 # 分类正确的,赋值为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
bestClasEst = predictedVals.copy()
bestStump['dim'] = i
bestStump['thresh'] = threshVal
bestStump['ineq'] = inequal
return bestStump, minError, bestClasEst
三层嵌套的for循环是程序中最主要的部分,第一层for循环在数据集的所有特征上遍历。通过计算最小值和最大值来了解应该需要多大的步长。第二层的for循环再这些值上遍历。甚至将阈值设置为整个取值范围之外也是可以的。最后一个for循环则是再大于和小于之间切换不等式。嵌套的三层for循环之内,在数据集及三个循环变量上调用stumpClassify()函数,基于这些循环变量,函数会返回分类预测结果。errArr的作用为:当predictedVals中的值不等于labelMat中的真正类别标签值,errArr的相应位置为1。
了解实际运行过程:
dataMat, classLabels = adaboost.loadSimpData()
D = mat(ones((5, 1)) / 5)
bestStump,minError,bestClasEst = adaboost.buildStump(dataMat,classLabels,D)
print('bestStump:\n', bestStump)
print('minError:', minError)
print('bestClasEst:\n', bestClasEst)
刚才创建了一个基于加权输入值进行决策的分类器。现在实现一个完整的AdaBoost算法就拥有了所有需要的所有信息。
实现的伪代码为:
对每次迭代:
利用buildStump()函数找到最佳的单层决策树
将最佳单层决策树加入到单层决策树数组
计算alpha
计算新的权重向量D
更新累计类别估计值
如果错误率等于0.0,退出循环
代码如下:
# 迭代次数为numIt次或者知道训练错误率为0为止
def adaBoostTrainDS(dataArr, classLabels, numIt = 40): # 设置迭代次数为40次
weakClassArr = []
m = shape(dataArr)[0] # m为dataArr的行数值
D = mat(np.ones((m, 1)) / m) # 包含每个数据点的权重 一开始所有权重为1/m
aggClassEst = mat(zeros((m, 1))) # 记录每个数据点的类别估计累计值
for i in range(numIt):
bestStump, error, classEst = buildStump(dataArr, classLabels, D) # 获得字典、错误率、类别估计值
print("D:", D.T) # 输出D
alpha = float(0.5 * log((1.0-error) / max(error, 1e-16))) # 确保没有错误时不会发生除零溢出
bestStump['alpha'] = alpha # alpha值加入到bestStump字典中
weakClassArr.append(bestStump) # 字典又添加到列表中,字典包括了分类所需要的所有信息
print("classEst:", classEst.T)
expon = multiply(-1*alpha*np.mat(classLabels).T, classEst)
D = multiply(D, exp(expon))
D = D / D.sum() # 计算新权重
aggClassEst += alpha*classEst
print("aggClassEst:", aggClassEst.T)
aggErrors = multiply(sign(aggClassEst) != mat(classLabels).T, ones((m,1)))
# sign()得到二值分类结果
errorRate = aggErrors.sum()/m # 误差值
print("total error:", errorRate, "\n")
if errorRate == 0.0: break
return weakClassArr
结果为:
dataMat, classLabels = adaboost.loadSimpData()
weakClassArr, aggClassEst = adaboost.adaBoostTrainDS(dataMat, classLabels)
print(weakClassArr)
print(aggClassEst)
拥有多个弱分类器以及其对应的alpha值,进行测试就很容易了,将弱训练器的训练过程从程序中抽离出来,应用到具体实例上,每个弱分类器的结果以其对应的alpha值作为权重。所有这些弱分类器的结果加权求和就得到了最后的结果。
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)
adaClassify()函数是利用训练出的弱分类器进行分类的函数,函数的输入是由一个或多个待分类样例datToClass以及多个弱分类器组成的数组classifierArr.函数adaClassify()首先将dtToClass转换成了一个Numpy矩阵,并得到datToClass中待分类样例的个数m。构建一个0列向量aggClassEst,这个列向量与adaBoostTrainDS()含义一致。
遍历classifierArr中所有弱分类器,并基于stumpClassify()对每个分类器得到一个类别的估计值,输出的类别估计值乘上该单层决策树的alpha权重然后累加到aggClassEstshang。
当没有弱分类器数组是,可以输入:
dataArr, labelArr = adaboost.loadSimpData()
classifierArr = adaboost.adaBoostTrainDS(dataArr, labelArr, 30)
adaboost.adaClassify([0, 0], classifierArr)
输出结果为:
添加自适应数据加载函数:
def loadDataSet(fileName):
numFeat = len(open(fileName).readline().split('\t'))
dataMat = []; labelMat = []
fr = open(fileName)
for line in fr.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
dataArr, labelArr = adaboost.loadDataSet('D:\\learning\\horseColicTraining2.txt')
classifierArray = adaboost.adaBoostTrainDS(dataArr, labelArr, 10)
print(classifierArray)
结果为:
表1 不同弱分类器数目情况下的AdaBoost测试和分类错误率错误率指的是在所有测试样例中错分的样例比例,实际上这样的错误掩盖了样例是如何被错分的事实,机器学习中存在一个普遍适用的混淆矩阵,可以帮助了解分类中的错误。
表2 一个三类问题的混淆矩阵当矩阵中的非对角元素均为0,就会得到一个完美的分类器。当面的二类问题时混淆矩阵可以表示为:
表3 二类问题的混淆矩阵,输出采用不同的类别标签当某个类别的重要性高于其他类别时,可以利用上述定义来定义出多个比错误率更好的新指标。第一个指标是正确率,给出的预测为正例样本中真正正例的比例,第二个指标是召回率给出的是预测为正例的真实正例的比例。在召回率很大的分类器中,真正判错的正例数目并不多。
ROC曲线是另一个用于度量分类中的非均衡性的工具,代表接收者操作特征。
ROC曲线不但可以用于比较分类器,还可以基于成本效益分析来做出决策。在不同的阈值下不同的分类器的表现情况可能各不相同。
ROC曲线的绘制以及AUC计算函数:
def plotROC(predStrengths, classLabels):
cur = (1.0, 1.0) # 绘制光标的位置
ySum = 0.0 # 用于计算AUC
numPosClas = np.sum(np.array(classLabels) == 1.0) # 统计正类的数量
yStep = 1 / float(numPosClas) # y轴步长
xStep = 1 / float(len(classLabels) - numPosClas) # x轴步长
sortedIndicies = predStrengths.argsort() # 预测强度排序
fig = plt.figure()
fig.clf() # 清除所有轴
ax = plt.subplot(111)
# 每得到一个标签为1.0的类,则要沿着y轴的方向下降一个步长,即不断降低真阳率
# 类似的对其他类别的标签,则是在x轴方向上到退了一个步长
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') # 绘制ROC
cur = (cur[0] - delX, cur[1] - delY) # 更新绘制光标的位置
ax.plot([0, 1], [0, 1], 'b--')
plt.title('ROC curve for adaboost horse colic detection system')
plt.xlabel('false positive rate',)
plt.ylabel('true positive rate')
ax.axis([0, 1, 0, 1])
print('AUC面积为:', ySum * xStep) # y轴的矩形高度乘xStep得到AUC面积
plt.show()
这里需要将adaboostTrainDS()的返回值改为:
return weakClassArr, aggClassEst
结果图为: