机器学习实战 决策树 算法 笔记

trees.py 源码部分:

from math import log
import operator
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 creataDataSet():
dataSet=[[1,1,'yes'],[1,1,'yes'],[1,0,'no'],[0,1,'no'],[0,1,'no']]
labels=['no surfacing','flippers']
return dataSet,labels
def splitDataSet(dataSet,axis,value):
retDataSet=[]
for featVec in dataSet:
if featVec[axis] == value:
reducedFeatVec=featVec[:axis]
reducedFeatVec.extend(featVec[axis+1:])
retDataSet.append(reducedFeatVec)
return retDataSet
def chooesBestFeatureToSplit(dataSet):
numFeatures=len(dataSet[0])-1 
baseEntropy=calcShannonEnt(dataSet) 
bestInFoGain=0.0
bestFeature=-1 
for i in range(numFeatures):
featList=[example[i] for example in dataSet]
uniqueVals=set(featList)
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
if(infoGain>bestInFoGain):
bestInFoGain=infoGain
bestFeature=i
return bestFeature
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]
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)
bestFeat=chooesBestFeatureToSplit(dataSet) 
bestFeatLabel=labels[bestFeat]
myTree={bestFeatLabel:{}}
del(labels[bestFeat])
featValues=[example[bestFeat] for example in dataSet]
uniqueVals=set(featValues)
for value in uniqueVals:
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])==dict:
classLabel=classify(secondDict[key],featLabels,testVec)
else:classLabel=secondDict[key]
return classLabel

treePlotter.py  源码部分:

import matplotlib.pyplot as plt
decisionNode=dict(boxstyle="sawtooth",fc="0.2")
leafNode=dict(boxstyle="round4",fc="0.8")
arrow_args=dict(arrowstyle="->")
def plotNode(nodeTxt,centerPt,parentPt,nodeType):
createPlot.ax1.annotate(nodeTxt,xy=parentPt,xycoords='axes fraction',xytext=centerPt,textcoords='axes fraction',va="center",ha="center",bbox=nodeType,arrowprops=arrow_args)
def createPlot():
fig=plt.figure(1,facecolor='blue')
fig.clf()
createPlot.ax1=plt.subplot(111,frameon=False)
plotNode('a decisionNode',(0.5,0.1),(0.1,0.5),decisionNode)
plotNode('a leafNode',(0.8,0.1),(0.3,0.5),leafNode)
plt.show()
def getNumLeafs(myTree):
numLeafs=0
firstStr=myTree.keys()[0]
secondDict=myTree[firstStr]
for key in secondDict.keys():
if type(secondDict[key])==dict:
numLeafs+=getNumLeafs(secondDict[key])
else: numLeafs+=1
return numLeafs
def getTreeDepth(myTree):
maxDepth=0
firstStr=myTree.keys()[0]
secondDict=myTree[firstStr]
for key in secondDict.keys():
if type(secondDict[key])==dict:
thisDepth=1+getTreeDepth(secondDict[key])
else: thisDepth=1
if thisDepth>maxDepth:maxDepth=thisDepth
return maxDepth
def retrieveTree(i):
listOfTree=[{'no surfacing':{0:'no',1:{'flippers':{0:'no',1:'yes'}}}},{'no surfacing':{0:'no',1:{'flippers':{0:{'head':{0:'no',1:'yes'}},1:'no'}}}}]
return listOfTree[i]
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)
def plotTree(myTree,parentPt,nodeTxt):
numLeafs=getNumLeafs(myTree)
depth=getTreeDepth(myTree)
firstStr=myTree.keys()[0]
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
for key in secondDict.keys():
if type(secondDict[key])==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 
def createPlot2(inTree):
fig=plt.figure(1,facecolor='white')
fig.clf()
axprops=dict(xticks=[],yticks=[])
createPlot.ax1=plt.subplot(111,frameon=False,**axprops)
plotTree.totalW=float(getNumLeafs(inTree))
plotTree.totalD=float(getTreeDepth(inTree))
plotTree.xOff=-0.5/plotTree.totalW
plotTree.yOff=1.0
plotTree(inTree,(0.5,1.0),'')
plt.show()

