笔记整理【机器学习实战】【百面机器学习】:决策树

决策树

1. 原理

决策树是一种自上而下,对样本数据进行树形分类的过程,由结点和有向边组成。结点分为内部节点和叶子结点,其中每个内部节点表示一个特征或者属性的划分,叶子结点表示 一个类别或输出。
根结点包含整个样本集,决策树从根节点开始,经过根节点第一次判断划分,样本被分到不同的子节点中,再根据内部节点的特征进一步划分,直到所有样本都被归到某一个叶子结点中,即类别中。

三种停止条件:

  1. 当前节点包含的样本全属于同一类别,无需划分
  2. 当前属性集为空,或是所有样本在所有属性上取值相同,无法划分
  3. 当前节点包含的样本集合为空,不能划分

决策树总体流程伪代码(西瓜书)
输入:
训练集 D = ( x 1 , y 1 ) , ( x 2 , y 2 ) , ⋯   , ( x m , y m ) D = {(x_1, y_1), (x_2, y_2), \cdots, (x_m, y_m)} D=(x1,y1),(x2,y2),,(xm,ym)
属性集 A = a 1 , a 2 , ⋯   , a d A = {a_1, a_2, \cdots, a_d} A=a1,a2,,ad
过程:函数TreeGenerate ( D , A ) (D, A) (D,A)

  1. 生成结点node
  2. if D中样本全属于同一类别C then
  3. 将node标记为C类叶结点;return
  4. end if // 情形1递归返回
  5. if A = ∅ A = \empty A=OR D中样本在A上取值相同 then
  6. 将node标记为叶结点,其类别标记为D中样本数最多的类(利用当前结点的后验分布);return
  7. end if // 情形2递归返回
  8. 从A中选择最优划分属性 a ∗ a_* a //决策树算法的核心
  9. for a ∗ a_* a的每一个值 a ∗ v a_*^v av do
  10. 为node生成一个分支;令 D v D_v Dv表示D中在 a ∗ a_* a上取值为 a ∗ v a_*^v av的样本子集;
  11. if D v D_v Dv为空 then
  12. 将分支结点标记为叶子结点,其类别标记在D中样本最多的类(将父结点的样本分布作为当前结点的先验分布);return // 情形3递归返回
  13. else
  14. 以TreeGenerate( D v , A ∖ a ∗ D_v, A \setminus {a_*} Dv,Aa)为分支结点
  15. end if
  16. end for
    输出:
    以node为根节点的一棵决策树

树模型的本质:特征空间的划分

2. 经典算法

  • ID3
  • C4.5
  • CART

这个三个是 非常著名的决策树算法。简单粗暴来说,ID3使用信息增益作为选择特征的准则;C4.5使用 信息增益比作为选择特征的准则;CART使用GINI指数作为选择特征的准则。

决策树学习包括3个步骤 :特征选择 ,决策树生成,决策树修剪(剪枝)

3. 决策树学习

从不同的决策树中选取最优的决策树是一个NP完全问题,即多项式复杂程度的非确定性问题,在实际中我们通常会采用启发式学习的方法去构建一棵满足启发式条件的决策树。

3.1 特征选择

特征选择在于选取对于训练数据具有分类能力的特征。

熵表示随机变量不稳定性的度量,即数据中包含的信息量的大小。熵越小,包含的信息量越小,数据的纯度越高 ,也就是说数据越趋于一致,这就是我们希望的划分之后每个节点的样子。

设X是一个取有限个值的离散随机变量 ,其概率分布为
P ( X = x i ) = p i ,        i = 1 , 2 , ⋯   , n P(X= x_i)=p_i, \;\;\; i=1,2,\cdots,n P(X=xi)=pi,i=1,2,,n
则随机变量X的熵为:
H ( X ) = H ( p ) = − ∑ i = 1 n p i l o g p i H(X)=H(p)=-\sum_{i=1}^n p_ilogp_i H(X)=H(p)=i=1npilogpi
其中,若 p i = 0 p_i=0 pi=0,则定义 0 l o g 0 = 0 0log0=0 0log0=0
p i = 1 n p_i=\frac{1}{n} pi=n1

