这个学期要学DM&ML,用的是《数据挖掘算法原理与实现》王振武 本着造福同学的思想,开一个DM&ML的笔记系列,打算给书上的源代码添加一点注释,方便阅读和理解。
写在前面:
提示:本博文不适合(未满18岁||码龄<1年||编码量<5K行||年均编码量2K行)的读者,
页面可能包含轻度或中度的吐槽,嘲讽,以及大量的不规范编码以及错误的编程设计等内容;
阅读时有可能产生轻微不适感;
请确信自己已满当地法律许可年龄且心智成熟后再来阅览;
警告:
《数据挖掘算法原理与实现》书中提供的C4.5源码,存在大量的浮点数判断错误,log函数设计错误,内存泄漏,编程设计等错误。切勿模仿。
典型错误举例分析:
浮点数判断错误:
if((double) n==0)诸如此类的浮点数相等/大小判断,由于浮点数存储的设计决定了会存在误差,所以应该引入误差处理,或者认为设定误差精度。
比如:if(fabs(1/2)-0.5<FLT_EPSILON) 或者 if(fabs(1/2)-0.5<0.00001/*精确到0.00001*/)。
本算法在计算信息熵的时候需要计算2为底的log值,由于math.h并不提供这样的函数,需要像ID3的实现那样使用换底公式,本实现一概没有使用。
内存泄漏:全文没有任何delete
- 程序设计上的错误:详情看注释
- 如果看到这里您并不打算浪费时间在如下地方:纠正代码错误,看懂不知所云的奇怪编程风格,纠结于递归出口,被代码误导日后编程,欢迎点击本页面的右上角查看其他关于C4.5的资料,并跳过本书的本部分教学内容。
前置知识要求:
C++,STL,树,深度优先搜索(DFS)
一点点数学(换底公式)
具体实现:
/*hiro:修改了头文件*/
/*hiro:为何要用缩进来伤害我!*/
#include <iostream>
#include <fstream>/*hiro:添加了用于文本IO的头文件*/
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
// using namespace std;
/*hiro:本来我可以用全局的using namesapce std的,但是考虑到
他好像写了个sort,我怕引起各方面的命名冲突,于是显式使用了明明空间
std::cout,std::endl,下面不再重复描述。*/
const int A = 4;//data中条件属性的个数
const int C = 3;//data中类的个数
const int ON = 150;//ordata中样本的个数
const int N = 120;//测试集中样本的个数
const int MAX = 200;//条件属性取值的最大值
const int CX = 2;//运行20次求平均值
double ordata[ON][A + 2];/*hiro:样例数据,更改为文本输入*/
double data[N][A + 2];//训练集
double test[ON - N][A + 2];//测试集
double rule[N][A + 2];//规则集
int bb = 0;//规则集中规则的个数
int attnum[A];//各个属性取值的个数
//int iii,jjj;
/***********统计各属性取值的个数************/
//初始化
/*
for(iii=0;iii<A;iii++)
{
attnum[iii]=1;
sort(&ordata[0][0],ON,iii+1);
for(jjj=1;jjj<ON;jjj++)
{
if(ordata[jjj][iii+1]==ordata[jjj-1][iii+1]) continue;
else attnum[iii]++;
}
std::cout<<attnum[iii]<<std::endl;
}
*/
struct node
{
int leaf;//标志,叶子为1,否则为中间节点,取值为0
double cla;//如果是叶子节点,表示决策类的值
int att[A];//已确定的条件属性,1为已确定,0为未确定
double attvalue[A];//已确定的条件属性的取值,初始值为-1
int i;//当前进行分支的条件属性号(0~A-1)
double nextvalue[MAX];//当前条件属性的取值
struct node *next[MAX];//指向前条件属性的取值的下一级
}*Tree;
int noden = 0;/*hiro:结点个数*/
void Input();/*hiro:添加的文本IO的函数*/
void out(double *a, int n);//输出数组a,它有n行,A+2列
void sort(double *a, int num, int n);//对长为num的a数组根据第n列进行排序
struct node * root();//生成根节点
double gainratio(double *d, int n, int a);//求有n个样本的数据集d的属性a的信息增益率
struct node * copynode(struct node *a, struct node *b);//拷贝a节点的数据到b节点
int isleaf(struct node *p);//判断节点p是否为叶子节点
void nextnode(struct node *p);//根据当前节点生成孩子节点,返回值为0表示都是叶子节点,为1表示还存在孩子节点
void outnode(struct node *p);//输出节点p
void outTree(struct node *p);//输出树,p为根,
int nodemaxi(struct node *p);//求节点p的i
//函数声明;
void main()
{
Input();/*hiro:添加的文本IO*/
int i, j, k;
int bz1, bz2;
int reco[CX];//能正确识别的样本个数
int cannotreco[CX];//拒绝识别
int recoerr[CX];//错误识别
double recoratio[CX];//能正确识别的样本个数
double cannotrecoratio[CX];//拒绝识别
double recoerrratio[CX];//错误识别
double nodenumber[CX];//节点个数
double averagereco = 0;//平均正确识别率
double averagecannotreco = 0;//平均拒绝识别率
double averagerecoerr = 0;//平均错误识别率
double averagerule = 0;//平均规则个数
double averagenodenumber = 0;//平均节点个数
int c;
for (c = 0; c<CX; c++)
{
reco[c] = 0;
recoerr[c] = 0;
cannotreco[c] = 0;
}
/************统计各属性取值的个数*************/
//初始化
for (i = 0; i<A; i++)
{
attnum[i] = 1;
sort(&ordata[0][0], ON, i + 1);
// out(&ordata[0][0],ON);
for (j = 1; j<ON; j++)
{
if (ordata[j][i + 1] == ordata[j - 1][i + 1])
/*hiro:!!!严重错误!!!,注意这里两个是浮点数,浮点数的相等判断是不能直接用
==的,*/
continue;
else
attnum[i]++;
}
// std::cout<<attnum[i]<<std::endl;
}
/*hiro:循环CX次*/
for (c = 0; c<CX; c++)
{
noden = 0;
bb = 0;
int l = 0;
/******************生成训练集*******************/
int a[ON];//标志
for (i = 0; i<ON; i++)
a[i] = 0;//标志初始化为0
/*hiro:在大数据的情况下我觉得不允许这样子写产生随机数
不然会死循环,之前写一个10000选7500的,像他这种暴力随机选取,
基本可以当作死循环了。应该维护一个未被选中的链表,或者一开始就生成
一个随机数map,从里面遍历取随机ID,*/
for (i = 0; i<N; i++)
{
/*hiro:↓↓↓我就想问一句,rand要不要初始化设seed?*/
k = rand() % ON;
if (a[k] == 0)//该随机产生的样本尚未被选中
{
//该样本记入data[i]
for (j = 0; j<A + 2; j++)
data[i][j] = ordata[k][j];
//标记a[rand()%150]为1
a[k] = 1;
}
else//该随机产生的样本已经被选中
{
k = rand() % ON;
//继续产生下一个随机数,直到对应样本未被选中
while (a[k] == 1)
{
k = rand() % ON;
}
//该样本记入data[i]
for (j = 0; j<A + 2; j++)
data[i][j] = ordata[k][j];
//标记a[rand()%150]为1
a[k] = 1;
}
}
/******************生成测试集*******************/
/*hiro:看样子,提供的ON个数中,N个数用于训练这个决策树,ON-N是用来检验这个
决策树的*/
k = 0;
for (i = 0; i<ON - N; i++)
{
while (a[k] == 1)
{
k++;
}
for (j = 0; j<A + 2; j++)
test[i][j] = ordata[k][j];
k++;
}
for (i = 0; i<N; i++)
/*hiro:A+1列当中,具体的数字貌似代表了分类,
考虑可读性其实可以用宏,关于这份代码,样例,场景的可读性,我会在后文详述*/
if (data[i][A + 1] == 2)
l++;
// std::cout<<"aaaa== "<<l<<std::endl;
/*hiro:给提取出来的数据(测试集和训练集)重新从1开始编号*/
for (i = 0; i<N; i++)
data[i][0] = i + 1;
for (i = 0; i<ON - N; i++)
test[i][0] = i + 1;
//for(i=0;i<A;i++)
//std::cout<<i<<":"<<gainratio(&ordata[0][0],ON,i)<<std::endl;
/*hiro:处理完数据,开始建树*/
Tree = root();
nextnode(Tree);
out(&data[0][0], N);
std::cout << std::endl;
std::cout << std::endl;
std::cout << std::endl;
std::cout << std::endl;
std::cout << std::endl;
out(&test[0][0], ON - N);
//outnode(Tree);
outTree(Tree);
//std::cout<<bb<<std::endl;
for (i = 0; i<ON - N; i++)
{
bz2 = 1;//默认为拒绝识别的
for (j = 0; j<bb; j++)
{
bz1 = 1;//默认为匹配的
for (k = 0; k<A; k++)
{
if ((test[i][k + 1] == rule[j][k + 1]) || (rule[j][k + 1] == 0))
bz1 = bz1 * 1;/*hiro:骚年你需要与或非吗?想出*1,*0也是辛苦你了*/
else
{
bz1 = bz1 * 0;
break;
}
}
if ((bz1 == 1) && (test[i][A + 1] == rule[j][A + 1]))
{
reco[c]++;
bz2 = 0;
break;
}
/*hiro:我没有眼花??不是我码多了吧??↓↓↓对比↑↑↑*/
/*if ((bz1 == 1) && (test[i][A + 1] != rule[j][A + 1]))
{
recoerr[c]++;
bz2 = 0;
break;
}*/
}
if (bz2 == 1)
{
cannotreco[c]++;
}
}
/*hiro:计算一堆统计信息,详情看对应注释*/
nodenumber[c] = (double)noden;
recoratio[c] = (double)reco[c] / (double)(ON - N);
recoerrratio[c] = (double)recoerr[c] / (double)(ON - N);
cannotrecoratio[c] = (double)cannotreco[c] / (double)(ON - N);
std::cout << "次数:" << c << std::endl;
std::cout << "recog-right:" << (double)reco[c] / (double)(ON - N) << std::endl;
std::cout << "recog-worng:" << (double)recoerr[c] / (double)(ON - N) << std::endl;
std::cout << "recog-cannot:" << (double)cannotreco[c] / (double)(ON - N) << std::endl;
std::cout << "nodenumber:" << noden << std::endl;
averagerule = averagerule + bb;
}
/*hiro:统计各种平均值*/
for (c = 0; c<CX; c++)
{
averagereco = averagereco + recoratio[c];
averagerecoerr = averagerecoerr + recoerrratio[c];
averagecannotreco = averagecannotreco + cannotrecoratio[c];
averagenodenumber = averagenodenumber + nodenumber[c];
}
averagereco = averagereco / (double)CX;
averagerecoerr = averagerecoerr / (double)CX;
averagecannotreco = averagecannotreco / (double)CX;
averagenodenumber = averagenodenumber / (double)CX;
std::cout << "average recog-right:" << averagereco << std::endl;
std::cout << "average recog-worng:" << averagerecoerr << std::endl;
std::cout << "average recog-cannot:" << averagecannotreco << std::endl;
std::cout << "average nodenumber:" << averagenodenumber << std::endl;
std::cout << "averagerule:" << averagerule / CX << std::endl;
// out(&test[0][0],ON-N);
// std::cout<<ON-N<<std::endl;
// out(&ordata[0][0],ON);
// std::cout<<std::endl;
// out(&data[0][0],N);
std::cout << "sss" << std::endl;
}
void Input(){
std::ifstream fin;
char c;
fin.open("input.txt");
for (int i = 0; i < ON; i++){
for (int j = 0; j < A + 2; j++){
fin >> ordata[i][j];
}
}
}
/*hiro:输出数组a[n][A+2]的内容*/
void out(double *a, int n)
{
int i, j;
for (i = 0; i<n; i++)
{
//std::cout<<a[i*(A+2)]<<",";
//std::cout<<a[i*(A+2)+A+1]<<",";
for (j = 0; j<A + 2; j++)
{/*hiro:这位兄台的加法真是好,看来是学习汇编很透彻,
对指针运算也很了解嘛
but,
why not a[i][j]???*/
std::cout << a[i*(A + 2) + j] << ',';
}
std::cout << std::endl;
}
}
void sort(double *a, int num, int n)//对a数组根据第n列进行排序
{
int i = 0, j = 0, k = 0;
double aa;
/*hiro:检验输入的n的合法性,但,,,
why 0<n&&n<A+2 ???*/
k = 0;
for (i = 0; i<A + 2; i++)
{
if (n != i)
k++;
}
if (k == A + 2)
{
std::cout << "依据错误的属性序号进行排序!" << std::endl;
exit(0);
}
/*hiro:嗯,下面开始排序,看好了,认真看*/
for (i = 0; i<num; i++)
{
j = i;
k = i;
/*hiro:先遍历整个表,找到最小值的下标*/
while (j<num)
{
/*hiro:大家当作复习指针加法*/
if (a[j*(A + 2) + n]<a[k*(A + 2) + n])
{
k = j;
}
j++;
}
/*hiro:然后,将最小的放到表的最前面*/
for (j = 0; j<A + 2; j++)
{
aa = a[i*(A + 2) + j];
a[i*(A + 2) + j] = a[k*(A + 2) + j];
a[k*(A + 2) + j] = aa;
}
/*hiro:.......*/
}
/*hiro:嗯,这位兄弟就是这样排序的。到这里我真的是死心了,
估计这位兄弟是数学系的研究生被老师抓去写代码了。
【这位道友,你没有听说过快排,写个冒泡总还可以吧,你这足足的O(N^2)啊...
冒泡都起码是O((N^2)/2)啊..........】
不过一想到可能是数学系的可能没有学过算法,只能用原生的思想写程序,能写这么长,已经是很不错的了。
嗯。
可这本是教材啊,不是写编程语言的实验啊,别误人子弟啊!*/
}
struct node * root()//生成根节点
{
/*hiro:果然是没有delete的,注意内存回收*/
struct node *r = new node;
noden++;
int i;
int k;//标志
double gainr[A];
int maxi = 0;
double max = 0;
//如果训练集为空,则直接返回空
if (N == 0) return(NULL);
//判断训练集是否只含有一个类
/*hiro:这个变量k完全可以不要*/
k = 0;
for (i = 1; i<N; i++)
{
if (data[i][A + 1] != data[0][A + 1])
{
k = 1;
break;
}
else continue;
}
/*hiro:直接判断i==N就行了*/
//如果标志k==1,表示不止一个类
if (k == 0)
{
r->leaf = 0;
r->cla = data[0][A + 1];
}
else
{
r->leaf = 0;
r->cla = 0;
for (i = 0; i<A; i++)
{
r->att[i] = 0;
r->attvalue[i] = -1;
}
//计算每个未确定条件属性的信息增益比
for (i = 0; i<A; i++)
{
gainr[i] = 0;
if (r->att[i] == 1)
continue;
gainr[i] = gainratio(&data[0][0], N, i);
if (gainr[i]>max)
{
maxi = i;
max = gainr[i];
}
}//maxi记录信息增益比最大的属性(0~3)
r->i = maxi;
}
return (r);
}
/*hiro:警告!!!!
该函数内有大量的浮点数错误,log函数调用错误,程序设计错误等问题
切勿模仿*/
double gainratio(double *d, int n, int a)//求有n个样本的数据集d的属性a的信息增益率
{
sort(d, n, a);
int i, j, k, m, s;
double sp = 0;//属性a的信息熵
double I = 0;//决策类的熵
double E = 0;//属性a的条件熵
double E1 = 0;
//value数组记录属性a各个取值
//double *value=new double(attnum[a]);
//num数组记录决策类各个取值的个数
int num[C];
/*******************求决策类的熵***********************/
sort(d, n, A + 1);
i = 0;
k = 1;/*hiro:K为计数变量*/
for (j = 1; j<n; j++)
{
if (d[j*(A + 2) + A + 1] == d[i*(A + 2) + A + 1])
k++;/*hiro:因为已经排序,所以可以这样统计有多少个相等的值*/
else
{
/*hiro:是不是应该用换底公式计算??貌似math.h库没有提供2为底的log函数
参考自www.cplusplus.com:
The natural logarithm is the base-e logarithm:
the inverse of the natural exponential function (exp).
For common (base-10) logarithms, see log10.*/
I = I - (double)k / (double)n*log((double)k / (double)n);
i = j;
k = 1;
}
}
I = I - (double)k / (double)n*log((double)k / (double)n);
//std::cout<<"I="<<I<<std::endl;
/*******************求属性a的熵和条件熵***********************/
sort(d, n, a + 1);
i = 0;
k = 1;
/*hiro:同上,先排序,然后统计个数,实际操作起来,因为这样利用到了cache
所以很有可能更加快,
相关问题阅读:
http://stackoverflow.com/questions/11227809/why-is-it-faster-to-process-a-sorted-array-than-an-unsorted-array*/
for (j = 1; j<n; j++)
{
/*hiro:浮点数判断问题*/
if (d[j*(A + 2) + a + 1] == d[i*(A + 2) + a + 1])
{
k++;
continue;
}
else
{/*hiro:同理,应该要使用换底公式*/
sp = sp - (double)k / (double)n*log((double)k / (double)n);//熵
for (m = 0; m<C; m++)
num[m] = 0;
for (s = i; s<j; s++)
{
for (m = 0; m<C; m++)
{
/*hiro:同样的严重问题,浮点数的相等判断*/
if (d[s*(A + 2) + A + 1] == (double)(m + 1))
num[m]++;
}
}
E1 = 0;
for (m = 0; m<C; m++)
{
/*hiro:同样的严重问题,浮点数的相等判断*/
if ((double)num[m] / (double)k == 0)
continue;
/*hiro:同样的问题,需要换底*/
E1 = E1 - (double)num[m] / (double)k*log((double)num[m] / (double)k);//条件熵
}
E = E + (double)k / (double)n*E1;
i = j;
k = 1;
}
}
/*hiro:需要换底*/
sp = sp - (double)k / (double)n*log((double)k / (double)n);
/*hiro:下面这一段应该不是手抖复制多了的
是由于上面的大循环写的姿势不算很好,对于有n类值的属性,上面的大循环只能
处理n-1,于是,,,他,,,,他,,,,他,,,,把第n次的处理,,,直接
,,,,,复制了一次代码。。。。。。
我是这么理解的*/
for (m = 0; m<C; m++)
num[m] = 0;
for (s = i; s<j; s++)
{
for (m = 0; m<C; m++)
{
/*hiro:浮点数问题*/
if (d[s*(A + 2) + A + 1] == (double)(m + 1))
num[m]++;
// break;
}
}
E1 = 0;
for (m = 0; m<C; m++)
{
if ((double)num[m] / (double)k == 0)
continue;
E1 = E1 - (double)num[m] / (double)k*log((double)num[m] / (double)k);//条件熵
}
E = E + (double)k / (double)n*E1;
/*hiro:到这里为止,*/
// std::cout<<"E="<<E<<std::endl;
// std::cout<<"sp="<<sp<<std::endl;
/*******************求属性a的条件熵***********************/
if (sp == 0) /*hiro:浮点数问题*/
return(-10000);
return ((I - E) / sp);
}
struct node * copynode(struct node *a, struct node *b)//拷贝a节点的数据到b节点,非完全拷贝,后三项重新附初值
{
/*hiro:这是我目前在这份代码里看得最舒服的一段函数了*/
int i;
b->leaf = a->leaf;
b->cla = a->cla;
for (i = 0; i<A; i++)
{
b->att[i] = a->att[i];
b->attvalue[i] = a->attvalue[i];
}
b->i = 0;
for (i = 0; i<MAX; i++)
{
b->nextvalue[i] = 0;
b->next[i] = NULL;
}
return(b);
}
void nextnode(struct node *p)//根据当前节点生成孩子节点,返回非叶子节点的个数
{/*hiro:呃。。。你还真好意思眼睁睁看着函数的返回类型写这
种函数说明的注释诶,,返回非叶子结点的个数*/
int notleaf = 0;
sort(&data[0][0], N, 0);
int i, j, k;
int jsq;
struct node *q;
// double gainr[A];
// int maxi=0;
// double max=0;
/****************生成当前数组d**************/
double *d;
int bz[N];
int n;//d中样本的个数
for (i = 0; i<N; i++)
bz[i] = 1; //等于1为满足节点p的样本
for (i = 0; i<A; i++)
{
if (p->att[i] == 0)
continue;
for (j = 0; j<N; j++)
{
/*hiro:浮点数问题*/
if (data[j][i + 1] != p->attvalue[i])
bz[j] = bz[j] * 0;
}
}
n = 0;
/*hiro:统计个数*/
for (i = 0; i<N; i++)
{
if (bz[i] == 1)
n++;
}
// std::cout<<"aaaaaaaaan"<<n<<std::endl;
/*hiro:忘记delete了吧*/
d = new double[n*(A + 2)];
k = 0;
/*hiro:寻找满足结点的样本并提取到d数组中*/
for (i = 0; i<n; i++)
{
while ((bz[k] == 0) && (k<N))
k++;
// std::cout<<k<<std::endl;
for (j = 0; j<A + 2; j++)
{
/*hiro:↓真是一日看尽长安花式的数组调用*/
d[i*(A + 2) + j] = data[k][j];
}
k++;
}
// out(d,n);~
//std::cout<<"aaaaaaaaaaaaaan:"<<n<<std::endl;
/************生成孩子节点************/
sort(&d[0], n, (p->i) + 1);
// std::cout<<"p->i:"<<p->i<<std::endl;
k = 0;
jsq = 0;//计数器清零
for (i = 0; i<n; i++)
{
if (d[i*(A + 2) + (p->i) + 1] == d[k*(A + 2) + (p->i) + 1])
continue;
/*hiro:忘记delete了大兄弟*/
q = new node;
noden++;
q = copynode(p, q);
q->att[p->i] = 1;
q->attvalue[p->i] = d[k*(A + 2) + (p->i) + 1];
p->nextvalue[jsq] = d[k*(A + 2) + (p->i) + 1];
p->next[jsq] = q;
jsq++;
k = i;
}
//最后一个孩子节点
q = new node;
noden++;
q = copynode(p, q);
q->att[p->i] = 1;
q->attvalue[p->i] = d[k*(A + 2) + (p->i) + 1];
p->nextvalue[jsq] = d[k*(A + 2) + (p->i) + 1];
p->next[jsq] = q;
jsq++;
p->nextvalue[jsq] = -1;
//标志叶子节点
// std::cout<<"jsq:"<<jsq<<std::endl;
for (i = 0; i<jsq; i++)
{
// std::cout<<i<<std::endl;
if (isleaf(p->next[i]) != 0)
{
p->next[i]->leaf = 1;
p->next[i]->cla = isleaf(p->next[i]);
}
else
{
// if(p->nextvalue[i]==-1) break;
notleaf++;
//计算每个未确定条件属性的信息增益比
p->next[i]->i = nodemaxi(p->next[i]);
//; std::cout<<"aaaaaaaaa"<<std::endl;
nextnode(p->next[i]);
}
}
/*hiro:忘记了写return?
这个递归函数我怎么看怎么觉得很危险,
各种爆栈,递归出口不明确*/
//return(notleaf);
}
int isleaf(struct node *p)//判断节点p是否为叶子节点,否返回0,是返回决策类的值
{
sort(&data[0][0], N, 0);
int i, j, k;
// int jsq;
// struct node *q;
/****************生成当前数组d**************/
double *d;
int bz[N];
int n;//d中样本的个数
for (i = 0; i<N; i++)
bz[i] = 1; //等于1为满足节点p的样本
for (i = 0; i<A; i++)
{
if (p->att[i] == 0)
continue;
for (j = 0; j<N; j++)
{
if (data[j][i + 1] != p->attvalue[i])
bz[j] = bz[j] * 0;
}
}
n = 0;
for (i = 0; i<N; i++)
{
if (bz[i] == 1) n++;
}
// std::cout<<"n"<<n<<std::endl;
d = new double[n*(A + 2)];
k = 0;
for (i = 0; i<n; i++)
{
while ((bz[k] == 0) && (k<N)) k++;
// std::cout<<k<<std::endl;
for (j = 0; j<A + 2; j++)
{
d[i*(A + 2) + j] = data[k][j];
}
k++;
}
/*hiro:嗯,是不是觉得上面的代码好眼熟?
同样的也是做提取数据的操作
写个函数比复制要困难很多吗?
实际上判断叶子节点的是下面这点*/
for (i = 1; i<n; i++)
{
if (d[i*(A + 2) + A + 1] != d[0 * (A + 2) + A + 1])
return(0);
}
p->cla = (int)d[0 * (A + 2) + A + 1];
return((int)d[0 * (A + 2) + A + 1]);
}
void outnode(struct node *p)//输出节点p
{
int i;
std::cout << "p->leaf:" << p->leaf << std::endl;
std::cout << "p->cla:" << p->cla << std::endl;
std::cout << " att: ";
for (i = 0; i<A; i++)
{
std::cout << i << "\t";
}
std::cout << std::endl;
std::cout << " att[i]: ";
for (i = 0; i<A; i++)
{
std::cout << p->att[i] << "\t";
}
std::cout << std::endl;
std::cout << "attvalue: ";
for (i = 0; i<A; i++)
{
std::cout << p->attvalue[i] << "\t";
}
std::cout << std::endl;
std::cout << "p->i:" << p->i << std::endl;
std::cout << " next[i]: " << std::endl;
i = 0;
while (p->nextvalue[i] != -1)
{
std::cout << p->nextvalue[i] << "\t" << isleaf(p->next[i]) << std::endl;;
i++;
}
std::cout << std::endl;
std::cout << std::endl;
std::cout << std::endl;
std::cout << std::endl;
}
void outTree(struct node *p)//输出树
{
int i;
if (isleaf(p) != 0)//是叶子
{
for (i = 0; i<A; i++)/*hiro:←这个for很可能是忘记和下面的一并注释的*/
// std::cout<<p->attvalue[i]*p->att[i]<<"\t";
// std::cout<<p->cla<<std::endl;
/*******写入规则集*******/
rule[bb][0] = bb;
for (i = 0; i<A; i++)
{
/*hiro:编程语言都支持逻辑运算的,请放过乘法器*/
rule[bb][i + 1] = p->attvalue[i] * p->att[i];
}
rule[bb][A + 1] = p->cla;
bb++;
}
else//是中间节点
{
i = 0;
while (p->nextvalue[i] != -1)
{
outTree(p->next[i]);
i++;
}
}
}
int nodemaxi(struct node *p)//求节点p的i
{
int notleaf = 0;
sort(&data[0][0], N, 0);
int i, j, k;
// int jsq;
// struct node *q;
double gainr[A];
int maxi = -1;
double max = -1;
/****************生成当前数组d**************/
double *d;
int bz[N];
int n;//d中样本的个数
for (i = 0; i<N; i++)
bz[i] = 1; //等于1为满足节点p的样本
for (i = 0; i<A; i++)
{
if (p->att[i] == 0)
continue;
for (j = 0; j<N; j++)
{
if (data[j][i + 1] != p->attvalue[i])
bz[j] = bz[j] * 0;
}
}
n = 0;
for (i = 0; i<N; i++)
{
if (bz[i] == 1)
n++;
}/*hiro:至此为止统计了训练集中满足结点P属性的数量n*/
//
// std::cout<<"n"<<n<<std::endl;
/*hiro:老问题,内存泄漏了*/
d = new double[n*(A + 2)];
k = 0;
for (i = 0; i<n; i++)
{
while ((bz[k] == 0) && (k<N))
k++;
// std::cout<<k<<std::endl;
for (j = 0; j<A + 2; j++)
{
d[i*(A + 2) + j] = data[k][j];
}
k++;
}
/*hiro:生成了一个待求gain_ratio的数据集及其样本数量n*/
/*hiro:下面就是用来求最大gain_ratio*/
// out(d,n);
max = -1;
for (j = 0; j<A; j++)
{
gainr[j] = 0;
if (p->att[j] == 1)
continue;
gainr[j] = gainratio(d, n, j);
// if(gainr[j]<=0) continue;
// std::cout<<"aaaaaaaaaaa"<<gainr[j]<<std::endl;
if (gainr[j]>max)
{
maxi = j;
max = gainr[j];
}
}//maxi记录信息增益比最大的属性(0~3)
// std::cout<<max<<","<<maxi<<std::endl;
return(maxi);
}
感想:
误人子弟。