当我们做决定时,可能会综合考虑很多个人的意见,一个人的意见可能并不可靠。
元算法(集成方法)是对其他算法进行组合的一种方式,AdaBoost便是一种元算法。
本文主要讲解内容有:
第一部分:基于数据集多重抽样的分类器
元算法(集成方法)有哪些类型呢?
1、不同算法的集成
2、同一算法在不同设置下的集成
3、数据集不同部分分配给不同分类器之后的集成
我们首先了解一下简单的一种集成方法:bagging。
bagging是怎么实现集成的呢,它是通过利用原始样本产生S个新的样本(S个新的样本的大小和原始样本的大小一样,采用随机抽样一部分替换原始数据集的一部分而形成),然后将这S个新样本作用于某个算法,然后就会产生参数不同的S个分类器,我们预测一个数据时就会产生S个结果,我们选择其中出现次数最多的结果即可。
我们知道Adaboost是boosting一种技术,而不是bagging的技术,那么boosting和bagging的区别又是啥呢?
我们知道bagging只要将S个新的样本得出来,那么就可以同时训练出来S个分类器,是并行实现了S个分类器。
而boosting的分类器不是这样得到的,而是通过串行的方式实现的,新的训练器则是在原有的训练器的基础上得到的。
通俗来讲,boosting的新的分类器的产生是由原有分类器错分的数据决定的,它会给每个分类器一个不一样的权重,而bagging的每个分类器的权重都是一样的。
Adaboost
优点:泛化错误率低、易编码、可以应用在大部分分类器上、无参数调整
缺点:对离群点敏感
使用数据类型:数值型和标称型数据
一般流程
(1)、收集数据:以采用任意方法。
(2)、准备数据:依赖于所使用的弱分类器的类型,本文使用的是单层决策树,这种分类器可以处理任何数据类型。当然也可以使用任意分类器作为弱分类器,一般我们要做集成,所以弱分类器简单点好。
(3)、分析数据:可以采用任一方法。
(4)、训练算法:AdaBoosting的大部分时间都用在训练上,分类器将多次在同一数据集上训练弱分类器。
(5)、测试算法:计算分类的错误率。
(6)、使用算法:同SVM一样,AdaBoost预测两个类别中的一个。如果想应用到分多个类别的场合,那么就要像多类SVM中的做法一样对AdaBoost进行修改。
来喽,来喽,它们来喽,重点来喽!
第二部分:训练算法:基于错误提升分类器的性能
Adaboost运行的过程如下:
我们知道训练数据有很多个样本,我们对每个样本分配一个权重值,开始的时候这些权重都是相等的。
然后我们利用某个算法执行这些样本,训练出来一个弱分类器,并且计算弱分类器的错误率:
然后我们针对样本继续训练出一个新的弱分类器,不过这时候样本的权重不一样了,具体的改变就是分类正确的样本它的权重降低了,分类错误的样本的权重升高了,改变如下:
我们看到上面的式子中有一个α的值,这是啥?
其实这是每个弱分类器对应的权重值,它怎么求得呢:
哈哈哈,到现在我们发现,我们每次训练出来一个弱分类器,然后计算出它的错误率,然后就能把它对应的权重值α求出来,然后我们就能计算出下一个弱分类器使用的样本对应的权重,然后我们就能训练出第二个弱分类器,然后计算它的错误率,然后计算它的权值........
过程其实就是:给样本分配相等权重,训练弱分类器,计算错误率,计算弱分类器权重,计算下一个弱分类器使用样本权重,训练,计算错误率......一直进行下去。
最后呢,最后我们得出S个弱分类器后,训练一个样本时,我们把S个结果乘上每个分类器对应的权重,最后那个结果对应的值更大我们就认为是那个结果了。
第三部分:基于单层决策树构建弱分类器
创建数据集并进行显示,显示的代码在下面:
# coding=utf-8
'''
adaboost.py:创建数据集
created by 飒公瑾 on 16 August 2019
'''
import numpy as np
from Adaboost import showPoints
from Adaboost import boost as btdef 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# 获得数据集
dataMat,classLabels = loadSimpData()# 显示数据点
#showPoints.showPoints(dataMat,classLabels)# 创建单棵决策树
#D = np.mat(np.ones((5,1)) / 5)
#bt.buildStump(dataMat,classLabels,D)classifierArr = bt.adaBoostTrainDS(dataMat,classLabels,9)
bt.adaClassify([0,0], classifierArr)
显示的代码:
# coding=utf-8
'''
showPoints.py
created by 飒公瑾 on 16 August 2019
'''
import numpy as np
import matplotlib.pyplot as pltdef showPoints(dataMat,classLabels):num0 = 0num1 = 0for i in range(len(classLabels)):if classLabels[i] == 1:num1 = num1 + 1else:num0 = num0 + 1X0points = np.zeros(num0)Y0points = np.zeros(num0)X1points = np.zeros(num1)Y1points = np.zeros(num1)step0 = 0step1 = 0for i in range(len(classLabels)):if classLabels[i] == 1:X1points[step1] = dataMat[i, 0]Y1points[step1] = dataMat[i, 1]step1 = step1 + 1else:X0points[step0] = dataMat[i, 0]Y0points[step0] = dataMat[i, 1]step0 = step0 + 1plt.xlabel("X轴", fontproperties='SimHei', fontsize=14)plt.ylabel("Y轴", fontproperties='SimHei', fontsize=14)plt.title('显示所有点',fontproperties='SimHei', fontsize=14, fontdict={'size': '18', 'color': 'r'})# 显示文字plt.text(1.1, 1.8, r'无法从坐标轴找一点将两种颜色不同的点分开', fontdict={'size': '18', 'color': 'g'},fontproperties='SimHei', fontsize=14) # 文字坐标# 画点plt.scatter(X0points, Y0points, c='red', alpha=1, marker='+', label='pickup')plt.scatter(X1points, Y1points, c='blue', alpha=1, marker='+', label='dropout')plt.show()
得出的结果是:
建立单层决策树伪代码:
将最小错误率minEroor设为无穷大
对数据集中的每一个特征(第一层循环):对每个步长(第二层循环):对每个不等号(第三层循环):建立一颗单层决策树并利用加权数据集对它进行测试如果错误率低于minError,则将当前单层决策树设为最佳单层决策树
返回最佳单层决策树
Adaboost的伪代码:
对每次迭代:利用buildStump()函数找到最佳的单层决策树将最佳单层决策树加入到单层决策树数组计算alpha计算新的权重向量D更新累计类别估计值如果错误率等于0.0,则退出循环
# coding=utf-8
import numpy as np
'''
boost.py:构建单层决策树+Adaboost训练+Adaboost预测
created by 飒公瑾 on 16 August 2019
'''
def stumpClassify(dataMatrix, dimen, threshVal, threshIneq):&#39;&#39;&#39;单层决策树分类函数:param dataMatrix: - 数据矩阵:param dimen: - 第dimen列&#xff0c;也就是第几个特征:param threshVal: - 阈值:param threshIneq: - 标志:return: - 分类结果&#39;&#39;&#39;retArray &#61; np.ones((np.shape(dataMatrix)[0], 1)) #初始化retArray为1if threshIneq &#61;&#61; &#39;lt&#39;:retArray[dataMatrix[:,dimen] <&#61; threshVal] &#61; -1.0 # 如果小于阈值&#xff0c;则赋值为-1else:retArray[dataMatrix[:,dimen] > threshVal] &#61; -1.0 # 如果大于阈值&#xff0c;赋值为-1return retArraydef buildStump(dataArr, classLabels, D):&#39;&#39;&#39;找到数据集上最佳的单层决策树--单层决策树是指只考虑其中的一个特征&#xff0c;在该特征上进行分类&#xff0c;寻找分类错误率最低的阈值即可。:param dataArr: - 数据矩阵:param classLabels: - 数据标签:param D: - 样本权重:return:bestStump: - 最佳单层决策树信息minError: - 最小误差bestClasEst: - 最佳的分类结果&#39;&#39;&#39;dataMatrix &#61; np.mat(dataArr)labelMat &#61; np.mat(classLabels).Tm, n &#61; np.shape(dataMatrix)numSteps &#61; 10.0 # 迭代十步bestStump &#61; {}bestClasEst &#61; np.mat(np.zeros((m,1)))minError &#61; np.inf # 最小误差初始化为正无穷大for i in range(n): # 首先针对两个特征遍历rangeMin &#61; dataMatrix[:,i].min()rangeMax &#61; dataMatrix[:,i].max()stepSize &#61; (rangeMax - rangeMin) / numSteps # 每次迭代的长度for j in range(-1, int(numSteps)&#43;1): # 然后针对步长遍历# lt是在该阈值下&#xff0c;如果<阈值&#xff0c;则分类为-1# gt是在该阈值下&#xff0c;如果》阈值&#xff0c;则分类为-1for inequal in [&#39;lt&#39;, &#39;gt&#39;]:threshVal &#61; (rangeMin &#43; float(j) * stepSize) # 计算阈值predictedVals &#61; stumpClassify(dataMatrix,i,threshVal,inequal) # 计算分类结果errArr &#61; np.mat(np.ones((m,1))) # 初始化误差矩阵errArr[predictedVals &#61;&#61; labelMat] &#61; 0 #分类正确的&#xff0c;误差为0# 基于权重向量D而不是其他错误计算指标来评价分类器的&#xff0c;不同的分类器计算方法不一样weightedError &#61; D.T * errArr # 这里不是采用常规方法来评价这个分类器的分类准确率&#xff0c;而是乘上了权重if weightedError
实例数据&#xff1a;在一个难数据集上应用AdaBoost
收集数据&#xff1a;提供的文本文件。
准备数据&#xff1a;确保类别标签是&#43;1和-1而非1和0。
分析数据&#xff1a;手工检查数据。
训练算法&#xff1a;在数据上&#xff0c;利用adaBoostTrainDS()函数训练出一系列的分类器。
测试算法&#xff1a;我们拥有两个数据集。在不采用随机抽样的方法下&#xff0c;我们就会对AdaBoost和Logistic回归的结果进行完全对等的比较。
使用算法&#xff1a;观察该例子上的错误率。不过&#xff0c;这可以构建一个web网站&#xff0c;让驯马师输入马的症状然后预测马是否会死去。
从文件中加载数据集&#xff0c;转变成我们想要的数据格式&#xff0c;先看下面自适应数据加载函数代码&#xff1a;
def loadDataSet(fileName):"""Function&#xff1a; 自适应数据加载函数Input&#xff1a; fileName&#xff1a;文件名称Output&#xff1a; dataMat&#xff1a;数据集labelMat&#xff1a;类别标签""" #自动获取特征个数&#xff0c;这是和之前不一样的地方numFeat &#61; len(open(fileName).readline().split(&#39;\t&#39;))#初始化数据集和标签列表dataMat &#61; []; labelMat &#61; []#打开文件fr &#61; open(fileName)#遍历每一行for line in fr.readlines():#初始化列表&#xff0c;用来存储每一行的数据lineArr &#61; []#切分文本curLine &#61; line.strip().split(&#39;\t&#39;)#遍历每一个特征&#xff0c;某人最后一列为标签for i in range(numFeat-1):#将切分的文本全部加入行列表中lineArr.append(float(curLine[i]))#将每个行列表加入到数据集中dataMat.append(lineArr)#将每个标签加入标签列表中labelMat.append(float(curLine[-1]))#返回数据集和标签列表return dataMat, labelMat
最终的测试代码函数&#xff1a;
#训练和测试分类器
def classify():#利用训练集训练分类器datArr,labelArr&#61;loadDataSet(&#39;horseColicTraining2.txt&#39;)#得到训练好的分类器classifierArray&#61;adaBoostTrainDS(datArr,labelArr,10)#利用测试集测试分类器的分类效果testArr,testLabelArr&#61;loadDataSet(&#39;horseClicTest2.txt&#39;)prediction&#61;adaClassify(testArr,classifierArray)#输出错误率num&#61;shape(mat(labelArr))[1]errArr&#61;mat(ones((num,1)))error&#61;errArr[prediction!&#61;mat(testLabelArr).T].sum()print("the errorRate is: %.2f",errorRate&#61;float(error)/float((num)))
基于上面的adaBoost分类器训练和测试代码&#xff0c;得到了下面的不同弱分类器数目情况下的AdaBoost测试和分类错误率。
总结摘自&#xff1a;机器学习实战之AdaBoost算法
观察商标的数据我们发现&#xff1a;
&#xff08;1&#xff09;随着分类器数目的增加&#xff0c;adaBoost分类器的训练错误率不断的减少&#xff0c;而测试错误率则是经历先减少到最小值&#xff0c;再逐渐增大的过程。显然&#xff0c;这就是所说的过拟合。因此&#xff0c;对于这种情况&#xff0c;我们应该采取相应的措施&#xff0c;比如采取交叉验证的方法&#xff0c;在训练分类器时&#xff0c;设定一个验证集合&#xff0c;不断测试验证集的分类错误率&#xff0c;当发现训练集错误率减少的同时&#xff0c;验证集的错误率较之上一次结果上升了&#xff0c;就停止训练。或者其他比较实用的模拟退火方法&#xff0c;基因遗传方法等。
&#xff08;2&#xff09;前面的第四章的logistic回归分类器对该数据集的分类错误率是35%&#xff0c;显然adaBoost分类器取得了更好的分类效果。
&#xff08;3&#xff09;有文献表明&#xff0c;对于表现好的数据集&#xff0c;AdaBoost的测试误差率会随着迭代次数的增加而逐渐稳定在某一个值附近&#xff0c;而不会出现上表中的先减小后上升的情况。显然&#xff0c;这里用到的数据集不能称为"表现好"的数据集&#xff0c;比较该数据集存在30%的数据缺失。在第四章的logistic回归中&#xff0c;我们讲这些确实的数据设置为0&#xff0c;显然这在logistic回归算法中是合适&#xff0c;这样不会对分类结果造成影响。但是&#xff0c;在adaBoost算法中依然这样设置&#xff0c;其合理性还有待证明&#xff0c;所以&#xff0c;有必要可以将这些缺失的数据值由0变成该特征相类似的数据&#xff0c;或者该特征数据的平均值&#xff0c;再来进行adaBoost算法训练&#xff0c;看看得到的结果会不会有所提升&#xff1f;
四&#xff0c;总结
adaBoost是boosting方法中最流行的一种算法。它是以弱分类器作为基础分类器&#xff0c;输入数据之后&#xff0c;通过加权向量进行加权&#xff0c;在每一轮的迭代过程中都会基于弱分类器的加权错误率&#xff0c;更新权重向量&#xff0c;从而进行下一次迭代。并且会在每一轮迭代中计算出该弱分类器的系数&#xff0c;该系数的大小将决定该弱分类器在最终预测分类中的重要程度。显然&#xff0c;这两点的结合是adaBoost算法的优势所在。
优点&#xff1a;泛化错误率低&#xff0c;容易实现&#xff0c;可以应用在大部分分类器上&#xff0c;无参数调整
缺点&#xff1a;对离散数据点敏感