TowardsDataScience 博客中文翻译 2020(七百七十)

原文:TowardsDataScience Blog

协议:CC BY-NC-SA 4.0

“大规模”运行谷歌的云数据融合批处理管道

原文:https://towardsdatascience.com/running-googles-cloud-data-fusion-batch-pipelines-at-scale-f1064b869dff?source=collection_archive---------30-----------------------

理解大数据

为数据科学工作流测试云数据融合

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

将数据从遗留 sqlserver 转移到 BigQuery 的典型 CDF 管道。作者图片

TLDR:当通过 REST api 大规模提交批量云数据融合(CDF)管道时,在每次调用之间暂停几秒钟,让 CDF 跟上进度。

背景:作为我们参与的迁移的一部分,我们的数据科学团队正在将数百个遗留的 MS Sqlserver ODS 表迁移到 BigQuery 中。虽然我们的工程团队正在处理实际的迁移,但我们(DS 团队)希望我们自己能够快速地在 GCP 构建、原型制作和迁移我们的模型,而无需等待我们的工程团队所承担的所有质量和范围广泛的要求。进入谷歌的云数据融合。基于 CDAP ,这是对我们团队问题的一个很好的解决方案:我们希望定期将遗留表复制到 BigQuery 中,以服务于我们的模型。我们,数据科学团队,完全控制调度和范围,CDF“无代码”接口使所有团队成员可以直接利用 spark 的能力。遵循一些简单的标准,很容易添加额外的管道。

问题:在迁移开始时,我们确定了要复制到 BigQuery 的所有表和源——基本上是迁移的范围。我们有大约 100 个表,我们构建了 1 个管道来迁移 1 个表,所以总共有大约 100 个管道。管道运行的节奏在每日和每月之间变化,大多数表介于两者之间。有一段时间一切都很好,直到我们构建了一个需要 60 个表同时加载的模型。我不会解释为什么我们要构建一个需要 60 个表的模型,而是强调这个需求是如何对云数据融合的可伸缩性进行有趣的测试的。

在这一点上,我们都同意 CDF 不是同时执行 60 个管道的最佳工具,每个管道迁移一个表,其中许多在 10 分钟内完成,但这是我们发现自己所处的情况。CDF 不太适合这项任务,原因有很多,其中一个原因是,它需要 60 个小型 MR/spark 集群来完成大部分 10 分钟的迁移任务。实际上,这些 MR/spark 集群(GCP 的 dataproc,AWS 的 e MR)的配置时间只有 2-3 分钟,成本实际上并不多。我们首先构建了一个管道列表,遍历该列表,并从 GCE VM 通过 CDAP 的 REST api 在 bash 脚本中执行它们。虽然我们最终异步调度了这些任务,但是我们想证明我们可以同时执行这些任务,并且 CDF 可以根据我们的需求进行扩展。

我们最初的发现令我们失望。我们的脚本通过 REST api 在一秒左右的时间内执行了启动管道命令,在 60 个管道中,有 25 个失败了,几乎没有日志。当我们解析 CDF 的 App Fabric 服务日志时,我们遇到了一个有趣的错误:

2020–10–30 22:27:34,715—WARN[pool-10-thread-1:I . c . c . I . a . s . runrecordcorrectorservice @ 148]—修复了 25 个状态为[正在启动、正在运行、已暂停]的运行记录,但程序实际上并未运行

2020–10–30 22:27:34,716—INFO[pool-10-thread-1:I . c . c . I . a . s . runrecordcorrectorservice @ 103]—更正了 25 条状态为[正在启动、正在运行、已暂停]的运行记录,这些记录没有实际运行的程序。这类程序很可能已经崩溃或被外部信号杀死

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

一个 dataproc 集群,它托管了我们的一个失败的管道,成功的和失败的管道的集群在日志方面是相同的。作者图片

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

纱线容器数与时间。在下午 6 点以后,可以看到成功创建了 dataproc 集群。作者图片

虽然 CDF 收到了 60 个“执行管道”api 调用,但它错过了 25 个运行记录。有趣的是,当我们查看 dataproc 日志时,我们发现它成功地创建了 60 个集群。也就是说,我们观察到成功创建的集群,甚至是托管失败管道的集群——这帮助我们认识到,我们在创建 dataproc 集群时没有可伸缩性问题,而是在创建 CDF“主”时有问题。问题在于 CDF 的簿记/跟踪,而不是 dataproc 集群的创建。作为数据科学家,而不是工程师或架构师,我们对这是如何工作的知识有限,但我们假设当我们在大约 1 秒钟内用 60 个 api 调用淹没系统时,我们过载了使用的任何跟踪数据库。我们再次尝试在每个 api 调用之间添加一些填充。我们发现,等待大约 10 秒钟会导致所有 60 个管道执行调用被成功提交和执行。虽然不是最优雅的解决方案,但它为我们完成了工作,因此我们可以考虑其他事情,这在数据科学中非常重要。

更新:我们已经探索并正在我们的工程团队的 Cloud Composer 实例上执行和调度这些带有气流的管道,但是当昂贵而复杂的编排资源不可用时,上面概述的方法仍然是一个可靠的选择。

在 15 分钟内在云上运行 Jupyter 笔记本电脑#Azure

原文:https://towardsdatascience.com/running-jupyter-notebook-on-the-cloud-in-15-mins-azure-79b7797e4ef6?source=collection_archive---------9-----------------------

文章做到了标题所说的。在 Azure 笔记本电脑(免费或付费)上运行 Jupyter 笔记本电脑,成本很低。

为了更便宜、更快、更好地运行最好的数据科学模型,我寻找了多种途径来编译复杂的代码。我经常在我的 Github 上运行深度学习、神经网络(RNN,CNN)、自然语言处理(NLP)和高级机器学习(ML)的模型。在我以前的文章中,我比较了数据科学家在云上运行笔记本的多种选择,现在我正在深入研究我最喜欢的一种选择——Azure 笔记本

在云上运行 Jupyter Notebook 的最大优势之一是,你可以拥有一台十年前的 PC/MAC ,它无法在本地运行最新的深度学习算法,但仍然能够在云上部署最先进的机器学习模型,只要你在本文的帮助下设置它。请继续阅读!

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

通过 Azure 云的计算有无限的潜力

在线云计算的核心问题是我们对云感到不舒服。配置、IP、存储选项、计算选择和连接的变化让我们不知所措。

我要为一个数据科学家破译云。

云是一个难以驾驭的空间,但是,通过一点点帮助,你将得到你想要的东西——一种运行 Jupyter 笔记本电脑的极其廉价和高效的方法。我将努力做到以下几点:

  1. 在 Azure 笔记本上设置 Jupyter 笔记本
  2. 激活并导航 Azure 门户
  3. 设置 Azure 数据科学虚拟机(DSVM)
  4. 将 DSVM 连接到 Azure 笔记本电脑

这可能是一个漫长的阅读,但是,如果你的意图与我的一致,你会满意的。

免费获得高级 Azure 服务:

快速补充说明:大多数追求相同目标的人都在一个知名教育机构的项目中工作。所以,我建议你利用 Azure 的学生福利,这样你就可以免费获得 100 美元的学分——只需使用你所在学院提供给你的电子邮件。我喜欢我们生活的这个时代,云计算比一罐可乐还便宜,所以去 Azure for Students 免费激活你的学生账户吧。这是一个简单的过程,不会超过几分钟。感谢我以后

1.在 Azure 笔记本上设置 Jupyter 笔记本

我在这里 为我的数据科学家同事 写了一个详细的分步过程,你可以按照它来设置并理解在线设置 Jupyter 笔记本的细微差别。

