一、问题描述:使用西瓜数据集构建决策树,并将构建的决策树进行可视化操作。
二、问题简析:首先我们简单的介绍一下什么是决策树。决策树是广泛用于分类和回归任务的模型。本质上,它从一层层的if/else问题中进行学习,并得出结论。然后不妨看看下面这个小思考题吧:(故事我瞎编的,看问题就好了嘛)
上官婉儿机缘巧合之下喜欢上了一个只有一面之缘的娜可露露(一见钟情嘛)。对吧,爱情的力量是伟大的,上官婉儿就不顾一切的裸辞叻,去找人家。这不总算找到了人家里,但是考验接踵而至呀!人娜可露露对上官婉儿还挺满意的哦,可姑娘的父母说还得考验一下不是?啥考验呢?
思辨:家里有两个卧室,老丈人,丈母娘各站一个卧室门外,娜可露露就在其中一个卧室中。老丈人向来只说真话,丈母娘向来只说假话。现在只允许上官婉儿向两人中任意一个人提一个问题,只有成功问出娜可露露在哪间卧室之中才能抱得美人归。上官婉儿该怎么提问?(答案在文章末尾)
为什么会来一个看似无关的废话连篇叻?其实问题有相通之处的,我们的目标是通过提出尽可能少的if/else问题来得到正确答案。
三、代码实现:
为了控制文章篇幅,现在就直接给出问题的代码实现吧:
from random import choice from collections import Counter import math # 定义数据集 D = [ {'色泽': '青绿', '根蒂': '蜷缩', '敲声': '浊响', '纹理': '清晰', '脐部': '凹陷', '触感': '硬滑', '好瓜': '是'}, {'色泽': '乌黑', '根蒂': '蜷缩', '敲声': '沉闷', '纹理': '清晰', '脐部': '凹陷', '触感': '硬滑', '好瓜': '是'}, {'色泽': '乌黑', '根蒂': '蜷缩', '敲声': '浊响', '纹理': '清晰', '脐部': '凹陷', '触感': '硬滑', '好瓜': '是'}, {'色泽': '青绿', '根蒂': '蜷缩', '敲声': '沉闷', '纹理': '清晰', '脐部': '凹陷', '触感': '硬滑', '好瓜': '是'}, {'色泽': '浅白', '根蒂': '蜷缩', '敲声': '浊响', '纹理': '清晰', '脐部': '凹陷', '触感': '硬滑', '好瓜': '是'}, {'色泽': '青绿', '根蒂': '稍蜷', '敲声': '浊响', '纹理': '清晰', '脐部': '稍凹', '触感': '软粘', '好瓜': '是'}, {'色泽': '乌黑', '根蒂': '稍蜷', '敲声': '浊响', '纹理': '稍糊', '脐部': '稍凹', '触感': '软粘', '好瓜': '是'}, {'色泽': '乌黑', '根蒂': '稍蜷', '敲声': '浊响', '纹理': '清晰', '脐部': '稍凹', '触感': '硬滑', '好瓜': '是'}, {'色泽': '乌黑', '根蒂': '稍蜷', '敲声': '沉闷', '纹理': '稍糊', '脐部': '稍凹', '触感': '硬滑', '好瓜': '否'}, {'色泽': '青绿', '根蒂': '硬挺', '敲声': '清脆', '纹理': '清晰', '脐部': '平坦', '触感': '软粘', '好瓜': '否'}, {'色泽': '浅白', '根蒂': '硬挺', '敲声': '清脆', '纹理': '模糊', '脐部': '平坦', '触感': '硬滑', '好瓜': '否'}, {'色泽': '浅白', '根蒂': '蜷缩', '敲声': '浊响', '纹理': '模糊', '脐部': '平坦', '触感': '软粘', '好瓜': '否'}, {'色泽': '青绿', '根蒂': '稍蜷', '敲声': '浊响', '纹理': '稍糊', '脐部': '凹陷', '触感': '硬滑', '好瓜': '否'}, {'色泽': '浅白', '根蒂': '稍蜷', '敲声': '沉闷', '纹理': '稍糊', '脐部': '凹陷', '触感': '硬滑', '好瓜': '否'}, {'色泽': '乌黑', '根蒂': '稍蜷', '敲声': '浊响', '纹理': '清晰', '脐部': '稍凹', '触感': '软粘', '好瓜': '否'}, {'色泽': '浅白', '根蒂': '蜷缩', '敲声': '浊响', '纹理': '模糊', '脐部': '平坦', '触感': '硬滑', '好瓜': '否'}, {'色泽': '青绿', '根蒂': '蜷缩', '敲声': '沉闷', '纹理': '稍糊', '脐部': '稍凹', '触感': '硬滑', '好瓜': '否'}, ] # ========== # 决策树生成类 # ========== class DecisionTree: def __init__(self, D, label, chooseA): self.D = D # 数据集 self.label = label # 哪个属性作为标签 self.chooseA = chooseA # 划分方法 self.A = list(filter(lambda key: key != label, D[0].keys())) # 属性集合A # 获得A的每个属性的可选项 self.A_item = {} for a in self.A: self.A_item.update({a: set(self.getClassValues(D, a))}) self.root = self.generate(self.D, self.A) # 生成树并保存根节点 # 获得D中所有className属性的值 def getClassValues(self, D, className): return list(map(lambda sample: sample[className], D)) # D中样本是否在A的每个属性上相同 def isSameInA(self, D, A): for a in A: types = set(self.getClassValues(D, a)) if len(types) > 1: return False return True # 构建决策树,递归生成节点 def generate(self, D, A): node = {} # 生成节点 remainLabelValues = self.getClassValues(D, self.label) # D中的所有标签 remainLabelTypes = set(remainLabelValues) # D中含有哪几种标签 if len(remainLabelTypes) == 1: # 当前节点包含的样本全属于同个类别,无需划分 return remainLabelTypes.pop() # 标记Node为叶子结点,值为仅存的标签 most = max(remainLabelTypes, key=remainLabelValues.count) # D占比最多的标签 if len(A) == 0 or self.isSameInA(D, A): # 当前属性集为空,或是所有样本在所有属性上取值相同,无法划分 return most # 标记Node为叶子结点,值为占比最多的标签 a = self.chooseA(D,A,self) # 划分选择 for type in self.A_item[a]: condition = (lambda sample: sample[a] == type) # 决策条件 remainD = list(filter(condition, D)) # 剩下的样本 if len(remainD) == 0: # 当前节点包含的样本集为空,不能划分 node.update({type: most}) # 标记Node为叶子结点,值为占比最多的标签 else: # 继续对剩下的样本按其余属性划分 remainA = list(filter(lambda x: x != a, A)) # 未使用的属性 _node = self.generate(remainD, remainA) # 递归生成子代节点 node.update({type: _node}) # 把生成的子代节点更新到当前节点 return {a: node} # 定义划分方法 # 随机选择 def random_choice(D, A, tree: DecisionTree): return choice(A) # 信息熵 def Ent(D,label,a,a_v): D_v = filter(lambda sample:sample[a]==a_v,D) D_v = map(lambda sample:sample[label],D_v) D_v = list(D_v) D_v_length = len(D_v) counter = Counter(D_v) info_entropy = 0 for k, v in counter.items(): p_k = v / D_v_length info_entropy += p_k * math.log(p_k, 2) return -info_entropy # 信息增益 def information_gain(D, A, tree: DecisionTree): gain = {} for a in A: gain[a] = 0 values = tree.getClassValues(D, a) counter = Counter(values) for a_v,nums in counter.items(): gain[a] -= (nums / len(D)) * Ent(D,tree.label,a,a_v) return max(gain.keys(),key=lambda key:gain[key]) # 创建决策树 desicionTreeRoot = DecisionTree(D, label='好瓜',chooseA=information_gain).root print('决策树:', desicionTreeRoot) # 决策树可视化类 class TreeViewer: def __init__(self): from graphviz import Digraph self.id_iter = map(str, range(0xffff)) self.g = Digraph('G', filename='decisionTree.gv') def create_node(self, label, shape=None): id = next(self.id_iter) self.g.node(name=id, label=label, shape=shape, fontname="Microsoft YaHei") return id def build(self, key, node, from_id): for k in node.keys(): v = node[k] if type(v) is dict: first_attr = list(v.keys())[0] id = self.create_node(first_attr+"?", shape='box') self.g.edge(from_id, id, k, fontsize = '12', fontname="Microsoft YaHei") self.build(first_attr, v[first_attr], id) else: id = self.create_node(v) self.g.edge(from_id, id, k, fontsize = '12', fontname="Microsoft YaHei") def show(self, root): first_attr = list(root.keys())[0] id = self.create_node(first_attr+"?", shape='box') self.build(first_attr, root[first_attr], id) self.g.view() # 显示创建的决策树 viewer = TreeViewer() viewer.show(desicionTreeRoot)
四、可能出现的错误:
实验中有一些库是必须要使用到的,总体上不会有太大的问题,但是我们需要特别注意一下graphviz库。一般情况下,使用AnaConda开发的话,我们会在AnaConda Prompt中直接使用pip install graphviz命令来安装包。这样使用之后,Jupyter Notebook中显示导包成功,但是有一堆报错,也无法让决策树成功可视化。上述问题的一般报错为graphviz.backend.ExecutableNotFound:
为了解决这个问题在网上寻找了很多种方法,也基本上都尝试了一下。现在就直接给大家一个比较简便的解决方法!
failed to execute [‘dot’, ‘-Tpdf’, ‘-O’, ‘test-table.gv’], make sure
the Graphviz executables are on your systems’ PATH
解决方法:去graphviz官网上下载graphviz-2.38.msi官网下载路径,或者直接点这个链接去下载graphviz绿色简便下载
推荐使用绿色简便下载,当然要是不怕麻烦的话也可以选择去官网下载哈。下载解压完成之后还需要对graphivz进行配置,将graphviz文件夹的bin目录完整路径添加在系统变量Path中。最后重启Jupyter Notebook,便可以运行成功。
或者下载后复制到当前目录添加如下代码
os.environ['PATH']+=r";.\Graphviz\bin"
五、样例运行结果:
思辨部分的参考问法(以问老丈人为例): “老丈人,你说我要是问丈母娘贝贝在哪间卧室,她会告诉我什么答案?”
这样问答案就出来了已经:因为老丈人只说真话,所以他告诉你的必然是丈母娘说的假话,故而正确的答案就是老丈人没有说的那一间卧室。向丈母娘提问也是一样的道理,解决问题的方法有很多。
blog.csdn.net/qq_45636998/article/details/110925825
最新评论