【机器学习】决策树算法

决策树 概念


决策树(Decision Tree)是一种基本的分类与回归方法,本文主要讨论分类决策树。决策树模型呈树形结构,在分类问题中,表示基于特征对实例进行分类的过程。它可以认为是if-then规则的集合,也可以认为是定义在特征空间与类空间上的条件概率分布。相比朴素贝叶斯分类,决策树的优势在于构造过程不需要任何领域知识或参数设置,因此在实际应用中,对于探测式的知识发现,决策树更加适用。

决策树学习通常包括 3 个步骤:特征选择、决策树的生成和决策树的修剪。

决策树 算法思想


模型定义

分类决策树模型是一种描述对实例进行分类的树形结构。决策树由结点(node)和有向边(directed edge)组成。结点有两种类型:内部结点(internal node)和叶结点(leaf node)。内部结点表示一个特征或属性(features),叶结点表示一个类(labels)。

用决策树对需要测试的实例进行分类:从根节点开始,对实例的某一特征进行测试,根据测试结果,将实例分配到其子结点;这时,每一个子结点对应着该特征的一个取值。如此递归地对实例进行测试并分配,直至达到叶结点。最后将实例分配到叶结点的类中。

例子:邮件分类系统

首先检测发送邮件域名地址。如果地址为 myEmployer.com, 则将其放在分类 "无聊时需要阅读的邮件"中。 如果邮件不是来自这个域名,则检测邮件内容里是否包含单词 "曲棍球" , 如果包含则将邮件归类到 "需要及时处理的朋友邮件", 如果不包含则将邮件归类到 "无需阅读的垃圾邮件" 。

决策树-流程图

 

决策树与if-then规则

现在我们可以更抽象一些。决策树可以看成一个if-then规则的集合:由决策树的根结点到叶结点的每一条路径构建一条规则;路径上的内部结点的特征对应着规则的条件,而叶结点对应着分类的结论。决策树的路径和其对应的if-then规则集合是等效的,它们具有一个重要的性质:互斥并且完备。这里的意思是说:每一个实例都被一条路径或一条规则所覆盖,而且只被一条规则所覆盖。

决策树的学习

决策树学习算法包含特征选择、决策树的生成与剪枝过程。决策树的学习算法通常是递归地选择最优特征,并用最优特征对数据集进行分割。开始时,构建根结点,选择最优特征,该特征有几种值就分割为几个子集,每个子集分别递归调用此方法,返回结点,返回的结点就是上一层的子结点。直到所有特征都已经用完,或者数据集只有一维特征为止。

使用伪代码:

扫描二维码关注公众号,回复: 9401198 查看本文章
def createBranch():
'''
此处运用了迭代的思想。 感兴趣可以搜索 迭代 recursion, 甚至是 dynamic programing。
'''
    检测数据集中的所有数据的分类标签是否相同:
        If so return 类标签
        Else:
            寻找划分数据集的最好特征(划分之后信息熵最小,也就是信息增益最大的特征)
            划分数据集
            创建分支节点
                for 每个划分的子集
                    调用函数 createBranch (创建分支的函数)并增加返回结果到分支节点中
            return 分支节点

算法特点

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

缺点:容易过拟合。

适用数据类型:数值型和标称型。

特征选择


决策树学习的关键就是如何选择最优划分属性

1.信息增益

著名的ID3决策树学习算法就是以信息增益为准则来选择划分属性

熵(entropy): 熵指的是体系的混乱的程度,在不同的学科中也有引申出的更为具体的定义,是各领域十分重要的参量。

信息论(information theory)中的熵(香农熵): 是一种信息的度量方式,表示信息的混乱程度,也就是说:信息越有序,信息熵越低。例如:火柴有序放在火柴盒里,熵值很低,相反,熵值很高。

信息增益(information gain): 在划分数据集前后信息发生的变化称为信息增益。

了解更多信息熵https://www.zhihu.com/question/22178202

熵(entropy) 
熵是表示随机变量不确定性的度量。设X是一个取有限个值的离散随机变量,其概率分布为

则随机变量的熵定义为

条件熵(conditional entropy)

设有随机变量(X,Y),其联合概率分布为

条件熵H(Y|X)表示在已知随机变量X的条件下随机变量Y的不确定性。随机变量X给定的条件下随机变量Y的条件熵H(Y|X),定义为X给定条件下Y的条件概率分布的熵对X的数学期望

信息增益(information gain)

信息增益表示得知特征X的信息而使得类Y的信息的不确定性减少的程度。特征A对训练数据集D的信息增益g(D,A),定义为集合D的经验熵H(D))与特征A给定条件下D的经验条件熵H(D|A)之差,即

如果选择一个特征后,信息增益最大(信息不确定性减少的程度最大),那么我们就选取这个特征。