[## Jupyter 笔记本崩溃了,所以我在 15 分钟内就把我的数据科学项目部署到了云中(对于…

你可以用一个很老很老的系统免费进行奇特的数据科学研究——只要动动脑筋,从这个博客中获得帮助。

medium.com](https://medium.com/datadriveninvestor/jupyter-notebook-crashed-so-i-deployed-my-data-science-project-in-the-cloud-in-15-minutes-for-56edc0ac4d0d)

它的本质如下所述:

  1. 打开 Azure 笔记本,登录并开始一个新项目
  2. 打开一个新笔记本并选择运行它的内核
  3. 选择自由层选项作为计算
  4. 导入所需数据并在 Jupyter 笔记本上运行

你现在应该有一个可以在云上运行的 Jupyter 笔记本了。上述空闲层中的基本配置是 1GB 存储和 4GB RAM。在这篇博客的后面,我们将致力于增加存储、内核、内存和计算能力。

2.激活并导航 Azure 门户

您用来制作 Azure 笔记本的帐户可以在此处登录 Azure 门户。你现在应该可以看到 Azure 仪表盘和门户了。现在让我们快速浏览一下我们可能关心的元素和术语:

  • 微软 Azure 是微软创建的云计算服务,用于通过微软管理的数据中心构建、测试、部署和管理应用程序和服务。
  • 虚拟机(VM) :虚拟机是计算机系统的仿真。这本质上就像在云中拥有一台计算机,但是,您不是管理硬件,而是根据自己的需要租用系统。为什么?
    这大大降低了计算成本。正如我在之前的博客中提到的,这意味着总成本约为 1500 美元——2000 美元的硬件可以在你十年前的笔记本电脑上运行,而成本只是它的一小部分。我说的分数是指固体构型低于 30/小时。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

通过 Azure 云服务( Pixabay )释放您电脑的真正潜力

  • 云服务:云计算是计算机系统资源的按需可用性,尤其是数据存储和计算能力,无需用户直接主动管理。本质上,你只需在服务被使用时付费。
  • 弹性:云适应性强。
    -运行简单的数据清理和特征工程步骤?
    →太好了!你可以使用免费的或较低层次的(更便宜的)作为计算。
    -运行需要更多计算的复杂模型?
    →又棒了!只需升级系统规格,运行模型一个小时,然后再次转移到较低层。
  • Blob 存储:Azure 存储的奇特术语。这是一个针对存储结构化和非结构化数据而优化的文件系统,您可以在 Jupyter 笔记本中使用它进行分析。

请记住,这个博客并没有教你如何利用 Azure 的所有功能,它专注于帮助数据科学项目,特别是通过 Azure 笔记本部署在云上的 Jupyter 笔记本。如果你感兴趣,我强烈建议你花些时间阅读微软的伟大文档。

3.设置 Azure 数据科学虚拟机(DSVM)

导航到 Azure 门户网站,你应该会看到一个类似这样的屏幕:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Azure 门户主页

我们现在将创建一个虚拟机来连接 Azure 笔记本。该接口将是一个 Jupyter 笔记本,其中的计算将出现在 Azure 服务上。如下所示创建一个资源。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

创建资源

搜索 Data Science Virtual Machine for Linux(Ubuntu)并选择它。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

搜索Data Science Virtual Machine for Linux(Ubuntu)

您现在应该会看到一个类似于下图的屏幕。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

继续创建一个基于 Linux 的数据科学虚拟机

您可以根据需要配置设置。我已经填写了一个基本的样本,你可以遵循。不要被价格吓倒,因为如果你决定整个月都运行这个系统,价格是被提到的。我们将运行虚拟机几个小时,而不是几天,因此,这将是一个非常小的一部分成本,您可以在继续之前查看。如果你使用 Azure 的学生订阅,那么你可以免费获得一年 100 美元。就算你不是,也是极其实惠的。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

示例配置— 1

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

示例配置— 2

现在选择审查+创建。最重要的是,您应该看到 Data Science 虚拟机每小时的成本。对于上面的示例配置(这与 2020 年的一些最新笔记本电脑一样好),设置成本仅为 1/小时。难以置信!根据你的需求,你甚至可以使用 GPU 进行深度学习建模,大约 50 英镑/小时。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

所选样本配置的每小时价格(印度卢比)

如果你对所有提到的规范感到满意,只需点击创建按钮,我们就可以开始了。否则,返回并重新配置规范。

该页面应该导航到一个新页面,这需要几秒钟或几分钟的时间(取决于设置虚拟机的配置)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

部署正在进行中

部署后,您可以看到以下屏幕。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

部署成功!

在这里,我建议您设置自动关闭并在本地计算机上下载部署细节。我还建议保留管理员用户名和密码的备份,以备将来参考。

注意:运行一个虚拟机是要花钱的。不使用时,选择停止按钮停止。如果虚拟机运行不正常,您可以重新部署它。

如果您点击转到资源按钮,您可以看到相关选项。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

示例虚拟机,您可以在其中重启、停止和删除 Data Science 虚拟机

干得好!现在,您已经使用想要使用的配置设置了 Data Science 虚拟机。

4.将 DSVM 连接到 Azure 笔记本电脑

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

将数据科学虚拟机与 Jupyter 笔记本( Pixabay )连接

让我们总结一下你迄今为止做得非常出色的事情:

  • 我们有运行在云上的 Jupyter 笔记本: Azure 笔记本设置好了,我们已经可以在自由层上运行 Jupyter 笔记本了

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

您有一个项目准备好在 Azure 笔记本电脑上计算

  • 我们已经建立了一个数据科学虚拟机,具有我们需要的存储和计算能力。

如果你前往 Azure 门户网站,你应该会看到最近资源下的资源,如图所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

现有资源下的 SampleVirtualMachine

到目前为止你做得很好!我们现在将继续进行我们已经进行了这么久的步骤:将我们的 Azure 笔记本与我们的 Azure Compute 连接起来。

转到您的 Azure 笔记本,选择在 Direct Compute 上运行的选项,如下所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

选择直接计算

您现在应该会看到一个选项来验证您的 Azure 虚拟机凭据,如下所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

只需输入您之前为虚拟机提供的管理员用户名和密码

对于 IP,您可以返回到虚拟机的设置页面,并复制公共 IP,如下所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

使用给定的公共 IP 来运行 Jupyter 笔记本

我看到的一个常见错误如下所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

DSVM 出错

只需选择突出显示的链接并将其粘贴到您的浏览器中。我相信这个错误的发生是因为我们没有一个安全的连接,但是没关系,因为我们是唯一使用它的人,所以个人部署不需要太多的安全性。但是,如果您正尝试为企业部署这一功能,我建议您联系您的 IT 部门,以确保遵循信息安全实践。

粘贴链接应该会将您带到一个要求您提供凭据的页面。您可以输入 DSVM 凭据并登录。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

部署成功!现在,只需点击运行

现在,刷新 Azure 笔记本页面,再次尝试直接计算选项,点击运行

您现在已经成功地在云端部署了您的 Jupyter 笔记本电脑。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

干得好。( Pixabay )

结论:

[## Anish Mahapatra -数据科学家-穆适马公司| LinkedIn

我正在努力理解数学、商业和技术如何帮助我们在未来做出更好的决策…

www.linkedin.com](https://www.linkedin.com/in/anishmahapatra/)

正如所承诺的,您已经在 15 分钟内成功地在云上运行了 Jupyter Notebook。我花了很多时间研究,并且非常喜欢写这篇文章。如果这对你有帮助,给我点爱!😄我也写关于千禧一代的生活方式咨询聊天机器人金融!如果您对此有任何问题或建议,请随时通过 LinkedIn联系我或关注我这里,我很想听听您的想法!

在 WSL 上运行 Jupyter 笔记本,同时在 Windows 上使用浏览器

原文:https://towardsdatascience.com/running-jupyter-notebook-on-wsl-while-using-firefox-on-windows-5a47ebfae4c1?source=collection_archive---------6-----------------------

体验 Ubuntu 或 Mac 的指南

如果您像我一样是一名数据科学家,最近刚刚迁移(回到)Windows,您可能会发现启动并运行系统很困难,尤其是当您想要使用 bash terminal 的强大功能时。你会立刻意识到你为苹果电脑支付的价格可能真的是值得的。你得到了两个世界的好处:基于 GUI 的应用程序可以正常工作,而基于 CLI 的应用程序可以加快速度。

虽然使用 Windows subsystem for Linux (WSL)可以帮助我让好的 ol’ bash 终端工作,但它不能在运行 Jupyter Notebook 时提供相同的现成体验。我也错过了好的终端,比如 Mac 上的 iTerm2 或者 Ubuntu 上的 Terminator 。你可以找到人们使用类似 Hyper 的变通方法,甚至可以回到旧的 tmux ,但我发现前者很迟缓,而后者没有 iTerm2 或终结者那么容易。在本帖中,我将向您介绍 Windows 终端

它很简单,相当快,类似 iTerm2。很好。

设置 Windows 终端

您可以从 Microsoft Store 安装它。您可能需要首先更新您的 Windows。别担心,等待是值得的。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

最低要求是 Windows 10 build 1903 | Image by author

完成了吗?

免责声明:我假设你已经用你最喜欢的发行版安装了 WSL。如果你还没有,那就按照 这个指南 。这种情况下,我用的是 Ubuntu 18.04。

接下来,打开您的 Windows 终端。默认情况下,您应该会看到 Windows PowerShell。使用 Windows 终端,你可以打开多个标签,从 Windows PowerShell、命令提示符、你选择的 WSL 甚至 Azure Cloud Shell 切换。如果你想让 bash 成为你的默认设置,点击 Ctrl+,或者点击向下的 v 形图标,然后点击“设置”。

找到“defaultProfile”并将值更改为“profiles”>“defaults”>“list”中的“guid”。

{
    "defaultProfile": **"{c6eaf9f4-32a7-5fdc-b5cf-066e8a4b1e40}"**,
    ...
    "profiles":
    {
        "defaults":
        {
            ...
        },
        "list":
        [
            {
                "guid": "**{c6eaf9f4-32a7-5fdc-b5cf-066e8a4b1e40}**",
                "hidden": false,
                "name": "Ubuntu-18.04",
                "source": "Windows.Terminal.Wsl",
                **"startingDirectory": "//wsl$/Ubuntu-18.04/home/<username>"**
            },
            ...
        ]
    },
    ...
}

注意,我还向 WSL 添加了**“starting directory”**。

使用默认设置,您可以通过按 Alt+Shift+D 打开一个新的窗格。现在你不再需要 Hyper 了,是吗?

配置 Jupyter 笔记本

我假设你已经在这里安装了 Python 和 Jupyter Notebook。如果没有,我推荐使用 Anaconda 来安装它们。提醒您,这将安装在您的 WSL 中。

接下来,使用以下命令生成笔记本配置:

jupyter notebook --generate-config

然后您将在中看到一个 Python 文件。朱庇特文件夹。使用您最喜欢的文本编辑器编辑它们。

code ~/.jupyter/jupyter_notebook_config.py

通过更改该行来禁止通过 redicect 文件启动浏览器(默认值为 True ):

c.NotebookApp.use_redirect_file = False

WSL 的好处是可以直接从 bash 打开 Windows 程序。因此,要让您的 Jupyter 笔记本在浏览器中打开一个选项卡,您可以在 bash 中将它们添加为$BROWSER。我这里用的是火狐,但是你可以换成你自己喜欢的浏览器。也就是说,可以编辑~/。bashrc,并添加以下行:

export BROWSER='/mnt/c/Program Files/Mozilla Firefox/firefox.exe'

瞧吧!

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

从 WSL 运行 Jupyter 将会在 Windows | Image by author 上调用您的浏览器

/mnt/c 意味着您可以直接从 bash 访问 Windows 上的文件!很整洁不是吗?因此,在这种情况下,我们在 Windows 上使用 Firefox。没有必要在 WSL 上安装 Firefox 甚至 X11。现在,你可以像在 Ubuntu 或 Mac 上一样启动你的笔记本了。

希望你觉得这有用!

Java 中的 Tensorflow SavedModel

原文:https://towardsdatascience.com/running-savedmodel-in-java-1351e7bdf0a4?source=collection_archive---------23-----------------------

如何将您的 TF SavedModel 与当前版本的 Tensorflow Java 一起使用

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

来源:https://unsplash.com/@ricaros

介绍

机器学习最流行的编程语言是 Python。大多数数据科学家和 ML 工程师用 Python 来构建他们管道。尽管 Python 是一个广泛使用的解决方案,但它可能不是所有栈或用例的最佳选择。在本文中,我将向您展示如何在当前版本的 Tensorflow Java 中使用您的 SavedModel,这可能会有用。

注:如你所见tensor flow Java还在建设中。因此,可能会发生一些意想不到的行为。请记住这一点。

设置 maven 项目

首先创建一个 Maven 项目并打开 pom.xml。我用 Java 11,因为它是 LTS。

<properties>
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  <maven.compiler.source>1.11</maven.compiler.source>
  <maven.compiler.target>1.11</maven.compiler.target>
</properties>

接下来,将 Tensorflow Java 工件添加到项目中。在撰写本文时,它们还没有托管在 maven 上。因此,您必须手动添加 Sonatype 的 OSS 库。你总能在他们的 Github 资源库 Tensorflow/Java 中找到最新的指令。

<repositories>
    <repository>
        <id>tensorflow-snapshots</id>               <url>https://oss.sonatype.org/content/repositories/snapshots/</url>
        <snapshots>
            <enabled>true</enabled>
        </snapshots>
    </repository>
</repositories>

<dependencies>
    <dependency>
       <groupId>org.tensorflow</groupId>
       <artifactId>tensorflow-core-platform</artifactId>
       <version>0.2.0-SNAPSHOT</version>
    </dependency>
</dependencies>

准备 SavedModel

出于演示的目的,我将使用 GPT-2 模型。但是任何其他 SavedModel 都可以。下载/创建 SavedModel 文件后,我将整个文件夹移动到 Java 项目的 resource 文件夹中。然后转到终端,输入以下行:

saved_model_cli show --dir PATH/TO/SAVEDMODEL/FOLDER --all
# my output:
MetaGraphDef with tag-set: 'serve' contains the following SignatureDefs:signature_def['predict']:
  The given SavedModel SignatureDef contains the following input(s):
  inputs['context'] tensor_info:
    dtype: DT_INT32
    shape: (1, -1)
    name: Placeholder:0
  The given SavedModel SignatureDef contains the following          output(s):
  outputs['sample'] tensor_info:
    dtype: DT_INT32
    shape: (1, -1)
    name: sample_sequence/while/Exit_3:0
Method name is: tensorflow/serving/predict

这为您提供了关于模型的所有相关信息,例如标签集、输入和输出节点等。执行推理时需要这些信息。

运行 SavedModel

创建一个新的 Java 类,创建一个 main 方法,并添加以下代码行来加载带有特定标记集的 SavedModel:

SavedModelBundle model = SavedModelBundle.load(modelPath, "serve");

此时,我们需要为模型创建输入张量。所有可用转换的概述可在TensorCreation.java中找到。在我的例子中,GPT-2 取一个带有形状(1,-1)和 int 值的张量。所以我用一些随机手写的 int 值创建了一个形状为(1,9)的张量

IntNdArray input_matrix = NdArrays.ofInts(Shape.of(1, 9));
input_matrix.set(NdArrays.vectorOf(1, 2, 3, 5, 7, 21, 23, 43, 123), 0);
Tensor<TInt32> input_tensor = TInt32.tensorOf(input_matrix);

然后创建一个映射,它有一个字符串键和一个张量作为值。关键是输入张量的名称和您将用作输入的张量的值。这应该会让你想起以前的 Tensorflow 1。x 天,您必须创建一个 feed_dict,并在会话期间将其插入到图表中。在 Java 中,我们可以简单地创建一个散列表,并将我们创建的输入传感器插入其中:

Map<String, Tensor<?>> feed_dict = new HashMap<>();
feed_dict.put("context", input_tensor);

剩下要做的就是调用 signature_def 函数。

model.function("predict").call(feed_dict)

并且你完成了,所有的代码都可以在这个要点中找到。

在 Docker 上运行、保护和部署弹性堆栈🐳

原文:https://towardsdatascience.com/running-securing-and-deploying-elastic-stack-on-docker-f1a8ebf1dc5b?source=collection_archive---------4-----------------------

在 Docker 上为小规模生产部署和开发构建和配置弹性堆栈(ELK)

什么是弹性叠加?

Elastic Stack(又名 ELK )是您组织的集中式结构化日志记录的当前首选堆栈。它收集吸收存储您的服务日志(以及指标),同时使它们可搜索&可聚集&可观察。然后,基于这些数据构建警报和仪表板。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

活动中的弹性堆栈。

运行弹性堆栈

在本帖中,我们将使用 Docker&Docker-Compose编写配置安全部署弹性堆栈。我们将构建的内容可用于 docker 主机上的开发小规模生产部署

  • 为每个组件构建一个映像。
  • 参数化配置&避免硬编码凭证。
  • 将 Elasticsearch 设置为可扩展的生产单节点集群。
  • 设置安全性和加密。
  • 在容器外保存秘密、证书和数据。
  • 在 Docker-Compose 中将所有内容组合在一起。

要跳过逐步教程你可以查看我的 Elastdocker 模板库,用一个命令在 Docker 上旋转弹性堆栈。

[## sherifabdlnaby/elastdocker

用像 Curator,Rubban,ElastAlert 这样的工具进行预警。弹性堆栈(又名 ELK) Docker 组成,预配置…

github.com](https://github.com/sherifabdlnaby/elastdocker)

git clone [https://github.com/sherifabdlnaby/elastdocker.git](https://github.com/sherifabdlnaby/elastdocker.git)
cd elastdocker
make setup & make elk

Elastdocker template 具有更完善的配置和更好的安全性,以及经常与 Elastic Stack 一起使用的监控和工具。为了简单起见,本教程中省略了这些额外的内容。

步伐

  1. 为我们的堆栈创建 Docker 映像(使用我们的配置)。
  2. 创建一个设置容器来生成 Elasticsearch 密钥库和证书。
  3. 使用 Docker-Compose 将堆栈放在一起。
  4. 开始运送日志!🎉

1.为我们的堆栈创建 Docker 图像。

让我们从制作根目录elk开始,创建五个独立的文件夹;elasticsearchlogstashkibanasetupsecrets目录。

另外,在根目录下创建一个.env文件来保存我们的参数。

弹性搜索

./elasticsearch目录中创建Dockerfile并添加以下内容:

然后我们还将elasticsearch.yml添加到./elastichsearch/config/ 目录中,这个文件将在容器启动时加载以配置 elasticsearch。

  • 注意${ELASTIC_XXX}配置将从容器的环境变量中设置,我们稍后将在运行时传递这些变量。

Logstash./logstash目录下创建Dockerfile并添加以下内容:

logstash.yml添加到logstash/config目录中。

logstash.conf添加到logstash/pipeline目录中。

从 Beats = to => Elasticsearch 转发数据的简单管道。

基巴纳

kibana目录中创建Dockerfile并添加以下内容:

kibana.yml添加到kibana\config目录:

2.创建一个设置容器来生成 Elasticsearch 密钥库和认证

我们需要创建一个容器来生成 Elasticsearch 密钥库,其中包含 Elasticsearch 的用户(和超级用户)密码,以及其他凭证(例如,如果您使用 S3 插件,就像 AWS 密钥一样)。
该密钥库由 Elasticsearch docker 映像中的*elasticsearch-keystore* 创建。

2.1 增加setup-keystore.sh/setup

这个脚本创建一个 Keystore 并将其写入/secrets/elasticsearch.keystore,然后将$ELASTIC_PASSWORD添加为默认超级用户elastic,稍后我们将使用docker-compose$ELASTIC_PASSWORD传递给安装容器。

2.2 增加setup-certs.sh/setup

该脚本创建一个自签名的单个 PKCS#12 密钥库,其中包括节点证书、节点密钥和 CA 证书。

2.3 在根目录下创建docker-compose.setup.yml将启动安装容器。

这个docker-compose创建了一个临时容器,它使用我们之前编写的脚本生成密钥库和证书。

使用以下命令运行:

docker-compose -f docker-compose.setup.yml run --rm keystore
docker-compose -f docker-compose.setup.yml run --rm certs

这将创建容器,并在完成时删除它们以节省空间。将其输出保存到/secrets目录。

3.使用 Docker-Compose 将堆栈放在一起。

最后,创建主docker-compose.yml文件,将所有内容放在一起。

Docker-Compose 文件中有什么?

  1. 我们声明 Elasticsearch 的容量将持久存储数据。
  2. 我们声明传递给 Elasticsearch 的秘密密钥库,它包含凭证(目前只有超级用户密码,但是以后可以通过扩展setup-keystore.sh脚本来保存许多其他凭证)
  3. 我们声明 ELK 的 3 个服务(Elasticsearch、Logstash 和 Kibana)
    传递构建图像和运行容器所需的环境变量,召回所有包含${ELASTIC_XXX}配置的配置文件,您必须在这里看到它们被传递。注意,我们也没有在Docker-Compose文件中硬编码它们的值,而是在.env文件中声明所有内容,这样它就保存了所有参数。
  4. 我们还声明了 Elasticsearch 的推荐设置,如ulimitES_JAVA_OPTS,并为堆栈组件设置了堆大小。

将所有参数添加到 **.env** 文件

启动堆栈

运行以下命令:

设置(仅运行一次)

docker-compose -f docker-compose.setup.yml run --rm keystore
docker-compose -f docker-compose.setup.yml run --rm certs

启动弹性堆栈

docker-compose up -d

前往localhost:5601
用户名 : elastic
密码 : changeme

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

登录弹性基巴纳

4.开始运送日志!🎉

您现在可以开始将日志和指标发送到 Elastic Stack。
1。使用 Filebeat 从服务中发送日志
2。使用 Metricbeat 从服务中发送指标。
直接到 Elasticsearch 或者用 Logstash 摄取。并在 Kibana 的发现窗口中浏览它们。

有许多指南可以帮助你将原木运送到弹性堆上。

从这里去哪里?

我们还可以进一步增强这种组合,我们可以启用自我监控,或添加 Prometheus Exporters 以使用 Grafana 监控堆栈,除了我们启用的内部节点传输加密之外,还可以为 HTTP 层启用 SSL,为映像添加健康检查,使用 ElastAlert 或 Curator 等工具来利用堆栈,向集群添加更多节点。

使用 Elastdocker 代替。

Elastdocker 是一个模板库,拥有我们刚刚制作的更复杂版本的弹性堆栈设置,包括上面提到的所有要点。

[## sherifabdlnaby/elastdocker

用像 Curator,Rubban,ElastAlert 这样的工具进行预警。弹性堆栈(又名 ELK) Docker 组成,预配置…

github.com](https://github.com/sherifabdlnaby/elastdocker)

缩放弹性堆栈

这种设置可以处理少量生产工作负载,但最终需要扩展。

  • 您需要将栈组件解耦,以便独立地部署到不同的 docker 主机上。
  • 您可能需要添加额外的 Elasticsearch 节点。
  • 您可能需要横向扩展 Logstash。
  • 将当前设置转换为 Kubernetes 设置,改为使用窗格图表。(前提是你的组织里有 k8。)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

分解成吊舱的弹性堆栈的可能 K8S 部署

您可以看看托管弹性堆栈产品。

本帖使用的代码:https://github.com/sherifabdlnaby/medium-elastic-stack

使用机器学习和 Strava 智能运行

原文:https://towardsdatascience.com/running-smart-with-machine-learning-and-strava-9ba186decde0?source=collection_archive---------19-----------------------

基于你和其他运动员的体育观察数据,你的最大潜在训练收获的有序列表。

聪明跑步?

作为一名跑步者,我总是希望提高我的个人最好成绩,我经常想,如何优化我跑步的时间,以获得最好的成绩,同时尽可能地懒惰。有时候我一周跑七次,但是几个月都没有提高速度。其他时候,在几周的徒步旅行、饮酒和每周只跑一两次之后,我跑得非常好。这违背了经营论坛和培训计划的智慧。幸运的是,我已经在 Strava 上记录了五年多的训练,许多人也是如此。这些数据中一定有某种模式。

所以我建立了一个服务,在其他运动员允许的情况下,从运动员的社交网络 Strava 获取数据。机器学习算法从所有运动员的数据中确定哪些因素对改善他们的跑步最重要。通过这种算法,有可能给运动员一个有序的训练改进列表,他们可以进行这些改进以获得更快的速度。

这篇文章很长。

这个问题不容易解决,解决方案也不容易解释,简而言之,因为跑步教练和机器学习工程师之间的重叠可能相当小。出于这个原因,我在顶部放了一个总结部分,并让每个技术部分对于那些研究细节的人来说都是相当独立的。我解释了整个过程,从开始到结束,包括生产“产品”

摘要

从多个跑步者那里获得数据后,我建立了两个模型:

  • 一个模型,可以预测,高达 89%的准确性®,什么时候运动员将运行在一场比赛(5k 至 42k)完全基于他们的训练计划。换句话说,根本不用他们的步速数据来确定他们的步速。这个模型并不总是对普通跑步者有用,因为在你的速度和你跑的频率或距离之间有很多关联。当与模型比较时,普通运动员可以看到他们相对于顶级运动员是如何训练的,但看不到如何到达那里。
  • 一个模型,它可以预测运动员根据他们的训练计划将会提高多少。这个模型对跑步者更有用,因为它可以告诉普通跑步者怎样做才能跑得更好。然而,我觉得我需要更多的训练数据来达到可接受的 70%。

整个系统现在可以在的一个简单网站中获得【更新:我已经关闭了该网站,因为该系统很快就用完了我的主办预算】,运动员可以在那里注册并深入了解他们的训练计划。

第一个模型的系统输出的原型可视化如下所示。这里,蓝色标记显示了运动员相对于前 10%和后 10%运动员的训练情况。该列表是按照运动员训练中最重要的因素排序的,是每个因素的重要性和运动员在其中的缺点的产物。

在这里,很明显,运动员需要每周跑更多次,跑更多的距离。在此过程中,他们应该花更多的时间在心率 2 区。然而,有一些事情他们做得很好,甚至相对于最好的运动员。例如,在他们跑步前的三个月,他们减少了花在非跑步活动上的时间,因为他们更专注于跑步,并且他们增加了长跑的比例。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

深入!

从现在开始,事情变得更加技术性:

为问题设定一些界限

  • Strava API 不会返回他们拥有的所有数据(细粒度的心率数据)或见解。返回的数据是每公里的平均值,这意味着人们无法看到在几分钟内均匀分布的因素。例如,由于非常短的短跑训练计划导致的心率或节奏的峰值不能被考虑。
  • 这种分析着眼于“宏观”的训练计划——你在长期训练中的表现。例如,你如何安排你的周数,增加距离,平均跑步的轻松程度。幸运的是,这是跑步者面临的最热门的问题之一。选择一个有效的培训计划很难。
  • 有许多方法可以解决这个问题,导致许多模型和特征工程决策更多的是艺术而不是科学。例如,我选择只关注个人最佳成绩(PB)前三个月训练的因素。这个决定背后有一些科学依据,但选择两个月或六个月可能会更有效。其他类似的决定包括为相对于其他跑步的“长”跑或“快”跑设置阈值。
  • 很多我想拥有的因素都不见了。这些模型永远不会完美,因为 Garmin 和 Strava 对他们的体重、饮食、压力和睡眠统计数据掌握得非常紧密。
  • 类似地,这些模型平均来说表现不错,但是每个运动员的效果可能不同。一些运动员比其他人给模型添加更多的噪音。随着更多的数据,这种噪声将在模型中考虑。但当涉及到个性化的可视化时,建议会有所不同:例如,你可能是一个非常健康的人,训练非常完美,但需要减掉 10 公斤的上身肌肉才能改善。
  • 同样,这个模型是为普通跑步者设计的。有了更大的数据集,我相信我可以在相似跑步者的子集上运行该模型,以便为跑步者提供更相关的建议,例如显示排名后 10%的跑步者,喜欢他们的人是如何改进的。

掌握技术——首先是一些跑步科学

我在这里解决的问题是长跑:5 公里到 42 公里之间。虽然这些仍然是非常不同的比赛,但动力是相似的,有一个公式和常数变量控制着运动员在这个范围内任何距离的速度之间的映射。假设我经常跑 25 公里到 30 公里的快速(速度或门槛)跑和长距离跑,从我的 5 公里时间推断出我的马拉松时间是相当简单的。这是通过传奇跑步教练杰克·丹尼尔开发的常数和公式“vdot”来完成的。在我的整个模型中,假设 vdot 是您整体跑步健康状况的准确代表,我们处理的是 5-42 公里距离范围内的健康状况。

然而,vdot 并不是健身的完美同义词,与其说它是速度的度量。对于健康的整体测量,人们必须考虑 OBLA、V02max、肌肉效率和肌肉功率输出与足部划水持续时间的关系。我们无法用腕表数据测量那些。但是我们可以衡量是什么帮助人们提高最终目标——速度。

大多数跑步者使用运动员社交网络 Strava 来上传他们的数据,这也是我获取数据的地方。Strava 确实在他们自己的模型中估计你的适合度(vdot 或他们自己的度量),但并不通过他们的 API 提供这一点。出于这个原因,我建立的模型只能在你跑个人最好成绩(PB)时推断你的健康状况,Strava 确实将它作为“估计的最佳努力”提供有可用的选项,例如根据心率和速度之间的关系来估计健身,但这将在最小可行产品(MVP)的模型中添加太多噪声。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我最大的努力

为了达到这些最佳效果,人们普遍认为,你必须在努力训练和恢复之间保持微妙的平衡,并通过大量轻松跑步来保持你的机器运转良好。前者由 trainingpeaks 所谓的脉冲反应模型定义——你让你的身体处于压力之下,它通过变得更强来回应这种压力。后者只是因为科学表明,我们的身体在有氧心率区获得最有效的表现,这对应于“区域 2”中相对轻松的跑步速度。

这将我们带到心率区:教练和运动员根据每位运动员的最大心率定义了一组区域或范围,为我们提供了基于心率数据进行训练的目标区域,从而简化了我们的工作。这些区域从 Z1 到 Z5,从最小努力到最大努力。许多专业运动员完全按照心率范围进行训练,而不是按照速度,因为我们的心脏是我们身体对训练反应的最佳指标。下图给出了区域的指示。请注意,根据您的年龄来估计您的心率范围是一种相对糟糕的做法,因为这是根据遗传而不是健康状况来变化的。例如,克里斯·弗鲁姆的最大心率是 161 ,根据这个模型应该是 186。幸运的是,Strava 不做这种假设,而是随着时间的推移来确定这一点。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

心率区(维基共享)

我们可以在心率较高的区域花较少的时间训练,但在较高的区域花一点时间会受益匪浅。不同的教练有不同的看法,一般的建议是花 80%的时间在“轻松”区 Z1-Z3,20%的时间在“困难”区 Z4 和 Z5。然而,这也有所不同,取决于你是在训练季节的早期,准备为一场比赛“达到顶峰”,还是为一场比赛“逐渐减少”你的努力。

为了管理你在这些区域的时间,并进一步简化训练,教练建议几种类型的跑步:

  • 轻松跑步
  • 长距离跑
  • 间歇训练
  • 节奏跑步
  • 用大步跑或法特莱克跑轻松跑,快速冲刺而没有太多相应的心率增加
  • 以上的任何组合。

如果这还不够,还有关于每周增加多少里程的科学,一个宏观类型的脉冲响应模型,在这里你的身体慢慢适应更多的努力,因此变得更健康。过度增加这一点的代价当然是受伤或倦怠。

简化的跑步科学

综上所述,对于每一个运动员来说,都存在着一个最佳的但又难以找到的训练平衡

  • 每项活动的轻松和努力
  • 跑步和休息,取决于你之前的活动
  • 轻松跑、长跑、间歇训练、速度跑
  • 跑步和交叉训练
  • 每周增加你的努力

如果没有对自己或教练的深入了解,即使是经验丰富的跑步者,也很难找到这种平衡。有可用的工具,如 TrainingPeaks 的性能管理器和 Strava 的高级产品。他们很棒,尤其是他们告诉你,你训练太辛苦了,需要休息。然而,我发现他们都专注于试图估计你当前的训练负荷,因此依赖于大量不准确的每日心率数据。我需要一些东西来建议我在几个月的时间里可以做哪些不同的事情,而不是每天都做。

所以,作为一名跑步者,我建立了这个分析作为一个辅助工具:告诉我在更长的几个月里,相对于其他运动员,我可以做些什么不同。

系统的高级概述

像这样的系统有几个部分,需要收集数据,训练模型,运行模型,并为前端用户提供见解。由于 API 访问或处理所需的时间,人们可以将它们视为如下交互的系统,绿色线条表示同步流,橙色线条表示异步流:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

获取数据:Strava API

Strava 对他们的 API 的使用有很大的限制。没有运动员的允许,它不能被查询,然后被限制速度,每天只允许大约 10 个跑步者的数据被提取。这降低了数据采集的速度。

除此之外,Strava API 为每位运动员提供:

  • 运动员的心率区——他们体验 Z1-Z5 的心率
  • 一个活动列表,根据活动类型进行标记,包括跑步、自行车或运动员选择上传的任何内容
  • 每次跑步的距离、心率、配速和“圈数”——通常以公里为单位
  • 每圈的平均心率、配速和节奏
  • 对于每一次运行,无论它是否是“最大努力”——换句话说,一个 PB

我的 20 个朋友非常友好地通过 Strava 向我提供了他们所有的数据。鉴于他们每个人的数据平均有 10 个 Pb,这为我的模型提供了近 200 个输入,足以建立一个精确的模型来估计 vdot,但不足以建立一个模型来估计 vdot 的改善,这是我稍后将解释的问题。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. 遍历所有运动员活动,确定哪些是 PBs。对于每个 PB,如果它在前一个 PB 之后超过一个月,并且超过前一个记录 0.5 vdot,则成为一个数据点。这是为了确保每个数据点都是足够大的独立 PB。
  2. 根据运动员的所有活动,构建一个回归变量,将该运动员在一个月的时间间隔内的步速映射到心率。这是因为许多运动员活动是在没有心率监测器的情况下进行的(一般来说,心率监测器只是在过去两三年才被广泛使用)。然而,用这个回归量仍然可以相当准确地估计心率,R 为 0.9。这在训练机器学习模型时非常有用:它将整个模型的 R 提高了 0.1。
  3. 对于每个 PB,使用杰克·丹尼尔公式确定该 PB 的 vdot。这是第一个机器学习模型的“y”:模型被训练的特征。对于第二个模型,使用 vdot 的变化或 delta-y。同样对于每个 PB,提取 PB 之前 3 个月的所有活动。根据最终模型的 R 选择 3 个月。
  4. 对于每个活动,提取有用的特征,例如活动的类型、长度、总体努力等。虽然我不会对模型特征进行太深入的讨论,但诸如心率的标准差之类的东西可用于确定活动是否是间歇训练,相对努力和活动的长度可用于确定它是一种节奏还是一次长跑。类似的逻辑也适用于加快步伐、爬山重复等。
  5. 特别是在几年前的活动的情况下,步速数据用于推断活动的预期心率,以使得能够从心率中提取一些以上和以下特征。
  6. 活动按周分组,这种方法我还是有点不舒服,因为有些运动员可能以 10 天或 14 天为周期进行训练。对于每周,提取更多的特征,例如每周里程、跑步和其他活动之间的分割以及跑步类型之间的比率。
  7. 将周分组为训练块也是如此。在这里,整个三个月被考虑,并且特征更符合三个月中里程、努力和整体行为变化的相对增加。最后,比赛前的锥度也被分析为一个因素。

所有上述特征都存储在每个运动员的数据库中。都是用来训练最终的机器学习模型。需要注意的是,节奏不是一个特征,因为用它来预测是很愚蠢的…步伐。

机器学习模型

对于机器学习专家来说,这一部分将是相当乏味的。根据所选择的特性,性能最好的模型是决策树,紧随其后的是普通的 XGBoost。调整这些模型显示,相对于通过一些特征工程来改善数据,收益甚微,例如在没有心率数据的情况下,从心率外推心率。

直觉上,并且基于模型性能,问题是近似线性的。

正如本文前面提到的,该模型分为两部分:

  • 一个模型是在 vdot 上训练的:确定解释运动员 vdot 的特征,本质上是马拉松配速。这在最好的情况下有 0.89 的 R,但是根据运动员的不同可以低至 0.6。
  • 另一个模型,真正的金罐,在运动员最后一次 PB 之后,在 vdot 的变化上被训练。这更有价值,因为它不容易受到相关性和混杂因素的影响。然而,在这里,最好的情况 R 只有 0.5。

每个的特征(基于我的 n=200 的小数据集)如下。随着数据库的增长,我将对此进行更新:

解释 vdot(健身)的功能

在这里,我们有一个相当健康的 SHAP 情节。在一个简单的系统中,该图的健康状况通常由红点和蓝点之间的清晰分隔来表示。

  • 所有功能都是针对 PB 之前的 3 个月培训期。
  • 普通特征(常数)以 f_ 为前缀。例如,f_avg_weekly_run_distance 是运动员跑步的平均距离(km)。
  • 相对特征(相对于这个运动员的其他数据)以 r_ 为前缀。例如,“r_proportion_yoga”是运动员在这三个月相对于之前训练的剩余时间做了多少瑜伽。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

请记住,这些特征被认为对非线性模型很重要:随机森林回归算法,所以我们不能把它作为跑步者的重要特征清单。我会对“r_proportion_yoga”这样的功能持保留态度。例如,该模型可能已经确定,如果运动员不做很多简单的跑步,那么他们花在做瑜伽上的时间就格外重要。但是由于这个模型的 R 相当好,并且考虑到我们有一个几乎的线性问题,以这种方式看特征是安全的,或者运行一个线性 XGBoost 特征分析。

关于跑步者在 PBs 前的表现,我们可以得出几个相当可靠的结论:

  • 他们比跑得慢的人跑得多,总次数也多
  • 他们在心率区 1、2、4 和 5 的时间较长,在心率区 3 的时间较短。换句话说,他们要么跑得很轻松,要么跑得很艰难。
  • 他们和更多的运动员一起跑步(r _ mean _ athlete _ count)——这肯定是相关的,因为他们可能会在社交生活中关注自己的主要爱好
  • 他们跑得更高了
  • …等等。

解释相对 vdot(适应性改善)的特征

作为一个快速的回顾,我没有看健康的运动员做了什么,而是训练了一个模型,这个模型是关于运动员通常做了什么来最大程度地提高 vdot。这可能是一个拥有足够数据的异常强大的分析,因为它会将相关性从等式中剔除。我们会看到普通人是如何变得更好的。

在这里,我们的 SHAP 情节并不健康。有许多对模型有很大影响的无关特性。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

该模型的最佳 R 为 0.5。因此,就目前的数据集规模而言,即使对于最容易解释的运动员表现,它也只能解释 50%的提高速度的因素。

同样重要的是,该模型比预测纯 vdot 的模型更不线性。一些运动员可能因为是这项运动的新手而跑 PBs,因此该模型可能会决定“那些平均每周跑不到 30 公里的人需要比平时跑得更多才能达到他们的 PB,但那些每周跑超过 120 公里的人需要跑得相对更少但跑得更快。”然而,让我们来看一看有意选择的功能是什么:

  • 快跑。很多。
  • 骑自行车的次数相对比以前少了(我住在荷兰,数据集中的许多运动员也住在荷兰,所以当我们终于在我们热爱的夏天停止骑自行车,专注于跑步时,我们变得更快了)
  • f _ slope _ time _ before _ taper:这是在运动员开始减量之前,锻炼时间“增加”的指示。它被认为是重要的,要么增加很多(对一些运动员来说),要么减少一点负荷(更多运动员)
  • f _ slope _ distances _ before _ taper:这里有一个相当明确的迹象表明运动员需要跑更多的距离。所以把这个和前一个变量考虑进去,可能是他们需要跑的更少,但是跑的更远。
  • f _ proportion _ distance _ activities:这支持了前面的两点:在跑 PB 之前跑更大比例的长距离显然是很重要的。请记住这种相关性的潜在危险——运动员经常在有意尝试马拉松 PB 之前进行几次长跑。

可视化

那么这对个人有什么用呢?有了上述模型,就有可能将某个运动员的训练与理想情况进行比较。如下所示,在此处有关于阅读图表的解释。

这些特性按照潜在增益排序:模型中特性的能力,乘以你在这方面的训练有多差。除了明显的“跑得更多”和“跑得更频繁”,该模型清楚地指导运动员“以 Z2 的心率跑得更多”、“跑更多的山”和“少游泳”。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

生产

这种小规模的生产相当简单,我使用了完全的谷歌云设置。结果是一切都非常快,除了 Strava API 查询的速度限制在每秒 1 次左右。

  • 由于特征工程和机器学习后端是在 Python 上运行的,所以我用了 Flask 前端
  • 该解决方案托管在谷歌云应用引擎上
  • 该数据库托管在谷歌云 SQL 上
  • 一旦新运动员注册,Cron jobs 就会运行特征工程和 ML 训练算法
  • 当加载可视化页面时,Flask 查询后端以生成 matplotlib 图像

下一步是什么?

有许多改进可以做,特别是对可视化及其解释。然而,首先,我想获得更多的数据,以便建立一个更好的模型来解释如何获得更快的速度。这种更具因果关系的模型对运动员个人来说会更加有用。

在 Docker 容器中运行 Spark NLP,用于命名实体识别和其他 NLP 特性

原文:https://towardsdatascience.com/running-spark-nlp-in-docker-container-for-named-entity-recognition-and-other-nlp-features-8acdb662da5b?source=collection_archive---------28-----------------------

在 Docker 环境下使用 Spark NLP 和 Jupyter notebook 进行自然语言处理

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者照片

如[1]所述,自然语言处理(NLP) 是语言学、计算机科学、信息工程、人工智能等许多研究领域共有的一个共同研究子领域。NLP 通常关注计算机和人类自然语言之间的交互,特别是如何使用计算机来处理和分析自然语言数据(例如,文本、语音等)。).NLP 中的一些主要挑战包括语音识别、自然语言理解(例如,文本理解)和自然语言生成。

机器学习在文本理解中的早期应用之一是电子邮件和垃圾消息检测[1]。随着深度学习的推进,许多新的高级语言理解方法已经问世,如深度学习方法 BERT (使用 MobileBERT 进行问答的例子见[2】)。

NLP 中另一个流行的方法是命名实体识别(NER) 。NER 的主要目的是提取命名实体(例如,个人姓名、组织名称、地点名称、产品名称等。)来自非结构化文本。有许多支持 NER 的开源 NLP 库/工具,如 NLTK 和 SpaCy [3]。最近,Spark NLP [4]得到了越来越多的关注,因为它提供了更完整的受支持的 NLP 特性列表[5][6]。

在我看来,Spark NLP [4]的开发是基于 Ubuntu Linux 和 OpenJDK 的。因此,由于 Colab 使用 Ubuntu 操作系统,所以在 Colab 中直接设置 Spark NLP 的环境(参见指令和代码示例)。然而,我注意到很难在 Mac 上为 Spark NLP 设置一个本地环境,原因如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

为了避免这个问题,本文演示了如何建立一个 Docker 环境[7]来运行 NER 的 Spark NLP 和 Docker 容器中的其他 NLP 特性。这样的 Docker 环境可以作为建立 Spark NLP 微服务平台的基础。

1.Docker 简介

如[7]中所述,Docker 是一种工具,它允许我们在沙箱(称为容器)中轻松部署应用程序(例如 Spark NLP),以在任何 Docker 支持的主机操作系统(即 Mac)上运行。

Docker 的基本概念是:

  • Dockerfile:
  • Docker 图像
  • 码头集装箱

1.1 文档文件

一个Docker file【7】是一个简单的文本文件,包含一个用于创建 Docker 映像的命令列表(类似于 Linux 命令)。这是一种自动化 Docker 图像创建过程的方法。

1.2 Docker 图像

一个 docker 映像【7】是一个只读模板,包含一组用于创建 docker 容器的指令,该容器可以在 Docker 平台上运行。它提供了一种打包应用程序和预配置服务器环境的便捷方式。

Docker 映像是从 Docker 文件构建的。

1.3 码头集装箱

容器是包含代码及其所有依赖项的标准软件包,因此应用程序可以从一个计算环境快速可靠地运行到另一个计算环境。一个 Docker 容器【7】是一个轻量级的、独立的、可执行的软件包,它包含了应用程序的一切,比如代码、运行时、系统工具、系统库和设置。

Docker 容器是从 Docker 映像构建的。

2.用 Jupyter 笔记本为 Spark NLP 设置 Docker 环境

设置 Docker 环境以使用 Jupyter notebook 运行 Spark NLP 的过程包括以下步骤:

  • 安装 Docker
  • 在 Docker Hub 注册
  • 创建 Dockerfile 文件
  • 建立码头形象
  • 启动码头集装箱
  • 推送 Docker 图像
  • 拉动 Docker 图像

2.1 安装 Docker

不同平台安装 Docker 的说明网上有: MacLinuxWindows

一旦 docker 安装完成,我们可以使用以下 Docker 命令和相应的输出来验证安装:

docker --version
Docker version 19.03.8, build afacb8b

2.2 在 Docker Hub 注册

类似于 Github 分享源代码文件, Docker Hub 就是分享 Docker 镜像。为了共享本地机器上的 docker 映像,需要将本地机器上的 docker 映像推送到 Docker Hub 服务器,以便其他人可以从 Docker Hub 获取 Docker 映像。

需要先去 Docker Hub 注册才能使用 Docker Hub 服务。

2.3 创建 Dockerfile

为了构建新的 Docker 映像,首先需要创建一个 Docker 文件。

为了简化运行 Spark NLP workshop 的过程, John Snow LABS 提供了一个Spark NLP workshop Docker file,用于在 Docker 容器中运行 workshop 示例。

为了在 Docker 容器中构建一个新的 Docker 映像来运行 Spark NLP 和 Jupyter notebook,我基于 Spark NLP workshop Dockerfile 文件创建了一个新的Docker 文件【8】,并做了以下修改:

  • 删除了教程和相关的笔记本和数据文件
  • 用 Spark NLP 2.5.1 替换 Spark NLP 2.4.5
  • 已调整 docker hub 用户名
  • 调整了 docker 容器中的主目录名
  • 添加了命令行选项,将主机上的当前工作目录映射到 Docker 容器中的主目录
  • 移除 Jupyter 笔记本配置

2.4 建立码头工人形象

使用新的 Docker 文件[8],可以按如下方式构建新的 Docker 映像:

docker build -t zhangyuefeng123/sparknlp:1.0 .

构建完成后,应该会显示以下 docker 命令和 Docker 图像标记:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

2.5 启动码头集装箱

一旦新的 Docker 映像准备就绪,就可以使用下面的命令启动一个新的 Docker 容器,使用 Jupyter notebook 运行 Spark NLP:

docker run -it --volume $PWD:/home/yuefeng -p 8888:8888 -p 4040:4040 zhangyuefeng123/sparknlp:1.0

如果一切顺利,应该会显示以下输出:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

2.6 推送 Docker 图像

为了与他人共享本地主机上的 Docker 映像(如zhangyue feng 123/spark NLP:1.0),需要将映像推送到 Docker Hub,如下所示:

docker push zhangyuefeng123/sparknlp:1.0

以下是 Docker Hub 推送 Docker 图片的结果:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

2.7 拉动 Docker 图像

如果具有预期功能的 Docker 映像(例如,zhangyue feng 123/spark NLP:1.0)已经存在于 Docker Hub 中,则它可以被拉到本地主机上以供重用,而不是从 Dockerfile 构建新的 Docker 映像,如下所示:

docker pull zhangyuefeng123/sparknlp:1.0

3.使用 Docker 容器中的 Jupyter 笔记本运行 Spark NLP

一旦一个新的 Docker 容器开始运行(详见第 2.5 节),我们可以复制生成的 URL,如下所示,并将其粘贴到 Web 浏览器中,以启动 Jupyter notebook Web 界面:

http://127.0.0.1:8888/?token=9785e71530db2288bc4edcc70a6133136a39c3f706779554

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

一旦 Jupyter 笔记本启动,我们就可以像往常一样使用它(详见下一节)。

4.将 Spark NLP 用于 NER 和其他 NLP 功能

为了验证 Docker container 中运行的 Jupyter notebook 具有相同的预期功能,我创建了一个新的 Jupyter notebookSpark-nlp-Docker-demo . ipynb,并使用它来执行[6]中的主要代码片段,以将 Spark NLP 应用于 ner 和其他 NLP 功能。

首先,下面的代码导入所需的 pyspark 和 spark NLP 库,然后启动一个 Spark 会话,在 Spark 上运行 Spark NLP:

from pyspark.sql import SparkSession
from pyspark.ml import Pipelineimport sparknlp
from sparknlp.annotator import *
from sparknlp.common import *
from sparknlp.base import *spark = sparknlp.start()

官方 CoNLL2003 数据集的训练和测试数据集被下载用于演示目的;

from urllib.request import urlretrieveurlretrieve('[https://github.com/JohnSnowLabs/spark-nlp/raw/master/src/test/resources/conll2003/eng.train'](https://github.com/JohnSnowLabs/spark-nlp/raw/master/src/test/resources/conll2003/eng.train'),
           'eng.train')urlretrieve('[https://github.com/JohnSnowLabs/spark-nlp/raw/master/src/test/resources/conll2003/eng.testa'](https://github.com/JohnSnowLabs/spark-nlp/raw/master/src/test/resources/conll2003/eng.testa'),
           'eng.testa')

下面的代码用于读取定型数据集并显示前 500 条记录。训练数据集遵循训练集中用于训练 NER 模型的注释的标准格式。

with open("eng.train") as f:
    c=f.read()print (c[:500])

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

训练数据集可以以更易读的格式加载:

from sparknlp.training import CoNLLtraining_data = CoNLL().readDataset(spark, './eng.train')
training_data.show(3)
training_data.count()

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

以下代码加载预训练的 BERT 嵌入模型,并使用它将测试数据集转换为 BERT 嵌入格式(即,将每个单词编码为 768 维向量)。

bert_annotator = BertEmbeddings.pretrained('bert_base_cased', 'en') \
 .setInputCols(["sentence",'token'])\
 .setOutputCol("bert")\
 .setCaseSensitive(False)\
 .setPoolingLayer(0)test_data = CoNLL().readDataset(spark, './eng.testa')test_data = bert_annotator.transform(test_data)
test_data.show(3)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

下面的代码显示了句子的标记、相应的 BERT 嵌入和相应的带标签的 NER 标签。

test_data.select("bert.result","bert.embeddings",'label.result').show()

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

以下代码首先将测试数据集中的 1,000 条记录保存到一个 Parquet 文件中,然后创建一个基于 Tensorflow 的字符级 CNN-DLSTM 模型 NerDLApproach ,使用经过训练的 BERT 嵌入模型 bert_annotator 和 NerDLApproach 模型形成一个管道,最后使用训练数据集中的 1,000 条记录和 Parquet 文件中保存的 1,000 条测试记录来训练管道。

test_data.limit(1000).write.parquet("test_withEmbeds.parquet")nerTagger = NerDLApproach()\
  .setInputCols(["sentence", "token", "bert"])\
  .setLabelColumn("label")\
  .setOutputCol("ner")\
  .setMaxEpochs(1)\
  .setLr(0.001)\
  .setPo(0.005)\
  .setBatchSize(8)\
  .setRandomSeed(0)\
  .setVerbose(1)\
  .setValidationSplit(0.2)\
  .setEvaluationLogExtended(True) \
  .setEnableOutputLogs(True)\
  .setIncludeConfidence(True)\
  .setTestDataset("test_withEmbeds.parquet")pipeline = Pipeline(
    stages = [
    bert_annotator,
    nerTagger
  ])ner_model = pipeline.fit(training_data.limit(1000))

然后,经过训练的管道可用于预测测试数据集的 NER 标签(参见下面的前 20 行结果):

predictions = ner_model.transform(test_data)
predictions.select('token.result','label.result','ner.result').show(truncate=40)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

标记为 NER 标签的前 20 行标记和相应的预测 NER 标签可以以更可读的格式显示:

import pyspark.sql.functions as Fpredictions.select(F.explode(F.arrays_zip('token.result','label.result','ner.result')).alias("cols")) \
.select(F.expr("cols['0']").alias("token"),
        F.expr("cols['1']").alias("ground_truth"),
        F.expr("cols['2']").alias("prediction")).show(truncate=False)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

以下代码显示了如何使用预训练的管道为给定的句子生成 NER 标记。

from sparknlp.pretrained import PretrainedPipelinepretrained_pipeline = PretrainedPipeline('recognize_entities_dl', lang='en')text = "The Mona Lisa is a 16th century oil painting created by Leonardo. It's held at the Louvre in Paris."result = pretrained_pipeline.annotate(text)list(zip(result['token'], result['ner']))

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

不同的预训练模型可用于形成新的管道:

import json
import os
from pyspark.ml import Pipeline
from sparknlp.base import *
from sparknlp.annotator import *
import sparknlpdef get_ann_pipeline ():

    document_assembler = DocumentAssembler() \
        .setInputCol("text")\
        .setOutputCol('document') sentence = SentenceDetector()\
        .setInputCols(['document'])\
        .setOutputCol('sentence')\
        .setCustomBounds(['\n']) tokenizer = Tokenizer() \
        .setInputCols(["sentence"]) \
        .setOutputCol("token") pos = PerceptronModel.pretrained() \
          .setInputCols(["sentence", "token"]) \
          .setOutputCol("pos")
    embeddings = WordEmbeddingsModel.pretrained()\
          .setInputCols(["sentence", "token"])\
          .setOutputCol("embeddings") ner_model = NerDLModel.pretrained() \
          .setInputCols(["sentence", "token", "embeddings"]) \
          .setOutputCol("ner") ner_converter = NerConverter()\
          .setInputCols(["sentence", "token", "ner"])\
          .setOutputCol("ner_chunk") ner_pipeline = Pipeline(
        stages = [
            document_assembler,
            sentence,
            tokenizer,
            pos,
            embeddings,
            ner_model,
            ner_converter
        ]
    ) empty_data = spark.createDataFrame([[""]]).toDF("text") ner_pipelineFit = ner_pipeline.fit(empty_data) ner_lp_pipeline = LightPipeline(ner_pipelineFit) return ner_lp_pipeline

以下代码使用上述函数创建一个新管道,然后使用它为给定的句子生成各种注释/标记:

conll_pipeline = get_ann_pipeline ()
parsed = conll_pipeline.annotate ("Peter Parker is a nice guy and lives in New York.")
parsed

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

5.摘要

Spark NLP [4]越来越受欢迎,因为它在一个系统中支持更多的 NLP 功能。Spark NLP 是在 Ubuntu Linux 系统上用 OpenJDK 开发的。根据我的经验,我注意到很难为 Spark NLP no Mac 设置一个本地环境,这是由于一个已知的异常“异常:Java 网关进程在发送其端口号之前退出”。

为了避免这个安装问题,在本文中,我演示了如何设置一个 Docker 环境来运行 Spark NLP 和 Jupyter notebook for NER 以及 Docker 容器中的其他 NLP 功能。

我使用[6]中的代码示例验证了 Mac 上 Spark NLP 的 Docker 环境。

Spark NLP 的 Docker 环境有潜力作为建立 Spark NLP 微服务平台的基础。

Docker 文件和 Docker 的 Jupyter 笔记本都可以在 Github [8]中获得。

参考

  1. Y.张,利用 word2vec-keras 进行自然语言处理的深度学习
  2. Y.张,面向移动设备的自然语言处理深度学习
  3. 南李,利用 NLTK 和 SpaCy 进行命名实体识别
  4. 火花 NLP
  5. 动词 (verb 的缩写)科贾曼,Spark NLP 简介:基础和基本组件
  6. 动词 (verb 的缩写)科贾曼,命名实体识别(NER)与 BERT 在 Spark NLP 中
  7. 页(page 的缩写)适合初学者的 docker
  8. Y.张,Github 中的 Dockerfile 和 Jupyter 笔记本

使用 Monk AI 的俄语字母分类

原文:https://towardsdatascience.com/russian-alphabets-classification-using-monk-ai-4df7d1ad8542?source=collection_archive---------54-----------------------

让计算机视觉应用变得简单有效

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

鸣谢:图片来自 FlickerPolyrus

目录:

  1. 介绍
  2. 关于数据集
  3. 设置 Monk 和先决条件
  4. 下载数据集
  5. 创建项目和实验
  6. 方法
  7. 选择最佳模型
  8. 结论

简介:

对我们来说,对手写信息进行分类是一件容易的事情,但对计算机来说,这是一项令人不安和令人畏惧的工作。手写字符分类通常是一项具有挑战性的任务,因为任何字符都有无数种书写方式。

尽管神经网络已经重新定义了这项任务,但它在开发这种分类器方面也发挥了巨大的作用,前提是为它提供了大量的数据来进行训练。

在这个特别的博客中,我们将探索俄语字母表,并以手写形式对它们进行分类。

关于数据集:

在数据集中,我们有 14190 个彩色图像分布在 3 个图像文件夹中,包括所有 33 个类别的俄语字母表。

第一个文件夹具有条纹背景(具有很少的水平线和/或垂直线),第二个文件夹具有白色背景,第三个文件夹具有图形类型背景(具有许多有序的水平线和垂直线)。

以下是数据集中的一些图像:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

数据集的样本图像

以下是用于预测目的的俄语字母及其相应的数字:

а=>1, б=>2, в=>3, г=>4, д=>5, е=>6, ё=>7, ж=>8, з=>9, и=>10, й=>11, к=>12, л=>13, м=>14, н=>15, о=>16, п=>17, р=>18, с=>19, т=>20, у=>21, ф=>22, х=>23, ц=>24, ч=>25, ш=>26, щ=>27, ъ=>28, ы=>29, ь=>30, э=>31, ю=>32, я=>33

所以不要担心,即使我们没有得到这篇文章,我们也会留下一些俄罗斯字母的印象!
完整的数据集由 Olga Belitskaya 准备并上传,可以在这里找到:

[## 手写信件的分类

俄罗斯字母的图像

www.kaggle.com](https://www.kaggle.com/olgabelitskaya/classification-of-handwritten-letters)

现在让我们开始设置僧侣和一些先决条件。

设置 Monk 和先决条件

我们从安装 monk 库开始。

1.)我们在这里使用过 colab,所以这里是同样的安装过程。

#Installation process for colab
pip install -U monk-colab

虽然我们可以在僧库探索其他的安装方式。

2.)将它添加到系统路径(每个终端或内核运行都需要)

import sys
sys.path.append("monk_v1/");

下载数据集

接下来,我们将从 Kaggle 直接获取数据集到 colab。为此,我们必须首先从 Kaggle 创建一个 API 令牌。

转到您的 Kaggle 个人资料>>我的帐户>>向下滚动到 API 部分>>单击创建新的 API 令牌(记住,如果以前使用过,请使所有其他令牌过期)

点击这个之后,kaggle.json 文件将被安装到您的系统中。
接下来,再次进入数据集页面,在新笔记本选项的右侧,我们可以找到一个图标来复制 API 命令。此命令将用于下载数据集。
现在,在 google colab 中上传 kaggle.json 文件,如下所示。

! pip install -q kaggle
from google.colab import files
files.upload()
#Upload the kaggle.json file here

此外,使用 API 命令下载数据集。

! mkdir ~/.kaggle
! cp kaggle.json ~/.kaggle/
! chmod 600 ~/.kaggle/kaggle.json#Download the full dataset zip file to Colab
! kaggle datasets download -d 'olgabelitskaya/classification-of-handwritten-letters'

现在,解压缩这个文件并创建一个新文件,将所有数据集存储在一个地方。

#Unzip the downloaded zip file and put it under a new files section! unzip -qq sample.zip
import zipfile
zip_ref = zipfile.ZipFile('classification-of-handwritten-letters.zip', 'r')
zip_ref.extractall('files')
zip_ref.close()

完成后,我们导入这些库和适当的后端。

import os
import sys
sys.path.append("monk_v1/");
sys.path.append("monk_v1/monk/");#Importing MXNet Gluon API backend
from monk.gluon_prototype import prototype

我在这里使用了 MXNet Gluon API 作为后端,只用一行代码我们就可以选择我们想要的后端,不用担心以后会遇到不同的语法。使用 monk 库可以做到这一点,只需使用一种语法,我们就可以跨不同的框架工作,如 PyTorch、MXNet、Keras、TensorFlow。

创建项目和实验

为了创建项目名和实验名,我们使用了 prototype 函数。我们现在可以在一个项目下创建多个实验,并在各个方面进行比较。

#Setup Project Name and Experiment Namegtf = prototype(verbose=1);
gtf.Prototype("Pilot", "Densenet_121");

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

新项目形成!

方法

模型训练的方法:

我们没有连接三个图像文件夹,这将允许我们一起训练整个数据集。
我们构建鲁棒模型的方法是首先通过使用受背景影响最小的图像(即第二图像文件夹)来训练模型。
此外,该文件夹相当大(包含大量图像),因此它可以在开始时有效地训练模型,并且我们可以通过更简单的数据了解模型的表现。

这将在一定程度上确保模型是否学会提取所需的特征,并且我们可以通过分析相同的图来验证这一点。
如果观察到模型已经学会正确地提取特征,那么我们可以用包含图形类型背景图像的下一个文件夹继续训练模型,即使这个文件夹足够大,因此希望它使模型学会忽略背景。

最后,我们用包含剥离背景的图像来更新和训练模型。

这种方法使我们能够找出数据集的哪一部分没有被正确分类,如果结果不令人满意,我们可以分析所有图,确定模型在哪里表现不佳,并专注于该部分。

开发模型的方法:
1。)首先,选择后端(MXNet 胶子),选择一些可能适合这个特定任务的基本模型。
2。)选择不太密集的变体,并比较它们的性能。

#Analysing basic models
analysis_name = "analyse_models";models = [["resnet34_v2", False, True],["densenet121", False, True],["vgg16", False, True],["alexnet",False,True],["mobilenetv2_1.0",False , True]];epochs=5
percent_data=15
analysis = gtf.Analyse_Models(analysis_name, models, percent_data, num_epochs=epochs, state="keep_all");

3.)为模型调整参数。这是开发一个好模型最关键的部分。

#Shuffle the datagtf.update_shuffle_data(True);#For densenets batch size of 2 and 4 did not work well, batch size of 8 worked just fine , after which validation loss starts to increasegtf.update_batch_size(8);#learning rate 0.1 and 0.05 were not working well ,lr lesser than that didn't vary much w.r.t val and training loss .0.01 best choice#Though optimizers like Adam and its variants are very fast at converging but the problem with them are they sometimes get stuck at local optima's.#Whereas famous optimizers linke SGD and SGD plus momentum are slower to converge , but they dont get stuck at local optima easily.#Here after analysing all these optimizers , sgd worked better.#sgd was the best optimizer even for densenetsgtf.optimizer_sgd(0.01);gtf.Reload();

在调整参数时,这种分析非常重要。

4.)使用相同的调整参数,尝试相同模型的更密集的变体。

