决策树模型与学习
1. 决策树模型
决策树定义:分类决策树是一种描述对实例进行分类的树型结构。决策树由结点和有向边组成。结点有两种类型:内部结点和叶结点。内部结点表示一个特征或属性,叶子结点表示一个类。
用决策树分类,从根结点开始,对实例的某一特征进行测试,根据测试结果,将实例分配到其他子结点;这时,每一个子结点对应着该特征的一个取值,如此递归地对实例进行分配,直至达到叶结点。最后将实例分到叶结点的类中。
如图所示,是一个决策树的示意图:
1.2决策树与if-then规则
可以将决策树看成是if-then规则的集合.将决策树转换成if-then规则的过程是这样的:由决策树的根结点到叶结点的每一条路径构建一条规则;路径上的内部结点的特征对应着规则的条件,而叶结点的类对应着规则的结论。决策树的路径或者其对应的if-then规则集合具有一个重要的性质:互斥并且完备,即每个实例都被一条路径或者一条规则所覆盖,而且只被一条路径或者一条规则所覆盖,这里所谓覆盖是指实例的特征与路径上的特征一致或实例满足规则的条件。
决策树与条件概率分布
决策树还表示给定特征条件下类的条件概率分布,这一条件概率分布定义在特征空间的一个划分上。将特征空间划分为互不相交的单元或者区域,并在每个单元定义一个类的概率分布就构成了一个条件概率分布。决策树的一条路径对应于划分中的一个单元。决策树所表示的条件概率分布由各个单元给定条件下类的条件概率分布组成。假设X为表示特征的随机变量,Y为表示类的随机变量,那么这个条件概率分布可以表示为。X取值于给定划分下单元的集合,Y取值于类的集合。各叶结点上的条件概率往往偏向某一个类,即属于某一类的概率较大。决策树分类时将该结点的实例强行分到条件概率比较大的那一类去。
1.3决策树学习
假设给定数据训练数据集
其中,为输入实例(特征向量),n为特征个数,
为类标记,
,N为样本容量。学习的目标是根据给定的训练数据集建一个决策树模型,使其能够对实例进行正确分类。
决策树本质上是从训练数据集中归纳出分类规则,能对训练数据进行正确分类的决策树,它们可能有多个,可能一个也没有。我们需要的是一个与训练数据矛盾较小的决策树,同时具有很好的泛化能力。从另一角度来看,决策树学习是由训练数据集估计条件概率模型。我们选择的条件概率模型应该不仅对训练数据有很好的拟合效果,而且对未知数据有很好的预测能力。
决策树学习用损失函数表示目标,决策树的损失函数通常是正则化的极大似然函数,决策树学习的策略是以损失函数为目标函数的最小化。当损失函数确定后,学习问题就变为了损失函数的条件下选择最优决策树的问题。在所有可能的决策树中选取最优决策树是NP完全问题(NP中的某些问题的复杂性与整个类的复杂性相关联.这些问题中任何一个如果存在多项式时间的算法,那么所有NP问题都是多项式时间可解的.这些问题被称为NP-完全问题),所以现实中决策树学习算法通常采用启发式算法,近似求解这一最优化问题,这样获得的决策树是次最优的。
决策树学习的算法通常的是一种递归地选择最优特征,并根据该特征对训练数据进行分割,使得对各个子数据集有一个最好的分类过程。这一过程对应着特征空间的划分,也就是决策树的构建。如图所示:
1.4 决策树特点介绍
优点:计算复杂度不高,输出结果易于理解,对中间值的缺失不敏感,可以理解不相关特征数据。
缺点:可能会产生过度匹配问题
适用范围:数值型和标称型
问题1:评估每个特征,将原始数据集划分为几个数据子集。
创建分支的伪代码函数createBranch()如下所示:
检测数据集中的每个子项是否属于同一分类:
if so return 类标签
else
寻找划分数据集的最好特征
划分数据集
创建分支节点
for 每个划分的子集
调用函数createBranch并增加返回结果到分支节点中
return 分支节点
决策树学习算法包含特征选择,决策树的生成和决策树的剪枝过程。由于决策树表示为一个条件概率分布,所以深浅不同的决策树对应着不同复杂程度的概率模型。决策树的生成对应于模型的局部选择,决策树的剪枝对应于模型的全局选择。决策树的生成考虑局部最优;相对地,决策树的剪枝则考虑全局最优。
2. 特征选择
特征选择在于选取那些对训练数据具有分类能力的特征,这样可以提高决策树的学习的效率。如果利用一个特征的进行分类的结果与随机分类的结果没有很大差别,则这个特征是没有分类能力的。经验上扔掉这样的特征对决策树学习的精度的影响不大。通常特征选择的准则是信息增益或者信息增益比。
例1.表1是一个由15个样本组成的贷款申请训练数据。数据包括贷款申请人的4个特征(属性):第一个特征年龄,有三个可能值:青年、中年、老年;第二个特征是有工作,有2个可能值:是、否;第三个特征是有自己的房子,有2个可能值:是、否;第四个特征是信贷情况,有3个可能值:非常好、好,一般。表的最后一列是类别,是否同意贷款,取2个值:是,否。
表1 贷款申请样本数据表
ID | 年龄 | 有工作 | 有自己的房子 | 信贷情况 | 类别 |
1 | 青年 | 否 | 否 | 一般 | 否 |
2 | 青年 | 否 | 否 | 好 | 否 |
3 | 青年 | 是 | 否 | 好 | 是 |
4 | 青年 | 是 | 是 | 一般 | 是 |
5 | 青年 | 否 | 否 | 一般 | 否 |
6 | 中年 | 否 | 否 | 一般 | 否 |
7 | 中年 | 否 | 否 | 好 | 否 |
8 | 中年 | 是 | 是 | 好 | 是 |
9 | 中年 | 否 | 是 | 非常好 | 是 |
10 | 中年 | 否 | 是 | 非常好 | 是 |
11 | 老年 | 否 | 是 | 非常好 | 是 |
12 | 老年 | 否 | 是 | 好 | 是 |
13 | 老年 | 是 | 否 | 好 | 是 |
14 | 老年 | 是 | 否 | 非常好 | 是 |
15 | 老年 | 否 | 否 | 一般 | 否 |
图(a)和图(b)表示从数据学习到的两个可能的决策树,图(a)表示年龄特征,有三个子结点,图(b)表示有工作的特征,有2个子结点。问题是:我们选择哪个特征更加好些。也就是说我们按照什么样的准则来分割子集,使得各个子集在当前条件下有最好的分类,那么就更应该选择这个特征。信息增益就是一个非常好的准则。
2.1 信息增益
在信息论与概率统计中,熵是表示随机变量不确定性的度量。设是一个取有限个值的离散随机变量,其概率分布为
则随机变量的熵的定义为
在式(2.1)中,若,则定义
,通常,式(2.1)中的对数以2为底或以e为底(自然对数),这时熵的单位分别称作比特(bit)或者纳特(nat)。有定义可知,熵只依赖于
的分布,而与
的取值无关,所以也可将
的熵记作
,即
熵越大,随机变量的不确定性就越大。从定义可验证
当随机变量只取两个值,例如1,0时,即的分布为
熵为
此时,熵随概率
变化的曲线如图所示(单位为比特,横坐标表示p,纵坐标表示
)
当p=0或者p=1时,,随机变量完全没有不确定性,当
时,
,熵取最大,此时随机变量的不确定性最大。
设有随机变量,其联合概率分布为
条件熵表示在已知随机变量
的条件下随机变量
的不确定性的,随机变量
给定的条件下随机变量Y的条件熵
,定义为
给定条件下
的条件概率分布的熵对
的数学期望
这里,
当熵和条件熵中的概率由数据统计(特别是极大似然估计)得到时,所对应的熵与条件熵分别称为经验熵和经验条件熵,此时,如果有0概率,令
信息增益表示得知特征的信息而使得类
的信息的不确定性减少的程度(即分类前的信息熵减去分类后的信息熵)。
定义(信息增益)特征A对训练数据集D的信息增益,定义为集合D的经验熵
与特征A给定条件下D的经验条件熵
之差,即
一般地,熵与条件熵
之差称为互信息,即
互信息。决策树学习中的信息增益等价于训练数据集中类与特征的互信息。
给定训练数据集D和特征A,信息增益表示由于特征A而使得对数据集D的分类的不确定性减少的程度。显然,对于数据集D而言,信息增益依赖于特征,不同的特征往往具有不同的信息增益,信息增益大的特征具有更加强的分类能力。
根据信息增益准则的特征选择方法是:对于训练数据集(或子集)D,计算其每个特征的信息增益,并比较它们的大小,选择信息增益最大的特征。
设训练数据集为D,表示其样本容量,即样本个数,设有
个类的
,
为属于类
的样本个数,
。设特征A有n个不同的取值
,根据特征A的取值将D划分为n个子集
为
的样本个数,
。记子集
中属于类
的样本的集合为
,即
为
的样本个数。于是信息增益的算法如下:
信息增益算法
输入:训练数据集D和特征A;
输出:特征A对训练数据集D的信息增益。
(1)计算数据集D的经验熵
(2)计算特征A对数据集D的经验条件熵
(3)计算信息增益
例1. 对于表1 所给的训练数据集,根据信息增益准则选择最优特征。
解答过程如下:
2.2 信息增益比
使用信息增益来作为划分训练数据集的特征,存在偏向于选择取值较多的特征的问题。使用信息增益比可以对这一问题进行校正,这是特征选择的另一准则。
信息增益比:信息增益与训练数据集D关于特征A的熵
之比,即
其中,n是特征A取值的个数。
3. 决策树的生成
ID3算法:从根结点开始,对结点计算所有可能的特征的信息增益,选择信息增益最大的特征作为结点的特征,由该特征的不同取值建立子结点,再对子结点递归地调用以上方法,构建决策树;直到所有的特征的信息增益均小或者没有特征可以选择为止。最后得到一个决策树。ID3相当于用极大似然法进行概率模型的选择。
算法过程:
输入:训练数据集D,特征集A,阈值;
输出:决策树T
(1)若D中所有实例属于同一类,则T为单结点树,并将类
作为该结点的类标记,返回T;
(2)若A=,则T为单结点树,并将D中实例数最大的类
作为该结点的类标记,返回T;
(3)否则,按信息增益的算法计算A中各个特征的信息增益,选择信息增益最大的特征;
(4)如果的信息增益小于阈值
,则置T为单结点树,并将D中实例数最大的类
作为该结点的类标记,返回T
(5)否则,对的每一可能值
,依
将D分割为若干非空子集
,将
中实例数最大的类作为标记,构建子结点,由结点及其子结点构成树T,返回T;
(6)对第i个子结点,以为训练集,以
为特征集,递归地调用步骤(1)~(5)得到子树
,返回
。
例2:对于例1中的训练数据集,用ID3算法构建决策树
解:结合例1的计算结果,由于特征(有自己的房子)的信息增益值最大,所以选择此特征作为根结点的特征,它将数据集划分为两个子集(特征取值为“是”)和
(特征取值为“否”)。而
中只有同一类的样本点,所以它成为一个叶结点,结点的类标记为“是”。
对则需要从特征(年龄)、(有工作)和(信贷情况)中选择新的特征。计算各个特征的信息增益:
年龄:
有工作:
信贷情况:
选择信息增益最大的特征(有工作)作为结点的特征,由于特征(有工作)有两个可能的取值,从这一结点引出两个子结点:一个对应“是”(有工作)的子结点,包含3个样本,它们属于同一类,所以这是一个叶结点,类标记为“是”;另一个是对应“否”(无工作)的子结点,包含6个样本,它们也属于同一类,所以这也是一个叶结点,类标记为“否”。
这样就生成了一个决策树(包含两个特征):
ID3算法只有树的生成,所以该算法生成的树容易过拟合
2.3 C4.5的生成算法
C4.5是ID3算法的升级版,C4.5算法在生成过程中,采用了信息增益比来选择特征。
算法过程:
输入:训练数据集D,特征集A,阈值;
输出:决策树T。
(1)如果D中所有实例属于同一类,则T为单结点树,并将类
作为该结点的类标记,返回T;
(2)若A=,则T为单结点树,并将D中实例数最大的类
作为该结点的类标记,返回T;
(3)否则,按信息增益比公式计算A中各个特征的信息增益比,选择信息增益比最大的特征;
(4)如果的信息增益比小于阈值
,则置T为单结点树,并将D中实例数最大的类
作为该结点的类标记,返回T
(5)否则,对的每一可能值
,依
将D分割为若干非空子集
,将
中实例数最大的类作为标记,构建子结点,由结点及其子结点构成树T,返回T;
(6)对第i个子结点,以为训练集,以
为特征集,递归地调用步骤(1)~(5)得到子树
,返回
。
2.4 决策树的剪枝
决策树对于训练数据集的分类很准确,但是对于未知数据的预测效果不是很好,容易出现过拟合现象。这是因为决策树过于考虑如何正确分类训练数据集,构造了一个相对复杂的决策树。所以需要对决策树进行简化处理(这个过程就是剪枝)。
决策树的剪枝常通过极小化决策树整体的损失函数或者是代价函数。设树T的叶结点个数为,t是树T的叶结点,该叶结点有
个样本点,其中k类的样本点有
个,
为叶结点t上面的经验熵,
为参数,则决策树学习的损失函数可以定义为
其中经验熵为
在损失函数中,将式(2.4.1)右端的第1项记作
这时有
(关于式(2.4.1)和式(2.4.2)的理解,我们可以参考链接:https://blog.youkuaiyun.com/wjc1182511338/article/details/76793598)
式(2.4.4)中,表示模型对训练数据的预测误差,即模型与训练数据的拟合程度,
表示模型复杂度,参数
控制两者之间的影响。较大的
促使选择较简单的模型(树),较小的
促使选择较复杂的模型(树)。
意味着中考虑模型与训练数据的拟合程度,不考虑模型的复杂度。
剪枝,就是当值确定时,选择损失函数最小的模型,即损失函数最小的子树。当
值确定时,子树越大,往往与训练数据的拟合越好,但是模型的复杂度就越高;相反,子树越小,模型的复杂度就越低,但是往往与训练数据的拟合不好,损失函数正好表示了对两者的平衡。
可以看出,决策树生成只考虑了通过提高信息增益(或者信息增益比)对训练数据进行更好的拟合。而决策树剪枝学习整体的模型。
式(2.4.1)和式(2.4.4)定义的损失函数的极小化等价于正则化的极大似然估计。所以,利用损失函数最小原则进行剪枝就是用正则化的极大似然估计进行模型选择。
(树的剪枝算法)
输入:生成算法产生的整个树T,参数;
输出:修剪后的子树。
(1)计算每个结点的经验熵;
(2)递归地从树的叶结点向上回缩;
设一组叶结点回缩到其父结点之前与之后的整体树分别为与
,其对应的损失函数值分别是
与
,如果
则进行剪枝,即将父结点变为新的叶结点。
(3)返回(2),直至不能继续为止,得到损失函数最小的子树
只需要考虑两个树的损失函数的差,其计算可以在局部进行。
2.5 CART算法
分类和回归树(classification and regression tree,CART)模型是Breiman等人于1984年提出,是应用于广泛的决策树学习方法。CART同样有特征选择、树的生成及剪枝组成,既可以用于分类也可以用于回归。
CART算法由以下两步组成:
(1)决策树生成:基于训练数据集生成决策树,生成的决策树要尽量大;
(2)决策树剪枝:用验证数据集对已生成的树进行剪枝并选择最优子树,这时用损失函数最小作为剪枝的标准。
2.5.1 CART生成
决策树的生成就是递归的构建二叉决策树的过程,对于回归树用平均误差最小化准则,对分类树用基尼系数最小化准则,进行特征选择,生成二叉树。
1.回归树的生成
假设X与Y分别为输入和输出变量,并且Y为连续变量,给定训练数据集
考虑如何生成回归树。
一个回归树对应着输入空间的一个划分以及划分的单元上面的输入值。假设已将输入空间划分为M个单元,并且在每个单元
上的有一个固定的输出值
,于是回归树模型可表示为
当输入空间的划分确定时,可以用平方误差 来表示回归树对训练数据的预测误差,用平方误差最小的准则求解每个单元上的最优输出值。可知,单元
上的
的最优值
是
上的所有输入实例
对应的输出
的均值,即
问题是怎样对输入空间进行划分。这里采用启发式的方法,选择第个变量
和它取的值s,作为切分变量和切分点并定义两个区域:
和
然后寻找最优切分变量和最优切分点s。具体地,求解。
对固定输入向量 可以找到最优切分点s。
和
变量所有的输入变量,找到最优的切分变量 ,构成一个对
。依此将输入空间划分为两个区域。接着,对每个区域重复上述划分过程,直到满足停止条件为止。对每个区域重复上述划分过程,直到满足停止条件为止。这样就生成一棵回归树。这样的回归树通常称为最小二乘回归树,现将算法叙述如下:
(最小二乘回归树生成算法)
输入:训练数据集D;
输出:回归树.
在训练数据集所在的输入空间中,递归地将每个区域划分为两个子区域并决定每个子区域上面的输出值,构建二叉决策树:
(1)选择最优切分变量与切分点s,求解
遍历变量,对固定的切分变量
扫描切分点s,选择使式(2.5.4)达到最小值的对
;
(2)用选定的对划分区域并决定相应的输出值:
,
(3)继续对两个子区域调用步骤(1)和(2),直至满足停止条件。
(4)将输入空间划分为M个区域,生成决策树:
2. 分类树的生成
分类树用基尼系数选择最优特征,同时决定该特征的最优二值切分点。
基尼系数:分类问题中,假设有K个类,样本点属于第k类的概率为,则概率分布的基尼指数定义为
对于二分类问题,若样本点属于第1个类的概率是p,则概率分布的基尼指数为
对于给定的样本集合D,其基尼系数为
这里,是D中属于第k类的样本子集,K是类的个数。
如果样本集合D根据特征A是否取某一可能值a被分割成和
两部分,即
则在特征A的条件下,集合D的基尼指数定义为
基尼指数表示集合D的不确定性,基尼指数
表示经
分割后集合D的不确定性,基尼指数越大,样本集合的不确定性也就越大,这一点与熵类似。
下面来比较二分类问题中基尼指数、熵(单位比特)之半
和分类误差率的关系。横坐标表示概率P,纵坐标表示损失。
从图中可以看出,基尼指数和熵之半的曲线很接近,都可以近似地代表分类误差率。
(CART生成算法)
输入:训练数据集D,停止计算的条件;
输出:CART决策树。
根据训练数据集,从根结点开始,递归地对每个结点进行以下操作,构建二叉决策树:
(1)设结点的训练数据集为D,计算现有特征对该数据集的基尼指数。此时,对每一个特征A,对其可能取的每个值a,根据样本点对A=a的测试为“是”或“否”将D分割成
和
两部分,利用式(2.5.10)计算
时的基尼指数;
(2)在所有可能的特征A以及它们所有可能的切分点a中,选择基尼指数最小的特征及其对应的切分点作为最优特征与最优切分点。依最优特征与最优切分点,从现结点生成两个子结点,将训练数据集依特征分配到两个子结点中去;
(3)对两个子结点递归地调用(1)和(2),直至满足停止条件;
(4)生成CART决策树
(注意:算法停止计算的条件是结点中的样本个数小于预定阈值,或者样本集的基尼指数小于预定阈值(样本基本属于同一类),或者没有更多特征)。
例3. 根据表1 所给训练数据集,应用CART算法生成决策树。
解答过程如下:
2.6 CART剪枝
CART剪枝算法从“完全生长”的决策树的底端剪去一些子树,使决策树变小(模型变简单),从而能够对未知数据有更准确的预测。CART剪枝算法由两步组成:
①首先从生成算法产生的决策树 底端开始不断剪枝,直到
的根结点,形成一个子树序列
;
②然后通过交叉验证法在独立的验证数据集上对子树序列进行测试,从中选择最优子树。
1. 剪枝,形成子树序列
在剪枝过程中,计算子树的损失函数:
其中, 为任意子树,
为对训练数据的预测误差(如基尼指数),
为子树的叶结点个数,
为参数,
为参数是
时的整体损失。参数
权衡的训练数据的拟合程度和模型的复杂度。
对固定的,一定存在使损失函数
最小的子树,将其表示为
,
在损失函数
最小的意义下是最优的。容易验证这样的最优子树是唯一的。当
大的时候,最优子树
偏小;当
小的时候,最优子树
偏大。极端情况,当
时,整体树是最优的。当
时,根结点组成的单结点树是最优的。
Breiman等人证明:可以用递归的方法对树进行剪枝。将从小增加,
,产生一系列的区间
;剪枝得到的子树序列对应着区间
的最优子树序列
,序列中的子树是嵌套的。
具体地,从整体树 开始剪枝,对
的任意的内部结点t,以t为单结点树的损失函数是
以t为根结点的子树 的损失函数是
当及
充分小时,有不等式
当增加时,在某一
有
当再增大时,不等式(2.6.4)反向。只要
,
与t 有相同的损失函数值,而t的结点少,因此t比
有相同的损失函数值,而t的结点少,因此t比
更加可取,对
进行剪枝。
为此,对中每一内部结点t,计算
它表示剪枝后整体损失函数减少的程度,在中剪去
最小的
,将得到的子树作为
,同时将最小的
设为
。
为区间
的最优子树。
如此剪枝下去,直至得到根结点。在这个过程中,不断地增加 的值,产生新的区间。
2. 在剪枝得到的子树序列 中通过交叉验证选取最优子树
具体地,利用独立的验证数据集,测试子树序列 中各棵子树的平均误差或者基尼指数。平方误差或基尼指数,最小的决策树被认为是最优的决策树。在子树序列中,每棵子树
都对应一个参数
。所以,当最优子树
确定时,对应的
也确定了,即得到最优决策树
。
CART剪枝算法
输入:CART算法生成的决策树;
输出:最优决策树。
(1)设;
(2)设;
(3)自上而下地对各内部结点t计算 ,
以及
这里,表示以t为根的结点的子树,
是对训练数据的预测误差,
是
的叶结点个数。
(4)对的内部结点t 进行剪枝,并对叶结点t 以多数表决法决定其类,得到树T。
(5)设。
(6)如果不是由根结点及两个叶结点构成的树,则回到步骤(3);否则令
。
(7)采用交叉验证法在子树序列 中选取最优子树
。
代码(包括构建决策树,用决策树区分鱼的种类和使用决策树预测隐形眼镜类型等):
#!/usr/bin/env python3
#coding=utf-8
from math import log
import operator
import matplotlib.pyplot as plt
#计算给定数据集的信息熵
def calcEntroy(dataSet):
numEntroy=len(dataSet)
labelCounts={}
for featVec in dataSet:
currentLabel=featVec[-1]
if currentLabel not in labelCounts.keys(): #labelCounts.keys()表示返回labelCounts字典的键列表
labelCounts[currentLabel]=0 #将字典不存在的键添加到字典中并且给键值赋初值
labelCounts[currentLabel]+=1 #记录当前类别的出现的次数
Entroy=0.0
'''计算信息熵'''
for key in labelCounts:
prob=float(labelCounts[key])/numEntroy
Entroy-=prob*log(prob,2)
return Entroy
#按照给定特征划分数据集
def splitDataSet(dataSet,axis,value):
retDataSet=[]
for featVec in dataSet:
if featVec[axis]== value:
reducedFeatVec=featVec[:axis]
reducedFeatVec.extend(featVec[axis+1:])
retDataSet.append(reducedFeatVec)
return retDataSet
#选择最好的数据集划分方式
def chooseBestFeatureToSplit(dataSet):
numFeatures=len(dataSet[0])-1 #数据集最好一项是标签,而前len(dataSet[0])-1表示数据特征数
baseEntropy = calcEntroy(dataSet)
bestInfoGain=0.0; bestFeature=-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*calcEntroy(subDataSet)
infoGain=baseEntropy-newEntropy
if (infoGain>bestInfoGain):
bestInfoGain=infoGain
bestFeature=i
#返回最好特征划分的索引值
return bestFeature
#计算出现次数最多的分类名称
def majorityVCnt(classList):
classCount={}
for vote in classCount:
if vote not in classCount.keys():
classCount[vote]=0
classCount[vote]+=1
sortedClassCount=sorted(classCount.items(),key=operator.itemgetter(1),reverse=True)
return sortedClassCount[0][0]
#构建决策树函数
def createTree(dataSet,labels):
classList=[example[-1] for example in dataSet] #用索引的-1位置表示列表最后的位置
#如果第一个类别的数目=整个数据集的长度,即整个数据集只有一个类别,可以直接返回该类标签
#第一个停止条件
if classList.count(classList[0])==len(classList):
return classList[0]
#如果数据集只有一列,则无法简单的返回唯一的类标签,这里使用前面介绍的majorityCnt函数挑选出现次数最多的类别作为返回值
if len(dataSet[0])==1:
return majorityVCnt(classList)
#获取数据集中最好特征划分的索引
bestFeatSplitIndex=chooseBestFeatureToSplit(dataSet)
#获取最好特征所属类别标签
bestFeatLabel=labels[bestFeatSplitIndex]
#初始化myTree
myTree={bestFeatLabel:{}}
del(labels[bestFeatSplitIndex])
#获取数据集中最优的列
featValues=[example[bestFeatSplitIndex] for example in dataSet]
uniqueVals=set(featValues)
for value in uniqueVals:
#求出剩余的类标签
subLables=labels[:]
#遍历当前选择特征包含的所有属性值,递归调用createTree()函数
myTree[bestFeatLabel][value]=createTree(splitDataSet(dataSet,bestFeatSplitIndex,value),subLables)
return myTree
#使用文本注解绘制树节点
'''使用dict(a="a",b="b")返回字典形式的数据类型{"a":"a","b":"b"}'''
#定义文本框和箭头格式
decisionNode=dict(boxstyle="sawtooth",fc="0.8")
leafNode=dict(boxstyle="round4",fc="0.8")
arrow_args=dict(arrowstyle="<-")
'''画节点'''
def plotNode(nodeText,centerPt,parentPt,nodeType):
#annotate()图形增加注释
#nodeText为显示的文本,centerPt为箭头所在的点,parentPt为指向文本的初始点
createPlot.ax1.annotate(nodeText,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() #清楚所有轴,但是窗口打开,这样它可以被重复使用
#creatplot.ax1为全局变量
#frameon表示是否绘制坐标轴矩阵
createPlot.ax1=plt.subplot(111,frameon=False) #111表示1行一列,最后的1表示第一个图
plotNode('决策节点',(0.5,0.1),(0.1,0.5),decisionNode)
plotNode('叶节点',(0.8,0.1),(0.3,0.8),leafNode)
plt.show()
"""
#获取叶子节点的数目和树的深度
def getNumLeafs(myTree):
#初始化叶子节点的数目
numLeafs=0
#获取第一次划分数据集的类别标签
firstStr=list(myTree.keys())[0]
secondDict=myTree[firstStr]
for key in secondDict.keys():
#判断子节点的数据类型是否为字典类型,如果是使用getNumLeafs()进行递归调用
if type(secondDict[key]).__name__=='dict':
numLeafs+=getNumLeafs(secondDict[key])
else:
numLeafs+=1
return numLeafs
def getTreeDepth(myTree):
#类似于上面的getNumleafs函数
maxDepth=0
firstStr=list(myTree.keys())[0]
secondDict=myTree[firstStr]
for key in secondDict.keys():
if type(secondDict[key]).__name__=='dict':
thisDepth=1+getTreeDepth(secondDict[key])
else:
thisDepth=1
if thisDepth>maxDepth:
maxDepth=thisDepth
return maxDepth
'''在父子节点间填充文本信息'''
def plotMidText(cntrPt,parentPt,txtString):
xMid=(parentPt[0]-cntrPt[0])/2.0+cntrPt[0]
yMid=(parentPt[0]-cntrPt[0])/2.0+cntrPt[1]
createPlot.ax1.text(xMid,yMid,txtString)
#构建可视化树函数
def plotTree(myTree,parentPt,nodeTxt):
#获取树的节点数目
numLeafs=getNumLeafs(myTree)
#获取树的深度
depth=getTreeDepth(myTree)
#获取第一次划分数据集的列表
firstStr=list(myTree.keys())[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]
plotTree.yOff=plotTree.yOff-1.0/plotTree.totalD
for key in secondDict.keys():
if type(secondDict[key]).__name__=='dict':
plotTree(secondDict[key],cntrPt,str(key))
else:
plotTree.xOff=plotTree.xOff+1.0/plotTree.totalW
plotNode(secondDict[key],(plotTree.xOff,plotTree.yOff),cntrPt,leafNode)
plotMidText((plotTree.xOff,plotTree.yOff),cntrPt,str(key))
plotTree.yOff=plotTree.yOff+1.0/plotTree.totalD
'''创建图形'''
def createPlot(inTree):
fig=plt.figure(1,facecolor='white')
fig.clf()
axprops=dict(xticks=[],yticks=[])
createPlot.ax1=plt.subplot(111,frameon=False,**axprops)
#使用plotTree.totalW来存储树的宽度,plotTree.totalD存储树的高度
plotTree.totalW=float(getNumLeafs(inTree))
plotTree.totalD=float(getTreeDepth(inTree))
#初始化plotTree.xOff和plotTree.yOff
plotTree.xOff=-0.5/plotTree.totalW
plotTree.yOff=1.0
#设置根节点的坐标为(0.5,1.0)
plotTree(inTree,(0.5,1.0),'')
plt.show()
#使用决策树进行分类
def classify(inputTree,featLabels,testVec):
firstStr=list(inputTree.keys())[0]
secondDict=inputTree[firstStr]
#获取第一个匹配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 classLabel
#决策树的存储
def storeTree(inputTree,filename):
import pickle
fw=open(filename,'w')
#序列化对象,并将结果数据流写入到文件对象中,即将inputTree对象保存到fw中
pickle.dump(inputTree,fw)
fw.close()
def grabTree(filename):
import pickle
fr=open(filename)
#反序列化对象,将文件中的数据解析为一个Python对象。
return pickle.load(fr)
#使用决策树预测隐形眼镜类型
fr=open(r'F:\python\machinelearninginaction\Ch03\lenses.txt')
lenses=[inst.strip().split('\t') for inst in fr.readlines()]
lensesLabels=['age','prescript','astigmatic','tearRate']
lensesTree=createTree(lenses,lensesLabels)
print(lensesTree)
createPlot(lensesTree)