学习笔记【1】:决策树

文章目录

  • 前言
  • 1. 基本概念
    • 1.1. 决策树概述
    • 1.2. 决策树和归纳算法
    • 1.3. 决策树生成过程
  • 2. 构建决策树
    • 2.1. 特征选择
      • 2.1.1. 熵
      • 2.1.2. 信息增益
      • 2.1.3. 信息增益比
      • 2.1.4. 基尼指数
    • 2.2. 决策树生成
      • 2.2.1. CLS 算法
      • 2.2.2. ID3算法
      • 2.2.3. C4.5 算法
      • 2.2.4. CART 算法
        • 2.2.4.1. 回归树
        • 2.2.4.2. 分类树
    • 2.3. 决策树剪枝
      • 2.3.1. 算法
      • 2.3.2. 示例
  • 3. 决策树可视化
    • 3.1. 创建决策树
    • 3.2. 在 Scikit-Learn 中使用决策树拟合数据
  • 总结


前言

决策树是一种解决分类问题的算法

在分类问题中,表示基于特征对实例进行分类的过程,可以认为是 if-else-then 的集合,也可以认为是定义在特征空间与类空间上的条件概率分布

本文基于北京邮电大学周芮西老师开设的公选课——统计机器学习及应用实践


1. 基本概念

1.1. 决策树概述

决策树是典型的分类方法

  • 首先对数据进行处理,利用归纳算法生成可读的规则和决策树
  • 然后使用决策对新数据进行分析

相关概念

  • 决策树学习的目标
    • 根据给定的训练数据集构建一个决策树模型,使它能够对实例进行正确的分类
    • 在损失函数的有意义的前提下,选择最优决策树的问题
  • 决策树学习的本质:从训练集中归纳出一组分类规则,或者说是由训练数据集估计条件概率模型
  • 决策树学习的损失函数:正则化的极大似然函数
  • 决策树学习的测试:最小化损失函数

决策树的特点

  • 优点
    • 推理过程容易理解,决策推理过程可以表示成 if-else-then 形式
    • 推理过程完全依赖于属性变量的取值特点
    • 可自动忽略目标变量没有贡献的属性变量
  • 缺点
    • 可能会产生过度匹配的问题

1.2. 决策树和归纳算法

决策树技术发现数据模式和规则的核心是归纳算法(Inductive algorithm)

归纳(归纳推理)

  • 从若干个事实中表征出的特征、特性和属性中,通过比较、总结、概括而得出一个规律性的结论
  • 试图从对象的一部分或整体的特定观察中获得一个完备且正确的描述
  • 从特殊事实到普遍性规律的结论

归纳学习由于依赖于检验数据,因此又称为检验学习

  • 基本的假设:
    • 任一假设如果能够在足够大的训练样本集中很好的逼近目标函数,则它也能在 未见样本中很好地逼近目标函数

1.3. 决策树生成过程

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

  • 构建根节点,将所有训练数据都放在根节点,选择一个最优特征,按着这一特征将训练数据集分割成子集,使得各个子集有一个在当前条件下最好的分类
  • 如果这些子集已经能够被基本正确分类,那么构建叶节点,并将这些子集分到所对应的叶节点去
  • 如果还有子集不能够被正确的分类,那么就对这些子集选择新的最优特征,继续对其进行分割,构建相应的节点,如果递归进行,直至所有训练数据子集被基本正确的分类,或者没有合适的特征为止
  • 每个子集都被分到叶节点上,即都有了明确的类,这样就生成了一颗决策树

下图为决策树示意图

  • 圆点——内部节点
  • 方框——叶节点(结果,不可再分)

学习笔记【1】:决策树_第1张图片

显然,决策树的生成是一个递归过程。在决策树基本算法中,有三种情形会导致递归返回:

  • 当前结点包含的样本全属于同一类别,无需划分
  • 当前属性集为空,或是所有样本在所有属性上取值相同,无法划分
    • 把当前结点标记为叶结点,并将其类别设定为该结点所含样本最多的类别
  • 当前结点包含的样本集合为空,不能划分
    • 把当前结点标记为叶结点,并将其类别设定为其父结点所含样本最多的类别

2. 构建决策树

2.1. 特征选择

一般而言,随着划分过程不断进行,我们希望决策树的分支结点所包含的样本尽可能属于同一类别,即结点的“纯度”越来越高

划分步骤:

  • 确定当前数据集上的决定性特征,为了得到该决定性特征,必须评估每个特征,完成测试之后,原始数据集就被划分为几个数据子集
  • 这些数据子集会分布在第一个决策点的所有分支上
    • 如果某个分支下的数据属于同一类型,则当前已经正确的划分数据分类,无需进一步对数据集进行分割
    • 如果不属于同一类,则要重复划分数据子集,直到所有相同类型的数据均在一个数据子集内

2.1.1. 熵

信息熵是信息量大小的度量,即表示随机变量不确定性的度量

  • 一般来说,小概率事件包含的信息量比大概率事件大
  • 变量的不确定性越大,熵越高,需要的信息量越大,纯度越低

