5. 决策树 DecisionTree

1. DecisionTree

决策树是一种用于解决 分类回归 问题的机器学习方法,其结构就是基本数据结构中的 结构,对应于 if-then 规则的集合。和 k k k-NN 一样,决策树的模型中也没有明确需要求解的参数,而是根据规则从训练数据中建立树。
要从训练数据中建立一棵可用于解决问题的决策树,需要以下 3 3 3 个步骤

  1. 特征选择
    • 信息增益
    • 信息增益比
    • 基尼指数
  2. 决策树生成
    • ID 3 算法
    • C 4.5 算法
    • CART 算法
  3. 决策树修剪

下面会详细的讲述这些步骤。

1.1 模型

决策树的模型,就是一棵树,内部结点表示 特征,叶结点表示 。决策树的分类过程,就是给出一个样本,从根结点开始,经历一系列选择,最后落入一个叶结点中,叶结点的类别就是该样本的类别。所以说,决策树相当于 if-then 规则的集合,内部结点就是一个个的 if-then
从另一个视角,也可以将决策树看做给定特征条件下类的条件概率分布,即 if-then 规则将特征空间划分为不相交的区域,每个叶结点对应一个区域。从根结点到叶结点的一条路径,对应于特征,记为 X X X,而该叶结点对应类别 Y Y Y,那么在已经确定各个特征取值的基础上 (即确定 x x x 的基础上),区域属于每个类别 y i y_i yi 有概率 p ( y i ∣ x ) p(y_i|x) p(yix),所有的 p ( y i ∣ x ) p(y_i|x) p(yix) 中最大的那个所对应的 y i y_i yi 就是该区域最有可能属于的类别,也就是说模型可表示为条件概率 P ( Y ∣ X ) P(Y|X) P(YX)

1.2 策略

决策树模型的策略从条件概率的角度看,和 NaiveBayes 的策略是一样的,即后验概率最大化,确定一系列特征的取值,在此条件下,属于哪一类的可能性最大,就分到哪一类。

1.3 算法

从训练数据中学习决策树模型,就是基于训练数据建立一棵决策树,其主要的 3 3 3 个部分如下

  1. 特征选择
    一条数据会有很多条特征,有的特征和该数据的类别关系很大,而有的特征和该数据的类别关系很小,甚至没有关系。特征选择就是从所有的特征中,选出对该数据类别影响比较大的特征,以其为依据建立 if-then 规则
  2. 决策树生成
    特征选择就决定了 if-then 规则,根据这些规则,就可以建立出一棵决策树
  3. 决策树修剪
    决策树不是越精细越好,不是 if-then 规则划分得越细致越好,因为有可能某些特征和分类结果之间没有相关性,用这些特征建立 if-then 规则有可能导致过拟合。也有可能训练数据存在噪声,也就是说训练数据有误,依据这样的数据进行太精细的划分,也可能导致过拟合。所以,需要适当的修剪决策树,也就是将决策树的某些子树剪枝成为叶结点。

2. 决策树算法与实现

2.1 特征选择

特征选择 就是要从所有的特征中,选出和分类结果相关性较强的特征,用于建立 if-then 规则。可以通过 信息增益信息增益比基尼指数 等指标定量的衡量各特征对分类的影响大小。

2.1.1 信息增益

2.1.1.1 基本概念

  1. entropy
    • 随机变量 X X X 的熵是其概率的 负对数期望,即 H ( X ) = E ( − log ⁡ p i ) = − ∑ i = 1 n p i log ⁡ p i H(X)=E(-\log p_i)=-\sum\limits_{i=1}^np_i\log p_i H(X)=E(logpi)=i=1npilogpi,熵只依赖于随机变量的期望,而不依赖于其取值,因此也可记做 H ( p ) H(p) H(p)
    • 熵代表了随机变量的不确定程度,熵越大随机变量的值越不确定
    • 例如当随机变量取某个值的概率为 0 0 0 1 1 1 时,随机变量已经完全确定,故熵值为 0 0 0,当概率为 0.5 0.5 0.5 时,随机变量取得该值和不取该值的概率一样大,因此最不能确定它,此时熵取到最大值 1 1 1
  2. 条件熵 conditional entropy
    • 条件熵 H ( Y ∣ X ) H(Y|X) H(YX) 是指在随机变量 X X X 的取值确定后, Y Y Y 的不确定程度
    • 可以记为 H ( Y ∣ X ) = ∑ i = 1 n p i H ( Y ∣ X = x i ) H(Y|X)=\sum\limits_{i=1}^np_iH(Y|X=x_i) H(YX)=i=1npiH(YX=xi)
  3. 信息增益 information gain
    • 信息增益 也称为 互信息,记为 g ( Y , X ) = H ( Y ) − H ( Y ∣ X ) g(Y,X)=H(Y)-H(Y|X) g(Y,X)=H(Y)H(YX)
    • 其体现了,确定随机变量 X X X,能够让随机变量 Y Y Y 的不确定性降低的程度
    • 信息增益越大,说明 X X X 越能确定 Y Y Y

