决策树与随机森林实验报告(纯Python实现)

一、实验内容简介

该实验主要利用ID3算法和已有的数据建立决策树和随机森林,并利用决策树和随机森林来预测未知数据的值。本实验使用Python实现。

二、算法说明

下面介绍几个基础但很重要的概念:

  • 决策树:决策树是在已知各种情况发生概率的基础上,通过构成决策树来求取净现值的期望值大于等于零的概率,评价项目风险,判断其可行性的决策分析方法,是直观运用概率分析的一种图解法。由于这种决策分支画成图形很像一棵树的枝干,故称决策树。在机器学习中,决策树是一个预测模型,他代表的是对象属性与对象值之间的一种映射关系。

  • 随机森林:随机森林是一种包含很多决策树的分类器,既可以用于处理分类和回归问题,也适用于降维问题。其对异常值与噪音也有很好的容忍,相较于决策树有着更好的预测和分类性能。

  • ID3算法:该算法是本实验的重点。ID3 算法的核心是根据信息增益来选择进行划分的特征,然后递归地构建决策树。它的思想并不复杂,步骤也比较简单。下面简单介绍一下ID3算法的步骤。

  1. 从根节点开始,计算所有可能的特征的信息增益,选择信息增益最大的特征,来划分节点。

  2. 按该特征的不同取值建立子节点。

  3. 如果没有特征可以选择或类别完全相同,则得到决策树,该算法结束。否则转向第4步。

  4. 对子节点递归1、2步。

三、算法分析与设计

说明:因为屎山代码比较长,所以这里部分只给出函数名,函数具体实现请见附录。

学习完算法的基本原理后,现在开始真正实现该算法。实现该算法就没有理解算法思想那么容易了。这里运用面向对象的思想,把决策树和随机森林分开来写,这样不至于混淆。

首先实现决策树,按照算法步骤首先需要计算信息增益,而想要计算信息增益首先需要计算熵和条件熵。这里把它们分到三个方法里:

def entropy(self, condition=None):
def entropy_condition(self, condition=None):
def information_gain(self, condition):

关于实现的具体过程,就是按照相关的数学表达式实现即可。比如说信息增益,等于最终特征本身的熵,减去给定A的条件下该特征的条件熵。
接着实现信息增益最大的节点进行分裂,方法很简单,就是计算出所有节点的信息增益,然后选择信息增益最大的节点即可。

def find_best(self, condition=None):

然后就可以使用递归的方法建立决策树,每走一步就算一遍信息增益,然后建立一个子节点。最后结果返回整棵决策树。

def create_tree(self, condition=None):

最后,也就是最重要的一步,那就是预测值,因为构建决策树是为了把数据分类,最终目的就是预测未知数据的值。因为在Python中我使用字典套字典的数据类型来存储树,具体代码就是每到一个key就识别一次,如果value是最终结果的话就返回,如果还是字典的话就递归下去,直到获取到最终的值。

def predict(self, info, t={}):

到这里决策树建立完毕,接下来就要建立随机森林了。一般来说,随机森林有很多棵树,但是这里因为数据和个人能力的原因,就只分了两棵树。具体步骤也不难,就是把数据集均分成两个,分别建立各自的决策树,然后按各自的预测结果汇总,数量最多的结果作为最终的预测结果。如果两种决策结果一样多,就返回结果不确定。
在这里,代码实现的主要难点在于数据集的划分,以及对应预测数据的划分,因为是随机选择的划分方法,很容易出现两者划分方法不一致,这样预测过程就会出问题,代码就会经常报错。最后处理的方法就是在划分数据集的时候把划分方式记录下来,来适配预测数据的划分模式。具体代码比较繁琐,就不展示了。
下面是使用的主要函数。

def build_forest(self, info=None):
def predict(self, info):
def split_dict_by_key(dictionary, keys, result):
def split_dict_by_key2(dictionary, keys, result):

四、测试结果

在写完代码后,最重要的就是进行测试,来分析其正确性与性能。

这里的数据量比较小,只有28条数据和5个特征,但也足够用来简单实现决策树和随机森林了。

首先测试决策树,先输出决策树:

img

然后测试两条数据,分别是[‘rain’, ‘mild’, ‘normal’, ‘no’]和[‘sunny’,‘hot’,‘high’,‘no’],然后给出预测结果。

img

与真实结果相吻合,这说明这棵决策树具备了一定的预测能力。

然后测试随机森林,先输出随机森林:

img

用元组来存储了这两棵树,然后就上面两条数据来进行预测,给出预测结果。

img

img

出现了两种不同的结果,分析具体的原因,是因为每棵树只分到了两个特征,而有可能这两个特征的信息增益比较低,不能很好地预测或者说预测效果很差。但当森林里有很多棵树时,这个问题可以大大缓解,并提升预测准确性。

下面给出总程序的预测结果:

img

五、分析与探讨

测试完算法后,来分析它的性能和优劣。首先分析ID3算法,ID3算法作为决策树算法较早出现的一个算法,在设计上有着它的瓶颈和缺陷。比如说ID3并没有考虑连续特征,对可取值数目较多的属性有倾向性,没有考虑过拟合的问题等。在ID3算法之后出现的决策树算法中,也借鉴了它ID3算法的思想。比如说C4.5算法,只不过把信息增益改为了信息增益比,就解决了倾向性问题和不能处理连续型数据的问题。又比如CART算法,生成二叉树,能同时处理连续型和离散型数据,还能处理数据缺失的问题,无疑是对以前算法的较大改进。

再来分析随机森林,虽然我程序里的随机森林比较低效,但事实上随机森林是有着比单棵决策树更好的预测能力,有着比决策树更加卓越的性能。比如说它可以判断特征的重要程度、不容易过拟合、实现简单、平衡误差等优势。这些优点使得随机森林成为机器学习中十分重要的算法。但即使如此,它也跟其他算法一样,有它本身的缺陷。其中最大的两个缺陷是1、随机森林已经被证明在某些噪音较大的分类或回归问题上会过拟合。2、对于有不同取值的属性的数据,取值划分较多的属性会对随机森林产生更大的影响,所以随机森林在这种数据上产出的属性权值是不可信的。因此,在使用不同的机器学习算法时,要充分考虑不同数据和算法的特点。没有最好的算法,只有最合适的算法。

附录:源代码

# 决策树和随机森林(Python实现)
import time
import math
import random
import pandas as pd