信息熵的两种解释:

  • 通俗解释
    • 事件 a i a_i ai 的信息量 I ( a i ) I(a_i) I(ai) 可度量为:
      I ( a i ) = p ( a i ) l o g 2 1 p ( a i ) I(a_i) = p(a_i) log_2 \frac{1}{p(a_i)} I(ai)=p(ai)log2p(ai)1
      • 其中 p ( a i ) p(a_i) p(ai) 表示事件 a i a_i ai 发生的概率
    • 假设有 n 个互斥的事件 a 1 , a 2 , a 3 , ⋯   , a n a_1, a_2, a_3, \cdots, a_n a1,a2,a3,,an,它们中有且仅有一个发生,则其平均的信息量(熵)可度量为
      I ( a 1 , a 2 , a 3 , ⋯   , a n ) = ∑ i = 1 n I ( a i ) = ∑ i = 1 n p ( a i ) l o g 2 1 p ( a i ) I(a_1, a_2, a_3, \cdots, a_n) = \displaystyle\sum_{i=1}^n I(a_i) = \displaystyle\sum_{i=1}^n p(a_i) log_2 \frac{1}{p(a_i)} I(a1,a2,a3,,an)=i=1nI(ai)=i=1np(ai)log2p(ai)1
  • 理论解释
    • 随机变量 的熵定义为:
      H ( x ) = ∑ x p ( X = x ) l o g 2 1 p ( X = x ) = − ∑ x p ( X = x ) l o g 2 ( p ( X = x ) ) H(x) = \displaystyle\sum_x p(X=x)log_2\frac{1}{p(X=x)} = - \displaystyle\sum_x p(X=x)log_2(p(X=x)) H(x)=xp(X=x)log2p(X=x)1=xp(X=x)log2(p(X=x))
      • 对数以 2 为底(或以自然对数 e 为底),这时熵的单位分别称作比特 bit 或纳特 nat
    • 熵只依赖于 的分布,与 的取值无关
      H ( x ) → H ( p ) H ( p ) = − ∑ i = 1 n p i l o g 2 p i H(x) \rightarrow H(p) \\ H(p) = - \displaystyle\sum_{i=1}^n p_i log_2 p_i H(x)H(p)H(p)=i=1npilog2pi
      • 0 ≤ H ( p ) ≤ l o g 2 n 0 \le H(p) \le log_2n 0H(p)log2n
    • 当随机变量 x 为 ( 0 , 1 ) (0, 1) (0,1) 分布时:
      P ( X = 1 ) = p ,   P ( X = 0 ) = 1 − p ,   0 ≤ p ≤ 1 P(X = 1) = p, \ P(X = 0) = 1 - p, \ 0 \le p \le 1 P(X=1)=p, P(X=0)=1p, 0p1
      • 此时,熵的表达式为:
        H ( p ) = − p l o g 2 p − ( 1 − p ) l o g 2 ( 1 − p ) H(p) = -p log_2p - (1-p)log_2(1-p) H(p)=plog2p(1p)log2(1p)
        学习笔记【1】:决策树_第2张图片

设有随机变量 ( X , Y ) (X, Y) (X,Y),其联合概率分布为:
P ( X = x i , Y = y i ) = p i j , i = 1 , 2 , ⋯   , n ;   j = 1 , 2 , ⋯   , m P(X = x_i, Y = y_i) = p_{ij}, i = 1,2,\cdots,n; \ j=1,2,\cdots,m P(X=xi,Y=yi)=pij,i=1,2,,n; j=1,2,,m

条件熵表示在己知随机变量 X 的条件下随机变量 Y 的不确定性

  • 定义为 X 给定条件下 Y 的条件概率分布的熵对 X 的数学期望
    H ( Y ∣ X ) = ∑ i = 1 n p i H ( Y ∣ X = x i ) H(Y|X) = \displaystyle\sum_{i=1}^n p_i H(Y|X = x_i) H(YX)=i=1npiH(YX=xi)
    • 其中 p i = P ( X = x i ) , i = 1 , 2 , ⋯   , n p_i = P(X = x_i), i = 1,2,\cdots,n pi=P(X=xi),i=1,2,,n

2.1.2. 信息增益

特征 A 对训练集 D信息增益 g(D, A)

  • 定义为集合 D 的经验熵 H(D) 与特征 A 给定条件下 D 的经验条件熵 H ( D ∣ A ) H(D|A) H(DA) 之差
    g ( D , A ) = H ( D ) − H ( D ∣ A ) g(D, A) = H(D) - H(D|A) g(D,A)=H(D)H(DA)
  • 表示得知特征 X 的信息而使得类 Y 的信息的不确定性减少的程度
  • 可以作为决策树的划分属性选择

互信息:熵 H(Y) 与条件熵 H ( D ∣ A ) H(D|A) H(DA)) 之差

决策树学习中的信息增益 ≈ \approx 训练数据集中类与特征的互信息

