一、何为决策树

决策树是监督学习算法之一,并且是一种基本的分类与回归方法;决策树也分为回归树和分类树,本文讨论的是分类树。如果了解或者学过数据结构,肯定对"树"这个概念是不陌生的,在此基础上学习掌握决策树也会更加容易,下面通过一个小例子帮助理解何为决策树。

下图所示流程图即为一个决策树,矩形代表判断模块、椭圆形则代表终止模块,表示已经得出结论可以终止程序的运行;左右箭头表示分支,可以通过它到达另一判断模块或终止模块。
机器学习笔记(三)——一文帮你搞懂决策树必备的信息增益_第1张图片
这个流程图主要是假想一个择偶系统,之前网上不流行这样一句话嘛,"阿姨我不想努力了",该树就以是否想继续努力为判断依据,如果你不想继续努力了,你可以选择找一个"富婆";反之,你想找一个女朋友一起奋斗,这里又以女孩的性格为判断依据,如果喜欢性格温柔的,即选择"温柔女孩",若喜欢性格高冷的,则选择"酷女孩"。

整个决策树可以看成一个if—then规则,即"如果判断条件,则……",并且需要注意以下三点:

  1. 根节点到每一个子节点的路径可构成一条规则。
  2. 每条路径上中间节点的特征对应该条规则的判断条件,叶子节点的标签对应该规则的结论。
  3. 每一个实例都被有且仅有一条实例覆盖,即实例的特征与路径上的特征一致。

    二、决策树的流程

  4. 收集数据:公开数据源或爬虫等方式。
  5. 准备数据:树构造算法只适用于标称型数据,因此数值型数据必须离散化。
  6. 分析数据:可以使用任何方法,构造树完成之后,需检查图形是否符合预期。
  7. 训练算法:构造树的数据结构。
  8. 测试算法:计算树模型的正确率。
  9. 使用算法:此步骤可以适用于任何监督学习算法,决策树可视化能更好地理解数据的内在含义。

构造决策树的数据必须要充足,特征较少的数据集可能会导致决策树的正确率偏低。若数据特征过多,不会选择特征也会影响决策树的正确率。构建一个比较理想的决策树,大致可分为以下三步:特征选择、决策树的生成与决策树的修剪。

三、特征选择

特征选择即决定用数据集中哪一个特征划分特征空间,主要在于选取对训练数据具有分类能力的特征。这样做的目的是提高决策树的效率,如果一个用特征的分类结果和随机分类的结果差别不大,那么这个特征是没有什么分类能力的,一般地讲这类特征去除对决策树的精度影响不大。

下面表格是一份关于海洋生物的数据,数据的两个特征分别为no surfacing(不浮出水面是否能生存)、flippers(是否有脚蹼),一个类标签fish(是否为鱼类)。

no surfacing flippers fish

不论一个数据集有多少特征,每次划分数据集时只能选一个特征,那么第一次选择哪个特征作为划分的参考属性才能将数据更快的分类呢?答案一定是一定是分类能力最好的那个特征,但问题来了,如何判断哪一个特征分类能力最好呢?这时就要引入一个新的概念——信息增益。

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

3.1 熵(香农熵)

在计算信息增益之前,需要先知道"熵"这个概念,"熵"究竟是什么东西,不必去深究它,我们只需了解熵定义为信息的期望值,在信息论与概率统计中,熵是表示随机变量不确定性的度量,用一句通俗的话讲就是这个体系的混乱程度是如何的。

假设有一个样本为n的数据集,第i类样本为Xi,那么符号Xi的信息可定义:
在这里插入图片描述
其中其中p(Xi)是选择该分类的概率。通过改变i的值即可获得数据集中所有类别的信息。

为了计算熵,我们需要计算所有类别所有可能值包含的信息期望值(数学期望),通过下面的公式得到:
在这里插入图片描述
熵越高,变量的不确定性越大,也就是数据的混合程度越高。

3.2计算熵

在创建数据集之前,对数据进行一下自定义简化,用1代表"是",0代表"否"。
计算熵代码如下:

#创建数据集
def createDataSet():
    DataSet =  [[1, 1, 'yes'],
                [1, 1, 'yes'],
                [1, 0, 'no'],
                [0, 1, 'no'],
                [0, 1, 'no']]
    #矩阵转化Dataframe
    DataSet = pd.DataFrame(DataSet,columns=['no surfacing','flippers','fish'])
    return DataSet
#计算信息熵
def calculatent(DataSet):
    #统计类标签的分类
    fish_type = DataSet['fish'].value_counts()
    '''
    no     3
    yes    2
    '''
    #获取样本个数
    m = DataSet.shape[0]
    #每个分类的概率,即p(Xi)
    p = fish_type/m
    #计算熵值
    ent = (-p*np.log2(p)).sum()
    return ent
    '''
    0.9709505944546686
    '''

