前言
今天看了会《机器学习实战》第三章:决策树,很迷,似懂非懂,专业术语太多了,而且有点混乱,对于一个大一概率论没学好的学渣来说,如今大三的我看到那些概率公式和一些概率论专业术语就头疼,马上就打了退堂鼓,早起看了半个小时没看明白果断又躺回了床上。直到我看到了这篇博文。
首先感谢博主:Jack-Cui
主页:http://blog.csdn.net/c406495762
决策树博文地址:https://blog.csdn.net/c406495762/article/details/75663451
这篇博文对书上的内容很形象的进行了表达,通俗易懂,用自己的实例来进行讲解,比书上讲的清楚太多,于是我才开始了学习,感激不尽,真心推荐。
我这篇博文大多从它的博文中摘抄,但也是我一个字一个敲出来的,算法我也是自己算过的,算是学完它的博文的一个总结吧,如果还看不明白的可以直接看他的吧。
明天我会继续按照它的博客学习。
进入正题。
决策树
概念
决策树是什么?决策树是一种基本的分类与回归方法。举个通俗易懂的例子,如下图所示的流程图就是一个决策树,长方形代表判断模块,椭圆形成代表终止模块,表示已经得出结论,可以终止运行。从判断模块引出的左右箭头称作为分支,它可以达到另一个判断模块或者终止模块。我们还可以这样理解,分类决策树模型是一种描述对实例进行分类的树形结构。决策树由结点和有向边组成。结点有两种类型:内部结点和叶结点。内部结点表示一个特征或属性,叶结点表示一个类。蒙圈没??如下图所示的决策树,长方形和椭圆形都是结点。长方形的结点属于内部结点,椭圆形的结点属于叶结点,从结点引出的左右箭头就是有向边。而最上面的结点就是决策树的根结点。这样,结点说法就与模块说法对应上了,理解就好。
我们回到这个流程图,对,你没看错,这就是一个假想的相亲对象分类系统。它首先检测相亲对方是否有房。如果有房,则对于这个相亲对象可以考虑进一步接触。如果没有房,则观察相亲对象是否有上进心,如果没有,直接Say Goodbye,此时可以说:"你人很好,但是我们不合适。"如果有,则可以把这个相亲对象列入候选名单,好听点叫候选名单,有点瑕疵地讲,那就是备胎。
不过这只是个简单的相亲对象分类系统,只是做了简单的分类。真实情况可能要复杂得多,考虑因素也可以是五花八门。脾气好吗?会做饭吗?愿意做家务吗?家里几个孩子?父母是干什么的?天啊,我不想再说下去了,想想都可怕。
我们可以把决策树看成一个if-then规则的集合,将决策树转换成if-then规则的过程是这样的:由决策树的根结点到叶结点的每一条路径构建一条规则;路径上内部结点的特征对应着规则的条件,而叶结点的类对应着规则的结论。决策树的路径或其对应的if-then规则集合具有一个重要的性质:互斥并且完备。这就是说,每一个实例都被一条路径或一条规则所覆盖,而且只被一条路径或一条规则所覆盖。这里所覆盖是指实例的特征与路径上的特征一致或实例满足规则的条件。
使用决策树做预测需要以下过程:
- 收集数据:可以使用任何方法。比如想构建一个相亲系统,我们可以从媒婆那里,或者通过参访相亲对象获取数据。根据他们考虑的因素和最终的选择结果,就可以得到一些供我们利用的数据了。
- 准备数据:收集完的数据,我们要进行整理,将这些所有收集的信息按照一定规则整理出来,并排版,方便我们进行后续处理。
- 分析数据:可以使用任何方法,决策树构造完成之后,我们可以检查决策树图形是否符合预期。
- 训练算法:这个过程也就是构造决策树,同样也可以说是决策树学习,就是构造一个决策树的数据结构。
- 测试算法:使用经验树计算错误率。当错误率达到了可接收范围,这个决策树就可以投放使用了。
- 使用算法:此步骤可以使用适用于任何监督学习算法,而使用决策树可以更好地理解数据的内在含义。
决策树构建的准备工作
使用决策树做预测的每一步骤都很重要,数据收集不到位,将会导致没有足够的特征让我们构建错误率低的决策树。数据特征充足,但是不知道用哪些特征好,将会导致无法构建出分类效果好的决策树模型。从算法方面看,决策树的构建是我们的核心内容。
决策树要如何构建呢?通常,这一过程可以概括为3个步骤:特征选择、决策树的生成和决策树的修剪。
1.特征选择
特征选择就是决定用哪个特征来划分特征空间,通常特征选择的标准是信息增益。
什么是信息增益呢?在划分数据集之前之后信息发生的变化成为信息增益,知道如何计算信息增益,我们就可以计算每个特征值划分数据集获得的信息增益,获得信息增益最高的特征就是最好的选择。
首先给出信息增益的算式,信息增益是相对于特征而言的,所以,特征A对训练数据集D的信息增益g(D,A)定义为集合D的经验熵H(D)与特征A给定条件下D的条件经验熵H(D|A)之差,即:g(D,A) = H(D) - H(D|A)。
1.1 熵、经验熵、条件熵
熵定义为信息的期望值。在信息论与概率论中,熵是表示随机变量不确定性的度量。如果待分类的事务可能划分在多个分类之中,则符号xi的信息定义为:
其中p(xi)是选择该分类的概率。通过上式,我们可以得到所有类别的信息。为了计算熵,我们需要计算所有类别所有可能值包含的信息期望值(数学期望),通过下面的公式得到:
其中n是分类的数目。熵越大,随机变量的不确定性就越大。
当熵中得到概率由数据估计(特别是最大似然估计)得到时,所对应的熵称为经验熵。
什么叫由数据估计?比如有10个数据,一共有两个类别,A类和B类。其中有7个数据属于A类,则该A类的概率即为十分之七。其中有3个数据属于B类,则该B类的概率即为十分之三。浅显的解释就是,这概率是我们根据数据数出来的。我们定义样本数据表中的数据为训练数据集D,则训练数据集D的经验熵为H(D),|D|表示其样本容量,及样本个数。设有K个类Ck,k = 1,2,3,···,K,|Ck|为属于类Ck的样本个数,这经验熵公式可以写为:
根据此公式计算经验熵H(D)。
条件熵H(Y|X)表示在已知随机变量X的条件下随机变量Y的不确定性,随机变量X给定条件下随机变量Y的条件熵H(Y|X),定义X给定条件下Y的条件概率分布的熵对X的数学期望:
其中
同理,当条件熵中的概率由数据估计(特别是极大似然估计)得到时,所对应的条件熵成为条件经验熵。
设特征A有n个不同的取值{a1,a2,…,an},根据特征A的取值将数据集D划分为n个子集D1,D2,…,Dn,|Di|为子集Di的样本个数。记子集Di中属于Ck(Ck是最终的类别)的样本的集合为Dik,即Dik = Di∩Ck,|Dik|为Dik的样本个数。于是条件熵的公式可以写为:
1.2 信息增益
在明确了经验熵、条件熵的概念之后,回到信息增益。前面说过,如何进行特征选择,需要看信息增益。也就是说信息增益是相对于特征而言的,信息增益越大,特征对最终的分类结果影响也就越大,我们就应该选择对最终分类结果影响最大的那个特征作为我们的分类特征。
所以,回到之前提到过的信息增益的定义与算式:
举例进行说明
1.3 举例与程序编写
以贷款申请样本数据表为例进行说明,如下表。
ID 年龄 有工作 有自己的房子 信贷情况 类别(是否个给贷款)
1 青年 否 否 一般 否
2 青年 否 否 好 否
3 青年 是 否 好 是
4 青年 是 是 一般 是
5 青年 否 否 一般 否
6 中年 否 否 一般 否
7 中年 否 否 好 否
8 中年 是 是 好 是
9 中年 否 是 非常好 是
10 中年 否 是 非常好 是
11 老年 否 是 非常好 是
12 老年 否 是 好 是
13 老年 是 否 好 是
14 老年 是 否 非常好 是
15 老年 否 否 一般 否
实现的目的:我们希望通过所给的训练数据学习一个贷款申请的决策树,用以对未来的贷款申请进行分类,即当新的客户提出贷款申请时,可以根据申请人的特征,利用决策树决定是否批准贷款申请。
首先我们对一个特征进行信息增益的计算:
首先计算年龄这个特征的信息增益,令年龄这个特征为A1。根据公式有
也就是经验熵与条件经验熵之差,首先根据提供的训练数据,算出训练集D的经验熵:
- 经验熵
使用Python3编写代码与计算经验熵:
在写代码之前,需要先对训练数据进行属性标注。
- 年龄:0代表青年,1代表中年,2代表老年;
- 有工作:0代表否,1代表是;
- 有自己的房子:0代表否,1代表是;
- 信贷情况:0代表一般,1代表好,2代表非常好;
- 类别(是否给贷款):no代表否,yes代表是。
确定这些之后,我们就可以创建数据集,并计算经验熵了,代码编写如下:
# -*- coding: UTF-8 -*-
from math import log
"""
函数说明:创建测试数据集
Parameters:
无
Returns:
dataSet - 数据集
labels - 分类属性
Author:
Jack Cui
Modify:
2017-07-20
"""
#创建训练数据
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#返回数据集和分类属性
"""
函数说明:计算给定数据集的经验熵(香农熵)
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] = 0
labelCounts[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(dataSet)
print(calcShannonEnt(dataSet))
-
经验熵计算完成之后,接下来计算条件经验熵(都是以特征值为年龄的条件下进行的):
以贷款申请样本数据表为例进行说明。看下年龄这一列的数据,也就是特征A1,一共有三个类别,分别是:青年、中年和老年。我们只看年龄是青年的数据,年龄是青年的数据一共有5个,所以年龄是青年的数据在训练数据集出现的概率是十五分之五,也就是三分之一。同理,年龄是中年和老年的数据在训练数据集出现的概率也都是三分之一。现在我们只看年龄是青年的数据的最终得到贷款的概率为五分之二,因为在五个数据中,只有两个数据显示拿到了最终的贷款,同理,年龄是中年和老年的数据最终得到贷款的概率分别为五分之三、五分之四。
具体计算过程如下图:计算结果为0.888
-
在计算完条件经验熵之后就可以算出信息增益
以特征值A1为例计算的信息增益直接可以从上得出,结果为0.971-0.888 = 0.083。
同理可以计算出其余特征的信息增益g(D,A2)、g(D,A3)和g(D,A4)。分别如下所示:
最后,从各个特征值的信息增益结果来看,特征A3(有自己的房子)的信息增益值最大,所以选择A3作为最优特征。
-
编写代码计算信息增益
在学会通过公式计算信息增益之后,就要开始编写代码了,计算信息增益,选择最优特征值。
代码如下:
# -*- coding: UTF-8 -*- from math import log """ 函数说明:计算给定数据集的经验熵(香农熵) 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] = 0 labelCounts[currentLabel] += 1 #Label计数 shannonEnt = 0.0 #经验熵(香农熵) for key in labelCounts: #计算香农熵 prob = float(labelCounts[key]) / numEntires #选择该标签(Label)的概率 shannonEnt -= prob * log(prob, 2) #利用公式计算 return shannonEnt #返回经验熵(香农熵) """ 函数说明:创建测试数据集 Parameters: 无 Returns: dataSet - 数据集 labels - 分类属性 Author: Jack Cui Modify: 2017-07-20 """ 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 #返回数据集和分类属性 """ 函数说明:按照给定特征划分数据集 Parameters: dataSet - 待划分的数据集 axis - 划分数据集的特征 value - 需要返回的特征的值 Returns: 无 Author: Jack Cui Modify: 2017-03-30 """ 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 - 信息增益最大的(最优)特征的索引值 Author: Jack Cui Modify: 2017-03-30 """ def chooseBestFeatureToSplit(dataSet): numFeatures = len(dataSet[0]) - 1 #特征数量 baseEntropy = calcShannonEnt(dataSet) #计算数据集的香农熵 bestInfoGain = 0.0 #信息增益 bestFeature = -1 #最优特征的索引值 for i in range(numFeatures): #遍历所有特征 #获取dataSet的第i个所有特征存到featList中 featList = [example[i] for example in dataSet] #print(featList)#每个特征的15项特征值列表 uniqueVals = set(featList) #创建set集合{},元素不可重复 #print(uniqueVals)#去除重复项 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 #返回信息增益最大的特征的索引值 if __name__ == '__main__': dataSet, features = createDataSet() print("最优特征索引值:" + str(chooseBestFeatureToSplit(dataSet)))
测试结果如下:
第0个特征的增益为0.083
第1个特征的增益为0.324
第2个特征的增益为0.420
第3个特征的增益为0.363
最优特征索引值:2