百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术资源 > 正文

通俗易懂的机器学习——根据CART算法使用python构建决策树

off999 2024-11-26 07:23 33 浏览 0 评论

前言

之前曾经实现过可以应用在离散取值区间的简易决策树,前天突发奇想仿照sklearn的实现效果写了一个取值范围可以是连续区间的通用决策树。 如果对之前的简易决策树了解不深可以先复习一下:简易决策树地址

代码介绍

依赖包

import numpy as np
from collections import Counter
from math import log2
import matplotlib.pyplot as plt
from sklearn import datasets
复制代码

在这里我们为了方便直接选用了sklearn里面的数据集(仅选用数据集,算法具体实现不依赖于sklearn)。 对于依赖包的解释也可以翻阅之前的简易决策树一文。

计算损失

def entropy(y_label):
    counter = Counter(y_label)
    ent = 0.0
    for num in counter.values():
        p = num / len(y_label)
        ent += -p * log2(p)
    return ent
复制代码

在本博客中我们选用的是信息熵,也可以选用基尼系数。

树结点

class TreeNode:
    def __init__(self, acc, imin=None, minD=None):
        self.acc = list(acc)  # 不同类别的精度
        self.imin = imin  # 最小分割的特征
        self.minD = minD  # 分割点
复制代码

树的结点中包含的信息有:在当前结点不同类别的精度、该结点损失最小分割特征、和按照损失最小分割特征分割的损失最小的分割值。

决策树类

class DecisionTree:
    def __init__(self, maxentropy=1e-10, max_depth=20, min_samples=0.1):
        self.tree = {}
        self.maxentropy = maxentropy  # 最大信息熵(分割条件:小于这个信息熵可以分割)
        self.max_depth = max_depth  # 递归树深度
        self.min_samples = min_samples  # 最小样本数

    # 训练决策树
    def fit(self, X, y):
        if self.min_samples < 1 and self.min_samples > 0:
            # 如果min_samples是小数则按照输入数据的数据量的比例确定min_samples,如果>1则给定的数值作为min_samples
            self.min_samples *= len(X)
        cols = list(range(X.shape[1]))
        # 对X得每一列数据,计算分割后得信息熵
        ylen = len(set(y))
        self.tree = self._genTree(cols, X, y, ylen, 1)

    # 递归生成决策树
    def _genTree(self, cols, X, y, ylen, depth):
        # 计算最小信息熵得特征
        imin = cols[0]  # 最下熵得列
        leftemin = 100  # 最小左熵值
        rightemin = 100  # 最小右熵值
        minD = None
        for i in cols:
            coli = X[:, i]  # 拿到第i个特征数据
            sortedcoli = coli
            sorted(sortedcoli)
            divide = []
            divide.append(coli[0])
            for j in range(len(sortedcoli)):
                # 划分分界线
                if j == len(sortedcoli) - 1:
                    divide.append(sortedcoli[j])
                else:
                    divide.append((sortedcoli[j] + sortedcoli[j+1]) / 2)
            for d in divide:
                # 选择不同特征的不同值所产生的最小信息熵
                leftenti = entropy(y[coli < d])
                rightenti = entropy(y[coli >= d])
                if leftenti + rightenti < leftemin + rightemin:
                    imin = i
                    leftemin = leftenti
                    rightemin = rightenti
                    minD = d
        # 求划分精度
        coli = X[:, imin]
        Acc = np.zeros(ylen)
        leftAcc = np.zeros(ylen)
        rightAcc = np.zeros(ylen)

        for idx in set(y):
            # print(y[coli < minD] == idx)
            leftAcc[idx] = np.sum(y[coli < minD] == idx) / len(y[coli < minD])
            rightAcc[idx] = np.sum(y[coli >= minD] == idx) / len(y[coli >= minD])
            Acc[idx] = np.sum(y == idx) / len(y)
        # print("acc:", Acc, leftAcc, rightAcc)
        # 创建树
        newtree = {}
        # print(imin, end=":")
        if leftemin < rightemin:
            # 新建左叶子
            Node = {}
            # print(leftAcc)
            Node[0] = (0, TreeNode(list(leftAcc), 0))
            # print("<", minD, leftAcc, 0)
            if rightemin > self.maxentropy and len(X) >= self.min_samples and depth < self.max_depth:
                # 裁剪数据集
                DataIndex = X[:, imin] > minD
                Xcopy = X[DataIndex].copy()
                ycopy = y[DataIndex].copy()
                # 新建右子树
                # cols.remove(imin)
                Node[1] = (1, self._genTree(cols, Xcopy, ycopy, ylen, depth+1))
            else:
                # print(rightAcc)
                Node[1] = (0, TreeNode(list(rightAcc), 0))
                # print(">", minD, rightAcc, 0)

        else:
            # 新建右叶子
            Node = {}
            Node[1] = (0, TreeNode(list(rightAcc), 0))
            # print(rightAcc)
            if leftemin > self.maxentropy and len(X) >= self.min_samples and depth < self.max_depth:
                # 裁剪数据集
                DataIndex = X[:,imin] <= minD
                Xcopy = X[DataIndex].copy()
                ycopy = y[DataIndex].copy()
                # 新建左子树
                # cols.remove(imin)
                Node[0] = (1, self._genTree(cols, Xcopy, ycopy, ylen, depth+1))
            else:
                Node[0] = (0, TreeNode(list(leftAcc), 0))
                print(leftAcc)

        newtree[TreeNode(list(Acc), imin, minD)] = Node
        return newtree

    # 预测新样本
    def predict(self, X):
        X = X.tolist()
        # print(X)
        y = [None for i in range(len(X))]
        for i in range(len(X)):
            tree = self.tree
            while True:
                node = list(tree.keys())[0]  # 获取结点
                acc = node.acc
                imin = node.imin
                minD = node.minD  # 获取结点中数据
                tree = tree[node]  # 获取左右子节点
                # print(imin)
                if X[i][imin] < minD:
                    # 选择左节点
                    tree = tree[0]
                    if tree[0] == 0:
                        # 当前为叶子结点,停止查找
                        y[i] = np.argmax(tree[1].acc)
                        break
                    else:
                        tree = tree[1]  # 将树根更新成右子树
                else:
                    # 选择右节点
                    tree = tree[1]
                    if tree[0] == 0:
                        # 当前为叶子结点,停止查找
                        y[i] = np.argmax(tree[1].acc)
                        break
                    else:
                        tree = tree[1]  # 将树根更新成右子树

        return y
复制代码

决策树类的代码较多,下文将按照函数详细讲解

作图函数

def plot_decision_boundary(model, X, y):
    x0_min, x0_max = X[:, 0].min() - 1, X[:, 0].max() + 1
    x1_min, x1_max = X[:, 1].min() - 1, X[:, 1].max() + 1
    x0, x1 = np.meshgrid(np.linspace(x0_min, x0_max, 100), np.linspace(x1_min, x1_max, 100))
    Z = model.predict(np.c_[x0.ravel(), x1.ravel()])
    Z = np.array(Z)
    Z = Z.reshape(x0.shape)

    plt.contourf(x0, x1, Z, cmap=plt.cm.Spectral)
    plt.ylabel('x1')
    plt.xlabel('x0')
    plt.scatter(X[:, 0], X[:, 1], c=np.squeeze(y))
    plt.show()
复制代码

按照数据集生成大量的数据并对生成的数据进行预测,画出预测结果的等高线,从而得到决策树划分结果

加载数据集

下面以make_circle、make_moons、iris为例,测试生成树的时候任选其一就行 make_circles:

X,y=datasets.make_circles(n_samples=1000,factor=0.5,noise=0.1)
复制代码

make_moons:

X,y = datasets.make_moons(n_samples=500,noise=0.3,random_state=42)
复制代码

iris:

iris = datasets.load_iris()
X = iris["data"][:, 2:]
y = iris["target"]
复制代码

为了方便画二维图像,这里的iris数据集只选用了两个特征

主程序

if __name__ == "__main__":
	# X,y=datasets.make_circles(n_samples=1000,factor=0.5,noise=0.1)
	# X,y = datasets.make_moons(n_samples=500,noise=0.3,random_state=42)
	iris = datasets.load_iris()
	X = iris["data"][:, 2:]
	y = iris["target"]
	dt = DecisionTree()
	dt.fit(X, y)
	print(dt.tree)
	print(dt.predict(X))
	X = np.array(X)
	y = np.array(y)
	plot_decision_boundary(dt, X, y)
复制代码

效果演示

make_circles数据集划分结果

make_moons数据集划分结果

iris数据集划分结果

决策树类中函数解释

init函数

    def __init__(self, maxentropy=1e-10, max_depth=20, min_samples=0.1):
        self.tree = {}
        self.maxentropy = maxentropy  # 最大信息熵(分割条件:小于这个信息熵可以分割)
        self.max_depth = max_depth  # 递归树深度
        self.min_samples = min_samples  # 最小样本数
复制代码

初始化树、最大信息熵、递归树的最大深度、划分需要的最小样本数

fit函数

    def fit(self, X, y):
        if self.min_samples < 1 and self.min_samples > 0:
            # 如果min_samples是小数则按照输入数据的数据量的比例确定min_samples,如果>1则给定的数值作为min_samples
            self.min_samples *= len(X)
        cols = list(range(X.shape[1]))
        # 对X得每一列数据,计算分割后得信息熵
        ylen = len(set(y))
        self.tree = self._genTree(cols, X, y, ylen, 1)
复制代码

(1)如果初始化的最小样本数min_samples<1说明min_samples表示的是最小样本数应该占总样本数的比例,应该乘以样本数获取实际最小样本数。如果初始化的最小样本数>1说明初始化的最小样本数参数即为实际最小样本数。 (2)cols代表的是每个特征的编号 (3)ylen代表数据集能够分类的最大标签数 (4)通过self._genTree函数建立决策树

_genTree函数

    def _genTree(self, cols, X, y, ylen, depth):
        # 计算最小信息熵得特征
        imin = cols[0]  # 最下熵得列
        leftemin = 100  # 最小左熵值
        rightemin = 100  # 最小右熵值
        minD = None
        for i in cols:
            coli = X[:, i]  # 拿到第i个特征数据
            sortedcoli = coli
            sorted(sortedcoli)
            divide = []
            divide.append(coli[0])
            for j in range(len(sortedcoli)):
                # 划分分界线
                if j == len(sortedcoli) - 1:
                    divide.append(sortedcoli[j])
                else:
                    divide.append((sortedcoli[j] + sortedcoli[j+1]) / 2)
            for d in divide:
                # 选择不同特征的不同值所产生的最小信息熵
                leftenti = entropy(y[coli < d])
                rightenti = entropy(y[coli >= d])
                if leftenti + rightenti < leftemin + rightemin:
                    imin = i
                    leftemin = leftenti
                    rightemin = rightenti
                    minD = d
        # 求划分精度
        coli = X[:, imin]
        Acc = np.zeros(ylen)
        leftAcc = np.zeros(ylen)
        rightAcc = np.zeros(ylen)

        for idx in set(y):
            # print(y[coli < minD] == idx)
            leftAcc[idx] = np.sum(y[coli < minD] == idx) / len(y[coli < minD])
            rightAcc[idx] = np.sum(y[coli >= minD] == idx) / len(y[coli >= minD])
            Acc[idx] = np.sum(y == idx) / len(y)
        # print("acc:", Acc, leftAcc, rightAcc)
        # 创建树
        newtree = {}
        # print(imin, end=":")
        if leftemin < rightemin:
            # 新建左叶子
            Node = {}
            # print(leftAcc)
            Node[0] = (0, TreeNode(list(leftAcc), 0))
            # print("<", minD, leftAcc, 0)
            if rightemin > self.maxentropy and len(X) >= self.min_samples and depth < self.max_depth:
                # 裁剪数据集
                DataIndex = X[:, imin] > minD
                Xcopy = X[DataIndex].copy()
                ycopy = y[DataIndex].copy()
                # 新建右子树
                # cols.remove(imin)
                Node[1] = (1, self._genTree(cols, Xcopy, ycopy, ylen, depth+1))
            else:
                # print(rightAcc)
                Node[1] = (0, TreeNode(list(rightAcc), 0))
                # print(">", minD, rightAcc, 0)

        else:
            # 新建右叶子
            Node = {}
            Node[1] = (0, TreeNode(list(rightAcc), 0))
            # print(rightAcc)
            if leftemin > self.maxentropy and len(X) >= self.min_samples and depth < self.max_depth:
                # 裁剪数据集
                DataIndex = X[:,imin] <= minD
                Xcopy = X[DataIndex].copy()
                ycopy = y[DataIndex].copy()
                # 新建左子树
                # cols.remove(imin)
                Node[0] = (1, self._genTree(cols, Xcopy, ycopy, ylen, depth+1))
            else:
                Node[0] = (0, TreeNode(list(leftAcc), 0))
                print(leftAcc)

        newtree[TreeNode(list(Acc), imin, minD)] = Node
        return newtree
