机器学习作业决策树算法

目录

一、什么是决策树

1.1决策树简介

1.2决策树特点

1.3构造决策树

1.4决策树算法的优缺点

二、学会构造决策树算法

2.1算法简介

2.1.1算法优点

2.2决策树构造

2.2.1构造

2.2.2信息熵的概念

 三、决策树算法实现(ID3)

运行结果


一、什么是决策树

1.1决策树简介

决策树:是一种基本的分类与回归方法,是一个类似于流程图的树状结构。其中,每个内部结点表示在一个属性上的测试,每个分支代表一个属性输出,而每一个树叶结点代表类或类分布。树的最顶层是根结点。它可以认为是if-then规则的集合,也可以认为是定义在特征空间与类空间上的条件概率分布

1.2决策树特点

相较于k-近邻算法可以完成很多分类任务,但是其最大的缺点是无法给出数据的内在含义,决策树的优势在于数据形式非常容易理解。

决策树通常有三个步骤:特征选择、决策树的生成、决策树的修剪

用决策树分类:从根节点开始,对实例的某一特征进行测试,根据测试结果将实例分配到其子节点,此时每个子节点对应着该特征的一个取值,如此递归的对实例进行测试并分配,直到到达叶子节点,最后将实例分到叶节点的类中。通俗简单的说,决策树原理和问答猜测结果游戏相似,根据一系列数据,然后给出游戏的答案,

相较于k-近邻算法可以完成很多分类任务,但是其最大的缺点是无法给出数据的内在含义,决策树的优势在于数据形式非常容易理解。

1.3构造决策树

1、构建根节点,正确分类子集构建叶节点,并将这些子集分到所对应的叶节点。2、若还有子集不能够被正确的分类,那么就对这些子集选择新的最优特征,继续对其进行分割,构建相应的节点,如此递归进行,直至所有训练数据子集被基本正确的分类,3、或者没有合适的特征为止。每个子集都被分到叶节点上,即都有了明确的类,这样就生成了一颗决策树。

1.4决策树算法的优缺点

决策树算法的优点:

1、决策树算法易理解,机理解释起来简单。

2、决策树算法可以用于小数据集。

3、能够处理多输出的问题。

4、对缺失值不敏感。

5、效率高,决策树只需要一次构建,反复使用,每一次预测的最大计算次数不超过决策树的深度。

决策树算法的缺点:

1、对连续性的字段比较难预测。

2、容易出现过拟合。

3、当类别太多时,错误可能就会增加的比较快。

4、在处理特征关联性比较强的数据时表现得不是太好。

5、对于各类别样本数量不一致的数据,在决策树当中,信息增益的结果偏向于那些具有更多数值的特征。

6、对于那些各类别样本数量不一致的数据,在决策树中,进行属性划分时,不同的判定准则会带来不同的属性选择倾向;信息增益准则对可取数目较多的属性有所偏好(典型代表ID3算法),而增益率准则(CART)则对可取数目较少的属性有所偏好,但CART进行属性划分时候不再简单地直接利用增益率尽心划分,而是采用一种启发式规则)(只要是使用了信息增益,都有这个缺点,如RF)。

7、ID3算法计算信息增益时结果偏向数值比较多的特征。

二、学会构造决策树算法

2.1算法简介

决策树的典型算法有ID3,C4.5,CART等

决策树算法是一种逼近离散函数值的方法。它是一种典型的分类方法,首先对数据进行处理,利用归纳算法生成可读的规则和决策树,然后使用决策对新数据进行分析。本质上决策树是通过一系列规则对数据进行分类的过程。

决策树方法最早产生于上世纪60年代,到70年代末。由J Ross Quinlan提出了ID3算法,此算法的目的在于减少树的深度。但是忽略了叶子数目的研究。C4.5算法ID3算法的基础上进行了改进,对于预测变量的缺值处理、剪枝技术、派生规则等方面作了较大改进,既适合于分类问题,又适合于回归问题。

