【机器学习7】决策树

决策树实例——买计算机

一、实验内容

1. 问题描述

使用ID3算法构造出决策树。决策树也是一种二元分类方法,一个值经过相应节点的测验,要么进入真分支,要么进入假分支。所以一组值经过决策树以后,就会形成从树跟到结果节点的一条唯一路径。

2. 数据集描述

在这里插入图片描述

3. 实验环境(软硬件)

软件
  1. 操作系统:Windows 10
  2. IDE:Pycharm Community Edition
  3. Python环境:Conda Python3.8
  4. sklearn环境:sklearn 0.24.1
硬件
  1. CPU:11th Gen Intel(R) Core(TM) i5-11300H @ 3.10GHz 3.11 GHz
  2. RAM:16.0 GB (15.8 GB 可用)

二、算法原理

1. 名词解释

(1) “树”相关术语
  • 根节点:包含数据集中的所有数据的集合
  • 内部节点:每个内部节点为一个判断条件,并且包含数据集中满足从根节点到该节点所有条件的数据的集合。根据内部结点的判断条件测试结果,内部节点对应的数据的集合别分到两个或多个子节点中。
  • 边:那些带有文字的线段(一般使用有箭头的有向线段),线的一端连的是中间节点、另一端连的是另一个中间节点或叶节点,线段上有表示判断的文字。
  • 叶节点:叶节点为最终的类别,被包含在该叶节点的数据属于该类别。
(2)信息的度量——香农熵

集合信息的度量方式称为香农熵或者熵。熵越大数据的混乱度越高,数据越无序,相似度更低。概率p越小,熵就越大(不确定度越大),如果极端情况一件事情概率为1,它的熵就变成0了。

2. 信息增益

在划分数据集前后信息发生的变化称为信息增益,根据某一特征划分完成后多获得的信息量(消除的不确定度)。计算根据每个特征值划分的数据集获得的信息增益,获得信息增益最高的特征就是最好的选择。

信息增益是信息论中学过的互信息量,其含义是:知道某个特征A时,消除的对类别D的不确定度,或比之前多获得的信息量。

(1)计算数据集的经验熵

按两个类别分类

image-20220416155928890 image-20220416155959544
(2)经验条件熵

以青年为例,统计买不买的数量
P ( 青 ) P ( 中 ) P ( 老 ) P(青)\\ P(中)\\ P(老) P()P()P()
image-20220416155834644
H ( 归类 ∣ 青 ) = I ( 买 ∣ 青 ) + I ( 不买 ∣ 青 ) H ( 归类 ∣ 中 ) = I ( 买 ∣ 中 ) + I ( 不买 ∣ 中 ) H ( 归类 ∣ 老 ) = I ( 买 ∣ 老 ) + I ( 不买 ∣ 老 ) H(归类|青)=I(买|青)+I(不买|青)\\ H(归类|中)=I(买|中)+I(不买|中)\\ H(归类|老)=I(买|老)+I(不买|老) H(归类)=I()+I(不买)H(归类)=I()+I(不买)H(归类)=I()+I(不买)
image-20220416160121490

image-20220416160238162 $$ H(D|A)=H(归类|年龄)=P(青)H(归类|青)+P(中)H(归类|中)+P(老)H(归类|老) $$
(3)信息增益(互信息)

选择信息增益最大的作为下一个节点
年龄 I ( D , A ) = H ( D ) − H ( D ∣ A ) 收入 I ( D , B ) = H ( D ) − H ( D ∣ B ) 学生 I ( D , C ) = H ( D ) − H ( D ∣ C ) 信誉 I ( D , E ) = H ( D ) − H ( D ∣ E ) 年龄I(D,A)=H(D)-H(D|A)\\ 收入I(D,B)=H(D)-H(D|B)\\ 学生I(D,C)=H(D)-H(D|C)\\ 信誉I(D,E)=H(D)-H(D|E) 年龄I(D,A)=H(D)H(DA)收入I(D,B)=H(D)H(DB)学生I(D,C)=H(D)H(DC)信誉I(D,E)=H(D)H(DE)

3. ID3生成树算法

输入特征数据集,输出决策树。

  1. 取最后一列类别
    1. 判断类别是否为一类,是则直接输出即可。
    2. 如果不为一类,判断是否最后的特征(即判断特征数是否为0),是则输出实例数最大的类作为结点的类标记。
  2. 选最好的特征的索引和标签值
    1. 根据第i个特征每个取值划分数据集,计算条件熵并累加
    2. 计算第i个特征对D的信息增益
    3. 选择信息增益最大的特征作为最优特征
  3. 创建字典类型的树
  4. 根据当前最佳特征的取值,划分数据集和标签输入生成函数,返回步骤1,递归生成一条路径

