ID3算法 决策树 C++实现

人工智能课的实验。

数据结构:多叉树

这个实验我写了好久,开始的时候从数据的读入和表示入手,写到递归建树的部分时遇到了瓶颈,更新样例集和属性集的办法过于繁琐;

于是参考网上的代码后重新写,建立决策树类,把属性集、样例集作为数据成员加入类中,并设立访问数组,这样每次更新属性集、样例集时只是标记访问数组的对应元素即可,不必实际拷贝。

主函数:

 1 #include "Decision_tree.h"
 2 using namespace std;
 3 int main()
 4 {
 5     int num_attr,num_example;
 6     char filename[30];
 7     cout << "请输入训练集文件名:" << endl;
 8     cin >> filename;
 9     freopen(filename, "r", stdin);//从样例文件读入训练内容
10     cin >> num_attr >> num_example;//读入属性个数、例子个数
11     Decision_tree my_tree=Decision_tree(num_attr,num_example);
12     fclose(stdin);
13     freopen("CON", "r", stdin);//重定向标准输入到控制台
14     my_tree.display_attr();
15     cout << "决策树已建成,按深度优先遍历结果如下:" << endl;
16     my_tree.traverse();
17     do{
18         cout << "请输入测试数据,格式:属性1值 属性2值..." << endl;
19         Example test;
20         for (int i = 0; i < num_attr; i++)
21             cin >> test.values[i];
22         int result = my_tree.judge(test);
23         if (result == 1) cout << "分类结果为P" << endl;
24         else if (result == -1) cout << "分类结果为N" << endl;
25         else if (result == -2) cout << "无法根据已有样例集判断" << endl;
26         cout << "继续吗?(y/n)";
27         fflush(stdin);
28     } while (getchar() == ‘y‘);
29 }

属性结构体

struct Attribute//属性
{
    string name;
    int count;//属性值个数
    int number;//属性的秩
    string values[MAX_VAL];
};

样例结构体

struct Example//样例
{
    string values[MAX];
    int pn;
    Example(){ pn = 0; }//默认为未分类的
};

决策树的结点

typedef struct Node//树的结点
{
    Attribute attr;
    Node* children[MAX_VAL];
    int classification[MAX_VAL];
    Node(){}
}Node;

