决策树的建模与剪枝

跟我一起机器学习系列文章将首发于公众号:月来客栈,欢迎文末扫码关注!

在前面的两篇文章中,笔者首先介绍了决策树的基本思想;然后接着介绍了两种用于构建决策树的生成算法:ID3和C4.5。在这篇文章中,笔者将将通过sklearn库来实现对决策树分类算法的建模。

1 Scikit-learn建模

1.1 Scikit-learn接口介绍

清楚决策树的生成算法后,再利用sklearn来进行建模就变得十分容易了。顺便多说两句,由于sklearn在实现各类算法模型时基本上都遵循了同一的接口风格,这使得我们在刚开始学习的时候很容易就能入门。在sklearn中对于各类模型的使用,基本上都遵循以下三个步骤:

  • 第一步:建立模型

    这一步通常来说都是通过在对应的路径下导入我们需要用到的模型类,例如:

    from sklearn.linear_model import SGDClassifier

    在导入模型类后,我们需要通过这个类来实例化一个模型对应的类对象并填入对应超参数,例如:

    model = SGDClassifier(loss='log', penalty='l2', alpha=0.5)

  • 第二步:训练模型

    在sklearn中,所有模型的训练(或者计算)过程都是通过.fit()这个方法来完成的。并且一般情况下需要按实际情况在调用.fit()时传入相应参数。如果是有监督模型则一般是model.fit(x,y),无监督则是mode.fit(x)。同时,还可可以调用model.score(x,y)来对模型的结果进行评估。

  • 第三步:模型预测

    在训练好一个模型后,通常都要对测试集或者新输入的数据进行预测。在sklearn中一般都是通过模型类对应的.predict(x)方法来实现的。

因此在sklearn中,基本上所有的算法都可以通过上面这三步走来完成对模型的建立、训练与预测。

1.2 决策树建模

  • 导入数据集

    本次示例用到的数据集为sklearn中内置的一个数据集wine,这是一个比较常见的数据集,包含3个类别、178个样本和13个特征维度。

    from sklearn.datasets import load_wine
    def load_data(scale=True):
        data = load_wine()
        x, y = data.data, data.target
        x_train, x_test, y_train, y_test = \
            train_test_split(x, y, test_size=0.3,
                             shuffle=True, random_state=20)
        if scale:
            ss = StandardScaler()
            x_train = ss.fit_transform(x_train)
            x_test = ss.transform(x_test)
        return x_train, x_test, y_train, y_test
    
  • 定义模型

    再导入数据后就可以进行建模训练。

    from sklearn.tree import DecisionTreeClassifier
    def train(x_train, x_test, y_train, y_test):
        model = DecisionTreeClassifier(criterion="entropy")
        model.fit(x_train, y_train)
        model.predict()
        print("Accuracy: ", model.score(x_test, y_test))
    
    
    if __name__ == '__main__':
        x_train, x_test, y_train, y_test = load_data()
        train(x_train, x_test, y_train, y_test)
    #输出结果
    Accuracy:  0.9629629629629629
    

    在上面的第3行代码中,参数criterion='entropy'表示使用信息增益作为决策树每个划分时的标准。遗憾的是在sklearn中并没有实现以信息增益作为划分标准时的情况。

1.3 决策树可视化

对于已经训练好的模型我们可以借助外部工具对其进行可视化。首先需要下载的工具就是graphviz[1]这个软件,由于众所周知的原因导致下载速度很慢,不过可以在公众号回复“决策树可视化”可获取网盘链接。下载完成后直接解压即可。

此时,我们需要将训练好的模型输出,然后通过该软件进行可视化:

from sklearn.tree import DecisionTreeClassifier,export_graphviz
def train(x_train, x_test, y_train, y_test):
    model = DecisionTreeClassifier(criterion="entropy",max_depth=3)
    model.fit(x_train, y_train)
    with open("visualization.dot", "w") as f:
        f = export_graphviz(model, feature_names=feature_names, out_file=f)
    print("Accuracy: ", model.score(x_test, y_test))

在解压后的文件中,进入目录release\bin并运行gvedit.exe,然后打开生成的这个visualization.dot文件即可,如下图所示:

从图中可以看出,决策树一开始的信息熵为1.579,然后越往下划分,信息熵逐渐变小。sample表示总的样本数,value表示每个类别中的分类结果样本树。

2 决策树剪枝

2.1 剪枝思想

