机器学习系列(三十五)——决策树Decision Tree

作者: Ice_spring | 来源:发表于2019-07-30 16:45 被阅读12次

    本篇主要内容:决策树,信息熵,Gini系数

    什么是决策树

    决策树(Decision Tree)和knn算法都是一种非参数的有监督学习方法,它能够从一系列有特征和标签的数据中总结出决策规则,并用树状图的结构来呈现这些规则,以解决分类和回归问题,而且决策树天然能解决多分类问题,具有非常好的可解释性。
    以一个判断哺乳动物的例子来直观理解一下决策树:

    哺乳动物判别

    这是一个树结构,每个叶子节点都是一个决策(分类),每当有一个新的样本,就按照这棵树结构进行判断。比如现在有动物企鹅,在不知道它是否是哺乳动物的情况下,用这棵决策树进行判断,首先在根节点判断它是恒温动物吗?回答是,接着在一个中间节点判断它是胎生吗?回答不是,这时判断就进行完毕,企鹅不是哺乳动物。这就是一个决策树的决策过程,当然实际中要进行的决策可能要复杂的多,不过原理是一样的。
    决策树中最初的问题所在的地方叫做根节点,在得到结论前的每一个问题都是中间节点,而得到的每一个结论(决策结果)都叫做叶子节点。决策树的深度是判断的最多的次数。


    构建决策树

    信息熵作为不纯度

    下面就来看看如何构造一棵决策树,在此之前首先给出信息熵的概念,熵在信息论中代表一个系统不确定性的度量,随机变量的熵越大,数据的不确定性越高。信息熵的计算公式为:
    Entropy=-\sum_{i=1}^kp_ilog(p_i)

    其中p_i是事件的发生概率,一个系统中,所有p_i的和是1。如${1,0,0}这个系统,可以计算得到信息熵为0,因为有概率为1的事件,于是这个系统没有不确定性。以二分类的信息熵为例,绘制它的函数图像:

    def H(p):
        return -p*np.log(p)-(1-p)*np.log(1-p)
    x = np.linspace(0.01,0.99,100)
    plt.plot(x,H(x))
    plt.show()
    
    信息熵

    可以看到等概率事件具有最大的信息熵,这也很好理解,等概率时是最没有把握将样本分为其中一类的,也就是等概率时系统的不确定性最高,这样的情况同样可以拓展到多分类。
    决策树就是一个按照信息增益不断提纯数据的过程,我们在样本的某个维度(某个特征)进行划分,又在某一维度的某个阀值(特征取值)进行划分,使得划分后的数不纯度不断降低。当前最好的划分就是让划分后的不纯度与原不纯度有最大的绝对值差。接下来我们就来使用信息熵来寻找最优划分,使用信息熵构建决策树就是ID3算法:

    '''信息熵寻找最优划分'''
    def split(x,y,d,value):
        index_a = (x[:,d] <= value)#布尔向量
        index_b = (x[:,d] > value)
        return x[index_a], x[index_b], y[index_a], y[index_b]
    
    from collections import Counter
    from math import log
    def entropy(y):
        counter = Counter(y)#统计各类数目,以键值对形式返回
        res = 0.0
        for num in counter.values():
            p = num / len(y)
            res += - p * log(p)
        return res
    def try_split(x, y):
        best_entropy = float('inf')
        best_d, best_v = -1, -1
        for d in range(x.shape[1]):#shape[1]=2共两个维度
            sorted_index = np.argsort(x[:, d])#返回排序后的索引
            for i in range(1, len(x)):
                if x[sorted_index[i-1], d] != x[sorted_index[i],d]:
                    v = (x[sorted_index[i-1], d] + x[sorted_index[i], d]) / 2
                    x_l, x_r, y_l, y_r = split(x, y, d, v)#进行划分
                    e = entropy(y_l) + entropy(y_r)#划分后求熵
                    
                    if e < best_entropy:
                        best_entropy, best_d, best_v = e, d, v#更新划分参数,直到最优
        return best_entropy,best_d,best_v
    

    为了展示我们找到的划分结果,首先我们使用sklearn中的决策树来对鸢尾花进行分类,同样为了可视化的方便,只取两个特征,取的是后两个特征:

    '''鸢尾花数据,取后两个特征'''
    import numpy as np
    import matplotlib.pyplot as plt
    from sklearn import datasets
    
    iris = datasets.load_iris()
    X = iris.data[:,2:]
    y = iris.target
    
    plt.scatter(X[y==0,0],X[y==0,1])
    plt.scatter(X[y==1,0],X[y==1,1])
    plt.scatter(X[y==2,0],X[y==2,1])
    plt.show()
    
    数据集图示

    接下来使用sklearn中的决策树进行分类,并绘制决策边界,这里指定决策树最大深度为2:

    '''这是使用很多次的决策边界绘制函数'''
    def plot_decision_boundary(model,axis):
        x0,x1=np.meshgrid(
            np.linspace(axis[0],axis[1],int((axis[1]-axis[0])*100)).reshape(-1,1),
            np.linspace(axis[2],axis[3],int((axis[3]-axis[2])*100)).reshape(-1,1)
        )
        x_new=np.c_[x0.ravel(),x1.ravel()]
        y_predict=model.predict(x_new)
        zz=y_predict.reshape(x0.shape)
        from matplotlib.colors import ListedColormap
        custom_cmap=ListedColormap(['#EF9A9A','#FFF59D','#90CAF9'])
        plt.contourf(x0,x1,zz,linewidth=5,cmap=custom_cmap)
    '''训练模型并绘制决策边界'''
    from sklearn.tree import DecisionTreeClassifier
    dt_clf = DecisionTreeClassifier(max_depth=2, criterion='entropy')
    dt_clf.fit(X,y)
    plot_decision_boundary(dt_clf,axis=[0.5,7.5,0,3])
    plt.scatter(X[y==0,0],X[y==0,1])
    plt.scatter(X[y==1,0],X[y==1,1])
    plt.scatter(X[y==2,0],X[y==2,1])
    plt.show()
    

    决策边界:

    决策边界

    从上面的决策边界大致可以看出,决策的基本过程是:

    决策过程

    如果安装了python-graphviz模块,还可以在Jupyter中画出决策树:

    from sklearn import tree
    feature_name = ['feature1','feature2']
    import graphviz
    dot_data = tree.export_graphviz(dt_clf
                                    ,feature_names=feature_name
                                    ,class_names=["A","B","C"]
                                    ,filled=True#填充颜色
                                    ,rounded=True#圆角
                                    )
    graph = graphviz.Source(dot_data)
    graph
    
    tree

    这里详细给出了决策树计算的一些指标。
    下面使用我们的信息熵划分代码进行划分:

    '''第一次划分'''
    best_entropy, best_d, best_v = try_split(X, y)
    print(best_entropy)
    print(best_v)
    print(best_d)
    
    第一次划分

    从结果看出,第一次划分是在特征1上的2.45值处,与sklearn结果一致,接着进行下一步划分,由于左边信息熵已为0,只需划分右边:

    '''第一次划分结果'''
    x1_l,x1_r,y1_l,y1_r=split(X,y,best_d,best_v)
    entropy(y1_l)#左边信息熵为0
    
    第一次划分后左边信息熵
    '''对右边继续划分'''
    #第二步划分
    best_entropy2,best_d2,best_v2=try_split(x1_r,y1_r)
    print(best_entropy2)
    print(best_v2)
    print(best_d2)
    
    第二次划分

    第二次划分是在特征2上的1.75值处,和sklearn中结果一致,此时划分后的左右信息熵:

    二次划分后

    划分后信息熵仍没变为0,实际上我们还能继续划分,直至所有叶子节点信息熵皆为0。不过sklearn这里指定了决策树最大深度为2,此时已达到最大深度,故没有继续下去了,所以我们这里也不继续进行划分了。

    Gini系数作为不纯度

    除了信息熵之外,我们还有其它指标对数据进行纯度衡量,从而划分数据,就是基尼系数(Gini),以Gini系数构建决策树是CART算法,Gini系数的计算公式为:
    Gini=1-\sum_{i=1}^mp_i^2

    其实它和信息熵有几乎相同的性质,Gini系数越大表示系统数据不确定性越大,如果对二分类数据画图,图像走势和使用信息熵是一致的,都在等概率时达到最大值,这个结论同样在多分类适用。

    Gini图像趋势

    既然二者性质相当,在实际使用中,绝大多数的情况信息熵和基尼系数的效果也都基本相同,那为什么还要使用Gini系数呢?这是因为Gini系数计算不涉及对数运算,计算比信息熵快很多,因此sklearn中默认的就是使用Gini。当然并非所有问题为了计算简便都使用Gini系数,比起Gini系数,信息熵对不纯度更加敏感,对不纯度的惩罚更强,所以信息熵作为指标时,决策树的生长会更加“精细”,因此当模型拟合程度不足的时候,即当模型在训练集和测试集上都表现不太好的时候,可以考虑信息熵。对于高维数据或者噪音很多的数据,信息熵往往容易过拟合,此时选用Gini系数。
    下面在同样的数据集上使用Gini系数作为不纯度指标来进行划分,并绘制决策边界:

    import numpy as np
    import matplotlib.pyplot as plt
    from sklearn import datasets
    iris=datasets.load_iris()
    x=iris.data[:,2:]
    y=iris.target
    
    from sklearn.tree import DecisionTreeClassifier
    dt_clf = DecisionTreeClassifier(max_depth=2,criterion="gini")
    dt_clf.fit(x,y)#训练模型
    '''绘制决策边界'''
    plot_decision_boundary(dt_clf,axis=[0.5,7.5,0,3])
    plt.scatter(x[y==0,0],x[y==0,1])
    plt.scatter(x[y==1,0],x[y==1,1])
    plt.scatter(x[y==2,0],x[y==2,1])
    plt.show()
    

    决策边界:

    Gini决策边界

    可以看到和用信息熵得到的决策边界是相同的。绘制此时的决策树:

    from sklearn import tree
    feature_name = ['feature1','feature2']
    import graphviz
    dot_data = tree.export_graphviz(dt_clf
                                    ,feature_names=feature_name
                                    ,class_names=["A","B","C"]
                                    ,filled=True#填充颜色
                                    ,rounded=True#圆角
                                    )
    graph = graphviz.Source(dot_data)
    graph
    

    决策树图示:

    决策树图示

    可见和使用信息熵的划分相同。

    相关文章

      网友评论

        本文标题:机器学习系列(三十五)——决策树Decision Tree

        本文链接:https://www.haomeiwen.com/subject/cnzgrctx.html