机器学习实战笔记(Python实现)-02-决策树

属原创文章,欢迎转载,但请注明出处:http://www.cnblogs.com/hemiy/p/6165759.html 谢谢!

代码及数据-->https://github.com/Wellat/MLaction

1、算法概述及实现

1.1 算法特点

优点:计算复杂度不高,输出结果易于理解,对中间值的缺失不敏感,可以处理不相关特征数据

缺点:可能会产生过度匹配问题

适用数据类型:数值型和标称型

1.2 构造决策树

在构造决策树时,需要解决的第一个问题就是,评估当前数据集上哪个特征在划分数据分类时起决定性作用。本书使用ID3算法划分数据集,即通过对比选择不同特征下数据集的信息增益和香农熵来确定最优划分特征。香农熵的定义如下:

1.2.1 计算香农熵:

 1 from math import log
 2 import operator
 3
 4 def createDataSet():
 5     ‘‘‘
 6     产生测试数据
 7     ‘‘‘
 8     dataSet = [[1, 1, ‘yes‘],
 9                [1, 1, ‘yes‘],
10                [1, 0, ‘no‘],
11                [0, 1, ‘no‘],
12                [0, 1, ‘no‘]]
13     labels = [‘no surfacing‘,‘flippers‘]
14     return dataSet, labels
15
16 def calcShannonEnt(dataSet):
17     ‘‘‘
18     计算给定数据集的香农熵
19     ‘‘‘
20     numEntries = len(dataSet)
21     labelCounts = {}
22     #统计每个类别出现的次数,保存在字典labelCounts中
23     for featVec in dataSet:
24         currentLabel = featVec[-1]
25         if currentLabel not in labelCounts.keys(): labelCounts[currentLabel] = 0
26         labelCounts[currentLabel] += 1 #如果当前键值不存在,则扩展字典并将当前键值加入字典
27     shannonEnt = 0.0
28     for key in labelCounts:
29         #使用所有类标签的发生频率计算类别出现的概率
30         prob = float(labelCounts[key])/numEntries
31         #用这个概率计算香农熵
32         shannonEnt -= prob * log(prob,2) #取2为底的对数
33     return shannonEnt
34
35 if __name__== "__main__":
36     ‘‘‘
37     计算给定数据集的香农熵
38     ‘‘‘
39     dataSet,labels = createDataSet()
40     shannonEnt = calcShannonEnt(dataSet)

1.2.2 划分数据集

 1 def splitDataSet(dataSet, axis, value):
 2     ‘‘‘
 3     按照给定特征划分数据集
 4     dataSet:待划分的数据集
 5     axis:   划分数据集的第axis个特征
 6     value:  特征的返回值(比较值)
 7     ‘‘‘
 8     retDataSet = []
 9     #遍历数据集中的每个元素,一旦发现符合要求的值,则将其添加到新创建的列表中
10     for featVec in dataSet:
11         if featVec[axis] == value:
12             reducedFeatVec = featVec[:axis]
13             reducedFeatVec.extend(featVec[axis+1:])
14             retDataSet.append(reducedFeatVec)
15             #extend()和append()方法功能相似,但在处理列表时,处理结果完全不同
16             #a=[1,2,3]  b=[4,5,6]
17             #a.append(b) = [1,2,3,[4,5,6]]
18             #a.extend(b) = [1,2,3,4,5,6]
19     return retDataSet

划分数据集的结果如下所示:

选择最好的数据集划分方式。接下来我们将遍历整个数据集,循环计算香农熵和 splitDataSet() 函数,找到最好的特征划分方式。

 1 def chooseBestFeatureToSplit(dataSet):
 2     ‘‘‘
 3     选择最好的数据集划分方式
 4     输入:数据集
 5     输出:最优分类的特征的index
 6     ‘‘‘
 7     #计算特征数量
 8     numFeatures = len(dataSet[0]) - 1
 9     baseEntropy = calcShannonEnt(dataSet)
