机器学习(Machine Learning)——第4章 决策树

本文深入探讨了决策树学习算法,包括基本流程、特征选择(信息增益、增益率、基尼指数)、剪枝处理(预剪枝与后剪枝)以及连续值和缺失值处理。通过实例展示了决策树的构建过程,解释了如何使用Python实现决策树,并介绍了sklearn库中的决策树应用。此外,还讨论了多变量决策树的概念。
摘要由CSDN通过智能技术生成

4.1 基本流程

  决策树是基于树模型做决策。一般的,一棵决策树包含一个根结点、若干个内部结点和若干个叶结点;叶结点对应于决策结果,其他每个结点则对应于一个属性测试;每个结点包含的样本集合根据属性测试的结果被划分到子结点中;根结点包含样本全集。
  从根结点到每个叶结点的路径对应了一个判定测试序列。决策树学习的目的是为了产生一棵泛化能力强,即处理未见示例能力强的决策树。
  根据表4-1可生成如图4-1所示的决策树。

表4-1 西瓜数据集

在这里插入图片描述
在这里插入图片描述

图4-1 决策树样例

  如上所述,图4-1决策树每一个通往叶子结点的路径就是一条判断序列。例如对于表4-1中的第1个样本,从决策树根结点出发,判断“纹理”特征,1号样本纹理为“清晰”,进入纹理结点最左侧的子结点中,接着判断“根蒂”,1号样本根蒂为“蜷缩”,进入根蒂结点最左侧的子结点中,该子结点为叶子结点,因此得到1号样本的预测结果为“好瓜”。
  根据训练集样本生成一棵决策树的算法,如图4-2所示。
在这里插入图片描述

图4-2 决策树学习基本算法

  决策树的生成是一个递归过程。简单得说,生成决策树主要分为两个部分。
  ① 特征选择: 特征选择是指从当前结点所拥有的特征中选择一个特征 a a a作为当前结点的划分标准,一般特征选择的策略将直接影响决策树算法的性能。此步骤对应图4-2中TreeGenerate过程的第8步。
  ② 生成子结点: 按照当前结点所包含的训练样本数据在属性 a a a上的取值将训练样本分成若干类,每一类作为当前结点的子结点所具有的数据样本。然后对未划分的结点进行考察,递归地进行属性选择和数据样本划分。
  例如,对于表4-1中的17个样本,我们选择“纹理”属性对数据集进行划分,得到三个数据子集,分别是:清晰(1,2,3,4,5,6,8,10,15),稍糊(7,9,13,14,17),模糊(11,12,16)。生成三个子结点,将这三个子集分别分到对应的子结点中,就形成了图4-3所示的二层决策树。然后递归处理,对新生成的三个子结点执行同样的操作(注意:新生成的三个子结点不包含属性“纹理”)。
在这里插入图片描述

图4-3 基于“纹理”属性对根结点划分

  
  在图4-2所示的算法流程中,有三种情形会导致递归返回。
  (1)当前结点所包含的样本全属于同一类别,无需划分;
  (2)当前属性集为空,或是所有样本在所有属性上取值相同,无法划分;
  (3)当前结点包含的样本集合为空,不能划分。
  对于情况(1),很好理解,当前结点所包含的样本全部属于同一类别,即使进行划分生成子树,该结点的子树所有叶结点也只能属于同一类别,这样做是没有意义的。
  对于情况(2),属性集为空,则没有特征可供选择,划分没有依据;所有样本在所有属性上取值相同,则划分出的集合只有一个(它本身,相当于没有划分)。对于此种情况,直接把当前结点标记为叶结点,并将其类别设定为该结点所含样本最多的类别。
  对于情况(3),直接把当前结点标记为叶结点,其类别为父结点所含样本最多的类别。

4.2 划分选择

  此小节主要介绍特征选择部分选择特征的原则(“特征”和“属性”是一个意思)。一般而言,随着划分过程不断进行,我们希望决策树的分支结点所包含的样本尽可能属于同一类别,即结点的“纯度”越来越高。
  常用三种指标来作为选择特征进行划分的依据。

4.2.1 信息增益

  信息熵: 度量样本集合纯度最常用的一种指标。假定当前样本集合 D D D中第 k k k类样本所占的比例为 p k ( k = 1 , 2 , . . . , ∣ y ∣ ) p_k(k=1,2,...,|y|) pk(k=1,2,...,y),则 D D D的信息熵定义为
