其实博弈树与决策树本质上是相同的 只是决策树追求的是信息嫡的下降 使可能性趋于一个最大值从而达到预测效果 而博弈树正如上篇博文所述追求的是选择从自身打分最高的一步棋
要是看懂了上篇文章的博弈树(https://blog.csdn.net/viafcccy/article/details/90648852)的思想这里决策树也就不难了 作为AI的入门算法决策树是异常重要的 这里我决定先去实现决策树 再进一步实现MFC五子棋博弈树算法 因为决策树这里使用一个经典的例子 数据集较为简单 不像五子棋数据较为复杂无法直接明白算法的内容 而是被冗杂的数据缠身
下面我们需要知道信息嫡是什么?
很简单就是我说明天太阳从东边出来 这个是小概率事件 也就是不可能 我们把这种事情称之为信息嫡很低的事情
但是我说明天我要去打篮球这个不确定的因素就很多了 要考虑天气 上课等等因素 这种事情叫做信息嫡很高
所以今天完成这样一个例子 输入一些天气建立决策树来预测明天我能不能去打篮球
具体看这篇博文 https://blog.csdn.net/viafcccy/article/details/88695908
其实怎样度量前人已经推导出来
//计算以2为底的对数
double Log2(double x)
{
if(x<=0)
return 0.0;
else
return (double)log10(x)/log10(2);
}
//计算信息嫡
double I(double p1,double p2)
{
return -(p1*Log2(p1)+p2*Log2(p2));
}
代码实现也很简单
这样想一下上次的五子棋博弈树算法 这个就相当于我打分的函数
接下来看到信息嫡的公式我么需要统计数据算出权值 来计算信息嫡
//训练数据集结构
struct TRAINING
{
int outLook;
int temperature;
int humidity;
int windy;
int play;
struct TRAINING *next;
};
struct TRAINING trainingSet[TRAINING_NUMBER]=
{
{SUNNY ,85,85,FALSE,NOT_PLAY,NULL},
{SUNNY ,80,90,TRUE ,NOT_PLAY,NULL},
{VOERCAST,83,78,FALSE,CAN_PLAY,NULL},
{RAIN ,70,96,FALSE,CAN_PLAY,NULL},
{RAIN ,68,80,FALSE,CAN_PLAY,NULL},
{RAIN ,65,70,TRUE ,NOT_PLAY,NULL},
{VOERCAST,64,65,TRUE ,CAN_PLAY,NULL},
{SUNNY ,72,95,FALSE,NOT_PLAY,NULL},
{SUNNY ,69,70,FALSE,CAN_PLAY,NULL},
{RAIN ,75,80,FALSE,CAN_PLAY,NULL},
{SUNNY ,75,70,TRUE ,CAN_PLAY,NULL},
{VOERCAST,72,90,TRUE ,CAN_PLAY,NULL},
{VOERCAST,81,75,FALSE,CAN_PLAY,NULL},
{RAIN ,71,80,TRUE ,NOT_PLAY,NULL}
};
我使用代码将这些数据使用链表储存来便于我们的操作
struct TRAINING *CreateTrainingLink(struct TRAINING set[])
{
struct TRAINING *head,*p,*q;
int i;
p=(struct TRAINING *)malloc(sizeof(struct TRAINING));
memset(p,0,sizeof(struct TRAINING));
head=p;
p->next=NULL;
q=p;
for(i=0;i<TRAINING_NUMBER;i++)
{
p=(struct TRAINING *)malloc(sizeof(struct TRAINING));
memset(p,0,sizeof(struct TRAINING));
p->outLook=set[i].outLook;
p->temperature=set[i].temperature;
p->humidity=set[i].humidity;
p->windy=set[i].windy;
p->play=set[i].play;
p->next=NULL;
q->next=p;
q=q->next;
}
return head;
}
计算加权平均值 用于计算信息嫡
//计算加权平均值
double InformationAttr(int attribute,struct TRAINING *head)
{
struct TRAINING *p;
struct TEMPER_HUMID_GAIN tGain[3];
struct TEMPER_HUMID_GAIN mGain[2];
int c=0;
int i=0,j=0;
int sunny=0,overcast=0,rain=0;
int sPlay=0,oPlay=0,rPlay=0;
int wind=0,windPlay=0;
int noWind=0,noWindPlay=0;
double p1=0,p2=0,p3=0;
double i1=0,i2=0,i3=0;
p=head->next;
switch(attribute)
{
case OUTLOOK:
while(p)
{
c++;
if(p->outLook==SUNNY)
{
sunny++;//晴天总数
if(p->play==CAN_PLAY)
sPlay++;//是晴天时且可以打球的天数
}
if(p->outLook==VOERCAST)
{
overcast++;
if(p->play==CAN_PLAY)
oPlay++;
}
if(p->outLook==RAIN)
{
rain++;
if(p->play==CAN_PLAY)
rPlay++;
}
p=p->next;
}
p1=(double)sunny/c;
p2=(double)overcast/c;
p3=(double)rain/c;
if(sunny)
i1=I((double)sPlay/sunny,
(double)(sunny-sPlay)/sunny);
if(overcast)
i2=I((double)oPlay/overcast,
(double)(overcast-oPlay)/overcast);
if(rain)
i3=I((double)rPlay/rain,
(double)(rain-rPlay)/rain);
return p1*i1+p2*i2+p3*i3;
case TEMPERATURE:
for(i=0;i<3;i++)
{
tGain[i].c=0;
tGain[i].playCount=0;
if(i==0)
tGain[i].value=HIGH_TEMPERATURE;
else if(i==1)
tGain[i].value=MID_TEMPERATURE;
else
tGain[i].value=COLD_TEMPERATURE;
}
while(p)
{
c++;
if(p->temperature/10==HIGH_TEMPERATURE)
{
tGain[0].c++;
if(p->play==CAN_PLAY)
tGain[0].playCount++;
}
else if(p->temperature/10==MID_TEMPERATURE)
{
tGain[1].c++;
if(p->play==CAN_PLAY)
tGain[1].playCount++;
}
else if(p->temperature/10==COLD_TEMPERATURE)
{
tGain[2].c++;
if(p->play==CAN_PLAY)
tGain[2].playCount++;
}
p=p->next;
}
p1=(double)tGain[0].c/c;
p2=(double)tGain[1].c/c;
p3=(double)tGain[2].c/c;
if(tGain[0].c)
i1=I((double)tGain[0].playCount/tGain[0].c,
(double)(tGain[0].c-tGain[0].playCount)/tGain[0].c);
if(tGain[1].c)
i2=I((double)tGain[1].playCount/tGain[1].c,
(double)(tGain[1].c-tGain[1].playCount)/tGain[1].c);
if(tGain[2].c)
i3=I((double)tGain[2].playCount/tGain[2].c,
(double)(tGain[2].c-tGain[2].playCount)/tGain[2].c);
return p1*i1+p2*i2+p3*i3;
case HUMIDITY:
for(i=0;i<2;i++)
{
mGain[i].c=0;
mGain[i].playCount=0;
if(i==0)
mGain[i].value=HIGH_HUMIDITY;
else
mGain[i].value=NORMAL_HUMIDITY;
}
while(p)
{
c++;
if(p->humidity>=mGain[0].value)
{
mGain[0].c++;
if(p->play==CAN_PLAY)
mGain[0].playCount++;
}
else if(p->humidity<mGain[1].value)
{
mGain[1].c++;
if(p->play==CAN_PLAY)
mGain[1].playCount++;
}
p=p->next;
}
p1=(double)mGain[0].c/c;
p2=(double)mGain[1].c/c;
if(mGain[0].c)
i1=I((double)mGain[0].playCount/mGain[0].c,
(double)(mGain[0].c-mGain[0].playCount)/mGain[0].c);
if(mGain[1].c)
i2=I((double)mGain[1].playCount/mGain[1].c,
(double)(mGain[1].c-mGain[1].playCount)/mGain[1].c);
return p1*i1+p2*i2;
case WINDY:
while(p)
{
c++;
if(p->windy==TRUE)
{
wind++;
if(p->play==CAN_PLAY)
windPlay++;
}
if(p->windy==FALSE)
{
noWind++;
if(p->play==CAN_PLAY)
noWindPlay++;
}
p=p->next;
}
p1=(double)wind/c;
p2=(double)noWind/c;
if(wind)
i1=I((double)windPlay/wind,
(double)(wind-windPlay)/wind);
if(noWind)
i2=I((double)noWindPlay/noWind,
(double)(noWind-noWindPlay)/noWind);
return p1*i1+p2*i2;
}
return 0.0;
}
接下来继续 这里我们继续引入一个概念信息增量 info gain
公式是这个
为了便于理解,我们还是以一个实际的例子来说明信息增益的概念。假设有下表样本
第一列为QQ,第二列为性别,第三列为活跃度,最后一列用户是否流失。我们要解决一个问题:性别和活跃度两个特征,哪个对用户流失影响更大?我们通过计算信息熵可以解决这个问题。
按照分组统计,我们可以得到如下信息:
其中Positive为正样本(已流失),Negative为负样本(未流失),下面的数值为不同划分下对应的人数。那么可得到三个熵:
整体熵:
性别熵:
性别信息增益:
同理计算活跃度熵:
活跃度信息增益:
活跃度的信息增益比性别的信息增益大,也就是说,活跃度对用户流失的影响比性别大。在做特征选择或者数据分析的时候,我们应该重点考察活跃度这个指标。
上面案例可以一目了然的看出信息增益偏向于选择取值较多的特征,但根据熵的公式可知,特征越多,熵越大,所以除A特征的熵正好抵消了特征变量的复杂程度,避免了过拟合的存在。
好了get了这个两个东西 下面我们来实现计算gain
double Gain(int attribute,struct TRAINING *head)
{
return Information(head)-InformationAttr(attribute,head);
}
到这里我们使用c语言完成了对于建立决策树数据的基本处理