决策树的Python实现

预备知识

决策树顾名思义是一种树形结构。

决策树在进行训练的时候,会使用某种策略(如ID3)进行最优属性选择,按照最优属性的取值将原始数据集划分为几个数据子集,然后递归的生成一棵决策树。

最优属性选择

显然,我们希望划分以后的数据子集尽可能地属于同一类别。因此,我们需要有一种衡量集合“纯度”的标准。以不同标准进行划分,也就有了各种版本的决策树,著名的有ID3,C4.5,Gini等。在此仅介绍ID3和Gini划分标准。

信息增益

首先,我们定义一个集合 D 的“信息熵”:

Ent(D)=kpklog2(pk)

其中 k 取遍所有的类别。
Ent(D) 的值越小, D 的纯度越高。
假定离散属性a可以有V个可能的值 {a1,a2,...,av} ,那么以离散属性a的值去划分数据集 D ,则会产生V个子集,记离散属性a取值为 av 的子集为 Dv 。于是可以定义属性a对数据集 D 进行划分所获得的“信息增益“:

Gain(D,a)=Ent(D)v|Dv||D|Ent(Dv)

一般认为信息增益越大,划分效果越好,即该策略会选择信息增益最大的那个属性去划分数据集。

利用这种策略去选取属性的叫做ID3决策树。

基尼指数

同样地,我们先定义一个集合 D 的“基尼值”:

Gini(D)=k=1kkpkpk=1kp2k

直观的理解就是,基尼值衡量了从集合 D 中随机取出两个样本,这两个样本不同的概率有多大。

显然,基尼值的取值越小,表示这两个样本不同的概率越小,也即是数据集 D 的纯度越高。

类似地,我们定义离散属性a的“基尼指数”:

Gini_index(D,a)=v|Dv||D|Gini(Dv)

这里,我们会选取划分后基尼指数最小的那个属性最为最优划分属性。

利用这种策略取选取最有划分属性的叫做CART决策树。

算法流程


D={(x1,y1),(x2,y2),...(xm,ym)}

A={a1,a2,...ad}

build-tree(D, A)

  1. if D中的样本全属于同一类别C then
  2. ​ return C
  3. if 属性集A为空 then
  4. ​ return D中样本数最多的类标签
  5. 以某种策略选择最优划分属性 ak
  6. 得到以属性 ak 划分的数据子集subsets {D1,D2,...,Dv}
  7. 新建根节点tree
  8. for Di in subsets:
  9. ​ subtree = build-tree( Di A{ak} )
  10. ​ 将subtree置为tree的孩子节点
  11. return tree

实现

