model

相关模型介绍

分类模型

决策树

决策树(Decision Tree)算法是一种基本的分类与回归方法,是最经常使用的数据挖掘算法之一。我们这章节只讨论用于分类的决策树。

决策树模型呈树形结构,在分类问题中,表示基于特征对实例进行分类的过程。它可以认为是 if-then 规则的集合,也可以认为是定义在特征空间与类空间上的条件概率分布。

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

决策树场景

一个叫做 "二十个问题" 的游戏,游戏的规则很简单: 参与游戏的一方在脑海中想某个事物,其他参与者向他提问,只允许提 20 个问题,问题的答案也只能用对或错回答。问问题的人通过推断分解,逐步缩小待猜测事物的范围,最后得到游戏的答案。

  • 首先检测发送邮件域名地址。如果地址为 myEmployer.com, 则将其放在分类 "无聊时需要阅读的邮件"中。

  • 如果邮件不是来自这个域名,则检测邮件内容里是否包含单词 "曲棍球" , 如果包含则将邮件归类到 "需要及时处理的朋友邮件"。

  • 如果不包含则将邮件归类到 "无需阅读的垃圾邮件" 。

  • 在 1-16 么?在

  • 在 9-16 么?在

  • 在 13-16 么? 在

  • 在 15 -16 么? 在

  • 是 15 么? 不是

答案:16 号球队夺冠。

tips 最多通过 5 次机会就猜测出哪只球队是冠军球队。这个 5 代表的是 5 bit(比特),相当于是 2^5=32,通过 5 bit 可以描述出这个信息量。 如果是 64 支球队,那么需要 6 bit 来描述这个信息量。

信息熵(香农熵)

香农通过世界杯预测冠军的问题提出了信息熵,故信息熵也叫香农熵,信息熵用来度量信息量;信息量的度量等于不确定性的多少。 通过上述问题描述,相信读者已经了解了信息量的比特数和所有可能情况的对数函数 log 有关。(log32 = 5, log64 = 6)。

当然,如果是你对足球比较了解,通过历届球队的实力比较了解,以及世界杯参赛球队球星也比较了解。你可能会对一些球队能力的强弱进行分组, 因为你了解的信息量会多一些,所以猜测的不确定性就会小一些,从而你可能不需要 5 次就可以猜中。故香农提出了如下公式来计算信息熵: H(x) = -(P1 logP1 + P2 logP2 + P3 logP3 + ... + P32 logP32), 其中P1、P2、P3...P32 分别代表 32 支球队夺冠的概率。

信息增益

信息增益 = 信息熵 - 条件熵。

条件熵:在你得知一个信息(条件)后,得出结论还需要多少信息量。

信息增益越大,说明你得到的这个信息就越重要。

决策树定义

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

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

决策树工作原理

如何构造一个决策树?

使用 create_branch() 方法,伪代码如下所示:

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

决策树开发流程

  • 收集数据: 可以使用任何方法。

  • 准备数据: 树构造算法 (这里使用的是ID3算法,只适用于标称型数据,这就是为什么数值型数据必须离散化。 还有其他的树构造算法,比如CART)。

  • 分析数据: 可以使用任何方法,构造树完成之后,我们应该检查图形是否符合预期。

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

  • 测试算法: 使用训练好的树计算错误率。

  • 使用算法: 此步骤可以适用于任何监督学习任务,而使用决策树可以更好地理解数据的内在含义。

决策树算法特点

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

  • 缺点: 容易过拟合。

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

决策树案例分析

实例一 鱼类非鱼类问题

项目概述

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

特征:

  • 不浮出水面是否可以生存。

  • 是否有脚蹼(du 第三声)。

构造数据集

# 构造数据集
def create_data_set():
    """
    :return: data_set 数据集, labels 标签数组
    """
    data_set = [
        [1, 1, 'yes'],
        [1, 1, 'yes'],
        [1, 0, 'no'],
        [0, 1, 'no'],
        [0, 1, 'no'],
        [0, 0, 'no']
    ]

    labels = ['no surfacing', 'flippers']

    return data_set, labels

计算香农熵(经验熵)shannonEnt

# 计算香农熵(经验熵)shannonEnt
def calc_shannon_ent(data_set):
    """

    :param data_set: 数据集
    :return: shannon_ent 香农熵

    """

    # 计算 list 的长度,表示计算参与训练的数据量
    num_entries = len(data_set)
    # 计算分类标签 label 出现的次数
    label_counts = {}

    for feat_vec in data_set:
        # 存储当前实例的标签存储,即每一行数据的最后一个数据代表的是标签
        current_label = feat_vec[-1]
        # 为所有可能的分类创建字典,如果当前值不存在,则扩展字典并将当前值加入字典。每个键值都记录了当前类别出现的次数。
        if current_label not in label_counts.keys():
            label_counts[current_label] = 0
        label_counts[current_label] += 1

    # 对于 label 标签的占比,求出 label 标签的香农熵
    shannon_ent = 0.0
    for key in label_counts:
        # 使用所有类标签的发生频率计算类别出现的概率
        prob = float(label_counts[key]) / num_entries
        # 计算香农熵
        shannon_ent -= prob * log(prob, 2)

    return shannon_ent