E n t ( D ) = − ∑ k = 1 ∣ y ∣ p k ∗ l o g 2 p k \begin{aligned} Ent(D)=-\sum_{k=1}^{|y|}p_k*log_2p_k \end{aligned} Ent(D)=k=1ypklog2pk
E n t ( D ) Ent(D) Ent(D)值越小,则 D D D的纯度越高。
  信息增益: 假定离散属性 a a a V V V个可能的取值 { a 1 , a 2 , . . . , a V } \{a^1,a^2,...,a^V\} {a1,a2,...,aV}(例如表4-1中“纹理”属性有:清晰、稍糊、模糊,三个取值) ,若使用 a a a来对样本集 D D D进行划分,则会产生 V V V个分支结点,其中第 v v v个分支结点包含了 D D D中所有在属性 a a a上取值为 a v a^v av的样本,记为 D v D^v Dv(例如图4-3)。 ∣ D v ∣ |D^v| Dv表示集合 D v D^v Dv包含样本的数量,则数据集 D D D在属性 a a a上的信息增益定义为
G a i n ( D , a ) = E n t ( D ) − ∑ v = 1 V ∣ D v ∣ ∣ D ∣ E n t ( D v ) \begin{aligned} Gain(D,a)=Ent(D)-\sum_{v=1}^{V}\frac{|D^v|}{|D|}Ent(D^v) \end{aligned} Gain(D,a)=Ent(D)v=1VDDvEnt(Dv)
  以表4-1的西瓜数据集为例,数据集记为D。D中样本分为两类,则 ∣ y ∣ = 2 |y|=2 y=2,好瓜占 8 17 \frac{8}{17} 178,坏瓜占 9 17 \frac{9}{17} 179。因此 D D D的信息熵为
E n t ( D ) = − ∑ k = 1 2 p k ∗ l o g 2 p k = − ( 8 17 ∗ l o g 2 8 17 + 9 17 ∗ l o g 2 9 17 ) = 0.998 \begin{aligned} Ent(D)=-\sum_{k=1}^{2}p_k*log_2p_k=-(\frac{8}{17}*log_2\frac{8}{17}+\frac{9}{17}*log_2\frac{9}{17})=0.998 \end{aligned} Ent(D)=k=12pklog2pk=(178log2178+179log2179)=0.998
  以属性“色泽”为例,使用色泽属性对 D D D进行划分,则得到3个子集: D 1 ( 色 泽 = 青 绿 ) D^1(色泽=青绿) D1(=绿) D 2 ( 色 泽 = 乌 黑 ) D^2(色泽=乌黑) D2(=) D 3 ( 色 泽 = 浅 白 ) D^3(色泽=浅白) D3(=)
  分别计算 D 1 , D 2 , D 3 D^1,D^2,D^3 D1,D2,D3的信息熵
E n t ( D 1 ) = − ( 3 6 ∗ l o g 2 3 6 + 3 6 ∗ l o g 2 3 6 ) = 1.000 \begin{aligned} Ent(D^1)=-(\frac{3}{6}*log_2\frac{3}{6}+\frac{3}{6}*log_2\frac{3}{6})=1.000 \end{aligned} Ent(D1)=(63log263+63log263)=1.000
E n t ( D 2 ) = − ( 4 6 ∗ l o g 2 4 6 + 2 6 ∗ l o g 2 2 6 ) = 0.918 \begin{aligned} Ent(D^2)=-(\frac{4}{6}*log_2\frac{4}{6}+\frac{2}{6}*log_2\frac{2}{6})=0.918 \end{aligned} Ent(D2)=(64log264+62log262)=0.918
E n t ( D 3 ) = − ( 1 5 ∗ l o g 2 1 5 + 4 5 ∗ l o g 2 4 5 ) = 0.722 \begin{aligned} Ent(D^3)=-(\frac{1}{5}*log_2\frac{1}{5}+\frac{4}{5}*log_2\frac{4}{5})=0.722 \end{aligned} Ent(D3)=(51log251+54log254)=0.722
  计算属性“色泽”在数据集 D D D上的信息增益为:
G a i n ( D , 色 泽 ) = E n t ( D ) − ∑ v = 1 3 ∣ D v ∣ ∣ D ∣ E n t ( D v ) = 0.998 − ( 6 17 ∗ 1.000 + 6 17 ∗ 0.918 + 5 17 ∗ 0.722 ) = 0.109 \begin{aligned} Gain(D,色泽)=Ent(D)-\sum_{v=1}^{3}\frac{|D^v|}{|D|}Ent(D^v)=0.998-(\frac{6}{17}*1.000+\frac{6}{17}*0.918+\frac{5}{17}*0.722)=0.109 \end{aligned} Gain(D,)=Ent(D)v=13DDvEnt(Dv)=0.998(1761.000+1760.918+1750.722)=0.109
  同理,计算出其他属性(根蒂、敲声、纹理、脐部、触感)的信息增益,从中选出信息增益最大的属性 a a a划分数据集 D D D。信息增益越大,意味着使用属性 a a a来进行划分所获得的“纯度提升”越大。
  信息增益准则对可取值数目较多的属性有所偏好。

