sklearn回归树

news2024/9/21 20:45:13

说明:内容来自菜菜的sklearn机器学习和ai生成

回归树

调用对象的参数

class sklearn.tree.DecisionTreeRegressor (criterion=’mse’, splitter=’best’, max_depth=None, min_samples_split=2, min_samples_leaf=1, min_weight_fraction_leaf=0.0, max_features=None, random_state=None, max_leaf_nodes=None, min_impurity_decrease=0.0, min_impurity_split=None, presort=False)

几乎所有参数,属性及接口都和分类树一模一样。需要注意的是,在回归树中,没有标签分布是否均衡的问题,因此没有class_weight这样的参数。

回归树衡量分枝质量的指标,支持的标准有三种:

1)输入"mse"使用均方误差mean squared error(MSE),父节点和叶子节点之间的均方误差的差额将被用来作为 特征选择的标准,这种方法通过使用叶子节点的均值来最小化L2损失

2)输入“friedman_mse”使用费尔德曼均方误差,这种指标使用弗里德曼针对潜在分枝中的问题改进后的均方误差

3)输入"mae"使用绝对平均误差MAE(mean absolute error),这种指标使用叶节点的中值来最小化L1损失 属性中最重要的依然是feature_importances_,接口依然是apply, fit, predict, score最核心。

M S E = 1 N ∑ i = 1 N ( f i − y i ) 2 MSE=\frac1N\sum_{i=1}^N(f_i-y_i)^2 MSE=N1i=1N(fiyi)2

其中N是样本数量,i是每一个数据样本,fi是模型回归出的数值,yi是样本点i实际的数值标签。所以MSE的本质, 其实是样本真实数据与回归结果的差异。在回归树中,MSE不只是我们的分枝质量衡量指标,也是我们最常用的衡 量回归树回归质量的指标,当我们在使用交叉验证,或者其他方式获取回归树的结果时,我们往往选择均方误差作 为我们的评估(在分类树中这个指标是score代表的预测准确率)。在回归中,我们追求的是,MSE越小越好。

回归树的接口score返回的是R平方,并不是MSE。R平方被定义如下:
R 2 = 1 − u v u = ∑ i = 1 N ( f i − y i ) 2 v = ∑ i = 1 N ( y i − y ^ ) 2 R^2=1-\frac uv\\u=\sum_{i=1}^N(f_i-y_i)^2\quad v=\sum_{i=1}^N(y_i-\hat{y})^2 R2=1vuu=i=1N(fiyi)2v=i=1N(yiy^)2
其中u是残差平方和(MSE * N)

预测值和真实值

v是总平方和

真实值和均值

u/v意义的思考

如果v高,说明模型本身分布比较复杂,不容易用回归树预测,波动是比较大的,在分母的位置上,我们认为可以容忍u有较大的值,可以认为是卷子的难度系数

对于u来说,直接衡量了回归树和真实值的计算结果,可以作为打分。当然是值越低越好,也就是说好的回归树是让 R 2 R^2 R2接近于1,u/v接近于0的

u/v越小,这个回归树越好

N是样本数量

i是每一个数据样本

f i f_i fi是模型回归出的数值

y i y_i yi 是样本点i实际的数值标签。

y ^ \hat{y} y^是真实数值标签的平均数。

R平方可以为正为负(如果模型的残差平方和远远大于模型的总平方和,模型非常糟糕,R平方就会为负),而均方误差永远为正。

值得一提的是,虽然均方误差永远为正,但是sklearn当中使用均方误差作为评判标准时,却是计算”负均方误差“(neg_mean_squared_error)。这是因为sklearn在计算模型评估指标的时候,会考虑指标本身的性质,均方误差本身是一种误差,所以被sklearn划分为模型的一种损失(loss),因此在sklearn当中,都以负数表示。真正的均方误差MSE的数值,其实就是neg_mean_squared_error去掉负号的数字。

中间是交叉验证的内容,在另一个文章中交叉验证

实例:一维回归的图像绘制

在二维平面观察决策树是怎样拟合一条曲线的。用回归树来拟合正弦曲线