表达式总结为:

  • 熵(Entropy): H ( X ) = ∑ x p ( X = x ) l o g 2 1 p ( x = x ) H(X) = \sum_x p(X = x) log_2 \frac{1}{p(x=x)} H(X)=xp(X=x)log2p(x=x)1
    • 熵测量的是编码一个从 X 中随机抽取所预期的比特数
    • 对于决策树,我们希望减少试图预测的随机变量的熵
  • 条件熵(Conditional Entropy): H ( Y ∣ X ) = ∑ x p ( X = x ) H ( Y ∣ X = x ) H(Y|X) = \sum_x p(X = x) H(Y|X = x) H(YX)=xp(X=x)H(YX=x)
    • 条件熵是特定条件熵的期望值
  • 互信息(Mutual Information): I ( Y ; X ) = H ( Y ) − H ( Y ∣ X ) I(Y; X) = H(Y) - H(Y|X) I(Y;X)=H(Y)H(YX)
    • 互信息是对减少的不确定性的度量

对于决策树,我们可以使用输出类 Y 和要拆分的某个属性 X 的互信息作为拆分标准

给定训练样本数据集 D,我们可以将所需的概率估计为:
P ( Y = y ) = N Y = y / N P ( X = x ) = N X = x / N P ( Y = y ∣ X = x ) = N Y = y , X = x / N X = x P(Y=y) = N_{Y=y} / N \\ P(X = x) = N_{X=x} / N \\ P(Y = y | X = x) = N_{Y=y, X=x} / N_{X=x} P(Y=y)=NY=y/NP(X=x)=NX=x/NP(Y=yX=x)=NY=y,X=x/NX=x
其中 N Y = y N_{Y=y} NY=y Y = y Y=y Y=y 的例子的数量

信息增益的算法:

  • 数据
    • 输入:训练数据集 D 和特征 A
    • 输出:特征 A 对训练数据集 D 的信息增益 g ( D , A ) g(D,A) g(D,A)
  • 计算数据集 D 的经验熵 H(D)
    H ( D ) = − ∑ k = 1 K ∣ C k ∣ ∣ D ∣ l o g 2 ∣ C k ∣ ∣ D ∣ H(D) = -\displaystyle\sum_{k=1}^K \frac{|C_k|}{|D|} log_2 \frac{|C_k|}{|D|} H(D)=k=1KDCklog2DCk
    • C k C_k Ck 即数据集 D 中取值为 C k C_k Ck 的样本的个数
  • 计算特征 A 对数据集 D 的经验条件熵 H ( D ∣ A ) H(D|A) H(DA)
    H ( D ∣ A ) = ∑ i = 1 n ∣ D i ∣ ∣ D ∣ H ( D i ) = − ∑ i = 1 n ∣ D i ∣ ∣ D ∣ ∑ k = 1 K ∣ D i k ∣ ∣ D ∣ l o g 2 ∣ D i k ∣ ∣ D ∣ H(D|A) = \displaystyle\sum_{i=1}^n \frac{|D_i|}{|D|}H(D_i) = - \displaystyle\sum_{i=1}^n \frac{|D_i|}{|D|}\displaystyle\sum_{k=1}^K \frac{|D_{ik}|}{|D|} log_2 \frac{|D_{ik}|}{|D|} H(DA)=i=1nDDiH(Di)=i=1nDDik=1KDDiklog2DDik
  • 计算信息增益 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)
    • 信息增益值最大的特征 → \rightarrow 最优特征

2.1.3. 信息增益比

以信息增益作为划分训练数据集的特征,存在偏向于选择取值较多的特征的问题:
g ( D , A ) = H ( D ) − H ( D ∣ A ) g(D, A) = H(D) - H(D|A) g(D,A)=H(D)H(DA)

信息增益比即特征 A 对训练数据集 D 的信息增益比,定义为信息增益与训练数据集 D 关于特征 A 的值的熵之比:
g R ( D , A ) = g ( D , A ) H A ( D ) H A ( D ) = − ∑ i = 1 3 ∣ D i ∣ ∣ D ∣ l o g 2 ∣ D i ∣ ∣ D ∣ g_R(D, A) = \frac{g(D, A)}{H_A(D)} \\ H_A(D) = - \displaystyle\sum_{i=1}^3\frac{|D_i|}{|D|}log_2\frac{|D_i|}{|D|} gR(D,A)=HA(D)g(D,A)HA(D)=i=13DDilog2DDi

n 是特征 A 取值的个数

2.1.4. 基尼指数

数据集 D 的纯度可以用基尼值来度量

假设有 K 个类,样本点属于第 k 类的概率为 p k p_k pk ,则:

G i n i ( p ) = ∑ k = 1 K p k ( 1 − p k ) = 1 − ∑ k = 1 K p k 2 Gini(p) = \displaystyle\sum_{k=1}^K p_k(1-p_k) = 1 - \displaystyle\sum_{k=1}^K p_k^2 Gini(p)=k=1Kpk(1pk)=1k=1Kpk2

转换成二分类问题:

G i n i ( p ) = 2 p ( 1 − p ) Gini(p) = 2p(1 − p) Gini(p)=2p(1p)

对于样本集合 D

G i n i ( D ) = 1 − ∑ k = 1 K ∣ C k D ∣ 2 Gini(D) = 1 - \displaystyle\sum_{k=1}^K |\frac{C_k}{D}|^2 Gini(D)=1k=1KDCk2

样本集 D 根据特征 A 是否取某一值 a 被分割成 D 1 D_1 D1 D 2 D_2 D2,即:

D 1 = { ( x , y ) ∈ D ∣ A ( x ) = a } D 2 = D − D 1 D_1 = \{ (x, y) \in D | A(x) = a \} \\ D_2 = D - D_1 D1={(x,y)DA(x)=a}D2=DD1

在特征 A 的条件下,集合 D 的基尼指数为:

G i n i ( D , A ) = D 1 D G i n i ( D 1 ) + D 2 D G i n i ( D 2 ) Gini(D, A) = \frac{D_1}{D} Gini(D_1) + \frac{D_2}{D} Gini(D_2) Gini(D,A)=DD1Gini(D1)+DD2Gini(D2)

即经 A = a A = a A=a 划分后集合 D 的不确定性

2.2. 决策树生成

2.2.1. CLS 算法

基本思想:

  • 从一棵空决策树开始,选择一个属性,根据该属性的不同取值将训练样 本分成相应的子集:
    • 如果该子集为空或属于同一个类,则该子集为叶结点,分类结束
    • 否则该子集对应于决策树的内部结点(测试结点),需要选择一个新的分类属性对该子集进行划分,直到所有的子集都为空或属于同一个类

算法步骤:

  1. 生成空决策树与样本属性集合(全局变量)
  2. 如果样本集合 T 中所有元素属于同一类,生成结点 T,并终止算法
  3. 相反,随机选取一个属性,并生成结点(记作 A)
  4. 根据该属性的不同取值,把 T 划分为不同子集
  5. 删除该属性 A
  6. 转到第 2 步,对每个子集递归调用 CLS 算法的 2-6 步

存在的问题:

  • 随机选取属性,而不同属性的分类效果差异巨大
  • 采用不同的测试属性及其先后顺序将会生成不同的决策树

2.2.2. ID3算法

核心思想:

  • 在决策树各个结点上应用信息增益准则选择特征,递归地构建决策树

算法步骤:

  • 从根节点开始,对结点计算所有可能的特征的信息增益,选择信息增益最大的
    特征作为结点的特征,由该特征的不同取值建子结点
  • 再对子结点递归地调用以上方法,构建决策树
  • 指导所有特征的信息增益均很小或没有特征可以选择为止
  • 最后得到一棵决策树

示例一:

该数据集包含 17 个训练样例,用一学习一棵能预测没剖开的是不是好瓜的决策树

学习笔记【1】:决策树_第3张图片

在决策树学习开始时,根结点包含 D 中的所有样例,其中正例占 p 1 = 8 17 p_1 = \frac{8}{17} p1=178,反例占 p 2 = 9 17 p_2 = \frac{9}{17} p2=179

计算根结点的信息熵
H ( D ) = − ∑ i = 1 2 p i l o g 2 p i = − ( 8 17 l o g 2 8 17 + 9 17 l o g 2 9 17 ) = 0.998 H(D) = - \displaystyle\sum_{i=1}^2 p_i log_2 p_i = - (\frac{8}{17} log_2 \frac{8}{17} + \frac{9}{17} log_2 \frac{9}{17}) = 0.998 H(D)=i=12pilog2pi=(178log2178+179log2179)=0.998

然后 ,我们要计算出当前属性集合 {色泽,根蒂,敲声,纹理,脐部,触感} 中每个属性的信息增益。以属性“色泽”为例,它有 3 个可能的取值:{青绿,乌黑,浅白}。若使用该属性对 D 进行划分,则可得到 3 个子集,分别记为 D 1 D^1 D1(色泽 = 青绿), D 2 D^2 D2(色泽 = 乌黑), D 3 D^3 D3(色泽 = 浅白)

用“色泽”划分之后所获得的 3 个分支结点的信息嫡为:
H ( D 1 ) = − ( 3 6 l o g 2 3 6 + 3 6 l o g 2 3 6 ) = 1.000 H(D^1) = - (\frac{3}{6} log_2 \frac{3}{6} + \frac{3}{6} log_2 \frac{3}{6}) = 1.000 H(D1)=(63log263+63log263)=1.000
H ( D 2 ) = − ( 4 6 l o g 2 4 6 + 2 6 l o g 2 2 6 ) = 0.918 H(D^2) = - (\frac{4}{6} log_2 \frac{4}{6} + \frac{2}{6} log_2 \frac{2}{6}) = 0.918 H(D2)=(64log264+62log262)=0.918
H ( D 3 ) = − ( 1 5 l o g 2 1 5 + 4 5 l o g 2 4 5 ) = 0.722 H(D^3) = - (\frac{1}{5} log_2 \frac{1}{5} + \frac{4}{5} log_2 \frac{4}{5}) = 0.722 H(D3)=(51log251+54log254)=0.722

属性“色泽 ”的信息增益为:
g ( D , 色泽 ) = H ( D ) − ∑ i = 1 3 ∣ D i ∣ ∣ D ∣ H ( D i ) = 0.998 − ( 6 17 × 1.000 + 7 16 × 0.918 + 5 17 × 0.722 ) = 0.109 g(D, 色泽) = H(D) - \displaystyle\sum_{i=1}^3\frac{|D_i|}{|D|}H(D_i) \\ =0.998 - (\frac{6}{17} \times 1.000 + \frac{7}{16} \times 0.918 + \frac{5}{17} \times 0.722) = 0.109 g(D,色泽)=H(D)i=13DDiH(Di)=0.998(176×1.000+167×0.918+175×0.722)=0.109