4.2.2 增益率

  定义属性 a a a在数据集 D D D上的“增益率”为
G a i n _ r a t i o ( D , a ) = G a i n ( D , a ) I V ( a ) \begin{aligned} Gain\_ratio(D,a)=\frac{Gain(D,a)}{IV(a)} \end{aligned} Gain_ratio(D,a)=IV(a)Gain(D,a)
其中
I V ( a ) = − ∑ v = 1 V ∣ D v ∣ ∣ D ∣ ∗ l o g 2 ∣ D v ∣ ∣ D ∣ \begin{aligned} IV(a)=-\sum_{v=1}^{V}\frac{|D^v|}{|D|}*log_2\frac{|D^v|}{|D|} \end{aligned} IV(a)=v=1VDDvlog2DDv
  属性 a a a的可能取值数目越多(即 V V V越大), I V ( a ) IV(a) IV(a)的值通常越大。
  增益率准则对可取值数目较少的属性有所偏好。因此,增益率常与信息增益结合使用:先从候选划分属性中找出信息增益高于平均水平的属性,再从中选择增益率最高的。

4.2.3 基尼指数

  定义数据集 D D D的基尼值为
G i n i ( D ) = ∑ k = 1 ∣ y ∣ ∑ k ′ ≠ k p k p k ′ = 1 − ∑ k = 1 ∣ y ∣ p k 2 \begin{aligned} Gini(D)=\sum_{k=1}^{|y|}\sum_{k'\neq k}p_kp_{k'}=1-\sum_{k=1}^{|y|}p_k^2 \end{aligned} Gini(D)=k=1yk=kpkpk=1k=1ypk2
   G i n i ( D ) Gini(D) Gini(D)反映了从数据集 D D D中随机抽取两个样本,其类别标记不一致的概率。 G i n i ( D ) Gini(D) Gini(D)越小,数据集 D D D纯度越高。
  定义属性 a a a在数据集 D D D上的基尼指数为
G i n i _ i n d e x ( D , a ) = ∑ v = 1 V ∣ D v ∣ ∣ D ∣ G i n i ( D v ) \begin{aligned} Gini\_index(D,a)=\sum_{v=1}^{V}\frac{|D^v|}{|D|}Gini(D^v) \end{aligned} Gini_index(D,a)=v=1VDDvGini(Dv)
  选择基尼指数最小的属性作为最优划分属性。

4.2.4 小结

表4-2 信息增益,增益率及基尼指数总结
指标确定最优划分属性偏好经典决策树算法
信息增益取最大值对应属性偏好可取值数目较多的属性ID3
增益率取最大值对应属性偏好可取值数目较少的属性C4.5
基尼指数取最小值对应属性——CART

4.2.5 决策树代码实现

  使用Python语言从底层实现决策树的学习算法、预测新样本等功能。未使用机器学习库,以便能帮助自己更好的理解算法细节。使用信息增益作为选择最优划分属性的依据。

# -*- coding: utf-8 -*-
"""
Created on Sat Nov 21 19:22:06 2020

@author: qiqi
"""

import math
import random
from collections import Counter

class TreeNode(object):
    
    def __init__(self):
        '''
        IsLeaf:当前结点是否为叶子结点
        C:当前结点标签类别,仅当IsLeaf为True时才有意义
        a:当前结点判断的属性
        Sons:当前结点子结点字典,通过a的取值作为key访问
        
        Returns
        -------
        None.

        '''
        self.IsLeaf = False
        self.C = None
        self.a = None
        self.Sons = dict()
        
