决策树之ID3算法

今天,我来讲解的是决策树。对于决策树来说,主要有两种算法:ID3算法C4.5算法。C4.5算法是

对ID3算法的改进。今天主要先讲ID3算法,之后会讲C4.5算法随机森林等。

 

Contents

 

     1. 决策树的基本认识

     2. ID3算法介绍

     3. 信息熵与信息增益

     4. ID3算法的C++实现

 

 

1. 决策树的基本认识

 

   决策树是一种依托决策而建立起来的一种树。在机器学习中,决策树是一种预测模型,代表的是一种对

   象属性与对象值之间的一种映射关系,每一个节点代表某个对象,树中的每一个分叉路径代表某个可能

   的属性值,而每一个叶子节点则对应从根节点到该叶子节点所经历的路径所表示的对象的值。决策树仅

   有单一输出,如果有多个输出,可以分别建立独立的决策树以处理不同的输出。接下来讲解ID3算法。

 

 

2. ID3算法介绍

 

   ID3算法是决策树的一种,它是基于奥卡姆剃刀原理的,即用尽量用较少的东西做更多的事。ID3算法

   即Iterative Dichotomiser 3迭代二叉树3代,是Ross Quinlan发明的一种决策树算法,这个

   算法的基础就是上面提到的奥卡姆剃刀原理,越是小型的决策树越优于大的决策树,尽管如此,也不总

   是生成最小的树型结构,而是一个启发式算法。

 

   在信息论中,期望信息越小,那么信息增益就越大,从而纯度就越高。ID3算法的核心思想就是以信息

   增益来度量属性的选择,选择分裂后信息增益最大的属性进行分裂。该算法采用自顶向下的贪婪搜索遍

   历可能的决策空间。

 

 

3. 信息熵与信息增益

 

   在信息增益中,重要性的衡量标准就是看特征能够为分类系统带来多少信息,带来的信息越多,该特征越

   重要。在认识信息增益之前,先来看看信息熵的定义

 

   这个概念最早起源于物理学,在物理学中是用来度量一个热力学系统的无序程度,而在信息学里面,熵

   是对不确定性的度量。在1948年,香农引入了信息熵,将其定义为离散随机事件出现的概率,一个系统越

   是有序,信息熵就越低,反之一个系统越是混乱,它的信息熵就越高。所以信息熵可以被认为是系统有序

   化程度的一个度量。

 

   假如一个随机变量决策树之ID3算法的取值为决策树之ID3算法,每一种取到的概率分别是决策树之ID3算法,那么

   决策树之ID3算法的熵定义为

 

             决策树之ID3算法

 

   意思是一个变量的变化情况可能越多,那么它携带的信息量就越大。

 

   对于分类系统来说,类别决策树之ID3算法是变量,它的取值是决策树之ID3算法,而每一个类别出现的概率分别是

 

             决策树之ID3算法

 

   而这里的决策树之ID3算法就是类别的总数,此时分类系统的熵就可以表示为

 

             决策树之ID3算法

 

   以上就是信息熵的定义,接下来介绍信息增益

 

   信息增益是针对一个一个特征而言的,就是看一个特征决策树之ID3算法,系统有它和没有它时的信息量各是多少,两者

   的差值就是这个特征给系统带来的信息量,即信息增益

 

   接下来以天气预报的例子来说明。下面是描述天气数据表,学习目标是play或者not play

 

   决策树之ID3算法

 

   可以看出,一共14个样例,包括9个正例和5个负例。那么当前信息的熵计算如下

 

   决策树之ID3算法

 

   在决策树分类问题中,信息增益就是决策树在进行属性选择划分前和划分后信息的差值。假设利用

   属性Outlook来分类,那么如下图

 

   决策树之ID3算法

 

      划分后,数据被分为三部分了,那么各个分支的信息熵计算如下

 

       决策树之ID3算法

 

       那么划分后的信息熵为

 

        决策树之ID3算法

 

        决策树之ID3算法代表在特征属性决策树之ID3算法的条件下样本的条件熵。那么最终得到特征属性决策树之ID3算法带来的信息增益为

 

        决策树之ID3算法

 

   信息增益的计算公式如下

 

   决策树之ID3算法

 

   其中决策树之ID3算法为全部样本集合,决策树之ID3算法是属性决策树之ID3算法所有取值的集合,决策树之ID3算法决策树之ID3算法的其中一个属性值,决策树之ID3算法决策树之ID3算法中属性决策树之ID3算法

   值为决策树之ID3算法的样例集合,决策树之ID3算法决策树之ID3算法中所含样例数。

 

   在决策树的每一个非叶子结点划分之前,先计算每一个属性所带来的信息增益,选择最大信息增益的属性来划

   分,因为信息增益越大,区分样本的能力就越强,越具有代表性,很显然这是一种自顶向下的贪心策略。以上

   就是ID3算法的核心思想。

 

 