决策树算法构造决策树来发现数据中蕴涵的分类规则.如何构造精度高、规模小的决策树是决策树算法的核心内容。决策树构造可以分两步进行。

第一步,决策树的生成:由训练样本集生成决策树的过程。一般情况下,训练样本数据集是根据实际需要有历史的、有一定综合程度的,用于数据分析处理的数据集。

第二步,决策树的剪枝:决策树的剪枝是对上一阶段生成的决策树进行检验、校正和修下的过程,主要是用新的样本数据集(称为测试数据集)中的数据校验决策树生成过程中产生的初步规则,将那些影响预衡准确性的分枝剪除。

2.1.1算法优点

决策树算法的优点如下:

(1)分类精度高;

(2)生成的模式简单;

(3)对噪声数据有很好的健壮性。

2.2决策树构造

2.2.1构造

决策树构造的输入是一组带有类别标记的例子,构造的结果是一棵二叉树或多叉树。二叉树的内部节点(非叶子节点)一般表示为一个逻辑判断,如形式为a=aj的逻辑判断,其中a是属性,aj是该属性的所有取值:树的边是逻辑判断的分支结果。多叉树(ID3)的内部结点是属性,边是该属性的所有取值,有几个属性值就有几条边。树的叶子节点都是类别标记。 [3] 

由于数据表示不当、有噪声或者由于决策树生成时产生重复的子树等原因,都会造成产生的决策树过大。因此,简化决策树是一个不可缺少的环节。寻找一棵最优决策树,主要应解决以下3个最优化问题:①生成最少数目的叶子节点;②生成的每个叶子节点的深度最小;③生成的决策树叶子节点最少且每个叶子节点的深度最小。

2.2.2信息熵的概念

这里引入信息熵的概念:与我们在热力学定律中学习到的热熵相似,信息熵是从信息的有序性上进行定义的,当数据量一致时,系统越有序,熵值越低;系统越混乱或者分散,熵值越高

在数学上,信息熵是这样计算的(需要加上权重):

其中 为集合中第K类属性所占样本的比例。

Ent(D)的值越小,则D的纯度越高

 

 三、决策树算法实现(ID3)

构造数据集,饮食(中脂肪中碳水高蛋白)和锻炼对减肥的影响

    dataSet = [[1, 1, 1, 'yes'],
               [1, 1, 1, 'yes'],
               [0, 1, 1, 'no'],
               [1, 0, 1, 'yes'],
               [1, 0, 1, 'yes'],
               [0, 0, 1, 'no']]
    labels = ['吃中脂中碳', '每天锻炼','高蛋白']

计算信息熵