说了那么多,我们现在可以尝试一下用Python语言实现决策树算法。

  • 首先导入必要的包

    import math
    import utility
    from collections import Counter
  • 生成一些人工数据

    def create_data():
        data = [[1, 1],
                [1, 1],
                [1, 0],
                [0, 1],
                [0, 1]]
        label = ['yes', 'yes', 'maybe', 'no', 'no']  # 标签
        feature_names = ['no surfacing','flippers']  # 属性集合
        return data, label, feature_names
  • 然后我们定义一个函数用以计算集合的信息熵

    def cal_entropy(y):
        cnter = Counter(y)
        n_examples = len(y)
        probs = [val*1.0/n_examples for val in cnter.itervalues()]  # 先计算每个类别占的比例
        ent = -sum(p*math.log(p, 2) for p in probs)   # 然后根据信息熵的定义即可求得信息熵
        return ent
  • 现在我们定义一个划分函数,这个函数可以指定要划分的属性,由参数axis决定

      def split_data(X, y, axis):
        ans = []
        all_vals = set(item[axis] for item in X)
        data_dict = {key:[] for key in all_vals}
        label_dict = {key:[] for key in all_vals}
        n_examples = len(y)
        for vec, lb in zip(X, y):
            # 下面两行代码删除(略去)了axis位置的值
            reduced_vec = vec[:axis]
            reduced_vec.extend(vec[axis+1:])
            data_dict[vec[axis]].append(reduced_vec)
            label_dict[vec[axis]].append(lb)
        for key in all_vals:
            ans.append((key, data_dict[key], label_dict[key]))
        return ans

    我们来细看一下这个函数在做什么:

    1. 首先我们获得属性axis的所有可能的取值all_vals

    2. 然后迭代每一个属性值去生成两个字典,字典的key是属性值,value是一个空的链表。data_dict是用来存储特征的,而label_dict是用来存储标签的

    3. 接着我们迭代每一个训练样本,把它们添加到特定的属性值的链表中

    4. 接着我们从字典中取出划分后的每一个子集,每一个子集包含三个东西(在axis上的属性值,数据,标签),即代码中的(key,data_dict[key],label_dict[key])

    5. 可以尝试调用一下,输出如下的结果:

      data_sets = split_data(data, label, 0)
      
      # [(0, [[1], [1]], ['no', 'no']), (1, [[1], [1], [0]], ['yes', 'yes', 'maybe'])]
      
      
      # 这里有两个个子集,分别为
      
      
      # (0, [[1], [1]], ['no', 'no'])和(1, [[1], [1], [0]], ['yes', 'yes', 'maybe'])
      
  • 有了上面这个函数,我们就可以利用它来计算信息增益以选取最优划分属性了

    def choose_feature_to_split(X, y):
        n_features = len(X[0])
        n_examples = len(y)
        best_ent_gain = -(1<<21)   # 设置成一个很小的值
        best_axis = -1
        best_data_sets = None
        # 迭代每个属性,对它进行划分,求信息增益
        for i in range(n_features):
            data_sets = split_data(X, y, i)
            cur_ent = 0.0
            for key, xx, yy in data_sets:
                part_ent = cal_entropy(yy)
                p = len(yy)*1.0 / n_examples
                cur_ent += p*part_ent
            # 这里不用去计算原始数据的信息熵,我们只需要比较一下相对大小就行,所以可以取0.0
            cur_ent_gain = 0.0 - cur_ent 
            # 记录最好的属性以及相应的数据划分
            if cur_ent_gain > best_ent_gain:
                best_ent_gain = cur_ent_gain
                best_axis = i
                best_data_sets = data_sets
        return best_axis, best_data_sets
  • 在真正建立决策树前,我们需要两个辅助函数

    • 投票函数
    def vote(y):
        cnter = Counter(y)
        ans = cnter.most_common(1)[0]
        return ans[0]
    • 检查集合是否属于同一类别
    def check_if_one_class(y):
        return y.count(y[0]) == len(y)
  • 最后我们把上面的子模块整合在一起,建立一棵决策树,差不多就是前面的伪代码

    def build_tree(X, y, feature_names):
        # 只剩一个类别
        if check_if_one_class(y):
            return y[0]
        # 没有属性可供我们划分了
        if len(X[0]) == 0:
            return vote(y)
        # 否则选取最优划分属性,获得划分后的数据子集
        axis, data_sets = choose_feature_to_split(X, y)
        best_label = feature_names[axis]
        # 记得把用过的属性删除掉
        del(feature_names[axis])
        subtree = {}
        for key, xx, yy in data_sets:
            # 记得复制,不能直接赋值
            feature_names_copy = feature_names[:]
            # 递归建立子树
            subtree[key] = build_tree(xx, yy, feature_names_copy)
        tree = {best_label:subtree}
        return tree

    有一点要说明的是上面的递归树是存储在字典中的,python的字典数据结构允许我们很容创建一棵树。

    它的结构具体如下{属性:{属性值1:子树1,属性值2:字树2,…,属性值n:子树n}}

    tree = build_tree(data, label, labelnames)
    print tree
    
    # 输出如下:
    
    
    # {'no surfacing': {0: 'no', 1: {'flippers': {0: 'maybe', 1: 'yes'}}}}
    
  • 然后我们就可以利用工具可视化这么一棵决策树了,具体代码看这里,效果如下

    决策树的Python实现_第1张图片

使用sklearn中的决策树

是不是觉得自己实现有点繁琐啊,那我们就来看一下python的机器学习包sklearn中决策树的使用(下面的代码是在jupyter notebook里写的)

  • 导入相应的包

    from sklearn.datasets import load_iris
    from sklearn.tree import DecisionTreeClassifier
    import matplotlib.pyplot as plt
    %matplotlib inline
  • 加载数据

    data = load_iris()
  • 将数据分为训练集和验证集

    from sklearn.model_selection import train_test_split
    X_train, X_test, y_train, y_test= train_test_split(data.data, data.target, test_size=.25)
  • 训练一个决策树分类器

    
    # 这里criterion就是我们说的划分策略,可以自己去试一下其他的标准
    
    clf = DecisionTreeClassifier(criterion='entropy')
    clf.fit(X_train, y_train)
  • 看一下效果怎么样

    print 'Training accuracy: ', clf.score(X_train, y_train)
    print 'Testing accuracy: ', clf.score(X_test, y_test)
    
    # Training accuracy:  1.0
    
    
    # Testing accuracy:  0.921052631579
    
    
    # 恩,还不错
    
  • 最后,我们来看一下这棵决策树长什么样子的

    • 这小段代码估计得先pip install pydotplus
    • 然后再到这个网站下载安装相应系统下的graphviz
    • 然后,应该就可以了
    from IPython.display import Image  
    from sklearn import tree
    import pydotplus
    dot_data = tree.export_graphviz(clf, out_file=None, 
                           feature_names=data.feature_names,  
                           class_names=data.target_names,  
                           filled=True, rounded=True,  
                           special_characters=True)  
    graph = pydotplus.graph_from_dot_data(dot_data)  
    Image(graph.create_png()) 

决策树的Python实现_第2张图片

(是不是比我们自己画的好看多了==)

小结

决策树有个很好的特性叫:highly interpretable,就是极强的可解释性,也就是它学习到的知识不像其他学习器(如神经网络)那么抽象,而是很具体清晰的一些规则,这也让决策树广泛应用于很多专家系统中。

决策树中还有相当多的知识值得探讨,比如缺失值和连续值的处理,怎样通过剪枝(预剪枝和后剪枝)来对付过拟合,还有如何通过多变量决策树解决复杂决策便捷问题等。

你可能感兴趣的:(决策树的Python实现)