决策树DT:ID3、C4.5原理及python实现


决策树模型与学习

决策树分类: 从根节点开始,对实例的某一特征进行测试,根据测试结果,将实例分配到对应子节点;若子节点为特征的一个取值,则递归的对实例进行测试并分配,直到到达叶节点(类别)。

决策树与条件概率分布(判别式模型) 特征空间被划分为互不相交的区域,每个区域定义一个类的概率分布,从而构成了一个条件概率分布。
各区域(叶节点)上的条件概率偏向于一个类,决策树分类时,强行将节点的实例划分为条件概率较大的一类。

特征选择

特征选择在于选取具有分类能力的特征,使得每次分割之后,各分支节点所含样本尽可能属于同一类别。这样可加快决策树的生成。

信息增益

信息熵(Information Entropy)是表示随机变量纯度/不确定性的指标。设 X X X为有限取值的随机变量(类别),其概率分布(类别比例)
P ( X = x i ) = p i , ( i = 1 , 2 , ⋯   , ∣ Y ∣ ) P(X=x_i)=p_i, \quad (i = 1, 2, \cdots, \mathcal{|Y|}) P(X=xi)=pi,(i=1,2,,Y)

则随机变量 X X X的信息熵记作 H ( X ) H(X) H(X),即
H ( X ) = − ∑ i = 1 ∣ Y ∣ p i log ⁡ 2 p i H(X)=-\sum_{i=1}^{\mathcal{|Y|}}p_i\log_2p_i H(X)=i=1Ypilog2pi

熵的取值与随机变量取值无关,仅与其概率分布有关。熵值越小,不确定性越低、数据集纯度越高。 0 ≤ H ( p ) ≤ log ⁡ ∣ Y ∣ 0 \leq H(p) \leq \log \mathcal{|Y|} 0H(p)logY

设有随机变量 ( X , Y ) (X, Y) (X,Y),其联合概率分布
P ( X = x i , Y = y i ) = p i j P(X = x_i, Y = y_i)=p_{ij} P(X=xi,Y=yi)=pij

随机变量X和Y的条件熵
H ( Y ∣ X ) = − ∑ i = 1 n ∑ j = 1 m p ( x i , y i ) log ⁡ 2 p ( y i ∣ x i ) = − ∑ i = 1 n ∑ j = 1 m p ( x i ) p ( y i ∣ x i ) log ⁡ 2 p ( y i ∣ x i ) = ∑ i = 1 n p ( x i ) H ( Y ∣ X = x i ) \begin{aligned} H(Y|X) &= - \sum_{i=1}^n \sum_{j=1}^m p(x_i, y_i) \log_2 p(y_i | x_i) \\ & = - \sum_{i=1}^n \sum_{j=1}^m p(x_i)p(y_i|x_i) \log_2 p(y_i | x_i) \\ & = \sum_{i=1}^n p(x_i) H(Y|X=x_i) \end{aligned} H(YX)=i=1nj=1mp(xi,yi)log2p(yixi)=i=1nj=1mp(xi)p(yixi)log2p(yixi)=i=1np(xi)H(YX=xi)

条件熵 H ( Y ∣ X ) H(Y|X) H(YX)表示已知 X X X的条件下 Y Y Y的不确定性,上式为已知 X X X的条件下 Y Y Y的条件概率分布的熵对 X X X的数学期望。当熵和条件熵由数据估计得到时,熵和条件熵分别称为经验熵经验条件熵

信息增益度量得知特征 X X X而使得类 Y Y Y不确定性减少的程度,定义为样本集 D D D的经验熵与已知特征 a a a条件下 D D D的经验条件熵之差,即
g ( D , a ) = H ( D ) − H ( D ∣ a ) g(D,a)=H(D)-H(D|a) g(D,a)=H(D)H(Da)

注:信息增益亦称为类与特征的互信息

图1 联合熵、条件熵与互信息(信息增益)间的关系

最优划分属性应使划分后的样本信息增益/纯度最大(不确定性降低),即条件熵取最小
a o p t = arg ⁡ min ⁡ A ∑ v = 1 V p v H ( Y ∣ X = x v ) a_{opt} = \arg \min_A \sum_{v=1}^Vp_vH(Y|X=x_v) aopt=argAminv=1VpvH(YX=xv)

可见,信息增益准则偏好于取值较多的属性(V较大)。

