决策树涉及信息论,但是仅仅是熵的变化而已;不是机器学习基石里面的cart但是是ID3算法
#coding=gbk
from math import log
import operator
''' 3-1
功能:
计算给定数据集的熵
Parameters:
dataSet - 数据集
Returns:
shannonEnt - 经验熵(香农熵)
时间:2018/7/14
'''
def calcShannonEnt(dataSet):
#获取行数
numEntries = len(dataSet)
#保存每个标签出现次数的字典
labelCounts = {}
#为每组特征向量进行统计
for featVec in dataSet:
#提取标签信息
currentLabel = featVec[-1]
#如果标签没有放入统计次数的字典,添加进去
if currentLabel not in labelCounts.keys():
labelCounts[currentLabel] = 0
#Label 计数
labelCounts[currentLabel] +=1
#经验熵,也是香农熵
shannonEnt = 0.0
#计算香农熵
for key in labelCounts :
#选择这个标签的概率
prob = float(labelCounts[key])/numEntries
#利用公式计算
shannonEnt -= prob*log(prob,2)
return shannonEnt
''' 3-2
功能:
按照给定功能划分数据集
Parameters:
dataSet - 待划分的数据集
axis - 划分数据集的特征,注:这个特征应该是下标
value - 特征的值
return:
retDataSet -
时间:2018/7/14
'''
def splitDataSet(dataSet , axis , value):
#创建新的list对象,也就是返回的数据集列表
retDataSet = []
#遍历整个数据集
for featVec in dataSet:
if featVec[axis] == value:
#这一步是为了去掉Axis特征
##将特征向量的0~axis-1列存入列表reducedFeatVec
reducedFeatVec = featVec[:axis]
#将特征向量的axis+1~最后一列存入列表reducedFeatVec
#extend()是将另外一个列表中的元素(以列表中元素为对象)一一添加到当前列表中,构成一个列表
#比如a=[1,2,3],b=[4,5,6],则a.extend(b)=[1,2,3,4,5,6]
reducedFeatVec.extend(featVec[axis+1:])
#符合条件的添加到返回的数据集
retDataSet.append(reducedFeatVec)
#返回划分后的数据集
return retDataSet
'''
说明:
(1)在划分数据集函数中,传递的参数dataSet列表的引用,在函数内部对该列表对象进行修改,会导致列表内容发生改变
于是,为了消除该影响,我们应该在函数中创建一个新的列表对象,将对列表对象操作后的数据集存入新的列表对象中
(2)需要区分一下append()函数和extend()函数
这两种方法的功能类似,都是在列表末尾添加新元素,但是在处理多个列表时,处理结果有所不同:
比如:a=[1,2,3],b=[4,5,6]
那么a.append(b)的结果为:[1,2,3,[4,5,6]],即使用append()函数会在列表末尾添加人新的列表对象b
而a.extend(b)的结果为:[1,2,3,4,5,6],即使用extend()函数
'''
'''
3-3
功能:
选择最好的数据集划分
Parameters:
dataSet - 待划分的数据集
函数要求:
1.数据集必须是列表元素。列表元素具有相同的长度
2.最后一列或者每个实例的最后一个元素是实例的类别标签
时间:2018/7/14
'''
def chooseBestFeatureToSplit(dataSet):
#获取数据集特征的数目(不包含最后一列的类标签)
numFeatures = len(dataSet[0])-1
#计算划分前的信息熵
baseEntropy = calcShannonEnt(dataSet)
#最有信息增益 ------ 最优特征值(是特征i)
bestInfoGain = 0.0;bestFeature = -1
for i in range(numFeatures):
""" dataSet=[[1,2],[3,4],[5,6]]
[example[0] for example in dataSet]
Out[118]: [1, 3, 5]
所以下面是得到特征i的特征值列表
"""
featList = [example[i] for example in dataSet]
#利用set的几何性质——元素的唯一性,得到特征i的值;
#注:100个一样的特征值整合为1个、所以这里面没几个特征值了。
uniqueVals = set(featList)
#信息增益0.0
newEntropy = 0.0
#对特征的每一个取值,分别构建相应的分支
for value in uniqueVals:
#根据特征i取值把数据集划分为不同的子集
#利用splitDataSet()获取特征值value分支包含的数据集
subDataSet = splitDataSet(dataSet , i , value)
#计算特征取值value 对应子集占数据集的比例
pro = len(subDataSet)/float(len(dataSet))
#计算占比*当前子集的信息熵,并进行累加得到总的信息熵
newEntropy += pro * calcShannonEnt(subDataSet)
#计算按照此特征划分数据集的信息增益
#公式特征A,数据集D
#可以H(D,A) = H(D) - H(D/A)
infoGain = baseEntropy - newEntropy
#比较此增益与当前保存的最大的信息增益
if(infoGain > bestInfoGain):
#保存信息增益的最大化
bestInfoGain = infoGain
bestFeature = i
#返回最优特征
return bestFeature
'''
多数表决
功能:
在类标签不是唯一的情况下,如何决定定义该叶子结点
采用多数表决的方法决定该叶子结点的分类。
Parameters:
dataSet - 待划分的数据集
return:
sortedClassCount - 采取多数表决的原则选取分支下类标签种类最多的分类作为该叶子结点的分类
时间:2018/7/17
'''
def majorityCnt(classList):
#提前创建一个类标签的字典
#获取数据集中的最后一列的类标签,存入classList
classCount = {}
#遍历类标签列表中的每一个元素
for vote in classList:
#如果这个元素不在字典当中
if vote not in classCount.keys():
#在字典中添加新的键值对
classCount[vote] = 0
#否则 , 当前键值对的值 增加1
classCOunt[vote] += 1
#对字典的键对应的值对所在的列!!进行从大到小进行排序
#@classCount.items 字典对象;注:可以是列表,也可以是字典
#@key = operator.itemgetter(1) 获取列表对象的第一个域的值
#@reverse = true 降序排序,默认是升序排序
sortedClassCount = sorted(classCount.items,key=operator.itemgetter(1),reverse = true)
#返回出现次数最多的类标签
return sortedClassCount[0][0]
'''
3-4
功能:
创建树的函数代码
Parameters:
dataSet - 待划分的数据集
labels - 标签
return:
时间:2018/7/17
'''
def createTree(dataSet , labels):
#获取数据集中的最后一列的类标签,存入classList
classList = [example[-1] for example in dataSet]
'''
递归函数停止的第一个条就是:所有的类标签完全相同,则直接返回这类标签
count()这个函数是获取标签内第一个标签的数目,如果和长度是一样的
说明所有标签都是相同的,属于同一类。
'''
if classList.count(classList[0]) == len(classList):
return classList[0]
'''
递归函数停止的第二个停止条件就是:使用安乐所有的特征,仍然不能把数据集
划分成唯一的类别的分组。注:这里可以用3-3函数挑选出次数最多的类别作为返回值
下段代码:遍历完所有的特征属性,结果数据集的列只有1了,只剩下类标签了,只能停止
'''
#遍历完所有的特征属性,时数据集的列是1,只有类标签列
if len(dataSet[0]) == 1:
#采用多数表决的原则,确定类标签
return majorityCnt(classList)
#确定出当前最优的分类特征
bestFeat = chooseBestFeatureToSplit(dataSet)
#在特征标签列表中获取该特征对应的值
bestFeatLabel = labels[bestFeat]
#采用字典嵌套字典的方法,存储分类树的信息
myTree = {bestFeatLabel:{}}
##书上为del(labels[bestFeat])
##相当于操作原始列表内容,导致原始列表内容发生改变
##按此运行程序,报错'no surfacing'is not in list
##以下代码已改正
#复制当前特征标签列表,防止改变原始列表的内容
subLabels = labels[:]
#删除属性列表中当前分类数据集特征
del(subLabels[bestFeat])
#获取数据集中最有特征所在列
featValues = [example[bestFeat] for example in dataSet]
#采用set集合性质,获取特征的所有唯一取值
uniqueVals = set(featValues)
#遍历每一个特征取值
for value in uniqueVals:
#采用递归的方法利用这个特征对进行分类
#参数代表含义如下
#@bestFeatLabel 分类特征的特征标签值
#@dataSet 要分类的数据集
#@bestFeat 分类特征的标称值
#@value 标称型特征的取值
#@subLabels 去除分类特征后的子特征标签列表
myTree[bestFeatLabel][value] = createTree(splitDataSet\
(dataSet,bestFeat,value),subLabels)
return myTree
#----------------测试算法--------------------------------------
#完成决策树的构造后,采用决策树实现具体应用
#@intputTree 构造好的决策树
#@featLabels 特征标签列表
#@testVec 测试实例
def classify(inputTree , featLabels , testVec):
#找到树的第一个分类特征,或者说根节点‘no surfacing’
#注意python2.x和3.x区别,2.x可写成firstStr=inputTree.keys()[0]
#不支持3.X
#都是字典的形式输出
firstStr = list(inputTree.key())[0]
#从树中得到了该分类特征的分支,0和1
secondDict = inputTree[firstStr]
#根据分了特征的索引找到对应的标称型数据值
#'no surfacing'对应的索引是0
featIndex = featLabels.index(firstStr)
#遍历分类特征所有的取值
for key in secondDict.keys():
#测试实例的第0个特征取值等于第key个子节点
if testVec[featIndex] == key:
#type()函数判断该子节点是否为字典类型
if type(secondDict[key]).__name__ == 'dict'):
#子节点为字典类型,则从该分支树开始继续遍历分类
classLabel = classify(secondDict[key] , featLabels , testVec)
#如果是叶子节点,则返回节点取值
else:classLabel = secondDict[key]
return classLabel
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