1. 程式人生 > >提升方法(Adaboost)

提升方法(Adaboost)

  提升(boosting)方法是一種常用的統計學習方法,應用廣泛且有效。在分類問題中,它通過改變訓練樣本的權重,學習多個分類器,並將這些分類器進行線性組合,提高分類的效能。
  基本思想:對於分類問題而言,給定一個訓練樣本集,求比較粗糙的分類規則(弱分類器)要比求精確的分類規則(強分類器)容易得多。提升方法就是從弱學習演算法出發,反覆學習,得到一系列弱分類器(又稱為基本分類器),然後組合這些弱分類器,構成一個強分類器。大多數的提升方法都是改變訓練資料的概率分佈(訓練資料的權值分佈),針對不同的訓練資料分佈呼叫弱學習演算法學習一系列弱分類器。
這裡寫圖片描述

1、Adaboost演算法簡介

  對提升方法來說,有兩個問題需要回答:一是在每一輪如何改變訓練資料的權值或概率分佈;二是如何將弱分類器組合成一個強分類器


  關於第1個問題,AdaBoost的做法是,提高那些被前一輪弱分類器錯誤分類樣本的權值,而降低那些被正確分類樣本的權值。這樣一來,那些沒有得到正確分類的資料,由於其權值的加大而受到後輪的弱分類器的更大關注。於是,分類問題被一系列的弱分類器“分而治之”。
  至於第2個問題,即弱分類器的組合,AdaBoost採取加權多數表決的方法。具體地,加大分類誤差率小的弱分類器的權值,使其在表決中起較大的作用,減小分類誤差率大的弱分類器的權值,使其在表決中起較小的作用

2、Adaboost演算法的思想

這裡寫圖片描述

【演算法說明】:

1. “在權值為D的訓練資料上,閾值取xxx時分類誤差率最低”,這句話的意思是,選擇一個弱分類器(單層決策樹),在這個弱分類器上,誤差是權值和弱分類器取值的乘積

,所以選擇不同的閾值對應的誤差也不同,,我們需要找到誤差最低時對應的弱分類器;

2. Gm(x)在加權的訓練資料集上的分類誤差率是被Gm(x)誤分類樣本的權值之和,由此可以看出資料權值分佈Dm與基本分類器Gm(x)的分類誤差率的關係;

3. 當em<=1/2時,am>=0,並且am隨著em的減小而增大,所以分類誤差率越小的基本分類器在最終分類器中的作用越大

4. 更新訓練資料的權值分佈為下一輪作準備,式(8.4}可以寫成:
這裡寫圖片描述
由此可知,被基本分類器Gm(x)誤分類樣本的權值得以擴大,而被正確分類樣本的權值卻得以縮小。.兩相比較,誤分類樣本的權值被放大。不改變所給的訓練資料,而不斷改變訓練資料權值的分佈,使得訓練資料在基本分類器的學習中起不同的作用,這是AdaBoost的一個特點。

3、Adaboost演算法例項及demo

這裡寫圖片描述

#pragma once
#include <iostream>
#include <vector>
#include <fstream>
#include <sstream>
#include <string>
#include <cmath>
#include <utility>
#include <tuple>
#include <algorithm>
using namespace std;

const double e = 2.718281828459;
const double err = 0.01;

class Adaboost_Test
{
public:
    Adaboost_Test() {}
    ~Adaboost_Test() {}
    void loadDataset(const string &filename);
    int init();
    void SetWeight();
    int judge(double d1, double d2, int flag);
    void Adaboost_Test::classify();
    void acc_result();
private:
    vector<double> trainMat;
    vector<int> trainLabel;
    vector<double> testMat;
    vector<int> testLabel;
    vector<double> weight;
    double threshVal = 0.0;
    double er_out = 0.01;
    double t_max;
    vector<vector<double>> g;
    vector<double> ap;
    vector<double> val;
    vector<int> flag;
};
#include "adaboost.h"

