决策树学习(下)——ID3、C4.5、CART深度剖析及源码实现

引言

《决策树学习(上)——深度原理剖析及源码实现》中,我们讨论了决策树的基本原理、所需要掌握的信息论知识,并在文章的最后给出了Java源码实现。在这一节,我们继续讨论基于决策树学习的算法。由于基于决策树的算法比较多且受篇幅限制,本文我们只讨论著名的ID3、C4.5以及CART算法,并在文章最后给出源码实现。


ID3与C4.5

ID3(Iterative Dichotomiser 3,迭代二叉树3代)由Ross Quinlan于1986年提出。1993年,他对ID3进行改进设计出了C4.5算法。值得称道的是,Quinlan在1998年提出了基于C4.5改进的C5.0算法。

《决策树学习(上)——深度原理剖析及源码实现》中(下文简称《上》),我们已经知道ID3与C4.5的不同之处在于,ID3根据信息增益选取特征构造决策树,而C4.5则是以信息增益率为核心构造决策树,这两种方式的计算法方法在《上》中已经给出。既然C4.5是在ID3的基础上改进得到的,那么这两者的优缺点分别是什么?

剖析ID3与C4.5优缺点

在《上》中我们已经讨论过,使用信息增益会让ID3算法更偏向于选择值多的属性。信息增益反映给定一个条件后不确定性减少的程度,必然是分得越细的数据集确定性更高,也就是信息熵越小,信息增益越大。因此,在一定条件下,值多的属性具有更大的信息增益。而C4.5则使用信息增益率选择属性。信息增益率通过引入一个被称作分裂信息(Split information)的项来惩罚取值较多的属性,分裂信息用来衡量属性分裂数据的广度和均匀性。这样就改进了ID3偏向选择值多属性的缺点。

此外,通过学术界及工业界的研究ID3还具有如下缺点:

  • ID3是单变量决策树(在分枝节点上只考虑单个属性),许多复杂概念的表达困难,属性相互关系强调不够,容易导致决策树中子树的重复或有些属性在决策树的某一路径上被检验多次。
  • 抗噪性差,训练例子中正例和反例的比例较难控制。
  • ID3是非递增算法。
  • 只能处理离散数据。

考虑到ID3的上述缺点,Quinlan对其进行改进的到C4.5。C4.5除了前面谈到的使用信息增益率而避免了选择值多的属性的优点之外,相比于ID3还有如下优点:
相对于ID3只能处理离散数据,C4.5还能对连续属性进行处理,具体步骤为:

  1. 把需要处理的样本(对应根节点)或样本子集(对应子树)按照连续变量的大小从小到大进行排序。
  2. 假设该属性对应的不同的属性值一共有N个,那么总共有N−1个可能的候选分割阈值点,每个候选的分割阈值点的值为上述排序后的属性值中两两前后连续元素的中点,根据这个分割点把原来连续的属性分成bool属性。实际上可以不用检查所有N−1个分割点。(连续属性值比较多的时候,由于需要排序和扫描,会使C4.5的性能有所下降。)
  3. 用信息增益比率选择最佳划分。

C4.5算法能够处理不完整的数据,常用的处理方法有以下三种:

  • 给缺失属性赋予最常见的值。
  • 丢弃含有缺失值的样本。
  • 根据节点的样例上该属性值出现的情况赋一个概率值。

在决策树构造的过程中进行剪枝,从而可以在一定程度上避免过拟合(Overfitting)

  • 建议在构造树的过程中不考虑拥有几个元素的节点。

从上面的讨论可以总结出,C4.5产生的分类规则易于理解,准确率较高。但由于在构造树的过程中,需要对数据集进行多次的顺序扫描和排序,因而导致算法会牺牲一定的效率。另外,无论是ID3还是C4.5最好在小数据集上使用,决策树分类一般适用于小数据。当属性取值很多时最好选择C4.5算法,ID3得出的效果会比较差。

ID3源码实现(C++版本)

感谢Coding for Dreams的源码贡献
训练数据集如下:

Day Outlook Temperature Humidity Wind PlayTennis
1 Sunny Hot High Weak no
2 Sunny Hot High Strong no
3 Overcast Hot High Weak yes
4 Rainy Mild High Weak yes
5 Rainy Cool Normal Weak yes
6 Rainy Cool Normal Strong no
7 Overcast Cool Normal Strong yes
8 Sunny Mild High Weak no
9 Sunny Cool Normal Weak yes
10 Rainy Mild Normal Weak yes
11 Sunny Mild Normal Strong yes
12 Overcast Mild High Strong yes
13 Overcast Hot Normal Weak yes
14 Rainy Mild High Strong no
end