2.1.1.2 计算信息增益

给定训练数据集 D D D 和特征 A A A,计算 A A A D D D 的信息增益 g ( D , A ) g(D,A) g(D,A) 的过程如下

  1. 计算 D D D 的熵
    H ( D ) = E ( − log ⁡ p i ) = − ∑ i = 1 n p i log ⁡ p i = − ∑ i = 1 n ∣ C i ∣ ∣ D ∣ log ⁡ ∣ C i ∣ ∣ D ∣ \begin{aligned} H(D)&=E(-\log p_i) \\ &=-\sum\limits_{i=1}^np_i\log p_i \\ &=-\sum\limits_{i=1}^n\frac{|C_i|}{|D|}\log\frac{|C_i|}{|D|} \end{aligned} H(D)=E(logpi)=i=1npilogpi=i=1nDCilogDCi
  2. 计算条件熵 H ( D ∣ A ) H(D|A) H(DA)
    H ( D ∣ A ) = ∑ i = 1 m p i H ( D i ) = − ∑ i = 1 m p i ∑ j = 1 n p j log ⁡ p j = − ∑ i = 1 m ∣ D i ∣ ∣ D ∣ ∑ j = 1 n ∣ D i j ∣ ∣ D i ∣ log ⁡ ∣ D i j ∣ ∣ D i ∣ \begin{aligned} H(D|A)&=\sum\limits_{i=1}^mp_iH(D_i) \\ &=-\sum\limits_{i=1}^mp_i\sum_{j=1}^np_j\log p_j \\ &=-\sum\limits_{i=1}^m\frac{|D_i|}{|D|}\sum\limits_{j=1}^n\frac{|D_{ij}|}{|D_i|}\log\frac{|D_{ij}|}{|D_i|} \end{aligned} H(DA)=i=1mpiH(Di)=i=1mpij=1npjlogpj=i=1mDDij=1nDiDijlogDiDij
  3. 计算信息增益 g ( D , A ) g(D,A) g(D,A)
    g ( D , A ) = H ( D ) − H ( D ∣ A ) g(D,A)=H(D)-H(D|A) g(D,A)=H(D)H(DA)

:

  1. 1 1 1 步中的 p i p_i pi 是训练集中数据属于第 i i i 类的概率
  2. 2 2 2 步中的 p i p_i pi 是训练集中数据的特征 A A A 为第 i i i 种情况的概率, p j p_j pj 是训练集中数据特征 A A A 为第 i i i 种情况的数据中,类别为 j j j 的概率
  3. 其实这里计算熵和条件熵的公式都是根据训练集由 参数估计 得到的,因此被称为 经验熵经验条件熵
  4. 参数估计的内容可参考 NaiveBayes

2.1.2 信息增益比

信息增益已经是很好的指标,但仍然存在问题,即信息增益倾向于选择取值比较多的特征。因此用信息增益比来修正这一问题,信息增益比定义为信息增益 g ( D , A ) g(D,A) g(D,A) 与训练集 D D D 关于特征 A A A 的值的熵 H A ( D ) H_A(D) HA(D) 之比,即
g R ( D , A ) = g ( D , A ) H A ( D ) g_R(D,A)=\frac{g(D,A)}{H_A(D)} gR(D,A)=HA(D)g(D,A)
其中 H A ( D ) = − ∑ i = 1 n ∣ D i ∣ ∣ D ∣ log ⁡ ∣ D i ∣ ∣ D ∣ H_A(D)=-\sum\limits_{i=1}^n\frac{|D_i|}{|D|}\log \frac{|D_i|}{|D|} HA(D)=i=1nDDilogDDi,即代表数据的特征 A A A 取值为 i i i 的随机变量的熵

2.1.3 基尼指数

2.1.3.1 基本概念