表1 西瓜数据集2.0
编号色泽根蒂敲声纹理脐部触感好瓜
1青绿蜷缩浊响清晰凹陷硬滑
2乌黑蜷缩沉闷清晰凹陷硬滑
3乌黑蜷缩浊响清晰凹陷硬滑
4青绿蜷缩沉闷清晰凹陷硬滑
5浅白蜷缩浊响清晰凹陷硬滑
6青绿稍蜷浊响清晰稍凹软粘
7乌黑稍蜷浊响稍糊稍凹软粘
8乌黑稍蜷浊响清晰稍凹硬滑
9乌黑稍蜷沉闷稍糊稍凹硬滑
10青绿硬挺清脆清晰平坦软粘
11浅白硬挺清脆模糊平坦硬滑
12浅白蜷缩浊响模糊平坦软粘
13青绿稍蜷浊响稍糊凹陷硬滑
14浅白稍蜷沉闷稍糊凹陷硬滑
15乌黑稍蜷浊响清晰稍凹软粘
16浅白蜷缩浊响模糊平坦硬滑
17青绿蜷缩沉闷稍糊稍凹硬滑

由上表可知,类别 ∣ Y ∣ = 2 \mathcal{|Y|}=2 Y=2,信息熵
H ( D ) = − 8 17 log ⁡ 2 8 17 − 9 17 log ⁡ 2 9 17 = 0.998 H(D)=-\frac{8}{17} \log_2\frac{8}{17} -\frac{9}{17} \log_2\frac{9}{17} = 0.998 H(D)=178log2178179log2179=0.998

若选择色泽作为划分属性,色泽包含的属性值有{青绿,乌黑,浅白},分别标记位 D 1 D_1 D1 D 2 D_2 D2 D 3 D_3 D3,则
g ( D , 色 泽 ) = H ( D ) − ( 6 17 H ( D 1 ) + 6 17 H ( D 2 ) + 5 17 H ( D 3 ) ) = 0.998 − 1 17 [ 6 ( − 3 6 log ⁡ 2 3 6 − 3 6 log ⁡ 2 3 6 ) + ( − 4 6 log ⁡ 2 4 6 − 2 6 log ⁡ 2 2 6 ) + ( − 4 5 log ⁡ 2 4 5 − 1 5 log ⁡ 2 1 5 ) ] = 0.109 \begin{aligned} g(D, 色泽) &= H(D) - \left(\frac{6}{17}H(D_1)+ \frac{6}{17}H(D_2) + \frac{5}{17}H(D_3)\right) \\ & = 0.998 - \frac{1}{17} \left[ 6\left( -\frac{3}{6} \log_2 \frac{3}{6} -\frac{3}{6} \log_2 \frac{3}{6} \right) + \left( -\frac{4}{6} \log_2 \frac{4}{6} -\frac{2}{6} \log_2 \frac{2}{6} \right) + \left( -\frac{4}{5} \log_2 \frac{4}{5} -\frac{1}{5} \log_2 \frac{1}{5} \right) \right] \\ & = 0.109 \end{aligned} g(D,)=H(D)(176H(D1)+176H(D2)+175H(D3))=0.998171[6(63log26363log263)+(64log26462log262)+(54log25451log251)]=0.109

同理,可计算出其他特征的信息增益,其中具有最大信息增益的属性为 g ( D , 纹 理 ) = 0.381 g(D, 纹理)=0.381 g(D,)=0.381。因此,使用纹理特征对样本集进行划分,所得样本的不确定性最小。

信息增益比

极端情况下,若样本集含n个样本,离散属性a含n个属性。若使用属性a分割样本集,则所得各分支节点的信息熵均为0,此时划分后的样本集纯度最大。但每个分支节点仅含1个样本,所得决策树无泛化能力。

定义增益率为特征 a a a的信息增益 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)

其中分母项为特征a的信息熵 H a ( D ) = ∑ i = v V ∣ D v ∣ D ∣ log ⁡ 2 D v D H_a(D) = \sum_{i=v}^V\dfrac{|D_v}{|D|} \log_2\dfrac{D_v}{D} Ha(D)=i=vVDDvlog2DDv

属性a的取值越多,分母可能越大,增益率准则偏好于较少取值多的属性。因此,最优分割属性应同时具有较高信息增益和增益比,如C.5算法先选择信息增益高于平均值的属性,再从中选择增益率高的属性。

ID3算法

决策树生成

输入:训练集 D D D,特征集 A A A,阈值 ε \varepsilon ε
输出:决策树 T T T

