决策树-DT

  • 1、概述
  • 2、决策树模型
  • 3、决策树学习
  • 4、决策树构建-三步骤
    • 4.1 特征选择
      • 4.1.1 熵(entropy)
      • 4.1.2 条件熵(entropy)
      • 4.1.3 信息增益
      • 4.1.4 信息增益比
    • 4.2 决策树算法
      • 4.2.1 ID3 算法
      • 4.2.2 C4.5算法
      • 4.2.3 手写python实现ID3、C4.5算法
    • 4.3 决策树的剪枝
  • 总结

1、概述


\quad \quad作为机器学习中的一大类模型,树模型一直以来都颇受学界和业界的重视。目前无论是各大比赛各种大杀器的XGBoost、lightgbm、还是像随机森林、AdaBoost等典型集成学习模型,都是以决策树模型为基础的。传统的经典决策树算法包括ID3算法、C4.5算法以及GBDT的基分类器CART算法。

\quad \quad三大经典决策树算法最主要的区别是其特征选择的准则不同。ID3算法选择特征的依据是信息增益、C4.5是信息增益比,而CART则是基尼指数。作为一种基础的分类和回归方法,决策树可以有以下两种理解方法:可以认为是if-then的集合,也可以认为是定义在特征空间与类空间上的条件概率分布。

2、决策树模型

\quad \quad定义: 分类决策树模型是一种描述对实例进行分类的树形结构。

\quad \quad决策树由结点和有向边组成。结点有两种类型:内部结点和叶结点,其中内部结点表示一个特征或属性,叶结点表示一个类。 一般的,一棵决策树包含一个根结点、若干个内部结点和若干个叶结点。叶结点对应于决策结果,其他每个结点则对应于一个属性测试。每个结点包含的样本集合根据属性测试的结果被划分到子结点中,根结点包含样本全集,从根结点到每个叶结点的路径对应了一个判定测试序列。在下图中,圆和方框分别表示内部结点和叶结点。决策树学习的目的是为了产生一棵泛化能力强,即处理未见示例能力强的决策树。

\quad \quad用决策树分类:从根节点开始,对实例的某一特征进行测试,根据测试结果将实例分配到其子节点,此时每个子节点对应着该特征的一个取值,如此递归的对实例进行测试并分配,直到到达叶节点,最后将实例分到叶节点的类中。

\quad \quad下图为决策树示意图,圆点——内部节点,方框——叶节点


\quad \quad上节介绍的k-近邻算法可以完成很多分类任务,但是其最大的缺点是无法给出数据的内在含义,决策树的优势在于数据形式非常容易理解。

3、决策树学习

\quad \quad假设给定训练数据集:

D={(x1,y1),(x2,y2),⋯,(xN,yN)}D=\{ (x_1 ,y_1 ),(x_2,y_2 ),⋯,(x_N,y_N) \} D={(x1​,y1​),(x2​,y2​),⋯,(xN​,yN​)}
其中,xi=(xi(1),xi(2),...,xi(n))Tx_i=(x_i^{(1)}, x_i^{(2)}, ..., x_i^{(n)})^Txi​=(xi(1)​,xi(2)​,...,xi(n)​)T 为输入实例,即特征向量,n为特征个数,yi∈{1,2,...,K}y_i \in \{ 1, 2, ..., K\}yi​∈{1,2,...,K} 为类标,i=1,2,...,N;N为样本容量i=1,2,...,N;N为样本容量i=1,2,...,N;N为样本容量。

  • 决策树学习的目标:根据给定的训练数据集构建一个决策树模型,使它能够对实例进行正确的分类。

  • 决策树学习的本质:从训练集中归纳出一组分类规则,或者说是由训练数据集估计条件概率模型。

  • 决策树学习的损失函数:正则化的极大似然函数

  • 决策树学习的策略:最小化损失函数

  • 决策树学习的目标:在损失函数的意义下,选择最优决策树的问题。

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

  1. 开始,构建根结点,将所有训练数据都放在根结点。选择一个最优特征,按照这一特征将训练数据集分割成子集,使得各个子集有一个在当前条件下最好的分类。

  2. 如果这些子集已经能够被基本正确分类,那么构建叶结点,并将这些子集分到所对应的叶结点中去,如果还有子集不能被基本正确分类,那么就对这些子集选择新的最优特征,继续对其进行分割,构建相应的结点。

  3. 如此递归地进行下去,直至所有训练数据子集被基本正确分类,或者没有合适的特征为止。

  4. 最后每个子集都被分到叶结点上,即都有了明确的类。这就生成了一棵决策树。

基本算法如下:

输入:
\qquad 训练集: D={(x1,y1),(x2,y2),⋯,(xN,yN)}D=\{ (x_1 ,y_1 ),(x_2,y_2 ),⋯,(x_N,y_N) \} D={(x1​,y1​),(x2​,y2​),⋯,(xN​,yN​)}
\qquad属性(特征)集:A={a1,a2,...,ad}A=\{ a_1,a_2,...,a_d \} A={a1​,a2​,...,ad​}
过程:
\qquad 函数TreeGenerate(D,A)TreeGenerate(D,A)TreeGenerate(D,A)
( 1 ) 生成结点根node
( 2 ) if D中样本全属于同一类别CkC_kCk​ then
( 3 )\qquad 将node标记为CkC_kCk​ 类叶节点; return
( 4 )end if
( 5 )if A=∅A = \varnothingA=∅ OR D中样本在 A 上取值相同 then
( 6 )\qquad 将node标记为叶结点,其类别标记为D中样本数最多的类 return
( 7 )end if
( 8 )从A中选择最优划分属性a∗a_*a∗​
( 9 )for a∗a_*a∗​ 的每一个值a∗va_*^va∗v​ do
(10)\qquad 为node生成一个分支:令DvD_vDv​表示D中在a∗a_*a∗​上取值为a∗va_*^va∗v​的样本子集
(11 )\qquadif DvD_vDv​为空 then
(12)\qquad\qquad将分支结点标记为叶结点,其类别标记为D中样本最多的类
(13 )\qquadelse
(14 )\qquad\qquad以TreeGenerate(Dv,A−{a∗})TreeGenerate(D_v, A - \{a_*\})TreeGenerate(Dv​,A−{a∗​})为分支结点
(15 )\qquadend if
(16)end for
输出:
\qquad 以node为根节点的决策树

\quad \quad以上方法生成的决策树可能对训练数据有很好的分类能力,但对未知的测试数据却未必有很好的分类能力,即可能发生过拟合现象。我们需要对已生成的树自下而上进行剪枝,将树变得更简单,从而使它具有更好的泛化能力。具体地,就是去掉过于细分的叶结点,使其回退到父结点,甚至更高的结点,然后将父结点或更高的结点改为新的叶结点。如果特征数量很多,也可以在决策树学习开始的时候,对特征进行选择,只留下对训练数据有足够分类能力的特征。

\quad \quad决策树通常有三个步骤:特征选择、决策树的生成、决策树的修剪。

使用决策树做预测需要以下过程:

  • 收集数据:可以使用任何方法。比如想构建一个相亲系统,我们可以从媒婆那里,或者通过参访相亲对象获取数据。根据他们考虑的因素和最终的选择结果,就可以得到一些供我们利用的数据了。

  • 准备数据:收集完的数据,我们要进行整理,将这些所有收集的信息按照一定规则整理出来,并排版,方便我们进行后续处理。

  • 分析数据:可以使用任何方法,决策树构造完成之后,我们可以检查决策树图形是否符合预期。

  • 训练算法:这个过程也就是构造决策树,同样也可以说是决策树学习,就是构造一个决策树的数据结构。

  • 测试算法:使用经验树计算错误率。当错误率达到了可接收范围,这个决策树就可以投放使用了。

  • 使用算法:此步骤可以使用适用于任何监督学习算法,而使用决策树可以更好地理解数据的内在含义。

4、决策树构建-三步骤

\quad \quad使用决策树做预测的每一步骤都很重要,数据收集不到位,将会导致没有足够的特征让我们构建错误率低的决策树。数据特征充足,但是不知道用哪些特征好,将会导致无法构建出分类效果好的决策树模型。从算法方面看,决策树的构建是我们的核心内容。

\quad \quad决策树要如何构建呢?通常,这一过程可以概括为3个步骤:特征选择、决策树的生成和决策树的修剪。

4.1 特征选择

\quad \quad特征选择在于选取对训练数据具有分类能力的特征。这样可以提高决策树学习的效率,如果利用一个特征进行分类的结果与随机分类的结果没有很大差别,则称这个特征是没有分类能力的。

\quad \quad特征选择的准则是信息增益(information gain)或信息增益比

\quad \quad那么,什么是信息增益?在讲解信息增益之前,让我们看一组实例,贷款申请样本数据表。


\quad \quad希望通过所给的训练数据学习一个贷款申请的决策树,用以对未来的贷款申请进行分类,即当新的客户提出贷款申请时,根据申请人的特征利用决策树决定是否批准贷款申请。

\quad \quad 特征选择就是决定用哪个特征来划分特征空间。比如,我们通过上述数据表得到两个可能的决策树,分别由两个不同特征的根结点构成。


\quad \quad 图5.3(a)所示的根结点的特征是年龄,有3个取值,对应于不同的取值有不同的子结点。图5.3(b)所示的根节点的特征是工作,有2个取值,对应于不同的取值有不同的子结点。两个决策树都可以从此延续下去。问题是:究竟选择哪个特征更好些?这就要求确定选择特征的准则。直观上,如果一个特征具有更好的分类能力,或者说,按照这一特征将训练数据集分割成子集,使得各个子集在当前条件下有最好的分类,那么就更应该选择这个特征。信息增益就能够很好地表示这一直观的准则。

4.1.1 熵(entropy)

\quad \quad在信息论与概率统计中,熵(entropy) 是表示随机变量 不确定性的度量。熵也指物体内部的混乱程度,熵值越高,混乱程度越高。

\quad \quad设XXX是一个取有限个值的离散随机变量,其概率分布为:
P(X=xi)=pi,i=1,2,⋯,nP(X=x_i )=p_i,i=1,2,⋯,nP(X=xi​)=pi​,i=1,2,⋯,n
则随机变量XXX的熵定义为:
H(X)=−∑i=1npilogpiH(X)=− \sum_{i=1}^np_ilogp_i H(X)=−i=1∑n​pi​logpi​

4.1.2 条件熵(entropy)

\quad \quad条件熵H(Y∣X)H(Y|X)H(Y∣X)表示在已知随机变量XXX的条件下随机变量YYY的不确定性。

