机器学习之决策树算法

下表为是否适合打垒球的决策表,预测E= {天气=晴,温度=适中,湿度=正常,风速=} 的场合,是否合适中打垒球。数组

 

天气 app

温度 ide

湿度 函数

风速 学习

活动 spa

code

炎热orm

blog

排序

取消

炎热

取消

炎热

进行

适中

进行

寒冷

正常

进行

寒冷

正常

取消

寒冷

正常

进行

适中

取消

寒冷

正常

进行

适中

正常

进行

适中

正常

进行

适中

进行

炎热

正常

进行

适中

取消

如何发现这些数据之中所掩藏的规律,从而较好的预测在给定条件下,所可能的结果。决策树是一种以示例为基础的概括学习方法,可以较好的解决这类问题。

 

  • 一个简单的例子

请给出布尔函数(A * -B+ C+:或,*:与,-非)的最小体积(或结点)决策树。

当C1时,AB无论取何值整个表达式都为真,此时这个表达式就能够肯定真假,因此选择C做为头结点。若C0,表达式没法肯定真假,还需进一步看AB的取值,A与非B是与的关系,二者具备相同的地位,因此接下来不管取A仍是B均可以,整个决策树构造结果以下图所示。

 

相似于这个简单例子对于打垒球这些数据,咱们能够将天气,温度,湿度,风速(能够成为属性或特征)类比成布尔函数的ABC,而它们的取值,如天气的取值能够是晴,雨,阴类比成ABC布尔取值真假,那么活动的取消或进行,就能够类比成整个布尔表达式的真或假。要构造一颗最小体积决策树,就要每次在各个属性中找到区分度最大的属性来做为当前决策树的节点。

 

  • 相关名词

一般熵表示事物的混乱程度,熵越大表示混乱程度越大,越小表示混乱程度越小。对于随机事件S,若是咱们知道它有N种取值状况,每种状况发生的概论为,那么这件事的熵就定义为:

 

例如对于打垒球的例子,要求活动的熵H(活动)。在活动一栏属性中发现活动的取值有两种:取消(5个)和进行(9个),它们所占的比例分别为5/149/14。那么H(活动)的取值为:,算出的结果约为0.94

对于熵的理解

若是一件事发生的多是1,不发生的肯为0那么这件事的熵为=0,这就代表这件事确定发生,没有不发生的状况,那么它的混乱程度是最小的0。同理当不发生的多是1,混乱程度也是0。当发生与不发生各占一半时,这件事就越很差肯定,因此此时熵为最大,其图像以下图所示。

 

计算熵的代码以下

 1 def calcShannonEnt(dataSet):#计算香农熵
 2     numEntries = len(dataSet)
 3     
 4     labelCounts = {}
 5     for featVec in dataSet:
 6         currentLabel = featVec[-1]   #取得最后一列数据,计算该属性取值状况有多少个
 7         if currentLabel not in labelCounts.keys():
 8             labelCounts[currentLabel] = 0
 9         labelCounts[currentLabel]+=1
10     
11     #计算熵
12     shannonEnt = 0.0
13     for key in labelCounts:
14         prob = float(labelCounts[key])/numEntries
15         shannonEnt -= prob*log(prob,2)
16         
17 return shannonEnt
View Code

 

信息增益

随机事件未按照某个属划的不一样取值划分时的熵减去按照某个属性的不一样取值划分时的平均熵。即先后两次熵的差值。

仍是对于打垒球的例子,未按照某个属划的不一样取值划分时的熵即H(活动)已算出未0.94。如今按照天气属性的不一样取值来划分,发现天气属性有3个不一样取值分别为晴,阴,雨。划分好后以下图所示。

天气 

温度 

湿度 

风速 

活动 

炎热

取消

炎热

取消

适中

取消

寒冷

正常

进行

适中

正常

进行

炎热

进行

寒冷

正常

进行

适中

进行

炎热

正常

进行

寒冷

正常

取消

适中

取消

适中

进行

寒冷

正常

进行

适中

正常

进行

在天气为晴时有5种状况,发现活动取消有3种,进行有2种,计算如今的条件熵

=0.971

同理天气为阴时有4种状况,活动进行的有4种,则条件熵为:

=0

同理天气为雨时有5种状况,活动取消的有2种,进行的有3种,则条件熵为:

=0.971

因为按照天气属性不一样取值划分时,天气为晴占整个状况的5/14,天气为阴占整个状况的4/14,天气为雨占整个状况的5/14,则按照天气属性不一样取值划分时的带权平均值熵为:算出的结果约为0.693.