在前面的介绍中我们说到,使用ID3算法进行决策数的生成时容易产生过拟合的现象,因此我们需要使用一种方法来缓解这种过拟合的现象。决策树过拟合表现形式一般为这棵树有很多的叶子节点,想象一下如果一颗树为每个样本点都生成一个叶节点,那么也就代表着这棵树能够拟合所有的样本点,因为决策树的每个叶节点都表示一个分类类别。同时过拟合的原因在于模型在学习时过多地考虑如何提高对训练数据的正确分类,从而构建出了过于复杂的决策树。因此,解决这个问题的办法就是考虑减少决策树的复杂度,对已经生成的决策树进行简化,也就是剪枝。

以下为选读内容(第一次学习可不读)

2.2 剪枝

决策树的剪枝往是通过最小化决策树整体的损失函数或者代价函数来实现。我们设树 T T T的叶结点个数为 ∣ T ∣ |T| T t t t是树 T T T的一个叶结点,该叶结点有 N t N_t Nt个样本点,其中类别 k k k的样本点有 N t k N_{tk} Ntk个, k = 1 , 2 , . . . , K ,      H t ( T ) k=1,2,...,K,\;\;H_t(T) k=1,2,...,K,Ht(T)为叶结点 t t t上的经验熵, α ≥ 0 \alpha\geq0 α0为参数,则决策树的损失函数可以定义为:
C α ( T ) = ∑ t = 1 ∣ T ∣ N t H t ( T ) + α ∣ T ∣ (1) C_{\alpha}(T)=\sum_{t=1}^{|T|}N_tH_t(T)+\alpha|T|\tag{1} Cα(T)=t=1TNtHt(T)+αT(1)

其中经验熵为
H t ( T ) = − ∑ k N t k N t log ⁡ N t k N t (2) H_t(T)=-\sum_k\frac{N_{tk}}{N_t}\log{\frac{N_{tk}}{N_t}}\tag{2} Ht(T)=kNtNtklogNtNtk(2)

C ( T ) = ∑ t = 1 ∣ T ∣ N t H t ( T ) = − ∑ t = 1 ∣ T ∣ ∑ k = 1 K N t k log ⁡ N t k N t (3) C(T)=\sum_{t=1}^{|T|}N_tH_t(T)=-\sum_{t=1}^{|T|}\sum_{k=1}^KN_{tk}\log{\frac{N_{tk}}{N_t}}\tag{3} C(T)=t=1TNtHt(T)=t=1Tk=1KNtklogNtNtk(3)
则损失函数可以写为:
C α ( T ) = C ( T ) + α ∣ T ∣ (4) C_{\alpha}(T)=C(T)+\alpha|T|\tag{4} Cα(T)=C(T)+αT(4)
其中 C ( T ) C(T) C(T)表示模型对训练数据的预测误差,即模型与训练集的拟合程度, ∣ T ∣ |T| T表示模型复杂度,参数 α ≥ 0 \alpha\geq0 α0控制两者之间的影响。较大的 α \alpha α促使选择较简单的模型(树),较小的 α \alpha α促使选择较复杂的模型(树)。 α = 0 \alpha=0 α=0意味着只考虑模型与训练集的拟合程度,不考虑模型的复杂度。可以发现,这里 α \alpha α的作用就类似于正则化中惩罚系数的作用。

剪枝步骤:
输入:生成算法产生的整个树 T T T,参数 α \alpha α
输出:修剪后的子树 T α T_{\alpha} Tα
(1)计算每个叶结点的经验(信息)熵;

(2)递归地从树的叶结点往上回溯;设一组叶结点回溯到其父结点之前与之后的整体树分别为 T B , T A T_B,T_A TB,TA,其对应的损失函数值分别是 C α ( T B ) , C α ( T A ) C_{\alpha}(T_B),C_{\alpha}(T_A) Cα(TB),Cα(TA),如果 C α ( T A ) ≤ C α ( T B ) C_{\alpha}(T_A)\leq C_{\alpha}(T_B) Cα(TA)Cα(TB)则进行剪枝,即将父结点变为新的叶结点。

(3)返回(2),直到不能继续为止,得到损失函数最小的子树 T α T_{\alpha} Tα

举例:下面我们根据之前建立好的决策树来进行剪枝计算

如上图所示,考虑是否要减掉“有工作”这个结点,首先需要计算的就是剪枝前的损失函数数值 C α ( T B ) C_\alpha(T_B) Cα(TB)。由于剪枝时,每次只考虑一个结点,所以在计算剪枝前和剪枝后的损失函数值时,仅考虑该结点即可。因为其他叶结点的经验熵对于剪枝前和剪枝后都没有变化。

易知“有工作”这个结点的训练数据如下

