6-集成方法
🚖 集成方法 ensemble method
集成方法: ensemble method /(元算法: meta algorithm):
-
概念: 对不同的分类算法进行组合
-
通俗来说: 当做重要决定时,大家可能都会考虑吸取多个专家而不只是一个人的意见。 机器学习处理问题时又何尝不是如此? 这就是元算法背后的思想。
1. 基于数据集多重抽样的分类器
① bagging:基于数据随机重抽样的分类器构建方法
自举汇聚法 bootstrap aggregating,也成为 bagging 方法,是在从原始数据集中随机选择一个样本来进行替换得到一个新数据集,新数据集和原数据集大小相等。重复该操作 S 次,则得到 S 个新数据集。然后,在 S 个数据集上分别应用某个学习算法,就得到了 S 个分类器,选择 S 个分类器结果中最多的类别作为最终结果。
🔈 注意:这里的替换可以重复替换同一个样本,这也就意味着新的 S 个数据集中可能有些数据集是相同的。
💡 还有一些更先进的 bagging 算法,比如 随机森林 random forest。
本节我们重点论述 boosting 方法,对 bagging 不做过多介绍。
② boosting
集成分类器方法 boosting,boosting 是一种与 bagging 很类似的技术。
对于 boosting 来说,不同的分类器是通过串行训练而获得的,即每个新分类器都根据已训练出的分类器的性能来进行训练 。通过集中关注被已有分类器错分的那些数据来获得新的分类器。
显然,对于 bagging 来说,每个分类器的权重是相等的,而 boosting 中的分类器权重并不相同,每个权重代表的是其对应分类器在上一轮迭代中的成功度。
boosting 方法有很多版本,本节我们重点关注 AdaBoost。
2. AdaBoost 算法详解
AdaBoost 算法的目的就是使用弱分类器和多个实例来构建一个强分类器。
💡 “ 弱 ” 意味着分类器的性能比随机猜测要略好,但是也不会好太多。
🌊 AdaBoost 的一般流程:
-
收集数据: 可以使用任意方法
-
准备数据: 依赖于所使用的弱分类器类型,本章使用的是单层决策树,这种分类器可以处理任何数据类型。
当然也可以使用任意分类器作为弱分类器,我们前面所学的分类方法中任一分类器都可以充当弱分类器。作为弱分类器,简单分类器的效果更好。
-
分析数据: 可以使用任意方法。
-
训练算法: AdaBoost 的大部分时间都用在训练上,分类器将多次在同一数据集上训练弱分类器。
-
测试算法: 计算分类的错误率。
-
使用算法: 通SVM一样,AdaBoost 预测两个类别中的一个。如果想把它应用到多个类别的场景,那么就要像多类 SVM 中的做法一样对 AdaBoost 进行修改。
🛵 AdaBoost 算法特点:
- 优点: 泛化(由具体的、个别的扩大为一般的)错误率低,易编码,可以应用在大部分分类器上,无参数调节。
- 缺点: 对离群点敏感。
- 适用数据类型: 数值型和标称型数据。
🔄 AdaBoost 运行过程:
- 训练数据中的每个样本,并赋予其一个权重,这些权重构成了向量 D。一开始,这些权重都初始成相等值。
- 首先在训练数据上训练出一个弱分类器并计算该分类器的错误率
- 然后重新调整每个样本的权重,第一次分对的样本的权重将会降低,分错的样本的权重将会提高,在同一数据集上再次训练弱分类器。
为了从所有弱分类器中得到最终的分类结果,AdaBoost 为每个分类器都分配了一个权重值 alpha,这些 alpha 值是基于每个弱分类器的错误率 ε 进行计算的。其中 错误率 ε 定义为:
![](https://cs-wiki.oss-cn-shanghai.aliyuncs.com/img/20200716094547.png)
每个分类器的权重值 alpha 的计算公式如下:
![](https://cs-wiki.oss-cn-shanghai.aliyuncs.com/img/20200716094622.png)
⛲ AdaBoost 的算法流程图如下:
![](https://cs-wiki.oss-cn-shanghai.aliyuncs.com/img/20200716094702.png)
计算出 alpha 后,可以对权重向量 D 进行更新,以使得那些正确分类的样本权重降低而错误分类的样本权重升高。权重向量 D 的计算方法如下:
-
如果该样本被正确分类:
-
如果该样本被错误分类:
综合成一个式子:假设对于第 i 次迭代, $G(x_i)$ 是我们的预测结果,$y_i$ 是我们真实的标签:
样本被错误分类即 $G(x_i)$ 和 $y_i$ 异号:$G(x_i) ≠ y_i$,样本被正确分类即 f(x) 和 h(x) 同号:$G(x_i) = y_i$
综合上两个式子可得权重向量 D 的更新公式:
⭐ $D_i^{t+1} = D_i^t e^{-αG(x_i)y_i} / Sum(D)$
计算出权重向量 D 后,AdaBoost 便进入下一次迭代过程
假设一共迭代 M 次,则基本分类器的线性组合为:
![](https://cs-wiki.oss-cn-shanghai.aliyuncs.com/img/20200716121933.png)
$α_mG_m(x)$ 即每次迭代后的权重alpha 乘以预测分类结果(对分类结果进行加权求和)。
⭐ 得到最终分类器:
![](https://cs-wiki.oss-cn-shanghai.aliyuncs.com/img/20200716121958.png)
3. 基于单层决策树构建弱分类器
单层决策树 decision stump 也称决策树桩,是一种简单的决策树,仅基于单个特征来做决策。
首先,构建一个简单的数据集:
import numpy as np
def loadSimpleData():
dataMat = np.mat([[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 dataMat, classLabels
上述数据集示意图如下:
![](https://cs-wiki.oss-cn-shanghai.aliyuncs.com/img/20200716100701.png)
如果想要从某个坐标轴选一个值,即选择一条平行于坐标轴的直线来将所有的圆形点和方形点分开,这显然是不可能的,这就是单层决策树难以处理的一个著名问题。可以通过多棵单层决策树,构建一个能对该数据集完全正确分类的分类器。
有了数据,接下来就可以通过构建多个函数来建立单层决策树
第一个函数用于测试是否有某个数据值不等于我们正在测试的阈值。第二个函数用于循环遍历加权数据集,找到具有最低错误率的单层决策树。
📜 伪代码如下:
![](https://cs-wiki.oss-cn-shanghai.aliyuncs.com/img/20200716101337.png)
✍ 具体 Python 实现:
# 单层决策树生成函数
def stumpClassify(dataMatrix, dimen,threshVal,threshIneq):
"""
Desc:
通过阈值比较对数据进行分类
Args:
dataMatirx:要分类的数据
dimen:在哪个维度(特征列)进行比较
threshVal:阈值
threshIneq:有两种,‘lt’=lower than,‘gt’=greater than
"""
retArray = np.ones((np.shape(dataMatrix)[0],1)) # 初始化所有数据类别为 1
if threshIneq == 'lt':
retArray[dataMatrix[:,dimen] <= threshVal] = -1.0
else:
retArray[dataMatrix[:,dimen] > threshVal] = -1.0
return retArray
def buildStump(dataArr,classLabels,D):
"""
Decs:
遍历stumpClassify 函数的所有输入值,并找到数据集上最佳的单层决策树
"""
dataMatrix = np.mat(dataArr)
labelMat = np.mat(classLabels).T
m,n = np.shape(dataMatrix)
numSteps = 10.0 # 用于在特征的所有可能值(2个特征,共 10 个值)上进行遍历
bestStump = {} # 用于存储给定权重向量 D 时所得到的最佳单层决策树的相关信息
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']: # 每个条件,大于阈值是1还是小于阈值是1
threshVal = (rangeMin + float(j) * stepSize) # 阈值设为最小值+第j个步长
# 分类预测结果:在 第 i 个特征列按照阈值对特征进行比较,置为1或-1
predictedVals = stumpClassify(dataMatrix,i,threshVal,inequal)
errArr = np.mat(np.ones((m,1))) # 错误向量
errArr[predictedVals == labelMat] = 0 # 正确分类的样本置为 0
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
上述过程就是,遍历每个特征,在每个特征上找合理的分界点,每次移动一个步长,对每个步长,根据它的大于阈值是 1.0 类(" gt ") 还是小于阈值是 1.0 类 (" lt ") 得到分类情况,计算错误率,找错误率最小的一种情况返回。bestStump是一个字典,找到一个好的分类器,当然要包含所选择的维度(映射到这个题目就是x轴还是y轴),分类的阈值是多少,大于还是小于阈值的是1.0类,把这些属性放入 bestStump 字典。
初始化权重向量 D:
以给定的 权重向量 D 运行代码:
OK,上述单层决策树的生成函数是决策树的一个简化版本。就是所谓的弱学习器(弱分类算法)。接下来,我们将使用多个弱分类器来构建 AdaBoost 代码。
4. 完整 AdaBoost 算法的实现
📜 整个实现的伪代码如下:
![](https://cs-wiki.oss-cn-shanghai.aliyuncs.com/img/20200716110617.png)
✍ 具体 Python 实现:
对于本代码中不明白的地方可以看第 2 节的数学公式~
def adaBoostTrainDs(dataArr,classLabels,numIt = 40):
"""
Desc:
Args:
dataArr: 数据集
classLabels: 标签集
numIt:迭代次数(注意:如果中途已经达到错误率为0,那么即使迭代次数未达到 numIt,也立即退出迭代过程)
"""
weakClassArr = []
m = np.shape(dataArr)[0] # 数据集行数
D = np.mat(np.ones((m,1)) / m) # 对每行样本赋予一个权重,构成权重向量 D:m x 1
aggClassEst = np.mat(np.zeros((m,1))) # 初始化每个样本的预估值为0
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 # 将alpha值也加入最佳树字典
weakClassArr.append(bestStump) # 将弱分类器加入数组
print("classEst: ",classEst.T) # 第 i 次迭代后的单层决策树的分类结果
# 更新权重向量D
expon = np.multiply(-1*alpha*np.mat(classLabels).T, classEst)
D = np.multiply(D, np.exp(expon))
D = D / D.sum()
# 累加错误率,直到错误率为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, "\n")
if errorRate == 0.0:
break
return weakClassArr
🏃 运行该代码:
算法在第 3 次循环后错误率达到 0,退出循环。则我们得到 3 个弱分类器,观察一下分类器的详细信息:
OK,此时,我们已经拥有了分类所需要的所有信息。接下来,我们就可以进行分类了 👇
5. 测试算法:基于 AdaBoost 的分类
现在,我们需要做的就是将弱分类器的训练过程从程序中抽出来,然后应用到某个具体的实例上。
# AdaBoost 分类函数
def adaClassify(dataToClass, classifierArr):
"""
Desc:
利用训练出的多个弱分类器进行分类的函数
Args:
dataToClass: 一个或多个待分类样例
classifierArr: 多个弱分类器组成的数组
"""
dataMatrix = np.mat(dataToClass)
m = np.shape(dataToClass)[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 += classEst * classifierArr[i]['alpha']
print(aggClassEst)
return np.sign(aggClassEst)
可以看到随着迭代的进行,数据点 [0,0]
的分类结果越来越强。
6. 非均衡分类问题
OK,到此为止,常用的分类算法已经介绍完毕,在我们结束这个主题之前,还必须讨论一个问题,即所有分类器都会遇到的一个通用问题:非均衡分类问题
在前面的所有分类介绍中,我们都假设所有类别的分类代价是一样的。比如说对于癌症检测来说,如果误判该样本患有癌症,那么这个在现实生活中所需要付出的代价是不是太大了?又或者说我们误把一封重要的邮件划分为垃圾邮件丢进垃圾箱,那么这封重要邮件的丢失带来的代价会不会难以承受?
本节,我们将会介绍一种新的分类器性能度量方法,并通过图像技术来对在上述非均衡问题下不同分类器的性能进行可视化处理。
① 其他分类性能度量指标
到现在为止,我们都是基于错误率(即所有测试样本中错分的样本比例)来衡量分类器的成功程度的,实际上,这样的度量错误地掩盖了样本如何被分错的事实。
Ⅰ 正确率和召回率
在机器学习中,有一个普遍适用的混淆矩阵 confusion matrix 工具,可以帮助人们更好地了解 分类中的错误,比如:
当矩阵中的非对角元素均为 0 时,就会得到一个完美的分类器。
接下来,我们看另一个混淆矩阵。在一个二类问题中:
-
如果将一个正例判断为正例,那么就认为产生了一个真正例 True Positive,TP,真阳
如果将一个反例判断为反例,那么就认为产生了一个真反例 True Negative,TN,真阴
-
如果 将一个正例判断为反例,那么就认为产生了一个伪反例 False Negative,FN,假阴
-
如果将一个反例判断为正例,那么就认为产生了一个伪正例 False Positive,FP,假阳
在分类中,当某个类别的重要性高于其他类别时,我们可以利用上述定义来定义出多个比错误率更好的新指标:
- 正确率 Precision = TP / (TP+FP):预测为正例的样本中真正例的比例
- 召回率 Recall = TP / (TP + FN):预测为正例的真正例占所有真实正例的比例。
这两个指标是互斥的,很难保证两者同时成立。比如如果所有样本有判断为正例,那么召回率达到百分之百(因为没有预测反例,FN 为 0),但是此时正确率很低。
Ⅱ ROC 曲线
另一个用于度量分类中的非均衡性的工具是 ROC 曲线(ROC curve)。
对不同的 ROC 曲线进行比较的一个指标是曲线下的面积(Area Unser the Curve, AUC)。AUC 越大分类效果越好。
AUC 给出的是分类器的平均性能值,当然它并不能完全代替对整条曲线的观察。一个完美分类器的 AUC 为1,而随机猜测的 AUC 则为0.5。
② 基于代价函数的分类器决策控制
除了调节分类器的阈值之外,我们还有一些其他可以用于除了非均匀分类代价问题的方法,其中的一种称为代价敏感的学习 cost-sensitive learning。考虑下图的代价矩阵:
第一张表就是我们之前的分类器的代价矩阵(代价不是 0 就是 1),分类代价的计算公式为:
![](https://cs-wiki.oss-cn-shanghai.aliyuncs.com/img/20200716152635.png)
第二张表的分类代价的计算公式为:
![](https://cs-wiki.oss-cn-shanghai.aliyuncs.com/img/20200716152659.png)
在分类算法中,我们有很多方法可以用来引入代价信息:
- 在 AdaBoost中,可以基于代价函数来调整错误权重向量 D
- 在朴素贝叶斯中,可以选择具有最小期望代价而不是最大概率的类别作为最后的结果
- 在 SVM 中,可以在代价函数中对于不同的类别选择不同的参数 C
总的来说,就是对于不同的分类错误给予不同的代价(惩罚)。
③ 数据抽样
另外一种针对非均衡问题调节分类器的方法,就是对分类器的训练数据进行改造。可以通过 欠抽样 undersampling(复制样例)或者 过抽样 oversampling(删除样例)来实现。
📜 一些经验法则:
- 超过1万、十万的样本甚至更多进行欠采样,即删除部分样本;
- 对不足1为甚至更少的样本进行过采样,即添加部分样本的副本;
- 尝试随机采样与非随机采样两种采样方法;
- 对各类别尝试不同的采样比例,不一定是1:1
- 考虑同时使用过采样与欠采样
✅ End
至此为止,分类方法结束 🎉。下节我们将开始学习另一类监督学习方法:回归方法。回归很像分类,但是和分类输出标称型类别值不同的是,回归方法会预测输出一个连续值。
📚 References
-
《Machine Learning in Action》
-
《统计学习方法 - 李航》
-
《机器学习 - 周志华》