10     bestInfoGain = 0.0; bestFeature = -1
11     for i in range(numFeatures):
12         #创建唯一的分类标签列表
13         featList = [example[i] for example in dataSet]
14         uniqueVals = set(featList)
15         #计算每种划分方式的信息熵
16         newEntropy = 0.0
17         for value in uniqueVals:
18             subDataSet = splitDataSet(dataSet, i, value)
19             prob = len(subDataSet)/float(len(dataSet))
20             newEntropy += prob * calcShannonEnt(subDataSet)
21         infoGain = baseEntropy - newEntropy
22         #计算最好的信息增益,即infoGain越大划分效果越好
23         if (infoGain > bestInfoGain):
24             bestInfoGain = infoGain
25             bestFeature = i
26     return bestFeature

1.2.3 递归构建决策树

目前我们已经学习了从数据集构造决策树算法所需要的子功能模块,其工作原理如下:得到原始数据集,然后基于最好的属性值划分数据集,由于特征值可能多于两个,因此可能存在大于两个分支的数据集划分。第一次划分之后,数据将被向下传递到树分支的下一个节点,在这个节点上,我们可以再次划分数据。因此我们可以采用递归的原则处理数据集。递归结束的条件是:程序遍历完所有划分数据集的属性,或者每个分支下的所有实例都具有相同的分类。

由于特征数目并不是在每次划分数据分组时都减少,因此这些算法在实际使用时可能引起一定的问题。目前我们并不需要考虑这个问题,只需要在算法开始运行前计算列的数目,查看算法是否使用了所有属性即可。如果数据集已经处理了所有属性,但是类标签依然不是唯一的,此时我们通常会采用多数表决的方法决定该叶子节点的分类。

在 trees.py 中增加如下投票表决代码:

 1 import operator
 2 def majorityCnt(classList):
 3     ‘‘‘
 4     投票表决函数
 5     输入classList:标签集合,本例为:[‘yes‘, ‘yes‘, ‘no‘, ‘no‘, ‘no‘]
 6     输出:得票数最多的分类名称
 7     ‘‘‘
 8     classCount={}
 9     for vote in classList:
10         if vote not in classCount.keys(): classCount[vote] = 0
11         classCount[vote] += 1
12     #把分类结果进行排序,然后返回得票数最多的分类结果
13     sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
14     return sortedClassCount[0][0]

创建树的函数代码(主函数):

 1 def createTree(dataSet,labels):
 2     ‘‘‘
 3     创建树
 4     输入:数据集和标签列表
 5     输出:树的所有信息
 6     ‘‘‘
 7     # classList为数据集的所有类标签
 8     classList = [example[-1] for example in dataSet]
 9     # 停止条件1:所有类标签完全相同,直接返回该类标签
10     if classList.count(classList[0]) == len(classList):
11         return classList[0]
12     # 停止条件2:遍历完所有特征时仍不能将数据集划分成仅包含唯一类别的分组,则返回出现次数最多的类标签
13     #
14     if len(dataSet[0]) == 1:
15         return majorityCnt(classList)
16     # 选择最优分类特征
17     bestFeat = chooseBestFeatureToSplit(dataSet)
18     bestFeatLabel = labels[bestFeat]
19     # myTree存储树的所有信息
20     myTree = {bestFeatLabel:{}}
21     # 以下得到列表包含的所有属性值
22     del(labels[bestFeat])
23     featValues = [example[bestFeat] for example in dataSet]
24     uniqueVals = set(featValues)
25     # 遍历当前选择特征包含的所有属性值
26     for value in uniqueVals:
27         subLabels = labels[:]
28         myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value),subLabels)
29     return myTree

本例返回 myTree 为字典类型,如下:

{‘no surfacing‘: {0: ‘no‘, 1: {‘flippers‘: {0: ‘no‘, 1: ‘yes‘}}}}

2、测试分类和存储分类器

利用决策树的分类函数:

 1 def classify(inputTree,featLabels,testVec):
 2     ‘‘‘
 3     决策树的分类函数
 4     inputTree:训练好的树信息
 5     featLabels:标签列表
 6     testVec:测试向量
 7     ‘‘‘
 8     # 在2.7中,找到key所对应的第一个元素为:firstStr = myTree.keys()[0],
 9     # 这在3.4中运行会报错:‘dict_keys‘ object does not support indexing,这是因为python3改变了dict.keys,
10     # 返回的是dict_keys对象,支持iterable 但不支持indexable,
11     # 我们可以将其明确的转化成list,则此项功能在3中应这样实现:
12     firstSides = list(inputTree.keys())
13     firstStr = firstSides[0]
14     secondDict = inputTree[firstStr]
15     # 将标签字符串转换成索引
16     featIndex = featLabels.index(firstStr)
17     key = testVec[featIndex]
18     valueOfFeat = secondDict[key]
19     # 递归遍历整棵树,比较testVec变量中的值与树节点的值,如果到达叶子节点,则返回当前节点的分类标签
20     if isinstance(valueOfFeat, dict):
21         classLabel = classify(valueOfFeat, featLabels, testVec)
22     else: classLabel = valueOfFeat
23     return classLabel
24
25 if __name__== "__main__":
26     ‘‘‘
27     测试分类效果
28     ‘‘‘
29     dataSet,labels = createDataSet()
30     myTree = createTree(dataSet,labels)
31     ans = classify(myTree,labels,[1,0])

决策树模型的存储

 1 def storeTree(inputTree,filename):
 2     ‘‘‘
 3     使用pickle模块存储决策树
 4     ‘‘‘
 5     import pickle
 6     fw = open(filename,‘wb+‘)
 7     pickle.dump(inputTree,fw)
 8     fw.close()
 9
10 def grabTree(filename):
11     ‘‘‘
12     导入决策树模型
13     ‘‘‘
14     import pickle
15     fr = open(filename,‘rb‘)
16     return pickle.load(fr)
17
18 if __name__== "__main__":
19     ‘‘‘
20     存取操作
21     ‘‘‘
22     storeTree(myTree,‘mt.txt‘)
23     myTree2 = grabTree(‘mt.txt‘)

3、使用 Matplotlib 绘制树形图

上节我们已经学习如何从数据集中创建决策树,然而字典的表示形式非常不易于理解,决策树的主要优点就是直观易于理解,如果不能将其直观显示出来,就无法发挥其优势。本节使用 Matplotlib 库编写代码绘制决策树。

创建名为 treePlotter.py 的新文件:

3.1 绘制树节点

 1 import matplotlib.pyplot as plt
 2
 3 # 定义文本框和箭头格式
 4 decisionNode = dict(boxstyle="sawtooth", fc="0.8")
 5 leafNode = dict(boxstyle="round4", fc="0.8")
 6 arrow_args = dict(arrowstyle="<-")
 7
 8 # 绘制带箭头的注释
 9 def plotNode(nodeTxt, centerPt, parentPt, nodeType):
10     createPlot.ax1.annotate(nodeTxt, xy=parentPt,  xycoords=‘axes fraction‘,
11              xytext=centerPt, textcoords=‘axes fraction‘,
12              va="center", ha="center", bbox=nodeType, arrowprops=arrow_args )
13
14 def createPlot():
15     fig = plt.figure(1, facecolor=‘grey‘)
16     fig.clf()
17     # 定义绘图区
18     createPlot.ax1 = plt.subplot(111, frameon=False) #ticks for demo puropses
19     plotNode(‘a decision node‘, (0.5, 0.1), (0.1, 0.5), decisionNode)
20     plotNode(‘a leaf node‘, (0.8, 0.1), (0.3, 0.8), leafNode)
21     plt.show()
22
23 if __name__== "__main__":
24     ‘‘‘
25     绘制树节点
26     ‘‘‘
27     createPlot()

结果如下:??

3.2 构造注解树

