机器学习——03决策树算法

时间:2022-12-20 18:00:30

机器学习——03决策树算法

参考资料

  1. AIlearning
  2. Machine-Learning-in-Action
  3. 庞善民.西安交通大学机器学习导论2022春PPT

具体算法的原理步骤请参考本人的另一篇博文:[机器学习导论]—— 第四课——决策树

一、信息熵与信息增益

????信息熵

信息熵使信息得以量化

1948年,香农(ClaudeShannon)在他著名的论文“通信的数学原理”中指出:“信息是用来消除随机不确定性的东西”,并提出了“信息熵”的概念(借用了热力学中熵的概念),来解决信息的度量问题。

一条信息的信息量和它的不确定性有着直接的关系

比如,要搞清楚一件非常不确定的事,或是我们一无所知的事情,就需要了解大量信息。相反,如果我们对某件事已经有了较多了解,那么不需要太多信息就能把它搞清楚

信息熵是消除不确定性所需信息量的度量,也即未知事件可能含有的信息量。需要引入消除不确定性的信息量越多,则信息熵越高,反之则越低。

例如“中国男足进军2022年世界杯决赛圈”,这个因为确定性很高,几乎不需要引入信息,因此信息熵很低。

信息熵的计算

Shannon定义的信息熵的计算公式如下:

机器学习——03决策树算法

其中X表示随机变量,随机变量的取值为{????1,????2,…,????????},????(????????)表示事件????????发生的概率,且有 ∑ ???? ( ???? ???? ) = 1 \sum????(????????)=1 P(xi)=1信息熵的单位为比特(bit)

熵越小表示概率分布的纯度越高,反之,熵越大表示概率分布的纯度越低。

数据集的信息熵

设数据集D中有m个不同的类C1,C2,C3,…,Cm

设Di是数据集D中Ci类的样本的集合,|D|和|Di|分别是D和Di中的样本个数

数据集D的信息熵
I n f o ( D ) = − ∑ i = 1 m p i log ⁡ 2 p i Info(D)=-\sum^m_{i=1}p_i\log_2p_i Info(D)=i=1mpilog2pi
其中????????是数据集D中任意样本属于类Ci的概率,用 ∣ D i ∣ ∣ D ∣ \frac{|D_i|}{|D|} DDi估计

使用熵衡量数据纯度

假设有一个数据集合D,其中只有两个类,一个是正例类,一个是负例类。计算D中正例类和负例类在三种不同的组分下熵的变化情况。
(1)D中包含有50%的正例和50%的负例。
I n f o ( D ) = − 0.5 ∗ log ⁡ 2 0.5 − 0.5 ∗ log ⁡ 2 0.5 = 1 Info(D) = -0.5 * \log_20.5 - 0.5 * \log_20.5 = 1 Info(D)=0.5log20.50.5log20.5=1
(2)D中包含有20%的正例和80%的负例。
I n f o ( D ) = − 0.2 ∗ log ⁡ 2 0.2 − 0.8 ∗ log ⁡ 2 0.8 = 0.722 Info(D) = -0.2 * \log_20.2 - 0.8 * \log_20.8 = 0.722 Info(D)=0.2log20.20.8log20.8=0.722
(3)D中包含有100%的正例和0%的负例。
I n f o ( D ) = − 1 ∗ log ⁡ 2 1 − 0 ∗ log ⁡ 2 0 = 0 Info(D) = -1 * \log_21 - 0 * \log_20 =0 Info(D)=1log210log20=0
当数据变得越来越“纯”时,熵的值变得越来越小:当D中正反例比例相同时,熵取最大值;当D中所有数据属于一个类时,熵取最小值。因此,熵可以作为数据纯净度的衡量指标。

信息增益

信息增益可以衡量划分数据集前后数据纯度提升程度。信息增益=原数据信息熵−数据划分之后的信息熵

机器学习——03决策树算法

其中,离散属性????有????个可能的取值{????1,????2,…,????????},其中第????个分支节点包含了????中所有在属性????上取值为 ???? ???? ????^???? ak的样本,记为 ???? ???? ????^???? Dk

机器学习——03决策树算法

二、决策树简介

决策树(Decision Tree)是一种基于树结构的分类预测方法

决策树引入

决策树(Decision Tree)又称为判定树,是用于分类的一种树结构。其中每个内部结点(internal node)代表对某个属性的一次测试,叶结点(leaf)代表某个(class)或者类的分布(class distribution),最上面的结点是根结点

决策树提供了一种展示在什么条件下会得到什么类别的方法。

