机器学习实战 决策树的构造

决策树的构造

我的微信公众号: s406205391; 欢迎大家一起学习,一起进步!!!

​        有一个二十个问题的小游戏,游戏规则很简单:参与游戏的一方在脑海了想某个事物,其他参与者向他提出问题,只允许提问20个问题,问题的答案也只能用对和错来回答。问问题的人通过推断分解,逐步缩小猜测事物的范围。决策树的工作原理与20个问题类似,用户输入一系列数据,然后给出游戏的答案。

        我们经常使用决策树处理分类问题,近来的调查表明决策树也是最经常使用的数据挖掘算法。决策树流行的很重要的原因就是他的工作很简单,不需要了解机器学习的知识。下图所示的流程图就是一个决策树。图中构造了一个假象的邮件分类系统,它首先检测发送邮件的域名地址。如果地址为:myEmployer.com,则将其放在“无聊时需要阅读的邮件”中,如果邮件不是来自这个域名,则检查邮件内容里是否包含单词曲棍球,如果包含则将邮件归类到“需要及时处理的朋友邮件”,如果不包含,则将邮件归类到“无需阅读的垃圾邮件”。

在这里插入图片描述

        决策树的主要优势再与数据形式非常容易理解。决策树的一个重要任务是为了理解数据中所蕴含的知识信息,因此决策树可以使用不熟悉的数据集合,并从中提取出一系列规则,这些机器根据数据集创建规则的过程,就是机器学习的过程。专家系统中经常使用决策树,而且决策树给出的结果往往可以匹敌在当前领域具有几十年工作经验的人类专家。

决策树的一般流程

​        决策树的优点是计算复杂度不高,输出结果易于理解,对中间值的缺失不敏感,可以处理不相干特征数据。缺点是可能会产生过度匹配问题。适用数据为数值型和标称型。

        在构造决策树时,首先需要解决的问题是,当前数据集上,哪个特征在划分数据集中起决定性作用。为了找到决定性特征,划分出最好的结果,我们必须评估每个特征。完成测试之后,原始数据集就被划分为几个数据子集。这些数据子集会分布在第一个决策点的所有分支上。

决策树构建的伪代码如下:

检测数据集中的每个子项是否属于同一分类:

        if so return 类标签
        else
                寻找划分数据集的最好特征
                划分数据集
                创建分支节点
                for 每个划分的子集
                递归调用本函数,并增加返回结果到分支节点中
        return 分支节点

为了解决寻找划分数据集的最好特征问题,就需要引入信息增益的概念。

信息增益

        划分数据的最大原则是:将无序的数据变得更加有序。在划分数据集之前之后信息发生的变化成为信息增益,知道如何计算信息增益,我们就可以计算每个特征值划分数据集获得的信息增益,获得信息增益最高的特征就是最好的选择。这就得引入熵的概念。

​        集合信息的度量方式成为香农熵或者简称为熵。熵定义为信息的期望值。如果待分类的事物可能划分在多个分类之中,则符号xi的信息定义为:

l(x_i) = -log_2p(x_i)

其中p(xi)是选择该分类的概率。那么其所有类别所有可能值包含的信息期望值的计算公式就为:

-\sum_{i=1}^{n}p(x_i)log_2p(x_i)

其中,n是分类数目。

我们首先定义一个函数,来计算数据集的香农熵

from math import log

def calc_shannon(data_set):
    """calculation shannon entropy"""

    # 默认数据集的最后一列是标签列
    feat_vec = [f[-1] for f in data_set]
    label_counts = Counter(feat_vec)

    # 使用信息期望值的计算公式,计算香农熵
    prob_list = [value / len(data_set) for key, value in label_counts.items()]
    shannon_ent = sum([-(prob * log(prob, 2)) for prob in prob_list])
    return shannon_ent

        熵越高,则混合的数据也越多。在得到熵之后,我们就可以按照获取最大信息增益的方法划分数据集。

        另一个度量集合无需程度的方法是基尼不纯度(Gini impurity),简单地说就是冲一个数据集中随机选取子项,度量其被错误风雷刀其他分组的概率。

划分数据集

        分类算法除了需要测量信息熵,还需要划分数据集,度量划分数据集的熵,以便判断当前是否正确地划分了数据集。下面的函数将对每个特征划分数据集的结果计算一次信息熵, 然后判断按照那个特征划分数据集是最好的方式。