基尼指数 也用来表示不确定性,和熵一样,基尼指数越大,代表不确定性越大。在分类问题中,若有 N N N 个类,样本属于第 i i i 类的概率为 p i p_i pi,则定义概率分布的基尼指数如下
Gini ⁡ ( p ) = ∑ i = 1 N p i ( 1 − p i ) = 1 − ∑ i = 1 N p i 2 \begin{aligned} \operatorname{Gini}(p)&=\sum\limits_{i=1}^Np_i(1-p_i) \\ &=1-\sum\limits_{i=1}^Np_i^2 \end{aligned} Gini(p)=i=1Npi(1pi)=1i=1Npi2
特别地,二分类问题的基尼指数
Gini ⁡ ( p ) = 2 p ( 1 − p ) \operatorname{Gini}(p)=2p(1-p) Gini(p)=2p(1p)
对于给定的样本集合 D D D,数据属于类别 C k C_k Ck 的概率为 ∣ C k ∣ ∣ D ∣ \frac{|C_k|}{|D|} DCk,即对样本集合 D D D 有基尼指数如下
Gini ⁡ ( D ) = 1 − ∑ i = 1 N ( ∣ C k ∣ ∣ D ∣ ) 2 \operatorname{Gini}(D)=1-\sum\limits_{i=1}^N(\frac{|C_k|}{|D|})^2 Gini(D)=1i=1N(DCk)2
对于某个特征 A A A 是否取特定值 a a a,可以将集合分割成 D 1 D_1 D1 D 2 D_2 D2 两部分,由此有 在特征 A A A 的条件下集合 D D D 的基尼指数
Gini ⁡ ( D , A ) = ∣ D 1 ∣ ∣ D ∣ Gini ⁡ ( D 1 ) + ∣ D 2 ∣ ∣ D ∣ Gini ⁡ ( D 2 ) \operatorname{Gini}(D,A)=\frac{|D_1|}{|D|}\operatorname{Gini}(D_1)+\frac{|D_2|}{|D|}\operatorname{Gini}(D_2) Gini(D,A)=DD1Gini(D1)+DD2Gini(D2)
Gini ⁡ ( D , A ) \operatorname{Gini}(D,A) Gini(D,A) 代表确定 A A A 后集合 D D D 的不确定性,因此 Gini ⁡ ( D , A ) \operatorname{Gini}(D,A) Gini(D,A) 越小 A A A 越能确定最终的分类

2.1.3.2 选取最优特征和最优切分点

注意到 基尼指数信息增益 之间有一些区别,即

  1. 基尼指数需要确定 是哪一个特征,以及 是该特征的哪一个取值 才能计算出来
  2. 信息增益则是确定 是哪一个特征 就可以计算出来

因此在选取利用基尼指数做特征选择时,需要计算出所有特征的所有取值的基尼指数,从中选出拥有最小基尼指数 的特征及其取值。例如,有 n n n 个特征,每个特征有 m m m 个取值,那么最终需要计算 n × m n\times m n×m 个基尼指数,最终选出有最小基尼指数的特征及其取值 ( n i , m j ) (n_i,m_j) (ni,mj) 来建立 if-then 规则。因此该过程描述如下

  1. 对每个特征 A A A,对其可能取的每个值 a a a,根据样本点对 A = a A=a A=a 的测试为 ,将 D D D 分割为 D 1 D_1 D1 D 2 D_2 D2 两部分,计算 A = a A=a A=a 的基尼指数
  2. 从所有可能的特征 A A A 及所有可能的切分点 a a a 中,选取 基尼指数最小 的特征及其对应切分点为最优特征与最优切分点

2.1.4 特征选择的实现

这里编写一个 feature_selection.py 文件,其中包含各种特征选择指标的计算函数,方便后续进行调用,具体的代码如下

def entropy(p):
    """
    计算熵
    Args:
        p(list): 一个列表, 其中为 y 各种取值出现的次数, 例如 [3, 2, 3] 即 0 类出现 3 次, 1 类出现 2 次, 2 类出现 3 次
    Returns:
        熵
    """
    # 1. 计算各种情况出现的概率
    s = sum(p)
    p = [i / s for i in p]
    
    # 2. 求负对数期望, 得到熵
    ans = sum(-i * log(i, 2) for i in p)
    
    # 3. 返回熵
    return ans

def entropy_of_split(X, Y, col):
    """
    计算条件熵
    Args:
        X(ndarray): 训练数据的特征矩阵
        Y(ndarray): 训练数据的标签向量
        col(int):   指取训练数据的第 col 个特征
    Returns:
        当 X 的第 col 个特征 A 确定时, Y 的条件熵 H(Y|A)
    """
    # 1. 统计 X 的第 col 个特征的每种取值各自出现的次数
    val_cnt = Counter(x[col] for x in X)
        
    # 2. 计算第 col 个特征取各个值时的 Y 的熵, 乘以权重得累加得条件熵
    ans = 0
    for val in val_cnt:
        weight = val_cnt[val] / len(X)                                              # 计算第 col 个特征取值 val 出现的概率
        entr = entropy(Counter(y for x, y in zip(X, Y) if x[col] == val).values())  # 计算当该特征取值 val 时, Y 的熵
        ans += weight * entr                                                        # 加入答案
            
    # 3. 返回条件熵
    return ans

