ID3算法 决策树 C++实现
人工智能课的实验。
数据结构:多叉树
这个实验我写了好久,开始的时候从数据的读入和表示入手,写到递归建树的部分时遇到了瓶颈,更新样例集和属性集的办法过于繁琐;
于是参考网上的代码后重新写,建立决策树类,把属性集、样例集作为数据成员加入类中,并设立访问数组,这样每次更新属性集、样例集时只是标记访问数组的对应元素即可,不必实际拷贝。
主函数:
#include "Decision_tree.h"
using namespace std;
int main()
{
int num_attr,num_example;
char filename[];
cout << "请输入训练集文件名:" << endl;
cin >> filename;
freopen(filename, "r", stdin);//从样例文件读入训练内容
cin >> num_attr >> num_example;//读入属性个数、例子个数
Decision_tree my_tree=Decision_tree(num_attr,num_example);
fclose(stdin);
freopen("CON", "r", stdin);//重定向标准输入到控制台
my_tree.display_attr();
cout << "决策树已建成,按深度优先遍历结果如下:" << endl;
my_tree.traverse();
do{
cout << "请输入测试数据,格式:属性1值 属性2值..." << endl;
Example test;
for (int i = ; i < num_attr; i++)
cin >> test.values[i];
int result = my_tree.judge(test);
if (result == ) cout << "分类结果为P" << endl;
else if (result == -) cout << "分类结果为N" << endl;
else if (result == -) cout << "无法根据已有样例集判断" << endl;
cout << "继续吗?(y/n)";
fflush(stdin);
} while (getchar() == 'y');
}
属性结构体
struct Attribute//属性
{
string name;
int count;//属性值个数
int number;//属性的秩
string values[MAX_VAL];
};
样例结构体
struct Example//样例
{
string values[MAX];
int pn;
Example(){ pn = ; }//默认为未分类的
};
决策树的结点
typedef struct Node//树的结点
{
Attribute attr;
Node* children[MAX_VAL];
int classification[MAX_VAL];
Node(){}
}Node;
决策树类的实现
class Decision_tree//决策树
{
Node *root;
Example e[MAX];//样例全集
Attribute a[MAX_ATTR];//属性全集
int num_attr, num_example;
int visited_exams[MAX];//样例集的访问情况
int visited_attrs[MAX_ATTR];//属性集的访问情况
Node* recursive_build_tree(int left_e[], int left_a[])//递归建树
{
double max = ;
int max_attr=-;
for (int i = ;i<num_attr;i++)
{//求信息增益最大的属性
if (left_a[i]) continue;
double temp = Gain(left_e, i);
if (max<temp)
{
max = temp;
max_attr = i;
}
}
if (max_attr == -) return NULL;//已没有可判的属性,返回空指针
//cout << a[max_attr].name << endl;
//以这个属性为结点,以各属性值为分支递归建树
int p = , n = ;
Node *new_node=new Node();
new_node->attr = a[max_attr];
for (int i = ; i<a[max_attr].count;i++)
{//遍历这个属性的所有属性值
for (int j = ; j < num_example;j++)
{//得到第i个属性值的正反例总数
if (left_e[j]) continue;
if (!e[j].values[max_attr].compare(a[max_attr].values[i]))
{//例子和属性都是循秩访问的,所以向量元素的顺序不能变
if (e[j].pn) p++;
else n++;
}
}
//cout << a[max_attr].values[i] << " ";
//cout << p << " " << n << endl;
if (p && !n)//全是正例,不再分
{
//cout << "P" << endl;
new_node->classification[i] = ;
new_node->children[i] = NULL;
}
else if (n && !p)//全是反例,不再分
{
//cout << "N" << endl;
new_node->classification[i] = -;
new_node->children[i] = NULL;
}
else if (!p && !n)//例子集已空
{
//cout << "none" << endl;
new_node->classification[i] = -;//表示未训练到这种分类,无法判断
new_node->children[i] = NULL;
}
else//例子集不空,且尚未能区分正反,更新访问情况,递归
{
new_node->classification[i] = ;
left_a[max_attr] = ;//更新属性访问情况
int left_e_next[MAX];//下一轮的例子集(为便于回溯,不修改原例子集)
for (int k = ; k < num_example; k++)
left_e_next[k] = left_e[k];
for (int j = ; j < num_example; j++)
{
if (left_e[j]) continue;
if (!e[j].values[max_attr].compare(a[max_attr].values[i]))
left_e_next[j] = ;//属性值匹配的例子,入选下一轮例子集
else left_e_next[j] = ;//属性值不匹配,筛除
}
new_node->children[i] = recursive_build_tree(left_e_next, left_a);//递归
left_a[max_attr] = ;//恢复属性访问情况
}
p = ;
n = ;
}
return new_node;
}
double I(int p, int n)
{
double a = p / (p + (double)n);
double b = n / (p + (double)n);
if (a == || b == ) return ;
return -a*log(a) / log() - b*log(b) / log();
}
double Gain(int left_e[], int cur_attr)//计算信息增益
{
int sum_p=, sum_n=;
int p[] = { }, n[] = { };
for (int i = ; i < num_example; i++)
{//求样例集的p,n
if (left_e[i]) continue;
if (e[i].pn) sum_p++;
else sum_n++;
}
if (!sum_p && !sum_n)
{
//cout << "no more examples!" << endl;
return -;//样例集是空集
} double sum_Ipn = I(sum_p, sum_n);
for (int i = ; i < a[cur_attr].count; i++)
{//求第i个属性值的p,n
for (int j = ; j < num_example; j++)
{
if (left_e[j]) continue;
if (!e[j].values[cur_attr].compare(a[cur_attr].values[i]))
if (e[j].pn) p[i]++;
else n[i]++;
}
}
double E = ;
for (int i = ; i < a[cur_attr].count; i++)//计算属性的期望
E += (p[i] + n[i])*I(p[i], n[i]);
E /= (sum_p + sum_n);
//cout << a[cur_attr].name <<sum_Ipn - E << endl;
return sum_Ipn - E;
}
void recursive_traverse(Node *current)//DFS递归遍历
{
if (current == NULL) return;
cout << current->attr.name << endl;
for (int i = ; i < current->attr.count; i++)
{
cout << current->attr.values[i] << " " << current->classification[i] << endl;
recursive_traverse(current->children[i]);
}
}
int recursive_judge(Example exa, Node *current)
{
for (int i = ; i < current->attr.count; i++)
{
if (!exa.values[current->attr.number].compare(current->attr.values[i]))
{
if (current->children[i]==NULL) return current->classification[i];
else return recursive_judge(exa, current->children[i]);
}
}
return ;
}
public:
Decision_tree(int num1,int num2)
{ //通过读文件初始化
num_attr = num1;
num_example = num2; for (int i = ; i<num_attr; i++)
{
a[i].number = i;//属性的秩
cin>>a[i].name;//读入属性名
cin>>a[i].count;//读入此属性的属性值个数
for (int j = ; j<a[i].count; j++)
{
cin>>a[i].values[j];//读入各属性值
}
} for (int i = ; i<num_example; i++)
{
string temp;
for (int j = ; j < num_attr; j++)
{
cin>>e[i].values[j];
}
cin >> temp;
if (!temp.compare("P")) e[i].pn = ;
else e[i].pn = ;
}
//检查
/*for (int i = 0; i<num_attr; i++)
{
cout << a[i].name << endl;//读入属性名
for (int j = 0; j<a[i].count; j++)
{
cout<<a[i].values[j]<<" ";//读入各属性值
}
cout << endl;
}
for (int i = 0; i<num_example; i++)
{
for (int j = 0; j < num_attr; j++)
cout<<e[i].values[j]<<" ";
cout<<e[i].pn<<endl; }
*/
memset(visited_exams, , sizeof(visited_exams));
memset(visited_attrs, , sizeof(visited_attrs));
root = recursive_build_tree(visited_exams,visited_attrs);
}
void traverse()
{
recursive_traverse(root);
}
int judge(Example exa)//判断
{
int result=recursive_judge(exa,root);
return result;
}
void display_attr()//显示属性
{
cout << "There are " << num_attr << " attributes, they are" << endl;
for (int i = ; i < num_attr; i++)
{
cout << "[" << a[i].name << "]" << endl;
for (int j = ; j < a[i].count; j++)
cout << a[i].values[j] << " ";
cout << endl;
}
}
};
Decision_tree
现在这个版本的代码用了10小时完成,去检查时被研究生贬得一文不值。。。也的确,现在我们写的实验题目面向的都是规模非常小的问题,自然体会不到自己的代码在大数据面前的劣势。不过我现在确实学得太少了,很多数据结构都没有动手实现过,算法也是。对C++也只能算入了门。俗话说“磨刀不误砍柴工”,“工欲善其事,必先利其器”,先把基础知识学好,多做基本练习,学到的数据结构和算法都动手实现一遍,这样遇到实际问题也好对应到合适的数据结构和算法。
另外,参照一本书学习好的代码风格和习惯也是很重要的,因为写代码的习惯是思维习惯的反映,而我现在还处于初学者阶段,按照一种典型的流派模仿,构建起自己的思维模式后再谈其他的。
忽然觉得自己学了快两年编程还这么水实在是不能忍,都怪大一时年少不懂事没好好学基础。。。
不过,“悟已往之不谏,知来者之可追”,有了方向,一步步走下去就好,不求优于别人,但一定要“优于过去的自己”。
ID3算法 决策树 C++实现的更多相关文章
- ID3算法 决策树的生成(2)
# coding:utf-8 import matplotlib.pyplot as plt import numpy as np import pylab def createDataSet(): ...
- ID3算法 决策树的生成(1)
# coding:utf-8 import matplotlib.pyplot as plt import numpy as np import pylab def createDataSet(): ...
- Python 实现基于信息熵的 ID3 算法决策树模型
版本说明 Python version: 3.6.6 |Anaconda, Inc.| (default, Jun 28 2018, 11:21:07) [MSC v.1900 32 bit (Int ...
- 决策树笔记:使用ID3算法
决策树笔记:使用ID3算法 决策树笔记:使用ID3算法 机器学习 先说一个偶然的想法:同样的一堆节点构成的二叉树,平衡树和非平衡树的区别,可以认为是"是否按照重要度逐渐降低"的顺序 ...
- 决策树---ID3算法(介绍及Python实现)
决策树---ID3算法 决策树: 以天气数据库的训练数据为例. Outlook Temperature Humidity Windy PlayGolf? sunny 85 85 FALSE no ...
- 02-21 决策树ID3算法
目录 决策树ID3算法 一.决策树ID3算法学习目标 二.决策树引入 三.决策树ID3算法详解 3.1 if-else和决策树 3.2 信息增益 四.决策树ID3算法流程 4.1 输入 4.2 输出 ...
- 决策树ID3算法的java实现(基本试用所有的ID3)
已知:流感训练数据集,预定义两个类别: 求:用ID3算法建立流感的属性描述决策树 流感训练数据集 No. 头痛 肌肉痛 体温 患流感 1 是(1) 是(1) 正常(0) 否(0) 2 是(1) 是(1 ...
- 数据挖掘之决策树ID3算法(C#实现)
决策树是一种非常经典的分类器,它的作用原理有点类似于我们玩的猜谜游戏.比如猜一个动物: 问:这个动物是陆生动物吗? 答:是的. 问:这个动物有鳃吗? 答:没有. 这样的两个问题顺序就有些颠倒,因为一般 ...
- 决策树 -- ID3算法小结
ID3算法(Iterative Dichotomiser 3 迭代二叉树3代),是一个由Ross Quinlan发明的用于决策树的算法:简单理论是越是小型的决策树越优于大的决策树. 算法归 ...
随机推荐
- kibana 版本kibana-4.3.1 修改地图
进入到安装目录下的src/ui/public/vislib/visualizations/目录 1.编辑_map.js文件 1 2 //url: 'https://otile{s}-s.mqcdn.c ...
- Pattern | CLiPS
Pattern | CLiPS Pattern Pattern is a web mining module for the Python programming language. It has t ...
- Java菜鸟学习笔记--数组篇(三):二维数组
定义 //1.二维数组的定义 //2.二维数组的内存空间 //3.不规则数组 package me.array; public class Array2Demo{ public static void ...
- GridBagLayout占多行效果注意
如果想要出现按钮2占两行的效果,必须按键3.按钮4同时存在且同时可见. 如果缺少按钮4,则按钮2不会占两行: 如果缺少按钮3.4,则按钮2也不会占两行. package com.wst.bj; imp ...
- Phoenix中Sequence的用法
Phoenix--HBase的JDBC驱动 序列(Sequence)是Phoenix提供的允许产生单调递增数字的一个SQL特性,序列会自动生成顺序递增的序列号,以实现自动提供唯一的主键值. 使用C ...
- ssh 与 Telnet 的区别
简单来书,ssh 和 telnet 是实现相同的功能 , ssh中 数据是经过加密的,是安全的 , 而 Telnet是明文传输的ssh 是加密的,基于 SSL .telnet 是明码传输的,发送的数据 ...
- .NETFramework类库
.NET Framework 包括可加快和优化开发过程并提供对系统功能的访问的类.接口和值类型. 为了便于语言之间进行交互操作,大多数 .NET Framework 类型都符合 CLS,因而可在编译器 ...
- Mongoose的模糊查询
var Commidity = require("./Model/commiditiesModel"); function search(response,request,key) ...
- oracle 数据库学习
1.更改数据库用户名密码: alter user '用户名' identified by ’password'. 2.查看数据库有哪些用户:sqlplus system/password ;selec ...
- Core Bluetooth【官方文档翻译】【02】
1.中心设备和外围设备以及它们在蓝牙通讯中的角色. 在所有的BLE( Bluetooth low energy,下文简称蓝牙4.0 )通讯中都涉及2个主要的角色:中心设备和外围设备.它是基于传统的客户 ...