第3章 决策树 1 决策树 概述 决策树(Decision Tree)算法是一种基本的分类与回归方法,是最经常使用的数据挖掘算法之一。我们这章节只讨论用于分类的决策树。
决策树模型呈树形结构,在分类问题中,表示基于特征对实例进行分类的过程。它可以认为是 if-then 规则的集合,也可以认为是定义在特征空间与类空间上的条件概率分布。
决策树学习通常包括 3 个步骤: 特征选择、决策树的生成和决策树的修剪。
2 决策树 场景
1 2 3 首先检测发送邮件域名地址。如果地址为 myEmployer.com, 则将其放在分类 "无聊时需要阅读的邮件"中。 如果邮件不是来自这个域名,则检测邮件内容里是否包含单词 "曲棍球" , 如果包含则将邮件归类到 "需要及时处理的朋友邮件", 如果不包含则将邮件归类到 "无需阅读的垃圾邮件" 。
决策树的定义
分类决策树模型是一种描述对实例进行分类的树形结构。决策树由结点(node)和有向边(directed edge)组成。结点有两种类型: 内部结点(internal node)和叶结点(leaf node)。内部结点表示一个特征或属性(features),叶结点表示一个类(labels)。
用决策树对需要测试的实例进行分类: 从根节点开始,对实例的某一特征进行测试,根据测试结果,将实例分配到其子结点;这时,每一个子结点对应着该特征的一个取值。如此递归地对实例进行测试并分配,直至达到叶结点。最后将实例分配到叶结点的类中。
3 决策树 原理 信息熵 & 信息增益
熵(entropy): 熵指的是体系的混乱的程度,在不同的学科中也有引申出的更为具体的定义,是各领域十分重要的参量。
信息论(information theory)中的熵(香农熵): 是一种信息的度量方式,表示信息的混乱程度,也就是说: 信息越有序,信息熵越低。例如: 火柴有序放在火柴盒里,熵值很低,相反,熵值很高。
信息增益(information gain): 在划分数据集前后信息发生的变化称为信息增益。
决策树 工作原理
如何构造一个决策树?我们使用 createBranch() 方法,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 def createBranch(): ''' 此处运用了迭代的思想。 感兴趣可以搜索 迭代 recursion, 甚至是 dynamic programing。 ''' 检测数据集中的所有数据的分类标签是否相同: If so return 类标签 Else: 寻找划分数据集的最好特征(划分之后信息熵最小,也就是信息增益最大的特征) 划分数据集 创建分支节点 for 每个划分的子集 调用函数 createBranch (创建分支的函数)并增加返回结果到分支节点中 return 分支节点
决策树 开发流程 1 2 3 4 5 6 收集数据: 可以使用任何方法。 准备数据: 树构造算法 (这里使用的是ID3算法,只适用于标称型数据,这就是为什么数值型数据必须离散化。 还有其他的树构造算法,比如CART) 分析数据: 可以使用任何方法,构造树完成之后,我们应该检查图形是否符合预期。 训练算法: 构造树的数据结构。 测试算法: 使用训练好的树计算错误率。 使用算法: 此步骤可以适用于任何监督学习任务,而使用决策树可以更好地理解数据的内在含义。
决策树 算法特点 1 2 3 优点: 计算复杂度不高,输出结果易于理解,数据有缺失也能跑,可以处理不相关特征。 缺点: 容易过拟合。 适用数据类型: 数值型和标称型。
4 决策树 项目案例 4.1 项目案例1: 判定鱼类和非鱼类 项目概述
根据以下 2 个特征,将动物分成两类: 鱼类和非鱼类。特征:
不浮出水面是否可以生存
是否有脚蹼
开发流程 完整代码地址
1 2 3 4 5 6 收集数据: 可以使用任何方法 准备数据: 树构造算法(这里使用的是ID3算法,因此数值型数据必须离散化。) 分析数据: 可以使用任何方法,构造树完成之后,我们可以将树画出来。 训练算法: 构造树结构 测试算法: 使用习得的决策树执行分类 使用算法: 此步骤可以适用于任何监督学习任务,而使用决策树可以更好地理解数据的内在含义
收集数据: 可以使用任何方法
我们利用 createDataSet() 函数输入数据
1 2 3 4 5 6 7 8 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
准备数据: 树构造算法
此处,由于我们输入的数据本身就是离散化数据,所以这一步就省略了。
分析数据: 可以使用任何方法,构造树完成之后,我们可以将树画出来。
计算给定数据集的香农熵的函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 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
按照给定特征划分数据集
将指定特征的特征值等于 value 的行剩下列作为子数据集。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 def splitDataSet (dataSet, index, value ): """splitDataSet(通过遍历dataSet数据集,求出index对应的colnum列的值为value的行) 就是依据index列进行分类,如果index列的数据等于 value的时候,就要将 index 划分到我们创建的新的数据集中 Args: dataSet 数据集 待划分的数据集 index 表示每一行的index列 划分数据集的特征 value 表示index列对应的value值 需要返回的特征的值。 Returns: index列为value的数据集【该数据集需要排除index列】 """ retDataSet = [] for featVec in dataSet: if featVec[index] == value: reducedFeatVec = featVec[:index] ''' 请百度查询一下: extend和append的区别 music_media.append(object) 向列表中添加一个对象object music_media.extend(sequence) 把一个序列seq的内容添加到列表中 (跟 += 在list运用类似, music_media += sequence) 1、使用append的时候,是将object看作一个对象,整体打包添加到music_media对象中。 2、使用extend的时候,是将sequence看作一个序列,将这个序列和music_media序列合并,并放在其后面。 music_media = [] music_media.extend([1,2,3]) print music_media #结果: #[1, 2, 3] music_media.append([4,5,6]) print music_media #结果: #[1, 2, 3, [4, 5, 6]] music_media.extend([7,8,9]) print music_media #结果: #[1, 2, 3, [4, 5, 6], 7, 8, 9] ''' reducedFeatVec.extend(featVec[index+1 :]) retDataSet.append(reducedFeatVec) return retDataSet
选择最好的数据集划分方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 def chooseBestFeatureToSplit (dataSet ): """chooseBestFeatureToSplit(选择最好的特征) Args: dataSet 数据集 Returns: bestFeature 最优的特征列 """ numFeatures = len (dataSet[0 ]) - 1 baseEntropy = calcShannonEnt(dataSet) bestInfoGain, bestFeature = 0.0 , -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 print 'infoGain=' , infoGain, 'bestFeature=' , i, baseEntropy, newEntropy if (infoGain > bestInfoGain): bestInfoGain = infoGain bestFeature = i return bestFeature
1 2 3 问: 上面的 newEntropy 为什么是根据子集计算的呢? 答: 因为我们在根据一个特征计算香农熵的时候,该特征的分类值是相同,这个特征这个分类的香农熵为 0; 这就是为什么计算新的香农熵的时候使用的是子集。
训练算法: 构造树的数据结构
创建树的函数代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 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 = chooseBestFeatureToSplit(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
测试算法: 使用决策树执行分类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 def classify (inputTree, featLabels, testVec ): """classify(给输入的节点,进行分类) Args: inputTree 决策树模型 featLabels Feature标签对应的名称 testVec 测试输入的数据 Returns: classLabel 分类的结果值,需要映射label才能知道名称 """ firstStr = list (inputTree.keys())[0 ] secondDict = inputTree[firstStr] featIndex = featLabels.index(firstStr) key = testVec[featIndex] valueOfFeat = secondDict[key] print '+++' , firstStr, 'xxx' , secondDict, '---' , key, '>>>' , valueOfFeat if isinstance (valueOfFeat, dict ): classLabel = classify(valueOfFeat, featLabels, testVec) else : classLabel = valueOfFeat return classLabel
使用算法: 此步骤可以适用于任何监督学习任务,而使用决策树可以更好地理解数据的内在含义。
4.2 项目案例2: 使用决策树预测隐形眼镜类型 完整代码地址
项目概述 隐形眼镜类型包括硬材质、软材质以及不适合佩戴隐形眼镜。我们需要使用决策树预测患者需要佩戴的隐形眼镜类型。
开发流程
收集数据: 提供的文本文件。
解析数据: 解析 tab 键分隔的数据行
分析数据: 快速检查数据,确保正确地解析数据内容,使用 createPlot() 函数绘制最终的树形图。
训练算法: 使用 createTree() 函数。
测试算法: 编写测试函数验证决策树可以正确分类给定的数据实例。
使用算法: 存储树的数据结构,以便下次使用时无需重新构造树。
收集数据: 提供的文本文件
文本文件数据格式如下:
1 2 3 young myope no reduced no lenses pre myope no reduced no lenses presbyopic myope no reduced no lenses
解析数据: 解析 tab 键分隔的数据行
1 2 lecses = [inst.strip().split('\t' ) for inst in fr.readlines()] lensesLabels = ['age' , 'prescript' , 'astigmatic' , 'tearRate' ]
分析数据: 快速检查数据,确保正确地解析数据内容,使用 createPlot() 函数绘制最终的树形图。
1 >>> treePlotter.createPlot(lensesTree)
训练算法: 使用 createTree() 函数
1 2 3 4 5 6 7 >>> lensesTree = trees.createTree(lenses, lensesLabels)>>> lensesTree{'tearRate' : {'reduced' : 'no lenses' , 'normal' : {'astigmatic' :{'yes' : {'prescript' :{'hyper' :{'age' :{'pre' :'no lenses' , 'presbyopic' : 'no lenses' , 'young' :'hard' &'soft' , 'presbyopic' :{'prescript' : {'hyper' :'soft' , 'myope' :'no lenses' &
测试算法: 编写测试函数验证决策树可以正确分类给定的数据实例。
使用算法: 存储树的数据结构,以便下次使用时无需重新构造树。
使用 pickle 模块存储决策树
1 2 3 4 5 6 7 8 9 10 def storeTree (inputTree, filename ): import pickle fw = open (filename, 'wb' ) pickle.dump(inputTree, fw) fw.close() def grabTree (filename ): import pickle fr = open (filename, 'rb' ) return pickle.load(fr)