def info_gain(X, Y, col):
    """
    计算信息增益
    Args:
        X(ndarray): 训练数据的特征矩阵
        Y(ndarray): 训练数据的标签向量
        col(int):   指取训练数据的第 col 个特征
    Returns:
        X 的第 col 个特征 A 对于 Y 的信息增益 g(Y, A)
    """
    # 1. 计算数据集的熵
    entropy_of_X = entropy(Counter(Y).values())
    
    # 2. 计算 X 的第 col 个特征 A 确定时的条件熵 H(Y|A)
    entropy_of_col = entropy_of_split(X, Y, col)
    
    # 3. 做减法即得信息增益, 将其返回
    return entropy_of_X - entropy_of_col

def info_gain_ratio(X, Y, col):
    """
    计算信息增益比
    Args:
        X(ndarray): 训练数据的特征矩阵
        Y(ndarray): 训练数据的标签向量
        col(int):   指取训练数据的第 col 个特征
    Returns:
        X 的第 col 个特征 A 对于 Y 的信息增益比 g_R(Y, A)
    """
    # 1. 计算 X 的第 col 个特征 A 对于 Y 的信息增益 g(Y, A)
    info_gain_of_col = info_gain(X, Y, col)
    
    # 2. 计算 X 的第 col 个特征 A 对数据集的熵
    entropy_of_col = entropy(Counter(x[col] for x in X).values())
    
    # 3. 做除法即得信息增益比, 将其返回
    return info_gain_of_col / entropy_of_col

def gini(Y):
    """
    计算基尼指数
    Args:
        Y(ndarray): 训练数据的标签向量
    Returns:
        基尼指数
    """
    # 1. 统计各个类别出现次数
    cnt = Counter(Y)
    
    # 2. 对每个类别计算 p_i^2, 进行累加
    ans = 0.
    for y in cnt:
        ans += (cnt[y] / len(Y)) ** 2
       
    # 3. 用 1 去减, 即得基尼指数, 将其返回
    return 1 - ans

2.2 决策树生成

决策树生成就是根据特征选择所建立的 if-then 规则来从训练集数据中构建起一棵决策树。根据特征选择的策略不同,有以下三种决策树生成的算法,即 ID 3C 4.5CART

2.2.1 ID 3 算法

2.2.1.1 算法描述

ID 3 特征选择的策略是 信息增益,算法描述如下:

Input : \textbf{Input}: Input: 训练集 D D D,特征集 A A A,阈值 ε \varepsilon ε
Output : \textbf{Output}: Output: 决策树 T T T

  1. 基准情形:
    • 情况 ( 1 ) (1) (1) D D D 中所有数据均属于同一类 C k C_k Ck: 将 T T T 作为单结点树返回,类标记为 C k C_k Ck
    • 情况 ( 2 ) (2) (2) A A A 为空集: 将 T T T 作为单结点树返回,类标记为 D D D 中实例数最大的类 C k C_k Ck
  2. 递归:
    • 计算各个特征对 D D D信息增益 g ( D , A i ) g(D,A_i) g(D,Ai)
    • 选择最大 信息增益 的特征 A g A_g Ag
      • 情况 ( 1 ) (1) (1) A g < ε A_g<\varepsilon Ag<ε: 将 T T T 作为单结点树返回,类标记为 D D D 中实例数最大的类 C k C_k Ck
      • 情况 ( 2 ) (2) (2) A g ≥ ε A_g\geq\varepsilon Agε: 根据 A g A_g Ag 的每个可能取值 a i a_i ai,将 D D D 分割成若干个不相交集合 D i D_i Di 用于构建子结点,每个子结点的类标记为 D i D_i Di 中实例数最大的类
      • 对每个 D i D_i Di,以 A − { A g } A - \{A_g\} A{Ag} 为特征集,递归的生成决策树

2.2.1.2 算法实现

import numpy as np
from collections import Counter
from feature_selection import *


