python机器学习实战(二)
html
版权声明:本文为博主原创文章,转载请指明转载地址node
http://www.cnblogs.com/fydeblog/p/7159775.htmlpython
前言算法
这篇notebook是关于机器学习监督学习中的决策树算法,内容包括决策树算法的构造过程,使用matplotlib库绘制树形图以及使用决策树预测隐形眼睛类型.
操做系统:ubuntu14.04(win也ok) 运行环境:anaconda-python2.7-jupyter notebook 参考书籍:机器学习实战和源码 notebook writer ----方阳 数据库
注意事项:在这里说一句,默认环境python2.7的notebook,用python3.6的会出问题,还有个人目录可能跟大家的不同,大家本身跑的时候记得改目录,我会把notebook和代码以及数据集放到结尾的百度云盘,方便大家下载!ubuntu
决策树原理:不断经过数据集的特征来划分数据集,直到遍历全部划分数据集的属性,或每一个分支下的实例都具备相同的分类,决策树算法中止运行。架构
决策树的优缺点及适用类型
优势 :计算复杂度不高, 输出结果易于理解,对中间值的缺失不敏感,能够处理不相关特征数据。
缺点 :可能会产生过分匹配问题。
适用数据类型:数值型和标称型app
先举一个小例子,让你了解决策树是干吗的,简单来讲,决策树算法就是一种基于特征的分类器,拿邮件来讲吧,试想一下,邮件的类型有不少种,有须要及时处理的邮件,无聊是观看的邮件,垃圾邮件等等,咱们须要去区分这些,好比根据邮件中出现里你的名字还有你朋友的名字,这些特征就会就能够将邮件分红两类,须要及时处理的邮件和其余邮件,这时候在分类其余邮件,例如邮件中出现buy,money等特征,说明这是垃圾推广文件,又能够将其余文件分红无聊是观看的邮件和垃圾邮件了。python2.7
1.决策树的构造机器学习
1.1 信息增益
试想一下,一个数据集是有多个特征的,咱们该从那个特征开始划分呢,什么样的划分方式会是最好的?
咱们知道划分数据集的大原则是将无序的数据变得更加有序,这样才能分类得更加清楚,这里就提出了一种概念,叫作信息增益,它的定义是在划分数据集以前以后信息发生的变化,变化越大,证实划分得越好,因此在划分数据集的时候,得到增益最高的特征就是最好的选择。
这里又会扯到另外一个概念,信息论中的熵,它是集合信息的度量方式,熵变化越大,信息增益也就越大。信息增益是熵的减小或者是数据无序度的减小.
一个符号x在信息论中的信息定义是 l(x)= -log(p(x)) ,这里都是以2为底,再也不复述。
则熵的计算公式是 H =-∑p(xi)log(p(xi)) (i=1,2,..n)
下面开始实现给定数据集,计算熵
参考代码:
1 from math import log #we use log function to calculate the entropy 2 import operator
1 def calcShannonEnt(dataSet): 2 numEntries = len(dataSet) 3 labelCounts = {} 4 for featVec in dataSet: #the the number of unique elements and their occurance 5 currentLabel = featVec[-1] 6 if currentLabel not in labelCounts.keys(): labelCounts[currentLabel] = 0 7 labelCounts[currentLabel] += 1 8 shannonEnt = 0.0 9 for key in labelCounts: 10 prob = float(labelCounts[key])/numEntries 11 shannonEnt -= prob * log(prob,2) #log base 2 12 return shannonEnt
程序思路: 首先计算数据集中实例的总数,因为代码中屡次用到这个值,为了提升代码效率,咱们显式地声明一个变量保存实例总数. 而后 ,建立一个数据字典labelCounts,它的键值是最后一列(分类的结果)的数值.若是当前键值不存在,则扩展字典并将当前键值加入字典。每一个键值都记录了当前类别出现的次数。 最后 , 使用全部类标签的发生频率计算类别出现的几率。咱们将用这个几率计算香农熵。
让咱们来测试一下,先本身定义一个数据集
下表的数据包含 5 个海洋动物,特征包括:不浮出水面是否能够生存,以及是否有脚蹼。咱们能够将这些动物分红两类: 鱼类和非鱼类。
根据上面的表格,咱们能够定义一个createDataSet函数
参考代码以下
1 def createDataSet(): 2 dataSet = [[1, 1, 'yes'], 3 [1, 1, 'yes'], 4 [1, 0, 'no'], 5 [0, 1, 'no'], 6 [0, 1, 'no']] 7 labels = ['no surfacing','flippers'] 8 #change to discrete values 9 return dataSet, labels
把全部的代码都放在trees.py中(如下在jupyter)
cd /home/fangyang/桌面/machinelearninginaction/Ch03
/home/fangyang/桌面/machinelearninginaction/Ch03
import trees
myDat, labels = trees.createDataSet()
myDat #old data set
如今来测试测试
import trees myDat, labels = trees.createDataSet() trees.chooseBestFeatureToSplit(myDat) #return the index of best character to split
0
好了,到如今,咱们已经知道如何基于最好的属性值去划分数据集了,如今进行下一步,如何去构造决策树
决策树的实现原理:获得原始数据集, 而后基于最好的属性值划分数据集,因为特征值可能多于两个,所以可能存在大于两个分支的数据集划分。第一次划分以后, 数据将被向下传递到树分支的下一个节点, 在这个节点上 ,咱们能够再次划分数据。所以咱们能够采用递归的原则处理数据集。
递归结束的条件是:程序遍历完全部划分数据集的属性, 或者每一个分支下的全部实例都具备相同的分类。
这里先构造一个majorityCnt函数,它的做用是返回出现次数最多的分类名称,后面会用到
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]
这个函数在实战一中的一个函数是同样的,复述一遍,classCount定义为存储字典,每当,因为后面加了1,因此每次出现键值就加1,就能够就算出键值出现的次数里。最后经过sorted函数将classCount字典分解为列表,sorted函数的第二个参数导入了运算符模块的itemgetter方法,按照第二个元素的次序(即数字)进行排序,因为此处reverse=True,是逆序,因此按照从大到小的次序排列。
让咱们来测试一下
import numpy as np classList = np.array(myDat).T[-1]
classList
array(['yes', 'yes', 'no', 'no', 'no'], dtype='|S21')
majorityCnt(classList) #the number of 'no' is 3, 'yes' is 2,so return 'no'
1 def createTree(dataSet,labels): 2 classList = [example[-1] for example in dataSet] 3 if classList.count(classList[0]) == len(classList): 4 return classList[0]#stop splitting when all of the classes are equal 5 if len(dataSet[0]) == 1: #stop splitting when there are no more features in dataSet 6 return majorityCnt(classList) 7 bestFeat = chooseBestFeatureToSplit(dataSet) 8 bestFeatLabel = labels[bestFeat] 9 myTree = {bestFeatLabel:{}} 10 del(labels[bestFeat]) #delete the best feature , so it can find the next best feature 11 featValues = [example[bestFeat] for example in dataSet] 12 uniqueVals = set(featValues) 13 for value in uniqueVals: 14 subLabels = labels[:] #copy all of labels, so trees don't mess up existing labels 15 myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value),subLabels) 16 return myTree
前面两个if语句是判断分类是否结束,当全部的类都相等时,也就是属于同一类时,结束再分类,又或特征所有已经分类完成了,只剩下最后的class,也结束分类。这是判断递归结束的两个条件。通常开始的时候是不会运行这两步的,先选最好的特征,使用 chooseBestFeatureToSplit函数获得最好的特征,而后进行分类,这里建立了一个大字典myTree,它将决策树的整个架构全包含进去,这个等会在测试的时候说,而后对数据集进行划分,用splitDataSet函数,就能够获得划分后新的数据集,而后再进行createTrees函数,直到递归结束。
来测试一下
myTree = trees.createTree(myDat,labels)
myTree
{'no surfacing': {0: 'no', 1: {'flippers': {0: 'no', 1: 'yes'}}}}
再来讲说上面没详细说明的大字典,myTree是特征是‘no surfacing’,根据这个分类,获得两个分支‘0’和‘1‘,‘0’分支因为全是同一类就递归结束里,‘1’分支不知足递归结束条件,继续进行分类,它又会生成它本身的字典,又会分红两个分支,而且这两个分支知足递归结束的条件,因此返回‘no surfacing’上的‘1’分支是一个字典。这种嵌套的字典正是决策树算法的结果,咱们可使用它和Matplotlib来进行画决策
这个就是将测试合成一个函数,定义为classify函数
参考代码以下:
1 def classify(inputTree,featLabels,testVec): 2 firstStr = inputTree.keys()[0] 3 secondDict = inputTree[firstStr] 4 featIndex = featLabels.index(firstStr) 5 key = testVec[featIndex] 6 valueOfFeat = secondDict[key] 7 if isinstance(valueOfFeat, dict): 8 classLabel = classify(valueOfFeat, featLabels, testVec) 9 else: classLabel = valueOfFeat 10 return classLabel
这个函数就是一个根据决策树来判断新的测试向量是那种类型,这也是一个递归函数,拿上面决策树的结果来讲吧。
{'no surfacing': {0: 'no', 1: {'flippers': {0: 'no', 1: 'yes'}}}},这是就是咱们的inputTree,首先经过函数的第一句话获得它的第一个bestFeat,也就是‘no surfacing’,赋给了firstStr,secondDict就是‘no surfacing’的值,也就是 {0: 'no', 1: {'flippers': {0: 'no', 1: 'yes'}}},而后用index函数找到firstStr的标号,结果应该是0,根据下标,把测试向量的值赋给key,而后找到对应secondDict中的值,这里有一个isinstance函数,功能是第一个参数的类型等于后面参数的类型,则返回true,不然返回false,testVec列表第一位是1,则valueOfFeat的值是 {0: 'no', 1: 'yes'},是dict,则递归调用这个函数,再进行classify,知道不是字典,也就最后的结果了,其实就是将决策树过一遍,找到对应的labels罢了。
这里有一个小知识点,在jupyter notebook中,显示绿色的函数,能够经过下面查询它的功能,例如
isinstance? #run it , you will see a below window which is used to introduce this function
让咱们来测试测试
trees.classify(myTree,labels,[1,0])
‘no’
trees.classify(myTree,labels,[1,1])
‘yes'
构造决策树是很耗时的任务,即便处理很小的数据集, 如前面的样本数据, 也要花费几秒的时间 ,若是数据集很大,将会耗费不少计算时间。然而用建立好的决策树解决分类问题,能够很快完成。所以 ,为了节省计算时间,最好可以在每次执行分类时调用巳经构造好的决策树。
解决方案:使用pickle模块存储决策树
参考代码:
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)
就是将决策树写到文件中,用的时候在取出来,测试一下就明白了
trees.storeTree(myTree,'classifierStorage.txt') #run it ,store the tree
trees.grabTree('classifierStorage.txt')
{'no surfacing': {0: 'no', 1: {'flippers': {0: 'no', 1: 'yes'}}}}
决策树的构造部分结束了,下面介绍怎样绘制决策树
2. 使用Matplotlib注解绘制树形图
前面咱们看到决策树最后输出是一个大字典,很是丑陋,咱们想让它更有层次感,更加清晰,最好是图形状的,因而,咱们要Matplotlib去画决策树。
2.1 Matplotlib注解
Matplotlib提供了一个注解工具annotations,它能够在数据图形上添加文本注释。
建立一个treePlotter.py文件来存储画图的相关函数
首先是使用文本注解绘制树节点,参考代码以下:
1 import matplotlib.pyplot as plt 2 3 decisionNode = dict(boxstyle="sawtooth", fc="0.8") 4 leafNode = dict(boxstyle="round4", fc="0.8") 5 arrow_args = dict(arrowstyle="<-") 6 7 def plotNode(nodeTxt, centerPt, parentPt, nodeType): 8 createPlot.ax1.annotate(nodeTxt, xy=parentPt, xycoords='axes fraction',\ 9 xytext=centerPt, textcoords='axes fraction',\ 10 va="center", ha="center", bbox=nodeType, arrowprops=arrow_args ) 11 12 def createPlot1(): 13 fig = plt.figure(1, facecolor='white') 14 fig.clf() 15 createPlot.ax1 = plt.subplot(111, frameon=False) #ticks for demo puropses 16 plotNode('a decision node', (0.5, 0.1), (0.1, 0.5), decisionNode) 17 plotNode('a leaf node', (0.8, 0.1), (0.3, 0.8), leafNode) 18 plt.show()
前面三行是定义文本框和箭头格式,decisionNode是锯齿形方框,文本框的大小是0.8,leafNode是4边环绕型,跟矩形相似,大小也是4,arrow_args是指箭头,咱们在后面结果是会看到这些东西,这些数据以字典类型存储。第一个plotNode函数的功能是绘制带箭头的注解,输入参数分别是文本框的内容,文本框的中心坐标,父结点坐标和文本框的类型,这些都是经过一个createPlot.ax1.annotate函数实现的,create.ax1是一个全局变量,这个函数很少将,会用就好了。第二个函数createPlot就是生出图形,也没什么东西,函数第一行是生成图像的画框,横纵坐标最大值都是1,颜色是白色,下一个是清屏,下一个就是分图,111中第一个1是行数,第二个是列数,第三个是第几个图,这里就一个图,跟matlab中的同样,matplotlib里面的函数都是和matlab差很少。
来测试一下吧
reset -f #clear all the module and data
cd 桌面/machinelearninginaction/Ch03
/home/fangyang/桌面/machinelearninginaction/Ch03
import treePlotter import matplotlib.pyplot as plt
treePlotter.createPlot1()
2.2 构造注解树
绘制一棵完整的树须要一些技巧。咱们虽然有 x 、y 坐标,可是如何放置全部的树节点倒是个问题,咱们必须知道有多少个叶节点,以即可以正确肯定x轴的长度;咱们还须要知道树有多少层,以即可以正确肯定y轴的高度。这里定义了两个新函数getNumLeafs()和getTreeDepth(),以求叶节点的数目和树的层数。
参考代码:
1 def getNumLeafs(myTree): 2 numLeafs = 0 3 firstStr = myTree.keys()[0] 4 secondDict = myTree[firstStr] 5 for key in secondDict.keys(): 6 if type(secondDict[key]).__name__=='dict':#test to see if the nodes are dictonaires, if not they are leaf nodes 7 numLeafs += getNumLeafs(secondDict[key]) 8 else: numLeafs +=1 9 return numLeafs 10 11 def getTreeDepth(myTree): 12 maxDepth = 0 13 firstStr = myTree.keys()[0] 14 secondDict = myTree[firstStr] 15 for key in secondDict.keys(): 16 if type(secondDict[key]).__name__=='dict':#test to see if the nodes are dictonaires, if not they are leaf nodes 17 thisDepth = 1 + getTreeDepth(secondDict[key]) 18 else: thisDepth = 1 19 if thisDepth > maxDepth: maxDepth = thisDepth 20 return maxDepth
咱们能够看到两个方法有点似曾相识,没错,咱们在进行决策树分类测试时,用的跟这个几乎同样,分类测试中的isinstance函数换了一种方式去判断,递归依然在,不过是每递归依次,高度增长1,叶子数一样是检测是否为字典,不是字典则增长相应的分支。
这里还写了一个函数retrieveTree,它的做用是预先存储的树信息,避免了每次测试代码时都要从数据中建立树的麻烦
参考代码以下
1 def retrieveTree(i): 2 listOfTrees =[{'no surfacing': {0: 'no', 1: {'flippers': {0: 'no', 1: 'yes'}}}}, 3 {'no surfacing': {0: 'no', 1: {'flippers': {0: {'head': {0: 'no', 1: 'yes'}}, 1: 'no'}}}} 4 ] 5 return listOfTrees[i]
这个没什么好说的,就是把决策树的结果存在一个函数中,方便调用,跟前面的存储决策树差很少。
有了前面这些基础后,咱们就能够来画树了。
参考代码以下:
1 def plotMidText(cntrPt, parentPt, txtString): 2 xMid = (parentPt[0]-cntrPt[0])/2.0 + cntrPt[0] 3 yMid = (parentPt[1]-cntrPt[1])/2.0 + cntrPt[1] 4 createPlot.ax1.text(xMid, yMid, txtString, va="center", ha="center", rotation=30) 5 6 def plotTree(myTree, parentPt, nodeTxt):#if the first key tells you what feat was split on 7 numLeafs = getNumLeafs(myTree) #this determines the x width of this tree 8 depth = getTreeDepth(myTree) 9 firstStr = myTree.keys()[0] #the text label for this node should be this 10 cntrPt = (plotTree.xOff + (1.0 + float(numLeafs))/2.0/plotTree.totalW, plotTree.yOff) 11 plotMidText(cntrPt, parentPt, nodeTxt) 12 plotNode(firstStr, cntrPt, parentPt, decisionNode) 13 secondDict = myTree[firstStr] 14 plotTree.yOff = plotTree.yOff - 1.0/plotTree.totalD 15 for key in secondDict.keys(): 16 if type(secondDict[key]).__name__=='dict':#test to see if the nodes are dictonaires, if not they are leaf nodes 17 plotTree(secondDict[key],cntrPt,str(key)) #recursion 18 else: #it's a leaf node print the leaf node 19 plotTree.xOff = plotTree.xOff + 1.0/plotTree.totalW 20 plotNode(secondDict[key], (plotTree.xOff, plotTree.yOff), cntrPt, leafNode) 21 plotMidText((plotTree.xOff, plotTree.yOff), cntrPt, str(key)) 22 plotTree.yOff = plotTree.yOff + 1.0/plotTree.totalD 23 #if you do get a dictonary you know it's a tree, and the first element will be another dict 24 25 def createPlot(inTree): 26 fig = plt.figure(1, facecolor='white') 27 fig.clf() 28 axprops = dict(xticks=[], yticks=[]) 29 createPlot.ax1 = plt.subplot(111, frameon=False, **axprops) 30 plotTree.totalW = float(getNumLeafs(inTree)) 31 plotTree.totalD = float(getTreeDepth(inTree)) 32 plotTree.xOff = -0.5/plotTree.totalW; plotTree.yOff = 1.0; 33 plotTree(inTree, (0.5,1.0), '') 34 plt.show()
第一个函数是在父子节点中填充文本信息,函数中是将父子节点的横纵坐标相加除以2,上面写得有一点点不同,但原理是同样的,而后仍是在这个中间坐标的基础上添加文本,仍是用的是 createPlot.ax1这个全局变量,使用它的成员函数text来添加文本,里面是它的一些参数。
第二个函数是关键,它调用前面咱们说过的函数,用树的宽度用于计算放置判断节点的位置 ,主要的计算原则是将它放在全部叶子节点的中间,而不只仅是它子节点的中间,根据高度就能够平分坐标系了,用坐标系的最大值除以高度,就是每层的高度。这个plotTree函数也是个递归函数,每次都是调用,画出一层,知道全部的分支都不是字典后,才算画完。每次检测出是叶子,就记录下它的坐标,并写出叶子的信息和父子节点间的信息。plotTree.xOff和plotTree.yOff是用来追踪已经绘制的节点位置,以及放置下一个节点的恰当位置。
第三个函数咱们以前介绍介绍过一个相似,这个函数调用了plotTree函数,最后输出树状图,这里只说两点,一点是全局变量plotTree.totalW存储树的宽度 ,全 局变量plotTree.totalD存储树的深度,还有一点是plotTree.xOff和plotTree.yOff是在这个函数这里初始化的。
最后咱们来测试一下
cd 桌面/machinelearninginaction/Ch03