# 实现ID3算法
class ID3:
    # 初始化
    def __init__(self, data, label, d):
        self.data = data
        self.condition_data = []
        self.tree = None
        self.label = label
        self.Dict = d
        self.black = set()
        self.queue = []

    def entropy(self, condition=None):
        """
        计算熵
        :param condition: 条件
        :return:
        """
        if not condition:
            numEntries = len(self.data)
            Labels = dict()
            for value in self.data:
                currentLabel = value[self.label[-1]]
                if currentLabel not in Labels.keys():
                    Labels[currentLabel] = 1
                else:
                    Labels[currentLabel] += 1
            shang = 0.0
            for Label in Labels.keys():
                p = float(Labels[Label]) / numEntries
                shang -= p * math.log2(p)
            return shang
        else:
            self.condition_data.clear()
            for j in range(len(self.data)):
                if self.data[j][condition[0]] == condition[1]:
                    self.condition_data.append(self.data[j])
            numEntries = len(self.condition_data)
            Labels = dict()
            for value in self.condition_data:
                currentLabel = value[self.label[-1]]
                if currentLabel not in Labels.keys():
                    Labels[currentLabel] = 1
                else:
                    Labels[currentLabel] += 1
            shang = 0.0
            for Label in Labels.keys():
                p = float(Labels[Label]) / numEntries
                shang -= p * math.log2(p)
            return shang

    def entropy_condition(self, condition=None):
        """
        计算条件熵
        :param condition:条件
        :return:
        """
        shang = 0.0
        d = dict()
        for value in self.data:
            if value[condition] not in d.keys():
                d[value[condition]] = 1
            else:
                d[value[condition]] += 1
        for value in d.keys():
            p = float(d[value]) / len(self.data)
            shang += self.entropy((condition, value)) * p
        return shang

    def information_gain(self, condition):
        """
        计算信息增益
        :param condition: 第几个条件
        :return:信息增益
        """
        return self.entropy() - self.entropy_condition(condition)

    def find_best(self, condition=None):
        """
        寻找信息增益最大的一项
        :param condition: 条件
        :return: 最大项,以及最大项具体各个值的信息
        """
        Max = 0
        lab = None
        dic = {}  # 字典,如{'sunny':[1,1],...} 第一项为pos,第二项为neg
        for i in self.label[:-1]:
            if i in self.black:
                continue
            if self.information_gain(i) > Max:
                Max = self.information_gain(i)
                lab = i
        self.queue.append(lab)
        num = self.label.index(lab)
        for value in self.Dict[num][lab]:
            dic[value] = [0, 0]
        for value in self.data:
            if condition and value[condition[0]] != condition[1]:
                continue
            if value['playtennis'] == 'positive':
                dic[value[lab]][0] += 1
            else:
                dic[value[lab]][1] += 1
        return lab, dic

    def create_tree(self, condition=None):
        """
        建立决策树
        :param condition:条件
        :return: 返回决策树
        """
        my_tree = {}
        if len(self.black) <= len(self.label) - 2:
            lab, dic = self.find_best(condition)
            my_tree = {lab: {}}
            for value in dic.keys():
                if dic[value][1] <= 1:  # 基本全为pos
                    my_tree[lab][value] = 'positive'
                elif dic[value][0] <= 1:  # 基本全为neg
                    my_tree[lab][value] = 'negative'
                else:
                    self.black.add(lab)
                    my_tree[lab][value] = self.create_tree((lab, value))
                    if my_tree[lab][value] == {}:
                        if dic[value][0] > dic[value][1]:
                            my_tree[lab][value] = 'positive'
                        else:
                            my_tree[lab][value] = 'negative'
        return my_tree

    def predict(self, info, t={}):
        """
        根据信息预测最有可能的值
        :param info: 信息
        :param t: 辅助参数
        :return: 预测结果
        """
        self.black.clear()
        if t == {}:
            tree = self.create_tree()
        else:
            tree = t
        for i in tree.keys():
            num = self.label.index(i)
            if tree[i][info[num]] == 'positive':
                return 'positive'
            elif tree[i][info[num]] == 'negative':
                return 'negative'
            else:
                return self.predict(info, tree[i][info[num]])


# 扩展:随机森林(两颗决策树)
class Forest:
    # 初始化
    def __init__(self, data, Dict):
        self.data = data
        self.forest = []
        self.Dict = Dict
        self.information = []

    def build_forest(self, info=None):
        """
        建立随机森林(基于决策树)
        :param info: 条件
        :return: 随机森林
        """
        labels = list(self.data[0].keys())[:-1]
        one = random.sample(labels, len(labels) // 2)
        two = list(set(labels) - set(one))
        if info:
            l = []
            r = []
            for j in info:
                for i in self.Dict[:-1]:
                    if j in list(i.values())[0] and list(i.keys())[0] in one:
                        l.append(j)
                        break
                    if j in list(i.values())[0] and list(i.keys())[0] in two:
                        r.append(j)
                        break
            for i in self.Dict[:-1]:
                if one[0] == list(i.keys())[0]:
                    if l[0] in list(i.values())[0]:
                        break
                    else:
                        l.reverse()
                        break
            for i in self.Dict[:-1]:
                if two[0] == list(i.keys())[0]:
                    if r[0] in list(i.values())[0]:
                        break
                    else:
                        r.reverse()
                        break
            self.information = [l, r]
        one.append(list(self.data[0].keys())[-1])
        two.append(list(self.data[0].keys())[-1])
        one_dict = split_dict_by_key(self.data, one, list(self.data[0].keys())[-1])
        two_dict = split_dict_by_key(self.data, two, list(self.data[0].keys())[-1])

        one_dict2 = split_dict_by_key2(self.Dict, one[:-1], list(self.data[0].keys())[-1])
        two_dict2 = split_dict_by_key2(self.Dict, two[:-1], list(self.data[0].keys())[-1])
        one_tree = ID3(one_dict, one, one_dict2)
        two_tree = ID3(two_dict, two, two_dict2)

        o = one_tree.create_tree()
        t = two_tree.create_tree()
        self.forest.append(one_tree)
        self.forest.append(two_tree)
        return o, t

    def predict(self, info):
        """
        随机森林预测
        :param info: 条件
        :return: 预测值
        """
        if not self.forest:
            print("随机森林为", self.build_forest(info))
        else:
            self.forest.clear()
            self.build_forest(info)
        a = self.forest[0].predict(self.information[0])
        b = self.forest[1].predict(self.information[1])
        if a == b:
            return a
        else:
            return '不确定'


# 字典分裂的两个函数
def split_dict_by_key(dictionary, keys, result):
    sub_dict = [{keys[0]: i[keys[0]], keys[1]: i[keys[1]], result: i[result]} for i in dictionary]
    return sub_dict


def split_dict_by_key2(dictionary, keys, result):
    sub_dict = []
    keys.append(result)
    for i in keys:
        for j in dictionary:
            if i == list(j.keys())[0] and j not in sub_dict:
                sub_dict.append(j)
                break
    return sub_dict


if __name__ == '__main__':
    begin = time.time()
    # 读取数据以及准备数据
    data = pd.read_csv("data.csv")
    data = data.to_dict(orient='records')
    label = ['outlook', 'temperature', 'humidity', 'wind', 'playtennis']
    Dict = [{'outlook': ['sunny', 'overcast', 'rain']},
            {'temperature': ['hot', 'mild', 'cool']},
            {'humidity': ['high', 'normal']},
            {'wind': ['yes', 'no']},
            {'playtennis': ['positive', 'negative']}]
    print("以下是单纯的决策树算法:")
    # 实例化对象
    a = ID3(data, label, Dict)
    # 输出决策树
    print('决策树为', a.create_tree())
    # 给定一组值,预测结果
    info = ['rain', 'mild', 'normal', 'no']
    info2=['sunny','hot','high','no']
    print(f'[rain,mild,normal,no]预测结果为{a.predict(info)}')
    print(f'[sunny,hot,high,no]预测结果为{a.predict(info2)}')
    # 随机森林
    print("----------------------------\n以下是引入随机森林的算法:")
    f = Forest(data, Dict)
    print(f'[rain,mild,normal,no]预测结果为{f.predict(info)}')
    print(f'[sunny,hot,high,no]预测结果为{f.predict(info2)}')
    # 运行时间
    print("本次运行共花费了%.3f秒" % (time.time() - begin))

附录:csv

outlook,temperature,humidity,wind,playtennis

sunny,hot,high,no,negative

sunny,hot,high,yes,negative

overcast,hot,high,no,positive

rain,mild,high,no,positive

rain,cool,normal,no,positive

rain,cool,normal,yes,negative

overcast,cool,normal,yes,positive

sunny,mild,high,no,negative

sunny,cool,normal,no,positive

rain,mild,normal,no,positive

sunny,mild,normal,yes,positive

overcast,mild,high,yes,positive

overcast,hot,normal,no,positive

rain,mild,high,yes,negative

sunny,hot,high,no,negative

sunny,hot,high,yes,negative

overcast,hot,high,no,positive

rain,mild,high,no,positive

rain,cool,normal,no,positive

rain,cool,normal,yes,negative

overcast,cool,normal,yes,positive

sunny,mild,high,no,negative

sunny,cool,normal,no,positive

rain,mild,normal,no,positive

sunny,mild,normal,yes,positive

overcast,mild,high,yes,positive

overcast,hot,normal,no,positive

rain,mild,high,yes,negative

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/538065.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

如何恢复 iPhone 删除的照片?

照片是iPhone空间不足的主要原因&#xff0c;因此许多用户选择删除一些重复或不喜欢的图片来释放设备。然而&#xff0c;人们在清洁过程中不小心遗漏了自己喜欢的照片的情况很常见。如果你找不到这些珍贵的照片&#xff0c;你一定很难过。其实&#xff0c;您不必担心这个问题&a…

Android 纵向双选日历

这个日历的布局分两部分&#xff0c;一部分是显示星期几的LinearLayout&#xff0c;另外就是一个RecyclerView&#xff0c;负责纵向滚动了。 工具类&#xff1a; implementation com.blankj:utilcode:1.17.3上activity_calendar代码&#xff1a; <?xml version"1.0&…

【教资】总结经验篇

4月.12日概述 今天是2024年上半学期中小学出成绩的一天&#xff0c;查到成绩的那一刻是灰常让人激动的&#xff0c;很开心&#xff0c;特此记下此时的真实感受&#xff0c;我也没有去问别人怎么样&#xff0c;特此针对自己以记之&#xff0c;加上最近有点摆烂&#xff0c;所以…

重磅!李彦宏内部讲话曝光,百度AI闭源策略引爆争议!|TodayAI

最近&#xff0c;百度公司创始人、董事长兼CEO李彦宏的一次内部讲话内容被公之于众。在这次讲话中&#xff0c;李彦宏表达了几个与行业普遍看法相左的观点&#xff0c;尤其在开源与闭源策略的选择上&#xff0c;引发了业界的广泛关注和讨论。 李彦宏在讲话中明确指出了百度对开…

gitlab、jenkins安装及使用文档二

安装 jenkins IP地址操作系统服务版本192.168.75.137Rocky9.2jenkins 2.450-1.1 jdk 11.0.22 git 2.39.3192.168.75.138Rocky9.2gitlab-ce 16.10.0 结合上文 jenkins安装 前期准备&#xff1a; yum install -y epel-release yum -y install net-tools vim lrzsz wget…

git知识

如何将develop分支合并到master分支 #简单版 git checkout master git pull origin master git merge origin/develop # 解决可能的冲突并提交 git push origin master#复杂版 git checkout master # 拉取远程 master 分支的最新代码并合并到本地 git pull origin master # 拉…

网页内容生成图片,这18般武艺你会几种呢?

前言 关于【SSD系列】&#xff1a; 前端一些有意思的内容&#xff0c;旨在3-10分钟里&#xff0c; 500-1000字&#xff0c;有所获&#xff0c;又不为所累。 网页截图&#xff0c;windows内置了快捷命令和软件&#xff0c;chrome开发者工具也能一键截图&#xff0c;html2canva…

【Linux杂货铺】文件系统

目录 &#x1f308;前言&#x1f308; &#x1f4c1; 硬盘 &#x1f4c2; 物理结构 &#x1f4c2; 存储结构 &#x1f4c2; CHS定址法 &#x1f4c2; 操作系统对硬盘的管理和抽象 &#x1f4c1; 文件系统 &#x1f4c2; 分区 &#x1f4c2; 分组 &#x1f4c2; inode号 分配…

DL00295-基于AirSim仿真环境的无人机深度强化学习算法路径规划完整实现含详细说明文档

-创建了一个开放的AI Gym环境&#xff0c;包括多旋翼和固定翼无人机的运动学模型。 -提供了一些UE4环境来训练和测试深度强化学习DRL导航策略。 -基于AirSim和SB3。 完整代码链接见文末。 DL00295-基于AirSim仿真环境的无人机深度强化学习算法路径规划完整实现含详细说明文档

力扣HOT100 - 189. 轮转数组

解题思路&#xff1a; 三次反转。 先反转一次&#xff0c;再根据 k 拆分成两部分各反转一次。 class Solution {public void rotate(int[] nums, int k) {k % nums.length;reverse(nums, 0, nums.length - 1);reverse(nums, 0, k - 1);reverse(nums, k, nums.length - 1);}pu…

【QT教程】QT6 Web性能优化

QT6 Web性能优化 使用AI技术辅助生成 QT界面美化视频课程 QT性能优化视频课程 QT原理与源码分析视频课程 QT QML C扩展开发视频课程 免费QT视频课程 您可以看免费1000个QT技术视频 免费QT视频课程 QT统计图和QT数据可视化视频免费看 免费QT视频课程 QT性能优化视频免费看 免费…

MURF1040CT-ASEMI快恢复二极管MURF1040CT

编辑&#xff1a;ll MURF1040CT-ASEMI快恢复二极管MURF1040CT 型号&#xff1a;MURF1040CT 品牌&#xff1a;ASEMI 封装&#xff1a;TO-220F 最大平均正向电流&#xff08;IF&#xff09;&#xff1a;10A 最大循环峰值反向电压&#xff08;VRRM&#xff09;&#xff1a;4…

kubectl_入门_Pod配置以及生命周期

Pod配置以及生命周期 1. Pod结构定义 每个pod中都可以包含一个或多个容器&#xff0c;这些容器可以分为两类 用户程序所在的容器&#xff0c;数量可多可少Pause容器&#xff0c;这是每个Pod都会有的一个根容器&#xff0c;它的作用有两个 可以以它为根据&#xff0c;评估整个…

工业相机飞拍功能的介绍(转载学习)

转载至&#xff1a; https://baijiahao.baidu.com/s?id1781438589726948322&wfrspider&forpc

【Python使用】嘿马头条完整开发md笔记第5篇:数据库,1 Redis事务【附代码文档】

嘿马头条项目从到完整开发笔记总结完整教程&#xff08;附代码资料&#xff09;主要内容讲述&#xff1a;课程简介&#xff0c;ToutiaoWeb虚拟机使用说明1 产品介绍,2 原型图与UI图,3 技术架构,4 开发,1 需求,2 注意事项。数据库&#xff0c;理解ORM1 简介,2 安装,3 数据库连接…

基于SSM学生考勤管理系统需求(内附设计LW + PPT+ 源码下载)

摘 要 高校的不断扩张让在校学生数量不断的增加&#xff0c;对于教师和管理人员的需求也在不断地增强&#xff0c;对日常的学生考勤管理的工作量也在日益增加&#xff0c;传统的人工点名签到的考勤管理模式已经给无法适用于当前高校考勤管理的需求&#xff0c;同时手动录入的…

一、flask入门和视图

run启动参数 模板渲染 后端给前端页面传参 前端页面设置css from flask import Flask, render_template,jsonify# 创建flask对象 app = Flask(__name__)# 视图函数 + 路由route @app.route("/") def hello_world():# 响应,返回给前端的数据return "hello worl…

股票价格预测 | Python股票价格数据导入和处理

文章目录 文章概述代码设计导入处理文章概述 股票价格预测 | Python股票价格数据导入和处理 代码设计 导入 import os import numpy as np import csv import pandas as pd import matplotlib.pyplot

逻辑benders分解

目录 1.可行割 &#xff08;1&#xff09;组合benders割&#xff08;本质是cover cut 覆盖割&#xff09; &#xff08;2&#xff09;最小可行割&#xff08;minimal infeasible set&#xff0c;MIS&#xff09; 2.最优割 &#xff08;1&#xff09;常规最优割 &#xf…

《四》QLineEdit单行输入框

QLineEdit单行输入框 QLineEdit 是 Qt 提供的一个控件类&#xff0c;它直接继承自 QWdiget 类&#xff0c;专门用来创建单行输入框&#xff0c;如下图所示&#xff1a; 单行文本输入框 实际开发中&#xff0c;我们经常用到 QLineEdit 输入框&#xff0c;比如接收用户输入的个…