递归生成决策树算法,如下:

  1. 若数据集 D D D中所有实例均属于同一类 C k C_k Ck,则 T T T为单节点树(叶节点),将类别 C k C_k Ck作为节点类标记,返回 T T T
  2. 若特征集 A A A为空,则 T T T为单节点树,将 D D D中含样本最多的类别 C k C_k Ck作为其类标记,返回 T T T
  3. 计算 A A A中各特征的信息增益,选择信息增益最大的特征 a o p t a_{opt} aopt对样本集 D D D进行划分;
  4. a o p t a_{opt} aopt的信息增益小于阈值 ε \varepsilon ε,则置 T T T为单节点树,将 D D D中含样本最多的类别 C k C_k Ck作为其类标记,返回 T T T
  5. 否则,使用 a o p t a_{opt} aopt V V V个不同属性值将 D D D划分为 V V V个不同的子集 D v D_v Dv,每个子集均为节点的一个分支;
  6. 对所有分支节点,令 D = D v D=D_v D=Dv A = A − a o p t A=A-a_{opt} A=Aaopt,递归调用上述步骤,得到分支树并返回;

ID3算法的不足

  1. 未考虑具有连续值的属性;
  2. 极端情况下选择信息增益最大的属性(完全随机性变量)会使模型失去泛化能力;
  3. 未考虑缺失值处理与过拟合问题;

C4.5算法

C4.5对ID3算法进行了改进,使用信息增益比选择每次划分数据集的最优特征,并对缺失值和过拟合问题进行了处理。

连续值处理

  1. 将连续值离散化,如m个样本的特征 a a a m m m个不同的属性值,将属性值升序排列为 a 1 , ⋯   , a m a_1,\cdots,a_m a1,,am
  2. 取相邻属性值的均值作为划分点,共计 m − 1 m-1 m1个,每个划分点 t t t可将 D D D分为子集 D t − D_t^- Dt D t + D_t^+ Dt+
  3. 分别计算 m − 1 m-1 m1个划分点作为二元分类的信息增益,取信息增益最大的点作为分类点;
    g R ( D , a ) = max ⁡ t ∈ T a g R ( D , a , t ) = max ⁡ t ∈ T a ( H ( D ) − ∑ λ ∈ { − , + } ∣ D t λ ∣ ∣ D ∣ H ( D t λ ) ) g_R(D, a) = \max_{t \in T_a} g_R(D, a, t) = \max_{t \in T_a} (H(D)- \sum_{\lambda \in \{-, +\}} \frac{|D_t^\lambda|}{|D|}H(D_t^\lambda)) gR(D,a)=tTamaxgR(D,a,t)=tTamax(H(D)λ{,+}DDtλH(Dtλ))
  4. 以连续属性划分的节点,连续属性还可作为其后代节点的划分属性;

缺失值处理

如何在属性值缺失的情况下选择最优划分属性?
给定训练集 D D D和属性 a a a,令 D ~ \tilde D D~表示 D D D在属性 a a a上没有缺失值的样本子集,每个样本 x x x的权重为 w x w_x wx(初始为1),则
ρ = ∑ x ∈ D ~ w x ∑ x ∈ D w x   p ~ k = ∑ x ∈ D ~ k w x ∑ x ∈ D ~ w x ( 1 ≤ k ≤ ∣ Y ∣ )   r ~ v = ∑ x ∈ D ~ v w x ∑ x ∈ D ~ w x ( 1 ≤ v ≤ V ) \begin{aligned} \rho &= \frac{\sum_{x \in \tilde D} w_x}{\sum_{x \in D} w_x} \\ \,\\ \tilde p_k &= \frac{\sum_{x \in \tilde D_k} w_x}{\sum_{x \in \tilde D} w_x} \quad(1 \leq k \leq |\mathcal{Y}|) \\ \,\\ \tilde r_v &= \frac{\sum_{x \in \tilde D^v} w_x}{\sum_{x \in \tilde D} w_x} \quad (1 \leq v \leq V) \end{aligned} ρp~kr~v=xDwxxD~wx=xD~wxxD~kwx(1kY)=xD~wxxD~vwx(1vV)

式中, ρ \rho ρ表示未缺失值比例、 p ~ k \tilde p_k p~k表示 D ~ \tilde D D~中类别为 k k k的样本比例、 r ~ v \tilde r_v r~v表示 D ~ \tilde D D~中属性 a a a取值为 a v a_v av的样本比例。