class DecisionTree(object):
    
    def __init__(self, AValue=dict(), Attribute=[]):
        '''
        Parameters
        ----------
        AValue : TYPE, optional
            记录每个属性的可以取哪些值. The default is dict().
        Attribute : TYPE, optional
            数据集属性/特征列表. The default is [].

        Returns
        -------
        None.

        '''
        self.AValue = AValue
        self.Attribute = Attribute
    
    def __CalClassNum(self, D):
        #计算数据集D中的样本类别数,其中D为二维列表,最后一列是类别标签
        s = set()
        D = list(D)
        for d in D:
            s.add(d[-1])
        return len(s)
    
    def __Same(self, D, A):
        #判断D中样本在A属性上取值是否相同。A为属性列表,A中某个属性在A的下标i,与D中第i列数据对应
        for a in A:
            s = set()
            for d in D:
                s.add(d[self.Attribute.index(a)])
            if len(s) > 1:
                return False
        return True
    
    def __CalMax(self, D):
        #计算数据集D中样本数最多的类别
        C = [d[-1] for d in D] #从D中的每一行取最后一个元素,得到每个样本的类别
        dic = Counter(C) #统计C中每个元素出现的次数,返回一个字典
        _, result = max(zip(dic.values(), dic.keys()))
        return result
    
    def __Ent(self, D):
        #计算信息熵
        C = [d[-1] for d in D] #从D中的每一行取最后一个元素,得到每个样本的类别
        dic = Counter(C) #统计C中每个元素出现的次数,返回一个字典
        rate = list(dic.values())
        Sum = 0
        for r in rate:
            Sum += (r/len(D)) * math.log2(r/len(D))
        return -Sum
    
    def __Gain(self, D, A, a):
        #计算属性a在数据集D上的信息增益
        EntD = self.__Ent(D)
        Gain = 0
        for a_v in self.AValue[a]:
            #将数据集D按照属性a的取值分为若干部分
            D_v = self.__GetSubset(D, A, a, a_v)
            Gain += (len(D_v)/len(D)) * self.__Ent(D_v)
        Gain = EntD - Gain
        return Gain
    
    def __SplitA(self, D, A):
        #计算数据集D对A中每个属性的信息增益或增益率或基尼指数作为评价值,选择最大评价值对应的属性
        Max = 0
        result = A[0]
        for a in A:
            e = self.__Gain(D, A, a)
            if e > Max:
                Max = e
                result = a
        return result
    
    def __GetSubset(self, D, A, a, a_v):
        #计算数据集D中在某一属性a上取值为a_v的样本子集,记为D_v
        D_v = []
        for d in D:
            if d[self.Attribute.index(a)] == a_v:
                D_v.append(d)
        return D_v
    
    def CalAValue(self, D, A):
        #计算属性集A中每个属性在数据集D中的取值,返回一个字典,字典key为属性,value为取值集合
        AValue = dict()
        for a in A:
            s = set()
            for d in D:
                s.add(d[A.index(a)])
            AValue[a] = s
        return AValue
        
    def TreeGenerate(self, D=None, A=None):
        node = TreeNode()  #生成结点node
        if self.__CalClassNum(D) == 1:
            #D中样本全属于同一类别C,将node标记为C类叶结点
            C = D[0][-1]
            node.IsLeaf = True
            node.C = C
            return node
        elif A is None or len(A)==0 or self.__Same(D, A):
            #当前属性集A为空,或者D中样本在A上取值相同,将node标记为叶结点,
            #其类别标记为D中样本数最多的类别
            node.IsLeaf = True
            node.C = self.__CalMax(D)
            return node
        else:
            #从A中选择最优划分属性a
            #对a的每一个值a_v为node生成一个分支,令D_v表示D中在a上取值为a_v的样本子集
            #如果D_v为空,将分支结点标记为叶结点,其类别标记为D中样本最多的类别,return
            #如果D_v不为空,以TreeGenerate(D_v, A\{a})为分支结点
            a = self.__SplitA(D, A)
            for a_v in self.AValue[a]:
                D_v = self.__GetSubset(D, A, a, a_v)
                if len(D_v) == 0:
                    son = TreeNode()
                    son.IsLeaf = True
                    son.C = self.__CalMax(D)
                    node.Sons[a_v] = son
                else:
                    A_a = A.copy()
                    A_a.remove(a)
                    son =  self.TreeGenerate(D_v, A_a)
                    node.IsLeaf = False
                    node.a = a
                    node.Sons[a_v] = son
            return node
        
    def Predict(self, root, A, d):
        #预测单个样本d
        if root is not None:
            if root.IsLeaf:
                return root.C
            else:
                return self.Predict(root.Sons[d[A.index(root.a)]], A, d)
        else:
            return None
    
    def Evaluate(self, root, A, D):
        #计算决策树在数据集D上的精度,保留两位小数
        right = 0
        for d in D:
            if self.Predict(root, A, d) == d[-1]:
                right += 1
        return round(right/len(D), 2)
    
    def Dfs(self, root, p, Path):
        #以深度优先遍历方式得到通往每个叶结点的路径
        if root.IsLeaf:
            p += '--' + root.C
            Path.append(p)
        else:
            for a_v in self.AValue[root.a]:
                tp = p
                p += root.a + '?' + '--' + '(' + a_v + ')' + '--'
                self.Dfs(root.Sons[a_v], p, Path)
                p = tp
            
