1.算法介绍

分类回归树算法:CART(Classification And Regression Tree)算法采用一种二分递归分割的技术,将当前的样本集分为两个子样本集,使得生成的的每个非叶子节点都有两个分支。因此,CART算法生成的决策树是结构简洁的二叉树。

分类树两个基本思想:第一个是将训练样本进行递归地划分自变量空间进行建树的想法,第二个想法是用验证数据进行剪枝。

建树:在分类回归树中,我们把类别集Result表示因变量,选取的属性集attributelist表示自变量,通过递归的方式把attributelist把p维空间划分为不重叠的矩形,具体建树的基本步骤参见:http://baike.baidu.com/view/3075445.htm。

CART算法是怎样进行样本划分的呢?它检查每个变量和该变量所有可能的划分值来发现最好的划分,对离散值如{x,y,x},则在该属性上的划分有三种情况({{x,y},{z}},{{x,z},y},{{y,z},x}),空集和全集的划分除外;对于连续值处理引进“分裂点”的思想,假设样本集中某个属性共n个连续值,则有n-1个分裂点,每个“分裂点”为相邻两个连续值的均值 (a[i] + a[i+1]) / 2。将每个属性的所有划分按照他们能减少的杂质(合成物中的异质,不同成分)量来进行排序,杂质的减少被定义为划分前的杂质减去划分之后每个节点的杂质量*划分所占样本比率之和,目前最流行的杂质度量方法是:GINI指标,如果我们用k,k=1,2,3……C表示类,其中C是类别集Result的因变量数目,一个节点A的GINI不纯度定义为:

其中,Pk表示观测点中属于k类得概率,当Gini(A)=0时所有样本属于同一类,当所有类在节点中以相同的概率出现时,Gini(A)最大化,此时值为(C-1)C/2。

对于分类回归树,A如果它不满足“T都属于同一类别or T中只剩下一个样本”,则此节点为非叶节点,所以尝试根据样本的每一个属性及可能的属性值,对样本的进行二元划分,假设分类后A分为B和C,其中B占A中样本的比例为p,C为q(显然p+q=1)。则杂质改变量:Gini(A) -p*Gini(B)-q*Gini(C),每次划分该值应为非负,只有这样划分才有意义,对每个属性值尝试划分的目的就是找到杂质该变量最大的一个划分,该属性值划分子树即为最优分支。

剪枝:在CART过程中第二个关键的思想是用独立的验证数据集对训练集生长的树进行剪枝。

分析分类回归树的递归建树过程,不难发现它实质上存在着一个数据过度拟合问题。在决策树构造时,由于训练数据中的噪音或孤立点,许多分枝反映的是训练数据中的异常,使用这样的判定树对类别未知的数据进行分类,分类的准确性不高。因此试图检测和减去这样的分支,检测和减去这些分支的过程被称为树剪枝。树剪枝方法用于处理过分适应数据问题。通常,这种方法使用统计度量,减去最不可靠的分支,这将导致较快的分类,提高树独立于训练数据正确分类的能力。

决策树常用的剪枝常用的简直方法有两种:事前剪枝和事后剪枝,CART算法经常采用事后剪枝方法:该方法是通过在完全生长的树上剪去分枝实现的,通过删除节点的分支来剪去树节点。最下面未被剪枝的节点成为树叶。

CART用的成本复杂性标准是分类树的简单误分(基于验证数据的)加上一个对树的大小的惩罚因素。惩罚因素是有参数的,我们用a表示,每个节点的惩罚。成本复杂性标准对于一个数来说是Err(T)+a|L(T)|,其中Err(T)是验证数据被树误分部分,L(T)是树T的叶节点树,a是每个节点的惩罚成本:一个从0向上变动的数字。当a=0对树有太多的节点没有惩罚,用的成本复杂性标准是完全生长的没有剪枝的树。在剪枝形成的一系列树中,从其中选择一个在验证数据集上具有最小误分的树是很自然的,我们把这个树成为最小误分树。

2.算法实现

本文根据一个样本集,进行了CART算法的简单实现。该样本集中每个样本有十六个特征属性和一个结果属性,为了降低划分的难度,每个特征属性取两个不同的离散值,结果属性有两个离散值:Yes和No。

数据结构定义:在该算法中定义了三种数据结构:存储样本属性名称及取值的Node属性,存储单个样本的EXampleSet属性,树的节点属性dataNode;存放在DataStructure.h中,代码如下:

typedef struct tagNode

{//存储属性

string name;//属性的名称

string value;//属性取值

}Node;

typedef struct tagExampleSet

{//样本存储

string example[16];//样本的每个属性上的属性值

string decision;//样本的结果类

}ExampleSet;

typedef struct Data_Node{

//节点的数据结构,结果分为两类yes类和No类

int Yesnum;//类yes得样本数目

int Nonum;//类no得样本数

vector myVector;//存储样本

Data_Node *LeftNode;//左子树

Data_Node *RightNode;//右子树

int Property;//划分选取的属性

string Proper_value;//所选的属性的值

int nodenum;//标示节点

bool leavenode;//标示叶节点

}dataNode;

typedef struct tagNode

{//存储属性

string name;//属性的名称

string value;//属性取值

}Node;

typedef struct tagExampleSet

{//样本存储

string example[16];//样本的每个属性上的属性值

string decision;//样本的结果类

}ExampleSet;

typedef struct Data_Node{

//节点的数据结构,结果分为两类yes类和No类

int Yesnum;//类yes得样本数目

int Nonum;//类no得样本数

vector myVector;//存储样本

Data_Node *LeftNode;//左子树

Data_Node *RightNode;//右子树

int Property;//划分选取的属性

string Proper_value;//所选的属性的值

int nodenum;//标示节点

bool leavenode;//标示叶节点

}dataNode;

样本读取及处理:用两个文件分别存储样本的属性及所有样本。文件t存储样本的十六个自变量属性、类别属性的名称和离散值集合,文件t1是所有样本的集合,用ReadFile类读取文件,并把它们分别存储在两个向量中。建树的过程在MySufan类中,该类地方法列表如下:

MySuanfa();

~MySuanfa();

void Method();//调用建树、剪枝方法

void BuildTree(Data_Node*thisNode);//建树方法,每次调用DeviceTree对非叶节点进行划分

void DeviceTree(Data_Node*thisNode,int i);//对非叶结点进行划分,分出左节点,有节点

int Choose_Property(Data_Node* thisNode);//返回选择的属性值

double pure(int i1,int i2,int i3);//纯度计算函数,每次计算最优划分时用

void Deal(Data_Node* d);//剪枝函数,此函数对建好的树用测试样本进行剪枝

void levelorder(Data_Node * p);//层次遍历,此方法按曾给决策点分配序号,用于剪枝

void inorder(Data_Node *p);//中序遍历,和建树的前序遍历用于确定树的结构

void BuildTest(Data_Node *d,int t);//此方法用于计算当取不同决策点时,建树样本的错误样本数,t为决策点数目

void CutTree(Data_Node *d,int k,int t);//k为单个样本,t为决策点数,根据决策点对测试样本集进行测试

void ClassOfNode(vector);//本方法用于切割原始样本集,将样本分为测试样本和建树样本

MySuanfa();

~MySuanfa();

void Method();//调用建树、剪枝方法

void BuildTree(Data_Node*thisNode);//建树方法,每次调用DeviceTree对非叶节点进行划分

void DeviceTree(Data_Node*thisNode,int i);//对非叶结点进行划分,分出左节点,有节点

int Choose_Property(Data_Node* thisNode);//返回选择的属性值

double pure(int i1,int i2,int i3);//纯度计算函数,每次计算最优划分时用

void Deal(Data_Node* d);//剪枝函数,此函数对建好的树用测试样本进行剪枝

void levelorder(Data_Node * p);//层次遍历,此方法按曾给决策点分配序号,用于剪枝

void inorder(Data_Node *p);//中序遍历,和建树的前序遍历用于确定树的结构

void BuildTest(Data_Node *d,int t);//此方法用于计算当取不同决策点时,建树样本的错误样本数,t为决策点数目

void CutTree(Data_Node *d,int k,int t);//k为单个样本,t为决策点数,根据决策点对测试样本集进行测试

void ClassOfNode(vector);//本方法用于切割原始样本集,将样本分为测试样本和建树样本

递归建树:建树按照递归方式进行建树,采用全部样本的2/3进行建树,首先找到一个划分值,如果不存在返回-1,然后判断一个树是否为叶子节点,不为叶子节点按照划分值进行划分,关键代码如下:

void MySuanfa::BuildTree(Data_Node* thisNode)

{

if(thisNode!=NULL){// //节点不为空

nodenum++;

thisNode->nodenum=nodenum;

int getProperty=Choose_Property(thisNode);//找到划分

thisNode->Property=getProperty;

if((thisNode->Yesnum*thisNode->Nonum==0)||getProperty==-1)

{//如果划分为-1,则无法再次划分

thisNode->Property=-1;

thisNode->leavenode=true;

}

else

{//递归建树

thisNode->leavenode=false;

DeviceTree(thisNode,getProperty);//将父节点按照划分属性进行划分

BuildTree(thisNode->LeftNode);//递归建立左子树

BuildTree(thisNode->RightNode);//递归建立右子树

}

}

}

void MySuanfa::BuildTree(Data_Node* thisNode)

{

if(thisNode!=NULL){// //节点不为空

nodenum++;

thisNode->nodenum=nodenum;

int getProperty=Choose_Property(thisNode);//找到划分

thisNode->Property=getProperty;

if((thisNode->Yesnum*thisNode->Nonum==0)||getProperty==-1)

{//如果划分为-1,则无法再次划分

thisNode->Property=-1;

thisNode->leavenode=true;

}

else

{//递归建树

thisNode->leavenode=false;

DeviceTree(thisNode,getProperty);//将父节点按照划分属性进行划分

BuildTree(thisNode->LeftNode);//递归建立左子树

BuildTree(thisNode->RightNode);//递归建立右子树

}

}

}

分析上面代码,Choose_Property(thisNode);函数的作用是将thisNode中的样本尝试进行最优划分,划分的依据就是杂质最大该变量,如果划分成功返回属性下标,否则返回-1,我们在样本中每个属性默认取两个离散值。注意到方法中对书中定义的leavenode和nodenum两个变量的操作,他们的用途是什么呢?nodenum的第一个作用是树的遍历,将每一个节点赋予一个唯一的值,建树的过程是前序建树,建树结束后根据树的中序遍历可以唯一确定树的结构,nodenum的第二个作用和leavenode的作用将会在剪枝过程中用到,后面将会提到。

当建树结束后,树的前序即为nodenum从小到大的排序,然后通过调用中序遍历函数输出树的中序序列,确定树的结构。该树含有17个决策点(非叶子节点),18个叶子节点。

图1. 结构

树中决策点的划分代码对应的属性名称:

0————handicapped-infants ;           1————water-project-cost-sharing

2————adoption-of-the-budget-resolution ; 3————physician-fee-freeze

4————el-salvador-aid ;                              5————religious-groups-in-schools

6————anti-satellite-test-ban;                        7————aid-to-nicaraguan-contras

8————mx-missile ;                                       9————immigration

10————synfuels-corporation-cutback ;        11————education-spending

12————superfund-right-to-sue ;                  13————crime

14————duty-free-exports ;                          15—export-administration-act-south-africa

按照递归分类的算法,最终生成的树的叶子节点中或者同属一类或者只有一个样本,分析树的结构我们可以发现,有两个叶子节点8和23不符合这种情况,却成了叶子节点。这与所选样本有关,在这两个叶节点中两个样本的十六个特征属性值都相同,只有所属类别不同,所以无法根据递归算法进行分类。另当选取physician-fee-freeze 和adoption-of-the-budget-resolution两种属性进行决策时,样本所属的类别已经基本判定,造成这种情况我们可认为这两种属性在样本中所占的权重很大,只要确定这两种情况,树的大部分样本的分类就已确定。

剪枝:用训练样本建树结束后,就是进行树的剪枝阶段,本算法采用样本集的后1/3作为测试进行剪枝。

树的决策点:如果一个节点为非叶节点,则称该节点为一个树的决策点。树的剪枝就是减去过分拟合给树带来的的冗余,用尽可能少的决策点、尽可能低的树高获取尽可能大的正确率。

如何获取树的决策点?逐层确定树的决策点,并根据决策点数目进行剪枝是剪枝的关键。

根据二叉树的特性可知树的非叶节点=叶节点-1;所以可以从树的节点数中得知树种非叶结点的数量。本程序根据这一特性将树的决策点逐层赋值,根节点赋值1,根节点的左节点赋值2……,这一过程通过层次遍历实现。并将该值赋给nodenum,对于叶子节点nodenum为0关键代码如下:

void MySuanfa::levelorder(Data_Node* p)

