《 machine learning in action》之决策树
计算给定数据集的香农熵
创建文件trees.py
# -*- coding=utf-8 -*-
#计算给定数据集的熵
from math import log
def calcShannonEnt(dataSet):
numEntries = len(dataSet)
labelCounts = {} #实例总数
for featVec in dataSet:
#为所有可能分类创建字典
currentLabel = featVec[-1] #将最后一列,即分类结果存入currentLabel
if currentLabel not in labelCounts.keys(): #若分类结果已经在labelCounts这个字典中
labelCounts[currentLabel] = 0 #若当前label不存在,则扩展字典,加入此键值
labelCounts[currentLabel] += 1 #字典中的每个键值都记录了当前类别的数量
#print 'labelCounts,currentLabel:',labelCounts,currentLabel
#print 'labelCounts.keys()',labelCounts.keys()
#print 'labelCounts.values()',labelCounts.values()
shannonEnt = 0.0
for key in labelCounts:
prob = float(labelCounts[key])/numEntries
shannonEnt -= prob*log(prob,2) #求以2为底的对数,其和即为熵
#print 'labelCounts[key]',labelCounts[key]
return shannonEnt
创建或导入dataSet
#鱼类数据集
def createDataSet():
dataSet = [[1,1,'yes'],
[1,1,'yes'],
[1,0,'no'],
[0,1,'no'],
[0,1,'no']]
labels = ['no surfacing','flippers']
return dataSet,labels
在命令提示符下输入:
In[9]: import trees
In[10]: myDat,labels = trees.createDataSet()
In[11]: myDat
Out[11]:
[[1, 1, 'yes'], [1, 1, 'yes'], [1, 0, 'no'], [0, 1, 'no'], [0, 1, 'no']]
In[12]: labels
Out[12]:
['no surfacing', 'flippers']
In[13]: trees.calcShannonEnt(myDat)
#混合的数据越多,熵越高,测试:
In[18]: myDat[0][-1] = 'maybe'
In[19]: myDat
Out[19]:
[[1, 1, 'maybe'], [1, 1, 'yes'], [1, 0, 'no'], [0, 1, 'no'], [0, 1, 'no']]
In[20]: trees.calcShannonEnt(myDat)
Out[20]:
1.3709505944546687
划分数据集
取出符合要求某个特征属性值的样本,并将其此特征从数据集中去除。
#按照给定特征划分数据集
def splitDataSet(dataSet,axis,value): #待划分的数据集,划分数据集的特征(第几列),需要返回的特征的值,(axis-->a,value-->v)
#为了不修改原始数据集,创建新的list对象
retDataSet = []
for featVec in dataSet:
#将符合特征的数据抽取出来
if featVec[axis] == value: #Python中的数据.即featVec中的数据从第0列开始,[0,1,2,3,4,5,……]
reducedFeatVec = featVec[:axis] #[:axis]表示前axis行,若axis为3,则表示取festVec的前3列,即第[0,1,2]列
reducedFeatVec.extend(featVec[axis+1:]) #[axis+1:] 表示跳过axis+1列,从下一列数据取到最后一列的数据,即跳过第[3]列,从第[4]列到最后一列
retDataSet.append(reducedFeatVec)
return retDataSet
在Python命令行中输入:
In[16]: reload(trees)
Out[16]:
<module 'trees' from '/home/vickyleexy/PycharmProjects/Classification of contact lenses/trees.py'>
In[17]: myDat,labels = trees.createDataSet()
In[18]: myDat
Out[18]:
[[1, 1, 'yes'], [1, 1, 'yes'], [1, 0, 'no'], [0, 1, 'no'], [0, 1, 'no']]
In[19]: trees.splitDataSet(myDat,0,1) #抽取出数据集中第0列中数据值为1的样本,并将此列剔除,组成新的样本集
Out[19]:
[[1, 'yes'], [1, 'yes'], [0, 'no']]
In[20]: trees.splitDataSet(myDat,0,0) #抽取出数据集中第0列中数据值为0的样本,并将此列剔除,组成新的样本集
Out[20]:
[[1, 'no'], [1, 'no']]
In[21]: trees.splitDataSet(myDat,1,0) #抽取出数据集中第1列中数据值为0的样本,并将此列剔除,组成新的样本集
Out[21]:
[[1, 'no']]
选取最好的数据集划分方式
即选取取出信息增益最大的特征。信息增益是熵减少或者说信息无序度的减少,信息增益越大,信息无序度减少越大,信息的可确定性越强。
#选择最好的数据集划分数据,即计算每种特征信息熵 Gain(D,a)
def chooseBestFeatureToSplit(dataSet):
numFeatures = len(dataSet[0]) - 1 #可选特征的个数
baseEntropy = calcShannonEnt(dataSet) # 计算总体信息熵Ent(D)
bestInfoGain = 0.0; #初始化最好的信息增益
bestFeature = -1; #初始化选取的最好的特征
#创建唯一的分类标签列表
for i in range(numFeatures): #遍历各个特征
featList = [example[i] for example in dataSet] #将第i列特征的值选取出来,并存入featList
uniqueVals = set(featList) #将featList存为集合的格式,即去除featList中重复的元素,因此uniqueVals中为每个特征的中不同的属性
newEntropy = 0.0
#计算每种划分方式的信息熵
for value in uniqueVals: #遍历每个特征中的各个属性
subDataSet = splitDataSet(dataSet,i,value) #选取符合条件的特征,并将此特征从样本中去除,以便进行下面的进一步计算
prob = len(subDataSet)/float(len(dataSet)) #符合此特征中此属性的个数占总体样本的比例,即|D^v|/|D|
newEntropy += prob * calcShannonEnt(subDataSet) #计算各个特征中每个属性的加权信息熵的和
infoGain = baseEntropy - newEntropy #计算信息增益,即Gain(D,a)
#计算最好额信息增益
if (infoGain > bestInfoGain):
bestInfoGain = infoGain
bestFeature = i
print '最好的特征,最好的信息增益:',bestFeature,bestInfoGain
return bestFeature
在Python命令行下输入以下代码进行测试:
In[9]: reload(trees)
Out[9]:
<module 'trees' from '/home/vickyleexy/PycharmProjects/Classification of contact lenses/trees.py'>
In[10]: myDat,labels = trees.createDataSet()
In[11]: myDat
Out[11]:
[[1, 1, 'yes'], [1, 1, 'yes'], [1, 0, 'no'], [0, 1, 'no'], [0, 1, 'no']]
In[12]: trees.chooseBestFeatureToSplit(myDat)
最好的特征,最好的信息增益: 0 , 0.419973094022
Out[12]:
0
递归构建决策树
递归结束的条件:
- 程序遍历完所有划分数据集的属性
- 每个分支下的所有实例都具有相同的分类
若已经处理了所有的属性,但类标签依然不是唯一的,此时采用多数表决的方法决定叶子节点的分类。
在trees.py文件的顶部添加operator库
import operator
统计剩余样本中属于哪一类别最多的数量最多:
def majorityCnt(classList): #classList类别列表
classCount = {} #新建立一个字典
for vote in classList: #遍历所有的类别
#统计各类别剩余样本的数量
if vote not in classCount.keys(): #如果此类别不在classCount的key中
classCount[vote] = 0
classCount[vote] += 1
sortedClassCount = sorted(classCount.iteritems(),key = operator.itemgetter(1),reverse = True) #将统计好的剩余样本类别和其数量,根据其数量进行从大到小的排序
return sortedClassCount[0][0] #取出剩余样本中数量最大的类别名称
创建树的函数代码:
#创建树的函数代码
def createTree(dataSet,labels):
classList = [example[-1] for example in dataSet]
#类别完全相同则停止继续划分
if classList.count(classList[0]) == len(classList): #若classList中第一个类别的数量等于样本的总数量,即样本类别完全相同
return classList[0]
#遍历完所有特征时返回出现次数最多的
if len(dataSet[0]) == 1:
return majorityCnt(classList)
bestFeat = chooseBestFeatureToSplit(dataSet)
bestFeatLabel = labels[bestFeat]
myTree = {bestFeatLabel:{}}
del(labels[bestFeat]) #去掉已经使用的(bestFeat)类标签
#得到最好的特征这一列,包含其所有属性值的列表
featValues = [example[bestFeat] for example in dataSet]
uniqueVals = set(featValues) #去掉featValues列表中重复的属性
for value in uniqueVals: #遍历最好的特征中所有的属性
subLabels = labels[:] #复制del(labels[bestFeat])后的结果,并将其存在新列表变量subLabels中
myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet,bestFeat,value),subLabels)
return myTree
命令行中输入:
In[66]: reload(trees)
Out[66]:
<module 'trees' from '/home/vickyleexy/PycharmProjects/Classification of contact lenses/trees.py'>
In[67]: myDat,labels = trees.createDataSet()
In[68]: myTree = trees.createTree(myDat,labels)
最好的特征,最好的信息增益: 0 , 0.419973094022
最好的特征,最好的信息增益: 0 , 0.918295834054
In[69]: myTree
Out[69]:
{'no surfacing': {0: 'no', 1: {'flippers': {0: 'no', 1: 'yes'}}}}