TowardsDataScience-博客中文翻译-2022-十八-

TowardsDataScience 博客中文翻译 2022(十八)

原文:TowardsDataScience

协议:CC BY-NC-SA 4.0

使用 Terraform 在 GCP 上部署云功能

原文:https://towardsdatascience.com/deploy-cloud-functions-on-gcp-with-terraform-111a1c4a9a88

在本教程中,您将使用 Terraform 部署一个由云存储事件触发的简单云功能

图片由geralt|Pixabay提供

云功能 是可扩展的“现收现付”功能即服务(FaaS)来自谷歌云平台 (GCP)运行您的代码,无需服务器管理。

云函数可以用 Node.jsPythonGoJava编写。NETRubyPHP 编程语言。为了可读性,本教程将包含一个用 Python 编写的云函数。但是请随意选择您喜欢的编程语言。

Terraform 是一款基础设施即代码(IaC)开发工具,允许您安全、可重复且高效地构建、更改和版本化基础设施。它将云 API(GCP、AWS、Azure 等)编译成声明性的配置文件。

在本教程结束时,你将有一个云功能,一旦一个文件被上传到谷歌云存储桶触发。

先决条件

要学习本教程,您需要:

然后,您可以在您的终端中运行gcloud auth application-default login的本地机器上通过 GCP 认证。

目标

本教程的目标是使用 Terraform 在 GCP 项目中进行部署:

  • 上传文件的桶。
  • 一个存储云函数源代码的桶。
  • 每次将文件上传到第一个存储桶时触发的云函数,其源代码在第二个存储桶中。

项目结构

您可以创建一个新文件夹,我将它命名为cloud_function_project,但是请随意选择一个对您来说方便的名称。

然后创建如下定义的文件。暂时将它们留空,随着教程的继续,您将完成它们。

.cloud_function_project/
│ 
├── terraform/
│    │
│    ├── backend.tf
│    ├── function.tf
│    ├── main.tf
│    ├── storage.tf
│    └── variables.tf
│
└── src/
     │
     ├── main.py 
     └── requirements.txt

在您开始归档不同的文件之前,我们将快速浏览一下每个文件的作用。

src文件夹包含云函数的源代码。它是 Python 特有的结构。

  • main.py:云函数的源代码。
  • requirements.txt:运行main.py需要的 python 库列表。(在本教程中您不需要它)

terraform文件夹包含要部署的环境的配置文件。

  • backend.tf:声明地形后端
  • main.tf:环境的主要声明。
  • variables.tf:变量的定义。
  • storage.tf:Google 云存储桶的声明。
  • function.tf:云函数声明。

创建云函数

编写和运行云函数不是本教程的重要部分。您将部署一个函数,该函数将记录一些关于已经上传到 bucket 中的文件的有用信息,以便进行跟踪。

注意:该功能没有需求,所以requirements.txt为空。
注意:这是一个 Python 云函数,所以你可以根据你选择的编程语言改变代码源。

创建地形基础设施

后端

通过指定 Terraform 后端开始声明您的环境。您可以选择[local](https://www.terraform.io/language/settings/backends/local),这意味着所有的状态文件都将存储在本地目录中。你也可以选择一个[gcs](https://www.terraform.io/language/settings/backends/gcs)后端,但是让我们保持简单。

变量

声明 Terraform 文件中使用的变量。您需要根据您想要在其中部署资源的项目的 ID 来更改project_id变量。当然,你也可以更换region

主要的

声明与 google 提供者的连接。

谷歌云存储

声明两个 Google 云存储桶,分别存储云函数的代码和上传文件。

注意:桶名以project_id为前缀,因为桶必须有唯一的名称。

谷歌云功能

最后一步:声明云函数。这需要将源代码压缩成 zip 文件,并上传到 bucket 中进行存储。然后在用 Terraform 创建云函数时可以访问源代码。

注意:文件有点长,所以可以随意查看注释来理解逻辑。

部署环境

一切准备就绪,可以部署了。在您的终端中找到项目的根目录,然后找到terraform文件夹的根目录。

$ cd ./terraform

初始化您的代码以下载代码中提到的需求。

$ terraform init

查看更改。

$ terraform plan

接受变更并将其应用于实际的基础设施。

$ terraform apply

测试您的云功能

测试一切工作正常:

  • 打开谷歌云控制台并连接到你的项目。
  • 进入谷歌云存储浏览器
  • 您可以看到<YOUR-PROJECT-ID>-function<YOUR-PROJECT-ID>-input铲斗。点击名为<YOUR-PROJECT-ID>-input的桶,将任意文件上传到其中,触发云功能。
  • 要验证它是否有效,请访问您的云功能列表。这里应该有一个名为function-trigger-on-gcs的云函数。点击它的名称并转到LOGS选项卡,查看它是由您上传的文件触发的。

知识就是分享。
支持我,一键获取 中我所有文章的访问

更进一步

我希望你喜欢这篇部署云功能的简短教程。可以应用许多改进。您可以:

  • 设置一个 IAM 策略
  • 添加变量并将该代码转换成可重复使用的地形模块
  • 将部署集成到云构建流程中,以实现持续部署。
  • 还有很多…

使用 AWS 代码管道快速部署 Lambdas

原文:https://towardsdatascience.com/deploy-lambdas-fast-with-aws-codepipeline-f90b0bf5ca64

用于在 AWS 上部署代码变更的连续部署管道

迈克·本纳在 Unsplash 上的照片

建立一个好的软件发布渠道很重要,这有很多原因。如果没有建立管道,程序员通常会花更多的时间测试和部署代码,而不是进行改进。理想情况下,我们希望将代码推送到我们的存储库中,让管道负责打包、测试和发布对我们环境的更改。

在 AWS 上,CodePipeline 是一种直接的软件发布服务。使用 CodePipeline,您可以定义一组在 AWS 上运行的阶段,每个阶段都包含按指定顺序运行的操作。如果你曾经使用 Jenkins 来构建管道,那么这可能听起来很熟悉。

在本文中,我将为您提供一个使用 AWS 代码管道部署 Lambda 的设置。我们将使用 CloudFormation 和 CodeBuild 等其他 AWS 服务的组合,因此如果您对 AWS 完全陌生,您会希望用其他资源来补充这篇文章,以使事情真正有意义。

以下是我们将涉及的所有内容:

  • 将 Github 存储库连接到 AWS
  • 创建服务角色
  • 使用 AWS Cloudformation 部署管道
  • 为代码构建准备 buildspec 文件
  • 为你的 Lambda 准备一个云模板
  • 使用代码管道部署 Lambda

让我们开始吧。我假设您已经建立了一个 git 存储库,其中包含您的 Lambda 代码。

将存储库连接到 AWS

如果您正在使用 Github,您必须在 AWS 和您的存储库之间创建一个连接,以便每当有新的代码更改时,您的管道可以得到通知。为此,请转到 AWS 控制台上的 AWS Codepipeline,单击“设置”下的“连接”并创建一个连接。这将打开一个向导,您将选择 Github 作为您的提供商,给你的连接一个名称,然后它将要求您通过一个 Github 弹出窗口授权您的连接。完成后,您将在 connections 下看到一个新的连接,它有自己的 ARN,我们稍后将在管道中使用。

创建服务角色

为此,我们将使用 AWS CodePipeline、CodeBuild 和 CloudFormation。我们需要创建一个服务角色,为每个服务授予代表您访问其他服务的权限。例如,服务角色将授予 CodeBuild 将您的代码上传到 S3 的权限。让我们使用 aws cli 为每个角色创建服务角色。

将下列信任策略保存在 3 个单独的文件夹中。每个服务的 json 文件。对于每个文件,使用“cloudformation.amazonaws.com”、“codepipeline.amazonaws.com”、“codebuild.amazonaws.com”更改突出显示的服务块

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": **[SERVICE]**
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

对于每个服务,创建一个服务角色并记下 ARN。

aws iam create-role --role-name role-example --assume-role-policy-document file://[your-file-for-each-trust-policy].json

现在您已经创建了三个角色,每个角色都可以控制您为其赋予信任策略的服务,但是它们没有访问其他服务的权限。让我们通过将内联策略附加到角色来授予这些权限。下面是一个简单的内联策略,它将为您的服务提供对任何服务的任何操作的权限。这不是一个好的做法,所以您需要在以后缩小范围,只对它需要的资源执行操作。

将这个内联策略存储在一个. json 文件中。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": "*",
      "Effect": "Allow",
      "Resource": "*"
    }
  ]
}

将该政策应用于您的每个角色,如下所示:

aws iam put-role-policy --role-name role-example --policy-name policy-example --policy-document file://[your-policy-file].json

部署具有云结构的管道

AWS CloudFormation 帮助我们设置和管理我们帐户中的资源。yaml 文件,这样我们就不必手动提供每个资源。下面我粘贴了将部署管道和构建项目的模板。

cloudformation 模板有几个主要组件。这里有两个——参数和资源。参数是资源的输入。资源是我们想要部署的 AWS 服务的定义。该模板部署两种资源:CodeBuild 和 CodePipeline。

让我们来分解一下我们的渠道阶段:

  • 源代码阶段:连接到您的代码库并获取代码更改
  • 构建阶段:使用 Codebuild 项目为 Lambda 安装 python 依赖项,并为 Lambda 资源打包 Cloudformation 脚本
  • 开发阶段:部署前一阶段打包的 Cloudformation 脚本

一旦我们通过 CloudFormation 部署了模板,它将自动提供和管理构建项目和管道。如果您在控制台上进入 CloudFormation 并找到一个包含您的资源的新堆栈,您将能够看到资源已部署。

在部署模板之前,创建一个 S3 存储桶,并将模板文件上传到存储桶中的某个位置。

aws s3api create-bucket --bucket bucket-example

此外,我们需要一个. json 文件来存储模板中的参数值。复制这个。json 文件,并用您之前创建的参数值替换这些参数值。

然后部署模板:

aws cloudformation create-stack --stack-name stack-example --template-url [your-s3-template-location] --parameters file://[your-param-file.json]

现在,您应该会在 CloudFormation 中看到一个用于管道和构建项目的新堆栈。如果导航到 CodePipeline,您还会看到一个名为“lambda-pipeline”的管道。管道将显示上面讨论的阶段。现在,每当您的存储库中有代码变更时,它将自动通过管道运行。管道将在部署阶段失败,因为我们的构建项目找不到 buildspec 文件。

为代码构建准备 buildspec 文件

一个 buildspec 文件包含一组在软件发布期间测试和打包代码时应该运行的命令。一个例子是为你的 Lambda 安装依赖项。这些命令被分成几个阶段,如 pre_build、build、post_build 和 outputs。查看链接的参考页面,了解所有阶段的更多信息。

管道的工作是将工件从前一个阶段移动到下一个阶段。在我们的例子中,管道将它在源代码阶段获取的代码库移动到构建阶段的代码构建项目中。工件由管道模板中的输入工件和输出工件指定。构建项目的输入工件将是我们的源代码。默认情况下,它位于构建容器的$CODEBUILD_SRC_DIR 目录中。

我们构建项目的目的是从 requirements.txt 文件安装 Lambda 的所有依赖项(我假设是 python Lambda),准备另一个描述 Lambda 的 cloudformation 模板,并将该模板作为输出工件返回到构建项目。下面是 buildspec 的样子。

cfn.yaml 文件是 Lambda 的 cloudformation 模板。让我们也创造它。

在这个模板中,我们有两个资源,一个 Lambda 角色和一个 Lambda 函数。Lambda 函数包含代码和处理程序属性,指向 Lambda 文件和处理程序函数的目录。buildspec 文件中的 cloudformation package 命令将打包来自这个源代码(包括已安装的依赖项)的代码,将包上传到命令中指定的 S3 位置,并将模板中的代码位置替换为 S3 的位置。然后,它将返回一个新的模板文件,其名称在$TEMPLATE_FILE_NAME 环境变量中指定。这是我们将指定为部署阶段的输出工件的文件,用于使用 Cloudformation 进行部署。

buildspec 文件中的输出还指定了一个 config.json 文件。该文件将包含 cloudformation 模板中任何参数的值。作为示例,我们添加了一个名为 S3ArtifactStore 的参数,因此 config.json 会:

{
 "Parameters": {
  "S3ArtifactStore": "your-s3-bucket-name"
 }
} 

现在您已经创建了这些文件,推动更改以查看您的管道启动。一旦管道成功运行,您将在 CloudFormation 中看到一个名为“lambda-stack”的 lambda 新堆栈,并在 AWS Lambdas 中看到一个新的 Lambda。

AWS 代码管道

现在,您已经使用自动化发布过程成功地部署了 Lambda。您所做的任何进一步的代码更改都将直接更新 AWS 上的 Lambda,因此您不必浪费任何时间来手动部署您的更改。

仅仅遵循这个指南应该可以帮助你成功地建立一个管道,但是如果你是这些服务的新手,那么你会发现我的解释在很多阶段都是欠缺的。最好的理解方法是查看每个阶段的 AWS 指南。与此同时,我可能会写更多的指南来更深入地研究所使用的一些服务,并将它们链接到这里。

使用数据面板部署物流操作仪表板

原文:https://towardsdatascience.com/deploy-logistics-operational-dashboards-using-datapane-585cf19532f1

使用 DataPane 部署报告解决方案以支持电子商务的仓储物流操作

皮卡伍德Unsplash 上拍摄的照片

目标

构建报告功能以提供供应链可见性 并支持中型零售商的配送中心的运营团队

介绍

在中国等一些市场,电子商务已经彻底颠覆了零售业。快速变化的消费者行为极大地改变了这些公司管理业务的方式。

这直接影响了这些零售商的物流运营 ,他们现在面临数量的高波动性、更大的产品组合以及非常短的交付周期

为小型企业构建数据分析能力

然而, SMEs (中小企业)公司可能没有预算来投资于昂贵的数据基础设施以建立报告能力

在本文中,我们将使用 Python 库 DataPane 为仓储操作设计一个简单的架构来部署一个交互式仪表板。

💌新文章免费直入你的收件箱:时事通讯

**SUMMARY**
**I. Context: Warehousing Operations for E-Commerce** Warehouse to prepare and ship orders for an E-Commerce website
**Objective** How can you support Operational Excellence with simple reports
**II. Build reports using DataPane
1\. Quick Introduction** Deploy and share your results with Datapane
**2\. Monitor the picking workload**
Build visuals showing the number of orders received per day**3\. Analyze the volumes pareto** How many SKU are representing 80% of your volume?
**III. Conclusion & Next Steps**

一、如何在没有 PowerBI 的情况下部署仪表盘?

问题陈述

作为一家中型在线零售商配送中心(仓库)持续改进工程师,你负责建立报告能力,为运营团队带来可见性。

电子商务的物流运作

(图片由作者提供)

配送中心负责订单履行和装运

  1. 顾客在网站上订购产品
  2. 这些订单由仓库管理系统(WMS) 接收
  3. 仓库操作员准备订单,并将其打包
  4. 包裹被运送给顾客

配送中心布局(图片由作者提供)

最重要的关键绩效指标(KPI) 是订单接收和包裹装运之间的提前期

此 KPI 受链中所有流程的影响;您将提供影响整体绩效的关键指标的可见性。

有关物流绩效管理的更多信息

http://samirsaci.com

二。使用 DataPane 构建报告

你不会用 ETL 作业和像 PowerBI、Tableau 或 Google Studio 这样的高级可视化工具来构建一个完整的云架构。

这个想法是从 WMS 中提取数据,在本地处理你的数据,并部署可供运营团队使用的报告。

1.使用 DataPane 部署报告功能

这个框架让你有机会与你的同事分享你的 Jupyter 笔记本的结果。

例如,您想分享这个简单的条形图。

(图片由作者提供)

该图表显示了仓库每天收到的订单(和订单行)数量。

你如何与同事分享这张图表?

这是一个非常简单的三步流程( 链接 )

  1. 使用 pip 获取客户端库
pip3 install datapane

2.在 DataPane 上注册并注册您的令牌

datapane login --server=https://datapane.com/ --token=yourtoken

3.展开你的视觉

您需要在代码中添加一个部分来部署您的 visual,您可以选择几个模板

4.您可以与同事分享这一视觉效果

(作者视觉)

他们甚至可以用按钮(左上角)选择星期。您可以通过发送给运营团队的链接私下分享这一视觉效果。

后续步骤

我们现在将基于特定流程构建一组视觉效果,为团队带来可见性。

你可以在我的 Github(跟随我:D)资源库中找到完整的代码和虚拟数据:链接
我的投资组合与其他项目:萨米尔萨奇

2.监控你的工作量

关注挑选过程

(图片由作者提供)

操作员正拿着他们的手推车,手推车上有顾客订购的商品清单,他们会在每个地点停下来清点订购的商品数量。

如果你想了解更多关于拣选过程的信息,请看下面的视频

详细挑选过程(视频由作者提供)

订单/订单行数

问题
我们每天从客户那里收到多少订单(和订单行)?

提货工作量的一个主要指标是客户订单(和订单行)的数量。

通过链接访问互动视频(作者提供的视频)

见解
第一周周日:拣货团队面临订单行高峰,这可能解释了这一天的糟糕表现。

每天的件数

问题
客户每天订购多少件商品?

该指标可以显示公司当天的营业额。这也影响了包裹运送量(立方米)。

通过链接访问互动视频(作者提供的视频)

感悟
第一周周三:由于某商品的特别促销,我们经历了每条线件数的激增。

按行/订单比率拆分订单

问题
每天单线订单的拆分(%)是多少?

由于每个订单的行数很多,您的操作员将会看到他们每个订单的行走距离在增加。因此,他们的提货生产率直接受到每个订单行数的影响。

通过链接访问视频(视频由作者提供)

见解
我们有大部分单行订单(1 行/订单)可以批量提货。

交付的城市数量

问题
我每天需要配送多少个不同的城市?

交付的城市数量会影响您的运输管理工作量。

通过链接访问视频(视频由作者提供)

见解
在这个月的大部分时间里,您经历了送货城市数量的激增,这可能会影响您的运输成本。

关注补充流程

(图片由作者提供)

当拣选位置(在地面上)是空的,你的叉车司机执行补给任务。他们从上面的存储区提取物品,以补充未来订单的提货地点。

每天补充的数量

问题
你们操作员每天完成多少补货任务?

这个过程可能会成为瓶颈,影响您的整体性能,您需要跟踪每天的工作量。

通过链接访问视频(视频由作者提供)

第一周星期三:你会看到大量的补给任务,这可能会影响你的工作效率。

每个通道的补充数量

问题
您仓库的哪个区域集中了您的大部分货量?

您的仓库由带有单元和拣选位置的小巷组成。

仓库布局示例(图片由作者提供)

瓶颈的一个主要来源是当人们集中在一个区域时。如果你遇到这种问题,最好的解决方法是避免在同一个地区分组高轮转。

通过链接访问视频(视频由作者提供)

洞察
第一周:你可以看到 A09 和 A10 代表了件量的近 20%,这可能会在订单高峰时造成瓶颈。

3.分析帕累托

问题
有多少 SKU 代表我总销量的 80%?

为了优化您的流程,您需要根据每件产品的数量进行产品细分。

高周转次数的物品需要放在满托盘的提货位置,而低周转次数的物品可以放在货架上以节省空间。

因为业务在发展,你需要追踪你的帕累托图调整你的布局和流程

通过链接访问视频(视频由作者提供)

如果你想了解更多关于布局优化的帕累托法则,请看下面的视频

利用帕累托定律优化布局(视频由作者提供)

三。结论和后续步骤

关注我的 medium,了解更多与供应链数据科学相关的见解。

结论

您已经构建了一组简单(但非常有用)的视觉效果,可供运营团队使用。

例如,它们可以嵌入到带有评论区的概念文档中,使其成为一个动态文档。

这种解决方案无法满足适当的云架构的性能和功能。然而,对于小型结构来说,它可以容易地实现,而不需要任何额外的成本。

在云上部署您的解决方案

您可以在云中部署用于构建这些视觉效果的代码(Heroku,Google App Engine ),以自动化这一过程并每天触发任务。

建造供应链控制塔

供应链控制塔传统上被定义为一组连接到各种系统的仪表板,使用数据监控整个供应链的重要事件

它使供应链部门能够更好地实时跟踪、了解和解决关键问题。

分三步跟踪货物—(图片由作者提供)

数据面板将用于跟踪货物,并向商场和物流经理报告事故:

  • 有多少货物延迟交付?
  • 目前运输中的货物在哪里?
  • 有多少转运货物面临风险?

欲知详情,

https://www.samirsaci.com/automated-supply-chain-control-tower-with-python/

关于我

让我们连接上 LinkedinTwitter ,我是一名供应链工程师,正在使用数据分析来改善物流运作和降低成本。

如果你对数据分析和供应链感兴趣,可以看看我的网站

https://samirsaci.com

参考

1 DataPane,用 Python 分析——在 DataPane 上共享,文档,链接

使用 Python Dash 和 Pipenv 部署机器学习模型

原文:https://towardsdatascience.com/deploy-machine-learning-model-using-dash-and-pipenv-c543569c33a6

用 Github 和 Heroku 公开托管一个 Web 应用

作者图片

数据科学家和机器学习工程师花费大量时间进行探索性数据分析(EDA)、ML 模型开发和超参数调整。通常,项目的最终目标是在生产中部署机器学习模型。本文旨在介绍一种简单而有效的方法来开发一个 web 应用程序,将一个经过训练的 ML 模型投入生产: Dash 和 Pipenv

步骤 1:创建虚拟环境

虚拟环境是一种工具,它创建一个隔离的空间来存储所有程序和文件,并保留项目所需的 Python 包的依赖关系。

为什么虚拟环境是必要的?

其他人可能会问你“为什么你的程序会在我的电脑上崩溃?“Python 有其独特的安装、存储和加载其包的方式,以便底层程序能够正常运行。如果您要在不同的项目之间切换或与其他人共享您的项目,创建一个虚拟环境会使管理所需的包变得更加容易。它是一个有用的工具,可以给项目带来很多好处。

  • 它可以在每个项目的基础上保持包的依赖性。例如,您可以在同一台计算机上对一个项目使用 Dash 2.1,对另一个项目使用 Dash 2.2。
  • 它可以为你的程序创建一个隔离空间,这样你的项目就是独立的,并且可以用所需的包进行复制。
  • 在虚拟环境中,您可以安装软件包而无需管理员权限

创建虚拟环境有不同的工具。对于本文,我将使用pipenv为我们的 ML 项目建立一个虚拟环境。

安装管道

pip install pipenv

初始化虚拟环境

打开 anaconda 提示符或命令提示符,键入以下命令来初始化虚拟环境。最初,它将在项目文件夹中创建一个名为“ Pipfile ”的文件。该文件将包含项目所需的所有包。

cd "project folder path"
pipenv shell

在虚拟环境中安装 Python 包

在命令提示符下键入以下命令。pipenv install [package]将在虚拟环境中安装软件包,并更新“Pipfile”文件以包含所有已安装的软件包。pipenv lock将创建一个名为“ Pipfile.lock ”的文件,其中包含项目的依赖项和子依赖项、已安装的版本以及已下载文件的散列。这确保了确定性的构建。

pipenv install "scikit-learn==1.0.2"
pipenv install "xgboost==1.4.2"
pipenv install "dash==2.3.0"
pipenv install "dash-bootstrap-components==1.0.3"
pipenv install "numpy==1.22.3"
pipenv lock

Pipfile(作者图片)

在虚拟环境中卸载包。您可以在移除包后使用pipenv uninstall [package1] [package2] [package3] ...,运行pipenv lock来更新“Pipfile.lock”。

步骤 2:开发一个 ML 模型

在我们创建了一个虚拟环境并安装了所有必要的 Python 包之后,我们就可以开始开发我们的机器学习模型了。对于本文,我们不打算关注模型开发和超参数调整,所以我使用 XGBoost 开发了一个简单的分类模型来预测鸢尾花的种类。

在我们训练完 ML 模型(即 clf)之后,我们可以使用pickle导出模型,这是一个序列化和反序列化 Python 对象的有用工具。确保导出项目文件夹中的 ML 模型,因为我们将在 Dash 程序中部署 ML 模型。

**# ML_model.py**
from sklearn import datasets
from **xgboost** import XGBClassifier
import pickle#Importing dataset from sklearn
iris = datasets.load_iris() 
X = iris.data               
y = iris.target#Create an XGB classifier
**clf = XGBClassifier()
clf.fit(X, y)**# Export the ML model
with open(r'**xgb_model_iris.pickle**', 'wb') as f:
    **pickle**.dump(clf, f)

步骤 3:使用 Dash 开发一个 Web 应用程序

现在我们可以开始开发一个漂亮的前端 Web 应用程序来部署我们的 ML 模型。我们将使用由 FlaskPlotly.jsReact.js 编写的轻量级 Python 框架、Dash 来构建 web 应用。

Dash 程序需要几个步骤。

  • 导入所需的库(例如,dash、dash_bootstrap_components、pickle 等)
  • dash 环境的设置
  • 使用 Pickle 加载 ML 模型
  • 为 web 应用程序准备布局,以便用户可以为 ML 模型输入他们的输入(例如,萼片长度、萼片宽度、花瓣长度和花瓣宽度)。输入可以采取不同的形式,例如,下拉菜单、文本框、滑块和复选框。为了让 app 结构看起来漂亮,我们将使用dash_bootstrap_components这个包,它将 Bootstrap 框架集成到 Dash app 中。
  • 创建回调以接收来自用户的输入,运行 ML 模型,然后输出预测(例如,鸢尾花的种类)。
**# Dash_App.py**
**### Import Packages ########################################**
import **dash**
from dash import dcc
from dash import html
from dash.dependencies import Input, Output, State
import dash_bootstrap_components as dbc
import numpy as np
import pickle**### Setup ###################################################**
app = dash.Dash(__name__)
app.title = 'Machine Learning Model Deployment'
server = app.server**### load ML model ###########################################**
with open('**xgb_model_iris.pickle**', 'rb') as f:
    clf = pickle.load(f)**### App Layout ###############################################**
app.layout = html.Div([
    **dbc.Row**([html.H3(children='Predict Iris Flower Species')]),
    dbc.Row([
        **dbc.Col**(html.Label(children='Sepal Length (CM):'), width={"order": "first"}),
        dbc.Col(**dcc.Slider**(min=4, max=8, value = 5.8, id='sepal_length')) 
    ]),
    dbc.Row([
        dbc.Col(html.Label(children='Sepal Width (CM):'), width={"order": "first"}),
        dbc.Col(dcc.Slider(min=2.0, max=5,  value = 3.0, id='sepal_width')) 
    ]),
    dbc.Row([
        dbc.Col(html.Label(children='Petal Length (CM):'), width={"order": "first"}),
        dbc.Col(dcc.Slider(min=1.0, max=7,  value = 3.8, id='petal_length')) 
    ]),
    dbc.Row([
        dbc.Col(html.Label(children='Petal Width (CM):'), width={"order": "first"}),
       dbc.Col( dcc.Slider(min=0.1, max=3,  value = 1.2, id='petal_width')) 
    ]),   
    dbc.Row([dbc.Button('Submit', id='submit-val', n_clicks=0, color="primary")]),
    html.Br(),
    dbc.Row([html.Div(id='prediction output')])

    ], style = {'padding': '0px 0px 0px 150px', 'width': '50%'})**### Callback to produce the prediction #########################** 
[@app](http://twitter.com/app).**callback**(
    **Output**('prediction output', 'children'),
    **Input**('submit-val', 'n_clicks'),
    State('sepal_length', 'value'),
    State('sepal_width', 'value'),
    State('petal_length', 'value'), 
    State('petal_width', 'value')
)

def update_output(n_clicks, sepal_length, sepal_width, petal_length, petal_width):    
    x = np.array([[float(sepal_length), float(sepal_width), float(petal_length), float(petal_width)]])
    prediction = **clf.predict**(x)[0]
    if prediction == 0:
        output = 'Iris-Setosa'
    elif prediction == 1:
        output = 'Iris-Versicolor'
    else:
        output = 'Iris-Virginica'
    return f'The predicted Iris species is {output}.'**### Run the App ###############################################**
if __name__ == '__main__':
    app.run_server(debug=True)

步骤 4:在本地部署 ML 应用程序

完成 Dash 程序后,我们现在可以在本地机器上部署 web 应用程序。打开 anaconda 提示符或命令提示符,并键入以下命令。

cd [project folder path]
pipenv shell
python **Dash_App.py**

它将运行我们的应用程序,该应用程序将在 http://localhost:8050/http://127.0.0.1:8050/ 上提供。

作者图片

第五步:用 GitHub 和 Heroku 公开部署 ML 应用

在公共网站上部署 web 应用程序最简单的方法之一是使用 Heroku ,这是一种云平台服务,只需一个免费帐户即可托管 web 应用程序。我们可以将所有的文件和程序导出到一个 Github 仓库,然后将 Github 仓库链接到你的 Heroku 账户,并在仓库中部署一个分支。

作者图片

除了我们上面提到的文件和程序,我们还需要存储库中的两个附加文件来在 Heroku 中正确运行应用程序, Procfileruntime.txt.

以下是 Procfile 的一个例子。这是一个没有文件扩展名的简单文本文件。“gunicorn”是一个用于 WSGI 应用程序的纯 Python HTTP 服务器,允许多个用户同时访问应用程序。这里的“Dash_App:server”表示 Dash_App.py 内部的服务器对象。

web: gunicorn Dash_App:server

下面是一个 runtime.txt 的例子。它指定了一个 Python 运行时(例如,主要、次要和补丁)。它区分大小写,不得包含空格。

python-3.8.5

作者图片

感谢您的阅读!!!

如果你喜欢这篇文章,并且想请我喝杯咖啡,请点击这里。

您可以注册一个 会员 来解锁我的文章的全部访问权限,并且可以无限制地访问介质上的所有内容。如果你想在我发表新文章时收到电子邮件通知,请订阅。

使用 Node.js Swagger、BigQuery 和 AWS Cloudformation 部署机器学习模型

原文:https://towardsdatascience.com/deploy-machine-learning-models-with-node-js-swagger-bigquery-and-aws-cloudformation-1c66f60e79a3

教程如何用一个命令部署你的机器学习模型

作者图片💡迈克·沙克霍米罗夫

对初学者来说真的简单易学。

包含代码的存储库可以在这里找到。

概述

在这篇文章中,我将创建一个简单的 API,并用 AWS Cloudformation 部署它。我想实现以下目标:

  • 创建一个节点。JS API 服务于我的机器学习模型。
  • 将 API 服务连接到数据仓库解决方案(在我的例子中是 BigQuery )
  • 使用 DockerAWS Cloudformation 部署我的服务

先决条件、库和设置

  • Node.js 。你将需要它来创建一个新的招摇 API。
  • GCP 账号已启用 Google BigQuery 。我将使用 BigQuery 作为数据仓库解决方案来存储我的数据和训练 ML 模型。当涉及到数据清理时,它是非常有用的。然后我可以导出数据并在 Spark 中训练我的模型,甚至直接在 BigQuery 中训练它。一些常见的任务如逻辑回归已经存在。
  • AWS 帐户来部署服务。是的,在这个练习中我们将完全混合。

免费层是可用的,所以它不会花费你任何东西,但它总是一个好主意,以保持对帐单的关注。

顺便说一下,使用 AWS Cloudformation,您可以删除所有相关资源,然后一键重新创建它们。

第一步。创建一个新的 Swagger 项目

我将使用swagger1 包来创建我的 API。

  • $ npm install -g swagger
  • cd Documents/code
  • $ swagger project create ml-service
  • 选择表示作为框架。
  • 成功!您可以通过运行$ swagger project start ml-service来启动您的新应用

第二步。连接您的 ml-service 和 BigQuery 数据仓库

我们假设我们在 BigQuery 中保存了一个机器学习模型,即流失预测等。让我们构建一个数据连接器,用我们的服务来服务这些预测。

为您的移动服务创建一个服务帐户

这需要授权您的应用程序,以便它可以访问 BigQuery 中的数据。

./bq-shakhomirov-b86071c11c27.jsonBigQuery 凭证文件的一个例子。您将需要这个 服务帐户凭证 文件来通过 Google 验证您的微服务,这样它就可以真正做一些事情。点击阅读更多关于服务账户认证的信息。只需从你的谷歌云平台账户下载并添加到你的应用文件夹。

看起来应该是这样的:

然而,我们正在用 swagger 构建一个服务,所以让我们把它添加到config/default.yaml中。

最后,它应该是这样的:

向 API 路由添加端点

我们将使用这个端点来返回我们需要的数据。

在接下来的步骤中,我们将创建一个函数作为控制器,这样每当我们点击 API 端点时,它将执行并从我们的 BigQuery 表中提取数据。

转到api/swagger/swagger.yaml,在默认/hello_world端点之后添加新的 API 端点:

添加一个 swagger api 控制器(函数)

它将运行一个 SQL 查询,并从 BigQuery 表中返回模型数据。让我们称它为api/controllers/userList.js,它将被用来返回一个用户列表,以防我们想以某种方式使用它,例如,在重定向中。

在这种情况下,api/controllers/userList.js将为我们的端点处理响应和状态代码。作为依赖项添加的bigQueryHelper将负责我们需要的任何数据仓库逻辑,包括潜在的数据转换、缓存和其他一切。

BigQuery 助手

这将处理实际的连接和我们的数据逻辑。让我们创建一个文件api/helpers/bigQueryHelper.js

安装所需的第三方依赖项

我们将只使用两个。运行以下命令:

$ npm i @google-cloud/bigquery http-status-codes --reg [http://registry.npmjs.org/](http://registry.npmjs.org/)

现在运行$ swagger project start并尝试一个建议的$ curl [http://127.0.0.1:10010/hello?name=Scott](http://127.0.0.1:10010/hello?name=Scott)

注意:如果你运行的 Node.js 高于 10.x,你很可能会遇到这个错误 4

我希望 swagger-node 会收到更多及时的更新,但这里是一个修复。

权宜之计

  • 更新你的 swagger-express-mw: "swagger-express-mw": "^0.7.0"

  • config/default.yaml将 swagger_params_parser 添加到 swagger_controllers

  • npm install

  • 运行swager project start

  • 再试试curl http://127.0.0.1:10010/hello?name=Scott。现在一切都应该运行正常。

在 BigQuery 中填充您的表

我们假设 model trainer 作为一个单独的流程运行,并填充我们的表,例如,每天。我们来模拟一些数据。在您的数据仓库中运行以下 SQL:

最后

如果一切正常,那么尝试运行我们的 BigQuery 数据连接器

输出必须是:

这里我们创建了一个简单的数据服务,它将从您的数据仓库中提供数据。

第三步。Docker 映像和 AWS Cloudformation 部署

创建文档文件./Dockerfile:

构建 Docker 映像

在命令行中运行:

  • $ docker build -f Dockerfile -t yourAccountNumber.dkr.ecr.eu-west-1.amazonaws.com/ml-service:latest .
  • $ docker run -p 80:10010 -it yourAccountNumber.dkr.ecr.eu-west-1.amazonaws.com/ml-service:latest

这将容器的端口 10011 绑定到主机 127.0.0.1 上的 TCP 端口 80。记得你暴露了端口 80,所以试试:$ curl [http://localhost:80/userList/3](http://localhost:80/userList/3)

输出:输出必须是:

现在,当我们构建了 Docker 映像后,让我们映像推送到 AWS ECR 库。我们需要先创建一个。

创建 AWS ECR 存储库

为此,您将需要 AWS CLI

创建 AWS Cloudofrmation 文件(复制—粘贴此文件):cloudformation/ecr.template

运行:

  • $ cd ./cloudformation
  • 用您的名字替换 MyMlServiceRepositoryml-service 并运行:
aws cloudformation create-stack — template-body file://ecr.template — stack-name MyMlServiceRepository — capabilities CAPABILITY_IAM — parameters ParameterKey=YourModule,ParameterValue=ml-service

如果成功的输出是这样的:

{ "StackId": "arn:aws:cloudformation:eu-west-1:53763437664:stack/MyMlServiceRepository/123f55-a9ea-11ec-97f2-02af2e5b45e7" }

现在,如果您转到 AWS 控制台并选择 Cloudformation ,您将看到您的堆栈以及相关的存储库资源。

作者图片💡迈克·沙克霍米罗夫 ak

现在,让我们将我们的形象推向这个回购:

  • $ docker push yourAccountNumber.dkr.ecr.eu-west-1.amazonaws.com/ml-service:latest

如果您遇到no basic auth credentials错误,您可能想先登录:

再次按下,您的 Docker 图像将被上传:

使用 Cloudformation 堆栈创建资源并部署我们的服务

现在我们在 AWS 中有了 Docker 映像,我们希望在云中部署我们的服务。

我想用一个命令创建所有相关的资源,即 EC 集群、任务定义和负载平衡器。

使用./cloudformation/cluster_and_task.yaml创建带有任务定义和负载均衡器的集群和 ECS 服务。删除并重新创建是安全的

AWS Cloudformation 可以轻松实现这一点

只需一个 Cloudformation 文件,即可创建 Docker 集群、任务定义和负载平衡器。

在运行create命令到达./cloudformation/cluster_and_task.yaml之前,确保堆栈参数存在:

  • 关键名称
  • 图像
  • VPC
  • 您的默认 VPC 的子网

转到 EC2 服务并创建一个名为dockerClusterKeyPair的密钥对。确保堆栈文件中的其他默认名称与您的 AWS 帐户匹配。

在你的命令行运行:

$ aws cloudformation create-stack --template-body file://cluster_and_task.yaml --stack-name MlServiceStaging --capabilities CAPABILITY_IAM

成功创建堆栈后,您将在输出中看到您的 ELB(负载平衡器)端点:

$ curl [http://ecsalbmed-1019625851.eu-west-1.elb.amazonaws.com/userList/3](http://ecsalbmed-1019625851.eu-west-1.elb.amazonaws.com/userList/3)

现在您可以安全地删除整个堆栈了。

或者,您可以从文件中提供所有堆栈参数,即

$ aws cloudformation create-stack --template-body file://cluster_and_task.yaml --stack-name MlServiceStaging --capabilities CAPABILITY_IAM --parameters file://staging.json

有关更多信息,请参见亚马逊 ECS 开发人员指南 6 的故障排除部分。

结论

我们已经创建了一个简单而可靠的 API 服务来服务我们来自 BigQuery 的机器学习模型。使用基础设施作为代码使得部署、改变和进行任何类型的修改变得非常容易。例如,您可能想要创建另一个数据连接器来从 Postgres 中提取预测,并在提供给客户之前应用一些奇特的数据转换逻辑。 AWS Cloudformation 让你的代码可以重用。包括 CI/CD 在内的部署过程变得非常简单。我知道,作为代码的基础设施是一个复杂的话题,但是一旦你掌握了它,它将对你的部署有巨大的帮助和难以置信的速度提升。

资源

【2】:https://swagger.io/tools/open-source/getting-started/

https://aws.amazon.com/cli/

[6]:https://docs . AWS . Amazon . com/AmazonECS/latest/developer guide/launch _ container _ instance . html

原载于 https://mydataschool.comhttps://mydataschool.com/blog/deploy-ml-model-with-swagger-bigquery-and-node-js/

将 MNIST 训练模型部署为 Web 服务

原文:https://towardsdatascience.com/deploy-mnist-trained-model-as-a-web-service-ba333d233a5d

我负责培训、服务和客户实现。该服务接收 0 到 9(张量格式)之间的手写数字图像,并猜测该图像代表哪个数字

图片由作者提供。

在我的一篇关于深度学习的文章中,我教了如何实现一个简单的图像识别系统。该程序在一个文件中完成所有的编码工作——数据集加载、模型定义、训练和评估。

在这篇文章中,我将带您了解如何保存模型并从使用 Flask (Python web framework)实现的服务中加载它。

我还将展示如何构建一个简单的客户端来调用服务。

代码在 Github 上有。回购包含以下文件,

  • client.py
  • service.py
  • train.py
  • 神经网络. py

培训和保存模型

我在之前的文章中提到了培训阶段。我用了 PyTorch。

这是一个简单的神经网络,有一个输入层、两个隐藏层和一个输出层。

我用的例子是图像识别的“Hello World”。对于刚开始深度学习的新手来说很棒。

训练后,我保存模型学习到的权重/参数。保存学习参数,也称为“保存用于推理”。这是推荐的方法

尽管保存整个模型更容易(代码更少),但不建议这样做,因为在保存模型时,数据被绑定到类和目录结构。

下面是我如何保存模型,

torch.save(model.state_dict(), "model.pth")

服务

为了开发服务,我使用了一个名为 Flask 的 Python web 框架。该实现只包含一个端点(/guess)。

作为参考,我使用了 Tanuj Jain 的这篇文章。Tanuj 还讲述了如何在云虚拟机(VM)中部署。

当执行 service.py 时,程序加载模型(以前保存的)。模型只加载一次。

请求和响应

该服务接收包含手写数字(张量表示)的 JSON。它将字符串 JSON 转换为数组,然后再转换为张量。

接下来,它将张量展平到大小 784 (28 乘以 28)并传递给模型。模型输出(10 个节点)包含对从 0 到 9 的每个数字的猜测。

torch.argmax返回所有输出节点的最大值。最后,服务响应是一个字符串。

service.py

图一。服务正在运行。图片由作者提供。

该服务在端口 8888 上运行。可能是任何港口。如果您选择端口 80 或其他标准端口号,您可能会面临“权限”错误。

停止服务

如果需要停止服务,我用的方法就是简单的根据某个关键字寻找进程,然后杀死它。

ps -ef | grep service.py
kill -9 <process>

客户

客户端很简单。这是一个加载 MNIST 数据集并将一些手写数字(张量表示)传递给服务的程序。你选择要发送的号码。

在调用服务之前,代码将张量转换成一个列表,并将其包装在 JSON 中。此外,在调用服务之前,它将数字显示为图像,以便与服务的响应进行比较。

client.py

图二。服务响应。图片由作者提供。

最后的想法

我希望你喜欢这个教程。这里展示的例子是为了让你开始并发展到更有趣的东西。例如,想象在纸上写一个数字,指向你的相机,然后调用服务。

最关键的一步是学习保存、加载模型,并将其作为服务。接下来解释如何在不同的云供应商中部署。我打算写一篇关于它的教程。

暂时就这样了。感谢阅读。

使用 Lambda 函数从 SageMaker 部署 XGBoost 模型

原文:https://towardsdatascience.com/deploy-xgboost-models-from-sagemaker-using-lambda-functions-be58284ca0cf

关于如何公开 SageMaker ML 模型端点供所有人使用的快速教程

杰克·法甘在 Unsplash 上的照片

在 Amazon SageMaker 中创建 ML 模型端点非常棒,但是除非您能够让其他人使用它们,否则这有什么意义呢?在上一个教程中:

用 4 个简单的步骤在亚马逊 SageMaker 中训练 XGBoost 模型

我们讨论了如何创建一个 XGBoost 分类器,它可以根据一个人的房子面积来预测一只完美的小狗。也就是说,对于小于 500 平方英尺的房屋,模型会推荐小猎犬(0),而对于大于 500 平方英尺的房屋,模型会推荐德国牧羊犬(1)。

来源:左1和右[2]

现在是时候在互联网上部署这个端点了!以下是方法:

作者图片

👉策略

  1. 创建一个 Lambda 函数,用于处理传入的请求有效负载(即住宅区输入),并将其传递给 SageMaker ML 模型端点
  2. 使用 API 网关创建一个 REST API 来接受客户机请求

这是我们努力实现的最终目标。我们希望能够从互联网上的任何地方向我们的 SageMaker 端点发送 POST 请求:

curl -X POST https://<Your_API_Gateway_REST_API>/resource/method -H 'Content-Type: application/json' -d '{"area":"300"}'

并接收以下输出响应:

"beagle"

让我们看看如何!

1.创建 Lambda 函数

在 AWS 中创建一个 Lambda 函数非常简单,只需几次点击。以下是您需要的代码:

在进入 AWS 控制台中的 Lambda 选项卡之前,首先确保记下您的 XGBoost 端点名称,您需要将它作为环境变量存储在 Lambda 函数中。

让我们创建 Lambda 函数

你可以在这里粘贴 lambda 代码:

接下来,输入 SageMaker 端点名称(您之前复制的)作为环境变量

接下来,我们需要给我们的 Lambda 函数权限来调用 SageMaker 端点。在权限下,单击执行角色:

我们将给予 Lambda 函数对 SageMaker 的完全访问权:

我们的 Lambda 函数现在知道要调用哪个端点,并且拥有相应的权限来这样做。是时候创建 REST API 了。

2.使用 API 网关创建一个 REST API

选择:

  • 休息
  • 新 API

给它一个名称,保留默认类型为区域点击:创建 API

接下来,我们需要创建资源作为 REST API 的一部分:

然后创建方法:

选择发布方法:

让我们将这个 REST API 指向我们在上一步中创建的 Lambda 函数(“demo1”):

现在我们将部署 API

复制 REST API url:

然后在发送请求时,确保将方法名(即 demo1)添加到复制的 API url 中

curl -X POST https://<Your_API_Gateway_REST_API>/test/demo1 -H 'Content-Type: application/json' -d '{"area":"300"}'**Output:**
"beagle"

最后的话

现在你知道了!现在,我们已经证明,我们的 SageMaker 模型已经成功地在互联网上公开,供每个人使用,如果我们有 React、Vue.js 或 Angular frontend 应用程序,它将能够提出类似的请求,并为用户提供完美的小狗推荐。

清理以停止产生成本!

1.通过在 Jupyter 笔记本中运行以下命令来删除已部署的端点

xgb_predictor.delete_endpoint()

2.停止 SageMaker 笔记本实例

感谢你的阅读,如果这篇文章帮你节省了时间或者解决了问题,一定要点击下面的按钮!总是非常感谢。

图像来源

[1]https://commons . wikimedia . org/wiki/File:Beagle _ puppy _ cadet . jpg

https://pxhere.com/en/photo/1003603

在五分钟内为 AWS Panorama 部署您的第一个计算机视觉应用

原文:https://towardsdatascience.com/deploy-your-first-computer-vision-app-for-aws-panorama-in-five-minutes-221e974606e3

了解如何以简单的方式设置您的第一个 Panorama 应用程序

瑞秋·麦克德莫特在 Unsplash 上的照片

大约一年前,我在这里发表了关于在 AWS Panorama 上部署物体探测器应用程序的数据科学分步指南。文章挺长的(17 分钟看完!)并涉及到许多微妙的步骤。使用官方工具创建 Panorama 应用程序并不容易:您应该使用固定且复杂的项目结构,记住许多关于项目的参数,并在每次调用它们时将它们传递给工具。然后,文件和文件夹必须以特定的方式命名,并且您必须在项目的不同部分维护相同信息(例如,深度学习模型属性)的一致副本。所有这些“内务”任务并没有交付真正的商业价值:它们仅仅是为了保持项目和样板代码的更新。

在过去的几个月里,我构建并部署了十几个 Panorama 应用程序,亲身经历了这种负担。显然,一些重复的任务和模式促使我尽可能自动化 Panorama 应用程序的设置和部署过程。我很高兴介绍 cookiecutter-panorama ,一个 AWS Panorama 项目生成器工具和构建系统。让我们深入探讨一下,从 AWS Panorama 应用程序的定义开始。

全景应用程序的剖析

如果你错过了之前的 ,AWS Panorama 是一个硬件和软件平台,可以运行计算机视觉(CV)应用程序,分析来自内部互联网协议(IP)摄像机的视频流。

Panorama 应用是打包的深度学习模型、业务逻辑代码和清单结构(也称为“应用图”)的集合,清单结构定义了这些组件之间的数据管道。

在“经典”全景应用程序的情况下,深度学习模型(在 Pytorch、Tensorflow 或 MXNet 中训练)由应用程序的构建过程打包在一个称为“模型资产”的工件中。类似地,您的业务逻辑代码(用 Python 编写,使用 Panorama 应用程序 SDK)被打包在“代码资产”工件中。这些工件被注册并上传到您的 AWS 帐户。每当您想要将 CV 应用程序部署到 Panorama 设备时,您必须提供引用这些工件的清单结构,并描述设备应该如何连接它们。

因此,Panorama 应用程序松散地耦合到一组深度学习和业务逻辑包,这些包通过清单文件绑定在一起。开发人员必须不断地同步应用程序清单文件和包中的引用。这是 cookiecutter-panorama 可以自动化的最大维护任务之一。

烹饪刀具

下面就来说说怎么做饼干吧!

安舒阿Unsplash 上拍照

Cookiecutter 是一个命令行实用程序,它从 Cookiecutter(项目模板)创建项目。它成为在 Python 生态系统中生成项目的事实上的标准,因为它漂亮简单的设计与引人注目的特性相匹配。目前,GitHub 上发布了超过 7000 个 cookiecutter 项目模板。Cookiecutter-panorama 将 panorama 应用程序模板添加到这个 pantheon 中。

开始吧!

首先,用 pip ( **pip install cookiecutter**)、conda ( **conda install -c conda-forge cookiecutter**)或 pipx ( **pipx install cookiecutter**,推荐方式)安装 cookiecutter 命令行工具。

SpaceXUnsplash 上拍摄的照片

创建 Panorama 应用程序项目非常简单:

**$ cookiecutter** [**https://github.com/mrtj/cookiecutter-panorama.git**](https://github.com/mrtj/cookiecutter-panorama.git)

模板引擎会问你一堆参数的值。但是,它也提供了合理的默认值,您可以在大多数情况下保留这些值并确认选项。

你只需要设置两个参数:project_names3_working_bucket。后者应该是您拥有读/写权限的帐户中现有 S3 存储桶的名称。你可以在 cookiecutter-panorama 的自述文件中找到参数的详细描述。

**$ cookiecutter** [**https://github.com/mrtj/cookiecutter-panorama.git**](https://github.com/mrtj/cookiecutter-panorama.git)
project_name [Panorama Video Processor]: ↲
project_slug [panorama_video_processor]: ↲
s3_working_bucket [my_bucket]: **my_real_bucket**
s3_working_path [s3://my_real_bucket/panorama_projects/panorama_video_processor]: ↲
camera_node_name [camera_input]: ↲
display_node_name [display_output]: ↲
code_package_name [panorama_video_processor_logic]: ↲
code_package_version [1.0]: ↲
code_asset_name [panorama_video_processor_logic_asset]: ↲
code_node_name [panorama_video_processor_logic_node]: ↲
model_package_name [panorama_video_processor_model]: ↲
model_package_version [1.0]: ↲
model_asset_name [panorama_video_processor_model_asset]: ↲
model_node_name [panorama_video_processor_model_node]: ↲
model_input_name [input0]: ↲
model_processing_width [224]: ↲
model_processing_height [224]: ↲

Cookiecutter 将在名为project_slug参数的目录中生成您的项目,在上面的例子中是panorama_video_processor:

**$ cd** **panorama_video_processor**

包含电池:构建系统

一旦生成了项目,您就可以开始与 Makefile 进行交互了。构建系统提供了一组丰富的功能:配置 git 存储库,构建应用程序容器和深度学习模型,将包上传到您的 AWS 帐户以准备部署到 Panorama 设备,在您的开发工作站上使用测试实用程序运行您的应用程序,防止您的 AWS 帐户 id 在公共 git 存储库中泄露,等等。有关功能的完整列表,请参考 Panorama 项目中生成的自述文件。这里是预览。

Unsplash 上拍照

cookiecutter-panorama 提供的一些任务您可以从中受益:

  • **make init-repo**:初始化一个 git 仓库
  • 安装所需的构建工具: aws-clipanorama-clidocker将 aws-cli 配置为注册 Panorama 设备的帐户的凭据。
  • **make import**:将项目导入您的 AWS 账户
  • **make build**:构建项目,创建一个虚拟的深度学习模型,以后可以用实际的模型替换。虚拟模型将使用模型包基础架构和 Panorama 设备的真实 GPU 计算输入视频帧的 RGB 通道的平均值。
  • **make package**:将编译好的应用容器和打包好的深度学习模型上传到你的 AWS 账号。这个脚本还将输出编译后的清单 JSON 的路径。

然后您可以拿起清单 JSON,前往 AWS Panorama 控制台并部署您的应用程序!

有问题吗?问题?建议? cookiecutter-panorama github 项目中打开一个问题,或使用以下联系人联系!

关于作者

Janos Tolgyesi 是 Neosperience 的 AWS 社区构建者和机器学习团队负责人,拥有 5 年多的 ML 技术专业知识和 8 年多的 AWS 经验。他喜欢构建东西,让它成为边缘的视频分析应用,或者基于点击流事件的用户分析器。你可以在 TwitterMediumLinkedIn 上找到我。

cookiecutter-panorama 开源项目得到了 Neosperience 的支持。

我要特别感谢卢卡·比安奇校对了这篇文章。

使用 GCP 的云在几分钟内将您的 ML 模型部署为 Web 服务

原文:https://towardsdatascience.com/deploy-your-ml-model-as-a-web-service-in-minutes-using-gcps-cloud-run-ee9d433d8787

使用 GCP 全面管理的无服务器服务分享您的 ML 模型

约翰-马克·史密斯在 Unsplash 上的照片

模型部署是 ML 项目生命周期中的关键步骤。有多种方式可以做到这一点,无论是在批处理或流处理管道或作为一个 web 服务。最后一个选项是本文的重点,我们将部署一个 ML 模型到 Cloud run,这是 Google Cloud 提供的一个服务,用于以无服务器的方式部署应用程序。

在这篇文章中,我们将分三步从一个项目想法到一个部署的服务:

  • 建立一个预测模型。
  • 将模型打包成 dockerized 服务。
  • 部署到 GCP。

这篇文章的代码可以在这里找到:【https://github.com/CVxTz/gcp_model_deploy_example

构建模型

首先,我们需要设置我们的 Python 开发环境。
我更喜欢使用 Anaconda 来创建新的虚拟环境:

conda create --name gcp_model_deploy python==3.8
conda activate gcp_model_deploy

然后,为了管理项目依赖,我使用了诗歌。你可以在这里找到如何在你的系统上安装它:https://python-poetry.org/docs/。这是一个非常酷的工具,它可以帮助我们定义 python 项目的依赖性,同时避免 pip 的一些缺点,例如有限的依赖性冲突解决方案。

在克隆了上面链接的存储库并创建了虚拟环境之后,我们可以使用以下命令安装项目的所有依赖项:

poetry install

现在,由于这个项目的重点不是集中在模型部分,我们将使用来自 SpaCy 的预训练的 NER 模型来检测句子中的实体。我们将 SpaCy 和“ en_core_web_md ”模型添加到我们的诗歌依赖关系中,这样我们就可以将该模型应用于如下句子:

import spacy

NLP = spacy.load("en_core_web_md")

def predict_entities(text):

    doc = NLP(text)

    entities = [
        {
            "text": entity.text,
            "label": entity.label_,
            "start_idx": entity.start_char,
            "end_idx": entity.end_char,
        }
        for entity in doc.ents
    ]

    return entities

例如,对于这句话:

On Halloween, Gotham City mayor Don Mitchell Jr. is murdered by a masked psychopath calling himself the Riddler.

预测函数将返回以下内容:

[
    {
        "text": "Halloween",
        "label": "**DATE**",
        "start_idx": 3,
        "end_idx": 12
    },
    {
        "text": "Gotham City",
        "label": "**GPE**",
        "start_idx": 14,
        "end_idx": 25
    },
    {
        "text": "Don Mitchell Jr.",
        "label": "**PERSON**",
        "start_idx": 32,
        "end_idx": 48
    },
    {
        "text": "Riddler",
        "label": "**PERSON**",
        "start_idx": 104,
        "end_idx": 111
    }
]

我们现在已经完成了模型部分!

将模型打包成一个 dockerized 服务

该服务:

我们将把预测函数包装成一个 FastApi 端点。

@app.post("/predict/", response_model=Response)
def create_item(in_query: Query):
    entities = predict_entities(in_query.text)

    return Response(text=in_query.text, entities=[Entity(**x) for x in entities])

因此,要获得预测,我们只需要调用/predict/ endpoint。
本地运行 API 可以从项目根目录使用以下命令来完成:

poetry run uvicorn --app-dir ner_app app:app --host 0.0.0.0 --port 8080 --workers 2

或者运行“make run_app”

然后,我们可以在 http://0.0.0.0:8080/docs 访问 API 的 swagger 页面

下一步是将这个应用程序打包成 docker 映像,这样我们就可以在任何地方运行它。

码头工人:

我们将使用 python:3.8-slim 作为我们的基础映像,然后运行一个两阶段构建,以便最终得到一个更小的映像大小:

FROM python:3.8-slim as *builder* WORKDIR "/app"

ENV *PYTHONFAULTHANDLER*=1 \
    *PYTHONHASHSEED*=random \
    *PYTHONUNBUFFERED*=1

ENV *PIP_DEFAULT_TIMEOUT*=100 \
    *PIP_DISABLE_PIP_VERSION_CHECK*=1 \
    *PIP_NO_CACHE_DIR*=1 \
    *POETRY_VERSION*=1.1.13

RUN pip install "poetry==$*POETRY_VERSION*"
RUN python -m venv /venv

COPY pyproject.toml poetry.lock ./
COPY ner_app ner_app

RUN . /venv/bin/activate && poetry install --no-dev --no-root
RUN . /venv/bin/activate && poetry build

FROM python:3.8-slim as *final* WORKDIR "/app"

COPY --from=*builder* /venv /venv
COPY --from=*builder* /app/dist .
COPY ner_app ner_app

RUN . /venv/bin/activate && pip install *.whl

CMD ["/venv/bin/python", "-m", "uvicorn", "--app-dir", "ner_app", "app:app", "--host", "0.0.0.0", "--port", "5000", "--workers", "2"]

第一阶段构建 python 虚拟环境,第二阶段只是复制它并使用它来运行 API。

现在,我们可以使用以下内容构建映像:

docker build . -t ner_app:0.0.1

然后使用以下命令运行它:

docker run -p 5000:5000 -i -t ner_app:0.0.1

该应用程序可在 http://0.0.0.0:5000/docs 下载

部署到 GCP

GCP 设置:

这里,我假设您已经有了一个 GCP 账户,您在那里创建了一个项目,并在您的开发环境中安装和验证了 gcloud CLI。

现在,我们需要激活 cloudbuild 功能,这样我们就可以轻松地构建 docker 映像并将其推送到 GCR (Google 容器注册表)。

您可以在 GCP 控制台中查找并启用该功能。这允许我们远程构建 docker 映像,然后将其推送到 GCR。

推送图像:

使用以下命令可以轻松构建映像并将其推送到 GCR:

PROJECT_ID := $(shell gcloud config get-value project)
HOSTNAME := eu.gcr.io
GCR_TAG := ${HOSTNAME}/${PROJECT_ID}/${APP_NAME}:${VERSION}
APP_NAME := ner_apprun_grc_build:
   echo "${GCR_TAG}"
   gcloud builds submit --tag ${GCR_TAG} -q

所以现在,我们可以运行“make run_grc_build”来构建并推送 dockerized API 到 GCP。

我们可以在这里找到我们推送的图像:

部署服务:

将服务部署到云运行也可以通过一个命令来完成:

PROJECT_ID := $(shell gcloud config get-value project)
HOSTNAME := eu.gcr.io
GCR_TAG := ${HOSTNAME}/${PROJECT_ID}/${APP_NAME}:${VERSION}
APP_NAME := ner_appcloud_run_deploy:
   gcloud run deploy ner-app --image=${GCR_TAG} \
--max-instances=2 --min-instances=0 --port=5000 \
--allow-unauthenticated --region=europe-west1 \
--memory=2Gi --cpu=4 -q

我们只需运行“make cloud_run_deploy ”,即可在不到一分钟的时间内将应用部署到云上。

Cloud run 是一种无服务器服务,所以如果没有流量,它可以缩小到零,在这种情况下,我们不必支付任何费用。这就是为什么我们把最小实例=0
该服务还可以根据 CPU 利用率自动扩展到 x 个实例。
我们还指定了一些其他参数,比如 memory=2Gi,因为默认值 512 Mib 不够用。

我们现在可以访问返回的 URL 来测试我们的 API:

Cloud run 还提供了一些有用的指标来监控您的 web 服务的健康状况:

注意:您可以使用 locust+GitHub 存储库中可用的 locustfile 对您部署的服务进行负载测试。

可以进一步调整部署配置,以改善 NER 应用程序的延迟和吞吐量。

结论

在这篇文章中,我们看到了如何使用 Cloud run 轻松地将机器学习模型部署到 Google 云平台。这项服务有助于降低模型部署的成本,因为我们只需为我们使用的服务付费,这对于负载变化很大的模型来说是一个很好的选择。

遵循教程的代码可以在这里找到:https://github.com/CVxTz/gcp_model_deploy_example

如果你有任何问题,欢迎在 Github 上发表评论或提出问题!

在 AWS 上部署数据科学平台:并行化实验(第三部分)

原文:https://towardsdatascience.com/deploying-a-data-science-platform-on-aws-parallelizing-experiments-part-iii-774d972aabba

数据科学云基础架构

使用开源软件在 AWS 上部署数据科学平台的分步指南

照片由克里斯里德Unsplash 上拍摄

在我们之前的帖子中,我们配置了 Amazon ECR 来将 Docker 映像推送到 AWS,并配置了一个 S3 桶来写入我们的数据科学实验的输出。

在这最后一篇文章中,我们将向您展示如何使用 PloomberSoopervisor 来创建可以在 AWS 批处理上并行运行的实验网格,以及如何动态请求资源(CPU、RAM 和 GPU)。

嗨!我的名字是爱德华多,我喜欢写所有的东西。如果您想了解我的最新内容。在 MediumTwitter 上关注我。感谢阅读!

这是我们建筑的样子:

平台的架构。图片作者。

使用aws CLI 进行身份验证

我们将再次使用aws CLI 来配置基础架构,因此请确保您已经过身份验证并且拥有足够的权限:

检查码头工人

这部分我们将使用 Docker,因此请确保它已启动并运行:

创建 Amazon ECR 存储库

首先,让我们创建另一个 ECR 存储库来托管我们的 Docker 映像:

输出:

REPOSITORY变量赋给前面命令的输出:

获取示例代码

我们现在将获得一个示例项目。首先,让我们安装所需的软件包。

注意:我们建议您在虚拟环境中安装它们。

grid目录下下载示例:

输出:

这下载了一个完整的项目:

输出:

我们下载的示例准备了一些数据,并并行训练了十几个机器学习模型,下面是一个图形表示:

我们工作流程的图形表示。图片作者。

让我们看一下pipeline.yaml文件,它指定了我们工作流中的任务:

输出:

pipeline.yaml是 Ploomber 用来描述计算工作流的一个接口(你也可以用 Python 来声明它们)。

任务部分包含五个条目,每个任务一个。前四个是处理一些输入数据的 Python 函数(tasks.raw.gettasks.features.sepaltasks.features.petaltasks.features.features),最后一个是适合一个模型的脚本(scripts/fit.py)。

注意,最后一个条目更长,因为它是一个网格任务:它将使用相同的脚本,并使用不同的参数执行多次。总的来说,该脚本将被执行 12 次,但这可能是一个更大的数字。

要了解更多关于pipeline.yaml文件和 Ploomber 的信息,查看我们的文档

现在让我们将 AWS Batch 配置为我们的云环境(也支持 Kubernetes、SLURM 和 Airflow)。

配置项目以在 AWS 上运行

输出:

========================= Loading DAG =========================
No pipeline.aws-env.yaml found, looking for pipeline.yaml instead Found /Users/Edu/dev/ploomber.io/raw/ds-platform-part-iii/grid/pipeline.yaml.
Loading...
Adding /Users/Edu/dev/ploomber.io/raw/ds-platform-part-iii/grid/aws-env/Dockerfile...
============================= Done ============================
Fill in the configuration in the 'aws-env' section in soopervisor.yaml then submit to AWS Batch with: soopervisor export aws-env
Environment added, to export it:
$ soopervisor export aws-env
To force execution of all tasks:
$ soopervisor export aws-env --mode force

我们需要配置一些额外的东西,为了便于设置,我们创建了一个脚本,可以根据您的 AWS 基础架构自动执行这些任务,让我们下载它:

输出:

现在,为您想要使用的 AWS 批处理作业队列和工件存储桶设置值。(如果有疑问,您可能想重温以前的教程:第一部分第二部分)。

让我们生成配置文件,指定要使用的作业队列和 ECR 存储库来上传我们的代码:

输出:

现在,让我们指定 S3 客户端,以便将管道的输出上传到 bucket:

输出:

修改pipeline.yaml,使其使用我们在上一步中创建的客户端:

将项目上传到 ECR 存储库

让我们将我们的项目上传到 ECR:

输出:

确保boto3作为我们项目的一部分安装。我们需要上传到 S3:

在 AWS 批处理中执行作业

我们现在准备好安排我们的工作流程了!让我们使用soopervisor export命令构建 Docker 映像,将其推送到 ECR,并在 AWS 批处理上调度作业:

您可以在 AWS 批处理控制台中监控执行。或者使用下面的命令,只要确保您更改了作业名称。以下命令检索fit-random-forest-1-gini任务的状态:

输出:

几分钟后,所有的任务都应该执行了!

检查输出

让我们检查一下 S3 桶中的输出:

输出:

您可以看到这里有一个.pickle文件(经过训练的模型).csv(经过处理的数据)和.html(从训练脚本生成的报告)的组合。

让我们下载其中一份报告:

输出:

打开report.html,你会看到训练脚本的输出!

请求更多资源

让我们来看看配置云环境的grid/soopervisor.yaml文件:

输出:

soopervisor.yaml文件指定要使用的后端(aws-batch)、默认使用的资源({memory: 16384, vcpus: 8})、作业队列、区域和存储库。

我们可以添加一个新的部分来指定每个任务的资源,以覆盖默认值:

结束语

在这最后一部分,我们展示了如何创建多步骤工作流,以及如何参数化脚本来创建可以并行运行的实验网格。现在,您有了一个可扩展的基础架构来运行数据科学和机器学习实验!

如果您需要帮助定制基础架构或想要分享您的反馈,请加入我们的社区

与我们的内容保持同步;在 TwitterLinkedIn上关注我们,订阅我们的时事通讯

尾声:清理基础设施

下面是您需要运行的命令,以删除我们在这篇文章中创建的 ECR 存储库。要删除所有基础结构,请重新访问前面的教程。

输出:

最初发布于ploomber . io

在 AWS 上部署数据科学平台:运行容器化实验(第二部分)

原文:https://towardsdatascience.com/deploying-a-data-science-platform-on-aws-running-containerized-experiments-part-ii-bef0e22bd8ae

数据科学云基础架构

使用开源软件在 AWS 上部署数据科学平台的分步指南

Guillaume BolducUnsplash 上拍摄的照片

在我们的上一篇文章中,我们看到了如何配置 AWS 批处理,并通过执行一个启动容器、等待 3 秒钟然后关闭的任务来测试我们的基础设施。

在本文中,我们将利用现有的基础设施,但这一次,我们将执行一个更有趣的例子。

我们将通过构建一个容器并将其存储在 Amazon ECR 中来将我们的代码发送到 AWS,这是一个允许我们存储 Docker 图像的服务。

如果你想及时了解我的数据科学内容。在媒体推特上关注我。感谢阅读!

使用aws CLI 进行身份验证

我们将再次使用aws CLI 来配置基础架构,因此请确保您已经过身份验证并且拥有足够的权限:

检查码头工人

这部分我们将使用 Docker,因此请确保它已启动并运行:

输出:

创建亚马逊 ECR 存储库

我们首先创建一个存储库,它将托管我们的 Docker 映像:

创建 ECR 存储库:

输出:

上面的命令将打印存储库 URI,将其赋给下一个变量,因为我们以后会用到它:

获取一些样本代码

我们现在将使用两个开源工具( PloomberSoopervisor )来编写我们的计算任务,生成 Docker 映像,将其推送到 ECR,并在 AWS 批处理中调度一个作业。

让我们安装软件包:

注意:我们建议您在虚拟环境中安装它们。

我们来举个例子。这个例子训练和评估一个机器学习模型:

输出:

让我们看看文件:

输出:

该结构是典型的 Ploomber 项目。Ploomber 允许您轻松地将计算工作流组织为函数、脚本或笔记本,并在本地执行它们。要了解更多信息,请查看 Ploomber 的文档。

另一方面,soobervisor允许您导出一个 Ploomber 项目并在云中执行它。

下一个命令将告诉 Soopervisor 创建必要的文件,以便我们可以导出到 AWS 批处理:

输出:

===================Loading DAG===================
No pipeline.aws-env.yaml found, looking for pipeline.yaml instead
Found /Users/Edu/dev/ploomber.io/raw/ds-platform-part-ii/example/pipeline.yaml.
Loading... Adding /Users/Edu/dev/ploomber.io/raw/ds-platform-part-ii/example/aws-env/Dockerfile... 
===================Done===================
Fill in the configuration in the 'aws-env' section in soopervisor.yaml then submit to AWS Batch with: soopervisor export aws-env
Environment added, to export it: $ soopervisor export aws-env
To force execution of all tasks: $ soopervisor export aws-env --mode force

soopervisor add将创建一个soopervisor.yaml文件和一个aws-batch文件夹。

aws-batch文件夹包含一个Dockerfile(我们需要它来创建一个 Docker 映像):

输出:

soopervisor.yaml文件包含配置参数:

输出:

这里我们必须配置一些参数,我们创建了一个小脚本来生成配置文件:

  • job_queue:作业队列的名称
  • aws_region:您的 AWS 批处理基础设施所在的区域
  • repository:URI 集控室

以下是我的基础架构的价值(用您的替换):

注意:如果您没有作业队列名称,您可以从 AWS 控制台 获取(确保您在正确的区域)。

让我们下载一个实用程序脚本来帮助创建配置文件:

输出:

创建soopervisor.yaml配置文件:

输出:

文件看起来是这样的:

输出:

现在让我们使用soopervisor export来执行 AWS 批处理中的命令。这样的命令将为我们做几件事:

  • 构建 Docker 容器
  • 将其推送到亚马逊 ECR 存储库
  • 将作业提交给 AWS 批处理

我们需要安装boto3,因为它是向 AWS 批处理提交作业的依赖项:

通过 Amazon ECR 认证,这样我们就可以推送图片:

输出:

现在让我们导出项目。请记住,该命令需要几分钟时间:

如果一切顺利,您将看到类似这样的内容:

如果您在使用soopervisor export命令时遇到问题,或者无法推送至 ECR,请加入我们的社区,我们将帮助您!

一旦命令执行完毕,作业将被提交给 AWS 批处理。让我们使用aws CLI 列出提交到队列的作业:

输出:

一分钟后,你会看到任务显示为SUCCEEDED(如果没有完成,会显示为RUNNABLESTARTINGRUNNING)。

然而,有一个问题:AWS 批处理运行了我们的代码,但是不久之后,它关闭了 EC2 实例,因此,我们不再能够访问输出。

为了解决这个问题,我们将在项目中添加一个 S3 客户端,这样所有的输出都会被存储。

创建一个 S3 存储桶来存储输出

让我们首先在 S3 创建一个桶。S3 桶名必须是唯一的,您可以在您的终端中运行以下代码片段,或者选择一个唯一的名称并将其赋给BUCKET_NAME变量:

输出:

创建存储桶:

输出:

向我们的管道添加客户端

Ploomber 允许我们指定一个 S3 桶,它会负责为我们上传所有输出。我们只需要创建一个短文件。generate.py脚本可以为我们创建一个:

输出:

我们需要配置我们的pipeline.yaml文件,以便它上传工件到 S3。让我们使用generate.py文件,让它为我们做这件事:

此外,让我们将boto3添加到我们的依赖项中,因为我们将调用它来将工件上传到 S3:

授予 AWS 批处理权限以访问存储桶

让我们将 S3 权限添加到 AWS 批处理任务中。生成策略:

输出:

应用它:

执行工作负荷

我们现在准备在 AWS 批处理上执行我们的任务!

让我们确保我们能够推进 ECR:

输出:

再次提交任务:

注意,这一次,soopervisor export命令快了很多,因为它缓存了我们的 Docker 图像!

让我们检查任务的状态:

输出:

一分钟后,您应该会看到它是SUCCEEDED

检查我们的 bucket 的内容,我们将看到任务输出(一个.parquet文件):

输出:

包装完毕

在这篇文章中,我们学习了如何上传代码并通过 Docker 映像在 AWS 批处理中执行它。我们还配置了 AWS Batch 来读写 S3 存储桶。有了这种配置,我们可以开始以可扩展的方式运行数据科学实验,而不用担心维护基础架构!

在本系列的下一篇(也是最后一篇)文章中,我们将看到如何轻松地生成数百个实验并检索结果。

如果你想第一个知道最终部分什么时候出来;在 TwitterLinkedIn上关注我们,订阅我们的简讯

尾声:清理基础设施

如果你想删除我们在这篇文章中创建的基础设施,这里有一些命令。

删除 ECR 存储库:

输出:

删除 S3 存储桶:

输出:

最初发布于ploomber . io

在 AWS 上部署数据科学平台:设置 AWS 批处理(第一部分)

原文:https://towardsdatascience.com/deploying-a-data-science-platform-on-aws-setting-up-aws-batch-part-i-da4a7566db7e

数据科学云基础架构

使用开源软件在 AWS 上部署数据科学平台的分步指南

你的笔记本电脑不够用,我们用云吧。由 CHUTTERSNAPUnsplash 上拍摄的照片

在这一系列教程中,我们将向您展示如何使用 AWS 和开源软件部署数据科学平台。到本系列结束时,您将能够通过一个命令向 AWS 可伸缩基础设施提交计算任务。

我们将部署的数据科学平台的体系结构。图片作者。

什么是 AWS 批次?

AWS 批处理控制台的屏幕截图,显示了我们最近的作业。图片作者。

为了实现我们的平台,我们将使用几个 AWS 服务。但是,中心的是 AWS Batch。

AWS Batch 是针对计算作业的托管服务。它负责维护作业队列、启动 EC2 实例、运行我们的代码和关闭实例。它根据我们提交的作业数量而上下调整。这是一个非常方便的服务,允许我们以可扩展的方式执行代码,并为计算密集型作业(例如,具有许多 CPU 和大内存的实例)请求定制资源,而不需要我们维护集群(不需要使用 Kubernetes!).

我们开始吧!

安装aws命令行界面

本教程的唯一要求是安装 AWS 命令行界面(以及有足够权限使用该工具的访问键)。遵循安装说明。如果您有问题,请在我们的空闲时间寻求帮助。

验证您的安装(确保您运行的是版本 2):

输出:

然后使用您的访问密钥进行身份验证:

网络配置

我们需要为运行我们任务的 EC2 实例创建一个 VPC(虚拟私有云),这一部分包含了配置 VPC 所需的所有命令。

请注意,所有 AWS 帐户都有一个默认的 VPC。如果您想使用它,请确保您有想要使用的子网 id 和安全组 id,并跳过这一部分。

如果你需要帮助,请随时向我们询问关于 Slack 的任何事情。

让我们创建一个新的 VPC,并检索 VPC ID:

输出:

让我们将 ID 分配给一个变量,这样我们就可以重用它(用您的 ID 替换它):

现在,让我们创建一个子网并获取子网 ID:

输出:

并将 ID 赋给一个变量(用您的 ID 替换):

我们需要修改子网的配置,以便每个实例获得一个公共 IP:

现在,让我们配置互联网接入:

输出:

将网关 ID 分配给以下变量(用您的 ID 替换):

让我们将互联网网关连接到我们的 VPC:

文档更详细地解释了上述命令。

请注意,允许实例访问互联网简化了网络设置。但是,如果您不希望 EC2 实例拥有公共 IP,您可以配置 NAT 网关。

现在,我们通过添加路由表来完成子网的配置:

输出:

分配路由表 ID(用您的 ID 替换):

让我们添加一个与我们的互联网网关相关联的路由:

输出:

并将该表与子网相关联:

输出:

最后,在我们的 VPC 中创建一个安全组:

输出:

并分配安全 ID(用您的 ID 替换):

配置权限

我们现在需要创建一个角色来允许 AWS Batch 调用 ECS(另一个 AWS 服务)。

下载配置文件:

输出:

创建角色:

输出:

创建实例配置文件:

输出:

将角色添加到实例配置文件:

附加角色策略:

配置计算环境

配置好网络和权限后,我们现在就可以配置计算环境了!

在 AWS Batch 中,计算环境决定了为我们的作业使用哪些实例类型。

我们创建了一个简单的脚本来生成您的配置文件:

输出:

运行脚本并传递子网和安全组 id:

输出:

您也可以编辑my-compute-env.json文件,将您的子网 id 放入subnets列表,将您的安全组 id 放入securityGroupIds列表。如果您需要针对您的计算环境进行更多定制,请加入我们的 Slack ,我们将为您提供帮助。

创建计算环境:

输出:

为了提交作业,我们需要创建一个作业队列。该队列将接收作业请求,并将它们路由到相关的计算环境。

注意:在运行下一个命令之前等待几秒钟,因为创建计算环境可能需要一点时间。

下载文件:

输出:

创建作业队列:

输出:

让我们测试一下一切是否正常!

我们定义了一个等待几秒钟并完成的示例作业:

输出:

让我们向队列提交一个作业:

输出:

让我们确保工作成功执行。复制执行命令时打印的jobId,并将其传递给下一条命令:

输出:

第一次运行上述命令时,您很可能会看到:RUNNABLE,这是正常的。

AWS Batch 会启动新的 EC2 机器,并在您的工作完成后关闭它们。这很好,因为它可以防止机器空转,继续计费。然而,由于新机器每次都在旋转,这引入了一些启动时间开销。等待一分钟左右,再次运行该命令,您应该很快就会看到STARTINGRUNNINGSUCCEEDED

如果超过几分钟后作业仍停留在RUNNABLE状态,请在我们的社区寻求帮助。

收尾

在这篇博文中,我们配置了 AWS Batch,这样我们就可以按需提交计算任务。不需要维护集群或者手动启动和关闭 EC2 实例。您只需为您提交的作业付费。此外,AWS Batch 是高度可伸缩的,因此您可以提交任意多的作业!

在下一篇文章中,我们将向您展示如何向 AWS Batch 提交一个自定义容器作业,并配置一个 S3 桶来读取输入数据和写入结果。

如果你想第一个知道第二部什么时候出来;在 TwitterLinkedIn上关注我们,订阅我们的简讯

尾声:清理基础设施

除 EC2 使用外,使用 AWS 批处理不收费。但是,如果您想要清理您的环境,请遵循以下步骤。

禁用 AWS 批处理队列和计算环境:

输出:

更新计算环境:

输出:

您需要等待 1-2 分钟,让队列和计算环境显示为DISABLED

删除队列和计算环境:

删除 VPC 及其组成部分:

删除 IAM 角色:

aws iam remove-role-from-instance-profile --instance-profile-name \ ploomber-ecs-instance-role \ --role-name ploomber-ecs-instance-role aws iam delete-instance-profile --instance-profile-name ploomber-ecs-instance-roleaws iam detach-role-policy --role-name ploomber-ecs-instance-role \
    --policy-arn arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Roleaws iam delete-role --role-name ploomber-ecs-instance-role

关于作者

嗨!我叫爱德华多,我喜欢写关于数据科学的所有东西。如果您想了解我的最新内容。在媒体推特上关注我。感谢阅读!

最初发表于 ploomber.io

在 Amazon SageMaker 上部署预先训练的 Sklearn 模型

原文:https://towardsdatascience.com/deploying-a-pre-trained-sklearn-model-on-amazon-sagemaker-826a2b5ac0b6

将本地培训的模型投入生产

图片来自穆罕默德·阿里·皮克拍摄的 Unsplash

我曾经写过如何在亚马逊 SageMaker 上训练和部署定制的 SklearnTensorFlow 模型。但是,对于某些用例,您可能有在其他地方培训过的预培训模型。在本文中,我们将探索如何获取预先训练好的模型数据,并将其部署到 SageMaker 上。此外,对于这个例子,我们将主要在本地环境中处理代码,以确保您不会在 AWS 控制台中有太多繁重的工作。

注意:对于那些刚接触 AWS 的人来说,如果你想跟进的话,请确保你在下面的 链接 做了一个账户。本文将假设一个新手对 AWS 和 SageMaker 的知识达到中级水平。我们也不会探究模型构建理论,本文的主要焦点将放在部署上。

目录

  1. 设置
  2. 模型部署脚本
  3. 模型调用
  4. 其他资源和结论

1.设置

首先,让我们快速地在本地训练一个可用于部署的 Sklearn 模型。您可以简单地运行以下 Python 脚本进行设置。

本地模型训练

这个脚本的主要关键是我们使用 joblib 模块来保存训练好的模型。您应该看到这个工件显示在您的本地目录中。

局部模型神器(作者截图)

有必要使用 joblib 来保存模型,因为这是 SageMaker 对 Sklearn 模型的预期格式。除了我们的本地模型脚本,我们可以提前提供的一个脚本是我们的推理脚本。这有助于 SageMaker 理解您的模型服务的输入和输出将如何配置。您可以提供四种默认功能:

  1. model_fn :这将反序列化并加载我们的 joblib 文件。
  2. input_fn :您可以传入您的模型期望输入的数据格式(json、csv 等)。
  3. predict_fn :我们的模型预测功能。
  4. output_fn :处理 predict_fn 的返回值以及端点将得到的响应类型。

推理处理函数

下一步是在 AWS 控制台中创建一个合适的 SageMaker IAM 角色。在这里,您需要为 SageMaker 提供适当的权限:S3 完全访问、SageMaker 完全访问和 ECR 完全访问。对于本例,这应该是您在 AWS 控制台中的主要工作。一旦您创建了这个角色,我们就可以专注于构建我们的模型部署脚本了。

2.模型部署脚本

首先,我们的模型部署脚本有以下导入。确保这个脚本与您的推理处理程序和模型数据(joblib)在同一个目录中。

进口

我们与 AWS 服务的大部分协调工作都是通过 SDK 完成的,在这里是 Python SDK: Boto3 。我们与 SageMaker 的 Boto3 客户端合作,协调我们的模型部署步骤。

SageMaker 设置

在这里,我们实例化了 SageMaker 的客户端和 S3 客户端,我们将在那里存储我们的模型数据,以便 SageMaker 访问。下一步是关键部分,SageMaker 需要一个model.tar.gz格式的模型工件/数据。为此,我们将把本地模型工件和 inference.py 脚本压缩到一个 tar 文件中。

创造 model.tar.gz

model.tar.gz 本地保存(作者截图)

接下来,SageMaker 要理解我们需要将这个模型工件放在 S3 的一个位置。

将模型数据上传至 S3

现在我们可以关注在 SageMaker 上构建端点的三个步骤。

  1. 模型创建
  2. 端点配置创建
  3. 端点创建

对于 SageMaker 模型创建,我们需要两个特性:模型数据和容器图像。在这种情况下,我们可以使用 SageMaker SDK 从 SageMaker 直接检索 sk learn 图像进行推理。

检索 Sklearn 图像

现在我们可以提供模型数据和图像来创建我们的 SageMaker 模型。

SageMaker 模型创建

现在我们可以使用 SageMaker 模型来创建我们的端点配置,在这里我们可以指定我们的实例类型和我们想要的端点数量。

SageMaker 端点配置创建

现在,我们可以开始创建我们的端点,这将需要几分钟的时间来成功创建。

创建端点

如果您现在运行该脚本,您将看到一个端点成功创建,并且在您的 AWS 控制台中也是可见的。

端点创建(作者截图)

3.模型调用

我们现在可以创建一个单独的调用文件,用一个样本点来测试我们的端点。我们不想将它添加到主文件中,因为每次执行都会创建一个新的端点。获取您的端点名称,并在下面的脚本中指定它,您将看到执行。

调用端点

结果(作者截图)

4.其他资源和结论

https://github.com/RamVegiraju/Pre-Trained-Sklearn-SageMaker

有关示例的完整代码,请访问上面的链接。理解了模型数据的格式和结构之后,在 SageMaker 上部署预先训练好的模型就非常简单了。需要发生的主要变化是您使用 SDK 检索的图像,这应该与您正在使用的框架相匹配。如果 SageMaker 不支持容器,请查看如何自带容器

额外资源

萨格马克推论的例子

带 SageMaker 的多模型 TensorFlow 端点

如果你喜欢这篇文章,请在LinkedIn上与我联系,并订阅我的媒体 简讯 。如果你是新来的中号,用我的 会员推荐 报名吧。

以 Kedro 的方式部署推荐系统

原文:https://towardsdatascience.com/deploying-a-recommendation-system-the-kedro-way-7aed36db7cef

使用 Kedro 和 MLFlow 创建推荐系统管道的教程

我们的管道中有需要定制的东西。就像这辆丰田 Supra。照片由 Garvin St. VillierPexels 拍摄

推荐系统是现代互联网不可或缺的一部分。如果你没有以某种方式为客户提供个性化的服务,他们就无法享受数字体验。甚至出现了“超个性化”一词,这是人工智能的高级能力,通过利用我们的数字面包屑为用户提供最相关的项目。用技术术语来说,这是将各种数据集(点击流、评级、文本、图像、商品本身等)尽可能实时地输入到机器学习算法中,以提供动态推荐体验。

在我之前的文章中,我介绍了 MLOps 以及 Kedro 如何成为实现模块化、可维护和可复制管道的首选框架。推荐者受益于 MLOps,因为需要快速试验、持续再培训和模型部署。成功的公司甚至使用先进的 A/B 测试技术同时部署几个模型(参见网飞实验平台)。因此,我认为分享我的经验将是令人兴奋的,我把一个推荐系统从工程、培训,一直带到服务。

为了演示工作流,我将使用 MovieLens 10M ,这是一个推荐者意义上的中等大小的数据集。就交互数据而言,它也相当密集,但这是一个很好的练习。此外,它还包含可用作项目功能的标签。端到端流程的独特之处在于,它不仅仅服务于 MLFlow 的 scikit-learn 模型。有一些定制要做。但在此之前,让我们从用户 API 需求开始。

注意,我假设读者已经熟悉协同过滤和最近邻索引。

如果你想跳过所有内容,直接到这里看我的代码。

用户 API

我们将处理两种情况,传入用户已经是系统的一部分(已知用户),传入用户未知(冷启动)。我们将有点独特地模拟我们的 API 响应实时浏览会话的情况。因此,我们将收到与他们在前一个小时左右浏览的内容相对应的商品 id。

已知用户案例

  • 我们的用户已经登录并浏览了几个项目。
  • 我们希望推荐与用户浏览的内容最接近的项目。
  • 我们的 API 接收用户 id 和商品 id 对。

冷启动案例

  • 这是我们第一次见到这个用户(或者他还没有登录)
  • 同上,我们推荐最接近的项目
  • 我们的 API 只接收项目 id

我们还可以有第三种情况,我们只接收用户 id。我们可以为培训产生的候选人服务。这对读者来说是一个很好的练习!

工作流程概述

Kedro 被用作我们的工作流框架。算法方面,我们将使用 LightFM 和 WARP loss。我们将使用生成的项目嵌入来产生我们最接近的项目。我们使用 Optuna 进行超参数优化。为了创建一个快速的索引服务,我们使用来构建我们的近似最近邻索引。最后,我们使用 MLFlow 来跟踪实验,存储工件,并为我们的模型服务。为了集成到 Kedro,我们使用 kedro-mlflow

为了满足我们的用户 API 需求,我们将使用 ANNOY 来查找与查询项目最接近的项目。这就是所谓的候选人选择。如果用户是一个已知的用户,那么我们更进一步,通过用户嵌入和被查询项目嵌入的点积来排列最接近的项目。如果用户是未知的,那么我们简单地使用默认的排名——流行度和项目年龄。这就是所谓的候选人排名。在一些工作流中,有另一种模型根据当前会话(时间、位置、用户的其他行为)对候选人进行排名。但是在本教程中,我们将简单地使用上述内容。

下图说明了工作流程。这很简单,因为网飞规模的系统可能会令人望而生畏!这是由 kedro-viz 生成的。绿色方框是我粘贴的评论。

作者图片

准备评分和准备项目功能

我将评级和项目特征从熊猫数据帧格式转换成稀疏格式。存储映射到其位置索引的用户和项目 id(“cid”是项目 ID,“rid”是用户 ID)。还存储了默认的排名和名称映射。想象这个阶段:

作者图片

在 Kedro 的 catalog.yaml 中,我已经将所有映射(dict 类型)定义为 pickled 对象。排名是一个 CSV 文件。

因数分解

在数据工程阶段之后,我们训练我们的模型。我们将数据分为训练和测试,并运行 LightFM。然后,我们产生我们的嵌入、偏差和模型度量。最后,我们抽样推荐。在真实的场景中,我们还可以在这里包括一些健全性检查和可视化,以尽可能多地实现自动化。

作者图片

如前所述,Optuna 用于超参数优化。为了集成到 MLFlow,我们使用回调。这实际上导致了一个新的 MLFlow 实验,不同于 kedro-mlflow 创建的实验。然而,最初的实验将存储最佳模型的参数,以及稍后将显示的所有工件。

索引

我们的最后一个阶段是使用项目因子来索引我们最近的邻居。这将确保为我们查询的项目提供快捷的服务。在下面的工作流中,validate_index 是我们的节点,用于在训练过程中立即测试我们创建的索引。

作者图片

这是我们第一次定制 Kedro 的地方。来自 aroy 库的对象不能被 pickled,所以我们需要为数据目录定制加载和保存功能。我们实现了 Kedro 的AbstractDataset ,并像下面这样使用它。

上传至 MLFlow

如果我们一切都正确,那么我们将把我们生成的每个工件上传到 MLFlow,因为它将用于模型服务。为了简单起见,我们将留在本地文件系统中。我们使用MlflowModelLoggerDataSet来保存工件,并在 MLFlow 标准下定义了另一个定制类KedroMLFlowLightFM。这将定义 MLFlow 将如何加载模型和处理输入。

这里发生了几件事。在第一个片段中,我们定义了上传到 MLFlow 工件存储库的文件。它看起来非常难看,因为它充满了临时文件作为上传过程的暂存区。我很想知道你对此是否有更好的方法。接下来,在第二个片段中,我们定义了KedroMLFlowLightFM。这将告诉 MLFlow 将如何存储和服务模型。这非常重要,因为这将决定mlflow model serve如何工作。请注意,预测功能捕获了我们的用户需求。

运行管道

既然管道的关键组件已经完成,让我们运行它。这里需要注意一些事情:

  1. 准备功能——我们在这里运行 1000 万部电影的一个非常小的子集。在本演示中,这是为了缩短周期时间。
  2. 训练——我们使用 Optuna 来优化嵌入的维度。
  3. 索引—返回值的类型为KedroAnnoyIndex
  4. 推荐范例——这里有一些有趣的东西值得一看。
  • 乍一看,《怪物史莱克》不应该接近《黑暗骑士》(风格迥异),但两者都是主流大片,所以算法可能已经注意到了这一点。
  • 《料理鼠王的近邻》中还包括其他动画电影,如《怪物公司》,甚至包括《龙猫邻居》这样的国际电影。
  • 《罗生门》是一部永恒的电影,看到它接近老式电影(马耳他之鹰,大睡)和其他大脑电影(迷失的高速公路,玫瑰的名字)很酷。
**> kedro run** 
-- a lot of things get logged here so this is abbreviated
**-- (1) Prep Features** 
Running node: prep_ratings: 
prep_sparse_ratings([ratings,params:preprocessing]) -> [interactions,rid_to_idx,idx_to_rid,cid_to_idx,idx_to_cid] Number of users: 5387 
Number of items: 2620 
Number of rows: (195359, 4) 
Sparsity: 0.013841563730609597 **-- (2) Training kedro.pipeline.node** 
Running node: factorize: factorize_optimize([train,test,eval_train,sp_item_feats,params:model]) -> [user_factors,item_factors,user_biases,item_biases,model_metrics] Train: 0.20478932559490204, Test: 0.1860404759645462 
Train: 0.24299238622188568, Test: 0.21084092557430267 
Train: 0.2665676772594452, Test: 0.22465194761753082 
Train: 0.28074997663497925, Test: 0.23137184977531433 
Train: 0.2892519235610962, Test: 0.23690366744995117 
Train: 0.2953035533428192, Test: 0.2383144646883011 
Train: 0.3050306737422943, Test: 0.24187859892845154 
Train: 0.3089289367198944, Test: 0.24299241602420807 
Train: 0.3151661455631256, Test: 0.2450343668460846 
Train: 0.3220716714859009, Test: 0.24473735690116882 
Trial 0 finished with value: 0.24800445139408112 and parameters: {'n_components': 59}. Best is trial 0 with value: 0.24800445139408112 -- and so on...**-- (3) Indexing** Running node: build_index: build_index([item_factors,params:index_params]) -> [kedro_annoy_dataset] 
-- and so on...**-- (4) Sampling indexing results** Running node: validate_index:
validate_index([kedro_annoy_dataset,idx_to_names]) -> [validated_kedro_annoy_dataset] Closest to Dark Knight, The (2008) : 
Dark Knight, The (2008) 
Sin City (2005) 
Shrek 2 (2004) 
Kill Bill: Vol. 1 (2003) 
Batman Begins (2005) 
Princess Mononoke (Mononoke-hime) (1997) 
Ratatouille (2007) 
Harry Potter and the Order of the Phoenix (2007) 
Scarface (1983) 
Lord of the Rings: The Fellowship of the Ring, The (2001) Closest to Ratatouille (2007) : 
Ratatouille (2007) 
Monsters, Inc. (2001) 
My Neighbor Totoro (Tonari no Totoro) (1988) 
Kiki's Delivery Service (Majo no takkyûbin) (1989) 
Spirited Away (Sen to Chihiro no kamikakushi) (2001) 
Who Framed Roger Rabbit? (1988) 
WALL·E (2008) 
Cars (2006) 
Howl's Moving Castle (Hauru no ugoku shiro) (2004) 
Shrek (2001) Closest to Rashomon (Rashômon) (1950) : 
Rashomon (Rashômon) (1950) 
Maltese Falcon, The (a.k.a. Dangerous Female) (1931) 
Lost Highway (1997) 
Big Sleep, The (1946) 
Name of the Rose, The (Der Name der Rose) (1986) 
Fanny and Alexander (Fanny och Alexander) (1982) 
Brick (2005) 
Dogville (2003) 
Vertigo (1958) 
Nine Queens (Nueve Reinas) (2000)

如果一切顺利,那么在我们的 MLFlow 实验中应该有以下内容。

图片由作者提供。在本例中,所有工件都存储在本地,但是将它们存储在云环境中应该是小菜一碟。

上菜(和测试!)

为了服务于模型,我们将打包我们的项目,然后使用 MLFlow 为我们部署一个 API。构建和部署脚本如下。像往常一样,我们会保持本地化。

-- our project is named prod-reco 
> kedro package -- in case we have installed our module already
> pip uninstall prod-reco -y -- install locally 
> pip install src/dist/prod_reco-0.1-py3-none-any.whl -- mlflow serve. You can use the ff way, or the model registry mlflow models serve -m "runs:/<run-id>/model" -p 5001 --no-conda

对于上面的例子,我使用了实验的 run-id,但是使用模型注册表也可以。此外,由于我只在本地安装了这个包,所以没有使用-conda。

现在,为了测试我们的 API,我们使用 pytest。我们以不同的方式调用我们的 API,看看它是否正确运行。如果成功,您应该得到下面的输出。

> pytest — no-cov -s src/tests/test_endpoint.py::TestEndpoint
==test session starts ==
…
…
…
plugins: mock-1.13.0, cov-3.0.0, anyio-3.5.0
collected 5 itemssrc/tests/test_endpoint.py 
Dark Knight
[“City of God (Cidade de Deus) (2002)”, “Dark Knight, The (2008)”, “History of Violence, A (2005)”, “3:10 to Yuma (2007)”, “Animatrix, The (2003)”]
.
Dark Knight & Kung Fu Panda
[“Wallace & Gromit: The Wrong Trousers (1993)”, “Monsters, Inc. (2001)”, “City of God (Cidade de Deus) (2002)”, “Dark Knight, The (2008)”, “Mulan (1998)”, “Ratatouille (2007)”, “History of Violence, A (2005)”, “3:10 to Yuma (2007)”, “Animatrix, The (2003)”, “Kung Fu Panda (2008)”]
.
Dark Knight & Kung Fu Panda & Godfather II
[“Godfather, The (1972)”, “Godfather: Part II, The (1974)”, “Wallace & Gromit: The Wrong Trousers (1993)”, “Monsters, Inc. (2001)”, “City of God (Cidade de Deus) (2002)”, “City of God (Cidade de Deus) (2002)”, “Untouchables, The (1987)”, “Dark Knight, The (2008)”, “Carlito’s Way (1993)”, “Mulan (1998)”]
.
User likes vintage movies
[{“userId”: 2241, “recos”: [“Mulan (1998)”, “Wallace & Gromit: The Wrong Trousers (1993)”, “Ratatouille (2007)”, “Monsters, Inc. (2001)”, “History of Violence, A (2005)”, “Kung Fu Panda (2008)”, “3:10 to Yuma (2007)”, “Animatrix, The (2003)”, “City of God (Cidade de Deus) (2002)”, “Dark Knight, The (2008)”]}]
.
First user likes vintage movies, second likes animation
[{“userId”: 190, “recos”: [“Animatrix, The (2003)”, “History of Violence, A (2005)”, “Kung Fu Panda (2008)”, “3:10 to Yuma (2007)”, “Ratatouille (2007)”, “City of God (Cidade de Deus) (2002)”, “Dark Knight, The (2008)”, “Mulan (1998)”, “Monsters, Inc. (2001)”, “Wallace & Gromit: The Wrong Trousers (1993)”]}, {“userId”: 2241, “recos”: [“Mulan (1998)”, “Wallace & Gromit: The Wrong Trousers (1993)”, “Ratatouille (2007)”, “Monsters, Inc. (2001)”, “History of Violence, A (2005)”, “Kung Fu Panda (2008)”, “3:10 to Yuma (2007)”, “Animatrix, The (2003)”, “City of God (Cidade de Deus) (2002)”, “Dark Knight, The (2008)”]}]== 5 passed in 1.40s ==

摘要

让管道运转起来花了我们很多时间,但非常值得。我们定义了我们的数据准备、训练、索引、服务和测试。我想强调的是,在这一点上,你可以很容易地做几件事:

  • 持续培训——编写一个 cronjob 或使用 kedro-airflow 将其转换为 Airflow DAG,以便在必要时使管道每天运行。注意,我们有适当的测试来检查它是否工作。批处理作业从未如此简单。
  • 将您的培训应用程序文档化—您可以将生成的容器集成到您组织的更大的编排管道中。
  • 部署您的服务应用程序—使用 MLFlow 从您的定制模型创建一个容器。
  • 更改配置—想要使用云资源?就像改变配置一样简单。代码库将基本保持不变!

感谢您的阅读,祝您在 MLOps 之旅中好运!

原载于 2022 年 3 月 28 日【http://itstherealdyl.comhttps://itstherealdyl.com/2022/03/29/deploying-a-recommendation-system-the-kedro-way/

使用 Helm 在 Google Kubernetes 引擎上部署气流

原文:https://towardsdatascience.com/deploying-airflow-on-google-kubernetes-engine-with-helm-28c3d9f7a26b

第一部分:使用 Helm 配置基本气流部署

Reza Rostampisheh 在 Unsplash 上拍摄的照片

目标

这篇由两部分组成的文章将展示如何使用官方掌舵图在 GCP 的 Google Kubernetes 引擎上部署和配置 Apache Airflow。

在第一部分中,我们将:

  • 在 GKE 建立一个 Kubernetes 集群。
  • 使用 Helm 和 values.yaml 文件部署和配置 Airflow。
  • 通过 GCP 负载均衡器在 GKE 上公开 Airflow web 服务器。

在本部分的最后,我们将有一个运行 LocalExecutor 的 Airflow 部署和一个通过 GCP 负载平衡器可访问的 Airflow web 服务器。但是没有任何匕首。

在第二部分,我们将:

  • 使用 GKE 秘密管理气流连接。
  • 通过从工件注册表加载的 Docker 映像安装气流依赖项和自定义操作符。
  • 使用 git-sync 特性从私有 GitHub 存储库中自动提取 Airflow DAGs。
  • 整合谷歌云存储等其他 GCP 服务。

在第二部分之后,我们将使用一个 DAG 扩展我们的气流部署,该 DAG 将每天一批数据写入 Google 云存储桶。

介绍

GCP 是气流云提供商的绝佳选择。Apache-airflow-providers-GooglePython 包提供了大量的 air flow 操作符、钩子和传感器。这使得将 Airflow 与许多 GCP 服务(如 BigQuery 和 GCS)集成变得轻而易举。

值得注意的是,GCP 提供了自己的气流管理部署,名为 Cloud Composer。然而,通过管理我们自己在 Kubernetes 上的部署,我们保持了对底层基础设施的更细粒度的控制。这使我们能够针对特定使用情形优化基础架构,并降低成本。

先决条件

本文假设您的工作站已经满足了先决条件:

  1. 一个名为“气流-gke”的 GCP 项目,有一个活跃的计费帐户(可能有免费试用信贷)。
  2. CLI 工具gcloudkubectlhelm

如果你需要 Kubernetes 的快速介绍,请观看这个轻松视频

1.在 GKE 创建一个库本内特星团

在 GKE 上初始化 Kubernetes 集群之前,我们必须首先在gcloud CLI 中使用项目 ID 设置项目:

项目 ID 可以在 GCP 仪表板的项目信息面板中找到。您的 GCP 项目将有一个不同于本文中的项目 ID。

作者图片

现在我们可以创建一个名为airflow-cluster的集群,带有一个公共端点。你可以自由选择不同的地理区域。

我们将使用kubectl CLI 与我们在 GKE 新部署的 Kubernetes 集群进行交互。使用以下命令对该集群验证kubectl:

最后,我们将使用kubectl CLI 为这个部署创建一个名为airflow的 Kubernetes 名称空间。这不是绝对必要的,但是了解一下这个特性是如何工作的是值得的。

不要忘记在下面的kubectl命令中将名称空间airflow传递给--namespace-n标志。

浏览到 Kubernetes 引擎上的集群选项卡,查看新创建的集群。

作者图片

2.部署官方的阿帕奇气流舵图

现在,群集已经启动并运行,我们可以使用 Helm 安装气流。Helm 是一个软件包管理器,它将 Kubernetes 应用程序捆绑到所谓的图表中。阿帕奇气流在 2021 年 7 月发布了气流的官方掌舵图。有了这个图表,我们可以相对容易地在我们新创建的 Kubernetes 集群上引导气流。

首先在您的本地舵库中安装 Apache Airflow 的官方舵图:

验证图表是否在您的本地存储库中:

现在,只需一个命令,气流就可以部署在 GKE 上:

  • 第一个airflow参数是我们给这个版本起的名字。
  • apache-airflow/airflow是我们部署的舵图。
  • 第二个airflow参数是我们之前创建的 Kubernetes 名称空间。
  • 建议使用--debug标志来查看进度和发现潜在问题。

通过浏览到 Kubernetes 引擎上的服务&入口选项卡来验证部署。

作者图片

在这里,我们可以看到我们的气流部署的各种服务。默认情况下,舵图表被配置为使用 CeleryExecutor,这就是为什么有airflow-flowerairflow-redis服务的原因。稍后我们将把它改为 LocalExecutor。

3.通过端口转发到 ClusterIP 服务来访问 Airflow web 服务器

默认情况下,Helm chart 配置为对airflow-webserver使用 Kubernetes ClusterIP 服务,如上图的 Type 列所示。该服务将请求路由到正确的 pod,但没有外部端点。要从群集外部访问它,我们必须将 pod 的端口 8080 转发到我们工作站的端口 8080:

现在可以在 localhost:8080 上访问网络服务器。默认凭证是用户名admin和密码admin

作者图片

每次我们想访问 web 服务器时都要转发端口,这很不方便。在下一节中,我们将用 GCP 负载平衡器替换 Kubernetes ClusterIP 服务,该负载平衡器将向外部公开 Airflow web 服务器。

4.配置我们的气流部署

在本节中,我们将通过编辑 Helm 图表的 values.yaml 文件来修改部署。将当前部署的默认配置写入名为 values.yaml 的文件:

我们将使用该文件来更改部署的两个方面:气流执行器和 web 服务器服务。为此,请在 values.yaml 文件中编辑以下值:

  1. 将 202 行的CeleryExecutor换成LocalExecutor
  2. 用 752 行的LoadBalancer替换ClusterIP服务。

一旦保存,您可以将文件传递给带有-f标志的helm命令,当我们upgrade集群:

让我们再次验证新的部署。

作者图片

注意到 Kubernetes 的服务比以前少了吗?这是因为LocalExecutor不需要 Redis 代理和 Flower UI。因此,GKE 删除了这些豆荚,代之以一个airflow-scheduler服务。

还要注意airflow-webserver服务现在是一个“外部负载平衡器”类型。GKE 发现了从 ClusterIP 到 LoadBalancer 的变化,并自动为您创建了这个 GCP 服务。现在,您可以通过“端点”列中的 IP 地址访问 web 服务器。

作者图片

目前没有 Dag。在本文的第二部分,我们将学习如何添加 DAG 及其依赖项。

结论

我们现在在 GKE 有一个功能齐全的气流部署。由于 values.yaml 文件存储了我们的配置,我们总是可以重新创建它。因此,建议通过命令行(或使用 Terraform 等工具)设置您的项目,并使用 GCP 界面来检查我们的工作。

我们看到了赫尔姆和 GCP 是如何在配置 Kubernetes 时抽象出大量的复杂性的。例如,仅仅用“负载平衡器”替换“集群 IP”就足以使 GCP 加速旋转并为气流网络服务器配置负载平衡器。同样,通过将“CeleryExecutor”更改为“local executor ”, GCP 负责终止冗余的 flower 和 redis 服务并启动调度程序服务。

在下一部分中,我们将通过添加 DAG 来进一步扩展我们的部署。我们将看到 Apache-air flow-providers-Google 包中的操作者如何轻松地集成各种 GCP 服务,如 GCS。

参考

朱利安·德·鲁特,巴斯·哈伦斯拉克。(2021).带阿帕奇气流的数据管道。奥莱利。

用头盔在谷歌 Kubernetes 引擎上部署气流——第二部分

原文:https://towardsdatascience.com/deploying-airflow-on-google-kubernetes-engine-with-helm-part-two-f833b0a3b0b1

添加创建数据并将其写入 GCS 的 DAG

UnsplashWonderlane 拍摄的照片

注意:这里是文章第一部分的链接。

介绍

在本文的第一部分中,我们获得了托管在 GKE 上的 Kubernetes 集群上的功能性气流部署。通过编辑 values.yaml 文件,我们让 GCP 为 Airflow web 服务器部署了一个负载平衡器,并用 LocalExecutor 替换了 CeleryExecutor。然而,我们还没有任何 Dag。这就是我们将在本文中重点讨论的内容。

具体来说,在第二部分,我们将学习如何:

1.使用 git-sync 特性从私有 GitHub 存储库中自动提取我们的 DAG。
2。通过从工件注册表中提取的 Docker 映像,为我们的 DAG 安装气流依赖项和自定义操作符。
3。将我们的 DAG 与谷歌云存储等 GCP 服务相集成。

在第二部分结束时,我们将使用 DAG 进行气流部署,该 DAG 使用自定义操作符创建数据并将其写入 GCS。

先决条件

除了第一部分中的先决条件之外,还应该安装和配置 Docker 以及“Docker”CLI。

关于目录结构的说明

本文假设您有一个私有 GitHub 存储库的本地和远程副本,目录结构如下:

  • 我们的狗的目录。
  • 一个目录“插件”,带有我们 DAG 中使用的自定义操作符。
  • 带有“requirements.txt”的“Dockerfile”用于构建我们自己的气流 Docker 图像。
  • 我们编辑的“values.yaml”文件,用于配置我们在 GKE 的气流部署。

DAG 概述

我们将使用以下 DAG 测试气流部署:

它只有一个任务,创建如下所示的临时 JSON 文件:

然后将这个 JSON 文件写入 Google 云存储,进行长期存储。默认情况下,“ExampleDataToGCSOperator”不存在。这是一个从插件目录自动加载的自定义操作符。稍后我们将看到如何将插件目录包含到我们的气流部署中。

这是“ExampleDataToGCSOperator”的代码:

这个操作符如何工作的细节并不十分重要。但是请注意这个操作符是如何导入“GCSHook”的。这意味着我们需要在我们的气流部署中安装“Apache-air flow-providers-Google”Python 包。这也将在后面讨论。

1.将 DAG 添加到气流部署中

第一种简单的方法是向“Dockerfile”添加“COPY”命令,该命令将“dags”目录复制到在我们的部署中用于气流的 Docker 映像中。这种方法的缺点是,每次我们对 DAG 进行更改时,都必须重新构建和重新部署这个 Docker 映像。这是不方便的

我们将选择一种更好的方法,使用 git-sync 从私有 GitHub 库中自动提取我们的 DAG。这个特性在 Kubernetes 上创建了一个额外的服务,将所有 Dag 从我们的 GitHub 存储库中的指定目录中提取到我们的 Airflow 部署中。默认情况下,拉入频率为 60 秒。

我们将假设我们的私有 GitHub 存储库对应于上面列出的目录结构。但是请注意,“dags”文件夹也可以在另一个私有的 GitHub 存储库中。

配置 git-sync

首先,我们需要为我们的私有存储库生成一个 SSH 密钥对。将 ssh-key 命名为“airflow-gke”。

输出应该类似于:

接下来,转到 GitHub 存储库中的“设置”,在“部署密钥”选项卡下,单击“添加部署密钥”。

作者图片

公共密钥从 airflow-gke.pub 复制到密钥字段:

作者图片

您不需要选择“Allow write access ”,因为 git-sync 只会从该存储库中提取 Dag,而不会推送。

现在我们必须在舵图中启用 git-sync。与本文的第一部分一样,我们通过编辑 values.yaml 文件来配置 Helm 图表。转到 values.yaml 文件的第 1339 行。这是我们将配置 git-sync 的地方。

进行以下更改:

  • 第 1340 行:将 enabled 设置为“真”。
  • 第 1346 行:更新仓库的 URL。不要忘记用斜线代替冒号`:',如第 1343 行所示。
  • 将分支名称设置为“main ”(包含气流 Dag 的分支)。
  • 将子路径更改为存储库中的正确路径。在我们的目录结构中,这对应于“Dag”。

现在我们必须添加私钥,它将被我们在 GKE 的气流部署用来从这个 GitHub 存储库中读取。取消对第 1381 行的注释,并用“airflow-gke-git-secret”替换该值。这将是我们稍后创建的 Kubernetes Secret 的名称。

注意,密钥必须是 base64 编码的,而我们之前创建的 SSH-key 不是。然而,GCP 会自动将我们的密钥转换为 base64。所以不要按照 气流文档 这种情况!

将 GitHub 私有 SSH 密钥添加为 Kubernetes 秘密

下面的命令从我们前面创建的私钥文件中创建 Kubernetes 秘密。确保将路径调整到您的私钥文件。

您可以通过浏览 Kubernetes 引擎上的“配置映射和机密”选项卡来检查是否成功创建了机密:

作者图片

最后,再次升级您的气流部署:

如果您对 git-sync 的工作原理感兴趣,可以运行以下命令:

请注意,现在调度程序有三个容器,而不是以前的两个。这个新的“sidecar”容器用于 git-sync。

作者图片

2.为气流添加自定义 Docker 图像

如果您转到 Airflow UI 检查 DAG 是否可用,您将会看到以下错误:

作者图片

这个错误告诉我们 DAG 确实是从 GitHub 库中提取的,但是它不能被导入,因为 Python 模块“custom_operators”在我们的 Airflow 部署中丢失了。这是有意义的,因为我们没有添加它。

要解决这个问题,我们必须在气流部署中注意两件事:

1.添加包含自定义操作符的插件目录。
2。安装“Apache-air flow-providers-Google”Python 依赖项,以便我们的自定义操作符可以导入“GCPHook”。

插件目录和 Python 依赖项都可以通过 Airflow 使用的 Airflow Docker 映像添加到我们的 Airflow 部署中。我们将为气流扩展基本图像,并将其推送到 GCP 上的工件注册表。然后,我们将配置头盔使用此图像的气流。

配置 GCP 工件注册表

首先,我们必须通过点击“enable”来启用 GCP 上的工件注册 API。

作者图片

以下命令允许“docker”CLI 根据我们将在 GCP 上创建的 Docker 存储库进行身份验证:

接下来,我们在 GCP 项目的工件注册中心创建一个 Docker 存储库:

收到此消息时,请回复“y ”:

项目[1077449300737]中未启用 API[artifactregistry.googleapis.com]。是否要启用并重试(这将需要几分钟时间)?
(y/N)?

我们可以在工件注册表上查看我们新创建的 Docker 存储库:

作者图片

为气流创建新的 Docker 图像

现在,我们可以基于“apache/airflow:2.2.1”映像创建一个新的 Docker 映像。下面的 docker 文件将插件目录和 requirements.txt 文件复制到“${ air flow _ HOME }”目录。

requirements.txt 文件包含 Python 依赖项,该依赖项包含“GCPHook”及其版本:

我们现在可以使用以下命令构建这个映像:

请注意,图像名称具有非常特殊的结构。这对于将它推送到工件注册中心的正确 Docker 存储库是必要的。让我们来看看它的各个部分:

  • ` europe-west4-docker.pkg.dev '是指 docker 注册表的区域。
  • “气流-gke-338120”是这个 GCP 项目的项目 ID。注意,对你来说会有所不同。
  • “airflow-gke”是我们在 Artifact Registry 上给 Docker 存储库起的名字。
  • “airflow-plugins-dependencies”是 Docker 图像的名称。
  • “1.0.0”是 Docker 图像的标签。

使用类似的命令将创建的映像推送到工件注册表:

这个图像现在可以在 GCP 用户界面的工件注册表中看到:

作者图片

更新 Airflow Docker 图像

我们现在可以从工件注册表中提取这个 Docker 图像,将其用作部署中气流的图像。第 48–53 行允许我们指定气流使用哪个 Docker 图像,而不是舵图中包含的默认图像。使用我们图像的名称和标签更新条目,如下所示:

再次升级气流部署:

我们现在可以在气流界面中看到我们的 DAG。

作者图片

3.配置 GCS

剩下的最后一步是将 GCS 集成到我们的气流部署中,以便我们的定制运营商可以将其数据写入一个桶中。我们将这个存储桶命名为“示例-数据-存储桶”。当我们的定制操作员执行“GCSHook”时,一个请求被发送到 GCP。这需要认证。有几种方法可以启用身份验证。在本文中,我们将依靠一个 GCP 服务帐户来完成这项工作。现在,我们将为我们的项目创建这个服务帐户,并授予适当的权限来写入 GCS。一个好的做法是不要给你的服务帐户比完成任务严格需要的更多的权限。

创建 GCP 服务帐户

在 GCP 用户界面中,浏览至“IAM & Admin ”,然后浏览至“服务帐户”。点击“创建服务帐户”。

作者图片

将其命名为“airflow-gke ”,然后单击“创建并继续”。接下来,选择产品“云存储”,并分配“存储对象管理员”的角色。这使得服务帐户可以完全控制 GCS 对象。单击“继续”和“完成”(我们不需要授予用户访问此服务帐户的权限)。

作者图片

现在,我们必须创建一个密钥文件,该文件将存储在任务用来针对 GCP 进行身份验证的气流连接中。选择“airflow-gke”服务帐户,并在“操作”选项卡下点击“管理密钥”。点击“添加密钥”和“创建新密钥”。密钥类型应该是 JSON。密钥将被下载到您的计算机上。

作者图片

创建气流连接

理想情况下,我们将通过来自 Kubernetes ConfigMap 的环境变量来创建这个气流连接。但是这种讨论会使我们偏离本文的目标太远。我们将通过气流 UI 手动完成。

在 Airflow UI 中,浏览至“管理”选项卡,然后浏览至“连接”。单击蓝色加号图标。

  • 连接 Id `airflow _ gke _ gcs _ conn _ id ”,以便它对应于 DAG 中“create_and_write_example_data”任务的“gcp_conn_id”参数。
  • 连接类型应该是“谷歌云”。
  • 将我们之前下载的服务帐户密钥文件的全部 JSON 内容复制到 Keyfile JSON 字段中。
  • “项目 ID”字段应包含您的 GCP 项目的项目 Id。
  • 将范围字段留空。

作者图片

创建 bucket Google 云存储

我们现在必须在 GCS 中创建“示例数据桶”。导航到 GCP 用户界面上的“云存储”,然后单击“创建存储桶”。

作者图片

多次点击“继续”(默认值就可以了),最后点击“创建”。

4.运行 DAG。

现在转到 Airlfow UI 并激活 DAG。DAG 现在应该成功运行:

作者图片

通过浏览 GCP 用户界面上的“云存储”,我们可以在 example-data-bucket 中看到示例数据 JSON 文件。

作者图片

结论

在这篇由两部分组成的文章的最后,我们现在有了一个功能完整的气流部署,它带有一个与 GCS 交互的 DAG。多亏了 git-sync 特性,我们可以通过将更改推送到私有的 GitHub 存储库来更新我们的 DAG。这些变化将自动纳入我们的气流部署。然而,如果我们想要编辑我们的自定义操作符或者添加新的 Python 依赖项,我们将不得不为 Airflow 构建并推送一个新版本的 Docker 映像。

我希望您喜欢这篇由两部分组成的文章!如果你有任何问题或遇到任何问题,请在评论中告诉我。

在 Google Cloud Run 上部署 Apache 超集

原文:https://towardsdatascience.com/deploying-apache-superset-on-google-cloud-run-4e8f9b85a78a

照片由伊萨贝尔·🇺🇦Unsplash 上拍摄

数据科学

在几分钟内完成概念验证

Apache Superset 是一个强大的开源仪表板工具。但是,设置可能很麻烦。我将一步一步地带你通过它。

介绍

本指南依赖于附带的 GitHub 库,可以在这里找到

概括地说,我们将使用这个存储库来:

  • 在本地 VS 代码开发容器中设置超集。
  • 我们不会在本地创建超集配置数据库,而是将开发容器指向 Google Cloud Platform 中的 SQL 数据库,我们将使用它来支持最终的部署。
  • 使用我们的本地部署来配置 GCP SQL 数据库。
  • 一旦谷歌云数据库配置完毕,我们将在谷歌工件注册中存放一个容器映像。
  • 从工件注册容器创建 Google Cloud Run 服务。

该存储库包含:

  • .devcontainer:包含将创建 VS 代码开发容器的文件的文件夹。
  • setup-scripts:包含 shell 脚本集合的文件夹,用于设置和配置谷歌云平台(GCP)服务。这些脚本可以根据您的需要定制,但是它们不需要任何定制,因为它们依赖于您将在.env.template中设置的 VS 代码开发容器中的环境变量。
  • src:包含构建 Google Cloud Run 服务的文件的文件夹。
  • .env.template允许您为您的部署设置环境变量的值。

如果您觉得本指南有用和/或您对更详细的入门指南感兴趣,请 在 Medium 上跟随我。

先决条件

执行上述步骤需要:

  1. Docker 桌面
  2. Visual Studio 代码

3.谷歌云 CLI

下面列出的所有 shell 命令/脚本都要从 VS 代码开发容器中的终端执行,这一点非常重要。

按照显示的顺序安装上述每个必备组件。Visual Studio 代码、远程容器扩展和 Google Cloud CLI 都很容易安装。明确遵循 Docker 安装说明! Docker 本身有许多先决步骤。如果你还没有安装 Docker,请注意安装步骤。

如果您在以下步骤中遇到错误,很可能是因为您没有正确执行必要的安装步骤。

入门

1.一旦你完成了先决条件的安装,将库克隆到你的机器上。不要从superset-on-gcp-cloud-run重命名存储库根文件夹。

2.将文件./.env.template重命名为./.env,并在记事本或其他文本编辑器中打开该文件。让它保持打开,直到指示保存并关闭它。我们将在下一节填充一些需要的值。

配置谷歌云

项目创建

如果你还没有为此建立一个谷歌云项目,你需要创建一个。所以,来看看谷歌云平台的网络界面— 谷歌云控制台

从“Google Cloud Platform”文本左上方的下拉列表中,可以轻松选择或创建项目。创建项目后,请确保您正在其中工作,方法是确保正确的项目名称出现在您刚刚选择的下拉列表旁边。

谷歌云控制台——项目选择和创建

确保对您创建的项目启用计费。

  1. 在顶部搜索栏中键入' Billing '并选择该选项。
  2. 您应该会收到一个通知,告知您该项目没有计费帐户。选择链接一个计费账户
  3. 选择(或创建)您的付费帐户。

确保./.env中的GOOGLE_CLOUD_PROJECT变量被设置为匹配您为项目名称选择的任何内容。注意,这个值也需要用在SUPERSET_CONNECTION_NAMESUPERSET_CONECTION_SECRET变量中,所以现在也要替换那些字符串的那部分。

配置谷歌同意屏幕

我们的部署旨在允许来自基于 Google 的组织的任何用户访问超集。换句话说,如果他们的电子邮件包含“@yourorganization.com”,他们就可以登录。为此,在继续之前,我们需要在 Google Cloud 控制台中配置一些东西。

  1. 从谷歌控制台主页键入“APIs &服务”并选择该部分。
  2. 从左侧菜单中选择“ OAuth 同意屏幕”。
  3. 选择“内部”并点击创建
  4. 设置以下字段。所有这些都可以由您自行决定,但请注意'授权域'将决定访问权限,因此请确保这是您组织的正确顶级域。
  • 应用名称
  • 用户支持电子邮件
  • 授权域
  • 开发者联系信息

5.点击保存并继续

6.点击添加或删除范围按钮。

7.选择' openid' 范围,点击更新

8.点击保存并继续

9.点击返回仪表板按钮。

创建凭证

  1. 从“API&服务”屏幕的左侧菜单中选择凭证
  2. 点击+创建凭证 > OAuth 客户端 ID
  3. 应用类型下拉框设置为“ Web 应用,并选择您喜欢的“名称”。
  4. 点击创建
  5. 您的客户 ID您的客户机密将在弹出窗口中显示。记下这些值,并将它们复制到./.env文件中各自的变量:GOOGLE_CLIENT_IDGOOGLE_CLIENT_SECRET。注意前导/尾随空格。明智的做法是也下载 JSON 。你应该不需要,但以防万一。

环境变量

确保这个库附带的./.env文件中的所有变量现在都有赋值。保存并关闭./.env文件。

打开本地开发容器

  1. 确保 Docker 正在运行并在您的机器上打开,然后打开 VS 代码。
  2. 在 VS 代码中,从左侧导航栏中选择远程浏览器图标,打开“远程容器扩展。
  3. 选择打开容器中的文件夹按钮,选择包含整个资源库 : superset-on-gcp-cloud-run的文件夹。在下载依赖项和构建容器的过程中,此步骤可能需要 5-10 分钟。当容器完全构建后,文件树应该显示在左窗格中。
  4. 从顶部 VS 代码菜单中选择端子 > 新建端子打开端子面板。
  5. 在终端中输入命令printenv并按回车键。这将打印出容器中所有环境变量的列表。浏览以确保那些在./.env中定义的变量显示正确的值。如果他们没有仔细检查文件,保存它,并重新构建容器。如果有不准确的地方,你将无法继续。

VS 代码—集装箱和码头控制

配置谷歌云平台基础设施

  1. 通过终端输入命令gcloud auth login登录 Google cloud。因为容器没有与外界连接,所以它会生成一个命令,您需要将这个命令复制到在容器外运行的 Google Cloud SDK 中。所以,在你的机器上打开 Google Cloud SDK Shell,从 VS 代码终端粘贴命令。这将打开一个浏览器窗口,请求您授权继续。使用部署超集的同一个域的 Google 帐户授予它访问权限。
  2. 将 Google Cloud SDK 的结果复制并粘贴回 VS 代码终端。如果成功,您应该会收到一条消息,提示“您现在以… 的身份登录”
  3. 通过输入命令gcloud config set project $GOOGLE_CLOUD_PROJECT切换到您之前设置的 Google Cloud 项目,这将利用定义您之前应该设置的 Google Cloud 项目名称的环境变量。如果成功,终端应该返回'更新的属性[核心/项目]。
  4. 通过在 VS 代码终端中键入setup-scripts/enable_gcp_services.sh来启用项目中我们需要的各种 Google 云服务。

一些用户报告了在尝试运行这些 shell 脚本时出现“权限被拒绝”的错误。如果您遇到这种情况,只需通过键入chmod u+x setup-scripts/enable_gcp_services.sh来允许自己执行该脚本。这将授予您对指定脚本的执行权限。

创建超集配置 SQL 数据库

  1. 通过在 VS 代码终端中键入setup-scripts/setup_sql.sh,从这个存储库中运行setup_sql.sh脚本,在 Google Cloud 中创建超集 SQL 数据库。

设置机密&服务账户

你的云运行服务将从 GCP 秘密管理器获取秘密。这些秘密都将根据您在./.env文件中设置的值来创建。

  1. 通过entering setup-scripts/create_gcp_secrets.sh在 VS 代码终端运行create_gcp_secrets.sh脚本。
  2. 我们还需要为 Superset 创建一个服务帐户,并授予它访问我们刚刚创建的秘密以及我们将依赖的各种服务的权限。在 VS 代码终端中运行setup-scripts/create_gcp_service_account.sh,在您的项目中创建一个名为“ superset ”的服务帐户,它可以完成这项工作。

构建超集配置 SQL 数据库

  1. 通过在终端运行这个命令,将 VS 代码开发容器连接到我们创建的 GCP 数据库:/cloud_sql_proxy -instances=$SUPERSET_CONNECTION_NAME=tcp:5432。如果成功,您应该会看到终端顶部的“端口”标题旁边的数字增加 1,并且可能会显示一个弹出窗口,通知您“您的应用程序现在正在端口 5432 上运行。
  2. 这个代理连接将独占您刚才工作的终端窗口。点击您刚刚使用的终端窗口右上角的 + ,打开一个新的终端窗口。
  3. 在你刚刚打开的新终端窗口中,键入superset db upgrade。这可能需要一些时间来执行,但是这个命令非常重要,因为它会用运行超集所需的所有表来填充 Google Cloud 托管的 SQL 数据库。

构建&部署 Apache 超集容器

接下来,我们将把 Docker 图像推送到 Google Cloud 项目中的 Google 工件注册中心。然后将创建一个云运行服务来部署该映像。

  1. 通过在 VS 代码终端中键入命令setup-scripts/create_gcp_artifact.sh来创建一个 Google 工件注册容器。
  2. 通过在 VS 代码终端中键入setup-scripts/create_gcp_image.sh,将存储库的src文件夹的内容上传到您刚刚创建的存储库,作为 Docker 映像。
  3. 通过在 VS 代码终端中键入命令setup-scripts/create_gcp_cloud_run.sh,将你上传的图片转换成一个活动的 Google Cloud Run 服务

更新服务凭证

运行上面的脚本后,您将收到一个 Google Cloud Run 服务 URL。

  1. 返回谷歌云控制台
  2. 在顶部搜索栏中搜索并选择API&服务
  3. 从左侧导航栏中选择凭证
  4. 选择铅笔图标编辑您的 OAuth 凭证,并将授权重定向 URIs 更新为<CLOUD-RUN-URL>/oauth-authorized/google,用 VS 代码终端返回的值替换<CLOUD-RUN-URL>等待几分钟后再继续。
  5. 导航到 VS 终端显示的云运行服务 URL(**,不包含您在步骤 4 中添加的额外文本。*)。这将验证您是超集部署的管理员。

完成后,您将需要运行下面的步骤,以确保未来的用户不会被授予管理员访问权限。

刷新超集角色

  1. superset_config.py中的AUTH_USER_REGISTRATION_ROLE更新为公共。保存并关闭该文件。所有新帐户将默认为公共帐户,而不再是管理员帐户。
  2. 通过在 VS 代码终端再次键入setup-scripts/create_gcp_image.sh来更新容器图像。
  3. 通过再次在 VS 代码终端中键入setup-scripts/create_gcp_cloud_run.sh来部署您的新容器版本。

初始化超集

  1. 确保您仍然通过 Google cloud sql 代理连接,并在 VS 代码终端中键入superset init。这将需要几分钟才能完成。

恭喜

现在一切都应该运行成功,您应该能够通过访问运行setup-scripts/create_gcp_cloud_run.sh时返回的 URL 来访问您的部署。

使用 DagsHub 在空间上部署 Gradio 应用程序:初学者教程

原文:https://towardsdatascience.com/deploying-gradio-app-on-spaces-using-dagshub-a-beginners-tutorial-a42664abcc14

基于项目的教程介绍了 MLOps 集成,如 DVC,DagsHub,Gradio web 框架,和拥抱脸空间

作者封面

由于公司正在为其复杂的机器学习(ML)模型寻找易于部署的解决方案,因此对 MLOps 工具的需求很高。为了使事情简单有效,我们将把 DVC,DagsHub,Gradio 和拥抱脸空间整合到我们的合作 ML 项目中。

在本教程中,我们将了解端到端的机器学习集成,并使用它们来创建图像生成 web 应用程序。我们将使用 Gradio 开发开源项目 SavtaDepth 的模型推理,它将提供一个用户友好的 web 界面。SavtaDepth 通过预测深度维度将 2D 图像转换为 3D 图像。这是一个初学者友好的教程,这意味着我们将学习一些技巧来简化复杂 ML 模型的部署过程。

SavtaDepth 项目

SavtaDepth 是一个用于单目深度估计的合作开源数据科学项目。它拍摄 2D 图像并估计物体的深度。深度估计用于创建 3D 图像、3D 绘图、安全监控和自动驾驶汽车。单目深度估计的目标是预测每个像素的深度值或推断深度信息,仅给定单个 RGB 图像作为输入— (keras.io)

该项目使用 U-Net 模型,作者已经将最后一层从对象分割改为深度估计。模型在 CC BY 4.0 license 下的 NYU 深度数据集 V2 上进行训练。该数据集包含来自微软 Kinect 的 RGB 和深度相机记录的各种室内场景的视频序列。该项目仍然是活跃的,因此贡献者可以帮助改善结果。如果你对这个项目感兴趣,请阅读投稿指南。

作者图片|单目深度估计

集成

在这一节中,我们将了解使我们的项目与众不同的各种集成。我们还将了解这些工具如何融入我们的项目生态系统。

  • DVC 是一个开源版本控制的机器学习系统。它附带了数据和模型版本控制、模型度量监控和重现实验。它是 Git 的一个扩展,所以学习 DVC 会很自然。
  • DagsHub 是一个类似 GitHub 的社区第一平台,用于机器学习和数据科学项目。它使其用户能够利用流行的开源工具来版本化数据集&模型、跟踪实验、标记数据和可视化结果。
  • Gradio 是为您的机器学习模型创建用户友好的 web 界面的最快方法。您可以在几分钟内构建并共享您的应用程序。它还带有 FastAPI 支持,这意味着您可以在任何地方访问该模型。
  • Spaces 是拥抱脸新推出的机器学习应用分享平台。您可以构建 Streamlit、Gradio 或 HTML web 应用程序,并使用几行代码将它们部署到空间中。

在这个项目中,我们将使用 DVC 进行数据和模型版本控制,使用 DagsHub 进行远程存储,使用 Gradio 进行 ML 应用程序接口,使用 Spaces 作为 web 服务器。

Gradio WebApp

Gradio 是一个轻量级的强大的网络界面,用于创建机器学习演示。在本节中,我们将学习如何添加图像,使用 FastAI 运行模型推理,并输出生成的结果。为了运行 web 应用程序而不出现依赖问题,我们需要首先安装 PyTorchFastAIGradio

模型推理

为了简单起见,我只添加了重要的部分,并删除了数据加载器。您可以通过查看 SavtaDepth 笔记本了解更多关于图像数据加载器和 create_data 功能的信息。

我们只是使用 FastAI 的函数 unet_learner 来创建模型架构,然后加载最新的模型检查点。最后,我们将创建一个 gen 函数,该函数将以 Numpy 数组的形式获取图像,并运行预测以生成黑白 3D 图像。

网络界面

创建 Gradio web 界面很容易。我们只需要定制 gradio。我们的用例与 Vallah 的接口函数!!!您的 web 应用程序已准备就绪。

让我们仔细看看接口函数中使用的参数:

  • 第一个参数是 fn 。我们需要为它提供模型推理,它接受输入并返回输出。在我们的例子中,它生成函数。
  • 第二个参数是输入。我们正在将图像形状转换为 640X480 Numpy 数组。这个功能自动为我们做了大部分的图像处理工作。
  • 第三个参数是输出。在我们的例子中,它是一个图像。所有的后处理都是由 Gradio 自动完成的。因此,我们不必担心使用 PIL 包来显示图像。
  • 标题取一个字符串显示应用名称或标题。
  • 描述采用一个简单的字符串、markdown 或 HTML 来显示副标题或标题下的图像。
  • 文章是应用程序的页脚,你可以在这里写申请信息,比如你的研究和项目库的链接。它还接受简单的文本、markdown 或 HTML。
  • 示例可用作示例输入,这样我们就不必寻找图像来运行应用程序。在我们的例子中,我们已经创建了一个文件夹,并从数据集的一个测试子集中复制了两个图像。这些示例接受一个相对文件路径数组,如下所示。
  • 主题是自定义 UI 的,你可以在文档中找到更多关于主题的信息。
  • allow_flagging 是一个很酷的功能,可以帮助你跟踪模型的性能。您可以标记错误的预测,以便开发人员可以根据反馈改进模型。
  • enable_queue 用于防止推理超时。

在最终版本中,我使用 HTML 脚本对描述文章做了一些修改。随时检查我的代码

作者 Gif

部署

在本节中,我们将学习如何将 Gradio 应用程序部署到拥抱面部空间。为此,我们需要使用拥抱脸网站创建新的空间,然后在 savta_app.py、README.md 和 requirement.txt 文件中进行更改。

拥抱面部空间

通过拥抱 Face 机器学习应用程序共享平台,人们可以在各种 Python web 框架上创建应用程序,并使用简单的 Git 函数进行部署。您还可以查看特色空间,体验最新的 ML 模型。

图片来自空间——拥抱脸

在我们开始部署之前,我们需要首先创建一个新空间,然后我们需要添加空间的名称、许可证,并选择 SDK。之后,我们可以克隆空间或者向当前的 Git 存储库添加一个远程。

作者图片

建立 DVC

用空间整合 DVC 很容易。我们只需要在主 Python 文件中运行一个 shell 脚本。下面的代码只从训练和测试数据集中提取了一个模型和一些样本。最后,它去掉了。dvc 文件夹来优化存储。确保在模型推理函数之前添加此代码。

自定义环境

为了将 Gradio 应用程序部署到 Spaces,我们需要做一些更改以避免错误和依赖性问题。

添加拥抱脸遥控器

首先,我们需要将 Space remote 添加到当前项目中。空间远程地址应该是这样的

README.md

然后转到您的 README.md 文件,以 yml 的形式添加元数据。这些元数据将告诉 Space 应用程序文件的位置、封面的表情符号、应用程序缩略图的颜色渐变、SDK 和许可证信息。您可以自定义颜色和表情符号,让您的缩略图更加醒目。

requirements.txt

我们受限于 CPU,为了优化存储,我们将使用 PyTorch CPU 版本。

图片来自入门| PyTorch

我们将只包含运行模型推理和 web 界面所必需的包到 requirements.txt 文件中。

最後的

完成所有更改后,就该提交代码并将其推送到 Space 远程服务器了。初始远程空间有 README.md 和。gitattributes,所以为了避免冲突,我们将使用 -f 标志。我们将使用 master:main 将所有文件从本地(主分支)发送到远程(主分支)服务器。

警告:请在开始时只使用一次 -f 标志,避免使用它,因为它会覆盖其他人的工作。

按下代码后,你会在你的应用上看到一个“大厦”的标志。大约需要 3 分钟来构建。

作者图片

恭喜您,您的应用已成功部署,可以在朋友和同事之间共享。

图片作者| 拥抱脸空间

奖金

额外的部分是给 MLOps 爱好者的,他们总是渴望学习更多集成新工具和数据库的方法。在这一部分,我们将整合拥抱脸数据集来收集所有的标志。这些标志将包括输入图像、输出图像和 CSV 文件。flag 选项有助于我们跟踪模型性能,并且我们可以在以后使用该数据集来提高模型性能。

我们需要将下面的代码添加到 savta_app.py 文件中,以便集成工作。它将使用HuggingFaceDatasetSaver来创建和更新标志数据集。该函数需要两个参数 HF_TOKEN ,您可以在设置和数据集名称中找到。最后,您可以在 Gradio 接口函数中将对象添加到 flagging_callback 中。

您还需要在空间设置中设置 HF_TOKEN 环境变量。只需复制并粘贴用户访问令牌

作者图片

之后,转到应用程序并标记几个预测,以便您可以在您的个人资料下看到标记的数据集。在我们的例子中,可以公开访问的是 savtadepth-flags

图像来自 savtadepth-flags

结论

拥抱面部空间提供了一个易于部署的平台,数据科学家可以通过用户友好的界面测试和分享机器学习模型。如果你是机器学习的初学者,想体验端到端的产品开发,那就尝试在 Gradio 上开发你的 app,与 DagsHub 集成,最后部署到 Spaces。它还将帮助你创建一个强大的投资组合,其中你可以提到部署机器学习模型的经验。

在本教程中,我们学习了如何使用 Git、DVC、DagsHub、Gradio 和 Hugging Face Space 来创建和部署复杂的机器学习应用程序。这是你成为 MLOps 工程师的第一步。

项目资源:

数据集参考: Nathan Silberman,P. K .,Derek Hoiem en Fergus,r .(2012)“RGBD 图像的室内分割和支持推断”,载于 ECCV

使用 Heroku 部署机器学习模型

原文:https://towardsdatascience.com/deploying-machine-learning-models-with-heroku-4dec1df87f71

不要只是培训,还要部署:一步一步的指南

罗曼·辛克维奇在 Unsplash 上拍摄的照片

最近,在机器学习领域运营的组织对大规模构建和部署数据驱动的智能系统非常感兴趣。这不仅导致了高效和精确系统的快速发展,还通过使用智能嵌入式产品对改善整体终端用户体验做出了深远的贡献。

由于与智能嵌入式软件相关的各种好处,在部署阶段管理机器学习模型已经成为当今机器学习工程师的一项基本技能。有时,这甚至需要单独的团队在部署中发布和管理产品。

因此,这篇文章将提供一个详细的概述,介绍如何将你训练好的模型应用到部署中。具体来说,我将演示如何使用 Flask 创建一个 web 应用程序,然后使用 Heroku 将机器学习模型部署到云中。

文章的亮点如下:

什么是部署?步骤 1:开发机器学习模型步骤 2:使用 Flask 创建 Web 应用程序步骤 3:将应用程序部署到 Heroku 服务器结论

我们开始吧!

什么是部署?

首先,部署是将经过训练的机器学习模型集成到生产环境中的过程,通常是为了服务于最终用户。

部署通常是机器学习产品开发生命周期的最后一个阶段。下图简要概述了开发生命周期:

机器学习产品的开发生命周期(图片由作者提供)

上面的“模型部署”阶段由一系列步骤组成,如下图所示:

模型部署阶段(图片由作者提供)

部署机器学习模型的三个主要步骤如下:

  • 首先,我们开发机器学习模型。这包括收集相关数据并对其进行分析、生成特征、选择模型、执行超参数调整,以及最后评估模型。
  • 一旦我们对预测和准确性指标有了信心,我们就将模型集成到 web 应用程序中,如 Flask、Django 等。这包括创建应用程序的前端(或用户界面)供用户交互并提供输入,然后将其与后端融合,将获得的数据馈送给机器学习模型以进行预测。
  • 最后,我们使用 Heroku、Google Cloud 等托管服务将应用程序部署到服务器上。

出于本教程的目的,我将使用 Flask 来构建 web 应用程序。接下来,我将把应用程序部署在一台 Heroku 服务器上。

步骤 1:开发机器学习模型

在本节中,让我们训练我们打算部署的机器学习模型。为了简单起见,也为了不偏离本文的主要目标,我将部署一个线性回归模型。

#1 生成虚拟数据点

我将对下面散点图中描述的一组虚拟数据点训练一个线性回归模型:

虚拟数据点的散点图(图片由作者提供)

从自变量到x到因变量y的映射实现如下:

#2 培训模型

接下来,我将使用 scikit-learn 来训练一个线性回归模型。下面演示了这一点:

模型学习的回归线如下所示:

带有回归线的虚拟数据点散点图(图片由作者提供)

#3 将模型保存到 Pickle 文件

要在 Heroku 服务器上部署训练好的模型,您应该将它保存为 pickle 文件,如下面的代码块所示:

步骤 2:使用 Flask 创建 Web 应用程序

Flask 是一个流行的 web 框架,用于在 python 中构建轻量级 web 应用程序。如上所述,在这个项目中使用 Flask 的目的是构建一个最终用户可以与之交互的 web 应用程序。

在本节中,我将使用在步骤 1 中创建的模型 pickle 文件,并将其集成到 web 应用程序中。web 应用程序的前端将允许用户向模型提供输入。这将被获取并传递给在后端运行的模型以进行预测。最后,我们将检索模型的预测,并将其显示给用户。

#1 项目要求

要在 Flask 中构建 web 应用程序,应该在 python 中安装 Flask 库。打开命令行,键入以下命令来安装 Flask:

pip install Flask

#2 Web 应用程序工作流程

暂时忽略技术实现,web 应用程序的分步预期工作流应该如下(通俗地说):

web 应用程序工作流(图片由作者提供)

  • 首先,我们显示一个带有表单的 HTML 页面,供用户输入。此外,用户应该单击“预测”按钮来了解相应的输出。
  • 一旦用户点击“预测”,web 应用程序应该获取输入值,并将其带到后端进行进一步处理。
  • 下一步是根据获取的值计算输入特征,并将它们作为输入提供给训练好的模型以生成输出。
  • 最后,应用程序应该将预测值传送到 HTML 页面并显示给用户。

#3 实施

要在 Flask 中构建这个 web 应用程序,我们需要编写两个文件并将它们集成在一起。这些是:

  1. app.py:这个文件包含了与网页交互的 Flask APIs。它负责获取输入值,在加载模型后计算预测,并将其返回到 HTML 文件。
  2. new.html:顾名思义,这个文件包含了我们的 web 应用程序的前端,用户将会看到它。

应用程序的当前目录结构是:

Linear_Regression_Model
├── templates
│   ├── new.html
├── app.py
└── model.pickle

这两个文件的实现如下所示:

web 应用程序的实现(图片由作者提供)

  • app.py文件定义了两种方法,new()predict()new()方法对应 app 的“/” URL,返回new.html网页。
  • predict()方法用于计算模型的预测值。当用户点击网页上的“预测按钮时,一个 POST 请求被发送到predict()方法。
  • 一旦模型给出了它的预测,我们再次呈现new.html页面并发送一个prediction_statement显示给用户。

#4 应用程序演练

要执行应用程序,运行项目目录中的python app.py。一旦服务器开始运行,在任何本地浏览器中前往[http://127.0.0.1:5000/](http://127.0.0.1:5000/)打开应用程序。

此应用程序的演练如下所示:

Flask web 应用程序的演练(Gif 由作者提供)

如上面的 gif 所示,用户得到一个输入值的表单。这被带到后端进行处理。一旦模型做出预测,预测值就会显示给用户。

步骤 3:将应用程序部署到 Heroku 服务器上

现在我们已经训练了机器学习模型,并将其集成到 web 应用程序中,我们的最后一步是将应用程序部署到 Heroku 服务器上,这是一个免费的云即服务平台,可以部署任何 web 应用程序。

支持的编程语言有 Java、PHP、Python、Go 等。此外,大多数数据科学家使用 Heroku 来获得在云上部署模型的实践经验。但是,在部署应用程序之前,您应该在 Heroku 上创建一个帐户。

为了将代码推送到 Heroku 服务器并进行部署,Heroku 提供了三种不同的方式:Heroku git、GitHub 和容器注册表。

将代码部署到 Heroku 服务器的选项(图片由作者提供)

在本文中,我将使用 Heroku Git 来部署这个模型。

现在,让我们从部署流程开始。

#1 安装要求

就包需求而言,您应该用 python 安装[gunicorn](https://pypi.org/project/gunicorn/)包,如下所示:

pip install gunicorn

接下来,您应该安装 Heroku 命令行界面(Heroku-CLI)。根据您的操作系统,您可以在这里找到说明。

#2 将 requirements.txt 和 Procfile 添加到目录中

在将代码推向部署之前,您应该在requirements.txt文件中指定项目的需求。该项目的文件如下所示:

Flask==2.1.0
Werkzeug==2.0.3
numpy==1.21.5
gunicorn==20.1.0
jinja2
Werkzeug
itsdangerous

接下来,创建一个新文件,命名为Procfile,并添加以下命令:

web: gunicorn app:new

本质上,该文件让生产环境知道 app 文件中的哪个函数是主方法。此外,它还提供了应用程序在启动时将运行的命令。app:new部分表示主文件是app.py,其中new()方法是主要函数。

完成此步骤后,您的目录结构应该如下所示:

Linear_Regression_Model
├── templates
│   ├── new.html
├── app.py
├── model.pickle
├── requirements.txt
└── Procfile

#3 在 Heroku 服务器上创建应用程序

导航至您的账户仪表盘并选择Create new app

在 Heroku 帐户仪表板上创建新应用程序的选项(图片由作者提供)

接下来,为您的应用程序键入一个名称,并选择Create app

在 Heroku 上输入要托管的应用程序名称的字段(图片由作者提供)

一旦创建了应用程序,就可以开始部署过程,这将在下一步中演示。

#4 将应用程序部署到 Heroku 服务器

首先,选择Heroku Git作为“部署方式”。

将代码部署到 Heroku 服务器的选项(图片由作者提供)

打开本地计算机上的终端并登录 Heroku。确保您已经安装了 Heroku-CLI。

$ heroku login

接下来,导航到您的项目并初始化一个 git 存储库,如下所示:

$ cd my-project/
$ git init
$ heroku git:remote -a linearregressiontest

现在,将您的代码提交到存储库,并使用 Git 将其部署到 Heroku。

$ git add .
$ git commit -am "deployment step 1"
$ git push heroku master

如果部署成功,您应该会在命令行中看到以下日志:

描述成功部署的图片(图片由作者提供)。

这样,你的机器学习模型就部署成功了!可以通过以下地址访问该应用程序:

https://your-app-name.herokuapp.com/

结论

总之,在这篇文章中,我详细介绍了如何从头开始部署机器学习模型。

具体来说,我首先演示了简单线性回归模型的训练,然后将它集成到使用 Flask 开发的 web 应用程序中。

最后,我展示了使用 Heroku Git 将 web 应用程序部署到 Heroku 服务器的分步过程。

你可以在这里找到这篇文章的代码。

感谢阅读!

🧑‍💻成为数据科学专家!获取包含 450 多个熊猫、NumPy 和 SQL 问题的免费数据科学掌握工具包。

✉️ 注册我的电子邮件列表 永远不要错过关于数据科学指南、技巧和提示、机器学习、SQL、Python 等的另一篇文章。Medium 会将我的下一篇文章直接发送到你的收件箱。

使用 TensorFlow 服务部署机器学习模型—简介

原文:https://towardsdatascience.com/deploying-machine-learning-models-with-tensorflow-serving-an-introduction-6d49697a1315

从初始环境设置到使用 TensorFlow 服务和 Docker 服务和管理多个模型版本的分步教程

Patrick Robert Doyle 在 Unsplash 拍摄的照片

目录

  1. 简介
  2. 环境设置
  3. 创建机器学习模型
    3.1 数据生成
    3.2 分割训练、验证和测试集
    3.3 训练并保存回归模型
  4. 服务于模型
    4.1 安装 TensorFlow 服务于 Docker
    4.2 服务于最新模型
    4.3 服务于多个模型版本
    4.4 将自定义标签应用于模型版本
    4.5 随着时间的推移自动重新加载配置
  5. 结论
  6. 参考文献

1.介绍

这篇文章涵盖了使用 TensorFlow Serving 作为 web 服务开始服务机器学习模型所需的所有步骤,TensorFlow 是一个灵活的高性能服务系统。

在这个例子中,我们将建立一个虚拟环境,在这个环境中,我们将为一个回归问题生成合成数据,训练多个模型,最后将它们部署为 web 服务,从 REST APIs 访问预测。

本教程的唯一先决条件是安装了 PythonDocker 引擎的工作机器。我们将最终使用 curl⁴ 来编写 API 调用,并通过它们的预测端点来消费机器学习模型。

2.环境设置

一个virtual environment是一个自洽的 Python 环境,可以创建它来管理和分离项目:它提供了隔离,因此依赖关系不会影响同一操作系统上的其他包。

对于本教程,我们在myProject文件夹中创建一个virtual environment。从命令行:

# create the virtual environment
python -m venv /myProject# activate the environment
myProject\Scripts\activate

一旦环境被激活,我们就可以安装所需的依赖项:

  • pip install scikit-learn利用便捷的数据准备方法;
  • pip install tensorflow用于机器学习开发;
  • pip install matplotlib以可视化方式探索数据和模型度量;
  • pip install jupyter使用笔记本。

安装完依赖项后,我们通过执行以下命令来启动 Jupyter Notebook:

jupyter notebook

从 Jupyter Notebook web 界面,我们可以在myProject文件夹中创建一个笔记本(create_models.ipynb),因为我们将使用它来生成通过 TensorFlow 服务提供的机器学习模型。

3.创建机器学习模型

从我们的笔记本开始,我们导入以前安装的依赖项:

3.1 数据生成

我们生成如下合成数据:

图片作者。

3.2 培训、验证和测试集的划分

我们将数据集分为:

  • 训练和验证组:在训练过程中使用。
  • 测试集:用于估计样本外性能。

我们观察获得的集合:

训练、验证和测试集。图片作者。

当我们训练一个新的模型时,我们希望将它存储在我们项目根的一个子文件夹中,这个子文件夹被随意命名为saved_models。在这个空间中,我们将每个模型保存在一个专用的目录中,该目录用增量整数命名:

3.3 训练和保存回归模型

我们拟合了由致密层构成的第一个简单模型:

第一个模型的训练历史。图片作者。

现在,让我们创建另一个略有不同的模型:

第二个模型的训练历史。图片作者。

我们可以观察两个不同模型的测试集预测:

测试集预测。图片作者。

通过浏览./myProject/saved_models文件夹的内容,我们可以观察到训练好的模型保存在专用目录中:

保存在 saved_models 文件夹中的增量命名目录中的模型。

我们的最终目标是探索如何使用 TensorFlow 服务将一组给定的模型部署为用于推理的 web 服务。因此,我们不会更深入地研究建模任务,尽管人们可能会测试不同的模型或进一步改进训练策略。

4.为模特服务

4.1 使用 Docker 安装 TensorFlow 服务

开始 TensorFlow 服务最简单的方法是拉最新的 docker image⁵.从命令行:

docker pull tensorflow/serving

提取映像后,我们可以通过运行以下命令来检查其可用性:

docker images

拉最新的 TensorFlow 服务 Docker 图片。图片作者。

4.2 提供最新型号

我们从提取的图像创建一个运行容器:

docker run --name myTFServing -it -v C:\myProject:/myProject -p 9001:9001 --entrypoint /bin/bash tensorflow/serving

让我们更详细地研究一下这个命令:

  • 从输入图像创建一个容器。
  • --name <myName>设置一个名称来标识 Docker 容器。
  • -it在交互模式下启动容器。
  • -v <host_volume>:<container_volume>将主机上的卷绑定到容器内的目录。在我们的例子中,容器将从容器内的/myProject目录访问主机上的项目文件夹C:\myProject
  • -p <host_port>:<container_port>将主机的端口绑定到容器的端口。
  • --entrypoint指定容器启动时应该运行的可执行文件。在我们这里,/bin/bash
  • tensorflow/serving是从中派生容器的图像的名称。

从容器内部,我们检查是否存在/myProject文件夹及其内容,这应该与主机上的C:\myProject相同:

由于 Docker 卷,容器可以访问主机上的项目根。图片作者。

当我们在容器内部时,我们启动 TensorFlow,如下所示:

tensorflow_model_server 
  --rest_api_port=9001 
  --model_name=regression_experiments 
  --model_base_path=/myProject/saved_models

我们注意到,我们将存储模型的文件夹传递给了model_base_path标志,并用model_name为模型指定一个任意的名称。该名称将成为 TensorFlow 服务公开的端点的一部分。

一旦命令被执行,日志显示只有最新的模型被加载用于推断:

默认情况下,会加载最新的模型进行推理。图片作者。

我们可以通过从容器外部使用curl执行 API 调用来测试预测。例如,我们从获得可用模型开始:

curl -X GET http:/localhost:9001/v1/models/regression_experiments

该调用返回:

图片作者。

事实上,默认情况下,TensorFlow Serving 只自动加载 model_base_path 中可用的不同模型的最新版本。

我们测试一个预测如下:

curl -X POST "http://localhost:9001/v1/models/regression_experiments:predict" ^
-H "Content-Type: application/json" ^
-d "{\"instances\":[[1.0], [2.0], [5.0]]}"

TensorFlow 服务成功服务于 model_base_path 中的最新模型。图片作者。

注意事项

  • 在 Windows 机器上,^字符可以用于 curl 语句中的换行符。在 MacOS 或 Unix 系统上,应该使用反斜杠\字符。
  • 人们可能想交替使用双引号"和单引号'来编写 curl 语句。例如,通过键入:-d '{"instances":[..]}'。在 Windows 上,这可能会导致以下消息:{"error":"Malformed request: POST /v1/models/regression_experiments/predict"},或者其他 curl / JSON 解析错误。为了避免任何问题,命令应该只包含双引号"(嵌套时用反斜杠屏蔽)。

4.3 提供多种型号版本

在真实的场景中,我们可能需要一次公开多个模型。例如,我们可能希望逐渐将流量从服务的先前版本切换到新版本(蓝绿色部署),或者我们可能需要随机地将用户重定向到多个共存版本中的一个以进行测试( A/B 测试)。

我们可以很容易地指示 TensorFlow Serving 加载不同的模型版本,并使用配置 files⁶ 进行推理。

myProject文件夹中,我们如下创建cfg1.conf文件:

model_config_list {
  config {
    name: 'regression_experiments'
    base_path: '/myProject/saved_models'
    model_platform: 'tensorflow'
    model_version_policy: {all: {}}
  }
}

在这个文件中,我们设置了一个策略,指示 TensorFlow 考虑给定基本路径中的所有可用模型。

我们从容器开始 TensorFlow 服务,如下所示:

# entering the container in interactive mode
docker exec -it myTFServing bin/bash# starting TensorFlow Serving with configuration file
tensorflow_model_server 
  --rest_api_port=9001 
  --allow_version_labels_for_unavailable_models 
  --model_config_file=/myProject/cfg1.conf

从服务日志中,我们可以看到现在两个型号版本都是在启动时加载的:

服务启动时的 TensorFlow 服务日志。图片作者。

让我们用来自容器外部的 GET 请求来检查可用的模型:

curl -X GET http:/localhost:9001/v1/models/regression_experiments

图片作者。

我们现在可以对服务执行外部 API 调用,并随意将流量重定向到任何所需的版本,如下所示:

# call to model version 1
curl -X POST
"http://localhost:9001/v1/models/regression_experiments/***versions/1:predict***" ^
-H "Content-Type: application/json" ^
-d "{\"instances\":[[1.0], [2.0], [5.0]]}"# call to model version 2
curl -X POST "http://localhost:9001/v1/models/regression_experiments/***versions/2:predict***" ^
-H "Content-Type: application/json" ^
-d "{\"instances\":[[1.0], [2.0], [5.0]]}"

不同模型版本的预测。图片作者。

4.4 将自定义标签应用于模型版本

我们可以很容易地将字符串标签应用到模型 versions⁶.通过这种方式,可以向我们的预测服务添加一个“语义抽象”层,从而提高可读性并促进 DevOps 实践。例如,通过 REST 接口消费我们的模型的集成层可能更喜欢调用“生产或“测试”版本,而不是像“ 23 或“ 57 ”这样的随机整数。

这一结果可以通过在配置文件中指定所需的标签来实现。让我们在项目目录中创建一个cfg2.conf文件,如下所示:

model_config_list {
  config {
    name: 'regression_experiments'
    base_path: '/myProject/saved_models'
    model_platform: 'tensorflow'
    model_version_policy {
      specific {
        versions: 1
        versions: 2
      }
    }
    version_labels {
      key: 'production'
      value: 1
    }
    version_labels {
      key: 'test'
      value: 2
    }
  }
}

在这个文件中,我们将我们的模型版本分别分配给productiontest标签。我们现在可以启动服务了:

# entering the container in interactive mode
docker exec -it myTFServing bin/bash# starting TensorFlow Serving with configuration file
tensorflow_model_server 
  --rest_api_port=9001 
  --allow_version_labels_for_unavailable_models 
  --model_config_file=/myProject/cfg2.conf

服务启动后,我们可以执行外部 API 调用。值得注意的是,这次终点将是/v1/models/<model_name>/**labels**/而不是/v1/models/<model_name>/**versions**/:

# call to production model
curl -X POST
"http://localhost:9001/v1/models/regression_experiments/***labels/production:predict***" ^
-H "Content-Type: application/json" ^
-d "{\"instances\":[[1.0], [2.0], [5.0]]}"# call to test model
curl -X POST "http://localhost:9001/v1/models/regression_experiments/***labels/test:predict***" ^
-H "Content-Type: application/json" ^
-d "{\"instances\":[[1.0], [2.0], [5.0]]}"

从模型的生产实例进行推断。图片作者。

4.5 随着时间的推移自动重新加载配置

我们在启动时通过将配置文件传递给--model_config_file标志来使用它们。

我们还可以传递--model_config_file_poll_wait_seconds标志来指示 TensorFlow Serving 定期检查指定路径的配置文件中的更新。例如,语句

# entering the container in interactive mode
docker exec -it myTFServing bin/bash# starting TensorFlow Serving
tensorflow_model_server 
  --rest_api_port=9001 
  --allow_version_labels_for_unavailable_models 
  --model_config_file=/myProject/cfg2.conf 
  --model_config_file_poll_wait_seconds=30

将基于来自cfg2.conf文件的配置启动myTFServing容器中的服务,并且更新将被定期提取。我们可以从日志中验证系统如何每 30 秒检查一次更新:

模型 _ 配置 _ 文件 _ 轮询 _ 等待 _ 秒标志的效果。图片作者。

5.结论

在这篇文章中,我们探讨了如何使用 TensorFlow 服务部署模型,使得预测可以通过 REST APIs 轻松访问。

特别是我们从零开始。我们创建了一个虚拟环境,在该环境中,我们安装了生成合成数据和适应多个模型所需的最小依赖集。然后,我们提取 TensorFlow 服务 Docker 映像,并从中创建一个运行容器,涵盖了管理和服务多个模型版本所需的所有步骤。

6.参考

[1]https://www.tensorflow.org/tfx/serving/architecture

https://docs.python.org/3/using/

https://docs.docker.com/engine/install/

https://curl.se/

https://www.tensorflow.org/tfx/serving/docker

https://www.tensorflow.org/tfx/serving/serving_config

使用 Streamlit 部署 ML 模型

原文:https://towardsdatascience.com/deploying-ml-models-using-streamlit-5d6212453bdd

使用 XGBoost & Streamlit 快速构建和部署 Python 应用程序

凯利·西克玛在 Unsplash 上的照片

GitHub 回购

Streamlit App

介绍

实话实说:很难让普通人对数据科学感兴趣。

即使在专业环境中,如果决策者不认同你工作的潜在好处,要获得他们的认同也可能是一场艰苦的战斗。

通常,我会收集客户的数据,进行分析,展示我的发现,并提出建议,只让他们忽略这些数据,做他们想做的事情。

我也不是在批评以前的客户。数据科学不是一个特别容易理解的学科,它如何工作的细节也不会成为引人入胜的内容。作为数据科学专业人员,我们有责任说明我们所做工作的价值,如果我们想最大限度地发挥其影响,就要真正赋予它生命。

写作的金科玉律是“秀,不要说”。我们希望读者通过思想、行动、情感和感官而不是枯燥的阐述来体验故事。对于数据科学家来说,我认为类似的原则也适用。当你只是告诉决策者你的结论和建议时,很难说服他们。如果你能有效地向他们展示你是如何做到这一点的,那会更有说服力,如果你能给他们工具让他们自己得出同样的结论,那就更有影响力了。

“教人钓鱼,你就喂了他们一辈子”,或者其他什么。

格雷森·约翰逊Unsplash 上拍摄的照片

宗旨声明

考虑到这一点,让我们来看看如何使用 Streamlit 将您创建的机器学习模型转化为一个简单的应用程序。但首先:为什么这值得去做?

基本原理

我认为,对于刚开始接触数据科学的人来说,了解如何部署他们创建的模型非常重要,原因有两个:

  1. 正如我上面提到的,它使你的工作更容易被非技术涉众所理解(因此也更有影响力)。
  2. 拥有一个切实可行的工具/应用程序是你投资组合中的一大亮点。
  3. 将你的模型从 Jupyter 笔记本中拿出来放到“真实世界”中,可以确保你的作品得到更多的曝光和实际应用。
  4. 能够快速创建提议的数据科学解决方案的原型/MVP 是专业环境中一项非常宝贵的技能。

数据

我在这个项目中使用的数据集是 Kaggle 的钻石数据集。根据 CC0,它被授权作为公共领域的一部分使用,可以通过上面的链接获得。感谢 Kaggle 用户 natedir 的维护。

我喜欢这个数据集,因为这是一个相当简单的线性回归问题,我们需要解决一些重要的问题。对于流量更大的线性回归数据集,如房地产、身体质量指数和医疗保险,这也是一个很好的替代方案。除此之外,它还有令人尊敬的 50,000+次观察,是一个有趣的预测应用程序。

目标

目标是使用数据集中的特征来预测钻石的价格。一旦我们有了一个表现令人满意的模型,我们将继续使用 Streamlit 在它的基础上构建应用程序。

Lucas SantosUnsplash 上拍摄的照片

构建模型

首先,让我们介绍一下我们将用来创建模型的库:

接下来,我们将读入数据(在 GitHub repo 中以 diamonds.csv 的形式提供)并检查前 20 个条目:

作者图片

以及其他一些杂项检查:

如果您继续跟进,我们的数据集中应该有 53,940 个观察值和 10 个要素。不存在缺失值,除切割、颜色和净度之外的所有变量都是数字。让我们深入研究这三个变量。

数据准备

通过检查这些变量的值,我们可以看到它们是分类的:

更重要的是,稍微搜索一下互联网就会发现,这些变量中的值有一个内在的层次结构。每颗钻石都有切割、颜色和净度等级。对于 Cut,等级(从最高到最低)为理想>优质>非常好>良好>一般。

由于这种层次结构,我们可以称这些序数分类变量。如果我们想要以数字形式适当地捕捉信息,我们必须根据固有的标度对它们进行编码。

对于每个变量中如此少的类别,最简单的方法是使用映射,但是 scikit-learn 也有一个OrdinalEncoder类:

一旦我们编码了序数变量,让我们去掉任何离群值和/或无意义的数据。

首先,任何 X、Y 或 Z 维度为 0 的观测值都将获得轴:

然后,我们将根据几个不同的变量将数据集削减到第 99 个百分位数,以排除最严重的异常值。这绝不是处理异常值的细微方法,在现实场景中,这部分工作需要付出更多的努力:

现在,数据已经为建模做好了准备,让我们来看看关联热图,看看我们可以预期哪些功能会影响价格以及如何影响:

作者图片

创建模型

首先,我们将进行标准的培训/验证/测试划分:

现在,因为我很懒,这是一个示例问题,所以我将向 XGBoost 回归器抛出一个 GridSearchCV,以快速得出最佳拟合。同样,不要在真实的场景中这样做,这很笨拙(也需要很长时间来运行):

验证分数:

  • 验证平均绝对误差: $235.76 美元
  • 验证 R2 评分: .98

测试分数:

  • 测试平均绝对误差: $229.80 美元
  • 测试 R2 得分: .98

既然我们对模型的性能感到满意(因为我是这么说的),我们可以继续保存它:

让我们继续使用 Streamlit 构建预测器应用程序!

构建应用程序

Streamlit 通过直接从公共 GitHub 存储库中读取代码来工作。因此,要使应用程序工作,我们需要一个包含以下内容的回购:

  • 的。JSON 文件,这是我们的模型。
  • requirements.txt 文件,该文件指定了 Streamlit 运行应用程序所需安装的 Python 包。
  • 答。创建和定义我们的 Streamlit 应用程序的 py 文件。

你可以在这里找到我的回购。我的 Streamlit 应用程序在这里。让我们来看看如何构建这个应用程序。

一、requirements.txt:

简单!最好的做法是包含您需要的每个包的版本,但是我没有在这里包含它,因为这又是一种懒惰。不要像我一样。

一旦我们告诉 Streamlit 要安装哪些包,我们就可以编写代码在我们的。py 文件:

在这里,我们已经从 JSON 文件中加载了我们的模型,并告诉 Streamlit 将它缓存到以便快速访问。

接下来,我们将定义一个接受用户输入(钻石特征)并输出价格预测的函数:

你会注意到我们再次对顺序变量进行了编码——这是因为我们将以原始格式接收这些值。

此外,由于该模型最初是使用 pandas 数据框架训练的,它坚持以类似的格式获取新数据。这就是为什么我们必须使用 pd.DataFrame 将变量提供给模型的预测方法。

定义了预测函数后,我们需要创建应用程序本身,以及用户输入的区域。先说 app:

这给了我们一个非常基本的标题和图像的登陆页面,以及一个带有说明的标题。

Streamlit 的好处在于它可以快速读取 GitHub repo。当您对代码进行小的更改并将它们提交给 repo 时,您的 Streamlit 应用程序将在稍后更新以反映这些更改。当您可以增量地完成时,它使格式更改和故障排除变得不那么痛苦。

一旦我们决定了我们的设计选择,我们定义用户输入:

这创建了许多数字输入和下拉菜单字段,与我们的模型输入一致。您可以定义数值输入的范围和最小值,以及分类输入的选项。

最后,我们需要定义一个调用预测函数的输入:

这将创建一个“预测价格”按钮,单击该按钮将输出模型对给定变量的预测。这是整个过程的 gif 图片:

正如你所看到的,创建这个“应用”所需的代码量很少,但现在任何人都可以使用它,并获得钻石价格的粗略估计。整洁!

结束语

能够快速创建交互式工具是非常有影响力的。对于刚开始接触数据科学的人来说,用自己编写的代码构建一个真正的“东西”是值得的。对于专业人士和执业数据科学家来说,能够以一种易于理解的格式展示您的工作有助于向利益相关者和决策者推销您的想法。

Streamlit 赋予我们做这些事情的能力。它快速、无痛苦、灵活,并且是 Python 所固有的。对于生产级别的部署来说,它可能不够健壮,但是值得您花时间来学习用于原型、模型、MVP、内部工具和个人项目的包。

我现在就知道这么多。如果您有任何问题或评论,请留言,感谢您的阅读!

Teemu PaananenUnsplash 上的照片

使用 CloudFormation 部署 SageMaker 端点

原文:https://towardsdatascience.com/deploying-sagemaker-endpoints-with-cloudformation-b43f7d495640

使用 SageMaker 将基础设施作为代码

图片来自 Abraham BarreraUnsplash

过去,我通过 Jupyter 笔记本和 Python 脚本与 SageMaker Deployment 合作过。这完全没问题,但通常在较大的应用程序范围内,您需要能够在一个中心模板中定义 SageMaker 资源和基础设施的其余部分。这引入了作为代码的基础设施的概念,然后引入 AWS CloudFormation 。当涉及到生产应用程序时,能够在各种中央模板中捕获您的资源是至关重要的,维护或管理笔记本或单个脚本的隔离流程变得非常困难。

在本文中,我们将了解如何使用 CloudFormation 来定义 SageMaker 资源并创建实时端点。利用这个模板,您应该能够为其他 SageMaker 推理选项(如多模型端点、无服务器推理和异步推理)推断和创建云信息模板。

注意:对于刚接触 AWS 的人来说,如果你想继续下去,请确保在下面的链接中建立账户。确保还安装了 AWS CLI 来处理该示例。这篇文章也将假设云形成的基本知识,如果你需要一个入门指南,在这里看一下这篇文章。本文还假设对 SageMaker 部署有一个中级理解,我建议遵循本文为了更深入地理解部署/推理,我们将在本文中使用相同的模型,并将其映射到 CloudFormation。

设置

在开始构建 CloudFormation 模板之前,我们需要了解我们的 SageMaker 端点需要什么。对于这个用例,我们将在 SageMaker 实时端点上部署一个预训练的 Sklearn 模型。利用下面的脚本,我们可以快速运行一个线性回归模型并生成一个模型数据工件。

本地模型

运行这个脚本后,您应该得到一个 model.joblib 文件,它包含部署所需的模型元数据。一般来说,使用 SageMaker 推理,您还需要一个推理脚本来控制自定义代码的前/后处理。

推理脚本

SageMaker 推理期望将这个模型数据和推理脚本打包成一个 tarball,因此我们运行下面的脚本将这些资源转换成期望的格式,并将其上传到 S3 存储桶。

创建并上传 model.tar.gz

现在我们已经有了模型工件,我们可以继续处理云的形成。

定义云形成参数

一个 CloudFormation 模板是一个 yaml 或 json 文件,你可以在其中定义你所有的基础设施。 CloudFormation 参数允许你在模板中注入自定义值。然后在定义资源时,您可以引用这些参数。在这种情况下,我们提供默认值,但是如果您愿意,您可以通过 CLI 覆盖它们。对于 SageMaker 端点,我们必须定义以下参数(注意,您可以随意命名这些参数,但要确保在命名时引用它们):

  • RoleARN :您授予权限的 SageMaker 执行角色。用您为 SageMaker 资源定义的 IAM 角色替换默认角色值。

  • ImageURI :这是 URI 的图像,你可以从现有的深度学习容器中检索到,或者如果你做了 BYOC 部署,你可以从 ECR 中自己定制图像 URI。对于这个例子,我们有一个 Sklearn 模型,所以我们已经用检索了那个托管容器的适当版本。

Sklearn 图像

  • 这是我们打包在一起并上传到 S3 桶的模型工件和推理脚本。

  • instance type&instance count:您正在为端点定义的硬件,针对无服务器推理(内存大小&并发)对其进行适当的更改。

实例配置

我们现在已经有了部署 SageMaker 实时端点所必需的参数,接下来让我们专注于定义我们的资源。

云信息资源和部署

为了部署 SageMaker 端点,有三个主要的实体协同工作: SageMaker 模型SageMaker 端点配置SageMaker 端点。SageMaker 模型实体定义了我们用于部署的模型数据和图像,并且是我们创建的第一个资源。

SageMaker 模型实体

注意,我们引用了我们已经定义的 ImageURI 和模型数据参数。接下来,我们对端点配置做了同样的事情,我们在端点后面定义了实例配置。

SageMaker 端点配置

现在,我们在定义 SageMaker 端点的最后一步时指向这个资源。

SageMaker 端点创建

使用 AWS CLI,我们可以通过指向我们的 yaml 文件来部署这个 CloudFormation 堆栈。

部署云架构堆栈

我们可以在控制台中验证这一点,几分钟后,您应该会看到所有三个资源都已创建。

云形成堆栈完成(作者截图)

SageMaker 端点已创建(作者截图)

其他资源和结论

https://github.com/RamVegiraju/cloudformation-sagemaker-ep

您可以在上面的链接中找到示例的完整代码。AWS CloudFormation 是一个非常强大的工具,它使得在一个中央模板中捕获您的 AWS 资源变得非常容易。没有作为代码的基础设施,在软件生命周期中迭代变得非常困难,这也适用于 ML 服务,如 SageMaker。我希望这篇文章对那些对 SageMaker、CloudFormation 和 AWS 感兴趣的人有用。

如果你喜欢这篇文章,请在LinkedIn上与我联系,并订阅我的媒体 简讯 。如果你是新来的中号,用我的 会员推荐 报名吧。

使用 ONNX 在 Android 应用中部署 Scikit-Learn 模型

原文:https://towardsdatascience.com/deploying-scikit-learn-models-in-android-apps-with-onnx-b3adabe16bab

📱移动机器学习

使用 scikit-learn 模型在 Android 应用程序中执行推理

图片由佩克斯的普兰拍摄

Scikit-learn 确实是一个彻底改变了机器学习和数据科学的包,并且仍然是任何 ML/DS 角色的最基本的先决条件。但是,随着机器学习领域从研究走向工业,ML 模型的部署现在在软件开发周期中起着至关重要的作用。

但是我们大多数人只能在 Python 脚本或 Jupyter 笔记本中运行 scikit-learn 模型,并且只有有限数量的博客/视频讨论了它们的部署。使用 FlaskDjangoFastAPI 之类的 web 框架可以轻松部署,这些框架可以帮助构建一个 API 来连接应用程序代码和 ML 代码。

部署能够为移动应用带来创新功能的 ML 模型将是一件大事,因为智能手机是唯一始终伴随用户并处理其大部分工作负载的设备。因此,在这个故事中,我们将讨论如何使用 ONNX 在 Android 应用程序上部署 scikit-learn 模型,这将充当两个世界之间的桥梁。如果你不知道 ONNX,不要担心,即使我在做这个故事之前不知道!

作为一名 Android 开发人员,我可以从头到脚讨论一个 ML 模型在 Android 上的部署过程。对于 iOS 或跨平台框架,也可以遵循类似于我们将要讨论的过程。

首先,我们将把 scikit-learn 模型转换成 ONNX,然后在 Android 应用程序中使用这个 ONNX 模型。

Android 应用程序的源代码可以在这里找到->

https://github.com/shubham0204/Scikit_Learn_Android_Demo Shubham Panchal

舒巴姆·潘查尔

📱Android 中的移动机器学习

View list17 stories

1.获取 ONNX 模型(Python 语言)

ONNX 是什么?

ONNX 代表开放式神经网络交换,这是一种用于将 ML 模型从一个框架轻松转换到另一个框架的中间表示。这是一个由微软、Amazon 和 Meta 共同开发的开源项目,旨在为部署和运行 ML 模型带来健壮性,从而使其框架不变。

例如,如果您必须将一个 PyTorch 模型转换为一个 TensorFlow 模型,您可以首先将其转换为 ONNX 模型。 TensorFlow 支持加载 ONNX 模型,这样你就可以从 ONNX 模型中得到一个 TF 模型。这里有一个关于 ONNX 框架的详细博客,

https://blog.roboflow.com/what-is-onnx/

A.构建 Scikit-Learn 模型

第一步是用 Python 构建一个 scikit-learn 模型。为了演示部署过程,我们将使用一个简单的LinearRegression模型来进行预测。你可以从 Kaggle 下载 Himanshu Nakrani学生学习时数数据集。数据集可通过 CC 1.0 公共域许可证获得。

代码片段 1:构建一个 scikit-learn 模型

运行上面的代码片段并记下输出。我们将使用相同的输入变量,即 8.5,在 Android 应用程序中进行预测

B.安装 sklearn2onnx 和 onnx 转换

我们将安装 sklearn-onnx ,这将帮助我们将 scikit-learn 模型转换为 onnx 模型(.onnx)。

pip install skl2onnx

sklearn-onnx 可以转换几乎所有的 scikit-learn 模型和预处理组件,你可以在这里找到一个列表。接下来,我们转换 scikit-learn 模型,

代码片段 2:转换为。onnx

C.转换到 ONNX 运行时(可选)

这一步是可选的,我们可以直接在 Android 中运行.onnx模型。由史葛麦凯Scikit _ Learn _ Android _ Demo中突出显示的点,

它(ORT 格式)的主要好处是,如果二进制大小是一个大问题,允许使用较小的版本(onnxruntime-mobile android 包)。ONNX 运行时对两种模型格式都进行了相同的图形优化——它们只是在创建。ort 格式模型,这样我们就不必在较小的构建中包含优化器代码。

然而,由于 onnxruntime-mobile 包所使用的操作符/类型,您的模型可能不被它支持,所以使用带有 onnxruntime-android 的. onnx 模型是最简单和最安全的选择。

如果二进制大小是一个问题,并且 onnxruntime-mobile 不支持您的模型,那么您可以进行自定义构建,以创建一个只包含您的模型所需的操作符和类型的包。这提供了可能的最小二进制大小。

注意,到目前为止,我们只将 scikit-learn 模型转换为 ONNX。ONNX 是一种表示机器学习模型的开放格式。在不同的系统或设备上运行 ONNX 模型或加速 ONNX 的推理需要运行时。运行时应该能够解析 ONNX 模型格式并从中做出预测。这样的运行时是来自微软开源的 onnxruntime 。

我们现在将把.onnx模型转换成.ort格式。官方文件上说,

ORT 格式是精简 ONNX 运行时版本支持的格式。缩减规模的构建可能更适合在规模受限的环境中使用,如移动和 web 应用程序。

pip install onnxruntime

接下来,我们需要使用convert_onnx_models_to_ort脚本来执行转换,比如,

!python -m onnxruntime.tools.convert_onnx_models_to_ort sklearn_model.onnx

你也可以在 Python 中运行.ort构建。看到这个官方教程,

接下来,我们继续讨论 Android 部分,并讨论如何使用.ort模型进行预测。

2.在 Android 应用程序中使用模型(在 Kotlin 中)

您可以打开一个带有所需配置的新 Android Studio 项目,并在build.gradle文件(模块级)中添加[onnxruntime](https://mvnrepository.com/artifact/com.microsoft.onnxruntime/onnxruntime) Maven 依赖项

implementation 'com.microsoft.onnxruntime:onnxruntime-android:latest.release'

A.创造一个OrtSession

首先,我们需要复制.ort.onnx文件并将其添加到app\src\main\res\raw文件夹中。创建新的 Android 应用程序项目时,最初不会创建此文件夹。如果你还没有创建raw文件夹,请参考的回答

“原始”文件夹中的 ORT 文件。图片来源:作者

接下来,在MainActivity.kt中定义一个方法来创建一个带有OrtEnvironmentOrtSession

代码片段 3 —创建一个运动会话

B.执行推理

接下来,我们需要使用我们在上一步中创建的OrtSession运行 ORT/ONNX 文件。这里有一个方法runPrediction可以做到这一点,

片段 4 —使用运动会话进行预测

我们用我们在步骤 1-B 中提供的形状initial_type创建一个OrtTensor。然后,我们只需要运行ortSession.run来运行 ORT 模型。通过 Android 中的一些自定义输入,我们可以在MainActivity.kt中的onCreate方法中添加以下几行,

代码片段 5——接受用户的输入并做出预测

运行应用程序,亲自体验神奇之处。该应用程序在 8.5 小时的学习中产生的输出(输入变量)与在 Python 中产生的输出相同,

应用程序的演示。图片来源:作者

结束了

到目前为止,您可能已经意识到,这种转换的过程非常简单,不包括任何跨平台的转换。许多开发者、创业公司和 ML 从业者可能已经想到了这样的转换,但是他们可能没有意识到这个简单的过程。

这可能是一个美化了的黑客,所以如果你在这个变通办法上面临任何问题,请在评论中分享它们,以便其他读者了解它们。你也可以在 GitHub 库上打开一个问题。

希望你觉得这个故事有趣。如果你有任何疑问,请在评论中告诉我,或者在equip intelligence @ Gmail上给我发消息。继续学习,祝你有美好的一天!

部署你的机器学习模型只是开始

原文:https://towardsdatascience.com/deploying-your-machine-learning-model-is-just-the-beginning-b4851e665b11

如何将 ML 模型转化为有用的商业行为:MLOps 入门

凯瑟琳·麦科马克在 Unsplash 上的照片

L 和许多刚开始从事 ML 的人一样,我接触到的第一个问题是泰坦尼克号数据集。这个问题的任务是在给定机票等级、客舱位置、性别、年龄等特征的情况下,“预测”一名乘客是否在泰坦尼克号灾难中幸存。虽然这是一个有趣的问题,但由于显而易见的原因,它实际上是无用的。没有人真的需要一个巨大的分类器。这个模型在现实世界中无法采取任何行动。

在现实世界中,行动很重要。一个实际上什么都不做的模型并不比完全没有模型好。然而,将 ML 模型转化为有用的商业行为的微妙之处通常不在 ML 研究的范围内。

在本帖中,我们将深入探讨:

  • 你的模型在现实世界中可以采取的一系列行动,
  • 如何监控模型的工作点,
  • 为什么在生产中既不能保证精度,也不能保证召回率(以及如何应对),以及
  • 如果循环中有人类注释者,应该考虑什么。

让我们开始吧。

您的 ML 系统可以采取哪些措施?

概括地说,您的模型可以采取 3 种类型的操作:

  • 软动作:把某样东西搁置一边供人调查,除此之外什么都不做。让人类决定并采取最终行动。
  • 硬动作:自动做一些事情(例如,将电子邮件移动到垃圾邮件文件夹,或者取消信用卡交易)。
  • 无操作/通过操作。什么都不做。

一个特定的 ML 应用程序可能需要在生产中采取几个行动,这取决于 ML 模型分数。例如,在信用卡欺诈检测系统中,您可以自动取消具有最高模型分数的订单(硬操作),通过具有最低模型分数的订单(无操作),并发送两者之间的所有信息以进行人工调查(软操作)。在垃圾邮件检测系统中,您可以自动将具有最高模型分数的电子邮件移动到垃圾邮件文件夹,并将具有中间模型分数的电子邮件标记为潜在垃圾邮件,但仍将其保留在用户的主邮箱中。

最后,排名模型又如何呢?它为搜索、订阅和广告等应用提供了动力。这里的操作是,嗯,排序:以尽可能好的顺序显示项目。

如何监控精度和召回

ML 课程教你精确度(正确的正面预测的比率)和回忆(我们的模型捕捉到的所有正面预测的比率)的重要性。这是一种交换,你可以牺牲一个来换取另一个。系统运行的精确度和召回率的精确组合称为运行点。但是我们实际上如何测量生产中的操作点呢?

先考虑测量精度的问题。如果你的系统采取软动作,那么精度相对容易跟踪。简单地跟踪人们对被搁置的项目所做的决定。如果你的系统采取艰难的行动,你可以建立一个控制组:对于体积的一小部分,让一个人来决定,而不是你的模型。那么控制组中的精度就是你的模型精度的估计。

衡量回忆可能要复杂得多。为了做到这一点,根据定义,你需要审核你的负面预测。这很棘手,因为典型的用例可能有不平衡的类。例如,假设您构建了一个欺诈检测系统,每天对 100 万个订单采取通过措施,您的召回率预计为 99.9%。这意味着,平均而言,您每天至少需要审核 1K 个否定,才能发现一个错误否定,如果这个数字具有统计意义,就需要更多。

因此,简单随机抽样的召回审核是非常不切实际的。一个更好的方法是重要性抽样,其中我们不是随机抽样否定,而是基于他们的模型分数。关键的想法是从具有高模型分数的数据点进行更多的采样,因为这是我们预期的大部分假阴性所在。在一篇博客文章中,谷歌研究人员 Arun Chaganty 展示了他如何使用重要性抽样将测量召回的成本降低了大约三分之一。

为什么在生产中既不能保证精度,也不能保证召回率(以及如何应对)

无论您如何在离线评估集上调整操作点,实际上您都无法保证您的系统在生产中达到一定的精度或召回率。这是因为数据漂移:生产中的数据分布总是与离线测试中的略有不同。数据漂移量取决于问题领域,在欺诈和滥用等敌对领域尤为严重。

这个问题怎么办?一个解决方案是添加一个缓冲区:例如,如果业务目标是 95%的精度,您可以尝试在离线评估集上将您的模型调整到 96–97%的操作点,以便考虑到数据漂移导致的预期模型降级。

对于 ML 团队来说,为商业利益相关者设定正确的期望也很重要。例如,我见过一个案例,ML 团队与业务涉众的合同是保证已知(历史)数据的 X%召回率。这是一份很好的合同,因为它不会试图对 ML 团队无法完全控制的事情做出保证:生产中的实际召回取决于数据漂移的数量,因此是不可预测的。

人类在厕所里的动态变化

一旦您在循环中引入了有人类调查员参与的外围操作,您的 ML 应用程序的操作方面就会发生巨大的变化。

首先,一些术语:

  • 积压是指你已经搁置的用于人工调查的项目数量。
  • 排队率是所有被你排除在外的订单的比率。
  • 容量衡量的是在给定的时间段内你最多可以排队多少。例如,如果您雇用 100 名人工注释员,每天工作 8 小时,一个注释需要 5 分钟,那么您每天可以对 10K 项目进行排队,这就是您的系统的容量。

积压管理的基本法则是这样的:如果您的排队率高于您的能力,您的积压就会增加。如果你的排队速度低于你的能力,你的积压就会减少。理想情况下,你会希望有一个稳定的积压工作,进来的和出去的一样多,就像一个浴缸,任何时候流入的水和流出的水一样多。

然而在实践中,由于数据漂移,队列速率可能是不可预测的。例如,在欺诈检测中,新的欺诈攻击可能会导致您的排队率激增。同样,在产品分类中,来自新供应商的产品可能会突然激增。出于这个原因,你会希望你的贴标劳动力有一定的弹性,即能够根据需要快速增加或减少劳动力的规模。

沃尔玛的产品分类渠道利用了人类专家和大众工作者。(来源)

沃尔玛实验室的一篇论文提出了这个问题的一个有趣的解决方案(Sun et al 2014):他们的系统使用专家人类分析师和人群工作者的组合来进行产品分类。虽然大众工作者不如人类专家准确,但他们也是极具弹性的劳动力。这种弹性尤其有助于应对“突然涌入的成百上千个项目”。此外,人类专家会定期审核群体生成的标签,以确保其质量。

结论:部署您的模型仅仅是开始

我希望至少已经明确了一件事:在部署您的 ML 模型之后,工作还远远没有完成。我认为这项工作才刚刚开始。总结一下:

  • ML 模型只有在采取行动时才有用。这些可以是硬动作(例如,取消信用卡交易)或软动作(例如,将其标记为手动检查)。
  • 为了跟踪整个系统的健康状况,监控精确度和召回率是至关重要的。对于高等级不平衡的问题来说,召回审计是昂贵的,但是通过重要性抽样可以降低成本。
  • 实际上,由于数据漂移,您无法保证生产中的精确度和召回率。因此,ML 团队和利益相关者之间的良好契约可以保证对已知数据的一定程度的召回/精确。
  • 当人工贴标机处于循环中时,除了精确度和召回率之外,您还需要跟踪队列速率。如果队列速度激增,您将需要额外的人工贴标机来跟上额外的数量。

最后,尽管在 ML 研究中关注模型性能,但应该清楚的是,并非所有对 ML 生产系统的改进都需要模型改进。例如,假设您可以通过为人类贴标机构建更好的 UI 来找到将调查时间减半的方法。这种改进将使我们能够节省标签成本,或者通过利用新的容量来提高整体召回率或精确度。无论哪种方式,如果不对 ML 模型进行任何改进,系统都会有显著的改进。

在你走之前…

喜欢这个内容吗?在 Medium 上关注/订阅,这样你就不会错过我以后写的新帖。 成为中等会员 这样你就可以无限阅读文章了。并确保在LinkedIn和/或Twitter上关注我!

折旧异常检测

原文:https://towardsdatascience.com/depreciation-anomaly-detection-fc8802ee6966

财务减记的应用统计过程控制

每一滴水都很重要(巴伐利亚瀑布,图片由作者提供)

折旧是指一种会计方法,用于在有形资产或实物资产的使用寿命内分配其成本。在本帖中,每个利润和成本中心每月计算一次所用资产的价值。折旧会计会极大地影响一个人的利润。这就是为什么密切监控非常重要。现在,我们将学习如何使用应用统计流程控制技术,立即发现月度减记中的异常情况。

首先让我们看看数据:

折旧是在过帐期间、帐户、利润和成本中心级别进行的。

分布:

让我们使用 Plotly Express 交互式绘制每个利润中心的金额分布和过账键。

Plotly 非常方便,因为它的交互式可视化(图片作者)

从每个利润中心的总体分布来看,我们可以看到一些利润中心比其他利润中心有更大的范围。

(图片由作者提供)

在这里同时考虑两种发布类型是没有意义的。我们将只考虑每个利润中心每月发布类别 40 的金额,并按日期对结果进行排序。

查看折线图,我们可以看到每个利润中心都可能存在缺失月份。介于两者之间的特定利润中心没有减记是有原因的,因此这很好。

中间可能有几个月没有折旧(图片由作者提供)

计算控制上下限:

我们的目标是建立一个利润中心级别的警报,一旦当月的偏差超出预期,就会触发我们。我们的想法是,当我们下个月折旧时,我们希望得到通知,以防特定利润中心的折旧明显高于或低于我们对前六个期间的预期。为此,首先我们对每月折旧进行分组和排序,并计算当月与上月(班次 1)之间的差异,第二次更新(班次 2)等等。

现在,在每一行中,我们可以将当前金额与前一个月(PrevWDown)、前二个月(Prev2MWDown)等进行比较。直到我们连续获得最近 6 个月的数据:

最后,我们计算这些月方差的均值、标准差和中值。

在这个例子中,我们不再考虑中位数。但是在继续之前,您可能想仔细看看您的数据的分布情况。因为只有当您知道数据的位置和分布时,您才能设置有意义的警报。我们将使用统计过程控制技术来计算警报。如果您想了解更多关于 SPC 和 QCC 的信息,请参阅本文:

我们不会完全遵循质量控制图原则,但我们会出于财务目的采用这些原则:

这意味着,如果最近一个月的金额高于平均值标准偏差的三倍,预警会将此行标记为“控制上限”(UCL)。如果每个利润中心的当前折旧应低于平均值减去三倍标准差,它将被标记为“控制下限”(LCL)。因此输出如下所示:

受控制移动范围图的启发,我们还将分别添加最近 6 个月与最新金额的偏差,以更好地对当前折旧进行排序:

我们使用 drop_duplicates 只保留每个利润中心的最新行

..并计算这些方差的均值和标准差:

现在,您可以轻松检查利润中心在当前折旧期内的任何异常值,以及它们的平均值和范围:

祝贺您,您已经将统计过程控制应用到您的财务减记中。下个月,您可以通知您的会计同事有关平均值或范围的任何触发的警报。如果数据可以描述为正态分布,通常在质量控制图上使用平均值的 3 倍标准差。您应该将此修改为您的数据分布,这样您就不会收到错误的警报。

非常感谢阅读,我希望这是支持。你可以在我的 Github 库中找到完整的 Jupyter 笔记本。随时在 LinkedInTwitter工作室(有或没有 VR) 与我联系。

Sigmoid 函数和交叉熵函数的导数

原文:https://towardsdatascience.com/derivative-of-sigmoid-and-cross-entropy-functions-5169525e6705

Sigmoid 激活和交叉熵损失函数的逐步微分

本文将逐步介绍 Sigmoid 函数和交叉熵函数。在模型训练期间执行反向传播时,理解这两个函数的导数在机器学习领域是至关重要的。

Saad AhmadUnsplash 上拍摄的照片

s 形函数的导数

Sigmoid/ Logistic 函数定义为:

图 1: Sigmoid 函数。左:Sigmoid 方程,右是方程的绘图(来源:作者)。

其中e欧拉数——一个近似等于2.718281828459的超越常数。对于x的任意值,Sigmoid 函数g(x)落在(0, 1)范围内。随着x的值减小,g(x)接近0,而随着x变大,g(x)趋向于1。例子,

g(x)的某些值给定 x 的值。

从这里开始,我们将使用两种方法— 商和微分链规则对 Sigmoid 函数进行微分。

用商法则求 Sigmoid 函数的导数

第一步:陈述商法则

商数法则。

商法则读作“商的导数是分母乘以分子的导数减去分子乘以分母的导数一切除以分母的平方。

第二步:应用商法则

根据 Sigmoid 函数、g(x) 和商法则,我们有

需要注意两件事:

  • 常数的导数等于零。这就是为什么u’=0
  • v中指数函数e*的微分被指数微分法则覆盖。

指数法则。

根据商和指数微分法则,我们有

这是 Sigmoid 函数的导数,但我们可以进一步简化,如下一步所示。

第三步:简化导数

在这一步中,我们将使用代数上的一些概念来简化第二步中的求导结果。

注意:在等式 5 中,我们在等式上加了 1,减了 1,所以我们实际上什么也没改变。

这标志着使用商法则的微分过程的结束。

用链式法则判别 Sigmoid 函数

第一步:链式法则

链式法则。

步骤 2: 将 Sigmoid 函数重写为负指数

步骤 3: 在步骤 2 中对 Sigmoid 函数应用链式法则

让,

然后,根据链式法则,我们将如下进行:

在这一点上,您可以使用我们在处理商数规则时所采取的相同步骤来简化方程(方程38)。

这是 Sigmoid 函数及其导数的图

Sigmoid 函数及其导数(来源:作者)。

交叉熵函数的导数

交叉熵损失函数是用于分类问题的一个非常重要的代价函数。然而,在本帖中,我们将只关注损失函数的微分。尽管如此,你可以在下面的链接中读到更多关于交叉熵损失函数的内容

交叉熵损失函数定义为:

其中 t 是真值,p 是 i ᵗʰ 类的概率。

对于具有两个类别的分类,我们有二元交叉熵损失,其定义如下

二元交叉熵损失函数,其中 t 是真值,yhat 是预测概率。

二元交叉熵函数的导数

二元损失的真实标签t是一个已知值,而yhat是一个变量。这意味着函数将对yhat进行微分,并将 t 视为常数。现在让我们继续研究导数。

第一步:陈述两条规则我们需要区分二元交叉熵损失

为了区分二元交叉熵损失,我们需要这两条规则:

而乘积法则是这样写的,“两个函数乘积的导数就是第一个函数乘以第二个函数的导数加上第二个函数乘以第一个函数的导数。

第二步:功能微分

我们将使用乘积规则来分别处理这两项的导数;然后,通过规则 *1*我们将结合两个导数。

由于我们有两个未知数——tyhat ——我们将实际处理偏导数(一个多变量函数的偏导数是它对其中一个变量的导数,其他变量被视为常数)。

因此,二元交叉熵损失函数的导数变成

这标志着本文的结束。感谢阅读:-)

结论

在本文中,我们研究了 Sigmoid 函数和二元交叉熵函数的导数。前者主要在机器学习中用作激活函数,而后者通常用作评估模型的成本函数。这里发现的导数在网络的反向传播过程中尤其重要——这是模型训练中的一个重要步骤。

以每月 5 美元的价格注册成为 medium 会员,以便能够阅读我和其他作者在 Medium 上的所有文章。

你也可以订阅,以便在我发表文章时将我的文章放入你的邮箱

感谢您的阅读,下次再见!!!

推导网球比赛时长的公式

原文:https://towardsdatascience.com/deriving-a-formula-for-the-length-of-a-tennis-game-9b5a4a1a92dd

Jeffery Erhunse 在 Unsplash 上的照片

使用基于点数的建模方法得出每场比赛的预期点数

在变得兴奋和深入二项变量和几何级数的本质之前,让我们从头开始。为什么?为什么我们会关心如何写一个网球比赛长度的方程?

当谈到能够为一场网球比赛的持续时间写一个“封闭形式的方程”时,答案可能是我们不在乎(除了只想掸掉数学蜘蛛网上的灰尘)。在计算机和模拟的时代,不需要没有用的数值帮助就能解决这个问题。但是能够将网球比赛的持续时间与发球方在任何给定的点获胜的概率p联系起来的想法是有用的。

网球目前是:

  • 随着比赛变得越来越长,非常长的比赛越来越频繁,显然陷入了一点危机。
  • 预示着新一批更高的球员,更高的球员带来更凶猛的发球,并在任何特定的比赛中给发球球员更多的统治力。

思考以上两件事是我如何在业余时间琢磨这个想法的。下面是一个使用“基于点的建模”的演练,演示如何在一场给定的网球比赛中推导出预期点数的公式,然后用 python 进行了一点模拟,以确保我没有在所有pn飞来飞去的时候走错地方。

基于点的建模基础

给定服务器赢得一分的概率p(因此返回者的(1-p)获胜),如果我们玩n分,我们可以将服务器赢得x分的概率写为:

作者图片

这只是一个陈述,概率遵循二项式分布,因为每个点都被视为二元结果(或者伯努利随机变量,如果你觉得有趣的话)。一场网球比赛可以被认为是围绕着一些标准的各种分数的集合,例如:

  • 一旦一个玩家得到 4 分,他们就赢得了比赛
  • 如果两个玩家都是 3 分,那么你需要得到 2 分(平手)

我们可以用下图描绘一场网球比赛的结构:

图片由作者使用https://app.diagrams.net/

让我们通过几个例子来解释这个图表。如果服务器赢了每一分,那么我们沿着上面的对角线——我们玩 4 分,并且以概率p服务器赢了每一分。按照上面的格式写下来:

作者图片

…在这里,我介绍了上面的p(4,0)符号,表示游戏以服务器赢得 4 分和返回者赢得 0 分而结束的概率。

类似地,如果服务器赢了 4 分,返回者赢了 1 分,那么我们沿着对角线向上,但是在某一点我们向下倾斜,因为返回者赢了一分。重要的是,这种下降不会发生在最后的时刻,即服务器赢得 4 分,然后返回者赢得他们的分,因为在这种情况下,游戏已经结束了。因此,我们需要消除发生这种情况的场景——当服务器连续赢得 4 分时。

这是包含-排除原则的一个应用,在整个过程中都很重要——假设我们没有平手,一旦一个玩家赢得 4 分,游戏就结束了。同样,如果你想想象一下,那么你可以说我们正在模拟一个马尔可夫链,我们已经达到了一个“吸收状态”——即一个玩家达到 4 分,我们不再“过渡”到其他状态,因为游戏已经结束。

使用上面的符号,我们最终得到:

作者图片

…这里的(4, 4)减法是去除 1 个状态,在该状态下,发球方连续赢得 4 分,然后发球方赢得他们的。

将获胜概率转化为游戏持续时间

在上面我们已经关注了概率,但是我们在这里关心的是一场游戏的持续时间。事实上,当我们有一个给定的服务器赢得分数的概率p时,我们关心的是期望的游戏持续时间。为了计算这一点,我们真的需要求和:

  • x点数后游戏结束的概率
  • 次数x -玩的点数

对于x的每个值。我们可以把它写成下面的等式:

作者图片

我们有:

  • “赢得爱情”(发球者或回球者)的概率乘以 4 点
  • “赢到 15 分”(发球方或回球方)的概率乘以 5 分
  • 等等。等等。等等。
  • 一些“包罗万象”的术语,指的是更复杂的平分,因为它涉及一些潜在的来回(将在后面讨论)。

考虑到每场比赛至少要有 4 分,我们可以将它改写为:

作者图片

…其中p(n)表示在n点后游戏结束的概率。在上文中,我们只是重新考虑了一些因素,因此我们没有“概率乘以点数”,而是“概率乘以额外的点数”——额外点数是指每场比赛最低 4 点以上的点数。我们现在可以依次讨论这些术语。

打了 5 分(多打了 1 分)

正如我们在上面看到的,我们有一个游戏“赢到 15 分”的概率公式。现在需要做的就是把服务器赢的概率加到 15,返回者赢的概率加到 15。这里我们可以利用二项分布是对称的这一事实;

作者图片

然后我们得到 5 分后游戏结束的概率:

作者图片

在这里,我们利用了对称性的事实来折叠二项式系数,即(5,1) = (5,4)(4,4) = (4,0)

打了 6 分(多打了 2 分)

与上面非常相似,我们可以计算出我们打 6 分并且比赛结束的概率,然后我们将它乘以 2,因为我们在网球比赛中比最低的 4 分多打了 2 分:

作者图片

两点

与前一个例子不同,使用“平分”,我们需要计算 2 个分量,而前一个例子中我们只需要计算 1 个分量——在该状态下结束的概率。这两个组件是:

  • 在打完n分后以那种状态结束的概率(就像以前一样)。
  • 玩的点数

我们可以按顺序拍摄。

平手的概率

这一点相对简单,因为平分的概率只是每个玩家赢得 3 分的情况下玩 6 分的概率——直接来自二项式分布:

作者图片

点数打成平手

这是最难的部分。一旦我们打成平手,我们知道我们将在一场比赛的最低 4 分的基础上再多打 4 分。换句话说,我们能玩的最低分是 8 分——6 分平手,2 分如果平手被直接“解决”。然而,如果我们来回一点,我们可能会玩得更多(先取得优势,然后回到平手等)。就像之前一样,我们需要把事情分成游戏结局:

  • 如果服务器赢了。
  • 如果返回者获胜。

如果服务器赢了,额外加分

如果服务器以平手获胜,我们可以将期望的额外点数表述为:

作者图片

这是什么意思?

  • p(n+2,n)是返回者有 n 分,服务器多 2 分的概率。从平手开始,比赛就结束了,因为从平手开始,再赢 2 分就足以赢得比赛。
  • [4 + 2n]是额外的分数,例如,如果我们立即“解决”平分,即n=0,那么我们有 4 个额外的分数。
  • 然后,我们对所有可能的n求和,即n=0是如果立即解决平分,n=1是如果我们来回一次,n=2是如果我们来回两次等等。等等。

我们首先需要进入p(n+2, n)——我们可以这样写:

作者图片

为了解决这个问题,我们可以去掉所有不依赖于j的东西,并利用二项式系数的和公式:

作者图片

这留给我们以下等式:

作者图片

将这个代入上面的等式,我们得到:

作者图片

…我们在上面的结果中进行了替换,并去掉了[4+2n]括号,将p^2移到求和之外,因为它不依赖于n。这意味着我们现在有两个术语:

  • 4(如果我们打成平手,所打的最少额外杆数)乘以服务器赢平手的概率。
  • 2n(来回的加分)乘以来回n轮后平手结束的概率。

查看这些表达式,我们会发现它们都是几何级数,但形式略有不同。第一项是以下形式的标准几何级数:

作者图片

…最后,我根据我们的情况进行了替换:r=2p(1-p)。第二个系列的形式为:

作者图片

我们可以先解决这个问题,如果我们对之前的“标准”几何级数进行微分,我们会得到:

作者图片

我们还可以区分该系列的解决方案,以获得:

作者图片

最后我们可以乘以r得到我们感兴趣的量:

作者图片

代入我们的情况r=2p(1-p),我们得出:

作者图片

现在我们有了构建模块,我们可以将它们放在一起,以获得预期的额外分数:

  • 一旦我们进入平手
  • 如果服务器赢了

作者图片

换句话说,这表明我们增加了 4 个点乘以服务器赢得平分的概率,再加上 2 个额外的点乘以服务器最终赢得平分的“来回”的预期持续时间。虽然有点拗口,但是能够用英语陈述这个等式总是很好的——即使它不是“简单”的英语。

如果返回者获胜,则加分

我们已经完成了服务器案例的所有繁重工作,返回器几乎完全相同,除了我们将几个p转换为(1-p)。为返回器添加的额外点数的表达式为:

作者图片

把所有的放在一起

现在我们已经计算了所有的组件,是时候把它们放在一起了。在所有的推导和替换之后提醒我们自己:

  • 4 个基点
  • 5 个总点数乘以 1 个额外点数后游戏结束的概率
  • 在 6 个总点数乘以这 2 个额外点数后游戏结束的概率
  • 我们打成平手并且服务器获胜的概率乘以与之相关的额外分数
  • 我们进入平手和返回者获胜的概率乘以与此相关的额外分数

现在是丑陋的部分:

作者图片

我们的函数在图形上是什么样的?

现在我们有了方程,让我们:

  • 用 python 写出来
  • 将输出与模拟游戏持续时间进行比较
  • 尝试产生一些有趣的结果

以下是上述公式在 python 中的实现:

那么它看起来如何呢——让我们画出p的不同值的game_length的输出。从上面的公式中可以看出:

  • 有了p=0p=1,我们会期望game_length输出 4——这个游戏只能因爱而赢或输
  • 随着p的到来,游戏的预期长度会增加,因为有更多的额外积分可以玩

作者图片

所以我们可以看到:

  • 正如所料,在极端情况下,我们最终最多打了 4 分。
  • 随着游戏变得更加平衡(p接近 50%),那么预期的游戏持续时间增加到 6.75 的峰值。
  • 预期游戏持续时间的增加是由于游戏“赢到 30 分”的概率增加,但尤其是进入平手,这总体上支配了额外的游戏时间。

由此得出了一个很好的结论。如果人们真的担心整个网球比赛的长度,那么有一个简单的建议——删除平分,并用“突然死亡”点代替。事实上,这是过去 4 年来在下一代 ATP 总决赛中测试过的东西。

使用 python 进行检查

现在,让我们模拟网球比赛,看看我们的公式的输出如何与它们生成的比赛长度分布进行比较。为了模拟网球比赛,我们将使用我编写的轻量级 tennisim 包。

作者图片

上图显示了不同的p值在我们运行模拟时游戏长度的分布情况。具体到左上角的图表,我们有:

  • 模拟 100 个游戏并记录它们的平均长度
  • 重复了 1000 次
  • 将这些值绘制成一个分布,并在我们推导出的方程值上标记为垂直红线

鉴于红线似乎正好位于每个分布的中间,那么我们可以说情况看起来相当不错。

结论

正如开始提到的,需要来表达一场网球比赛的长度,因为一个封闭形式的方程是最小的。这一点通过以下事实得到了证明:

  • “暴力破解”和模拟数百万场网球比赛只需要很少的代码
  • 100,000 场游戏需要< 1 second to simulate (using 【 on my mac)

Nevertheless sometimes it’s fun to see if it can be done as well as provide some useful insights as to 为什么游戏越平衡时间越长。

如上图之一所示,更长预期游戏的真正驱动因素是进入平手。杜斯相对于4的最短持续时间增加的额外 4 个镜头,以及如果你来回移动的话甚至更多的潜力是真正推动期望更高的原因。如果真的担心网球比赛的长度,那么取消平局可能是保留其他驱动比赛长度的方面(抢七局,现有的设置结构)的一种方式,同时缩短比赛——因此比赛。

使用 SafeGraph 获得位置智能洞察

原文:https://towardsdatascience.com/deriving-location-intelligence-insights-using-safegraph-d53803e239cb

SafeGraph 提供基于动态位置的数据。让我们利用这一点来获得研究见解。

香港夜间交通| PxHere

在他们的网站上,SafeGraph 提到他们有超过 1100 万条兴趣点(POI)记录,包括位置、品牌等数据。查看文档,基本上有三种数据集:地点、几何和模式(他们最近也引入了新的花费数据集)。在本文中,我将结合 3 种主要的数据类型,介绍一个基于位置的 SafeGraph 数据用例。

安全图表 API

首先,我将假设我是一个流量很大的店主,我需要更多关于访客的详细信息。这就把我的选择范围缩小到了零售店、杂货店,基本上是人们经常去的地方(甚至在疫情期间)。我选择了我们家最喜欢的杂货店——长城超市 (GW 超市),位于德卢斯地区,离亚特兰大不到一个小时的路程。我妻子是中国人,我们喜欢中国菜。此外,我还和食品柜台和收银台的人交了一些朋友,因为他们很乐意和一个看起来不像中国人,但会说一点普通话的人交谈。

GW 超市在当地非常有名,因为它的食品种类繁多,商店规模也很大。我听说过这样的故事,人们经常从附近的州开车去长城超市,把食物储存在大冰柜里,然后马上回家。让我们看看 SafeGraph 是否可以提供任何关于 GW 超市访客来自哪里的位置见解。

如果一切顺利,我们可以使用 SafeGraph 核心 API 来查找 GW 超市的 placekey(我们需要查询模式信息)。

成功!我们现在有了 GW 超市的钥匙!

来自 SafeGraph |塞犍陀·维韦克的 GW placekey

我们可以使用这个 placekey 通过 SafeGraph 核心和月度模式 API 查询相关信息:

最受欢迎的日子是星期六和星期天,最不受欢迎的日子是星期一和星期二。就我个人而言,我宁愿在本周晚些时候去买杂货。

2021 年 12 月白天参观 GW 超市|塞犍陀·维韦克

按小时划分的访问量显示,高峰时间是上午 11 点至下午 5 点。我通常在上午 10 点或下午 2 点去 GW,但没有真正注意到哪个时间更拥挤(尽管我注意到在某些情况下 GW 更拥挤)。下次我们买杂货时,我会记住这一点的!

2021 年 12 月 GW 超市每小时访问量|塞犍陀·维韦克

去 GW 的游客更有可能去沃尔玛、好市多和麦当劳。对于一点位置上下文;沃尔玛就在 GW 的正对面,最近的好市多也在 1.5 英里之外。好市多很少。所以这是有道理的。

2021 年 12 月 GW 游客去过的其他品牌|塞犍陀·维维克

安全图几何

我已经向您展示了 SafeGraph 的位置和模式功能。但是 SafeGraph 还有另一个强大的几何功能,有助于理解和可视化地理空间趋势。具体来说,我感兴趣的问题是 GW 的访问者主要来自哪里?他们是本地人吗?他们来自其他州吗?

对应于 GW 访问数据的数据帧由单行组成。一些列包含可以用来推断用户来自哪里的字典。特别是,‘visitor _ daytime _ cbgs’列包含访问者的数量及其主要的人口普查区块组(cbg) ID 。然而,这个 cbg 号码并没有告诉我们任何关于地点的信息。那么我们如何得到 cbg 的位置和几何形状呢?

为此,我求助于 SafeGraph 提供的人口普查数据(这是免费下载的!).cbg 几何数据为 geojson 格式,可通过 geopandas 读取。

CBG 几何数据来自 SafeGraph |塞犍陀·维韦克

最后,我将其保存为 geojson,并使用 kepler.gl(参见我最近关于 kepler.gl 的文章)来可视化显示 CBG 访问的 Choropleth 地图,以及 GW(大黄点)的位置。

GW 位置(大黄点)和 2021 年 12 月主要日间 CBGs 的访问量|塞犍陀·维韦克

缩小,你可以看到有来自佐治亚州以外的 CBGs 的游客,包括田纳西州和北卡罗来纳州!看起来,人们开车几个小时去 GW 购物的故事可能有些道理!

GW 位置(大黄点)和 2021 年 12 月主要日间 CBGs 的访问量(缩小)|塞犍陀·维韦克

外卖食品

SafeGraph 数据提供了相当多的位置洞察力,他们的 API 超级友好。没有太多的教程介绍用例,但这可能是因为该公司相对较新。对于那些希望在不投资密集型架构的情况下提高位置洞察力的公司来说,跟踪商店访问量是非常诱人的。

数据是聚合的,好坏取决于你的需要。好的一面是 SafeGraph 已经做了一些(实际上相当周到!)初步分析,以便您可以使用它们的格式化数据来生成见解。此外,这种聚合消除了某些隐私问题。不好的一面是,你可能会错过一些对访问有用的细节性见解。

总的来说,SafeGraph 是一个提供位置洞察力的伟大工具。最近有许多研究使用 SafeGraph 数据来回答重要的研究问题,特别是与新冠肺炎疫情期间人们的运动模式相关的问题。我敢肯定,在未来几年,使用 SafeGraph 进行位置智能的研究人员和公司的数量只会越来越多!

关注我 如果你喜欢这篇文章——我经常写复杂系统、物理学、数据科学和社会的界面。

如果你还不是中会员,想支持我这样的作家,可以通过我的推荐链接随意报名:https://skanda-vivek.medium.com/membership

每周数据透视 订阅此处

DESeq2 和 edgeR 不应再成为大样本差异基因表达分析的默认选择

原文:https://towardsdatascience.com/deseq2-and-edger-should-no-longer-be-the-default-choice-for-large-sample-differential-gene-8fdf008deae9

除非必要,否则不应增加复杂性。

大样本数据可能不需要假设。

Author: fei.zhao (思考问题的熊)

原帖:https://kaopubear . top/blog/2022-03-20-do-use-deseq 2-edger-in-human-population-samples

经作者许可,由葛新洲和李静怡翻译

对于我们的基因组生物学文章的简短介绍,请参见https://towardsdatascience . com/a-large-sample-crisis-or-not-640224020757

在博士期间,我喜欢在不忙的时候评估和比较各种生物信息学软件包。有时我会对转录组样本应用两三种差异分析方法。当我想要严谨时,我会取这些方法的鉴别差异表达基因(DEGs)的交集;当我想变得“精明”时,我会选择结果看起来“最好”的方法(想必你也是这样)。

在 2021 年,当我将研究领域从植物转向癌症后,当我分析大样本数据集如 TCGA 数据时,我经常直接使用 Wilcoxon 秩和检验来比较肿瘤样本和正常样本,以找到 deg。原因无非是这个经典测试的无敌计算效率。当然,在大多数情况下,我会同时使用 Wilcoxon 秩和检验和 DESeq2,这样做的好处是我可以让 DESeq2 为大样本数据运行,并离开座位休息一会儿。

在 2022 年,几天前,发表在 Genome Biology 上的一篇论文使用了相对严格的论点,认为简单的 Wilcoxon 秩和检验应该在大样本 RNA-seq 差异表达分析中取代 DESeq2 和 edgeR,即使计算时间不是一个问题。

下面是论文链接(https://doi.org/10.1186/s13059-022-02648-4)。你可以直接看原文,也可以继续看我的文章。

李、、葛新洲、彭芳略、、李静怡。"在分析人类群体样本时,流行的差异表达方法夸大了假阳性."基因组生物学 23,第 1 期(2022 年 3 月 15 日):79。

RNA-seq 实验需要多少个重复?

自从 RNA-seq 出现以来,最基本但关键的应用是通过比较两组(有时是多组):肿瘤和正常组织样品、不同治疗条件下的细胞系等来鉴定 deg。

在实践中,我们通常只测量高通量转录组数据的 3 个重复,这里的“重复”是指生物学重复而不是技术重复。有时两次复制就够了,甚至一次复制也能被一些软件处理。

为什么要重复 3 次?原因是“第三次才是魅力”,材料和预算有限。几年前,一些研究论文讨论了多少次重复就足够了;然而,他们没有达成共识:有人说至少 6 次重复,有人说 4 次重复就足够了,还有人说找到所有 deg 至少需要 12 次重复(见下面的截图)。

截图来自https://rnajournal.cshlp.org/content/22/6/839.short

2016 年,一篇关于 RNA-seq 分析的高被引综述论文,也发表在了基因组生物学上,给出了下表和样本量建议。简而言之,至少需要 3 次重复。

截图自https://genomebiology . biomed central . com/articles/10.1186/s 13059-016-0881-8

截图自https://genomebiology . biomed central . com/articles/10.1186/s 13059-016-0881-8

事实上,以前对重复次数的分析大多是基于 DEseq2 和 edgeR 等流行软件进行的。现在有趣的是,DESeq2 和 edgeR 不被推荐用于大样本分析。

DEseq2 和磨边机不适用于大样本

从上面关于样本量的讨论中,我们可以看到转录组差异分析的最大挑战一直是小样本量:三次重复是魅力所在吗?

面对这种小样本问题,基于关于 RNA-seq 数据的各种参数分布假设,开发了统计方法和软件。最受欢迎的两种方法是 DEseq2 和 edgeR,其核心假设是每个基因的序列读取计数在一个条件下遵循负二项分布。基于这一假设,他们根据自己的逻辑集合对原始读取计数进行规范化。另一种方法 DEGseq 采用泊松分布,而不是负二项分布,当每个条件只有一个重复时,可以使用这种方法。

此外,DEseq2 和 edgeR 等软件固有地假设两种条件下的样本在很大程度上是相同的:大多数基因没有差异表达。这带来了第一个问题:在样本量从几十到几千的群体水平的 RNA-seq 研究中,我们还能相信大多数基因没有差异表达吗?

为了评估 DESeq2 和 edgeR 识别 DEGs 的能力,李等人(这篇新的基因组生物学论文的作者)在 13 个群体水平的 RNA-seq 数据集上测试了 DESeq2 和 edgeR,总样本量从 100 到 1376 不等。分析显示,DESeq2 和 edgeR 在这些数据集上发现了非常不同的 deg。

补充图来自https://genomebiology . biomed central . com/articles/10.1186/s 13059-022-02648-4

从上图中的结果可以看出,在免疫治疗数据集上,通过 DESeq2 或 edgeR 鉴定的 deg 中只有 8%是一致的(通过两种方法鉴定)。在其他群体水平的 RNA-seq 数据集上,DESeq2 比 edgeR 识别了更多的 deg。

出了事,肯定有心魔。要看到问题出在哪里,我们需要从错误发现率(FDR)控制的角度进行深入调查。大样本对于 FDR 评估有一个固有的优势:它们可以被置换以产生阴性对照样本,在此基础上可以估计 FDR。

因此,李等人对两组样本进行了置换,使得每一个新的组都包含来自两个原始组的样本。通过重复随机置换 1000 次,李等从原始数据集生成 1000 个置换数据集。

结果令人激动。

首先,DESeq2 和 edgeR 分别有 84.88%和 78.89%的概率从置换数据集比从原始数据集识别更多的 deg。

第二,在分别由 DESeq2 和 edgeR 从原始数据集中识别的 144 和 319 DEGs 中,有 22 个(15.3%)和 194 个(60.8%)是从至少 50%的置换数据集中识别的,因此是虚假的。也就是说,DESeq2 和 edgeR 有太多的误报。

最重要的是,李等人发现,具有较大倍数变化的基因更有可能被 DESeq2 和 edgeR 从置换数据集中识别出来。在实践中,我们都倾向于认为具有较大倍数变化的基因更可能重要,但事实是这些基因不一定有差异表达。

还有一件事。如果你很好奇那些假阳性 DEGs 中富集了哪些生物功能?嗯…免疫相关。

图 1 来自https://genomebiology . biomed central . com/articles/10.1186/s 13059-022-02648-4

为什么 DEseq2 和 edgeR 不起作用?

为什么 DESeq2 和 edgeR 从这个免疫治疗数据集中发现了这么多假阳性 DEGs?最直接的猜测是,该数据集中的基因计数不再符合 DESeq2 和 edgeR 假设的负二项分布。

为了验证这个假设,李等人选择了两组基因。一组包含从≥20%置换数据集鉴定为 deg 的基因;另一组由从≤0.1%置换数据集鉴定为 deg 的基因组成。在评估负二项式模型对每组基因的拟合优度时,李等人揭示了第一组基因的模型拟合度确实较差,这与这些基因为假阳性的事实相一致。

进一步说,为什么负二项式模型拟合度很差?作者在至少 10%的置换数据集中检查了所有被误认为 DEGs 的基因。相对于 DESeq2 或 edgeR 拟合的负二项式模型,所有这些基因都有异常值测量。

补充图来自https://genomebiology . biomed central . com/articles/10.1186/s 13059-022-02648-4

在 edgeR 和 DESeq2 等参数测试方法中,无效假设是基因在两种情况下具有相同的平均表达。因此,异常值的存在会严重影响他们的分析结果。相比之下,Wilcoxon 秩和检验在存在异常值时表现更好,因为其零假设是一个基因在一种条件下比在另一种条件下具有更高或更低的表达的概率相等。也就是说,Wilcoxon 秩和检验更关注基因表达等级,而不是实际的基因表达水平。

除了 DESeq2 和 edgeR,在免疫治疗数据集上,李还比较了其他几种有代表性的方法,其中 limma-voom 是和 DESeq2、edgeR 一样的参数检验;NOISeq 和 dearseq(最近发表的一种为大样本设计的方法)是像 Wilcoxon 秩和检验一样的非参数检验。

在哪些方法发现更少的假阳性 DEGs 方面,DESeq2 和 edgeR 显然输掉了竞争,而其他方法在这个免疫治疗数据集上表现良好。如果我们比较一下这些方法找到真正 deg 的能力会怎么样呢?

等人从 12 个 GTEx 和数据集分别生成了 50 个半合成数据集(具有已知的真 deg 和非 deg)。如下图所示,只有 Wilcoxon 秩和检验可以在 0.001%至 5%的 FDR 阈值范围内持续控制 FDR。此外,就实际 FDR 的功效而言,Wilcoxon 秩和检验优于其他五种方法。下图中,蓝线显示的是 Wilcoxon 秩和检验,黄色和紫色虚线对应的是 DEseq2 和 edgeR。

图 2A 来自https://genomebiology . biomed central . com/articles/10.1186/s 13059-022-02648-4

什么时候使用 Wilcoxon 秩和检验?

由于 Wilcoxon 秩和检验适用于大样本的 DEG 分析,所以关键问题是什么样的样本量可以被认为是大样本?

为了研究样本大小如何影响六种 DEG 识别方法的性能,Li 等人对每个数据集进行下采样,以获得每个条件下样本大小从 2 到 100 的数据集。

从下图可以看出,在 1% FDR 阈值下,当每个条件的样本量小于 8 时,我们不应使用 Wilcoxon 秩和检验。同时,当每个条件的样本量超过 8 时,与三种参数检验方法(DESeq2、edgeR 和 limma-voom)和其他两种非参数检验方法相比,Wilcoxon 秩和检验取得了相当或更好的功效。

考虑到 DEG 在所有基因中的比例可能会对结果产生影响,李也生成了 5 个 DEG 比例(1%、3%、5%、9%、20%)的数据集,评估结果显示 Wilcoxon 秩和检验仍然具有良好的 FDR 控制和功效。

因此,从李等人的分析结果来看,当每个条件的样本量大于 8 时,可以毫不犹豫地使用 Wilcoxon 秩和检验。

图 2B 来自https://genomebiology . biomed central . com/articles/10.1186/s 13059-022-02648-4

如何使用 Wilcoxon 秩和检验?

众所周知,三种参数测试方法——deseq 2、edgeR 和 limma——长期以来一直主导着转录组研究。基本上所有的大规模研究都用它们来寻找 deg。然而,这三种方法最初都是为解决小样本问题而设计的。

在样本量更大(至少几十个)的总体水平研究中,参数假设通常不再必要。同时,样本量越大,越有可能出现异常值,这将违反参数假设并使 p 值计算无效,从而使 FDR 控制成为问题。

最后一个问题是如何合理使用 Wilcoxon 秩和检验进行差异表达分析。

与 DESeq2、edgeR 和 limma 不同,Wilcoxon 秩和检验不是一种基于回归的方法,因此不能调整可能的混杂因素(如测序深度的差异)。因此,在使用 Wilcoxon 秩和检验之前,建议研究人员对 RNA-seq 样本进行标准化,以消除可能的批次效应。

关于数据规模,我个人不认为我们使用 DESeq2 的相对对数表达式、edgeR 的 M 值的修剪平均值或 TPM 是一个大问题。(如果你有悟性,一定会愿意全部尝试。)

最后,李等人提供了一个 R 代码实例,使用 edgeR TMM + wilcox.test()进行 DEG 分析。

# read datareadCount <- read.table(file = "examples/examples.countMatrix.tsv", header = T, row.names = 1, stringsAsFactors = F, check.names = F)conditions <- read.table(file = "examples/examples.conditions.tsv", header = F)conditions <- factor(t(conditions))# edgeR TMM normalizey <- DGEList(counts = readCount, group = conditions)## Remove rows conssitently have zero or very low countskeep <- filterByExpr(y)y <- y[keep, keep.lib.sizes = FALSE]## Perform TMM normalization and convert to CPM (Counts Per Million)y <- calcNormFactors(y, method = "TMM")count_norm <- cpm(y)count_norm <- as.data.frame(count_norm)# Run the Wilcoxon rank-sum test for each genepvalues <- sapply(1:nrow(count_norm), function(i){data <- cbind.data.frame(gene = as.numeric(t(count_norm[i,])), conditions)p <- wilcox.test(gene~conditions, data)$p.valuereturn(p)})fdr <- p.adjust(pvalues, method = "fdr")# Calculate the fold-change for each geneconditionsLevel <- levels(conditions)dataCon1 <- count_norm[,c(which(conditions==conditionsLevel[1]))]dataCon2 <- count_norm[,c(which(conditions==conditionsLevel[2]))]foldChanges <- log2(rowMeans(dataCon2)/rowMeans(dataCon1))# Output results based on the FDR threshold 0.05outRst <- data.frame(log2foldChange = foldChanges, pValues = pvalues, FDR = fdr)rownames(outRst) <- rownames(count_norm)outRst <- na.omit(outRst)fdrThres <- 0.05write.table(outRst[outRst$FDR<fdrThres,], file = "examples/examples.WilcoxonTest.rst.tsv", sep="\t", quote = F, row.names = T, col.names = T)

遗言

毕竟我觉得没必要对我们之前做过的分析有太多的自我怀疑。但是在将来,当对每组 8 个以上的样本进行差异分析时,我们不要忘记也尝试一下 Wilcoxon 秩和检验。如果有人问你为什么不用专门为微分分析设计的 DESseq2 和 edgeR,只要出示这张纸,当然还有这张图。

图片来自https://kaopubear . top/blog/2022-03-20-do-use-deseq 2-edger-in-human-population-samples;由https://knowyourmeme.com/memes/distracted-boyfriend的迷因赵飞修改

自动驾驶汽车机器学习平台的挑战

原文:https://towardsdatascience.com/design-challenges-of-machine-learning-platform-for-autonomous-vehicles-58917c59f575

意见

图片来自 Pixabay

自动驾驶汽车(AV)是移动的未来。由于最近的技术突破,它们不再只存在于科幻电影或技术演示中。今天,我们可以看到自动驾驶汽车在大都市的街道上行驶,提供 robotaxi 服务。

由于过去十年深度学习的巨大成功,作为 AVs 大脑的软件在很大程度上从基于规则的算法转向了数据驱动的机器学习(ML)算法。如今,ML 已经渗透到自主车辆设计的几乎每个阶段,从感知、行为预测、运动规划到控制、地图绘制和路径选择等。

ML 平台的作用

AVs 极高的安全要求为系统中每个阶段的 ML 算法提出了很高的标准。ML 模型中 1%精度的提高可能表明驾驶安全性的显著提高和/或减少不必要的车辆搁浅的机会。由于这些原因,影音公司的 ML 从业者正在尽最大努力推动最先进(SOTA)模型的边界。同时,这些模型还需要适合车辆的板载,并满足延迟、存储器和功率限制。

考虑到 ML 模型开发如此复杂和具有挑战性的目标,ML 平台的作用是提供来自底层 ML 基础设施的简化接口,使得具有建模专业知识的 ML 从业者可以更加专注于 ML 算法设计和增强。

设计挑战

除了更好的可用性和资源效率是广告定位和推荐系统等其他行业所期望的目标之外,无人驾驶汽车行业的 ML 平台设计还面临一些独特的挑战:

海洋大小数据挖掘

由于数据源的性质,例如远程和短程激光雷达、全景摄像机、雷达探测、环境声音、高清晰度道路地图等。,训练数据的规模是巨大的。一方面,原始数据对提高模型质量非常有价值,但获取成本很高;另一方面,某些事件极其罕见,使得数据集高度不平衡。挑战不仅在于如何高效地存储和传输数据,还在于如何从这个巨大的数据空间中智能地识别出有趣的事件。

异构模型

AVs 的 ML 系统是真正的多模态和多任务的。训练数据的形式包括 3D 点云、2D 视频、雷达扫描、高清道路图和表格数据,如路况、限速、交通信息、天气等。每个模型可以被训练用于各种任务,包括行人检测、车辆检测、虚假物体(例如路边的树枝)检测等。当我们看到网络架构向基于变压器的主干网发展的趋势时,我们期待在几个有代表性的模型中有更多的模型任务整合。同时,即使对于诸如行为预测的相同任务,预测行人和车辆的行为可能需要不同的建模算法。

尽管非常希望为自动驾驶汽车建立和训练一个端到端的模型(从传感器数据到最终的车辆控制信号),但由于车载推理延迟的限制和每个阶段的模型可解释性要求,这在不久的将来可能是不可行的。

培训效率

鉴于训练数据的庞大规模和建模算法的频繁发展,ML 平台必须提供一种解决方案,使 ML 从业者能够快速迭代模型开发周期,同时有效利用计算资源。因为自动驾驶汽车的大多数模型需要在专用加速器集群上训练,如 GPU、TPU 等。一个好的 ML 平台需要设计一个高效的分布式培训基础设施,但不需要 ML 从业者进行太多的手动配置,以使其易于使用。

另一方面,由于各种数据处理和增强需求,训练数据管道也可能成为计算瓶颈。ML 平台需要提供有效的输入数据处理管道,以确保加速器不会缺乏训练数据。

机载推理优化

一方面,模型的准确性可以改变自动驾驶汽车的安全性和商业成功;另一方面,一旦训练了高质量的模型,如何将它部署到汽车上(也称为车载),以便它可以在所需的延迟限制(通常小于 100 毫秒)内帮助做出驾驶决策。

由于物理和成本的限制,自动驾驶汽车无法在车上携带无限的计算资源。在汽车上运行的模型之间也存在顺序依赖性(例如,一些感知模型需要在行为模型之前运行)。它变成了数据流图上的约束时间约束优化问题。延迟优化需要首先在时序关键路径上执行。各种模型推理优化技术,如网络剪枝和模型量化等。可以被利用,然而,不像在一些边缘 ML 的情况下,模型精度不能妥协。

同样,一个好的 ML 平台应该为 ML 从业者提供这样的优化解决方案,使他们相信他们训练的模型可以成功地部署在汽车上,而不会损失准确性。

特别培训要求

由于在严格的延迟限制内训练高质量模型并将其部署到汽车上的难度,一个好的 ML 平台应该为 ML 从业者提供各种解决方案。例如,一种神经架构搜索(NAS)解决方案,用于找到产生高精度但满足推理延迟约束的良好模型架构。或者是一种蒸馏解决方案,它在船外训练一个大而精确的模型,并蒸馏出一个较小但同样精确的模型,以部署在船上。

未来

移动的未来正在到来。在旧金山和凤凰城等大城市,我们已经看到完全自动驾驶的出租车在街上行驶,为数千名乘客——公众成员——提供服务。我们预计在不久的将来,这种车队将会迅速扩大规模,在卡车运输和本地配送等其他领域也将会出现 AVs。

这个行业的可行性和可扩展性需要深度学习模型算法的持续进步,以及支持有效数据挖掘、更快模型迭代和优化部署的良好 ML 平台。当自动驾驶汽车行业扩展到更多的汽车和地理位置时,ML 平台也应该支持持续学习和更好地与模拟集成。

声明: 以上为作者个人观点。这里陈述的任何假设和观点都是他们的,不代表他们现在或以前的雇主。

模型部署系统的设计考虑

原文:https://towardsdatascience.com/design-considerations-of-model-deployment-system-c16a4472e2be

理解部署机器学习模型的跨职能需求的工程师指南

凯利·西克玛Unsplash 上拍摄的照片

机器学习的影响日益广泛,推动了产品推荐、欺诈检测和对话式人工智能等应用。数据科学团队不再只是通过仪表板或演示文稿与决策者分享业务见解。更常见的是,只有将模型应用到最终应用中,才能实现 ML 的全部潜力。

然而,部署机器学习模型仍然是最耗时的工程挑战之一。作为负责构建模型部署管道的工程师,理解这是一项跨职能的工作并需要与多个组织的利益相关者合作是至关重要的。

在本文中,我们将深入模型部署系统的关键需求,这些需求来自三个利益相关者:数据科学团队、DevOps 团队和产品团队。然后,我们将分解主要的设计考虑事项(标有💡)以满足这些要求,同时提出切实可行的建议。

数据科学家

数据科学团队可能是需要考虑最多(也可能是最具挑战性)需求的团队,毕竟,您要实现的是他们的模型。他们的主要目标是建立准确而有影响力的模型

#1:从培训环境无缝过渡

数据科学家使用专门的开发工具开发他们的模型,例如 Jupyter notebook、不同类型的 ML 培训框架和实验管理平台,这些环境对于许多软件工程师来说可能是陌生的。这使得模型工件和特性提取代码的产品化成为部署管道中最容易出错的过程之一。

💡 一个成熟的模型部署系统必须能够轻松地与多种 ML 框架和模型开发环境集成,让数据科学团队能够灵活地选择任何适合其模型培训需求的工具。

#2:不断重新部署新模型的能力

数据科学团队知道,模型在部署到生产环境后不会保持不变,概念漂移和数据漂移可能会随着时间的推移而发生,需要定期重新培训和更新新模型。您的数据科学团队可能还想加入新的特性,更新模型架构,或者尝试新的 ML 框架。部署系统应该允许数据科学团队在生产中轻松更新新模型,并且充满信心。

💡 你需要一种方法来管理代码,以及模型和库的所有依赖关系。此外,为了可再现性和平滑回滚(如果必要),所有这些部分都必须进行版本控制并一起部署。

这个需求还引入了一个版本化的挑战:发布的模型越多,就越难跟踪重要的细节,比如每个模型是如何被训练的,谁有权限,以及谁负责监控以确保质量。

💡 记录和调用特定细节的模型注册中心将简化与管理模型的多个版本相关的操作开销。

#3:模型性能的可见性

了解模型在运行后的表现是至关重要的。考虑这个例子:当一个推荐产品的新模型导致比以前少的销售时,这可能是一个新模型预测不太准确的信号。您的系统应该帮助检测到这一点,并尽快回滚到以前的版本。

随着时间的推移,随着习惯和趋势的改变,概念和数据也会发生漂移。当这种情况发生时,数据科学团队需要进行调查,以确定模型是否需要重新训练。

💡 您的 ML 服务解决方案应该让数据科学家能够轻松地实时检索和分析预测。CloudWatch、DataDog 或 Grafana Loki 等简单解决方案可能足以满足某些情况,但更复杂的情况可能需要 WhyLabs、Fiddler.ai 或 Arize 等公司的专业 ML 监控解决方案。

#4:轻松访问功能

ML 模型的可行性不仅依赖于其原始输入数据,还依赖于创建附加派生特征的变换。在在线预测场景中,很多时候这些转换也需要在数据被摄取时实时执行。关键的挑战是定义一个检索特征数据的通用接口,并以同样的方式处理训练和在线服务的特征转换。

💡 对于大多数特征转换,最好直接将代码嵌入服务管道,因为这样可以确保模型和特征处理代码始终处于同一版本。然而,当处理需要对时间序列数据进行在线聚合的特征时,以低延迟计算这些特征是具有挑战性的。对于这种类型的用例,您可以考虑使用一个专用的特性库,它为在线服务和培训管道提供一个公共接口来访问特性。一个好的特征库还可以帮助协调新特征或特征的新版本的推出,以便它与您的模型同步。

DevOps 工程师

您可能会与 DevOps 团队紧密合作来部署 ML 模型。这个团队主要负责为公司软件产品提供动力的底层技术基础设施,监督关键领域,如整个运营的稳定性和安全性。

#1:在首选平台上部署模型

模型服务工作负载通常依赖于 DevOps 团队管理的其他现有服务来配置资源、检索功能、流式日志和监控。在已经建立了特定的云或本地环境、部署模式和工具的情况下,以类似的方式部署新的 ML 服务以减轻维护负担是很重要的。

💡 您的预测服务应该能够与不同的日志记录、监控和供应工具一起工作。此外,它应该根据您的使用情况进行优化,以便以多种不同的形式运行——无论是作为 Kubernetes 上运行的实时服务,使用 Spark 的批处理推理作业,还是通过 Terraform 或 CloudFormation 提供的云中的无服务器部署。

#2:确保服务的可靠性和维护

一旦部署了模型,服务的可靠性对 DevOps 团队来说是极其重要的。

💡 开发运维团队应该能够监控、重现相同的部署以创建测试环境,并整合 CI 测试。

用于监控、跟踪和警报:服务于工作负载的模型应该易于与 Prometheus、OpenTelemetry、CloudWatch 或 Datadog 等系统集成。这使得 DevOps 团队可以像监控其他现有服务一样监控这些新的 ML 服务。

为了可再现性:为了最小化将来失败部署的机会,系统应该能够再现相同的部署来创建测试环境。在生产中断的情况下,能够轻松地将所有系统恢复到以前的状态让 DevOps 团队高枕无忧。GitOps 流是一种标准的、声明性的方式,用于描述所需的部署状态,并在需要时自动重新生成整个堆栈。

例如,下面的代码示例显示了 Kubernetes 与 Yatai 和 BentoML 的模型部署规范:

对于 CI 和测试:由于服务逻辑或模型预测的变化而导致的意外行为可能具有极大的破坏性,这就是为什么 CI 测试是在问题进入生产之前必须进行的。

💡 您的模型部署解决方案应该考虑到与 CI 管道的集成,这样可以测试整个服务管道。内联自动化测试不仅应该评估代码中的业务逻辑,还应该验证关键模型预测指标没有超出可接受的范围。

#3:保护对模型的访问

保护对 ML 模型的访问与保护对业务的其他关键部分(如客户数据或您公司的知识产权)的访问同等重要。

💡 ML 预测服务,就像任何其他在线服务一样,应该在认证和访问方面应用通用安全 最佳实践 。我们看到许多用户在 Istio 背后部署预测服务。这种类型的服务网格可以针对更复杂的身份验证和路由场景进行配置。同样值得注意的是,特别是 ML 服务可能会出现新类型的安全挑战,如 对抗性示例 ,旨在导致模型出错。

产品经理

你的产品团队是一个重要的利益相关者——毕竟,他们不仅要对 ML 的商业影响负责,还要对 ML 的所有经济、社会和法律问题负责。他们对 ML 部署系统的考虑主要集中在证明模型的价值交付具有出色用户体验的最终产品这两个阵营。

注意:这里的产品经理通常是指产品负责人——在你的组织中负责 ML 项目的成功并不断倡导最终用户和商业价值的任何人。

#1:快速验证模型的价值

产品团队希望尽快将一个功能性的原型推向市场,以验证其价值。由于这种上市时间的要求,许多 ML 专家认为 ML 项目应该从较小的规模开始,然后随着时间的推移进行调整和完善。

💡 将模型部署到生产中时,最初跳过更高级的功能。当模型看起来可行时,一切都可以微调以获得最佳结果。

#2:满足延迟要求

预测延迟是产品团队的一个关键考虑因素,因为它直接影响最终用户的体验(因此也影响收入)。考虑这个例子:一家视频游戏公司使用 ML 模型来匹配具有相似技能的竞争玩家,这必须是接近即时的——如果它运行太慢,可能会导致沮丧的用户退出。

💡 根据不同的用例,模型服务系统可能需要考虑不同的延迟 SLA(服务级别协议)。对于一些时间敏感和任务关键的用例,如广告定位,ML 服务系统需要以 50 毫秒的延迟实时提供预测。对于其他人来说,比如用于发行信用卡的欺诈检测模型,几分钟就足够了。

#3:降低运营成本

大规模机器学习是极其计算密集型的工作负载,并且可能涉及数千台同时运行的机器。这为您的组织带来了能源、维护和云支出方面的成本。一个 ML 模型只对一个企业有意义,如果它提供的价值超过运行它的成本。这就是为什么产品团队希望确保模型交付的价值,同时优化成本。

💡 开箱即用的水平和垂直扩展优化解决方案可确保从一开始就将成本降至最低。不同的用例可能需要其中之一。

垂直扩展和资源利用:如果预测流量需要专门的硬件加速,或者通过垂直扩展可以更加经济高效,您需要确保软件针对您选择的硬件进行了优化。具体来说,您需要确保它支持常见的性能最佳实践,例如预测请求的自适应批处理,将负载适当地分配给所有可用的 CPU 内核,并在需要的地方利用 GPU 或自定义加速器。这不仅有助于最大限度地减少单个请求的延迟,而且通过高效利用资源减少了所需的计算资源量。

水平自动扩展:如果预测流量在指定时间段内发生变化(例如,午餐时间或黑色星期五和圣诞节前后与事件相关的高峰时段的食品配送服务),解决方案应该能够自动水平扩展,以便平衡延迟要求和资源利用率。

无服务器和零扩展:如果预测流量仅限于每月几次预测,您可能会考虑像 KnativeAWS Lambda 这样的无服务器架构。当没有请求正在进行时,这些解决方案可以缩减到 0 ,从而节省成本。

结论

数据科学、开发运维以及产品团队都与 ML 项目的成功息息相关。这些团队需求的多样性带来了与典型的软件部署过程非常不同的挑战。虽然可能没有必要在 ML 模型部署系统的第一次迭代中解决所有这些考虑事项,但是知道未来可能会发生什么是有帮助的,以便可以进行适当的规划和研究来确保最大的成功可能性。

一点背景:我叫杨,是开源模型服务框架BentoML的创建者。此前,我曾在 Databricks 的统一数据科学平台早期帮助创建该平台。如果你有兴趣了解更多关于这个话题的信息或参与讨论,请加入我们的 社区 slack ,在这里数百名传销从业者聚集一堂,讨论传销的所有事情。

机器学习中的设计模式

原文:https://towardsdatascience.com/design-patterns-in-machine-learning-for-mlops-a3f63f745ce4

概述创建成功的机器学习解决方案时遇到的一些最常见的设计模式

朱莉安娜·马尔他在 Unsplash 上拍摄的照片

介绍

设计模式是针对常见问题的一组最佳实践和可重用解决方案。数据科学和软件开发、架构等其他学科。由大量重复出现的问题构成,因此尝试对最常见的问题进行分类,并提供不同形式的蓝图来轻松识别和解决它们,可以为更广泛的社区带来巨大的好处。

在软件开发中使用设计模式的想法首先是由 Erich Gamma 等人提出的。艾尔。在“设计模式:可重用面向对象软件的元素”【1】中,以及最近应用于机器学习过程中,感谢Sara Robinson 等人。艾尔。在“机器学习设计模式”【2】。

作为本文的一部分,我们现在将发现构成 MLOps 的不同设计模式。MLOps(机器学习- >操作)是一套旨在将实验性机器学习模型转化为生产性服务的流程,可随时在现实世界中做出决策。其核心是,MLOps 基于与 DevOps 相同的原则,但更侧重于数据验证和持续培训/评估(图 1)。

图 1: DevOps 和 MLOps(图片由作者提供)。

MLOps 的一些主要优势包括:

  • 缩短上市时间(更快的部署)。
  • 增强的模型稳健性(更容易识别数据漂移、重新训练模型等。).
  • 更灵活地训练/比较不同的 ML 模型。

另一方面, DevOps 强调了软件开发的两个关键概念:持续集成(CI)和持续交付(CD)。持续集成侧重于使用中央存储库作为团队在项目上协作的手段,并在不同团队成员添加新代码时尽可能自动化添加、测试和验证新代码的过程。通过这种方式,可以随时测试应用程序的不同部分是否可以正确地相互通信,并尽快识别任何形式的错误。连续交付的重点是平稳地更新软件部署,尽量避免任何形式的停机。

MLOps 设计模式

工作流管道

机器学习(ML)项目由许多不同的步骤构成(图 2)。

图 2: ML 项目关键步骤(图片由作者提供)。

当建立一个新模型的原型时,为了对整个过程进行编码,通常会从使用一个单独的脚本( monolithic )开始,但是随着项目复杂性的增加,越来越多的团队成员可能会参与进来,那么就有必要将项目的每个不同的步骤分成一个单独的脚本(微服务)。采用这种方法的一些好处是:

  • 更容易试验不同步骤编排的变化。
  • 通过定义使项目可伸缩(新的步骤可以很容易地添加和删除)。
  • 每个团队成员可以专注于流程中的不同步骤。
  • 可以为每个不同的步骤创建分离的赝像。

工作流管道设计模式旨在定义一个蓝图来创建 ML 管道。ML 管道可以用一个有向无环图(DAG) 来表示,其中的每一步都用一个容器来表征(图 3)。

图 3:有向无环图示例(图片由作者提供)。

按照这种结构,就有可能创造可重复和可管理的 ML 过程。使用工作流管道的一些好处是:

  • 通过添加和删除流程中的步骤,可以创建复杂的实验来测试不同的预处理技术、机器学习模型和超参数。
  • 单独保存每个不同步骤的输出,如果仅在最后的步骤中应用了任何改变,则可以避免在流水线的开始重新运行步骤(因此节省了时间和计算能力)。
  • 在出现错误的情况下,可以很容易地确定哪个步骤可能需要更新。
  • 一旦使用 CI/CD 部署到生产中,就可以根据不同的因素安排管道重新运行,这些因素包括:时间间隔、外部触发器、ML 指标的变化等。

功能存储

特征存储是为机器学习过程设计的数据管理层(图 4)。这种设计模式的主要用途是简化组织管理和使用机器学习功能的方式。这是通过创建某种形式的中央存储库来完成的,该存储库由公司用来存储为 ML 过程创建的所有特征。通过这种方式,如果数据科学家可能需要不同 ML 项目的相同特征子集,他们就不必多次经历将原始数据转换为处理过的特征的过程(这可能很耗时)。两种最常见的开源特性商店解决方案是 FeastHopsworks

图 4:特性存储设计模式(图片由作者提供)。

关于特性商店的更多信息可以在我之前的文章中找到。

改变

Transform 设计模式旨在通过将输入、功能和转换保持为独立的实体,使在生产中部署和维护机器学习模型变得更加容易(图 5)。事实上,原始数据通常需要经过不同的预处理步骤,以便随后用作机器学习模型的输入,并且这些转换中的一些需要被保存,以便在预处理数据以进行推断时被重新使用。

图 5:输入和特性的关系(图片由作者提供)。

例如,在训练 ML 模型之前,通常将归一化/标准化技术应用于数值数据,以便处理异常值并使数据看起来更像高斯分布。然后应该保存这些转换,以便将来有新数据可供推断时可以重用。如果不保存这些转换,那么我们将在训练和服务之间创建一个数据偏差,其中为推理提供的输入数据与用于训练 ML 模型的输入数据相比具有不同的分布。

为了避免培训和服务之间的任何类型的偏差,另一个解决方案是利用特性存储设计模式。

多模态输入

不同类型的数据,如图像、文本、数字等。可以用于训练 ML 模型,尽管某些类型的模型只能接受特定类型的输入数据。例如,Resnet-50 能够仅将图像作为输入数据,而其他 ML 模型如 KNN (K 最近邻)能够仅将数字数据作为输入。

为了解决一个 ML 问题,可能需要使用不同形式的输入数据。在这种情况下,需要应用某种形式的转换来创建所有不同类型的输入数据的公共表示(多模态输入设计模式)。作为一个例子,让我们想象我们被提供了文本、数字和分类数据的组合。为了训练一个 ML 模型,我们可以利用诸如情感分析、单词包或单词嵌入等技术将文本数据转换成数字格式,并使用 one-hot-encoding 来转换分类数据。通过这种方式,我们将拥有相同格式(数字)的所有数据,准备用于训练。

串联

在某些情况下,仅仅使用单一的 ML 模型是不可能解决 ML 问题的。在这种情况下,有必要创建一系列相互依赖的 ML 模型,以实现最终目标。作为一个例子,让我们假设我们正试图预测向用户推荐什么样的商品(图 6)。为了解决这个问题,我们希望首先创建一个能够预测用户年龄是大于还是小于 18 岁的模型,然后根据该模型的响应将我们的流路由到两个不同的 ML 推荐引擎之一(一个用于为 18 岁以上的用户推荐产品,另一个用于为 18 岁以下的用户推荐产品)。

图 6:级联设计模式(图片由作者提供)。

为了创建 ML 模型的这个级联,我们需要确保一起训练它们。事实上,由于它们相互依赖,如果第一个模型发生了变化(没有更新其他模型),那么这可能会导致后续模型的不稳定性。然后,可以使用工作流管道设计模式来自动化这种类型的流程。

结论

在本文中,我们探讨了支撑 MLOps 的一些最常见的设计模式。如果你有兴趣了解更多关于机器学习中的设计模式,更多信息可以在 AIDevFest20 上 Valliappa Lakshmanan 的 this talk 和“机器学习设计模式”图书 public GitHub repository 中找到。

联系人

如果你想了解我最新的文章和项目,请通过媒体关注我,并订阅我的邮件列表。以下是我的一些联系人详细信息:

文献学

1“设计模式:可重用面向对象软件的要素”(Addison-Wesley,1995)。访问地点:www.uml.org.cn/c%2B%2B/pdf/DesignPatterns.pdf

[2]“机器学习设计模式”(Sara Robinson et .艾尔。,2020)访问:https://www . oreilly . com/library/view/machine-learning-design/9781098115777/

面向机器学习工程师的 Python 设计模式:抽象工厂

原文:https://towardsdatascience.com/design-patterns-with-python-for-machine-learning-engineers-abstract-factory-f761f89a3c12

奥马尔·弗洛雷斯在 Unsplash 上拍摄的照片

了解如何通过采用设计模式来构建代码

简介

一个模式描述了一个经常重复出现的问题,并根据类/对象组织提出了一个可能的解决方案
,这个解决方案
通常被认为可以有效地解决问题本身。
设计模式有四个主要特征:

  • 名称:帮助我们用一两个词来识别问题和解决方案的助记参考。
  • 问题:问题的描述和模式提供解决方案的环境。
  • 解决方案:描述了构成
    解决方案的基本元素以及它们之间的关系。
  • 后果:规定应用建议的解决方案可能带来的后果。

设计模式有几个类别,但有两个主要标准:

图案所指:

  • 对象:在
    执行时可以改变的对象之间的关系。
  • :关注类和子类之间的关系。

模式的作用(目的):

  • 创建:关于创建对象的过程。
  • 结构:关注类和对象的组成。
  • 行为:定义类和对象如何交互,以及
    在它们之间分配职责。

但是存在多少设计模式呢?在下图中,您将看到一个设计模式列表,该列表根据它们的范围和目的组织在一个表格中。在下面的文章中,我们将介绍最常见的设计模式。

设计模式(作者图片)

如何定义一个设计模式?

设计模式是由一些描述和促进其使用的基本属性定义的。这些属性是:

  • 结构:通过 UML 图形化表示所涉及的类以及它们之间的关系
    。(1) 参与者:参与模式的班级,他们的关系和责任。(2) 协作:各个阶层如何协作实现目标。
  • 后果:使用该模式的优缺点以及使用该模式可能产生的副作用。
  • 实现:实现
    的技术和建议,也参考具体的编程语言。
  • 示例源代码:为实现提供
    指南的代码片段。
  • 已知用途:在现有系统中使用的例子。
  • 相关模式:与其他模式的区别和最重要的关系。

抽象工厂

在我看来,理解如何定义设计模式的最简单的方法是展示一个例子,让我们从抽象工厂开始。

我们经常发现自己不得不创造一些相似但彼此不同的新对象。假设我们必须创建几辆汽车,它们都将共享几个属性,例如有四个轮子和一个方向盘,它们还将共享几个功能,例如加速停止(刹车)
但是如果我们想要创造出有自己特色的汽车,比如“老爷车”“赛车”,这些汽车将会有特定于子类的属性和功能。

这正是设计模式帮助我们的地方,帮助我们轻松、动态地管理这些共享属性和功能但又互不相同的对象的创建。抽象工厂推荐的第一件事是为每个产品显式声明接口(不能被实例化的类),例如一辆汽车和一辆自行车产品。然后你可以让所有的产品变体实现这些接口,这样我们就可以确信它们将共享所有对象共有的属性和功能,比如 accelerate()stop()

作者图片

现在我们定义一个抽象工厂的接口,也就是说,一个类的接口,其功能允许我们创建一个类型为 CarBike 的新对象。将实际创建类型为 racevintage 的汽车的工厂必须实现该接口的方法。

作者图片

这样,每个特定的子产品都有一个工厂,并且在创建每个单独的对象时,不必添加属性和函数。

此外,使用 RaceFactory 等工厂规格的客户将获得一辆赛车。几个月后,当他去买一辆新自行车时,它也将是一辆赛车,因为它来自同一家工厂,所以他不必担心有不同风格的产品。

抽象工厂 UML(作者图片)

让我们编码

如果到目前为止你仍然不清楚这种设计模式是为了什么以及它是如何工作的,欢迎加入我们的俱乐部!如果你和我一样,编程会让你头脑清醒。

首先,我们创建一个 AbstractFactory,在 Python 中,抽象类是继承 ABC 的类,如下例所示。这个抽象类有两个方法,一个创建汽车,一个创建自行车。所以任何继承这个抽象类的类都必须以自己的方式实现这两个方法。

在 RaceFactory 和 VintageFactory,我们都有制造汽车和自行车的方法。您会看到两者都返回类型为 Car 和 Bike 的对象(用符号表示: - > Car )。
但实际上,Car 和 Bike 是抽象类,实际的类是 RaceCar 和 VintageCar (RaceBik 和 VintageBike)。通过这种方式,每个工厂将生产与之相关的汽车(或自行车)。
在这种情况下,RaceCar (RaceBike)和 VintageCar (VintagBike)唯一不同的是,它打印的是“快”而不是“慢”

现在,客户将选择其中一家工厂(将其作为输入),然后他将创建一辆汽车和一辆自行车,他将确保汽车和自行车具有相同的风格,它们要么是赛车型,要么是复古型。

在创建它们之后,他将使用 Car 和 Bike 类提供的方法对它们进行测试。

最后的想法

设计模式描述了一个经常重复出现的问题,并根据类/对象组织提出了一种可能的解决方案,这种解决方案通常被认为可以有效地解决问题本身。我们经常面临这样的问题:想要实例化一个对象而不精确地指定类,但是尊重多个对象之间的一致性,为此,学习如何使用抽象工厂是非常有用的。
在未来的文章中,我将阐述机器学习工程师需要了解的其他设计模式,以便能够编写干净的结构化代码。

结束了

马赛洛·波利蒂

LinkedinTwitterCV

用 Python 为机器学习工程师设计模式:观察者

原文:https://towardsdatascience.com/design-patterns-with-python-for-machine-learning-engineers-observer-23cde7ecb2ed

梁杰森Unsplash 上的照片

通过一个基于 Instagram 的例子了解如何使用观察者设计模式

介绍

在我的上一篇文章“用 Python 为机器学习工程师设计模式:抽象工厂”中,我介绍了设计模式,以及为什么了解它们并能够使用它们很重要。我还深入研究了本系列的第一个设计模式 AbstractFactory。如果你还没有读过,我建议你看一看。

为了快速总结计算机科学中的设计模式,特别是在软件工程中,它是一个概念,可以被定义为对一个重复出现的问题的通用设计解决方案。它是一种描述或逻辑模型,用于解决在软件设计和开发阶段的不同情况下可能出现的问题,甚至在计算部分的求解算法定义之前。

设计模式总是由 5 个元素定义:名称、问题、解决方案和结果。

根据设计模式所解决的问题以及模式是应用于类还是对象,设计模式有多种类型。在下图中,您可以看到最常见的设计模式。

设计模式(作者图片)

观察者

在我看来,理解如何定义设计模式的最简单的方法是通过展示一个例子,让我们从观察者设计模式开始!

假设你是一个足球迷,你最喜欢的球员是克里斯蒂亚诺罗纳尔多。你充满热情,想要知道关于这个球员的每一条新闻和更新,所以你决定在 Instagram 上关注他。
现在你关注了克里斯蒂亚诺·罗纳尔多,每次他在 Instagram 上发布帖子或做出动作,你都会收到通知,所有内容都会出现在你的订阅源中。
在这个模式中,我们有两个演员,你是观察者,而 c 罗是主体

但你会很容易明白,这不可能是一对一的关系。克里斯蒂亚诺罗纳尔多是 Instagram 上最受关注的账户之一,迄今为止拥有大约 4 . 82 亿关注者。这会让你明白一个主题可以有多个观察者,他们都对他的更新感兴趣。所以关系必须是一对多类型的。

主体与观察者的关系(图片由作者提供)

类图

在我向您展示如何用 Python 实现这种设计模式之前,我为您带来了一个带有类图的图像,以便您能够理解需要应用的类和方法之间的关系。

类图(图片由作者提供)

  • 观察者和主体是界面
  • 所有需要数据的观察者都需要实现观察者接口。
  • notify()方法定义了当主体提供其数据时要采取的动作。
  • 该主题维护一个名为“观察者”的观察集合,它是当前注册的观察者的列表。
  • registerObserver()和 unregisterObserver()分别是添加和移除观察器的方法。
  • 当数据发生更改并且需要向观察器提供新数据时,会调用 notifyObservers()。

我们来编码吧!

首先,我们创建抽象类 Observer 和 Subject。在 Python 中,要创建一个抽象类,你必须继承 ABC。观察者将拥有一个 update()抽象方法,当他关注的 VIP 账户发布新内容时,该方法会立即通知他。
而主题,即拥有众多关注者的 VIP 有 3 个抽象功能,一个用于注册新用户,一个用于删除他,另一个(最重要的)用于通知他的所有关注者他创建了一个新帖子。

现在我们创建 InstaVIP 类,即负责实例化我们的 VIP(如 Cristiano Ronaldo)的类。
注意,notifyObserver 滚动查看名为 observers_collection 的列表中保存的所有 VIP 关注者,以通知他们。

现在我们发展跟随者类,也就是我们观察者的类。每个观察者由一个用户名定义。在这个类中,我们应该定义 update()方法,每当 VIP 发布内容时都会调用这个方法。理想情况下,这种方法将更新我们的 Instagram 提要,但在这种情况下,它只会简单地打印出 VIP 发布了新内容的事实。

现在我们主要创建两个 Instagram 账户:@真实账户 1@官方账户 2。然后,我们实例化我们的 VIP 克里斯蒂亚诺罗纳尔多。
受试者必须注册我们的关注,当这两个账户在罗纳尔多的 Instagram 账户中点击“关注”时,就会发生这种情况。
现在 c 罗将使用 new post()方法创建一个新帖子。

在生成克里斯蒂亚诺罗纳尔多的新帖子时,将调用两个帐户的更新方法,这实际上将打印:

created new post
notification for @theRealAccount1 : new post created by Ronaldo notification for @account2Official : new post created by Ronaldo

最后的想法

设计模式描述了一个经常重复出现的问题,并根据类/对象组织提出了一种可能的解决方案,这种解决方案通常被认为可以有效地解决问题本身。

这种模式被广泛使用:它被用于许多库、GUI 工具包、MVC 架构模式、消息传递和实时系统。

这种设计模式带来了多种优势:

  • 该模式是应用使对象之间的耦合尽可能弱的原则的一个很好的例子。所以,增加了所谓的宽松。
  • 它允许将数据有效地发送到其他对象,而不需要对 Subject 或 Observer 类进行任何更改。
  • 受试者和观察者是独立的,其中一个组件的变化不需要另一个组件的变化。
  • 观察员可以随时添加/删除

在各种各样的应用程序中,我们发现了同样的问题,即需要在主题更改数据时得到通知(例如,see 已经发布了一些内容),这种设计模式如果应用得当,可以让我们的生活变得更加轻松!在未来的文章中,我将讨论机器学习工程师需要知道的其他设计模式,以便能够编写干净的结构化代码。

结束了

马赛洛·波利蒂

LinkedinTwitterCV

设计思维提高您的数据科学

原文:https://towardsdatascience.com/design-thinking-improves-your-data-science-4c4aaaa9204a

了解您的问题并增加项目影响的新方法

杰森古德曼Unsplash 上拍摄的照片

设计思维帮助你组织思想,创造最佳解决方案。在项目开始时,我们这些数据科学家会就问题陈述和数据提出问题。涉众通常会对他们的问题给出一些具体(或不具体)的答案。这通常发生在几次会议中。我们将需求牢牢地掌握在手中,并开始争论数据、构建模型等。

但是…如果你正在做的问题陈述不是你被要求解决的真正问题呢?如果最后,在做了大量工作之后,你向涉众展示了你的成果,而他们并不感兴趣,或者更糟的是,没有使用这些成果,那该怎么办?在这篇文章中,我将介绍一些使用设计思维来避免这些结果的方法。

这对数据科学家有什么影响?

数据科学家可能犯的最大错误之一是直接跳到项目的开发,而没有花足够的时间来理解具有最大影响的真实目标。

有时候,这些目标很容易确定,也很容易看到并发症的到来。如果数据科学家熟悉问题空间和/或集成到业务流程中,这通常是正确的。

许多数据科学家作为“第三方”参与进来,帮助推动团队的战略目标。这意味着误解问题和创建不符合涉众期望的解决方案的风险很高。或者更糟——创造出错误的解决方案。如果您参与了这些类型的项目,那么这篇文章就是为您准备的。在开始数据科学工作之前,我们可以使用一个框架来帮助消除这种风险。这个框架就是设计思维。

设计思维帮助你组织思想,创造最佳解决方案。

什么是设计思维?

根据交互设计基金会的说法——“设计思维是一个迭代的过程,在这个过程中,你寻求理解你的用户,挑战假设,重新定义问题,并创造出你可以原型化和测试的创新解决方案。”

这使我们作为数据科学家能够:

  • 弄清楚我们试图解决的真正问题是什么
  • 了解我们是否需要一个模型
  • 确保利益相关者的想法就是我们要提供的
  • 了解该模式将如何影响用户/业务

有几个正式的基于设计思维的过程。如果你愿意,你可以选择跟随他们(比如设计冲刺)。然而,对我来说,设计思维不是一个正式的过程,而是一个如何思考以及如何理解你在和谁/什么一起工作的框架。这意味着设计思维结构可以根据受众、业务和问题而变化。

设计思维的一般阶段

通常,设计思维被认为是移情、定义、构思、原型和测试的迭代路径。这里强调的是迭代这个词。虽然我正在检查的步骤是线性显示的,但是每个步骤都可以根据需要再次执行。很多时候,我重复这些步骤来收集更多的信息,并进一步完善我对问题、用户或涉众的理解。

每个步骤的一般定义是:

  • 感同身受:对你的客户和他们面临的问题建立理解。
  • 定义:定义需要解决的问题。
  • 想出解决问题的所有不同方法。任何想法都可能是好主意。之后,缩小到最佳想法。
  • 原型:为你想要建造的东西开发一个尽可能简单的工作模型。
  • 测试:和你的利益相关者一起测试这个想法。获得他们的反馈。

让我们回顾一下如何利用每个步骤开始下一个项目。

有同感

作为数据科学家,我们的第一本能是开始理解我们将要用来解决问题的数据。然而,我们需要了解,对于涉及这个问题的人来说,数据之外还有什么。我们可以拥有世界上所有的数据,但如果我们不知道用户或利益相关者如何与产品交互,不了解他们对产品的理解,我们就不可能做出完全解决他们问题的解决方案。这里我最喜欢的一句话是,我们需要“爱上问题”。爱上这个问题让我们能够理解我们的客户/利益相关者正在经历的事情。

有几种不同的方法可以让我们产生共鸣:

面谈

  • 花一个小时(或更多)与利益相关者进行一对一会谈。
  • 学习你的利益相关者现在使用的语言、行话和方法,以及他们看到的差距。
  • 了解他们今天是如何工作的,或者他们目前看到了什么。
  • 收集他们的想法,如果他们有魔杖可以让一切变得更好,他们会如何使用该产品。

产品探索

  • 了解您正在使用的产品,如果它现在有版本的话
  • 在测试环境中获得一个用户帐户,或者在生产环境中获得一个只读帐户,以测试产品并熟悉新用户如何与产品交互。
  • 利用您对数据的理解来了解您的解决方案将如何影响您的解决方案的客户。
  • 作为一名数据科学家,您有一个非常有用的视角来识别其他人可能看不到的其他可能的改进或机会。

与领导层、产品经理和团队领导的讨论

  • 询问领导层目前如何使用产品/数据/系统来推动他们的业务目标(如果是的话)。确定解决方案将如何或可能如何促成这些结果。注:如果解决方案对业务目标没有贡献,它是涉众想要或需要的吗?
  • 如果领导层没有使用结果来驱动任何业务目标,那么就要弄清楚向流程中添加模型度量的需求。注意:也许可以创建一个有助于业务总体目标的新指标。

规定

一旦收集了背景和理解,我们需要将它们提炼为需要解决的问题。定义阶段应该与您的利益相关者一起完成,它将鼓励就需要解决的问题进行富有成效的对话。这些对话还确保了问题对客户/利益相关方和数据科学家的意义的一致性。

在“定义”阶段,将模糊的用户反馈转化为离散的问题。

其中最重要的方面是:

  1. 用利益相关者使用的术语和语言来定义问题
  2. 使用从访谈中获得的知识来告知应该生成什么问题陈述

现在,问题陈述有可能演变成远景陈述。减轻这种情况的方法是确保您定义的问题是可管理的和可操作的。如果你和习惯于为团队设定愿景的人一起工作,这一点尤其重要,因为这种愿景设定对他们来说可能是最自然的。

相反的情况也可能是正确的——一个问题陈述可能被锁定在当今问题的框架中。如果你的观众是由那些在日常工作中的人组成的,这种情况就会发生。重要的是要确保问题陈述是一个延伸目标,并且我们不必考虑当今的框架、限制或结构。在项目完成后,它们可能没有用,甚至不存在。

有几种方法来定义问题陈述,但我最常用的两种是“我们如何”(HMW)陈述和五个 W 的使用,这将在下面讨论。

我们可能如何“HMW”陈述

我最喜欢的生成问题陈述的方法是使用 HMW 陈述。在这个过程中,每个人都要写下问题陈述,以“我们该如何……”开始。它们通常是单独生成的,并由小组投票以获得最佳的问题陈述。HMW 的陈述写得很积极,以确保我们记住用户的感受。

一些例子是:

  • " 我们如何……重新设计购车体验,让人们随时知道他们的车值多少钱?”
  • 我们如何……让用户确信他们的数据是安全的?”

确保 HMW 的陈述没有暗示结果——目标不是找出如何解决这个问题,而是确定问题是什么。以下是根据前面的例子改写的 HMW 语句的一些例子,这些例子暗示了一种结果:

  • " 我们如何……构建一个模型,从汽车元数据中预测汽车价值?”
  • 我们如何……创建一个数据锁定系统来保护我们的数据?”

看到暗示一个结果有多容易了吗?我们都有想法,但在我们开始考虑如何解决问题之前,我们希望确保我们在问题上意见一致。有时候,你可能会发现你被要求解决的问题可以用模型之外的东西来解决。

现在,这个过程的目标是提出一个或多个方法。相关人员还需要理解这条路径是迭代的,团队正在寻找正确的问题和正确的解决方案——知道想法和需求可以像业务一样变形和改变。

五个 W

如果您时间紧迫,这五个 W 允许将问题陈述快速分解成一个单独的问题陈述。这五个 W 是谁、什么、何时、何地和为什么。在这种情况下,问问你自己和团队(可能会有一些争论):

  • 谁将使用结果?
  • 他们需要用它做什么?
  • 他们打算什么时候使用它?(在他们流程的哪个点?)
  • 他们会在哪里,在什么平台上使用?
  • 他们为什么要使用这个结果?(重要吗?)

一旦你有了 HMW 陈述或者回答了五个 W,你就至少有了一个团队可以解决的问题陈述。投票选出最重要的问题陈述后,下一步是考虑解决问题的所有方法。这是被称为构思的设计思维过程的想法产生步骤。

想象

现在你有了我们的问题陈述,你需要考虑解决问题的不同方法。在这一步,任何想法都是好想法,重数量轻质量。如果可能的话,确保与你的利益相关者一起完成这一步,这样你就可以立即收到关于这些想法的反馈。利用这段时间将技术性发言融入到非技术性的故事中。

当你构思时,试着讨论以前解决过类似问题陈述的类似解决方案。这可能是在同一个行业或不同的行业。这些类型的对话可以产生更多有趣的想法和灵感,将现有的或新的数据与问题陈述结合起来。例如,如果你试图找出卡车司机停车睡觉的最佳地点,你能建立一个类似 Airbnb 的模型,让司机呆在家里吗?

使用白板、PowerPoint 或 Google Sheets 来记录想法是有益的。想出尽可能多的想法,并根据复杂性、完成时间和影响对它们进行优先排序。为此,我喜欢记住闪电决策堵塞 对努力矩阵的影响。

闪电决策堵塞对工作矩阵的影响

原型

一旦你有了如何解决问题的想法列表,下一步就是建立一个原型。

想出几个快速建造的原型。

现在,我们不是在谈论一个功能齐全的应用程序。这里的目标是建立一些看起来可以工作的东西,但是要抱着“假装直到你成功”的心态。

最重要的是,这个成果不是模型设计。它应该是您希望构建的任何模型的输出。最终,您的数据科学解决方案可能会以某种可视化形式结束,这可能是一个仪表板、应用程序中的通知或 API 响应。在这种情况下,我们期待构建出最终结果可能的样子,而不是一个实际的完全功能的结果。

这个原型可以是模型将返回的内容的 JSON 文件,应用程序外观的 PowerPoint,或者模型将生成的图表的手绘图片。没有构建原型的“最佳”方法。我通常会采用这种方法,让我在几个小时内创建几个逼真的原型。

试验

一旦你有了一个或多个原型,测试它们并通过展示给利益相关者来收集关于设计的反馈。这是反馈的重要时机。如果你想要创造的东西没有得到涉众的认可,这通常是任何差异出现的时候。这也是反馈和调整的时间。涉众注意到的任何问题或者可以改进的地方都应该被添加到最终模型的需求中。

收集关于原型的反馈时,一些重要的考虑事项是:

回答问题不引导反馈

尽量不要直接回答类似“这个指标应该是什么?”不要直接用“那是每小时预约的数量”来回答,而是问用户利益相关者或者用户认为是什么。

例如,回答“这个指标应该是什么?”“如果有的话,你认为这个指标意味着什么?”

这让您更深入地了解问题的驱动因素。可能是特征不够清楚,或者是结果没有用或者不符合预期?

沉默是好的

不要介意沉默——让利益相关者或用户玩或研究结果。尽量不要引导用户浏览你的原型。如果你的解决方案应该是不言自明的,那么用户应该能够理解解决方案告诉他们什么。当用户研究原型时,如果有安静的咒语也没关系。

测试后

在我们得到简单原型的反馈后,真正的工作就开始了。

有了清晰的结果,有了能够为客户着想的保证,有了提供客户重视的解决方案的信心,您就可以开始构建真正的解决方案了。

设计思维过程的好处是,你可以清楚地知道需要做什么。此外,您的利益相关者感觉与您的流程有联系。他们相信你正在创造对他们有益的东西。他们相信你理解他们的观点,他们的机会在哪里,并且你能发现其他的机会在哪里。

尝试在你的下一个项目中使用这些方法——你可能会对你学到的东西感到惊讶。

更多资源

如果你有兴趣深入研究几个不同的设计思维过程的细节,有几个优秀的资源也有助于激发这篇文章。我最喜欢的两个是:

除非另有说明,所有图片均为作者所有。

请随时在 LinkedIn 上与我联系。

具有激活和丢失功能的设计思维

原文:https://towardsdatascience.com/design-thinking-with-activation-and-loss-functions-5516cf323942

根据“人-术语”重新定义算法问题

照片由像素皮克斯拜拍摄

深度学习越来越受欢迎,因为它能够获取大量数据来解决复杂的问题。虽然像机器学习这样的应用看起来几乎很神奇,但重要的是要记住它们并不神奇。

关于深度学习架构的一点信息

深度学习架构被热切地研究和开发,具有独特的功能和理念来解决不同的问题。例如,递归神经网络(RNNs)在自然语言处理中表现良好,因为它们可以保留基于时间的信息并理解句子结构。相比之下,卷积神经网络(CNN)更适合对图像进行分类,因为它们可以通过卷积层处理空间数据。

在深度学习模型中的许多对象和函数中,激活和损失函数提供了许多可定制性。类似于单词的组合可以构建成千上万个不同的句子,这些功能可以实现许多解决问题的技术。

激活功能

激活函数将一个神经元的输入映射到下一层的输入。在这样做的时候,深度学习架构被设计成处理非线性问题,非线性问题比线性问题更难解决。

卷积神经网络或 CNN 的密集层内的激活函数的一般示意图(图片由作者在 Notability 提供)。

在这个例子中,一个神经元接收四个输入,每个输入都有一个权重来确定它在网络中的重要性。净输入是输入权重乘积与随后添加的偏差的总和。

接下来是激活函数,它将最终调用传递到下一个输入层的内容。虽然有许多不同的激活功能,这里是一些常用的。

整流线性单元(ReLu)

这些函数中的每一个都利用不同的方法来解决负 x 值的问题。

整流线性单元采用整流激活函数来确定神经元的输出。对于正的 x 值,结果就是简单的 x 。对于负的 x 值,结果固定为 0。ReLu 函数使每个具有负净输入的神经元无效,导致许多死亡神经元。

其他激活方法通过使神经网络中的负值持续存在,在防止死亡神经元方面变得可行。对于小于 0 的值 x ,Leaky ReLu 将函数的斜率设置为 0.01,而参数 ReLu (PReLu)允许模型为值 a 的斜率进行训练。

仅凭这三个函数,我们就能更好地评估问题的复杂程度。使用 ReLu 表现良好的模型不需要太多的信息来得出准确的结论。相比之下,由许多不同斜率值的预激活函数组成的网络需要更高的精度和数据处理。

乙状结肠

Sigmoid 函数通常用于深度学习架构,因为它们很容易将非线性引入网络(图片由作者以 Notability 表示)。

通常使用 Sigmoid 函数,因为它们将净输入展平到 0 到 1 之间的值。该激活函数通常位于输出层之前,因为它为每个输出标签提供了概率。鉴于运算的简单性质,Sigmoid 函数也很好地引入了非线性。

门控线性单元(GLU)

门控线性单元有一种独特的激活神经元的方法。

GLUs 将净输入乘以通过 sigmoid 函数的净输入产生的输出。这样,他们以一种微妙的方式增加了网络的非线性。当输出变得非常大时,Glu 表现良好,因为它们乘以 0 到 1 之间的值。

Softmax

Softmax 函数提供了另一种分配概率的方法(由作者以显著性显示图像)。

与 sigmoid 函数类似,softmax 函数输出概率。然而,这个激活函数做得更明确一些。分子代表一个类的输入,分母代表类输出的总和。这种激活函数改善了对模型数据的直观分析,因为具有较大输出的神经元将比其他输出指示更可能的预测。

损失函数

损失函数与“成本函数”同义,因为它们计算函数的损失来确定其可行性。

损失函数在神经网络的末端执行,比较实际和预测的输出,以确定模型的准确性(图像由作者以显著性表示)。

上面我们有一个激活函数输出输出层的概率。通过比较预测值和实际答案,我们可以计算模型的准确性,并更新整个网络的权重。比如说,一个卷积神经网络预测一个图像有 30%的几率是猫,10%的几率是青蛙,60%的几率是马。如果图像的实际标签是一只猫,那么损失会非常大。

选择最佳损失函数至关重要,因为它是模型是否过度拟合或欠拟合的最佳指标。下面是一些典型的损失函数,供你在下一个深度学习项目中考虑。

平均绝对误差(MAE)和均方误差(MSE)

平均绝对误差和均方误差在不正确预测的惩罚中有所不同(图片由著名作者提供)。

平均绝对误差和平方误差是深度学习架构中不同哲学的很好例子。当预测值和实际值之间的差异增大时,MSE 与 MAE 相比会受到更大的惩罚。

如果模型变得对错误过于敏感并记忆训练数据,那么这种更大的损失可能变得不利。但是,MAE 损失函数可能会导致缺少重量变化,从而导致模型不符合要求。

二元交叉熵

二元交叉熵可以使用 2 种不同的计算方法,具体取决于实际值(图片由著名作者提供)。

二进制交叉熵(BCE)利用-log(x)函数,因此相对于 1,概率值越高,损失越小。这种方法将有助于解决二元分类问题。如果模型试图确定图像是钢笔还是铅笔,并且它预测图像是钢笔的概率为 20%,则图像是铅笔的概率为 80%。

现在,假设图像实际上是一支铅笔。根据该等式,当实际值为 1 时,您将执行 0.8 的负对数,导致损失 0.097。您还可以通过比较笔的预测输出 0.2 和实际值 0 来计算该损失。

交叉熵

交叉熵只通过实际类的预测值计算损失(图片由《显要》作者提供)。

交叉熵(CE)损失函数有效地利用了与二进制交叉熵(BCE)损失函数相同的函数。然而,CE 将该函数推广到具有多类输出的网络。也是因为这个原因,模型只能通过正确类别的预测值来计算损失函数。一键编码将 0 分配给错误的类,将 1 分配给有效的类,以区分它们。

运用设计思维

设计思维参与解决问题的过程。虽然它大量用于用户界面和用户体验(UI/UX)设计,但它适用于构建深度学习架构。

这种解决问题的技术与神经网络通过其流畅的“开箱即用”但系统的方法理解复杂的非线性关系的能力有相似之处。我发现有一句话很好地概括了这一点,它是这样的:

“设计思维是一个非线性的迭代过程,团队使用它来了解用户,挑战假设,重新定义问题,并创建原型和测试的创新解决方案。”—交互设计基础

那么,我们到底该如何应对这一切呢?

设计思维有 5 个步骤,它们和科学方法一样经过了反复试验。

  1. 感同身受:与用户建立联系,倾听他们的心声。尽可能了解用户的问题。
  2. 定义:清楚地概述你的用户的问题。试着将用户的问题与他们的其他方面联系起来。
  3. 构思:针对每个确定的问题提出多种解决方案。尝试将你的解决方案与过去或替代方案进行比较,找出改进的方法。
  4. 原型:为每个问题集中到一个单一的解决方案,并为它们制定小的、具体的实例。
  5. 测试:部署和评估您的解决方案。如果遇到困难,尝试重新定义你的问题。

当测试和重新评估模型的感兴趣的问题和解决方案时,确定使用哪个激活和损失函数变得相关。通过比较和对比某些函数组合的准确性,我们可以更好地理解手头的问题。

例如,如果我们试图在给定多个因素的情况下预测房屋的最优销售价格,我们可以运行 ReLu 作为我们的激活函数,并尝试均方误差、二元交叉熵和交叉熵损失函数。更进一步,假设 MSE 损失函数是提供有意义信息的唯一成本函数。然后你可以推断出你正在处理一个线性回归问题。假设 MSE 成本函数比 MAE 成本函数提供更好的结果。在这种情况下,您可以得出结论,在您的数据中存在相对更复杂的关系,需要一个更具响应性的损失函数来正确训练权重。

有了这些信息,您就可以尝试 ReLu、PReLu 和 Leaky ReLu,找出最佳的激活功能。如果 Leaky ReLu 表现最好,您将有更多的证据证明权重必须非常敏感,以考虑数据中的复杂性。

我们可以将相同类型的演绎分析分别应用于使用 BCE 和 CE 损失函数的二元逻辑回归和多类分类问题。根据哪些函数执行得最好和最差,您可以将问题转移到更直接的方面。

这种解决问题的技术也可以推广到深度学习架构。与主要使用 tanh 和 sigmoid 激活函数的递归神经网络(RNNs)相反,通常发现卷积神经网络(CNN)使用 ReLu 激活函数。这一发现表明,在分析句子时,RNNs 必须对识别时间模式更敏感。

总结

“设计团队使用设计思维来解决定义不清/未知的问题(又名邪恶问题),因为他们可以用以人为中心的方式重新构建这些问题,并专注于对用户来说最重要的事情。”—交互设计基础

在整篇文章中,越来越清楚的是,一系列精心设计的激活和损失函数对于构建最佳深度学习架构是必要的。当面对抽象的计算和结果时,我们应该评估模型是如何处理信息流的。激活函数处理前向传播(信息的前向流动)以向前传递数据,而损失函数处理反向传播(信息的后向流动)以提高赋予数据片段重要性的权重。

这种循环的测试和检查过程与测试和重新定义模型功能的设计思维结合得很好。此外,理解每个激活和损失函数背后的特质和推理将使我们能够深入思考在我们蓬勃发展的世界中等待着的错综复杂的问题。

资源

1 B. Jason,训练深度学习神经网络时如何选择损失函数 (2020),机器学习掌握

[2] B. Jason,训练深度学习神经网络的损失和损失函数 (2019),机器学习掌握

3 S. Fawaz,《完整的神经网络训练营:理论、应用》第 2 节&第 3 节 (2021)

4 D. Yann,F. Angela,A. Michael 和 G. David,用门控卷积神经网络进行语言建模

[5] B. Avijeet,2022 年你应该知道的 10 大深度学习算法 (2021),SimpliLearn

[6] B. Pragati, 12 种类型的神经网络激活函数:如何选择? (2022),V7 实验室

[7] 设计思维,交互设计基础

用数据科学设计化学反应器

原文:https://towardsdatascience.com/designing-a-chemical-reactor-with-data-science-9e2c714d2475

Viktor Kiryanov 在 Unsplash 上的照片

最后一年项目。大学生和工作生活之间的最后障碍。作为一名化学工程专业的学生,我的期末项目需要我设计和优化一个化学反应器。

就我个人而言,我的目标不是获得最好的成绩,而是将它作为一个数据科学问题来处理,展示我在 3.5 年的(持续)数据科学之旅中学到的所有知识。我是这样做的。

反应堆设计仪表板——作者制作的动画,在 https://share.streamlit.io/reoneo97/reactor-sim/app.py公开发布

这个项目可以分为两个阶段:模拟和优化。在这篇博文中,我将谈论帮助我解决这个问题的不同数据科学技巧。

模拟

照片由 Unsplash 上的真空背景拍摄

像生活中的许多事情一样,反应堆的机制可以用几个方程精确地描述和预测。模拟程序的目标是能够根据参数预测(或者更准确地说,计算)反应堆的输出是什么样的。给定反应器的长度、直径和温度等参数,模拟必须计算反应产生的化学产物的量。

模拟用能量平衡偏微分方程

与数据科学中的回归问题不同,这个过程是完全确定的。也就是说,大多数描述反应堆的方程都是偏微分方程,求解起来非常困难,计算量也很大。

数据科学家的一项重要技能是将数学方程转化为代码的能力。给定教科书中的方程或研究论文,一个优秀的数据科学家应该能够完全从头开始实现这些想法(使用 Numpy ),或者与 PyTorch 等自动微分库一起实现。

能量平衡代码

为了这个项目,我不得不重新阅读关于热传递和反应动力学的旧教科书,并将这些冗长的方程转换成 Python 代码。作为一个额外的挑战,我试图只使用 Numpy 来求解这些方程,而不依赖于像 Scipy 这样的包来求解 PDEs。

可视化和仪表板

用于应用程序设计的可视化和仪表板包

数据科学家的另一项重要技能是可视化。熟悉可视化和绘图库使我能够创建一个仪表板来说明反应堆的不同条件。为此,我使用了 Streamlit 和 Plotly,它们都是构建交互式仪表盘的优秀软件包。

使用 Streamlit 滑块的模拟配置—图片由作者提供

Streamlit 通过易于编程的滑块来改变模拟中的不同参数,从而增加了交互性。它还允许使用时间滑块来可视化比二维更多的信息。另一方面,Plotly 帮助我只用几行代码就创建了令人惊叹的可视化效果。它还支持内置的“悬停”功能,以快速获取海量数据点中特定点的值。

编码

阿诺德·弗朗西斯卡在 Unsplash 上拍摄的照片

写好代码和组织好数据科学项目是一项最近才获得的技能。也就是说,我有,但这真的很重要。最初,组织好你的代码,而不是仅仅使用一个 Jupyter 笔记本可能会有点耗时,但从长远来看,它会带来巨大的回报。对于有许多不同的机器学习模型可以尝试的大型项目,拥有一个良好的代码结构可以避免一次又一次地重写相同的代码行。这也使得代码更容易理解,并且更容易在同一个项目中与其他人协作。

看看我关于这个话题的博客吧!

在我的 FYP 的上下文中,这意味着为反应器中许多不同的组件编写类和抽象。Reaction将是一个具有特定参数和函数的基类,然后我们可以在其上定义EquilibriumReaction,重用相同的方法。一个现实的反应堆RealPFR 只是一个更简单的理想化的IdealPFR的子类,它可以进一步被建模为从基类Reactor继承的类。

拥有中间类和基类帮助我更快地开发代码,构建在已经编写的代码之上,并且每当我想要尝试和实现不同的反应器设置时,不必从头开始。

最佳化

项目的第二阶段是优化。本质上,任务是根据某些度量标准选择最佳的反应器。

这是与数据科学的相似之处变得更加明显的地方。所有机器学习算法都可以从优化的角度来看,对数据科学家来说,很好地理解优化很重要。

当执行优化时,我们希望最大化或最小化某些特定目标。对于分类问题,这个度量通常是精度或召回率。由于这一指标很难显式优化,机器学习在一个独立的函数上执行优化,如负对数似然(NLL)。

然而,在这个特定的背景下,有两个优化目标:成本(C)和转换(X)。转化率是指转化为产物的反应物的量,而成本仅仅是整个反应器设置的成本。在这种情况下,我们希望最大化 X 并最小化 C,这与最大化-C 是相同的。

最大化目标

因为这两个目标同等重要,所以一个简单的方法是设计一个考虑这两项的损失函数(或效用函数)。转换的范围是[0,1],而成本的范围是≈ [10⁶,10⁹],因此我们可以使用缩放使它们具有相等的权重。

优化反应器的参数实际上类似于优化机器学习模型的超参数。两个函数都不是连续的,需要无梯度方法来优化它们。可以使用模拟退火或粒子群优化等常见的优化方法,但我决定使用贝叶斯优化,这是一种用于调整模型超参数的常见算法。

贝叶斯优化(使用bayes_opt库【1】)是一种概率优化技术。它依赖于创建一个高斯过程候选函数,并在获得更多信息时逐步更新该函数。在第一阶段,贝叶斯优化随机探索搜索空间,映射出目标函数的拓扑结构。随后,该算法将开始对很有可能找到最优值的多个点进行采样。本质上,该算法开始利用高确定性的区域来逐渐收敛到最优。

作者对温度变量图像的优化

从上面的图中,我们可以看到这个过程在起作用。模型在探索(蓝色)阶段开始随机采样点。优化(绿色)阶段的运动更加系统化,模型最终收敛到反应堆的最佳温度 410K 左右。在任何时候,贝叶斯优化将考虑整个样本空间,因此,不太可能收敛到局部最优。

最后的想法

弗拉德·巴加西安在 Unsplash 上拍摄的照片

作为一名化学工程专业的学生,进入数据科学领域是一段相当艰难的旅程。在 Udemy 上学习了无数个小时,在大学上了额外的数学课,并完成了 2 次数据科学实习后,我现在很兴奋地开始了我的数据科学之旅的下一个阶段。这个项目虽然侧重于化学工程,但让我展示了我的数据科学技能,并巩固了我对利用数据科学技能解决任何问题的多功能性和潜力的信念。

查看代码:

https://github.com/reoneo97/reactor-sim https://reoneo.medium.com/membership

参考文献

1 F. Nogueira,“贝叶斯优化:Python 的开源约束全局优化工具”。2014 — .【在线】可用:https://github.com/fmfn/BayesianOptimization

设计数据系统:复杂性和模块化设计

原文:https://towardsdatascience.com/designing-data-systems-complexity-modular-design-384b28fec672

数据科学家和工程师的设计思维方法。

Med Badr ChemmaouiUnsplash 上拍摄的照片

动机

从笔记本到创建在现实世界中工作的机器学习系统,意味着从编写简单的脚本、笔记本和在实验室环境中可视化数据转变环境。现在是考虑建立一个系统的时候了,好的系统具有关键特征,即弹性、性能和可靠性。通常,当我们设计系统时,由于在开发过程中采取的增量决策,复杂性的摩擦阻力开始变得明显。这些增量决策是几个约束条件的结果,例如期限、预算约束和开发团队的技术技能。

系统环境中的复杂性是指任何使理解系统变得困难,并且在以后的阶段修改系统变得困难的东西。随着系统的开发和扩展,增加的复杂性增加了以添加新功能、性能优化等形式进行更改的时间成本。仅举几个例子。因此,让我们在这篇文章中开始讨论,以更深入地理解什么是复杂性,以及它如何影响我们开发的系统。

复杂性

让我们正式思考复杂性。当我们开发系统时,为了对系统进行修改,我们需要采取一定数量的行动。这些动作有一个与之关联的时间值,即完成该动作所用的时间。因此,如果我们想用数学的方法来表达,我们可以把总复杂性看作是与每个动作相关的复杂性的总和a比如我们称之为c(a),与每个动作相关的时间为t(a)。因此总复杂度为SUM(c(a).t(a))。为了最小化复杂性,一个显而易见的方法是最小化t(a),并且在可能的情况下,以这样一种方式设计系统的单个组件,使得与那些组件相关联的动作具有更少的c(a)。复杂性的主要衍生因素是依赖性和模糊性。

依赖性是指系统各部分之间的相互依赖性。系统的一部分按照其规格运行的依赖性越多,该部分就会变得越复杂。晦涩是缺乏可视性,最终导致系统的复杂性。在团队环境中处理大规模系统时,晦涩是很常见的,随着代码库变得越来越老,越来越大,晦涩也越来越多。

为了更好地理解复杂性及其衍生因素,让我们通过观察复杂性的影响来进一步分解它,然后理解首先是什么导致了复杂性的产生。

复杂性的影响

随着系统复杂性的增加,一些事情被放大了:改变的难度,认知负荷的增加,以及未知的数量。

改变难度

让我们用一个简单的调度器的例子来理解,这个调度器为欺诈检测系统获取数据。假设您需要以相同的时间间隔从两个不同的财务来源获取数据,并合并这些数据源以生成最终的数据集。您以每天一次的频率编写两个解析器。对于您的 cron 作业脚本,您硬编码了频率值,因为您只编写了两个脚本,并且您可以很容易地对将来可能出现的任何变化进行修改(对吗?我们都去过…)!现在,假设您必须进行一项更改,将获取频率从每天一次更改为每小时一次,以便为您的系统提供更多实时数据。现在,您必须进入这两个脚本并进行更改。

现在,您的欺诈检测系统已经投入使用,并受到高级管理层的欢迎,他们会派销售团队去获得更多的数据合同,以纳入更多的数据,并增加最终数据集的广度。如果您获得了 10 个以上的数据源,那么每当获取频率发生配置更改时,您就必须在 12 个获取脚本中进行更改。

硬编码的 cron 作业脚本。(作者:尤弗伦德·吉尔)

现在,避免硬编码的一个简单方法是用变量fetchFreq改变频率的硬编码值,并从某个配置文件中修改该变量。现在新的建筑看起来像这样,

配置文件连接的 cron 作业脚本。(作者:尤弗伦德·吉尔)

有了这个架构,我们需要在配置文件中做一个改变,系统的其余部分将被更新。在这里,我们能够最小化从单个 cron 作业的各种freq变量副本到一个配置文件的获取调度的依赖性。这种方法通过将依赖性减少到仅仅一个文件来处理依赖性引起的复杂性。

对配置文件所做的更改更不容易出错,并且更省时。开发人员需要担心配置文件中的变量fetchFreq,而不是担心所有单独的脚本。这让我们讨论复杂性的下一个普遍影响。

认知负荷增加

认知负荷是工程师在做出改变之前需要了解的关于系统的信息量。具有大量依赖性的高度耦合的代码要求工程师在开始计划特定于变化的实现细节之前,理解其他代码的所有本质,以理解它们的变化的下游影响。简单来说,他们花更多的时间在与手头任务无关的细节上。

在任何真实世界的系统中,都会有依赖性,这是不可避免的。问题是这些依赖带来了多少复杂性,对维护系统的工程师的认知负荷有什么相对影响?

回到我们调度器的前一个例子,我们通过引入配置文件来分离获取脚本。现在从工程师的角度来看,他们只需要在修改之前关注配置文件中的代码。他们不需要学习我们 12 个脚本的任何实现细节。这减少了工程师的认知工作,从而减少了学习时间,提高了生产率。

未知数的数量

未知是您的变更在系统中引入的任何问题,只有当系统投入使用并且开始出现错误时,您才能意识到这些问题。现在让我们稍微扩展一下我们的调度程序。假设您的经理进来并注意到,在其中一个数据源中,我们需要根据频率对其中一个字段进行单位转换。Martin 目前是负责 cron 脚本的工程师,他加入了更改字段值的代码。现在你的系统看起来像这样,

添加了修改的 Cron 作业脚本。(作者:尤弗伦德·吉尔)

假设马丁找到了他的下一个任务,而你接替了他的角色。由于更改很小且很快,Martin 忘记记录更改。现在,您的工程经理进来将获取频率从每天一次增加到每小时一次,您继续修改配置文件。因为您不知道添加到第二个脚本中的代码,所以您肯定所有的东西都经过了测试,并且您的更改是可以进行的。只有在变更被合并到生产中之后,您的业务团队才会开始注意到他们的报告中突然出现的奇数,并且您将获得另一张票来修复这些变更。所以这些都是随着系统复杂性的增加而增加的未知因素。

未知的根本原因是复杂系统中的晦涩。现实世界的开发发生得很快,没有时间记录所有的变化,因为重点可能是发布一个特性而不是干净的代码。因此,在处理代码库和功能不断增长的实际系统时,晦涩是一个普遍的问题。减少晦涩依赖于各种因素,例如设计的简单和清晰,文档和沟通,等等。

复杂性是它现在和将来的所在。让我们尝试用模块化设计方法来降低这种复杂性!

标准设计

模块化设计是将系统分解成一组更小的组件的哲学,这些组件称为模块,可以重复使用,相对独立于其他部分,比原始系统更简单。让我们回到我们的欺诈检测系统来理解模块化设计方法。

欺诈检测系统有几个要求,即获取数据、数据清理和准备、数据建模、分析、日志记录和健康监控。如果系统以模块化方式设计,每个服务负责一项特定任务,则所有这些要求都可以满足。例如,数据获取可以是一个服务,数据建模可以是另一个服务,它们的实现完全相互独立。它是系统最高层的基本分解。这种方法可以从较高层到较低层迭代地继续。

让我们把重点放在数据获取服务上,并进一步分解它。该服务可以进一步分解为负责获取数据和调度服务的逻辑功能的子服务。我们现在有两个相对独立的模块。为了进一步分解,我们可以采用其中一个子服务,比如调度,并将其分解为单独的 cron 作业,就像我们前面看到的那样。每个 cron 作业都是相互独立的,在这个抽象层次上是一个独立的模块。

我希望您已经理解了这个想法的基本要点以及在不同抽象层次上创建模块的过程。现在让我们了解声音模块的主要特征。

连接

每个模块都有一个接口,该接口承载着关于模块的正式和非正式信息。任何模块的正式信息都由输入属性、输出值和任何与错误相关的警告组成。具体来说,如果我们在代码级别讨论,正式信息将包括你的函数签名或类签名。非正式信息是关于依赖关系和特定行为的,比如顺序调用模块的某些部分。

在设计一个接口时,关键的事情之一是提供足够的关于接口的信息,让用户知道它的主要功能和方法/服务。实现细节应该对用户隐藏。接口是为模块提供抽象。如果一个摘要给出了太多关于实现的细节,或者没有提供正确使用所需要的必要细节,那么它就会出错并变得复杂。

假设您正在创建一个处理大数据文件的框架,并创建一个读取文件的模块。一个好的界面必须为用户提供打开、阅读和关闭文件的功能。假设您的接口提供了以下方法,

def open_file(file)
def read_file(file)
def close_file(file)
def seek_file(file, seek_position)

上述方法提供了非常少的关于实现的信息,并且为用户提供了足够的信息来满足他们读取文件的需要。这四种方法允许用户打开和关闭文件,从开始到结束顺序读取文件,并使用seek_file方法随机读取文件。上述方法没有公开它们如何处理不同的文件类型、它们与底层文件系统的交互、键盘中断和崩溃管理。

履行

实现是指模块中隐藏在接口下的部分,负责完成模块提供的所有重要功能。

一个模块的良好实现试图将模块代码与其他系统模块隔离开来。例如,在我们的欺诈管理系统中,负责预处理数据的服务将实现代码,而不考虑为建模而实现的代码,反之亦然。系统的建模部分可能依赖于预处理服务。尽管如此,它只关心预处理提供的接口,而不关心预处理的实现细节。这种解耦在任何数据系统的模块化设计中是至关重要的,以减少依赖性。

接口宽度与实现深度的比率是良好抽象的关键。提供大界面但功能少的模块增加了复杂性,而具有深度功能的简单界面有助于降低复杂性。—斯坦福大学的约翰·奥斯特胡特

结尾注释

创建端到端的机器学习系统引入了复杂性,随着产品的增长而影响产品,并给负责产品的人员带来了巨大的负担。复杂性是系统中的依赖性和模糊性的结果,导致增加的变更难度、认知负荷和引入的未知。模块化设计原则试图通过将系统分解为具有连续抽象层次的子系统、服务和代码文件来降低复杂性。每个模块相对于实现中的其他模块是独立的,并为其用户提供了使用其中代码的接口。

我希望这个关于复杂性的讨论能让你了解你未来的项目在进展中会受到怎样的影响,以及用模块化哲学设计的战略方法会如何帮助你驾驭复杂性。我很乐意继续听到你关于复杂性和模块化设计的更多意见,所以请随意发表评论并继续讨论。

为可访问性设计电源 BI 报告

原文:https://towardsdatascience.com/designing-power-bi-reports-for-accessibility-70a152df7d77

设计 Power BI 报告是一回事,但设计可访问性需要完全不同的思维方式

作者图片

您可能知道,同一份报告在不同的设备上看起来可能完全不同。包含完全相同的数据点的 Power BI 报告,在桌面上看起来与在移动电话上看起来完全不同。

类似的情况适用于不同的人——你必须记住,你的用户可能有视觉、运动或认知障碍。因此,从有缺陷的用户的角度来看,同一个报告可能看起来不同。

为什么可访问性很重要?

如果你问自己:为什么可访问性很重要?让我给你看一个非常基本的例子。如果你是一个正常人,当我说正常时,我的意思是你没有任何视觉障碍,这就是你将如何看到这个漂亮的气球飞过绿色的田野:

作者图片

但是,如果你被诊断为“多盲症”,这是一种绿色盲障碍的医学名称,同样的图片看起来会非常不同,你不同意吗?

绿盲症

这同样适用于报告中使用的颜色。考虑到这一点,您应该始终将您的报告设计为尽可能多的用户可以访问。

Power BI 中的辅助功能

幸运的是,Power BI 提供了一整套内置的辅助功能。其中有些甚至不需要额外的配置,下面就简单介绍一下:

内置功能

  • 键盘导航 —所有 Power BI 视觉效果都是“键盘友好的”,您可以在它们之间导航。您也可以单击问号来访问最常用的键盘快捷键

作者图片

  • 屏幕阅读器 —一般来说,每个 Power BI 对象都兼容屏幕阅读器
  • 高对比度颜色 —如果您在 Windows 中设置高对比度模式,Power BI 将自动检测 Windows 中使用的主题,并将相同的设置应用于您的报告。要在 Power BI 服务中手动设置主题,当您处于报告的编辑模式时,选择视图 > 高对比度颜色,并选择您想要应用的主题

作者图片

  • 聚焦模式 —使用户能够在屏幕上占据更多空间
  • 显示数据表——使用快捷键 Alt Shift F11,用户可以从视觉上切换到数据的表格视图

可配置功能

除了这些不需要任何额外配置的内置功能,Power BI 还提供了一组开箱即用的功能,但它们需要由报告作者进行配置。让我们来看看所有这些特性:

  • Alt text —允许您在报表页面上添加可视元素的文本描述。这样,即使用户看不到视觉效果本身,他们也能理解视觉效果所提供的信息

作者图片

  • Tab 键顺序 —帮助平滑报表元素之间的键盘导航,匹配用户对相同元素的可视化处理方式。您可以启用视图选项卡下的选择窗格,并配置选项卡顺序:

作者图片

  • 标题和标签 对你的视觉元素提供清晰度是极其重要的。因此,避免使用首字母缩写词和其他缩写词,因为这可能会给用户造成混淆
  • 标记 是提高系列图像可读性的强大技术

作者图片

  • 最后,仔细选择 报告主题、颜色。不要忘记气球和绿色田野的照片,因为色觉缺陷的用户可能会对基于色盲不友好颜色的数据理解有问题。以下颜色组合对于色觉缺陷的用户来说尤其困难:
  • 绿色和红色
  • 绿色和棕色
  • 蓝色和紫色
  • 绿色和蓝色
  • 浅绿色和黄色
  • 蓝色和灰色
  • 绿色和灰色
  • 绿色和黑色

电源 BI 报告的可访问性清单

让我们快速重申一下设计具有可访问性的 Power BI 报告的最佳实践:

  • 确保报告元素之间的颜色对比度至少为 4.5:1
  • 尽量避免使用颜色作为传递信息的唯一方式。您可能想用文本或图标来补充颜色。
  • 始终使用清晰的报告元素文本描述。
  • 如果您在报告中使用非装饰性的视觉效果,请确保在其中添加替代文本
  • 为报告选择调色板时要小心,并确保色觉障碍的用户也能理解数据
  • 避免使用工具提示作为传递重要信息的方法。对于有运动障碍的用户或没有电脑鼠标的用户来说,很难使用它们

结论

设计报表是一回事,但是为可访问性而设计需要完全不同的思维方式。许多报表开发人员基于大多数没有视觉障碍或任何其他障碍的消费者来“校准”他们的解决方案和可视化。但是,无论何时创建整体解决方案,您都必须记住那些在使用数据时可能面临问题的用户。

请这样想:如果您在为移动设备设计报表时,需要相对于“常规”桌面体验调整您的方法,那么您也应该为具有不同需求的用户调整您的设计方法。

如果你想了解更多关于 Power BI 中的可访问性设计,我强烈推荐你阅读 Meagan Longoria 关于这个主题的文章。

感谢阅读!

为人员数据设计 XAI

原文:https://towardsdatascience.com/designing-xai-for-people-data-f41bc4cf1fc8

使用人员数据的三个表格桩

人-数据是敏感的,我们的算法应该公正对待数据集中涉及的人。在这篇文章中,我将深入一个实际的例子,同时强调三个必须具备的条件,让你的见解变得有价值和有效。在 Two Story 我们的工作中融入了所有这些实践。

处理人员数据的风险很高,我们在 Two Story 了解到这一点。照片由亚采克·迪拉格Unsplash 上拍摄

简介

随着人工智能的进步,社会要求算法处理数据的方式更加透明。当仔细检查时,一种特定类型的数据比其他数据更突出,这就是人员数据。我们负责高度监控这类数据和算法处理。有意或无意滥用人的数据会产生后果。例如,@Julien Lauret1强调了偏见是如何发生在任何人身上的(即使是亚马逊)。虽然这只是一个例子,但在处理人们的数据时,还有更多偏见的情况,所以我们必须深刻理解我们的数据/人工智能的含义。

免责声明:我坚信 XAI,所以这篇文章带有 XAI 式的偏见。

我花了好几年时间研究“人工智能”和数据科学,我从事的项目很少对现实世界产生影响,因为它们从未离开过实验室。但是,毕业后,我进入了“工业”具体来说,是一个几乎专门处理人事数据的职位。在本文中,我将通过一个实际例子来说明我是如何构建 XAI 来容纳这些敏感数据的。我将使用合成的 IBM 流失[2]数据集来展示影响结果的洞察力。

全局解释

点:给模特所学的大图

我开始走这条路,我们寻找不同的用例来应用数据科学工具。一个最明显的例子是根据心理测量数据预测员工的绩效/保留率。所以,我用我的数据科学工具来解决这个问题,我意识到它们是不够的。我的困惑矩阵看起来很棒;我的准确性是惊人的。但是,我的同事们并不在意。他们想明确地知道数据中的模式——关键特征是什么。这是个问题。我无法用大多数开源包中的基本分类算法给出他们想要的答案。所以,我回到绘图板,我有了一个灵感。他们在寻找一种工具来生成最重要的特征;这是一项功能选择任务。不幸的是,标准的特征选择工具不能提供所需的洞察力。以防你不知道,人们充满了不确定性。我是模糊逻辑3的忠实粉丝,我意识到人是模糊的。因此,模糊算法将是最合适的。有了这些信息,我学会了将人们与结果数据联系起来的隶属函数。例如,从 IBM 流失数据集获得的成员资格值如下:

图片由我来展示“甜蜜点”(绿色),表示某人将留在公司的位置。

为什么这很重要?嗯,模糊的输出是可以被人类理解的。如果这些特征是可预测的,最终用户可以做出数据驱动的决策,定制他们的策略以获得更好的现实结果。例如,离家的距离是一个突出的特征,因此来自该数据点的可操作的见解将使公司能够改进其对那些离家较远的人的保留策略。

了解模型的全局可以为最终用户提供信息,以便做出更好的现实决策(即使有些决策没有内置到 AI 中)。使用模型,人类可以应用上下文。例如,如果这家公司正在考虑一个新的位置,他们可以考虑这种类型的洞察力,以确保他们在某个地方建立他们的新网站。

本地解释

ppoint:给一个具体的例子以上下文

全局解释提供了模型所学内容的大画面。再深入一点,终端用户也想了解为什么一个人工智能会做出一个特定的预测。像 LIME 4这样的算法很好地解释了为什么一个人工智能会做出一个特定的决定,但是它们在处理“黑箱”时表现得更好。但是,所学的隶属函数也是可以解释的。我们可以显示“触发”了哪些规则,从而有效地回答了哪些特征对预测值产生了哪些影响。例如,我们学习了 IBM 数据集中一些不同特性的以下规则。

图片由我提供。突出显示数据集中由个人触发的规则(绿色表示好,红色表示有问题)

你可以想象,如果一个人明白“特征 3”是他们最有可能辞职的原因,这将如何为他提供如何留住人才的最佳视角。提供可操作的见解发生在多个层面。

准确(性)

要点:精准驱动 XAI 关联

如果你关注过我的任何一篇文章[5,6],我通常会把重点放在对算法的解释上。然而,如果模型没有学到任何预测性的东西,那么任何解释都没有意义。如果模型只有 10%的准确性,那么学习到的规则只在 10%的时间里适用。必须注意 XAI 和分类优化算法之间的典型权衡。对于 IBM 数据集,我已经能够找到一个模糊规则库,它能够以 70/30 的训练/测试分割预测 80%的准确率。然而,我发现了一些例子,用户可以预测高达 90%的准确率。因此,重要的是要注意,伟大的解释将以降低准确性为代价。

包扎

总之,处理人们的数据是一个敏感的领域,典型的算法并不能解决这个问题,因为它们并没有针对处理这些数据进行优化。在尝试提供以人为本的可行见解时,我最大的收获是:

  1. 解释全球模式允许人类在其他情况下应用这些模式
  2. 局部解释可以为数据集中的个体提供价值
  3. 准确性为解释注入信心

然而,我们知道得比这更好,我们通过创建全球和本地解释以及高度精确的模型来保持更高的标准。

参考

[1]https://becoming human . ai/amazons-sex ist-ai-recruiting-tool-how-do-it-go-so-wrong-e3d 14816 d98e

[2]https://www . ka ggle . com/drghulamhussain/IBM-HR-attachment-analytics/data

[3]https://medium . com/forward-data-science/fuzzy-systems-life-between-the-1s-and-0s-949 de 445 f 58 e

https://homes.cs.washington.edu/~marcotcr/blog/lime/

[5]https://medium . com/forward-data-science/the-most-the-important-types-of-xai-72 F5 be B9 e 77 e

[6]https://medium . com/forward-data-science/are-we-thinking-about-explability-backwards-c9a 719 CB 1250

B ryce Murray 博士是 Two Story 的应用人工智能科学家,他在那里构建算法来实现新兴技术和规模人员分析。他在可解释的人工智能数据融合方面的工作已经发表在 IEEE 计算智能新兴主题汇刊和其他地方。布莱斯的专业领域包括数据融合、深度学习、机器学习和模糊逻辑。他在密苏里大学获得了电子和计算机工程博士学位。

设计数据库模式

原文:https://towardsdatascience.com/designing-your-database-schema-best-practices-31843dc78a8d

决定使用星型模式还是雪花型模式适合您,规范化与反规范化如何影响您的分析,以及数据库模式设计的未来是什么样子

良好的数据库模式设计对于任何利用关系数据库管理系统的公司都是至关重要的。如果现在不花时间去设计一个逻辑和直观的数据库模式,以后就会花时间去弄清楚表之间是如何关联的,以及如何在表之间执行连接。什么是数据库模式?一个 模式 是数据库中包含的所有对象(表、视图、列、键等)的快照。)和他们的关系。这是数据库结构的鸟瞰图。使用实体关系图 (ERD)来表示模式,这是一个描述实体在数据库系统中如何相关的流程图,其中矩形表示实体(例如表)、椭圆形、属性(例如列)、菱形、关系(例如一对一、一对多、多对一和多对多)。有关组成 ERD 的元素的更多信息,请查看 Lucidchart 的这篇文章。

实体关系图示例(维基共享)

在这里,我将讨论不同的模式模型,规范化与反规范化,以及模式设计的未来。我假设该模式是在企业数据仓库(EDW)中实现的,并且数据库本质上是关系型的。我们开始吧!▶️

模式模型

关系数据库系统中最常见的模式模型是星型模式和雪花型模式。

星形模式⭐️

星型模式是最简单也是最常用的模式模型。历史上,它是由 Ralph Kimball 开发的,并在的数据仓库工具包 (1996 年)中引入。一个星型模式由一个中心的事实表表示,它可以被周围的维度表连接。星型模式模型的维度表是非规范化的,需要更少的连接,从而简化了查询逻辑并提高了查询性能。

自己的图表(使用 Lucidchart 创建)

SELECT 
    loc.region, 
    loc.country,
    vir.family as virus_subfamily_name,
    vir.infect_rate,
    fact.death_cnt
FROM fact_pandemic AS fact
    LEFT JOIN dim_location AS loc
        ON fact.location_id = loc.id
    LEFT JOIN dim_virus AS vir
        ON fact.virus_id = vir.id
    LEFT JOIN dim_dates AS d
        ON fact.dates_id = d.id
WHERE d.year = 2020

雪花(" 3NF ")模式❄️

另一方面,雪花模式(或“第三范式”模式)被认为是星型模式的前身。数据仓库创建者比尔·恩门在 20 世纪 90 年代早期引入了雪花模式模型。雪花模式的设计类似于星型模式,除了维度表是完全规范化的。规范化有很多好处:它有助于减少数据中的重复项,降低存储空间的使用量(通常,维度表没有事实数据表大),以及避免在多个地方执行数据删除或更新命令。但是,由于要执行更多的连接,这确实会降低查询性能。

自己的图表(使用 Lucidchart 创建)

从下面的 SELECT 语句中可以看出,还有更多的连接要执行!

SELECT 
    r.region, 
    c.country,
    fam.name AS virus_subfamily_name,
    t.infect_rate, 
    fact.death_cnt
FROM fact_pandemic AS fact
    LEFT JOIN dim_country AS c
        ON fact.location_id = c.id
    LEFT JOIN dim_region AS r
        ON r.id = c.region_id
    LEFT JOIN dim_virus AS vir
        ON fact.virus_id = vir.id
    LEFT JOIN dim_virus_family AS fam
        ON fam.id = vir.family_id 
    LEFT JOIN dim_transmission t
        ON vir.type_id = t.id 
    LEFT JOIN dim_dates AS d
        ON fact.dates_id = d.id
    LEFT JOIN dim_year AS y
        ON d.year_id = y.id
WHERE y.year = 2020

星系模式(事实星座模式)🌌

星系模式(也称为事实星座模式)是星形和雪花模式模型的组合。它是完全规范化的,但涉及更多的设计复杂性,因为可能有多个事实表,并且在维度和事实表之间可能存在多个依赖关系。其中的一些优势是,您可以期待更高的数据质量和准确性,这可以增强您的报告。但是,您可能会注意到报表的刷新按钮旋转的时间有点长,因为 galaxy 模式很复杂,可能会影响查询的性能。我推荐阅读这篇文章来学习更多关于事实星座模式的知识。

数据仓库 2.0🔐

Data Vault 2.0 由 Dan Linstedt 在 2000 年创建的 Data Vault 演变而来。根据其设计者的说法,data vault 是一种“混合方法,包含第三范式(3NF)和星型模式之间的最佳组合”,它提供了一个灵活的框架来扩展和适应 EDW(参见 Dan Linstedt 的Super Charge Your Data Warehouse)。该模型是围绕三件事构建的:枢纽、卫星和链接。

自己的图表(受这篇文章的启发)

Hubs 是存储主键的表,主键唯一地标识一个业务元素。其他信息包括散列键(对在 Hadoop 系统上运行模型有用)、数据源和加载时间。

卫星是包含业务对象属性的表格。它们存储可以在集线器或链接表中引用的外键,以及以下信息:

  • 父哈希键(集线器中哈希键的外键)
  • 加载开始和结束日期(在卫星中,记录历史变化)
  • 数据源
  • 业务对象的任何相关维度

链接通过集线器中定义的业务键设置 2 个集线器之间的关系。链接表包含:

  • 一个散列键,其作用类似于主键,以散列格式唯一标识 2 个集线器之间的关系
  • 引用集线器中主哈希键的外部哈希键
  • 引用中心中主要业务键的外部业务键
  • 装载日期
  • 数据源

data vault 模型具有适应性(如果添加或删除列、更改数据类型或更新记录,则需要执行相对较少的手动操作)和可审计性(数据源和日期记录允许跟踪数据)。然而,它仍然需要被转换成维度模型,以便可用于商业智能目的。出于这个原因,您可能希望以数据集市的形式添加一个维度层,以支持 BI 分析师。维度表将来源于集线器,而事实表将来源于链路表以及卫星。

规范化与反规范化

我之前讲过规格化和反规格化。但是这些是什么意思呢?

正常化

规范化是通过将较大的表分解成较小的表来减少数据冗余的过程。通过避免重复行,规范化允许更多的存储空间,从而提高数据库系统的性能。通过消除重复行,数据变得干净而有条理。然而,你可能会给你的分析带来压力。规范化数据库需要更多的表间连接,这会影响查询性能。想想 Tableau、Looker、PowerBI 和 SSRS 仪表板:如果您有一个支持报表的查询,并且它正在从一个规范化的数据库中提取数据,那么报表页面可能需要花一些时间来加载。使用非规范化的表可能是提高性能的更好选择。

反规格化

另一方面,反规范化指的是将表组合在一起的过程。扁平化或反规范化的表是同义词。联接通常内置于扁平视图中,从而减少了检索数据所需的联接。您将看到仪表板和其他报告加载速度加快。但是,使用非规范化表的缺点是,现在数据集中有重复的行,这可能需要额外的数据争论,然后才能生成清晰准确的最终报告进行分发。

模式设计的未来

人工智能驱动的模式设计

这些天,人工智能和机器学习(AI/ML)成了热门话题。你不能一天不听到关于人工智能和它改变世界的方式的消息。机器学习在数据库模式设计中也发挥着作用。今天,设计数据库模式的过程是手工的和劳动密集型的。然而,数据库学教授 Andy Pavlo 告诉我们,从 RDBMS 的早期开始,人们就考虑开发一个自主的、“自适应的”数据库系统,包括自动化模式设计。

一个最初的项目着眼于自动数据库模式设计: AutoMatch 。它解决了“在两个语义相关的数据库模式的属性之间寻找映射”的问题如果添加了一个新的数据库元素会怎样——如何将它正确地映射到数据库中的其他元素?通过使用特征选择和对概率结果进行排序,Automatch 将为每个发现分配一个排序的预测值,对两个元素相互关联的可能性进行评分。虽然机器的预测需要验证,但像 Automatch 这样的项目是简化数据库设计操作的有力例子。

除了简化数据库模式设计流程之外,拥有自动化 ERD 设计的工具还可以为企业节省大量成本和时间……只要预测准确率高。一些公司开始提供这样的服务,比如 dbdiagram.io 。它提供了一个使用 DSL 代码自动设计数据库图表的工具。然而,数据库元素之间的链接仍然是手工完成的,可能还需要几年时间,我们才能看到完全自动化实体关系图设计的功能性工具,包括新元素到现有数据库元素的映射。

敏捷方法

尽管公司希望预测他们 10 年后的数据需求,但预测未来并不总是可能的。这就是为什么在创建或重塑数据库模式设计时采用敏捷方法很重要。随着公司越来越多地从本地服务器迁移到云,迁移过程是思考新的创新方法以提高 EDW 的敏捷性、适应性和可扩展性的时候了。Data vault 2.0 提供了一个良好的基础。展望未来,下一次迭代将不得不关注于构建敏捷模式模型,同时提供高度优化和高性能的功能来执行分析操作。

结论

最终,最佳数据库模式设计将取决于公司收集的数据类型、组织的数据成熟度及其分析目标。在本文中,我介绍了不同的模式模型、规范化和反规范化之间的差异,以及模式设计的未来,特别是使用机器学习来自动化设计过程。希望您能够更好地设计下一个数据库模式模型!

这篇文章最后编辑于 2022 年 6 月 12 日。这里表达的观点仅属于我自己,并不代表我的雇主的观点。

尽管取得了巨大成就,大型语言模型仍然没有对语言学做出贡献

原文:https://towardsdatascience.com/despite-their-feats-large-language-models-still-havent-contributed-to-linguistics-657bea43a8a3

乔姆斯基的语言学和语言学硕士观述评

照片由 DeepMindUnsplash 上拍摄

在你拿着干草叉和火把来找我讨论这个有争议的话题之前,请听我说完。在过去的几年里,我们已经看到了关于大型语言模型(BERT、GPT-3、LaMDA 等)的标题和例子。)可以做的事情—从情感分类、文本生成、问题回答等任务中的能力爆炸。

本文并没有质疑大型语言模型(LLM)在过去五年中取得的工程进展。更确切地说,这是一个关于语言学硕士对语言学科学贡献的评论。我将主要讨论诺姆·乔姆斯基教授对语言学的观点和看法,以及最近的法学硕士;我将引用以下来源:

让我进一步指出,这并不意味着贬低工程的重要性,我对乔姆斯基的理论和观点的解释可能不是最好的——任何错误的论点都可能是由于我平庸的解释。我强烈建议你去看他的采访(也看看他的书)!

目录

  • 理科 vs 工科
  • 乔姆斯基的 3 个模型
  • 新的法律硕士真的“理解”语言吗?
  • 为什么更多的计算对相同的范例没有帮助
  • 决定论和自由意志问题
  • 从这里到哪里?
  • 这对我们意味着什么?

对于那些不熟悉…

大型语言模型

语言模型是概率模型,试图映射单词序列(短语、句子等)的概率。)出现(即一个句子出现的可能性有多大)。他们在一组文本上接受训练,并从中得出概率分布。LLM 和普通语言模型之间的关键区别在于,LLM 是在数量大得多的文本上训练的,计算量也是指数级的。

在涉及文本生成的任务中(例如:摘要、问题回答、提示完成),LLM 在尝试生成文本时会使用条件概率。换句话说,当决定挑选下一个单词时,LLMs 将查看序列中的前一个单词,并基于此选择最有可能匹配的单词。请参见下面的示例:

基于上下文的下一个单词的概率示例(图片由作者提供)

想要一个好的、高水平的解释,请听听斯坦福大学的克里斯托弗·曼宁博士的演讲。

诺姆·乔姆斯基教授

乔姆斯基也许是上个世纪最著名的语言学教授。自 20 世纪 50 年代以来,他用他的普遍语法的概念和想法以及他对我们如何学习的传统观念的挑战,改变了语言学和认知科学的领域——争论有多少知识和行为是大脑/大脑与生俱来的。

至少可以说,他在麻省理工学院的大量工作很有影响力;他的谈话和采访提供了深刻的见解。在研究自然语言处理的现状与语言学的关系时,提到他是再合适不过了。

科学 vs 工程

在“机器学习街谈”采访的开头,乔姆斯基明确区分了科学和工程:

  • 科学需要质疑和 理解 为什么 事物是自然的,为什么它们不以其他方式发生——它寻求 解释 我们在现实世界中目睹的潜在现象。
  • 工程学是应用我们从科学中所知道的来解决问题。

然后他提出了一些来自自然科学的类比:

  • 如果一名研究人员看到一个物体落在窗外,简单地记录事件并回放视频就能解释重力是如何工作的吗?
  • 如果一名研究人员看到什么东西着火了,记录下来并回放视频会解释燃烧反应是如何进行的吗?

人们可以模仿上面的例子,但是仅仅模仿无助于我们理解 为什么 这些现象正在发生,以及 为什么 别的什么(在这些条件下)。

乔姆斯基接着指出,语言学硕士(像 GPT-3)没有帮助我们理解语言学背后的科学——他们没有解释语言/语法是如何或为什么以这种方式运作的。相反,因为这些 LLM 以概率的方式运行,它们只是尽最大努力模仿一个人在给定的上下文中会说/写什么。此外,由于 LLM 的概率性质,他们也有机会产生不合语法的句子。

最终,LLM 遵循一种“一切皆有可能”的方法——它们有时会产生无意义的东西,并且它们无法映射一种语言中所有可能的正确句子。在自然科学中,当我们有一个定理/定律(例如:牛顿万有引力定律、燃烧理论等。),它需要解释和包含所有可能的情况。

  • 牛顿引力定律适用于任何 2 个有质量的物体,具有普适性。
  • 燃烧理论告诉我们,为了发生燃烧反应,氧气(更具体地说,是一种氧化剂)必须存在。

每个定律/理论的公式(在上面的链接中)在所有情况下都是一致的。

一种语言可以产生无限多的句子。LLM 可能擅长产生最可能的句子,但这并不意味着它有一个公式或定理可以表示所有可能的语法句子,同时排除不可能的(不合语法的)句子。我们看到自然科学在这方面有所不同,如上所示。

乔姆斯基的三个模型

为了更好地理解乔姆斯基的观点,让我们绕个弯子,看看他在《句法结构》(乔姆斯基,1957)一书中对语言结构模型的研究。

在这本书的核心,乔姆斯基说:“语言 L 的一个语法本质上是语言 L 的一个理论。任何科学理论都是基于有限数量的观察,并且它寻求联系观察到的现象,并通过根据假设的构造(例如在物理学中)“质量”和“电子”)来构建一般规律来预测新的现象。”(乔姆斯基,1957 年,第 49 页)。

这意味着,一种语言的正确的“T20”语法(一种理论/公式)将允许一个人从中推导出所有语法正确的句子,同时不会产生语法错误的句子。

  • 打个比方,在数学中,我们有一个简单的公式,它允许我们映射从 1 到 n 的所有整数的和:

从 1 到 n 的所有整数的总和(图片由作者提供)

上面的公式适用于所有的 n 值,同样,一个合适的“语法”需要适用于一种语言的所有语法句子。

乔姆斯基涵盖了以下三种语言结构模型:

有限状态

有限状态语法使用有限状态来表示句子。换句话说,它从一个词(某个状态)开始,从那里开始,有有限数量的下一个状态。

图 1:产生两个句子的有限状态语法(图片由作者提供,灵感来自乔姆斯基(1957))

图 1 显示了一个简单的有限状态语法,灵感来自书中的一个例子(Chomsky,1957,第 19 页)。我们可以看到,所有的句子都以单词“the”开头,只有两种可能的结果(“孩子跑了。”,“孩子们跑了。”).这种语法看起来非常类似于当前 LLM 的操作方式(,它来自 20 世纪 50 年代!),但是没有考虑概率。我们在这个模型中有更多的控制权,因为我们可以显式地排除会导致不合语法的句子的状态(而 LLM 有时仍然可以产生它们)。

我们显然想要比图 1 中的语法更复杂、更广泛的东西,但即使这样,也不能产生英语中所有符合语法的句子,因为英语(像许多语言一样)不是有限状态语言!这是因为句子的语素结构不能以有限状态格式映射。我们有后缀、前缀、单词的不同形式/时态等。这取决于句子及其结构。正因为如此,有限状态语法对于语言来说不是合适的语法。

短语结构

短语结构语法以不同的方式处理句子映射。它获取一个句子,将其分解成短语,更具体地说是一个名词短语(NP)和一个动词短语(VP),然后进一步将每个短语分解成其部分(词类,如冠词、名词、动词等)。).

图 2:短语结构语法的一个例子(图片由作者提供,灵感来自乔姆斯基(1957))

图 2 显示了一个短语结构语法的例子,这个例子受到了书中(乔姆斯基,1957 年,第 27 页)产生句子“男孩跑回家”的启发这比有限状态语法更强大,因为它不局限于以严格的线性方式(从左到右,一次一个单词)在句子中生成单词。

然而,这种语言结构也是不完美的,因为它不能应用于某些自然语言(因此,不能产生所有的语法句子)。乔姆斯基提出了下面的两个句子的例子(乔姆斯基,1957 年,第 36 页):

  • “这部电影的场景是在芝加哥”
  • “我写的那个场景是在芝加哥”

这些简单的句子可以用短语结构语法产生,但是如果我们试图用相同的范例将它们组合成一个更复杂的句子,我们最终会得到不合语法的句子:

  • “我写的这部电影的场景是在芝加哥”

这是一个有点棘手的概念,但它的缺陷最好通过例子来展示,就像上面的例子。

转换结构

转换结构语法采用短语结构范式,但结合了删除、插入和移动等转换以及形态音位规则。al)。所有这些转换都可以应用于短语结构语法中呈现的短语,使得转换结构语法更加强大。

在这种模式下,短语结构部分中的示例 2 句子可以组合成如下形式:

  • “我在电影中写的场景是在芝加哥”

在采访中,乔姆斯基说第三种语法开始变得有意义,因为它开始告诉我们一些关于语言的事情,而不是像其他两种那样简单地“描述”它。我个人不确定这个理论的缺点,但它看起来需要很多特定的转换规则才能工作,这使得它与自然科学中的普通理论相比不那么优雅。在映射所有可能的语法句子方面,它无疑显得更加灵活和通融。

新的 LLM 真的“理解”语言吗?

关于 LLMs 能力的新闻循环中充斥着令人不快的标题,这可能会给你一种印象,即它们“理解”人类语言。已经到了一些人声称像 LaMDA 这样的车型已经实现了感知的地步。**

我认为有很多例子可以证明这种观点是错误的,比如 GPT-3 在一些推理任务中无法产生可接受的输出。以下面的任务为例:

  • 输入:“你正在举行一个小型晚宴。你想在客厅准备晚餐。餐桌比门口宽,所以要把它搬进客厅,你必须。”
  • 输出:"拆下门。你有一个台锯,所以你把门切成两半,去掉上半部分。

GPT-3 不仅不能解决让桌子通过门口的任务,还留给你一扇坏门!

还有一个有趣的例子,人工智能研究员贾内尔·谢恩向 GPT-3 展示了当被问及作为一只松鼠是什么感觉时的反应。

从本质上讲,LLM 非常擅长在正确的环境下模仿人类语言(他们知道如何以正确的方式回应)。此外,他们仍然可以通过产生语法错误的句子来违反语言原则(他们也可以产生无意义的句子)。

即使我们获得了更精确的 LLM,这也仅仅意味着产生“不可接受”输出的边缘情况会更少。假设这些模型满足 99.999999%的用例,以至于我们很难找到任何提示来混淆它们——它们仍然以概率的方式运行。

为什么更多的计算无助于相同的范例

在采访中,乔姆斯基坚持认为,即使我们增加更多的计算来训练更大、更复杂的模型,最终,我们仍然不会有什么东西来产生合适的语言语法/理论,正如我们之前所描述的那样。同样,概率模型的本质阻止了这一点。他们将能够产生更多可接受的输出,在正确的场景中模仿正确的反应,但这并不意味着他们能够描绘出每一个可能的语法正确的句子。

区分语法正确的句子和有意义的句子是很重要的。一个句子可能是无意义的,但语法上是正确的。以乔姆斯基的经典例子为例:

  • "无色的绿色想法疯狂地沉睡."(乔姆斯基,1957 年,第 15 页)

这句话符合语言的原则,但绝对没有意义。而且因为没有意义,所以发生的概率很低。鉴于 LLM 的性质,他们不会产生这样的句子,因为它们不太可能发生。有无限多的语法正确,但同时又无意义的句子不能被 LLM 映射,不管它有多强大。

决定论和自由意志问题

一些人认为,人类在给定的上下文中说出/写出最可能的回答,并暗示如果 LLM 真的擅长选择可能的回答(满足 99.999999%的用例),那么他们将“理解”语言。这开始分支到人类是否以决定论的方式运作,以及我们是否有自由意志的争论。

我喜欢乔姆斯基的观点,那些认为人类以决定论的方式运作的人通过这样做证明了自由意志的存在。否则,他们为什么要被迫这样做?显而易见,人类不仅仅使用光束搜索来思考、说话和写作。

从这里去哪里?

你们中的一些人可能会想,“这个乔姆斯基只是一个黛比·唐纳”。嗯,我们也可以看看深度学习领域的其他杰出人物,比如 Yann LeCun。LeCun 已经公开表示,为了在设计“理解”系统方面取得下一个飞跃,我们需要“放弃概率模型”。

他提倡“基于能量的模型”,这种模型来源于统计物理学,可以克服概率模型的一些缺点。他还提出了一个新的“世界模型”架构,这可能有助于系统理解我们所看到的世界并与之互动。

我不知道这些新想法会有多有效,而且 LeCun 自己也说过这可能不是正确的方法,但最终,我们需要放弃这些本质概率方法

这对我们意味着什么?

可能没什么(有点虎头蛇尾,我知道)。作为数据科学家/机器学习工程师/无论你有什么头衔,在这个领域,我们只关心提供商业价值。如果 LLMs 可以作为一项满足我们需求的工程壮举,即使它们实际上并不“理解”,也没有关系。事实上,我认为这让我们的事情变得更容易,因为我们不必担心这些系统会像人类一样理解/变得“有知觉”并开始机器人起义。我们可以放心地知道,这些 LLM,不管它们有多好,本质上都将是很好的模仿工具。

这与我们过去几年在 LLM 崛起的过程中所看到的观点完全不同。我发现乔姆斯基的评论在 LLMs 收到的广泛宣传中非常发人深省,并作为评估我们在哪里的一个很好的思想练习!

作为一个喜欢和 LLM 一起工作的人,我认为这种批评是必要的,以便在 NLP 中开发下一个突破,而不是陷入困境。

参考

1 N .乔姆斯基,句法结构* (1957),马蒂诺*

[2]k·达格尔,t·斯卡夫和 w·萨巴, #78 —诺姆·乔姆斯基教授(特别版)【视频】 (2022),机器学习街谈

3 B. Duiganan,E. Hamp,J. Lyons 和 P. Ivi,乔姆斯基语法 (2012),大英百科全书

4 T. Ray, Meta 的人工智能杰出人物 LeCun 探索深度学习的能量前沿 (2022),ZDNet

专业人士使用的详细仪表板设计指南

原文:https://towardsdatascience.com/detailed-dashboard-design-guidelines-used-by-professionals-c4c612b6d92

设计仪表板的技巧

Unsplash 上由 Carlos Muza 拍摄的照片

数据分析师通过获取原始数据并将其转化为数据驱动的可视化数据来赋予数据意义,从而帮助企业发展。所有这些看待事物的方式只有在布局和图表正确的情况下才有帮助。一致的布局和图表设计有助于确保外观上的小差异看起来不像大差异,并改变受众对数据的理解方式。

本文的目标是描述、定义并举例说明我公司的数据分析师在谈论数据时是如何使用样式元素的。这将确保所有图表部分看起来都一样,并且当团队成员必须决定如何正确和一致地使用图表部分时,这将为他们节省时间。

布图设计

组织设计整个仪表盘

在将数据放在仪表板上之前,您应该考虑如何组织数据,以便最终用户可以轻松理解。除了显示事实,仪表板还应该考虑信息的叙述性流动。

  • 将最重要的信息放在故事的开头,然后是支持信息和细节。
  • 仅呈现手头任务所需的最少数据。
  • 建议将标题放在顶部,过滤器面板放在左侧,高级指标放在顶部,可视化放在内容体上。
  • 重要的指标应该在顶部和左侧,当你移动到右侧时,你会发现更少的重要指标。

报告的布局设计(图片由作者提供)

  • 永远不要忽视停车标志的颜色。红色表示警告和错误,绿色表示成功或完成。
  • 保留特定的色调,比如你的组织的主色(对我来说,是橙色),来表示用户必须执行的活动。

图表设计

关于如何选择和设计每个图表的建议

Alberto Cairo(视觉和数据记者)说“图表不仅仅是插图,也不仅仅是图画,它们承载着意义”。

选择正确的图表并确保它包含所有重要信息是非常重要的。因为图表不是数据;这是对数据的看法。

主旨 : 去掉噪音

数据可视化中的“更少,但更好”意味着去掉不必要的东西,而只保留、改进和突出支持或显示我们试图用数据表达的东西。

我们经常犯这样的错误,向数据可视化添加太多不必要的东西。

最大化数据:油墨比

数据:墨水比率是用于显示数据信息的墨水(即像素)的比例。换句话说,去掉图形中不添加新信息的部分,即使用较少的墨水,会使图形更有效、更有吸引力,对观众来说也更重要。

举个例子,

最大化数据:油墨比之前的条形图(图片由作者提供)

使用上面的图表作为指导,你不需要一个灰色的背景,网格,图例,或者条形阴影和不同的颜色。X 标题和 Y 标题都没有添加任何内容,因为我们可以将它们放在图表的标题中。添加数据点比添加 y 轴更好。在最大化数据:油墨比之后,

最大化数据:油墨比例后的条形图(图片由作者提供)

减少杂乱的步骤

极少使用网格线

使用尽可能少的网格线和刻度,并且只查看数据的范围和上下文。默认情况下,不应有主要或次要网格线。如果网格线对于阅读数据很重要,那么在能够看到网格线的情况下,网格线要尽可能的轻。

移除边框

删除图表周围的边框,以便有更多的空间。

最小化标签

您应该尽可能不引人注目地标记您的轴,以留出空间来强调轴上或数据中的关键点。在所有图表中统一标注轴。避免使用不常见的缩写。

尽量减少图例的使用

应该尽可能使用标签而不是图例来帮助用户理解数据可视化。

坐标轴样式格式

轴线和刻度线不应该在图表上占据太多的空间,但是它们应该易于阅读。使用柔和的颜色、字体和线条,不要太粗。只有在没有其他视觉线索来帮助用户理解其含义时,才需要使用轴标题。

去掉不必要的装饰和效果。

删除表单周围任何不必要的关键线条、阴影和斜角效果。

不要试图展示太多。

如果需要显示大量数据,避免将一个图表制作得过于复杂;相反,可以考虑使用几个更简单的图表。

尽量减少颜色的使用

从数据中去掉颜色编码,这些数据只是为了给出上下文或进行比较。为上下文信息使用更亮/更细的灰色线条,便于以后使用颜色突出显示。

突出显示数据的步骤

焦点

关注范围,使数据尽可能清晰。除非它不会带走重要的东西。

使用颜色/线条粗细。

让重要的信息突出出来,给它一个一致的颜色,并使线条变粗。

直接标注

标注能立刻引起最终用户注意的重要信息。

突出显示重要的阈值和上下文范围

您可以通过添加强网格线或更改颜色来显示阈值周围的数据。例如,如果跨越阈值是一个指标,则选择红色。

添加图表的标题。

这将有助于你传达你的信息。保留一个标题,告诉他们将在图表中看到什么。

改进演示文稿布局的步骤

你对图表的了解

条形图:条形图用于显示数字数据,如计数或百分比,其中较大的条形表示较大的数字或较大的百分比。

条形图(图片由作者提供)

时使用条形图

  • 比较不同观察值的数值。即年龄组、产品类别、级别
  • 当你想显示相对数量时。比如哪个类别是最高或最常见的,以及其他组与其他组相比如何

专业人员使用条形图所遵循的标准

  • 避免使用三维条形图
  • y 轴应以 0 开始。但是在某些情况下,比如发现小的变化,如果 y 轴不是从零开始也没关系。

零值基线(图片由作者提供)

  • 条的宽度应该是条间距宽度的大约两倍。

带宽度说明的条形图(图片由作者提供)

  • 如果所有的条测量相同的变量,使他们都有相同的颜色。颜色不同与事实无关。也就是说,在我们的例子中,我们有每个地区的销售额,因为销售额只是一个度量,如果每个条形是不同的颜色,最终用户会感到困惑。
  • 如果有十个以上的类别,使用水平网格线和一个 y 轴;否则,直接识别数据点。

折线图:折线图是一种图表,用于显示随时间变化的信息。折线图是通过绘制一系列点并在它们之间画一条直线来制作的。

折线图(图片由作者提供)

时使用折线图

  • 显示一个变量随时间的变化。所以,当你想展示某样东西的价值如何随时间而变化,或者几样东西的价值如何随时间而变化。
  • 如果多个变量具有相同的刻度,您可以用多条线显示多个变量。

专业人员使用折线图遵循的标准

  • 尝试将单个图表中的行数限制在三到四行。更多并不总是更好。在同一个图表上放太多的线会让人难以理解,也违背了初衷。
  • 图例应横跨图表的顶部,其排列应与图表的排列相对应。
  • 避免使用双轴。双轴图表具有任意的刻度,这可能会(有意地)误导读者两个数据系列之间的关系。
  • 当要绘制的点很少时,考虑显示所有的数据标记,而不仅仅是线条。如果显示点会妨碍图表的可解释性,另一种方法是在线中加入一个间隙来指示缺少值的位置。

带数据标记的折线图(图片由作者提供)

饼图:饼图是数据的圆形图形表示,它被分段以展示数字比例。

饼图(图片由作者提供)

使用饼状图时

  • 表示整体的某一部分不相称地小或大。
  • 如果您希望读者对数据中部分与整体的关系有一个总体感觉,那么比较切片的具体大小就不那么重要了。

专业人士使用饼状图所遵循的标准

  • 使用饼图有几个缺点,例如占用更多的空间,如果有许多类别,就很难阅读,所以总是选择一个替代方案,如表格或条形图。
  • 如果你必须使用饼图,保持不超过五组。
  • 利用适当的配色方案突出关键信息。
  • 考虑标记图表外的区域。

选择正确图表的备忘单

备忘单(作者图片)

结论

本文讨论了数据分析师在为仪表板制作布局和图表时应该遵循的一些规则。我认为,如果我们遵循这些规则,我们可以制作出不仅好看而且有助于统一整个组织的设计过程的仪表板。我希望你喜欢这篇文章,并希望它对你的工作有所帮助。如果你认为我错过了一个标准的定义,请留下评论。

形象学分

所有图片,除非特别注明,均为作者所有。

Seaborn 的 Violinplot 和 Relplot 的细节

原文:https://towardsdatascience.com/details-of-violinplot-and-relplot-in-seaborn-30c63de23a15

Tetiana SHYSHKINA 在 Unsplash 上拍摄的照片

充分发挥潜力在 Seaborn 使用小提琴和 Relplots

Python 的 Seaborn 库是一个非常流行的可视化库。它建立在 Matplotlib 之上,包含了很多内置样式的高级剧情。我有一篇关于 Seaborn 的文章,涵盖了最受欢迎的情节(本文末尾提供了链接)。我决定对一些情节做详细的教程。

在本文中,我将关注 Seaborn 库中两个非常常见的情节:

  1. 小提琴情节
  2. 重新绘图

先说小提琴情节。

小提琴情节

小提琴图如此有用的原因是它给了你一个核密度和一个盒图。所以,在一个图中,你可以得到一个变量的很多信息。我将使用企鹅数据集绘制小提琴图:

import seaborn as sns
import matplotlib.pyplot as pltpen = sns.load_dataset("penguins")

我会从最基本的小提琴情节开始,慢慢走向一些更复杂的。

下面是 body_mass 的小提琴剧情:

sns.violinplot(data = pen, x = "body_mass_g")

这里你可以先看到密度图。这种分布是右偏的。中间的方框图显示了中间值(中间的小白点)、第一个四分位数、第三个四分位数、最小值和最大值。

对于下一个图,我将在 x 方向使用分类变量,在 y 方向使用 body_mass。我选择“岛”作为 x 方向。“岛屿”一栏中有三个岛屿。

我们将得到三个岛屿企鹅体重的三幅小提琴图:

plt.figure(figsize=(8, 6))
sns.violinplot(data = pen, x = 'island', y = "body_mass_g")
plt.show()

所以,我们有三个小提琴图,代表三个岛屿。你可以看到个别岛屿企鹅体重的核心密度和箱线图。

再往前走一步,我们甚至可以进入更细粒度的情节。

使用“色调”参数,我们现在将根据性别分离小提琴图:

plt.figure(figsize=(8, 6))
sns.violinplot(data = pen, x = 'island', y = "body_mass_g", hue ="sex")
plt.show()

对于每个岛屿,我们现在有两个小提琴图。一个用于男性人口,另一个用于女性人口。但是小提琴图两边的核密度是一样的。因此,Seaborn 库可以选择使用“split”参数使用两边来绘制两个类别的内核密度。

在这里,我用小提琴图的一边代表男性,一边代表女性:

plt.figure(figsize=(8, 6))
sns.violinplot(data = pen, x = 'island', y = "body_mass_g", hue ="sex", split=True)
plt.show()

这个图的一个缺点是你只能得到一个岛屿总人口的箱线图。当我们对男性和女性有单独的小提琴图时,我们也有单独的箱线图。

我们可以用虚线代替箱线图来表示四分位数:

plt.figure(figsize=(8, 6))
sns.violinplot(data = pen, x = 'island', y = "body_mass_g", hue ="sex", split=True, inner = "quartile")
plt.show()

我们得到了显示第一、第二和第三四分位数的四分位数,而不是箱线图。提醒一下,第二个四分位数是中位数。所以,这次我们得到了每个岛上男性和女性人口的独立四分位线。

除了四分位数,我们还可以通过使用“stick”作为内部参数来获得代表数据点的线。此外,我们总是有默认顺序的岛屿小提琴情节:“托格森”,“比斯开”,和“梦想”。顺序也可以改变:

plt.figure(figsize=(8, 6))
sns.violinplot(data = pen, x = 'island', y = "body_mass_g", hue ="sex", split=True, inner = "stick", order=['Dream', 'Torgersen', 'Biscoe'])
plt.show()

岛屿的顺序变了!

假设,岛上的两只企鹅被选为任何独特实验的特殊对象,我们想用不同的颜色展示它们。假设特殊的岛屿是比斯开和梦:

pen['special'] = pen['island'].isin(['Dream', 'Biscoe'])plt.figure(figsize=(8, 6))
sns.violinplot(data = pen, x = 'island', y = "body_mass_g", hue = "special")
plt.show()

看,比斯科和梦想的小提琴情节是不同的颜色!

小提琴图的最后一个图将显示使用 Seaborn 的小平面网格选项来绘制小提琴图。小提琴剧情本身没有那个特权。

我们可以使用“catplot”并将“kind”用作小提琴:

sns.catplot(data = pen, x = 'island', y = "body_mass_g", hue ="sex", split=True, inner = 'quartile', kind = 'violin', col = 'species', height = 5, aspect = 0.6)

我们对不同的物种有不同的情节。梦里只有‘下巴颏儿’,比斯开只有‘巴布亚’。

小提琴剧情到此为止!

我还有一个视频教程,一步步展示所有这些情节:

重新绘图

Seaborn 中的“relplot”也非常有用,因为它显示了两个变量之间的统计关系。它使用散点图和线图。这一部分将详细介绍 relplot。

我将使用著名的“泰坦尼克号”数据集:

ti = sns.load_dataset('titanic')

以下是该数据集的列:

ti.columns

输出:

Index(['survived', 'pclass', 'sex', 'age', 'sibsp', 'parch', 'fare', 'embarked', 'class', 'who', 'adult_male', 'deck', 'embark_town', 'alive', 'alone'], dtype='object')

像往常一样,我将从最基本的 relplot 开始。

从使用两个连续变量“年龄”和“费用”的最基本曲线开始:

sns.relplot(data = ti, x = 'age', y = "fare")

默认情况下,它使用散点图。我们将在后面看到如何使用线图。

添加“色调”参数,以获得不同类别的不同颜色:

sns.relplot(data = ti, x = 'age', y = "fare", hue = "alive")

我们再给它加一个变量。

将“pclass”变量作为“col”添加,以获得三个“pclass”的三个单独的图:

sns.relplot(data = ti, x = 'age', y = "fare", hue = "alive",
           col = "pclass", height = 4, aspect = 0.8)

我们为三个“类”提供了三个单独的地块。现在让我们进一步分离数据。

下一个图将为单个“embark_town”添加行:

sns.relplot(data = ti, x = 'age', y = "fare", hue = "alive",
     col = "pclass", row = "embark_town",  height = 4, aspect = 0.8)

我一直只使用默认的散点图。让我们看看如何使用线图。

我将再次回到最基本的情节,只看“年龄”和“费用”之间的关系。但这一次符合剧情:

sns.relplot(data = ti, x = 'age', y = "fare", kind = 'line')

默认情况下,线图带有直线和沿着直线的置信带。如果您不想要置信带,可以使用 ci = None 来避开它。

在下一个图中,我们将避开置信带,使用色调参数根据性别用不同颜色分隔数据,还将使用基于性别的样式和标记:

sns.relplot(data = ti, x = 'age', y = "fare", kind = 'line', ci = None, hue = "sex", dashes = True, style = "sex", markers= True)

这里,我使用了相同的变量作为“色调”参数和“样式”参数。但是如果你愿意,你也可以使用不同的变量。请尝试一下,看看你是否喜欢。

对于下一个图,让我们为三个“p 类”绘制三个单独的图,使用置信带,并根据“性别”绘制颜色和样式:

sns.relplot(data = ti, x = 'age', y = "fare", hue = "alive", col = "pclass", height = 4, aspect = 0.8, style = "sex", kind = "line")

如果这个情节对你来说太复杂了,那就摘掉信心带。那可能会有帮助。此外,请尝试在“行”中使用另一个变量,就像我之前在散点图中所做的那样。

下面是 relplot 的视频教程:

结论

我想向你们展示 Seaborn 库的两个重要图,它们有助于绘制连续变量,并提供许多有用的见解。我希望这有所帮助。

请随时在 Twitter脸书页面上关注我,并查看我的 YouTube 频道

更多阅读

https://pub.towardsai.net/data-analysis-91a38207c92b </30-very-useful-pandas-functions-for-everyday-data-analysis-tasks-f1eae16409af> https://medium.datadriveninvestor.com/what-is-a-b-testing-how-to-perform-an-a-b-testing-892cc8f35cbf

解缠结解码

原文:https://towardsdatascience.com/detangling-decoding-7e8842edfa0a

后处理是介于算法输出和洞察力之间的数据争论。

电唱机——图片来源:(unsplash.com/photos/-iKrBACW3lQ)

解码器模拟

如果你把一张黑胶唱片放在耳边,无论你吹掉多少灰尘,你都不会听到音乐。记录包含的信息以机器可读的格式编码。类似地,当机器学习算法返回预测时,它们通常以下列格式之一编码:

  • 二进制——0(s)和 1(s)的组合。
  • 按比例缩放 —小数在-1:0:1 之间。

我们不能告诉我们的最终用户他们的服装尺寸是 FloatTensor(-0.139374) 或者他们的诊断是 ndarray([0,0,0,1,0,0]) 。这些预测需要首先通过解码的过程变得可读。

在之前的文章中,我们简要地提到了 fit()/transform() 编码模式,其中 inverse_transform() 方法稍后用于解码。乍一看,解码过程似乎很容易,“只需运行 inverse_transform() 即可,对吗?”果然,当执行监督的分析时,解码预测相当简单,因为标签通常是与单个编码器相关联的单个列。

超越基础。

可视化阵列不断增加的维度(作者的图片)

然而,当使用生成算法时,事情变得更加复杂。由于自我监督旨在再现原始特征的修改版本,解码这些预测需要跟踪许多适合混合列的编码器。让我们一步一步地了解在解码(样本时间步长特征)的 3D 形状预测时可能会遇到的挑战。随着这种情况的发生,我们将很快看到对解码数据的系统方法的需求——就像我们的唱机如何机械地翻译音乐一样。

与此同时,我们将看看https://github.com/aiqc/aiqc(这篇博文的作者开发的 MLOps 开源框架)如何应对这些挑战。

https://github.com/aiqc/aiqc(别忘了⭐ )****

事情变得支离破碎

使用混合编码方法的 3D 生成式预测的列(作者图片)

💾 (1) 元数据要求。

  • 如果我们一开始就没有坚持我们的 fit() ,那么我们基本上就不走运了。我们无法解码我们的预测,除非我们能以某种方式重现分裂/折叠,并重新拟合()。记录“哪个样本进入哪个分割”并不是常见的做法,因此这种变通办法不太可能。**
  • 需要注意的一个主要缺陷是,一些编码器类根本没有 inverse_transform() 方法!最好在一开始就阻止它们的使用。
  • 由于一个 fit() 可以属于一个或多个列,我们需要一个“哪些列属于哪些编码器”的映射编码器通常不会保留这些元数据。
***class FittedEncoderset():**
    fitted_encoders      = PickleField()
    job                  = ForeignKeyField(Job, ...)
    encoderset           = ForeignKeyField(Encoderset, ...) **class Featurecoder():**
    sklearn_preprocess   = PickleField()
    matching_columns     = JSONField()
    encoded_column_names = JSONField()
    leftover_columns     = JSONField()
    leftover_dtypes      = JSONField()
    original_filter      = JSONField()
    encoding_dimension   = CharField()
    only_fit_train       = BooleanField()
    is_categorical       = BooleanField()
    index                = IntegerField()
    encoderset           = ForeignKeyField(Encoderset, ...)*

看一下 AIQC 数据库模式的源代码,我们可以看到每个 Featurecoder 都保留了在后处理期间对我们的预测进行反向工程所需的元数据。当编码器集适合分割/折叠时,一个fitdencoderset系统地捕获 fit()**

🎶②执行解码。****

  • 一旦我们知道我们需要做什么样的反变换,我们仍然会被卡住,因为编码器不支持高于 2 的维度。在应用 inverse_transform() 之前,我们需要将我们的宽 3D 阵列重新整形为一个高 2D 阵列。
  • 解码器不会就地覆盖列,而是生成独立的数据子集。所以我们需要在连接它们之前单独解码每个子集。
  • 如果使用了 OneHotEncoder() s,事情就会变得非常棘手。这类编码器根据其观察的类别数量产生 3+个编码列。它有一个 categories_ 属性,包含 1+列名数组,这可以帮助我们确定我们的解码器应该切掉多少列。
*import numpy as np
arr = np.array([[‘a’,1],[‘b’,2],[‘a’,4],[‘c’,3]])from sklearn.preprocessing import OneHotEncoder
OneHotEncoder().fit(arr).categories_"""
# Contains an array of discrete classes for each column.
[
    array(['a', 'b', 'c'], dtype='<U1'), 
    array(['1', '2', '3', '4'], dtype='<U1')
]
"""*

📐(3) 对解码后的数据进行整形。

  • 尽管已经执行了 inverse_transform() ,我们仍然需要将数据重新塑造成正确的形状。所以我们将把那个高大的 2D 阵列分成一个宽阔的三维阵列。
  • 由于编码器分别应用于子集,解码后的列顺序可能与原始数据中的列顺序不匹配。因此,在将列移回原位之前,我们必须对列映射进行逆向工程。

参考 aiqc。Jobs.predict() 方法来查看动态解码的效果。

计算尺—照片来源:(unsplash.com/photos/e6pPIcJ05Jg)

自动化还是不自动化

对于评估的每个分裂/折叠以及数据集中包括的任何其他多模态特征,必须重复整个解码过程。

在实践中,解码具有挑战性,因为它需要关于原始样本的元数据,并且特征往往会在编码期间改变顺序并扩展。

向自动化 MLOps 迈进一步是不是很可怕?是的,尤其是对数据科学家来说;一个如此习惯于批判性思考他们对数据采取的每一个行动的含义的群体。然而,在面对上述问题并将其解决到 5D(彩色图像时间序列数据集或多站点多序列数据集)后,我可以向您保证这是一项乏味且容易出错的工作。最好用一个动态的、经过良好测试的系统一次性解决这类问题,而不是针对每个特定的分析从头开始解决它们。

机器学习是时候超越互联网代码片段的口述历史,进入一个定义良好的框架。

使用主成分分析检测遥测数据中的异常

原文:https://towardsdatascience.com/detect-anomalies-in-telemetry-data-using-principal-component-analysis-98d6dc4bf843

鲁珀特·布里顿在 Unsplash 上的照片

使用简单的方法主动识别遥测数据中的问题

异常检测在许多领域都扮演着重要角色,尤其是在金融和医疗保健领域。主动识别金融交易中的异常有助于避免巨额损失。同样,在医疗保健中,它有助于在诊断早期识别健康风险。这篇博客主要关注使用主成分分析(PCA)的异常检测(AD)的简单形式。我们将把该算法应用到一个众所周知的领域——应用遥测。我们将学习在更高的环境中识别罕见事件。这篇文章的要点是:

  • 理解使用 PCA 的异常检测。
  • 基于 PCA 的 AD 在云应用中的应用。
  • 寻找异常的示例设计和代码。

在文章的最后,我们还学习了一些高级的异常检测方法。让我们开始吧。

什么是异常?

异常是指不规则的、意外的、罕见的数据,这些数据在很大程度上偏离了数据集中的其余记录。例如,如果有一个应用程序被 100 个用户访问,假设用户发出的每个唯一请求都会创建一个事件,该事件会向下游发送更多消息,从而消耗资源。在这种情况下,如果您注意到在很短的时间间隔内来自一个用户的数百万个请求,它可以被标记为罕见事件中最罕见的一个,一个异常。如果我是一名网站可靠性工程师,我希望有一个自动化系统,能够检测-警告并阻止此类事件不必要地消耗更多资源并导致其他地方的服务降级。理解异常并不总是坏事是至关重要的,某些罕见的事件,如采购订单的突然增加,对业务是有益的。没有适当的业务上下文,很难将数据项定性为异常。尽管如此,在管道的早期识别和检测它们通常是重要的。

用于异常检测的 PCA。

主成分分析主要是一种降维技术。它通过识别主要成分来工作。主成分是独立的特征向量,也称为给定数据的特征向量,其解释了数据中的最大方差。每个 PC 是现有相关特征的线性组合,并且与其他特征向量正交。使用主成分分析可以在不损失信息价值的情况下减少特征向量的数量。让我们看看如何为 IRIS 数据集找到 PC。

识别虹膜数据集的主成分

上面这段代码将一个数组输出为 array([0.92461872,0.05306648,0.01710261,0.00521218])。这意味着 92%的数据可以用第一个 PC 来解释,97% (92%+5%)的数据可以用第一个和第二个主成分来解释。因此,通过将特征维度从 4 个减少到 2 个,我仍然可以解释 97%的数据。这里的降维称为主分量。

异常检测依赖于重建误差。一旦 PC 被识别,通过选择所有的主成分,我们可以在没有数据丢失的情况下从变换的数据重建原始数据。类似地,通过只选择解释大部分方差的 PC,我们应该能够重建原始数据的近似值。生成原始数据时在重建期间产生的误差称为重建误差。对于数据中的异常,重建误差为

遥测数据中的异常检测。

Azure 允许您为云基础架构配置诊断,使用它我们可以详细捕获资源使用情况。除了资源使用情况,我们还可以将应用程序使用情况记录到一个名为 Log Analytics 的公共数据存储中。日志分析中的数据代表用户如何使用应用程序和基础架构。在下面的步骤中,我建议使用以下机制来使用日志分析数据识别异常。

使用遥测数据进行异常检测的逐步方法(图片由作者提供)

步骤 1:收集数据

使用诊断设置配置关键资源,并选择日志分析帐户目标。您可以设置连续导出或使用自定义查询下载所需的数据进行培训。例如,示例数据集这里的包含每 1 分钟收集的 Azure Keyvault 指标。我只考虑了几个指标,但是您可以根据需要扩展它们。

第二步:PCA 培训

由于 PCA 是一个静态过程,在下面的步骤中,我们使用 PCA 对数据和训练进行归一化。在这里,我将特征尺寸从 4 个减少到 1 个。第一主成分足以解释 96%的数据方差。

PCA 培训(图片由作者提供)

步骤 3:重建错误

在该步骤中,我们确定重建误差阈值,该阈值将在将来用于识别异常。在这种情况下,我选择 1 作为重建误差的阈值。任何大于 1 的重建误差都被视为异常(下图中的红色垂直线)

重建错误(图片由作者提供)

您可以轻松地将这段代码转换为 cron 作业,该作业获取最新的 Azure 指标,使用 PCs 进行重构,如果任何数据点重构超过错误阈值,就会发出警报。helper 方法的源代码和完整的笔记本可以在这里找到。

摘要

我们已经看到了使用主成分分析来自动检测和警告遥测数据中的异常的实用方法。我想提醒你,主成分分析并不是唯一用于异常检测的最大似然方法,其他方法有孤立树、一类 SVM 和局部异常 SVM。PCA 也有一些限制,主要是特征向量或主成分只能是现有特征的线性组合。如果数据不能用线性组合来解释,主成分分析就没那么有用了。在这些情况下,可以使用 t-SNE 等非线性方法。PCA 还降低了可解释性,因为实际特征被简化为线性组合,我们不再能够指出与目标向量高度相关的特定特征。

通过验证和通知尽早检测数据管道中的缺陷

原文:https://towardsdatascience.com/detect-defects-in-a-data-pipeline-early-with-validation-and-notifications-83e9b652e65a

使用深度检查和提督在 Python 中构建健壮的数据管道

动机

数据科学项目包括主要的组成部分,如获取数据、处理数据、训练 ML 模型,然后将其投入生产。

在将每个组件的输出提供给工作流中的下一个组件之前,验证每个组件的输出以确保每个组件正常工作是非常重要的。

作者图片

在本文中,您将学习如何:

  • 使用深度检查来验证数据科学管道研究阶段的组件
  • 使用提督在验证失败时发送通知

作者图片

建立

Deepchecks 是一个 Python 库,用于测试和验证你的机器学习模型和数据。

要安装 Deepchecks,请键入:

pip install deepchecks

Prefect 是一个 Python 库,可以监控、协调和编排应用程序之间的数据流。

要安装提督,请键入:

pip install -U prefect

本文中使用的提督版本是 2.0.2:

pip install prefect==2.0.2

创建验证套件

数据完整性套件

数据完整性套件允许您在拆分数据或使用数据进行处理之前验证数据。

作者图片

创建带有深度检查的验证套件有两个步骤:

  • 定义一个数据集对象,该对象保存关于数据集的相关元数据

  • 运行深度检查套件。要运行数据完整性套件,请使用data_integrity

要检查整个套件是否通过,请使用result.passed()

如果套件没有通过,上面的代码将引发一个错误。

现在我们已经熟悉了基本语法,让我们创建一个名为check_data_integrity的文件来加载配置和数据,然后运行 Deepcheck 套件。

运行该文件将在您的本地目录中创建一个 HTML 报告。您应该会看到类似以下 GIF 的报告。

作者图片

查看完整报告。

从报告中,我们可以看到数据集中存在冲突标签和数据重复。

作者图片

但是,数据通过了其余的数据完整性检查。

作者图片

该报告还显示了每项检查的详细信息。下图显示了要素标注相关性检查的详细信息。

作者图片

列车测试验证套件

当您想要验证两个数据子集(如训练集和测试集)时,训练测试验证套件非常有用。

作者图片

下面的代码显示了以下功能:

  • 用训练集和测试集初始化数据集对象
  • 创建培训测试验证套件

全码

运行上面的代码将生成另一个报告。以下是报告摘要。

作者图片

作者图片

查看完整报告。

模型评估套件

模型评估套件在训练模型之后或部署模型之前非常有用。

作者图片

要创建模型评估套件,请使用model_evaluation方法。

运行代码将创建一个报告。以下是我的模型评估套件报告摘要。

作者图片

作者图片

下图显示了简单模型比较的结果。

作者图片

查看完整报告。

当验证套件失败时发送通知

理想情况下,当验证套件失败时,我们希望:

  • 停止执行管道中的下一个组件
  • 向负责管道的团队发送通知
  • 修复代码并再次运行管道

作者图片

概括地说,为了在代码到达某个状态时创建发送通知,我们将:

  • 将 Python 函数转化为完美的流程
  • 给那个流附加一个标签(例如,dev)

作者图片

  • 创建发送通知的规则。具体来说,我们将设置规则,以便如果带有特定标签(即dev)的任何流的运行进入失败状态,提督将向 Slack 发送通知。

作者图片

创造完美的流程

为了了解如何创建一个完美的流,让我们从运行数据完整性套件的代码开始:

函数check_data_integrity包括创建数据完整性套件的函数。

要将这个函数变成一个完美的流程,只需在函数中添加装饰器flow

flow装饰器添加到管道中研究阶段的其他主要功能中,例如过程数据列车模型创建列车测试套件,以及创建模型评估套件

将所有这些流放在development流下。这将把它们变成子流。

流中的子流按顺序执行。如果一个子流程失败,下一个子流程将不会执行。例如,如果子流程check_data_integrity失败,子流程prepare_for_training将不会运行。

查看这篇文章,了解如何使用提督发送时差通知:

https://medium.com/the-prefect-blog/sending-slack-notifications-in-python-with-prefect-840a895f81c

设置通知后,当流失败时,您应该会在 Slack 通道中收到一条消息:

作者图片

结论

恭喜你!您刚刚学习了如何设置工作流来验证管道中每个组件的输出,并在验证失败时发送通知。

随意发挥,并在这里叉这篇文章的源代码:

https://github.com/khuyentran1401/prefect2-mlops-demo/tree/deepchecks

我喜欢写一些基本的数据科学概念,并尝试不同的数据科学工具。你可以在 LinkedIn 和 Twitter 上与我联系。

如果你想查看我写的所有文章的代码,请点击这里。在 Medium 上关注我,了解我的最新数据科学文章,例如:

https://medium.com/the-prefect-blog/orchestrate-your-data-science-project-with-prefect-2-0-4118418fd7ce https://pub.towardsai.net/github-actions-in-mlops-automatically-check-and-deploy-your-ml-model-9a281d7f3c84

用卷积神经网络和迁移学习检测钢中的缺陷

原文:https://towardsdatascience.com/detect-the-defects-in-steel-with-convoluted-neural-networks-cnns-and-transfer-learning-f761ca6c0d1f

照片由 Unsplash 上放大

用卷积神经网络和迁移学习检测钢中的缺陷

在机器学习和数据科学的帮助下检测钢铁中的缺陷,以便为钢铁工程师和制造商提供见解。

用途广泛,如修建铁路建筑道路家电其他基础设施。体育场桥梁这样的东西,大多是借助钢铁打造出来的。通过观察钢的各种使用方式,我们发现钢在自然界中无处不在。

照片由 Luca UpperUnsplash 拍摄

注:数据集的所有者是 PAO Severstal(https://www.severstal.com/)

然而,在钢的制造过程中,经常有缺陷被忽视的可能性。结果,最终的钢铁产品质量大打折扣。换句话说,我们得到的钢有缺陷,不能为我们建造桥梁和铁路的应用提供很多功能。除此之外,人类识别缺陷也相当困难,因为这通常是耗时且困难的过程。

尽管如此,在卷积神经网络的帮助下,随着定位检测并准确识别钢中的缺陷是可能的,因此对于工程师制造商来说将会非常方便。这将大大减少生产优质钢的时间。当定位也被提供时,它将帮助工程师发现导致钢中这种缺陷的设计错误和材料问题。本文主要考虑钢的缺陷识别,不包括定位

在这篇文章中,我重点介绍了我的 GitHub 项目的钢材缺陷检测 并详细说明了预测缺陷所采取的步骤。因此,这减少了工程师和制造商在钢铁生产中的时间和精力。

阅读图书馆

现在是时候看看卷积神经网络如何工作,以及转移学习如何显著提高我们的准确性。下面是我们将在这个项目中使用的库。

这些是执行卷积神经网络计算必须导入的一些基本库。

Numpy 用于将列表转换为数组,计算等等。

Seaborn 用于帮助我们交互绘图。

Pandas 用于读写中的数据。csv" 文件在我们的案例中。

cv2 是一个高效的库,用于读取图像并将其转换为像素值或数学值进行卷积。

Matplotlib 用于绘制可视化图形,以便用户能够分别理解工作。

Tensorflow 库用于训练和计算我们将在项目中使用的图像。

指定训练和测试路径

我们还将为训练集和测试集指定文件的路径。也就是说,我们需要寻找包含我们钢铁图像的目录。在指定了这个路径之后,我们将以数组的形式读取图像,以便用它来执行计算。

迁移学习

迁移学***减少了我们的训练时间,也提高了模型的准确性。在迁移学习中,已经为特定数据集训练的模型正在用于我们的应用程序。我们使用预训练模型和之前数据集中学习到的所有权重,而不是从零开始训练我们的模型,这需要很多时间,因此它为我们的钢分类任务产生了良好的结果。要了解更多关于迁移学习的知识,我建议查看这个博客。

深度学习迁移学习温和介绍(machinelearningmastery.com)

VGG19 网络

在上面的编码单元格中,我们看到 headmodel 变量考虑到了 VGG19 模型。预训练的权重取自 imagenet 数据集。我们使用 VGG19 作为我们的头模型或起始模型。添加此架构后,我们从各层获取输出,并在二维空间中执行平均池化。后来,有一个层的展平,以便最后,它被转换成一个 1D 数组,分别用于我们的训练。我们使头模型(VGG19) 的所有层不可训练,这意味着当我们使用 steel 数据集训练时,这些权重不会被修改。

编译模型

现在是时候编译模型,并为我们的最终模型选择正确的损失优化器指标。我们选择了分类交叉熵,因为它是一个多类分类问题。选择 Adam optimizer 是因为它在很大程度上减少了损失,并且没有太多噪声。将要显示的度量是精度

定义检查点

模型回调当模型在训练模型的任务中达到特定结果时,在深度学习中使用。我们使用检查点变量来存储基于交叉验证准确性的最佳模型,如上所示。在拟合模型时,会给出 checkout 变量,以便启动回调。

拟合最终模型并采用 fitted_model 变量中的参数,使其可用于绘图和评估机器学习模型的性能。我们用更少的时期数(10)进行训练,同时打印交叉验证准确度。

评估模型

评估深度学习和卷积网络(CNN)模型性能的最佳方式之一是包括其训练误差和交叉验证误差的。为了了解模型是过拟合还是欠拟合,最好看看误差值是如何随着时期数的增加而减小还是增加的。要了解更多关于过度拟合和欠拟合的信息,请随意参考我写在下面的文章。

什么是机器学习中的过拟合和欠拟合?|作者 Suhas Maddali | mlearning . ai | Medium

让我们也探索其他网络,为我们预测钢中缺陷的任务选择最佳网络。

高效网络

现在是时候使用有效网络来查看模型的表现如何,以及最佳模型是否会随着准确性的增加而减少交叉验证误差。值得注意的是,有效网络有多种变体,但我们使用 EfficientNetB0 作为起点。我们通常不改变网络的最后几层。我们再次将预训练的 EfficientNet 模型用于 imagenet 数据。但是我们将训练我们网络的最后几层来完成检测钢中缺陷的任务。再次应用对 VGG19 型号采取的上述步骤。也就是说,我们将拟合模型并将最佳模型存储在.【H5】文件中,并且评估模型。让我们也来看看其他提高交叉验证数据准确性的网络。

例外网络

同样,借助于例外网络和 imagenet 上的预训练权重,我们将了解如何减少交叉验证误差或提高准确度。为了获得最佳性能,输入必须分别为 (299,299,3) 的形状。因此,输入的形状和尺寸是在启动网络时指定的。

为例外网络指定相同的优化器和损失,对于多类分类问题,度量为准确度

我们拟合模型并将结果再次存储在 fitted_model 变量中,该变量稍后分别用于绘图。在执行训练后查看这些图有助于理解模型是否分别具有高方差或偏差。基于这些图,采取行动是增加时期的数量还是增加训练样本的数量。

InceptionV3 网络

现在是时候为我们预测钢中缺陷的任务初始化网络了。 InceptionV3 是由谷歌开发的网络。它最初从较少数量的通道开始。当我们向前传播到网络深处时,很明显,信道的数量增加了,而卷积运算减少了激活单元的宽度和高度。我知道这听起来很过分。你可以看看下面的网站来更好地了解 InceptionV3 网络。

盗梦空间高级指南 v3 |云 TPU |谷歌云

结论

在对模型进行训练和测试后,我发现 InceptionV3 网络与其他网络相比表现最佳。然而,这取决于我们所考虑的问题或任务。对于 imagenet 等其他任务,高效网表现最佳,同时也显著减少了训练时间。因此,可能有许多模型在特定的任务集上表现良好。在部署生产中的最佳型号之前,考虑所有型号是很重要的。希望这篇文章对你有所帮助。以下是您可以联系我或查看我的作品的详细信息。

GitHub: 苏哈斯马达利(Suhas Maddali)(github.com)

LinkedIn: (1)苏哈斯·马达利,东北大学,数据科学| LinkedIn

中等: 苏哈斯·马达利—中等

计算机视觉中数据漂移的检测和修复

原文:https://towardsdatascience.com/detecting-and-fixing-data-drift-in-computer-vision-8d0853af1246

带有可运行代码的实际案例研究

图片由来自 PixabayDimitris Vetsikas 拍摄

简介

如果您已经在数据科学和 ML 领域工作了一段时间,您会知道您所训练的模型在生产环境中的表现可能会出乎意料。这是因为生产数据往往不同于用于创建模型的封闭训练数据集。此外,生产数据会随着时间不断变化,因此即使最初表现良好的模型也会随着时间的推移而退化。

作者图片

上面所描述的就是所谓的数据漂移,它在 ML 中非常普遍。

有很多文章深入解释了这个概念,但在本教程中,我们将重点放在数据漂移检测的实际部分,并在一个计算机视觉示例中解决它。

我们将在这里向您全面解释我们创建的数据漂移监控系统,有关更多细节和完整代码,您可以查看这个 colab 笔记本

问题陈述

猫还是狗?—作者图片

在这个案例研究中,我们将监控一个经过训练可以区分猫和狗的计算机视觉分类器。我们使用的实际模型已经使用 Keras 进行了训练,它只是一个简单的 CNN。

将要监控的数据将模拟一周的生产性能,因此我们将图像分成几天一批(第 1 天、第 2 天、…、第 7 天)。

此外,我们在以后的日子里模拟了数据漂移,所以在第一批中,您将看到正常的猫和狗的图像…

然而后来的几天出现了一些相机问题,一些像素被破坏,图像看起来更像这样…

相机问题—图片由作者提供

让我们看看能否用漂移监测系统检测到它。

如何检测数据漂移并获得早期警报

我们将使用开源库 whylogs 来分析我们的数据,并将分析结果发送到 WhyLabs 进行数据漂移和 ML 性能监控。

使用 whylogs 创建的配置文件是高效的、可合并的,并且只包含必要的摘要统计信息。这使得它们可以用于几乎任何应用,无论是需要批量或流处理数据,还是医疗保健和金融等敏感行业。在 GitHub 上阅读更多关于 whylogs 的内容。

WhyLabs 可以轻松存储和监控 whylogs 创建的配置文件,以检测机器学习管道中的异常,如数据漂移、数据质量问题和模型偏差。

在这篇文章中,我们在 Colab 笔记本中介绍了监控我们的计算机视觉数据和模型的细节,但在下面,您可以看到 WhyLabs 仪表板的预览。

检测 WhyLabs 中的数据漂移—图片由作者提供

在它的左侧,您可以看到正在监控的不同特性(亮度、像素高度、像素宽度、饱和度等)。其中几个旁边有一个红色的感叹号。这表明我们的数据已经在这些维度上漂移了。您可以详细探索每个功能(仪表板的中心)。上面的屏幕截图显示了亮度值,您可以看到亮度值在漂移监测的第 4 天开始漂移,并在第 5、第 6 和第 7 天继续漂移。

这将是有意义的,记住,我们已经应用了一些像素的图像发送到以后的日子。

带人工注释的模型漂移检测

我们已经被告知,后面几天的一些数据看起来与我们的模型被训练的数据有些不同。但是模型的性能受到影响了吗?

为了检查它,需要人工注释。我们可以自己做,但这是非常不切实际和不可扩展的,所以我们将使用 Toloka 众包平台。

为了做到这一点,我们首先需要建立一个贴标项目,在那里我们为贴标机指定说明,并设计一个界面。后者可以使用 Toloka 中的 Template Builder 工具来完成,如下所示。

分类项目任务界面—作者图片

一旦我们建立了一个项目,我们可以上传所有需要注释的照片。我们可以使用 python 和 Toloka-Ki 库以编程的方式来实现。请记住,在本笔记本中,您有设置该系统所需的全部代码。

一旦你的每日批次被上传,你应该让他们按照如下所示的日常方式进行组织。

作者图片

在标记开始之前,我们需要选择将参与项目的表演者。因为这是一个简单的任务,我们不需要做复杂的过滤技术。我们将允许说英语(因为这是编写说明的语言)的注释者参与进来。

此外,我们还设置了一些质量控制规则,如验证码和控制任务。这很重要,因为众包要成为有效的工具,需要严格的质量控制规则。我们还将相同的图像发送给三个不同的标注者进行标注,以便对每个标注有更多的信心。

当所有注释者完成工作后,我们可以将这些数据(我们的黄金标准)与模型预测一起发送,并使用 WhyLabs 对它们进行进一步分析。

将模型预测与人工注释进行比较

我们现在可以使用 WhyLabs,通过比较模型预测值和基本事实来监控机器学习性能指标。还可以配置监视器来检测这些指标是否发生变化。

下面你可以看到一个仪表盘,上面有我们案例研究的准确度、精确度、回忆、f 分数和混淆矩阵。请注意,模型的准确性在后来的日子里从大约 80%下降到 40%。看起来我们在本案例研究开始时从输入要素获得的初始警报是正确的,并且现在得到了地面事实的证实。我们的模型漂移了!

在 WhyLabs 中监控机器学习性能指标—图片由作者提供

一旦我们发现模型漂移,通常的程序是根据新的例子重新训练模型,以说明环境的变化。因为我们已经用 Toloka 注释了新数据,所以我们现在可以用它来重新训练模型。或者,如果我们认为新样本太小,我们将触发更大的数据标注管道来收集更多的训练样本。

总结

本教程教你如何为一个计算机视觉项目建立一个 ML 模型漂移监控系统。我们使用了一个简单的猫和狗的分类例子,但这个案例研究可以很容易地扩展到更复杂的项目。

记住你可以在这个笔记本里查看完整的代码。此外,我们还举办了一个现场研讨会,一步一步地解释这个流程的每个阶段都发生了什么。这是一个很长的录音,但如果你对更详细的解释感兴趣,值得一看。

我还要感谢本文的合著者塞奇·艾略特(Sage Elliott)和与我们合著了最初的笔记本丹尼尔·费杜洛夫(Daniil Fedulov)

PS:我正在 Medium 和aboutdatablog.com上撰写以简单易懂的方式解释基本数据科学概念的文章。你可以订阅我的 邮件列表 在我每次写新文章的时候得到通知。如果你还不是中等会员,你可以在这里加入https://medium.com/@konkiewicz.m/membership

下面还有一些你可能喜欢的帖子

* *

利用 OpenMMLab 用 YOLOX 检测 PCB 中的缺陷

原文:https://towardsdatascience.com/detecting-defects-in-pcbs-with-yolox-using-openmmlab-91c1775ac884

使用 OpenMMLab 和著名的 YOLO 系列中的新架构 YOLOX 进行对象检测。我们将尝试检测印刷电路板中的不同缺陷。

Alexandre Debiève 在 Unsplash 上的照片

印刷电路板

我知道我知道,你一定会问,什么是 PCB?你不是吗?嗯,对于不知道 PCB 是什么的人来说,这里有一个来自 维基百科 的定义:

一个印刷电路板 ( PCB )是导电层和绝缘层的叠层夹层结构。PCB 有两个互补的功能。第一种是通过焊接将电子元件固定在外层的指定位置。第二是以受控的方式在元件的端子之间提供可靠的电连接(以及可靠的开路),这通常被称为 PCB 设计。

我敢打赌,你一生中肯定至少见过一次 PCB,但可能懒得知道它是什么。你看过 DVD 阅读器的背面吗?这里有一张 DVD 阅读器上 PCB 的图片,来自 维基百科 :

来自维基百科的 PCB 示例

多氯联苯随处可见。几乎所有的电子设备都有一块 PCB 隐藏在其中。在许多情况下,这些多氯联苯在设计时或使用后都有缺陷。这里列出了一些常见的 PCB 缺陷,如互联网上所列的,还有一张来自⁴.的免费数据集的图片

  1. 打开
  2. 过量焊料
  3. 组件移位
  4. 冷接缝
  5. 焊料桥
  6. 网状和飞溅
  7. 抬起的垫子

图 1:来自⁵PCB 数据集 Github 知识库的示例图像

我们不会深究它们的确切含义,因为这不是这篇博客的内容。但是,从了解一点计算机视觉和深度学习的计算机工程师的角度来看,在 PCB 的数字图像中检测缺陷似乎是一个可以解决的问题。

我们将使用来自⁸的 OpenMMLabmmdetection ⁶来检测 PCB 图像中的缺陷。OpenMMLab⁸是一个深度学习库,拥有计算机视觉领域大多数最新实现的预训练模型。它实现了几乎所有众所周知的视觉问题,如分类、对象检测&分割、姿态估计、图像生成、对象跟踪等等。

YOLOX:超越 2021⁷yolo 系列

在这篇博客中,我们将使用 YOLOX ⁷,我们将用 mmdetection ⁶.进行微调 YOLOX ⁷是 2021 年发布的最先进的车型,是 YOLO 系列的改进。作者做了一些重要的改进,列举如下。

  1. 用于标签分配的 SimOTA 简介
  2. 移除锚箱
  3. 特别注意数据增强,如抖动混淆
  4. 用于检测和分类的分离头

图二:从 YOLOX 截取的建筑图:超越 202 ⁷的 YOLO 系列

之前从 v3 到 v5 的 YOLO 系列都有一个单一的预测头,包括边界框预测、分类得分预测以及客观得分预测,如上图上半部分所示。

这在 YOLOX⁷系列中有所改变,作者选择使用一个分离的头,所有预测都使用单独的头。如上面图 2 的下半部分所示,检测头和分类头位于不同的头中。这有助于改善训练过程中的收敛时间(如图 3 所示),并略微提高模型精度。

图 3:从 YOLOX 截取的训练指标图:超越 202 ⁷的 YOLO 系列

由于这一点,模型的速度受到影响,因为参数的数量随着分成两个头而显著增加。正如我们在图 4 中看到的,YOLOX-L 比 YOLOv5-L 慢一点。它还有专门为参数低得多的边缘设备构建的纳米和微型版本。

图 4:从 YOLOX 截取的速度和尺寸与精度的关系图:超越 202 ⁷的 YOLO 系列

与之前最先进的对象检测模型相比,它们在平均精度方面有所提高,FPS 略有下降。

图 5:从 YOLOX 截取的物体检测模型对比表:超越 202 ⁷的 YOLO 系列

最后,正如伟大的莱纳斯·托沃兹曾经说过的,

空谈不值钱。给我看看代码。

让我们直接跳进来吧!

使用 mmdetection 微调 YOLOX

我们有一个开源的 PCB 缺陷数据集,称为 DeepPCB⁵ 。该数据集由 1500 个图像对组成,每个图像对具有无缺陷的模板图像和具有缺陷的图像,该图像具有 6 种常见缺陷类型的边界框注释,即。开路、鼠咬、短路、杂散、假铜和针丨孔丨。如果你需要的话,你可以从数据集链接 ⁵中获得更多关于它的信息。

这些图像的尺寸为 640×640,在我们的例子中是完美的,因为 YOLOX⁷是在相同的尺寸上训练的。

OpenMMLab⁸

OpenMMLab 使得微调最先进的模型变得非常容易,只需很少的代码更改。它有一个针对特定用例的综合 API。我们将使用 mmdetection⁶ 对 DeepPCB⁵数据集上的 YOLOX⁷进行微调。

数据集格式

注意:PCB 缺陷数据集是从 DeepPCB Github repo 中获取的开源数据集,带有 MIT 许可证,可在此处参考

我们需要将数据集修改为 COCO 格式或 Pascal VOC 格式来重新训练模型。这是 mmdetection⁶加载我们的自定义数据集进行训练所必需的。出于培训目的,我们将采用 COCO 格式。你不需要麻烦地将数据集转换成 COCO 格式,因为它已经为你完成了。您可以从这里直接下载转换后的数据集。整个数据集与 DeepPCB⁵的相同,只是增加了带有用于训练的 COCO 格式注释的训练和测试 JSON 文件。

我不会经历到 COCO 格式的转换,因为你可以找到许多文档经历这个过程,就像在 mmdetection 文档中提到的一样。请随意浏览脚本,如果您需要任何帮助,不要介意留下评论。把这个数据集转换成 COCO 格式的脚本在这里分享

将 DeepPCB 数据集注释转换为 COCO 格式的脚本

数据集配置

下一步是修改数据集配置,以使用我们的自定义数据集。我们将需要添加/修改特定的东西,如number of classesannotation pathsdataset pathnumber of epochsbase config path,以及一些数据加载器参数。

我们将复制预先编写的 YOLOX-s 配置,并针对我们的数据集对其进行修改。配置的其余部分,如增强、优化器和其他超参数将是相同的。

我们不会做太多改变,因为这篇博客的主要目的是熟悉手头的问题,尝试最先进的 YOLOX 架构,并试验 mmdetection 库。我们将把这个文件命名为yolox_s_config.py,并用于训练。

我们将为我们的预测头添加class names并改变number of classes。需要更改base path,因为配置将从根目录而不是configs目录加载。

_base_ = ['configs/_base_/schedules/schedule_1x.py', 'configs/_base_/default_runtime.py']classes = ('open', 'short', 'mousebite', 'spur', 'copper', 'pin-hole')bbox_head = dict(type='YOLOXHead', num_classes=6, in_channels=128, feat_channels=128)

我们需要稍微修改一下训练数据集加载器,以使用我们的classesannotations路径。

train_dataset = dict(
    type='MultiImageMixDataset',
    dataset=dict(
        type=dataset_type,
        classes=classes,
        ann_file='train.json',
        img_prefix='',
        pipeline=[
            dict(type='LoadImageFromFile'),
            dict(type='LoadAnnotations', with_bbox=True)
        ],
        filter_empty_gt=False,
    ),
    pipeline=train_pipeline)

我们需要对这里的验证和测试集做同样的事情。我们不打算在这里使用一个单独的测试集,相反,我们将使用同一个测试集进行验证和测试。

data = dict(
    samples_per_gpu=8,
    workers_per_gpu=4,
    persistent_workers=True,
    train=train_dataset,
    val=dict(
        type=dataset_type,
        classes=classes,
        ann_file='test.json',
        img_prefix='',
        pipeline=test_pipeline),
    test=dict(
        type=dataset_type,
        classes=classes,
        ann_file='test.json',
        img_prefix='',
        pipeline=test_pipeline))

我们将只训练 20 个时期的模型,并且每 5 个时期得到验证结果。我们不需要再训练它了,因为我们在仅仅 20 个纪元内就获得了不错的结果。虽然这不是最好的模式,但为了博客,这是不错的。

max_epochs = 20
interval = 5

你也可以从要点这里复制整个编辑过的配置,并用它来训练模型。你可以以后再谢我!😉

培养

我们可以很好地处理数据集部分。接下来我们需要做的是训练模型。mmdetection⁶图书馆最好的一点是,所有与培训相关的事情都已经为你做好了。您需要做的就是从tools目录运行训练脚本,并将路径传递给我们上面创建的数据集配置。

python3 tools/train.py yolox_s_config.py

瞧啊!🔥

在 mmdetection⁶.的帮助下,您已经使用 DeepPCB⁵数据集成功训练了 YOLOX⁷模型来检测 PCB 缺陷

推理

让我们看看我们的模型在一些例子上的表现。你一定想知道,看着训练模型是多么容易,一定有一个命令来对图像进行推理?有!但是,不要让训练模型的简单过程宠坏了你。让我们写一些用于推理的代码,但是让你高兴的是,它不到 10 行代码。

from mmdet.apis import init_detector, inference_detector, show_result_pyplotconfig_file = 'yolox_s_config.py'checkpoint_file = 'best_bbox_mAP_epoch_20.pth'
device = 'cuda:0'# init a detector
model = init_detector(config_file, checkpoint_file, device=device)# inference the demo image
image_path = 'demo.jpg'op = inference_detector(model, image_path)
show_result_pyplot(model, image_path, op, score_thr=0.6)

这将显示一个图像,其上绘制了带有预测类名的边界框。这是一个包含模型预测的数据集的示例图像。

来自 DeepPCB 数据集的预测示例

我们做到了!🎊

你可以在谷歌实验室这里找到训练和推理的代码。你也可以尝试我们预先训练好的模型,并用它进行推理。预先训练好的模型可以在这里下载。请随意使用它,如果你遇到任何问题,请在评论中告诉我。乐意帮忙!😉

结论

今天,我们了解了现实世界中普遍存在的一个新问题,并尝试用像 YOLOX⁷.这样的艺术模型来解决这个问题

我们还使用深度学习社区中领先的开源库之一 mmdetection⁶来训练对象检测模型。如果我不提 mmdetection⁶是如何宠坏了我们,让我们几乎不用任何定制脚本就能如此快速轻松地解决问题,那将是不公平的。

我希望你今天学到了新东西!请随意运行代码。

深度学习分析,我们非常热衷于使用机器学习来解决现实世界的问题。我们已经帮助许多企业部署了创新的基于人工智能的解决方案。如果你看到合作的机会,请通过我们的网站这里联系我们。

参考

  1. PCB 定义:【https://en.wikipedia.org/wiki/Printed_circuit_board
  2. PCB 图:https://en . Wikipedia . org/wiki/File:SEG _ DVD _ 430 - Printed _ circuit _ board-4276 . jpg
  3. 常见 PCB 缺陷:https://blog . matric . com/7-PCB 类型-焊接-缺陷-注意-for
  4. PCB 缺陷数据集论文:https://arxiv.org/abs/1902.06197
  5. PCB 缺陷数据集:https://github.com/tangsanli5201/DeepPCB
  6. mm detection:https://github.com/open-mmlab/mmdetection
  7. YOLOX Paper:https://arxiv.org/abs/2107.08430
  8. OpenMMLab:https://openmmlab.com/

用深度学习检测象牙文物

原文:https://towardsdatascience.com/detecting-ivory-artifacts-among-open-source-met-museum-data-with-cnn-based-deep-learning-481739b03298

图像识别有助于打击非法网上象牙销售

米隆·奥利拉在 Unsplash 上的照片

介绍

迄今为止,野生动物保护团体和组织已经将外交和公众压力运动作为他们对抗象牙市场继续存在的主要工具。虽然这种形式的行动肯定是值得的,并能带来巨大的成功(我将在下面进一步描述),但它不必是唯一的工具。可以部署技术和机器学习来打击网上非法象牙销售,一旦象牙销售在一个国家成为非法,这是关键的下一步。本文将概述我们如何开发这样一个模型的概念证明,使用基于象牙的文物的开源数据来分类物体是否由象牙组成,准确率超过 80%。

背景

大象不仅是生物和文化的象征,也是生态系统中的基石生物。由三种主要物种组成——非洲森林象、非洲稀树草原象和亚洲象——每一种在各自的环境中都有无数的用途,如创造栖息地、传播种子、创造森林通道和灌木覆盖管理。1930 年,非洲大陆上估计有 1000 万头野生大象。但是经过几十年的偷猎、栖息地丧失和其他人类干预,这个数字在 2007 年下降到大约 496,000 只。在接下来的七年里,非洲的大象数量进一步减少了 30%,只有 352,000 头,这是有史以来最大的野生动物调查之一。

图表从我们的世界中获得数据

世界野生动物基金会估计偷猎者每年杀死 20,000 头野生大象。这些杀戮中的绝大多数都是出于非法商业目的,主要包括大象的贵重象牙,也就是众所周知的象牙。由于大象数量如此之少,这种偷猎行为对地球上大象的持续生存构成了威胁。2016 年,包括美国、英国、新加坡和其他国家在内的许多国家几乎全面禁止进口和销售象牙和象牙制品。第二年,中国也采取了一个重大举措,结束了象牙在国内的合法销售,据环境调查机构称,这一举措被广泛认为是“结束屠杀大象的最大一步”。史密森尼杂志估计禁令消除了多达 70%的全球象牙市场。

尽管这是一个重大的进步,但仍有许多工作要做。中国仍然允许消费者从国外运输象牙,而在越南和泰国等国家,象牙销售名义上是非法的,很少或根本没有执行。这导致了一个完全不受监管的市场;《可持续发展时报》估计,2009 年至 2018 年间,超过 56 吨象牙进入越南黑市,另有 20 吨在运输途中被截获。

为了打击象牙贸易的持续存在,野生动物保护主义者正在不懈地敦促世界各国制定更严格的立法,但在线购物和点对点市场的出现带来了一个具有挑战性的路障。英国肯特郡杜雷尔保护与生态研究所(DICE)最近的一项研究发现,非法象牙贸易在网上点对点市场上蓬勃发展。虽然在大多数主要网站上可以通过关键词搜索特定的产品列表,但没有简单的方法来判断象牙产品是否以别名或假名出现在帖子中。

这个项目的目的是创建一个机器学习图像识别模型的概念证明,该模型处理三维对象和人工制品的图像数据,并将它们分类为包含或不包含象牙。这种概念验证可以作为一个模型的起点,该模型可以帮助在线 P2P 市场、拍卖网站、执法机构和野生动物非政府组织识别可能包含非法象牙产品的清单,并标记它们以供进一步调查。

数据来源

由于在线象牙销售在世界上大多数国家都是非法的,因此获得足够大规模和质量的数据集来训练神经网络并不是一项简单的任务。理想情况下,这样的数据集将从当代加工的象牙制品中获得,这些象牙制品在该模型打算部署的市场上出售。缺少了这些,我们决定走非当代路线,使用来自纽约大都会艺术博物馆的图像数据。该博物馆收藏了近 50 万幅跨越 5000 多年历史的油画、素描、雕塑和其他文物,并通过博物馆的收藏 API 开源了几乎所有藏品的数据。该 API可免费用于商业和非商业用途,使用该服务不需要 API 密钥。

通过对象的“objectId”在 API 中搜索工件,objectId 是一个 5 或 6 位的识别码。搜索返回一个包含 57 项信息的字典,这些信息包括艺术品的部门、名称、艺术家姓名、艺术媒介、地区和尺寸,尽管大多数项目的许多字段都是空白的。博物馆通过返回字典中的特定键链接到包含 jpeg 图像的 URL,在 API 中提供大多数文物的图像。

对于这个项目,我们首先搜索在对象的媒介中列出单词“象牙”的工件。这些艺术品形成了最初的象牙艺术品收藏,我们用它们来训练我们的神经网络。这个查询总共返回了 5975 个包含单词“象牙”的工件,作为对象的媒介列出。对于训练神经网络的非象牙物体图像,我们搜索了包含介质中列出的“陶瓷”的物体,因为我们推断陶瓷人物的大小和形状与大多数象牙物体相似,因此很难与象牙文物进行比较,以便神经网络进行分类。为了感受一些物体的外观,下面是来自最终数据集的五幅图像,每个工件上方都显示了真实的标签。

图片来源于纽约大都会艺术博物馆

经过对数据的进一步探索,我们发现我们最初收集的象牙物品中的许多文物(所有文物都将象牙列为媒介)包含许多艺术品,其中象牙只是设计的一个次要组成部分。在许多这样的案例中,很明显象牙在艺术品的成分中是如此有限,以至于包含这样的物体会引入比信号更多的噪音。出于这个原因,我们决定将传递到模型中的象牙制品集合限制在象牙是唯一或主要组件的对象中。在我们最初的 API 调用中的 5,975 个象牙制品中,有 1,769 个在选择细化和预处理后仍然存在。这 1769 幅象牙图像与 1767 幅非象牙图像配对。

模型

在建模之前,数据被分成训练/验证/测试部分。在数据集中总共 3,536 幅图像中,2,265 幅用于训练,565 幅用于验证,706 幅用于测试。非象牙文物被指定为这个二元分类的正常状态,并给予 0 类标签。象牙制品被指定为异常状态,并被贴上 1 的标签。

作为该项目的一部分,总共测试了六个模型。我们的基线模型是在 Keras 中实现的全连接(密集)神经网络。基准模型在测试数据上获得了 72%的准确率——在数据集具有平衡的类权重的情况下,比虚拟分类器提高了 22%。

我们表现最好的模型实际上是我们第一次迭代的卷积神经网络(CNN),在测试数据上的准确率为 82.4%。该模型的详细信息、参数和评估指标可以在下面找到。CNN 的后续迭代调整了优化器(自适应矩估计又名亚当),使用了正则化( L2 又名岭正则化),减少了批量大小,增加了滤波器的数量。尽管进行了调整和修补,但在测试数据准确性得分方面,这些模型都没有超过第一个 CNN 模型,尽管有些非常接近。

最佳表现者:V1 Keras 序列卷积神经网络

神经网络模型架构:
时序

  • 输入层:Conv2D(层数:32,内核大小:(3,3),激活:relu)
  • MaxPooling2D(池大小:(2,2))
  • Conv2D(层数:32,内核大小:(4,4),激活:relu)
  • MaxPooling2D(池大小:(2,2))
  • Conv2D(层数:64,内核大小:(3,3),激活:relu)
  • MaxPooling2D(池大小:(2,2))
  • 展平()
  • 密集(单位:64,激活:relu)
  • 密集(单位:1,活化:乙状结肠)

参数:

评估指标(测试数据):

  • 精确度:0.824
  • 损耗:0.444
  • 精度:0.840
  • 回忆:0.802

作为模型评估阶段的一部分,我们检查了一些被模型错误分类的工件。下图显示了五个这样的工件,标签再次表示每个对象的基本事实类(因此模型预测的是标签的反面)。

图片来源于纽约大都会艺术博物馆

你可能已经注意到,没有一件象牙制品具有象牙所特有的棕褐色或黄褐色。此外,第一件和最后一件物品看起来几乎类似于某种石头或岩石,当然也不是人们所期望的象牙艺术品的形状。第四个物体确实有一个更经典的图形和表现,人们可能会期望,但它实际上不是象牙。根据我们的分析,当物体的颜色和形状不符合象牙制品(淡黄色,雕刻成人类或动物雕像)的模型时,该模型最难出现假阴性;当制品符合这样一个物体的模型,但当然不符合时,该模型最难出现假阳性。

结论

虽然该数据集中的许多象牙制品很难分类,但我们的结果表明,成功分类象牙制品是可能的。82.4%的准确性分数可能太低,不足以部署,但我们能够构建一个性能远远好于机会的模型这一事实表明,我们的概念验证是成功的。此外,由于数据集的性质,很难在这些对象上获得非常高的准确度分数。我们数据集中的一些物品已经有数百或数千年的历史,这可能会导致褪色或变形,或者丢失部分原始艺术品。也有许多文物可能具有历史或艺术意义,但很可能不是今天在非法黑市上出售的象牙制品的代表。

尽管在测试数据的整体准确性方面,没有其他模型比第一个 CNN 得分更高,但在精确度-召回权衡方面,一些模型确实摇摆不定。从上面的评估指标和混淆矩阵中可以看出,我们的最佳模型在正确分类非象牙制品方面表现得更好(精度更高)。然而,我们的 Adam 优化模型恰恰相反,正确地分类了更多基于象牙的对象(更高的召回率)。使用这种模型的组织应该考虑精确召回的权衡,以及他们希望模型倾向于哪个方面。如果抓住每一个潜在的象牙销售是首要任务,那么高召回分数应该优先考虑。另一方面,如果组织希望限制在错误线索上浪费资源,并且只让模型标记很可能是象牙的图像,则他们应该优先考虑高精度分数。

如果有更多的时间和资源,可以采取多个后续步骤,这些步骤有可能产生更好的结果。首先也是最重要的是,获得额外的和更新的数据最有可能产生更准确和有用的模型。理想情况下,这些数据将由模型部署的在线市场上出售的象牙物品的真实发布所附带的图像组成。此外,其他神经网络的实验和进一步的超参数调整也可能证明是有益的。在这个项目中,我们只使用了密集和卷积神经网络,但也有其他形式的神经网络已经在图像识别任务中显示出有希望的结果。

照片由比萨哈·达塔Unsplash 上拍摄

我们连线吧!我鼓励你评论、分享或直接给我发消息,告诉我你对这里介绍的想法和技术的想法,或者对我应该继续研究的有趣主题或资源的建议。

LinkedIn | 项目 GitHub 资源库

用简单和先进的技术检测异常值

原文:https://towardsdatascience.com/detecting-outliers-with-simple-and-advanced-techniques-cb3b2db60d03

关于如何使用标准差、四分位数范围、隔离森林、DBSCAN 和局部异常值因子检测异常值的教程

本杰明·沃罗斯Unsplash 拍摄的照片

离群值是远离数据集中大多数观察值的数据点。异常值的出现有多种原因,例如群体行为的自然偏差、欺诈活动以及人为或系统错误。然而,在运行任何统计分析或为训练机器学习模型准备数据之前,检测和识别异常值是必不可少的。

在本文中,我们将介绍单变量和多变量异常值,它们之间的区别,以及如何使用统计方法和自动化异常检测技术来识别它们。我们将看到用于检测单变量异常值和隔离森林的四分位距和标准差方法、用于检测多变量异常值的 DBSCAN(带噪声的基于密度的应用程序空间聚类)和 LOF(局部异常值因子)。

在阅读本文的同时,我鼓励您查看我的 GitHub 上的 Jupyter 笔记本以获得完整的分析和代码。

我们有很多要讲的,让我们开始吧!🚀

数据

在本文中,我们将使用来自 UCI 的玻璃识别数据集,该数据集具有 8 个与玻璃含量和玻璃类型相关的属性(即“Na”)。

import pandas as pdglass = pd.read_csv('glass.csv')

单变量和多变量异常值

通过使用seaborn’s pairplot,我们可以绘制玻璃含量之间的成对关系,通过这种视觉,我们可以看到我们的基础数据的分布情况。

import seaborn as snssns.pairplot(glass, diag_kws={'color':'red'})

变量之间的成对关系

如果我们仔细观察上面的图,我们会发现玻璃属性位于 x 轴和 y 轴上。沿着红色的对角线,我们可以看到显示分布的直方图。

如您所见,并非所有属性都遵循钟形曲线的正态分布,但事实上,大多数属性都偏向较低值(如 Ba、Fe)或较高值(如 Mg)。为了检测单变量异常值,我们应该关注单个属性的分布,并找到远离该属性大部分数据的数据点。例如,如果我们选择“Na”并绘制一个箱线图,我们可以找到哪些数据点在胡须之外,可以标记为异常值。

Na 的箱线图—显示晶须外部的数据点。

相反,为了检测多元异常值,我们应该关注 n 维空间中至少两个变量的组合。例如,在玻璃数据集中,我们可以使用玻璃的所有八个属性,并在 n 维空间中绘制它们,并通过检测哪些数据点落在远处来找到多元异常值。

由于绘制超过三个维度是不可能的,我们应该找到一种方法将八个维度转换到一个更低维度的空间。PCA —主成分分析是一种降维技术,它通过最大化低维表示中的方差来执行高维空间到低维空间的线性映射。我们可以通过用n_components=3.执行 PCA 将八个维度转换成三维空间

具有 3 种成分的 PCA 可视化(颜色代表玻璃的“类型”)

您可以在图中看到,有些数据点彼此靠近(构成密集区域),有些远离,可能是多元异常值。

我们需要遵循不同的程序来检测单变量和多变量异常值。让我们从单变量异常值开始,学习标准差和四分位距方法来检测它们。

单变量异常检测

1.标准偏差法

假设一个变量(几乎)是正态分布的。在这种情况下,其直方图应遵循钟形曲线,68.3%的数据值位于平均值的一个标准偏差内,95.4%的数据值位于平均值的两个标准偏差内,99.7%的数据值位于平均值的三个标准偏差内。

因此,如果数据点距离平均值超过三个标准差,我们就可以检测出异常值。

通过使用标准偏差技术,我们根据“Na”变量的分布(这是一个极值)删除了两个记录。您可以扩展并覆盖所有其他属性,以移除单变量异常值。

Shape of original dataset: (213, 9) 
Shape of dataset after removing outliers in Na column: (211, 9)

2.四分位极差法

四分位距方法(最好使用箱线图显示)通过定义三个点将数据分成四分位:

四分位数 1 (Q1)代表第 25 百分位
四分位数 2 (Q2)代表第 50 百分位
四分位数 3 (Q3)代表第 75 百分位

箱线图中的方框表示 IQR 范围,其被定义为 Q1 和 Q3 之间的范围;IQR = Q3 — Q1和低于Q1 - 1.5*IQR或高于Q3 + 1.5*IQR的数据点被定义为异常值。

在方框图中,Q1 - 1.5*IQRQ3 + 1.5*IQR用触须表示,异常值用上方或下方的点表示。

Shape of original dataset: (213, 9) 
Shape of dataset after removing outliers in Na column: (206, 9)

通过使用 IQR 技术,我们根据“Na”变量的分布删除了七个记录。正如你可以注意到的,标准差方法只能找到 2 个异常值,这是真正的极值点,但使用 IQR 方法,我们能够检测到更多(5 个不那么极端的记录)。由您和您的用例来决定哪种方法对数据集更好,以及您是否有空间丢弃更多的数据点。

让我们继续讨论多元异常值,了解隔离森林、DBSCAN(含噪声的基于密度的应用程序空间聚类)和 LOF(本地异常值因子)。

多元异常检测

1.隔离森林法

隔离森林是一种基于随机森林的无监督机器学习算法。你可能知道,随机森林是一个集成学习模型,它使用基本模型(比如 100 棵决策树)和在最终决策中权重较高的模型的集成。

如果你需要一个关于集合学习的复习,你可以看看这篇文章。

隔离林遵循随机林的方法,但相反,它检测(或换句话说隔离)异常数据点。为了做到这一点,它做了两个基本假设:离群值是少数,他们是不寻常的。

隔离林通过随机选择一个功能,然后随机选择一个拆分规则来分隔所选功能的值,从而创建决策树。这个过程一直持续到达到设定的超参数值。然后,隔离林考虑如果树更短并且分区更少,则相应的值是异常值(少数和不寻常)。

孤立森林异常检测图解

让我们看看使用来自 scikit-learnIsolationForest类的实现。

from sklearn.ensemble import IsolationForestIsolationForest(*n_estimators=100*, *max_samples='auto'*, *contamination='auto'*, *max_features=1.0*, *bootstrap=False*, *n_jobs=None*, *random_state=None*, *verbose=0*, *warm_start=False*)

隔离森林算法有几个超参数,例如用于集成的基本估计量数量的 n_estimators 、用于训练模型的样本数量的 max_samples 、用于定义数据中异常值比例的 contamination 、用于从数据中提取用于训练的特征数量的 max_features 。其余的可以看文档

我们通过将基本模型的数量设置为 100,将最大特征的数量设置为特征的总数,将污染设置为'auto'来启动隔离林,该隔离林使用原始文件中确定的偏移和污染阈值。如果污染为 0.1,那么数据集的 10%将被定义为异常值。

通过调用glass['outlier'].value_counts(),我们可以看到有 19 条记录被标记为-1——异常值,其余 195 条记录被标记为1——非异常值。

如前所述,我们可以通过主成分分析将特征数量减少到三个分量来可视化异常值。

隔离林检测到异常值(黄色)(n 估计值=100,污染=“自动”)

为了保持内容的重点,我不会展示如何调优超参数,但是如果您感兴趣,可以查看本文。

2.基于密度的含噪声应用空间聚类(DBSCAN)方法

DBSCAN 是一种流行的聚类算法,通常用作 K-means 的替代算法。它是基于密度的,这意味着它侧重于许多数据点所在的高密度区域。它通过测量数据之间的特征空间距离(即欧几里德距离)来执行空间聚类,以识别哪些可以被聚类在一起。它允许有噪声的应用,这意味着我们可以对有噪声的数据使用 DBSCAN。但这还不是全部,DBSCAN 的最大优势之一是我们不需要预先定义集群的数量。

让我们看看使用 scikit-learn 中的DBSCAN类的实现。

from sklearn.cluster import DBSCANDBSCAN(*eps=0.5*, *min_samples=5*, *metric=’euclidean’*, *metric_params=None*, *algorithm=’auto’*, *leaf_size=30*, *p=None*, *n_jobs=None*)

DBSCAN 有几个超参数,例如用于在同一个聚类中考虑的两个数据点之间的最大距离的EPS(ε)、用于被认为是核心点的点的接近数据点的数量的 min_samples 、用于计算点之间距离的度量。其余的可以看文档

启动 DBSCAN 时,仔细选择超参数非常重要。例如,如果 eps 值选择得太小,那么大多数数据可以被归类为异常值,因为邻域面积被定义为更小。相反,如果 eps 值选择得太大,则大多数点可以聚集在一起,因为它们可能位于相同的邻域中。这里,我们使用k-距离图选择 eps 为 0.4。

k-距离图,其中大部分数据距离第 10 个最近的邻居不超过 0.4 个单位

此外,min_samples 是一个重要的超参数,通常等于或大于 3,大多数情况下选择为 D+1,其中 D 是数据集的维数。在我们的示例中,我们将 min_samples 设置为 10。

因为 DBSCAN 通过密度来识别聚类,所以高密度区域是聚类发生的地方,低密度区域是异常值发生的地方。通过调用glass['outlier'].value_counts(),我们可以看到有 22 条记录被标记为-1——异常值,其余 192 条记录被标记为1——非异常值。

我们可以使用主成分分析来可视化异常值。

DBSCAN 检测到异常值(黄色)(eps=0.4,min_samples=10)

3.本地异常因素(LOF)

LOF 是一种流行的无监督异常检测算法,它计算数据点相对于其邻居的局部密度偏差。计算后,密度较低的点被视为异常值。

让我们看看使用来自 scikit-learnLOF类的实现。

from sklearn.neighbors import LocalOutlierFactor*LocalOutlierFactor(n_neighbors=20, algorithm='auto', leaf_size=30, metric='minkowski', p=2, metric_params=None, contamination='auto', novelty=False, n_jobs=None)*

LOF 有几个超参数,如 n_neighbors 用于选择默认等于 20 的邻域数,以及contaminance用于定义异常值的比例,它可以等于(0,0.5)范围内的'float'或使用原始论文中确定的偏移和污染阈值的'auto'

通过调用glass['outlier'].value_counts(),我们可以看到有 34 条记录被标记为-1——异常值,其余 180 条记录被标记为1——非异常值。

最后,我们可以使用主成分分析来可视化这些异常值。

LOF 检测到异常值(黄色)(n_neighbors=20,污染=“自动”)

结论

在本文中,我们探索了不同的方法来检测数据集中的异常值。我们从单变量异常值检测技术开始,涵盖了标准差和四分位间距方法。我们对玻璃鉴定数据集中的“Na”列执行了这些方法。然后,我们转向多元异常检测技术,涵盖了隔离森林、DBSCAN 和局部异常因素。通过这些方法,我们学会了如何使用特征空间中的所有维度来检测异常值。除了异常值检测,我们还学习了如何使用 PCA——一种降维技术来可视化 n 维数据。

在介绍这些方法时,我们没有花太多时间调优超参数。然而,超参数调整是 ML 模型开发中的一个重要步骤。如果你想学习或者刷新一下知识,可以看看下面这篇文章。

我希望您喜欢阅读异常值检测,并发现这篇文章对您的工作有用!

如果你喜欢这篇文章,你可以在这里阅读我的其他文章 关注我上媒如果您有任何问题或建议,请告诉我。✨**

喜欢这篇文章吗? 成为会员求更!

参考

  1. 隔离森林由 Scikit-Learn
  2. 通过 Scikit-Learn 进行数据库扫描
  3. sci kit-Learn 的局部异常因子
  4. 本杰明·沃罗斯Unsplash 拍摄的标题照片
  5. 所有其他图片均由作者提供
  6. 来自 Kaggle 的玻璃鉴定数据集作者:Dua,d .和 Graff,C. (2019)。UCI 机器学习知识库[http://archive . ics . UCI . edu/ml]。加州欧文:加州大学信息与计算机科学学院。

使用图像散列函数检测重复图像

原文:https://towardsdatascience.com/detection-of-duplicate-images-using-image-hash-functions-4d9c53f04a75

使用 Python 库 undouble 自动搜索(几乎)相同的照片

在整个系统中搜索(几乎)相同的照片可能是一项繁琐的工作;你需要在成千上万的照片中点击,然后决定每张照片是否是一张【相似】的照片。检测重复的最直接的方法是根据文件大小或文件名。然而,照片通常来自不同的来源,如移动设备、社交媒体应用程序,这导致了文件大小、名称以及分辨率、缩放比例、压缩和亮度的差异。 哈希函数 由于对微小变化的鲁棒性,是检测(接近)相同照片的理想选择。我将总结一下散列函数的概念,随后是一个实践教程,演示在最小化误报冲突的同时检测重复的步骤。所有结果都是使用 Python 库 undouble 导出的。

乔恩·泰森在 Unsplash 上的照片

如果你觉得这篇文章很有帮助,可以使用我的 推荐链接 继续无限制学习,并注册成为中级会员。另外, 关注我 关注我的最新内容!

视觉相似但数值不同。

两幅图像可能在视觉上相似,但在数字上不同。数字差异可能由各种原因造成,例如使用社交媒体应用程序可能会改变亮度、对比度、伽马校正、压缩、分辨率和/或缩放比例。例如,使用 Whatsapp 发送图像会导致分辨率降低(图 1)。请注意,减少量可能因设备和用户定义的设置而异。

图一。视觉上相似但数字不同的照片。a .最左边是源图像。b .中间是用 Whatsapp 发完之后。c .右图像是两个图像之间的差异。路易·菲利浦·波伊特拉斯在 Unsplash 上拍摄的照片

从视觉角度来看,很难看出Whatsapp 图像之间的任何变化,但是当我们减去这两幅图像时,差异就变得很明显了(图 1C)。如果你的磁盘上只有几幅图像需要确认的话,选择分辨率最好的图像是很容易的。然而,当你偶尔或者可能每年将你所有的图像转储到磁盘时,这就成了一项耗时的任务,当家人/朋友/同事也与你分享他们几乎相似的时刻时,这就更具挑战性了。

问题不是你有没有重复的照片,而是它们在哪里。

像按文件大小排序或图像相减这样的方法就会失败。尽管有解决方案; 哈希函数! 哈希函数对亮度、对比度、伽马校正、压缩、缩放和/或分辨率的微小变化具有鲁棒性,因此非常适合检测(接近)相同的图像。散列函数的使用有很多应用,例如在数字取证版权强制中,以及更一般的用于磁盘空间减少并因此取消发布。

可撤销库搜索重复的图像。

可撤销可用于检测整个系统或任何输入目录中的相同图像。给定输入数据,对图像执行预处理,计算散列,并基于图像散列对图像进行分组。为了在不执行手动操作的情况下组织磁盘上的图像,移动功能将移动相同的图像,但分辨率最大的图像除外(该图像被复制)。所采取步骤的示意图如下所示。

在 undouble 中根据图像哈希对图像进行分组所采取的步骤的示意图。图片来自作者

在接下来的部分中,我将更详细地描述预处理步骤、散列函数、分组结果和绘图功能,比如图像散列。

图像哈希函数。

哈希函数将输入数据转换或映射为固定长度的字符串,可以看作输入数据的“指纹““签名”图像哈希。因此,一个好的散列函数应该完全由输入数据决定,或者在我们的例子中,由图像决定。哈希函数有很多种,比如;平均哈希、感知哈希、差分哈希、Haar 小波哈希、Daubechies 小波哈希、HSV 颜色哈希、抗裁剪哈希。每个哈希函数都有特定的属性,使其能够抵抗某些变化,如亮度、对比度、伽马、校正、水印、压缩、缩放和灰度变化

图像哈希函数将图像映射到称为图像哈希的短字符串,可用于图像认证或用作数字指纹。

然而,两个视觉上不同的图像 得到相同的图像散列,这被称为 碰撞 ,这是可能发生的。使用 101 objects 数据集展示了不止一个示例,但是让我们开始评估最著名的哈希函数的健壮性。通过改变单幅图像的亮度(-50%、-20%、+20% +50%)、对比度(-50%、-20%、+20% +50%)、缩放和压缩(png、jpg) 来评估鲁棒性。总共为“猫和狗”图像(图 2)创建了 10 种不同的变化。请注意,这不会评估冲突,而是评估不同哈希函数的亮度和对比度的影响。所有哈希函数都使用 python 库 undo double进行评估,该库依次使用图像哈希库image hash【3】的功能。

图二。亮度和对比度变化的四个例子。路易·菲利浦·波伊特拉斯在 Unsplash 上拍摄的照片

计算哈希之前的预处理。

在我们能够确定图像散列之前,需要预处理步骤; 1。脱色,2。归一化像素值,以及 3 .缩放图像。脱色的原因是“识别”图像所需的信息很容易出现在灰度通道中。此外,从 RGB 的每像素 24 位减少到每像素 8 位在计算上更有吸引力;在时间和记忆方面。下一步是将图像向下采样/缩放到更小的尺寸。大多数情况下,选择 64 位哈希,这仅仅意味着图像被下采样为 8 x 8 像素。下面是一个示例,展示了如何使用 Python 为各种哈希函数导出图像哈希。

pip install undouble

演示计算图像散列和创建绘图的示例。

平均哈希。

在脱色和缩放步骤之后,将每个像素块与图像的所有像素值的平均值(顾名思义)进行比较。在下面的例子中,我们将生成一个 64 位散列,这意味着图像被缩放到 8×8 像素。如果像素块中的值大于平均值,则得到值 1(白色),否则得到值 0(黑色)。图像散列是通过将二进制数组展平成向量来创建的。

图二。镜像哈希二进制:11111111111101111110011100000011000000011111000011111111111。图片由作者提供。

图 3。10 个更改图像的平均哈希。

如果我们对 10 个被修改的图像运行平均哈希函数,我们可以看到图像哈希非常稳定,只有微小的差异(图 3)。只有在亮度增加 50%的情况下,图像哈希才开始出现偏差。在所有组合中,图像散列平均有 2.1 个变化。

感性杂凑。

感知散列函数仅从图像脱色开始,然后应用离散余弦变换(DCT );首先是每行,然后是每列。高频像素现在被裁剪为 8 x 8 像素。然后将每个像素块与图像的所有灰度值的中值进行比较。如果像素块中的值大于中值,则得到值 1,否则为 0。最终图像散列之后是将二进制数组展平成向量。

图 4。感性-哈希:111010101100011010100110110111011001000110110000011011011000001010。图片由作者提供。

图 5。感知对于 10 个被改变的图像。图片由作者提供。

如果我们对 10 张图像运行感知哈希函数并进行一些修改,就可以检测到图像哈希中的各种微小差异(图 5)。在亮度增加 50%的情况下,图像散列开始更加偏离。在所有组合中,图像散列平均有 4 次变化。

差异哈希。

在脱色和缩放步骤之后,像素被连续地(每行从左到右)与其右边的相邻像素进行比较。如果位置 x 处的字节小于位置(x+1)处的字节,则得到值 1,否则为 0。最终图像散列之后是将二进制数组展平成向量。

图 6。镜像哈希:000111100001100110011001100011000010010100010001000010110000001。图片由作者提供。

图 7。10 张更改图像的差异哈希。图片由作者提供。

如果我们对 10 张图像运行差异哈希函数,并做一些修改,图像哈希会产生一些小的变化(图 7)。50%亮度的增加显示了最大的差异。在所有组合中,图像散列平均有 3.8 次变化。

哈尔小波哈希。

在脱色和缩放步骤之后,对图像应用二维小波变换。然后将每个像素块与图像的所有灰度值的中值进行比较。如果像素块中的值大于中值,则得到值 1,否则为 0。最终的图像散列之后是将数组展平成向量。

图 8。镜像哈希:111001111110110111001110000001000000010000001110000011111111101。图片由作者提供。

图 9。 haar 小波哈希用于 10 幅修改后的图像。图片由作者提供。

如果我们比较 10 个被改变的图像的图像散列,我们可以看到 haar 小波散列是相当稳定的,尽管在被改变的图像上有一些微小的变化。在所有组合中,图像散列平均有 2.5 个变化。

在真实数据集中查找相同的图像。

为了演示各种散列函数在真实数据集上的性能,我使用了 加州理工学院 101【2】数据集,并将其保存到我的本地磁盘。针对 aHash、pHash、dHash、小波哈希检查重复的检测。**加州理工数据集包含 9144 张属于 101 个类别的真实世界图像。大约 40 至 800 每类图像。每个图像的大小大约为 300 x 200 像素。 可撤销库 的输入可以简单地是存储所有图像的目录位置。所有子目录也将被递归分析。请注意,该数据集不包含相同图像的地面实况标签。因此,我将直观地检查分组的所有结果,并描述每个散列函数的结果。

*pip install undouble*

在 101 对象数据集中检测具有相同图像散列的图像的示例。

平均哈希 功能检测到 135 个组,这些组可以链接到具有相同哈希(阈值=0)的 335 个图像,基于输入哈希大小 8 (64 位)。尽管检测到相同的图像,但大多数组显示了碰撞,如左上角和左下角,和/或几乎相同的图像,如摩托车(图 11)。通过将散列大小增加到 16 (256 位),检测到 64 个图像的 28 个组。 没有碰撞出现,但是检测到近乎相同的图像 ,比如摩托车。

图 11。平均哈希在 335 个具有相同哈希的图像中检测到 135 个组。碰撞和几乎相同的图像出现在几乎所有的组中。图片由作者提供。

小波哈希 功能检测到 141 个组,根据输入哈希大小 8 (64 位),这些组可以链接到具有相同哈希(阈值=0)的 513 个图像。目测显示,几乎所有组都包含碰撞或几乎相同的图像(图 12)。谁知道草莓会有和摩托车相似的图片呢?通过将散列大小增加到 16 (256 位),检测到 51 个图像的 25 个组。 未出现碰撞,但检测到近乎相同的图像 ,如摩托车。

图 12。小波哈希检测到 513 个具有相同哈希的图像的 141 组。几乎每一组都包含了一幅几乎完全相同的图像。图片由作者提供。

差分哈希 功能检测到 28 个图像可以链接到具有相同哈希(阈值=0)的 31 个图像。视觉检查显示没有碰撞,但检测到几乎相同的图像(两辆摩托车)。通过将散列大小增加到 16 (256 位),检测到 16 个图像的 8 个组。没有碰撞出现,但只有一些几乎相同的图像,如摩托车。通过将散列大小增加到 16 (256 位),检测到 16 个图像的 8 个组。 没有碰撞,也没有检测到近似相同的图像 ,所有图像在视觉上都是相似的。

图 13。差分哈希检测到 28 组 31 个图像具有相同的哈希。没有观察到碰撞,只有一个图像几乎相同(摩托车)。图片由作者提供。

感知哈希 功能检测到 38 个组,这些组可以链接到具有相同哈希(阈值=0)的 41 个图像。视觉检查显示没有碰撞,但是检测到几乎相同的图像,例如摩托车,如图 14 所示。通过将散列大小增加到 16 (256 位),检测到 20 个图像的 10 个组。 没有检测到碰撞和几乎相同的图像 ,所有图像在视觉上都是相似的。

图 14。感知散列检测到具有相同散列的 41 个图像的 38 个组。没有观察到碰撞,但是在 4 组中出现了几乎相同的图像。图片由作者提供。

结论。

猫和狗图像实验的情况下,每个哈希函数的结果都相当稳定。然而,当我们使用真实世界的数据集时,很明显,散列大小为 8 (64 位)会导致平均散列小波散列的许多冲突。此外,它还集合了许多近乎相同的图像。差分散列的结果是准确的,但也是最保守的。另一方面,**的感知哈希也显示了准确的结果,但不那么保守。**当哈希大小增加到 16 (256 位)时,没有哈希函数导致冲突,但是对于平均哈希小波哈希检测到几乎相同的图像。感知哈希差分哈希都没有显示冲突或几乎相同的图像。这里再次说明,感知哈希不那么保守。

感知哈希对于重复图像的检测最为准确。**

此外,结果还可能取决于输入图像的类型。当图像有一个坚实的背景,如摩托车和交通标志,碰撞会更经常发生。原因之一是二进制像素信息表征了图像,并且形成的图像散列变得不那么唯一。例如,使用平均哈希小波哈希可以很容易地得到一个相同的图像哈希。

哈希函数还是聚类?

哈希函数和聚类方法旨在对相似的图像进行分组,但还是有区别的。哈希函数将创建图像的“指纹“或“”签名“”;图像哈希。两个相同的图像散列应该代表两个相同的图像。在聚类的情况下,提取特征,选择距离度量和链接类型,最后,图像被聚类。采取不同的步骤和/或方法将产生不同的分组,因为它隐含地对数据施加了结构,从而对样本进行了划分。或者换句话说,有更多的自由度来定义“相似”图像。

哈希函数旨在创建可用于检测相同图像的唯一签名,而聚类方法旨在检测相似图像组。

为了比较散列函数和聚类得出的结果,我将使用相同的 101 对象数据集并执行聚类方法。 clustimage 库【5】用于对仅给出路径位置作为输入的图像进行聚类。子目录中的所有图像都被递归收集。使用默认设置和 PCA 方法,检测到 63 个最佳聚类(图 15 ),其中显示了聚类的质心图像。尽管一些聚类包含具有高相似性的图像(图 15D),但是图像不一定是相同的或接近相同的。如果你想了解更多关于图像聚类的知识,请阅读这篇博客。

对 101 对象数据集进行聚类的示例。

图 15。Caltech101 数据集[2] 下载链接。a .轮廓分数在 63 个聚类处检测到最佳值。b .使用检测到的聚类标签对样本进行着色的 tSNE 嵌入。c .并非所有聚类都产生高度相似的图像。在群集 13 中检测到的图像主要是钢琴的图像。图片由作者提供。

最后的话。

我提到了用于检测(几乎)相同图像的散列函数的概念。 建议使用感知哈希函数 检测重复图像。使用 undouble可以相对简单地检测整个系统或目录中(几乎)相同的映像。它管道化了图像预处理(灰度化、归一化和缩放)、计算图像散列以及基于图像散列差异对图像进行分组的过程(图 10)。阈值为 0 将对具有相同图像哈希的图像进行分组。然而,当取消发布我的个人照片库时,阈值 10 显示了最佳结果,因为照片(如连拍的照片)也被分组。使用绘图功能可以轻松查看结果,使用移动功能可以显示图像。在移动图像的情况下,具有最高分辨率的组中的图像将被复制,并且所有其他图像被移动到“undouble”子目录中。如果您需要更多关于图像分组的灵活性和自由度,我推荐您阅读这篇关于图像聚类的博客【4】,它描述了使用 clustimage 库【5】进行图像聚类。

注意安全。保持冷静。

欢呼 e .

如果你觉得这篇文章很有帮助,可以使用我的 推荐链接 继续无限制学习,并注册成为中级会员。另外, 关注我 保持我的最新内容!

软件

我们连线吧!

参考

  1. Taskesen,E. (2022)。检测(接近)相同图像的 Python 库。【电脑软件】。
  2. 长度飞飞,r .弗格斯和 p .佩罗娜。从少数训练实例中学习生成视觉模型
    :在
    101 个对象类别
    上测试的增量贝叶斯方法。
    IEEE。CVPR 2004,基于生成模型
    的视觉研讨会。2004
  3. https://github.com/JohannesBuchner/imagehash
  4. Taskesen,E. 图像聚类的分步指南。 走向数据科学,2021。
  5. Taskesen,E. (2021)。 Python 包 clustimage 用于图像的无监督聚类。【电脑软件】。https://towards data science . com/a-step-by-step-step-guide-for-clustering-images-4b 45 f 9906128

确定正确的 LDA 主题模型大小,第二部分

原文:https://towardsdatascience.com/determining-the-right-lda-topic-model-size-part-ii-ff4312e9fd9

UnsplashKier In Sight 的照片

D 尽管有大量关于 LDA 主题模型实现的博客帖子和文章,但很少有人深入研究开发人员必须做出的最重要、最神秘的决策:选择要建模的主题数量。本文及其配套文章 使用指标来确定正确的 LDA 主题模型大小 ,提供了一个深入的示例,说明如何系统地使用指标来确定典型项目的最佳主题模型大小。

迄今为止我们的英雄…

总结第一篇文章:手头的任务是创建一个 LDA 主题模型,普通读者可以使用它将 30,000 篇新闻文章分类到大致有意义的类别中。在练习的第一部分,我们评估了 30 个题目,从 5 个到 150 个,每 5 个一步。每个模型运行三次(以平均出 LDA 算法的随机特征中固有的统计噪声),并且每次运行运行九个度量:两个基于 PMI 的度量,CV 和 NPMI,以及评估每个模型的顶部主题模型词的七个度量。那些对语料库的细节感兴趣的人——从 Kaggle 上提供的新闻文章的更大集合中随机选择的 30,000 篇文章子集,以及评估的第一步和其他技术细节,应该回顾一下旧的文章。本文继续从那里开始的过程,用主题和文本本身的样本来补充基于度量的方法。

在第一步中,使用九个指标来确定一组候选的主题大小——5、10、20、35、50 和 80。对结果模型的粗略检查导致了 5、50 和 80 主题模型的快速移除。上一篇文章到此结束。在这里,我们将继续评估并添加三个模型尺寸,以更清楚地了解不同模型尺寸的分步效果。以下评估的型号适用于 10、20、30、35、40 和 45 号尺码。

主题和拟合采样

为了进行语义分析,180 个主题中的每一个都用 1-3 的标准进行了评级,包括两个标准:内聚性和可理解性。内聚性是对单词如何一起工作的判断,可理解性是衡量前十个单词是否一起工作以形成容易理解的想法、主题或描述。得分为“1”的主题群被认为是不连贯的或可理解的。得分为“2”的主题被认为“有些”连贯或可理解,得分为“3”的主题似乎“大部分”或更好地代表了各自的标准。一个相似的尺度被应用到 1800 个随机选择的文档中,六个模型中的每一个从每个主题中选择 10 个。

在每个模型中,主题连贯性和可理解性的平均得分是接近的。在这里,正如我们将在下面看到的,20 和 45 模型得分很高。主题评估是 30 模型与 20 和 45 模型竞争的唯一量度。

图片作者。

虽然测量话题的衔接性和可理解性是评估话题模型输出的一种常用方法,但在这种情况下,小样本量和模型之间的相对差异导致作者不愿意根据这些结果得出明确的结论。

评估的下一个度量是特定主题词簇代表单个样本文档的语义的程度。这里 24 和 45 型号做得最好。

图片作者。

为了获得这一指标,总共对 1800 份文件进行了单独审查和评分。在这个指标中,十个主题的模型表现最差,大概是因为它的主题太少,不足以将大量的文档分组。虽然这些结果总体上,像主题模型衔接/可理解性测量,似乎有很小的差异,但样本量要大得多。

分布可以提供线索

该数据的另一个视图是一个箱线图,它将给出按主题的平均分数分布的某种意义。该图显示了 20 个模型主题的分布更加均匀,即使其平均分数略低于 45 个模型的平均分数。

图片作者。

虽然每个文档的评估是主题模型评估的黄金标准,尽管事实上 45 个主题模型比 20 个主题模型得分更高,但我们必须考虑到这个分数可能在统计上有偏差。这与最初决定如何选择样本有关。每个文档的评估不仅是为了测量整体的模型性能,也是为了更深入地了解每个主题是如何表现的。因此,通过选择从每个主题中抽取 10 个文档,文档总数(45 个主题模型为 450 个,20 个主题模型为 200 个)在统计上并不相等。虽然在较大的模型中采样了 1.5%的文档,但在较小的模型中采样了不到 0.7%的语料库。此外,因为主题本身并不是均匀地分布在文档中(在 45 模型中的一些主题群非常大/小),所以结果可能在统计上进一步受到影响。

支配和适应

可以通过将拟合分数与我们知道在统计上更可信的数据相关联来利用拟合分数,并从抽样问题中恢复。由于我们有 30,000 个文档中每个文档的主要贡献值(给定文档的单个最可能的主题),因此可以对每个主题的这些值进行平均,然后将它们与每个主题的平均文档适合度评级进行比较:

图片作者。

该图显示了对于语料库中 30,000 个文档中的每一个,文档适合度分数和 LDA 生成的主导主题值之间的正相关。在该图中,每个主题的平均适合度与平均优势可能性进行了比较。趋势线显示正相关。

这种观点暗示了主导主题值在决定模型的语义方面是很重要的。因为我们对 30,000 个文档中的每一个都有值,所以抽样问题不重要。虽然此图显示 Ten 模型的表现比其他模型好得多,但我们已经知道它测量的是过度拟合数据,可以将其排除。通过按主题绘制总体优势值,我们可以看到 20 个主题的模型比更大的模型有明显的优势:

作者图片

将这些数据考虑在内,并与所有其他指标进行权衡,Twenty 模型似乎最适合我们的目的。

结论

这项工作已经完成了一个完整的循环,并返回到由两个基于 PMI 的指标确定的 20 个主题模型。这些度量标准越来越受欢迎,因为它们被认为收敛于与人类主题模型判断相匹配的主题大小。有趣的是,在这种情况下,主导主题值和模型的语义之间似乎存在关联。十主题模型在这方面是一个异常值——它有很高的优势值,但不是一个好的主题大小。我们可能会猜测,只要主题本身有意义,优势值就是相关的。百分之百确定文档中存在无意义的主题簇不是一个好的结果。另一方面,如果我们有一个有很好语义的主题聚类,我们会期望优势值会与语义相关。有趣的是,至少在这种情况下,基于非 PMI 的指标在确定最佳主题大小方面表现不佳。希望这里描述的过程增加了您对确定特定项目的最佳主题模型大小所涉及的问题的理解,并为您提供了可以用来改进您自己工作的想法。

DETR:使用变压器的端到端对象检测和 Python 实现

原文:https://towardsdatascience.com/detr-end-to-end-object-detection-with-transformers-and-implementation-of-python-8f195015c94d

变压器架构的使用不仅在速度方面提供了优势,而且在一些特定类型的对象检测问题方面也提供了优势

在脸书的研究团队发表的“DETR:用变形金刚进行端到端的物体检测”论文中,最新的变形金刚技术被用于物体检测问题。这种算法比传统的目标识别技术有许多优点。通过使用这种算法,在本文的后面阶段已经用 python 解决了一个示例对象检测问题。

托古列夫的 Unsplash

通过在目标检测问题中使用不同的方法,已经产生了许多解决方法。在第一种方法中,存在由用于检测目标对象的分类和回归阶段组成的两阶段架构。在第一阶段,使用选择性搜索或区域提议网(RPN)来生成区域提议。之后,执行分类和回归过程。R-CNN、快速 R-CNN 和更快 R-CNN 是该架构最知名的算法。虽然这些算法的准确率很高(特别是对于微小的物体),但在速度上并没有达到理想的水平。在另一种方法中,目标检测在单个阶段中完成。这种方法不使用选择性搜索或 RPN。存在用于对象检测过程的单个神经网络模型。虽然这是一种比第一种方法快得多的技术,但它在检测小尺寸物体方面的性能相对较差。

2020 年,脸书研究团队在一篇名为“利用变压器进行端到端物体检测”的文章中介绍了一种新技术。据宣布,一种新的对象检测模型是使用变压器架构创建的,变压器架构通常用于 NLP(自然语言处理)解决方案。

图一。DETR 模式— 来源

DETR 架构基本上由三层组成(图 1)。

  • CNN 层用于从图像(主干)中提取特征
  • 变压器中的编码器-解码器结构
  • 涉及在预测对象和真实对象之间执行二分匹配的集合损失函数

在第一阶段,通过主干层从图像中提取特征图。可以使用许多不同的模型,例如 RestNet-50 或 ResNet-101。以这种方式,二维结构信息被保留。在接下来的阶段中,数据被展平,因此我们得到一个一维结构。在位置编码之后,它被传送到编码器-解码器机制。最后,每个输出被转发到前馈网络。最后一层由 3 个节点组成。在使用 Relu 激活函数的情况下,获得预测对象的归一化中心坐标以及对象的预测高度和宽度值。当在节点中使用 softmax 激活函数时,预测相关对象的类别。因此,不需要非最大抑制(NMS)。

抑制(NMS):它是物体识别算法的基本课题之一。在模型的预测期间,可以用多于一帧来估计目标对象。这些框架有些可能是倾斜的,有些可能太大。对于 NMS,在这些类型的帧中选择最合适的一个。联合值的交集用于此过程。

图 2 算法中使用的变压器架构— 来源

在位置编码部分,根据元素在数组中的位置重新创建每个元素(或 NLP 世界中的标记)的向量。因此,同一个字在数组中的不同位置可以有不同的向量。

在编码器层,执行高维特征矩阵到低维特征矩阵的缩减。它由每个编码器层中的多头自关注、规格化器和前馈网络模块组成。

在解码器层,有多头自关注,规范和前馈网络模块一样,编码器。n 个对象查询被转换为输出嵌入。在下一阶段,使用前馈网络执行最终估计过程。

注:由于将对变压器架构进行更全面的研究,因此这些章节很短。然而,如果您想获得有关变压器的更多详细信息,您可以访问相关文章https://arxiv.org/abs/1706.03762

变压器架构的使用不仅在速度方面,而且在对象检测问题的某些特定类型的问题方面提供了很大的优势。通过这种架构,根据对象检测算法的图像内容进行预测。因此,在图像中上下文很重要的情况下,使用这种方法可以获得更高的成功。当递归神经网络用于对象检测项目时,已经看到精度较低并且模型运行较慢。因为操作是连续的。由于这些操作是从变压器架构中并行执行的,因此我们得到了一个更快的模型。

Python 项目

项目中使用的数据集已经从 Kaggle 下载。这里可以访问。数据集在 Kaggle 上获得了麻省理工学院的许可。如果你想得到详细的信息,你可以使用这个链接

图三。来自 Kaggle 的样本数据集— 来源

在这个数据集中有不同类型的小麦(图 3)。本项目旨在正确检测这些小麦类型。存储库用于轻松完成训练和预测过程(https://github.com/ademakdogan/plant_detector)。下载数据集时,可以在图 4 中看到原始的标记数据。

图 4。原始标记数据

首先,这种标记结构应该根据存储库进行更改。由于这种改变,csv 文件中的列名应该分别为 image_name、page_width、page_height、x、y、width、height、labels 。csv 文件的转换版本可以在图 5 中看到。

图五。转换的数据

这里应该注意的是,图像名称及其扩展名被写在图像名称列中。事实上,这个存储库可以很容易地用于所有对象检测项目,其中可用数据可以以这种格式进行更改。编写一个只将数据转换为上述格式的转换器就足够了。

1-数据准备

获得的数据必须适应 DETR 算法。以下代码可用于此目的。

对于培训:

*python data_preparation.py -c /Users/.../converted_train.csv -i True*

用于测试:

*python data_preparation.py -c /Users/.../converted_test.csv -i False*

运行名为 data_preparation 的 python 脚本后,在名为 /data/json_files 的文件夹下创建了 custom_train.jsoncustom_test.json 文件。如果在文件的创建中没有问题,则开始训练阶段。

2-培训

训练也可以简单地用下面的代码来完成。在开始训练之前,可以根据需要更改 config.json 文件中的参数。

*python train.py -n <train_image_folder_path> -t <test_image_folder_path>*

作为训练过程的结果,获得了以下结果。

平均精度(AP)@[IoU = 0.50:0.95 | area = all | maxDets = 100]= 0.326
平均精度(AP)@[IoU = 0.50 | area = all | maxDets = 100]= 0.766
平均精度(AP)@[IoU = 0.75 | area = all | maxDets = 100]= 0.229
平均精度(AP)@[IoU = 0.56 = 0.410
平均召回率(AR)@[IoU = 0.50:0.95 | area = all | maxDets = 1]= 0.020
平均召回率(AR)@[IoU = 0.50:0.95 | area = all | maxDets = 10]= 0.161
平均召回率(AR)@[IoU = 0.50:0.95 | area = all | maxDets = 100]= 0.465

当分析结果时,可以清楚地看到,由于 epoch 低,成功率不是很高。很明显,当 config.json 中的 max_steps 参数增加时,将获得更高的精度。但是,根据硬件功率的不同,训练时间将会增加。培训也可以通过 docker 完成。

*make docker*

使用上面的命令在项目的主目录中创建了一个新的 docker 映像。默认情况下,这个 docker 图像的名称是“detr”。如果希望更改图像名称,可以使用 makefile。自动完成必要的安装后,使用以下命令开始培训过程。

*make docker_run v=<full_path_of_the_project> n=<train_image_folder_path> t=<test_image_folder_path>*

这个过程的结果是,在 model 文件夹下创建了一个名为 model.ckpt 的模型文件。之后,使用该模型执行预测过程。

3-预测

使用作为训练结果获得的模型,可以创建许多不同的预测场景。以下命令可用于项目中的预测用法示例。

*python prediction.py -p /Users/..../test/sample.jpg*

结果如下图所示。

图六。示例图像预测

结论

本文分析了基于变压器(DETR)的端到端目标检测方法,并与其他目标检测方法进行了比较。给出了关于构成该体系结构的各层的一般信息。第一阶段使用 ResNet 架构提取特征。在第二层中,在使用二分匹配技术计算损失值之后,将变换层用于编解码机制。DETR 速度很快,因为它具有并行处理能力,并且不使用锚盒和 NMS 等限制性技术。此外,在图像中的内容很重要的情况下,它比其他对象检测架构更强大。为了给这种情况树立一个榜样,用全球小麦检测数据集做了一个样本项目。要检测的对象通常是相互关联的;因此,尽管历元数量很少,该模型也能够进行检测。

示例项目使用了 Python。代码是共享的,因此这个架构可以更容易地使用。这些代码可在 https://github.com/ademakdogan/plant_detectorhttps://github.com/ademakdogan/plant_detector***获得。从 Kaggle 下载的数据集 (MIT-licensed)用于我们的示例项目。本项目对数据预处理、训练和预测阶段进行了详细说明。*****

不同的对象检测算法将在以后的文章中详细讨论。

****Github:https://github.com/ademakdogan

****领英:https://www.linkedin.com/in/adem-akdo%C4%9Fan-948334177/

参考

1尼古拉·卡里翁、弗朗西斯科·马萨、加布里埃尔·西纳伊夫、尼古拉·乌苏尼尔、亚历山大·基里洛夫、谢尔盖·扎戈鲁科。利用变压器进行端到端目标检测。2020

[2]https://github.com/facebookresearch/detr

3曹希鹏,彭远,,冯,牛昆.CF-DETR:用于端到端物体检测的粗到细转换器。2022

用 4 个简单的步骤开发一个对话式人工智能机器人

原文:https://towardsdatascience.com/develop-a-conversational-ai-bot-in-4-simple-steps-1b57e98372e2

了解如何使用 PyTorch transformers、FastAPI 和 Docker 创建聊天机器人

图片由作者提供。

目录

  1. 介绍
  2. 步骤 1:利用预先训练的模型
  3. 步骤 2:构建后端
  4. 步骤 3:构建前端
  5. 第四步:用 Docker 打包 app
  6. 结论
  7. 参考

介绍

对话式 AI 聊天机器人无疑是目前最先进的聊天机器人。这种聊天机器人混合使用自然语言处理(NLP)和人工智能(AI)来理解用户的意图,并提供个性化的响应。

使用专有数据训练(或微调)这种模型,使公司能够直接通过聊天窗口向客户提供提交保险索赔、升级数据计划、更改航班等多种方式。

在本帖中,我们将讨论:

  1. 如何使用预先训练好的 PyTorch 模型构建聊天机器人;
  2. 如何使用 FastAPI 和 Jinja 与模型对话;
  3. 如何使用 Docker 部署我们的定制模型?

步骤 1:利用预先训练的模型

让我们首先安装必要的 Python 包来构建和测试我们的新聊天机器人。

pip install --no-cache-dir transformers[torch] uvicorn fastapi jinja2 python-multipart

对于我们的聊天机器人,我们将使用来自微软的预先训练好的DialoGPT-large模型。为了加载这个模型,我们可以简单地对transformers中的AutoTokenizerAutoModelForCausalLM类使用from_pretrained方法。

要调用模型,我们需要:
1。encode用户消息使用tokenizer
2。generate使用model对象的机器人响应;
3。decode使用tokenizer的响应。

将下面的片段复制并粘贴到终端或笔记本电池上进行测试。

如果上面所有的库都安装正确,那么这段代码应该运行时不会出现错误或警告。如果是这样,应该会返回以下消息:

I'm good, you?

现在我们知道模型工作得很好,让我们将这两个片段包装在一个可重用的类中。

上面的代码片段有两个方法,一个用于加载模型(load_model),另一个用于在用户给定消息的情况下从机器人那里获得回复(get_reply)。注意,我们扩展了get_reply方法来考虑过去的聊天历史。此外,为了提高对话的一致性,我们调整了模型的top_ktop_ptemperature

你可以在这里找到完整的文件: chatbot/app/model.py

步骤 2:构建后端

既然我们已经理清了 chatbot 模型,下一步就是通过标准的 HTTP 方法使这个模型可用。为此,我们将在 FastAPI 应用程序中包装模型。

因为我们只对给定用户消息的模型的响应感兴趣,所以我们只需要实现一个端点(/)来获得来自聊天机器人的回复。

注意,我们在上面的 FastAPI 端点中使用了Form语法。当我们实现前端时,使用Form类型的理由将变得更加明显。要测试上面的代码片段,请运行以下代码:

如果一切顺利,您应该会得到下面的 JSON。

{'message': "I'm good, you?"}

你可以在这里找到完整的文件: chatbot/app/main.py

步骤 3:构建前端

我们的前端将由用户和机器人之间的简单对话组成,类似于 WhatsApp 或 Messenger。

我们将根据麻省理工学院的许可改编 bootsnipp.com 大学提供的 pablocorezzola 的原创作品,而不是从头开始构建对话。这里使用的用户和机器人头像图标是从 flaticon.com 免费获得的(在代码中的图标旁边和这个 post)⁴⁵.的末尾提供了对作者的鸣谢

对于这个应用程序,请确保您下载了CSS文件。你将不需要HTMLJS文件。在下面的结构中添加CSS文件,一个空的index.html和头像图标。

static/
  styles.css
  avatar_user.png
  avatar_bot.pngtemplates/
  index.html

在编辑模式下打开index.html文件,粘贴以下内容。这里有两条重要的线:

  • 第 8 行:将styles.css文件添加到 FastAPI 应用程序中;
  • 第 15 行:允许我们使用jinja TemplateResponse注入用户和机器人生成的必要对话框。

为此,我们还需要将下面的代码片段添加到我们的 FastAPI 应用程序中。

  • 第 5 行:允许 FastAPI 应用程序显示/static文件夹中的任何文件;
  • 第 9 行:告诉 Jinja 模板index.html文件在哪里。

最后,我们需要修改 FastAPI 端点来呈现对话框 HTML,而不是之前的 JSON 消息。该对话框将用于使用 Jinja 替换templates/index.html中的{{ chat|safe }}占位符。

为此,用下面的代码片段替换前面的端点(/)。

注意build_html_chat()函数还没有定义。该函数接受 3 个参数:

  • is_me:布尔定义给定消息是来自用户还是机器人;
  • text:用户/机器人消息;
  • time:处理消息的时间。

因为这个函数只是一个标准的 HTML 代码块,所以我不会在本文中涉及它。而是可以从以下链接(以及完整的前端脚本)获取:chatbot/app/html _ utils . py聊天机器人/app/templates/index.html

第四步:用 Docker 打包 app

最后一步是用 Docker 打包我们的新应用。这一步将允许我们在本地、专用服务器或云上部署我们的应用程序,而不需要任何额外的工作。

让我们从定义最终的应用程序结构开始,如下所示。因为我们还没有定义Dockerfile,所以简单地创建一个空白文件。

app/
  static/
    styles.css
    avatar_user.png
    avatar_bot.png
  templates/
    index.html
  main.py
  model.py
  html_utils.py
Dockerfile

Dockerfile必须包括:

  • 我们在步骤 1 中安装的 Python 库;
  • 上面列出的后端和前端app/文件;
  • 启动容器时将调用的入口点文件(main.py)。

要构建和运行容器,只需在终端上键入以下内容:

docker build . -t chatbot && \
  docker run -p 8000:8000 chatbot

如果 Docker buildrun命令按预期执行,以下输出应该是清楚的:

INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)

要测试 Docker 容器,请在您最喜欢的浏览器中打开一个新标签,并键入 http://0.0.0.0:8000/ 。然后,键入“嗨”。“你好吗,”在文本框中。

交叉手指,希望几秒钟后,您会看到两条消息。第一个是你刚刚输入的,第二个是机器人的回复。

用户输入“嗨”后,聊天机器人应用程序完成的例子。“你好吗,”

构建和运行这个应用程序所需的所有文件都可以在这里找到 main/chatbot/app

结论

智能聊天机器人的革命已经到来。这种高度复杂和强大的模型允许多个公司以方便和可扩展的方式提供不同的服务。

在这篇文章中,我介绍了构建你自己的聊天机器人的基本思想,从模型创建到后端和前端。请注意,这只是一个简单的例子,说明了如何实现一个简单的对话机器人,而不应该被用于任何超过说明。

如果你想知道更多关于如何以无服务器的方式部署这样的应用,看看我过去的文章"用 Amazon Lambda 和 API Gateway 构建无服务器 API"⁶"和"使用 AWS Lambda 和 EventBridge 部署" remindme " Reddit bot" ⁷.

加入我的邮件列表,我一发布新内容,你就能收到新内容!

如果你喜欢阅读这样的故事,并想支持我成为一名作家,可以考虑报名成为一名媒体成员。每月 5 美元,让你可以无限制地访问 Python、机器学习和数据科学文章。如果你使用我的链接注册,我会赚一小笔佣金,不需要你额外付费。

https://andrefsr.medium.com/membership

参考

1张等著《对话:会话回应生成的大规模生成性预训练》(2020),arXiv

[2]拥抱脸团队,“一个最先进的大规模预训练反应生成模型(dialog pt)”
https://huggingface.co/microsoft/DialoGPT-large

3pablocrezzola,“简单聊天”:Bootstrap 3.0 片段——在麻省理工学院许可下发布https://bootsnipp.com/snippets/y8e4W

4 Freepick, Bot 免费图标【https://www.flaticon.com/free-icon/bot_1786548?】
term = bot % 20 avatar&page = 1&position = 1&page = 1&position = 1&related _ id = 1786548&origin = search

【5】自由选择,男人自由图标https://www.flaticon.com/premium-icon/man_2202112?】
term =头像&page = 1&position = 2&page = 1&position = 2&related _ id = 2202112&origin = search

6 a .里贝罗。"用亚马逊 Lambda 和 API 网关构建无服务器 API"
https://towardsdatascience . com/Build-a-server less-API-with-Amazon-Lambda-and-API-Gateway-DFD 688510436

7 a .里贝罗。"使用 AWS Lambda 和 EventBridge 部署一个“RemindMe”Reddit Bot"
https://towardsdatascience . com/build-a-q-a-app-with-py torch-CB 599480 e29

开发和部署无服务器事件驱动的机器学习应用程序

原文:https://towardsdatascience.com/develop-and-deploy-serverless-event-driven-applications-using-zappa-flask-and-aws-f39b817a9937

如何使用 Zappa、Python、Flask 和 AWS 创建托管机器学习模型的应用编程接口(API)

目录:

1.无服务器介绍

2。AWS
3 上的 IAM 权限。开发烧瓶应用程序
4。用 Zappa
5 展开。用密钥保护 API

1.介绍

随着全球各地的公司开始扩大其数据科学和机器学习能力,通过无服务器计算部署端点的过程在最近几个月出现了大幅增长。根据最近一篇关于 DataDog 1的文章,大多数云提供商(AWS、GCP 等)的无服务器架构正在被超过 50%的客户主流采用。尽管这种适应性的重大变化有多种驱动因素和原因,但在本文开始时,有一点很重要,即虽然无服务器计算可能是许多应用程序的最佳解决方案,但它肯定不是所有应用程序的最佳解决方案。记住这一点,让我们仔细看看无服务器方法的一些关键优势。

关注业务逻辑——“关注什么,而不是如何”

随着数据科学家开始处理他们的项目,他们经常面临如何将他们的应用程序部署到云的难题,这迫使他们有时更多地关注部署过程,而不是模型本身的开发。无服务器计算的最大优势之一是它的易用性,我们将在本章后面看到。

图 1——该图显示了对业务逻辑的关注增加,而对架构的关注减少的趋势。(图片由作者提供)

高效、可扩展且强大—“可扩展性始终是关键”

无服务器基础架构的另一个巨大优势是能够以“随用随付”的方式按需扩展和缩减。提供商处理几乎所有与可伸缩性相关的项目,并管理所需的资源。然而,这是以限制对特定运行时设置的访问为代价的。如图 2 所示,根据需要扩展和缩减的能力也大大有助于节省成本。我们在这里可以看到,传统的可伸缩性是逐步增加的,而无服务器的可伸缩性更加线性。

图 2 —展示无服务器计算相对于传统方法的可扩展性的图表(图片由作者提供)

方便且易于部署

相对于全服务器架构,无服务器架构提供了许多优势。最大的优势之一是在部署基础架构和之后进行管理时的易用性。Zappa 可以使用 CloudFormation 来处理这两个项目,cloud formation 本质上是 IaC(基础设施即代码)。记住这一点,让我们开始吧!

2.AWS 上的 IAM 权限

在开始使用 Zappa 之前,让我们先在 AWS 中按顺序获取权限。首先,在 AWS 中导航到 IAM,单击用户组,并创建一个新组。我们可以称这个群为“zappa_group”。

图 AWS 的 IAM 菜单。(作者截图)

创建组后,单击“添加权限”,然后单击“创建内联策略”。

图 AWS 的 IAM 下拉菜单。(作者截图)

单击 JSON 选项卡,并向其中添加以下策略:

这个策略将使 Zappa 能够创建基础设施,并相应地为您标记它。请注意,您需要将 AWS_ACCOUNT_NUMBER 更改为您各自的号码。您可以通过单击屏幕右上角的用户名在下拉选项卡中找到它。

现在导航到“用户”并创建一个新用户。我们可以称这个用户为“zappa_user”。如果你觉得更有创意,请随意更改名称!将用户添加到您创建的组中,以赋予用户适当的权限。完成后,请务必记下访问密钥和密码。

导航到您的终端命令行,并添加您的密钥。您可以使用 vim 在本地编辑 aws 密钥文件:

vim ~/.aws/credentials

进入编辑器后,单击“I”插入新内容,并添加您的密钥。如果您已经有一个名为 default 的配置文件,我建议您创建一个名为“zappa”的新配置文件:

[zappa] 
aws_access_key_id = ADD_KEY_HERE 
aws_secret_access_key = ADD_SECRET_HERE

至此,我们已经完成了 AWS 上的权限,现在可以把注意力转移到 Zappa 上了。

3.开发烧瓶应用程序

现在我们有了一些关于无服务器方法的背景知识,让我们继续准备一个 Flask 应用程序。我们的目标是使用 scikit-learn 训练一个简单的机器学习模型,保存工件,并将其加载到 Flask API 中。

我们可以从创建一个新目录开始,输入目录:

mkdir sklearn-zappa-ml-with-preprocessingcd sklearn-zappa-ml-with-preprocessing

现在让我们创建一个名为“venv”的新虚拟环境并激活它:

virtualenv venvsource venv/bin/activate

现在让我们继续安装感兴趣的库。对于本教程,我们当然需要 zappa 来进行部署,flask 来构建应用程序,scikit-learn 来管理我们的模型,joblib 来保存和加载我们的模型:

pip install zappa scikit-learn flask joblib

安装完我们的库后,让我们再次激活虚拟环境:

source venv/bin/activate

让我们继续创建一个名为 train 的新目录和另一个名为 models 的目录,在 train 目录中,我们将创建一个名为 train.py 的文件:

mkdir train
mkdir models
cd train
vim train.py

在 train.py 文件中,我们将添加以下代码:

回想一下,我们在这里的目标是训练一个样本模型,并演示我们可以加载分类器本身,以及预处理工件。如果您检查 models 目录,您现在应该看到两个 joblib 文件。

回到主目录,我们现在创建一个名为 app.py 的新文件:

vim app.py

在这个文件中,我们将准备我们的 API 框架。我们将导入我们的库,加载模型,并准备两条路径:一条路径到主目录“/”,另一条路径到“/predict”:

到目前为止,我们应该有以下目录结构:

sklearn-zappa-ml-with-preprocessing
    |__ venv |__ models
        |__ rfc.joblib
        |__ scaler.joblib |__ train
        |__ train.py |__ app.y

我们可以通过运行 Flask 服务器来测试我们的模型:

python3 app.py

使用 Postman [2],您可以通过创建针对本地服务器的 POST 请求来测试 API:

图 5—显示 POST 请求的 Postman 应用程序的屏幕截图,带有示例输入数据(作者的屏幕截图)

4.使用 Zappa 部署

既然我们已经确认了我们的应用程序可以在本地工作,现在让我们开始配置 Zappa。我们可以从使用 Zappa 中的 init 函数开始:

zappa init

当您完成初始化过程时,会要求您提供一些项目:

  • 环境:您可以指定感兴趣的环境,比如 dev、test 或 prod。出于本教程的目的,请输入“dev”
  • 应用程序路径:这里你需要设置主应用程序的路径。我们可以使用默认值“app.app”
  • 桶名:您可以在这里指定感兴趣的 AWS S3 桶。我建议您保留默认值,让 Zappa 为您处理这个问题。
  • 全局:你可以使用这个特性来优化分配。出于本教程的目的,输入“n ”,这是跳过此功能的默认值。

完成此过程后,Zappa 将在您的当前目录中生成一个名为 zappa_settings.json 的文件。继续操作并打开该文件以检查设置。打开文件,继续添加两个新的键值对:“slim_handler”和“tags”。slim_handler 配置将用于优化部署过程,因为我们使用的是 scikit-learn——一个相对较大的库。此外,“标签”将用于确保我们创建的基础架构被适当标记,以用于组织和计费目的。

{
“dev”: {
    “app_function”: “app.app”,
    “profile_name”: “zappa”,
    “project_name”: “sklearn-zappa-m”,
    “runtime”: “python3.8”,
    “s3_bucket”: “zappa-123456789”,
    “slim_handler”: true,
    “tags”: {
        “Project”: “ZappaProject”,
        “Stage”: “dev”
        }
    }
}

确认设置正确后,现在可以使用以下命令部署 API 了:

zappa deploy dev

这将继续并准备您的基础设施,以服务于我们之前准备的 Flask 端点。最后,需要了解两个主要组件:管理 API 外部流量的 API Gateway,以及包含应用程序逻辑的脚本 AWS Lambda。

图 AWS 架构的表示(图片由作者提供)

完成部署过程后,Zappa 将向您返回一个 URL,其结构如下所示:

https://123456789.execute-api.us-east-2.amazonaws.com/dev

您可以使用 postman 再次测试应用程序的部署,并在上面的 URL 末尾添加“/predict”。您应该会收到一个状态为 200 的成功响应,与我们之前看到的类似。

5.用密钥保护 API

既然已经成功部署了 API,现在全世界都可以看到和使用它了。在许多情况下,我们的 API 端点可能是保密的,我们只想授予特定用户或应用程序访问权限。我们可以使用两种方法之一来实现这一点:API 密钥和 IAM 权限。让我们看一个使用 API 键实现这一点的例子。

继续操作,在您的 AWS 帐户上导航到 API Gateway。您应该能够看到刚刚部署的应用程序列在该页面中。如果没有,请确保选择了正确的区域。

点击 API,然后点击左侧的 Resources。单击名为{proxy+}的资源,您将看到类似下图的内容:

图 7 —来自 AWS API 网关的屏幕截图。(作者截图)

您可以看到 API 键当前被设置为“不需要”。继续点击下面的“任何”和“方法请求”。将“需要 API 密钥”更改为 True。注意,为了使用它,我们需要都生成一个密钥,并将其添加到一个使用计划中。

您可以通过单击屏幕左侧的 API 密钥来生成密钥。单击“操作”,然后单击“创建 API 密钥”。

图 8—显示 AWS 上 API 键下拉菜单的屏幕截图(作者截图)

接下来,继续创建新的使用计划。为计划命名和描述,然后根据需要设置限制和配额设置。单击 next,然后将 API 密钥添加到使用计划中。

这样,你现在应该都准备好了。您可以在 Postman 上测试密钥,方法是将密钥添加到您的请求的标头中。密钥应该是“x-api-key”,值应该是您生成的密钥。

图 9 —添加了 api 键的 postman 请求(作者截图)

这样,您现在就有了一个在 AWS 上部署和保护的无服务器机器学习模型!概括地说,这种模型部署方法的主要优点之一是,您将使用“随用随付”模型,确保您不会收到无意义的账单,即使在不活动时也是如此。

参考资料:

[1]https://www . prnewswire . com/news-releases/data dogs-2022-state-of-server less-report-finds-server less-reaching-mainstream-adoption-301560418 . html

[2]https://www.postman.com/

使用 SAM 开发 Lambdas 并在本地调试它们

原文:https://towardsdatascience.com/develop-lambdas-and-debug-them-locally-using-sam-8f367793cc1a

开发无服务器应用程序有其挑战性,SAM 为这些挑战提供了一个解决方案。了解如何使用 SAM 开发 lambdas 并在本地调试它们。

图片由珀西·博尔梅尔提供。Gopher 由拓也·上田提供,原始 Go Gopher 由勒内·弗伦奇提供(CC BY 3.0)

无服务器应用程序很棒,我们可以在生产中以超快的速度启动并运行一个功能,正常运行时间非常长,而且几乎没有成本。

Lambda 是一项 AWS 服务,它允许您部署可运行的小段代码。例如,我们可以部署一个小型的 Go 二进制程序来执行一个任务,并通过一个 API 来触发它,调度它,或者让它基于其他几个事件来执行。

然而,调试这些服务可能很难,因为它们运行在云上。我过去构建函数的方式可以很容易地在本地运行它们,并且简单地用 lambda 处理程序包装那个函数。这是可行的,但是有时我们想要调试整个流程以及我们使用的服务。

我经常鼓吹软件需要在本地运行,SAM 帮助我们在本地运行我们的云应用。

在本教程中,我希望你已经有了一些要求,否则就没有理由阅读如何本地调试 lambda 了。

  • 拥有足够 IAM 权限的 AWS 帐户
  • 安装和配置 AWS CLI

如果您没有 AWS 帐户或 AWS CLI,请按照官方文档安装。

如果你更喜欢视频格式,你可以在我的 Youtube 上找到这篇文章。

本文的视频版本

你可以在 GitHub 找到视频中使用的完整代码。

山姆是谁?

Sam(无服务器应用模型)是一个帮助我们构建云应用的 AWS 框架。Sam 有一个模板语言,我们可以用它来编写云架构的结构,我们可以利用该模板在本地部署和运行它。Sam 正在幕后使用 CloudFormation ,你现在不需要了解 CF。

在本文中,我们将创建几个简单的 lambda,并使用 SAM 对它们进行测试,我们还将尝试通过附加远程调试器来调试 lambda。SAM 允许我们使用 docker 在本地运行 lambdas 或 lambdas 的 API。

使用 SAM 与 IDE 无关,您可以使用任何 IDE 来利用 SAM。据我所知,大多数人确实倾向于使用 VS 代码,因为他们有很棒的插件,我们将在最后介绍。

我们从安装 SAM 开始,你可以找到关于如何在 AWS 上安装的官方文档。

我使用的是 Linux,所以下面是我安装 SAM 的方法。

用于安装 SAM 的安装脚本

您可以通过运行 version 命令进行验证

sam --version

创建 Lambda

让我们创建第一个简单的 lambda。我将在本教程中使用 Go 来演示,但是你应该能够使用任何你想要的支持 lambdas 的语言。您可以在 AWS 文档中找到关于您的语言的代码示例,每种支持的语言都有一个名为Working with $LANGUAGENAME的章节。

创建一个新文件夹并初始化一个 go 模块,创建一个名为main.go的文件和一个名为template.yml的文件。暂时忘记模板文件,让它保持空白,我们稍后会谈到它。

touch sam-lambda-demo
cd sam-lambda-demo
go mod init programmingpercy.tech/sam-demo
touch main.go
touch template.yml

让我们用整个宇宙中存在的最简单的λ来填充main.go,我们将接受一个包含名称的输入事件,我们将打印Hello $name

你猜对了,这是一个 Hello World Lambda!

main . go—gopher 历史上最简单的 lambda。

Event结构定义了我们的 lambda 输入应该是什么样子,我们将输出一个string,error。您可以返回您想要的任何结构,或者任何 AWS 定义的事件。稍后,当我们尝试在 lambda 前面使用 API 网关时,我们将介绍 AWS 事件。

使用 Sam 模板

在我们开始调试之前,我将介绍 SAM 的一些简单方面,以便我们理解正在发生的事情。

Sam 打包了一系列功能,它甚至可以将您的应用程序部署到 AWS 中。现在,让我们慢慢来,构建 lambda 并尝试调用它。

当我说应用程序时,我指的是整个云资源集,而不是您的单个 Go 二进制文件——资源集构成了我们的无服务器应用程序

为了构建我们的应用程序,我们首先需要定义应用程序拥有哪些云资源。这是在template.yml中使用特定的格式完成的。

我们将从我能想象到的最简单的模板开始,并经历一切是什么。

template.yml —单个 lambda 的简单 SAM 模板文件

在文件的开头,你会看到一些我从未见过改变的默认设置。我们定义要使用的模板版本。事实上,根据文档只有一个有效版本。

我们将重点关注Resources部分。在其中,我们可以定义整个 SAM 应用程序将拥有哪些资源。

语法非常简单,您以资源的名称开始 yml,我们创建一个名为HelloGopher的 lambda。这可以是你喜欢的任何东西,重要的是你可以用这个名字来引用其他资源,当你需要一个特定的 arn 时,这很重要。

所有资源都接受一个Type输入,类型可以是 cloudformation 允许的任何东西,通常是AWS::Serverless:RESOURCETYPE。如你所见,我们将类型设置为AWS::Serverless::Function,这告诉 cloudformation 为我们生成一个 lambda。

每种类型都有自己的一组可用属性,可以在文档中找到。要找到函数的可用属性,请查看它们的文档

CodeUri非常重要,这是包含我们代码的 ZIP 文件的本地路径或 S3 路径。在这个例子中,我使用了同一个文件夹,在一个真实的项目中,你可能有 lambdas,你可以创建一个类似于lambdas/hello-gopher/的文件夹结构,并将 codeuri 更改为一个更具可伸缩性的解决方案。

Handler用于设置一旦执行 lambda 就会调用的某个二进制文件,让我们试着用 SAM 来解释这个。

如果您现在尝试通过运行sam local invoke来运行 Lambda,您应该会看到一个崩溃报告,说没有这样的文件。

由于没有这样的文件,Sam 崩溃

这只是因为我们将Handler设置为指向尚不存在的hello-gopher二进制文件。让我们开始利用山姆来帮助我们。

Sam 构建和调用

我们可以使用 SAM 来打包我们的应用程序,build 命令带有许多参数标志。例如,您可以构建并将其发送到 S3。

使用 SAM build 很好,因为它将利用您的模板文件,并通过使用runtime属性来使用 go 的正确版本。

它非常容易使用,在与您的template.yml相同的文件夹中运行以下命令

sam build

您应该会看到一个.aws文件夹出现,打开该文件夹会显示 lambda 和名为hello-gopher的二进制文件。

通过本地调用 lambda 重试运行项目的时间。

sam local invoke

你应该看到它打印,你好,但没有给出名称。这是因为我们需要添加输入事件。这是通过使用-e--event选项来完成的,它们可以指向一个文件或一个 JSON 字符串。我更喜欢使用文件,因为这也可以作为 lambda 的示例文档。

在名为event.json的文件夹中创建一个文件,并粘贴到与 lambda 中的事件结构相匹配的 JSON 中。

event . JSON——我们期望 lambda 中的有效负载。

现在,再次调用 lambda,但是这次我们添加了指向event-json文件的-e标志。

Sam 使用有效负载调用

令人惊讶的是,它现在打印了我们在有效载荷中使用的名称。AWS SDK 中有大量的自定义 lambda 事件可以使用,但是我们将在特定于 lambda 的教程中介绍这些事件。

有一个-d标志允许我们传入一个远程调试端口,记住这一点非常重要。这是允许我们将调试器附加到 lambda 的特性。

请注意,我们只有一个 lambda,但是如果您有多个 lambda,您可以通过在命令中添加 lambda 的名称来指定要运行的 lambda。

sam local invoke hello-gopher # Runs a specific lambda

如果你想运行一个 lambda 并将其作为一个服务公开,就像它在真实的云上运行一样,你可以使用start-lambda命令来模拟。

让我们尝试运行以下命令

sam local start-lambda

这应该会打印出 lambda 公开的 URL,我们可以将它添加到 AWS cli 来调用 lambda。默认的网址是 http://127.0.0.1:3001

您可以使用 URL 作为 AWS CLI 的端点,通过以下命令调用它。

aws lambda invoke --function-name HelloGopher --endpoint "[http://127.0.0.1:3001](http://127.0.0.1:3001)" --payload '{ "name": "percy"}' response.json

这个命令将调用 lambda,插入有效负载并将响应输出到response.json

Sam API 网关

很多时候,您希望在运行 Lambda 时,前面有一个 API 网关。API 网关将把 Lambda 作为 HTTP API 公开。

与 SAM 一起管理和设置非常简单。我们需要创建 lambda 资源监听的Events,这不一定是 HTTP,它可以是 SQS 或许多其他 AWS 服务事件。使用类型API会让 Sam 知道这是一个 API 网关。

我们将修改template.yml来添加 API 端点作为 POST。

template.yml —添加 API 事件

我们还需要修改 lambda 来接受 API 网关事件,这些事件的结构有点不同。它们用一些元数据(如请求标识符)包装原始请求。

main . go——修改了我们的 lambda,使其作为 API 端点运行

要打开 API 并在本地公开端点,这在您开发具有许多端点的 API 并希望在本地尝试您的系统时非常方便,我们可以再次使用 SAM。

让我们构建新的 Lambda 并用sam local start-api运行 API。

sam build
sam local start-api

您应该看到一个输出,它指定了 API 在哪个端口上运行,对我来说,输出如下。

Mounting HelloGopher at [http://127.0.0.1:3000/api/hellogopher](http://127.0.0.1:3000/api/hellogopher) [POST]

我们可以用 CURL 来尝试,并发送预期的数据负载。

curl -X POST localhost:3000/api/hellogopher -d '{"name": "percy"}'

Sam 环境变量

大多数时候,你的 lambda 需要配置。很多时候这是通过使用环境变量来完成的。

我建议在template.yml中指定期望的变量,这提供了使用 CLI 修改变量的能力,我们将很快介绍这一点。

为了添加环境,我们修改了模板并添加了一个简单的Environment属性。

下面是我的template.yml和一个名为my-cool-variable的环境变量的片段。

template.yml —将环境变量添加到我们的 lambda 中

接下来,我们需要开始使用 lambda 中的环境变量,我将只把它的值添加到输出中,修改main.go中的第 31 行如下。

main.go:31 —添加了环境变量

这看起来似乎微不足道,但是您现在可以在 SAM CLI 中非常灵活地利用它来设置新的变量。我们可以使用-n参数修改调用之间的变量。记住,在 API 中可以有多个 lambda 端点,每个 lambda 可以需要自己的一组环境变量。

您可以创建一个特殊的环境 JSON 文件来控制每个 lambdas 环境。您必须使用来自template.yml的资源名称,并且只有在template.yml中指定的环境是可修改的。如果你试图设置一个没有在模板中声明的变量,它将不会被添加。

创建一个名为environments.json的新文件,我们将用它来修改每个 lambda resources 环境变量。

environments.json —我们要修改的变量

尝试使用-n标志重新构建并执行 API 来指出环境文件,您现在应该会看到打印出的新值。

sam local start-api -n environments.json

还有一些参数,它们与环境变量不同,但它们与云的形成有更大的关系,我们在这里不讨论细节。

Sam 生成事件

并不是所有的 lambda 都被公开为 API,例如,一些 lambda 监听 SQS 事件或 S3 事件来触发它们。

一种非常常见的方法是用 lambda 监听 SQS 事件,遗憾的是这很难测试。没有办法将 Sam 连接到 SQS 队列来测试它,相反,测试它的方法是生成一个 SQS 有效负载并使用该有效负载调用 lambda。这将模拟一个被发射到 lambda 中的现场事件。

现在你可能想知道,SQS 事件看起来如何?我不知道,我们也不需要知道,因为 Sam 可以帮助我们为通常与 lambdas 相关的已知 AWS 服务生成虚假的有效载荷。

让我们从更新template.yml并添加一个新的 lambda 开始,这个 lambda 将在一个名为my-awesome-queue的队列上监听 SQS 事件。现在,正如我所说的,我们不能让萨姆在本地监听my-awesome-queue,但是我们可以伪造有效载荷。以下要点显示了如何告诉山姆关于 SQS 的事情,template.yml中唯一的新部分是新的 lambda,它会触发 SQS 事件。

yml——我们添加了一个 lambda 函数,它在 SQS 队列上触发

CodeUri中,我们指定了./sqslambda的位置,所以我们可以从创建那个文件夹开始,并用我们的 lambda 在其中添加一个main.go

mkdir ./sqslambda
touch main.go

我们新的 lambda 将非常简单,只将输入事件打印到日志中。我们将使用来自 AWS SDK 的相同的events包,并指定该事件是一个SQSEvent。我们可以进入 SDK,尝试使用 JSON 标签复制 SQSEvent 结构,但这将是一项繁重的工作。

。/sqslambda/main.go —接受 SQS 负载的简单 lambda

现在,在我们触发这个 lambda 之前,我们需要一个 SQSEvent。我们可以使用 Generate-Event 命令为我们创建一个event.json文件,我们可以将它作为有效载荷传入。

语法非常简单,使用generate-event后跟服务名和receive-message子命令。目前实际上只有一个子命令,即 receive-message。谁知道也许他们已经计划好了send-message

我们将在--body中传递一个参数,用于修改 SQS 有效载荷的主体,主体是用户特定的有效载荷。

sam local generate-event sqs receive-message --body 'My Own Event Payload'

运行它,您将看到一个生成的 JSON 有效负载,它模拟了一个 SQS 事件。您可以将这个有效负载写入一个名为event.json的文件,并像以前一样使用-e标志将该有效负载传递给调用。

对于-e标志有一个很好的技巧,但是,如果你传递一个-作为输入,它将从标准输入中读取值。这意味着我们可以将生成事件命令与调用链接在一起。

sam local generate-event sqs receive-message --body 'My Own Event Payload' | sam local invoke -e - SQSLambda

运行该命令应该会打印出整个事件并调用 lambda。

太好了,我们现在可以测试任何 lambda,不管是什么服务触发了它。

附加远程调试器

在开发服务时,调试是一个非常重要的方面,附加一个调试器来查看运行时发生了什么是解决问题的最有用的方法之一。

为此,我们可以在 Sam 命令中添加-d标志来打开远程调试器端口,您可以将端口指定为-d 的参数。这适用于所有调用,start-lambdastart-api也接受调试器标志。

Sam 需要安装 Linux Delve 调试器,如果您没有运行 Linux,您仍然可以使用“GOOS = Linux go arch = amd64 go install github.com/go-delve/delve/cmd/dlv@latest"”来安装它们

记住调试器应该安装在您的主机上,我们可以使用--debugger-path=hostURL/to/debugger参数指定调试器的位置。我还需要使用 delveAPI 版本 2 来让调试器顺利运行。

让我们运行 SQS 事件来调试 lambda。我将添加它所需的调试标志,以便在调试模式下公开 lambda。请注意,当您使用-d调用时,它会在开始时暂停 lambda 并等待调试器连接。

sam local invoke SQSLambda -d 8099 --debugger-path=/home/percy/go/bin --debug-args="-delveAPI=2"

接下来,我们需要附加一个调试器,如何做取决于你是使用 VS 代码还是 Goland 等等。我正在使用 VS 代码,所以我将在我的.vscode/launch.json中添加一个新的配置。如果你用的是 Goland,请看这里如何附加调试器

。vscode/launch.json —这是我们的调试附件

基本上我们所做的是创建一个新的附加请求到一个远程调试器,在 localhost:8099 上。确保您使用的端口与您给-d命令的端口相同。

保存文件并在第 13 行的sqslambda/main.go中放置一个调试器。然后运行调试配置。您应该看到它在您放置的断点处中断。

我们在调试时附加了一个断点

现在,让它运行需要一点工作,有一些技巧可以自动运行调试器命令,比如在 launch.json 中使用预启动效果。但是,如果您使用 VS 代码,我将很快介绍如何使用 AWS 插件,这将使调试更加容易。他们不再需要使用任何 CLI 命令,这非常方便。

在 SAM 中使用 VS 代码

如果你正在使用 VS 代码,我建议你下载 AWS 工具包,如果你还没有的话。

进入扩展,搜索AWS toolkit并安装它。

使用 AWS 云资源的 VS 代码扩展

下载后打开扩展并登录您想要的配置文件,只需按下Connect to AWS按钮。

用插件连接到 AWS

这个过程应该很简单,它会要求您进行一些配置,如默认区域、要使用的配置文件等。选择您想要的选项。之后,您应该会看到一堆云服务出现。

在本教程中,除了本地部署,我们不会部署任何 SAM 应用程序,但是您可以这样做(即使没有插件)。

该插件现在将添加许多 SAM 内置功能,以取代需要运行命令行。最好的特性是现在每个 lambda 上面都会有一个文本,询问你是否想要创建一个调试配置。

AWS 插件可以为我们生成调试处理程序。

点击Add Debug Configuration,你的launch.json应该会更新。

launch.json — AWS 插件为我们生成了一个全新的调试器。

现在,这看起来和我们之前创建的有很大不同。这是这个插件很酷的一点,它创建了一个独特的调试类型,叫做aws-sam,允许我们调用 lambdas。还有一种 API 调用的请求类型,可以通过访问 API lambda 并为其生成一个调试调用来生成。

如您所见,您可以指定调用目标、环境变量和有效负载。这些都非常方便,所以我们可以跳过使用 API,而只使用一个简单的 launch.json。

您应该熟悉所有您可以更改的内容,因为这正是我们在本文中讨论的内容。

那么,为什么我们不从一开始就使用插件呢?因为那样会有太多的魔法,我们也不明白插件在为我们做什么。我相信,从长远来看,学习工具的基础是最好的,而且你不一定要使用 VS 代码,而是任何你想要的 IDE,因为我们知道如何附加调试器,以及为什么我们需要这样做。

设置本地堆栈和 SAM 网络

如果您的 lambdas 使用其他 AWS 资源,您可以模拟那些使用 Localstack 的资源。在本教程中,我不会讨论 localstack,但是我们将看看如何强制 SAM 资源与 localstack 实例在同一个网络上运行。

如果您使用 localstack 并通过 docker 运行它,请确保您还指定了 docker 网络。如果您在名为mock-aws-network的 docker 网络上运行 localstack,您可以通过在大多数命令上使用输入标志docker-network mock-aws-network来让 Sam 使用相同的网络。

sam local invoke --docker-network mock-aws-network
sam local start-api --docker-network mock-aws-network

这很方便,因为 lambdas 倾向于使用其他需要通信的 AWS 服务,这样我们也可以调试它。

如果你使用的是 VS 代码插件,你可以把这一行添加到launch.json

launch.json —指定用于 Sam 的 docker 网络

结论

在本教程中,我们学习了如何使用 SAM 开发 lambdas,以及如何在本地调试它们。我认为 SAM 在开发 lambdas 时提供了帮助,消除了我在开始开发无服务器应用程序时遇到的许多挫折。调试之类的事情很难,但现在不再是了。

SAM 中还有更多我没有在这里介绍的特性,比如创建其他 AWS 资源(SQS 队列等)以及在 lambda 中直接引用它们的 ARN。

您应该开始试用 SAM,并找出它提供的所有令人惊奇的工具。

感谢您的阅读,一如既往,我喜欢反馈和讨论。

使用 Python 开发您自己的日历来跟踪重要日期

原文:https://towardsdatascience.com/develop-your-own-calendar-to-track-important-dates-with-python-c1af9e98ffc3

开发一个日历 GUI 界面来管理您 2022 年及以后的计划

Unsplash 上由Towfiqu barb huya拍摄的照片

时间是至关重要的。时间不等人。关于时间的重要性,还可以引用几百句话。因此,如果你计划掌握数据科学或编程,你将需要一个简明的计划来获得这一整年的最佳知识。

随着新的一年已经开始,跟踪你的效率和生产力变得很重要。有什么比借助你自己的日历更好的方法来有效地跟踪这一进程呢?这将有助于你为今年剩下的时间创造一个清晰的、有指导意义的视角和方向。

在本文中,我们将着重于构建一个日历,通过它我们可以查看必要的数据并相应地分析我们的计划。我们还将关注您可以对该项目进行的一些关键更新和改进,这样您就可以不断地提醒自己这一年中还有很多重要的事情要做。

在我们开始用 Python 构建我们的日历 GUI 界面之前,如果您刚刚开始学习数据科学,并希望在今年内掌握它,我有一篇文章非常详细地介绍了这一点。查看我以前的一篇博客,通过下面提供的链接中的 12 个关键步骤,你可以在 12 个月内掌握数据科学。

</12-steps-for-beginner-to-pro-in-data-science-in-12-months-c6f6ba01f96e>

开发您的日历:

作者截图

从上图中我们可以注意到,这个项目需要我们使用图形用户界面(GUI)来构建一个视觉上吸引人的日历。我们将利用 python 中可用的 Tkinter 库来构建这样一个用户界面。要了解更多关于这些 GUI 工具的知识,我建议查看我以前的一篇文章,这篇文章解释了其中的七个工具,并附有入门代码,可以帮助您快速入门。

</7-best-ui-graphics-tools-for-python-developers-with-starter-codes-2e46c248b47c>

除了 Tkinter GUI 模块之外,我们还需要安装一个额外的 tkcalendar,它为 Tkinter 界面提供了日历和日期输入小部件。它允许用户对不同的窗口小部件进行定制控制,从而按照用户的要求相应地操纵日期和时间。下面的命令应该允许您轻松安装下面的模块。

pip install tkcalendar

一旦我们成功安装了必要的库,我们现在就可以相应地导入它们了。进行导入时的星号(' * ')表示我们正在导入 Tkinter 模块的所有类,因为我们将需要其中的一些类来成功计算这个项目。从 Tkinter 日历库中,我们将只需要日历模块,通过它我们可以显示交互式日历及其相应的小部件。

# Importing The Essential Libraries
from tkinter import *
from tkcalendar import Calendar

一旦我们完成了所需库的导入,我们将继续创建 GUI 对象作为显示界面的根。然后,我们可以根据用户的选择相应地选择我们的几何设置。我将界面尺寸设置为 700 x 700,因为我喜欢在大多数任务中使用更大的可交互屏幕。然后,我们将添加我们最近安装的日历模块,作为 Tkinter GUI 的附加小部件。

使用这个日历模块,我们可以根据需要设置根路径、特定的日、月和年。在打包这个变量时,我将在 y 轴上应用填充,这样,从交互式屏幕的顶部到我们创建按钮、标签或任何其他类型的交互式小部件的地方,距离为 20 个像素。我们将填充两边并将变量扩展到屏幕的末端,这样我们就可以有一个更丰富的日历视图。这个过程的代码如下面的代码片段所示。

# Create The Gui Object
tk = Tk()

# Set the geometry of the GUI Interface
tk.geometry("700x700")

# Add the Calendar module
cal = Calendar(tk, selectmode = 'day',
               year = 2022, month = 1,
               day = 11)

cal.pack(pady = 20, fill="both", expand=True)

在下一个代码片段中,我们将创建一个函数,通过它我们可以获取用户需要的特定日期。grad date 函数包含的文本将在我们单击其中一个按钮部件时显示。如果您想在下面的函数中添加更多的命令,那么您可以随意地进一步探索它。

# Function to grab the selected date
def grad_date():
    date.config(text = "Selected Date is: " + cal.get_date())

最后,我们将为我们的项目创建一些需求来选择合适的日期和时间。首先,我们将创建一个按钮,通过我们的函数来获取我们用鼠标光标选择的特定日期。然后,我们将添加一个标签,当单击按钮时,该标签将在屏幕上显示带有特定日期的文本。应用 20 像素的填充操作来保持标签和按钮的相等距离。然后,我们将为日历项目执行 Tkinter 循环。

# Adding the Button and Label
Button(tk, text = "Get Date",
       command = grad_date).pack(pady = 20)

date = Label(tk, text = "")
date.pack(pady = 20)

# Execute Tkinter
tk.mainloop()

一旦您完美地执行了以下代码片段,您就可以继续运行 Python 程序来接收类似于上面显示的图像的结果。让我们转到下一节,探索完整的代码,并讨论一些我们可以添加的改进,使这个项目变得更好。

更新和改进:

下面提供了用 Python 构建日历 GUI 界面项目的完整代码。通过添加更多的函数和其他创新来开发这个日历项目的各种独特的应用程序,您可以随意试验代码。

我建议添加到日历中的一个主要改进是将所选日期保存在文本文件中的能力,以便您可以在不同的 Python 项目中使用这些保存的日期来构建待办事项提醒列表,该列表将提醒您某人的生日、特定事件、重要日期等等。我建议检查一个以前的提醒应用程序项目,为这些日期创建一个提醒提醒。

另一个绝妙的主意是将你的日历与特定的日期联系起来,使你的电子邮件自动化,或者将信息转发给你想接收信息的人。我们将讨论如何实现这些目标的未来项目。在此之前,请继续探索您刚刚创建的日历的众多隐藏可能性!

结论:

este 扬森斯Unsplash 上拍摄

“时间是免费的,但它是无价的。你不能拥有它,但你可以使用它。你不能留着它,但你可以花它。一旦你失去了它,就再也找不回来了。”
—哈维·麦凯

日历是生活中必不可少的一部分,因为它帮助我们管理一年中的日程、计划和其他任务。它们帮助我们很好地记录时间,但显示在墙上的项目或手机上的日历应用程序有时会很无聊。你不能添加额外的功能到它们里面,这些功能完全是你自己构建的。因此,构建自己的日历 GUI 是一个好主意!

在本文中,我们讨论了时间的重要性,并使用 Tkinter GUI 界面构建了一个日历项目。我们能够从图形界面中选择日期,并单击界面上的一个按钮,向我们返回一个存储所选日期、日期和年份的标签。我们还研究了通过额外的更新和集成可以对此项目进行的一些改进。

如果你想在我的文章发表后第一时间得到通知,请点击下面的链接订阅邮件推荐。如果你希望支持其他作者和我,请订阅下面的链接。

https://bharath-k1297.medium.com/membership

如果你对这篇文章中提到的各点有任何疑问,请在下面的评论中告诉我。我会尽快给你回复。

看看我的一些与本文主题相关的文章,你可能也会喜欢阅读!

谢谢你们坚持到最后。我希望你们都喜欢这篇文章。祝大家有美好的一天!

用 Python 开发自己的牛顿-拉夫森算法

原文:https://towardsdatascience.com/develop-your-own-newton-raphson-algorithm-in-python-a20a5b68c7dd

求解最优解、平衡点等。使用 NR 方法

戴维·克洛德在 Unsplash 上的照片

牛顿-拉夫森方法是一种迭代方法,用于逼近函数的根或零点。由于许多原因,确定根可能是重要的;它们可以用来优化金融问题,解决物理学中的平衡点,模拟计算流体动力学等。正如你所看到的,它的用途远远超出了任何一个主题。一般来说,在复杂的方程中,根是不能明确求解的,所以它们必须是近似的;这就是牛顿-拉夫森方法发挥作用的地方。

牛顿-拉夫森方法(或算法)是计算根的最流行的方法之一,因为它简单而快速。结合计算机,该算法可以在不到一秒的时间内求解根。该方法要求函数符合以下形式。在大多数情况下,这可以通过简单的加法或减法来实现。

以下面的例子为例。简单的减法就是将方程转换成上面的形式所需要的。等式的左边将是函数, f(x) 。我们稍后会用到这个等式,所以请记住它。

该方法的第一步采用初始猜测,并使用函数和函数导数来计算下一个猜测。然后,以类似的方式使用该猜测来计算下一个猜测,依此类推,直到满足容差或迭代限制。如下面的动画所示,导数f’(x)(红线)被用作斜率,以帮助计算 x 的下一个猜测值。

牛顿-拉夫森方法可视化【由拉尔夫·普费菲创造】

让我们把方程中发生的事情写出来,这样更有意义一点。第一次迭代如下所示:

第二次迭代:

后续迭代,直到达到 f(xᵢ) 值的某个容差(或迭代极限):

如果你(或者一个代码)可以计算出 f(x) 的导数,那么你就可以用这个算法迭代计算一个方程的根。你最初的猜测可能非常重要。根据问题的不同,如果你从一个糟糕的猜测开始,它会让你的收敛需要一段时间或者根本不收敛。然而,如果猜测正确,牛顿-拉夫森算法将在几次迭代内相对快速地收敛到一个解。如果你有一个多重根的方程,你最初的猜测也很重要。根据你的第一个猜测,你可以收敛到任何一个根。这些问题可以通过使用有根据的初步猜测来缓解。

牛顿-拉夫森算法可以使用 Python 或任何编码语言相对容易地实现。正如你在例子中看到的,当最初的猜测是合理的时候,计算机可以很快得出想要的答案。我们还会看到当你最初的猜测很糟糕时会发生什么。让我们开始编码:

导入包

我通常通过从 Matplotlib 导入 N umPypyplot 来开始我所有的 Python 代码。 NumPy (为便于调用,定义为 np )用于数组操作和基本数学函数,如余弦、正弦、指数和对数函数。 Pyplot (为便于调用,定义为 plt )用于创建图形和可视化数据。这两个软件包都很棒,比我在这里展示的功能多得多。

# Importing Packages
import numpy as np
import matplotlib.pyplot as plt

定义函数

在这部分代码中,我们将定义一个方程,我们试图找到它的根和它的导数。这两个方程将被定义为 Python 函数,所以我们可以为它们提供一个输入值, x ,它们将返回方程的值及其在该值处的导数。作为参考,我们将使用本文前面的等式。

# Defining Equation and Derivative
def f(x):
    res = np.cos(x)-2*x**3
    return res

def dfdx(x):
    res = -np.sin(x)-6*x**2
    return res

牛顿-拉夫逊回路

本节提供了确定目标方程的根所需的迭代循环。在迭代 while 循环之前,最好包含一个最大迭代次数变量 max_iter 。这很重要,因为如果算法不能收敛到一个解,它将防止 while 循环无限期运行。公差 tol ,用于确保我们得到我们想要的精度。在开始我们的迭代之前,我们还包括最初的猜测,【x₀】。

现在,在 while 循环中,我们使用牛顿-拉夫森通用方程来获得我们的下一个猜测,【Xi】,根据上一个猜测,【Xi _ 1(x₀用于第一次迭代)。然后将 xi 值输入到原始方程中,以对照所选公差进行检查。这里,我们正在确定我们的新猜测是否使原始方程接近于零。当等式的绝对值小于我们的容差或者我们已经达到最大迭代次数时,将会中断 while 循环。如果它没有被破坏,循环继续更新对根的值的猜测。

*# Newton-Raphson Algorithm
max_iter = 20  # Max iterations
tol = 1E-15  # Tolerance
i = 0  # Iteration counter
x0 = 1  # Initial guess
xi_1 = x0
print(‘Iteration ‘ + str(i) + ‘: x = ‘ + str(x0) + ‘, f(x) = ‘ + 
      str(f(x0)))# Iterating until either the tolerance or max iterations is met
while abs(f(xi_1)) > tol or i > max_iter:
    i = i + 1
    xi = xi_1-f(xi_1)/dfdx(xi_1)  # Newton-Raphson equation
    print(‘Iteration ‘ + str(i) + ‘: x = ‘ + str(xi) + ‘, f(x) = ‘ +    
          str(f(xi)))
    xi_1 = xi*

你可能已经注意到,我们在每次迭代中打印了【Xi】f(xi) 的值,以及它们发生在什么迭代中。这有助于我们跟踪算法的执行情况。该代码将输出以下内容:

*Iteration 0: x = 1, f(x) = -1.4596976941318602
Iteration 1: x = 0.7866397888154096, f(x) = -0.2673205221391448
Iteration 2: x = 0.7261709381607133, f(x) = -0.018132645287873284
Iteration 3: x = 0.7214340390454733, f(x) = -0.0001059518195203335
Iteration 4: x = 0.7214060336500903, f(x) = -3.6893424981698786e-09
Iteration 5: x = 0.721406032674848, f(x) = -1.1102230246251565e-16*

在查看我们的输出后,看起来牛顿-拉夫森算法在 5 次迭代内收敛。相当快。它也接近我们非常小的公差的解决方案。这意味着我们成功实现了融合!

绘制方程和结果

您可以通过绘制包含最终根结果的范围的方程来检查您的结果。您也可以绘制您的根值,以查看您的结果如何与直线匹配。

*# Creating Data for the Line
x_plot = np.linspace(-2, 2, 1000)
y_plot = f(x_plot)

# Plotting Function
fig = plt.figure()
plt.plot(x_plot, y_plot, c=’blue’)
plt.plot(xi, f(xi), c=’red’, marker=’o’, fillstyle=’none’)
plt.xlim([-2, 2])
plt.ylim([-2, 2])
plt.xlabel(‘x’)
plt.ylabel(‘y’)
plt.grid()
plt.show()*

这将创建下面的情节。正如你所看到的,牛顿-拉夫森算法的结果与 x 轴交叉处非常吻合。这意味着我们计算了这个例子方程的根的一个很好的近似值。

牛顿-拉夫森示例[由作者创建]

让我们尝试一个不好的猜测,看看牛顿-拉夫森方法有多好。如果我们不知道零可能在哪里,我们可能会猜测根在 100 左右。这将导致算法花费 17 次迭代来达到最终结果,但是它仍然以期望的容差达到最终结果。这就是算法的力量。以下是这次试验的结果,以供参考:

*Iteration 0: x = 100, f(x) = -1999999.1376811278
Iteration 1: x = 66.66639972214995, f(x) = -592586.2434629743
Iteration 2: x = 44.44370527065607, f(x) = -175573.33453879558
Iteration 3: x = 29.629768898508654, f(x) = -52025.53704873974
Iteration 4: x = 19.751306671606308, f(x) = -15409.906816284196
Iteration 5: x = 13.170008297565373, f(x) = -4567.829385058682
Iteration 6: x = 8.783189380524185, f(x) = -1355.9491714365759
Iteration 7: x = 5.857511541738072, f(x) = -401.03685300814516
Iteration 8: x = 3.9055163566277287, f(x) = -119.86426242296075
Iteration 9: x = 2.585811794335439, f(x) = -35.42914973938403
Iteration 10: x = 1.7141632818943917, f(x) = -10.216519425753903
Iteration 11: x = 1.1654743473093252, f(x) = -2.7718839271038136
Iteration 12: x = 0.859829105239771, f(x) = -0.6187868181406497
Iteration 13: x = 0.7406842579217826, f(x) = -0.07469127827436395
Iteration 14: x = 0.7218536001388265, f(x) = -0.0016940906277266299
Iteration 15: x = 0.7214062815658834, f(x) = -9.415553438030244e-07
Iteration 16: x = 0.721406032674925, f(x) = -2.9121149935917856e-13
Iteration 17: x = 0.721406032674848, f(x) = -1.1102230246251565e-16*

正如你所看到的,这是一个非常强大的方法来获得一个方程的零点的非常接近的近似值。根的求解可以不用基于图的猜测,也不用代数求解(如果可能的话)。这可以应用于许多不同的领域,所以你自己试试吧!

感谢您阅读文章!如果您对代码有任何问题或者想了解更多关于这种方法的信息,请告诉我。如果你有兴趣,可以看看我关于 Python、轨道力学和物理学的其他文章!

用 Python 开发不到 10 行的天气应用程序

原文:https://towardsdatascience.com/develop-your-weather-application-with-python-in-less-than-10-lines-6d092c6dcbc9

使用 Python 构建我们的天气电视广播应用程序,以接收所需位置的更新

美国国家海洋和大气管理局在 Unsplash 拍摄的照片

天气是我们生活中最重要的方面之一。它规定了我们想要在一天、一周或一个月中计划的不同种类的活动。解读天气模式的一种古老方法是观察天空,预测天气是晴朗、下雨还是潮湿。

如果你不像我一样精通气候,并且总是以预测错误告终,这种方法可能不是最有效的。另一种方法包括等待和观看新闻频道的每日报道来分析天气。然而,这些方式对于我们今天生活的现代来说似乎有点过时了。

有不同的设备和工具可以让你从卫星报告和众多的科学研究中解读当前的天气状况。在本文中,我们将利用 Python 中的 API 技术,用不到十行代码创建您自己的天气预报应用程序。所以,事不宜迟,让我们开始完成这个项目。

发展我们的天气电视广播:

照片由唐纳德·詹纳蒂Unsplash 上拍摄

对于这个项目,我们将利用 Python 和一个天气报告服务,该服务为我们提供一个 API 密钥来解释任何特定地方的每日天气情况。这个项目需要的少数几个库需求之一是 Python 中可用的请求模块。如果您还没有它,可以用一个简单的 pip install 命令安装下面的模块,并导入如下所示的库。这个工具将允许我们直接访问所需天气 API 应用程序的 HTTP 链接。

import requests

现在,我们将需要一个网站的 API 键,该网站存储有关天气的信息,并预测它们的准确结果。 Open Weather Map 网站提供了一种科学而简单的方法,在我们的 Python 代码中利用他们的技术来快速生成天气预报的预期结果。要生成您自己的 API 密钥,只需登录以下网站并创建一个完全免费的帐户。您可以使用他们建议的 API 密钥之一,也可以自己生成一个。将这个生成的 API 键粘贴到您选择的变量中,如下所示。

API_Key = ""

我们的下一步是允许用户输入他们想要分析或查看天气状况的位置。我们可以使用输入命令执行以下操作,允许用户在所需的位置键入内容。请注意,只允许您键入实际存在的位置。如果您键入了错误的位置名称或不存在的位置名称,将会显示一条错误消息。

我们现在将创建一个变量来存储天气访问网站的默认 URL 位置。对于最终的 URL,我们将把打开的天气地图网站的默认路径与我们之前生成并存储在 API_Key 变量中的 API 键结合起来。最后,在天气数据变量中,我们将使用请求库来获取天气数据并相应地存储信息。

location = input("Enter Your Desired Location: ")weather_url = f"http://api.openweathermap.org/data/2.5/weather?q={location}&appid="
final_url = weather_url + API_Keyweather_data = requests.get(final_url).json()

现在,我们已经成功地在各自的变量中收集了天气信息,我们可以继续向用户显示所需的数据。下面的操作可以用 print 语句来完成,如下面的代码片段所示。

print(weather_data)

您可能会注意到,print 语句中显示的信息并不像您预期的那样漂亮,因为收集和显示的数据是原始格式的。因此,我们可以利用 Data pretty 打印机模块,以一种对用户来说更可展示、更可读的方式打印有用的信息。

from pprint import pprintpprint(weather_data)

在本文的下一部分,我们将查看构建天气应用程序的完整代码,并分析我们可以对该项目进行的大量改进和提高。

最终代码和进一步改进:

通过对上一节中所有基本代码片段的讨论,我们可以将它们组合在一起,为这个项目构建最终的代码块。请随意使用下面提供的代码来试验不同的位置。下面是一个结果,显示了这个项目的有效运作。

import requests
from pprint import pprintAPI_Key = ""location = input("Enter Your Desired Location: ")weather_url = f"[http://api.openweathermap.org/data/2.5/weather?q={location}&appid=](http://api.openweathermap.org/data/2.5/weather?q={location}&appid=)"
final_url = weather_url + API_Keyweather_data = requests.get(final_url).json()pprint(weather_data)

作者截图

Enter Your Desired Location: Bangalore
{'base': 'stations',
 'clouds': {'all': 15},
 'cod': 200,
 'coord': {'lat': 12.9762, 'lon': 77.6033},
 'dt': 1641477819,
 'id': 1277333,
 'main': {'feels_like': 294.17,
          'grnd_level': 912,
          'humidity': 45,
          'pressure': 1013,
          'sea_level': 1013,
          'temp': 294.78,
          'temp_max': 296.05,
          'temp_min': 294.78},
 'name': 'Bengaluru',
 'sys': {'country': 'IN',
         'id': 2040609,
         'sunrise': 1641431598,
         'sunset': 1641472610,
         'type': 2},
 'timezone': 19800,
 'visibility': 10000,
 'weather': [{'description': 'few clouds',
              'icon': '02n',
              'id': 801,
              'main': 'Clouds'}],
 'wind': {'deg': 118, 'gust': 8.31, 'speed': 4.81}}

用户可以试用这个项目的一个独特的变化是,每当你启动这个程序时,显示你所在位置的天气报告。并且每隔几个小时继续显示一次,以便快速检查气候变化,从而相应地计划您的日程。您可以按照代码中的要求设置您想要的位置,并以类似于我在以前的一篇文章中提到的提醒应用程序的方式构造项目。请随意从下面提供的链接中查看。

用户可以做出的另一个额外改进是构建一个 GUI 应用程序来相应地显示天气状况。如果你不太熟悉 Python 的图形工具,你可以看看我以前的一篇文章,了解更多关于 7 个最好的 GUI 的入门代码。

</7-best-ui-graphics-tools-for-python-developers-with-starter-codes-2e46c248b47c>

结论:

NOAA 在 Unsplash 上拍摄的照片

"无论你去哪里,无论天气如何,都要带上自己的阳光."安东尼·j·德安杰洛

如前所述,天气在我们的日常生活中起着至关重要的作用。因此,开发一个天气应用程序来帮助我们成功地跟踪自然的这一不可或缺的元素是非常有益的,这样我们就可以相应地计划我们的时间表,并选择我们日常生活中的最佳行动路线。

在本文中,我们学习了如何用大约十行 Python 代码构建一个天气预报应用程序。我们研究了简单的 API 密钥生成,并使用请求模块来访问显示特定位置的准确天气报告所需的信息。我们还分析了一些额外的改进,我们可以习惯于这个项目,使它更有吸引力和有用。

如果你想在我的文章发表后第一时间得到通知,请点击下面的链接订阅邮件推荐。如果你希望支持其他作者和我,请订阅下面的链接。

https://bharath-k1297.medium.com/membership

如果你对这篇文章中提到的各点有任何疑问,请在下面的评论中告诉我。我会尽快给你回复。

看看我的一些与本文主题相关的文章,你可能也会喜欢阅读!

谢谢你们坚持到最后。我希望你们都喜欢这篇文章。祝大家有美好的一天!

为浓缩咖啡建立一个体面的形象

原文:https://towardsdatascience.com/developing-a-decent-profile-for-espresso-c2750bed053f

咖啡数据科学

试图模仿金快车

我等着买一台像样的浓缩咖啡机,因为我仍在寻找我的 Kim Express 机器的改进之处,但我最终买了这台由数据和控制回路驱动的机器。到达后,我立即着手制定一个与我的 Kim Express 相关的基线,然后从那里开始改进。

所有图片由作者提供

金特快列车

金快递是一个弹簧杠杆机器,在我看来,是最好的家用杠杆机器,特别是对于不受控制的水锅炉。这台机器提供了许多有趣的能力,但它们都不是金特快独有的。许多功能与其他杠杆机器相同,这些功能指导我充分利用 DE:

预浸:由于活塞允许水进入咖啡球,所有的杠杆机器都提供自然的预浸,并且这种预浸是可变的。使用控制杆,您可以缩短或延长预输注时间,甚至改变预输注压力。

水温:灯头在锅炉内部,所以灯头处的水温非常接近锅炉,这使得水温保持在锅炉的温度附近。因为加热器是不受控制的,所以仍然有一些温度冲浪,但它比其他机器更容易管理。

体面的个人资料

开发这些配置文件教会了我很多关于机器如何运行的知识,当我开始向前倾斜时,我的配置文件能够变得非常好。在这两者之间,我有许多个人资料,但我想强调最有趣的变化以及我是如何做到这一点的。

压力脉冲 1

这第一个轮廓是建立在开花轮廓上的。我用的是同样的预灌注,开花期间没有压力。我对脉冲进行了明确的转换,但这些脉冲有一个大约 3 秒的大间隔。我在金特快上的典型间隔是 0.5 秒。在 profile maker 中,我还被限制了最多 20 步。

我开始的时候水温是 97C,但是经过思考后我开始改变它。

压力脉动 2

我首先减少了预灌注时间,然后是温度,我在开花期增加了一个缓慢的斜坡。

压力脉动 3

预输注对我来说不太合适,所以我从一个较慢的斜坡开始,但它仍然由流量控制。我也开始最大限度地提高温度,因为我发现金快车的温度要高得多。事实上,我发现 116℃到 123℃之间的罐温范围对 Kim 来说是理想的,因为它提供了大约 2.2 巴的罐压,这对于预浸泡来说是理想的。

另外,我把开花期的流速调得比零高。金快车没有零压开花阶段,我认为这有助于排出一些释放的二氧化碳。

压力脉动 9

我延长了花期,并开始计算何时结束基于水进入冰球的预灌注。这可以在咖啡和重量中拨入。我的目标是过滤器在进入开花期之前被咖啡覆盖。人们建议使用秤和最初的几滴,但当时,我的 Acaia Pyxis 不被支持。这种情况正在改变,但最好的办法是用相机来提供这种反馈。

压力脉动 14

我把开花期调整到压力控制,就像 2.2 巴左右的 Kim Express。我还设置了 10 毫升的退出标准,因为我通常会等待大约那么多的液体出来。

然后这是偶然发生的:

冰球在开花阶段产生阻力,导致 PID 控制器中的质量波动,因为流量和压力控制器努力达到它们的目标。

我意识到,通常人们会把压力和流量作为你实际可以达到的目标,但也许这可以成为我的优势。

压力脉冲 16:改变脉冲

我进行了一项测试,将压力目标设置为高于适当的流量目标。我意识到这是一种不用 18 步就能产生更快脉冲的方法。

压力脉动 20

我将预输注变得更简单,并且我在压力脉动部分做了改变。开花期的流量目标是有问题的。

压力脉冲 27

我再次将开花期转换为压力目标,并在预输注中添加了第二期。第一阶段是让所有人自由达到我的压力目标,然后我开始寻找一个退出点。

压力脉冲 28

在压力脉动期间,我开始增加压力目标,并改变了流量和压力从 2 巴/步过渡到 6 巴/步的速度。改变步进限制器有助于增加脉冲频率。

压力脉动 30

然后,我修改了预输注,使其在流速低于 3 ml/s 时结束。这更接近我的全自动化目标。

压力脉动 33

我再次增加压力脉冲,我开始接近 1 秒的脉冲间隔。然而,压力徘徊在 3 到 4 巴之间。多次发射的平均压力在 3 到 4 之间。这远远低于人们认为你应该使用的 6 到 9 格浓缩咖啡,但我的提取率很高。

压力脉动 36

我再次增加脉冲的压力,但是我也发现了一个问题。如果我的拨片拨得不对或者剂量太高,压力脉冲就不会正常工作。相反,如果剂量太低或研磨太粗。所以我开始建立一个适应性档案。

压力脉冲 38 自适应

我定义了压力截止点,因此如果压力由于高阻力而变高,它可以下降到较低的压力并继续。我又做了一个改变,插入阶段,让压力下降一点,然后再尝试脉冲。每个阶段都有一套不同的压力和流量目标,以允许持续的压力脉动。

压力脉冲 40 手动

对于所有这些变化,我仍然没有在盖上过滤器的时候停止预输注。花开也不是在最好的时候结束的。所以我做了一个 profile 延长预灌注和开花来强迫我手动进入下一阶段。这一改变稍微影响了提取并减少了消耗。

更改日志

我做了很多小改动,还原了很多改动。我追踪它们是为了好玩,在这里未经编辑就把它们呈现出来。

绩效指标

我使用一些指标来评估技术之间的差异:最终得分和咖啡萃取。

最终得分 是记分卡 7 个指标(尖锐、浓郁、糖浆、甜味、酸味、苦味和回味)的平均值。当然,这些分数是主观的,但它们符合我的口味,帮助我提高了我的拍摄水平。分数有一些变化。我的目标是保持每个指标的一致性,但有时粒度很难确定。

用折射仪测量总溶解固体量(TDS),这个数字结合咖啡的输出重量和输入重量用于确定提取到杯中的咖啡的百分比,称为提取率(EY)** 。**

强度半径(IR) 定义为 TDS vs EY 控制图上原点的半径,所以 IR = sqrt( TDS + EY)。这一指标有助于标准化产量或酿造比的击球性能。

我还采用了每个配置文件的最大 TDS、EY、IR 和最终得分(味道)指标,以查看数据显示了什么,并警告有多次烘烤:

随着时间的推移,TDS、EY 和 IR 都开始出现大幅增长,但有些是倒退。也有一些断奏镜头混合在一起,在所有指标中得分更高。

我的 Pyxis 秤现在得到了支持,所以我做了一些基于输出重量的结束步骤的工作,特别是开花。然而,我仍然发现在过滤器被覆盖的基础上结束预浸的第二阶段已经提供了最好的提取产量。

如果你愿意,可以在推特、 YouTubeInstagram 上关注我,我会在那里发布不同机器上的浓缩咖啡照片和浓缩咖啡相关的视频。你也可以在 LinkedIn 上找到我。也可以关注我在订阅

我的进一步阅读:

我未来的书

我的链接

浓缩咖啡系列文章

工作和学校故事集

与 GPT-3 一起为一个非政府组织开发战略机器人

原文:https://towardsdatascience.com/developing-a-strategy-bot-for-an-ngo-39cddf912eba

如何创建自己的自定义版本的 GPT-3,甚至更好的结果

JESHOOTS.COMUnsplash 上拍照

这是怎么回事?

OpenAI 在 2020 年发布 GPT-3 时,自然语言处理(NLP)社区变得疯狂(类似于过去几个月由文本到图像模型创造的炒作,如 DALL-E 2稳定扩散)。几周之内,人们意识到并收获了用 GPT-3 创造惊人演示和应用的潜力,并取得了惊人的成果。2021 年 12 月,OpenAI 引入了微调 GPT-3 的能力,这意味着客户可以根据他们的特定应用创建他们自己的定制版本的模型。

在这篇博文中,我们将学习如何创建一个定制版的 GPT 3,我们还将看到一个非政府组织如何使用这项技术为年轻的社会企业家创建一个战略机器人。

为什么这很重要?

自从引入像 BERT微调这样的流行的最先进的 NLP 模型以来,这些 NLP 模型已经成为适应特定任务的主要机制。这种技术利用了 迁移学习 的概念:使用预先训练的模型,并使其适应新的(专门的)领域,这显著提高了模型性能并降低了训练成本。

来源:https://www . CSE . ust . hk/~ qyang/Docs/2009/tkde _ transfer _ learning . pdf

GPT-3 最初没有提供微调的选项。相反,它已经接受了各种任务和专业领域的训练,无需进一步训练就能表现出色。然而,随着时间的推移,组织开始意识到开箱即用的模型令人印象深刻,但往往不够好,不足以用于生产。

在定制数据集上微调 GPT-3 并创建其定制版本的选项将使模型性能超过组织可以轻松将其用于生产工作负载的阈值。

问题陈述

阿育王成立于 1980 年,是世界上最大的社会企业家网络。这些社会企业家开发系统改变解决方案,通常以战略文件开始,详细说明他们想要解决的问题,他们的方法,以及他们的解决方案如何扩大间接和系统的影响。制定这些策略可能是一项艰巨的任务,Ashoka 希望用一个策略机器人来支持社会企业家,即一个可以帮助编写这些策略的文本生成模型。

他们尝试了 GPT-3 的普通版本,发现结果很有希望,但对于他们的特定目的来说还不够好。他们知道微调 GPT-3 的选项,并需要有人可以在他们的数据集上训练模型,这就是我参与的地方🙂那么,让我们来看看我们该如何着手此事!

解决方案演练

阿育王有 4000 多份以前的战略文件样本,加上一点功能工程,是 GPT-3 的一个很好的训练数据集。在开始微调之旅之前,我们还想了解一下它的成本。最后,在开始培训之前,我们必须以特定的方式准备数据集。让我们一步一步来。

数据准备

Ashoka 提供的数据集由几列感兴趣的文本组成,我们的目标是将它们浓缩成两列:一列是提示文本,另一列是我们希望 GPT-3(理想情况下)从提示中生成的文本。这是根据 OpenAI 的指导方针,数据需要如何准备进行微调:

来源:https://beta.openai.com/docs/guides/fine-tuning

对于提示文本,我们可以使用数据集中名为简介的列。不过,有些还是太长了,所以我们决定只把那篇文章的前两句话作为提示。此外,我们发现,如果我们在提示符后附加一条简短的指令,GPT-3 的性能甚至会更好:

该练习的结果将如下所示:

作者图片

类似地,我们将把来自不同列的文本编译成完成特性,让 GPT-3 知道我们希望它生成什么。要了解这些策略需要什么,你可以查看 https://www.ashoka.org/en-us/story/strategy-plan-action 的。

需要注意的是,完成文本应该有一个独特的短语来表示文本生成的结束(关于这个的更多信息在 OpenAI 的网站)。因此,我们以这段代码结束了创建完成:

估价

既然我们已经准备好了数据,我们可以提交它来创建培训作业。不过,在我们这么做之前,我们想快速计算一下培训的成本——没有什么比培训一个模型之后才意识到成本远远超出预期更糟糕的了。(事实上,如果 OpenAI 能够提供一个预先估计微调工作价格的功能,那就太好了,但据我所知,在我撰写本文时,这个功能还不存在)

幸运的是 OpenAI 的定价网站给了我们一些如何计算价格的线索:培训最有能力的模型(达芬奇)的费用是每 1000 个代币 3 美分。该网站还声明,一个代币是文字块,1 个代币大约相当于 4 个字符:

来源:https://openai.com/api/pricing/

最后,OpenAI 还提供了一些关于我们应该在多少个例子上训练模型的指导:

来源:https://beta . open ai . com/docs/guides/fine-tuning/preparing-your-dataset

我们决定用 500 个例子,因此我们的价格估计是,培训将花费我们大约 10 美元,相当实惠🤗

微调

我们已经准备好了数据,我们很清楚微调的成本,所以现在是扣动扳机的时候了。 OpenAI 的 API 参考非常清晰且易于理解——要微调模型,我们只需上传训练文件并调用 FineTune.create API:

作者图片

提交培训工作后,大约需要两个小时才能完成,但这显然取决于您的培训数据,您的里程可能会有所不同。培训工作完成后,新模型将出现在操场应用程序中:

作者图片

现在我们终于可以测试我们自己的 GPT-3 模型了😃

测试

在测试这些模型时,我们没有太多可以自动化的东西——不幸的是,仍然没有好的基准来评估生成的文本是否“好”。所以我们做了自己的手工测试,我们对模型的表现感到非常惊讶。

该模型从一个简单的想法中创建的策略包括间接和系统的影响,它还包括围绕开源和培训其他组织的各种聪明的策略。这正是我们所需要的🎉

结论

在这篇博文中,我们看到了创建我们自己的 GPT-3 模型是多么容易(而且相当实惠)。我们已经看到,微调模型的结果超出了我们的预期,足以用于生产工作负载。Ashoka 目前正在内部测试和实施这一模式,并将向社会企业家推广,以帮助他们创造新的战略,让世界变得更好🌍

如果你有兴趣联系,请联系 LinkedIn https://www . aiml . consulting/

用 aiSTROM 框架开发成功的人工智能策略

原文:https://towardsdatascience.com/developing-a-successful-ai-strategy-with-the-aistrom-framework-a1d8f979b2a9

由 Dall-E2 生成。

如何实现人工智能技术的战略

大量人工智能项目失败。Rackspace Technology 的调查估计这个数字高达 34% [2]。这种失败很大程度上是由于管理层不理解人工智能技术的风险和复杂性,反之亦然,开发人员不知道如何扩展技术或业务需求。许多管理人员似乎认为人工智能项目就像典型的软件项目一样,然而,也有一些特殊的挑战。这些挑战可能涉及团队所需的技能、要求模型透明的法律问题、大数据治理、采用的文化挑战等等。

在我最近关于[aiSTROM——在 IEEE Access 1中开发成功人工智能策略](https://ieeexplore.ieee.org/document/9612182)的路线图的论文中,我提出了如何应对这些挑战的详细路线图。缩写 aiSTROM 代表以人工智能为中心的战略路线图。如需深入讨论,请参考全文 1。在接下来的内容中,我们将简要介绍 aiSTROM 路线图的各个步骤。

aiStrom 概述,图片基于1

1.发现机会

人工智能技术提供的机会可能是巨大的。aiSTROM 建议与领域级专家和技术专家一起举办头脑风暴研讨会,以提出一个 top- n 项目列表,例如前 5 个项目。在确定这些问题时要问的一些问题可能包括:我们的竞争对手在做什么?我们能自动化流程吗?我们能利用人工智能以新的方式做事并提供创新服务吗?

关于选择项目时需要注意的事项的完整列表和广泛讨论,请参考1。在下文中,将根据框架中的下一步,首先是"数据",审查每个项目的考虑因素。

2.数据

“大数据”的可用性是当前深度学习时代的催化剂之一,此外还有 CNN 等新技术以及 GPU 硬件的广泛可用性。我们可以将大数据的首次提及追溯到 1997 年的 Cox 和 ells worth3:

“……给计算机系统带来了一个有趣的挑战:数据集通常非常大,占用了主内存、本地磁盘甚至远程磁盘的容量。我们称之为大数据的问题。当数据集不适合主内存(在内核中)时,或者当它们甚至不适合本地磁盘时,最常见的解决方案是获取更多资源……”

大数据通常有三个特征:量(大量数据)、多样性(杂乱数据)和速度(快速增长)。这些最初的 V 有时会随着价值(商业价值)和准确性(偏见)而扩展。在机器学习中,需要大数据来训练我们的模型。这意味着我们需要建立必要的数据库或仓库基础设施来处理项目所需的容量。下面讨论一些需要记住的特殊注意事项。

数据来源。组织中是否已经有可用的数据?如果没有,可能需要收集,或者你会考虑购买数据吗?如果你考虑自己收集数据,请记住这需要很多时间,通常需要伦理委员会的批准,以及预处理和质量检查。然而,开始收集越早越好,即使你还不确定你是否需要这些数据。看看亚马逊 MTurk 等众包方法可能会提供一种比自我收集更快的替代方法,但在质量方面往往更差。找到正确、高质量的数据源是一项重要的决策。更多的数据通常是好的,但请记住贝尔曼[5]的“维数灾难”,以及维护大型数据池的成本高昂这一事实。

法律问题。存储/收集数据时,需要牢记大量隐私和安全注意事项。您是否遵守当地的隐私法,组织中谁有权访问数据等。是需要考虑的重要问题。有必要在允许人工智能员工大规模访问你公司的所有数据(快速开发想法)与隐私和安全之间找到一个良好的平衡。

存储数据。公司是组织内部存储(这涉及大量成本和管理人力),还是使用外部数据中心?在决定时,考虑数据应该存储在靠近客户所在地的地方,因为传输速度较慢。此外,这些数据是以原始形式存储,例如存储在一个数据湖中,还是经过预处理成为结构化形式?这些选择应考虑数据管理原则,如 CAP 定理和 PACELC 定理4

3.人工智能团队

开发人工智能模型不仅仅是一项具有技术挑战性的任务,它涉及前沿模型和创造力,以及研究水平的思维能力和问题解决能力以及领域知识。Herremans 在1 中提供了一个更全面的技能列表。公司经常雇佣博士级别的开发人员,和/或与学术机构合作。另一种快速雇佣大量人才库的方法是通过收购:当一家公司收购另一家公司,但只是为了重用这些人员。这方面的一个例子是 2005 年谷歌雇佣 Android,当时还没有开发出任何产品。

4.艾在公司

人工智能的发展将会如何发生?

团队定位。AI 团队将如何在组织中定位?一些组织选择分散的方法,每个部门负责自己的人工智能项目。在一个更集中的方法中,可以雇佣一个可以跨部门工作的 CAI。这种方法可以形成一个卓越的中心或研究实验室。最后,一些组织可能更喜欢混合方法。最佳设置将取决于组织的需求和准备情况。

投资组合方法。 AI 项目和金融资产有一些共同点:两者都会有风险等级。一家公司可以实施一系列人工智能项目,这将降低风险,并在其中一些项目出错时提供缓冲。

AIaas —人工智能即服务。一些服务已经存在,为什么不直接使用 API 呢?这可能有效,但如果服务对核心业务至关重要,则不建议这样做。

是否在内部开发?外包可能是快速进行开发的一个好选择。这个决定可能取决于公司的人工智能准备程度。除了内部开发,另一个选择是收购一家已经开发出该技术的公司。

敏捷。像任何 IT 项目一样,敏捷开发方法通常被推荐。另一个最佳实践是 MLOps,这是 DevOps 与机器学习的融合。

5.技术

我们在这里的目的不是概述人工智能技术。但是,有意思的是,要强调与技术选择相关的几点:

准确性与黑盒。 AI 系统是随机系统。经理可以命令开发商预测客户的行为,但不能保证模型的准确性。事实上,这往往取决于它是否是一个黑箱模型(更准确,但无法解释)。在某些领域,如信用评分,法律要求公司对做出决定的原因做出解释。在这种情况下,需要使用简单、不太准确的系统,如基于规则的系统,而(通常)更准确的深度学习模型本质上是黑盒模型。

人类在回路中。一些策略使用预先标记的数据集,例如,有人浏览了数据并标记了照片中的所有汽车。然而,在强化学习策略中,系统将不断地接受来自用户的反馈和纠正。

取代或增加人类。人工智能系统通常被视为对人类工作的威胁。实际上,它们为用户提供了机会,使他们能够更好、更有创造性、更容易、更好地控制自己的工作。

云与内部托管。这一决定将基于内部托管服务器的成本(人力资本+设备)与订阅费,后者通常比租用服务器更容易扩展。

6.KPI

像任何项目一样,从一开始就有清晰的基于价值的成功指标是至关重要的。这些应该与组织的战略目标相联系。请注意上面的“基于价值”一词,这表明我们应该不仅仅关注财务目标,还应该关注人工智能技术如何为客户或员工创造价值。另外,考虑到 AI 模型是随机的,准确性也不能保证,这也是要评估的事情!

7.设定风险等级

AI 系统在开发过程中需要监控的 风险 包括:

  • 它们的随机性。模型可能无法准确工作!
  • 偏见和道德。在开发的任何模型中,必须避免种族/性别/…习得性偏见
  • 安全。该模型应该能够抵抗对抗性攻击以及欺骗攻击。
  • 其他战略决策。风险可能源自之前的任何决策:内部开发、招聘、托管失败、数据不安全等。

在考虑风险的时候,我们还要权衡 利益 。理想情况下,这些由上述基于价值的 KPI 来捕获。

一个 SWOT 分析 可以把这两者放在一起,提供一个很好的管理决策概览。

8.促成文化转变

直接参与项目的人员,无论是 it 经理还是开发人员,都必须了解人工智能技术。甚至其他员工,尽管他们看起来与项目相去甚远,也为组织的整体文化做出了贡献。接受人工智能教育的员工可能会基于他们对公司的特定知识贡献想法和见解。

通过建立一个卓越中心,该公司可以提高意识并教育员工,这将导致整个组织拥抱人工智能技术。任何对被人工智能技术取代的恐惧通常都会通过提高人工智能素养来缓解,因此这将极大地有助于培养人工智能采用的文化。

结论

我希望已经提供了一些伴随着实施长期人工智能战略而来的战略管理决策的良好概述。aiSTROM 提供了一个路线图,指导经理在实施人工智能战略时通过不同的领域和需要战略决策的领域。

如果你有兴趣阅读更多的细节,请查看原文 aiSTROM 论文,或者联系讨论我可以如何帮助你!需要生成 aiSTROM 报表的界面或模板,或者有其他需求,请告诉我!

关于作者

dorien herre mans(IEEE 资深会员)在安特卫普大学获得应用经济学博士学位。她获得了玛丽-居里奖学金,在伦敦玛丽皇后大学数字音乐中心工作。在此之前,她于 2005 年毕业于安特卫普大学管理信息系统专业,成为一名商业工程师。之后,她在世界领先的瑞士布鲁切莱斯罗切斯酒店管理学院担任 Drupal 顾问和 IT 讲师。她目前是新加坡科技与设计大学的助理教授。在 SUTD,她还是 SUTD 游戏实验室的主任,并领导音频、音乐和人工智能(AMAAI)实验室以及人工智能金融(AIFi)小组。她的激情包括战略思维以及人工智能技术的新颖应用。她在许多委员会和董事会任职,并应邀在世界各地发表演讲。她入选了新加坡 2021 年 100 名科技女性榜单,该榜单旨在表彰和庆祝新加坡为科技行业做出重大贡献的鼓舞人心的女性。

参考

1 D. Herremans,“aiSTROM——开发一个成功的人工智能策略的路线图”,在 IEEE Access,第 9 卷,第 155826-155838 页,2021,doi:10.11109/Access . 2002003005

[2] 全球报告:组织在人工智能和机器学习方面取得成功了吗?2021 年【在线】可用:【https://www.rackspace.com/solve/succeeding-ai-ml/ty.】T2

3 M. Cox 和 D. Ellsworth,“用于核外可视化的应用程序控制的按需分页”, Proc .第八次会议。Vis。【VIS】《T5》,第 235 页,1997 年。

4 M. Chen,S. Mao 和 Y. Liu,“大数据:一项调查”,移动网络。申请,第 19 卷,第 2 期,第 171–209 页,2014 年。

[5] R. Bellman,自适应控制过程:导游,普林斯顿,新泽西州,美国:普林斯顿大学出版社,第 3 卷,第 2 页,1961 年。

开发软件:使用 Linux,即使是在 Windows 中

原文:https://towardsdatascience.com/developing-software-use-linux-even-in-windows-62dfaff0c3e3

使用相同的操作系统进行开发,测试和部署

在开发、测试和生产中使用相同的环境,因此对于开发,测试和部署。由托尔加·乌尔坎Unsplash 拍摄的照片

下面的六个词构成了今天的故事:

在 Linux 中部署?在 Linux 中开发。

这比乍看起来更重要——对任何软件来说,数据科学产品也不例外。

有些人可能认为开发环境并不重要,特别是当项目中使用的编程语言可以在任何操作系统下工作时。但事实远不止如此。

即使你的编程语言可以在任何操作系统中工作,也不意味着它在所有操作系统中的工作方式都是一样的。有时候差异可能不会太大;有时候是这样的。这就像说所有的汽车都以非常相似的方式工作。基本上是这样,但这是否意味着你会使用沙漠环境来开发一辆在斯堪的纳维亚国家使用的汽车?当然不是。在这两种情况下,都需要四轮驱动,但在沙漠中工作良好的汽车可能在严冬条件下工作不太好。也许会,但你不能确定,是吗?

对于软件,事情以类似的方式工作。如果你想开发一个好的产品部署在一个特定的环境中,你应该在尽可能相似的环境中开发它。在 IT 部门,通常不可能重新创建一个生产环境,尤其是当该环境已经运行了相当长一段时间,并且为其客户提供了许多不同的软件产品时。尽管如此,通常还是有可能创建一个类似的环境,而且越类似于生产环境越好。

虽然这个主题很笼统,但本文将集中讨论这个故事的一个方面——但也是最重要的一个方面:操作系统。更准确地说,我将关注开发人员的工作方式,并考虑以下问题:当产品将被部署在 Linux 环境中时,我是否应该,或者我是否能够在 Windows 中进行开发?

为了简单起见,我将集中讨论这两个操作系统,原因有二。第一,在行业中,这两者在软件项目中频繁混用。第二,我对这两个操作系统有经验,但对其他操作系统经验不多。根据我开发数据科学软件的经验,在 Windows 机器上开发并部署到 Linux 是很常见的。这就是我几年来的工作方式。而这是没什么好怕的,只要你知道怎么做。

当提到三种类型的环境之一时,我将使用以下缩写:

  • DEV for 开发环境,
  • 测试测试环境,以及
  • 生产环境产品。

为什么?

我想你会同意我的观点,在 IT 领域,开发和生产环境中最重要的一个方面是操作系统。通常情况下,它将是环境中最重要的方面,在开发、测试和部署时会产生很大的影响。

不成文的规定如下:

在尽可能接近生产环境的环境中开发和测试代码。

注意,规则并不严格。它没有说同样的环境,而是一个尽可能接近生产环境的环境。由于这个细节,这个规则给了你相当大的调整余地。您可以根据具体情况,尤其是项目所拥有的资源来使用这种余量。但是如果你不想冒险让这个项目给你带来超出你所能接受的麻烦,尽量不要过度使用这个余量。如果你真的过度使用保证金,那你就活该头疼,除非你这么做是因为别人让你这么做的。我曾经遇到过这种情况,我非常头疼,因为我得到的开发资源有限,没有任何测试资源。

我想说,这条规则不仅仅与编程或 IT 相关。这是一个相当普遍的规则,类似于我们在科学中发现的许多类似的规则——就此而言,在我们的生活中也是如此。

为了说明这一点,让我给你看一个完全不同的例子。想象你正在开发一种新的植物除草剂。你应该在它将被应用的环境中开发它。让它成为一种用于谷类植物的除草剂。如果是这种情况,那么你不应该想在果园里进行实验(这是开发过程的一部分),而是用谷物——以及不同的谷物品种,就此而言,不只是一种。此外,你永远不应该把除草剂当作肥料、食品配料或飞机燃料来测试。不,你应该在尽可能接近最终产品的环境中开发和测试产品。

在我们软件开发的舒适区的背景下,应该如何理解这一点?正如你将在下一句中看到的,我们应该以非常相似的方式来理解它。当开发一个软件产品时——不管这个产品是什么——你应该在一个类似于生产环境的环境中开发和测试它。

为什么不能直接用生产环境本身来开发和测试产品呢?当我们已经在使用这种环境时,这难道不是最简单的方法吗(这在行业中是常有的事),也是最好的方法?您几乎永远无法重新创建生产环境,除非是一个全新的环境。那么,为什么不使用它而不是重新创建它呢?

事实上,这个看起来是目前为止最好的解决方案:产品将在它将被部署的相同环境中被开发。这样,在开发过程中就已经知道产品将如何在这个环境中工作,可能会发生什么样的问题(如果有的话),等等。没有其他环境能给我们提供这样的信心,使我们相信产品在生产中会很好地发挥作用。**

即使这种方法可能很诱人…它不仅不是最好的,而且可能非常危险。在我看来,只有在极少数情况下,当你完全没有其他选择的时候,你才应该选择这个解决方案。在开发软件的时候,我们总是可以对开发环境做一些改变,甚至打破它。有时,要恢复环境,您需要从头重新安装。这就是为什么我们不应该在生产环境中开发的原因:虽然破坏开发或测试环境是每个人都接受的风险,但是破坏生产环境会带来不可接受的风险,尤其是当生产环境已经被其他软件产品使用时。

这就是为什么软件通常在开发环境中开发,在测试环境中检查,然后在生产环境中部署。这给了我们自然的顺序DEV → TEST → PROD,尽管大多数项目包括混合了三个步骤中的两个,通常是DEV ↔ TESTTEST ↔ PROD

不幸的是,现实常常强加限制,迫使软件开发者适应有限的资源。我们经常被迫在两种环境中工作:开发和生产。如果你不幸发现自己在这样的项目中,你必须选择其中的哪一个应该成为测试环境。不幸的是,这两种解决方案都不好——说实话,最终,这类项目的测试阶段并不是真正的测试。虽然涉及到一些测试,但这不是真正的产品测试,除非你能在生产环境中执行。我说过,这是,也应该是,业内非常罕见的情况。

这三个环境(包括测试)的可用性对于有重要时间限制的项目特别有用。在我过去的一个项目中,我必须检查一个附加包的重新安装。不幸的是,当我这样做时,新版本的包破坏了测试环境。要再次使用它,我必须完全重新安装,但由于时间限制,这是不可能的。因此,在这里我使用开发环境进行开发和测试。但是,如果这发生在只有两个环境(DEV 和 PROD)的项目中会怎么样呢?我将不得不重新安装开发,没有时间了;除了夜以继日地工作,我还有什么选择呢?幸运的是,我是一个幸运的开发人员,有三个环境可以使用。因此,我能够在开发环境中完成产品,尽管我没有真正的可能性来测试产品。我直接从开发转到生产,由于产品的复杂性,这让我很不舒服。此外,在开发期间,开发环境经历了产品环境中没有的小的修改;直到我在 PROD 上完成安装并尝试运行该应用程序的那一刻,我才意识到这一点。最终,在额外的工作之后,一切都结束得很好,我完成了这个项目,并获得了一些重要的经验,从那以后我一直在使用这些经验。拥有三个环境总是一件好事:开发、测试和生产;在 DEV 上开发,然后在 test 上测试,只有在一切就绪后,才在 PROD 中部署。

让我们总结一下为什么?这里的部分解释了为什么我们应该在尽可能接近生产的环境中开发。为此,让我们使用上面使用的更具体的场景,即 Windows 对 Linux 开发。

当你正在开发的软件产品要部署在 Linux 机器上、云中或任何地方时,你应该用 Linux 开发代码。我猜在业界,Linux 是几乎总是的生产操作系统。在我们创建软件解决方案的 20 多个数据科学项目中,我记得只有一个项目我们必须将解决方案部署到 Windows 服务器上。这不是一开始就计划好的,而是一系列事件的意外结果。

总而言之,作为一名软件开发人员,你应该尽最大努力在尽可能接近计划生产环境的环境中开发和测试软件产品——而不是要部署产品的实际生产环境。

这基本上意味着,无论何时您在一个项目中工作,其中的解决方案将在 Linux 中部署,您也应该在 Linux 中开发和测试代码。

怎么会?

我想前一部分现在对你来说非常有意义。是吗?

应该的。这是它应该如何工作。但是很多时候,不管是在小公司还是大公司,甚至是最大的公司,情况都不是这样。

假设我们的小公司雇佣了 2-3 名开发人员和大约 10 名其他人员。不是 It 公司,是用的多的公司;比方说,它应用数据科学来支持它的研究,然后将它的结果呈现给外人。它的软件产品是数据科学产品,因此它们使用户能够为他们的数据运行一些模型。

像往常一样,该公司的员工使用装有 Windows 操作系统的笔记本电脑。然而,由于规模太小,该公司没有足够的资源来维护自己的服务器,以部署和维护他们的产品。因此,他们转而租用服务器和各种云服务。

我想你已经明白我的意思了。开发人员在 Windows 笔记本电脑上工作,但是在 Linux 环境中部署他们的产品。这不是他们的选择;这是他们仅有的资源。他们很少能使用他们的私人资源,即使他们想用(我不会),所以他们被迫使用他们所拥有的。对于开发,该资源是一台 Windows 笔记本电脑——这台笔记本电脑就是我们应该考虑的开发环境。

他们应该如何进行?

我们可以列出很多方法,但我将强调其中的两种。人们使用一种时下流行的工具,集装箱化。然而,这不是你通常认为的标准集装箱化。当我设想容器时,我通常看到它们在 PROD 环境中实现。在这里,我们将讨论用于开发的容器,它们不同于 PROD 中使用的容器。当我们讨论开发容器时,请记住这一点。

第二种方法提供了第一种方法所没有的工作便利性。当在 Windows 机器上使用 Linux 时,这是可行的——但不使用虚拟机,因为这种方法,至少在我看来,很不方便。相反,这种方法使用 Linux 的 Windows 子系统(WSL)。我一直在使用它,在我几乎所有的项目中——我非常重视它。

在这两者之间,容器化使您能够使用类似的环境来生产。由于 WSL 使用安装在您机器上的 Linux 子系统,模拟 PROD 操作系统是非常困难的,通常是不可能的,特别是对于几个项目。WSL 的主要好处是它可以在大多数项目中使用,但是这个优点很容易变成一个缺点。一旦我们完成了开发容器,我们将回到这个方法。

开发容器

如前所述,它们与生产容器不同:前者用于在 DEV 环境中开发产品,而后者用于在 PROD 环境中安装产品(及其环境)。

开发容器提供了一个专用的开发环境。在这里,您可能有单元测试,这在生产容器中是不需要的。同样,您可能需要一些在生产中不需要的附加包。通常,开发容器的组织方式不同于生产容器。简单地说,组织它们就像在本地机器上开发代码一样,没有容器。

考虑下面的例子。您开发您的代码,以便一旦代码准备好就可以打包。因此,您的开发容器将包含一个文件夹,您将在其中保存要打包的代码。另一方面,您的生产容器将而不是包含这些原始代码。相反,这个包将从包注册中心安装到生产容器中。这导致两种类型的容器具有非常不同的组织结构。

通常,您的开发容器将提供与生产容器环境相似的环境。但是,它可以包含 PROD 中不需要的其他工具。因此,在开发环境中运行良好的代码不一定要在生产环境中以同样的方式运行——您需要检查这一点。然后你可以测试生产容器,为此,有一个测试环境是很好的。

在我看来,开发容器非常有意义,而且非常有帮助。然而,它们有一个明显的缺点:使用它们很困难。一方面,容器应该在不同的机器上以相同的方式运行。另一方面,它们会造成一些困难,尤其是在几台 Windows 机器上使用时;机器越多,发生意外的风险就越大。有时它可能是非常小的事情,但找到解决方案可能比看起来要困难得多。

用于 Linux 的 Windows 子系统(WSL)

WSL 提供了一个在 Windows 操作系统中模拟 Linux 的好方法。用了好几年了,很欣赏。

当您选择安装了 WSL 的 Windows 机器时,您可以同时使用 Windows 和 Linux。在许多项目中,您可能需要能够使用 Windows。当你别无选择,必须使用 Windows 机器时(这可能是由于公司资源的原因),WSL 是开发人员的绝佳选择,因为正如我之前提到的,大多数软件产品都部署在 Linux 中。

许多用户没有选择,但是我有:我可以选择 Linux 机器。而是选择了 Windows 机,我这么做是有原因的。我这样做是因为有时出于各种原因,我不得不使用 Windows。在这种情况下,Linux 机器没有任何帮助。所以,我有了 Windows——感谢 WSL,我也有了 Linux。如果我没有使用 WSL 的可能性,我会更喜欢使用 Linux 机器。但是自从我可以使用 WSL 以来,我完全可以使用 Windows 笔记本电脑。

我知道,这很重要。WSL 不是 Linux。它没有真正的 Linux 快。可能存在差异,有些差异不如其他差异显著。但是,作为一个开发代码的人,所有这些对我来说并没有什么大问题。例如,WSL 没有 Linux 快?我开发代码,而不是运行它;所以,这对我来说根本不是问题。其他问题?是的,我遇到过一些小问题(例如,行尾),但是它们太容易解决了,我甚至都不记得了。坦白地说,我确实遇到了一个关于行尾的大问题,但是是在…一个开发容器中!

在 Windows 机器上使用 WSL 还有一个好处。当你开发一个可以在两个操作系统中使用的产品时,你应该在两个操作系统中检查你的产品。虽然 WSL 当然不是真正的 Linux(尽管 WSL 2 确实有一个 Linux 内核),但是当某些东西在 WSL 中工作时,它很可能也在 Linux 中工作。例如,我已经编写或合作编写了四个开源包( easycheckmakepackageperftesterrounder);我在 WSL 中处理了每一个问题,然后在 Windows 中检查了每一个问题。

总而言之,当你在 WSL 中开发时,你可以在一个将要开发产品的操作系统中开发(除非这个 OS 没有 WSL 版本)。一旦完成了 WSL 的安装,一切都变得简单了。最重要的 ide 与 WSL 合作。我使用 Visual Studio 代码,由于专用的 WSL 扩展,它工作得非常好。当我在 WSL 中开发时,我有在 Linux 中开发的相同感觉。

老实说,现在我已经好几年没有在 Windows 上开发任何有意义的东西了,我感觉自己像个 Linux 迷。每当我转移到生产环境时,几乎总是在 Linux 中,一切都感觉很自然。如果你曾经试图在 Windows 中开发一个可以在 Linux 中部署的产品,你应该明白我的意思。我去过那里,那种感觉并不好。就好像产品内部的某些东西似乎是错误的,可能会导致大问题。WSL 是一种很好的止痛药,它让我作为开发人员的生活变得更好;实际上,是为了更好。

开发容器还是 WSL?

我和他们都合作过。虽然很难说哪个更好,但我知道我更喜欢哪个。这也是为什么我会把重点放在我的喜好上,并解释它们。然后你可以自己决定什么对你的项目更好。

老实说,如果没有我信任的 DevOps,我不会选择开发容器。我甚至不知道如何开始!有了一个好的 DevOps,开发容器可能是最好的解决方案——但是有时会减慢你的编码工作。在这种情况下,除非你知道如何解决这样的问题,否则你可能会感到非常沮丧,因为简单的事情似乎无法用简单的解决方案来解决,而你几乎肯定这些解决方案会奏效

我记得在一个项目中,我们的 DevOps 在一个两人数据科学团队中编排开发容器,加上他自己作为第三个成员。虽然他对此没有问题(向他致敬!),我们确实遇到了一些问题,他不得不时不时地帮助我们。有一两次,我不得不重新构建整个容器,因为事情停止工作,没有特别的原因,只有重新安装似乎有所帮助。对我来说,这是一项艰难的工作。我们所有人都必须使用 Visual Studio 代码(这对我来说不成问题,因为我以前就用过),以及一些与容器相关的扩展。

我确实记得我在那个项目中遇到的一个重大而不寻常的问题。DevOps 最终找到了问题的根源,那就是 Git。我使用安装在 WSL 中的 Git,这是我一贯的做法。很明显,这个 Git 工具没有很好地与我们的开发容器配合。相反,我应该使用 Git bash 。在我开始使用那个之后,所有这些问题都消失了。我不喜欢这种奇怪的问题。对我来说,这个例子表明开发容器可能很精致——对我的开发人员来说太精致了。

总而言之,我认为开发容器提供了一个很好的解决方案,但是对于没有丰富 DevOps 知识的人来说太难了。没有它,任何安装和更改看起来都很困难。如果你遇到一个更大的问题,你可能会觉得自己很无助,就像我在上面描述的项目中几次遇到的那样。

另一方面,WSL 提供了一个简单得多的解决方案。要在您的机器上安装它,您不必是 DevOps。在过去的几年里,我在 WSL 中没有遇到任何我自己不能解决的问题。这表明基于 WSL 的解决方案比开发容器更容易使用。

然而,WSL 并不是没有代价的。它提供给你一个 Linux 替代品,仅此而已。在那里安装了许多包,在 WSL 和生产容器中有完全相同的环境的可能性很小。因此,使用 WSL 意味着在 Linux 中工作,而不是在相同的——甚至是非常相似的——环境中工作。

因此,WSL 是否适合您取决于各种因素,其中最重要的两个因素是您的技能和项目本身。在许多情况下,当次要的细节不那么重要时,它可以是你的解决方案。但是如果他们这样做了,开发容器可以给你提供一个更大的机会,让你的产品运行良好。

我们讨论了在DEV → TEST → PROD链中开发软件产品的两种方法,但是还有其他方法。我描述了两个我知道并使用过的。如果你知道并使用其他方法在 Windows 下的开发环境中开发软件产品,但该产品将部署在 Linux* 下的生产环境*中,那么请在评论中描述它。**

你也可能对使用开发容器和 WSL 有不同的看法。请也在评论里分享一下吧。这篇文章旨在收集知识和观点,并将其传达给其他人,但这个话题很容易带有主观性——对我有效的不一定对你有效。

感谢阅读这篇文章。我意识到它比平常少了很多技术性,但这并没有降低它的重要性。事实上,我们已经触及了非常重要的话题。你看,DEV → TEST → PROD软件开发链构成了任何软件项目的本质。在一些项目中,它被改变了;例如,没有测试或者有一个额外的预生产(我们可以称之为PRE-PROD)环境。

您如何组织环境会产生影响,有时这种影响比您在编码过程中需要决定的一些特定的技术细节要大得多。因此,千万不要忽视DEV → TEST → PROD基础设施的重要性!

资源

*https://learn.microsoft.com/en-us/windows/wsl/about https://code.visualstudio.com/docs/devcontainers/containers *

使用 Streamlit 快速开发基于 Web 的实时视频/音频处理应用

原文:https://towardsdatascience.com/developing-web-based-real-time-video-audio-processing-apps-quickly-with-streamlit-7c7bcd0bc5a8

在本文中,我们将了解如何使用 Streamlit 创建浏览器就绪的实时视频/音频处理应用。

作者图片

Streamlit 是一个 Python 框架,开发人员可以使用它快速构建 web 应用程序,而无需前端编码。在此基础上,开发人员可以开发实时视频/音频处理应用程序,从用户的媒体设备接收视频/音频流,在最简单的例子中,只需大约 10 行代码。

由于这类应用程序是基于网络的,它们可以被部署到云上,与用户轻松共享,并拥有现代化和用户友好的用户界面。

这个技术堆栈对于创建视频/音频应用程序的演示和原型设计非常有用,例如人体或物体检测、风格转换、图像过滤器、语音识别、视频聊天应用程序等。

一个简单的基于网络的物体检测应用程序。用户可以在执行过程中交互更改阈值。 在线试玩🎈

一个基于 web 的风格转移应用程序示例。用户可以在执行过程中交互更改模型类型和模型参数。 在线演示🎈

您可以在下面的示例部分看到更多示例。

:这些样本应用托管在公共云上( Streamlit Cloud ),视频和音频流传输到云服务器上进行处理。虽然这些数据只在内存中处理,不会保存到任何存储中,但是,如果您担心,请不要使用它们。至于本文中的以下内容,我们可以在本地全部执行。此外,您可以按照下面的示例部分的说明,在本地尝试上述示例。

注:我在europhon 2022上做了一个关于这个主题的演讲,题目是“使用 Streamlit 的实时浏览器计算机视觉应用”演讲视频如下:

更新:本文已于 2022/09/02 更新,使用的是从 v0.40.0 开始可用的streamlit-webrtc新引入的 API。

基于网络的应用程序的优势

我们通常使用 OpenCV 来构建图像或视频处理的实时演示应用。你们中的一些人(尤其是这类领域的开发人员或研究人员)可能已经多次看到下面的代码或类似的代码。

import cv2

cap = cv2.VideoCapture(0)

while True:
    ret, frame = cap.read()

    img = cv2.Canny(frame, 100, 200)  # Some image processing

    cv2.imshow('frame', img)
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

cap.release()
cv2.destroyAllWindows()

与上面使用运行在本地环境的cv2.VideoCapturecv2.imshow的 GUI 应用程序相比,基于网络的应用程序有如下优势。

易于共享和运行:

  • 如果我们在云上部署应用程序,我们可以通过发送 URL 与用户分享应用程序。
  • 用户只有通过网络浏览器才能使用这些应用程序。它不需要任何设置或外部依赖。

可在智能手机上使用:

  • 因为用户只需要网络浏览器,所以他们可以在智能手机上使用这些应用。如果我们能在这样的便携设备上展示演示,那就太方便了。

用户友好的用户界面:

  • 开发人员可以使用文本输入、滑块或其他基于 web 的组件来接受用户输入或显示数据。近来,这种基于网络的用户界面比桌面图形用户界面对用户更友好。

辅导的

我们将创建一个简单的基于 web 的实时视频处理应用程序,其 LoC 约为 10 或 20。请在有网络摄像头和麦克风的环境中尝试本教程。

你可以在这个资源库里查看这个教程的最终结果。此处是部署的在线演示🎈

在本教程中,我们将在app.py中编写代码。请先创建一个空的app.py

$ touch app.py

安装必要的软件包

接下来,我们必须安装本教程所需的软件包。

$ pip install -U streamlit streamlit-webrtc opencv-python-headless
  • streamlit:Streamlit 主包。
  • streamlit-webrtc:Streamlit 的定制组件,处理实时视频和音频流。
  • opencv-python-headless : OpenCV。我们在这里选择 headless 版本,因为我们将使用 Streamlit 构建 UI。

第一次接触 Streamlit

注意:如果您有使用 Streamlit 的经验,请跳过这一部分。

首先,用下面的命令启动 Streamlit。请运行与app.py相同目录下的命令。

$ streamlit run app.py

过一会儿,Streamlit 服务器进程将启动。然后访问 http://localhost:8501 看到如下图的页面(或者默认会在浏览器中自动打开)。这里的截图是在黑暗模式下,如果你使用的是光明模式,看起来会有所不同。

这时,网页上没有内容,因为app.py是空的。我们将在 Streamlit 应用程序的app.py中添加代码行。

用你的编辑器打开app.py,写下下面的代码。

import streamlit as st

st.title("My first Streamlit app")
st.write("Hello, world")

当您保存文件时,Streamlit 将检测文件更改,并在屏幕右上角显示“重新运行”和“总是重新运行”按钮。

单击“重新运行”按钮。然后网页被重新加载,页面内容如下所示。网页内容基于app.py代码生成。

如果你点击了“总是重新运行”按钮,每次文件改变时,页面会自动重新加载。

请注意,在更新app.py时,您必须按照下面的说明重新加载页面。

现在,我们已经了解了 Streamlit 应用的基本开发流程。你用像st.title()st.write()这样的 Streamlit 组件编写 Python 代码并传递给streamlit run命令,然后 Streamlit 在网页上生成相应的前端内容。

在下一节中,我们将看到如何在 Streamlit 之上开发一个实时视频处理应用程序。除此之外,Streamlit 本身涵盖了更多的用例,如机器学习、数据科学或更通用的用途。此类用例请参见官方 Streamlit 教程举例。

引入实时视频/音频流组件

如下更新app.py

import streamlit as st
from streamlit_webrtc import webrtc_streamer

st.title("My first Streamlit app")
st.write("Hello, world")

webrtc_streamer(key="example")

我们用webrtc_streamer()添加了一行。web 应用程序将类似于下面的屏幕截图。

在第一次试用时,可能需要一些时间来编译软件包,以便在单击“重新运行”按钮后,页面在一段时间内保持显示“正在运行”的消息。在这种情况下,请等待该过程完成。

单击“开始”按钮开始视频和音频流。第一次试用时,可能会要求您允许使用网络摄像头和麦克风。在这种情况下,请给予许可。

上面的webrtc_streamer(key="example")是一个 Streamlit 组件,它通过 web 浏览器处理视频和音频实时 I/O。key参数是脚本中标识组件实例的唯一 ID。我们在这里将其设置为"example",但是您可以使用任何字符串。该示例中的组件仅接收来自客户端网络摄像头和麦克风的视频和音频,并输出原始流。这是组件的最基本版本。我们将通过在以下部分添加其他选项来增强它的功能。

实时视频处理应用程序的开发

如下更新app.py

import streamlit as st
from streamlit_webrtc import webrtc_streamer
import av
import cv2

st.title("My first Streamlit app")
st.write("Hello, world")

def callback(frame):
    img = frame.to_ndarray(format="bgr24")

    img = cv2.cvtColor(cv2.Canny(img, 100, 200), cv2.COLOR_GRAY2BGR)

    return av.VideoFrame.from_ndarray(img, format="bgr24")

webrtc_streamer(key="example", video_frame_callback=callback)

像上一节一样,通过单击“开始”按钮来尝试一下。在这个新示例中,您可以发现图像过滤器被应用于视频流。

我们已经定义了一个接收输入帧并返回输出帧的回调。我们还将图像处理(本例中是边缘检测)代码放在回调函数中。于是,我们通过回调把图像处理代码注入到实时视频 app 中。

关于代码的详细解释如下。

  • webrtc_streamer()可以通过video_frame_callback自变量取一个函数对象作为回调。
  • 回调接收并返回输入和输出图像帧。这些是来自[PyAV](https://github.com/PyAV-Org/PyAV)[VideoFrame](https://pyav.org/docs/develop/api/video.html#av.video.frame.VideoFrame)类的实例。PyAV库是ffmpeg的 Python 绑定,提供视频和音频功能。它作为streamlit-webrtc的依赖项安装。
  • 回调的参数是来自网络摄像头的输入视频流中的图像帧。可以用frame.to_ndarray()转换成 NumPy 数组。
  • 回调的返回值显示在屏幕上。在上面的示例中,要返回的新的VideoFrame对象是从一个 NumPy 数组中生成的,带有av.VideoFrame.from_ndarray(img, format="bgr24")
  • 任何代码都可以放在回调函数中。在上面的例子中,我们使用了边缘检测滤波器cv2.Canny(img, 100, 200)(和灰度转换器cv2.cvtColor(img, cv2.COLOR_GRAY2BGR))作为例子。

现在,我们已经创建了一个浏览器就绪的实时视频处理应用程序!在这个例子中,我们使用了一个简单的 Canny 边缘检测器,您可以在您的原始应用程序中用任何图像处理代码替换它。

如果我们对该部分使用对象检测或样式转换,该应用程序将类似于本文开头的截图。

接收用户输入

如下更新app.py

import streamlit as st
from streamlit_webrtc import webrtc_streamer
import av
import cv2

st.title("My first Streamlit app")
st.write("Hello, world")

threshold1 = st.slider("Threshold1", min_value=0, max_value=1000, step=1, value=100)
threshold2 = st.slider("Threshold2", min_value=0, max_value=1000, step=1, value=200)

def callback(frame):
    img = frame.to_ndarray(format="bgr24")

    img = cv2.cvtColor(cv2.Canny(img, threshold1, threshold2), cv2.COLOR_GRAY2BGR)

    return av.VideoFrame.from_ndarray(img, format="bgr24")

webrtc_streamer(key="example", video_frame_callback=callback)

然后点击“开始”按钮。你会发现在这个例子中有 2 个滑块。您可以通过滑块修改cv2.Canny()的参数,即使是在实时执行期间。

有了这次更新,

  • 我们添加了threshold1threshold2变量。
  • 我们添加了两个带有st.slider()的滑块组件,并将它们的值赋给这些变量。st.slider()是 Streamlit 的内置组件。它的官方 API 参考是https://docs . streamlit . io/library/API-reference/widgets/ST . slider
  • 然后我们将这些变量作为参数传递给回调函数中的cv2.Canny()

现在我们有交互式输入来控制实时视频过滤器!

回调的执行模式和重要注意事项

与 OpenCV 不同,streamlit-webrtc需要回调来处理图像和音频帧。这种基于回调的设计是 OpenCV GUI 和streamlit-webrtc之间的一个主要区别,关于它有一些事情你必须知道。

请注意,回调是在一个分叉线程中执行的,该线程不同于运行 Streamlit 应用程序代码的主线程。它做了如下一些限制。

  • global关键字在回调中没有像预期的那样工作。
  • 诸如st.write()之类的 Streamlit 方法不能在回调中使用。
  • 回调内部和外部之间的通信必须是线程安全的。

将应用部署到云

我们将把 web 应用程序部署到云中,让每个人都可以使用它。

配置 WebRTC

要将应用程序部署到云中,我们必须将rtc_configuration参数添加到webrtc_streamer()中。

webrtc_streamer(
    key="example",
    video_frame_callback=callback,
    rtc_configuration={  # Add this line
        "iceServers": [{"urls": ["stun:stun.l.google.com:19302"]}]
    }
)

当服务器在远程主机上时,此配置是建立媒体流连接所必需的。

streamlit_webrtc使用 WebRTC 进行视频和音频流传输。它必须访问全局网络中的“STUN 服务器”,以便远程对等点(确切地说,是 NAT 上的对等点)建立 WebRTC 连接。虽然我们在本文中没有看到关于 STUN 服务器的细节,但是如果感兴趣的话,请使用关键字如 STUN、TURN 或 NAT traversal 来搜索它。

在上面的例子中,我们将代码配置为使用 Google 提供的免费 STUN 服务器。您也可以使用任何其他可用的 STUN 服务器。

参数rtc_configuration的值将被传递给前端的[RTCPeerConnection](https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/RTCPeerConnection)构造函数。

HTTPS

我们必须通过 HTTPS 在远程主机上提供网络应用程序,以使用网络摄像头或麦克风。

不仅我们在这里使用的webrtc_streamer()组件,而且任何访问客户端网络摄像头或麦克风的前端应用程序都使用[MediaDevices.getUserMedia()](https://developer.mozilla.org/ja/docs/Web/API/MediaDevices/getUserMedia) API。这个 API 不能在“不安全的上下文”中工作

文件

简而言之,安全上下文是使用 HTTPS 或 *file:///* URL 方案加载的页面,或者从 *localhost* 加载的页面。

media devices . getuser media()-隐私和安全

因此,我们需要 HTTPS 在访问客户端网络摄像头或麦克风的远程主机上提供网络应用。

流线云

我推荐使用 Streamlit Cloud 托管 Streamlit 应用。只需点击几下鼠标,你就可以从 GitHub 库部署应用程序,它会通过 HTTPS 自动提供应用程序。而且 Streamlit Cloud 似乎提供了比 Heroku free-tier 更好的运行时,而 Streamlit Cloud 免费提供了大部署容量。

其用法请参考公文

我在 Streamlit Cloud 上实际部署了我们在本文中看到的 app:https://share . Streamlit . io/whit phx/Streamlit-webrtc-article-tutorial-sample/main/app . py

它的 GitHub 资源库是https://GitHub . com/whit phx/streamlit-webrtc-article-tutorial-sample

注意添加了requirements.txt来在 Streamlit 云环境中安装必要的依赖项(streamlit-webrtcopencv-python-headless):https://github . com/whit phx/Streamlit-webrtc-article-tutorial-sample/blob/main/requirements . txt

通知;注意

如上所述,源自客户端设备的视频和音频流被传输到服务器并在服务器处被处理。

因此,这个库是不可扩展的,并且依赖于网络连接。您可能认为它主要用于原型制作或演示目的。

如果担心将媒体传输到远程云服务器,你还必须考虑将应用托管在本地网络中。

例子

本部分是在https://github.com/whitphx/streamlit-webrtc的样品清单的副本。

展示包括以下例子和更多

⚡️Repository🎈在线演示

  • 对象检测(这是本文开头的样例应用程序的截图)
  • OpenCV 过滤器
  • 单向视频流
  • 音频处理

您可以在您的本地 env 上使用以下命令来试用这个示例应用程序。

$ pip install streamlit-webrtc opencv-python-headless matplotlib pydub
$ streamlit run https://raw.githubusercontent.com/whitphx/streamlit-webrtc-example/main/app.py

实时语音转文本

⚡️Repository🎈在线演示

它能实时将你的声音转换成文本。这个 app 是自带的;它不依赖于任何外部 API。

实时视频风格传输

⚡️Repository🎈在线演示

它将各种各样的风格转换过滤器应用于实时视频流。

视频聊天

⚡️Repository (不提供在线演示)

你可以用大约 100 行 Python 代码创建视频聊天应用。

东京 2020 象形图

⚡️Repository : 🎈在线演示

MediaPipe 用于姿态估计。

音频呢?

您可以像处理视频一样处理音频流。如果您定义了一个回调函数并将其传递给audio_frame_callback参数,那么回调将会在音频帧中执行。在音频的情况下,回调的输入参数和返回值是[AudioFrame](https://pyav.org/docs/develop/api/audio.html#module-av.audio.frame)的实例。

请参见上面示例中的更改音频增益的示例应用程序或语音转文本应用程序的源代码。

原载于https://www . whit phx . info

计算机视觉的发展

原文:https://towardsdatascience.com/developments-occurring-within-computer-vision-9d485a072d46

概述计算机视觉领域以及技术基础设施的进步如何支持其发展和可扩展性

Nubelson Fernandes 在 Unsplash 上的照片

从事计算机视觉(CV)工作的人工智能(AI)从业者和开发者在计算机和计算机系统内实施和集成涉及视觉的问题的解决方案。图像分类、人脸检测、姿态估计和光流是 CV 任务的典型例子。

深度学习算法非常适合解决计算机视觉问题。卷积神经网络的结构特征使得能够检测和提取图像数据中存在的空间模式和特征。换句话说,机器可以识别和分类物体,甚至对它们做出反应。

因此,计算机视觉工程师称自己为深度学习工程师或只是普通的旧机器学习工程师。

计算机视觉是一个快速发展的领域,包括研究、商业和商业应用。对计算机视觉的高级研究现在可以更直接地应用于商业领域。

计算机视觉领域正在快速向前发展,这使得 CV 专家必须跟上最新的发现和进步。

关键要点

  • 帮助扩展深度学习解决方案的云计算服务
  • 自动机器学习(AutoML)解决方案减少了标准机器学习流程中所需的重复工作量
  • 研究人员努力使用变压器架构来优化计算机视觉任务

云计算

云计算通过互联网向个人或企业提供计算资源,如数据存储、应用服务器、网络和计算基础设施。与使用本地资源执行计算相比,云计算解决方案为计算资源可用性和扩展提供了快速且经济高效的解决方案。

机器学习解决方案的实施需要存储和处理能力。在机器学习项目的早期阶段(数据聚合、清理和争论)对数据的关注涉及用于数据存储和应用/数据解决方案接口访问的云计算资源(BigQuery、Hadoop、BigTable)。

泰勒·维克在 Unsplash 上的照片

最近,具有计算机视觉能力的设备和系统显著增加,例如用于步态分析的姿态估计、用于移动电话的面部识别、自动车辆中的车道检测等。

对云存储的需求正在增加,据预计2021 年该行业的价值将达到 3903.3 亿美元,是当前市场价值的五倍。

预计市场规模和应用程序的计算机视觉将大幅增长,从而导致使用入站数据来训练机器学习模型的数量增加。开发和训练 ML 模型所需的数据样本的增加与更大的数据存储容量需求和广泛强大的计算资源直接相关。

GPU 可用性的提高加速了计算机视觉解决方案的发展。然而,当服务于数千甚至数百万消费者时,仅靠 GPU 并不总能提供这些应用所需的可扩展性和正常运行时间。这个问题的显而易见的答案是云计算。

云计算平台,包括亚马逊网络服务(AWS)谷歌云平台(GCP)微软 Azure ,为机器学习和数据科学项目管道的核心组件提供解决方案,包括数据聚合、模型实现、部署和监控。

提高对与计算机视觉和通用机器学习相关的云计算服务的认识,会使任何 CV 工程师在企业中占据优势。通过进行深入的成本效益分析,可以确定云计算服务的好处。

一个很好的经验法则是,确保作为一名 CV 工程师,您了解或以某种形式接触到至少一个主要的云服务提供商及其解决方案,包括它们的优势和劣势。

大规模计算机视觉需要云服务集成

以下是支持典型计算机视觉操作的 NVIDIA 服务的示例,以强调哪种类型的云计算服务适合 CV 工程师。

利用英伟达广泛的预训练深度学习模型的英伟达图形处理单元云(NGC)目录抽象出深度学习模型实施和训练的复杂性。深度学习脚本为 CV 工程师提供现成的管道,可定制以满足独特的需求。健壮的模型部署解决方案自动向最终用户交付模型。

此外, NVIDIA Triton 推理服务器支持在任何基于 GPU 或 CPU 的基础设施上部署来自 TensorFlow 和 PyTorch 等框架的模型。NVIDIA Triton 推理服务器提供了跨各种平台的模型可扩展性,包括云、边缘和嵌入式设备。

此外,NVIDIA 与云服务提供商如 AWS 的合作伙伴关系支持部署基于 CV 的资产的能力。无论是 NGC 还是 AWS,通过利用由英伟达专家整合的打包解决方案,对基础设施和计算资源的考虑都很少。这意味着 CV 工程师可以更专注于模型性能和优化。

鼓励企业在可行的情况下降低成本和优化策略。云计算和云服务提供商通过提供基于使用和基于服务需求扩展的计费解决方案来满足这一需求。

AutoML

机器学习算法和模型开发是涉及许多任务的过程,这些任务可以通过创建自动化操作管道从自动化和减少手动过程中受益。

以特征工程和模型选择为例。特征工程是一个涉及从数据中检测和选择相关信息和属性的过程,该过程非常适合于描述数据集或提高基于机器学习的解决方案的性能。

模型选择涉及评估一组机器学习分类器、算法或给定问题的解决方案的性能。这些活动需要 ML 工程师和数据科学家花费相当多的时间来完成,并且经常需要从业者重新访问程序操作以提高模型性能或准确性。

人工智能(AI)领域致力于自动化机器学习过程中的大量手动和重复操作,称为自动化机器学习或 AutoML。

斯蒂芬·道森在 Unsplash 上拍摄的照片

有几个正在进行的大型项目来简化机器学习项目管道的复杂性。AutoML 是一项超越抽象的努力,它专注于 ML 工作流和过程的自动化和增强,以使 ML 对于非 ML 专家来说容易和可访问。

花一点时间来考察汽车行业的市场价值,预测预计到 2030 年汽车市场将达到 140 亿美元。这意味着其规模将比目前的价值增加近 42 倍。

计算机视觉项目有一系列重复的任务来实现预期的目标。参与模型实现的 CV 工程师太了解了。寻找合适的超参数的重复工作的数量使得模型的训练能够收敛到最佳损失并实现期望的精度,该过程被称为超参数优化/调整。

模型选择和特征工程是耗时且重复的过程。AutoML 是机器学习管道中自动化重复过程的努力。

机器学习和自动化的这种特殊应用正在获得牵引力。CV 工程师需要了解 AutoML 的优势和局限性。

实践中的自动化

AutoML 仍然是一项专注于自动化标准机器学习程序的新技术。然而,从长远来看,所获得的优势是显著的。

AutoML 对 CV 和 ML 工程师的一个明显好处是节省时间。数据聚合、数据准备和超参数优化是耗时的过程,可以说不使用 ML 工程师的核心技能和能力。

超参数调优涉及一个带有有根据猜测的试错过程。虽然数据准备和汇总是必要的过程,但它们涉及重复的任务,并取决于找到适当的数据源。事实证明,AutoML 功能在自动化这些流程方面非常成功,使 CV 工程师能够将更多的时间和精力投入到要求更高、更有成就感的任务中。

AutoML 及其应用程序,尤其是数据源,仍然有助于数据质量,主要是模型性能。特定于问题领域的高质量数据的获取对于自动化来说还不成熟,需要专业的人工观察和监督。

对于那些对探索 GPU 驱动的 AutoML 感兴趣的人来说,广泛使用的基于树的流水线优化工具(TPOT) 是一个自动化的机器学习库,旨在通过遗传编程优化机器学习过程和流水线。 RAPIDS cuML 提供通过 GPU 计算资源加速的 TPOT 功能。这篇文章提供了更多关于 TPOT 和急流城的信息。

机器学习库和框架

机器学习库和框架在任何 CV 工程师的工具包中都是必不可少的。ML 库和框架的发展和进步是渐进和持续的。主要的深度学习库如 TensorFlowPyTorchKeras和 MXNet 在 2021 年得到了不断的更新和修复,没有理由认为这不会持续到 2022 年。

最近,在以移动为重点的深度学习库和软件包方面取得了令人兴奋的进展,这些库和软件包优化了常用的 DL 库。

MediaPipe 在 2021 年扩展了其姿态估计功能,通过 BlazePose 模型提供 3D 姿态估计,该解决方案可在浏览器和移动环境中使用。在 2022 年,预计会看到更多涉及动态运动的使用案例中的姿态估计应用,并需要稳健的解决方案,如舞蹈中的运动分析和虚拟角色运动模拟。

PyTorch Lighting 由于其简单性、对复杂神经网络实现细节的抽象以及对硬件考虑的增强,在研究人员和专业机器学习实践者中越来越受欢迎。

最先进的深度学习

深度学习方法长期以来一直被用来应对计算机视觉挑战。用于进行面部检测、车道检测和姿态估计的神经网络架构都使用卷积神经网络的深度连续层。

CV 工程师非常了解 CNN,并且需要更加适应该领域的研究发展,特别是使用 Transformer 来解决计算机视觉任务。Transformer,2017 年《注意力是你需要的全部》论文中介绍的深度学习架构。

该文章提出了一种新的方法,通过利用注意力机制来导出输入数据的一部分相对于其他输入数据段的重要性,从而创建数据的计算表示。变压器神经网络架构没有利用卷积神经网络的惯例,但是研究已经展示了变压器在视觉相关任务中的应用。

通过 NGC 目录探索变压器模型,其中包括 PyTorch 中实际变压器模型的架构和利用的详细信息。

变压器在 NLP 领域产生了相当大的影响,只需参考 GPT(生成式预训练变压器)和伯特(来自变压器的双向编码器表示)的成就。

这篇发表于 2021 年最后一个季度的论文,提供了 Transformer 网络架构在计算机视觉中的应用的高级概述。

CV 工程师对应用 ML 感兴趣,不熟悉阅读研究论文,那么这篇就为大家呈现了一套系统的阅读和理解研究论文的方法。

移动设备

边缘设备变得越来越强大,设备上的推理能力是期望快速服务交付和人工智能功能的客户所使用的移动应用程序的必备功能。

照片由 Unsplash 上主屏化

在移动设备中结合计算机视觉使能功能减少了获得模型推断结果的等待时间;将计算机视觉功能集成到移动设备中可带来诸多优势,例如:

  • 减少获得模式推断结果时的延迟。在设备上进行和提供的推理结果不依赖于云服务器,反而减少了对推理结果的等待时间。
  • 根据设计,设备上的推理功能限制了数据从设备到云服务器的传输。该功能增强了数据的保密性和安全性,因为几乎没有数据传输要求。
  • 消除对云 GPU/CPU 服务器的依赖以进行推理的成本降低提供了额外的财务优势。

许多企业正在探索其产品和服务的移动产品,这也包括探索如何在移动设备上复制现有人工智能功能的方法。CV 工程师应该知道几个平台、工具和框架来实现移动优先的 AI 解决方案。

摘要

随着人工智能越来越多地融入我们的日常生活,计算机视觉技术的使用将会增加。随着它在我们的社会中变得越来越普遍,对具有计算机视觉系统知识的专家的需求将会上升。

CV 工程师必须紧跟行业的最新发展和趋势,以保持领先地位并利用最新的进步。2022 年,你应该意识到 PyTorch 照明、以移动为重点的深度学习库以及变形金刚在计算机视觉应用中的使用越来越受欢迎。

此外,边缘设备正变得越来越强大,企业正在探索其产品和服务的移动产品。以移动为重点的深度学习库和包值得关注,因为它们很可能在未来一年中增加使用。

2022 年,预计 AutoML 功能将得到更广泛的应用,ML 库和框架将继续增长。增强和虚拟现实应用的不断发展将允许 CV 工程师将其技能扩展到新的领域,如开发将真实对象复制到 3D 空间的直观有效的方法。计算机视觉应用将继续改变和影响未来,在支持计算机视觉系统的技术基础设施方面将会有更多的发展。

这篇文章的一个版本最早出现在 Nvidia 开发者博客

摘要

完成步骤 1-4,了解我在媒体和其他平台上制作的最新内容。

  1. 成为推荐媒介会员,支持我的写作
  2. 订阅我的 YouTube 频道
  3. 订阅我的播客 苹果播客|Spotify|可听
  4. 订阅我的 邮件列表 获取我的简讯

利用卷积神经网络从 X 射线图像中诊断肺炎

原文:https://towardsdatascience.com/diagnosing-pneumonia-from-x-ray-images-using-convolutional-neural-network-fe9975cab808

图像分类技术在医学诊断中的应用

乌曼诺德Unsplash 上拍摄的照片

免责声明:本文中的信息无意提供或替代任何专业医疗建议、诊断或治疗。

介绍

深度学习,也称为神经网络,近年来获得了牵引力,现在被广泛应用于多个领域,其中一个受欢迎的应用是使用卷积神经网络(" CNN ")的图像分类。在医学诊断领域中,也正在探索图像分类的潜力。在本文中,我将讨论如何构建一个卷积神经网络,帮助我们从胸部 x 射线图像中诊断肺炎。

神经网络

首先,神经网络可以描述为一系列算法,其目标是识别数据中的关系或模式。简单的神经网络由不同类型的层组成,即输入层、输出层和隐藏层。每一层都包含许多神经元,每个神经元都由算法组成,这些算法将前一层的输出作为输入,生成进一步的输出并将它们传递到下一层。

下图显示了一个简单神经网络的示例架构。在本例中,输入层中有 4 个变量,用绿色圆圈表示。它们被输入到第一个隐藏层中由蓝色圆圈表示的 4 个神经元中的每一个。然后,这些神经元将产生进一步的输出,传递给第二个隐藏层中的神经元。最后,第二个隐藏层的输出将进入由橙色圆圈表示的输出层中的神经元,该神经元将生成最终输出。

作者图片

卷积神经网络(CNN)

卷积神经网络是一类通常应用于与图像相关的任务的神经网络。一般来说,它包括 3 个不同类型的层,即卷积层,池层和全连接层。

完全连接的层基本上与上面简单神经网络示例中的隐藏层相同。下面我将依次讨论卷积层和池层。

卷积层

顾名思义,卷积层对数据进行卷积运算。这是一种数学运算,将两个数字数组相乘,得到第三个数字数组。

例如,在下图中,3×3 过滤器(或内核)中的每个值都乘以 4×4 图像的 3×3 子部分,并且相乘的结果在每个子部分中相加。然后,滤波器在图像上移动到下一个子部分,并重复计算,直到填满整个输出阵列。

作者图片

示例中的滤波器可以被认为是从图像中识别边缘的边缘检测器。其他类型的特征可以用不同的过滤器来识别。在卷积层中,我们通常会有多个滤波器来检测不同类型的特征。

然而,我们可能不一定知道哪些过滤器最适合应用于我们的任务。因此,代替设置我们自己的过滤器,神经网络将确定最佳过滤器,该过滤器最好地检测我们的分类任务所需的特征。

图像通常表示为 RGB 值,由 0 到 255 的 3 个值组成,每个值对应红、绿、蓝三种颜色。因此,卷积滤波器通常是三维的(例如 3x3x3)。

汇集层

汇集层通过将图像的子部分简化为单个值来减少数据的维度。例如,在下图中,应用最大池会通过将图像每个子部分中的最大值作为输出来缩小图像。

作者图片

数据准备和扩充

在本文中,我使用的数据集来自这里的(数据集是 CC BY 4.0),其中包括 5856 张标记为“肺炎”或“正常”的胸部 x 光图像。这些胸部 x 光图像选自广州妇女儿童医疗中心 1 至 5 岁儿童患者的回顾性队列。这些 x 射线图像被分成训练集、验证集和测试集。训练集中有 5,216 幅图像,验证集中有 624 幅图像,测试集中有 16 幅图像。我保存文件夹的目录,并在下面的代码中相应地命名它们。

下面我从训练集中画出了 10 张 x 光图像,其中 5 张是“正常”,5 张是“肺炎”。在普通的胸部 x 射线图像检查中,放射科医生会在肺部寻找白色斑点,以确定患者是否患有肺炎。然而,作为非医学专业人员,我们可能不一定有专业知识或经验来区分“肺炎”图像和“正常”图像。

作者图片

让我们看看神经网络是否可以帮助我们这些非医学专业人士区分肺炎和非肺炎的胸部 x 光图像。首先,我在下面的代码中列出了数据准备和扩充的步骤。

我使用 Tensorflow 中的图像预处理库从文件夹中提取并生成批量图像。如上所述,图像用 RGB 值表示,范围从 0 到 255。在卷积神经网络中,每个图像应该只贡献等量的信息。然而,具有高像素值的图像可能比具有低像素值的图像具有更大的权重。因此,我对图像应用了 1/255 的重缩放因子,使它们的值在 0 和 1 之间。

我还应用样本集中和标准化来集中和标准化图像。图像的平均值为 0,标准偏差为 1,这是通过减去每个图像中的平均像素值并除以像素值的标准偏差得到的。

为了减少模型可能过度拟合训练集并且不能很好地概括它从未见过的图像的影响,我应用数据扩充来生成更多看起来与原始图像略有不同的图像,以包括在数据集中。额外的图像被随机缩放 0.2 倍,并且它们的高度和宽度与总量相比被随机移动 0.1 的分数。

我将数据增强应用于训练集和验证集,以评估模型在具有相似数据增强的相似图像分布中的表现。然而,我没有对测试集应用数据扩充,而只是对测试集进行了重新调整和标准化,以评估该模型在图像失真可能性较小的正常情况下更可能遇到的图像上的表现。

类别模式设置为二进制,因为类别为“肺炎”或“正常”。我将图像缩小到(160,160)的大小,以减少输入的数量。批量大小设置为 16,这意味着模型中的内部参数将在每运行 16 个样本时进行更新。可以认为,该模型每观察 16 个样本,就会向最优值迈一小步。

构建卷积神经网络

我在下面的代码中列出了我的卷积神经网络的结构。

有 4 个类似的层块,一个卷积层,然后是批量标准化和最大池层。对于跨越所有四个块的卷积层,过滤器的数量从 32 个增加到 128 个,内核大小为 3×3。

在每个块的卷积层之后,我应用批量归一化来归一化结果。然后它们被输入到激活函数中,我将其设置为 ReLU 函数。然后,我应用最大池层,池大小为 2x2。

在四个块卷积和池化层之后,我展平该层,并包括一个具有 512 个神经元的全连接层,以 ReLU 作为激活函数。最后,我包括一个具有 1 个神经元的输出层,其 sigmoid 函数作为我们的二元分类任务的激活函数。

我使用随机梯度下降作为优化方法,学习率从 0.01 开始指数衰减,二进制交叉熵作为损失函数。

然后,我用 20 个时期(或周期)训练该模型,每个时期的步数等于训练集中的图像数除以批量大小,这将允许在每个时期训练整个训练集。我在下面列出了模型摘要。

作者图片

我绘制了各种指标,包括准确度、丢失率、精确度和召回率。

作者图片

训练集中的准确率提高到 95%以上。我们可能会倾向于得出这样的结论:这个模型做得相当好。然而,当我们查看跨时期验证集中的准确率时,它徘徊在 75%到 90%左右,没有随着我们通过更多时期训练模型而增加的明显趋势。

作者图片

训练集中的损失呈下降趋势,而验证损失在最初几个时期后没有显示出明显的下降趋势。

作者图片

作者图片

相似的观察结果可以在精度和召回率中被识别,其中在训练和验证集之间没有明显的收敛。

我们还看到,总体精度低于验证集中的精度,这表明一定比例的预测是假阳性;而验证集中的召回率通常较高,这表明低百分比的预测是假阴性。

上述观察可能暗示我们的模型遭受过拟合的问题,该模型过拟合训练集,并且当它遇到从未见过的新图像时不能很好地概括。

混淆矩阵

下面我列出了模型在预测训练、验证和测试集时的混淆矩阵。

作者图片

在训练集中,该模型正确地预测了大多数“肺炎”图像,以及精确度稍低的“正常”图像。

作者图片

在验证集中,模型准确地预测了“肺炎”图像,但在“正常”图像中预测得不太好。

作者图片

在测试集中可以看到类似的模式,即模型不能有效地区分“正常”图像和“肺炎”图像。

从验证集和测试集中的结果,我们可以得出结论,该模型可以正确地预测大多数肺炎的胸部 x 射线图像,但不能预测正常的胸部 x 射线图像。

因此,该模型不是一种万无一失的方法,在该方法中,高百分比的“正常”图像被分类为“肺炎”图像。

后续步骤

上述结果可能是由于数据集中的不平衡,因为“肺炎”图像的比例较大,而“正常”图像相对较少。神经网络可能没有看到足够多的“正常”图像来正确识别它们。

此外,过度拟合似乎仍然是一个问题,尽管我们扩大了数据集的数据增加。因此,收集更多的胸部 x 射线图像添加到我们的数据集,特别是正常图像,可能会给我们一个更准确的模型。

我们也可以探索迁移学习的应用。迁移学习是采用预先训练的模型,该模型在非常大的图像集上进行训练,并针对更具体的任务重新训练顶层。这有可能超过我们当前的模型,因为预训练的模型已经从非常大的图像集中学习了基本特征,这将理想地帮助模型处理更复杂的任务。

结论

卷积神经网络作为神经网络的一个分支,常用于图像相关的任务,可以应用于医疗诊断等不同领域。然而,深度学习是数据饥渴的,为了训练一个性能良好的模型,需要大量的数据,这可能是医疗领域的一个障碍。

诊断分析—如何进行根本原因分析

原文:https://towardsdatascience.com/diagnostic-analytics-how-to-conduct-a-root-cause-analysis-4e92583cf008

主动提供对指标变化的真实见解

注意:术语根本原因分析通常在 IT 和数据工程中用作识别故障或问题根本原因的过程。本文重点关注诊断分析,以理解业务指标变化的驱动因素。

rawpixel.com 在 Freepik 上的图片

为什么收入下降了?为什么转化率会飙升?为什么平均订单值持平?

根据您的行业和业务目标,您的关键指标可能会有所不同。但是如果你关心回答“我如何改进我的关键指标?”,你需要先了解他们为什么先改变。

我看到许多团队努力回答,以确定业务度量变化背后的根本原因。根据他们的诊断分析成熟度,原因会有所不同(参见诊断分析差距)。

这有什么关系?

为了能够提出改进性能的建议,团队首先需要理解他们的度量标准中发生的变化。如果不深入观察,就不可能找到真正的洞察——不仅仅是观察——供团队采取行动(参见文章“如何交付真正的数据驱动洞察”)。

理想情况下,他们应该定期这样做,而不是在观察到指标急剧下降或上升时进行调查。否则,团队将陷入被动的分析循环,这可能会由于延迟的见解和行动而代价高昂。

现状

根据与数十个数据和业务团队的对话和协作,人们可以根据诊断分析的方法、分析彻底性和洞察时间,将诊断分析成熟度分为 4 种状态。

(诊断分析的状态—图片由 Kausa 提供)

卡在什么里面

团队主要描述正在发生的事情(例如,指标正在下降),并将点与高层次的定性事实(例如,业务中发生的事件,如网站更新)联系起来。该流程是非结构化的,不是数据驱动的。他们大多处于救火模式,调查来自业务团队的特别请求。

结果:团队甚至没有意识到的未开发的潜力,削弱的数据文化

常见的疑点(即“业务主导”的方法)

团队非常倾向于只测试通常的怀疑或业务团队提出的假设。虽然从业务单元接收上下文和方向是有用的,但是团队应该不仅仅是测试这些假设。这可能会引入明显的偏见,并使团队忽略宝贵的机会。

结果:决策过程中的重大偏差,导致错失良机。缺乏真正的洞察力。

速度的需求

团队处于诊断分析的更成熟状态,他们认识到深入探究原因的价值。虽然他们有更结构化的方法,但使用现有工作流执行全面的根本原因分析过于复杂和耗时。他们通常不能像业务要求的那样快速获得洞察力。

结果:洞察力发现得太慢,无法产生真正的商业影响和改善决策。

理想状态——全部力量

全面的诊断分析需要基于面向影响的全面根本原因分析的主动方法。

(作者的幻灯片—在诊断分析中发挥全部力量)

快速而全面

团队使用机器学习来增强诊断分析。通过这种方式,他们可以在几分钟内测试变化背后所有可能的驱动因素,而不会牺牲速度或全面性,也不会消除人为偏见。

积极主动的

团队不仅关注剧烈的/令人惊讶的变化,而且根据业务的发展速度,每天/每周保持他们度量的脉搏。他们参加绩效会议时,已经准备好了推动对话的潜在变革驱动因素。每天/每周主动分享这些见解,而不是对业务问题做出反应。

面向影响

在一个最大的市场中,一个小的变化可能仍然很大,但在一个小群体中的重大变化可能会显得更加突出,除非你将真正的影响/实际贡献考虑在内。(此处查看更多)。

当观察一个特定的子群时,它对全局指标变化的贡献必须来源于两个影响:

  • 此子组的给定度量的变化
  • 亚组数量的变化(该亚组在全球人口中的份额)

假设其他条件相同(所有其他条件相同),这两种效应可以单独计算。通过将两种效应相加,你可以得到这个子群对全局度量变化的贡献。

让我们假设您正在查看每周的平均订单价值(AOV)变化。

全球平均订单价值从 50€增加到 52€。

你想了解每个国家对全球变化的贡献。让我们以法国为例:

  • 公制变更: 法兰西的 AOV 从 60€增加到 70€
  • 数量/体积法国订单的份额一直保持在 10%
  • 实际影响:由两种影响之和得出
  • 指标变化:法国 AOV 订单的增加对全球 AOV 有积极贡献。假设其他条件不变(即法兰西的规模将保持 10%),效果是(70 个€-60 个€)10% = 1 个€*
  • 体积变化: 0(假定尺寸保持不变)
  • 考虑到这两种影响,如果其他因素保持不变,全球 AOV 将上移 1 个€(即,这是法国对全球 AOV 变化的贡献)

进行根本原因分析的最佳方法

最佳实践方法包括 3 个关键步骤:将变更缩小到最具影响力的子群体,分析相关/依赖指标,以及将点与所采取的行动、交互和外部事件联系起来。然后,关键是要确保这些见解得到适当的传达和呈现,以推动行动。

1)将变更缩小到最具影响力的子群体

在测试了所有潜在的相关因素以避免人为偏见之后,将变更缩小到驱动大部分变更的子群体。这是人力资源无法完成的部分。即使你花了几天/几周的时间在一个改变上,你也不太可能调查所有的因素并根据影响给它们打分。

使用机器学习/增强分析,可以在几分钟内检查数千甚至数百万个组合,并且只有重要的组合可以根据前面解释的实际影响进行评分。

2)分析相关/依赖指标

在将变更缩小到最有影响力的子群体之后,查看这些子群体的相关/依赖指标是如何演变的。通常,一个指标是其他指标的函数(例如,游戏中的 ROAS 是预期收入、成本和安装的函数)。在这些情况下,记住这个度量树有助于进一步理解事情为什么会发生变化(例如,由于成本激增,ROAS 下降)

3)把这些点连接起来

与业务团队协作,将点点滴滴联系起来,并推动可操作的见解。集体讨论采取了哪些行动,以及交互、事件和可能影响您正在研究的业务指标的外部因素。相关事实可分为 3 组:

  • 内部(即公司采取的行动)
  • 外部和行业特定 —竞争对手采取的行动(例如,新的营销活动)或行业的总体趋势(例如,夏季季节性)会影响您正在寻找的业务指标
  • 外部和特定地理区域 —影响特定区域的更广泛行动(如巴西的选举)

4)有效地展示和交流见解

提前解释关键事实和驱动因素,陈述你的假设并量化影响。开发一致的方法来展示结果(例如,瀑布图、如下所示的备忘录)

例子

让我们回到平均订单价值的例子——AOV

AOV 追踪顾客每次在网站或手机应用上下单时的平均消费金额。产品/市场团队旨在通过测试不同的活动,并随着时间的推移对网站/应用程序进行更改,从而最大化 AOV。

在这种情况下,AOV 从 62.4€增加到 63.7€哇。增长了 2.1%。

(图片由 Kausa 提供—公制变化快照)

由于这不是一个大的峰值或下降,许多团队很可能会忽略这一点。在全员层面,每周都要对 AOV 等关键业务指标进行检查,以发现每一个机会并最大化业务影响。

那么,这 2%从何而来?

通过扩大工作流程,您可以测试影响 AOV 的所有因素——在这个案例中有超过 500,000 种组合——并查看哪些驱动因素具有最大的影响。

(图片由 Kausa 提供—主要驱动因素的优先顺序)

几秒钟之内,我就能看出国家、活动和客户年龄是最大的影响驱动因素。有趣的是,从高层次来看,有相当多的因素相互影响,相互抵消。

让我们看看哪些国家做出了消极和积极的贡献。

(图片由 Kausa 提供——各国的实际贡献)

有趣的是,德国的表现非常好,而法国、美国和韩国表现不佳。

你很好奇为什么德国的表现比其他国家更好。因此,您需要进一步挖掘,以确定活动 ID 是导致德国 AOV 增长的子因素。但是哪一个或哪些活动?

(图片由 Kausa 提供——根据实际贡献优先考虑德国的主要驱动因素)

在德国有一个活动为整个 AOV 贡献了 3.18€。有意思…

(图片由 Kausa 提供——德国的活动根据实际贡献进行优先排序)

现在,您可以查看相关的指标以获得更多的上下文信息。看起来营销团队增加了此次活动的广告支出,从而推动了订单价值和订单量的增长。子群的体积相当小。你可能想要通知我们也可以在其他部分测试的团队。

(图片由 Kausa 提供—查看相关指标)

在你联系营销团队之前,你想看看你是否能收集到任何其他见解。年龄似乎是一个重要的次要因素。该运动在 18-20 岁年龄组中表现尤为突出。

(图片由 Kausa 提供——一起看三维)

太好了!现在,您已经准备好与营销团队交流这些发现,以获得更多的业务背景。

快速签到后,你发现他们正在为这次活动测试新的创意视觉效果。有了这些信息,营销团队开始在其他地区测试该活动,数据团队将密切关注是否在其他国家观察到相同的趋势。您可以将时间分配给更有趣的数据分析项目,而不是花费数小时/数天进行深入研究。

双赢。

底线:影响驱动的方法

从现状来看,提高诊断分析和实现商业价值最大化的机会很大。目前,处理诊断分析的方式有时会让人感觉像是故意伤害(从持续救火到错过最后期限等等)。但这种疯狂是有可能带来逻辑和结构的。从哪里开始?取决于您的起点,但请考虑以下几点:

  • 发展您的现代数据堆栈,使其包含决策智能/诊断分析平台,从而能够扩展工作流
  • 开始测试手头的所有假设,发现被忽视的机会
  • 根据影响指标的驱动因素的实际影响对其进行评分,以便能够关注真正重要的因素
  • 主动与相关利益相关方交流调查结果

想法?伸出手去 若昂索萨处长成长处 考萨 。敬请关注更多关于如何确定诊断分析和增加数据价值的帖子&分析。

Python 中作为代码的图

原文:https://towardsdatascience.com/diagrams-as-code-python-d9cbaa959ed5

用 Python 创建云系统架构图

照片由玛丽奥拉·格罗贝尔斯卡Unsplash 上拍摄

在过去的几年里,我使用了许多不同的工具来绘制系统架构图,包括数据平台和云架构,包括 draw.ioExcalidraw

尽管这些平台提供了各种各样的工具,可以帮助你画出想要的图,但我一直在为一些事情而挣扎。首先,对我来说,与组织中的其他人分享图表并不容易,这样他们就有可能更新或修改它们。其次,对我的图进行版本控制几乎是不可能的,在大多数情况下,我必须保存一个包含元数据的文件,该文件可以用来重新加载一个旧的图。

最后,我的图缺乏一致性——在我看来——这是非常重要的,因为你需要创建几个不同的图,并且必须呈现给用户和同事。我所说的一致性是指能够使用各种图表组件——包括边、节点等。—始终如一。

最近,我遇到了一个 Python 包,它可以让你在没有任何设计工具的情况下,用 Python 代码绘制云系统架构。换句话说,它以代码的形式提供了图表,您可以通过编程的方式绘制图表,同时能够对它们进行版本控制。

图表包

Diagrams 是一个 Python 包,可以用来创建云系统架构图,支持亚马逊 Web Services (AWS)、微软 Azure、谷歌云平台(GCP)、Kubernetes、阿里云、甲骨文云等六大云提供商。此外,它还支持其他常用的技术,如特定的编程语言、框架、聊天系统和更多的节点。同时,您还可以选择创建定制节点,以服务于您的特定用例。

该包需要 Python 3.6 或更高版本,因此在尝试之前,请确保您的主机上有兼容的 Python 版本。此外,您还必须安装Graphviz——一个开源的图形可视化软件,Diagrams package 使用它来呈现图表。

如果你使用的是自制软件,macOS 用户可以通过brew install graphviz下载 Graphviz。同样,安装了 Chocolatey 的 Windows 用户也可以运行choco install graphviz

—图表文档

现在你已经仔细检查了你的 Python 版本并安装了 Graphviz,你可以继续通过pip安装这个包了:

$ pip install diagrams

用 Python 创建云架构图

在将我们的第一个图创建为代码之前,让我们探索一下这个包的一些最基本的组件。

第一个是Diagram,它是表示全局图上下文的主要对象。你可以使用Diagram作为上下文管理器

**from** diagrams **import** Diagram
**from** diagrams.gcp.analytics **import** Bigquery **with** Diagram('My Diagram'):
    BigQuery('Data Warehouse')

上面的代码片段将创建一个由单个 BigQuery 节点组成的图表,该节点是 Google 云平台上的托管数据仓库服务。

这个包的第二个基本组件是Node,它是一个抽象的概念,代表一个单一的系统组件对象。典型的Node由三个基本部分组成;提供方资源类型名称。例如,我们在前面的代码片段中使用的 BigQuery 节点是由gcp提供者提供的,属于analytics资源类型(显然 BigQuery 对应于节点的名称)。请注意,您甚至可以创建自己的自定义节点:

**from** diagrams **import** Diagram, Node**with** Diagram('My Diagram'):
    Node('This is a custom node')

使用这个库创建图的第三个关键组件叫做Cluster,它允许多个节点以一种方式组合在一起,这些节点都与集群中不包含的任何其他节点相隔离。

例如,考虑下面由三个节点组成的图表;一个用于 Google 云存储(这是 Google 云平台上的托管对象存储服务),一个云 SQL 节点(这是 GCP 上的托管 Postgres 服务)和一个本地 MongoDB。

**from** diagrams **import** Cluster, Diagram
**from** diagrams.gcp.database **import** SQL
**from** diagrams.gcp.storage **import** GCS
**from** diagrams.onprem.database **import** MongoDB**with** Diagram('My Diagram', direction='TB'):
  gcs = GCS('Google Cloud Storage')**with** Cluster('Databases'):
  cloud_sql = SQL('Cloud SQL')
  mongodb = MongoDB('MongoDB')

带有数据库集群的示例图—来源:作者

最后,图的最后一个基本组件是Edge——一个表示两个Node对象之间的边的对象,有三个属性;标签、颜色和样式。

**from** diagrams **import** Diagram, Edge, Node **with** Diagram('My Diagram', direction='TB'):
    n1 = Node('n1')
    n2 = Node('n2')
    n3 = Node('n3')
    n4 = Node('n4')
    n5 = Node('n5')
    n6 = Node('n6')   

    n1 >> n2
    n3 - n4
    n5 >> Edge(label='This is a label', color='red') >> n6

不同类型边的示例—来源:作者

创建架构图

现在我们已经了解了用 Python 构建一个图所需的基本对象,让我们使用前面提到的组件创建一个更真实的流程。

在下面的例子中(摘自官方文档),我们创建了一个与 Google 云平台上的消息收集系统相对应的图表。

**from** diagrams **import** Cluster, Diagram
**from** diagrams.gcp.analytics **import** BigQuery, Dataflow, PubSub
**from** diagrams.gcp.compute **import** AppEngine, Functions
**from** diagrams.gcp.database **import** BigTable
**from** diagrams.gcp.iot **import** IotCore
**from** diagrams.gcp.storage **import** GCS**with** Diagram("Message Collecting", show=False):
    pubsub = PubSub("pubsub")**with** Cluster("Source of Data"):
        [IotCore("core1"),
         IotCore("core2"),
         IotCore("core3")] >> pubsub**with** Cluster("Targets"):
        with Cluster("Data Flow"):
            flow = Dataflow("data flow")**with** Cluster("Data Lake"):
            flow >> [BigQuery("bq"),
                     GCS("storage")]**with** Cluster("Event Driven"):
            with Cluster("Processing"):
                flow >> AppEngine("engine") >> BigTable("bigtable")**with** Cluster("Serverless"):
                flow >> Functions("func") >> AppEngine("appengine")pubsub >> flow

Google 云平台上的消息收集图—来源:文档

最后的想法

架构图非常重要,因为它们清楚地展示了组织中各种组件的工作方式。因此,重要的是让他们正确,并以一种良好、直观和一致的方式呈现他们。

此外,能够容易地共享这些图也很重要,通过这种方式,它们可以容易地被不同的人修改,同时被版本控制。

当涉及到绘制和共享架构图时,作为代码的图是一种可以帮助您朝着这个方向前进的方法。在今天的教程中,我们展示了如何利用diagrams包来用 Python 编程创建图表。

成为会员 阅读介质上的每一个故事。你的会员费直接支持我和你看的其他作家。你也可以在媒体上看到所有的故事。

https://gmyrianthous.medium.com/membership

相关文章您可能也喜欢

Dickey Fuller 直接评估—测试统计计算速度提高 50 倍

原文:https://towardsdatascience.com/dickey-fuller-direct-estimation-speed-up-to-50x-test-statistic-computation-af3cb28b9803

通过相关系数直接估计 Dickey-Fuller 检验统计量,避免不必要的回归和矩阵求逆。

图片作者。

Dickey-Fuller 检验可能是时间序列分析中最著名的平稳性(单位根)检验。测试的计算程序依赖于具体统计公式的线性回归结果。然而,线性回归需要矩阵求逆,这可能是计算密集型的,甚至是数值不稳定的。

在这个故事中,我们将探索 OLS(普通最小二乘法)背后的数学,并使用这样的分析来推导 Dickey-Fuller 检验统计量的闭合形式表达式(使用 1 个时滞和一个常数)。所得表达式仅使用相关系数,没有矩阵求逆或计算密集型操作。这可以将计算速度提高 50 倍。

目录

  • 封闭形式表达式推导
  • 封闭形式的表达式结果
  • 健全性检查
  • 转速试验
  • p 值
  • 最后的话

封闭形式表达式推导

如果你想跳过数学细节,滚动到下一节,没有伤害。

对于那些还在这里的人,让我们首先正式阐明这个问题。最多一个滞后和一个常数的 Dickey-Fuller 检验(非扩充)自回归模型规格为:

使用ε i.i.d,该等式可以转换为时间序列增量显式的形式:

通过 OLS 估计α、β及其方差。

迪基-富勒检验统计量定义为:

我们可以用数值方法进行 OLS 回归,得到β和它的方差,然后就到此为止。这将涉及计算量很大的矩阵求逆和矩阵乘法。所以我们不会这么做。

我们可以走的另一条路是做数学。似乎现在我大部分时间都在电脑上捣鼓数字,几乎没有时间在黑板上做实际的数学运算。在这种情况下,做数学确实会有回报。

首先,我们将用矩阵和向量来表示我们的回归

其中 S _d 是由 S 的差组成的维度为 T 的向量,

并且 X 是一个 T x2 矩阵

S _L 为滞后时间序列 ST 维向量,则以下关系成立:

  • S _L 的含义:

  • S _L 的方差:

  • Sd 的意思是:

  • S _d 的方差:

  • S _L 和 S _d 的协方差:

请注意,我们不使用贝塞尔校正,因为由此产生的方程会有更多的项。当有疑问时,就去寻找能产生最佳数学表达式的结果。你可以自己试试,按照下面的步骤使用贝塞尔的方差修正。

矩阵形式的 OLS 估计量为:

其中 T 上标表示矩阵转置。我们有

它的反义词:

因此,

其中,ρ是滞后序列和差异序列之间的皮尔逊相关系数,以及

请注意,即使我们对方差使用贝塞尔校正,α和β的结果也将保持不变。

现在我们需要得到β的方差。根据 OLS,我们得到δ的协方差矩阵为:

那么β的方差为:

在哪里

是回归残差的方差。注意,我们在分母中使用了 T -2,而不是 T ,因为在 OLS,残差只有 T -2 个自由度,因为这两个约束成立:

即,通过构造,残差的平均值为零,回归量和残差之间的协方差为零。

然后,展开残差方差的等式,注意时间序列差异的估计为:

我们知道:

如果我们使用贝塞尔对方差的修正,这个结果会更混乱。

那么我们可以将β的方差表示为

最后,经过我们所有的努力,我们可以写出 Dickey-Fuller 检验统计量的封闭形式:

封闭形式的表达式结果

Dickey-Fuller 检验统计量的封闭表达式的结果是:

其中 T +1 是我们数据的样本量,ρ是滞后时间序列(样本量 T )和差分时间序列(样本量 T )之间的相关系数。

我们唯一需要计算的是相关系数,这比计算 OLS 更有效。这在优化例程和时间序列的实时分析中变得很方便,因为每一毫秒都很重要。

健全性检查

在本节中,我们将把我们的结果与从 Statsmodels (Python)库中获得的结果进行比较。让我们为 Dickey-Fuller 检验统计量定义我们的函数:

我们将使用带有标准正态增量(布朗运动)的 AR(1)单位根过程进行测试:

现在,我们得到我们的直接 Dickey-Fuller 估计和 OLS 方法(Statsmodels)的相对平均误差的估计,即| DF _ stat models—DF _ direct |/| DF _ direct |。这正是下一个函数要完成的。它用随机布朗运动运行“n_tests ”,并返回一个有差异的数组。

运行测试和绘图:

请注意,您的结果会有所不同,因为这个测试是随机的。但对于样本量为 10,000 的时间序列,相对平均误差约为 1%。所以我们的理智检查确实是成功的。然而,在某些试验中,这两种估计方法之间存在微小的差异,这是由于估计方法中的单位根过程引起的数值不稳定性。然而,考虑到计算效率的提高,这是我们可以忍受的。

速度测试

现在是最精彩的部分。在本节中,我们将比较 OLS 方法(Statsmodels)和 Dickey-Fuller 检验的直接估计的速度。下面的代码对这两个函数(方法)中的任何一个进行时间测试。

对 100 到 100,000 的样本大小范围进行测试,并绘制:

我们可以看到,对于大样本量的时间序列,速度提高了约 50 倍,但即使对于较小的样本量,速度也提高了约 10 倍。所以,的确,做数学得到了回报。

p 值

在本节中,我们将编写一个类来获取 Dickey-Fuller 测试直接估计的 p 值。没有 p 值的统计工具是不完整的。我们将使用上面描述(和编码)的 AR(1)单位根过程进行蒙特卡罗模拟。

注意,在这个类中,我们使用了前面章节中的“get_DF”函数和“get_unit_root_proc”。

例如,我们使用 DFProbTable 对象来获得 T=500 的 P 值:

最后的话

至少可以说,这个故事中的数学有点长,但最终,我们得到了一个值得的结果。这里提出的 Dickey-Fuller 统计量公式不仅有助于优化计算效率,而且有助于以另一种方式理解统计量。

还有一个教训需要吸取:有时作为数据科学家,在没有太多底层数学知识的情况下,使用库和建模一切是非常容易的。然而,深入研究数学是一个好主意,不仅仅是作为学习练习,也是获得新的不同见解的一种方式。一知半解是一件危险的事情。

参考

1 M. L. de Prado,D. Leinweber,协整和子集相关套期保值方法的进展 (2012),《投资策略杂志》,第 1 卷第 2 期,第 67–115 页

[2]https://web . Stanford . edu/~ mrosenfe/SOC _ meth _ proj 3/matrix _ OLS _ NYU _ notes . pdf

[3]http://web . vu . lt/MIF/a . buteikis/WP-content/uploads/PE _ Book/3-2-ols . html

我希望这个故事对你有用。如果我错过了什么,请让我知道。如果你想知道更多这样的故事,请关注我。

https://medium.com/subscribe/@diego-barba

喜欢这个故事吗?通过我的推荐链接成为媒体会员,可以无限制地访问我的故事和许多其他内容。

https://medium.com/@diego-barba/membership

Dickey-Fuller 优化:正面解决时间序列协整问题

原文:https://towardsdatascience.com/dickey-fuller-optimization-tackle-time-series-cointegration-head-on-f924f7c51477

不要介意演习;直接最小化 Dickey-Fuller 统计量以获得平稳的时间序列。完整的 Python 代码。

图片作者。

有许多方法可以找到协整向量。一些方法利用 OLS,其他矩阵特征分解。不管采用哪种方法,通常都要对结果时间序列进行平稳性测试,以此来进行健全性检查。

工作流程是这样的:

  1. 找到协整向量(你选择的方法)
  2. 对步骤 1 的向量生成的时间序列进行平稳性测试,并丢弃虚假结果

参加迪基-富勒测试。通常情况下,我们将在步骤 2 中使用的平稳性检验正是 Dickey-Fuller 检验统计量。

如果我们最终检查 Dickey-Fuller 统计量是否足够小,以说明我们的协整方法是否成功,为什么不首先简单地最小化 Dickey-Fuller 统计量?

本文将通过直接最小化 Dickey-Fuller 检验统计量来解决协整问题。通常,在使用普通函数优化作为算法的主要驱动力之前,我会三思而行。当然,我们可以将 Python 中许多可用库中的任何函数扔给优化器,看看会发生什么。我以前做过很多次了。这通常不是一个好主意。

函数优化可能会出错,尤其是在函数相对未知的情况下;其中包括:

  • 函数调用可能很昂贵
  • 该函数可能有噪声
  • 该函数可以是非凸的

即使优化效果很好,也可能会非常慢。

在这个故事中,我们将解决所有这些问题。我们将编写一个在速度上与约翰森方法和 BTCD 相当的方法,并且具有相同甚至更高的可靠性。然而,会涉及到一些数学问题,所以要小心。为了成功地优化函数(而不是永远这样),我们将计算 Dickey-Fuller 检验统计量的梯度和 Hessian 矩阵。

故事结构

  • 协整,问题设置
  • 迪基-富勒统计,直接估计
  • 梯度
  • 黑森矩阵
  • 轻视
  • 代码摘要
  • 最后的话

如果你想跳过数学,跳过故事的渐变和粗麻布部分。完整的代码将出现在代码摘要部分。

协整,问题设置

实际上,协整是指多个时间序列(过程)的一阶整合。也就是说,这些时间序列(另一个时间序列)的线性组合是平稳的。谁不喜欢平稳的时间序列?

数学上,给定 N 个时间序列 P_i,i=1,2,.., .n,样本大小为**T+1,我们试图找到一个向量 w 与分量 w_i 这样的线性组合:

是静止的。

给定向量w(“w _ vec”)和进程 P_i ,编码为矩阵(“p_mat”),每列一个进程,得到 S 非常简单:

迪基-富勒统计,直接估计

我们要解决的第一件事是优化函数调用速度。通常,为了计算迪基-富勒检验,我们会做 OLS 回归。那是昂贵的。相反,我们对测试统计数据使用直接估计:

其中 ρ_SL,Sd 是滞后 S 序列( S_L )和差分 S 序列(S_d)的相关性(Pearson)。注意 S_LS_d 的样本量都是 T

如果你想知道更多关于上述结果的细节,请查看我的之前的故事和数学证明。

使用这种形式的 Dickey-Fuller 统计更快,因为计算相关系数在计算上比 OLS 矩阵求逆和乘法更有效。此外,它还有另一个优点;统计量对向量 w 的依赖性是清楚的。因为相关性是:

并且 S_LS_d 的协方差和方差可以使用协方差矩阵和 w 向量写成二次型,即

其中 V_L 为滞后 P_i 时间序列的协方差矩阵, V_d 为差分 P_i 时间序列的协方差矩阵, V_L,dV_d,L 为滞后差分时间序列的互协方差矩阵,定义为:

具有以下属性:

上标表示矩阵转置。

对谈话进行编码时,我们用计算中需要的基本变量初始化“_SInit”对象:

然后是另一个对象“_ PCovMatrices”,它包含我们需要的协方差矩阵:

最后,滞后和差异 S 系列的标准偏差对象:

有了编码的初始对象,我们现在可以陈述我们的目标函数,Dickey-Fuller 统计直接估计:

梯度

DF (迪基-富勒)相对于 w 的梯度为:

相关性的梯度是:

使用二次型的梯度,标准差和协方差的梯度为:

因此,

编码渐变:

黑森矩阵

为了得到 Hessian 矩阵,我们将使用张量微积分,因为我们需要处理矩阵对向量的导数,以及这类有趣的数学,所以张量微积分是最适合的。

我们将使用爱因斯坦的重复指数求和约定;因为我们有许多指数,所以求和符号会使一切变得更加复杂。此外,我们将使用以下简化:

在协方差矩阵中,我们将下标字段用于索引:

索引将用希腊字母书写,以区别于其他下标和上标。

黑森是:

在哪里

是相关的梯度,用向量微积分符号表示

假设梯度是列向量。

我们需要计算ρ的海森数。也就是说,让我们定义我们将使用的两个矩阵:

和一个 4 张量:

那么ρ的梯度(在前面的部分中已经在向量微积分符号中)在张量微积分符号中是:

很难不喜欢张量微积分的优雅。

最后,ρ的黑森式是:

虽然它看起来像广义相对论中坍缩黑洞的方程(开玩笑),但用 Python 编写这个非常简单,这要感谢 NumPy 的“einsum”:

轻视

一旦我们有了目标函数、梯度和 Hessian,最小化代码块就非常简单了。本质上,我们用协整问题的参数来包装 SciPy 的最小化函数:

  • “p_mat”是 P_i 过程矩阵。
  • “w_vec_init”是对 w_vec 的初始猜测。不供一个也不用担心;将生成一个随机猜测。
  • “方法”只能取两个值,要么是“信任-克雷洛夫”,要么是“信任-精确”。

注意:这不是一个全局优化器,所以给定一个初始的 w 向量对于收敛是必不可少的。在某些问题中,随机选取的向量足以收敛。但是,最好是针对您的特定情况生成一个更好的初始猜测。

我们将生成离散采样的相关布朗运动(单位根过程),并使用该矩阵作为“p_mat”来测试算法。如果你不知道布朗运动或如何产生它们,不要担心;看看我之前关于这个话题的报道。我们将使用那个故事中的代码来生成“p_mat ”,因此将来自布朗运动故事的代码保存为“brownian_motion.py ”,并将其放在运行以下代码的同一个目录中。

最小化并绘图:

图片作者。

一切都按计划进行。

代码摘要

为了完整起见,这里是故事中开发的所有代码。通过最小化 Dickey-Fuller 检验统计量来获得协整向量所需的代码:

最后的话

开发的优化算法按预期工作。速度不如特征分解算法,但不相上下。它非常健壮和可靠。

然而,唯一的缺点是协整向量的初始猜测的选择。我已经看到该方法对于初始猜测的错误选择是非常宽容的;大多数时候,随机的初始猜测非常有效。然而,做出好的选择可以提高解决方案的速度和可靠性。记住,这是局部优化器,不是全局优化器。

最后,使用梯度和 hessian,很容易将此代码扩展为约束优化,这更适合于现实世界的问题。可以很容易地将约束添加到该方法中。特征分解方法必须重写以适应约束。

参考

1 M. L. de Prado,D. Leinweber,协整和子集相关套期保值方法的进展 (2012),《投资策略杂志》,第 1 卷第 2 期,第 67–115 页

我希望这个故事对你有用。在 Medium 上关注我,如果你想要更多这样的故事,请订阅。

https://medium.com/subscribe/@diego-barba

如果我错过了什么,请让我知道。对于任何质疑、批评等。,留言评论。

喜欢这个故事吗?通过我的推荐链接成为媒体会员,可以无限制地访问我的故事和许多其他内容。

https://medium.com/@diego-barba/membership

如何将字典转换成熊猫数据框架

原文:https://towardsdatascience.com/dict-to-pandas-df-29ba731fc256

使用 Pandas 将 Python 字典转换为数据帧

照片由大卫·舒尔茨Unsplash 上拍摄

[pandas](https://pandas.pydata.org/)是 Python 生态系统中最受欢迎的库之一,它通过提供直观而强大的 API 让开发人员与数据进行交互,以快速有效的方式用于数据分析和操作。

使用 Python 和 pandas 时,最常见的任务之一是将字典转换为数据帧。当您想要对当前存储在字典数据结构中的数据进行快速分析甚至可视化时,这将非常有用。

在本文中,我们将探讨如何以几种不同的方式将 Python 字典转换成 pandas 数据框架,这取决于数据最初在 dict 中是如何构造和存储的。

从 Python 字典创建熊猫数据框架

现在,为了从 Python 字典中创建一个 pandas 数据帧,我们可以使用pandas.DataFrame.from_dict方法,该方法用于从类似数组的 dict 或 dicts 对象中构造数据帧。

让我们用一些虚拟值创建一个示例 Python 字典,我们将在接下来的几节中使用这些虚拟值来演示一些将其转换成 pandas 数据帧的有趣方法。

users = {
  'fist_name': ['John', 'Andrew', 'Maria', 'Helen'],
  'last_name': ['Brown', 'Purple', 'White', 'Blue'],
  'is_enabled': [True, False, False, True],
  'age': [25, 48, 76, 19]
}

在这个示例字典中,键对应于数据帧的列,而列表中的每个元素对应于特定列的行值。因此,我们可以(可选地)指定orient等于'columns'

数据的“方向”。如果传递的字典的键应该是结果数据帧的列,则传递' columns '(默认)

熊猫文档

import pandas as pd 

users = {
  'fist_name': ['John', 'Andrew', 'Maria', 'Helen'],
  'last_name': ['Brown', 'Purple', 'White', 'Blue'],
  'is_enabled': [True, False, False, True],
  'age': [25, 48, 76, 19]
}

df = pd.DataFrame.from_dict(users)

我们刚刚使用 Python 字典创建了一个熊猫数据框架!

print(df)

  fist_name last_name  is_enabled  age
0      John     Brown        True   25
1    Andrew    Purple       False   48
2     Maria     White       False   76
3     Helen      Blue        True   19

这种方法仅适用于字典中的数据是以每个键对应于 DataFrame 列的方式构造的情况。如果我们有不同的结构会怎么样?

将字典关键字填充为行

现在让我们假设我们有一个字典,它的键对应于我们想要创建的数据帧的行。

users = {
  'row_1': ['John', 'Brown', True, 25],
  'row_2': ['Andrew', 'Purple', False, 48],
  'row_3': ['Maria', 'White', False, 76],
  'row_4': ['Helen', 'Blue', True, 19],
}

在这个场景中,我们必须使用下面代码片段中展示的orient='index'选项,这样字典中的每个键值对都被解析为一个 DataFrame 行。但是请注意,当使用orient='index'时,我们必须在调用from_dict()方法时显式指定列名:

import pandas as pd

users = {
  'row_1': ['John', 'Brown', True, 25],
  'row_2': ['Andrew', 'Purple', False, 48],
  'row_3': ['Maria', 'White', False, 76],
  'row_4': ['Helen', 'Blue', True, 19],
}

cols = ['first_name', 'last_name', 'is_enabled', 'age']
df = pd.DataFrame.from_dict(users, orient='index', columns=cols)

我们又一次设法用 Python 字典构造了一个 pandas 数据帧,这次是通过将每个键值对解析为一个数据帧行:

print(df)

      first_name last_name  is_enabled  age
row_1       John     Brown        True   25
row_2     Andrew    Purple       False   48
row_3      Maria     White       False   76
row_4      Helen      Blue        True   19

您可能已经注意到,每个键也成为新填充的数据帧的索引。如果您希望删除它,可以通过运行以下命令来实现:

df.reset_index(drop=True, inplace=True)

现在应该重置索引:

print(df)

  first_name last_name  is_enabled  age
0       John     Brown        True   25
1     Andrew    Purple       False   48
2      Maria     White       False   76
3      Helen      Blue        True   19

紧密定向选项

从 pandas v1.4.0 开始,当从 Python 字典构造 pandas 数据帧时,您也可以使用orient='tight'选项。该选项假设输入字典有以下按键:'index''columns''data''index_names''column_names'

例如,以下字典符合此要求:

data = {
  'index': [('a', 'b'), ('a', 'c')],
  'columns': [('x', 1), ('y', 2)],
  'data': [[1, 3], [2, 4]],
  'index_names': ['n1', 'n2'],
  'column_names': ['z1', 'z2']
}

df = pd.DataFrame.from_dict(data, orient='tight')

print(df)
z1     x  y
z2     1  2
n1 n2      
a  b   1  3
   c   2  4

在构建多索引数据框架时,最后一种方法通常很有用。

最后的想法

将 Python 字典转换成 pandas 数据框架是一个简单明了的过程。通过根据原始字典的结构方式使用pd.DataFrame.from_dict方法和正确的orient选项,您可以很容易地将数据转换成 DataFrame,这样您现在就可以使用 pandas API 执行分析或转换。

成为会员 阅读介质上的每一个故事。你的会员费直接支持我和你看的其他作家。你也可以在媒体上看到所有的故事。

https://gmyrianthous.medium.com/membership

相关文章你可能也喜欢

金钱跟着球走了吗:分析金钱球前后棒球击球统计的重要性

原文:https://towardsdatascience.com/did-the-money-follow-the-ball-analyzing-the-importance-of-baseball-batting-statistics-pre-144d7d452e1f

在这篇文章中,我们探讨了击球统计数据的重要性,如 OBP,SLG 和 AVG 是如何随着球员工资的变化而变化的

图片由吉米·科诺弗在 Unsplash 上拍摄

介绍

在迈克尔·刘易斯的《金钱球:赢得不公平比赛的艺术》中,奥克兰运动家队总经理比利·比恩因发现了棒球运动员市场的低效而受到赞誉。这本获奖的书中反复出现的一个故事是关于棒球中的保送。

传统上,棒球教练、球探和评论家主要根据击球手的击球能力,即击球率(SLG)来识别击球天赋。然而,比恩和他的团队,通过各种数据和性能分析技术,意识到画保送的能力是一项被其他棒球组织严重低估的技能。当 Beane 领导下的奥克兰运动家队开始雇佣基于保送能力的球员时,他们踏上了未知的领域,因为这是一项通常被棒球界低估的技能。比利·比恩和他的统计学家能够更准确地识别击球人才,并设法形成一个获胜的团队,尽管资金紧张。

《Moneyball》的出版分散了整个棒球圈的这种明显的低效率,并对球队策略和球员招募产生了广泛的影响。钱球的故事中有一个微妙的经济基础,它与市场的运作有关,两位经济学家 Jahn K. Hakes 和 Raymond D. Sauer 强调了这一点。他们指出,如果畅销故事确实是正确的,那么以下两个论点应该成立:

  1. Moneyball 之前,保送的能力(由 OBP 的上垒率反映)应该对球队的输赢率有影响,但球员的工资不应受到影响,因为在 Moneyball 之前,保送被认为是一项被低估的技能。
  2. 有能力获得更多保送(更高的 OBP)的球员应该有更高的薪水,因为在这本书出版后,球队应该改变他们的策略来掩盖他们早期方法中的低效率。

Hakes 和 Sauer 决定看看他们是否真的能证明这个假设。在一篇名为 对钱球假说 (2006)的经济评估的研究论文中,他们实际上证明了书中提出的论点确实站得住脚,并得到了数据的支持。

因此,在这篇文章中,我们将着手重现 Hakes 和 Sauer 论文中的表 3 (附后),两位作者在论文中测试并展示了假设的结果,即我们应该预期观察到工资与 OBP 的强相关,而不是与 SLG 的强相关。他们通过对 OBP、SLG 和他们认为可能影响球员工资的其他因素运行击球手工资的回归模型来实现这一点:本垒板出场次数、仲裁资格和自由代理权,以及击球手是担任捕手还是内野手。除了这些因素,我们还将把击球率纳入我们的回归分析。

作者图片

定义

SLG: (单打+ 2x 双打+ 3x 三垒+ 4 x 全垒打)/ At bats
OBP: (安打+保送+被投球击中)/(At bats +保送+被投球击中+牺牲飞人)
log(薪水)= B0+B1 * OBP+B2 * SLG+B3 * PA+B4 * Arb+b5 *自由人+B6 *捕手+B7 *内野手

数据

为了在本文中进行分析,我们将使用肖恩·拉赫曼的棒球数据库来收集球员的工资和击球数据,并使用数据库中的出场文件为仲裁/自由代理和防守位置创建数据。

薪资数据

作者图片

一旦导入了所有需要的包,我们就将薪水数据加载到一个名为 Salary 的数据框架中。我们有 26,428 行关于每支球队球员个人工资的数据,这些数据从 1985 年持续到 2016 年。

Salary = Salary[Salary['salary'] > 0]
Salary['log_salary'] = np.log(Salary['salary'])
Salary = Salary.rename(columns = {'yearID':'salary_year'})
Master = Salary

然后,我们检查薪水栏中缺失的值,以确保在特定赛季没有薪水记录的球员从数据框中删除。接下来,考虑到我们可以支配的工资范围很广,我们将通过取其对数来转换工资变量。这样做是为了确保非常大的工资值不会最终扭曲预测模型。我们还将把 yearID 变量重命名为 salary_year。这将有助于我们稍后的分析,那时我们将在这个专栏上合并不同的数据框架,并且我们将分析球员在特定年份的表现和他们在相应年份的工资。最后,我们将把薪水数据帧复制到数据帧中,这将是我们的核心数据帧,我们将在整个分析过程中从这里进行操作。

击球数据

作者图片

注:拉赫曼数据库中的击球档案涵盖了自 1871 年以来的所有大联盟球员,并且每年更新一次。

击球数据帧中有 102816 行,由每个球员的原始棒球统计信息组成。此外,在一个赛季中转换球队并为不止一个球队打球的球员将为他的第一队获得一个名额以及相应的击球次数,为第二队获得两个名额以及该队的相应统计数据。因此,为了考虑球员在整个赛季中的表现,我们通过按球员和年份分组来汇总各阶段的数据。

Batting = Batting.groupby(['playerID','yearID']).sum()
Batting.reset_index(inplace=True)
Batting = Batting[(Batting.yearID >= 1998) & (Batting.yearID <= 2006) & (Batting.AB >= 130)]

我们还将我们的数据限制在 1998 年到 2006 年之间,以便充分分析金钱球年前后的年份。此外,我们只包括那些在职业棒球赛中至少有 130 次击球的球员。分数低于这个数字的玩家被认为是新手。

注:在拉赫曼击球数据框架中,单打和板的外观没有单独给出,但它们可以定义如下:

单打:安打——全垒打——三垒打——双打
板出场:击球+保送+投球+安打+牺牲安打+牺牲飞人

Batting['PA'] = Batting['AB'] + Batting['BB'] + Batting['HBP'] + Batting['SH'] + Batting['SF']Batting['OBP'] = (Batting['H'] + Batting['BB'] + Batting['HBP'])/(Batting['AB'] + Batting['BB'] + Batting['HBP'] + Batting['SF'])Batting['SLG'] = ((Batting['H'] - Batting['Doubles'] - Batting['Triples'] - Batting['HR']) + 2*Batting['Doubles'] + 3*Batting['Triples'] + 4*Batting['HR'])/Batting['AB']Batting['AVG'] = Batting['H']/Batting['AB']Batting['salary_year'] = Batting['yearID'] + 1

为了结束这一小节,我们将使用现有的击球数据计算击球出场数(PA)、上垒率(OBP)、击球率(SLG)和击球率(AVG)。我们还创建了一个 salary_year 变量,方法是在 yearID 变量上加 1,以便将今年的薪水与去年的击球统计数据相匹配——逻辑是薪水是基于击球表现的,因此球员的表现应该是可以观察到的。

和以前一样,我们将把我们的击球数据帧复制到数据帧中,这是我们正在维护的核心数据集。这里是在击球统计数据被复制后的一瞥。

作者图片

球员数据

继续,我们现在创建另外两个数据帧:外表。我们希望考虑球员在工资谈判方面的地位,通常有三种球员类别:新秀(0-2 年的经验,他们讨价还价的能力有限,他们没有谈判能力),符合仲裁条件(如果球员有 2-6 年的经验,并且对他们的工资不满意,他们可以向独立仲裁人提出争议,仲裁人将为球员决定公平的工资), 和自由代理(如果玩家的经验超过 6 年,他们实质上成为自由的,可以向最高出价者出售他们的服务/与最高出价者签订合同)。

Debut = People[['playerID','debut']].copy()
Debut['debut_year'] = Debut['debut'].astype(str).str[0:4]
Debut = Debut[['playerID','debut_year']]

为了将每个玩家归入这些类别中的一个,我们首先要找出每个玩家的经验。我们可以通过找出每个球员首次亮相的年份,然后计算从那以后的年份来做到这一点。

Master = pd.merge(Master, Debut, on=['playerID'], how = 'left')
Master['experience'] = Master['yearID'] - Master['debut_year'].astype(int)
Master['arbitration'] = np.where((Master['experience'] <= 6) & (Master['experience'] >= 3),1,0)
Master['free_agent'] = np.where(Master['experience'] > 6, 1, 0)

现在,我们将出道年份变量合并到我们的主数据框架中,并为三个玩家状态类别中的每一个创建三个二进制变量。

为回归分析准备数据的最后一步是确定每个球员的防守位置。虽然在棒球中有 8 个守备位置,Hakes 和 Sauer 使用了两个类别:接球手和内野手被他们定义为二垒、三垒或短停,默认类别被给予剩余的守备位置。出场的防守位置如下:

(G_c):接球手
(G_1b):一垒
(G_2b):二垒
(G_3b):三垒
(G_ss):短停
(G_of):外场手
(G_dh):指定击球手

def Position(df):
    if (df['max_games'] == df['G_c']): return "C"
    elif (df['max_games'] == df['G_1b']): return "1B"
    elif (df['max_games'] == df['G_2b']): return "2B"
    elif (df['max_games'] == df['G_3b']): return "3B"
    elif (df['max_games'] == df['G_ss']): return "SS"
    elif (df['max_games'] == df['G_of']): return "OF"
    elif (df['max_games'] == df['G_dh']): return "DH"Appearances = Appearances.groupby(['playerID','yearID'])['G_c','G_1b','G_2b','G_3b','G_ss','G_of','G_dh'].sum()                                       
Appearances.reset_index(inplace=True)
Appearances['max_games'] = Appearances[["G_c","G_1b","G_2b","G_3b","G_ss","G_of","G_dh"]].max(axis=1)
Appearances['position'] = Appearances.apply(Position, axis = 1)

再说一次,我们必须注意限制,因为一个球员可能在一个赛季中为不止一个球队效力,因此,他在两个球队的上场次数都需要考虑。因此,我们通过对球员和年份 id 进行分组来计算每个位置的总和。

这里要注意的另一件重要的事情是,虽然大多数球员主要在一个位置上比赛,但也有一些球员在一个赛季中在不止一个位置上比赛。因此,为了“定义”一个特定球员的防守位置,我们将假设该球员在整个赛季中的位置是他参加比赛次数最多的位置。因此,我们需要将出场中的数据转换成 Hakes 和 Sauer 在他们的研究论文中使用的格式。

Appearances = Appearances[Appearances['max_games'] > 0] 
Appearances = Appearances[['playerID','yearID','position']]
Appearances['catcher'] = np.where(Appearances['position'] == "C", 1, 0)
Appearances['infielder'] = np.where((Appearances['position'] == "2B") | (Appearances['position'] == "3B") | (Appearances['position'] == "SS"), 1, 0)Master = pd.merge(Master, Appearances, on=['playerID','yearID'], how = 'left')

最后,我们将排除非位置球员,只保留必要的变量用于演示。我们还将为我们的数据帧定义捕手和内野手虚拟变量,使用与 Hakes 和 Sauer 在将其合并到数据帧之前使用的相同定义,现在看起来是这样的:

作者图片

回归分析

在将我们所掌握的数据组织成一个统一的数据框架后,我们现在能够像 Hakes 和 Sauer 在他们的研究论文中所做的那样定义和运行我们的回归模型。如上所述,我们将使用以下公式作为回归方程:

日志(工资)= B0+B1 * OBP+B2 * SLG+B3 * PA+B4 * Arb+b5 *自由人+B6 *捕手+B7 *内场手

首先,我们将把我们的数据帧分成数据集: MB_Data_1MB_Data_2。前者包括《Moneyball》出版前(2000-2003 年)的季节性数据,后者涵盖该书出版后的三年(2004-2006 年)。

MB_Data_1 = Master[(Master.salary_year >= 2000) & (Master.salary_year <= 2003)]
reg1 = smf.ols(formula = 'log_salary ~ OBP + SLG + AVG + PA + arbitration + free_agent + catcher + infielder', data=MB_Data_1).fit()
reg1.summary()

作者图片

如果我们现在将回归模型返回的系数与 Hakes 和 Sauer 论文的表 3 中给出的系数进行比较,我们可以观察到,总的来说,这些系数非常相似。即使是标准误,表 3括号中的数字也和我们回归模型返回的数字差不多。根据 R 平方值,69.1%的对数工资方差由自变量解释。我们还可以看到,在《金钱球》出版之前的几年里,OBP 和 AVG 在统计上并不显著。

MB_Data_2 = Master[(Master.salary_year >= 2004) & (Master.salary_year <= 2006)]
reg2 = smf.ols(formula = 'log_salary ~ OBP + SLG + AVG + PA + arbitration + free_agent + catcher + infielder', data=MB_Data_2).fit()
reg2.summary()

作者图片

在《Moneyball》出版后的三年里,观察回归模型的结果,第一个值得注意的事情是,OBP 和 AVG 现在在统计上是显著的。不仅如此,OBP 系数已经飙升至 5.4004,几乎是 SLG 系数的两倍。这反过来表明,在后金钱球时代(2004 年至 2006 年),OBP 在决定球员薪酬方面变得更加重要了约 45%。

SLG 在 Moneyball 之前和之后仍然具有统计意义,但它没有 OBP 在 2004 年之后那么重要。另一件要注意的事情是,在这本书出版之前和之后,这两个守备位置在决定球员工资方面都没有统计学意义。最后,有趣的是,在后金钱球时代,球队并不看重平均击球率来决定球员的薪水,这似乎与传统的平均击球率观点相反。

MB_Data_2000 = Master[(Master.salary_year == 2000)]
MB_Data_2001 = Master[(Master.salary_year == 2001)]
MB_Data_2002 = Master[(Master.salary_year == 2002)]
MB_Data_2003 = Master[(Master.salary_year == 2003)]
MB_Data_2004 = Master[(Master.salary_year == 2004)]
MB_Data_2005 = Master[(Master.salary_year == 2005)]
MB_Data_2006 = Master[(Master.salary_year == 2006)]reg_2000 = smf.ols(formula = 'log_salary ~ OBP + SLG + AVG + PA + arbitration + free_agent + catcher + infielder', data=MB_Data_2000).fit()
reg_2001 = smf.ols(formula = 'log_salary ~ OBP + SLG + AVG + PA + arbitration + free_agent + catcher + infielder', data=MB_Data_2001).fit()
reg_2002 = smf.ols(formula = 'log_salary ~ OBP + SLG + AVG + PA + arbitration + free_agent + catcher + infielder', data=MB_Data_2002).fit()
reg_2003 = smf.ols(formula = 'log_salary ~ OBP + SLG + AVG + PA + arbitration + free_agent + catcher + infielder', data=MB_Data_2003).fit()
reg_2004 = smf.ols(formula = 'log_salary ~ OBP + SLG + AVG + PA + arbitration + free_agent + catcher + infielder', data=MB_Data_2004).fit()
reg_2005 = smf.ols(formula = 'log_salary ~ OBP + SLG + AVG + PA + arbitration + free_agent + catcher + infielder', data=MB_Data_2005).fit()
reg_2006 = smf.ols(formula = 'log_salary ~ OBP + SLG + AVG + PA + arbitration + free_agent + catcher + infielder', data=MB_Data_2006).fit()Header = ['2000','2001','2002','2003','2004','2005','2006']
Table_3 = summary_col([reg_2000,reg_2001,reg_2002,reg_2003,reg_2004,reg_2005,reg_2006,],regressor_order =['OBP','SLG','AVG','PA','arbitration','free_agent','catcher','infielder'],stars=True,float_format="'%.3f'",model_names = Header)
print(Table_3)

我们可以格式化和呈现我们的回归结果的另一种方式是如何在表 3 中完成的。为此,我们需要有一个每年(2000-2006)的回归模型。使用 statsmodels 的 summary_col 功能,我们能够说明每个季节中每个变量的系数和标准误差。因此,下面所附的表格实质上是我们对 Hakes 和 Sauer 表 3 的复制。

作者图片

因此,我们能够重申 Moneyball 故事中的关键叙述,并得到 Hakes 和 Sauer 出版物的证实:在 Moneyball 之前的时期(2000-2003 年),slugging 百分比对工资的影响几乎是 on-base 百分比的两倍,而后者在那个时期是微不足道的。然而,在 Moneyball 之后(2004-2006),上垒率不仅在统计上变得显著,而且在决定球员工资方面,它几乎是击球率的两倍重要,这是比利·比恩和他的统计学家支持的观点。

关于这个话题的更多信息

体育分析中的毕达哥拉斯期望,以及不同体育项目的例子

英超(EPL)毕达哥拉斯预测者

参考文献

  1. Jahn K. Hakes 和 Raymond D. Sauer 对钱球假说的经济评估
  2. 金钱球和超越密歇根大学
  3. 肖恩·拉赫曼的数据库许可证

机器学习中偏差和方差的区别

原文:https://towardsdatascience.com/differences-between-bias-and-variance-in-machine-learning-7f79110626da

茱莉亚·佐洛托娃在 Unsplash 上的照片

机器学习数据科学在最近十年里获得了很多关注。我们看到无人驾驶汽车、垃圾邮件过滤、检测制造单位的缺陷和人脸识别等众多应用。再者,如果成功实现机器学习和人工智能,估计公司可以达到高身价。然而,有时候机器学习模型并没有被 ML 的从业者彻底理解,这常常会导致困惑和沮丧。我们现在要讨论的概念分别是偏差方差。这些主题在大量的在线课程中有所涉及,但是值得记下它们之间的差异,并理解必须采取的正确步骤来克服它们。事不宜迟,让我们开始更详细地了解这些主题。

什么是偏见?

Unsplash 上由 Pablo Arroyo 拍摄的照片

当我们使用机器学习模型时,我们做的第一件事就是使用我们的数据并训练我们的模型,以在他们以前没有见过的数据上获得预期的结果。为了做到这一点,我们不断地训练我们的模型,并更新权重和参数,直到我们得到预期的结果。在这样做的时候,可能会有这样的情况,我们没有完全训练好模型,它们对于我们的机器学习预测任务来说太简单了。换句话说,我们只是使用简单的模型,而不是调整那些参数或改变模型来反映数据集中显示的复杂性。在这种情况下,我们看到模型(用于训练)有很高的偏差。因此,如果数据集存在高偏差,它就不能很好地理解数据集。

如何克服偏高?

在我们没有充分探索这些算法的潜力的机器学习中,具有高偏差可能是一个问题。因此,我们可以看看可以在很大程度上减少偏差的各种方法。

不太复杂的模型 —高偏差的主要原因是模型不够复杂,无法捕捉数据集中的复杂性以及输入和输出之间的关系。我们可能必须采取正确的措施来克服这种情况,并利用机器学习的全部力量。克服较高偏差的最佳方法是添加更复杂的预测模型。

交叉验证数据 —克服高偏倚的第二种方法是使用交叉验证数据,以便它可以帮助识别高偏倚问题。当我们在测试我们的模型之前将数据分为训练和交叉验证时,我们正在调整超参数并改变它们,以确保它们在交叉验证数据上表现良好。

寻找正确的数据 —另一个方便的方法是使用正确的数据而不偏向模型。尽管我们的模型过于复杂,并且能够理解数据中的错综复杂,但也有可能仅仅因为数据不包含输入和输出之间的关系或包含最小的关系,它们就失败了。因此,在确定模型是否具有高偏差之前,您可能还希望检查输入与输出的相关程度。

什么是方差?

CALIN STANUnsplash 上拍摄的照片

可能存在模型在训练数据上表现异常出色的情况。但是,当我们试图在它以前没有见过的数据上判断性能时,它失败了,或者至少没有在测试数据上给出预期的性能。在这种情况下,我们可以说模型过度拟合,并且具有很高的方差。换句话说,模型只是从训练数据中学到了很多,而不能对以前没有见过的数据进行很好的概括。这与我们日常生活中学生准备考试的情形非常相似。如果学生只从课本中学习,而不去探索其他来源,很有可能学生不能很好地理解课本中没有的新例子或信息。在这种情况下,学生在准备考试时过于强调课本,而不能对题目有一个大致的了解或了解。用外行人的话来说,这就是所谓的过度适应。

过度拟合可能是机器学习中的一个问题,在这种情况下,从业者得到的是 ML 模型性能的夸大图,并假设该模型可以对其之前未见过的数据执行类似的操作。然而,在大多数情况下,这远非事实。由于我们正在通过获取训练数据来优化和修改我们的算法,因此它在这些数据上表现良好也就不足为奇了。然而,当我们将新数据(测试数据)放入我们的模型时,它有时可能会失败,并且在训练数据和测试数据上的表现之间有很大的差异。因此,一个从业者必须花时间去了解和知道模型是否过拟合。以下是一些有助于在很大程度上减少过度拟合的方法。

如何克服高方差?

可以有各种步骤来帮助我们的 ML 模型减少过度拟合(高方差)。现在让我们来探索下面的每一种方法。

正则化— 这是一种技术,用于惩罚高度复杂的 ML 模型,并且权重从训练数据中捕获大量趋势。使用正则化可以确保通常分配的权重减少,并且在基于输入确定输出时不会起很大作用。换句话说,这使得模型能够很好地概括,而不需要过多地关注训练数据,并且还能够对他们以前没有见过的数据给出良好的结果。

集成— 当我们过于依赖一个 ML 模型而不考虑一个群体时,它就有可能过度拟合数据。另一方面,从模型(集合)列表中获取输出可以确保在决定模型的最佳结果之前考虑不同的可能输出。

减少特征— 数据中包含大量特征(高维度)有时也会导致模型从训练数据中学习过多。更糟糕的是,在训练数据中容易获得的特性,如果在测试阶段不可用,可能会使这些看不见的数据的结果不太准确。因此,减少特征并赋予模型对测试数据进行良好概括的能力是一种简便的方法。

添加训练数据— 如果我们的训练数据较少,这意味着 ML 模型没有那么复杂,它只是使用输入和输出之间的关系来对以前没有见过的数据进行预测。添加更多的数据会使它变得更加复杂,模型可能需要付出额外的努力来确定输入和输出之间的正确关系,然后再对看不见的数据进行预测。

进行交叉验证 —在实时数据上进行性能测试之前,将数据分为训练数据和交叉验证数据两部分的过程。最初,使用训练数据来训练 ML 模型,然后使用超参数的适当调整在交叉验证数据上观察性能。一旦 ML 模型在训练和交叉验证数据上的性能之间没有巨大差异,从业者可以停止训练过程,并使用这个被超参数调谐到的模型来测量其在实时数据上的性能,以减少高方差的问题。

使用提前停止 —人工智能的一些最受欢迎的应用是在深度学习领域。有大量的定制可用于网络,还有一个流行的机制叫做“迁移学习”,它正在彻底改变这个领域。有时,这些网络可能变得太复杂,并且从训练数据中学习得太好,而没有能力对测试数据进行很好的概括。因此,在训练阶段使用早期停止等方法会很方便,并导致高方差的减少。

结论

通读完这篇文章后,希望你发现这篇文章在执行任何与 ML 相关的查询或任务时是有帮助的并且是可操作的。在诊断模型和获得正确的解决方案之前,理解高方差和高偏差之间的区别是有用的。此外,用 ML 克服这些挑战对整个数据周期的发展有很好的影响。

如果你想获得更多关于我的最新文章的更新,并且每月只需 5 美元就可以无限制地访问中型文章,请随时使用下面的链接来添加你对我工作的支持。谢了。

https://suhas-maddali007.medium.com/membership

以下是您联系我或查看我作品的方式。

GitHub: 苏哈斯·马达利(Suhas Maddali)(github.com)

****YouTube:https://www.youtube.com/channel/UCymdyoyJBC_i7QVfbrIs-4Q

****LinkedIn:(1)Suhas Maddali,东北大学,数据科学| LinkedIn

中等: 苏哈斯·马达利——中等

使用 SQL 的 BigQuery 中编号函数之间的差异

原文:https://towardsdatascience.com/differences-between-numbering-functions-in-bigquery-using-sql-658fb7c9af65

了解如何使用等级、密集等级、行号、累积分布、百分位数等级、四分位数、百分位数等等

澳大利亚摄影师 Unsplash 上拍摄的照片

什么是编号功能?

编号功能为表中的每条记录分配一个数字(或小数)。它们主要用于对数据进行排序或分配序号,以便进一步处理(重复数据删除、过滤、分组)。

它们通常需要按特定维度排序(日期、收入、薪水、ID 等)。

它们可用于回答以下问题:

  • 收入最高的国家有哪些?
  • 我如何按赛区和工资给排球运动员排名?
  • 按产品类别划分,表现最佳的国家有哪些?
  • 根据摄取日期复制了哪些行?

本文将分为两节。第一部分将介绍RANK()DENSE_RANK()ROW_NUMBER()的机制,因为它们的目的非常相似,但输出和机制略有不同。

第二部分将涵盖PERCENT_RANKCUME_DISTNTILE,它们具有不同的目的、机制和输出。

我还建议阅读伟大的谷歌文档。

https://cloud.google.com/bigquery/docs/reference/standard-sql/numbering_functions

为了更好地理解这些函数之间的区别,我们将查询以下数据集,该数据集包含来自谷歌商品商店的不同国家和产品类别的销售额。

我们简单分析用例的基础表。(图片由作者提供)

行数

函数ROW_NUMBER()将总是返回一个唯一的数字,从 1 开始按顺序递增(1,2,3,4…,8,9…)。不需要指定顺序,即使行或值相似,输出编号也总是唯一的。

如果没有使用ORDER BY子句,结果将是不确定的,这意味着即使输入数据相同,结果也会不同。

让我们看两个例子:

ROW_NUMBER()返回一个连续且唯一的数字。(图片由作者提供)

在本例中,我们使用一个空的OVER()子句,这意味着函数将遍历表并为每一行随机分配一个数字(即使 BigQuery 如何分配它可能有一些逻辑)。

让我们看看在我们的第二个例子中,当我们向 revenue 字段添加一个ORDER BY子句时会发生什么。

ROW_NUMBER()返回一个按收入排序的连续且唯一的数字。(图片由作者提供)

行号现在按收入排序,我们希望结果按降序排列,这就是为什么我们添加了一个DESC命令(默认情况下,它是升序)。

对于具有相同值、IndonesiaTaiwan两行,功能输出数保持递增。

隐含地,还有一个字母顺序,这可以通过手动添加另一个排序参数来改变。

ROW_NUMBER() OVER(ORDER BY revenue DESC, country DESC)

假设我们想要分解每个产品类别的行号。为此,我们可以使用一个PARTITION BY子句,并按收入值降序排序。

ROW_NUMBER()为每个分区返回一个从 1 开始的连续且唯一的数字。(图片由作者提供)

这对于在数据集中可用的不同组或类别中排列/分配顺序非常有益。

秩和稠密 _ 秩

函数RANK()DENSE_RANK()的行为与ROW_NUMBER()相同,但有两个例外:它们如何序列号以及它们如何管理相似的值

对于RANK(),相似的行将获得相同的等级号,但是函数将在两个或更多相同的行之后留下一个间隙。

对于DENSE_RANK(),相似的行将接收相同的等级编号,但是等级编号总是递增 1,并且在我们的编号序列中不会有间隔。

让我们举例说明一个查询中的三个功能:

这三个功能按收入排序。(图片由作者提供)

这是我们不同函数的输出。您可以关注国家/地区Venezuela(重复)的第 5 行和第 6 行上的函数如何工作,以及之后:

  • ROW_ NUMBER()保持其递增顺序(1,2,3, 4,5,6,7
  • RANK()给出相同的输出值(5,5),但随后丢失其增量序列(1,2,3, 4,5,5,7 )
  • DENSE_RANK()给出相同的输出值(5,5),但保持其递增顺序(1,2,3, 4,5,5,6

第 10 行和第 11 行的机制相同,因为我们是按收入排序的,它们的收入相同。这种增量序列机制对于任意数量的对等值都是一样的。

为什么不用 ROW_NUMBER 代替 RANK 或者 DENSE_RANK?

您可以使用ROW_NUMBER()作为排名函数,但是有时为所有相似/对等行保持相同的排名值是很有趣的。

对于某些用例,用DENSE_RANK()保持数字序列总是递增 1 可能是一个好的选择。

CUME_DIST

函数CUME_DIST()计算数据集或分区内值的累积分布。它返回从 0 到 1 的值(> 0 且≤1)。

这个函数需要一个ORDER BY子句来对值进行排序。

根据 Google 的文档,它是使用公式计算的: NP/NR。我们可以这样来解释它:

  • NP位于当前行之前或与当前行相似的行数
  • NR是(整个数据集或一个分区的)总行数

它将向您展示数据集中的值是如何分布的。例如,基于收入的数据集行分布:

按收入排序的所有行的累积分布。(图片由作者提供)

我们使用ROUND()函数和乘法*100将数据转换成可读性更好的百分比格式。

让我们手动计算。对于我们的第一行,只有 1 个值,并且没有低于 1323 的收入。这给了我们 1/12 = 8% (我们的数据集中有 12 行)。

现在,让我们看看第 4 行,Nigeria,有 3 行的值在当前行的 3314 + 下。这给了我们 4/12 = 33%

有趣的部分是针对第 2 排和第 3 排(或第 7 排和第 8 排)。它们具有相同的收入值。因此,如果我们查看第 2 行,我们可以预期计算结果为 2/12 = 16%。但是,由于第 3 行是相似的,所以两行的结果都是 3/12 = 25%。

恩蒂莱

NTILE()函数允许您将一组已排序的数据点分成均匀分布的桶。你可能知道这就是分位数,它可以有不同的类型:

  • 四分位数 (4 个分位数)
  • 十分位数 (10 个分位数)
  • 百分位数 (100 个分位数)

例如, quartiles 将数据集分成四个大小相等的桶。这意味着第一个四分位数(Q1)包含 25%的数据点。

让我们将四分位数应用到我们的表中:

我们的行根据收入分成 4 个大小相等的桶。(图片由作者提供)

在这种情况下,我们根据有序收入将我们的数据点分成四个相等的桶(每个桶有 3 行,即 3/12 = 25% )。

请记住这不是总数的百分比。如果我们将第四个四分位数国家(法国、日本和美国)的收入相加,它确实占总收入的 90%。

使用此功能时,您必须提供一个输入号码,例如:NTILE(4)。你不能让这个参数为空,给一个 0 或负值的输入数而没有看到一个错误。

您可以使用这种方法在您的数据中找到离群值(高于 95%百分点的数据点)根据他们的购买价值(前 25%)对您的客户进行分类,等等。

百分比排名

函数PERCENT_RANK()计算一组值中值的百分比分布。它返回从 0 到 1 的值。

这个函数需要一个ORDER BY子句来对值进行排序。

所有行按收入排序的百分位数排名(图片由作者提供)

同样,我们使用ROUND()函数和乘法*100将数据转换成可读性更好的百分比格式。

看产出,Germany收入最低(不大于其他任何国家),所以百分位排名为零。

另一方面,United States拥有所有国家中最大的收入(大于任何其他国家),因此百分位等级为 1(或 100%)。

对于France,百分位数排名为 82%。这意味着它的收入高于所有其他国家的 82%。

结论和想法

从实际经验来看,最常用的功能有ROW_NUMBER()RANK()DENSE_RANK()NTILE()

例如,我的一项任务需要使用DENSE_RANK()来识别收购产品,基本上是对客户订单中的产品进行排序,以识别哪些是最先购买的产品。这个函数允许我们将序列递增 1,在一个订单中处理多个产品,并且仍然能够计算客户订单总数的正确数量。

在另一项任务中,NTILE()帮助将客户分类为近期和频繁购买者类别(如 RFM 模型(近期、频率、货币)),这将用于在我们的电子邮件服务提供商系统中进行细分。

您可以在其他数据库系统(Amazon Redshift、MySQL、Postgres、Snowflake 等)中找到这些函数,因为它们是流行的 SQL windows 函数(至少对于排名和行号)。

引用和数据集

本文中使用的数据集来自于 BigQuery 公共数据,是在 CC BY 4.0 许可下的 Google analytics 数据样本。

https://developers.google.com/analytics/bigquery/web-ecommerce-demo-dataset

LDA、QDA 和高斯朴素贝叶斯分类器的区别

原文:https://towardsdatascience.com/differences-of-lda-qda-and-gaussian-naive-bayes-classifiers-eaa4d1e999f6

深入研究建模假设及其含义

在挖掘经典分类方法的细节时,我发现了关于高斯朴素贝叶斯(GNB)、线性判别分析(LDA)和二次判别分析(QDA)的异同的稀疏信息。这篇文章集中了我为下一个学习者找到的信息。

总结:所有这三种方法都是贝叶斯分类器的一个具体实例,它们都处理连续高斯预测器,它们在预测器之间和类之间关系的假设上有所不同(即,它们指定协方差矩阵的方式)。

贝叶斯分类器

我们有一组 X 个 p 个 预测值,以及一个离散响应变量 Y(类),取值 k = {1,…,K},用于样本的 n 个 观察值。

我们遇到了一个新的观察值,我们知道预测值 X 的值,但不知道 Y 类的值,所以我们想根据我们拥有的信息(我们的样本)对 Y 进行猜测。

贝叶斯分类器将测试观测值分配给具有最高条件概率的类,由下式给出:

贝叶斯定理

其中:pi_k 是先验估计,f_k (x)是我们的似然。为了获得类 k 的概率,我们需要定义先验和似然的公式。

先验。观察到类别 k 的概率,即我们的测试观察值属于类别 k,但没有关于预测值的进一步信息。查看我们的示例,我们可以将类 k 中的情况视为具有二项式分布的随机变量的实现:

在一组试验次数 n(样本量)中成功次数(k 类中的观察值)的分布。

其中,对于n 次试验,在每次试验中,观察值属于(成功)或不属于(失败)类别 k。可以看出,相对成功频率——试验总数中的成功次数— 是 pi_k 的无偏估计量。因此,我们使用相对频率作为观察值属于类别 k 的概率的先验。

可能性:可能性是看到 X 的这些值的概率,假设观测值实际上属于类别 k。因此,我们需要找到类别 k 中预测值 X 的分布。我们不知道“真实”的分布是什么,所以我们无法“找到”它,我们宁愿对它的样子做一些合理的假设,然后使用我们的样本来估计它的参数。

如何选择合理的配送?离散的和连续的预测值之间有一个明显的区别。这三种方法都假设在每个类中,

预测值具有高斯分布(p=1)或多元高斯分布(p>1)。

k 类条件下多元高斯分布的一般形式。

因此,只有当我们有连续的预测器时,才能使用这些算法。事实上,高斯朴素贝叶斯是一般朴素贝叶斯的一个特例,具有高斯似然性,这就是为什么我在这篇文章中将它与 LDA 和 QDA 进行比较。

从现在开始,我们将考虑能够展示三种方法之间差异的最简单的情况:两个预测器(p=2)和两个类(K=2)。

线性判别分析

LDA 假设跨类的协方差矩阵是相同的。

这意味着类别 1 和类别 2 中的预测值可能具有不同的均值,但是它们的方差和协方差是相同的。这意味着预测值之间的“分布”和关系在各个类别中是相同的。

基于 LDA 假设的类别分布可视化

上面的图是从每种形式的分布中生成的:

LDA 第 1 类和第 2 类预测因子的分布

我们观察到协方差矩阵是相同的。如果我们预期预测值之间的关系在不同类别之间不会发生变化,并且如果我们只是观察到分布均值的变化,那么这种假设是合理的。

二次判别分析

如果我们放松 LDA 的恒定协方差矩阵假设,我们有 QDA。

QDA 没有假设跨类的协方差矩阵不变。

基于 QDA 假设的类别分布可视化

上面的图是从每种形式的分布中生成的:

QDA 第一类和第二类预测因子的分布

我们观察到这两个分布的所有参数都可以变化。如果我们预期不同类别的预测者之间的行为和关系非常不同,这是一个合理的假设。

在这个例子中,甚至两个预测值之间的关系的方向也从类别 1 到类别 2 变化,从正协方差 4 到负协方差-3。

高斯朴素贝叶斯

GNB 是朴素贝叶斯的一个特例,其中预测值是连续的,并且正态分布在每个 k 类中。一般的朴素贝叶斯(因此,GNB 也是)假设:

给定 Y,预测因子 X 是条件独立的。

独立意味着不相关,即协方差等于零。

基于 GNB 假设的类别分布可视化

上面的图是从每种形式的分布中生成的:

GNB 第一类和第二类预测因子的分布

使用朴素贝叶斯,我们假设预测值之间没有关系。在实际问题中很少出现这种情况,然而,正如我们将在下一节中看到的那样,这大大简化了问题。

假设的含义

选择模型后,我们估计类内分布的参数,以确定我们的测试观察的可能性,并获得我们用来分类它的最终条件概率。

不同的模型导致不同数量的参数被估计。提醒:我们有 p 预测器和 K 总类。对于所有的模型,我们需要估计预测值的高斯分布的平均值,这在每一类中都是不同的。这就产生了一个基数,p*K为所有方法估计的参数。

此外,如果我们选择 LDA,我们将估计所有 p 个预测值的方差和每对预测值的协方差,从而得出

用 LDA 估计的参数数量

参数。这些是跨类的常数。

对于 QDA,由于它们在每一类中都不同,我们将 LDA 的参数数量乘以 K,得到下面的估计参数数量的等式:

与 QDA 一起估计的参数数量

对于 GNB,我们只有每一类中所有预测因子的方差: **pK** 。*

很容易看出对于 p 和/或 K 的大值使用 GNB 的优势。对于经常出现的二进制分类问题,即当 K=2 时,这是三种算法的模型复杂度如何随着 p 的增加而发展。

那又怎样?

从建模的角度来看,当应用一种方法时,知道你正在处理的假设是很重要的。需要估计的参数越多,最终分类对样本变化越敏感。同时,如果参数的数量太少,我们将无法捕捉到类之间的重要差异。

谢谢你的时间,我希望它是有趣的。

除特别注明外,所有图片均为作者所有。

来源:

  • 机器学习第三章——汤姆·m·米切尔,2017
  • 《统计学习导论》第 4 章—James,Witten,Hastie,Tibshirani,2013

将 Google Drive 连接到 Google Colab 笔记本的不同方式!(第二部分)

原文:https://towardsdatascience.com/different-ways-to-connect-google-drive-to-a-google-colab-notebook-part-2-b867786aed55

使用 Google Colab 处理数据的协作方式

照片由戴恩·托普金Unsplash 上拍摄

继续讨论将 Google Drive 连接到 Google Colab 笔记本的不同方式这一次我想分享仅通过文档名称调用 google sheet 文件并使其成为熊猫数据框的方法。此外,我想分享 2 种不同的方式,我用来发送数据帧到一个新的谷歌表文件或更新现有的一个。

照片由 Maxime HorlavilleUnsplash 上拍摄

Google 协作代码片段

谷歌合作实验室有一个很棒的地方,那就是他们的代码片段可以帮助你做很多事情。从使用 Altair 库的可视化到使用 python 的数据库连接,甚至是使用网络摄像头捕捉图像以便在运行时处理的代码片段(Colab 内核)。

Google Colab 代码片段 Altair 库可视化—作者图片

要访问此 Google 协作片段:

  1. 去你的 Google Colab 笔记本。
  2. 点击笔记本左下方的代码符号。

代码片段—作者图片

3.单击显示代码片段列表后,它会在笔记本上自动提示为一个新选项卡。

代码片段 2 —作者图片

4.您可以使用过滤器部分来搜索您需要的代码片段。

Google Sheets 代码片段——作者图片

如果您忘记了连接到 Google Drive 或将数据保存到 Google Sheet 的代码,这可能会对您有所帮助,我将在本文中向您展示这一点。

连接到一个特定的 google 工作表并使其成为一个数据框架

在第一部分中,您将学习如何通过使用文档名将 google sheets 数据连接到 google colab 笔记本。

  1. 你将认证
  2. 然后将带来谷歌证书进行连接
  3. 授权连接
  4. 使用 google 工作表的名称连接到该工作表(输入名称,替换“”中的{}。并指定文档的工作表(选项卡)。
  5. 导出所有数据值。
  6. 使用 pandas 将其转换为 Pandas 数据框架
  7. 搞定了。开始你的分析。

代码和示例—作者图片

更新现有的 Google 表单

下一段代码将帮助您获取已经存在的 google sheet,并用 Google Colab 中的新数据更新它。

因为在上一点中,我们学习了使用名称打开 Google Sheet,所以让我们使用 Google Sheet 键(URL 中的一系列数字和字母,给出了唯一的键标识符)。

  1. 让我们从 google collaboratory 中的 vega_datasets、 datasets 中获取数据。

Czars 数据集-按作者分类的图像

2.现在我们已经有了数据集,让我们确保它不包含任何 Nan 值,因为这在更新过程中不会被识别,并且会给我们带来错误。
-我们可以使用下面的代码来查看任何包含空值的列,以及显示了多少个空值:

cars.isnull().sum()

由于数据中出现的空值是数值,我们可以用 0 填充这些值。由于这只是一个练习,我们并没有对数据进行深入的分析,所以我将用 0 来填充这些数据,但是在现实世界中,如果我们想要有好的数据,您应该更好地处理这些空值。
-我过去用过的一些处理空值的方法:
1。计算您的列中的空值百分比,如果它很大,您应该考虑删除该列,如果基本上整个列都是 Nan,它不会给您任何洞察力。
2。如果%根本不重要,并且你认为它可以处理,看看你是否可以使用平均值或使用 0。
3。使用数据估算器(参见我的文章用 Plotly 在地图中显示布宜诺斯艾利斯属性,其中我展示了如何使用估算器来填充经度和纬度数据列中的空值)。

3.用 0 填充空值

cars.fillna(0, inplace=True)

4.在用 0 填充这些空值之后,让我们使用键连接到 google sheet。在这里,您需要将{key}更改为可以在 URL 中找到的 google sheet key

谷歌工作表关键字——作者图片

5.现在,我们需要将数据转换成列表,以便在更新过程中可以识别数据,这是为了显示数据应该在哪里(单元格)以及每个值的列名。
在我们进行更新之前,我们应该确保我们所有的数据都是以字符串或 int/float 数据类型分配给更新列表的。

代码示例更新—作者图片

使用 Google Colab 创建新的电子表格并添加数据

最后,我们正在使用 Google Colab 笔记本创建一个新的电子表格,并添加新数据。如果您正在使用 google colab 对您带来的一些数据进行分析,并希望使用 google sheets 对其进行分析,但不想下载并上传到 Google sheets,或者不想更新您已经拥有的电子表格,并希望使用新的电子表格进行输出,这可能会对您有所帮助。你可以在不同的场合使用这个。

与我们之前使用的代码有些类似,但这次只是为了从头开始创建一个新的电子表格。

创建和添加数据-按作者分类的图像

结果—作者提供的图像

这样,我最终确定了一些将 Google Drive 连接到 Google Colab 笔记本的不同方式。这些是我过去在协作环境中使用过的一些东西,对我帮助很大,最重要的是,您不需要将代码或 Jupyter 笔记本发送给某人来运行、安装库并为他们创建一个运行它的环境,这也有助于避免发送文件,这需要时间,只需在同事或团队成员之间共享即可。

希望这对 yall 很有用!

一如既往,我将很高兴知道您的任何反馈!如果你有任何与此相关的新话题,想知道如何去做,请告诉我,我会写下来的!

PD:不要忘记所有这些代码片段都可以在 google colab 笔记本的代码片段特性下找到!

马特·琼斯Unsplash 上拍照

将 Google Drive 连接到 Google Colab 笔记本的不同方式!(第一部分)

原文:https://towardsdatascience.com/different-ways-to-connect-google-drive-to-a-google-colab-notebook-pt-1-de03433d2f7a

使用 Google Colab 处理数据的协作方式

约书亚·阿拉贡在 Unsplash 上拍摄的照片

如今,我们生活在一个协作的环境中,我们的项目与我们的同事共享,以供审查或协作。此外,我们每次都越来越多地使用我们的云存储,如果它必须用于任何数据目的(。csv,。json,。xlsx 文件等。)更好的是,最重要的是,如果我们在一个协作的环境中,任何可以访问这些信息的人都可以在我们的项目中合作或共享更多数据。

因此,在本文的第 1 部分,我将与您分享我在数据路径和协作工作中学到的一些技巧!

连接 Google Drive 的特定文件夹

假设我们想要分析 Google Colab 笔记本中的一些数据。最近 Google Drive 分享给我们的 csv 文件。但是,我们不想从 Google Drive 下载所有数据,然后上传到我们的笔记本电脑,这将需要时间,也许那些。csv 文件可能会在未来发生变化,我们将不得不重新制作整个过程。

为此,Google Colab 有一种使用以下代码挂载我们的 Google Drive 的方法:

运行此代码后,将会提示一些消息:

连接到 Google Drive —图片由作者提供

选择您想要安装的 Google Drive 帐户——按作者分类的图片

选择“允许”就可以了。—作者图片

安装 Google Drive 后,您可以浏览所有 Google Drive 文件夹,并导航到您想要读取和分析已共享文件的文件夹。为此,您需要使用以下代码导航到 Google Drive 的主目录:

%cd gdrive/MyDrive

最后,使用 %cd 继续浏览您的文件夹和文件。

如果你想从一个文件夹中读取很多文件,这是我发现的最好的选择。

我通常使用下面这段代码来读取和连接单个数据帧中的所有文件(仅当所有文件具有相同的模式时;相同数量的列和名称):

你可以查看我的 bikes 笔记本 我用这些代码挂载我的 Google Drive 的地方,导航到我所有的。托管 csv 文件。然后,我读取、清理、处理、转换和连接所有这些文件到一个单一的数据框架。

将文件保存在您访问的同一文件夹中

在导航到所需的文件夹并获得所有要分析的数据后,您还可以将最终结果保存在同一文件夹中,或者导航到驱动器中的任何其他文件夹,并使用以下保存代码保存数据。csv 文件:

df.to_csv("name_file.csv")

或将数据保存到您希望保存的文件类型中的任何其他代码。

将特定文件从 Google Drive 上传到我的 Google Colab 笔记本

也许有时我们只需要/想要从我们的 Google Drive 上传单个文件到我们的运行时/内核/环境,而我们不想挂载整个驱动器。

我们可以通过使用以下代码将我们的文件下载到 Google Colab 中来实现:

首先,我们需要导入必要的库:

然后,您可以运行下面的代码:

在运行上面的代码之前,您需要先找到您的文件 id,为此,在 Google Drive 中找到您的文件,右键单击它以找到文件链接:

点击“获取链接”——作者图片

一旦你得到链接,你会看到这样的内容:

[https://drive.google.com/file/d/{"ID"}/view?usp=sharing](https://drive.google.com/file/d/1F758TadSiQtX5iew2UdUfS5IUu3CBVe-/view?usp=sharing)

红框=文件 id-作者图片

红框中包含数字和字母的整个字符串将是您的 id,需要插入代码的这一部分:

file_id = '{insert your id here}'

然后会提示一条消息:

点击链接并按照步骤操作——作者图片

点击该链接后,它会要求您登录您的谷歌帐户,并提供访问谷歌 colab 以访问您的谷歌驱动器,最后它会给你一个代码,复制并粘贴到“ 输入验证码:

复制粘贴代码,并将其插入输入框——作者图片

最后,你可以对这些文件做任何你想做的事情。

结论

我向你展示了两种不同的方式来上传或访问你的谷歌硬盘上的文件。

  1. 将你的 Google Drive 安装到你的 Google Colab
    ——你可以通过浏览文件夹将文件保存到你的 Google Drive 中。

优点:

  • 你把你的整个 Google Drive 安装到你的 Google Colab 笔记本上。
  • 如果您的项目需要来自不同位置的多个文件,您可以浏览 Google Drive 中的文件夹。

缺点:

  • 您保存的任何文件都将保存在您所在的确切文件夹中,因此,如果您不确定您在 Google Drive 中的确切位置,那么在保存之前运行以下表达式会很有帮助:
%pwd
  • 有时,如果你不能完全确定你在哪里,文件的保存可能会在你的 Google Drive 里变得一团糟,然后你就需要自己整理一切了。

2.上传文件,无需安装整个 Google Drive。

优点:

  • 您不需要挂载整个驱动器,因此您将始终位于 Google Colab 运行时内的相同路径/目录中。
  • 保存的文件将保存到您的运行时中,而不是您的 Google Drive 中,这将是一个 Pro 如果您不想将文件保存到 Google Drive 中,或者不需要浏览文件夹,您可以在完成项目后随时将文件下载到您的 Pc 中。

缺点:

  • 这对于多个文件来说不是最佳的,你需要多次将它们下载到你的运行时(运行相同的代码),每次下载不同的文件。

最后,这是我将要写的一系列 Google Drive 和 Google Colab 连接的第 1 部分。希望这对您的协作环境有所帮助!

我很高兴知道您的任何反馈!如果你有任何与此相关的新话题,想知道如何去做,请告诉我,我会写下来的!

照片由刘汉宁·内巴霍Unsplash 上拍摄

在 Python 文件中运行 Bash 脚本的不同方式

原文:https://towardsdatascience.com/different-ways-to-run-bash-scripts-in-your-python-file-8aeb721bc3d1

利用子流程、OS、Glob 等

图片来自 UnsplashMichael Dziedzic

对于数据科学家来说,理解 Shell 脚本的基础知识至关重要。通过命令行(CLI)的强大功能,许多流程可以实现自动化和简化。有时,当您开始构建更复杂的模块/类时,您会发现自己需要将 Shell 命令合并到 Python 代码中。

幸运的是,我们有几个 Python 模块可以让它变得非常简单。我们将主要关注子进程操作系统glob 模块。他们每个人都有自己的额外津贴,我只是想给一个如何利用他们的快速概述。

子过程

正如他们的文档所示,这个模块有三个主要方面。

  1. 可以产生新的过程
  2. 连接到它们的输入/输出/错误管道
  3. 获取他们的返回代码

让我们直接进入主题,通过几个简单的片段快速了解一下如何执行 Shell 命令。让我们先导入我们的模块。

制作目录

让我们首先使用 mkdir Linux 命令创建一个名为“code”的目录。

创建一个名为 code 的目录

我们现在可以看到在我们的文件结构中创建的目录。

目录已创建(作者截图)

建立档案

让我们继续尝试一些更基本的 Linux 命令。让我们创建一个文件,稍后我们可以将它移动到我们的目录中。

建立档案

我们现在可以在同一个目录中看到我们的 Python 文件。

作者截图

您可以继续使用这个 调用方法 来执行您选择的不同命令。

要生成一个新流程,请检查子流程。Popen 方法。当您开始处理必须执行的更复杂的命令和程序时,这将特别有用。Subprocess 是一个功能强大的库,具有最新的特性,查看他们的文档这里

操作系统(Operating System)

顾名思义,模块可以用来执行许多操作系统任务。它涵盖了子流程模块提供的许多相同功能。让我们重复上面对子流程所做的相同任务。

创建目录

使用操作系统模块创建目录

现在,您应该能够看到另一个目录被创建。

作者截图)

假设我们想要列出或解析当前路径的所有文件和目录,您可以使用下面的命令来完成。

获取文件路径

使用操作系统获取路径

我们可以将该路径传递给 listdir 命令,该命令将返回该目录中的所有目录和文件。

列出文件/目录

获取路径中的文件/目录

我们应该会看到我们在当前目录中创建的所有目录和文件。

作者截图

使用操作系统模块,您可以处理大量文件来执行您可能需要执行的任何标准操作或解析。

一团

具体来说, glob 模块可以真正用于文件名匹配。通常,您会希望解析具有特定模式或常见类型的文件。

让我们快速看一下如何列出我们目录中的所有 Python 文件。

列出所有 python 文件

这应该会返回我们已经创建的两个 Python 文件。

作者截图

随着您的用例变得越来越复杂,您可以搜索越来越复杂的模式,并加入正则表达式来过滤您的文件。这里有一篇关于 TDS 的优秀的文章,深入探讨了 glob 模块。

结论

通过使用这三个模块,甚至更多的模块,比如 Pathlib ,您可以将 Shell 命令合并到您的 Python 脚本中。本文中的简单示例可能不会显示直接的用例,但是随着您对更深层次的代码库的深入研究,有必要与 CLI 进行交互。

如果你喜欢这篇文章,请在LinkedIn上与我联系,并订阅我的媒体 简讯 。如果你是新手,使用我的 会员推荐 报名。

差分进化:非线性凸优化的替代方案

原文:https://towardsdatascience.com/differential-evolution-an-alternative-to-nonlinear-convex-optimization-690a123f3413

了解差分进化的基础知识及其在 Python 中的应用

阿曼德·库利在 Unsplash 上拍摄的照片

优化是一种决策制定,或者更具体地说,是决策制定机制中的主要量化工具之一,其中必须做出决策,以在一些规定的情况下优化一个或多个目标(Bilal 等人,2020)。

差分进化(DE) (Storn & Price,1997)是一种进化算法(EA),最初设计用于解决连续域上的优化问题。它实现简单,但解决问题的质量很好,这使它成为最受欢迎的基于群体的算法之一,有几个成功的应用报告。

从最初的概念来看,DE 的设计是为了满足一些使其特别有用的要求:

  1. 处理不可微、非线性和多模态成本函数的能力。
  2. 处理计算密集型成本函数的并行性。
  3. 易于使用:很少的控制变量来控制最小化。这些变量也应该是稳健的和易于选择的。
  4. 良好的收敛性:在连续的独立试验中一致收敛到全局最小值。

在整篇文章中,我们将看到差分进化的基础知识以及对单目标优化问题的应用——尽管它对多目标优化也有一些扩展。它将与传统的基于凸梯度的算法进行比较,以评估每种算法何时更合适。所有这些都将使用 scipy.optimize Python 模块来执行。感兴趣的可以在我的示例笔记本中找到实现细节。

如果你对非线性优化不熟悉,建议阅读我之前的文章 非线性规划:理论与应用

下坡过程在当时是一个很好的类比…然而,搜索空间在本文中可能会变得更加混乱。

算法解释

就像自然界一样,进化算子对进化算法种群进行操作,试图产生适应度越来越高的解。与这些算法相关的三个主要算子是突变、重组(交叉)和选择(存活) (Coello 等人,2007)

下面表示了 DE 算法的基本结构,接下来将描述其中的主要算子和它们各自的控制参数。

差异进化的基本结构。(图片由作者提供)。

该算法首先基于用户指定的个体数量 N 和问题的每个决策变量的边界来初始化群体。每个个体对应一个优化变量的向量。在 5 到 10 倍的决策变量之间选择 N 可能是一个好的开始。

基于个体对应的目标函数值和可能的约束值,个体被分配一个适应值。最初,DE 没有约束处理的规则,这是后来几篇文章的焦点。Lampinen (2002 年)提出了一种有用的方法,因为它表现出有竞争力的性能,并且在实施时不需要额外的控制参数。这是在 scipy DE 实现中采用的方法。

然后,种群在连续的世代中迭代,直到满足某些停止标准。在每一次迭代中,新的试验向量通过称为突变交叉的操作产生。然后,将试验向量与其对应的相同索引的父向量进行比较,并将每对中的最佳向量传递给下一代。停止标准通常基于目标函数和代数的改进。

已经为 DE 提出了几种复制方案。通常用 DE/x/y/z 表示,其中 x 对应变异亲本选择方案, y 对应差异向量个数, z 对应交叉策略。

可能最流行的突变方案是 DE/rand/1,由下面的等式表示。

差异进化的突变。(图片由作者提供)。

其中, v 对应一个索引为 i 的突变载体, r1r2r3 为互不相同且与 i 不同的三个索引。参数 F 是用户自定义的控制参数,表示为突变参数或比例因子

一些策略可能会强制开发超过探索,这取决于如何选择父向量。加强利用的一种常用策略是最佳/1 策略,其中基本向量对应于种群中具有最佳适应值的个体。根据我的经验,这种策略通常会导致过早收敛,这也是我通常避免这种策略的原因。

开发探索之间的权衡也存在于突变参数 F. 的选择中。例如,当需要强调探索时,例如在具有不连续决策空间的问题中,使用更高的值可能是有用的。相反,为了强调利用,使用较低的限制值可以改善结果。

此阶段通常执行的其他操作有抖动抖动抖动根据用户指定值的范围,为每个创建的突变向量单独随机化 F 。一开始,一个好的选择可以是[0.3,1.0]。抖动将每个差向量的每个分量乘以一个随机值,从而加上旋转。

最常见的交叉策略是二项式交叉或仅 bin ,这在下面的等式中描述。

差分进化中的二项式交叉。(图片由作者提供)。

其中对应于结合相应突变载体 v 和目标 x 的元素而创建的试验载体。参数 CR 控制从每一个继承一个属性的概率,并且附加的规则规定的至少一个属性必须从 v 继承以避免重复。**

根据 Price 等人(2005)的观点,在低 CR 值下表现良好的目标函数是可分解的——可以写成一维函数的和,而那些需要接近 1 的值的目标函数是不可分解的。Zaharie (2009)对差分进化算法中交叉算子的影响进行了详细的研究,我建议对算法细节感兴趣的人进一步阅读。

以我的经验来看,大部分非线性现实问题都是不可分的,其中高 CR 值,比如 0.7-0.9,是个不错的选择。例外情况包括具有周期性项和强多模态的问题,其中沿着独立坐标轴的搜索可能是有利的。在这类问题中,选择 0.2–0.5 可能会产生更好的结果。

接下来让我们看一些应用。在我们的实现中,将使用来自 scipy.optimizedifferential_evoluton 函数。

凸问题

在第一个例子中,让我们尝试在 上一篇文章 中使用的相同目标函数。

凸问题的目标函数。(图片由作者提供)。

用 Python 代码。

**# Defining the objective function
def obj_fun(x):
    return (x[0] - 0.5) ** 2 + 0.7 * x[0] * x[1]\
        + 1.2 * (x[1] + 0.7) ** 2

# Defining the gradient function
def gradient_fun(x):
    return np.array([2 * (x[0] - 0.5) + 0.7 * x[1],\
        0.7 * x[0] + 2 * 1.2 * (x[1] + 0.7)])**

看起来像这样。

决策空间与凸问题的真最优。(图片由作者提供)。

我们将使用拟牛顿 BFGS 方法和 DE 来解决这个问题。

**# Optimization using BFGS (gradient-based method)
sol_cvx = minimize(obj_fun, [2.5, 0.0], jac=gradient_fun,
                   method="BFGS")

# Otimization using Differential Evolution
sol_de = differential_evolution(
    obj_fun,
    bounds=[(-5., 5.), (-5., 5.)],
    popsize=50, strategy="rand1bin",
    mutation=(0.3, 1.0),
    recombination=0.7,
    tol=1e-8,
    maxiter=200,
    updating="deferred",
    polish=False
)**

请注意,第一个区别是指定决策变量的界限,而不是初始估计值 x0 。这些界限用于创建初始群体。

自变量 popsize 对应于 N 人口数量;策略为 DE/x/y/z 策略;突变F 参数;重组CRtol 对停止准则方面的改进;和 maxiter 到最大代数。通过将上升设置为“延迟”,我选择使用最初的策略,即每一代只更新当前群体一次。参数 polish 定义了在当前最佳解决方案的每次迭代中是否使用局部优化器,我选择不使用。

在真正的最优解中,这些解基本上是重叠的。

凸问题的结果。(图片由作者提供)。

主要缺点是 DE 使用 50 的种群大小花费了 56 代来收敛,因此它进行了几个目标函数评估,这可能是计算上昂贵的。相反, BFGS 进行了 6 次迭代和 7 次函数评估。

理解问题是至关重要的,这样选择的算法才是有效的,而不会导致不必要的函数计算。在第一个问题中,基于凸梯度的算法显然是比 DE 更有效的选择,尽管两者导致完全相同的最终结果。

让我们看看它会如何改变…

多模态问题

如果搜索空间有几个局部最优怎么办?基于凸梯度的算法是局部搜索方法,因此它们很可能陷入这些点。当局部最优不够时,差分进化是一个更有效的工具。

让我们给我们的目标函数添加一些多模态术语。

**# In the nonconvex problem we add some periodical terms
def obj_fun_2(x):
    A = np.array([[1, 0.3],
                 [0.3, 0.7]])
    xt = x.dot(A)
    rugosity_1 = (xt[0] * np.sin(15 * xt[0]) - 0.5) ** 2\
        + (xt[1] * np.sin(15 * xt[1]) - 2) ** 2
    rugosity_2 = np.sin(15 * x[0]) ** 2 + np.sin(25 * x[1]) ** 2
    convex = obj_fun(x)
    return 5 * rugosity_2 + rugosity_1 + convex**

非凸例子。(图片由作者提供)。

我觉得这张图片很漂亮,尽管相当混乱,而且基于梯度的搜索方向肯定不会带我们去任何地方。让我们试着解决这个问题来看看。

**sol_cvx_2 = minimize(obj_fun_2, [2.5, 0.0], method="BFGS")

sol_de_2 = differential_evolution(
    obj_fun_2,
    bounds=[(-5., 5.), (-5., 5.)],
    popsize=50, strategy="rand1bin",
    mutation=(0.2, 1.0),
    recombination=0.7,
    tol=1e-8, maxiter=200,
    updating="deferred", polish=False)**

使用预先指定的初始估计,已经实现了 12.12 的目标函数,比 DE 实现的 1.613 差得多(这很可能是全局最优)。

非凸问题的结果。(图片由作者提供)。

我们可以尝试几种不同的初始估计,在二维问题中,可能会导致我们满意的结果。在这个问题中,我尝试了 500 种不同的初始估计,得到的最高结果是 1.855。然而,高维搜索空间的稀疏性使得这样的过程非常无效。

工程压力容器设计

下一个问题由 Sandgren (1990)提出,它使压力容器设计的成本最小化。由于材料供应标准,该问题有两个离散变量 x1x2 ,因此在其原始公式中是不可微的。然而,在本节中,我们将首先解决问题的连续实值变体,然后解决原问题。

目标函数由下面的等式描述。

压力容器设计的目标函数。(图片由作者提供)。

和边界约束由下面的等式来表示。

压力容器设计的功能限制。(图片由作者提供)。

压力容器设计的界限。(图片由作者提供)。

用 Python 代码。

**def f_vessel(x):
    return 0.6224 * x[0] * x[2] * x[3]\
        + 1.7781 * x[1] * x[2] ** 2\
        + 3.1611 * x[0] ** 2 * x[3]\
        + 19.84 * x[0] ** 2 * x[2]

def c1_vessel(x):
    return - (0.0193 * x[2] - x[0])

def c2_vessel(x):
    return - (0.00954 * x[2] - x[1])

def c3_vessel(x):
    return - (750 * 1728 - \
        np.pi * x[2] ** 2 * x[3]\
        - 4/3 * np.pi * x[2] ** 3)**

第一次尝试是使用 SLSQP 解决问题。注意,我提到了目标函数的梯度,我已经在示例笔记本中实现了。这里我决定省略这些定义,因为它们太长了。

**sol_cvx_vessel = minimize(
    f_vessel, [2, 2, 50, 50],
    jac=grad_vessel,
    bounds=bounds_vessel,
    constraints=cons_vessel,
    method="SLSQP"
)**

不幸的是,由于第三个约束的非线性性质, SLSQP 无法找到合适的解决方案。

**message: 'Positive directional derivative for linesearch'**

然而,差分进化算法并不是解决连续变量问题的唯一合适算法… 信赖域方法也是线搜索方法的替代方法,两者都使用在由某个规则定义的搜索方向上迭代地采取步骤的概念。并且两者都可以使用梯度信息来定义搜索方向。这可以通过仅仅改变方法参数来完成。这将导致一个可行点,具有很大的目标函数值。

**sol_tr_vessel = minimize(
    f_vessel, [2, 2, 50, 50],
    jac=grad_vessel,
    bounds=bounds_vessel,
    constraints=cons_vessel,
    method="trust-constr"
)**

现在,让我们实现 DE,看看它的性能如何。

**sol_de_vessel = differential_evolution(
    f_vessel, bounds=bounds_vessel,
    constraints=cons_vessel,
    popsize=50, strategy="rand1bin",
    recombination=0.7, mutation=(0.3, 1.0),
    maxiter=300, seed=12,
    init='latinhypercube', polish=False
)**

虽然找到了可行解,但是 DE(没有 finding)并不能产生低至trust-const的目标函数值,仍然取了更多的函数调用。

因此,在 DE 之前尝试其他算法仍然是有用的,特别是如果没有多模态或不可微项的证据。

添加离散变量

增加离散变量使得这个问题不可微。因此,在这种情况下,预期 DE 是必要的。使用基于树的模型来映射目标函数的情况在实践中并不罕见。这些也是不可微的问题,DE 为解决它们提供了一个有用的选择。

让我们修改我们的目标函数,它将连续的实变量 x1x2 解释为 0.0625 的离散倍数。

**def integer_x(x):
    x[0] = int(x[0] / 0.0625 + 1) * 0.0625
    x[1] = int(x[1] / 0.0625 + 1) * 0.0625
    return x

def f_vessel_minlp(x):
    x = integer_x(x)
    return f_vessel(x)**

以及实施…

**sol_de_minlp = differential_evolution(
    f_vessel_minlp, bounds=bounds_vessel,
    constraints=cons_vessel,
    popsize=50, strategy="rand1bin",
    recombination=0.7, mutation=(0.3, 1.0),
    maxiter=300, seed=12,
    init='latinhypercube', tol=1e-8, polish=False,
)**

这与 Lampinen & Storn (2004)报告的结果完全相同,因此可以认为是成功的。

**constr_violation: 0.0
fun: 7197.731584692868
Real x: [ 1.125       0.625      58.29011889 43.69286662]**

进一步阅读

差分进化是众多基于种群的算法中的一种,这种算法在解决优化问题时非常有效。我建议感兴趣的读者探索粒子群优化遗传算法作为两个强大的替代方案。

此外,在本文中,我们已经看到了差分进化算法在单目标问题中的一些应用。但是如果我们追求多个相互冲突的目标呢?DE 有几个扩展来处理多目标优化,在这些情况下非常有用。感兴趣的人会发现 python 库 pymoode 非常有用。在另一篇媒体文章中有一个概述:

* *

结论

在本文中,使用 scipy 实现,用实际的实现例子解释和说明了差分进化的基本理论方面。提出了凸的、多峰的和不可微的问题,其中 DE 与其他优化方法相比具有其性能。所使用的代码在这个 GIT 资源库中对读者完全可用。

参考

Bilal 等人,2020 年。二十多年研究的回顾。英国工程师。应用程序 Artif。智能。,第 90 卷,第 103479 页。

Lampinen,2002 年。差分进化算法的约束处理方法。2002 年进化计算大会会议录。CEC'02,第 2 卷,第 1468–1473 页。

兰皮宁,j .和斯托恩,r .,2004 年。差异进化。工程中的新优化技术。柏林,海德堡:施普林格出版社,第 123-166 页。**

普莱斯,K. V .,Storn,R. M .和 Lampinen,J. A .,2005 年。差分进化:一种实用的全局优化方法。第 1 版。柏林。

桑德格伦,1990 年。机械设计优化中的非线性整数和离散规划。《机械设计学报》,* 112(2),第 223–229 页。*

storn r .和 Price k .,1997 年。差分进化——连续空间全局优化的简单有效的启发式算法。 J。Optim。,第 11 卷第 4 期,第 359—341 页。

扩散模型

原文:https://towardsdatascience.com/diffusion-models-91b75430ec2

它们是什么,它们是如何工作的,为什么是现在?

来源:由稳定扩散生成…因为我当然必须这么做。

这篇文章旨在帮助你推导和理解扩散模型。如果你读完这篇文章后的第一个想法是,“为什么我没有想到这个?!?"那好吧,我成功了🎉。如果没有,好吧,还是谢谢你🤝希望你旅途愉快。

我们不会重建稳定扩散,但我们会在这一集结束时制作一些玩具模型来展示一切是如何工作的。除了这篇文章,我还创建了一个配套的 Github 库来收集所有与扩散相关的东西。在几个点上,我会参考回购的更多细节。截至 2022 年底,有两件事你会想看看:

  1. 在玩具数据集上实现扩散模型的简单代码(参见 DDPM 类和这个玩具脚本)。
  2. 全教程,含数学。填补了这个帖子所掩盖的任何空白,也有一些有趣的物理东西。如果你觉得这个帖子有意思,我推荐通读一下笔记!

强制性的非技术性介绍

我不想撒谎。出于尴尬,我开始写这篇文章。

感觉像是上辈子以前我曾在统计物理领域工作,这可以用一个想法来概括,“是的,一个粒子很酷,但你知道什么更酷吗?其中 10 个。”统计物理是一个广阔的领域,但毫不夸张地说,非平衡物理的研究在其中占有突出的地位。非平衡物理学是现代物理学中令人兴奋的领域之一,我们仍然没有真正理解,但仍然包含大量有意义的发现的机会。

然而,如果你要我准确描述什么是非平衡物理,我真的不能告诉你。如果你随便选一个物理问题,问我“这是非平衡的吗?”我可能会回答,"不,这是帕特里克……"然后,在尴尬的沉默中坐下来,决定是笑还是退缩。但是,有一种现象我可以说确实属于非平衡物理,因此应该属于我的“专业知识”领域:扩散。

如果你已经跟上了 2022 年的 ML 流行文化,那么“扩散”这个词应该会触发你的人工智能流行词警报⏰。当我第一次注意到人们开始随便折腾非平衡物理术语时,我感到胃里一阵剧痛。我的思绪淹没在漫漫长夜中,苦读着范·坎彭的 物理和化学中的随机过程 。但是,如果有一个足够强大的动机来克服数学引起的恶心,那就是尴尬。由于害怕有人会问我扩散模型是如何工作的,我开始尽快研究它们。这篇文章不是关于我试图自我证明我的教育或抱怨缓慢(lol ok 快)忘记我在研究生院学到的一切。不,这是关于分享一些我来之不易的知识,关于扩散模型如何在基本水平上运作。谢天谢地,原来这些东西都挺酷的!

什么和为什么

什么是扩散模型?

  1. 设计用于从分布 p(x)中有效抽取样本的模型。
  2. 生成模型。他们学习一些数据的概率分布 p(x)。
  3. 自然无监督(这与整个生成部分密切相关),尽管您可以对它们进行调节或学习有监督的目标。
  4. 不是真正的模特。扩散模型泛指调度程序、先验分布和转移核(通常由神经网络参数化)的集合。结合起来,这些片段可以从 p(x)生成样本。

为什么扩散模型很酷?

扩散模型并不是人们发明的第一个生成模型,问我们为什么特别关心这些模型是公平的。为了说明原因,让我们回顾一些历史。

你可能会很自然地想到,在深度学习时代,学习概率分布 P(x)是微不足道的:加载你最喜欢的神经网络,制作一个参数化的函数 E_𝝑(x,并通过最小化| p(x)-e_𝝑(x)|.来学习值𝝑然而,这是行不通的。原因是正常化。P(x)不是我们想要学习的任意函数。它必须服从∫ P(x) dx = 1 的约束,也就是说如果我们要学习一个无约束函数 E_𝝑(x),我们实际上需要优化|P(x) — E_𝝑(x) / ∫ E_𝝑(x) dx|,这个现在因为积分完全拧了。归一化常数 Z(𝝑) = ∫ E_𝝑(x) dx 也称为配分函数,通常用大写字母 z 表示。遵循学习无约束函数 E_𝝑(x 方法的模型称为基于能量的模型 (EBMs)。

值得注意的是,有几种方法可以克服归一化常数问题。冒着错过大量研究的风险,我建议读者参考 2015 年论文使用非平衡动力学的深度无监督学习的第 1.2 节,以获得更好的概述。我将简单记下一些比较流行的方法:

  1. 通过用随机样本近似积分的通常方法,在训练期间估计归一化常数 Z(𝝑。抽取一个好的随机样本是这里最难的部分。这被称为对比发散训练。
  2. 了解一些简单的、固有的归一化函数 q_𝝑(x 的参数,其中近似于 P(x)。这就是变分法。
  3. 仔细构建一组可逆的、可训练的变换,这些变换采用简单的标准化概率分布(像正态分布)并将其转化为更复杂的东西。这是规范化流程的方法。
  4. 求解导数𝛁_x P(x)而不是 P(x)。由于𝛁_xz(𝝑= 0,这就完全消除了归一化常数。这很好,但是还不清楚如何利用这一点。这种方法被称为分数模型,事实证明,对于某些培训目标,它可以被证明为等同于扩散模型(参见倒数第二节注释基于分数的模型和扩散模型的等效性)
  5. 将自己从标准化的暴政中解放出来,只需学习一个直接生成样本的函数。让你不稳定的模型对着你的耳朵说一些关于博弈论的甜言蜜语,就像你慢慢忘记学习 P(x)的必要性一样。这是甘斯的做法。

扩散模型很有趣,因为它们把自己作为条目(6)添加到上面的列表中。它们提供了处理规范化问题的不同方式。

基本的见解

这个想法

您可以将扩散模型方法视为我们之前列出的避免归一化常数的方法中的方法(3)和(4)的混合。扩散模型源自一个简单的想法:

与其直接尝试对分布 P(x)进行建模,不如我们可以找到一些操作,采用一个蹩脚的答案 P_crap(y),并将其转换为稍微好一点的答案 P_better(x)怎么样?

为什么这会有帮助?假设我们发现了这样一个操作。如果这个操作可以采用任何旧的猜测 P_crap(x)并使其更好,即使是无穷小的,那么理论上我们可以一遍又一遍地重复这个操作,直到我们达到正确的分布 p(x)!现在让我们把第(6)项加入我们的清单

6.求解一个取归一化分布 q_t(x)的转换核,把它变成稍微好一点的归一化分布 q_{t+1}(x)。这暗示着 p(x) = q_{∞}(x)。这就像一个连续的正常化流程。

如果你想要一个有趣的,尽管不完全类似的,正在发生的事情的视觉表现,有一种叫做镍钛诺的令人着迷的材料,它能够记住自己的形状。这里有一个用这种材料制成的回形针的视频。成型的回形针可以认为是我们要学习的分布 p(x)。向前(扩散)的过程将相当于拉直回形针,使其形成一个漂亮而简单的均匀分布。逆向(生殖)过程是将它倒入热水中,看着它卷曲回原来的形状。

抛开对阿松的生财怜款

过渡核以一个 D 维 x_t 作为输入,返回另一个 D 维向量 x_{t+1}作为输出,意味着核产生一个 向量场这里有一个展示向量场的演示。确保关闭等电位曲线。

观察到我们正在学习一个向量场是很有帮助的。它允许我们问这样一个问题,“如果我们什么都没学到,只是直接计算出如果训练数据集中的每个点都被认为是一个微小的质量时会产生的引力场,会怎么样?”你可以的。您刚刚创建的是一个函数,它取空间中的任意点,并将其映射到训练数据中的现有点。这本身并不坏,但我们真正想要的是插值,即映射到不在训练数据集中的点的能力。我可以想到两种方法来实现这一点。第一种方法是在这个映射过程中添加一些随机噪声。这可以做得很严谨,并且是扩散理论的基础。这就是我们在这篇文章中探索的方法。

第二种方法,是仔细构建先验分布和向量场,这样就存在一个确定性的方程来映射 p(x)的先验。这是泊松生成流模型背后的中心思想。还有更多的空白要填,但这就是它的主旨。它们简单、强大,相对于扩散模型有很多好处。我将在以后的文章中讨论这些问题。

方程式

在这一小节中,我们将把我们的想法(使用一些运算将垃圾转化为更少的垃圾)转化为数学。从贝叶斯方程中,我们可以知道我们可以引入一个辅助变量 y,这样 p(x) = ∫ p(x,y) dy = ∫ p(x | y) p(y) dy。现在,让我们将时间离散为步长 t ϵ ℕ,并将 p(x)定义为某个最终时间 t 的 p(x_t)。使用贝叶斯方程,我们可以写出对任何分布 p(x)都有效的一般马尔可夫序列:

我们将调用函数 p(x _ t | x _ { t-1 };𝜗)的跃迁内核。我们已经通过一些参数𝜗将其参数化,这正是我们要训练我们的模型学习的。我们还可以引入一个连续的版本,p _∏t(x | y ),它取决于一个小的时间步长 t。直观地说,转移核 p _∏t(x | y)表示,在时间 t+t 的一个点 x 的概率密度 p(x)等于在时间 t 的所有其他点 y 的概率密度之和,乘以从 y 跳到 x 的转移概率 p(x | y)。换句话说,到达某个地方的唯一方法是要么从那里开始,要么从某个地方开始将所有这些方法相加,根据旅行的可能性进行加权,你就得到了新的概率。

在这一点上,你可能会尝试采用形式极限 t -> 0,并创建一个∂p/∂t 项。这样做不会对我们有太大帮助,但我可以告诉你这条路上会有什么。你实际上是试图重新推导出 主方程 (链接指向 Kramers-Moyal 展开式,因为它有我想要的主方程版本)。这个方程增加了一个我在直觉中没有提到的部分,即建立一个一般的积分微分方程,你还必须考虑在 t=0 时处于正确位置的一些概率在 t =∏t 时移动到错误位置的可能性,无论如何,这里不需要主方程的完整机制。

我们现在有了给出数据点 x_t 的对数似然的基本方程,并且我们有一些想要学习的参数。为了通过梯度下降来学习这些参数,我们需要找到一个损失函数来最小化。不过这并不是最难的部分。困难的部分是处理所有的积分和变量 x_0,…,x_t,我们刚刚介绍过的。

物理学视角

这一节对于继续推导扩散模型算法是不必要的,但是它将从不同的角度使事情变得更清楚。

首先,我要写下三个方程,并声称这三个方程描述了同一个物理过程:受某个势 V(x)支配的粒子系综的演化。这些方程是朗之万方程福克-普朗克方程和翁萨格-马赫卢普泛函。

对于翁萨格-马赫卢普泛函,我能找到的最好的资源是 646 页的奥尔特兰和西蒙斯凝聚态物理。注意,在任何固定时间,η(t)都是从正态分布中得出的!

让我们从中线开始,福克-普朗克(FP)方程。它的定态解出现在∂p/∂t = 0 时,解为 p ~ e^{-V / D}。重新排列,我们发现对于选择 V(x) = -ln q(x),定态解将是 q(x)。换句话说,我们可以写出一个 FP 方程,它在很长时间内会给出我们想要的精确分布。

如果将我们巧妙选择的 V(x)代入朗之万方程(第一行),然后引入小时间ϵ并离散化,我们可以找到朗之万马尔可夫链蒙特卡罗(MCMC)的定义方程

只要我们知道我们想要取样的分布的梯度,我们就可以用上面的方程模拟一堆粒子 x 的随机动力学,从而抽取样本。另外,注意我们只需要 q(x)的梯度。听起来熟悉吗?这正是我们在第(4)节“为什么扩散模型很酷”中定义的分数。这意味着我们不需要一个归一化常数来从 q(x)中采样🎉。

我们现在知道如何定义一个分布,并在已知分数的情况下从中抽取样本。最后,我们可以使用路径积分表示来定义帮助我们学习分数的训练目标。注意如果我们离散化这个动作会发生什么(指数中的东西);我们得到类似 e^{-(x_{t+1} - x_t -ϵ F_t) / 4ϵ}的东西,其中 F_t = -𝛁 V(x_t)。这只是一个正态分布,它的形式和我们之前的转移核公式一样。我们发现,有一个非常令人信服的理由为什么我们应该选择高斯核,除了它是物理学中唯一一个知道如何处理的函数🤷。‍:如果我们也离散化这个测度,我们会发现一个无限的转移核乘积,它再现了我们之前的马尔可夫序列方程。

先不说这个总结:

  1. Fokker-Planck 方程告诉我们,存在一个稳定的分布,具有我们想要的值 p(x ),和由分数函数给出的力。
  2. 朗之万方程可以从 p(x)中抽取样本。
  3. 离散化路径积分再现了马尔可夫序列,该序列可用于制作优化我们的模型的损失函数。

抛开朗之万不谈

请随意跳过这一步。仅供参考:有更好的方法来采样高维函数,许多聪明人已经花了很多时间来思考它们。在这里,我只想证明朗之万·麦克公司的这种方法确实有效。这里有一些代码将使用 Pytorch 来计算我制作的随机函数的对数梯度,然后用 20k 个样本运行 Langevin MCMC 1000 步

import torch
import numpy as np
import matplotlib.pyplot as plt
from scipy.integrate import quad

# pick any random distribution to sample from
numpy_fn = lambda x: (0.2 + np.cos(2 * x) ** 2) * np.exp(- 0.1 * x ** 2)
norm = quad(numpy_fn, -np.inf, np.inf)[0]
torch_fn = lambda x: ((0.2 + torch.cos(2 * x) ** 2) * torch.exp(- 0.1 * x ** 2) ) / norm
# Run Langevin MCMC
samples, steps, eps = 20000, 1000, 5e-2
x = (2 * torch.rand(samples, requires_grad=True) - 1) * 10
for _ in range(steps):
    potential = torch.autograd.grad(torch.log(torch_fn(x)).sum(), [x])[0]
    noise = torch.randn(samples)
    x = x + eps * potential + np.sqrt(2 * eps) * noise

# Plot against the true distribution
y = torch.linspace(-2 * np.pi, 2 * np.pi, 100)
plt.plot(y, torch_fn(y), 'k--')
plt.hist(x.detach().numpy(), density=True, bins=300)
plt.xlim(-2 * np.pi, 2 * np.pi)

如果您运行代码,您应该会得到类似这样的结果:

我不想撒谎,这个例子是精心挑选的。使用马尔可夫链蒙特卡罗(MCMC)方法进行适当的采样本身就是一项精细的技能,但是希望你可以放心,这至少在理论上是可行的!

训练扩散模型

导出损失函数

当 t 趋于无穷大时,我们希望生成分布 p(x_t)与观测分布 p_data(x)相匹配。然而,由于无穷很难处理,让我们做一些奇怪但符合文献的事情。假设在时间 t=0,我们有期望的分布 p(x,t=0) = p_data(x)。然后,不失一般性地,我们假设在未来的某个时间 t=T,我们已经充分破坏(扩散)了我们的分布,使得 p(x,t=0) = N(x |0,1)。最后,我们需要一个损耗来推动观察到的和参数化的分布彼此更接近。我们可以通过最小化它们的KL-散度来做到这一点,这给了我们看似简单的目标L =-∫p _ data(x _ 0)LNP(x _ 0;𝜗) dx_0。

通常,p_data 是不可积的(我们不知道它,但我们有样本,即数据集)。这可以通过通常的方法来克服,用随机小批量观察数据集的求和来代替积分(积分<=>求和交换)。对数项是困难的一项。使用完全展开的马尔可夫形式,以及随之而来的所有烦人的积分,我们可以这样写:

这里我们用的是简写 dx_1:T = dx_1…dx_T

从这里开始,我们需要去掉积分并简化,直到我们有了可以编码的东西。这样做是一项艰巨的任务,所以我将概述您需要的主要见解。完整的推导过程可在随附注释中找到。

用重要性抽样去除积分

首先我们需要处理那些讨厌的积分。为此,我们将使用一个古老的技巧:重要性抽样。如果你有一些积分,比如∫ f(x) dx,给你带来麻烦,你可以把它近似为其他分布 q(x)的期望值 E_{x ~ q(x)}[f(x)/q(x)]。如果你想听起来很新奇,你可以说你从退火重要性抽样和 Jarzynski 等式中获得了这个想法的灵感。我们称分布为 q(x_0,…,x_T)。任何分布都可以,但问题是,我们选什么?答案是我们能找到的最便宜的。

幽默旁白:有一次我去海外旅行,觉得从当地的一家酒店带几瓶葡萄酒回家会很不错。那时我还是一个穷学生,不用说,我正经历着轻微的贴纸休克。我不知所措,轻轻地走到店主面前,用平静的声音礼貌地问最便宜的瓶子是什么。接下来,我所知道的是,这位老店主睁大了眼睛,用手掌拍着桌子,大声斥责道:“我们不卖便宜的酒!”我想指出的是,作为一名学生不仅仅意味着你破产了,还意味着你很笨拙。因此,我对这位大声嚷嚷的店主的反应是眯着眼睛,好像我在重演 Futurama Fry meme ,并回答说,“好吧……但根据定义,至少像这些葡萄酒中的一种是最便宜的,对吗?”店主感到不安,开始从桌子上抬起她的手,把她的指关节转向我,用拇指捏她的手指。然后她俯下身子,用平静得多的声音向我解释道:“最便宜的。不便宜。我可以卖给你最便宜的酒。直到今天,我都确保从来没有要求过便宜的东西。我总是要求最便宜的🤌.

这就是扩散模型的扩散部分发挥作用的地方。一个基本的扩散过程几乎是我们能为 q(x_0,…,x_T)构造的最简单的马尔可夫分布,它允许一个解析表达式。现在让我们为我们的两个发行版命名:

正向(破坏性/扩散性)过程 : q(x_0,…,x_T)。这就是我们将分析解决的用于重要性抽样的扩散。

逆向(生成式)过程: p(x_0,…,x_T)。这是包含我们的可学习参数和转换内核的东西,将用于生成样本。

现在的损失是:

使用证据下限(ELBO)

技巧#2 是另一个熟悉的技巧(在这种情况下近似)。请使用詹森不等式来优化 ELBO。这意味着你可以将对数移过积分和 q(x_1:T | x_0):

做一些删除,进一步简化,最终你可以用 KL 散度来表示

情商。 DDPM 纸业的 a . 21。没有 KL 分歧。

KL 的分歧。

这是将军 组建

把正向过程变成扩散过程

到目前为止,除了它们是马尔可夫过程之外,我们还没有说任何关于前向或后向过程的东西。我们现在为了易处理而牺牲一般性,把正向过程变成扩散过程。我们将遵循去噪扩散概率模型 (DDPM)论文中的方法,并做出如下选择(诀窍#3!)对于正向转换内核

注意,这个选择是特定于 DDPM 扩散的。它不是所有扩散模型的通用方程。

其中⍺_t's 是固定的、依赖于时间的值。我们可以看到,通过方差βt = 1 —⍺_t.,它们很容易与不同时间的噪声方差相关联

以后选择更大的方差

我们对扩散核的选择看起来完全是随机的,但它实际上是非常聪明的。请注意,扩散过程是由其方差定义的,这里我们决定方差应该是时间相关的。之前的调度会奇迹般的让 q(x_t | x_0)以封闭形式解析(注释中显示的),并且保证方差不会随时间爆炸。

我还想说一句重要的话。随着样本离 p_data(x)越来越远,选择逐渐变大的方差对于扩散模型的性能至关重要。直觉上,你可以这样想。

假设你远离分布的中心。对你来说,它可能看起来像天空中的星星——所有的形状、凸起和特征都模糊成远处某处的一个焦点。在这个阶段,进行大幅度跳跃是有意义的,直到你足够接近以更好地分辨距离,然后才减小跳跃的幅度。否则,你将花费大量时间来优化随机的东西,这些东西与空间旅行的正确方向毫无关系。

将反向(生成)核定义为高斯核

信不信由你,到目前为止,生成过程仍然是通用的。如果你把物理学放在一边看,下一步的动机应该是清楚的。如果没有,就把它当成一个容易的选择。我们将通过具有可学习的均值和固定方差的正态分布来参数化后向核:p(x_t | x_{t-1}) = N(μ(x,t;𝜗),β(t))。这将把大量的积分转换成两个高斯函数之间的 KL-散度,一般来说,使数学变得更简单。最终,你会发现总损失是每个可能的时间步长损失的总和,每个损失都有相同的形式。结果是:

根据定义的方差,我们定义β_t = 1 — ⍺_t.。我们还将⍺_t 棒线定义为截至时间 t: ∏_0^t ⍺_t.的所有阿尔法的乘积

其中,L_{t-1}是第(t-1)个时间步长的损耗,最后一步是仅用噪声ϵ来重写,因此我们得到:

第一个等式是ϵ_𝜗的定义,第二个等式是 x_t 的定义,第三个等式是期望损耗

μ项只在采样时才需要记住。

最终结果,一个去噪目标

损失表达式非常简单,也非常直观。它说,在任何给定的时间步长,都有一些高斯噪声应用于输入,我们的任务是预测它(因此去噪)。这表明了下面的简化,这在实践中证明是更有效的:降低损失函数中的系数。我们终于达到了扩散算法。在这里,我将从原始论文中复制/粘贴算法,而不是重新键入它

资料来源:Ho、Jonathan、Ajay Jain 和 Pieter Abbeel。"去噪扩散概率模型."神经信息处理系统进展33(2020):6840–6851。

σ_t 只是标准差,由√(1-⍺_t 给出的)(老实说,我不知道他们为什么不能写出来🤷‍♂️).

抽样

我忽略了这一点,但是由于我们有正态分布,采样很容易得到。上面的推导来自于再参数化技巧,即可以通过首先采样 z ~ N(0,1),然后计算 x = + σ z,从 x_T 开始采样,然后反向采样,我们看到 x_{T-1} ~ p(x_{T-1} | x_T)容易采样,这意味着 x_{T-2} ~ p(x_{T-2} | x_{T-1})容易采样等等。这也被命名为祖先采样,它也包含更复杂的图条件依赖。

为了完整起见,如果您查看算法 2 中的步骤(4 ),请注意它具有 Langevin 方程的形式,正如我们之前讨论的那样。

解决纷争

不良投入的问题

当我第一次读到最终的 DDPM 算法时,老实说,我对它的工作感到非常震惊。为什么我们的模型能够从撤销一些应用于它的随机高斯噪声中得到任何信息?当我们的数据点 x 远离分布的中心时,这将如何工作呢?当我们实际上只得到一张时间快照时,我们怎么能学会动态变化呢?

对于第一个问题,答案是如果看情商。(5)在训练算法中,我们实际上是将噪声输入到模型中。这里的关键点是,我们也在时间中进食。这些额外的信息似乎会产生影响。我的猜测是,它允许模型推断它有多接近真实分布,并使用它来更好地过滤噪声。

对于第三个问题,我认为答案是组装。如果平衡分布已知,写下福克-普朗克方程就很简单了。不管怎样,这就是它们最初的设计目的。一般来说,我们可以通过(a)长时间模拟一些粒子或者(b)短时间模拟大量粒子来确定平衡。随着你获得越来越多的数据,我认为你越来越接近选项(b ),这就是为什么这是可行的。

对于第二个问题,答案是不会。lol。我不想伤害研究过这个的 OG 作者,所以我尊重他自己的关于这个的博客。在标题为“天真的基于分数的生成模型及其缺陷”的章节中,你会找到要点。简而言之,低密度区域完全陷入了损失函数。这是学习左下角和右上角的两个高斯流的视觉效果:

来源:https://yang-song.net/blog/2021/score/

我认为这个问题在泊松流模型中得到了一定程度的解决,因为这些模型偏向于复制单极场。但是,我没有证据证明这一点。

高差异

最糟糕的感觉是看着你的扩散模型慢慢地越来越接近一个相当好的图像,只是在最后一刻大喊 skrt skrrrrtttt,然后转向一个看起来像 90 年代的电视调到 100 频道。在步长和方差之间有一个折衷。更小的方差+更多的步数总是随机微分方程的更精确的表示,但是它也更加计算密集。这里一定要注意。

编码

让这些东西在实践中工作实际上有点烦人,主要是由于上一节中关于低密度区域的问题(2)。您需要确保在能够通过扩散从真实目标分布到达的区域中对先验分布进行采样。此外,您需要调整扩散步骤的数量和变化时间表。其他一些论文(参见改进的去噪扩散概率模型)着手优化这些东西,发现用一些简单的时间 1D 函数代替我们推导的花哨的离散方差时间表可以做得更好。

至于编码,实际上非常简单。你需要:

  1. 将向量 x 和时间 t 作为输入,并返回与 x 维数相同的另一个向量 y 的模型。具体来说,该函数类似于 y = model(x,t)。根据您的变化计划,对时间 t 的依赖可以是离散的(类似于变压器中的令牌输入)或连续的。如果是离散的,你可以使用老式的变换位置编码(不要脸的自插),如果是连续的,你可以使用高斯随机特征。
  2. 一个 Mixin,处理特定模型的所有调度、采样和去噪损失计算。

为了说明这是如何工作的,我们将尝试使用扩散模型来学习一个简单的 2D 螺旋模式。这是最初的 2015 论文中使用的玩具数据集,所以我们将使用 DDPM 重做它。

作为调度器的例子,你可以在这里查看 DDPM 类。为方便起见,我复制/粘贴以下内容:

import torch
from torch import nn

class DDPM(nn.Module):
    """Dataclass to maintain the noise schedule in the DDPM procedure of discrete noise steps
    Mathematically, the transition kernel at time $t$ is defined by:
    $$
    q(x_t|x_{t-1}) = \mathcal{N}(x_t| \sqrt{\alpha_t} x_{t-1}, 1 - \alpha_t)
    $$
    We further define quantities $\beta$ and $\bar \alpha$ in terms $\alpha$:
    $$
    \beta_t \equiv 1 - \alpha_t
    $$
    $$
    \bar \alpha_t = \prod_{t' < t}\alpha_{t'}
    $$
    which will be useful later on when computing transitions between non adjacent times.
    """

    def __init__(self, n_steps: int, minval: float = 1e-5, maxval: float = 5e-3):
        super().__init__()
        assert 0 < minval < maxval <= 1
        assert n_steps > 0
        self.n_steps = n_steps
        self.minval = minval
        self.maxval = maxval
        self.register_buffer("beta", torch.linspace(minval, maxval, n_steps))
        self.register_buffer("alpha", 1 - self.beta)
        self.register_buffer("alpha_bar", self.alpha.cumprod(0))

    def diffusion_loss(self, model: nn.Module, inp: torch.Tensor) -> torch.Tensor:
        device = inp.device
        batch_size = inp.shape[0]

        # create the noise perturbation
        eps = torch.randn_like(inp, device=device)

        # convert discrete time into a positional encoding embedding
        t = torch.randint(0, self.n_steps, (batch_size,), device=device)

        # compute the closed form sample x_noisy after t time steps
        a_t = self.alpha_bar[t][:, None]
        x_noisy = torch.sqrt(a_t) * inp + torch.sqrt(1 - a_t) * eps

        # predict the noise added given time t
        eps_pred = model(x_noisy, t)

        # Gaussian posterior, i.e. learn the Gaussian kernel.
        return nn.MSELoss()(eps_pred, eps)

    def sample(self, model: nn.Module, n_samples: int = 128):
        with torch.no_grad():
            device = next(model.parameters()).device

            # start off with an intial random ensemble of particles
            x = torch.randn(n_samples, 2, device=device)

            # the number of steps is fixed before beginning training. unfortunately.
            for t in reversed(range(self.n_steps)):
                # apply the same variance to all particles in the ensemble equally.
                a = self.alpha[t].repeat(n_samples)[:, None]
                abar = self.alpha_bar[t].repeat(n_samples)[:, None]

                # deterministic trajectory. eps_theta is similar to the Force on the particle
                eps_theta = model(x, torch.tensor([t] * n_samples, dtype=torch.long))
                x_mean = (x - eps_theta * (1 - a) / torch.sqrt(1 - abar)) / torch.sqrt(
                    a
                )
                sigma_t = torch.sqrt(1 - self.alpha[t])

                # sample a different realization of noise for each particle and propagate
                z = torch.randn_like(x)
                x = x_mean + sigma_t * z

            return x_mean  # clever way to skip the last noise addition

对于一些样本模型在离散和连续时间的情况下,你可以在这里查看代码。

训练过程与普通训练过程非常相似,除了在您的批处理中没有目标,您必须使用 DDPM 调度程序中的 diffusion_loss 方法来计算损失。你可以在 DDPM 包的 main.py 脚本中找到训练循环。

如果训练成功,您会发现生成的分布如下所示:

从随机的高斯斑点到整齐有序的螺旋。

包装东西

在过去的几年里,扩散领域的事情发展得非常快,这是有充分理由的。至少可以说,最近(2022 年)的一些模型,如 Dalle-2、StableDiffusion 和 Midjourney 的结果令人惊讶。结合当前对生殖人工智能的狂热,你有新的快速发展。因此,除了这篇文章之外,还有很多令人兴奋的东西可以探索。

首先,我没有提到任何流行的文本到图像的模型。这些都是大型的、复杂的、多模态的架构,许多 GPU 超出了本文的范围。进一步研究的其他途径可能包括实际训练扩散模型的更好方法。我只是简单地提到了一些关于优化这些的研究,但是当然你可以做得更深入。另一个有趣的途径是观察条件模型。我提到的一些论文也涉及到这一点,但我不想说得太远。已经有很多有趣的工作来引导这些扩散模型走向特定的结果,无论是阶级条件作用还是其他。其他领域的研究着眼于新颖性和保真度之间的权衡,以及如何以其他方式调整输出,如通过负采样。

对我来说,在开始写这篇博客之前,我想回答的最大问题是,“这些东西实际上是如何工作的,为什么?”我希望这篇文章至少给出了一些部分令人满意的答案😄!

扩散模型变得简单

原文:https://towardsdatascience.com/diffusion-models-made-easy-8414298ce4da

了解去噪扩散概率模型的基础知识

图 1:去噪扩散概率模型的过程(图片由作者提供)

1简介

在最近的过去,我谈到了 GANs 和 VAEs 作为两个重要的生成模型,已经取得了很大的成功和认可。gan 非常适合多种应用,但是它们很难训练,并且由于一些挑战,例如模式崩溃和消失梯度,它们的输出缺乏多样性。虽然 VAEs 具有最坚实的理论基础,但是,在 VAEs 中,良好的损失函数的建模是一个挑战,这使得它们的输出是次优的。

还有另一套技术源自概率似然估计方法,并从物理现象中获得灵感;这就是所谓的扩散模型。扩散模型背后的中心思想来自气体分子的热力学,由此分子从高密度向低密度区域扩散。这种运动在物理学文献中经常被称为熵增加或热寂。在信息论中,这等同于由于噪声的逐渐介入而导致的信息丢失。

扩散建模中的关键概念是,如果我们可以建立一个学习模型,该模型可以学习由于噪声导致的信息的系统衰减,那么应该可以逆转该过程,从而从噪声中恢复信息。这个概念类似于 VAEs,它试图通过首先将数据投影到潜在空间,然后将其恢复到初始状态来优化目标函数。然而,该系统的目的不是学习数据分布,而是在马尔可夫链中模拟一系列噪声分布,并通过以分层方式撤销/去噪数据来“解码”数据。

2。 去噪扩散模型

去噪扩散模型的思想由来已久。它源于扩散图概念,这是机器学习文献中使用的降维技术之一。它还借用了概率方法中的概念,如已经在许多应用中使用的马尔可夫链。最初的去噪扩散方法是在 Sohl-Dickstein 等人中提出的。1.

去噪扩散建模是两步过程:正向扩散过程和反向过程或重建。在前向扩散过程中,连续引入高斯噪声,直到数据变成全噪声。反向/重建过程通过使用神经网络模型学习条件概率密度来消除噪声。这种过程的示例描述可以在图 1 中看到。

3。 前进过程

我们可以将前向扩散过程正式定义为马尔可夫链,因此,与 VAEs 中的编码器不同,它不需要训练。从初始数据点开始,我们为 T 连续步骤添加高斯噪声,并获得一组噪声样本。时间 t 的概率密度预测仅依赖于时间 t-1 的直接前趋,因此条件概率密度可以计算如下:

整个过程的完整分布可以计算如下:

这里,密度函数的均值和方差取决于参数βτ,该参数是一个超参数,其值可以在整个过程中取为常数,也可以在连续步骤中逐渐改变。对于微分参数值分配,可能存在可用于模拟行为的函数范围(例如,sigmoid、tanh、linear 等。).

上面的推导足以预测连续状态,然而,如果我们想要在任何给定的时间间隔 t 采样,而不经过所有中间步骤,因此,允许有效的实现,那么我们可以通过将超参数替换为ατ = 1 — βτ 来重新制定上面的等式。上面的重新表述变成了:

为了在时间步长 t 产生样本,并且在时间步长t-1 可用概率密度估计,我们可以采用热力学中的另一个概念,称为,朗之万动力学。根据随机梯度朗之万动力学【2】我们可以仅通过马尔可夫链更新中密度函数的梯度来采样系统的新状态。基于前一时间点 t-1 的步长 ε 的新数据点在时间 t 的采样可以计算如下:

4。重建重建**

相反的过程要求在给定系统当前状态的情况下,在较早的时间步估计概率密度。这意味着当T =T时,估计 q (χτ-1 | χτ),从而从各向同性高斯噪声中生成数据样本。然而,与正向过程不同,从当前状态对先前状态的估计需要所有先前梯度的知识,在没有能够预测这种估计的学习模型的情况下,我们无法获得这些知识。因此,我们必须训练一个神经网络模型,该神经网络模型基于学习到的权重 θ 和在时间tt的当前状态来估计ψθ(χτ-1 |χτ)。这可以估计如下:**

**

平均函数的参数化由 Ho提出。等* 3并可计算如下:*

作者在何。et al.* 3建议使用固定方差函数,如σθ=βτ。在时间 t-1 的样本可以计算如下:*

5。 训练和结果

5.1。 模型的构建

用于扩散模型的训练中的模型遵循与 VAE 网络相似的模式,然而,与其他网络架构相比,它通常保持更简单和直接。输入层的输入大小与数据维度的输入大小相同。根据网络要求的深度,可以有多个隐藏层。中间层是具有各自激活功能的线性层。最终层的大小再次与原始输入层的大小相同,从而重建原始数据。在去噪扩散网络中,最终层由两个独立的输出组成,每个输出分别用于预测概率密度的均值和方差。

5.2。 损失函数的计算

网络模型的目标是优化以下损失函数:

Sohl-Dickstein 等人【1】提出了这种损失函数的简化形式,它用两个高斯分布和一组熵之间的 KL 散度的线性组合来表示损失。这简化了计算,并且使得实现损失函数变得容易。损失函数于是变成:

**Ho 等人【3】在损失函数中提出了进一步的简化和改进,其中均值的参数化如前一节所述用于正向过程。因此,损失函数变成:

5.3。结果结果**

通过遵循马尔可夫链添加高斯噪声的正向过程的结果可以在下图中看到。时间步长的总数是 100,而该图显示了来自生成的序列集的 10 个样本。

图 2:S 曲线合成数据集前向扩散过程的结果(图片由作者提供)

下图显示了反向扩散过程的结果。最终输出的质量取决于超参数的调整和训练时期的数量。

图 3:从各向同性高斯噪声中重建数据的结果(图片由作者提供)

图 4:去噪扩散模型在 3 个不同数据集上的结果:瑞士卷、月亮和 S 曲线(图片由作者提供)

6。 结论

在这篇文章中,我们讨论了扩散模型的基础,以及它们的实现。尽管扩散模型在计算上比其他深度网络架构更昂贵,但是它们在某些应用中表现得更好。例如,最近在文本和图像合成任务中的应用,扩散模型的表现优于其他架构4。更多的实现细节和代码可以在下面的 github 资源库中找到:https://github . com/Azad-academy/noking-diffusion-model . git

订阅并关注更多更新:azad-wolf.medium.com/

参考文献

1 Sohl-Dickstein,j .、Weiss,E. A .、Maheswaranathan,n .&Ganguli,S. (2015)。使用非平衡热力学的深度无监督学习。arXiv 预印本 arXiv:1503.03585。

[2]马克斯·韦林和伊惠德。“通过随机梯度朗之万动力学进行贝叶斯学习。” ICML 2011。

3 何,j,贾恩,a .,&阿贝耳,P. (2020)。去噪扩散概率模型。预印本 arXiv:2006.11239。

4 普拉富拉·达瑞瓦尔亚历克斯·尼科尔扩散模型在图像合成上击败甘斯,arXiv: 2105.05233

DiFluid 与 Atago 在浓缩咖啡中的总溶解固体量(TDS)

原文:https://towardsdatascience.com/difluid-vs-atago-for-total-dissolved-solids-tds-in-espresso-d474614ad66f

咖啡数据科学

合格折光仪

在过去几年中,数字折光率仪市场有所增长。起初,只有 VST。当 Atago 上市时,一些实验结果表明,两者产生了相似的结果,在统计上没有差异。现在,一种更便宜、更小的 DiFluid 折光仪已经上市,所以让我们收集数据并看一看。

咖啡中的折射计用于测量折射率,折射率与总溶解固体(TDS)相关联,这可用于计算提取的咖啡量。折光率仪测量折光率,有了数据,公司就有了用折光率和温度来测量 TDS 的方程式。否则,人们将不得不干燥用过的咖啡坯来确定提取了多少咖啡。

在充分披露,DiFluid 寄给我这个折射计,他们没有与我有任何沟通,因为。他们认为我在做独立分析,我们没有业务关系。我的评论关注的是使用仪表读取浓缩咖啡的读数,对用户体验几乎没有评论。该仪器读取样品的速度比 Atago 更快,这有时是有利的,但它要求您冷却样品。

我只与 Atago 进行了比较,之前我已经对其进行了多次描述:

测量咖啡折光仪的准确度

内外咖啡折光仪测量

咖啡和浓缩咖啡的折光仪精确度

浓缩咖啡的平价咖啡溶解度工具(TDS):Brix vs Atago

咖啡中总溶解固体的测量会受到样品温度 的轻微影响

用于 TDS 咖啡测量的过滤注射器似乎没有必要

所有图片由作者提供

在几个星期的时间里,我将这个仪表与我的 Atago 一起使用,以获取读数。我在每个容器中放了一个样本,直到 Atago 记录了一个读数,我才从液体中得到一个读数。这使得样品以与在 Atago 中相同的方式冷却。Atago 通常需要一分钟左右。

我之前曾发现在冷却样品时,TDS 会有轻微的统计偏移,但我继续使用热样品来保持我的测量技术的一致性。我也没有过滤样本,因为我发现没有必要

数据

先说热样。我取了 45 个样本,在这 45 个样本中,有 25 个样本在我用移液器的顶部在冷水(20C)中冷却相同的样本后有了额外的样本。

随着 Atago 的 TDS 增加,二液 TDS 以更高的速度增加。

将这些样本与冷样本进行比较,有一个不同的趋势。

观察冷却样品和热样品,两种仪表都遵循相似的趋势,但是 DiFluid 的偏移更大,这意味着在测量 TDS 时,它比 Atago 更受温度的影响。

当比较冷却的样品时,DiFluid 和 Atago 的性能几乎相同。在双尾 t 检验中,p 值为 0.46,这意味着它们分布的差异没有统计学意义(p>0.05)。

DiFluid 还显示折光率(nD ),因此我绘制了 nD,以显示它如何随着热样品和冷样品而变化。趋势非常线性,这表明两种设备的 TDS 测量差异是由于 DiFluid 使用的公式,而不是硬件的内在差异。

一段时间内的单一样本

我观察了一段时间内钻井液的单个样本。一旦温度稳定下来,折射率也随之变化。

总的来说,我真的很喜欢 DiFluid,对于浓缩咖啡来说,它是一个很好的伴侣。对于那些不想倾家荡产的人来说,我认为这是一个不错的选择。应该为低 TDS 咖啡提供更多的数据,但总体上似乎缺乏折光仪的数据。我希望更多的数据将被分享,这样作为一个社区,我们可以更好地了解折光仪如何对咖啡起作用。

如果你愿意,可以在推特、 YouTubeInstagram 上关注我,我会在那里发布不同机器上的浓缩咖啡照片和浓缩咖啡相关的视频。你也可以在 LinkedIn 上找到我。也可以关注我在订阅

我的进一步阅读:

我未来的书

我的链接

浓缩咖啡系列文章

工作和学校故事集

用曲目嵌入挖掘我的 Spotify 流媒体年

原文:https://towardsdatascience.com/digging-into-my-year-of-spotify-streaming-with-track-embeddings-6cad51523b0e

受“Spotify Wrapped”的启发,我用嵌入、聚类和情绪分类来审视我这一年的音乐流

作者图片

每年年底,Spotify 都会发布一份“Spotify Wrapped ”,总结用户过去一年的流媒体内容。受到这个总结的启发,我想对我这一年的音乐流媒体进行一次高层次的挖掘。

这篇文章首先有区别地描述了我的听力历史。接下来是基于曲目和艺术家的分布式矢量表示(嵌入)的更“语义”的分析。然后,我挖掘我听过的歌曲中歌词的情绪,并计算一年中情绪的分布。最后,我将一年的流媒体放入一个播放列表,其中包含代表一年中每个月的曲目。

虽然我不会深入分析背后的代码细节,但所有代码都可以在 Github 上我的 MyYearOfSpotify 回购中获得。这也包含了互动的情节,不幸的是太大了,无法嵌入到这篇文章中。

现在,让我们开始吧🚀

获取数据

在开始之前,我需要获得我的流历史。这可以通过 Spotify 个人资料页面的“个人资料”部分进行申请和下载。这个数据转储包含有关我的播放列表、个人资料、库的信息,最重要的是,还有我的整个流媒体历史记录。流媒体历史记录的格式如下,其中msPlayed是指音轨播放的毫秒数:

[ { "endTime" : "2021-09-19 15:46", "artistName" : "Rainbow", "trackName" : "Can't Let You Go", "msPlayed" : 259867 }, { "endTime" : "2021-09-19 15:51", "artistName" : "Rainbow", "trackName" : "Snake Charmer", "msPlayed" : 270427 },
  ...
]

初步分析

在这一节中,我将在下一节深入探讨跟踪嵌入之前,描述我在基本统计数据方面的一年。

我总共播放了 14944 首歌曲。然而,为了清理数据集,我删除了所有跳过的曲目。当一首曲目播放时间少于 30 秒时,我认为它被跳过了。我使用 Spotify API 来获取可能跳过的曲目的时长,这样短于 30 秒的曲目就不会被视为跳过。这次清理将磁道数量从 14944 减少到 11353。

最常播放的曲目和艺术家

最直接的分析是找出我在这一年中最常播放的曲目和艺术家。这可以通过总播放时间或总播放次数来衡量。我发现结果是相似的,所以我只显示基于总流时间的图表。

下表显示了最多流化的曲目。不过,我认为这有点偏颇,因为我在学习用吉他弹奏 U2。

我最常播放的曲目,以毫秒计算

类似地,我已经确定了我最流式的艺术家,这显示了比曲目更多的多样性,尽管大多数艺术家代表不同的摇滚流派:

我播放次数最多的艺术家,以毫秒计算

跳过最多的曲目

有些音轨比其他音轨更容易被跳过。为了找到跳过最多的曲目,我计算了我听的所有曲目的跳过率。以避免误点击等。,我只考虑了至少有 4 次(部分)播放的曲目。如下表所示,一些曲目从未播放结束,有些甚至一直被跳过。显然,我不太喜欢灰熊和格里塔·范·弗利特的两首歌!

跳过最多的曲目

以下是被跳过最多的艺术家。似乎 Ozric Tentacles 的迷幻太空摇滚对我来说太多了!

大多数被跳过的艺术家

一天/一周/一月/一年的时间

总共听了 35 天 21 小时 22 分 2 秒的音乐。我在流媒体播放的任何一天(即我至少播放一首歌曲的一天)的平均收听时间是 2 小时 44 分钟。让我们深入了解这些溪流在一周内是如何分布的。

下面的图表显示了我在工作日和一天中的不同时段的收听时间。很明显,我的大部分会议都是在星期二和星期五上午,我喜欢在星期天上午听一些音乐。

受 Github 贡献图的启发,我在下面的图表中总结了我一年来的流媒体工作。要是我的 Github 贡献图也是这样就好了!

Github 风格的播放列表

轨道嵌入

在没有任何附加知识的情况下,音轨标题和艺术家姓名不是特别描述音乐音轨的属性。为了更好地理解这些曲目,我需要一种在某种程度上基于其音乐特性的表现形式。

由于许可的原因,分析音轨的音频数据是不可行的,所以我寻找其他相关的数据集来丰富音轨的信息。我最终发现 Spotify 百万播放列表数据集挑战包含了来自 Spotify 的一百万个播放列表。

基于相同播放列表中的曲目之间存在关系的假设,该数据集用作我的曲目嵌入的源。

Word2Vec、Node2Vec 和… Track2Vec

将单词嵌入分布式表示的最流行和最简单的方法之一是 Word2Vec。Word2Vec 通过训练一个模型来工作,该模型给定一个单词,要么预测该单词在该单词上下文中的分布(skip-gram),要么预测给定该单词上下文的单词(CBOW)。在优化模型之后,每个单词的权重可以用作密集向量表示。这些向量已经被证明可以捕捉有用的语义属性——最著名的是国王-男人+女人=王后。

Word2Vec 的思想后来被扩展到 Node2Vec,这是一种学习图中节点的分布式表示的方法。Node2Vec 不使用单词的上下文,而是使用通过二阶随机行走可从目标节点到达的节点。

因为我确实想要一个不随播放列表中曲目顺序变化的嵌入,所以我的方法基于 Word2Vec 和 Node2Vec 的混合。我将播放列表中的每首曲目视为一个节点,它与该播放列表中的其他曲目都有边。为了简单起见,我一次只考虑一个播放列表,所以即使一首曲目出现在几个播放列表中,也只有当前播放列表中的曲目被视为邻居。此外,我只对邻居进行了采样,使用的是一阶随机游走,边上没有权重。实际上,这是通过从当前播放列表中随机采样上下文轨道来实现的。

使用 Gensim 和 CBOW 方法训练该模型,即基于从播放列表中采样的其他曲目预测目标曲目。我在每个目标轨道上随机抽取了 5 个情境样本。我对播放列表数据集进行了基本的清理,合并了同一艺术家同名的曲目。这消除了专辑中出现的重复曲目。

下图显示了 Track2Vec 的上下文采样。

Track2Vec 采样示例。图片作者。

除了 Track2Vec,我还训练了艺术家向量。除了我只考虑每个播放列表中不同的艺术家之外,这些都是以与曲目相同的方式训练的。

T-SNE 可视化

为了可视化轨道嵌入,我使用 T-SNE 绘制了它们的二维图。

颜色代表到 10 个最近邻居的平均距离,即原始嵌入空间中的密度。由于大量的轨迹,使用来近似点之间的距离。

下图显示了 Track2Vec 嵌入,标签指示不同的轨道簇。流派相当分明,不同流派之间的距离看似合理。例如,器乐和古典音乐的位置很近,电子音乐和舞蹈也是如此。我很高兴街机游戏的原声音乐恰好在嵌入空间中有自己的位置👾

Track2Vec 可视化。图片作者。

类似地,我绘制了 Artist2Vec 嵌入。类似地,艺术家的流派也定义了聚类:

Artist2Vec 可视化。作者图片

Track2Vec 和 Artist2Vec 剧情的交互剧情都可以在库中找到。

基于歌词的基本情绪分布

虽然曲目嵌入代表了曲目,但我想更深入地挖掘曲目的意义,尤其是我这一年所听曲目的意义。我从⁴百万歌曲数据库下载了歌词,其中包含了 237,662 首歌曲的歌词。由于歌词的版权,数据集只包含歌曲的词袋表示。然而,考虑到论文“的发现:在自然语言理解任务中,句子中单词的顺序有多重要?“⁵,这对于一个相对简单的情绪分类器来说已经足够了。我使用 Musixmatch API 来获取不属于开放数据集的歌曲的歌词,虽然它是有序的,但只有 30 %的歌词是由免费 API 提供的。

为了进行情绪分类,我使用了拥抱脸的bhadre sh-savani/distil Bert-base-un cased-emotion情绪分类器。

下面的视频形象化了我一整年的音乐心情。嵌入图上的亮点是我在给定时间听的曲目。虽然我预计我的情绪会随着一年中的情感事件而变化,但我无法找到任何模式——可能是因为情绪不仅仅基于歌词,还基于流派、节奏、乐器等。所以我将以我第三首听完的歌曲的标题来结束我的分析:我仍然没有找到我要找的东西。

2021 年的情绪分布

总结:作为播放列表的我的一年

最后,我根据我在 2021 年期间听过的歌曲生成了一个播放列表,结束了我的一年。该播放列表由每个月的五首经过重复删除的歌曲组成,代表我在该特定月份的收听历史。通过对我每个月播放的曲目的曲目嵌入运行 K-means ( k =5)来选择曲目,然后在所有曲目(不仅仅是我听的那些)中选择最接近五个质心的曲目。这意味着播放列表不一定包含我在这一年中流式播放的曲目,而是代表我流式播放的曲目类型的曲目,尽管我可以看到,我播放的一些曲目是存在的。

下图显示了一月份的五个群集。

我的一月集群的可视化。图片作者。

这是结果——我这一年的流媒体播放列表:

参考

1 C.W. Chen、P. Lamere、M. Schedl 和 H. Zamani。 Recsys Challenge 2018:自动音乐播放列表延续。2018 年第 12 届 ACM 推荐系统会议论文集(RecSys '18)。

[2] T .米科洛夫,k .陈,g .科拉多,j .迪安。向量空间中单词表示的有效估计。2013 年,Arxiv.org

3 A .格罗弗,j .莱斯科维奇。 node2vec:网络的可扩展特征学习。2016 年,Arxiv.org

4 T .贝尔坦-马希约,D.P.W .埃利斯,b .惠特曼,p .拉梅雷。
百万歌曲数据集。第 12 届国际音乐信息检索会议论文集(ISMIR 2011),2011 年

[5] T.M. Pham,T. Bui,L. Mai,A. Nguyen。无序:自然语言理解任务中,一个句子中单词的先后顺序有多重要?Arxiv.org,2021 年

基于单层感知器的数字分类

原文:https://towardsdatascience.com/digit-classification-with-single-layer-perceptron-9a4e7d4d9628

从零开始构造单层感知器及其在二进制数字分类中的应用

Unsplash 上由 Waldemar Brandt 拍照

G 一般来说,当一个人要对图像应用监督学习技术时,首先想到的是利用卷积神经网络(CNN)。事实上,这种类型的神经网络最适合这种类型的任务,主要是由于维数的减少。

如果我们想象一个图像数据集,其中的图像已经被展平(例如,一个 4x4 矩阵的图像被转换为 16 维向量,如图图 1 所示),这些图像是 n 维空间中的数据点,其中 n 是图像中的像素数。可以推断,当我们谈论图像时,数据的维度是巨大的,因此这意味着在神经网络中有大量的参数,这反过来导致更高的计算成本和执行时间。CNN 在神经网络的每一层中减少了图像的维度,也减少了训练中所需的参数数量,并优化了这类任务的模型性能。有关 CNN 的更多信息可在本出版物中找到:

图一。图像的展平。作者图片

另一方面,本文旨在通过单层感知器来解决数字图像的二进制分类,这是一种比 CNN 更简单、更古老的架构,因此尽管其起源过早,但仍展示了其巨大的潜力,并为任何想要了解深度神经网络(DNNs)和最新深度学习模型如何工作的人提供了一个良好的起点。

感知器

感知机是由心理学家弗兰克·罗森布拉特在 1957 年发明的,它由一个能够执行二元分类任务的线性鉴别器组成。虽然感知器很快就开始用于使用数字特征来训练它的分类,但 Rosenblatt 最初的目的是使用感知器进行图像分类,这最终在 1962 年实现,并在这里再次复制。

感知器可以很容易地理解为一个数学函数,因为它接收大量的输入并从中获得一个结果。这些输入是训练数据集中数据点的每个维度的值。正如我们之前所说,当我们处理展平的图像时,数据点属于一个 n 维的空间,其中 n 是像素的数量。感知器因此接收 n 个输入。

感知器。作者图片

上图中感知机的数学函数定义如下。当 y 为 0 时,该等式是 n 维空间上的超平面的等式(0 = w x + b,其中 wx 都是 n 维向量),该等式将用于根据数据点在超平面的哪一侧来将点分类为一类或另一类。

感知器的数学函数。作者图片

所以感知器接收输入向量 x ,但是 wb 在函数中取什么值呢?这些值分别是权重和偏差,是感知器在训练过程中必须学习的参数。

此时,感知器的高级操作被定义:它接收一个 n 维向量作为输入,用训练好的权重应用它的函数,并返回一个值;这个值将是负的或正的,这取决于它在划分超平面的哪一侧。

感知器的结果,如上所述是正值或负值,通过激活函数映射到其相应的类。这个功能可以在图 2 中看到。,映射到 0 或 1 时称为阶跃函数。

图二。步进功能。作者图片

用于从感知器获得预测的代码以及激活函数的定义如下所示。

只剩下一件事需要理解。感知器如何学习哪些权重是最佳的,以最佳地执行分类?接下来的步骤将展示负责优化感知器权重的算法的逻辑,以及实现它的代码。

1。正向传播

首先,将训练数据集中的一个数据点加载到感知器中,并计算感知器函数的结果。对这个输出应用激活函数得到最终预测,最后通过比较预测值和真实值得到损耗。

要最小化的变量误差损失通过对实际值和预测值之间的差值求平方来计算(在这种特殊情况下),如下所示。

误差/损失函数。作者图片

在这种情况下,前向函数将利用它已经做出的预测来计算误差和误差对预测 y 的偏导数(计算该偏导数的原因和有用性在下面的步骤中解释)。

2.反向传播

计算损失相对于每个权重的偏导数。这些都是通过应用链式法则来计算的,链式法则将导数一分为二,大大简化了计算过程。这些偏导数是在感知器中的每个权重上计算的,并将在以后的优化过程中使用。

计算这些偏导数(更好地称为梯度)的理论原因是,它们形成了一个向量,指示在何处更新感知器的权重向量( w ),以使损失最小化。为了更好地理解它,如果我们考虑三维图中的损失函数 r(看起来像一组山),梯度向量( d W )将具有 3 个值/维度,将指示到达局部或全局最小值(或者继续类推,到达山的最低点)所遵循的方向,从而允许我们在每次迭代中最小化损失。有关渐变的更多信息,请访问本文:

https://machinelearningmastery.com/gradient-in-machine-learning/

下面你可以看到基于链式法则计算偏导数的数学发展。

3.权重更新

权重按照下面所示的等式更新(注意, Ww 沿着文章可互换使用)。导数向量 d W 已经从反向传播步骤中获得,并且学习速率 η 是由用户设置的超参数,其将影响感知机学习的速度。非常低的值会导致算法陷入局部最小值,而太高的值会使算法永远不会收敛。因此,学习率是一个必须仔细确定的参数,通常通过试错法或基于以前的研究。

4.优化感知器

优化将存在于一个循环中,该循环将重复上述三个步骤,重复用户想要的个时期(迭代)。每次迭代将选取训练数据的每个值,对其应用前向传播和后向传播,并相应地更新权重。

感知器的底层功能已经被理解,将其应用于图像的二值分类是可行的。

数字分类

单层感知器只能进行线性分离,因此只能进行二进制分类,所以只能从数据集中的 9 个数字中选择两个数字。在这种情况下,选择 0 和 1,因为在计算机科学的应用中,机器可以提供更大的效用来分类 0 和 1。除了提取与所选数字对相关的数据之外,还需要将标签的值转换为 0 和 1(在这种情况下没有必要,因为标签已经是 0 和 1,但是对于任何其他值对来说都是如此),或者改变激活函数,以便将感知器的输出映射到一个值或另一个值。映射到 0 和 1 的常量激活函数,以及将任意一对数字的标签更改为 0 和 1 的数据预处理管道,似乎是这项任务的最佳选择。

第一步是加载数据集并过滤标签为 0 和 1 的实例。然后,创建一个训练集和测试集,并依次划分为用于训练的标签和特征。此外,数据点被重新调整为 0 到 1 之间的值。

如前所述,感知器接受一个向量作为输入,该向量的维数与图像中的像素数一样多。许多图像数据集将图像存储为矩阵,因此在这种情况下,有必要将这些矩阵转换为矢量。还需要注意的是,数据集通常以 CSV 文件的形式出现,由于其表格性质,很难将图像存储为矩阵。因此,大多数 CSV 格式的图像数据集将图像存储为矢量而不是度量。

在这种特殊情况下,要使用的数据集包含作为向量的数据点,因此在预处理期间没有必要展平图像。然而,在下面你可以看到在矩阵形式的图像数据集上展平图像的代码。这只是将每个数据点从一个矩阵m×l重新整形为一个矢量n×1,即n = m×l

一旦数据被预处理,之前构建的感知器被训练。在optimize _ perceptron()函数的主 while 循环的条件中可见,为训练设置的两个停止条件是时期数和误差:如果达到最大时期数或损失低于给定阈值,则训练结束。

现在权重已经被优化了,有趣的是注意到感知机学到了什么。经过训练的权重向量在视觉上不能给出太多信息,但是如果将其转换为 28×28 矩阵,我们将能够看到感知器给予训练图像的哪些像素更大的重要性。

****训练过的重量。作者图片

感知器的权重显示零的形状!画出 0 形状的像素具有暗色调,这意味着这些权重是负的并且具有高绝对值。因此,当这些像素通过接收零而被激活时,感知器的结果将是负的,因为那些权重是负的。相比之下,1 预期通过图像的中心,因此具有正值的重要权重在图像的中心,从而当感知器接收到 1 的图像时,其输出为正。用测试数据集评估感知机的准确性将确认机器已经学会区分类别,正如权重形成的图像似乎显示的那样。

通过获得测试数据集中所有图像的预测,并将正确预测的数量除以总预测来计算准确度函数。函数的实现可以在我的 GitHub 资源库中找到。

****准确度。图片由用户提供

感知器取得了 99.72%的准确率,令人难以置信的好成绩!它成功地对测试数据集中的 2204 幅图像中的 2198 幅进行了正确分类,这意味着它已经正确地学习了该任务,并且能够将该知识推广到它从未见过的图像。

结论

在观察了在二进制数字分类中获得的结果之后,可以说 Frank Rosenblatt 在建立图像分类器的愿望方面做得非常好。此外,深度学习领域年复一年的进步表明,罗森布拉特奠定了该学科绝大多数发展所基于的基础。

最后,值得注意的是,在像数字分类这样复杂的任务中,从 1957 年的算法中可以获得的结果是惊人的。虽然感知器的性质排除了多类分类,但通过将若干训练与数字对的所有组合相结合,可以构建 9 类数字分类器,这在当时是一项真正令人惊叹的技术!

完整代码

所有的功能都可以在我的 GitHub 存储库中找到,还有一个真实的运行示例 Jupyter Notebook 和用于训练的数据集。

尽管如此,尝试理解其功能,并亲自对其进行编程仍然是一个很好的练习!

如果你喜欢这篇文章和/或觉得它有用,请关注我以后的文章,分享这篇文章,这样其他人也可以学习。非常感谢您的阅读和支持!

****GitHub 库:

**https://github.com/JavierMtz5/ArtificialIntelligence **

数据

本文使用的数据摘自 ka ggle competition数字识别器的名为 train.csv 的数据集,可以在下面的链接中找到。该数据集在知识共享署名-共享 3.0 许可下可用。

**https://www.kaggle.com/competitions/digit-recognizer/overview **

数字艺术摊牌:稳定扩散、DALL-E 和中途

原文:https://towardsdatascience.com/digital-art-showdown-stable-diffusion-dall-e-and-midjourney-db96d83d17cd

从文本提示创作新作品的流行人工智能扩散模型的比较

“一个专注的葡萄牙人的油画”和“一个放着灯、书和老花镜的床头柜的油画” 由稳定扩散渲染(左) DALL-E (中)(右),图片由作者提供

我之前写过[关于使用 OpenAI 最新的 DALL-E 1模型从文本提示中创建数字艺术。在这篇文章中,我将 DALL-E 与另外两个流行的文本到图像模型进行比较,这两个模型分别是 LMU 慕尼黑的 CompVis 集团的 Stable Diffusion 和 Midjourney 3,这两个模型是由同名的研究实验室开发的。](https://medium.com/towards-data-science/exploring-dall-e-for-digital-art-creation-b244e1a2ed12)

我将从扩散模型的一些背景信息开始,它是这里描述的所有三个系统的基础。然后,我将讨论我如何使用同样来自 OpenAI 的 CLIP 模型4来自动计算客观指标,以使用一种称为对比相似性测试的新技术来判断生成的艺术。

接下来,我将详细介绍这三种型号,并讨论可用的功能和成本。然后我将展示当我发送 16 个不同的提示时,这三个系统创造了什么,展示美学质量和提示相似性的度量。我将通过总结最好的系统来结束这场对决,然后进行简短的讨论。

扩散模型

扩散模型是机器学习(ML)系统,最初设计用于从图像中去除噪声。随着降噪系统的训练时间越来越长,变得越来越好,它们最终可以从纯噪声作为唯一的输入生成逼真的图片[5]。

最近,扩散模型已经取代生成对抗网络(GANs)成为最先进的图像生成器。2021 年 6 月,OpenAI 发表了一篇名为“扩散模型在图像合成上击败了 GANs”的论文。[6]作者是这样说的。

扩散模型是一类基于似然性的模型,其最近被证明产生高质量的图像,同时提供期望的属性,例如分布覆盖、固定的训练目标和容易的可伸缩性。这些模型通过逐渐去除信号中的噪声来产生样本,并且它们的训练目标可以表示为重新加权的变分下界。…[通过]改进模型架构,然后通过设计一个用多样性换取保真度的方案…我们实现了一个新的艺术状态,在几个不同的指标和数据集上超过了 GANs。

扩散模型的作者在数百万个文本/图像对上训练他们的系统,因此当你输入文本提示时,系统将交互式地生成与提示匹配的新图像。例如,下一节中的截图显示了提示“秋林印象画”的结果

认识一下竞争者

稳定扩散

****使用稳定扩散的 DreamStudio 用户界面,图片由作者提供

摊牌中的第一个扩散模型是稳定扩散。康普维斯集团在 LMU 慕尼黑开发了它。该模型是与稳定 AI跑道联合开发的。

该模型的组件图显示了输入图像(x)如何在扩散过程中被编码成“潜在空间”并被解码成输出图像(x̄).解码过程在使用文本、图像等的训练期间受到限制。

****稳定扩散分量图,图片来自康普维斯论文

作者将其作为一个开源项目发布在 GitHub 上,并作为一项商业服务发布,它有一个简单易用的网络用户界面,名为 DreamStudio。我使用了 DreamStudio 服务进行这次摊牌。

达尔-埃

****DALL-E 用户界面,作者图片

被称为 DALL-E 的系统是 OpenAI 1开发的扩散模型的第二次迭代,它与他们称为 CLIP 4的图像/文本编码系统结合使用。在本文中,图像到文本的扩散模型被称为解耦模型。论文的组件图展示了 CLIP 的工作原理,以及该模型如何从文本提示中呈现图像。

****解开元件图,图像来自 OpenAI 的论文

为了摊牌,我使用了 OpenAI 的 DALL-E 商业服务。

中途

****中途用户界面,作者图片

Midjourney 是一个商业文本到图像的扩散模型,由一个也叫 Midjourney 的研究实验室创建。该组织由 David Holz 领导,他曾是 Leap Motion 的创始人之一。中途系统使用不和谐频道上的机器人作为用户界面,结果显示在账户这里

比较系统

特征

这三个系统都将从文本描述中创建图像。例如,这里有一些来自提示的图像,“秋天树林的印象派绘画。”

****《印象派秋林图》稳定扩散渲染(左) DALL-E (中)中途(右),图片作者

DreamStudio 和 Midjourney 中的稳定扩散模型有设置屏幕,您可以在其中调整各种参数。对于 DreamStudio,设置在用户界面中的图像旁边。对于中途,键入“/settings”在 Discord 服务器中调出 UI。DALL-E 本身没有任何设置。只有文本提示可用。

这是 DreamStudio 和 Midjourney 的设置界面。

******

稳定扩散设置(左)和中途设置(右),图片由作者提供**

如你所见,这两个系统有很多选择。大多数设置都是自我描述的,而且它们都工作得很好。然而,对于我的摊牌项目,我只使用了默认值。

图像尺寸

这三种型号以不同的图像尺寸运行,但是每一种都有进一步调整大小的选项。

DreamStudio 中的稳定扩散模型默认使用 512x512 的图像大小,但是您可以使用 64 像素增量的设置将图像大小放大到 1024x1024。我用 1024 作为最长的尺寸创建了大多数来自稳定扩散的图像,除了肖像,我用了 512x640。原因是较大的 832x1024 图像经常有“幻影”部分人出现在构图中,而较小的 512x640 图像只有一个人出现。你可以在下面的例子中看到不同之处。

****

“一个专注的葡萄牙人的油画”,以 832x1024 (左)、512x640 (右)的稳定扩散渲染,图片由作者提供

请注意,左边的图像在左下方看起来有一个人的一部分,而右边的图像只显示了一个人。DALL-E 和 Midjourney 系统没有这个问题。

DALL-E 型号创建的图像本身为 1024x1024。该系统还提供了一个选项,使用“添加生成帧”功能来放大图像和更改纵横比。在下面的例子中,你可以看到我是如何通过延长肖像来显示这个人的头顶和躯干的一点点来增加长宽比的。

****

“一个专注的葡萄牙人的油画”,以 1024x1024 (左)和 1024x1280 (右)的比例在 DALL-E 上绘制,图片由作者提供

您可以看到 DALL-E 系统如何出色地按照原画的风格渲染图像的缺失部分。

默认情况下,Midjourney 以 256x256 的分辨率从文本提示中渲染图像。系统允许用户使用命令行参数随意指定纵横比,即- ar 4:5。使用此纵横比可以生成四幅 256x320 的图像。该系统还允许用户将选定的图像放大 4 倍,达到 1024×1280 的宽高比。调整大小算法使用提示来添加上下文细节。这是一个中途放大的图像的例子。

****

“一个专注的葡萄牙人的油画”,中途以 256x320 (左)、1024x1280 (右)渲染,图片由作者提供

你可以看到 Midjourney 是如何在这个人的脸上添加细节,并在笔触中添加额外的细节。

定价

梦工厂定价

虽然在 GitHub 上有一个免费的开源版本的 Stable Diffusion,但是它没有任何 DreamStudio 中可用的漂亮 UI 特性。在定价方面,这项服务使用信用系统。当你注册的时候,他们会免费给你 200 学分。收费点数取决于图像的大小和创建图像的“步骤”数量。按照目前的价格,创建一个默认步数为 50 的 1024x1024 图像将花费 9.4 个信用点。所以你可以免费生成 21 张高分辨率的图像。您可以再花 10 美元购买 1000 个积分。在这个价格下,生成 512x512 大小的 50 步图像需要 1 美分,1024x1024 需要 9.4 美分。更多关于 DreamStudio 定价的信息请点击这里

DALL-E 定价

DALL-E 也使用信用系统。生成四张带提示的图片需要一个积分。注册时你可以获得 50 个免费学分,每个月还可以额外获得 15 个学分。你可以再花 15 美元购买 115 个积分。一组 4 张图片 13 美分,每张 3.2 美分。更多关于 DALL-E 免费信用的信息可以在这里获得,信用定价信息在这里获得

中途定价

Midjourney 有使用他们服务的包月计划。它基于为“GPU 分钟数”付费。您可以免费获得 25 分钟的 GPU 时间,并可以每月支付 10 美元获得 200 分钟的 GPU 时间。这相当于 4 幅图像 6.7 美分,每幅图像 6.7 美分以调整到 1024×1024。更多关于中途定价的信息请点击这里

概括地说,对于一个 1024x1024 的图像,DreamStudio 的成本是 9.4 美分,DALL-E 的成本是 3.2 美分,Midjourney 实际上是 13.3 美分。

政策

像大多数在线服务一样,这三个系统都定义了它们的使用条款。

DreamStudio 政策

以下是 DreamStudio 的使用条款。第一项是一个大项目。用户不拥有他们使用 DreamStudio 服务创建的图像。这些图像被自动发布到公共领域。请注意,这本身并不排除商业用途;只是用户不用为公共领域的作品付钱给你。声明:我不是律师。

所有使用 DreamStudio Beta 和 Stable Diffusion beta Discord 服务的用户特此确认已阅读并接受完整的 CC0 1.0 通用公共域声明(可在https://creative commons . org/Public Domain/zero/1.0/),,)获取,其中包括但不限于前述对任何内容的知识产权的放弃。
DreamStudio 测试版和 Stable Diffusion 测试版不应用于:
-NSFW、淫秽或性材料
-仇恨或暴力图像,如反犹太主义肖像、种族主义漫画、厌恶女性和厌恶男性的宣传等。
-关于您自己或任何其他人的个人信息。这包括但不限于电话号码、居住地址、社会安全号码、驾照号码、账号等。
-提示中应避免版权或商标材料。

DreamStudio 的完整使用条款是这里是

DALL-E 政策

相比之下,OpenAI 允许 DALL-E 用户拥有他们的图像,并将其用于商业目的

以下是 DALL-E 内容政策的重点。

在您的使用中,您必须遵守我们的内容政策:
不要试图创建、上传或共享非 G 级或可能造成伤害的图像。不要误导你的观众关于人工智能的参与。尊重他人的权利。请通过我们的帮助中心向我们的团队报告任何涉嫌违反这些规则的行为。

DALL-E 的完整使用条款在这里是。

中途政策

对于用户创建的图像的所有权,Midjourney 区分了非付费用户和付费用户。非付费用户不拥有他们创建的图像,但 Midjourney 授予您这些作品的知识共享非商业性 4.0 归属国际许可证。另一方面,付费用户拥有他们创作的图片的版权,但会授予 Midjourney 完整的使用许可。

中途的完整使用策略是这里是

除了使用政策,Midjourney 还有一个内容审核政策。亮点如下。

Midjourney 旨在通过 Discord 和成员画廊成为一个默认开放的社区。我们的规则状态内容必须是 PG-13,特别是,#rules 频道声明,
不要创建图像或使用本质上不尊重、攻击性或其他辱骂性的文本提示。任何形式的暴力或骚扰都是不可容忍的。
无成人内容或血块。请避免制作视觉上令人震惊或不安的内容。我们将自动阻止一些文本输入。

中途的整个内容审核策略是这里是

社会问题

《稳定扩散》和《DALL-E》的作者在论文中讨论了图像生成模型可能存在的社会问题。

像图像这样的媒体生成模型是一把双刃剑:一方面,它们使各种创造性的应用成为可能,特别是像我们这样降低训练和推理成本的方法,有可能促进这种技术的使用,并使其探索民主化。另一方面,这也意味着创建和传播被操纵的数据或传播错误信息和垃圾邮件变得更加容易。特别是,故意操纵图像("深度伪造")是这种情况下的一个常见问题,尤其是妇女受到的影响更大。—罗宾·龙巴赫,LMU 慕尼黑康普维斯集团[2]

正如在 GLIDE 文件中所讨论的,图像生成模型带有与欺骗性和有害内容相关的风险。夹具的性能改进也提高了滑行的风险。随着技术的成熟,它会留下更少的痕迹和指示,即输出是人工智能生成的,这使得更容易将生成的图像误认为真实的图像,反之亦然。还需要对架构的变化如何改变模型在训练数据中学习偏差的方式进行更多的研究。阿迪亚·拉梅什等人。艾尔。,OpenAI 1

量化好的和坏的艺术

在我比较这三个模型的输出之前,我将讨论我开发的一项技术,这项技术完成了一项看似不可能的任务:使用自动算法为艺术品的美学赋予一个量化值。

好艺术

为了看看算法是否可行,我先收集了 9 部我们社会认为不错的作品。我在谷歌搜索中输入“名画”,下面是一些出现的图片。

wikiart 的杰作。org ,第一排:克洛德·莫内的《印象日出》、艾伯特·比尔兹塔德的《俯瞰约塞米蒂山谷》、文森特·梵高的《在埃加利耶附近的高林河畔有柏树的麦田》、第二排:文森特·梵高的自画像、爱杜尔·马奈的《贝尔特·摩里索特带着一束紫罗兰》、詹姆斯·麦克尼尔·惠斯勒的《惠斯勒的母亲》、第三排:文森特·梵高的《向日葵》、保罗 _ 的《窗帘、酒壶和水果》****

如你所见,我选择了三幅风景画、三幅肖像画和三幅静物画。是的,收藏品中有三幅梵高的作品。

糟糕的艺术

接下来,我需要挑选九幅“糟糕”的画。好消息是,在我居住的波士顿地区有一个糟糕艺术博物馆。我得到了博物馆“永久代理临时执行董事”的许可,在这篇文章中使用了他们收藏的一些画作。这里是烂艺术的集合。

来自吟游诗人艺术博物馆的画作,第一排:难以辨认的复活节岛,威尔的新的一天,无名氏灯塔,第二排:简·道尔的克林顿总统,米德尔顿的拉奎尔,无名氏的粉红女郎登高,第三排:人字拖,冰球,网球,钱包,无名氏的项链, 珍妮·加拉诺的《鹿角静物》,匿名的museumofbadart.org 的图片,经许可使用

好吧,这似乎是一些真正糟糕的艺术,尤其是与上面的杰作相比。

相似性测试

为了看看我是否可以使用剪辑模型来区分两组绘画,我首先将每幅图像的嵌入内容与短语“真正的艺术”和“好的艺术”的嵌入内容进行了比较,并绘制了图像和文字之间的相似之处。正如你在左下图中看到的,这并没有像我预期的那样把好的艺术和坏的艺术分开。事实上,在图表顶部附近有更多“糟糕”的绘画。

****

对比相似性测试

我尝试了一下,发现诀窍是输入四个短语,“假艺术”、“真艺术”、“坏艺术”和“好艺术”,然后对结果做一点数学运算。在获得文本和图像嵌入的相似性之后,我使用下面的等式来获得我正在寻找的度量。

好因素=好艺术-坏艺术
真实因素=真实艺术-假艺术

你可以在右上图中看到结果。我称之为对比相似性测试,它似乎对我的 18 张图片样本很有效。

然后,我将好的和真实的因素结合起来,创建了一个单一的审美质量指标:审美质量=好的因素+真实的因素。以下是用这个标准衡量好的和坏的艺术作品的方法。

****好画和坏画的审美质量,作者图表

这似乎大体符合我对画作的分析。你可以看到比尔·克林顿的肖像(BP1)是坏中之好,莫奈的印象日出(GL1)是好中之坏。

渲染摊牌

为了测试这三个系统,我设计了 16 个提示,并输入它们来生成图像。

**Landscapes** 1\. Impressionist painting of autumn woods
2\. painting of rolling farmland
3\. modern seascape with crashing waves
4\. realistic painting of the Boston city skyline**Abstract Paintings**
1\. abstract painting of triangles in orange
2\. block color painting with purple and green squares
3\. abstract painting with spheres in ocean blue
4\. splatter painting with thin yellow and black lines**Still Lifes**
1\. still life painting of a bowl of fruit
2\. Impressionist oil painting of sunflowers in a magenta vase
3\. still life painting of colorful glass bottles
4\. oil painting of a nightstand with lamp, book, and reading glasses**Portraits**
1\. Cubist painting of a man from the 1920s
2\. charcoal drawing of a young Brazilian woman
3\. oil painting of a focused Portuguese guy
4\. pastel painting of a concerned Korean woman

然后,我通过 CLIP 运行结果,找到两个指标并绘制结果。

审美质量=良好因素+真实因素
提示相似度=余弦相似度(提示嵌入,图像嵌入)

抽象画

提示:“橙色三角形抽象画”

****“橙色的三角形抽象画”在稳定扩散中渲染(左) DALL-E (中)midway(右),图片由作者提供

****即时相似性与审美质量,作者图表

提示:“用紫色和绿色方块拼色绘画”

****“带有紫色和绿色方块的块彩画”以稳定扩散渲染(左) DALL-E (中)midway(右),图片由作者提供

****即时相似性与审美质量,作者图表

提示:“海洋蓝色球体抽象画”

“海洋蓝色球体抽象画” 渲染稳定扩散(左) DALL-E (中)中途(右),图片由作者提供

****即时相似性与审美质量,作者提供的图表

提示:“带有黄色和黑色细线的飞溅绘画”

“带有黄色和黑色细线的泼溅绘画”(左) DALL-E (中)中途(右),图片由作者提供****

即时相似性与审美质量,作者提供的图表

风景

提示:“秋天树林的印象派画作。”

《秋林印象派绘画》 渲染稳定扩散(左) DALL-E (中)中途(右),图片作者****

即时相似性与审美质量,作者图表

提示:“起伏农田画”

《起伏农田图 渲染》稳定扩散(左) DALL-E (中)中途(右),图片作者****

即时相似性与审美质量,作者提供的图表

提示:“海浪翻滚的现代海景”

《惊涛拍岸的现代海景》(左) DALL-E (中)中途(右),图片作者****

即时相似性与审美质量,作者提供的图表

提示:“波士顿城市天际线的写实绘画”

“波士顿城市天际线的写实绘画” 渲染稳定扩散(左)达尔-E (中)中途(右),图片由作者提供****

即时相似性与审美质量,作者图表

静物

提示:“一碗水果的静物画”

《一碗水果的静物画》渲染稳定扩散(左) DALL-E (中)中途(右),图片作者****

即时相似性与审美质量,作者图表

提示:“洋红色花瓶中的向日葵印象派油画”

“洋红色花瓶中的向日葵印象派油画”在稳定扩散中渲染(左)达尔-E (中)中途(右),图片由作者提供****

即时相似性与审美质量,作者提供的图表

提示:“彩色玻璃瓶静物画”

《五彩玻璃瓶静物画》渲染稳定扩散(左) DALL-E (中)中途(右),图片作者****

即时相似性与审美质量,作者提供的图表

提示:“有灯、书和老花镜的床头柜油画”

“有灯、书和老花镜的床头柜油画”在稳定扩散中渲染(左) DALL-E (中)中途(右),图片由作者提供****

即时相似性与审美质量,作者图表

画像

提示:“20 世纪 20 年代一名男子的立体派画作”

“20 世纪 20 年代一个人的立体派绘画”(左) DALL-E (中)midway(右),图片由作者提供****

即时相似性与审美质量,作者提供的图表

提示:“一个年轻巴西女人的素描”

“一个年轻的巴西女人的炭笔素描”在稳定扩散中渲染(左) DALL-E (中),以及中途(右),图片由作者提供****

即时相似性与审美质量,作者提供的图表

提示:“一个专注的葡萄牙人的油画”

“一个专注的葡萄牙人的油画”(左)【DALL-E】(中)(右),图片由作者提供********

即时相似性与审美质量,作者图表

提示:“一位忧心忡忡的朝鲜妇女的蜡笔画”

“一个忧心忡忡的朝鲜女人的粉彩画”以稳定扩散的方式呈现(左)、 DALL-E (中)、以及中途(右),图片由作者提供****

即时相似性与审美质量,作者图表

结论

将所有数据合并到一个图表中,您可以看到 Midjourney 如何获得美学质量的最佳指标。虽然 DALL-E 有一些渲染图与提示最匹配,但与 Midjourney 相比,整体质量有所下降。根据数字和我的眼睛,稳定扩散似乎是三者中表现最差的系统。

提示相似性与审美质量的概述,作者提供的图表

利用赋予我的权力,我在此宣布 midway 为这场对决的胜利者。

源代码和 Colabs

这个项目的所有源代码都可以在 GitHub 获得。我在 CC BY-SA 许可下发布了源代码。

知识共享署名共享

感谢

我要感谢詹尼弗·林对这个项目的帮助。

参考

1a . Ramesh 等人的 DALL-E 2,带剪辑潜在时间的分层文本条件图像生成 (2022)

[2]r . Rombach 等人的稳定扩散,利用潜在扩散模型的高分辨率图像合成 (2022)

3中途https://midjourney.gitbook.io/docs/

4a .拉德福德等人的剪辑,从自然语言监督中学习可转移的视觉模型 (2021)

[5] P. Dhariwal 和 A. Nichol,扩散模型在图像合成上击败了 GANs(2021)

[6] J. Ho 和 C. Saharia,使用扩散模型生成高保真图像 (2021)

为了无限制地访问 Medium 上的所有文章,成为了的会员,每月支付 5 美元。非会员每月只能看三个锁定的故事。

使用机器学习和约束优化的数字孪生建模

原文:https://towardsdatascience.com/digital-twin-modeling-using-machine-learning-and-constrained-optimization-401187f2a382

比较分析

Vishal Bansal 在 Unsplash 上拍摄的照片

如今,数据科学被广泛用于创建数字双胞胎,它们是真实世界物理系统或过程的数字副本,可用于模拟、预测输入行为、监控、维护、规划等。虽然这种数字双胞胎在认知客户服务机器人等日常应用中很常见,但在本文中,我将通过行业中的例子来比较用于建模两种不同类型双胞胎的数据科学技术。

本文中讨论的数字双胞胎的两个广泛使用的数据科学领域如下:

a) 诊断和预测分析:给定一系列输入,Twin 应该能够诊断原因或预测系统的未来行为。基于物联网的机器学习模型用于创建智能机器和工厂,通过实时分析来自传感器的输入来诊断、预测,从而在问题和故障发生之前预防它们。

b) 说明性分析:这是对整个网络进行模拟,在给定一组要遵守的变量和约束条件的情况下,从大量候选方案中确定最佳或可行的解决方案,通常目标是最大化规定的业务目标,如吞吐量、利用率、产量等。这些优化问题广泛用于供应链规划和调度,例如当物流提供商为其资源(车辆、人员)创建调度以最大化准时交付时,或者制造商创建调度以优化机器和操作员的利用以实现最大 OTIF 交付时。这里使用的数据科学技术是约束数学优化,使用强大的求解器来解决复杂的决策驱动问题。

总之,ML 模型基于历史预测一组给定输入特征的可能结果,而优化模型帮助您决定如果预测的结果发生,您应该如何计划处理/减轻/利用它,因为您的企业有几个可能竞争的目标,您可以选择用有限的资源来追求这些目标。

数据科学的这两个领域虽然共享一些工具,如 python 库,但部署的数据科学家具有完全不同的技能,通常需要不同的思维方式和业务问题建模方式。因此,让我们尝试理解和比较所涉及的方法,以便在一个领域有经验的数据科学家能够理解、交叉利用可能适用于另一个领域的技能和技术。

数字孪生模型的抽象

为了进行比较,让我们考虑一个基于 ML 的生产根本原因分析(RCA)过程,其目的是诊断在成品或制造过程中发现的缺陷或异常的根本原因。这将有助于生产线经理根据工具的预测对最有可能的根本原因进行故障排除,明确识别问题并快速实施 CAPA(纠正和预防措施),而无需花费太多的人力来检查所有机器维护记录、操作员历史记录、流程 sop、物联网传感器输入等。目标是最大限度地减少机器停机时间、生产损失和提高资源利用率。

从技术上讲,这可以被认为是一个多类分类问题,在给定缺陷的情况下,该模型试图预测一组可能的根本原因标签中的每一个的概率,例如机器相关、操作员相关、过程说明相关、原材料相关或其他,并且在这些 1 级类别中,有诸如机器校准、机器维护、操作员技能、操作员培训等粒度原因。虽然这种情况下的最优解需要评估几个复杂的 ML 模型,但出于本文的目的,让我们假设这是一个多项式逻辑回归问题(原因将在下一节中显而易见)。

作为比较,考虑一个生产计划过程的优化双胞胎,它生成一个时间表,在给定机器、操作员、过程步骤、持续时间、原材料到达时间表、到期日期等的情况下,试图最大化诸如产量或收入等目标。像这样的自动化时间表有助于组织快速调整其资源,以应对市场出现的新机遇(如新冠肺炎病毒导致的药物需求),或通过改变原材料、供应商、物流提供商和客户/市场组合,最大限度地减少不可预见事件(如最近的供应链瓶颈)的影响。

在对任何业务问题建模的基本层面上,开发这样的数字双胞胎有以下因素:

A.输入特征或尺寸

B.输入数据-这些尺寸的值

C.输入到输出的转换规则

D.输出或目标

让我们更深入地分析和比较 ML 和约束优化模型的这些因素:

A .输入特征:这些是系统中的数据维度,ML 和优化都有。对于试图诊断生产过程中的问题的 ML 模型,要考虑的特征可以是物联网输入、机器维护历史、操作员技能和培训、原材料质量、遵循的 SOP 等。

类似地,在约束优化中,设备可用性、操作员可用性、原材料可用性、工作时间、生产率、技能是制定最优生产计划所需的典型特征

B. 输入数据— 这是两种方法以显著不同的方式使用特征值的地方。ML 模型期望大量的历史数据用于训练。然而,在将数据提供给模型之前,通常需要进行与数据准备、处理和标准化相关的大量工作。需要注意的是,历史记录是实际发生的事件的记录(例如,机器故障,或导致输出不足的操作员技能问题),但通常不是这些特征可以采用的每个可能值的组合。换句话说,事务历史包含更多频繁发生的场景的记录,较少的一些其他场景的记录,可能很少的一些很少发生的场景的记录。训练模型的目标是它应该能够学习特征和输出标签之间的关系,并且即使对于训练数据中很少或不存在的特征值或组合,也能够预测准确的标签。

另一方面,为了优化,特征值通常保留为它们的实际值,例如,天数、批次、截止日期、按日期的原材料可用性、维护计划、机器的转换时间、工艺步骤、操作员技能等。ML 模型的关键区别在于输入数据处理需要生成主数据特征值的每一个可能的有效组合(例如:日、技能、机器、操作员、过程类型)转换成索引表,这些索引表可以形成可行解决方案的一部分。例如,该流程的步骤 1 由技能水平为 S1 的操作员 A 在一周的第 1 天使用机器 M1 执行,或者步骤 1 由技能水平为 S2 的操作员 B 在第 2 天使用机器 M1 执行..等等,对于操作员、机器、技能水平、日期等的每一个可能的组合,不考虑这些组合中的任何一个是否在过去实际发生过。这将产生非常大的输入数据记录集,并提供给优化引擎。优化模型的目标是挑选符合规定约束的特征值的特定组合,同时最大化(或最小化)目标方程。

C. 输入到输出的转换规则:这也是两种方法中有显著差异的地方。虽然 ML 和优化模型都基于高等数学,但与 ML 相比,在优化中对复杂的业务问题进行数学建模和编程通常需要付出更多的努力,这一点在下面的章节中将会很明显。

原因在于,在 ML 中,使用 scikit-learn 等库、Pytorch 或 Tensorflow 等框架或任何云提供商的 ML/深度学习模型,将输入转换为输出的规则留给模型来寻找,包括损失校正以导出最佳规则(权重、偏差、激活函数等)。).数据科学家的主要职责是确保输入要素及其值的质量和完整性。

对于优化而言,情况并非如此,在优化中,必须使用详细的方程来编写关于输入如何相互作用以及如何转换为输出的规则,然后将这些规则提供给 Gurobi、CPLEX 等求解器,以找到最佳或可行的解决方案。此外,将业务问题公式化为数学方程需要深入了解建模过程中的相互关系,以及数据科学家与业务分析师的密切合作。

让我们用下图的问题 RCA 应用的逻辑回归模型来说明这一点:

逻辑回归 ML 模型

请注意,在这种情况下,从输入生成结果(Zᵢ)的规则留给模型来推导,数据科学家通常忙于使用定义明确的混淆矩阵、RMSE 等指标来可视化预测准确性。

与优化生成生产计划的方式形成对比:

I)第一步是定义封装规划过程的业务规则(约束)。以下是生产计划示例的一个示例:

首先,我们定义一些输入变量(其中一些可以是决策变量,用于驱动目标):

**Bᵦ,ₚ,**ᵢ — Binary variable denoting if the batch β(in the batches table) of product *p*(in the products table) is scheduled on day *i* **Oₒ,ₚ,ᵢ** – Binary variable denoting if operator at index *o* (in the Operators table), is scheduled to work on a batch of product *p* on day *i* **Mₘ,ₚ,ᵢ** — Binary variable denoting if machine at index *m* (in the Machines table), is scheduled to work on a batch of product *p* on day *i*

和一些系数

**TOₒ,ₚ** — Time taken by an operator *o* for a batch of product *p* **TMₘ,ₚ** — Time taken by a machine *m* for a batch of product *p* **OAvₒ,ᵢ** — Availability hours of Operator at index *o* on day *i* **MAvₘ,ᵢ** — Availability hours of Machine at index *m* on day *i*

在这种情况下,一些约束(规则)如下:

A)一个特定批次在计划中只能启动一次

每批每种产品,其中 Bt 为批次总数,Pr 为产品总数,D 为附表中的天数:

b)一个产品一天只能在一个操作员或机器上启动一次

每个产品每天,其中 Op 是所有操作员的集合,Mc 是所有机器的集合:

c)批次(所有产品)所用的总时间不应超过当天操作员和机器的可用小时数

对于每个操作员的每一天,

对于每台机器的每一天,

d)如果操作员在计划的前 5 天处理一批产品,则同一产品的所有其他批次必须分配给相同的操作员。这可能是为了保持操作员的连续性和生产力。

对于每一天 d(从第 6 天开始)每个操作员和每个产品:

以上是数百个约束条件中的一小部分,这些约束条件需要编写在程序中,以便将实际生产调度场景的业务规则公式化为数学方程。注意,这些约束是线性方程(或者更具体地说,是混合整数方程)。然而,与逻辑回归 ML 模型的复杂性差异是明显的。

ii)一旦定义了约束条件,就需要定义输出目标。这是至关重要的一步,也可能是一个复杂的过程,下一节将对此进行解释。

iii)最后,输入决策变量、约束和目标被发送到求解器,以获得解决方案(时间表)。

下图描述了基于优化的 Twin:

优化模型

D. 输出或目标

对于一个 ML 模型,基于问题的类型(分类、回归、聚类),输出和度量其准确性的度量是相当好地建立的。虽然我不会在本文中深入研究这些,但考虑到已经有大量的可用信息,值得注意的是,有一个高水平的自动化可用于评估各种模型的输出,如领先的 CSP(AWS Sagemaker、Azure ML 等)的输出。)

评估优化模型是否产生正确的输出更具挑战性。优化模型通过尝试最大化或最小化被称为目标的计算表达式来工作。与约束一样,目标留给数据科学家根据业务试图实现的目标来设计。这是通过对决策变量附加奖励和惩罚来实现的,优化器试图最大化这些变量的总和。对于现实世界的问题,需要多次迭代才能找到不同目标的正确权重,以便在有时相互矛盾的目标之间找到良好的平衡。

为了说明生产调度的例子,两个这样的目标可以是:

a)时间表应该是前期加载的;应该尽快安排批次,并且计划中剩余的任何能力应该在计划的末尾。为此,我们可以将一天罚金附加到一个批处理中,该罚金随着时间表中每一天的流逝而递增。

b)另一方面,我们还想对同一产品的批次进行分组,以使资源(操作员和机器)得到最佳利用,前提是批次满足交货期限,并且该组在一次运行中不超过机器能力。因此,我们定义了一个 Batch_group_bonus ,如果批量在较大的组中比在较小的组中被调度,则它提供显著更高的奖金(因此是下面表达式中的指数)。需要注意的是,这有时会与之前的目标相冲突,因为一些本可以在今天开始的批次会与几天后可用的更多批次一起开始,从而可能在计划的早期留下一些未充分利用的资源。

由于求解者的工作方式,实际实现需要一个批处理组决策变量,但这传达了概念。

求解器将最大化目标=批次 _ 组 _ 奖金+天数 _ 惩罚

目标的上述两个组成部分中的哪一个对日程安排的给定日期具有更大的影响取决于 W₁、W₂以及日程安排的日期的权重,因为在日程安排的后面部分中,日期惩罚将逐渐变大(更高的 i 值)。如果在某个时间点,日惩罚变得大于 Batch_group_bonus ,则求解器将发现不调度批是明智的,因此,即使调度中还有资源能力,也不会招致调度的零惩罚,并且会招致净负惩罚,从而最大化目标。这类问题需要数据科学家来排查和解决。

ML 与优化项目的相对努力

基于以上讨论,可以推测,一般来说,优化项目比 ML 项目需要更多的努力。在开发过程的几乎每个阶段,优化都需要大量的数据科学工作。总结一下:

a) 输入数据处理:在 ML 和优化中,这都是由数据科学家完成的。ML 数据处理需要选择相关特征、标准化、离散化等。对于文本等非结构化数据,它可能包括基于 NLP 的方法,如特征提取、标记化等。有许多语言版本的库可用于特征的统计分析以及 PCA 等降维方法。

在优化中,每个业务和时间表都有细微差别,需要引入到模型中。优化问题不处理历史数据,而是将每一个可能的数据变化和已识别特征的组合公式化为决策变量和约束必须基于的索引。虽然与 ML 不同,但数据处理需要大量的开发工作。

b) 模型开发:如上所述,用于优化的模型公式化需要数据科学家和业务分析师付出大量努力来公式化约束和目标。求解器运行数学算法,虽然它的任务是同时求解数百甚至数千个方程来找到一个解决方案,但它没有业务上下文。

在 ML 中,模型训练是高度自动化的,算法打包成开源库 API,或者由云服务提供商提供。高度复杂、预先训练的神经网络模型根据业务特定数据,将训练任务简化到最后几层。AWS Sagemaker Autopilot 或 Azure AutoML 等工具,甚至可以自动化输入数据处理、特征选择、不同模型的训练和评估以及输出生成的整个过程。

c) 测试和输出处理:在 ML 中,来自模型的输出可以用最少的处理来利用。它通常很容易解释(例如,不同标签的概率),尽管可能需要一些努力来引入额外的方面,例如结果的可解释性。输出和错误可视化可能也需要一些工作,但与输入处理相比,这些工作并不多。

在这里,优化问题也需要反复的手工测试,并由计划专家训练有素的眼睛来评估时间表。虽然求解程序试图最大化目标,但从进度质量的角度来看,这本身通常没有什么意义。与 ML 不同,不能说高于或低于阈值的目标值包括正确或不正确的时间表。当发现一个调度不满足业务目标时,问题可能出在约束、决策变量或目标函数上,需要仔细分析以找出大型复杂调度中异常的原因。

此外,要考虑的是将求解器的输出解释为人类可读格式所需的开发。求解器输入决策变量,决策变量是调度中实际物理实体的索引值,例如,批次组索引、批次优先级索引、操作员和机器索引,并返回已被选择的那些。需要进行反向处理,以将这些指数值从相应的数据帧转换成连贯的时间表,该时间表可以由专家直观地呈现和分析

d)最后,甚至在操作阶段,与训练阶段相比,ML 模型消耗更少的计算和时间来生成对观察的预测。但是,每次都是从头开始构建计划,每次运行都需要相同的资源。

下图是 ML 和优化项目各阶段相对工作量的粗略说明:

相对努力— ML 与优化

ML 和优化能一起工作吗?

机器学习和优化解决了企业的互补问题,因此在 ML 模型的输出为优化提供反馈的情况下共存,反之亦然。AI/ML 应用,如物联网支持的预测性维护和故障检测、AR/VR 远程维护以及这里提到的生产流程 RCA,构成了制造商互联工厂战略的一部分。

优化应用构成了供应链规划的基础,可以被认为是连接业务战略和运营的。他们帮助组织应对和计划不可预见的事件。例如,如果在生产线上检测到问题,RCA 工具将帮助生产线经理快速缩小可能的原因范围,并实施必要的措施。然而,这有时可能会导致计划外的机器停机时间或重新分配操作员。因此,可能需要用可用的缩减产能重新生成生产计划。

ML 的一些技术可以应用于优化吗,反之亦然?

将从 ML 项目中获得的知识应用于优化是可能的,反之亦然。例如,对于优化输出来说至关重要的目标函数有时不像约束那样被商业数学建模很好地定义,约束是必须遵守的规则,因此通常是众所周知的。举例来说,业务目标可能如下:

a)在遵守交付期限的同时,应按照优先级顺序尽可能早地安排批次

b)时间表应该是前期加载的;尽可能减少差距和资源利用不足

c)批次应分组在一起,以有效利用生产能力

d)对于高价值产品具有较高技能水平的操作员应优先分配这些批次。

其中一些可能是需要适当平衡的竞争优先级,这导致数据科学家编写复杂的影响因素组合(奖励和惩罚),通常是通过试错法,这些因素似乎适用于最常见的规划场景,但有时很难从逻辑上理解,并且在出现缺陷时很难维护。因为优化解算器是第三方产品,其代码对于构建模型以进行调试的数据科学家来说是不可用的,所以不可能看到某些奖励和惩罚在计划生成期间的任何特定点上采取了什么值,这使得它以现在的方式运行,这使得编写令人信服的客观表达式变得很重要。

因此,这有助于采用奖金和惩罚的标准化,这是一种广泛使用的 ML 实践。然后,可以使用配置参数或其他方式,以受控方式缩放标准化值,以控制每个因素的影响、它们如何彼此相关以及它们如何与每个因素内的前一个和后一个值相关。

结论

机器学习和优化都是高度数学化的方法,可以解决组织中和我们日常生活中的不同问题。它们用于部署物理设备、流程或网络的数字双胞胎。虽然这两种类型的应用程序遵循相似的高级开发阶段,但 ML 项目可以利用库中可用的高级自动化和云原生算法,而优化则需要业务和数据科学家的密切合作,以充分模拟复杂的规划流程。一般来说,优化项目需要更多的开发工作,并且是资源密集型的。ML 和优化工具在企业中协同工作,这两种技术对数据科学家学习都很有用。

所有图片,除非特别注明,均为作者所有。

线性不可分数据的降维

原文:https://towardsdatascience.com/dimensionality-reduction-for-linearly-inseparable-data-5030f0dc0f5e

基于核主成分分析的非线性降维

斯蒂夫·约翰森Unsplash 上拍照

标准 PCA 适合于线性维度缩减,因为它在减少数据中的特征数量时进行线性变换。换句话说,标准 PCA 很好地处理线性可分离的数据,其中不同的类别可以通过绘制直线(在 2D 数据的情况下)或超平面(在 3D 和更高维数据的情况下)来清楚地分离。

标准 PCA 对于线性不可分的数据效果不佳,在这些数据中,不同的类别不能通过绘制直线或超平面来清楚地分开,而只能通过使用弯曲的判定边界来分开。

对于非线性降维,我们可以使用标准 PCA 的非线性形式 核 PCA

标准 PCA 和核 PCA 都降低了数据的维数(特征的数量),但是只有核 PCA 可以使数据线性分离,同时仍然降低数据的维数——作者

内核和内核技巧

内核绝招 是将线性不可分的数据转换到数据线性可分的更高维度的过程。这是通过使用内核实现的。内核是一个转换数据的函数。

KenelPCA()中的重要超参数

内核 PCA 是通过使用 Scikit-learn 中的 Kernel PCA()类实现的。使用该类时,用户需要指定 3 个重要的超参数。

  • n_components: 我们想要保留的组件数量。当n_components=None(默认)时,所有组件被保留。阅读这篇文章可以找到更多关于调整这个超参数的信息。
  • 内核:用于 PCA 的内核类型。KernelPCA()类中使用了 4 种类型的核:、【poly】、、【rbf】、、【sigmoid】、。当内核为‘线性’时,将应用标准(线性)PCA。可以通过使用其他内核之一来执行非线性 PCA。‘RBF’(径向基函数)是最常见的一种。默认为‘线性’内核。**
  • gamma: 这就是所谓的‘poly’‘RBF’‘sigmoid’内核的内核系数。浮点值可用于 gamma。默认值为None,它使用由(1/特征数)计算的值。阅读这篇文章以找到关于调整这个超参数的更多信息。

应用主成分分析和核主成分分析的例子

生成非线性数据

为了解释应用 PCA 和内核 PCA 的过程,我们将使用 Scikit-learnmake _ moons函数生成 moons 数据 (线性不可分)。

卫星数据包含两个交错的半圆,可以如下显示。

*import matplotlib.pyplot as plt
plt.figure(figsize=[7, 5])

from sklearn.datasets import make_moons
X, y = make_moons(n_samples=100, noise=None, 
                  random_state=0)

plt.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap='plasma')
plt.title('Linearly inseparable data')
plt.savefig("original_data.png")*

(图片由作者提供)

生成的数据中有 100 个观察值。每个点代表一个观察。数据中有两类。它们由两种不同的颜色(黄色和蓝色)表示。这两个类是线性不可分的。我们不能通过画一条直线来区分这两个阶级。

对线性不可分的数据应用线性 PCA

在对数据应用核 PCA 之前,我们将首先尝试应用线性 PCA 并查看输出。

示例 1: 通过保持 2 个分量来应用线性 PCA

这里,我们通过保留 2 个主成分将线性 PCA 应用于卫星数据。卫星数据的原始维度是二维的。因此,当保持两个(全部)分量时,维数不会降低。

*from sklearn.decomposition import PCA

pca = PCA(n_components=2)
X_pca = pca.fit_transform(X)

plt.figure(figsize=[7, 5])
plt.scatter(X_pca[:, 0], X_pca[:, 1], c=y, s=50, cmap='plasma')
plt.title('First 2 components after linear PCA')
plt.xlabel('PC1')
plt.ylabel('PC2')
plt.savefig("linear_PCA_2_components.png")*

(图片由作者提供)

在通过保留两个分量将线性 PCA 应用于 2 维非线性卫星数据之后,数据的维数没有减少,并且类别仍然不是线性可分的。

示例 2: 通过仅保留一个分量来应用线性 PCA

这里,我们只保留一个主成分,将线性主成分分析应用于卫星数据。卫星数据的原始维度是二维的。所以保留一个分量,维度会减少一半。

*import numpy as np
from sklearn.decomposition import PCA

pca = PCA(n_components=1)
X_pca = pca.fit_transform(X)

plt.figure(figsize=[7, 5])
plt.scatter(X_pca[:, 0], np.zeros((100,1)), c=y, s=50, cmap='plasma')
plt.title('First component after linear PCA')
plt.xlabel('PC1')
plt.savefig("linear_PCA_1_component.png")*

(图片由作者提供)

在通过保留一个分量将线性 PCA 应用于二维非线性卫星数据之后,数据的维数已经减少了一半,但是类别仍然不是线性可分的。

虽然线性 PCA 可以降低线性不可分数据的维数,但这两类仍然是线性不可分的。

对线性不可分数据应用核主成分分析

现在,我们将内核 PCA 应用于数据,并查看输出。

示例 3: 通过保留 2 个分量来应用内核 PCA

这里,我们通过保留 2 个主成分将核 PCA 应用于卫星数据。卫星数据的原始维度是二维的。因此,在应用核主成分分析后,维数不会减少。

*from sklearn.decomposition import KernelPCA

kpca = KernelPCA(n_components=2, kernel='rbf', gamma=15)
X_kpca = kpca.fit_transform(X)

plt.figure(figsize=[7, 5])
plt.scatter(X_kpca[:, 0], X_kpca[:, 1], c=y, s=50, cmap='plasma')
plt.title('First 2 components after kernel PCA')
plt.axvline(x=0.0, linestyle='dashed', color='black', linewidth=1.2)
plt.xlabel('PC1')
plt.ylabel('PC2')
plt.savefig("kernel_PCA_2_components.png")*

(图片由作者提供)

在通过保留两个分量将核 PCA 应用于 2 维非线性卫星数据之后,数据的维数没有减少,但是类现在是线性可分的!*

*幕后:**对线性不可分的数据应用核主成分分析时,核(此处为‘RBF’)函数将原始数据临时投影到一个新的高维特征空间中,在该空间中类变得线性可分。然后,该算法将高维数据投影回二维数据,该二维数据可以绘制成 2D 图,如上述示例所示。这个过程是不可见的。所以,降维过程已经在幕后发生了——来源:2021 年你应该知道的 11 种降维技术(我自己的文章)

示例 4: 通过仅保留一个分量来应用内核 PCA

在这里,我们只保留一个主成分,将核主成分分析应用于卫星数据。卫星数据的原始维度是二维的。因此,应用核主成分分析后,维数将减少一半。

*import numpy as np
from sklearn.decomposition import KernelPCA

kpca = KernelPCA(n_components=1, kernel='rbf', gamma=15)
X_kpca = kpca.fit_transform(X)

plt.figure(figsize=[7, 5])
plt.scatter(X_kpca[:, 0], np.zeros((100,1)), c=y, s=50, cmap='plasma')
plt.axvline(x=0.0, linestyle='dashed', color='black', linewidth=1.2)
plt.title('First component after kernel PCA')
plt.xlabel('PC1')
plt.savefig("kernel_PCA_1_component.png")*

(图片由作者提供)

在通过保留一个分量将核 PCA 应用于 2 维非线性卫星数据之后,数据的维数已经降低,并且类现在是线性可分的!

只有内核 PCA 可以使数据线性分离,同时仍然降低数据的维数!

使用内核 PCA 的限制

  • 用户应将【RBF】【poly】【sigmoid】内核的gamma**值指定为超参数。它需要实现超参数调谐技术,例如 随机搜索网格搜索
  • 与 PCA 不同, n_component 超参数不接受浮点值。因此,我们不能通过指定需要由主成分解释的方差来选择最佳的成分数。

今天的帖子到此结束。

如果您有任何问题或反馈,请告诉我。

人工智能课程怎么样?

*

点击图片进入课程!(作者截图)*

阅读下一篇(强烈推荐)

  • 2021 年你应该知道的 11 种降维技术

*</11-dimensionality-reduction-techniques-you-should-know-in-2021-dcb9500d388b>

  • 降维的 11 种不同用途

</11-different-uses-of-dimensionality-reduction-4325d62b4fa6>

  • 为数据集选择最佳数量的主成分
  • 用 Scikit 学习主成分分析(PCA)
  • 主成分分析—回答了 18 个问题

https://rukshanpramoditha.medium.com/principal-component-analysis-18-questions-answered-4abd72041ccd

支持我当作家

我希望你喜欢阅读这篇文章。如果你愿意支持我成为一名作家,请考虑 注册会员 以获得无限制的媒体访问权限。它只需要每月 5 美元,我会收到你的会员费的一部分。

https://rukshanpramoditha.medium.com/membership

非常感谢你一直以来的支持!下一篇文章再见。祝大家学习愉快!

鲁克山·普拉莫迪塔
2022–12–19*

降维:拥有的越多,看到的越少!

原文:https://towardsdatascience.com/dimensionality-reduction-the-more-you-have-the-less-you-see-ac9dbc546be6

人类向机器学习:第二部分

人工智能和机器学习概念的哲学要点:降维

来源:作者图片

欢迎来到人类向机器学习的第二部分。在这个系列中,我们讨论人工智能中一些有趣概念的哲学相关性。第一部:维度的诅咒;越多不一定越好! ,我们探讨了更多数据如何在计算负担、空间体积、可视化和参数估计方面影响可学性。

在这一部分中,我们将讨论如何处理维数灾难,以及如何应用这些概念来改善我们的福祉。

1.4 处理维数灾难

当我们结束第一部分时,当我们被我们周围的无限数据淹没时,我们会改变我们的视角,从不同的角度看待事物,以区分噪音和相关信息。这本质上就是所谓的降维。

因此,降维是只保留数据的“有趣”特征并去除噪声内容的过程。这使我们能够保留相关信息,同时大大减少存储信息所需的空间。

现在,让我们看看如何实现这一点。

2.降维

降维可以通过几种方法实现。在本文中,我们将讨论一些有助于使用较少的要素来表示数据的方法。有基于上下文的和无监督的降维。

2.1 特征提取

来源:松鼠图片由 Onder Ornel 拍摄,作者修改

这是处理维数灾难最常见的方法之一。在这个过程中,我们试图从现有的特征中提炼出新的特征,这些新的特征可以代表具有更低维度和更高“区分度”能力的数据。这是一种基于上下文的方法,其中来自数据的领域知识和智能模式识别起着重要作用。

根据用例,我们可以减少要智能使用的数据。

例如,当你明天有一场考试,必须在 6 个小时内完成 40 章,因为你几天前才正式“开始”学习,但进入动力阶段需要看 7 部电影,打扫公寓,用荒谬的明确命令整理衣柜,看 YouTube 上关于人们用竹子和粘土建造游泳池的视频,并梦想着你准备充分的场景,最后问上帝为什么我又把一切都拖到最后一分钟?!!!😛

来源:作者图片

目标慢慢的从在学科上打好基础变成了莫名其妙的通过考试!最低限度是我们现在正在寻找的!现在你的选择是

  1. 6 小时端到端学习 40 章,这是最难的选择;我们会希望自己像谢尔顿·库珀迈克·罗斯一样拥有过目不忘的记忆力,并认识到这是不可能的,然后放手
  2. 在网上找一些有 40 章概要的东西,它将给出一个概述。互联网将再次呈现无限资源,需要一段时间来清理路径,开始学习。
  3. 去找那位神级的朋友,他会做最后一分钟的讲座,只包含最重要的部分,并提供关于其他部分的基本背景,这样你至少可以尝试从那部分提问,并得到部分分数。

当然,选项 3 听起来最合适..!!!让我们抽出一点时间来感谢所有在最后一刻演讲并救了我们一命的朋友们。他们是真正的无名英雄!😄

因此,您会注意到,最初的数据是 40 个章节,需要进行端到端的处理,然后缩减为一个摘要,只关注一些相关的部分。在这里,有一个已经处理过数据的乐于助人的朋友为我们减少了数据。

与这一现实场景类似,我们也可以应用逻辑来实现这一缩减。例如,在面部识别中,我们输入一个人的图像作为输入,并使用模型将整个图像简化为仅存储相关信息,如眼睛、鼻子等的位置。,这大大减少了要存储和比较的信息。

因此,使用直观或逻辑过程,特征提取集中于对手边的问题最重要的几个特征。

类似地,当我们被想法淹没时,我们可以通过专注于我们能控制的事情并忽略我们不能控制的事情来进行特征提取,80%的噪音立即被清除。

拥抱不确定性,专注于我们能控制的事情,是在不被压垮的情况下完成事情的方法。

现在让我们来看看最流行的降维方法之一,称为主成分分析(PCA)。

2.2 主成分分析:视角改变的力量

在之前的特征提取中,我们看到了如何过滤特征并组合多个特征来降低维数。如果我们获得的数据是全新的,并且我们没有太多上下文来进行智能特征提取,该怎么办?下面是解决这个问题的数学方法,称为 PCA。

PCA 从数据中确定主要的变化模式。该算法将坐标系与数据的形状相匹配,然后将数据投影到这个“自然”坐标系中。协方差方差的概念用于理解每个属性的重要性及其与其他属性的关系。

因此,在 PCA 中,我们消除了不太“有趣”的特征,即具有低方差的特征,然后仅使用“有趣”的特征,即具有高方差的特征来表示数据。新的特征将被表现出来,这样它们就彼此独立了。因此,命名为“主要组件”。下图显示了 PCA 的示例。

来源:作者图像

图中左图为二维随机数据,右图为二维随机数据的 PCA 空间投影。请注意,相同的数据在这里用正交轴表示。黑线表示存在最大差异的轴。请注意,在 PCA 后的图像中,我们可以看到 PC1 轴的值在-0.6 和 0.8 之间变化,而 PC2 轴的值在-0.2 和 0.2 之间变化。因此,我们可以看到,PC1 维度的方差非常高,PC2 维度的方差较低。因此,对于某些问题,我们可以决定只使用 PC1 维度。

PCA 的技术解释有点数学化,超出了本文的范围。根据上一篇文章的反馈,我将减轻技术负担,保持轻松阅读。伙计们,让我们放松并阅读!😄

如果你有足够的动力去钻研主成分分析的一些数学知识,你可以阅读这篇由 Matt Brems 撰写的解释清晰的文章

因此,当呈现无限维的数据时,会有很多比信息更混乱的信息。当你过于专注于某件事太久,它总是模糊的。

所以最好的选择是休息一下,放松一下,换个角度看问题。当你冷静下来,没有偏见的假设时,你可以看到相关的信息。

所以,让我们试着找出我们生活中的“主要”组成部分,试着把我们的时间和精力花在那里,而不要迷失在这个充满维度的世界里!😛

非常感谢您花费宝贵的时间。在下一集《人类向机器学习》中,我们将探索学习的基本类型,以及我们如何在不同的情况下运用它们来实现最佳成长。请继续关注人类向机器学习:第三部分,

监督和非监督学习;生命的阴阳!

10 月上映!(还是那句话,慢作家。为延迟道歉:P)

请在 LinkedIn 或通过电子邮件告诉我您的宝贵建议和反馈。

鸣谢:降维的技术方面我是从伯明翰大学计算机科学学院的彼得·蒂诺博士、伊恩·斯泰尔斯博士和卡希夫·拉吉普特博士的讲座中学到的。花一点时间来表达我对他们无价服务的感激之情!😄

一如既往,

快乐学习:D

Python 中基于主成分分析和 t-SNE 的降维方法

原文:https://towardsdatascience.com/dimensionality-reduction-with-pca-and-t-sne-in-python-c80c680221d

数据科学学会研讨会 18:什么是降维、PCA 实现、t-SNE 实现

阿什利·朱利斯在 Unsplash 上拍摄的照片

今年,作为 UCL 数据科学协会的科学负责人,该协会将在整个学年举办一系列 18 场研讨会,主题包括数据科学家工具包 Python 简介和机器学习方法。每个人的目标是创建一系列的小博客文章,这些文章将概述主要观点,并为任何希望跟进的人提供完整研讨会的链接。所有这些都可以在我们的 GitHub 资源库中找到,并将在全年更新新的研讨会和挑战。

本系列的第十八个研讨会是 Python 数据科学研讨会系列的一部分,涵盖了降维方法。在本次研讨会中,我们将讨论什么是降维以及主成分分析和 t 分布随机邻居嵌入方法的实施。和往常一样,这篇博文是整个研讨会的总结,可以在这里找到这里还包括使用随机森林分类的数据准备和特征提取。

如果你错过了我们最近三次研讨会中的任何一次,你可以在这里找到这些

什么是降维?

降维(在大多数情况下)属于无监督的机器学习算法,这意味着我们没有特定的目标。该方法的主要目的是减少数据集中的要素数量,从而减少模型所需的资源,或者在执行任何分析之前帮助可视化数据。这是通过减少数据集中属性或变量的数量,同时尽可能多地保留原始数据集中的变化来实现的。这是一个预处理步骤,意味着它主要在我们创建或训练任何模型之前执行,将它与通常在初始建模之后完成的特征提取分开。

有许多算法可用于降维,但它们主要来自两组主要的线性代数和流形学习:

线性代数

这是从矩阵分解方法中得出的,矩阵分解方法可以通过检查我们可能使用的变量之间的线性关系来进行降维。这一分支中的常用方法包括:

  • 主成分分析
  • 奇异值分解
  • 非负矩阵分解
  • 要素分析
  • 线性判别分析

流形学习

这与线性代数方法的不同之处在于,它使用非线性方法来降维,因此与线性代数方法相比,它通常可以捕获变量之间更复杂的关系。这一分支中一些流行的方法包括:

  • Isomap 嵌入
  • 局部线性嵌入
  • 多维标度
  • 光谱嵌入
  • t 分布随机邻居嵌入

特征提取

这组方法可以松散地归入降维的范畴,因为尽管上述方法从现有变量中创建新的变量组合,但特征提取只是移除特征。我们已经从随机森林中看到了这一点,但是在这方面流行的方法包括:

  • 反向消除
  • 预选
  • 随机森林

降维体系下的每种算法都提供了不同的方法来应对降维的挑战。这通常意味着不存在适用于所有情况的最佳降维方法。这也意味着,如果不使用受控实验和广泛探索,就没有简单的方法来为您的数据找到最佳算法。在这种程度上,我们将包括在 NBA 数据集上实现 PCA 和 t-SNE,目的是可视化关于球员位置的二维数据。

主成分分析

PCA 只是降维的线性代数方法之一。这有助于我们从现有的大量变量中提取一组新的变量,这些新变量采用主成分的形式。这样做的目的是用最少的主成分获取尽可能多的信息。在最小化信息损失的同时获得的变量越少,在计算资源和时间方面有助于模型训练,并且有助于可视化。

其中,主成分是变量的线性组合,其组织方式是第一主成分解释数据集中的最大方差。然后,第二个主成分试图解释数据集中与第一个主成分不相关的剩余方差,以此类推。

为了实现 PCA 算法,我们需要归一化数据,因为否则它将导致对具有大方差的变量的大量关注,这是不期望的。这意味着我们不应该将 PCA 应用于分类变量,因为尽管我们可以将它们转化为数值变量,但它们只会取 0 和 1 的值,这自然会有很高的方差。

为此,在实现模型之前,我们将使用来自scikit-learn库的StandardScaler来缩放数据:

#import the standard scaler
from sklearn.preprocessing import StandardScaler#initialise the standard scaler
sc = StandardScaler()#create a copy of the original dataset
X_rs = X.copy()#fit transform all of our data
for c in X_rs.columns:
    X_rs[c] = sc.fit_transform(X_rs[c].values.reshape(-1,1))

然后,我们可以对标准化数据实施模型,如下所示:

#import the PCA algorithm from sklearn
from sklearn.decomposition import PCA#run it with 15 components
pca = PCA(n_components=15, whiten=True)#fit it to our data
pca.fit(X_rs)#extract the explained variance
explained_variance = pca.explained_variance_ratio_
singular_values = pca.singular_values_

既然我们已经使 PCA 适合我们的数据,我们实际上需要评估它产生的成分。我们可以通过解释方差比来做到这一点,方差比可用于查看主成分的有用程度,从而选择要在模型中使用的主成分。我们可以把这想象成:

#create an x for each component
x = np.arange(1,len(explained_variance)+1)#plot the results
plt.plot(x, explained_variance)#add a y label
plt.ylabel('Share of Variance Explained')
plt.title("PCA explained variance plot")
plt.xlabel("Components")#show the resuling plot
plt.show()

#iterate over the components
#to print the explained variance
for i in range(0, 15):
    print(f"Component {i:>2} accounts for {explained_variance[i]*100:>2.2f}% of variance")

如果我们要在模型中使用这些数据,那么我们需要决定在模型中使用多少组件来平衡计算资源和模型性能之间的平衡。为此,我们可以使用几种方法来选择主成分的最佳数量:

  • 检查解释的方差图中的拐点,在我们的例子中,它出现在 4- 6 个主要成分周围
  • 保留占数据集中方差 1%以上的分量,在我们的例子中是在 14 个分量之后
  • 在模型中保留总计占解释方差 80%的变量,在我们的例子中是前 7 个组成部分。

这可能取决于主成分分析的目的和您正在实施的模型。在我们的例子中,我们想尝试在二维空间中可视化数据,以及这与我们的目标变量的关系。因此,我们可以这样实现:

#set the components to 2
pca = PCA(n_components=2, whiten=True) 
#fit the model to our data and extract the results
X_pca = pca.fit_transform(X_rs)#create a dataframe from the dataset
df = pd.DataFrame(data = X_pca,
                 columns = ["Component 1", 
                            "Component 2"])#merge this with the NBA data
NBA = pd.merge(NBA,
              df,
              left_index=True,
              right_index=True,
              how = "inner")#plot the resulting data from two dimensions
g = sns.jointplot(data = NBA,
                 x = "Component 1",
                 y = "Component 2",
                 hue = "Pos")

作者图片

从这个图中我们可以看到,将数据减少到两个分量并不一定表明玩家的位置和他们的统计数据之间的明确关系。我们可以从中得出的结论是,中锋的位置可能与其他位置不同,有几个位置与大前锋的位置相融合。而与此同时,控卫、得分后卫和小前锋之间存在着相当大的混合,这在某种程度上是可以预料的。

当然,您是否希望这是两个或更多的主成分(14 个成分的方差大于 1%)将取决于您最终想要对数据做什么。在早期的随机森林分类器研讨会的情况下,我们可能希望在模型中使用 14 个组件,而不是我们最初开始时的 23 个,因为其余的可能是噪声,只会增加模型的复杂性。

t-SNE

t-SNE 是另一种降维算法,但与 PCA 不同,它能够解释非线性关系。在这个意义上,数据点可以通过两种主要方式映射到较低的维度:

  • 局部方法:将较高维度上的邻近点映射到较低维度上的邻近点
  • 全局方法:试图在所有比例下保持几何形状,使附近的点靠得很近,同时使远处的点彼此远离

t-SNE 是为数不多的能够通过计算点在高维度和低维度空间的概率相似性在低维度数据中保留两种结构的降维算法之一。

t-SNE 的输出通常被称为用数据创建的维度,其中固有特征在数据中不再可识别。这意味着我们不能仅基于 t-SNE 的输出做出任何直接的推断,将其限制于主要的数据探索和可视化,尽管输出也可以用于分类和聚类(尽管不能用于测试和训练数据集)。

实施 t-SNE 算法时,我们需要注意几个关键参数:

  • n_components:从数据中创建的尺寸
  • perplexity:与其他流形学习算法中使用的最近邻数相关,用于确定要保留的模型中全局和局部关系的权衡。虽然注意到 t-SNE 通常对此参数不太敏感,但它应该小于建议的最佳范围在 5 到 50 之间的点数
  • learning_rate:学习率通常是模型行为的关键参数,因此在这方面探索参数空间是值得的,但它通常在 100 到 1000 之间。

scikit-learn 文档建议,由于算法的复杂性,如果数据集中的特征数量超过 50,则在 t-SNE 之前使用 PCA 或截断 SVD。然而,在我们的例子中,这不应该是一个问题,所以我们可以在我们已经有的数据上实现这个模型。因为我们的目标主要是观想,那么我们可以将维度的数量设置为两个,以便能够在 2D 观想图上绘制如下:

#import the method
from sklearn.manifold import TSNE#set the hyperparmateres
keep_dims = 2
lrn_rate = 700
prp = 40#extract the data as a cop
tsnedf = X_rs.copy()#creae the model
tsne = TSNE(n_components = keep_dims, 
            perplexity = prp, 
            random_state = 42,
            n_iter = 5000,
            n_jobs = -1)#apply it to the data
X_dimensions = tsne.fit_transform(tsnedf)
#check the shape
X_dimensions.shape#out:
(530, 2)

我们可以把它想象成:

#create a dataframe from the dataset
tsnedf_res = pd.DataFrame(data = X_dimensions,
                         columns = ["Dimension 1", 
                                "Dimension 2"])#merge this with the NBA data
NBA = pd.merge(NBA,
              tsnedf_res,
              left_index=True,
              right_index=True,
              how = "inner")#plot the result
g = sns.jointplot(data = NBA,
                 x = "Dimension 1",
                 y = "Dimension 2",
                 hue = "Pos")

作者图片

我们在这里可以看到,在某种程度上,我们比之前使用 PCA 得到了更清晰的不同位置的图像。我们现在可以看到,中锋的位置显得更加明显,得分后卫的分布非常广泛,而控卫则倾向于集中在一起,小前锋和大前锋也一起出现。

这使得它稍微清晰一些,并表明变量之间的关系可能确实是非线性的,因此 t-SNE 可能在这个数据集上更好。当然,在思考这个问题时,你必须注意你想用维度或主要成分做什么。

如果您想了解我们协会的更多信息,请随时关注我们的社交网站:

https://www.facebook.com/ucldata

insta gram:https://www.instagram.com/ucl.datasci/

领英:https://www.linkedin.com/company/ucldata/

如果你想了解 UCL 数据科学协会和其他优秀作者的最新信息,请使用我下面的推荐代码注册 medium。

https://philip-wilkinson.medium.com/membership

或者查看我在 Medium 上的其他文章

DINO-ViT —超越自我监督的分类

原文:https://towardsdatascience.com/dino-vit-beyond-self-supervised-classifications-3f5d43178216

在没有监督的情况下提取精细的特征

图一。自我监督学习是实现真正人工智能的重要一步。从 Unsplash 检索的图像。

以前,我写过几篇文章简要讨论自我监督学习,特别是对比学习。然而,还没有包括的是一个并行的自我监督方法的分支,它使用了最近出现并表现出色的多个网络的交互作用。迄今为止,最先进的培训方法之一是一种主要的知识提取方法,名为 DINO,应用于视觉变形金刚(DINO-ViT)。然而,这种架构最令人惊讶的元素不再是其强大的分类知识,而是其密集的功能,这些功能实际上能够执行更细粒度的任务,如部分分割,甚至跨多个对象的对应。

在本文中,我们将回顾 DINO-ViT 是如何被训练的,然后是一个简短的教程,介绍如何利用现有的库进行零件共分割和寻找对应关系。

迪诺维特是什么?

恐龙这个词来自于没有监督的自我升华。顾名思义,DINO-ViT 利用传统知识提炼方法的变体,将其应用于强大的视觉转换器(ViT) 架构。这个想法多少受到了技术 引导你自己的潜能(BYOL) 的启发,我们将在接下来的文章中更全面地介绍这个技术。

知识提炼是如何工作的?

图二。恐龙训练方法概述。从https://arxiv.org/abs/2104.14294检索的图像。

简单地说,知识提炼的目标是允许学生网络 Ps 向教师网络 Pt 学习。从计算机视觉的角度来看,目标可以转化为更新学生网络,以最小化给定图像 x:

自我监督的知识蒸馏

下面描述了蒸馏法是如何扩展到恐龙训练的:

给定特定的图像 x,我们现在将该图像裁剪成两个全局视图和多个局部视图。所有作物(全局和本地)都被输入到学生网络中,但只有全局视图被输入到教师网络中。然后,我们按照知识提取方法最小化交叉熵。为了实现这一目标,训练学习在图像内创建局部和全局对应,从而鼓励学生网络学习适当的图像特征。

从学生那里得到老师

与知识提炼的原始问题设置不同,其中教师网络的权重是先验给定的,DINO 没有经过预训练的教师。因此,教师网络实际上是通过移动平均方法从以前的学生中检索的。执行额外的居中以避免两个模型塌陷。

分类、细分等等

图二。从 https://arxiv.org/abs/2104.14294取回的迪诺-维特图像注意图

分类一直是所有自我监督方法的标准基准,DINO-ViT 无疑是领先的得分者之一。然而,DINO-ViT 的迷人之处在于其通过自蒸馏获得的精细致密特性。图 2 说明了在没有任何监督的情况下,迪诺-维特的注意力是如何令人惊讶地集中在物体的前景上的。这意味着高分类精度实际上仅仅是能做更多事情的表示的结果。

下一步是什么?

图 3。使用 DINO-ViT 特征的零件共同分割结果。图片来自 https://arxiv.org/abs/2112.05814。

在 DINO-ViT 之后是一篇名为 的论文,深度 ViT 特征作为密集的视觉描述符 。Amir 等人在这项工作中描述了 DINO-ViT 的密集分片特征,以及额外的聚类和简单的无监督方法,可以执行非常具有挑战性的任务,例如部分共分割和对应查找(如图 3 所示)。事实上,这些特征如此强大,以至于它可以在不同类别的图像中找到对应关系。

使用密集特征

Amir 等人在他们的项目页面上为有兴趣的个人发布了现成的 Google Colab 笔记本。您也可以按照 GitHub 的说明安装软件包,并批量运行这些方法。

该代码的链接可在此处找到:

https://github.com/ShirAmir/dino-vit-features

结束注释

创造真正的人工智能的漫长旅程似乎是无穷无尽的,但自我监督学习无疑是朝着它迈出的一大步。迪诺-维特展示了对网络如何学习的见解,这些学习特征背后的巨大潜力确实将成为计算机视觉领域的重要垫脚石。

感谢您坚持到现在🙏 我定期写关于计算机视觉/深度学习的不同领域,所以 加入并订阅 如果你有兴趣了解更多!

随着批量增加,CNN 训练时间不连续

原文:https://towardsdatascience.com/discontinuity-in-cnn-training-time-with-increase-batch-size-bd2849129283

致谢:&【禺期】李 @ 艾帕卡公司

照片由艾米丽·莫特Unsplash 上拍摄

背景

随着新的机器学习模型的规模越来越大,模型训练时间分析是当今的重要课题之一。除了像 GPTs 这样的超级巨型模型,计算机视觉模型对于像数据科学家和研究人员这样的普通最终用户来说训练起来很慢。根据任务和数据,计算机视觉模型的训练时间可能从几个小时到几周不等。

在本文中,我们讨论了我们对模型训练时间的研究中的一个有趣的发现。为了明确我们所说的训练时间的含义,我们想知道一个 GPU 配置为一批数据训练一个模型需要多长时间。显然,它取决于许多变量,如模型结构、优化器、批量大小等。然而,给定关于配置和模型设置的足够知识,并且如果一批的训练时间是已知的,我们能够计算一个时期的训练时间,因此也能够计算给定时期数目的总训练时间。

当我们获得 CNN 模型的训练时间数据时,我们固定了模型结构、输入和输出大小、优化器和损失函数,但是没有固定批量大小。换句话说,我们想知道在其他条件不变的情况下,增加批量是如何影响训练时间的。

在我们这样做之前,我们确定的是批量大小和批量训练时间之间存在正相关。我们不确定的是,这是线性关系还是非线性关系?如果是线性的,斜率是多少?如果是非线性的,是二次还是三次关系?带着这些问题,我们做了实验,观察到了一些我们没有想到的东西。

实验

我们在特斯拉 T4 云实例上运行 TensorFlow VGG16 ,使用默认输入形状(224,224,3)、优化器和损失函数的 CategoricalCrossentropy。我们将批量从 1 增加到 70。实验结果如下所示,x 轴是批量大小,y 轴显示相应的批量训练时间。有趣的是,我们的预期是部分正确的,我们确实观察到批量大小和批量训练时间之间的正线性关系。然而,在批量大小等于 16、32、48 和 64 时,我们观察到批量训练时间的“跳跃”。

作者制作的图像

这是特斯拉 T4 上 VGG16 的批量大小对批量时间的图,我们观察到关系的总体斜率几乎没有变化,这意味着很可能证实了线性关系。然而,在特定的批量,特别是 16、32、48 和 64,线性关系破裂,并且在这些位置发生不连续。

我们可以肯定地说,值 16、32、48 和 64 不会随机出现,它们是 16 的倍数,恰好与 GPU 的 PCIe 链接最大宽度 16x 的值相同。PCIe 是 PCI Express 的缩写,引自 wiki“PCI Express 电气接口是通过同时通道的数量来衡量的。(通道是数据的单个发送/接收行。这个比喻就是一条双向交通的高速公路。)".简而言之,PCIe 越宽,同时传输的数据流量就越多。

我们对培训过程的假设如下。在 VGG16 的训练周期中,对于每个批量训练步骤,批量中的每个数据点被分配使用一个 PCIe 通道,如果批量小于或等于 16,则不需要额外的轮次,来自每个 PCIe 通道的结果被组合,因此我们具有线性关系。当批大小大于 16 但小于 32 时,需要另一轮来计算整个批,这由于新一轮的分配而导致训练时间的“跳跃”(我们假设新一轮需要一些额外的时间来导致曲线的移动或“跳跃”)。

不同 GPU 上的轨迹

为了验证我们对上述实验的观察,我们进行了相同的实验,但在不同的 GPU 设置上,下图显示了特斯拉 K80、特斯拉 P100 和特斯拉 V100 的结果。

从图中我们可以看出,第一,V100 的速度> P100 > T4 > K80,因为对于相同的批量,V100 的批量时间< P100 < T4 < K80。其次,他们在 16 岁、32 岁、48 岁和 64 岁都有“跳跃”。对于所有四个 GPU,它们都具有 16 倍的 PCIe 链接最大宽度。(我们想要比较 PCIe 链接最大宽度不是 16 倍的 GPU 的结果,但是,我们在谷歌上可以找到的所有 GPU 云实例都是 PCIe 链接最大宽度 16 倍的)。

不同模型结构的试验

为了测试我们在不同模型上的发现,我们在 V100 上对 VGG19 MobileNet 和 ResNet50 运行了相同的实验。

结果很有趣。对于 VGG19,我们仍然可以找到与 VGG16 完全相同的模式,但是预期的训练时间稍长。然而,对于 MobileNet 和 ResNet50,我们不再观察到这种模式。事实上,与 VGGs 相比,MobileNet 和 ResNet50 的训练时间波动更大。

对于这种现象,我们还没有一个好的解释。我们现在可以说的是,对于类似于 VGGs 的常规 CNN 结构,这种“跳跃”行为是成立的。对于其他不同的 CNN 结构,我们不再观察。进一步的调查和研究正在进行中。

作者制作的图像

结束语

这项研究来自一个名为 培训成本计算器 (TCC)的开源研究项目。项目目标是通过产生一个巨大的 ML 实验数据库来理解影响机器学习训练时间(TT)的因素。基于数据库,TCC 能够预测不同云服务器上培训作业的 TT,从而为您的特定 ML 模型匹配最佳服务器。如果你对这个领域感兴趣,请加入我们成为贡献者。

在这篇文章中,我们展示了类似 VGG 的 CNN 模型的“跳跃”现象。我们的解释是 PCIe 车道分配导致了这一点。

但我们目前的解释仍然存在问题:为什么我们没有从我们的解释中看到双倍的训练时间,因为如果批量大小为 32,GPU 需要进行两轮相同的计算。以及为什么这种情况只发生在 VGG16 和 VGG19 上,而没有发生在 MobileNet 和 ResNets 上。这些问题需要进一步的调查和研究。

代码复制实验

在 TDS 上发现新的激动人心的声音

原文:https://towardsdatascience.com/discover-new-and-exciting-voices-on-tds-9a0e3ca15d4b

没有什么比将读者与 TDS 新作者的作品联系起来更让我们激动的了——我们的团队每周都会审查几十篇文章,以找到最好的文章与您分享。今天,我们庆祝我们最新的一批贡献者:本期《变量》中的文章都是由我们在过去几周欢迎的数据科学作家撰写的。它们都值得你花时间。

你会在这里找到的推荐读物涵盖了各种各样的主题、方法和观点,其背后的人来自各种各样的背景和职业道路。在这个过程中,你一定会学到一些新东西。(如果浏览这些帖子激励你加入我们快乐的作者社区,我们希望收到你的来信。)

Unsplash 上由 Lasse Nystedt 拍摄的照片

  • 用数据支撑精神卫生服务 。对于公共卫生部门的数据科学家和分析师来说,寻找和理解可用数据可能是一场艰苦的战斗。Sebastien Peytrignet 最近分享了一份使用 NSH 精神健康数据的实用指南,即使你不在英国,你也会发现它很有用。
  • 贝叶斯分类器的变种,解包 。如果你只是在学习如何使用经典的分类方法(大声说出来:很有趣!),如果你发现区分高斯朴素贝叶斯(GNB)、线性判别分析(LDA)和二次判别分析(QDA)有点令人困惑,没有人会反对你。Francesca Argenziano 的清晰讲解将带你走上正确的道路。
  • 熟悉匿名化 。处理个人身份信息(PII)和应对日益复杂的监管环境已经成为许多数据从业者的必备技能。凌镇·陈写了一篇关于这个主题的很有帮助的介绍,汇集了理论、最佳实践和推荐的资源。
  • 跨专业应用合适的课程 。凯特琳·雷的职业生涯跨越了数据科学和机器学习工程,这两个领域之间的转换让她对解决问题、讲故事和简单性的重要性有了一些深刻的见解。
  • 深挖二分图 。作为一名训练有素的考古学家,詹姆斯·斯科特红衣主教亲身经历了解决挖掘现场“大拼图”的挑战。阅读 James 的第一篇 TDS 文章,了解数据科学方法如何对一个看似遥远的学科产生巨大影响。
  • 一种通达因果关系的方法。回答因果问题和提高推理效率是数据专业人员的两项基本技能。 Eden Zohar 探索了反向倾向加权(IPW)作为确定治疗和结果之间关系的一种强有力的方法。

一如既往地感谢您对我们作者工作的支持和花费的时间。如果你想以其他有意义的方式表达你的支持,可以考虑成为一名中级会员

直到下一个变量,

TDS 编辑

发现强大的数据:NLP 的金矿

原文:https://towardsdatascience.com/discovering-powerful-data-the-guardian-news-api-into-python-for-nlp-1829b568fb0f

使用免费的卫报新闻应用编程接口解锁过多的标记数据

由 www.freepik.com 弗里皮克设计

想想看,寻找带标签的数据令人头痛,任何曾经解决过 NLP 问题的人都知道这一点。但是哪个行业需要大量的写作,然后在他们的网站上贴上漂亮的标签呢?没错;新闻文章。所以让我们利用这一点。《卫报》(新闻机构)有一个非常强大的免费 API——每天有 5000 个电话和近 200 万条内容。他们所有的内容都有自己的标签:环境、金钱、环境/能源等。有人已经为我们做了所有的艰苦工作。

请求访问 API,您就有了 NLP 的大量数据。这些标签是完美的标签。这使得它非常适合 NLP 分类训练。它可能不仅仅是为了 NLP,而是任何种类的分析,比如:容量分析,一袋单词分析等等。

首先,在这里请求访问:https://openplatform.theguardian.com/access/

在本文中,我们将介绍:

1)将数据拉入 Python

2)在一个函数中拉入 200 多个结果

3)拉入多个标签

4)端到端代码。

5)数据的潜在用途

鉴于能源危机,我们将探索寻找与能源有关的新闻文章的用例。即能源行业标签。

如果您在获取自己的数据时遇到困难,那么整篇文章中有功能完整的代码片段,只要您用自己的 API 键替换“您的 API 键”就可以了。

将数据拉入 Python

在拉入数据之前,请确保从https://open-platform.theguardian.com/access/注册并获取您的 API 密钥。

正如在文档中看到的,它有我们可以用于请求包的典型 API 调用方法,在我们的例子中,我们使用 GET 方法并将变量放入 URL 的字符串中。由于我们专注于标签/标注,以利用《卫报》对每篇文章进行人工标注的工作,我们的示例将是按标签查询。但是,也可以通过搜索关键字进行查询。

因为我们将通过标记进行查询,所以我们需要首先找出存在哪些标记,根据 API,我们可以使用 get 方法以 JSON 文件的形式获得一个标记列表。请记住,《卫报》有数百个标签,所以最好通过搜索您想要的标签来筛选。

下面我们搜索与 能量有关的标签。

注意:用您的 API 密钥替换您的 API 密钥

tag_json = requests.get(‘https://content.guardianapis.com/tags?q=energy&page-size=200&api-key=YOUR API KEY).json()

返回的是一个 JSON,其中包含所有带有关键字 energy 的标签。所以我只是从列表中选了一些我感兴趣的放入一个列表中。下面是我挑的几个。

tag_list = ['environment/energy', 'money/energy', 'technology/energy', 'science/energy', 'environment/energy-storage',
           'business/energy-industry', 'environment/energy-monitoring', 'environment/renewableenergy',
           'sustainable-business/hubs-energy-efficiency']

让我们看看列表中的第一个标签,我将查询文章数据中的特定标签“环境/能源”。我把最大页面大小设置为 200,这似乎是每次调用所能达到的最大值。

注意:API 调用中的 Page 表示页码,例如,如果我在一个页面上放置 200,放置 1 作为页码将提取第一个 200,放置 2 将提取第二个 200

问题是,您一次只能查询大约 200 个,所以让我们讨论一下如何绕过它。

在一个函数中拉入超过 200 行(限制)

为此,我们可以简单地编写能够处理 for 循环的函数。幸运的是,我们知道有多少结果,所以我们可以计划我们需要多少数据。如果我们每页有 200 行,那么我们可以写一个 If 语句和 for 循环来说,如果有超过 200 行要获取,那么做一个 for 循环来获取每页。

我们可以创建 3 个函数:

1)查询一个响应

2)检查存在多少数据并调用更多页面。将所有内容放在一个列表中

3)将 json 响应列表转换为数据帧

有了这三个功能,您现在可以根据自己的需要调用任意多的文章,只要每天调用次数不超过 5,000 次,即 1,000,000 篇文章!

拉入多个标签

最后一个功能是如果我们想拉进多个标签。之前我创建了一个我感兴趣的标签列表。谜题的最后一部分是在这些标签上运行 for 循环。

端到端代码

如果你愿意的话,下面是可以复制粘贴的端到端代码:

不要忘记更改您的 API 密钥。

样本输出:

潜在的未来用途

Tableau 仪表板监控新闻

为了掌握能源行业的最新消息,我创建了一个 Tableau 仪表板来查看一段时间内的新闻量,如果我点击一个条形,下面会输出一个文章列表。

NLP 培训(使用 Google AutoML)

我之前说过,这是使用这种标记来训练 NLP 模型的完美数据。您可以获得带有特定标签的数据集,并使用它们来训练您的模型。我推荐使用 Google AutoML,你可以在这里找到的教程。或者,继续使用 Python 和 Scikit learn。

我希望这有所帮助——愉快的数据提取。

如果你喜欢这篇文章,请留下掌声和关注支持!

或者,如果您有兴趣加入 Medium 社区,这里有一个推荐链接:

https://medium.com/@adrian.causby15/membership [## 通过我的推荐链接加入 Medium-Adrian caus by

medium.com](https://medium.com/@adrian.causby15/membership)

通过合著网络发现研究合作

原文:https://towardsdatascience.com/discovering-research-collaborations-through-co-authorship-networks-fd4930682bae

数据可以告诉你很多,前提是你知道去哪里找,怎么找。

AuthorRank 可以帮助发现和解释跨学术文本、跨大型语料库或过滤结果(如搜索结果)的协作。了解 AuthorRank——在开源 Python 实现中可用此处——如何帮助量化学术合作的强度,并发现沟通研究团队的关键人物。

为说明目的而创建的合作作者网络示例。位于协作网络更中心的作者获得更高的权重,由他们的节点的大小来指示。在这种视觉效果中,作者之间线条的粗细表明了基于合作关系的个人关系强度。来源: AuthorRank Python 包(由瓦伦蒂诺·康斯坦蒂努创建,发布在链接库中)。

来自协作网络的见解

数据可以告诉你很多,前提是你知道去哪里找,怎么找。

当利用适当的数据处理和自然语言处理(NLP)技术时,文本数据(自然语言)的语料库可以提供丰富的信息:数据中包含的主题及其随时间的分布,特定位置或个人被提及的频率,甚至语言结构如何随时间变化。总的来说,这些技术有助于提供有价值的见解,可以用来理解以前的工作或为未来的决策提供信息。

当想要理解大量的研究工作(学术论文)时,更好地理解论文作者是如何合作的会很有帮助。合作是科学和研究工作的核心,有效的跨学科合作往往能够产生变革性的成就。此外,了解哪些作者在一部作品中最有影响力可能有助于确定主题专业知识。

在本文中,我们探索并应用了刘等人提出的作者排名方法。艾尔。到一系列学术论文,特别是公开的出版物,其作者利用来自微波肢体探测器(MLS) 实验的数据,这是一个由加州帕萨迪纳喷气推进实验室管理的美国宇航局地球科学项目。这些学术论文都以某种方式与来自 MLS 实验的数据相关,但都集中在各种不同的研究领域。

以下摘自 AuthorRank Github 知识库的摘录深入探讨了 AuthorRank 的工作方式:

AuthorRank 方法是对 PageRank 的一种改进,page rank 是谷歌对网页搜索结果进行排名的原始算法。PageRank 的工作原理是转移状态。页面的排名是其反向链接排名的总和——如果一个网页有许多反向链接或几个排名高的反向链接,其排名也高。该算法适用于有向图,其中节点是网页,有向边表示从一个页面到另一个页面的链接。假设每个节点将其等级均匀地转移到它所连接的所有其他节点。

AuthorRank 创建了一个合作作者网络,代表科学合作的结构和单个研究人员的状态,而不是网页。在网络中,每个节点代表一个作者,每个边代表一个协作。边是双向的,表示协作的对称性质。与假定每个节点平等地转移状态的 PageRank 不同,当考虑协作中的状态时,应该给予经常一起合著的作者更高的状态,并且随着论文中作者数量的增加,应该降低状态。因此,根据下图所示的合著频率和文章的合著者总数,对边进行加权。

让我们看看哪些作者影响最大。

关于数据的一点点

从 1991 年首次发射高层大气研究卫星(UARS)开始,到 2004 年发射地球观测系统(EOS) Aura 任务,MLS 实验测量地球大气层边缘自然发生的微波热辐射,遥感特定大气气体、温度、压力和云冰的垂直剖面。

MLS 实验有助于理解地球大气层如何随时间演变。大气科学研究人员,更一般地说,地球科学研究人员经常利用 MLS 数据来更好地了解我们星球对来自太阳的致命紫外线辐射的唯一保护。

如果你感兴趣,可以从美国国家航空航天局的地球数据门户网站下载 MLS 的数据。

我们今天要检查的数据是 PDF 文档的语料库,特别是引用 MLS 数据的出版物(就像上面的例子)。PDF 文档已被下载,并由名为 PDFMiner 的软件预处理成机器可读的文本,元数据如作者列表和出版日期由 CrossRef API 提供(使用论文的数字对象标识符或 DOI 进行查询)。我们在这里只使用作者列表和文本数据,但是数据集——在这里可用及其所有特性——也可以以其他方式使用。

使用 AuthorRank Python 包

AuthorRank 算法是用 Python 3 实现的,带有一个同名的 Python 包。这个易于使用且灵活的 Python 包是我们将用于将 AuthorRank 方法应用于我们的文档语料库(利用 MLS 数据的学术论文)的工具。

在开始之前,建立一个虚拟环境是个好主意。如果你对虚拟环境不熟悉,看一下 Python 语言文档的这一部分以了解更多关于如何和为什么使用它们的信息。

AuthorRank Python 包可以从 Python 包索引 PyPI:

pip install author-rank

检查以确保 AuthorRank 已安装非常简单:

# import AuthorRank
import author_rank as ar# check version 
print(ar.__version__)

发现研究小组和关键链接

让我们深入研究这些数据。我们将从使用 Python 的json模块加载数据开始:

import json# read in the MLS data
with open("microwave_limb_sounder.json", 'r') as f:
    # read in docs that have authors
    docs = [json.loads(d) for d in f.readlines()]
    docs = [d["_source"] for d in docs if "author" in     d["_source"].keys()]

上面的代码首先导入json模块,然后读入 JSON 文件的每一行(每一行代表一个文档)。然后,每份文件都被用json.loads()转换成一本字典。最后,只保留列出了作者的文档进行分析。

总体分析数据肯定是有逻辑的,尤其是对于较小的数据集,比如文档数为数百甚至数千的数据集。但是,我们可能有兴趣了解哪些研究人员在特定的研究领域最有影响力。

MLS 数据通常用于了解臭氧气体和臭氧层。让我们筛选包含“臭氧”一词的论文数据:

# subsetting to documents with a substring in the textozone_docs = [d for d in docs if "ozone" in d["text"]]

这种过滤产生 950 个文档。

要将 AuthorRank 应用于这组文档,首先创建一个 AuthorRank graph 对象:

# create an AuthorRank object
ar_graph = ar.Graph()

AuthorRank Python 包在幕后利用了 NetworkX ,这意味着您可以将该包的工具应用于最终的合著关系图。但首先,将 AuthorRank 算法应用于与臭氧相关的文档:

# fit to the data
ar_graph.fit(    
    documents=ozone_docs,    
    progress_bar=False, # helpful
    authorship_key="author",    
    keys=set(["given", "family"])
)

注意authorship_keykeys参数,它们告诉 Python 包在哪里寻找作者信息以及如何解释它。authorship_key参数告诉 Python 包哪个键包含唯一标识的作者信息。另一方面,keys参数告诉 Python 包哪些键——当一起使用时——可以用作作者的惟一标识符,在本例中是名和姓。

最后,让我们检索顶级作者列表,并在结果列表中调整分数:

# get the top authors for a set of documents
top = ar_graph.top_authors(    
    normalize_scores=True, # scales top-n to [0,1]
    n=5
)# print the results
for i, j in zip(top[0], top[1]):    
    print(i, j)>>>
('L.', 'Froidevaux') 1.0
('J. W.', 'Waters') 0.3836040020890074
('M. L.', 'Santee') 0.27529129022124343
('G. L.', 'Manney') 0.24224671041380447
('W. G.', 'Read') 0.0

长度根据 AuthorRank 的说法,Froidevaux 是利用 MLS 数据进行臭氧研究的最有影响力的作者之一。快速浏览一下这位科学家的研究兴趣和选定的出版物,就可以看出 Froidevaux 博士对平流层臭氧趋势和臭氧层的浓厚兴趣。

Froidevaux 博士的精选出版物说明了他对臭氧层和臭氧气体研究的关注。

有许多技术可以帮助更好地理解文本语料库,从命名实体识别(NER)到使用变压器模型的文本摘要(深度学习)。这些技术,尤其是总体而言,可以让数据科学家和企业了解之前锁定在数据中且未被发现的见解。

在这里,我们探索了使用 AuthorRank——一种基于谷歌 PageRank 的图表技术——来更好地了解作者在特定研究领域的影响(利用 MLS 数据的臭氧相关论文)。我们将 AuthorRank 应用于 MLS 数据的子集,并看到 AuthorRank 表明 l . Froidevaux——后来研究臭氧的科学家——是该研究领域最有影响力的作者之一。AuthorRank 和它的 Python 实现可以很容易地应用于其他文本语料库和数据集。

更多数据科学文章

喜欢这篇文章吗?请查看下面的其他数据科学文章。

发现分数的形状

原文:https://towardsdatascience.com/discovering-the-shape-of-fractions-e9034ab0085c

1/n 形式分数的十进制展开的可视化数据驱动探索

作者收集了 1/n .图像形式的部分可视化。

数学家的图案,就像画家或诗人的一样,必须是美丽的…

G.哈代

上周,我又看了一个很棒的数字迷视频。在视频中,布雷迪马特·汉德森谈论了更多他对数字的迷人想象。在这一集 Matt 讲述了我们如何使用海龟图形和一个非常酷的笔式绘图仪来可视化无理数的无限位数,例如πφ(黄金比例)。

这让我想到了我女儿最近在做的一些关于分数及其小数展开式的数学,以及 Matt 的一些想法是否会帮助她更好地理解正在发生的事情?至少,我希望一个更直观的关于数字的对话能够激发对支撑数学的美丽模式的更深刻的欣赏…嘿,爸爸可以做梦…

无论如何,在这篇文章中,我将描述我是如何应用 Matt 的一些想法来开始我自己对数字视觉景观的探索的。我决定把重点放在形式为 1/n 的简单分数上,这里我将查看从 1 到 100 的 n 的值。我的目的是描述我所使用的方法,并展示由此产生的观想,以及它们最有趣的一些方面,但在大多数情况下,我会让观想自己说话。

Matt 使用可视化方法非常简单,我在本文的最后提供了一个基本 Python 实现的代码片段。方法大概是这样的:

  • 想象一下,我们正在控制一个小型机器人(海龟图形中的“海龟”),它能够在移动时绘制出它所遵循的路径。
  • 为了简单起见,这个机器人总是向前移动一个单位的距离,我们想要可视化的数字决定了它移动的方向。
  • 因为有 10 个可能的数字(0,1,2,…,9),所以有 10 个可能的方向,数字 d 转换成角度 d360/10 度。因此,*数字 1 将使机器人从当前航向旋转 36 度,数字 2 将产生 72 度的旋转,依此类推。
  • 以这种方式,任何数字都可以通过遵循编码在其数字中的方向指令来可视化。这些数字也可以用来给路径上产生的每个线段分配一种颜色;10 个数字有 10 种可能的颜色。

下面的动画展示了我们如何使用这种方法来可视化分数 1/14 的十进制展开的前 64 位(. 0714285…)。1/14 的十进制扩展是无限的——它被称为重复分数重复十进制——数字 714285 永远重复,但正如我们将看到的,不是任何分数都是重复的。每个连续的数字对应一个新的彩色编码线段。在这种情况下,前两个数字(00)导致两个水平(蓝色)线段,因为;转向角度为 0。接下来是数字 7 的 252 度急转弯(7 x 36 = 252 ),然后是数字 1 的 36 度小转弯,以此类推。

为 1/14 的十进制扩展中的前 64 位数字生成的可视化动画;每个数字定义了旋转角度,并被分配了一个预定义的颜色。图片作者。

为了便于读者更从容地分析,下面的序列显示了该动画的各个帧。在每一帧中,起点由填充标记指示,终点由未填充标记指示。每一帧都标有相应的数字。

1/14 的十进制扩展的前 64 位的动画帧。起点由填充标记指示,终点由未填充标记指示。根据十进制扩展中相应的数字对线段进行颜色编码。图片作者。

现在我们已经掌握了可视化过程,让我们来看看将它应用于一些熟悉的分数的结果。

下面是形式为 1/n 的所有分数的网格,其中 n 的值从 1 到 100。每个图显示了十进制扩展的前 1000 位数字,如果它们存在的话;简单的不重复的分数,如 1/21/4 等。不要有无限的十进制扩展,所以只贡献少数数字给观想。如上所述,线段被着色以反映数字和相应的转角,所以每个可视化的形状和颜色告诉我们十进制扩展的数字。

为 1/n 形式的分数的十进制展开的前 1000 个数字产生的可视化,其中 n 的值从 1 到 100。每一个可视化都标有分数及其部分小数展开。图片作者。

当我第一次看到这些图像时,我被它们的视觉趣味和震撼力所打动。我没想到像 1/7 这样简单的分数会产生如此对称的“螺旋桨”,看看 1/83 或 1/98 的视觉化的复杂本质,它们看起来就像精致的花卉花环。

可视化 1/27 的十进制扩展的前 100 位。图片作者。

作为一个题外话,值得强调的是,所有这些可视化都被缩放以最大化易读性——通过确保它们填充可用的框架——但这模糊了 1/27 可视化的结构,从表面上看,它看起来像一条简单的直线,表示不重复的分数。事实上,这个分数有一个更复杂的阶梯状可视化,如上图所示,当我们放大它的十进制展开的前 100 位时。

显而易见,复杂的结构和旋转对称在许多可视化中是显而易见的,这是由于它们的十进制扩展的周期性而产生的。它们重复出现的数字共同创造了视觉化的重复结构。在数学语言中,这些无限重复的数字序列被称为 重复重复 ,重复的长度被称为其周期

四次可视化,以突出每个分数的重复数字序列的第一次出现。每一个可视化都用它的分数、它的十进制扩展的一部分和它的重复序列的长度(重复周期)标记在括号中。图片作者。

上面的四个可视化显示了分数 1/3、1/14、1/85 和 1/97 的十进制展开中的前 1000 个数字,以某种方式突出显示了与每个分数的重复数字的第一次出现相对应的可视化部分。

选择这些部分是因为它们具有不同的重复周期。1/3 的重复是单个数字“3”(句点 1),这意味着它的可视化由与当前航向成 108 度角的单个线段(红色)的重复组成,如图所示。

分数 1/14 有一个 6 位数的 reptend ('714285 '),它转化为一个单独的'叶片',形成 10 叶片,螺旋桨式可视化。请注意,对于 1/14 的 6 位数 reptend,只有 5 条线段可见。这是因为它的最后一个数字“5”导致视觉化返回自身(180 度旋转)来模糊“8”的前一个线段。

时间越长,事情越复杂。1/85 的 16 位数重复对应于它的五边形视觉化的一边,而 1/97 的 96 位数重复创造了它的 5 个角块之一。

从可视化的角度来看,重复序列的长度是复杂性的一个有用的度量,因为具有较长重复序列的片段往往比那些具有较短重复序列的片段产生更丰富的可视化。为了了解这一点,下面的图表按照其长度的降序重新排列。此外,他们突出了对应于重复第一次出现的观想部分。这有助于阐明每一个观想是如何由一系列重复的组成部分组成的。

n 值从 1 到 100 的重复(重复周期> 1)分数 1/n 的十进制展开的前 1000 位的可视化。每个可视化的起点由填充的标记指示,终点由未填充的标记指示。每个可视化突出显示其第一个重复序列,每个可视化的标题显示分数本身,直到其重复数字的前 10 位,以及其重复数字序列的长度(其重复周期)。如前所述,基于相应的重复数字对每个线段进行颜色编码。图片作者。

通常,reptend 有助于视觉化的作品是直观的。例如,在像 1/71/14 这样的片段中,它对应于主'叶片中的一个,类似地,对于 1/19 这样的片段,它对应于观想的单个'。然而,对于像 1/941/711/62 这样的片段来说,重复结构远没有那么明显,实际上是以一种复杂得多的方式通过可视化编织而成的。

还要注意素数分数 ( 素数的倒数 ) — 1/p 其中 p 是素数p>5与非素数分数相比,具有更长的表示和更大的可视化复杂度。我确信这是数论者众所周知的——例如,对于除了 2 和 5 之外的任何素数 p,1/p 的十进制展开以一个周期重复划分 p-1——但有趣的是看到它也反映在可视化中,值得进一步研究。

这篇文章的目的是开始对形式为 1/n 的简单分数的可视化探索,使用它们的十进制展开作为一组方向指令来指导可视化过程。我发现,就其揭示的结构的丰富性和规律性而言,结果既引人注目又引人入胜。

我已经集中在观想和重复十进制展开的本质之间的关系上。具有更长重复序列的分数与更复杂的可视化相关联,但是到目前为止我只触及了表面,还有许多值得进一步研究的内容:

  • 为什么大部分的可视化显示出 5 重或者 10 重旋转对称?想必和我用过 base 10 有关?对于较大的 n 值或 1/n 的倍数,这种情况会持续吗?在其他数基中,观想是如何变化的?对称性可以从十进制展开本身确定吗,也就是说,不产生观想?
  • 显然,一些分数之间存在着密切的关系。例如,基于 3、6 和 7 的倍数的分数是相似的,当然还有 10 的倍数(1/n、1/10n、1/100n 等。)将产生几乎相同的可视化,除了由于小数点后任何额外的' 0 '而导致的平移。从这些关系中我们可以学到什么?它们如何影响观想?
  • 当我们组合——加/减,乘/除——分数时会怎样?他们的观想会以可预测的方式改变吗?
  • 有明显不同种类的观想,例如,星星、螺旋桨、五边形和十边形、楼梯等等。这是完整的集合吗?还是有简单分数的额外视觉化类别?

我最初的目标是帮助我的女儿对我们周围的数字中存在的模式有更高的鉴赏力…这是如何实现的呢?我们只能说这是一项正在进行的工作。但是从好的一面来看,至少可视化技术是成功的。在我们家,我们现在都有自己最喜欢的分数,当她试图发现可视化中的重复结构并将它们与十进制扩展的数字匹配时,看到她的兴趣得到了发展,这是令人欣慰的。

附录

下面的 Python 代码片段包含了给定数字的可视化代码。在这项工作中,我使用 Matplotlib 来产生可视化效果,并使用 Mpmath 来获得所需的浮点精度;Python 的默认精度不够。

def draw_number(ax, n):
    """Plot a simple graph for a number n.

    Args:
    ax: axis (Matplotlib) used for the visualisation.
    n: the number to visualise; a decimal expansion of a fraction.
    """ # Convert n into a string of digits; remove the decimal point.
    digits = str(n).replace('.', '')    coords = [(0, 0)]   # The origin/start coord.
    heading = 0         # An 'easterly' heading. # Iterate over each digit...
    for digit in digits: # Convert the digit into an int.
        n = int(digit) # Use n to pick a turn angle; assumes base 10.
        deg = n*360/10

        # Update the current heading with the new turn angle.
        heading+=deg # The current x, y coords.
        current_x, current_y = coords[-1]

        # Calculate the x and y offset based on the new heading.
        x_offset = math.cos(math.pi*heading/180)
        y_offset = math.sin(math.pi*heading/180)

        # The new x and y coords.
        new_x, new_y = current_x + x_offset, current_y + y_offset

        # Plot the new line from the current to the new xy
        ax.plot([current_x, new_x], [current_y, new_y]) # Add the new xy to the coord list.
        coords.append((new_x, new_y))

    # Mark the start and end points
    ax.plot(
      [coords[0][0]]*2, [coords[0][1]]*2, 
      marker='o', markersize=8, markeredgecolor='k', color='k'
    )
    ax.plot(
      [coords[-1][0]]*2, [coords[-1][1]]*2, 
      marker='o', markersize=8, markeredgecolor='k', color='w'
    ) # Return the coords than have been plotted.
    return coords

为语义语境确定任务发现不同层次的 BERT 嵌入趋势

原文:https://towardsdatascience.com/discovering-trends-in-bert-embeddings-of-different-levels-for-the-task-of-semantic-context-268733fdb17e

伊戈尔·沙巴林的照片,经许可

如何从 BERT 模型输出中提取句子的上下文信息

谈到 BERT 中的上下文嵌入,我们指的是预训练模型的隐藏状态。然而,首先,BERT 使用从查找表中提取的非上下文的、预训练的(静态)嵌入。它发生在编码器层之前的嵌入层,然后编码器层为正在处理的序列生成隐藏状态。

简而言之,语境嵌入来自非语境,从某种意义上说,它是相邻标记嵌入扩散的产物。

这里讨论的方法可以用于广泛的分类 NLP 任务,如 NSP(下一句预测),NER(命名实体识别)等。

假设

假设令牌的初始非上下文嵌入和随后使用编码器为该相同令牌生成的上下文嵌入之间的差异可以包含关于上下文本身的信息,因此,例如,当您需要在诸如句子蕴涵的任务中为整个句子生成摘要嵌入时,这可能是有用的。

直觉

一个记号的嵌入只不过是一个在 n 维空间中表示该记号的数字向量。每个维度都包含该标记的一些语义信息。这个概念可以通过下面的简化例子很容易理解。假设特征是银行、邮政、游戏。然后,单词 Card 可以用下面的三维向量来表示:(0.85,0.6,0.45)。正如您可能猜到的,这是单词卡的非上下文表示,它只显示该单词在特定上下文中出现的概率。现在假设单词 Card 出现在下面的短语中:

I sent her a postal card.

因此,Card 的上下文表示现在可能如下所示:(0.2,1,0.1),增加与邮政相关的值,减少与其他类别相关的值。要按特征查看原始向量和新向量之间的变化,您可以用后者除以前者(按元素),使用对数确保平滑近似:

c = log(b/a)
where a — non-cotextual (initial) vector,
b — cotextual vector

在这个特定的例子中,上述计算的结果向量将如下所示:

(-1.4, 0.5, -1.5)

这种新的表现形式让你对每个特征的变化有一个清晰的概念,无论是积极的还是消极的。你可能想知道,为什么我需要这种表示——为什么不用上下文向量来代替呢?其思想是,当绝对值变化不大时,知道特征值(而不是其绝对值)的变化对模型更有帮助。当不清楚在当前环境中哪个特性占主导地位,哪个应该首先被关注时,就会发生这种情况。这可以通过例子得到最好的理解。继续我们的卡片例子,考虑下面的两句话:

We’re going to send her a card.
Where is the nearest mailbox?

第一句没有明确告诉你指的是哪种卡。然而,这里使用的及物动词 Send 允许您的模型谨慎地猜测是指一张明信片。因此,根据第一句话的上下文,卡的初始嵌入到上下文嵌入的过渡可能如下所示,邮政特征的值增加一点,而所有其他特征的值减少:

(0.85, 0.6, 0.45) -> (0.8, 0.75, 0.4)

但是,正如您所看到的,Banking 特性的值(首先出现)仍然大于其他值,这将使您的模型在决定上下文时将其视为优先特性。相比之下,考虑特征值的变化会提供更多信息,因为它明确显示了哪些权重增加了,哪些权重减少了:

log((0.8, 0.75, 0.4)/(0.85, 0.6, 0.45)) -> (-0.1, 0.2, -0.1)

在这个特定的例子中,第二个句子的上下文与邮资明确相关。因此,在句子蕴涵的任务中,所提出的方法将帮助你的模型做出正确的预测。

试用 BERT 嵌入

与上面的玩具例子不同,真实模型通常使用几百个维度来嵌入。例如,基本的 BERT 模型使用 768 维空间进行嵌入,其中每个维度都不与明确命名的语义类别相关联。但主要思想是不变的:如果两个嵌入在同一个维度上有很高的值,说明它们对应的词与某一个、同一个语义范畴有联系,比如银行、游戏等。

让我们用简单明了的例子来试验 BERT 嵌入。考虑以下三句话:

I want an apple.
I want an orange.
I want an adventure.

在上面的每个句子中,我们使用同一个及物动词 Want。但是在前两个句子中,我们使用了直接宾语:苹果和桔子,它们属于同一个语义范畴:水果。第三句用了直接宾语:显然属于另一个范畴的冒险。

The purpose of our experiment: 

检查直接宾语的语义差异如何影响及物动词的嵌入。
如果有这样的效果,使它更清楚地表达出来。
实现
Google Colab:https://Colab . research . Google . com/drive/1k _ R1 QoS 79 au ws 2 jej 7d 1 mymxhxa d 29 FD?usp =共享

为了获得预训练的 BERT 模型,我们将使用拥抱脸中的变形金刚:

pip install transformers

我们需要 Bert 记号赋予器和裸露的 BERT 模型转换器:

import torch
from transformers import BertTokenizer, BertModel

然后,我们可以加载预训练的模型标记器(词汇)和模型:

tokenizer = BertTokenizer.from_pretrained(‘bert-base-uncased’)
model = BertModel.from_pretrained(‘bert-base-uncased’,
 output_hidden_states = True, # Making the model return all hidden-states.
 )

我们将模型置于“评估”模式。

model.eval() 

下面我们定义在我们的例子中使用的例句:

sents = []
sents.append(‘I want an apple.’)
sents.append(‘I want an orange.’)
sents.append(‘I want an adventure.’)

接下来,我们需要标记这些句子:

tokenized_text = []
segments_ids = []
tokens_tensor = []
segments_tensors = []
for i, sent in enumerate(sents):
 tokenized_text.append(tokenizer.encode(sent, add_special_tokens=True))
 segments_ids.append([1] * len(tokenized_text[i]))
 tokens_tensor.append(torch.tensor([tokenized_text[i]]))
 segments_tensors.append(torch.tensor([segments_ids[i]]))

之后,我们可以通过 BERT 运行我们的句子,并收集其层中产生的所有隐藏状态:

outputs = []
hidden_states = []
with torch.no_grad():
 for i in range(len(tokens_tensor)):
 outputs.append(model(tokens_tensor[i], segments_tensors[i]))
 # we set `output_hidden_states = True`, the third item will be the 
 # hidden states from all layers. See the documentation for more details:
 # https://huggingface.co/transformers/model_doc/bert.html#bertmodel
 hidden_states.append(outputs[i][2])

因此,对于每个句子,我们有嵌入层的输出+所有 12 个编码器层的输出。

len(hidden_states[0])
13 

每层有 768 个维度。

len(hidden_states[0][12][0][2])
768

为了简单起见,我们将只考虑嵌入的前 10 个值:

hidden_states[0][12][0][2][:10].numpy()
array([ 0.44462553, 0.21318859, 1.1400639 , -0.05000957, 0.43685108, 0.91370475, -0.6992555 , 0.13507934, -0.42180806, -0.66882026], dtype=float32)

在这个例子的每个句子中,我们只对第二个词(Want)感兴趣。下面我们获得在第 12 编码器层中为 Want 生成的嵌入,并将它们转换成 numpy:

l12_1 = hidden_states[0][12][0][2][:10].numpy()
l12_2 = hidden_states[1][12][0][2][:10].numpy()
l12_3 = hidden_states[2][12][0][2][:10].numpy() 

将获得的向量相互比较,以找出它们在语义上如何相互接近,这将是有趣的:

from scipy import spatial
1 — spatial.distance.cosine(l12_1, l12_2)
0.9869935512542725
1 — spatial.distance.cosine(l12_1, l12_3)
0.8980972170829773
1 — spatial.distance.cosine(l12_2, l12_3)
0.8874450922012329 

正如你所看到的(也可能是你所期望的),前两个句子中 Want 的嵌入比第三个句子中 Want 的嵌入更接近。

现在让我们检查一下,我们是否能更清楚地了解嵌入中的差异,反映出由上下文引起的语义差异。

首先,我们需要获得单词 Want 在每个句子中的初始嵌入:

l0_1 = hidden_states[0][0][0][2][:10].numpy()
l0_2 = hidden_states[1][0][0][2][:10].numpy()
l0_3 = hidden_states[2][0][0][2][:10].numpy() 

我们现在可以将第 12 编码器层中生成的 Want 嵌入除以相应的初始嵌入:

import numpy as np
l0_12_1 = np.log(l12_1/l0_1)
l0_12_2 = np.log(l12_2/l0_2)
l0_12_3 = np.log(l12_3/l0_3)
Before proceeding we need to replace NaNs to 0s:
l0_12_1 = np.where(np.isnan(l0_12_1), 0, l0_12_1)
l0_12_2 = np.where(np.isnan(l0_12_2), 0, l0_12_2)
l0_12_3 = np.where(np.isnan(l0_12_3), 0, l0_12_3) 

现在让我们计算结果向量之间的距离,以了解这些新的表示是否可以更好地指示基础单词之间的语义差异:

1 — spatial.distance.cosine(l0_12_1, l0_12_2)
0.9640171527862549
1 — spatial.distance.cosine(l0_12_1, l0_12_3)
0.4167512357234955
1 — spatial.distance.cosine(l0_12_2, l0_12_3)
0.3458264470100403 

与先前从第 12 层生成的嵌入词获得的相似性结果相比较,我们可以得出结论,这些新的表示使我们能够更清楚地理解潜在的词如何根据它们所处的上下文而彼此不同。

注意力权重选择上下文中最重要的单词

毫无疑问,您已经意识到,一般的想法是,将一个标记的上下文嵌入除以这个相同标记的静态嵌入所得到的向量包括关于整个句子的上下文的信息。前面的例子说明了所提出的方法在应用于句子的及物动词时是如何工作的。问题出现了:它必须总是一个及物动词吗?对于这种分析来说,每句话一个词就足够了吗?

嗯,直观上很清楚,就上下文而言,它应该是一个重要的词。要在特定的句子中选择一个,您可以利用编码器层中生成的注意力权重。代码如下:

在继续之前,让我们先来看看我们要获取注意力权重矩阵的令牌:

tokenizer.convert_ids_to_tokens(tokenized_text[0])
[‘[CLS]’, ‘i’, ‘want’, ‘an’, ‘apple’, ‘.’, ‘[SEP]’]

这是句子“我想要一个青苹果”的第 12 层注意力权重。

outputs[0].attentions[0][0][11].numpy().round(2)
array([[0.93, 0.02, 0\. , 0.01, 0\. , 0\. , 0.03],
 [0.3 , 0.05, 0.24, 0.07, 0.14, 0.06, 0.15],
 [0.38, 0.41, 0.04, 0.02, 0.06, 0.02, 0.07],
 [0.48, 0.11, 0.16, 0.02, 0.02, 0.04, 0.17],
 [0.07, 0.07, 0.26, 0.27, 0.06, 0.05, 0.23],
 [0.52, 0.05, 0.06, 0.04, 0.07, 0\. , 0.26],
 [0.71, 0.06, 0.03, 0.03, 0.01, 0\. , 0.15]], dtype=float32) 

我们按列求和,不包括特殊符号:

np.sum(outputs[0].attentions[0][0][11].numpy(), axis=0)[1:-1]
array([0.7708196 , 0.7982767 , 0.45694995, 0.36948416, 0.17060593],
 dtype=float32) 

根据上面所说,第二个单词(Want)是句子中最重要的一个。

结论

本文中讨论的方法说明了如何在不同的层次上利用 BERT 模型生成的嵌入。特别是,您已经看到了如何获取并使用一个向量表示,该向量表示包含关于上下文嵌入值相对于同一标记的静态嵌入的变化的信息。这种表示携带了关于该标记所处的上下文的信息,从而为诸如句子蕴涵这样的分类任务打开了大门。

时间序列中季节性的发现

原文:https://towardsdatascience.com/discovery-of-seasonality-in-time-series-4b3d6073b5a6

使用 R 的傅立叶变换分析

摄影Unsplash 上拍摄的照片

G 政府收支、旅客流量、进出口值都是与经济现象相关的时间序列。就其本质而言,它们受趋势、结构变化和其他因素的影响。当试图寻找这种时间序列的最佳季节性特征时,这些因素会产生错误的结论。在下面的段落中,我们将分析一个案例,这个案例起初可能会带来一些挫折,但最终是时间序列分析中最敏捷的技术之一的一个极好的应用:傅立叶变换。对于这个材料,我使用 R 语言来获取和处理数据,进行调查,并生成图表。该代码的链接位于正文的末尾。这是对那些阅读到底的人的奖励。

看看下面的系列,它描述了用于确定巴西中央政府运营结果的一个要素的 23 年月度行为。请注意,周期指向周期性峰值和这些峰值之间的一些凹痕。还要注意的是,有一个增长的趋势,然后有些稳定。

整个时间序列。作者图片

我们可以看看第一个赌注,最关键的季节性是由周期性峰值确定的年度季节性,从整个时间序列中每 12 个月出现的图表中可以合理地相信这一点。但是,另一方面,一个更谨慎的方法可能会让我们认为凹痕也是季节性发生的,从而标志着另一个周期性。
见下面循环视图中分布的数值。

循环图。图片由作者提供。

可以看出,高峰期出现在 12 月。其他月份没有显示出明显的缩减,这使我们加强了年度季节性的假设。这里还值得注意的是,颜色渐变表示一系列年份:越接近粉红色,年份越近。这种与较高值相关联的更接近粉红色的颜色配置证实了本文开头已经指出的增长趋势。

尽管该图给人的印象是季节性是每年一次的,但寻找更强大的分析工具来减少纯视觉分析的不确定性是很方便的。文献推荐使用傅立叶变换来识别周期信号中最关键的频率。遵循从系列中排除趋势的指导方针,我使用 R 语言来识别最基本的频率,然后开始所有的惊喜。见下图。

最关键的季节性。图片由作者提供。

上图改编自傅里叶变换分析的最常见输出。通常,水平轴是频率,垂直轴是与每个频率相关的频谱,表示每个频率的重要性。根据定义,频率是给定周期的反函数( f=1/T ,其中 f 为频率, T 为周期)。对于我们的例子,我们最初对标记季节性的时期更感兴趣,所以我选择在水平轴上显示这个变量。

正如您所看到的,与最高频谱相关联的周期对应于两个时间单位,在本例中是两个月。最关键的季节性是每两个月一次,而不是每年一次。顺便说一句,在上面的同一张图中,当查看相关的光谱时,12 个月被其他几个季节超过了。

但是如何解释这一发现呢?与傅立叶变换相关的算法的详细分解可以帮助我们理解。在接下来的图表和段落中跟随我。

用一组频率表示月份。图片由作者提供。

时间函数的傅立叶变换从原始时间函数中存在的频率和中分解信号。在我们的例子中,我们使用没有趋势的序列来检查 144 个不同频率的贡献。所有这 144 个频率都对实际过程有贡献,但只有极少量是有意义的。由于我们对月度数据的时间序列感兴趣,我们在上图中突出显示并命名了双月、三月、四月、半年和一年的频率。

傅立叶变换产生的值是类型为 a+bi复数,其中 a 是实部,b 是虚部。图形的水平轴对应于实数元素,垂直轴对应于虚数部分。

还要注意,该图试图表示半径为 1000 的圆。半径测量在每个白点处表示的复数的所谓模数。我们将对该值应用三角函数,以获得实部和虚部。

有了这些初步的解释,我们可以更好地理解图中所示的频率。请看双月频率图。我们看到两个点占据水平轴或实轴:一个在零的左边,另一个在右边。左边的点与数字 1、3、5、7、9 和 11 相关,另一个点与 2、4、6、8、10 和 12 相关。每个数字代表一年中的一个月。这样,偶数月与奇数月相对。

奇数月份的点在圆的 0 度角上。对于这种情况,我们知道水平轴上的值是 1000,垂直轴上的值是 0。这样,我们就有了有序对(1000,0)。用复数来说,这个点对应的是 1000+0i ,我们用三角函数来计算:

*1000 *余弦(0)+1000 正弦(0 )i.

由于余弦(0 )=1,正弦(0 )=0,我们回到 1000+0i

偶数月份的点在圆上 180 度处。这里横轴上的值是-1000,纵轴上的值是 0。用复数表示法,对应 -1000+0i 。使用正弦和余弦:

*1000 *余弦(180)+1000 正弦(180 )i.

由于余弦(180 )=-1,正弦(180 )=0,所以我们有: -1000+0i

现在让我们跳到年度频率。请注意,相等的角度分隔了这些数字。由于 360 度圆上有 12 个点,每个点与另一个点相距 30 度。因此,第 12 个月的点表示一个复数,来自:

*1000 *余弦(30)+1000 正弦(30)一.

即: 866,02+500i

一个建议:尝试计算年频率的其他点,并观察每个月的实部和虚部中存在的正值和负值。另一个建议:检查不同的频率和与每个频率相关的点。观察来自组成、分布和月份对冲的模式。

目前,我们将停止在这一审查。我们稍后将需要这个符号来计算向量,这些向量是从分析的序列中计算出的每个点的实数和虚数的组合中产生的。

傅里叶变换在时间序列分解中的应用。图片由作者提供。

上图显示了在实践中,我们如何根据 12 个月和随机选择的 6 年中的每一年的双月或年度频率,用趋势排除法分解时间序列。我在每一年中突出显示了 12 月的假设值。比如第 3 年,这个值是 41874 。第 7 年是 42314 ,以此类推。这些值是复数的模。请注意,第 12 个月通常远离其他月份。尤其是在年 20 中,12 月 12 日的模数值比其他月份高得多。记住,我们可以用复数符号来表示这些点:

双月频率

  • 第 3 年第 12 个月:

41874 *余弦(180)+41874 *正弦(180 ) = -41874+0i

  • 第 7 年第 12 个月:

42314 *余弦(180)+42314 *正弦(180 ) = -42314+0i

年频率

  • 第 3 年第 12 个月:

41874 *余弦(30)+41874 *正弦(30 ) = 36263.95+20937i

  • 第 7 年第 12 个月:

42314 *余弦(30)+42314 *正弦(30 ) = 36645+21157i

现在是时候将每年的实部和虚部的值分别相加,然后计算得到的向量,这将允许我们验证哪一个提供更大的频谱、年度频率或双月频率。请看下图所示。

一些合成向量。图片由作者提供。

上图显示了随机选择的六年的合成向量。我们通过将与每个频率的每个月相关联的每个复数相加来计算结果向量。换句话说,对于所分析的两个频率中的每一个,我们将每年的所有实部和虚部相加。这样,我们就有了结果向量的模在水平轴和垂直轴上的投影。投影在图中用虚线表示。带点的整条线表示末端的合成矢量,有助于指示方向。

我们应用勾股定理计算合成向量的模:√(σ实数+σ虚数)。例如,在第 3 年,年频率的值是 9832,双月频率的值是 13002。

注意,随着时间的推移,双月频率的合成向量的模通常比年频率的更重要。此外,请注意,在年频率下,向量的方向与第 12 个月在同一象限,即在 0°和 90°之间。至于双月频率,向量与第 2、4、6、8、10 和 12 个月对齐,即 180 度。

现在我们进入最后一步,计算每个频率的最终合成向量。请看下图。

整个级数的矢量合成。图片由作者提供。

上图显示了考虑 23 年的总合成向量。请注意,双月频率的模数 464842 大于年频率的模数 366136 。我们确认双月频率在数学上与比年频率更大的频谱相关联。

该图还允许我们从向量的方向显示哪个双月组合占优势。注意,这个方向指向偶数个月。我们得出结论,偶数月份的值之和大于奇数月份的值之和。生成此配置的可能性之一是,每个偶数月的值都比它之前的奇数月的值高。

下图有助于验证这种可能性。

按月累计的值。图片由作者提供。

上图显示了整个时间序列的累积值。奇数月和接下来的偶数月之间有一些细微的区别。唯一的例外是第 12 个月,这比第 11 个月高得多。

有了最后一个数字,就很清楚为什么双月周期比任何其他月份安排的配置更好地代表了本文中分析的时间序列,包括一年一次的安排,这是我们一眼就能看出的。

数据、代码和联系人

数据来自巴西国库秘书处的开放数据门户。我准确地使用了“1.3——Arrecada ao líquida para o RGPS”系列。数据集也可以被这个 R 包消费这里。由于数据源是使用 ODbL 许可证许可的,任何人都可以出于任何目的使用数据。

读者可以从我的 GitHub 中访问数据和代码。考虑在推特上查找我的信息以获得进一步的澄清。

致谢

感谢费尔南达·佩肖托·索托路易斯·菲利佩·科英布拉·科斯塔米莲娜·奥齐耶的建议和反馈。

疾病通知神经网络

原文:https://towardsdatascience.com/disease-informed-neural-networks-aa1f17f598a4

神经网络能够学习疾病如何传播,预测其进展,并找到其独特的参数(如死亡率)。

在本教程中,我介绍了疾病通知神经网络(DINNs)。在论文中,我们使用 DINNs 来确定 11 种高传染性和致命性疾病的动态。这些系统的复杂性和参数数量各不相同。这些疾病包括 COVID,炭疽,艾滋病毒,寨卡,天花,结核病,肺炎,埃博拉病毒,登革热,脊髓灰质炎和麻疹。完整的代码&实验可以在这里找到,具体的教程笔记本可以在这里找到。

DINN 示例架构。输入是不同的时间点(第 1 天、第 2 天、…、第 100 天),输出是每个时间点每个疾病区室(易感、感染、死亡等)的值。图片作者。

疾病在它们影响的有机体、它们的症状和它们传播的速度方面有很大的不同。为了更好地理解疾病,就像大多数机器学习问题一样,我们需要数据。在本教程中,为了简化,我们将生成数据,而不是从真实世界环境中收集数据(尽管在本文中,我们确实使用了来自这里的真实 COVID 数据)。

对于那些没有流行病学和微分方程背景的人来说,我会在这里保持简单。DINN 输入时间步长(第 1 天、第 2 天、…、第 100 天),每天输出每个疾病区间中的人数。“车厢”只是对不同群体的一个花哨称呼。例如,我们有“受感染”组,它代表人口中有多少人被感染(它实际上是一个计数,比如 5 个人)。“易感”群体代表有多少人没有被感染,但可以被感染。诸如此类。这些区间/组是由数学家/其他人决定的,他们对疾病如何传播进行建模,并在数学上写成一组微分方程。

好了,这就是背景,让我们开始编码。

考虑下面的微分方程系统

dS/dt =-(α/N)S I

dI/dt =(α/N)S I-βI-γI

dD/dt =γI

dR/dt =βI

在哪里

beta =“有效/明显”每日回收率

gamma = "有效/明显"每日死亡率

α=感染率

N =人口规模

他的系统代表了一个 COVID 模型,有 4 个区间:易感(ds/dt)、感染(dI/dt)、死亡(dD/dt)和康复(dR/dt)。同样为了简化,这些车厢只是对每个车厢里有多少人的计数。

一旦有了方程式,生成数据就非常简单:

导入用于生成和可视化数据的库

**import** numpy **as** np
**from** scipy.integrate **import** odeint
**import** matplotlib.pyplot **as** plt

我们首先设定一些从文献中获得的信息

*# Initial conditions (from the literature)*
N **=** 59e6 *#population size*

S0 **=** N **-** 1 *#everyone starts out as susceptible, except for 1 person that is infected*
I0 **=** 1 *#1 infected person*
D0 **=** 0
R0 **=** 0

*# A grid of time points (in days)*
t **=** np**.**linspace(0, 500, 100) *#from day 0 to day 500, generate 100 points*

*#parameters (from the literature)*
alpha **=** 0.191
beta **=** 0.05
gamma **=** 0.0294

然后,我们用一个函数写出方程组,该函数将计算每个时间步长(例如一天)下每个区间的值

*# The SIDR model differential equations.*
**def** deriv(y, t, alpha, betta, gamma):
    S, I, D, R **=** y
    dSdt **=** **-** (alpha **/** N) ***** S ***** I
    dIdt **=** (alpha **/** N) ***** S ***** I **-** beta ***** I **-** gamma ***** I 
    dDdt **=** gamma ***** I
    dRdt **=** beta ***** I

    **return** dSdt, dIdt, dDdt, dRdt

然后,我们将初始条件传递给函数,以获得我们之前选择的长度(500 天,每个区间 100 个数据点)的每个区间的值

*# Initial conditions vector*
y0 **=** S0, I0, D0, R0
*# Integrate the SIR equations over the time grid, t.*
ret **=** odeint(deriv, y0, t, args**=**(alpha, beta, gamma))
S, I, D, R **=** ret**.**T

我们现在可以画出结果

*# Plot the data*
fig **=** plt**.**figure(figsize**=**(12,12))
ax **=** fig**.**add_subplot(111, facecolor**=**'#dddddd', axisbelow**=True**)
ax**.**set_facecolor('xkcd:white')

ax**.**plot(t, S, 'violet', alpha**=**0.5, lw**=**2, label**=**'Susceptible', linestyle**=**'dashed')
ax**.**plot(t, I, 'darkgreen', alpha**=**0.5, lw**=**2, label**=**'Infected', linestyle**=**'dashed')
ax**.**plot(t, D, 'blue', alpha**=**0.5, lw**=**2, label**=**'Dead', linestyle**=**'dashed')
ax**.**plot(t, R, 'red', alpha**=**0.5, lw**=**2, label**=**'Recovered', linestyle**=**'dashed')

ax**.**set_xlabel('Time /days')
ax**.**set_ylabel('Number')
ax**.**yaxis**.**set_tick_params(length**=**0)
ax**.**xaxis**.**set_tick_params(length**=**0)
ax**.**grid(b**=True**, which**=**'major', c**=**'black', lw**=**0.2, ls**=**'-')
legend **=** ax**.**legend()
legend**.**get_frame()**.**set_alpha(0.5)
**for** spine **in** ('top', 'right', 'bottom', 'left'):
    ax**.**spines[spine]**.**set_visible(**False**)
plt**.**show()

COVID 生成的数据。图片作者。

并将结果保存为 csv 文件

*#save to csv file*
COVID_Data **=** np**.**asarray([t, S, I, D, R]) np**.**savetxt("COVID_Tutorial.csv", COVID_Data, delimiter**=**",")

太好了!现在我们有数据可以处理了。开始训练神经网络。

我将把整个训练过程分成几个部分。这些部分本身可能是不可运行的(也就是说,你不能运行代码——这就是为什么我要把它们注释掉)。关键是,我认为首先理解每个部分在做什么,然后看看它们是如何一起工作的,这将更容易理解这个过程。我将在主函数前写“> > > >”,这样更容易理解(因为我会在后面添加一些部分)。

1.导入库

**import** torch
**from** torch.autograd **import** grad
**import** torch.nn **as** nn
**from** numpy **import** genfromtxt
**import** torch.optim **as** optim
**import** matplotlib.pyplot **as** plt
**import** torch.nn.functional **as** Ftorch**.**manual_seed(1234) *#set seed (optional)*

2.加载数据

covid_data **=** genfromtxt('COVID_Tutorial.csv', delimiter**=**',') *#in the form of [t,S,I,D,R]*

3.定义类别

**#>>>> MAIN FUNCTION
class** DINN(nn**.**Module):
    **def** __init__(self, t, S_data, I_data, D_data, R_data): *# remember that the data was saved as [t,S,I,D,R]*
        super(DINN, self)**.**__init__()
        *# here all the "loading the data" and training is happening*
        **pass**

4.现在我们需要定义一些初始条件

**#>>>>
class** DINN(nn**.**Module):
    **def** __init__(self, t, S_data, I_data, D_data, R_data): *#[t,S,I,D,R]*
        super(DINN, self)**.**__init__()

        self**.**N **=** 59e6 *#population size*

        *#for the time steps, we need to convert them to a tensor, a float, and eventually to reshape it so it can be used as a batch*
        self**.**t **=** torch**.**tensor(t, requires_grad**=True**)
        self**.**t_float **=** self**.**t**.**float()
        self**.**t_batch **=** torch**.**reshape(self**.**t_float, (len(self**.**t),1)) *#reshape for batch*         *#for the compartments we just need to convert them into tensors*
        self**.**S **=** torch**.**tensor(S_data)
        self**.**I **=** torch**.**tensor(I_data)
        self**.**D **=** torch**.**tensor(D_data)
        self**.**R **=** torch**.**tensor(R_data) self**.**losses **=** [] *# here I saved the model's losses per epoch*

5.这部分有两个选项。要么你知道参数(α,β,γ)的值,要么你不知道。

如果您想知道,如果我们只是使用它们生成数据,我们怎么可能不知道它们的值,请记住,我们也可以从环境中获取数据,因此我们可能没有参数

如果我们不知道它们的值,我们让神经网络来学习

注意“蒂尔达”部分的原因很快就清楚了。基本上就是把变量限制在一定的范围内(想象一下告诉神经网络学习 alpha,但不是从负无穷大到无穷大,而是从-100 到 100)

*# self.alpha_tilda = torch.nn.Parameter(torch.rand(1, requires_grad=True))*
*# self.beta_tilda = torch.nn.Parameter(torch.rand(1, requires_grad=True))*
*# self.gamma_tilda = torch.nn.Parameter(torch.rand(1, requires_grad=True))*

如果我们知道它们的值,我们只需设置它

*# self.alpha_tilda = torch.tensor(0.191)*
*# self.beta_tilda = torch.tensor(0.05)*
*# self.gamma_tilda = torch.tensor(0.0294)*

假设我们不认识他们:

**#>>>>
class** DINN(nn**.**Module):
    **def** __init__(self, t, S_data, I_data, D_data, R_data): *#[t,S,I,D,R]*
        super(DINN, self)**.**__init__()

        self**.**N **=** 59e6 *#population size*

        *#for the time steps, we need to convert them to a tensor, a float, and eventually to reshape it so it can be used as a batch*
        self**.**t **=** torch**.**tensor(t, requires_grad**=True**)
        self**.**t_float **=** self**.**t**.**float()
        self**.**t_batch **=** torch**.**reshape(self**.**t_float, (len(self**.**t),1)) *#reshape for batch*         *#for the compartments we just need to convert them into tensors*
        self**.**S **=** torch**.**tensor(S_data)
        self**.**I **=** torch**.**tensor(I_data)
        self**.**D **=** torch**.**tensor(D_data)
        self**.**R **=** torch**.**tensor(R_data) self**.**losses **=** [] *# here I saved the model's losses per epoch* *#setting the parameters*
        self**.**alpha_tilda **=** torch**.**nn**.**Parameter(torch**.**rand(1, requires_grad**=True**))
        self**.**beta_tilda **=** torch**.**nn**.**Parameter(torch**.**rand(1, requires_grad**=True**))
        self**.**gamma_tilda **=** torch**.**nn**.**Parameter(torch**.**rand(1, requires_grad**=True**))

6.标准化数据

还记得我们的人口规模是 5900 万吗?还记得只有 1 名感染者和几乎 5900 万易感人群吗?这些价值观的巨大变化对网络学习来说相当具有挑战性。因此,为了便于训练,我们将每个区间标准化为 0 到 1 之间

*# find values for normalization**# max values*
*# self.S_max = max(self.S)*
*# self.I_max = max(self.I)*
*# self.D_max = max(self.D)*
*# self.R_max = max(self.R)**# min values*
*# self.S_min = min(self.S)*
*# self.I_min = min(self.I)*
*# self.D_min = min(self.D)*
*# self.R_min = min(self.R)**# create new normalized parameters (which is why the "hat" parts)*
*# self.S_hat = (self.S - self.S_min) / (self.S_max - self.S_min)*
*# self.I_hat = (self.I - self.I_min) / (self.I_max - self.I_min)*
*# self.D_hat = (self.D - self.D_min) / (self.D_max - self.D_min)*
*# self.R_hat = (self.R - self.R_min) / (self.R_max - self.R_min)***#>>>>
class** DINN(nn**.**Module):
    **def** __init__(self, t, S_data, I_data, D_data, R_data): *#[t,S,I,D,R]*
        super(DINN, self)**.**__init__()

        self**.**N **=** 59e6 *#population size*

        *#for the time steps, we need to convert them to a tensor, a float, and eventually to reshape it so it can be used as a batch*
        self**.**t **=** torch**.**tensor(t, requires_grad**=True**)
        self**.**t_float **=** self**.**t**.**float()
        self**.**t_batch **=** torch**.**reshape(self**.**t_float, (len(self**.**t),1)) *#reshape for batch*         *#for the compartments we just need to convert them into tensors*
        self**.**S **=** torch**.**tensor(S_data)
        self**.**I **=** torch**.**tensor(I_data)
        self**.**D **=** torch**.**tensor(D_data)
        self**.**R **=** torch**.**tensor(R_data) self**.**losses **=** [] *# here I saved the model's losses per epoch* *#setting the parameters*
        self**.**alpha_tilda **=** torch**.**nn**.**Parameter(torch**.**rand(1, requires_grad**=True**))
        self**.**beta_tilda **=** torch**.**nn**.**Parameter(torch**.**rand(1, requires_grad**=True**))
        self**.**gamma_tilda **=** torch**.**nn**.**Parameter(torch**.**rand(1, requires_grad**=True**)) *#find values for normalization*
        self**.**S_max **=** max(self**.**S)
        self**.**I_max **=** max(self**.**I)
        self**.**D_max **=** max(self**.**D)
        self**.**R_max **=** max(self**.**R)
        self**.**S_min **=** min(self**.**S)
        self**.**I_min **=** min(self**.**I)
        self**.**D_min **=** min(self**.**D)
        self**.**R_min **=** min(self**.**R) *#normalize*
        self**.**S_hat **=** (self**.**S **-** self**.**S_min) **/** (self**.**S_max **-** self**.**S_min)
        self**.**I_hat **=** (self**.**I **-** self**.**I_min) **/** (self**.**I_max **-** self**.**I_min)
        self**.**D_hat **=** (self**.**D **-** self**.**D_min) **/** (self**.**D_max **-** self**.**D_min)
        self**.**R_hat **=** (self**.**R **-** self**.**R_min) **/** (self**.**R_max **-** self**.**R_min)

7.稍后计算梯度的一个有点“黑客”的方法

PyTorch 似乎没有计算梯度的简单方法。问题是“grad”只知道如何从标量张量传播梯度(我们网络的输出不是),这就是为什么我必须计算雅可比矩阵。所以我没有计算整个雅可比矩阵,而是用这种“黑客”的方式

*# matrices (x4 for S,I,D,R) for the gradients*
*# What's important here:*
*# We have 4 compartments, hence the value 4 in "torch.zeros((len(self.t), 4))".* 
*# If we had 20 compartments we would write torch.zeros((len(self.t), 20))*
*# Also, we're setting each specific column in the formed matrices to 1**# self.m1 = torch.zeros((len(self.t), 4)); self.m1[:, 0] = 1*
*# self.m2 = torch.zeros((len(self.t), 4)); self.m2[:, 1] = 1*
*# self.m3 = torch.zeros((len(self.t), 4)); self.m3[:, 2] = 1*
*# self.m4 = torch.zeros((len(self.t), 4)); self.m4[:, 3] = 1**# See (https://stackoverflow.com/questions/67472361/using-pytorchs-autograd-efficiently-with-tensors-by-calculating-the-jacobian) for more details***#>>>>
class** DINN(nn**.**Module):
    **def** __init__(self, t, S_data, I_data, D_data, R_data): *#[t,S,I,D,R]*
        super(DINN, self)**.**__init__()

        self**.**N **=** 59e6 *#population size*

        *#for the time steps, we need to convert them to a tensor, a float, and eventually to reshape it so it can be used as a batch*
        self**.**t **=** torch**.**tensor(t, requires_grad**=True**)
        self**.**t_float **=** self**.**t**.**float()
        self**.**t_batch **=** torch**.**reshape(self**.**t_float, (len(self**.**t),1)) *#reshape for batch*         *#for the compartments we just need to convert them into tensors*
        self**.**S **=** torch**.**tensor(S_data)
        self**.**I **=** torch**.**tensor(I_data)
        self**.**D **=** torch**.**tensor(D_data)
        self**.**R **=** torch**.**tensor(R_data) self**.**losses **=** [] *# here I saved the model's losses per epoch* *#setting the parameters*
        self**.**alpha_tilda **=** torch**.**nn**.**Parameter(torch**.**rand(1, requires_grad**=True**))
        self**.**beta_tilda **=** torch**.**nn**.**Parameter(torch**.**rand(1, requires_grad**=True**))
        self**.**gamma_tilda **=** torch**.**nn**.**Parameter(torch**.**rand(1, requires_grad**=True**)) *#find values for normalization*
        self**.**S_max **=** max(self**.**S)
        self**.**I_max **=** max(self**.**I)
        self**.**D_max **=** max(self**.**D)
        self**.**R_max **=** max(self**.**R)
        self**.**S_min **=** min(self**.**S)
        self**.**I_min **=** min(self**.**I)
        self**.**D_min **=** min(self**.**D)
        self**.**R_min **=** min(self**.**R) *#normalize*
        self**.**S_hat **=** (self**.**S **-** self**.**S_min) **/** (self**.**S_max **-** self**.**S_min)
        self**.**I_hat **=** (self**.**I **-** self**.**I_min) **/** (self**.**I_max **-** self**.**I_min)
        self**.**D_hat **=** (self**.**D **-** self**.**D_min) **/** (self**.**D_max **-** self**.**D_min)
        self**.**R_hat **=** (self**.**R **-** self**.**R_min) **/** (self**.**R_max **-** self**.**R_min)        *#matrices (x4 for S,I,D,R) for the gradients*
        self**.**m1 **=** torch**.**zeros((len(self**.**t), 4)); self**.**m1[:, 0] **=** 1
        self**.**m2 **=** torch**.**zeros((len(self**.**t), 4)); self**.**m2[:, 1] **=** 1
        self**.**m3 **=** torch**.**zeros((len(self**.**t), 4)); self**.**m3[:, 2] **=** 1
        self**.**m4 **=** torch**.**zeros((len(self**.**t), 4)); self**.**m4[:, 3] **=** 1

8.让我们初始化网络和可学习的参数

*# # Initializing the neural network*
*# self.net_sidr = self.Net_sidr()*

*# # adding the parameters (alpha, beta, gamma) to the list of learnable parameters (basically, without this part only the neural network's weights will be updated, so we're telling the model to learn alpha, beta, and gamma as well)*
*# self.params = list(self.net_sidr.parameters())*
*# self.params.extend(list([self.alpha_tilda, self.beta_tilda, self.gamma_tilda]))***#>>>>
class** DINN(nn**.**Module):
    **def** __init__(self, t, S_data, I_data, D_data, R_data): *#[t,S,I,D,R]*
        super(DINN, self)**.**__init__()

        self**.**N **=** 59e6 *#population size*

        *#for the time steps, we need to convert them to a tensor, a float, and eventually to reshape it so it can be used as a batch*
        self**.**t **=** torch**.**tensor(t, requires_grad**=True**)
        self**.**t_float **=** self**.**t**.**float()
        self**.**t_batch **=** torch**.**reshape(self**.**t_float, (len(self**.**t),1)) *#reshape for batch* 

        *#for the compartments we just need to convert them into tensors*
        self**.**S **=** torch**.**tensor(S_data)
        self**.**I **=** torch**.**tensor(I_data)
        self**.**D **=** torch**.**tensor(D_data)
        self**.**R **=** torch**.**tensor(R_data)

        self**.**losses **=** [] *# here I saved the model's losses per epoch*

        *#setting the parameters*
        self**.**alpha_tilda **=** torch**.**nn**.**Parameter(torch**.**rand(1, requires_grad**=True**))
        self**.**beta_tilda **=** torch**.**nn**.**Parameter(torch**.**rand(1, requires_grad**=True**))
        self**.**gamma_tilda **=** torch**.**nn**.**Parameter(torch**.**rand(1, requires_grad**=True**))

        *#find values for normalization*
        self**.**S_max **=** max(self**.**S)
        self**.**I_max **=** max(self**.**I)
        self**.**D_max **=** max(self**.**D)
        self**.**R_max **=** max(self**.**R)
        self**.**S_min **=** min(self**.**S)
        self**.**I_min **=** min(self**.**I)
        self**.**D_min **=** min(self**.**D)
        self**.**R_min **=** min(self**.**R)

        *#normalize*
        self**.**S_hat **=** (self**.**S **-** self**.**S_min) **/** (self**.**S_max **-** self**.**S_min)
        self**.**I_hat **=** (self**.**I **-** self**.**I_min) **/** (self**.**I_max **-** self**.**I_min)
        self**.**D_hat **=** (self**.**D **-** self**.**D_min) **/** (self**.**D_max **-** self**.**D_min)
        self**.**R_hat **=** (self**.**R **-** self**.**R_min) **/** (self**.**R_max **-** self**.**R_min)        

        *#matrices (x4 for S,I,D,R) for the gradients*
        self**.**m1 **=** torch**.**zeros((len(self**.**t), 4)); self**.**m1[:, 0] **=** 1
        self**.**m2 **=** torch**.**zeros((len(self**.**t), 4)); self**.**m2[:, 1] **=** 1
        self**.**m3 **=** torch**.**zeros((len(self**.**t), 4)); self**.**m3[:, 2] **=** 1
        self**.**m4 **=** torch**.**zeros((len(self**.**t), 4)); self**.**m4[:, 3] **=** 1

        *#NN*
        self**.**net_sidr **=** self**.**Net_sidr()
        self**.**params **=** list(self**.**net_sidr**.**parameters())
        self**.**params**.**extend(list([self**.**alpha_tilda, self**.**beta_tilda, self**.**gamma_tilda]))

9.迫使参数在一定范围内

正如我之前提到的,我们可以让模型学习从负无穷大到无穷大的参数,但我们为什么要这样做呢?这些参数通常有一些合理的范围。这里我们强制这些范围

*#force parameters to be in the range of (-1, 1)*
@property
**def** alpha(self):
    **return** torch**.**tanh(self**.**alpha_tilda) 

@property
**def** beta(self):
    **return** torch**.**tanh(self**.**beta_tilda) 

@property
**def** gamma(self):
    **return** torch**.**tanh(self**.**gamma_tilda) 

*#note that you can easily play with that:*

*#force parameters to be in various ranges*
@property
**def** alpha(self):
    **return** torch**.**tanh(self**.**alpha_tilda) ***** 0.5 *# range of (-0.5, 0.5)*

@property
**def** beta(self):
    **return** torch**.**tanh(self**.**beta_tilda) ***** 0.01 **+** 1 *# range of (-0.99, 1.01)*

@property
**def** gamma(self):
    **return** torch**.**tanh(self**.**gamma_tilda) ***** 100 *# range of (-100, 100)*

*# Also note that we call these alpha, beta, and gamma (in comparison to "alpha_tilda", etc. from before)*

*# If you know the values of the parameters you just need to change this to:*
@property
**def** alpha(self):
    **return** self**.**alpha_tilda

@property
**def** beta(self):
    **return** self**.**beta_tilda

@property
**def** gamma(self):
    **return** self**.**gamma_tilda**#>>>>
class** DINN(nn**.**Module):
    **def** __init__(self, t, S_data, I_data, D_data, R_data): *#[t,S,I,D,R]*
        super(DINN, self)**.**__init__()

        self**.**N **=** 59e6 *#population size*

        *#for the time steps, we need to convert them to a tensor, a float, and eventually to reshape it so it can be used as a batch*
        self**.**t **=** torch**.**tensor(t, requires_grad**=True**)
        self**.**t_float **=** self**.**t**.**float()
        self**.**t_batch **=** torch**.**reshape(self**.**t_float, (len(self**.**t),1)) *#reshape for batch* 

        *#for the compartments we just need to convert them into tensors*
        self**.**S **=** torch**.**tensor(S_data)
        self**.**I **=** torch**.**tensor(I_data)
        self**.**D **=** torch**.**tensor(D_data)
        self**.**R **=** torch**.**tensor(R_data)

        self**.**losses **=** [] *# here I saved the model's losses per epoch*

        *#setting the parameters*
        self**.**alpha_tilda **=** torch**.**nn**.**Parameter(torch**.**rand(1, requires_grad**=True**))
        self**.**beta_tilda **=** torch**.**nn**.**Parameter(torch**.**rand(1, requires_grad**=True**))
        self**.**gamma_tilda **=** torch**.**nn**.**Parameter(torch**.**rand(1, requires_grad**=True**))

        *#find values for normalization*
        self**.**S_max **=** max(self**.**S)
        self**.**I_max **=** max(self**.**I)
        self**.**D_max **=** max(self**.**D)
        self**.**R_max **=** max(self**.**R)
        self**.**S_min **=** min(self**.**S)
        self**.**I_min **=** min(self**.**I)
        self**.**D_min **=** min(self**.**D)
        self**.**R_min **=** min(self**.**R)

        *#normalize*
        self**.**S_hat **=** (self**.**S **-** self**.**S_min) **/** (self**.**S_max **-** self**.**S_min)
        self**.**I_hat **=** (self**.**I **-** self**.**I_min) **/** (self**.**I_max **-** self**.**I_min)
        self**.**D_hat **=** (self**.**D **-** self**.**D_min) **/** (self**.**D_max **-** self**.**D_min)
        self**.**R_hat **=** (self**.**R **-** self**.**R_min) **/** (self**.**R_max **-** self**.**R_min)        

        *#matrices (x4 for S,I,D,R) for the gradients*
        self**.**m1 **=** torch**.**zeros((len(self**.**t), 4)); self**.**m1[:, 0] **=** 1
        self**.**m2 **=** torch**.**zeros((len(self**.**t), 4)); self**.**m2[:, 1] **=** 1
        self**.**m3 **=** torch**.**zeros((len(self**.**t), 4)); self**.**m3[:, 2] **=** 1
        self**.**m4 **=** torch**.**zeros((len(self**.**t), 4)); self**.**m4[:, 3] **=** 1

        *#NN*
        self**.**net_sidr **=** self**.**Net_sidr()
        self**.**params **=** list(self**.**net_sidr**.**parameters())
        self**.**params**.**extend(list([self**.**alpha_tilda, self**.**beta_tilda, self**.**gamma_tilda]))

    *#force parameters to be in a range*
    @property
    **def** alpha(self):
        **return** torch**.**tanh(self**.**alpha_tilda) 

    @property
    **def** beta(self):
        **return** torch**.**tanh(self**.**beta_tilda) 

    @property
    **def** gamma(self):
        **return** torch**.**tanh(self**.**gamma_tilda)

10.创建神经网络

**class** Net_sidr(nn**.**Module): *# input = [[t1], [t2]...[t100]] -- that is, a batch of timesteps* 
    **def** __init__(self):
        super(DINN**.**Net_sidr, self)**.**__init__()

        self**.**fc1**=**nn**.**Linear(1, 20) *#takes 100 t's*
        self**.**fc2**=**nn**.**Linear(20, 20)
        self**.**fc3**=**nn**.**Linear(20, 20)
        self**.**fc4**=**nn**.**Linear(20, 20)
        self**.**fc5**=**nn**.**Linear(20, 20)
        self**.**fc6**=**nn**.**Linear(20, 20)
        self**.**fc7**=**nn**.**Linear(20, 20)
        self**.**fc8**=**nn**.**Linear(20, 20)
        self**.**out**=**nn**.**Linear(20, 4) *#outputs S, I, D, R (100 S, 100 I, 100 D, 100 R --- since we have a batch of 100 timesteps)*

    **def** forward(self, t_batch):
        sidr**=**F**.**relu(self**.**fc1(t_batch))
        sidr**=**F**.**relu(self**.**fc2(sidr))
        sidr**=**F**.**relu(self**.**fc3(sidr))
        sidr**=**F**.**relu(self**.**fc4(sidr))
        sidr**=**F**.**relu(self**.**fc5(sidr))
        sidr**=**F**.**relu(self**.**fc6(sidr))
        sidr**=**F**.**relu(self**.**fc7(sidr))
        sidr**=**F**.**relu(self**.**fc8(sidr))
        sidr**=**self**.**out(sidr)
        **return** sidr**#>>>>
class** DINN(nn**.**Module):
    **def** __init__(self, t, S_data, I_data, D_data, R_data): *#[t,S,I,D,R]*
        super(DINN, self)**.**__init__()

        self**.**N **=** 59e6 *#population size*

        *#for the time steps, we need to convert them to a tensor, a float, and eventually to reshape it so it can be used as a batch*
        self**.**t **=** torch**.**tensor(t, requires_grad**=True**)
        self**.**t_float **=** self**.**t**.**float()
        self**.**t_batch **=** torch**.**reshape(self**.**t_float, (len(self**.**t),1)) *#reshape for batch* 

        *#for the compartments we just need to convert them into tensors*
        self**.**S **=** torch**.**tensor(S_data)
        self**.**I **=** torch**.**tensor(I_data)
        self**.**D **=** torch**.**tensor(D_data)
        self**.**R **=** torch**.**tensor(R_data)

        self**.**losses **=** [] *# here I saved the model's losses per epoch*

        *#setting the parameters*
        self**.**alpha_tilda **=** torch**.**nn**.**Parameter(torch**.**rand(1, requires_grad**=True**))
        self**.**beta_tilda **=** torch**.**nn**.**Parameter(torch**.**rand(1, requires_grad**=True**))
        self**.**gamma_tilda **=** torch**.**nn**.**Parameter(torch**.**rand(1, requires_grad**=True**))

        *#find values for normalization*
        self**.**S_max **=** max(self**.**S)
        self**.**I_max **=** max(self**.**I)
        self**.**D_max **=** max(self**.**D)
        self**.**R_max **=** max(self**.**R)
        self**.**S_min **=** min(self**.**S)
        self**.**I_min **=** min(self**.**I)
        self**.**D_min **=** min(self**.**D)
        self**.**R_min **=** min(self**.**R)

        *#normalize*
        self**.**S_hat **=** (self**.**S **-** self**.**S_min) **/** (self**.**S_max **-** self**.**S_min)
        self**.**I_hat **=** (self**.**I **-** self**.**I_min) **/** (self**.**I_max **-** self**.**I_min)
        self**.**D_hat **=** (self**.**D **-** self**.**D_min) **/** (self**.**D_max **-** self**.**D_min)
        self**.**R_hat **=** (self**.**R **-** self**.**R_min) **/** (self**.**R_max **-** self**.**R_min)        

        *#matrices (x4 for S,I,D,R) for the gradients*
        self**.**m1 **=** torch**.**zeros((len(self**.**t), 4)); self**.**m1[:, 0] **=** 1
        self**.**m2 **=** torch**.**zeros((len(self**.**t), 4)); self**.**m2[:, 1] **=** 1
        self**.**m3 **=** torch**.**zeros((len(self**.**t), 4)); self**.**m3[:, 2] **=** 1
        self**.**m4 **=** torch**.**zeros((len(self**.**t), 4)); self**.**m4[:, 3] **=** 1

        *#NN*
        self**.**net_sidr **=** self**.**Net_sidr()
        self**.**params **=** list(self**.**net_sidr**.**parameters())
        self**.**params**.**extend(list([self**.**alpha_tilda, self**.**beta_tilda, self**.**gamma_tilda]))

    *#force parameters to be in a range*
    @property
    **def** alpha(self):
        **return** torch**.**tanh(self**.**alpha_tilda) *#* 0.1 + 0.2*

    @property
    **def** beta(self):
        **return** torch**.**tanh(self**.**beta_tilda) *#* 0.01 + 0.05*

    @property
    **def** gamma(self):
        **return** torch**.**tanh(self**.**gamma_tilda) *#* 0.01 + 0.03*

    **class** Net_sidr(nn**.**Module): *# input = [[t1], [t2]...[t100]] -- that is, a batch of timesteps* 
        **def** __init__(self):
            super(DINN**.**Net_sidr, self)**.**__init__()

            self**.**fc1**=**nn**.**Linear(1, 20) *#takes 100 t's*
            self**.**fc2**=**nn**.**Linear(20, 20)
            self**.**fc3**=**nn**.**Linear(20, 20)
            self**.**fc4**=**nn**.**Linear(20, 20)
            self**.**fc5**=**nn**.**Linear(20, 20)
            self**.**fc6**=**nn**.**Linear(20, 20)
            self**.**fc7**=**nn**.**Linear(20, 20)
            self**.**fc8**=**nn**.**Linear(20, 20)
            self**.**out**=**nn**.**Linear(20, 4) *#outputs S, I, D, R (100 S, 100 I, 100 D, 100 R --- since we have a batch of 100 timesteps)*

        **def** forward(self, t_batch):
            sidr**=**F**.**relu(self**.**fc1(t_batch))
            sidr**=**F**.**relu(self**.**fc2(sidr))
            sidr**=**F**.**relu(self**.**fc3(sidr))
            sidr**=**F**.**relu(self**.**fc4(sidr))
            sidr**=**F**.**relu(self**.**fc5(sidr))
            sidr**=**F**.**relu(self**.**fc6(sidr))
            sidr**=**F**.**relu(self**.**fc7(sidr))
            sidr**=**F**.**relu(self**.**fc8(sidr))
            sidr**=**self**.**out(sidr)
            **return** sidr

11.现在有点复杂的部分,我们创建另一个函数,它采用时间步长批处理,并将其传递给神经网络

我们主要是想优化神经网络,还有这个有方程组的函数

**def** net_f(self, t_batch):

        *#pass the timesteps batch to the neural network*
        sidr_hat **=** self**.**net_sidr(t_batch)

        *#organize S,I,D,R from the neural network's output -- note that these are normalized values -- hence the "hat" part*
        S_hat, I_hat, D_hat, R_hat **=** sidr_hat[:,0], sidr_hat[:,1], sidr_hat[:,2], sidr_hat[:,3]**#>>>>
class** DINN(nn**.**Module):
    **def** __init__(self, t, S_data, I_data, D_data, R_data): *#[t,S,I,D,R]*
        super(DINN, self)**.**__init__()

        self**.**N **=** 59e6 *#population size*

        *#for the time steps, we need to convert them to a tensor, a float, and eventually to reshape it so it can be used as a batch*
        self**.**t **=** torch**.**tensor(t, requires_grad**=True**)
        self**.**t_float **=** self**.**t**.**float()
        self**.**t_batch **=** torch**.**reshape(self**.**t_float, (len(self**.**t),1)) *#reshape for batch* 

        *#for the compartments we just need to convert them into tensors*
        self**.**S **=** torch**.**tensor(S_data)
        self**.**I **=** torch**.**tensor(I_data)
        self**.**D **=** torch**.**tensor(D_data)
        self**.**R **=** torch**.**tensor(R_data)

        self**.**losses **=** [] *# here I saved the model's losses per epoch*

        *#setting the parameters*
        self**.**alpha_tilda **=** torch**.**nn**.**Parameter(torch**.**rand(1, requires_grad**=True**))
        self**.**beta_tilda **=** torch**.**nn**.**Parameter(torch**.**rand(1, requires_grad**=True**))
        self**.**gamma_tilda **=** torch**.**nn**.**Parameter(torch**.**rand(1, requires_grad**=True**))

        *#find values for normalization*
        self**.**S_max **=** max(self**.**S)
        self**.**I_max **=** max(self**.**I)
        self**.**D_max **=** max(self**.**D)
        self**.**R_max **=** max(self**.**R)
        self**.**S_min **=** min(self**.**S)
        self**.**I_min **=** min(self**.**I)
        self**.**D_min **=** min(self**.**D)
        self**.**R_min **=** min(self**.**R)

        *#normalize*
        self**.**S_hat **=** (self**.**S **-** self**.**S_min) **/** (self**.**S_max **-** self**.**S_min)
        self**.**I_hat **=** (self**.**I **-** self**.**I_min) **/** (self**.**I_max **-** self**.**I_min)
        self**.**D_hat **=** (self**.**D **-** self**.**D_min) **/** (self**.**D_max **-** self**.**D_min)
        self**.**R_hat **=** (self**.**R **-** self**.**R_min) **/** (self**.**R_max **-** self**.**R_min)        

        *#matrices (x4 for S,I,D,R) for the gradients*
        self**.**m1 **=** torch**.**zeros((len(self**.**t), 4)); self**.**m1[:, 0] **=** 1
        self**.**m2 **=** torch**.**zeros((len(self**.**t), 4)); self**.**m2[:, 1] **=** 1
        self**.**m3 **=** torch**.**zeros((len(self**.**t), 4)); self**.**m3[:, 2] **=** 1
        self**.**m4 **=** torch**.**zeros((len(self**.**t), 4)); self**.**m4[:, 3] **=** 1

        *#NN*
        self**.**net_sidr **=** self**.**Net_sidr()
        self**.**params **=** list(self**.**net_sidr**.**parameters())
        self**.**params**.**extend(list([self**.**alpha_tilda, self**.**beta_tilda, self**.**gamma_tilda]))

    *#force parameters to be in a range*
    @property
    **def** alpha(self):
        **return** torch**.**tanh(self**.**alpha_tilda) *#* 0.1 + 0.2*

    @property
    **def** beta(self):
        **return** torch**.**tanh(self**.**beta_tilda) *#* 0.01 + 0.05*

    @property
    **def** gamma(self):
        **return** torch**.**tanh(self**.**gamma_tilda) *#* 0.01 + 0.03*

    **class** Net_sidr(nn**.**Module): *# input = [[t1], [t2]...[t100]] -- that is, a batch of timesteps* 
        **def** __init__(self):
            super(DINN**.**Net_sidr, self)**.**__init__()

            self**.**fc1**=**nn**.**Linear(1, 20) *#takes 100 t's*
            self**.**fc2**=**nn**.**Linear(20, 20)
            self**.**fc3**=**nn**.**Linear(20, 20)
            self**.**fc4**=**nn**.**Linear(20, 20)
            self**.**fc5**=**nn**.**Linear(20, 20)
            self**.**fc6**=**nn**.**Linear(20, 20)
            self**.**fc7**=**nn**.**Linear(20, 20)
            self**.**fc8**=**nn**.**Linear(20, 20)
            self**.**out**=**nn**.**Linear(20, 4) *#outputs S, I, D, R (100 S, 100 I, 100 D, 100 R --- since we have a batch of 100 timesteps)*

        **def** forward(self, t_batch):
            sidr**=**F**.**relu(self**.**fc1(t_batch))
            sidr**=**F**.**relu(self**.**fc2(sidr))
            sidr**=**F**.**relu(self**.**fc3(sidr))
            sidr**=**F**.**relu(self**.**fc4(sidr))
            sidr**=**F**.**relu(self**.**fc5(sidr))
            sidr**=**F**.**relu(self**.**fc6(sidr))
            sidr**=**F**.**relu(self**.**fc7(sidr))
            sidr**=**F**.**relu(self**.**fc8(sidr))
            sidr**=**self**.**out(sidr)
            **return** sidr

    **def** net_f(self, t_batch):

            *#pass the timesteps batch to the neural network*
            sidr_hat **=** self**.**net_sidr(t_batch)

            *#organize S,I,D,R from the neural network's output -- note that these are normalized values -- hence the "hat" part*
            S_hat, I_hat, D_hat, R_hat **=** sidr_hat[:,0], sidr_hat[:,1], sidr_hat[:,2], sidr_hat[:,3]

12.我们现在想要得到每个隔间对时间的导数(这就是为什么我们必须做“hacky”雅可比部分)

我们这样做是为了插入方程组(你将在下一步看到)

*# #S_t*
*# sidr_hat.backward(self.m1, retain_graph=True)*
*# S_hat_t = self.t.grad.clone()*
*# self.t.grad.zero_()*

*# #I_t*
*# sidr_hat.backward(self.m2, retain_graph=True)*
*# I_hat_t = self.t.grad.clone()*
*# self.t.grad.zero_()*

*# #D_t*
*# sidr_hat.backward(self.m3, retain_graph=True)*
*# D_hat_t = self.t.grad.clone()*
*# self.t.grad.zero_()*

*# #R_t*
*# sidr_hat.backward(self.m4, retain_graph=True)*
*# R_hat_t = self.t.grad.clone()*
*# self.t.grad.zero_()***#>>>>
class** DINN(nn**.**Module):
    **def** __init__(self, t, S_data, I_data, D_data, R_data): *#[t,S,I,D,R]*
        super(DINN, self)**.**__init__()

        self**.**N **=** 59e6 *#population size*

        *#for the time steps, we need to convert them to a tensor, a float, and eventually to reshape it so it can be used as a batch*
        self**.**t **=** torch**.**tensor(t, requires_grad**=True**)
        self**.**t_float **=** self**.**t**.**float()
        self**.**t_batch **=** torch**.**reshape(self**.**t_float, (len(self**.**t),1)) *#reshape for batch* 

        *#for the compartments we just need to convert them into tensors*
        self**.**S **=** torch**.**tensor(S_data)
        self**.**I **=** torch**.**tensor(I_data)
        self**.**D **=** torch**.**tensor(D_data)
        self**.**R **=** torch**.**tensor(R_data)

        self**.**losses **=** [] *# here I saved the model's losses per epoch*

        *#setting the parameters*
        self**.**alpha_tilda **=** torch**.**nn**.**Parameter(torch**.**rand(1, requires_grad**=True**))
        self**.**beta_tilda **=** torch**.**nn**.**Parameter(torch**.**rand(1, requires_grad**=True**))
        self**.**gamma_tilda **=** torch**.**nn**.**Parameter(torch**.**rand(1, requires_grad**=True**))

        *#find values for normalization*
        self**.**S_max **=** max(self**.**S)
        self**.**I_max **=** max(self**.**I)
        self**.**D_max **=** max(self**.**D)
        self**.**R_max **=** max(self**.**R)
        self**.**S_min **=** min(self**.**S)
        self**.**I_min **=** min(self**.**I)
        self**.**D_min **=** min(self**.**D)
        self**.**R_min **=** min(self**.**R)

        *#normalize*
        self**.**S_hat **=** (self**.**S **-** self**.**S_min) **/** (self**.**S_max **-** self**.**S_min)
        self**.**I_hat **=** (self**.**I **-** self**.**I_min) **/** (self**.**I_max **-** self**.**I_min)
        self**.**D_hat **=** (self**.**D **-** self**.**D_min) **/** (self**.**D_max **-** self**.**D_min)
        self**.**R_hat **=** (self**.**R **-** self**.**R_min) **/** (self**.**R_max **-** self**.**R_min)        

        *#matrices (x4 for S,I,D,R) for the gradients*
        self**.**m1 **=** torch**.**zeros((len(self**.**t), 4)); self**.**m1[:, 0] **=** 1
        self**.**m2 **=** torch**.**zeros((len(self**.**t), 4)); self**.**m2[:, 1] **=** 1
        self**.**m3 **=** torch**.**zeros((len(self**.**t), 4)); self**.**m3[:, 2] **=** 1
        self**.**m4 **=** torch**.**zeros((len(self**.**t), 4)); self**.**m4[:, 3] **=** 1

        *#NN*
        self**.**net_sidr **=** self**.**Net_sidr()
        self**.**params **=** list(self**.**net_sidr**.**parameters())
        self**.**params**.**extend(list([self**.**alpha_tilda, self**.**beta_tilda, self**.**gamma_tilda]))

    *#force parameters to be in a range*
    @property
    **def** alpha(self):
        **return** torch**.**tanh(self**.**alpha_tilda) *#* 0.1 + 0.2*

    @property
    **def** beta(self):
        **return** torch**.**tanh(self**.**beta_tilda) *#* 0.01 + 0.05*

    @property
    **def** gamma(self):
        **return** torch**.**tanh(self**.**gamma_tilda) *#* 0.01 + 0.03*

    **class** Net_sidr(nn**.**Module): *# input = [[t1], [t2]...[t100]] -- that is, a batch of timesteps* 
        **def** __init__(self):
            super(DINN**.**Net_sidr, self)**.**__init__()

            self**.**fc1**=**nn**.**Linear(1, 20) *#takes 100 t's*
            self**.**fc2**=**nn**.**Linear(20, 20)
            self**.**fc3**=**nn**.**Linear(20, 20)
            self**.**fc4**=**nn**.**Linear(20, 20)
            self**.**fc5**=**nn**.**Linear(20, 20)
            self**.**fc6**=**nn**.**Linear(20, 20)
            self**.**fc7**=**nn**.**Linear(20, 20)
            self**.**fc8**=**nn**.**Linear(20, 20)
            self**.**out**=**nn**.**Linear(20, 4) *#outputs S, I, D, R (100 S, 100 I, 100 D, 100 R --- since we have a batch of 100 timesteps)*

        **def** forward(self, t_batch):
            sidr**=**F**.**relu(self**.**fc1(t_batch))
            sidr**=**F**.**relu(self**.**fc2(sidr))
            sidr**=**F**.**relu(self**.**fc3(sidr))
            sidr**=**F**.**relu(self**.**fc4(sidr))
            sidr**=**F**.**relu(self**.**fc5(sidr))
            sidr**=**F**.**relu(self**.**fc6(sidr))
            sidr**=**F**.**relu(self**.**fc7(sidr))
            sidr**=**F**.**relu(self**.**fc8(sidr))
            sidr**=**self**.**out(sidr)
            **return** sidr

    **def** net_f(self, t_batch):

            *#pass the timesteps batch to the neural network*
            sidr_hat **=** self**.**net_sidr(t_batch)

            *#organize S,I,D,R from the neural network's output -- note that these are normalized values -- hence the "hat" part*
            S_hat, I_hat, D_hat, R_hat **=** sidr_hat[:,0], sidr_hat[:,1], sidr_hat[:,2], sidr_hat[:,3]

            *#S_t*
            sidr_hat**.**backward(self**.**m1, retain_graph**=True**)
            S_hat_t **=** self**.**t**.**grad**.**clone()
            self**.**t**.**grad**.**zero_()

            *#I_t*
            sidr_hat**.**backward(self**.**m2, retain_graph**=True**)
            I_hat_t **=** self**.**t**.**grad**.**clone()
            self**.**t**.**grad**.**zero_()

            *#D_t*
            sidr_hat**.**backward(self**.**m3, retain_graph**=True**)
            D_hat_t **=** self**.**t**.**grad**.**clone()
            self**.**t**.**grad**.**zero_()

            *#R_t*
            sidr_hat**.**backward(self**.**m4, retain_graph**=True**)
            R_hat_t **=** self**.**t**.**grad**.**clone()
            self**.**t**.**grad**.**zero_()

13.我们现在取消了区间的标准化,因为我们实际上不希望方程改变,我们只是希望网络学习得更快

*# #unnormalize*
*# S = self.S_min + (self.S_max - self.S_min) * S_hat*
*# I = self.I_min + (self.I_max - self.I_min) * I_hat*
*# D = self.D_min + (self.D_max - self.D_min) * D_hat* 
*# R = self.R_min + (self.R_max - self.R_min) * R_hat***#>>>>
class** DINN(nn**.**Module):
    **def** __init__(self, t, S_data, I_data, D_data, R_data): *#[t,S,I,D,R]*
        super(DINN, self)**.**__init__()

        self**.**N **=** 59e6 *#population size*

        *#for the time steps, we need to convert them to a tensor, a float, and eventually to reshape it so it can be used as a batch*
        self**.**t **=** torch**.**tensor(t, requires_grad**=True**)
        self**.**t_float **=** self**.**t**.**float()
        self**.**t_batch **=** torch**.**reshape(self**.**t_float, (len(self**.**t),1)) *#reshape for batch* 

        *#for the compartments we just need to convert them into tensors*
        self**.**S **=** torch**.**tensor(S_data)
        self**.**I **=** torch**.**tensor(I_data)
        self**.**D **=** torch**.**tensor(D_data)
        self**.**R **=** torch**.**tensor(R_data)

        self**.**losses **=** [] *# here I saved the model's losses per epoch*

        *#setting the parameters*
        self**.**alpha_tilda **=** torch**.**nn**.**Parameter(torch**.**rand(1, requires_grad**=True**))
        self**.**beta_tilda **=** torch**.**nn**.**Parameter(torch**.**rand(1, requires_grad**=True**))
        self**.**gamma_tilda **=** torch**.**nn**.**Parameter(torch**.**rand(1, requires_grad**=True**))

        *#find values for normalization*
        self**.**S_max **=** max(self**.**S)
        self**.**I_max **=** max(self**.**I)
        self**.**D_max **=** max(self**.**D)
        self**.**R_max **=** max(self**.**R)
        self**.**S_min **=** min(self**.**S)
        self**.**I_min **=** min(self**.**I)
        self**.**D_min **=** min(self**.**D)
        self**.**R_min **=** min(self**.**R)

        *#normalize*
        self**.**S_hat **=** (self**.**S **-** self**.**S_min) **/** (self**.**S_max **-** self**.**S_min)
        self**.**I_hat **=** (self**.**I **-** self**.**I_min) **/** (self**.**I_max **-** self**.**I_min)
        self**.**D_hat **=** (self**.**D **-** self**.**D_min) **/** (self**.**D_max **-** self**.**D_min)
        self**.**R_hat **=** (self**.**R **-** self**.**R_min) **/** (self**.**R_max **-** self**.**R_min)        

        *#matrices (x4 for S,I,D,R) for the gradients*
        self**.**m1 **=** torch**.**zeros((len(self**.**t), 4)); self**.**m1[:, 0] **=** 1
        self**.**m2 **=** torch**.**zeros((len(self**.**t), 4)); self**.**m2[:, 1] **=** 1
        self**.**m3 **=** torch**.**zeros((len(self**.**t), 4)); self**.**m3[:, 2] **=** 1
        self**.**m4 **=** torch**.**zeros((len(self**.**t), 4)); self**.**m4[:, 3] **=** 1

        *#NN*
        self**.**net_sidr **=** self**.**Net_sidr()
        self**.**params **=** list(self**.**net_sidr**.**parameters())
        self**.**params**.**extend(list([self**.**alpha_tilda, self**.**beta_tilda, self**.**gamma_tilda]))

    *#force parameters to be in a range*
    @property
    **def** alpha(self):
        **return** torch**.**tanh(self**.**alpha_tilda) *#* 0.1 + 0.2*

    @property
    **def** beta(self):
        **return** torch**.**tanh(self**.**beta_tilda) *#* 0.01 + 0.05*

    @property
    **def** gamma(self):
        **return** torch**.**tanh(self**.**gamma_tilda) *#* 0.01 + 0.03*

    **class** Net_sidr(nn**.**Module): *# input = [[t1], [t2]...[t100]] -- that is, a batch of timesteps* 
        **def** __init__(self):
            super(DINN**.**Net_sidr, self)**.**__init__()

            self**.**fc1**=**nn**.**Linear(1, 20) *#takes 100 t's*
            self**.**fc2**=**nn**.**Linear(20, 20)
            self**.**fc3**=**nn**.**Linear(20, 20)
            self**.**fc4**=**nn**.**Linear(20, 20)
            self**.**fc5**=**nn**.**Linear(20, 20)
            self**.**fc6**=**nn**.**Linear(20, 20)
            self**.**fc7**=**nn**.**Linear(20, 20)
            self**.**fc8**=**nn**.**Linear(20, 20)
            self**.**out**=**nn**.**Linear(20, 4) *#outputs S, I, D, R (100 S, 100 I, 100 D, 100 R --- since we have a batch of 100 timesteps)*

        **def** forward(self, t_batch):
            sidr**=**F**.**relu(self**.**fc1(t_batch))
            sidr**=**F**.**relu(self**.**fc2(sidr))
            sidr**=**F**.**relu(self**.**fc3(sidr))
            sidr**=**F**.**relu(self**.**fc4(sidr))
            sidr**=**F**.**relu(self**.**fc5(sidr))
            sidr**=**F**.**relu(self**.**fc6(sidr))
            sidr**=**F**.**relu(self**.**fc7(sidr))
            sidr**=**F**.**relu(self**.**fc8(sidr))
            sidr**=**self**.**out(sidr)
            **return** sidr

    **def** net_f(self, t_batch):

            *#pass the timesteps batch to the neural network*
            sidr_hat **=** self**.**net_sidr(t_batch)

            *#organize S,I,D,R from the neural network's output -- note that these are normalized values -- hence the "hat" part*
            S_hat, I_hat, D_hat, R_hat **=** sidr_hat[:,0], sidr_hat[:,1], sidr_hat[:,2], sidr_hat[:,3]

            *#S_t*
            sidr_hat**.**backward(self**.**m1, retain_graph**=True**)
            S_hat_t **=** self**.**t**.**grad**.**clone()
            self**.**t**.**grad**.**zero_()

            *#I_t*
            sidr_hat**.**backward(self**.**m2, retain_graph**=True**)
            I_hat_t **=** self**.**t**.**grad**.**clone()
            self**.**t**.**grad**.**zero_()

            *#D_t*
            sidr_hat**.**backward(self**.**m3, retain_graph**=True**)
            D_hat_t **=** self**.**t**.**grad**.**clone()
            self**.**t**.**grad**.**zero_()

            *#R_t*
            sidr_hat**.**backward(self**.**m4, retain_graph**=True**)
            R_hat_t **=** self**.**t**.**grad**.**clone()
            self**.**t**.**grad**.**zero_() 

            *#unnormalize*
            S **=** self**.**S_min **+** (self**.**S_max **-** self**.**S_min) ***** S_hat
            I **=** self**.**I_min **+** (self**.**I_max **-** self**.**I_min) ***** I_hat
            D **=** self**.**D_min **+** (self**.**D_max **-** self**.**D_min) ***** D_hat      
            R **=** self**.**R_min **+** (self**.**R_max **-** self**.**R_min) ***** R_hat

14.最后(几乎),我们写出我们想要学习的方程组

这些几乎与最初的方程组相同,除了我们在这里有另一个规范化组件(例如"/ (self。S_max —自我。S_min)"),并且我们在这里使用偏导数(例如“S”相对于时间)。我们基本上是把每个隔间的右边移到左边(因此导数后面有负号)

*# f1_hat = S_hat_t - (-(self.alpha / self.N) * S * I)  / (self.S_max - self.S_min)*
*# f2_hat = I_hat_t - ((self.alpha / self.N) * S * I - self.beta * I - self.gamma * I ) / (self.I_max - self.I_min)*
*# f3_hat = D_hat_t - (self.gamma * I) / (self.D_max - self.D_min)*
*# f4_hat = R_hat_t - (self.beta * I ) / (self.R_max - self.R_min)***#>>>>
class** DINN(nn**.**Module):
    **def** __init__(self, t, S_data, I_data, D_data, R_data): *#[t,S,I,D,R]*
        super(DINN, self)**.**__init__()

        self**.**N **=** 59e6 *#population size*

        *#for the time steps, we need to convert them to a tensor, a float, and eventually to reshape it so it can be used as a batch*
        self**.**t **=** torch**.**tensor(t, requires_grad**=True**)
        self**.**t_float **=** self**.**t**.**float()
        self**.**t_batch **=** torch**.**reshape(self**.**t_float, (len(self**.**t),1)) *#reshape for batch*         *#for the compartments we just need to convert them into tensors*
        self**.**S **=** torch**.**tensor(S_data)
        self**.**I **=** torch**.**tensor(I_data)
        self**.**D **=** torch**.**tensor(D_data)
        self**.**R **=** torch**.**tensor(R_data) self**.**losses **=** [] *# here I saved the model's losses per epoch* *#setting the parameters*
        self**.**alpha_tilda **=** torch**.**nn**.**Parameter(torch**.**rand(1, requires_grad**=True**))
        self**.**beta_tilda **=** torch**.**nn**.**Parameter(torch**.**rand(1, requires_grad**=True**))
        self**.**gamma_tilda **=** torch**.**nn**.**Parameter(torch**.**rand(1, requires_grad**=True**)) *#find values for normalization*
        self**.**S_max **=** max(self**.**S)
        self**.**I_max **=** max(self**.**I)
        self**.**D_max **=** max(self**.**D)
        self**.**R_max **=** max(self**.**R)
        self**.**S_min **=** min(self**.**S)
        self**.**I_min **=** min(self**.**I)
        self**.**D_min **=** min(self**.**D)
        self**.**R_min **=** min(self**.**R) *#normalize*
        self**.**S_hat **=** (self**.**S **-** self**.**S_min) **/** (self**.**S_max **-** self**.**S_min)
        self**.**I_hat **=** (self**.**I **-** self**.**I_min) **/** (self**.**I_max **-** self**.**I_min)
        self**.**D_hat **=** (self**.**D **-** self**.**D_min) **/** (self**.**D_max **-** self**.**D_min)
        self**.**R_hat **=** (self**.**R **-** self**.**R_min) **/** (self**.**R_max **-** self**.**R_min)        *#matrices (x4 for S,I,D,R) for the gradients*
        self**.**m1 **=** torch**.**zeros((len(self**.**t), 4)); self**.**m1[:, 0] **=** 1
        self**.**m2 **=** torch**.**zeros((len(self**.**t), 4)); self**.**m2[:, 1] **=** 1
        self**.**m3 **=** torch**.**zeros((len(self**.**t), 4)); self**.**m3[:, 2] **=** 1
        self**.**m4 **=** torch**.**zeros((len(self**.**t), 4)); self**.**m4[:, 3] **=** 1 *#NN*
        self**.**net_sidr **=** self**.**Net_sidr()
        self**.**params **=** list(self**.**net_sidr**.**parameters())
        self**.**params**.**extend(list([self**.**alpha_tilda, self**.**beta_tilda, self**.**gamma_tilda])) *#force parameters to be in a range*
    @property
    **def** alpha(self):
        **return** torch**.**tanh(self**.**alpha_tilda) *#* 0.1 + 0.2* @property
    **def** beta(self):
        **return** torch**.**tanh(self**.**beta_tilda) *#* 0.01 + 0.05*

    @property
    **def** gamma(self):
        **return** torch**.**tanh(self**.**gamma_tilda) *#* 0.01 + 0.03* **class** Net_sidr(nn**.**Module): *# input = [[t1], [t2]...[t100]] -- that is, a batch of timesteps* 
        **def** __init__(self):
            super(DINN**.**Net_sidr, self)**.**__init__() self**.**fc1**=**nn**.**Linear(1, 20) *#takes 100 t's*
            self**.**fc2**=**nn**.**Linear(20, 20)
            self**.**fc3**=**nn**.**Linear(20, 20)
            self**.**fc4**=**nn**.**Linear(20, 20)
            self**.**fc5**=**nn**.**Linear(20, 20)
            self**.**fc6**=**nn**.**Linear(20, 20)
            self**.**fc7**=**nn**.**Linear(20, 20)
            self**.**fc8**=**nn**.**Linear(20, 20)
            self**.**out**=**nn**.**Linear(20, 4) *#outputs S, I, D, R (100 S, 100 I, 100 D, 100 R --- since we have a batch of 100 timesteps)* **def** forward(self, t_batch):
            sidr**=**F**.**relu(self**.**fc1(t_batch))
            sidr**=**F**.**relu(self**.**fc2(sidr))
            sidr**=**F**.**relu(self**.**fc3(sidr))
            sidr**=**F**.**relu(self**.**fc4(sidr))
            sidr**=**F**.**relu(self**.**fc5(sidr))
            sidr**=**F**.**relu(self**.**fc6(sidr))
            sidr**=**F**.**relu(self**.**fc7(sidr))
            sidr**=**F**.**relu(self**.**fc8(sidr))
            sidr**=**self**.**out(sidr)
            **return** sidr **def** net_f(self, t_batch):

            *#pass the timesteps batch to the neural network*
            sidr_hat **=** self**.**net_sidr(t_batch)

            *#organize S,I,D,R from the neural network's output -- note that these are normalized values -- hence the "hat" part*
            S_hat, I_hat, D_hat, R_hat **=** sidr_hat[:,0], sidr_hat[:,1], sidr_hat[:,2], sidr_hat[:,3] *#S_t*
            sidr_hat**.**backward(self**.**m1, retain_graph**=True**)
            S_hat_t **=** self**.**t**.**grad**.**clone()
            self**.**t**.**grad**.**zero_() *#I_t*
            sidr_hat**.**backward(self**.**m2, retain_graph**=True**)
            I_hat_t **=** self**.**t**.**grad**.**clone()
            self**.**t**.**grad**.**zero_() *#D_t*
            sidr_hat**.**backward(self**.**m3, retain_graph**=True**)
            D_hat_t **=** self**.**t**.**grad**.**clone()
            self**.**t**.**grad**.**zero_() *#R_t*
            sidr_hat**.**backward(self**.**m4, retain_graph**=True**)
            R_hat_t **=** self**.**t**.**grad**.**clone()
            self**.**t**.**grad**.**zero_()            *#unnormalize*
            S **=** self**.**S_min **+** (self**.**S_max **-** self**.**S_min) ***** S_hat
            I **=** self**.**I_min **+** (self**.**I_max **-** self**.**I_min) ***** I_hat
            D **=** self**.**D_min **+** (self**.**D_max **-** self**.**D_min) ***** D_hat      
            R **=** self**.**R_min **+** (self**.**R_max **-** self**.**R_min) ***** R_hat                    f1_hat **=** S_hat_t **-** (**-**(self**.**alpha **/** self**.**N) ***** S ***** I)  **/** (self**.**S_max **-** self**.**S_min)
            f2_hat **=** I_hat_t **-** ((self**.**alpha **/** self**.**N) ***** S ***** I **-** self**.**beta ***** I **-** self**.**gamma ***** I ) **/** (self**.**I_max **-** self**.**I_min)
            f3_hat **=** D_hat_t **-** (self**.**gamma ***** I) **/** (self**.**D_max **-** self**.**D_min)
            f4_hat **=** R_hat_t **-** (self**.**beta ***** I ) **/** (self**.**R_max **-** self**.**R_min)

15.最后,我们返回我们学到的想要优化的值— — S、I、D、R 和每个系统的区间(例如 f1_hat)

*# return f1_hat, f2_hat, f3_hat, f4_hat, S_hat, I_hat, D_hat, R_hat***#>>>>
class** DINN(nn**.**Module):
    **def** __init__(self, t, S_data, I_data, D_data, R_data): *#[t,S,I,D,R]*
        super(DINN, self)**.**__init__()

        self**.**N **=** 59e6 *#population size*

        *#for the time steps, we need to convert them to a tensor, a float, and eventually to reshape it so it can be used as a batch*
        self**.**t **=** torch**.**tensor(t, requires_grad**=True**)
        self**.**t_float **=** self**.**t**.**float()
        self**.**t_batch **=** torch**.**reshape(self**.**t_float, (len(self**.**t),1)) *#reshape for batch* 

        *#for the compartments we just need to convert them into tensors*
        self**.**S **=** torch**.**tensor(S_data)
        self**.**I **=** torch**.**tensor(I_data)
        self**.**D **=** torch**.**tensor(D_data)
        self**.**R **=** torch**.**tensor(R_data)

        self**.**losses **=** [] *# here I saved the model's losses per epoch*

        *#setting the parameters*
        self**.**alpha_tilda **=** torch**.**nn**.**Parameter(torch**.**rand(1, requires_grad**=True**))
        self**.**beta_tilda **=** torch**.**nn**.**Parameter(torch**.**rand(1, requires_grad**=True**))
        self**.**gamma_tilda **=** torch**.**nn**.**Parameter(torch**.**rand(1, requires_grad**=True**))

        *#find values for normalization*
        self**.**S_max **=** max(self**.**S)
        self**.**I_max **=** max(self**.**I)
        self**.**D_max **=** max(self**.**D)
        self**.**R_max **=** max(self**.**R)
        self**.**S_min **=** min(self**.**S)
        self**.**I_min **=** min(self**.**I)
        self**.**D_min **=** min(self**.**D)
        self**.**R_min **=** min(self**.**R)

        *#normalize*
        self**.**S_hat **=** (self**.**S **-** self**.**S_min) **/** (self**.**S_max **-** self**.**S_min)
        self**.**I_hat **=** (self**.**I **-** self**.**I_min) **/** (self**.**I_max **-** self**.**I_min)
        self**.**D_hat **=** (self**.**D **-** self**.**D_min) **/** (self**.**D_max **-** self**.**D_min)
        self**.**R_hat **=** (self**.**R **-** self**.**R_min) **/** (self**.**R_max **-** self**.**R_min)        

        *#matrices (x4 for S,I,D,R) for the gradients*
        self**.**m1 **=** torch**.**zeros((len(self**.**t), 4)); self**.**m1[:, 0] **=** 1
        self**.**m2 **=** torch**.**zeros((len(self**.**t), 4)); self**.**m2[:, 1] **=** 1
        self**.**m3 **=** torch**.**zeros((len(self**.**t), 4)); self**.**m3[:, 2] **=** 1
        self**.**m4 **=** torch**.**zeros((len(self**.**t), 4)); self**.**m4[:, 3] **=** 1

        *#NN*
        self**.**net_sidr **=** self**.**Net_sidr()
        self**.**params **=** list(self**.**net_sidr**.**parameters())
        self**.**params**.**extend(list([self**.**alpha_tilda, self**.**beta_tilda, self**.**gamma_tilda]))

    *#force parameters to be in a range*
    @property
    **def** alpha(self):
        **return** torch**.**tanh(self**.**alpha_tilda) *#* 0.1 + 0.2*

    @property
    **def** beta(self):
        **return** torch**.**tanh(self**.**beta_tilda) *#* 0.01 + 0.05*

    @property
    **def** gamma(self):
        **return** torch**.**tanh(self**.**gamma_tilda) *#* 0.01 + 0.03*

    **class** Net_sidr(nn**.**Module): *# input = [[t1], [t2]...[t100]] -- that is, a batch of timesteps* 
        **def** __init__(self):
            super(DINN**.**Net_sidr, self)**.**__init__()

            self**.**fc1**=**nn**.**Linear(1, 20) *#takes 100 t's*
            self**.**fc2**=**nn**.**Linear(20, 20)
            self**.**fc3**=**nn**.**Linear(20, 20)
            self**.**fc4**=**nn**.**Linear(20, 20)
            self**.**fc5**=**nn**.**Linear(20, 20)
            self**.**fc6**=**nn**.**Linear(20, 20)
            self**.**fc7**=**nn**.**Linear(20, 20)
            self**.**fc8**=**nn**.**Linear(20, 20)
            self**.**out**=**nn**.**Linear(20, 4) *#outputs S, I, D, R (100 S, 100 I, 100 D, 100 R --- since we have a batch of 100 timesteps)*

        **def** forward(self, t_batch):
            sidr**=**F**.**relu(self**.**fc1(t_batch))
            sidr**=**F**.**relu(self**.**fc2(sidr))
            sidr**=**F**.**relu(self**.**fc3(sidr))
            sidr**=**F**.**relu(self**.**fc4(sidr))
            sidr**=**F**.**relu(self**.**fc5(sidr))
            sidr**=**F**.**relu(self**.**fc6(sidr))
            sidr**=**F**.**relu(self**.**fc7(sidr))
            sidr**=**F**.**relu(self**.**fc8(sidr))
            sidr**=**self**.**out(sidr)
            **return** sidr

    **def** net_f(self, t_batch):

            *#pass the timesteps batch to the neural network*
            sidr_hat **=** self**.**net_sidr(t_batch)

            *#organize S,I,D,R from the neural network's output -- note that these are normalized values -- hence the "hat" part*
            S_hat, I_hat, D_hat, R_hat **=** sidr_hat[:,0], sidr_hat[:,1], sidr_hat[:,2], sidr_hat[:,3]

            *#S_t*
            sidr_hat**.**backward(self**.**m1, retain_graph**=True**)
            S_hat_t **=** self**.**t**.**grad**.**clone()
            self**.**t**.**grad**.**zero_()

            *#I_t*
            sidr_hat**.**backward(self**.**m2, retain_graph**=True**)
            I_hat_t **=** self**.**t**.**grad**.**clone()
            self**.**t**.**grad**.**zero_()

            *#D_t*
            sidr_hat**.**backward(self**.**m3, retain_graph**=True**)
            D_hat_t **=** self**.**t**.**grad**.**clone()
            self**.**t**.**grad**.**zero_()

            *#R_t*
            sidr_hat**.**backward(self**.**m4, retain_graph**=True**)
            R_hat_t **=** self**.**t**.**grad**.**clone()
            self**.**t**.**grad**.**zero_() 

            *#unnormalize*
            S **=** self**.**S_min **+** (self**.**S_max **-** self**.**S_min) ***** S_hat
            I **=** self**.**I_min **+** (self**.**I_max **-** self**.**I_min) ***** I_hat
            D **=** self**.**D_min **+** (self**.**D_max **-** self**.**D_min) ***** D_hat      
            R **=** self**.**R_min **+** (self**.**R_max **-** self**.**R_min) ***** R_hat        

            f1_hat **=** S_hat_t **-** (**-**(self**.**alpha **/** self**.**N) ***** S ***** I)  **/** (self**.**S_max **-** self**.**S_min)
            f2_hat **=** I_hat_t **-** ((self**.**alpha **/** self**.**N) ***** S ***** I **-** self**.**beta ***** I **-** self**.**gamma ***** I ) **/** (self**.**I_max **-** self**.**I_min)
            f3_hat **=** D_hat_t **-** (self**.**gamma ***** I) **/** (self**.**D_max **-** self**.**D_min)
            f4_hat **=** R_hat_t **-** (self**.**beta ***** I ) **/** (self**.**R_max **-** self**.**R_min)        

            **return** f1_hat, f2_hat, f3_hat, f4_hat, S_hat, I_hat, D_hat, R_hat

16.培训过程:

这里,我们只创建一个名为“train”的函数,它将需要多个历元来训练,并将训练网络

**def** train(self, n_epochs):
      *# train*
      print('\nstarting training...\n')

      **for** epoch **in** range(n_epochs):
        *# lists to hold the output (maintain only the final epoch)*
        S_pred_list **=** []
        I_pred_list **=** []
        D_pred_list **=** []
        R_pred_list **=** []

        *# we pass the timesteps batch into net_f*
        f1, f2, f3, f4, S_pred, I_pred, D_pred, R_pred **=** self**.**net_f(self**.**t_batch) *# net_f outputs f1_hat, f2_hat, f3_hat, f4_hat, S_hat, I_hat, D_hat, R_hat*

        self**.**optimizer**.**zero_grad() *#zero grad*

        *#append the values to plot later (note that we unnormalize them here for plotting)*
        S_pred_list**.**append(self**.**S_min **+** (self**.**S_max **-** self**.**S_min) ***** S_pred)
        I_pred_list**.**append(self**.**I_min **+** (self**.**I_max **-** self**.**I_min) ***** I_pred)
        D_pred_list**.**append(self**.**D_min **+** (self**.**D_max **-** self**.**D_min) ***** D_pred)
        R_pred_list**.**append(self**.**R_min **+** (self**.**R_max **-** self**.**R_min) ***** R_pred)

        *#calculate the loss --- MSE of the neural networks output and each compartment*
        loss **=** (torch**.**mean(torch**.**square(self**.**S_hat **-** S_pred))**+** 
                torch**.**mean(torch**.**square(self**.**I_hat **-** I_pred))**+**
                torch**.**mean(torch**.**square(self**.**D_hat **-** D_pred))**+**
                torch**.**mean(torch**.**square(self**.**R_hat **-** R_pred))**+**
                torch**.**mean(torch**.**square(f1))**+**
                torch**.**mean(torch**.**square(f2))**+**
                torch**.**mean(torch**.**square(f3))**+**
                torch**.**mean(torch**.**square(f4))
                ) 

        loss**.**backward()
        self**.**optimizer**.**step()
        self**.**scheduler**.**step() 

        *# append the loss value (we call "loss.item()" because we just want the value of the loss and not the entire computational graph)*
        self**.**losses**.**append(loss**.**item())

        **if** epoch **%** 1000 **==** 0:          
          print('\nEpoch ', epoch)

          print('alpha: (goal 0.191 ', self**.**alpha)
          print('beta: (goal 0.05 ', self**.**beta)
          print('gamma: (goal 0.0294 ', self**.**gamma)

          print('#################################')                

      **return** S_pred_list, I_pred_list, D_pred_list, R_pred_list**#>>>>
class** DINN(nn**.**Module):
    **def** __init__(self, t, S_data, I_data, D_data, R_data): *#[t,S,I,D,R]*
        super(DINN, self)**.**__init__()

        self**.**N **=** 59e6 *#population size*

        *#for the time steps, we need to convert them to a tensor, a float, and eventually to reshape it so it can be used as a batch*
        self**.**t **=** torch**.**tensor(t, requires_grad**=True**)
        self**.**t_float **=** self**.**t**.**float()
        self**.**t_batch **=** torch**.**reshape(self**.**t_float, (len(self**.**t),1)) *#reshape for batch* 

        *#for the compartments we just need to convert them into tensors*
        self**.**S **=** torch**.**tensor(S_data)
        self**.**I **=** torch**.**tensor(I_data)
        self**.**D **=** torch**.**tensor(D_data)
        self**.**R **=** torch**.**tensor(R_data)

        self**.**losses **=** [] *# here I saved the model's losses per epoch*

        *#setting the parameters*
        self**.**alpha_tilda **=** torch**.**nn**.**Parameter(torch**.**rand(1, requires_grad**=True**))
        self**.**beta_tilda **=** torch**.**nn**.**Parameter(torch**.**rand(1, requires_grad**=True**))
        self**.**gamma_tilda **=** torch**.**nn**.**Parameter(torch**.**rand(1, requires_grad**=True**))

        *#find values for normalization*
        self**.**S_max **=** max(self**.**S)
        self**.**I_max **=** max(self**.**I)
        self**.**D_max **=** max(self**.**D)
        self**.**R_max **=** max(self**.**R)
        self**.**S_min **=** min(self**.**S)
        self**.**I_min **=** min(self**.**I)
        self**.**D_min **=** min(self**.**D)
        self**.**R_min **=** min(self**.**R)

        *#normalize*
        self**.**S_hat **=** (self**.**S **-** self**.**S_min) **/** (self**.**S_max **-** self**.**S_min)
        self**.**I_hat **=** (self**.**I **-** self**.**I_min) **/** (self**.**I_max **-** self**.**I_min)
        self**.**D_hat **=** (self**.**D **-** self**.**D_min) **/** (self**.**D_max **-** self**.**D_min)
        self**.**R_hat **=** (self**.**R **-** self**.**R_min) **/** (self**.**R_max **-** self**.**R_min)        

        *#matrices (x4 for S,I,D,R) for the gradients*
        self**.**m1 **=** torch**.**zeros((len(self**.**t), 4)); self**.**m1[:, 0] **=** 1
        self**.**m2 **=** torch**.**zeros((len(self**.**t), 4)); self**.**m2[:, 1] **=** 1
        self**.**m3 **=** torch**.**zeros((len(self**.**t), 4)); self**.**m3[:, 2] **=** 1
        self**.**m4 **=** torch**.**zeros((len(self**.**t), 4)); self**.**m4[:, 3] **=** 1

        *#NN*
        self**.**net_sidr **=** self**.**Net_sidr()
        self**.**params **=** list(self**.**net_sidr**.**parameters())
        self**.**params**.**extend(list([self**.**alpha_tilda, self**.**beta_tilda, self**.**gamma_tilda]))

    *#force parameters to be in a range*
    @property
    **def** alpha(self):
        **return** torch**.**tanh(self**.**alpha_tilda) *#* 0.1 + 0.2*

    @property
    **def** beta(self):
        **return** torch**.**tanh(self**.**beta_tilda) *#* 0.01 + 0.05*

    @property
    **def** gamma(self):
        **return** torch**.**tanh(self**.**gamma_tilda) *#* 0.01 + 0.03*

    **class** Net_sidr(nn**.**Module): *# input = [[t1], [t2]...[t100]] -- that is, a batch of timesteps* 
        **def** __init__(self):
            super(DINN**.**Net_sidr, self)**.**__init__()

            self**.**fc1**=**nn**.**Linear(1, 20) *#takes 100 t's*
            self**.**fc2**=**nn**.**Linear(20, 20)
            self**.**fc3**=**nn**.**Linear(20, 20)
            self**.**fc4**=**nn**.**Linear(20, 20)
            self**.**fc5**=**nn**.**Linear(20, 20)
            self**.**fc6**=**nn**.**Linear(20, 20)
            self**.**fc7**=**nn**.**Linear(20, 20)
            self**.**fc8**=**nn**.**Linear(20, 20)
            self**.**out**=**nn**.**Linear(20, 4) *#outputs S, I, D, R (100 S, 100 I, 100 D, 100 R --- since we have a batch of 100 timesteps)*

        **def** forward(self, t_batch):
            sidr**=**F**.**relu(self**.**fc1(t_batch))
            sidr**=**F**.**relu(self**.**fc2(sidr))
            sidr**=**F**.**relu(self**.**fc3(sidr))
            sidr**=**F**.**relu(self**.**fc4(sidr))
            sidr**=**F**.**relu(self**.**fc5(sidr))
            sidr**=**F**.**relu(self**.**fc6(sidr))
            sidr**=**F**.**relu(self**.**fc7(sidr))
            sidr**=**F**.**relu(self**.**fc8(sidr))
            sidr**=**self**.**out(sidr)
            **return** sidr

    **def** net_f(self, t_batch):

            *#pass the timesteps batch to the neural network*
            sidr_hat **=** self**.**net_sidr(t_batch)

            *#organize S,I,D,R from the neural network's output -- note that these are normalized values -- hence the "hat" part*
            S_hat, I_hat, D_hat, R_hat **=** sidr_hat[:,0], sidr_hat[:,1], sidr_hat[:,2], sidr_hat[:,3]

            *#S_t*
            sidr_hat**.**backward(self**.**m1, retain_graph**=True**)
            S_hat_t **=** self**.**t**.**grad**.**clone()
            self**.**t**.**grad**.**zero_()

            *#I_t*
            sidr_hat**.**backward(self**.**m2, retain_graph**=True**)
            I_hat_t **=** self**.**t**.**grad**.**clone()
            self**.**t**.**grad**.**zero_()

            *#D_t*
            sidr_hat**.**backward(self**.**m3, retain_graph**=True**)
            D_hat_t **=** self**.**t**.**grad**.**clone()
            self**.**t**.**grad**.**zero_()

            *#R_t*
            sidr_hat**.**backward(self**.**m4, retain_graph**=True**)
            R_hat_t **=** self**.**t**.**grad**.**clone()
            self**.**t**.**grad**.**zero_() 

            *#unnormalize*
            S **=** self**.**S_min **+** (self**.**S_max **-** self**.**S_min) ***** S_hat
            I **=** self**.**I_min **+** (self**.**I_max **-** self**.**I_min) ***** I_hat
            D **=** self**.**D_min **+** (self**.**D_max **-** self**.**D_min) ***** D_hat      
            R **=** self**.**R_min **+** (self**.**R_max **-** self**.**R_min) ***** R_hat        

            f1_hat **=** S_hat_t **-** (**-**(self**.**alpha **/** self**.**N) ***** S ***** I)  **/** (self**.**S_max **-** self**.**S_min)
            f2_hat **=** I_hat_t **-** ((self**.**alpha **/** self**.**N) ***** S ***** I **-** self**.**beta ***** I **-** self**.**gamma ***** I ) **/** (self**.**I_max **-** self**.**I_min)
            f3_hat **=** D_hat_t **-** (self**.**gamma ***** I) **/** (self**.**D_max **-** self**.**D_min)
            f4_hat **=** R_hat_t **-** (self**.**beta ***** I ) **/** (self**.**R_max **-** self**.**R_min)        

            **return** f1_hat, f2_hat, f3_hat, f4_hat, S_hat, I_hat, D_hat, R_hat

    **def** train(self, n_epochs):
        *# train*
        print('\nstarting training...\n')

        **for** epoch **in** range(n_epochs):
            *# lists to hold the output (maintain only the final epoch)*
            S_pred_list **=** []
            I_pred_list **=** []
            D_pred_list **=** []
            R_pred_list **=** []

            *# we pass the timesteps batch into net_f*
            f1, f2, f3, f4, S_pred, I_pred, D_pred, R_pred **=** self**.**net_f(self**.**t_batch) *# net_f outputs f1_hat, f2_hat, f3_hat, f4_hat, S_hat, I_hat, D_hat, R_hat*

            self**.**optimizer**.**zero_grad() *#zero grad*

            *#append the values to plot later (note that we unnormalize them here for plotting)*
            S_pred_list**.**append(self**.**S_min **+** (self**.**S_max **-** self**.**S_min) ***** S_pred)
            I_pred_list**.**append(self**.**I_min **+** (self**.**I_max **-** self**.**I_min) ***** I_pred)
            D_pred_list**.**append(self**.**D_min **+** (self**.**D_max **-** self**.**D_min) ***** D_pred)
            R_pred_list**.**append(self**.**R_min **+** (self**.**R_max **-** self**.**R_min) ***** R_pred)

            *#calculate the loss --- MSE of the neural networks output and each compartment*
            loss **=** (torch**.**mean(torch**.**square(self**.**S_hat **-** S_pred))**+** 
                    torch**.**mean(torch**.**square(self**.**I_hat **-** I_pred))**+**
                    torch**.**mean(torch**.**square(self**.**D_hat **-** D_pred))**+**
                    torch**.**mean(torch**.**square(self**.**R_hat **-** R_pred))**+**
                    torch**.**mean(torch**.**square(f1))**+**
                    torch**.**mean(torch**.**square(f2))**+**
                    torch**.**mean(torch**.**square(f3))**+**
                    torch**.**mean(torch**.**square(f4))
                    ) 

            loss**.**backward()
            self**.**optimizer**.**step()
            self**.**scheduler**.**step() 

            *# append the loss value (we call "loss.item()" because we just want the value of the loss and not the entire computational graph)*
            self**.**losses**.**append(loss**.**item())

            **if** epoch **%** 1000 **==** 0:          
                print('\nEpoch ', epoch)

                print('alpha: (goal 0.191 ', self**.**alpha)
                print('beta: (goal 0.05 ', self**.**beta)
                print('gamma: (goal 0.0294 ', self**.**gamma)

                print('#################################')                

        **return** S_pred_list, I_pred_list, D_pred_list, R_pred_list

17.训练网络

**%%**timedinn **=** DINN(covid_data[0], covid_data[1], covid_data[2], covid_data[3], 
            covid_data[4]) *#in the form of [t,S,I,D,R]*learning_rate **=** 1e-6
optimizer **=** optim**.**Adam(dinn**.**params, lr **=** learning_rate)
dinn**.**optimizer **=** optimizerscheduler **=** torch**.**optim**.**lr_scheduler**.**CyclicLR(dinn**.**optimizer, base_lr**=**1e-5, max_lr**=**1e-3, step_size_up**=**1000, mode**=**"exp_range", gamma**=**0.85, cycle_momentum**=False**)dinn**.**scheduler **=** schedulerS_pred_list, I_pred_list, D_pred_list, R_pred_list **=** dinn**.**train(50000) *#train*

……..……..

starting training... Epoch  0
alpha: (goal 0.191  tensor([0.0290], grad_fn=<TanhBackward>)
beta: (goal 0.05  tensor([0.3816], grad_fn=<TanhBackward>)
gamma: (goal 0.0294  tensor([0.2541], grad_fn=<TanhBackward>)

……..……..

18.绘制损失和价值

plt**.**plot(dinn**.**losses[0:], color **=** 'teal')
plt**.**xlabel('Epochs')
plt**.**ylabel('Loss')

损失与时代。图片作者。

fig **=** plt**.**figure(figsize**=**(12,12))
ax **=** fig**.**add_subplot(111, facecolor**=**'#dddddd', axisbelow**=True**)
ax**.**set_facecolor('xkcd:white')

ax**.**plot(covid_data[0], covid_data[1], 'pink', alpha**=**0.5, lw**=**2, label**=**'Susceptible')
ax**.**plot(covid_data[0], S_pred_list[0]**.**detach()**.**numpy(), 'red', alpha**=**0.9, lw**=**2, label**=**'Susceptible Prediction', linestyle**=**'dashed')

ax**.**plot(covid_data[0], covid_data[2], 'violet', alpha**=**0.5, lw**=**2, label**=**'Infected')
ax**.**plot(covid_data[0], I_pred_list[0]**.**detach()**.**numpy(), 'dodgerblue', alpha**=**0.9, lw**=**2, label**=**'Infected Prediction', linestyle**=**'dashed')

ax**.**plot(covid_data[0], covid_data[3], 'darkgreen', alpha**=**0.5, lw**=**2, label**=**'Dead')
ax**.**plot(covid_data[0], D_pred_list[0]**.**detach()**.**numpy(), 'green', alpha**=**0.9, lw**=**2, label**=**'Dead Prediction', linestyle**=**'dashed')

ax**.**plot(covid_data[0], covid_data[4], 'blue', alpha**=**0.5, lw**=**2, label**=**'Recovered')
ax**.**plot(covid_data[0], R_pred_list[0]**.**detach()**.**numpy(), 'teal', alpha**=**0.9, lw**=**2, label**=**'Recovered Prediction', linestyle**=**'dashed')

ax**.**set_xlabel('Time /days')
ax**.**set_ylabel('Number')
ax**.**yaxis**.**set_tick_params(length**=**0)
ax**.**xaxis**.**set_tick_params(length**=**0)
ax**.**grid(b**=True**, which**=**'major', c**=**'black', lw**=**0.2, ls**=**'-')
legend **=** ax**.**legend()
legend**.**get_frame()**.**set_alpha(0.5)
**for** spine **in** ('top', 'right', 'bottom', 'left'):
    ax**.**spines[spine]**.**set_visible(**False**)
plt**.**show()

COVID 数据和 DINNs COVID 预测。图片作者。

给你。我们从不同群体计数形式的数据(即多少人易感、感染、死亡等)开始,训练一个神经网络来预测疾病将如何传播。不仅如此,我们还使用神经网络来预测疾病参数(如死亡率)。

接下来,你可以探索我们研究过的其他疾病(在页面顶部的 github 链接中),或者你可以针对一种新的疾病训练你自己的 DINN。

如果你对我的更多作品感兴趣,你可以看看我的 Github ,我的学者页面,或者我的网站

迪士尼电影是对的——我们都是特殊的,从统计数据来看也是如此

原文:https://towardsdatascience.com/disney-movies-were-right-we-are-all-special-and-statistically-so-3bb56e79ab71

一点点统计理论会让你相信“普通人”的想法其实只是一个大神话

来自 PixaBay 的 StockSnap 照片

我喜欢迪斯尼电影!!!

除了所有神奇的王国和惊险的冒险、神秘和浪漫,迪士尼电影最擅长的一件事就是让我相信,毕竟我是特别的。

如今,我们通过与他人的比较而生活。我们想要过令人兴奋的生活,不仅仅是一个“普通人”或“简”,如果你在技术行业工作,我们想要成为 10 倍于工程师的朋友,从一个初创企业赚到数百万美元,并在太浩湖买得起一栋度假别墅。因此,在所有不断提醒我们在单调的资本主义激烈竞争中是多么微不足道的电影中,当我们在周日晚上重新观看《玩具总动员》或《摇滚夏令营》等电影时,会有一种深深的舒适感。通过一系列振奋人心的歌曲,看到伍迪或米切尔(黛米·洛瓦托饰)认识到他们是特别的,他们应该因为做自己而得到同样多的爱和关注,这难道不好吗?然后到了星期一,你告诉自己,你又回到了激烈竞争的现实中:“那很有趣,但毕竟是电影。”

但是,如果我告诉你,从统计学上来说,这些电影是正确的,我们几乎不可能是“正常”和“普通”的,我们每个人都是真正特殊的人,那会怎么样?

正态分布的误导性

标准的正态分布。每对红色、黑色和黄色线显示了与平均值的 1、2 和 3 标准偏差差。图片作者。

我们大多数人都熟悉正态分布和著名的 68–95–99.7 规则,即 68%、95%和 99.7%的人口将位于平均值的 1、2 和 3 个标准偏差内。换句话说,几乎所有观察到的数据都极其接近平均值。

一个流行的例子是身高。美国成年男性(> 20 岁)的身高根据经验呈正态分布,均值为 5'7 ' ",标准差为 3 英寸。所以当你看到你周围的大多数人身高在 5 英尺 2 英寸到 6 英尺 2 英寸之间时,你应该不会感到惊讶。

这可能会让你想到:同样的逻辑,它也应该适用于其他属性,如体重、天赋、教育水平等。对吗?我们中的任何一个人,包括我自己,似乎都很可能在各方面都很一般,没有什么特别的东西可以展示。

嗯,有一件事你忘了考虑:维度。

维度的“祝福”

让我们用数学术语重新审视这个问题:假设每个人的有 n 个因子,每个因子都由一个独立的标准正态随机变量表示。也就是说,每个人的属性向量的形式为( x 1, x 2, x 2,…, xn ),其中每个 xi i.i.d .均为正态,均值为 0,方差为 1。每个因子的期望值都是 0,所以你可能会期望大部分点都堆在原点附近(0,0,0,…,0),对吧?

要想知道我们离平均值(0,0,0,…,0)有多远,一个方法就是看看欧几里德范数。为了简化计算,我使用了平方欧几里德范数,但想法是一样的。特别地,我们想要找到这个距离 P(d)的分布,并且看到分布质量最大化的地方或者大多数点位于哪里。

由于 d 来自于 n- i.i.d 标准正态分布的平方和,所以 d 遵循具有 n 自由度的卡方分布。为了研究 n 自由度卡方分布的行为,我们可以用均值为 n 方差为 2n 的正态分布来近似它。

既然很难去思考 d 是如何表现的,尤其是当 n 增加时,我们也可以改变我们的变量,看看 d /n 。直观上,我们可以想到围绕原点的多个半径值不同的球体。我们特别关心半径为 n,的球体,我们想看看相对于这个半径为 n 的虚拟球体,这些点是如何分布的。很容易看出找到 d /n 的分布为:

由于 d /n 遵循正态分布,我们也可以应用 68–95–99.7 规则:

这意味着 95%的点位于我们之前提到的半径为 nn的假想球的[1–2 σ,1+2σ]之间。随着 n 的增加,这个空间变得越来越小,也就是说这些点离球体越来越近。当 n → ∞时,所有点都位于假想球面上。

这是令人惊讶的,因为最初,我们认为大部分的点会靠近原点。事实上,它们中的大部分都在一个半径为 nn 维肥皂泡中,距离原点n

最初,我们认为大部分的点会靠近原点。事实上,它们中的大部分都在一个半径为 nn 维肥皂泡中,距离原点n

模拟

我知道不是每个人都喜欢数学证明,所以我会试着用一个小模拟来说服你。

作者图片

对于不同的 n 值,我们采样了大量的“人”——其中每个人都来自一个 n- 维正态分布。然后我们计算每个人到“平均乔”或原点的距离(欧几里德范数的平方)。这里的图表显示了当 n = 100 时的距离分布。

两条红线表示偏离平均值 2 个标准偏差,95%的点存在于此处。在下图中,我们看到当 n → ∞时,边界和 n 之间的比率接近 1,这表明大多数点位于距离原点 n 距离的非常小的区域内。

作者图片

另一种看待这个问题的方式显示在右图中:当我们达到 2000 维时,几乎所有从分布中采样的向量都具有接近 1 的幅度,并且几乎没有点接近原点。

一些警告:

所以我希望我已经让你相信:存在着或多或少属于平均身高或体重的人。但是,如果有 100 个或更多大致独立变化的标准来衡量人们,那么可能没有人在各方面都是平均的;与众不同的方式太多了。

然而,这种分析依赖于独立性的假设。我们需要想出很多独立的因素来说服自己,我们是特别的。大多数共同属性不一定如此,例如,身高和体重可以通过遗传相关联,你从事各种运动的能力通过你的体格相关联,等等。

但是,至少现在,每当你看到另一部告诉你要相信特殊自我的劣质迪士尼电影时,至少你知道这种说法有一些统计数据支持。

如果你喜欢这篇文章,你可能也会喜欢我的另一篇关于有趣的统计事实和经验法则的文章

对于其他深潜分析:

这个项目的完整代码(模拟和图形)可以在我的 Github 中找到。

消除数字化和数据分析的刻板印象

原文:https://towardsdatascience.com/dispelling-stereotypes-of-digitalization-and-data-analytics-59731c52406d

企业遭受的 7 个误解

图片来源:https://unsplash.com/photos/pREq0ns_p_E?utm_source=unsplash&UTM _ medium = referral&UTM _ content = creditShareLink

在这篇文章中,我想更详细地探讨大多数企业遭受的七大数据分析和数字化误解。让我们开始吧!

不幸的是,市场上的一部分公司仍然认为“数字化转型”、“大数据”和“商业分析”只是花哨的词汇。有些人低估了他们的影响力,有些人高估了他们的影响力。结果,公司遇到了严重的问题。在很大程度上,这种状况与对数字化过程本质的误解和管理层的刻板思维有关。为了改变这种情况,重要的是要摆脱这些围绕数据分析的核心误解、恐惧和神话。

我作为旨在数字化东欧公司的项目的集成商和第三方顾问的多年经验使我能够区分至少 7 种根深蒂固的误解,这些误解通常会妨碍数据管理系统、业务分析和数字化转型的成功实施。

“我比你的报告更清楚”

这种误解经常发生在老派管理者身上。他们习惯于根据自己的经验和直觉亲自做决定。他们更喜欢解释直接从他们的下属那里得到的信息。

一方面,如果一个人是他们业务中的专家,他们对有关他们业务的数据的解释对他们很有用。他们关注时事、生产管理以及员工报告和转发信息的环境。这种模式最常见于国家机构和市政府机构。

然而,另一方面,实践表明,这种方法只能到此为止。主要的问题是,上级接受的不是可靠的证据和实际数据(数字、值和参数),而是来自其他人的主观意见和评价。结果导演大多听到了他们想听到的。此外,下属做任何事情都不会让他们不高兴。他们对老板的期望很敏感,总的来说,通过不告诉他们的上司所有的事情或给他们错误的信息等来迎合这些期望。

这正是可以通过数字化解决的问题,因为它展示了真实的画面。基于客观数据做出正确的管理决策要容易得多。

“一旦我设置了分析功能,如何运营我的业务将变得一目了然”

第二个错误信念本质上与第一个相反。在这种情况下,决策者将他的赌注完全放在从公司系统收集的分析、数据和报告上。这位领导者真诚地认为,数字化可以解决他们业务中的所有问题,让一切恢复正常。这可能包括了解每个部门发生了什么,安排有效的销售系统或规划整个生产计划。但这只是一种错觉。

事实上,分析和报告只是从大量数据中收集必要信息的工具。在这里,由企业设定的数据需求成为最重要的问题。

例如,分析可以帮助获得公司销售动态的信息,以及每个销售阶段的转化率。例如,下属打电话的次数,发送的商业要约的次数,或者召开的会议和处理的销售的次数。所有这些数据都以某种形式呈现出来,例如,销售渠道各阶段的直方图。该图显示,在发送要约和举行会议之间,转换率降低。这是一条有用的信息,但它真正暴露了什么?如何解读这些数据?解释取决于决策者。他们的职责是将报告中的事实与手头的任务进行比较,以及他们应该做出什么样的决定。这就是为什么 BI 系统在俄语行话中被称为“支持管理决策的系统”。他们的任务是提供数据。

市场上的少数专家认为,数据解释将很快由独立的、经过专门训练的神经网络来完成。他们将能够在任何特定情况下为企业提供具体的管理决策。但实践表明,这是幼稚的。每个公司都是独一无二的,不可能创建一个自动化的数据解释系统。曾有一些俄罗斯开发者试图创造类似的人工智能系统,然而,它们从未出现在市场上。

“我们将建立自己的分析系统,毕竟我们有自己的程序员”

通常,主管或 IT 部门主管确信他们的公司有能力独立构建分析系统,而无需第三方开发人员或顾问的参与。这种组织工作程序的方式要求员工在工作时间积极使用 Excel。所述企业的员工中也有大量的 IT 专家,他们“掌握”(或者更可能只是学习)Power BI 或类似工具,并且如果他们的上级分配任务,他们准备执行集成过程。尽管如此,80%的此类项目还是失败了。剩下的 20%是雇佣具有独特技能的 IT 专家团队的组织(例如,IT 公司,包括电信和金融科技领域的公司)和对分析系统几乎没有需求的停滞企业。

一般来说,BI 系统是为了业务发展而实施的。它有助于对其活动进行实验,并观察其结果(无论是好是坏)。也就是说,一个系统也应该随着它的公司一起发展。这可以是架构上的、技术上的,或者通过数据处理方法(报告、分析、预测)。

分析系统应该以正确的方式设计。首先,它应该是可扩展的。开发人员必须精通建模,因为任何业务数据都包含特定的模型。从技术的角度来看,这个模型需要仔细地构建到 BI 系统中,数据存储应该结构化,应该创建正确的连接,应该适当地安排数据提取,应该刷新数据等等。通常,这种技能在程序员中很难找到,即使是在大型 IT 公司。

“我们将把来自不同系统的大量数据合并到一个数据库中,一切都会变得清晰”

这一条直接关系到数据的质量。当一家公司的数据完全混乱时,这种情况并不罕见。例如,相同的事物(例如,商品项目和项目名称)在不同的公司系统中可以被不同地命名,单独的不相连的项目主数据源被维护,等等。尽管如此,IT 主管可能仍然认为,只要将这些细分和原始数据加载到一个数据库中,事情就会变得清晰,因此,报告将变得透明和高质量。

事实上,汇集各种系统的数据并不能解决主要问题。这个问题本质上不是技术问题,而是方法问题。了解一家公司是否在销售、营销、广告和客户服务中运行统一的物品主数据系统,甚至在商店中打印物品价格标签,这一点很重要。如果没有,则应在分析实施阶段之前创建一个统一的主数据体,它将成为所有其他公司系统中标准化项目名称的来源。根据我的经验,管理主数据和监管参考数据在俄罗斯商界是一个非常热门的话题。不解决这些问题,任何商务智能系统都无法运行。

“分析非常昂贵,我需要一次性投入大量资金”

在考虑数字化的公司中,经常有这样一种观点,即实施分析系统是一种奢侈。部分原因是这样。事实上,集成商自己使用了书中的每一个技巧来维持一个高的项目价格,在研究他们客户企业的内部流程的过程中,用高劳动投入来解释它。他们检查数据来源、存储和提取规则、比较项目名称、计算公式等。之后,客户肯定会开始害怕在这个方向投资,因为他们对自己的需求没有清晰的认识。

无需建立独立的 It 基础设施,也无需外界帮助,只需从企业系统中提取数据,就可以构建一些基本的业务分析。在这种情况下,费用会低很多。与此同时,这项工作可以由业务分析和部门主管来完成。如果有必要,他们可以请求 IT 专家帮助开发自动化的数据提取工具。人们还可以基于这些工具(平台、可视化、仪表板等)来调整分析工作。)对于在 BI 领域迈出第一步的公司来说,这是一种有益的体验,也是一种非常划算的解决方案。此外,他们的专家可以自己检查数据,并了解他们对分析系统的要求。完成后,他们可能会考虑全面的基础架构。

“利用分析的好处在于节省劳动力成本”

通常,许多数字化项目都与现有报告的自动化和消除手工劳动有关,同时都需要数据分析。IT 主管经常坚持根据节省的劳动力成本来评估集成新系统的经济效益。但实际上,双系统的影响并不仅限于减少工资支出。这只是冰山一角。

分析系统提供了新的商业机会,因为它使数据可访问,发现一致性,并产生意想不到的洞察力。它有助于决策者从本质上做出完全不同的管理决策。例如,他们可以选择合适的城市开设分支机构,或者用在线视频会议取代面对面的员工会议。很有可能这些改进将为公司带来显著的经济效益,而不仅仅是通过简化准备报告而获得的劳动力成本的降低。因此,企业可能开始获得更多利润,并获得进一步增长的动力。

“我将雇用一名集成商,他们将为我提供最好的数据管理方法”

客户中很常见的一个错误是完全不参与分析系统的开发和集成过程。在这种情况下,企业完全依赖于所选择的系统集成商的专业知识。这样,他们只是编写一个通用的技术规范,而不参与项目的后续阶段。

实际上,在这种情况下,开发人员往往对解决客户任务的基本要素和原则一无所知,他们只是看到了赚钱的机会。因此,承包商只会进行试验,费用由客户承担,这将影响项目的质量和成功率。同样重要的是,团队必须在主题领域经验丰富。仅仅能够使用模板构建 BI 系统是不够的。拥有相关领域的专业知识很重要。每个行业都有自己成熟的实践、自己的一套工具和自己的连接系统(例如 SAP 被用作 ERP),因此承包商应该对这些方面有很好的理解。

由于这个原因,客户必须密切监视开发人员的工作,如果双方开始有分歧,他们的对话变成纯粹的形式,就没有成功的机会。

总而言之,如果你希望你的企业成功,我会鼓励你不要低估数据分析和整个数字化过程的力量。

有问题吗?在ab @ a17 . team不要犹豫

在 Streamlit 中显示和下载 PDF:一个博客用例

原文:https://towardsdatascience.com/display-and-download-pdf-in-streamlit-a-blog-use-case-5fc1ac87d4b1

简化用例想法—创建一个博客来记录和展示您的工作

图片由皮克斯拜(作者修改)

介绍

Streamlit 是一个开源的 python 框架,主要由数据科学家和数据工程师用来快速轻松地构建机器学习和数据 app。然而,Streamlit 的用例当然不仅限于构建数据应用。

例如,作为一名有抱负的数据科学家,您也可以使用 Streamlit 创建一个个人网站来展示和推广您的个人资料和数据科学工作。你可以在 Streamlit 应用程序中以 PDF 文档的形式显示你的简历,或者如果你写了一些数据科学文章,你也可以创建一个博客页面,并将你的 PDF 帖子嵌入到你的 Streamlit 应用程序中,如下所示。

作者图片

由于我已经在之前的文章中分享了如何使用streamlit-option-menu创建一个多页面的 streamlit 应用程序,所以我不会在这里花时间解释如何创建导航栏以及应用程序中的所有页面。相反,我将重点向您展示如何在 Streamlit 中显示和下载 PDF 文档,以及如何应用我们学到的知识在该应用程序中创建“博客”页面。

https://medium.com/codex/create-a-multi-page-app-with-the-new-streamlit-option-menu-component-3e3edaf7e7ad

下面是一个 YouTube 视频短片,演示了博客页面的外观:

作者提供的 YouTube 视频演示

如何在 Streamlit 中嵌入 PDF

要在 Streamlit 中嵌入/显示 PDF 文档,我们需要做以下三件事:

1.打开 PDF 文件,并使用 base64 编码对其进行编码(如果没有 base64 编码,您的 PDF 文件将无法正常显示,即 404 错误)。此外,base64 编码可以很好地处理 2MB 以下的文件,所以如果您的 PDF 文件大小大于 2MB,您应该在编码前先压缩

with open(file_path,"rb") as f: base64_pdf = base64.b64encode(f.read()).decode('utf-8')

2.在 HTML iframe 中嵌入 base64 编码的文件。您可以使用widthheight参数来调整 iframe 的宽度和高度。

pdf_display = f'<iframe src="data:application/pdf;base64,{base64_pdf}" width="800" height="800" type="application/pdf"></iframe>'

3.使用st.markdown在 Streamlit 中渲染 pdf 文档

st.markdown(pdf_display, unsafe_allow_html=True)

将这三个步骤放在一起,我们可以定义一个在 Streamlit 中显示 PDF 文档的函数:

作者图片

适合页面(图片由作者提供)

如何在 Streamlit 中下载 PDF

为了使用户能够从 Streamlit 应用程序下载 PDF 文件,我们可以使用st.download_button()来完成。

创建“博客”页面

现在我们知道了如何嵌入 PDF 文档来简化它,并使用户能够下载它,让我们将刚刚学到的东西付诸实践,并创建演示应用程序中显示的“博客”页面。

在博客页面上,我们将列出三篇(或更多)文章。对于每个帖子,我们将显示帖子的特色图片以及标题和一些介绍性文本。用户可以点击链接选择继续阅读介质上的剩余帖子。用户可以选择打开 PDF 版本的帖子,并在应用程序中阅读。他们也可以关闭文档或将其下载到自己的计算机上。

第 1–5 行:我们定义了一个函数,稍后将调用该函数在 Streamlit 中显示 PDF 文档。

第 7–18 行:我们为第一篇文章添加了特色图片和介绍性文字。我们使用st.container()来并排放置图像和文本。在第 18 行,我们还使用st.markdown()来包含文章最初发布的 URL。

第 20–32 行:我们创建了三个按钮,使用户能够打开、关闭或下载 PDF 文档。确保在第 23 行将 PDF 文件放在与 streamlit 应用程序的 python 文件相同的文件夹中。如果您的 PDF 文件大于 2MB,您需要先压缩文件并将其大小减小到 2MB 以下。

第 34–35 行:我们使用 Streamlit 的新定制组件streamlit-text-rating添加了一个小的反馈部分。该组件使用户能够对他们在 Steamlit 应用程序中阅读的文本内容进行评级。您可以使用以下命令安装这个新组件:

pip install streamlit-text-rating

作者图片

这就对了。您已将第一篇文章添加到“博客”页面。使用相同的代码结构,您可以轻松地向“博客”页面添加更多的文章。你可以在我的 Github repo 中找到创建导航菜单和博客页面的完整代码。

为了让事情变得更有趣,你也可以将你的简历作为 PDF 文档嵌入到“关于”页面,并在其他页面展示你的项目。下一次,当你面试你梦想中的数据科学职位时,你可以自豪地在你用 Streamlit 创建的个人网站上展示你出色的工作!

感谢阅读!希望你喜欢这篇文章,并渴望尝试使用 Streamlit 创建自己的网站!

你可以通过这个推荐链接注册 Medium 会员(每月 5 美元)来获得我的作品和 Medium 的其他内容。通过这个链接注册,我将收到你的会员费的一部分,不需要你额外付费。谢谢大家!

剖析咖啡萃取:第 2 部分

原文:https://towardsdatascience.com/dissecting-coffee-extraction-part-2-74eb35805089

咖啡数据科学

分层分割提取

在之前的中,我看了一些干咖啡豆的提取,一些镜头在层与层之间有一个纸过滤器。我用这个纸过滤器干净利落地切开圆盘,逐层检查咖啡萃取物。这些结果与以前的结果一致,以前的结果显示,对于普通的咖啡果,顶部比底部提取得快得多。然而,对于断奏圆盘,底部(精细层)提取速度比顶部快。

我收集了更多的数据,但是是在一台新机器上(像样的咖啡机)。以前,我使用杠杆机器,这意味着即使在我停止击球后,仍有更多的水通过冰球。有了像样的浓缩咖啡机,我可以只用稍微多一点的水通过冰球来检查冰球。

所有图片由作者提供

设备/技术

浓缩咖啡机 : 像样的浓缩咖啡机

咖啡研磨机 : 小生零同道码SPP 毛刺

咖啡:家庭烘焙咖啡,中杯(第一口+ 1 分钟)

镜头准备:断奏夯实(常规)和断奏

预输注:长,约 25 秒

输液:压力脉动

过滤篮 : 20g VST

其他设备: Atago TDS 计Acaia Pyxis 秤

绩效指标

我使用提取率(EY ),通过干燥圆盘并称重干燥的圆盘来计算。

首先,我比较了散点图中的顶部和底部。断奏的底部比顶部有更高的 EY,但常规的底部有更低的 EY。

我整理了这些数据,并把它们绘制成了线图。两个断奏的镜头没有跟随这个趋势,因为它们引导了很多。

该数据给出了一些增加 EY 的指示。断奏圆盘的底部提取得很好,这意味着如果增加顶部 EY,底部可能会过度提取。但这也是一个更清楚的迹象,表明顶部可以更好地设计,以增加提取。

此外,常规圆盘的底部没有被提取。这也可能是顶层卡在底层的问题。这也可以暗示水是如何从顶部和底部流过的。如果水在顶部更均匀,但在底部开始形成通道(即 donuting),那么底部的水就更少。这可以使用用过的圆盘上的径向切口来进一步研究。

如果你愿意,可以在推特、 YouTubeInstagram 上关注我,我会在那里发布不同机器上的浓缩咖啡照片和浓缩咖啡相关的视频。你也可以在 LinkedIn 上找到我。也可以关注我在订阅

我的进一步阅读:

我未来的书

我的链接

浓缩咖啡系列文章

工作和学校故事集

剖析生日悖论

原文:https://towardsdatascience.com/dissecting-the-birthday-paradox-c26754aff6b5

统计学并不总是很直观

照片由阿迪·戈尔茨坦Unsplash

介绍

试着回答这个问题,但不要实际算出来。

你需要多少人在一个房间里来确保有 50%的机会让两个人过同一天生日?

你会纯粹凭直觉回答什么?合理的答案应该是什么样的?50 个人?100?183?365?

当我遇到这个问题时,我猜测答案是大约 70-80 人。之后我算了一下,答案让我大吃一惊。

显然只需要 23 个人!

让我们证明这一点。

目录

* The Math -- proving it mathematically
* Exploring Real World Data -- checking our theory
* Why this isn't so intuitive
* Code and References

数学

假设

我们将做以下假设来简化问题:

  • 没有闰年
  • 每一天都同样可能是一个潜在的生日

第一个假设让我们假设有 365 个可能的出生日期。

第二个假设意味着我们正在解决最坏的情况,因为任何不平衡都会使两个日期更有可能冲突(比如,两个生日在同一天)。

微不足道的案子

假设我们正好有两个人。他们同一天生日的概率有多大?

第一个人可以在一年中的任何一天过生日,但是第二个人需要和第一个人在同一天过生日。

所以概率可以这样算出来:

P_2 = number of possible days for the 1st person * number of possibilities for the 2nd Person / total number of possibilities.

这相当于:

P_2 = 365 * 1 / 365 * 365 = 1/365

这只是 0.3%左右。

对于人数较多的人

当我们有更多的人要考虑时,比如说,5 个人,首先计算没有生日匹配的概率,然后找到这个概率的补集就更容易了。

对于 5 个人:

  • 第一个人可以有 365 个生日中的任何一个——365 种方式
  • 第二个人可以拥有任何东西,除了第一个人的生日——364 种方式。
  • 类似地,第三、第四和第五个人有 363、362 和 361 个可能的生日
  • 我们将它们相乘,然后除以可能日期的总数——365 乘以 365..(5 次)—^ 5 区 365 号
  • 我们从 1 中减去这个结果,得到匹配的概率,因为我们刚刚计算的是没有匹配

我们在这里的结果是:

P(A) = 1 - 365 * 364 * 363 * 362 * 361 / 365 ^ 5
     = 0.027

通解

让我们为一群人解决这个问题。

按照与上面相同的逻辑,我们将首先计算补数,然后从1中减去它,以获得我们想要的答案。第一个人可以在任何一天生日— 365可能性。除了第一个人的生日之外,第二个人可以在任何一天过生日——364可能性,等等。

P_n(A)' = 365*364*363*...(365-(n-1)) / 365 ^ n

从鸽笼原理(甚至通过直观的方法),对于n > 365P_n == 1

这可以像这样进一步简化。

计算互补概率—维基百科

P_n(A)' = (365 p n) / 365 ^ n

其中(365 p n)代表总数365n项的排列数。

因此,为了计算我们实际需要什么,我们有:

P_n(A) = 1 - P_n(A)'

对于n = 23,我们得到的刚好超过50%(准确地说是50.7%),这与我们最初的答案一致,即需要 23 个人才能有50%的生日匹配机会。

真实世界的数据

现在让我们来看一些真实的数据,看看我们的假设是否足以模拟真实世界的场景。

我们将使用来自538的出生日期数据集。它链接在下面的参考文献中。这是在知识共享署名 4.0 国际许可下,正如他们的网站上提到的。

数据集是 2000 年至 2014 年间的出生频率,看起来像这样。

数据集的几行-按作者分类的图像

一个更广泛的 EDA 将被添加到附带的笔记本,链接在最后。

预处理

很难找到如此干净的数据集。我们所要做的就是删除一些列并创建一个日期时间列。

# drop an irrelevant column
df = df.drop('day_of_week', axis=1)# rename day_of_month to day -- helps with the datetime conversion
df = df.rename({"date_of_month": "day"}, axis=1)# convert year, month and day into datetime
df['date'] = pd.to_datetime(df[['year', 'month', 'day']])# drop the now redundant year, month and day cols
df = df.drop(['year', 'month', 'day'], axis=1)

我们的数据框df现在看起来像这样。

经过处理的数据框-作者提供的图像

分析数据

我们将使用calmap库来绘制 2014 年的出生热图。该库接受用 DateTimeIndex 索引的数据,所以让我们先创建它。

# accessing the rows belonging for 2014
df_14 = df[df['date'].dt.year == 2014]# duplicating the "birth" column into a separate series
df_dates_14 = pd.Series(df_14['births'])# setting the index to df_14’s date column
df_dates_14.index = df_14['date']

现在让我们创建日历图。

plt.figure(figsize=(30, 10))_ = calmap.yearplot(df_dates_14)

2014 年日历热图—图片由作者提供

很明显,如我们所料,一致性假设不成立。你可以看到,在周末和一些公共假日,出生人数明显减少。让我们更仔细地看看这个。

挑战一致性假设

虽然我们没有闰日的第一个假设足够安全,不会对我们的答案产生重大影响,但我们做出的第二个假设(每个日期的可能性相等)似乎有点不现实。嗯,确实是。

数据显示,生日有两种潜在的模式——美国模式和欧洲模式。美国的出生模式显示在九月有一个明显的高峰,而欧洲模式在春季有一个大的高峰,随后在九月有一个较小的高峰。

另一个反常现象出现在节假日——因为更多的医院关门,在像公共假日这样的日子出生的可能性更小。

同样,周末的出生人数也比平日略少。

所有这些都指向一个事实,即真实世界的数据并不遵循一致性的假设。我们很快就会看到这种变化对我们的答案有多大影响。

寻找不同样本量的概率

让我们假设一年,比如 2014 年。我们将随机抽取不同样本量的日期,并计算至少有两个相同生日的概率。

我们将进行两种实验——一种有一致性假设,另一种没有。

效用函数

让我们首先创建一个效用函数,它接受:

  • 数据帧
  • 样本量
  • 用于测试的样本数量
  • 一面引发统一的旗帜

在高层次上,这个函数简单地从我们的数据框中创建给定样本大小的多个样本,并检查该样本是否有生日匹配。然后,它返回这些样本中匹配的部分。

它看起来会像这样:

效用函数——作者的形象

现在,让我们针对不同的样本大小,计算至少有一个生日匹配的样本的分数。

我们暂时不强加一致性假设。请记住,我们现在期望在样本量为23时比计算出的0.507有更高的概率,因为出生日期的不一致性使得生日匹配更容易。

计算不同样本大小的概率-按作者分类的图片

让我们画出结果。

不同样本大小的匹配发生概率图(不一致)-按作者分类的图像

不出所料,我们得到了一个比0.5略高的值。

让我们重复这个实验。但这一次,我们将对日期进行统一加权。对于23的样本量,我们期望概率略大于 0.5,但小于我们在前一个案例中得到的概率。

不同样本大小的匹配发生概率图(统一)-图片由作者提供

这和我们没有一致性假设时得到的值非常相似。这里的关键点是,对于n = 23,我们得到了一个比没有均匀性假设时稍低的值。

这与直觉一致,即如果一些日期比其他日期更有可能,那么两个人更有可能有相同的生日。虽然这只是我们数据集中 15 年中的一年,但对所有年份进行平均可能会得到更一致的结果(尽管我们不会这样做,因为文章越来越长)。

一致性假设有多重要?

记住这些发现,这个假设有多重要?我们计算出的答案 23 会不会比 1 高了很多?还是 3?也许更多?

Mario Cortina Borja 和 John Haigh 的论文对此进行了深入的解释。他们使用递归公式来计算答案会有多大的变化。他们得出了一个乘数0.99917,这意味着答案在很大程度上仍然是23,即使我们放松了一致性规则。

这篇论文在参考文献部分有链接。

如果我们从数据集(2000 年至 2014 年)中检查这一趋势,我们会得到一个图来加强这一点。

样本大小为 23 的匹配概率趋势—作者图片

几乎所有的年份,“均匀”的情节都比“不均匀”的情节低很多。趋势线后面非常淡的蓝色和橙色水平线代表均值。两者接近,一致均值低于预期。

为什么它不那么直观

正如你可能想象的那样,我们非常重视两个特定人的生日匹配,这是一个0.3%。虽然这种概率确实很小,但我们没有考虑这样一个事实,即当我们有23人时,我们谈论的是253对个体。

这样想想,任何两个人同一天生日的几率似乎都没那么小,尤其是唯一的生日只有 365 个。

示例代码和参考

资料组

我在这里使用了出生数据集(知识共享许可)。

https://data.fivethirtyeight.com/

论文谈均匀性假设

https://RSS . online library . Wiley . com/doi/10.1111/j . 1740-9713.2007 . 00246 . x

密码

https://github.com/Polaris000/BlogCode/tree/main/BirthdayParadox

更新

26.4.22

添加缺少的结果

最后

我写这篇文章是为了分享一个统计问题,这个问题的答案在没有实际解决之前并不明显。这表明统计在某些情况下不是很直观。

我希望这是一次有趣的阅读,你能从中得到一些东西。感谢阅读。

类伯特模型的提炼:代码

原文:https://towardsdatascience.com/distillation-of-bert-like-models-the-code-73c31e8c2b0a

就像化学蒸馏一样,我们将从模型中提取重要的东西:知识。照片由上提升

概述

如果你没有注意到,机器学习模型已经变得越来越大,以至于训练一个模型可能会对那些没有多余集群的人造成伤害。此外,即使有一个训练有素的模型,当你的硬件不符合模型对它应该运行的预期时,推理的时间和内存成本也会飙升。因此,为了缓解这个问题,与其说放弃类似伯特的模型和它们的厚层,不如说被称为蒸馏的技术已经被开发出来,用来将网络缩小到一个合理的大小,同时将性能损失降到最低。

如果你已经阅读了本系列的第一篇文章,那就不是新闻了,这里是这里是。在这篇文章中,我们讨论了 DistilBERT 1如何引入一种简单而有效的蒸馏技术,这种技术可以很容易地应用于任何类似 BERT 的模型,但是我们避开了任何具体的实现。现在,我们将进入细节,看看我们如何从想法进入。py 文件。

本文是关于以 DistilBERT 的方式提取类似 BERT 的模型的两部分系列文章的第 2 部分。对于第一部分,你可以跟随这个链接。然而,如果你认为你已经很好地掌握了蒸馏法,可以跳过阅读。

摘要

一、学生模型的初始化
二。自定义损失函数
III。一个更漂亮的实现。五、结论

一.学生模型的初始化

因为我们想从一个现有的模型初始化一个新的模型,我们需要访问旧模型的权重,也就是老师。我们会认为预先存在的模型是在 PyTorch 上实现的拥抱脸模型,因为它当然是。因此,要获得权重,我们首先必须知道如何访问它们。我们将使用罗伯塔[2]大号作为我们的教师模型。

拥抱脸的模型结构

我们可以尝试的第一件事是打印出模型,这应该能让我们深入了解它是如何制造的。当然,我们总是可以挖掘拥抱脸文档3,但这并不有趣。

运行这段代码后,我们会得到:

简单的罗伯塔印花给人第一印象(图片由作者提供)

模型的结构开始出现,但我们可以让它更漂亮。在拥抱脸模型中,我们可以通过使用。children()生成器。因此,如果我们想要搅动整个模型,我们需要调用。children(),在每个 child 上产生,等等…这描述了一个递归函数,我们在这里编码:

这给出了:

罗伯塔的递归窥视(作者图片)

通过展开这一树,RoBERTa 模型的结构似乎与其他类似 BERT 的模型一样,如下所示:

类伯特模型的架构(图片由作者提供)

复制老师的体重

我们知道,要以 DistilBERT 1的方式初始化一个类似 BERT 的模型,我们只需要复制除了 Roberta 层最深层次之外的所有内容,我们忽略了其中的一半。首先,我们需要创建学生模型,其架构与教师相同,但隐藏层数是教师的一半。
要做到这一点,我们只需要使用教师模型的配置,它是一个类似字典的对象,描述了拥抱脸模型的架构。当查看 roberta.config 属性时,我们可以看到以下内容:

罗伯塔配置(图片由作者提供)

我们感兴趣的是 num-hidden-layers 属性。让我们编写一个函数来复制这个配置,通过将其除以 2 来更改该属性,并使用新配置创建一个新模型:

当然,这个函数引入了一个缺失的部分,distill _ roberta _ weights。这个函数会把老师一半的权重放在学生的图层里,但是我们还是需要编码。既然递归在探索教师模型方面工作得很好,我们可以用同样的想法来探索和复制它的一部分。我们将同时浏览教师模型和学生模型,在此过程中从一个模型向另一个模型复制零件。唯一的诀窍是小心隐藏层部分,只复制一半。
该函数实现了以下功能:

该函数通过递归和类型检查,确保学生模型与教师模型相同,对于 Roberta 层是安全的。人们可以注意到,如果我们想在初始化教师时改变复制哪些层,只有编码器部分的 for 循环需要改变。

现在我们有了学生模型,我们需要训练它。这部分相对简单,除了我们将要使用的损失函数

二。自定义损失函数

作为对 DistilBERT 培训过程的回顾,我们可以看看下图:

蒸馏过程(图片由作者提供)

我们将注意力转向写着“损失”的红色大盒子。但是在揭示里面是什么之前,我们需要知道我们如何收集我们要喂它的东西。从这个图表中,我们可以看到我们需要 3 样东西:标签、学生和教师的嵌入。标签,我们已经有了,否则,我们可能会有更大的问题。现在让我们得到另外两个。

检索教师和学生的输入

在这里,我们将坚持我们的例子,并使用一个带有分类头的 RoBERTa 来说明这一部分。我们需要的是一个函数,给定一个类似 BERT 的模型的输入,那么两个张量,input_ids 和 attention_mask,以及模型本身,将返回该模型的 logits。由于我们使用了拥抱脸,这非常简单,我们唯一需要的知识就是往哪里看。

我们为学生和老师这样做,第一个有渐变,第二个没有。

编码损失

如果损失函数有点模糊,我们可以推荐回到第一篇文章来阅读损失函数。但是,如果您没有时间做这个,这个图表应该有所帮助:

蒸馏者的损失(图片由作者提供)

我们称之为“收敛余弦损失”的是用于对齐两个输入向量的常规余弦损失。要了解更多信息,请参考本系列的第一部分。这是代码:

三。更漂亮的实现

我希望 python 是一种面向对象的编程语言不会让您感到震惊。因此,由于所有这些函数使用几乎相同的对象,不把它们作为的一部分似乎有些奇怪。如果你想实现这一点,我建议使用一个蒸馏器类来整理代码,就像这个要点一样。我们不打算嵌入这个,因为它很长。

当然,还缺少一些东西,比如 GPU 支持,整个训练程序等等。但是 DistilBERT 所有的关键思想都可以在那里找到。

四。结果

那么,以这种方式提炼出来的模型最终表现如何呢?对于 DistilBERT,可以阅读原文1。对于罗伯塔来说,拥抱脸已经有了一个蒸馏过的版本,就在这里。在 GLUE benchmark 4上,我们可以比较这两个模型:

罗伯塔对迪夫罗伯塔(图片由作者提供)

至于时间和内存成本,这个模型的大小大约是 roberta-base 的三分之二,速度是 Roberta-base 的两倍。

动词 (verb 的缩写)结论

通过这一系列的两篇文章,您应该有足够的知识来提炼出您遇到的任何类似 BERT 的模型。但是为什么就此打住呢?自然界充满了蒸馏方法,像 TinyBERT [5]或 MobileBERT [6]。如果你认为其中一个更符合你的需要,你应该读一读这些文章。谁知道呢,你可能想尝试一种全新的蒸馏方法,因为这是一个日益发展的领域。

谢谢

感谢我以前在professor ob . ai的同事 Ha Quang Lee 和 Meng,他们首先教会了我关于 DistilBERT 的知识,并纠正了我的代码。

参考

1维克多·桑,弗拉达利出道,朱利安·肖蒙德,托马斯·沃尔夫,蒸馏伯特,伯特的蒸馏版:更小,更快,更便宜,更轻 (2019),拥抱脸

[2]刘、米莱奥特、纳曼戈亚尔、杜、曼达尔乔希、陈、奥梅尔列维、、卢克塞特勒莫耶、韦塞林斯托扬诺夫、罗伯塔:稳健优化的伯特预训练方法 (2019)、arXiv

3拥抱脸团队归功于朱利安·肖蒙德,拥抱脸的罗伯塔文档,拥抱脸

4 Alex WANG,Amanpreet SINGH,Julian MICHAEL,Felix HILL,Omer LEVY,Samuel R. BOWMAN, GLUE:一个用于自然语言理解的多任务基准和分析平台 (2019),arXiv

[5]焦,尹宜春,,尚,辛江,,,,李,, TinyBERT:蒸馏 BERT 用于自然语言理解 (2019),arXiv

[6]孙志清,于鸿坤,,宋,,刘,,丹尼·周, MobileBERT:一种面向资源受限设备的紧凑任务不可知 BERT(2020),arXiv

区分 SQL 中的 4 个排名函数

原文:https://towardsdatascience.com/distinguish-4-ranking-functions-in-sql-37db99107c05

不同排名函数的备忘单

埃里克·普劳泽特在 Unsplash 上拍摄的照片

介绍

SQL 中有一些排名函数,有时区分它们的用法令人沮丧。这就是为什么在这篇文章中,我想和你分享我在处理不同排名函数时的备忘单。希望在查询结果的时候能节省你一些时间。

总的来说,当谈到等级函数时,会有四种类型的函数:

  • 排名()
  • 密集等级()
  • ROW_NUMBER()
  • NTILE()

让我们在下面的例子中看看这四个。

数据集

在我们开始每个函数之前,让我们创建一个用于操作的数据集。这些数据显示了学生在三个科目上的最终成绩:数学、化学和历史。

data = {'student_id':[123,123,123,241,241,241,466,466,466],
        'subject':['Maths','Chemistry','History','Maths', 'Chemistry','History','Maths','Chemistry','History'],
        'final_score':[8,4,5,6,8,7,5,9,2]}
df = pd.DataFrame(data)

句法

RANK、DENSE_RANK、ROW_NUMBER 函数的语法实际上非常相似。

RANK_FUNCTION() OVER (
   [PARTITION BY expression, ]
   ORDER BY expression (ASC | DESC) );

同时, NTILE 函数的语法与这三个略有不同:

NTILE(number of groups) OVER (
   [PARTITION BY expression, ] 
   ORDER BY expression [ASC | DESC]); 

我们可以这样解释这些论点:

  • RANK_FUNCTION: ROW_NUMBERRANKDENSE_RANK
  • 分区由 : 非强制参数。它将输出分成多个分区。
  • ORDER BY: 指定排序后如何将排名数字应用于结果集。
  • 组数:要生成的组数。

行数

这是最基本的排名功能。基于每个分区的 OVER 子句中所述的顺序,按顺序返回每行的等级(例如,1、2、3…)。需要注意的一点是,在具有相同值的行中,我们不会有相同的排名。

例 1(无分区):我想根据学生在所有科目上的总分对他们进行排名。输出如图 1 所示。

SELECT *, 
ROW_NUMBER() OVER(ORDER BY total_score DESC) RowNumber
   FROM (SELECT student_id, SUM(final_score) AS total_score
            FROM df 
            GROUP BY student_id)

图 1:基于总分的排名——按作者排序的图片

例 2(带分区):学生各科成绩排名

SELECT *, 
ROW_NUMBER() OVER(PARTITION BY subject ORDER BY final_score DESC) RowNumber
   FROM df

图 2:分区排名——作者图片

如图 2 所示,通过PARTITION BY“主题】,排名是基于每个主题内的分数决定的。

等级

基本上,RANKROW_NUMBER的作用是一样的。唯一的区别是,ROW_NUMBER避免了排名结果的重复,而RANK给予相同的值相同的排名数字。因此,排名数字不是唯一的。

例 3 :找出谁的最终分数最高,不分科目。

SELECT *, 
RANK() OVER(ORDER BY final_score DESC) Rank
   FROM df

图 3:应用等级函数

图 3 显示,与避免排名数字重复的ROW_NUMBER不同,RANK将最终分数相似的学生放在同一排名中。学生 123 和 242 在数学和化学上得了 8 分,所以他们都排名第二,因为他们有第二高的分数。

密集 _ 秩

在图 3 中,RANK将两个最终得分相同的学生放在第二位,然后跳过第三位继续第四位。另一方面,如果DENSE_RANK处于相同的情况,则等级号不会跳到 4,而是继续等级号 3。

SELECT *, 
RANK() OVER(ORDER BY final_score DESC) Rank, 
ROW_NUMBER() OVER(ORDER BY final_score DESC) RowNumber, 
DENSE_RANK() OVER(ORDER BY final_score DESC) Dense_Rank 
   FROM df

图 4:应用密集等级——按作者排序的图像

恩蒂莱

函数有助于将行分组。根据提供的标准为每个行组分配一个等级。组数在NTILE()功能中指定。

例 4: 根据学生各科成绩,对学生进行排名,分为 2 组。

如图 5 所示,对于每个科目,分数最高的学生被分到一组,分数较低的学生被分到第二组。因此,有了NTILE,我们可以根据学生在每门学科的表现将他们分成不同的组。

SELECT *, 
 NTILE(2) OVER(PARTITION BY subject ORDER BY final_score DESC) Ntile 
   FROM df

图 5: NTILE 排名并将排名分为两组

总结

行数

基于每个分区的 OVER 子句中所述的顺序,按顺序返回每行的等级(例如,1、2、3…)。排名数字没有重复。

军阶

类似于ROW_NUMBER,它为每一行分配一个递增的排名号。不过和ROW_NUMBER有一点不同的是RANK把相同的排名数赋予了相等的值。然后,该函数在复制后跳过下一个排名号。

密集 _ 等级

RANK类似,DENSE_RANK为相同的值分配相同的等级编号,但它不像RANK那样跳过等级编号。

恩蒂莱

该函数有助于对值进行排序和分组。

结论

以上文章是我对排名函数区别的解释。我希望我能让你明白。

利用 Spark、Nixtla 和 Fugue 对 15 分钟以内的 1M 时间序列进行分布式预测

原文:https://towardsdatascience.com/distributed-forecast-of-1m-time-series-in-under-15-minutes-with-spark-nixtla-and-fugue-e9892da6fd5c

使用开源项目 StatsForecast、Fugue 和 Spark 进行可扩展的时间序列建模

由凯文·科、汪涵马克斯·梅根塔尔费德里科·加尔萨·拉米雷斯主演。

TL:DR 我们将展示如何利用 Spark 的分布式能力和 StatsForecast 的高效代码在几分钟内拟合数百万个模型。

对一段时间内收集的数据进行趋势和季节性的时间序列建模、分析和预测是一种快速增长的软件应用。

从电力和经济到医疗保健分析,企业每天都在收集时间序列数据,以预测模式并构建更好的数据驱动型产品体验。例如,温度和湿度预测用于制造以防止缺陷,流指标预测有助于识别音乐的流行艺术家,对供应链中不同位置的数千个 SKU 的销售预测用于优化库存成本。随着数据生成量的增加,预测的必要性已经从模拟几个时间序列发展到预测数百万个时间序列。

动机

Nixtla 是一个开源项目,专注于最先进的时间序列预测。他们有几个库,例如用于统计模型的 StatsForecast ,用于深度学习的 NeuralForecast ,以及用于预测不同层级的聚合的 HierarchicalForecast 。这些是面向生产的时间序列库,侧重于不同的建模技术。

本文着眼于 StatsForecast ,这是一个拥有统计和计量经济学模型的快速预测库。Nixtla 的 AutoARIMA 模型比的 pmdarima 快 20 倍,ETS(误差、趋势、季节)模型比的 statsmodels 快 4 倍,而且更稳健。要复制的基准和代码可以在这里找到。性能提升的很大一部分是由于使用了名为 numba 的 JIT 编译器来实现高速度。

更快的迭代时间意味着数据科学家可以运行更多的实验,并更快地收敛到更准确的模型。这也意味着大规模运行基准测试变得更加容易。

在本文中,我们对 StatsForecast 库在使用赋格库拟合 SparkDask 模型时的可伸缩性感兴趣。这种结合将允许我们在一个临时集群上快速地分布训练大量的模型。

实验设置

当处理大型时间序列数据时,用户通常必须处理数千个逻辑上独立的时间序列(想想不同用户或不同产品销售的遥测数据)。在这种情况下,我们可以在所有系列上训练一个大模型,或者我们可以为每个系列创建一个模型。这两种方法都是有效的,因为较大的模型将获得整个群体的趋势,而训练数千个模型可能更好地拟合单个系列数据。

注意:要在一个模型中获得时间序列人口的微观和宏观趋势,请检查 Nixtlahierarchical forecast库,但这也是计算成本更高、规模更棘手的方法。

本文将讨论我们为每个单变量时间序列训练几个模型(AutoARIMA 或 ETS)的场景。对于此设置,我们按时间序列对完整数据进行分组,然后为每个组训练每个模型。下图说明了这一点。分布式数据帧可以是 Spark 或 Dask 数据帧。

按分区自动排序—按作者排序的图像

Nixtla 之前发布了关于在 Ray 上分发这个模型训练的基准测试。设置和结果可以在这个博客中找到。结果也如下所示。在 35 分钟内运行一百万个 AutoARIMA 模型需要 2000 个 CPU。我们将把它与在 Spark 上运行进行比较。

光线结果的统计预测—图片由作者提供

统计预测代码

首先,我们将查看用于在射线上分布式运行 AutoARIMA 的 StatsForecast 代码。这是运行一百万时间序列场景的简化版本。它还为最近的 StatsForecast v1.0.0 版本进行了更新,因此它看起来可能与以前基准测试中的代码有点不同。

射线上分布式运行状态预测

StatsForecast 的界面非常小。它已经被设计为对每组数据执行 AutoARIMA。只需提供ray_address就可以让这个代码片段分布式运行。如果没有它,n_jobs将指示用于预测的并行流程的数量。model.forecast()将在一个步骤中完成拟合和预测,并在时间范围内输入到该方法中进行预测。

用神游在 Spark 和 Dask 上运行

Fugue 是一个抽象层,将 Python、Pandas 和 SQL 代码移植到 Spark 和 Dask。最少的接口是transform()函数。这个函数接收一个函数和数据帧,并把它送到 Spark 或 Dask。我们可以使用transform()函数来激活 StatsForecast 的执行。

下面的代码有两个部分。首先,我们在forecast_series函数中定义了预测逻辑。为了简单起见,一些参数是硬编码的。最重要的是那个n_jobs=1。这是因为 Spark 或 Dask 已经充当了并行化层,拥有两级并行会导致资源死锁。

运行状态预测与神游火花

其次,transform()函数用于在 Spark 上应用forecast_series()函数。前两个参数是要应用的数据帧和函数。输出模式是 Spark 的一个需求,所以我们需要将它传入,分区参数将负责通过unique_id分割时间序列建模。

这段代码已经运行并返回一个 Spark DataFrame 输出。

尼克斯特拉氏河豚

上面的transform()是大致看看神游能做什么。实际上,Fugue 和 Nixtla 团队合作向 StatsForecast 库中添加了一个更加本地化的FugueBackend。伴随它的是一个实用的forecast()功能,用于简化预测界面。下面是对一百万个时间序列运行 StatsForecast 的端到端示例。

我们只需要创建 FugueBackend,它接收一个 SparkSession 并将其传递给forecast()。该函数可以采用数据帧或数据的文件路径。如果提供了文件路径,它将与并行后端一起加载。在上面的例子中,我们在每次运行实验来生成基准时都替换了这个文件。

同样重要的是要注意,我们可以在对完整数据运行forecast()之前进行本地测试。我们所要做的就是不为平行论证提供任何东西;一切都将在熊猫身上按顺序运行。

基准测试结果

基准测试结果如下所示。在撰写本文时,Dask 和 Ray 发布了最新版本,所以只有 Spark 指标是最新的。在用更新运行这些实验之后,我们将发表一篇后续文章。

Spark 和 Dask stats 基准测试大规模预测

注意:我们试图使用 2000 个 CPU,但是受到 AWS 上可用计算实例的限制。

这里重要的部分是 AutoARIMA 在不到 15 分钟的时间内训练了一百万个时间序列模型。集群配置附在附录中。用很少的几行代码,我们就能够分布式地编排这些时间序列模型的训练。

结论

分散地训练数千个时间序列模型通常需要使用 Spark 和 Dask 进行大量编码,但是我们能够用很少几行代码运行这些实验。Nixtla 的 StatsForecast 能够快速利用所有可用的计算资源,为每个时间序列找到最佳模型。所有用户需要做的就是提供一个相关的并行后端(Ray 或 Fugue)在集群上运行。

在一百万时间序列的规模上,我们对 AutoARIMA 的总训练时间为 12 分钟。这相当于我们立即运行了近 400 个 cpu 小时,允许数据科学家快速大规模迭代,而无需编写显式的并行化代码。因为我们使用了一个短暂的集群,所以成本实际上与在 EC2 实例上顺序运行这个集群(在所有内核上并行化)是一样的。

资源

  1. Nixtla StatsForecast 回购
  2. 统计预测文档
  3. 赋格回购
  4. 赋格教程

要与我们聊天:

  1. 赋格松弛
  2. 尼克斯特拉松驰

附录

对任何人来说。对集群配置感兴趣,可以看下面。这将启动 Databricks 集群。重要的是使用机器的node_type_id

{
    "num_workers": 20,
    "cluster_name": "fugue-nixtla-2",
    "spark_version": "10.4.x-scala2.12",
    "spark_conf": {
        "spark.speculation": "true",
        "spark.sql.shuffle.partitions": "8000",
        "spark.sql.adaptive.enabled": "false",
        "spark.task.cpus": "1"
    },
    "aws_attributes": {
        "first_on_demand": 1,
        "availability": "SPOT_WITH_FALLBACK",
        "zone_id": "us-west-2c",
        "spot_bid_price_percent": 100,
        "ebs_volume_type": "GENERAL_PURPOSE_SSD",
        "ebs_volume_count": 1,
        "ebs_volume_size": 32
    },
    "node_type_id": "m5.24xlarge",
    "driver_node_type_id": "m5.2xlarge",
    "ssh_public_keys": [],
    "custom_tags": {},
    "spark_env_vars": {
        "MKL_NUM_THREADS": "1",
        "OPENBLAS_NUM_THREADS": "1",
        "VECLIB_MAXIMUM_THREADS": "1",
        "OMP_NUM_THREADS": "1",
        "NUMEXPR_NUM_THREADS": "1"
    },
    "autotermination_minutes": 20,
    "enable_elastic_disk": false,
    "cluster_source": "UI",
    "init_scripts": [],
    "runtime_engine": "STANDARD",
    "cluster_id": "0728-004950-oefym0ss"
}

分布式学习:入门

原文:https://towardsdatascience.com/distributed-learning-a-primer-790812b817f1

让机器学习模型更大、更好、更快的算法背后

稳定扩散生成的图像

分布式学习是现代科技公司的 ML 堆栈中最关键的组件之一:通过在大量机器上并行化,人们可以更快地在更多数据上训练更大的模型,以更快的迭代周期释放更高质量的生产模型。

但不要只相信我的话。以推特为例:

使用定制的分布式培训[…]使我们能够更快地迭代,并根据更多更新鲜的数据来训练模型。

或者谷歌的:

我们的实验表明,我们新的大规模训练方法可以使用一群机器来训练即使是中等规模的深度网络,速度也比 GPU 快得多,而且没有 GPU 对模型最大大小的限制。

或者网飞的:

我们试图实现一个大规模的神经网络训练系统,该系统利用了 GPU 和 AWS 云的优势。我们希望使用合理数量的机器,通过神经网络方法实现强大的机器学习解决方案。

在这篇文章中,我们将探索分布式学习背后的一些基本设计考虑,特别关注深度神经网络。您将了解到:

  • 模型并行与数据并行训练,
  • 同步与异步训练,
  • 集中式与分散式培训,以及
  • 大批量训练。

让我们开始吧。

模型并行与数据并行

在深度神经网络的分布式训练中有两种主要的范例,模型并行,其中我们分布模型,以及数据并行,其中我们分布数据。

模型并行性意味着每台机器只包含模型的一个分区,例如深度神经网络的某些层(“垂直”分区),或者来自同一层的某些神经元(“水平”分区)。如果一个模型太大而不能在一台机器上运行,那么模型并行性可能是有用的,但是它需要在机器之间发送大量的张量,这会带来很高的通信开销。在最坏的情况下,一台机器可能会在等待前一台机器完成其部分计算时处于空闲状态。

数据并行性意味着每台机器都有一个完整的模型副本,并在本地批量数据上向前和向后传递。根据定义,这种模式的伸缩性更好:我们也可以随时向集群添加更多的机器

  • 通过保持全局(集群范围)批处理大小固定,并减少本地(每台机器)批处理大小,或者
  • 通过保持本地批量大小不变并增加全局批量大小。

在实践中,模型和数据并行性不是互斥的,而是互补的:没有什么可以阻止我们将模型和数据分布到我们的机器集群上。正如推特的一篇博客文章所概述的,这种混合方法有其自身的优势。

最后,还有超参数并行,每台机器在相同的数据上运行相同的模型,但是具有不同的超参数。在其最基本的形式中,这是令人尴尬的平行。

同步与异步训练

在数据并行中,在训练周期的每次迭代中,一批全局数据均匀地分布在集群中的所有机器上。例如,如果我们在一个有 32 台机器的集群上以 1024 的全局批量进行训练,我们将向每台机器发送 32 个本地批量。

为了实现这一点,我们需要一个参数服务器,一个存储和跟踪最新模型参数的专用机器。工人将他们本地计算的梯度发送到参数服务器,参数服务器又将更新的模型参数发送回工人。这可以同步或异步完成。

同步训练中,参数服务器等待来自所有工人的所有梯度到达,然后基于在所有工人上聚集的平均梯度更新模型参数。这种方法的优点是平均梯度的噪声更小,因此参数更新的质量更高,使得模型收敛更快。然而,如果一些工人需要更长的时间来计算他们的局部梯度,那么所有其他工人都必须闲置,等待掉队者赶上来。当然,闲置是对计算资源浪费:理想情况下,每台机器都应该随时有事可做。

异步训练中,参数服务器一接收到来自单个工作者的单个梯度就更新模型参数,并将更新的参数立即发送回该工作者。这消除了闲置的问题,但它引入了另一个问题,即陈旧。一旦基于来自单个工作者的梯度更新了模型参数,所有其他工作者现在就使用陈旧的模型参数。工人越多,问题越严重:例如,有 1000 个工人,当最慢的工人完成计算时,它将落后 999 步。

异步数据并行。每个工人将他们的本地梯度发送到参数服务器,并接收模型参数。(图片来源:兰格等人 2020,链接)

同步数据并行。在发送更新的模型参数之前,参数服务器聚集来自所有工人的梯度。(图片来源:兰格等人 2020,链接)

因此,一个好的经验法则是,如果节点数量相对较少,则使用异步训练,如果节点数量非常大,则切换到同步训练。例如,来自谷歌的研究人员在 32 个 Nvidia K40 GPUs 上使用异步并行性训练了他们的'洪水填充网络',这是一种用于大脑图像分割的深度神经网络。然而,为了在一台拥有 2048 个计算节点的超级计算机上训练相同的模型架构,来自阿贡国家实验室(包括本文作者)的研究人员改用了同步并行。

在实践中,人们也可以在同步和异步并行之间找到有用的折衷。例如,来自微软的研究人员提出了一个对同步并行的“残酷”修改:简单地留下最慢的工人。他们报告说,这种修改将训练速度提高了 20%,而对最终模型的准确性没有影响。

集中式与分散式培训

拥有中央参数服务器的缺点是,对该服务器的通信需求随着集群的大小而线性增长。这就产生了一个瓶颈,限制了这种集中式设计的规模。

为了避免这个瓶颈,我们可以引入多个参数服务器,并为每个参数服务器分配模型参数的子集。在最分散的情况下,每个计算节点既是一个工作者(计算梯度),也是一个参数服务器(存储模型参数的子集)。这种去中心化设计的优点是,所有机器的工作负载和通信需求都是相同的,这消除了任何瓶颈,并使其更容易扩展。

大批量训练

在数据并行中,全局批处理大小随着集群大小线性增长。在实践中,这种缩放行为支持具有极大批量的训练模型,这在单台机器上是不可能的,因为它的内存有限。

大批量训练中最关键的问题之一是如何根据聚类大小调整学习速率。例如,如果模型训练在单个单机上运行良好,批量大小为 32,学习率为 0.01,那么当再添加 7 台机器,导致全局批量大小为 256 时,正确的学习率是多少?

在 2017 年的一篇论文中,来自脸书的研究人员提出了线性缩放规则:简单地将学习率与批量大小线性缩放(即,在上面的例子中使用 0.08)。使用具有 256 台机器和 8192(每台机器 32)的全局批量大小的 GPU 集群,作者仅用 60 分钟就在 ImageNet 数据集上训练了一个深度神经网络,这在论文发表时是一个了不起的成就,也是大批量训练的力量的展示。

大批量训练图解。批量越大,梯度噪音越小,步长越大,收敛越快。(图片来源:McCandlish et al 2018,链接)

然而,大批量训练有其局限性。正如我们已经看到的,为了利用更大的批量,我们需要提高学习率,以便利用额外的信息。但是如果学习率太大,在某一点上模型可能会超调并且不能收敛。

来自 OpenAI 的一篇 2018 年论文解释说,大批量训练的限制似乎取决于领域,从 ImageNet 的数万批次到学习玩游戏 Dota 2 的数百万批次强化学习代理。找到这些极限的理论解释是一个尚未解决的研究问题。毕竟,ML 研究很大程度上是经验性的,缺乏理论支柱。

结论

概括一下,

  • 分布式学习是现代科技公司 ML 堆栈中的一个关键组件,能够更快地在更多数据上训练更大的模型。
  • 在数据并行中,我们分发数据,在模型并行中,我们分发模型。在实践中,两者可以结合使用。
  • 在同步数据并行中,参数服务器等待所有工人发送他们的梯度,而在异步数据并行中则不会。同步数据并行实现了更精确的梯度,代价是引入了一些空闲时间,而最快的工人必须等待最慢的工人。
  • 在完全去中心化的数据并行中,每个工作者也是模型参数子集的参数服务器。去中心化设计均衡了所有机器的计算和通信需求,因此消除了任何瓶颈。
  • 数据并行支持大批量训练:我们可以在大规模集群上快速训练模型,方法是将学习速率与全局批量成线性比例。

而这只是冰山一角。分布式学习仍然是一个活跃的研究领域,有一些开放性的问题,如:大批量训练的局限性是什么?如何优化具有多个培训工作的真实集群,从而产生竞争负载?如何最好地处理包含混合计算资源(如 CPU 和 GPU)的集群?当搜索许多可能的模型或超参数时,我们如何平衡探索和利用?

欢迎来到分布式学习的迷人世界。

📫 订阅 把我的下一篇文章直接发到你的收件箱。
💪
成为中等会员 并解锁无限权限。
🐦关注我上
LinkedInTwitter

分布式并行训练:数据并行和模型并行

原文:https://towardsdatascience.com/distributed-parallel-training-data-parallelism-and-model-parallelism-ec2d234e3214

深度学习的分布式训练

如何在 PyTorch 中扩展培训大型模型,如 GPT-3 和达尔-E 2

马克·哈普尔在 Unsplash 上拍摄的照片

近年来,分布式并行训练的规模和深度学习模型的规模呈指数级增长。特别是,基于 Transformer 的语言模型已经抢尽了风头。臭名昭著的 GPT-3 爆发了 1750 亿个参数和 96 个关注层,批量大小为 3.2 米,单词为 4990 亿个。整整半年后,谷歌发布了拥有 1.6 万亿参数的开关变压器。同一天(2021 年 1 月 11 日),北京人工智能研究院(BAAI)发布了初始的悟道 1.0。不久,悟道 2.0 于 2021 年 5 月 31 日首次亮相,成为最大的语言模型,拥有 1.75 万亿个参数,是 GPT-3 参数的十倍。

假设我们在亚马逊 SageMaker 训练平台的 240 ml.p4d.24xlarge 实例上训练 GPT-3,整个模型将需要 25 天来训练。挑战不仅仅是处理,还有记忆。吴涛 2.0 似乎需要超过 1000 个 GPU 来存储它的参数。

对于像 GPT-3 和 DALL-E 2 这样的深度学习大型模型,采用分布式并行训练势在必行。有两种主要类型的分布式并行训练:数据并行和模型并行。我们进一步将后者分为两个子类型:流水线并行和张量并行。我们将在这里涵盖所有分布式并行培训,并演示如何在 PyTorch 中开发。

了解分布式并行培训

分布式并行训练有两个高级概念:并行和分布。

并行是一种解决大型模型规模或提高训练效率的框架策略,分布式是一种向外扩展的基础架构。

除了这两种基本类型的并行,还有更多的变体,比如专家并行。此外,它们可以混合两种或全部,如数据和模型混合并行。对于大规模模型,混合模型和数据并行是很常见的。例如,最大的 T5 型号和 GPT-3 采用模型和数据相结合的并行方式。然而,所有这些都应该是 DL 建模框架策略的一部分。

另一方面,分布最终会在云或集群中扩展并行性。容器化使扩展节点变得容易,Kubernetes 或云解决方案可以有效地编排它们。每个节点可以有多个 GPU(或 TPU 和其他设备)和容器群集中的各种容器。在云原生解决方案中,节点可以对用户隐藏。一个容器管理一个或多个 GPU。并行性可以跨分布式 GPU 容器集群进行调度。所以分布是基础架构的实现。

Google Switch Transformers 的数据和权重划分策略(来源: Fedus 等人,2021

以上说明了 Google Switch 传输中的数据和权重划分策略。每个 4×4 虚线网格代表 16 个内核,阴影方块是该内核上的数据(模型权重或一批令牌)。它演示了如何为每个策略拆分模型权重和数据张量。第一行展示了模型权重如何在内核间分配。该行中不同大小的形状表示前馈网络(FFN)层中较大的权重矩阵(例如,较大的 dff 大小)。阴影方块的每种颜色标识一个唯一的权重矩阵。每个内核的参数数量是固定的,但是更大的权重矩阵将对每个令牌应用更多的计算。第二行展示了如何在内核间分割数据批次。每个内核持有相同数量的令牌,在所有策略中保持固定的内存使用量。分区策略具有不同的属性,允许每个内核在不同颜色的内核之间具有相同或不同的令牌。

PyTorch 中的数据并行性

数据并行性使用相同的模型在所有内核之间分割数据。PyTorch 分布式数据并行、SageMaker 分布式和 Horovod 等数据并行框架主要完成以下三项任务:

  1. 首先,它创建并分发模型的副本,每个加速器一个副本。
  2. 它对数据进行分片,然后将其分发给相应的设备。
  3. 它最终在反向传播步骤中将所有结果聚集在一起。

因此我们可以看到,第一个任务应该在每次训练中出现一次,但最后两个任务应该在每次迭代中出现。

PyTorch 分布式数据并行 (DDP)实现了模块级的数据并行,可以跨多台机器运行。它可以与 PyTorch 模型并行工作。DDP 应用程序应该生成多个进程,并为每个进程创建一个 DDP 实例。DDP 使用torch.distributed包中的集体通信来同步梯度和缓冲区。此外,DDP 为来自model.parameters()的每个参数注册了一个自动签名的钩子,当在向后传递中计算出相应的梯度时,它将触发。然后,DDP 使用该信号触发过程间的梯度同步。

因此,在 PyTorch 中设置和运行 DDP 有三个主要步骤:

  1. 通过torch.distributed建立分布式系统。
  2. 通过torch.nn.parallel定义 DDP 建模。
  3. 产卵贯穿torch.multiprocessing

请参见下面的示例代码。

**import** **torch
import** **torch.nn** **as** **nn
import** **torch.distributed** **as** **dist**
**import** **torch.multiprocessing** **as** **mp****from** **torch.nn.parallel** **import** **DistributedDataParallel** **as** **DDP** ### ***Step 1***: *setup and cleanup setups*
**def** **setup(rank,** **world_size):
    ...** *# initialize the process group*
    **dist.init_process_group(**"tst"**, rank=rank, world_size=world_size)**

**def** **cleanup():**
    **dist.destroy_process_group()** ### ***Step 2***: *define DDP modeling*
**def** **dummy_init(rank,** **world_size):**
    **setup(rank,** **world_size)**
    **model** **=** **DummyModel().to(rank)**
    **ddp_model** **=** **DDP(model,** **device_ids=[rank])** **...** **cleanup()** ### ***Step 3***: *Spawn to run*
**def** **run_dummy(dummy_fn,** **world_size):**
    **mp.spawn(dummy_fn,**
             **args=(world_size,),**
             **nprocs=world_size,**
             **join=True)**

PyTorch 中的模型并行性

与数据并行不同,模型并行将模型(即其层或张量)分割到多个内核,为所有训练内核复制相同的模型。PyTorch 减轻了并行实现的负担,并对其进行了最小的修改。

简而言之,在调用损失函数时,您需要通过三个相应的区域中的“to(device)”来指定神经网络层和到所需内核的即时输出:建模定义,“forward”方法和“backward”方法。PyTorch 将在幕后处理所有其他的事情。请在此处查看示例代码

在大模型并行的现实世界中,这可能并不简单。它通常需要额外的努力来提高培训效率和资源利用率。以流水线并行为例, PipeDream 通过牺牲内存来存储权重的多个副本,提高了流水线效率。 TeraPipe 引入了另一种特定于单变压器架构的流水线技术,这种流水线技术是跨令牌而不是微批处理进行的。此外, Mesh-TensorFlowMegatron-LM 分别基于 TensorFlow 和 PyTorch 创建了用于优化训练十亿参数模型的张量并行框架。

Amazon sage makermodel parallelism是 PyTorch 之上的一个软件库。它是一个通用而灵活的框架,支持流水线和张量并行,具有节省内存的特性。其流水线并行引擎支持基于模块-服务器设计的任意模型架构的负载平衡自动分区和流水线运行时。与流水线并行一样,张量并行的基本计算单位是nn.Module。本质上,张量并行性在于遍历模型并用它们的分布式实现替换模型的特定子模块。

外卖

分布式并行训练有并行和分布两个高级概念。并行是框架策略,分发是基础架构。分布式并行培训至关重要,但在行业和研究中仍处于萌芽状态。我们可以期待未来会出现三个创新领域。

  1. 并行性分割数据、模型或混合数据,以进行大型模型训练。随着数据和模型呈指数级增长,优化内存使用和处理效率变得至关重要。
  2. 训练一个大模特很贵。复用训练层的迁移学习将改变大规模分布式并行训练的游戏。
  3. ML 生命周期涉及多个分布式系统,从数据收集到处理、模型训练和服务。ML 平台经常受到复杂性、数据通信成本和系统不稳定性的阻碍。统一 ML 的所有分布式系统意义重大。

参考

  1. 亚马逊 SageMaker 模型并行性:大型模型训练的通用灵活框架:https://arxiv.org/abs/2111.05972
  2. 开关变压器:用简单有效的稀疏性扩展到万亿参数模型:【https://arxiv.org/abs/2101.03961
  3. 语言模型是很少出手的学习者:【https://arxiv.org/abs/2005.14165

分布式并行训练—模型并行训练

原文:https://towardsdatascience.com/distributed-parallel-training-model-parallel-training-a768058aa02a

分布式培训

PyTorch 中大模型的分布式模型并行训练

丹妮拉·奎瓦斯在 Unsplash 上的照片

近年来,深度学习模型的规模和分布式并行训练的挑战呈指数级增长。例如,著名的 GPT-3 有 1750 亿个参数和 96 个关注层,批量大小为 3.2 M,字数为 4990 亿。亚马逊 SageMaker 训练平台在 120ml . p4d . 24x 大型实例和 1750 亿个参数上可以达到每秒 32 个样本的吞吐率。如果我们将此增加到 240 个实例,完整的模型将需要 25 天来训练。

对于大型模型,在 GPU 上训练并行性变得非常必要。有三种典型的分布式并行训练类型:分布式数据并行、模型并行和张量并行。我们常常把后两种归为一类:模型并行,再分为流水线并行和张量并行两个亚型。我们将在这里重点介绍分布式模型并行训练,并演示如何在 PyTorch 中进行开发。

了解分布式模型并行训练

模型并行性在多个 GPU 之间分割模型,这与数据并行性为所有训练 GPU 复制同一模型不同。模型并行训练有三个关键点:1 .如何有效地分割模型的层;2.如何并行而不是顺序地训练分片层;3.如何设计优秀的节点间吞吐量?如果我们不能平衡碎片或者并行运行它们,我们就不能实现模型并行的目标。

分布式训练是一种在集群或资源池中具有多个节点的训练并行性。容器化使得扩展节点变得容易,Kubernetes 很好地协调了它们。每个节点可以有多个 GPU 和多个容器。一个容器可以控制一个或多个 GPU。模型并行性可以跨分布式 GPU 节点集群分派模型的各层。这种方法可以有效地扩展培训。

我们可以在下面举例说明分布式模型并行训练。

模型并行性解释(作者)

在上面的示例中,集群中有两个节点。每个节点有一个或两个容器,每个容器也有一个或两个 GPU。模型的七层分布在这些 GPU 上。

每个 GPU 或节点都有资源限制。模型并行性使培训能够并行化,而分布式培训最终可以横向扩展。

PyTorch 中的模型并行性

上面的描述表明,分布式模型并行训练有两个主要部分。为了实现这一点,有必要在多个 GPU 中设计模型并行性。PyTorch 对此进行了包装并减轻了实现负担。PyTorch 只有三个小变化。

  1. 使用“to(device)”来标识模型的特定层(或子网)的特定设备(或 GPU)。
  2. 相应地添加一个“forward”方法,在设备间移动中间输出。
  3. 调用损失函数时,指定同一设备上的标签输出。而“backward()”和“torch.optim”会像在单个 GPU 上运行一样自动处理渐变。

让我们写一些伪代码来说明它。假设在两个 GPU 上运行一个简单的两层模型,将每个线性层放在每个 GPU 上,并将输入和中间输出相应地移动到相关层 GPU 上。我们可以如下定义虚拟模型:

**import** **torch**
**import** **torch.nn** **as** **nn****class** **DummyModel(nn.Module):**
    **def** __init__**(**self**):**
        super**(DummyModel,** self**).**__init__**()**
        self**.net0** **=** **nn.Linear(**20**,** 10**).to(**'cuda:0'**)**
        self**.relu** **=** **nn.ReLU()**
        self**.net1** **=** **nn.Linear(**10**,** 10**).to(**'cuda:1'**)**

    **def** **forward(**self**,** **x):**
        **x** **=** self**.relu(**self**.net0(x.to(**'cuda:0'**)))**
        **return** self**.net1(x.to(**'cuda:1'**))**

现在我们可以用下面的损失函数添加训练代码:

**import** **torch**
**import** **torch.nn** **as** **nn
import** **torch.optim** **as** **optim
import** **DummyModel****model** **=** **DummyModel()**
**loss_fn** **=** **nn.MSELoss()**
**optimizer** **=** **optim.SGD(model.parameters(),** **lr=**0.001**)**

**optimizer.zero_grad()**
**outputs** **=** **model(torch.randn(**30**,** 10**))**
**labels** **=** **torch.randn(**30**,** 10**).to(**'cuda:1'**)**
**loss_fn(outputs,** **labels).backward()**
**optimizer.step()**

同样的想法可以很快扩展到更复杂的模型。我们可以在一个Sequential中分组多个层,通过to(device).分配到一个特定的 GPU 上,当然还有更多优化并行效率的改进,这里就不赘述了。

TL;速度三角形定位法(dead reckoning)

分布式模型并行训练有两个主要概念。模型并行实现了训练无法在单个 GPU 或设备上运行的大型模型。分布式培训可以通过在分布式设备之间划分模型来有效地向外扩展。PyTorch 和其他库(如 SakeMaker)通过最小的修改使生活变得更容易,尽管它在内部实现起来很复杂。

参考

https://arxiv.org/abs/2111.05972 https://aws.amazon.com/blogs/machine-learning/deploy-large-models-on-amazon-sagemaker-using-djlserving-and-deepspeed-model-parallel-inference/ https://aws.amazon.com/blogs/machine-learning/train-175-billion-parameter-nlp-models-with-model-parallel-additions-and-hugging-face-on-amazon-sagemaker/

分布式事务的设计模式

原文:https://towardsdatascience.com/distributed-transactions-cdc-event-sourcing-outbox-cqrs-patterns-ee0cf70339b1

了解事件来源、命令查询责任分离(CQRS)、变更数据捕获(CDC)和发件箱模式

最终演变为微服务架构的领域驱动的分布式应用架构具有许多优势,如交付服务的速度和敏捷性、小型和专注的团队、可扩展的设计、较小的代码库、容错和数据隔离。在适应微服务架构方面存在各种挑战,架构师和开发人员通常会遇到设计复杂性、数据完整性问题、治理和版本问题。

幸运的是,有架构驱动的解决方案和设计模式可以克服这些挑战。在这篇文章中,我们将主要关注解决数据一致性挑战,这是由于整个数据架构中的分布式事务。

Shubham DhageUnsplash 上拍摄

在本帖中,我们将简要介绍以下内容:

  • 活动采购
  • 变更数据捕获(CDC)
  • 命令查询响应分离
  • 发件箱模式

https://linkedin.com/in/p-jainani

在我们分别讨论每个问题之前,让我们先描述两个重要的概念:

  • 领域事件:它们是参与者与应用程序交互产生的业务事件。它们代表真实世界的事件,如loan approval、FraudDetected、ChartUpdated 或 OrderCancelled。领域事件与事件源相关联。
  • 变更事件:随着底层数据库中数据状态的变化而产生。这些是数据库事务日志,与变更数据捕获相关联。

活动采购

由业务逻辑生成的应用程序状态的变化被捕获为软件日志中的域事件。该日志允许遍历到时间线中任意点的应用程序的特定状态。日志是一个只追加存储,并且是不可变的。它们可以在任何时间点重复播放,并且是真实的单一来源。日志中的域事件按 ID 分组,以捕捉对象在任一时间点的状态。快照是一种重新创建对象状态的机制。

事件来源[图片由作者提供]

变更数据捕获(CDC)

如上所述,来自数据库事务日志的变更事件被下游消费者捕获和消费。因此,它是一种机制,通过这种机制,我们可以通过一些集成模式将应用程序状态共享给外部系统。

物化视图是 CDC 方法的关键概念,有一个外部过程将变更事件物化并转发给下游消费者。

有一个消息代理,它处理事件消息并将其转发给下游的消费者,并保证至少一次交付。

改变数据捕捉(CDC) 【图片由作者提供】

发件箱模式

发件箱模式确保应用程序状态(存储在应用程序数据库中)及其各自的域事件(转发给外部使用者)在单个事务下是一致的和持久的。发件箱表在应用程序的数据库中实现,以收集与事务对应的域事件。一旦我们有了事务保证机制,我们就可以使用发件箱表通过 CDC 传播事件交付,如上所述,与 CDC 连接器集成的代理将消息转发给外部消费者。

发件箱的重要之处在于,它只是一个临时的传出事件数据存储库,域事件在下游处理后会立即被删除。

发件箱图案【图片由作者提供】

命令查询责任分离(CQRS)

它通常与事件源相关联,有时用发件箱模式实现。作为视图的只读数据投影是 CQRS 实现的关键概念。对于不同的消费者,从相同的数据集可以得到多个这样的预测。这构成了分离查询部分 w.r.t. CQRS 实现的基础。

或者,CQRS 的命令方面适用于应用程序执行的动作,以生成作为域事件的响应。这使得能够生成投影的状态。因此,这与它与事件源的联系密切相关。

我们必须注意,将命令查询分离会导致两个数据模型的最终一致性。CQRS 模式的实现比带有发件箱的 CDC 更复杂。

命令查询责任分离(CQRS) 【图片由作者提供】

结论

本文简要介绍了分布式事务架构实现的各种设计模式。有各种全面的文献和文档解释了实现这些模式的细节、某些注意事项、优点和缺点。实际的实现也因用例而异,并且依赖于对实现技术的选择。

在后续的文章中,我们将通过解决一个业务用例,同时利用云原生平台服务,详细阐述每个模式的实现。

https://linkedin.com/in/p-jainani

深入现代 Web 部署:构建动态应用程序快速入门指南

原文:https://towardsdatascience.com/dive-into-modern-web-deployment-quickstart-guide-on-building-dynamic-applications-9e1eb2979f1e

用顺风 CSS 创建 Vue 应用

马克·哈普尔在 Unsplash 上拍摄的照片

对于构建动态应用程序的工具和工作流有足够的选择。经过一段时间的研究,你可能会同意我的观点,Vue 和 Tailwind CSS 在列表中名列前茅。对我来说,合乎逻辑的下一步是在做出承诺之前尝试一下。写这篇文章的目的是让你不用花太多时间就能体验一下这个设置,这样你就可以自己做决定了。

概观

Vue 是一个渐进式的 JavaScript 框架,这意味着你可以在了解整体之前就开始使用它。

Vue 的目标是构建用户界面,但要有风格。换句话说,Vue 需要使用 CSS 框架。Tailwind 是 CSS 领域的一颗新星,如果你耐心地给 Tailwind 一个机会,它会越来越吸引你。我喜欢顺风顺水的时尚和清新,相比之下,Bootstrap 的商业证明,但往往很无聊。

将 Vue 与 Tailwind CSS 集成的两种显而易见的方法是:1)用 Tailwind 初始化项目,然后添加 Vue,或者 2)在 Vue 中初始化,然后添加 Tailwind

我的经验表明,后者效果更好。来自顺风网站的文档比来自 Vue 网站的文档更准确。让我们通过以下步骤开始集成:

  • 创建 Vue 应用程序
  • 将 Tailwind 安装到应用程序中

在 Vue 中创建应用程序

要创建您的项目,请运行npm init vite project-name。您应该看到这个:

% **npm init vite v901**Need to install the following packages:
create-vite
Ok to proceed? (y)✔ **Select a framework:** › vue
✔ **Select a variant:** › vueScaffolding project in /Users/seanzhai/www/v901...
Done. Now run:cd v901
npm install
npm run dev

最后一个命令npm run dev现在不需要。不过测试一下也无妨。它在端口 3000 上创建一个本地 web 服务器。您可以在任何浏览器中验证这一点。

配置开发服务器

您可能注意到了 vite 开发服务器运行在本地主机的端口 3000 上。如果你想让你的开发服务器可以被其他机器访问,你可以通过改变vite.config.js文件来实现。

export default defineConfig({
  plugins: [vue()],
  server: {
 **port: '3030',
    host: '0.0.0.0'**
  }
})

通过如上所示的设置,当你做npm run dev时,你可以看到你在 3030 端口上服务于世界。你可能知道你可能需要注意你的防火墙设置来使它工作。

安装顺风 CSS

安装 Tailwind 并创建其初始配置。

npm install -D tailwindcss@latest postcss@latest autoprefixer@latest
npx tailwindcss init -p

第二个命令生成了文件tailwind.config.js。编辑行content,使文件如下所示。

module.exports = {
 **content: [
    "./vitesrc/index.html",
    "./vitesrc/**/*.{vue,js,ts,jsx,tsx}",
  ],**
  theme: {
    extend: {},
  },
  variants: {
    extend: {},
  },
  plugins: [],
}

然后,在src文件夹下创建index.css

/* ./src/index.css */
@tailwind base;
@tailwind components;
@tailwind utilities;

src用于所有源代码,下面有一个文件夹components用于存放 Vue 组件。我们需要将 index.css 文件导入 main.js,这是所有 app 的入口。

// src/main.js
import { createApp } from 'vue'
import App from './App.vue'
**import './index.css'**createApp(App).mount('#app')

到目前为止,您应该有一个支持 Tailwind CSS 的功能性 Vue 应用程序。祝贺您成功整合。

顺风美丽的 Vue

对 Tailwind 最常见的批评是缺乏预建组件,这可能是有 bootstrap 背景的人拒绝 Tailwind 的主要原因。

顺风的补救措施是推广 tailwindui.com 的付费服务。如果预算不是问题,你当然可以试一试;它展示了许多布局和小工具,但它们不容易用作模板。您需要花费一些努力来适应 tailwindui 提供的组件。

我发现最有用的是 Headless UI,它是专门为使用 Tailwind 而设计的。是 tailwindui 背后的同一批人开发的。

安装和第一步

无头 UI 安装非常容易。

npm install @headlessui/vue @heroicons/vue -D

看一下它的官方网站。

https://headlessui.dev/

注意它的组件支持 ReactVue 。默认设置是 React,请在进入特定组件前点击 Vue 。我曾经忘记了这一点,无意中进入 React 部分,我发现代码有点奇怪。出于比较的好奇,要达到相同的结果,Vue 中需要的行数几乎总是比 React 中少。

使用无头用户界面风格化

花一点时间剖析一个组件可以学到很多东西。菜单(下拉菜单)就是一个很好的例子。让我们一起使用 Visual Studio 代码来完成这项工作。

用 VS 代码打开项目根文件夹,定位src。创建了一个名为components的文件夹,然后添加了一个文件Dropdown.vue。让我们用琼·狄迪恩最著名的书来填充菜单。该文件如下。它基于 HeadlessUI 网站上的示例。我把它简化了一点,使它成为一个独立的页面,这样更容易理解。

要使用这个组件,我们只需要从主应用程序中引用它。我添加了一些基本样式,使组件易于查看。

结果看起来像这样:

带有顺风 CSS 的 Vue 中的 HeadlessUI 组件示例|来源:Sean Zhai

收场白

Vue 和 Tailwind 都是很神奇的工具。我最喜欢使用 Vue 和 Tailwind 的地方是,它让你大部分时间都呆在同一个上下文中,没有在 JavaScript、CSS 和 HTML 文件之间跳跃;开发服务器的自动重新加载也节省了时间。这是一个让你进步的工作流程,你会发现自己变得更有效率,压力更小。

它既有趣又美丽。

深入研究 C-支持向量分类

原文:https://towardsdatascience.com/diving-into-c-support-vector-classification-221ced32e4b4

SVC 算法能为我们做的技巧

大卫·罗蒂米Unsplash 上的照片

介绍

最近我一直在研究 Scikit-Learn,并记下了这个神奇的库所提供的工具。在本帖中,我们来了解一下C-支持向量机分类器。

支持向量机(SVM)是一种监督学习算法,可用于分类或回归。它适用于边界的线性和非线性计算,因此对一些问题很有用。

支持向量机可以替代其他好的算法,如决策树、逻辑回归或随机森林,因此它是一个很好的技能补充。

支持向量机

支持向量机是一种将数据点分为两类的算法。一旦对所有点都完成了,算法就开始追踪两个类之间的分离边缘处的一些线,目标是最大化它们之间的距离。它找到最大距离的地方就是最佳分隔线所在的地方。

蓝线是两个等级之间的最大距离。图片由作者提供。

对于线性分离的数据集,该算法工作得非常好。但是,如果我们的数据不是线性的呢?怎么还能用?

如果我们查看文档,我们会发现有一个名为kernel的超参数。内核是算法用来将点分成不同组并对它们进行分类的逻辑。

内核: { '线性',' poly ',' rbf ',' sigmoid ',' precomputed'}或可调用,默认='rbf'

线性的

linear内核非常简单。SVM 将创建线条,就像之前显示的图形一样。

SVC(kernel='linear')

多项式

poly选项是针对多项式内核的。如果你观察多项式的形状,你会发现随着次数的增加,曲线越来越多,变得越来越不规则。因此,对于模型欠拟合,增加多项式次数可能是一个好主意,使决策边界绕过更多的点。C是正则化超参数,coef0平衡了模型受高次或低次多项式的影响。

SVC(kernel='poly', degree=3, coef0=1, C=5)

肾血流量(renal blood flow 的缩写)

该内核rbf用于高斯径向径向径向径向函数。它创建高斯分布来计算哪一个点更适合,以确定如何对这些点进行分类。超参数gamma使高斯曲线更窄(高伽玛值,更多偏差)或更宽(低伽玛值,更平滑的边界)。所以,如果我们增加伽玛,我们的决策边界会更加不规则。如果你的模型不合适,试着增加这个数字。如果过拟合,降低伽玛。C是正则化数。它的工作方式与 gamma 参数相似。

SVC(kernel='rbf', gamma=4, C=100)

乙状结肠的

内核sigmoid使用类似逻辑回归的逻辑,其中高达 50%的概率属于一个类,超过这个数字,它属于相反的类。你可以使用gamma超参数来正则化。

SVC(kernel='sigmoid', gamma=2)

预先计算的

最后,这最后一个内核用于更高级/定制的情况,您可以创建自己的内核来运行模型。

编码

使用 sklearn 的 SVC 类创建一个基本的 SVM,并不需要花费太多时间。

# Imports
import pandas as pd
import seaborn as sns# Data
from sklearn.datasets import make_classification# sklearn
from sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.svm import SVC
from sklearn.metrics import confusion_matrix

创建数据集并在训练和测试中拆分。

# Dataset
X, y = make_classification(n_classes=2, n_features=6, n_samples=500, n_informative=2, scale=100, random_state=12)# Dataframe
df = pd.DataFrame(X, columns=['var'+str(i) for i in range(1, X.shape[1]+1)])
df['label'] = y#Train test split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=12)

快速查看创建的数据。

本例的数据集。图片由作者提供。

让我们使用 RBF 内核创建一个 SVM。建议您缩放数据以获得更好的结果。为此,我们可以用运行该任务的步骤和函数的名称创建元组。请注意,我们正在(1)缩放数据;(2)用核 RBF 训练一个 SVC。

steps = [('scaler', StandardScaler()),
         ('svm_classif', SVC(kernel='rbf', gamma=0.5, C=10))]# Create Pipeline object
rbf_kernel = Pipeline(steps)# Run the pipeline (fit) 
#Scale data and Fit the model
rbf_kernel.fit(X_train,y_train)# Predictions
preds = rbf_kernel.predict(X_test)

接下来看看表现。

# performance dataframe
result = pd.DataFrame(X_test, columns=['var'+str(i) for i in range(1, X.shape[1]+1)])result['preds'] = preds# Plot var1(on x) and var5(on y)
sns.scatterplot(data=result, x='var1', y='var5', hue='preds');

这就产生了下面的情节。

这里是混淆矩阵,看看这个模型在分类方面的表现。

# Confusion Matrix
pd.DataFrame(confusion_matrix(y_test, result.preds, labels=[0,1]))

SVC 的混淆矩阵。图片由作者提供。

非常好!只有 5 个假阳性和 1 个假阴性,准确率高达 94%。

如果我们训练一个随机森林分类器,这就是结果。

from sklearn.ensemble import RandomForestClassifiersteps = [('scaler', StandardScaler()),
         ('rf_classif', RandomForestClassifier())]# Create pipeline
rf = Pipeline(steps)# Fit
rf.fit(X_train,y_train)# Preds
preds = rf.predict(X_test)# performance
result = pd.DataFrame(X_test, columns=['var'+str(i) for i in range(1, X.shape[1]+1)])result['preds'] = preds# Confusion Matrix
pd.DataFrame(confusion_matrix(y_test, result.preds, labels=[0,1]))

随机森林的混淆矩阵。图片由作者提供。

类似的结果。这里的准确率是 92%,稍微低一点,但是我们必须注意到对于随机森林没有任何调整。可以改进。

在你走之前

我相信知道更多的算法和它们如何在引擎盖下工作是很好的。像数据科学中的许多事情一样,最佳选择不是这个或那个算法,而是为您的问题提供最佳结果的算法。所以,多了解一个,你就增加了获得更好结果的机会。

在这篇文章中,我们深入研究了SVC算法,学习如何为每个内核选择主超参数,以及它们本质上是如何工作的。

请记住,文档是您的朋友,可以提供很多帮助。另一个很好的资源是这本书《sklearn、Keras 和 Tensorflow 的机器学习实践》。我一直在阅读和享受很多。

在 GitHub 的这个库中找到这篇文章的代码。

这是我的博客,如果你喜欢这个内容,想关注我或者在 Linkedin 找到我。

http://gustavorsantos.medium.com/

如果你想成为中级会员,这个推荐代码会激励我加入你的订阅。

参考

https://scikit-learn.org/stable/modules/generated/sklearn.svm.SVC.html https://scikit-learn.org/stable/modules/svm.html#svm-kernels

https://en.wikipedia.org/wiki/Support_vector_machine

https://www.amazon.com/Hands-Machine-Learning-Scikit-Learn-TensorFlow/dp/1492032646/ref=asc_df_1492032646/?tag=hyprod-20&linkCode=df0&hvadid=385599638286&hvpos=&hvnetw=g&hvrand=847972974391386897&hvpone=&hvptwo=&hvqmt=&hvdev=c&hvdvcmdl=&hvlocint=&hvlocphy=9009674&hvtargid=pla-523968811896&psc=1&tag=&ref=&adgrpid=79288120515&hvpone=&hvptwo=&hvadid=385599638286&hvpos=&hvnetw=g&hvrand=847972974391386897&hvqmt=&hvdev=c&hvdvcmdl=&hvlocint=&hvlocphy=9009674&hvtargid=pla-523968811896 https://stats.stackexchange.com/questions/90736/the-difference-of-kernels-in-svm

一个漂亮的 Github 页面,包含每个内核的所有可视化内容:

https://gist . github . com/WittmannF/60680723 ed 8d d0 CB 993051 a 7448 f 7805

利用泰坦尼克号的数据潜入潮汐湖

原文:https://towardsdatascience.com/diving-into-the-tidyverse-using-the-titanic-data-83f54295d5df

使用 R 的 dplyr 和 tidyr 软件包提取见解

Max van den Oetelaar 在 Unsplash 上拍摄的照片

如今,数据比以往任何时候都更容易获得。我们可以很容易地通过互联网上的许多资源找到我们感兴趣的东西。然而,当试图从数据中得出结论时,我们很快意识到,尽管这些数据很容易找到,但其格式并不能用于更详细的分析。当几个人试图合作进行某项研究时,也会出现同样的问题。如果他们之前没有就如何标记和输入数据达成一致,每个人很可能会提出自己的系统。这最终会导致共享和分析数据时的混乱。这些问题产生了对独特的数据组织系统的需求。

列夫·托尔斯泰在他的作品《安娜·卡列尼娜》中表达了这样的思想:

所有幸福的家庭都是相似的;每个不幸的家庭各有各的不幸。

引用这句话,R 中编程的远见者之一 Hadley Wickham 用下面的话解决了数据组织的问题:

整洁的数据集都是相似的,但每一个杂乱的数据集都有自己的杂乱之处。

有了整齐的数据,每一行就是一个观察值,每一列就是一个变量。假设我们按马力和重量列出汽车的类型。有序组织的数据将包含三列:汽车类型、马力和质量。因此,通过查看一行数据,我们将获得一种类型汽车的所有信息。聚焦于一个数据列将提供关于所有类型汽车的单个属性(例如质量)的信息。

为了将数据转换成一种整洁的格式,Hadley Wickham 开发了tidyverse——一套软件包,包括以下软件包:dplyrtidyrggplot2purrrreadrtibble。在下面的文本中,我们将重点关注用于数据争论的这些包的子集- dplyrtidyrtibble

尽管tidyverse提出了一套一致且有效的方法来转换和可视化数据,但强调两点还是很重要的:

  • 使用基本的 R 函数可以获得使用tidyverse函数获得的结果(尽管通常以更复杂/更不直观的方式)
  • r 是一种编程语言,tidyverse是一组旨在促进数据转换和可视化的包。换句话说,tidyverse包不能解决我们可能遇到的所有问题,所以了解 R 编程的基础也很重要。

泰坦尼克号数据集

NOAA 在 Unsplash 上拍摄的照片

在下文中,我将介绍dplyrtidyrtibble软件包最重要的功能。我将展示如何使用这些函数来执行数据清理和探索性数据分析中涉及的各种常见任务。我将使用最著名的船只之一泰坦尼克号的乘客数据。

作为第一步,我们必须加载必要的包,并研究我们将使用的数据的结构。除了数据争论包,我们还必须加载包含所需数据的titanic包。

Titanic 数据集是一个data.frame类型的对象。因此,如果我们打印 Titanic 数据集,将返回全部 891 行。由于这样的布局既笨拙又麻烦,所以我使用 base R head函数只打印了前十行。为了绕过这个问题,tibble包引入了一个tibble数据类型。这种类型对data.frame类进行了改进,在显示数据集时只打印前十行。它还打印出数据集的行数和列数以及每列的数据类型。

甚至head函数也可以改进,因为它不指示列数据类型,并且当有许多变量(许多列)时,它不显示所有变量,而是只显示屏幕上能容纳的数量。相反,我们可以使用glimpse命令显示打印输出的转置版本,以获得更完整的数据概览。

我们可以看到,数据包含 12 个变量:乘客序列号(PassengerId)、二进制乘客生存变量(Survived)、乘客类别(Pclass)、姓名(name)、性别(sex)、年龄(age)、机上兄弟姐妹和配偶人数(Sibsp)、机上子女/父母人数(Parch)、票号(ticket)、已付车费(fare)、客舱号(cabin)和乘客的登机地点(Embarked)。

管道%>%运算符

照片由西格蒙德Unsplash 上拍摄

在继续分析之前,我想介绍一下管道操作符。管道操作符的优点是能够绑定函数。更准确地说,它将一个函数的结果作为第一个参数传递给序列中的下一个函数。乍一看,这似乎不是很有用,但是当使用许多函数的组合时,它极大地增加了代码的清晰度。让我们看一个例子。

管道运算符使我们能够从左到右阅读作文,就像我们在阅读一本书一样!一旦你习惯使用它,我保证你永远不会回头。这就是为什么,即使没有它所有的 R 函数都可以使用,我将在这篇文章的剩余部分使用管道。

使用 dplyr 包提取信息

dplyr包包含的函数有助于数据操作以提取有意义的见解。它在一定程度上受到 SQL(结构化查询语言)的启发,所以那些有 SQL 背景的人可能会发现它们非常直观。

重命名列

我们将从使用rename重命名列开始,以便使它们更容易理解。

通过使用rename_with函数将一个函数应用于多个列,也可以进行重命名。例如,我们可能更喜欢所有变量名都用小写字母书写的约定:

根据名称选择列

我们通常对所有的数据集变量都不感兴趣。select函数允许我们对感兴趣的列进行子集划分。

如果所需的列在初始数据框中相邻放置,还有一种更简单的方法来选择它们:

如果我们只想选择姓名和与船上亲属数量相关的变量,该怎么办?当然,我们可以再次使用第一个例子中的技术。然而,select也可以与大量的帮助功能结合,使这些任务更快更容易执行。例如,contains助手允许我们通过部分列名进行搜索。如果我们注意到与亲戚数量相关的两个变量都包含下划线符号,我们就可以使用这个。

类似于contains功能,我们也可以使用starts_withends_with功能。要获得所有帮助函数的列表和解释,您可以通过在 R 控制台中键入?select来查阅帮助文件。

也可以通过指定我们想要从数据中删除的列来执行列选择。这是通过在不需要的列的名称前放置减号来实现的。

让我们通过删除机票、客舱号和与机上亲属/配偶人数相关的列来简化数据集:

基于值的列选择

如果我们只对包含数值的列感兴趣呢?我们可以手动查找变量类型,然后按照上一小节中的步骤操作。

幸运的是,这个繁琐的任务可以自动化,因为通过将selectwhere函数组合起来,也可以基于存储在其中的值来执行列选择。

使用 mutate 转换现有列或添加新列

mutate功能允许我们使用外部数据或数据框中已有的数据转换现有列或创建新列。

r 有一个方便的内置factor类,当分类变量的可能值(级别)有限时,应该使用这个类。我们可以看到性别变量有这样的性质。mutate使我们能够改变列数据类型:

你们中的一些人可能也想缩写性别值。这可以使用case_when功能来完成:

一次转换多列

你们中的一些人可能已经注意到,将 Survived 和 also 列的数据类型从字符转换为因子也不错。

我们可以通过应用mutate + across组合来同时转换多个列,而不是通过逐个指定列来实现。across函数指定要转换的列和要应用的函数。

在讨论select函数的应用时,也可以使用提到的辅助函数来选择across函数中的列。

Filter数据框行

filter函数用于从数据中提取观察值的子集。因此,它可以用来回答关于机上不同乘客群体的问题。

坠机事件中幸存的乘客比例是多少?

只有三分之一多一点的乘客幸免于难。乘客性别对存活率有影响吗?

乘客中女性的比例是多少?

坠机事件中幸存的女性乘客比例是多少?

因此,尽管女性乘客人数较少,但她们的生还几率大约是男性的两倍。当然,这是意料之中的,因为妇女和儿童在救援任务中享有优先权。

接下来,我们将检查存活率如何根据乘客等级而变化。

如果你在下层阶级旅行,生还的机会会更低。这可能是由于富裕乘客的影响,也可能是由于头等舱更靠近救援船停靠的甲板。

还有filter帮助函数if_anyif_all,允许我们一次过滤多个列。这些可用于执行移除具有缺失值的观测值的常见任务。

Arrange基于列值的行

照片由托尔加·乌尔坎Unsplash 上拍摄

通常,我们希望根据感兴趣的特定标准对数据进行排序。这是使用arrange通过dplyr完成的。我们先按年龄升序对乘客进行排序,然后按姓名字母顺序对同龄乘客进行排序。默认情况下,排序是升序,但是可以使用desc命令来进行降序排序。

从这个输出,我们可以假设老年乘客更可能是男性而不是女性。此外,年龄较大的乘客似乎是等级较高的乘客。对我来说,这是一个看似合理的说法,因为我知道泰坦尼克号从英国驶向美国,载着许多人寻找新的机会和更好的生活。我认为这样的人会更年轻,因为年纪大的人没有动力搬走。我们将能够用下面两个小节中介绍的工具回答所有这些问题。

summarise获取列汇总统计

安妮·尼加德在 Unsplash 上拍摄的照片

当我们对单个值不感兴趣,而是对数据集的汇总统计数据感兴趣时,这个命令非常有用。让我们计算一下乘客的平均年龄。

我们得到了意想不到的结果。这是因为数据中的“漏洞”已被替换为 NA(“不可用”,缺少数据)。我们可以通过在计算汇总统计数据之前过滤数据帧以排除年龄未知的乘客来解决这个问题。在过滤时,我们使用一个特殊的命令来测试数据是否属于NA - is.na类型。

就像使用mutatesummarise也可以与across结合使用,以便一次获得多个列的摘要。

为了回答年龄差异取决于性别和乘客等级的问题,我们可以首先过滤感兴趣的人群,然后计算所需的汇总。然而,这种方法要求我们每次都要写一份新的总结,类似于我们根据乘客等级计算存活率的方法。为了避免这种情况,我们将使用下一小节中介绍的group_by函数。

使用group_by根据分类属性对观察值进行分组

就其本身而言,summarise函数并不那么有用,但是与group_by函数配合使用,它可以快速汇总所有级别的分类变量的信息。group_by函数根据某个变量的类别将数据分组。然后使用summarise将感兴趣的函数分别应用于这些组中的每一组,并将结果组合回单个数据帧。

group_by 如何在幕后工作。图片作者。

这种结合最终使我们能够优雅地回答早先提出的假设。

船上 55 岁以上的男性比我们多三倍,所以看来我们的第一个假设是正确的。

让我们也检查一下关于阶级相关的年龄差异的第二个假设:

我们的预感似乎得到了数据的支持,因为乘客等级和年龄之间确实存在关联。

请注意,这种方法也使我们能够更容易地找出基于乘客等级的生存差异,这在filter小节中讨论过。

summarise功能类似,group by命令可以与mutatefilter命令结合使用。

让我们创建一个变量,根据性别按年龄对乘客进行排名。rank命令将帮助我们从最小到最大为一组值分配一个序数。与arrange命令一样,使用desc可以颠倒默认顺序。

例如,将group_byfilter结合起来,我们可以得到每种性别的三名最年长乘客的列表。

结果与我们之前的结论一致,因为年龄最大的女性乘客比年龄最大的男性年轻 17 岁。

事实证明,还有一个方便的slice_max函数可以达到同样的目的,所以上面的片段应该只是作为一个教育的例子。

要取消分组,我们使用ungroup功能。建议在任何涉及分组的转换结束时这样做。这是因为忘记我们已经根据一些变量对数据进行了分组,可能会在进一步的分析中导致意想不到的结果。

组合/匹配数据帧的功能

联接函数的工作原理。图片作者。

此前,在计算平均年龄时,我们注意到泰坦尼克号的数据是不完整的。让我们看看这些乘客是谁:

幸运的是,泰坦尼克号的失事如此著名,以至于有许多网页专门列出乘客及其背景。因此,我们可以创建一个新的数据框,其中包含一些数据缺失的乘客的姓名和年龄。

由于丢失的数据分散在 Titanic 数据集中(看看 PassengerId 值,它实际上只是行号),很难手工追加额外的信息。这些类型的问题可以通过left_join来解决,这是一种基于公共标识变量将数据从一个数据帧添加到另一个数据帧的功能。

请注意,我们有 Age.x 和 Age.y,而不是单个年龄变量。这是为了让我们能够辨别年龄变量来自哪个数据框。我们可以使用mutatecase_when将两个年龄列合并成一个。

与 tidyr 一起整理

顾名思义,tidyr包包含将数据帧转换成整齐格式的命令。泰坦尼克号的数据框架已经很整洁了,因为每一行代表一个乘客,每一列代表一个独立的乘客特征。然而,数据集通常不是这样。此外,在某些情况下,如绘图,可能需要“不整洁”的格式。下一小节将说明这样一个例子。

使用 pivot_wider 和 pivot_longer 进行宽长格式转换

这些可能是tidyr包中最有价值的功能。当我们想要将多个列转换成具有多个子组的单个列时,pivot_longer函数非常有用。下图说明了这是如何工作的。

枢纽函数的工作原理。图片作者。

左边的表格包含三个相关的列,它们都代表一种相对类型。知道了这一点,我们可能希望将它们分组到一个“相对”列中。这使得数据更长(因此函数名也更长)。此外,它变得凌乱,因为同一个人是由三个独立的行代表。我们为什么要这么做?

如果我们想做一个条形图,在水平轴上显示相对类型,我们必须提供一个变量。我们可以用右边的格式做到这一点,但不能用左边的格式。

现在让我们展示如何将这些转换应用到泰坦尼克号数据上。为了便于理解转换,我们将只选择 Titanic 数据帧的前三行:

通过应用pivot_longer,我们可以使用更少的列显示相同的数据。例如,我们可以有一个乘客姓名列和两个包含乘客属性所有信息的列(乘客级别、年龄、性别、生存)。为什么属性有两列而不是一列?因为一列必须包含有关所讨论的属性的信息,另一列必须包含该属性的值。因为我们要组合数字和字符值,所以我们必须首先将所有值转换为相同的类型(字符)。

正如所料,行数增加了 7 倍,因为现在我们必须重复每个乘客的姓名 7 次——对 Parameter_name 列中的每个参数重复一次。当然,与上图中的例子相反,以这种方式对参数进行分组是没有意义的,因为它们是不相关的。但是,如果您经常处理数据,这些类型的问题将是最不重要的,因为输入至少是一致的。您可能永远也不会自己创建这样的数据,但是我在这里这样做是为了向您展示当有人把它带过来时,如何摆脱这种混乱(相信我,最终会有人这样做……)。

作为pivot_longer函数的逆函数,pivot_wider就是答案。

当然,我们还应该将列值转换回它们正确的类型。

分离()和联合()

威尔·弗朗西斯在 Unsplash 上拍摄的照片

和前面提到的pivot_longerpivot_wider一样,这两个函数也是互逆的。使用separate功能的最简单的例子是分隔日期记录。假设我们有一列日 _ 月 _ 年格式的奥运奖牌获得者的生日,我们想计算获奖者的平均年龄。这意味着我们必须从日期中提取年份。使我们能够做到这一点,例如,从一个日期变量生成三个变量:日、月和年。unite函数的工作方式正好相反,将多个变量连接成一个变量(例如,将日、月、年转换成格式为日 _ 月 _ 年的日期变量)。本质上,它只是一个带有一些附加参数的paste函数,比如允许从数据框中自动删除输入列。

看看乘客的名字,我们可以看出他们可以分成几组。逗号前的第一个字是姓,后面是头衔和名。将这些组分开是有用的,因为我们可以获得额外的信息。例如,乘客头衔中的婚姻状况信息—已婚乘客的先生/夫人,单身乘客的主人/小姐。

对于那些不熟悉正则表达式的人来说,表达式[,\\.]可以翻译为:每当遇到逗号或点时,就将字符串分解。为什么我们需要点前面的反斜杠?因为点本身在正则表达式中有特殊的含义,所以这是告诉 R 我们感兴趣的是点字符本身,而不是它的特殊功能。

这些标题让我们对乘客群体的社会结构有了深入的了解,否则我们很难了解。

结论

我希望这篇文章对你有用,并且你学到了一些新东西。我试图涵盖尽可能多的常见任务和问题。然而,没有一个单独的(非模拟的)数据集包含您在数据分析中可能遇到的所有问题,因此还有许多问题和场景没有涉及。此外,没有可视化的数据,任何探索性的数据分析都是不完整的。因为加上这个会让这篇文章太长,所以我把它留到以后的故事里。然而,如果您有任何问题或遇到一个复杂的问题(包括可视化)想要使用tidyverse工具箱解决,请随时发表评论!

附言:要快速浏览/提醒dplyrtidyr封装中的功能及其用法,您可以使用 RStudio 提供的便捷的备忘单

posted @ 2024-10-18 09:31  绝不原创的飞龙  阅读(363)  评论(0)    收藏  举报