决策树组成

决策树的基本组成部分:根节点、决策结点、分枝和叶子结点。树是由节点和分枝组成的层次数据结构。

机器学习——03决策树算法

决策树是描述分类过程的一种数据结构,从上端的根节点开始,各种分类原则被引用进来,并依这些分类原则将根节点的数据集划分为子集,这一划分过程直到某种约束条件满足而结束。

决策树基本原理

首先对数据进行处理,利用归纳法生成可读的规则和决策树,然后使用决策对新数据进行分析。

本质上决策树是通过一系列规则对数据进行分类的过程。

决策树技术发现数据模式和规则的核心是采用递归分割的贪婪算法

使用 createBranch() 方法,如下所示:

def createBranch():
    检测数据集中的所有数据的分类标签是否相同:
        If so return 类标签
        Else:
            寻找划分数据集的最好特征(划分之后信息熵最小,也就是信息增益最大的特征)
            划分数据集
            创建分支节点
                for 每个划分的子集
                    调用函数 createBranch (创建分支的函数)并增加返回结果到分支节点中
            return 分支节点

决策树基本流程

1️⃣收集待分类的数据,这些数据的所有属性应该是完全标注的。

2️⃣设计分类原则,即数据的哪些属性可以被用来分类,以及如何将该属性量化。

3️⃣分类原则的选择,即在众多分类准则中,每一步选择哪一准则使最终的树更令人满意。

4️⃣设计分类停止条件,实际应用中数据的属性很多,真正有分类意义的属性往往是有限几个,因此在必要的时候应该停止数据集分裂:

该节点包含的数据太少不足以分裂,

继续分裂数据集对树生成的目标(例如ID3中的熵下降准则)没有贡献,

树的深度过大不宜再分。

决策树开发流程

  • 收集数据: 可以使用任何方法。
  • 准备数据: 树构造算法 (下面案例使用的是ID3算法,只适用于标称型数据,这就是为什么数值型数据必须离散化。 还有其他的树构造算法,比如CART) ,详情参考置顶博客。
  • 分析数据: 可以使用任何方法,构造树完成之后,我们应该检查图形是否符合预期。
  • 训练算法: 构造树的数据结构。
  • 测试算法: 使用训练好的树计算错误率。
  • 使用算法: 此步骤可以适用于任何监督学习任务,而使用决策树可以更好地理解数据的内在含义。

决策树算法特点

优点: 计算复杂度不高,输出结果易于理解,数据有缺失也能跑,可以处理不相关特征。

缺点: 容易过拟合。 适用数据类型: 数值型和标称型。

标称型:标称型目标变量的结果只在有限目标集中取值,如真与假(标称型目标变量主要用于分类)

数值型:数值型目标变量则可以从无限的数值集合中取值,如0.100,42.001等 (数值型目标变量主要用于回归分析)

三、决策树项目案例——判断鱼类和非鱼类

ID3算法原理

ID3算法在决策树各个节点上使用信息增益准则选择特征(属性)进行数据划分,从而递归地构建决策树。

具体方法

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

2️⃣由该特征的不同取值建立子节点

3️⃣再对子节点递归的调用以上方法,构建决策树,直到所有特征的信息增益均很小或没有特征可以选择为止,最后得到一个决策树。

选择划分属性示例

以买电脑为例进行决策树划分说明

年龄 收入 学生 信用 买了电脑
<30 一般
<30
30-40 一般
>40 中等 一般
>40 一般
>40
30-40
<30 一般
<30 一般
>40 一般
<30
30-40
30-40 一般
>40

1️⃣ 确立初始的信息熵

|D|=14,|D1|=5,|D2|=9,即不买的有5个人,买的有9个人

信息熵如下:
I n f o ( D ) = − 5 14 log ⁡ 2 5 14 − 9 14 log ⁡ 2 9 14 = 0.940 Info(D)=-\frac{5}{14}\log_2\frac{5}{14}-\frac{9}{14}\log_2\frac{9}{14}=0.940 Info(D)=145log2145149log2149=0.940

2️⃣ 确立第一次分裂的属性

???? 如果按照年龄划分

年龄<30的有5个,其中3个为“否”

年龄30-40的有4个,其中0个为“否”

年龄>40的有5个,其中2个为“否”

机器学习——03决策树算法

???? 如果按照收入划分

收入=高的有4个,其中2个为“否”

收入=中的有6个,其中2个为“否”

收入=低的有4个,其中1个为“否”

机器学习——03决策树算法

???? 如果按照学生划分

是学生的有7个,其中1个为“否”

