决策树 value_《机器学习》之 深入浅出决策树(原理+代码)

1 介绍

决策树(Decision Tree)是机器学习中比较经典的算法之一,也属于有监督学习中的一员。与线性模型(逻辑回归、神经网络等)不同的是,它的学习过程不是为每个特征学习一个权重,而是根据某种决策不断的对数据集进行分裂,使得到的子数据集上的标签越来越纯净,最终得到的模型就是一个树形结构,故其名曰决策树。

2 原理

决策树算法的效果好,可用于分类,也可用于回归 (比如CART树) 。决策树有多种,这里我介绍三种最经典的决策树:ID3、C4.5、C5.0。这三种树的唯一区别就是在节点划分时决策的选择上有所不同。

2.1 ID3

公式详解:

进入正题之前先了解一下什么是信息熵 (香农熵) ,前面说了决策树是不断的对数据集进行划分,使子数据集的标签越来越纯净,那么纯净度该如何度量呢?

032e491d89186996956653c49f48eaf0.png

(D表示数据集样本数量,Ck表示数据集中类别为k的样本数量,K为数据集的类别数) 上述公式即可对一个数据集D的纯净度进行度量,统计各类标签所占总样本的比例即可得到。H(D)越小,表示数据集D的标签纯度越高。

了解了信息熵之后,再来看一下什么是信息增益。信息增益显然就是两个信息熵之间的差距,具体来说,信息增益等于未划分时数据集的信息熵,减去划分之后所有子数据集的信息熵之和。

d46481f5d457fb4fd034ec9c8ec5922e.png

( Gain(D,A)表示用特征A划分得到的信息增益,H(D)表示未划分时D的信息熵,H(D|A)表示用特征A划分得到的所有子数据集的信息熵之和 )

175ee907eadc6a36d3a4836c339f66e2.png

( Di 表示划分之后得到的子数据集数量,H(D|A) 就是各个子数据集信息熵 H(Di) 的加权求和,权重为子样本数占全部样本的比例 )

ID3算法:

决策树每次对数据集进行划分需要选择最优的划分特征,ID3就是对每个特征进行遍历,然后计算按此特征划分后得到的信息增益,选择增益最大的特征进行数据集的划分。然后再对每个子数据集进行同样的划分操作。

缺点:
1 ID3信息增益准则对可取值数目较多的特征有所偏好,比如ID类特征的信息增益可接近于1;
2 只能用于处理离散分布的特征,不能处理连续值与缺失值;
3 只能用于分类。

2.2 C4.5

为了克服ID3倾向于取值数目较多的特征的缺陷,C4.5在信息增益的基础上添加了分母项,引入了信息增益率,计算公式如下:

cbe2dfa516afcaef9165f3bf4d9dc86b.png

d030571ce9945a91603dca4edbbef4d9.png

分母项Ha会对取值数目较多的特征进行惩罚,特征取值数目越多,Ha越大,信息增益率就越小。所以很好的解决了ID3的缺点1。信息增益率同信息增益,都是越大越好。

[注]:信息增益率虽然解决了倾向于取值较大的特征,但是又引入了倾向于取值较少特征的缺陷,所以其选择特征时不是直接选取信息增益率最大的特征,而是先从候选特征中找到信息增益高于平均值的特征,再从中选择使增益率最高的作为最优划分特征。

优点:
1 改进ID3倾向于选择取值较多的特征的缺点;
2 可处理连续值与缺失值。
缺点:
1 跟ID3一样只能用于分类;
2 计算量较大,计算信息熵有大量的对数运算,以及选取最优特征时,对连续值的排序等。

2.3 C5.0 (CART)

C5.0又称为CART(分类回归树),C5.0抛弃了用信息熵来衡量数据集的纯净度,使用了一种更简单的计算方式,称为基尼系数,其公式如下:

157f500c9f31a1af8dc44a0612acaae5.png

Gini(D)表示从D中随机抽取两个样本,这两个样本的标签不一样的概率。概率越小,表示同类的样本数越多,数据集D越纯净,这性质与信息熵越大样本越纯净相反。

C5.0不再是考量划分前后信息熵的差距,而是基尼系数的差距。C5.0的计算过程没有引入log对数计算,所以它的计算复杂度要优于ID3与C4.5。

优点:
1 使用 Gini 系数来度量样本纯净度,减少了大量的对数运算;
2 可同时用于分类与回归;
缺点:
1 跟ID3一样,都倾向于多取值特征。

3 总结