def split_data_set(data_set, axis, value):
    """按照给定特征划分数据集
    
    从待划分的数据集中,返回特征值列为value的数据集
    
    :param data_set: 待划分的数据集
    :param axis: 划分数据集的特征所在列的索引
    :param value: 需要返回的特征值
    
    """

    ret_data_set = [feat_vec[: axis] + feat_vec[axis+1:] for feat_vec in data_set if feat_vec[axis] == value]
    return ret_data_set

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

​        在了解了如何划分数据集合求信息熵之后,就可以通过信息增益来选择最好的数据集划分方式了。首先,我们会求整个数据集的原始信息熵,然后遍历没一个特征值,对特征值下的所有特征求一遍信息熵,其和就是该特征值的信息熵。原始信息熵与该特征值的信息熵的差就是该特征值的信息增益。信息增益越大,则该特征值越好划分。

def choose_best_feature_to_split(data_set):
    """选择最好的数据划分方式
    
    函数调用需要满足要求:
    1. 数据必须是由一种列表元素组成的列表,且所有的列表元素都具有相同的数据长度
    2. 数据的最后一列或者每个实例的最后一个元素时当前实例的类别标签 
    
    """

    num_feature = len(data_set[0]) - 1  # 确定特征的数量
    base_ent = calc_shannon(data_set)  # 计算原始的信息熵

    # 遍历所有的特征以及特征值,确定最好的划分数据集的特征
    best_info_gain = 0
    best_feature = -1
    for i in range(num_feature):
        new_ent = 0
        # 计算该特征的信息熵
        for value in set([ex[i] for ex in data_set]):
            sub_data_set = split_data_set(data_set, i, value)
            prob = len(sub_data_set) / len(data_set)
            new_ent += prob * calc_shannon(sub_data_set)
        # 计算信息增益,确定最好的特征
        info_gain = base_ent - new_ent
        if info_gain > best_info_gain:
            best_info_gain = info_gain
            best_feature = i
    return best_feature

递归构建决策树

        现在,我们可以通过前面的函数,来构建决策树了。其工作原理如下:得到原始数据集,然后基于最好的属性值划分数据集,由于特征值可能多于两个,因此可能存在大于两个分支的数据集划分。第一次划分后,数据将被向下传递到树分支的下一个节点,在这个节点上,我们可以再次划分数据。因此我们可以采用递归的原则处理数据集。递归结束的条件是:程序遍历完所有划分数据集的属性,或者每个分支下的所有实例都具有相同的分类。如果所有实例具有相同的分类,则得到一个叶子节点或者终止块。如果遍历完所有属性,但是类标签仍然不是唯一的,那么就采用多数表决的方法,决定该叶子节点的分类。

def create_tree(data_set, labels):
    """构建决策树
    
    递归函数终止条件:
    1. 遍历完所有的特征,返回数量最多的标签作为叶子节点
    2. 所有的类标签完全相同,直接返回包含唯一类别的分组
    
    """
    
    # 递归函数终止条件
    class_list = [ex[-1] for ex in data_set]
    if len(set(class_list)) == 1:
        return class_list[0]
    if len(data_set[0]) == 1:
        return Counter(class_list).most_common(1)[0][0]

    # 寻找最好的特征属性
    best_feat = choose_best_feature_to_split(data_set)
    best_feat_label = labels[best_feat]
    my_tree = {best_feat_label: {}}
    del(labels[best_feat])
    # 递归构建决策树
    for value in set([ex[best_feat] for ex in data_set]):
        sub_labels = labels[:]
        my_tree[best_feat_label][value] = create_tree(split_data_set(data_set, best_feat, value), sub_labels)
    return my_tree

使用决策树执行分类

​        依靠训练数据构造了决策树之后,就可以将它用于实际数据的分类了。下面的函数,可以根据输入的测试数据集构建决策树,并预测测试向量的所属分类

def classify(input_tree, feat_labels, test_vec):
    """使用决策树进行分类"""

    first_str = list(input_tree.keys())[0]
    second_dict = input_tree[first_str]
    feat_index = feat_labels.index(first_str)
    class_label = "unknown"
    for key in second_dict.keys():
        if test_vec[feat_index] == key:
            if type(second_dict[key]).__name__ == 'dict':
                class_label = classify(second_dict[key], feat_labels, test_vec)
            else:
                class_label = second_dict[key]
    return class_label

示例: 使用决策树预测隐形眼镜类型

        接下来,我们通过一个例子讲解如何预测患者需要佩戴的隐形眼镜类型。数据集见lenses.txt(提取码为:uhww)。数据一共分为五列,前四列为特征列,最后一列是分类标签。

#!/usr/bin/env python3
# coding: utf-8
# Author:Shen Yi
# Date :2020/2/15 2:35

