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

本篇主要内容:决策树、信息熵、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,此时已达到最大深度,故没有继续下去了,所以我们这里也不继续进行划分了。
实际上使用信息熵作为划分标准是有一定局限性的,ID3采用的信息增益度量存在一个隐形内在偏置,它优先选择有较多属性值的特征,因为属性值多的特征会有相对较大的信息增益。比如(\frac{1}{4},\frac{1}{4},\frac{1}{4},\frac{1}{4})(\frac{1}{2},\frac{1}{2}),同样是等概率的集合,将前者分开后的信息增益却比后者大。避免这个不足的一个办法就是用信息增益比率(gain ratio)来进行划分,这就是C4.5算法,有兴趣的可查阅资料学习一下,这里不再过多介绍。

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

决策树图示:

决策树图示

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

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

推荐阅读更多精彩内容