公司业务需要加入一些机器学习的算法,组织了培训,总结学习笔记和查阅的相关资料,后附本菜鸟的渣代码
5.算法实例(计算信息熵,条件熵,信息增益)
任务:
根据天气预测否去打网球
数据:
这个数据集来自Mitchell的机器学习,叫做是否去打网球play-tennis,以下数据仍然是从带逗号分割的文本文件,复制到纪事本,把后缀直接改为.csv就可以拿Excel打开:
play-tennis data,其中6个变量依次为:编号、天气{Sunny、Overcast、Rain}、温度{热、冷、适中}、湿度{高、正常}、风力{强、弱}以及最后是否去玩的决策{是、否}。一个建议是把这些数据导入Excel后,另复制一份去掉变量的数据到另外一个工作簿,即只保留14个观测值。这样可以方便地使用Excel的排序功能,随时查看每个变量的取值到底有多少。/
NO. , Outlook , Temperature , Humidity , Wind , Play
1 , Sunny , Hot , High , Weak , No
2 , Sunny , Hot , High , Strong , No
3 , Overcast , Hot , High , Weak , Yes
4 , Rain , Mild , High , Weak , Yes
5 , Rain , Cool , Normal , Weak , Yes
6 , Rain , Cool , Normal , Strong , No
7 , Overcast , Cool , Normal , Strong , Yes
8 , Sunny , Mild , High , Weak , No
9 , Sunny , Cool , Normal , Weak , Yes
10 , Rain , Mild , Normal , Weak , Yes
11 , Sunny , Mild , Normal , Strong , Yes
12 , Overcast , Mild , High , Strong , Yes
13 , Overcast , Hot , Normal , Weak , Yes
14 , Rain , Mild , High , Strong , No
用决策树来预测:
决策树的形式类似于“如果天气怎么样,去玩;否则,怎么着怎么着”的树形分叉。那么问题是用哪个属性(即变量,如天气、温度、湿度和风力)最适合充当这颗树的根节点,在它上面没有其他节点,其他的属性都是它的后续节点。
那么借用上面所述的能够衡量一个属性区分以上数据样本的能力的“信息增益”(Information Gain)理论。
如果一个属性的信息增益量越大,这个属性作为一棵树的根节点就能使这棵树更简洁,比如说一棵树可以这么读成,如果风力弱,就去玩;风力强,再按天气、温度等分情况讨论,此时用风力作为这棵树的根节点就很有价值。如果说,风力弱,再又天气晴朗,就去玩;如果风力强,再又怎么怎么分情况讨论,这棵树相比就不够简洁了。
一.ID3算法
1.用熵来计算信息增益:(信息增益=系统熵-条件熵)
1.1计算分类系统熵
类别是 是否出去玩。取值为yes的记录有9个,取值为no的有5个,即说这个样本里有9个正例,5 个负例,记为S(9+,5-),S是样本的意思(Sample)。那么P(c1) = 9/14, P(c2) = 5/14
这里熵记为Entropy(S),计算公式为:
Entropy(S)= -(9/14)*log2(9/14)-(5/14)*log2(5/14)#用Matlab做数学运算
-(9/14.0)*math.log((9/14.0),2)-(5/14.0)*math.log((5/14.0),2)=0.9402859586706309
2 分别以Wind、Humidity、Outlook和Temperature作为根节点,计算其信息增益
Wind的信息增益:
当Wind固定为Weak时:记录有8条,其中正例6个,负例2个;
同样,取值为Strong的记录6个,正例负例个3个。我们可以计算相应的熵为:
Entropy(Weak)=-(6/8)*log(6/8)-(2/8)*log(2/8)=0.811
-(6/8.0)*math.log((6/8.0),2)-(2/8.0)*math.log((2/8.0),2)
Entropy(Strong)=-(3/6)*log(3/6)-(3/6)*log(3/6)=1.0
-(3/6.0)*math.log((3/6.0),2)-(3/6.0)*math.log((3/6.0),2)
现在就可以计算出相应的信息增益了:
所以,对于一个Wind属性固定的分类系统的信息量为 (8/14)*Entropy(Weak)+(6/14)*Entropy(Strong)
Gain(Wind)=Entropy(S)-(8/14)*Entropy(Weak)-(6/14)*Entropy(Strong)=0.940-(8/14)*0.811-(6/14)*1.0=0.048
这个公式的奥秘在于,8/14是属性Wind取值为Weak的个数占总记录的比例,同样6/14是其取值为Strong的记录个数与总记录数之比。
Humidity信息增益:
Entropy(High)= -(3/7.0)*math.log((3/7.0),2)-(4/7.0)*math.log((4/7.0),2)=0.9852281360342516
Entropy(Normal)=-(6/7.0)*math.log((6/7.0),2)-(1/7.0)*math.log((1/7.0),2)=0.5916727785823275
Entropy(High)=0.985 ; Entropy(Normal)=0.592
Gain(Humidity)=0.940-(7/14)*Entropy(High)-(7/14)*Entropy(Normal)=0.151
0.940-(7/14.0)*0.985-(7/14.0)*0.592=0.15149999999999997
Outlook信息增益:
Entropy(Sunny)=-(2/5.0)*math.log((2/5.0),2)-(3/5.0)*math.log((3/5.0),2)=0.9709505944546686
Entropy(Overcast)=0.0
Entropy(Rain)=-(3/5.0)*math.log((3/5.0),2)-(2/5.0)*math.log((2/5.0),2)=0.9709505944546686
Entropy(Sunny)=0.971 ; Entropy(Overcast)=0.0 ; Entropy(Rain)=0.971
Gain(Outlook)=0.940-(5/14)*Entropy(Sunny)-(4/14)*Entropy(Overcast)-(5/14)*Entropy(Rain)=0.247
0.940-(5/14.0)*0.970-(5/14.0)*0.970=0.2471428571428571
Temperature信息增益:
Entropy(Cool)=0.811 ; Entropy(Hot)=1.0 ; Entropy(Mild)=0.918
Gain(Temperature)=0.940-(4/14)*Entropy(Cool)-(4/14)*Entropy(Hot)-(6/14)*Entropy(Mild)=0.029
这样我们就得到了以上四个属性相应的信息增益值:
Gain(Wind)=0.048 ;Gain(Humidity)=0.151 ; Gain(Outlook)=0.247 ;Gain(Temperature)=0.029
>>> -(2/4.0)*math.log((2/4.0),2)-(2/4.0)*math.log((2/4.0),2)
1.0
>>> -(4/6.0)*math.log((4/6.0),2)-(2/6.0)*math.log((2/6.0),2)
0.9182958340544896
>>> -(3/4.0)*math.log((3/4.0),2)-(1/4.0)*math.log((1/4.0),2)
0.8112781244591328
>>> 0.940-(4/14.0)*1.0-(6/14.0)*0.918-(4/14.0)*0.811
0.029142857142857137
>>>
1.2排序增益信息,取最大为分裂属性
最后按照信息增益最大的原则选Outlook为根节点。子节点重复上面的步骤。这颗树可以是这样的,它读起来就跟你认为的那样!!!
2.ID3算法的缺点
ID3采用的信息增益度量存在一个内在偏置,它优先选择有较多属性值的Feature,因为属性值多的Feature会有相对较大的信息增益。
(信息增益反映的给定一个条件以后不确定性减少的程度,必然是分得越细的数据集确定性更高,也就是条件熵越小,信息增益越大).
为了克服ID3的缺点,引进C4.5算法
————————————————
二.C4.5算法
1.计算信息增益率
是对ID3算法的改进,避免ID3不足的一个度量就是不用信息增益来选择Feature,而是用信息增益比率(gainratio),增益比率通过引入一个被称作分裂信息(Splitinformation)的项来惩罚取值较多的Feature,分裂信息用来衡量Feature分裂数据的广度和均匀性:
eg:
outlook属性的增益率:
SplitInformation(outlook)=SplitInformation(D,A1)
=-5/14*log(5/14)-4/14*log(4/14)-5/14*log(5/14)=1.577
g(D,A1)=0.247(ID3算法结果,直接引用)
GainRatio(D,A1)=g(D,A1) /SplitInformation(D,A1)(增益率=信息增益/分裂信息量)
=0.247/1.577= 0.157
Temperature的增益率:
splitinfo = -(4/14.0)*math.log((4/14.0),2)-(6/14.0)*math.log((6/14.0),2)-(4/14.0)*math.log((4/14.0),2)=1.557
gain=0.029
gainratio=0.029/1.557=0.019
Humidity的增益率:
splitinfo =-(7/14.0)*math.log((7/14.0),2)-(7/14.0)*math.log((7/14.0),2)=1
gain=0.151
gainratio=0.151/1=0.151
Windy的增益率:
splitinfo =-(8/14.0)*math.log((8/14.0),2)-(6/14.0)*math.log((6/14.0),2)=0.985
gain=0.048
gainratio=0.048/0.985=0.049
2.选取信息增益率最大的属性作为分裂的根节点
day > outlook > humidity > windy > temperature
3.C4.5算法优缺点分析
优点:
(1)通过信息增益率选择分裂属性,克服了ID3算法中通过信息增益倾向于选择拥有多个属性值的属性作为分裂属性的不足;
(2)能够处理离散型和连续型的属性类型,即将连续型的属性进行离散化处理;
(3)构造决策树之后进行剪枝操作;
(4)能够处理具有缺失属性值的训练数据。
缺点:
(1)算法的计算效率较低,特别是针对含有连续属性值的训练样本时表现的尤为突出。
(2)算法在选择分裂属性时没有考虑到条件属性间的相关性,只计算数据集中每一个条件属性与决策属性之间的期望信息,有可能影响到属性选择的正确性。
——————————————
三.CART算法
CART,又名分类回归树,是在ID3的基础上进行优化的决策树,学习CART记住以下几个关键点:
• (1)CART既能是分类树,又能是分类树;
• (2)当CART是分类树时,采用GINI值作为节点分裂的依据;当CART是回归树时,采用样本的最小方差作为节点分裂的依据;
• (3)CART是一棵二叉树。
接下来将以一个实际的例子对CART进行介绍:
表1 原始数据表
从以下的思路理解CART:
1.分类树和回归树
分类树的作用是通过一个对象的特征来预测该对象所属的类别,而回归树的目的是根据一个对象的信息预测该对象的属性,并以数值表示。
CART既能是分类树,又能是决策树,如上表所示,如果我们想预测一个人是否已婚,那么构建的CART将是分类树;如果想预测一个人的年龄,那么构建的将是回归树。
分类树和回归树是怎么做决策的?假设我们构建了两棵决策树分别预测用户是否已婚和实际的年龄,如图1和图2所示:
图1 预测婚姻情况决策树 图2 预测年龄的决策树
图1表示一棵分类树,其叶子节点的输出结果为一个实际的类别,在这个例子里是婚姻的情况(已婚或者未婚),选择叶子节点中数量占比最大的类别作为输出的类别;
图2是一棵回归树,预测用户的实际年龄,是一个具体的输出值。怎样得到这个输出值?一般情况下选择使用中值、平均值或者众数进行表示,图2使用节点年龄数据的平均值作为输出值。
2.CART算法:基尼值和最小方差
分裂的目的是为了能够让数据变纯,使决策树输出的结果更接近真实值。那么CART是如何评价节点的纯度呢?如果是分类树,CART采用GINI值衡量节点纯度;如果是回归树,采用样本方差衡量节点纯度。节点越不纯,节点分类或者预测的效果就越差。
GINI值的计算公式:
节点越不纯,GINI值越大。以二分类为例,如果节点的所有数据只有一个类别,则 ,如果两类数量相同,则 。
回归方差计算公式:
方差越大,表示该节点的数据越分散,预测的效果就越差。如果一个节点的所有数据都相同,那么方差就为0,此时可以很肯定得认为该节点的输出值;如果节点的数据相差很大,那么输出的值有很大的可能与实际值相差较大。
因此,无论是分类树还是回归树,CART都要选择使子节点的GINI值或者回归方差最小的属性作为分裂的方案。即最小化(分类树):
或者(回归树):
CART如何分裂成一棵二叉树
节点的分裂分为两种情况,连续型的数据和离散型的数据。
CART对连续型属性的处理与C4.5差不多,通过最小化分裂后的GINI值或者样本方差寻找最优分割点,将节点一分为二,在这里不再叙述,详细请看C4.5。
对于离散型属性,理论上有多少个离散值就应该分裂成多少个节点。但CART是一棵二叉树,每一次分裂只会产生两个节点,怎么办呢?很简单,只要将其中一个离散值独立作为一个节点,其他的离散值生成另外一个节点即可。这种分裂方案有多少个离散值就有多少种划分的方法,举一个简单的例子:如果某离散属性一个有三个离散值X,Y,Z,则该属性的分裂方法有{X}、{Y,Z},{Y}、{X,Z},{Z}、{X,Y},分别计算每种划分方法的基尼值或者样本方差确定最优的方法。
以属性“职业”为例,一共有三个离散值,“学生”、“老师”、“上班族”。该属性有三种划分的方案,分别为{“学生”}、{“老师”、“上班族”},{“老师”}、{“学生”、“上班族”},{“上班族”}、{“学生”、“老师”},分别计算三种划分方案的子节点GINI值或者样本方差,选择最优的划分方法,如下图所示:
第一种划分方法:{“学生”}、{“老师”、“上班族”}
预测是否已婚(分类):
预测年龄(回归):
>>> math.sqrt(math.pow(12,2)+math.pow(18,2)+math.pow(21,2)-3*math.pow(17,2))=6.48
>>> math.sqrt(math.pow(26,2)+math.pow(47,2)+math.pow(36,2)+math.pow(29,2)-4*math.pow(32.5,2))=28.23
(3/7.0)*(1-(math.pow((2/3.0),2)+math.pow((1/3.0),2)))+(4/7.0)*(1-(math.pow((3/4.0),2)+math.pow((1/4.0),2)))=0.4
第二种划分方法:{“老师”}、{“学生”、“上班族”}
预测是否已婚(分类):
预测年龄(回归):
(2/7.0)*(1-(math.pow((1/2.0),2)+math.pow((1/2.0),2)))+(5/7.0)*(1-(math.pow((3/5.0),2)+math.pow((2/5.0),2)))=0.49
第三种划分方法:{“上班族”}、{“学生”、“老师”}
预测是否已婚(分类):
预测年龄(回归):
综上,如果想预测是否已婚,则选择{“上班族”}、{“学生”、“老师”}的划分方法,如果想预测年龄,则选择{“老师”}、{“学生”、“上班族”}的划分方法。
伪代码:
#!/usr/bin/python
# -*- coding:utf-8 -*-
import math
import operator
import pandas as pd
import numpy
from fractions import *
"""
函数说明:创建测试数据集
"""
def createDataSet():
dataSet = [[0, 0, 0, 0, 'no'], #数据集
[0, 0, 0, 1, 'no'],
[0, 1, 0, 1, 'yes'],
[0, 1, 1, 0, 'yes'],
[0, 0, 0, 0, 'no'],
[1, 0, 0, 0, 'no'],
[1, 0, 0, 1, 'no'],
[1, 1, 1, 1, 'yes'],
[1, 0, 1, 2, 'yes'],
[1, 0, 1, 2, 'yes'],
[2, 0, 1, 2, 'yes'],
[2, 0, 1, 1, 'yes'],
[2, 1, 0, 1, 'yes'],
[2, 1, 0, 2, 'yes'],
[2, 0, 0, 0, 'no']]
labels = ['age', 'salary', 'house', 'credit','result'] #分类属性
df = pd.DataFrame(dataSet,columns=labels)
# print df
return dataSet, labels,df
# 计算熵
def entropy(dataSet,labels):
totalsample = len(dataSet)
sample = []
temp = set()
for k in dataSet:
sample.append(k[-1])
temp.add(k[-1])
temp1 = {}
totalentropy = 0
for k in temp:
temp1[k] = sample.count(k)
pentropy = 0
# print float(temp1[k])
# print float(totalsample)
pentropy = -(float(temp1[k])/float(totalsample))*math.log(Fraction(temp1[k],totalsample),2)
totalentropy = totalentropy + pentropy
return round(totalentropy,3)
#计算条件熵,计算最大增益
def maxgain(df,totalentropy,labels):
# print (u"递归中dataframe%s") %df
totalgain = []
target = df.columns[len(df.columns)-1]
target1 = df[target].values
result = set()
# print len(df.columns)-1
# 提取每一列
# print labels[0:len(labels)-1]
for i in labels[0:len(labels)-1]:
# print i
currentline = df[i].values#当前列的值
targetset = set(target1)#result列的值取集合
# print ("clo :%s") %clo
t = 0
tempset = set(currentline)#当前列的值取集合
coltotalgain = totalentropy
# print tempset
# print ("have result %s")%len(tempset)
while t < len(tempset):#遍历[0,1]
# print t
val = tempset.pop()
# print ("val =%s") %val
j = 0
newcurrent = []
newtarget = []
#循环当前列,分成0/1两类
while j <len(currentline):
if val == currentline[j]:
newcurrent.append(currentline[j])#[0,0,0,0,0,0]
newtarget.append(target1[j])#[yes,yes,no,no,yes,no]
j +=1
newtargetset = set(newtarget)#按[0,1]分类后对应的结果集,即[yes,no]
k = 0
newtarget1 = {}
contotalgain = 0.0
# print newtargetset
while k <=len(newtargetset):
val1 = newtargetset.pop()
newtarget1[val1] = 0#{"yes":0}计数用,比如有几个yes,定义为float方便除法即对数的运算
for n in newtarget:
if val1 == n:
newtarget1[val1] = newtarget1[val1] +1 #计数,每次加1,统计有几个“yes”
#统计完有几个yes和几个no,计算一个条件的增益,不如house为1的条件下的熵
congain = -(float(newtarget1[val1])/float(len(newcurrent)))*math.log(Fraction(newtarget1[val1],len(newcurrent)),2)
# print ("gain = %.3f") %congain
k+=1
contotalgain = contotalgain + congain
# print ("contotalgain = %.3f") %contotalgain
coltotalgain = coltotalgain - (float(len(newcurrent))/float(len(currentline)))*contotalgain
# print ("coltotalgain = %.3f") %coltotalgain
t+=1
totalgain.append(round(coltotalgain,3))
# print totalgain
if len(totalgain) == 1:
# print ("IIIIIIIIIIIIIIIIIIIIIIIIIam here")
return labels[labels.index(i)]
totalgain = totalgain[0:len(totalgain)-1]
print (u"各项增益值%s") %totalgain
print (u"最大增益值%s") %max(totalgain)
return df.columns[totalgain.index(max(totalgain))]
# 创建决策树
def Createtree(df,labels):
resultset = set(df.columns[len(df.columns)-1])
totalentropy = entropy(dataSet,labels)#计算熵 交叉图 透视图区别
# print totalentropy
bestmaxgain = maxgain(df,totalentropy,labels)#计算条件熵
print (u"最大增益项为%s") % bestmaxgain
mytree = {bestmaxgain:{}}
featurevalue = set(df[bestmaxgain])
# print (u"特征的值%s")%featurevalue
# print labels.index(bestmaxgain)
# 从特征的值的集合拿出特征的值
for i in featurevalue:#[0,1]
# print ("here %s i = %s") % (bestmaxgain,i)#i=0/1
newdf = pd.DataFrame(columns = labels)
#取出原表当中特征列中特征的值等于i的行,放入新表,进入递归求条件熵
for k in df.index:#检索行
if df.iloc[k][labels.index(bestmaxgain)] == int(i):
newdf = newdf.append(df.iloc[k],ignore_index=True)
print ("new df = %s") %newdf
#如果特征的一个值的结果都同为yes或no,直接返回结果
tempresult = newdf.columns[len(newdf.columns)-1]
tempresultset = set(newdf[tempresult].values)
# print tempresultset
if len(tempresultset) == 1:#结果集不分裂的时候直接返回结果,进入下一个结果的增益计算
hereset = tempresultset.pop()
# print ("hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh%s")%hereset
mytree[bestmaxgain][i] = hereset
print mytree
continue
#特征集删除已经作为节点的label,生成新lables传入递归
newlabels = labels[:]
newlabels.remove(bestmaxgain)
newdf = newdf.drop(bestmaxgain,1)
if len(newlabels) == 1:#属性只有1个的时候停止递归
print mytree
return mytree
mytree[bestmaxgain][i] = Createtree(newdf,newlabels)
print mytree
return mytree
# tree为训练的决策树结果,testvalue要分析的数据['age', 'salsry', 'house', 'credit']为了保证结果,list顺序不能乱
# 训练结果显示决定结果的特征只有house和salary
def analysis(df,decisiontree,testvalue):
#将测试数据转换成键值对dict
label =[]
for i in df.columns[0:len(df.columns)-1]:
label.append(i)
testdata = {}
i=0
while i<len(label):
testdata[label[i]] = testvalue[i]
i+=1
# 对比键值,判断结果
result =check(df,decisiontree,testdata)
return result
#递归检索测试集
def check(df,decisiontree,testdata):
for k in decisiontree:
resultset = set(df[df.columns[len(df.columns)-1]].values)
# print resultset
if k in testdata:#{}
for v in decisiontree[k]:
if v == testdata[k]:
if type(decisiontree[k][v]).__name__ == 'str':
result = decisiontree[k][v]
return result
else:
result = check(df,decisiontree[k][v],testdata)
return result
if __name__ == '__main__':
dataSet, labels ,df= createDataSet()#生成样本 TODO:批量生产样本,从文件读取样本
decisiontree= Createtree(df,labels)#训练-生成tree
testValue = [0,1,0,1]#按顺序输入测试集数据 TODO:从文件批量读取
result = analysis(df,decisiontree,testValue)#决策
print result
# #TODO:1.结果写入文件 2.画出决策树图
#如何剪枝?