类似的,我们可计算出其他属性的信息增益:

  • g(D, 根蒂) = 0.143
  • g(D, 敲声) = 0.141
  • g(D, 纹理) = 0.381
  • g(D, 脐部) = 0.289
  • g(D, 触感) = 0.006

显然,属性“纹理”的信息增益最大,于是它被选为划分属性。下图给出了基于“纹理”对根结点进行划分的结果,各分支结点所包含的样例子集显示在结点中:

学习笔记【1】:决策树_第4张图片

然后,决策树学习算法将对每个分支结点做进一步划分(步骤相同,略)

示例二:

学习笔记【1】:决策树_第5张图片

对数据集进行属性标注:

  • 年龄:0 代表青年,1 代表中年,2 代表老年
  • 有工作:0 代表否,1 代表是
  • 有自己的房子:0 代表否,1 代表是
  • 信贷情况:0 代表一般,1 代表好,2 代表非常好
  • 类别(是否给贷款):no 代表否,yes 代表是

创建数据集,计算经验熵的代码如下:

from math import log

"""
函数说明:创建测试数据集
Parameters:无
Returns:
    dataSet:数据集
    labels:分类属性
Modify:
    2022-09-28

"""
def creatDataSet():
    # 数据集
    dataSet=[[0, 0, 0, 0, 'no'],
            [0, 0, 0, 1, 'no'],
            [0, 1, 0, 1, 'yes'],
            [0, 1, 1, 0, 'yes'],
            [0, 0, 0, 0, 'no'],
            [1, 0, 0, 0, 'no'],
            [1, 0, 0, 1, 'no'],
            [1, 1, 1, 1, 'yes'],
            [1, 0, 1, 2, 'yes'],
            [1, 0, 1, 2, 'yes'],
            [2, 0, 1, 2, 'yes'],
            [2, 0, 1, 1, 'yes'],
            [2, 1, 0, 1, 'yes'],
            [2, 1, 0, 2, 'yes'],
            [2, 0, 0, 0, 'no']]
    # 分类属性
    labels=['年龄','有工作','有自己的房子','信贷情况']
    # 返回数据集和分类属性
    return dataSet,labels

"""
函数说明:计算给定数据集的经验熵(香农熵)
Parameters:
    dataSet:数据集
Returns:
    shannonEnt:经验熵
Modify:
    2022-09-28

"""
def calcShannonEnt(dataSet):
    #返回数据集行数
    numEntries=len(dataSet)
    #保存每个标签(label)出现次数的字典
    labelCounts={}
    #对每组特征向量进行统计
    for featVec in dataSet:
        currentLabel=featVec[-1]                     # 提取标签信息
        if currentLabel not in labelCounts.keys():   # 如果标签没有放入统计次数的字典,添加进去
            labelCounts[currentLabel]=0
        labelCounts[currentLabel]+=1                 # label计数

    shannonEnt=0.0                                   # 经验熵
    #计算经验熵
    for key in labelCounts:
        prob=float(labelCounts[key])/numEntries      # 选择该标签的概率
        shannonEnt-=prob*log(prob,2)                 # 利用公式计算
    return shannonEnt                                # 返回经验熵

# main函数
if __name__=='__main__':
    dataSet,features=creatDataSet()
    print(dataSet)
    print(calcShannonEnt(dataSet))
    

对应的输出结果:

第0个特征的增益为0.083
第1个特征的增益为0.324
第2个特征的增益为0.420
第3个特征的增益为0.363
第0个特征的增益为0.252
第1个特征的增益为0.918
第2个特征的增益为0.474
{'有自己的房子': {0: {'有工作': {0: 'no', 1: 'yes'}}, 1: 'yes'}}

创建数据集,计算信息增益的代码如下:

from math import log

"""
函数说明:创建测试数据集
Parameters:无
Returns:
    dataSet:数据集
    labels:分类属性
Modify:
    2022-09-28

"""
def creatDataSet():
    # 数据集
    dataSet=[[0, 0, 0, 0, 'no'],
            [0, 0, 0, 1, 'no'],
            [0, 1, 0, 1, 'yes'],
            [0, 1, 1, 0, 'yes'],
            [0, 0, 0, 0, 'no'],
            [1, 0, 0, 0, 'no'],
            [1, 0, 0, 1, 'no'],
            [1, 1, 1, 1, 'yes'],
            [1, 0, 1, 2, 'yes'],
            [1, 0, 1, 2, 'yes'],
            [2, 0, 1, 2, 'yes'],
            [2, 0, 1, 1, 'yes'],
            [2, 1, 0, 1, 'yes'],
            [2, 1, 0, 2, 'yes'],
            [2, 0, 0, 0, 'no']]
    #分类属性
    labels=['年龄','有工作','有自己的房子','信贷情况']
    #返回数据集和分类属性
    return dataSet,labels