import numpy as np
from sklearn.tree import DecisionTreeRegressor
import matplotlib.pyplot as plt
#创建一条含有噪声的正弦曲线
rng = np.random.RandomState(1)
X = np.sort(5*rng.rand(80,1),axis = 0)
#从[80,1]变到80
y = np.sin(X).ravel()#将多维数组展平为一维数组
# .ravel()返回的是数组的视图,而.flatten()返回的是数组的拷贝
#每隔五个添加一个噪声
y[::5] += 3 * (0.5 - rng.rand(16))

#实例化和训练模型
regr_1 = DecisionTreeRegressor(max_depth=2)
regr_2 = DecisionTreeRegressor(max_depth=5)
regr_1.fit(X, y)
regr_2.fit(X, y)

X_test = np.arange(0.0, 5.0, 0.01)[:, np.newaxis]#类切片,添加一个新的维度
#回归树传入的数据必须是二维的
y_1 = regr_1.predict(X_test)
y_2 = regr_2.predict(X_test)
plt.figure()
#先绘制散点图
plt.scatter(X, y, s=20, edgecolor="black",c="darkorange", label="data")
plt.plot(X_test, y_1, color="cornflowerblue",label="max_depth=2", linewidth=2)
plt.plot(X_test, y_2, color="yellowgreen", label="max_depth=5", linewidth=2)
plt.xlabel("data")
plt.ylabel("target")
plt.title("Decision Tree Regression")
plt.legend()
plt.show()

在这里插入图片描述

手写一个回归树的算法的思路

数据准备

  • 特征选择:选择相关的特征进行建模。
  • 数据预处理:包括处理缺失值、标准化等。

构建树

  • 选择最佳分割点:这是算法的核心,包括遍历特征、计算MSE、选择最优分割点。
  • 递归分割数据:根据选择的分割点,将数据分为左右子树。
  • 确定叶子节点值:当达到停止条件时,计算当前节点数据的平均值作为预测值。

停止条件

  • 达到最大深度
  • 样本数小于设定阈值
  • 所有样本具有相同的目标值

预测

  • 遍历树结构:根据新数据的特征值,遍历树直到达到叶子节点。
  • 返回叶子节点值:将叶子节点的值作为预测结果。

评估和优化

  • 交叉验证:评估模型性能
  • 剪枝:减少过拟合
  • 超参数调优:如最大深度、最小样本分割数等
class TreeNode:
    def __init__(self, feature_index=None, threshold=None, left=None, right=None, value=None):
        self.feature_index = feature_index  # 特征索引
        self.threshold = threshold  # 分割点
        self.left = left  # 左子树
        self.right = right  # 右子树
        self.value = value  # 叶子节点值(如果是叶子节点)

class DecisionTreeRegressor:
	#深度和最小不可分割的样本数量
    def __init__(self, max_depth=None, min_samples_split=2):
        self.max_depth = max_depth  # 最大深度
        self.min_samples_split = min_samples_split  # 最小分割样本数
        self.root = None  # 根节点

    def fit(self, X, y):
        self.root = self._build_tree(X, y)

    def _build_tree(self, X, y, depth=0):
        # 检查是否达到了停止条件
        if len(X) < self.min_samples_split or depth >= self.max_depth:
            return TreeNode(value=np.mean(y))

        # 找到最佳分割点
        feature_index, threshold = self._best_split(X, y)
        if feature_index is None:
            return TreeNode(value=np.mean(y))

        # 根据最佳分割点分割数据
        left_indices = X[:, feature_index] < threshold
        right_indices = X[:, feature_index] >= threshold
        left_child = self._build_tree(X[left_indices], y[left_indices], depth + 1)
        right_child = self._build_tree(X[right_indices], y[right_indices], depth + 1)

        # 返回当前节点
        return TreeNode(feature_index, threshold, left_child, right_child)

    def _best_split(self, X, y):
        # 初始化最小误差
        best_mse = float('inf')
        best_feature_index = None
        best_threshold = None

        # 遍历所有特征和分割点
        for feature_index in range(X.shape[1]):
            thresholds = np.unique(X[:, feature_index])
            for threshold in thresholds:
                # 计算分割后的左右子集
                left_indices = X[:, feature_index] < threshold
                right_indices = X[:, feature_index] >= threshold
                left_y, right_y = y[left_indices], y[right_indices]

                # 计算均方误差
                mse = (len(left_y) * np.var(left_y) + len(right_y) * np.var(right_y)) / len(y)

                # 如果当前分割更好,更新最佳分割点
                if mse < best_mse:
                    best_mse = mse
                    best_feature_index = feature_index
                    best_threshold = threshold

        return best_feature_index, best_threshold

    def predict(self, X):
        return np.array([self._predict_one(x) for x in X])

    def _predict_one(self, x):
        node = self.root
        while node.value is None:
            if x[node.feature_index] < node.threshold:
                node = node.left
            else:
                node = node.right
        return node.value

核心方法最佳分割:

 def _find_best_split(self, X, y):
 		#最佳特征,最好阈值,最好的均方误差
        best_feature, best_threshold, best_mse = None, None, float('inf')
		#对于每个特征进行遍历
        for feature in range(X.shape[1]):
        	#对于每个特征,我们找出所有唯一的值作为可能的阈值。这减少了需要检查的阈值数量。
            thresholds = np.unique(X[:, feature])
            #遍历阈值
            for threshold in thresholds:
            	#创建布尔掩码来进行数据分割
           		#数据比阈值小的
                left_mask = X[:, feature] <= threshold
                right_mask = ~left_mask
				# 如果掩码为0,说明是一个失败的分割,进行下一次分割
                if np.sum(left_mask) == 0 or np.sum(right_mask) == 0:
                    continue
				#计算左侧和右侧的均方误差
                left_mse = np.mean((y[left_mask] - np.mean(y[left_mask])) ** 2)
                right_mse = np.mean((y[right_mask] - np.mean(y[right_mask])) ** 2)
                #加权的MSE
                mse = (np.sum(left_mask) * left_mse + np.sum(right_mask) * right_mse) / len(y)
				#如果这个mse是小于最优的,就进行更新
                if mse < best_mse:
                    best_feature, best_threshold, best_mse = feature, threshold, mse

        return best_feature, best_threshold

构建树

这个方法是构建回归树的核心,它通过递归的方式构建整个树结构。让我们深入分析这个方法:

首先,让我们回顾一下_build_tree方法的代码:

def _build_tree(self, X, y, depth=0):
    n_samples, n_features = X.shape
    
    # 检查停止条件
    if (depth >= self.max_depth or 
        n_samples < self.min_samples_split or 
        np.all(y == y[0])):
        return np.mean(y)

    best_feature, best_threshold = self._find_best_split(X, y)
    
    if best_feature is None:
        return np.mean(y)

    left_mask = X[:, best_feature] <= best_threshold
    right_mask = ~left_mask

    left_tree = self._build_tree(X[left_mask], y[left_mask], depth + 1)
    right_tree = self._build_tree(X[right_mask], y[right_mask], depth + 1)

    return {
        'feature': best_feature,
        'threshold': best_threshold,
        'left': left_tree,
        'right': right_tree
    }

现在,让我们一步步解析这个方法:

  1. 初始化:

    n_samples, n_features = X.shape
    

    我们首先获取当前数据集的样本数和特征数。

  2. 检查停止条件:

    if (depth >= self.max_depth or 
        n_samples < self.min_samples_split or 
        np.all(y == y[0])):
        return np.mean(y)
    

    这里检查三个停止条件:

    • 如果达到最大深度
    • 如果样本数小于最小分割数
    • 如果所有样本的目标值相同
      如果满足任何一个条件,我们就停止分割,返回当前节点样本的平均值作为叶子节点的预测值。
  3. 寻找最佳分割点:

    best_feature, best_threshold = self._find_best_split(X, y)
    

    调用_find_best_split方法来找到最佳的分割特征和阈值。

  4. 检查分割是否可能:

    if best_feature is None:
        return np.mean(y)
    

    如果找不到有效的分割(例如,所有特征值都相同),我们也停止分割,返回平均值。

  5. 分割数据:

    left_mask = X[:, best_feature] <= best_threshold
    right_mask = ~left_mask
    

    根据最佳分割点将数据分为左右两部分。

  6. 递归构建左右子树:

    left_tree = self._build_tree(X[left_mask], y[left_mask], depth + 1)
    right_tree = self._build_tree(X[right_mask], y[right_mask], depth + 1)
    

    对左右两部分数据递归调用_build_tree方法,深度加1。

  7. 返回节点信息:

    return {
        'feature': best_feature,
        'threshold': best_threshold,
        'left': left_tree,
        'right': right_tree
    }
    

    返回一个字典,包含当前节点的分割特征、阈值,以及左右子树。