#Comparing DenseNets for different depth of densenet variants.analysis_name = "analyse_models";
models = [["densenet121", False, True], ["densenet161", False, True], ["densenet169", False, True],["densenet201", False, True]];
epochs=10
percent_data=15
analysis = get.Analyse_Models(analysis_name, models, percent_data, num_epochs=epochs, state="keep_none");

如果我们直接选择密集网络,可能会因为梯度爆炸/递减而表现不佳,最终我们可能根本不会考虑这个选项。

选择最佳模型

在所有这些努力之后,当暴露于看不见的数据时,很容易找出哪个模型能给你最好的结果。在这种情况下,结果是 Densenet,所选择的 dense net 的适当深度是 121,这足以给出期望的结果。
Resnets 架构在经过调优后的整体性能上排名第二。Mobilenets 可能会表现得非常好,尽管这需要花费大量的训练时间和空间来完成这个特殊的任务。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这里的三个图是相同的模型,但是具有更新的数据。Densenet_121_3 在完整数据
上进行训练。该图表明该模型在所有三种数据集上都学习得相当好。
我们从我们的模型中获得了难以超越的验证性能。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

最终模型

该模型看起来不像是过度拟合的,并且在对新图像进行分类时表现良好,确保该模型可以运行。
如果我们可以对现有模型进行任何改进的话,那就是集中精力改进/增加带有图形类型背景的图像。如果我们仔细观察,在那些图像被更新和模型被训练之后,模型的性能下降了一点。
这是另一个可以探索的工作领域。

