文章目录
前言
元算法是对其他算法进行组合的一种方式,本章我们会学习到不同分类器的集成方法,然后主要关注boosting方法及其代表分类器AdaBoost,然后我们会建立一个单层决策树的分类器,还会讨论非均衡类问题。
7.1 基于数据集多重抽样的分类器
回顾一下我们之前学习过的物种不同的分类方法,k-近邻、决策树、朴素贝叶斯、Logistic回归、支持向量机。将不同的分类器组合起来,这种组合的结果称为集成方法或元方法。使用集成方法时会有多种形式:可以是不同算法的集成,也可以是同一算法在不同设置下的集成,还可以是数据集不同部分分配给不同分类器之后的集成。这里我们主要学习Adaboost算法。
7.1.1 bagging:基于数据随机重抽样的分类器构建方法
自举汇聚法(bootstrap aggregating),也称为bagging方法,是在原始数据集选择S次后得到S个新数据集的一种技术。新数据集和原始数据集的大小相等。每个数据集都是通过在原始数据集中随机选择一个样本来进行替换而得到的。这里的替换就意味着可以多次地选择同一样本。这一性质就允许新数据集中可以有重复的值,而原始数据集的某些值在新集合中则不再出现。
在S个数据集建好之后,将某个学习算法分别作用于每个数据集就得到了S个分类器。当我们对新数据进行分类时,就可以应用这S个分类器进行分类。与此同时,选择分类器投票结果中最多的类别作为最后的分类结果。
还有一些更先进的bagging算法,比如随机森林。
7.1.2 boosting
boosting是一种与bagging很类似的技术。
1.相似:不论是在boosting还是bagging当中,所使用的多个分类器的类型都是一致的。
2.不同:
(1)在bagging中,不同的分类器是通过串行训练而获得的,每个新分类器都根据已训练出的分类器的性能来进行训练。而boosting是通过集中关注被已有分类器错分的那些数据来获得新的分类器。
(2)由于boosting分类的结果是居于所有分类器的加权求和结果的,因此boosting与bagging不太一样。bagging中的分类器权重是相等的,而boosting中的分类器权重并不相等,每个权重代表的是其对应分类器在上一轮迭代中的成功度。
这里我们关注boosting的最流行的一个版本Adaboost。
1.优点:泛化错误率低,易编码,可以应用在大部分分类器上,无参数调 。
2.缺点:对离散点敏 。
3.适用数据类型:数值型和标型数据。
7.2 训练算法:基于错误提升分类器的性能
AdaBOOST是adaptive boosting(自适应boosting)的缩写,其运行过程如下:训练数据中的每
个样本,并赋予其一个权重,这些权重构成了向量D。一开始,这些权重都初始化成相等值。首
先在训练数据上训练出一个弱分类器并计算该分类器的错误率,然后在同一数据集上再次训练弱
分类器。在分类器的第二次训练当中,将会重新调整每个样本的权重,其中第一次分对的样本的
权重将会降低,而第一次分错的样本的权重将会提高。为了从所有弱分类器中得到最终的分类结
果,AdaBoost为每个分类器都分配了一个权重值alpha,这些alpha值是基于每个弱分类器的错误
率进行计算的。其中,错误率£的定义为:
ε
=
未正确分类的样本数目
所有样本数目
\varepsilon=\frac{\text{未正确分类的样本数目}}{\text{所有样本数目}}
ε=所有样本数目未正确分类的样本数目
而alpha的计算公式如下:
α
=
1
2
ln
(
1
−
ε
ε
)
\alpha=\frac12\ln\left(\frac{1-\varepsilon}\varepsilon\right)
α=21ln(ε1−ε)
计算出alpha之后,可以对权重向量乃进行更新,以使得那些正确分类的样本的权重降低而错分样本的权重升高。D的计算方法如下。
如果某个样本被正确分类,那么该样本的权重更改为:
D
i
(
t
+
1
)
=
D
i
(
t
)
e
−
α
S
u
m
(
D
)
D_i^{(t+1)}=\frac{D_i^{(t)}\mathrm{e}^{-\alpha}}{\mathrm{Sum}(D)}
Di(t+1)=Sum(D)Di(t)e−α
而如果某个样本被错误分类,那么该样本的权重更改为:
D
i
(
t
+
1
)
=
D
i
(
t
)
e
α
S
u
m
(
D
)
D_i^{(t+1)}=\frac{D_i^{(t)}\mathrm{e}^{\alpha}}{\mathrm{Sum}(D)}
Di(t+1)=Sum(D)Di(t)eα
在计算出D之后,AdaBoost对又开始进入下一轮迭代。AdaBoost算法会不断地重复训练和调整
权重的过程,直到训练错误率为0或者弱分类器的数目达到用户的指定值为止。
7.3 基于单层决策树构建分类器
单层决策树仅基于单个特征来作决策,由于这棵树只有一次分裂的过程,因此它实际上就是一个树桩。
首先我们创建单层决策树的数据集,代码如下:
#创建单层决策树的数据集
def loadSimpData():
datMat = np.matrix([[ 1. , 2.1],
[ 1.5, 1.6],
[ 1.3, 1. ],
[ 1. , 1. ],
[ 2. , 1. ]])
classLabels = [1.0, 1.0, -1.0, -1.0, 1.0]
return datMat,classLabels
有了这些数据,接下来就可以通过构建多个函数来建立单层决策树,单层决策树生成函数包含两个函数,第一个函数将用于测试是否有某个值小于或者大于我们正在测试的阈值。第二个函数则更加
复杂一些,它会在一个加权数据集中循环,并找到具有最低错误率的单层决策树。
代码如下:
# 单层决策树分类函数
def stumpClassify(dataMatrix,dimen,threshVal,threshIneq):
#初始化retArray为1
retArray = np.ones((np.shape(dataMatrix)[0],1))
if threshIneq == 'lt':
#如果小于阈值,则赋值为-1
# 这里比较的是矩阵
retArray[dataMatrix[:,dimen] <= threshVal] = -1.0
else:
#如果大于阈值,则赋值为-1
retArray[dataMatrix[:,dimen] > threshVal] = -1.0
return retArray
#找到数据集上最佳的单层决策树
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)))
#最小误差初始化为正无穷大 ‘inf’代表的就是无穷
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):
#大于和小于的情况,均遍历。lt:less than,gt:greater than
for inequal in ['lt', 'gt']:
#计算阈值 特征中最小值加
threshVal = (rangeMin + float(j) * stepSize)
#计算分类结果
predictedVals = stumpClassify(dataMatrix, i, threshVal, inequal)
#初始化误差矩阵
errArr = np.mat(np.ones((m,1)))
#分类正确的,赋值为0 这里也是直接操作矩阵
errArr[predictedVals == labelMat] = 0
#计算误差 权重D为矩阵5*1,转置为1*5
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
7.4 完整的AdaBoost算法实现
基于单层决策树的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,使error不等于0,因为分母不能为0
alpha = float(0.5 * np.log((1.0 - error) / max(error, 1e-16)))
#存储弱学习算法权重
bestStump['alpha'] = alpha
#存储单层决策树
weakClassArr.append(bestStump)
print("classEst: ", classEst.T)
#计算e的指数项
expon = np.multiply(-1 * alpha * np.mat(classLabels).T, classEst)
# 矩阵计算
D = np.multiply(D, np.exp(expon))
#根据样本权重公式,更新样本权重
D = D / D.sum()
#计算AdaBoost误差,当误差为0的时候,退出循环
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("total error: ", errorRate)
#误差为0,退出循环
if errorRate == 0.0: break
return weakClassArr, aggClassEst
向量D包含了每个数据点的权重。
7.5 测试算法:基于AdaBoost的分类
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'])
#每个分类器的alpha值乘以分类结果
aggClassEst += classifierArr[i]['alpha'] * classEst
print(aggClassEst)
# 返回结果符号
return np.sign(aggClassEst)
上述的adaClassify()函数就是利用训练出的多个弱分类器进行分类的函数。该函数的输人是由一个或者多个待分类样例datToClass以及多个弱分类器组成的数组classifierArr。函数adaClassify()首先将datToClass转换成了一个NumPy阵 ,并且得到datToClass中的待分类样例的个数m, 然后构建一个0列向量aggClassEst,这个列向量与adaBoostTrainDS()中含义一样。
接下来,遍历classfierArr中所有弱分类器,并基于stumpClassify()对每个分类器得到一个类别的估计值。
7.6 非均衡分类问题
非均衡问题是指在分类器训练时,正例数目和反例数目不相等(相差很大),或者错分正反例导致的代价不同时存在的问题。而大多数情况下,不同类别的分类代价并不相等,而诸如信用卡欺诈等场景中,正反例的样本的数目相差巨大,这就需要一些新的分类器性能度量方法和技术,来处理上述均衡问题。
7.6.1 其他分类性能度量指标
1.错误率:在所有测试样例中错分的样例比例。
2.混淆矩阵:可以帮助人们很好的了解分类中的错误,如下图:
3.下面是一个简单二类问题的混淆矩阵
真正例:将一个正例正确判为正例;
真反例:将一个反例正确判为反例;
伪正例:将一个正例错误判为反例;
伪反例:将一个反例错误判为正例;
正确率:TP/(TP+FP),给出的时在预测为正例的样本中的真正的正例比例;
召回率:TP/(TP+FN),给出的时预测为正例的真正正例占所有真实正例的比例;
4.ROC曲线:ROC代表接受者操作特征,如图:
在ROC曲线中,给出了两条线,一条虚线一条实线。图中的横轴是伪正例的比例(假
阳率=FP/(FP+TN)),而纵轴是真正例的比例(真阳率=TP?(TP+FN))。ROC曲线给出的是当阈值变化时假阳率和真阳率的变化情况。虚线给出的是随机猜测的结果曲线。。
在理想情况下,最佳的分类器应该尽可能位于左上角,这意味着分类器在假阳率很低的同时获得了很高的真阳率。
对不同的ROC曲线进行比较的一个指标是曲线下面积(Area Unser the Curve,AUV),AUV给出的是分类器的平均性能值。
ROC曲线绘制及AUC计算函数代码如下:
# 绘制ROC
from matplotlib.font_manager import FontProperties
def plotROC(predStrengths, classLabels):
font = FontProperties(fname=r"c:\windows\fonts\simsun.ttc", size=14)
#绘制光标的位置
cur = (1.0, 1.0)
#用于计算AUC
ySum = 0.0
#统计正类的数量
numPosClas = np.sum(np.array(classLabels) == 1.0)
#y轴步长
yStep = 1 / float(numPosClas)
#x轴步长
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]
#绘制ROC
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.title('AdaBoost马疝病检测系统的ROC曲线', fontproperties = font)
plt.xlabel('假阳率', fontproperties = font)
plt.ylabel('真阳率', fontproperties = font)
ax.axis([0, 1, 0, 1])
#计算AUC
print('AUC面积为:', ySum * xStep)
plt.show()
7.7.2 基于代价函数的分类器决策控制
在分类算法中,我们有很多方法可以用来引人代价信息。在AdaBoost中,可以基于代价函数来调整错误权重向量D 。在朴素贝叶斯中,可以选择具有最小期望代价而不是最大概率的类别作为最后的结果。在SVM 中,可以在代价函数中对于不同的类别选择不同的参数0。上述做法就会给较小类更多的权重,即在训练时,小类当中只允许更少的错误。
7.7.3 处理非均衡问题的数据抽样方法
可以通过**欠抽样(删除样例)和过抽样(复制样例)**来实现。
1.欠抽样:选择那些里决策边较远的样例进行删除;使用反例类别的欠抽样和正例类别的过抽样相混合的方法。
2.过抽样:可以复制已有样例或者加入与已有样例相似的点,但这种方法可能导致过拟合的问题(测试错误率在达到一个最小值后又开始上升了)。
总结
集成方法通过组合多个分类器的分类结果,获得了比简单的单分类器更好的的分类结果。
多个分类器组合可能会进一步凸显出单分类器的不 足,比如过拟合问题。如果分类器之间差别显著,那么多个分类器组合就可能会缓解这一问题。分类器之间的差别可以是算法本身或者应用于算法上的数据的不同。
本章介绍了bagging和boosting,在bagging中,是通过随机抽样的替换方式,得到了与原始数据集规模一样的数据集。而boosting在bagging的思路上更进了一步,它在数据集上顺序应用了多个不同的分类器。另一个成功的集成方法就是随机森林。
本章主要介绍了AdaBoost算法,以单层决策树作为弱学习器构建了AdaBoost分类器。
还介绍了非均衡类问题,以及不同分类器的评价方法——ROC曲线,正确率和召回率,以及通过欠取样和过取样来调节数据集中的正例和反例的数目。