基於單決策樹的AdaBoost


 

①起源:Boosting算法

Boosting算法的目的是每次基於全部數據集,通過使用同一種分類器不同的抽取參數方法(如決策樹,每次都可以抽取不同的特征維度來剖分數據集)

訓練一些不同弱分類器(單次分類錯誤率>0.5),然后將其組合起來,綜合評估(默認認為每個分類器權重等價)進行分類。

AdaBoost算法進行了對其進行了改進。

一、每次訓練分類器時,給予每條數據用於計算誤差的不同權重D。

二、每個分類器,給予用於分類的不同權重alpha。

兩種權的使用,使得AdaBoost在組合分類器時,能夠根據當前的訓練情況做一些調整。

 

②弱分類器:單決策樹

單決策樹應該是AdaBoost中使用最多的弱分類器。硬傷是單決策樹是個0/1二類分類器。(實際是-1/+1二類)

一般決策樹是通過DFS,連續選擇不同維度多深度划分數據集。

單決策樹就是一般決策樹的1維版本,只搜dep=1就結束。

單決策樹有兩個好處:

一、單次分類效果很差(滿足弱分類器定義)

二、根據決策屬性不同,即便重復使用單決策樹,也可以誕生出分類效果不同的分類器。

單決策樹對於連續型數據,采用閾值(threshold)分類法。

首先確定所有數據中某個特征維度里的min/max范圍。

然后采用步進的方法(每次閾值=min+當前步數*步長)來確定閾值。

基於同一個閾值,又可以把小於/大於閾值的分到不同類(2種分法)。

所以單決策樹最多有【D*步數*2】種變形分類器。

 

②雙權問題

一、分類器權alpha

 定義 $\alpha = \frac{1}{2} \ln \left ( \frac{1-\varepsilon }{\varepsilon } \right )$,其中$\varepsilon$為錯誤率。

P.S 實際Coding時,為防止$\varepsilon$為零,通常取max($\varepsilon$,eps)

$\alpha$這個函數比較有趣,求導之后,假設每次使用新的分類器,錯誤率都會變小,以錯誤率變小為x正軸,有圖像:

這個圖像告訴我們,AdaBoost對后續分類器,給的權是逐漸變大的,因為我們有理由相信,后續分類器更科學一點。

分類器權如何使用呢?假設當前分類器的二類分類結果向量[-1,1,-1,1],那么其加權之后就是$\alpha$ * [-1,1,-1,1]。

對於二類問題,將全部分類器的加權結果累加起來,通過判斷加權結果符號的正負判斷分類,因為$\alpha$的值域是R,所以只能看符號判斷分類。

二、數據權D

每條數據都有權D,該權實際是個概率權,總權和=1。

如果一條數據被正確分類,則$D_{i}^{new}= \frac{D_{i}^{old}*e^{-\alpha}}{Sum(D)}$

如果一條數據被錯誤分類,則$D_{i}^{new}= \frac{D_{i}^{old}*e^{\alpha}}{Sum(D)}$

正確分類的權會被減小,錯誤分類的權會被增大。有趣的是,這兩個式子如果單從$\alpha$角度來看,似乎有點問題。

如果某次$\alpha$為負,那么$D_{i}^{new}= \frac{D_{i}^{old}*e^{-\alpha}}{Sum(D)}$$D_{i}^{new}$似乎好像是變大了。

但如果你把$\alpha$的定義式拉到$D_{i}^{new}$里面就會發現:

$\left\{\begin{matrix}
D_{i}^{new}= \frac{1}{\sqrt{e}}*\frac{1-\varepsilon }{\varepsilon }
\\
D_{i}^{new}= \sqrt{e}*\frac{1-\varepsilon }{\varepsilon}
\end{matrix}\right.$

這樣,很明顯就可以看出,正確分類的權確實相對於錯誤分類的權被減小了。

數據權D的引入,主要是采用一種新的方式計算錯誤率。

一般的錯誤率$\varepsilon=\frac{errorExamples}{allExamples}$

但是AdaBoost里對錯誤率也進行加權了,$\varepsilon=\sum_{i=1}^{m}D_{i}*isError?1:0$

由於D是概率權,這樣算出來的也算是錯誤率。如果一條數據分錯了、又分錯了、還是分錯了,那么這條數據理應得到重視,其也會導致錯誤率比較大。

這種加權錯誤率對於在單決策樹里,從眾多變形分類器中,選擇一個錯誤率最低分類器,有很好的參考意義。

 

③算法過程

★訓練過程

while(true)

{

        一、構建一個錯誤率最低的單決策樹分類器,並保存記錄該分類器全部屬性(α、閥值、選取維度、分類結果)

        二、利用構建的單決策樹分類器計算α加權分類結果

        若數據樣本分類全部正確(大數據情況下幾乎不可能)、分類正確率到達一定情況(多見於正確率收斂)則break

}

★測試過程

for(全部構建的分類器)

      for(全部測試數據)

           一、使用當前分類器分類

           二、累計計算使用全部分類器的加權結果(記錄α的原因)

根據加權結果的正負判斷分類

 

不難發現,測試過程其實就是訓練過程的某一步提取出來的。

 

④代碼

#include "iostream"
#include "fstream"
#include "sstream"
#include "math.h"
#include "vector"
#include "string"
#include "cstdio"
using namespace std;
#define fin cin
#define inf 1e10
#define D dataSet[0].feature.size()
#define delta 100
int sign(double x) {return x<0?-1:1;}
struct Data
{
    vector<double> feature;
    int y;
    Data(vector<double> feature,int y):feature(feature),y(y) {}
};
struct Stump
{
    vector<int> classEst;
    int dim,symbol;
    double threshold,error,alpha;
    Stump() {}
    Stump(vector<int> classEst,int dim,double threshold,int symbol,double error):classEst(classEst),dim(dim),threshold(threshold),symbol(symbol),error(error) {}
};
vector<Data> dataSet;
void read()
{
    ifstream fin("traindata.txt");
    string line;
    double fea,cls;
    int id;
    while(getline(cin,line))
    {
        vector<double> feature;
        stringstream sin(line);
        sin>>id;
        while(sin>>fea) feature.push_back(fea);
        cls=feature.back();feature.pop_back();
        dataSet.push_back(Data(feature,cls==0?-1:1));
    }
}
vector<int> stumpClassify(int d,double threshold,int symbol)
{
    vector<int> ret(dataSet.size(),1);
    for(int i=0;i<dataSet.size();i++)
    {
        if(!symbol) {if(dataSet[i].feature[d]<=threshold) ret[i]=-1;}
        else {if(dataSet[i].feature[d]>threshold) ret[i]=-1;}
    }
    return ret;
}
Stump buildStump(vector<double> dataWeight)
{
    int step=10;
    Stump bestStump;
    double minError=inf,weightError;
    for(int i=0;i<D;i++)
    {
        double minRange=inf,maxRange=-inf,stepSize;
        for(int j=0;j<dataSet.size();j++)
        {
            minRange=min(minRange,dataSet[j].feature[i]);
            maxRange=max(maxRange,dataSet[j].feature[i]);
        }
        stepSize=(maxRange-minRange)/step;
        for(int j=0;j<=step;j++)
        {
            double threshold=minRange+stepSize*j;
            for(int k=0;k<=1;k++)
            {
                vector<int> classEst=stumpClassify(i,threshold,k);
                weightError=0.0;
                for(int t=0;t<classEst.size();t++)
                    if(classEst[t]!=dataSet[t].y) weightError+=dataWeight[t];
                if(weightError<minError)
                {
                    bestStump=Stump(classEst,i,threshold,k,weightError);
                    minError=weightError;
                }
            }
        }
    }
    return bestStump;
}
vector<Stump> mainProcess()
{
    int iter=0,repeat=0,last_cnt=-1;
    vector<double> dataWeight(dataSet.size(),1.0/dataSet.size());
    vector<double> aggClassEst(dataSet.size(),0.0);
    vector<Stump> stumpList;
    while(1)
    {
        iter++;
        Stump nowStump=buildStump(dataWeight);
        double alpha=0.5*log((1.0-nowStump.error)/max(nowStump.error,1e-16)),DSum=0.0;
        nowStump.alpha=alpha;
        stumpList.push_back(nowStump);
        int err_cnt=0;
        for(int i=0;i<dataSet.size();i++)
        {
            double ret=-1*alpha*dataSet[i].y*nowStump.classEst[i];
            dataWeight[i]=dataWeight[i]*exp(ret);
            DSum+=dataWeight[i];
        }
        for(int i=0;i<dataSet.size();i++)
        {
            dataWeight[i]/=DSum;
            aggClassEst[i]+=alpha*nowStump.classEst[i];
            if(sign(aggClassEst[i])!=dataSet[i].y) err_cnt++;
        }
        //cout<<err_cnt<<"/"<<dataSet.size()<<endl;
        if(err_cnt!=last_cnt) {last_cnt=err_cnt;repeat=0;}
        else repeat++;
        if(err_cnt==0||repeat>delta) break;
    }
    return stumpList;
}
void classify(vector<Stump> stumpList)
{
    ifstream fin("testdata.txt");
    dataSet.clear();
    string line;
    double fea,cls;
    int id;
    while(getline(cin,line))
    {
        vector<double> feature;
        stringstream sin(line);
        sin>>id;
        while(sin>>fea) feature.push_back(fea);
        cls=feature.back();feature.pop_back();
        dataSet.push_back(Data(feature,cls==0?-1:1));
    }
    vector<double> aggClassEst(dataSet.size(),0.0);
    for(int i=0;i<stumpList.size();i++)
    {
        vector<int> classEst=stumpClassify(stumpList[i].dim,stumpList[i].threshold,stumpList[i].symbol);
        for(int j=0;j<dataSet.size();j++)
          aggClassEst[j]+=(stumpList[i].alpha*classEst[j]);
    }
    for(int i=0;i<dataSet.size();i++)
        if(sign(aggClassEst[i])==-1) printf("%test #%d:  origin: %d class %d\n",i,dataSet[i].y,-1);
        else printf("%test #%d: origin: %d class %d\n",i,dataSet[i].y,1);
}
int main()
{
    read();
    vector<Stump> stumpList=mainProcess();
    classify(stumpList);
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM