第3章 决策树

embedded/2024/12/23 3:37:58/

决策树经常处理分类问题,近来的调查表明决策树也是经常使用的数据挖掘算法
决策树的流程图:
长方形代表判断模块(decision block),椭圆形代表中止模块(terminating block),表示已经得出结论,可以中止运行。
从判断模块引出左右箭头称作分支(branch),它可以到底另一个判断模块或者中止模块。
决策树算法能够读取数据集合,构建决策树
决策树的一个重要任务是为了数据中蕴含的知识信息,因此决策树可以使用不熟悉的数据集合,并从中提取出一系列规则,在这些机器根据数据集创建规则时,就是机器学习的过程。

3.1 决策树的构造

  1. 优点:计算复杂度不高,输出结果易于理解,对中间值确实不敏感,可以处理不相关特征数据。
  2. 缺点:可能会产生过度匹配问题。
  3. 适用数据类型:数值型和标称型。

创建分支的伪代码函数createBranch()如下所示:

检测数据集中的每个子项是否属于同一分类:If so return 类标签;Else寻找划分数据集的最好特征划分数据集创建分支节点for 每个划分的子集## 递归调用createBranch调用createBranch并增加返回结果到分支节点中return 分支节点

决策树的一般流程:

  1. 收集数据:可以使用任何方法。
  2. 准备数据:树构造只是用于标称型数据,因此数值型数据必须离散化。
  3. 分析数据:可以使用任何方法,构造树完成之后,我们应该检查图形是否符合预期。
  4. 训练算法:构造树的数据结构。
  5. 测试算法:使用经验树计算错误率。
  6. 使用算法:此步骤可以适用于任何监督学习算法,而使用决策树可以更好地理解数据的内在含义。

3.1.1 信息增益

在划分数据集之前之后信息发生的变换称为信息增益。
获得信息增益最高的特征就是最好的选择。

熵:定义为信息的期望值
如果待分类的事务划分在多个分类之中,在符号 x i x_i xi的信息定义为:
l ( x i ) = − log ⁡ 2 p ( x i ) l(x_i) = - \log _{2} p(x_i) l(xi)=log2p(xi)
为了计算熵,我们需要计算所有类别所有可能值包含的信息期望值,通过下面的公式得到:
H = − ∑ i = 1 n p ( x i ) log ⁡ 2 p ( x i ) H = - \sum _{i = 1}^{n} p(x_i) \log _{2} p(x_i) H=i=1np(xi)log2p(xi)

计算给定数据集的香农熵

    from math import logdef calcShannonEnt(dataSet):numEntries = len(dataSet)labelCount = {}##为所有可能分类创建字典for featVec in dataSet:currentLabel = featVec[-1]if currentLabel not in labelCount.keys():labelCount[currentLabel] = 0labelCount[currentLabel] += 1shannonEnt = 0.0for key in labelCount:prob = float(labelCount[key]) / numEntriesshannonEnt -= prob * log(prob, 2)       ##以2为底数求对数return shannonEnt##简单鱼鉴定数据集def creatDataSet():dateSet = [[1, 1, 'yes'],[1, 1, 'yes'],[1, 0, 'no'],[0, 1, 'no'],[0, 1, 'no']]labels = ['no surfacing', 'flippers']return dateSet, labelsif __name__ == '__main__':dataSet, labels = creatDataSet()shannonEnt = calcShannonEnt(dataSet)print(shannonEnt)

3.1.2 划分数据集

按照给定特征划分数据集

    def splitDataSet(dataSet, axis, value):''':param dataSet: 待划分的数据集:param axis: 划分数据集的特征:param value: 需要返回的特征的值:return:  结果数据集'''retDataSet = []     ##创建一个新的list对象for featVec in dataSet:if featVec[axis] == value:##抽取reducedFeatVec = featVec[:axis] reducedFeatVec.extend(featVec[axis + 1:])retDataSet.append(reducedFeatVec)return retDataSetif __name__ == '__main__':dataSet, labels = creatDataSet()retDataSet = splitDataSet(dataSet, 0, 1)print(retDataSet)

Python列表对象的方法,append()和extend()用于在列表末尾添加元素,但它们的用法和效果有所不同。

  1. append()方法:
    用法:list.append(element)
    参数:element,要添加的单个元素。
    功能:将指定的元素添加到列表的末尾。
    结果:在列表的末尾添加一个新的元素,并扩展列表的长度。
    特点:可以添加任意类型的元素,包括可迭代对象(如列表)。当添加可迭代对象时,整个对象作为单个元素添加到列表中。
  2. extend()方法:
    用法:list.extend(iterable)
    参数:iterable,一个可迭代对象,如列表、元组、字符串等。
    功能:将可迭代对象中的每个元素添加到列表的末尾。
    结果:不会创建一个新的列表对象,而是在原列表的末尾追加元素。
    特点:只能添加可迭代对象的元素值,而不是整个可迭代对象。当添加的是另一个列表时,extend()会将那个列表中的每个元素逐个添加到原列表中。
  3. 总结:
    append()适用于添加单个元素或可迭代对象,但整个可迭代对象被视为单个元素添加。
    extend()适用于添加可迭代对象的每个元素,而不是整个可迭代对象。
    这些方法都不返回新列表,而是直接修改原列表。

选择最好的数据集划分方式

    def chooseBestFeatureToSplit(dataSet):numFeatures = len(dataSet[0]) - 1baseEntropy = calcShannonEnt(dataSet)bestInfoGain = 0.0bestFeature = -1for 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 = infoGainbestFeature = ireturn bestFeatureif __name__ == '__main__':dataSet, labels = creatDataSet()feature = chooseBestFeatureToSplit(dataSet)print(feature)

3.1.3 递归构建决策树

其工作原理如下:
创建原始数据集,然后基于最好的属性值划分数据集合,由于特征值可能多余两个,因此可能存在大于两个分支的数据集划分。第一次划分之后,数据将向下递归到树分支的下一个节点,在这个节点上我们再次划分数据。因此我们可以采用递归的原则处理数据集。
递归结束的条件:
程序遍历完所有划分数据集的性质,或者每个分支下的所有实例都有相同的分类。如果所有实例具有相同的分类,则得到一个叶子节点或者终止块。任何到达叶子节点的数据必然属于叶子节点的分类。

	import operatordef majorityCnt(classList):''':param classList: 分类名称列表:return: 出现次数最多的分类标签'''classCount = {}for vote in classList:if vote not in classCount.keys(): classCount[vote] = 0classCount[vote] += 1sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)return sortedClassCount[0][0]

创建树的函数代码:

	def createTree(dataSet, labels):''':param dataSet: 数据集:param labels: 标签列:return:'''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 myTreeif __name__ == '__main__':myDat, labels = creatDataSet()myTree = createTree(myDat, labels)print(myTree)

3.2 在Python中使用Matplotlib注解绘制树形图

3.2.1 Matplotlib注解

Matplotlib提供了一个注解工具annotations,非常有用,它可以在数据图形上添加文本注释。注解通常用于解释数据的内容。

使用文本注解绘制树节点

    import matplotlibmatplotlib.use('TkAgg')import matplotlib.pyplot as plt## 定义文本框的箭头格式decisionNode = dict(boxstyle="sawtooth", fc="0.8")leafNode = dict(boxstyle="round4", fc="0.8")arrow_args = dict(arrowstyle="<-")def plotNode(nodeTxt, centerPt, parentPt, nodeType):'''绘制带箭头的注解:param nodeTxt::param centerPt::param parentPt::param nodeType::return:'''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='white')fig.clf()createPlot.ax1 = plt.subplot(111, frameon= False)plotNode(U'Decision Node', (0.5, 0.1), (0.1, 0.5), decisionNode)plotNode(U'Leaf Node', (0.8, 0.1), (0.3, 0.8), leafNode)plt.show()if __name__ == '__main__':createPlot();