"""机器学习实战 决策树的python实现"""

from collections import Counter
from math import log


def calc_shannon(data_set):
    """计算香农熵"""

    feat_vec = [f[-1] for f in data_set]
    label_counts = Counter(feat_vec)

    prob_list = [value / len(data_set) for key, value in label_counts.items()]
    shannon_ent = sum([-(prob * log(prob, 2)) for prob in prob_list])
    return shannon_ent


def split_data_set(data_set, axis, value):
    """按照给定特征划分数据集"""

    ret_data_set = [feat_vec[: axis] + feat_vec[axis+1:] for feat_vec in data_set if feat_vec[axis] == value]
    return ret_data_set


def choose_best_feature_to_split(data_set):
    """选择最好的数据划分方式"""

    num_feature = len(data_set[0]) - 1
    base_ent = calc_shannon(data_set)

    best_info_gain = 0
    best_feature = -1
    for i in range(num_feature):
        new_ent = 0
        for value in set([ex[i] for ex in data_set]):
            sub_data_set = split_data_set(data_set, i, value)
            prob = len(sub_data_set) / len(data_set)
            new_ent += prob * calc_shannon(sub_data_set)
        info_gain = base_ent - new_ent
        if info_gain > best_info_gain:
            best_info_gain = info_gain
            best_feature = i
    return best_feature


def create_tree(data_set, labels):
    """构建决策树"""

    class_list = [ex[-1] for ex in data_set]
    if len(set(class_list)) == 1:
        return class_list[0]
    if len(data_set[0]) == 1:
        return Counter(class_list).most_common(1)[0][0]

    best_feat = choose_best_feature_to_split(data_set)
    best_feat_label = labels[best_feat]
    my_tree = {best_feat_label: {}}
    del(labels[best_feat])
    for value in set([ex[best_feat] for ex in data_set]):
        sub_labels = labels[:]
        my_tree[best_feat_label][value] = create_tree(split_data_set(data_set, best_feat, value), sub_labels)
    return my_tree


def classify(input_tree, feat_labels, test_vec):
    """使用决策树进行分类"""

    first_str = list(input_tree.keys())[0]
    second_dict = input_tree[first_str]
    feat_index = feat_labels.index(first_str)
    class_label = "unknown"
    for key in second_dict.keys():
        if test_vec[feat_index] == key:
            if type(second_dict[key]).__name__ == 'dict':
                class_label = classify(second_dict[key], feat_labels, test_vec)
            else:
                class_label = second_dict[key]
    return class_label


def demo_predict_glass():
    fr = open('data\\Ch03\\lenses.txt')
    lenses = list(map(lambda line: line.strip().split('\t'), fr))
    lenses_label = ['age', 'prescript', 'astigmatic', 'tearRate']
    lenses_tree = create_tree(lenses, lenses_label)
    test_age = input("age: ")
    test_prescript = input("prescript: ")
    test_astigmatic = input("astigmatic: ")
    test_tear_rate = input("tearRate: ")
    lenses_label = ['age', 'prescript', 'astigmatic', 'tearRate']
    class_label = classify(lenses_tree, lenses_label, [test_age, test_prescript, test_astigmatic, test_tear_rate])
    print(class_label)


if __name__ == '__main__':
    demo_predict_glass()

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,527评论 5 470
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,314评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,535评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,006评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,961评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,220评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,664评论 3 392
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,351评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,481评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,397评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,443评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,123评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,713评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,801评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,010评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,494评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,075评论 2 341

推荐阅读更多精彩内容

  • 一. 决策树(decision tree):是一种基本的分类与回归方法,此处主要讨论分类的决策树。在分类问题中,表...
    YCzhao阅读 2,115评论 0 2
  •   决策树(Decision Tree)是一种基本的分类与回归方法,其模型呈树状结构,在分类问题中,表示基于特征对...
    殉道者之花火阅读 4,489评论 2 2
  • 一、前言 你是否玩过二十个问题的游戏?游戏规则很简单:参与游戏的一方在脑海中想某个事物,其他参与者向其提问,只允许...
    Pomodoro_m阅读 2,182评论 0 0
  • 决策树基础概念 决策树分为分类树和回归树两种,分类树对离散变量做决策树,回归树对连续变量做决策树。每个内部节点(非...
    我只要喝点果粒橙阅读 2,476评论 0 0
  • 决策树理论在决策树理论中,有这样一句话,“用较少的东西,照样可以做很好的事情。越是小的决策树,越优于大的决策树”。...
    制杖灶灶阅读 5,817评论 0 25