因此缺失值属性a的信息增益推广为
g ( D , a ) = ρ × g ( D ~ , a ) = ρ × ( H ( D ~ ) − ∑ v = 1 V r ~ v H ( D ~ v ) ) , H ( D ~ v ) = − ∑ k = 1 ∣ Y ∣ p ~ k log ⁡ 2 p k g(D, a) = \rho \times g(\tilde D, a) = \rho \times \left(H(\tilde D) - \sum_{v=1}^V \tilde r_v H(\tilde D^v) \right), \quad H(\tilde D^v) =-\sum_{k=1}^{|\mathcal{Y}|} \tilde p_k \log_2 p_k g(D,a)=ρ×g(D~,a)=ρ×(H(D~)v=1Vr~vH(D~v)),H(D~v)=k=1Yp~klog2pk

给定划分属性,若样本在该属性上值缺失,如何对样本进行分配?

若样本 x x x在划分属性 a a a上取值已知,则将 x x x划入其取值对应的子节点,样本权值保持不变;若取值未知则将样本 x x x划分到所有分支节点,样本权值在 a v a^v av的分支节点中调整为 r ~ v ⋅ w x \tilde r_v \cdot w_x r~vwx

直观上,同一样本以不同概率分配到不同的分支节点。

C4.5算法的不足

  1. 生成的决策树为多叉树(一个父节点含多个子节点),没有二叉树模型效率高;
  2. 只能用于分类,不能用于回归;
  3. 使用熵模型选取最优划分属性,计算复杂,尤其是处理连续值属性;

决策树剪枝

一般情况下递归产生的决策树对训练数据的分类很准确,但有可能对未知数据的分类表现很差。上述现象称为过拟合,一般通过简化决策树复杂度(剪枝)解决。

预剪枝
在划分之前,评估单节点树以及节点按属性值展开后的预测精度,若单节点树的预测精度不低于展开后树的预测精度,则将该节点作为叶节点。

后剪枝
定义决策树的损失函数如下:
C α ( T ) = ∑ t = 1 ∣ T ∣ N t H t ( T ) + α ∣ T ∣ = C ( T ) + α ∣ T ∣ , H t ( T ) = − ∑ k N t k N t log ⁡ N t k N t C_\alpha(T) = \sum_{t=1}^{|T|}N_tH_t(T)+\alpha |T| = C(T) + \alpha |T|, \quad H_t(T)=-\sum_k \frac{N_{tk}}{N_t} \log \frac{N_{tk}}{N_t} Cα(T)=t=1TNtHt(T)+αT=C(T)+αT,Ht(T)=kNtNtklogNtNtk

式中, ∣ T ∣ |T| T为叶节点数目(模型的复杂度), t t t是树 T T T的叶节点, N t N_t Nt为节点 t t t所含样本数, N t k N_{tk} Ntk t t t节点中类别为 k k k的样本数, k = 1 , 2 , ⋯   , K k=1,2,\cdots,K k=1,2,,K H t ( T ) H_t(T) Ht(T) t t t节点的经验熵,可变参数 a ≥ 0 a \geq 0 a0 C ( T ) C(T) C(T)为模型对训练数据的预测误差。

参数 α \alpha α的值可平衡模型复杂度和训练误差。 α = 0 \alpha=0 α=0时,表示不考虑模型复杂程度,只考虑训练误差。较大的 α \alpha α偏向于较简单的模型( T T T小,训练误差大),较小的 α \alpha α偏向于较复杂的模型( T T T大,训练误差小)。

决策树的生成只考虑拟合效果(局部模型),而决策树剪枝是通过优化损失函数。


程序实现ID3

from collections import Counter
from itertools import chain
from math import log2

import numpy as np

from_iterable = chain.from_iterable


def cal_entropy(array):
    """计算信息熵"""
    total = 1. * len(array)
    ent = 0.0
    for value in Counter(array).values():
        prob = value / total
        ent -= prob * log2(prob)
    return ent


def cal_gain(feat_values, categories):
    """计算某特征的信息增益"""
    uniques = set(feat_values)
    nums = len(uniques)
    feat_dict = dict(zip(uniques, range(nums)))

    # 提取各特征值的子集
    sub_labels = [[] for _ in range(nums)]
    for value, category in zip(feat_values, categories):
        sub_labels[feat_dict[value]].append(category)

    # 计算条件熵
    cond_ent = 0.0
    total = len(categories)
    for array in sub_labels:
        cond_ent += cal_entropy(array) * len(array) / total

    # 返回信息增益
    return cal_entropy(categories) - cond_ent