结论

我们已经使用各种架构执行了迁移学习,使用定义明确的方法构建了一个可靠的分类器。尽管这里有一点需要注意,创建项目和实验使我们很容易管理和比较实验/模型。

此外,我们可以使用很少的代码行执行如此多的复杂操作。如果我们采用传统的方法,在模型之间进行这样的比较将会花费我们不希望的代码,这可能很难调试。当我们使用 monk 库时,更新数据集和重新训练模型也是一项基本任务。

还提到在某些领域可以做更多的工作,进一步提高模型的性能和可靠性。

我们还使用一些测试图像进行了推断,请访问下面的代码链接查看它们。

对于整个代码:

[## sanskar 329/俄语字母

这个项目是关于分类俄语字母。总共有 33 类俄语字母。的目标是

github.com](https://github.com/Sanskar329/Russian-Letters)

或者可以在图像分类动物园查看代码。

对于更多此类应用:

[## 镶嵌成像/monk_v1

Monk 是一个低代码深度学习工具,是计算机视觉的统一包装器。— Tessellate-Imaging/monk_v1

github.com](https://github.com/Tessellate-Imaging/monk_v1/tree/master/study_roadmaps/4_image_classification_zoo)

Monk 教程:

[## 镶嵌成像/monk_v1

模块 1:入门路线图第 1.1 部分:Monk 入门第 1.2 部分:Monk 的基本特性第 1.3 部分…

github.com](https://github.com/Tessellate-Imaging/monk_v1/tree/master/study_roadmaps)

如果这篇文章能帮助你学到新的东西,请分享!

感谢阅读!

面向 STT 和 TTS 的俄语文本规范化

原文:https://towardsdatascience.com/russian-text-normalization-for-stt-and-tts-a6d8f03aaeb9?source=collection_archive---------49-----------------------

包括 STT(语音到文本)和 TTS(文本到语音)在内的许多语音相关问题都需要将抄本转换成真实的“口语”形式,即说话者所说的准确单词。这意味着在一些书写的表达式成为我们的抄本之前,它需要被规范化。换句话说,文本必须分几个步骤进行处理,包括:

  • 转换数字:1984 год->тысячадевятьсотвосемьдесятчетвёртыйгод
  • 展开缩写:2 мин. ненависти->двеминутыненависти
  • 拉丁符号的转写(多为英文):Orwell - > Оруэлл
    等等。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在这篇文章中,我想总结一下我们在俄语语音数据集 Open_STT 中的文本规范化方法,简单介绍一下我们使用的思想和工具。

作为锦上添花,我们决定与社区共享我们的 seq2seq 标准化器。它实际上是通过一行程序调用的,只要看看我们的 github 就知道了。

norm = Normalizer() 
result = norm.norm_text('С 9 до 11 котики кушали whiskas')>>> 'С девяти до одиннадцати котики кушали уискас'

关于任务的更多信息

那么,怎么了?理论上,如果很难将缩写扩展为完整的形式,缩写就不会如此常用:每个人都知道“Dr .”代表“医生”。事实上,这并不简单,一些母语人士的直觉是理解所有细微差别的必备属性。

想知道兔子洞有多深,请看下面的例子:

  • - второе(ые)/the 第二但是t5】-двае/两个 e;
  • 2 части - две части/two 部分,-нетвторойчасти/no 第二部分;
  • длиной до 2 км-длинойдодвухкилометиов/长达两公里,-ееемдовто
  • = 2/5-равнодвепятых/等于五分之二,但是д. 2/5-домдвадробьпять/house二建五甚至-двапять/二建五。

缩写和首字母缩略词也是一个问题:同一个缩写可以根据语境(г - городгод )或说话人(БЦ - б цбизнес центр ?).想象一下一个令人头痛的音译是什么:不知何故,它相当于学习另一种语言。上述所有问题对口语形式来说都是特别具有挑战性的。

统计管道

人们很容易迷失在这无尽的变化中:你沉浸在搜索和处理越来越多新案件的无尽循环中。在某些时候,最好停下来回忆一下帕累托原则或 20/80 法则。我们可以处理大约 20%的最流行的案例,覆盖大约 80%的整个语言,而不是解决一般的任务。

第一个 Open_STT 版本的方法更加残酷:如果你看到一个数字,就把它改成一个默认的基数。作为对 STT 应用程序的攻击,这个决定甚至是合理的。通过将2020 год转换为 две тысячи двадцать год ,你只丢失了 1-2 个字母,而忽略一个完整的数字会导致三个单词的错误。

渐渐地,我们添加了某种形式的上下文依赖。现在год这个词前面的数字变成了序数2020最后变成了 две тысячи двадцатый 。因此,我们的“手动”统计管道出现了——我们找到最流行的组合,并将它们添加到规则集中。

序列到序列网络

在某种程度上,序列到序列(seq2seq)架构显然非常适合我们的任务。事实上,seq2seq 与手动管道工作得一样好,甚至更好:

  • 将一个序列转换为另一个序列—选中;
  • 模型上下文依赖关系—检查;
  • 找到最合适的规则来转换序列—检查;

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

序列“5 января".”的注意力得分图为了生成“пятого”模型的结尾,不仅考虑了单词“5 ”,还考虑了单词“января".”的后续字符

作为基础,我们从这里的开始,在 PyTorch 上实现 seq2seq。要了解更多关于这个架构的信息,请阅读原版指南,它非常棒。更详细地说:

  • 我们的模型是 Char 级的;
  • 源词典包含俄语字母+英语+标点+特殊记号;
  • 目标词典—仅俄语字母+标点符号。

你拥有的多样化和高质量的数据越多,你的模型训练得就越好——这是常识。如果你曾经试图为英语语言找到这样的数据,那么你知道这是非常简单的。此外,甚至还有数字规范化的开源解决方案。至于俄语,一切就更复杂了。

因此,训练数据是以下各项的组合:

  • 开源规范化数据— 俄语文本规范化 —大部分是书籍,部分用规则集规范化;
  • 使用我们的手动管道处理的随机网站的过滤数据;
  • 一些使模型更加健壮的扩展——例如,任意位置的标点和空格、大写、长数字等。

火炬报

除了解决这个问题,我们还想测试一些新的有趣的工具,比如 TorchScript。
TorchScript 是 PyTorch 提供的一个很棒的工具,可以帮助你从 Python 中导出你的模型,甚至可以作为 C++程序独立运行。

简而言之,PyTorch 中有两种使用 TorchScript 的方式:

  1. 硬核,那需要完全沉浸到 TorchScript 语言中,一切后果自负;
  2. 轻柔,使用开箱即用的torch.jit.script(和torch.jit.trace)工具。

事实证明,对于比官方指南中介绍的更复杂的模型,您必须重写几行代码。然而,这不是火箭科学,只需要一些小的改变:

  • 打字要多注意;
  • 想办法重写不支持的函数和方法。

有关更详细的指南,请查看 a 通道柱

例子

综上所述,查看几个归一化结果。以下所有句子均取自测试数据集,即该模型未见过这些示例中的任何一个:

  • norm.norm_string("Вторая по численности группа — фарсиваны — от 27 до 38 %.")

‘вторая·численности·фарсиваны·двадцати·тридцативосьмипроцентов.’

  • norm.norm_string("Висенте Каньяс родился 22 октября 1939 года")

‘висентеканьясродилсядвадцатьвторогооктябрятысячадевятьсоттридцатьдевятогогода’

  • norm.norm_string("играет песня «The Crying Game»")

песнязэ

  • norm.norm_string("к началу XVIII века")

‘началувосемнадцатоговека’

  • norm.norm_string("и в 2012 году составляла 6,6 шекеля")

тысячидвенадцатом·составлялашестьцелых·шестьдесятыхшекеля’

原载于 2020 年 3 月 4 日https://spark-in . me

Rust-Powered 命令行实用程序可提高您的工作效率

原文:https://towardsdatascience.com/rust-powered-command-line-utilities-to-increase-your-productivity-eea03a4cf83a?source=collection_archive---------9-----------------------

您腰带下的现代快速工具

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者图片

**Table of Contents**[**Introduction**](#74de)1\. [du alternatives: dust and dutree](#d923)
  ∘ [dust](#97b0)
  ∘ [dutree](#9641)
2\. [time alternative: hyperfine](#bb52)
3\. [A fuzzy finder: skim](#e9fc)
4\. [sed alternative: sd](#5960)
5\. [top/htop alternatives: ytop and bottom](#0dd0)
6\. [A Bonus Tip](#69a8)[**Conclusion**](#1fb3)

介绍

上个月,我写了一篇文章分享七个 Rust 驱动的命令行实用程序。

这些都是现代化的快速工具,您可以每天使用您的终端。

自从发表那篇原始文章以来,我一直在寻找更多的 Rust 驱动的命令行实用程序,并且我发现了更多的精华,今天我很高兴与大家分享。

这些工具将帮助您高效完成终端工作。

我推荐安装 Rust。当你安装 Rust 时,它会把 Cargo 作为工具链之一安装。你可以在货物上安装生锈的动力工具。

如果你想开始学 Rust,这篇文章可以帮你入门。

让我们开始吧。

du替代品:dustdutree

dust

在 Linux 中,du命令估计文件空间的使用情况。dustdutree是铁锈动力du的替代品。

对我来说很难使用du命令。例如使用du,列出文件和文件夹,并计算包括我使用的子文件夹在内的总大小:

$ du -sk * | sort -nr

Dust 给你一个目录的即时概览,它的磁盘空间和命令更简单。

例如

$ dust
$ dust <dir>
$ dust <dir>  <another_dir> <and_more>
$ dust -p <dir>  (full-path)

你可以在这里找到所有的用法。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

~/的灰尘结果。货物目录。作者图片

在上图中,可以看到 app 目录占用了 57MB(29%),目标目录占用了 139 MB (71%)的磁盘空间。

可以带货安装dust

$ cargo install du-dust
$ dust --help

你可以在这里找到其他装置

dutree

[dutree](https://github.com/nachoparker/dutree)是分析磁盘使用情况的另一个du选择。

可以带货安装dutree

$ cargo install dutree

可以通过dutree --help找到用法。

dutree也有简单的命令选项。例如,您可以使用-d选项显示不同目录深度的目录。

$ dutree -d // the default is 1
$ dutree -d2 // show directories up to depth 2

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

dutree 结果。作者图片

time备选:hyperfine

在 Linux 中,time命令显示给定命令运行需要多长时间。它对于测试脚本和命令的性能非常有用。

假设您有两个脚本或命令在做同样的工作。您可以使用time命令来比较这些脚本所需的时间。

hyperfine 是一款 Rust 驱动的、time替代命令行基准测试工具。

用货物安装:

$ cargo install hyperfine

如果使用--warmup N运行hyperfine,超精细将在实际测量前执行 N 次预热。

// warmup run
$ hyperfine --warmup 3 'dutree -d' 'dutree -d2' 'dust'
// actual run
$ hyperfine 'dutree -d' 'dutree -d2' 'dust'

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

超精细结果。作者图片

一个模糊查找器:skim

模糊查找器帮助您快速搜索和打开文件。是grep的互动版。

[skim](https://github.com/lotabout/skim)是铁锈中的模糊查找器。([fzf](https://github.com/junegunn/fzf)是一个用 Go 语言编写的模糊查找器,也是一个很好的选择。)

可以用 Cargo 安装。

$ cargo install skim

其他安装请参见本页。

您可以搜索文件。

$ sk
$ file-name

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

使用 sk 命令进行文件搜索

或者使用grep在文件中查找一行。

sk --ansi -i -c 'grep -rI --color=always --line-number "{}" .'

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

使用 sk 命令进行线搜索

sed备选:sd

在 Linux 中,可以使用[sed](https://www.gnu.org/software/sed/manual/sed.html)进行基本的文本替换。

可以用货物安装sd

cargo install sd

sd 使用更简单的语法。

使用标清:

$ sd before after

而使用 sed:

$ sed s/before/after/g

sd替换换行符和逗号:

$ sd '\n' ','

并且用sed:

$ sed ':a;N;$!ba;s/\n/,/g'

sd是不是sed

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图像来自标清

top/htop替代品:ytopbottom

htop在 Linux 中是一个交互式的进程查看器。[ytop](https://github.com/cjbassi/ytop)[bottom](https://github.com/ClementTsang/bottom)是图形过程监视器。

您可以将它们与货物一起安装:

$ cargo install ytop
$ cargo install bottom

您可以使用btm运行bottom

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

底层在行动。作者图片

额外的小费

当您使用另一台计算机、服务器或系统时,您将使用 Linux 命令。即使您正在使用 Rust 提供的替代工具,继续使用 Linux 命令也是一个好主意。

我建议创建别名。

在我的.zshrc中,我有以下内容:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者在 my .zshrc. Image 中的别名

如果您是 Powershell 用户:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Powershell 中的别名

结论

我希望你开始使用这些生锈的工具。你会发现它们非常快,看起来很现代。如果你知道更多,请告诉我!

通过 成为 的会员,可以完全访问媒体上的每一个故事。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

https://blog.codewithshin.com/subscribe

[## 通过将 Python 转换成 Rust 来学习 Rust

Rust 基础入门教程

towardsdatascience.com](/learning-rust-by-converting-python-to-rust-259e735591c6) [## 7 个强大的 Rust 驱动的命令行工具

适合每个开发人员的现代 Linux 命令

towardsdatascience.com](/awesome-rust-powered-command-line-utilities-b5359c38692) [## 你想学 Rust 但是不知道从哪里开始

Rust 初学者的完整资源

towardsdatascience.com](/you-want-to-learn-rust-but-you-dont-know-where-to-start-fc826402d5ba)

生锈的强盗

原文:https://towardsdatascience.com/rusty-bandit-724a9a7a3606?source=collection_archive---------48-----------------------

用铁锈建造多臂强盗

为什么不像其他数据科学家一样使用 Python 呢?Python 适合前端工作,但是所有强大的数据科学算法在后端使用更快的东西。为什么不是 C++?很多人避免使用 C++的原因是一样的;做一些严重不安全的事情太容易了。为什么不是 Java 或者 Scala?JVM 上没有任何东西赢得任何速度竞赛。

但是为什么会生锈呢?因为它几乎和 C++一样快,而且没有很多缺点。Rust 对内存和线程安全近乎偏执。可变性必须显式指定,这鼓励程序员谨慎使用。一个变量在任何时候最多只能有一个可变引用,编译器会积极地检查这一点。线程也以类似的细节进行检查。最终的结果是这种语言很难做傻事(至少对于 IDE 来说),而且几乎所有的错误都在编译时被发现。作为一个额外的好处,编译器的错误消息是非常有用的,这是编程世界中几乎独一无二的特性。

但是 Rust 不是数据科学语言,也不打算是。C++也不是,它正在愉快地支撑 Tensorflow、XGBoost、PyTorch 和 LightGBM 的后端。Rust 可能会有类似的未来,运行 Keras 优雅的语法所隐藏的所有杂乱的矩阵计算。

除此之外,它就在那里。学习生锈是一个挑战,这是一个足够好的理由。

随着预备工作的结束,本文的剩余部分将由三部分组成。第一部分将快速介绍多臂土匪问题。第二篇将描述我是如何设计和编码 Ratel ,一个用于执行多臂强盗实验的 Rust 程序。第三部分将讨论其中一个实验的结果。

(注意程序名。多臂强盗式学习包括探索(做出新的选择)和利用(做出相同的、明智的选择)的结合。我想不出有哪种动物比蜜獾或南非荷兰语 ratel 更擅长探险或开发。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

CT Cooper /公共领域

多臂土匪

独臂强盗是老虎机的一个古老俚语。对于独臂强盗,用户拉动杠杆或手臂,希望获得奖励,同时让机器偷一点自己的钱作为特权。一个多臂强盗有几个可以拉的 am,每一个都按照某种概率分布产生一个奖励。好消息是,在大多数应用程序中,多臂匪徒比拉斯维加斯的胜算更大。坏消息是,每只手臂的奖励分配是秘密的,而且很可能会保持秘密。

多臂 bandit 学习的目标不是找到每个臂的分布,而仅仅是具有最高平均奖励的臂。概括地说,只有一种方法可以做到这一点;拉所有的手臂很多次,直到你有一个合理的准确测量他们的平均值。这不完全是一个算法。该算法决定如何选择臂。我们有几个选择。

贪婪算法(完全利用)

对每个臂进行初始猜测。这通常是可能值的已知范围的高端。随机选择初始臂,并基于输出更新对该臂的猜测。随后的猜测选择迄今为止平均回报率最高的那只手臂,任何平局都是随机打破的。

这是一个不错的策略,但如果最初几次猜测不走运,它确实有选择次优手臂的风险。我将在第三部分量化这种风险;现在举个例子就够了。

假设有两臂,一臂支付 1 25%的时间,另一臂支付 1 75%的时间。其余时间他们支付 0。每只手平均支出的最初猜测都是 1。偶然地,第二只手臂的前四次拉动支付 0,而第一只手臂的前四次拉动支付 3 次 0 和 1 次 1。我们对第二只手臂的平均回报率 0.2 的估计,现在低于第一只手臂的实际平均回报率 0.25。遵循一个贪婪的策略,我将继续拉臂 1,几乎没有机会纠正我最初的错误。

这似乎是一个极端的例子;二号臂连续出现四个 0 的几率不到 1%。在现实生活中,bandit arms 的返回之间的分布通常要小得多,并且贪婪算法找到坏的局部最大值的机会相应地更大。

ε-贪婪算法(探索与剥削)

贪婪算法陷入局部最大值的唯一方式是次优 arm 的估计回报高于最优 arm 的估计回报。实际上,这只会在实验的相对早期发生;给定足够的时间,大数定律保证最佳的手臂将有最好的估计。“足够的时间”这个短语在数学中是一个有趣的短语;这可能是一段非常非常长的时间。此外,多种武器的匪徒通常在“生产”阶段接受训练,需要及时达到最佳或接近最佳的性能。我们没有“足够的时间”

进入ε-贪婪算法。它与贪婪算法只有一点不同。在某种概率ε(0.01 和 0.1 是流行的,但它因问题而异)下,ε-贪婪算法将拉出一个随机臂。如果贪婪算法做出了错误的选择,这允许最佳手臂回到游戏中,同时在大多数时间仍然基于当前知识做出最佳猜测。

要想变得更复杂,就要逐渐降低ε的值。

乐观算法

到目前为止,我讨论的两种算法都只使用了 bandit arm 支出的平均值。如果我们想用方差的信息。最佳非参数估计来自霍夫丁不等式,并给出了我们的估计:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

理查德·萨顿和安德鲁·巴尔托,《强化学习:导论》,第二版。

在该公式中,Q_t(a)是臂 a 值的当前估计平均值,t 是试验次数,N_t(a)是臂 a 被拉动的次数,c 是用户定义的常数。该算法挑选具有最高置信上限的臂。这具有类似于ε值递减的ε贪婪算法的效果,只是不需要手动选择初始ε或衰减规则。仍然需要选择 c 的值。

Ratel

一个多兵种武装土匪模拟器可以拆分成两部分;土匪本身和球员,或代理人。在 Python、C++或 Java 中,这两个组件都可以编码为类。Rust 的面向对象设置本身没有类,而是依赖于结构,它携带有组织的数据集合,以及特征,它包含可以为结构实现的方法。Structs 非常类似于 C++ structs ,而 traits 类似于 Java 接口。与通常的编码概念一样,这将通过一个例子变得更加清晰。

强盗

我的基本强盗特征如下,包括一个好强盗需要的所有功能。

*/// A trait for common members of the Bandits* pub trait Bandit<T: ToPrimitive> {
    */// The number of arms of the Bandit* fn arms(&self) -> usize;

    */// The arm with the highest average reward.* fn best_arm(&self) -> usize;

    */// The maximum average reward of all the arms.* fn max_reward(&self) -> f64 {
        self.mean(self.best_arm())
    }

    */// The average reward of a given arm.* fn mean(&self, arm: usize) -> f64;

    */// The average rewards of all the arms.* fn means(&self) -> Vec<f64> {
        (0..self.arms()).map(|arm| self.mean(arm)).collect()
    }

    */// The reward from a pull of a given arm.* fn reward(&self, arm: usize) -> T;

    */// The standard deviation of a given arm.* fn std(&self, arm: usize) -> f64;

    */// the standard deviations of all the arms.* fn stds(&self) -> Vec<f64> {
        (0..self.arms()).map(|arm| self.std(arm)).collect()
    }
}

可以定义的方法有。那些需要来自特定结构类型的数据的将在实现中定义。一个土匪需要的数据,简单来说就是每个手臂的概率分布。下面是每个臂都具有二项分布的情况。

use rand_distr::Binomial;*/// A bandit whose arms distribute rewards according to binomial distributions.* pub struct BinomialBandit<*'a*> {
    */// Vector of number of trials of a* `yes-no` *experiment.* nums: &*'a* Vec<u32>,

    */// Vector of experiment success probabilities.* probs: &*'a* Vec<f64>,

    */// The bandit arm with highest reward.* best_arm: usize,

    */// Distributions of the arms.* distributions: Vec<Binomial>,
}

该结构需要是公共的,因为它将在其当前模块之外被引用。Rust 中的对象有多个隐私级别;使用最严格的限制几乎总是一个好主意。

这段代码中可能让新手感到困惑的其他部分是&符号&'a的使用。前者表示值是引用,类似于指针。在这个结构中,numsprob不是向量本身,而仅仅是向量的内存地址。奇怪的小'a符号控制着这些引用的寿命,并确保它们不会比它们所指向的向量活得更长。Rust 不允许悬空指针。

二项式分布由试验次数和每次试验的成功概率定义。存储定义参数和分布向量似乎是多余的,事实也的确如此。按照目前的设计,这些发行版不能返回它们的参数,所以值得额外的空间来单独保存它们。

定义自定义构造函数通常会有所帮助。为此,请实现结构。

impl<*'a*> BinomialBandit<*'a*> {
    */// Initializes a new Bandit where each arm distributes rewards according to a binomial
    /// distribution.* pub fn *new*(nums: &*'a* Vec<u32>, probs: &*'a* Vec<f64>) -> BinomialBandit<*'a*> {
        assert_eq!(nums.len(), probs.len());
        assert!(probs.val_max() <= 1.0);
        assert!(probs.val_min() >= 0.0);
        assert!(nums.val_min() > 0);
        let dist = nums
            .iter()
            .zip(probs)
            .map(|(&n, &p)| Binomial::*new*(u64::*from*(n), p).unwrap())
            .collect();
        let best_arm = nums
            .iter()
            .zip(probs)
            .map(|(&n, &p)| f64::*from*(n) * p)
            .collect::<Vec<f64>>()
            .arg_max();
        BinomialBandit {
            nums,
            probs,
            best_arm,
            distributions: dist,
        }
    }
}

这大部分是不言自明的。一些基本的断言语句确保我不会加载垃圾。然后,这两个向量被压缩并迭代,以创建分布向量。可能需要注释的一位代码是u64::from(n)。现代编程语言,至少是编译过的各种语言,似乎正在脱离自动类型转换的思想,Rust 也不例外。用一个整型数除以一个浮点型数是行不通的。这可能对类型安全有好处,但是也有强制使用难看的显式类型转换语句的缺点。

在这一点上,我只是有一个土匪坐在周围不能做太多。为了解决这个问题,我需要为 BinomialBandit 结构实现 Bandit 特征。这包括在 Bandit 中定义依赖于结构数据的五个方法。

impl<*'a*> Bandit<u32> for BinomialBandit<*'a*> {
    *///Returns the number of arms on the bandit.* fn arms(&self) -> usize {
        self.nums.len()
    }

    *///Returns the arm with highest average reward.* fn best_arm(&self) -> usize {
        self.best_arm
    }

    */// Computes the expected return of each arm.* fn mean(&self, arm: usize) -> f64 {
        f64::*from*(self.nums[arm]) * self.probs[arm]
    }

    */// Determines the reward for pulling a given arm.* fn reward(&self, arm: usize) -> u32 {
        self.distributions[arm].sample(&mut thread_rng()) as u32
    }

    */// Computes the standard deviations of each arm.* fn std(&self, arm: usize) -> f64 {
        (f64::*from*(self.nums[arm]) * self.probs[arm] * (1.0 - self.probs[arm])).sqrt()
    }
}

最初的 Bandit 特性将输出类型留给了单独的实现。在这里,我指定 BinomialBandit 的业务是给出无符号整数奖励。如果你看一下高斯-班迪特,你会看到它在漂浮。

代理人

正如有多种类型的强盗一样,也有多种类型的代理。我为三种算法中的每一种设计了一个代理结构,用于寻找最优策略和一个包含它们都需要的功能的通用代理特征。和强盗一样,我将首先描述代理特征。

*/// A trait for common members of the Agents.* pub trait Agent<T: ToPrimitive> {
    */// The action chosen by the Agent.* fn action(&self) -> usize;

    */// The number of arms in the Bandit the Agent is playing.* fn arms(&self) -> usize {
        self.q_star().len()
    }

    */// The Agent's current estimate of the value of a Bandit's arm.* fn current_estimate(&self, arm: usize) -> f64 {
        self.q_star()[arm]
    }

    */// The Agent's current estimate of all the Bandit's arms.* fn q_star(&self) -> &Vec<f64>;

    */// Reset the Agent's history and give it a new initial guess of the Bandit's arm values.* fn reset(&mut self, q_init: Vec<f64>);

    */// Update the Agent's estimate of a Bandit arm based on a given reward.* fn step(&mut self, arm: usize, reward: T);

    */// Calculate the update of the Agent's guess of a Bandit arm based on a given reward.* fn update(&mut self, arm: usize, reward: T) -> f64 {
        self.stepper().step(arm) * (reward.to_f64().unwrap() - self.q_star()[arm])
    }

    */// Returns a reference to the Agent's step size update rule.* fn stepper(&mut self) -> &mut dyn Stepper;
}

与 bandit 特性一样,大部分功能已经被填充。唯一需要讨论的方法是update。所有的代理都通过一个类似于随机梯度下降的规则来更新他们的估计。取当前奖励和估计的平均奖励之间的差,并通过该值的某个倍数(类似于学习率)来调整估计值。究竟是什么规则决定了“学习率”取决于用户;一个好的选择是调和递减率,它使估计的平均值成为样本平均值。可用的步进器类别在utilsstepper模块中。

在 trait 的开头,有必要明确声明代理使用的任何类型T都必须可转换为浮点型。

看一看 EpsilonGreedy 结构将有助于向我们展示如何使用代理特征。

*/// Agent that follows the Epsilon-Greedy Algorithm.
///
/// A fixed (usually small) percentage of the
/// time it picks a random arm; the rest of the time it picks the arm with the highest expected
/// reward.* pub struct EpsilonGreedyAgent<*'a*, T> {
    */// The current estimates of the Bandit arm values.* q_star: Vec<f64>,

    */// The Agent's rule for step size updates.* stepper: &*'a* mut dyn Stepper,

    */// The fraction of times a random arm is chosen.* epsilon: f64,

    */// A random uniform distribution to determine if a random action should be chosen.* uniform: Uniform<f64>,

    */// A random uniform distribution to chose a random arm.* pick_arm: Uniform<usize>,
    phantom: PhantomData<T>,
}