//載入資料。
void Adaboost_Test::loadDataset(const string &filename)
{
    ifstream file(filename);
    string line;
    while (getline(file, line))
    {
        istringstream record(line);
        vector<double> data;
        double temp;
        while (record >> temp)
            data.push_back(temp);
        if (filename.find("train") != string::npos)
        {
            trainLabel.push_back(int(temp));
            data.pop_back();
            trainMat.push_back(data.front());
        }
        else
        {
            testLabel.push_back(int(temp));
            data.pop_back();
            testMat.push_back(data.front());
        }
    }
}

//返回訓練集的長度。
int Adaboost_Test::init()
{
    int sz = trainLabel.size();
    return sz;
}

//設定權重。
void Adaboost_Test::SetWeight()
{
    int sz = this->init();
    for (int i = 0; i < sz; ++i)
        weight.push_back(1.0 / sz);
}

//返回訓練集誤分類資料個數。
int erpt(vector<double> vec1, vector<int> vec2)
{
    vector<int> vec_temp;
    for (int i = 0; i < vec1.size(); ++i)
    {
        if (vec1[i] > 0)
            vec_temp.push_back(1);
        else
            vec_temp.push_back(-1);
    }
    int cnt = 0;
    for (int i = 0; i < vec1.size(); ++i)
    {
        if (vec_temp[i] != vec2[i])
            ++cnt;
    }
    return cnt;
}

//根據係數向量和閾值向量判斷在強分類器上的輸出類別。
int Adaboost_Test::judge(double d1, double d2, int flag)
{
    if (flag == 1)//判斷上閾值還是下閾值。
    {
        if (d1 < d2)
            return 1;
        else
            return -1;
    }
    else
    {
        if (d1 < d2)
            return -1;
        else
            return 1;
    }
}

void Adaboost_Test::classify()
{
    t_max = *max_element(trainMat.begin(), trainMat.end());
    for (int k = 0; k < 5; ++k)
    {
        threshVal = 0.01;//設定閾值。
        int sz = trainLabel.size();
        double cnt = 0;
        vector<pair<double, double>> vp;
        vector<pair<double, vector<int>>> vp2;
        //獲取閾值及其對應的誤差率,以及基本分類器向量。
        //閾值可能是上閾值也可能是下閾值,所以有兩個vector。
        while (threshVal < t_max)
        {
            vector<int> g_test1;
            vector<int> g_temp1;
            vector<int> g_test2;
            vector<int> g_temp2;
            for (int i = 0; i < sz; ++i)
            {
                if (trainMat[i] < threshVal)
                {
                    g_temp1.push_back(1);
                    g_temp2.push_back(-1);
                }
                else
                {
                    g_temp1.push_back(-1);
                    g_temp2.push_back(1);
                }
                if (g_temp1[i] == trainLabel[i])
                    g_test1.push_back(0);
                else
                    g_test1.push_back(1);
                if (g_temp2[i] == trainLabel[i])
                    g_test2.push_back(0);
                else
                    g_test2.push_back(1);
            }
            double er_temp1 = 0.0;
            double er_temp2 = 0.0;
            for (int i = 0; i < g_test1.size(); ++i)
            {
                er_temp1 += weight[i] * g_test1[i];
            }
            for (int i = 0; i < g_test2.size(); ++i)
            {
                er_temp2 += weight[i] * g_test2[i];
            }
            vp.push_back(make_pair(threshVal, er_temp1));
            vp.push_back(make_pair(threshVal, er_temp2));
            vp2.push_back(make_pair(er_temp1, g_temp1));
            vp2.push_back(make_pair(er_temp2, g_temp2));
            threshVal += 0.01;
        }
        //對誤差率進行排序。
        sort(vp.begin(), vp.end(),
            [](const pair<double, double> &x, const pair<double, double> &y) -> bool
        {
            return x.second < y.second;
        });
        //獲取最低的誤差率及閾值。
        auto it = vp.begin();
        threshVal = it->first;
        auto er = it->second;
        //計算係數。
        double alpha = (1.0 / 2)*log((1 - er) / er);
        vector<int> g_temp;
        //獲取該閾值對應的基本分類器向量。
        for (auto it = vp2.begin(); it != vp2.end(); ++it)
        {
            if (er == it->first)
            {
                g_temp.assign(it->second.begin(), it->second.end());
                break;
            }
        }
        //判斷是上閾值還是下閾值,並放在閾值向量中。
        if (g_temp[0] == 1)
            flag.push_back(1);
        else
            flag.push_back(-1);
        //更新權值向量。
        vector<double> weight_temp(sz);
        for (int i = 0; i < sz; ++i)
        {
            if (g_temp[i] == trainLabel[i])
                weight_temp[i] = weight[i] * (1.0 / (2 * (1 - er)));
            else
                weight_temp[i] = weight[i] * (1.0 / (2 * er));
        }
        weight.assign(weight_temp.begin(), weight_temp.end());
        //計算強分類器的誤差率。
        vector<double> f(sz);
        for (int i = 0; i < sz; ++i)
        {
            f[i] = alpha * g_temp[i];
        }
        g.push_back(f);
        vector<double> f_out;
        for (int i = 0; i < sz; ++i)
        {
            double sum = 0.0;
            for (int j = 0; j < g.size(); ++j)
            {
                sum += g[j][i];
            }
            f_out.push_back(sum);
        }
        er_out = (double)erpt(f_out, trainLabel) / sz;
        //儲存係數向量和閾值向量。
        ap.push_back(alpha);
        val.push_back(threshVal);
        //如果強分類器的誤差率小於指定值,則訓練完成。
        if (er_out <= err)
            break;
    }
}