{

int node=1;

listq;

if(p)q.push_back(p);

p->nodenum=node;

while(!q.empty())

{

p=q.front();

q.pop_front();

if(p->LeftNode)

{

if(p->LeftNode->leavenode)

{//如果该节点的左节点是子节点,则将nodenum赋0

p->LeftNode->nodenum=0;

}

else

{//否则将该节点赋一个node值,该值表示此决策点的顺序

node++;

p->LeftNode->nodenum=node;

q.push_back(p->LeftNode);

}

}

if(p->RightNode)

{

if(p->RightNode->leavenode)//

{//如果该节点的右节点是子节点,则将nodenum赋0

p->RightNode->nodenum=0;

}

else

{//否则将该节点赋一个node值,该值表示此决策点的顺序

node++;

p->RightNode->nodenum=node;

q.push_back(p->RightNode);

}

}

}

}

void MySuanfa::levelorder(Data_Node* p)

{

int node=1;

listq;

if(p)q.push_back(p);

p->nodenum=node;

while(!q.empty())

{

p=q.front();

q.pop_front();

if(p->LeftNode)

{

if(p->LeftNode->leavenode)

{//如果该节点的左节点是子节点,则将nodenum赋0

p->LeftNode->nodenum=0;

}

else

{//否则将该节点赋一个node值,该值表示此决策点的顺序

node++;

p->LeftNode->nodenum=node;

q.push_back(p->LeftNode);

}

}

if(p->RightNode)

{

if(p->RightNode->leavenode)//

{//如果该节点的右节点是子节点,则将nodenum赋0

p->RightNode->nodenum=0;

}

else

{//否则将该节点赋一个node值,该值表示此决策点的顺序

node++;

p->RightNode->nodenum=node;

q.push_back(p->RightNode);

}

}

}

}

遍历结束后,每一个决策点数目可以确定一个树,我们就可以根据树的决策点数对训练样本和测试样本的误差进行统计,怎样根据决策点数确定树的结构?可以将树的前序遍历进行改进,对于t个决策点,节点为0或大于t的都是叶子节点,一旦确定叶子节点,树的结构就清楚了,下图为重新赋值后的树,在该图中,如当有3个决策点时,2的子节点和3的子节点都是叶子节点,当用改进的前序遍历便立时会输出有3个决策点:(1,2,3);4个叶子节点(4,5,0,6)的子树:

图2给树的节点重新赋值

不同决策点可对应不同子树,通过前序遍历可以将叶子节点中的错误样本统计出来计算该树情况下错误样本的个数,然后再用测试样本遍历树,统计测试样本再改树下错误样本个数最后得出结果集如下:

图3 不同决策点时建树误差与测试误差

通过比较可知当树有8和9个决策点时,测试误差最小,我们取8,因为此时树比9个决策点简单,我们取含有8个决策点为最小误分树。最小误分树结构如下:

图4 最小误分树

上图中最小误分树非叶节点中的两个值,第一个表示决策点表示,第二个表示选择的属性的代码,叶子节点中两数表示每一类的数目。

我们定义最优剪枝的方法是在剪枝序列中含有误差在最小误差树的一个标准差之内的最小树,算出的最小误差率被砍做一个带有标准差等于

的随机变量的观测值,其中Emin对最小误差树的错误率,Nval是验证集的个数:Emin=5.41%,Nval=148,所以到当树有4个决策点时,为最优剪枝。

图5 最优剪枝树