3.2.2 构造注解树

获得叶节点的数目和树的层数

    def getNumLeafs(myTree):numLeafs = 0;firstStr = list(myTree)[0]secondDict = myTree[firstStr]for key in secondDict.keys():## 测试节点的数据类型是否为字典if type(secondDict[key]).__name__=='dict':numLeafs += getNumLeafs(secondDict[key])else:numLeafs += 1return numLeafsdef getTreeDepth(myTree):maxDepth = 0firstStr = list(myTree)[0]secondDict = myTree[firstStr]for key in secondDict.keys():if type(secondDict[key]).__name__ == 'dict':thisDepth = 1 + getTreeDepth(secondDict[key])else:thisDepth = 1if thisDepth > maxDepth:maxDepth = thisDepthreturn maxDepthdef retrieveTree(i):listOfTrees = [{'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 listOfTrees[i]if __name__ == '__main__':listOfTrees = retrieveTree(1)print(listOfTrees)myTree = retrieveTree(0)print(getNumLeafs(myTree))print(getTreeDepth(myTree))

plotTree函数

    def plotTree(myTree, parentPt, nodeTxt):## 计算树的宽与高numLeafs = getNumLeafs(myTree)depth = getTreeDepth(myTree)firstStr = list(myTree)[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]## 减少y偏移值plotTree.yOff = plotTree.yOff - 1.0 / plotTree.totalDfor key in secondDict.keys():if type(secondDict[key]).__name__ == 'dict':plotTree(secondDict[key], cntrPt, str(key))else:plotTree.xOff = plotTree.xOff + 1.0 / plotTree.totalWplotNode(secondDict[key], (plotTree.xOff, plotTree.yOff), cntrPt, leafNode)plotMidText((plotTree.xOff, plotTree.yOff), cntrPt, str(key))plotTree.yOff = plotTree.yOff + 1.0 / plotTree.totalD##更新后的createPlotdef createPlot(inTree):fig = plt.figure(1, facecolor='white')fig.clf()# createPlot.ax1 = plt.subplot(111, frameon= False)# plotNode(U'Decision Node', (0.5, 0.1), (0.1, 0.5), decisionNode)# plotNode(U'Leaf Node', (0.8, 0.1), (0.3, 0.8), leafNode)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.totalWplotTree.yOff = 1.0plotTree(inTree, (0.5, 1.0), '')plt.show()if __name__ == '__main__':myTree = retrieveTree(0)createPlot(myTree)

3.3 测试和存储分类器

3.3.1 测试算法:使用决策树执行分类

使用决策树的分类函数

    def classify(inputTree, featLabels, testVec):firstStr = list(inputTree)[0]secondDict = inputTree[firstStr]## 将标签字符串转换为索引featIndex = featLabels.index(firstStr)for key in secondDict.keys():if testVec[featIndex] == key:if type(secondDict[key]).__name__ == 'dict':classLabel = classify(secondDict[key], featLabels, testVec)else:classLabel = secondDict[key]return classLabelif __name__ == '__main__':myDat, labels = creatDataSet()print(labels)myTree = treePlotter.retrieveTree(0)print(myTree)print(classify(myTree, labels, [1, 0]))print(classify(myTree, labels, [1, 1]))

3.3.2 使用算法决策树的存储

使用pickle模块存储决策树

    def storeTree(inputTree, filename):import picklefw = open(filename, 'wb')pickle.dump(inputTree, fw)fw.close()def grabTree(filename):import picklefr = open(filename, 'rb')return pickle.load(fr)if __name__ == '__main__':myDat, labels = creatDataSet()print(labels)myTree = treePlotter.retrieveTree(0)storeTree(myTree, './resource/classifierStorage.txt')classifyTree = grabTree('./resource/classifierStorage.txt')print(classifyTree)

3.4 示例:使用决策树预测隐形眼镜类型

    def use():fr = open('./resource/lenses.txt', 'r')lenses = [inst.strip().split('\t') for inst in fr.readlines()]lensesLabels = ['age', 'prescript', 'astigmatic', 'tearRate']lensesTree = createTree(lenses, lensesLabels)print(lensesTree)treePlotter.createPlot(lensesTree)if __name__ == '__main__':use()

决策树非常好地匹配了实验数据,然而这些匹配选项可能太多了。我们将这种问题称之为过度匹配(overfitting)。为了减少过度匹配问题,我们可以裁剪决策树,去掉一些不必要地叶子节点。如果叶子节点只能增加少许信息,则可以删除该节点,将它并入到其他叶子节点中。


http://www.ppmy.cn/embedded/6438.html

相关文章

2024运营级租房源码管理PHP后台+uniapp前端(app+小程序+H5)

内容目录 一、详细介绍二、效果展示1.部分代码2.效果图展示 一、详细介绍 房产系统 一款基于ThinkPHPUniapp开发的房产管理系统&#xff0c;支持小程序、H5、APP&#xff1b;包含房客、房东、经纪人三种身份。核心功能有&#xff1a;新盘销售、房屋租赁、地图找房、房源代理、…

设计模式-抽象工厂模式(Abstract Factory Pattern)结构|原理|优缺点|场景|示例

目录 设计模式&#xff08;分类&#xff09; 设计模式&#xff08;六大原则&#xff09; 创建型 工厂方法 抽象工厂模式 单例模式 建造者模式 原型模式 结构型 适配器模式 抽象工厂模式&#xff08;Abstract Factory Pattern&#xff…

每日三个JAVA经典面试题(四十四)

1.什么是反应式编程&#xff1f;它如何优化Web应用的性能&#xff1f; 反应式编程是一种编程范式&#xff0c;它通过异步数据流的方式处理事件和数据&#xff0c;以实现高效的数据处理和事件驱动的程序设计。在反应式编程中&#xff0c;数据流可以被观察&#xff08;Observabl…

Java工具类:封装Okhttp实现:Get、Post、上传/下载文件、Stream响应、代理ip

不好用请移至评论区揍我 原创代码,请勿转载,谢谢! 一、介绍 本文代码是引入Okhttp_v4.11.0,在这个基础上进行二次封装使调用方更加容易,只关注业务,而无需处理各种请求相关的重复性操作,类似文件类型请求体封装或者Form表单构造及body传参等一系列处理工具代码包括但不限…

if __name__ == ‘__main__‘ 是个什么鬼?

有句话经典的概括了这段代码的意义&#xff1a; “Make a script both importable and executable” 意思就是说让你写的脚本模块既可以导入到别的模块中用&#xff0c;另外该模块自己也可执行。 咋一看&#xff0c;其实我也不理解到底是个什么球意思&#xff0c;这里我们写两个…

Oracle SQL - HAVING和分析函数的执行顺序

分析函数是基于最终的结果集进行开窗的&#xff0c;所以HAVING比分析函数先执行 ↓ 没有HAVING时&#xff0c;MAX(col3) over()是A2 SQL> WITH subq_a AS2 (SELECT A col1, A1 col2, 10 col33 FROM dual4 UNION ALL5 SELECT A col1, A1 col2, -5 col36 F…

导致程序崩溃删除的地方

1. 注册日志回调函数 qInstallMessageHandler(outputMessage);解决方法 Android 环境下直接不重定向日志&#xff0c;经过后期调试应该是 dynamic_static 转换失败导致。 #ifndef __ANDROID__qInstallMessageHandler(outputMessage); #endif2. 单例上锁&#xff0c;新增了 A…

SpringBlade dict-biz/list SQL 注入漏洞复现

0x01 免责声明 请勿利用文章内的相关技术从事非法测试&#xff0c;由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;作者不为此承担任何责任。工具来自网络&#xff0c;安全性自测&#xff0c;如有侵权请联系删…