def calcShanonEnt(dataSet):
    :param dataSet:划分好的数据集
    :return:子数据集的信息熵,还没有考虑权重进来
    """

    # 数据中实例的总数
    numEntries = len(dataSet)
    # 为数据集所有可能性划分字典
    labelCounts = {}
    for featVec in dataSet:
        currentLabel = featVec[-1]
        # 如果字典中不存在,添加到字典中
        if currentLabel not in labelCounts.keys():
            labelCounts[currentLabel] = 0
        # 对存在的结果计数
        labelCounts[currentLabel] += 1
        # print(f'我是labelCounts2:\n{labelCounts}')
        # 得到{'yes': 2, 'no': 3}
    shannonEnt = 0.0
    for key in labelCounts:
        prob = float(labelCounts[key]) / numEntries
        shannonEnt -= prob * log(prob, 2)
    # print(f'我是初始信息熵shannonEnt:\n{shannonEnt}')
    return shannonEnt

构建决策树

def createTree(dataSet, labels):
    """
    构造树的过程
    :param dataSet:
    :param labels:
    :return:
    """
    classList = [example[-1] for example in dataSet]
    print(f'我是classList:\n{classList}')
    # 递归函数的第一个停止条件是所有的类标签完全相同,则直接返回该类标签
    # print(classList.count(classList[0],'==',len(classList)))
    # print(classList.count(classList[0]==len(classList)))
    if classList.count(classList[0]) == len(classList):
        """
        通过打印发现错误,第一个if判断语句的地方没有进来
        """
        print('yyyyy', classList[0])
        return classList[0]
    # 递归函数的第二个停止条件是使用完了所有特征,仍然不能将数据集划分成仅包含唯一类别的分组
    elif len(dataSet[0]) == 1:
        return majorityCnt(classList)
    # print(f'我是classList[0]:\n{classList[0]}')
    # return majorityCnt(classList[0])
    # if len(dataSet[0]) == 1:
    #     return majorityCnt(classList)
    bestFeat = chooseBestFeatureToSplit(dataSet)
    print(f'bestFeat:\n{bestFeat}')
    bestFeatLabel = labels[bestFeat]
    print(f'我是bestFeatLabel:\n{bestFeatLabel}')
    # 分类结果储存到字典中
    myTree = {bestFeatLabel: {}}
    # 在标签列表中删除最佳标签
    del (labels[bestFeat])
    # 得到最佳标签一列的所有可能特征值集合
    featValues = [example[bestFeat] for example in dataSet]
    uniqueVals = set(featValues)
    # 遍历特征值集合
    for value in uniqueVals:
        # 复印列表清单
        subLabels = labels[:]
        # 递归迭代,逐条插入字典
        myTree[bestFeatLabel][value] = createTree(spliDataStet(dataSet, bestFeat, value), subLabels)
    return myTree

 全部代码

# coding:utf-8
from math import log
import operator


def createDataSet():
    """
    传入要处理的数据集
    :return: 数据集内容,数据集标签
    """
    dataSet = [[1, 1, 1, 'yes'],
               [1, 1, 1, 'yes'],
               [0, 1, 1, 'no'],
               [1, 0, 1, 'yes'],
               [1, 0, 1, 'yes'],
               [0, 0, 1, 'no']]
    labels = ['吃中脂中碳', '每天锻炼','高蛋白']
    # print(f'dataset:\n{dataSet}')
    return dataSet, labels


def calcShanonEnt(dataSet):
    """
    计算数据集的信息熵
    此处还没有乘权重
    :param dataSet:划分好的数据集
    :return:子数据集的信息熵,还没有考虑权重进来
    """

    # 数据中实例的总数
    numEntries = len(dataSet)
    # 为数据集所有可能性划分字典
    labelCounts = {}
    for featVec in dataSet:
        currentLabel = featVec[-1]
        # 如果字典中不存在,添加到字典中
        if currentLabel not in labelCounts.keys():
            labelCounts[currentLabel] = 0
        # 对存在的结果计数
        labelCounts[currentLabel] += 1
        # print(f'我是labelCounts2:\n{labelCounts}')
        # 得到{'yes': 2, 'no': 3}
    shannonEnt = 0.0
    # 此部分用于计算一个数据集的信息熵,还没有加入权重(不一定是二叉树,有几个key分支就包含几种,这里考虑到了)
    for key in labelCounts:
        prob = float(labelCounts[key]) / numEntries
        shannonEnt -= prob * log(prob, 2)
    # print(f'我是初始信息熵shannonEnt:\n{shannonEnt}')
    return shannonEnt


# 把这个数据集给写活很重要
def spliDataStet(dataSet, axis, value):
    """
    递归的思想在此处有体现,被抽出去递归的部分
    :param dataSet: 待划分的数据集
    :param axis: 划分数据集的特征
    :param value: 特征的返回值;value应该是一个变量
    :return:子数据集
    """

    retDataSet = []
    for featVec in dataSet:
        if featVec[axis] == value:
            # 除去这一元素外的其它元素组成新的数列
            """
            这里注意一下,主要卡在这里了
            """
            reducedFeatVec = featVec[:axis]
            reducedFeatVec.extend(featVec[axis + 1:])
            retDataSet.append(reducedFeatVec)
    # print(f'我是retDataSet:\n{retDataSet}')
    return retDataSet


def chooseBestFeatureToSplit(dataSet):
    """
    对于信息熵的计算
    :param dataSet:
    :return:
    """
    # 得到特征数据的长度(特征的个数) 2
    numFeatures = len(dataSet[0]) - 1
    # 原数据集的信息熵
    baseEntropy = calcShanonEnt(dataSet)
    # 定义初始的熵增益
    bestInfoGain = 0.0;
 
    bestFeature = 0;
    for i in range(numFeatures):
        # 取某一列特征的值,example在此处代表列 我是featList:[1, 1, 0, 1, 1]
        featList = [example[i] for example in dataSet]
        # set集合去重取出每一特征下的所有可能情况 我是uniqueVals:{0, 1}
        uniqueVals = set(featList)
        # 定义一个变量为新的信息熵
        newEntropy = 0.0
        # 结合上方,按照某一个特征的不同value可以将数据集分割成len(uniqueVals)个子集,分割子数据集,并对子数据集求信息熵
        for value in uniqueVals:
            subDataSet = spliDataStet(dataSet, i, value)
            # print(f'我是subDataSet:\n{subDataSet}')
            # 此段代码中,prob是在某一特征分类下的权重,而self.calcShanonEnt函数计算出的为数据集的信息熵
            prob = len(subDataSet) / float(len(dataSet))
            # 得到的为按某一特征划分后的信息熵
            newEntropy += prob * calcShanonEnt(subDataSet)
        # 熵增益
        infoGain = baseEntropy - newEntropy
        # infoGain = newEntropy
        if (infoGain > bestInfoGain):
            bestInfoGain = infoGain
            bestFeature = i
    # print(f'bestFeature:\n{bestFeature}')
    return bestFeature


# 按分类后类别数量排序
def majorityCnt(classList):
    # 创建键值为classList中唯一值的数据字典,字典对象存储了classList中每个类标签出现的频率
    # 利用operator操作键值排序字典,并返回出现次数最多的分类名称
    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)
    # print(f'我是sortedClassCount:\n{sortedClassCount}')
    # print(f'我是sortedClassCount:\n{sortedClassCount[0][0]}')
    return sortedClassCount[0][0]


def createTree(dataSet, labels):
    """
    构造树的过程
    :param dataSet:
    :param labels:
    :return:
    """
    classList = [example[-1] for example in dataSet]
    print(f'我是classList:\n{classList}')
    # 递归函数的第一个停止条件是所有的类标签完全相同,则直接返回该类标签
    # print(classList.count(classList[0],'==',len(classList)))
    # print(classList.count(classList[0]==len(classList)))
    if classList.count(classList[0]) == len(classList):
        """
        通过打印发现错误,第一个if判断语句的地方没有进来
        """
        print('yyyyy', classList[0])
        return classList[0]
    # 递归函数的第二个停止条件是使用完了所有特征,仍然不能将数据集划分成仅包含唯一类别的分组
    elif len(dataSet[0]) == 1:
        return majorityCnt(classList)
    # print(f'我是classList[0]:\n{classList[0]}')
    # return majorityCnt(classList[0])
    # if len(dataSet[0]) == 1:
    #     return majorityCnt(classList)
    bestFeat = chooseBestFeatureToSplit(dataSet)
    print(f'bestFeat:\n{bestFeat}')
    bestFeatLabel = labels[bestFeat]
    print(f'我是bestFeatLabel:\n{bestFeatLabel}')
    # 分类结果储存到字典中
    myTree = {bestFeatLabel: {}}
    # 在标签列表中删除最佳标签
    del (labels[bestFeat])
    # 得到最佳标签一列的所有可能特征值集合
    featValues = [example[bestFeat] for example in dataSet]
    uniqueVals = set(featValues)
    # 遍历特征值集合
    for value in uniqueVals:
        # 复印列表清单
        subLabels = labels[:]
        # 递归迭代,逐条插入字典
        myTree[bestFeatLabel][value] = createTree(spliDataStet(dataSet, bestFeat, value), subLabels)
    return myTree


if __name__ == '__main__':
    dataSet, labels = createDataSet()  # 创造示列数据
    print(createTree(dataSet, labels))  # 输出决策树模型结果

运行结果

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值