将指定特征值等于 value 的行剩下列作为子数据集

# 将指定特征值等于 value 的行剩下列作为子数据集
def split_data_set(data_set, index, value):
    """
    (通过遍历 data_set 数据集,求出 index 对应的 column 列的值为 value 的行)
    就是依据 index 列进行分类,如果index列的数据等于 value 的时候,就要将 index 划分到我们创建的新的数据集中

    :param data_set: 数据集                 待划分的数据集
    :param index: 每一行的 index 列          划分数据集的特征
    :param value: index 列对应的 value 值    需要返回的特征的值
    :return:
        ret_data_set 指定特征值等于 value 的行剩下列(不包含值等于该 value 的列)作为子数据集
    """
    ret_data_set = []
    for feat_vec in data_set:
        # index 列为 value 的数据集[该数据集需要排除 index 列]
        # 判断 index 列的值是否为 value
        if feat_vec[index] == value:
            # chop out index used for splitting
            # [:index]表示前 index 行,即若 index 为 2,就是取 feat_vec 的前 2 行
            reduced_feat_vec = feat_vec[:index]
            '''
            请百度查询一下:  extend和append的区别
            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]]

            music_media.extend([7,8,9])
            print music_media
            #结果: 
            #[1, 2, 3, [4, 5, 6], 7, 8, 9]
            '''
            # 取 index+1 行开始,后面的所有行数据
            reduced_feat_vec.extend(feat_vec[index + 1:])

            # 收集结果值 index 列为 value 的行[该行需要排除 index 列]
            ret_data_set.append(reduced_feat_vec)
    return ret_data_set

选择信息增益最大的特征列

def choose_best_feature_to_split(data_set):
    """
    :param data_set: 数据集
    :return:
        best_feature 最优的特征列
    """
    # 求第一行有多少列的 Feature, 最后一列是 label 列
    num_features = len(data_set[0]) - 1
    # 数据集的原始信息熵
    base_entropy = calc_shannon_ent(data_set)
    # 最优的信息增益值, 和最优的 feature 编号
    best_info_gain, best_feature = 0.0, -1

    # iterate over all the features
    for i in range(num_features):
        # create a list of all the examples of this feature
        # 获取对应的 feature 下的所有数据
        feat_list = [example[i] for example in data_set]
        # get a set of unique values
        # 获取剔重后的集合,使用set对list数据进行去重
        unique_vals = set(feat_list)
        print("unique_vals:", unique_vals)
        # 创建一个临时的信息熵
        new_entropy = 0.0
        # 遍历某一列的 value 集合,计算该列的信息熵
        # 遍历当前特征中的所有唯一属性值,对每个唯一属性值划分一次数据集,计算数据集的新熵值,并对所有唯一特征值得到的熵求和。
        for val in unique_vals:
            sub_data_set = split_data_set(data_set, i, val)
            # 计算概率
            prob = len(sub_data_set) / float(len(data_set))
            print("prob:", prob)
            # 计算信息熵
            new_entropy += prob * calc_shannon_ent(sub_data_set)
            print("new_entropy:", new_entropy)
        # gain[信息增益]: 划分数据集前后的信息变化, 获取信息熵最大的值
        # 信息增益是熵的减少或者是数据无序度的减少。最后,比较所有特征中的信息增益,返回最好特征划分的索引值。
        info_gain: float = base_entropy - new_entropy
        print('info_gain=', info_gain, 'best_feature=', i, base_entropy, new_entropy)
        if info_gain > best_info_gain:
            best_info_gain = info_gain
            best_feature = i
    return best_feature

构建决策树

