机器学习(MACHINE LEARNING) 【周志华版-”西瓜书“-笔记】 DAY4-决策树

在这里插入图片描述
4.1 基本流程

决策树(decision)是一类常见的机器学习算法,以二分类任务为例,我们希望从给定训练数据集学得一个模型用以对新示例进行分类,这个把样本分类的任务,可看做对“当前样本属于正类吗”这个问题的“决策”或“判定”过程。顾名思义,决策树是基于树结构进行决策的,这恰是人类在面临决策问腿时一种很自然的处理机制。
在这里插入图片描述
python实现决策树分类


from math import log
import operator

'''******************************python实现决策树******************************'''
'''加载数据集'''
def loadData():
    dataSet = [[1, 1, 'yes'],
               [1, 1, 'yes'],
               [1, 0, 'no'],
               [0, 1, 'no'],
               [0, 1, 'no']]
    labels = ['no surfacing', 'flippers']
    return dataSet, labels
'''计算数据集的熵,熵越小,分类效果越好'''
def calShang(data):
    num=len(data)
    #特征的字典,键值为字典,值为特征个数
    classCount={}
    for feat in data:
        featVec=feat[-1]
        #若字典中不存在此键值,则创建
        if featVec not in classCount.keys():
            classCount[featVec]=0
        classCount[featVec]+=1
    Shang=0.0
    for key in classCount:
        prob=classCount[key]/float(num)
        Shang-=prob*log(prob,2)
    return Shang
'''分割数据集,根据特征值value分割'''
def splitData(data,axis,value):
    retData=[]
    for dataList in data:
        if dataList[axis]==value:
            newData=dataList[:axis]
            newData.extend(dataList[axis+1:])
            retData.append(newData)
    return retData
'''根据数据集的熵值大小,寻找最优的数据集划分方式'''
def findOptSplit(data):
    #特征变量的个数
    numFeat=len(data[0])-1
    #先将原始数据集的熵值作为最小的熵值
    bestShang=calShang(data)
    for i in range(numFeat):
        Shang=0.0
        featList=[feat[i] for feat in data]
        featSet=set(featList)
        for value in featSet:
            splData=splitData(data,i,value)
            prob=len(splData)/float(len(data))
            Shang+=prob*calShang(splData)
        if Shang<bestShang:
            bestShang=Shang
            bestFeat=i
    return bestFeat
'''获取出现次数最多的类别名称'''
def majorityCnt(classList):
    classCount={}
    for vote in classList:
        if vote not in classCount.keys():
            classCount=0
        classCount[vote]+=1
    sortedClassCount=sorted(classCount.iteritems(),key=operator.itemgetter(1),reverse=True)
    return sortedClassCount
'''创建树'''
def createTree(dataSet,labels):
    classList=[example[-1] for example in dataSet]
    #如果类别相同,则返回当前类别
    if classList.count(classList[0])==len(classList):
        return classList[0]
    #如果只存在一个数据,则返回当前最多的类别
    if len(dataSet[0])==1:
        return majorityCnt(classList)
    #寻找最好的数据集划分方式
    bestFeat=findOptSplit(dataSet)
    bestFeatLbel=labels[bestFeat]
    myTree={bestFeatLbel:{}}
    #删除当前特征
    del(labels[bestFeat])
    featValues=[example[bestFeat] for example in dataSet]
    uniqueVals=set(featValues)
    for value in uniqueVals:
        subLabels=labels[:]
        myTree[bestFeatLbel][value]=createTree(splitData(dataSet,bestFeat,value),subLabels)
    return myTree

4.2 划分选择

如何选择最优划分属性,一般而言,随着划分过程不断进行,我们希望决策树的分支节点所办含的样本尽可能属于同一类别,既节点的纯度(purity)越来越高。

这里涉及到信息增益和基尼指数,后面有介绍。
4.3 剪枝处理
就好象算法里的剪枝处理。
4.4 连续与缺失值

4.4.1
连续值处理
到目前为止,我们仅仅讨论了基于离散属性生成决策树,现实任务中常会遇到连续属性,有必要讨论在决策树学习中使用连续属性。
4.4.2
缺失值处理
现实任务中,常会遇到不完整样本,既样本的属性值缺失,例如由于诊测成本,隐私保护等因素,
患者的医疗数据在某些属性上的取值(如HIV测试结果)未知,

4.5 多变量决策树

与传统的单变量决策树不用,在多变量决策树学习过程中,不是为没一个非叶节点找到一个最优有划分属性,
而是试图建立一个合适的线性分类器,
可以对西瓜数据集,3.0阿发进行处理,找到多变量决策树对应的分类边界。

以上4个方面很重要,后续会写详细一点,不在这里介绍了哈。
信息增益
划分数据集的大原则是:将无序数据变得更加有序,但是各种方法都有各自的优缺点,信息论是量化处理信息的分支科学,在划分数据集前后信息发生的变化称为信息增益,获得信息增益最高的特征就是最好的选择,所以必须先学习如何计算信息增益,集合信息的度量方式称为香农熵,或者简称熵。

希望通过所给的训练数据学习一个贷款申请的决策树,用以对未来的贷款申请进行分类,即当新的客户提出贷款申请时,根据申请人的特征利用决策树决定是否批准贷款申请。

特征选择就是决定用哪个特征来划分特征空间。比如,我们通过上述数据表得到两个可能的决策树,分别由两个不同特征的根结点构成
在这里插入图片描述

图(a)所示的根结点的特征是年龄,有3个取值,对应于不同的取值有不同的子结点。图(b)所示的根节点的特征是工作,有2个取值,对应于不同的取值有不同的子结点。两个决策树都可以从此延续下去。问题是:究竟选择哪个特征更好些?这就要求确定选择特征的准则。直观上,如果一个特征具有更好的分类能力,或者说,按照这一特征将训练数据集分割成子集,使得各个子集在当前条件下有最好的分类,那么就更应该选择这个特征。信息增益就能够很好地表示这一直观的准则。

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

熵定义为信息的期望值,如果待分类的事物可能划分在多个类之中,则符号xix_ix
i

的信息定义为:
l(xi)=−log2p(xi)l(x_i)=-log_2 p(x_i)
l(x
i

)=−log
2

p(x
i

)

其中,p(xi)p(x_i)p(x
i

)是选择该分类的概率。

为了计算熵,我们需要计算所有类别所有可能值所包含的信息期望值,通过下式得到:
H=−Σni=1p(xi)log2p(xi)H=-\Sigma_{i=1}^n p(x_i)log_2 p(x_i)
H=−Σ
i=1
n

p(x
i

)log
2

p(x
i

)

其中,nnn为分类数目,熵越大,随机变量的不确定性就越大。

当熵中的概率由数据估计(特别是最大似然估计)得到时,所对应的熵称为经验熵(empirical entropy)。什么叫由数据估计?比如有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)=−Σ∣ck∣∣D∣log2∣ck∣∣D∣H(D)=-\Sigma \frac{|c_k|}{|D|}log_2\frac{|c_k|}{|D|}
H(D)=−Σ
∣D∣
∣c
k



log
2

∣D∣
∣c
k


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

log
2

15
9


15
6

log
2

15
6

=0.971

经过计算可知,数据集D的经验熵H(D)的值为0.971。

在理解信息增益之前,要明确——条件熵

信息增益表示得知特征X的信息而使得类Y的信息不确定性减少的程度。

条件熵H(Y∣X)H(Y|X)H(Y∣X)表示在已知随机变量X的条件下随机变量Y的不确定性,随机变量X给定的条件下随机变量Y的条件熵(conditional entropy) H(Y|X),定义X给定条件下Y的条件概率分布的熵对X的数学期望:
H(Y∣X)=∑ni=1piH(Y∣X=xi)H(Y|X)=\sum_{i=1}^n p_iH(Y|X=x_i)
H(Y∣X)=
i=1

n

p
i

H(Y∣X=x
i

)

其中,pi=P(X=xi)p_i=P(X=x_i)p
i

=P(X=x
i

)

当熵和条件熵中的概率由数据估计(特别是极大似然估计)得到时,所对应的分别为经验熵和经验条件熵,此时如果有0概率,令0log0=00log0=00log0=0

信息增益:信息增益是相对于特征而言的。所以,特征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)。决策树学习中的信息增益等价于训练数据集中类与特征的互信息。

信息增益值的大小相对于训练数据集而言的,并没有绝对意义,在分类问题困难时,也就是说在训练数据集经验熵大的时候,信息增益值会偏大,反之信息增益值会偏小,使用信息增益比可以对这个问题进行校正,这是特征选择的另一个标准。

信息增益比:特征AAA对训练数据集D的信息增益比gR(D,A)g_R(D,A)g
R

(D,A)定义为其信息增益g(D,A)g(D,A)g(D,A)与训练数据集DDD的经验熵之比:

gR(D,A)=g(D,A)H(D)g_R(D,A)=\frac{g(D,A)}{H(D)}
g
R

(D,A)=
H(D)
g(D,A)

3.1.2 编写代码计算经验熵
在这里插入图片描述
在编写代码之前,我们先对数据集进行属性标注。

年龄:0代表青年,1代表中年,2代表老年;
有工作:0代表否,1代表是;
有自己的房子:0代表否,1代表是;
信贷情况:0代表一般,1代表好,2代表非常好;
类别(是否给贷款):no代表否,yes代表是。
创建数据集,计算经验熵的代码如下:

from math import log

"""
函数说明:创建测试数据集
Parameters:无
Returns:
    dataSet:数据集
    labels:分类属性
Modify:
    2018-03-12

"""
def creatDataSet():
    # 数据集
    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:经验熵
Modify:
    2018-03-12

"""
def calcShannonEnt(dataSet):
    #返回数据集行数
    numEntries=len(dataSet)
    #保存每个标签(label)出现次数的字典
    labelCounts={}
    #对每组特征向量进行统计
    for featVec in dataSet:
        currentLabel=featVec[-1]                     #提取标签信息
        if currentLabel not in labelCounts.keys():   #如果标签没有放入统计次数的字典,添加进去
            labelCounts[currentLabel]=0
        labelCounts[currentLabel]+=1                 #label计数

    shannonEnt=0.0                                   #经验熵
    #计算经验熵
    for key in labelCounts:
        prob=float(labelCounts[key])/numEntries      #选择该标签的概率
        shannonEnt-=prob*log(prob,2)                 #利用公式计算
    return shannonEnt                                #返回经验熵

#main函数
if __name__=='__main__':
    dataSet,features=creatDataSet()
    print(dataSet)
    print(calcShannonEnt(dataSet))

结果:

0个特征的增益为0.0831个特征的增益为0.3242个特征的增益为0.4203个特征的增益为0.3630个特征的增益为0.2521个特征的增益为0.9182个特征的增益为0.474
{'有自己的房子': {0: {'有工作': {0: 'no', 1: 'yes'}}, 1: 'yes'}}


3.1.4利用代码计算信息增益


from math import log

"""
函数说明:创建测试数据集
Parameters:无
Returns:
    dataSet:数据集
    labels:分类属性
Modify:
    2018-03-12

"""
def creatDataSet():
    # 数据集
    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:经验熵
Modify:
    2018-03-12

"""
def calcShannonEnt(dataSet):
    #返回数据集行数
    numEntries=len(dataSet)
    #保存每个标签(label)出现次数的字典
    labelCounts={}
    #对每组特征向量进行统计
    for featVec in dataSet:
        currentLabel=featVec[-1]                     #提取标签信息
        if currentLabel not in labelCounts.keys():   #如果标签没有放入统计次数的字典,添加进去
            labelCounts[currentLabel]=0
        labelCounts[currentLabel]+=1                 #label计数

    shannonEnt=0.0                                   #经验熵
    #计算经验熵
    for key in labelCounts:
        prob=float(labelCounts[key])/numEntries      #选择该标签的概率
        shannonEnt-=prob*log(prob,2)                 #利用公式计算
    return shannonEnt                                #返回经验熵


"""
函数说明:计算给定数据集的经验熵(香农熵)
Parameters:
    dataSet:数据集
Returns:
    shannonEnt:信息增益最大特征的索引值
Modify:
    2018-03-12

"""


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]
        #创建set集合{},元素不可重复
        uniqueVals = set(featList)
        #经验条件熵
        newEntropy = 0.0
        #计算信息增益
        for value in uniqueVals:
            #subDataSet划分后的子集
            subDataSet = splitDataSet(dataSet, i, value)
            #计算子集的概率
            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

"""
函数说明:按照给定特征划分数据集
Parameters:
    dataSet:待划分的数据集
    axis:划分数据集的特征
    value:需要返回的特征的值
Returns:
    shannonEnt:经验熵
Modify:
    2018-03-12

"""
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


#main函数
if __name__=='__main__':
    dataSet,features=creatDataSet()
    # print(dataSet)
    # print(calcShannonEnt(dataSet))
    print("最优索引值:"+str(chooseBestFeatureToSplit(dataSet)))


结果:

0个特征的增益为0.0831个特征的增益为0.3242个特征的增益为0.4203个特征的增益为0.363
最优索引值:2


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

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

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

3.2.1 决策树的构建

  1. ID3算法
    ID3算法的核心是在决策树各个结点上对应信息增益准则选择特征,递归地构建决策树。

具体方法是:

1)从根结点(root node)开始,对结点计算所有可能的特征的信息增益,选择信息增益最大的特征作为结点的特征。

2)由该特征的不同取值建立子节点,再对子结点递归地调用以上方法,构建决策树;直到所有特征的信息增益均很小或没有特征可以选择为止;

3)最后得到一个决策树。

ID3相当于用极大似然法进行概率模型的选择

发布了545 篇原创文章 · 获赞 129 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/weixin_43838785/article/details/104219337