从零开始使用Python手写回归树算法
哈喽!今天心血来潮给大家带来了《从零开始使用Python手写回归树算法》,想必大家应该对文章都不陌生吧,那么阅读本文就都不会很困难,以下内容主要涉及到,若是你正在学习文章,千万别错过这篇文章~希望能帮助到你!
为了简单起见这里将使用递归来创建树节点,虽然递归不是一个完美的实现,但是对于解释原理他是最直观的。
首先导入库
import pandas as pd import numpy as np import matplotlib.pyplot as plt
首先需要创建训练数据,我们的数据将具有独立变量(x)和一个相关的变量(y),并使用numpy在相关值中添加高斯噪声,可以用数学表达为
这里的 是噪声。代码如下所示。
def f(x): mu, sigma = 0, 1.5 return -x**2 + x + 5 + np.random.normal(mu, sigma, 1) num_points = 300 np.random.seed(1) x = np.random.uniform(-2, 5, num_points) y = np.array( [f(i) for i in x] ) plt.scatter(x, y, s = 5)
回归树
在回归树中是通过创建一个多个节点的树来预测数值数据的。 下图展示了一个回归树的树结构示例,其中每个节点都有其用于划分数据的阈值。
给定一组数据,输入值将通过相应的规格达到叶子节点。 达到节点M的所有输入值可以用X的子集表示。从数学上讲,让我们用一个函数表达此情况,如果给定的输入值达到节点M,则可以给出1个,否则为0。
找到分裂数据的阈值:通过在每个步骤中选择2个连续点并计算其平均值来迭代训练数据。 计算的平均值将数据分为两个的阈值。
首先让我们考虑随机阈值以演示任何给定的情况。
threshold = 1.5 low = np.take(y, np.where(x threshold)) plt.scatter(x, y, s = 5, label = 'Data') plt.plot([threshold]*2, [-16, 10], 'b--', label = 'Threshold line') plt.plot([-2, threshold], [low.mean()]*2, 'r--', label = 'Left child prediction line') plt.plot([threshold, 5], [high.mean()]*2, 'r--', label = 'Right child prediction line') plt.plot([-2, 5], [y.mean()]*2, 'g--', label = 'Node prediction line') plt.legend()
蓝色垂直线表示单个阈值,我们假设它是任意两点的均值,并稍后将其用于划分数据。
我们对这个问题的第一个预测是所有训练数据(y轴)的平均值(绿色水平线)。而两条红线是要创建的子节点的预测。
很明显这些平均值都不能很好地代表我们的数据,但它们的差异也是很明显的:主节点预测(绿线)得到所有训练数据的均值,我们将其分为2个子节点,这2个子节点有自己的预测(红线)。与绿线相比这2个子节点更好地代表了它们对应的训练数据。回归树就是将不断地将数据分成2个部分——从每个节点创建2个子节点,直到达到给定的停止值(这是一个节点所能拥有的最小数据量)。它会提前停止树的构建过程,我们将其称为预修剪树。
为什么会有早停的机制?如果我们要继续进行分配直到节点只有一个值是,这创建一个过度拟合的方案,每个训练数据都只能预测自己。
说明:当模型完成时,它不会使用根节点或任何中间节点来预测任何值;它将使用回归树的叶子(这将是树的最后一个节点)进行预测。
为了得到最能代表给定阈值数据的阈值,我们使用残差平方和。它可以在数学上定义为
让我们看看这一步是如何工作的。
既然计算了阈值的SSR值,那么可以采用具有最小SSR值的阈值。使用该阈值将训练数据分为两个(低和高部分),其中其中低部分将用于创建左子节点,高部分将用于创建右子节点。
def SSR(r, y): return np.sum( (r - y)**2 ) SSRs, thresholds = [], [] for i in range(len(x) - 1): threshold = x[i:i+2].mean() low = np.take(y, np.where(x threshold)) guess_low = low.mean() guess_high = high.mean() SSRs.append(SSR(low, guess_low) + SSR(high, guess_high)) thresholds.append(threshold) print('Minimum residual is: {:.2f}'.format(min(SSRs))) print('Corresponding threshold value is: {:.4f}'.format(thresholds[SSRs.index(min(SSRs))]))
在进入下一步之前,我将使用pandas创建一个df,并创建一个用于寻找最佳阈值的方法。所有这些步骤都可以在没有pandas的情况下完成,这里使用他是因为比较方便。
df = pd.DataFrame(zip(x, y.squeeze()), columns = ['x', 'y']) def find_threshold(df, plot = False): SSRs, thresholds = [], [] for i in range(len(df) - 1): threshold = df.x[i:i+2].mean() low = df[(df.x threshold)] guess_low = low.y.mean() guess_high = high.y.mean() SSRs.append(SSR(low.y.to_numpy(), guess_low) + SSR(high.y.to_numpy(), guess_high)) thresholds.append(threshold) if plot: plt.scatter(thresholds, SSRs, s = 3) plt.show() return thresholds[SSRs.index(min(SSRs))]
创建子节点
在将数据分成两个部分后就可以为低值和高值找到单独的阈值。需要注意的是这里要增加一个停止条件;因为对于每个节点,属于该节点的数据集中的点会变少,所以我们为每个节点定义了最小数据点数量。如果不这样做,每个节点将只使用一个训练值进行预测,会导致过拟合。
可以递归地创建节点,我们定义了一个名为TreeNode的类,它将存储节点应该存储的每一个值。使用这个类我们首先创建根,同时计算它的阈值和预测值。然后递归地创建它的子节点,其中每个子节点类都存储在父类的left或right属性中。
在下面的create_nodes方法中,首先将给定的df分成两部分。然后检查是否有足够的数据单独创建左右节点。如果(对于其中任何一个)有足够的数据点,我们计算阈值并使用它创建一个子节点,用这个新节点作为树再次调用create_nodes方法。
class TreeNode(): def __init__(self, threshold, pred): self.threshold = threshold self.pred = pred self.left = None self.right = None def create_nodes(tree, df, stop): low = df[df.x tree.threshold] if len(low) > stop: threshold = find_threshold(low) tree.left = TreeNode(threshold, low.y.mean()) create_nodes(tree.left, low, stop) if len(high) > stop: threshold = find_threshold(high) tree.right = TreeNode(threshold, high.y.mean()) create_nodes(tree.right, high, stop) threshold = find_threshold(df) tree = TreeNode(threshold, df.y.mean()) create_nodes(tree, df, 5)
这个方法在第一棵树上进行了修改,因为它不需要返回任何东西。虽然递归函数通常不是这样写的(不返回),但因为不需要返回值,所以当没有激活if语句时,不做任何操作。
在完成后可以检查此树结构,查看它是否创建了一些可以拟合数据的节点。 这里将手动选择第一个节点及其对根阈值的预测。
plt.scatter(x, y, s = 0.5, label = 'Data') plt.plot([tree.threshold]*2, [-16, 10], 'r--', label = 'Root threshold') plt.plot([tree.right.threshold]*2, [-16, 10], 'g--', label = 'Right node threshold') plt.plot([tree.threshold, tree.right.threshold], [tree.right.left.pred]*2, 'g', label = 'Right node prediction') plt.plot([tree.left.threshold]*2, [-16, 10], 'm--', label = 'Left node threshold') plt.plot([tree.left.threshold, tree.threshold], [tree.left.right.pred]*2, 'm', label = 'Left node prediction') plt.plot([tree.left.left.threshold]*2, [-16, 10], 'k--', label = 'Second Left node threshold') plt.legend()
这里看到了两个预测:
- 第一个左节点对高值的预测(高于其阈值)
- 第一个右节点对低值(低于其阈值)的预测
这里我手动剪切了预测线的宽度,因为如果给定的x值达到了这些节点中的任何一个,则将以属于该节点的所有x值的平均值表示,这也意味着没有其他x值参与 在该节点的预测中(希望有意义)。
这种树形结构远不止两个节点那么简单,所以我们可以通过如下调用它的子节点来检查一个特定的叶子节点。
tree.left.right.left.left
这当然意味着这里有一个向下4个子结点长的分支,但它可以在树的另一个分支上深入得多。
预测
我们可以创建一个预测方法来预测任何给定的值。
def predict(x): curr_node = tree result = None while True: if x curr_node.threshold: if curr_node.right: curr_node = curr_node.right else: break return curr_node.pred
预测方法做的是沿着树向下,通过比较我们的输入和每个叶子的阈值。如果输入值大于阈值,则转到右叶,如果小于阈值,则转到左叶,以此类推,直到到达任何底部叶子节点。然后使用该节点自身的预测值进行预测,并与其阈值进行最后的比较。
使用x = 3进行测试(在创建数据时,可以使用上面所写的函数计算实际值。-3**2+3+5 = -1,这是期望值),我们得到:
predict(3) # -1.23741
计算误差
这里用相对平方误差验证数据
def RSE(y, g): return sum(np.square(y - g)) / sum(np.square(y - 1 / len(y)*sum(y))) x_val = np.random.uniform(-2, 5, 50) y_val = np.array( [f(i) for i in x_val] ).squeeze() tr_preds = np.array( [predict(i) for i in df.x] ) val_preds = np.array( [predict(i) for i in x_val] ) print('Training error: {:.4f}'.format(RSE(df.y, tr_preds))) print('Validation error: {:.4f}'.format(RSE(y_val, val_preds)))
可以看到误差并不大,结果如下
概括的步骤
更深入的模型
一个更适合回归树模型的数据:因为我们的数据是多项式生成的数据,所以使用多项式回归模型可以更好地拟合。我们更换一下训练数据,把新函数设为
def f(x): mu, sigma = 0, 0.5 if x = 3 and x = 6: return 5 + np.random.normal(mu, sigma, 1) np.random.seed(1) x = np.random.uniform(0, 10, num_points) y = np.array( [f(i) for i in x] ) plt.scatter(x, y, s = 5)
在此数据集上运行了上面的所有相同过程,结果如下
比我们从多项式数据中获得的误差低。
最后共享一下上面动图的代码:
import pandas as pd import numpy as np import matplotlib.pyplot as plt from matplotlib.animation import FuncAnimation #===================================================Create Data def f(x): mu, sigma = 0, 1.5 return -x**2 + x + 5 + np.random.normal(mu, sigma, 1) np.random.seed(1) x = np.random.uniform(-2, 5, 300) y = np.array( [f(i) for i in x] ) p = x.argsort() x = x[p] y = y[p] #===================================================Calculate Thresholds def SSR(r, y): #send numpy array return np.sum( (r - y)**2 ) SSRs, thresholds = [], [] for i in range(len(x) - 1): threshold = x[i:i+2].mean() low = np.take(y, np.where(x threshold)) guess_low = low.mean() guess_high = high.mean() SSRs.append(SSR(low, guess_low) + SSR(high, guess_high)) thresholds.append(threshold) #===================================================Animated Plot fig, (ax1, ax2) = plt.subplots(2,1, sharex = True) x_data, y_data = [], [] x_data2, y_data2 = [], [] ln, = ax1.plot([], [], 'r--') ln2, = ax2.plot(thresholds, SSRs, 'ro', markersize = 2) line = [ln, ln2] def init(): ax1.scatter(x, y, s = 3) ax1.title.set_text('Trying Different Thresholds') ax2.title.set_text('Threshold vs SSR') ax1.set_ylabel('y values') ax2.set_xlabel('Threshold') ax2.set_ylabel('SSR') return line def update(frame): x_data = [x[frame:frame+2].mean()] * 2 y_data = [min(y), max(y)] line[0].set_data(x_data, y_data) x_data2.append(thresholds[frame]) y_data2.append(SSRs[frame]) line[1].set_data(x_data2, y_data2) return line ani = FuncAnimation(fig, update, frames = 298, init_func = init, blit = True) plt.show()
到这里,我们也就讲完了《从零开始使用Python手写回归树算法》的内容了。个人认为,基础知识的学习和巩固,是为了更好的将其运用到项目中,欢迎关注golang学习网公众号,带你了解更多关于Python,数据,回归树的知识点!