该结构最重要的组件是q_star,它包含了每条臂平均值的估计值。其余的大部分都有助于更新。我已经讨论过stepperepsilon仅仅是选择随机臂的频率,两个均匀分布决定何时以及如何选择随机臂。最后一个参数phantom是一个必需的引用,以确保该结构被视为使用了T类型的东西,否则该结构只由实现特征使用。

我们如何实现 EpsilonGreedyAgent 的代理?见下文。

impl<*'a*, T: ToPrimitive> Agent<T> for EpsilonGreedyAgent<*'a*, T> {
    */// The action chosen by the Agent. A random action with probability* `epsilon` *and the greedy
    /// action otherwise.* fn action(&self) -> usize {
        if self.uniform.sample(&mut thread_rng()) < self.epsilon {
            self.pick_arm.sample(&mut thread_rng())
        } else {
            self.q_star.arg_max()
        }
    }

    */// The Agent's current estimate of all the Bandit's arms.* fn q_star(&self) -> &Vec<f64> {
        &self.q_star
    }

    */// Reset the Agent's history and give it a new initial guess of the Bandit's arm values.* fn reset(&mut self, q_init: Vec<f64>) {
        self.q_star = q_init;
        self.stepper.reset()
    }

    */// Update the Agent's estimate of a Bandit arm based on a given reward.* fn step(&mut self, arm: usize, reward: T) {
        self.q_star[arm] += self.update(arm, reward)
    }

    */// Returns a reference to the Agent's step size update rule.* fn stepper(&mut self) -> &mut dyn Stepper {
        self.stepper
    }
}

