决策树

熵(entropy)是表示随机变量不确定性的度量。设X是一个取有限个值的离散随机变量,其概率分布$P(X=x_{i})=p_{i},i=1,2,...,n$,则随机变量X的熵定义为
$$\begin{equation}H(X)=-\sum ^{n}_{i=1}p_{i}\log p_{i}\tag{1}\end{equation}$$
在上式中,若$p_{i}=0$,则定义$0\log0=0$。通常,式(5.1)中的对数以2为底或以e为底,这时熵的单位分别称为比特(bit)或纳特(nat)。由定义可知,熵只依赖于X的分布,而与X的取值无关,所以也可以将X的熵记作$H(p)$,即$H(p)=-\sum ^{n}_{i=1}p_{i}logp_{i}$。熵越大,随机变量的不确定性据越大。

条件熵

设有随机变量$(X,Y)$,其联合概率分布$P(X=x_{i},Y=y_{i})=p_{ij}, i=1,2,...,m$,条件熵$H(Y|X)$表示在已知随机变量X的条件下随机变量Y的不确定性。随机变量X给定的条件下随机变量Y的条件熵(conditional entropy) H(Y|X),定义为X给定条件下Y的条件概率分布的熵对X的数学期望
$$\begin{equation}H(Y|X)=\sum ^{n}_{i=1}p_{i}H(Y|X=x_{i})\tag{2}\end{equation}$$
这里,$p_{i}=P(X=x_{i}),i=1,2,..,n$。
当熵和条件熵中的概率由数据估计(特别是极大似然估计)得到时,所对应的熵与条件熵分别称为经验熵(empirical entropy)和经验条件熵(empirical conditional entropy)。此时,如果有0概率,令$0log0=0$。

信息增益

信息增益(information gain)表示得知特征X的信息而使得Y的信息的不确定性减少的程度。特征A对训练数据集D的信息增益g(D,A),定义为集合D的经验熵H(D)与特征A给定条件下D的经验条件熵H(D|A)之差,即
$$\begin{equation}g(D,A)=H(D)-H(D|A)\tag{3}\end{equation}$$
一般地,熵H(Y)与条件熵H(Y|X)之差称为互信息(mutual information)。决策树学习中的信息增益等价于训练数据集中类与特征的额互信息
决策树学习应用信息增益准则选择特征。给定训练数据集D和特征A,经验熵H(D)表示对数据集D进行分类的不确定性。而经验条件熵H(D|A)表示在特征A给定的条件下对数据集D进行分类的不确定性。那么它们的差,即信息增益,就表示由于特征A而使得对数据集D的分类的不确定性减小的程度。显然,对于数据集D而言,信息增益依赖于特征,不同特征往往具有不同的信息增益,信息增益最大的特征具有更强的分类能力。因此,根据信息增益准则的特征选择方法是:对训练数据集(或子集)D,计算其每个特征的信息增益,并比较它们的大小,选择信息增益最大的特征。

设训练数据集为D,|D|表示其样本容量,即样本个数。设有K个类$C_{k},k=1,2,...,K, |C_{k}|$为属于类$C_{k}$的样本个数,$\sum ^{K}_{k=1}|C_{k}|=|D|$。设特征A有n个不同的取值$\{a_{1},a_{2},...,a_{n}\}$,根据特征A的取值将D划分为n个子集$D_{1},D_{2},...,D_{n},|D_{i}|$为$D_{i}$的样本个数,$\sum ^{n}_{i=1}|D_{i}|=|D|$。记子集$D_{i}$中属于类$C_{k}$的样本的集合为$D_{ik}$,即$D_{ik}=D_{i}\cap C_{k}, |D_{ik}|$为$D_{ik}$的样本个数。于是信息增益算法如下:
(1)计算数据集D的经验熵H(D)$$H(D)=-\sum ^{K}_{k=1}\frac {| C_{k}| }{|D|} \log_{2} \frac {| C_{k}| }{|D|}$$
(2)计算特征A对于数据集D的经验条件熵H(D|A)$$H(D|A)=\sum ^{n}_{i=1}\frac {| D_{i}| }{|D|}H(D_{i})=-\sum ^{n}_{i=1}\frac {|D_{i}|}{|D|}\sum ^{K}_{k=1}\frac {|D_{ik}|}{|D_{i}|}\log_{2}\frac {|D_{ik}|}{|D_{i}|}$$
(3)计算信息增益$$g(D,A)=H(D)-H(D|A)$$