#定义数据集
D = [['青绿', '蜷缩', '浊响', '清晰', '凹陷', '硬滑', '好瓜'],
     ['乌黑', '蜷缩', '沉闷', '清晰', '凹陷', '硬滑', '好瓜'],
     ['乌黑', '蜷缩', '浊响', '清晰', '凹陷', '硬滑', '好瓜'],
     ['青绿', '蜷缩', '沉闷', '清晰', '凹陷', '硬滑', '好瓜'],
     ['浅白', '蜷缩', '浊响', '清晰', '凹陷', '硬滑', '好瓜'],
     ['青绿', '稍蜷', '浊响', '清晰', '稍凹', '软粘', '好瓜'],
     ['乌黑', '稍蜷', '浊响', '稍糊', '稍凹', '软粘', '好瓜'],
     ['乌黑', '稍蜷', '浊响', '清晰', '稍凹', '硬滑', '好瓜'],
     ['乌黑', '稍蜷', '沉闷', '稍糊', '稍凹', '硬滑', '坏瓜'],
     ['青绿', '硬挺', '清脆', '清晰', '平坦', '软粘', '坏瓜'],
     ['浅白', '硬挺', '清脆', '模糊', '平坦', '硬滑', '坏瓜'],
     ['浅白', '蜷缩', '浊响', '模糊', '平坦', '软粘', '坏瓜'],
     ['青绿', '稍蜷', '浊响', '稍糊', '凹陷', '硬滑', '坏瓜'],
     ['浅白', '稍蜷', '沉闷', '稍糊', '凹陷', '硬滑', '坏瓜'],
     ['乌黑', '稍蜷', '浊响', '清晰', '稍凹', '软粘', '坏瓜'],
     ['浅白', '蜷缩', '浊响', '模糊', '平坦', '硬滑', '坏瓜'],
     ['青绿', '蜷缩', '沉闷', '稍糊', '稍凹', '硬滑', '坏瓜']]

#定义属性集,属性集每个属性的顺序需要与D中取值对应
A = ['色泽', '根蒂', '敲声', '纹理', '脐部', '触感']
random.shuffle(D)  #打乱数据集
train = D[0:10]  #训练集取前10个样本
test = D[10:]  #验证集取剩余样本
Dec = DecisionTree(AValue=dict(), Attribute=A)
Dec.AValue = Dec.CalAValue(D, A)  #计算数据集D中每个属性可以取到什么值
root = Dec.TreeGenerate(train, A)  #使用训练数据训练决策树
print('测试集精度:', Dec.Evaluate(root, A, test))
Path = []
Dec.Dfs(root, '', Path)
print('决策树通向每个叶结点的判断路径如下')
edge = ''
for _ in range(max(len(p) for p in Path)*2): edge += '*'
print(edge)
for i in range(len(Path)): print('路径%d:' % (i), Path[i])
print(edge)

  代码运行结果如图4-4所示。由于训练集和测试集是随机划分的,并且样本数量很少,因此代码运行多次,可能得到的结果相差很大。若使用 D D D中全部17个样本训练,则可得到如上面图4-1所示的决策树。图4-4中每条路径对应了决策树由根结点通往每个叶结点的判定序列。

在这里插入图片描述

图4-4 代码运行结果

4.3 剪枝处理

  剪枝是决策树学习算法对付“过拟合”的主要手段。决策树剪枝的基本策略有“预剪枝”和“后剪枝”。

4.3.1 预剪枝

  预剪枝是指在决策树生成过程中,对每个结点在划分前先进行估计,若当前结点的划分不能带来决策树泛化性能提升,则停止划分并将当前结点标记为叶结点。
  简单来说,就是在每次对当前结点划分前,计算划分前验证集的精度 a 1 a_1 a1(也可以是其他评价指标),再试探性地对当前结点进行划分,计算试探性划分后验证集精度 a 2 a_2 a2。若 a 1 < a 2 a_1<a_2 a1<a2,则进行这次划分;若 a 1 > a 2 a_1>a_2 a1>a2,则不进行此次划分,此试探性划分作废,将当前节点标记为叶结点;若 a 1 = a 2 a_1=a_2 a1=a2,则划分与不划分两者均可,一般不进行划分,因为这样做可降低生成的决策树的规模,减少预测新样本时的时间开销。划分步骤属性的选择和叶结点类别标记设置的策略与上面讲到的图4-2所示的决策树学习算法一致。
  图4-5所示决策树基于表4-3数据集生成的未剪枝决策树,图4-6所示决策树基于表4-3生成的预剪枝决策树。

表4-3 西瓜数据集划分出的训练集(双线上部)与验证集(双线下部)

在这里插入图片描述
在这里插入图片描述

图4-5 基于表4-3生成的未剪枝决策树

在这里插入图片描述

图4-6 基于表4-3生成的预剪枝决策树

4.3.2 后剪枝

  后剪枝先从训练集生成一棵完整的决策树,然后自底向上地对非叶结点进行考察,若将该结点对应的子树替换为叶结点能够带来决策树泛化性能提升,则将该子树替换为叶结点,叶结点类别标记取当前考察非叶结点包含样本数最多的类别,若有多个类别满足条件,则可从其中任取一个。
  简单来说,就是自底向上依次考察每个非叶结点,计算当前决策树下验证集的精度 a 1 a_1 a1(也可以是其他评价指标),再试探性地把当前结点修改为叶结点,计算试探性修改后整棵决策树验证集精度 a 2 a_2 a2。若 a 1 < a 2 a_1<a_2 a1<a2,则保存这次试探性修改;若 a 1 > a 2 a_1>a_2 a1>a2,则撤销此次试探性修改;若 a 1 = a 2 a_1=a_2 a1=a2,则两者均可,一般保存修改,因为这样做可降低生成的决策树的规模,减少预测新样本时的时间开销,此种情形下验证集精度虽无提高,但根据奥卡姆剃刀准则,剪枝后的模型更好。