不是学生的有7个,其中4个为“否”

机器学习——03决策树算法

???? 如果按照信用划分

信用好的有6个,其中3个为“否”

信用一般的有8个,其中2个为“否”

机器学习——03决策树算法

综上,“年龄”属性具有最高信息增益,成为分裂属性

3️⃣ 确立第二次分裂的属性

机器学习——03决策树算法

按照上述方法,可以确定第二次分裂的属性为学生

4️⃣ 划分到不可划分为止

ID3算法总结

算法流程

自上而下贪婪搜索

遍历所有的属性,按照信息增益最大的属性进行分裂

根据分裂属性划分样本

重复上述流程,直至满足条件结束

优点

分类过程和领域知识无关,几乎所有模式和场景都可以得到结果

缺点

ID3算法倾向于选择属性值较多的属性,有些时候不能提供有价值的信息

不适用于连续变量

只能用于分类

一次只用一个特征值进行划分

在样本量较小时,可能会导致过度分类

属性值缺失的情况无法处理

项目概述

根据以下 2 个特征,将动物分成两类: 鱼类和非鱼类。

特征:

  1. 不浮出水面是否可以生存
  2. 是否有脚蹼

开发流程

收集数据

不浮出水面是否可以生存? 是否有脚蹼? 是????吗?
Yes Yes Yes
Yes Yes Yes
Yes No No
No Yes No
No Yes No

使用createDataSet()函数输入数据

def createDataSet():
    dataSet = [[1, 1, '是????'], [1, 1, '是????'], [1, 0, '不是????'], [0, 1, '不是????'],
               [0, 1, '不是????']]
    labels = ['不浮出水面是否可以生存', '是否有脚蹼']
    return dataSet, labels

准备数据

此处,由于我们输入的数据本身就是离散化数据,所以这一步就省略了。

分析数据

使用如下公式计算计算给定数据集的信息熵:
I n f o ( D ) = − ∑ i = 1 m p i log ⁡ 2 p i Info(D)=-\sum^m_{i=1}p_i\log_2p_i Info(D)=i=1mpilog2pi

def calInfoEntropy(dataSet):
    """计算信息熵  
    Args:
        dataSet 数据集
    Returns:
        返回 每一组feature下的某个分类下的信息熵
    """    
    # 统计标签出现的次数
    label_count = Counter(data[-1] for data in dataSet)
    # 计算概率
    probs = [p[1] / len(dataSet) for p in label_count.items()]
    # 计算香农熵
    infoEntropy = sum([-p * log(p, 2) for p in probs])
    return infoEntropy   

按照给定特征划分数据集

def splitDataSet(dataSet, index, value):
    """就是依据index列进行分类,如果index列的数据等于 value的时候,就要将 index 划分到我们创建的新的数据集中
    Args:
        dataSet 数据集                 待划分的数据集
        index 表示每一行的index列        划分数据集的特征
        value 表示index列对应的value值   需要返回的特征的值。
    Returns:
        index列为value的数据集【该数据集需要排除index列】
    """
    retDataSet = []
    for featVec in dataSet:
        # index列为value的数据集【该数据集需要排除index列】
        # 判断index列的值是否为value
        if featVec[index] == value:
            reducedFeatVec = featVec[:index]
            reducedFeatVec.extend(featVec[index+1:])
            # 收集结果值 index列为value的行【该行需要排除index列】
            retDataSet.append(reducedFeatVec)
    return retDataSet

选择最好的数据集划分方式:具有最高信息增益

def chooseBestFeatureToSplit(dataSet):
    """chooseBestFeatureToSplit(选择最好的特征)
    Args:
        dataSet 数据集
    Returns:
        bestFeature 最优的特征列
    """
    # 求特征数
    numFeatures = len(dataSet[0]) - 1
    # 数据集的原始信息熵
    baseEntropy = calInfoEntropy(dataSet)
    # 最优的信息增益值, 和最优的Featurn编号
    bestInfoGain, bestFeature = 0.0, -1
    # 遍历所有的特征
    for i in range(numFeatures):
        # 获取对应的feature下的所有数据
        featList = [example[i] for example in dataSet]
        # 获取剔重后的集合,使用set对list数据进行去重
        uniqueVals = set(featList)
        # 创建一个临时的信息熵
        newEntropy = 0.0
        # 遍历某一列的value集合,计算该列的信息熵
        # 遍历当前特征中的所有唯一属性值,对每个唯一属性值划分一次数据集
        # 计算数据集的新熵值,并对所有唯一特征值得到的熵求和。
        for value in uniqueVals:
            subDataSet = splitDataSet(dataSet, i, value)
            # 计算概率
            prob = len(subDataSet)/float(len(dataSet))
            # 计算信息熵
            newEntropy += prob * calInfoEntropy(subDataSet)。
        infoGain = baseEntropy - newEntropy
        print('infoGain=', infoGain, 'bestFeature=', i, baseEntropy, newEntropy)
        if (infoGain > bestInfoGain):
            bestInfoGain = infoGain
            bestFeature = i
    return bestFeature

