参开资料:http://blog.csdn.net/haidao2009/article/details/7514787
更多挖掘算法:https://github.com/linyiqun/DataMiningAlgorithm
介绍
在介绍AdaBoost算法之前,需要了解一个类似的算法,装袋算法(bagging),bagging是一种提高分类准确率的算法,通过给定组合投票的方式,获得最优解。比如你生病了,去n个医院看了n个医生,每个医生给你开了药方,最后的结果中,哪个药方的出现的次数多,那就说明这个药方就越有可能性是最由解,这个很好理解。而bagging算法就是这个思想。
算法原理
而AdaBoost算法的核心思想还是基于bagging算法,但是他又一点点的改进,上面的每个医生的投票结果都是一样的,说明地位平等,如果在这里加上一个权重,大城市的医生权重高点,小县城的医生权重低,这样通过最终计算权重和的方式,会更加的合理,这就是AdaBoost算法。AdaBoost算法是一种迭代算法,只有最终分类误差率小于阈值算法才能停止,针对同一训练集数据训练不同的分类器,我们称弱分类器,最后按照权重和的形式组合起来,构成一个组合分类器,就是一个强分类器了。算法的只要过程:
1、对D训练集数据训练处一个分类器Ci
2、通过分类器Ci对数据进行分类,计算此时误差率
3、把上步骤中的分错的数据的权重提高,分对的权重降低,以此凸显了分错的数据。为什么这么做呢,后面会做出解释。
完整的adaboost算法如下
最后的sign函数是符号函数,如果最后的值为正,则分为+1类,否则即使-1类。
我们举个例子代入上面的过程,这样能够更好的理解。
adaboost的实现过程:
图中,“+”和“-”分别表示两种类别,在这个过程中,我们使用水平或者垂直的直线作为分类器,来进行分类。
第一步:
根据分类的正确率,得到一个新的样本分布D2,一个子分类器h1
其中划圈的样本表示被分错的。在右边的途中,比较大的“+”表示对该样本做了加权。
算法最开始给了一个均匀分布 D 。所以h1 里的每个点的值是0.1。ok,当划分后,有三个点划分错了,根据算法误差表达式得到 误差为分错了的三个点的值之和,所以ɛ1=(0.1+0.1+0.1)=0.3,而ɑ1 根据表达式的可以算出来为0.42. 然后就根据算法 把分错的点权值变大。如此迭代,最终完成adaboost算法。
第二步:
根据分类的正确率,得到一个新的样本分布D3,一个子分类器h2
第三步:
得到一个子分类器h3
整合所有子分类器:
因此可以得到整合的结果,从结果中看,及时简单的分类器,组合起来也能获得很好的分类效果,在例子中所有的。后面的代码实现时,举出的也是这个例子,可以做对比,这里有一点比较重要,就是点的权重经过大小变化之后,需要进行归一化,确保总和为1.0,这个容易遗忘。
算法的代码实现
输入测试数据,与上图的例子相对应(数据格式:x坐标 y坐标 已分类结果):
- 151
- 231
- 31-1
- 45-1
- 561
- 64-1
- 671
- 761
- 87-1
- 82-1
Point.java
- packageDataMining_AdaBoost;
- /**
- *坐标点类
- *
- *@authorlyq
- *
- */
- publicclassPoint{
- //坐标点x坐标
- privateintx;
- //坐标点y坐标
- privateinty;
- //坐标点的分类类别
- privateintclassType;
- //如果此节点被划错,他的误差率,不能用个数除以总数,因为不同坐标点的权重不一定相等
- privatedoubleprobably;
- publicPoint(intx,inty,intclassType){
- this.x=x;
- this.y=y;
- this.classType=classType;
- }
- publicPoint(Stringx,Stringy,StringclassType){
- this.x=Integer.parseInt(x);
- this.y=Integer.parseInt(y);
- this.classType=Integer.parseInt(classType);
- }
- publicintgetX(){
- returnx;
- }
- publicvoidsetX(intx){
- this.x=x;
- }
- publicintgetY(){
- returny;
- }
- publicvoidsetY(inty){
- this.y=y;
- }
- publicintgetClassType(){
- returnclassType;
- }
- publicvoidsetClassType(intclassType){
- this.classType=classType;
- }
- publicdoublegetProbably(){
- returnprobably;
- }
- publicvoidsetProbably(doubleprobably){
- this.probably=probably;
- }
- }
- packageDataMining_AdaBoost;
- importjava.io.BufferedReader;
- importjava.io.File;
- importjava.io.FileReader;
- importjava.io.IOException;
- importjava.text.MessageFormat;
- importjava.util.ArrayList;
- importjava.util.HashMap;
- importjava.util.Map;
- /**
- *AdaBoost提升算法工具类
- *
- *@authorlyq
- *
- */
- publicclassAdaBoostTool{
- //分类的类别,程序默认为正类1和负类-1
- publicstaticfinalintCLASS_POSITIVE=1;
- publicstaticfinalintCLASS_NEGTIVE=-1;
- //事先假设的3个分类器(理论上应该重新对数据集进行训练得到)
- publicstaticfinalStringCLASSIFICATION1="X=2.5";
- publicstaticfinalStringCLASSIFICATION2="X=7.5";
- publicstaticfinalStringCLASSIFICATION3="Y=5.5";
- //分类器组
- publicstaticfinalString[]ClASSIFICATION=newString[]{
- CLASSIFICATION1,CLASSIFICATION2,CLASSIFICATION3};
- //分类权重组
- privatedouble[]CLASSIFICATION_WEIGHT;
- //测试数据文件地址
- privateStringfilePath;
- //误差率阈值
- privatedoubleerrorValue;
- //所有的数据点
- privateArrayList<Point>totalPoint;
- publicAdaBoostTool(StringfilePath,doubleerrorValue){
- this.filePath=filePath;
- this.errorValue=errorValue;
- readDataFile();
- }
- /**
- *从文件中读取数据
- */
- privatevoidreadDataFile(){
- Filefile=newFile(filePath);
- ArrayList<String[]>dataArray=newArrayList<String[]>();
- try{
- BufferedReaderin=newBufferedReader(newFileReader(file));
- Stringstr;
- String[]tempArray;
- while((str=in.readLine())!=null){
- tempArray=str.split("");
- dataArray.add(tempArray);
- }
- in.close();
- }catch(IOExceptione){
- e.getStackTrace();
- }
- Pointtemp;
- totalPoint=newArrayList<>();
- for(String[]array:dataArray){
- temp=newPoint(array[0],array[1],array[2]);
- temp.setProbably(1.0/dataArray.size());
- totalPoint.add(temp);
- }
- }
- /**
- *根据当前的误差值算出所得的权重
- *
- *@paramerrorValue
- *当前划分的坐标点误差率
- *@return
- */
- privatedoublecalculateWeight(doubleerrorValue){
- doublealpha=0;
- doubletemp=0;
- temp=(1-errorValue)/errorValue;
- alpha=0.5*Math.log(temp);
- returnalpha;
- }
- /**
- *计算当前划分的误差率
- *
- *@parampointMap
- *划分之后的点集
- *@paramweight
- *本次划分得到的分类器权重
- *@return
- */
- privatedoublecalculateErrorValue(
- HashMap<Integer,ArrayList<Point>>pointMap){
- doubleresultValue=0;
- doubletemp=0;
- doubleweight=0;
- inttempClassType;
- ArrayList<Point>pList;
- for(Map.Entryentry:pointMap.entrySet()){
- tempClassType=(int)entry.getKey();
- pList=(ArrayList<Point>)entry.getValue();
- for(Pointp:pList){
- temp=p.getProbably();
- //如果划分类型不相等,代表划错了
- if(tempClassType!=p.getClassType()){
- resultValue+=temp;
- }
- }
- }
- weight=calculateWeight(resultValue);
- for(Map.Entryentry:pointMap.entrySet()){
- tempClassType=(int)entry.getKey();
- pList=(ArrayList<Point>)entry.getValue();
- for(Pointp:pList){
- temp=p.getProbably();
- //如果划分类型不相等,代表划错了
- if(tempClassType!=p.getClassType()){
- //划错的点的权重比例变大
- temp*=Math.exp(weight);
- p.setProbably(temp);
- }else{
- //划对的点的权重比减小
- temp*=Math.exp(-weight);
- p.setProbably(temp);
- }
- }
- }
- //如果误差率没有小于阈值,继续处理
- dataNormalized();
- returnresultValue;
- }
- /**
- *概率做归一化处理
- */
- privatevoiddataNormalized(){
- doublesumProbably=0;
- doubletemp=0;
- for(Pointp:totalPoint){
- sumProbably+=p.getProbably();
- }
- //归一化处理
- for(Pointp:totalPoint){
- temp=p.getProbably();
- p.setProbably(temp/sumProbably);
- }
- }
- /**
- *用AdaBoost算法得到的组合分类器对数据进行分类
- *
- */
- publicvoidadaBoostClassify(){
- doublevalue=0;
- Pointp;
- calculateWeightArray();
- for(inti=0;i<ClASSIFICATION.length;i++){
- System.out.println(MessageFormat.format("分类器{0}权重为:{1}",(i+1),CLASSIFICATION_WEIGHT[i]));
- }
- for(intj=0;j<totalPoint.size();j++){
- p=totalPoint.get(j);
- value=0;
- for(inti=0;i<ClASSIFICATION.length;i++){
- value+=1.0*classifyData(ClASSIFICATION[i],p)
- *CLASSIFICATION_WEIGHT[i];
- }
- //进行符号判断
- if(value>0){
- System.out
- .println(MessageFormat.format(
- "点({0},{1})的组合分类结果为:1,该点的实际分类为{2}",p.getX(),p.getY(),
- p.getClassType()));
- }else{
- System.out.println(MessageFormat.format(
- "点({0},{1})的组合分类结果为:-1,该点的实际分类为{2}",p.getX(),p.getY(),
- p.getClassType()));
- }
- }
- }
- /**
- *计算分类器权重数组
- */
- privatevoidcalculateWeightArray(){
- inttempClassType=0;
- doubleerrorValue=0;
- ArrayList<Point>posPointList;
- ArrayList<Point>negPointList;
- HashMap<Integer,ArrayList<Point>>mapList;
- CLASSIFICATION_WEIGHT=newdouble[ClASSIFICATION.length];
- for(inti=0;i<CLASSIFICATION_WEIGHT.length;i++){
- mapList=newHashMap<>();
- posPointList=newArrayList<>();
- negPointList=newArrayList<>();
- for(Pointp:totalPoint){
- tempClassType=classifyData(ClASSIFICATION[i],p);
- if(tempClassType==CLASS_POSITIVE){
- posPointList.add(p);
- }else{
- negPointList.add(p);
- }
- }
- mapList.put(CLASS_POSITIVE,posPointList);
- mapList.put(CLASS_NEGTIVE,negPointList);
- if(i==0){
- //最开始的各个点的权重一样,所以传入0,使得e的0次方等于1
- errorValue=calculateErrorValue(mapList);
- }else{
- //每次把上次计算所得的权重代入,进行概率的扩大或缩小
- errorValue=calculateErrorValue(mapList);
- }
- //计算当前分类器的所得权重
- CLASSIFICATION_WEIGHT[i]=calculateWeight(errorValue);
- }
- }
- /**
- *用各个子分类器进行分类
- *
- *@paramclassification
- *分类器名称
- *@paramp
- *待划分坐标点
- *@return
- */
- privateintclassifyData(Stringclassification,Pointp){
- //分割线所属坐标轴
- Stringposition;
- //分割线的值
- doublevalue=0;
- doubleposProbably=0;
- doublenegProbably=0;
- //划分是否是大于一边的划分
- booleanisLarger=false;
- String[]array;
- ArrayList<Point>pList=newArrayList<>();
- array=classification.split("=");
- position=array[0];
- value=Double.parseDouble(array[1]);
- if(position.equals("X")){
- if(p.getX()>value){
- isLarger=true;
- }
- //将训练数据中所有属于这边的点加入
- for(Pointpoint:totalPoint){
- if(isLarger&&point.getX()>value){
- pList.add(point);
- }elseif(!isLarger&&point.getX()<value){
- pList.add(point);
- }
- }
- }elseif(position.equals("Y")){
- if(p.getY()>value){
- isLarger=true;
- }
- //将训练数据中所有属于这边的点加入
- for(Pointpoint:totalPoint){
- if(isLarger&&point.getY()>value){
- pList.add(point);
- }elseif(!isLarger&&point.getY()<value){
- pList.add(point);
- }
- }
- }
- for(Pointp2:pList){
- if(p2.getClassType()==CLASS_POSITIVE){
- posProbably++;
- }else{
- negProbably++;
- }
- }
- //分类按正负类数量进行划分
- if(posProbably>negProbably){
- returnCLASS_POSITIVE;
- }else{
- returnCLASS_NEGTIVE;
- }
- }
- }
- /**
- *AdaBoost提升算法调用类
- *@authorlyq
- *
- */
- publicclassClient{
- publicstaticvoidmain(String[]agrs){
- StringfilePath="C:\\Users\\lyq\\Desktop\\icon\\input.txt";
- //误差率阈值
- doubleerrorValue=0.2;
- AdaBoostTooltool=newAdaBoostTool(filePath,errorValue);
- tool.adaBoostClassify();
- }
- }
输出结果:
- 分类器1权重为:0.424
- 分类器2权重为:0.65
- 分类器3权重为:0.923
- 点(1,5)的组合分类结果为:1,该点的实际分类为1
- 点(2,3)的组合分类结果为:1,该点的实际分类为1
- 点(3,1)的组合分类结果为:-1,该点的实际分类为-1
- 点(4,5)的组合分类结果为:-1,该点的实际分类为-1
- 点(5,6)的组合分类结果为:1,该点的实际分类为1
- 点(6,4)的组合分类结果为:-1,该点的实际分类为-1
- 点(6,7)的组合分类结果为:1,该点的实际分类为1
- 点(7,6)的组合分类结果为:1,该点的实际分类为1
- 点(8,7)的组合分类结果为:-1,该点的实际分类为-1
- 点(8,2)的组合分类结果为:-1,该点的实际分类为-1
我们可以看到,如果3个分类单独分类,都没有百分百分对,而尽管组合结果之后,全部分类正确。
我对AdaBoost算法的理解
到了算法的末尾,有必要解释一下每次分类自后需要把错的点的权重增大,正确的减少的理由了,加入上次分类之后,(1,5)已经分错了,如果这次又分错,由于上次的权重已经提升,所以误差率更大,则代入公式ln(1-误差率/误差率)所得的权重越小,也就是说,如果同个数据,你分类的次数越多,你的权重越小,所以这就造成整体好的分类器的权重会越大,内部就会同时有各种权重的分类器,形成了一种互补的结果,如果好的分类器结果分错 ,可以由若干弱一点的分类器进行弥补。
AdaBoost算法的应用
可以运用在诸如特征识别,二分类的一些应用上,与单个模型相比,组合的形式能显著的提高准确率。