最后返回熵为0.9709505944546686

这份数据中,最后的类标签共有两类"是鱼类"、"不是鱼类",前者占总标签个数的2/5,后者占3/5。
所以这部分代码的核心部分即套用求熵公式:
在这里插入图片描述
在得到熵之后,就可以按照最大信息增益的方法划分数据集,下一部分开始计算信息增益。

3.3信息增益

计算过熵后,怎么计算信息增益呢?信息增益的计算就是将父节点的熵减去其下所有子节点的熵之和,并且在求和时,由于类别比重不同,需要对其实现加权平均。

以"no surfacing"这一列举例,5个样本中,"1"有3个,"0"有2个,所以二者的权重一个为3/5,另一个为2/5;

其中对于"1"这三个样本,样本标签fish中的""有两个,""有一个,所以分类的概率分别为2/3和1/3,同理对于"0"这两个样本,样本标签fish都为"",所以分类概率为1。

这一列的信息增益计算公式如下:
机器学习笔记(三)——一文帮你搞懂决策树必备的信息增益_第2张图片
两个特征的信息增益计算结果如下:
机器学习笔记(三)——一文帮你搞懂决策树必备的信息增益_第3张图片
计算每个特征信息增益的目的就是要选择出每次分类时当前的最优特征,所以一定会有一个比较过程,便于得到最大的信息增益,并且返回该特征的索引,然后就可以利用这个最优特征对数据集进行切割。

所以这里构造两个函数,一个选择最优特征函数,另一个是分割数据集函数,具体代码如下:

#最优特征函数
def ChooseBF(DataSet):
    #获取基础信息熵
    all_ent = calculatent(DataSet)
    BestGain = 0 #初始化一个信息增益
    col = -1 #初始化一个最优特征索引
    for i in range(DataSet.shape[1]-1): #遍历所有特征
        #获取第i列的分类
        index_ = DataSet.iloc[:,i].value_counts().index
        child_ent = 0 #初始化某列的信息熵
        for j in index_:
            #将第i列按照类别分类
            child_DataSet = DataSet[DataSet.iloc[:,i]==j]
            #计算某一类别的信息熵
            ent = calculatent(child_DataSet)
            #将所有类别信息熵加权求和
            child_ent += (child_DataSet.shape[0]/DataSet.shape[0])*ent
        print("第%s列的熵为%s"%(i,child_ent))
        TheGain = all_ent-child_ent #获取信息增益
        print("第%s列的信息增益为%s"%(i,TheGain))
        #得出最优特征索引
        if(TheGain>BestGain):
            BestGain = TheGain
            col = i
    print("最优特征索引为:%s"%col)
    return col

这个函数两次调用计算信息熵函数calculatent,一次是为了获取基础信息熵,即上面公式中ent('总');另一次则是计算特征中不同类别的信息熵,即上面公式中ent('是')、ent('否')
代码运行截图如下:
机器学习笔记(三)——一文帮你搞懂决策树必备的信息增益_第4张图片
返回的最优特征索引为0,也就是"no surfacing"这一列;并且两列的信息增益都与上文手写计算的结果一致,所以代码是完全没有问题的。

下一步,利用最优特征对数据集进行划分,代码如下:

#切分数据集函数
def splitSet(DataSet,col,value):
    #找到最优特征索引标签
    index = (DataSet.iloc[:,col]).name
    #切分数据集,并把当前最优特征删去
    resetdata = DataSet[DataSet.iloc[:,col]==value].drop(index,axis = 1)
    return resetdata

这个函数需要传入三个参数,分别是需要切分的数据集、最优特征索引、特征中需保留的分类,可能有的人还没有理解这个value的作用,对比一下运行结果就懂了。

    '''
    col = 0,value = 1
    flippers fish 
0         1  yes
1         1  yes
2         0   no
    col = 0,value = 0
    flippers fish
3         1   no
4         1   no
    '''

上面两个DataFrame,是分割数据集函数返回的结果,当value = 1时,保留下来的这三个样本,都是"no surfacing"这一特征中值为1的;而当value = 0时,保留下来的两个样本,就是"no surfacing"这一特征中值为0的,这样一对比就很容易理解value的作用了。

文末总结

至此熵与信息增益的计算方法大致上已经介绍完毕,文中所取数据集特征数很少,所以导致数据集分类次数也会很少,当数据特征比较多时,经过第一次划分之后,数据集向下传递到决策树的分支的下一个结点,在这个结点上,我们可以再次划分数据,因此需要采用递归的原则处理数据集。

下文将会介绍如何用递归构建决策树以及决策树的可视化,毕竟决策树一个很大的优点就是通过可视化更好的理解数据是如何一步一步划分的。

公众号【奶糖猫】后台回复“信息增益”即可获取源码供参考,感谢阅读。