"""
函数说明:计算给定数据集的经验熵(香农熵)
Parameters:
    dataSet:数据集
Returns:
    shannonEnt:经验熵
Modify:
    2022-09-28

"""
def calcShannonEnt(dataSet):
    #返回数据集行数
    numEntries=len(dataSet)
    #保存每个标签(label)出现次数的字典
    labelCounts={}
    #对每组特征向量进行统计
    for featVec in dataSet:
        currentLabel=featVec[-1]                     #提取标签信息
        if currentLabel not in labelCounts.keys():   #如果标签没有放入统计次数的字典,添加进去
            labelCounts[currentLabel]=0
        labelCounts[currentLabel]+=1                 #label计数

    shannonEnt=0.0                                   #经验熵
    #计算经验熵
    for key in labelCounts:
        prob=float(labelCounts[key])/numEntries      #选择该标签的概率
        shannonEnt-=prob*log(prob,2)                 #利用公式计算
    return shannonEnt                                #返回经验熵


"""
函数说明:计算给定数据集的经验熵(香农熵)
Parameters:
    dataSet:数据集
Returns:
    shannonEnt:信息增益最大特征的索引值
Modify:
    2022-09-28

"""


def chooseBestFeatureToSplit(dataSet):
    #特征数量
    numFeatures = len(dataSet[0]) - 1
    #计数数据集的香农熵
    baseEntropy = calcShannonEnt(dataSet)
    #信息增益
    bestInfoGain = 0.0
    #最优特征的索引值
    bestFeature = -1
    #遍历所有特征
    for i in range(numFeatures):
        # 获取dataSet的第i个所有特征
        featList = [example[i] for example in dataSet]
        #创建set集合{},元素不可重复
        uniqueVals = set(featList)
        #经验条件熵
        newEntropy = 0.0
        #计算信息增益
        for value in uniqueVals:
            #subDataSet划分后的子集
            subDataSet = splitDataSet(dataSet, i, value)
            #计算子集的概率
            prob = len(subDataSet) / float(len(dataSet))
            #根据公式计算经验条件熵
            newEntropy += prob * calcShannonEnt((subDataSet))
        #信息增益
        infoGain = baseEntropy - newEntropy
        #打印每个特征的信息增益
        print("第%d个特征的增益为%.3f" % (i, infoGain))
        #计算信息增益
        if (infoGain > bestInfoGain):
            #更新信息增益,找到最大的信息增益
            bestInfoGain = infoGain
            #记录信息增益最大的特征的索引值
            bestFeature = i
            #返回信息增益最大特征的索引值
    return bestFeature

"""
函数说明:按照给定特征划分数据集
Parameters:
    dataSet:待划分的数据集
    axis:划分数据集的特征
    value:需要返回的特征的值
Returns:
    shannonEnt:经验熵
Modify:
    2022-09-28

"""
def splitDataSet(dataSet,axis,value):
    retDataSet=[]
    for featVec in dataSet:
        if featVec[axis]==value:
            reducedFeatVec=featVec[:axis]
            reducedFeatVec.extend(featVec[axis+1:])
            retDataSet.append(reducedFeatVec)
    return retDataSet


#main函数
if __name__=='__main__':
    dataSet,features=creatDataSet()
    # print(dataSet)
    # print(calcShannonEnt(dataSet))
    print("最优索引值:"+str(chooseBestFeatureToSplit(dataSet)))

对应的输出结果:

第0个特征的增益为0.083
第1个特征的增益为0.324
第2个特征的增益为0.420
第3个特征的增益为0.363
最优索引值:2

最优特征的索引值为 2,也就是特征 A3

2.2.3. C4.5 算法

ID3 算法相似,但是做了改进,将信息增益比作为选择特征的标准:

  • 信息增益准则对可取值数目较多的属性有所偏好,信息增益比可以减少这种不利的影响
  • 信息增益比可对取值数目较少的属性有所偏好
    • 先从候选划分属性中找出信息增益高于平均水平的属性,再从中选取增益率最高的

递归构建决策树:

  • 从数据集构造决策树算法所需的子功能模块工作原理如下:
    • 得到原始数据集,然后基于最好的属性值划分数据集
    • 由于特征值可能多于两个,因此可能存在大于两个分支的数据集划分
    • 第一次划分之后,数据将被向下传递到树分支的下一个节点,在此节点在此划分数据,因此可以使用递归的原则处理数据集
  • 递归结束的条件是:
    • 程序完全遍历所有划分数据集的属性,或者每个分支下的所有实例都具有相同的分类
    • 如果所有实例具有相同的分类,则得到一个叶子节点或者终止块,任何到达叶子节点的数据必然属于叶子节点的分类。

2.2.4. CART 算法

CART ID3C4.5 不同之处在于 CART 生成的树必须是二叉树。也就是说,无论是回归还是分类问题,无论特征是离散的还是连续的,无论属性取值有多个还是两个,内部节点只能根据属性值进行二分。

根据变量选择多种指标:

  • 分类目标
    • Gini 指标
    • 分类树
  • 连续目标
    • 最小平方残差
    • 回归树

2.2.4.1. 回归树

回归树中,使用平方误差最小化准则来选择特征并进行划分。每一个叶子节点给出的预测值,是划分到该叶子节点的所有样本目标值的均值,这样只是在给定划分的情况下最小化了平方误差。