325062bea1d4f9d2716544a951bc224b.png

4 代码实现

该代码为ID3的实现: (修改节点划分函数NodeSplit中增益gain的计算,即可扩展成C4.5与C5.0)

import numpy as np
import pandas as pd

class DecisionTree:
    def __init__(self):
        pass

    #信息熵的计算
    def getEnt(self, data):
        labels = data.iloc[:, -1]   #取标签列,默认最后一列为样本标签
        ent = 0                     #信息熵初始化0
        p = labels.value_counts().values/len(labels)  #每一类标签数量/总样本数,列表p存储每类标签所占比例
        ent -= p*np.log2(p)         #列表ent为每类标签的信息熵
        return np.sum(ent)          #对所以类标签求和得到整个样本信息熵

    #遍历寻找最优划分特征
    def NodeSplit(self, data):
        beforeEnt = self.getEnt(data)   #划分前data的信息熵
        bestFeature = -1                #最优划分特征的下标
        bestGain = 0                    #记录最大信息增益
        for i in range(data.shape[1]-1):#遍历每个特征,除了标签
            val = data.iloc[:, i].value_counts().index
            ents = 0
            for v in val:               #遍历特征i的所有取值
                childData = data[data.iloc[:, i] == v]  #取特征i的取值为v的部分样本
                ents += (len(childData)/len(data)) * self.getEnt(childData) #计算子样本的信息熵(累加)
            gain = beforeEnt - ents     #划分前后的信息增益
            if(gain>bestGain):
                bestGain = gain         #更新最大增益
                bestFeature = i         #记录最优划分特征下标
        return bestFeature

    #根据找到的最优划分特征,对数据集进行划分
    def DataSplit(self, data, feature, value):  #划分出特征feature下取值为value的样本
        col = data.columns[feature]             #根据下标取列名
        childData = data.loc[data[col]==value, :]
        childData = childData.drop(col, axis=1) #最优划分的特征使用一次后需删除
        return childData

    #递归生成 Decision Tree
    def getTree(self, data):
        columns = list(data.columns)             #数据集所有的列
        labels = data.iloc[:, -1].value_counts() #获取最后一列标签
        #递归结束条件:
        #1、样本标签全部为一类
        #2、样本只剩下一个特征
        if labels[0]==len(data) or len(columns)==1:
            return labels.index[0]               #返回标签

        bestFeature = self.NodeSplit(data)       #计算当前数据集最佳划分特征的索引
        col = columns[bestFeature]               #获取索引取对应的特征名
        myTree = {col: {}}                       #采用字典嵌套的方式存储树信息
        del columns[bestFeature]                 #使用后删除最优划分特征

        for val in set(data[col]):               #遍历特征col的每个特征值,对每一个值递归建树
            childData = self.DataSplit(data, bestFeature, val) #取每个值对应的子样本
            myTree[col][val] = self.getTree(childData) #对子样本进行建树,并保存在嵌套字典中
        return myTree

修改NodeSplit中gain的计算,扩展成C4.5的代码如下:(C5.0比较简单,可自行实现)

# 替换NodeSplit()中的for循环即可,添加分母项 H
     for i in range(data.shape[1]-1):#遍历每个特征,除了标签
         val = data.iloc[:, i].value_counts().index
         ents = 0
         H = 0
         for v in val:               #遍历特征i的所有取值
             childData = data[data.iloc[:, i] == v]  #取特征i的取值为v的部分样本
             ents += (len(childData)/len(data)) * self.getEnt(childData) #计算子样本的信息熵(累加)
             p = len(childData)/len(data)
             H -= p*np.log2(p)
         gain = beforeEnt - ents     #划分前后的信息增益
         gain = gain/H

使用测试:

dict = {'a': [1, 1, 1, 0, 0],
        'b': [1, 1, 0, 1, 1],
        'c': [0, 1, 0, 1, 2],
        'd': [1, 2, 1, 0, 1],
        'label': ['yes', 'yes', 'no', 'no', 'no']}
Data = pd.DataFrame(dict)

print(DecisionTree().getTree(Data))
# 生成的tree如下:
# {'a': {0: 'no', 1: {'b': {0: 'no', 1: 'yes'}}}}

如有问题,欢迎评论或者私信与我联系。

觉得有收获,就请点个赞吧~万分感谢!

写在最后

机器学习​zhuanlan.zhihu.com
cd657376e872ea817663da1d219ed916.png

如果你对机器学习感兴趣,欢迎关注我的机器学习专栏~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值