《机器学习实战》个人学习记录笔记(四)———决策树

第三章 决策树

PS:个人笔记 根据《机器学习实战》这本书,Jack-Cui的博客,以及深度眸的视频进行学习

1 决策树原理

决策树(decision tree)是一种基本的分类与回归方法。决策树模型呈树形结构,在分类问题中,表示基于特征对实例进行分类的过程。它可以认为是if-then规则的集合,也可以认为是定义在特征空间与类空间上的条件概率分布。


2 优缺点及适用数据范围

优点:

计算复杂度不高,输出结果易于理解 (每次都是基于某一列特征来算,在算下一次递归后,就不考虑该列特征了。并且通过剪枝使的计算不复杂。结果是通过树形结果显示,很清楚就能分别。)

对中间值的缺失不敏感

可以处理不相关特征数据(因为是基于特征列来算的,不考虑列与列之间的关系)

缺点:

可能会产生过度匹配问题(就是过拟合了)

适用数据类型:

数值型和标称型


3 集合信息的度量方式 ⭐  

香农熵或者简称为熵(entropy),这个名字来源于信息论之父克劳德·香农

熵定义为信息的期望值。在信息论与概率统计中,熵是表示随机变量不确定性的度量。如果待分类的事务可能划分在多个分类之中,则符号xi的信息定义

  ,     其中p(xi)是选择该分类的概率。

通过上式,我们可以得到所有类别的信息。为了计算熵,我们需要计算所有类别所有可能值包含的信息期望值(数学期望),通过下面的公式得到:             


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

当熵中的概率由数据估计(特别是最大似然估计)得到时,所对应的熵称为经验熵(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),分析贷款申请样本数据表中的数据。最终分类结果只有两类,即放贷和不放贷。根据表中的数据统计可知,在15个数据中,9个数据的结果为放贷,6个数据的结果为不放贷。所以数据集D的经验熵H(D)为:

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

PS:总结就是对于类别Xi,Xi的信息是-log2P(Xi),然后熵就是sum{-P(Xi)log2P(Xi)},最后经验熵就是把其中的概率带入。

信息熵用于度量信息的混乱程度,越混乱则熵越大,反之亦然。

3.1编写代码计算经验熵

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                #返回数据集和分类属性

"""
Parameters:
    dataSet - 数据集
Returns:
    shannonEnt - 经验熵(香农熵)
"""
def calcShannonEnt(dataSet):                        #计算给定数据集的经验熵(香农熵)
    numEntires = len(dataSet)                        #返回数据集的个数;这里是15个
    labelCounts = {}                                #保存每个标签(Label)出现次数的字典;就是贷款与否
    for featVec in dataSet:                            #对每组特征向量进行统计
        currentLabel = featVec[-1]                    #提取标签(Label)信息,这里就是最后一个信息贷款与否
        if currentLabel not in labelCounts.keys():    #如果标签(Label)没有放入统计次数的字典,添加进去
            labelCounts[currentLabel] = 0             #因为刚开始字典里面没有,所以这一步是建立键Key
        labelCounts[currentLabel] += 1                #Label计数
    shannonEnt = 0.0                                #经验熵(香农熵)
    for key in labelCounts:                            #计算香农熵
        prob = float(labelCounts[key]) / numEntires    #选择该标签(Label)的概率;就是先[0]/15,后[1]/15
        shannonEnt -= prob * log(prob, 2)            #利用公式计算;熵的公式
    return shannonEnt                                #返回经验熵(香农熵)

if __name__ == '__main__':
    dataSet, features = createDataSet()
    print(dataSet)
    print(calcShannonEnt(dataSet))                 #这里算的是最后贷款与否的经验熵


4 信息增益

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

条件熵H(Y|X)表示在已知随机变量X的条件下随机变量Y的不确定性,随机变量X给定的条件下随机变量Y的条件熵(conditional entropy) H(Y|X),定义X给定条件下Y的条件概率分布的熵对X的数学期望:

这里,                                                    

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

特征A对训练数据集D的信息增益g(D,A),定义为集合D的经验熵H(D)与特征A给定条件下D的经验条件熵H(D|A)之差,即


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

设特征A有n个不同的取值{a1,a2,···,an},根据特征A的取值将D划分为n个子集D1,D2,···,Dn,|Di|为Di的样本个数。记子集Di中属于Ck的样本的集合为Dik,即Dik = Di ∩ Ck,|Dik|为Dik的样本个数。于是经验条件熵的公式可以些为:


下面这个例子超级好理解信息增益

以贷款申请样本数据表为例进行说明。看下年龄这一列的数据,也就是特征A1,一共有三个类别,分别是:青年、中年和老年。我们只看年龄是青年的数据,年龄是青年的数据一共有5个,所以年龄是青年的数据在训练数据集出现的概率是十五分之五,也就是三分之一。同理,年龄是中年和老年的数据在训练数据集出现的概率也都是三分之一。现在我们只看年龄是青年的数据的最终得到贷款的概率为五分之二,因为在五个数据中,只有两个数据显示拿到了最终的贷款,同理,年龄是中年和老年的数据最终得到贷款的概率分别为五分之三、五分之四。所以计算年龄的信息增益,过程如下:

 同理,计算其余特征的信息增益g(D,A2)、g(D,A3)和g(D,A4)。分别为:

 最后,比较特征的信息增益,由于特征A3(有自己的房子)的信息增益值最大,所以选择A3作为最优特征。

PS:总结就是先选出信息增益最大的那个特征,通过条件经验熵把一个特征对于分类的信息增益算出来就可以了。


4.1 编写代码计算信息增益

from math import log

def calcShannonEnt(dataSet):
    numEntires = len(dataSet)                      
    labelCounts = {}                           
    for featVec in dataSet:                         
        currentLabel = featVec[-1]                   
        if currentLabel not in labelCounts.keys():   
            labelCounts[currentLabel] = 0
        labelCounts[currentLabel] += 1             
    shannonEnt = 0.0                              
    for key in labelCounts:                           
        prob = float(labelCounts[key]) / numEntires    
        shannonEnt -= prob * log(prob, 2)          
    return shannonEnt                              

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:
    无
"""
def splitDataSet(dataSet, axis, value):                  #axis划分数据集的特征  这里value从0开始
    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                        #特征数量,就是第一个样本的长度5-1=4,
    baseEntropy = calcShannonEnt(dataSet)                    #计算数据集的香农熵,就是计算贷款与否那一列的熵
    bestInfoGain = 0.0                                       #信息增益
    bestFeature = -1                                         #最优特征的索引值
    for i in range(numFeatures):                             #遍历所有特征 i从0开始到3
        featList = [example[i] for example in dataSet]       #获取dataSet的第i个所有特征,比如第0列的特征
        uniqueVals = set(featList)                           #创建set集合{},这里就是去重,得到第0列特征的分类(0,1,2)
        newEntropy = 0.0                                     #经验条件熵
        for value in uniqueVals:                             #计算信息增益  这里就是for value in (0,1,2):  所以value从0开始
            subDataSet = splitDataSet(dataSet, i, value)     #subDataSet划分后的子集  这里i=0;value=0
            prob = len(subDataSet) / float(len(dataSet))     #计算子集的概率   这里划分后的青年5个/总的15个
            newEntropy += prob * calcShannonEnt(subDataSet)  #根据公式计算经验条件熵  然后遍历中年,老年的情况,最后加总
        infoGain = baseEntropy - newEntropy                  #信息增益  这里就是H(D)-H(D丨A),也就是特征[0]的信息增益了
        print("第%d个特征的增益为%.3f" % (i, infoGain))        #打印每个特征的信息增益,
        if (infoGain > bestInfoGain):                        #信息增益的比较
            bestInfoGain = infoGain                          #更新信息增益,找到最大的信息增益
            bestFeature = i                                  #记录信息增益最大的特征的索引值
    return bestFeature                                       #返回信息增益最大的特征的索引值

if __name__ == '__main__':
    dataSet, features = createDataSet()
    print("最优特征索引值:" + str(chooseBestFeatureToSplit(dataSet)))


这张图便于理解splitDataSet()这个函数。其工作原理如下:得到原始数据集,然后基于最好的属性值划分数据集,由于特征值可能多于两个,因此可能存在大于两个分支的数据集划分。第一次划分之后,数据集被向下传递到树的分支的下一个结点。在这个结点上,我们可以再次划分数据。因此我们可以采用递归的原则处理数据集。


阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页