在这里插入图片描述

图4-7 基于表4-3生成的后剪枝决策树

4.3.3 小结

  预剪枝可以在生成决策树时裁剪掉众多分支,这不仅降低了过拟合风险,还能显著减少决策树训练时间和测试时间开销。但有些分支的当前划分虽不能提升泛化性能、甚至导致泛化性能下降,但在其基础上进行的后续划分却有可能导致性能显著提高。预剪枝基于“贪心”本质禁止某些分支展开,容易造成欠拟合
  后剪枝 通常比预剪枝保留了更多的分支,后剪枝决策树欠拟合风险很小,泛化性能往往优于预剪枝。但后剪枝在训练决策树时开销远大于预剪枝。一般情况下,如果条件允许的话,还是建议使用后剪枝

4.4 连续与缺失值

4.4.1 连续值处理

  由于连续属性的可取值数目不再有限,因此,不能直接根据连续属性的可取值来对结点进行划分。此时,需要使用连续属性离散化技术,最简单的策略是采用二分法。
  给定样本集 D D D和连续属性 a a a,假定 a a a D D D上出现了 n n n个不同的取值,将这些值从小到大进行排序,记为 { a 1 , a 2 , … , a n } \{a^1,a^2,…,a^n \} {a1,a2,,an}。基于划分点 t t t可将 D D D分为子集 D t − D_t^− Dt D t + D_t^+ Dt+,其中 D t − D_t^− Dt包含那些在属性 a a a上取值不大于 t t t的样本,而 D t + D_t^+ Dt+则包含那些在属性 a a a上取值大于 t t t的样本。
  对连续属性 a a a,可考察包含 n − 1 n-1 n1个元素的候选划分点集合
T a = { a i + a i + 1 2 ∣ 1 ≤ i ≤ n − 1 } \begin{aligned} T_a=\{\frac{a^i+a^{i+1}}{2} |1 \le i \le n-1\} \end{aligned} Ta={2ai+ai+11in1}
即把区间 [ a i , a i + 1 ) [a^i,a^{i+1}) [ai,ai+1)的中位点 a i + a i + 1 2 \frac{a^i+a^{i+1}}{2} 2ai+ai+1作为候选划分点,然后使用每个中位点 t t t a a a的属性值划分为两个集合,并计算此种划分后的信息增益 G a i n ( D , a , t ) Gain(D,a,t) Gain(D,a,t),从中选择最大信息增益所对应的划分点对属性 a a a进行划分,生成当前结点的子结点。
G a i n ( D , a ) = max ⁡ t ∈ T a G a i n ( D , a , t ) = max ⁡ t ∈ T a E n t ( D ) − ∑ λ ∈ { − , + } ∣ D t λ ∣ ∣ D ∣ E n t ( D t λ ) \begin{aligned} Gain(D,a)=\max_{t\in T_a}Gain(D,a,t)=\max_{t\in T_a}Ent(D)-\sum_{\lambda \in \{-,+\}}\frac{|D_t^\lambda|}{|D|}Ent(D_t^\lambda) \end{aligned} Gain(D,a)=tTamaxGain(D,a,t)=tTamaxEnt(D)λ{,+}DDtλEnt(Dtλ)
其中 G a i n ( D , a , t ) Gain(D,a,t) Gain(D,a,t)是数据集 D D D基于划分点 t t t二分后的信息增益,选择是 G a i n ( D , a , t ) Gain(D,a,t) Gain(D,a,t)最大化的划分点。
  例如,对于一组数据共 6 6 6个样本,某一个属性 a a a取值有 6 6 6种,分别为 { 1 , 2 , 3 , 4 , 5 , 6 } \{1,2,3,4,5,6\} {1,2,3,4,5,6},其候选划分点集合为 T a = { 1.5 , 2.5 , 3.5 , 4.5 , 5.5 } T_a=\{1.5, 2.5, 3.5, 4.5, 5.5\} Ta={1.5,2.5,3.5,4.5,5.5},依次使用每个候选划分点将 6 6 6个样本分为两部分,划分后其中一部分的所有样本的属性 a a a的值均小于等于使用的划分点,另一部分大于划分点。
  假设取划分点为 2.5 2.5 2.5,即 t = 2.5 t=2.5 t=2.5,则将得到两个划分集合 D t − = { 1 , 2 } D_t^−=\{1,2\} Dt={1,2} D t + = { 3 , 4 , 5 , 6 } D_t^+=\{3,4,5,6\} Dt+={3,4,5,6},进而计算出 G a i n ( D , a , t = 2.5 ) Gain(D,a,t=2.5) Gain(D,a,t=2.5)。依次考虑 T a T_a Ta中每个元素,假设使用 3.5 3.5 3.5划分后 G a i n ( D , a , t ) Gain(D,a,t) Gain(D,a,t)最大,则当前节点的判定条件为“ a ≤ 3.5 a \le 3.5 a3.5”,当前节点生成两个子结点,并将 D t − D_t^- Dt D t + D_t^+ Dt+集合所对应的样本分别放入这两个子结点中。
  注意,与离散属性不同,若当前结点划分属性为连续属性,该属性还可作为其后代结点的划分属性。