4. ID3算法的C++实现

 

   接下来开始用C++实现ID3算法,包括以下文件

 

   决策树之ID3算法

 

ID3.h

[cpp] view plain copy
  1. #ifndef _ID3_H_  
  2. #define _ID3_H_  
  3.    
  4. #include <utility>  
  5. #include <list>  
  6. #include <map>  
  7.    
  8. #define Type int   //样本数据类型  
  9.    
  10. #define   Map1        std::map< int, Type >    //定义一维map  
  11. #define   Map2        std::map< int, Map1 >    //定义二维map  
  12. #define   Map3        std::map< int, Map2 >    //定义三维map  
  13. #define   Pair        std::pair<int, Type>  
  14. #define   List        std::list< Pair >        //一维list  
  15. #define   SampleSpace std::list< List >        //二维list 用于存放样本数据  
  16. #define   Child       std::map< int, Node* >   //定义后继节点集合  
  17. #define   CI          const_iterator  
  18.    
  19. /* 
  20.  *   在ID3算法中,用二维链表存放样本,结构为list< list< pair<int, int> > >,简记为SampleSpace,取名样本空间 
  21.  *   样本数据从根节点开始往下遍历。每一个节点的定义如下结构体 
  22.  */  
  23.    
  24. struct Node  
  25. {  
  26.     int index;                    //当前节点样本最大增益对应第index个属性,根据这个进行分类的  
  27.     int type;                     //当前节点的类型  
  28.     Child next;                   //当前节点的后继节点集合  
  29.     SampleSpace sample;           //未分类的样本集合  
  30. };  
  31.    
  32. class ID3{  
  33.    
  34. public:  
  35.    
  36.     ID3(int );      
  37.     ~ID3();  
  38.    
  39.     void PushData(const Type*, const Type);   //将样本数据Push给二维链表  
  40.     void Build();                             //构建决策树  
  41.     int  Match(const Type*);                  //根据新的样本预测结果  
  42.     void Print();                             //打印决策树的节点的值  
  43.    
  44. private:  
  45.    
  46.     void   _clear(Node*);  
  47.     void   _build(Node*, int);  
  48.     int    _match(const int*, Node*);  
  49.     void   _work(Node*);  
  50.     double _entropy(const Map1&, double);  
  51.     int    _get_max_gain(const SampleSpace&);  
  52.     void   _split(Node*, int);  
  53.     void   _get_data(const SampleSpace&, Map1&, Map2&, Map3&);  
  54.     double _info_gain(Map1&, Map2&, doubledouble);  
  55.     int    _same_class(const SampleSpace&);  
  56.     void   _print(Node*);  
  57.    
  58. private:  
  59.    
  60.     int dimension;  
  61.     Node *root;  
  62. };  
  63.    
  64. #endif // _ID3_H_  


ID3.cpp

