机器学习之决策树(ID3 C4.5 CART)

优缺点和适用范围:

优点:

  • 计算不那么费时
  • 便于人类的理解
  • 可处理非相关特征

缺点:

  • 容易过拟合(关于如何尽量避免这种缺点带来的影响,本篇文章后期(剪枝)有介绍和相关代码)

适用范围:

  • 数值型和非数值型数据均可

决策树的构型其实也是在模拟人的的思维过程:
一个人在获得一个结论的过程中总是需要多步判断。

但是在开始学习决策树之前应该先了解一些信息论的知识
假设x 为一种分类, p(x) 为这种分类在总的分类中出现的概率
1. 信息的概念:
信息是指人类社会中传播的一切内容。我们显然需要将信息进行量化度量,香农于1948年提出了信息熵(或称香农熵)的概念,用于量化信息
2. 信息熵:
信息的大小很大程度上依赖于信息的不确定性。这也是信息论一个基本的思想,即,一件小概率事件的发生要比一件大概率事件的发生提供更多的信息。信息的不确定性越大,其背后隐藏的信息更大。
3. 信息(或称“自信息”)的计算:
信息则被定义为I(x) = -log(p(x)) ,其中log以2为底。用于表示该标签单独的信息大小
4. 信息熵的计算:
而熵则被定义为 H = SUM(I(x) * p(x)) 即, 对于所有的标签,I(x) * p(x) 的加和。即每一种标签的出现的概率和这种标签下信息的乘积
5. 条件熵:
已知了一种分类条件,首先计算在该分类条件下分得的每一类的信息熵 * 这一类出现概率。最后将所有的进行加和得到了条件熵。理论上来讲,不管哪一种分类方式,条件熵都会比样本的信息熵要小,一是,公式中可以直接看出来。二是,如果将一个数据集进行了分类,显然数据在向着有序的方向在进行。但是,哪一种分类方式能使得当前数据的混乱程度最小呢??因此引出了 信息增益的概念
6. 信息增益:
信息增益则被定义为 熵 - 条件熵
因此信息增益为正代表着数据向着有序的方向进行
而决策树所做的事情就是绘出一个能让熵下降最快的树,因此每次都要选择在当前情况下信息增益最大的分类方法

一. ID3决策树

有了以上的知识:我们可以开始对于ID3决策树怎么创建进行理解了
决策树的建立是一个递归的过程,事实上如果大家对于树这种结构有一些了解的话可以很快理解为什么这应该是一个递归的过程,因此不再赘述。
首先我们拿到了一个用于训练的样本集合,计算得到了该样本的信息熵。然后以每一个feature分别做分类,获得了每种分类的条件熵。从而计算得到每一种分类的信息增益,其中信息增益最大的那一种对应的就是数据混乱程度下降最快的方向,因此使用这一种的分类,这一分类即为这一点上的决策节点。然后基于这种分类将数据划分成多个子集合,分别再将这些子集合进行如上操作。从而建立决策树

计算熵的代码如下:

def calculate_entropy(data_set):
    """
    used to calculate entropy
    :param data_set: all of labels
    :return: entropy
    """
    dirc = {}
    length = data_set[:, 0].size
    label_pos = data_set[0].size -1
    # label_list = label_set.tolist()
    res = 0
    for i in data_set[:, label_pos]:
        if i[0, 0] in dirc.keys():
            dirc[i[0, 0]] += 1
        else:
            dirc[i[0, 0]] = 1
    for key in dirc.keys():
        prob = float(dirc[key])/float(length)
        res -= prob * math.log(prob, 2)  # 第二个参数才是基数
    return res

那么在一系列数据中想要知道怎样决策,熵下降最快,依靠如下代码计算哪一个特征最适合做数据划分:
思想比较简单,就是分别根据每一个特征进行数据集的划分,找到熵最小的,这个就是决策树的头节点

def split_data_set(data_set, condition):
    """
    split the data set according to the condition
    :param data_set: data set
    :param condition: feature number
    :return: the data sets after split
    """
    dict = {}
    length = data_set[:, 0].size
    width = data_set[0, :].size
    choice = []
    for i in range(0, length):
        if data_set[i, condition] not in dict.keys():
            dict[data_set[i, condition]] = []
            choice.append(data_set[i, condition])
        dict[data_set[i, condition]].append([data_set[i, j] for j in range(0, width) if j is not condition])
    return [np.mat(dict[key]) for key in dict.keys()], choice


def choose_best_conditional_entropy(data_set):
    """
    actually it is choose the best condition which makes the increament is biggest
    :param data_set: ..
    :return: the condition which performs best
    """
    width = data_set[0, :].size
    length = data_set[:, 0].size
    origin_entropy = calculate_entropy(data_set)
    condition = -1
    increament = 0
    for i in range(0, width-1):
        cond_entropy = 0
        curr_data_sets, choice = split_data_set(data_set, i)
        for data in curr_data_sets:
            curr_prob = data[:, 0].size/length
            cond_entropy += curr_prob * calculate_entropy(data)
        if origin_entropy - cond_entropy > increament:
            increament = origin_entropy - cond_entropy
            condition = i
    return condition