复制代码

(1)遍历各个特征的各个分割点(分割点是按照数据集两个临近数据的均值决定),记录信息熵最小的分割特征以及其对应的分割点。 (2)求当前结点对应的每个标签的精度以及按照信息熵最小的分割特征以及其对应的分割点划分后的左节点和右节点对应的每个标签的精度。 (3)根据决策树划分的特性,每次运用得到的分割点划分后总会有一个结点可以将一部分数据完全划分出来。 (4)如果小于分割点的部分可以完全被划分出来,左子树为叶子节点。在去除小于分割点的数据之后递归建立右子树。 (5)如果大于分割点的部分可以完全被划分出来,右子树为叶子节点。在去除大于分割点的数据之后递归建立左子树。 (6)返回值是树的根节点

结点数据解释

结点的数据为一个元组,元组第0位表示该结点是叶子结点还是子树的根节点,第一位表示叶子结点或子树的根节点。

这样做的目的是为了方便使用predict函数进行预测。

predict函数

    def predict(self, X):
        X = X.tolist()
        # print(X)
        y = [None for i in range(len(X))]
        for i in range(len(X)):
            tree = self.tree
            while True:
                node = list(tree.keys())[0]  # 获取结点
                acc = node.acc
                imin = node.imin
                minD = node.minD  # 获取结点中数据
                tree = tree[node]  # 获取左右子节点
                # print(imin)
                if X[i][imin] < minD:
                    # 选择左节点
                    tree = tree[0]
                    if tree[0] == 0:
                        # 当前为叶子结点,停止查找
                        y[i] = np.argmax(tree[1].acc)
                        break
                    else:
                        tree = tree[1]  # 将树根更新成右子树
                else:
                    # 选择右节点
                    tree = tree[1]
                    if tree[0] == 0:
                        # 当前为叶子结点,停止查找
                        y[i] = np.argmax(tree[1].acc)
                        break
                    else:
                        tree = tree[1]  # 将树根更新成右子树

        return y
复制代码

对于每个待预测数据按照决策树每个结点的特征以及特征对应的分隔值不断遍历决策树,直到遍历到叶子结点为止。选取叶子结点中精度最高的标签作为该数据的预测结果

相关推荐

win7系统破解激活工具(windows7破解激活)

方法如下:1、开机到欢迎界面时,按Ctrl+Alt+Delete,跳出帐号窗口,输入用户名:administrator,回车。2、如果这个帐号也有密码采用开机启动时按F8选“带命令行的安全模式”。...

怎么制作winpeu盘启动盘(制作winpe启动盘有什么作用)

我们应先理解U盘启动盘:简单理解就是用U盘启动盘代替电脑以前的光驱,所以它只有3个最基本的功能:1、帮助电脑正常启动。比如电脑无限在启动界面循环;2、格式化硬盘。格式化硬盘所有分区,再重新分区;3、重...

磁力搜索引擎入口(磁力搜索器引擎)

01.磁力熊磁力熊,是一个内容丰富、功能最为强大的一个磁力搜索网站,通过它不仅仅可以搜索到大量纯净的1080P高分电影,像一些比较小众的影视剧这里也都能找到。02.夕阳小站夕阳小站,虽然网站整体界面设...

手机变成安全模式怎么解除(手机变成安全模式是怎么回事)