[cpp] view plain copy
  1. #include <iostream>  
  2. #include <cassert>  
  3. #include <cmath>  
  4.    
  5. #include "ID3.h"  
  6.    
  7. using namespace std;  
  8.    
  9. //初始化ID3的数据成员  
  10. ID3::ID3(int dimension)  
  11. {  
  12.     this->dimension = dimension;  
  13.    
  14.     root = new Node();  
  15.     root->index = -1;  
  16.     root->type = -1;  
  17.     root->next.clear();  
  18.     root->sample.clear();  
  19. }  
  20.    
  21. //清空整个决策树  
  22. ID3::~ID3()  
  23. {  
  24.     this->dimension = 0;  
  25.     _clear(root);  
  26. }  
  27.    
  28. //x为dimension维的属性向量,y为向量x对应的值  
  29. void ID3::PushData(const Type *x, const Type y)  
  30. {  
  31.     List single;  
  32.     single.clear();  
  33.     for(int i = 0; i < dimension; i++)  
  34.         single.push_back(make_pair(i + 1, x[i]));  
  35.     single.push_back(make_pair(0, y));  
  36.     root->sample.push_back(single);  
  37. }  
  38.    
  39. void ID3::_clear(Node *node)  
  40. {  
  41.     Child &next = node->next;  
  42.     Child::iterator it;  
  43.     for(it = next.begin(); it != next.end(); it++)  
  44.         _clear(it->second);  
  45.     next.clear();  
  46.     delete node;  
  47. }  
  48.    
  49. void ID3::Build()  
  50. {  
  51.     _build(root, dimension);  
  52. }  
  53.    
  54. void ID3::_build(Node *node, int dimension)  
  55. {  
  56.     //获取当前节点未分类的样本数据  
  57.     SampleSpace &sample = node->sample;  
  58.    
  59.     //判断当前所有样本是否是同一类,如果不是则返回-1  
  60.     int y = _same_class(sample);  
  61.    
  62.     //如果所有样本是属于同一类  
  63.     if(y >= 0)  
  64.     {  
  65.         node->index = -1;  
  66.         node->type = y;  
  67.         return;  
  68.     }  
  69.    
  70.     //在_max_gain()函数中计算出当前节点的最大增益对应的属性,并根据这个属性对数据进行划分  
  71.     _work(node);  
  72.    
  73.     //Split完成后清空当前节点的所有数据,以免占用太多内存  
  74.     sample.clear();  
  75.    
  76.     Child &next = node->next;  
  77.     for(Child::iterator it = next.begin(); it != next.end(); it++)  
  78.         _build(it->second, dimension - 1);  
  79. }  
  80.    
  81. //判断当前所有样本是否是同一类,如果不是则返回-1  
  82. int ID3::_same_class(const SampleSpace &ss)  
  83. {  
  84.     //取出当前样本数据的一个Sample  
  85.     const List &f = ss.front();  
  86.    
  87.     //如果没有x属性,而只有y,直接返回y  
  88.     if(f.size() == 1)  
  89.         return f.front().second;  
  90.    
  91.     Type y = 0;  
  92.     //取出第一个样本数据y的结果值  
  93.     for(List::CI it = f.begin(); it != f.end(); it++)  
  94.     {  
  95.         if(!it->first)  
  96.         {  
  97.             y = it->second;  
  98.             break;  
  99.         }  
  100.     }  
  101.    
  102.     //接下来进行判断,因为list是有序的,所以从前往后遍历,发现有一对不一样,则所有样本不是同一类  
  103.     for(SampleSpace::CI it = ss.begin(); it != ss.end(); it++)  
  104.     {  
  105.         const List &single = *it;  
  106.         for(List::CI i = single.begin(); i != single.end(); i++)  
  107.         {  
  108.             if(!i->first)  
  109.             {  
  110.                 if(y != i->second)  
  111.                     return -1;         //发现不是同一类则返回-1  
  112.                 else  
  113.                     break;  
  114.             }  
  115.         }  
  116.     }  
  117.     return y;     //比较完所有样本的输出值y后,发现是同一类,返回y值。  
  118. }  
  119.    
  120. void ID3::_work(Node *node)  
  121. {  
  122.     int mai = _get_max_gain(node->sample);  
  123.     assert(mai >= 0);  
  124.     node->index = mai;  
  125.     _split(node, mai);  
  126. }  
  127.    
  128. //获取最大的信息增益对应的属性  
  129. int ID3::_get_max_gain(const SampleSpace &ss)  
  130. {  
  131.     Map1 y;  
  132.     Map2 x;  
  133.     Map3 xy;  
  134.    
  135.     _get_data(ss, y, x, xy);  
  136.     double s = ss.size();  
  137.     double entropy = _entropy(y, s);   //计算熵值  
  138.    
  139.     int mai = -1;  
  140.     double mag = -1;  
  141.    
  142.     for(Map2::iterator it = x.begin(); it != x.end(); it++)  
  143.     {  
  144.         double g = _info_gain(it->second, xy[it->first], s, entropy);    //计算信息增益值  
  145.         if(g > mag)  
  146.         {  
  147.             mag = g;  
  148.             mai = it->first;  
  149.         }  
  150.     }  
  151.    
  152.     if(!x.size() && !xy.size() && y.size())   //如果只有y数据  
  153.         return 0;  
  154.     return mai;  
  155. }  
  156.    
  157. //获取数据,提取出所有样本的y值,x[]属性值,以及属性值和结果值xy。  
  158. void ID3::_get_data(const SampleSpace &ss, Map1 &y, Map2 &x, Map3 &xy)  
  159. {  
  160.     for(SampleSpace::CI it = ss.begin(); it != ss.end(); it++)  
  161.     {  
  162.     int c = 0;  
  163.         const List &v = *it;  
  164.         for(List::CI p = v.begin(); p != v.end(); p++)  
  165.         {  
  166.             if(!p->first)  
  167.             {  
  168.                 c = p->second;  
  169.                 break;  
  170.             }  
  171.         }  
  172.         ++y[c];  
  173.         for(List::CI p = v.begin(); p != v.end(); p++)  
  174.         {  
  175.             if(p->first)  
  176.             {  
  177.                 ++x[p->first][p->second];  
  178.                 ++xy[p->first][p->second][c];  
  179.             }  
  180.         }  
  181.     }  
  182. }  
  183.    
  184. //计算熵值  
  185. double ID3::_entropy(const Map1 &x, double s)  
  186. {  
  187.     double ans = 0;  
  188.     for(Map1::CI it = x.begin(); it != x.end(); it++)  
  189.     {  
  190.         double t = it->second / s;  
  191.         ans += t * log2(t);  
  192.     }  
  193.     return -ans;  
  194. }  
  195.    
  196. //计算信息增益  
  197. double ID3::_info_gain(Map1 &att_val, Map2 &val_cls, double s, double entropy)  
  198. {  
  199.     double gain = entropy;  
  200.     for(Map1::CI it = att_val.begin(); it != att_val.end(); it++)  
  201.     {  
  202.         double r = it->second / s;  
  203.         double e = _entropy(val_cls[it->first], it->second);  
  204.         gain -= r * e;  
  205.     }  
  206.     return gain;  
  207. }  
  208.    
  209. //对当前节点的sample进行划分  
  210. void ID3::_split(Node *node, int idx)  
  211. {  
  212.     Child &next = node->next;  
  213.     SampleSpace &sample = node->sample;  
  214.    
  215.     for(SampleSpace::iterator it = sample.begin(); it != sample.end(); it++)  
  216.     {  
  217.         List &v = *it;  
  218.         for(List::iterator p = v.begin(); p != v.end(); p++)  
  219.         {  
  220.             if(p->first == idx)  
  221.             {  
  222.                 Node *tmp = next[p->second];  
  223.                 if(!tmp)  
  224.                 {  
  225.                     tmp = new Node();  
  226.                     tmp->index = -1;  
  227.                     tmp->type = -1;  
  228.                     next[p->second] = tmp;  
  229.                 }  
  230.                 v.erase(p);  
  231.                 tmp->sample.push_back(v);  
  232.                 break;  
  233.             }  
  234.         }  
  235.     }  
  236. }  
  237.    
  238. int ID3::Match(const Type *x)  
  239. {  
  240.     return _match(x, root);  
  241. }    
  242.    
  243. int ID3::_match(const Type *v, Node *node)  
  244. {  
  245.     if(node->index < 0)  
  246.         return node->type;  
  247.    
  248.     Child &next = node->next;  
  249.     Child::iterator p = next.find(v[node->index - 1]);  
  250.     if(p == next.end())  
  251.         return -1;  
  252.    
  253.     return _match(v, p->second);  
  254. }  
  255.    
  256. void ID3::Print()  
  257. {  
  258.     _print(root);  
  259. }  
  260.    
  261. void ID3::_print(Node *node)  
  262. {  
  263.     cout << "Index    = " << node->index << endl;  
  264.     cout << "Type     = " << node->type << endl;  
  265.     cout << "NextSize = " << node->next.size() << endl;  
  266.     cout << endl;  
  267.    
  268.     Child &next = node->next;  
  269.     Child::iterator p;  
  270.     for(p = next.begin(); p != next.end(); ++p)  
  271.         _print(p->second);  
  272. }  

