#-*-coding:utf-8-*-
from math import log
import operator
# 3.1 计算给定数据集的香农熵calcShannonEnt()
def calcShannonEnt(dataSet):
# 首先计算数据集中实例的总数
numEntries = len(dataSet)
# 创建一个数据字典,
labelCounts = { }
for featVec in dataSet:
# 它的键值是最后一列的数值
currentLabel = featVec[-1]
# 如果当前键值不存在,则扩展字典并将当前键值加入字典
if currentLabel not in labelCounts.keys():
labelCounts[currentLabel] = 0
# 每个键值都记录了当前类别出现的次数
labelCounts[currentLabel] += 1
shannonEnt = 0.0
# 使用所有类别标签的发生频率计算类别出现的概率
for key in labelCounts:
prob = float(labelCounts[key])/numEntries
shannonEnt -= prob*log(prob,2)
return shannonEnt
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
# 3.2 按照给定特征划分数据集(带划分的数据集,划分数据集的特征,特征的返回值)
# python语言不同考虑内存分配问题,python语言在函数中的传递的是列表的引用,在函数内部对列表兑现的修改,将会影响该列表对象的整个生存周期。为了消除这种不良影响,我们需要在函数的开始生命一个新的列表对象
def splitDataSet(dataSet, axis, value):
# 因为该函数代码在同一数据集上被调用多次, 为了不修改原始数据集,创建一个新的列表对象。
retDataSet = []
# 数据集这个列表中的各个元素也是列表,我们要变美丽数据集中的每个元素,符合要求的值添加到新创建的列表中
for featVec in dataSet:
if featVec[axis] == value:
reducedFeatVec = featVec[:axis]
# 当我们按照某个特征划分数据集时,就需要将所有符合要求的元素抽取出来。代码中使用了自带的exten()和append()方法
reducedFeatVec.extend(featVec[axis+1:])
retDataSet.append(reducedFeatVec)
return retDataSet
# 接下来遍历整个数据集,循环计算香农熵和splitDataSet()函数,找到最好的特征划分方法。熵计算将告诉我们如何划分数据集是最好的数据组织方式
# 选择最好的数据集划分方式,选取特征,划分数据集,计算得出最好的划分数据集的特征
# 函数chooseBestFeatureToSplit()使用了calcShannonEnt(dataSet)和 splitDataSet(dataSet, axis, value),在函数中调用的数据需要满足一定的要求:
# 一是数据必须是一种由列表元素组成的列表,而且所有的列表元素都要具有相同的数据长度;二是,数据的最后一列或每个实例的最后一个元素是当前实例的类别标签
def chooseBestFeatureToSplit(dataSet):
numFeatures = len(dataSet[0]) - 1 #the last column is used for the labels
# 计算整个数据集的原始香农熵
baseEntropy = calcShannonEnt(dataSet)
# 保存最初的无序度量值
bestInfoGain = 0.0; bestFeature = -1
for i in range(numFeatures): #iterate over all the features
# 使用推导列表来创建新的列表,将数据集中所有第i个特征值或者所有可能存在的值写入这个新的list中
featList = [example[i] for example in dataSet]#create a list of all the examples of this feature
# 使用python语言原生的集合(set)数据类型。set数据类型和list数据类型相似,不同之处仅在于set类型中的每个值互不相同,从列表中创建集合时python语言得到列表中唯一元素值得最快方式
uniqueVals = set(featList) #get a set of unique values
newEntropy = 0.0
# 遍历当前特征中的唯一属性值,对每个特征划分一次数据集
for value in uniqueVals:
subDataSet = splitDataSet(dataSet, i, value)
prob = len(subDataSet)/float(len(dataSet))
# 然后计算新数据集的新熵值,并对所有唯一特征值得到的熵求和。信息增益是熵的减少或者是数据无序度的减少
newEntropy += prob * calcShannonEnt(subDataSet)
infoGain = baseEntropy - newEntropy #calculate the info gain; ie reduction in entropy
# 最后,比较所有特征中的信息增益,返回最好特征划分的额索引值
if (infoGain > bestInfoGain): #compare this to the best gain so far
bestInfoGain = infoGain #if better than current best, set to best
bestFeature = i
return bestFeature #returns an integer
# 与classify0部分的投票表决代码非常类似,该函数使用分类名称的列表,然后创建键值为classList中唯一值得数据字典,字典对象村出了classList中每个类标签出现的频率,最后利用operator操作键值排序字典,并返回出现次数最多的分类名称
def majorityCnt(classList):
classCount = { }
for vote in classList:
if vote not in classCount.keys():
classCount[vote] = 0
classCount[vote] += 1
sortedClassCount = sorted(classCount.iteritems(), key = operator.itemgetter(1), reverse = True)
return sortedClassCount[0][0]
# 3.4 创建树的函数代码
# 两个输入参数:数据集和标签列表。标签列表包含了数据集中所有特征的标签(算法本身并不需要这个变量,但是为了给出数据明确的含义)
def createTree(dataSet, labels):
classList = [example[-1] for example in dataSet]
# 递归函数的第一个停止条件是所有的类标签完全相同,则直接返回该标签
if classList.count(classList[0]) == len(classList):
return classList[0]
# 递归函数的第二个停止条件是使用完了所有特征,仍然不能将数据集划分成仅包含唯一类别的分组。
if len(dataSet[0])==1:
return majorityCnt(classList)
# 由于第二个条件无法简单地返回唯一的类标签,这里使3.3函数挑选出现次数最多的列别作为返回值
bestFeat = chooseBestFeatureToSplit(dataSet)
bestFeatLabel = labels[bestFeat]
# 下一步程序开始创建树,使用python语言的字典类型存储树的信息,也可以声明特殊的数据类型存储树。字典变量myTree存储了树的所有信息,对其后绘图十分重要
# 当前数据集选取的最好特征值存储在bestFeat中,
myTree = {bestFeatLabel:{}}
# 得到列表包含的所有属性值
del(labels[bestFeat])
featValues = [example[bestFeat] for example in dataSet]
uniqueVals = set(featValues)
# 最后遍历当前选择特征包含的所有属性值,在每个数据集划分上递归调用函数createTree(),得到的返回值将被插入到字典变量myTree中,因此函数终止执行时,字典中将会嵌套很好代表叶子节点信息的字典数据。
for value in uniqueVals:
# 这行代码复制了类标签,并将其存储在新列表变量subLabels中。
# 之所有这样做,是因为在python语言中函数参数是列表类型时,参数是按照引用方式传递的。为了保证每次调用createTree()函数时不改变原始列表的内容,使用新变量subLabels代替原始列表
subLabels = labels[:]
myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value),subLabels)
return myTree
def classify(inputTree, featLabels, testVec):
firstStr = inputTree.keys()[0]
secondDict = inputTree[firstStr]
featIndex = featLabels.index(firstStr)
for key in secondDict.keys():
if testVec[featIndex] == key:
if type(secondDict[key]).__name__ == 'dict':
classLabel = classify(secondDict[key], featLabels, testVec)
else:
classLabel = secondDict[key]
return classLabel
def storeTree(inputTree, filename):
import pickle
fw = open(filename, 'w')
pickle.dump(inputTree, fw)
fw.close()
def grabTree(filename):
import pickle
fr = open(filename)
return pickle.load(fr)
MLIA笔记_决策树
最新推荐文章于 2020-10-18 16:21:10 发布