解除比较安全模式的方法主要有三种:1、按电源键长按机器会弹出重启菜单,将手机重启即可解除比较安全模式。2、查询手机操作手册,进入设置里找到“比较安全模式”,可以改变比较安全模式的状态,即可解除比较安全...

win7官方最小精简版(最小win7精简版系统239m)
win7官方最小精简版(最小win7精简版系统239m)

推荐win7系统精简版一、雨林木风系统v1906雨林雨林木风GhostWin7SP1旗舰版一如既往注重稳定与安全,本次6月版本更新优化注册表增强系统运行效率,不对系统关键文件进行修改保证稳定性,关闭系统可能会感染病毒的端口,更新最新...

2026-01-11 14:51 off999

华硕牌子电脑怎么样(华硕牌子电脑怎么样值得买吗)

1、华硕笔记本电脑在市场上有很高的认知度和认可度。除了在零售市场有出色口碑外,在特殊领域华硕笔记本一样有惊人的表现;2、华硕笔记本电脑的优点在于它的主板性能好还有就是它的散热效果也不错,性能比较稳定;...

两个文件夹内容自动同步(两个文件夹内容自动同步,删除不了)

D:盘中点右键,新建公文包B,将文件夹A拖到公文包B中。如果以后文件夹A中的文件修改了,打开公文包B,点菜单上的“公文包、全部更新”。则公文包B就会自动更新文件,与文件夹A中的保持一致。这种方法可以有...

无法删除的文件夹怎么删(无法删除文件夹或文件的原因有哪些)

删除不了的软件、文件或文件夹的解决方法:1、开机按F8不动,到高级选项出现在松开手,用上下键选安全模式,按照提示进入到安全模式中删除即可(安全模式与正常模式操作一样)。2、如果使用其他办法无法删...

win7重装系统不用u盘(不用u盘新手重装系统win7)

可以通过以下步骤在不使用U盘的情况下重装Win7系统:首先需要备份您的电脑中的重要数据,以免在系统重装时丢失。进入系统的“控制面板”,找到“系统与安全”选项并单击进入。在“系统与安全”页面中,找到“备...

扣扣安全中心怎么修改密码(扣扣安全中心修改不了密码)

1、首先,打开QQ面板左下角的三个条形图标,然后在弹出选项的“安全”中单击“安全中心主页”。2、然后在打开的QQ安全中心页面中,单击头像下方的“修改密码”。3、然后将弹出一个提示来确认该QQ号码,并单...

win10两台电脑怎么共享文件(win10两台电脑怎么共享文件夹)

在Windows10中,您可以使用以下步骤共享文件:1.在要共享的文件夹上单击右键,选择“属性”。2.选择“共享”选项卡,然后选择“高级共享”。3.在“高级共享”对话框中,选中“共享此文件...

电脑复制粘贴不了是怎么回事

电脑无法复制粘贴原因分析及解决方法:如果是中病毒的话,会有以下的这些情况:1、系统不能上网,例如宽带账号无法登录,qq登录不上,网页无法打开。2、复制粘贴功能失效。3、电脑任务栏上的信息不能显示。4、...

win7一键烟雾头(win7烟雾头设置)

要调整Win7系统的烟雾头,首先需要打开“显示设置”窗口,在这个窗口中可以找到“分辨率”、“屏幕比例”等选项。接着,在“高级设置”中找到“显示适配器属性”选项,点击进入。在这个界面中,可以找到“3D设...

win7系统一键装机下载(w7一键安装操作系统)

可以在温十系统电脑上下载温七装机系统,但需要按照正确的步骤进行安装。以下是一个可能的安装步骤:1.在温十系统电脑上下载温七装机系统的ISO文件,可以从互联网上下载,也可以从其他媒体(如DVD或USB驱...

qq互联管理中心(qq互联管理中心是干什么的)

QQ互联是基于Discuz!云平台的一项服务,因此在开通QQ互联之前首先需要开通Discuz!云平台。在Discuz!X2中已经内置了云平台和相关服务,无需安装,在后台直接开启即可。可以呀,有...

取消回复欢迎 发表评论: