資料探勘:id3 演算法
阿新 • • 發佈:2019-01-26
1 簡述
1.1
id3是一種基於決策樹的分類演算法,由J.Ross Quinlan
在1986年開發。id3根據資訊增益,運用自頂向下的貪心策略
建立決策樹。資訊增益用於度量某個屬性對樣本集合分類的好壞程度。
由於採用了資訊增益,id3演算法建立的決策樹規模比較小,
查詢速度快。id3演算法的改進是C4.5演算法,C4.5演算法可以
處理連續資料,採用資訊增益率,而不是資訊增益。
理解資訊增益,需要先看一下資訊熵。
1.2 資訊熵
資訊熵是隨機變數的期望。度量資訊的不確定程度。
資訊的熵越大,資訊就越不容易搞清楚。處理資訊就是
為了把資訊搞清楚,就是熵減少的過程。
Entropy(X) = -Sum(p(xi) * log(p(xi))) {i: 0 <= i <= n}
p(x)是概率密度函式;對數是以2為底;
1.3 資訊增益
用於度量屬性A降低樣本集合X熵的貢獻大小。資訊增益
越大,越適於對X分類。
Gain(A, X) = Entropy(X) - Sum(|Xv| / |X| * Entropy(Xv)) {v: A的所有可能值}
Xv表示A中所有為v的值;|Xv|表示A中所有為v的值的數量;
2 id3演算法流程
輸入:樣本集合S,屬性集合A
輸出:id3決策樹。
1) 若所有種類的屬性都處理完畢,返回;否則執行2)
2)計算出資訊增益最大屬性a,把該屬性作為一個節點。
如果僅憑屬性a就可以對樣本分類,則返回;否則執行3)
3)對屬性a的每個可能的取值v,執行一下操作:
i. 將所有屬性a的值是v的樣本作為S的一個子集Sv;
ii. 生成屬性集合AT=A-{a};
iii.以樣本集合Sv和屬性集合AT為輸入,遞迴執行id3演算法;
3 一個的例子
3.1
這個例子來源於Quinlan的論文。
假設,有種戶外活動。該活動能否正常進行與各種天氣因素有關。
不同的天氣因素組合會產生兩種後果,也就是分成2類:能進行活動或不能。
我們用P表示該活動可以進行,N表示該活動無法進行。
下表描述樣本集合是不同天氣因素對該活動的影響。
Attribute class
outlook temperature humidity windy
---------------------------------------------------------
sunny hot high false N
sunny hot high true N
overcast hot high false P
rain mild high false P
rain cool normal false P
rain cool normal true N
overcast cool normal true P
sunn y mild high false N
sunny cool normal false P
rain mild normal false P
sunny mild normal true P
overcast mild high true P
overcast hot normal false P
rain mild high true N
3.2
該活動無法進行的概率是:5/14
該活動可以進行的概率是:9/14
因此樣本集合的資訊熵是:-5/14log(5/14) - 9/14log(9/14) = 0.940
3.3
接下來我們再看屬性outlook資訊熵的計算:
outlook為sunny時,
該活動無法進行的概率是:3/5
該活動可以進行的概率是:2/5
因此sunny的資訊熵是:-3/5log(3/5) - 2/5log(2/5) = 0.971
同理可以計算outlook屬性取其他值時候的資訊熵:
outlook為overcast時的資訊熵:0
outlook為rain時的資訊熵:0.971
屬性outlook的資訊增益:gain(outlook) = 0.940 - (5/14*0.971 + 4/14*0 + 5/14*0.971) = 0.246
相似的方法可以計算其他屬性的資訊增益:
gain(temperature) = 0.029
gain(humidity) = 0.151
gain(windy) = 0.048
資訊增益最大的屬性是outlook。
3.4
根據outlook把樣本分成3個子集,然後把這3個子集和餘下的屬性
作為輸入遞迴執行演算法。
4 程式碼演示
4.1
程式碼說明:
程式碼只是演示上一節的例子,寫的比較倉促,沒有經過仔細的設計和編碼,
只是在fedora 16上做了初步的測試,所以有一些錯誤和不適當的地方。
4.2
編譯:
g++ -g -W -Wall -Wextra -o mytest main.cpp id3.cpp
4.3
執行:
./mytest
4.4
id3.h:
================================================
// 2012年 07月 12日 星期四 15:07:10 CST
// author: 李小丹(Li Shao Dan) 字 殊恆(shuheng)
// K.I.S.S
// S.P.O.T
#ifndef ID3_H
#define ID3_H
#include <list>
#include <map>
#include <utility>
// value and index: >= 0, and index 0 is classification
// value and index: not decision is -1
class id3_classify {
public:
id3_classify(int);
~id3_classify();
public:
int push_sample(const int *, int);
int classify();
int match(const int *);
void print_tree();
private:
typedef std::list<std::list<std::pair<int, int> > > sample_space_t;
struct tree_node {
int index;
int classification;
std::map<int, struct tree_node *> next;
sample_space_t unclassified;
};
private:
void clear(struct tree_node *);
int recur_classify(struct tree_node *, int);
int recur_match(const int *, struct tree_node *);
int max_gain(struct tree_node *);
double cal_entropy(const std::map<int, int> &, double);
int cal_max_gain(const sample_space_t &);
int cal_split(struct tree_node *, int);
void att_statistics(const sample_space_t &,
std::map<int, std::map<int, int> > &,
std::map<int, std::map<int, std::map<int, int> > > &,
std::map<int, int> &);
double cal_gain(std::map<int, int> &,
std::map<int, std::map<int, int> > &,
double, double);
int is_classfied(const sample_space_t &);
void dump_tree(struct tree_node *);
private:
sample_space_t unclassfied;
struct tree_node *root;
std::map<int, int> *attribute_values;
int dimension;
};
#endif
===================================================
id3.cpp:
==================================================
// 2012年 07月 16日 星期一 10:07:43 CST
// author: 李小丹(Li Shao Dan) 字 殊恆(shuheng)
// K.I.S.S
// S.P.O.T
#include <iostream>
#include <cmath>
#include <cassert>
#include "id3.h"
using namespace std;
id3_classify::id3_classify(int d)
:root(new struct tree_node), dimension(d)
{
root->index = -1;
root->classification = -1;
}
id3_classify::~id3_classify()
{
clear(root);
}
int id3_classify::push_sample(const int *vec, int c)
{
list<pair<int, int> > v;
for(int i = 0; i < dimension; ++i)
v.push_back(make_pair(i + 1, vec[i]));
v.push_front(make_pair(0, c));
root->unclassified.push_back(v);
return 0;
}
int id3_classify::classify()
{
return recur_classify(root, dimension);
}
int id3_classify::match(const int *v)
{
return recur_match(v, root);
}
void id3_classify::clear(struct tree_node *node)
{
unclassfied.clear();
std::map<int, struct tree_node *> &next = node->next;
for(std::map<int, struct tree_node *>::iterator pos
= next.begin(); pos != next.end(); ++pos)
clear(pos->second);
next.clear();
delete node;
}
int id3_classify::recur_classify(struct tree_node *node, int dim)
{
sample_space_t &unclassified = node->unclassified;
int cls;
if((cls = is_classfied(unclassified)) >= 0) {
node->index = -1;
node->classification = cls;
return 0;
}
int ret = max_gain(node);
unclassified.clear();
if(ret < 0) return 0;
map<int, struct tree_node *> &next = node->next;
for(map<int, struct tree_node *>::iterator pos
= next.begin(); pos != next.end(); ++pos)
recur_classify(pos->second, dim - 1);
return 0;
}
int id3_classify::is_classfied(const sample_space_t &ss)
{
const list<pair<int, int> > &f = ss.front();
if(f.size() == 1)
return f.front().second;
int cls;
for(list<pair<int, int> >::const_iterator p
= f.begin(); p != f.end(); ++p) {
if(!p->first) {
cls = p->second;
break;
}
}
for(sample_space_t::const_iterator s
= ss.begin(); s != ss.end(); ++s) {
const list<pair<int, int> > &v = *s;
for(list<pair<int, int> >::const_iterator vp
= v.begin(); vp != v.end(); ++vp) {
if(!vp->first) {
if(cls != vp->second)
return -1;
else
break;
}
}
}
return cls;
}
int id3_classify::max_gain(struct tree_node *node)
{
// index of max attribute gain
int mai = cal_max_gain(node->unclassified);
assert(mai >= 0);
node->index = mai;
cal_split(node, mai);
return 0;
}
int id3_classify::cal_max_gain(const sample_space_t &ss)
{
map<int, map<int, int> >att_val;
map<int, map<int, map<int, int> > >val_cls;
map<int, int> cls;
att_statistics(ss, att_val, val_cls, cls);
double s = (double)ss.size();
double entropy = cal_entropy(cls, s);
double mag = -1; // max information gain
int mai = -1; // index of max information gain
for(map<int, map<int, int> >::iterator p
= att_val.begin(); p != att_val.end(); ++p) {
double g;
if((g = cal_gain(p->second, val_cls[p->first],
s, entropy)) > mag) {
mag = g;
mai = p->first;
}
}
if(!att_val.size() && !val_cls.size() && cls.size())
return 0;
return mai;
}
void id3_classify::att_statistics(const sample_space_t &ss,
map<int, map<int, int> > &att_val,
map<int, map<int, map<int, int> > > &val_cls,
map<int, int> &cls)
{
for(sample_space_t::const_iterator spl = ss.begin();
spl != ss.end(); ++spl) {
const list<pair<int, int> > &v = *spl;
int c;
for(list<pair<int, int> >::const_iterator vp
= v.begin(); vp != v.end(); ++vp) {
if(!vp->first) {
c = vp->second;
break;
}
}
++cls[c];
for(list<pair<int, int> >::const_iterator vp
= v.begin(); vp != v.end(); ++vp) {
if(vp->first) {
++att_val[vp->first][vp->second];
++val_cls[vp->first][vp->second][c];
}
}
}
}
double id3_classify::cal_entropy(const map<int, int> &att, double s)
{
double entropy = 0;
for(map<int, int>::const_iterator pos = att.begin();
pos != att.end(); ++pos) {
double tmp = pos->second / s;
entropy += tmp * log2(tmp);
}
return -entropy;
}
double id3_classify::cal_gain(map<int, int> &att_val,
map<int, map<int, int> > &val_cls,
double s, double entropy)
{
double gain = entropy;
for(map<int, int>::const_iterator att = att_val.begin();
att != att_val.end(); ++att) {
double r = att->second / s;
double e = cal_entropy(val_cls[att->first], att->second);
gain -= r * e;
}
return gain;
}
int id3_classify::cal_split(struct tree_node *node, int idx)
{
map<int, struct tree_node *> &next = node->next;
sample_space_t &unclassified = node->unclassified;
for(sample_space_t::iterator sp = unclassified.begin();
sp != unclassified.end(); ++sp) {
list<pair<int, int> > &v = *sp;
for(list<pair<int, int> >::iterator vp = v.begin();
vp != v.end(); ++vp) {
if(vp->first == idx) {
struct tree_node *tmp;
if(!(tmp = next[vp->second])) {
tmp = new struct tree_node;
tmp->index = -1;
tmp->classification = -1;
next[vp->second] = tmp;
}
v.erase(vp);
tmp->unclassified.push_back(v);
break;
}
}
}
return 0;
}
int id3_classify::recur_match(const int *v, struct tree_node *node)
{
if(node->index < 0)
return node->classification;
map<int, struct tree_node *>::iterator p;
map<int, struct tree_node *> &next = node->next;
if((p = next.find(v[node->index-1])) == next.end())
return -1;
return recur_match(v, p->second);
}
void id3_classify::print_tree()
{
return dump_tree(root);
}
void id3_classify::dump_tree(struct tree_node *node)
{
cout << "I: " << node->index << endl;
cout << "C: " << node->classification << endl;
cout << "N: " << node->next.size() << endl;
cout << "+++++++++++++++++++++++\n";
map<int, struct tree_node *> &next = node->next;
for(map<int, struct tree_node *>::iterator p
= next.begin(); p != next.end(); ++p) {
dump_tree(p->second);
}
}
====================================================
main.cpp:
===================================================
// 2012年 07月 18日 星期三 13:59:10 CST
// author: 李小丹(Li Shao Dan) 字 殊恆(shuheng)
// K.I.S.S
// S.P.O.T
#include <iostream>
#include "id3.h"
using namespace std;
int main()
{
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}};
id3_classify cls(4);
cls.push_sample((int *)&samples[0], 0);
cls.push_sample((int *)&samples[1], 0);
cls.push_sample((int *)&samples[2], 1);
cls.push_sample((int *)&samples[3], 1);
cls.push_sample((int *)&samples[4], 1);
cls.push_sample((int *)&samples[5], 0);
cls.push_sample((int *)&samples[6], 1);
cls.push_sample((int *)&samples[7], 0);
cls.push_sample((int *)&samples[8], 1);
cls.push_sample((int *)&samples[9], 1);
cls.push_sample((int *)&samples[10], 1);
cls.push_sample((int *)&samples[11], 1);
cls.push_sample((int *)&samples[12], 1);
cls.push_sample((int *)&samples[13], 0);
cls.classify();
cls.print_tree();
cout << "===============================\n";
for(int i = 0; i < 14; ++i)
cout << cls.match((int *)&samples[i]) << endl;
return 0;
}
================================================