def tree_depth(tree):
    """计算决策树深度,字典形式存储"""
    if isinstance(tree, dict):
        branches = [value for value in tree.values()][0]
        return max(tree_depth(node) for node in branches.values()) + 1
    else:
        return 1


class DecisionTree_ID3:

    def __int__(self):

        self.tree = None
        pass

    def train(self, X, y, names=None):

        self._feat_names = ['FEAT_{}'.format(i) for i in
                            range(len(X[0]))] if names is None else names

        unique = list(set(y))
        category2num = dict((category, num) for category, num in
                            zip(unique, range(len(unique))))
        self._names = unique

        categories = [category2num[i] for i in y]
        data_set = np.hstack((X, np.array([categories]).T))

        # 生成决策树
        self.tree = self._make_tree(data_set, self._feat_names)

    def _make_tree(self, data_set, feat_names):
        """生成决策树"""
        categories = data_set[:, -1]

        # 当前节点包含的样本为同一类别
        category = categories[0]
        if np.all(categories == category):
            return self._names[int(category)]

        # 当前属性集为空,返回样本中频数最大的类别
        if len(data_set[0]) == 1:
            return Counter(categories).most_common(1)[0][0]

        best_num = self.best_feat(data_set)
        best_name = feat_names[best_num]

        # 以当前节点为根的树
        my_tree = {best_name: {}}
        node = my_tree[best_name]

        # 最优特征
        values = data_set[:, best_num]

        # 遍历各属性值
        data_set = np.delete(data_set, best_num, axis=1)
        feat_names = np.delete(feat_names, best_num)
        for value in set(values):
            sub_data_set = data_set[values == value]
            node[value] = self._make_tree(sub_data_set, feat_names)

        return my_tree

    def best_feat(self, data_set):
        """计算最优特征,ID3算法每次选取信息增益最大的特征
        """
        categories = data_set[:, -1]
        nums = len(data_set[0]) - 1
        gains = [cal_gain(data_set[:, i], categories) for i in range(nums)]

        # 找到最大信息增益特征的所在列位置
        num = 0
        max_gain = gains[0]
        for i, gain in enumerate(gains[1:]):
            if max_gain < gain:
                num = i + 1
                max_gain = gain
        return num


if __name__ == '__main__':

    X = [['青绿', '蜷缩', '浊响', '清晰', '凹陷', '硬滑'],
        ['乌黑', '蜷缩', '沉闷', '清晰', '凹陷', '硬滑'],
        ['乌黑', '蜷缩', '浊响', '清晰', '凹陷', '硬滑'],
        ['青绿', '蜷缩', '沉闷', '清晰', '凹陷', '硬滑'],
        ['浅白', '蜷缩', '浊响', '清晰', '凹陷', '硬滑'],
        ['青绿', '稍蜷', '浊响', '清晰', '稍凹', '软粘'],
        ['乌黑', '稍蜷', '浊响', '稍糊', '稍凹', '软粘'],

        ['乌黑', '稍蜷', '浊响', '清晰', '稍凹', '硬滑'],
        ['乌黑', '稍蜷', '沉闷', '稍糊', '稍凹', '硬滑'],
        ['青绿', '硬挺', '清脆', '清晰', '平坦', '软粘'],
        ['浅白', '硬挺', '清脆', '模糊', '平坦', '硬滑'],
        ['浅白', '蜷缩', '浊响', '模糊', '平坦', '软粘'],
        ['青绿', '稍蜷', '浊响', '稍糊', '凹陷', '硬滑'],
        ['浅白', '稍蜷', '沉闷', '稍糊', '凹陷', '硬滑'],
        ['乌黑', '稍蜷', '浊响', '清晰', '稍凹', '软粘'],
        ['浅白', '蜷缩', '浊响', '模糊', '平坦', '硬滑'],
        ['青绿', '蜷缩', '沉闷', '稍糊', '稍凹', '硬滑']]

    y = ['好瓜', '好瓜', '好瓜', '好瓜', '好瓜', '好瓜', '好瓜', '好瓜', '坏瓜', '坏瓜', '坏瓜', '坏瓜',
         '坏瓜', '坏瓜', '坏瓜', '坏瓜', '坏瓜']

    names = ['色泽', '根蒂', '敲击', '纹理', '脐部', '触感']

    tree = DecisionTree_ID3()
    tree.train(X, y, names)

    print(tree.tree)    
    # print(tree_depth(tree.tree))
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值