cart 算法采用二分递归回归技术,将当前的样本集分为两个子样本集,使得生成得每个非叶子节点都有两个分支。所以,算法生成得决策树是简洁得二叉树。
分类树得两个基本思想:第一个是将训练样本进行递归地划分自变量空间进行建树得想法,第二个想法是用验证数据进行剪枝。
cart进行属性分类得是用gini指标
如果我们用k,k=1,2,3……C表示类,其中C是类别集Result的因变量数目,一个节点A的GINI不纯度定义为:
其中,Pk表示观测点中属于k类得概率,当Gini(A)=0时所有样本属于同一类,当所有类在节点中以相同的概率出现时,Gini(A)最大化,此时值为(C-1)C/2。
对于分类回归树,A如果它不满足“T都属于同一类别or T中只剩下一个样本”,则此节点为非叶节点,所以尝试根据样本的每一个属性及可能的属性值,对样本的进行二元划分,假设分类后A分为B和C,其中B占A中样本的比例为p,C为q(显然p+q=1)。则杂质改变量:Gini(A) -p*Gini(B)-q*Gini(C),每次划分该值应为非负,只有这样划分才有意义,对每个属性值尝试划分的目的就是找到杂质gai变量最大的一个划分,该属性值划分子树即为最优分支。
作业源码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#define EPS 0.000001
/*
1.为了节约时间,直接在c4.5的基础上将其转化为cart算法。
2.使用gini指标评判杂质量。
3.非连续变量选择分割使用异化程度最小的对应属性值划分,即x和非x
4.连续变量使用和c4.5划分方法一样得划分,但是是使用gini指标进行划分
*/
typedef struct Tuple
{
int i;
int g;
double h;
int c;
}tuple;
typedef struct TNode{
double gap;
int attri;
int reachValue;
struct TNode *child[50];
int kind;
}node;
tuple trainData[100];
double cal_entropy(tuple *data,int len);
double choose_best_gap(tuple *data,int len);
double cal_grainRatio(tuple *data,int len);
double cal_grainRatio2(tuple *data,int len,double gap);
double cal_splitInfo(tuple *data,int len);
int check_attribute(tuple *data,int len);
int choose_attribute(tuple *data,int len);
node *build_tree(tuple *data,int len,double reachValue,double gap);
void print_blank(int depth);
void traverse(node *no,int depth);
void test_data(node *root,tuple *data);
int cmp(const void *a, const void *b)
{
tuple *a1=(tuple *)a;
tuple *b1=(tuple *)b;
return a1->h-b1->h>0?1:-1;
}
void copy_tuple(tuple *source,tuple *destination)
{
destination->c=source->c;
destination->g=source->g;
destination->h=source->h;
destination->i=source->i;
}
double cal_gini(tuple *data,int len)
{
int i,j;
double result=0.0;
int cnt;
for(i=0;i<3;i++)//有三类
{
cnt=0;
for(j=0;j<len;j++)
{
if(data[j].c==i)
{
cnt++;
}
}
result+=(cnt*1.0/len)*(cnt*1.0/len);
}
//printf("in cal_gini: %lf\n",result);
return 1-result;
}
double cal_gender(tuple *data,int len)//计算性别分类的gini差值
{
int i,j;
double preGini=cal_gini(data,len);//计算分类前得gini数
tuple subData[100];
int subLen;
double result=0.0;
for(i=0;i<2;i++)
{
subLen=0;//统计某个性别得个数
for(j=0;j<len;j++)
{
if(data[j].g==i)//属于某个性别
{
copy_tuple(&data[j],&subData[subLen++]);//存入数组当中
}
}
result=result+subLen*1.0/len*cal_gini(subData,subLen);
}
return preGini-result;
}
double cal_height(tuple *data, int len,int *at)//计算性别分类的gini差值
{
int i,j;
double preGini=cal_gini(data,len);
//printf("preGini: %lf\n",preGini);
//getchar();
tuple small[100],big[100];
int smallLen,bigLen;
double maxv=-1;
for(i=0;i<len;i++)//寻找最大得gini差值得各个测试单元
{
smallLen=0;bigLen=0;
for(j=0;j<len;j++)
{
if(data[j].h<=data[i].h)
{
copy_tuple(&data[j],&small[smallLen++]);
}
else
{
copy_tuple(&data[j],&big[bigLen++]);
}
}
//printf("i: %d\n",i);
//printf("smallLen: %d\n",smallLen);
//printf("bigLen: %d\n",bigLen);
double smallGini=cal_gini(small,smallLen);
//printf("smallGini: %lf\n",smallGini);
double bigGini=cal_gini(big,bigLen);
//printf("bigGini: %lf\n",bigGini);
double temp=preGini-(smallLen*1.0/len*smallGini+bigLen*1.0/len*bigGini);
//printf("temp: %lf\n",temp);
if(temp>maxv)
{
maxv=temp;
*at=i;
//printf("at: %d data[at]: %lf\n",*at,data[*at].h);
}
}
//printf("maxv: %lf\n",maxv);
return maxv;
}
int main()
{
FILE *fp;
fp=fopen("./data.txt", "r");
if(fp==NULL)
{
printf("can not open the file: data.txt\n");
return 0;
}
char name[50];
double height;
char gender[10];
char kind[10];
int i=0;
while(fscanf(fp, "%s",name)!=EOF)
{
trainData[i].i=i;
fscanf(fp,"%s",gender);
if(!strcmp(gender, "M"))
{
trainData[i].g=0;
}
else trainData[i].g=1;
fscanf(fp,"%lf",&height);
trainData[i].h=height;
fscanf(fp,"%s",kind);
if(!strcmp(kind, "Short"))
{
trainData[i].c=0;
}
else if(!strcmp(kind,"Medium"))
{
trainData[i].c=1;
}
else{
trainData[i].c=2;
}
i++;
}
int rows=i;
node *root=build_tree(trainData,rows,-1,-1);
traverse(root,0);printf("\n");
fp=fopen("./testData.txt", "r");
if(fp==NULL)
{
printf("can not open the file!\n");
return 0;
}
tuple testData;
fscanf(fp, "%s",name);
fscanf(fp,"%s",gender);
if(!strcmp(gender, "M"))
{
testData.g=0;
}
else testData.g=1;
fscanf(fp,"%lf",&height);
testData.h=height;
// printf("testData: gender: %d\theight: %lf\n",testData.g,testData.h);
fclose(fp);
fp=NULL;
test_data(root,&testData);
}
void test_data(node *root,tuple *data)
{
/*
1.检查节点得属性值
2.如果是身高则检查gap得值如果<=就往左,否则就往右
3.如果是性别就判断reachValue的值
*/
if(root->attri==-1)
{
printf("the test data belongs to:");
switch (root->kind) {
case 0: printf("Short\n");break;
case 1: printf("Medium\n");break;
case 2: printf("Tall\n");break;
default:break;
}
return;
}
if(root->attri==0)
{
if(data->g==0)
{
test_data(root->child[0],data);
}
else
{
test_data(root->child[1], data);
}
}
else
{
//printf("gap: %lf\n",root->gap);
if(data->h<=root->gap)
{
test_data(root->child[0], data);
}
else{
test_data(root->child[1], data);
}
}
}
void print_blank(int depth)
{
int i;
for(i=0;i<depth;i++)
{
printf("\t");
}
}
void traverse(node *no,int depth)
{
if(no==NULL)return;
int i;
printf("-------------------\n");
print_blank(depth);
printf("attri: %d\n",no->attri);print_blank(depth);
printf("gap: %lf\n",no->gap);print_blank(depth);
printf("kind: %d\n",no->kind);print_blank(depth);
printf("reachValue: %d\n",no->reachValue);print_blank(depth);
printf("-------------------\n");print_blank(depth);
for(i=0;no->child[i]!=NULL;i++)
{
traverse(no->child[i], depth+1);
}
}
int choose_attribute(tuple *data,int len)//选择属性函数,返回代表属性的代号
{
int i;
/*
1.如果是性别,就直接计算增益
2.如果是身高就计算最高得增益值得gap
3.性别和身高得增益进行比较的到最佳得分类属性
*/
double genderGini=cal_gender(data, len);
int heightChoice;
double heightGini=cal_height(data,len,&heightChoice);
if(genderGini<heightGini)
{
return 1;
}
else
{
return 0;
}
//printf("gGrainRatio: %lf\n",gGrainRatio);
/*计算连续属性值的增益
1.排序确定gap
2.计算各个gap的信息增益率
3.选定最大得信息增益率确定该属性的最大信息增益率
*/
}
node *build_tree(tuple *data,int len,double reachValue,double gap)
{
//getchar();getchar();
int i,j;
/*for(i=0;i<len;i++)
{
printf("data i: %d g:%d h:%lf c:%d\n",data[i].i,data[i].g,data[i].h,data[i].c);
}*/
int kind=check_attribute(data, len);//检查所有得元组是否属于同一个类
//printf("kind: %d\n",kind);
if(kind!=0)//如果所有得元组都属于同一类则作为叶子节点返回
{
// printf("leaves constructed completed!\n");
node *newNode=(node *)malloc(sizeof(node));
newNode->gap=-1;//如果是按照身高分类就用得到gap;
newNode->attri=-1;
newNode->reachValue=reachValue;
newNode->kind=kind-1;
for(i=0;i<50;i++)newNode->child[i]=NULL;//初始化所有的孩子节点
return newNode;
}
//从元组中选择最优属性值进行分类
int attribute=choose_attribute(data, len);
//printf("choose: %d\n",attribute);
//执行分类 深度优先构建树结构
node *newNode=(node *)malloc(sizeof(node));
newNode->reachValue=reachValue;
newNode->attri=attribute;
newNode->kind=-1;
newNode->gap=gap;
for(i=0;i<50;i++)newNode->child[i]=NULL;
if(attribute==0)//选择性别进行构建
{
for(i=0;i<2;i++)
{
tuple subData[100];
int sublen=0;
for(j=0;j<len;j++)
{
if(data[j].g==i/*是男的或者女的*/)
{
copy_tuple(&data[j],&subData[sublen++]);
}
}
if(sublen==0)continue;
newNode->child[i]=build_tree(subData,sublen,i,-1);//因为是用性别构建得,所以不用gap分区间取值
}
}
else
{
//选择高度构建
/*
1.选择最优得分割值
2.将元组分割成left和right两个部分
*/
int index=0;
double heightGini=cal_height(data,len,&index);
double gap=data[index].h;//选择分割连续变量得值
newNode->gap=gap;
//printf("best gap: %lf\n",gap);
tuple leftData[100],rightData[100];//分割完成后,放入左右两个数组里面
int leftlen=0;//左右数组的长度
int rightlen=0;
for(i=0;i<len;i++)
{
if(data[i].h<=gap)
{
copy_tuple(&data[i],&leftData[leftlen++]);
}
else{
copy_tuple(&data[i],&rightData[rightlen++]);
}
}
if(leftlen!=0)
newNode->child[0]=build_tree(leftData,leftlen,-1,gap);//使用身高构建子树,因此必须分区间进行
if(rightlen!=0)
newNode->child[1]=build_tree(rightData,rightlen,-1,gap);
}
return newNode;
}
int check_attribute(tuple *data,int len)//检查所有得元组是否都是一类
{
/*
1.扫描所有得元组,如果出现不适同一类得元组,则返回
*/
int i;
for(i=1;i<len;i++)
{
if(data[i].c!=data[i-1].c)return 0;
}
return data[0].c+1;
}