信息增益比

以信息增益作为划分训练数据集的特征,存在偏向于选择取值较多的特征的问题。可以考虑一个极端的情况,在数据集D中,每一个样本关于特征A的取值都是不一样的,则H(D|A)为0,信息增益g(D,A)最大。使用信息增益比(information gain ratio)可以对这个问题进行校正。特征A对训练数据集D的信息增益比$g_{R}(D,A)$定义为其信息增益$g(D,A)$与训练数据集D关于特征A的值的熵$H_{A}(D)$(不是数据集D的经验熵H(D))之比,即
$$\begin{equation}g_{R}(D,A)=\frac {g(D,A)}{H_{A}(D)}\tag{4}\end{equation}$$
其中,$H_{A}(D)=-\sum ^{n}_{i=1}\frac {|D_{i}|}{|D|}\log_{2}\frac{|D_{i}|}{|D|}$。

决策树

决策树算法有两个核心问题,

  1. 如何找到最有特征和最佳分割点,即树的生成问题;
  2. 如何让决策树停止生长以及对决策树进行剪枝,防止过拟合。

ID3算法生成决策树

ID3算法的核心是在决策树各个结点上应用信息增益准则选择特征,递归地构建决策树。具体方法是:从根节点(root node)开始,对结点计算所有可能的特征的信息增益,选择信息增益最大的特征作为结点的特征,由该特征的不同取值建立子结点;再对子结点递归地调用以上方法,构建决策树;直到所有特征的信息增益很小或没有特征可以选择为止,最后得到一个决策树。
(1)若D中所有实例属于同一类$C_{k}$,则T为单结点树,并将类$C_{k}$作为该结点的类标记,返回T;
(2)若$A=\emptyset$,则T为单结点树,并将D中实例数最大的类$C_{k}$作为该结点的类标记,返回T;
(3)否则,计算A中个特征对D的信息增益,选择信息增益最大的特征$A_{g}$;
(4)如果$A_{g}$的信息增益小于阈值$\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}$
ID3算法有以下缺点

  1. 无法处理连续变量的特征(因为它会按照特征的每一个取值构建子结点,如步骤(5)所示);
  2. 使用信息增益准则选择特征,容易选择取值较多的特征作为最优特征;
  3. 对缺失值敏感,没有考虑缺失值的情况;
  4. 容易过拟合,没有剪枝设置。

C4.5算法生成决策树

C4.5算法对ID3算法进行了改进。C4.5在生成的过程中,使用信息增益比(如式(4))来选择特征。
(1)若D中所有实例属于同一类$C_{k}$,则T为单结点树,并将类$C_{k}$作为该结点的类标记,返回T;
(2)若$A=\emptyset$,则T为单结点树,并将D中实例数最大的类$C_{k}$作为该结点的类标记,返回T;
(3)否则,计算A中个特征对D的信息增益比,选择信息增益最大的特征$A_{g}$;
(4)如果$A_{g}$的信息增益小于阈值$\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}$

处理连续变量

C4.5还增加了处理连续变量的手段,对于连续的特征,C4.5会先按照该特征的值对所有样本进行排序,假设该特征一共有n个不同的值,通过对相邻的值去均值,产生n-1个切分点。对于离散的特征,正常计算信息增益比,对于连续的特征,分别使用n-1个切分点,对数据进行二分,计算信息增益比。最终选择信息增益值最大的划分点进行样本集合的划分(可能是离散特征,也可能是连续特征)。
主要注意的是,

  1. 如果当前选择的最优特征为连变量,该特征还可作为其后代节点的划分属性(而如果特征是离散变量,一旦被选为最优特征来划生成子树,则不可以在被使用)。
  2. 当计算连续特征的信息增益比时,需对最佳分裂点的信息增益进行修正:减去log2(n-1)/|D|(n是连续特征的取值个数,|D|是训练数据数目,此修正的原因在于:当离散属性和连续属性并存时,C4.5算法倾向于选择连续特征做最佳树分裂点)
处理缺失值