这个过程的关键点在于:

  • 递归构建:树的构建是自顶向下的递归过程。每次分割后,我们对左右子树继续应用相同的构建过程。

  • 贪心策略:在每个节点,我们都选择当前最优的分割,而不考虑未来的分割。这是一种局部最优策略。

  • 自适应结构:树的结构会根据数据的特点自适应地生长。有些分支可能很深,而其他分支可能很快就到达叶子节点。

  • 停止条件的重要性:停止条件防止树过度生长,这对于避免过拟合非常重要。

  • 叶子节点的预测值:在叶子节点,我们使用该节点样本的平均值作为预测值。这是因为在回归问题中,均值是平方误差损失下的最优预测。

这个构建过程的结果是一个嵌套的字典结构,每个非叶子节点包含分割信息和子树,叶子节点则直接是一个数值(预测值)。

使用树回归预测

这个过程涉及到如何使用构建好的树结构来对新的数据点进行预测。让我们深入了解这个过程。

首先,让我们回顾一下与预测相关的代码:

def predict(self, X):
    return np.array([self._traverse_tree(x, self.tree) for x in X])

def _traverse_tree(self, x, node):
    if not isinstance(node, dict):
        return node

    if x[node['feature']] <= node['threshold']:
        return self._traverse_tree(x, node['left'])
    else:
        return self._traverse_tree(x, node['right'])

现在,让我们详细解析预测过程:

  1. 预测方法 predict:

    • 这个方法接收一个包含多个样本的数据集 X
    • 它对 X 中的每个样本 x 调用 _traverse_tree 方法。
    • 最后返回一个包含所有预测结果的 numpy 数组。
  2. 树遍历方法 _traverse_tree:

    • 这个方法接收一个单独的样本 x 和当前的树节点 node
    • 它递归地遍历树,直到到达叶子节点。
  3. 预测过程详解:

    a. 开始于根节点:

    • 预测从树的根节点开始。

    b. 检查是否为叶子节点:

    if not isinstance(node, dict):
        return node
    
    • 如果当前节点不是字典(即是一个数值),说明我们到达了叶子节点。
    • 在这种情况下,直接返回该值作为预测结果。

    c. 在内部节点进行决策:

    if x[node['feature']] <= node['threshold']:
        return self._traverse_tree(x, node['left'])
    else:
        return self._traverse_tree(x, node['right'])
    
    • 如果是内部节点,我们比较样本在分割特征上的值与阈值。
    • 如果小于等于阈值,我们进入左子树。
    • 否则,我们进入右子树。

    d. 递归遍历:

    • 这个过程递归进行,直到到达叶子节点。

    e. 返回预测值:

    • 当到达叶子节点时,返回该节点存储的值(通常是该节点训练样本的平均值)作为预测结果。
  4. 预测的特点:

    • 路径唯一性: 对于每个输入样本,在树中只有一条唯一的路径从根到叶。
    • 快速预测: 预测过程的时间复杂度是 O(log n),其中 n 是树的节点数。这使得决策树在预测阶段非常高效。
    • 可解释性: 我们可以跟踪样本在树中的路径,理解为什么会得到特定的预测结果。
  5. 示例:
    假设我们有一个简单的树结构:

    {
      'feature': 0,
      'threshold': 0.5,
      'left': {
        'feature': 1,
        'threshold': 0.3,
        'left': 2.5,
        'right': 3.7
      },
      'right': 4.2
    }
    

    对于输入样本 [0.4, 0.2]

    • 首先检查特征 0:0.4 <= 0.5,所以进入左子树。
    • 然后检查特征 1:0.2 <= 0.3,所以进入左子树的左子树。
    • 到达叶子节点,返回预测值 2.5。