4.4.2 缺失值处理

  现实任务中常会遇到不完整样本,即样本的某些属性值缺失。对于此类情况,一般在数据预处理步骤使用特征工程相关方法予以解决。但本小结主要介绍在决策树学习算法中如何应对这样的问题。
  面对缺失数据我们需要解决两个问题:
  (1)如何在属性值缺失的情况下进行划分属性选择,也就是如何计算每个属性 a a a的信息增益 G a i n ( D , a ) Gain(D,a) Gain(D,a)
  (2)给定划分属性,若样本在该属性上的值缺失,如何对样本进行划分。例如:西瓜的色泽属性有“乌黑”、“青绿”、“浅白”三种,根据每个样本的色泽属性取值将样本分成三类,但若存在某个样本 x x x x x x的色泽属性值缺失,那么样本 x x x该分到哪一类?
  给定数据集 D D D和属性 a a a,令 D ~ \tilde{D} D~表示 D D D中在属性 a a a上没有缺失值的样本子集。对问题(1),可根据 D ~ \tilde{D} D~来判断属性 a a a的优劣。假定属性 a a a V V V个可取值 { a 1 , a 2 , . . . , a V } \{a^1,a^2,...,a^V\} {a1,a2,...,aV},令 D v ~ \tilde{D^v} Dv~表示 D ~ \tilde{D} D~中在属性 a a a上取值为 a v a^v av的样本子集, D k ~ \tilde{D_k} Dk~表示 D ~ \tilde{D} D~中属于第 k k k ( k = 1 , 2 , . . . , ∣ y ∣ ) (k=1,2,...,|y|) (k=1,2,...,y)的样本子集,则显然有 D ~ = ∪ k = 1 ∣ y ∣ D k ~ \tilde{D}=\cup _{k=1}^{|y|}\tilde{D_k} D~=k=1yDk~ D ~ = ∪ v = 1 V D v ~ \tilde{D}=\cup _{v=1}^{V}\tilde{D^v} D~=v=1VDv~。假定为每个样本 x x x赋予一个权重 w x w_x wx,并定义
ρ = ∑ x ∈ D ~ w x ∑ x ∈ D w x \begin{aligned} \rho=\frac{\sum_{x\in \tilde{D}}w_x}{\sum_{x \in D}w_x} \end{aligned} ρ=xDwxxD~wx
p k ~ = ∑ x ∈ D k ~ w x ∑ x ∈ D ~ w x       ( 1 ≤ k ≤ ∣ y ∣ ) \begin{aligned} \tilde{p_k}=\frac{\sum_{x\in \tilde{D_k}}w_x}{\sum_{x \in \tilde{D}}w_x} \ \ \ \ \ (1 \le k \le |y|) \end{aligned} pk~=xD~wxxDk~wx     (1ky)
r v ~ = ∑ x ∈ D v ~ w x ∑ x ∈ D ~ w x       ( 1 ≤ v ≤ V ) \begin{aligned} \tilde{r_v}=\frac{\sum_{x\in \tilde{D^v}}w_x}{\sum_{x \in \tilde{D}}w_x} \ \ \ \ \ (1 \le v \le V) \end{aligned} rv~=xD~wxxDv~wx     (1vV)
  对属性 a a a ρ \rho ρ表示无缺失值样本所占的比例, p k ~ \tilde{p_k} pk~表示无缺失值样本中第 k k k类所占的比例, r v ~ \tilde{r_v} rv~表示无缺失值样本中在属性 a a a上取值 a v a^v av的样本所占的比例。推广信息增益公式为