cart算法 java_CART算法原理及实现相关推荐

  1. cart算法 java_CART算法学习及实现

    CART算法学习及实现 作者:大卡卡   撰写时间:2011.9.29 1.算法介绍 分类回归树算法:CART(Classification And Regression Tree)算法采用一种二分递 ...

  2. DL之CNN:卷积神经网络算法简介之原理简介——CNN网络的3D可视化(LeNet-5为例可视化)

    DL之CNN:卷积神经网络算法简介之原理简介--CNN网络的3D可视化(LeNet-5为例可视化) CNN网络的3D可视化 3D可视化地址:http://scs.ryerson.ca/~aharley ...

  3. DL之DNN之BP:神经网络算法简介之BP算法/GD算法之不需要额外任何文字,只需要八张图讲清楚BP类神经网络的工作原理

    DL之DNN之BP:神经网络算法简介之BP算法/GD算法之不需要额外任何文字,只需要八张图讲清楚BP类神经网络的工作原理 目录 BP类神经网络理解 1.信号正向传播FP 2.误差反向传播BP+GD B ...

  4. Algorithm:C++语言实现之SimHash和倒排索引算法相关(抽屉原理、倒排索、建立查找树、处理Hash冲突、Hash查找)

    Algorithm:C++语言实现之SimHash和倒排索引算法相关(抽屉原理.倒排索.建立查找树.处理Hash冲突.Hash查找) 目录 一.SimHash算法 1.SimHash算法五个步骤 2. ...

  5. DL之CNN:卷积神经网络算法简介之原理简介(步幅/填充/特征图)、七大层级结构(动态图详解卷积/池化+方块法理解卷积运算)、CNN各层作用及其可视化等之详细攻略

    DL之CNN:卷积神经网络算法简介之原理简介(步幅/填充/特征图).七大层级结构(动态图详解卷积/池化+方块法理解卷积运算).CNN各层作用及其可视化等之详细攻略 目录 CNN 的层级结构及相关概念 ...

  6. DL之LSTM:LSTM算法论文简介(原理、关键步骤、RNN/LSTM/GRU比较、单层和多层的LSTM)、案例应用之详细攻略

    DL之LSTM:LSTM算法论文简介(原理.关键步骤.RNN/LSTM/GRU比较.单层和多层的LSTM).案例应用之详细攻略 目录 LSTM算法简介 1.LSTM算法论文 1.1.LSTM算法相关论 ...

  7. 操作系统:基于页面置换算法的缓存原理详解(下)

    概述: 在上一篇<操作系统:基于页面置换算法的缓存原理详解(上)>中,我们主要阐述了FIFO.LRU和Clock页面置换算法.接着上一篇说到的,本文也有三个核心算法要讲解.分别是LFU(L ...

  8. 数据结构与算法--B树原理及实现

    B树 前几篇文中讨论的数据结构我们都是假设所有的数据都存储在计算机的主存中.可说总要那么海量的数据需要通过个中数据结构去存储,我们不可能有这么多内存区存放这些数据.那么意味着我们需要将他们放磁盘.所以 ...

  9. 数据结构与算法--二叉查找树实现原理

    二叉查找树 二叉树的一个重要应用就是他在查询中的使用,假设书中每个节点存储一项数据.在我们的案例中,任意复杂的项在java中都容易处理,但为了简单还是假设都是整数.还假设他们都是不重复的整数,使二叉树 ...

最新文章

  1. 收集的一些主流网站的GA代码
  2. Linux中如何杀掉僵尸进程
  3. springboot:实现分页查询,以及翻页功能
  4. 科达南沙电子警察“扩编”
  5. javascript函数上的prototype属性的理解
  6. matlab利用霍夫,基于matlab的霍夫变换
  7. 深入理解Spring异常处理
  8. QTP:General Error while saving the test 的解决方法
  9. DBA 1.0与DBA眼中的DBA 2.0时代
  10. php获取ip归属地
  11. Oracle查询上周日期sql,Oracle 获取上周一到周末日期的查询sql语句
  12. 跨专业考研渣硕是如何拿到BAT、TWH等研发offer的!
  13. Xmind 免费安装使用教程
  14. 项目初始化报 404 Not Found - GET https://registry.npmjs.org(转)
  15. 『幽默』网络经典语录1600条~~(可累死我了)
  16. Oracle: SQL精妙SQL语句讲解(常用sql) .
  17. hybird (混合开发 - 视频详解) - 王云飞 - 小四
  18. C#使用iTextSharp合并多个PDF
  19. 提效120%!优维科技助力德邦快递实现自动化运维
  20. EPLAN 软件平台中的词“点“大全

热门文章

  1. word密码破解工具
  2. Linux | 人生苦短,我用Vim【最受欢迎的编辑器】
  3. wifi室内定位讲解——K邻近法
  4. 等保测评证书是由什么部门发的?申请需要满足什么条件?
  5. adreno性能天梯图_深度学习之GPU显卡性能天梯图
  6. Formail邮件导出方法,邮件导出fox格式文件,邮件存档
  7. python三大器之一——装饰器详解
  8. Python学习之旅(核心编程基础篇003运算符)
  9. Win10家庭版Hyper-V出坑(完美卸载,冲突解决以及Device Guard问题)
  10. 冯唐:我给20、30岁IT职场年轻人的建议