这个预测过程展示了决策树的一个主要优势:预测速度快且直观。每个预测本质上是一系列简单的if-else决策,这使得决策树模型非常适合需要快速决策的实时应用。预测类似于一个二叉树查找。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

大数据基础:数仓架构演变

文章目录 数仓架构演变 一、传统离线大数据架构 二、​​​​​​Lambda架构 三、Kappa架构 四、​​​​​​​​​​​​​​混合架构 五、湖仓一体架构 六、流批一体架构 数仓架构演变 20世纪70年代&#xff0c;MIT(麻省理工)的研究员致力于研究一种优化的技术架构&…

Linux shell编程学习笔记75:sed命令——沧海横流任我行(下)

0 前言 在 Linux shell编程学习笔记73&#xff1a;sed命令——沧海横流任我行&#xff08;上&#xff09;-CSDN博客文章浏览阅读684次&#xff0c;点赞32次&#xff0c;收藏24次。在大数据时代&#xff0c;我们要面对大量数据&#xff0c;有时需要对数据进行替换、删除、新增、…

OpenCV几何图像变换(9)仿射变换函数warpAffine()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 函数是应用一个仿射变换到图像上。 warpAffine 函数使用指定的矩阵对源图像进行仿射变换&#xff1a; dst ( x , y ) src ( M 11 x M 12 y M…

Elasticsearch:使用 ELSER 进行语义搜索 - sparse_vector

Elastic Learned Sparse EncodeR&#xff08;或 ELSER&#xff09;是由 Elastic 训练的 NLP 模型&#xff0c;可让你使用稀疏向量表示执行语义搜索。语义搜索不是根据搜索词进行文字匹配&#xff0c;而是根据搜索查询的意图和上下文含义检索结果。 本教程中的说明向你展示了如…

[医疗 AI ] 3D TransUNet:通过 Vision Transformer 推进医学图像分割

[医疗 AI ] 3D TransUNet&#xff1a;通过 Vision Transformer 推进医学图像分割’ 论文地址 - https://arxiv.org/pdf/2310.07781 0. 摘要 医学图像分割在推进医疗保健系统的疾病诊断和治疗计划中起着至关重要的作用。U 形架构&#xff0c;俗称 U-Net&#xff0c;已被证明在…

提高实时多媒体传输效率的三大方法

实时多媒体数据传输面临的挑战 实时多媒体数据的传输具有数据量巨大、对时延和时延抖动高度敏感及能容忍丢分组的特点。然而&#xff0c;当今互联网的网络层协议提供的仅是一种“尽最大努力服务”&#xff0c;对分组的端到端时延、时延抖动和分组丢失率等指标不做任何承诺。这…

MySQL的延迟复制

目录 1 MySQL 延迟复制介绍 1.1 延迟复制语法&#xff1a; 1.2 延迟复制可用于多种用途&#xff1a; 1.3 延迟复制的有关的参数 1.4 延迟复制的操作 2 MySQL 延迟复制 实操 2.1 实验环境 2.2 对 SLAVE --MySQL-3 进行延迟复制操作 2.3 停止相关进程的原因 2.4 实验测试 2.5 动…

Variomes:支持基因组变异筛选的高召回率搜索引擎

《Bioinformatics》2022 Variomes&#xff1a; https://candy.hesge.ch/Variomes Source code&#xff1a; https://github.com/variomes/sibtm-variomes SynVar&#xff1a; https://goldorak.hesge.ch/synvar 文章摘要&#xff08;Abstract&#xff09; 动机&#xff08;Mot…

读软件开发安全之道:概念、设计与实施07密码学(上)

1. 加密工具 1.1. 加密工具之所以没有得到充分使用&#xff0c;就是因为人们往往认为密码学是一个准入门槛极高的专业领域 1.2. 如今的加密学大部分都源自纯数学&#xff0c;所以只要能够正确使用&#xff0c;加密学确实行之有效 1.2.1. 不代表这些算法本身确实无法破解&…

机器学习 | 基于wine数据集的KMeans聚类和PCA降维案例

KMeans聚类&#xff1a;K均值聚类是一种无监督的学习算法&#xff0c;它试图根据数据的相似性对数据进行聚类。无监督学习意味着不需要预测结果&#xff0c;算法只是试图在数据中找到模式。在k均值聚类中&#xff0c;我们指定希望将数据分组到的聚类数。该算法将每个观察随机分…