笔记部分:

注:一般情况下 如果出现错误都是由于Python换行的问题 tab键和space键不公用 我用的是notepad++ 文本工具 没有自带换行功能 
烦的一B 。。。 也没刻意去下个编辑器。。就随便弄着写 发现好麻烦 换行的问题 还有函数for if 的区分全靠缩进格数 
如果缩进格数错了就直接GG 还念C/java 的大括号。。。(*^__^*) 嘻嘻…… 因为这个问题出现了巨大问题 导致我现在对Python
有点烦。。。 不过方便是比java/c方便多了 就是这个缩进问题。。。改天弄个编辑器玩玩。。看能不能解决 要是不能解决我就GG




1:香农熵:集合信息的度量方式成为香农熵或者简称熵,这个名字来源于信息论之父克劳德·香农。熵定义为信息的期望值。
如果待分类的事务可能划分在多个分类中。关于期望的计算就是对于x*p(x)求和,p(x)表示事件x发生的概率。我们计算香农熵也遵循着这种
方式,下面是计算给定数据集的香农熵:
from math import log /*导入math模块中的log函数,因为计算X的信息时需要用到log函数
def calcShannonEnt(dataSet) /* 输入数据集
numEntries=len(dataSet) /*统计数据集中实例的总数
labelCounts={} /*定义一个字典
for featVec in dataSet: /*依照每行遍历数据集
currentLabel=featVec[-1] /*取每行中最后一个元素 
if currentLabel not in labelCounts.keys(): /*如果最后一个元素不是键值
labelCounts[currentLabel]=0 /*设置currentLabel为建,值为0 
labelCounts[currentLabel] +=1 /*由于字典中出现了一个currentLabel,就是上面被置为0的,所以变为1,
/*上面一条语句是新建,若没执行if则说明已有一条currentLabel存在,即+1
shannonEnt = 0.0
for key in labelCounts:
prob = float(labelCounts[key])/numEntries /*求概率,出现次数/总数
shannonEnt -=prob*log(prob,2) /*求对数,自增 就是期望, 期望就是对概率和事件乘集的求和
return shannonEnt /*数据后
第一个for循环本质是记录键出现的次数,若没出现则新建,新建后置次数为0 ,然后通过语句+1,这是对+=1这条语句的利用
2:根据数据集的特征进行划分。
def splitDataSet(dataSet,axis,value): /*输入样本集, axis代表样本集中第i行的第axis号元素,表示特征属性
value代表想要测量第axis号元素的值 表示特征属性的值
retDataSet=[] /%为了存储分类完毕的集合
for featVec in dataSet:
if featVec[axis] == value: /*若第 axis号元素等于初始给定的value值 则记录下来
reducedFeatVec=featVec[:axis] 
reducedFeatVec.extend(featVec[axis+1:]) /*以上两条语句是把featVec数组中除了特征值的元素给保留下来
[1,0,yes] 若0零特征值 则最后保存的是[1,yes]
retDataSet.append(reducedFeatVec)
return retDataSet
3:选择最好的数据集划分方式。我们是按照获取最大信息增益来划分的数据集。也就是计算最小的熵。初始熵是dataSet的熵,
通过划分得到的熵是该划分所占原始数据的百分比乘以该划分下来的子集的熵 然后对两个子集的熵相加才是划分后的熵
注意!!!注意区分【特征&特征值】 
def chooesBestFeatureToSplit(dataSet):
numFeatures=len(dataSet[0])-1 /* 特征值的数量 减去yes or no 
baseEntropy=calcShannonEnt(dataSet) /* 先置最优熵为为划分是数据集的熵
bestInFoGain=0.0;beastFeatutre=-1 /* 默认最好的特征为-1
for i in range(numFeatures) /*在特征数量内遍历 遍历所有的特征 i代表的是axis
featList=[example[i] for exapmle in dataSet] /* featList数组存放的是特征值 存放len(dataSet)个
uniqueVals=set(featList) /* set集合中存放的是不同的 在本列中是[0,1] 
所代表的的是特征值 也就是value
newEntropy=0.0
for value in uniqueVals: /* 进行遍历 调用splitDataSet()进行划分
subDataSet=splitDataSet(dataSet,i,value)
prob=len(subDataSet)/float(len(dataSet)) /*划分后在原集合中占的比例
newEntropy +=prob*calcShannonEnt(subDataSet) /* 比例乘以划分后集合的熵 相加表示两个比例相加为1
然后各自乘以各自划分集合的熵 与原数据的熵比较
infoGain=baseEntropy-newEntropy /* 表示的是未划分集合(原集合dataSet)的熵减去划分好各自熵的和
if(infoGain>bestInFoGain): /* 与0相比 若大于则表示熵减小了 也就是这种划分方式是有利的
bestInFoGain=infoGain /*用这个划分与原数据的熵的差值代替0,并设置该划分为当前最优划分
在随后的循环中直接计算下次划分与当前最优划分的熵的大小
bestFeature=i
return bestFeature /* 输出的是最好的划分中的特征 
4:通过多数表决的方式确定叶子节点的分类
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)
/* iteritems是返回当前字典操作后的迭代
key是获取itemgetter的第一个域的值并保持 域从0开始 为排序的关键字
reverse默认False是升序 True表示降序
return sortedClassCount[0][0]
5:创建tree 在for循环中创建树myTree 通过循环字典的方式来保存myTree 
def createTree(dataSet,labels):
classList=[example[-1] for example in dataSet] /* 取得dataset的最后一列数据保持在classlist中
if classList.count(classList[0])==len(classList): /*如果classlist中的第一个值在classlist中的总数等于长度
也就是classlist中所有值都一样
return classList[0] /*返回值
if len(dataSet[0])==1:
return majorityCnt(classList) /*如果
bestFeat=chooesBestFeatureToSplit(dataSet) /*最优的特征 先划分出判断节点 
bestFeatLabel=labels[bestFeat] /*最优特征对应的标签
myTree={bestFeatLabel:{}} /*把标签存在树中 这时候树是用循环字典来保存的
del(labels[bestFeat]) /*在标签中删除已经选择过的标签
featValues=[example[bestFeat] for example in dataSet]
uniqueVals=set(featValues) 
for value in uniqueVals:
subLabels=labels[:] /*去掉已经删除过的标签后的标签集合
myTree[bestFeatLabel][value]=createTree(splitDataSet(dataSet,bestFeat,value),subLabels) 
/*循环字典的值,这里是根据最优特征划分出来的集合,然后把该集合放入树中
return myTree
6:通过上面的代码我们了解了如何创建tree ,但是字典模式的tree不是很方便理解,下面我们利用matplotlib来绘制tree
首先绘制树节点,然后用文本注解 对于代码中的各种值,自己更改几次就显然能清楚什么是代表什么意思 
import matplotlib.pyplot as plt

decisionNode=dice(boxstyle="sawtooth",fc="0.8") /* 格式 格子类型是sawtooth 锯齿形 亮度是0.8 
leafNode=dice(boxstyle="round4",fc="0.8") /* fc是rnage(0-1) 0黑色 1白色
arrow_args=dict(arrowstyle="<-") /*箭头标签格式

def plotNode(nodeTxt,centerPt,parentPt,nodeType): /*绘制带箭头的注解 节点文字,子节点,父节点,节点类型
createPlot.ax1.annotate(nodeTxt,xy=parentPt,xycoords='axes fraction',xytext=centerPt,textcoords='axes fraction'
,va='center',ha='center',bbox=nodeType,arrowprops=arrow_args)
/* 首先这是一句调用createPlot.ax1 该值存放在createPlot()函数中,Python所有变量都是
全局变量,可以直接访问,不像java/C一样需要调用函数内变量时通过 
bbox表示边框类型 va,ha表示给定坐标是node的中心 而不是左侧或者右侧 xycoords是轴对称
createPlot.createPlot.ax1来调用
def createPlot(): /*开始绘制
fig=plt.figure(1,facecolor='white') /*建立白色绘图区
fig.clf() /*清除场景 用在绘制之前清除之前留下的图像
createPlot.ax1=plt.subplot(111,frameon=False) /*不用框架
plotNode('a decision node',(0.5,0.1),(0.1,0.5),decisionNode)
plotNode('a leaf node',(0.8,0.1),(0.3,0.8),leafNode)
plt,show()

7:获取叶节点的数目和树的层数 代码过于简单不做分析 QAQ 
卧槽 尼玛发现个史诗巨鳄 _name_ 如果加上_name_ 会报错 str object has no attribute ‘_name_‘
我在Python27下亲自试过 不加_name_ type(secondDict[key]) 直接输出的就是type'dict' 或者是 type'str' 
但是最重要事情来了。。。'dict' 和dict的区别 ==右侧如果是'dict' 直接字典就不是字典了。。而改成dict 
不加单引号 就正确了 加上单引号直接就是叶子节点2 深度是1 但是如果不加单引号就正确了 和书上一样 也和
字典中实际一样 和树中一样 叶子节点是3 深度是2 麻痹烦死了。。。这么个小东西百度了半天没结果 
反正我是自己试了老半天。。。初学者真心伤不起。。。。调试了半小时。。囧
8:根据7的代码进行绘制tree,首先在线条中间绘制[0,1],不分析 代码中1.0的意思是绘制的总大小,因为坐标都是0-1的
所以通过1/W 1/D来表示每个节点 或者 每层的树 占据坐标轴的比例 方便定位
初始的时候plotTree.yOff=1.0是因为绘制树的顶层 plotT.xOff初始值是第一个父节点的位置,当然是顶层最中间,
1/D就是每层的高度 那么yOff-1/D就是偏移了一小格 就是从第一层变为第二层 然后循环递归 这段有点小费脑筋。。。
花了40分钟才理解(没看书中下文的解释 看了的话可能会很简单) 要结合下一段的createPlot的代码观察 
def plotTree(myTree,parentPt,nodeTxt): /*输入 树,父节点,文本 开始绘制树
numLeafs=getNumLeafs(myTree) /*取得叶子节点的个数和深度
depth=getTreeDepth(myTree) 
firstStr=myTree.keys()[0] 
cntrPt=(plotTree.xOff+(1.0+float(numLeafs))/2.0/plotTree.totalW,plotTree.yOff)
/* plotTree.totalD和plotTree.totalW是深度和子节点的float值
plotTree.xOff和plotTree.yOff是 -(1除以2倍的叶子节点个数)和1.0
plotMidText(cntrPt,parentPt,nodeTxt) /*两个节点中间添加文本
plotNode(firstStr,cntrPt,parentPt,decisionNode) /* 节点文字 子节点 父节点 节点类型
secondDict=myTree[firstStr] /*myTree是字典 fristStr在上面表示节点文字 在字典表示键 取到键值
plotTree.yOff=plotTree.yOff-1.0/plotTree.totalD
for key in secondDict.keys():
if type(secondDict[key])==dict: /*如果之后是字典 就是判断节点 就要开始绘制下一层 如果不是那么
就是叶子节点 之前的cntrpt就是现在的parentpt
plotTree(secondDict[key],cntrPt,str(key))
else:
plotTree.xOff=plotTree.xOff+1.0/plotTree.totalW
plotTree(secondDict[key],(plotTree.xOff,plotTree.yOff),cntrPt,leafNode)
plotMidText((plotTree.xOff,plotTree.yOff),cntrPt,str(key))
plotTree.yOff=plotTree.yOff+1.0/plotTree/totalD
9:createPlott 的代码没有什么好说的。。。和上面的绘制代码差不多 比较类似 秒懂的节奏 不分析 
为了方便区分 我用createPlot2来代替 
10:算法都写完了 现在就到了见证奇迹的时刻,测试算法,使用决策树执行分类。



你可能感兴趣的:(机器学习实战,Python,决策树)