如何使用 AWS 和 GCP 上的数千个 Spot 实例运行基于 CPU 的深度学习工作负载,而不会感到头痛
安德烈·沙皮洛在 Unsplash 上拍摄的照片
深度学习因在训练过程中消耗大量 GPU 资源而臭名昭著。然而,深度学习工作流程中有多个部分需要大量的 CPU 资源:
- 运行大规模推理作业
- 预处理输入数据——并将其物化在磁盘上作为
训练准备
这些工作负载通常具有以下属性:
- 工作负载(作业)会定期触发(相对于持续处理)
- 一个作业由许多*项组成。*每个项目都可以独立于其他项目进行。
- 项目被读取、处理并写回到某个存储器(通常是对象存储器或集中式文件系统)
- 在单个 CPU 内核上处理一个项目可能需要几秒到几分钟的时间
- 用户关心作业的吞吐量,而不是处理单个项目的延迟
- 单个项目的大小范围从几十 KB 到几 MB 甚至几 GB 的数据
- 该处理是无状态的—即,如果单个项目的处理失败,可以安全地重试,而不会产生任何副作用。
当您有足够多的项目要处理,并且单个项目的处理足够繁重时,这些作业会消耗大量的 CPU 资源。
很多是多少?
工作负载示例—训练数据的 3D 渲染
假设我们想要用在不同环境中拍摄的大量真实世界对象的图像来训练一个模型。制作大量示例的一个好方法是使用合成数据——获取物体的 3D 模型,并从多个角度、照明条件等渲染这些模型的视图。
3D 渲染是众所周知的 CPU 密集型工作负载;在单个 CPU 内核上渲染一幅图像可能需要几分钟时间。
渲染 250K 的图像会消耗 30K+的 CPU 时间。
我们如何处理这样的工作量?首先,让我们形式化我们的需求。
系统需求
一般
- 横向扩展至数千个内核
- 通过 就地运行实例来最大限度地降低云成本
- 最大限度降低基础设施设置和维护成本
用于运行作业的 API
- 配置每个作业的处理逻辑
- 配置作业中每个项目所需的 CPU+内存
- 配置从何处读取/写入数据
- 提交要处理的作业的所有项目
- 取消作业(即不处理其项目)
- 观察每个作业的成功/剩余/失败项目数
- 观察 CPU/内存消耗(帮助调整资源)
- 查看每个项目的日志(最好有—搜索日志)
逻辑系统设计
这些需求非常适合队列+工人的设计模式。
逻辑批处理系统设计。作者图片
逻辑流程
让我们浏览图表,了解每个阶段发生了什么:
1-用户将容器推入容器注册表。
该容器包含所需的处理逻辑和依赖关系。
2-用户将包含要处理的项目的消息排入队列。
每条消息包含:
- 存储中某个项目的 URI
- 可选—控制处理逻辑的配置/元数据
3 —如果需要,计算会自动缩放。
准备就绪后,消息会出队并交给可用的容器。
4 —容器从存储中读取一个项目,对其进行处理,并根据消息中的指令将输出写回
最后,系统会自动收集指标并记录到一个集中的位置。
艰难的方式——在 Kubernetes 上自己构建
以下是使用 K8s 实现这一目标所需的几个步骤:
- 首先—您需要设置 K8s,以便能够管理数千个节点 —从冗余到调整控制平面等。
- 创建使用混合策略在现场运行的节点组
- 部署消息总线——在集群上或集群外部
- 弄清楚如何向外扩展:
您是否应该让用户预先指定规模并部署一个副本集+通过更新大小和使用集群自动缩放器杀死空闲节点来找到自动向下扩展的方法?
或者使用像 KEDA 那样的动态缩放器? - 如果存储是一个共享文件系统,您需要创建持久卷,并自己将它们装入容器;如果是对象存储,我们可能需要将凭证作为秘密传入
- 通过集中式日志记录和监控添加完整的可观察性堆栈
- 用 Terraform 将它转化为红外线代码
- 等等。
这对于大多数团队来说是不可行的,因为工作量非常大,尤其是需要旋转和管理集群中成百上千的节点,即使是临时的
编辑—其他基于集群的解决方案
这一部分是在 LinkedIn 上热烈讨论后添加的。
除了 K8s 之外,还有其他解决方案可以帮助管理横向扩展工作负载,例如 Ray、Dask 甚至 Spark。
所有这些工具的共同点是,它们都有集群的概念,为机器之间需要交换数据的工作负载而构建,并且至少需要一些机器之间的某种形式的通信(例如,主对工人)。
当您扩展到 1000 个节点时,这种管理和通信会引入各种边缘情况,处理起来并不容易。如果您部署了集群,您就拥有了它。即使在像 EKS 这样的“托管”集群中,您仍然需要确保它正常运行。
在推理/预处理中—在节点通信的集群上运行没有任何附加价值。这些是不共享的工作负载。集群组件只是一个负担。相反,你想要的是提供你的代码和要处理的项目列表,并让计算机为你处理它。
这让我们有了更简单的选择…
注意:如果您的工作负载是用 Python 之外的语言编写的,或者需要不寻常的操作系统级库,并非所有上面提到的集群管理器都支持它。
简单的方法—利用托管解决方案
自动警报系统
AWS 为这种类型的工作负载构建了完美的工具,恰当地命名为
AWS Batch。
大规模批处理系统的 AWS 批处理解决方案。作者图片
让我们浏览图中的实体,并解释其工作原理:
计算环境
计算资源集合;可以包含多种节点类型,包括定点或按需节点。
计算环境指定其混合/最大大小,其中将最小值设置为 0 使其能够在没有工作要执行时扩展到零。
横向扩展时,计算环境基于实例模板创建实例。
AWS 批量计算环境。作者图片
实例模板
关于如何初始化实例的标准 EC2 构造。
可以包含向实例添加挂载等指令。
队列
AWS Batch 为您管理队列。每个队列都连接到一个特定的计算环境。这使我们能够轻松地为单独的作业创建单独的队列,并将作业相互隔离,使它们不会争用资源。
作业定义(注意:AWS 批处理术语中的作业是单个项目 ) 单个项目处理的模板。
指定:
- docker 图像。变量和其他详细信息
- 来自主机的装载点
- 此类作业所需的计算资源(CPU/mem 甚至 GPU)
- 如何将参数从消息传递到容器的入口点。
用户 Docker 图片
在 AWS Batch 中,用户的 docker 需要包含一个可以处理队列中单个项目的命令。它的返回代码用于确定处理是成功还是失败。
工单
单项要处理。与作业定义有“实例”关系,包含处理特定项目的特定参数值。
作业对象是我们插入到队列中执行的对象。
将流程整合在一起
- 用户将作业放入队列。作业引用了一个作业定义**
- 如果需要,c 计算机环境通过从实例模板创建一个新节点来扩展
- 系统根据新实例上的作业定义启动一个容器
- 系统从队列中弹出一个项目
- 系统调用容器上的处理命令,并从作业的主体向其传递参数
- 命令的返回值用于确定处理是成功还是失败。
可观察性
AWS Batch 提供了一个仪表板,其中显示了所有队列、多少作业(项目)正在等待/运行/成功/失败,以及对日志的访问。
日志是在项目级别上提供的,跨项目搜索日志并不容易。要获得基础架构级别的监控,您需要在底层 ECS 集群上启用容器洞察。
AWS 批处理作业仪表板。作者图片
总之,AWS Batch 获得 9/10 作为大规模 CPU 作业的解决方案
GCP
GCP 没有针对此类工作负载的内置解决方案。然而,您可以使用较低级别的构建块和少量的粘合代码实现与 AWS Batch 非常相似的东西,而不会比 AWS 产生更多的持续操作开销。
该解决方案的本质依赖于 GCP 独有的一个特性:启动一个计算实例的能力,该实例在启动时自动启动带有参数的 docker 容器。
基于 GCP 计算引擎的批处理系统解决方案。作者图片
托管 Instace 组
这是一组可以根据条件放大和缩小的实例。该组的实例是从一个实例模板创建的。参见自动缩放部分。
实例模板
定义系统中单个实例的外观:
- 实例(即容器)的资源— CPU/Mem/GPU。
- 挂载点
- 引导时启动容器的配置:
- Docker 图像
- 命令+参数
这些参数是静态的,即从该模板启动的所有容器将以完全相同的命令和参数启动。
- 为容器本身安装
- 该模板的实例是否可抢占
队列
在这里,您需要使用自己的队列。一个好的选择是为您的环境利用一个 PubSub 队列,以及一个默认订阅。
工作
作业作为定制的 json 有效载荷写入到 PubSub 订阅中;除此之外,它们应该包含处理单个项目所需的所有信息,包括项目的 URI 和任何处理配置
用户 Docker 图像
因为 GCP 没有提供基于消息调用容器的内置框架,所以从队列中取出工作项是容器的责任。
回想一下,在启动时传递给容器的参数是为所有运行在来自同一个模板的实例上的容器指定的。利用这些参数的一个好方法是配置它们来保存容器需要读取的订阅的名称。
最后,因为容器启动一次,而且只有在它的实例启动时才启动一次,所以容器的 entry 命令需要在一个循环中提取和处理项目,直到队列为空。
自动缩放
托管实例组能够基于 Stackdriver 指标进行伸缩。
具体来说,您将希望基于“发布订阅中未传递的消息”进行缩放。详见本帖。
将流程整合在一起
- 用户将包含要处理的项目的消息排入 PubSub 队列中。
- 托管实例组根据实例模板使用“订阅不足”缩放规则创建新实例
- 当实例启动时,它使用实例模板中提供的命令和静态参数运行用户的容器
- 用户的命令循环运行:从队列中弹出一个项目,处理它,等等。
- 当队列为空时,管理实例组将缩小所有实例
可观察性
GCP 提供了比 AWS 更方便的记录和监控解决方案;您可以搜索现成的日志流,查看实例组级别的指标以及单个机器等。
GCP 管理的实例组监视仪表板。作者图片
将功能包装在 SDK 中
为了便于采用这样的系统,明智的做法是为用户提供一个 CLI/SDK 来处理具体细节。
SDK 的主要 API 用于提交新的作业进行处理,通过指定:
- 作业名
- 队列名称(或自动创建一个与作业同名的新队列)
- 代表计算模板的名称(可以是作业定义或具有适当 InstanceTemplate 的 ManagedInstanceGroup 的名称)
- URI 待加工物品清单
然后,SDK 将使用底层云提供商的 API 来:
- 如果需要,创建队列(在 GCP,这意味着以编程方式为作业创建新的实例模板和托管实例组)
- 构造消息并将其放入队列,做一些事情,如转换路径或 ID,以便远程容器可以访问它们等。
注意:
可以添加更多的 API,例如获取工作进度报告等。
摘要
随着团队在深度学习模型的开发过程中继续充分利用他们的数据,他们通常需要运行大型 sclae CPU 密集型工作负载的能力。
这些批处理作业执行一些任务,例如对大型数据集执行推理,或者将大文件预处理成更有用的表示形式。
这些大规模作业可能需要数千个 CPU 内核,并对自我管理的基础架构提出了巨大的扩展挑战。
在这篇文章中,我们介绍了如何构建一个框架,以经济高效的方式在 GCP 和 AWS 中运行这样的工作。
如何运行脸书-先知预测 X100 更快
向量化先知的不确定性建模
- 01/25/23 编辑:FB 工程师将本文中的解决方案集成到版本 1.1.2 的包中。由于一些实现上的差异,它仍然比本文最后的解决方案稍慢,但不是很慢。
- 如果您对学习矢量化和 Prophet 的内部工作方式不感兴趣,而只想更快地运行 Prophet,您可以跳到**TL;**博士在最后。
脸书先知
脸书的用于时间序列预测的 Prophet 包于 2017 年发布,自此成为最受欢迎的预测算法之一:据 PyPy 称,Prophet 被下载了约 2000 万次。尽管有人批评模型的准确性以及许多机器学习应用程序向神经网络发展的总体趋势,下载流仍在继续。Prophet 的受欢迎程度可能源于其简单的开箱即用、置信区间、清晰的可视化,以及它经常胜过经典统计算法(ARIMA、ETS)的事实。
无论多受欢迎,Prophet 都有一个巨大的缺点:它非常慢,并且不能扩展到数十万个项目。
让我们尝试运行以下代码,创建一个随机时间序列:
import pandas as pd
import numpy as np
import datetime
from fbprophet import Prophetn = 100
some_data = pd.DataFrame({‘ds’:pd.date_range(datetime.datetime(2020,1,1,), freq=’W’, periods=n), ‘y’: np.random.rand(n)})
现在让我们测试运行时间:
%%timeit
prophet = Prophet(interval_width=0.8)
prophet.fit(some_data)
输出:
# 91.5 ms ± 1.66 ms per loop
大约 0.1 秒来拟合数据。但是真正的痛苦出现在“预测”阶段:
%%timeit
prophet.predict(some_data)
输出:
1.15 s ± 55.9 ms per loop
得到预测需要整整一秒以上的时间!这是令人惊讶的,因为在大多数 ML 模型中,训练是昂贵的,而预测是廉价的。
在这篇文章中,我们将看到为什么 Prophet 的 predict 函数相对较慢,以及如何让它运行快 100 倍(注意,您仍然必须首先拟合模型,因此 fit+predict 只会看到 5-20 倍的改善)。在这个过程中,我们将学习一些关于时间序列不确定性建模、统计分布和矢量化的知识。
让我们首先解释 Prophet 的不确定性建模及其部分代码,然后展示我们如何轻松地对函数进行矢量化。
寻找耗时函数
如果您剖析这个“预测”代码,您就可以看到错误在哪里。
预言家的预测简介。(图片由作者提供)
大概 98%的时间都花在“预测 _ 不确定性”上。此函数在结果数据帧中创建“yhat_upper”和“yhat_lower ”,它们是 80%(或 interval_width 中给定的任何其他值)置信区间的边缘——实际点可能位于此处。
当 Prophet 对不确定性建模时,它会做什么,这需要很长时间?
先知的不确定性
(如果你不熟悉 Prophet 的附加模型,你可能应该从一个快速概述开始)
Prophet 建模假设数据中有两个不确定性来源:
- 趋势线周围的高斯残差
- 更改坡度值
拟合时,Prophet 会在训练数据中找到趋势变化的最佳点,以最佳地拟合数据-假设趋势在任何给定点都是线性的,但趋势的斜率可能会发生变化。自然地,发现的趋势变化越多,相邻斜率之间的差值越大,未来值的不确定性就越大,如这些例子所示。
左图:当有许多变化点时,不确定性会快速增长。右图:历史变化点很少(它们之间的差值很小),不确定性主要取决于历史残差。(图片由作者提供)
如果训练数据中的趋势变化显著且频繁,则未来可能值的范围很大。相比之下,如果趋势在训练数据中相对恒定,我们更确定它将在未来以直线路径继续,在这种情况下,唯一剩余的误差源是我们已经在历史数据中看到的残差(它不随时间增长)。
我们不会讨论 Prophet 如何找到变化点及其增量,这都是在拟合阶段完成的。
预测拟合模型中的不确定性
以下是 Prophet’s predict_uncertainty 的“释义代码”(压缩后,为了清晰起见,去掉了一些不重要的部分)(可以直接跳到文字描述):
让我们用文字来概括这个过程:
以下是这些趋势样本的样子:
用 fbprophet 随机抽样未来趋势。(图片由作者提供)
训练数据有两个斜率变化。图中显示了五条未来趋势线,注意每条线的变化点数量、发生时间以及斜率之间的差值都是不同的。
Prophet 将高斯噪声添加到这些趋势线中,为我们提供了:
随机抽样期货与 fbprophet,包括随机残差。(图片由作者提供)
最后,它为每个时间步长的值找到 10%和 90%的分位数,给出我们的置信区间。
优化运行时间
我们的目标是通过向量化它使这个过程更有效。对矢量化的详细解释超出了本文的范围,但可以说,如果我们去除 for 循环并在 NumPy 数组上执行所有操作,该过程将运行得更快。
在我们对代码进行矢量化之前,让我们注意到这个过程的一部分是多余的:均值周围的置信区间的大小仅取决于斜率变化的可能性和大小以及过去的残差;因此,我们不需要连接训练数据的斜率、截距和增量,并评估它们的值。该区间的平均值取决于最终趋势线 yhat,yhat 取决于训练数据截距和斜率,但它是在 predict_uncertainty 开始之前计算的。
我们可以假设斜率=0,截距=0,使用斜率变化的可能性和大小,找到所有时间步长的 10%-90%间隔(例如+/-8)。然后,我们将这个值加到 yhat(例如 17)上,得到置信区间(9-25)。
主预测已创建(虚线)
一、不确定性的大小计算在 0 左右。二。该间隔将被添加到主预测中。(图片由作者提供)
请注意,随着时间的增加,置信区间的宽度也会增加(这在直觉上是有意义的)。这是因为我们对未来的预测越多,趋势变化的影响就越大。
现在让我们向量化
请记住,我们的目标是创建一个矩阵,其中每行是一个采样趋势,每列是未来的某个日期。例如
(图片由作者提供)
但是具有 1000+行。
首先,我们需要对斜率变化的数量和位置进行采样。这是 Prophet 从每一行的泊松分布中取样,然后随机分配变点数。没有泊松,我们会得到同样的结果。在任何时间步长发生变点的可能性由训练数据中观察到的变点数量除以训练数据的长度确定。假设我们有一个经过训练的 prophet_obj 和一个预测未来日期的 forecast_df,可能性是:
prophet_obj.changepoints_t 是训练数据中发生变化点的时间列表。什么是 single_diff?这是 Prophet 对时间进程建模的结果。
先知的时间进程
在 train 和 predict 函数中,Prophet 将 Pandas 数据帧中的 time(“ds”)列转换为一个名为 t 的数组,表示时间的进展。由于种种原因,训练时 t 在 0-1 范围内比较方便。所以,在训练中,t 是通过将任意两个元素之间的间隔设置为 1/training_length 来创建的。例如,如果有 50 个训练数据点,t 等于[0,0.02,0.04,…, 0.98, 1.].对于预测,它将继续以相同的间隔[1.02,1.04…]直到 forecast_df 的长度。
因为 t 数组代表时间的进程,所以它可以用来计算一段时间的进程。例如, some_coef*t 创建线性趋势,其中 some_coef 代表斜率。因此,斜率为 4 意味着在每个时间步长中,该系列增加 4*single_diff (在本例中为 4*0.02)。
随着时间变化斜率=4 的值(y 轴)( x 轴)。作者图片
回到矢量化斜率变化
我们现在有了在任一给定点斜率变化的可能性。让我们创建一个斜率变化矩阵:
其中 k 是样本数(行数)。结果是一个布尔矩阵:
随机布尔变点矩阵。(图片由作者提供)
这就是奇迹发生的地方。取每行的总和,绘制分布图,你会得到:
随机布尔矩阵的行和是泊松分布。(图片由作者提供)
具有似然 len(future_t_time)均值的泊松分布*!准确地从哪个 Prophet 样本中为每一行设置 n_changes!
这直接来源于泊松的定义——如果你有 q 个独立的抽奖,每个抽奖都有 l 为正的可能性,那么你会得到一个平均值为 q*l 的泊松分布。
我们替换了 Prophet 对一行中的变点数量的 for-loop 采样——然后对它们在时间步长中的位置进行采样——并且用整个矩阵的一行来替换它!
但是我们不需要一个布尔值来判断是否发生了变化——我们需要变化的增量。这部分很简单:
我们创建一个新的样本矩阵,这个矩阵来自拉普拉斯分布,具有平均绝对训练数据增量的标度。为什么是拉普拉斯?我不知道是否有理论上的正当理由,但这就是 Prophet 所做的,所以我们遵循它(除了 Prophet 为每一行单独执行采样,我们采样一个矩阵)。取两个矩阵的乘积,得到:
斜率变化值矩阵。(图片由作者提供)
每个时间步长内每个采样趋势的斜率变化(记住行=采样趋势,列=时间步长)。
我们快完成了!现在我们需要从斜率变化过渡到实际预测值。
斜率-实际值的变化
假设我们有下面的斜率增量(相邻台阶之间斜率值的变化):【0,1,0,0,-4,0,2】。假设我们从 0 的实际斜率开始,每一步的斜率将是[0,1,1,1,-3,-3,-1],对于每一个时间步,我们取增量之和,或者换句话说:“累计”。
实际值的差值示例。(图片由作者提供)
但是这仅仅给出了每个点的斜率(而不是斜率增量)。我们想要实际值**!线性斜率意味着我们在每次迭代中增加斜率值。如果 3 个时间步长的斜率为 2,实际值将为[2,4,6];很明显,这是另一个坎姆。因此,从斜率增量矩阵过渡到时间序列的实际值:**
记住斜率为 4 意味着 4*single_diff 是实际值,所以我们取这个新矩阵与 single_diff 的乘积。
添加高斯残差
现在我们有了样本趋势矩阵,我们只需要添加高斯噪声:
其中 sigma 是训练数据中趋势的标准偏差。
我们完成了样本矩阵的矢量化创建。如果我们希望置信区间在 10%-90%之间,我们设置
简单地将区间的上下边缘添加到 yhat 预测中。全部完成!除了一些小问题(请随意跳过接下来的 3 个部分)。
中期趋势变化
矢量化代码和 Prophet 之间有一个微小的建模差异。Prophet 允许斜率变化在时间步长之间发生。例如,如果 t = [1.02,1.04,1.06…],斜率变化可以设置为 1.028。或者换句话说:如果您的数据代表周一的周值,Prophet 允许周六或其他任何一天的斜率变化。
我不确定这种建模是否对所有数据集都有意义。还有,从我的经验来看,差别可以忽略不计。
然而,为了完整起见:让我们考虑在第一个实际步骤之前发生的趋势变化(在 1.0–1.02 中)。如果它接近 1.0(前一个星期一),到时间步长 1.02 时,我们将得到一个完整步长的变化(4*single_diff)。但如果接近 1.02(周日),斜率变化对那个时间步长影响不大。这种变化可能发生在光谱的任何地方,最好的修正是两个极端的平均值:每一个时间步长都是前一个时间步长(第一个时间步长为 0)。
另一种方法是创建一个具有 k 倍多的列的矩阵,并且改变点的可能性是先前可能性的 1/k。该矩阵将表示比时间序列中的间隔更小的间隔。在 cumsum.cumsum 之后,我们可以提取每第 k 列。但是,这种差异可能是微不足道的。
逻辑增长
默认情况下,Prophet 假设增长是线性的,但您可以将其设置为逻辑,在这种情况下,斜率变化的创建保持不变,但从矩阵到采样趋势的转换完全不同(非线性)。Prophet 为这种转变编写的代码是巨大的,但是将代码转换成矢量化版本是微不足道的。我不会在这篇文章中解释它,但下面给出了矢量化代码供您使用。
训练数据
最后一个问题——以上所有内容都适用于对未来的预测,在这种情况下,趋势值是未知的,因此需要进行采样。但是,有时我们对训练数据调用预测函数。那里的不确定性只取决于趋势周围的高斯噪声,所以我们可以将 sample_trends 设置为零。
时间和准确度对比
矢量化版本的速度有多快?
%%timeit
add_prophet_uncertainty(p, forecast_df)
输出:
2.13 ms ± 139 µs per loop
相比之下,原始的、非矢量化的 Prophet 版本为 1s+。这是 500 倍的进步。
它返回的结果和 Prophet 一样吗?相当接近。
(图片由作者提供)
为什么结果不一样?请记住,这是一个随机过程,由 Prophet 和矢量化代码创建的 1000 个样本不会有完全相同的值。如果标准差很大— 1000 个样本不足以让大数定律生效。再次运行它,你会得到一个稍微不同的结果。事实上,由于矢量化版本快得多,我将 k 样本设置为 10000,这给出了更一致的结果,这就是为什么它比上面例子中 Prophet 的预测更平滑。即使有 10000 个样本,仍然只需要大约 13 毫秒。
TL;博士
首先定义这些函数(如果您从不使用逻辑增长—您可以删除 prophet_logistic_uncertainty 及其用法,这占代码的一半以上):
给定一些 training_df,如果您需要训练和预测的不确定性区间,请运行以下代码:
在 Python Jupyter 笔记本中运行线性混合效果模型的三种方法
实践教程
关于如何在 Python 和 Jupyter 笔记本中运行线性混合效应回归(LMER)模型的教程
我不能完全从 R 切换到 Python 进行数据分析的原因之一是线性混合效应模型只在 R 中可用。线性混合效应模型是一种强大的统计方法,在处理纵向、层次或聚类数据时非常有用。简而言之,如果您的数据具有重复的样本、相关数据或自然“分组”,例如来自同一个人的重复响应、按不同地理位置聚类的数据,甚至是来自一组交互的人的数据,您可能希望在分析中使用线性混合效应模型。
您可以从这些资源(林德斯特罗姆&贝茨,1988 ) ( 贝茨等人,2015 )中了解更多关于线性混合效应模型或线性混合效应回归(LMER)如何以及为什么有效的信息,但在本教程中,我们将重点关注如何在 Python Jupyter 笔记本环境中运行这些模型。在早期,人们从 Python 中保存数据,在 R 中打开数据并运行 LMER 模型。多年来,R & Python 对彼此有了更好的了解,出现了几种用 Python 运行 LMER 分析的选项。下面是我们将探讨的三个选项,我为每个选项提供了示例代码:
- 统计模型中的 LMER
- 使用 rpy2 和%Rmagic 访问 R 中的 LMER
- Pymer4 无缝接入 LMER R
这里有一个 Google Colab Jupyter 笔记本来遵循这些方法!
统计模型中的 LMER
目前,最简单的开箱即用解决方案是使用 Statsmodels 包中的 LMER 实现(示例此处为)。安装最容易,就像pip install statsmodels
一样简单。安装后,您可以像下面这样运行 LMER。
为了提供更多的背景信息,我们正在分析dietox
数据集(在此了解更多关于数据集的信息),以预测猪的weight
作为time
的函数,其随机斜率由re_formula="~Time"
指定,随机截距由groups=data["Pig"]
自动指定。输出如下图所示,包括系数、标准误差、z 统计、p 值和 95%置信区间。
Statsmodels LMER 输出
虽然这很好,但是用这种语法指定随机效应有些不方便,并且偏离了 LMER 在 R 中使用的传统公式表达式。例如,在 R 中,随机斜率和截距是在模型公式中指定的,在一行中,例如:
lmer('Weight ~ Time + (1+Time|Pig)', data=dietox)
这导致了我们的第二个选择,即通过 rpy2 在 Python 和 R 之间的直接接口在 R 中使用 LMER。
使用 rpy2 和%Rmagic 访问 R 中的 LMER
第二种选择是通过 rpy2 接口直接访问 R 中原来的 LMER 包。rpy2 接口允许用户在 Python Jupyter 笔记本环境和 R 环境之间来回传递数据和结果。rpy2 过去在安装上是出了名的挑剔,但是这些年来它变得更加稳定了。要使用这个选项,您需要在您的机器上安装 R 和 rpy2,这可以在 Google Colab 中通过以下代码实现:
第一行使用 Linux 语法安装 R。如果你使用的是 Mac 或 Windows,你可以简单地按照安装说明来完成。下一组命令行安装 rpy2,然后使用 rpy2 安装lme4
和lmerTest
包。
接下来,您需要通过运行以下代码,在 Jupyter 笔记本单元中激活 Rmagic。
%load_ext rpy2.ipython
在这之后,任何以%%R
开头的 Jupyter 笔记本单元都允许你从笔记本上运行 R 命令。例如,要运行我们在 statsmodels 中运行的模型,您需要执行以下操作:
请注意,该代码以%%R
开头,表示该单元包含 R 代码。我们还使用了比 statsmodels 更简单的公式表达式,我们能够指定我们的分组是Pigs
,并且我们正在通过(1+Time|Pig)
估计随机斜率和截距。这将给出一些结果,如果您在 r 中使用过 LMER,您会更加熟悉这些结果
rpy2 的 LMER 输出
正如我前面提到的,您也可以将您的熊猫数据帧传递给 r。还记得我们之前在 statsmodels 部分加载的data
数据帧吗?我们可以将它传递给 R,运行相同的 LMER 模型,并像这样检索系数:
-i data
将我们的 Python 熊猫数据帧data
发送到 R 中,我们用它来估计我们的模型m
。接下来,我们从带有beta <- fixef(m)
的模型中检索贝塔系数,该系数被导出回我们的笔记本,因为我们在第一行-o betas
中指定了。
这种方法最好的部分是,在运行模型之前,您可以在 R 环境中添加额外的代码。例如,您可能希望确保名为Evit
的列被识别为带有data$Evit <- as.factor(data$Evit)
的因子,使用contrasts(data$Evit) <- contr.poly
为该分类变量指定新的对比,或者甚至在公式本身中重新调整分类数据。当使用 rpy2 从 r 访问 LMER 时,所有这些都可以很容易地实现
总之, rpy2 接口为您提供了最大的灵活性和访问 LMER 和 R 中附加功能的能力,如果您正从 R 过渡到 Python,您可能会更熟悉这些功能。最后,我们将使用 Pymer4 包来接触一个中间选项。
LMER 与皮梅尔 4
Pymer4 ( Jolly,2018 )可以作为直接通过 rpy2 使用 LMER 和在 Statsmodels 中使用 LMER 实现之间的一个方便的中间地带。这个包基本上给你带来了使用 R 公式语法的便利,但是以一种更 Pythonic 化的方式,而不必处理 R 魔细胞。
Pymer4 的 LMER 输出
估计值包括随机截距和斜率估计值。
结论
我们介绍了在 Python Jupyter 笔记本环境中运行线性混合效果模型的 3 种方法。Statsmodels 可能是最方便的,但是对于已经使用过 R 语法中的 LMER 的用户来说,这种语法可能并不熟悉。使用 rpy2 为您提供了最大的灵活性和能力,但这可能会变得很麻烦,因为您需要使用 Rmagic 在 Python 和 R 单元之间切换。Pymer4 是一个很好的折衷方案,它提供了对 R 中 LMER 的方便访问,同时最小化了语言之间的切换成本。
这里有一个谷歌 Colab Jupyter 笔记本来运行所有的教程。
感谢您的阅读,并随时查看我的其他数据科学教程!
https://jinhyuncheong.medium.com/membership
如何在 Python 中对聚合数据运行逻辑回归
每个数据科学家都应该知道的 3 个简单解决方案
照片由叶小开·克里斯托弗·古特瓦尔德在 Unsplash 上拍摄
ι将向您展示 3 种技术,当您想要执行逻辑回归时,它们将帮助您处理 Python 中的聚集数据。
让我们创建一些虚拟数据。
import pandas as pd
import numpy as np
import statsmodels.api as sm
import statsmodels.formula.api as smf
df=pd.DataFrame(
{
'Gender':np.random.choice(["m","f"],200,p=[0.6,0.4]),
'Age':np.random.choice(["[<30]","[30-65]", "[65+]"],200,p=[0.3,0.6,0.1]),
"Response":np.random.binomial(1,size=200,p=0.2)
}
)
df.head()Gender Age Response
0 f [30-65] 0
1 m [30-65] 0
2 m [<30] 0
3 f [30-65] 1
4 f [65+] 0
非聚集数据的逻辑回归
首先,我们将对非汇总数据运行逻辑回归模型。我们将使用库统计模型,因为这是我们将用于聚合数据的库,并且更容易比较我们的模型。此外,统计模型可以以更经典的统计方式(如 r)给我们一个模型的摘要。
提示:如果您不想将分类数据转换成二进制来执行逻辑回归,您可以使用 统计模型公式 来代替 Sklearn。
model=smf.logit('Response~Gender+Age',data=df)
result = model.fit()
print(result.summary())
Logit Regression Results
==============================================================================
Dep. Variable: Response No. Observations: 200
Model: Logit Df Residuals: 196
Method: MLE Df Model: 3
Date: Mon, 22 Feb 2021 Pseudo R-squ.: 0.02765
Time: 18:09:11 Log-Likelihood: -85.502
converged: True LL-Null: -87.934
Covariance Type: nonrobust LLR p-value: 0.1821
================================================================================
coef std err z P>|z| [0.025 0.975]
--------------------------------------------------------------------------------
Intercept -2.1741 0.396 -5.494 0.000 -2.950 -1.399
Gender[T.m] 0.8042 0.439 1.831 0.067 -0.057 1.665
Age[T.[65+]] -0.7301 0.786 -0.929 0.353 -2.270 0.810
Age[T.[<30]] 0.1541 0.432 0.357 0.721 -0.693 1.001
================================================================================
聚合数据的逻辑回归
1.使用有反应者和无反应者的逻辑回归
在下面的代码中,我们对数据进行了分组,并为响应者( Yes )和非响应者( No )创建了列。
grouped=df.groupby(['Gender','Age']).agg({'Response':[sum,'count']}).droplevel(0, axis=1).rename(columns={'sum':'Yes','count':'Impressions'}).eval('No=Impressions-Yes')
grouped.reset_index(inplace=True)
groupedGender Age Yes Impressions No
0 f [30-65] 9 38 29
1 f [65+] 2 7 5
2 f [<30] 8 25 17
3 m [30-65] 17 79 62
4 m [65+] 2 12 10
5 m [<30] 9 39 30
glm_binom = smf.glm('Yes + No ~ Age + Gender',grouped, family=sm.families.Binomial())
result_grouped=glm_binom.fit()
print(result_grouped.summary())
Generalized Linear Model Regression Results
==============================================================================
Dep. Variable: ['Yes', 'No'] No. Observations: 6
Model: GLM Df Residuals: 2
Model Family: Binomial Df Model: 3
Link Function: logit Scale: 1.0000
Method: IRLS Log-Likelihood: -8.9211
Date: Mon, 22 Feb 2021 Deviance: 1.2641
Time: 18:15:15 Pearson chi2: 0.929
No. Iterations: 5
Covariance Type: nonrobust
================================================================================
coef std err z P>|z| [0.025 0.975]
--------------------------------------------------------------------------------
Intercept -2.1741 0.396 -5.494 0.000 -2.950 -1.399
Age[T.[65+]] -0.7301 0.786 -0.929 0.353 -2.270 0.810
Age[T.[<30]] 0.1541 0.432 0.357 0.721 -0.693 1.001
Gender[T.m] 0.8042 0.439 1.831 0.067 -0.057 1.665
================================================================================
2.加权逻辑回归
对于这个方法,我们需要创建一个新列,其中包含每个组的响应率。
grouped['RR']=grouped['Yes']/grouped['Impressions']glm = smf.glm('RR ~ Age + Gender',data=grouped, family=sm.families.Binomial(), freq_weights=np.asarray(grouped['Impressions']))
result_grouped2=glm.fit()
print(result_grouped2.summary())
Generalized Linear Model Regression Results
==============================================================================
Dep. Variable: RR No. Observations: 6
Model: GLM Df Residuals: 196
Model Family: Binomial Df Model: 3
Link Function: logit Scale: 1.0000
Method: IRLS Log-Likelihood: -59.807
Date: Mon, 22 Feb 2021 Deviance: 1.2641
Time: 18:18:16 Pearson chi2: 0.929
No. Iterations: 5
Covariance Type: nonrobust
================================================================================
coef std err z P>|z| [0.025 0.975]
--------------------------------------------------------------------------------
Intercept -2.1741 0.396 -5.494 0.000 -2.950 -1.399
Age[T.[65+]] -0.7301 0.786 -0.929 0.353 -2.270 0.810
Age[T.[<30]] 0.1541 0.432 0.357 0.721 -0.693 1.001
Gender[T.m] 0.8042 0.439 1.831 0.067 -0.057 1.665
================================================================================
3.展开聚合数据
最后,我们可以“解组”我们的数据,并将我们的因变量转换为二进制,这样我们就可以像往常一样执行逻辑回归。
grouped['No']=grouped['No'].apply(lambda x: [0]*x)
grouped['Yes']=grouped['Yes'].apply(lambda x: [1]*x)
grouped['Response']=grouped['Yes']+grouped['No']
expanded=grouped.explode("Response")[['Gender','Age','Response']]
expanded['Response']=expanded['Response'].astype(int)
expanded.head() Gender Age Response
0 f [30-65] 1
0 f [30-65] 1
0 f [30-65] 1
0 f [30-65] 1
0 f [30-65] 1
model=smf.logit('Response~ Gender + Age',data=expanded)
result = model.fit()
print(result.summary())
Logit Regression Results
==============================================================================
Dep. Variable: Response No. Observations: 200
Model: Logit Df Residuals: 196
Method: MLE Df Model: 3
Date: Mon, 22 Feb 2021 Pseudo R-squ.: 0.02765
Time: 18:29:33 Log-Likelihood: -85.502
converged: True LL-Null: -87.934
Covariance Type: nonrobust LLR p-value: 0.1821
================================================================================
coef std err z P>|z| [0.025 0.975]
--------------------------------------------------------------------------------
Intercept -2.1741 0.396 -5.494 0.000 -2.950 -1.399
Gender[T.m] 0.8042 0.439 1.831 0.067 -0.057 1.665
Age[T.[65+]] -0.7301 0.786 -0.929 0.353 -2.270 0.810
Age[T.[<30]] 0.1541 0.432 0.357 0.721 -0.693 1.001
================================================================================
结论
对于所有 4 个模型,我们得出了相同的系数和 p 值。
根据我的经验,我发现获取项目的原始数据并不常见,在大多数情况下,我们处理的是聚合/分组数据。这些技术将帮助你轻松地处理它们,这就是为什么我认为是你的 Python 工具箱的一个很好的附件。
如果你正在使用 R,你可以阅读这个非常有用的帖子。
以后我会写更多初学者友好的帖子。在媒体上关注我或访问我的博客了解他们。
我欢迎提问、反馈和建设性的批评,你可以通过推特(Twitter)或社交网站(Instagram)联系我。
原载于https://predictivehacks.com
如何运行(与模型无关的元学习)MAML 算法
MAML 是一类元学习算法
作者图片
MAML 是一类元学习算法,由斯坦福研究中心和加州大学伯克利分校校友切尔西·芬恩博士创建。MAML 受到了这个问题背后的想法的启发,这个问题就是学习一件事情到底需要多少数据。我们能教算法学会如何学习吗?
在这样的背景下,传统的机器学习算法面临一些挑战:
- 需要强化训练
- 某些问题的标记数据可能是有限的
- 网络的性能可能对超参数的选择敏感
在这方面,元学习算法可以被设计来处理以下任务:
- 感应偏置的动态选择
- 构建多任务学习的元规则
- 学习如何通过超参数优化来学习
摘自切尔西·芬恩的原始研究:
MAML 是一种元学习算法,它与用梯度下降算法训练的任何模型兼容,并涵盖分类、强化学习(RL)和回归的问题
MAML 解决了什么样的问题?
MAML 被设计为在各种任务上训练模型,使得它可以仅用少量训练样本来学习新的学习任务。
MAML 的几个要点是:
- MAML 没有增加学习参数的数量。
- 对模型的架构或网络没有限制。
- 可以与其他深度学习框架结合,如递归神经网络(RNN)、卷积神经网络(CNN)和多层感知器(MLP)。
问题设置
MAML 的问题设置是从原始论文中复制的:
MAML 引入了一个叫做元训练的外部循环。
如何运行 MAML 代码?
Chelsea Finn 的 Github repo 提供了重现 MAML 结果的代码。您可以使用以下步骤来重现其结果:
- 我将创建一个 python 虚拟环境并安装依赖项:
sudo apt install virtualenv virtualenv — python=python3.6 maml
source maml/bin/activate
- 接下来,我们安装依赖项:
pip install tensorflow==1.11.0 pip install image
我没有使用最新版本的 Tensorflow,因为 MAML 代码是几年前写的,当时 TF2 还没有公开发布。- 克隆 MAML 回购:
git clone [https://github.com/cbfinn/maml](https://github.com/cbfinn/maml)
- 下载 omniglot 数据,对于本文,除了正弦示例之外,我将只运行 omniglot 示例:
wget [https://github.com/brendenlake/omniglot/raw/master/python/images_background.zip](https://github.com/brendenlake/omniglot/raw/master/python/images_background.zip) wget [https://github.com/brendenlake/omniglot/raw/master/python/images_evaluation.zip](https://github.com/brendenlake/omniglot/raw/master/python/images_evaluation.zip)
- 将 images_background 和 images_evaluation zip 文件解压到
maml/data/omniglot
文件夹,其中mamal
文件夹是 Github repo 文件夹。目录结构如下所示:
7.导航到maml
文件夹的data
子文件夹,将omniglot
的内容复制到omniglot_resized
。运行调整图像大小脚本
cd maml/data
cp -r omniglot/* omniglot_resized/
cd omniglot_resized
python resize_images.py
8.现在,我们回到 maml repo
的根目录,运行两个示例:
a .正弦曲线示例:
python main.py --datasource=sinusoid --logdir=logs/sine/ --metatrain_iterations=70000 --norm=None --update_batch_size=10
b. omniglot 示例:
python main.py --datasource=omniglot --metatrain_iterations=60000 --meta_batch_size=32 --update_batch_size=1 --update_lr=0.4 --num_updates=1 --logdir=logs/omniglot5way/
每个例子的检查点都会保存在log/
目录下。
**更新:**我发现代码库中有一个 bug。我认为这个漏洞可能是无意中引入的。在main.py
中,第 160 行saver.save(sess, FLAGS.logdir + ‘/’ + exp_string + ‘/model’ + str(itr))
,itr
变量是for
循环迭代器,我认为saver.save
语句应该在循环末尾的 for 循环内执行,而不是在循环外执行。
从这里开始,我相信一个中级水平的机器学习实践者应该能够修改流水线以满足他/她的要求。
如何使用 Docker 运行 PostgreSQL 和 pgAdmin
Docker 使 PostgreSQL 管理变得更加容易
照片由 bongkarn thanyakij 从 Pexels 和 Guillaume Bolduc 在 Unsplash
如果您不喜欢使用命令行界面管理数据库,您可以使用 pgAdmin 作为替代解决方案。它是基于 web 的 PostgreSQL 数据库服务器的前端。
我们将使用 Docker 进行设置,因为我们不想担心环境管理。通过使用 Docker,我们不必担心 PostgreSQL 或 pgAdmin 的安装。此外,您可以使用 Docker 在 macOS、Windows 和 Linux 发行版上运行这个项目。
通过这篇文章,您将了解如何使用 Docker 将 pgAdmin 连接到 PostgreSQL 数据库服务器。
设置
首先,你需要安装 Docker 。我将使用 macOS 进行演示。
方法一
我们将使用一个 Docker compose 文件作为我们的第一个方法,我们需要将 docker-compose.yml 放在一个文件夹中。在这种情况下,文件夹的名称是 pgAdmin 。我们来分解一下 docker-compose.yml 文件的各个成分。
version: '3.8'
services:
db:
container_name: pg_container
image: postgres
restart: always
environment:
POSTGRES_USER: root
POSTGRES_PASSWORD: root
POSTGRES_DB: test_db
ports:
- "5432:5432"
pgadmin:
container_name: pgadmin4_container
image: dpage/pgadmin4
restart: always
environment:
PGADMIN_DEFAULT_EMAIL: admin@admin.com
PGADMIN_DEFAULT_PASSWORD: root
ports:
- "5050:80"
首先,我们使用版本标记来定义合成文件格式,即 3.8。还有其他文件格式——1、2、2.x 和 3.x。你可以从 Docker 的文档中了解更多信息。
然后我们有一个散列叫做服务。在这里面,我们必须定义我们的应用程序想要使用的服务。对于我们的应用程序,我们有两个服务, **db、**和 pgadmin 。
为了方便起见,我们对两个服务都使用了标签 container_name ,将默认容器名改为 pg_container 和 pgadmin4_container 。
第二个标签图像用于定义 db 和 pgadmin 服务的 Docker 图像。为了使我们的设置过程快速简单,我们将使用预先构建的官方映像 PostgreSQL 和 pgAdmin 。
在我以前的 Docker 帖子中,我已经谈到了重启、环境和端口标签的使用。看看下面的帖子,你可以学习这些标签。
现在从 docker-compose.yml 文件所在的目录运行下面的命令。
cd pgAdmin
docker compose up
命令docker compose up
启动并运行整个应用程序。恭喜你!您正在使用 Docker 在您的机器上成功运行 PostgreSQL 数据库和 pgadmin4。现在让我们将 pgadmin4 连接到我们的 PostgreSQL 数据库服务器。
首先,通过访问 URLhttp://localhost:5050/,经由您喜爱的网络浏览器访问 pgadmin4 。使用【admin@admin.com】作为邮箱地址,使用 root 作为密码登录。
创建一个服务器。
点击服务器>创建>服务器创建新的服务器。
填写名称、主机名/地址、用户名和密码的数据。
选择通用标签。对于名称字段,使用任何名称。在这种情况下,我将使用 my_db 。现在移动到连接标签。要获得主机的值,运行以下命令。
docker ps
命令 ps 将显示所有正在运行的容器的简要信息。
docker ps
请先阅读更新章节。现在我们可以获取 PostgreSQL 容器的容器 id。
docker inspect fcc97e066cc8 | grep IPAddress
命令 inspect 显示正在运行的集装箱的详细信息。此外,我们使用管道和 grep 命令提取 IPAddress 信息。
码头工人检查 fcc97e066cc8
最后,我们得到了主机的值,,在本例中是192.168.80.2
。使用值 root 作为用户名,使用 root 作为密码,还要勾选**保存密码?**如果您不想在每次登录 pgadmin4 时都键入密码,请选择此框。
更新【2021 年 4 月 16 日】
今天早上我收到了一封来自 Dosenwerfer 的邮件,说这个人在再次运行服务时遇到了一些问题。因为它更改了 PostgreSQL 容器的 IP 地址,您必须再次设置配置。推荐的解决方案是使用容器名。因为容器名与主机名相同,所以您可以从这里的阅读更多内容。因此,我们当前的配置如下。
填写名称、主机名/地址、用户名和密码的数据。
您可以使用docker ps
命令找到 PostgreSQL 数据库服务器的容器名称,并从 NAMES 列中获取该名称。在这篇文章中,我们在 docker-compose.yml 文件中明确地命名了容器,所以你也可以参考它。非常感谢多森威尔。
方法 2
对于第二种方法,您可以将一个 pgAdmin docker 容器连接到一个正在运行的 PostgreSQL 容器。当您的 docker-compose.yml 文件中没有 pgAdmin 服务时,这很有帮助。看看下面的帖子,在那里你可以学到这个方法。
额外的
如果您想导入一些数据进行测试,可以使用我已经准备好的 SQL 查询。点击服务器> my_db >数据库> test_db >模式>表。右键点击表格,选择查询工具。将 SQL 查询从我的 GitHub 库中复制粘贴到查询编辑器中,然后单击 play 按钮。该操作将创建两个名为的表,学生和标记以及一些测试数据。
包裹
通过命令行界面管理数据库可能会很伤脑筋。为了解决这个问题,我们可以使用带有接口的工具。pgAdmin 解决了这个问题。而且,Docker 让整个流程更加流畅。此外,您可以使用我提供的测试数据来试验 PostgreSQL 查询。希望这能帮助你入门 PostgreSQL,pgAdmin,和 Docker。编码快乐!
相关职位
如何在 R 中轻松运行 Python ML 算法
如何让 Python 的 XGBoost 在 R 中轻松工作的例子
图片由来自 Pixabay 的 Arek Socha 提供
毫无疑问,Python 拥有所有编程语言中最广泛的 ML 算法,如果我打算进行任何形式的预测建模,Python 通常是我的第一选择。也就是说,我更喜欢用 R 来整理和准备数据,并且希望能够将 Python 算法导入 R,这样我就可以两全其美了。所以我最近决定看看是否可以在 r 中轻松运行 Python ML 算法。我选择尝试 k 倍交叉验证的 XGBoost 模型。
我认为在 RStudio 中做这件事很容易,所以我在这里写了“如何做”。我还用这个例子组合了一个 Github repo 。
要让这些方法工作,您需要在 R 项目中工作,并指向 Conda 环境或 Virtualenv 中包含您需要的所有 Python 包的 Python 可执行文件。您可以通过使用 R 项目目录中的一个.Rprofile
文件来做到这一点。每当您在 R 中启动一个项目时,这个文件都会在启动时执行它的内容。我的.Rprofile
有两行代码。第一行告诉 R 在 Conda 环境中哪里可以找到正确的 Python 可执行文件,我已经安装了所有需要的包(即pandas
、scipy
、scikit-learn
和XGBoost
)。这需要进行编辑,以指向您机器上的正确路径。
Sys.setenv(RETICULATE_PYTHON = "/home/rstudio/.local/share/r-miniconda/envs/r_and_py_models/bin/python3")
第二行是为了我方便。它打印出正在使用的 Conda 环境的确认。这是为了在启动时让我放心,R 知道我希望它在哪里执行 Python 代码。
print(paste("Python environment forced to", Sys.getenv("RETICULATE_PYTHON")))
当我开始我的项目时,我收到这条消息来确认正在使用预期的 Conda 环境。
用 R 编写 Python 函数在数据集上运行
我创建了一个名为python_functions.py
的 Python 文件,在其中我用 Python 编写了所需的函数,以便在任意 Pandas 数据帧上执行 XGBoost 模型。我这样做是为了让这些函数的所有参数都在一个名为parameters
的字典中。我需要编写四个 Python 函数——一个将我的数据分成训练和测试数据,一个缩放我的要素,一个运行 XGBoost,最后一个创建分类报告作为数据帧。以下是包含四个必需函数的文件内容:
现在在我的 R 项目中,我可以使用reticulate
包在 R 中获得这四个函数,使它们成为 R 函数。
library(reticulate)
source_python("python_functions.py")
示例:在 R 中使用 Python XGBoost
我们现在在 R 中使用这些函数来尝试学习预测一款高质量的葡萄酒。首先,我们下载白葡萄酒和红葡萄酒的数据集。
white_wines <- read.csv("https://archive.ics.uci.edu/ml/machine-learning-databases/wine-quality/winequality-white.csv",
sep = ";")red_wines <- read.csv("https://archive.ics.uci.edu/ml/machine-learning-databases/wine-quality/winequality-red.csv",
sep = ";")
我们将创建“白色对红色”作为一个新功能,我们将“高质量”定义为 7 分或以上的质量分数。
library(dplyr)white_wines$red <- 0
red_wines$red <- 1wine_data <- white_wines %>%
bind_rows(red_wines) %>%
mutate(high_quality = ifelse(quality >= 7, 1, 0)) %>%
select(-quality)head(wine_data)
现在我们的数据已经为建模设置好了,是时候将我们的 Python 函数付诸实践了。
现在我们设置我们的参数列表(R 中的列表相当于 Python 中的 dict):
params <- list(
input_cols = colnames(wine_data)[colnames(wine_data) != 'high_quality'],
target_col = 'high_quality',
test_size = 0.3,
random_state = 123,
subsample = (3:9)/10,
xgb_max_depth = 3:9,
colsample_bytree = (3:9)/10,
xgb_min_child_weight = 1:4,
k = 3,
k_shuffle = TRUE,
n_iter = 10,
scoring = 'f1',
error_score = 0,
verbose = 1,
n_jobs = -1
)
现在我们已经准备好运行 XGBoost 模型,比如说,三重交叉验证。首先,我们使用我们的 Python 函数split_data
分割数据——注意reticulate
将在幕后把输入翻译成它们的 Python 等价物,因此wine_data
将成为熊猫数据帧而params
将成为字典。
split <- split_data(df = wine_data, parameters = params)
我们的 Python 函数返回一个 dict,R 中的输出将是一个列表,我们可以将它输入到缩放函数中:
scaled <- scale_data(split$X_train, split$X_test)
同样,输出将是一个列表。现在,我们可以使用训练集上定义的参数运行 XGBoost 算法:
trained <- train_xgb_crossvalidated(
scaled$X_train_scaled,
split$y_train,
parameters = params
)
最后,我们可以为我们的测试集生成一个分类报告:
generate_classification_report(trained, scaled$X_test_scaled, split$y_test)
现在我们有了。你也可以使用训练过的对象trained
来生成新的预测,就像你在 Python 中做的那样。例如,我们可以看到测试集的第一行被归类为高质量。
test_data <- py_to_r(scaled$X_test_scaled)
trained$predict(test_data[1, ])[1] 1
本文展示了一个更通用的编写 Python 函数的过程,这些函数将处理任意输入,然后可以很容易地在 r 中执行。它应该很容易推广到任何其他类型的过程,无论是另一个 ML 算法还是像编写 Powerpoint 文档这样的过程。我希望它对你正在做的其他工作是一个有用的基础。
最初我是一名纯粹的数学家,后来我成为了一名心理计量学家和数据科学家。我热衷于将所有这些学科的严谨性应用到复杂的人的问题上。我也是一个编码极客和日本 RPG 的超级粉丝。在LinkedIn或Twitter上找我。也可以看看我在 drkeithmcnulty.com 的上的博客。
如何在 Jupyter 中运行 R 脚本
环境设置
关于如何在 Jupyter 中安装并运行 R 内核的简短教程
图片由 Carlos Andrés Ruiz Palacio 来自 Pixabay
Jupyter Notebook 是一个允许用不同语言创建实时代码的网络应用程序。通常,开发人员利用 Jupyter 笔记本用 Python 编写代码。但是,Jupyter 也支持其他编程语言,包括 Java、R、Julia、Matlab、Octave、Scheme、Processing、Scala 等等。
Jupyter 是如何工作的
Jupyter 不提供任何编译器或解释器。相反,它是一个与实际的编译器/解释器进行通信的过程。实际上,它将代码发送给编译器/解释器,并返回结果。
为了在 Jupyter 单元中运行(给定语言的)代码片段,安装该语言的相应内核就足够了。
R 的核
在本教程中,我将演示如何为 R 软件安装 Jupyter 内核。
首先,我需要在你的电脑上安装 R 软件。我可以从的官方网站下载 R 软件。安装完成后,我可以打开一个终端并启动 R,只需在控制台上输入R
,然后输入 Enter 命令。
注意:如果使用 Mac OS,需要从安装 R 的目录下运行 R 软件。通常,该目录是
/Library/Frameworks/R.framework/Versions/<version>/Resources/bin
其中<version>
表示 R 版本。我可以通过在控制台上键入以下命令来运行 R:
./R
一旦启动了 R 控制台,我必须通过以下命令下载devtools
包:
install.packages("devtools")
我选择镜像编号(例如,意大利是 48),然后按 Enter 键。
一旦安装完成,我可以从 Github 安装 IRKernel。我运行以下命令:
devtools::install_github("IRkernel/IRkernel")
前面的命令可能会失败。在这种情况下,我可以通过以下命令强制安装:
devtools::install_github("IRkernel/IRkernel", force=TRUE)
之后我安装了IRkernel
:
IRkernel::installspec()
现在我可以通过输入退出 R
quit()
我可以经营朱庇特。当 Jupyter 在浏览器中打开时,我可以点击右上角的 New 并选择 R 作为内核。快乐享受:)
作者图片
摘要
在本教程中,我已经演示了如何在 Jupyter 中安装 R 内核。这个过程非常简单快捷。
如果你仍然遇到安装问题,请查看我的 Youtube 安装教程。
如果你想了解我的研究和其他活动的最新情况,你可以在 Twitter 、 Youtube 和 Github 上关注我。
相关文章
[## 如何在 Android 设备上安装 Python 和 Jupyter Notebook
towardsdatascience.com](/how-to-install-python-and-jupyter-notebook-onto-an-android-device-900009df743f) https://alod83.medium.com/how-to-install-xampp-bcb43fb11912 https://alod83.medium.com/how-to-import-and-export-a-dataset-in-phpmyadmin-b108288a3ca3
新到中?您可以每月订阅几美元,并解锁无限的文章— 单击此处。
如何在 Python Pandas 中对数据帧进行采样
数据预处理
使用不同的技术对 Python Pandas 中的数据集进行采样的现成代码
图片来自 Pixabay
您可能只需要 Python 数据帧中的一些行。你可以通过不同的采样技术达到这个结果。
在本教程中,我演示了通过 Python Pandas 执行行采样的以下技术:
- 随意采样
- 有条件抽样
- 以恒定速率采样
完整的代码可以从我的 Github 库下载。
加载数据集
在本教程中,我利用了由scikit-learn
库提供的iris
数据集,并将其转换为pandas
数据帧:
from sklearn.datasets import load_iris
import pandas as pddata = load_iris()
df = pd.DataFrame(data.data, columns=data.feature_names)
作者图片
数据集由 4 列 150 行组成。
随意采样
给定一个有 N 行的数据帧,随机抽样从数据帧中抽取 X 个随机行,其中 X ≤ N. Python pandas
提供了一个名为sample()
的函数来执行随机抽样。
要提取的样本数量可以用两种可选方式表示:
- 指定要提取的随机行数
- 指定要提取的随机行的百分比。百分比以 0 到 1 之间的数字表示。
确切数量
在这种情况下,您可以将参数n
传递给sample()
函数,如下所示:
subset = df.sample(n=100)
在前面的例子中,sample()
函数提取了 100 个随机行。您可以通过shape
功能检查subset
结果数据集的形状:
subset.shape
它给出了以下输出:
(100, 4)
百分率
如果您想要指定要提取的随机行的百分比,您可以将frac
参数作为sample()
函数的输入:
subset = df.sample(frac=0.5)
在前面的例子中,sample()
函数提取了 50%的随机行。注意,您只能在n
和frac
参数之间指定一个。
有条件抽样
有条件抽样只允许提取满足给定条件的一些行。首先,必须指定条件。例如,作为一个条件,您可以只选择sepal width (cm)
列的值小于 3 的行:
condition = df['sepal width (cm)'] < 3
变量condition
是一个与df
大小相同的数列,包含True/False
,取决于行是否满足条件。
然后,检索与满足上述条件的行相关联的索引:
true_index = condition[condition == True].index
在当前示例中,57 行满足条件,因此您最多可以采样 57 行。
sample()
功能可用于执行采样,条件如下:
subset = df[condition].sample(n = 10)
以恒定速率采样
另一种采样策略是以恒定速率采样,这意味着您希望两个相邻样本之间的距离恒定。例如,您可能希望以 4 的速率进行采样,如下图所示:
作者图片
在这种情况下,首先指定速率:
rate = 10
然后,你可以简单地提取样本:
subset = df[::rate]
在iris
数据集中,样本数为 150,因此 10 的采样率将产生 15 行的子集:
subset.shape
它给出了以下输出:
(15, 4)
获取数据集的剩余部分
一旦提取了数据集的子集,还可以提取剩余部分。例如,如果您想在训练和测试集中分割数据集,可以使用这种策略,而不使用scikit-learn
库提供的train_test_split()
函数。
可以采用两种可能的解决方案来提取数据集的剩余部分。这两种解决方案产生了相同的结果。
第一种解决方案
第一种解决方案删除原始数据帧df
中提取的数据帧subset
的行,并将结果存储在新的数据帧中。这可以通过将待删除的索引列表传递给drop()
函数来实现:
remaining = df.drop(labels=subset.index)
第二种解决方案
第二种解决方案仅选择原始数据帧df
中的行,其中索引不在提取的数据帧subset
的索引列表中:
remaining = df[~df.index.isin(subset.index)]
摘要
在本教程中,我演示了如何以不同的方式执行数据帧采样:
- 随机抽样,抽取 N 个随机行;
- 带条件抽样,应用一个条件,然后随机抽取 N 行;
- 以恒定速率采样。
如果你想了解我的研究和其他活动的最新情况,你可以在 Twitter 、 Youtube 和 Github 上关注我。
相关文章
如何利用数据挽救网球中的破发点
任何网球比赛的关键之一是挽救破发点的能力。看一看 ATP 排行榜,你会想起这个事实,因为你会看到游戏中的顶级职业选手始终保持在破发点挽救百分比的最顶端。但是是什么让一个球员擅长挽救破发点呢?借助杰夫·萨克曼(Jeff Sackman)过去 10 年每个大满贯的逐点数据集,我们可以深入了解职业选手如何在网球的最大舞台上挽救破发点。
首先,我们开始导入相关库的:
import pandas as pd
import seaborn as sn
import matplotlib.pyplot as plt
import numpy as np
然后我们需要导入数据。从 2018 年开始,澳大利亚网球公开赛和法国网球公开赛的数据被不同地打包,所以我们现在放弃了这几场比赛。
years = [i for i in range(2011, 2021)]
slams = ['ausopen', 'frenchopen', 'usopen', 'wimbledon']
data = pd.DataFrame()for year in years:
for slam in slams:
if year >= 2018 and slam in ['ausopen', 'frenchopen']: #these slams did not have the same data collected
continue
try:
new_data = pd.read_csv('./tennis_slam_pointbypoint-master/' + str(year) + '-' + slam + '-points.csv')
if year == 2011 and slam == 'ausopen':
data = new_data
else:
data = pd.concat([new_data, data])
except FileNotFoundError:
print(year, slam)
continue
接下来,我删除了没有对打数据的行(在这种情况下,这些行没有发球速度读数),并收集了相关的特征,如 ace、净得分、非受迫性失误、获胜者等。我会推荐任何感兴趣的人参考 Github 文件,因为把所有内容都发布到文章中会让人不知所措。
终于我们可以开始分析了。在网球场上做出的第一个决定是是否先发球。如下所示,先发球似乎有很大的帮助,因为第一场比赛的破发点比第二场少得多(大约减少了 25%)。善于挽救破发点的最好方法是首先不要面对它们,首先发球可以最大限度地减少球员在整场比赛中面临的破发点。
#imported library's include (Seaborn, Matplotlib.pyplot, pandas)
#sets the x axis to be 12 games and then uses a pandas groupby statementa_plot = sn.lineplot(x = [i for i in range(0,12)], y = data.groupby('GamesPlayed').mean()['BreakPoint'][0:11])a_plot.set(ylim = (0, .19))
plt.title("Break Points Faced per Game")
plt.grid()
plt.xlabel("Games into Set")
第一场比赛显示,与其他比赛相比,面临的破发点减少了 25%
接下来我们要研究的是如何在破发点发球,毕竟这是网球场上你可以完全控制的事情。虽然我们在某种程度上受到限制,因为如果球员错过了第一次发球(即他们试图击球的力度),我们仍然可以检查发球速度如何影响破发点挽救百分比的一些关键因素。
我将发球速度从每小时 80-140 英里,每 5 英里一组。所有指标都与这个循环相关联,每个指标的名称都应该相当直观
for i in range(80, 145, 5): #bucketing by fives and then appending to lists corresponding to different metricsbucket_data = data[(data.ServeSpeed >= i) & (data.ServeSpeed <= i + 5)]
trimmed_data = bucket_data[bucket_data.BreakPoint]
x = bucket_data.groupby('BreakPoint').mean()#all data
y = trimmed_data.groupby('BreakPointWon').mean()#just bp data
save_percentage.append(1 - x.loc[True, 'BreakPointWon'])#doing the one because has two entries True and False
rally_length.append(y.loc[True, 'Rally'])
buckets.append(i)
winner_percentage_loss.append(y.loc[False, 'Winner'])
winner_percentage_save.append(y.loc[True, 'Winner'])
unforced_error_percentage_loss.append(y.loc[True, 'UnforcedError'])
unforced_error_percentage_save.append(y.loc[False, 'UnforcedError'])
net_point.append(x.loc[True, 'ServerNetPoint'])
net_point_won.append(x.loc[True, 'ServerNetPointWon'] / x.loc[True, 'ServerNetPoint'])
return_net_point.append(x.loc[True, 'NetPoint'] - x.loc[True, 'ServerNetPoint'])
return_net_point_won.append((x.loc[True, 'NetPointWon'] - x.loc[True, 'ServerNetPointWon']) / (x.loc[True, 'NetPoint'] - x.loc[True, 'ServerNetPoint']))
正如你所料,发球速度的提高与破发点挽救率相关。然而,一旦你达到 130,这种影响似乎变平了。最终发球速度可以公式化为一个决策方程。如果一名球员可以量化每增加一英里他们错过第一次发球的可能性有多大,以及他们在第二次发球时赢得分数的可能性有多大,他们就能够计算出他们应该发球的最佳速度。
import matplotlib.ticker as mtick #imports to enable percentage reading#graphs save percentage by serve speed
ax = sn.lineplot(x = buckets, y = [i * 100 for i in save_percentage])
ax.yaxis.set_major_formatter(mtick.PercentFormatter())#sets y axis as a percentplt.grid()
plt.title("Save Percentage by Serve Speed")plt.ylabel("Save Percentage")
plt.xlabel("Serve Speed")
130 英里/小时后,图表稳定增长,影响有限
接下来,我们考虑赢家和非受迫性失误。正如我们从下面看到的,当你的发球速度提高时,胜率稳步上升,而当你的对手击中胜方的几率保持相对稳定。作为服务器,这可能会导致重要的战术决策。例如,如果你正在打一个打了很多致胜球的大击球手,打一个较慢的发球可能没问题,因为发球速度似乎不会影响他们打致胜球的机会。然而,如果你不得不打致胜球来赢得你的大部分分数,你可以冒险在你的第一次和第二次发球时打得更用力一点,以增加打致胜球的可能性。
#uses two lines on one plot to visualize Winner percentage
ax1 = sn.lineplot(x = buckets, y = [i *100 for i in winner_percentage_loss], label = 'Server')
ax2 = sn.lineplot(x = buckets, y = [i * 100 for i in winner_percentage_save], label = 'Returner')
ax1.yaxis.set_major_formatter(mtick.PercentFormatter())
ax2.yaxis.set_major_formatter(mtick.PercentFormatter())plt.grid()
plt.title("Winner Percentage Server vs Returner by Serve Speed")plt.ylabel("Winner Percentage")
plt.xlabel("Serve Speed MPH")
对于返回者来说,赢家比例保持不变
我们还必须考虑发生非受迫性失误的可能性。正如下图所示,如果你的发球速度是 90 英里/小时,而不是 135 英里/小时,你的对手基本上不会犯非受迫性错误——140 英里/小时的百分比可能是由样本量低造成的。然而,随着你发球速度的提高,你的发球出现非受迫性失误的几率会稳步下降。这同样会导致重要的战术变化。如果你的对手是零星的,犯了很多非受迫性错误,你可能想打一个更软的第一次发球,因为发球速度不会影响他们犯错误的倾向。然而,如果作为一个发球者,你一直不稳定,通过更努力的发球来降低非受迫性失误的概率可能是正确的举动。
#uses two lines on one plot to visualize unforced errors
ax1 = sn.lineplot(x = buckets, y = [i * 100 for i in unforced_error_percentage_save], label = 'Server')
ax2 = sn.lineplot(x = buckets, y = [i * 100 for i in unforced_error_percentage_loss], label = 'Returner')
ax1.yaxis.set_major_formatter(mtick.PercentFormatter())
ax2.yaxis.set_major_formatter(mtick.PercentFormatter())plt.grid()
plt.title("Unforced Error Percentage Server vs Returner by Serve Speed")plt.ylabel("Unforced Error Percentage")
plt.xlabel("Serve Speed MPH")
服务器的非受迫性错误百分比随着速度的增加而降低
最后,我们发现当一个球员来到网前,他们有 68%的机会赢得这一分。上网是一个成功的策略,尤其是在破发点,当你迫使对手在高压情况下进行艰难的射门时。正如我们所看到的,随着你发球速度的增加,网前胜率保持不变,这两种情况都可能是由于样本量小,但你上网的能力显著增加——从 80 英里/小时的 10%增加到 120-130 英里/小时的 20%左右。
#graphs by percentage chance to get to the Net
ax = sn.lineplot(x = buckets, y = [i * 100 for i in net_point])ax.yaxis.set_major_formatter(mtick.PercentFormatter())plt.grid()
plt.title("Percentage Chance to Get to the Net")plt.ylabel("Server Net Point Percentage")
plt.xlabel("Serve Speed MPH") #graphs by win percentage at the net
ax = sn.lineplot(x = buckets, y = [i * 100 for i in net_point_won])
ax.yaxis.set_major_formatter(mtick.PercentFormatter())plt.grid()
plt.title("Win Percentage at the Net by Serve Speed")plt.ylabel("Server Net Point Win Percentage")
plt.xlabel("Serve Speed MPH")
同样,这些信息取决于你的对手和你的实力。如果网络机会是你战略的一个关键部分,你可能会通过增加你的 MPH 得到更好的服务。此外,增加你的 MPH 也会降低你的对手入网的概率,而他们的胜率保持不变,除非是 135 MPH 的异常值。
#Returner percent chance to get to the net
ax = sn.lineplot(x = buckets, y = [i * 100 for i in return_net_point])
ax.yaxis.set_major_formatter(mtick.PercentFormatter())plt.grid()
plt.title("Oppenent Percent Chance to get to Net by Serve Speed")plt.ylabel("Returner Net Point Win Percentage")
plt.xlabel("Serve Speed MPH") #Returner win percentage at the net
ax = sn.lineplot(x = buckets, y = [i * 100 for i in return_net_point_won])
ax.yaxis.set_major_formatter(mtick.PercentFormatter())plt.grid()
plt.title("Returner Win Percentage at the Net by Serve Speed")plt.ylabel("Returner Net Point Win Percentage")
plt.xlabel("Serve Speed MPH")
虽然比赛中做出的每个决定都完全取决于你和对手的比赛风格,但这些统计数据为在破发点上做出明智的选择提供了一些证据。显然,不是每个人都能够发球超过 130 英里/小时,但是发球速度显然是节省破发点的一个重要部分,可能是你下次练习时的一个重要考虑因素。杰夫·萨克曼的数据为网球和网球统计数据提供了宝贵的视角,我们只能希望 ATP 像 MLB 一样开放 IBM 和 InfoSys 的跟踪数据,以增加对这项运动的分析关注。
接下来,我希望做我上一篇文章做的事情(聚类),同时使用大满贯的逐点数据,看看是否可以找到任何额外的见解。
github:https://github . com/dcaustin 33/Medium-hosting/blob/main/Break % 20 point % 20 save % 20% 25 . py
如何在 Docker 中保存状态——数据科学家指南
使用数据科学容器时,从持久化数据和状态开始。Docker 中的数据存储和持久性指南。
弗雷迪·雅各布在 Unsplash 上拍摄的照片
介绍
在这篇文章中,我们将讨论 Docker 中的数据持久性,以及如何让您的图像与容器外部的数据进行交互。如果你熟悉 Docker 或者已经浏览过本系列的前几篇文章,请跳到下一个标题!
到目前为止,在这个系列中,我们已经介绍了如何获得一个基本的 Hello World!使用 Flask 和 Python 构建并使用 Docker 部署的 web 应用程序。在第 2 部分中,我们浏览了一个端到端的机器学习示例,为 Iris 数据集构建了一个随机森林分类器,并使用 Flask 将其作为一个预测 web 应用程序,然后将其共享到公共 Docker Hub 存储库。
如果你错过了他们:
现在你可能不想在任何情况下公开分享你的图片。幸运的是,有许多选项可供您构建和维护私有存储库。每个主要的云提供商 Azure、AWS 和 GCP 都有托管存储库选项,这些选项很容易启动和运行。对于那些热衷于使用 Azure 的人,我还写了一个快速指南,介绍如何使用 Docker 和 Azure CLI 运行私有存储库:
如果您已经启动并运行了,并且只是想开始使用数据持久性,那么让我们开始吧。
数据持久性
使用容器时有两个核心假设:
- 容器应该是一次性的。这意味着他们应该是无国籍的。容器提供了完成工作所需的计算能力、应用程序、配置和代码。然后,在退出时,当他们退出时,一切都被清除。
- 为了支持微服务方法,容器应该被设计成专家——尽可能专注于几件事情。这允许系统设计者组合多个简单的容器来创建复杂的行为,而不用担心相互依赖。
这种方法有很多好处。无状态支持可伸缩性、可移植性和弹性。如果您正在运行多个容器,并且其中一个出现故障,那么重启它或者启动一个替换容器是很简单的。
为了捕捉状态,它应该在图像之外。这使得微服务可以轻松地共享信息,而无需考虑每个容器的性能或准确性的复杂性,这在传统的应用程序设计中是一个挑战。
将数据管理从应用程序中分离出来可以导致更快的开发和更低的维护开销(阅读:为我们赢得一些时间,这是每个数据科学家都需要的!).
对于在外部何处保持该状态,有几个选项:
- 磁盘上——这可能是容器可以访问的 Docker 主机本地保存的另一个文件夹
- Docker 定义的卷—可以在多个容器之间共享的装载存储卷
- 数据库—有了足够的凭证和配置,每个容器都可以读写本地/网络/云数据库的状态
- 网络资源——容器可以从另一种类型的网络资源(如服务总线或消息队列)获取状态
在本文中,我们将介绍如何使用 Docker 卷来存储状态。
Docker volumes 允许您将容器的特定文件系统路径连接回主机。当容器中的一个目录被挂载时,该目录中的更改也会在主机上看到。然后,您可以跨容器重启挂载相同的目录,您将看到相同的文件。
我将使用来自 Docker Hub 的标准 Jupyter 笔记本图像来强调在 Docker 环境中移动数据和熟悉的应用程序是多么容易。您可以使用以下命令从 Docker Hub 中提取映像:
docker pull jupyter/datascience-notebook
请注意,这是一个相当大的映像(4.16 GB),如果空间或时间不足,请尝试使用最小的笔记本映像(1.49 GB),并相应地更改以下所有代码。您可以使用以下方法提取最小图像:
docker pull jupyter/minimal-notebook
创建 Docker 卷
首先,让我们从 Docker 卷开始——这是一种由 Docker 管理的资源,容器可以轻松访问它以将其用作存储。要在 Docker 中创建卷,请使用以下语法:
docker volume create <volume_name>
您可以使用如下所示的docker volume ls
来检查这是否成功。
在 Docker 中创建和检查卷(图片由作者提供)。
现在,如果我们想把一些本地文件放到一个容器中,我们可以用几种方法。首先,让我们运行一个容器,并在其中挂载一个目录。然后,我们可以将 Docker 卷装载到同一个容器中,从而允许我们跨容器复制内容。
在这里,我们将刚刚创建的demo-volume
挂载到容器内的一个目录中。我们将使用/home/jovyan/demo-dir
把它放在默认的 Jupyter 位置。语法如下:
docker run -v <volume_name>:<container_directory> <image_name>
因为我们在这里使用 Jupyter,我们还需要记住映射端口。因此,该命令如下所示:
docker run -p 8888:8888 -v demo-volume:/home/jovyan/demo-dir jupyter/datascience-notebook
运行 Jupyter 笔记本容器的命令,包括端口映射和 Docker 卷安装(图片由作者提供)。
然后,您只需要转到带有令牌的链接,并且(输出中的第二个链接对我来说更加一致):
http://127.0.0.1:8888/?token=a7a30a23821b5764d7f754838af13099b6b134c8687c415f
你应该被认证和重定向,然后你会看到熟悉的 Jupyter 前屏幕和我们的demo-dir
安装在我们能看到的地方:
我们的 Docker 卷挂载到容器中的/demo-dir(图片由作者提供)。
这允许我们在卷中存储内容,并像对待任何挂载的文件夹一样对待它。当使用容器和服务时,这实际上是在 Docker 中持久化数据的首选方式。一些更强的卷使用情形包括:
- 如果您想在多个运行的容器之间共享数据,卷是最好的选择。当已装载的容器停止、崩溃或被删除时,卷仍然存在。这意味着其他容器仍然可以连接到它,并与其中的数据进行交互。
- 您还可以使用它将包含服务不同部分(例如机器学习服务和数据库)的不同图像连接到相同的数据。
- 当主机可能没有所需的目录结构时,卷是很好的选择。使用卷允许您通过显式装载主机来放松对主机的这一要求。
- 卷使跨主机备份和迁移数据变得更加容易。
- 如果您需要本地文件系统行为,Docker volumes 可以帮助您解决这个问题。
结论
在这篇文章中,我们已经看到了在使用 Docker 时持久化数据的一些选项。我们还介绍了一种最方便的方法,用于在容器之外保存数据,以及在同一主机上运行的容器之间保存数据。
有希望的是,可以看到以这种方式组合 Docker 容器——作为原子的、自包含的单元——允许数据科学家开始构建强大的系统,这些系统保持可伸缩性、灵活性和难以置信的模块化。
进一步阅读
Docker 中的数据持久性还有很多。由 Lorenz Vanthillo 撰写的这篇文章介绍了如何将 Docker 容器连接到 PostgreSQL 数据库:
在这里, Thiago S. Adriano 写了一篇关于连接 SQL Server 的精彩指南:
https://medium.com/dockerbr/accessing-sql-server-on-docker-container-86b84efcaa1c
再次感谢您的阅读,如果您对进一步的内容有任何反馈或要求,请务必告诉我。
如何对几款车型进行规模化训练
基于 python 和 celery 的简单解决方案
动机
让我们考虑以下任务:预测几个城市对某种东西的需求。这是一个时间序列预测问题。预测在本地模型和全球模型之间存在差异。对于本地模型,您为每个城市训练一个模型。使用全局模型,您可以为所有城市训练一个模型。总而言之:训练多个相似的模型有时是必要的。
简单的解决方案是依次训练模型:一个接一个的模型。想象你需要 10 分钟训练一个城市的模型,你有 100 个城市。你需要 1 000 分钟来训练所有的模型(16 小时)。这是生产力杀手。由于适应模型是部署的一部分,这将违反持续集成和持续开发的原则。
为了在相同的持续时间内完成训练,与模型的数量无关,解决方案是在 n 台计算机(也称为工人)之间分配训练。本文将描述一个 python 解决方案。解决方案基于著名的图书馆芹菜。github 上有一个原型。
解决方案概述
这个问题通常用任务队列管理器来解决。在 python 中,最著名的任务队列管理器是 celery。我们将拥有:
- 1 个主机,创建任务
- n 个工人,每个人都符合一个模型
作者图片
对于培训模型,有两个更重要的要求:
- 指标:我们想知道所有模型的平均指标。
- 成本:一旦培训结束,工人必须停工。
作者图片
履行
该实现可在 g ithub 上获得。
第一步——第一根芹菜用法&原型骨架
先决条件:安装芹菜包;安装并启动 redis 并了解芹菜的第一步。
首先,定义一个名为“fit”的芹菜任务。它将由工人来执行。它是为适应模型而执行的代码,它返回精度。
然后,主人将计算委托给工人。添加此代码,例如在 bin/master.py 中。
最后,执行原型,如下所示:
# start a worker
celery -A tasks worker# start the leader
python bin/master.py
注意:在 github 上,已经创建了一个文件(bin/worker.sh)用于启动一个 worker。
步骤 2——等待任务处理
主设备可能必须等待任务被处理。这很可能发生在部署管道中。这是以下代码的责任:
步骤 3——获得每次训练的平均准确度
为了更好地了解和跟踪精确度,主服务器可以打印每个城市的平均精确度。下面的代码演示了主服务器如何读取其工作线程的结果。
第四步——停止工人
在 src/tasks.py 中定义一个任务“shutdown ”,一个 worker 的单次执行将关闭所有 worker。但是工人将首先完成他们当前任务的执行
主机将任务“关机”委托给工作机(在 bin/master.py 中):
第五步——运行一切!
在一个选项卡中启动 3 个流程(1 个主流程和 2 个工作流程):
作者图片
其他可能性
- RabbitMQ :以及所有其他实现 AMQP 协议的消息队列。
- 不是卡夫卡:用卡夫卡解决这个问题很难,也不是最优的。卡夫卡不是解决这个问题的好工具,因为它的设计。它不允许您将几个用户连接到同一个分区。
结论
这篇文章表明,芹菜是一个简单的解决方案,分布培训的几个模型。Celery 是最著名的用于任务处理的 python 库。你用芹菜赢得的知识和技能可以转移到其他问题上。开箱即用,Celery 自带了很棒的特性,比如“关闭所有工作器”或者“从工作器返回一个值给主控器”。尽管如此,其他任务队列管理器可以解决同样的问题。您可以直接使用数据库(例如 Redis)或 AMQP 消息代理(例如 RabbitMQ)来开发解决方案。即使这看起来像是编写自己的任务队列管理器,但它可能是一个更具成本效益的解决方案。
如果你对更多细节感兴趣,请留言告诉我!
如何通过采用 SQL(有时抛弃 Git)来扩展您的分析组织
为什么分析工作应该优先考虑可发现性和可再现性,而不是版本控制和代码审查。
[图片来自 Freepik]
流程对于组织的扩展至关重要,而我们对分析流程的理解是错误的。
扩展组织的一个关键方面是过程。过程允许您规范化和合并最佳实践,以确保事情顺利和可伸缩地工作,即使没有人注意控制。但是,分析组织中的流程是经常被忽略的东西,我们经常默认工程遵守的相同流程:即使用 git 和相关模式来共享和存储分析工作。
对于数据科学/分析的某些部分,这样的工程行为转移是合适的:分析基础设施、分析工程、机器学习模型的部署、库-所有这些工作流都是固有的基于代码的,并受益于工程组织中熟悉的严格的测试+ PR 文化。但是对于剩余的分析工作——在 SQL IDEs 和 Jupyter 笔记本中每天都会发生的那种——拟合度很差。作为分析师和数据科学家,我们 90%的工作都是探索性的。不幸的是,在这里,工程实践不仅达不到要求,而且可能对组织有害。为什么?
盲目地将版本控制和代码审查作为共享探索性工作的看门人,会导致非共享的探索性工作。
所以我认为我们需要一个不同的过程。为了理解需要什么样的流程,我们首先需要确立分析组织的目标。在工程中,可维护性、可靠性和可伸缩性是支撑诸如版本控制、代码审查、代码覆盖、验证测试等实践的目标。但是在分析工作中,潜在的目标必然是不同的:可靠性、可维护性和可伸缩性仍然是重要的,但是它们表现得不同。让我们扔掉皇帝的衣服,用我们真正想要的东西来代替这些概念:可发现性和再现性。换句话说,我们需要将“科学”放回数据科学(和分析)中。
记住这些概念后,我将在本文中讨论以下内容:
- 为什么可发现性和再现性在分析和数据科学组织中至关重要。
- 如何将过程导向这些目标。
第一个支柱:可发现性
为什么我们需要可发现性:代码天生就是可发现的。分析工作不是。
这是一个过于简化的工程代码库,其中箭头表示导入。
图片作者。
任何在现代 IDE 中呆过一段时间的人都知道遍历这个图很容易。每一个现代的 IDE 都有“跳转到”功能,您可以立即跳转到对象引用。从def pet()
开始,你可以很容易地跳到class Llama
的定义,然后沿着面包屑轨迹一路回到父类Animal
。
另一方面,您的分析代码库看起来有些不同:
图片作者。
仍然存在互连性(通过数据本身),但是这些连接不能通过您的 IDE 发现(因此线是虚线)。这使得很难像工程师看到函数引用那样看到表引用。那么解决办法是什么呢?
如何获得可发现性:使用一个低摩擦、网络化的系统来做(或至少文档化)查询工作。
不过,在深入探讨之前,让我们先来谈谈采用工程最佳实践的分析组织的首选解决方案: git 。许多组织求助于 git 来跟踪任何一种推动洞察力的查询。这是合理的,并且允许通过比如说表名来搜索数据,但是根据我的经验,git 有一些不便之处:
- 验证繁琐/盲目。对于 SQL,您必须复制、粘贴并重新执行查询来验证 repo 中的工作。这是一个小小的不便,但是因为 git 中通常不跟踪结果,所以如果自己不重新运行查询,就很难跟踪查询之间的逻辑流。对于基于代码的笔记本电脑来说,这甚至会变得更糟—您需要在获取必要数据的基础上复制虚拟环境。不像在工程中,数据工作的输出几乎和代码本身一样重要,所以如果你只是看代码,你就错过了故事的一半。Git 不适合追踪逻辑的审查过程。
- 工作往往缺乏足够的背景。特别是,对于原始 SQL,git 提供的上下文很少,查询只能在编写它们的上下文中使用。当然,您可以在查询中添加一个自述文件,但是您编写的所有假设和用来验证这些假设的片段呢?你把那些放进去了吗?如果是这样的话,你是否愿意对它们进行版本控制?
- Git 中的工作是可搜索的,但不是合格的。筛选过去的工作是粗糙的,因为 git 没有提供查询或分析的健壮性或可重用性的指示——只是说它以前被写过。
- 在理想的世界中,代码评审是很棒的,但是评审跟不上分析的步伐。代码仍然应该被检查,但是商业决策的速度经常需要在事后和/或逐案的基础上进行检查,而不是强制性的同行评审。一旦发现,见解的有用性通常会呈指数衰减,因此用正式的评审周期来阻碍业务不是一个好主意。当然,你可以在没有代码评审的情况下使用 git,但是总是有潜在的压力要退回到正式的评审过程中——养成在没有评审的情况下合并代码的习惯也可能是个坏主意。😉
我从以上几点得出有争议的结论:
Git 不适合分享大多数分析工作。
最终,这些不便仅仅是不便,但是,也许令人惊讶的是,这些不便经常足以导致人们不分享他们的工作。我在 Airbnb、Wayfair 和其他一些试图实施同样措施的公司亲眼目睹了这种情况。虽然一些非常精美的作品会被分享(例如通过 Airbnb 的知识回购),但这只占完成作品的 1%。剩余的工作存在于 SQL IDEs 的选项卡、本地文件、本地 Jupyter 笔记本中,因此这些工作会被不同的分析师和数据科学家一遍又一遍地重复。
解决办法?不惜一切代价让作品被发现**。一个合理的方法是在一个非 git 支持的地方分享你的工作。我们已经为这类事情专门构建了 hyperquery.ai ,但是我已经看到使用更通用的笔记解决方案取得了相当大的成功,比如使用 concept、Confluence 或 Library(h/tBrittany Bennett),如果你不介意将 IDE 和查询共享环境分开的话。对于 Jupyter/R markdown 来说,不幸的是,git 可能仍然是所有罪恶中最小的。但这让我想到了我的第二个目标:再现性。**
第二个支柱:再现性
再现性很重要,因为分析是科学。
分析/数据科学就是科学。因此,它需要是可复制的。你产生的任何洞见都是两件事:洞见本身和你为获得洞见所采取的步骤。
可复制性尤其重要,因为我已经告诉过你不要在托管的 git 平台中进行同行评审。虽然避免强制性的同行评审流程阻碍洞察力的使用很重要,但让同事检查关键的、决定决策的工作片段仍然非常重要,优先考虑可重复性可以实现这一点。此外,与代码评审相比,可再现性更好地促进了对结果的仔细验证,正如我所提到的,代码评审在数据输出方面通常是不透明的。
使用 SQL 最容易获得再现性,而不是 Python/R。
为什么?因为 SQL 减少了复制工作的摩擦。与可发现性一样,减少摩擦也很重要,否则没有人会经历如实再现您的努力和验证您的结论的痛苦。正如他们所说,如果你有一个 5 行 git commit,你将得到 50 个请求的修改。如果你有一个 500 行的提交,你会得到一个 LGTM。要求用户建立虚拟环境,并与基于代码的笔记本中充满的隐藏状态进行斗争,这意味着其他人根本不会试图复制你的工作。没有什么比因为你没有仔细检查你的工作而把生意推向错误的方向更糟糕的了。
所以现在我们来看我这篇文章的第二个有争议的陈述:
尽可能使用 SQL,而不是 Python/R。
Python/R 爱好者:在你关闭浏览器并永远屏蔽我之前,请听我说完。我喜欢 Python 和 R,并且是 IPython 和 Jupyter 的最早用户之一(我整个研究生院都在用 Python 研究 rivers)。我甚至发布了几个 Python 开源库。
也就是说,你必须承认:Jupyter 笔记本和 R Markdown 并不是存储可复制作品的最佳地方。隐藏状态、缺乏易执行性、需求文件和缓存数据提供了许多失败点。在一天结束时,运动中的数据打破,并且 SQL 的使用最小化了运动。
当然,假设你不买这个——你是制造完美可复制笔记本的大师。这是完全可能的,如果你有一个基于 odbc 的库,它直接从你的仓库中提取数据,如果你确保在共享你的代码之前总是从头开始重新执行你的所有单元格,如果你确保你有一个好的系统来共享这些笔记本而不意外地存储大量的数据(哎呀,你执行了df
并以明文列出了几百兆字节的数据)。但即便如此,如果你把这些事情都做对了,Jupyter 笔记本还是有一定程度的不透明性(尤其是利益相关者无法访问),这将不可避免地降低对你分析的信任度。另一方面,SQL 片段是自包含的,开箱即用。如果利益相关者有疑问,他们可以(并且愿意)执行 SQL,只要您的组织有合理的数据卫生,可复制性是免费的。
要实施的流程:(1)共享知识和(2) SQL(如果可能)
在本文的这一点上,您可能已经很清楚了,但是在我看来,只有两个必要的过程,当实现时,将推动可发现性和可再现性向前飞跃。我会把它们放在一个多余的表格里,帮助你消化它们:
希望这些建议不会像我之前的观点让你期待的那样激烈:
- 对于可发现性,我甚至不是说要放弃任何基于 git 的版本控制分析,只是建立一些可以共享和更容易发现查询和特别工作的东西。如果你被要求做某件事,并且你的组织足够大,保证有人以前做过。
- 为了再现性,敦促分析师和 DS 尽可能使用 SQL,而不是 python、R 或 excel。当你在研究一个模糊的研究问题时,一头扎进熊猫或潮汐中显然是好的,但要把它作为例外而不是常规。当您的 DS 在 pandas 中运行基本的聚合时,他们天生会牺牲可见性和可重复性,以换取他们认为这样做的好处(在我的例子中,通常只是熟悉度)。**
结束语
我建议的改变很小。在你的 analytics onboarding 文档中明确说明“SQL(尽可能)”,并建立一个支持知识共享的 SQL 编写环境(参见 hyperquery.ai )。但是除此之外,没有什么需要改变的了。
试一试,让我知道这些调整对你有什么效果(或者你是否通过其他方式成功地推动了可发现性和可再现性)。这些微小的变化可能意味着沮丧、超负荷工作的数据团队与通过可重用、自由共享的 SQL 来简化和减轻这种工作负载的团队之间的差异。
推文@ imrobertyi/*@*hyperquery来问好。👋
关注我们LinkedIn。🙂* 要了解更多关于 Hyperquery 的信息(并注册我们的私人测试版),请访问Hyperquery . ai。 (如果你对帮助建立感兴趣,我们正在招聘——查看我们的 空缺职位 )。)***
如何在 Google Cloud 上安排 Python 脚本
如何使用 Google Cloud Scheduler 和 Cloud Function 安排一个递归 Python 脚本
没有在您的服务器中设置气流?没有任何设置 cron 作业来调度脚本的经验?别担心。今天我们将学习如何用最少的 cron 作业知识在 Google 云平台中调度您的 Python 脚本。
Cloud Scheduler 是一个受管理的 Google 云平台 (GCP)产品,它允许您指定一个频率来计划一个重复的作业。简而言之,它是一个轻量级的托管任务调度程序。
为什么我们需要一个调度程序?调度程序允许您在任何时间运行工作流。它在失败的情况下重试,甚至让你在凌晨 3 点运行一些东西,这样你就不需要在半夜醒来。
入门指南
创建云调度程序
让我们从创建云调度程序开始。去谷歌云平台找云调度器或者直接去这个链接。
注意:您必须设置您的计费帐户才能使用云调度程序。你将每月 3 个免费工作,每个计费帐户。更多详情,可以参考云调度器定价。
让我们从“安排工作”开始。
在本例中,我将安排一个 Python 脚本来发送电子邮件,因此作业的名称将是schedule-email
。
计划频率采用 unix-cron 格式。如果不熟悉 unx-cron,可以参考 crontab guru 网站。我总是参考这个网站,以确保我设置了正确的时间表。
例如,30 8 * * *
表示该作业将在每天的 08:30 运行。
0 9 6 * *
表示该作业将在每月 6 日 09:00 运行。
接下来,我们必须配置作业的目标。在本例中,我们将使用发布/订阅作为目标类型。新主题schedule-email
为该作业创建了一个新主题。
这里的高级设置是可选的。但是为了安全起见,让我们设置一个重试尝试。之后,让我们单击“创建”按钮来创建云调度程序。
创建云函数
接下来我们去云函数创建一个云函数。
让我们用名称和地区来配置我们的云函数。触发器类型将是发布/订阅,主题将是我们刚刚创建的主题schedule-email
。
接下来,我们必须用main.py
和requirement.txt
设置脚本。
在本例中,我们将发送一封带有此调度程序的简单电子邮件。因此,这个脚本不需要额外的包/库。我将保持requirement.txt
不变。
如果你有兴趣安排一封带附件的邮件,请参考我的另一篇文章。
入口点将是您定义在脚本中运行的函数,在本例中是schedule_email
。
您还可以选择配置分配的内存、超时和运行时环境变量。如果你运行一个很长的脚本,请记住分配更多的时间和内存。
耶!邮件成功安排在晚上 10 点。
现在你已经学会了如何使用 Google Cloud Scheduler 来调度 Python 脚本。您可以将该调度器用于其他任务,如临时批处理作业、大数据处理作业或基础设施自动化工具。好的一面是云调度程序为您处理所有繁重的工作!
可以在我的 Github 要诀 中查看main.py
。干杯!
如果你喜欢读这篇文章,你可能也会喜欢这些:
你可以在 Medium 上找到我其他作品的链接,关注我 这里 。感谢阅读!
如何安排无服务器谷歌云功能定期运行
你有一些需要定期运行的代码吗?请继续阅读,了解如何在谷歌云平台(GCP)上使用无服务器功能来实现这一点。
在 Unsplash 上由 Boitumelo Phetla 拍摄的照片
作为一名数据科学家/工程师,我经常有需要定期运行的代码。这可能是每天下午 02:00 处理一些日志文件,或者每天凌晨 01:00 运行机器学习模型。
如果它可以在内存限制 8 GiB 内运行,并且不到 9 分钟,那么它可能值得作为一个无服务器功能来实现。
如果您对此感兴趣,那么在本文中,我将向您展示如何使用无服务器架构,利用谷歌云平台(GCP)的云功能无服务器计算产品来调度您的代码。
要了解更多关于谷歌云功能及其好处的信息,请查看我在 Medium 上的另一篇文章。第一节简明扼要地解释了它。😄
⚠️⚠️家政公司
本文假设您已经拥有一个 GCP 账户。如果你没有,在这里注册https://cloud.google.com/可以获得一些免费积分。
如果你想从你的本地机器上与你的 GCP 账户进行交互,使用 这里 列出的步骤安装 Google Cloud SDK。
确保在您的 GCP 项目中使用 API 控制台 为 Google 云存储、函数、发布/订阅和调度程序启用 API。**
本文中的所有代码都是用 Python 3.8 开发的。所有必要的代码都可以通过 GitHub Gist 获得。
任务和解决方案架构示例
那么,我们在建造什么?为了本文的目的,我们将安排一个每 15 分钟打印一次钞票的云函数。当然不是字面上的钱😄,而是钱这个字。
云解决方案架构。(来源:作者)
我们将使用谷歌云平台(GCP)的 4 项服务来完成这项工作。
- 云功能 :哦,是的,这个无服务器计算服务将托管并执行我们所有的代码。该服务将每隔 15 分钟触发一次。当它执行时,它将运行我们的代码,将单词“money”写入一个文本文件,并将其保存在 Google 云存储桶中。这是一项简单的任务,可以轻松适应您的使用情形。
- *Cloud Pub/Sub**😗这是一个事件驱动的实时消息服务,允许我们创建异步通信的系统。它使得系统设计中存在事件生产者和消费者,也称为发布者和订阅者。在我们的例子中,云调度器将产生一个 Pub/Sub 事件,该事件将触发我们的云功能消费者,该消费者正在从 Pub/Sub 服务中监听特定的主题。
- 云调度器 : 这是一款来自 GCP 的全托管企业级 cron 作业调度器。它基本上可以安排任何事情。在这种情况下,我们使用它每隔 15 分钟为一个主题生成一个发布/订阅事件。
- 云存储 : 嗯……这里真的不多说了。它基本上是一个保存任何类型数据的位置。它可能没有其他的性感,但在我看来,它是 GCP 上一切的可靠支柱!
现在…我们开始建造!🚀 🚧
步骤 0:创建一个 Google 云存储桶
在 GCP 控制台上搜索存储。(来源:作者)
在 GCP 控制台中,搜索存储以找到云存储,然后单击创建存储桶。给存储桶取一个合适的名称,并确保在计划运行云功能的同一区域创建存储桶。您可以保留其余设置,然后点击创建。
创建云存储桶来存储文本文件。(来源:作者)
第一步:创建并配置云功能
现在在 GCP 控制台上搜索云函数,点击创建函数。给云函数取一个合适的名字,并确保它与存储桶在同一个区域。
选择功能触发类型为云发布/订阅。
创建由发布/订阅触发的云函数。(来源:作者)
点击创建一个主题来创建一个新的发布/订阅主题,这将触发这个云功能。给它起一个合适的名字,点击创建主题,然后点击保存来完成发布/订阅触发器。
创建新的发布/订阅主题。(来源:作者)
现在,在运行时、构建和连接设置部分下,保持运行时选项卡设置不变。我们的功能非常简单。因此,256 MiB 内存的执行环境已经足够了。
但是,在连接选项卡中,选择仅允许内部流量。出于安全原因,这样做是因为它只允许来自项目环境内部的流量,而不会被恶意的外部请求触发。
完成后,点击下一个的对功能进行编码。
步骤 2:编码和部署云功能
现在,您应该会看到内联源代码编辑器窗口。这是我们定义运行时环境和编写要执行的函数的地方。
选择运行时环境为 Python 3.8,因为我们将用 Python 编码。
云函数的内联编辑器。(来源:作者)
如您所见,源代码内联编辑器选项卡下显示了两个文件。让我们了解它们是什么。
文件:main.py
这个文件是所有函数代码驻留的地方,当触发事件发生时被执行。因为我们已经选择了 Pub/Sub 作为触发器,所以在这个文件中应该有一个签名为 hello_pubsub(event,context) 的函数,默认情况下会填充这个函数。
这是由发布/订阅事件触发的主函数的签名。显然,您可以更改主函数名,但请确保在入口点函数名选项卡中相应地更新它。这就是环境如何知道调用哪个函数来处理发布/订阅事件。
出于本文的目的,我们将保持名称不变,只更新内容。
文件:requirements.txt
在这里,我们声明需要安装在云函数环境中的库,以执行我们的函数。默认情况下,环境预装了一堆库。因为这是一个简单的函数,所以我们不需要安装太多额外的库。
编码和部署
你可以从下面的要点中复制并粘贴这两个文件的内容。代码是不言自明的,并被注释。如果您有任何问题,请联系我们。😃
本文所需的云函数代码。(来源:作者)
一旦将代码从 gist 复制到相关文件中,就可以点击 deploy 。
这将需要一些时间,因为正在设置云功能环境,并且安装了所有要求。您应该会在函数名旁边看到一个加载圆圈。
成功部署函数时,函数名称旁边会出现一个绿色对勾。👊
云功能部署成功。(来源:作者)
好吧,那么…让我们看看它是否有效!😅为此,点击动作下的 3 点按钮,并点击测试功能。
步骤 3:测试云功能
测试部署的云功能。(来源:作者)
由于我们的云函数不需要任何输入数据或上下文,我们只需单击蓝色的测试函数按钮,让触发事件输入保持空白。
当函数成功完成时,它应该在下面的日志中显示 OK。如果出现错误,请阅读日志进行诊断。
我们还应该看到云存储桶里有一个新的文本文件,里面有钱!💲 💲 😄
云存储桶,带有由云功能生成的文本文件。(来源:作者)
现在剩下的就是安排函数定期运行。
步骤 4:计划云功能
回到 GCP 控制台,搜索云调度程序,并点击创建作业。这应该会将您带到一个设置页面来配置 cron 作业。
设置云调度程序作业。(来源:作者)
在下定义作业部分,给出一个合适的名称,并使用 unix-cron 格式,指定调度频率。
*因为我们想安排我们的函数每 15 分钟运行一次,所以使用下面的: **/15 * * * *** 。您可以从这里了解更多关于格式的信息,并根据您的需求进行调整。
哦,还要确保你在正确的时区。😅
为我们的发布/订阅主题设置 cron 作业的目标。(来源:作者)
在配置作业目标部分下,选择发布/订阅主题作为目标。这将显示当前项目中的所有主题。确保您选择了正确的发布/订阅主题,如步骤 1 中所创建的。
必须提供消息正文。我们只是说“你好”,尽管在我们的体系结构中它不会被处理。
创建后的云调度程序作业列表。(来源:作者)
现在点击 create 来安排这个任务。现在应该每 15 分钟运行一次我们的云功能。
但是,我们可以点击调度程序作业页面中的立即运行,立即运行该功能。
您应该能够在云存储桶中看到一个新的货币文本文件,它是在您点击 run now 时创建的。如果出现错误,请检查日志进行诊断。
现在,在你最后一次运行后的每 15 分钟,你应该能够看到新的货币文本文件被添加到云存储桶中。
预定的云功能每 15 分钟产生一次钱。(来源:作者)
恭喜你,你现在已经成功地安排了一个定期执行的无服务器功能。😄 🚀
**⚠️注意:确保删除/暂停云调度程序作业和其他资源,以避免产生持续成本。🔥
最后的想法
通常,人们(包括我)会使用虚拟机和其他工具来运行他们的 cron 作业。但是,重新审视这些任务,看看它们的内存/计算约束是否符合云功能的限制,可能是值得的。
从数据科学系统的角度来看,我可以看到如此多的任务可以采用这种架构。定期处理一些 CSV 数据文件、运行模型预测、每天生成摘要报告表、每天将外部文件加载到大型查询表中,等等。
*此外,拥有像这样的无服务器架构可以实现很大的灵活性并提高可维护性。嗯…这就是微服务架构的全部。 ****条款和条件适用。*😄 ******
希望这篇文章能为您的下一个解决方案架构提供一些思路。
感谢您的阅读。
**希望这篇文章对你有用。如果你有任何问题或者你认为我能帮忙,请联系我。总是期待与新朋友建立联系。😄
你可能也会喜欢我的这些文章:
*https://python.plainenglish.io/the-only-data-science-machine-learning-book-i-recommend-4fc23b947dfe https://medium.com/codex/3-simple-side-hustles-to-make-extra-income-per-month-as-a-data-scientist-7470030fbf43 *
如何在 Python 中安排航班
使用 CVXPY 分配足够的飞机,同时最小化成本
动机
想象你是一家航空公司的所有者。您的航空公司需要分配其在纽约的飞机来覆盖所有即将到来的定期航班。
共有 10 个航班和 8 个航班序列。每个航班序列由多个航班组成。
例如,一系列航班可以包括从纽约到布法罗、从布法罗到芝加哥以及从芝加哥到纽约的航班。
数据来源于应用整数规划,由陈博士,巴特森,r . g .&党,Y. (2010)
作者图片
每个飞行序列都有一定的成本。我们需要选择一个飞行序列的子集,以便每个航班至少有一架飞机。
有许多可能的方法来选择航班序列的组合,以便每个航班都有可用的飞机。一种可能的组合如下:
作者图片
另一种可能的组合如下:
作者图片
还有很多。
您的航空公司应该选择哪种序列组合才能满足约束条件并使成本最小化?
很多主要航空公司都面临这个问题。美国航空公司估计,他们基于数学编程的系统每年节省约 2000 万美元。
在本文中,您将学习如何使用整数编程和 Python 来解决这个问题。
什么是整数规划?
整数规划涉及带有目标和约束的问题。部分或全部变量被限制为整数。
CVXPY 是一个 Python 工具,为许多整数编程求解器提供了接口。要安装 CXVPY,请键入:
pip install cvxpy
让我们确定这个问题中的输入、目标和约束。
检索数据
从从 Google Drive 下载数据开始:
数据来源于应用整数规划,由陈,陈德生,巴特森,r . g .&党,Y. (2010)
获取每个飞行序列的时间表、成本和小时数。
把所有正数变成 1:
输入参数
定义输入参数:
作者图片
决策变量
使用cp.Variable
定义决策变量:
作者图片
限制
每个航班必须至少有一架飞机。例如,为了确保至少有一架飞机从纽约飞往布法罗,必须选择航班顺序 1、4 或 7。
为了确保从纽约→辛辛那提的航班至少有一架飞机,必须选择航班顺序 2 或 5。
因此,对于从纽约→布法罗的航班,我们有约束条件:
作者图片
将这一约束推广到所有 10 次飞行,我们得到:
作者图片
目标
我们想选择使总成本最小化的航班顺序。
作者图片
解决问题
现在我们有了约束和目标,让我们来解决问题吧!
13.0
目标的值是 13。让我们看看 y 的值是多少。
[1\. 1\. 1\. 0\. 0\. 0\. 0\. 0.]
酷!因为 y 数组的前 3 个值是 1,所以我们的飞机通过选择航班 1、2 和 3 的顺序来节省最多的钱。
这意味着飞机应该飞行:
- 从纽约→布法罗→芝加哥→纽约出发
- 从纽约→辛辛那提→匹兹堡→纽约
- 从纽约→芝加哥→辛辛那提→纽约
作者图片
酷!如果我们的飞机希望飞行总小时数不超过 1700 怎么办?
让我们将这个约束添加到我们的问题中,看看会发生什么。
对总时数的约束
首先,我们需要获得代表每个飞行序列的小时数的数组:
作者图片
将第一个约束与新约束合并:
作者图片
解决问题:
20
带有附加约束的目标值为 20,大于 13。为什么目标的值会改变?
这是因为选择了不同的飞行顺序。
作者图片
但是为什么第二个和第三个航班序列没有被选中呢?如果我们仔细观察代表小时的数组,我们可以看到前 3 次飞行的总小时数超过 1700。
作者图片
作者图片
但是,第一、第五和第六个航班序列的总小时数小于 1700,这满足约束条件。
作者图片
这是新选择的飞行序列的可视化表示。
作者图片
结论
恭喜你!您刚刚学习了如何选择航班顺序,同时将成本降至最低。我希望这篇文章能够激励您使用整数编程和 Python 解决类似的问题。
在 Github repo 中,您可以随意使用本文的代码:
我喜欢写一些基本的数据科学概念,并尝试不同的算法和数据科学工具。你可以通过 LinkedIn 和 Twitter 与我联系。
如果你想查看我写的所有文章的代码,请点击这里。在 Medium 上关注我,了解我的最新数据科学文章,例如:
参考
陈(2010)。应用整数规划:建模与求解。j .威利&的儿子们。
如何使用 Cron 调度 Python 脚本——您需要的唯一指南
自动化您的 Python 脚本执行—在 Linux 和 macOS 上运行
照片由乔尔&贾斯敏·福斯特伯德在 Unsplash 拍摄
当涉及到重复性的任务时,你更擅长自动化它们。这篇文章将教你如何做。
阅读完本文后,您将知道如何自动执行两个 Python 脚本来获取、解析和保存来自 web 的数据。我们开始吧!
这篇文章的结构如下:
- Cron 是什么?
- 写剧本
- 编辑 Crontab 文件
- 测试
- MacOS Gotchas
- 结论
Cron 是什么?
可以把 Cron 看作是在 Linux 和 macOS 环境中调度任务的最简单的方法之一。“Cron”一词来源于希腊语“Chronos”(时间),“Crontab”一词代表“Cron table”或时间表。你很快就会知道这张表指的是什么。
任何时候你都应该使用 Cron 来实现自动化,比如操作系统作业或者 Python 脚本。不用说,自动化的 Python 脚本基本上可以做任何事情。
在 Linux 和 macOS 上,Crontab 由六个字段组成。前五个字段保留用于计划执行的日期和时间(分钟、一月中的某天、一年中的某月、一周中的某天),最后一个字段保留用于要执行的命令。
您可以在日期-时间字段中自由使用星号、值和范围,但稍后会详细介绍。
您现在知道 Cron 和 Crontab 是什么了,但是在进行调度之前,我们仍然需要一些 Python 脚本。接下来我们来介绍一下。
写剧本
今天我们将安排两个简单的脚本。他们的工作是从以下虚假网站获取数据:
这些网站包含各种主题的虚拟数据,但我们将只关注用户和帖子。我们处理两个站点的唯一原因是展示如何在不同的时间间隔调度 cron 作业。
这两个脚本都将通过requests.get()
API 调用获取数据,解析 JSON 数据,处理它(只保留某些属性),将其转换成 Pandas 数据帧,并保存到 CSV 文件中。听起来很多,但是只有几行代码。
让我们从get_users.py
文件开始:
get_posts.py
文件将或多或少地相同——除了处理部分:
最后,确保在你的脚本所在的地方创建output
。这就是我们所需要的。如果您要运行脚本,CSV 将以<name>_<timeastamp>.csv
名称结构保存在output
目录中。时间戳是为了确保文件不会被覆盖。
接下来看看怎么安排任务。
编辑 Crontab 文件
无论您使用的是 Linux 还是 macOS,这一部分都是一样的。macOS 有一些权限问题,但我们稍后会谈到。
如前所述,您必须遵循特定的语法来调度 cron 作业。好消息是,你可以使用 Crontab.guru 网站来制定你的日程安排。
我们希望get_users.py
每隔偶数分钟运行一次(例如,0,2,4),而get_posts.py
每隔奇数分钟运行一次(例如,1,3,5)。以下是每隔一分钟运行一个作业的正确模式:
图 1 —每隔一分钟执行一次的 Cron 作业模式(图片由作者提供)
以下是奇数分钟:
图 2 —每隔几分钟执行一次的 Cron 作业模式(图片由作者提供)
很好——让我们使用这些模式来安排执行。打开一个终端窗口,执行pwd
和which python3
命令,获得脚本文件夹和 Python 的绝对路径:
图 3-获取绝对路径(图片由作者提供)
一旦有了这些,输入crontab -e
命令编辑一个 cron 文件,或者创建一个不存在的文件:
图 4 —访问 crontab 文件(作者图片)
它将打开一个 VIM 编辑器——在那里,点击键盘上的I
键进入插入模式。您必须指定调度模式、Python 可执行文件的完整路径和脚本的完整路径,以使调度工作正常进行。使用下图作为参考:
图 5 —编辑 crontab 文件(作者图片)
完成后,按下ESC
键退出插入模式,紧接着按下:wq
和ENTER
键。这将保存 crontab 文件,您将立即返回到终端窗口:
图 6 —成功创建 crontab 文件(作者图片)
要验证文件是否已成功保存,您可以使用crontab -l
命令——它将列出所有计划的作业:
图 7-列出所有计划的作业(按作者排序的图像)
这就是你要做的。让我们在下一节检查调度是否有效。
测试
除了坐在那里看着你的脚本每一分钟被执行,你别无选择。这是几分钟后我的output
文件夹的样子:
图 8-输出文件夹(作者提供的图片)
如您所见,一切都按预期运行。不要忘记删除这两个作业,否则每天会有 1440 个新的 CSV 文件。
MacOS Gotchas
Linux 用户应该不会有任何问题,但是在 macOS 上情况就不同了。默认情况下,macOS 不会给终端和 Cron 提供完整的磁盘访问权限,所以您必须手动完成。
需要明确的是,您不会得到任何错误,但是output
文件夹将保持为空。
如果是这样的话,请跟随这篇文章——它将向您展示如何向终端和 Cron 授予完整的磁盘访问权限。
结论
现在您已经知道了——如何在 Linux 和 macOS 上使用 Cron 轻松调度 Python 脚本。
可能性是无限的——从预定的 web 抓取到 ETL 管道的自动执行。代码可以改变,但原则保持不变。
玩得开心!
喜欢这篇文章吗?成为 中等会员 继续无限制学习。如果你使用下面的链接,我会收到你的一部分会员费,不需要你额外付费。
https://medium.com/@radecicdario/membership
了解更多信息
- 每个数据科学家必读的 3 本编程书籍
- 如何让 Python 静态类型化—基本指南
- 使用 Python 进行面向对象编程——你需要知道的一切
- Python 字典:你需要知道的一切
- 介绍 f 字符串 Python 中字符串格式化的最佳选项