三、代码分析

1. 流程图

ID3

2. 代码

用到的库

from math import log  # 香农熵用到log2
import operator 
(1)数据预处理
  • 年龄:青0 中1 老2
  • 收入:高0 中1 低2
  • 学生:否0 是1
  • 信誉:良0 优1
image-20220423134323893
if __name__ == '__main__':
    # dataSet, labels = createDataSet()
    raw_data = pd.read_csv('tree.csv')
    data = raw_data.values
    dataSet = []
    for d in data:
        d_list = d.tolist() # 数据类型arrary——>list
        for i in range(d[0]):
            dataSet.append(d_list[1::])

    labels = ['信誉', '学生', '收入', '年龄']  # 特征标签
    
    featLabels = []
    myTree = createTree(dataSet, labels, featLabels)
    print(myTree)
(2)自定义函数
- 香农熵计算函数
"""
Parameters:
    dataSet - 数据集
Returns:
    shannonEnt - 经验熵(香农熵)
"""
def calcShannonEnt(dataSet):
    # 补充该函数 H(D)=-sum(pi*logpi)
    H = 0.0  # 香农熵
    labelCounts = {}  # 计数
    n = len(dataSet)  # 样本数量

    # 统计概率
    for data in dataSet:
        datalabel = data[n-1]  # 取类别"yes","no"
        if datalabel not in labelCounts.keys():
            labelCounts[datalabel] = 0
        labelCounts[datalabel] += 1
	# 计算熵
    for key in labelCounts.keys():
        prob = float(labelCounts[key]) / n
        H -= prob * log(prob, 2)  # H(D)=-sum(pi*logpi)

    return H
- 数据集和标签划分函数
"""
Parameters:
    dataSet - 待划分的数据集
    axis - 划分数据集的特征
    value - 需要返回的特征的值
Returns:
    无
"""

# 函数说明:按照给定特征划分数据集
def splitDataSet(dataSet, axis, value):
    retDataSet = []
    for featVec in dataSet:# 每一行样本的特征向量
        if featVec[axis] == value: # axis维特征取值是否是这个分类
            reducedFeatVec = featVec[:axis]  # 该特征之前的特征
            reducedFeatVec.extend(featVec[axis + 1:]) # 之后的特征
            retDataSet.append(reducedFeatVec)
    return retDataSet
# 函数说明:按照给定特征的位置划分标签集
def splitLabels(labels, axis):
    retLabels = labels[:axis]
    retLabels.extend(labels[axis + 1:])
    return retLabels
- 选择最优特征,返回索引号
"""
Parameters:
    dataSet - 数据集
Returns:
    bestFeature - 信息增益最大的(最优)特征的索引值
"""

# 函数说明:选择最优特征
def chooseBestFeatureToSplit(dataSet):
    numFeatures = len(dataSet[0]) - 1  # 特征的维度数
    baseEntropy = calcShannonEnt(dataSet) # 经验熵
    bestInfoGain = 0.0
    bestFeature = -1

    for i in range(numFeatures):    # 遍历所有特征(第i维)
        featList = [example[i] for example in dataSet]   #  获取dataSet的第i个所有特征
        uniqueVals = set(featList)                        #  创建set集合{},元素不可重复,set去重功能
        #  uniqueVals是第i维特征有多少取值
        newEntropy = 0.0                                 #  经验条件熵初始化

        # 求信息增益
        for value in uniqueVals:
            subDataSet=splitDataSet(dataSet, i, value)
            prob = len(subDataSet) / float(len(dataSet))    # 统计value数量,占比作为先验概率
            newEntropy += prob * calcShannonEnt(subDataSet)  # 条件熵
        infoGain = baseEntropy - newEntropy                # 信息增益
        print("第%d个特征的增益为%.3f" % (i, infoGain))   #打印每个特征的信息增益

        # 找到最大信息增益和信息增益最大的特征索引值
        if (infoGain > bestInfoGain):
            bestInfoGain = infoGain
            bestFeature = i
    return bestFeature   # 返回信息增益最大的特征的索引值
- 统计出现最多的类标签
"""
Parameters:
    classList - 类标签列表
Returns:
    sortedClassCount[0][0] - 出现此处最多的元素(类标签)
"""

# 函数说明:统计classList中出现此处最多的元素(类标签)
def majorityCnt(classList):
    classCount = {}
    for vote in classList:
        if vote not in classCount.keys(): classCount[vote] = 0
        classCount[vote] += 1
    sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
    return sortedClassCount[0][0]