I D 年龄 有工作 有自己的房子 贷款情况 类别 3 青年 是 否 好 是 13 老年 是 否 好 是 D 21 14 老年 是 否 非常好 是 1 青年 否 否 一般 否 2 青年 否 否 好 否 5 青年 否 否 一般 否 D 22 6 中年 否 否 一般 否 7 中年 否 否 好 否 15 老年 否 否 一般 否 \begin{array}{c|cc}\hline ID&\text{年龄}&\text{有工作}&\text{有自己的房子}&\text{贷款情况}&\text{类别}\\\hline3&\text{青年}&\text{是}&\text{否}&\text{好}&\text{是}\\13&\text{老年}&\text{是}&\text{否}&\text{好}&\text{是}&D_{21}\\14&\text{老年}&\text{是}&\text{否}&\text{非常好}&\text{是}\\\hline1&\text{青年}&\text{否}&\text{否}&\text{一般}&\text{否}\\2&\text{青年}&\text{否}&\text{否}&\text{好}&\text{否}\\5&\text{青年}&\text{否}&\text{否}&\text{一般}&\text{否}&D_{22}\\6&\text{中年}&\text{否}&\text{否}&\text{一般}&\text{否}\\7&\text{中年}&\text{否}&\text{否}&\text{好}&\text{否}\\15&\text{老年}&\text{否}&\text{否}&\text{一般}&\text{否}\\\hline\end{array} ID313141256715年龄青年老年老年青年青年青年中年中年老年有工作有自己的房子贷款情况非常好一般一般一般一般类别D21D22

则根据公式 ( 3 ) (3) (3)有:
C α ( T B ) = C ( T B ) + α ∣ T B ∣ C ( T B ) = − ∑ t = 1 2 ∑ k = 1 2 N t k log ⁡ N t k N t = − [ ( 3 log ⁡ 3 3 + 0 ) + ( 6 log ⁡ 6 6 + 0 ) ] = 0 C α ( T B ) = 0 + α × 2 C α ( T A ) = C ( T A ) + α ∣ T A ∣ C ( T A ) = − ∑ t = 1 1 ∑ k = 1 2 N t k log ⁡ N t k N t = − [ ( 3 log ⁡ 3 9 + 6 log ⁡ 6 9 ) ] ≈ 8 C α ( T A ) = 8 + α × 1 \begin{aligned} &C_{\alpha}(T_B)=C(T_B)+\alpha|T_B|\\[1ex] &C(T_B)=-\sum_{t=1}^{2}\sum_{k=1}^2N_{tk}\log{\frac{N_{tk}}{N_t}}=-\left[(3\log{\frac{3}{3}}+0)+(6\log{\frac{6}{6}}+0)\right]=0\\[1ex] &C_{\alpha}(T_B)=0+\alpha\times 2\\[3ex] &C_{\alpha}(T_A)=C(T_A)+\alpha|T_A|\\[1ex] &C(T_A)=-\sum_{t=1}^{1}\sum_{k=1}^2N_{tk}\log{\frac{N_{tk}}{N_t}}=-\left[(3\log{\frac{3}{9}}+6\log{\frac{6}{9}})\right]\approx 8\\[1ex] &C_{\alpha}(T_A)=8+\alpha\times1 \end{aligned} Cα(TB)=C(TB)+αTBC(TB)=t=12k=12NtklogNtNtk=[(3log33+0)+(6log66+0)]=0Cα(TB)=0+α×2Cα(TA)=C(TA)+αTAC(TA)=t=11k=12NtklogNtNtk=[(3log93+6log96)]8Cα(TA)=8+α×1

由此可以,当设定的 α ≥ 8 \alpha\geq8 α8时,就会剪枝。因为此时$ C_{\alpha}(T_A)=8+\alpha\leq C_{\alpha}(T_B)=2\alpha$满足剪枝条件。

3 总结

在本篇内容中,笔者首先介绍了sklearn中各类算法模型的一个通用性法则,并将其总结为三步走方法;其次笔者通过一个示例演示了如何通过sklearn来完成决策树的建模,同时还借助第三方工具对拟合后的决策树进行了可视化;接着笔者介绍了缓解决策树过拟合现象的剪枝方法,并进一步的对剪枝的原理进行了介绍。在下一篇文章中,笔者将继续介绍另外一种使用更为广泛的决策树生成与剪枝算法模型CART。本次内容就到此结束,感谢阅读!

若有任何疑问与见解,请发邮件至moon-hotel@hotmail.com并附上文章链接,青山不改,绿水长流,月来客栈见!

引用

[1] graphviz https://graphviz.gitlab.io/_pages/Download/Download_windows.html

[2]《统计机器学习(第二版)》李航,公众号回复“统计学习方法”即可获得电子版与讲义

[3]《Python与机器学习实战》何宇健

[4] 示例代码 : https://github.com/moon-hotel/MachineLearningWithMe

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页