机器学习之决策树详解——以西瓜书的数据集为例

Markdown:这里先记录一下,这是一种最近比较流行的XHTML语言,后期记得去仔细研究一下(突然想到的,和本文无关)。


前言

随着人工智能的不断发展,机器学习这门技术也越来越重要,很多人都开启了学习机器学习,本文简单介绍了机器学习中的一个重要分类模型——决策树


一、决策树是什么?

决策树(Decision Tree)是在已知各种情况发生概率的基础上,通过构成决策树来求取净现值的期望值大于等于零的概率,评价项目风险,判断其可行性的决策分析方法,是直观运用概率分析的一种图解法。
以二分类为例,我们判断一个瓜是好瓜还是坏瓜,可以转化为:这个瓜是好瓜吗?回答是“是”或者“否”。人类在思考、处理问题的时候也会有这样的思考过程。顾名思义,决策树就是一种基于树结构的分类决策模型,不同的是:判断一个瓜是否为好瓜,肯定不止一个带筛选的属性集,先需要就行“色泽”、“根蒂”等一系列的子决策,最后我们才能去判断这个瓜是不是好瓜。
下图是在西瓜书中,构建决策树的基本步骤:
决策树的步骤

二、决策树的构建步骤

1.准备数据

在机器学习中,数据是非常重要的一部分。合适的数据对应合适的模型,在本文中,我使用了周志华老师西瓜书中案例的数据,还有一小份网上的数据(其实自己手写也可以),虽然数据量不大,但是用于做简单的demo还是绰绰有余的。
准备的西瓜的数据集
数据文件我放在网盘,需要的自取。
链接:https://pan.baidu.com/s/1Vro25wSPVGNQ0kdIaYQG7Q
提取码:cm1d

2.读取数据

本文使用的语言是python3.7.7,IDE是Spyder4.0。
代码如下:

def createDataSet():
    """
    outlook->  0: sunny | 1: overcast | 2: rain
    temperature-> 0: hot | 1: mild | 2: cool
    humidity-> 0: high | 1: normal
    windy-> 0: false | 1: true 
    """
    dataSet = [[0, 0, 0, 0, 'N'],
               [0, 0, 0, 1, 'N'],
               [1, 0, 0, 0, 'Y'],
               [2, 1, 0, 0, 'Y'],
               [2, 2, 1, 0, 'Y'],
               [2, 2, 1, 1, 'N'],
               [1, 2, 1, 1, 'Y']]
    labels = ['outlook', 'temperature', 'humidity', 'windy']
    return dataSet, labels

def createDataSetForWM():
    '''
    input
    -------
    None

    Returns
    -------
    dataSet : TYPE: 2d list.
    labels  : TYPE: 1d list.

    '''
    # 创建属于西瓜的数据集
    dataSet_df = pd.read_table('dataSet.txt', header=0, sep='\t', names=None)
    dataSet = np.array(dataSet_df)
    dataSet = list(dataSet)
    # dataSet
    # 遍历dataSet
    for index, value in enumerate(dataSet):
        dataSet[index] = list(dataSet[index])
        for index2, value2 in enumerate(dataSet[index]):
            # 判断如果是字符串,去除前后的字符
            if(type(dataSet[index][index2]) == str):
                dataSet[index][index2] = dataSet[index][index2].strip()
            # 如果不是字符串,转换为字符串
            else:
                dataSet[index][index2] = str(dataSet[index][index2])
    # labels
    labels = list(np.array(dataSet_df.columns))
    
    return dataSet, labels
	
	dataSet, labels = createDataSet()
    dataSet, labels = createDataSetForWM()

第一个数据集是网上找的,数据量和维度相对较少,第二个数据集是西瓜书里面的,我手打进了txt文件。返回的格式都是一样的,dataSet是2d的list,labels是1d的list。如果要使用的话,选择一个数据集读取即可。

2.找到几种导致递归返回的情况

构建树,本质上是调用递归的过程,递归必须要有一个或者多个出口。在决策树中,有以下几个递归的出口

  1. 划分的训练集中的样本都属于同一类别C,则将当前结点标记为叶结点,当下所有的数据归为类别C;
  2. 子属性集合为空(说明没有类别没有可以被用于划分),则将当前结点标记为叶结点,当下所有数据中样本数最多的类标记为类别D。

递归出口代码参考:

if classList.count(classList[0]) == len(classList):
    return classList[0]  # splitDataSet(dataSet, 0, 0)此时全是N,返回N

# 属性值的集合为空集(还差一个样本在属性集上取值相同)
if len(dataSet[0]) == 1:  # [0, 0, 0, 0, 'N']
    # 出现次数最多的属性
    return majorityCnt(classList)

3.在待筛选的属性集中找到一个最优的划分属性

敲重点!这里是决策树乃至于所有树结构分类模型的精髓。不同的分类依据其实就对应了不同的树结构。
在决策树中,随着划分的不断进行,我们希望决策树的分支结点所包含的样本尽可能属于同一类别,也就是说让结点的“纯度”越来越高。
说到这里,必须提到计算“纯度”的方式——“信息熵”。
信息熵是一种典型的纯度算法,假设当前样本D中的第k类样本所占的比例为pk,那么Ent(D)的定义为:
信息熵的计算公式
信息熵的计算结果越小,代表样本越纯。
在信源中,对于信息熵的定义是:考虑的不是某一单个符号发生的不确定性,而是要考虑这个信源所有可能发生情况的平均不确定性。若信源符号有n种取值:U1…Ui…Un,对应概率为:P1…Pi…Pn,且各种符号的出现彼此独立。这时,信源的平均不确定性应当为单个符号不确定性-logPi的统计平均值(E),可称为信息熵。类比到决策树中,其实本质上是各个不确定性样本的概率和,和越小,说明整体样本的不确定性越小,样本越纯。
代码如下:

def calcShannonEnt(dataSet):
    """
    输入:数据集
    输出:数据集的香农熵
    描述:计算给定数据集的香农熵;熵越大,数据集的混乱程度越大
    """
    numEntries = len(dataSet)
    # 求每个类别的数量
    labelCounts = {}
    for featVec in dataSet:
        currentLabel = featVec[-1]
        if currentLabel not in labelCounts.keys():
            labelCounts[currentLabel] = 0
        # 数每一类各多少个, {'Y': 4, 'N': 3}
        labelCounts[currentLabel] += 1      
    # 求属性集的熵
    shannonEnt = 0.0
    for key in labelCounts:
        # 每个类别对应的概率(比重)
        prob = float(labelCounts[key]) / numEntries
        shannonEnt += (-1) * prob * log(prob, 2)
    return shannonEnt

计算完信息熵后,我们得到了在每一个结点上的样本纯度,接下来我们应该更具纯度去选择合适的划分属性。例如我们在一个根结点上计算出对应的信息熵是x,那么我们在这个结点的基础上,计算每一种划分属性的划分结果,比如说按照“色泽”来划分当前结点,所得到的结点的纯度,大于用“纹理”来划分当前结点的纯度,那我们就应该用“色泽”来划分当前结点(仅考虑了这两个结点)。
在这样的比对模式下,我们采用“信息增益”来表示纯度的增减情况。即用根结点的信息熵减去划分属性后对应子结点的信息熵的和。信息增益越大,说明纯度下降的越多,即纯度提升的越多。下图是信息增益的计算公式:
信息增益计算公式
虽然说信息增益在一定程度上可以代表样本的提纯幅度,但是当我们考虑类别数近乎等于样本数的属性时(例如“编号”这一属性),计算出来的信息增益也很大,由此我们定义了“信息增益率”这一新评估标准,在计算完信息增益后除以对应属性的“属性固有值”,用以表示增长的速率。信息增益率的计算公式如下:
信息增益率计算公式
选择最优结点建树的代码如下:

def chooseBestFeatureToSplit(dataSet):
    """
    输入:数据集
    输出:最好的划分维度
    描述:选择最好的数据集划分维度
    """
    # 属性的个数(最后一个是标签)
    numFeatures = len(dataSet[0]) - 1
    # 根节点的熵
    # 信息熵:即计算一个整体的混乱程度,熵越小,说明混乱程度越小,样本越纯
    baseEntropy = calcShannonEnt(dataSet)
    # 最优信息增益率
    # 信息增益:即计算从一个分类节点到另一个之间产生的信息熵差异,原则上是减少,所以减少的越多,说明分类的结果越好
    bestInfoGainRatio = 0.0
    # 最优的属性
    bestFeature = -1
    # 遍历所有的属性
    print('每个属性的信息增益率:')
    for i in range(numFeatures):
        # 所有数据的指定属性组成的集合
        featList = [example[i] for example in dataSet]  
        # 每个list的唯一值集合
        uniqueVals = set(featList)
        # 新的信息熵
        newEntropy = 0.0
        # 指定属性的固有值
        splitInfo = 0.0
        for value in uniqueVals:
            # 每个唯一值对应的剩余feature的组成子集
            subDataSet = splitDataSet(dataSet, i, value)
            # 当前类别样本占总样本的比例,即计算概率
            prob = len(subDataSet) / float(len(dataSet))
            # 计算熵
            newEntropy += prob * calcShannonEnt(subDataSet)
            # 计算指定属性的固有值(a),固有值越大,说明类别越杂,过杂的类别容易造成过拟合
            splitInfo += -prob * log(prob, 2)
        # 这个feature的infoGain(信息增益)
        infoGain = baseEntropy - newEntropy
        # fix the overflow bug
        if (splitInfo == 0):  
            continue
        # 这个feature的infoGainRatio(信息增益率)
        infoGainRatio = infoGain / splitInfo  
        
        print('当前的信息增益率为:{}'.format(infoGainRatio))
        # 选择最大的gain ratio
        if (infoGainRatio > bestInfoGainRatio):  
            bestInfoGainRatio = infoGainRatio
            # 选择最大的gain ratio对应的feature
            bestFeature = i  
            
    return bestFeature

这样一来,我们便可以选择一个最优的属性,用以划分某一个根结点。如下图所示,这是一棵完整的决策树:基于信息增益率的决策树


总结

决策树和其他的树型分类模型一样,本质上就是递归地去创建结点,直到碰到递归地边界才返回。不同地地方就是选择属性的判断条件不一样,这也就很大程度上影响了对于不同数据集的分类效果。理解熵的概念并不难,只要能看懂基本的数学公式,基本就可以对应理解公式的含义。
完整代码链接我放在我的blog主页,需要的自取。

  • 7
    点赞
  • 62
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值