在这里插入图片描述
由定义,得
0 ⩽ H ( p ) ⩽ l o g   n 0 \leqslant H(p) \leqslant log \ n 0H(p)log n

设有随机变量 ( X , Y ) , (X, Y), (X,Y),其联合分布
P ( X = x i , Y = y i ) = P i j ,     i = 1 , 2 , ⋯   , n ;     j = 1 , 2 , 3 , ⋯   , n P(X=x_i, Y=y_i) = P_{ij}, \ \ \ i = 1,2,\cdots,n; \ \ \ j = 1,2,3, \cdots, n P(X=xi,Y=yi)=Pij,   i=1,2,,n;   j=1,2,3,,n
随机变量X给定的条件下随机变量Y的条件熵
H ( Y ∣ X ) = ∑ i = 1 n   p i H ( Y ∣ X = x i ) H(Y|X) = \sum_{i=1}^n\ p_i H(Y|X=x_i) H(YX)=i=1n piH(YX=xi)
即X给定条件下Y的条件概率分布的熵对X的数学期望。条件熵 H ( Y ∣ X ) H(Y|X) H(YX)表示在已知随机变量X的条件下随机变量Y的不确定性。

特征A对训练集D的信息增益
g ( D , A ) = H ( D ) − H ( D ∣ A ) g(D, A) = H(D) - H(D|A) g(D,A)=H(D)H(DA)
即集合D的经验熵H(D)与特征A给定条件下D的经验条件熵H(D|A)之差。
其中,当熵和条件熵由数据估计(极大似然估计)得到时,对应的熵和条件熵分别称为经验熵和经验条件熵。

3.1.1 最大信息增益 —ID3算法

对于样本集合D, ∣ D ∣ |D| D表示其样本容量,类别数为K。

  • ∣ C k ∣ |C_k| Ck为属于类 C k C_k Ck的样本的个数, ∑ k = 1 K ∣ C k ∣ = ∣ D ∣ \sum_{k=1}^K|C_k| = |D| k=1KCk=D
  • 设特征A由n个不同的特征取值,根据特征A的取值将D划分为n个子集 D 1 , D 2 , ⋯   , D n D_1, D_2, \cdots, D_n D1,D2,,Dn ∣ D i ∣ |D_i| Di D i D_i Di的样本数, ∑ i = 1 n ∣ D i ∣ = ∣ D ∣ \sum_{i=1}^n |D_i| = |D| i=1nDi=D
  • 记子集 D i D_i Di中属于类 C k C_k Ck的样本的集合为 D i k D_{ik} Dik,即 D i k = D i ∩ C k D_{ik}=D_i \cap C_k Dik=DiCk ∣ D i k ∣ |D_{ik}| Dik D i k D_{ik} Dik的样本个数
  1. 首先可得数据集D的经验熵: H ( D ) = − ∑ k = 1 K ∣ C k ∣ ∣ D ∣ l o g 2   ∣ C k ∣ ∣ D ∣ H(D)=-\sum_{k=1}^{K}\frac{|C_k|}{|D|}log_2\ \frac{|C_k|}{|D|} H(D)=k=1KDCklog2 DCk

  2. 计算某个特征A对于数据集D的经验条件熵H(D|A): 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 i ∣ l o g 2   ∣ D i k ∣ ∣ D i ∣ H(D|A) = \sum_{i=1}^n \frac{|D_i|}{|D|}H(D_i) = -\sum_{i=1}^n \frac{|D_i|}{|D|} \sum_{k=1}^{K}\frac{|D_{ik}|}{|D_i|}log_2\ \frac{|D_{ik}|}{|D_i|} H(DA)=i=1nDDiH(Di)=i=1nDDik=1KDiDiklog2 DiDik

  3. 计算求得信息增益: g ( D , A ) = H ( D ) − H ( D ∣ A ) g(D, A) = H(D) - H(D|A) g(D,A)=H(D)H(DA)