G a i n ( D , a ) = ρ ∗ G a i n ( D ~ , a ) = ρ ∗ ( E n t ( D ~ ) − ∑ v = 1 V r v ~ E n t ( D v ~ ) ) \begin{aligned} Gain(D,a)=\rho*Gain(\tilde{D},a)=\rho*(Ent(\tilde{D})-\sum _{v=1} ^{V}\tilde{r_v}Ent(\tilde{D^v})) \end{aligned} Gain(D,a)=ρGain(D~,a)=ρ(Ent(D~)v=1Vrv~Ent(Dv~))
其中,
E n t ( D ~ ) = − ∑ k = 1 ∣ y ∣ p k ~ l o g 2 p k ~ \begin{aligned} Ent(\tilde{D})=-\sum _{k=1} ^{|y|}\tilde{p_k}log_2 {\tilde{p_k}} \end{aligned} Ent(D~)=k=1ypk~log2pk~
  对于问题(2),若样本 x x x在划分属性 a a a上的取值已知,则将 x x x划入与其取值对应的子结点,且样本权值在子结点中保持为 w x w_x wx。若样本 x x x在划分属性 a a a上的取值未知,则将 x x x同时划入所有子结点,且样本权值在与属性 a v a^v av对应的子结点中调整为 r v ~ ∗ w x \tilde{r_v}*w_x rv~wx;根结点中的样本权值全部初始化为 1 1 1。直观感受,这是让同一个样本以不同概率划入到不同的子结点中。
  例如,在如表4-4所示的“西瓜数据集——缺失部分数据”中,假设对“纹理”进行根结点划分。划分结果是使编号 { 1 , 2 , 3 , 4 , 5 , 6 , 15 } \{1,2,3,4,5,6,15\} {1,2,3,4,5,6,15}的样本进行“纹理=清晰”分支,编号 { 7 , 9 , 13 , 14 , 17 } \{7,9,13,14,17\} {7,9,13,14,17}的样本进入“纹理=稍糊”分支,编号 { 11 , 12 , 16 } \{11,12,16\} {11,12,16}的样本进行“纹理=模糊”分支,且样本在各子结点中权值保持为 1 1 1。对于样本 8 8 8,将该样本放入全部三个子结点中,并分别将权值调整为 1 ∗ 7 15 1*\frac{7}{15} 1157 1 ∗ 5 15 1*\frac{5}{15} 1155 1 ∗ 3 15 1*\frac{3}{15} 1153。注意,在对子结点进行划分时,子结点中样本 8 8 8的权值不再是 1 1 1

表4-4 西瓜数据集——缺失部分数据

在这里插入图片描述

4.5 多变量决策树

  以上所叙述的决策树都是单变量决策树,即每个非叶结点都是对单个属性的判断。而除了单变量决策树之外,还有多变量决策树,即每个非叶结点是对多个属性组合的判断。这里多个属性组合的方式可以是简单的线性组合,也可以是复杂组合,如每个非叶结点设置一个神经网络对多个属性的复杂组合进行判断。
  图4-8展示了一棵采用线性组合方式的多变量决策树。

在这里插入图片描述

图4-8 多变量决策树

  我基于sklearn的线性回归模型实现了多变量决策树,详细内容可以关注我的另一篇博客

4.6 sklearn机器学习库——决策树

  下面代码基于sklearn机器学习库实现了乳腺癌数据分类,sklearn中还包含了可用于回归任务的决策树。sklearn包含了众多经典的机器学习算法,并做了很多相关的优化工作。建议在实际工程中使用,但由于方法库屏蔽掉了很多算法细节,因此在学习阶段不建议过于依赖方法库。sklearn中文文档

# -*- coding: utf-8 -*-
"""
Created on Tue Nov 24 14:59:04 2020

@author: qiqi
"""

import pydot
from sklearn.tree import export_graphviz
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import train_test_split
from sklearn.datasets import load_breast_cancer

cancer = load_breast_cancer()  #加载乳腺癌数据集

#参数random_state是指随机生成器
X_train, X_test, y_train, y_test = train_test_split(cancer['data'],cancer['target'], random_state=42)

#设置深度为4,即产生4个问题就停止生长.这是一种预剪枝手段,防止过拟合。
tree = DecisionTreeClassifier(max_depth=4, random_state=0)

#训练决策树
tree.fit(X_train,y_train)
print('Train score:{:.3f}'.format(tree.score(X_train,y_train)))
print('Test score:{:.3f}'.format(tree.score(X_test,y_test)))

#生成可视化图,Windows下class_names使用中文会乱码
export_graphviz(tree, out_file='tree.dot',class_names=['serious','slight'],feature_names=cancer.feature_names,impurity=False,filled=True)
#展示可视化图
(graph,) = pydot.graph_from_dot_file('tree.dot')
graph.write_png('tree.png')

  上面代码使用了预剪枝策略(代码确实简短),生成的决策树训练集精度为 0.99 0.99 0.99,测试集精度为 0.92 0.92 0.92。图4-9是使用sklearn库方法绘制的决策树结构图,sklearn确实好用。

在这里插入图片描述

图4-9 决策树结构图

博客参考周志华教授《机器学习》(西瓜书)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

奇齐

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值