main.cpp

[cpp] view plain copy
  1. #include <iostream>  
  2. #include "ID3.h"  
  3.    
  4. using namespace std;  
  5.    
  6. enum outlook {SUNNY, OVERCAST, RAIN };  
  7. enum temp    {HOT,   MILD,     COOL };  
  8. enum hum     {HIGH,  NORMAL         };  
  9. enum windy   {WEAK,  STRONG         };  
  10.    
  11. int samples[14][4] =  
  12. {  
  13.     {SUNNY   ,       HOT ,      HIGH  ,       WEAK  },  
  14.     {SUNNY   ,       HOT ,      HIGH  ,       STRONG},  
  15.     {OVERCAST,       HOT ,      HIGH  ,       WEAK  },  
  16.     {RAIN    ,       MILD,      HIGH  ,       WEAK  },  
  17.     {RAIN    ,       COOL,      NORMAL,       WEAK  },  
  18.     {RAIN    ,       COOL,      NORMAL,       STRONG},  
  19.     {OVERCAST,       COOL,      NORMAL,       STRONG},  
  20.     {SUNNY   ,       MILD,      HIGH  ,       WEAK  },  
  21.     {SUNNY   ,       COOL,      NORMAL,       WEAK  },  
  22.     {RAIN    ,       MILD,      NORMAL,       WEAK  },  
  23.     {SUNNY   ,       MILD,      NORMAL,       STRONG},  
  24.     {OVERCAST,       MILD,      HIGH  ,       STRONG},  
  25.     {OVERCAST,       HOT ,      NORMAL,       WEAK  },  
  26.     {RAIN    ,       MILD,      HIGH  ,       STRONG}  
  27. };  
  28.    
  29. int main()  
  30. {  
  31.     ID3 Tree(4);  
  32.     Tree.PushData((int *)&samples[0], 0);  
  33.     Tree.PushData((int *)&samples[1], 0);  
  34.     Tree.PushData((int *)&samples[2], 1);  
  35.     Tree.PushData((int *)&samples[3], 1);  
  36.     Tree.PushData((int *)&samples[4], 1);  
  37.     Tree.PushData((int *)&samples[5], 0);  
  38.     Tree.PushData((int *)&samples[6], 1);  
  39.     Tree.PushData((int *)&samples[7], 0);  
  40.     Tree.PushData((int *)&samples[8], 1);  
  41.     Tree.PushData((int *)&samples[9], 1);  
  42.     Tree.PushData((int *)&samples[10], 1);  
  43.     Tree.PushData((int *)&samples[11], 1);  
  44.     Tree.PushData((int *)&samples[12], 1);  
  45.     Tree.PushData((int *)&samples[13], 0);  
  46.    
  47.     Tree.Build();  
  48.     Tree.Print();  
  49.     cout << endl;  
  50.     for(int i = 0; i < 14; ++i)  
  51.         cout << "predict value :    " <<Tree.Match( (int *)&samples[i] ) << endl;  
  52.     return 0;  
  53. }  