3.1.2 最大信息增益比—C4.5算法

特征A对训练集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)
即,信息增益 g ( D , A ) g(D, A) g(D,A)与训练数据D关于特征A的经验熵 H A ( D ) H_A(D) HA(D)之比。
其中, H A ( D ) = − ∑ i = 1 n ∣ D i ∣ ∣ D ∣ l o g 2 ∣ D i ∣ ∣ D ∣ H_A(D)=-\sum_{i=1}^n\frac{|D_i|}{|D|}log_2 \frac{|D_i|}{|D|} HA(D)=i=1nDDilog2DDi
以信息增益比为特征选择标准偏向取值较少的特征。当特征取值较少时 H A ( D ) H_A(D) HA(D)的值较小,因此其倒数较大。

信息增益比的本质
是在信息增益的基础之上乘上一个惩罚参数。特征个数较多时,惩罚参数较小;特征个数较少时,惩罚系数较大。

信息增益以及信息增益比实例

3.1.3 基尼指数(Gini index)—CART算法

Gini描述的是数据的纯度,与信息熵含义类似。
G i n i ( D ) = 1 − ∑ k = 1 n ( ∣ C k ∣ ∣ D ∣ ) 2 Gini(D)=1-\sum_{k=1}^n(\frac{|C_k|}{|D|})^2 Gini(D)=1k=1n(DCk)2
反映了从D中随机抽取两个样例,其类别标记不一致的概率。
Gini(D)越小,数据集D的纯度越高。

CART在每一次迭代中选择基尼指数最小的特征及其对应的切分点进行分类。但是与ID3、C4.5不同的是,CART是一棵二叉树,采用二元切割法,每一步将数据按特征A的取值切成两份,分别进入左右子树。特征A的Gini指数定义为:
G i n i ( D ∣ A ) = ∑ i = 1 n ∣ D i ∣ ∣ D ∣ G i n i ( D i ) Gini(D|A)=\sum_{i=1}^n\frac{|D_i|}{|D|}Gini(D_i) Gini(DA)=i=1nDDiGini(Di)
在候选属性集合中,选取那个使划分后基尼指数最小的属性

设对于给定的样本集合D,其基尼指数 G i n i ( D ) = 1 − ∑ k = 1 K ( ∣ C k ∣ ∣ D ∣ ) 2 Gini(D) = 1-\sum_{k=1}^K(\frac{|C_k|}{|D|})^2 Gini(D)=1k=1K(DCk)2
其中, C k C_k Ck是D中属于第k类的样本集合,K是类别个数。
如果样本集合D根据特征A是否取某一可能值a被分割成 D 1 D_1 D1 D 2 D_2 D2两个部分,则在特征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)
基尼指数表示集合D的不确定性,基尼指数Gini(D, A)表示经A=a分割后集合D的不确定性。基尼指数值越大,样本集合的不确定性也越大。

Gini指数计算实例

3.1.4 基尼指数 VS 熵 VS 分类错误率

基尼指数、熵、分类错误率三者之间的关系:

  • H ( X ) = − ∑ k = 1 K p k l n   p k H(X) = -\sum_{k=1}^Kp_kln\ p_k H(X)=k=1Kpkln pk,泰勒一阶展开可得到 1 − ∑ k = 1 K p k 2 1-\sum_{k=1}^K p_k^2 1k=1Kpk2,即信息熵 = Gini指数
3.1.5 RSS(误差平方)—回归树

假设一个回归问题,预估结果 y ∈ R y \in R yR,特征向量为 X = [ x 1 , x 2 , ⋯   , x p ] ∈ R X=[x_1, x_2, \cdots, x_p] \in R X=[x1,x2,,xp]R,回归书的2个步骤:

  1. 把整个特征空间X切分成J个没有重叠的区域, R 1 , R 2 , ⋯   , R J R_1, R_2, \cdots, R_J R1,R2,,RJ
  2. 其中区域 R J R_J RJ中的每个样本我们都给一样的预测结果 y ~ R j = 1 n ∑ y i \tilde{y}_{R_j} = \frac{1}{n}\sum y_i y~Rj=n1yi,其中n是 R J R_J RJ中的总样本数。
    R S S = ∑ j = 1 J ∑ i ∈ R j ( y i − y ~ R j ) 2 RSS= \sum_{j=1}^J\sum_{i \in R_j}(y_i-\tilde{y}{R_j})^2 RSS=j=1JiRj(yiy~Rj)2
    要使得回归最小即使得RSS最小。
    但是这个最小化和探索的过程,计算量是非常大的。
    可以采用探索式的递归二分来尝试解决这个问题。

3.2 决策树生成

3.2.1 ID3算法

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

步骤:

  1. 若D中所有实例属于同一类 C k C_k Ck,则T为单结点树,并将类 C k C_k Ck作为该结点的类标记,返回T;
  2. A = ∅ A=\empty A=,则T为单结点树,并将D中实例数最大的类 C k C_k Ck作为该结点的类标记,返回T;
  3. 否则,计算A中各特征对D的信息增益,选择信息增益最大的特征 A g A_g Ag
  4. 如果 A g A_g Ag的信息增益小于阈值 ε \varepsilon ε,则置T为单结点树,并将D中实例数量最大的类 C k C_k Ck作为该结点的类标记,返回T;
  5. 否则,对 A g A_g Ag的每一个可能值 a i a_i ai,依 A g = a i A_g=a_i Ag=ai将D分割为若干非空子集 D i D_i Di,将 D i D_i Di中实例数对大的类作为标记,构建子结点,由结点及其子结点构成树T,返回T;
  6. 对第 i i i个子结点,以 D i D_i Di为训练集,以 A − A g A-{A_g} AAg为特征集,递归地调用步1~步5,得到子树 T i T_i Ti,返回 T i T_i Ti

总结:
ID3采用信息增益作为评价标准,所以会出现一个问题:对可取值数目较多的属性有所偏好。
信息增益反映的是给定条件以后不确定性减少的程度,特征取值越多就意味着确定性更高,也就是条件熵越小,信息增益越大。在实际应用中是一个缺陷,好比对人分类,采用的特征为DNA,虽然分类能力强,但是泛化能力非常弱,出现过拟合的问题。

3.2.2 C4.5算法

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

步骤和上述的ID3算法类似,只有第三步不一样:
ID3算法是计算信息增益,选择信息增益最大的特征 A g A_g Ag;
而C4.5则是计算信息增益比,选择信息增益比最大的特征 A g A_g Ag

3.2.3 CART分类树算法

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

  1. 设结点的训练数据集为D,对每一个特征A,对其可能取的每个值a,根据样本点对A=a的测试为“是”或“否”,将D分割成 D 1 D_1 D1 D 2 D_2 D2两部分,并计算Gini(D, A)
  2. 在所有可能的特征A以及其所有可能的切分点a中,选择基尼指数最小的特征与最优切分点。依次从现结点生成两个子结点,将训练数据集依特征分配到两个子结点中。
  3. 对两个子结点递归地调用1和2,直至满足条件停止。
  4. 生成CART决策树T
3.2.4 CART回归树算法

递归二分:

  • 自顶向下的贪婪式递归方案
    • 自顶向下:从所有样本开始,不断从当前位置,把样本切分到2个分支里
    • 贪婪:每一次的划分,只考虑当前最优,而不回头考虑之前的划分
      最小二乘回归树算法:

输入:训练数据集D
输出:回归树 f ( x ) f(x) f(x)

  1. 选择最优切分变量 x ( j ) x^{(j)} x(j)与切分点 s s s j , s = a r g   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 ] j,s=arg \ \underset{(j,s)}{min} [\underset{c_1}{min} \sum_{x_i \in R_1(j,s)}(y_i-c_1)^2+ \underset{c_2}{min}\sum_{x_i\in R_2(j,s)}(y_i-c_2)^2] j,s=arg (j,s)min[c1minxiR1(j,s)(yic1)2+c2minxiR2(j,s)(yic2)2]
  2. 用最优切分变量 x ( j ) x^(j) x(j)与切分点s划分区域并决定相应的输出值
  3. 继续对两个子区域调用步骤1和2,直到满足停止条件
  4. 将输入空间划分为M个区域 R 1 , R 2 , ⋯   , R M R_1,R_2,\cdots,R_M R1,R2,,RM,生成决策树
    f ( x ) = ∑ m = 1 M c m I ( x ∈ R m ) f(x)=\sum_{m=1}^M c_mI(x \in R_m) f(x)=m=1McmI(xRm)
三种算法的区别
  • 从样本类型角度
    • ID3只能处理离散型变量,而C4.5和CARR都可以处理连续型变量
  • 从应用角度
    • ID3和C4.5只能用于分类任务,而CART可用于分类也可用于回归
  • 从实现细节、优化过程等角度看
    • ID3对样本特征缺失值比较敏感,而C4.5和CART可以对缺失值进行不同方式处理
    • ID3和C4.5可以在每个结点产生出多个分支,且每个特征在层级之间不会复用,而CART每个结点只会产生两个分支,最终形成一棵二叉树。
    • ID3和C4.5通过剪枝来权衡树的准确性和泛化能力,而CART直接利用全部数据发现所有可能的树结构进行对比

3.3 决策树剪枝

3.3.1 决策树的剪枝

决策树的剪枝通过极小化决策树整体的损失函数或代价函数来实现。
设树T的叶子结点个数为 ∣ T ∣ |T| T,t是树T的叶结点,该叶结点有 N t N_t Nt个样本点,其中k类的样本点有 N t k N_{tk} Ntk个, k = 1 , 2 , ⋯   , K k=1,2,\cdots,K k=1,2,,K H t ( T ) H_t(T) Ht(T)为叶结点t上的经验熵。则损失函数为: C α ( T ) = ∑ t = 1 ∣ T ∣ N t H t ( T ) + α ∣ T ∣ C_{\alpha}(T)=\sum_{t=1}^{|T|}N_tH_t(T)+\alpha|T| Cα(T)=t=1TNtHt(T)+αT
其中,经验熵为: H t ( T ) = − ∑ k N t k N t l o g   N t k N t H_t(T)=-\sum_k \frac{N_{tk}}{N_t}log\ \frac{N_{tk}}{N_t} Ht(T)=kNtNtklog NtNtk

树的剪枝算法:
输入:决策树T,参数 α \alpha α
输出:修剪后的子树T

  1. 计算每个结点的经验熵
  2. 递归地从树的叶结点向上回缩。设一组叶结点回缩到其父节点之前与之后的整体树分别是 T B T_B TB T A T_A TA,其对应的损失函数值分别是 C α ( T B ) C_{\alpha}(T_B) Cα(TB) C α ( T A ) C_{\alpha}(T_A) Cα(TA),如果 C α ( T A ) ≤ C α ( T B ) C_{\alpha}(T_A) \leq C_{\alpha}(T_B) Cα(TA)Cα(TB),则进行剪枝,即将父结点变为新的叶结点
  3. 返回第二步,直到不能继续为止,得到损失函数最小的子树T
3.3.2 回归树剪枝

如果让回归树充分“生长”,会有过拟合的风险。
解决方法:添加正则化项衡量
考虑剪枝后得到的子树 { T α } \{T_\alpha\} {Tα},其中 α \alpha α是正则化项的系数,当固定一个 α \alpha α后,最佳的 T α T_\alpha Tα就是使得下列式子值最小的子树。 ∑ m = 1 ∣ T ∣ ∑ x i ∈ R m ( y i − y R 2 ~ ) 2 + α ∣ T ∣ \sum_{m=1}^{|T|}\sum_{x_i\in R_m}(y_i-\tilde{y_{R_2}})^2+\alpha|T| m=1TxiRm(yiyR2~)2+αT
其中, ∣ T ∣ |T| T是回归树叶子结点的个数以及 α \alpha α可以通过交叉验证去选择

3.3.3 剪枝的分类

剪枝分为预剪枝和后剪枝。

  • 预剪枝:在决策树生成过程中,在每次划分时候,考虑是否带来决策树性能的提升。
    1. 当树到达一定深度的时候,停止树的生长
    2. 当到达当前结点的样本数量小于某个阈值的时候,停止树的生长
    3. 计算每次分裂对测试集的准确度提升,当小于某个阈值的时候,不能继续扩展
  • 后剪枝:先从训练集生成一棵完整的决策树,然后自底向上的对决策树进行剪枝,与预剪枝最大的不同就是:决策树是否生长完整。(著名的有CART的CCP【最小误差剪枝】)

CCP公式为: α = C ( t ) − C ( T t ) ∣ T t ∣ − 1 \alpha = \frac{C(t)-C(T_t)}{|T_t|-1} α=Tt1C(t)C(Tt)
其中, C ( t ) C(t) C(t)为剪枝后的损失; C ( T t ) C(T_t) C(Tt)为剪枝前的损失; ∣ T t ∣ |T_t| Tt为叶子结点的个数

决策树的生成是学习局部模型,后剪枝则是学习整体的模型。

两种剪枝策略的对比:

  • 后剪枝决策树通常比预剪枝决策树保留了更多的分支;
  • 后剪枝决策树的欠拟合风险很小,泛化性能往往优于预剪枝决策树;
  • 后剪枝决策树训练时间开销比未剪枝决策树和预剪枝决策树都要大的多
    剪枝链接1
    剪枝链接2

树模型的优缺点

优点

  • 容易解释
  • 对特征预处理要求少
    • 能处理离散值和连续值混合的输入(理论上,实际实现可具体工具包要求)
    • 对特征的单调变换不敏感(只与数据的排序有关)
    • 能自动进行特征选择
    • 可处理缺失数据
  • 可扩展到大数据规模

缺点

  • 正确率不高:建树过程过于贪心
    • 可作为Boosting的弱学习器(深度不太深)
  • 模型不稳定(方差大):输入数据小的变化会带来树结构的变化
    • Bagging:随机森林
  • 当特征数目相对样本数目太多时,容易过拟合

Scikit-Learn中的决策树

scikit-learn实现了CART算法的改进版本

  • 建树:穷举搜索所有特征所有可能的分裂点
  • 没有实现剪枝(采用交叉验证选择最佳的树的超参数)
  • 分类决策树:DecisionTreeClassifier
  • 回归决策树:DecisionTreeRegressor

