一文搞懂决策树算法

作者: somenzz | 来源:发表于2019-07-06 00:04 被阅读29次

    原文链接

    有一个房间,里面有 100 个人,每个人有 100 元。每过一会,每个有钱的人给随机的其他人 1 元,经过一段时间后,房间内的资金分配情况是怎样?

    也许有人会认为最终大家的钱趋于平均数,其实不然,你可以使用自己熟悉的编程语言来实现,下图的结果是模拟 45 人初始为 45 元的结果,100 个人结果也是一样的。

    这个结果和现实世界中的财富占比是一致的,我想,这大概就是运气吧。

    不确定性才是客观世界的本质属性。换句话说,上帝还真就掷骰子。不确定性的世界只能使用概率模型来描述,通过概率模型来量化信息量,进而做出各种预测,来帮助决策。本文介绍机器学习中的决策树算法,如果你能看懂,我的目的就达到了。

    先提出一个问题:如何让计算机生成一个树,根据眼睛、头发、身高来区分一个人是不是东方人?训练数据集如下所示:

    no eye hair height result
    1 Black Black Short Yes
    2 Black White Tall Yes
    3 Black White Short Yes
    4 Black Black Tall Yes
    5 Brown Black Tall Yes
    6 Brown White Short Yes
    7 Blue Gold Tall No
    8 Blue Gold Short No
    9 Blue White Tall No
    10 Blue Black Short No
    11 Brown Gold Short No

    为了让你有个直观的认识,这里先给出答案:

    有了这个树,再依据眼睛、发色、身高来判断是否是东方人,就非常简单了,这个树就是决策树。

    你肯定想知道,这是怎么做到的?

    程序、算法都是人脑设计的,想弄明白电脑是如何做的,先思考人脑会如何解决这个问题。

    为了创建决策树,我们先要选择一个节点作为根节点,有三个特征,该选哪个呢?理论上所有特征都可以作为根节点,但如果选择不恰当,会导致树有非常多的分叉,变得臃肿,决策时效率非常低下。最简化的情况是树只有一层,只使用一个特征就可以满足判断。因此选择根节点时要选择最有区分度的那个特征作为根节点,也就是说仅凭这个特征就可以判断出大多数结果,本例中眼睛这个特征就符合这个条件,如果眼睛是蓝色和黑色,可以直接判断结果,只有是棕色时,需要借助其他特征。

    根节点确定之后,在剩余特征中选择最有区分度的特征作为子节点,本例子中就是头发。通过分析发现,仅使用眼睛、头发两个特征就可以将所有结果判断完毕,因此也就不需要身高加入决策树。

    总结一下,就是选择最有区分度的特征作为根节点,次有区分度的作为子节点,再次之,直到特征用完。

    人脑可以靠观察分析判断,电脑如何量化这种区分度呢?这里就要引用信息量,信息熵,信息增益这些概念,不用慌,这个其实好理解。

    熵的本质,是一个系统内存的混乱程度,熵越大,代表越混乱,越无序。比如气体扩散后不可能自己缩回去,温度只能自发从高温传到低温,这些都是熵增的过程。而我们整理混乱的房间,系统性的学习知识,努力的工作,则是熵减的过程。

    在生活中,信息的载体是消息,而不同的消息带来感觉也是不同的。比如,“中国男子足球队获得世界杯冠军”的信息显然要比“中国男子乒乓球队获得世界杯冠军”的信息量要大得多。究其原因,国足勇夺世界杯是小概率事件,发生的可能性微乎其微;而男乒夺冠已经让国人习以为常,丢掉冠军才是意外。因此,以不确定性来度量信息是一种合理的方式。不确定性越大的消息可能性越小,其提供的信息量就越大。

    “熵”这个词来源于百科全书式的科学家约翰·冯诺伊曼,香农把它引申到信息论,用于量化信息量

    信息量.png

    信息量的单位为比特,假如中国男子足球队获得世界杯冠军的概率为 1/1000,则其信息量约为 10 比特,国乒夺冠概率为 1/2 的话,则其信息量仅为 1 比特,如果一件事情百分之百发生,那么信息量为 0 比特。

    因此概率越小,其熵越大,说明信息量越大。

    假如一个信息包含多个信源,求得这些信源信息量的数学期望,就可以得到信源熵(信息熵),上述例子中判断是否为东方人这一信息就包含头发,眼睛,身高这三个信源。

    信息熵的差就是信息增益。

    例子一:一个盒子中分别有 5 个白球和 5 个黑球,随机取出一个球判断它的颜色?这个问题信息量多大呢?由于黑球和白球出现的概率都是1/2,那么就可以得到其信息熵为:

    H(x1)= - (1/2log2(1/2)+1/2log2(1/2)) = 1

    是的,这里的信息熵是 1 比特。

    例子二:有黑白两个盒子,黑色装 5 个黑球,白色装 5 个白球,随意从这两个盒子取一个球,颜色不用判断了,这个问题的信息量有多大呢?

    H(x2)= - (1/21log2(1)+1/21log2(1)) = 0

    信息增益为 Gain(2) = H(x1) - H(x2) = 1-0=1

    例子三:有黑白两个盒子,黑色装 4 个黑球 1 个白球,白色装 4 个白球 1 个黑球,随意从这两个盒子取一个球,判断颜色,这个问题的信息量有多大呢?

    H(x3)= - (1/2(1/5log2(1/5)+4/5log2(4/5))+1/2(1/5log2(1/5)+4/5log2(4/5))) = 0.7219280948873623

    信息增益为 Gain(3) = H(x1) - H(x3) = 1 -0.7219280948873623 = 0.2780719051126377

    因为 Gain(2) > Gain(3) ,因此例子二的分法更有区分度。从概率上也能说明问题:从例子二的盒子中拿到黑球的概率是 100%,从例子三中拿到黑球的概率是 80%(4/5),从例子一中拿到黑球的概率是 50%。

    同样的道理,我们计算眼睛、发色、身高三个特征的信息增益来选择最佳的特征。

    第一步:计算训练数据集的总信息熵

    数据共 11 条,有 6 条结果是 yes, 有 5 条结果是 no。
    H(总)= -(6/11log2(6/11)+5/11log2(5/11)) = 0.9940302114769565

    第二步:分别计算每个特征的信息熵和信息增益

    先看眼睛,眼睛为 Black 的有 4 条,其中全部是东方人

    H(eye=Black)=-1*log2(1) = 0

    眼睛为 Blue 的有 4 条,其中全部不是东方人

    H(eye=Blue)=-1*log2(1) = 0

    眼睛为 Brown 的有 3 条,其中 2 个是东方人,1 个不是东方人:
    H(eye=Brown)=-(2/3log2(2/3)+1/3log2(1/3)) = 0.9182958340544896

    总条数为 11 条,其中 eye = Black 的占 4 条,eye = Blue 的占 4 条,eye = Brown 的占 3 条,对三种颜色求期望可得到眼睛的信息熵为:
    H(eye)=4/11 * H(eye=Black) + 4/11H(eye=Blue)+3/11H(eye=Brown)=0+0+3/11*0.9182958340544896 = 0.2504443183784972

    信息熵与概率的关系如下图所示:

    可以看出,当概率 P(x) 越接近 0 或者 1 时,信息熵越小,其不确定性越小,即数据越纯信息熵越小,说明越有序。

    因此眼睛这个特征的 信息增益 为
    Gain(眼睛)= H(总)-H(eye) = 0.9940302114769565-0.2504443183784972
    = 0.7435858930984593

    信息增益越大,说明特征的信息熵越小,则说明该特征越有序,更有区分度。

    我们在选择特征时,选择信息增益最大的特征,即,让数据尽量往更纯净的方向上变换,因此,信息增益是用来衡量数据变得更有序、更纯净的指标。

    我们按第二步的方法求出所有特征的信息增益,并排序。

    Gain(眼睛信息增益) = 0.7435858930984593

    Gain(头发信息增益) = 0.4040097573248599

    Gain(身高信息增益) = 0.00723448672483451

    因此我们可以按照 眼睛-> 头发 -> 身高 的顺序来构建决策树。

    第三步:构建决策树

    眼睛做为根节点确认之后,发现眼睛的取值有 3 个,因此从眼睛出发,有 3 条线,分别为 Black、Bule、Brown,其中 Black 和 Bule 的结果都是一致的 yes 或 no,因此也可直接得出结论,不需要再有子节点。

    当为眼睛 Brown 时结果有两个 yes 和 一个 no,此时需要借助具有第二区分度的特征 头发 来再次判断。

    处理头发时,道理是一致的,编码时可以递归处理,最终发现不需要借助 身高 这个特征就可以将所有数据判断完毕,此时树构建完毕。

    获取源代码

    创建决策树的函数如下:

    def createTree(dataSet, labels):
        """
        Desc:
            创建决策树
        Args:
            dataSet -- 要创建决策树的训练数据集
            labels -- 训练数据集中特征对应的含义的labels,不是目标变量
        Returns:
            myTree -- 创建完成的决策树
        """
        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
    

    关注公众号 somenzz,后台回复「决策树」获取完整源码。如果有任何疑问欢迎加微信与我交流。

    (完)

    相关文章

      网友评论

        本文标题:一文搞懂决策树算法

        本文链接:https://www.haomeiwen.com/subject/pzayhctx.html