\quad \quad设有随机变量(X,Y)(X, Y)(X,Y)其联合概率分布为:
P(X=xi,Y=yj)=pij,i=1,2,...,n;j=1,2,...,mP(X=x_i,Y=y_j)=p_{ij} ,i=1,2,...,n;j=1,2,...,m P(X=xi​,Y=yj​)=pij​,i=1,2,...,n;j=1,2,...,m

随机变量XXX给定的条件下随机变量YYY的条件熵(conditional entropy)H(Y∣X)H(Y|X)H(Y∣X),定义为XXX给定条件下YYY的条件概率分布的熵对XXX的数学期望:
H(Y∣X)=∑i=1npiH(Y∣X=xi)H(Y|X) = \sum_{i = 1}^n p_iH(Y|X = x_i)H(Y∣X)=i=1∑n​pi​H(Y∣X=xi​)

其中,pi=p(X=xi),i=1,2,...,np_i=p(X=x_i),i=1,2,...,npi​=p(X=xi​),i=1,2,...,n

\quad \quad当熵和条件熵中的概率由数据估计(如极大似然估计)得到时,所对应的熵与条件熵分别称为经验熵(empirical entropy)和经验条件熵(empirical conditional entropy)。此时,如果有0概率,令0log0=00log0=00log0=0。

4.1.3 信息增益

\quad \quad信息增益表示得知特征X的信息而使得类Y的信息不确定性减少的程度。

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

g(D,A)=H(D)−H(D∣A)g(D,A)=H(D)-H(D|A)g(D,A)=H(D)−H(D∣A)

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

根据信息增益准则的特征选择方法

\quad \quad对训练数据集(或子集)D,计算其每个特征的信息增益,并比较它们的大小,选择信息增益最大的特征。

算法(信息增益算法)
输入:训练数据集DDD和特征AAA
输出:特征AAA对训练数据集DDD的信息增益g(D,A)g(D,A)g(D,A)

\quad \quad其中,H(D)H(D)H(D)是数据集DDD的熵,H(Di)H(D_i)H(Di​)是数据集DiD_iDi​的熵,H(D∣A)H(D|A)H(D∣A)是数据集DDD对特征AAA的条件熵。 DiD_iDi​是DDD中特征AAA取第iii个值的样本子集,CkC_kCk​是DDD中属于第kkk类的样本子集。nnn是特征AAA取 值的个数,KKK是类的个数。

根据此公式计算经验熵H(D)H(D)H(D),分析贷款申请样本数据表中的数据。最终分类结果只有两类,即放贷和不放贷。根据表中的数据统计可知,在15个数据中,9个数据的结果为放贷,6个数据的结果为不放贷。所以数据集D的经验熵H(D)为:

H(D)=−915log2915−615log2615=0.971H(D)=-\frac{9}{15}log_2\frac{9}{15}-\frac{6}{15}log_2\frac{6}{15}=0.971H(D)=−159​log2​159​−156​log2​156​=0.971

4.1.4 信息增益比

\quad \quad以信息增益作为划分训练数据集的特征,存在偏向于选择取值较多的特征的问题。使用信息增益比可以对这个问题进行校正,这是特征选择的另一个标准。

定义:

\quad \quad特征AAA对训练数据集DDD的信息增益比gR(D,A)g_R(D,A)gR​(D,A)定义为其信息增益g(D,A)g(D,A)g(D,A)与训练数据集DDD关于特征A的值的经验熵HA(D)H_A(D)HA​(D)之比:
gR(D,A)=g(D,A)HA(D)g_R(D,A)=\frac{g(D,A)}{H_A(D)}gR​(D,A)=HA​(D)g(D,A)​

其中,HA(D)=−∑i=1n∣Di∣∣D∣log2∣Di∣∣D∣,n是特征A取值的个数。H_A(D)=-\sum_{i=1}^n\frac{|D_i|}{|D|}log_2\frac{|D_i|}{|D|},n是特征A取值的个数。HA​(D)=−i=1∑n​∣D∣∣Di​∣​log2​∣D∣∣Di​∣​,n是特征A取值的个数。