决策树算法特有的参数:

  • criterion、splitter、max_features (策略)
  • max_depth、max_leaf_nodes (控制树的复杂度)
  • min_samples_split、min_samples_leaf、min_weight_fraction_leaf、min_impurity_decrease (控制树的复杂度,提早停止的条件)
  1. criterion:缺省’gini’(CART);entropy(C4.5)
  2. splitter:分裂方式:‘best’、‘random’。 best:适合样本量不大的时候,大的话推荐random。
  3. max_features:寻找最佳分裂点时,考虑的特征数目。如果小于50,用默认,否则可以考虑最大特征树,以控制决策树的生成时间。
  4. max_depth:在建树时不限制树的深度,直到每个叶子结点都是纯净的叶子结点的样本数目小于min_samples_split。样本量多特征多的情况下,推荐限制最大深度,常用的可以10~100
  5. max_leaf_nodes:最大叶子结点数目,如果不为None,忽略max_depth
  6. min_samples_split:对中间结点进行分裂的最小样本数。如果样本数量级非常大,则推荐增大该参数,default为2。如果10W样本,可以设置为10.
  7. min_samples_leaf:叶子结点包含的最小样本数。如果某叶子结点数目小于该参数,则会和兄弟结点一起被剪枝。min_samples_split约为min_samples_leaf的2倍。
  8. min_weight_fraction_leaf:叶子结点所有样本权重和最小值。如果某叶子结点样本权重和小于该参数,则会和兄弟结点一起被剪枝。
  9. min_impurity_decrease:结点分裂最小不纯度下降量。如果结点分裂带来的不纯下降度小于该阈值,则不会再分裂。
    10.presort:是否对数据进行预先排序,以便在fitting时加快最优划分。大数据集,使用Fasle;小数据集,使用True。

使用注意事项

  • 决策树很容易过拟合。一般来说,样本数比特征数多一些会比较容易建立健壮的模型
  • 决策树的数组使用的是numpy的float32类型,如果训练数据不是这样的格式,算法会先做copy再运行。
  • 如果输入的样本矩阵是稀疏的,推荐在模型训练前对训练数据调用csc_matrix稀疏化,在预测前对测试数据调用csr_matrix稀疏化。

ID3的代码参考

# ID3的特征只能是离散型
import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets
from math import log2
def createDataSet():
    """
    创建数据集
    """
    dataSet = [[1, 1, 'yes'],
               [1, 1, 'yes'],
               [1, 0, 'no'],
               [0, 1, 'no'],
               [0, 1, 'no']]
    labels = ['no surfacing', 'flippers']
    return dataSet, labels
dataSet, labels = createDataSet()
# print(dataSet)

def chacShannonEnt(dataSet):
    """
    计算经验熵
    param:
        dataSet: 数据集
    return:
        shannonEnt: 经验熵
    """
    numEntries = len(dataSet)  # 样本的总个数
    labelCounts = {}
    for featVec in dataSet:
        currentLabel = featVec[-1]
        if currentLabel not in labelCounts.keys():
            labelCounts[currentLabel] = 0
        labelCounts[currentLabel] += 1
    shannonEnt = 0.0
    for key in labelCounts.keys():
        prob = float(labelCounts[key]) / numEntries  # 每个类别的概率
        shannonEnt -= prob * log2(prob)
    return shannonEnt

# chacShannonEnt = chacShannonEnt(dataSet)
# print(chacShannonEnt)

def splitDataSet(dataSet, axis, value):
    """
    将数据集根据特征(父结点)生成两个子结点(数据集)
    param:
        dataSet: 待划分的数据集
        axis: 划分数据集的特征的索引
        value: 需要返回的特征值
    return:
        retDataSet: 返回的划分后的数据集
    """
    retDataSet = []
    for featVec in dataSet:
        if featVec[axis] == value:
            reducedFeatVec = featVec[:axis]  # 去掉axis特征
            reducedFeatVec.extend(featVec[axis+1:])
            retDataSet.append(reducedFeatVec)
    return retDataSet  # list

# retDateSet_test = splitDataSet(dataSet, 0, 1)
# print(retDateSet_test)