绘制一棵完整的树需要一些技巧。我们虽然有 x, y 坐标,但是如何放置所有的树节点却是个问题。我们必须知道有多少个叶节点,以便可以正确确x轴的长度;我们还需要知道树有多少层,来确定y轴的高度。这里另一两个新函数 getNumLeafs() 和 getTreeDepth() ,来获取叶节点的数目和树的层数,createPlot() 为主函数,完整代码如下:

  1 import matplotlib.pyplot as plt
  2
  3 # 定义文本框和箭头格式
  4 decisionNode = dict(boxstyle="sawtooth", fc="0.8")
  5 leafNode = dict(boxstyle="round4", fc="0.8")
  6 arrow_args = dict(arrowstyle="<-")
  7
  8 # 绘制带箭头的注释
  9 def plotNode(nodeTxt, centerPt, parentPt, nodeType):
 10     createPlot.ax1.annotate(nodeTxt, xy=parentPt,  xycoords=‘axes fraction‘,
 11              xytext=centerPt, textcoords=‘axes fraction‘,
 12              va="center", ha="center", bbox=nodeType, arrowprops=arrow_args )
 13
 14 def createPlot(inTree):
 15     ‘‘‘
 16     绘树主函数
 17     ‘‘‘
 18     fig = plt.figure(1, facecolor=‘white‘)
 19     fig.clf()
 20     # 设置坐标轴数据
 21     axprops = dict(xticks=[], yticks=[])
 22     # 无坐标轴
 23     createPlot.ax1 = plt.subplot(111, frameon=False, **axprops)
 24     # 带坐标轴
 25 #    createPlot.ax1 = plt.subplot(111, frameon=False)
 26     plotTree.totalW = float(getNumLeafs(inTree))
 27     plotTree.totalD = float(getTreeDepth(inTree))
 28     # 两个全局变量plotTree.xOff和plotTree.yOff追踪已经绘制的节点位置,
 29     # 以及放置下一个节点的恰当位置
 30     plotTree.xOff = -0.5/plotTree.totalW; plotTree.yOff = 1.0;
 31     plotTree(inTree, (0.5,1.0), ‘‘)
 32     plt.show()
 33
 34
 35 def getNumLeafs(myTree):
 36     ‘‘‘
 37     获取叶节点的数目
 38     ‘‘‘
 39     numLeafs = 0
 40     firstSides = list(myTree.keys())
 41     firstStr = firstSides[0]
 42     secondDict = myTree[firstStr]
 43     for key in secondDict.keys():
 44         # 判断节点是否为字典来以此判断是否为叶子节点
 45         if type(secondDict[key]).__name__==‘dict‘:
 46             numLeafs += getNumLeafs(secondDict[key])
 47         else:   numLeafs +=1
 48     return numLeafs
 49
 50 def getTreeDepth(myTree):
 51     ‘‘‘
 52     获取树的层数
 53     ‘‘‘
 54     maxDepth = 0
 55     firstSides = list(myTree.keys())
 56     firstStr = firstSides[0]
 57     secondDict = myTree[firstStr]
 58     for key in secondDict.keys():
 59         if type(secondDict[key]).__name__==‘dict‘:
 60             thisDepth = 1 + getTreeDepth(secondDict[key])
 61         else:   thisDepth = 1
 62         if thisDepth > maxDepth: maxDepth = thisDepth
 63     return maxDepth
 64
 65
 66 def plotMidText(cntrPt, parentPt, txtString):
 67     ‘‘‘
 68     计算父节点和子节点的中间位置,并在此处添加简单的文本标签信息
 69     ‘‘‘
 70     xMid = (parentPt[0]-cntrPt[0])/2.0 + cntrPt[0]
 71     yMid = (parentPt[1]-cntrPt[1])/2.0 + cntrPt[1]
 72     createPlot.ax1.text(xMid, yMid, txtString, va="center", ha="center", rotation=30)
 73
 74 def plotTree(myTree, parentPt, nodeTxt):#if the first key tells you what feat was split on
 75     # 计算宽与高
 76     numLeafs = getNumLeafs(myTree)  #this determines the x width of this tree
 77     depth = getTreeDepth(myTree)
 78     firstSides = list(myTree.keys())
 79     firstStr = firstSides[0]
 80     cntrPt = (plotTree.xOff + (1.0 + float(numLeafs))/2.0/plotTree.totalW, plotTree.yOff)
 81     # 标记子节点属性值
 82     plotMidText(cntrPt, parentPt, nodeTxt)
 83     plotNode(firstStr, cntrPt, parentPt, decisionNode)
 84     secondDict = myTree[firstStr]
 85     # 减少y偏移
 86     plotTree.yOff = plotTree.yOff - 1.0/plotTree.totalD
 87     for key in secondDict.keys():
 88         if type(secondDict[key]).__name__==‘dict‘:#test to see if the nodes are dictonaires, if not they are leaf nodes
 89             plotTree(secondDict[key],cntrPt,str(key))        #recursion
 90         else:   #it‘s a leaf node print the leaf node
 91             plotTree.xOff = plotTree.xOff + 1.0/plotTree.totalW
 92             plotNode(secondDict[key], (plotTree.xOff, plotTree.yOff), cntrPt, leafNode)
 93             plotMidText((plotTree.xOff, plotTree.yOff), cntrPt, str(key))
 94     plotTree.yOff = plotTree.yOff + 1.0/plotTree.totalD
 95
 96
 97 def retrieveTree(i):
 98     ‘‘‘
 99     保存了树的测试数据
