本篇博文涵盖如下内容:
- 组合相似的分类器来提高分类性能
- 应用AdaBoost算法
- 处理非均衡分类问题
元算法(meta-algorithm): 对其他算法进行组合的一种方式。
最为流行的元算法: AdaBoost
本篇博文会讨论以下几个问题:
- 讨论不同分类器的集成方法
- 主要关注boosting方法及其代表分类器Adaboost
- 建立一个单层决策树(decision stump)分类器,Adaboost 算法将应用在单层决策树分类器之上。
- 讨论非均衡分类问题。当我们试图对样例数目不均衡的数据进行分类时,就会遇到这个问题。
7.1 基于数据集多重抽样的分类器
集成方法(ensemble method)或者元算法(meta-algorithm):将不同的分类器组合起来,使用集成方法时会有多种形式,可以是不同算法的集成,也可以是同一算法在不同设置下的集成。
AdaBoost
优点: 泛化错误率低,易编码,可以应用在大部分分类器上,无参数调整。
缺点:对离群点敏感。
适用数据类型:数值型和标称型数据。
7.1.1 bagging:基于数据随机重抽样的分类器构建方法
自举汇聚法(bootstrap aggregating),也称为bagging方法。 是在从原始数据集选择S次后得到S个新数据集的一种技术。新数据集和原数据集的大小相等。每个数据集都是通过在原始数据集中随机 选择一个样本进行替换而得到的。
7.1.2 boosting
boosting 是一种与bagging很类似的技术。不论在boosting还是bagging当中,所使用的多个分类器的类型都是一致的。在bagging当中,不同的分类器是通过串行训练而获得的,每个新分类器都根据已训练出的分类器的性能来进行训练。boosting是通过集中关注被已有分类器错分的那些数据来获得新的分类器。
boosting 分类的结果是基于所有分类器的加权求和结果的,因此boosting与bagging不太一样。
bagging 中的分类器权重是相等的。
而boosting中的分类器权重并不相等,每个权重代表的是其对应分类器在上一轮迭代中的成功度。
7.2 训练算法:基于错误提升分类器的性能
AdaBoost是adaptive boosting(自适应boosting)的缩写,其运行过程如下: 训练数据中的每个样本,并赋予其一个权重,这些权重构成了向量D。
?
?
7.3 基于单层决策树构建弱分类器。
单层决策树(decisionstump,也称为决策树桩)是一种简单的决策树。
# 加载数据
from numpy import matrix
def loadSimpData():
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
通过使用多棵单层决策树,我们就可以构建一个能够对该数据集完全正确分类的分类器。
?
?通过构建多个函数来建立单层决策树。
第一个函数将用于测试是否有某个值小于或者大于我们正在测试的阈值。
第二个函数则更加复杂一些,它会在一个加权数据集中循环,并找到具有最低错误率的单层决策树。
?
'''
Author: Maxwell Pan
Date: 2022-04-25 17:23:12
LastEditTime: 2022-04-25 19:53:59
FilePath: \cp07\adaboost.py
Description:
Software:VSCode,env:
'''
# 加载数据
from cmath import inf
from numpy import matrix
import numpy as np
def loadSimpData():
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
# 单层决策树生成函数
# stumpClassify()通过阈值比较对数据进行分类的。以-1,1进行分类。该函数通过
# 数组过滤实现,首先将返回数组的全部元素设置为1,然后将所有不满足不等式要求的元素设置为-1
def stumpClassify(dataMatrix,dimen, 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
# buildStump()将会遍历stumpClassify()函数所有的可能输入值。并找到数据集上最佳的单层决策树。
def buildStump(dataArr,classLabels,D):
dataMatrix = np.mat(dataArr)
labelMat = np.mat(classLabels).T
m,n = np.shape(dataMatrix)
numSteps = 10.0
bestStump = {}
bestClasEst = np.mat(np.zeros((m,1)))
minError = 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 inequal: %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
以上为弱分类算法。上述单层决策树的生成函数是决策树的一个简化版本。它就是所谓的弱学习器,即弱分类算法。 到现在为止,我们已经构建了单层决策树,并生成了程序,做好了过渡到完整AdaBoost算法的准备。
7.4 完整AdaBoost算法的实现
# 基于单层决策树的AdaBoost训练过程
def adaBoostTrainDS(dataArr,classLabels,numIt=40):
weakClassArr = []
m = np.shape(dataArr)[0]
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)))
bestStump['alpha'] = alpha
weakClassArr.append(bestStump)
print("classEst: ",classEst.T)
# 为下一次迭代计算D
expon = multiply(-1*alpha*np.mat(classLabels).T,classEst)
D = multiply(D,np.exp(expon))
D = D/D.sum()
# 错误率累加计算
aggClassEst += alpha*classEst
print("aggClassEst: ",aggClassEst.T)
aggErrors = 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
?
AdaBoost算法的输入参数包括数据集、类别标签以及迭代次数numIt,其中numIt是在整个AdaBoost算法中唯一需要用户指定的参数。它是AdaBoost中最流行的弱分类器,当然并非唯一可用的弱分类器。
?该数组包含三部词典,其中包含了分类所需要的所有信息。此时,一个分类器已经构建成功, 而且只要我们愿意,随时都可以将训练错误率降到0。
7.5 测试算法:基于AdaBoost的分类
# AdaBoost分类函数
def adaClassify(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)
上述的adaClassify()函数就是利用训练出来2的多个弱分类器进行分类的函数。该函数的输入是由一个或者多个待分类样例datToClass以及多个弱分类器组成的数组classifierArr.函数adaClassify()首先将datToClass转换成了一个NumPy矩阵,并且得 到datToClass中的待分类样例的个数m。然后构建一个0列向量aggClassEst,这个列向量与 adaBoostTrainDS()中的含义一样。
接下来,遍历classifierArr中的所有弱分类器,并基于stumpClassify()对每个分类器 得到一个类别的估计值。
?7.6 示例:在一个难数据集上应用AdaBoost
示例:在一个难数据集上的AdaBoost应用
(1) 收集数据:提供的文本文件。
(2) 准备数据:确保类别标签是+1和-1而非1和0。
(3) 分析数据:手工检查数据。
(4) 训练算法:在数据上,利用adaBoostTrainDS()函数训练出一系列的分类器。
(5) 测试算法:我们拥有两个数据集。在不采用随机抽样的方法下,我们就会对AdaBoost 和Logistic回归的结果进行完全对等的比较。
(6) 使用算法:观察该例子上的错误率。不过,也可以构建一个Web网站,让驯马师输入 马的症状然后预测马是否会死去。
# 自适应数据加载函数
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
?
?
?7.7 非均衡分类问题
7.7.1 其他分类性能度量指标:正确率、召回率及 ROC 曲线
在理想的情况下,最佳的分类器应该尽可能地处于左上角,这就意味着分类器在假阳率很低 的同时获得了很高的真阳率。
# ROC曲线的绘制及AUC计算函数。
def plotROC(predStrengths, classLabels):
import matplotlib.pyplot as plt
cur = (1.0, 1.0)
ySum = 0.0
numPosClas = sum(np.array(classLabels)==1.0)
yStep = 1/float(numPosClas)
xStep = 1/float(len(classLabels)-numPosClas)
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([0,1],[0,1],'b--')
plt.xlabel('False Postive Rate')
plt.ylabel('True Postive 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)
?为了了解实际运行效果,我们需要将adaboostTrainDS()的最后一行代码替换成:
return weakClassArr,aggClassEst
'''
Author: Maxwell Pan
Date: 2022-04-25 17:23:12
LastEditTime: 2022-04-25 21:35:00
FilePath: \cp07\adaboost.py
Description:
Software:VSCode,env:
'''
# 加载数据
from cmath import inf
from numpy import matrix, multiply
import numpy as np
def loadSimpData():
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
# 单层决策树生成函数
# stumpClassify()通过阈值比较对数据进行分类的。以-1,1进行分类。该函数通过
# 数组过滤实现,首先将返回数组的全部元素设置为1,然后将所有不满足不等式要求的元素设置为-1
def stumpClassify(dataMatrix,dimen, 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
# buildStump()将会遍历stumpClassify()函数所有的可能输入值。并找到数据集上最佳的单层决策树。
def buildStump(dataArr,classLabels,D):
dataMatrix = np.mat(dataArr)
labelMat = np.mat(classLabels).T
m,n = np.shape(dataMatrix)
numSteps = 10.0
bestStump = {}
bestClasEst = np.mat(np.zeros((m,1)))
minError = 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 inequal: %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
# 基于单层决策树的AdaBoost训练过程
def adaBoostTrainDS(dataArr,classLabels,numIt=40):
weakClassArr = []
m = np.shape(dataArr)[0]
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)))
bestStump['alpha'] = alpha
weakClassArr.append(bestStump)
print("classEst: ",classEst.T)
# 为下一次迭代计算D
expon = multiply(-1*alpha*np.mat(classLabels).T,classEst)
D = multiply(D,np.exp(expon))
D = D/D.sum()
# 错误率累加计算
aggClassEst += alpha*classEst
print("aggClassEst: ",aggClassEst.T)
aggErrors = 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,aggClassEst
# AdaBoost分类函数
def adaClassify(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)
# 自适应数据加载函数
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
# ROC曲线的绘制及AUC计算函数。
def plotROC(predStrengths, classLabels):
import matplotlib.pyplot as plt
cur = (1.0, 1.0)
ySum = 0.0
numPosClas = sum(np.array(classLabels)==1.0)
yStep = 1/float(numPosClas)
xStep = 1/float(len(classLabels)-numPosClas)
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([0,1],[0,1],'b--')
plt.xlabel('False Postive Rate')
plt.ylabel('True Postive 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)
?
|