创建决策树:

def most_probably_result(data_set):
    """
    when all of features have been considered ,still there are different classification
    we just choose the most possible one
    :param data_set: the data left
    :return: the most possible result
    """
    dict = {}
    length = data_set[:, 0].size
    res = ""
    for i in range(0, length):
        if data_set[i, 0] not in dict.keys():
            dict[data_set[i, 0]] = 0
        dict[data_set[i, 0]] += 1
    for key in dict.keys():
        if res == "" or dict[res] < dict[key]:
            res = key
    return res


def create_decision_tree(data_set, feature_name):
    """
    create decision tree
    :param data_set: data set
    :param label_set: all of labels
    :return: the dictionary of tree
    """
    # 出口条件
    width = data_set[0, :].size
    length = data_set[:, 0].size
    flag = 0
    if width == 1:
        return most_probably_result(data_set)
    for i in range(0, length):
        if data_set[i, width-1] != data_set[0, width-1]:
            flag = 1
            break
    if flag == 0:
        return data_set[0, width-1]

    condition = choose_best_conditional_entropy(data_set)
    splited_data_set, choice = split_data_set(data_set, condition)
    dict = {feature_name[condition]: {}}
    len_of_choice = len(choice)
    sub_feature_name = [i for i in feature_name if i is not feature_name[condition]]
    for i in range(0, len_of_choice):
        dict[feature_name[condition]][choice[i]] = create_decision_tree(splited_data_set[i], sub_feature_name)
    return dict

效果:



二. C4.5 决策树

实际上,ID3决策树还是存在一些问题的,比如,假设有n个样本,如果有一个特征(比如:编号,1,2,3.....n)对于每一个样本都不一样。在计算熵时候,显然,根据这个特征获得的条件熵最小,信息增益最大,所以编号就会成为这棵树的根结点,然后,这个结点会有n个分支,每一个分支指向一个叶子结点(即一个样本)
这显然不是我们想要的,因为这样的决策树,只适用于测试样本,无法适应新的样本。
为解决这个问题出现了 C4.5
C4.5 引出了一个新的概念:
增益率:
前面我们已经介绍了信息增益:这里信息增益用Gain(D, a)表示,其中D表示样本集合,a表示所选定的分类标准。
1. 分类标准(或者说 属性)的“固有值”(intrinsic value)定义为:

IV(a) = - \sum_{v=1}^V\frac{|D^v|}{|D|}log_2\frac{|D^v|}{|D|}

其中:
|B| 这种表示方式,表达的是B这个集合中元素的数量
D^v 表示的是a这一特征中的取v的样本的集合
如果定义:p(v) = \frac{|D^v|}{|D|}
IV(a)可以被写为:

IV(a) = -\sum_{v=1}^Vp(v)log_2p(v)

有没有觉得上式有一些眼熟,没错,如果a不是分类标准而是样本的最终类别的话,IV(a)就是信息熵
信息熵用于描述数据的不确定性,因此,如果a这一特征下的的数据取值数目越多,IV(a)越大。举个例子:甜度这一属性:取值为甜、不甜的 与 取值为 甜、还好、不甜的 相比IV(a)更小

2. 增益率定义为:

Gain\_ratio(D, a) = \frac{Gain(D, a)}{IV(a)}

这样的设定之后会发现,增益率同时考虑了信息增益和可取值数量的问题。但是由于公式的原因,实际上,增益率更加偏好可取值数目少的属性。因此C4.5 决策树并没有直接使用增益率,而是先挑出信息增益高于平均值的属性,然后在从其中挑出增益率最高的
修改代码:

def calc_shannon_ent(data_set, pos):
    num_entries = len(data_set)  # 获得数据量
    label_counts = {}  # 为标签出现次数计数,字典
    for feat_vec in data_set:
        current_label = feat_vec[pos]  # 获得数据的标签,因为数据的最后一项是标签
        if current_label not in label_counts.keys():
            label_counts[current_label] = 0
        label_counts[current_label] += 1  # 计数
    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


def calculate_gain_ratio(gain, data_set, feat_pos):
    iv = calc_shannon_ent(data_set, feat_pos)
    return gain/float(iv)


