决策树入门

  这一部分简单介绍了决策树的原理和sklearn的调用方法。

import warnings
warnings.filterwarnings('ignore')
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
import seaborn as sns
import mglearn

  决策树本质上是一系列if/else语句,通过不断询问特征来分类。
下载.png

构造决策树

  为了构造决策树,算法遍历所有可能询问的问题(称为测试),找出对于目标变量来说信息量最大的一个,将数据集分为两部分,重复此过程直到结束,其原理如下:

createBranch()方法:

检测数据集中的所有数据的分类标签是否相同:

If so return 类标签
Else:
    寻找划分数据集的最好特征(划分之后信息熵最小,也就是信息增益最大的特征)
    划分数据集
    创建分支节点
        for 每个划分的子集
            调用函数 createBranch(创建分支的函数)并增加返回结果到分支节点中
    return 分支节点

香农熵

如果数据集中的数据属于多个分类,对于每一个类别$x_i$其信息定义为$$l(x_i)=-log_2p(x_i)$$
香农熵定义为数据集中所有类别的信息的均值$$H=-\sum _{i=1}^np(x_i)log_2p(x_i)$$
其中$p(x_i)$为类别$x_i$在数据集中的概率

基尼系数

信息还可以使用基尼系数衡量$$gini = 1-\sum_{i=1}^np(x_i)^2$$

  直观来看,基尼系数表示的是从样本中选出两个样本点,两样本点属于不同类别的概率

控制决策树复杂度

  决策树可以通过不断的“询问”来区分每一条信息,但是,这会早成模型非常复杂并且过拟合,因此,我们能需要对决策树的复杂程度进行控制。控制的方法主要有两种:
  1)预剪枝:及早停止树的生长,如限制最大数深(max_depth)、最大叶节点数(max_leaf_nodes)、一个节点内最小数据量(min_samples_leaf)
  2)后剪枝:先构造树,再修剪,sklearn中未实现。

  按照惯例,首先在乳腺癌数据上训练一个决策树模型:

#使用乳腺癌数据集构造决策树
from sklearn.datasets import load_breast_cancer
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import train_test_split

cancer = load_breast_cancer()
X_train,X_test,y_train,y_test = train_test_split(cancer.data,cancer.target,stratify=cancer.target,random_state=42)

DTC =DecisionTreeClassifier().fit(X_train,y_train)

print('训练集精度:{}'.format(DTC.score(X_train,y_train)))
print('测试集精度:{}'.format(DTC.score(X_test,y_test)))
训练集精度:1.0
测试集精度:0.9300699300699301

  由于树深度很大,训练精度很高,但是测试精度较低,下面限制树深查看结果:

##使用乳腺癌数据集构造决策树
DTC =DecisionTreeClassifier(max_depth=3).fit(X_train,y_train)

print('训练集精度:{}'.format(DTC.score(X_train,y_train)))
print('测试集精度:{}'.format(DTC.score(X_test,y_test)))
训练集精度:0.9765258215962441
测试集精度:0.9440559440559441

分析决策树

from sklearn.tree import export_graphviz
export_graphviz(DTC,out_file='tree.dot',class_names=["malignant","benign"],feature_names=cancer.feature_names)
import graphviz
with open('tree.dot','r') as f:
    dot_graph = f.read()
graphviz.Source(dot_graph)

output_14_0.png

树的特征重要性

  树的特征重要性可以使用tree.feature_importance_查看,特征重要性基于决策树的实现算法使用gini或者香农熵进行计算,特征重要性的和为1,且每个特征的重要性位于[0,1]之间。

print('特征重要性:{}'.format(DTC.feature_importances_))
特征重要性:[0.         0.         0.         0.         0.         0.
 0.         0.         0.         0.         0.         0.0504697
 0.         0.01063382 0.         0.         0.         0.
 0.         0.         0.75793681 0.03465357 0.         0.
 0.         0.         0.01896644 0.12733965 0.         0.        ]


def plot_feature_importance_cancer(model):
    plt.subplots(figsize=(10,8))
    plt.barh(range(cancer.data.shape[1]),model.feature_importances_)
    plt.yticks(range(cancer.data.shape[1]),cancer.feature_names)
    plt.xlabel('Feature Importance')
    plt.ylabel('Feature Name')
plot_feature_importance_cancer(DTC)

output_18_0.png

优点、缺点、参数

参数:DecisionTreeClassifier(max_depth=5,max_leaf_nodes=5,min_samples_leaf=10),分别为最大树深,最大叶片数,最小叶片数据量。

优点:模型容易可视化,算法完全不受数据缩放影响,无需数据标准化以及归一化。

缺点:即使预剪枝也容易过拟合,泛化能力差。

决策树进阶

决策树原理之ID3算法

决策树ID3算法的信息论基础

  决策树实际上就是一系列的ifelse语句,实现决策树的算法关键是如何选择哪个特征先做ifelse,基于信息论的ID3算法实现了这一选择。

  前文提到过香农熵$$H=-\sum _{i=1}^np(x_i)log_2p(x_i)$$香农熵实际上度量的是数据集的不确定性,不确定性越大,香农熵越大。

  使用拉格朗日法求其最大值有:$$L(p_i,\lambda)=-\sum_{i=1}^n p_ilog_2p_i+\lambda(\sum_{i=1}^np_i-1)$$

$$\frac{\partial L}{p_i}=-\frac1{ln2}-log2p_i+\lambda=0$$

故有$$p_i=\frac 1n$$

  信息增益:对数据集进行一次划分后,划分后所得数据集的香农熵的均值与原数据集香农熵之差的绝对值。

如对数据集D按照特征A进行划分,特征A有$A_1 A_2 A_3$三个取值,分为三个数据集,对每个数据集计算香农熵,并以数据集所含数据量为权重计算加权平均值。

ID3算法原理

  ID3的算法思路如下:

  • 判断输入数据集D是否为同一类别,如果是,则返回单节点树T,标记类别为该类别。
  • 如果不是同一类别,则计算每一特征的信息增益,选择信息增益最大的特征进行划分,同时,删除该特征。
  • 对每一子节点重复以上两步直到结束。

  以上方法最终会得到纯的节点,这会导致模型复杂度很大,且容易过拟合,泛化能力差。改进的一种思路是设置继续划分的信息增益阈值:

  • 初始化信息增益的阈值ϵ
  • 判断样本是否为同一类输出$D_i$,如果是则返回单节点树T。标记类别为$D_i$
  • 判断特征是否为空,如果是则返回单节点树T,标记类别为样本中输出类别D实例数最多的类别。
  • 计算A中的各个特征(一共n个)对输出D的信息增益,选择信息增益最大的特征$A_g$
  • 如果Ag的信息增益小于阈值ϵ,则返回单节点树T,标记类别为样本中输出类别D实例数最多的类别。
  • 否则,按特征$A_g$的不同取值$A_{gi}$将对应的样本输出D分成不同的类别$D_i$。每个类别产生一个子节点。对应特征值为$A_{gi}$。返回增加了节点的数T。
  • 对于所有的子节点,令D=$D_i$,A=$A-A_g$递归调用2-6步,得到子树Ti并返回。

ID3算法的不足

原始的ID3算法虽然提出了新思路,但是还是有很多值得改进的地方。  

  • ID3没有考虑连续特征,比如长度,密度都是连续值,无法在ID3运用。这大大限制了ID3的用途。
  • ID3采用信息增益大的特征优先建立决策树的节点。很快就被人发现,在相同条件下,取值比较多的特征比取值少的特征信息增益大,比如一个训练集有10个样本,每个样本的特征A分别为1-10十个数字,如果按照特征A划分,则划分后的每个数据集只有一个样本,熵为0,信息增益达到最大,但是这样是没有意义的。

决策树原理之C4.5算法

C4.5是ID3的改进

  • 针对连续化数据的思路是离散化。如m个样本的连续特征A有m个,从小到大排列为$a_1,a_2,...,a_m$,则C4.5取相邻两样本值的平均数,一共取得m-1个划分点,对于这m-1个点,分别计算以该点作为二元分类点时的信息增益。选择信息增益最大的点作为该连续特征的二元离散分类点。比如取到的增益最大的点为$a_i$,则小于$a_i$的值为类别1,大于$a_i$的值为类别2。
  • 针对划分方法的改进是寻找另外的指标衡量。在这里,我们引入一个信息增益比变量$$Gainratio = \frac {\Delta}{IV(A)}$$$$IV(A) = -\sum\limits_{i=1}^{n}\frac{|D_i|}{|D|}log_2\frac{|D_i|}{|D|}$$

其中:

$\Delta$为信息增益

$IV(A)$为分裂信息(Split information)

$|D|$为样本数

$|D_i|$为特征$A_i$划分后的样本数

可以看出,当特征A的类别n增大时,$IV(A)$增大,从而促使$Gainratio$减小。

C4.5算法的不足

  • 由于决策树算法非常容易过拟合,因此对于生成的决策树必须要进行剪枝。剪枝的算法有非常多,C4.5的剪枝方法有优化的空间。思路主要是两种,一种是预剪枝,即在生成决策树的时候就决定是否剪枝。另一个是后剪枝,即先生成决策树,再通过交叉验证来剪枝。
  • C4.5生成的是多叉树,即一个父节点可以有多个节点。很多时候,在计算机中二叉树模型会比多叉树运算效率高。如果采用二叉树,可以提高效率。
  • C4.5只能用于分类,如果能将决策树用于回归的话可以扩大它的使用范围。
  • C4.5由于使用了熵模型,里面有大量的耗时的对数运算,如果是连续值还有大量的排序运算。如果能够加以模型简化可以减少运算强度但又不牺牲太多准确性的话,那就更好了。

决策树原理之CART(Classification and regression tree)分类回归树算法

  ID3和C4.5只能用于分类,CART不仅能够用于分类,还可以用于回归,并且,其使用了剪枝技术避免过拟合。

  决策树是一种贪心算法,它只在本节点做出最优选择,并不关心是不是全局最优。ID3每次都选取最优的特征分割数据,并按照特征类别划分,也就是说如果一个特征有5种取值,那么决策树就会有5个分叉,并且在使用该特征后删除特征,这导致数据切分过于快速,并且ID3无法处理连续数据,需要对数据进行离散化,但在离散化过程中,必然会损失一定的信息。

  与之相对的,有一种方法被称为二元切分法,它每次只把数据集划分为两部分,如果特征值等于给定值,则数据进入左部分,否则进入右部分。同时只需要将等于改为大于或小于就可以直接处理连续数据

CART的特征选择方法

  在ID3和C4.5中,我们使用香农熵(H)来选择特征,在CART中,我们使用前文提到过的基尼系数$$Gini(p) = \sum\limits_{k=1}^{K}p_k(1-p_k) = 1- \sum\limits_{k=1}^{K}p_k^2$$同理使用拉格朗日乘数法可以求得其最大值$$F(P)=\sum\limits_{k=1}^{K}p_k(1-p_k)+\lambda(\sum\limits_{k=1}^{K}p_k-1)$$
$$\frac {\partial F(P)}{\partial p_k}=-2p_k+\lambda=0$$
$$p_k=\frac 1K$$

可以看到,同香农熵一样,在各个类别平均分布时,Gini系数有最大值,也可以用来衡量数据集的不纯度。

同理,有数据集D在特征A的划分下的基尼系数$$Gini(D,A) = \frac{|D_1|}{|D|}Gini(D_1) + \frac{|D_2|}{|D|}Gini(D_2)$$

CART算法分类树原理

  • 计算数据集D的基尼系数,如果为0,则返回子树,当前节点停止递归
  • 计算当前节点现有的各个特征的各个特征值对数据集D的基尼系数。对于离散值,特征A的实例是否为$A_i$为一个遍历点,使用二分树。对于连续值,将特征值排序后遍历任意两个连续值的均值计算基尼系数。
  • 在计算出来的各个特征的各个特征值对数据集D的基尼系数中,选择基尼系数最小的特征A和对应的特征值$A_i$。根据这个最优特征和最优特征值,把数据集划分成两部分D1和D2,同时建立当前节点的左右节点,做节点的数据集D为D1,右节点的数据集D为D2.
  • 重复以上步骤。

  CART分类与ID3的不同之处在于使用Gini并且使用二叉树。

CART算法回归树原理

  CART回归树与分类树十分类似,其建立和预测的区别主要有下面两点:

  • 特征选择方法不同

  Gini系数显然不适用于连续值的预测,在评价划分优劣时,使用划分所得集合的数据的方差加权,加权值最小的划分是最优划分。$$\underbrace{min}_{A,s}\Bigg[\sum\limits_{x_i \in D_1(A,s)}(y_i - c_1)^2 + \sum\limits_{x_i \in D_2(A,s)}(y_i - c_2)^2\Bigg]$$

  • 决策树建立后做预测的方式不同

  分类树预测时,使用节点中多数样本所在的类别作为预测类别,回归树使用节点的平均值或中位数作为预测值。

CART算法的剪枝

  树的节点过多表明结果可能过拟合,可以通过交叉验证来检验模型是否过拟合,通过降低模型复杂度来避免树模型过拟合的方法叫做剪枝(pruning),剪枝分为预剪枝和后剪枝。

预剪枝

  预剪枝是一种提前结束决策树生长的方法。预剪枝算法的原理是,在第k层节点处选择特征A进行划分,基于以上理论划分后获得k+1层节点,使用划分后获得k+1层节点的模型以及只有k层节点的模型分别对预先划分出的验证集进行预测,如果划分后验证集的精度大于未划分,则该节点划分保留,否则,不在该节点处划分。

  预剪枝是一种基于贪心算法的方法,它关注某个节点在下一次划分后是否会提高验证集准确率,如果不能提高,那将停止该节点的生长。但是,如果k+1层划分不能提升准确率,但是该节点下k+2层可以提高准确率,使用预剪枝就会带来欠拟合的风险。

后剪枝

  后剪枝是一种先构建完整的决策树,再去除部分节点的方法。其方法是,构建一颗决策树,假设决策树深度为k,则第k层为叶节点,选择第k-1层的节点,去除其第k层划分,如果验证集精度上升,则删除该划分,否则保留。

  不难看出,后剪枝比预剪枝更为优越,通常它会保留更多的节点并具有更强的泛化能力,但是,由于需要构建完整的树并且验证多个节点,模型的复杂程度会更高。

CART算法小结

算法 支持模型 树结构 特征选择 连续值处理 缺失值处理 剪枝
ID3 分类 多叉树 信息增益 不支持 不支持 不支持
C4.5 分类 多叉树 信息增益比 支持 支持 支持
CART 分类,回归 二叉树 基尼系数,均方差 支持 支持 支持

  CART算法对ID3进行了全方位的改进,但它仍然有很多缺点和改进空间。

  首先,在此之前我们的决策树都是使用单个变量进行划分,划分结果是如下图彩色图片所示的平行于坐标轴的划分,这是因为我们的划分条件是X[i]>k,但是如果在叶结点中使用一个线性分类器就可以获得如下黑白图片所示的曲线边界。

  其次,少量的样本变动会使得树的结构变化极大,即鲁棒性差,可以通过决策树的集成来避免。

from sklearn.datasets import make_moons
X,y = make_moons(n_samples=100,noise=0.25,random_state=3)
X_train,X_test,y_train,y_test = train_test_split(X,y,stratify=y,random_state=42)

DSC = DecisionTreeClassifier().fit(X_train,y_train)
mglearn.plots.plot_tree_partition(X_train,y_train,DSC)

output_56_1.png

决策树算法小结

以下总结来自于sklearn英文文档。

优点:

  • 简单直观,生成的决策树很直观。
  • 基本不需要预处理,不需要提前归一化,处理缺失值。
  • 使用决策树预测的代价是$O(log_2m)$。 m为样本数。
  • 既可以处理离散值也可以处理连续值。很多算法只是专注于离散值或者连续值。
  • 可以处理多维度输出的分类问题。
  • 相比于神经网络之类的黑盒分类模型,决策树在逻辑上可以得到很好的解释
  • 可以交叉验证的剪枝来选择模型,从而提高泛化能力。
  • 对于异常点的容错能力好,健壮性高。

缺点:

  • 决策树算法非常容易过拟合,导致泛化能力不强。可以通过设置节点最少样本数量和限制决策树深度来改进。
  • 决策树会因为样本发生一点点的改动,就会导致树结构的剧烈改变。这个可以通过集成学习之类的方法解决。
  • 寻找最优的决策树是一个NP难的问题,我们一般是通过启发式方法,容易陷入局部最优。可以通过集成学习之类的方法来改善。
  • 有些比较复杂的关系,决策树很难学习,比如异或。这个就没有办法了,一般这种关系可以换神经网络分类方法来解决。
  • 如果某些特征的样本比例过大,生成决策树容易偏向于这些特征。这个可以通过调节样本权重来改善。

Scikit-Learn决策树使用小结

简介

  在开头入门部分我们已经简单介绍了sklearn的用法,sklearn使用的CART树方法,既可以分类又可以回归。分类是使用DecisionTreeClassifier,回归使用DecisionTreeRegressor。

回归分类树参数对比

参数 DecisionTreeClassifier DecisionTreeRegressor
特征选择标准criterion 可以使用"gini"或者"entropy",前者代表基尼系数,后者代表信息增益。一般说使用默认的基尼系数"gini"就可以了,即CART算法。除非你更喜欢类似ID3, C4.5的最优特征选择方法。   可以使用"mse"或者"mae",前者是均方误差(mean squared error)即误差平方和的平均数,后者是平均绝对误差(mean absolute error)误差绝对值的平均数。推荐使用默认的"mse"。一般来说"mse"比"mae"更加精确。除非你想比较二个参数的效果的不同之处。
特征划分点选择标准splitter 可以使用"best"或者"random"。前者在特征的所有划分点中找出最优的划分点。后者是随机的在部分划分点中找局部最优的划分点。 默认的"best"适合样本量不大的时候,而如果样本数据量非常大,此时决策树构建推荐"random" 
划分时考虑的最大特征数max_features 可以使用很多种类型的值,默认是"None",意味着划分时考虑所有的特征数;如果是"log2"意味着划分时最多考虑log_2N个特征;如果是"sqrt"或者"auto"意味着划分时最多考虑sqrt{N}个特征。如果是整数,代表考虑的特征绝对数。如果是浮点数,代表考虑特征百分比,即考虑(百分比~~~~xN)取整后的特征数。其中N为样本总特征数。 一般来说,如果样本特征数不多,比如小于50,我们用默认的"None"就可以了,如果特征数非常多,我们可以灵活使用刚才描述的其他取值来控制划分时考虑的最大特征数,以控制决策树的生成时间。
决策树最大深max_depth  决策树的最大深度,默认可以不输入,如果不输入的话,决策树在建立子树的时候不会限制子树的深度。一般来说,数据少或者特征少的时候可以不管这个值。如果模型样本量多,特征也多的情况下,推荐限制这个最大深度,具体的取值取决于数据的分布。常用的可以取值10-100之间。
内部节点再划分所需最小样本数min_samples_split 这个值限制了子树继续划分的条件,如果某节点的样本数少于min_samples_split,则不会继续再尝试选择最优特征来进行划分。 默认是2.如果样本量不大,不需要管这个值。如果样本量数量级非常大,则推荐增大这个值。我之前的一个项目例子,有大概10万样本,建立决策树时,我选择了min_samples_split=10。可以作为参考。
叶子节点最少样本数min_samples_leaf  这个值限制了叶子节点最少的样本数,如果某叶子节点数目小于样本数,则会和兄弟节点一起被剪枝。 默认是1,可以输入最少的样本数的整数,或者最少样本数占样本总数的百分比。如果样本量不大,不需要管这个值。如果样本量数量级非常大,则推荐增大这个值。之前的10万样本项目使用min_samples_leaf的值为5,仅供参考。
叶子节点最小的样本权重和min_weight_fraction_leaf 这个值限制了叶子节点所有样本权重和的最小值,如果小于这个值,则会和兄弟节点一起被剪枝。 默认是0,就是不考虑权重问题。一般来说,如果我们有较多样本有缺失值,或者分类树样本的分布类别偏差很大,就会引入样本权重,这时我们就要注意这个值了。
最大叶子节点数max_leaf_nodes  通过限制最大叶子节点数,可以防止过拟合,默认是"None”,即不限制最大的叶子节点数。如果加了限制,算法会建立在最大叶子节点数内最优的决策树。如果特征不多,可以不考虑这个值,但是如果特征分成多的话,可以加以限制,具体的值可以通过交叉验证得到。
类别权重class_weight 指定样本各类别的的权重,主要是为了防止训练集某些类别的样本过多,导致训练的决策树过于偏向这些类别。这里可以自己指定各个样本的权重,或者用“balanced”,如果使用“balanced”,则算法会自己计算权重,样本量少的类别所对应的样本权重会高。当然,如果你的样本类别分布没有明显的偏倚,则可以不管这个参数,选择默认的"None"  不适用于回归树
节点划分最小不纯度min_impurity_split  这个值限制了决策树的增长,如果某节点的不纯度(基尼系数,信息增益,均方差,绝对差)小于这个阈值,则该节点不再生成子节点。即为叶子节点 。
数据是否预排序presort 这个值是布尔值,默认是False不排序。一般来说,如果样本量少或者限制了一个深度很小的决策树,设置为true可以让划分点选择更加快,决策树建立的更加快。如果样本量太大的话,反而没有什么好处。问题是样本量少的时候,我速度本来就不慢。所以这个值一般懒得理它就可以了。

除了这些参数要注意以外,其他在调参时的注意点有:

  • 当样本少数量但是样本特征非常多的时候,决策树很容易过拟合,一般来说,样本数比特征数多一些会比较容易建立健壮的模型
  • 如果样本数量少但是样本特征非常多,在拟合决策树模型前,推荐先做维度规约,比如主成分分析(PCA),特征选择(Losso)或者独立成分分析(ICA)。这样特征的维度会大大减小。再来拟合决策树模型效果会好。
  • 推荐多用决策树的可视化,同时先限制决策树的深度(比如最多3层),这样可以先观察下生成的决策树里数据的初步拟合情况,然后再决定是否要增加深度。
  • 在训练模型前,注意观察样本的类别情况(主要指分类树),如果类别分布非常不均匀,就要考虑用class_weight来限制模型过于偏向样本多的类别。
  • 决策树的数组使用的是numpy的float32类型,如果训练数据不是这样的格式,算法会先做copy再运行。
  • 如果输入的样本矩阵是稀疏的,推荐在拟合前调用csc_matrix稀疏化,在预测前调用csr_matrix稀疏化。

HH丶丶
29 声望8 粉丝