C4.5还可以处理缺失值,分两步实现:

  1. 计算所有特征的信息增益比时,假设数据集一共10000个样本,特征A的值缺失的有5000个样本,则无视缺失值,使用剩余的5000个样本中计算关于特征A的信息增益率,最后乘以0.5。思想就是缺失值多的特征通过这种降低权重的方式来体现信息的缺失;
  2. 如果运气不好,正好这个A特征乘0.5之后得到的增益率还是最大的,则需要将存在缺失值的样本按照比例划分入分裂之后的新的分支,假设根据特征A分裂得到两个新的分支,一个分支有2000个样本有2000个样本,一个分支有3000个样本,则按照比例将2000个缺失特征A的样本和3000个缺失特征A的样本分别进入两个分支。

决策树的剪枝

在决策树学习中将已生成的树进行简化的过程称为剪枝(pruning)。具体地,剪枝从已生成的树上裁掉一些子树或叶结点,并将其根结点或父结点作为新的叶结点,从而简化分类树模型。

决策树的剪枝往往通过极小化决策树整体的损失(loss function)或代价函数(cost function)来实现。设树T的叶结点个数为|T|,t是树T的叶结点,叶结点有$N_{t}$个样本点,其中k类的样本点有$N_{tk}$个,$k=1,2,...,K$,$H_{t}(T)$为叶结点$t$上的经验熵,$\alpha \geq 0$为参数,则决策树学习的损失函数可以定义为
$$\begin{equation}C_{\alpha}(T)=\sum ^{|T|}_{t=1}N_{t}H_{t}(T)+\alpha |T|\tag{5}\end{equation}$$
其中经验熵为$H_{t}(T)=-\sum ^{}_{k}\frac{N_{tk}}{N_{t}}\log\frac{N_{tk}}{N_{t}}$,
在损失函数中,将式(5)右端的第一项记作
$$\begin{equation}C(T)=\sum ^{|T|}_{t=1}N_{t}H_{t}(T)=-\sum ^{|T|}_{t=1}\sum ^{K}_{k=1}N_{tk}\log\frac{N_{tk}}{N_{t}}\tag{6}\end{equation}$$
这时有$C_{\alpha}(T)=C(T)+\alpha|T|$,C(T)表示模型对训练数据的预测误差,即模型与训练数据的拟合程度。用来衡量叶子结点的“不纯度”。
确定了决策树的损失函数后,就可以根据损失函数,对决策树进行剪枝。
(1) 计算每个结点的经验熵。
(2) 递归地从树的叶结点向上回溯。
设一组叶结点回溯到其父节点之前与之后的整体树分别为$T_{B}$和$T_{A}$,其对应的损失函数值分别是$C_{\alpha}(T_{B})$与$C_{\alpha}(T_{A})$,如果$C_{\alpha}(T_{A}) \leq C_{\alpha}(T_{B})$则进行剪枝,即将父结点变为新的叶结点。
(3)返回(2),直至不能继续为止,得到损失函数最小的子树$T_{\alpha}$。

CART

CART是对C4.5的改进。

分类树

CART分类树使用基尼指数选择最有特征和切分点。
在分类问题中,有K个类,样本属于第k类的概率为$p_k$,则概率分布的基尼指数定义为,
$$\begin{equation}Gini(p)=\sum_{k=1}^{K}p_k(1-p_k)=1-\sum_{k=1}^{K}p_k^2\tag{7}\end{equation}$$
给定样本集D,其基尼指数为$Gini(D)=1-\sum_{k=1}^{K}(\frac{|C_k|}{|D|})$,$C_k$表示D中属于第k类的样本集。
对于特征A,如果A是离散特征,则样本集根据A是否取可能值a被分割为$D_1$和$D_2$;如果是连续特征,按切分点a被分割为$\leq$a和$>$a的两个子集,$D_1$和$D_2$。则基尼指数定义为,
$$\begin{equation}Gini(D,A=a)=\frac{D_1}{|D|}Gini(D_1)+\frac{|D_2|}{|D|}Gini(D_2)\tag{8}\end{equation}$$
选择使基尼指数Gini(D,A=a)小的A和a作为最有特征和切分点。

回归树