则此时的信息增益Gain(活动,天气)= H(活动) - H(活动|天气) = 0.94- 0.693 = 0.246

同理咱们能够计算出按照温度属性不一样取值划分后的信息增益:

Gain(活动,温度)= H(活动) - H(活动|温度) = 0.94- 0.911 = 0.029

按照湿度属性不一样取值划分后的信息增益:

Gain(活动,湿度)= H(活动) - H(活动|湿度) = 0.94- 0.789 = 0.151

按照风速属性不一样取值划分后的信息增益:

Gain(活动,风速)= H(活动) - H(活动|风速) = 0.94- 0.892 = 0.048

对于信息增益的理解

信息增益就是两个熵的差,当差值越大说明按照此划分对于事件的混乱程度减小越有帮助。

计算各个属性的信息增益,并选择信息增益最大的属性的代码以下

 1 #定义按照某个特征进行划分的函数splitDataSet
 2 #输入三个变量(待划分的数据集,特征,分类值)
 3 #axis特征值中0表明no surfacing,1表明flippers
 4 #value分类值中0表明否,1表明是
 5 def splitDataSet(dataSet,axis,value):
 6     retDataSet = []
 7     for featVec in dataSet:#取大列表中的每一个小列表
 8         if featVec[axis]==value:
 9             reduceFeatVec=featVec[:axis]
10             reduceFeatVec.extend(featVec[axis+1:])
11             retDataSet.append(reduceFeatVec)
12     
13     return retDataSet #返回不含划分特征的子集
14     
15 def chooseBestFeatureToSplit(dataSet):
16     numFeature = len(dataSet[0]) - 1
17     baseEntropy = calcShannonEnt(dataSet)
18     bestInforGain = 0
19     bestFeature = -1
20     
21     for i in range(numFeature):
22         featList = [number[i] for number in dataSet]#获得某个特征下全部值(某列)
23         uniquelVals = set(featList) #set无重复的属性特征值,获得全部无重复的属性取值
24         
25         #计算每一个属性i的概论熵
26         newEntropy = 0
27         for value in uniquelVals:
28             subDataSet = splitDataSet(dataSet,i,value)#获得i属性下取i属性为value时的集合
29             prob = len(subDataSet)/float(len(dataSet))#每一个属性取值为value时所占比重
30             newEntropy+= prob*calcShannonEnt(subDataSet)
31         inforGain = baseEntropy - newEntropy #当前属性i的信息增益
32         
33         if inforGain>bestInforGain:
34             bestInforGain = inforGain
35             bestFeature = i
36    
37 return bestFeature#返回最大信息增益属性下标
View Code

 

  • 构造决策树

决策树的构造就是要选择当前信息增益最大的属性来做为当前决策树的节点。所以咱们选择天气属性来作为决策树根节点,这时天气属性有3取值可能:晴,阴,雨,咱们发现当天气为阴时,活动全为进行所以这件事情就能够肯定了,而天气为晴或雨时,活动中有进行的也有取消的,事件还没法肯定,这时就须要在当前按照天气属性划分下的剩下的属性中递归再次计算活动熵和信息增益,选择信息增益最大的属性来做为下一个节点,直到整个事件可以肯定下来。

例如当天气为晴时,获得以下表所示的事件

天气 

温度 

湿度 

风速 

活动 

炎热

取消

炎热

取消

适中

取消

寒冷

正常

进行

适中

正常

进行

咱们须要递归处理,继续在温度,湿度,风速这三个属性中找到信息增益最大的属性来作为下一个节点。

首先继续计算活动熵,此时有5个样例,活动取消有3个,进行有2个,则活动熵为:

=0.971

接着计算信息增益,在天气为晴的前提下,按照温度属性的不一样取值分类后结果以下所示

天气 

温度 

湿度 

风速 

活动 

炎热

取消

炎热

取消

适中

取消

寒冷

正常

进行

适中

正常

进行

发现湿度为高时有3种状况,活动取消有3种,进行有0种,则条件熵为:

=0

湿度正常有2种状况,活动取消0种,进行2中,则条件熵为:

 

=0

因为按照湿度属性不一样取值划分时,湿度为高占总状况的3/5,湿度正常占总状况的2/5,则按照湿度属性不一样取值划分时的带权平均值熵为:算出的结果约为0

因此此时在天气为晴的前提下,按照湿度属性的不一样取值划分的信息增益为:

Gain= H(活动|天气=) - H(活动|天气,湿度) = 0.971- 0=0.971

同理还需继续计算在天气为晴的前提下,按照温度,风速属性的不一样取值划分的信息增益,找到信息增益最大的做为决策树的下一个节点。

递归构造决策树的代码以下

 1 #递归建立树,用于找出出现次数最多的分类名称
 2 def majorityCnt(classList):
 3     classCount={}
 4     for vote in classList:#统计当前划分下每中状况的个数
 5         if vote not in classCount.keys():
 6             classCount[vote]=0
 7         classCount[vote]+=1
 8     sortedClassCount=sorted(classCount.items,key=operator.itemgetter(1),reversed=True)#reversed=True表示由大到小排序
 9     #对字典里的元素按照value值由大到小排序
10     print("****************")
11     print(sortedClassCount[0][0])
12     return sortedClassCount[0][0]
13 
14 
15 def createTree(dataSet,labels):
16     classList=[example[-1] for example in dataSet]#建立数组存放全部标签值,取dataSet里最后一列(结果)
17     #类别相同,中止划分
18     if classList.count(classList[-1])==len(classList):#判断classList里是否全是一类,count() 方法用于统计某个元素在列表中出现的次数
19         return classList[-1] #当全是一类时中止分割
20     #长度为1,返回出现次数最多的类别
21     if len(classList[0])==1: #当没有更多特征时中止分割,即分到最后一个特征也没有把数据彻底分开,就返回多数的那个结果
22         return majorityCnt(classList)
23     #按照信息增益最高选取分类特征属性
24     bestFeat=chooseBestFeatureToSplit(dataSet)#返回分类的特征序号,按照最大熵原则进行分类
25     bestFeatLable=labels[bestFeat] #该特征的label, #存储分类特征的标签
26     
27     myTree={bestFeatLable:{}} #构建树的字典
28     del(labels[bestFeat]) #从labels的list中删除该label
29     
30     featValues=[example[bestFeat] for example in dataSet]
31     uniqueVals=set(featValues)
32     for value in uniqueVals:
33         subLables=labels[:] #子集合 ,将labels赋给sublabels,此时的labels已经删掉了用于分类的特征的标签
34         #构建数据的子集合,并进行递归
35         myTree[bestFeatLable][value]=createTree(splitDataSet(dataSet,bestFeat,value),subLables)
36 return myTree
View Code

最后获得的决策树以下图所示

 

 

