本节代码存放在github网址上,环境:python3
利用AdaBoost元算法提高分类性能
元算法(meta-algorithm)或者集成方法(ensemble method)是对其他算法进行组合的一种方式
AdaBoost算法
优点:范化错误率低,易编码,可以应用在大部分分类器上,无参数调整
缺点:对离群点敏感
适用数据类型: 数值型和标称型数据
bagging和boosting所使用的多个分类器类型都是一致的!
bagging: 基于数据随机抽样的分类器构建方法
放回取样得到S个数据集,将某个学习算法分别作用于每个数据集就得到了S个分类器,要对新数据进行分类时,可以应用这S个分类器进行分类,
选取分类器投票结果中最多的类别作为最后的分类结果。分类器的权重是相等的。
random forest 也是bagging方法的一种
boosting:通过集中关注被已有分类器错分的那些数据来获得新的分类器
不同的分类器是通过串行训练获得的,每个新的分类器都根据已训练出的分类器的性能来训练。
其分类结果是基于所有分类器的加权求和结果的,分类器权重并不相等,每个权重代表的是其对应分类器在上一轮迭代中的成功度。
AdaBoost一般流程
- 收集数据:可以使用任何方法
- 准备数据:依赖于所使用的弱分类器类型,本章使用的是单层决策树,这种分类器可以处理任何数据类型。当然也可以使用任意分类器作为弱分类器,KNN, 决策树,朴素贝叶斯,Logistic回归,SVM中任一分类器都可以充当弱分类器。作为弱分类器,简单分类器的效果最好。 确保类别标签是+1和-1,而非1和0.
- 分析数据:可以使用任一方法分析数据:可以使用任一方法
- 训练算法:AdaBoost的大部分时间都用在训练上, 分类器将多次在同一数据集上训练弱分类器。
- 测试算法:计算分类的错误率测试算法:计算分类的错误率
- 使用算法:同SVM同,AdaBoost预测两个类别中的一个。若想把它应用到多分类的场合,需要像多类SVM中的做法一样对AdaBoost进行修改。使用算法:同SVM同,AdaBoost预测两个类别中的一个。若想把它应用到多分类的场合,需要像多类SVM中的做法一样对AdaBoost进行修改。
AdaBoost运行过程
训练数据中的每个样本,并赋予其一个权重,这些权重构成了向量D. 一开始,这些权重都初始化成相等值。首先在训练集上训练出一个弱分类器,并计算该分类器的错误率,然后在同一数据集上再次训练弱分类器。在分类器的第二次训练中会调整每个样本的权重,其中第一次分对的的样本中的权重将会降低,而第一次分错的样本中的权重将会提高。为了从所有弱分类器中得到最终的分类结果,AdaBoost为每个弱分类器都分配了一个权重值alpha,这些alpha是基于每个弱分类器的错误率进行计算的。
错误率ε定义为:
alpha定义为:
计算出alpha后,可以对权重D进行更新,以使那些正确分类的样本权重降低而错分样本的权重提高。D的计算方法如下:
若某个样本被正确分类,该样本权重更改为:
若某个样本被错分,该样本权重更改为:
基於单层决策树构建弱分类器
伪代码如下:
将最小错误率minError设为+inf
对数据集中每个特征(第一层循环):
对每个步长(第二层循环):
对每个不等号(第三层循环):
建立一棵单层决策树并利用加权数据集对它进行测试
如果错误率低于minError,则将当前单层决策树设为最佳单层决策树
返回最佳单层决策树
python代码如下:
# -*- coding: utf-8 -*-
__author__ = 'WF'
# import warnings
import numpy as np
# warnings.simplefilter(action='ignore', category=FutureWarning)
# 加载简单数据集
def loadSimpleData():
dataMat = np.matrix([[1., 2.1],
[2., 1.1],
[1.3, 1.],
[1., 1.],
[2., 1.]])
classLabel = [1.0, 1.0, -1.0, -1.0, 1.0]
return dataMat, classLabel
# 图形化展示数据
def plot_simpledata():
import matplotlib.pyplot as plt
x, y = loadSimpleData()
y = np.array(y)
x = np.array(x)
# print(y)
# x1 = x[np.nonzero(y)[0]]
x1 = x[np.where(y == 1.0)[0]]
# print(np.nonzero(y)[0])
x0 = x[np.where(y == -1.0)[0]]
# print(x0[:, 0], x0[:, 1])
figure = plt.figure()
ax = figure.add_subplot(111)
ax.scatter(x0[:, 0], x0[:, 1], marker='<', c='r', s=50)
ax.scatter(x1[:, 0], x1[:, 1], marker='s', c='g', s=20)
plt.show()
# 单层决策树生成函数
def stumpClassify(dataMatrix, dimen, threshVal, threshIneq):
'''
通过阈值比较对数据进行分类,所有在阈值一边的数据会被分到-1,另一边的数据被分到1
:param dataMatrix:数据矩阵
:param dimen: 维度属性
:param threshVal: 阈值
:param threshIneq:阈值比较符号
:return:单层决策树字典,错误率,类别估计
'''
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
# 遍历stumpClassify()函数所有的可能输入值,并找到数据集上最佳的单层决策树
def buildStump(dataArr, classLabels, D):
# classLabels = np.reshape(classLabels, (len(classLabels), 1))
dataMatrix = np.mat(dataArr)
labelMat = np.mat(classLabels).T
# print(np.shape(labelMat)) # (5,1)
m, n = np.shape(dataMatrix)
numSteps = 10.0
bestStump = {}
bestClasEst = np.mat(np.zeros((m, 1)))
minError = np.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)
# print('p:', predictedVals)
# print(np.shape(predictedVals)) #(5, 1)
errArr = np.mat(np.ones((m, 1)))
# print(np.where(predictedVals == labelMat)[0])
# for num in range(len(predictedVals)):
# print(predictedVals[num] == classLabels[num])
# if float(predictedVals[num]) == float(classLabels[num]):
# errArr[num][0] = 0
# print(errArr)
errArr[predictedVals == labelMat] =0
# print(errArr)
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
**
完整AdaBoost算法实现
**
伪代码:
对每次迭代:
利用buildStump()函数找到最佳的单层决策树
将最佳的单层决策树加入到单层决策树组
计算alpha
计算新的权重向量D
更新累计类别估计值
如果错误率等于0.0, 则退出循环
python代码如下:
def adaBoostTrainDS(dataArr, classLabels, numIt = 40):
'''
:param dataArr:数据集(不包含label)
:param classLabels: 类别标签
:param numIt: 迭代次数
:return:
'''
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 = np.multiply(-1*alpha*np.mat(list(map(float, 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('errorRate:', errorRate)
if errorRate == 0.0:
break
return weakClassArr, aggClassEst
基于AdaBoost的分类
每个弱分类器的结果以其对应的alpha作为权重,所有这些弱分类器的结果加权求和就得到最后的结果。程序返回aggClassEst(记录每个数据点的类别估计累计值)的符号,如果aggClassEst大于0则返回+1, 否则返回-1
python代码如下:
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:', aggClassEst)
return(np.sign(aggClassEst))
完整代码如下
:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
__author__ = 'WF'
# import warnings
import numpy as np
# warnings.simplefilter(action='ignore', category=FutureWarning)
def loadSimpleData():
dataMat = np.matrix([[1., 2.1],
[2., 1.1],
[1.3, 1.],
[1., 1.],
[2., 1.]])
classLabel = [1.0, 1.0, -1.0, -1.0, 1.0]
return dataMat, classLabel
def adaptive_load_data(filename):
numFeat = len(open(filename).readline().strip().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
def plot_simpledata():
import matplotlib.pyplot as plt
x, y = loadSimpleData()
y = np.array(y)
x = np.array(x)
# print(y)
# x1 = x[np.nonzero(y)[0]]
x1 = x[np.where(y == 1.0)[0]]
# print(np.nonzero(y)[0])
x0 = x[np.where(y == -1.0)[0]]
# print(x0[:, 0], x0[:, 1])
figure = plt.figure()
ax = figure.add_subplot(111)
ax.scatter(x0[:, 0], x0[:, 1], marker='<', c='r', s=50)
ax.scatter(x1[:, 0], x1[:, 1], marker='s', c='g', s=20)
plt.show()
def stumpClassify(dataMatrix, dimen, threshVal, threshIneq):
'''
通过阈值比较对数据进行分类,所有在阈值一边的数据会被分到-1,另一边的数据被分到1
:param dataMatrix:数据矩阵
:param dimen: 维度属性
:param threshVal: 阈值
:param threshIneq:阈值比较符号
:return:单层决策树字典,错误率,类别估计
'''
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):
# classLabels = np.reshape(classLabels, (len(classLabels), 1))
dataMatrix = np.mat(dataArr)
labelMat = np.mat(classLabels).T
# print(np.shape(labelMat)) # (5,1)
m, n = np.shape(dataMatrix)
numSteps = 10.0
bestStump = {}
bestClasEst = np.mat(np.zeros((m, 1)))
minError = np.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)
# print('p:', predictedVals)
# print(np.shape(predictedVals)) #(5, 1)
errArr = np.mat(np.ones((m, 1)))
# print(np.where(predictedVals == labelMat)[0])
# for num in range(len(predictedVals)):
# print(predictedVals[num] == classLabels[num])
# if float(predictedVals[num]) == float(classLabels[num]):
# errArr[num][0] = 0
# print(errArr)
errArr[predictedVals == labelMat] =0
# print(errArr)
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
def adaBoostTrainDS(dataArr, classLabels, numIt = 40):
'''
:param dataArr:数据集(不包含label)
:param classLabels: 类别标签
:param numIt: 迭代次数
:return:
'''
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 = np.multiply(-1*alpha*np.mat(list(map(float, 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('errorRate:', errorRate)
if errorRate == 0.0:
break
return weakClassArr, aggClassEst
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:', aggClassEst)
return(np.sign(aggClassEst))
def plotROC(predStrengths, classLabels):
import matplotlib.pyplot as plt
cur = (1.0, 1.0)
ySum = 0.0 # 用于计算AUC的值
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() # 清除当前 figure 的所有axes,但是不关闭这个 window,所以能继续复用于其他的 plot。
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)
if __name__ == "__main__":
dataMat, classLabels = loadSimpleData()
# plot_simpledata()
# D = np.mat(np.ones((5, 1))/5)
# bestStump, minError, bestClasEst = buildStump(dataMat, classLabels, D)
# print(bestStump, minError, bestClasEst)
# weakClassArr = adaBoostTrainDS(dataMat, classLabels, 9)
# print(weakClassArr)
#
# print(adaClassify([[0, 0], [5, 5]], weakClassArr))
datArr, labelArr = adaptive_load_data('horseColicTraining2.txt')
# print(datArr)
print(labelArr)
classifierArr, aggClassEst = adaBoostTrainDS(datArr, labelArr, 10)
# testArr, testLabelArr = adaptive_load_data('horseColicTest2.txt')
# prediction10 = adaClassify(testArr, classifierArr)
# print(prediction10.T)
# errArr = np.mat(np.ones((67, 1)))
# errnum = errArr[prediction10 != np.mat(testLabelArr).T].sum()
# print('errornum:', errnum, 'errorRate:', errnum/67)
plotROC(aggClassEst.T, labelArr)
分类性能度量指标:准确率、召回率、ROC曲线及AUC
TP(真阳):将正例判定为正例
FP(假阳):将反例判定为正例
TN(真阴):将反例判定为反例
FN(假阴):将正例判定为反例
错误率指在所有测试样例中错分的比例。
准确率指在预测为正例的样本中真正正例的比例
P = TP/(TP+FP)
召回率(查全率)指预测为正例的真正正例占所有真实正例的比例
R=TP/(TP+FN)
我们很容易构造一个高准确率或者高召回率的分类器,但是很难保证两者同时成立。如果将任何样本都判为正例,则召回率达到100%,但是准确率很低。
另一个用来度量分类中的非均衡性的工具是ROC曲线(ROC Curve),ROC代表接收者操作特征(receiver operating characteristic)
ROC曲线中,横轴是假阳率=FP/(FP+TN),纵轴是真正率=TP/(TP+FN).
ROC曲线给出的是当阈值变化时假阳率和真阳率变化的情况。左下角点是将所有样例判为反例的情况。
右上角是将所有样例判为正例的情况。
理想情况下,最佳分类器应该尽可能的处于左上角,即分类器在假阳率很低的同时获得了很高的真阳率。
对不同的ROC曲线进行比较的一个指标是曲线下的面积AUC(Area Under the Curve),其给出的是分类器的平均性能值,一个完美的分类器AUC为1.0, 随机猜测的AUC为0.5.
上述代码的ROC曲线如下图:
基于代价函数的分类器决策控制
cost = TP*0 + FP*1 + TN*0 + FN*1
cost = TP*(-5) + FP*50 + TN*0 + FN*1
处理非均衡问题的数据抽样方法
过抽样或者欠抽样
过抽样是复制样例,欠抽样是删除样例,选择离决策边界较远的样例进行删除。