def chooseBestFeatureToSplit(dataSet):
    """
    选择最优特征
    param:
        dataSet: 数据集
    return:
        bestFeature: 最大信息增益的特征
    """
    numFeatures = len(dataSet[0]) - 1
    baseEntropy = chacShannonEnt(dataSet)  # 计算数据集的经验熵
    bestInfoGain = 0.0
    bestFeature = -1
    # 计算条件经验熵
    for i in range(numFeatures):  # 遍历所有特征
        featList = [example[i] for example in dataSet]
        uniqueVals = set(featList)
        newEntropy = 0.0
        for value in uniqueVals:
            subDataSet = splitDataSet(dataSet, i, value)  # 取出来含有第i个特征的值的样本
            prob = len(subDataSet) / float(len(dataSet))
            newEntropy += prob * chacShannonEnt(subDataSet)  # 计算信息增益
        infoGain = baseEntropy - newEntropy
        print('第{}个特征的最大增益为{:.3f}'.format(i, infoGain))
        if infoGain > bestInfoGain:
            bestInfoGain = infoGain
            bestFeature = i
    return bestFeature  # 返回该特征的索引(index)

import operator
def majorityCnt(classList):
    """
    统计结点中出现最多的标签
    param:
        classList: 结点中样本的标签列表
    return:
        sortedClassCount[0][0]: 样本中出现最多的标签
    """
    classCount = {}
    for vote in classList:
        if vote not in classCount.keys():
            classCount[vote] = 0
        classCount[vote] += 1
    # sortedClassCount = sorted(classCount.items(), key=lambda x:x[1], reverse=True)
    sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
    return sortedClassCount[0][0]

def createTree(dataSet, labels, featLabels):
    """
    构建递归决策树
    param:
        dataSet: 训练集
        labels: 的分类属性标签
        featLabels: 存储选择最优特征标签
    return:
        myTree: 决策树
    """
    classList = [example[-1] for example in dataSet]
    # 树的停止条件1:如果样本中所有的类别标签归属于一类,返回该结点的类别
    if classList.count(classList[0]) == len(classList):
        return classList[0]
    # 树的停止条件2:如果特征全部遍历完,返回类别出现最多的类别    
    if len(dataSet[0]) == 1:  # 特征全部被遍历完,只剩标签,即长度为1
        return majorityCnt(classList)  # 返回出现次数最多的类别

    bestFeat = chooseBestFeatureToSplit(dataSet)  # 获取最优特征索引
    bestFeatLabel = labels[bestFeat]  # 最优特征类别
    featLabels.append(bestFeatLabel)
    myTree = {bestFeatLabel:{}}  # 根据最优特征的标签生成树
    del(labels[bestFeat])  # 删除已经使用特征标签
    
    # 将子树再递归建树
    featValues = [example[bestFeat] for example in dataSet]
    uniqueVals = set(featValues)  # 最优特征的值
    for value in uniqueVals:
        subLabels = labels[:]
        # 递归调用函数createTree(),遍历特征,创建决策树
        myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value), 
                                       subLabels, featLabels)  # 标签类别 
    return myTree

def classify(inputTree, featLabels, testVec):
    """
    使用决策树进行分类
    param:
        inputTree: 已经生成的决策树
        featLabels: 存储选择的最优特征标签
        testVec: 测试数据列表
    return:
        classLabel: 分类结果
    """
    firstStr = next(iter(inputTree))  # 获取决策树的结点(即特征标签)
    secondDict = inputTree[firstStr]
    featIndex = featLabels.index(firstStr)
    # print(featIndex)
    for key in secondDict.keys():
        if testVec[featIndex] == key:
            if type(secondDict[key]).__name__ == 'dict':
                classLabel = classify(secondDict[key], featLabels, testVec)
            else:
                classLabel = secondDict[key]
    return classLabel

if __name__ == "__main__":
    dataSet, labels = createDataSet()
    featLabels = []
    myTree = createTree(dataSet, labels, featLabels)
    print(myTree)
    testVec = [0, 1]  # 测试数据
    result = classify(myTree, featLabels, testVec)
    if result == 'yes':
        print('鱼类')
    if result == 'no':
        print('非鱼类')

结果:

# ID3的特征只能是离散型...0个特征的最大增益为0.4201个特征的最大增益为0.1710个特征的最大增益为0.918
{'no surfacing': {0: 'no', 1: {'flippers': {0: 'no', 1: 'yes'}}}}
非鱼类
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值