决策树

1、决策树的概念

决策树是什么?

决策树(decision tree)是一种基本的分类与回归方法,通俗的讲也就是一颗用于决策的树。

决策树长什么样呢?

举个通俗易懂的例子,如下图所示的流程图就是一个决策树。

这个决策树中,长方形代表判断模块(decision block),也就是用于条件判断的模块。
椭圆形代表终止模块(terminating block),表示已经得出结论,可以终止运行。
从判断模块引出的左右箭头称作为分支(branch),它可以达到另一个判断模块或者终止模块。

回到这个流程图,这就是一个假想的相亲对象分类系统。这个系统首先检测相亲对方是否有房。如果有房,则对于这个相亲对象可以考虑进一步接触。如果没有房,则观察相亲对象是否有上进心,如果没有,直接Say Goodbye,此时可以说:"你人很好,但是我们不合适。"如果,则可以把这个相亲对象列入候选名单,好听点叫候选名单,有点瑕疵地讲,那就是备胎

举个例子

如果有一个男人(没房,没有上进心),输入这个相亲系统,就会得到Say Goodbye;男人(有房,有上进心),输入这个相亲系统,就会得到值的认真考虑;男人(没房,有上进心),输入这个相亲系统,就会得到备胎

换一种说法

可以这样理解,分类决策树模型是一种描述对实例进行分类的树形结构。决策树由结点(node)有向边(directed edge)组成。结点有两种类型:内部结点(internal node)叶结点(leaf node)内部结点表示一个特征或属性叶结点表示一个

像上面例子中,内部结点也就是特征或属性为:相亲对象有房子有上进心叶结点也就是为:Say Goodbye值的认真考虑备胎有向边类似if-then规则,满足条件往右走,不满足往左边走。

决策树就是一颗在内部结点加上判断条件,在叶结点加上结论即的树。

2、决策树开发流程

使用决策树做预测需要以下过程:

收集数据:可以使用任何方法。
准备数据:收集完的数据,我们要进行整理,将这些所有收集的信息按照一定规则整理出来,并排版,方便我们进行后续处理。
分析数据:可以使用任何方法,决策树构造完成之后,我们可以检查决策树图形是否符合预期。
训练算法:这个过程也就是构造决策树,同样也可以说是决策树学习,就是构造一个决策树的数据结构。
测试算法:使用经验树计算错误率。当错误率达到了可接收范围,这个决策树就可以投放使用了。
使用算法:此步骤可以使用适用于任何监督学习算法,而使用决策树可以更好地理解数据的内在含义。

3、决策树的构建的准备工作

接下来说一下,如何构建决策树?

通常,这一过程可以概括为3个步骤:特征选择决策树的生成决策树的修剪

1、特征选择
为什么进行特征选择?

现实中有分类能力的特征往往在数据样本中占少数,不是所有的特征都可以用来进行分类或者回归。而特征选择在于选取对训练数据具有分类能力的特征。这样可以提高决策树学习的效率,如果利用一个特征进行分类的结果与随机分类的结果没有很大差别,则称这个特征是没有分类能力的。

特征选择就是决定用哪个特征来划分特征空间

那么如何进行特征选择呢?

这就要求确定选择特征的准则。直观上,如果一个特征具有更好的分类能力,或者说,按照这一特征将训练数据集分割成子集,使得各个子集在当前条件下有最好的分类,那么就更应该选择这个特征。信息增益就能够很好地表示这一直观的准则。

经验上扔掉对决策树学习的精度影响不大的特征。通常特征选择的标准是信息增益(information gain)或信息增益比,为了简单,本文使用信息增益作为选择特征的标准。

什么是信息增益?

在划分数据集之后信息发生的变化称为信息增益,知道如何计算信息增益,我们就可以计算每个特征值划分数据集获得的信息增益,获得信息增益最高的特征就是最好的选择。

(1)香农熵

在可以评测哪个数据划分方式是最好的数据划分之前,我们必须学习如何计算信息增益集合信息的度量方式称为香农熵或者简称为熵(entropy),这个名字来源于信息论之父克劳德·香农。

如果看不明白什么是信息增益和熵,请不要着急,因为他们自诞生的那一天起,就注定会令世人十分费解。克劳德·香农写完信息论之后,约翰·冯·诺依曼建议使用"熵"这个术语,因为大家都不知道它是什么意思。

熵定义为信息的期望值在信息论与概率统计中,熵是表示随机变量不确定性的度量。如果待分类的事物可能划分在多个分类之中,则符号xi的信息定义为 :
I ( x i ) = − log ⁡ 2 p ( x i ) I(x_{i}) = -\log_{2}p(x_{i}) I(xi​)=−log2​p(xi​)
其中 p ( x i ) p(x_{i}) p(xi​)是选择该分类的概率。有人可能会问,信息为啥这样定义啊?答曰:前辈得出的结论。这就跟1+1等于2一样,记住并且会用即可。上述式中的对数以2为底,也可以e为底(自然对数)。

通过上式,我们可以得到所有类别的信息。为了计算熵,我们需要计算所有类别所有可能值包含的信息期望值(数学期望),通过下面的公式得到:
H = − ∑ k = 1 n p ( x i ) log ⁡ 2 p ( x i ) H = -\sum_{k=1}^np(x_{i})\log_{2}p(x_{i}) H=−k=1∑n​p(xi​)log2​p(xi​)
期中n是分类的数目。熵越大,随机变量的不确定性就越大。

当熵中的概率由数据估计(特别是最大似然估计)得到时,所对应的熵称为经验熵(empirical entropy)

什么叫由数据估计?

比如有10个数据,一共有两个类别,A类和B类。其中有7个数据属于A类,则该A类的概率即为十分之七。其中有3个数据属于B类,则该B类的概率即为十分之三。浅显的解释就是,这概率是我们根据数据数出来的

在编写代码之前,我们先对数据集进行属性标注。

年龄:0代表青年,1代表中年,2代表老年;
有工作:0代表否,1代表是;
有自己的房子:0代表否,1代表是;
信贷情况:0代表一般,1代表好,2代表非常好;
类别(是否给贷款):no代表否,yes代表是。
确定这些之后,我们就可以创建数据集,代码编写如下:

from math import log"""
函数说明:创建测试数据集Parameters:无
Returns:dataSet - 数据集labels - 分类属性
"""
def createDataSet():dataSet = [[0, 0, 0, 0, 'no'],         #数据集[0, 0, 0, 1, 'no'],[0, 1, 0, 1, 'yes'],[0, 1, 1, 0, 'yes'],[0, 0, 0, 0, 'no'],[1, 0, 0, 0, 'no'],[1, 0, 0, 1, 'no'],[1, 1, 1, 1, 'yes'],[1, 0, 1, 2, 'yes'],[1, 0, 1, 2, 'yes'],[2, 0, 1, 2, 'yes'],[2, 0, 1, 1, 'yes'],[2, 1, 0, 1, 'yes'],[2, 1, 0, 2, 'yes'],[2, 0, 0, 0, 'no']]labels = ['不放贷', '放贷']             #分类属性return dataSet, labels                #返回数据集和分类属性

我们定义贷款申请样本数据表中的数据为训练数据集D,则训练数据集D的经验熵为H(D),|D|表示其样本容量,及样本个数。设有K个类Ck, = 1,2,3,…,K,|Ck|为属于类Ck的样本个数,因此经验熵公式就可以写为 :
H ( D ) = − ∑ k = 1 K ∣ C k ∣ ∣ D ∣ log ⁡ 2 ∣ C k ∣ ∣ D ∣ H(D) = -\sum_{k=1}^K\frac{|C_{k}|}{|D|}\log_{2}\frac{|C_{k}|}{|D|} H(D)=−k=1∑K​∣D∣∣Ck​∣​log2​∣D∣∣Ck​∣​

根据此公式计算经验熵H(D),分析贷款申请样本数据表中的数据。最终分类结果只有两类,即放贷和不放贷。根据表中的数据统计可知,在15个数据中,9个数据的结果为放贷,6个数据的结果为不放贷。所以数据集D的经验熵H(D)为:
H ( D ) = − 9 15 log ⁡ 2 9 15 − 6 15 log ⁡ 2 6 15 H(D) = -\frac{9}{15}\log_{2}\frac{9}{15}-\frac{6}{15}\log_{2}\frac{6}{15} H(D)=−159​log2​159​−156​log2​156​
经过计算可知,数据集D的经验熵H(D)的值为0.971。

(2)编写代码计算经验熵
"""
函数说明:计算给定数据集的经验熵(香农熵)Parameters:dataSet - 数据集
Returns:shannonEnt - 经验熵(香农熵)
Author:Jack Cui
Modify:2017-03-29
"""
def calcShannonEnt(dataSet):numEntires = len(dataSet)                        #返回数据集的行数labelCounts = {}                                #保存每个标签(Label)出现次数的字典for featVec in dataSet:                            #对每组特征向量进行统计currentLabel = featVec[-1]                    #提取标签(Label)信息if currentLabel not in labelCounts.keys():    #如果标签(Label)没有放入统计次数的字典,添加进去labelCounts[currentLabel] = 0labelCounts[currentLabel] += 1                #Label计数shannonEnt = 0.0                                #经验熵(香农熵)for key in labelCounts:                            #计算香农熵prob = float(labelCounts[key]) / numEntires    #选择该标签(Label)的概率shannonEnt -= prob * log(prob, 2)            #利用公式计算return shannonEnt                                #返回经验熵(香农熵)if __name__ == '__main__':dataSet, features = createDataSet()print(calcShannonEnt(dataSet))#output
0.9709505944546686
(3) 信息增益

在上面,我们已经说过,如何选择特征,需要看信息增益。也就是说,信息增益是相对于特征而言的,信息增益越大,特征对最终的分类结果影响也就越大,我们就应该选择对最终分类结果影响最大的那个特征作为我们的分类特征

在讲解信息增益定义之前,我们还需要明确一个概念,条件熵(conditional entropy)

我们知道是什么,条件熵又是个什么鬼?

条件熵: H ( Y ∣ X ) H(Y|X) H(Y∣X)表示在已知随机变量X的条件下随机变量Y的不确定性。
随机变量X给定的条件下随机变量Y的条件熵,定义为X给定条件下Y的条件概率分布的对X的数学期望:
H ( Y ∣ X ) = − ∑ k = 1 K P ( X = x i ) log ⁡ 2 H ( Y ∣ X = x i ) , i = 1 , 2 , . . . , n H(Y|X) = -\sum_{k=1}^KP(X=x_{i})\log_{2}H(Y|X=x_{i}),i=1,2,...,n H(Y∣X)=−k=1∑K​P(X=xi​)log2​H(Y∣X=xi​),i=1,2,...,n

条件熵中的概率由数据估计(特别是极大似然估计)得到时,所对应的条件熵称为条件经验熵(empirical conditional entropy)

明确了条件熵经验条件熵的概念。接下来,让我们说说信息增益。前面也提到了,信息增益是相对于特征而言的。所以,特征A对训练数据集D信息增益g(D,A),定义为集合D的经验熵H(D)特征A给定条件下D的经验条件熵H(D|A)之差,即:
g ( D , A ) = H ( D ) − H ( D ∣ A ) g(D,A)=H(D)-H(D|A) g(D,A)=H(D)−H(D∣A)

一般地,熵H(D)条件熵H(D|A)之差称为互信息(mutual information)。决策树学习中的信息增益等价于训练数据集中类与特征的互信息。

特征An个不同的取值{a1,a2,···,an},根据特征A的取值将D划分为n个子集{D1,D2,···,Dn}|Di|为Di的样本个数。记子集Di中属于Ck的样本的集合为Dik,即Dik = Di ∩ Ck,|Dik|为Dik的样本个数,|Ck|为属于类Ck的样本个数。于是经验条件熵的公式可以些为:
H ( D ∣ A ) = − ∑ i = 1 n ∣ D i ∣ ∣ D ∣ H ( D i ) = − ∑ i = 1 n ∣ D i ∣ ∣ D ∣ ∑ k = 1 K ∣ C i k ∣ ∣ D i ∣ log ⁡ 2 ∣ C i k ∣ ∣ D i ∣ H(D|A)= -\sum_{i=1}^n\frac{|D_{i}|}{|D|}H(D_{i})= -\sum_{i=1}^n\frac{|D_{i}|}{|D|}\sum_{k=1}^K\frac{|C_{ik}|}{|D_{i}|}\log_{2}\frac{|C_{ik}|}{|D_{i}|} H(D∣A)=−i=1∑n​∣D∣∣Di​∣​H(Di​)=−i=1∑n​∣D∣∣Di​∣​k=1∑K​∣Di​∣∣Cik​∣​log2​∣Di​∣∣Cik​∣​

说了这么多概念性的东西,可能没弄懂这个公式咋用的,没关系,举几个例子,再回来看一下概念,就懂了。

贷款申请样本数据为例进行说明

年龄:0代表青年,1代表中年,2代表老年;
有工作:0代表否,1代表是;
有自己的房子:0代表否,1代表是;
信贷情况:0代表一般,1代表好,2代表非常好;
类别(是否给贷款):no代表否,yes代表是。A1  A2   A3  A4  A5
[[0, 0, 0, 0, 'no'],
[0, 0, 0, 1, 'no'],
[0, 1, 0, 1, 'yes'],
[0, 1, 1, 0, 'yes'],
[0, 0, 0, 0, 'no'],
[1, 0, 0, 0, 'no'],
[1, 0, 0, 1, 'no'],
[1, 1, 1, 1, 'yes'],
[1, 0, 1, 2, 'yes'],
[1, 0, 1, 2, 'yes'],
[2, 0, 1, 2, 'yes'],
[2, 0, 1, 1, 'yes'],
[2, 1, 0, 1, 'yes'],
[2, 1, 0, 2, 'yes'],
[2, 0, 0, 0, 'no']]

看下年龄这一列的数据,也就是特征A1,一共有三个类别,分别是:青年、中年和老年。我们只看年龄是青年的数据,年龄是青年的数据一共有5个,所以年龄是青年的数据在训练数据集出现的概率是十五分之五,也就是三分之一。同理,年龄是中年和老年的数据在训练数据集出现的概率也都是三分之一。现在我们只看年龄是青年的数据的最终得到贷款的概率为五分之二,因为在五个数据中,只有两个数据显示拿到了最终的贷款,同理,年龄是中年和老年的数据最终得到贷款的概率分别为五分之三五分之四。所以计算年龄的信息增益,过程如下:
g ( D , A 1 ) = H ( D ) − H ( D ∣ A 1 ) g(D,A_{1})=H(D)-H(D|A_{1}) g(D,A1​)=H(D)−H(D∣A1​)
H ( D ) = = − ∑ k = 1 K ∣ C k ∣ ∣ D ∣ log ⁡ 2 ∣ C k ∣ ∣ D ∣ = − 9 15 log ⁡ 2 9 15 − 6 15 log ⁡ 2 6 15 = 0.971 H(D)== -\sum_{k=1}^K\frac{|C_{k}|}{|D|}\log_{2}\frac{|C_{k}|}{|D|}\\=-\frac{9}{15}\log_{2}\frac{9}{15}-\frac{6}{15}\log_{2}\frac{6}{15}=0.971 H(D)==−k=1∑K​∣D∣∣Ck​∣​log2​∣D∣∣Ck​∣​=−159​log2​159​−156​log2​156​=0.971
H ( D ∣ A 1 ) = − ∑ i = 1 n ∣ D i ∣ ∣ D ∣ H ( D i ) = 5 15 H ( D 1 ) + 5 15 H ( D 2 ) + 5 15 H ( D 3 ) H(D|A_{1})= -\sum_{i=1}^n\frac{|D_{i}|}{|D|}H(D_{i})\\= \frac{5}{15}H(D_{1})+ \frac{5}{15}H(D_{2})+ \frac{5}{15}H(D_{3}) H(D∣A1​)=−i=1∑n​∣D∣∣Di​∣​H(Di​)=155​H(D1​)+155​H(D2​)+155​H(D3​)
H ( D 1 ) = − 2 5 log ⁡ 2 2 5 − 3 5 log ⁡ 2 3 5 H(D_{1})= -\frac{2}{5}\log_{2}\frac{2}{5}-\frac{3}{5}\log_{2}\frac{3}{5} H(D1​)=−52​log2​52​−53​log2​53​
H ( D 2 ) = − 3 5 log ⁡ 2 3 5 − 2 5 log ⁡ 2 2 5 H(D_{2})= -\frac{3}{5}\log_{2}\frac{3}{5}-\frac{2}{5}\log_{2}\frac{2}{5} H(D2​)=−53​log2​53​−52​log2​52​
H ( D 3 ) = − 4 5 log ⁡ 2 4 5 − 1 5 log ⁡ 2 1 5 H(D_{3})= -\frac{4}{5}\log_{2}\frac{4}{5}-\frac{1}{5}\log_{2}\frac{1}{5} H(D3​)=−54​log2​54​−51​log2​51​
则, g ( D , A 1 ) = H ( D ) − H ( D ∣ A 1 ) = 0.971 − 0.888 = 0.083 g(D,A_{1})=H(D)-H(D|A_{1})=0.971-0.888=0.083 g(D,A1​)=H(D)−H(D∣A1​)=0.971−0.888=0.083
同理,计算其余特征的信息增益g(D,A2)、g(D,A3)和g(D,A4)。分别为:
g ( D , A 2 ) = H ( D ) − H ( D ∣ A 2 ) = H ( D ) − ( 5 15 H ( D 1 ) + 10 15 H ( D 2 ) ) = 0.971 − ( 5 15 × 0 + ( − 4 10 log ⁡ 2 4 10 − 6 10 log ⁡ 2 6 10 ) = 0.971 − 0.647 = 0.324 g(D,A_{2})=H(D)-H(D|A_{2})\\=H(D)-(\frac{5}{15}H(D_{1})+ \frac{10}{15}H(D_{2}))\\=0.971-(\frac{5}{15} \times 0+(-\frac{4}{10}\log_{2}\frac{4}{10}-\frac{6}{10}\log_{2}\frac{6}{10})\\=0.971-0.647=0.324 g(D,A2​)=H(D)−H(D∣A2​)=H(D)−(155​H(D1​)+1510​H(D2​))=0.971−(155​×0+(−104​log2​104​−106​log2​106​)=0.971−0.647=0.324
g ( D , A 3 ) = H ( D ) − H ( D ∣ A 3 ) = H ( D ) − ( 6 15 H ( D 1 ) + 9 15 H ( D 2 ) ) = 0.971 − 0.551 = 0.420 g(D,A_{3})=H(D)-H(D|A_{3})\\=H(D)-(\frac{6}{15}H(D_{1})+ \frac{9}{15}H(D_{2}))\\=0.971-0.551=0.420 g(D,A3​)=H(D)−H(D∣A3​)=H(D)−(156​H(D1​)+159​H(D2​))=0.971−0.551=0.420
g ( D , A 4 ) = H ( D ) − H ( D ∣ A 4 ) = 0.971 − 0.608 = 0.363 g(D,A_{4})=H(D)-H(D|A_{4})=0.971-0.608=0.363 g(D,A4​)=H(D)−H(D∣A4​)=0.971−0.608=0.363
最后,比较特征的信息增益,由于特征A3(有自己的房子)的信息增益值最大,所以选择A3作为最优特征。
g ( D , A 1 ) < g ( D , A 2 ) < g ( D , A 4 ) < g ( D , A 3 ) g(D,A_{1})<g(D,A_{2})<g(D,A_{4})<g(D,A_{3}) g(D,A1​)<g(D,A2​)<g(D,A4​)<g(D,A3​)

#####(4)编写代码计算信息增益

"""
函数说明:按照给定特征划分数据集Parameters:dataSet - 待划分的数据集axis - 划分数据集的特征value - 需要返回的特征的值
Returns:无
"""
def splitDataSet(dataSet, axis, value):       retDataSet = []                                        #创建返回的数据集列表for featVec in dataSet:                             #遍历数据集if featVec[axis] == value:reducedFeatVec = featVec[:axis]                #去掉axis特征reducedFeatVec.extend(featVec[axis+1:])     #将符合条件的添加到返回的数据集retDataSet.append(reducedFeatVec)return retDataSet                                      #返回划分后的数据集

"""
函数说明:选择最优特征Parameters:dataSet - 数据集
Returns:bestFeature - 信息增益最大的(最优)特征的索引值
"""
def chooseBestFeatureToSplit(dataSet):numFeatures = len(dataSet[0]) - 1                    #特征数量baseEntropy = calcShannonEnt(dataSet)                 #计算数据集的香农熵bestInfoGain = 0.0                                  #信息增益bestFeature = -1                                    #最优特征的索引值for i in range(numFeatures):                         #遍历所有特征#获取dataSet的第i个所有特征featList = [example[i] for example in dataSet]uniqueVals = set(featList)                         #创建set集合{},元素不可重复newEntropy = 0.0                                  #经验条件熵for value in uniqueVals:                         #计算信息增益subDataSet = splitDataSet(dataSet, i, value)         #subDataSet划分后的子集prob = len(subDataSet) / float(len(dataSet))           #计算子集的概率newEntropy += prob * calcShannonEnt(subDataSet)     #根据公式计算经验条件熵infoGain = baseEntropy - newEntropy                     #信息增益print("第%d个特征的增益为%.3f" % (i, infoGain))            #打印每个特征的信息增益if (infoGain > bestInfoGain):                             #计算信息增益bestInfoGain = infoGain                             #更新信息增益,找到最大的信息增益bestFeature = i                                     #记录信息增益最大的特征的索引值return bestFeature                                             #返回信息增益最大的特征的索引值

splitDataSet函数是用来选择各个特征的子集的,比如选择年龄(第0个特征)的青年(用0代表)的自己。
调用splitDataSet(dataSet,0,0)返回的子集就是年龄为青年的5个数据集。
splitDataSet(dataSet,0,1)返回的子集就是年龄为中年的5个数据集。
splitDataSet(dataSet,0,2)返回的子集就是年龄为老年的5个数据集。
其他情况以此类推。

测试

if __name__ == '__main__':dataSet, features = createDataSet()print("最优特征索引值:" + str(chooseBestFeatureToSplit(dataSet)))#output
第0个特征的增益为0.083
第1个特征的增益为0.324
第2个特征的增益为0.420
第3个特征的增益为0.363
最优特征索引值:2

对比我们自己计算的结果,发现结果完全正确!最优特征的索引值为2,也就是特征A3(有自己的房子)。

2、决策树生成和修剪

我们已经学习了从数据集构造决策树算法所需要的子功能模块,包括经验熵的计算和最优特征的选择,其工作原理如下:得到原始数据集,然后基于最好的属性值划分数据集,由于特征值可能多于两个,因此可能存在大于两个分支的数据集划分。第一次划分之后,数据集被向下传递到树的分支的下一个结点。在这个结点上,我们可以再次划分数据。因此我们可以采用递归的原则处理数据集

构建决策树的算法有很多,比如C4.5、ID3和CART,这些算法在运行时并不总是在每次划分数据分组时都会消耗特征。由于特征数目并不是每次划分数据分组时都减少,因此这些算法在实际使用时可能引起一定的问题。目前我们并不需要考虑这个问题,只需要在算法开始运行前计算列的数目,查看算法是否使用了所有属性即可。

决策树生成算法递归地产生决策树,直到不能继续下去为止。这样产生的树往往对训练数据的分类很准确,但对未知的测试数据的分类却没有那么准确,即出现过拟合现象。过拟合的原因在于学习时过多地考虑如何提高对训练数据的正确分类,从而构建出过于复杂的决策树。解决这个问题的办法是考虑决策树的复杂度,对已生成的决策树进行简化。

4、决策树 项目案例

项目案例1: 使用决策树预测贷款类型

1、ID3算法

ID3算法的核心是在决策树各个结点上对应信息增益准则选择特征,递归地构建决策树。具体方法是:从根结点(root node)开始,对结点计算所有可能的特征的信息增益,选择信息增益最大的特征作为结点的特征,由该特征的不同取值建立子节点;再对子结点递归地调用以上方法,构建决策树;直到所有特征的信息增益均很小或没有特征可以选择为止。最后得到一个决策树。ID3相当于用极大似然法进行概率模型的选择

利用上面求得的结果,由于特征A3(有自己的房子)的信息增益值最大,所以选择特征A3作为根结点的特征。它将训练集D划分为两个子集D1(A3取值为"是")D2(A3取值为"否")。由于D1只有同一类的样本点,即都是同一个类别,所以它成为一个叶结点,结点的类标记为“是”。

D2则需要从特征A1(年龄)A2(有工作)A4(信贷情况)中选择新的特征,计算各个特征的信息增益:
g ( D 2 , A 1 ) = H ( D 2 ) − H ( D 2 ∣ A 1 ) = 0.251 g(D_{2},A_{1})=H(D_{2})-H(D_{2}|A_{1})=0.251 g(D2​,A1​)=H(D2​)−H(D2​∣A1​)=0.251
g ( D 2 , A 2 ) = H ( D 2 ) − H ( D 2 ∣ A 2 ) = 0.918 g(D_{2},A_{2})=H(D_{2})-H(D_{2}|A_{2})=0.918 g(D2​,A2​)=H(D2​)−H(D2​∣A2​)=0.918
g ( D 2 , A 4 ) = H ( D 2 ) − H ( D 2 ∣ A 4 ) = 0.474 g(D_{2},A_{4})=H(D_{2})-H(D_{2}|A_{4})=0.474 g(D2​,A4​)=H(D2​)−H(D2​∣A4​)=0.474
根据计算,选择信息增益最大的特征A2(有工作)作为结点的特征。由于A2有两个可能取值,从这一结点引出两个子结点:一个对应"是"(有工作)的子结点,包含3个样本,它们属于同一类,所以这是一个叶结点,类标记为"是";另一个是对应"否"(无工作)的子结点,包含6个样本,它们也属于同一类,所以这也是一个叶结点,类标记为"否"

这样就生成了一个决策树,该决策树只用了两个特征(有两个内部结点),生成的决策树如下图所示。

2、编写代码构建决策树

我们使用字典存储决策树的结构,比如上面的决策树,用字典可以表示为:

{'有自己的房子': {0: {'有工作': {0: 'no', 1: 'yes'}}, 1: 'yes'}}
"""
函数说明:统计classList中出现此处最多的元素(类标签)Parameters:classList - 类标签列表
Returns:sortedClassCount[0][0] - 出现此处最多的元素(类标签)"""
def majorityCnt(classList):classCount = {}for vote in classList:    #统计classList中每个元素出现的次数if vote not in classCount.keys():classCount[vote] = 0   classCount[vote] += 1sortedClassCount = sorted(classCount.items(), key = operator.itemgetter(1), reverse = True)        #根据字典的值降序排序return sortedClassCount[0][0]  #返回classList中出现次数最多的元素
"""
函数说明:创建决策树Parameters:dataSet - 训练数据集labels - 分类属性标签featLabels - 存储选择的最优特征标签
Returns:myTree - 决策树
"""
def createTree(dataSet, labels, featLabels):classList = [example[-1] for example in dataSet] #取分类标签(是否放贷:yes or no)if classList.count(classList[0]) == len(classList):  #如果类别完全相同则停止继续划分return classList[0]if len(dataSet[0]) == 1 or len(labels) == 0:  #遍历完所有特征时返回出现次数最多的类标签return majorityCnt(classList)bestFeat = chooseBestFeatureToSplit(dataSet)  #选择最优特征bestFeatLabel = labels[bestFeat]  #最优特征的标签featLabels.append(bestFeatLabel)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, featLabels)return myTree

递归创建决策树时,递归有两个终止条件:第一个停止条件是所有的类标签完全相同,则直接返回该类标签第二个停止条件是使用完了所有特征,仍然不能将数据划分仅包含唯一类别的分组,即决策树构建失败,特征不够用此时说明数据维度不够,由于第二个停止条件无法简单地返回唯一的类标签,这里挑选出现数量最多的类别作为返回值

测试

if __name__ == '__main__':dataSet, labels = createDataSet()featLabels = []myTree = createTree(dataSet, labels, featLabels)print(myTree)
#输出
myTree: {'有自己的房子': {0: {'有工作': {0: 'no', 1: 'yes'}}, 1: 'yes'}}
3、使用决策树执行分类

用决策树做分类的代码很简单,编写代码如下:


"""
函数说明:使用决策树分类Parameters:inputTree - 已经生成的决策树featLabels - 存储选择的最优特征标签testVec - 测试数据列表,顺序对应最优特征标签
Returns:classLabel - 分类结果
"""
def classify(inputTree, featLabels, testVec):firstStr = next(iter(inputTree)) #获取决策树结点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__':dataSet, labels = createDataSet()featLabels = []myTree = createTree(dataSet, labels, featLabels)testVec = [0,1]                                        #测试数据result = classify(myTree, featLabels, testVec)if result == 'yes':print('放贷')if result == 'no':print('不放贷')
#输出
myTree: {'有自己的房子': {0: {'有工作': {0: 'no', 1: 'yes'}}, 1: 'yes'}}
featLabels: ['有自己的房子', '有工作']
放贷
4、决策树的存储

假设我们已经得到决策树{'有自己的房子': {0: {'有工作': {0: 'no', 1: 'yes'}}, 1: 'yes'}},使用pickle.dump存储决策树。

import pickle"""
函数说明:存储决策树Parameters:inputTree - 已经生成的决策树filename - 决策树的存储文件名
Returns:无
"""
def storeTree(inputTree, filename):with open(filename, 'wb') as fw:pickle.dump(inputTree, fw)if __name__ == '__main__':myTree = {'有自己的房子': {0: {'有工作': {0: 'no', 1: 'yes'}}, 1: 'yes'}}storeTree(myTree, 'classifierStorage.txt')
5、决策树的载入
import pickle"""
函数说明:读取决策树Parameters:filename - 决策树的存储文件名
Returns:pickle.load(fr) - 决策树字典
"""
def grabTree(filename):fr = open(filename, 'rb')return pickle.load(fr)if __name__ == '__main__':myTree = grabTree('classifierStorage.txt')print(myTree)

项目案例2: 使用决策树预测隐形眼镜类型

1、实战背景

眼科医生是如何判断患者需要佩戴隐形眼镜的类型的?一旦理解了决策树的工作原理,我们甚至也可以帮助人们判断需要佩戴的镜片类型。

隐形眼镜数据集是非常著名的数据集,它包含很多换着眼部状态的观察条件以及医生推荐的隐形眼镜类型。隐形眼镜类型包括硬材质(hard)软材质(soft)以及不适合佩戴隐形眼镜(no lenses)。数据来源与UCI数据库,数据集下载地址:https://github.com/Jack-Cherish/Machine-Learning/blob/master/Decision%20Tree/classifierStorage.txt

一共有24组数据,数据的Labels依次是age、prescript、astigmatic、tearRate、class,也就是第一列是年龄,第二列是症状,第三列是是否散光,第四列是眼泪数量,第五列是最终的分类标签。数据如下图所示:

young    myope   no  reduced no lenses
young   myope   no  normal  soft
young   myope   yes reduced no lenses
young   myope   yes normal  hard
young   hyper   no  reduced no lenses
young   hyper   no  normal  soft
young   hyper   yes reduced no lenses
young   hyper   yes normal  hard
pre myope   no  reduced no lenses
pre myope   no  normal  soft
pre myope   yes reduced no lenses
pre myope   yes normal  hard
pre hyper   no  reduced no lenses
pre hyper   no  normal  soft
pre hyper   yes reduced no lenses
pre hyper   yes normal  no lenses
presbyopic  myope   no  reduced no lenses
presbyopic  myope   no  normal  no lenses
2、使用Sklearn构建决策树

官方英文文档地址:http://scikit-learn.org/stable/modules/generated/sklearn.tree.DecisionTreeClassifier.html

sklearn.tree模块提供了决策树模型,用于解决分类问题和回归问题。方法如下图所示:

本次实战内容使用的是DecisionTreeClassifierexport_graphviz,前者用于决策树构建,后者用于决策树可视化

DecisionTreeClassifier构建决策树:

让我们先看下DecisionTreeClassifier这个函数,一共有12个参数

class sklearn.tree.DecisionTreeClassifier(*, criterion='gini', splitter='best', max_depth=None, min_samples_split=2, min_samples_leaf=1, min_weight_fraction_leaf=0.0, max_features=None, random_state=None, max_leaf_nodes=None, min_impurity_decrease=0.0, min_impurity_split=None, class_weight=None, ccp_alpha=0.0)

参数说明如下:

criterion:特征选择标准,可选参数,默认是gini,可以设置为entropygini是基尼不纯度,是将来自集合的某种结果随机应用于某一数据项的预期误差率,是一种基于统计的思想。entropy是香农熵,也就是上面讲过的内容,是一种基于信息论的思想。Sklearngini设为默认参数,应该也是做了相应的斟酌的,精度也许更高些?ID3算法使用的是entropy,CART算法使用的则是gini

splitter:特征划分点选择标准,可选参数,默认是best,可以设置为random。每个结点的选择策略。best参数是根据算法选择最佳的切分特征,例如gini、entropy。random随机的在部分划分点中找局部最优的划分点。默认的"best"适合样本量不大的时候,而如果样本数据量非常大,此时决策树构建推荐"random"。

max_features划分时考虑的最大特征数,可选参数,默认是None。寻找最佳切分时考虑的最大特征数(n_features为总共的特征数),有如下6种情况:

  • [1] 如果max_features是整型的数,则考虑max_features个特征;
  • [2] 如果max_features是浮点型的数,则考虑int(max_features * n_features)个特征;
  • [3] 如果max_features设为auto,那么max_features = sqrt(n_features);
  • [4] 如果max_features设为sqrt,那么max_featrues = sqrt(n_features),跟auto一样;
  • [5] 如果max_features设为log2,那么max_features = log2(n_features);
  • [6] 如果max_features设为None,那么max_features = n_features,也就是所有特征都用。

一般来说,如果样本特征数不多,比如小于50,我们用默认的"None"就可以了,如果特征数非常多,我们可以灵活使用刚才描述的其他取值来控制划分时考虑的最大特征数,以控制决策树的生成时间。

max_depth决策树最大深,可选参数,默认是None。这个参数是这是树的层数的。层数的概念就是,比如在贷款的例子中,决策树的层数是2层。如果这个参数设置为None,那么决策树在建立子树的时候不会限制子树的深度。一般来说,数据少或者特征少的时候可以不管这个值。或者如果设置了min_samples_slipt参数,那么直到少于min_smaples_split`个样本为止。如果模型样本量多,特征也多的情况下,推荐限制这个最大深度,具体的取值取决于数据的分布。常用的可以取值10-100之间。

min_samples_split内部节点再划分所需最小样本数,可选参数,默认是2。这个值限制了子树继续划分的条件。如果min_samples_split为整数,那么在切分内部结点的时候,min_samples_split作为最小的样本数,也就是说,如果样本已经少于min_samples_split个样本,则停止继续切分。如果min_samples_split为浮点数,那么min_samples_split就是一个百分比,ceil(min_samples_split * n_samples),数是向上取整的。如果样本量不大,不需要管这个值。如果样本量数量级非常大,则推荐增大这个值。

min_samples_leaf叶子节点最少样本数,可选参数,默认是1。这个值限制了叶子节点最少的样本数,如果某叶子节点数目小于样本数,则会和兄弟节点一起被剪枝。叶结点需要最少的样本数,也就是最后到叶结点,需要多少个样本才能算一个叶结点。如果设置为1,哪怕这个类别只有1个样本,决策树也会构建出来。如果min_samples_leaf是整数,那么min_samples_leaf作为最小的样本数。如果是浮点数,那么min_samples_leaf就是一个百分比,同上,celi(min_samples_leaf * n_samples),数是向上取整的。如果样本量不大,不需要管这个值。如果样本量数量级非常大,则推荐增大这个值。
min_weight_fraction_leaf:叶子节点最小的样本权重和,可选参数,默认是0。这个值限制了叶子节点所有样本权重和的最小值,如果小于这个值,则会和兄弟节点一起被剪枝。一般来说,如果我们有较多样本有缺失值,或者分类树样本的分布类别偏差很大,就会引入样本权重,这时我们就要注意这个值了。

max_leaf_nodes:最大叶子节点数,可选参数,默认是None。通过限制最大叶子节点数,可以防止过拟合。如果加了限制,算法会建立在最大叶子节点数内最优的决策树。如果特征不多,可以不考虑这个值,但是如果特征分成多的话,可以加以限制,具体的值可以通过交叉验证得到。

class_weight:类别权重,可选参数,默认是None,也可以字典、字典列表、balanced。指定样本各类别的的权重,主要是为了防止训练集某些类别的样本过多,导致训练的决策树过于偏向这些类别。类别的权重可以通过{class_label:weight}这样的格式给出,这里可以自己指定各个样本的权重,或者用balanced,如果使用balanced,则算法会自己计算权重,样本量少的类别所对应的样本权重会高。当然,如果你的样本类别分布没有明显的偏倚,则可以不管这个参数,选择默认的None。

random_state:可选参数,默认是None。随机数种子。如果是证书,那么random_state会作为随机数生成器的随机数种子。随机数种子,如果没有设置随机数,随机出来的数与当前系统时间有关,每个时刻都是不同的。如果设置了随机数种子,那么相同随机数种子,不同时刻产生的随机数也是相同的。如果是RandomState instance,那么random_state是随机数生成器。如果为None,则随机数生成器使用np.random。

min_impurity_split:节点划分最小不纯度,可选参数,默认是1e-7。这是个阈值,这个值限制了决策树的增长,如果某节点的不纯度(基尼系数,信息增益,均方差,绝对差)小于这个阈值,则该节点不再生成子节点。即为叶子节点 。

presort数据是否预排序,可选参数,默认为False,这个值是布尔值,默认是False不排序。一般来说,如果样本量少或者限制了一个深度很小的决策树,设置为true可以让划分点选择更加快,决策树建立的更加快。如果样本量太大的话,反而没有什么好处。问题是样本量少的时候,我速度本来就不慢。所以这个值一般懒得理它就可以了。

除了这些参数要注意以外,其他在调参时的注意点有:

当样本数量少但是样本特征非常多的时候,决策树很容易过拟合,一般来说,样本数比特征数多一些会比较容易建立健壮的模型

如果样本数量少但是样本特征非常多,在拟合决策树模型前,推荐先做维度规约,比如主成分分析(PCA),特征选择(Losso)或者独立成分分析(ICA)。这样特征的维度会大大减小。再来拟合决策树模型效果会好。

推荐多用决策树的可视化,同时先限制决策树的深度,这样可以先观察下生成的决策树里数据的初步拟合情况,然后再决定是否要增加深度。

在训练模型时,注意观察样本的类别情况(主要指分类树),如果类别分布非常不均匀,就要考虑用class_weight来限制模型过于偏向样本多的类别。

决策树的数组使用的是numpy的float32类型,如果训练数据不是这样的格式,算法会先做copy再运行。

如果输入的样本矩阵是稀疏的,推荐在拟合前调用csc_matrix稀疏化,在预测前调用csr_matrix稀疏化。

sklearn.tree.DecisionTreeClassifier()提供了一些方法供我们使用,如下图所示:

了解到这些,我们就可以编写代码了。

from sklearn import treeif __name__ == '__main__':fr = open('lenses.txt')lenses = [inst.strip().split('\t') for inst in fr.readlines()]print(lenses)lensesLabels = ['age', 'prescript', 'astigmatic', 'tearRate']clf = tree.DecisionTreeClassifier()lenses = clf.fit(lenses, lensesLabels)


我们可以看到程序报错了,这是为什么?因为在fit()函数不能接收string类型的数据,通过打印的信息可以看到,数据都是string类型的。在使用fit()函数之前,我们需要对数据集进行编码,这里可以使用两种方法:

  1. LabelEncoder :将字符串转换为增量值
    2.OneHotEncoder:使用One-of-K算法将字符串转换为整数

为了对string类型的数据序列化,需要先生成pandas数据,这样方便我们的序列化工作。这里我使用的方法是,原始数据->字典->pandas数据,编写代码如下:

import pandas as pdif __name__ == '__main__':with open('lenses.txt', 'r') as fr:   #加载文件lenses = [inst.strip().split('\t') for inst in fr.readlines()] #处理文件lenses_target = []    #提取每组数据的类别,保存在列表里for each in lenses:lenses_target.append(each[-1])lensesLabels = ['age', 'prescript', 'astigmatic', 'tearRate']   #特征标签       lenses_list = []      #保存lenses数据的临时列表lenses_dict = {}      #保存lenses数据的字典,用于生成pandasfor each_label in lensesLabels:     #提取信息,生成字典for each in lenses:lenses_list.append(each[lensesLabels.index(each_label)])lenses_dict[each_label] = lenses_listlenses_list = []print(lenses_dict)     #打印字典信息lenses_pd = pd.DataFrame(lenses_dict)    #生成pandas.DataFrameprint(lenses_pd)
lenses 为:[['young', 'myope', 'no', 'reduced', 'no lenses'], ['young', 'myope', 'no', 'normal', 'soft'], ['young', 'myope', 'yes', 'reduced', 'no lenses'], ['young', 'myope', 'yes', 'normal', 'hard'], ['young', 'hyper', 'no', 'reduced', 'no lenses'], ['young', 'hyper', 'no', 'normal', 'soft'], ['young', 'hyper', 'yes', 'reduced', 'no lenses'], ['young', 'hyper', 'yes', 'normal', 'hard'], ['pre', 'myope', 'no', 'reduced', 'no lenses'], ['pre', 'myope', 'no', 'normal', 'soft'], ['pre', 'myope', 'yes', 'reduced', 'no lenses'], ['pre', 'myope', 'yes', 'normal', 'hard'], ['pre', 'hyper', 'no', 'reduced', 'no lenses'], ['pre', 'hyper', 'no', 'normal', 'soft'], ['pre', 'hyper', 'yes', 'reduced', 'no lenses'], ['pre', 'hyper', 'yes', 'normal', 'no lenses'], ['presbyopic', 'myope', 'no', 'reduced', 'no lenses'], ['presbyopic', 'myope', 'no', 'normal', 'no lenses'], ['presbyopic', 'myope', 'yes', 'reduced', 'no lenses'], ['presbyopic', 'myope', 'yes', 'normal', 'hard'], ['presbyopic', 'hyper', 'no', 'reduced', 'no lenses'], ['presbyopic', 'hyper', 'no', 'normal', 'soft'], ['presbyopic', 'hyper', 'yes', 'reduced', 'no lenses'], ['presbyopic', 'hyper', 'yes', 'normal', 'no lenses']]
lenses_dict 为:{'age': ['young', 'young', 'young', 'young', 'young', 'young', 'young', 'young', 'pre', 'pre', 'pre', 'pre', 'pre', 'pre', 'pre', 'pre', 'presbyopic', 'presbyopic', 'presbyopic', 'presbyopic', 'presbyopic', 'presbyopic', 'presbyopic', 'presbyopic'], 'prescript': ['myope', 'myope', 'myope', 'myope', 'hyper', 'hyper', 'hyper', 'hyper', 'myope', 'myope', 'myope', 'myope', 'hyper', 'hyper', 'hyper', 'hyper', 'myope', 'myope', 'myope', 'myope', 'hyper', 'hyper', 'hyper', 'hyper'], 'astigmatic': ['no', 'no', 'yes', 'yes', 'no', 'no', 'yes', 'yes', 'no', 'no', 'yes', 'yes', 'no', 'no', 'yes', 'yes', 'no', 'no', 'yes', 'yes', 'no', 'no', 'yes', 'yes'], 'tearRate': ['reduced', 'normal', 'reduced', 'normal', 'reduced', 'normal', 'reduced', 'normal', 'reduced', 'normal', 'reduced', 'normal', 'reduced', 'normal', 'reduced', 'normal', 'reduced', 'normal', 'reduced', 'normal', 'reduced', 'normal', 'reduced', 'normal']}
lenses_pd 为:age prescript astigmatic tearRate
0        young     myope         no  reduced
1        young     myope         no   normal
2        young     myope        yes  reduced
3        young     myope        yes   normal
4        young     hyper         no  reduced
5        young     hyper         no   normal
6        young     hyper        yes  reduced
7        young     hyper        yes   normal
8          pre     myope         no  reduced
9          pre     myope         no   normal
10         pre     myope        yes  reduced
11         pre     myope        yes   normal
12         pre     hyper         no  reduced
13         pre     hyper         no   normal
14         pre     hyper        yes  reduced
15         pre     hyper        yes   normal
16  presbyopic     myope         no  reduced
17  presbyopic     myope         no   normal
18  presbyopic     myope        yes  reduced
19  presbyopic     myope        yes   normal
20  presbyopic     hyper         no  reduced
21  presbyopic     hyper         no   normal
22  presbyopic     hyper        yes  reduced
23  presbyopic     hyper        yes   normal

接下来,将数据序列化,编写代码如下:

import pandas as pd
from sklearn.preprocessing import LabelEncoderimport pydotplus
from sklearn.externals.six import StringIOif __name__ == '__main__':with open('lenses.txt', 'r') as fr:     #加载文件lenses = [inst.strip().split('\t') for inst in fr.readlines()]  #处理文件lenses_target = []     #提取每组数据的类别,保存在列表里for each in lenses:lenses_target.append(each[-1])lensesLabels = ['age', 'prescript', 'astigmatic', 'tearRate'] #特征标签       lenses_list = []       #保存lenses数据的临时列表lenses_dict = {}       #保存lenses数据的字典,用于生成pandasfor each_label in lensesLabels:     #提取信息,生成字典for each in lenses:lenses_list.append(each[lensesLabels.index(each_label)])lenses_dict[each_label] = lenses_listlenses_list = []# print(lenses_dict)          #打印字典信息lenses_pd = pd.DataFrame(lenses_dict)     #生成pandas.DataFrame# print(lenses_pd)        #打印pandas.DataFramele = LabelEncoder()    #创建LabelEncoder()对象,用于序列化            for col in lenses_pd.columns:    #为每一列序列化lenses_pd[col] = le.fit_transform(lenses_pd[col])print(lenses_pd)lenses_pd 为:age  prescript  astigmatic  tearRate
0     2          1           0         1
1     2          1           0         0
2     2          1           1         1
3     2          1           1         0
4     2          0           0         1
5     2          0           0         0
6     2          0           1         1
7     2          0           1         0
8     0          1           0         1
9     0          1           0         0
10    0          1           1         1
11    0          1           1         0
12    0          0           0         1
13    0          0           0         0
14    0          0           1         1
15    0          0           1         0
16    1          1           0         1
17    1          1           0         0
18    1          1           1         1
19    1          1           1         0
20    1          0           0         1
21    1          0           0         0
22    1          0           1         1
23    1          0           1         0
3、使用Graphviz可视化决策树

Graphviz的是AT&T Labs Research开发的图形绘制工具,他可以很方便的用来绘制结构化的图形网络,支持多种格式输出,生成图片的质量和速度都不错。它的输入是一个用dot语言编写的绘图脚本,通过对输入脚本的解析,分析出其中的点,边以及子图,然后根据属性进行绘制。是使用Sklearn生成的决策树就是dot格式的,因此我们可以直接利用Graphviz将决策树可视化。

在讲解编写代码之前,我们需要安装两样东西,即pydotplusGrphviz
(1)安装Pydotplus

pip3 install pydotplus

(2)安装Graphviz

Graphviz不能使用pip进行安装,我们需要手动安装,下载地址:https://www.graphviz.org

下载好安装包,进行安装,安装完毕之后,需要设置Graphviz的环境变量。

首先,按快捷键win+r,在出现的运行对话框中输入sysdm.cpl,点击确定,出现如下对话框:


添加好环境变量之后,重启电脑,我们就可以正常使用Graphviz了。

可视化部分的代码不难,都是有套路的,直接填参数就好,详细内容可以查看官方教程:http://scikit-learn.org/stable/modules/tree.html#tree

from sklearn.preprocessing import LabelEncoder, OneHotEncoder
from sklearn.externals.six import StringIO
from sklearn import tree
import pandas as pd
import numpy as np
import pydotplus
if __name__ == '__main__':with open('lenses.txt', 'r') as fr:  #加载文件lenses = [inst.strip().split('\t') for inst in fr.readlines()] #处理文件lenses_target = []    #提取每组数据的类别,保存在列表里for each in lenses:lenses_target.append(each[-1])print(lenses_target)lensesLabels = ['age', 'prescript', 'astigmatic', 'tearRate']   #特征标签       lenses_list = []        #保存lenses数据的临时列表lenses_dict = {}         #保存lenses数据的字典,用于生成pandasfor each_label in lensesLabels:    #提取信息,生成字典for each in lenses:lenses_list.append(each[lensesLabels.index(each_label)])lenses_dict[each_label] = lenses_listlenses_list = []# print(lenses_dict)    #打印字典信息lenses_pd = pd.DataFrame(lenses_dict)      #生成pandas.DataFrame# print(lenses_pd)       #打印pandas.DataFramele = LabelEncoder()    #创建LabelEncoder()对象,用于序列化           for col in lenses_pd.columns:    #序列化lenses_pd[col] = le.fit_transform(lenses_pd[col])# print(lenses_pd) #打印编码信息clf = tree.DecisionTreeClassifier(max_depth = 4)   #创建DecisionTreeClassifier()类clf = clf.fit(lenses_pd.values.tolist(), lenses_target)   #使用数据,构建决策树dot_data = StringIO()tree.export_graphviz(clf, out_file = dot_data,   #绘制决策树feature_names = lenses_pd.keys(),class_names = clf.classes_,filled=True, rounded=True,special_characters=True)graph = pydotplus.graph_from_dot_data(dot_data.getvalue())graph.write_pdf("tree.pdf")  #保存绘制好的决策树,以PDF的形式存储。

注意:可能会报错

解决方法:
查阅资料后发现sklearn 0.23版本已经删掉了这个包。
因此直接安装six包:

pip install six

然后直接引入即可:

from six import StringIO

运行代码,在该python文件保存的相同目录下,会生成一个名为tree的PDF文件,打开文件,我们就可以看到决策树的可视化效果图了。

确定好决策树之后,我们就可以做预测了。可以根据自己的眼睛情况和年龄等特征,看一看自己适合何种材质的隐形眼镜。使用如下代码就可以看到预测结果:

print(clf.predict([[1,1,1,0]]))   #输出:
['hard']

总结

决策树的一些优点:

  • 易于理解和解释。决策树可以可视化。
  • 几乎不需要数据预处理。其他方法经常需要数据标准化,创建虚拟变量和删除缺失值。决策树还不支持缺失值。
  • 使用树的花费(例如预测数据)是训练数据点(data points)数量的对数。
  • 可以同时处理数值变量和分类变量。其他方法大都适用于分析一种变量的集合。
  • 可以处理多值输出变量问题。
  • 使用白盒模型。如果一个情况被观察到,使用逻辑判断容易表示这种规则。相反,如果是黑盒模型(例如人工神经网络),结果会非常难解释。
  • 即使对真实模型来说,假设无效的情况下,也可以较好的适用。

决策树的一些缺点:

  • 决策树学习可能创建一个过于复杂的树,并不能很好的预测数据。也就是过拟合。修剪机制(现在不支持),设置一个叶子节点需要的最小样本数量,或者数的最大深度,可以避免过拟合。
  • 决策树可能是不稳定的,因为即使非常小的变异,可能会产生一颗完全不同的树。这个问题通过decision trees with an ensemble来缓解。
  • 概念难以学习,因为决策树没有很好的解释他们,例如,XOR, parity or multiplexer problems。
    如果某些分类占优势,决策树将会创建一棵有偏差的树。因此,建议在训练之前,先抽样使样本均衡。

参考文章:

  • 机器学习实战教程(二):决策树基础篇之让我们从相亲说起 | Jack Cui
  • ApacheCN 第3章 决策树
  • 《机器学习实战》

本文出现的所有代码,来自github参考代码。

机器学习之决策树学习笔记相关推荐

  1. Python3《机器学习实战》学习笔记(三):决策树实战篇

    转载请注明作者和出处: http://blog.csdn.net/c406495762  运行平台: Windows  Python版本: Python3.x  IDE: Sublime text3 ...

  2. 《机器学习实战》学习笔记(三):决策树

    欢迎关注WX公众号:[程序员管小亮] [机器学习]<机器学习实战>读书笔记及代码 总目录 https://blog.csdn.net/TeFuirnever/article/details ...

  3. Python3《机器学习实战》学习笔记(三):决策树实战篇之为自己配个隐形眼镜

    转载请注明作者和出处: http://blog.csdn.net/c406495762 运行平台: Windows Python版本: Python3.x IDE: Sublime text3 一 前 ...

  4. python predictabel_Python3《机器学习实战》学习笔记(三):决策树实战篇之为自己配个隐形眼镜...

    ) p1Vect = np.log(p1Num/p1Denom) #取对数,防止下溢出 p0Vect = np.log(p0Num/p0Denom) return p0Vect,p1Vect,pAbu ...

  5. Python3《机器学习实战》学习笔记(二):决策树基础篇之让我们从相亲说起

    转载请注明作者和出处: http://blog.csdn.net/c406495762 运行平台: Windows Python版本: Python3.x IDE: Sublime text3 个人网 ...

  6. cart算法_决策树学习笔记(三):CART算法,决策树总结

    点击上方"Python数据科学",选择"星标公众号" 关键时刻,第一时间送达! 作者:xiaoyu 介绍:一个半路转行的数据挖掘工程师 推荐导读:本篇为树模型系 ...

  7. 《机器学习实战》学习笔记(四):基于概率论的分类方法 - 朴素贝叶斯

    欢迎关注WX公众号:[程序员管小亮] [机器学习]<机器学习实战>读书笔记及代码 总目录 https://blog.csdn.net/TeFuirnever/article/details ...

  8. Python3《机器学习实战》学习笔记(五):朴素贝叶斯实战篇之新浪新闻分类

    转载请注明作者和出处:http://blog.csdn.net/c406495762 Github代码获取:https://github.com/Jack-Cherish/Machine-Learni ...

  9. 《机器学习实战》学习笔记(七):利用AdaBoost 元算法提高分类性能

    欢迎关注WX公众号:[程序员管小亮] [机器学习]<机器学习实战>读书笔记及代码 总目录 https://blog.csdn.net/TeFuirnever/article/details ...

最新文章

  1. 1013 Battle Over Cities (25分)(用割点做)
  2. Express调用mssql驱动公共类dbHelper
  3. springboot获取URL请求参数的几种方法
  4. unsigned char对应java_Java的数据类型,怎么就没有uchar呢
  5. C++ deque 底层原理及 queue、stack 容器的使用详解
  6. C++获取当前系统时间并格式化输出
  7. 使用 InSpec 实现符合性即代码
  8. css3动画过渡按钮
  9. 计算一的阶乘累加到十的阶乘
  10. CrossApp 0.4.2 发布,隆重推出 WebView
  11. vue中配置filemanager-webpack-plugin的报错和解决
  12. 搭建无线打印服务器,用旧电脑轻松架设无线网络打印服务器
  13. 漫谈多模光纤类型:OM1、OM2、OM3、OM4、OM5,深度好文,值得阅读
  14. 可视化常见绘图(四)柱状图
  15. Win10系统开机后任务栏卡死解决方法
  16. 均匀节点插值与切比雪夫插值以及龙格现象
  17. Linux系统高树攀登之路
  18. Android 控件 之 Adapter
  19. 主定理(主方法)求解递归式
  20. 嵌入式linux解决方法 | u-boot NFS下载文件报错:Loading: *** ERROR: File lookup fail

热门文章

  1. 5款电脑软件,每款都好用到爆
  2. python mta 模拟试题_统计模拟试题
  3. JAVA 小Q 世界上最遥远的距离
  4. ajax获取后台数据的几种方式及如何获取ajax内部的数据供外部使用
  5. 使用js获取最快获取到本地ip
  6. 噩梦系列篇之Player的动画状态机
  7. Shell 脚本打印最终执行脚本内容
  8. Heat equation
  9. GBase 8a 常用show命令-- show tables、show databases
  10. 网络通讯-Socket-TCP-demo(C语言版)