100     ‘‘‘
101     listOfTrees =[{‘no surfacing‘: {0: ‘no‘, 1: {‘flippers‘: {0: ‘no‘, 1: ‘yes‘}}}},
102                   {‘no surfacing‘: {0: ‘no‘, 1: {‘flippers‘: {0: {‘head‘: {0: ‘no‘, 1: ‘yes‘}}, 1: ‘no‘}}}}
103                   ]
104     return listOfTrees[i]
105
106
107
108 if __name__== "__main__":
109     ‘‘‘
110     绘制树
111     ‘‘‘
112     createPlot(retrieveTree(1))

测试结果:

4、实例:使用决策树预测隐形眼镜类型

4.1 处理流程

数据格式如下所示,其中最后一列表示类标签:

4.2 Python实现代码 

1 import trees
2 import treePlotter
3
4 fr = open(‘lenses.txt‘)
5 lenses = [inst.strip().split(‘\t‘) for inst in fr.readlines()]
6 lensesLabels=[‘age‘,‘prescript‘,‘astigmatic‘,‘tearRate‘]
7 lensesTree = trees.createTree(lenses,lensesLabels)
8 treePlotter.createPlot(lensesTree)

产生的决策树:

本节使用的算法成为ID3,它是一个号的算法但无法直接处理数值型数据,尽管我们可以通过量化的方法将数值型数据转化为标称型数值,但如果存在太多的特征划分,ID3算法仍然会面临其他问题。

时间: 2024-08-04 22:17:12

机器学习实战笔记(Python实现)-02-决策树的相关文章

机器学习实战笔记(Python实现)-03-朴素贝叶斯

--------------------------------------------------------------------------------------- 本系列文章为<机器学习实战>学习笔记,内容整理自书本,网络以及自己的理解,如有错误欢迎指正. 源码在Python3.5上测试均通过,代码及数据 --> https://github.com/Wellat/MLaction -----------------------------------------------

机器学习实战笔记(Python实现)-06-AdaBoost

--------------------------------------------------------------------------------------- 本系列文章为<机器学习实战>学习笔记,内容整理自书本,网络以及自己的理解,如有错误欢迎指正. 源码在Python3.5上测试均通过,代码及数据 --> https://github.com/Wellat/MLaction -----------------------------------------------

机器学习实战笔记(Python实现)-01-K近邻算法(KNN)

属原创文章,欢迎转载,但请注明出处:http://www.cnblogs.com/hemiy/p/6155425.html 谢谢! 代码及数据-->https://github.com/Wellat/MLaction 1 算法概述 1.1 算法特点 简单地说,k-近邻算法采用测量不同特征值之间的距离方法进行分类. 优点:精度高.对异常值不敏感.无数据输入假定 缺点:计算复杂度高.空间复杂度高 适用数据范围:数值型和标称型 1.2 工作原理 存在一个训练样本集,并且每个样本都存在标签(有监督学习)

