提升方法(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依赖于弱分类器,而弱分类器的训练时间往往很长

参考:https://www.cnblogs.com/YongSun/p/4767513.html
https://www.cnblogs.com/zy230530/p/6909288.html
https://blog.csdn.net/guyuealian/article/details/70995333

猜你喜欢

转载自blog.csdn.net/daaikuaichuan/article/details/81198336
今日推荐