决策树(Decision Tree)分类算法原理及应用

4.1 概述

决策树——是一种被广泛使用的分类算法。相比贝叶斯算法,决策树的优势在于构造过程不需要任何领域知识或参数设置。在实际应用中,对于探测式的知识发现,决策树更加适用

决策树通常有三个步骤:特征选择、决策树的生成、决策树的修剪。

4.2 算法思想

通俗来说,决策树分类的思想类似于找对象。现想象一个女孩的母亲要给这个女孩介绍男朋友,于是有了下面的对话:

  女儿:多大年纪了?
  母亲:26
  女儿:长的帅不帅?
  母亲:挺帅的。
  女儿:收入高不?
  母亲:不算很高,中等情况。
  女儿:是公务员吗?
  母亲:是,公务员,在税务局上班呢。
  女儿:那好,我去见见。

这个女孩的决策过程就是典型的分类树决策。实质:通过年龄、长相、收入和是否公务员将男人分为两个类别:见和不见

假设这个女孩对男人的要求是:30岁以下、长相中等以上并且是高收入者或中等以上收入的公务员,那么这个可以用下图表示女孩的决策逻辑。

上图完整表达了这个女孩决定是否见一个约会对象的策略,其中:

  • 绿色节点表示判断条件
  • 橙色节点表示决策结果
  • 箭头表示在一个判断条件在不同情况下的决策路径

图中红色箭头表示了上面例子中女孩的决策过程。这幅图基本可以算是一颗决策树,说它”基本可以算“是因为图中的判定条件没有量化,如收入高中低等等,还不能算是严格意义上的决策树,如果将所有条件量化,则就变成真正的决策树了。

决策树分类算法的关键就是根据”先验数据“构造一棵最佳的决策树,用以预测未知数据的类别

决策树:是一个树结构(可以是二叉树或非二叉树)。其每个非叶节点表示一个特征属性上的测试,每个分支代表这个特征属性在某个值域上的输出,而每个叶节点存放一个类别。使用决策树进行决策的过程就是从根节点开始,测试待分类项中相应的特征属性,并按照其值选择输出分支,直到到达叶子节点,将叶子节点存放的类别作为决策结果。

4.3 决策树构造

4.3.1 决策树构造样例

假如有以下判断苹果好坏的数据样本:

样本    红     大      好苹果

0       1      1         1

1       1      0         1

2       0      1         0

3       0      0         0

样本中有2个属性,A0表示是否红苹果。A1表示是否大于苹果。假如要根据这个数据样本构建一棵自动判断苹果好坏的决策树。由于本例中的数据只有2个属性,因此,我们可以穷举所有可能构造出来的决策树,就2课树,如下图所示:

显然左边先使用A0(红色)做划分依据的决策树要优于右边用A1(大小)做划分依据的决策树。当然这是直觉的认知。而直觉显然不适合转化成程序的实现,所以需要有一种定量的考察来评价这两棵树的性能好坏。

决策树的评价所用的定量考察方法为计算每种划分情况的信息熵增益:如果经过某个选定的属性进行数据划分后的信息熵下降最多,则这个划分属性是最优选择。

4.3.2 属性划分选择(即构造决策树)的依据

熵:信息论的奠基人香农定义的用来信息量的单位。简单来说,熵就是“无序,混乱”的程度。

公式:H(X)=- Σ pi * logpi, i=1,2, ... , n,pi为一个特征的概率

通过计算来理解:

1、原始样本数据的熵:

样例总数:4
好苹果:2
坏苹果:2

 熵:-(1/2 * log(1/2) + 1/2 * log(1/2)) =1

信息熵为1表示当前处于最混乱,最无序的状态

2、两颗决策树的划分结果熵增益计算

  • 树1先选A0作划分,各子节点信息熵计算如下:

          0,1叶子节点有2个正例,0个负例。信息熵为:e1 = -(2/2 * log(2/2) + 0/2 * log(0/2)) =0。

          2,3叶子节点有0个正例,2个负例。信息熵为:e2 = -(0/2 * log(0/2) + 2/2 * log(2/2)) =0。

          因此选择A0划分后的信息熵为每个子节点的信息熵所占比重的加权和:E = e1 * 2/4 + e2 * 2/4 = 0。

          选择A0做划分的信息熵增益G(S,A0) = S - E = 1 - 0 =1。

          事实上,决策树叶子节点表示已经都属于相同类别,因此信息熵一定为0。

  • 树2先选A1作划分,各子节点信息熵计算如下:

          0,2子节点有1个正例,1个负例。信息熵为:e1 = -(1/2 * log(1/2) + 1/2 * log(1/2)) = 1。

          1,3子节点有1个正例,1个负例。信息熵为:e2 = -(1/2 * log(1/2) + 1/2 * log(1/2)) = 1。

          因此选择A1划分后的信息熵为每个子节点的信息熵所占比重的加权和:E = e1 * 2/4 + e2 * 2/4 = 1。也就是说分了跟没分一样!

           选择A1做划分的信息熵增益G(S,A1) = S - E = 1 - 1 = 0。

           因此,每次划分之前,我们只需要计算出信息熵增益最大的那种划分即可。

4.4 算法要点

4.4.1 指导思想

经过决策属性的划分后,数据的无序度越来越低,也就是信息熵越来越小

4.4.2 算法实现

梳理出数据中的属性,比较按照某特定属性划分后的数据的信息熵增益,选择信息熵增益最大的那个属性作为第一划分依据,然后继续选择第二属性,以此类推。

4.5 决策树分类算法Python实战

4.5.1 案例需求

我们的任务就是训练一个决策树分类器,输入身高和体重,分类器能给出这个人是胖子还是瘦子。

所用的训练数据如下,这个数据一共有8个样本,每个样本有2个属性,分别为头发和声音,第三列为性别标签,表示“男”或“女”。该数据保存在1.txt中。

头发 声音 性别

4.5.2 模型分析

决策树对于“是非”的二值逻辑的分枝相当自然。

本例决策树的任务是找到头发、声音将其样本两两分类,自顶向下构建决策树。

在这里,我们列出两种方案:

①先根据头发判断,若判断不出,再根据声音判断,于是画了一幅图,如下:

 

于是,一个简单、直观的决策树就这么出来了。头发长、声音粗就是男生;头发长、声音细就是女生;头发短、声音粗是男生;头发短、声音细是女生。

② 先根据声音判断,然后再根据头发来判断,决策树如下:

那么问题来了:方案①和方案②哪个的决策树好些?计算机做决策树的时候,面对多个特征,该如何选哪个特征为最佳多得划分特征?

划分数据集的大原则是:将无序的数据变得更加有序。

我们可以使用多种方法划分数据集,但是每种方法都有各自的优缺点。于是我们这么想,如果我们能测量数据的复杂度,对比按不同特征分类后的数据复杂度,若按某一特征分类后复杂度减少的更多,那么这个特征即为最佳分类特征。为此,Claude Shannon定义了熵和信息增益,用熵来表示信息的复杂度,熵越大,则信息越复杂。信息增益表示两个信息熵的差值。

首先计算未分类前的熵,总共有8位同学,男生3位,女生5位

熵(总)= -3/8*log2(3/8)-5/8*log2(5/8)=0.9544

接着分别计算方案①和方案②分类后信息熵。

方案①首先按头发分类,分类后的结果为:长头发中有1男3女。短头发中有2男2女。

熵(长发)= -1/4*log2(1/4)-3/4*log2(3/4)=0.8113

熵(短发)= -2/4*log2(2/4)-2/4*log2(2/4)=1 

熵(方案①)= 4/8*0.8113+4/8*1=0.9057 (4/8为长头发有4人,短头发有4人)

信息增益(方案①)= 熵(总)- 熵(方案①)= 0.9544 - 0.9057 = 0.0487

同理,按方案②的方法,首先按声音特征来分,分类后的结果为:声音粗中有3男3女。声音细中有0男2女。

熵(声音粗)= -3/6*log2(3/6)-3/6*log2(3/6)=1 

熵(声音细)= -2/2*log2(2/2)=0

熵(方案②)= 6/8*1+2/8*0=0.75 (6/8为声音粗有6人,2/8为声音细有2人)

信息增益(方案②)= 熵(总)- 熵(方案②)= 0.9544 - 0.75 = 0.2087

按照方案②的方法,先按声音特征分类,信息增益更大,区分样本的能力更强,更具有代表性。

以上就是决策树ID3算法的核心思想。

4.5.3 python实现ID3算法

from math import log
import operator

def calcShannonEnt(dataSet):  # 计算数据的熵(entropy)
    numEntries=len(dataSet)  # 数据条数
    labelCounts={}
    for featVec in dataSet:
        currentLabel=featVec[-1] # 每行数据的最后一个字(类别)
        if currentLabel not in labelCounts.keys():
            labelCounts[currentLabel]=0
        labelCounts[currentLabel]+=1  # 统计有多少个类以及每个类的数量
    shannonEnt=0
    for key in labelCounts:
        prob=float(labelCounts[key])/numEntries # 计算单个类的熵值
        shannonEnt-=prob*log(prob,2) # 累加每个类的熵值
    return shannonEnt

def createDataSet1(): # 创造示例数据
    dataSet = [['长', '粗', '男'],
               ['短', '粗', '男'],
               ['短', '粗', '男'],
               ['长', '细', '女'],
               ['短', '细', '女'],
               ['短', '粗', '女'],
               ['长', '粗', '女'],
               ['长', '粗', '女']]
    labels = ['头发', '声音'] # 两个特征
    return dataSet, labels

def splitDataSet(dataSet, axis, value): # 按某个特征分类后的数据
    retDataSet = []
    for featVec in dataSet:
        if featVec[axis] == value:
            reducedFeatVec = featVec[:axis]
            reducedFeatVec.extend(featVec[axis+1:])
            retDataSet.append(reducedFeatVec)
    return retDataSet

def chooseBestFeatureToSplit(dataSet): # 选择最优的分类特征
    numFeatures = len(dataSet[0]) - 1
    print(numFeatures)
    baseEntropy = calcShannonEnt(dataSet) # 原始的熵
    bestInfoGain = 0
    bestFeature = -1
    for i in range(numFeatures):
        featList = [example[i] for example in dataSet]
        uniqueVals = set(featList)
        newEntropy = 0
        for value in uniqueVals:
            subDataSet = splitDataSet(dataSet, i, value)
            prob = len(subDataSet) / float(len(dataSet))
            newEntropy += prob * calcShannonEnt(subDataSet) # 按特征分类后的熵
        infoGain = baseEntropy - newEntropy # 原始熵与按特征分类后的熵的差值
        if (infoGain > bestInfoGain): # 若按某特征划分后,熵值减少的最大,则次特征为最优分类特征
            bestInfoGain = infoGain
            bestFeature = i
    return bestFeature

def majorityCnt(classList): # 按分类后类别数量排序,比如:最后分类为2男1女,则判定为男:
    classCount={}
    for vote in classList:
        if vote not in classCount.keys():
            classCount[vote]=0
        classCount[vote]+=1
    sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
    return sortedClassCount[0][0]

def createTree(dataSet, labels):
    classList = [example[-1] for example in dataSet] # 类别:男或女
    if classList.count(classList[0]) == len(classList):
        return classList[0]
    if len(dataSet[0]) == 1:
        return majorityCnt(classList)
    bestFeat = chooseBestFeatureToSplit(dataSet) # 选择最优特征
    bestFeatLabel = labels[bestFeat]
    myTree = {bestFeatLabel:{}} # 分类结果以字典形式保存
    del(labels[bestFeat])
    featValues = [example[bestFeat] for example in dataSet]
    #print(featValues)
    uniqueVals = set(featValues)
    for value in uniqueVals:
        subLabels = labels[:]
        myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value), subLabels)
    return myTree

if __name__ == '__main__':
    dataSet, labels = createDataSet1() # 创造示例数据
    print(createTree(dataSet, labels)) # 输出决策树模型结果

这时候会输出

{'声音': {'细': '女', '粗': {'头发': {'长': '女', '短': '男'}}}}

4.5.4 决策树的保存

一棵决策树的学习训练是非常耗费运算时间的,因此,决策树训练出来后,可进行保存,以便在预测新的数据时只需要直接加载训练好的决策树即可

本案例的代码中已经把决策树的结构写入了tree.dot中。打开该文件,很容易画出决策树,还可以看到决策树的更多分类信息。

本例的tree.dot如下所示:

digraph Tree {
[label="X[1] <= 55.0000\nentropy = 0.954434002925\nsamples = 8", shape="box"] ;
[label="entropy = 0.0000\nsamples = 2\nvalue = [ 2.  0.]", shape="box"] ;
-> 1 ;
[label="X[1] <= 70.0000\nentropy = 0.650022421648\nsamples = 6", shape="box"] ;
-> 2 ;
[label="X[0] <= 1.6500\nentropy = 0.918295834054\nsamples = 3", shape="box"] ;
-> 3 ;
[label="entropy = 0.0000\nsamples = 2\nvalue = [ 0.  2.]", shape="box"] ;
-> 4 ;
[label="entropy = 0.0000\nsamples = 1\nvalue = [ 1.  0.]", shape="box"] ;
-> 5 ;
[label="entropy = 0.0000\nsamples = 3\nvalue = [ 0.  3.]", shape="box"] ;
-> 6 ;
}

根据这个信息,决策树应该长的如下这个样子:

发布了831 篇原创文章 · 获赞 680 · 访问量 134万+

猜你喜欢

转载自blog.csdn.net/zhangchen124/article/details/105190163
今日推荐