void Adaboost_Test::acc_result()
{
    classify();
    int accnum = 0;
    int n2 = testLabel.size();
    for (int i = 0; i < n2; ++i)
    {
        int result;
        double sum = 0.0;
        //對於輸入值,計算強分類器對應的輸出值。
        for (int j = 0; j < ap.size(); ++j)
        {
            sum += ap[j] * judge(testMat[i], val[j], flag[j]);
        }
        //通過符號函式輸出類別。
        if (sum > 0)
            result = 1;
        else
            result = -1;
        //計算正確分類數。
        if (result == testLabel[i])
            accnum++;
    }
    cout << "accuracy: " << accnum*100.0 / n2 << "%" << endl;
    cout << "classification: " << accnum << " / " << n2 << endl;
}
#include "adaboost.h"
#include <string>
using namespace std;

int main()
{
    string trainFile("outtrain.txt");
    string testFile("outtest.txt");
    Adaboost_Test ad;
    ad.loadDataset(trainFile);
    ad.loadDataset(testFile);
    ad.SetWeight();
    ad.acc_result();
    system("pause");
    return 0;
}

4、Adaboost演算法總結

  Adaboost是boosting方法中最流行的一種演算法。它是以弱分類器作為基礎分類器,輸入資料之後,通過加權向量進行加權,在每一輪的迭代過程中都會基於弱分類器的加權錯誤率,更新權重向量,從而進行下一次迭代。並且會在每一輪迭代中計算出該弱分類器的係數,該係數的大小將決定該弱分類器在最終預測分類中的重要程度。顯然,這兩點的結合是Adaboost演算法的優勢所在。

【優點】:

1. Adaboost提供一種框架,在框架內可以使用各種方法構建子分類器。可以使用簡單的弱分類器,不用對特徵進行篩選,也不存在過擬合的現象

2. Adaboost演算法不需要弱分類器的先驗知識,最後得到的強分類器的分類精度依賴於所有弱分類器。無論是應用於人造資料還是真實資料,Adaboost都能顯著的提高學習精度

3. Adaboost演算法不需要預先知道弱分類器的錯誤率上限,且最後得到的強分類器的分類精度依賴於所有弱分類器的分類精度,可以深挖分類器的能力。Adaboost可以根據弱分類器的反饋,自適應地調整假定的錯誤率,執行的效率高

4. Adaboost對同一個訓練樣本集訓練不同的弱分類器,按照一定的方法把這些弱分類器集合起來,構造一個分類能力很強的強分類器,即“三個臭皮匠賽過一個諸葛亮”。

【缺點】:

1. 在Adaboost訓練過程中,Adaboost會使得難於分類樣本的權值呈指數增長,訓練將會過於偏向這類困難的樣本,導致Adaboost演算法易受噪聲干擾。此外,Adaboost依賴於弱分類器,而弱分類器的訓練時間往往很長