class ID3:
    """ID3 算法"""
    class Node:
        """内部类: 定义树结点"""
        def __init__(self, col, Y):
            """
            Args:
                col(int): 指第 col 个特征
                Y(ndarray): 标签向量
            """
            self.col = col                                  # 该结点所在层次是按第 col 个特征来划分产生子树
            self.children = {}                              # 该结点的儿子集合
            self.cnt = Counter(Y)                           # 统计标签向量中, 各个类别出现的次数
            self.label = self.cnt.most_common(1)[0][0]      # 每个结点的类别, 为其所包含数据中最多的那个类别

    def __init__(self, info_gain_threshold=0.):
        """
        Args:
            info_gain_threshold: 信息增益阈值
        """
        self.info_gain_threshold = info_gain_threshold

    def build(self, X, Y, selected):
        """
        建立决策树的方法
        Args:
            X(ndarray): 特征矩阵
            Y(ndarray): 标签向量
            selected: 已经被挑选过的特征集合
        Returns:
            返回建立好的树根结点
        """
        # 1. 将传入的数据集作为单个结点, 即构成单结点树
        cur = self.Node(None, Y)

        # 2. 如果还有特征没有被选择且标签向量 Y 中不止一个类别, 则进行特征选择, 否则直接返回单结点树
        if len(selected) != self.feat_cnt and len(set(Y)) > 1:
            # 2.1 计算还未被挑选的特征的信息增益, 选择信息增益最大的特征
            left_feats = list(set(range(self.feat_cnt)) - selected)
            info_gain_arr = [info_gain(X, Y, col) for col in left_feats]
            col_idx = np.argmax(info_gain_arr)
            best_info_gain = info_gain_arr[col_idx]
            col = left_feats[col_idx]
            
            # 2.2 若此时最大的信息增益比阈值更大, 那么遍历该特征的各种取值, 取值相同的放到同一个列表, 用它们递归的生成子树
            if best_info_gain > self.info_gain_threshold:
                cur.col = col
                for val in set(x[col] for x in X):
                    idx = [x[col] == val for x in X]
                    child_X = [x for i, x in zip(idx, X) if i]
                    child_Y = [y for i, y in zip(idx, Y) if i]
                    cur.children[val] = self.build(child_X, child_Y, selected | {col})
        
        # 3. 返回生成的树的根
        return cur

    def query(self, root, x):
        """
        辅助预测方法: 通过给定的决策树, 来预测一条测试数据 x 的类别
        Args:
            root: 一棵决策(子)树的根结点
            x(ndarray): 一条数据, 即一个特征向量
        Returns:
            对 x 预测的类别
        """
        # 1. 如果给的是单结点树或者 x 的第 root.col 个特征没有与该结点儿子的这个特征取值相同的, 则令 x 的标签与该结点的标签相同, 直接返回
        if root.col is None or x[root.col] not in root.children:
            return root.label
        
        # 2. 否则递归的在与 x 的第 root.col 个特征取值相同的那棵子树中去查询
        return self.query(root.children[x[root.col]], x)

    def fit(self, X, Y):
        """
        训练方法: 即接收训练数据, 调用 ID3 建立决策树
        Args:
            X(ndarray): 特征矩阵
            Y(ndarray): 标签向量
        """
        self.feat_cnt = len(X[0])                         # 特征的数量
        self.root = self.build(X, Y, set())

    def _predict(self, x):
        """
        预测的辅助方法
        Args:
            x(ndarray): 一条数据, 即一个特征向量
        Returns:
            对 x 预测的类别
        """
        return self.query(self.root, x)

    def predict(self, X):
        """
        预测方法
        Args:
            X(ndarray): 特征矩阵
        Returns:
            一个向量, 其中包含对每条数据 x 的预测类别
        """
        return [self._predict(x) for x in X]

2.2.2 C 4.5 算法

2.2.2.1 算法描述

C 4.5 特征选择的策略是 信息增益比,和 ID 3 相比较,区别仅仅是特征选择时计算的是信息增益比,而非信息增益,其他地方都没有区别,算法描述如下:

Input : \textbf{Input}: Input: 训练集 D D D,特征集 A A A,阈值 ε \varepsilon ε
Output : \textbf{Output}: Output: 决策树 T T T

  1. 基准情形:
    • 情况 ( 1 ) (1) (1) D D D 中所有数据均属于同一类 C k C_k Ck: 将 T T T 作为单结点树返回,类标记为 C k C_k Ck
    • 情况 ( 2 ) (2) (2) A A A 为空集: 将 T T T 作为单结点树返回,类标记为 D D D 中实例数最大的类 C k C_k Ck
  2. 递归:
    • 计算各个特征对 D D D信息增益比 g ( D , A i ) g(D,A_i) g(D,Ai)
    • 选择最大 信息增益比 的特征 A g A_g Ag
      • 情况 ( 1 ) (1) (1) A g < ε A_g<\varepsilon Ag<ε: 将 T T T 作为单结点树返回,类标记为 D D D 中实例数最大的类 C k C_k Ck
      • 情况 ( 2 ) (2) (2) A g ≥ ε A_g\geq\varepsilon Agε: 根据 A g A_g Ag 的每个可能取值 a i a_i ai,将 D D D 分割成若干个不相交集合 D i D_i Di 用于构建子结点,每个子结点的类标记为 D i D_i Di 中实例数最大的类
      • 对每个 D i D_i Di,以 A − { A g } A - \{A_g\} A{Ag} 为特征集,递归的生成决策树