【代码实现经验熵、信息增益、信息增益比】

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline
import math
import operator
from matplotlib.font_manager import FontProperties
from math import log
import pprint
from sklearn.datasets import load_iris
from collections import Counter"""
函数说明:计算经验熵(香农熵)Parameters:datasets - 数据集j-数据集第j列
Returns:ent - 经验熵(香农熵)当j=-1时,返回训练数据集D的经验熵当j为其他时,返回训练数据集关于第j特征的值的熵
"""
def single_ent(datasets,j):data_length = len(datasets)#返回数据集的行数即样本个数label_count = {}#保存每个标签(Label)出现次数的字典for i in range(data_length):label = datasets[i][j]#数据集的最后一列if label not in label_count:#如果标签(Label)没有放入统计次数的字典,添加进去label_count[label] = 0label_count[label] += 1#Label计数ent = -sum([(p / data_length) * log(p / data_length, 2)#计算经验熵for p in label_count.values()])return ent#返回经验熵
"""
函数说明:计算各个特征对于训练集的条件经验熵Parameters:datasets - 数据集j - 数据集第j列即特征值索引
Returns:cond_ent - 条件经验熵(香农熵)
"""
# 经验条件熵$ H(D|A)$
def cond_ent(datasets, j):#参数j:指定特征data_length = len(datasets)feature_sets = {}for i in range(data_length):feature = datasets[i][j]if feature not in feature_sets:#如果特征没有放入统计次数的字典,添加进去feature_sets[feature] = []feature_sets[feature].append(datasets[i])#划分数据集cond_ent = sum([(len(p) / data_length) * single_ent(p,-1) for p in feature_sets.values()])return cond_ent
"""
函数说明:计算某特征对于训练集的信息增益Parameters:datasets - 数据集j - 数据集第j列即特征值索引
Returns:信息增益"""# 信息增益
def info_gain(datasets, j):return single_ent(datasets,-1)-cond_ent(datasets,j)
"""
函数说明:计算某特征对于训练集的信息增益比Parameters:datasets - 数据集j - 数据集第j列即特征值索引
Returns:信息增益比"""
# 信息增益比
def info_gain_ratio(datasets,j):return 0 if single_ent(datasets,j)==0 else info_gain(datasets,j)/single_ent(datasets,j)"""
函数说明:输出所有特征信息增益,并选择信息增益最大的特征,为根结点特征
Parameters:datasets - 数据集
Returns:bestFeature - 信息增益最大的(最优)特征的索引值"""
def info_gain_bestfeature(datasets):count = len(datasets[0]) - 1  #特征数量features = [] #记录各个特征的信息增益for c in range(count):c_info_gain = info_gain(datasets,c)#信息增益features.append((c, c_info_gain))print('特征({}) - 信息增益 - {:.3f}'.format(labels[c], c_info_gain))# 比较大小best_ = max(features, key=lambda x: x[-1])bestFeature=best_[0]return '特征({})的信息增益最大,选择为根节点特征'.format(labels[best_[0]])"""
函数说明:输出所有特征信息增益比,并选择信息增益比最大的特征,为根结点特征
Parameters:datasets - 数据集
Returns:bestFeature - 信息增益最大的(最优)特征的索引值"""def info_gain_ratio_bestfeature(datasets):count = len(datasets[0]) - 1  #特征数量features1 = [] #记录各个特征的信息增益for c in range(count):c_info_gain_ratio = info_gain_ratio(datasets,c)#信息增益比features1.append((c, c_info_gain_ratio))print('特征({}) - 信息增益比 - {:.3f}'.format(labels[c], c_info_gain_ratio))# 比较大小best_ = max( features1, key=lambda x: x[-1])bestFeature=best_[0]return '特征({})的信息增益比最大,选择为根节点特征'.format(labels[best_[0]])

实现上述案例表5.1

1、创建数据集

def create_data():datasets = [['青年', '否', '否', '一般', '否'],['青年', '否', '否', '好', '否'],['青年', '是', '否', '好', '是'],['青年', '是', '是', '一般', '是'],['青年', '否', '否', '一般', '否'],['中年', '否', '否', '一般', '否'],['中年', '否', '否', '好', '否'],['中年', '是', '是', '好', '是'],['中年', '否', '是', '非常好', '是'],['中年', '否', '是', '非常好', '是'],['老年', '否', '是', '非常好', '是'],['老年', '否', '是', '好', '是'],['老年', '是', '否', '好', '是'],['老年', '是', '否', '非常好', '是'],['老年', '否', '否', '一般', '否'],]labels = [u'年龄', u'有工作', u'有自己的房子', u'信贷情况', u'类别']# 返回数据集和每个维度的名称return datasets, labels

可以表格的形式查看原表格(非必要的一步)

datasets, labels = create_data()
train_data = pd.DataFrame(datasets, columns=labels)
train_data

2、套用上述模块

info_gain_bestfeature(np.array(datasets))

查看结果,与李航统计学习方法书上例题5.2答案一样。

info_gain_ratio_bestfeature(np.array(datasets))

4.2 决策树算法

\quad \quad构建决策树的算法有很多,比如C4.5、ID3和CART。

4.2.1 ID3 算法

\quad \quadID3算法的核心是在决策树各个结点上应用信息增益准则选择特征,递归地构建决策树。

具体方法是:

1)从根结点(root node)开始,对结点计算所有可能的特征的信息增益,选择信息增益最大的特征作为结点的特征。

2)由该特征的不同取值建立子节点,再对子结点递归地调用以上方法,构建决策树;直到所有特征的信息增益均很小或没有特征可以选择为止;

3)最后得到一个决策树。

算法步骤:

算法 5.2 (ID3)算法
输入:训练数据集D,特征集A,阈值ε\varepsilonε;
输出:决策树T
(1)若D中所有实例属于同一类CkC_kCk​,则T为单结点树,并将类CkC_kCk​作为该结点的类标记,返回T;
(2)若A=∅A=\emptysetA=∅,则T为单结点树,并将D中实例数最大的类CkC_kCk​作为该结点的类标记,返回T;
(3)否则,计算A中各特征对D的信息增益,选择信息增益最大的特征AgA_gAg​;
(4)如果AgA_gAg​的信息增益小于阈值ε\varepsilonε,则置T为单结点树,并将D中实例数最大的类CkC_kCk​作为该结点的类标记,返回T;
(5)否则,对AgA_gAg​的每一可能值aia_iai​,依Ag=aiA_g=a_iAg​=ai​将D分为若干非空子集DiD_iDi​,将DiD_iDi​中实例数最大的类作为标记,构建子结点,由结点及其子结点构成树T,返回T;
(6)对第i个子结点,以DiD_iDi​为训练集,以A−{Ag}A-\{A_g\}A−{Ag​}为特征集,递归地调用步(1)~(5),得到子树TiT_iTi​,返回TiT_iTi​。

决策树ID3算法的局限性
  
  ID3算法虽然提出了新思路,但是还是有很多值得改进的地方。

a) ID3没有考虑连续特征,比如长度,密度都是连续值,无法在ID3运用。这大大限制了ID3的用途。

b) ID3采用信息增益大的特征优先建立决策树的节点。很快就被人发现,在相同条件下,取值比较多的特征比取值少的特征信息增益大。比如一个变量有2个值,各为1/2,另一个变量为3个值,各为1/3,其实他们都是完全不确定的变量,但是取3个值的比取2个值的信息增益大。如果校正这个问题呢?

c) ID3算法对于缺失值的情况没有做考虑

d) 没有考虑过拟合的问题

\quad \quadID3 算法的作者昆兰基于上述不足,对ID3算法做了改进,这就是C4.5算法,下面我们就来聊下C4.5算法。

4.2.2 C4.5算法

\quad \quad上一节我们讲到ID3算法有四个主要的不足,一是不能处理连续特征,第二个就是用信息增益作为标准容易偏向于取值较多的特征,最后两个是缺失值处理的问和过拟合问题。昆兰在C4.5算法中改进了上述4个问题:

  • 对于第一个问题,不能处理连续特征, C4.5的思路是将连续的特征离散化。比如m个样本的连续特征A有m个,从小到大排列为a1,a2,...,ama_1,a_2,...,a_ma1​,a2​,...,am​,则C4.5取相邻两样本值的平均数,一共取得m-1个划分点,其中第i个划分点Ti表示为:TI=ai+ai+12T_I=\frac{a_i+a_{i+1}}{2}TI​=2ai​+ai+1​​。对于这m-1个点,分别计算以该点作为二元分类点时的信息增益。选择信息增益最大的点作为该连续特征的二元离散分类点。比如取到的增益最大的点为ata_tat​,则小于ata_tat​的值为类别1,大于ata_tat​的值为类别2,这样我们就做到了连续特征的离散化。要注意的是,与离散属性不同的是,如果当前节点为连续属性,则该属性后面还可以参与子节点的产生选择过程。
  • 对于第二个问题,信息增益作为标准容易偏向于取值较多的特征的问题。我们引入一个信息增益比,用信息增益比选择特征。
  • 对于第三个缺失值处理的问题,主要需要解决的是两个问题,一是在样本某些特征缺失的情况下选择划分的属性,二是选定了划分属性,对于在该属性上缺失特征的样本的处理。
  • 对于第4个问题,C4.5引入了正则化系数进行初步的剪枝。


决策树C4.5算法的局限性
   
 \quad \quadC4.5虽然改进或者改善了ID3算法的几个主要的问题,仍然有优化的空间。

1)由于决策树算法非常容易过拟合,因此对于生成的决策树必须要进行剪枝。剪枝的算法有非常多,C4.5的剪枝方法有优化的空间。思路主要是两种,一种是预剪枝,即在生成决策树的时候就决定是否剪枝。另一个是后剪枝,即先生成决策树,再通过交叉验证来剪枝。剪枝算法见下篇。

2)C4.5生成的是多叉树,即一个父节点可以有多个节点。很多时候,在计算机中二叉树模型会比多叉树运算效率高。如果采用二叉树,可以提高效率。

3)C4.5只能用于分类,如果能将决策树用于回归的话可以扩大它的使用范围。

4)C4.5由于使用了熵模型,里面有大量的耗时的对数运算,如果是连续值还有大量的排序运算。如果能够加以模型简化可以减少运算强度但又不牺牲太多准确性的话,那就更好了。

\quad \quad这4个问题在CART树里面部分加以了改进。所以目前如果不考虑集成学习话,在普通的决策树算法里,CART算法算是比较优的算法了。scikit-learn的决策树使用的也是CART算法。在下篇里我们会重点聊下CART算法的主要改进思路,上篇就到这里。

4.2.3 手写python实现ID3、C4.5算法

\quad \quad由于 ID3 算法与C4.5算法相似,因此可以写一个python模板,只不过增加一个选方法的函数。

【代码实现,构建决策树】

第一部分:构建决策树

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline
import math
import operator
from matplotlib.font_manager import FontProperties
from math import log
import pprint
from sklearn.datasets import load_iris
from collections import Counter"""
函数说明:计算经验熵(香农熵)Parameters:datasets - 数据集j-数据集第j列
Returns:ent - 经验熵(香农熵)当j=-1时,返回训练数据集D的经验熵当j为其他时,返回训练数据集关于第j特征的值的熵
"""
def single_ent(datasets,j):data_length = len(datasets)#返回数据集的行数即样本个数label_count = {}#保存每个标签(Label)出现次数的字典for i in range(data_length):label = datasets[i][j]#数据集的最后一列if label not in label_count:#如果标签(Label)没有放入统计次数的字典,添加进去label_count[label] = 0label_count[label] += 1#Label计数ent = -sum([(p / data_length) * log(p / data_length, 2)#计算经验熵for p in label_count.values()])return ent#返回经验熵
"""
函数说明:计算各个特征对于训练集的条件经验熵Parameters:datasets - 数据集j - 数据集第j列即特征值索引
Returns:cond_ent - 条件经验熵(香农熵)
"""
# 经验条件熵$ H(D|A)$
def cond_ent(datasets, j):#参数j:指定特征data_length = len(datasets)feature_sets = {}for i in range(data_length):feature = datasets[i][j]if feature not in feature_sets:#如果特征没有放入统计次数的字典,添加进去feature_sets[feature] = []feature_sets[feature].append(datasets[i])#划分数据集cond_ent = sum([(len(p) / data_length) * single_ent(p,-1) for p in feature_sets.values()])return cond_ent
"""
函数说明:计算某特征对于训练集的信息增益Parameters:datasets - 数据集j - 数据集第j列即特征值索引
Returns:信息增益"""# 信息增益
def info_gain(datasets, j):return single_ent(datasets,-1)-cond_ent(datasets,j)
"""
函数说明:计算某特征对于训练集的信息增益比Parameters:datasets - 数据集j - 数据集第j列即特征值索引
Returns:信息增益比"""
# 信息增益比
def info_gain_ratio(datasets,j):return 0 if single_ent(datasets,j)==0 else info_gain(datasets,j)/single_ent(datasets,j)"""
函数说明:选取最有特征
Parameters:datasets - 数据集method-选择最优特征准则:ID3:依据信息增益;C4.5:依据信息增益比
Returns:bestFeature - 信息增益最大的(最优)特征的索引值"""
def bestfeature(datasets,method='ID3'):assert method in ['ID3','C4.5'],"method 须为id3或c45"def calcEnt(datasets,j):if method=='ID3':return info_gain(datasets,j)if method=='C4.5':return info_gain_ratio(datasets,j)count = len(datasets[0]) - 1  #特征数量   features = [] #记录各个特征的信息增益for c in range(count):c_info_gain = calcEnt(datasets,c)#信息增益features.append((c, c_info_gain))#print('特征({}) - 信息增益 - {:.3f}'.format(labels[c], c_info_gain))# 比较大小best_ = max(features, key=lambda x: x[-1])bestFeature=best_[0]return bestFeature"""
函数说明:统计classList中出现最多的类标签Parameters:classList - 类标签列表
Returns:sortedClassCount[0][0] - 出现此处最多的元素(类标签)"""
def majorityCnt(classList):classCount = {}for vote in classList:                                        #统计classList中每个元素出现的次数if vote not in classCount.keys():classCount[vote] = 0   classCount[vote] += 1sortedClassCount = sorted(classCount.items(), key = operator.itemgetter(1), reverse = True)        #根据字典的值降序排序return sortedClassCount[0][0]   #返回classList中出现次数最多的元素"""
函数说明:按照给定特征划分数据集Parameters:datasets - 待划分的数据集axis - 划分数据集的特征value - 需要删除的特征的值
Returns:无"""
def splitDataSet(datasets, axis, value):       retDataset = []                                        #创建返回的数据集列表for featVec in datasets:                             #遍历数据集if featVec[axis] == value:reducedFeatVec = featVec[:axis]                #去掉axis特征reducedFeatVec.extend(featVec[axis+1:])     #将符合条件的添加到返回的数据集retDataset.append(reducedFeatVec)return retDataset                                      #返回划分后的数据集"""
函数说明:创建决策树Parameters:datasets - 训练数据集labels - 分类属性标签featLabels - 存储选择的最优特征标签
Returns:myTree - 决策树"""
def createTree(datasets, labels, featLabels):classList = [example[-1] for example in datasets]            #取分类标签(是否放贷:yes or no)if classList.count(classList[0]) == len(classList):            #1、如果类别完全相同则停止继续划分return classList[0]if len(datasets[0]) == 1:                                   #2、如果特征集为空即数据集只有1列return majorityCnt(classList)                          #遍历完所有特征时返回出现次数最多的类标签bestFeat = bestfeature(datasets,method='ID3')                #3、选择最优特征,方法可以更改bestFeatLabel = labels[bestFeat]                            #最优特征的标签featLabels.append(bestFeatLabel)myTree = {bestFeatLabel:{}}                                    #根据最优特征的标签生成树del(labels[bestFeat])                                        # 已经选择的特征不再参与分类featValues = [example[bestFeat] for example in datasets]        #得到训练集中所有最优特征的属性值uniqueVals = set(featValues)                                #去掉重复的属性值for value in uniqueVals:                                    #遍历特征,创建决策树。                       myTree[bestFeatLabel][value] = createTree(splitDataSet(datasets, bestFeat, value), labels, featLabels)return myTree

第二部分:决策树可视化

"""
函数说明:获取决策树叶子结点的数目Parameters:myTree - 决策树
Returns:numLeafs - 决策树的叶子结点的数目"""
def getNumLeafs(myTree):numLeafs = 0                                                #初始化叶子firstStr = next(iter(myTree))                                #python3中myTree.keys()返回的是dict_keys,不在是list,所以不能使用myTree.keys()[0]的方法获取结点属性,可以使用list(myTree.keys())[0]secondDict = myTree[firstStr]                                #获取下一组字典for key in secondDict.keys():if type(secondDict[key]).__name__=='dict':                #测试该结点是否为字典,如果不是字典,代表此结点为叶子结点numLeafs += getNumLeafs(secondDict[key])else:   numLeafs +=1return numLeafs
"""
函数说明:获取决策树的层数Parameters:myTree - 决策树
Returns:maxDepth - 决策树的层数
"""
def getTreeDepth(myTree):maxDepth = 0                                                #初始化决策树深度firstStr = next(iter(myTree))                                #python3中myTree.keys()返回的是dict_keys,不在是list,所以不能使用myTree.keys()[0]的方法获取结点属性,可以使用list(myTree.keys())[0]secondDict = myTree[firstStr]                                #获取下一个字典for key in secondDict.keys():if type(secondDict[key]).__name__=='dict':                #测试该结点是否为字典,如果不是字典,代表此结点为叶子结点thisDepth = 1 + getTreeDepth(secondDict[key])else:   thisDepth = 1if thisDepth > maxDepth: maxDepth = thisDepth            #更新层数return maxDepth"""
函数说明:绘制结点Parameters:nodeTxt - 结点名centerPt - 文本位置parentPt - 标注的箭头位置nodeType - 结点格式
Returns:无
"""
def plotNode(nodeTxt, centerPt, parentPt, nodeType):arrow_args = dict(arrowstyle="<-")                                            #定义箭头格式font = FontProperties(fname=r"c:\windows\fonts\simsun.ttc", size=14)        #设置中文字体createPlot.ax1.annotate(nodeTxt, xy=parentPt,  xycoords='axes fraction',    #绘制结点xytext=centerPt, textcoords='axes fraction',va="center", ha="center", bbox=nodeType, arrowprops=arrow_args, FontProperties=font)"""
函数说明:标注有向边属性值Parameters:cntrPt、parentPt - 用于计算标注位置txtString - 标注的内容
Returns:无"""
def plotMidText(cntrPt, parentPt, txtString):xMid = (parentPt[0]-cntrPt[0])/2.0 + cntrPt[0]                                            #计算标注位置                   yMid = (parentPt[1]-cntrPt[1])/2.0 + cntrPt[1]createPlot.ax1.text(xMid, yMid, txtString, va="center", ha="center", rotation=30)"""
函数说明:绘制决策树Parameters:myTree - 决策树(字典)parentPt - 标注的内容nodeTxt - 结点名
Returns:无"""
def plotTree(myTree, parentPt, nodeTxt):decisionNode = dict(boxstyle="sawtooth", fc="0.8")                                        #设置结点格式leafNode = dict(boxstyle="round4", fc="0.8")                                            #设置叶结点格式numLeafs = getNumLeafs(myTree)                                                          #获取决策树叶结点数目,决定了树的宽度depth = getTreeDepth(myTree)                                                            #获取决策树层数firstStr = next(iter(myTree))                                                            #下个字典                                                 cntrPt = (plotTree.xOff + (1.0 + float(numLeafs))/2.0/plotTree.totalW, plotTree.yOff)    #中心位置plotMidText(cntrPt, parentPt, nodeTxt)                                                    #标注有向边属性值plotNode(firstStr, cntrPt, parentPt, decisionNode)                                        #绘制结点secondDict = myTree[firstStr]                                                            #下一个字典,也就是继续绘制子结点plotTree.yOff = plotTree.yOff - 1.0/plotTree.totalD                                        #y偏移for key in secondDict.keys():                               if type(secondDict[key]).__name__=='dict':                                            #测试该结点是否为字典,如果不是字典,代表此结点为叶子结点plotTree(secondDict[key],cntrPt,str(key))                                        #不是叶结点,递归调用继续绘制else:                                                                                #如果是叶结点,绘制叶结点,并标注有向边属性值                                             plotTree.xOff = plotTree.xOff + 1.0/plotTree.totalWplotNode(secondDict[key], (plotTree.xOff, plotTree.yOff), cntrPt, leafNode)plotMidText((plotTree.xOff, plotTree.yOff), cntrPt, str(key))plotTree.yOff = plotTree.yOff + 1.0/plotTree.totalD"""
函数说明:创建绘制面板Parameters:inTree - 决策树(字典)
Returns:无
"""
def createPlot(inTree):fig = plt.figure(1, facecolor='white')                                                    #创建figfig.clf()                                                                                #清空figaxprops = dict(xticks=[], yticks=[])createPlot.ax1 = plt.subplot(111, frameon=False, **axprops)                                #去掉x、y轴plotTree.totalW = float(getNumLeafs(inTree))                                            #获取决策树叶结点数目plotTree.totalD = float(getTreeDepth(inTree))                                            #获取决策树层数plotTree.xOff = -0.5/plotTree.totalW; plotTree.yOff = 1.0;                                #x偏移plotTree(inTree, (0.5,1.0), '')                                                            #绘制决策树plt.show()                                                                                 #显示绘制结果     

案例:


if __name__ == '__main__':datasets, labels = create_data()#上一例子已创建featLabels = []myTree = createTree(datasets, labels, featLabels)print(myTree)createPlot(myTree)

{‘有自己的房子’: {‘是’: ‘是’, ‘否’: {‘有工作’: {‘是’: ‘是’, ‘否’: ‘否’}}}}

4.3 决策树的剪枝

见此文

总结

优点:

  • 计算复杂度不高;
  • 对中间缺失值不敏感;
  • 解释性强,在解释性方面甚至比线性回归更强;
  • 与传统的回归和分类方法相比,决策树更接近人的决策模式,可以用图形表示,非专业人士也可以轻松理解;
  • 可以直接处理定性的预测变量而不需创建哑变量。

缺点:

  • 决策树的预测准确性一般比回归和分类方法弱,但可以通过用集成学习方法组合大量决策树,显著提升树的预测效果;

  • 可能会产生过度匹配的问题

适用数据类型:数值型和标称型

参考资料:
1、李航《统计学习方法》
2、https://blog.csdn.net/c406495762/article/details/76262487

机器学习笔记15——决策树(DT)、ID3算法、C4.5算法原理以及python实现案例相关推荐

  1. 机器学习笔记16——决策树剪枝算法原理及python实现案例

    决策树剪枝算法 引言 1.算法目的 2.算法基本思路: 3.决策树损失函数 4.剪枝类型: 4.1 预剪枝 4.2 后剪枝 4.3 两种剪枝策略对比 引言 \quad \quad在决策树.ID3.C4 ...

  2. 机器学习算法(5)——决策树(ID3、C4.5、CART)

    决策树又称为判定树,是运用于分类的一种树结构.决策树(decision tree)是一个树结构(可以是二叉树或非二叉树).其每个非叶节点表示一个特征属性上的测试,每个分支代表这个特征属性在某个值域上的 ...

  3. 决策树之ID3、C4.5、C5.0等五大算法及python实现

    每每以为攀得众山小,可.每每又切实来到起点,大牛们,缓缓脚步来俺笔记葩分享一下吧,please~ --------------------------- C5.0决策树之ID3.C4.5.C5.0算法 ...

  4. 【机器学习笔记】——决策树(Decision Tree)

    目 录 1 决策树 1.1 特征选择 1.1.1 基础定义 1.1.2 最优特征标准 1.2 树的生成 1.2.1 ID3 算法 1.2.2 C4.5 算法 1.2.2.1 如果特征是连续的 1.2. ...

  5. 机器学习面试题——决策树DT(Decision Tree),二叉树或多叉树分支决策分类

    机器学习面试题--决策树DT(Decision Tree),二叉树或多叉树分支决策分类 提示: 决策树算法起源于E.B.Hunt等人于1966年发表的论文"experiments in In ...

  6. EMD算法之Hilbert-Huang Transform原理详解和案例分析

    目录 Hilbert-Huang Transform 希尔伯特-黄变换 Section I 人物简介 Section II Hilbert-Huang的应用领域 Section III Hilbert ...

  7. 决策树的生成之ID3与C4.5算法

    跟我一起机器学习系列文章将首发于公众号:月来客栈,欢迎文末扫码关注! 1 基本概念 在正式介绍决策树的生成算法前,我们先将上一篇文章中介绍的几个概念重新梳理一下:并且同时再通过一个例子来熟悉一下计算过 ...

  8. 基于ID3、C4.5算法的决策树相关知识

    文章目录 基础概念 熵 条件熵 信息增益 信息增益比 决策树生成 ID3生成算法 决策树剪枝 C4.5生成算法 基础概念 熵 离散型变量X的概率分布是P(X).它的熵H(X)orH(P){H(X) \ ...

  9. python机器学习案例系列教程——决策树(ID3、C4.5、CART)

    全栈工程师开发手册 (作者:栾鹏) python数据挖掘系列教程 决策树简介 决策树算是最好理解的分类器了.决策树就是一个多层if-else函数,就是对对象属性进行多层if-else判断,获取目标属性 ...

  10. 机器学习-决策树(ID3、C4.5、CART)

    [机器学习]决策树(上)--ID3.C4.5.CART 决策树是一个非常常见并且优秀的机器学习算法,它易于理解.可解释性强,其可作为分类算法,也可用于回归模型. 对于基本树我将大致从以下四个方面介绍每 ...

最新文章

  1. 图像分类经典项目:基于开源数据集Fashion-MNIST的应用实践
  2. Centos 7.5 安装Zabbix4.0
  3. python 人工智能库_人工智能与Python库的关系
  4. 记录一次Socket编程:OutputStream的flush方法
  5. filco蓝牙不好用_800元和300元的机械键盘差多少,Filco圣手104晒单
  6. android 查看gpio状态_GPIO子系统重要概念
  7. java判断对称素数_SM2非对称算法的原理及实现 Java SM2的代码案例 | 一生孤注掷温柔 | 小奋斗...
  8. 东北到底有没有互联网?!
  9. 科普:什么是人工智能
  10. The Algorithms
  11. shell编程三大神器之awk
  12. thinkpad e470外接显示器后无声音
  13. TensorFlow 线性代数编译框架 XLA
  14. b站网页版倍速无效_看网课讲师太啰嗦太慢?在线视频课程效率低?教你自定义超倍速看
  15. 我工作时戴耳机是为了治疗电脑
  16. 自己动手模仿 springmvc 写一个 mvc框架
  17. {}System.Threading.ThreadAbortException: 正在中止线程。
  18. 【51CTO学院三周年】生活不止眼前的苟且,还是让我们一起欢乐的抠腚吧
  19. mac-pip3换源
  20. Cisco 交换机端口聚合技术(EtherChannel)

热门文章

  1. Flutter 2.0 Null-Safety(空安全)使用和理解
  2. Java语言基础03-标识符、关键字、字面值、变量、数据类型
  3. python安装pip之后镜像源配置
  4. idea中编译DataSphereStudio编译方法及问题排查
  5. 2022年电子造粒计数器市场前景分析及研究报告
  6. element ui自定义图标
  7. UnityEditor代码分享导出材质贴图和Mesh本体
  8. 对数正态分布(Log-Normal Distribution)
  9. CentOS7 修改Swap大小
  10. 参加PHPCONChina 2016大会