PS:最近希望自己多拓宽一下知识面,所以看的较多,写的较少,就把看过的转载下来了
转自:http://blog.csdn.net/acdreamers/article/details/44661149
今天,我来讲解的是决策树。对于决策树来说,主要有两种算法: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年,香农引入了信息熵,将其定义为离散随机事件出现的概率,一个系统越
是有序,信息熵就越低,反之一个系统越是混乱,它的信息熵就越高。所以信息熵可以被认为是系统有序
化程度的一个度量。
假如一个随机变量
的取值为
,每一种取到的概率分别是
,那么
的熵定义为
![](http://images.cnitblog.com/blog/571227/201412/112112589313898.png)
意思是一个变量的变化情况可能越多,那么它携带的信息量就越大。
对于分类系统来说,类别
是变量,它的取值是
,而每一个类别出现的概率分别是
![](http://images.cnitblog.com/blog/571227/201412/112121283685834.png)
而这里的
就是类别的总数,此时分类系统的熵就可以表示为
![](http://images.cnitblog.com/blog/571227/201412/112123167755487.png)
以上就是信息熵的定义,接下来介绍信息增益。
信息增益是针对一个一个特征而言的,就是看一个特征
,系统有它和没有它时的信息量各是多少,两者
的差值就是这个特征给系统带来的信息量,即信息增益。
接下来以天气预报的例子来说明。下面是描述天气数据表,学习目标是play或者not play。
![](http://images.cnitblog.com/blog/571227/201412/121357107127323.png)
可以看出,一共14个样例,包括9个正例和5个负例。那么当前信息的熵计算如下
![](http://images.cnitblog.com/blog/571227/201412/121404563217970.png)
在决策树分类问题中,信息增益就是决策树在进行属性选择划分前和划分后信息的差值。假设利用
属性Outlook来分类,那么如下图
![](http://images.cnitblog.com/blog/571227/201412/121409376035137.png)
划分后,数据被分为三部分了,那么各个分支的信息熵计算如下
![](http://images.cnitblog.com/blog/571227/201412/121418263844215.png)
那么划分后的信息熵为
![](http://images.cnitblog.com/blog/571227/201412/121424414622037.png)
代表在特征属性
的条件下样本的条件熵。那么最终得到特征属性
带来的信息增益为
![](http://images.cnitblog.com/blog/571227/201412/121433352752920.png)
信息增益的计算公式如下
![](http://images.cnitblog.com/blog/571227/201412/121628452909401.png)
其中
为全部样本集合,
是属性
所有取值的集合,
是
的其中一个属性值,
是
中属性
的
值为
的样例集合,
为
中所含样例数。
在决策树的每一个非叶子结点划分之前,先计算每一个属性所带来的信息增益,选择最大信息增益的属性来划
分,因为信息增益越大,区分样本的能力就越强,越具有代表性,很显然这是一种自顶向下的贪心策略。以上
就是ID3算法的核心思想。
4. ID3算法的C++实现
接下来开始用C++实现ID3算法,包括以下文件
![](http://images.cnitblog.com/blog/571227/201412/121454212909217.png)
ID3.h
- #ifndef _ID3_H_
- #define _ID3_H_
-
- #include <utility>
- #include <list>
- #include <map>
-
- #define Type int //样本数据类型
-
- #define Map1 std::map< int, Type > //定义一维map
- #define Map2 std::map< int, Map1 > //定义二维map
- #define Map3 std::map< int, Map2 > //定义三维map
- #define Pair std::pair<int, Type>
- #define List std::list< Pair > //一维list
- #define SampleSpace std::list< List > //二维list 用于存放样本数据
- #define Child std::map< int, Node* > //定义后继节点集合
- #define CI const_iterator
-
-
-
-
-
-
- struct Node
- {
- int index;
- int type;
- Child next;
- SampleSpace sample;
- };
-
- class ID3{
-
- public:
-
- ID3(int );
- ~ID3();
-
- void PushData(const Type*, const Type);
- void Build();
- int Match(const Type*);
- void Print();
-
- private:
-
- void _clear(Node*);
- void _build(Node*, int);
- int _match(const int*, Node*);
- void _work(Node*);
- double _entropy(const Map1&, double);
- int _get_max_gain(const SampleSpace&);
- void _split(Node*, int);
- void _get_data(const SampleSpace&, Map1&, Map2&, Map3&);
- double _info_gain(Map1&, Map2&, double, double);
- int _same_class(const SampleSpace&);
- void _print(Node*);
-
- private:
-
- int dimension;
- Node *root;
- };
-
- #endif // _ID3_H_
ID3.cpp
- #include <iostream>
- #include <cassert>
- #include <cmath>
-
- #include "ID3.h"
-
- using namespace std;
-
-
- ID3::ID3(int dimension)
- {
- this->dimension = dimension;
-
- root = new Node();
- root->index = -1;
- root->type = -1;
- root->next.clear();
- root->sample.clear();
- }
-
-
- ID3::~ID3()
- {
- this->dimension = 0;
- _clear(root);
- }
-
-
- void ID3::PushData(const Type *x, const Type y)
- {
- List single;
- single.clear();
- for(int i = 0; i < dimension; i++)
- single.push_back(make_pair(i + 1, x[i]));
- single.push_back(make_pair(0, y));
- root->sample.push_back(single);
- }
-
- void ID3::_clear(Node *node)
- {
- Child &next = node->next;
- Child::iterator it;
- for(it = next.begin(); it != next.end(); it++)
- _clear(it->second);
- next.clear();
- delete node;
- }
-
- void ID3::Build()
- {
- _build(root, dimension);
- }
-
- void ID3::_build(Node *node, int dimension)
- {
-
- SampleSpace &sample = node->sample;
-
-
- int y = _same_class(sample);
-
-
- if(y >= 0)
- {
- node->index = -1;
- node->type = y;
- return;
- }
-
-
- _work(node);
-
-
- sample.clear();
-
- Child &next = node->next;
- for(Child::iterator it = next.begin(); it != next.end(); it++)
- _build(it->second, dimension - 1);
- }
-
-
- int ID3::_same_class(const SampleSpace &ss)
- {
-
- const List &f = ss.front();
-
-
- if(f.size() == 1)
- return f.front().second;
-
- Type y = 0;
-
- for(List::CI it = f.begin(); it != f.end(); it++)
- {
- if(!it->first)
- {
- y = it->second;
- break;
- }
- }
-
-
- for(SampleSpace::CI it = ss.begin(); it != ss.end(); it++)
- {
- const List &single = *it;
- for(List::CI i = single.begin(); i != single.end(); i++)
- {
- if(!i->first)
- {
- if(y != i->second)
- return -1;
- else
- break;
- }
- }
- }
- return y;
- }
-
- void ID3::_work(Node *node)
- {
- int mai = _get_max_gain(node->sample);
- assert(mai >= 0);
- node->index = mai;
- _split(node, mai);
- }
-
-
- int ID3::_get_max_gain(const SampleSpace &ss)
- {
- Map1 y;
- Map2 x;
- Map3 xy;
-
- _get_data(ss, y, x, xy);
- double s = ss.size();
- double entropy = _entropy(y, s);
-
- int mai = -1;
- double mag = -1;
-
- for(Map2::iterator it = x.begin(); it != x.end(); it++)
- {
- double g = _info_gain(it->second, xy[it->first], s, entropy);
- if(g > mag)
- {
- mag = g;
- mai = it->first;
- }
- }
-
- if(!x.size() && !xy.size() && y.size())
- return 0;
- return mai;
- }
-
-
- void ID3::_get_data(const SampleSpace &ss, Map1 &y, Map2 &x, Map3 &xy)
- {
- for(SampleSpace::CI it = ss.begin(); it != ss.end(); it++)
- {
- int c = 0;
- const List &v = *it;
- for(List::CI p = v.begin(); p != v.end(); p++)
- {
- if(!p->first)
- {
- c = p->second;
- break;
- }
- }
- ++y[c];
- for(List::CI p = v.begin(); p != v.end(); p++)
- {
- if(p->first)
- {
- ++x[p->first][p->second];
- ++xy[p->first][p->second][c];
- }
- }
- }
- }
-
-
- double ID3::_entropy(const Map1 &x, double s)
- {
- double ans = 0;
- for(Map1::CI it = x.begin(); it != x.end(); it++)
- {
- double t = it->second / s;
- ans += t * log2(t);
- }
- return -ans;
- }
-
-
- double ID3::_info_gain(Map1 &att_val, Map2 &val_cls, double s, double entropy)
- {
- double gain = entropy;
- for(Map1::CI it = att_val.begin(); it != att_val.end(); it++)
- {
- double r = it->second / s;
- double e = _entropy(val_cls[it->first], it->second);
- gain -= r * e;
- }
- return gain;
- }
-
-
- void ID3::_split(Node *node, int idx)
- {
- Child &next = node->next;
- SampleSpace &sample = node->sample;
-
- for(SampleSpace::iterator it = sample.begin(); it != sample.end(); it++)
- {
- List &v = *it;
- for(List::iterator p = v.begin(); p != v.end(); p++)
- {
- if(p->first == idx)
- {
- Node *tmp = next[p->second];
- if(!tmp)
- {
- tmp = new Node();
- tmp->index = -1;
- tmp->type = -1;
- next[p->second] = tmp;
- }
- v.erase(p);
- tmp->sample.push_back(v);
- break;
- }
- }
- }
- }
-
- int ID3::Match(const Type *x)
- {
- return _match(x, root);
- }
-
- int ID3::_match(const Type *v, Node *node)
- {
- if(node->index < 0)
- return node->type;
-
- Child &next = node->next;
- Child::iterator p = next.find(v[node->index - 1]);
- if(p == next.end())
- return -1;
-
- return _match(v, p->second);
- }
-
- void ID3::Print()
- {
- _print(root);
- }
-
- void ID3::_print(Node *node)
- {
- cout << "Index = " << node->index << endl;
- cout << "Type = " << node->type << endl;
- cout << "NextSize = " << node->next.size() << endl;
- cout << endl;
-
- Child &next = node->next;
- Child::iterator p;
- for(p = next.begin(); p != next.end(); ++p)
- _print(p->second);
- }
main.cpp
- #include <iostream>
- #include "ID3.h"
-
- using namespace std;
-
- enum outlook {SUNNY, OVERCAST, RAIN };
- enum temp {HOT, MILD, COOL };
- enum hum {HIGH, NORMAL };
- enum windy {WEAK, STRONG };
-
- int samples[14][4] =
- {
- {SUNNY , HOT , HIGH , WEAK },
- {SUNNY , HOT , HIGH , STRONG},
- {OVERCAST, HOT , HIGH , WEAK },
- {RAIN , MILD, HIGH , WEAK },
- {RAIN , COOL, NORMAL, WEAK },
- {RAIN , COOL, NORMAL, STRONG},
- {OVERCAST, COOL, NORMAL, STRONG},
- {SUNNY , MILD, HIGH , WEAK },
- {SUNNY , COOL, NORMAL, WEAK },
- {RAIN , MILD, NORMAL, WEAK },
- {SUNNY , MILD, NORMAL, STRONG},
- {OVERCAST, MILD, HIGH , STRONG},
- {OVERCAST, HOT , NORMAL, WEAK },
- {RAIN , MILD, HIGH , STRONG}
- };
-
- int main()
- {
- ID3 Tree(4);
- Tree.PushData((int *)&samples[0], 0);
- Tree.PushData((int *)&samples[1], 0);
- Tree.PushData((int *)&samples[2], 1);
- Tree.PushData((int *)&samples[3], 1);
- Tree.PushData((int *)&samples[4], 1);
- Tree.PushData((int *)&samples[5], 0);
- Tree.PushData((int *)&samples[6], 1);
- Tree.PushData((int *)&samples[7], 0);
- Tree.PushData((int *)&samples[8], 1);
- Tree.PushData((int *)&samples[9], 1);
- Tree.PushData((int *)&samples[10], 1);
- Tree.PushData((int *)&samples[11], 1);
- Tree.PushData((int *)&samples[12], 1);
- Tree.PushData((int *)&samples[13], 0);
-
- Tree.Build();
- Tree.Print();
- cout << endl;
- for(int i = 0; i < 14; ++i)
- cout << "predict value : " <<Tree.Match( (int *)&samples[i] ) << endl;
- return 0;
- }
Makefile
- Test: main.cpp ID3.h ID3.cpp
- g++ -o Test ID3.cpp main.cpp
-
- clean:
- rm Test