信息增益恰好是:信息熵-条件熵。

2.信息增益率

实际上,信息增益准则对可取值数目较多的属性有所偏好,为了减少这种偏好可能带来的不利影响,C4.5决策树算法不直接使用信息增益,而是使用增益率来选择最优划分属性

特征A对训练数据集D的信息增益比gR(D,A)定义为其信息增益g(D,A)与训练数据集D关于特征A的值的熵HA(D)之比,即

需要注意的是,增益率准则对可取值数目较少对属性有所偏好。所以,C4.5算法并不是直接选择增益率最大对候选划分属性,而是用了一个启发式:先从候选划分属性中找出信息增益高于平均水平的属性,再从中选择增益率最高的。

信息增益与信息增益率计算详例参考 https://www.cnblogs.com/feiyumo/p/9284490.html

3.基尼指数

CART决策树算法使用基尼指数来选择划分属性,这个准则不再用熵和增益来衡量数据集的纯度,而是用基尼值来度量。直观来说这个值反映了从数据集中随机抽取两个样本,其类别标记不一致的概率。因此基尼值越小,则数据集的纯度越高。

项目案例-信息增益的代码实现


判定鱼类与非鱼类

项目概述

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

特征:

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

创建数据

海洋生物数据

创建一组示例数据

def create_dataset():
    data_set = [[1, 1, 'yes'],
            [1, 1, 'yes'],
            [1, 0, 'no'],
            [0, 1, 'no'],
            [0, 1, 'no']]
    features = ['no surfacing', 'flippers']
    return data_set, features

分析数据

计算给定数据的信息熵(根节点信息熵)

def calcShannonEnt(dataSet):
    # 求list的长度,表示计算参与训练的数据量
    numEntries = len(dataSet)
    # 计算分类标签label出现的次数
    labelCounts = {}
    # the the number of unique elements and their occurrence
    for featVec in dataSet:
        # 将当前实例的标签存储,即每一行数据的最后一个数据代表的是标签
        currentLabel = featVec[-1]
        # 为所有可能的分类创建字典,如果当前的键值不存在,则扩展字典并将当前键值加入字典。每个键值都记录了当前类别出现的次数。
        if currentLabel not in labelCounts.keys():
            labelCounts[currentLabel] = 0
        labelCounts[currentLabel] += 1

    # 对于 label 标签的占比,求出 label 标签的香农熵
    shannonEnt = 0.0
    for key in labelCounts:
        # 使用所有类标签的发生频率计算类别出现的概率。
        prob = float(labelCounts[key])/numEntries
        # 计算香农熵,以 2 为底求对数
        shannonEnt -= prob * log(prob, 2)
    return shannonEnt

按照给定特征划分数据集,即划分分支节点数据集

def splitDataSet(dataSet, index, value):
    """splitDataSet
        依据index特征列进行分类,编列dataset,如果index列的数据等于value的时候,就要将此行划分到新的数据集中
    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:
            # chop out index used for splitting
            # [:index]表示前index行,即若 index 为2,就是取 featVec 的前 index 行
            reducedFeatVec = featVec[:index]
            '''           
            music_media.append(object) 向列表中添加一个对象object
            music_media.extend(sequence) 把一个序列seq的内容添加到列表中 (跟 += 在list运用类似, music_media += sequence)
            1、使用append的时候,是将object看作一个对象,整体打包添加到music_media对象中。
            2、使用extend的时候,是将sequence看作一个序列,将这个序列和music_media序列合并,并放在其后面。
            music_media = []
            music_media.extend([1,2,3])
            print music_media
            #结果:
            #[1, 2, 3]
            music_media.append([4,5,6])
            print music_media
            #结果:
            #[1, 2, 3, [4, 5, 6]]
            '''
            reducedFeatVec.extend(featVec[index+1:])
            # [index+1:]表示从跳过 index 的 index+1行,取接下来的数据
            # 收集结果值 index列为value的行【该行需要排除index列】
            retDataSet.append(reducedFeatVec)
    return retDataSet

选择最好的数据集划分方式

def chooseBestFeatureToSplit(dataSet):
    """chooseBestFeatureToSplit
       选择最好的特征
    Args:
        dataSet 数据集
    Returns:
        bestFeature 最优的特征列
    """
    # 求第一行有多少列的 Feature, 最后一列是label列嘛
    numFeatures = len(dataSet[0]) - 1
    # 数据集的原始信息熵
    baseEntropy = calcShannonEnt(dataSet)
    # 最优的信息增益值, 和最优的Featurn编号
    bestInfoGain, bestFeature = 0.0, -1
    # iterate over all the features
    for i in range(numFeatures):
        # create a list of all the examples of this feature
        # 获取对应的feature下的所有数据
        featList = [example[i] for example in dataSet]
        # get a set of unique values
        # 获取剔重后的集合,使用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 * calcShannonEnt(subDataSet)
        # gain[信息增益]: 划分数据集前后的信息变化, 获取信息熵最大的值
        # 信息增益是熵的减少或者是数据无序度的减少。最后,比较所有特征中的信息增益,返回最好特征划分的索引值。
        infoGain = baseEntropy - newEntropy
        print 'infoGain=', infoGain, 'bestFeature=', i, baseEntropy, newEntropy
        if (infoGain > bestInfoGain):
            bestInfoGain = infoGain
            bestFeature = i
    return bestFeature

训练算法:构造树的数据结构

def majorityCnt(classList):
    major_label = Counter(classList).most_common(1)[0]
    return major_label

def creat_tree(dataset, feature_names):
    """
    递归创建决策树
    :param dataset: 数据集,每递归一层删减最优特征列
    :param feature_names: 特征名称,每递归一层删减最优特征列名称
    :return:
    """
    labels = [data[-1] for data in dataset]
    # 第一个停止条件:所有的类标签完全相同,则直接返回该类标签。
    if labels.count(labels[0]) == len(dataset):
        return labels[0]
    # 如果数据集只有1列,那么出现label次数最多的一类,作为结果
    # 第二个停止条件:使用完了所有特征,仍然不能将数据集划分成仅包含唯一类别的分组。
    if len(dataset[0]) == 1:
        return majorityCnt(labels)

    # 信息增益,选择最优的列
    best_feat_index = chooseBestFeatureToSplit(dataset)
    # 获取feature的名称作为树节点名称
    best_feat_name = feature_names[best_feat_index]
    # 初始化本层决策树节点和分支
    tree = {best_feat_name: {}}
    del (labels[best_feat_index])
    # 依据最优列划分分支数据,继续迭代以上信息增益步骤
    best_feat_column = [data[best_feat_index] for data in dataset]
    best_feat_class = set(best_feat_column)
    for one_class in best_feat_class:
        # 分支数据,且不包含最优列数据
        branch_data = split_dataset(dataset, best_feat_index, one_class)
        tree[best_feat_name][one_class] = creat_tree(branch_data, labels[:])
    
    return tree

测试算法:使用决策树执行分类

def classify(decision_tree, feat_labels, test_data):
    """
    以字典的方式递归遍历树,找到最终分类
    :param decision_tree: 决策树 dict结构
    :param feat_labels: 特征名称 ["xxx","xx"]
    :param test_data: 测试数据,列表[1,1]
    :return:
    """
    # 获取tree的根节点对于的key值
    top_key = decision_tree.keys()[0]
    # 通过key得到根节点对应的value
    value_dict = decision_tree[top_key]
    # 判断根节点名称获取根节点在features中的为重
    feat_index = feat_labels.index(top_key)
    # 获取测试数据的特征值,决定分支走向
    test_feat_value = test_data[feat_index]
    class_label = value_dict[test_feat_value]

    # 判断分枝是否结束: 判断valueOfFeat是否是dict类型
    if isinstance(class_label, dict):
        class_label = classify(class_label, feat_labels, test_data)
    
    return class_label

注意问题


连续与缺失值

1、连续值处理

以上的属性值都是离散的,现实中通常会遇到连续的属性值。这个时候,连续属性离散化技术就可以用上啦,C4.5决策树算法采用的是最简单的策略二分法来对连续值进行处理。给西瓜属性考虑密度的属性,首先把不同样本的密度值按照从小到大排序,然后找到候选划分点,把每个候选划分点的信息增益算出来,取max的值作为密度的信息增益。第二次max就是找到最优划分属性了。

这里需要注意的是,与离散属性值不同,若当前结点划分属性为连续的,那么该属性还可以作为后面结点的划分属性。

2、缺失值处理

如果样本中有缺失值,那么会产生以下两个问题:

(1)如何在属性值缺失的情况下计算属性的信息增益,从而进行划分属性的选择?

(2)给定划分属性,如果样本在该属性值上是缺失的,如何对这个样本进行划分?

针对(1)的处理非常简单,假设17个样本在“色泽”属性上只有14个是有值的,另外3个是缺失的,那么就以这14个样本的属性值计算信息增益,然后把这个值乘以14/17,相当于乘上一个权重,当作这全部17个样本的信息增益。然后进行后续的属性划分。

针对(2)的处理也很简单,本来每个样本在结点中的权重都是1,当样本「8」在“纹理”上出现缺失值,那么在“纹理=清晰”、“纹理=模糊”、“纹理=稍糊”这三个分支中都会出现样本「8」,只是权重不再是1,而是7/15,5/15,3/15(这三个权重的来源是这三个分支中各个样本的比例,不包括这个样本「8」)
 

发布了36 篇原创文章 · 获赞 18 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_42022528/article/details/104369781