决策树类的实现

  1 class Decision_tree//决策树
  2 {
  3     Node *root;
  4     Example e[MAX];//样例全集
  5     Attribute a[MAX_ATTR];//属性全集
  6     int num_attr, num_example;
  7     int visited_exams[MAX];//样例集的访问情况
  8     int visited_attrs[MAX_ATTR];//属性集的访问情况
  9     Node* recursive_build_tree(int left_e[], int left_a[])//递归建树
 10     {
 11         double max = 0;
 12         int max_attr=-1;
 13         for (int i = 0;i<num_attr;i++)
 14         {//求信息增益最大的属性
 15             if (left_a[i]) continue;
 16             double temp = Gain(left_e, i);
 17             if (max<temp)
 18             {
 19                 max = temp;
 20                 max_attr = i;
 21             }
 22         }
 23         if (max_attr == -1) return NULL;//已没有可判的属性,返回空指针
 24         //cout << a[max_attr].name << endl;
 25         //以这个属性为结点,以各属性值为分支递归建树
 26         int p = 0, n = 0;
 27         Node *new_node=new Node();
 28         new_node->attr = a[max_attr];
 29         for (int i = 0; i<a[max_attr].count;i++)
 30         {//遍历这个属性的所有属性值
 31             for (int j = 0; j < num_example;j++)
 32             {//得到第i个属性值的正反例总数
 33                 if (left_e[j]) continue;
 34                 if (!e[j].values[max_attr].compare(a[max_attr].values[i]))
 35                 {//例子和属性都是循秩访问的,所以向量元素的顺序不能变
 36                     if (e[j].pn) p++;
 37                     else n++;
 38                 }
 39             }
 40             //cout << a[max_attr].values[i] << " ";
 41             //cout << p << " " << n << endl;
 42             if (p && !n)//全是正例,不再分
 43             {
 44                 //cout << "P" << endl;
 45                 new_node->classification[i] = 1;
 46                 new_node->children[i] = NULL;
 47             }
 48             else if (n && !p)//全是反例,不再分
 49             {
 50                 //cout << "N" << endl;
 51                 new_node->classification[i] = -1;
 52                 new_node->children[i] = NULL;
 53             }
 54             else if (!p && !n)//例子集已空
 55             {
 56                 //cout << "none" << endl;
 57                 new_node->classification[i] = -2;//表示未训练到这种分类,无法判断
 58                 new_node->children[i] = NULL;
 59             }
 60             else//例子集不空,且尚未能区分正反,更新访问情况,递归
 61             {
 62                 new_node->classification[i] = 0;
 63                 left_a[max_attr] = 1;//更新属性访问情况
 64                 int left_e_next[MAX];//下一轮的例子集(为便于回溯,不修改原例子集)
 65                 for (int k = 0; k < num_example; k++)
 66                     left_e_next[k] = left_e[k];
 67                 for (int j = 0; j < num_example; j++)
 68                 {
 69                     if (left_e[j]) continue;
 70                     if (!e[j].values[max_attr].compare(a[max_attr].values[i]))
 71                         left_e_next[j] = 0;//属性值匹配的例子,入选下一轮例子集
 72                     else left_e_next[j] = 1;//属性值不匹配,筛除
 73                 }
 74                 new_node->children[i] = recursive_build_tree(left_e_next, left_a);//递归
 75                 left_a[max_attr] = 0;//恢复属性访问情况
 76             }
 77             p = 0;
 78             n = 0;
 79         }
 80         return new_node;
 81     }
 82     double I(int p, int n)
 83     {
 84         double a = p / (p + (double)n);
 85         double b = n / (p + (double)n);
 86         if (a == 0 || b == 0) return 0;
 87         return -a*log(a) / log(2) - b*log(b) / log(2);
 88     }
 89     double Gain(int left_e[], int cur_attr)//计算信息增益
 90     {
 91         int sum_p=0, sum_n=0;
 92         int p[10] = { 0 }, n[10] = { 0 };
 93         for (int i = 0; i < num_example; i++)
 94         {//求样例集的p,n
 95             if (left_e[i]) continue;
 96             if (e[i].pn) sum_p++;
 97             else sum_n++;
 98         }
 99         if (!sum_p && !sum_n)
100         {
101             //cout << "no more examples!" << endl;
102             return -1;//样例集是空集
103         }
104
105         double sum_Ipn = I(sum_p, sum_n);
106         for (int i = 0; i < a[cur_attr].count; i++)
107         {//求第i个属性值的p,n
108             for (int j = 0; j < num_example; j++)
109             {
110                 if (left_e[j]) continue;
111                 if (!e[j].values[cur_attr].compare(a[cur_attr].values[i]))
112                     if (e[j].pn) p[i]++;
113                     else n[i]++;
114             }
115         }
116         double E = 0;
117         for (int i = 0; i < a[cur_attr].count; i++)//计算属性的期望
118             E += (p[i] + n[i])*I(p[i], n[i]);
119         E /= (sum_p + sum_n);
120         //cout << a[cur_attr].name <<sum_Ipn - E << endl;
121         return sum_Ipn - E;
122     }
123     void recursive_traverse(Node *current)//DFS递归遍历
124     {
125         if (current == NULL) return;
126         cout << current->attr.name << endl;
127         for (int i = 0; i < current->attr.count; i++)
128         {
129             cout << current->attr.values[i] << " " << current->classification[i] << endl;
130             recursive_traverse(current->children[i]);
131         }
132     }
133     int recursive_judge(Example exa, Node *current)
134     {
135         for (int i = 0; i < current->attr.count; i++)
136         {
137             if (!exa.values[current->attr.number].compare(current->attr.values[i]))
138             {
139                 if (current->children[i]==NULL) return current->classification[i];
140                 else return recursive_judge(exa, current->children[i]);
141             }
142         }
143         return 0;
144     }
145 public:
146     Decision_tree(int num1,int num2)
147     {
148
149         //通过读文件初始化
150         num_attr = num1;
151         num_example = num2;
152
153         for (int i = 0; i<num_attr; i++)
154         {
155             a[i].number = i;//属性的秩
156             cin>>a[i].name;//读入属性名
157             cin>>a[i].count;//读入此属性的属性值个数
158             for (int j = 0; j<a[i].count; j++)
159             {
160                 cin>>a[i].values[j];//读入各属性值
161             }
162         }
163
164         for (int i = 0; i<num_example; i++)
165         {
166             string temp;
167             for (int j = 0; j < num_attr; j++)
168             {
169                 cin>>e[i].values[j];
170             }
171             cin >> temp;
172             if (!temp.compare("P")) e[i].pn = 1;
173             else e[i].pn = 0;
174         }
175         //检查
176         /*for (int i = 0; i<num_attr; i++)
177         {
178             cout << a[i].name << endl;//读入属性名
179             for (int j = 0; j<a[i].count; j++)
180             {
181                 cout<<a[i].values[j]<<" ";//读入各属性值
182             }
183             cout << endl;
184         }
185         for (int i = 0; i<num_example; i++)
186         {
187             for (int j = 0; j < num_attr; j++)
188                 cout<<e[i].values[j]<<" ";
189             cout<<e[i].pn<<endl;
190
191         }
192         */
193         memset(visited_exams, 0, sizeof(visited_exams));
194         memset(visited_attrs, 0, sizeof(visited_attrs));
195         root = recursive_build_tree(visited_exams,visited_attrs);
196     }
197     void traverse()
198     {
199         recursive_traverse(root);
200     }
201     int judge(Example exa)//判断
202     {
203         int result=recursive_judge(exa,root);
204         return result;
205     }
206     void display_attr()//显示属性
207     {
208         cout << "There are " << num_attr << " attributes, they are" << endl;
209         for (int i = 0; i < num_attr; i++)
210         {
211             cout << "[" << a[i].name << "]" << endl;
212             for (int j = 0; j < a[i].count; j++)
213                 cout << a[i].values[j] << " ";
214             cout << endl;
215         }
216     }
217 };