游戏

为了进行实验,代理人和强盗必须相互作用。交互由一个Game结构管理,该结构包含一个Agent、一个Bandit,以及使它们交互的计数器和方法。

*///Structure to make the Agent interact with the Bandit.* pub struct Game<*'a*, T: AddAssign + Num + ToPrimitive> {
    */// Agent learning about bandit.* agent: &*'a* mut dyn Agent<T>,
    *///Bandit used by agent.* bandit: &*'a* dyn Bandit<T>,
    */// Records wins and losses from each arm pull. Win means pulling the best arm.* wins: RecordCounter<u32>,
    */// Records rewards from each arm pull.* rewards: RecordCounter<T>,
}

基本结构不言自明。除了AgentBandit之外,它还包含计数器来记录wins(挑选最佳手臂)和rewards(所有手臂拉动的结果)。)

实现更有趣一点。

impl<*'a*, T: AddAssign + Copy + Num + ToPrimitive> Game<*'a*, T> {
    */// Initializes a Game with an Agent, Bandit, and new counters.* pub fn *new*(agent: &*'a* mut dyn Agent<T>, bandit: &*'a* dyn Bandit<T>) -> Game<*'a*, T> {
        assert_eq!(agent.arms(), bandit.arms());
        Game {
            agent,
            bandit,
            wins: RecordCounter::*new*(),
            rewards: RecordCounter::*new*(),
        }
    }

    */// Returns the number of bandit arms.* pub fn arms(&self) -> usize {
        self.bandit.arms()
    }

    */// Agent chooses an arm to pull and updates based on reward.* fn pull_arm(&mut self) {
        let current_action = self.agent.action();
        self.wins
            .update((current_action == self.bandit.best_arm()) as u32);
        let reward = self.bandit.reward(current_action);
        self.rewards.update(reward);
        self.agent.step(current_action, reward);
    }

    */// Resets Game. Resets Agent with new initial guess and resets counters.* pub fn reset(&mut self, q_init: Vec<f64>) {
        self.agent.reset(q_init);
        self.rewards.reset();
        self.wins.reset();
    }

    */// Returns vector of rewards.* pub fn rewards(&self) -> &Vec<T> {
        self.rewards.record()
    }

    */// Run game for a certain number of steps.* pub fn run(&mut self, steps: u32) {
        for _ in 1..=steps {
            self.pull_arm()
        }
    }

    */// Returns vector of wins.* pub fn wins(&self) -> &Vec<u32> {
        self.wins.record()
    }
}

通常,大多数类方法都是简单的 setters 和 getters。最有趣的两个方法是pull_armrun。第一个选择一个动作,检查它是否是最好的,然后返回奖励和它是否是最好的。第二个通过拉手臂进行完整的实验,拉的次数是实验需要的次数。

这些都是进行多臂强盗实验所需要的基础。Ratel 代码的其余部分要么是将实验放在一起的胶水,要么是产生输出的代码。任何人在自己的工作中使用这个模块都必须编写自己的版本,所以我不会详细讨论我的代码。在下一部分,我将讨论我的一个实验。

两两伯努利盗匪

当考虑任何复杂的系统时,从简单开始是值得的。最简单的多臂强盗只有两臂,最简单的概率分布可以说是伯努利分布。一个以某个概率 p 返回,零以概率 1-p 返回。给定两条臂,一条左臂和一条右臂,具有不同概率的伯努利输出(否则它将是微不足道的),使用 a)贪婪算法,b)ε-贪婪算法找到最佳臂的可能性有多大?(乐观算法更适合连续分布;这里就不考虑了。)随着概率的变化,这种可能性如何变化?

我以 0.01 为增量,从 0.01 到 0.99 取概率的全范围。用概率 p 测试臂 A,用概率 q 测试臂 B,然后反过来用概率 q 测试臂 A,用概率 B 测试臂 B,这是没有好处的,所以我可以通过总是给左臂更小的概率来将情况的数量减半。我还有 4851 种组合可以尝试。同时,我还将尝试不同的初始值,从 0.1 到 1.0,增量为 0.1。现在有 48510 种组合。

这就是在 Rust 工作真正开始有回报的地方。如果我一直从事 Python 的工作,这将是我一个月以来最后一次给你写信。Rust 的速度大约快 100 倍,这意味着我甚至可以在一夜之间运行大型实验。

贪婪算法收敛非常快;200 次迭代足以获得所有组合的精确结果。这让我可以将每个实验运行 100000 次。对于六核 i7 处理器(总共 12 个线程),贪婪算法实验运行大约 3.5 小时。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

选择初始猜测值为 0.1 的最佳臂的概率。斧头是每只手臂奖励 1 的几率。

实验中最低可能的初始猜测是双臂 0.1。如果我以此开始,那么当只有一个臂具有高概率时,贪婪算法选择正确臂的概率是高的,在这种情况下,问题是非常容易的,或者两个臂都具有低概率,在这种情况下,它们的相对差异仍然是显著的。当左臂达到 0.7 的概率时,选择最佳手臂的机会几乎等于零。

随着最初猜测的增加,情况会有所改善。对于臂概率的所有选择,每次初始猜测增加 0.1,挑选最佳臂的可能性增加,或者至少不减少。当最初的猜测达到 0.7 时,低概率成功的区域是一个小长条,其中两臂的概率都很高,并且它们的概率差很低(三角形对角线的较低部分)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

为每个初始猜测值选择最佳臂的概率。随着最初猜测的增加,选择最佳手臂的可能性也会增加。

寓意是,对于贪婪算法,初始猜测是重要的,应该尽可能地高。在这种情况下,这很容易,因为已知分布是伯努利分布。随着分布信息的减少,一个好的初步猜测将会变得更加困难。

另一个教训是,即使有很好的猜测,也有贪婪算法不那么好的情况。在这个玩具的例子中,很容易限定,如果不是完全量化,坏的区域,但随着更多的武器或更复杂的分布,奢侈品将不再可用。

一个显而易见的问题是,是否有可能通过更长的运行获得更高的准确性;200 次迭代不算多。事实证明这已经足够了。绘制实验的子集,其中两臂的概率仅相差 0.01(取左臂为 0.1、0.2、0.3 等),很明显,在第 100 次迭代之前达到了收敛。这是贪婪算法得到的最好结果。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

ε贪婪

有没有更好的策略?是的,尽管这是有代价的。除了最极端的情况之外,选择一个概率较低的随机臂几乎可以得到最佳结果。然而,ε-贪婪算法有两个缺点。首先,成功的概率有一个硬上限,小于 1.0。如果以概率ε选择随机臂,则可以以最多(1-ε)+ε/(臂数)的概率选择最佳臂。随着臂的数量变大,这接近 1-ε,这是保持ε小的动机。

第二个问题是ε-greedy 需要更长的时间来收敛,ε值越小,收敛时间越长。

ε= 0.1

我从ε= 0.1 开始。对于两个手臂,选择最佳手臂的概率应该最大为大约 0.95。和贪婪算法一样,我尝试了 10 个不同的初始值,范围从 0.1 到 1.0。每次运行由 4000 次迭代组成,我运行了 10000 次(是贪婪算法的十分之一)。实验进行了大约 7.5 个小时。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

为每个初始猜测值选择最佳臂的概率。选择最佳臂的可能性很大程度上与最初的猜测无关。

首先要注意的是,对最初猜测的依赖几乎已经消除。仅这一点就成为对 epsilon-greedy 有利的重要论据。第二,除了对角线上最窄的细长条,所有的臂组合都有接近其最大值的精度。第三,正如预期的那样,最大精度没有贪婪算法那么高。即使有一个警告,这仍然是一个比以前的结果有所改善。

在代理商仍然难以选择正确手臂的地区,有更多的好消息。学习还没有饱和。进一步的迭代可能会产生更好的结果。绘制与上面相同的实验子集表明胜率还没有稳定下来。再重复 4000 次,我会得到更好的结果。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

长期胜率。

ε= 0.01

如果我尝试更低的ε值会发生什么?当ε的值等于 0.01 时,我得到的结果具有贪婪算法和ε-贪婪算法的大部分优点,并且ε更大。除了ε的变化之外,该实验的所有参数与上述相同。

再次,有最大可能的精度,但现在是 0.995。同样,结果在很大程度上独立于最初的猜测。再一次,除了两臂的概率相似的相当窄(尽管稍微宽)的子集之外,对于所有的子集都达到了这种精度。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

那么窄的范围呢?有没有可能更多的迭代会带来更好的结果。是的,但是它将需要更多。精度仍在提高,但速度比ε= 0.1 时慢得多。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

长期胜率

结论

Ratel 可以做许多改进。目前,只有两种概率分布支持土匪,高斯和二项式,但增加其他将是微不足道的。类似地,还可以添加更复杂的代理算法。一个稍微小一点的改进是允许来自多个发行版家族的 bandit arms,尽管我所知道的大多数应用程序并不要求这样。

在实现的代理中,似乎最成功的整体是ε贪婪的,ε值较低。贪婪算法在臂可以被容易地区分的情况下工作得最好。ε-greedy 在除了最难区分的情况之外的所有情况下都是成功的。

也许最重要的结论是,Rust 作为建模和仿真语言表现良好。

SaaS 收入倍数、利率和 R 中的建模

原文:https://towardsdatascience.com/saas-revenue-multiples-and-modeling-in-r-29ce10905488?source=collection_archive---------35-----------------------

证明利率不能完全解释 SaaS 估值的 6 个简单步骤

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

莎伦·麦卡琴在 Unsplash 上的照片

软件公司似乎每年都变得更有价值,在股票市场中也变得更加重要。就在本周,典型的软件即服务 (SaaS)公司 sales force加入道琼斯工业平均指数。它现在是指数的第三大组成部分。自 Covid 危机以来,这些公司的价值增长甚至加速了。

看看 Salesforce 的数据,很明显,其不断增长的估值很大一部分来自于不断增加的收入。Salesforce 和其他 SAAS 公司只是不断赚更多的钱。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

数据位于https://github . com/taubergm/SaaS _ interest _ rates/blob/master/sales force . CSV

然而,这不是唯一的因素。看公司的价格销售比,或者股票“倍数”。在过去的 5 年里,它徘徊在 8 左右,今年飙升至近 12。这种变化让我们许多人想知道,SAAS 的估值是否超前了?

要回答这个问题,才华横溢的风险投资家查马斯·帕里哈皮蒂亚(Chamath Palihapitiya)有一个答案——低利率。

这让我想知道,仅仅使用利率我们能在多大程度上模拟科技公司的估值?如果我们将所有 SAaS 公司放在一起平均,我们能忽略收入增长、利润、客户获取、终身价值、留存以及其他构成经典 SaaS 模型的指标吗?

我试图用 r 中的一些基本建模技术来回答这个问题。该项目的代码和数据都可以在这里找到。这个练习在简单模型的陷阱和我们对软件公司估值的假设方面提供了一些很好的教训。

1 —收集数据

首先,我收集了一份美国所有最大的 SaaS 上市公司的名单。Mike Sonders 有一个很棒的列表,我不得不补充一些他漏掉的。接下来,我收集了这些公司的历史价格销售比数据(又名“倍数”)。这方面的最佳来源是宏观趋势,它有大量的金融信息。

最后,我记录了同期的几个利率指标。我第一次从圣路易斯联邦储备银行网站获得有效联邦基金利率。然后,我决定用 10 年期和 30 年期美国国债的收益率来衡量可能更好,因为这将反映对未来利率的预期。使用 marketwatch 很容易收集这些信息。数据收集的最终结果是一个大矩阵。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

数据位于https://github . com/taubergm/SaaS _ interest _ rates/blob/master/SaaS _ mt2 . CSV

每一列代表一个收益季度,每一个值代表一个价格销售比。底部是利率,以百分比来衡量。

1 —数据探索和目测

首先,我想看看利率和 SaaS 倍数之间是否有任何明显的视觉关系。由于数据已经在 Excel 中,很容易绘制出平均 SaaS 公司 P/S 比率(蓝色)与 10 年期收益率、30 年期收益率和有效联邦基金利率的关系。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

数据在https://github . com/taubergm/SaaS _ interest _ rates/blob/master/SaaS _ averages . CSV

看起来确实有很好的相关性,但是不同的尺度很难区分。使用标准化数据的相同曲线图如下。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

现在这种关系已经不那么明显了。快速检查一下,SaaS 倍数似乎与 30 年期国债收益率呈显著负相关,与 10 年期国债的负相关程度较低。倍数似乎也与联邦基金利率有一定关系,但不是很明显。在 R 中确认这一点很容易。

saas_averages = read.csv(‘saas_averages.csv’)cor(saas_averages)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

最令人惊讶的是,倍数膨胀与时间的简单推进有多么密切的关系。将 SaaS 倍数与一个基本递增序列进行比较,我得到的相关性为 0.86!我生成了简单的图表,将这些变量相互比较,看看我是否遗漏了什么。

plot(time, multiples)
plot(ten_yr, multiples)
plot(thirty_yr, multiples)
plot(fed_rate, multiples)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

2 —归一化其他输入

直接比较平均值的一个问题是它们隐藏了一些信息。在这种情况下,较年轻的 SaaS 公司往往会以非常高的市盈率进行交易,这可能是由于炒作和较高的收入增长的综合作用。这些公司偏向于最近几个季度的平均倍数。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

为了避免这种偏差,我对每个公司的 P/S 数据进行了标准化,以便对所有行进行同等加权。

normalize <- function(x) {
 return ((x — min(x, na.rm = TRUE)) / (max(x,na.rm = TRUE) — min(x,na.rm = TRUE)))
}saas_multiples_normalized = t(apply(saas_multiples_data, 1, normalize))# average quarters and reverse order to increasing in time
saas_multiples_normalized_average = rev(colMeans(saas_multiples_normalized, na.rm = TRUE))

现在,有了干净的数据和一些关于我在寻找什么的直觉,是时候建立一些基本的模型了。

3 —构建模型

R 语言通过“lm”函数使构建线性模型变得轻而易举。使用“lm ”,很容易构建简单的公式,以查看他们如何密切跟踪真实的 SaaS 数据。我决定构建各种利率指标之间的各种多项式关系,看看哪一个表现最好。下面是一些模型。

model_time = lm(multiples_normalized ~ time)model_fed_cubic = lm(multiples_normalized ~ poly(fed_rate,3))model_tenyr_cubic = lm(multiples_normalized ~ poly(ten_yr,3))model_thirtyyr_cubic = lm(multiples_normalized ~ poly(thirty_yr,3))model_tenyr_thirtyyr_fed = lm(multiples_normalized ~ thirty_yr + ten_yr + fed_rate)model_time_tenyr_thirtyyr_fed = lm(multiples_normalized ~ thirty_yr + ten_yr + time + fed_rate)model_thirtyyr_squared_fed_squared_tenyr_squared = lm(multiples_normalized ~ poly(thirty_yr,2) + poly(fed_rate,2) + poly(ten_yr,2))model_time_thirtyyr_squared_fed_squared_tenyr_squared = lm(multiples_normalized ~ time + poly(thirty_yr,2) + poly(fed_rate,2) + poly(ten_yr,2))

这些模型中的每一个都代表了时间、30 年期收益率、10 年期收益率、联邦基金利率和标准化 SaaS 倍数的某种多项式组合

4 —测试模型

我构建的许多模型都是不必要的复杂。例如,没有明显的理由为什么联邦基金利率、30 年期收益率和 10 年期收益率的多项式之和应该表现良好。不过,这是我观察到的。也许这是一个过度拟合的经典案例,或者也许在短期和长期利率预期之间存在某种更深层次的关系。

使用 R“预测”函数可以很容易地计算出这些模型的质量。然后使用“rmse”函数计算预测值和实际 SaaS 平均值之间的均方根误差。

