提升方法(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依賴於弱分類器,而弱分類器的訓練時間往往很長。