- 创建决策树
"""
Parameters:
    dataSet - 训练数据集
    labels - 分类属性标签
    featLabels - 存储选择的最优特征标签
Returns:
    myTree - 决策树
"""

# 函数说明:创建决策树
def createTree(dataSet, labels, featLabels):
    print("---树---")
    classList = [example[-1] for example in dataSet]  # 取最后一列数值,即分类标签(是否放贷:yes or no)
    if classList.count(classList[0]) == len(classList):  # 如果全是一类,len(classList)统计样本个数,
        # #classList.count(classList[0])统计classList[0]在classList中出现的个数,如果两者相同就说明classList中只有一类
        # print(classList)
        print('---end---')
        return classList[0]  # 则返回当前的分类


    if len(dataSet[0]) == 1:  # 没剩特征了,只剩最后的分类。原来dataSet[0]是[0,0,0,0,'no']每选一个特征属性,就会删除一列
        # #当所有特征属性都作为结点后,就只剩下最后一列:表述“yes”或“no”
        return majorityCnt(classList)  # 则数分类中最大的个数进行返回

    bestFeat = chooseBestFeatureToSplit(dataSet)  # 选出最好的特征的索引
    bestFeatLabel = labels[bestFeat]  # 最好标签的值
    print("最好特征:", bestFeatLabel)
    featLabels.append(bestFeatLabel)  # 最好标签列表增加
    myTree = {bestFeatLabel: {}}  # 构建一棵树
    # del (labels[bestFeat])  # 删除这个标签???????
    featValues = [example[bestFeat] for example in dataSet]  # 最佳标签的一列
    uniqueVals = set(featValues)  # 最佳标签的值,set去重
    for value in uniqueVals:
        print("特征值:", value)
        myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value), splitLabels(labels,bestFeat), featLabels)

    return myTree
(3)输出结果
------0个特征的增益为0.0451个特征的增益为0.1732个特征的增益为0.0183个特征的增益为0.266
最好特征: 年龄
特征值: 0
------0个特征的增益为0.0441个特征的增益为0.9182个特征的增益为0.459
最好特征: 学生
特征值: 0
------
no
---end---
特征值: 1
------
yes
---end---
特征值: 1
------
yes
---end---
特征值: 2
------0个特征的增益为0.8941个特征的增益为0.0482个特征的增益为0.046
最好特征: 信誉
特征值: 0
------
yes
---end---
特征值: 1
------0个特征的增益为0.0081个特征的增益为0.008
最好特征: 学生
特征值: 0
------0个特征的增益为0.000
最好特征: 收入
特征值: yes
------
yes
---end---
特征值: no
------
no
---end---
特征值: 1
------
no
---end---
树
(4)决策树

与上一次手动推导的信息增益比较,基本一致

{‘年龄’: {0: {‘学生’: {0: ‘no’, 1: ‘yes’}}, 1: ‘yes’, 2: {‘信誉’: {0: ‘yes’, 1: {‘学生’: {0: {‘收入’: {‘no’: ‘no’, ‘yes’: ‘yes’}}, 1: ‘no’}}}}}}

微信图片_20220420103430

3. 关键量的变化过程

数据预处理,注意类型DataFrame——>ndarray——>list

dataSet有1024个样本,每个样本除最后一维是类别外都是特征的取值

image-20220423142036359
进入生成树函数
  • 分类标签列表classList
image-20220423142504981
  • 选出最好特征(信息增益最大)的索引bestFeature

    经验熵:baseEntropy=0.9537113737696566

    (目前的还没有累加完的)经验条件熵:newEntropy=0.5664228731218516

    第一维特征是信誉有优良两种 ,所以特征取值范围uniqueVals={0,1}

    当前在计算信誉为良0value=0的信息增益

    查看被划分的数据集,共同点:第一维信誉特征都是良value=0,保留除信誉以外的特征

    image-20220423145559397

    计算信息增益infoGain,更新最大信息增益的下标(也是返回值)bestFeature

沿着最好的特征递归构建决策树

树是字典类型dict,此时最好特征是年龄 ,有老中青三种取值uniqueVals={0,1,2}

image-20220423150840221
递归构建决策树

年龄0——>学生0——>no 路径结束

image-20220423151956003 image-20220423151211058 ad0a1dfdb5746b07f466c964352094e

四、 总结

1. 遇到的问题与解决方法

(1)思考如何组织输入数据的结构?

因为createTree(dataSet, labels, featLabels)函数的参数格式均为list型,所以该实验的数据预处理需要特别注意:

  1. 数据以csv格式的文件读入raw_data = pd.read_csv('tree.csv')数据格式 DataFrame

  2. 转成ndarray格式data = raw_data.values

  3. 提取有效特征数据,并转成list格式d_list = d.tolist()

    • 每一个样本的第0维表示重复次数

    • 第一维及其以后是实际要用到的特征数据

    • 循环重复写入样本特征,每类样本重复次数为第一维的“计数”

      for i in range(d[0]): dataSet.append(d_list[1::])

  4. 标签labels = ['信誉', '学生', '收入', '年龄'] # 特征标签

(2)Debug:增加标签划分函数

决策树中一条从根节点到叶子节点的路径下,经过的标签不重复。为保证这一点上一个例子使用的方法为:每向下延申一层,就删除经历过的特征标签(如下)。

del (labels[bestFeat])  # 删除这个标签

但是在递归生成树的时候,被删掉的标签就不再出现,会影响周围路径的生成,导致超出索引范围。所以改用自己写的“标签划分函数”,其与数据集划分函数的算法原理相同:剔除当前位置的标签,之前的标签和之后的标签拼接重组。

splitLabels(labels,bestFeat)
"""
Parameters:
    dataSet - 待划分的标签列表
    axis - 划分数据集的特征下标
Returns:
    无
"""
def splitLabels(labels, axis):
    retLabels = labels[:axis]
    retLabels.extend(labels[axis + 1:])
    return retLabels

2. 知识点总结

分类器对比
  • 上周学的朴素贝叶斯分类器将概率论于数理统计的知识运用到分类中,找出输出类别Y与特征X之间的关系,即联合概率分布P(X,Y)

  • 本周所学的决策树是从信息论的角度分析如何划分数据。信息论用随机事件发生的概率来度量事件的“不确定度”,将随机事件所含有的不确定度用数学公式表示出来,集合信息的度量方式称为香农熵或者熵。决策树引入香农信息论中熵的概念。

决策树通过递归生成
  • 每一条向下延申的”树枝“,生成过程是按照数据的内在规律不断划分数据集为小的分支,直到叶子节点,则分类完毕。
  • 选择最好的划分方式,就要先选择最好的划分特征,我们希望收获的信息量越多越好,即划分之后熵越小越好,即数据越整齐越好。
    • 我们将对每个特征划分的数据集求计算一次熵,然后判断该特征是否为最好的数据集划分特征(即一个决策过程)。这里将每一个特征作为一次数据集的划分依据,然后求得划分后的数据集的熵newEntropy,当新的数据集的熵最小时,即选取该特征划分数据集前后获得最大的信息增益时,数据的混乱度降低,该特征为最好的数据集划分特征。
    • 上述数据集划分结束的标志是:每一条记录都分配到一个叶子节点,每次选取一个特征后,都将数据集划分为几个更小的数据集,我们只要对分割后的分支数据集继续使用chooseBestFeatureToSplit函数即可继续划分数据集,递归可以简单的实现这一过程。
  • 我们在实际操作中,还可以指定叶子结点的个数(即分类数,C4.5和CART算法)

3. 算法的思考

信息增益相同时如何抉择?

该例子中最后两类特征的信息增益相同应该怎么选择?此代码是选择排在前面的特征,在实际情况下这种方式是否合理?

0个特征的增益为0.0081个特征的增益为0.008
过度拟合问题

比如最后一个计数为1的样本,就像是噪声数据。决策树算法增长树的每一个分支的深度,直到恰好能对训练样例比较完美地分类。实际应用中,当数据中有噪声或训练样例的数量太少以至于不能产生目标函数的有代表性的采样时,该策略可能会遇到困难。

目前是二分类问题,能否推广到多分类问题?

**时,数据的混乱度降低,该特征为最好的数据集划分特征。

  • 上述数据集划分结束的标志是:每一条记录都分配到一个叶子节点,每次选取一个特征后,都将数据集划分为几个更小的数据集,我们只要对分割后的分支数据集继续使用chooseBestFeatureToSplit函数即可继续划分数据集,递归可以简单的实现这一过程。
  • 我们在实际操作中,还可以指定叶子结点的个数(即分类数,C4.5和CART算法)

3. 算法的思考

信息增益相同时如何抉择?

该例子中最后两类特征的信息增益相同应该怎么选择?此代码是选择排在前面的特征,在实际情况下这种方式是否合理?

0个特征的增益为0.0081个特征的增益为0.008
过度拟合问题

比如最后一个计数为1的样本,就像是噪声数据。决策树算法增长树的每一个分支的深度,直到恰好能对训练样例比较完美地分类。实际应用中,当数据中有噪声或训练样例的数量太少以至于不能产生目标函数的有代表性的采样时,该策略可能会遇到困难。

目前是二分类问题,能否推广到多分类问题?
  • 1
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值