def create_tree(data_set, labels):
    """
    :param data_set: 数据集
    :param labels: label 标签
    :return: 返回决策树
    """
    class_list = [example[-1] for example in data_set]
    # 如果数据集的最后一列的第一个值出现的次数=整个集合的数量,也就说只有一个类别,就只直接返回结果就行
    # 第一个停止条件: 所有的类标签完全相同,则直接返回该类标签。
    # count() 函数是统计括号中的值在list中出现的次数
    if class_list.count(class_list[0]) == len(class_list):
        return class_list[0]
    # 如果数据集只有 1 列,那么最初出现 label 次数最多的一类,作为结果
    # 第二个停止条件: 使用完了所有特征,仍然不能将数据集划分成仅包含唯一类别的分组。
    if len(data_set[0]) == 1:
        return majority_cnt(class_list)
    # 选择最优的列,得到最优列对应的label含义
    best_feat = choose_best_feature_to_split(data_set)
    # 获取label的名称
    best_feat_label = labels[best_feat]
    # 初始化myTree
    my_tree = {best_feat_label: {}}
    # 注: labels列表是可变对象,在PYTHON函数中作为参数时传址引用,能够被全局修改
    # 所以这行代码导致函数外的同名变量被删除了元素,造成例句无法执行,提示'no surfacing' is not in list
    del (labels[best_feat])
    # 取出最优列,然后它的branch做分类
    feat_vals = [example[best_feat] for example in data_set]
    unique_vals = set(feat_vals)
    for val in unique_vals:
        # 求出剩余的标签label
        sub_labels = labels[:]
        # 遍历当前选择特征包含的所有属性值,在每个数据集划分上递归调用函数create_tree()
        my_tree[best_feat_label][val] = create_tree(split_data_set(data_set, best_feat, val), sub_labels)
        # print('myTree', val, my_tree)
    return my_tree

通过输入的特征,预测分类

def classify(input_tree, feat_labels, test_vec):
    """
    给输入的节点,进行分类
    :param input_tree: 决策树模型
    :param feat_labels: feature标签对应的名称
    :param test_vec: 测试输入的数据
    :return:
        class_label 分类的结果值,需要映射 label 才能知道名称
    """
    # 获取 tree 的根节点对于的 key 值
    first_str = list(input_tree.keys())[0]
    # 通过key得到根节点对应的value
    second_dict = input_tree[first_str]
    # 判断根节点名称获取根节点在label中的先后顺序,这样就知道输入的testVec怎么开始对照树来做分类
    feat_index = feat_labels.index(first_str)
    # 测试数据,找到根节点对应的label位置,也就知道从输入的数据的第几位来开始分类
    key = test_vec[feat_index]
    feat_val = second_dict[key]
    print('+++', first_str, 'xxx', second_dict, '---', key, '>>>', feat_val)
    # 判断分枝是否结束: 判断 feat_val 是否是 dict 类型
    if isinstance(feat_val, dict):
        class_label = classify(feat_val, feat_labels, test_vec)
    else:
        class_label = feat_val
    return class_label

选择出现次数最多的一个结果

def majority_cnt(class_list):
    """
    选择出现次数最多的一个结果
    :param class_list: label 列的集合
    :return: best_feature 最优的特征列
    """
    # -----------majorityCnt的第一种方式 start------------------------------------
    class_count = {}
    for vote in class_list:
        if vote not in class_count.keys():
            class_count[vote] = 0
        class_count[vote] += 1
    # 倒叙排列classCount得到一个字典集合,然后取出第一个就是结果(yes/no),即出现次数最多的结果
    sorted_class_count = sorted(class_count.iteritems(), key=operator.itemgetter(1), reverse=True)
    print('sortedClassCount:', sorted_class_count)
    return sorted_class_count[0][0]
    # -----------majorityCnt的第一种方式 end------------------------------------

    # # -----------majorityCnt的第二种方式 start------------------------------------
    # major_label = Counter(classList).most_common(1)[0]
    # return major_label
    # # -----------majorityCnt的第二种方式 end------------------------------------

GBDT

GBDT: Gradient Boost Decision Tree。DT-Decision Tree决策树,GB是Gradient Boosting,是一种学习策略,GBDT的含义就是用Gradient Boosting的策略训练出来的DT模型。可以处理二分类问题。

LightGBM

XGBoost

分类树与回归树

XGBoost是以CART回归树作为基本分类器。 分类树:分类树的样本输出都是以类别的形式,比如说判断用户会不会购买华为Mate40,判断西瓜是甜还是不甜。

回归树:回归树的样本输出是数值的形式,比如给某人发放房屋贷款的数额,给某人发放的红包金额。

Boost介绍

Boost可以用于回归和分类问题,它每一步会产生一个弱分类器 (如决策树),然后通过加权累加起来变成一个强分类器。比如每一步都会产生一个f(x),F(x)=sum(fi(x)),其实就是一堆分类器通过加权合并成一个强分类器。

提升树

首先要明确一点,xgboost 是基于提升树的。

什么是提升树,简单说,就是一个模型表现不好,我继续按照原来模型表现不好的那部分训练第二个模型,依次类推。

来几个形象的比喻就是:

做题的时候,第一个人做一遍得到一个分数,第二个人去做第一个人做错的题目,第三个人去做第二个人做错的题目,以此类推,不停的去拟合从而可以使整张试卷分数可以得到100分(极端情况)。

把这个比喻替换到模型来说,就是真实值为100,第一个模型预测为90,差10分,第二个模型以10为目标值去训练并预测,预测值为7,差三分,第三个模型以3为目标值去训练并预测,以此类推。

Random Forests

Last updated