源码如下:

#include <iostream>
#include <string>
#include <vector>
#include <map>
#include <algorithm>
#include <cmath>
using namespace std;
#define MAXLEN 6//输入每行的数据个数

//多叉树的实现 
//1 广义表
//2 父指针表示法,适于经常找父结点的应用
//3 子女链表示法,适于经常找子结点的应用
//4 左长子,右兄弟表示法,实现比较麻烦
//5 每个结点的所有孩子用vector保存
//教训:数据结构的设计很重要,本算法采用5比较合适,同时
//注意维护剩余样例和剩余属性信息,建树时横向遍历考循环属性的值,
//纵向遍历靠递归调用

vector <vector <string> > state;//实例集
vector <string> item(MAXLEN);//对应一行实例集
vector <string> attribute_row;//保存首行即属性行数据
string end("end");//输入结束
string yes("yes");
string no("no");
string blank("");
map<string,vector < string > > map_attribute_values;//存储属性对应的所有的值
int tree_size = 0;
struct Node{
  //决策树节点
    string attribute;//属性值
    string arrived_value;//到达的属性值
    vector<Node *> childs;//所有的孩子
    Node(){
        attribute = blank;
        arrived_value = blank;
    }
};
Node * root;

//根据数据实例计算属性与值组成的map
void ComputeMapFrom2DVector(){
    unsigned int i,j,k;
    bool exited = false;
    vector<string> values;
    for(i = 1; i < MAXLEN-1; i++){
  //按照列遍历
        for (j = 1; j < state.size(); j++){
            for (k = 0; k < values.size(); k++){
                if(!values[k].compare(state[j][i])) exited = true;
            }
            if(!exited){
                values.push_back(state[j][i]);//注意Vector的插入都是从前面插入的,注意更新it,始终指向vector头
            }
            exited = false;
        }
        map_attribute_values[state[0][i]] = values;
        values.erase(values.begin(), values.end());
    }   
}

//根据具体属性和值来计算熵
double ComputeEntropy(vector <vector <string> > remain_state, string attribute, string value,bool ifparent){
    vector<int> count (2,0);
    unsigned int i,j;
    bool done_flag = false;//哨兵值
    for(j = 1; j < MAXLEN; j++){
        if(done_flag) break;
        if(!attribute_row[j].compare(attribute)){
            for(i = 1; i < remain_state.size(); i++){
                if((!ifparent&&!remain_state[i][j].compare(value)) || ifparent){
  //ifparent记录是否算父节点
                    if(!remain_state[i][MAXLEN - 1].compare(yes)){
                        count[0]++;
                    }
                    else count[1]++;
                }
            }
            done_flag = true;
        }
    }
    if(count[0] == 0 || count[1] == 0 ) return 0;//全部是正实例或者负实例
    //具体计算熵 根据[+count[0],-count[1]],log2为底通过换底公式换成自然数底数
    double sum = count[0] + count[1];
    double entropy = -count[0]/sum*log(count[0]/sum)/log(2.0) - count[1]/sum*log(count[1]/sum)/log(2.0);
    return entropy;
}

//计算按照属性attribute划分当前剩余实例的信息增益
double ComputeGain(vector <vector <string> > remain_state, string attribute){
    unsigned int j,k,m;
    //首先求不做划分时的熵
    double parent_entropy = ComputeEntropy(remain_state, attribute, blank, true);
    double children_entropy = 0;
    //然后求做划分后各个值的熵
    vector<string> values = map_attribute_values[attribute];
    vector<double> ratio;
    vector<int> count_values;
    int tempint;
    for(m = 0; m < values.size(); m++){
        tempint = 0;
        for(k = 1; k < MAXLEN - 1; k++){
            if(!attribute_row[k].compare(attribute)){
                for(j = 1; j < remain_state.size(); j++){
                    if(!remain_state[j][k].compare(values[m])){
                        tempint++;
                    }
                }
            }
        }
        count_values.push_back(tempint);
    }

    for(j = 0; j < values.size(); j++){
        ratio.push_back((double)count_values[j] / (double)(remain_state.size()-1));
    }
    double temp_entropy;
    for(j = 0; j < values.size(); j++){
        temp_entropy = ComputeEntropy(remain_state, attribute, values[j], false);
        children_entropy += ratio[j] * temp_entropy;
    }
    return (parent_entropy - children_entropy); 
}

int FindAttr
  • 3
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值