机器学习实战笔记(Python实现)-07-分类性能度量指标

1.混淆矩阵 下图是一个二类问题的混淆矩阵,其中的输出采用了不同的类别标签 常用的衡量分类性能的指标有: 正确率(Precision),它等于 TP/(TP+FP) ,给出的是预测为正例的样本中的真正正例的比例. 召回率(Recall),他等于 TP/(TP+FN),给出的是预测为正例的真实正例占所有真实正例的比例. 2.ROC曲线 图中的横轴是伪正例的比例(假阳率=FP/(FP+TN)),而纵轴是真正例的比例(真阳率=TP/(TP+FN)).ROC曲线给出的是当阈值变化时假阳率和真阳率的变化情

机器学习实战笔记7(Adaboost)

1:简单概念描述 Adaboost是一种弱学习算法到强学习算法,这里的弱和强学习算法,指的当然都是分类器,首先我们需要简单介绍几个概念. 1:弱学习器:在二分情况下弱分类器的错误率会高于50%.其实任意的分类器都可以做为弱分类器,比如之前介绍的KNN.决策树.Na?ve Bayes.logiostic回归和SVM都可以.这里我们采用的弱分类器是单层决策树,它是一个单节点的决策树.它是adaboost中最流行的弱分类器,当然并非唯一可用的弱分类器.即从特征中选择一个特征来进行分类,该特征能是错误率

机器学习实战笔记1(机器学习基础)

1:如何选择合适的算法 2:python简介 (1)   python的优势:相对于matlab,matlab单个软件授权就要花费数千美元,也没有一个有影响力的大型开源项目.相对于c++/c/java,完成简单的操作就需要编写大量的代码:而如今我们应该花费更多的时间去处理数据内在的含义,而无需花费太多精力解决计算机如何得到数据结果(python简洁) (2)   python具有numpy科学函数库,它是一个使运算更容易.执行更迅速的库:另外还有matplotlib绘图工具. 3:python语

机器学习实战笔记5(logistic回归)

1:简单概念描述 假设现在有一些数据点,我们用一条直线对这些点进行拟合(改线称为最佳拟合直线),这个拟合过程就称为回归.训练分类器就是为了寻找最佳拟合参数,使用的是最优化算法. 基于sigmoid函数分类:logistic回归想要的函数能够接受所有的输入然后预测出类别.这个函数就是sigmoid函数,它也像一个阶跃函数.其公式如下: 其中: z = w0x0+w1x1+-.+wnxn,w为参数, x为特征 为了实现logistic回归分类器,我们可以在每个特征上乘以一个回归系数,然后把所有的结果

机器学习实战笔记6(SVM)

鉴于July大哥的SVM三层境界(http://blog.csdn.net/v_july_v/article/details/7624837)已经写得非常好了,这里我就不详细描述,只是阐述简单的几个概念.如果看SVM三层境界有困惑,我也愿意与大家交流,共同进步. 简单概念描述: (1)      支持向量机(SVM, support vectormachine)就是通过最大化支持向量到分类超平面之间的分类间隔.分类超平面就是我们想要得到的决策曲面:支持向量就是离分类超平面最近的点,而间隔即为支持

机器学习实战笔记之非均衡分类问题

通常情况下,我们直接使用分类结果的错误率就可以做为该分类器的评判标准了,但是当在分类器训练时正例数目和反例数目不相等时,这种评价标准就会出现问题.这种现象也称为非均衡分类问题.此时有以下几个衡量标准. (1)   正确率<precise>和召回率<Recall> 如下图所示:其中准确率指预测的真实正例占所有真实正例的比例,等于TP/(TP+FP),而召回率指预测的真实正例占所有真实正例的比例,等于TP/(TP+FN).通常我们可以很容易的构照一个高正确率或高召回率的分类器,但是很难