要确定最优化分,还需要遍历所有属性,以及其所有的取值来分别尝试划分并计算在此种划分情况下的最小平方误差,选取最小的作为此次划分的依据。由于回归树生成使用平方误差最小化准则,所以又叫做最小二乘回归树。

回归树的生成:

  • 输入
    • 训练数据集 D
  • 输出
    • CART 回归树 f(x)
  • 在训练数据集所在的输入空间中,递归地将每个区域划分为两个子区域并决定每个子区域上的输出值,构建二叉决策树:
    • 利用式子 m i n j , s ( m i n c 1 ∑ x i ∈ R 1 ( j , s ) ( y i − c 1 ) 2 + ( m i n c 2 ∑ x i ∈ R 2 ( j , s ) ( y i − c 2 ) 2 ) min_{j, s}(min_{c_1} \sum_{x_i \in R_1(j,s)} (y_i - c_1)^2 + (min_{c_2} \sum_{x_i \in R_2(j,s)} (y_i - c_2)^2 ) minj,s(minc1xiR1(j,s)(yic1)2+(minc2xiR2(j,s)(yic2)2)
    • 选择平方误差最小的切分变量 j(切分点 s),选定的对 ( j, s) 划分区域并决定相应的输出值
    • 继续对两个子区域调用上述两个步骤,直至满足停止条件
    • 将输入空间划分为 m 个区域 R 1 , R 2 , . . . , R m R_1, R_2, ..., R_m R1,R2,...,Rm, 生成决策树

2.2.4.2. 分类树

分类树中,使用 Gini 指数最小化准则来选择特征并进行划分:

  • Gini 指数表示集合的不确定性,或者是不纯度
    • 基尼指数越大,集合不确定性越高,不纯度也越大
      • 这一点和熵类似
    • 另一种理解基尼指数的思路是,基尼指数是为了最小化误分类的概率

分类树的生成

  • 输入
    • 训练数据集 D
  • 输出
    • CART 决策树
  • 根据训练数据,从根节点开始,递归地对每个结点进行以下操作,构建二叉决策树:
    • 利用式子 G i n i ( D , A ) = D 1 D G i n i ( D 1 ) + D 2 D G i n i ( D 2 ) Gini(D, A) = \frac{D_1}{D} Gini(D_1) + \frac{D_2}{D} Gini(D_2) Gini(D,A)=DD1Gini(D1)+DD2Gini(D2) 计算 A = a 时的基尼指数
    • 选择基尼指数最小的特征(切分点)作为最优特征(最优切分点), 从现结点生成两个子节点,将训练数据集依特征分配到两个子节点中去
    • 对两个子节点递归地调用上述两步, 直至满足停止条件
    • 生成 CART 决策树

2.3. 决策树剪枝

剪枝是决策树学习算法应对“过拟合”问题的主要方法。在决策树学习中,为了尽可能正确地分类训练样本,结点划分过程将不断重复,有时会造成决策树分支过多,导致把训练集自身的一些特点当作所有数据都具有的一般性质而导致的过拟合。

2.3.1. 算法

剪枝步骤:

  • 剪枝,形成子树序列:
    • 从生成算法产生的决策树 T_0 底端开始不断剪枝,直到 T_0 的根结点,形成子树序列 { T 0 , T 1 , . . . , T n } \{ T_0, T_1, ..., T_n \} {T0,T1,...,Tn}
    • T 0 T_0 T0 中每个内部结点 t,计算:
      g ( t ) = C ( t ) − C ( T t ) ∣ T t ∣ − 1 g(t) = \frac{C(t) - C(T_t)}{|T_t| - 1} g(t)=Tt1C(t)C(Tt)
    • T 0 T_0 T0 中剪去 g(t) 最小的 T t T_t Tt,将得到的子树作为 T 1 T_1 T1,同时将最小的 g(t) 设为 a 1 a_1 a1 T 1 T_1 T1 为区间 [ a 1 , a 2 ) [a_1, a_2) [a1,a2) 的最优子树
    • 如此剪枝下去,直到根节点,不断增加 a 的值,产生新的区间
  • 在剪枝得到的子树序列 { T 0 , T 1 , . . . , T n } \{ T_0, T_1, ..., T_n \} {T0,T1,...,Tn} 中通过交叉验证选取最优子树 T a T_a Ta
    • 通过交叉验证法在独立的验证数据集上对子树序列进行测试,从中选择最优子树
    • 利用独立的验证数据集,测试子树序列中各子树的平方误差或基尼指数,最小的决策树就是最优决策树

剪枝过程中子树的损失函数:

C α ( T ) = C ( T ) + α ∣ T ∣ C_\alpha(T) = C(T) + \alpha |T| Cα(T)=C(T)+αT

  • T 为任意的子树
  • C(T) 为对训练数据的预测误
  • |T| 为子树 T 的叶结点个数
  • α ∣ T ∣ \alpha |T| αT 为非负数,权衡模型对训练数据的拟合程度与模型本身复杂度两者之间的关系

学习笔记【1】:决策树_第6张图片

2.3.2. 示例

现在假设通过 CART 算法生成了一棵如下图所示的决策树:

学习笔记【1】:决策树_第7张图片

可对树 T 进行剪枝的内部节点有 t 0 , t 1 , t 2 , t 3 t_0, t_1, t_2, t_3 t0,t1,t2,t3, 因此根据上述截图中的剪枝步骤 (3) 可以分别算出 g ( t 0 ) , g ( t 1 ) , g ( t 2 ) , g ( t 3 ) g(t_0), g(t_1), g(t_2), g(t_3) g(t0),g(t1),g(t2),g(t3),此时还不满足 (6)

学习笔记【1】:决策树_第8张图片

此时,可对树 T 进行剪枝的内部节点有 t 0 , t 1 , t 2 t_0, t_1, t_2 t0,t1,t2, 因此根据上述截图中的剪枝步骤 (3) 可以分别算出 g ( t 0 ) , g ( t 1 ) , g ( t 2 ) g(t_0), g(t_1), g(t_2) g(t0),g(t1),g(t2),此时还不满足 (6)

学习笔记【1】:决策树_第9张图片

图中有误,更正剪枝后的左图中的 t 1 t_1 t1 t 2 t_2 t2

此时,可对树 T 进行剪枝的内部节点有 t 0 , t 2 t_0, t_2 t0,t2, 因此根据上述截图中的剪枝步骤 (3) 可以分别算出 g ( t 0 ) , g ( t 2 ) g(t_0), g(t_2) g(t0),g(t2),此时还不满足 (6)

学习笔记【1】:决策树_第10张图片

此时,得到整个子树序列:

学习笔记【1】:决策树_第11张图片

接着采用交叉验证在子树序列中选择最优子树 T a T_a Ta 即可


3. 决策树可视化

3.1. 创建决策树

考虑以下二维数据,它一共有 4 种标签:

from sklearn.datasets import make_blobs

X, y = make_blobs(n_samples=300, centers=4,
                  random_state=0, cluster_std=1.0)
plt.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap='rainbow');

学习笔记【1】:决策树_第12张图片

在这组数据上构建的简单决策树不断将数据的一个特征或另一个特征按照某种判定条件进行分割。每分割一次,都将新区域内点的多数投票结果标签分配到该区域上。

下图展示了该数据的决策树分类器的前四级的可视化情况:

学习笔记【1】:决策树_第13张图片

需要注意的是,在第一次分割之后,上半个分支里面的所有数据点都没有变化,因此这个分支不需要继续分割

3.2. 在 Scikit-Learn 中使用决策树拟合数据

使用 DecisionTreeClassifier 评估器

from sklearn.tree import DecisionTreeClassifier
tree = DecisionTreeClassifier().fit(X, y)

编写一个辅助函数,对分类器的结果进行可视化:

def visualize_classifier(model, X, y, ax=None, cmap='rainbow'):
    ax = ax or plt.gca()
    
    # Plot the training points
    ax.scatter(X[:, 0], X[:, 1], c=y, s=30, cmap=cmap,
               clim=(y.min(), y.max()), zorder=3)
    ax.axis('tight')
    ax.axis('off')
    xlim = ax.get_xlim()
    ylim = ax.get_ylim()
    
    # fit the estimator
    model.fit(X, y)
    xx, yy = np.meshgrid(np.linspace(*xlim, num=200),
                         np.linspace(*ylim, num=200))
    Z = model.predict(np.c_[xx.ravel(), yy.ravel()]).reshape(xx.shape)

    # Create a color plot with the results
    n_classes = len(np.unique(y))
    contours = ax.contourf(xx, yy, Z, alpha=0.3,
                           levels=np.arange(n_classes + 1) - 0.5,
                           cmap=cmap, clim=(y.min(), y.max()),
                           zorder=1)

    ax.set(xlim=xlim, ylim=ylim)

visualize_classifier(DecisionTreeClassifier(), X, y)

学习笔记【1】:决策树_第14张图片

随着决策树深度的不断加深,我们可能会得到非常奇怪的分类区域。这些区域显然不是根据数据本身的分布情况生成的正确分类结果,而更像是一个特殊的数据样本或数据噪音形成的干扰结果。也就是说,出现了过拟合


总结

决策树的优点:

  • 简单直观,决策树可以可视化
  • 需要很少的数据准备。不需要提前归一化,处理缺失值
  • 使用决策树预测的代价是 O(log2m)
    • m 为样本数
  • 既处理分类问题,也可以处理回归问题
    • 很多算法只是专注于分类或者回归
  • 能够处理多分类问题
  • 相比于神经网络之类的黑盒分类模型,决策树在逻辑上可以得到很好的解释
  • 可以交叉验证的剪枝来选择模型,从而提高泛化能力
  • 对于异常点的容错能力好,鲁棒性高

决策树的缺点:

  • 决策树算法非常容易过拟合,导致泛化能力不强
    • 可以通过设置节点最小样本数和限制决 策树深度来改进
  • 决策树会因为数据的一个较小的波动导致树结构的剧烈改变
    • 可以通过集成学习之类的方法缓解
  • 有些比较复杂的关系,比如异或决策树很难学习
    • 这种关系可以换神经网络分类方法来解 决
  • 对于非均衡的数据,生成决策树容易偏向于比例较大的类
    • 可以通过调节样本权重,欠采样,过采样等方法来改善

参考资料

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