整个程序以下

  1 from math import log
  2 from operator import *
  3 
  4 def storeTree(inputTree,filename):
  5     import pickle
  6     fw=open(filename,'wb') #pickle默认方式是二进制,须要制定'wb'
  7     pickle.dump(inputTree,fw)
  8     fw.close()
  9     
 10 def grabTree(filename):
 11     import pickle
 12     fr=open(filename,'rb')#须要制定'rb',以byte形式读取
 13     return pickle.load(fr)
 14 
 15 
 16 def createDataSet():
 17     '''
 18     dataSet=[[1,1,'yes'],[1,1,'yes'],[1,0,'no'],[0,1,'no'],[0,1,'no']]
 19     labels = ['no surfacing','flippers']
 20     '''
 21     dataSet = [['sunny','hot','high','weak','no'],
 22                ['sunny','hot','high','strong','no'],
 23                ['overcast','hot','high','weak','yes'],
 24                ['rain','mild','high','weak','yes'],
 25                ['rain','cool','normal','weak','yes'],
 26                ['rain','cool','normal','strong','no'],
 27                ['overcast','cool','normal','strong','yes'],
 28                ['sunny','mild','high','weak','no'],
 29                ['sunny','cool','normal','weak','yes'],
 30                ['rain','mild','normal','weak','yes'],
 31                ['sunny','mild','normal','strong','yes'],
 32                ['overcast','mild','high','strong','yes'],
 33                ['overcast','hot','normal','weak','yes'],
 34                ['rain','mild','high','strong','no']]
 35     labels = ['outlook','temperature','humidity','wind']
 36     return dataSet,labels
 37 
 38 def calcShannonEnt(dataSet):#计算香农熵
 39     numEntries = len(dataSet)
 40     
 41     labelCounts = {}
 42     for featVec in dataSet:
 43         currentLabel = featVec[-1]   #取得最后一列数据,该属性取值状况有多少个
 44         if currentLabel not in labelCounts.keys():
 45             labelCounts[currentLabel] = 0
 46         labelCounts[currentLabel]+=1
 47     
 48     #计算熵
 49     shannonEnt = 0.0
 50     for key in labelCounts:
 51         prob = float(labelCounts[key])/numEntries
 52         shannonEnt -= prob*log(prob,2)
 53         
 54     return shannonEnt
 55 
 56 #定义按照某个特征进行划分的函数splitDataSet
 57 #输入三个变量(待划分的数据集,特征,分类值)
 58 #axis特征值中0表明no surfacing,1表明flippers
 59 #value分类值中0表明否,1表明是
 60 def splitDataSet(dataSet,axis,value):
 61     retDataSet = []
 62     for featVec in dataSet:#取大列表中的每一个小列表
 63         if featVec[axis]==value:
 64             reduceFeatVec=featVec[:axis]
 65             reduceFeatVec.extend(featVec[axis+1:])
 66             retDataSet.append(reduceFeatVec)
 67     
 68     return retDataSet #返回不含划分特征的子集
 69     
 70 def chooseBestFeatureToSplit(dataSet):
 71     numFeature = len(dataSet[0]) - 1
 72     baseEntropy = calcShannonEnt(dataSet)
 73     bestInforGain = 0
 74     bestFeature = -1
 75     
 76     for i in range(numFeature):
 77         featList = [number[i] for number in dataSet]#获得某个特征下全部值(某列)
 78         uniquelVals = set(featList) #set无重复的属性特征值,获得全部无重复的属性取值
 79         
 80         #计算每一个属性i的概论熵
 81         newEntropy = 0
 82         for value in uniquelVals:
 83             subDataSet = splitDataSet(dataSet,i,value)#获得i属性下取i属性为value时的集合
 84             prob = len(subDataSet)/float(len(dataSet))#每一个属性取值为value时所占比重
 85             newEntropy+= prob*calcShannonEnt(subDataSet)
 86         inforGain = baseEntropy - newEntropy #当前属性i的信息增益
 87         
 88         if inforGain>bestInforGain:
 89             bestInforGain = inforGain
 90             bestFeature = i
 91    
 92     return bestFeature#返回最大信息增益属性下标
 93     
 94 #递归建立树,用于找出出现次数最多的分类名称
 95 def majorityCnt(classList):
 96     classCount={}
 97     for vote in classList:#统计当前划分下每中状况的个数
 98         if vote not in classCount.keys():
 99             classCount[vote]=0
100         classCount[vote]+=1
101     sortedClassCount=sorted(classCount.items,key=operator.itemgetter(1),reversed=True)#reversed=True表示由大到小排序
102     #对字典里的元素按照value值由大到小排序
103    
104     return sortedClassCount[0][0]
105 
106 
107 def createTree(dataSet,labels):
108     classList=[example[-1] for example in dataSet]#建立数组存放全部标签值,取dataSet里最后一列(结果)
109     #类别相同,中止划分
110     if classList.count(classList[-1])==len(classList):#判断classList里是否全是一类,count() 方法用于统计某个元素在列表中出现的次数
111         return classList[-1] #当全是一类时中止分割
112     #长度为1,返回出现次数最多的类别
113     if len(classList[0])==1: #当没有更多特征时中止分割,即分到最后一个特征也没有把数据彻底分开,就返回多数的那个结果
114         return majorityCnt(classList)
115     #按照信息增益最高选取分类特征属性
116     bestFeat=chooseBestFeatureToSplit(dataSet)#返回分类的特征序号,按照最大熵原则进行分类
117     bestFeatLable=labels[bestFeat] #该特征的label, #存储分类特征的标签
118     
119     myTree={bestFeatLable:{}} #构建树的字典
120     del(labels[bestFeat]) #从labels的list中删除该label
121     
122     featValues=[example[bestFeat] for example in dataSet]
123     uniqueVals=set(featValues)
124     for value in uniqueVals:
125         subLables=labels[:] #子集合 ,将labels赋给sublabels,此时的labels已经删掉了用于分类的特征的标签
126         #构建数据的子集合,并进行递归
127         myTree[bestFeatLable][value]=createTree(splitDataSet(dataSet,bestFeat,value),subLables)
128     return myTree
129 
130 
131 if __name__=="__main__":
132     my_Data,labels = createDataSet()
133     
134     #print(calcShannonEnt(my_Data))
135     Mytree = createTree(my_Data,labels)
136     print(Mytree)
View Code
相关文章
相关标签/搜索