CART回归树的生成方式为,在训练数据所在的输入空间中,递归地将每个区域划分为两个子区域并决定每个子区域的输出值,构建二叉树。
(1)选择最有切分变量(特征)j与切分点s,求解
$$\begin{equation}\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]\tag{9}\end{equation}$$
遍历所有的特征j,固定j,按所有的可能值s进行分割,使式(9)最小。
(2)对于选定的(j,s),如果j是离散变量,则$R_1(j,s)=\{x|x^{(j)}=s\}$,$R_2(j,s)=\{x|x^{(j)}\neq 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$。
(3)继续对两个子区域$R_1$和$R_2$调用步骤(1)和步骤(2),直至满足停止条件。
(4)将输入空间划分为M个区域$R_1,R_2,\cdots,R_m$,生成决策树$f(x)=\sum_{m=1}^{M}\hat{c}_m Indicator(x\in R_m)$。

不同点

  1. ID3和C4.5都只能处理分类问题,而CART既可以处理分类问题,也可以处理回归问题;
  2. CART可以处理任意类型的特征,包括连续和离散,ID3无法处理连续特征;
  3. CART为二叉树,而ID3和C4.5为多叉树;
  4. 在解决分类问题时,CART使用基尼指数(Gini)选择最优特征和切分点;解决回归问题时,使用平方误差;
  5. CART的最优特征可以在子树中继续使用,直到关于该特征的所有可能值(切分点)都被使用过。ID3算法在进行分支时,每次会消耗一个特征,C4.5如果选择离散特征进行分支,则该离散特征在子树中不能够再被使用,如果是连续特征,则可以继续使用。

剪枝

cart树怎么进行剪枝?

案例

以泰坦尼克号幸存者预测问题为例,数据集地址https://www.kaggle.com/c/titanic

import pandas as pd 
import numpy as np
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import train_test_split
from sklearn.model_selection import GridSearchCV
from sklearn.model_selection import cross_val_score

def load_data(path):
    data = pd.read_csv(path, index_col=0)
    return data

def preprocess(data):
    path = "./sklearn/data/train.csv"
    data = load_data(path)
    data.drop(["Cabin", "Name", "Ticket"], inplace=True, axis=1)
    data["Age"] = data["Age"].fillna(data["Age"].mean())
    data = data.dropna()
    data["Sex"] = (data["Sex"]=="male").astype("int")
    embarked_city = data["Embarked"].unique().tolist()
    data["Embarked"] = data["Embarked"].apply(lambda x : embarked_city.index(x))
    return data

def get_train_test(data):
    x = data.iloc[:, data.columns != "Survived"]
    y = data.iloc[:, data.columns == "Survived"]
    Xtrain, Xtest, Ytrain, Ytest = train_test_split(x, y, test_size=0.3)
    for i in [Xtrain, Xtest, Ytrain, Ytest]:
        i.index = range(i.shape[0])
    return Xtrain, Xtest, Ytrain, Ytest, x, y

def model(Xtrain, Ytrain, Xtest, Ytest):
    clf = DecisionTreeClassifier(random_state=40)
    clf.fit(Xtrain, Ytrain)
    score_ = clf.score(Xtest, Ytest)
    print(score_)
    return clf

def cross_and_valid(x, y):
    clf = DecisionTreeClassifier(random_state=40)
    score = cross_val_score(clf, x, y, cv=10).mean()
    print(score)

def grid_search(Xtrain, Ytrain, params):
    clf = DecisionTreeClassifier(random_state=40)
    GS = GridSearchCV(clf, param_grid=params, cv=10)
    GS.fit(Xtrain, Ytrain)
    print(GS.best_params_)
    print(GS.best_score_)

if __name__ == "__main__":
    path = "./sklearn/data/train.csv"
    data = load_data(path)
    print(data.head())
    print(data.info())
    data = preprocess(data)
    print(data.head())
    print(data.info())
    Xtrain, Xtest, Ytrain, Ytest, x, y = get_train_test(data)
    model(Xtrain, Ytrain, Xtest, Ytest)
    cross_and_valid(x, y)
    params = {'splitter':('best', 'random')
            ,'criterion':('gini', 'entropy')
            ,'max_depth':[*range(1,11)]
            ,'min_samples_leaf':[*range(1, 50, 10)]
            ,'min_impurity_decrease':[*np.linspace(0, 0.5, 10)]}
    grid_search(Xtrain, Ytrain, params)
    

输出

0.7902621722846442
0.773914708886619
{'criterion': 'entropy', 'min_samples_leaf': 1, 'max_depth': 4, 'min_impurity_decrease': 0.0, 'splitter': 'best'}
0.8360128617363344

参考

  1. 统计学习方法方法,李航
  2. https://zhuanlan.zhihu.com/p/...
  3. https://live.bilibili.com/125...
  4. https://www.zhihu.com/search?...
阅读 319

推荐阅读