决策树

决策树(descision tree)(分类树)

1.决策树的基本概念

1.1 概念

决策树是一种基本的分类于回归方法,在分类问题中,表示基于特征对实例进行分类的过程。

1.2 决策树的三个基本步骤

  • 特征选择
  • 决策树的生成
  • 决策树的修剪

1.3 用决策树分类

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

其中圆点是内部节点,方框是叶节点

关键概念:结点

  1. 根节点:没有进边,有出边,包含最初的,针对特征问题提问
  2. 中间节点:即有进边也有出边,进边只有一条,出边可以有很多条,都是针对特征问题提问
  3. 叶子节点:有进边,没有出边,每一个叶子节点都是一个类别的标签

1.4 决策树的其他信息

  • 决策树学习的目标:根据训练数据构成一个模型,然后对未知数据能够进行合理的分类
  • 决策树的本质:从训练集中归纳出一组分类规则,或者说是由训练数据集估计条件概率模型
  • 决策树学习的损失函数:正则化的极大似然函数
  • 决策树学习的测试:最小化损失函数
  • 决策树学习的目标:在损失函数的意义下,选择最优决策树的问题
  • 决策树原理和问答猜测结果游戏相似,根据一系列数据,然后给出游戏的答案。(就是一直问问题)

1.5 决策树算法核心问题

  1. 如何从数据表中找出最佳节点和最佳分枝?
  2. 如何让决策树停止生长,防止过拟合?

2. 决策树的构造

2.1 决策树构造步骤

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

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

2.2 决策树的特点

  • 优点:计算复杂程度不高,输出结果好理解,中间值缺失不敏感,可以处理不相关特征数据。
  • 缺点:可能会产生过度匹配的问题。
  • 使用数据类型:数值型和标签型。

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

2.3 决策树伪代码

创建分支的伪代码createBranch()

检测数据集中每一个子项是否属于同一类:

if so return 类标签
else
    寻找划分数据集的最好特征
    划分数据集
    创建分支节点
        for 每个划分的子集
            调用createBranch()并且返回结果到分支节点中
        return 分支节点

2.4 信息增益

2.4.1 简单概述

划分数据集的大原则是:将无序数据变得更加有序,信息论是量化处理信息得分支科学,在划分数据集前后信息发生得变化称为信息增益获得信息增益最高的特征就是最好的选择,集合信息的度量方式称为:香农熵,或者简称为

2.4.2 结合实例

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

特征选择就是决定用哪个特征来划分特征空间

第一个图的根节点的特征是年龄,有3个取值,不同的取值有不同的节点。第二个图的根节点的特征是工作,有2个取值,不同的取值有不同的节点。两个决策树都可以延续下去,但是,我们应该选取那一个特征更好一些呢?一般来说,如果一个特征具有更好的分类能力,或者说,按照这个特征将数据集分割的子集,使得各个子集在当前条件下有最好的分类,那么就更应该选择这个特征。

2.4.3 信息增益和经验熵

什么是信息增益?在划分数据集之前之后信息发生的变化成为信息增益,知道如何计算信息增益,我们就可以计算每个特征值划分数据集获得的信息增益,获得信息增益最的特征就是最好的选择。

熵定义为信息的期望,在待分类的事物可能划分在多个类中,则$x_{i}$的信息定义为:

$$ l(x_{i})=-log_{2}p(x_i) $$

其中$p(x_{i})$是该分类的概率。

为了计算熵,我们需要计算所有类别所有可能值所包含的信息期望值,通过下式得到:

$$ H=-\sum_{i=1}^{n}p(x_{i})log_{2}p(x_{i}) $$

其中n是分类的数目,n为分类数目,熵越大,随机变量的不确定性就越大。

当熵中的概率是由数据计算的时候,我们就称为经验熵。数据计算就是比如有10个数据,一共有两个类别,A类和B类。其中有7个数据属于A类,则该A类的概率即为十分之七。其中有3个数据属于B类,则该B类的概率即为十分之三。浅显的解释就是,这概率是我们根据数据数出来的。我们定义贷款申请样本数据表中的数据为训练数据集D,则训练数据集D的经验熵为H(D),|D|表示其样本容量,及样本个数。假设共有k个类$C_{k}, k=1,2,3..,k,|C_{k}|$为属于类$C_{k}$的样本个数,所以经验熵公式可以写为:

$$ H(D)=-\sum_{k=1}^{K}\frac{|C_{k}|}{|D|}log_{2}\frac{|C_{k}|}{|D|} $$

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

$$ H(D)=-\frac{9}{15}log_{2}\frac{9}{15}-\frac{6}{15}log_{2}\frac{6}{15}=0.971 $$

所以,经计算可知,数据集D的经验熵H(D)为0.971

