目录
学习完机器学习实战的AdaBoost元算法,简单的做个笔记。文中部分描述属于个人消化后的理解,仅供参考。
所有代码和数据可以访问 我的 github
如果这篇文章对你有一点小小的帮助,请给个关注喔~我会非常开心的~
0. 前言
在分类任务中,我们通常可以不仅仅采用一个算法,而是多个算法模型结合使用,综合每个弱分类器的结果,称为集成方法(ensemble method)或元算法(meta-algorithm)。
通常有以下两种方法:
- bagging:自举汇聚法(bootstrap aggregating),从原数据集中选择若干次与原数据集大小相等的新数据集,每个数据的选择是随机的,即新数据集中,数据可能重复,原数据集中有些数据可能也不会出现在新数据集中。
- boosting:分类器是串行训练的,新分类器根据已有的分类器性能进行训练,集中关注被已有分类器分错的那些数据。
bagging 中,分类器的权重是相等的,boosting 中,分类器的权重不相等。
本篇中,主要介绍 boosting 中的 AdaBoost(adaptive boosting)。
- 优点:泛化错误率低,易编码
- 缺点:对离群点敏感
- 适用数据类型:数值型和标称型数据
1. AdaBoost
AdaBoost 算法流程可描述如下:
- 对每个训练样本设定相等的权重,即数据集构成权重向量
- 训练一个加权错误率最低的最佳弱分类器
- 根据加权错误率,计算弱分类器的分类器权重
- 根据权重向量 和弱分类器的分类器权重 ,更新权重向量
- 继续训练下一个弱分类器......
- 直到弱分类器达到指定数量,或者弱分类器的训练错误率为 为止
- 预测时,将数据通过每个弱分类器,将其结果加权求和
初始时,权重向量 被设定为每个样本相同,即 。
弱分类器的加权错误率 定义为错误样本的权重相加。
弱分类器的分类器权重 定义如下:
更新权重向量 时,若样本分类正确,则权重下降,若样本分类错误,则权重上升:
注:AdaBoost 每次训练的弱分类器,会集中关注那些被分类错误的样本。
2. 单层决策树
单层决策树(decision stump,也称决策树桩),是仅仅基于单个特征进行分类的弱分类器。
在本篇中,使用单层决策树作为弱分类器,使用三重循环构建最佳的单层决策树:
- 第一层循环:遍历每一个特征
- 第二层循环:遍历此特征的每一个阈值(即小于或者大于阈值,属于一类)
- 第三层循环:遍历小于阈值属于正类大于阈值属于反类,和小于阈值属于反类大于阈值属于正类
每一次都计算加权错误率,最后选择加权错误率最小的单层决策树作为这次的弱分类器。
3. 非均衡数据
对于数据是非均匀的情况,可根据实际情况选择:
- Precision、Recall、F-score(F1-measure)
- TPR、FPR、TNR、FNR、AUC
- Accuracy
例如,宁可将反类判成正类,也不愿将正类判成反类,就可以使用 Recall。
这部分可以详见 吴恩达机器学习(九)Precision、Recall、F-score、TPR、FPR、TNR、FNR、AUC、Accuracy
或者可对数据进行欠抽样(undersampling)和过抽样(oversampling):
- 欠抽样:删除部分样例,可将离决策边界较远的样例删除
- 过抽样:复制部分样例,或者可加入与已有样例相似的点
4. 实战案例
以下将展示书中案例的代码段,所有代码和数据可以在github中下载:
4.1. 马病死亡案例
# coding:utf-8
from numpy import *
import matplotlib.pyplot as plt
"""
马病死亡案例
"""
# 加载数据集
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
# 根据特征和阈值划分数据类别
def stumpClassify(dataMatrix, dimen, threshVal, threshIneq):
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)
# 阈值划分数量
numSteps = 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) / 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 = mat(ones((m, 1)))
errArr[predictedVals == labelMat] = 0
# 计算加权错误率
weightedError = D.T * errArr
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 = 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)))
bestStump['alpha'] = alpha
weakClassArr.append(bestStump)
print("classEst: ", classEst.T)
# 更新样本权重向量
expon = multiply(-1 * alpha * mat(classLabels).T, classEst)
D = multiply(D, exp(expon)) / D.sum()
# 更新每个样本的加权分类结果
aggClassEst += alpha * classEst
print("aggClassEst: ", aggClassEst.T)
# 计算当前加权的错误率
aggErrors = multiply(sign(aggClassEst) != mat(classLabels).T, ones((m, 1)))
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)
# 画ROC曲线
def plotROC(predStrengths, classLabels):
cur = (1.0, 1.0)
ySum = 0.0
# 正类数量
numPosClas = sum(array(classLabels) == 1.0)
# 1/正类数量
yStep = 1 / float(numPosClas)
# 1/反类数量
xStep = 1 / float(len(classLabels) - numPosClas)
# 按照从小到大,索引排序
sortedIndicies = predStrengths.argsort()
fig = plt.figure()
fig.clf()
ax = plt.subplot(111)
# 遍历排序后的索引
# 表示属于正类的概率
# 因排序,属于正类的概率越来越大,条件越来越苛刻
# 由初始 TPF->1 FPR->1
# 最终 TPR->0 FPR->0
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 = (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()
# 微积分算AUC面积
# 对多个小长方块求面积之和
# 小长方块的宽为 xStep
# 每一次的长为 cur[1]_i
# 即 cur[1]_1 * xStep + ... + cur[1]_n * xStep
# 即 ySum * xStep
print("the Area Under the Curve is: ", ySum * xStep)
if __name__ == '__main__':
datArr, labelArr = loadDataSet('horseColicTraining2.txt')
classifierArr, aggClassEst = adaBoostTrainDS(datArr, labelArr, 50)
testArr, testLabelArr = loadDataSet('horseColicTest2.txt')
pred = adaClassify(testArr, classifierArr)
errArr = mat(ones((67, 1)))
errNum = errArr[pred != mat(testLabelArr).T].sum()
print(float(errNum) / len(testLabelArr))
plotROC(aggClassEst.T, labelArr)
如果这篇文章对你有一点小小的帮助,请给个关注喔~我会非常开心的~