2.2.2.2 算法实现

因为它和 ID3 算法的区别仅在于选择特征时采用 信息增益比 而非 信息增益,因此其实现和 ID3 相比也仅有 build 方法处有所不同,故此处只给出 build 方法

def build(self, X, Y, selected):
    """
    建立决策树的方法
    Args:
        X(ndarray): 特征矩阵
        Y(ndarray): 标签向量
        selected: 已经被挑选过的特征集合
    Returns:
        返回建立好的树根结点
    """
    # 1. 将传入的数据集作为单个结点, 即构成单结点树
    cur = self.Node(None, Y)

    # 2. 如果还有特征没有被选择且标签向量 Y 中不止一个类别, 则进行特征选择, 否则直接返回单结点树
    if len(selected) != self.feat_cnt and len(set(Y)) > 1:
        # 2.1 计算还未被挑选的特征的信息增益比, 选择信息增益比最大的特征
        left_feats = list(set(range(self.feat_cnt)) - selected)
        info_gain_ratio_arr = [info_gain_ratio(X, Y, col) for col in left_feats]
        col_idx = np.argmax(info_gain_ratio_arr)
        best_info_gain_ratio = info_gain_ratio_arr[col_idx]
        col = left_feats[col_idx]
        
        # 2.2 若此时最大的信息增益比比阈值更大, 那么遍历该特征的各种取值, 取值相同的放到同一个列表, 用它们递归的生成子树
        if best_info_gain > self.info_gain_threshold:
            cur.col = col
            for val in set(x[col] for x in X):
                idx = [x[col] == val for x in X]
                child_X = [x for i, x in zip(idx, X) if i]
                child_Y = [y for i, y in zip(idx, Y) if i]
                cur.children[val] = self.build(child_X, child_Y, selected | {col})
    
    # 3. 返回生成的树的根
    return cur

2.2.3 CART 算法

2.2.3.1 算法描述

CART 生成的决策树显然是一棵二叉决策树,算法的核心就是计算基尼指数,并借助基尼指数筛选出每一次的最优特征和最优切分点。具体地,算法描述如下:

Input : \textbf{Input}: Input: 训练集 D D D,停止计算的条件
Output : \textbf{Output}: Output: 决策树 T T T

  1. 基准情形:
    • 若满足停止条件,则返回决策树 T T T
  2. 递归:
    • 计算基尼指数,选出最优特征和最优切分点
    • 根据选出的最优特征和最优切分点将 D D D 分割为 D 1 D_1 D1 D 2 D_2 D2 两部分
    • 对两个子结点递归的调用 CART 算法

: 算法停止的条件可以是以下内容

  1. 结点中的样本数小于预定阈值: 类标记为结点中实例数最大的类 C k C_k Ck
  2. 样本集的基尼指数小于预定阈值: 类标记为结点中实例数最大的类 C k C_k Ck
  3. 没有更多特征: 没有特征了,那最终分到一个结点中的肯定完全相同,类标记就为此类

2.2.3.2 算法实现

2.3 决策树修剪

修剪决策树的过程也被称为 剪枝 Pruning,目的是防止过拟合。具体地,剪枝是从已生成的树上裁剪掉一些子树或叶结点,将其根结点或父节点作为新的叶结点,从而降低决策树模型的复杂程度,避免过拟合。

2.3.1 简单剪枝算法

简单剪枝算法的想法是,模型既要能够较好的拟合训练数据集,又应该拥有较简单的模型复杂度。显然两个要求不可兼得,拟合训练集的效果越好,复杂度就越高,复杂度越低,拟合训练集的效果就越差,因此剪枝的目标就是在这二者中权衡,取到最满意的效果。

2.3.1.1 损失函数

要实现算法,首先需要能够定量的描述 拟合效果模型复杂度 这两个概念

  1. 对预测数据的训练误差 C ( T ) C(T) C(T): 显然 C ( T ) C(T) C(T) 越小,拟合效果越好
  2. 叶结点数 ∣ T ∣ |T| T: 显然,叶结点数越多,说明模型分类越细致,复杂度越高