四大消息队列:Kafka、ActiveMQ、RabbitMQ、RocketMQ对比

四大消息队列&#xff1a;Kafka、ActiveMQ、RabbitMQ、RocketMQ对比 1. 社区活跃度2. 持久化消息3. 技术实现4. 高并发性能5. RabbitMQ与Kafka对比 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 在软件开发中&#xff0c;消息队列&#xf…

【Redis】Redis数据结构——Hash 哈希

哈希 命令hsethgethexistshdelhkeyshvalshgetallhmgethlenhsetnxhincrbyhincrbyfloat命令小结 内部编码使用场景缓存⽅式对⽐ ⼏乎所有的主流编程语⾔都提供了哈希&#xff08;hash&#xff09;类型&#xff0c;它们的叫法可能是哈希、字典、关联数组、映射。在 Redis 中&#…

Python furl库:一键搞定复杂URL操作

更多Python学习内容&#xff1a;ipengtao.com 在Web开发和数据处理的过程中&#xff0c;URL的解析、修改和构建是不可避免的操作。然而&#xff0c;直接操作URL字符串不仅繁琐&#xff0c;而且容易出错。Python的furl库提供了一种简单且强大的方法来处理URL&#xff0c;使得URL…

简易的 Websocket + 心跳机制 + 尝试重连

文章目录 演示大纲基础 WebSocket前端: 添加心跳机制前端: 尝试重新连接历史代码 还没有写完&#xff0c;bug 是有的&#xff0c;我在想解决办法了… 演示 大纲 基础的 webSocket 连接前后端&#xff1a;添加心跳机制后端无心跳反应&#xff0c;前端尝试重新连接设置重新连接…

Java 日常反常识踩坑

作者&#xff1a;若渝 本文主要是日常业务开发中自身碰到过跟常识不一致的坑&#xff0c;问题虽然基础&#xff0c;但却可能造成比较大的线上问题。 一、转 BigDecimal 类型时精度丢失 public class Test { public static void main(String[] args) { BigDecimal bi…

算法-分隔链表

一、题目描述 (一) 题目 给你一个链表的头节点 head 和一个特定值 x &#xff0c;请你对链表进行分隔&#xff0c;使得所有 小于 x 的节点都出现在 大于或等于 x 的节点之前。你应当保留两个分区中每个节点的初始相对位置。 (二) 示例 示例 1&#xff1a; 输入&#xff1a;…

用Python实现9大回归算法详解——07. 支持向量机回归算法

1. 支持向量机回归的基本概念 支持向量机回归&#xff08;Support Vector Regression, SVR&#xff09;是支持向量机&#xff08;SVM&#xff09;的一个应用&#xff0c;主要用于回归任务。与分类任务中的 SVM 类似&#xff0c;SVR 通过找到一个最大化边界&#xff08;即支持向…

[WUSTCTF2020]spaceclub

上sublime txt 每一行的长短对应一个二进制位&#xff0c;长空格是1&#xff0c;短空格是0&#xff0c;全部替换掉得到 上python脚本 import binasciiwith open(attachment_5.txt, r) as file:lines file.readlines() # 逐行读取文本内容output # 初始化输出字符串# 遍历…

vscode 写了未定义的方法不报错,配置全局ESLint

最近接触了一个旧的vue2的项目&#xff0c;里面没有ts和eslint配置 在正在维护的页面里复制了其他页面的一个方法&#xff0c;方法里面包含lodash的cloneDeep&#xff0c;cloneDeep在这个页面并没有引入&#xff0c;但是vscode却没有提示&#xff0c;很不友好&#xff0c;容易…

JUC阻塞队列(五):SynchronousQueue

1、SynchronousQueue介绍 SynchronousQueue与前边的其他几个阻塞队列的差异是挺大的&#xff0c;在一般逻辑中队列是一个用 来存储数据的中间容器&#xff08;前边几个阻塞队列也是用来存放数据的&#xff09;&#xff0c;但SynchronousQueue 却不是用来存放数据的&#xff0c;…