决策树 ID3 算法
1.编程题目理解
利用基于信息熵进行划分选择的决策树算法,实现对于下图数据的处理。
2.ID3算法的原理解释
一棵树包含一个根节点、若干个内部节点和若干个叶子节点,叶子节点对应于决策结果,其他每个结点则对应于一个属性测试;每个结点包括的样本集合根据属性测试的结果被划分到子节点中;根结点包含样本全集。从根节点到每个叶结点的路径对应了一个判定测设序列。决策树学习的目的是为了产生一颗泛化能力强,即处理未见示例能力强的决策树,其基本原理是“分而治之”的策略。
3.设计思路
4.测试结果及分析
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-r0cUCycU-1638601757795)(C:\Users\王鹏展\AppData\Roaming\Typora\typora-user-images\image-20211005172054645.png)]
上图为最后生成的决策数。可以看出,结果计算纹理是权重最大的特征,因此用它来第一次分类筛选。纹理本身有三种特性,分为三个分支路线。在清晰这一路线中,根据密度小于0.3815又可以二项分类,判断是好瓜还是坏瓜。稍糊这一类,根据触感进行二项分类。最后可得上图的决策树,好瓜和坏瓜的比例可由下面的坐标轴进行比较。
5.代码结构,核心代码分析
class Node(object): # 创建一个类,用来表示节点的信息def __init__(self,title):self.title = title # 上一级指向该节点的线上的标记文字self.v = 1 # 节点的信息标记self.children = [] # 节点的孩子列表self.deep = 0 # 节点深度self.ID = -1 # 节点编号
'''
这个类是后面决策树的开端
'''
def mostCommonY(D): # 寻找D中样本数最多的类别res = dataset[D[0],n-1]# D中第一个样本标签maxC = 1count = {}count[res] = 1 # 该标签数量记为1for i in range(1,len(D)):curV = dataset[D[i],n-1]# 得到D中第i+1个样本的标签if curV not in count: # 若之前不存在这个标签count[curV] = 1 # 则该标签数量记为1else:count[curV] += 1 # 否则 ,该标签对应的数量加一if count[curV]>maxC: # maxC始终存贮最多标签对应的样本数量maxC = count[curV] # res 存贮当前样本数最多的标签类型res = curVreturn res # 返回的是样本数最多的标签的类型'''
计算出样本第一的标签类型进行第一个分类
'''
def entropyD(D): # 参数D中所存的样本的交叉熵types = [] # 存贮类别标签count = {} # 存贮每个类别对应的样本数量for i in range(len(D)): # 统计D中存在的每个类型的样本数量curY = dataset[D[i],n-1]if curY not in count:count[curY] = 1types.append(curY)else:count[curY] += 1ans = 0total = len(D) # D中样本总数量for i in range(len(types)): # 计算交叉熵ans -= count[types[i]]/total*math.log2(count[types[i]]/total)return ansdef gain(D,p): # 属性 p 上的信息增益if type(dataset[0,p])== type(dataset[0,8]): # 判断若是连续属性,则调用另一个函数res,divideV = gainFloat(D,p)else:types = []count = {}for i in range(len(D)):#得到每一个属性取值上的样本编号a = dataset[D[i],p]if a not in count:count[a] = [D[i]]types.append(a)else:count[a].append(D[i])res = entropyD(D) # D的交叉熵total = len(D)for i in range(len(types)): # 计算出每一个属性取值分支上的交叉熵,再计算出信息增益res -= len(count[types[i]])/total*entropyD(count[types[i]])divideV = -1000 # 这个只是随便给的一个值,没有实际意义return res,divideV
def treeGenerate(D,A,title):node = Node(title)if isSameY(D): # D中所有样本是否属于同一类node.v = dataset[D[0],n-1]return node# 是否所有属性全部使用过或者D中所有样本的未使用的属性均相同if isBlankA(A) or isSameAinD(D,A):node.v = mostCommonY(D) # 此时类别标记为样本数最多的类别(暗含可以处理存在异常样本的情况)return node # 否则所有样本的类别应该一致entropy = 0floatV = 0p = 0for i in range(len(A)): # 循环遍历A,找可以获得最大信息增益的属性if(A[i]>0):curEntropy,divideV = gain(D,i)if curEntropy > entropy:p = i # 存贮属性编号entropy = curEntropyfloatV = divideVif isSameValue(-1000,floatV,EPS): # 说明是离散属性node.v = Attributes[p]+"=?" # 节点信息curSet = attributeList[p] # 该属性的所有取值for i in curSet:Dv = []for j in range(len(D)): # 获得该属性取某一个值时对应的样本标号if dataset[D[j],p]==i:Dv.append(D[j])
# 若该属性取值对应没有符合的样本,则将该分支作为叶子,类别是D中样本数最多的类别
# 其实就是处理在没有对应的样本情况下的问题。那就取最大可能性的一类。if Dv==[]:nextNode = Node(i)nextNode.v = mostCommonY(D)node.children.append(nextNode)else: # 若存在对应的样本,则递归继续生成该节点下的子树newA = copy.deepcopy(A) # 注意是深度复制,否则会改变A中的值newA[p]=-1node.children.append(treeGenerate(Dv,newA,i))else: # 若对应的是连续的属性Dleft = []Dright = []node.v = Attributes[p]+"<="+str(floatV)+"?" # 节点信息for i in range(len(D)): # 根据划分点将样本分成左右两部分if dataset[D[i],p]<=floatV: Dleft.append(D[i])else: Dright.append(D[i]) node.children.append(treeGenerate(Dleft,A[:],"是")) # 左边递归生成子树,是 yes 分支 node.children.append(treeGenerate(Dright,A[:],"否")) # 同上。 注意,在此时没有将对应的A中值变成 -1return node # 因为连续属性可以使用多次进行划分
6.本次实验解决的问题,主要收获
了解了决策树的实现机制,决策树可以很好的给我们一个分类思路,而且树状结构易读简单。决策树的应用范围很广,尤其在一些策略性游戏中,比如钢铁雄心等。并且注册了Github账号,学会了下载了Github其他用户上传的源码和文件。
7.参考来源
Decision-Tree-based-on-information-entropy/main.py at master · qdbszsj/Decision-Tree-based-on-information-entropy (github.com)
(9条消息) 西瓜书课后题——第四章(决策树)_乂乂乂乂的博客-CSDN博客
(9条消息) 西瓜书 习题4.3 编程实现信息熵决策树、绘制决策树、解决matplotlib中文乱码问题_世靖的码场-CSDN博客
import numpy as np
import pandas as pd
import math
import copy
import matplotlib.pyplot as plt
import matplotlib as mpl
mpl.rcParams[u'font.sans-serif'] = ['simhei']
mpl.rcParams['axes.unicode_minus'] = Falsedataset = pd.read_csv('./watermelon_3.csv',encoding = 'utf8') # 读取数据
Attributes = dataset.columns #所有属性的名称
#print(Attributes)
m,n = np.shape(dataset) # 得到数据集大小
dataset = np.matrix(dataset)
for i in range(m): # 将标签替换成 好瓜 和 坏瓜if dataset[i,n-1]=='是': dataset[i,n-1] = '好瓜'else : dataset[i,n-1] = '坏瓜'
attributeList = [] # 属性列表,每一个属性的取值,列表中元素是集合
for i in range(n):curSet = set() # 使用集合是利用了集合里面元素不可重复的特性,从而提取出了每个属性的取值for j in range(m):curSet.add(dataset[j,i])attributeList.append(curSet)
#print(attributeList)
D = np.arange(0,m,1) # 表示每一个样本编号
A = list(np.ones(n)) # 表示每一个属性是否被使用,使用过了标为 -1
A[-1] = -1 # 将数据里面的标签和编号列标记为 -1
A[0] = -1
#print(A)
#print(D)
EPS = 0.000001class Node(object): # 创建一个类,用来表示节点的信息def __init__(self,title):self.title = title # 上一级指向该节点的线上的标记文字self.v = 1 # 节点的信息标记self.children = [] # 节点的孩子列表self.deep = 0 # 节点深度self.ID = -1 # 节点编号def isSameY(D): # 判断所有样本是否属于同一类curY = dataset[D[0],n-1]for i in range(1,len(D)):if dataset[D[i],n-1] != curY:return Falsereturn Truedef isBlankA(A): # 判断 A 是否是空,是空则返回truefor i in range(n):if A[i]>0: return Falsereturn Truedef isSameAinD(D,A): # 判断在D中,是否所有的未使用过的样本属性均相同for i in range(n):if A[i]>0:for j in range(1,len(D)):if not isSameValue(dataset[D[0],i],dataset[D[j],i],EPS):return Falsereturn Truedef isSameValue(v1,v2,EPS): # 判断v1、v2 是否相等if type(v1)==type(dataset[0,8]):return abs(v1-v2)<EPSelse: return v1==v2def mostCommonY(D): # 寻找D中样本数最多的类别res = dataset[D[0],n-1] # D中第一个样本标签maxC = 1count = {}count[res] = 1 # 该标签数量记为1for i in range(1,len(D)):curV = dataset[D[i],n-1] # 得到D中第i+1个样本的标签if curV not in count: # 若之前不存在这个标签count[curV] = 1 # 则该标签数量记为1else:count[curV] += 1 # 否则 ,该标签对应的数量加一if count[curV]>maxC: # maxC始终存贮最多标签对应的样本数量maxC = count[curV] # res 存贮当前样本数最多的标签类型res = curVreturn res # 返回的是样本数最多的标签的类型def entropyD(D): # 参数D中所存的样本的交叉熵'''交叉熵是信息论中的一个重要概念,它的大小表示两个概率分布之间的差异,可以通过最小化交叉熵来得到目标概率分布的近似分布'''types = [] # 存贮类别标签count = {} # 存贮每个类别对应的样本数量for i in range(len(D)): # 统计D中存在的每个类型的样本数量curY = dataset[D[i],n-1]if curY not in count:count[curY] = 1types.append(curY)else:count[curY] += 1ans = 0total = len(D) # D中样本总数量for i in range(len(types)): # 计算交叉熵ans -= count[types[i]]/total*math.log2(count[types[i]]/total)return ansdef gain(D,p): # 属性 p 上的信息增益'''信息增益越大,纯度提升越大,纯度越高,样本越能划分为同一类别'''if type(dataset[0,p])== type(dataset[0,8]): # 判断若是连续属性,则调用另一个函数res,divideV = gainFloat(D,p)else:types = []count = {}for i in range(len(D)): # 得到每一个属性取值上的样本编号a = dataset[D[i],p]if a not in count:count[a] = [D[i]]types.append(a)else:count[a].append(D[i])res = entropyD(D) # D的交叉熵total = len(D)for i in range(len(types)): # 计算出每一个属性取值分支上的交叉熵,再计算出信息增益res -= len(count[types[i]])/total*entropyD(count[types[i]])divideV = -1000 # 这个只是随便给的一个值,没有实际意义return res,divideVdef gainFloat(D,p): # 获得在连续属性上的最大信息增益及对应的划分点a = []for i in range(len(D)): # 得到在该属性上的所有取值a.append(dataset[D[i],p])a.sort() # 排序T = []for i in range(len(a)-1): # 计算每一个划分点T.append((a[i]+a[i+1])/2)res = entropyD(D) # D的交叉熵ans = 0divideV = T[0]for i in range(len(T)): # 循环根据每一个分割点进行划分left = []right = []for j in range(len(D)): # 根据特定分割点将样本分成两部分if (dataset[D[j],p]<=T[i]):left.append(D[j])else:right.append(D[j])temp = res-entropyD(left)-entropyD(right) # 计算特定分割点下的信息增益if temp>ans:divideV = T[i] # 始终存贮产生最大信息增益的分割点ans = temp # 存贮最大的信息增益return ans,divideVdef treeGenerate(D,A,title):node = Node(title)if isSameY(D): # D中所有样本是否属于同一类node.v = dataset[D[0],n-1]return node# 是否所有属性全部使用过 或者 D中所有样本的未使用的属性均相同if isBlankA(A) or isSameAinD(D,A):node.v = mostCommonY(D) # 此时类别标记为样本数最多的类别(暗含可以处理存在异常样本的情况)return node # 否则所有样本的类别应该一致entropy = 0floatV = 0p = 0for i in range(len(A)): # 循环遍历A,找可以获得最大信息增益的属性if(A[i]>0):curEntropy,divideV = gain(D,i)if curEntropy > entropy:p = i # 存贮属性编号entropy = curEntropyfloatV = divideVif isSameValue(-1000,floatV,EPS): # 说明是离散属性node.v = Attributes[p]+"=?" # 节点信息curSet = attributeList[p] # 该属性的所有取值for i in curSet:Dv = []for j in range(len(D)): # 获得该属性取某一个值时对应的样本标号if dataset[D[j],p]==i:Dv.append(D[j])# 若该属性取值对应没有符合的样本,则将该分支作为叶子,类别是D中样本数最多的类别# 其实就是处理在没有对应的样本情况下的问题。那就取最大可能性的一类。if Dv==[]:nextNode = Node(i)nextNode.v = mostCommonY(D)node.children.append(nextNode)else: # 若存在对应的样本,则递归继续生成该节点下的子树newA = copy.deepcopy(A) # 注意是深度复制,否则会改变A中的值newA[p]=-1node.children.append(treeGenerate(Dv,newA,i))else: # 若对应的是连续的属性Dleft = []Dright = []node.v = Attributes[p]+"<="+str(floatV)+"?" # 节点信息for i in range(len(D)): # 根据划分点将样本分成左右两部分if dataset[D[i],p]<=floatV: Dleft.append(D[i])else: Dright.append(D[i])node.children.append(treeGenerate(Dleft,A[:],"是")) # 左边递归生成子树,是 yes 分支node.children.append(treeGenerate(Dright,A[:],"否")) # 同上。 注意,在此时没有将对应的A中值变成 -1return node # 因为连续属性可以使用多次进行划分def countLeaf(root,deep):root.deep = deepres = 0if root.v=='好瓜' or root.v=='坏瓜': # 说明此时已经是叶子节点了,所以直接返回res += 1return res,deepcurdeep = deep # 记录当前深度for i in root.children: # 得到子树中的深度和叶子节点的个数a,b = countLeaf(i,deep+1) #递归调用res += aif b>curdeep: curdeep = breturn res,curdeepdef giveLeafID(root,ID): # 给叶子节点编号if root.v=='好瓜' or root.v=='坏瓜':root.ID = IDID += 1return IDfor i in root.children:ID = giveLeafID(i,ID)return IDdef plotNode(nodeTxt,centerPt,parentPt,nodeType): # 绘制节点plt.annotate(nodeTxt,xy = parentPt,xycoords='axes fraction',xytext=centerPt,textcoords='axes fraction',va="center",ha="center",bbox=nodeType,arrowprops=arrow_args)def dfsPlot(root):if root.ID==-1: # 说明根节点不是叶子节点childrenPx = []meanPx = 0for i in root.children:cur = dfsPlot(i)meanPx += curchildrenPx.append(cur)meanPx = meanPx/len(root.children)c = 0for i in root.children:nodetype = leafNodeif i.ID<0: nodetype=decisionNodeplotNode(i.v,(childrenPx[c],0.9-i.deep*0.8/deep),(meanPx,0.9-root.deep*0.8/deep),nodetype)plt.text((childrenPx[c]+meanPx)/2,(0.9-i.deep*0.8/deep+0.9-root.deep*0.8/deep)/2,i.title)c += 1return meanPxelse:return 0.1+root.ID*0.8/(cnt-1)myDecisionTreeRoot = treeGenerate(D,A,"root") # 生成决策树
cnt,deep = countLeaf(myDecisionTreeRoot,0) # 得到树的深度和叶子节点的个数
giveLeafID(myDecisionTreeRoot,0)
# 绘制决策树
decisionNode = dict(boxstyle = "sawtooth",fc = "0.9",color='blue')
leafNode = dict(boxstyle = "round4",fc="0.9",color='red')
arrow_args = dict(arrowstyle = "<-",color='green')
fig = plt.figure(1,facecolor='white')
rootX = dfsPlot(myDecisionTreeRoot)
plotNode(myDecisionTreeRoot.v,(rootX,0.9),(rootX,0.9),decisionNode)
plt.show()
决策树 ID3 算法相关推荐
- 决策树---ID3算法
决策树---ID3算法 决策树: 以天气数据库的训练数据为例. Outlook Temperature Humidity Windy PlayGolf? sunny 85 85 FALSE no ...
- 决策树ID3算法[分类算法]
ID3分类算法的编码实现 1 <?php 2 /* 3 *决策树ID3算法(分类算法的实现) 4 */ 5 6 7 8 /* 9 10 *求信息增益Grain(S1,S2) 11 12 */ 1 ...
- 机器学习算法—决策树(ID3)算法
机器学习--决策树(ID3)算法 1.决策树(ID3)算法 1.1 算法引入 我们首先以一个分类问题开始,假设我们有这样一份样本数据: 我们的目标是想通过色泽.根蒂.敲声.纹理.脐部.触感来判断这是不 ...
- 【Machine Learning in Action --3】决策树ID3算法
1.简单概念描述 决策树的类型有很多,有CART.ID3和C4.5等,其中CART是基于基尼不纯度(Gini)的,这里不做详解,而ID3和C4.5都是基于信息熵的,它们两个得到的结果都是一样的,本次定 ...
- id3决策树_信息熵、信息增益和决策树(ID3算法)
决策树算法: 优点:计算复杂度不高,输出结果易于理解,对中间值的缺失不敏感,可以处理不相关的特征数据. 缺点:可能会产生过度匹配问题. 适用数据类型:数值型和标称型. 算法原理: 决策树是一个简单的为 ...
- python决策树id3算法_python实现决策树ID3算法
一.决策树概论 决策树是根据训练数据集,按属性跟类型,构建一棵树形结构.可以按照这棵树的结构,对测试数据进行分类.同时决策树也可以用来处理预测问题(回归). 二.决策树ID3的原理 有多种类型的决策树 ...
- 数据挖掘--决策树ID3算法(例题)
决策树分类算法 决策树分类算法通常分为两个步骤:决策树生成和决策树修剪. 决策树生成算法的输入参数是一组带有类别标记的样本,输出是构造一颗决策树,该树可以是一棵二叉树或多叉树.二叉树的内部结点(非叶子 ...
- 【机器学习】决策树-ID3算法
1.ID3算法 ID3算法利用信息增益进行特征的选择进行树的构建.信息熵的取值范围为0~1,值越大,越不纯,相反值越小,代表集合纯度越高.信息增益反映的是给定条件后不确定性减少的程度.每一次对决策树进 ...
- 决策树ID3算法手动实现
1. ID3算法 决策树中每一个非叶结点对应着一个非类别属性,树枝代表这个属性的值.一个叶结点代表从树根到叶结点之间的路径对应的记录所属的类别属性值. 每一个非叶结点都将与属性中具有最大信息量的非类别 ...
- python决策树id3算法_决策树ID3算法预测隐形眼睛类型--python实现
标签: 本节讲解如何预测患者需要佩戴的隐形眼镜类型. 1.使用决策树预测隐形眼镜类型的一般流程 (1)收集数据:提供的文本文件(数据来源于UCI数据库) (2)准备数据:解析tab键分隔的数据行 (3 ...
最新文章
- Mysql数据库 sql 语句调优
- 用计算机a 3如何定义,计算机绘图A 3次.doc
- 实战SSM_O2O商铺_35【商品】商品编辑之View层的实现
- Hadoop MapReduce的一些相关代码Code
- Oracle收购云安全创企Palerra,以加强安全堆栈
- java 文件封装_Java 封装
- linux平台下使用boost库
- 02Framelayout:帧布局
- 11月16日云栖精选夜读:阿里云 oss JavaScript客户端签名文件上传 vue2.0
- Spring Boot整合MongoDB实现增删改查
- 绿茶软件测试自学,7号心理测试小程序
- 计算机桌面维护面试题,100 | 运维常见面试题
- 修改了下exeScope的导出函数功能,让它只导出函数名。。。
- Java架构师发展路线
- C# 海康人脸识别设备初开发(一)
- Failed to obtain JDBC Connection;
- 玩吧接入流程(暂时记记)
- Linux 修改DNS配置
- 外贸公司怎么做开发信,什么企业邮箱更适合发开发信呢?
- 什么是Hash哈希(散列表)