因此,我们的目标是使 C ( T ) C(T) C(T) ∣ T ∣ |T| T 都比较小,因而得到剪枝的损失函数如下:
C α ( T ) = C ( T ) + α ∣ T ∣ = ∑ t = 1 ∣ T ∣ N t H t ( T ) + α ∣ T ∣ = − ∑ t = 1 ∣ T ∣ ∑ i = 1 N N t i log ⁡ N t i N t + α ∣ T ∣ \begin{aligned} C_\alpha(T)&=C(T)+\alpha|T| \\ &=\sum\limits_{t=1}^{|T|}N_tH_t(T)+\alpha|T| \\ &=-\sum\limits_{t=1}^{|T|}\sum\limits_{i=1}^NN_{ti}\log\frac{N_{ti}}{N_t}+\alpha|T| \end{aligned} Cα(T)=C(T)+αT=t=1TNtHt(T)+αT=t=1Ti=1NNtilogNtNti+αT
其中对训练集的预测误差 C ( T ) = ∑ t = 1 ∣ T ∣ N t H t ( T ) C(T)=\sum\limits_{t=1}^{|T|}N_tH_t(T) C(T)=t=1TNtHt(T),即将叶结点的经验熵与叶结点中数据个数的乘积累加起来,代表了所有结点的不确定性。显然,不确定性越大,误差越大,不确定性越小,误差越小。

2.3.1.2 剪枝算法

Input : \textbf{Input}: Input: 生成算法产生的树 T T T,参数 α \alpha α
Output : \textbf{Output}: Output: 修剪后的树 T α T_\alpha Tα,及树 T α T_\alpha Tα 的损失函数值

  1. 计算将该树在根处剪枝,得到的单结点树的损失函数值 prune_loss
  2. 基准情形:
    • 若该树是单结点,则无须剪枝,直接返回单结点树的损失函数值
  3. 递归
    • 递归的在其各个儿子上进行剪枝操作,累加其各个儿子剪枝后产生子树的损失函数值 cur_loss
    • prune_loss <= cur_loss: 进行剪枝,并返回产生单结点树的损失函数值
    • prune_loss > cur_loss: 不剪枝,返回 cur_loss

2.3.1.3 剪枝算法实现

此处给出一个简单剪枝算法的实现

import numpy as np
from collections import Counter
from feature_selection import *
from ID3 import ID3

def prune(root, X, Y, alpha=.0):
    """
    剪枝算法: 
    Args:
        root: 传入的树根结点
        X(ndarray): 训练数据的特征矩阵
        Y(ndarray): 训练数据的真实类别向量
        alpha: 控制决策树拟合程度与模型复杂度之间影响的参数
    Returns:
        在该点处进行 prune 操作后产生的树的损失函数值
    """  
    # 1. 计算将该树在此处剪枝 (即将该树中包含的数据变为一个结点) 时的损失
    pruned_entropy = len(X) * entropy(Counter(Y).values())
    pruned_loss = pruned_entropy + alpha                # 每个结点处加上一个 alpha, 有 |T| 个叶结点, 累加起来就是 alpha|T|
    
    # 2. 若它本身就是一个结点, 则也不必剪枝, 直接返回该单结点树的损失函数值
    if not root.children:
        return pruned_loss
        
    # 3. 递归的在各个儿子上进行 prune 操作, 累加得到不在 root 处剪枝时的损失函数值
    cur_loss = 0.
    for col_val in root.children:
        child = root.children[col_val]
        idx = [x[root.col] == col_val for x in X]
        childX = [x for i, x in zip(idx, X) if i]
        childY = [y for i, y in zip(idx, Y) if i]
        cur_loss += prune(child, childX, childY, alpha)
    
    # 4. 若是在 root 处剪枝后得到的单结点树的损失 < 不在 root 处剪枝的树的损失, 则剪枝 (清空所有儿子), 返回单结点树的损失, 否则返回不在该点处剪枝, 保留下来的树的损失
    if pruned_loss < cur_loss:
        root.children.clear()
        return pruned_loss
    else:
        return cur_loss

2.3.2 CART 剪枝算法

3. 回归决策树及其实现

前文已经说过,决策树是一种基本的 分类回归 方法,即决策树既可以用于分类,也可以用于回归。而之前所述,均为 分类决策树 的生成算法,而没有涉及到 回归决策树。实际上 2.2.3 2.2.3 2.2.3 中所述的 CART 的全称是 classification and regression tree,即 分类与回归树。因此,在这里叙述 CART 回归树的生成算法。