python实现方式:

#coding:utf-8
from math import log


#计算当前信息熵(shang)
def calcShannonEnt(dataSet):
numEntries = len(dataSet)
labelCounts = {}
for featVec  in dataSet:
currentLabel = featVec[-1]
if currentLabel not in labelCounts.keys():
labelCounts[currentLabel] = 0
labelCounts[currentLabel] += 1
shannonEnt = 0.0
for key in labelCounts:
prob = float(labelCounts[key])/numEntries
shannonEnt -= prob *log(prob,2)
return shannonEnt
#按照给定特征划分数据集
def splitDataSet(dataSet,axis,value):
retDataSet = []
for featVec in dataSet:
if featVec[axis] == value:
reducedFeatVec = featVec[:axis]
reducedFeatVec.extend(featVec[axis+1:])
retDataSet.append(reducedFeatVec)
return retDataSet
#选择最好的数据集划分方式
def chooseBestFeatureToSplit(dataSet):
numFeatures = len(dataSet[0]) - 1 #取出属性个数
baseEntropy = calcShannonEnt(dataSet)
bestInfoGain = 0.0
bestFeature = -1
for i in range(numFeatures):
featList = [example[i] for example in dataSet]  #取出属性对应的所有取值
uniqueVals = set(featList)   #set数据结构可以去除重复的值
newEntropy = 0.0
for value in uniqueVals:
subDataSet = splitDataSet(dataSet,i,value)
prob = len(subDataSet)/float(len(dataSet))
newEntropy += prob * calcShannonEnt(subDataSet)
infoGain = baseEntropy - newEntropy
if (infoGain > bestInfoGain):
bestInfoGain = infoGain
bestFeature = i
return bestFeature
#创建数据集
def createDataSet():
dataSet = [[1,1,'yes'],
[1,1,'yes'],
[1,0,'no'],
[0,1,'no'],
[0,1,'no']]
labels = ['no surfacing','flippers']
return dataSet,labels


myDat,labels = createDataSet()
print chooseBestFeatureToSplit(myDat)
'''shannonEnt=calcShannonEnt(myDat)
print shannonEnt
print splitDataSet(myDat,0,1)'''