from math import log
import operator
from matplotlib.font_manager import FontProperties
import matplotlib.pyplot as plt
import copy
"""
函数说明:计算给定数据集的经验熵(香农熵)
Parameters:
dataSet:数据集
Returns:
shannonEnt:经验熵
Modify:
2018-03-12
"""
def calcShannonEnt(dataSet):
#返回数据集行数
numEntries=len(dataSet)
#保存每个标签(label)出现次数的字典
labelCounts={}
#对每组特征向量进行统计
for featVec in dataSet:
currentLabel=featVec[-1] #提取标签信息
if currentLabel not in labelCounts.keys(): #如果标签没有放入统计次数的字典,添加进去
labelCounts[currentLabel]=0
labelCounts[currentLabel]+=1 #label计数
shannonEnt=0.0 #经验熵
#计算经验熵
for key in labelCounts:
prob=float(labelCounts[key])/numEntries #选择该标签的概率
shannonEnt-=prob*log(prob,2) #利用公式计算
return shannonEnt #返回经验熵
"""
函数说明:创建测试数据集
Parameters:无
Returns:
dataSet:数据集
labels:分类属性
Modify:
2018-03-13
"""
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'],
[0, 1, 0, 2, 'yes'],
[1, 0, 0, 0, 'no'],
[1, 0, 0, 1, 'no'],
[1, 0, 1, 1, 'no'],
[1, 1, 0, 1, 'yes'],
[1, 1, 1, 1, 'yes'],
[1, 1, 1, 0, 'yes'],
[1, 0, 1, 2, 'yes'],
[2, 0, 1, 2, 'yes'],
[2, 0, 1, 1, 'yes'],
[2, 1, 0, 1, 'yes'],
[2, 1, 1, 1, 'no'],
[2, 1, 0, 2, 'yes'],
[2, 0, 0, 0, 'no']]
#分类属性
labels=['年龄','有工作','有自己的房子','信贷情况']
#返回数据集和分类属性
return dataSet,labels
"""
函数说明:按照给定特征划分数据集
Parameters:
dataSet:待划分的数据集
axis:划分数据集的特征
value:需要返回的特征值
Returns:
无
Modify:
2018-03-13
"""
def splitDataSet(dataSet,axis,value):
#创建返回的数据集列表
retDataSet=[]
#遍历数据集
for featVec in dataSet:
if featVec[axis]==value:
#去掉axis特征
reduceFeatVec=featVec[:axis]
#print(1)
#print(reduceFeatVec)
#将符合条件的添加到返回的数据集
reduceFeatVec.extend(featVec[axis+1:])
#print(2)
#print(reduceFeatVec)
retDataSet.append(reduceFeatVec)
#返回划分后的数据集
return retDataSet
"""
函数说明:计算给定数据集的经验熵(香农熵)
Parameters:
dataSet:数据集
Returns:
shannonEnt:信息增益最大特征的索引值
Modify:
2018-03-13
"""
def chooseBestFeatureToSplit(dataSet):
#特征数量
numFeatures = len(dataSet[0]) - 1
#计数数据集的香农熵
baseEntropy = calcShannonEnt(dataSet)
#信息增益
bestInfoGain = 0.0
#信息增益比
bestInfoGainRate = 0.0
#最优特征的索引值
bestFeature = -1
#遍历所有特征
for i in range(numFeatures):
# 获取dataSet的第i个所有特征
featList = [example[i] for example in dataSet]
#创建set集合{},元素不可重复
uniqueVals = set(featList)
#经验条件熵
newEntropy = 0.0
#信息增益比的除数
rateBase = 0.0
#计算信息增益
for value in uniqueVals:
#subDataSet划分后的子集
subDataSet = splitDataSet(dataSet, i, value)
#计算子集的概率
prob = len(subDataSet) / float(len(dataSet))
#根据公式计算条件熵
newEntropy += prob * calcShannonEnt((subDataSet))
#信息增益比的除数HA(D)
rateBase -= prob * log(prob,2)
#信息增益
print("第%d个特征的条件熵:%.3f" % (i, newEntropy))
infoGain = baseEntropy - newEntropy
print("第%d个特征的增益g(D,A):%.3f" % (i, infoGain))
print("第%d个特征的增益比的除数HA(D):%.3f" % (i, rateBase))
if (infoGain == 0):
infoGainRate = 0
else:
infoGainRate = infoGain / rateBase
#打印每个特征的信息增益
print('第%d个特征的增益比gR(D,A):%.3f' % (i, infoGainRate))
#关键代码部分start-------------------------------------------
#比较信息增益比C4.5
if (infoGainRate > bestInfoGainRate):
#比较信息增益ID3
#if (infoGain > bestInfoGain):
#关键代码部分end-------------------------------------------
#关键代码部分start-------------------------------------------
#更新信息增益比,找到最大的信息增益比C4.5
bestInfoGainRate = infoGainRate
#更新信息增益,找到最大的信息增益ID3
#bestInfoGain = infoGain
#关键代码部分end-------------------------------------------
#记录信息增益最大的特征的索引值
bestFeature = i
#返回信息增益最大特征的索引值
return bestFeature
"""
函数说明:统计classList中出现次数最多的元素(类标签)
Parameters:
classList:类标签列表
Returns:
sortedClassCount[0][0]:出现次数最多的元素(类标签)
Modify:
2018-03-13
"""
def majorityCnt(classList):
classCount={}
#统计classList中每个元素出现的次数
for vote in classList:
if vote not in classCount.keys():
classCount[vote]=0
classCount[vote]+=1
#根据字典的值降序排列
sortedClassCount=sorted(classCount.items(),key=operator.itemgetter(1),reverse=True)
return sortedClassCount[0][0]
"""
函数说明:创建决策树
Parameters:
dataSet:训练数据集
labels:分类属性标签
featLabels:存储选择的最优特征标签
Returns:
myTree:决策树
Modify:
2018-03-13
"""
def createTree(dataSet,labels,featLabels):
#取分类标签(是否放贷:yes or no)
classList=[example[-1] for example in dataSet]
#如果类别完全相同,则停止继续划分
if classList.count(classList[0])==len(classList):
return classList[0]
#遍历完所有特征时返回出现次数最多的类标签
if len(dataSet[0])==1:
print('majorityCnt')
return majorityCnt(classList)
#选择最优特征
bestFeat=chooseBestFeatureToSplit(dataSet)
print('bestFeat:%d' % bestFeat)
#最优特征的标签
bestFeatLabel=labels[bestFeat]
featLabels.append(bestFeatLabel)
#根据最优特征的标签生成树
myTree={bestFeatLabel:{}}
#得到训练集中所有最优特征的属性值
featValues=[example[bestFeat] for example in dataSet]
#去掉重复的属性值
uniqueVls=set(featValues)
#遍历特征,创建决策树
for value in uniqueVls:
#删除已经使用的特征标签
lables_copy = copy.deepcopy(labels)
del(lables_copy[bestFeat])
myTree[bestFeatLabel][value]=createTree(splitDataSet(dataSet,bestFeat,value),
lables_copy,featLabels)
return myTree
"""
函数说明:获取决策树叶子节点的数目
Parameters:
myTree:决策树
Returns:
numLeafs:决策树的叶子节点的数目
Modify:
2018-03-13
"""
def getNumLeafs(myTree):
numLeafs=0
firstStr=next(iter(myTree))
secondDict=myTree[firstStr]
for key in secondDict.keys():
if type(secondDict[key]).__name__=='dict':
numLeafs+=getNumLeafs(secondDict[key])
else: numLeafs+=1
return numLeafs
"""
函数说明:获取决策树的层数
Parameters:
myTree:决策树
Returns:
maxDepth:决策树的层数
Modify:
2018-03-13
"""
def getTreeDepth(myTree):
maxDepth = 0 #初始化决策树深度
firstStr = next(iter(myTree)) #python3中myTree.keys()返回的是dict_keys,不在是list,所以不能使用myTree.keys()[0]的方法获取结点属性,可以使用list(myTree.keys())[0]
secondDict = myTree[firstStr] #获取下一个字典
for key in secondDict.keys():
if type(secondDict[key]).__name__=='dict': #测试该结点是否为字典,如果不是字典,代表此结点为叶子结点
thisDepth = 1 + getTreeDepth(secondDict[key])
else: thisDepth = 1
if thisDepth > maxDepth: maxDepth = thisDepth #更新层数
return maxDepth
"""
函数说明:绘制结点
Parameters:
nodeTxt - 结点名
centerPt - 文本位置
parentPt - 标注的箭头位置
nodeType - 结点格式
Returns:
无
Modify:
2018-03-13
"""
def plotNode(nodeTxt, centerPt, parentPt, nodeType):
arrow_args = dict(arrowstyle="<-") #定义箭头格式
font = FontProperties(fname=r"./fonts/simsun.ttc", size=14) #设置中文字体
createPlot.ax1.annotate(nodeTxt, xy=parentPt, xycoords='axes fraction', #绘制结点
xytext=centerPt, textcoords='axes fraction',
va="center", ha="center", bbox=nodeType, arrowprops=arrow_args, FontProperties=font)
"""
函数说明:标注有向边属性值
Parameters:
cntrPt、parentPt - 用于计算标注位置
txtString - 标注的内容
Returns:
无
Modify:
2018-03-13
"""
def plotMidText(cntrPt, parentPt, txtString):
xMid = (parentPt[0]-cntrPt[0])/2.0 + cntrPt[0] #计算标注位置
yMid = (parentPt[1]-cntrPt[1])/2.0 + cntrPt[1]
createPlot.ax1.text(xMid, yMid, txtString, va="center", ha="center", rotation=30)
"""
函数说明:绘制决策树
Parameters:
myTree - 决策树(字典)
parentPt - 标注的内容
nodeTxt - 结点名
Returns:
无
Modify:
2018-03-13
"""
def plotTree(myTree, parentPt, nodeTxt):
decisionNode = dict(boxstyle="sawtooth", fc="0.8") #设置结点格式
leafNode = dict(boxstyle="round4", fc="0.8") #设置叶结点格式
numLeafs = getNumLeafs(myTree) #获取决策树叶结点数目,决定了树的宽度
depth = getTreeDepth(myTree) #获取决策树层数
firstStr = next(iter(myTree)) #下个字典
cntrPt = (plotTree.xOff + (1.0 + float(numLeafs))/2.0/plotTree.totalW, plotTree.yOff) #中心位置
plotMidText(cntrPt, parentPt, nodeTxt) #标注有向边属性值
plotNode(firstStr, cntrPt, parentPt, decisionNode) #绘制结点
secondDict = myTree[firstStr] #下一个字典,也就是继续绘制子结点
plotTree.yOff = plotTree.yOff - 1.0/plotTree.totalD #y偏移
for key in secondDict.keys():
if type(secondDict[key]).__name__=='dict': #测试该结点是否为字典,如果不是字典,代表此结点为叶子结点
plotTree(secondDict[key],cntrPt,str(key)) #不是叶结点,递归调用继续绘制
else: #如果是叶结点,绘制叶结点,并标注有向边属性值
plotTree.xOff = plotTree.xOff + 1.0/plotTree.totalW
plotNode(secondDict[key], (plotTree.xOff, plotTree.yOff), cntrPt, leafNode)
plotMidText((plotTree.xOff, plotTree.yOff), cntrPt, str(key))
plotTree.yOff = plotTree.yOff + 1.0/plotTree.totalD
"""
函数说明:创建绘制面板
Parameters:
inTree - 决策树(字典)
Returns:
无
Modify:
2018-03-13
"""
def createPlot(inTree):
fig = plt.figure(1, facecolor='white')#创建fig
fig.clf()#清空fig
axprops = dict(xticks=[], yticks=[])
createPlot.ax1 = plt.subplot(111, frameon=False, **axprops)#去掉x、y轴
plotTree.totalW = float(getNumLeafs(inTree))#获取决策树叶结点数目
plotTree.totalD = float(getTreeDepth(inTree))#获取决策树层数
plotTree.xOff = -0.5/plotTree.totalW; plotTree.yOff = 1.0#x偏移
plotTree(inTree, (0.5,1.0), '')#绘制决策树
plt.show()#显示绘制结果
if __name__ == '__main__':
dataSet, labels = createDataSet()
featLabels = []
myTree = createTree(dataSet, labels, featLabels)
print(myTree)
createPlot(myTree)
使用信息增益ID3的结果
#分类属性
labels=[‘年龄’,‘有工作’,‘有自己的房子’,‘信贷情况’]
dataSet=
[0, 1, 0, 1, ‘yes’],
[0, 1, 1, 0, ‘yes’],
[0, 1, 0, 2, ‘yes’],
[1, 1, 0, 1, ‘yes’],
[1, 1, 1, 1, ‘yes’],
[1, 1, 1, 0, ‘yes’],
[2, 1, 0, 1, ‘yes’],
[2, 1, 1, 1, ‘no’],
[2, 1, 0, 2, ‘yes’]
第0个特征(年龄)的增益g(D,A):0.197
第0个特征(年龄)的增益比的除数HA(D):1.585
第0个特征(年龄)的增益比gR(D,A):0.124
第1个特征(有自己的房子)的增益g(D,A):0.143
第1个特征(有自己的房子)的增益比的除数HA(D):0.991
第1个特征(有自己的房子)的增益比gR(D,A):0.144
使用信息增益比C4.5的结果
#分类属性
labels=[‘年龄’,‘有工作’,‘有自己的房子’,‘信贷情况’]
dataSet=
[0, 1, 0, 1, ‘yes’],
[0, 1, 0, 2, ‘yes’],
[1, 1, 0, 1, ‘yes’],
[2, 1, 0, 1, ‘yes’],
[2, 1, 0, 2, ‘yes’]
[0, 1, 1, 0, ‘yes’],
[1, 1, 1, 1, ‘yes’],
[1, 1, 1, 0, ‘yes’],
[2, 1, 1, 1, ‘no’],
第0个特征(年龄)的增益:0.197
第0个特征(年龄)的增益比:0.124
第1个特征(有自己的房子)的增益:0.143
第1个特征(有自己的房子)的增益比:0.144
第0个特征(年龄)的增益 > 第1个特征(有自己的房子)的增益
第0个特征(年龄)的增益比 < 第1个特征(有自己的房子)的增益比
第0个特征(年龄)的类别比较多,有3个,增益比较大。ID3更倾向于取类别比较多的特征。
python代码参照: