决策树

决策树是一种基本的分类和回归算法。它可以认为是if-then规则的集合,也可以认为是特征空间与类空间上的条件概率分布。其主要优点是模型具有可读性,分类速度快。学习时利用训练数据,根据损失函数最小化的原则建立决策树模型。预测时,对新的数据,利用决策树模型进行分类。

决策树学习通常包含3个步骤:特征选择决策树的生成决策树的剪枝

决策树模型

决策树由结点和有向边组成。结点有内部结点和叶子结点两种类型。内部结点表示一个属性或特征,叶子结点表示一个类别。

如下图所示,圆形表示内部结点,方形表示叶子结点。

image-20200312194800319.png

决策树可以看做if-then规则的集合,从决策树的根节点到叶结点的每一条路径对应一条规则,路径上的内部结点表示规则的条件,而叶结点的类对应着符合该规则的类别。决策树的路径或对应的if-then规则集合具有一个重要的性质,互斥并且完备。即每一个实例对应着一条路径(1对1的关系)。

决策树学习,假设给定训练数据集$D=\{(x_1,y_1),(x_2,y_2),...,(x_n,y_n)\}$,其中$x_i=(x_i^{(1)},x_i^{(2)},...,x_i^{(n)})$为特征向量,$y_i=\{1,2,...,K\},i\in \{1,2,...,N\}$为类标记。N为样本容量,n为特征数目(维度)。学习的目标是根据给定的训练数据集构建一个决策树模型,使它能够对实例进行正确的分类。

决策树学习本质上是从训练数据中归纳出一组分类规则。从另一个角度来看,决策树学习是由训练数据集估计条件概率模型。基于特征空间划分的类的条件概率模型有无穷多个。我们选择的条件概率模型应该不仅对训练数据有很好的拟合,而且对未知数据有很好的预测。

决策树学习用损失函数表示这一目标。决策树学习的损失函数通常是正则化的极大似然损失函数。决策树学习的策略是以损失函数为目标函数的最小化。

决策树学习的算法通常是一个递归的选择最优特征,并根据该特征对训练数据进行分割,使得对各个子数据集有一个最好的分类的过程。这一过程对应着特征空间的划分,也对应着决策树的构建。

以上方法生成的决策树可能对训练数据有很好的分类能力,但对未知的测试数据却未必有很好的分类能力,即可能发生过拟合现象。我们需要对已生成的树自下而上进行剪枝,将树变得更简单,从而使它具有更好的泛化能力。

如果特征数量很多,也可以在决策树学习开始的时候,对特征进行选择,只留下对训练数据有足够分类能力的特征。

决策树学习算法包含特征选择决策树的生成决策树的剪枝过程。由于决策树表示一个条件概率分布,所以深浅不同的决策树对应着不同复杂度的概率模型。决策树的生成对应于模型的局部选择,决策树的剪枝对应于模型的全局选择。决策树的生成只考虑局部最优,相对地,决策树的剪枝则考虑全局最优。

决策树学习的常用算法有ID3、C4.5和CART,以下结合算法分别介绍决策树算法的3个过程。

特征选择

特征选择在于选取对数据具有分类能力的特征。这样可以提高决策树学习的效率。通常特征选择的准则是信息增益或信息增益比。

特征选择是决定用哪个特征来划分特征空间。

信息增益

在信息论和概率统计中,熵(Entropy)是表示随机变量不确定性的度量。设X为一个取有限个值的离散随机变量,其概率分布为

$$P(X=x_i)=p_i,i=1,2,...,n$$

则随机变量X的熵定义为:

$$H(X)=-\sum_{i=1}^np_iInp_i \tag{1}$$

在式(1)中,若$p_i=0$,则定义$0In0=0$。通常式(1)中的对数取自然对数,这时熵的单位分别称作比特(bit)或纳特(nat)。由定义可知,熵只依赖于X的分布,而与X的取值无关,所以也可将X的熵记作H(p),即

$$H(p)=-\sum_{i=1}^np_iInp_i \tag{2}$$

熵越大,随机变量的不确定性就越大,从定义可以验证:

$$0\leq H(P) \leq Inn \tag{3}$$

当随机变量只取两个值时,例如0、1,即X的分布为$P(X=1)=p,P(X=0)=1-p,0\leq p \leq 1$,熵为

$$H(p)=-pInp-(1-p)In(1-p)\tag{4}$$

这时,熵H(p)随概率p变化的曲线如下图所示
image-20200313131948852.png

当p=0或p=1时,随机变量的不确定性为0,当p=0.5时,H(p)=1,熵取值最大,随机变量不确定性最大。

设有随机变量(X,Y)其联合概率分布为

$$P(X=x_i, Y=y_j)=p_{ij},i=1,2,...,n;j=1,2,...,m$$

条件熵H(Y|X)表示在已知随机变量X的条件下随机变量Y的不确定性。随机变量X给定的条件下随机变量Y的条件熵H(Y|X),定义为X给定条件下Y的条件概率分布的熵对X的数学期望

$$H(Y|X)=\sum_{i=1}^np_iH(Y|X=x_i)\tag{5}$$

其中$p_i=p(X=x_i),i=1,2,...,n$。

当熵和条件熵中的概率由数据估计(特别是极大似然估计)得到时,所对应的熵与条件熵分别称为经验熵和经验条件熵。

信息增益(information gain)表示得知特征X的信息而使得类Y的信息的不确定性减少的程度

信息增益:特征A对训练数据集D的信息增益g(D, A),定义为集合D的经验熵H(D)与特征A给定条件下D的经验条件熵H(D|A)之差,即

$$g(D,A)=H(D)-H(D|A)\tag{6}$$

一般地,熵H(Y)与条件熵H(Y|X)之差称为互信息(mutual information)。决策树学习中的信息增益等价于训练数据集中类与特征的互信息

决策树学习应用信息增益准则选择特征。给定训练数据集D和特征A,经验熵H(D)表示对数据集进行分类的不确定性。而经验条件熵H(D|A)表示在特征A给定的条件下对数据集D进行分类的不确定性。那么它们的差,即信息增益,就表示由于特征A而使得对数据集D的分类的不确定性减少的程度,显然,对于数据集D而言,信息增益依赖于特征,不同的特征往往具有不同的信息增益,信息增益大的特征具有更强的分类能力。

根据信息增益准则的特征选择方法是:对训练数据集(或子集)D,计算其每个特征的信息增益,并比较它们的大小,选择信息增益最大的特征。

设训练数据集为D,D的样本容量为N(N个样本)。设有K个类别$c_k,k=1, 2,...,K$,$N_k$为类别为$c_k$的样本容量,$\sum_{k=1}^KN_k=N$。设特征A有n个不同的取值$\{a_1,a_2,...,a_n\}$,根据特征A的取值将D划分为n个子集$D_1,D_2,...,D_n,N_i$表示$D_i$的样本个数,$\sum_{i=1}^nN_i=N$,记子集$D_i$中属于类$C_k$的样本的集合为$D_{ik}$,即$D_{ik}=D_i\bigcap C_k,N_{ik}$表示$D_{ik}$的样本个数。于是信息增益的算法如下:

输入:训练数据集D和特征A;

输出:特征A对D的信息增益。

下面以表5.1的数据为例,分别计算信息增益

image-20200313160405666.png

  1. 计算数据集D的经验熵H(D)

    $$H(D)=-\sum_{k=1}^K\frac{N_k}{N}In\frac{N_k}{N}$$

    上表中数据集共有2个类别,即K=2,类别为否的样本$D_1$数目为6,类别为是的样本$D_2$数目为9,所以有

    $$H(D)=-\frac{6}{15}In\frac{6}{15}-\frac{9}{15}In\frac{9}{15}=0.673$$

  2. 计算年龄A对数据集D的条件经验熵

    $$H(D|A)=\sum_{i=1}^n\frac{N_i}{N}H(D_i)=-\sum_{i=1}^n\frac{N_i}{N}\sum_{k=1}^{K}\frac{N_{ik}}{N_i}In\frac{N_{ik}}{N_i}$$

    $\begin{split} H(D|A)&=\frac{5}{15}H(D_1)+\frac{5}{15}H(D_2)+\frac{5}{15}H(D_3)\\&=\frac{5}{15}(-\frac{2}{5}In\frac{2}{5}-\frac{3}{5}In\frac{3}{5})+\frac{5}{15}(-\frac{2}{5}In\frac{2}{5}-\frac{3}{5}In\frac{3}{5})+\frac{5}{15}(-\frac{4}{5}In\frac{4}{5}-\frac{1}{5}In\frac{1}{5})\\ &=0.615\end{split}$

  3. 计算信息增益

    $$g(D,A)=H(D)-H(D|A)=0.673-0.615=0.058$$

  4. 按照这样的方式计算其余3个特征(”有工作“、”有自己的房子“、”信贷情况“)的信息增益。

通过计算,得到特征($A_3$,有自己的房子)的信息增益最大,最终选择特征$A_3$作为最优特征。

信息增益比

信息增益值的大小是相对于训练数据集而言的,并没有绝对意义。在分类问题困难时,也就是说在训练数据集的经验熵大的时候,信息增益值会偏大。反之,信息增益值会偏小。比如一个数据集中包含日期特征或是序号等混乱的特征(特征的经验熵很大),这时信息增益会很大。而使用信息增益比(Information gain ratio)可以对这一问题进行校正。这是特征选择的另一准则。

信息增益比:特征A对训练数据集D的信息增益比$g_R(D,A)$定义为其信息增益g(D,A)与训练数据集D的经验熵H(D)之比

$$g_R(D,A)=\frac{g(D,A)}{H(D)}\tag{7}$$

决策树的生成

ID3算法

ID3算法的核心是在决策树各个结点上应用信息增益准则选择特征,递归地构建决策树。具体方法是:从根节点开始,对结点计算所有可能的特征的信息增益,选择信息增益最大的特征作为结点的特征,由该特征的不同取值建立子节点;再对子节点递归地调用以上方法,构建决策树;直到所有特征的信息增益均很小或没有特征可以选择为止。最后得到一个决策树。ID3相当于用极大似然法进行概率模型的选择

ID算法

输入:训练数据集D,特征集A,阈值$\xi$

输出:决策树T

  1. 若D中所有实例属于同一类$C_k$,则T为单节点树,并将类$C_k$作为该节点的类标签,并返回T;
  2. 若$A=\oslash$,则T为单节点树,并将D中实例数最大的类$C_k$(包含样本最多的类别)作为该节点的类标记,并返回T;
  3. 否则,按公式(6)计算A中每个特征对于数据集D的信息增益,选择信息增益最大的特征$A_g$;
  4. 如果$A_g$的信息增益小于阈值$\xi$,则置T为单节点树,并将D中实例树最大的类$C_k$作为该节点的类标记,返回T;
  5. 否则,对$A_g$的每一可能值$a_i$,依$A_g=a_i$将D分割为若干非空子集$D_i$,将$D_i$中实例数最大的类作为标记,构建子节点,由结点机器子节点构成数T,返回T;
  6. 对第i个子节点,以$D_i$为训练集,以$A-\{A_g\}$为特征集,递归地调用步骤1-5,得到子树$T_i$,返回$T_i$。

构建决策树的代码如下:

class DecisionTree:
    def __init__(self, thresh=1e-2, criterion='gain'):
        self.tree = None
        self.thresh = thresh
        self.criterion = self.calc_gain if criterion == 'gain'else self.calc_gain_ratio
        self.num_leaf = 0
        self.feat_names = None
    
    def fit(self, x_train, y_train):
        self.tree = self._construct(x_train, y_train)
        print('Finish train')

    
    def predict(self, x_test,y_test=None):
#         预测
        y_pred = []
        x_test = np.array(x_test)
        x_test = x_test.reshape((x_test.shape[0], -1))
        for x in x_test:
            y_pred.append(self._search(x))
        if y_test is not None:
            self._score(y_test, y_pred)
            
        return y_pred
    
    def _score(self, y_test, y_pred):
#         计算得分
        return np.count_nonzero(y_pred=y_test) / len(y_test)
        
    
    def _search(self, x_test):
        res = self.tree
        key = list(res.values())[0]
        
        while key is not None:

            fname = list(res.keys())[0]
            fidx = self.feat_names.index(fname)
            res = res[fname][x_test[fidx]]
            key = list(res.values())[0]
            
            
        return list(res.keys())[0]
        
    def calc_entropy(self, label):
    #     计算数据集的经验熵
        res = 0
        for lab in label.unique():
            ratio = len(label[label == lab]) / len(label)
            res += ratio * np.log(ratio)

        return -res


    def calc_cond_entropy(self, feature, label):
    #     计算数据集的经验条件熵
        res = 0
        for f in feature.unique():
            idx = feature == f
            fcount = idx.sum()
            fratio = fcount / len(feature)
            fres = 0
            for lab in label.unique():
                ratio = sum(label[idx] == lab) / fcount
                if ratio != 0:
                    fres += ratio * np.log(ratio)
                else:
                    fres += 0
            res += fratio * fres 

        return -res


    def calc_gain(self, feature, label):
        """
        Calculate information gain for specific feature
        计算指定特征对数据集的信息增益
        Params:
            feature(pandas.Series): feature A
            label(pandas.Series): labels
        """
        #         
        exp_ent = calc_entropy(label)
        cond_ent = calc_cond_entropy(feature, label)
        return exp_ent - cond_ent

    def calc_gain_ratio(self, feature, label):
        """
        Calculate information gain ratio for specific feature
        计算指定特征对数据集的信息增益比
        Params:
            feature(pandas.Series): feature A
            label(pandas.Series): labels
        """
        exp_ent = calc_entropy(label)
        cond_ent = calc_cond_entropy(feature, label)
        return (exp_ent - cond_ent)/ exp_ent
    
    
    def _construct(self, x, y):
        """
        构建二叉树
        
        Params:
            x(Pandas.DataFrame): train features
            y(Pandas.Series): train labels
        """
        
        ck, cnts = np.unique(y, return_counts=True)
#         如果只有一个类别
        if len(ck) == 1:
            self.num_leaf += 1
            return {ck[0]: None}
        
#         如果没有特征
        if len(x) == 0:
            self.num_leaf += 1
            return {ck[cnts.argmax(0)]: None}
            
            
        self.feat_names = list(x.columns)
#       逐特征计算信息增益
        max_gain = 0
        max_fidx = 0
        for i in range(x.shape[1]):
            cur_gain = self.criterion(x.iloc[:, i], y)
            if cur_gain > max_gain:
                max_gain = cur_gain
                max_fidx = i
                
#       如果最大的信息增益小于阈值, 则返回
        if max_gain < self.thresh:
            self.num_leaf += 1
            return {ck[cnts.argmax(0)]: None}
        
        
        fk = np.unique(x.iloc[:, max_fidx])
        res = dict()
#       对于特征的每个取值,往下构建节点
        for i in fk:
            fidx = x.iloc[:, max_fidx] == i
            res.update({i: self._construct(x[fidx], y[fidx])})
        
        self.tree = {x.columns[max_fidx]: res}
        
        return self.tree

ID3算法只有树的生成,所以该算法生成的树容易产生过拟合。

C4.5的生成算法

C4.5算法采用信息增益率准则来选择特征, 过程同上。

总结

本文主要介绍了决策树算法的实现过程, 并分别使用ID3和C4.5算法构建了决策树分类模型。

  1. 分类决策树模型是表示基于特征对实例进行分类的树形结构。决策树可以转换成一个if-then规则的集合,也可以看作是定义在特征空间划分上的类的条件概率分布。
  2. 决策树学习旨在构建一个与训练数据拟合很好,并且复杂度小的决策树。因为从可能的决策树中直接选取最优决策树是NP完全问题。现实中采用启发式方法学习次优的决策树。
  3. 决策树学习算法包括3部分:特征选择、树的生成和树的剪枝。常用的算法有ID3、C4.5和CART。
  4. 决策树特征选择使用的主要准则有信息增益(ID3)、信息增益比(C4.5)和基尼指数(CART)。ID3算法只能处理离散特征,C4.5和CART既可以处理离散特征也可以处理连续特征。

Reference

李航《统计学习方法》


mhxin
84 声望15 粉丝