- 上一篇
- 用PHP开发的二手回收网站实现用户登录日志功能

- 下一篇
- 摈弃Python的for循环的挑战
-
- 文章 · python教程 | 5小时前 |
- Python追加文件内容的实用方法及技巧
- 403浏览 收藏
-
- 文章 · python教程 | 6小时前 |
- Python导入模块的正确方法及技巧
- 213浏览 收藏
-
- 文章 · python教程 | 6小时前 | Django Flask 性能优化 输入验证 RESTfulAPI
- Python实现RESTfulAPI的技巧与方法
- 104浏览 收藏
-
- 文章 · python教程 | 6小时前 |
- VSCode配置Python:插件推荐与调试技巧
- 167浏览 收藏
-
- 文章 · python教程 | 6小时前 |
- FastAPI在Python中依赖注入的使用技巧
- 445浏览 收藏
-
- 文章 · python教程 | 7小时前 | JSON 数据处理 beautifulsoup Pandas xml.etree.ElementTree
- Python爬虫数据处理实用技巧及应用
- 112浏览 收藏
-
- 文章 · python教程 | 7小时前 |
- 获取淘宝服务器时间的Python代码实战
- 460浏览 收藏
-
- 文章 · python教程 | 7小时前 | 工厂模式 单例模式 类方法 @classmethod 类变量
- Python类方法定义的终极攻略
- 269浏览 收藏
-
- 文章 · python教程 | 8小时前 | scikit-learn DBSCAN 数据预处理 K-means 轮廓系数
- Python聚类分析教程与实战技巧分享
- 259浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 542次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 508次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 497次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 484次学习
-
- 笔灵AI生成答辩PPT
- 探索笔灵AI生成答辩PPT的强大功能,快速制作高质量答辩PPT。精准内容提取、多样模板匹配、数据可视化、配套自述稿生成,让您的学术和职场展示更加专业与高效。
- 30次使用
-
- 知网AIGC检测服务系统
- 知网AIGC检测服务系统,专注于检测学术文本中的疑似AI生成内容。依托知网海量高质量文献资源,结合先进的“知识增强AIGC检测技术”,系统能够从语言模式和语义逻辑两方面精准识别AI生成内容,适用于学术研究、教育和企业领域,确保文本的真实性和原创性。
- 44次使用
-
- AIGC检测-Aibiye
- AIbiye官网推出的AIGC检测服务,专注于检测ChatGPT、Gemini、Claude等AIGC工具生成的文本,帮助用户确保论文的原创性和学术规范。支持txt和doc(x)格式,检测范围为论文正文,提供高准确性和便捷的用户体验。
- 40次使用
-
- 易笔AI论文
- 易笔AI论文平台提供自动写作、格式校对、查重检测等功能,支持多种学术领域的论文生成。价格优惠,界面友好,操作简便,适用于学术研究者、学生及论文辅导机构。
- 53次使用
-
- 笔启AI论文写作平台
- 笔启AI论文写作平台提供多类型论文生成服务,支持多语言写作,满足学术研究者、学生和职场人士的需求。平台采用AI 4.0版本,确保论文质量和原创性,并提供查重保障和隐私保护。
- 43次使用
-
- Flask框架安装技巧:让你的开发更高效
- 2024-01-03 501浏览
-
- Django框架中的并发处理技巧
- 2024-01-22 501浏览
-
- 提升Python包下载速度的方法——正确配置pip的国内源
- 2024-01-17 501浏览
-
- Python与C++:哪个编程语言更适合初学者?
- 2024-03-25 501浏览
-
- 品牌建设技巧
- 2024-04-06 501浏览