训练算法

创建树的函数代码如下:

def majorityCnt(classList):
    """majorityCnt(选择出现次数最多的一个结果)
    Args:
        classList label列的集合
    Returns:
        bestFeature 最优的特征列
    """
    major_label = Counter(classList).most_common(1)[0]
    return major_label
    
def createTree(dataSet, labels):
    classList = [example[-1] for example in dataSet]
    # 如果数据集的最后一列的第一个值出现的次数=整个集合的数量,也就说只有一个类别,就只直接返回结果就行
    # 第一个停止条件: 所有的类标签完全相同,则直接返回该类标签。
    # count() 函数是统计括号中的值在list中出现的次数
    if classList.count(classList[0]) == len(classList):
        return classList[0]
    # 如果数据集只有1列,那么最初出现label次数最多的一类,作为结果
    # 第二个停止条件: 使用完了所有特征,仍然不能将数据集划分成仅包含唯一类别的分组。
    if len(dataSet[0]) == 1:
        return majorityCnt(classList)

    # 选择最优的列,得到最优列对应的label含义
    bestFeat = chooseBestFeatureToSplit(dataSet)
    # 获取label的名称
    bestFeatLabel = labels[bestFeat]
    # 初始化myTree
    myTree = {bestFeatLabel: {}}
    # 注: labels列表是可变对象,在PYTHON函数中作为参数时传址引用,能够被全局修改
    # 所以这行代码导致函数外的同名变量被删除了元素,造成例句无法执行,提示'no surfacing' is not in list
    del(labels[bestFeat])
    # 取出最优列,然后它的branch做分类
    featValues = [example[bestFeat] for example in dataSet]
    uniqueVals = set(featValues)
    for value in uniqueVals:
        # 求出剩余的标签label
        subLabels = labels[:]
        # 遍历当前选择特征包含的所有属性值,在每个数据集划分上递归调用函数createTree()
        myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value), subLabels)
        # print ('myTree', value, myTree)
    return myTree

算法得出的决策树如下:

机器学习——03决策树算法

测试算法

def classify(inputTree, featLabels, testVec):
    """classify(给输入的节点,进行分类)

    Args:
        inputTree  决策树模型
        featLabels Feature标签对应的名称
        testVec    测试输入的数据
    Returns:
        classLabel 分类的结果值,需要映射label才能知道名称
    """
    # 获取tree的根节点对于的key值
    firstStr = list(inputTree.keys())[0]
    # 通过key得到根节点对应的value
    secondDict = inputTree[firstStr]
    # 判断根节点名称获取根节点在label中的先后顺序,这样就知道输入的testVec怎么开始对照树来做分类
    featIndex = featLabels.index(firstStr)
    # 测试数据,找到根节点对应的label位置,也就知道从输入的数据的第几位来开始分类
    key = testVec[featIndex]
    valueOfFeat = secondDict[key]
    print('+++', firstStr, 'xxx', secondDict, '---', key, '>>>', valueOfFeat)
    # 判断分枝是否结束: 判断valueOfFeat是否是dict类型
    if isinstance(valueOfFeat, dict):
        classLabel = classify(valueOfFeat, featLabels, testVec)
    else:
        classLabel = valueOfFeat
    return classLabel

def fishTest():
    # 1.创建数据和结果标签
    myDat, labels = createDataSet()
    myTree = createTree(myDat, copy.deepcopy(labels))
    # [1, 1]表示要取的分支上的节点位置,对应的结果值
    print(classify(myTree, labels, [1, 1]))

+++ 不浮出水面是否可以生存 xxx {0: '不是????', 1: {'是否有脚蹼': {0: '不是????', 1: '是????'}}} --- 1 >>> {'是否有脚蹼': {0: '不是????', 1: '是????'}}  
+++ 是否有脚蹼 xxx {0: '不是????', 1: '是????'} --- 1 >>> 是????
是????

使用算法:

此步骤可以适用于任何监督学习任务,而使用决策树可以更好地理解数据的内在含义。