本章节是《机器学习实战》第一部分-分类 的最后一个章节,旨在通过AdaBoost方法提升分类器的性能,多次在同一数据集上训练若分类器,将这些多个弱分类器组合成一个强分类器,以到达更好的分类效果

本次代码考虑了两种缺失值填补法,第一种方法可以在一定程度降低错误率

那么,正文就从这里开始啦!(我的代码都是可直接运行的,只要环境正确

1、adaBoost01_base.py

'''
集成分类器方法有:bagging(boosting aggregating,自举汇聚法)、随机森林(random forest)、boosting等
boosting也可细分为很多种,其中比较流行的一种是AdaBoost(adaptive boosting, 自适应boosting)
AdaBoost一般流程为:
1、收集数据
2、准备数据
3、分析数据
4、训练算法:AdaBoost的大部分时间用在训练上,分类器将多次在同一数据集上训练弱分类器
5、测试算法:计算分类的错误率
6、使用算法
以下是利用多个单层决策树和adaboost算法,在小数据上的运用实例
'''
from numpy import *
import matplotlib.pyplot as plt


def loadSimpleData():
dataMat = matrix([[1.0, 2.1], [2, 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 dataMat, classLabels


# 分析数据,数据可视化
def plot(dataMat, labelMat):
dataArr = array(dataMat)
num = shape(dataArr)[0]
xcord1 = []
ycord1 = [] # 标签为1的数据点坐标
xcord0 = []
ycord0 = [] # 标签为0的数据点坐标
for i in range(num):
if int(labelMat[i]) == 1:
xcord1.append(dataArr[i, 0])
ycord1.append(dataArr[i, 1])
else:
xcord0.append(dataArr[i, 0])
ycord0.append(dataArr[i, 1])
fig = plt.figure()
fig.set_size_inches(18.5, 18.5)
ax = fig.add_subplot(111)
ax.scatter(xcord1, ycord1, s=30, c='red', marker='s')
ax.scatter(xcord0, ycord0, s=30, c='green', marker='o')
plt.show()


# 通过阈值比较对数据进行分类
def stumpClassifiy(dataMat, dimen, threshVal, threshIneq):
retArr = ones((shape(dataMat)[0], 1))
if threshIneq == 'lt':
retArr[dataMat[:, dimen] <= threshVal] = -1.0
else:
retArr[dataMat[:, dimen] > threshVal] = -1.0
return retArr


# 构建单层决策树(决策树的简化版本),是一种弱分类器算法
def buildStump(dataArr, classLabels, D):
dataMat = mat(dataArr)
labelMat = mat(classLabels).T
m, n = shape(dataMat)
numSteps = 10.0 # 用于在特征的所有可能值上进行遍历
bestStump = {} # 在给定权重向量D的情况下,最佳单层决策树的相关信息
bestClassEst = mat(zeros((m, 1)))
minError = inf # 用于寻找最小错误率
for i in range(n): # 第一层循环:在数据集的所有特征上遍历
rangeMin = dataMat[:, i].min() # 找到当前特征的最大/小值
rangeMax = dataMat[:, i].max()
stepSize = 0
try:
stepSize = (rangeMax - rangeMin) / numSteps # 已知数值范围与步数,求步长
except BaseException:
print("error print:", dataMat)
return 0
for j in range(-1, int(numSteps) + 1): # 第二层循环:按一定步长,遍历当前特征的特征值
for inequal in ['lt', 'gt']: # 第三层循环:在大于和小于之间切换不等式
threshVal = (rangeMin + float(j) * stepSize)
# 根据阈值对数据进行分类,得到预测分类值
predictedVals = stumpClassifiy(dataMat, i, threshVal, inequal)
errArr = mat(ones((m, 1)))
errArr[predictedVals == labelMat] = 0
# errArr中类别预测错误处标为1,其余为0;此处求加权和作为错误率
# 此处是AdaBoost与分类器交互的地方,此处基于权重向量D来评价分类器
weightedError = D.T * errArr
# print("split: dim", i, ", thresh", threshVal, ", thresh inequal",
# inequal, ", the weighted error is", weightedError)
if weightedError < minError:
minError = weightedError
bestClassEst = predictedVals.copy()
bestStump['dim'] = i
bestStump['thresh'] = threshVal
bestStump['ineq'] = inequal

return bestStump, minError, bestClassEst


'''
参数:数据集,类别标签,迭代次数(整个算法中唯一需要用户指定的参数)
DS,即 decision stump,单层决策树
'''
def adaBoostTrainDS(dataArr, classLabels, numIt=40):
weakClassArr = []
m = shape(dataArr)[0]
D = mat(ones((m, 1)) / m) # 权重向量存储了每一个数据点的权重,初始值相等;且D为概率分布向量,所有值的和为1
aggClassEst = mat(zeros((m, 1))) # 记录每个数据点的类别估计累计值
for i in range(numIt): # 达到最大循环次数,或者训练错误率为0,则退出
bestStump, error, classEst = buildStump(dataArr, classLabels, D)
# print("D:", D.T)
# 对应分类器的权重
alpha = float(0.5 * log((1.0 - error) / max(error, 1e-16))) # 此处分母这么设计是为了避免除0溢出
bestStump['alpha'] = alpha # 该字典包含了分类所需要的所有信息
weakClassArr.append(bestStump)
# print("classEst:", classEst.T)
expon = multiply((-1) * alpha * 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)))
errorRate = aggErrors.sum() / m
print("total error:", errorRate)
if errorRate == 0.0:
break

return weakClassArr, aggClassEst


'''
参数:多个待分类样例 dataToClass,多个弱分类器 classifierArr
'''


def adaclassify(dataToClass, classifierArr):
dataMat = mat(dataToClass)
m = shape(dataMat)[0]
aggClassEst = mat(zeros((m, 1)))
for i in range(len(classifierArr)):
classEst = stumpClassifiy(dataMat, classifierArr[i]['dim'],
classifierArr[i]['thresh'], classifierArr[i]['ineq'])
aggClassEst += classifierArr[i]['alpha'] * classEst
# print(aggClassEst)

return sign(aggClassEst)


if __name__ == "__main__":
dataMat, classLabels = loadSimpleData()
# plot(dataMat, classLabels)

# D = mat(ones((5, 1)) / 5) # 权重向量存储了每一个数据点的权重,初始值相等;且D为概率分布向量,所有值的和为1
# bestStump, minError, bestClassEst = buildStump(dataMat, classLabels, D)
# # {'dim': 0, 'thresh': 1.3, 'ineq': 'lt'}、[[0.2]]、[[-1.] [1.] [-1.] [-1.] [1.]]
# print(bestStump, minError, bestClassEst)

classifierArr = adaBoostTrainDS(dataMat, classLabels, 30)[0]
# print(classifierArr)
sign = adaclassify([[1.3, 1.2], [1.0, 1.1]], classifierArr)
print(sign)

数据可视化:                                                                                  

机器学习实战(第七章-利用AdaBoost元算法提高分类性能-所有代码与详细注解-python3.7)_机器学习实战

运行结果:

机器学习实战(第七章-利用AdaBoost元算法提高分类性能-所有代码与详细注解-python3.7)_第七章_02

2、adaBoost02_horseColic.py

'''
以下是利用多个单层决策树和adaboost算法,在难数据集(马疝病数据集)上的运用实例
1、收集数据
2、准备数据,标签为-1,1而非0,1
处理数据集中的缺失值,可选方法有:(此数据集中的属性部分,0为缺失值)
(1) 使用可用特征的均值填补缺失值
(2) 使用特殊值(如-1)填补缺失值
(3) 忽略有缺失值的样本
(4) 使用相似样本的均值填补缺失值
(5) 使用另外的机器学习算法预测缺失值
3、分析数据
4、训练数据:在数据集上训练处一系列的分类器
5、测试算法:并且与logistic回归的分类结果进行对等的比较
6、使用算法
与logistic回归算法作比较:
logistic回归的平均错误率是0.35,而本adaboost一般的运行结果都不超过0.35
'''

from adaBoost01_base import *

def loadDataSet(fileName):
numFeature = 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(numFeature - 1):
# 原算法随着若分类器数量的增加,会出现过拟合现象,此处对原数据中的缺失值(即0值)做适当调整,看是否能够提高性能
# 缺失值补充法1:把缺失值0用任一负值来替换,发现错误率变化比较随机
# the error rate is 0.23880597014925373\0.208955223880597
# if float(curLine[i]) - 0.0 < 1e-6:
# curLine[i] = "-10.0"
lineArr.append(float(curLine[i]))
dataMat.append(lineArr)
# 书中直接使用处理后的数据,而本文采用原数据,所以此处进行标签转换
if float(curLine[-1]) - 0.0 < 1e-6:
curLine[-1] = "-1.000000"
labelMat.append(float(curLine[-1]))
return dataMat, labelMat

if __name__ == "__main__":

# 训练
trainDataArr, trainLabelArr = loadDataSet("data/horseColicTraining.txt")
classifierArr = adaBoostTrainDS(trainDataArr, trainLabelArr, 10)[0]

# 测试
testDataArr, testLabelArr = loadDataSet("data/horseColicTest.txt")
prediction = adaclassify(testDataArr, classifierArr)
# print(prediction)

errArr = mat(ones((len(testLabelArr), 1)))
errCount = errArr[prediction != mat(testLabelArr).T].sum()
print(prediction.T.tolist()[0])
print(testLabelArr)
# 因为没有随机性,所以结果是固定的:
# the number of test data is 67 , among which the error number is 16.0
# the error rate is 0.23880597014925373
print("the number of test data is", len(testLabelArr), ", among which the error number is", errCount)
print("the error rate is", (float(errCount) / len(testLabelArr)))

运行结果:

机器学习实战(第七章-利用AdaBoost元算法提高分类性能-所有代码与详细注解-python3.7)_第七章_03

3、adaBoost02_horseColic_fill_missing_value.py

'''
以下是缺失值的第2种尝试(用样本均值填充),结果是失败的,错误率很高,仅作为提供思路,亦可不看

处理数据集中的缺失值,可选方法有:(此数据集中的属性部分,0为缺失值)
(1) 使用可用特征的均值填补缺失值
(2) 使用特殊值(如-1)填补缺失值
(3) 忽略有缺失值的样本
(4) 使用相似样本的均值填补缺失值
(5) 使用另外的机器学习算法预测缺失值
'''
from adaBoost02_horseColic import *

# 缺失值补充法2:采用标签相同的特征平均值
# 该方法貌似错误更高了,可能原因是,我们可以通过该方法把训练数据的缺失值补齐,却不能补齐测试数据的缺失值
# the error rate is 0.29850746268656714
def loadDataSet02Train(fileName):
numFeature = len(open(fileName).readlines()[0].split('\t'))
dataMat = []
labelMat = []
fr = open(fileName)
for line in fr.readlines():
lineArr = []
curLine = line.strip().split('\t')
for i in range(numFeature - 1):
lineArr.append(float(curLine[i]))
dataMat.append(lineArr)
# 书中直接使用处理后的数据,而本文采用原数据,所以此处进行标签转换
if float(curLine[-1]) - 0.0 < 1e-6:
curLine[-1] = "-1.000000"
labelMat.append(float(curLine[-1]))

# 计算不同类别不同特征平均值
m, n = shape(dataMat)
sum = zeros((2, m))
count = zeros((2, m))
for i in range(m):
for j in range(n):
if dataMat[i][j] - 0.0 >= 1e-6:
row = int((int(labelMat[i]) + 1) / 2)
sum[row][j] += dataMat[i][j]
count[row][j] += 1
average = sum / count

# 将缺失值(即0值)一一替换掉
for i in range(m):
for j in range(n):
if dataMat[i][j] - 0.0 < 1e-6:
row = int((int(labelMat[i]) + 1) / 2)
dataMat[i][j] = average[row][j]

return dataMat, labelMat, average

# 缺失值补充法2:采用标签相同的特征平均值
# 结合loadDataSet02Train方法,用所有数据的均值补齐测试数据的缺失值,试试看,结果错误率很高,仅做思路吧
# the error rate is 0.47761194029850745
def loadDataSet03Test(fileName, average):
numFeature = len(open(fileName).readlines()[0].split('\t'))
dataMat = []
labelMat = []
fr = open(fileName)
for line in fr.readlines():
lineArr = []
curLine = line.strip().split('\t')
for i in range(numFeature - 1):
# the error rate is 0.23880597014925373\0.208955223880597
if float(curLine[i]) - 0.0 < 1e-6:
curLine[i] = (average[0][i] + average[1][i]) / 2.0
lineArr.append(float(curLine[i]))
dataMat.append(lineArr)
if float(curLine[-1]) - 0.0 < 1e-6:
curLine[-1] = "-1.000000"
labelMat.append(float(curLine[-1]))
return dataMat, labelMat

if __name__ == "__main__":

# 训练
trainDataArr, trainLabelArr, average = loadDataSet02Train("data/horseColicTraining.txt")
classifierArr = adaBoostTrainDS(trainDataArr, trainLabelArr, 10)[0]

# 测试
testDataArr, testLabelArr = loadDataSet03Test("data/horseColicTest.txt", average)
prediction = adaclassify(testDataArr, classifierArr)

errArr = mat(ones((len(testLabelArr), 1)))
errCount = errArr[prediction != mat(testLabelArr).T].sum()
print(prediction.T.tolist()[0])
print(testLabelArr)
print("the number of test data is", len(testLabelArr), ", among which the error number is", errCount)
print("the error rate is", (float(errCount) / len(testLabelArr)))

4、adaBoost03_ROC_AUC.py

from adaBoost02_horseColic import *

'''
参数:分类器的预测强度(即每个特征对应的类别累计估计值),数据标签
'''
def plotROC(predictStrengths, classLabels):
import matplotlib.pyplot as plt
cur = (1.0, 1.0) # 绘制光标的位置
ySum = 0.0 # 用于计算AUC的值
numPosClass = sum(array(classLabels) == 1.0) # 正例的数目
yStep = 1 / float(numPosClass) # 纵坐标表示实际正例中被正确识别的概率
xStep = 1 / float(len(classLabels) - numPosClass) # 横坐标表示实际反例中被错误识别的概率
sortedIndicies = predictStrengths.argsort() # 获取排序索引:由小到大
fig = plt.figure()
fig.clf()
ax = plt.subplot(111)
for index in sortedIndicies.tolist()[0]:
if classLabels[index] == 1.0:
delX = 0 # delX是横坐标变化值,delY是纵坐标变化值
delY = yStep
else:
delX = xStep
delY = 0
ySum += cur[1] # 把每一小段的y值相加,最后乘以xStep就是面积AUC
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.savefig('ROC.png')
plt.show()
print("the Area Under the Curve is: ", ySum * xStep)

if __name__ == "__main__":

dataArr, labelArr= loadDataSet("data/horseColicTraining.txt")
classifierArr, aggClassEst = adaBoostTrainDS(dataArr, labelArr, 10)
# the Area Under the Curve is: 0.8582969635063604
plotROC(aggClassEst.T, labelArr)

ROC曲线:

机器学习实战(第七章-利用AdaBoost元算法提高分类性能-所有代码与详细注解-python3.7)_第七章_04