predicted_model_time = predict(model_time)
predicted_model_fed_cubic = predict(model_fed_cubic)
predicted_model_tenyr_cubic = predict(model_tenyr_cubic)
predicted_model_thirtyyr_cubic = predict(model_thirtyyr_cubic)
predicted_model_tenyr_thirtyyr_fed = predict(model_tenyr_thirtyyr_fed)predicted_model_time_tenyr_thirtyyr_fed = predict(model_time_tenyr_thirtyyr_fed)predicted_model_thirtyyr_squared_fed_squared_tenyr_squared = predict(model_thirtyyr_squared_fed_squared_tenyr_squared)predicted_model_model_time_thirtyyr_squared_fed_squared_tenyr_squared = predict(model_time_thirtyyr_squared_fed_squared_tenyr_squared)error_model_time = rmse(multiples_normalized, predicted_model_time)error_model_fed_cubic = rmse(multiples_normalized, predicted_model_fed_cubic)error_model_tenyr_cubic = rmse(multiples_normalized, predicted_model_tenyr_cubic)error_model_thirtyyr_cubic = rmse(multiples_normalized, predicted_model_thirtyyr_cubic)error_model_tenyr_thirtyyr_fed = rmse(multiples_normalized, predicted_model_tenyr_thirtyyr_fed)error_model_tenyr_thirtyyr_fed = rmse(multiples_normalized, predicted_model_tenyr_thirtyyr_fed)error_model_time_tenyr_thirtyyr_fed = rmse(multiples_normalized, predicted_model_time_tenyr_thirtyyr_fed)error_model_thirtyyr_squared_fed_squared_tenyr_squared = rmse(multiples_normalized, predicted_model_thirtyyr_squared_fed_squared_tenyr_squared)error_model_time_thirtyyr_squared_fed_squared_tenyr_squared = rmse(multiples_normalized, predicted_model_model_time_thirtyyr_squared_fed_squared_tenyr_squared)

复杂模型表现最好。同样,这可能是由于过度拟合。真正的考验是使用这些模型来预测未来的数据。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

5 —目视验证

我从上一节中选择了一些表现较好的模型,并查看了它们的公式

*模型 _ 时间= 0.01568 时间+ 0.22692

T5【模型 _ fed _ 立方= 0.2984 * fed+0.2884 * fed—0.2989 * fed+0.4073

model _ thirtyyr _ squared _ fed _ squared _ tenyr _ squared

***=-1.80371 *三十 _ 年+0.22576 *三十 _ 年+0.02209 *年+0.29625 年+1.72330 十 _ 年—0.23045 十 _ 年+ 0.40729

记住这些公式后,我将它们与实际的 SaaS 数据进行对比,看看它们看起来如何。归一化的 Saas 倍数是红点,模型预测是蓝点。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

代码位于https://github . com/taubergm/SaaS _ interest _ rates/blob/master/SaaS _ mt . R

6 —结论和最终想法

我认为公平地说,科技股和利率之间的关系并不完全清楚。虽然以较低的速率扩展 SaaS 倍数有直观的意义,但我们需要一些非常复杂的多项式模型来很好地拟合数据。我曾听金融大师说过类似“降息 0.5 个百分点会导致 2 个百分点的倍数扩张”之类的话。虽然这在某些情况下是正确的,但它肯定不是一条绝对的定律。

现在,有许多可能的原因导致我没能在利率和倍数之间找到一个好的、清晰的关系。也许没有足够多的公司来进行汇总,所以企业的个体差异使事情扭曲得太多了。也许这种关系并不是线性的。在联邦基金利率接近于零的情况下,市盈率非常高,但在经济繁荣、利率接近 2%时,市盈率也很高。也许我的分析遗漏了更大的宏观层面的变量,包括投资者情绪。事实上,从这些粗略数据来看,SaaS 多次波的最佳单一预测因子是时间。牛市的每一个季度都会扩大倍数,超出利率本身的预测。技术人员都希望有一些数量上的原因,让他们变得如此富有如此之快。也许就像我们的动物精神一样,真正的原因是无法量化的。

为疲软的经济牺牲生命

原文:https://towardsdatascience.com/sacrificing-lives-for-a-weak-economy-e2aae2115ee1?source=collection_archive---------51-----------------------

美国如何在科维德战争中犯下大错

在美国,新冠肺炎疫情战争已经进行了七个月,距离总统大选还有两周。我们现在有大量数据来评估和评价各国在抗击病毒方面的表现。美国对疫情采取了随意的应对措施,没有明确的国家计划,州和地方领导人采取了不一致的策略。这种领导力的缺乏导致了疲软的经济和令人难以置信的高死亡率。

下图显示了疫情到目前为止的时间表。欧洲首次出现重大疫情,死亡率高峰出现在 4 月 10 日,美国的高峰出现在两周后的 4 月 24 日,加拿大的高峰出现在两周后的 5 月 7 日。拉丁美洲在夏季晚些时候经历了高峰期。在可怕的初始爆发后,疫情在欧洲和加拿大基本平息。然而,美国制定了不同的路线。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

美国许多州没有阻止病毒的传播,而是选择保持经济开放。政治领导人淡化了病毒的风险,鼓励人们不受干扰地生活。结果是,美国的死亡率远高于其他富裕国家,其曲线类似于较贫困的拉丁美洲地区。美国的人均国内生产总值甚至比最富裕的拉丁美洲国家还要高四倍——我们本应该拥有抗击这种病毒的资源。

COVID 怀疑论者认为减缓病毒传播的努力从根本上被误导了。他们说这种病毒是就像流感(它不是),即使它是致命的,经济比拯救生命更重要。选择对抗病毒的州长的批评者喜欢指出瑞典是一个在对抗病毒方面无所作为的国家的光辉榜样,然而人们似乎仍然乐在其中。数字显示宽松的方法并没有奏效。

下图显示了新冠肺炎的死亡率,以及 COVID 对 30 个最富裕国家的经济影响。经济影响是 2020 年的预计经济增长与没有 COVID 的情况下可能发生的情况之间的差异。国际货币基金组织预计,美国经济遭受的损失将比中位数国家少 22%,但我们的死亡率比中位数高 457%。这种死亡率/增长权衡至少是不利的,特别是因为这些国家中有四分之一既有较低的死亡率,又有较好的经济结果。如果死亡率达到中值,美国本可以挽救超过 170,000 人的生命,这需要更早的行动和更好的协调

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

最好的方法是积极的早期反应。考虑三个成功国家的行动:南韩日本德国。每个国家在早期都暴露于源于中国和意大利爆发的一连串病例。坚持戴口罩、社交距离/活动限制、大量检测以及隔离和接触者追踪的综合措施使得这些国家能够阻止病毒的传播。结果显示,韩国以第二低的死亡率和第二好的经济结果高居榜首。尽管人口老龄化,但日本的死亡率排名第三,财政状况排名第五。德国的死亡率和增长率高于中值,表现远好于其西方邻国。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

另一方面,对数据的合理解读表明,瑞典的宽松政策并不是一个值得效仿的好榜样。下表显示,瑞典及其北欧邻国拥有相似的经济和人口统计数据。区别在于瑞典对 COVID 的反应最小,最大严格指数比其邻国低 35%。结果是,瑞典的死亡率是邻国挪威、芬兰和丹麦的七倍,而牺牲却没有带来经济效益。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

瑞典的邻国在财富、典型的经济增长、贸易开放度以及城市中心人口比例方面都不相上下。最大严格程度反映了政府对 COVID 采取卫生相关和经济措施的程度。瑞典奉行宽松的政策,这是最低的严格性。

毫不奇怪,新冠肺炎疫情成为一个热门的政治问题,因为努力减缓疾病将有经济成本。理性的人可能不同意这种平衡行为,但公共话语很快变成了兜售阴谋论和 T2 坏科学的怀疑论者。像德国这样的国家向我们展示了本来可以怎样,用科学和理性的辩论推动更好的结果。今年 11 月,美国需要让其领导人保持同样高的期望。

本文使用的代码和数据可以在 这里找到

编者注: 走向数据科学 是一份以数据科学和机器学习研究为主的中型刊物。我们不是健康专家或流行病学家,本文的观点不应被解释为专业建议。想了解更多关于疫情冠状病毒的信息,可以点击 这里

SAEMI:电子显微镜图像的尺寸分析

原文:https://towardsdatascience.com/saemi-size-analysis-of-electron-microscopy-images-1-4bb9cd47ad3c?source=collection_archive---------33-----------------------

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

来源: CNR-IOM ( 抄送)

第 1 部分,开发电子显微镜图像定量分析工具

本文是我的系列文章的第 1 部分,详细介绍了 SAEMI 的使用和开发,这是我创建的一个 web 应用程序,用于执行电子显微镜图像的高通量定量分析。你可以点击这里查看应用,点击这里查看其 github 。此外,查看第 2 部分这里(在这里我给出了如何使用该应用程序的步骤)和第 3 部分这里(在这里我回顾了我的图像分割模型的训练过程)。在本文中,我将讨论开发 SAEMI 背后的动机、它在科学研究中的用途以及如何使用该应用程序的快速概述。

电子显微镜

20 世纪初,法国物理学家路易·德布罗意(Louis de Broglie)提出了一个革命性的命题:电子很像光,既可以充当粒子,也可以充当波。这种波粒二象性的想法现在是当代量子力学的基础,并导致了许多现代技术的发展,如个人电脑和智能手机,磁共振成像和 x 光等医疗设备,以及光纤互联网。

然而,从德布罗意强有力的陈述中得出的一项鲜为人知的技术是电子显微镜。它的相对默默无闻有很多原因,尽管这可能是因为它们过于昂贵,并且只在实验室环境中使用。尽管如此,它们在实验室环境中的价值是不可低估的。

与光学显微镜类似,电子显微镜允许研究人员观察世界上肉眼看不到的微小部分。然而,由于电子的波长比光短得多,研究人员可以使用电子显微镜来解析比传统光学显微镜更小数量级的特征。光学显微镜可以放大 20,000 倍,而电子显微镜可以放大 100 万倍以上。

在图 1 中,我们可以看到与传统光学显微镜相比,电子显微镜可以实现不同的长度范围。虽然光学显微镜可分辨的特征确实令人印象深刻(导致我们对细菌、疾病和细胞生物学的现代理解),但电子显微镜可获得的特征将我们对世界的理解提高到了一个全新的水平。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图一。光学和电子显微镜长度标尺。作者制作的图像。

在化学和材料研究中,电子显微镜(EM)已被证明对于理解新型纳米材料的组成和结构至关重要,其用途从氢燃料电池发电到微芯片制造。在生物医学领域,EM 也允许研究人员探测光学显微镜无法观察到的生物物体的特征,如蛋白质、DNA 结构或树突细胞。随着科学研究越来越关注长度尺度越来越小的物质世界,EM 作为纳米尺度物体的表征工具变得越来越有价值。

那么问题出在哪里?

尽管 EM 拥有令人惊叹的能力,但这并不意味着它没有问题。虽然 EM 可以在前所未有的长度范围内快速提供颗粒的定性分析,但对 EM 图像进行定量分析可能会变得繁琐而耗时。例如,考虑下图:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2 一组纳米颗粒的电子显微镜图像。来源: CNR-IOM ( 抄送)

要定量确定像图像中粒子的平均大小这样简单的事情,您必须首先手动标记所有的粒子(或者至少是它们的良好代表)。然后你必须计算每个粒子的像素数,从这个数中可以计算出每个粒子的平均面积(以像素为单位)。

一种常用于帮助定量分析 EM 图像的工具是 ImageJ 。ImageJ 是一个开源平台,使用 javascript 来帮助研究人员分析科学图像。有了它,研究人员可以使用多边形选择工具来定义图像的特定区域,然后单击一个按钮,就可以计算出定义区域内的像素数量。虽然这些工具很有帮助,但必须手动定义图像内的区域仍然妨碍了一次分析许多 EM 图像的高通量方法。

缺乏用于定量图像分析的自动化工具是因为传统上,很难将图像输入计算机,然后让计算机理解它在看什么。虽然一个人可以看到一幅图像并立即从背景中识别出前景物体,但计算机只能得到一个代表特定点颜色强度的像素值矩阵。这如图 3 所示,其中图像显示为像素值的三个堆叠 2D 阵列(或单个 3D 阵列)。第一 2D 阵列通常代表红色通道,而第二和第三阵列分别代表蓝色和绿色通道。正是这些 2D 阵列中的像素值之间的密集复杂关系层通知了前景对象与背景的分离。因此,开发一种方法来分割 EM 图像中的不同粒子(也可以很好地推广)不是一件容易的事。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3 表示图像的像素阵列。作者制作的图像。

作为解决方案的深度学习

幸运的是,并不是所有的希望都破灭了。深度学习是一类机器学习算法,近年来,它推动了计算机视觉的极限。通过使用基于深度学习的模型,研究人员已经能够在自动驾驶汽车、面部识别系统和 3D 模型构建等技术上取得突破。特别是,深度学习模型已经允许计算机不仅可以自动对图像进行分类,还可以将它们分割成不同的组成部分。

对于 EM 图像,深度学习可用于分割图像的组成部分,以便像素可被分类为背景像素或粒子像素。这在下面的图 5 中示出,白色表示背景像素,黑色表示粒子像素。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 5 纳米粒子的 EM 图像及其通过深度学习的分割。来源: CNR-IOM ( 抄送)

注意,深度学习模型将图像从范围从 0 到 255 的像素值的 3D 阵列(对于具有 8 位像素值的图像)转换为像素值为 0 或 1 的 2D 阵列。通过降低阵列的复杂性并对其进行限制,以便只保留最相关的特征,可以明确地进行更多的定量分析,而不用担心“噪声”。例如,如果我们希望量化 EM 图像中有多少像素被粒子占据,我们只需计算 1(粒子像素的值)在我们的分段 2D 阵列中出现的频率。

相比之下,如果我们使用一个更简单的方法,如二值化。对图 5 中的 EM 图像进行二值化的结果将产生如图 6 所示的图像。与使用深度学习来分割图像不同,在图 6 中简单地计算 1(黑色像素)的频率将导致非常不准确的分析。因此,需要更复杂的分析模式来获得准确的结果。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 6 使用二值化分割图 5 中的 EM 图像的结果。作者制作的图像。

尺寸分布的测量

现在我们有了一个很好的分割图像,我们想要进行什么样的定量分析呢?

量化 EM 图像以促进科学研究的最简单但也是最有效的方法之一是找出样品中颗粒的尺寸分布。例如,我们为了健康而服用的许多药物必须具有一定的尺寸,以便靶向特定的器官并穿透我们的细胞壁。同样,对于许多化学合成来说,反应物必须在一定的尺寸范围内,否则它们会太大而不能发生反应。

其他更复杂的过程也可能在纳米尺度上独特地依赖于尺寸。金属和金属氧化物纳米颗粒中的量子尺寸效应通常决定了它们的光子和光电性质。由于金属纳米粒子的大小与其吸收特定波长光的能力有着错综复杂的联系,研究人员经常会调整他们的光电设备(如太阳能电池、led、光纤激光器等)。)通过简单地改变这些设备中使用的纳米颗粒的尺寸。

然而,为了成功地调整所有这些有趣的属性,必须首先确定样本的大小。在上面的图 5 中,计算分割图像阵列中 1(粒子像素)的频率对于计算机程序来说是一项微不足道的任务。由于大多数 EM 图像也有相关的比例尺,因此可以通过简单地将该频率乘以像素到纳米/微米的转换比率来确定颗粒的面积。这将导致图 5 中纳米颗粒尺寸的定量测量。

然而,当图像中有许多粒子时,确定尺寸分布需要稍多的步骤。简单地计算图像中 1 的频率将会发现所有粒子的大小都是一个单独的斑点,而不是单个的实体。为了确保保持每个粒子的个性,您可以给图像中每个 1 的连接区域一个唯一的标签。这被称为连通分量标记。例如,考虑下面的 2D 阵列:

[ [ 1,0,0,1,1 ],
,【0,0,0,1,1】,

,【1,0,0,0,0】,
,【0,0,0,1,1】]

为 1 的每个连接区域分配一个唯一的标签将产生以下数组:

[[ 1,0,0,2,2 ],
,[ 0,0,0,2,2 ],
,[ 3,3,0,0,0 ],
,[ 3,0,0,0,0,0 ],
,[ 0,0,0,4,4 ] ]

获得大小分布仅仅是计算每个独特的标记在阵列中出现的频率。因此,在上面的例子中,粒子 1 具有一个像素的大小,粒子 2 具有四个像素的大小,粒子 3 具有三个像素的大小,粒子 4 具有两个像素的大小。

把所有的放在一起

为了给研究人员提供一种获得 EM 图像中纳米颗粒尺寸分布的定量测量方法,我利用深度学习开发了 SAEMI ,这是一款利用深度学习计算 EM 图像中纳米颗粒尺寸分布的 web 应用程序。

为了使用该应用程序,首先上传一张图像,随后调整其大小,以确保输入像素的数量与我用于训练深度学习模型的像素数量相匹配。这是使用双线性插值来完成的,双线性插值使用沿两个轴的线性插值来将一个网格值(在这种情况下是像素值)映射到另一个不同大小的网格值上。

一旦完成,图像就被输入到我的深度学习模型中,该模型是根据从 NFFA-欧洲拍摄的一组 EM 图像进行训练的。由于这个图像集只包含原始的 EM 图像(我们的 x 值),我不得不提供我自己的基本事实标签( y 值)。这是使用上述的 opencv-python 算法和 ImageJ 图像分析工具的组合来完成的。在下面的图 6 中,您可以看到一个原始 EM 图像及其对应的地面实况标签的示例。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 6 a)原始电磁图像。来源:CNR-IOM(CC-BY)b)EM 图像的地面真实标签。

经过我训练好的模型,结果是一个二元预测。这个二进制预测是由 0 和 1 组成的 2D 值数组,其中 0 表示背景像素,1 表示粒子像素。这种预测的一个例子如图 7 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 8 a)原始 SEM 图像。来源: CNR-IOM ( CC-BY ) b)通过深度学习模型馈入后的预测二值图像。金色代表像素值 0,紫色代表像素值 1

最后,通过计数 2D 阵列中 1 的每个连接区域并报告它们的频率来进行定量分析。大小分布随后显示为直方图,并可以下载为. csv 文件,以供研究人员在认为合适时进行进一步分析。下图 9 显示了图 8 所示 EM 图像的直方图。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 9 图 8 的纳米颗粒尺寸分布直方图。截图来自 SAEMI

所有这些计算,从调整图像大小到使用深度学习分割图像,再到确定颗粒尺寸分布,都是通过点击按钮和最少的输入来执行的。通过利用深度学习的能力,可以以高效和高通量的方式进行 EM 图像的定量分析。

在我系列的第 2 部分中,我将更详细地介绍如何使用该应用程序,在我系列的第 3 部分中,我将更深入地研究训练深度学习模型的过程,以及如何使用深度学习来分割图像。敬请关注,如果你碰巧参与了纳米尺度的研究,请查看我的应用,亲自测试一下。随时欢迎反馈!

SAEMI:电子显微镜图像的尺寸分析

原文:https://towardsdatascience.com/saemi-size-analysis-of-electron-microscopy-images-36c9f61d52ed?source=collection_archive---------54-----------------------

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

来源: CNR-IOM ( 抄送)

第三部分,训练电子显微镜的分割模型

本文是我的系列文章的第 3 部分,详细介绍了 SAEMI 的使用和开发,这是我创建的一个 web 应用程序,用于执行电子显微镜图像的高通量定量分析。你可以点击这里查看应用,点击这里查看其 github 。此外,请查看第 1 部分这里(我在这里谈论创建该应用程序背后的动机)和第 2 部分这里(我在这里给出如何使用该应用程序的演示)。在这篇文章中,我将讲述我如何训练一个深度学习模型来分割电子显微镜(EM)图像,以便对纳米颗粒进行定量分析。

选择数据集

为了训练我的模型,我使用了来自NFFA-欧洲的数据集,其中包含来自意大利的里雅斯特 CNR-IOM 的 20,000 多张扫描电子显微镜(SEM)图像。这是唯一一个我可以免费访问的大型 EM 图像数据库,正如你所想象的,大多数 EM 图像都有很强的版权问题。该数据库包括 20,000 多张 SEM 图像,分为 10 个不同的类别(生物、纤维、薄膜涂层表面、MEMS 器件和电极、纳米线、颗粒、图案化表面、多孔海绵、粉末、尖端)。然而,对于我的训练,我将训练图像限制为仅取自粒子类别,该类别包含不到 4000 张图像。