2.4.4 信息增益和条件熵

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

条件熵H(Y|X)表示在已知随机变量X的条件下随机变量Y的不确定性,定义X给定条件下Y的条件概率分布的熵对X的数学期望:

$$ H(Y|X)=\sum_{i=1}^{n}p_{i}H(Y|X=x_{i}) $$

其中,$p_{i}=p(X=x_{i})$

当熵和条件熵中的概率由数据估计(特别是极大似然估计)得到时,所对应的分别为经验熵和经验条件熵,此时如果有0概率,则令$0log0=0$

信息增益:信息增益是相对于特征而言的。所以,特征A对训练数据集D的信息增益$g(D,A)$,定义为集合D的经验熵H(D)与特征A给定条件下集合D的经验条件熵$H(D|A)$之差,即:

$$ g(D,A)=H(D)-H(D|A) $$

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

信息增益值的大小相对于训练数据集而言的,并没有绝对意义,在分类问题困难时,也就是说在训练数据集经验熵大的时候,信息增益值会偏大,反之信息增益值会偏小,使用信息增益比可以对这个问题进行校正,这是特征选择的另一个标准。

信息增益比:特征A AA对训练数据集D的信息增益比$g_{R}(D,A)$定义为其信息增益$g(D,A)$与训练数据集D关于特征A的经验熵之比:

$$ g_{R}(D,A)=\frac{g(D,A)}{H\_A(D)} $$

3. sklearn中的决策树

3.1 模块sklearn.tree

sklearn中的决策树都在tree这个模块下,这个模块包含五个类:

tree.DecisionTreeClassifier 分类树
tree.DecisionTreeRegressor 回归树
tree.expeort_graphiz 将决策树导出为DOT格式,画图
tree.ExtraTreeClassifier 高版本分类树
tress.ExtraTreeRegressor 高版本回归树

3.2 sklearn的基本建模流程

  1. 实例化,建立评估模型对象(实例化时需要使用的参数)
  2. 通过模型接口训练模型(将训练样本放入其中)
  3. 通过模型接口提取需要的信息

这个流程中,分类树对应代码如下:

# 导入需要的模块
from sklearn import tree

# 实例化
clf = tree.DecisionTreeClassifier()
# 导入训练集数据,用来训练模型
clf = clf.fit(x_train, y_train)
# 导入测试集数据,然后通过接口获得想要的信息
result = clf.score(x_test, y_test)

3.3 重要参数

3.3.1 criterion

为了要将表格转化为一棵树,决策树要找到最佳节点和最佳的分枝方法,对分类树来说,衡量这个算法的叫做“不纯度”。通常来说,不纯度越低,决策树对训练集的拟合越好,目前使用的决策树算法在分枝上大都是围绕某个不纯度相关指标的最优化上。

树中的每一个节点都有一个不纯度,并且子节点的不纯度一定低于父节点的不纯度。

criterion这个参数决定不纯度的计算方法。sklearn提供了两种选择:

  1. 输入‘entropy’,使用信息熵
  2. 输入‘gini’,使用基尼系数

$$ Entropy(t)=-\sum_{i=0}^{c-1}p(i|t)log_{2}p(i|t) $$

$$ Gini(t)=1-\sum_{i=0}^{c-1}p(i|t)^2 $$

其中$t$代表给定的节点(上述中的数据集D),$i$代表标签的任意分类,$P(i|t)$代表标签分类$i$在节点$t$上所占的比例。sklearn实际计算的是基于信息熵的信息增益,即是父节点的信息熵和子节点的信息熵之差。

信息熵对不纯度更加敏感,对不纯度的惩罚最强。但是在实际应用中,信息熵和基尼系数基本相同。信息熵涉及到了对数,所以它的计算会比基尼系数要慢一些。信息熵更敏感,所以信息熵作为指标时,决策树会更加“精细”,因此对于高维数据或者噪声很多的数据时,防止过拟合,基尼系数会比较好,但并不是绝对的。

参数 criterion
如何影响模型? 确定不纯度的计算方法,找出最佳节点和最佳分枝,不纯度越低,决策树的拟合效果越好
可能的输入有那些? 不填默认基尼系数,填写gini使用基尼系数,entropy是使用信息熵
怎么样选取参数? 通常使用基尼系数
数据维度较大,噪声大用基尼系数
维度低,数据比较清晰,两者差不多
决策树拟合程度不够时,用信息熵
两个都试试

3.3.2 基本流程概括

  1. 计算全部特征党的不纯度指标
  2. 选取不纯度指标最优的特征来分枝
  3. 在第一个特征的分枝下,计算全部特征的不纯度
  4. 选取指标最优的特征继续分枝

知道没有特征可用,或整体的不纯度指标已经最优,决策树就会停止生长。

3.3.3 sklearn实例和一些问题

以sklearn中的红酒数据集为例

数据的导入与训练

# 对模块和数据的导入
from sklearn import tree
from sklearn.datasets import load_wine
# 这个包是用来分割数据的
from sklearn.model_selection import train_test_split
import pandas as pd

# 导入数据
wine = load_wine()

# 将导入的数据合并成表
pd.concat([pd.DataFrame(wine.data), pd.DataFrame(wine.target)], axis=1)

# 注意前面数据所放的位置,不要弄错
Xtrain, Xtest, Ytrain, Ytest = train_test_split(wine.data, wine.target, test_size=0.3)

clf = tree.DecisionTreeClassifier(criterion="entropy")
clf = clf.fit(Xtrain, Ytrain)
score = clf.score(Xtest, Ytest)   # 返回预测的准确度accuracy

画决策树

import graphviz

feature_names = ['酒精', '苹果酸', '灰', '灰的碱性', '煤', '总酚', '类黄酮', '非黄烷类酚类', '花青素', '颜色强度'
                , '色调', 'od280/od315稀释葡萄酒', '脯氨酸']

dot_data = tree.export_graphviz(clf
                                , feature_names = feature_names
                                , class_names=["琴酒", "雪莉", "贝尔摩德"]
                                , filled = True
                                , rounded =True
                                    
)

graph = graphviz.Source(dot_data)
graph

数据特征的重要程度

clf.feature_importances_
[*zip(feature_names, clf.feature_importances_)]

我们在已经了解一个参数的情况下,建立了一个完整的决策树,但是建立模型,score会还是有波动,同样的数据,不同的训练,但是结果都不一样。他为什么这么不稳定呢?时间上是优化每一个节点,得到一个最优化的决策树,但是最优的节点一定能保证最优的树嘛?就比如,最好的种子一定能种出最好的苹果树嘛?集成算法就被用来解决这个问题:sklearn表示,既然每一棵树都不能保证最优,那就建立很多个不同的树,然后从中挑选最好的。(用很多的好种子去种树,然后把得到的树中最好的结果拿出来。)怎么样从一个数据集中建不同的树?在每次分枝的时候,不使用全部的特征,而是随机选取了一部分特征,从中选取不纯度相关指标最优的最为分枝用的节点。这样,树也就不同了。

3.3.4 random_state & splitter

  • random_state是用来设置分枝中的随机模式的参数,默认为None,在高纬度时的随机性会更加的明显,低纬度数据集比如鸢尾花,而随机性几乎不会出现。输入任意一个整数,会生长出同一棵树,让模型稳定下来。
  • splitter也是控制决策树中的随机项的,有两种输入值,输入“best”,决策树在分枝时,虽然随机,但是它会选择更重要的特征进行分枝(feature_importances_的结果),输入“random”,分枝会更加的随机,树会更深,对数据的拟合程度会降低,这也是防止过拟合的方式。
# 设置一个随机数种子
clf = tree.DecisionTreeClassifier(criterion="entropy", random_state=0)
clf = clf.fit(Xtrain, Ytrain)
score = clf.score(Xtest, Ytest)
score
# 加入splitter参数
clf = tree.DecisionTreeClassifier(criterion="entropy", 
                                                    random_state=0,
                                                     splitter="random")
clf = clf.fit(Xtrain, Ytrain)
score = clf.score(Xtest, Ytest)
score

import graphviz

feature_names = ['酒精', '苹果酸', '灰', '灰的碱性', '煤', '总酚', '类黄酮', '非黄烷类酚类', '花青素', '颜色强度'
                , '色调', 'od280/od315稀释葡萄酒', '脯氨酸']

dot_data = tree.export_graphviz(clf
                                , feature_names = feature_names
                                , class_names=["琴酒", "雪莉", "贝尔摩德"]
                                , filled = True
                                , rounded =True
                                    
)

graph = graphviz.Source(dot_data)
graph

4. 决策树的构建

4.1 ID3算法

ID3算法的核心是在决策各个结点上对于信息增益准则选择特征,递归构建决策树

具体方法是:

  1. 从根结点(root node)开始,对结点计算所有可能的特征的信息增益,选择信息增益最大的特征作为结点的特征。
  2. 由该特征的不同取值建立子节点,再对子结点递归地调用以上方法,构建决策树;直到所有特征的信息增益均很小或没有特征可以选择为止。
  3. 最后得到一个决策树。

ID3相当于用极大似然法进行概率模型的选择

算法步骤:

  • 输入:训练数据集是D,特征集是A,阈值是$\varepsilon$
  • 输出:决策树T
  • (1):如果D中所有实例属于同一类$C_{k}$ ,则T为单结点树,并将类$C_{k}$ 作为该节点的类标记,返回T
  • (2):如果A=$\emptyset$,则T为单节点树,并将D中实例树最大的类$C_{k}$ 作为该节点的类标记,返回T
  • (3):否则计算A中的各特征对D的信息增益,选择信息增益最大的特征$A_{g}$
  • (4):如果$A_{k}$的信息增益小于阈值$\varepsilon$,则置T为单节点树,并将D中实例树最大的类$C_{k}$ 作为该节点的类标记,返回T
  • (5):否则,对$A_{g}$的每一个可能取值$a_{i}$,依$A_{g}=a_{i}$将D分割为若干非空子集$D_{i}$,将$D_{i}$中实例树最大的类作为标记,构建子节点,由节点及子节点构成树T,返回T;
  • (6):对第$i$个子节点,以$D_{i}$为训练集,以$A-A_{g}$为特征集,递归调用第(1)到第(5)步,得到子树$T_{i}$,然后返回$T_{i}$。

4.2 C4.5的生成算法

跟ID3相比,将信息增益比作为选择特征的标准。

5.决策树的剪枝

决策树生成算法递归的产生决策树,直到不能继续下去为止,这样产生的树往往对训练数据的分类很准确,但对未知测试数据的分类缺没有那么精确,即会出现过拟合现象。过拟合产生的原因在于在学习时过多的考虑如何提高对训练数据的正确分类,从而构建出过于复杂的决策树,解决方法是考虑决策树的复杂度,对已经生成的树进行简化。

5.1 基本解释

从已经生成的树上裁掉一些子树或叶节点,并将其根节点或父节点作为新的叶子节点,从而简单分类树模型。

5.2 实现方式

极小化决策树整体的损失函数或代价函数来实现

决策树学习的损失函数定义为:

$$ C_{\alpha}(T)=\sum_{t=1}^{|T|}N_{t}H_{t}(T)+\alpha|T| $$

其中:

参数 意义
T 表示这棵树的叶子节点
$H_{t}(T)$ $H_{t}(T)=-\sum_{k}\frac{N_{tk}}{N_{t}}log\frac{N_{tk}}{N_{t}}$
表示第$t$个叶子的熵
$N_{t}$ 表示该叶子所含的训练样例的个数
$\alpha$ 惩罚系数

因为有:

$$ C(T)=\sum_{t=1}^{T}N_{t}H_{t}(T)=-\sum_{t=1}^{|T|}\sum_{k=1}^{k}N_{tk}log\frac{N_{tk}}{N_{t}} $$

所以有:

$$ C_{\alpha}(T)=C(T)+\alpha|T| $$

其中参数:

参数 意义
$C(T)$ 表示模型对训练数据的预测误差,即模型与训练数据的拟合程度。
$\alpha$ 参数$\alpha \geq 0$控制两者之间的影响,较大的$\alpha$促使选择简单的模型(树),较小的$\alpha$促使选择较为复制的模型(树),$\alpha=0$意味着只考虑模型与训练数据的拟合程度,不考虑模型的复杂程度。

剪枝就是当$\alpha$确定时,选择损失函数最小的模型,即损失函数最小的子树。

  • 当$\alpha$值确定时,子树越大,往往与训练数据拟合越好,但是模型的复杂度越高
  • 子树越小,模型的复杂程度越低,往往与训练数据的拟合不好
  • 损失函数正好表示了对两者的平衡

损失函数认为对于每个分类终点(叶子节点)的不确定性程度就是分类的损失因子,而叶子节点的个数是模型的复杂程度,作为惩罚项,损失函数的第一项是样本的训练误差,第二项是模型的复杂度。如果一棵子树的损失函数值越大,说明这棵子树越差,因此我们希望让每一棵子树的损失函数值尽可能得小,损失函数最小化就是用正则化的极大似然估计进行模型选择的过程。

决策树的剪枝过程(泛化过程),就是从叶子节点开始递归,记其父节点将所有子节点回缩后的子树为

$T_{b}$(特质值比例所占最短的那个),未回缩的子树为$T_{a}$,如果$C_{\alpha}(T_{a}) \geq C_{\alpha}(T_{b})$说明回缩后使损失函数减小了,那么这棵子树就应该回缩,递归直到无法回缩为止。可以看出,决策树的生成只是考虑通过提高信息增益对训练数据进行更好的拟合,而决策树剪枝通过优化损失函数还考虑了减小模型复杂度。

5.3 预剪枝和后剪枝

  • 预剪枝是指在决策树的生成过程中,对每个节点在划分前先进行评估,若当前的划分不能带来泛化性能的提升,则停止划分,并将当前节点标记为叶节点。
  • 后剪枝是指先从训练集生成一颗完整的决策树,然后自底向上对非叶节点进行考察,若将该节点对应的子树替换为叶节点,能带来泛化性能的提升,则将该子树替换为叶节点。
  • 那么怎么来判断是否带来泛化性能的提升那?最简单的就是留出法,即预留一部分数据作为验证集来进行性能评估。

6. sklearn中的剪枝参数调优

在不加限制的条件下,一棵决策树会生长到衡量不纯度指标最优,或者没有更多的特征可用为止,这样的决策树往往会过拟合,这就是说,它会在训练集上表现很好,在测试集上却表现不太行。我们收集的数据不可能与整体的状况完全一致,因此当一棵决策树对训练数据有了很好的解释性,也就是说它找出的规则必然包括了训练样本中的噪声,并使它对未知的数据拟合程度不足。

就比如说我们的树对训练集的拟合程度是1.0,但是对测试集只有的拟合程度只有80左右,说明它对训练集过拟合了,融入了噪声等

6.1 剪枝参数

为了让决策树有更好的泛化性,我们要对决策树进行剪枝。剪枝策略对决策树的影响巨大,正确的剪枝策略是优化决策树算法的核心

6.1.1 max_depth

限制树的最大深度,超过设点的深度的树枝全部被剪掉,在高纬度低样本量时非常有效果。决策树多生长一层,对样本量的需求会更增加一倍,以限制树深度能够有效地限制过拟合。在集成算法中也非常实用。在实际中,可以先从3开始,根据结果再决定是否增加深度。也就是说最终结果只有max_depth层,超过这个数值的层将会被全部删除。

clf = tree.DecisionTreeClassifier(criterion="entropy", 
                                  random_state=0,
                                  splitter="random",
                                  max_depth=3)
clf = clf.fit(Xtrain, Ytrain)
score = clf.score(Xtest, Ytest)
score

这个结果跟原来的一样,说明,原先的决策树3层往后是多余的,更推荐使用这个,计算少,正确率高。

6.1.2 min_samples_leaf

min_samples_leaf限定后,一个节点在分枝后,每个子节点必须包含至少min_samples_leaf个训练样本,否则该该分枝可能就不会发生,或者,分枝会朝着满足每个节点都包含样本个数达到min_samples_leaf个样本的方向发生。

一般是搭配max_depth一起用,在回归树中,可以让模型变得更加平滑。这个参数的数量设置太小可能会引起过拟合,设置得太大就会阻止模型学习数据。一般建议从5开始使用。如果叶节点含有得样本数量大,建议输入浮点数作为样本量百分比来使用,同时这个参数可以保证每个叶子得最小尺寸。避免低方差,过拟合的叶子节出现。类别不同的问题,1通常是最好的选择。

clf = tree.DecisionTreeClassifier(criterion="entropy", 
                                  random_state=0,
                                  splitter="random",
                                  max_depth=3,
                                    min_samples_leaf=10)
clf = clf.fit(Xtrain, Ytrain)
score = clf.score(Xtest, Ytest)
score

在原来的叶节点中,有的样本个数都是在10以下,但是加了这个参数之后,这些节点的个数都在10以上了,跟其同级叶子节点的个数平均了。然后得分变低了,不采用这个参数。

6.1.3 min_samples_split

min_samples_split限定,一个节点必要包含至少min_samples_split个训练样本,这个节点才能被允许分枝,否则分枝就不会发生。

clf = tree.DecisionTreeClassifier(criterion="entropy", 
                                  random_state=0,
                                  splitter="random",
                                  max_depth=3,
                                  max_samples_split=25)
clf = clf.fit(Xtrain, Ytrain)
score = clf.score(Xtest, Ytest)
score

在图中我们可以发现,它的节点(样本数小于25的)已经不再分了。得分更低,不采用。

6.1.4 max_features

max_features限制分枝时考虑的特征个数,超过限制个数的特征都会被舍弃。

max_features是用来限制高纬度数据的过拟合的剪枝参数,其方法更加直接。比如900个特征,只选择max_features个。是直接限制可以使用的特征个数,而强制让决策树停下的参数。在不知道决策树各个特征重要程度的前提下,不要使用,避免学习不足。如果希望降维的话,还是建议使用PCA、ICA或者其他降维方法。

6.1.5 min_impurity_decrease

min_impurity_decrease限制信息增益的大小。信息增益小于设定数值的分枝不会发生。

6.2 确认最优的剪枝参数

那么我们该如何确定每个参数应该怎么填呢?手段填的话,未免会效率很低。这个时候,我们就要使用超参数的曲线来判断了,继续使用我们已经训练好的决策树模型clf。超参数的学习曲线,是一条以超参数的取值为横坐标,模型的度量指标为纵坐标的曲线,它是用来衡量不通过超参数取值下模型的表现的线。我们这个模型中,模型的度量指标就是score。

# 如何确定最优的参数
import matplotlib.pyplot as plt
test = []
for i in range(10):
    clf = tree.DecisionTreeClassifier(criterion='entropy',
                                      random_state=0,
                                      splitter='random',
                                      max_depth=i+1  
                                                        )
    clf = clf.fit(Xtrain, Ytrain)
    score = clf.score(Xtest, Ytest)
    test.append(score)
plt.plot(range(10), test, color='red', label='max_depth')
plt.legend()
plt.show()

思考:

剪枝参数一定能够提升模型在测试上的表现嘛?

7. 重要属性和接口

属性是在模型训练之后,能够调用查看的模型的各种性质。对决策树来说,最重要的属性就是feature_importances_ 能够查看各个特征对模型的重要性,

sklearn中许多的算法的接口都是相似的,比如fit、score,几乎对每个算法都可以使用这两个接口,决策树的最常用的接口还有apply和predict。apply中输入测试集并返回每个测试样本所对应的类别的索引,predict输入测试集返回每个测试样本的标签。返回的内容一目了然并且很容易。

所有的接口中要求输入的X_train,X_test的部分,输入的特征的特征矩阵至少是一个二维矩阵,sklearn不接收任何一维矩阵作为特征矩阵被输入。如果只有一个特征,那必须使用reshape来增加矩阵的维度。

# apply返回每个测试样本所在叶子节点的索引
clf.apply(Xtest)

# predict返回每个测试样本的分析/回归结果
clf.predict(Xtest)

回归树(DecisonTreeRegressor)

1. 理论解释

对于分类树来说,它的标签是离散型的,如果标签是连续型的呢?这个时候我们就用到了回归树。

假设X和Y分别作为输入和输出变量,并且Y是连续变量,给定训练数据集$D=\{(x_{1}, y_{1}), (x_{2}, y_{2}), \cdots , (x_{N}, y_{N}) \}$考虑如何生成回归树

  • 如何选择划分点?
  • 如何决定树中叶节点的输出值?

一个回归树对应着输入空间(特征空间)的一个划分以及在划分单元上的输出值。假设输入空间划分为M个单元$R_{1}, R_{2}, \cdots, R_{M}$,斌且每个单元$R_{m}$上有一个固定的输出值$c_{m}$,所以回归树模型可以表示为:

$$ f(x)=\sum_{m=1}^Mc_{m}I(x\in R_{m}) $$

可以用平方误差$\sum_{x_{i}\in R_{m}}(y_{i}-f(x_{i}))^2$来表示回归树对于训练树据的预测误差,用平方误差最小的准则来求解每个单元上的最优输出值。易知,$R_{m}$上的$c_{m}$的最优值$\hat{c}_m$是$R_m$上所有的输入实例$x_i$对应输出$y_i$的均值,即:

$$ \hat{c}_m = ave(y_i|x_i \in R_m) $$

  • 问题1:怎样对输入空间进行划分?即如何选择划分点?

    CART回归树采用启发式的方法对输入空间进行划分,选择第j个变量$x^{(j)}$和它的取值s,作为切分变量和切分点,斌且定义两个区域:

    $$ R_{1}(j,s)=(x|x^{(j)} \leq s)和R_{2}(j,s)=(x|x^{(j)} > s ) $$

    然后我们就可以寻找到最优切分变量j和最优切分点s。具体求解

    $$ min_{j,s}[min_{c_{1}}\sum_{x_{i}\in R_{1}(j,s)}(y_{i}-c_{1})^2+min_{c_{2}}\sum_{x_{2}\in R_{2}(j,s)}(y_{i}-c_{2})^2] $$

    对于固定的输入变量j可以找到最优切分点s

  • 问题2:如何决定树中叶节点的输出值?

    用选定的最优切分变量j和最优切分点s划分区域并决定相应的输出值:

    $$ \hat{c}_1 =ave(y_i | x_i \in R_1 (j,s) ) 和 \hat{c}_2=ave (y_i |x_i \in R_2 (j,s) ) $$

    遍历所有的输入变量,找到最优的切分变量j,构成一个对(j, s)。依此将输入空间划分为两个区域。接着,对每个区域重复上述划分过程,直到满足停止条件为止。这样就生成一颗回归树。这样的回归树通常称为最小二乘回归树(least squares regression tree)。

2. 算法流程

输入:训练数据集D

输出:回归树f(x)

在训练数据集所在的输入空间中,递归地将每个区域划分为两个子区域并决定每个子区域上地输出值,构建二叉决策树:

  1. 选择最优切分变量j和切分点s,求解

$$ min_{j,s}[min_{c_{1}}\sum_{x_{i}\in R_{1}(j,s)}(y_{i}-c_{1})^2+min_{c_{2}}\sum_{x_{2}\in R_{2}(j,s)}(y_{i}-c_{2})^2] $$

​ 遍历变量j,对固定地切分变量j扫描切分点s,使其结果达到最小值的对(j, s)

  1. 选定的对(j, s)划分区域并决定相应的输出值:

    $$ R_1(j,s)=\{ x|x_(j) \leq s \}, R_2(j, s)=(x| x_j > s ) $$

其中对于上式有:

$\hat{c}_m= \frac{1}{N_m} \sum_{x_i \in R_m(j,s) } y_i, x_i\in R_m,m=1,2$

  1. 继续对两个子区域调用步骤1和步骤2,知道满足停止条件
  2. 将输入空间划分为M个区域$R_{1}, R_{2}, \cdots, R_{M}$,生成决策树:

    $$ f(x)=\sum_{m=1}^M\hat{c}_mI(x\in R_m) $$

3. 回归树实例

试用平方误差损失准则生成一个二叉回归树

$x_{i}$ 1 2 3 4 5 6 7 8 9 10
$y_{i}$ 4.50 4.75 4.91 5.34 5.80 7.05 7.90 8.23 8.70 9.00

选择最优切分变量j和切分点s:

$$ min_{j,s}[min_{c_{1}}\sum_{x_{i}\in R_{1}(j,s)}(y_{i}-c_{1})^2+min_{c_{2}}\sum_{x_{2}\in R_{2}(j,s)}(y_{i}-c_{2})^2] $$

其中:

$c_{1}=ave(y_{i} | x_{i} \in R_{1}(j,s)) 和c_{2}=ave(y_{i} | x_{i} \in R_{2}(j,s))$

例如,取s=1。此时有$R_{1}={1}, R_{2}=\{2, 3, 4, 5, 6, 7, 8, 9, 10 \}$,这两个区域的输出值分别为:

$c_{1}=4.50$

$c_{2}=\frac{1}{9}(4.75+4.91+5.34+5.80+7.05+7.90+8.23+8.70+9.00)=6.85$

根据上述计算得到下表:

s 1 2 3 4 5 6 7 8 9 10
$c_{1}$ 4.50 4.63 4.72 4.88 5.06 5.39 5.75 5.18 6.35 6.62
$c_{2}$ 6.85 7.12 7.43 7.78 8.18 8.46 8.64 8.85 9.00 0.00

把$c_{1}, c_{2}$的值带入到均方差中:

$m(1)=(4.50-4.50)^2+(4.75-6.85)^2+(4.91-6.85)^2+(5.34-6.85)^2+(5.80-6.85)^2+(7.05-6.85)^2+(8.23-6.85)^2+(8.70-6.85)^2+(9.00-6.85)^2$

其中跟$c_{1}$有关的只有第一项,跟$c_{2}$有关的则是有9项。

同理,我们可以得知:

s 1 2 3 4 5 6 7 8 9 10
m(s) 22.65 17.70 12.19 7.38 3.36 5.07 10.05 15.18 21.33 27.63

显然取s=5时,m(s)有最小,所以第一个最优切分变量为:j=x、最优切分点为:s=5。

用选定的(j, s)划分区域,并决定输出值:

两个划分区域分别是:$R_{1}=\{1,2,3,4,5 \}, R_{2}=\{6,7,8,9,10 \}$。输出值用公式:

$\hat{c}_1=ave(y_i|x_i in R_1(j,s) 和\hat{c}_2=ave(y_i|x_i \in R_2(j,s) $

得到$c_{1}=5.06, c_{2}=8.18$

然后对两个子区域继续调用算法流程中的步骤(1)、(2)

对$R_{1}$继续划分:

$x_{i}$ 1 2 3 4 5
$y_{i}$ 4.50 4.75 4.91 5.34 5.80

取切分点分别为:[1, 2, 3, 4, 5],则各个区域的输出值c如下:

s 1 2 3 4 5
$c_{1}$ 4.50 4.63 4.72 4.88 5.06
$c_{2}$ 5.20 5.35 5.57 5.80 0.00

计算m(s):

s 1 2 3 4 5
m(s) 0.67 0.43 0.19 0.37 1.06

s=3时,m(3)最小。

然后进行递归,可以得到完整的二叉回归树。

4.sklearn中的回归树

4.1 重要参数,属性和接口

  • criterion

回归树衡量分枝质量的指标,支持的标准有三种:

  1. 输入“mse“使用均方差(MSE),父节点和叶子节点直接的均方误差的差额将作为特征选择的标准,这种方法通过使用叶子节点的均值来最小化$L_{2}$损失。
  2. 输入”friedman_mse“使用费尔德曼均方误差,这样的指标使用针对潜在分枝种的问题改进方法。
  3. 输入”mae“使用绝对平均误差MAE,这种指标使用叶节点的中值来最小化$L{1}$损失。

在回归树种,MSE不只是我们的分枝质量衡量标准,也是我们常用的衡量回归树回归质量的指标。

当我们在交叉验证的时候,或者其他方式获取回归树的结果时候,我们往往会选择均方误差作为我们的评估(score是准确率)。在回归中,我们追求的是MSE越小越好。然后回归树的接口score返回的是$R^2$而不是MSE

$$ R^2=1-\frac{u}{v} $$

$u=\sum_{i=1}^{N}(f_{i}-y_{i})^2,v=\sum_{i=1}^{N}(y_{i}-\bar{y_{i}})^2$

其中u是残差平方和,v是总平方和。

4.2 交叉验证

对于模型的训练,我们最简单的想法就行把整个数据集分成两个部分,一部分用于训练,另一部分用于验证,也就是常说的训练集和测试集。

这样会有一个弊端,也就是模型与参数的选取将极大程度依赖于对训练集和测试集的划分。不同划分下,TEST的MSE的变动是很大的,而且对应的最优degree也是不一样的。如果我们对于训练集和测试集的划分方法不够好,很可能不能得到最好的模型和参数。

4.2.1 LOOCV

LOOCV也包含将数据集分成训练集和测试集这一步骤。但是不同的是,我们只用一个数据作为测试集,其他剩余的数据都作为训练集,并且将此步骤重复N次。(N是数据集的数据量)

就比如于n个数据{1, 2, 3, ..., n},LOOCV的方法就是每次取一个数据作为测试集的唯一元素,而其他的n-1个数据作为训练集用于训练模型和调参。比如第一次用1来作为测试集,则剩下的数据{2, 3, 4, ..., n}就可以作为它的训练集,依次类推,直到用n来作为测试集,剩下的数据作为训练集为止。结果就是我们最终训练了n个模型,每次都能得到一个MSE。最终test MSE则就是将这n个MSE取平均。

$$ CV_{n} = \frac{1}{n}\sum_{i=1}^{n}MSE_{i} $$

  • 优点:首先它不受测试集合训练集划分方法的影响,因为每一个数据都单独的做过测试集。同时,其用了n-1个数据训练模型,也几乎用到了所有的数据,保证了模型的bias更小。
  • 缺点:那就是计算量过于大,是test set approach耗时的n-1倍。

为了解决计算成本太大的弊端,对算法进行更进,使得LOOCV计算成本和只训练一个模型一样快。

$$ CV_{n}=\frac{1}{n}\sum_{i=1}^{n}(\frac{y_{i}-\hat{y_{i}}}{1-h_{i}})^{2} $$

4.2.2 k-fold Cross Validation

k折交叉验证和LOOCV的不同在于,我们每次的测试集不再只包含一个数据,而是多个,具体数目将根据K的选取来决定。比如,k=5,那么我们利用五折交叉验证的步骤就是:

  1. 将所有数据集分成5份
  2. 不重复地每次取其中一份作为测试集,用其他四份作训练集训练模型,之后计算该模型在测试集上的$MSE_{i}$
  3. 将5次的$MSE_{i}$取平均得到最后的MSE

    $$ CV_{k} = \frac{1}{k}\sum_{i=1}^{k}MSE_{i} $$

不难理解,其实LOOCV是一种特殊的k-fold Cross Validation (K=N)。

4.2.3 Bias-Variance Trade-Off for k-Fold Cross-Validation

最后,我们要说说K的选取。事实上,和开头给出的文章里的部分内容一样,K的选取是一个Bias和Variance的trade-off。

K越大,每次投入的训练集的数据越多,模型的Bias越小。但是K越大,又意味着每一次选取的训练集之前的相关性越大(考虑最极端的例子,当k=N,也就是在LOOCV里,每次都训练数据几乎是一样的)。而这种大相关性会导致最终的test error具有更大的Variance。

一般来说,根据经验我们一般选择k=5或10。

4.2.4 sklearn种交叉验证的实现

from sklearn.datasets import load_boston
from sklearn.model_selection import cross_val_score
from sklearn.tree import DecisionTreeRegressor

boston = load_boston()
regressor = DecisionTreeRegressor(random_state=0)
cross_val_score(regressor, boston.data, boston.target, cv=5,
                scoring='neg_mean_squared_error')

在cross_val_score这个函数中,regressor是任一构建好的模型实例,可是是SVM等等,后两个参数直接输人原始数据,不需要有任何改动或者划分。cv参数就是将数据集划分的个数。因为在回归树中,我们一般是看其MSE,所以加上scoring参数,要不然返回的就是得分。

你可能感兴趣的