def choose_best_feature_to_split(data_set):
    num_features = len(data_set[0]) - 1
    base_entropy = calc_shannon_ent(data_set, -1)
    best_gain_ratio = 0.0
    best_feature = -1
    entropy_list = [0] * num_features
    for i in range(num_features):
        feat_list = [example[i] for example in data_set]
        unique_vals = set(feat_list)
        new_entropy = 0.0
        for value in unique_vals:
            sub_data_set = split_data_set(data_set, i, value)
            prob = len(sub_data_set)/float(len(data_set))
            new_entropy += prob * calc_shannon_ent(sub_data_set, -1)
        info_gain = base_entropy - new_entropy
        entropy_list[i] = info_gain
    average_info_gain = sum(entropy_list)/float(num_features)
    for i in range(num_features):
        if entropy_list[i] > average_info_gain:
            new_gain_ratio = calculate_gain_ratio(entropy_list[i], data_set, i)
            if new_gain_ratio > best_gain_ratio:
                best_gain_ratio = new_gain_ratio
                best_feature = i
    return best_feature

对比ID3和C4.5:
数据:

1,young,myope,no,reduced,no lenses
2,young,myope,no,normal,soft
3,young,myope,yes,reduced,no lenses
4,young,myope,yes,normal,hard
5,young,hyper,no,reduced,no lenses
6,young,hyper,no,normal,soft
7,young,hyper,yes,reduced,no lenses
8,young,hyper,yes,normal,hard
9,pre,myope,no,reduced,no lenses
10,pre,myope,no,normal,soft
11,pre,myope,yes,reduced,no lenses
12,pre,myope,yes,normal,hard
13,pre,hyper,no,reduced,no lenses
14,pre,hyper,no,normal,soft
15,pre,hyper,yes,reduced,no lenses
16,pre,hyper,yes,normal,no lenses
17,presbyopic,myope,no,reduced,no lenses
18,presbyopic,myope,no,normal,no lenses
19,presbyopic,myope,yes,reduced,no lenses
20,presbyopic,myope,yes,normal,hard
21,presbyopic,hyper,no,reduced,no lenses
22,presbyopic,hyper,no,normal,soft
23,presbyopic,hyper,yes,reduced,no lenses
24,presbyopic,hyper,yes,normal,no lenses

ID3:

ID3

C4.5:
C4.5

从以上可以清晰的看见C4.5使用增益率的效果

三. CART决策树

以上两种决策树都是通过计算熵来表现数据的混乱程度
而这里即将介绍的是利用“基尼指数”(Gini index)来选择划分属性
基尼值:随机抽取两个样本,其标识不一样的概率
Gini(D) = \sum_{k=1}^{|y|}\sum_{k'\not=k}p_kp_{k'} = 1 - \sum_{k=1}^{|y|}p_k^2
基尼值越小,代表,随机抽取两个样本,其标识一样的概率越大。因此,数据纯度越高
基尼指数:对应于条件熵,表现在某种分类下的对应的尼基值
Gini_index(D,a)=\sum_{v=1}^{V}\frac{|D|}{|D^v|}Gini(D^v)
相比于以上两种决策树,唯一改变的就是计算方式
因此不再对于代码赘述

四.剪枝(pruning)处理

剪枝处理分为了:预剪枝和后剪枝两种处理方式

  • 预剪枝:在建立树之前剪枝
  • 后剪枝:在建立树之后剪枝

怎么剪枝??
剪枝的思想很简单,只要判断如果这个结点作为了子树根结点相比于直接将这个结点处理成叶子结点的准确率是否有提升:
如果有提升:这个结点将会成为子树根结点
如果没有提升:这个结点将会成为该分支下数据集的出现最多的类别

预剪枝:
在即将在这个点上确定结点之前,将这个点是根结点和这个点是叶子结点两种情况分别建立出来。然后分别用测试集判断准确率。比较两种准确率,哪种的准确率高,用哪种。

后剪枝:
在已经建立好的树中,倒着遍历每一个根结点,判断这个结点适合是根结点还是叶子结点,对树做修改

虽然说决策树大多数场合适用于离散值情况,但是并不代表不能应用于连续值情况,一般来说,连续值情况,决策树的处理方式是利用二分将连续值分成两类,进行决策,此类不再赘述

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

推荐阅读更多精彩内容

  • 一、概述 决策树是机器学习中最基础、应用最广泛的算法模型,常用于解决分类和回归问题。 决策树的构建是一种自上而下的...
    郑壮杰阅读 3,373评论 2 1
  • 机器学习中,决策树是一个预测模型;代表对象属性和对象值之间的一种映射关系。树中每个节点表示某个对象,而每个分叉表示...
    swensun阅读 10,517评论 0 4
  • 一. 决策树(decision tree):是一种基本的分类与回归方法,此处主要讨论分类的决策树。在分类问题中,表...
    YCzhao阅读 2,015评论 0 2
  • 前言: 通过第前面的学习介绍了机器学习回归模型创建的流程,并且知道了机器学习要做的事情是找到目标函数,优化它,通过...
    飘涯阅读 6,284评论 4 83
  • 1:与简书结缘 说来也巧,我与简书结缘还是在微信朋友圈看了张露的《为一个人奔赴一座城》开始的。 在这里既有长篇小说...
    追梦CEO阅读 356评论 9 9