其中一个主要原因是,对于几乎所有其他类别,实际上没有办法从图像中获得有用的尺寸分布。例如,考虑图 1a 所示的纤维的 EM 图像。分割图像后,我可以计算图像中每根纤维的大小,但你也可以清楚地看到纤维延伸出图像。因此,我计算的尺寸仅限于电子显微镜图像,我无法单独从图像中提取纤维的长度。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1 a)来自数据集的纤维的 EM 图像 b)来自数据集的颗粒的 EM 图像。来源: CNR-IOM ( 抄送)

将其与图 1b 中颗粒的 EM 图像进行比较,其中图像中显示的尺寸显然是整个颗粒的尺寸。不仅如此,这一类别中的图像往往具有最小程度的遮挡,这使得标记和训练更加容易。

在粒子类别中,所有的图像都是 768 像素高,1024 像素宽。大多数粒子的形状大致为圆形,一些图像中有数百个粒子,而另一些图像中只有一个粒子。由于放大倍数的不同,像素中颗粒的尺寸也有很大差异,范围从 1 微米到 10 纳米。下图 2 显示了数据集中一些粒子图像的示例:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2 4 在数据集中发现的颗粒的电子显微镜图像。来源: CNR-IOM ( 抄送)

移除横幅

为了开始训练过程,首先必须对原始图像进行预处理。在很大程度上,这意味着删除包含图像元数据的横幅,同时保留尽可能多的有用图像数据。为了做到这一点,我使用了一种叫做“反射填充”的技术。实际上,我用顶部和底部的倒影替换了横幅区域。这方面的一个例子如图 3 所示,反射以红色突出显示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3 反射填充的例子。来源:CNR-IOM(CC-BY)

然而,为了执行这个转换,我首先必须使用 OpenCV 检测横幅区域。首先,我用我的图像创建了一个二进制蒙版,其中所有高于 250 的像素值都变成了 255,所有低于阈值的像素值都变成了 0。下面显示了用于执行此操作的代码以及图 4 中的结果。

import cv2# convert image to grayscale
grayscale = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)# create binary mask based on threshold of 250
ret, binary = cv2.threshold(gray, 250, 255, cv2.THRESH_BINARY)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 4 a)原始图像 b)二值化后的图像

虽然阈值处理在寻找横幅区域方面已经相当有效,但我仍然可以检测到图像中不属于横幅的一些元素。为了确保只检测到横幅区域,而没有其他区域残留,我在阈值图像上使用了腐蚀和膨胀来找到图像中垂直线和水平线的位置。侵蚀是一种图像处理技术,其中给定大小的核在图像上扫描。当内核沿着图像行进时,找到最小像素值,并且中心的像素值被该最小值替换。膨胀是一种类似的操作,但使用的是最大像素值。

下面显示了检测垂直线和水平线的代码,以及它们在图 5 中的结果。

# create tall thin kernel
verticle_kernel = cv2.getStructuringElement(
    cv2.MORPH_RECT, 
    (1, 13)
)# create short long kernel
horizontal_kernel = cv2.getStructuringElement(
    cv2.MORPH_RECT, 
    (13, 1)
) # detect vertical lines in the image
img_v = cv2.erode(thresh, verticle_kernel, iterations = 3)
img_v = cv2.dilate(img_v, verticle_kernel, iterations = 3)# detect horizontal lines in the image
img_h = cv2.erode(thresh, horizontal_kernel, iterations = 3)
img_h = cv2.dilate(img_h, horizontal_kernel, iterations = 3)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 5 a)垂直侵蚀/膨胀 b)水平侵蚀/膨胀

然后我把两个结果加在一起,并对反转的数组进行了最终的腐蚀+二值化。代码和结果如下面的图 6 所示。

# add the vertically eroded and horizontally eroded images
img_add = cv2.addWeighted(img_v, 0.5, img_h, 0.5, 0.0)# perform final erosion and binarization
img_final = cv2.erode(~img_add, kernel, iterations = 3)
ret, img_final = cv2.threshold(
    img_final, 
    128, 
    255, 
    cv2.THRESH_BINARY | cv2.THRESH_OTSU
)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 6 a)添加垂直腐蚀和水平腐蚀的图像 b)执行最终腐蚀和二值化

有了图 6b 所示的结果,我终于可以安全地检测到横幅并将其删除。首先,我找到了最终结果中像素值为 0 的地方,并将左上角和右下角的坐标放入单独的变量中。同样,我计算了横幅区域的高度和宽度。

import numpy as npbanner = np.argwhere(img_final == 0)# coordinates of the top left corner
banner_x1, banner_y1 = banner[0, 1], banner[0, 0]# coordinates of the bottom right corner
banner_x2, banner_y2 = banner[-1, 1], banner[-1, 0] # calculate width and height of banner
banner_width = banner_x2 - banner_x1
banner_height = banner_y2 - banner_y1

接下来,我用这些坐标和横幅的高度/宽度找到它的上下边缘的“倒影”。

# finding the reflection below the banner
bot_reflect = img[
    banner_y2:banner_y2 + banner_height // 2, 
    banner_x1:banner_x2, 
    :
]
bot_reflect = np.flipud(bot_reflect)# finding the reflection above the banner
top_reflect = img[
    banner_y1 - (banner_height - len(bot_reflect)):banner_y1,    
    banner_x1:banner_x2, 
    :
]
top_reflect = np.flipud(top_reflect)

我找到的区域在下面的图 7 中用绿色突出显示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 7 图像中用于反射填充的区域(以绿色突出显示)。来源: CNR-IOM ( 抄送)

最后,我把上面和下面的倒影连接起来,用倒影替换了横幅区域。

reflect_pad = np.concatenate((top_reflect, bot_reflect), axis = 0)
imgcopy = img.copy()
imgcopy[banner_y1:banner_y2, banner_x1:banner_x2] = reflect_pad

最终结果(imgcopy)如下面的图 8 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 8 反射填充的最终结果。来源: CNR-IOM ( CC-BY )

基本事实标签

移除横幅后,我使用 OpenCV 和 ImageJ 的组合创建地面真相标签。为了帮助创建地面真实标签,使用 OpenCV 使用了分水岭算法

分水岭算法是一种分割算法,它将图像视为地形表面,其中高亮度像素值为丘陵,低亮度值为山谷。该算法背后的想法是,你用水填充“山谷”,随着水位上升,你沿着水汇合的边缘建造屏障。这些障碍代表了图像的分割边界。

虽然分水岭算法非常有用,但它也往往对噪声非常敏感。通常,您需要对图像执行更多的预处理,如阈值处理和 T2 距离变换,以获得良好的分割效果。这使得基于分水岭的分割算法难以很好地推广到各种图像。下面的图 9 显示了一个应用分水岭算法(带有一些预处理步骤)的例子。

# remove noise using a mean filter
shifted = cv2.pyrMeanShiftFiltering(imgcopy, 21, 51)# threshold the image 
graycopy = cv2.cvtColor(shifted, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(
    graycopy, 
    0, 
    255, 
    cv2.THRESH_BINARY | cv2.THRESH_OTSU
)

# apply adistance transform and find the local maxima
dt = cv2.distanceTransform(thresh, 2, 3)
localmax = peak_local_max(
    dt, 
    indices = False, 
    min_distance = min_distance_bt_peaks, 
    labels = thresh
) # apply the watershed algorithm
markers, num_seg = label(localmax, structure = np.ones((3, 3)))
labels = watershed(-dt, markers, mask = thresh)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 9 使用分水岭算法的结果

如上所述,分水岭算法本身不足以创建地面真实标签。然而,作为第一遍近似,它的表现并不太差。因此,我决定使用上面的代码,首先为尽可能多的图像提供标签,然后用 ImageJ 再次检查标签,以清理标签,使它们更准确地匹配图像。

ImageJ 是一个用 Java 编写的图像处理和分析工具,专为科学图像设计。下面的图 10 显示了 ImageJ 菜单的一个例子。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 10 图像 j 菜单

我使用菜单左侧的五个选择工具,用鼠标选择图像的区域,然后选择:编辑→选择→创建遮罩,以清理从分水岭算法创建的第一遍地面真实标签。为了标记尽可能多的图像,我试图限制标记,以便只要图像中有足够多的粒子可以给出尺寸分布的良好近似,地面真实标记是可以接受的。这方面的一些例子如图 9 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 9 不充分的事实标签。来源: CNR-IOM ( CC-BY )

起初,我用这种方法标记了大约 750 张图片。然而,我在训练中很快发现,使用这种质量的标签并不能很好地训练,我不得不更加谨慎地用 ImageJ 对它们进行第二次检查。这一次,我尽可能确保图像中的每一个粒子都以接近像素的精确度被标记出来。

为了确保数据质量,地面实况标签覆盖了相应的图像,并用肉眼检查,以确定它们相互反映的程度。然后,继续使用 ImageJ 的选择工具,我会用放大 200-300 倍的覆盖图来修饰标签。这些新的基本事实标签的例子如图 10 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 10 最终地面真相标签。来源: CNR-IOM ( CC-BY )

总共有大约 250 张图片被第二次完全贴上标签。这些被放在单独的目录中,一个目录用于原始图像(从现在开始称为path_img),另一个目录用于地面真相标签(从现在开始称为path_lbl)。虽然这是一个比以前小得多的数据集,但由于影像和地面实况标签之间不存在不准确性,因此在跟踪训练损失时,它表现出了更好的性能。

创建数据集群

一旦创建了足够多的地面真相标签来形成我的训练集,我就开始训练我的深度学习模型。为了做到这一点,我使用了 fastai ,这是一个基于 PyTorch 构建的强大库,可以使使用深度学习的培训更加直观和简化。第一步是确保我的训练集以适合 fastai 框架的方式组织。为此,需要将训练数据和基础事实标签放入一个 DataBunch 对象中。我将展示下面的代码,并在下面的段落中更详细地解释它。

# use smaller image sizes for to deal with memory issues
size = (192, 256)# batch size
bs = 24# create DataBunch
data = (SegmentationItemList.from_folder(path_img)
        .split_by_rand_pct(0.2)
        .label_from_func(lambda x: path_lbl/f'{x.stem}.png')
        .transform(
              get_transforms(flip_vert=True, max_warp=None),    
              tfm_y=True, 
              size=size
         )
         .databunch(bs=bs)
         .normalize())

这段代码的主要部分从“创建数据集中”部分开始,在这里变量data变成了数据集中对象。它首先从path_img目录中收集原始图像,并将数据分成训练集和验证集。这是在.split_by_rand_pct(0.2)行中完成的,其中随机选择 20%的数据作为验证集,以防止训练集过度拟合。

每个图像的相应基本事实标签(即损失函数中使用的 y 值)然后在.label_from_func中收集。这将一个函数应用于在path_img中收集的每一项,并将所有输出放入一个列表中。我应用的函数lambda x: path_lbl/f'{x.stem}.png'获取图像文件名,并在path_lbl目录中寻找匹配的文件名(扩展名为. png)。

然后将一系列图像变换应用于训练和验证集,作为数据扩充的一种形式。我使用的变换包括(但不限于)翻转任一轴上的图像,旋转图像和放大图像。关于我使用的转换的完整列表,请点击这里的。请注意,我还应用了tfm_y=True,这意味着我们应用于原始图像的任何变换也会应用于相应的标签。这对于进行图像分割时的训练至关重要。

此外,对所有图像应用了调整大小转换,确保它们都是 192x256 像素。这个转换应用于所有图像的原因很不幸,因为如果图像再大一点,我在训练它们的时候就会耗尽 CUDA 内存。出于类似的原因,用于训练的批量大小也是一次只有 24 个图像。

最后,像素值在结束时都被归一化,以防止大的异常值影响训练过程。

骰子系数

训练前的最后一步是确定一个度量标准,以确定模型的表现如何。用于图像分割模型的一个常见度量是骰子系数。它与 F1 的分数相同,尽管表达方式略有不同。

骰子系数通过以下公式计算:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

其中 TP 是真阳性,FP 是假阳性,FN 是假阴性。在这种情况下,真正值指的是由预测确定的所有正值,这些值也出现在基础真值标签中的相同位置。例如,如果 7x7 图像的基本事实标签是:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

预测是:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

真阳性的数量将是 11 ie。有 11 个出现在预测和基本事实标签的相同位置。假阳性的数量将是 1(即在预测中出现一个不存在于基本事实标签中的 1)并且假阴性的数量是 2(即两个 1 存在于预测没有获得的基本事实标签中)。骰子系数为:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

培训— U-Net

既然已经创建了 DataBunch 对象并选择了度量标准,我终于可以开始训练了。用于图像分割任务的常见架构是 U-Net 架构。U-Net 主要由两个主要部分组成,下采样路径(也称为编码器)和上采样路径(也称为解码器)。它具有自动编码器的许多功能,但在编码和解码部分之间明显增加了跳跃连接。稍后我将解释跳过连接,但首先,让我们讨论它作为自动编码器的属性。下面的图 9 是取自原始 U-Net 论文的 U-Net 架构的一个例子。你可以在这里罚款原纸

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 9 U-Net 架构。来源:https://arxiv.org/abs/1505.04597

网络的左半部分是标准的 CNN,具有卷积层,后面是 ReLU 和 max pool 层。这是一个经典的 CNN,通常用于图像分类问题,目标是输入图像像素值并输出单个分类。然而,在图像分割中,目标是对图像中的每个像素进行分类。因此,您的最终张量应该与原始图像的大小和形状相同(在我们的例子中,为 192x256)。这是通过在架构的上采样部分使用上卷积层来实现的。

您可以将向上卷积(也称为转置卷积)视为在像素值之间额外填充零的卷积。图 10 显示了一个例子。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 10 a)用于上采样的输入 b)在像素值之间用零填充的输入 c)与 3×3 内核卷积后的输出矩阵

请注意,填充后,执行卷积会产生比输入维数更大的输出。这使得我们最终的输出与输入的大小和形状相同。

现在让我们来谈谈跳过连接。回到图 9,注意连接从下采样路径到上采样路径的层的灰色箭头。这些被称为跳过连接,它们将下采样层的输出连接到相应的上采样层。这些跳跃连接的目的是允许将在下采样路径中学习到的特征和空间信息传输到上采样路径。这允许上采样路径在执行分段时记住“在哪里”以及“是什么”。

幸运的是,fastai 库提供了一个函数,可以在一行代码中创建整个架构。

learn = unet_learner(data, models.resnet18, metrics=dice)

在我的训练中,我使用 resnet18 CNN 作为下采样路径。data参数是我们之前创建的 DataBunch,而用于评估模型的指标是 Dice 指标(如果您不知道 Dice 指标是什么,我会在讨论如何确定模型的有效性时详细介绍)。

然后,通过整个 U-Net 后的最终输出是一个二进制遮罩,其像素值被分类为背景像素(0)或粒子像素(1)。

培训—超参数调整

当训练一个模型时,你总是要经历大量的超参数调整。最重要的超参数之一是学习率,这是获得正确结果的关键。幸运的是,fastai 提供了许多资源,使得优化学习率变得更加容易。

创建 U-Net 架构后,进行了模拟训练,使用从 1e-7 到 10 的各种学习率计算损失。这是使用 fastai 库中的 LR 查找器完成的。结果如图 11 所示。

lr_find(learn)
learn.recorder.plot()

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 11 学习率探测器图

确定学习率时,通常希望在曲线达到最小值之前选择一个值。但是,请记住,这只是一个起点,您可能仍然需要根据训练过程调整学习速度。例如,如果您的损失在后续迭代后开始增加,您可能仍然需要选择较低的学习速率。用于训练模型的最终学习速率是 2e-5 和 5e-5 之间的循环学习速率

另一个要考虑的超参数是冻结或解冻图层组的程度。当通过 fastai 创建 U-Net 时,权重参数已经在 ImageNet 数据库中进行了训练。这背后的想法是,由于许多图像通常彼此相关,使用一个数据集确定的权重通常可以转移到另一个数据集,并获得相当好的结果。这允许您使用较少数量的图像来训练相对较好的模型。在我的训练中,我冻结了三层组中的前两层。

learn.unfreeze()
learn.freeze_to(2)

最后,要确定的最后一个超参数是时期数和重量衰减。在多次测试和实验之后,发现这些超参数的最佳值是 50 个时期和 0.1 的重量衰减。

learn.fit_one_cycle(50, slice(2e-5, 5e-5), wd=1e-1)

图 12 显示了培训期间培训和验证损失的图表。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 12 培训期间的培训和验证损失

估价

最后一步是评估模型的有效性。训练之后,创建了最终的第三个测试集,该测试集根本不参与训练。Dice 系数是使用该测试集上的预测和基本事实标签计算的,发现为 97.5%。

此外,最终预测与原始图像重叠,并通过肉眼检查,以确定模型在给定 EM 图像上的表现如何。下面的图 13 显示了这样一个例子:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 13 预测覆盖在原始图像上。截图来自 SAEMI

虽然该模型在预测粒子的位置方面做得很好,但当粒子在图像中非常接近时,它很难分离粒子。目前,该应用程序允许您执行一些后处理,以避免这一问题,但在未来,我想尝试通过在更平衡的数据集上训练实例分割模型来改进我的模型,同时增加训练图像的数量。

摘要

由从 NFFA-欧洲获得的 SEM 图像组成的训练集,并进行相应的处理以获得良好的数据。地面实况标签是使用 OpenCV 和 ImageJ 的组合制作的。通过利用 U-Net 和 Dice 度量,使用 fastai 库进行训练。由单独的测试组确定的最终骰子得分为 97.5%。

如果你想了解更多关于这个培训背后的动机,请点击这里查看我的系列的第一部分。如果你想了解如何使用我创建的部署这个模型的应用程序,请查看我的系列文章第 2 部分这里。非常感谢你阅读这篇文章,我希望你能从中获得许多有价值的信息!

基于Python的天气预测和天气可视化项目源码+文档说明(高分毕设/大作业),个人经导师指导并认可通过的高分设计项目,评审分99分,代码完整确保可以运行,小白也可以亲自搞定,主要针对计算机相关专业的正在做大作业的学生和需要项目实战练习的学习者,可作为毕业设计、课程设计、期末大作业,代码资料完整,下载可用。 基于Python的天气预测和天气可视化项目源码+文档说明(高分毕设/大作业)基于Python的天气预测和天气可视化项目源码+文档说明(高分毕设/大作业)基于Python的天气预测和天气可视化项目源码+文档说明(高分毕设/大作业)基于Python的天气预测和天气可视化项目源码+文档说明(高分毕设/大作业)基于Python的天气预测和天气可视化项目源码+文档说明(高分毕设/大作业)基于Python的天气预测和天气可视化项目源码+文档说明(高分毕设/大作业)基于Python的天气预测和天气可视化项目源码+文档说明(高分毕设/大作业)基于Python的天气预测和天气可视化项目源码+文档说明(高分毕设/大作业)基于Python的天气预测和天气可视化项目源码+文档说明(高分毕设/大作业)基于Python的天气预测和天气可视化项目源码+文档说明(高分毕设/大作业)基于Python的天气预测和天气可视化项目源码+文档说明(高分毕设/大作业)基于Python的天气预测和天气可视化项目源码+文档说明(高分毕设/大作业)基于Python的天气预测和天气可视化项目源码+文档说明(高分毕设/大作业)基于Python的天气预测和天气可视化项目源码+文档说明(高分毕设/大作业)基于Python的天气预测和天气可视化项目源码+文档说明(高分毕设/大作业)基于Python的天气预测和天气可视化项目源码+文档说明(高分毕设/大作业)基于Python的天气预测和天气可视化项目源码+文档说明(高分毕设/大作业
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值