Decision_tree

现在这个版本的代码用了10小时完成,去检查时被研究生贬得一文不值。。。也的确,现在我们写的实验题目面向的都是规模非常小的问题,自然体会不到自己的代码在大数据面前的劣势。不过我现在确实学得太少了,很多数据结构都没有动手实现过,算法也是。对C++也只能算入了门。俗话说“磨刀不误砍柴工”,“工欲善其事,必先利其器”,先把基础知识学好,多做基本练习,学到的数据结构和算法都动手实现一遍,这样遇到实际问题也好对应到合适的数据结构和算法。

另外,参照一本书学习好的代码风格和习惯也是很重要的,因为写代码的习惯是思维习惯的反映,而我现在还处于初学者阶段,按照一种典型的流派模仿,构建起自己的思维模式后再谈其他的。

忽然觉得自己学了快两年编程还这么水实在是不能忍,都怪大一时年少不懂事没好好学基础。。。

不过,“悟已往之不谏,知来者之可追”,有了方向,一步步走下去就好,不求优于别人,但一定要“优于过去的自己”。

时间: 2024-10-15 05:27:23

ID3算法 决策树 C++实现的相关文章

决策树---ID3算法(介绍及Python实现)

决策树---ID3算法   决策树: 以天气数据库的训练数据为例. Outlook Temperature Humidity Windy PlayGolf? sunny 85 85 FALSE no sunny 80 90 TRUE no overcast 83 86 FALSE yes rainy 70 96 FALSE yes rainy 68 80 FALSE yes rainy 65 70 TRUE no overcast 64 65 TRUE yes sunny 72 95 FALSE

决策树-预测隐形眼镜类型 (ID3算法,C4.5算法,CART算法,GINI指数,剪枝,随机森林)

1. 1.问题的引入 2.一个实例 3.基本概念 4.ID3 5.C4.5 6.CART 7.随机森林 2. 我们应该设计什么的算法,使得计算机对贷款申请人员的申请信息自动进行分类,以决定能否贷款? 一个女孩的母亲要给这个女孩介绍男朋友,于是有了下面的对话: 女儿:多大年纪了? 母亲:26. 女儿:长的帅不帅? 母亲:挺帅的. 女儿:收入高不? 母亲:不算很高,中等情况. 女儿:是公务员不? 母亲:是,在税务局上班呢. 女儿:那好,我去见见. 决策过程: 这个女孩的决策过程就是典型的分类树决策.

决策树ID3算法的java实现(基本试用所有的ID3)

已知:流感训练数据集,预定义两个类别: 求:用ID3算法建立流感的属性描述决策树 流感训练数据集 No. 头痛 肌肉痛 体温 患流感 1 是(1) 是(1) 正常(0) 否(0) 2 是(1) 是(1) 高(1) 是(1) 3 是(1) 是(1) 很高(2) 是(1) 4 否(0) 是(1) 正常(0) 否(0) 5 否(0) 否(0) 高(1) 否(0) 6 否(0) 是(1) 很高(2) 是(1) 7 是(1) 否(0) 高(1) 是(1) 原理分析: 在决策树的每一个非叶子结点划分之前,先

决策树ID3算法[分类算法]

ID3分类算法的编码实现 1 <?php 2 /* 3 *决策树ID3算法(分类算法的实现) 4 */ 5 6 /* 7 *把.txt中的内容读到数组中保存 8 *$filename:文件名称 9 */ 10 11 //-------------------------------------------------------------------- 12 function gerFileContent($filename) 13 { 14 $array = array(NULL); 15

决策树的基本ID3算法

一  ID3算法的大致思想 基本的ID3算法是通过自顶向下构造决策树来进行学习的.我们首先思考的是树的构造从哪里开始,这就涉及到选择属性进行树的构造了,那么怎样选择属性呢?为了解决这个问题,我们使用统计测试来确定每一个实例属性单独分类训练样例的能力,把分类能力最好的属性作为树根节点的测试.然后为根节点属性的每个可能值产生一个分支,并把训练样例排列到适当的分支之下.然后重复整个过程,用每个分支节点关联的训练样例来选取在该点被测试的最佳属性.这形成了对合格决策树的贪婪搜索,也就是算法从不回溯重新考虑

决策树 -- ID3算法小结

ID3算法(Iterative Dichotomiser 3 迭代二叉树3代),是一个由Ross Quinlan发明的用于决策树的算法:简单理论是越是小型的决策树越优于大的决策树. 算法归纳: 1.使用所有没有使用的属性并计算与之相关的样本熵值: 2.选取其中熵值最小的属性 3.生成包含该属性的节点 4.使用新的分支表继续前面步骤 ID3算法以信息论为基础,以信息熵和信息增益为衡量标准,从而实现对数据的归纳分类:所以归根结底,是为了从一堆数据中生成决策树而采取的一种归纳方式: 具体介绍: 1.信

决策树ID3算法的java实现

决策树的分类过程和人的决策过程比较相似,就是先挑“权重”最大的那个考虑,然后再往下细分.比如你去看医生,症状是流鼻涕,咳嗽等,那么医生就会根据你的流鼻涕这个权重最大的症状先认为你是感冒,接着再根据你咳嗽等症状细分你是否为病毒性感冒等等.决策树的过程其实也是基于极大似然估计.那么我们用一个什么标准来衡量某个特征是权重最大的呢,这里有信息增益和基尼系数两个.ID3算法采用的是信息增益这个量. 根据<统计学习方法>中的描述,G(D,A)表示数据集D在特征A的划分下的信息增益.具体公式: G(D,A)

数据挖掘之决策树ID3算法(C#实现)

决策树是一种非常经典的分类器,它的作用原理有点类似于我们玩的猜谜游戏.比如猜一个动物: 问:这个动物是陆生动物吗? 答:是的. 问:这个动物有鳃吗? 答:没有. 这样的两个问题顺序就有些颠倒,因为一般来说陆生动物是没有鳃的(记得应该是这样的,如有错误欢迎指正).所以玩这种游戏,提问的顺序很重要,争取每次都能够获得尽可能多的信息量. AllElectronics顾客数据库标记类的训练元组 RID age income student credit_rating Class: buys_comput

[转载]简单易学的机器学习算法-决策树之ID3算的

一.决策树分类算法概述 决策树算法是从数据的属性(或者特征)出发,以属性作为基础,划分不同的类.例如对于如下数据集 (数据集) 其中,第一列和第二列为属性(特征),最后一列为类别标签,1表示是,0表示否.决策树算法的思想是基于属性对数据分类,对于以上的数据我们可以得到以下的决策树模型 (决策树模型) 先是根据第一个属性将一部份数据区分开,再根据第二个属性将剩余的区分开. 实现决策树的算法有很多种,有ID3.C4.5和CART等算法.下面我们介绍ID3算法. 二.ID3算法的概述 ID3算法是由Q