3.1 决策树生成

3.1.1 输出

对于分类问题,输出是 实例的类别,分类决策树决定输出的策略很简单,对于一个叶子结点,其包含的实例中占比最大的类别,就是该结点的类别。而对于回归问题,输出是一个 实数值,那么应该怎么决定一个叶子结点的输出呢 ? ? ?
CART 回归树的策略是,叶子结点所包含实例的 y i y_i yi 的均值 c ^ m \hat{c}_m c^m 为该叶子结点的输出值,即如下式子所示
c ^ m = ave ( y i ∣ x i ∈ R m ) \hat{c}_m=\text{ave}(y_i|x_i\in\mathbf{R_m}) c^m=ave(yixiRm)
其中 R m \mathbf{R_m} Rm 代表第 m m m 个叶子结点,将该结点中所有实例的 y i y_i yi 求均值,即得叶子结点输出。
之所以采用这个策略,归根结底是因为 CART 回归树用 平方误差最小化准则 来生成决策树。显然,当已经确定了一个叶子结点 R m \mathbf{R_m} Rm 时,要让平方误差最小化,也就是如下式所表达之意
min ⁡ ∑ x i ∈ R m ( y i − c ^ m ) 2 \min\limits\sum\limits_{x_i\in\mathbf{R_m}}(y_i-\hat{c}_m)^2 minxiRm(yic^m)2
显然,当 c ^ m \hat{c}_m c^m 取值为叶子结点中所有 x i x_i xi 对应 y i y_i yi 均值时,上式取得最小值。

3.1.2 空间划分准则

这里的空间划分准则,对应的就是如何生成左右儿子的准则。显然,每个变量有一个取值范围,记第 j j j 个变量为 x ( j ) x^{(j)} x(j),其一个取值为 s s s,那么就可以根据这个变量及其取值将数据集一分为二,分别记为 R 1 R_1 R1 R 2 R_2 R2,如此就得到了左右儿子。具体地,我们要做的事情有两件

  1. 选择哪个一个变量 x x x
  2. 选择该变量的哪一个取值 s s s

解决了这两件事,我们也就得到了空间划分的准则。
CART 回归树的策略是求解下式,得到最优切分变量 j j j 和最优切分点 s s s
arg min ⁡ j , s ( min ⁡ c 1 ∑ x i ∈ R 1 ( j , s ) ( y i − c 1 ) 2 + min ⁡ c 2 ∑ x i ∈ R 2 ( j , s ) ( y i − c 2 ) 2 ) \argmin\limits_{j, s}\left(\min\limits_{c_1}\sum\limits_{x_i\in R_1\left(j, s\right)}\left(y_i-c_1\right)^2+\min\limits_{c_2}\sum\limits_{x_i\in R_2\left(j, s\right)}\left(y_i-c_2\right)^2\right) j,sargminc1minxiR1(j,s)(yic1)2+c2minxiR2(j,s)(yic2)2
即,求取使得划分后的两部分的 平方误差 总和最小的变量及取值。
显然,
min ⁡ c 1 = c ^ 1 = ave ( y i ∣ x i ∈ R 1 ( j , s ) ) min ⁡ c 2 = c ^ 2 = ave ( y i ∣ x i ∈ R 2 ( j , s ) ) \begin{aligned} \min\limits c_1=\hat{c}_1=\text{ave}(y_i|x_i\in R_1(j,s)) \\ \min\limits c_2=\hat{c}_2=\text{ave}(y_i|x_i\in R_2(j,s)) \end{aligned} minc1=c^1=ave(yixiR1(j,s))minc2=c^2=ave(yixiR2(j,s))
也就是说,确定了 j j j s s s 之后,就得到了对应的 c ^ 1 \hat{c}_1 c^1 c ^ 2 \hat{c}_2 c^2,从而可以轻易的算出上式的值。
又由于 j j j 的取值范围是 离散 的, s s s 的取值范围是 连续 的,因此,取顶一个 j j j 后,可以找到对应的最优 s s s。我们遍历每个 j j j,分布求取对应的最佳 s s s。就得到了一系列的 ( j , s ) (j, s) (j,s) 对,其中使得式子取值最小的 j j j s s s 就是 最优切分变量最优切分点 了。
显然,我们将数据集按此准则一份为二,得到两个子数据集,再递归地应用此准则于子数据集之上,反复直到满足停止条件,最终就可以得到一棵回归树,称为 最小二乘回归树

3.1.3 生成算法

3.1.4 算法实现

3.2 决策树修剪

你可能感兴趣的:(机器学习,决策树,算法)