提升(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依赖于弱分类器,而弱分类器的训练时间往往很长。
参考:https://www.cnblogs.com/YongSun/p/4767513.html
https://www.cnblogs.com/zy230530/p/6909288.html
https://blog.csdn.net/guyuealian/article/details/70995333