决策树学习(下)——ID3、C4.5、CART深度剖析及源码实现_决策树id3c45cart代码-程序员宅基地

技术标签: 机器学习  决策树学习  C4-5  ID3  数据挖掘  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
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/yangmuted/article/details/47906143

智能推荐

在类Unix平台实现TCP客户端-程序员宅基地

文章浏览阅读1k次,点赞8次,收藏16次。我们这个TCP客户端将从命令行接收两个参数,一个是IP地址或域名,另一个是端口,并尝试连接在这个IP地址的TCP服务端。

彩虹外链网盘界面UI美化版超级简洁好看-程序员宅基地

文章浏览阅读114次。彩虹外链网盘,是一款PHP网盘与外链分享程序,支持所有格式文件的上传,可以生成文件外链、图片外链、音乐视频外链,生成外链同时自动生成相应的UBB代码和HTML代码,还可支持文本、图片、音乐、视频在线预览,这不仅仅是一个网盘,更是一个图床亦或是音乐在线试听网站。适合小文件快速共享,文件可以设置访问密码,doc,图片等文件可以预览,音频可以在线播放,也适合做图床,支持在线下载,美化前台UI,运行天数和友链在后台统计代码修改。访问后台/admin 默认账号密码admin/123456。

vue项目启动不显示Network,及Network无法访问的问题_vue network网址访问不了-程序员宅基地

文章浏览阅读4k次,点赞2次,收藏15次。首先找到build文件夹下webpack.dev.conf.js文件,修改compilationSuccessInfo里边的messages属性。在vue.config.js(或者配置config了的,就在config下的index.js )文件下设置。最后在package.json文件中 scripts 下的 dev 或者 serve 后面加上。以上设置可以显示Network 调试地址,但是无法访问,还需设置一下。回车后会出现一串信息,复制IPV4 地址即可。地址 ,获取IPV4 地址方法,_vue network网址访问不了

C++cin.getline和std::getline的用法和区别-程序员宅基地

文章浏览阅读632次。从文件或控制台读入数据。都可以一次性读入多个字符,即读一串字符。cin.getline 是类函数。是基本输入流的函数。标准输入流,是一个类对象。也可以是。std::getline 是标准函数,使用时需要引入头文件 。cin.getline读入的数据一般放在字符数组中,std::getline读入的数据一般放在string对象中。由于cin.getline函数读入的数据放在字符数组中,所以要给出读入字符的(最大)数量。std::getline函数需要给出读入的文件流对象。 : 指向要存储字符到的字符数组的_std::getline

倍福--EL2521控制步进_倍福el2521接线图-程序员宅基地

文章浏览阅读944次。使用了EL2521控制步进电机。对于脉冲型步进或伺服驱动器,可使用EL2521模块控制操作流程1.1. EL2521简介1.1.1. 外观和接线EL2521 xxxx输出端子改变二进制信号的频率并输出(与K总线电气隔离)。频率由来自自动化设备的16位值预设。EtherCAT端子的信号状态由发光二极管指示。LED与输出同步,每个LED显示一个活动输出。1.1.2. EL2521信号类型EL2521模块的后缀有-0000,-0024,-0025,-0124几种,各种类型的接线是不同的,这点要稍_倍福el2521接线图

转载-mac下完全卸载Node.js(亲测可用)_mac 卸载 node.js-程序员宅基地

文章浏览阅读46次。【代码】转载-mac下完全卸载Node.js(亲测可用)_mac 卸载 node.js

随便推点

变色龙哈希函数 Chameleon Hash 可变型区块链_变色龙哈希改写区块-程序员宅基地

文章浏览阅读1.3w次,点赞7次,收藏42次。哈希函数 Hash:众所周知,区块链有着极其优秀的安全性就是因为其充分使用了哈希函数。哈希简单用一句话来讲,就是:将任意长度输入的字串可转换成一个固定长度的字串,通过原始字串可以很容易地算出转换后的字串,通过转换后的字串很难还原出原始字串。哈希函数特征:1. 对于任意m作为输入,得到输出的结果,很难找到另一个输入m' (m'不等于m),使得m'的Hash结果也为同样的输出_变色龙哈希改写区块

【LSTM回归预测】基于蜣螂算法优化长短时记忆DBO-LSTM风电数据预测(含前后对比)附Matlab代码-程序员宅基地

文章浏览阅读796次,点赞13次,收藏17次。基于蜣螂算法优化长短时记忆(DBO-LSTM)模型的风电数据预测是一个备受关注的研究领域。风电是一种清洁能源,其预测对于能源规划和市场运营具有重要意义。本文旨在通过对比蜣螂算法优化前后的长短时记忆(DBO-LSTM)模型在风电数据预测中的表现,探讨蜣螂算法在优化风电数据预测模型中的有效性。首先,长短时记忆(LSTM)是一种适用于时间序列数据的深度学习模型,它能够捕捉数据中的长期依赖关系,因此在风电数据预测中具有较好的表现。

小米便签data包源码解读_小米便签开源代码注释-程序员宅基地

文章浏览阅读1.8k次,点赞36次,收藏53次。对于小米便签data包源码的一些解读,可能不是很准确_小米便签开源代码注释

KE分析之slub_debug功能使用记录_photonicmodulat-程序员宅基地

文章浏览阅读2.1k次。1. 前记前段时间遇到一个问题,具有一定的代表性,特此记录,后续遇到类似问题时,可以参考这个方向2. 问题说明此问题为低概率煲机测试类问题,概率较低,需要长时间的运行才可以触发到,排查起来比较困难;2.1 测试方法设置机器为自动化休眠唤醒测试,时间周期为1分钟;usb 口有连接u盘,唤醒后会自动播放u盘内歌曲;无其他特别步骤测试时通过串口打印debug信息,另外打开系统中集成的debug功能,即收集logcat + kernel log信息,在遇到anr\je\ne\ke\hwt时会将现场_photonicmodulat

Spring + spring Mvc No Servlet Set 错误_no servletcontext set-程序员宅基地

文章浏览阅读60次。在使用注解开发,SpringMvc配置类的情况下开启@EnableWebMvc,如果在springConfig类中的@Component扫描Bean对象再次扫描到spring和springMvc的配置Bean对象就会出现No Servlet Set错误。_no servletcontext set

初学cesium时的一些笔记,过于潦草看看就好_cesium pbr 3200-程序员宅基地

文章浏览阅读840次。Cesium ion(假如要用自己的数据则需要上传数据)Cesium依赖:基于HTML5标准,无插件,跨平台;无法单独运行,依赖于浏览器(Cesium Lab基于Electron架构)浏览器基于HTTP协议,所以必须有HTTP ServerCesium功能介绍:定义影像矢量模型:{1使用3d tiles格式模式加载各种不同的3d数据,包含倾斜摄影模型,三位建筑物,CAD和BIM的外..._cesium pbr 3200

推荐文章

热门文章

相关标签