面向对象程序设计课程设计:利用决策树方法判定西瓜质量
目录
- 第一章课程设计的目的及要求
- 1.1课程设计目标
- 1.2课程设计实验环境
- 1.3课程设计的预备知识
- 1.4课程设计要求
- 第二章课程设计的内容
- 2.1c++语言程序设计——《利用决策树方法判定西瓜质量》课题内容
- 2.1.1问题描述
- 2.1.2 功能要求
- 2.2问题分析
- 2.2.1 引入
- 2.2.2相关概念
- 2.2.3 基本理论
- 2.3 后端程序设计
- 2.3.1 架构
- 2.3.2类的属性
- 2.3.3类的函数
- 2.3.4main函数的流程
- 2.3.5头文件说明
- 2.4后端程序设计的图像化
- 2.4.1 UML类图
- 2.4.2 决策树构建的流程图
- 2.4.3利用决策树进行判断的流程图
- 2.5 程序运行结果
- 2.5.1信息增益
- 2.5.2决策树
- 2.5.3随机数据
- 2.5.4判断数据并输出
- 第三章 总结与感悟
- 3.1程序设计中的不足之处
- 3.2对答辩中问题的解答
- 3.3感悟
- 附录:部分核心代码(完整代码请移步下载区)
- treenode.h
- treenode.cpp
- main.cpp
第一章课程设计的目的及要求
1.1课程设计目标
《面向对象编程课程设计》是必修的实践性教学环节之一,是学习了《程序设计基础》和《面向对象程序设计》课程之后的综合性实验课程,是对这两门课程所学知识所进行的一次全面的综合训练。通过学生完成所要求的设计项目,使学生系统掌握面向对象程序设计的基本理念、基本语法、实现方法、设计特性以及编程思想,综合培养学生利用所学的编程知识解决复杂工程问题的能力。学生根据设计要求首先进行需求分析、制定总体方案、设计程序架构、功能及类层次结构图,然后完成算法设计、程序开发、程序调试、程序优化和程序发布,最后撰写课程设计报告并提交程序代码清单。课程设计具体目标如下:
课程目标1. 能够针对设计题目,通过调研或者查找资料,进行初步需求分析和系统总体设计的能力。
课程目标2.能够利用面向对象程序设计的思想完成系统的设计并编程实现。
课程目标3.能够根据设计工作量和人员特点进行合理分工。小组人员配合默契,能够互相交流,能够完成个人承担的任务,并且整个项目能够按时完成和保证质量。
课程目标4.能够对设计的内容进行表述,并回答老师和同学提出的问题。
课程目标5.能够绘制类图和流程图,具有按照软件工程思路撰写完整的课程设计报告。
课程目标6. 能够对设计过程进行总结,能够意识到不足,并具有改进的意识。
1.2课程设计实验环境
硬件要求:能运行Windows 操作系统的微机系统。
软件要求:后端、底层使用Visual Studio2019,或其他C++语言应用程序的开发软件。前端使用vs.net ,QT等开发工具设计图形化的界面。
1.3课程设计的预备知识
C++程序设计基础、面向对象的程序设计:熟悉C++语言的基本知识与编程思想,会使用Visual Studio的功能对程序进行开发、调试与编译。
1.4课程设计要求
1.学习决策树相关知识,学习周志华《机器学习》相关章节。
2.根据题目要求构建完整且自洽的算法并运行,编写出符合要求的程序。
3.积极上机编写调试源程序,增强自学能力、编程能力、调试能力与心态调整能力。
4.积极与其他组员认真沟通交流。协调工作、合理分配工作任务,开展高效的多人协同开发工作,增强合作能力。
5.认真准备演示、答辩,认真书写课程设计说明报告。
6.服从指导教师安排,确保能按时完成课程设计的相关任务。
第二章课程设计的内容
2.1c++语言程序设计——《利用决策树方法判定西瓜质量》课题内容
2.1.1问题描述
以下数据集是经过确认的西瓜属性,请根据这些信息,利用决策树方法判定另外一批西瓜的质量。
编号 | 色泽 | 根蒂 | 敲声 | 纹理 | 脐部 | 触感 | 密度 | 含糖率 | 好瓜 |
---|---|---|---|---|---|---|---|---|---|
1 | 青绿 | 蜷缩 | 浊响 | 清晰 | 凹陷 | 硬滑 | 0.697 | 0.46 | 是 |
2 | 乌黑 | 蜷缩 | 沉闷 | 清晰 | 凹陷 | 硬滑 | 0.774 | 0.376 | 是 |
3 | 乌黑 | 蜷缩 | 浊响 | 清晰 | 凹陷 | 硬滑 | 0.634 | 0.264 | 是 |
4 | 青绿 | 蜷缩 | 沉闷 | 清晰 | 凹陷 | 硬滑 | 0.608 | 0.318 | 是 |
5 | 浅白 | 蜷缩 | 浊响 | 清晰 | 凹陷 | 硬滑 | 0.556 | 0.215 | 是 |
6 | 青绿 | 稍蜷 | 浊响 | 清晰 | 稍凹 | 软粘 | 0.403 | 0.237 | 是 |
7 | 乌黑 | 稍蜷 | 浊响 | 稍糊 | 稍凹 | 软粘 | 0.481 | 0.149 | 是 |
8 | 乌黑 | 稍蜷 | 浊响 | 清晰 | 稍凹 | 硬滑 | 0.437 | 0.211 | 是 |
9 | 乌黑 | 稍蜷 | 沉闷 | 稍糊 | 稍凹 | 硬滑 | 0.666 | 0.091 | 否 |
10 | 青绿 | 硬挺 | 清脆 | 清晰 | 平坦 | 软粘 | 0.243 | 0.267 | 否 |
11 | 浅白 | 硬挺 | 清脆 | 模糊 | 平坦 | 硬滑 | 0.245 | 0.057 | 否 |
12 | 浅白 | 蜷缩 | 浊响 | 模糊 | 平坦 | 软粘 | 0.343 | 0.099 | 否 |
13 | 青绿 | 稍蜷 | 浊响 | 稍糊 | 凹陷 | 硬滑 | 0.639 | 0.161 | 否 |
14 | 浅白 | 稍蜷 | 沉闷 | 稍糊 | 凹陷 | 硬滑 | 0.657 | 0.198 | 否 |
15 | 乌黑 | 稍蜷 | 浊响 | 清晰 | 稍凹 | 软粘 | 0.36 | 0.37 | 否 |
16 | 浅白 | 蜷缩 | 浊响 | 模糊 | 平坦 | 硬滑 | 0.593 | 0.042 | 否 |
17 | 青绿 | 蜷缩 | 沉闷 | 稍糊 | 稍凹 | 硬滑 | 0.719 | 0.103 | 否 |
2.1.2 功能要求
1.学习有关决策树的相关知识
2.构建每个属性的信息增益,并写入到文件Gain.txt中
3.绘制决策树,保存成文件, Decision_tree.jpg
4.利用随机算法生成不少于5W条的数据集,写入文件data.csv 中
5.利用决策树进行判定,判定结果写入到result.csv中,并记录判定所花费的绝对时间
2.2问题分析
2.2.1 引入
决策树是机器学习的一种常见方法。机器学习,就是指我们从给定的训练数据集中,构建一个模型。之后,利用这个模型,对新的未知数据集进行分类。这是一个构建抽象模型并回归具体事物的过程。而决策树是一种常用的抽象模型。它是基于树结构进行构建的。
例如,我们如何决定明天出不出去玩?我们能根据多年的经验构建出一套对天气的认知,而这构建认知的过程就可以类比“机器学习”。我们根据这一套经验,先夜观天象,如果天有异象,则明天不出去;如果天气晴朗,则进一步判断:天气预报说明天温度是多少……这是一个先根据前置条件而选择分支,后进入分支做进一步决断的过程,直到找到最终的结果,这就是决策树的模型。
2.2.2相关概念
本文对相关文献中涉及的概念进行了简化处理。
叶结点:表示决策判定的结果值。
根结点:决策树的第一步判定的属性值,可导出表示结果的叶结点或下一步判定属性值的内部节点。
内部结点:表示中间步骤中判定属性值的结点。与根结点相同,可导出表示结果的叶结点或下一步判定属性值的内部节点。
训练数据集:包括了若干项属性值与一项结果值,用作构建决策树的参数。
待测数据集:包括了若干项属性值的数据集。可以通过决策树导出其结果值。
2.2.3 基本理论
以下给出了决策树学习基本流程,简化但牺牲了一定严谨性。比起周志华老师的《机器学习》原文,这里作简化的原因包括但不限于:训练集样本容量较小、训练集样本没有缺失值、决策树深度较小,无需进行预剪枝与后剪枝。因此经过最多2次分类后,每个西瓜的好坏都是非常明确的。在此可以将几个递归返回的情形进行统一。
方法TreeGenerate(训练数据集D)
1.生成结点node;
2.If(数据集的结果值全相同,为C)
3. 将该node标记为C类叶结点 return;
4.End if
5.从数据集中选择最优划分属性值
6.for 对的每一个值()do
7. 为node生成一个分支,令表示D在上取值为的样本子集
8. if (为空)
9. 将该分支节点标记为叶结点,其判定结果值标记为D中结果值最多的值
10. return;
11. else
12. 以TreeGenerate()为分支结点,并剔除属性
该流程最关键的步骤在第5行,从数据集中选择最优划分属性值a∗a_*a∗。对于属性值划分,主要存在三种算法:信息增益(ID3),信息增益率(C4.5),基尼系数(CART)。在本文,我们使用信息增益(ID3)计算并选取最优划分属性。
“信息熵”是度量样本集合纯度的指标。假定当前样本集合D中第k类样本所占比例为pk(k=1,2,...,∣y∣)p_k(k=1,2,...,|y|)pk(k=1,2,...,∣y∣),∣y∣|y|∣y∣表示该类属性的取值的总数。则D的信息熵定义为:Ent(D)=−∑k=1∣y∣pklog2pk,若p=0则规定plogp=0Ent(D)=-\sum _{k=1}^{|y|}p_k \log _2p_k,若p=0则规定p\log p=0Ent(D)=−∑k=1∣y∣pklog2pk,若p=0则规定plogp=0。Ent(D)Ent(D)Ent(D)的值越小,D纯度越高。对离散属性a有∣y∣|y|∣y∣个可能取值,按a划分产生了∣y∣|y|∣y∣个分支节点,第v个分支节点包含了D中所有在属性a上取值为ava_vav的样本,记作DvD^vDv。我们可以计算DvD^vDv的信息熵,加权∣Dv∣/∣D∣|D^v|/|D|∣Dv∣/∣D∣为样本集DvD^vDv占D的比例,于是可以计算出用属性a对样本集D进行划分所得的信息增益Gain(D,a)=Ent(D)−∑v=1∣y∣DvDEnt(Dv)Gain(D,a)=Ent(D)-\sum_{v=1}^{|y|}\frac {D^v}{D}Ent(D^v)Gain(D,a)=Ent(D)−∑v=1∣y∣DDvEnt(Dv)。信息增益越大,则按属性a划分所得的“纯度”的提升也越大。我们使用信息增益对属性进行划分。
由于样本集合中涉及了连续值,因此,我们要引入对连续属性离散化的机制。最简单的机制是二分法。若D上的有连续属性a,a在D上出现n个不同取值记为{a1,a2,...,an}\{a_1,a_2,...,a_n\}{a1,a2,...,an},我们构建一个候选划分点的集合Ta={ai+ai+12∣1≤i≤n−1}T_a=\{\frac{a_i+a_{i+1}}{2}|1 \le i \le n-1 \}Ta={2ai+ai+1∣1≤i≤n−1}),即把[ai,ai+1)[a_i,a_{i+1})[ai,ai+1)的中位点ai+ai+12\frac{a_i+a_{i+1}}22ai+ai+1作为候选划分点。这样即完成了将连续属性离散化处理。对于每个划分点,我们也可以进行信息增益的计算:Gain(D,a)=maxt∈TaGain(D,a,t)=maxt∈TaEnt(D)−∑λ∈{−,+}∣Dtλ∣∣D∣Ent(Dtλ)Gain(D,a)=\max _{t \in{T_a}}Gain(D,a,t)=\max_{t \in T_a}Ent(D)-\sum_{λ \in \{-,+\}}\frac {|D_t^λ|}{|D|}Ent(D_t^λ)Gain(D,a)=maxt∈TaGain(D,a,t)=maxt∈TaEnt(D)−∑λ∈{−,+}∣D∣∣Dtλ∣Ent(Dtλ),Gain(D,a,t)Gain(D,a,t)Gain(D,a,t)是D对于划分点t二分后的信息增益,选其为信息增益最大化的划分点。
在决策树构建完毕后,我们使用类似于前序遍历决策树的方法判定未知数据集:首先访问根结点,之后从左至右访问子树。这里的“左”“右”指代按a∗a_*a∗属性划分时a∗va_{*v}a∗v(a0,a1,a2,...,a∣y∣−1a_0,a_1,a_2,...,a_{|y|-1}a0,a1,a2,...,a∣y∣−1)的vvv的取值。
2.3 后端程序设计
2.3.1 架构
由于本课题侧重于算法构建而非对象、类的关联与架构,所以本程序架构相对简单,由main.cpp,TreeNode.cpp,TreeNode.h三份文档组成。所构建的类有且只有一个:TreeNode类
main.cpp涉及了简单的输入输出、生成随机数据、调用生成决策树与利用决策树进行判断的函数,以便将后续工作顺利交接给前端。
TreeNode.h写了TreeNode类属性与函数的声明,TreeNode.cpp写了各个函数的具体实现。这些函数主要分为两类:一类是计算工具,计算、信息熵、信息增益、连续值间断点、判断该集合的数据是否在判定结果值上相同。这些函数代码由队友完成。另一类函数即为构建决策树与利用决策树判断所涉及的核心算法与理论。这一部分代码由我完成。在下文中,我将具体阐述这些算法的具体实现。
2.3.2类的属性
TreeNode类是树的结点,有13个属性。
Continuouspoint为double类数组,长度为2,因为数据集中有且只有2个连续属性:密度、含糖率。在每次计算信息增益时,会将密度属性的划分点写入 Continuouspoint[0],将含糖率的划分点写入Continuouspoint[1]。
isContinuous为bool类,用以判断该中间结点是否按照连续值来划分。
breakingpoint为double类,若该中间结点按照连续值划分,即(TreeNode.isContinuous),则该属性给出了划分点,该划分点由属性Continuouspoint取得。若该中间结点不按连续值划分,则该值无意义,默认值为0.
indexOfAttribute为int类,用以记录中间结点或根结点以哪个属性进行划分。
rem_wat为double类的二维数组,remaining watermelons,长度为18*10.该数组记录了剩下的未分的数据集。
sizeOfWatermelons为int类,用以记录剩下未分的数据集的数据个数。
isLeafNode为bool类,判断该节点是否为叶结点。
isGood为bool类。若该结点为叶结点,则可通过isGood类知道该西瓜是否为好瓜,得到决策判定的结果值。若该结点不是叶节点,则该属性值无意义。
entD为double类,储存了该结点所带数据集的初始信息熵。
rem_att为int类数组,长度为10。指代remaining attributes,用于记录剩下的还未分完的属性的集合。
attSize为int类,attribute size,用于记录剩下还未分完的属性的个数。
childTree为TreeNode类vector,若该结点是根结点或中间结点,则该vector记录了子树的位置。若该结点为叶结点,则该vector的长度为0且无意义。
attribute_size为静态int数组,储存了每项属性最多有几项取值。其中,编号的取值数记为-1,色泽、根蒂、敲声、纹理、脐部这五项属性都有3种取值,触感属性有2种取值。密度、含糖率为连续值,取值数量记为-1.
言而简之,每个结点都记录了rem_wat,entD,Continuouspoint,sizeOfWatermelons,attSize,rem_att,isLeafNode,childTree。中间结点与根结点还有属性isContinuous,breakingpoint,indexOfAttribute。叶结点还有属性判断西瓜好坏isGood。
2.3.3类的函数
在本文,我将侧重于介绍由我构建的函数。
有两个构造函数。一个构造函数的参数为空,将各个属性初始化并置为0.另一个构造函数有4个参数,依次为:剩下的训练数据集、剩下的数据的个数、剩下的未划分的属性的集合、剩下的属性的个数。这个构造函数能够将这四个参数代入属性值、将所有其他属性值初始化为0,并计算初始的信息熵。
deleteatt函数英文全名为delete attribute。在将大训练数据集按某个属性值分为多个小训练数据集后,调用该函数剔除该属性值,以便在子结点按另外的数据进行进一步的细分。
generate_childNode函数为这个结点生成子结点。其流程根据2.2.3中提及的TreeGenerate,参数为rem_wat和sizeOfWatermelons,即训练数据集。利用这个函数,我们便能实现对决策树的构建。这个构建决策树的过程对于连续值的划分点进行了特化处理:在计算信息增益的过程中就确定了该结点该属性的划分点。由于前6个属性均为离散值,第7和第8项属性为连续值,为了简化代码,就不用attribute_size判断是否为连续值,而是直接分为两种情况:划分属性在1~6中间、划分属性在第7或第8项。若该属性为连续值,childNode[0],childNode[1]分别储存了小于、大于划分点时子树的位置。若该属性是离散值,则childNode[i]储存了该离散值取i时子树的位置。需要说明的是,在前端的输入输出工作中,我们已经将离散值的具体文字转化为0/1/2等数字。
judgeByTree函数遍历决策树并判断未知西瓜的好坏。该函数的逻辑如下:若该结点为叶结点,则该叶结点存储的isGood属性表示了该西瓜的好坏属性,完成判断。若该结点不是叶节点而是中间结点或根结点,则该结点的indexOfAttribute存储了该结点分类依据的属性,childNode[i]是根据该属性分类构建出的子树,并将西瓜数据交给子树判断。用这种方法递归,最终可以找到叶结点并判断该西瓜的好坏。这是一种前序遍历的逻辑。
2.3.4main函数的流程
这里只涉及后端构建的main函数,而不涉及融合前端后的main函数流程。
第一步,从watermelon.csv文件中读取数据,这些数据是原始的训练数据集,保存在data[17][10]这个double类数组中。而这个文件已经将离散值的文字处理成了0/1/2等数字。
第二步,对这数据集的每列属性计算信息一次增益,并输出。
第三步,将剩余未划分的属性rem_att[8]初始化,包括了全部8个属性。
第四步,构建TreeNode类对象mytree,将data和rem_att作为参数,构建出决策树。
第五步,随机生成50000个西瓜数据,这些西瓜的数据不包括西瓜本身好坏这一属性。创建double类的行指针,即double (*ListData)[10]。由于栈区内存不能同时存放50000条数据,我们将这50000条数据存放到堆区,以便进行进一步处理。
第六步,开始计时。
第七步,利用决策树对这50000条数据进行判断,得出西瓜的好坏。
第八步,输出这50000条数据。
第九步,结束计时,并输出第五步和第八步中间所经历的绝对时间。
第十步,释放堆区内存,结束程序运行。
另外还有一个独立的步骤:用easyx将决策树绘制并输出。这一步是在已经预知结果的情况下对该结果进行输出,而非根据第四步在系统内存中构建出的决策树来输出。
2.3.5头文件说明
本文只说明后端所使用的的头文件,对前端运用QT等开发工具所使用的头文件不作涉及,对绘图所导入的库也不作涉及。
为了防止TreeNode.h与TreeNode.cpp这两份文档被多次编译,加入#pragma once语句。
Vector为stl库中的一个类模板,vector可以理解为自由伸缩长度的数组,用于记录子结点的地址。
time.h头文件提供了计时功能。
Random头文件提供了生成随机数据的功能。
Fstream,iostream,math.h,iomanip,cstring,stdlib.h等头文件保证了程序各个功能的正常运行。
2.4后端程序设计的图像化
2.4.1 UML类图
2.4.2 决策树构建的流程图
2.4.3利用决策树进行判断的流程图
2.5 程序运行结果
2.5.1信息增益
构建每个属性的信息增益,并写入gain.txt中
如图所示,我们可以得知第一轮划分中每种属性的信息增益:
Gain(D,色泽)=0.109 Gain(D,根蒂)=0.143
Gain(D,敲声)=0.141 Gain(D,纹理)=0.381
Gain(D,脐部)=0.289 Gain(D,触感)=0.006
Gain(D,密度)=0.262 Gain(D,含糖率)=0.349
2.5.2决策树
(注:应为密度≤0.3815\le 0.3815≤0.3815)
2.5.3随机数据
2.5.4判断数据并输出
第三章 总结与感悟
3.1程序设计中的不足之处
3.2对答辩中问题的解答
3.3感悟
附录:部分核心代码(完整代码请移步下载区)
treenode.h
#ifndef TREENODE_H
#define TREENODE_H#endif // TREENODE_H
#pragma once
#include<map>
#include<vector>
#include<string>
#include<iostream>
#include<fstream>
//#include"Tree.h"
using namespace std;
class TreeNode
{public:void generate_childNode(double data[18][10],int size);void splitdata();double log2x(double x);int findIndexOfAttribute();//计算信息增益最大值所对应的属性的位置double calcEntropy(int total_size,int true_size);double calc_gain(int kind);//参数为属性的序号值bool stop(double temp_data[18][10], int size);TreeNode(){Continuouspoint[2] = { 0 };breakingpoint = 0;entD = 0;indexOfAttribute = 0;isContinuous = 0;isGood = 0;isLeafNode = 0;rem_wat[18][10] = { 0 };sizeOfWatermelons = { 0 };attSize = 9;for (int i = 0; i < 9; i++)rem_att[i] = i;}void setNodeAttribute(int indexOf){this->indexOfAttribute = indexOf;}virtual ~TreeNode(){}TreeNode(double Data[18][10] , int n,int atts[10],int att_size){isLeafNode = false;isContinuous = false;breakingpoint = 0;isGood = false;indexOfAttribute = 0;sizeOfWatermelons= n;childTree.clear();attSize = att_size;for (int i = 0; i < att_size; i++)rem_att[i] = atts[i];for (int i = 0; i < sizeOfWatermelons; i++){for (int j = 0; j < 10; j++)rem_wat[i][j] = Data[i][j];}//计算未划分时的信息熵int RootT = 0; //未划分时的正例for (int i = 0; i < sizeOfWatermelons; i++)//统计正例个数if (rem_wat[i][9] == 1)RootT++;entD =calcEntropy(sizeOfWatermelons, RootT);}void deleteatt(int index){int i;for ( i = 0; i < attSize; i++){if (rem_att[i] == index)break;}for (int j = i; j < attSize - 1; j++)rem_att[j] = rem_att[j + 1];attSize--;}bool judgeByTree(double data[10]);void WriteGain();
private:static int attribute_size[10];double Continuouspoint[2];int indexOfAttribute;//当前节点的属性对应的位置double rem_wat[18][10];//剩下的没分完的西瓜的int sizeOfWatermelons;//剩下的西瓜的个数bool isLeafNode;//是否为叶子结点bool isContinuous;//是否为连续值double breakingpoint;//若是连续值,则连续值的间断点vector<TreeNode> childTree;//若不是叶结点,则子结点的地址bool isGood;//若是叶结点,则是否为好瓜double entD;//初始信息熵int rem_att[10];//剩余的属性int attSize;//余下属性的序号
};
treenode.cpp
#pragma once
#include "TreeNode.h"
#include<vector>
#include<Windows.h>
#include<iostream>
#include<fstream>
using namespace std;
int TreeNode::attribute_size[10] = { -1,3,3,3,3,3,2,-1,-1,2 };//每个属性分别对应着几种取值
bool TreeNode::stop(double temp_data[18][10], int size)
{//如果所有数据属于同一类,则停止分类int sumT = 0;for (int i = 0; i < size; i++)if (temp_data[i][9] == 1)sumT++;if (sumT == 0)return true;else if (sumT == size)return true;elsereturn false;
}
double TreeNode::log2x(double x)
{double den = log10(2), num = log10(x), sum;sum = num / den;return sum;
}
double TreeNode::calcEntropy(int total_size, int true_size)
{//所有实例数量、正例数量double entropy, Texample, Fexample, Tproportion, Fproportion;//信息熵;正例信息熵;反例信息熵;正例占比;反例占比Tproportion = true_size * 1.0 / total_size;Fproportion = (total_size - true_size) * 1.0 / total_size;if (Fproportion == 0){entropy = (-1) * Tproportion * log2x(Tproportion);return entropy;}else if (Tproportion == 0){entropy = (-1) * Fproportion * log2x(Fproportion);return entropy;}else{Texample = Tproportion * log2x(Tproportion);Fexample = Fproportion * log2x(Fproportion);entropy = -Texample - Fexample;return entropy;}
}
double TreeNode::calc_gain(int kind)
{this->isContinuous = false;this->breakingpoint = 0;if (kind == 1 || kind == 2 || kind == 3 || kind == 4 || kind == 5 || kind == 6)//具有离散值的属性{//遍历,用于计算该节点的类别个数int ZeroKind = 0, OneKind = 0, TwoKind = 0;for (int i = 0; i < sizeOfWatermelons; i++){if (rem_wat[i][kind] == 0)ZeroKind++;else if (rem_wat[i][kind] == 1)OneKind++;elseTwoKind++;}if (ZeroKind != 0 && OneKind != 0 && TwoKind != 0)//各属性中具有三个类别的信息增益{//统计各类别的总数、正例数量int OneTrue = 0, TwoTrue = 0, ZeroTrue = 0, OneSum = 0, TwoSum = 0, ZeroSum = 0;for (int i = 0; i < sizeOfWatermelons; i++){if (rem_wat[i][9] == 1)//好瓜{if (rem_wat[i][kind] == 0)ZeroTrue++;else if (rem_wat[i][kind] == 1)OneTrue++;elseTwoTrue++;}else//不是好瓜{if (rem_wat[i][kind] == 0)ZeroSum++;else if (rem_wat[i][kind] == 1)OneSum++;elseTwoSum++;}}ZeroSum += ZeroTrue; OneSum += OneTrue; TwoSum += TwoTrue;//三种信息熵double ZeroEntropy, OneEntropy, TwoEntropy;ZeroEntropy = calcEntropy(ZeroSum, ZeroTrue);OneEntropy = calcEntropy(OneSum, OneTrue);TwoEntropy = calcEntropy(TwoSum, TwoTrue);//信息增益double gain = entD - ZeroSum * ZeroEntropy * 1.0 / sizeOfWatermelons - OneSum * OneEntropy * 1.0 / sizeOfWatermelons - TwoSum * TwoEntropy * 1.0 / sizeOfWatermelons;return gain;}else if ((ZeroKind != 0 && OneKind != 0 && TwoKind == 0) || (ZeroKind == 0 && OneKind != 0 && TwoKind != 0) || (ZeroKind != 0 && OneKind == 0 && TwoKind != 0)) //各属性中具有两个类别的信息增益{//统计各类别的总数、正例数量int OneTrue = 0, ZeroTrue = 0, OneSum = 0, ZeroSum = 0;for (int i = 0; i < sizeOfWatermelons; i++){if (rem_wat[i][9] == 1)//好瓜{if (rem_wat[i][kind] == 0)ZeroTrue++;elseOneTrue++;}else//不是好瓜{if (rem_wat[i][kind] == 0)ZeroSum++;elseOneSum++;}}ZeroSum += ZeroTrue; OneSum += OneTrue;//两种信息熵double ZeroEntropy, OneEntropy;ZeroEntropy = calcEntropy(ZeroSum, ZeroTrue);OneEntropy = calcEntropy(OneSum, OneTrue);//信息增益double gain = entD - ZeroSum * ZeroEntropy * 1.0 / sizeOfWatermelons - OneSum * OneEntropy * 1.0 / sizeOfWatermelons;return gain;}else//属性里只有一个类别,信息熵是0;信息增益max=entD但不可以按照该类别划分,按0处理{return entD;}}else //连续值属性{//arrange存储连续属性数据,cQuality储存对应的好瓜判断,double* arrange = new double[sizeOfWatermelons];double* cQuality = new double[sizeOfWatermelons];for (int i = 0; i < sizeOfWatermelons; i++){arrange[i] = rem_wat[i][kind];cQuality[i] = rem_wat[i][9];}//从小到大排序double tempt, t;for (int m = 0; m < sizeOfWatermelons - 1; m++){int min = m;for (int i = m + 1; i < sizeOfWatermelons; i++)if (arrange[i] < arrange[min])min = i;//交换当前点和最小值点tempt = arrange[min];arrange[min] = arrange[m];arrange[m] = tempt;//交换当前点的好坏瓜和最小值点的好坏瓜t = cQuality[min];cQuality[min] = cQuality[m];cQuality[m] = t;}//按照算法取端点值存储到point数组,二分法double* point = new double[sizeOfWatermelons - 1];for (int i = 0; i < sizeOfWatermelons - 1; i++)point[i] = (arrange[i] + arrange[i + 1]) / 2;//计算不同端点处的信息增益并储存到PointGain数组中int i;//端点下标double* PointGain = new double[sizeOfWatermelons - 1];double BeforeEntropy, AfterEntropy;//以端点为界限分两组,并统计两组的正例for (i = 0; i < sizeOfWatermelons - 1; i++){int BeforeTrue = 0, AfterTrue = 0;for (int m = 0; m < i + 1; m++)if (cQuality[m] == 1)BeforeTrue++;for (int m = i + 1; m < sizeOfWatermelons; m++)if (cQuality[m] == 1)AfterTrue++;//两种信息熵,记录端点左边的和端点右边的BeforeEntropy = calcEntropy(i + 1, BeforeTrue);AfterEntropy = calcEntropy(sizeOfWatermelons - 1 - i, AfterTrue);//信息增益PointGain[i] = entD - (i + 1) * BeforeEntropy * 1.0 / sizeOfWatermelons - (sizeOfWatermelons - 1 - i) * AfterEntropy * 1.0 / sizeOfWatermelons;}int max = 0;for (int i = 1; i < sizeOfWatermelons - 1; i++)if (PointGain[i] > PointGain[max])max = i;this->Continuouspoint[kind-7] = point[max];
// cout << "continue=" << Continuouspoint[kind - 7]<<endl;this->breakingpoint = this->Continuouspoint[kind - 7];double gain = PointGain[max];delete[]PointGain;delete[]point;delete[]arrange;delete[]cQuality;return gain;}
}
void TreeNode::generate_childNode(double data[18][10],int size)
{if ((this->stop(data, size)) || (size == 0) || this->isLeafNode){this->isLeafNode = true;this->isGood = bool(data[0][9]);
// cout <<bool( isGood);return;}else{//从A中选择最优划分属性a*this->isLeafNode = false;this->indexOfAttribute = 0;double maxgain = 0;for (int i = 0; i < attSize; i++){if (this->calc_gain(rem_att[i]) > maxgain){indexOfAttribute = rem_att[i];maxgain = this->calc_gain(rem_att[i]);}}
// cout << indexOfAttribute;this->deleteatt(indexOfAttribute);// cout << endl;//生成分支if ((indexOfAttribute >= 1) && (indexOfAttribute <= 6)){isContinuous = 0;double temp_data[18][10];int sizeOfTemp_data = 0;for (int i = 0; i < attribute_size[indexOfAttribute]; i++){sizeOfTemp_data = 0;for (int j = 0; j < sizeOfWatermelons; j++){if (data[j][indexOfAttribute] == i)//复制西瓜,归类{for (int k = 0; k < 10; k++)temp_data[sizeOfTemp_data][k] = data[j][k];sizeOfTemp_data++;}}TreeNode tempTree(temp_data, sizeOfTemp_data,rem_att,attSize);if (sizeOfTemp_data == 0){tempTree.isLeafNode = true;tempTree.isGood = bool(data[0][10]);return;}tempTree.generate_childNode(temp_data,sizeOfTemp_data);childTree.push_back(tempTree);}return;}if (indexOfAttribute == 7 || indexOfAttribute == 8){isContinuous = 1;double temp_data1[18][10];double temp_data2[18][10];int sizeOfTemp_data = 0;breakingpoint = Continuouspoint[indexOfAttribute - 7];
// cout <<"breakingpoint="<< this->breakingpoint<<endl;for (int i = 0; i < sizeOfWatermelons; i++){if (data[i][indexOfAttribute] <= breakingpoint){for (int k = 0; k < 10; k++)temp_data1[sizeOfTemp_data][k] = data[i][k];sizeOfTemp_data++;}}TreeNode tempTree1(temp_data1, sizeOfTemp_data, rem_att, attSize);if (sizeOfTemp_data == 0){tempTree1.isLeafNode = true;tempTree1.isGood = bool(data[0][10]);return;}tempTree1.generate_childNode(temp_data1, sizeOfTemp_data);childTree.push_back(tempTree1);for (int i = 0; i < sizeOfWatermelons; i++)if (data[i][indexOfAttribute] > breakingpoint){for (int k = 0; k < 10; k++)temp_data2[sizeOfTemp_data][k] = false;sizeOfTemp_data++;}TreeNode tempTree2(temp_data2, sizeOfTemp_data, rem_att, attSize);if (sizeOfTemp_data == 0){tempTree2.isLeafNode = true;tempTree2.isGood = bool(data[0][10]);return;}tempTree2.generate_childNode(temp_data2, sizeOfTemp_data);childTree.push_back(tempTree2);}}return;}
bool TreeNode::judgeByTree(double data[10])
{if (isLeafNode)return isGood;//如果这个结点就是叶结点,则叶结点的isgood属性记录了这个西瓜是好是坏,返回else//这个结点不是叶结点{int i = 0;//访问哪个子结点int index;//第几项属性index = indexOfAttribute;//处理判断离散值,为0则进入第0个子结点,为1则进入第1个子结点……if ((index >= 1) && (index <= 6)){//attribute_size表示这种属性有几种取值for (i = 0; i < attribute_size[index]; i++){if (data[index] == i)//找到西瓜的第index项属性对应的值break;}}//判断连续值,小于间断点则进第0个子结点,大于间断点则进第1个子结点if ((index == 7) || (index == 8)){if (data[index] < breakingpoint)i = 0;elsei = 1;}//交给子决策树继续判断西瓜的好坏return childTree[i].judgeByTree(data);}}void TreeNode::WriteGain()
{//计算各属性的信息增益double gain[9];for (int i = 1; i < 9; i++)gain[i] = calc_gain(i);//写入文件fstream binfile;binfile.open("d:\\a\\Gain.txt", ios::binary | ios::out | ios::ate);if (!binfile){cerr << "Tdata.csv open or create error!" << endl;exit(1);}for (int i = 1; i < 9; i++)//输出binfile.write((char *)&gain[i], sizeof(double));for (int i = 1; i < 9; i++)//输出cout<<gain[i]<<" ";cout<<endl;binfile.close();
}
main.cpp
#include<iostream>
#include<fstream>
#include<math.h>
#include<iomanip>
#include<cstring>
#include<time.h>
#include<random>
#include<stdlib.h>#include "treenode.h"
double ListData[50000][10];//50000个瓜;9:0ID,1色泽,2根蒂,3敲声,4纹理,5脐部,6触感,7密度,8含糖率,9好瓜[1好瓜]int main(int argc, char *argv[])
{clock_t start, end;start = clock();ifstream bin;double data[17][10];bin.open("D:\\a\\watermelon.csv", ios::binary);if (!bin){cerr << "data.csv open or create error!" << endl;exit(1);}for (int i = 0; i < 17; i++)for (int k = 0; k < 10; k++)bin.read((char*)&data[i][k], sizeof(double));bin.close();/* cout << "原始数据:" << endl;for (int i = 0; i < 17; i++){for (int j = 0; j < 10; j++)cout << data[i][j] << " ";cout << endl;}*/for (int i = 0; i < 50000; i++){ListData[i][0] = i + 1;for (int k = 1; k < 6; k++)ListData[i][k] = rand() % 3;ListData[i][6] = rand() % 2;ListData[i][7] = rand() % 1001;ListData[i][7] /= 1000;ListData[i][8] = rand() % 1001;ListData[i][8] /= 1000;ListData[i][9] = rand() & 1;}fstream randomfile;randomfile.open("D:\\a\\data.csv", ios::binary | ios::out | ios::trunc);if (!randomfile){cerr << "Tdata.csv open or create error!" << endl;exit(1);}for (int i = 0; i <50000 ; i++)//输出for (int k = 0; k < 9; k++)randomfile.write((char*)&ListData[i][k], sizeof(double));randomfile.close();int rem_att[8] = { 1,2,3,4,5,6,7,8};//开局剩下8个属性没分,从色泽到含糖率TreeNode myTree(data, 17,rem_att,8);myTree.WriteGain();// system("pause");myTree.generate_childNode(data, 17);for (int i = 0; i < 50000; i++)if (myTree.judgeByTree(ListData[i]))ListData[i][9] = 1;elseListData[i][9] = 0;/* cout << "测试结果:"<<endl;*/fstream binfile;binfile.open("D:\\a\\result.csv", ios::out | ios::trunc);if (!binfile){cerr << "Tdata.csv open or create error!" << endl;exit(1);}for (int i = 0; i <50000 ; i++)//输出for (int k = 0; k < 10; k++)binfile.write((char*)&ListData[i][k], sizeof(double));binfile.close();for (int i = 0; i < 50000; i++){for (int j = 0; j < 10; j++)cout << ListData[i][j] << " ";cout << endl;}for(i=0;i<50000;i++) judgebytree(data[i]);/* 创建表格视图 *//* 显示 */end = clock();cout << "time = " << double(end - start) / CLOCKS_PER_SEC << "s" << endl;}
面向对象程序设计课程设计:利用决策树方法判定西瓜质量相关推荐
- C++《面向对象程序设计课程设计》
C++<面向对象程序设计课程设计> <面向对象程序设计课程设计>课程说明 适用专业:计算机科学与技术 课程周数:5周 一.根据计算机科学与技术专业人才培养方案制订. (一)课程 ...
- 武汉工程大学信息与计算科学专业面向对象程序设计课程设计题
课程设计内容(从以下任务中任选一个) 1 售票处的服务系统 设计民航售票处的计算机系统可以为客户提供以下各项服务: (1)查询航线:根据旅客提出的终点站名输出以下信息:航班号.飞机号.星期几飞行.最 ...
- c++面向对象程序设计------课程设计
课程设计要求 本次考试可以选择使用的理论知识点罗列如下: 常量.new/delete.引用.构造函数.析构函数.拷贝构造.静态成员.友元.组合类.重载函数.虚函数.运算符重载.模板.错误异常处理.格式 ...
- 面向对象程序设计课程设计——MFC实现同学通讯录管理系统
先上效果图; 1.设计目的 同学通讯录管理程序是为了更好地管理学生信息而开发的数据管理软件.如今,同学与同学.老师与同学联系都是通过电话联系.但是,通常这些数据与其他人的信息混合在一起,同学信息并不方 ...
- 猜数字java程序设计分析_JAVA程序设计课程设计-猜数字游戏设计
JAVA程序设计课程设计-猜数字游戏设计 课 程 设 计 报 告课程设计名称 Java 程序设计 专 业 计算机科学与技术 班 级 2 班 学 号 08030212 姓 名 指导教师 成 绩 2011 ...
- C语言程序设计课程设计题目[2023-02-11]
C语言程序设计课程设计题目[2023-02-11] C语言程序设计课程设计题目 选题说明: 1.以下题目有部分可能功能相近,请同学们可从不同的角度来分析实现. 2.题目描述和要求仅供参考,同学们可以查 ...
- 用MATLAB编程正弦稳态相量图,matlab课程设计--利用MATLAB对线性电路正弦稳态特性分析...
matlab课程设计--利用MATLAB对线性电路正弦稳态特性分析 课程设计任务书 学生姓名: 专业班级: 指导教师: 刘 新 华 工作单位:信息工程学院 题 目: 利用MATLAB对线性电路正弦稳态 ...
- C语言程序项目计划书,(C语言程序设计课程设计计划书.doc
(C语言程序设计课程设计计划书 C语言程序设计课程设计计划书 一.目的 1.熟悉并掌握C语言程序设计的基本方法与技能. 2.掌握利用递归进行程序设计的方法. 3.掌握对随机事件进行全程模拟的基本方法. ...
- 程序设计课程设计报告(学生成绩管理系统)
程序设计课程设计报告 课程设计题目及内容 题目:学生成绩管理系统 设计要求及提示如下: (1).设计一个学生类Student,包括数据成员:姓名.学号.二门课程(面向对象程序设计.高等数学)的成绩. ...
最新文章
- 对比es1.x和es2.0纹理加载方法
- 3.程序的局部性原理
- 国外机房供电模式不如国内的?对比一下就知道了
- OpenCV学习笔记二
- 句子中单词首字母大写转换
- 简易中控紫猫插件版(3)压缩包使用说明
- 在mojoportal项目中发邮件使用的是dotnetopenmail
- 【操作系统复习】中断和异常
- java中异常+连接重置_java.net.SocketException:连接重置
- TensorFlow 教程 --教程--2.9曼德布洛特(Mandelbrot)集合
- C#3.0中的新特性
- Git学习系列(七)Bug和Feature分支管理详解
- scrapy爬虫返回302,301,解决方法
- 【优化选址】基于matlab穷举法求解小区基站选址优化问题【含Matlab源码 439期】
- linux gvim字体大小配置,Gvim 字体大小设置
- 使用计算机编辑文档的同时 还可播放mp3,win7系统电脑怎么使用Windows Movie Maker剪辑音频文件...
- python 类的执行中保部存值_python 生成有效的四要素
- 【STM32学习】(30)STM32实现18B20温度采集(标准库和HAL库实现)
- vs2017\vs2019 VGG19处理cifar-10数据集的TensorFlow实现
- 三栏布局的七种实现方式
热门文章
- InvalidDefinitionException: No serializer found for class... 因为没有给对象写get、set方法
- python夯实基础日记-for循环、优化技巧、函数
- 电子计算机与多媒体课件背景,《多媒体CAI课件制作》教学课件(全套).ppt
- 语义分析语义关联挖掘
- 麦克斯韦方程的积分形式及应用、麦克斯韦方程组的微分形式及应用
- 我们可以从挑战者灾难中学到什么关于网络安全的知识?一切。
- 8赛道,64匹马找最快的8匹马
- 中国人工智能有多厉害,未来机器人都能做手术?老外表示不可思议
- openzipkin/brave初步了解
- (HDOJ)Vowel Counting-Java实现