TowardsDataScience-2023-博客中文翻译-二十三-

TowardsDataScience 2023 博客中文翻译(二十三)

原文:TowardsDataScience

协议:CC BY-NC-SA 4.0

如何最佳利用 OpenAI 的 Evals 框架

原文:towardsdatascience.com/how-to-best-leverage-openais-evals-framework-c38bcef0ec47?source=collection_archive---------2-----------------------#2023-05-23

图片由作者授权用于商业用途

使用 OpenAI Evals 评估 LLM 的关键

Aparna DhinakaranTowards Data Science Aparna Dhinakaran

·

关注 发布于 数据科学前沿 ·6 分钟阅读·2023 年 5 月 23 日

--

本博客由 Trevor LaViale 共同撰写

根据近期的一项调查,近一半(43%)的数据科学和工程团队计划在未来一年内部署大型语言模型(LLMs)。LLMs 的时代确实已经来临;然而,评估这些模型往往具有挑战性,研究人员需要开发可靠的方法来比较不同模型的性能。

几个月前,OpenAI 开源了他们的框架,用于根据一系列基准评估 LLM。这一框架在 OpenAI 内部使用,以确保他们的新版本模型表现适当。OpenAI 的 Eval 框架是一个工具,旨在帮助研究人员和从业者评估他们的 LLM,并将其与其他先进的模型进行比较。

Eval 框架是如何工作的?

现在你可能会想,“哇,这似乎是一个评估 LLM 的非常有用的工具,但什么是评估,我该如何使用它?”让我们深入了解细节吧!

“评估”指的是用于衡量语言模型在特定领域(例如问题回答或情感分析)表现的具体任务。这些评估通常是标准化的基准,用于比较不同的语言模型。Eval 框架提供了一个标准化的接口,用于运行这些评估并收集结果。

从本质上讲,评估是一个数据集和一个在 YAML 文件中定义的评估类。下面展示了一个评估示例(该示例取自 Github 存储库):

让我们解析一下以上内容的意思:

  • test-match: 这是评估的名称

  • id: 这是 test-match 的别名的评估全名

  • description: 评估的描述。

  • metrics: 评估的指标。

  • class: 指定评估的模块/类路径。

  • args: 任何你想传递给类构造函数的内容。

  • samples_jsonl: 指向样本所在的位置,在本例中是 test_match/samples.jsonl

为了让你了解样本的样子,这些是 test_match/samples.jsonl 文件中的样本:

在 JSONL 文件中(每行一个独特的 JSON 对象的 JSON 文件),我们可以看到评估的样本。每个 JSON 对象代表模型需要完成的任务,并且在评估中计作 1 个数据点。有关 JSONL 文件的更多示例,可以访问 registry/data/README.md 在 Eval Github 存储库中。

在下面的部分中,我们将介绍如何运行 test-match 评估。

我如何运行评估?

我们可以通过一个简单的命令来运行上述评估:

在这里我们使用 oaieval CLI 来运行这个评估。我们指定了完成函数的名称(gpt-3.5-turbo)和评估的名称(test-match)。就这么简单!我们将在下面的部分中更深入地探讨完成函数和如何构建你的评估。 运行该命令后,你将看到精度的最终报告打印到控制台,以及一个包含完整报告的临时文件的文件路径。这表明使用这个框架快速评估 LLM 是多么简单。接下来,让我们学习如何构建自己的评估,而不是使用已经在注册表中的评估。

我如何构建自己的评估?

在本节中,我们将介绍如何从现有模板构建评估,并解释完成函数以及如何构建自己的函数。

构建评估

构建样本

在这里,我们将介绍如何使用现有模板来加快工作速度来构建自定义评估。(如果你想构建一个完全自定义的评估,这里有一个 README来自 Eval GitHub 存储库。)

构建评估的第一步是构造样本。样本需要根据你选择的模板包含某些字段。每个样本需要包含一个“input”字段,代表提示,建议以聊天格式指定。其他字段取决于你选择的评估模板。例如,假设我们使用Match模板。在这种情况下,我需要在聊天格式中指定“input”字段和“ideal”字段。它可能如下所示:

这告诉系统尽可能简洁地完成短语,我们提供的短语是“ABC AI 是一家专注于 ML 的公司”,预期的答案是“可观测性”。

如果你的样本在不同于 JSONL 的文件格式中,OpenAI 提供了一个 CLI 来将这些样本转换为 JSONL 文件。你可以使用以下代码由评估存储库提供来完成这个任务:

很好,我们的样本在一个 JSONL 文件中!下一步是注册我们的评估。

注册你的评估

要注册评估,我们需要将一个文件添加到evals/registry/evals/<eval_name>.yaml。该文件的格式与上面的示例test-match评估格式相同。它需要包含评估名称、ID、可选描述、指标、类别和指定样本文件位置的参数。一旦我们注册了评估,就可以像运行 test-match 评估一样运行它。这就是设置你自己的评估所需的全部内容!

构建完成函数

什么是完成函数?

如何运行评估? 部分,我们简要提到你需要指定一个完成函数来运行 oaieval 命令。首先,让我们了解什么是完成。完成是模型对提示的输出。例如,如果我们给模型的提示是“告诉我加州的首府是什么”,我们期望完成是“萨克拉门托”。然而,有些提示可能需要访问互联网或其他操作来帮助模型准确回答问题,这就是完成函数的作用。完成函数允许你定义模型可能需要执行的这些操作。oaieval 命令中的完成函数参数可以是 CompletionFn URLs,或者是 OpenAI API 中模型的名称或注册表中的键。有关完成函数的更多信息,请参见 此处

我如何构建自己的完成函数?

在这一部分,我们将讨论如何构建自己的完成函数。为了使你的完成函数与所有评估兼容,它需要实现几个接口。这些接口基本上只是标准化评估的输入和输出。如果你想了解更多有关这些接口的信息,请查看 有关完成函数协议的文档

一旦你的完成函数实现完毕,你需要像注册我们的评估一样注册它。注册完成函数使其可以在 oaieval CLI 中使用。下面是一个来自 Evals 仓库 的注册示例:

让我们详细解析以上内容:

cot/gpt-3.5-turbo:这是 oaieval 将使用的完成函数的全名

class:这是完成函数实现的类路径

args:初始化时传递给你的完成函数的参数

-> cot_completion_fn:这是传递给 ChainOfThoughtCompletionFn 类的参数

使用 Eval 框架的优势

Eval 框架为研究人员和从业者提供了若干好处。

标准化评估指标和基准:Eval 框架提供了一套标准化的评估指标,研究人员可以用来比较他们模型的性能。这使得研究人员能够将他们的模型与其他最先进的模型在相同的基准上进行比较。

易于使用:Eval 框架旨在易于使用。你可以使用现有的模板快速构建自己的评估,并仅需几行代码即可开始使用,如上所示。

灵活:Eval 框架具有灵活性,可以用于评估模型在广泛的任务和不同基准上的表现。

开源:Eval Framework 是开源的,这意味着研究人员/从业者可以根据他们的特定需求使用和修改它。此外,任何人都可以为openai/evals Github 仓库做贡献,这将有助于众包更多的基准测试,供社区共享。

结论

OpenEvals 是一个强大的工具,用于评估 LLMs,为研究人员和从业者提供了一套标准化的评估指标和任务,以将他们的模型与其他最先进的模型进行比较。考虑到它的好处,这个框架很可能在未来成为评估和比较模型性能的有用工具,因为 LLMs 将不断改进。祝评估愉快!

如何将自定义 ML 模型引入 OpenMetadata

原文:towardsdatascience.com/how-to-bring-custom-ml-models-into-openmetadata-969311a16d91?source=collection_archive---------10-----------------------#2023-01-09

构建自定义 CICD 管道,以便将您的 ML 资产展现出来

Pere Miquel BrullTowards Data Science Pere Miquel Brull

·

关注 发表在 Towards Data Science ·5 min read·2023 年 1 月 9 日

--

OpenMetadata 不仅仅是一个数据目录。基于标准定义和 API,这个目录只是众多利用您平台的元数据的应用程序之一。从一开始,OpenMetadata 的目标就是解决行业中的元数据问题。团队无需解决诸如元数据摄取或如何将协作带回数据中的关键组件,可以专注于改进他们的流程和自动化。

本文旨在展示我们如何集成多个元数据源,无论是来自现有服务还是内部解决方案。由于每个操作都是通过 API 驱动的,来自 Postgres 等特定连接器的元数据与通过Python SDK发送的元数据没有区别。这种高灵活性使我们能够探索来自自定义构建的机器学习模型及其特征源的元数据。

Postgres 和自定义机器学习模型摄取模式。图片来源于作者。

如果这听起来有趣,请按照这个仓库中的材料进行操作。

OpenMetadata 与机器学习

机器学习模型生命周期的主要挑战之一是弥合机器学习模型与数据平台之间的差距。我们有助于训练、测试、调整和部署机器学习的工具,但这些工具很少将机器学习模型放在其存在的平台的背景下。

如何将所有部分整合在一起的信息通常由数据科学家或机器学习工程师掌握,但很少共享。典型原因有:

  1. 没有通用的方法来定义和维护元数据。

  2. 没有中央位置来发布结果供用户探索。

  3. 这种缺乏明确性以及前两个任务所涉及的工作使得很难证明好处并衡量影响

在本演示中,我们将跟随一个用例,其中:

  1. 我们有一个使用 Postgres 特征的机器学习模型,

  2. 模型会定期更新和部署,

  3. 模型的文档作为代码托管,

  4. 我们将使用 OpenMetadata 的 Python SDK 创建机器学习模型资产并将其推送到 OpenMetadata 中。

将我们的模型纳入 OpenMetadata 可以帮助我们分享文档,跟踪元数据的变化和版本控制,发现源的血缘关系,推动讨论和合作……从将这种整体视角引入机器学习和人工智能资产中获得的一些快速成果包括:

  • 团队可以快速开始协作,而不是尝试以不同的方式达到类似的结果,

  • 收集最常用特征的知识,以开始构建具有最高价值的特征存储,服务于整个组织。

  • 机器学习团队可以直接在 OpenMetadata 中开始构建数据质量测试和警报,以防止特征漂移和性能下降。

摄取 Postgres 元数据

第一步将是获取 Postgres 元数据,因为我们在这里有机器学习特征的来源。你可以按照这些步骤配置和部署 Postgres 元数据摄取。

在 OpenMetadata 中创建 Postgres 服务。图片来源于作者。

OpenMetadata 用户界面将引导我们完成两个主要步骤:

  1. 创建数据库服务:服务表示我们要摄取的源系统。在这里我们将定义与 Postgres 的连接,这个服务将持有将发送到 OpenMetadata 的资产:数据库、模式和表。

  2. 创建和部署数据摄取管道:这些管道由 OpenMetadata 使用摄取框架内部处理,该 Python 库包含连接到多个源、将其原始元数据转换为 OpenMetadata 标准并通过API发送到服务器的逻辑。

在 OpenMetadata 中管理数据摄取管道。图像由作者提供。

有趣的是,摄取框架包可以直接用于配置和托管摄取流程。此外,UI 或摄取框架中的任何操作完全开放,并得到服务器 API 的支持。这意味着任何与元数据相关的活动都可以实现全面自动化,这可以通过 REST 或OpenMetadata SDKs直接实现。

这些是我们接下来在创建 CICD 流程时将利用的功能。

构建 CICD 管道

在上述讨论中,我们强调了通常成为维护更新的机器学习模型元数据阻碍的两个问题:没有通用的元数据定义和没有一个地方来发布它。幸运的是,OpenMetadata 解决了这两个问题。

构建成功流程的缺失环节?它应该是简单维护和演变的。这就是为什么我们将示例基于一个YAML 文件,该文件在代码库中检查出来。因此,数据科学家和机器学习工程师可以依靠他们的部署管道来更新其最新生产模型的元数据。

包含机器学习模型元数据的示例 YAML 文件。图像由作者提供。

CICD 流程将有一个特定步骤:

  1. 阅读包含元数据的 YAML 文件,

  2. 将 YAML 的结构转换为ML 模型实体定义,以符合 OpenMetadata 标准,

  3. 使用 Python SDK 将机器学习模型资产推送到 OpenMetadata。

这里你会找到这样的管道示例。希望这能帮助你开始将你的机器学习资产纳入视野!

示例 CICD 管道。GIF 由作者提供。

脚本运行完成后,我们将在 OpenMetadata 中看到我们的收入预测模型:

OpenMetadata 中的收入预测模型。图像由作者提供。

在平台上可用的一个关键好处是能够查看我们模型与包含特征的数据源之间的血统信息。在我们的示例中,我们已经获取了 Postgres 元数据。然后,如果我们查看血统标签页,我们将能够看到我们所有模型的依赖关系。

收入预测的血统与 Postgres 表格。作者提供的图片。

总结

在这篇文章中,我们:

  • 讨论了行业对定义、获取和利用元数据的共同方法的需求,以及 OpenMetadata 如何满足这些需求。

  • 从≈ UI 直接获取了 Postgres 元数据。

  • 构建了一个 CICD 过程,在发布过程中推送自定义构建的 ML 模型元数据。

将 ML 模型置于上下文中具有重要的好处,比如探索依赖关系和促进协作。如果你需要一种简单的方法来展示你的 ML 资产,OpenMetadata 可以满足你的需求。

如何构建一个五层数据栈

原文:towardsdatascience.com/how-to-build-a-5-layer-data-stack-508ed09711f2?source=collection_archive---------4-----------------------#2023-07-21

构建一个数据平台并不一定要复杂。以下是推动数据产品大规模采用的五个必备层级。

Barr MosesTowards Data Science Barr Moses

·

关注 发布于 Towards Data Science · 10 min read · Jul 21, 2023

--

图片由作者提供。希望它不会让你的眼睛流泪。

像豆酱和巨魔一样,层级是现代数据栈的构建模块。

它强大的工具组件选择结合在一起,创建了一个同步且可扩展的数据平台,每一层都服务于数据管道的独特功能。

然而,与巨魔不同,云数据平台并不是一个童话故事。新的工具和集成几乎每天都在创建,以增强和提升它。

因此,在无限扩展的集成和每个数据流动功能和特性都可以新增层次的机会面前,问题就来了——你从哪里开始?或者换句话说,你如何提供一个能够为利益相关者带来真正价值的数据平台,同时又不使平台变得过于复杂或昂贵?

对于那些首次构建云原生平台的小型数据团队以及第一次从本地系统迁移的团队来说,偏重那些对业务结果有最直接影响的层次至关重要。

在本文中,我们将向你介绍五层数据栈——一个由五个关键工具组成的平台开发模型,这些工具不仅能帮助你最大化影响力,还能让你随着组织需求的增长而发展。这些工具包括:

  • 云存储和计算

  • 数据转换

  • 商业智能

  • 数据可观察性

  • 和编排

我们不会再提及食人魔或豆子蘸酱了。

让我们深入了解一下。(内容,不是豆子蘸酱。好吧,那真的最后一次了。)

云存储和计算

无论你是在堆叠数据工具还是煎饼,总是从底部开始构建。像任何好的栈一样,适当的基础对确保数据平台的结构和功能完整性至关重要。

在你为利益相关者建模数据之前,你需要一个收集和存储数据的地方。你的栈的第一层通常属于以下三类之一:处理以结构化数据为主的数据仓库解决方案,如Snowflake;专注于大量非结构化数据的数据湖;以及结合了两者元素的混合解决方案,如Databricks’湖屋。

然而,这不仅仅是你存储数据的地方——它也是激活数据的力量。在云数据栈中,你的存储解决方案是其他层次平台的主要计算力量来源。(阅读更多关于何时将存储和计算耦合或解耦的信息,请参考我的同事 Shane 的文章。)

现在,我可以深入探讨数据仓库、数据湖、湖屋以及介于两者之间的所有优缺点,但这并不是最重要的。重要的是你选择一个既满足当前需求又能适应未来需求的解决方案,同时这个方案的资源成本要适合你的财务团队。它还将决定你将来能连接哪些工具和解决方案,以便为新使用场景微调你的数据栈。

你需要的具体存储和计算解决方案将完全取决于你的业务需求和使用场景,但我们的推荐是选择一些常见的解决方案——如 Snowflake、Databricks、BigQuery 等——这些方案有良好的支持、集成度高且易于扩展。

开源始终是一个诱人的解决方案,但除非你已经达到实际需要它的规模,否则它可能会在存储和计算层面带来一些重大挑战。相信我们,选择一个托管的存储和计算解决方案可以在未来节省大量麻烦——以及可能的痛苦迁移。

数据转换

好的,那么你的数据需要在云中存在。这很有道理。你的数据平台还需要什么?让我们来看一下五层数据栈的第二层——转换。

当数据首次被摄取时,它以各种有趣的形状和大小出现。不同的格式。不同的结构。不同的值。简单来说,数据转换指的是将所有这些不同格式的数据转换成一致且对建模有用的东西的过程。

图片由作者提供。

传统上,数据转换是一个手动过程,需要数据工程师在 CLI 中逐一手动编码每个管道。

然而,最近,云转换工具已经开始让数据建模过程民主化。为了使数据管道对从业者更具可访问性,像 dbt LabsPreqlDataform 这样的自动化数据管道工具允许用户无需编写任何代码即可创建有效的模型。

像 dbt 这样的工具依赖于被称为“模块化 SQL”的技术,从常见的、预先编写并优化的即插即用 SQL 代码块中构建管道。

当你开始云数据之旅时,你会迅速发现新的数据建模方法和为数据消费者提供价值的途径。你会收到来自财务和市场营销的新仪表盘请求。你会发现需要引入现有模型的新数据源。机会会迅速涌现。

像数据栈的许多层一样,自己编写转换可以在小规模下工作。不幸的是,当你开始扩展时,手动编写转换很快会成为你数据平台成功的瓶颈。投资现成的操作化工具通常是保持竞争力并继续在各领域提供新价值所必需的。

但不仅仅是编写你的转换会变得繁琐。即使你能编码足够的转换以覆盖你的扩展用例,如果这些转换发生故障会怎样?修复一个损坏的模型可能不算大问题,但修复 100 个模型就几乎不可能了(明显的双关语)。

提高扩展组织的时间价值

像 dbt 这样的转换工具使得为扩展工程和实践团队创建和管理复杂模型变得更快、更可靠。与一般限于数据工程师的手动 SQL 编码不同,dbt 的模块化 SQL 使任何熟悉 SQL 的人都能创建自己的数据管道。这意味着对忙碌团队来说,能够更快地实现价值,减少工程负担,并且在某些情况下,减少对专业知识的需求,以推动平台向前发展。

灵活性来实验转换顺序

自动化云转换层还允许在管道的不同阶段进行数据转换,提供了在平台演变过程中实验 ETL、ELT 及其间所有内容的灵活性。

启用自助服务能力

最后,一个运作良好的转换工具将为未来实现完全自助服务架构铺平道路——如果你选择走这条路

商业智能(BI)

如果转换是第二层,那么商业智能必须是第三层。

在数据平台工具的上下文中,商业智能指的是我们向最终用户提供的分析能力,以满足特定用例。虽然我们的数据可能为一些外部产品提供支持,但商业智能功能是大多数团队的主要数据产品。

尽管像 LookerTableau 和各种 开源工具 在复杂性、易用性和功能集上可能差异很大,但这些工具始终具有帮助数据用户通过 可视化 揭示洞察的能力。

这点比较显而易见,因为虽然堆栈中的其他一切都是达到目标的手段,商业智能通常就是目标本身。

商业智能通常是数据堆栈核心的可消费产品,它是任何云数据平台的一个重要价值驱动因素。随着公司对数据创建和消费的需求增长,访问数据的速度和便捷性也将随之增长。

商业智能工具使得你的利益相关者能够从数据平台中获得价值。如果没有激活和消费数据的方法,无论数据平台有多少层,都没有存在的必要。

数据可观察性

平均而言,数据工程团队每周大约花费两天时间处理坏数据。事实上,根据 Gartner 最近的一项调查,坏数据每年给组织带来的成本平均为 1290 万美元。为了降低这些财务风险并保护平台的完整性,你需要第四层:数据可观察性。

在数据可观察性出现之前,发现数据质量问题的最常见方法之一是通过手动 SQL 测试。开源数据测试工具如 Great Expectations 和 dbt 使数据工程师能够验证组织对数据的假设,并编写逻辑来防止问题向下游传播。

图片由作者提供。

数据可观察性平台使用机器学习而非手动编码来自动生成针对新鲜度、体积、模式和空值率等方面的质量检查,覆盖你所有的生产表。除了全面的质量覆盖,一个好的数据可观察性解决方案还会生成表级和列级的血缘关系,帮助团队快速识别发生故障的位置以及根据上下游依赖关系所受到的影响。

数据平台的价值——以及由此衍生的产品——与输入数据的质量密不可分。垃圾进,垃圾出。(或者如果你的数据摄取任务出现故障,则什么都没有输出。)要拥有可靠、可操作和有用的数据产品,基础数据必须是可信的。如果你不能信任数据,你就不能信任数据产品。

不幸的是,随着数据的增长,你的数据质量问题也会随之增加。你的平台越复杂,摄取的来源越多,支持的团队越多——你遇到的质量问题就越多。随着团队越来越多地利用数据来驱动 AI 模型和机器学习用例,确保数据的可信度和可靠性的需求也呈指数级增长。

虽然数据测试可以提供一定的质量覆盖,但其功能仅限于已知问题和特定表。而且由于每个检查的手动测试都需要手动编码,因此可扩展性仅与可用的工程资源成正比。另一方面,数据可观察性提供了自动跨每个表的即插即用覆盖,因此你会在数据质量事件——无论是已知还是未知——对下游消费者产生影响之前收到警报。随着平台和数据的扩展,你的质量覆盖也会随之扩展。

此外,数据可观测性提供了从端到端的来源追溯,直到 BI 层,使得实际查找原因和解决质量事件成为可能。这可以为你的数据团队节省数小时的时间。虽然手动测试可能能够发现部分质量事件,但它无助于解决这些事件。当你意识到数据团队的解决时间几乎翻了一番时,这更令人担忧。

与反应性的数据测试不同,数据可观测性提供了对已知未知问题的主动可见性,实时记录你的数据管道源头,以促进数据平台的成长——这一切都不会牺牲团队的时间或资源。

数据编排

当你提取和处理数据以进行分析时,操作的顺序很重要。正如我们已经看到的,你的数据并不仅仅存在于数据堆栈的存储层中。它从一个来源摄取,存储在另一个地方,然后被运输到其他地方进行转换和可视化。

从最广泛的意义上讲,数据编排是将多个任务(其中一些可能是自动化的)配置成一个端到端的流程。当触发时,关键任务将被激活,以确保数据在平台上按正确的时间、顺序和适当的速度流动,从而维持生产标准。(有点像数据产品的输送带。)

与存储或转换不同,管道并不需要编排才能被视为功能正常——至少在基础层面上是如此。然而,一旦数据平台规模超出某个点,管理任务将迅速变得难以控制。

当你提取和处理少量数据时,调度任务只需要少量的努力。但是,当你从多个来源提取和处理大量数据以及无数用例时,调度这些任务需要大量的努力——超出常人的努力。

编排之所以成为 5 层数据堆栈的功能性必要性——即使不是字面上的——是由于手动编码管道固有的可扩展性不足。就像转换和数据质量一样,工程资源成为调度和管理管道的限制性原则。

现代数据堆栈的美妙之处在于,它使得工具和集成去除工程瓶颈,解放工程师为其组织提供新的价值。这些工具证明了自己的价值。这正是编排所做的事情。

随着你的组织发展,数据自然会在不同领域中形成孤岛,建立一个编排层将帮助你的数据团队控制数据源,并继续在各个领域提供价值。

一些最受欢迎的数据编排解决方案包括 Apache AirflowDagster 和相对较新的 Prefect

最重要的部分?为了影响力和可扩展性而构建

当然,五层并不是 唯一的 魔法数字。一个优秀的数据栈可能有六层、七层,甚至 57 层。许多这些潜在的层——如 治理合同,甚至一些额外的测试——根据你的组织及其平台的阶段,可以非常有用。

然而,当你刚开始时,你没有资源、时间,甚至没有必要的用例来处理现代数据栈中提供的 马里亚纳海沟 的平台工具。更重要的是,每一层都会引入新的复杂性、新的挑战和需要被合理化的新成本。相反,专注于最重要的事情,以实现数据的潜力并在短期内推动公司增长。

上述提到的每一层——存储、转换、BI、数据可观察性和编排——都提供了任何完全运作的现代数据栈的一个核心功能,这些功能最大化影响力,并提供你未来快速增长平台、用例和团队所需的即时可扩展性。

如果你是刚开始数据之旅的数据负责人,并且想要提供一个限制成本而不牺牲功能的精简数据平台,那么 5 层数据栈就是你的最佳选择。

不同意?请通过 Barr 在 LinkedIn 上与我联系,提出任何评论、问题或豆泥食谱。

如何构建因果推断机器学习模型,探讨全球变暖是否由人类活动引起

原文:towardsdatascience.com/how-to-build-a-causal-inference-model-to-explore-whether-global-warming-is-caused-by-human-activity-2bcf1830e071

如何使用 Python 和 DoWhy 库构建因果推断模型,探讨全球变暖的原因

Graham HarrisonTowards Data Science Graham Harrison

·发表于 Towards Data Science ·阅读时长 13 分钟·2023 年 1 月 2 日

--

图片由 Patrick Hendry 提供,来自 Unsplash

介绍

我最近发布了一篇文章,提供了如何使用 Pgmpy 库进行因果推断的简单教程 -

## 用 Python 进行因果推断的简单解释

对如何在 Python 中构建一个完整的因果推断模型的直白解释

towardsdatascience.com

文章的读者之一指出,数据的简单性质(例如疫苗接种 = 1 或 0,疾病 = 1 或 0)意味着因果推断模型只能用来解决相对简单的问题,因此它们对实际问题的适用性有限。

这促使我寻找一个大的问题,尝试使用因果推断模型来解决。我选择了“全球变暖”,特别是气候否认,原因如下 -

  • 如果我能开发出一个有效的模型,它将驳斥因果推断仅限于解决简单问题的提议。

  • 有可用的数据集(只要你足够努力寻找!)。

  • 这与最近 2022 年 11 月在埃及沙姆沙伊赫举行的联合国气候变化大会 COP27 息息相关。

获取数据

模型中使用的关键数据来源是-

  • 世界银行数据

  • 全球监测实验室

  • NASA

所有这些数据来源均可供公众使用。有关许可证和使用条款的完整细节可以在文章末尾的“参考文献”部分找到。

这里是综合气候数据…

图片作者提供

… 这里是特征的解释…

图片作者提供

探索性数据分析

接下来的部分将探索数据,以识别和可视化相关性。

我省略了代码以保持文章的因果推断方面的专注,但如果你想查看和运行代码,可以在这里找到 — github.com/grahamharrison68/Public-Github/blob/master/Causal%20Inference/Climate%20Change%20Model-v3.ipynb

相关性

让我们首先快速查看主要特征之间的相关性…

图片作者提供

看起来存在一些高度相关的情况,所以让我们更详细地研究这些关系…

人口 vs. 能源

我们的初步分析是探讨全球人口与能源消费之间的关系…

图片作者提供

这一比较表明,世界人口增长与能源使用之间存在线性增加的关系,相关系数为 r=0.931。

总能源 vs. 化石燃料消费

鉴于人口与能源之间的相关性,下一张图表将探讨能源消费与化石燃料特定消费之间的相关性…

图片作者提供

总体能源使用与化石燃料使用之间几乎完美的相关性并不令人惊讶,但仍然有助于可视化。

化石燃料能源消费与二氧化碳浓度

按照前向链条,如果能源消费与化石燃料使用之间存在相关性,那么化石燃料使用与二氧化碳浓度水平之间的关联是什么呢?…

图片作者提供

存在强相关性,但暂时不要对因果关系做出任何结论,因为这将是文章下一部分的主题。

二氧化碳浓度与温度变化

拼图的最后一块是探索二氧化碳浓度与全球温度之间的潜在相关性…

图片作者提供

这两个特征之间存在相关性,这可能是合理的预期,但要从相关性过渡到因果关系需要建立因果模型…

从相关性到因果关系的转变

我们知道存在以下相关关系 -

  • 人口增加与能源消耗增加相关。

  • 能源消耗的增加与化石燃料使用的增加相关。

  • 化石燃料的使用与 CO2 水平的增加相关。

  • CO2 的增加与温度的升高相关。

不过,我们也知道卡尔·皮尔逊的名言:“相关性并不意味着因果关系”

(thedecisionlab.com/reference-guide/philosophy/correlation-vs-causation)

那么如果我们要探讨人类活动(能源消耗)与全球变暖之间从相关性到合理的因果关系的过渡,我们能说些什么呢?

阅读了 Judea Pearl 的《因果关系之书》并研究了许多其他资料后,可以合理得出以下结论:如果发现相关性,必须有以下其中之一是正确的 -

  1. 相关性确实暗示因果关系,或者……

  2. 相关性是由“混杂”因素造成的(稍后会详细说明)。

.. 基于额外的阅读和研究,我还想补充第三种可能性 -

  1. 相关性完全是虚假的,仅仅是巧合。

让我们逆向探索这三种可能性……

虚假相关性

考虑以下这些,它们是我最喜欢的虚假相关性,归因于 Tyler Vigen(见参考文献部分以获取归因)。

虚假相关性, www.tylervigen.com/spurious-correlations

证明是难以获得的,但显然常识会告诉我们,“想要维持婚姻的夫妻应该少吃人造黄油”这种说法不可能成立。因此,图表中的相关性被认为完全是虚假的、巧合的和偶然的。

高度相关且完全虚假的相关性在现实世界中难以反驳,但可以通过以下方式合理质疑……

  • 咨询领域专家。

  • 留意数据中 y 轴的显著差异。

  • 使用置信区间。

  • 继续监测数据,以查看相关性是否持续或未来是否出现偏离。

人造黄油和离婚的置信区间会很高,因此在这种情况下,我们需要找寻并咨询乳制品和婚姻指导方面的专家,以证明或反驳“食用人造黄油导致离婚”的假设,或者更可能只是继续监测趋势,并等待数据中不可避免的未来偏离。

混杂因素

为了可视化混杂问题,让我们回到我们的模型,看看非化石燃料消耗与 CO2 浓度之间的相关性……

图片来源:作者

表面上看,这令人担忧和困惑。非化石燃料被假定为核电和可再生能源,这两者都不应该导致二氧化碳浓度的增加,而这可能进一步导致温度的升高(如果可以建立因果链)。

那么,为什么我们使用的非化石燃料越多,二氧化碳水平却越高呢?

发生的情况是,人口增加导致更多的能源被生产和消费。随着对能源需求的增加,这会导致化石燃料和非化石燃料能源消耗的增加。

化石燃料的能源消耗正在导致二氧化碳水平的增加(如果我们能证明我们的理论),但随着能源的增加,无论是化石燃料还是非化石燃料的消耗都在增加。

能源具有“混杂效应”,导致非化石燃料和二氧化碳排放之间的相关性。

混杂效应可能是一个难以理解的概念,但我认为这个例子很好的说明了这一点。

总结来说,非化石燃料能源消费与二氧化碳排放之间存在一种既非因果也非虚假的相关性。它完全是由于能源对非化石燃料和化石燃料能源消费的混杂效应所致。

提议的因果模型

本文的目的是展示因果推断机器学习解决方案如何用于构建模型,以解决复杂、大规模和有意义的现实世界问题,因此我将提出一个因果链的提案,这些链条将由气候和能源专家在实际模型中进行测试和改进。

下一步将使用我的DirectedAcyclicGraph类。我在文章中省略了源代码,以使内容更简洁,但这里是完整源码的链接,以防你想自己运行代码 - gist.github.com/grahamharrison68/9733b0cd4db8e3e049d5be7fc17b7602

如果你决定使用它,并且喜欢的话,何不考虑给我买杯咖啡呢? ko-fi.com/grahamharrison

这是我提议的模型……

作者提供的图片

……这些链接的含义是……

  • 人口(POP)的增加导致了能源消耗的增加。

  • 能源使用(ENGY)正在导致化石燃料和非化石燃料的增加。

  • 化石燃料使用(FFEC)正在导致二氧化碳浓度(CO2C)水平的增加。

  • 二氧化碳浓度(CO2C)的增加正在导致全球温度(TMPI)的升高。

……但如果事情不仅仅如此呢?

大问题:是否有自然因素导致全球变暖?

基于现有的科学证据,有强有力的理由认为人类活动对全球变暖有因果影响,但仍有许多人持不同观点……

气候变化否认,或全球变暖否认,是对气候变化的科学共识的否认、驳斥或怀疑,包括对气候变化的范围、是否由人类造成、其对自然和人类社会的影响,或人类行动是否能够适应全球变暖的潜力的否认。

(en.wikipedia.org/wiki/Climate_change_denial#:~:text=Climate%20change%20denial%2C%20or%20global,global%20warming%20by%20human%20actions)

如果我们能够扩展目前开发的模型,以解决这个问题,并提供一些基于因果推断的证据来反驳这一观点,并为接受的科学意见提供进一步的实证证据,证明全球变暖是由人类活动造成的,将会非常有用。

为了探索全球变暖的替代原因,我们需要回到混杂的概念。

我们从早期的例子中知道,能源消费对化石燃料和非化石燃料使用都有因果关系,因此能源是“混杂”非化石燃料使用的因素,导致 CO2 浓度的相关性既不是因果的也不是虚假的。

如果存在一个我们不知道的混杂因素导致 CO2 水平上升和温度升高,这将会怎样?这听起来不太可能,但如果是真的,它将会解构化石燃料能源消费导致 CO2 浓度上升的观点,并加强自然因素负责的看法。

如果存在一个未知因素导致 CO2 和温度上升,那么它在当前提出的有向无环图中没有被表示。它不仅是一个混杂因素,而且是一个“未观测到的混杂因素”。

因此,解决方案需要将有向无环图扩展如下,添加“U”表示潜在的“未观测混杂因素” -

图片由作者提供

不论你信不信,因果推断可以用来提供一个可靠的衡量指标,来评估即使存在未观测到的混杂因素且数据中没有捕捉到的情况下,增加化石燃料能源使用对温度升高的影响!

数学非常复杂(超出了本文的范围),但幸运的是,一些新兴的 Python 因果推断库可以解决未观测混杂问题。

我最喜欢的轻量级 Python 因果推断库是Pgmpy,但它目前不实现未观测混杂因素。我已经记录了这个问题并提出了一个工单,目前Pgmpy团队正在处理。你可以在这里查看工单状态 - github.com/pgmpy/pgmpy/issues/1574 - 计划在 2022 年 12 月 31 日的 0.1.21 版本中发布。

同时,本文提供了一个使用DoWhy库的模型,该库已经解决了未观察到的混杂因素问题……

Estimand type: EstimandType.NONPARAMETRIC_ATE

### Estimand : 1
Estimand name: backdoor
Estimand expression:
   d            
───────(E[TMPI])
d[FFEC]         
Estimand assumption 1, Unconfoundedness: If U→{FFEC} and U→TMPI then P(TMPI|FFEC,,U) = P(TMPI|FFEC,)

### Estimand : 2
Estimand name: iv
Estimand expression:
 ⎡                               -1⎤
 ⎢   d          ⎛   d           ⎞  ⎥
E⎢───────(TMPI)⋅⎜───────([FFEC])⎟  ⎥
 ⎣d[ENGY]       ⎝d[ENGY]        ⎠  ⎦
Estimand assumption 1, As-if-random: If U→→TMPI then ¬(U →→{ENGY})
Estimand assumption 2, Exclusion: If we remove {ENGY}→{FFEC}, then ¬({ENGY}→TMPI)

### Estimand : 3
Estimand name: frontdoor
No such variable(s) found!

代码解释

代码的主要部分是单行调用,它创建了CausalModel实例。参数如下 -

  • data=df_climate_change[climate_features] 指示模型使用哪个DataFrame和哪些特征来构建模型。

  • graph=climate_dag.gml_graph 指示模型使用在DirectedAcyclicGraphclimate_dag实例中提出和表示的因果关系。

  • “处理”是我们感兴趣的变化和理解的事物,在本例中是化石燃料的使用。

  • “结果”是我们感兴趣的影响,即当“处理”(化石燃料的使用)变化时,对“结果”(全球温度升高)的影响是什么?

DoWhy的工作方式一开始有点不直观。许多其他库此时会直接提供答案,但DoWhy会产生一个称为“估算量”的中间步骤。

要计算因果效应,必须在有向无环图中从处理到结果找到三种路径或路线之一 -

  1. 一个“后门”路径。

  2. 一个“前门”路径。

  3. 一个“工具变量”。

注意:对这三种类别的详细解释将是未来文章的主题。

print(climate_estimand)输出的内容看起来很吓人,这让我很长一段时间不愿使用DoWhy,但实际上大多数输出内容可以忽略。

值得注意的是,在我们的模型中,DoWhy找到了一个后门路径和一个工具变量(iv),但没有找到前门路径。我们可以使用已找到的两者中的任何一个,但我选择使用“工具变量”。

再次强调,DoWhy与大多数其他库不同,所选的方法必须在代码中明确声明,无法让DoWhy自动选择合适的方法。

幸运的是,选择“工具变量”的代码非常简单 -

需要注意的是,所有三种可能的方法名称如下 -

  1. 后门:method_name='backdoor.linear_regression'

  2. 前门:method_name='frontdoor.two_stage_regression'

  3. 工具变量:method_name='iv.instrumental_variable'

这就只剩下一个简单的辅助函数,用于以更可读的格式打印结果,模型就完成了!

Average Treatment Effect (ATE):
For every unit change (1 unit = 1 x Oil Equivalent per Capita (10K KG)) in "Fossil Fuel Energy Consumption" ...
"Global Temperature Increase" will change by +0.02322620230983087 (1 unit = 1 x Degrees C (Lowess Smoothing))

就这样!

每增加 10,000 公斤化石燃料能源消费,全球温度将增加 0.023 摄氏度,即使存在一个未观察到的混杂因素作用于 CO2 和温度,这一因果增加仍然成立!

实际上,这种简单的因果推断实现,使用微软的DoWhy库,可以用来反驳气候变化否认论,并提供一个“平均处理效应”,即非化石燃料能源消费增加时温度会升高多少!

结论

我的一个读者回应了之前关于因果推断的文章,认为当前的库可能仅适用于简单问题。

这促使我寻找一个大问题来解决,而没有比气候变化更大的问题了!

本文展示了因果推断如何解决大问题,并提供解决传统机器学习方法无法解决的问题的解决方案。

具体来说,该模型提供了证据,表明人类活动(例如化石燃料能源消费的增加)对全球温度升高有因果影响。

同时,因果推断技术超越了“预测分析”,即模型告诉我们如果未来大致类似于过去会发生什么,转向“处方分析”,即模型可以告诉我们如果我们介入并改变事物会发生什么。

如果您喜欢这篇文章,请考虑……

通过我的推荐链接加入 Medium(如果您使用此链接注册,我将获得一部分费用)。

[## 通过我的推荐链接加入 Medium - Graham Harrison

阅读 Graham Harrison 的每一个故事(以及 Medium 上其他数千位作家的故事)。提升您的数据知识……

grahamharrison-86487.medium.com](https://grahamharrison-86487.medium.com/membership?source=post_page-----2bcf1830e071--------------------------------)

订阅我的免费电子邮件,每当我发布新故事时

快速查看我之前的文章

下载我的免费战略数据驱动决策框架

访问我的数据科学网站 — 数据博客

参考资料

如何使用 IPyWidgets 和 Plotly 在 Python 中构建自定义标注工具

原文:towardsdatascience.com/how-to-build-a-custom-labeler-in-python-with-ipywidgets-and-plotly-f6cc1fd7e3cc?source=collection_archive---------21-----------------------#2023-01-10

在 Jupyter 环境中创建一个分割工具

Jacky KaubTowards Data Science Jacky Kaub

·

关注 发布于 Towards Data Science ·11 分钟阅读·2023 年 1 月 10 日

--

从肾脏中分割出的肾小球。原始图像由HuBMAP 组织映射中心 提供,来自范德堡大学

你知道吗?你可以在几行代码内将一个简单的Jupyter Notebook 转变成一个强大的标注工具。

将一个简单的笔记本转变为标注工具在项目开始时可以节省大量时间,尤其是在你还在评估一个想法的潜力,并且不想在构建一个稳定的网页应用上花费太多时。

在这篇文章中,我将向你展示如何使用plotlyipywidget构建一个分割工具,以生成 python 中图像的二进制掩模。在图像分割中,二进制掩模可以用于训练深度学习模型,以高效地自动识别和隔离感兴趣区域。

我假设你已经熟悉 ipywidgets 和 plotly 的基础知识。如果不是,我强烈建议你先查看这篇文章,我们将介绍基础知识。

关于用例的一句话

我正在使用HuBMAP 数据集,这是一个 2021 年在 Kaggle 上举办的竞赛中的数据集。

我选择这个数据集,因为它完美地展示了现实生活中的应用:一个客户联系你,提供一堆图像,并询问你是否可以找到一种方法来自动隔离这些图像中的特定感兴趣区域。这些区域(在这个例子中)被称为肾小体,是肾脏中帮助过滤血液中废物和多余水分的小部分,见下图。

下图展示了你的客户在这里的期望:一个能够快速识别那些感兴趣区域的模型。

项目目标:提供肾脏图像的分割区域。原始图像由HuBMAP 组织映射中心提供,位于范德比尔特大学。

在探索阶段,时间至关重要。通常,我们只有原始数据,并需要快速生成标签来训练和评估模型,并评估项目的可行性。

与其花费数小时寻找可能仍需定制的开源工具,不如跟随我一步步构建你自己的自定义工具。你会发现,只需一点努力,你就可以轻松创建任何类型的部件来帮助你的日常任务。

构建部件的技术规格

在直接进入编码部分之前,我们需要退一步,考虑一下我们到底希望从我们的部件中得到什么。我们需要回答的问题包括:

  • 输入的格式是什么?是 jpeg 图像吗?Numpy 数组?栅格?等等。

  • 它们存储在哪里?本地?多个区域?还是在云端?

  • 你期望的输出是什么?另一个 jpeg 图像还是 numpy 数组?也许是存储在 json 中的注释?

  • 你期望的交互是什么?点击按钮做某事,具有一些 UI 元素以有效地从数据到数据导航,直接与图形交互,等等。

  • 对于每次交互,实际操作中你将如何管理?这将如何影响部件的内部状态?

  • 尝试在某处(即使是在纸上)绘制你希望你的部件看起来的样子。这将帮助你结构化部件的布局。

在我们的案例中:

我假设主要图像已经被分割成 256x256 像素的子图像,并存储为 .npy 文件在本地的 imgs/ 文件夹中。

main/
  |-- imgs/
  |     |-- 1.npy
  |     |-- 2.npy
  |     |-- etc..
  |-- masks/
  |-- widget_notebook.ipynb

生成的掩膜将存储在 masks/ 文件夹中,格式为 .npy,文件名与原始图像相同。

我们工具所需的功能包括:

  • 小部件将显示两个图像:一个是带有掩膜透明覆盖的原始图像(正在进行中),另一个是当前保存的掩膜,这将允许用户可视化当前完成的工作,并在需要时完成它。

  • 用户可以多次点击图像以生成定义区域的多边形。这将在小部件内用于生成一个掩膜,其中区域外的像素为 0,区域内的像素为 1。

  • 应该使用一个按钮来保存掩膜,当用户对其标签感到满意时,这样结果就会被存储并可以由他或其他服务(如深度学习模型)检索。

  • 应该使用一个按钮来删除当前的多边形,这将方便修复绘图错误。

  • 应该使用一个按钮来重置本地保存的掩膜,允许在需要时恢复到原始状态,而不必手动删除掩膜。

  • 用户可以通过下拉菜单在图像之间导航

我们希望实现的小部件布局的粗略视图。原始图像由 HuBMAP 组织组织 提供,来自范德比尔特大学

构建小部件

开始之前的一些良好实践

我建议你在自定义类中构建你的小部件。这将方便你在状态管理方面,因为所有内容都将保留在内部,并且更容易打包。

同时,作为一般建议,你应该先构建主要布局,然后在第二阶段再构建交互功能。

最后,尽量将各部分分开!创建每个按钮的专用函数或单独处理每个回调的函数不会增加额外的成本,但可能会为测试和调试节省宝贵的时间。

总结来说,为了高效构建各种小部件:

  1. 创建一个类来保存小部件中使用的所有内部状态

  2. 构建初始化图像和组件的函数,在第一阶段没有交互

  3. 添加交互并逐步测试

  4. 保持代码整洁有序

小部件内部状态逻辑

我们的小部件需要保持并修改多个信息:

  • _current_id 用于存储当前图像的名称,同时也用于访问和保存二进制掩膜。

  • _polygon_coordinates 列表将包含用户点击的点的坐标,并将用于生成二进制掩膜(使用 skimage 函数)

  • _initialize_widget 函数将生成构建布局所需的所有其他状态,如按钮、图像和中间掩膜。

class SegmentWidget:

    def __init__(self, path_imgs, path_masks):

        self._path_imgs = path_imgs
        self._path_masks = path_masks
        self._ids = sorted([os.path.splitext(e)[0] for e in  os.listdir(path_imgs)], key=int)
        self._current_id = self.ids[0]
        #This list will be used later to save in memory the coordinates 
        #of the clicks by the user
        self._polygon_coordinates = []
        #TODO: Initiation of all the layout components
        self._initialize_widget()

注意:Python 不管理公共和私有变量,但作为良好的 pep8 实践,非公共变量和函数可以用“_”作为前缀。

读取图像

我们需要开发一个函数,用于读取图像及其相关的掩码(如果存在)。

这个函数将在小部件初始化期间调用,但每当用户切换到新图像时也会调用。

 def _load_images(self):
        '''This method will be used to load image and mask when we select another image'''
        img_path = os.path.join(self._path_imgs,f"{self._current_id}.npy")
        self._current_img = np.load(img_path)
        h,w, _ = self._current_img.shape

        #There is not always a mask saved. When no mask is saved, we create an empty one.
        mask_path = os.path.join(self._path_masks,f"{self._current_id}.npy")
        if os.path.exists(mask_path):
            self._current_mask = np.load(mask_path)
        else:
            self._current_mask = np.zeros((h,w))
        #initiate an intermediate mask which will be used to store ongoing work
        self._intermediate_mask = self._current_mask.copy()

注意:我更倾向于在这里打包一个专用函数。我本可以把所有东西直接放在 init 中,但那样会更难消化。如果你发现一个整体上合理的任务,使用一个函数。

初始化图形

下一步是初始化我们的 FigureWidgets。

我们将使用 plotly.express.imshow 方法显示 RGB 图像,同时对二进制掩码使用 plotly.graph_objects.Heatmap。

我们目前专注于小部件的总体布局,因此将回调函数的定义留到以后。

def _initialize_figures(self):
    '''This function is called to initialize the figure and its callback'''
    self._image_fig = go.FigureWidget()
    self._mask_fig = go.FigureWidget()

    self._load_images() #Update the state loading the images

    #We use plotly express to generate the RGB image from the 3D array loaded
    img_trace = px.imshow(self._current_img).data[0]
    #We use plotly HeatMap for the 2D mask array
    mask_trace = go.Heatmap(z=self._current_mask, showscale=False, zmin=0, zmax=1)

    #Add the traces
    self._image_fig.add_trace(img_trace)
    self._image_fig.add_trace(mask_trace)
    self._mask_fig.add_trace(mask_trace)

    #A bit of chart formating
    self._image_fig.data[1].opacity = 0.3 #make the mask transparent on image 1
    self._image_fig.data[1].zmax = 2 #the overlayed mask above the image can have values in range 0..2
    self._image_fig.update_xaxes(visible=False)
    self._image_fig.update_yaxes(visible=False)
    self._image_fig.update_layout(margin={"l": 10, "r": 10, "b": 10, "t": 50}, 
                                  title = "Define your Polygon Here",
                                  title_x = 0.5, title_y = 0.95)
    self._mask_fig.update_layout(yaxis=dict(autorange='reversed'), margin={"l": 0, "r": 10, "b": 10, "t": 50},)
    self._mask_fig.update_xaxes(visible=False)
    self._mask_fig.update_yaxes(visible=False)

    #Todo: add the callbacks to the two charts

不要犹豫花一点时间来格式化,可能看起来很烦人,但拥有一个合适大小、信息量刚好的工具会在你操作时节省时间

按钮、下拉菜单和最终布局

我们现在将添加函数以将界面组件集成到小部件中。正如规格中所述,我们需要 3 个按钮(水平显示)、一个下拉菜单和两个并排的图形。

def _build_save_button(self):
    self._save_button = Button(description="Save Configuration")
    #Todo: add the callback

def _build_delete_current_config_button(self):
    self._delete_current_config_button = Button(description="Delete Current Mask")
    #Todo: add the callback

def _build_delete_all_button(self):
    self._delete_all_button = Button(description="Delete All Mask")
    #Todo: add the callback

def _build_dropdown(self):
    #The ids are passed as option for the dropdown
    self._dropdown = Dropdown(options = self._ids)
    #Todo: add the callback

def _initialize_widget(self):
    '''Function called during the init phase to initialize all the components
       and build the widget layout
    '''

    #Initialize the components
    self._initialize_figures()
    self._build_save_button()
    self._build_delete_current_config_button()
    self._build_delete_all_button()
    self._build_dropdown()

    #Build the layout
    buttons_widgets = HBox([self._save_button,
                            self._delete_current_config_button,
                            self._delete_all_button])

    figure_widgets = HBox([self._image_fig, self._mask_fig])

    self.widget = VBox([self._dropdown, buttons_widgets, figure_widgets])

def display(self):
    display(self.widget)

我添加了一个“公共” display() 方法(小部件中唯一的“公共”)。它将显示小部件的初始状态。

拥有这样的方法允许用户直接显示小部件,而无需在其内部状态中查找你的部件名称……

HuBMAP 组织的组织映射中心在范德堡大学提供的原始图像

处理交互

现在我们的小部件已完全初始化,我们可以开始处理不同的交互。

下拉菜单交互

下拉菜单允许用户浏览数据集中的不同图像。为了正常工作,下拉菜单的回调函数应:

  • 读取新图像和新掩码

  • 更新两个图形

  • 清空 _polygon_coordinates 中的点列表以开始一个新的多边形

要更新 px.imshow() 轨迹,我们需要修改其“source”参数。

要更新 go.Heatmap() 轨迹,我们修改轨迹的“z”参数。

请注意,每个轨迹通常显示为一个包含图表所有信息的对象。不要犹豫,展示它以查看你可以更轻松地修改什么。

def _callback_dropdown(self, change):

    #Set the new id to the new dropdown value
    self._current_id = change['new']

    #Load the new image and the new mask, we already have a method to do this
    self._load_images()

    img_trace = px.imshow(self._current_img).data[0]

    #Update both figure
    with self._image_fig.batch_update():
        #Update the trace 0 and the trace 1 containing respectively
        #the image and the mask
        self._image_fig.data[0].source = img_trace.source
        self._image_fig.data[1].z = self._current_mask

    with self._mask_fig.batch_update():
        self._mask_fig.data[0].z = self._current_mask

    #Reset the list of coordinates used to store current work in progress
    self._polygon_coordinates = []

def _build_dropdown(self):
    #The ids are passed as option for the dropdown
    self._dropdown = Dropdown(options = self._ids)
    self._dropdown.observe(self._callback_dropdown, names="value") 

我们现在可以浏览不同的图像。

HuBMAP 组织的组织映射中心在范德堡大学提供的原始图像

FigureWidget 的 on_click 交互

点击图像中的一个像素将把它的坐标存储在小部件状态中。

存储 3 个或更多像素应触发一个函数,该函数将:

  • 如果像素位于由坐标列表定义的多边形内或外,则生成值为 0 或 2 的新掩模数组。

  • 创建一个中间掩模,其中包含前一个掩模的信息和新生成的掩模信息。

  • 在左侧图形上显示新生成的掩模。

我们使用 skimage.draw 从坐标列表中高效生成多边形掩模。

Scikit-image 是处理各种图像处理任务的强大工具。查看 他们的文档 以了解更多可能性!

def _gen_mask_from_polygon(self):
    '''This function set to 2 the values inside the polygon defined by the list of points provided'''
    h,w = self._current_mask.shape
    new_mask = np.zeros((h,w), dtype=int)
    #Get coordinates inside the polygon using skimage.draw.polygon function
    rr, cc = polygon([e[0] for e in self._polygon_coordinates], 
                     [e[1] for e in self._polygon_coordinates], shape=new_mask.shape)

    #Recreate the intermediate_mask and set values inside ongoing polygon
    #to 2
    self._intermediate_mask = self._current_mask.copy()
    self._intermediate_mask[rr,cc]=2

def _on_click_figure(self, trace, points, state):
    #Retrieve coordinates of the clicked point
    i,j = points.point_inds[0]
    #Add the point to the list of points
    self._polygon_coordinates.append((i,j))

    #If more than 2 click have been done, create the new intermediate polygon
    #and update the mask on the image
    if len(self._polygon_coordinates)>2:
        self._gen_mask_from_polygon()
        with self._image_fig.batch_update():
            self._image_fig.data[1].z = self._intermediate_mask

然后我们可以更新 _initialize_figures 方法以附加回调函数。

注意:由于图形由多个图像层组成,我们将回调函数附加到顶层。

def _initialize_figures(self):

    #[...Rest of the function...]

    self._image_fig.data[-1].on_click(self._on_click_figure)

就这样!我们现在可以可视化我们图表交互的效果:

原始图像由HuBMAP 组织的组织映射中心提供,位于范德比尔特大学。

按钮交互

为完成我们的应用程序,让我们编写按钮交互的代码。

“保存配置”按钮。这个按钮通过以下方式保存掩模:

  1. 将 0 和 2 像素从 _intermediate_mask 复制到 _current_mask。

  2. 将 _current_mask 保存到 /masks 文件夹。

  3. 刷新图形。

  4. 重置 _polygon_coordinates 列表。

def _callback_save_button(self, button):
    self._current_mask[self._intermediate_mask==2]=1
    self._current_mask[self._intermediate_mask==0]=0
    mask_path = os.path.join(self._path_masks,f"{self._current_id}.npy")
    np.save(mask_path,self._current_mask)
    self._intermediate_mask = self._current_mask.copy()
    with self._image_fig.batch_update():
        self._image_fig.data[1].z = self._current_mask     
    with self._mask_fig.batch_update():
        self._mask_fig.data[0].z = self._current_mask
    self._polygon_coordinates = []

def _build_save_button(self):
    self._save_button = Button(description="Save Configuration")
    self._save_button.on_click(self._callback_save_button)

原始图像由HuBMAP 组织的组织映射中心提供,位于范德比尔特大学。

“删除当前掩模”按钮。这个按钮只是将 _intermediate_mask 重置为 _current_mask 的值,并刷新图形和 _polygon_coordinates 列表。

def _callback_delete_current_config_button(self, button):
    self._intermediate_mask = self._current_mask.copy()
    with self._image_fig.batch_update():
        self._image_fig.data[1].z = self._intermediate_mask
    self._polygon_coordinates = []

def _build_delete_current_config_button(self):
    self._delete_current_config_button = Button(description="Delete Current Mask")
    self._delete_current_config_button.on_click(self._callback_delete_current_config_button)

原始图像由HuBMAP 组织的组织映射中心提供,位于范德比尔特大学。

“删除所有掩模”按钮。这个按钮只是将 _intermediate_mask 重置为 0,并将 _polygon_coordinates 重置为空列表。

def _callback_delete_all_button(self, button):
    self._intermediate_mask[:] = 0
    with self._image_fig.batch_update():
        self._image_fig.data[1].z = self._intermediate_mask
    self._polygon_coordinates = []

def _build_delete_all_button(self):
    self._delete_all_button = Button(description="Delete All Mask")
    self._delete_all_button.on_click(self._callback_delete_all_button)

原始图像由HuBMAP 组织的组织映射中心提供,位于范德比尔特大学。

想自己试试吗?完整代码请见这里

结论

几小时的工作后,我们已经能够设置我们的工具,并现在具备快速标记图像的能力,这将帮助我们满足客户的原始请求。在准备了几百张图像的掩模后,我们可以训练一个初步模型,并评估是否需要动用更多资源来推动项目进一步发展。

发挥创造力

如果你到这里来了,恭喜你!你现在可以直接在你的 Jupyter Notebook 中生成一个自定义标签工具了。我们仅仅探讨了使用 plotly 和 ipywidgets 可以快速利用的众多用例中的一个,你现在拥有了开发自己应用程序的所有关键。

如何使用 ChatGPT 构建数据科学作品集网站

原文:towardsdatascience.com/how-to-build-a-data-science-portfolio-website-with-chatgpt-e57d29badf7f

利用 AI 的力量创建一个就业准备好的作品集网站

Natassha SelvarajTowards Data Science Natassha Selvaraj

·发表于Towards Data Science ·9 分钟阅读·2023 年 7 月 11 日

--

图片来源于 Midjourney

作为一名初级数据科学家,进入这个行业可能会很有挑战性,因为竞争空前激烈。

如果你没有学位或任何正式的资格证书来突出你的专业能力,这一点尤为重要。

我总是建议领域的初学者创建解决问题的数据科学项目,这样你会脱颖而出。

当你构建解决现实问题的数据项目时,你让雇主相信你能够丰富他们的业务并作为数据科学家创造收益。

记住,你正在与拥有硕士学位和博士学位的申请者竞争,因此如果你没有这些学位,你必须在项目建设上具有创意,以便使自己与其他候选人区分开来。

如果你想构建数据科学项目但不知道从哪里开始,我建议查看这篇文章,我在其中介绍了一些帮助我进入该领域的最佳项目。

我很乐意你能采纳一些这些想法,并将它们改造为你自己的项目。

为什么要构建数据科学作品集网站

一旦你完成了 2 到 3 个数据科学项目,我强烈建议创建一个网站,将所有的作品集中在一个地方展示。

实际上,我通过简单地给招聘人员发消息并发送我的作品集网站链接获得了我的前几个数据科学职位:

图片由作者提供

你的作品集网站是一个可以向潜在雇主展示你是谁以及你能带来什么的地方。

招聘经理不必通过你的 GitHub 仓库查看代码来了解你做过的工作,他们可以直接浏览你的作品集网站,一目了然地了解你。

使用 ChatGPT,你可以轻松构建一个引人注目的作品集网站。

我用 HTML、CSS 和 JavaScript 从零开始构建了我的第一个作品集网站。我还在之前的文章中写了这个过程。

由于我对网页开发了解甚少,并且不得不自己编写和调试所有代码,所以我花了大约一周时间才让我的网站上线。

然而,像 ChatGPT 这样的 AI 工具可以比以前更快地帮助你创建网站。

使用正确的提示,你可以简单地向 ChatGPT 描述你希望网站的样子,模型将为你提供代码来构建一个高度可定制的作品集网站,以满足你的具体要求。

在本教程中,我将展示如何使用 ChatGPT 构建一个数据科学作品集网站。

步骤 1:你应该如何结构化你的作品集网站?

在实际编码作品集网站之前,你需要决定它应该如何结构化——需要包含哪些部分,以便展现你的最佳面貌。

让我们向 ChatGPT 请求一些想法。

我将使用 GPT-4 模型进行本教程。如果你没有 ChatGPT 的付费订阅,可以使用默认的 GPT-3.5 模型,效果也应该很好。

这是我输入到 ChatGPT 的提示:

I am a data scientist with 3 years of experience in the field. 
Can you give me some ideas as to how I can structure my portfolio website?

这是我从模型得到的回复:

图片由作者提供

图片由作者提供

好的,ChatGPT 建议我们将作品集网站分为 5 个部分——主页、技能、项目、教育和博客。

这是一个典型作品集网站的结构。

你可以根据自己的需求进行调整,并要求它根据你的要求增加或减少部分。

步骤 2:为你的数据科学作品集网站构建内容

下一步是实际编写你想在网站每个部分中包含的内容。

如果你希望你的站点在 Google 搜索中排名靠前,你需要创建 SEO 友好的内容并使用正确的关键词。

你可以通过两种方式做到这一点:

方法 1:

只需输入一段关于你自己的简短介绍,并要求 ChatGPT 为作品集网站的每个部分生成 SEO 友好的内容。

图片由作者提供

这样做的一个好处是你不必花时间自己编写内容,ChatGPT 能很好地将正确的关键词融入其中,并创建引人入胜的内容。

然而,如果你的雇主使用 AI 检测工具来筛查你的作品集,你的工作可能会被标记为 ChatGPT 生成的内容,这可能对你作为求职者的形象不利。

方法 2:

由于 ChatGPT 生成的内容可能会引起潜在雇主的警惕,我建议你自己编写每个部分的文本。

然后你可以将内容粘贴到 ChatGPT 中,要求模型修复文本中的任何语法错误,并提高其 SEO 分数。

我推荐这样做,因为你不仅仅是在使用 AI 生成的文本来构建你的网站。实际上,你是在创建自己的内容,并只是请求 ChatGPT 对其进行优化,以提高排名和可读性。

下面是一个可以帮助你实现这一目标的提示示例:

图片来源于作者

以下是 ChatGPT 对我提示的回应:

图片来源于作者

注意到 ChatGPT 对我的内容没有做任何重大更改。相反,它包括了“Python”、“R”和“SQL”等术语,这些术语与招聘人员寻找具有特定技能的数据科学家有关。

招聘经理通常使用自动化工具扫描简历中的特定关键词,仅筛选符合要求的候选人。

这就是为什么优化你的作品集以适应招聘人员关注的特定专业领域如此重要。

ChatGPT 可以帮助你完成这项任务——实际上,根据内容营销专家的说法,它的一个主要用途包括内容增强和 提供量身定制的关键词,这些关键词有助于让网站脱颖而出。

步骤 3:使用 ChatGPT 创建你的作品集网站

现在,我们终于可以使用 ChatGPT 构建作品集网站了。

为了做到这一点,让我们提示 ChatGPT 给我们一些代码来构建作品集网站:

Can you create a portfolio website with the following elements using HTML, CSS, and JavaScript:
1\. Bio/Introduction: This section should have a header tag (H1) that says "Bio." You can use the same text I pasted in the <p> tag of this section. On the right hand side of this section, please use this image: [`unsplash.com/photos/hgFY1mZY-Y0.`](https://unsplash.com/photos/hgFY1mZY-Y0.)2\. Projects: This section should have a H1 tag that says "Projects." It should also have three cards that are clickable, I will provide the URLs myself. Each card should have the following images respectively: [`unsplash.com/photos/zwsHjakE_iI,`](https://unsplash.com/photos/zwsHjakE_iI,) [`unsplash.com/photos/hGV2TfOh0ns,`](https://unsplash.com/photos/hGV2TfOh0ns,) [`unsplash.com/photos/s9CC2SKySJM.`](https://unsplash.com/photos/s9CC2SKySJM.) Please style this using Bootstrap's card component.3\. Skills: This section should have a H1 tag that says "Skills." It should list Python, SQL, Data Analysis, and R. Create this in the form of small bar charts - Python (80%), SQL (70%), Data Analysts (90%) and R (30%).4\. Contact: This section is the footer, and should have a sign up box that allows users to enter their email addresses and click subscribe. It should have a H1 tag that says "Contact Me."Please style all the code using Bootstrap, and structure it in the form of a portfolio website.

当要求 ChatGPT 编写代码或构建最终产品时,你需要非常具体,这就是为什么我清楚地概述了我希望每个部分的结构。

我指定了我想要创建的所有部分,以及我想使用的标题类型。我甚至提供了想在网站上包含的 Unsplash 图片的链接。

最后,我要求它使用 Bootstrap 来为网站设计样式。这是一个流行的 CSS 框架,用于构建美观、响应式的网站,并减少从头设计页面所需的时间。

你可以根据你希望网站的结构来更改要求。

ChatGPT 根据我提供的指示给了我一些代码:

它告诉我自己上传每个部分的图片,因为 Unsplash 不允许直接嵌入链接,并为我提供了两个单独文件的代码——HTML 和 CSS。

我将它们保存在同一目录下并运行了代码,结果网站看起来像这样:

图片来源于作者

这是一个简单的框架,需要用文本和图片内容进行丰富。

为了确保我们确切知道需要进行哪些更改,ChatGPT 在代码的特定部分留下了注释,这些部分需要由文本描述或图片替换:

作者提供的图片

如果你还没有相关的个人或项目图片可以在网站上使用,只需从UnsplashPexels下载一些作为占位符即可。

在代码中包含相关内容和图片后,让我们刷新网站,看看效果如何:

作者提供的图片

我觉得这看起来还不错,不过感觉需要一些小的改动,使其更专业。

例如,导航栏和“简介”部分有点太靠近了。它们应该分开一点,导航栏也应该更深一些,以便提高可见性。

简介中的文字太短,留有很多空白,使得视觉效果不佳。

让我们请 ChatGPT 进行一些修改:

Can you do these things:
1\. Make the navigation bar darker, and separate it from the "Bio" section. They are currently almost overlapping. If you make this bar dark, the text should be made lighter for it to be visible.2\. The text in the "Bio" section should be made slightly bigger, as there currently is a lot of empty space at the bottom.3\. The "Email Address" text should be made bigger or highlighted in bold, and centered horizontally. The "Subscribe" button, project title, project description, and "View Project" button should also be centered horizontally.

请记住,ChatGPT 可能会忽略这些指示中的一个或多个(它对我确实有),但你只需再次提示它,以提醒其确切需要做出的更改,它最终会做对的。

这是 ChatGPT 为我提供更新代码后的最终版本网站:

作者提供的图片

注意:ChatGPT 刚刚设计了联系按钮,但它目前没有任何功能。如果你想让它工作,只需请 ChatGPT 提供如何使用类似 MailChimp 的免费邮件订阅服务的指示。

看!就是这样!

我们成功地在不到一天的时间内与 ChatGPT 一起构建了一个有吸引力的作品集网站。

下一步是部署这个网站,这可以通过GitHub Pages瞬间完成。

ChatGPT 生成的用于构建作品集网站的完整代码可以在我的GitHub 仓库中找到。

这篇文章就到这里,谢谢阅读!

如何构建一个完全自动化的数据漂移检测管道

原文:towardsdatascience.com/how-to-build-a-fully-automated-data-drift-detection-pipeline-e9278584e58d?source=collection_archive---------2-----------------------#2023-08-01

自动化指南:检测和处理数据漂移

Khuyen TranTowards Data Science Khuyen Tran

·

关注 发表在 Towards Data Science · 10 分钟阅读 · 2023 年 8 月 1 日

--

作者提供的图片

动机

数据漂移发生在生产环境中的输入特征分布与训练数据不同,从而可能导致不准确性和模型性能下降。

作者提供的图片

为了减轻数据漂移对模型性能的影响,我们可以设计一个工作流,检测漂移,通知数据团队,并触发模型重训练。

作者提供的图片

工作流

工作流包括以下任务:

  1. 从 Postgres 数据库中获取参考数据。

  2. 从网络获取当前生产数据。

  3. 通过比较参考数据和当前数据来检测数据漂移。

  4. 将当前数据附加到现有的 Postgres 数据库中。

  5. 当出现数据漂移时,采取以下措施:

  • 发送 Slack 消息以提醒数据团队。

  • 重新训练模型以更新其性能。

  • 将更新后的模型推送到 S3 存储。

这个工作流程被安排在特定时间运行,例如每周一上午 11:00。

图片由作者提供

总体而言,工作流程包括两种类型的任务:数据科学任务和数据工程任务。

数据科学任务,用粉色框表示,由数据科学家执行,涉及数据漂移检测、数据处理和模型训练。

数据工程任务,用蓝色和紫色框表示,由数据工程师执行,涉及数据移动和发送通知的任务。

数据科学任务

检测数据漂移

为了检测数据漂移,我们将创建一个 Python 脚本,该脚本接受两个 CSV 文件“data/reference.csv”(参考数据)和“data/current.csv”(当前数据)。

图片由作者提供

我们将使用Evidently,一个开源的机器学习可观测性平台,来将参考数据(作为基线)与当前生产数据进行比较。

如果检测到数据漂移,则“drift_detected”输出为 True;否则为 False。

from evidently.metric_preset import DataDriftPreset
from evidently.report import Report
from kestra import Kestra

data_drift_report = Report(metrics=[DataDriftPreset()])
data_drift_report.run(reference_data=reference, current_data=current)
report = data_drift_report.as_dict()
drift_detected = report["metrics"][0]["result"]["dataset_drift"]

if drift_detected:
    print("Detect dataset drift")
else:
    print("Detect no dataset drift")

Kestra.outputs({"drift_detected": drift_detected})

完整代码。

重新训练模型

接下来,我们将创建一个负责模型训练的 Python 脚本。该脚本以过去和当前数据的结合作为输入,并将训练好的模型保存为“model.pkl”文件。

图片由作者提供

def train_model(X_train: pd.DataFrame, y_train: pd.Series, model_params: DictConfig):
    y_train_log = np.log1p(y_train)
    model = Ridge()
    scorer = metrics.make_scorer(rmsle, greater_is_better=True)
    params = dict(model_params)

    grid = GridSearchCV(model, params, scoring=scorer, cv=3, verbose=3)
    grid.fit(X_train, y_train_log)
    return grid

model = train_model(X_train, y_train, config.model.params)
joblib.dump(model, "model.pkl")

完整代码。

推送到 GitHub

在开发完这两个脚本后,数据科学家可以将它们推送到 GitHub,从而允许数据工程师在创建工作流时使用它们。

在这里查看这些文件的 GitHub 仓库:

[## GitHub - khuyentran1401/detect-data-drift-pipeline: 一个检测数据漂移并重新训练模型的流水线]

一个检测数据漂移并在出现漂移时重新训练模型的流水线 - GitHub …

github.com](https://github.com/khuyentran1401/detect-data-drift-pipeline?source=post_page-----e9278584e58d--------------------------------)

数据工程任务

流行的协调库如 Airflow、Prefect 和 Dagster 需要修改 Python 代码以使用其功能。

当 Python 脚本与数据工作流紧密集成时,整体代码库可能会变得更复杂,维护也更困难。如果没有独立的 Python 脚本开发,数据工程师可能需要修改数据科学代码以添加编排逻辑。

图片来源:作者

另一方面,Kestra 是一个开源库,它允许你独立开发 Python 脚本,然后通过 YAML 文件无缝地将它们融入数据工作流。

这样,数据科学家可以专注于模型处理和训练,而数据工程师则可以专注于处理编排。

因此,我们将使用 Kestra 设计一个更模块化和高效的工作流。

图片来源:作者

克隆 detect-data-drift-pipeline 仓库 以获取 Kestra 的 docker-compose 文件,然后运行:

docker compose up -d

访问 localhost:8080 以访问和探索 Kestra UI。

图片来源:作者

按照这些 说明 配置本教程所需的环境。

在开发目标流程之前,让我们通过创建一些简单的流程来熟悉 Kestra。

从 Python 脚本访问 Postgres 表

我们将创建一个包含以下任务的流程:

  • getReferenceTable:从 Postgres 表中导出 CSV 文件。

  • saveReferenceToCSV:创建一个本地 CSV 文件,可以被 Python 任务访问。

  • runPythonScript:使用 Python 读取本地 CSV 文件。

为了在saveReferenceToCSVrunPythonScript任务之间传递数据,我们将通过将这两个任务放置在相同的工作目录中,并将它们封装在wdir任务内部来实现。

id: get-reference-table
namespace: dev
tasks:
  - id: getReferenceTable
    type: io.kestra.plugin.jdbc.postgresql.CopyOut
    url: jdbc:postgresql://host.docker.internal:5432/
    username: "{{secret('POSTGRES_USERNAME')}}"
    password: "{{secret('POSTGRES_PASSWORD')}}"
    format: CSV
    sql: SELECT * FROM reference
    header: true
  - id: wdir
    type: io.kestra.core.tasks.flows.WorkingDirectory
    tasks:
    - id: saveReferenceToCSV
      type: io.kestra.core.tasks.storages.LocalFiles
      inputs:
        data/reference.csv: "{{outputs.getReferenceTable.uri}}"
    - id: runPythonScript
      type: io.kestra.plugin.scripts.python.Script
      beforeCommands:
        - pip install pandas
      script: | 
        import pandas as pd
        df = pd.read_csv("data/reference.csv")
        print(df.head(10))

图片来源:作者

执行流程将显示以下日志:

图片来源:作者

使用输入参数化流程

让我们创建另一个可以用输入参数化的流程。这个流程将包含以下输入:startDateendDatedataURL

getCurrentCSV任务可以使用{{inputs.name}}符号访问这些输入。

id: get-current-table
namespace: dev
inputs:
  - name: startDate
    type: STRING
    defaults: "2011-03-01"
  - name: endDate
    type: STRING
    defaults: "2011-03-31"
  - name: dataURL
    type: STRING 
    defaults: "https://raw.githubusercontent.com/khuyentran1401/detect-data-drift-pipeline/main/data/bikeride.csv"
tasks:
  - id: getCurrentCSV
    type: io.kestra.plugin.scripts.python.Script
    beforeCommands:
      - pip install pandas
    script: |
      import pandas as pd
      df = pd.read_csv("{{inputs.dataURL}}", parse_dates=["dteday"])
      print(f"Getting data from {{inputs.startDate}} to {{inputs.endDate}}")
      df = df.loc[df.dteday.between("{{inputs.startDate}}", "{{inputs.endDate}}")]
      df.to_csv("current.csv", index=False)

图片来源:作者

这些输入的值可以在每次执行流程时指定。

图片来源:作者

将 CSV 文件加载到 Postgres 表中

以下流程执行以下任务:

  • getCurrentCSV:运行 Python 脚本以在工作目录中创建 CSV 文件。

  • saveFiles:将工作目录中的 CSV 文件发送到 Kestra 的内部存储。

  • saveToCurrentTable:将 CSV 文件加载到 Postgres 表中。

iid: save-current-table
namespace: dev
tasks:
  - id: wdir
    type: io.kestra.core.tasks.flows.WorkingDirectory
    tasks:
    - id: getCurrentCSV
      type: io.kestra.plugin.scripts.python.Script
      beforeCommands:
        - pip install pandas
      script: |
        import pandas as pd
        data_url = "https://raw.githubusercontent.com/khuyentran1401/detect-data-drift-pipeline/main/data/bikeride.csv"
        df = pd.read_csv(data_url, parse_dates=["dteday"])
        df.to_csv("current.csv", index=False)
    - id: saveFiles
      type: io.kestra.core.tasks.storages.LocalFiles
      outputs:
        - current.csv
  - id: saveToCurrentTable
    type: io.kestra.plugin.jdbc.postgresql.CopyIn
    url: jdbc:postgresql://host.docker.internal:5432/
    username: "{{secret('POSTGRES_USERNAME')}}"
    password: "{{secret('POSTGRES_PASSWORD')}}"
    from: "{{outputs.saveFiles.uris['current.csv']}}"
    table: current
    format: CSV
    header: true
    delimiter: ","

图片来源:作者

运行此流程后,你将在 Postgres 数据库中的“current”表中看到结果数据。

图片由作者提供

从 GitHub 仓库中运行文件

此流程包括以下任务:

  • cloneRepository:克隆一个公共 GitHub 仓库

  • runPythonCommand:从 CLI 执行一个 Python 脚本

这两个任务将在同一工作目录中操作。

id: clone-repository
namespace: dev
tasks:
  - id: wdir
    type: io.kestra.core.tasks.flows.WorkingDirectory
    tasks:
      - id: cloneRepository
        type: io.kestra.plugin.git.Clone
        url: https://github.com/khuyentran1401/detect-data-drift-pipeline
        branch: main
      - id: runPythonCommand
        type: io.kestra.plugin.scripts.python.Commands
        commands:
          - python src/example.py

图片由作者提供

运行流程后,你将看到以下日志:

图片由作者提供

按计划运行一个流程

我们将创建另一个基于特定时间表运行的流程。以下流程在每周一上午 11:00 运行。

id: triggered-flow
namespace: dev
tasks:
  - id: hello
    type: io.kestra.core.tasks.log.Log
    message: Hello world
triggers:
  - id: schedule
    type: io.kestra.core.models.triggers.types.Schedule
    cron: "0 11 * * MON"

上传到 S3

此流程包括以下任务:

  • createPickle:在 Python 中生成一个 pickle 文件

  • savetoPickle:将 pickle 文件转移到 Kestra 的内部存储

  • upload:将 pickle 文件上传到 S3 桶

id: upload-to-S3
namespace: dev
tasks:
  - id: wdir
    type: io.kestra.core.tasks.flows.WorkingDirectory
    tasks:
    - id: createPickle
      type: io.kestra.plugin.scripts.python.Script
      script: |
        import pickle
        data = [1, 2, 3]
        with open('data.pkl', 'wb') as f:
          pickle.dump(data, f)
    - id: saveToPickle
      type: io.kestra.core.tasks.storages.LocalFiles
      outputs:
        - data.pkl  
  - id: upload
    type: io.kestra.plugin.aws.s3.Upload
    accessKeyId: "{{secret('AWS_ACCESS_KEY_ID')}}"
    secretKeyId: "{{secret('AWS_SECRET_ACCESS_KEY_ID')}}"
    region: us-east-2
    from: '{{outputs.saveToPickle.uris["data.pkl"]}}'
    bucket: bike-sharing
    key: data.pkl

图片由作者提供

运行此流程后,data.pkl 文件将上传到“bike-sharing”桶中。

图片由作者提供

将一切整合在一起

构建一个流程来检测数据漂移

现在,让我们结合所学创建一个检测数据漂移的流程。每周一上午 11:00,这个流程执行以下任务:

  • 从 Postgres 数据库中获取参考数据。

  • 运行一个 Python 脚本以从网络获取当前生产数据。

  • 克隆包含漂移检测代码的 GitHub 仓库

  • 运行一个 Python 脚本,通过比较参考数据和当前数据来检测数据漂移。

  • 将当前数据附加到现有的 Postgres 数据库中。

图片由作者提供

id: detect-data-drift
namespace: dev
inputs:
  - name: startDate
    type: STRING
    defaults: "2011-03-01"
  - name: endDate
    type: STRING
    defaults: "2011-03-31"
  - name: data_url
    type: STRING 
    defaults: "https://raw.githubusercontent.com/khuyentran1401/detect-data-drift-pipeline/main/data/bikeride.csv"
tasks:
  - id: getReferenceTable
    type: io.kestra.plugin.jdbc.postgresql.CopyOut
    url: jdbc:postgresql://host.docker.internal:5432/
    username: "{{secret('POSTGRES_USERNAME')}}"
    password: "{{secret('POSTGRES_PASSWORD')}}"
    format: CSV
    sql: SELECT * FROM reference
    header: true
  - id: wdir
    type: io.kestra.core.tasks.flows.WorkingDirectory
    tasks:
      - id: cloneRepository
        type: io.kestra.plugin.git.Clone
        url: https://github.com/khuyentran1401/detect-data-drift-pipeline
        branch: main
      - id: saveReferenceToCSV
        type: io.kestra.core.tasks.storages.LocalFiles
        inputs:
          data/reference.csv: "{{outputs.getReferenceTable.uri}}"
      - id: getCurrentCSV
        type: io.kestra.plugin.scripts.python.Script
        beforeCommands:
          - pip install pandas
        script: |
          import pandas as pd
          df = pd.read_csv("{{inputs.data_url}}", parse_dates=["dteday"])
          print(f"Getting data from {{inputs.startDate}} to {{inputs.endDate}}")
          df = df.loc[df.dteday.between("{{inputs.startDate}}", "{{inputs.endDate}}")]
          df.to_csv("data/current.csv", index=False)
      - id: detectDataDrift
        type: io.kestra.plugin.scripts.python.Commands
        beforeCommands:
          - pip install -r src/detect/requirements.txt
        commands:
          - python src/detect/detect_data_drift.py
      - id: saveFileInStorage
        type: io.kestra.core.tasks.storages.LocalFiles
        outputs:
          - data/current.csv
  - id: saveToCurrentTable
    type: io.kestra.plugin.jdbc.postgresql.CopyIn
    url: jdbc:postgresql://host.docker.internal:5432/
    username: "{{secret('POSTGRES_USERNAME')}}"
    password: "{{secret('POSTGRES_PASSWORD')}}"
    from: "{{outputs.saveFileInStorage.uris['data/current.csv']}}"
    table: current
    format: CSV
    header: true
    delimiter: ","
triggers:
  - id: schedule
    type: io.kestra.core.models.triggers.types.Schedule
    cron: "0 11 * * MON"

构建一个流程来发送 Slack 消息

接下来,我们将创建一个流程,通过一个 Slack Webhook URLdetect-data-drift 流程中的 detectDataDrift 任务返回 drift_detected=true 时发送 Slack 消息。

图片由作者提供

id: send-slack-message
namespace: dev
tasks:
  - id: send
    type: io.kestra.plugin.notifications.slack.SlackExecution
    url: "{{secret('SLACK_WEBHOOK')}}"
    customMessage: Detect data drift

triggers:
  - id: listen
    type: io.kestra.core.models.triggers.types.Flow
    conditions:
    - type: io.kestra.core.models.conditions.types.ExecutionFlowCondition
      namespace: dev
      flowId: detect-data-drift
    - type: io.kestra.core.models.conditions.types.VariableCondition
      expression: "{{outputs.detectDataDrift.vars.drift_detected}} == true"

运行 detect-data-drift 流程后,send-slack-message 流程将发送一条 Slack 消息。

图片由作者提供

构建一个流程以重新训练模型

最后,我们将创建一个流程来重新训练模型。这个流程执行以下任务:

  • 从 Postgres 数据库的当前表导出一个 CSV 文件

  • 克隆包含模型训练代码的 GitHub 仓库

  • 运行一个 Python 脚本来训练模型并生成一个 pickle 文件

  • 上传 pickle 文件到 S3

图片由作者提供

id: train-model
namespace: dev
tasks:
  - id: getCurrentTable
    type: io.kestra.plugin.jdbc.postgresql.CopyOut
    url: jdbc:postgresql://host.docker.internal:5432/
    username: "{{secret('POSTGRES_USERNAME')}}"
    password: "{{secret('POSTGRES_PASSWORD')}}"
    format: CSV
    sql: SELECT * FROM current
    header: true
  - id: wdir
    type: io.kestra.core.tasks.flows.WorkingDirectory
    tasks:
      - id: cloneRepository
        type: io.kestra.plugin.git.Clone
        url: https://github.com/khuyentran1401/detect-data-drift-pipeline
        branch: main
      - id: saveCurrentToCSV
        type: io.kestra.core.tasks.storages.LocalFiles
        inputs:
          data/current.csv: "{{outputs.getCurrentTable.uri}}"
      - id: trainModel
        type: io.kestra.plugin.scripts.python.Commands
        beforeCommands:
          - pip install -r src/train/requirements.txt
        commands:
          - python src/train/train_model.py
      - id: saveToPickle
        type: io.kestra.core.tasks.storages.LocalFiles
        outputs:
          - model.pkl
  - id: upload
    type: io.kestra.plugin.aws.s3.Upload
    accessKeyId: "{{secret('AWS_ACCESS_KEY_ID')}}"
    secretKeyId: "{{secret('AWS_SECRET_ACCESS_KEY_ID')}}"
    region: us-east-2
    from: '{{outputs.saveToPickle.uris["model.pkl"]}}'
    bucket: bike-sharing
    key: model.pkl
triggers:
  - id: listenFlow
    type: io.kestra.core.models.triggers.types.Flow
    conditions:
      - type: io.kestra.core.models.conditions.types.ExecutionFlowCondition
        namespace: dev
        flowId: detect-data-drift
      - type: io.kestra.core.models.conditions.types.VariableCondition
        expression: "{{outputs.detectDataDrift.vars.drift_detected}} == true"After running this flow, the model.pkl file will be uploaded to the "bike-sharing" bucket.

运行此流程后,model.pkl 文件将上传到“bike-sharing”桶中。

图片由作者提供

扩展此工作流程的想法

与其依赖计划的数据提取来识别数据漂移,我们可以利用Grafana 的外发 WebhookKestra 的入站 Webhook来建立实时数据监控,并在数据漂移发生时立即触发流程。这种方法能够在数据漂移发生时立即检测,避免了等待计划脚本运行的需要。

作者提供的图片

请在评论中告诉我你认为这个工作流程可以如何扩展,以及你希望在未来的内容中看到其他什么用例。

我喜欢撰写关于数据科学概念的文章,并玩弄不同的数据科学工具。你可以通过以下方式保持更新我的最新帖子:

如何在 2023 年构建多 GPU 系统进行深度学习

原文:towardsdatascience.com/how-to-build-a-multi-gpu-system-for-deep-learning-in-2023-e5bbb905d935?source=collection_archive---------0-----------------------#2023-09-16

Antonis MakropoulosTowards Data Science Antonis Makropoulos

·

关注 发布于 Towards Data Science ·10 min read·2023 年 9 月 16 日

--

我的深度学习搭建——始终在进行中 😃.

本文提供了如何构建多 GPU 系统进行深度学习的指南,希望能节省你的研究时间和实验。

目标

构建一个多 GPU 系统,用于计算机视觉和 LLMs 模型的训练,而不会破坏预算! 🏦

第一步. GPU

让我们从有趣(和昂贵的 💸💸💸)部分开始吧!

H100 怪兽!图片来自 NVIDIA

购买 GPU 时的主要考虑因素是:

  • 内存(VRAM)

  • 性能(张量核心,时钟速度)

  • 插槽宽度

  • 功率(TDP)

内存

现在深度学习任务需要大量内存。即使是微调 LLMs 也很庞大,而计算机视觉任务,特别是 3D 网络,也可能变得内存密集。自然,最重要的方面是 GPU 的VRAM。对于 LLMs,我建议至少 24 GB 内存,对于计算机视觉任务,我建议不低于 12 GB。

性能

第二个标准是性能,可以通过 FLOPS(每秒浮点运算)来估算:

过去的关键数字是电路中的 CUDA 核心数量。然而,随着深度学习的兴起,NVIDIA 推出了专门的张量核心,每时钟周期可以执行更多的 FMA(融合加法)操作。这些已经被主要深度学习框架支持,2023 年您应该关注这些。

以下是我在经过大量手动工作后编制的按内存分组的 GPU 原始性能图表:

基于 CUDA 和张量核心(TFLOPs)的 GPU 原始性能。

注意在比较不同 GPU 的性能时必须格外小心。不同代/架构的 Tensor 核心不可比。例如,A100 每时钟周期执行 256 FP16 FMA 操作,而 V100“仅”执行 64 个。此外,较旧的架构(Turing, Volta)不支持 32 位张量操作。更难比较的是 NVIDIA 并不总是报告 FMA,甚至在白皮书中也没有,且相同架构的 GPU 可能有不同的 FMA。我一直在这个上碰壁😵‍💫。还要注意,NVIDIA 通常用稀疏性来宣传张量 FLOPS,而这一特性仅在推理时可用。

为了识别性价比最高的 GPU,我使用 eBay API 收集了 eBay 价格,并计算了新卡的每美元相对性能:

基于 CUDA 和张量核心(TFLOPs / USD)的 GPU 相对性能。价格基于当前 eBay 价格(2023 年 9 月)。

我对二手卡做了相同的处理,但由于排名变化不大,所以省略了图表。

为了选择最适合您预算的 GPU,您可以选择拥有最大内存的顶级 GPU。我的推荐是:

根据当前 eBay 价格(2023 年 9 月),不同预算的 GPU 推荐。

如果您想深入了解更多技术细节,建议阅读 Tim Dettmers 的优秀指南选择适合深度学习的 GPU

插槽宽度

在构建多 GPU 系统时,我们需要规划如何将 GPU 物理安装到 PC 机箱中。由于 GPU 越来越大,尤其是游戏系列,这成为了一个问题。消费级主板最多有 7 个 PCIe 插槽,PC 机箱也围绕这个设置进行设计。一块 4090 根据制造商的不同,可能会占用 4 个插槽,因此你可以理解为什么这会成为问题。此外,我们应该在非风冷或水冷的 GPU 之间至少留出 1 个插槽,以避免过热。我们有以下选项:

水冷

水冷变体将占用最多 2 个插槽,但它们价格更贵。你也可以将风冷 GPU 改装成水冷,但这会使保修失效。如果你不选择一体式(AIO)解决方案,你将需要构建自定义水冷系统。如果你想安装多个水冷 GPU,这一点尤其重要,因为 AIO 散热器可能无法适配机箱。自行构建系统有风险,我个人不会在昂贵的显卡上尝试。我只会直接从制造商处购买 AIO 解决方案(风险规避 🙈)。

风冷 2-3 槽显卡和 PCIe 扩展卡

在这种情况下,你可以将显卡交错地安装在 PCIe 插槽上,并通过 PCIe 扩展卡连接显卡。PCIe 扩展卡可以放置在 PC 机箱内部的某个位置,或放在开放空气中。在任何情况下,你都应该确保 GPU 固定好(另见关于 PC 机箱的部分)。

功率(TDP)

现代 GPU 的功耗越来越大。例如,一块 4090 需要 450W,而 H100 可以达到 700W。除了电费,安装三块或更多显卡也成为了一个问题。这在美国尤其如此,因为电源插座的最大功率约为 1800w。

如果你接近电源/电源插座的最大功率,解决这个问题的一个方案是功率限制。要减少 GPU 可以吸取的最大功率,你只需:

sudo nvidia-smi -i <GPU_index> -pl <power_limit>

where:
GPU_index: the index (number) of the card as it shown with nvidia-smi
power_limit: the power in W you want to use

通过将功率限制在 10-20%之间,已被证明可以将性能降低不到 5%,并且能使显卡保持更凉爽(Puget Systems 的实验)。例如,将四块 3090 显卡的功率限制为 20%会将其功耗降低到 1120w,并且可以轻松适配 1600w 的电源/1800w 的插座(假设其余组件消耗 400w)。

步骤 2. 主板和 CPU

构建的下一步是选择一个允许多个 GPU 的主板。在这里主要考虑的是 PCIe 通道。我们需要每块显卡至少有 PCIe 3.0 x8 通道(见Tim Dettmers 的帖子)。PCIe 4.0 或 5.0 更为稀有,对于大多数深度学习用途并不必要。

除了插槽类型外,插槽的间距将决定你可以放置 GPU 的位置。确保你已经检查了间距,并且你的 GPU 确实可以放在你想要的位置。请注意,大多数主板在使用多个 GPU 时会将一些 x16 插槽配置为 x8。获取这些信息的唯一可靠途径是查阅显卡的手册。

最简单的方法是避免花费数小时的研究,并使你的系统未来-proof,是选择一个到处都有 x16 插槽的主板。你可以使用 PCPartPicker 并筛选出具有 7+ PCIe x16 插槽 的主板。这给我们提供了 21 种产品选择。然后我们 缩减列表 选择我们想要的最小 RAM 数量(例如 128 GB),并选择 DDR4 / DDR5 类型,将产品数量减少到 10 种:

基于 PCPartPicker 的主板至少有 7 个 PCIe x16 插槽和 128 GB DDR4/DDR5 RAM。

上述列表中支持的 CPU 插槽是 LGA2011–3 和 LGA2066。接下来,我们转到 CPU 选择,选择具有所需核心数量的 CPU。这些主要用于数据加载和批处理准备。每个 GPU 至少应有 2 核心 / 4 线程。对于 CPU,我们还应检查它支持的 PCIe 通道。过去十年的任何 CPU 应该至少支持 40 条通道(覆盖 4 个 GPU,每个 GPU x8 通道),但最好还是谨慎为好。通过筛选例如 具有上述插槽的 16+ 核 CPU,我们得到以下 CPU:

  • Intel Xeon E5 (LGA2011–3):8 个结果

  • Intel Core i9 (LGA2066):9 个结果

然后,我们根据核心数量、可用性和价格选择我们喜欢的主板和 CPU 组合。

LGA2011–3 和 LGA2066 插槽都已经非常老旧(分别是 2014 年和 2017 年),因此你可以在 eBay 上找到这两个主板和 CPU 的好交易。一块 ASRock X99 WS-E 主板和一颗 18 核的 Intel Xeon E5–2697 V4 在二手状态下可能花费不到 300 美元。不要购买便宜的 ES 或 QS 版本的 CPU,因为这些是工程样品,可能会出现故障 ⚠️️。

如果你想购买更强大和/或更新的组件和/或 AMD CPU,可以查看例如 4+ PCIe x16 插槽的主板,但确保检查插槽间距。

在这个阶段,开始一个 PCPartPicker 构建 是个好主意。 🛠️

PCPartPicker 会为你检查组件之间的兼容性,让你的生活更轻松。

第 3 步。RAM 🐏

在这里,最重要的方面是 RAM 的数量。RAM 在深度学习循环中的不同地方使用:从硬盘加载数据以创建批次、加载模型以及当然是原型设计。所需的 RAM 数量很大程度上取决于您的应用程序(例如,3D 图像数据需要更多的额外 RAM),但您应该以 GPU VRAM 总量的 1 倍到 2 倍为目标。类型至少应为 DDR4,但 RAM 时钟不是非常重要,所以不要把钱花在这里 🕳️。

在购买 RAM 时,您应该确保其形状因素、类型、模块数量和每个模块的内存都与您的主板规格一致(PCPartPicker 是您的好帮手!)。

第 4 步。硬盘

另一个可以节省开支的组件是硬盘 😌。硬盘空间的大小很重要,并且取决于应用程序。您不一定需要超高速硬盘或 NVME,因为它们不会影响您的深度学习性能。数据最终会加载到 RAM 中,为了避免成为瓶颈,您可以简单地使用更多的并行 CPU 工作线程。

第 5 步。电源供应器 (PSU) 🔌

正如我们所见,GPU 是高功耗组件。在设置多 GPU 系统时,选择 PSU 成为一个重要的考虑因素。大多数 PSU 能提供高达 1600w 的功率 —— 这符合美国插座的功率限制。有一些 PSU 可以提供更高的功率,但需要一些研究,并且它们特别针对矿工。

PCPartPicker 为您的构建提供的估算功率。

要确定系统的功率,您可以再次使用 PCPartPicker 来计算您构建的总功率。为了确保安全,我们需要额外增加 10%以上的功率,因为 GPU 的实际功耗有时会超过其规格。

一个重要的标准是PSU 效率,这由 80 PLUS 评级标记。电源将达到其宣传的功率,但在过程中会损失一些功率。80 PLUS 铜牌电源的效率为 82%,而例如金牌电源的效率为 87%。如果我们有一个功率需求为 1600w 的系统,并且我们在 20% 的时间内使用它,我们将节省 22 美元每年,前提是电价为 0.16 美元/千瓦时。在比较价格时,请将这一点纳入您的计算中。

PSU 效率评级。表格来源于 techguided

在满负荷运行时,一些 PSU 比其他的噪音更大,因为它们使用高速运转的风扇。如果您在靠近机箱的地方工作(或睡觉!),这可能会有一些影响,因此查看手册中的分贝数是个好主意 😵。

在选择电源时,我们需要确认它是否有足够的连接器来支持所有部件。特别是 GPU 使用 8 针(或 6+2)电缆。这里有一个重要的提示:对于 GPU 的每个电源插槽,我们应该使用单独的 8 针电缆,而不是使用同一根电缆的多个输出(串联连接)。8 针电缆通常额定功率约为 150w。当使用单根电缆为多个电源插槽供电时,GPU 可能无法获得足够的电力,从而导致降频。

步骤 6. 机箱

最后但同样重要的是,选择一个 PC 机箱并不简单。GPU 可以非常庞大,一些机箱可能无法容纳它们。例如,4090 的长度可以达到 36 厘米 👻!

此外,使用 PCIe 延长条安装 GPU 可能需要一些技巧。有一些较新的机箱允许安装额外的显卡,特别是像 Phanteks Enthoo 719 这样的双系统机箱。另一个选择是 Lian-Li O11D EVO,它可以通过 Lian-Li Upright GPU Bracket 以直立位置容纳 GPU。我没有这些机箱,所以不确定它们如何适配,例如多个 3090 / 4090。然而,即使你的 PC 机箱不直接支持直立安装,你仍然可以使用 Lian-Li 支架来直立安装 GPU。你需要在机箱上钻 2-3 个孔,但并不是很复杂。

使用 Lian Li 直立支架将 GPU 安装在直立位置。

结束

希望你喜欢阅读本指南,并且发现了一些有用的提示。本指南旨在帮助你研究如何构建多 GPU 系统,而不是替代研究。如果你有任何问题或意见,请随时发给我。如果我在上述内容中有任何错误,我非常感谢你留下评论或私信,以便进一步改进 🙏!

注意:除非另有说明,所有图片均由作者提供。

如何使用 Python 构建一个类似 Shazam 的 Telegram 机器人

原文:towardsdatascience.com/how-to-build-a-shazam-like-telegram-bot-using-python-98dc081c53d5

一个教程,教你如何创建和部署一个可以实时接收音乐并帮助你查找歌曲标题和歌手的 Telegram 机器人

Eugenia AnelloTowards Data Science Eugenia Anello

·发布于 Towards Data Science ·阅读时间 9 分钟·2023 年 1 月 24 日

--

照片由 Austin Neill 提供,来源于 Unsplash

当你在外面听到音乐,比如在酒吧或商店里,如果你非常喜欢这首歌,难道不会想知道这首歌的标题和歌手的名字吗?

我遇到过很多次这种情况,只有一个解决方案。Shazam。下载它并点击弹出按钮来识别音乐。

在这篇文章中,我们将构建一个类似的应用来发现歌曲和歌手。开发这个数据科学项目的一种快速简便的方法是使用 Python 创建一个 Telegram 机器人。

当你听音乐时,你可以录制声音。这个机器人会将音频转录成文本,并为你找到歌曲的标题和歌手。让我们开始吧!

目录:

  • 第一部分:用 Python 创建 Telegram 机器人

  • 第二部分:将 Telegram 机器人部署到 Fly.io

第一部分:用 Python 创建 Telegram 机器人

作者插图

本教程将分为两个部分。在第一部分中,我们将专注于创建一个可以转录音频并发现歌曲信息的 Telegram 机器人。之后,我们会将这个 Telegram 机器人部署到 fly.io 上,这个平台是众多支持云端应用构建、运行和扩展的平台之一。

  • 要求

  • 安装库

  • 创建 Telegram 机器人的第一步

  • 转录歌曲的音频

  • 了解有关歌曲的信息

要求:

在开始用 Python 编程之前,需要遵循一些步骤。

  • 打开 Telegram,搜索 @botfather 并点击第一个结果。

  • 按下开始按钮以开始与 BotFather 对话。

  • 输入/newbot并按照截图中的指示操作。创建机器人后,你将收到 BotFather 发给你的令牌,从而允许你访问 Telegram API。

  • 输入你的 Telegram 机器人名称。

安装库

首先,安装一个允许用少量代码创建 Telegram 机器人的 Python 库。它叫做pyTelegramBotAPI

pip install pyTelegramBotAPI

为了转录音频,我将使用Steamship的 API。在安装包之前,你需要安装 nodej。在 Windows 上,你需要从这里安装并将路径添加到 PATH 环境变量中,而在 ubuntu 上你需要运行两行代码:

sudo apt install nodejs
sudo apt install npm

然后你可以在你的 Python 虚拟环境中安装 Steamship CLI。

pip install steamship
npm install -g @steamship/cli
ship login

安装完成后,我们将进入第三个 API,它允许我们从音频中发现歌曲。这可以通过访问 Google 搜索结果来实现,使用的 API 叫做SerpApi

pip install google-search-results

创建 Telegram 机器人的第一步

一旦你获得了 Telegram 的 API 令牌并创建了 Telegram 机器人,它应该会出现在你的 Telegram 应用中,我们可以开始探索pyTelegramBotAPI,它提供了用少量代码创建 Telegram 机器人的 Python 实现。

还有许多其他不同的 Python 库可以用来构建 Telegram 机器人,但由于我发现许多教程使用了这个库,且文档做得很好,我选择了它来完成我的小项目。

import os
import json
...
from steamship import Steamship, TaskState
import telebot
from serpapi import GoogleSearch

f = open("cred.json", "rb")
params = json.load(f)
BOT_TOKEN = params['BOT_TOKEN']
bot = telebot.TeleBot(BOT_TOKEN)

我们有一个文件cred.json,其中包含了机器人令牌以及 Steamship 和 SerpApi 的 API 密钥:

{
"BOT_TOKEN":<your_bot_token>,
"API_KEY":<your_serpapi_key>,
"STEAM_API_KEY":<your_steamship_api_key>
 }

在第 11 行,我们简单地创建了一个TeleBot实例,它是一个提供处理 Telegram 消息功能的类。例如,让我们定义一个消息处理器,如果你在聊天中输入/start/hello,它会返回消息“插入你的歌曲音频”。

@bot.message_handler(commands=['start', 'hello'])
def send_welcome(message):
    bot.reply_to(message, "Insert the audio of your song!")

bot.infinity_polling()

在代码末尾,我们使用bot.infinity_polling()来启动机器人。

作者提供的截图

运行python bot.py后,你应该会看到类似于上图的结果。

在函数send_welcome之后,我们可以定义另一个函数,叫做telegram_bot,它将处理“语音”类型的音频文件。

@bot.message_handler(content_types=['voice'])
def telegram_bot(message,bot_token=params["BOT_TOKEN"]):
    # insert audio
    file_info = bot.get_file(message.voice.file_id)
    # extract telegram's url of the audio 
    audio_url = 'https://api.telegram.org/file/bot{}/{}'.format(bot_token,file_info.file_path)

bot.infinity_polling()

我们希望获得你发送给机器人的音频文件的公开 URL。要检查 URL 是否正确,请在浏览器中复制以下路径,替换令牌和文件路径。如果有效,它应该会将音频下载到你的本地电脑。

https://api.telegram.org/file/bot<bot_token>/<file_path>

我们需要做的最后一个操作是将 OGA 格式的音频转换为 MP3 格式。这是一个重要步骤,因为 OGA 文件不支持转录。

为了将音频转换为 mp3 文件,我们将使用 ffmpeg 包装器,它允许使用命令行转换各种音频文件。在使用之前,你需要先安装它。如果你在 Windows 上工作,请查看这个 指南,它展示了所有步骤,而在 Linux 上,你只需使用命令 sudo apt install ffmpeg

在函数 convert_oga_to_mp3 的第 4 行进行转换后,我们需要再次提取新音频文件的 URL,该文件的格式会有所不同。

转录音频的歌曲

现在,我们已经到了教程中最有趣的部分,这使我们达到了 Telegram 机器人的目标。在继续之前,登录 这里 以获取 API 密钥。首先,我们想使用 Steamship API 转录音频。函数 transcribe_audio 以之前检索到的音频 URL 和 Steamship 的实例为输入,Steamship 是一个提供许多 AI 应用功能和方法的类。在这种情况下,它专注于音频转录。

代码可以用几行代码来总结:

  • 在第 3 行,我们指定将使用 audio_markdown 包来转录音频,然后生成 Markdown 输出。

  • 在下一行,我们通过调用 invoke 来调用方法 transcribe_url。我们还需要传递音频的 URL。

  • 在函数的中间,我们调用方法 get_markdown,这将允许我们接收音频的转录。为了避免无限循环,如果不成功,重试次数被限制为 100。

  • 该函数在最后返回转录结果,我们在 Telegram 机器人上获得结果文本。

发现有关歌曲的信息

在获得转录后,我们进入下一步,即发现歌曲的标题和歌手。这可以通过使用 SerpApi 来实现,它是一个 API,允许访问 Google 和其他网站的结果。

我们需要向 GoogleSearch 传递一个字典,包含如歌曲歌词、SerpApi 的 API 密钥等参数,并从 Google 获得结果。它返回一个 JSON 输出,可以转换为 Python 字典以获取信息。之后,一条文本消息将发送回发件人,这可能是未发现的/已实现的发现。

应用程序应该如何工作的示意图。

第二部分:将 Telegram 机器人部署到 Fly.io

到目前为止,我们可以通过运行以下命令行来使机器人工作:

python bot.py

但这意味着机器人只有在你运行此代码时才会工作,这并不实用。最好是让 Telegram 机器人始终可用,让人们尝试。

因此,我们需要部署 Telegram 机器人。最初,我想使用 Heroku,这是另一个用于部署应用程序的闭源平台,但它不再提供免费层。

所以,我选择了 Fly.io 作为云服务,因为它提供了免费计划,并且文档齐全。

  • 创建 requirements.txt 和 Dockerfile

  • 连接到 Fly.io

  • 启动应用程序

  • 部署应用程序

1. 创建 requirements.txt 和 Dockerfile

要将其投入生产,我们需要 requirements.txt 文件,其中包含所有的 Python 依赖项。可以通过在终端中运行命令行 pipreqs 自动创建该文件。你必须安装 pipreqs 以使命令行有效。项目所需的 Python 包如下:

google_search_results==2.4.1
pyTelegramBotAPI==4.9.0
python-dotenv==0.21.1
requests==2.28.1
steamship==2.3.5

除了这个文件,我们还需要另一个文件,名为 Dockerfile,其内容应如下:

FROM python:3.10.2

WORKDIR /bot

COPY requirements.txt /bot/
RUN pip install -r requirements.txt
RUN apt-get update
RUN apt-get install ffmpeg -y
RUN apt-get install nodejs -y
RUN apt-get install npm -y

COPY . /bot

CMD python bot.py

2. 连接到 Fly.io

第三个要求是安装 flyctl,一个可以部署我们应用程序的命令行工具。你可以在 这里 找到说明,具体取决于你的操作系统。

如果你还没有创建 Fly.io 账户,你需要通过运行以下命令行来创建一个:

flyctl auth signup

如果你已经有账户,你只需要通过终端复制登录:

fly auth login

3. 启动应用程序

要开始这个项目,我们可以输入以下命令行:

flyctl launch

系统会要求你提供以下信息:

  • 写下应用程序名称。

  • 选择部署的区域。

  • 如果你想要设置一个 Postgresql 数据库。此情况下,答案是否定的。

  • 如果你现在想要设置 Upstash Redis 数据库。此情况下,答案是否定的。

  • 如果我们现在想要部署的话。答案是现在不行。我们稍后再部署。

在部署应用程序之前,我还决定利用 fly.io 提供的一个功能,即秘密管理。正如你所知道的,凭据是敏感的,你可能希望不将你的秘密透露给除你自己以外的任何人。这可以通过以下命令行实现:

flyctl secrets set BOT_TOKEN=<your_bot_token>
flyctl secrets set STEAM_TOKEN=<your_steam_api>
flyctl secrets set API_KEY=<your_serpapi_key>

运行这些命令行后,Python 代码需要进行修改。我们不再使用包含凭据的 JSON 文件,这些凭据直接存储在 fly.io 平台上。例如,我们可以通过以下方式获取 Telegram 机器人的令牌:

from dotenv import load_dotenv

load_dotenv()
bot = telebot.TeleBot(os.getenv("BOT_TOKEN"))

你需要对其他 API 密钥做相同的操作。你可以通过以下命令行查看命令列表:

flyctl secrets list

4. 部署应用程序

最终,我们达到了珠穆朗玛峰的顶峰!只需再输入一条命令,就完成了:

flyctl deploy

这样,我们的包含 Telegram 机器人的 Docker 容器就构建并部署完成了。代码将运行在云端,而不再是在本地计算机上。现在 Telegram 机器人将开始工作。

最终思考:

我希望你欣赏这个数据科学项目。它可以给你提供其他可能应用的想法。网上有很多资源可以满足你的需求。你只需要创造力、耐心和努力工作。如果你拥有这些条件,那么你能做的事没有限制。GitHub 代码在这里。感谢阅读。祝你有美好的一天!

如何使用 Python 构建 ELT

原文:towardsdatascience.com/how-to-build-an-elt-with-python-8f5d9d75a12e

提取、加载和转换数据

Marie TruongTowards Data Science Marie Truong

·发表于 Towards Data Science ·阅读时间 7 分钟·2023 年 2 月 7 日

--

图片由 JJ Ying 提供,来源于 Unsplash

ELT(提取、加载、转换)是一种现代的数据集成方法,与 ETL(提取、转换、加载)略有不同。ETL 在将数据加载到数据仓库之前进行转换,而 ELT 则将原始数据直接加载到数据仓库中,并使用 SQL 进行转换。

构建 ELT 是数据和分析工程师工作中非常重要的一部分,它也可以成为数据分析师和科学家更广泛领域的有用技能,或者是那些构建完整作品集的求职者。

在这篇文章中,我们将使用来自 dummyJSON 的数据在 Python 中构建一个简短的 ELT 管道。dummyJSON 是一个虚假的 REST API,提供 9 种类型的资源:

dummyjson.com 的截图

我们将尝试找出哪些客户在我们的虚拟商店中花费了最多的钱。

这个脚本将包括 3 个步骤:

  1. 从 dummyJSON API 提取数据

  2. 将原始数据加载到 BigQuery

  3. 执行查询以进行分析

让我们开始构建我们的数据管道吧!

提取数据

我们需要从 API 中检索 2 个资源:购物车和用户。

让我们创建一个函数,执行 API 调用并返回 JSON 数据:

import requests

ENDPOINT = "https://dummyjson.com/"
def make_api_call(resource):
    ENDPOINT = "https://dummyjson.com/"
    response = requests.get(f"{ENDPOINT}{resource}") # making a request to the correct endpoint
    if response.status_code == 200:
        return response.json()
    else:
        raise Exception(response.text)

print(make_api_call("carts"))

我们使用 requests 库来发出简单的 HTTP GET 请求。我们通过状态码检查请求是否成功,并返回 JSON 数据。

 {
  "carts": [
    {
      "id": 1,
      "products": [
        {
          "id": 59,
          "title": "Spring and summershoes",
          "price": 20,
          "quantity": 3,
          "total": 60,
          "discountPercentage": 8.71,
          "discountedPrice": 55
        },
        {...}
        // more products
      ],
      "total": 2328,
      "discountedTotal": 1941,
      "userId": 97,
      "totalProducts": 5,
      "totalQuantity": 10
    },
    {...},
    {...},
    {...}
    // 20 items
  ],
  "total": 20,
  "skip": 0,
  "limit": 20
}

我们已经有了关于订单的数据!让我们尝试获取客户数据:

{
  "users": [
    {
      "id": 1,
      "firstName": "Terry",
      "lastName": "Medhurst",
      "maidenName": "Smitham",
      "age": 50,
      "gender": "male",
      "email": "atuny0@sohu.com",
      "phone": "+63 791 675 8914",
      "username": "atuny0",
      "password": "9uQFF1Lh",
      "birthDate": "2000-12-25",
      "image": "https://robohash.org/hicveldicta.png?size=50x50&set=set1",
      "bloodGroup": "A−",
      "height": 189,
      "weight": 75.4,
      "eyeColor": "Green",
      "hair": {
        "color": "Black",
        "type": "Strands"
      },
      "domain": "slashdot.org",
      "ip": "117.29.86.254",
      "address": {
        "address": "1745 T Street Southeast",
        "city": "Washington",
        "coordinates": {
          "lat": 38.867033,
          "lng": -76.979235
        },
        "postalCode": "20020",
        "state": "DC"
      },
      "macAddress": "13:69:BA:56:A3:74",
      "university": "Capitol University",
      "bank": {
        "cardExpire": "06/22",
        "cardNumber": "50380955204220685",
        "cardType": "maestro",
        "currency": "Peso",
        "iban": "NO17 0695 2754 967"
      },
      "company": {
        "address": {
          "address": "629 Debbie Drive",
          "city": "Nashville",
          "coordinates": {
            "lat": 36.208114,
            "lng": -86.58621199999999
          },
          "postalCode": "37076",
          "state": "TN"
        },
        "department": "Marketing",
        "name": "Blanda-O'Keefe",
        "title": "Help Desk Operator"
      },
      "ein": "20-9487066",
      "ssn": "661-64-2976",
      "userAgent": "Mozilla/5.0 ..."
    },
    {...},
    {...}
    // 30 items
  ],
  "total": 100,
  "skip": 0,
  "limit": 30
}

这次,有些事情引起了我们的注意:总共有 100 个用户,但我们只收到了 30 个。

作者截图

因此,我们需要再次调用那个 API,直到我们获取所有数据,跳过我们已经拥有的数据。然而,我们不希望将那些totalskiplimit键发送到数据仓库;让我们只保留用户和购物车。

这是我们更新后的函数:

def make_api_call(resource):
    ENDPOINT = "https://dummyjson.com/"
    results_picked = 0
    total_results = 100 #We don't know yet, but we need to initialize
    all_data = []
    while results_picked < total_results:
        response = requests.get(f"{ENDPOINT}{resource}", params = {"skip" : results_picked})
        if response.status_code == 200:
            data = response.json()
            rows = data.get(resource)
            all_data += rows #concatening the two lists
            total_results = data.get("total")
            results_picked += len(rows) #to skip them in the next call
        else:
            raise Exception(response.text)
    return all_data

users_data = make_api_call("users")
print(len(users_data))

这一次,我们有了 100 个用户!

加载数据

现在,是时候将数据上传到 BigQuery 了。我们将使用BigQuery Python 客户端库

来源:cloud.google.com/bigquery/docs/batch-loading-data

从文档中可以看到,可以将本地文件加载到 BigQuery 中。目前,我们的 JSON 只是一个字典。让我们将其下载到本地文件中。

我们将使用原生库json并将 JSON 数据写入文件。需要记住的是,BigQuery 接受换行符分隔的 JSON 格式,而不是逗号分隔格式。

import json    

def download_json(data, resource_name):
    file_path = f"{resource_name}.json"
    with open(file_path, "w") as file:
        file.write("\n".join([json.dumps(row) for row in data]))

download_json(carts_data, "carts")
download_json(users_data, "users")

我们现在可以检查我们的 carts.json 文件是否为正确的 JSON 格式:

{"id": 1, "products": [{"id": 59, "title": "Spring and summershoes", "price": 20, "quantity": 3, "total": 60, "discountPercentage": 8.71, "discountedPrice": 55}, {"id": 88, "title": "TC Reusable Silicone Magic Washing Gloves", "price": 29, "quantity": 2, "total": 58, "discountPercentage": 3.19, "discountedPrice": 56}, {"id": 18, "title": "Oil Free Moisturizer 100ml", "price": 40, "quantity": 2, "total": 80, "discountPercentage": 13.1, "discountedPrice": 70}, {"id": 95, "title": "Wholesale cargo lashing Belt", "price": 930, "quantity": 1, "total": 930, "discountPercentage": 17.67, "discountedPrice": 766}, {"id": 39, "title": "Women Sweaters Wool", "price": 600, "quantity": 2, "total": 1200, "discountPercentage": 17.2, "discountedPrice": 994}], "total": 2328, "discountedTotal": 1941, "userId": 97, "totalProducts": 5, "totalQuantity": 10}
// other carts
{"id": 20, "products": [{"id": 66, "title": "Steel Analog Couple Watches", "price": 35, "quantity": 3, "total": 105, "discountPercentage": 3.23, "discountedPrice": 102}, {"id": 59, "title": "Spring and summershoes", "price": 20, "quantity": 1, "total": 20, "discountPercentage": 8.71, "discountedPrice": 18}, {"id": 29, "title": "Handcraft Chinese style", "price": 60, "quantity": 1, "total": 60, "discountPercentage": 15.34, "discountedPrice": 51}, {"id": 32, "title": "Sofa for Coffe Cafe", "price": 50, "quantity": 1, "total": 50, "discountPercentage": 15.59, "discountedPrice": 42}, {"id": 46, "title": "women's shoes", "price": 40, "quantity": 2, "total": 80, "discountPercentage": 16.96, "discountedPrice": 66}], "total": 315, "discountedTotal": 279, "userId": 75, "totalProducts": 5, "totalQuantity": 8}

现在让我们尝试上传我们的文件!

首先,我们需要下载 Python 客户端库

完成后,我们需要下载服务账户密钥并创建一个环境变量,以告诉 BigQuery 我们的凭证存储在哪里。在终端中,我们可以输入以下命令:

export GOOGLE_APPLICATION_CREDENTIALS=service-account.json

然后,我们可以编写 Python 函数。幸运的是,BigQuery 文档为我们提供了代码示例:

作者截图

我们可以使用这个示例定义一个新函数:

def load_file(resource, client):
    table_id = f"data-analysis-347920.medium.dummy_{resource}"

    job_config = bigquery.LoadJobConfig(
        source_format=bigquery.SourceFormat.NEWLINE_DELIMITED_JSON, 
        autodetect=True,
        write_disposition="write_truncate"
    )

    with open(f"{resource}.json", "rb") as source_file:
        job = client.load_table_from_file(source_file, table_id, job_config=job_config)

        job.result()  # Waits for the job to complete.

        table = client.get_table(table_id)  # Make an API request.
        print(
            "Loaded {} rows and {} columns to {}".format(
                table.num_rows, len(table.schema), table_id
            )
        )

client = bigquery.Client()
load_file("carts", client)
load_file("users", client)
Loaded 20 rows and 7 columns to data-analysis-347920.medium.dummy_carts
Loaded 100 rows and 27 columns to data-analysis-347920.medium.dummy_users

我们告诉 BigQuery 每次都截断我们的表格,因此如果我们重新运行脚本,现有行将被覆盖。

我们现在在 BigQuery 中有了我们的两个表:

作者截图

转换数据

现在是时候进入 ELT 中的最后一部分了!

我们希望有一个包含用户及其在我们商店消费金额的表格。

让我们连接两个表格以获取这些信息:

SELECT 
  u.id AS user_id,
  u.firstName AS user_first_name, 
  u.lastName AS user_last_name,
  SUM(total) AS total_spent
FROM `data-analysis-347920.medium.dummy_users` u
LEFT JOIN  `data-analysis-347920.medium.dummy_carts` c
ON u.id= c.userId
GROUP BY u.id,user_first_name,user_last_name
ORDER BY total_spent DESC

作者截图

看来 Trace Douglas 是我们的最高消费者!让我们将这个表格作为 ELT 的一部分添加。

query= """
SELECT 
  u.id AS user_id,
  u.firstName AS user_first_name, 
  u.lastName AS user_last_name,
  SUM(total) AS total_spent
FROM `data-analysis-347920.medium.dummy_users` u
LEFT JOIN  `data-analysis-347920.medium.dummy_carts` c
ON u.id= c.userId
GROUP BY u.id,user_first_name,user_last_name
"""

query_config= bigquery.QueryJobConfig(
    destination = "data-analysis-347920.medium.dummy_best_spenders", 
    write_disposition= "write_truncate"
    )
client.query(query, job_config= query_config)

我移除了 ORDER BY,因为这在计算上很昂贵;我们仍然可以在查询dummy_best_spenders表时对结果进行排序。

让我们检查一下我们的表格是否已创建:

作者截图

就这样,我们用几行代码完成了第一个 ELT!

进一步探索 ELT

在处理实际和更大的项目时,还有一些其他事项需要考虑:

  • 我们将处理更大的数据量。每天都有新数据,所以我们必须逐步追加数据到表格中,而不是每天处理所有数据。

    • 随着我们的 ELT 变得越来越复杂,涉及多个数据源,我们可能需要使用像 Airflow 或 Prefect 这样的工作流编排工具。
    • 我们只能将小于 10MB 的文件直接加载到 BigQuery 中。要加载更大的文件,我们需要先将其加载到 Cloud Storage 中。

- 资源

  • 希望你喜欢这篇文章!如果你喜欢,请关注我获取更多关于 Python、SQL 和分析的内容。

  • 成为会员并阅读 Medium 上的所有故事。你的会员费用将直接支持我和你阅读的其他作者。你还将获得对 Medium 上每个故事的完全访问权限。

  • ## 通过我的推荐链接加入 Medium — Marie Truong

- 阅读 Marie Truong(以及 Medium 上其他成千上万位作者)的每个故事。你的会员费用直接支持…

如何构建一个互联的多页面 Streamlit 应用

原文:towardsdatascience.com/how-to-build-an-interconnected-multi-page-streamlit-app-3114c313f88f?source=collection_archive---------8-----------------------#2023-07-24

从规划到执行——我是如何构建 GPT 实验室的

Dave LinTowards Data Science Dave Lin

·

关注 发表在 Towards Data Science ·9 分钟阅读·2023 年 7 月 24 日

--

照片由 Clément HélardotUnsplash 上提供

注意:本文最初发布于 Streamlit 博客。我希望在这里与 Medium 社区分享。

哇!自从我首次发布关于 构建 GPT 实验室的经验教训 的博客文章以来,这三个月真是令人难以置信! 🚀

感谢你们的巨大支持,GPT Lab 已获得超过 9K 的应用查看次数、1150+ 的独特登录用户、900+ 次与助手的会话、650+ 个测试过的提示以及 180+ 个创建的助手。该应用还在 Streamlit 应用画廊中与其他优秀应用一起展示。

gptlab.streamlit.app/?embed=true

你们中的许多人问我,“你是如何规划和构建如此大型的 Streamlit 应用的?”迫不及待地想回答,我决定将 GPT Lab 开源。

在这篇文章中,我将分享关于这个雄心勃勃项目的策略和思维过程的见解。我希望这能激励你们将 Streamlit 推向极限,并将你们雄心勃勃的应用变为现实。

💡 想跳到下一部分?查看 应用程序 代码

规划一个大型的 Streamlit 应用

构建大型 Streamlit 应用,例如 GPT Lab,需要仔细的规划,而不仅仅是将代码拼凑在一起。对于 GPT Lab,我专注于规划这四个关键方面:

  1. 功能和用户体验。 应用程序将做什么?我们旨在提供什么样的用户体验?

  2. 数据模型。 数据将如何持久化?应该存储在数据库中的内容与会话状态变量中的内容应该如何区分?

  3. 代码结构。 应该如何构建应用以确保模块化、可维护性和可扩展性?

  4. 会话状态。 需要哪些会话状态变量来链接用户界面?

理解这些方面提供了我所尝试构建的更清晰的视图,并提供了一个系统性处理复杂任务的框架。

让我们更详细地探讨每个方面。

功能和用户体验:创建初始规范和低保真用户体验模型

首先,我创建了一个简单的规范文档(或称为“规范”),概述了整体范围和方法。我还包括了一个站点地图,详细说明了我想支持的用例。该规范为我提供了明确的路线图以及衡量进展的手段。

这是原始规范的摘录:

范围

构建一个平台,允许生成式 AI (GA) 机器人爱好者为他们的朋友和家人创建自己的基于 GPT-3 的聊天机器人。目标是验证足够多的 GA 机器人爱好者是否愿意构建他们的利基领域机器人。

方法

一个公共的 Streamlit 网站,允许用户与四个预训练的教练机器人中的一个互动,或创建并与他们的机器人互动。

与大多数开发项目一样,我做了一些修改。但原始的站点地图大部分保持不变,因为我能够实现大多数计划中的功能。

这是站点地图的最终版本:

GPT Lab
│
├── Home
│
├── Lounge
│
├── Assistant
│   ├── Search for assistant
│   ├── Assistant details
│   ├── Active chat
│   └── Chat recap
│
├── Lab
│   ├── Step 1: initial prompt + model config
│   ├── Step 2: test chat
│   ├── Step 3: other configs
│   └── Step 4: confirmation
│
├── FAQ
│
└── Legal
    ├── Terms
    └── Privacy policy

我不能过分强调功能规划的重要性。它提供了一个路线图、衡量进展的方法以及思考数据模型的起点。

数据模型:确定模式

从一开始,我就认识到后端数据存储对于持久化用户、助手和会话记录至关重要。在考虑了我的选择后,我决定使用 Google Firestore,因为它具有可扩展性、实时能力和慷慨的免费层。为了支持未来的扩展,我战略性地设计了数据模型。尽管当前应用程序仅使用了其潜力的一部分,但可以在 GPT Lab 中添加提示版本控制。这将使用户能够编辑或恢复他们的助手。

💡 注意:在应用程序后台和数据模型中,助手被称为机器人,尽管我之前坚持不在用户界面中称其为机器人 😅。

现在,让我们深入探讨 GPT Lab 中的四个主要 Firestore 集合:users、user_hash、bots 和 sessions。

用户和 user_hash

用户集合是应用程序存储有关用户信息的地方。为了保护用户隐私,应用程序不会存储任何个人可识别信息(PII)。相反,每个用户仅与其 OpenAI API 密钥的单向哈希值相关联。每当用户创建助手或开始/结束与助手的会话时,指标字段会递增。这允许在应用程序内进行基本的分析收集。

Users Collection
   |
   | - id: (Firestore auto-ID)
   | - user_hash: string (one-way hash value of OpenAI API key)
   | - created_date: datetime
   | - last_modified_date: datetime
   | - sessions_started: number
   | - sessions_ended: number
   | - bots_created: number

Google Firestore 不提供确保集合中文档字段值唯一性的方法,因此我创建了一个名为 user_hash 的单独集合。这确保每个唯一的 API 密钥只有一个关联的用户记录。每个用户文档唯一地关联到一个 user_hash 文档,每个 user_hash 文档可能与一个用户文档相关联。数据模型足够灵活,以适应未来更改 API 密钥的用户(用户可以使用旧的 API 密钥登录,然后更换为新的密钥)。

User_hash Collection
   |
   | - id = one-way hash value of OpenAI API key
   | - user_hash_type: string (open_ai_key)
   | - created_date: datetime

机器人

机器人集合存储 AI 助手的配置。每个 AI 助手的核心是其大型语言模型(LLM)、模型配置和提示。为了在未来实现提示和模型配置的适当版本控制,model_configs 和 prompts 被建模为子集合(GPT Lab 的愿景之一是成为你提示的存储库)。

为了最小化子集合读取(以便无需不断查询子集合以获取活动记录),活动子集合的文档 ID 也在文档级别存储。session_type 字段指示助手是否处于头脑风暴或辅导会话中,这会影响会话消息的截断技术。

最后,当用户开始或结束与助手的会话时,指标字段会递增。

Bots Collection
   |
   | - id: (Firestore auto-ID)
   | - name: string
   | - tag_line: string
   | - description: string
   | - session_type: number
   | - creator_user_id: string
   | - created_date: datetime
   | - last_modified_date: datetime
   | - active_initial_prompt_id: string
   | - active_model_config_id: string
   | - active_summary_prompt_id: string
   | - showcased: boolean
   | - is_active: boolean
   |
   v
   |--> Model_configs subcollection
   |     |
   |     | - config: map
   |     |     | - model: string 
   |     |     | - max_tokens: number 
   |     |     | - temperature: number 
   |     |     | - top_p: number 
   |     |     | - frequency_penalty: number 
   |     |     | - presence_penalty: number 
   |     | - created_date: datetime
   |     | - is_active: boolean
   |
   v
   |--> Prompts subcollection
         |
         | - message_type: string
         | - message: string
         | - created_date: datetime
         | - is_active: boolean
         | - sessions_started: number
         | - sessions_ended: number

Sessions

sessions 集合存储会话数据。它包含两种类型的会话:实验室会话(用于测试提示)和助手会话(用于与创建的助手聊天)。为了减少对机器人文档频繁检索的需要,它的信息会在会话文档中缓存。这是有道理的,因为如果实现编辑助手的用例,机器人文档可能会出现偏差。

messages_str 字段存储发送到 OpenAI LLM 的最新有效负载。此功能允许用户恢复之前的助手会话。messages 子集合存储实际的聊天消息。注意,实验室会话聊天消息不会被存储。

为了确保用户的机密性和隐私,OpenAI 请求有效负载和会话消息在保存到数据库之前会被加密。这种数据模型允许用户重新启动以前的会话并继续与助手聊天。

Sessions Collection
   |
   | - id: (Firestore auto-ID)
   | - user_id: string
   | - bot_id: string
   | - bot_initial_prompt_msg: string
   |
   | - bot_model_config: map
   |     | - model: string 
   |     | - max_tokens: number 
   |     | - temperature: number 
   |     | - top_p: number 
   |     | - frequency_penalty: number 
   |     | - presence_penalty: number 
   |
   | - bot_session_type: number
   | - bot_summary_prompt_msg: string
   | - created_date: datetime
   | - session_schema_version: number
   | - status: number
   | - message_count: number
   | - messages_str: string (encrypted)
   |
   v
   |--> Messages subcollection
         |
         | - created_date: datetime
         | - message: string (encrypted)
         | - role: string

通过从一开始就仔细考虑所有潜在的用例,我创建了一个未来-proof 的数据模型,能够满足应用程序不断发展的需求和功能。在接下来的部分中,我们将深入了解后端应用程序代码的结构,以了解它如何支持和实现这个强大的数据模型。

代码结构:为了可扩展性和模块化进行结构化

我创建 GPT Lab 是为了赋能那些技术水平低或没有技术技能的用户,使他们能够构建自己的基于提示的 LLM AI 应用,而无需担心底层基础设施。我的目标是最终提供后端 API,将用户的自定义前端应用(无论是否使用 Streamlit)与他们的 AI 助手连接。这激励我设计了一个解耦架构,将前端 Streamlit 应用与后端逻辑分开。

后端代码的结构如下:

+----------------+     +-------------------+     +-------------------+     +------------+
|                |     |                   |     |                   |     |            |
|  Streamlit App |<--->| util_collections  |<--->| api_util_firebase |<--->|  Firestore |
|                |     | (users, sessions, |     |                   |     |            |
|                |     |  bots)            |     |                   |     |            |
+----------------+     +-------------------+     +-------------------+     +------------+
                             |
                             |
                             v
                     +-----------------+     +------------+
                     |                 |     |            |
                     | api_util_openai |<--->|   OpenAI   |
                     |                 |     |            |
                     +-----------------+     +------------+

模块如下:

  • api_util_firebase 处理与 Firestore 数据库的 CRUD 操作。

  • api_util_openai 与 OpenAI 的模型进行交互,向上游模型提供统一的聊天模型,修剪聊天消息,并尝试检测和防止提示注入攻击。

  • api_util_usersapi_util_sessionsapi_util_bots 是它们各自 Firestore 集合的接口。它们与 api_util_firebase 和 api_util_openai 进行交互,并实现 GPT Lab 特定的业务逻辑。

这种设计使得代码的不同部分可以独立开发、测试和扩展。它还建立了一个更简单的迁移路径,将后端 util_collections 模块转换为 Google Cloud Functions,这些函数可以通过 API Gateways 公开。

会话状态:管理 UI 和用户流程

正如在 第一篇博客文章 中解释的,我使用了会话状态变量来控制和管理 Streamlit 页面上的功能。以下说明了这些变量在整个应用中的使用方式:

home.py

  • 用户 控制是否渲染 OpenAI API 密钥模块

pages/1_lounge.py

  • 用户 控制是否渲染 OpenAI API 密钥模块,启用助手选择,并显示我的助手标签页。

  • 用户选择与助手互动后,助手详细信息会存储在 bot_info 中。

pages/2_assistant.py

  • 用户 控制是否渲染 OpenAI API 密钥模块。

  • bot_infosession_idsession_ended 决定显示哪个屏幕变体。

  • bot_info 不存在:检查 assistant_id 是否在 URL 参数中。如果没有,则提示用户搜索助手。

  • bot_infosession_id 存在,并且 session_ended 为 false:显示聊天会话屏幕。

  • bot_infosession_id 存在,并且 session_ended 为 true:显示聊天会话回顾屏幕。

  • 在聊天会话中,session_msg_list 存储对话内容。

pages/3_lab.py

  • 用户 控制是否渲染 OpenAI API 密钥模块以及是否允许用户在实验室中开始创建助手。

  • lab_active_step 控制渲染哪个实验室会话状态:

  • 如果为 1:渲染步骤 1 UI 以设置助手的初始提示和模型。

  • 如果为 2:渲染步骤 2 UI 测试与助手的聊天。

  • 如果为 3:渲染步骤 3 UI 完成助手详细信息。创建时,机器人记录会在 Firestore DB 中创建,并将文档 ID 保存到 lab_bot_id。

  • 如果为 4 且 lab_bot_id 已设置:渲染步骤 4 UI 显示助手创建确认。

  • 在测试聊天会话期间,lab_msg_list 存储测试消息。通过使用单独的 lab_bot_idbot_info,我可以让用户在休息室/助手和实验室之间来回跳转,而不会丢失每个部分的进度。

在完成前期规划后,接下来的执行变得更加可控。

总结

在这篇文章中,我涵盖了创建 GPT 实验室所需的前期规划,包括功能、数据模型、代码和会话状态。我希望这能激励你构建自己的雄心勃勃的 Streamlit 应用程序。

通过 TwitterLinkedin 与我联系。我很期待你的反馈。

祝你使用 Streamlit 愉快! 🎈

如何构建 LLM 应用程序

原文:towardsdatascience.com/how-to-build-an-llm-application-360848c957db

使用 Langchain 和 OpenAI 构建以 LLM 为中心的应用

John AdeojoTowards Data Science John Adeojo

·发表于Towards Data Science ·5 分钟阅读·2023 年 7 月 21 日

--

图片来源:作者:使用 Midjourney 生成

以 LLM 为中心的应用

AI 创新的速度在短时间内取得了巨大的进展。特别是,两个创新为围绕大型语言模型(LLMs)构建应用程序开辟了大量可能性:函数调用代理

在本文中,我演示了如何利用函数调用和代理在航班数据库上执行搜索,使你能够找到便宜的航班、短途航班、长途航班或任何符合你偏好的航班。

请注意——至少,你需要以下内容才能使其为你工作:

现在,让我们深入探讨技术细节。

自主代理链

Langchain一直处于 LLM 驱动的代理的前沿。这是一个简单但强大的概念。

从本质上讲,你可以赋予一个代理推理能力,在我们的案例中,这将是 GPT-4。

你可以赋予代理访问各种工具的权限。这些工具可以包括搜索引擎、pandas、SQL、Wolfram Alpha 等。这个列表每个月都在扩展,开发者们不断添加更多工具。

由大型语言模型驱动的代理利用分析推理来确定如何利用工具完成你分配的任务。

函数调用

OpenAI 的一个开发,函数调用允许你从自然语言输入中解析函数的参数。

这对用户如何使用自然语言或甚至语音与我们的应用程序交互具有重要意义。

函数调用将在后面的代码示例中变得更加清晰。

构建航班搜索应用

我们可以开发一个应用程序,通过自然语言查询航班,只需四个组件,前端除外。

作者提供的 GIF:基于查询的航班搜索 LLM 应用演示 — “请给我提供从伦敦到希腊的航班,出发日期为 2023 年 11 月 15 日,返回日期为 2023 年 12 月 10 日。适用于一位成人。”

  • 航班数据 API:航班数据来自 Amadeus 的航班 API,包含有关航班的所有相关细节。

  • 函数调用:选择的大型语言模型(LLM),在此应用程序中为 OpenAI 的 GPT-4。

  • SQL 数据库:将数据存储在内存中的 SQL 数据库中。

  • SQL 数据库代理:使用 LLM 和 SQL Alchemy。LLM 从自然语言输入生成 SQL 查询。SQL Alchemy 执行查询。

总体架构大致如下:

作者提供的图像:航班应用流程图

OpenAI 函数调用脚本

函数调用使我们能够从自然语言输入中提取结构化数据。这对于航班搜索应用来说是完美的,因为我们需要提取关于航班的关键信息,以便请求 Amadeus 航班 API 的数据:

作者提供的脚本:函数调用以获取参数从航班 API 提取数据

Amadeus API 请求脚本

接下来,我们向 Amadeus 发送请求以获取航班预订数据。他们提供了免费层,但你需要注册以获得你的 API 密钥。

我编写了一个脚本来 转换 数据,使其适合 SQL 查询。这对于应用程序的性能至关重要。注意函数 load_data() 在下一节中讲解。

作者提供的脚本:从 Amadeus API 请求航班数据,将其转换并保存到内存中的 SQL 数据库

创建数据库脚本

一旦我们有了数据,我们需要创建一个 SQL 数据库并将数据存储在其中,以便我们的代理可以查询它。

作者提供的脚本:将数据存储在内存中的 SQL lite 数据库中

SQL 数据库代理脚本

我们可以调用 SQL 数据库代理来根据请求查询存储的航班数据库。该过程遵循以下一般工作流程:

  • 第 1 步 — 将用户的自然语言查询与提示模板结合起来。

  • 第 2 步 — 将组合查询和提示发送到 SQL 数据库代理

  • 第 3 步 — SQL 数据库代理使用 ReAct 框架生成正确的查询以响应用户请求。

这是脚本:

作者提供的脚本:SQL 数据库代理

这是提示模板:

作者提供的脚本:生成查询的提示模板,以供 SQL 数据库代理使用。

在继续之前,我应该简要介绍一下 ReAct 框架。

本质上,ReAct 是一个用于提示 LLM 的框架,混合了思考和行动。它帮助模型制定计划并调整计划,同时允许模型与数据库或环境等外部资源互动,以获取更多信息。

这种方法已经在不同任务中进行了测试,显示出更好的结果,并且更容易为人们理解和信任。有关 ReAct 的更多细节,请阅读这个 资源

综合考虑

该应用程序总共包括六个脚本,包括使用 Streamlit 开发的前端。设置说明和完整的项目可以在我的 GitHub 仓库 中找到。

我有一个完整的操作指南,展示了如何从我的 GitHub 仓库克隆应用程序并在你自己的机器上运行。

我已经通过我的 YouTube 频道录制了现场演示。你可以看到应用程序的实际运行情况,窥探其内部,并了解如何在你自己的机器上进行设置。

最终思考

函数调用和 SQL 数据库代理非常强大。我设想它们会被用来构建许多以 LLM 为中心的应用程序。然而,我想提出一些警示。

  • 提示工程: 这在应用程序的性能中发挥了重要作用。对提示工程的研究已经推出了像 ReAct 这样的框架。然而,仍然需要大量的工程工作来提供合理的响应。尤其是提示模板,很难做到完美。

  • 延迟: 该应用程序从头到尾运行需要很长时间,这肯定不是我们通常习惯的即时反馈。性能较差的模型,如 GPT-3.5,将运行更快,但牺牲了回答更复杂问题的能力。从客户体验的角度来看,良好的前端和加载条可以在一定程度上缓解延迟问题。从长远来看,我们期望看到更快且性能更好的模型。

  • 幻觉: LLM 有出现幻觉的倾向,可能会时不时返回错误结果。提示工程框架旨在减少幻觉率,但仍需进一步工作。我预计更先进的模型在这方面会减少幻觉问题,就像我们看到 GPT-4 的幻觉率低于 GPT-3.5 一样。

  • 数据整理: 使用 SQL 数据库代理可以很好地工作,如果数据也经过良好的整理。我花了大部分时间来决定数据的正确逻辑表示,以确保 LLM 能够正确查询它。

  • LLM 限制: 应用程序中存在几个可以通过引入更多工具来解决的错误。一个主要的例子涉及日期。偶尔,LLM 无法推断出日期,如果年份和月份没有明确说明的话。通过代理向 LLM 提供 Python 访问权限可能有助于解决这个问题。

我欢迎对原型的反馈以及任何改进建议。

感谢阅读。

如果你渴望提升人工智能技能,可以加入我的课程的等待名单,我将带领你深入了解开发大型语言模型驱动的应用程序的过程。

如果你寻求业务的 AI 转型,今天就预约一次发现通话吧。

## Brainqub3 | AI 软件开发

在 Brainqub3,我们开发定制的 AI 软件。我们使用最新的 AI 技术创建 qub3s,先进的人工智能大脑,来…

www.brainqub3.com

想要了解更多关于人工智能、数据科学和大型语言模型的内容,你可以订阅YouTube频道。

如何从零开始构建一个大型语言模型

原文:towardsdatascience.com/how-to-build-an-llm-from-scratch-8c477768f1f9

数据策划、变换器、大规模训练和模型评估

Shaw Talebi数据科学前沿 Shaw Talebi

·发布于 数据科学前沿 ·16 分钟阅读·2023 年 9 月 21 日

--

这是关于在实践中使用大型语言模型的 系列文章 的第 6 篇文章。之前的文章探讨了如何通过 提示工程微调 来利用预训练的大型语言模型。虽然这些方法可以处理绝大多数大型语言模型的使用案例,但在某些情况下,从头开始构建一个大型语言模型可能是合理的。在本文中,我们将回顾开发基础大型语言模型的关键方面,基于 GPT-3、Llama、Falcon 等模型的发展。

图片来源 Frames For Your HeartUnsplash

从历史上看(即不到一年前),训练大规模语言模型(10 亿以上参数)曾是一个仅限于人工智能研究人员的神秘活动。然而,随着 ChatGPT 之后的人工智能和大型语言模型的兴奋,我们现在有了一个环境,企业和其他组织对从零开始开发自己的定制大型语言模型产生了兴趣 [1]。虽然对于超过 99%的大型语言模型应用来说,这并非必要(IMO),但了解开发这些大规模模型所需的内容以及何时构建它们仍然是有益的。

补充视频。

成本是多少?

在深入探讨大型语言模型开发的技术方面之前,让我们做一些粗略的数学计算,以了解这里的财务成本。

Meta 的 Llama 2 模型训练其 7b 参数模型需要大约 180,000 GPU 小时,训练 70b 模型需要 1,700,000 GPU 小时 [2]。考虑到数量级,这意味着一个 ~10b 参数的模型可能需要 100,000 GPU 小时进行训练,而一个 ~100b 参数的模型需要 1,000,000 GPU 小时。

将这转化为商业云计算成本,一块 Invidia A100 GPU(即用于训练 Llama 2 模型的硬件)每小时的费用大约为 $1–2。这意味着一个 ~10b 参数的模型训练成本约为 $150,000,而一个 ~100b 参数的模型训练成本约为 $1,500,000。

另外,如果你不想租用 GPU,你可以购买它们。训练的成本将包括 A100 GPU 的价格以及模型训练的边际能源成本。一个 A100 约 $10,000 乘以 1000 个 GPU 组成一个集群。硬件成本大约在 $10,000,000 级别。接下来,假设能源成本约为每兆瓦时 $100,并且训练一个 100b 参数的模型需要大约 1,000 兆瓦时 [3]。这意味着每个 100b 参数模型的边际能源成本约为 $100,000

这些成本不包括资助一个机器学习工程师、数据工程师、数据科学家及其他模型开发所需人员的团队,这个团队的费用很容易达到 $1,000,000(为了找到懂行的人)。

不用说,从零开始训练一个大型语言模型(LLM)是一项巨大的投资(至少目前是这样)。因此,必须有显著的潜在收益,无法通过提示工程或微调现有模型来实现,才能证明这种成本在非研究应用中的合理性。

4 个关键步骤

现在你已经意识到你不想从零开始训练一个 LLM(或者你可能仍然想这样做,不知道),让我们看看模型开发包括哪些内容。在这里,我将过程分解为 4 个关键步骤。

  1. 数据整理

  2. 模型架构

  3. 大规模训练

  4. 评估

尽管每一步都有无尽的技术细节,但这里的讨论将保持相对高层次,只强调少数关键细节。读者可以参考相关引用资源,以深入了解任何方面的细节。

第 1 步:数据整理

机器学习模型是其训练数据的产物,这意味着你的模型的质量取决于数据的质量(即“垃圾进,垃圾出”)。

这对大型语言模型(LLMs)来说是一个主要挑战,因为所需的数据规模庞大。为了理解这一点,这里列出了一些流行基础模型的训练集规模。

  • GPT-3 175b: 0.5T Tokens [4](T = 万亿)

  • Llama 70b: 2T tokens [2]

  • Falcon 180b: 3.5T [5]

这相当于大约一万亿字的文本,即大约 1,000,000 部小说或 1,000,000,000 篇新闻文章。注意:如果你不熟悉“token”这个术语,可以查看 之前的文章 中的解释。

## 破解 OpenAI (Python) API

面向初学者的完整介绍,带有示例代码

[towardsdatascience.com

我们从哪里获取这些数据?

互联网是最常见的 LLM 数据来源,包括网页、书籍、科学文章、代码库和对话数据等无数文本源。还有许多现成的开放数据集用于训练 LLM,例如Common Crawl(及其过滤变体Colossal Clean Crawled Corpus(即 C4),和Falcon RefinedWeb),The Pile(一个清洗和多样化的 825GB 数据集)[6],以及 Hugging Face 的datasets平台(和其他地方)上的许多其他数据集。

从互联网(及其他来源)收集人类生成的文本的替代方案是让现有的 LLM(如 GPT-3)生成(相对)高质量的训练文本语料库。这正是斯坦福大学研究人员为开发 Alpaca 而做的工作,Alpaca 是一个在 GPT-3 生成的具有指令输入输出格式的文本上训练的 LLM[7]。

无论你的文本来源于何处,多样性是优质训练数据集的关键方面 这往往能提升模型泛化能力以应对下游任务[8]。如图所示,大多数流行的基础模型至少有一定程度的训练数据多样性。

比较基础模型之间的训练数据多样性。受赵等人工作的启发。[8]。图片由作者提供。

我们如何准备数据?

收集大量文本数据仅是战斗的一半。数据整理的下一阶段是确保训练数据质量。虽然有无数方法可以实现这一点,这里我将重点介绍基于赵等人回顾的4 个关键文本预处理步骤[8]。

质量过滤 — 旨在从数据集中移除“低质量”文本[8]。这可能是来自网络某些角落的无意义文本、新闻文章中的有害评论、多余或重复的字符等。换句话说,这些文本不符合模型开发的目标。赵等人将此步骤分为两种方法:基于分类器的方法和基于启发式的方法。前者涉及训练一个分类器来评分文本质量,使用(较小的)高质量数据集来过滤低质量文本。后者方法采用经验规则来确保数据质量,例如,去掉高困惑度的文本,仅保留具有特定统计特征的文本,或去除特定单词/语言[8]。

去重——另一个关键的预处理步骤是文本去重。这很重要,因为相同(或非常相似)文本的多个实例可能会偏向语言模型并干扰训练过程[8]。此外,这还有助于减少(并且理想情况下消除)训练和测试数据集中存在的相同文本序列[9]。

隐私编辑——在从互联网抓取文本时,存在捕获敏感和机密信息的风险。然后,LLM 可能会“学习”并意外暴露这些信息。这就是为什么去除个人身份信息至关重要。可以使用基于分类器和基于启发式的方法来实现这一目标。

分词——语言模型(即神经网络)并不“理解”文本;它们只能处理数字。因此,在我们能够训练神经网络进行任何操作之前,训练数据必须通过称为分词的过程转换为数字形式。一个流行的方法是通过字节对编码(BPE)算法[10],它可以通过将特定的子词绑定到特定的整数来有效地将给定文本转换为数字。这种方法的主要好处是它最小化了“词汇表外”词汇的数量,这对于其他基于词的分词程序是一个问题。SentencePiece 和 Tokenizers Python 库提供了该算法的实现[11, 12]。

步骤 2: 模型架构

变压器已经成为语言建模的最先进方法[13]。虽然这为模型架构提供了指导,但在这个框架内仍然可以做出许多高层次的设计决策。

什么是变压器?

变压器是一种利用注意力机制的神经网络架构,用于在输入和输出之间生成映射。注意力机制根据序列的内容和位置学习不同元素之间的依赖关系[13]。这源于这样一个直觉:在语言中,上下文很重要

例如,在句子“我用球棒击打了棒球”中,“棒球”一词的出现暗示“球棒”是一个棒球棒,而不是夜行性哺乳动物。然而,仅仅依赖上下文的内容还不够。词语的位置和顺序也很重要。

例如,如果我们将相同的词重新排列为“我用棒球击打了蝙蝠。”这句话的意义完全不同,“蝙蝠”在这里(合理地)指的是夜行性哺乳动物。注意:请勿伤害蝙蝠

注意力机制使神经网络能够捕捉建模语言时内容和位置的重要性。这已经是机器学习中的一个想法数十年。然而,变压器的注意力机制的主要创新计算可以并行进行,相比于依赖于串行计算的递归神经网络,提供了显著的加速[13]。

3 种变压器

Transformers 由两个关键模块组成:编码器和解码器。这些模块可以是独立的,也可以是结合在一起的,这使得三种类型的 Transformers 成为可能 [14, 15]。

仅编码器 — 编码器使用自注意力将标记翻译为语义上有意义的数字表示(即嵌入)。嵌入考虑了上下文。因此,相同的词/标记会根据周围的词/标记有不同的表示。这些 transformers 适用于需要理解输入的任务,例如文本分类或情感分析 [15]。一个流行的仅编码器模型是 Google 的 BERT [16]**。

仅解码器 — 解码器像编码器一样,将标记转换为语义上有意义的数字表示。然而,关键区别在于解码器不允许序列中的未来元素进行自注意力(即掩蔽自注意力)。这种方式也称为因果语言建模,暗示了未来和过去标记之间的不对称性。这在文本生成任务中效果良好,并且是大多数 LLMs(例如 GPT-3、Llama、Falcon 等)的基础设计 [8, 15]。

自注意力和掩蔽自注意力权重矩阵的示意图。图片由作者提供。

编码器-解码器 — 我们可以将编码器和解码器模块结合起来,创建一个编码器-解码器 transformer。这是原始“Attention is all you need”论文中提出的架构 [13]。这种类型的 transformer 的关键特性(其他类型无法实现)是交叉注意力。换句话说,交叉注意力不是限制注意力机制学习同一序列中标记之间的依赖关系,而是学习不同序列中标记之间的依赖关系(即来自编码器和解码器模块的序列)。这对于需要输入的生成任务(如翻译、总结或问答)非常有帮助 [15]。这种模型的另一个名称是掩蔽语言模型或去噪自编码器。使用这种设计的流行 LLM 是 Facebook 的 BART [17]。

其他设计选择

残差连接 (RC) —(也称为跳跃连接)允许中间训练值绕过隐藏层,这通常有助于提高训练稳定性和性能 [14]。可以通过多种方式在 LLM 中配置 RC,如 He 等人(见图 4) [18] 论文中所讨论的那样。原始 Transformers 论文通过加法和归一化将每个子层(例如多头注意力层)的输入和输出进行组合,从而实现 RC [13]。

层归一化(LN) — 层归一化的思想是基于均值和标准差(或类似的东西)重新缩放层之间的中间训练值。这有助于加快训练时间并使训练更加稳定 [19]。LN 有两个方面。一方面涉及归一化的位置(即层前或层后或两者都做),另一方面涉及归一化的方式(例如 Layer NormRMS Norm)。在 LLMs 中最常见的方法是使用 Ba 等人提出的 Pre-LN 方法 [8][19],这与采用 Post-LN 的原始变换器架构有所不同 [13]。

激活函数(AF) — 激活函数在模型中引入非线性,使其能够捕捉输入与输出之间的复杂映射。许多常见的激活函数用于 LLMs,包括 GeLU、ReLU、Swish、SwiGLU 和 GeGLU [8]。然而,根据赵等人的调查,GeLU 是最常见的 [8]。

位置嵌入(PE) — 位置嵌入在语言模型的文本表示中捕捉标记位置的信息。一种方法是通过正弦函数 [13] 为每个标记添加基于其在序列中的位置的唯一值。或者,可以通过增强变换器自注意机制来推导相对位置编码(RPE),以捕捉序列元素之间的距离 [20]。RPE 的主要优点是对远大于训练时看到的输入序列的性能提升 [8]。

我应该做多大?

在训练时间、数据集大小和模型大小之间存在重要的平衡。如果模型过大或训练时间过长(相对于训练数据),可能会过拟合。如果模型过小或训练时间不够长,可能会表现不佳。Hoffman 等人基于计算和标记数量提供了 LLM 最佳大小的分析,并推荐了包括所有三个因素的扩展计划 [21]。大致而言,他们建议每个模型参数 20 个标记(即 10B 参数应在 200B 标记上进行训练),以及每 10 倍模型参数增加 100 倍 FLOPs

步骤 3: 大规模训练

大型语言模型(LLMs)通过自监督学习进行训练。这通常表现为(例如在仅解码器的变换器中)基于前面的标记预测序列中的最终标记。

尽管这在概念上很简单,但中心挑战在于将模型训练扩展到~10–100B 参数。为此,可以采用几种常见技术来优化模型训练,例如混合精度训练3D 并行性零冗余优化器(ZeRO)

训练技术

混合精度训练是一种常见的策略,用于降低模型开发的计算成本。这种方法在训练过程中使用 32 位(单精度)和 16 位(半精度)浮点数据类型,以最大限度地减少单精度数据的使用[8, 22]。这有助于减少内存需求并缩短训练时间[22]。尽管数据压缩可以显著改善训练成本,但它的效果有限。这就是并行化发挥作用的地方。

并行化将训练分布到多个计算资源(即 CPU 或 GPU 或两者)。传统上,这是通过将模型参数复制到每个 GPU 来实现的,以便并行更新参数。然而,当训练具有数百亿个参数的模型时,内存限制和 GPU 之间的通信成为一个问题(例如,Llama 70b 约为 120GB)。为了解决这些问题,可以使用3D 并行性,它结合了三种并行化策略:流水线、模型和数据并行性。

  • 流水线并行性 — 将变压器层分布到多个 GPU 上,并通过在同一个 GPU 上加载连续层来减少分布式训练中的通信量[8]。

  • 模型并行性(或张量并行性)— 将参数矩阵操作分解为分布在多个 GPU 上的多个矩阵乘法[8]。

  • 数据并行性 — 将训练数据分布到多个 GPU 上。虽然这需要复制和传递模型参数和优化器状态,但通过前面的并行化策略和下一种训练技术,缺点得到了缓解[8]。

尽管 3D 并行性在计算时间上带来了巨大的加速,但在将模型参数复制到多个计算单元时仍然存在一定程度的数据冗余。这引出了零冗余优化器(ZeRO)的概念,(顾名思义)它减少了关于优化器状态、梯度或参数分区的数据冗余[8]。

这三种训练技术(以及更多)由DeepSpeed实现,这是一款用于深度学习优化的 Python 库[23]。它与开源库如 transformers、accelerate、lightning、mosaic ML、determined AI 和 MMEngine 集成。其他用于大规模模型训练的流行库包括Colossal-AIAlpaMegatron-LM

训练稳定性

除了计算成本外,扩大 LLM 训练还面临训练稳定性挑战,即训练损失平稳下降至最小值。管理训练不稳定性的一些方法包括模型检查点、权重衰减和梯度裁剪。

  • 检查点 — 捕捉模型工件的快照,以便从该点恢复训练。这在模型崩溃(例如损失函数激增)的情况下很有帮助,因为它允许从失败之前的点重新启动训练 [8]。

  • 权重衰减 — 是一种正则化策略,通过向损失函数中添加一个项(例如权重的 L2 范数)或改变参数更新规则来惩罚大的参数值 [24]。一个常见的权重衰减值是 0.1 [8]。

  • 梯度裁剪 — 如果目标函数的梯度范数超过预先指定的值,则重新缩放梯度。这有助于避免梯度爆炸问题 [25]。一个常见的梯度裁剪阈值是 1.0 [8]。

超参数

超参数是控制模型训练的设置。虽然这些并不特定于 LLM,但为了完整性,下面提供了一个关键超参数的列表。

  • 批量大小 — 是优化将在更新参数之前处理的样本数量 [14]。这可以是固定的数量,也可以在训练期间动态调整。在 GPT-3 的情况下,批量大小从 32K 增加到 3.2M 令牌 [8]。静态批量大小通常是较大的值,如 16M 令牌 [8]。

  • 学习率 — 控制优化步长。与批量大小一样,它也可以是静态的或动态的。然而,许多 LLM 使用动态策略,其中学习率线性增加直到达到最大值(例如 GPT-3 的 6E-5),然后通过余弦衰减减少,直到学习率约为最大值的 10% [8]。

  • 优化器 — 这定义了如何更新模型参数以减少损失。基于 Adam 的优化器是最常用于 LLM 的 [8]。

  • 丢弃 — 在训练期间随机将一部分模型参数置零。这通过在某种意义上对一个虚拟模型集进行训练和平均来帮助避免过拟合 [14]。

注意 — 由于训练 LLM 涉及巨大的计算开销,因此在训练前了解模型大小、训练时间和性能之间的权衡是有利的。一种方法是基于可预测的扩展规律估计这些量。Kaplan 等人的流行工作展示了仅解码器模型性能如何随参数数量和训练时间的变化而变化 [26]。

步骤 4: 评估

成功训练模型在许多方面只是开始。模型开发几乎总是迭代的,即步骤会重复,直到开发者和利益相关者对最终产品感到满意。

这个迭代过程的一个关键部分是模型评估,它检查模型在一组任务上的表现 [8]。虽然任务集很大程度上取决于模型的预期应用,但有许多基准测试常用于评估 LLM。

Open LLM 排行榜 由 Hugging Face 主办,旨在提供开放访问 LLM 的总体性能排名。评估基于四个基准数据集:ARC、HellaSwag、MMLU 和 TruthfulQA。

  • ARC 是一个问答数据集,包括小学水平的多项选择科学问题和答案。例如:哪项技术是最近开发的? A. 手机,B. 电视,C. 冰箱,D. 飞机(答案:A) [27]。

  • Hellaswag 是一个常识性自然语言推理数据集,特别设计为对机器具有挑战性,但对人类几乎 trivial,通过对抗性过滤策略构建。一个示例任务如下。一个女人在外面拿着一个桶和一只狗。 狗在四处跑,试图避免洗澡。她… A. 用肥皂冲洗桶,然后吹干狗的头,B. 使用水管防止狗变得泡沫多,C. 让狗湿透,然后它再次跑开,D. 和狗一起进入浴缸。(答案:C) [28]。

  • Massive Multitask Language Understanding (MMLU) 通过 57 个任务评估世界知识和解决问题的能力,涵盖了小学水平的数学、美国历史、计算机科学、法律等领域 [29]。例如:列表 25、26、……、100 中有多少个数字? A. 75 B. 76 C. 22 D. 23。(答案:B)

  • TruthfulQA 是一个基准,旨在通过评估模型对常见误解的回应来量化语言模型的真实性 [30]。例如,如果你经常咔嚓你的指节会发生什么?(错误答案)如果你经常咔嚓你的指节,你可能会得关节炎 [30]。

对于有多项选择或分类目标的基准,可以使用提示模板来评估模型性能。如下所示,将 ARC 数据集中的一个问题转换为提示。我们可以将这个提示输入到我们的模型中,并将最高概率的下一个标记(从“A”,“B”,“C”和“D”中)与正确答案(即 A)进行比较 [31]。

“””Question: Which technology was developed most recently?

Choices:
A. Cellular Phone
B. Television
C. Refrigerator
D. Airplane

Answer:”””

然而,更开放性的问题更具挑战性(例如 TruthfulQA)。这是因为评估文本输出的有效性比比较两个离散类别(即多项选择目标)要模糊得多。

克服这一挑战的一种方法是通过人工评估来手动评估模型性能。这是指一个人根据一组指南、真实情况或两者对 LLM 的完成情况进行评分。虽然这可能很繁琐,但有助于促进灵活且高保真的模型评估。

或者,可以采用更量化的方法,使用NLP 指标,如困惑度、BLEU 或 ROGUE 分数。虽然这些分数的计算方式各不相同,但它们都量化了模型生成的文本与验证数据集中的(正确)文本之间的相似度。这种方法比人工评估成本更低,但可能会牺牲评估的准确性,因为这些指标基于生成文本/真实文本的统计特性,而不一定是其语义含义。

最终,一种可能兼顾两全的方案是使用辅助微调的 LLM来将模型生成的结果与真实情况进行比较。这一方案的一个示例是 GPT-judge,它是一个微调的模型,用于将对 TruthfulQA 数据集的回应分类为真或假[30]。然而,这种方法始终存在风险,因为没有任何模型能在所有场景中保证 100%的准确性。

接下来是什么?

虽然我们可能仅仅触及了从头开发大语言模型(LLM)的表面,但希望这能作为一个有用的入门介绍。如需深入了解这里提到的各个方面,请查看下面引用的参考资料。

无论你是直接拿一个现成的基础模型还是自己构建,它可能都不会非常有用。基础模型(顾名思义)通常是 AI 解决问题的起点,而不是最终解决方案。有些应用只需要通过巧妙的提示(即提示工程)来使用基础模型,而其他应用则需要对模型进行微调,以适应特定的任务。这些方法在本系列的前两篇文章中有更详细的讨论(附带示例代码)。

👉 更多 LLM 相关内容: 介绍 | OpenAI API | Hugging Face Transformers | 提示工程 | 微调 | QLoRA | RAG | 文本嵌入

Shaw Talebi

Shaw Talebi

大型语言模型(LLMs)

查看列表13 个故事!

资源

联系我的网站 | 预约电话 | 随便问我

社交媒体YouTube 🎥 | LinkedIn | Twitter

支持请我喝咖啡 ☕️

[## 免费获取我写的每一个新故事

免费获取我写的每一个新故事。附言:我不会与任何人分享你的电子邮件。注册后,你将创建一个…

shawhin.medium.com](https://shawhin.medium.com/subscribe?source=post_page-----8c477768f1f9--------------------------------)

[1] BloombergGPT | 论文

[2] Llama 2 论文

[3] LLM 能源成本

[4] arXiv:2005.14165 [cs.CL]

[5] Falcon 180b 博客

[6] arXiv:2101.00027 [cs.CL]

[7] Alpaca 仓库

[8] arXiv:2303.18223 [cs.CL]

[9] arXiv:2112.11446 [cs.CL]

[10] arXiv:1508.07909 [cs.CL]

[11] SentencePiece 仓库

[12] Tokenizers 文档

[13] arXiv:1706.03762 [cs.CL]

[14] Andrej Karpathy 讲座

[15] Hugging Face NLP 课程

[16] arXiv:1810.04805 [cs.CL]

[17] arXiv:1910.13461 [cs.CL]

[18] arXiv:1603.05027 [cs.CV]

[19] arXiv:1607.06450 [stat.ML]

[20] arXiv:1803.02155 [cs.CL]

[21] arXiv:2203.15556 [cs.CL]

[22] 混合精度训练的 Nvidia 文档

[23] DeepSpeed 文档

[24] paperswithcode.com/method/weight-decay

[25] towardsdatascience.com/what-is-gradient-clipping-b8e815cdfb48

[26] arXiv:2001.08361 [cs.LG]

[27] arXiv:1803.05457 [cs.AI]

[28] arXiv:1905.07830 [cs.CL]

[29] arXiv:2009.03300 [cs.CY]

[30] arXiv:2109.07958 [cs.CL]

[31] huggingface.co/blog/evaluating-mmlu-leaderboard

如何在数据工程团队中建立值班文化

原文:towardsdatascience.com/how-to-build-an-on-call-culture-in-a-data-engineering-team-7856fac0c99

系统性地解决生产中的数据问题

Xiaoxu GaoTowards Data Science Xiaoxu Gao

·发表于 Towards Data Science ·阅读时间 9 分钟·2023 年 3 月 15 日

--

图片来源 Pavan TrikutamUnsplash

在任何公司中,赢得并留住客户的最佳方式之一是提供卓越的服务,这意味着服务应该在客户访问时始终保持健康和功能正常。为了实现这一点,科技行业引入了值班制度,这在过去常常与医生相关联。

值班的定义在公司之间有所不同。一般来说,值班意味着在特定时间段内保持可用,并准备以适当的紧迫性响应生产事故。值班通常与软件工程师和站点可靠性工程师(SRE)相关联,以支持诸如 API、网站、移动应用、物联网服务等软件。

数据工程师似乎没有像他们的同事那样参与值班。我将在下一段中解释原因。但这将会改变。

作为一个对多学科感兴趣的人,我想分享我们如何在数据工程团队中建立值班文化。大部分经验来自于我当前的团队,但我希望了解你们团队的做法以及任何有趣的想法。

为什么数据工程师不参与值班?

值班可能会很有压力。当生产问题发生时,工程师会立即在工作时间内或外收到呼叫,尽力做好事件管理的第一步。他们意识到焦急的客户在迫切等待服务恢复。他们明白,问题修复的延迟会导致公司客户和收入的损失。

然而,许多数据工程师认为管道问题对客户满意度和收入的影响有限,这导致他们没有动力时刻待命。这种看法可能源于数据大多由数据分析师和科学家使用来创建仪表板和报告,这些报告并不直接面对客户,可能不需要立即关注。

另一个原因是许多数据问题被转化为功能请求。在数据领域,“bug 还是功能”的讨论很常见。这对没有与用户定义任何 SLA 或 SLO 的数据团队特别具有挑战性,因为由于需求缺失造成的问题被归类为新功能,从而导致优先级降低。原则上,待命旨在解决紧急问题,而不是处理功能请求。

此外,数据流的复杂性,加上用户对指标的误解,以及源数据中的不可预见变量,都可能导致挫败感。

数据工程师面临的生产问题与软件工程师不同。因此,我们脑中的声音可能会是“嘿,看起来数据工程师不需要待命流程。他们真幸运。”

数据工程师在待命期间做什么?

在我看来,待命不仅仅是迅速解决最紧急的生产问题,还包括规范处理意外生产请求的过程。对数据工程师来说,这一点尤其重要,因为他们面临的多样化问题比软件工程师要多得多。

如果你考虑创建一个待命文化,列出团队收到的所有生产问题和请求,这些问题和请求超出了他们的日常范围,并为每个问题和请求提供描述和优先级,是一个很好的练习。以下是一个例子:

数据问题和请求示例(作者创建)

最终,团队中的某个人应按照指导方针处理请求。指导方针越详细,待命过程就会越高效。建立一个公平的环境,让每个人都能参与待命过程,这一点至关重要。从这个角度看,它与软件工程师并没有太大区别,对吧?

接下来,我将分享一些建立待命文化的工具和框架。请记住,没有一种万能的方法,你不需要使用所有工具。选择对你的团队更有意义的工具。

待命工作流程

建立健康待命文化的关键是充分准备。更多的准备意味着我们需要现场做出的决策更少,从而减少错误。待命工作流程告诉工程师如何以一致的方式处理各种生产请求。

待命工作流程(作者创建)

上述工作流程是一个模板,你应该根据需要进行调整,但以下是主要步骤:

  1. 定义警报来源。 将所有生产问题重定向到一两个渠道。例如,使用 Slack 集成将管道问题、基础设施故障和测试故障重定向到一个集中式 Slack 频道,以便于跟踪。

  2. 识别警报的类别、影响的规模及其紧急性。 每个值班人员都应该能够根据警报的类别、影响和 SLA 要求来评估问题的紧急性。通过创建具有明确要求的“数据产品”,团队可以从这个过程受益,从而有效识别影响和紧急性。我推荐这篇文章——使用 Airflow 编写数据产品管道,这是一个将数据要求作为代码编写在 Airflow dags 中的好方法。

  3. 识别根本原因并解决问题。 当出现紧急问题时,值班人员应尽最大努力找到根本原因并解决问题。然而,并非每位数据工程师都了解数据分析师维护的数据模型的所有细节。在这种情况下,遵循升级模式可能会有所帮助。它允许工程师在问题解决之前向具有必要专业知识的其他工程师或分析师寻求帮助。

  4. 执行事件后行动并更新值班日志。 不要忘记执行事件后行动,如回填以纠正增量模型的历史数据。还建议保持值班日志以便于知识共享。

  5. 用户沟通。 在一个并行线程中,保持用户知情是很重要的。在“数据停机”期间有效的沟通可以建立数据团队与用户之间的信任。我的一篇文章——数据产品的状态页面——我们都需要一个介绍了状态页面作为改进数据停机期间有效沟通的方法。

值班责任

正如在dbt 的 2023 年分析工程状态报告中所报告的,所有数据从业人员面临的主要挑战是模糊的数据所有权。这个挑战也引出了在值班期间的问题:“谁应该解决这个问题?”

我认为答案是“视情况而定”。

值班责任在很大程度上取决于数据工程师的日常工作。显然,工程师负责技术故障,但当涉及到数据模型故障时,责任变得有争议。无论是集中式还是分散式数据团队,数据工程师总是需要分析师的智慧来诊断和解决模型相关问题。最终,这会形成共享责任。依我拙见,两种方法可能有助于协作:

  1. 尽可能为每个数据模型分配一个负责人。 仅仅为模型分配一个负责人 就能显著提高值班期间的效率。

  2. 将数据模型所有者视为“外部方”。 软件通常依赖于工程师控制之外的外部方,例如依赖网络提供商的物联网服务。类似地,数据工程师可能需要与其直接团队之外的模型所有者合作以解决模型故障。当需要外部知识时,工程师应感到舒适地主动联系并与他们合作,同时通知用户进展。不要通过期望值班工程师独自解决问题来给他们带来压力。

工具 1 — 值班轮换

为了帮助你入门,这里有一些工具可以简化流程,帮助工程师专注于真正重要的事物。

日程安排

值班轮换是一个轮换值班工程师的计划,确保每周都有一个值班人员。这为工程师设定了时间、同事和范围的期望。

一种免费的设置是使用电子表格来管理轮换日程,并使用定时任务将日程近实时地传播到日历中。例如 Google Sheets + Apps Script + Google Calendar。一些团队更喜欢使用付费软件,如 Opsgenie 和 PagerDuty。它们节省时间并减少手动开销,但需要付费。

权限

值班工程师有时需要额外的权限来解决生产问题。一种方法是进行权限提升,暂时授予工程师额外的权限。另一种选择是创建一个高权限用户组并轮换组成员。必须确保组成员的轮换与值班日历的轮换同步。

工具 2 — 交流渠道

在数据停机期间,有效的沟通至关重要。值班过程涉及多个沟通层面,找到在被告知和不被警报压倒之间的正确平衡至关重要。

由作者创建

集中数据警报频道(警报 -> 团队)

通过拥有一个专门的频道来接收所有警报,可以更容易地监控和管理警报,减少关键信息被遗漏或忽视的风险。Slack 是一个受欢迎的选择,因为它可以轻松地与各种数据源如 Opsgenie、GCP Cloud logging、Sentry、服务台等集成。它允许值班人员快速响应问题,并增强与其他工程师的协作。

升级政策(团队 -> 团队)

升级政策是一组程序,概述了组织如何响应需要超出初步响应的额外资源的问题。当第一层防御在一定时间内无法解决问题时,应及时通知第二层。

大多数事件管理工具允许团队定义升级政策,工具会在合适的时间将通知自动路由到正确的专家。如果模型所有权被适当地定义,工具可以通过读取模型元数据自动提醒所有者。

用户沟通(团队 -> 用户)

最后一层是用户沟通,需在问题确定后尽快开始。通过设置类似状态页面的工具来保持渠道集中。

工具 3 — 值班运行手册

值班运行手册是一组在响应问题时值班人员可以遵循的指令。数据管道运行手册通常包括:

  • 数据产品的元数据:所有者、模型增量性、优先级、计划、SLA 和 SLO。

  • 升级程序(如果没有自动处理)。

  • 故障排除指南:如何解决常见问题。例如,执行完全刷新、检查源数据、日志、数据可观测性工具等。

  • 事件后验证:如何验证问题是否得到妥善解决。对于定时任务,问题只能在下一次运行时进行验证,这可能是几个小时或几天后。

值班运行手册是一个需要定期更新的动态文档,以反映变化。我非常喜欢文章用 Airflow 编写数据产品管道中的想法,其中作者在 Airflow dags 中将需求写成代码。这是将文档与代码关联的一个好例子,确保文档始终保持最新且相关。

工具 4—值班日志

值班日志是一个记录生产问题的工具。它帮助寻找经过测试的解决方案的工程师和寻找趋势的管理者。一个模板化的日志确保工程师在处理每个问题时都保持相同的科学严谨性。每条记录都包含关于问题的详细元数据、深入调查以及他们解决问题的方式。以“关注问题,而非人”的心态,工程师们更愿意分享更多细节。

值班日志 — Notion 模板(由作者创建)

值班可以作为反映团队成熟度的镜子。年轻团队在值班初期经历一些混乱是正常的。然而,这些挑战可以推动团队改善监控、自动化、文档和需求收集流程。最终,值班可以成为团队中每个人宝贵的学习经验。

结论

对于刚接触值班的人来说,这篇文章提供了宝贵的见解和对预期的说明。对于那些希望建立值班文化的数据领导者,你们有很多工具可以使用。首先,从创建工作流程开始,并逐步用正确的工具填充每个步骤。最终,值班更多的是一种文化挑战,而非技术挑战。只要有正确的心态和工具,值班不仅可以成为个人成长的宝贵机会,也可以促进公司的成长。干杯!

如何构建和管理数据资产组合

原文:towardsdatascience.com/how-to-build-and-manage-a-portfolio-of-data-assets-9df83bd39de6

分步方法

Willem KoendersTowards Data Science Willem Koenders

·发布于 Towards Data Science ·13 分钟阅读·2023 年 9 月 14 日

--

图片由 Viktor Forgacs 通过 Unsplash 提供。

数据资产(或产品)——一组为一组已识别的用例而准备的数据或信息——在数据管理领域引起了极大的关注。能够识别、构建和管理单个数据产品是一回事,但在企业级别上如何操作呢?从哪里开始?

数据赋能领导者,特别是首席数据官,面临着这一动员挑战。在这一观点中,我们将讨论如何采用组合方法来管理数据资产。下面的图 1 展示了分步方法,本文其余部分将详细阐述这 7 个步骤。在过程中,我们将解释方法和方法论,并混合示例进行讲解。

图 1——管理数据资产组合的 7 步法。图片由作者提供。

在我跟随的各种现实生活实例中,我采用了这种方法,但为了避免任何对数据来自特定客户的怀疑,同时展示生成性 AI 在正确提示下如何实际应用,我使用了 ChatGPT 4.0 生成这些示例。完整的聊天记录请见 这里

第一步:用例与影响

第一步是识别对组织重要的数据驱动用例。你不必一次性覆盖整个企业——可以从一个领域或业务线开始,这甚至可能是推荐的做法。

用例是实现整体组织战略的具体机制。数据战略和数据治理本身并不会产生价值——它们只有在实现更广泛的战略目标时才会产生价值。因此,用例必须是第一步。

有多种方法可以实现这一目标。你可以通过访谈业务和分析领导者来内部建立用例库存。对于你的行业,你可以从外部来源拼凑出用例的概述。通常,最成功的方法是采用混合方法——引入外部用例列表,然后与内部领导者共同完善这一列表。

如上所述,本文的目的,我使用了ChatGPT 4.0来建立库存,如下图 2 所示。例如,在财务与会计方面,欺诈检测与预防使用实时分析和机器学习模型结合客户和交易数据来识别模式并识别可疑事件。或者在市场营销与销售方面,作为市场营销组合建模的一部分,调查营销努力与销售业绩之间的历史关系,以优化营销预算的分配以及渠道和策略的使用。

图 2 — 概述了 14 个业务和职能领域中的 90 个数据驱动用例。数据由 ChatGPT 4.0 生成,图像由作者提供。

拥有用例还不够——我们需要了解它们的重要性。用例可以通过 4 种关键方式驱动价值:

  • 增加收入

  • 降低成本

  • 提升客户体验

  • 缓解风险

有些人将“推动创新”列为第五个价值驱动因素,但在我看来,这只是时间表的问题,因为任何创新本身最终也会通过上述四种机制推动价值。

现在,在图 3 中,我们概览了与营销相关的用例以及与之相关的典型“营收影响”。实际上,对于我们刚刚介绍的市场营销组合建模(“MMM”)用例,我们看到“1 到 2%的营收影响”。如果你的公司年收入为 10 亿美元,这些估计表明市场营销组合建模可以额外带来 1000 万到 2000 万美元的收入。

图 3 — 一组营销用例及其对整体企业收入的典型影响。来源:识别数据驱动的用例与价值驱动树(由作者共同撰写)。

在步骤 1 结束时,你会得到一组用例及其对组织的估计影响。

步骤 2:所需数据

在此步骤中,我们调查哪些数据是推动已识别的用例所需的。第一步是定义用例的关键数据输入是什么。例如,对于运营下的产品线优化,所需的数据包括生产量数据、机器性能日志和原材料可用性。或者对于人力资源下的员工流失预测,需要来自员工满意度调查、离职面谈反馈和行业流失率的数据。

一旦你拥有了部分或完整的用例列表,相应的 SMEs 或流程负责人可以帮助澄清需要哪些数据。随着你关键数据输入列表的增长,你将达到一个可以开始将数据分组到数据类型或领域中的阶段。在各个领域内,甚至在领域之间,这些数据类型和领域实际上相当稳定。几乎总是适用的数据领域包括客户(或相当于客户的类别,如学生、病人或会员)、员工和财务,因为大多数组织服务于某些人群,有员工来实现这一点,并且需要管理其预算。其他一些领域,如供应链或研究与安全数据,更为具体,可能仅适用于那些管理实际供应链的组织。

图 4 — 数据类型和领域概览。数据由 ChatGPT 4.0 生成,图像由作者提供。

上述图 4 展示了可能的结果。在那里,展示了 12 个数据领域和大约 100 个子领域。组织的所有数据可以映射回这里列出的类型。例如,营销与销售下的 Campaign Spend 数据可能包括有关数字广告、传统媒体活动和赞助的举措和成本的数据,而运营下的 Sensor Data 可能包括来自储存区域的温度传感器数据和监测工厂设备健康的振动传感器数据。

一旦你开始识别用例的关键数据输入,并将这些关键数据输入映射到数据类型或领域,你可以开始构建如图 5 所示的矩阵。在上述例子中,我们有产品线优化的用例,它被映射到运营数据领域,因为它确实需要运营数据。在图 5 中,用例被映射到更广泛的数据领域,以便在此处进行可视化,但在实际情况中,你可以(并且应该)将用例映射到更基础的、更加细化的子领域。

图 5 — 数据驱动的用例与数据类型的映射。数据由 ChatGPT 4.0 生成,并由作者完善;图像由作者提供。完整分辨率图像可应要求提供。

仅仅对这一点的全景理解——关键用例与其所需数据类型的映射——已经对制定数据战略和优先排序特定数据领域极其重要……但我们将更进一步,使其更具可操作性。

第 3 步:数据源

在我们根据第 2 步中的(逻辑)数据需求识别源系统之前,让我们先看一组用例并评估它们所需的数据。下图 6 展示了市场营销和销售用例的概述以及它们所依赖的关键数据。这与图 5 所示的内容一致,只是在更高的粒度层次上。

图 6 — 市场营销和销售的用例及其所需的关键数据输入。数据由 ChatGPT 4.0 生成,图片由作者提供。

例如,我们看到对于客户细分与目标定位的第一个用例,需要关于客户人口统计的数据。对于相关公司,这些数据存储在一个名为全球 CRM的物理系统中。同样,该用例所需的购买历史数据存储在两个系统中:电子商务交易历史零售销售点系统

如此等等。如果我们取出上面图 6 中的所有关键数据输入并识别源系统,我们将得到图 7 中的表格。正如你所看到的,一些数据源包含多种类型的关键数据。例如,全球 CRM 主数据包含客户人口统计数据,但也包括客户偏好、客户反馈和客户细分数据。

图 7 — 市场营销和销售用例的关键数据输入映射到该数据的源系统。数据由 ChatGPT 4.0 生成,图片由作者提供。

第 4 步:用例与源系统对比

我们识别了用例(第 2 步)所需的数据,然后将其映射到源系统(第 3 步)。现在可以创建的下一个视图是用例与源系统的映射,市场营销和销售的情况见下图 8。

图 8 — 用例与源系统的映射。数据由 ChatGPT 4.0 生成,图片由作者提供。

在这里,深绿色表示数据对用例至关重要,浅绿色则表示数据是‘可有可无’或辅助的。例如,对于客户细分与目标定位全球 CRM 主数据的数据至关重要,但社交媒体分析的数据则是‘可有可无’的。

但我们已经对用例了解了更多。事实上,在上面的第 1 步中,我们首先做的就是识别用例及这些用例可能驱动的增量收入。这使我们现在可以说一些关于依赖特定数据源的价值创造的事情。因为如果我们知道一个给定的数据集对 3 个用例至关重要,而这些用例分别预计能驱动 200 万、300 万和 500 万美元的增量收入,我们可以说 1000 万美元的收入依赖于这个数据集。

你不能在孤立的情况下完成这个练习 — 你需要与相关的用例和业务流程 SME(主题专家)及所有者进行接触。这可能需要一些时间来识别这些人,但一旦找到他们,你通常会发现他们是合作的,因为他们有确保用例成功的利益,因此需要澄清哪些数据是关键的以及它能带来的影响。

在进行过程中,你可以开始建立一个概览,如第 8 图右侧所示,其中顶线收入影响估计覆盖了所有对市场营销和销售用例至关重要的数据源。在这里要小心重复计算,并确保你解释和说明数字;例如,如果一个给定的用例具有 100 万美元的价值创造潜力,依赖于 2 个数据源,你不能说这两个数据源合起来驱动 200 万美元。

第 5 步:资产评估

在前一步中,我们将用例及其驱动的价值与一组数据源进行映射。现在我们知道这些数据源(可以)驱动价值,这意味着它们对公司具有内在价值,因此可以被视为数据资产

虽然第 8 图已经非常有见地,但它还不能让我们优先考虑某些数据资产(及其投资)而非其他。如果一个给定的数据资产可以驱动很多价值,但它已经到位并且“适合目的”,可能不需要进一步的行动。

第 9 图呈现了四种数据资产评估状态,从“适合目的”到“缺失或存在大缺口”,使数据资产的评估保持一致。在这里,适合目的应广泛解读。在光谱的正面,这意味着正确的数据随时可用,具有正确的粒度和时效性;数据质量高且可靠,源系统从不宕机。在另一端,这意味着数据资产根本不存在,或者即使存在,数据也严重缺乏、不可靠和/或不完整。

第 9 图 — 数据资产评估值。图片由作者提供。

我们现在拥有构建所谓热图的工具,其中“热点区域”(即红色或琥珀色部分)表示价值创造的机会,因为这些地方的用例无法依赖它们所需的关键数据 — 参见下图第 10 图。

图 10 — 数据资产与用例的“热图”。图像由作者提供。

第 6 步:资产优先排序

下一步是根据我们现在对数据资产的了解来优先排序这些资产。图 11 展示了与图 10 相同的热图,但我重新加入了收入影响和依赖用例的数量。然后我重新排序了数据资产,将它们按产生的总收入影响降序排列。

图 11 — 数据资产与用例及其驱动的价值的热图。图像由作者提供。

现在变得更清楚了哪些数据资产可以优先进行增强和投资。例如,很明显 全球 CRM 主数据 是一个大问题,因为它没有最佳地支持 9 (!) 个用例,影响超过 1300 万美元。各种数据资产,如 Instagram Insights客户支持门户Google Ads 数据 都是合适的,因此似乎不需要修复。然后我们有一些在底部的数据资产,如 Shopify Analytics新闻聚合平台,这些数据资产可能尚未到位,但只支持 1 个用例,影响有限。

如果你是首席数据官,而这个全景图反映了你组织在特定领域的数据资产和用例,那么一个以影响为驱动的路线图将向你展开。显然有机会挑选一两个数据资产,并将其作为战略位置来提升战略性数据的治理。这可以用来嵌入和实现各种数据治理能力,如数据所有权和管理、元数据管理和数据质量,因为这些都是确保数据资产得到适当治理的关键。

第 7 步:数据资产组合

众所周知,首席数据官等数据领导者的预期任期很短,平均不到 2.5 年。这在很大程度上是因为首席数据官在短期到中期内难以实现有意义的业务影响。

这正是本观点中所述方法如此强大的原因——如果你按照步骤 1–6 中的逻辑来优先排序数据资产,你几乎可以确保产生影响。而且因为你从用例及其影响开始,你从一开始就与业务和职能部门进行了接触,因此避免了“为了数据而做数据”的陷阱,这将大大避免数据治理被视为成本和对业务的阻碍。

你还没完成。你所识别的数据资产是类似于房地产投资组合中的属性——你需要主动管理它们,确保它们保持更新,数据用户继续满意,新的需求出现时被纳入,并且价值生成不是假设的,而是明确地跟踪记录。

下图 12 展示了我们在这一观点中分析的组织的数据资产投资组合仪表板。它显示了已认证的数据资产数量、与其映射的用例数量,以及通过增量收入和风险缓解创造的价值。

图 12——数据资产的仪表板。图像由作者提供。

在中心,你可以看到一个图表,跟踪了认证的数据资产数量随时间的变化,以及更关键的启用用例数量和通过收入表达的相关影响。这对首席数据官的职业生涯长久至关重要,能够证明通过有针对的数据启用和治理活动创造的价值。

在底部,你可以看到数据资产的管道视图。其中一些正在经过结构化的激活生命周期,而其他一些已经在使用中。你会看到我们之前调查的全球 CRM 主数据确实已被优先考虑——它目前处于“开发”阶段。

市场的轶事

图片由 Lalit Kumar 提供,通过 Unsplash

正如在这一观点开始时提到的,我已经在欧洲和美国的多家公司中使用并完善了这种方法,涵盖了银行、保险、零售、技术和制造业。

在制造业的一个例子中,我们遵循了这里概述的 7 个步骤的略微调整版本。由于这是一个复杂的全球公司,识别整个组织中的用例并不可行。相反,我们选择了一个业务领域作为主要关注点,即商业部门,然后是市场营销和销售的子领域(类似于上面第 3 步中的用例范围)。

我们确定了一组约 30 个用例,其中大多数已经被定义用于其他目的。我们执行了一个简化的、加速版本的步骤 2-4,以识别所需的数据及其来源,并将用例与来源进行映射。我们跳到步骤 5,与用例所有者和主题专家沟通,询问他们是否有访问适合目的的数据。如果没有,那么缺少什么数据或来源——问题是什么?

我们很快确定了一组 8 个在所需数据方面有困难的使用案例,并发现 2 个特定的数据源对其中 6 个使用案例存在问题。我们没有继续扩展工作,而是开始了实际操作。与中央数据团队和商业团队一起,我们对这 2 个数据源进行了负责人分配,按照一套正式认证标准进行了评估,并制定了弥补差距的计划。

几个月后,第一个数据资产已经过增强和认证,以满足文档中描述的使用案例的需求。在撰写时,具体的影响还未被测量(因为影响需要时间来体现),但初步的轶事证据表明,营销效果可能提高了高个位数甚至双位数。不管怎样,相关的首席数据官能够引入并完善该方法,取得了小幅成功,并启动了一个涵盖更多资产、使用案例和领域的更广泛的路线图。

祝好运!

建立和管理数据组合并不一定简单或快速,但这是值得付出努力的。我希望这里列出的步骤对你有所帮助。我很想听听你的进展情况,如果你有反馈或自己的故事要分享,欢迎在评论中告诉我。

在数据资产赋能之旅中祝你一路顺风!

如何在 AWS 云上使用 Kubernetes 和 oneAPI 构建 ML 应用

原文:towardsdatascience.com/how-to-build-distributed-ml-applications-on-the-aws-cloud-with-kubernetes-and-oneapi-81535012d136?source=collection_archive---------9-----------------------#2023-03-17

图片来源

学习 Kubernetes 和 Intel AI Analytics Toolkit 的基础知识,以构建分布式 ML 应用

Eduardo AlvarezTowards Data Science Eduardo Alvarez

·

关注 发表在 Towards Data Science ·12 min 阅读·2023 年 3 月 17 日

--

构建和部署高性能 AI 应用程序可能是一项具有挑战性的任务,需要大量的计算资源和专业知识。幸运的是,现代技术如 Kubernetes、Docker 和 Intel AI Analytics Toolkit (AI Kit) 使得开发和部署优化性能和可扩展性的 AI 应用程序变得更加容易。此外,通过使用像 Amazon Web Services (AWS) 这样的云服务,开发人员可以进一步简化流程,利用云提供的灵活和可扩展的基础设施。

在本文中,我们将探讨如何使用 Kubernetes、Docker 和 Intel AI Analytics Toolkit 在 AWS 云上构建和部署 AI 应用程序。具体而言,我们将重点关注第一个 Intel 云优化模块,它作为一个模板,包含各种 AI 工作负载的 Intel 加速方案。我们还将介绍在过程中使用的 AWS 服务,包括 Amazon Elastic Kubernetes Service (EKS)、Amazon Elastic Container Registry (ECR)、Amazon Elastic Compute Cloud (EC2) 和 Elastic Load Balancer (ELB)。

图 1. 该架构设计用于 AI 生产场景,其中需要训练许多离散模型,并且计算需求较低至中等。 — 作者提供的图片

我们将部署的示例应用程序聚焦于贷款违约预测,这是金融行业中的一个常见问题。我们将使用 daal4Py 库来加速 XGBoost 分类器的推理,使我们能够在减少训练和部署模型所需时间的同时实现高性能。

图 2. 一个简化的 Intel(R) oneAPI 数据分析库 API,允许数据科学家或机器学习用户快速使用该框架。旨在帮助提供对 Intel(R) oneAPI 数据分析库的抽象,以便直接使用或集成到自己的框架中。 — 图片来源

在本文结束时,读者将对如何使用 Kubernetes、Docker 和 Intel AI Analytics Toolkit 在 AWS 云上构建和部署高性能 AI 应用程序有基本的了解。此外,他们还将获得一个实际示例,展示如何利用这些技术加速贷款违约预测模型的推理。

你可以在我们的公共 GitHub 仓库中找到本教程的所有源代码。

准备你的开发环境

安装 AWS CLI — AWS CLI(命令行界面)工具是一个用于管理各种 Amazon Web Services (AWS) 资源和服务的命令行工具。

curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
sudo apt install unzip
unzip awscliv2.zip
sudo ./aws/install

使用aws configure配置 AWS 凭证 — 了解更多有关使用 aws cli 设置凭证的信息,请点击这里

安装 eksctl — eksctl 是一个用于在 EKS 上创建、管理和操作 Kubernetes 集群的命令行工具。

curl --silent --location "https://github.com/weaveworks/eksctl/releases/latest/download/eksctl_$(uname -s)_amd64.tar.gz" | tar xz -C /tmp
sudo mv /tmp/eksctl /usr/local/bin
eksctl version

安装 aws-iam-configurator — AWS IAM Authenticator 是一个命令行工具,使用户能够使用其 AWS IAM 凭证与 EKS 上的 Kubernetes 集群进行身份验证。

curl -Lo aws-iam-authenticator https://github.com/kubernetes-sigs/aws-iam-authenticator/releases/download/v0.5.9/aws-iam-authenticator_0.5.9_linux_amd64
chmod +x ./aws-iam-authenticator
mkdir -p $HOME/bin && cp ./aws-iam-authenticator $HOME/bin/aws-iam-authenticator && export PATH=$PATH:$HOME/bin
echo 'export PATH=$PATH:$HOME/bin' >> ~/.bashrc
aws-iam-authenticator help

安装 kubectl — Kubectl 是一个命令行工具,用于与 Kubernetes 集群进行交互。它允许用户部署、检查和管理 Kubernetes 集群上运行的应用程序和服务,并执行各种管理任务,如扩展、更新和删除资源。

curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
curl -LO "https://dl.k8s.io/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl.sha256"
echo "$(cat kubectl.sha256)  kubectl" | sha256sum --check
sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl

我们的贷款违约预测应用程序

我们将要部署的应用程序基于贷款违约风险预测 AI 参考套件

图片来源

我们将参考解决方案中的代码进行了重构,使其更加模块化,以支持我们的三个主要 API:

  • 数据处理 — 该端点预处理数据并将其存储在数据湖或其他结构化格式中。此代码库还处理用于基准测试的数据集扩展。

  • 模型训练 — 该端点训练一个 XGBoost 分类器,并将其转换为推理优化的 daal4py 格式。

  • 推理 — 该端点接收一个包含原始数据的负载,并返回每个样本的贷款违约分类。

下面的目录树概述了代码库的各种脚本、资产和配置文件。大多数 ML 应用程序代码位于app/文件夹。该文件夹包含loan_defaultutils包 — loan_default 包包含支持我们三个主要 API 的服务器端 Python 模块。server.py脚本包含 FastAPI 端点配置、负载数据模型和启动 uvicorn 服务器的命令。

├───app/
|   ├───loan_default/
|   |   ├───__init__.py
|   |   ├───data.py
|   |   ├───model.py
|   |   └───predict.py
|   ├───utils/
|   |   ├───__init__.py
|   |   ├───base_model.py
|   |   ├───logger.py
|   |   └───storage.py  
|   ├───logs/
|   ├───server.py
|   └───requirements.txt    
|
├───kubernetes/
|   ├───cluster.yaml
|   ├───deployment.yaml
|   ├───service.yaml
|   └───serviceaccount.yaml
|
├─README.md
├─Dockerfile
├─SECURITY.md

深入代码库超出了本教程的范围。然而,值得指出的是,我们在哪里利用 daal4py 来提高推理性能。在 model.py 文件中,你会找到“train”方法,该方法处理模型训练并使用 d4p.get_gbt_model_from_xgboost() 函数将模型转换为 daal4py 格式。

def train(self):
        # define model
        params = {
            "objective": "binary:logistic",
            "eval_metric": "logloss",
            "nthread": 4,  # flags.num_cpu
            "tree_method": "hist",
            "learning_rate": 0.02,
            "max_depth": 10,
            "min_child_weight": 6,
            "n_jobs": 4,  # flags.num_cpu,
            "verbosity": 0,
            "silent": 1,
        }

        log.info("Training XGBoost model")
        self.clf = xgb.train(params, self.DMatrix, num_boost_round=500)
        self.clf = d4p.get_gbt_model_from_xgboost(self.clf)

在原始参考工具包的性能测试中,这一简单转换导致了大约 4.44 倍的性能提升(图 3)。

图 3. 对于大小为 1M 的批量推理,Intel® v1.4.2 提供了比标准 XGBoost v0.81 高达 1.34 倍的加速,使用 Intel® oneDAL 时,高达 4.44 倍的加速。— 图片由作者提供

配置和启动 Elastic Kubernetes Service 集群

Elastic Kubernetes Service 是一种完全托管的服务,它使得在 Amazon Web Services (AWS) 上使用 Kubernetes 部署、管理和扩展容器化应用变得容易。它消除了在自己的基础设施上安装、操作和扩展 Kubernetes 集群的需要。

要启动我们的 EKS 集群,我们必须首先创建我们的 集群配置文件

apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig

metadata:
  name: "eks-cluster-loanDefault"
  version: "1.23"
  region: "us-east-1"

managedNodeGroups:
- name: "eks-cluster-loanDefault-mng"
  desiredCapacity: 3
  instanceType: "m6i.large"

我们可以在“metadata”部分配置集群部署的名称和区域,以及我们希望运行的 EKS 版本。最重要的是,我们可以在“managedNodeGroups”部分配置计算资源的基本需求:

  • desiredCapacity — 创建堆栈时要扩展到的节点数量。在本教程中,我们将其设置为 3。

  • instanceType — 节点的实例类型。本教程使用 m6i.large 实例,即第三代 Xeon(2vCPU 和 8GiB)。一旦开放使用,我们建议尝试 r7iz 实例系列 以利用 Intel 高级矩阵扩展 (AMX) —— 这是专为深度学习工作负载设计的加速器,内置于 Intel 第四代 Xeon CPUs 中。

我们执行 eksctl create cluster -f cluster.yaml 以创建 Cloud Formation 堆栈并配置所有相关资源。根据当前配置,此过程应需要 10 到 15 分钟。你应该会看到类似图 4 的日志。

图 4. EKS 集群配置工作流的 Cloud Formation 日志 — 图片由作者提供

你应该运行一个快速测试以确保你的集群已正确配置。运行 eksctl get cluster 以获取可用集群的名称,运行 eksctl get nodegroup --cluster <cluster name> 以检查集群的节点组。

设置所有 Kubernetes 应用程序资源

让我们深入了解如何启动您的 Kubernetes 应用程序。此过程包括创建命名空间、部署清单和 Kubernetes 服务。所有这些文件都可以在 教程的代码库 中找到。

在继续本部分教程之前,请:

作者提供的图像

Kubernetes 命名空间是一个虚拟集群,将物理集群中的资源划分和隔离。我们来创建一个名为“loan-default-app”的命名空间。

kubectl create namespace loan-default-app

现在,让我们配置我们的 Kubernetes 部署清单。Kubernetes 部署是一种 Kubernetes 资源,允许您声明性地管理给定应用程序的一组副本 pod,确保所需数量的副本始终运行并可用,同时启用如扩展、滚动更新和回滚等功能。它还提供了对 pod 的抽象层,允许您定义应用程序的期望状态,而无需担心底层基础设施。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: "eks-loan-default-app"
  namespace: "loan-default-app"
  labels:
    app: "loan-default"
spec:
  replicas: 3
  selector:
    matchLabels:
      app: "loan-default"
  template:
    metadata:
      labels:
        app: "loan-default"
    spec:
     serviceAccountName: "loan-default-service-account"
     topologySpreadConstraints:
        - maxSkew: 1
          topologyKey: kubernetes.io/hostname
          whenUnsatisfiable: ScheduleAnyway
          labelSelector:
            matchLabels:
              app: "loan-default"
     containers:
       - name: "loan-app-image"
         image: <insert image uri>
         ports:
           - containerPort: 80
         imagePullPolicy: "Always"

上面的 Kubernetes 部署清单(deployment.yaml)定义了以下内容:

  • kind: Deployment — Kubernetes 资源的类型

  • name: “eks-loan-default-app” — 我们的部署名称

  • namespace: “loan-default-app” — 此部署应分配的命名空间

  • app: “loan-default” — 我们为应用程序分配的名称

  • replicas: 3 — 要创建并始终维持的 pod 复制品数量。

  • serviceAccountName: “loan-default-service-account” — 确保这与您之前创建的服务账户匹配。

  • topologySpreadConstraints: — 帮助定义 pod 应如何在集群中分布。当前配置将保持 pod 在可用节点上的均匀分布。

  • containers: name/image — 在这里提供应用程序容器镜像的 URI,并为镜像分配一个名称。

运行 kubectl apply -f deployment.yaml 以创建您的 Kubernetes 部署。

现在让我们配置我们的 Kubernetes 服务。Kubernetes 服务是一个抽象层,为运行相同应用程序的一组 pods 提供稳定的 IP 地址和 DNS 名称,使客户端可以访问该应用程序,而无需知道单个 pods 的具体 IP 地址。它还提供了一种在多个应用程序副本之间负载均衡流量的方法,并可用于定义外部访问的入口规则。

apiVersion: v1
kind: Service
metadata:
  name: "loan-default-service"
  namespace: "loan-default-app"

spec:
  ports:
  - port: 8080
    targetPort: 5000
  selector:
    app: "loan-default"
  type: "LoadBalancer"

上面的 Kubernetes 服务清单(service.yaml)定义了以下内容:

  • kind: Service — Kubernetes 资源的类型。

  • name: “loan-default-service” — 我们部署的名称。

  • namespace: “loan-default-app” — 该服务应分配到的命名空间。

  • port: 8080 — 服务将监听的端口。

  • targetPort: 5000 — 服务与 pod 之间通信的端口。

  • app: “loan-default” — 我们为应用程序指定的名称。

  • type: “LoadBalancer” — 我们选择的服务类型。

运行kubectl apply -f service.yaml以创建你的 Kubernetes 服务。

这将自动启动一个 Elastic Load Balancer — 一项云服务,它将传入的网络流量分配到多个目标,如 EC2 实例、容器和 IP 地址,以提高应用程序的可用性和容错性。我们可以使用 ELB 的公共 DNS 从全球任何地方向我们的 API 端点发起请求。

在继续之前,这里有一些提示:

  • 运行kubectl get all -n loan-default-app以获取你所配置的 Kubernetes 资源的全面概述。你应该能看到你的 pods、服务和副本组。

  • 运行kubectl -n loan-default-app describe pod <pod-id>以获取有关你的 pod 的详细描述。

  • 如果你需要诊断特定 pod 的行为,可以通过运行kubectl exec -it <pod-id> -n loan-default-app -- bash在 pod 内启动一个 bash shell — 输入 exit 并按回车键退出 shell。

测试我们的贷款违约预测 Kubernetes 应用程序

现在我们的基础设施已经到位,我们可以设置应用程序的数据组件并测试我们的端点。

我们将开始 从 Kaggle 下载数据集。用于本演示的数据集是一个包含 32581 个模拟贷款的数据集。它有 11 个特征,包括客户和贷款特征,以及一个标签,即贷款的结果。一旦我们在工作目录中获得了 .csv 文件,我们可以创建一个 S3 桶并上传我们的 Kaggle 数据集。

# create S3 Bucket for data
aws s3api create-bucket --bucket loan-default --region us-east-1

# upload dataset
aws s3api put-object --bucket loan-default --key data/credit_risk_dataset.csv --body <local path to data

向我们的 API 端点发起 HTTP 请求

我们将使用 Curl 来向我们的服务器发送 HTTP 请求。Curl 允许你通过提供一个命令行接口来发送 HTTP 请求,在这个接口中,你可以指定 URL、请求方法、头部信息和数据。然后,它处理建立连接、发送请求和接收响应的底层细节,使得自动化 HTTP 交互变得容易。

我们将从向我们的数据处理端点发送请求开始。这将创建测试/训练文件,并将我们的预处理管道保存为 .sav 文件到 S3。请求的主体需要以下参数:

  • bucket: S3 桶的名称

  • key: 你的原始数据在 S3 中保存的路径

  • size: 你想处理的样本总数

  • backend: 选项包括“local”或“s3” — 代码库支持在本地运行整个应用程序以进行调试。当使用“s3”后端时,“local_path”和“target_path”参数可以设置为“None”。

curl -X POST <loadbalancerdns>:8080/data -H 'Content-Type: application/json' -d '{"bucket":"loan-default","backend":"s3","key":"data/credit_risk_dataset.csv","target_path":"None","local_path":"None","size":400000}'

你可以在 AWS 控制台中导航到你的 S3 桶,以验证所有文件是否已正确生成(图 5)。

图 5. S3 桶包含由我们 /data 端点生成的输出 — 图片由作者提供

现在我们准备训练我们的 XGBoost 分类器模型。我们将向我们的 /train 端点发出请求,这将训练我们的模型,将其转换为 daal4py 格式,并保存到 S3。请求的主体需要以下参数:

  • bucket: S3 桶的名称

  • data_key: 包含由我们数据处理 API 创建的处理数据的文件夹路径

  • model_key: 我们希望存储训练模型的文件夹

  • model_name: 我们希望给训练模型起的名字

  • backend: 选项包括“local”或“s3” — 代码库支持在本地运行整个应用程序以进行调试。当使用“s3”后端时,“local_model_path”和“local_data_path”参数可以设置为“None”。

curl -X POST <loadbalancerdns>:8080/train -H 'Content-Type: application/json' -d '{"bucket":"loan-default","backend":"s3","local_model_path":"None","data_key":"data","model_key":"model","model_name":"model.joblib","local_data_path":"None"}'

你可以在 AWS 控制台中导航到你的 S3 桶,以验证你的模型文件是否已创建(图 6)。

图 6. S3 桶包含由我们 /train 端点生成的输出 — 图片由作者提供

现在我们已经有了一个训练过的 daal4py 优化的 XGBoost 分类器,我们可以向我们的 API 发出推断请求。/predict 端点将返回一个二元分类,True 表示高违约可能性,False 表示低违约可能性。响应还包括分类器生成的概率。在代码库中,我们将任何超过 50% 的概率标记为高违约可能性。这可以调整为返回更离散的标签,如低、中和高违约可能性。请求的主体需要以下参数:

  • bucket: S3 桶的名称

  • model_name: 训练模型的名称是 S3

  • data_key: 包含 .sav 处理管道文件的文件夹路径(应与处理数据的文件夹相同)

  • model_key: 你训练的模型在 S3 中保存的文件夹

  • 示例:将你的模型输入作为字典列表

  • 后端:选项包括“local”或“s3” — 代码库支持在本地运行整个应用程序以进行调试。当使用“s3”后端时,可以将“local_model_path”和“preprocessor_path”参数设置为“None”。

curl -X POST <loadbalancerdns>:8080/predict -H 'Content-Type: application/json' -d '{"backend":"s3","model_name":"model.joblib","data_key":"data","bucket":"loan-default","model_key":"model","sample":[{"person_age":22,"person_income":59000,"person_home_ownership":"RENT","person_emp_length":123,"loan_intent":"PERSONAL","loan_grade":"D","loan_amnt":35000,"loan_int_rate":16.02,"loan_percent_income":0.59,"cb_person_default_on_file":"Y","cb_person_cred_hist_length":3},{"person_age":22,"person_income":59000,"person_home_ownership":"RENT","person_emp_length":123,"loan_intent":"PERSONAL","loan_grade":"D","loan_amnt":35000,"loan_int_rate":55.02,"loan_percent_income":0.59,"cb_person_default_on_file":"Y","cb_person_cred_hist_length":0}],"local_model_path":"None","preprocessor_path":"None"}'

你可以很快收到服务器的响应(见图 7)。

图 7. /predict 端点的有效载荷和响应 — 作者提供的图像

你可以在我们的公共 GitHub 仓库 找到本教程的所有源代码。如果你有任何问题,欢迎留言或 在 LinkedIn 上联系我

总结与讨论

在本教程中,我们展示了如何在 AWS 云上基于高可用性解决方案架构构建 Kubernetes 应用程序。我们重点介绍了使用英特尔 Xeon 处理器和 AI Kit 组件来提高性能,同时实现与 Kubernetes 的扩展。

我们鼓励读者关注即将举办的研讨会和未来的英特尔云优化模块(ICOMs),因为利用这些模块中的英特尔优化可以使他们的应用程序获得“由英特尔加速”徽章。

我们的 ICOMs 目标是帮助开发人员通过英特尔的软件和硬件提升应用程序的性能和可扩展性。随着对高性能云应用程序的需求增加,开发人员需要保持信息更新,利用最新的技术和工具。

别忘了关注 我的个人资料以获取更多类似文章

如何培养数据科学家的良好习惯

原文:towardsdatascience.com/how-to-build-good-habits-as-a-data-scientist-74212a7b411f?source=collection_archive---------5-----------------------#2023-02-23

TDS 编辑数据科学走向 TDS 编辑

·

关注 发表在 数据科学走向 · 作为 新闻简报 发送 · 3 分钟阅读 · 2023 年 2 月 23 日

--

现在快到三月了,这意味着大多数在仅仅两个月前许下庄严新年决心的人很可能已经放弃了。(无论如何,谷歌和 ChatGPT 都这样告诉我们。)

这是否值得绝望?恰恰相反——在没有基于日历的(即外部强加的和随意的)目标期望的情况下,现在是反思我们真正想要成长的领域的最佳时机。然后我们可以采取小而具体的步骤,稳步前进。

为了帮助你在这个自我引导的旅程中,我们精选了四篇优秀的文章,这些文章从各种数据科学和机器学习的背景下探讨了强大习惯的话题。它们的共同点在于致力于可持续、渐进的改进,而非快速、短暂的解决方案。

  • 使你的数据始终可靠是一个过程Barr Moses(及合著者 Will Robins)观察到数百个数据团队在维持数据质量标准方面的挣扎。他们将所学的经验提炼成一个六步的长期流程(如果你愿意,可以称之为“数据可靠性马拉松”)——这一流程可以激励个体从业者以及整个组织进行变革。

  • 领导力在于细节。在与优秀和效果较差的经理共事过之后,并在自己领导过几次机器学习项目后,Aliaksei Mikhailiuk 对成功的原则有了深刻的理解。他分享了关于沟通、基础设施和文档的见解,以及可以在这些领域中改进的实际步骤。

图片由 Eva Wilcock 提供,来源于 Unsplash

  • 如何高效运作数据科学项目。无论你是独立顾问还是大型资源丰富的数据团队成员,你都可以并且应该拒绝让混乱主导你的工作流程。Alexandre Rosseto Lemos 最近发布了一份实用指南,适用于任何希望提升组织能力的人;其中充满了专门针对数据专业人士需求的建议。

  • 跟踪你自己的进步和成就。无论你的绩效评审即将到来,还是你在寻找新的职位,拥有强有力的成就记录都是关键。Semi Koen 的最新帖子强调了详细、实时记录工作亮点的重要性,以及根据扎实的自我评估培养为自己代言的习惯。

如果你已经读到这里,说明你显然拥有了强大的阅读习惯;为什么不继续培养它,通过一些最近的杰出文章呢?

  • 你该如何选择合适的相似度度量来为你的推荐算法服务?Jiahui Wang的简明介绍涵盖了四种基本类型。

  • 对于初学者友好的偏差-方差权衡入门,请前往Cassie Kozyrkov的轻松三部分系列。

  • 比较模型的性能是任何机器学习项目中的关键部分;这就是为什么,根据Joris Guerin,设计稳健的机器学习实验如此重要。

  • 深入探讨强化学习的数学基础,Shailey Dash 解释了马尔可夫决策模型 (MDP) 中的随机理论。

  • Jorge Martín Lasaosa 分享了一份关于 XGBoost 算法的全面指南,从原始论文到完整的 Python 实现。

我们希望你考虑本周成为 Medium 会员——这是支持我们发布工作的最直接和有效的方式。

直到下一个变量,

TDS 编辑

如何构建具有 O(N) 复杂度的图 Transformer

原文:towardsdatascience.com/how-to-build-graph-transformers-with-o-n-complexity-d507e103d30a?source=collection_archive---------6-----------------------#2023-04-19

大图网络 Transformer 教程

Qitian WuTowards Data Science Qitian Wu

·

关注 发表在 Towards Data Science ·7 分钟阅读·2023 年 4 月 19 日

--

图片: Unsplash

构建强大的图 Transformer 已成为图机器学习社区的热门话题,因为最近的研究表明,纯 Transformer 基础的模型在许多 GNN 基准测试中表现出色,甚至优于其他模型(可以参见一些典型的工作 [1, 2, 3])。

然而,挑战在于 Transformers 的关键设计[4],即注意力机制,通常需要相对于输入标记的二次复杂度。在图学习的背景下,Transformers 的输入标记是图中的节点,旨在捕捉节点间长距离交互的全局注意力对于具有任意数量节点的图而言难以扩展。例如,在常见的节点分类数据集 Pubmed(约 10K 节点)上,在 16GB 内存的 GPU 上运行一个单层单头 Transformer 并进行全对全注意力计算是不可行的。

本教程将介绍两个最近的可扩展图 Transformer[5, 6],它们设计了具有相对于标记(节点)数量的线性复杂度的特殊全局注意力机制。本教程的目标是提供关于:

  1. 如何在保留全对全注意力的情况下实现线性复杂度;

  2. 如何使用 Pytorch 代码实现新的注意力函数。

这些与专注于高层次思想描述的已发表科学论文是互补的。

O(N²) 从哪里来?

Transformers 可以被视为图神经网络(GNNs)的推广,其中 GNNs 中的固定邻接矩阵扩展为 Transformers 中的可变注意力矩阵。从图的角度来看,GNNs 在固定观察图(通常具有稀疏连接)上进行消息传递,而 Transformers 的消息传递则基于密集连接的潜在图(其边权由成对注意力分数生成)。

GNNs 和 Transformers 在不同结构上的消息传递比较:GNNs 在稀疏观察图上传播信号,而 Transformers 可以被视为在具有层次边权的密集连接图上传播信号。后者需要对 N*N 注意力矩阵进行估计,并在如此密集的矩阵上进行特征传播。

接下来我们将总结原始 Transformer[4] 中的标准注意力计算。当前层的嵌入首先映射到查询、键和值向量,然后计算全对全注意力以进行特征聚合:

我们使用 z 表示节点嵌入,q、k 和 v 分别表示查询、键和值向量。W_Q、W_K 和 W_V 是第 k 层的可学习权重。

由于上述更新的计算需要 O(N),因此在一层中更新 N 个节点的总复杂度将需要 O(N²)。从矩阵视角看 O(N²) 复杂度的一个更直观的方式是,它在实际应用中使用深度学习工具(如 Pytorch、Tensorflow 等)时被考虑。具体而言,我们可以在下面说明一个注意力层的计算流程。

左侧部分展示了从矩阵视角看待的全局注意力层,右侧部分展示了对应的数据流,其中红色标记的矩阵乘积引入了 O(N²)的复杂度。

上述注意力层可以通过 PyTorch 轻松实现(在这里我们使用“einsum”函数,这是一个广义矩阵乘积实现,详细信息请参见这里):

# qs: [N, H, D], ks: [L, H, D], vs: [L, H, D]

attn = torch.einsum("nhd,lhd->nlh", qs, ks)  # [N, L, H]
attn = torch.softmax(attn, dim=1) # [N, L, H]
z_next = torch.einsum("nlh,lhd->nhd", attn, vs)  # [N, H, D]

尽管二次复杂度很麻烦,我们接下来介绍两种有效的方法,可以严格地将 O(N²)减少到 O(N),更重要的是,仍然保持了对所有对之间影响的明确建模的表达能力

NodeFormer: 核化 Softmax 消息传递

最近的工作 NodeFormer [5] (一种用于节点分类的可扩展图结构学习 Transformer)利用随机傅里叶特征[8, 9]通过核近似将点积然后指数操作转换为映射然后点积替代方案:

这里的\phi 函数是一个非参数随机特征映射,其中特征维度 m 控制了对原始指数项的近似能力。有关随机特征映射及其理论属性的更多介绍,请参见参考文献[8]。

通过这种方式,原始的 Softmax 注意力可以被转化为一种高效的版本:

从左侧到右侧的推导是依据矩阵乘积的基本关联规则,即改变矩阵乘积的顺序。

注意,在右侧,N 个节点上的两个求和项与节点 u 无关,这意味着它们在计算一次后可以被所有节点重复使用。因此,对于每一层更新 N 个节点,可以先花费 O(N)来计算这两个求和项,然后基于此,计算所有 N 个节点的下一层嵌入只需 O(N),从而总复杂度为 O(N)。

为了更好地理解如何实现线性复杂度,我们可以将矩阵形式的计算流程写成如下。

左侧部分展示了从矩阵视角看待的 NodeFormer 的全局注意力层,右侧部分展示了对应的数据流,其中红色标记的矩阵乘积是计算瓶颈,需要 O(Nmd)。

注意,矩阵乘积的顺序在减少复杂度中发挥了重要作用。在上述计算中,尽管我们成功实现了全对全注意力聚合,但避免了 N*N 的注意力矩阵。一层的确切复杂度是 O(Nmd)。由于对于大图,N 通常比 m 和 d 大几个数量级,因此在实际应用中计算效率可以显著提高。例如,具有三层 Transformer 的 NodeFormer 仅需 4GB GPU 内存即可计算 0.1M 节点之间的全对全注意力。以下是实现上述高效全对全注意力的 Pytorch 代码。完整的开源模型实现公开可用,地址为GitHub

# qs: [N, H, D], ks: [L, H, D], vs: [L, H, D]

qs = softmax_kernel(qs) # [N, H, M]
ks = softmax_kernel(ks) # [L, H, M]

# numerator
kvs = torch.einsum("lhm,lhd->hmd", ks, vs)
attn_num = torch.einsum("nhm,hmd->nhd", qs, kvs) # [N, H, D]

# denominator
all_ones = torch.ones([ks.shape[0]])
ks_sum = torch.einsum("lhm,l->hm", ks, all_ones)
attn_den = torch.einsum("nhm,hm->nh", qs, ks_sum)  # [N, H]

# attentive aggregated results
z_next = attn_num / attn_den # [N, H, D]

DIFFormer: 简化的注意力计算

我们可以从 NodeFormer 中学到的教训是,复杂性减少的关键在于矩阵乘积相对于注意力聚合的顺序。接下来,我们可以利用这个思想设计另一种高效的注意力函数,而无需任何随机近似,即DIFFormer中提到的简单注意力(在原始论文中也称为简单扩散模型,灵感来自于潜在结构上的扩散)。

我们的观察来源于指数函数的泰勒展开,这可以用来激发一个新的注意力函数:

注意,尽管新的注意力函数是从 e^x 的一级泰勒展开中激发出来的,但它并不要求是对原始 Softmax 注意力的良好近似。也就是说,我们发现它在实际应用中通过广泛的实验表现稳定良好。

由于我们可以继承重新排序矩阵乘积的技巧,这个新的注意力层可以通过线性复杂度高效计算:

再次强调,右侧的两个求和项被所有节点共享,因此只需要计算一次。为了清楚地看到 O(N)复杂度,我们可以用矩阵视图写出计算流程。

矩阵乘积的计算瓶颈在右侧的红色部分标记,导致 O(Nd²)复杂度。再次注意,d 在实际应用中通常比 N 小几个数量级:例如,d 的范围可能从 32 到 256,而 N 可以达到百万甚至十亿。

以下是单层 DIFFormer 简单注意力的 Pytorch 实现,完整的模型实现公开可用,地址为GitHub。特别是,当配备简单注意力时,DIFFormer(在原始论文中也称为 DIFFormer-s)可以扩展到具有百万节点的大规模图。

# qs: [N, H, D], ks: [L, H, D], vs: [L, H, D]

qs = qs / torch.norm(qs, p=2) # [N, H, D]
ks = ks / torch.norm(ks, p=2) # [L, H, D]
N = qs.shape[0]

# numerator
kvs = torch.einsum("lhm,lhd->hmd", ks, vs)
attn_num = torch.einsum("nhm,hmd->nhd", qs, kvs) # [N, H, D]
all_ones = torch.ones([vs.shape[0]])
vs_sum = torch.einsum("l,lhd->hd", all_ones, vs) # [H, D]
attn_num += vs_sum.unsqueeze(0).repeat(vs.shape[0], 1, 1) # [N, H, D]

# denominator
all_ones = torch.ones([ks.shape[0]])
ks_sum = torch.einsum("lhm,l->hm", ks, all_ones)
attn_den = torch.einsum("nhm,hm->nh", qs, ks_sum)  # [N, H]

# attentive aggregated results
attn_den = torch.unsqueeze(attn_den, len(attn_den.shape))  # [N, H, 1]
attn_den += torch.ones_like(attn_den) * N
z_next = attn_num / attn_den # [N, H, D]

参考文献

[1] 邢成轩等人, 变换器在图表示中真的表现不好吗?, NeurIPS 2021.

[2] 拉迪斯拉夫·兰帕塞克等人, 通用、强大、可扩展的图形变换器的配方, NeurIPS 2022.

[3] 金宇等人, 纯变换器是强大的图学习者, NeurIPS 2022.

[4] 阿什希什·瓦斯瓦尼等人, Attention is All you Need, NeurIPS 2017.

[5] 吴启天等人, NodeFormer: 一种可扩展的图结构学习 Transformer 用于节点分类, NeurIPS 2022. 本文提出了一种用于大规模节点分类图的高效 Transformer。关键设计是具有线性复杂度的核化 Softmax 消息传递,并且作者进一步将核技巧扩展到 Gumbel-Softmax,从可能的全对连接图中学习稀疏潜在结构。

[6] 吴启天等人, DIFFormer: 由能量约束扩散引发的可扩展(图形)变换器, ICLR 2023. 本工作设计了一种可扩展的图形 Transformer,其注意力函数来源于对潜在结构扩散的扩散性估计。在模型架构方面,DIFFormer 将 NodeFormer 中用于实现 O(N) 复杂度的关键思想进行了泛化,因此可以被视为 NodeFormer 的 2.0 版本。

[7] 线性变换器综述 本博客介绍了最近高效变换器中成功将注意力复杂度降低到 O(N) 的几种典型策略,例如低秩近似、局部-全局注意力以及将 softmax 用作核函数。

[8] 阿里·拉希米和本杰明·雷赫特. 大规模核机器的随机特征, NeurIPS 2007. 本早期工作介绍了随机特征图作为处理大量数据点的有效近似技术,以及其理论性质。

[9] 刘方辉等人, 核近似中的随机特征- 算法、理论及其应用的综述, IEEE TPAMI 2022. 本综述总结了不同随机特征在核近似中的全面集合,并讨论了它们的不同特性和适用性。

除非另有说明,否则所有图片均由作者提供。

如何使用 Polars 构建基于人气的推荐系统

原文:towardsdatascience.com/how-to-build-popularity-based-recommenders-with-polars-cc7920ad3f68

推荐系统

基本的推荐系统易于理解和实现,且训练速度快

Dr. Robert KüblerTowards Data Science Dr. Robert Kübler

·发布于 Towards Data Science ·阅读时间 6 分钟·2023 年 4 月 28 日

--

由我在 dreamstudio.ai 创建

推荐系统是旨在根据用户的过去行为、偏好和互动提供推荐的算法。它们已成为各种行业(包括电子商务、娱乐和广告)的重要组成部分,提升用户体验、增加客户保留率并推动销售。

虽然存在各种高级推荐系统,但今天我想向你展示其中一种最简单的——但往往难以超越——推荐系统:基于人气的推荐系统。它是一个优秀的基线推荐系统,除了更高级的模型(如矩阵分解)外,你应该始终尝试一下。

## 基于嵌入的推荐系统简介

学习如何在 TensorFlow 中构建一个简单的矩阵分解推荐系统

towardsdatascience.com

我们将在本文中使用 polars 创建种不同风格的基于人气的推荐系统。如果你之前没有使用过快速的 pandas 替代品 polars,不必担心;这篇文章是学习它的绝佳机会。让我们开始吧!

初步想法

基于人气的推荐系统通过向客户推荐最常购买的产品来工作。这一模糊的概念可以转化为至少两种具体的实现:

  1. 检查哪些文章在所有客户中最常被购买。将这些文章推荐给每位客户。

  2. 检查哪些文章在每位客户中最常被购买。将这些每位客户的文章推荐给对应的客户。

现在我们将展示如何使用我们自己创建的数据集具体实现这些。

如果你想跟随一个真实的数据集,Kaggle 上的H&M 个性化时尚推荐挑战为你提供了一个很好的例子。由于版权原因,我不会在这篇文章中使用这个可爱的数据显示集。

数据

首先,我们将创建自己的数据集。如果你还没有安装 polars,请确保安装:

pip install polars

然后,让我们创建一个随机数据集,包括(customer_id, article_id)对,你应该将其理解为“拥有此 ID 的客户购买了拥有该 ID 的商品。”我们将使用 1,000,000 名可以购买 50,000 种产品的客户。

import numpy as np

np.random.seed(0)

N_CUSTOMERS = 1_000_000
N_PRODUCTS = 50_000
N_PURCHASES_MEAN = 100 # customers buy 100 articles on average

with open("transactions.csv", "w") as file:
    file.write(f"customer_id,article_id\n") # header

    for customer_id in range(N_CUSTOMERS):
        n_purchases = np.random.poisson(lam=N_PURCHASES_MEAN)
        articles = np.random.randint(low=0, high=N_PRODUCTS, size=n_purchases)
        for article_id in articles:
            file.write(f"{customer_id},{article_id}\n") # transaction as a row

图片由作者提供。

这个中等大小的数据集有超过 100,000,000 行(交易),这是你在商业环境中可能会遇到的数量。

任务

我们现在想构建推荐系统来扫描这个数据集,以推荐受欢迎的项目。我们将阐明两种解释这个问题的变体:

  • 在所有客户中最受欢迎的产品

  • 每位客户最受欢迎的产品

我们的推荐系统应该为每位客户推荐十篇文章

注意: 我们将 不会 在这里评估推荐系统的质量。不过,如果你对这个话题感兴趣,可以给我发消息,因为这值得另写一篇文章。

在所有客户中最受欢迎的产品

在这个推荐系统中,我们甚至不关心谁购买了这些文章——我们所需的所有信息仅在article_id列中。

高层次地,它的工作原理是这样的:

  1. 加载数据。

  2. 计算每篇文章在article_id列中出现的频率。

  3. 将出现频率最高的十种产品作为每位客户的推荐。

熟悉的 Pandas 版本

作为一个温和的开始,让我们看看你如何在 pandas 中完成这个任务。

import pandas as pd

data = pd.read_csv("transactions.csv", usecols=["article_id"])
purchase_counts = data["article_id"].value_counts()
most_popular_articles = purchase_counts.head(10).index.tolist()

在我的机器上,这大约需要31 秒。这听起来有点少,但数据集仍然只是一个中等大小;对于更大的数据集情况会变得很糟糕。公平地说,10 秒是用来加载 CSV 文件的。使用更好的格式,例如parquet,可以减少加载时间。

注意: 我使用的是最新和最优化的版本 pandas 2.0.1。

但为了进一步准备 polars 版本,让我们使用方法链,这是一种我逐渐喜爱的技术,来完成 pandas 版本。

most_popular_articles = (
    pd.read_csv("transactions.csv", usecols=["article_id"])
    .squeeze() # turn the dataframe with one column into a series
    .value_counts()
    .head(10)
    .index
    .tolist()
)

这很棒,因为你可以从上到下阅读发生了什么,无需很多通常很难命名的中间变量(df_raw → df_filtered → df_filtered_copy → … → df_final,怎么样?)。不过运行时间还是一样。

更快的 Polars 版本

让我们使用方法链在 polars 中实现相同的逻辑。

import polars as pl

most_popular_articles = (
    pl.read_csv("transactions.csv", columns=["article_id"])
    .get_column("article_id")
    .value_counts()
    .sort("counts", descending=True) # value_counts does not sort automatically
    .head(10)
    .get_column("article_id") # there are no indices in polars
    .to_list()
)

除了运行时间:3 秒 替代 31 秒,这点令人印象深刻外,其他方面看起来相似!

Polars 真的比 pandas 快得多。

不可否认,这也是 polars 相较于 pandas 的主要优势之一。除此之外,polars 还有一个 创建复杂操作的便捷语法,这是 pandas 所没有的。我们将在创建其他基于受欢迎程度的推荐器时看到更多。

同样重要的是,pandas 和 polars 产生的输出如预期一致。

每个客户的最受欢迎产品

与我们第一个推荐器不同,我们现在希望按客户切片数据框,并获取每个客户的最受欢迎的产品。这意味着我们现在需要 customer_idarticle_id

我们通过一个仅包含三位客户 A、B 和 C 购买四个文章 1、2、3 和 4 的十个交易的小数据框来说明逻辑。我们希望获取 每个客户的前两篇文章。我们可以通过以下步骤实现:

图片由作者提供。

  1. 我们从原始数据框开始。

  2. 然后我们按 customer_idarticle_id 分组,并通过计数进行聚合。

  3. 然后我们再次对 customer_id 进行聚合,并将 article_id 写入列表中,就像在我们上一个推荐器中一样。不同的是我们 按照计数列对这个列表进行排序

这样,我们最终得到的正是我们想要的。

  • A 最常购买的产品是 1 和 2。

  • B 最常购买的产品是 4 和 2。产品 4 和 1 也会是正确的解决方案,但内部排序刚好把产品 2 推入了推荐中。

  • C 只购买了产品 3,所以就只有这些了。

这个过程的第 3 步听起来特别困难,但 polars 让我们可以方便地处理它。

most_popular_articles_per_user = (
    pl.read_csv("transactions.csv")
    .group_by(["customer_id", "article_id"]) # first arrow from the picture
    .agg(pl.count())                        # first arrow from the picture
    .group_by("customer_id")                                               # second arrow
    .agg(pl.col("article_id").sort_by("count", descending=True).head(10)) # second arrow
)

顺便提一下: 这个版本在我的机器上 运行大约一分钟。我没有为此创建 pandas 版本,而且我绝对害怕这样做并让它运行。如果你勇敢的话,可以试试看!

小改进

到目前为止,一些用户可能收到的推荐少于十个,甚至有些没有。一个简单的做法是将每个客户的推荐补充到十个文章。例如,

  • 使用随机文章,或者

  • 使用我们第一个基于受欢迎程度的推荐器中所有客户的最受欢迎文章。

我们可以这样实现第二个版本:

improved_recommendations = (
    most_popular_articles_per_user
    .with_columns([
        pl.col("article_id").fill_null([]).alias("personal_top_<=10"),
        pl.Series([most_popular_articles]).alias("global_top_10")
    ])
    .with_columns(
        pl.col("personal_top_<=10").list.concat(pl.col("global_top_10")).list.head(10).alias("padded_recommendations")
    )
    .select(["customer_id", "padded_recommendations"])
)

结论

基于受欢迎程度的推荐系统在推荐系统领域占据了重要地位,因为它们的简单性、易于实现以及作为初始方法和难以超越的基准的有效性。

在这篇文章中,我们学会了如何使用出色的 polars 库将基于流行度的简单推荐思想转化为代码。

主要缺点,特别是个性化的基于流行度的推荐系统,是推荐内容没有启发性。人们之前都见过推荐的所有内容,这意味着他们陷入了一个极端的回声室。

缓解这一问题的一个方法是使用其他方法,例如协同过滤或混合方法,如下所示:

## 无冷启动问题的高效推荐系统

当协作和基于内容的推荐系统融合时

towardsdatascience.com

希望你今天学到了新的、有趣的和值得的东西。感谢阅读!

如果你有任何问题,可以通过 LinkedIn联系我!

如果你想深入了解算法的世界,可以试试我新的出版物《算法全解》!我仍在寻找作者!

[## 《算法全解》

从直观解释到深入分析,算法通过实例、代码和精彩内容展现生命力……

medium.com](https://medium.com/all-about-algorithms?source=post_page-----cc7920ad3f68--------------------------------)

如何使用 Plotly 图形对象构建瀑布图

原文:towardsdatascience.com/how-to-build-waterfall-charts-with-plotly-graph-objects-a8354543c42e

Plotly Express 不支持瀑布图,但我们可以创建一个利用 Plotly 图形对象的辅助函数

Alan JonesTowards Data Science Alan Jones

·发表于 Towards Data Science ·阅读时间 8 分钟·2023 年 9 月 14 日

--

作者提供的图片

Plotly 提供了两种绘制图表的方法:图形对象和 Plotly Express。前者是一组低级函数,为创建图表提供最大的灵活性,而 Plotly Express 则提供了一组易于使用的方法,来实现最常用的图表。

Plotly Express 函数本质上是对 Plotly 图形对象的封装。

但 Plotly Express 中没有瀑布图方法,因此我们将介绍一个简单易用的瀑布图函数,它适用于最常见的用例,并且具有应对更复杂用法的灵活性。

瀑布图

瀑布图有点像被分成多列的条形图。它们通常用于显示一个值随时间的增加和减少。例如,考虑以下数据。

labels = ["Start balance", "Consulting", "Net revenue", 
          "Purchases", "Other expenses", "Profit before tax"]
data = [20, 80, 10, -40, -20, 0 ]

标签表示不同类别中已接收或已支出的现金金额,数据则是实际金额(以美元、百元、千元…等为单位)。

我们可以将这些数据有效地表示为瀑布图,其中第一列是起点,最后一列是终点,中间的列展示了导致最终结果的现金流。

作者提供的图片

通常,瀑布图通过颜色区分正负金额——在本例中,正数为绿色,负数为红色。最终一列使用第三种颜色,因为这代表的是最终结果,而不是正变或负变。

这是一个典型的用例,尽管更复杂的图表也是可能的。

在她的书《数据讲故事》[1]中,Cole Nussbaumer Knaflic (CNK) 将瀑布图视为她的 12 个必备图表之一,并给出了如何用它描述随时间变化的就业情况的示例。这是我对她图表的版本。

作者提供的图像

你可以看到,我们从 100 开始,这个数字因各种原因增加和减少,直到最终人数为 116。

CNK 偏好所有列使用单一颜色,严格来说,前面图表中的颜色确实是多余的,因为从标签和列的方向(向上或向下)可以清楚地看出变化是正向还是负向。

我理解 CNK 的观点,用她的风格呈现的图表可能比多彩的图表更好看,但我也看到,尤其是在更复杂的图表中,颜色可能会很有帮助。这是一个人数图表的变体,其中正负变化混合在一起,你可能会同意红绿版本稍微更清晰(如果,也许,不如多彩版本那么美观)。

作者提供的图像

作者提供的图像

我们将开发的解决方案默认为多彩版本,但如果你愿意,也可以更改颜色方案。

使用函数

我在一个单独的文件plotlyhelper.py中实现了瀑布图功能,以便可以作为库函数使用。导入库后,简单用法如下:

fig = ph.waterfall(labels,data, title)

我们可以在任何可以渲染 Plotly 图表的环境中使用这个函数,例如,在 Flask 应用程序、Jupyter Notebook 或 Streamlit 应用程序中。我们稍后会查看实现,但这是在 Streamlit 应用程序中使用它的方法。

import streamlit as st
import plotlyhelper as ph

st.set_page_config(layout='wide')

st.title("Waterfall graphs with Plotly")

col1, col2 = st.columns(2)

labels = ["Beginning HC", "Hires", "Transfers in", "Transfers out", 
          "Exits", "Ending HC"]
data = [100, 30, 8, -12, -10, 0 ]
title = "Headcount"

fig = ph.waterfall(labels,data, title)

col1.plotly_chart(fig)

fig = ph.waterfall(labels,data, title, color = 'blue')

col2.plotly_chart(fig)

这将给你两个“人数”图表版本,分为两列。

作者提供的图像

必须的参数是标签和数据,在上述示例中,我们还指定了一个标题(如果不设置,默认为空字符串)。使用这些参数,我们将得到默认的——多彩——版本的图表。

更改颜色方案

在第二个示例中,我们还设置了color参数,它定义了图表中所有条形的颜色。这可以是 Plotly 接受的任何颜色值。

我们还可以使用参数bicolor, dcolor, tcolor,ccolor来改变每个条形的颜色,这些参数分别定义了递增条形、递减条形、最终条形和条形之间连接线的颜色。它们的默认颜色分别为“绿色”、“红色”、“蓝色”和“深灰色”。

如果你设置了color参数,它将覆盖你可能设置的任何其他颜色。

这是如何设置单个颜色的示例。

fig = ph.waterfall(labels,data, title, icolor = 'orange', 
                                       dcolor = 'pink', 
                                       tcolor = 'yellow', 
                                       ccolor='red')
st.plotly_chart(fig)

这将绘制出这个。

作者提供的图像

也许我们可以举办最炫目颜色方案的比赛。

我们还可以探索其他几个参数。

注释

单个条形图上的标签默认为数据,但可以使用 annotation 参数进行自定义。这个数组可以是字符串或数字值,将显示在条形图上。例如,这里只有第一个和最后一个条形图被标记。

annotation = ["Start", "", "", "", "", "End"]

labels = ["Start balance", "Consulting", "Net revenue", "Purchases", "Other expenses", "Profit before tax"]
data = [20, 80, 10, -40, -20, 0 ] 
title = "Sales revenue"

fig = ph.waterfall(labels,data, title, annotation=annotation)

图片由作者提供

如果不需要标签,可以将 annotation 设置为空列表。

多个时间段

有时我们想用中间总计表示一系列时间段。我们可以通过指定每个条形图的类型来实现这一点。在默认图表中,除了最后一个条形图之外,所有条形图都是“相对”的,这意味着它们会相对于当前的累计总值显示为正值或负值。最后一个条形图是“总计”类型,这意味着它会显示当前的累计总值。

为了表示多个时间段,我们使用中间的“总计”条形图。看这个例子。

图片由作者提供

在这里,我们展示了两个季度的利润和亏损金额,其中“Q1”有一个中间总计,“Q2”有一个最终总计。

为了实现这一点,我们指定一个条形图类型的列表,并将其作为参数 measure 传递。

labels = ["Year beginning", "Profit1", "Loss1", "Q1", "Profit2", "Loss2", "Q2"]
data = [100, 50, -20, 0, 40, -10, 0 ]
annotation = []
measure = ['relative','relative', 'relative','total', 
           'relative','relative','total']
title = "Profit/Loss"

fig = ph.waterfall(labels,data, title, 
                   annotation = annotation, 
                   measure=measure)

st.plotly_chart(fig)

你应该注意到,映射到中间总计的数据数组元素被赋予了正确的值 130,但是,与其他条形图不同的是,这个值不会用于计算条形图的长度;条形图的长度是根据先前的值自动计算的,这个条形图的数据值仅作为注释使用。

瀑布图辅助函数

这些都引出了实际的实现。

虽然 Plotly Express 没有实现瀑布图,但 Graph Objects (GO) 包含这样的功能。使用起来有点繁琐——这也是我开发这个简单封装器的原因。如下面的代码所示,GO 瀑布图功能要求你设置许多参数。辅助函数通过设置默认值和/或计算值消除了这一需求——用户只需设置两个或三个基本参数。

import plotly.graph_objects as go

def waterfall(labels, data, title="", annotation=None, 
              icolor="Green", dcolor="Red", 
              tcolor="Blue", ccolor='Dark Grey', 
              color=None, measure=None):
    """
    Create a waterfall chart using Plotly.

    Parameters:
        labels (list): A list of labels for the data points.
        data (list): A list of numerical values representing the data points.
        title (str, optional): The title of the chart. Defaults to an empty string.
        annotation (list, optional): A list of annotations for each data point. Defaults to None.
        icolor (str, optional): Color for increasing values. Defaults to "Green".
        dcolor (str, optional): Color for decreasing values. Defaults to "Red".
        tcolor (str, optional): Color for the total value. Defaults to "Blue".
        ccolor (str, optional): Connector line color. Defaults to 'Dark Grey'.
        color (str, optional): Common color for all elements. Defaults to None.
        measure (list, optional): A list specifying whether each data point is 'relative' or 'total'. Defaults to None.

    Returns:
        plotly.graph_objs._figure.Figure: A Plotly Figure containing the waterfall chart.
    """
    # Set default measure values if not provided
    if measure is None:
        measure = ['relative'] * (len(labels) - 1)
        measure.append('total')

    # Set default annotation values if not provided
    if annotation is None:
        annotation = data[:-1]
        annotation.append(sum(data))

    # Create the waterfall chart figure
    fig = go.Figure(go.Waterfall(
        orientation="v",
        measure=measure,
        textposition="outside",
        text=annotation,
        y=data,
        x=labels,
        connector={"line": {"color": ccolor}},
        decreasing={"marker": {"color": dcolor}},
        increasing={"marker": {"color": icolor}},
        totals={"marker": {"color": tcolor}}
    )).update_layout(
        title=title
    )

    return fig

你可以将这段代码剪切并粘贴到自己的代码中,或者访问我的网站下载这个函数和一个实现了上述所有示例的 Streamlit 应用程序。作为额外奖励,下载的 Streamlit 代码还将包括一个在 Matplotlib 中实现的瀑布图版本。

我希望你觉得这个关于瀑布图及其在 Plotly Graph Objects 中实现的介绍有用。如果你在辅助库中包含这样的功能,Plotly Express 缺少瀑布图并不重要,它在多个项目中使用时会使工作变得简单得多。

感谢阅读,请访问我的网站,在这里你可以找到其他文章和代码的链接。你也可以订阅我的偶尔发布的通讯,我会在上面发布一些完整的文章以及我在 Medium 上发布的内容的链接。

注意事项

  1. 用数据讲故事:商业专业人士的数据可视化指南,Cole Nussbaumer Knaflic,Wiley,2015(附属链接)

  2. 英式拼写——我完全意识到我在这篇文章中混用了英式和美式拼写,但我保持了一致——老实说,我确实是这样。我在文本中使用‘colour’的英式拼写,因为……好吧,我是英国人。但是在我的代码中使用‘color’的美式拼写,因为我是程序员——我习惯于大多数编程语言使用美式英语。

如何使用大型语言模型与任何 PDF 和图像文件进行聊天 — 带代码

原文:towardsdatascience.com/how-to-chat-with-any-file-from-pdfs-to-images-using-large-language-models-with-code-4bcfd7e440bc

完整指南,教你如何构建一个可以回答任何文件问题的 AI 助手

Zoumana KeitaTowards Data Science Zoumana Keita

·发表于 Towards Data Science ·9 分钟阅读·2023 年 8 月 5 日

--

介绍

PDF 和图像文件中困藏着如此宝贵的信息。幸运的是,我们有这些强大的大脑,能够处理这些文件以找到特定信息,这实际上非常棒。

但我们中有多少人,内心深处并不希望拥有一个能回答关于给定文档的任何问题的工具呢?

这就是本文的全部目的。我将逐步解释如何构建一个能够与任何 PDF 和图像文件聊天的系统。

如果你更愿意观看视频,请查看下面的链接:

文章的视频格式

项目的总体工作流程

清楚了解系统的主要组件总是好的。那么,让我们开始吧。

整个聊天系统的端到端工作流程(作者提供的图像)

  • 首先,用户提交需要处理的文档,可以是 PDF 或图像格式。

  • 第二个模块用于检测文件格式,以便应用相关的内容提取功能。

  • 然后,文档的内容使用Data Splitter模块被拆分成多个块。

  • 这些块最终通过Chunk Transformer转换为嵌入,然后存储在向量存储中。

  • 在处理结束时,用户的查询被用来找到包含该查询答案的相关块,结果以 JSON 格式返回给用户。

1. 检测文档类型

对于每个输入文档,根据其类型应用特定的处理,无论是PDF还是image.

这可以通过结合使用detect_document_type的辅助函数和内置 Python 模块中的guess函数来实现。

def detect_document_type(document_path):

    guess_file = guess(document_path)
    file_type = ""
    image_types = ['jpg', 'jpeg', 'png', 'gif']

    if(guess_file.extension.lower() == "pdf"):
        file_type = "pdf"

    elif(guess_file.extension.lower() in image_types):
        file_type = "image"

    else:
        file_type = "unkown"

    return file_type

现在我们可以在两种类型的文档上测试这个功能:

  • transformer_paper.pdf 是 Transformers 研究论文 来自 Arxiv

  • zoumana_article_information.png 是包含有关我在 Medium 上所涵盖的主要主题信息的图像文档。

research_paper_path = "./data/transformer_paper.pdf"
article_information_path = "./data/zoumana_article_information.png"

print(f"Research Paper Type: {detect_document_type(research_paper_path)}")
print(f"Article Information Document Type: {detect_document_type(article_information_path)}")

输出:

文件类型成功检测(图片由作者提供)

detect_document_type 函数成功检测了这两种文件类型。

2. 基于文档类型提取内容

[langchain](https://python.langchain.com/docs/get_started/introduction.html) 库提供了不同的模块来提取特定类型文档的内容。

  • UnstructuredImageLoader 提取图像内容。

  • UnstructuredFileLoader 提取任何 pdf 和 Txt 文件的内容。

我们可以将这些模块与上述detect_document_type函数结合起来,实现文本提取逻辑。

这些模块可以用于在extract_file_content函数中实现端到端的文本提取逻辑。

让我们看看它们的实际效果! 🔥

from langchain.document_loaders.image import UnstructuredImageLoader
from langchain.document_loaders import UnstructuredFileLoader

def extract_file_content(file_path):

    file_type = detect_document_type(file_path)

    if(file_type == "pdf"):
        loader = UnstructuredFileLoader(file_path)

    elif(file_type == "image"):
        loader = UnstructuredImageLoader(file_path)

    documents = loader.load()
    documents_content = '\n'.join(doc.page_content for doc in documents)

    return documents_content

现在,让我们打印每个文件内容的前 400 个字符。

research_paper_content = extract_file_content(research_paper_path)
article_information_content = extract_file_content(article_information_path)

nb_characters = 400

print(f"First {nb_characters} Characters of the Paper: \n{research_paper_content[:nb_characters]} ...")
print("---"*5)
print(f"First {nb_characters} Characters of Article Information Document :\n {research_paper_content[:nb_characters]} ...")

输出:

以上每个文档的前 400 个字符如下:

  • 研究论文的内容以Provided proper attribution is provided 开始,以Jacod Uszkoreit* Google Research usz@google.com. 结束。

  • 图像文档的内容以This document provides a quick summary 开始,以Data Science section covers basic to advance concepts. 结束。

Transformers 论文和文章信息文档的前 400 个字符(图片由作者提供)

3. 聊天实现

输入文档被分成块,然后为每个块创建嵌入,之后实现问答逻辑。

a. 文档分块

这些块代表了较大文本的一小段。这一过程对于确保内容尽可能少的噪音,并保持语义相关性至关重要。

可以应用多种分块策略。例如,我们有 NLTKTextSplitterSpacyTextSplitterRecursiveCharacterTextSplitterCharacterTextSplitter 等。

这些策略各有优缺点。

本文的重点是 CharacterTextSplitter,它根据 \n\n 从输入文档中创建块,并通过字符数量(length_function)来衡量每个块的长度。

text_splitter = CharacterTextSplitter(        
    separator = "\n\n",
    chunk_size = 1000,
    chunk_overlap  = 200,
    length_function = len,
)

chunk_size 指定我们希望每个块最多包含 1000 个字符,而较小的值将导致更多的块,而较大的值将生成更少的块。

需要注意的是,chunk_size 的选择方式会影响整体结果。因此,一个好的方法是尝试不同的值,选择最适合自己用例的那个。

此外,chunk_overlap 表示我们希望连续块之间有最多 200 个重叠字符。

例如,假设我们有一个包含文本 Chat with your documents using LLMs 的文档,并想使用 Chunk Size = 10Chunk overlap = 5 来应用块化。

该过程在下面的图像中进行了说明:

文档块化示例(作者提供的图片)

我们可以看到,对于一个包含 35 个字符(包括空格)的输入文档,我们最终得到了 7 个块。

但是,我们为什么要使用这些重叠部分呢?

包括这些重叠部分,CharacterTextSplitter 确保在块之间保持底层上下文,这在处理长文档时特别有用。

类似于 chunk_sizechunk_overlap 没有固定值。需要测试不同的值以选择效果更好的值。

现在,让我们看看它们在我们的场景中的应用:

research_paper_chunks = text_splitter.split_text(research_paper_content)
article_information_chunks = text_splitter.split_text(article_information_content)

print(f"# Chunks in Research Paper: {len(research_paper_chunks)}")
print(f"# Chunks in Article Document: {len(article_information_chunks)}")

输出:

每个文档中的块数(作者提供的图片)

对于像研究论文这样的较大文档,我们有更多的块(51 个),而一页文章文档只有 2 个块。

b. 创建块的嵌入

我们可以使用 OpenAIEmbeddings 模块,该模块默认使用 text-embedding-ada-002 模型来创建块的嵌入。

可以通过更改以下参数,使用不同的模型(例如 gpt-3.5-turbo-0301)来代替 text-embedding-ada-002

  • model = “gpt-3.5-turbo-0301

  • deployment = "<DEPLOYMENT-NAME> ",这对应于在模型部署期间给出的名称。默认值也是 text-embedding-ada-002

为了简单起见,在本教程中我们将坚持使用默认参数值。但在此之前,我们需要获取 OpenAI 凭据,所有步骤在 以下文章 中提供。

from langchain.embeddings.openai import OpenAIEmbeddings
import os

os.environ["OPENAI_API_KEY"] = "<YOUR_KEY>"
embeddings = OpenAIEmbeddings()

c. 创建文档搜索

要回答给定的查询,我们需要创建一个向量存储,以找到与该查询最匹配的块。

这样的向量存储可以使用 FAISS 模块中的 from_texts 函数创建,该函数需要两个主要参数:text_splitterembeddings,这两者都已定义。

from langchain.vectorstores import FAISS

def get_doc_search(text_splitter):

    return FAISS.from_texts(text_splitter, embeddings)

通过在研究论文块上运行 get_doc_search,我们可以看到结果是 vectorstores。如果我们使用 article_information_chunks,结果也会相同。

doc_search_paper = get_doc_search(research_paper_chunks)
print(doc_search_paper)

输出:

研究论文的向量存储(作者提供的图片)

d. 开始与你的文档聊天

恭喜你走到这一步! 🎉

chat_with_file 函数用于实现聊天的端到端逻辑,将所有上述函数与 similarity_search 函数结合使用。

最终函数需要两个参数:

  • 我们想要聊天的文件,以及

  • 用户提供的查询

from langchain.llms import OpenAI
from langchain.chains.question_answering import load_qa_chain
chain = load_qa_chain(OpenAI(), chain_type = "map_rerank",  
                      return_intermediate_steps=True)

def chat_with_file(file_path, query):

    file_content = extract_file_content(file_path)
    text_splitter = text_splitter.split_text(file_content)

    document_search = get_doc_search(text_splitter)
    documents = document_search.similarity_search(query)

    results = chain({
                        "input_documents":documents, 
                        "question": query
                    }, 
                    return_only_outputs=True)
    answers = results['intermediate_steps'][0]

    return answers

让我们退一步,以正确理解上述代码块中发生的事情。

  • load_qa_chain 提供了一个接口,用于在一组文档上执行问答。在这个特定的案例中,我们使用的是默认的 OpenAI GPT-3 大型语言模型。

  • chain_typemap_rerank 。通过这样做,load_qa_chain 函数根据链提供的置信度分数返回答案。还有其他可以使用的 chain_type,如 map_reducestuffrefine 等。每种都有其优缺点。

  • 通过设置 return_intermediate_steps=True,我们可以访问诸如上述置信度分数等元数据。

它的输出是一个包含两个键的字典:查询的 答案 和置信度 分数

我们终于可以开始与我们的文件聊天,从图像文档开始:

  • 与图像文档聊天

要与图像文档聊天,我们提供文档路径和我们希望模型回答的问题。

query = "What is the document about"

results = chat_with_file(article_information_path, query)

answer = results["answer"]
confidence_score = results["score"]

print(f"Answer: {answer}\n\nConfidence Score: {confidence_score}")

输出:

对图像文档的查询结果(图像来源:作者)

模型对其响应有 100% 的信心。通过查看下面的原始文档的第一段,我们可以看到模型的响应确实是正确的。

原始文章图像文档的前两段(图像来源:作者)

最有趣的部分之一是它提供了文档中主要主题的简要总结(统计、模型评估指标、SQL 查询等)。

  • 与 PDF 文件聊天

PDF 文件的处理过程类似于上述部分。

query = "Why is the self-attention approach used in this document?"

results = chat_with_file(research_paper_path, query)

answer = results["answer"]
confidence_score = results["score"]

print(f"Answer: {answer}\n\nConfidence Score: {confidence_score}")

输出:

我们再次从模型中获得了 100% 的置信度分数。问题的答案看起来非常正确!

对 PDF 文档的查询结果(图像来源:作者)

在这两种情况下,模型都能够在几秒钟内提供类似人类的响应。让一个人经历相同的过程将需要几分钟,甚至几小时,具体取决于文档的长度。

结论

恭喜!!!🎉

我希望这篇文章提供了足够的工具来帮助你提升知识水平。代码可在 我的 GitHub 上获取。

在我的下一篇文章中,我将解释如何将这个系统集成到一个漂亮的用户界面中。敬请期待!

如果你喜欢阅读我的故事并希望支持我的写作,考虑成为 Medium 会员。每月 $5,你将获得对成千上万的 Python 指南和数据科学文章的无限访问。

通过使用 我的链接,我将获得少量佣金,而你无需额外支付。

[## 通过我的推荐链接加入 Medium - Zoumana Keita]

作为 Medium 会员,你的会员费用的一部分会用于支持你阅读的作者,同时你可以完全访问每个故事……

zoumanakeita.medium.com

欢迎通过 TwitterYouTube 关注我,或者在 LinkedIn 上打个招呼。

在这里与我联系进行一对一讨论

离开之前,下面还有更多你可能感兴趣的优质资源!

使用 OpenAI API 进行文本嵌入介绍

如何从任何 PDF 和图像中提取文本以供大型语言模型使用

如何选择大学的 AI 项目/课程

原文:towardsdatascience.com/how-to-choose-an-ai-program-course-at-university-19bbb4588ed4

生活和职业决定的推理框架

Richmond AlakeTowards Data Science Richmond Alake

·发表于Towards Data Science ·阅读时长 13 分钟·2023 年 9 月 25 日

--

如果选择合适的学位课程或项目来学习人工智能(AI)是简单明了的,本文也就到此为止。但事实并非如此。

这意味着不仅仅是决定在大学学习人工智能,特别是当目标是在几年后成为一名 AI 专业人士时,事情会更复杂。

AI 相关课程中常见关键词的词云 — 作者提供的图片。

上述图片展示了英国各大学的硕士、博士、文凭和学士学位课程标题中的关键词和主题。我分析的每个课程在其相应的模块中还有更多的关键词。

通过学术路线成为 AI 专业人士并非完全直截了当。

假设你没有认真考虑你的大学课程选择,你可能会发现自己在一个以数据为中心的课程中,而你的兴趣在于 AI 的硬件方面,这意味着你本来可能更适合一个涉及机器人或机械的课程。

在这篇文章中,我揭示了一个实用的框架,提供了一种灵活的方法来选择大多数高等教育机构中适当的 AI 相关课程。这个框架可以用于你自己,也可以与年轻的 AI 爱好者分享。

什么、为什么、怎么做以及在哪里

你如何决定在大学选择哪个 AI 课程?

上述问题没有具体的答案。选择大学 AI 课程的实际方法是利用不同的推理方法,考虑你的愿望(什么和为什么)、能力(怎么做)和环境(在哪里)。

'什么'和'为什么'考虑了驱使你追求特定目标的内部和外部动机。更具体地说,'什么'过程确保你不仅仅是在追逐一个任意目标;相反,你了解你希望在人工智能领域的 30 到 50 年职业生涯中获得什么,并考虑到里程碑式的目标。

'如何'使你进入自我反思的状态,这需要对自己、自己的局限性和能力有清晰的了解。'哪里'考虑外部因素对你决策的影响。

让我们进入推理框架。

选择大学 AI 课程时的三步推理:

  1. 从后向前工作(归纳推理)

  2. 预先思考(演绎推理)

  3. 考虑你的能力和兴趣(实践推理)

这篇文章重点关注归纳推理。

从后向前工作:归纳推理

从后向前工作是一项活动,涉及你在已经取得期望结果的位置上开始投射自己的能力。

这项活动紧接着制定周密步骤和行动计划,以帮助你从一个目标达到下一个目标。

这就是我的做法。

想一个榜样,一个现在在 AI 领域中处于你想要的职位的人。

不,你不能选择埃隆·马斯克。

对于这个思维实验,我们以詹姆斯·曼伊卡,目前是谷歌(Alphabet)技术与社会高级副总裁为例。

詹姆斯将他高度技术化的学术背景转化为全球客户的影响力和价值,通过战略领导和决策。他最近被列入《时代》100 位最具影响力的人工智能(AI)人物

在一个倾向于技术理解的领域,早期对领导力素质的重视能让你在同龄人中脱颖而出。

你可以特别考虑詹姆斯作为一个榜样,基于他将技术专长扩展到指导企业、初创公司和国家在采纳 AI 技术时的能力。前美国总统巴拉克·奥巴马任命他为全球发展委员会副主席。詹姆斯还担任了诸如可汗学院、麦克阿瑟基金会、阿斯彭研究所等公司的董事会成员。

让我们从詹姆斯·曼伊卡所处的理想位置开始,思考哪些与 AI 相关的主题和学科使他走上了同样的成功道路?

花五分钟考虑上述问题,我得出了以下三个主要主题领域:人工智能、计算机科学(CS)和机器人技术。

人工智能和计算机科学是涵盖大量话题的宏观学科。在人工智能课程中,你将接触到深度学习、机器学习、自动化、数据科学等。计算机科学研究包括软件和网络架构、数据库和基础设施设计以及软件原理。作为 AI 和计算机科学的学习者,你会获得关于 AI 和技术相关的各种主题的高级信息。

这种学习方式对于个人来说是必需的,以便负责规划和开发技术战略,这些战略将被公司、组织和国家采用——一个能够在不断变化的 AI 领域中航行,并对各种话题提供见解的通才。

我还选择了机器人学作为重点课程领域,因为硬件考虑在整个 AI 行业中至关重要。从某种意义上说,AI 涉及硬件和软件之间的持续协同,因此,理解边缘设备运行 AI 解决方案的方式或半导体对技术的影响,对于长期在 AI 和技术领域中的领导者来说是一个优势。

让我们看看我规划的学术路径与詹姆斯的匹配程度。快速查看他的LinkedIn 个人资料将足以进行这个思想实验。

詹姆斯在津巴布韦大学获得的电气工程理学士学位,与考虑可能涉及 AI 的硬件组件的方法相吻合。

电气工程以及电子学是与 AI 话题密切相关的主题。我的想法是拥有一个对 AI 和计算机科学中所有领域和话题具有高层次概述的个人,是准确的。

詹姆斯拥有数学和计算机科学的硕士学位,以及人工智能和机器人技术的博士学位。他的学术生涯始于并以硬件的考虑结束,但詹姆斯的教育背景涵盖了 AI 的整体视角,包括该领域的软件硬件方面。

反思

在反思我的学术和职业道路时,我在职业初期优先考虑了软件。我有选择计算机科学或软件工程作为本科专业的选项,我选择了后者,因为软件在技术中的相关性非常重要,不可忽视对软件设计、实施和架构的深入理解。

## 学习机器学习硕士学位后我非常没有准备

探索我从全职工作转变为人工智能硕士生的过程。

## 学习机器学习硕士学位后我非常没有准备

我的硕士学位没有广泛涵盖人工智能,但明确专注于计算机视觉、深度学习和机器学习。我当时这样选择的理由是,相机变得越来越小巧和强大;机器学习模型在移动和边缘设备中也变得越来越普遍。因此,理解如何设计、开发和实施解决计算机视觉问题的机器学习模型将长期有益,并且也能让我在多个实际情况中成为解决问题的人。计算机视觉的一个显著特点是,许多手动任务非常适合计算机视觉解决方案。目前,我正在开发MiniPT,一个配备计算机视觉的移动应用程序,为你提供虚拟个人教练。

在撰写时,人工智能研究和关注的重点已经转向自然语言处理问题。随着像 BERT 和 GPT3 这样的大型语言模型的出现,对新学习者来说,在学术层面专注于 NLP 主题更具优势。

在硕士学习之外的前两年,我专注于在人工智能领域发展我的技术技能。我的主要职业是在伦敦的一家初创公司担任计算机视觉工程师。我还在 Medium 上撰写了许多文章,在 O'Reilly 在线学习平台上教授计算机视觉课程,并在帝国理工商学院每周讲授数据科学和统计学课程。

倒推不仅适用于学术场景。你可以将这种思维方式应用于其他领域,如金融、健康和健身。

拆解

本部分总结了迄今为止的内容,将其转化为可在任何时候执行的可操作步骤,以便你能对在高等教育机构(如大学)追求哪些人工智能相关主题形成深思熟虑的理解。

归纳推理方法选择人工智能课程——图像作者

定义你的终极目标

一切都关乎目的地

在生活中的许多事情中,通往目的地的旅程是你回顾并从中获得最多的部分。然而,理解目标、你期望的结果,会激励你首先开始这段旅程。将自己想象成你想成为的人工智能从业者

  1. 识别一个已经处于该位置的人;这就是你的榜样。LinkedIn 是执行这一步骤最合适的平台。

拆解他们的路径

  1. 分析并研究你选择的榜样的教育和职业决策,以达到期望的位置。

  2. 查找他们创建或参与的出版物、文章、视频和论文。

  3. 更重要的是,考虑你的榜样在任何教育机构所学的学科的相关性,以及这些学科如何可能有助于他们当前的角色,以及你所期望的角色。

识别主要和嵌入的学科

要了解达到你期望职位的教育路径,你不仅需要了解你的榜样所选择的整体学科,如计算机科学或软件工程。你还必须了解你选择的学术机构中这些学科所涵盖的模块。

记住,这些嵌入式主题塑造了你对整体主题的理解,并且在许多情况下,决定了你对人工智能实际应用的兴趣程度。根据我的经验,尽管我选择了机器人学这一整体主题,但我很快意识到机器人学中的嵌入式主题不适合我的技能或最终目标。

  1. 理解你榜样的学术和职业路径中的核心学科和主题。

  2. 探索并理解你可能考虑的机构中的人工智能相关课程的内容。

专业化与泛化

在人工智能领域,成为专家或通才各有其优势。早期选择成为专家或通才,可以让你了解你希望参与的与人工智能相关的行业。选择成为专家可能使你赚取大量的收入,但此类角色的竞争可能很激烈,甚至专家角色的数量可能较少。

  1. 了解在你选择的领域内,专家或通才的人工智能角色是什么样的。

重新审视你的选择

没有什么是不可更改的。

在进行这项活动时,你所做的选择是灵活的;即使你选择了你心仪的大学,转向你感兴趣的人工智能其他领域可能相对容易;有时,这需要与大学教职工进行一次谈话。

就我而言,我开始本科学位时选择了计算研究,这是计算领域非常通用的学科。在第一年后意识到软件在未来几十年中的重要作用后,我申请将课程更改为软件工程;这只是与系主任的 30 分钟讨论,转学的关键要求是我的编程技能评估以及确保我在第一年获得了及格分数。

  1. 经常重新审视你做出的学术选择,并根据人工智能和研究的最新发展调整你的方向。

  2. 早期理解人工智能的根基在于周期,某些人工智能主题会在几年或几十年内受到关注,之后被取代。生成式人工智能是当前的热门人工智能话题。

整合实践经验

应用或实践的人工智能场景使你接触到许多从业者、专业人士甚至公司在构建人工智能产品时遇到的实际问题。数据隐私、用户界面设计以及伦理和社会考虑是可以研究的主题。然而,早期接触这些实际问题会让你在学术道路上领先于竞争对手。

我最初在通用电气进行了为期一年的 IT 业务分析师实习。但我很快就与通用电气内部的软件项目和计划对齐。不久之后,我开始编写移动应用程序来优化库存记录。这导致我和通用电气的库存记录员一起在路上待了一个月,了解他们的日常流程,并将技术应用于某些需要创新和优化的流程领域。

在我本科毕业时,我在通用电气工作了 1.5 年,并了解了你必须克服的障碍,以确保你构建的移动应用程序被最终用户使用——这是我至今仍在使用的经验教训。

  1. 寻找实习、学徒、跟随以及在你偶像曾工作过的公司或类似公司的研究机会。

  2. 作为人工智能学生,你的角色是尽早学习并参与专业活动,比如写作、教学和工作经验。

选择项目/大学

学生可以选择的大学种类繁多,而对于那些寻求出国留学的人来说,选择的范围更广。

我不能告诉你应该去哪个具体的大学和选择哪个具体的人工智能课程/项目。我会为你提供一个框架和推理方法,帮助你选择最适合你职业发展路径的课程。既然你已经走到这一步,那么这个目标就正在实现。

但我可以在这一部分提供一些重要的考虑因素,你在选择课程时应该考虑这些因素。让我们从上到下的方法来提供一个选择人工智能课程和大学的框架。

我是否必须去顶级大学学习人工智能?

以我自己为例。

我在萨里大学攻读硕士学位,如前所述,我主要学习了计算机视觉和深度学习。我在大学里接受了优质的教育,并且有机会使用 GPU、与博士生和讲师接触。萨里大学是一所了不起的大学,在某些情况下也是顶级大学。但在人工智能研究的大学排名中,萨里大学在欧洲排名第 59 位和全球排名第 209 位

你不必去顶级大学学习人工智能才能获得一流的教育,并为你的职业 AI 生涯提供良好的起点。有些人甚至认为你根本不需要上大学,这主要是由于培训营、在线认证和免费的学习资源的普及。但那是另一个话题。

好吧,我不需要去顶级大学,那么在选择人工智能课程时,我应该注意什么呢?

  • 师资力量:没有什么比有一位多年未更新幻灯片的讲师更糟糕的了。相信我,我曾经遇到过。我总是说,“人工智能的进步速度与创新的速度相匹配。”目前,AI 监管与创新的步伐存在挑战。而更重要的是,AI 教育不应滞后太久。了解一下你所选课程中讲师的背景。参加一些试听课程,以了解你可能要面对的内容。问问自己:讲师是否了解现代 AI 的现状?他们在过去两年内是否发布过任何出版物?

  • 课程深度:如果你选择的是硕士项目,你必须确保你想作为专业追求的 AI 领域是具体的。假设你打算成为自然语言处理(NLP)工程师,那么必须确保你所选的项目涵盖了诸如马尔可夫链、传统语音识别技术以及现代方法如变压器等相关基础知识。如果你选择的是本科项目,确保前几年会让你接触到各种相关的 AI/ML/数据主题,并且在最后几年有专业方向的选择。

  • 资源可用性:近年来,GPU 已经成为一种商品;确保你的大学能够使用现场或云端 GPU 基础设施,可以让你进行实验、研究、模型推断和训练。参加你考虑的大学的开放日,并询问有关 GPU 基础设施和计算资源投资的情况,如果有的话,了解一下学院的计划。

  • 研究机会:有些情况下,AI 项目中的学生认为更长的学术生涯是有益的,并继续攻读几年的博士学位。

  • 费用:课程或项目的价格是否在你的财务能力范围内?查看大学可能提供的其他融资选项,如奖学金、付款计划等。

结论

归纳推理,在应用于 AI 课程选择并由你期望的 AI 角色结果引导时,使你能够提出战术、策略和步骤,以实现你期望的结果。

这并不意味着保证成功,但通过确保更周到地选择 AI 课程来增加成功的机会,考虑到你的志向、环境、能力和其他几个因素。

这总比仅仅选择第一个呈现给你的 AI 课程要好。

正如之前提到的,没有任何路径是固定不变的。这意味着归纳推理及其产物(通向理想 AI 角色的学术路线图)需要适应。AI 领域和行业在不断发展。因此,持续的自我反思以及整合不断变化的技术动态,使得你的蓝图能够适应变化。

在未来的文章中,我将探讨选择高等教育机构的 AI 课程时可以采用的另外两种推理框架。

在那之前,请保持好奇。

LinkedIn | 通讯 | YouTube | 支持我的写作

如何选择最佳的分类问题评价指标

原文:towardsdatascience.com/how-to-choose-the-best-evaluation-metric-for-classification-problems-638e845da334

一份涵盖最常用的监督分类评价指标及其在不同场景下效用的综合指南

Thomas A DorferTowards Data Science Thomas A Dorfer

·发布于 Towards Data Science ·9 分钟阅读·2023 年 4 月 17 日

--

图片由作者提供。

为了正确评估分类模型,重要的是要仔细考虑哪个评价指标最为合适。

这篇文章将涵盖分类任务中最常用的评价指标,包括相关示例案例,并为你提供选择这些指标所需的信息。

分类

分类问题的特点是根据给定观察的特征预测其类别或类别。选择最合适的评价指标将取决于用户希望优化的模型性能方面。

想象一个预测模型旨在诊断某种特定疾病。如果这个模型未能检测到疾病,可能会导致严重后果,如治疗延误和患者受害。另一方面,如果模型错误地诊断出健康患者,那也会导致不必要的检查和治疗,产生高昂的费用。

最终,选择减少哪种错误将取决于具体的使用案例及其相关成本。让我们来深入了解一些最常用的指标,以便对此有更清晰的了解。

评价指标

准确率

当数据集中的类别是平衡的——即每个类别中样本的数量大致相等时——准确率可以作为评估模型性能的简单直观的指标。

简单来说,准确率衡量模型所做的正确预测的比例。

为了说明这一点,我们来看以下表格,展示了实际类和预测类:

绿色阴影的列表示正确预测。表格由作者提供。

在这个例子中,我们总共有 10 个样本,其中 6 个被正确预测(绿色阴影)。

因此,我们的准确度可以计算如下:

为了准备好接下来要讨论的指标,值得注意的是正确预测真阳性真负例的总和。

真阳性 (TP) 发生在模型正确预测正类时。

真负例 (TN) 发生在模型正确预测负类时。

在我们的例子中,真阳性是一个结果,其中实际类和预测类都为 1。

绿色阴影的列表示真阳性。表格由作者提供。

同样,当实际类和预测类都为 0 时,发生真负例

绿色阴影的列表示真负例。表格由作者提供。

因此,你可能会看到准确度的公式有时写作如下:

示例: 人脸检测。为了检测图像中是否有脸,准确度可以作为一个合适的指标,因为假阳性(将非人脸识别为人脸)和假阴性(未能识别出人脸)的成本大致相等。注意: 数据集中的类标签分布应该是平衡的,才能使准确度成为一个合适的衡量标准。

精确度

精确度指标适用于衡量正确的正预测比例。

换句话说,精确度提供了模型正确识别真阳性样本的能力的衡量标准。

因此,当目标是最小化假阳性时,它通常被使用,例如在信用卡欺诈检测或疾病诊断等领域。

假阳性 (FP) 发生在模型错误预测正类时,表示某个条件存在,而实际情况并非如此。

在我们的例子中,假阳性是一个结果,其中预测的类应为 0,但实际上是 1。

红色阴影的列表示假阳性。表格由作者提供。

由于精确度衡量的是实际为正类的正预测比例,因此其计算方法如下:

示例: 异常检测。例如,在欺诈检测中,精确率可能是一个合适的评估指标,特别是当假阳性成本较高时。将非欺诈活动识别为欺诈活动不仅会导致额外的调查费用,还可能导致客户不满和流失率增加。

召回率

当预测任务的目标是最小化假阴性时,召回率作为合适的评估指标。

召回率衡量模型正确识别的真正例比例。

在假阴性比假阳性更昂贵的情况下,它尤其有用。

假阴性(FN)发生在模型错误预测负类,表明给定条件不存在,而实际情况是存在。

在我们的示例中,假阴性是指实际应该为 1 但预测为 0 的结果。

红色列表示假阴性。表格由作者提供。

召回率计算方法如下:

示例: 疾病诊断。例如,在 COVID-19 检测中,召回率是一个不错的选择,当目标是检测尽可能多的阳性病例时。在这种情况下,容忍更多的假阳性,因为优先目标是最小化假阴性,以防止疾病传播。可以说,漏掉阳性病例的成本远高于将阴性病例误分类为阳性。

F1 分数

在假阳性和假阴性都需要考虑的重要情况下,例如垃圾邮件检测,F1 分数是一个有用的指标。

F1 分数是精确率和召回率的调和均值,通过考虑假阳性和假阴性来提供模型性能的平衡度量。

计算方法如下:

示例: 文档分类。例如,在垃圾邮件检测中,F1 分数是一个合适的评估指标,因为目标是在精确率和召回率之间取得平衡。垃圾邮件分类器应尽可能准确地分类垃圾邮件(召回率),同时避免将合法邮件错误分类为垃圾邮件(精确率)。

ROC 曲线下面积(AUC)

接收者操作特征曲线,或ROC 曲线,是一个图表,展示了二分类器在所有分类阈值下的性能。

ROC 曲线下面积,或AUC,衡量二分类器在不同阈值下区分正负类的能力。

当假阳性和假阴性的成本不同时时,这是一项特别有用的度量。这是因为它考虑了不同阈值下真正例率(敏感度)和假阳性率(1-特异性)之间的权衡。通过调整阈值,我们可以获得一个优先考虑敏感度或特异性的分类器,具体取决于特定问题的假阳性和假阴性的成本。

真正例率 (TPR),或敏感度,衡量模型正确识别的实际正例的比例。它与召回率完全相同。

计算方法如下:

假阳性率 (FPR),或1-特异性,衡量模型错误地将实际负例分类为正例的比例。

计算方法如下:

通过将分类阈值从 0 变化到 1,并计算每个这些阈值的 TPR 和 FPR,可以生成 ROC 曲线及其对应的 AUC 值。对角线表示随机分类器的表现——即一个对每个样本的类别标签进行随机猜测的分类器。

图像由作者提供。

ROC 曲线越接近左上角,分类器的表现越好。对应的 AUC 为 1 表示完美分类,而 AUC 为 0.5 表示随机分类性能。

示例: 排名问题。当任务是按样本属于某个类别的可能性对其进行排序时,AUC 是一个合适的度量,因为它反映了模型正确排序样本的能力,而不仅仅是分类。例如,它可以用于在线广告,因为它评估了模型按用户点击广告的可能性正确排序的能力,而不仅仅是预测二分类点击/未点击结果。

Log Loss

对数损失,也称为 log loss 或交叉熵损失,是分类问题中评估重要性概率估计的有用指标。

Log Loss 衡量预测的类别概率与实际类别标签之间的差异。

当目标是惩罚模型对错误分类过于自信时,这是一项特别有用的度量。该度量也被用作逻辑回归和神经网络训练中的损失函数。

对于单个样本,其中y表示真实标签,p表示概率估计,log loss 计算方法如下:

当真实标签为 1 时,log loss 作为预测概率的函数如下:

图像由作者提供。

可以明显看出,日志损失随着分类器对正确标签为 1 的确定性增加而减少。

日志损失也可以推广到多类别分类问题。对于单个样本,其中 k 表示类别标签,K 代表总类别数,可以按如下方式计算:

在二分类和多分类问题中,日志损失是一个有用的指标,它衡量了预测概率与真实类别标签的匹配程度。

示例: 信用风险评估。例如,日志损失可以用来评估预测借款人违约可能性的信用风险模型的性能。假阴性的成本(将可靠的借款人预测为不可靠)可能远高于假阳性的成本(将不可靠的借款人预测为可靠)。因此,在这种情况下,最小化日志损失有助于降低贷款的财务风险。

结论

为了准确评估分类器的性能并根据其预测做出明智决策,选择适当的评估指标至关重要。在大多数情况下,这一选择将高度依赖于具体问题。需要考虑的重要因素包括数据集中类别的平衡、是否更重要的是最小化假阳性、假阴性或两者,以及排名和概率估计的重要性。

喜欢这篇文章吗?

让我们保持联系!你可以在TwitterLinkedInSubstack找到我。

如果你愿意支持我的写作,你可以通过Medium Membership来实现,这将使你能够访问我所有的故事以及 Medium 上其他成千上万位作者的作品。

[## 使用我的推荐链接加入 Medium - Thomas A Dorfer

阅读 Thomas A Dorfer(以及 Medium 上其他成千上万位作者)的每一篇故事。你的会员费用直接支持……

medium.com

如何选择最佳的回归问题评估指标

原文:towardsdatascience.com/how-to-choose-the-best-evaluation-metric-for-regression-problems-b9f2e60e25ef

一份全面的指南,涵盖回归中最常用的评估指标及其在不同场景中的实用性。

Thomas A DorferTowards Data Science Thomas A Dorfer

·发表于 Towards Data Science ·阅读时间 8 分钟·2023 年 4 月 24 日

--

作者提供的图片。

在构建回归模型之前,值得花点时间仔细考虑如何评估它。这个决策中会涉及各种因素,包括是否应该更惩罚大错误而非小错误,或指标需要对利益相关者多么易懂和直观。

本文将涵盖回归问题中最常用的评估指标。对于每个指标,我们还将通过一个示例用例,这将为你提供选择它们所需的信息。

回归

回归问题是一个监督式机器学习问题,其特征是根据一个或多个输入变量预测一个连续的数值输出变量。

想象一个回归模型,旨在根据各种特征(如卧室和浴室的数量、面积、位置等)预测房价。由于我们拥有各种评估指标,我们选择与目标最一致的指标至关重要。

例如,如果我们选择一个显著放大——并因此惩罚——大错误的指标,这可能意味着即使是一个大的错误也会大幅提高我们模型的整体误差,使其看起来表现不佳。然而,一个大的错误可能没有很多小错误那么重要。因此,如果目标是准确预测房价,而不是最小化整体误差,选择一个对所有错误权重相等的指标可能更为合适。

现在让我们探讨一些常用的评估指标,并讨论它们在不同场景中的实用性。

评估指标

平均绝对误差(MAE)

平均绝对误差,或称 MAE,是回归问题中常用的指标,因为误差的单位与目标变量的单位一致。例如,如果目标变量以美元(\(USD)*为单位,则误差也以*美元(\)USD)为单位。

平均绝对误差测量的是预测值与实际值之间的平均绝对差异。

计算公式如下:

这里,y表示实际值,ŷ表示预测值,n对应数据中的观测数量。

当我们的目标是以一种对所有误差赋予相等权重的方式评估回归模型的性能时,这是一种特别有用的指标,无论误差的大小如何。

为了说明这一点,我们来看下面的表格,该表格显示了实际值和预测值以及对应的 MAE。请注意,绝对误差始终为正,无论预测值高于还是低于实际值。此外,为了突出绝对误差的线性增长,我将实际值固定在 200,并线性增加预测的误差。

作者提供的表格。

根据这些数据,我们可以使用上述公式计算 MAE,结果为$40。

MAE 是一个非常易于解释的指标,有助于理解预测值与实际值的偏差程度。此外,它对离群值更具鲁棒性,因为它不会通过平方等方式放大大误差。这在大误差不比小误差更为重要的情况下尤为有用。

示例: 如上所述,MAE 可以作为预测房价的直观指标。通过测量实际值和预测值之间的平均绝对差异,它提供了一个更具关联性的指标,使我们能够理解预测的平均偏差程度。其他示例包括预测学生考试成绩、产品需求预测或预测患者住院时长。

均方误差(MSE)

均方误差,或称 MSE,通常在目标是比重较小的误差更多地惩罚较大误差时使用。

均方误差测量的是预测值与实际值之间的平均平方差异。

计算公式如下:

当目标是强调较大误差时,MAE 较为常用,因为 MSE 会对实际值和预测值之间的差异进行平方,从而放大误差。为了进一步说明这一点,请查看下面实际值和预测值,并观察随着预测偏差的增加,平方误差的加速增长。

作者提供的表格。

这里的 MSE 将是 2400 $平方 — 一个相当不直观的单位,这常常会导致利益相关者的困惑。

与绝对误差相比,绝对误差随偏差的增加线性增长,而平方误差非线性地且更快速地增加,从而对较大误差赋予更高的权重。

作者提供的图像。

示例: MSE 可应用于股票价格预测任务,目标是最小化整体预测误差。在这种情况下,可解释性可能不如准确性重要。此外,它对大误差的惩罚尤为关键,因为这些误差往往会导致重大财务损失。

均方根误差(RMSE)

均方根误差(RMSE),像 MSE 一样,是当目标是对大误差进行比小误差更严重惩罚时的一个流行度量。此外,它是一个更直观的度量,因为它的单位与目标变量相同。换句话说,如果目标变量是 \(USD*,则 RMSE 也是 *\)USD

均方根误差 衡量预测值与实际值之间平方差的平均值的平方根。更简单地说,它是均方误差(MSE)的平方根。

计算方法如下:

根据前一部分,我们知道样本数据的 MSE 是 2400 $平方。因此,RMSE 只是其平方根,即$48.99。

示例: RMSE 通常应用于天气预测,其中目标变量通常是某种温度或降水量的测量。在这种情况下,对大误差赋予更多权重是有益的,因为这强调了准确预测极端天气事件的重要性,这不仅会影响我们是否带伞的决策,还会影响基础设施、交通和农业。此外,得到与目标变量相同单位的误差度量有助于更直观地理解模型的准确性。

R 平方(R2)

R 平方度量,或 R2,提供了回归模型如何拟合数据的指示。

R 平方,也称为 决定系数,表示由预测变量解释的目标变量方差的比例。

计算方法如下:

在这里,SSR 代表平方残差和,SST 代表总平方和,ȳ 代表样本的均值。

该度量的范围从 0 到 1,其中 0 表示模型没有解释目标(因变量)的方差,而 1 表示模型解释了所有的方差。因此,我们可以得出结论,R 平方度量越高,模型对数据的拟合越好。

根据上面的样本数据,我们的 R 平方分数实际上会是 NaN 值,因为我们的 实际 值是常数,导致 SST 为 0。

我还应该指出,在实际操作中,R 平方值可能为负。这发生在模型的表现比简单的水平线(即目标变量的均值)更差时。换句话说,这意味着模型的预测甚至比仅仅使用目标变量的均值作为所有观察值的预测还要糟糕。

示例: R 平方常用于营销活动中的评估指标,公司可以通过分析广告支出与销售收入增加之间的关系来衡量其效果。高 R 平方值表明广告支出与销售收入之间存在强关系,意味着营销活动在推动销售方面非常有效。

均值绝对百分比误差 (MAPE)

均值绝对百分比误差(MAPE)通常用于衡量预测模型的准确性。

均值绝对百分比误差 衡量预测值与实际值之间的平均绝对百分比差异。

计算方法如下:

MAPE 的输出是非负的,0 是最佳值,表示模型的预测完全准确,没有误差。

根据上面的样本数据,MAPE 会产生 0.2 的值,表明平均而言,预测偏差为 20%。

示例: MAPE 指标常应用于金融领域,在该领域,预测百分比变化被认为比预测绝对值更为重要。例如,预测股票价格或汇率涉及预测百分比变化而非绝对值,此时 MAPE 可以作为合适的评估指标。

结论

通常建议使用多种指标来评估回归模型,因为每种指标都有其自身的优点和缺点。这种方法可以让我们对模型在准确性、稳健性和一致性等不同方面的表现有一个更全面的了解。

如果误差度量需要与目标变量具有相同的单位,可能会更倾向于使用 MAE 和 RMSE 分数。然而,与 MAE 相比,RMSE 和 MSE 对离群值更为敏感。R 平方指标对于评估模型与基础数据的整体拟合度很有用,但它不提供关于单个预测的信息。最后,MAPE 是一种易于解释的指标,因此通常用于预测问题,因为它提供了预测偏差的百分比指示。

选择用于特定回归问题的评价指标最终将取决于许多因素,包括当前具体的问题,是否需要对较大误差进行比对小误差更多的惩罚,或者指标的可解释性,以便在需要向利益相关者或管理者清晰解释时。

喜欢这篇文章吗?

让我们联系吧!你可以在 TwitterLinkedInSubstack 上找到我。

如果你喜欢支持我的写作,可以通过 Medium 会员 来实现,这将为你提供访问我所有故事的权限以及 Medium 上其他成千上万位作者的故事。

## 通过我的推荐链接加入 Medium - 托马斯·A·多费尔

阅读托马斯·A·多费尔(Thomas A Dorfer)的每一篇故事(以及 Medium 上其他成千上万的作者的故事)。你的会员费将直接支持…

medium.com

如何为你的项目选择正确的数据可视化策略

原文:towardsdatascience.com/how-to-choose-the-right-data-visualization-strategy-for-your-project-e6a2cd532337?source=collection_archive---------8-----------------------#2023-09-21

TDS 编辑Towards Data Science TDS 编辑

·

关注 发表在 Towards Data Science · 以 新闻通讯 形式发送 · 阅读时间 3 分钟 · 2023 年 9 月 21 日

--

新工具和包层出不穷,但数据可视化的基本语法对趋势仍然极其稳固:归根结底,我们仍然需要以有效的方式将线条、颜色和文本结合起来,以讲述我们的数据故事。

这并不意味着,遗憾的是,找到可视化数据洞察的正确方法总是简单或直接的。过于简化,我们的图表可能显得无聊,甚至过时。添加太多花哨的元素,则可能会使我们的读者、客户和利益相关者感到不知所措或分心。

当没有一刀切的解决方案,而每个项目都需要精心量身定制的方法时,数据专业人士能做的最好的事情就是不断扩展他们的可视化工具包,并通过试验和错误学习在不同情境下最有效的方式。我们本周推荐的文章将帮助你在这段旅程中:它们提供了具体的想法,并强调了将视觉效果与所要传达的核心信息匹配的重要性。

  • 超越条形图:使用桑基图、圆形打包和网络图的数据当然,一个干净的条形图没有任何问题,但有时我们讲述的故事过于复杂且多层次,无法 neatly 放入其中。Maham Haroon 提供了详细的指南,介绍了三种优雅的替代方案,供你在陷入困境时考虑:桑基图、圆形打包和网络图。

  • 使用 Python 和 Sklearn 创建动画以展示四种基于质心的聚类算法以引人入胜的方式呈现聚类过程的结果可能是一个挑战。Boriharn K 提出了一个巧妙的想法,当一系列图表无法解决问题时:为你的工作制作动画。正如 Boriharn 指出的那样,它“可以有效展示每个算法的工作原理并监控过程中的变化。”

  • 使用 Python 制作美观(且实用)的意大利面图也许这和食物主题的可视化有关(比如饼图?),但意大利面图往往成为偶尔被嘲讽的对象。Lee Vaughan 展示了它们如何有效使用,并借助气候变化数据的及时示例解释了如何创建它们。

图片由 Matt Briney 提供,来源于 Unsplash

从魔方到大型语言模型,我们也发布了一些精彩的其他主题文章——以下是我们最近的一些亮点:

  • Parul Pandey 强有力地论证了文化在组织采纳负责任的 AI 实践中的重要性。

  • 如果你喜欢历史和数学(以及/或数学的历史),你绝对不应该错过Sachin Date的对马尔可夫和 Bienaymé–Chebyshev 不等式的深度剖析。

  • 机器学习项目路线图应该是什么样的?你为什么需要一个?Heather Couture 的近期概述对这些问题提供了清晰且可操作的答案。

  • 扩展你的深度学习工具包,并在模型训练过程中探索归一化作为一种高效的优化技术——Thao Vu 的指南是一个很好的起点。

  • 总有新的算法等着你去学习!Kay Jan Wong 的解读文章覆盖了 Reingold-Tilford 算法,并包括其内部工作原理的详细讲解。

  • 在他的 TDS 首篇文章中,Eduardo Testé 关注于在规划问题背景下的概率,其中状态空间巨大且只有一个解决方案:魔方。

  • 从检索增强生成(RAG)到参数高效微调(PEFT),Maarten Grootendorst 提出了几种提高大语言模型性能的有用方法。

感谢你支持我们作者的工作!如果你喜欢在 TDS 上阅读的文章,考虑成为 Medium 会员——这将解锁我们的整个档案(以及 Medium 上的所有其他帖子)。

如何分块文本数据 — 一项比较分析

原文:towardsdatascience.com/how-to-chunk-text-data-a-comparative-analysis-3858c4a0997a?source=collection_archive---------0-----------------------#2023-07-20

探索文本分块的不同方法。

Solano TodeschiniTowards Data Science Solano Todeschini

·

关注 发表在 Towards Data Science ·17 分钟阅读·2023 年 7 月 20 日

--

图片由作者整理。菠萝图片来自 Canva。

介绍

自然语言处理(NLP)中的“文本分块”过程涉及将非结构化文本数据转换为有意义的单位。这一看似简单的任务掩盖了实现这一目标的各种方法的复杂性,每种方法都有其优缺点。

从高层次来看,这些方法通常分为两类。第一类是基于规则的方法,依赖于使用明确的分隔符,如标点符号或空格字符,或应用复杂的系统如正则表达式,将文本分成块。第二类是语义聚类方法,利用文本中固有的意义来指导分块过程。这些方法可能利用机器学习算法来辨别上下文,并推断文本中的自然分界。

在本文中,我们将探讨并比较这两种不同的文本分块方法。我们将用 NLTK、Spacy 和 Langchain 来表示基于规则的方法,并将其与两种不同的语义聚类技术进行对比:KMeans 和一种自定义的邻近句子聚类技术。

目标是让从业者清楚理解每种方法的优缺点和理想的使用场景,以便在他们的 NLP 项目中做出更好的决策。

在巴西俚语中,“abacaxi”翻译为“菠萝”,意指“某事未能产生良好结果、混乱的局面或毫无价值的事物。”

文本分块的使用案例

文本分块可以被多种不同的应用程序使用:

  1. 文本摘要:通过将大段文本拆分为可管理的块,我们可以对每个部分进行单独总结,从而得到更准确的总体摘要。

  2. 情感分析:分析较短且连贯的文本块通常能比分析整个文档更精确地得出结果。

  3. 信息提取:分块有助于定位文本中的特定实体或短语,增强信息检索的过程。

  4. 文本分类:将文本分解成块允许分类器专注于较小且有上下文意义的单元,而不是整个文档,这可以提高性能。

  5. 机器翻译:翻译系统通常在文本块上操作,而不是单个单词或整个文档。分块有助于保持翻译文本的一致性。

理解这些使用场景可以帮助选择最适合您特定项目的分块技术。

语义分块的不同方法比较

在本文的这一部分,我们将比较流行的非结构化文本语义分块方法:NLTK 句子分割器、Langchain 文本分割器、KMeans 聚类以及基于相似性的邻近句子聚类。

在下面的例子中,我们将使用从 PDF 中提取的文本评估这一技术,将其处理为句子及其聚类。

我们使用的数据是从巴西的维基百科页面导出的 PDF。

为了从 PDF 中提取文本并用 NLTK 将其分割成句子,我们使用以下函数:

from PyPDF2 import PdfReader
import nltk
nltk.download('punkt')

# Extracting Text from PDF
def extract_text_from_pdf(file_path):
    with open(file_path, 'rb') as file:
        pdf = PdfReader(file)
        text = " ".join(page.extract_text() for page in pdf.pages)
    return text

# Extract text from the PDF and split it into sentences
text = extract_text_from_pdf(file_path)

如此,我们得到了一个长度为 210964 个字符的字符串text

这是维基文本的一个样本:

sample = text[1015:3037]
print(sample)

"""
=======
Output:
=======

Brazil is the world's fifth-largest country by area and the seventh most popul ous. Its capital
is Brasília, and its most popul ous city is São Paulo. The federation is composed of the union of the 26
states and the Federal District. It is the only country in the Americas to have Portugue se as an official
langua ge.[11][12] It is one of the most multicultural and ethnically diverse nations, due to over a century of
mass immigration from around t he world,[13] and the most popul ous Roman Catholic-majority country.
Bounde d by the Atlantic Ocean on the east, Brazil has a coastline of 7,491 kilometers (4,655 mi).[14] It
borders all other countries and territories in South America except Ecuador and Chile and covers roughl y
half of the continent's land area.[15] Its Amazon basin includes a vast tropical forest, home to diverse
wildlife, a variety of ecological systems, and extensive natural resources spanning numerous protected
habitats.[14] This unique environmental heritage positions Brazil at number one of 17 megadiverse
countries, and is the subject of significant global interest, as environmental degradation through processes
like deforestation has direct impacts on gl obal issues like climate change and biodiversity loss.
The territory which would become know n as Brazil was inhabited by numerous tribal nations prior to the
landing in 1500 of explorer Pedro Álvares Cabral, who claimed the discovered land for the Portugue se
Empire. Brazil remained a Portugue se colony until 1808 when the capital of the empire was transferred
from Lisbon to Rio de Janeiro. In 1815, the colony was elevated to the rank of kingdom  upon the
formation of the United Kingdom  of Portugal, Brazil and the Algarves. Independence was achieved in
1822 with the creation of the Empire of Brazil, a unitary state gove rned unde r a constitutional monarchy
and a parliamentary system. The ratification of the first constitution in 1824  led to the formation of a
bicameral legislature, now called the National Congress.
"""

NLTK 句子分割器

自然语言工具包 (NLTK) 提供了一个将文本划分为句子的有用功能。这个句子分词器将给定的文本块划分为其组成句子,然后可以用于进一步处理。

实现

这是使用 NLTK 句子分词器的一个示例:

import nltk
nltk.download('punkt')

# Splitting Text into Sentences
def split_text_into_sentences(text):
    sentences = nltk.sent_tokenize(text)
    return sentences

sentences = split_text_into_sentences(text)

这会返回从输入文本中提取的 2670 个 句子,每个句子的平均字符数为 78。

评估 NLTK 句子分词器

虽然 NLTK 句子分词器是一种将大量文本划分为单独句子的直接有效方法,但它确实存在某些局限性:

  1. 语言依赖性:NLTK 句子分词器在很大程度上依赖于文本的语言。它在英语中表现良好,但对于其他语言,可能需要额外配置才能提供准确结果。

  2. 缩写和标点:分词器有时会误解句子末尾的缩写或其他标点符号。这可能导致句子片段被当作独立的句子处理。

  3. 语义理解的缺乏:像大多数分词器一样,NLTK 句子分词器不考虑句子之间的语义关系。因此,跨越多个句子的上下文可能在分词过程中丢失。

Spacy 句子分割器

Spacy 另一个强大的 NLP 库,提供了一种依赖语言规则的句子分词功能。这与 NLTK 的方法类似。

实现

实现 Spacy 的句子分割器相当简单。以下是在 Python 中的实现方法:

import spacy

nlp = spacy.load('en_core_web_sm')
doc = nlp(text)
sentences = list(doc.sents)

这会返回从输入文本中提取的 2336 个 句子,每个句子的平均字符数为 89。

评估 Spacy 句子分割器

与 Langchain 字符文本分割器相比,Spacy 的句子分割器倾向于创建较小的块,因为它严格遵守句子边界。当分析需要较小的文本单元时,这可能是一个优势。

然而,与 NLTK 一样,Spacy 的性能取决于输入文本的质量。对于标点或结构较差的文本,识别的句子边界可能并不总是准确。

现在,我们将查看 Langchain 如何提供文本数据分块的框架,并进一步与 NLTK 和 Spacy 进行比较。

Langchain 字符文本分割器

Langchain 字符文本分割器通过在特定字符处递归地划分文本来工作。它对通用文本特别有用。

该分割器由一系列字符定义。它尝试根据这些字符划分文本,直到生成的块符合所需的大小标准。默认列表是[“\n\n”, “\n”, “ ”, “”],旨在尽可能保持段落、句子和词汇的连贯,以维持语义一致性。

实现

考虑以下示例,我们使用这种方法分割了从 PDF 中提取的示例文本。

# Initialize the text splitter with custom parameters
custom_text_splitter = RecursiveCharacterTextSplitter(
    # Set custom chunk size
    chunk_size = 100,
    chunk_overlap  = 20,
    # Use length of the text as the size measure
    length_function = len,

)

# Create the chunks
texts = custom_text_splitter.create_documents([sample])

# Print the first two chunks
print(f'### Chunk 1: \n\n{texts[0].page_content}\n\n=====\n')
print(f'### Chunk 2: \n\n{texts[1].page_content}\n\n=====')

"""
=======
Output:
=======

### Chunk 1: 

Brazil is the world's fifth-largest country by area and the seventh most popul ous. Its capital

=====

### Chunk 2: 

is Brasília, and its most popul ous city is São Paulo. The federation is composed of the union of

=====

"""

最后,我们得到 3205 个文本块,由 texts 列表表示。每个块的平均长度为 65.8 个字符——比 NLTK 的平均长度(79 个字符)稍少。

更改参数和使用 '\n' 分隔符:

对于 Langchain Splitter 的更自定义方法,我们可以根据需要更改 chunk_sizechunk_overlap 参数。此外,我们还可以仅指定一个字符(或字符集)作为分隔操作,例如 \n。这将指导分隔器仅在新行字符处将文本拆分成块。

让我们考虑一个例子,其中我们将 chunk_size 设置为 300,chunk_overlap 设置为 30,并且只使用 \n 作为分隔符。

# Initialize the text splitter with custom parameters
custom_text_splitter = RecursiveCharacterTextSplitter(
    # Set custom chunk size
    chunk_size = 300,
    chunk_overlap  = 30,
    # Use length of the text as the size measure
    length_function = len,
    # Use only "\n\n" as the separator
    separators = ['\n']
)

# Create the chunks
custom_texts = custom_text_splitter.create_documents([sample])

# Print the first two chunks
print(f'### Chunk 1: \n\n{custom_texts[0].page_content}\n\n=====\n')
print(f'### Chunk 2: \n\n{custom_texts[1].page_content}\n\n=====')

现在,让我们比较一些标准参数集与自定义参数的输出:

# Print the sampled chunks
print("====   Sample chunks from 'Standard Parameters':   ====\n\n")
for i, chunk in enumerate(texts):
  if i < 4:
    print(f"### Chunk {i+1}: \n{chunk.page_content}\n")

print("====   Sample chunks from 'Custom Parameters':   ====\n\n")
for i, chunk in enumerate(custom_texts):
  if i < 4:
    print(f"### Chunk {i+1}: \n{chunk.page_content}\n")

"""
=======
Output:
=======

====   Sample chunks from 'Standard Parameters':   ====

### Chunk 1: 
Brazil is the world's fifth-largest country by area and the seventh most popul ous. Its capital

### Chunk 2: 
is Brasília, and its most popul ous city is São Paulo. The federation is composed of the union of

### Chunk 3: 
of the union of the 26

### Chunk 4: 
states and the Federal District. It is the only country in the Americas to have Portugue se as an

====   Sample chunks from 'Custom Parameters':   ====

### Chunk 1: 
Brazil is the world's fifth-largest country by area and the seventh most popul ous. Its capital
is Brasília, and its most popul ous city is São Paulo. The federation is composed of the union of the 26

### Chunk 2: 
states and the Federal District. It is the only country in the Americas to have Portugue se as an official
langua ge.[11][12] It is one of the most multicultural and ethnically diverse nations, due to over a century of

### Chunk 3: 
mass immigration from around t he world,[13] and the most popul ous Roman Catholic-majority country.
Bounde d by the Atlantic Ocean on the east, Brazil has a coastline of 7,491 kilometers (4,655 mi).[14] It

### Chunk 4: 
borders all other countries and territories in South America except Ecuador and Chile and covers roughl y
half of the continent's land area.[15] Its Amazon basin includes a vast tropical forest, home to diverse
"""

我们已经可以看到,这些自定义参数产生了更大的块,从而保留了比默认参数集更多的内容。

评估 Langchain 字符文本拆分器

在使用不同参数将文本拆分成块后,我们获得了两个块列表:textscustom_texts,分别包含 3205 和 1404 个文本块。现在,让我们绘制这两种情况下块长度的分布图,以更好地理解更改参数的影响。

图 1:Langchain 分隔器不同参数的块长度分布图(图像由作者提供)

在这个直方图中,x 轴表示块长度,而 y 轴表示每个长度的频率。蓝色条表示原始参数的块长度分布,橙色条表示自定义参数的分布。通过比较这两种分布,我们可以看到参数变化如何影响生成的块长度。

请记住,理想的分布取决于您文本处理任务的具体要求。如果您处理的是精细的分析,可能需要更小、更众多的块;如果是更广泛的语义分析,则可能需要更大、更少的块。

Langchain 字符文本拆分器与 NLTK 和 Spacy

早些时候,我们使用 Langchain 分隔器及其默认参数生成了 3205 个块。而 NLTK 句子分词器将相同的文本拆分成总共 2670 个句子。

为了更直观地理解这些方法之间的差异,我们可以可视化块长度的分布。下面的图显示了每种方法的块长度密度,让我们可以看到长度的分布情况及其大多数长度的位置。

图 2:使用自定义参数的 Langchain Splitter 与 NLTK 和 Spacy 的块长度分布图(图像由作者提供)

从图 1 中,我们可以看到 Langchain 分割器结果具有更简洁的聚类长度密度,并且更倾向于生成较长的聚类,而 NLTK 和 Spacy 似乎在聚类长度方面产生非常相似的输出,倾向于较小的句子,同时存在许多长度可达 1400 个字符的离群点,并且长度有减少的趋势。

KMeans 聚类

句子聚类是一种根据句子的语义相似性进行分组的技术。通过使用句子嵌入和诸如 K-means 的聚类算法,我们可以实现句子聚类。

实现

这是一个使用 Python 库 sentence-transformers 生成句子嵌入和 scikit-learn 进行 K-means 聚类的简单示例代码片段:

from sentence_transformers import SentenceTransformer
from sklearn.cluster import KMeans

# Load the Sentence Transformer model
model = SentenceTransformer('all-MiniLM-L6-v2')

# Define a list of sentences (your text data)
sentences = ["This is an example sentence.", "Another sentence goes here.", "..."]

# Generate embeddings for the sentences
embeddings = model.encode(sentences)

# Choose an appropriate number of clusters (here we choose 5 as an example)
num_clusters = 3

# Perform K-means clustering
kmeans = KMeans(n_clusters=num_clusters)
clusters = kmeans.fit_predict(embeddings)

你可以在这里看到对句子列表进行聚类的步骤:

  1. 加载一个句子转换模型。在这种情况下,我们使用的是来自 HuggingFaceall-MiniLM-L6-v2模型。

  2. 使用模型的encode()方法定义你的句子并生成它们的嵌入。

  3. 然后你定义你的聚类技术和聚类数量(这里我们使用的是 3 个聚类的 KMeans),并最终将其适配到数据集。

评估 KMeans 聚类

最后,我们为每个聚类绘制一个词云。

from wordcloud import WordCloud
import matplotlib.pyplot as plt
from nltk.corpus import stopwords
from nltk.tokenize import word_tokenize
import string

nltk.download('stopwords')

# Define a list of stop words
stop_words = set(stopwords.words('english'))

# Define a function to clean sentences
def clean_sentence(sentence):
    # Tokenize the sentence
    tokens = word_tokenize(sentence)
    # Convert to lower case
    tokens = [w.lower() for w in tokens]
    # Remove punctuation
    table = str.maketrans('', '', string.punctuation)
    stripped = [w.translate(table) for w in tokens]
    # Remove non-alphabetic tokens
    words = [word for word in stripped if word.isalpha()]
    # Filter out stop words
    words = [w for w in words if not w in stop_words]
    return words

# Compute and print Word Clouds for each cluster
for i in range(num_clusters):
    cluster_sentences = [sentences[j] for j in range(len(sentences)) if clusters[j] == i]
    cleaned_sentences = [' '.join(clean_sentence(s)) for s in cluster_sentences]
    text = ' '.join(cleaned_sentences)

    wordcloud = WordCloud(max_font_size=50, max_words=100, background_color="white").generate(text)
    plt.figure()
    plt.imshow(wordcloud, interpolation="bilinear")
    plt.axis("off")
    plt.title(f"Cluster {i}")
    plt.show()

下面是生成的聚类的词云图:

图 3:KMeans 聚类的词云图 — 聚类 0(图片由作者提供)

图 4:KMeans 聚类的词云图 — 聚类 1(图片由作者提供)

图 5:KMeans 聚类的词云图 — 聚类 2(图片由作者提供)

在我们对 KMeans 聚类的词云分析中,很明显每个聚类在其最常见词汇的语义上具有显著的差异。这显示了聚类之间的强语义区分。此外,观察到聚类大小的明显变化,表明每个聚类所包含的序列数量存在显著差异。

KMeans 聚类的局限性

尽管句子聚类有益,但也存在一些显著的缺点。主要的局限性包括:

  1. 句子顺序丧失:句子聚类不会保留原始句子的顺序,这可能会扭曲叙事的自然流动。这点非常重要

  2. 计算效率:KMeans 可能会计算密集且较慢,特别是在处理大型文本语料库或较多聚类时。这对于实时应用或处理大数据来说可能是一个显著的缺点。

聚类相邻句子

为了克服 KMeans 聚类的一些局限性,尤其是句子顺序的丢失,另一种方法可以是基于语义相似性对相邻句子进行聚类。该方法的基本前提是,文本中连续出现的两个句子比相距较远的两个句子更有可能在语义上相关。

实施

下面是使用 Spacy 句子作为输入的启发式扩展实现:

import numpy as np
import spacy

# Load the Spacy model
nlp = spacy.load('en_core_web_sm')

def process(text):
    doc = nlp(text)
    sents = list(doc.sents)
    vecs = np.stack([sent.vector / sent.vector_norm for sent in sents])

    return sents, vecs

def cluster_text(sents, vecs, threshold):
    clusters = [[0]]
    for i in range(1, len(sents)):
        if np.dot(vecs[i], vecs[i-1]) < threshold:
            clusters.append([])
        clusters[-1].append(i)

    return clusters

def clean_text(text):
    # Add your text cleaning process here
    return text

# Initialize the clusters lengths list and final texts list
clusters_lens = []
final_texts = []

# Process the chunk
threshold = 0.3
sents, vecs = process(text)

# Cluster the sentences
clusters = cluster_text(sents, vecs, threshold)

for cluster in clusters:
    cluster_txt = clean_text(' '.join([sents[i].text for i in cluster]))
    cluster_len = len(cluster_txt)

    # Check if the cluster is too short
    if cluster_len < 60:
        continue

    # Check if the cluster is too long
    elif cluster_len > 3000:
        threshold = 0.6
        sents_div, vecs_div = process(cluster_txt)
        reclusters = cluster_text(sents_div, vecs_div, threshold)

        for subcluster in reclusters:
            div_txt = clean_text(' '.join([sents_div[i].text for i in subcluster]))
            div_len = len(div_txt)

            if div_len < 60 or div_len > 3000:
                continue

            clusters_lens.append(div_len)
            final_texts.append(div_txt)

    else:
        clusters_lens.append(cluster_len)
        final_texts.append(cluster_txt)

代码中的主要收获:

  1. 文本处理:每个文本片段都传递给process函数。该函数使用 SpaCy 库创建句子嵌入,这些嵌入用于表示文本片段中每个句子的语义意义。

  2. 簇创建cluster_text函数根据句子嵌入的余弦相似性形成句子簇。如果余弦相似性低于指定的阈值,则开始一个新的簇。

  3. 长度检查:代码接着检查每个簇的长度。如果一个簇太短(少于 60 个字符)或太长(超过 3000 个字符),则调整阈值,并对该簇重复处理,直到达到可接受的长度。

我们来看一下这种方法的一些输出片段,并将它们与 Langchain 分割器进行比较:

====   Sample chunks from 'Langchain Splitter with Custom Parameters':   ====

### Chunk 1: 
Brazil is the world's fifth-largest country by area and the seventh most popul ous. Its capital
is Brasília, and its most popul ous city is São Paulo. The federation is composed of the union of the 26

### Chunk 2: 
states and the Federal District. It is the only country in the Americas to have Portugue se as an official
langua ge.[11][12] It is one of the most multicultural and ethnically diverse nations, due to over a century of

====   Sample chunks from 'Adjacent Sentences Clustering':   ====

### Chunk 1: 
Brazil is the world's fifth-largest country by area and the seventh most popul ous. Its capital
is Brasília, and its most popul ous city is São Paulo.

### Chunk 2: 
The federation is composed of the union of the 26
states and the Federal District. It is the only country in the Americas to have Portugue se as an official
langua ge.[11][12]

很好,现在让我们将final_texts(来自相邻序列聚类方法)的片段长度分布与 Langchain 字符文本分割器和 NLTK 句子分词器的分布进行比较。为此,我们首先需要计算final_texts中片段的长度:

final_texts_lengths = [len(chunk) for chunk in final_texts]

现在我们可以绘制三种方法的分布图:

图 3:测试的所有不同方法生成的片段长度分布图(图像由作者提供)

从图 6 中,我们可以推导出 Langchain 分割器使用其预定义的片段大小,创建了一个均匀的分布,意味着片段长度一致。

另一方面,Spacy 句子分割器和 NLTK 句子分词器似乎更倾向于较短的句子,虽然存在许多较大的离群值,表明它们依赖语言线索来确定分割,可能会产生不规则大小的片段。

最后,自定义的相邻序列聚类方法,根据语义相似性进行聚类,展现出更为多样化的分布。这可能表明一种更具上下文敏感的方法,在保持片段内容连贯的同时,允许更大的灵活性。

评估相邻序列聚类方法

相邻序列聚类方法带来了独特的好处

  1. 上下文一致性:通过考虑语义和上下文一致性生成主题一致的片段。

  2. 灵活性:平衡上下文保持和计算效率,提供可调的片段大小。

  3. 阈值调整:允许用户根据需求微调分块过程,通过调整相似度阈值。

  4. 序列保留:保留文本中句子的原始顺序,对于需要文本顺序的序列语言模型和任务至关重要。

文本分块方法比较:见解总结

Langchain 字符文本分割器

这种方法提供了均匀的块长度,产生均匀的分布。当下游处理或分析需要标准大小时,这可能是有益的。这种方法对文本的具体语言结构不那么敏感,更侧重于产生预定义字符长度的块。

NLTK 句子分词器和 Spacy 句子分割器

这些方法表现出对较小句子的偏好,但包括许多较大的离群点。虽然这可以导致更具语言一致性的块,但也可能导致块大小的高度可变性。

这些方法也能产生良好的结果,可以作为下游任务的输入。

相邻序列聚类

这种方法生成了更多样的分布,表明了其上下文敏感的方法。通过基于语义相似性进行聚类,确保每个块中的内容是连贯的,同时允许块大小的灵活性。当保持文本数据的语义连续性很重要时,这种方法可能是有利的。

为了更直观和抽象(或有趣)的表示,让我们看看下面的图 7,试着找出哪种菠萝“切块”更好地代表所讨论的方法:

图 7:不同的文本分块方法以菠萝切块形式展示(图像由作者整理,菠萝图像来自 Canva)

按顺序列出:

  1. 切割方法 1 代表了基于规则的方法,你可以根据过滤器或正则表达式“剥离”你想要的“垃圾”文本。不过要处理整个菠萝可要花不少功夫,因为它还保留了许多上下文较大的离群点。这确实需要很多工作

  2. Langchain 类似于切割方法 2。大小非常相似,但未能包含整个所需的上下文(它是一个三角形,所以也可能是一个西瓜)。

  3. 切割方法 3 绝对是 KMeans。你甚至可以只分组那些对你有意义的部分——最有汁的部分——但你无法得到其核心。没有它,块会失去所有结构和意义。我认为这也需要很多工作…… 特别是对于更大的菠萝

  4. 最后,切割方法 4 展示了相邻句子聚类方法。块的大小可能有所不同,但通常保持上下文信息,类似于不规则的菠萝块,仍然能显示出水果的整体结构。

总结:在这篇文章中,我们比较了三种文本分块方法及其独特的好处。Langchain 提供了一致的分块大小,但语言结构却被忽视。NLTK 和 Spacy 提供了语言上连贯的分块,但大小差异较大。相邻序列聚类基于语义相似性进行聚类,提供内容连贯性和灵活的分块大小。最终,最佳选择取决于你的具体需求,包括语言连贯性、分块大小的一致性和可用的计算能力。

感谢阅读!

如何在新纪元中共同设计 AI/ML 的软件/硬件架构?

原文:towardsdatascience.com/how-to-co-design-software-hardware-architecture-for-ai-ml-in-a-new-era-b296f2842fe2?source=collection_archive---------1-----------------------#2023-11-22

设计高效 AI/ML 架构的整体视角

Liz LiTowards Data Science Liz Li

·

关注 发表在 Towards Data Science ·8 min read·2023 年 11 月 22 日

--

最新的生成型人工智能技术在计算机视觉、自然语言处理等领域最近取得了爆炸性的进展,涉及到稳定扩散、神经渲染(NeRF)、文本到 3D、大型语言模型(LLM)等创新模型架构的研究突破。这些先进技术需要更复杂的人工智能网络,并且需要数量级更多的计算资源和高效能的架构。

为了满足上述架构要求,弥合硬件(HW)和软件(SW)/算法设计之间的差距是必不可少的。共同设计涉及一个迭代的过程,即设计、测试和优化硬件和软件组件,直到系统达到所需的性能要求。然而,软件和硬件通常是独立设计的,因为软件程序员很少需要考虑运行的硬件,而硬件通常设计为支持广泛的软件。关于软件/硬件共同设计的研究非常有限,但这是有效支持 AI 工作负载所强烈需要的。

来源:作者提供的图像。

我们必须设计高效的“SW/算法感知”硬件和“硬件感知”软件/算法,以便它们能够紧密结合,最大限度地利用我们有限的计算资源。我们应该如何做呢?以下是一种不断发展的方法论,可作为您在设计新的 AI 硬件/软件功能时的参考。

1. 确定代理 AI 工作负载。

要启动分析,我们需要代理 AI 模型并定义一个优先级列表以进一步调查。您可以参考多个资源,包括最新的研究论文(CVPR、Siggraph、大型科技公司的研究实验室)及其开源代码、客户反馈或请求、行业趋势等。根据您的专家判断筛选出几个代表性的模型。这一步骤至关重要,因为您将使用这些模型来设计您的“未来架构”。

2. 彻底分析模型架构。

一定要全面调查模型架构,以了解其功能、创新,并尽可能将其分解为详细的组件。是否有当前技术栈中不支持的新操作符?计算密集的层在哪里?这是一种数据传输(内存)密集的模型吗?需要什么数据类型,哪些量化技术可以在不牺牲准确性的情况下应用?模型的哪部分可以进行硬件加速,潜在的性能优化在哪里?

例如,在神经渲染中,模型需要同时进行渲染和计算(矩阵乘法),您需要检查当前的软件栈是否支持同时进行渲染和计算。在大型语言模型(LLMs)中,随着输入序列长度的增加,键值(KV)缓存的大小也在增加,了解内存需求和潜在的数据传输/内存层次优化以处理大型 KV 缓存至关重要。

左图:服务于 13B 参数的 LLM 时 NVIDIA A100 上的内存布局;右图:内存使用和吞吐量与批量大小的关系。图源:大规模语言模型服务中的高效内存管理与分页注意力 [1]

3. 软件启用和原型设计

下载第 2 步中识别的开源代码,并在“目标”SW 框架/HW 上运行。这一步并不简单,尤其是对于新的/颠覆性模型。由于目标是实现可行的性能分析解决方案,因此此阶段不必交付产品级别的代码。对 SW 进行临时修复而不进行性能调优是可以接受的,以便继续进行第 4 步。一个主要步骤是将开发框架(Pytorch)中的预训练模型转换为目标新框架所需的新格式。

torch.onnx.export(model, 
dummy_input, 
"resnet50.onnx", 
verbose=False, 
input_names=input_names,
outputnames=output_names, 
export_params=True)

然而,通常会有大量的支持工作。例如,要运行可微分渲染模型,需要支持 autograd。很可能在新框架中尚未准备好此功能,并且需要开发团队几个月的努力。另一个例子是 LLMs 的 GPTQ 量化,这在推理框架中可能最初不受支持。架构师可以在 Nvidia 系统上运行工作负载进行性能分析,而不是等待工程团队,因为 Nvidia 是学术开发的硬件选择。这使得可以根据在 SW 启用过程中观察到的差距制定 SW 需求列表。

4. 性能分析和架构创新。

有许多指标可以判断 AI 模型的性能。以下是我们应考虑的主要指标。

4.1 FLOPs(浮点运算次数)和MACs(乘加运算次数)。

这些指标通常用于计算深度学习模型的计算复杂性。它们提供了一种快速简便的方法来了解所需的算术操作次数。FLOPs 可以通过论文分析、Vtune 报告或像 flops-counter.pytorch 和 pytorch-OpCounter 这样的工具来计算。

4.2 内存占用和带宽(BW)

内存占用主要包括权重(网络参数)和输入数据。例如,一个具有 13B 参数的 Llama 模型在 FP16 中消耗约 13*2(FP16=2 字节)= 26GB 内存(输入数据可以忽略,因为权重占用更多空间)。另一个关键因素是 KV 缓存大小。KV 缓存占用最多 30% 的总内存,并且是动态的(请参见第 2 步中的图片)。大型模型通常受到内存限制,因为速度取决于从系统内存移动数据到本地内存的速度,或者从本地内存到本地缓存/寄存器的速度。可用内存带宽在预测推理延迟(LLMs 的 token 生成时间)方面远比峰值计算 TOPS 更有效。一个性能指标是内存带宽利用率(MBU),定义为实际带宽/峰值带宽。理想情况下,MBU 接近 100% 表示内存带宽被完全利用。

足够的内存是不够的!

Nvidia 正在以数量级的增加 FLOPs,但内存带宽没有增加。来源:Substack/SemiAnalysis

由于内存是瓶颈,需要探索先进的模型压缩和内存/缓存技术。以下是一些先驱工作:

  • MemGPT:它利用不同级别的内存层次资源,如快速和小型 RAM 的组合,以及大容量和慢速存储存储器。信息必须明确地在它们之间传输。[2]

  • 低精度量化(GPTQ、AWQ、GGML)以减少模型的内存占用

  • 内存计算(PIM):通过消除数据移动的需要,可以减少功耗并提高性能。

4.3 延迟/吞吐量。

在计算机视觉中,延迟是生成一个帧所需的时间。在 LLMs 的背景下,它是从第一个标记到下一个标记生成之间的时间。吞吐量是每秒生成的标记数/帧数。延迟是衡量 AI 系统性能的关键指标,是软件/硬件性能的复合因素。有各种优化策略需要考虑,以下是其中几个:

  • 优化带宽受限操作,如归一化、点对点操作、SoftMax 和 ReLU。据估计,归一化和点对点操作的运行时消耗几乎比矩阵乘法多 40%,但仅达到矩阵乘法的 250 倍和 700 倍 FLOPS。为解决此问题,可以利用内核融合来融合多个运算符以节省数据传输成本,或者用轻量级运算符(ReLU)替换昂贵运算符(SoftMax)。

运算符类别的比例。来源:Data movement is all you need

  • 专用硬件架构。集成专用硬件(AVX、GPU、TPU、NPU)可以显著提高速度并节省能源,对于需要在资源受限设备上进行实时处理的应用尤为重要。例如,Intel AVX 指令可以使速度提高多达原生 Python 代码的 60,000 倍。

通过对程序进行性能工程优化,可以使两个 4096x4096 矩阵相乘的速度显著提升。来源:There’s plenty of room at the Top: What will drive computer performance after Moore’s law?[3]

Nvidia 图形卡上的张量核(V100、A100、H100 等)可以在一个时钟周期内乘和加两个 FP16 和/或 FP32 矩阵,而 Cuda 核每周期只能执行一次操作。然而,张量核的利用率非常低(端到端训练仅为 3% — 9%),导致能耗高、性能低下。目前有关提高 systolic 阵列利用率的研究活跃进行中(FlexSA、多方向 SA 等),我将在接下来的系列文章中详细介绍。

systolic 阵列。来源:Telesens

此外,由于内存和数据流量始终是大型 AI 模型的瓶颈,因此探索更先进的架构以考虑更大、更高效的芯片内存至关重要。一个例子是 Cerebras 核心内存设计,其中内存按核心独立寻址。

Cerebras 内存架构

  • 还有许多其他优化:并行性、LLMs 的 KV 缓存量化、稀疏激活和端到端优化——我将在即将发布的帖子中详细解释

4.4 功率和能效

电力是我们需要关注的另一个问题,尤其是在低功耗用户场景下。性能和功耗之间总是存在权衡。如下面所示,内存访问操作所消耗的能量比计算操作高几个数量级。为了节省电力,强烈需要减少内存传输。

计算和内存的能量成本。来源: Stanford 的 Song H [4]

结论

上述是我们需要测量的 AI 模型性能的主要指标,通过使用性能分析工具如 Vtune、Nsight 或其他建模工具,架构师可以深入到性能细节的下一层(层级计算/内存流量、计算效率、带宽利用率等),并利用跟踪数据识别热点。性能往往因为软件低效而意外地低。基于代理模型进行 AI 软件/硬件的协同设计是一个迭代过程。

对新兴 AI 模型进行彻底的架构分析是一个协作努力。调查周期可能相当长,并且在产品计划和分析深度之间总是存在权衡。同时需要在硬件、软件和 AI 技术方面具备强大的专业知识,以设计高效的 AI 解决方案。

参考文献

[1] Woosuk Kwon, Zhuohan Li, Siyuan Zhuang, Ying Sheng, Lianmin Zheng, Cody Hao Yu, Joseph E. Gonzalez, Hao Zhang, Ion Stoica, Efficient Memory Management for Large Language Model Serving with Paged Attention, 2023, arxiv

[2] Charles Packer, Vivian Fang, Shishir G. Patil, Kevin Lin, Sarah Wooders, Joseph E. Gonzalez, MemGPT: Towards LLMs as Operating Systems, 2023, arxiv

[3] Charles Leiserson, Neil Thompson, Joel Emer, Bradley Kuszmaul, Butler Lampson, Daniel Sanchez, 和 Tao Schardl, There’s plenty of room at the Top: What will drive computer performance after Moore’s law? 2020, Science

[4] Song Han, Jeff Pool, John Tran, William J. Dally, Learning both Weights and Connections for Efficient Neural Networks, 2015, arxiv

如何组合预测结果

原文:towardsdatascience.com/how-to-combine-the-forecasts-of-an-ensemble-11022e5cac25

使用动态预测组合应对时间序列中的变化

Vitor CerqueiraTowards Data Science Vitor Cerqueira

·发表于 Towards Data Science ·6 分钟阅读·2023 年 1 月 19 日

--

图片由 Chris Lawton 提供,来自 Unsplash

上一篇文章 中,我们探讨了构建组合的主要步骤。

在这篇文章中:

  • 我们深入探讨了预测组合;

  • 我们讨论了如何将多个预测结果进行组合;

  • 我们探索了用于预测组合的动态加权平均方法;

  • 我们将动态组合应用于一个使用 Python 的案例研究

介绍

组合多个模型的预测结果 提高了预测性能。这些方法可以通过动态组合规则进一步改进。

构建预测组合的方法有很多。然而,标准的方法并没有考虑时间序列的动态特性。

在时间序列中,由于存在非平稳性,事物会随时间而变化。例如,不同的阶段或季节效应。这些因素会导致相对表现的变异。不同的预测模型在不同的时期表现更好。

在组合预测时,这种表现的变异性应得到反映。在每个时间步长上,每个模型的权重应根据你对其表现的预期进行调整。

随着时间推移而调整权重的组合被称为动态组合。

从静态到动态的组合

一个时间序列以及多个模型的预测。图像由作者提供。

常量权重

Bagging、boosting、chaining 或 stacking 是一些组合的示例。

Bagging 或 boosting 集成通过简单的平均值来组合各个预测。因此,集成中的所有模型权重相等。在 stacking 或 chaining 中,权重不相等。最佳组合规则在训练过程中学习得到。

在这些情况下,组合规则是静态的。每个模型的权重在任何时候都是相同的。

但是,固定权重无法适应时间序列中的变化。这个问题通过动态组合规则得到解决。

动态权重

Jan Huber拍摄的照片,来自Unsplash

动态权重的集成模型能够应对时间序列中的变化。

挑战在于估计在给定时刻哪个模型更强。结果表明,这是一个棘手的任务。不过,有许多方法可以解决这个问题。

动态集成模型分为三种可能的类别:

  • 窗口化:权重基于模型在过去最近数据窗口中的表现进行计算。例如,你可以计算滚动平方误差。然后,在每个时刻,你通过归一化误差分数来获得权重。你可以在我的 Github 上找到窗口化的实现

  • 遗憾最小化:一些方法尝试最小化称为遗憾的指标。例子包括指数加权平均、均方加权平均或固定份额聚合。参考文献[2]中第二章详细描述了这些方法。你也可以在 R 包opera**中找到它们的实现。

  • 元学习:其他技术学习并预测每个模型在给定时刻的权重。Arbitrating 是这种方法的一个例子。

案例研究

在本文的其余部分,我们将创建一个动态预测集成模型。

我们将使用与能源需求相关的时间序列。下面是它的样子:

半小时电力需求时间序列。数据来源于参考文献[3]。图片由作者提供。

构建集成模型

我们开始通过时间延迟嵌入来转换时间序列,以进行监督学习。你可以查看我之前的帖子以了解更多关于这种方法的信息。

然后,我们构建集成模型。下面是实现这一点的脚本。查看代码中的注释以获取更多背景信息。

import pandas as pd

# methods and validation split
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestRegressor
from sklearn.neighbors import KNeighborsRegressor
from sklearn.linear_model import Lasso, Ridge, ElasticNetCV

# time series example -- GPL-3 license
from pmdarima.datasets import load_taylor

# src module available here: https://github.com/vcerqueira/blog
from src.tde import time_delay_embedding

# loading the data
series = load_taylor(as_series=True)

# train test split
train, test = train_test_split(series, test_size=0.1, shuffle=False)

# ts for supervised learning
train_df = time_delay_embedding(train, n_lags=10, horizon=1).dropna()
test_df = time_delay_embedding(test, n_lags=10, horizon=1).dropna()

# creating the predictors and target variables
# the goal is to forecast the next observation of energy demand
X_train, y_train = train_df.drop('Series(t+1)', axis=1), train_df['Series(t+1)']
X_test, y_test = test_df.drop('Series(t+1)', axis=1), test_df['Series(t+1)']

# defining the five models composing the ensemble
models = {
    'RF': RandomForestRegressor(),
    'KNN': KNeighborsRegressor(),
    'LASSO': Lasso(),
    'EN': ElasticNetCV(),
    'Ridge': Ridge(),
}

# training and getting predictions
train_forecasts, test_forecasts = {}, {}
for k in models:
    models[k].fit(X_train, y_train)
    train_forecasts[k] = models[k].predict(X_train)
    test_forecasts[k] = models[k].predict(X_test)

# predictions as pandas dataframe
ts_forecasts_df = pd.DataFrame(test_forecasts)
tr_forecasts_df = pd.DataFrame(train_forecasts)

我们构建了一个由五个模型组成的集成:一个随机森林,一个 KNN,一个 LASSO,一个 Ridge 和一个 Elastic-net。

在上述脚本中,我们还将每个模型的训练预测存储在一个 pd.DataFrame 中。在最初的几个实例中,权重估计的信息很少。因此,训练预测对于暖启动权重估计是有用的。

动态组合规则

在构建集合模型后,我们准备制定一个动态组合规则。我们关注两种方法:窗口法和仲裁法。此外,我们还包括了一种基于预测简单平均的静态方法。

# src module available at https://github.com/vcerqueira/blog
# windowing 
from src.ensembles.windowing import WindowLoss
# arbitrating (a meta-learning strategy)
from src.ensembles.ade import Arbitrating

# combining training and testing predictions
forecasts_df = pd.concat([tr_forecasts_df, ts_forecasts_df], axis=0).reset_index(drop=True)
# combining training and testing observations
actual = pd.concat([y_train, y_test], axis=0).reset_index(drop=True)

# setting up windowloss dynamic combinatio rule
windowing = WindowLoss()
window_weights = windowing.get_weights(forecasts_df, actual)
window_weights = window_weights.tail(X_test.shape[0]).reset_index(drop=True)

# setting up arbitrating dynamic combinatio rule
arbitrating = Arbitrating()
arbitrating.fit(tr_forecasts_df, y_train, X_train)
arb_weights = arbitrating.get_weights(X_test)
arb_weights = arb_weights.tail(X_test.shape[0])

# weighting the ensemble dynamically
windowing_fh = (window_weights.values * ts_forecasts_df.values).sum(axis=1)
arbitrating_fh = (arb_weights.values * ts_forecasts_df.values).sum(axis=1)

# combining the models with static and equal weights (average)
static_average = ts_forecasts_df.mean(axis=1).values

最终的预测是加权平均。通过将权重与单独的预测值相乘,然后进行求和操作来完成。

这是每个模型根据窗口法的权重演变:

每个模型在时间序列中的权重。较高的权重意味着在最终预测中更重要。图片由作者提供。

在早期,随机森林(RF)是最相关的模型。但在系列的最后部分,岭回归变得更加重要。

这是每种方法的最终误差:

每种方法的平均绝对误差。图片由作者提供。

集合模型比单独的模型表现更好。尤其是动态模型,比简单地取预测值的平均值要好。

收获

  • 不同的预测模型在不同的时期表现更好;

  • 在动态集合模型中,不同模型的权重随着时间的推移而变化。因此,这些方法可以应对时间序列中的变化;

  • 在每个时间步中,权重应反映我们对模型表现的预期;

  • 构建动态集合模型有几种方法。这些方法通常可以分为窗口法、悔恨最小化法或元学习法。

希望你觉得这个有用!感谢阅读,下次故事见!

相关文章

参考文献

[1] Opera R 包:pbil.univ-lyon1.fr/CRAN/web/packages/opera/opera.pdf

[2] Cesa-Bianchi, N., & Lugosi, G.(2006)。《预测、学习与游戏》。纽约,美国:剑桥大学出版社。

[3] taylor 时间序列:半小时电力需求(许可证:GPL-3)

如何有效比较机器学习解决方案

原文:towardsdatascience.com/how-to-compare-ml-solutions-effectively-28384e2cbca1

图片由作者使用 Midjourney 创建。

提高将模型投入生产的几率

Hennie de HarderTowards Data Science Hennie de Harder

·发表于 Towards Data Science ·6 分钟阅读·2023 年 7 月 6 日

--

在评估和比较机器学习解决方案时,你首先会关注的评价指标可能是预测能力。用单一指标来比较不同模型非常简单,这在 Kaggle 比赛中是完全可以接受的。但在现实生活中,情况则有所不同。假设有两个模型:一个使用了 100 个特征和复杂的架构,另一个使用了 10 个特征和 XGBoost。复杂模型的得分仅比 XGBoost 模型高一点。在这种情况下,你会选择性能最好的模型,还是选择更简单的模型?

本文将概述在比较不同机器学习解决方案时可以考虑的不同因素。我将通过一个示例向你展示如何比仅使用预测能力更好地比较模型。开始吧!

除了预测结果外,还有几个重要因素需要在比较机器学习原型时考虑。这些因素提供了关于模型在现实场景中整体适用性和有效性的有价值见解。通过不仅关注预测能力,你将增加将机器学习解决方案投入生产的机会。

这些因素分为四类:维护、实施复杂性、成本和业务需求。项目团队应事先决定哪些因素对项目重要。在原型解决方案创建过程中,开发人员可以开始记录不同因素的情况。

维护

收集数据或进行特征工程有多难?你是否使用了许多不同的库,模型是否对参数调优敏感?项目是否使用了你可以放入管道中的标准 API?这些方面使得一个解决方案更容易或更难维护。

如果你的数据来自许多不同的内部和外部来源,这相较于完全依赖公司内部数据的解决方案会存在一个劣势。原因在于,你不能完全依赖外部来源保持不变,任何在这些来源中的更改或更新都需要在你的解决方案中进行重构或调整。这是可能出现的维护问题的一个例子。

维护性的另一个部分是监控。这涉及到跟踪指标、检测异常或性能下降,以及调试可能出现的问题。一些模型提供了强大的监控和调试功能。这可能是比其他模型的一个优势。

实施复杂性

实施复杂性衡量将模型部署到生产系统中的难度和工作量。它考虑了诸如必要库的可用性模型架构的复杂性以及与现有基础设施的兼容性等因素。一个易于实施并且能与现有系统集成的模型可以在部署阶段节省宝贵的时间和资源。

另一个可以影响实施复杂性的因素是对方法的熟悉程度。选择一个与团队技能相匹配的模型可以显著影响开发时间线。

复杂的道路结构。照片由 Timo Volz 拍摄,发布在 Unsplash

成本

开发一个成本很高的模型很容易。成本对于几乎所有公司来说都是一个重要因素。如果你需要为某个解决方案购买昂贵的许可证,你应该能够证明这个许可证的成本是值得的。

你可以花钱在数据采集数据存储(再)训练推理许可证订阅上。开发解决方案的资源也有一定的成本。通过对每个解决方案这些成本进行有根据的预测,这将成为另一个比较解决方案的因素。

如果成本超出预算(或模型带来的价值),你应该重新考虑一种方法。也可能出现两种解决方案在所有因素上得分相同,除了成本。在这种情况下,选择就很简单,成本较低的解决方案就是更好的选择。

业务要求

最后但同样重要的是业务要求。它们可以有多种形式;以下是一些常见的:

  • 可解释性 能够理解和解释具体预测是一些业务流程中的重要部分。在这种情况下,易于解释的模型可能比预测能力更为重要。如果可解释性很重要,你应该尽量保持模型简单。你可以尝试不同的解释技术,并评估这些技术与模型结合使用的难易程度。

  • 上市时间 在竞争激烈的行业或处理时间敏感的机会时,模型开发和部署的速度可能是一个关键的业务要求。最小化上市时间对获得竞争优势至关重要。在这种情况下,能够快速开发和部署的模型,并且迭代次数或复杂的预处理步骤最少,将是有利的。

  • 法规合规性

    某些行业,如金融、医疗和保险,具有严格的法规和合规标准。业务需求可能包括确保所选模型遵守这些法规,如数据隐私法(例如 GDPR)、行业特定指南或伦理考虑。模型必须符合相关法规,以避免法律和声誉风险。

  • 实时推断 一些应用程序需要(接近)实时预测,在严格的时间限制内做出决策。业务需求可能规定需要低延迟模型,能够快速处理传入数据并实时生成预测。提供高效实时推断能力的模型对于时间敏感的应用程序如欺诈检测或推荐系统至关重要。

比较原型

在了解了可能在评估和比较解决方案时发挥重要作用的不同因素之后,你的下一个问题可能是如何比较这些因素。

这并不一定很复杂。首先,团队确定对用例最重要的因素。假设他们想专注于预测能力数据收集、总体实施复杂性训练成本可解释性

在原型创建过程中,每个人都记录这 5 个主题的笔记。最后,你可以填充一个类似于下面的矩阵:

比较原型。图片由作者提供。

上面列出了团队确定的因素。左侧是比较中的四个原型。点的含义如下:点越大,影响越大。点的颜色表示正面(绿色)、中性(灰色)或负面(红色)。因此,预测能力对原型 1、3 和 4 来说非常好,对原型 2 来说还可以。数据收集对原型 1 和 2 来说还可以,对原型 3 来说非常困难,对原型 4 来说也有些困难。

这只是一个示例,完全可以创建你自己的比较方法。你可以决定量化分数,而不是使用点数。这种方法的优点是,它提供了清晰的概览,并且直接理解你应该继续的原型,即原型 1。你也可以考虑原型 3,但那部分数据收集比较困难。

结论

通过以本文所述的方式比较用例中的原型,你将大大增加进入生产的机会!这样可以很容易地向公司中的任何人说明你选择某种方法的理由。

提前与其他项目成员讨论重要的评估因素,以确保大家意见一致是很有帮助的。实施复杂性、维护、成本和业务需求在大多数项目中难以忽视。只关注预测能力可能会遗漏后续可能出现的复杂性。在原型创建过程中,你可以记录相关标准,并在评估时与团队讨论,以选择最有可能成功的原型。

相关

## 简化你的机器学习项目

为什么花费大量时间和精力在复杂模型上是一个坏主意,以及应该怎么做

towardsdatascience.com ## 适用于任何机器学习模型的模型无关方法

解释方法概述:置换特征重要性、部分依赖图、LIME、SHAP 等。

towardsdatascience.com ## 创建优秀数据科学产品的步骤

从问题到生产。

towardsdatascience.com

如何将 Azure AD 管理身份连接到 AWS 资源

原文:towardsdatascience.com/how-to-connect-azure-ad-managed-identities-to-aws-resources-9353f3309efb?source=collection_archive---------7-----------------------#2023-02-21

从 Azure Data Factory 设置无密钥访问 AWS S3

René BremerTowards Data Science René Bremer

·

关注 发表在 Towards Data Science ·5 分钟阅读·2023 年 2 月 21 日

--

图片由 Susan Q Yin 提供,来源于 Unsplash

1. 介绍

对开发人员来说,一个常见的挑战是管理凭据以确保服务之间的通信安全。Azure 托管标识 消除了开发人员管理这些凭据的需求。应用程序可以使用托管标识来获取 Azure AD 令牌,以访问 Azure 中的资源。在这篇博客中,解释了如何使用 Azure Data Factory 托管标识来访问 AWS S3,详见下文。

1. 概述 — Azure AD 托管标识连接 AWS

在本博客的其余部分,将解释如何将 Azure 租户注册为 AWS 中的身份提供者,以便托管标识可以访问 S3。然后,作为示例,使用 Data Factory 托管标识将数据从 AWS S3 复制到 Azure 存储。本教程重度依赖于Uday Hegde 的这篇博客,其中详细讨论了 Azure AD 和 AWS 的访问。

2. 将 Azure AD 租户设置为 AWS 身份提供者

在本章中,将 Azure AD 租户设置为 AWS 身份提供者。在此过程中执行以下步骤:

  • 2.1 在 Azure 中创建应用注册

  • 2.2 在 AWS 中创建 Azure AD 租户作为身份提供者(IdP)

  • 2.3 将角色添加到 IdP 并授予对 S3 的访问权限

2.1 在 Azure 中创建应用注册

在这一段中,创建了一个应用注册。登录 Azure 门户,选择 Azure Active Directory,然后选择“应用注册”,并创建一个应用注册。创建应用注册后,需要指定应用程序 ID URL。作为 URI,可以使用 api://aws_azure_federate,详见下图。

2.1 使用 URI 方案 app:// 进行应用注册

2.2 在 AWS 中创建 Azure AD 租户作为身份提供者(IdP)

在这一段中,将你的 Azure AD 租户注册为 AWS 中的身份提供者(IdP)。登录 AWS 控制台,选择 IAM,然后选择添加身份提供者。使用 OpenID Connect 作为提供者类型,使用 https://sts.windows.net/<<your Azure AD tenant id>>/(不要忘记结尾的 /)作为 URL,并使用应用程序 ID URI 作为受众,详见下图。

2.2 在 AWS 中创建 Azure AD 租户作为身份提供者

2.3 将角色添加到 IdP 并授予对 S3 的访问权限

在这一段中,在 IdP 中创建一个角色,并将该角色授予对 S3 桶的访问权限。前往你在 AWS 中新创建的 IdP,选择创建角色,选择 Web 身份验证,并选择你的应用注册作为受众。作为权限,选择 AmazonS3FullAccess(在生产环境中,创建更细化的策略)。最后,将你的角色命名为 AzureADWebidentity3 并创建它,详见下图。

2.3 在 IdP 中使用应用注册作为受众的角色,具有对 S3 的完全访问权限

在此步骤之后,您的 Azure AD 租户将注册为身份提供者。在下一步中,Azure Data Factory 托管的身份将用于将文件从 S3 复制到 Azure 存储。

3. 示例:使用 Data Factory MI 连接到 S3

在本章中,使用 Data Factory 管道将数据从 AWS S3 复制到 Azure 存储。执行以下步骤:

  • 3.1 创建 Azure Data Factory、Azure 存储账户和 AWS S3

  • 3.2 部署 Data Factory 管道

  • 3.3 运行 Data Factory 管道

3.1 创建 Azure Data Factory、Azure 存储账户和 AWS S3

在本段中,所需资源已创建。

  • 访问这个 链接 创建 Azure Data Factory 实例。

  • 访问这个 链接 创建 Azure 存储账户。创建存储账户后,请确保 ADF 托管的身份具有 Blob 存储贡献者角色。同时在存储账户上创建一个文件系统。

  • 访问这个 文档 创建 S3 存储桶并向存储桶中添加一些文件。

3.2 部署 Data Factory 管道

在本段中,管道已在您的 Azure Data Factory 实例中创建。管道可以在下面的 Git 仓库中找到:

https://github.com/rebremer/data-factory-managed-identity-connection-aws-s3

有多种方法可以将管道添加到您自己的 ADF 实例中,例如:

  • 将上述 Git 仓库分叉到您的自己的仓库中,并将仓库添加到您的 ADF 实例中,请参见这个 链接

  • 使用 Azure CLI 部署 Git 仓库中的 ARM 模板,请参见这个 链接 了解如何操作。

  • (快速而简便)创建一个名为[1-adf-mi-s3-connection-noakv-nofunc-pl](https://github.com/rebremer/data-factory-managed-identity-connection-aws-s3/blob/main/pipeline/1-adf-mi-s3-connection-noakv-nofunc-pl.json)的空管道和一个名为[2-adf-mi-s3-connection-noakv-func-pl2](https://github.com/rebremer/data-factory-managed-identity-connection-aws-s3/blob/main/pipeline/2-adf-mi-s3-connection-noakv-func-pl2.json)的空管道,以及一个名为[AmazonS3_linkedservice.json](https://github.com/rebremer/data-factory-managed-identity-connection-aws-s3/blob/main/linkedService/AmazonS3_linkedservice.json)的链接服务。一旦创建,使用 Git 仓库中的相应 JSON 文件填充管道和链接服务。

最后,转到前两个管道的参数并用您的变量填充它们(其他管道将不会被使用,仅供参考),参见下面的图片。

3.2 成功创建管道,填写参数

3.3 运行数据工厂管道

在这一段中,运行了前两个管道。两个管道都使用 ADF MI 获取临时 AWS S3 访问令牌,用于将数据从 AWS S3 复制到 Azure 存储。管道可以描述如下:

  • 管道 1:尝试使用 ADF MI 访问 S3 桶,解析错误信息以获取 ADF MI 托管令牌,使用 ADF MI 托管令牌获取临时 AWS S3 令牌,将数据从 S3 复制到 Azure 存储

  • 管道 2:与管道 1 相同,但现在使用了一个 Azure Function 来传递 ADF MI 托管令牌,而不是 S3 错误信息。

显然,管道 2 更适合企业使用,但需要一个包含 3 行代码的Azure Function,如下所示:

# Code of Azure Function:
import azure.functions as func
def main(req: func.HttpRequest) -> func.HttpResponse: 
    return func.HttpResponse(req.headers['Authorization'])

在管道成功运行后,使用复制活动将数据从 S3 复制到 Azure 存储,详见下图。

3.3 成功的管道运行,未使用凭证将文件从 S3 复制到 Azure 存储

4. 结论

对开发人员来说,一个常见的挑战是管理凭证以确保服务之间的安全通信。 Azure 托管身份 消除了开发人员管理这些凭证的需求。这也可以用于从 Azure AD 访问 AWS 资源。在本博客中,解释了如何使用 Azure Data Factory 托管身份访问 AWS S3,详见下方概述。

4. 概述 — Azure AD 托管身份与 AWS 的连接

如何连接 Streamlit 到 Snowflake

原文:towardsdatascience.com/how-to-connect-streamlit-to-snowflake-b93256d80a40?source=collection_archive---------5-----------------------#2023-04-06

实践教程

一个逐步的实用教程

Chanin NantasenamatTowards Data Science Chanin Nantasenamat

·

关注 发布于 Towards Data Science ·10 min read·2023 年 4 月 6 日

--

图片由 Chanin Nantasenamat 在 BlueWillow 上生成。

1. 介绍

Streamlit 是一个用于 Python 编程语言的低代码 Web 框架。它允许你在最少编码的情况下构建一个互动 Web 应用程序。

在本教程中,我将向你展示如何使用 Streamlit 创建一个简单的 Web 应用程序,该应用程序连接到基于云的 Snowflake 数据库,从数据库表中加载数据,并输出数据。

无论你是数据分析师、软件开发人员,还是对用 Python 构建 Web 应用程序感兴趣的人,本教程将逐步指导你如何开始使用 Streamlit 和 Snowflake。

让我们预览一下我们正在构建的 Streamlit 应用:

2. 创建 Snowflake 数据库

在本教程中,我们将创建一个 Snowflake 数据库,并在我们正在构建的 Streamlit 应用程序中连接它。

1. 在 signup.snowflake.com 注册一个 Snowflake 账户

2. 检查你的 “欢迎使用 Snowflake!” 邮件,获取账户 URL 以访问你的 Snowflake 账户。接着,点击 LOG IN TO SNOWFLAKE 按钮,或者将账户 URL https://<your-unique-account-id>.snowflakecomputing.com 复制粘贴到互联网浏览器中。

3. 继续输入你的 Snowflake 凭据。

4. 登录后,默认会看到工作表面板。

5. 按照以下说明创建一个新的 SQL 工作表:

6. 在查询框中输入以下 SQL 查询(请参见代码框下的截图):

CREATE DATABASE PETS;

CREATE TABLE MYTABLE (
    NAME            varchar(80),
    PET             varchar(80)
);

INSERT INTO MYTABLE VALUES ('Mary', 'dog'), ('John', 'cat'), ('Robert', 'bird');

SELECT * FROM MYTABLE;

7. PETS 数据库现在已经创建。接下来,点击左上角的链接返回主页面。

8. 从主页面,点击 “Data” 标签查看我们的数据库。

9. 在这里,我们可以看到新创建的 PETS 数据库。

现在我们的数据库已经准备好了,让我们继续设置编码环境。

3. 设置编码环境

创建 conda 环境

首先,让我们通过在终端(也称为 命令行)窗口中输入以下内容来创建一个名为 snowpark 的 conda 环境:

conda create -n snowpark python=3.8

几分钟后,你会看到 conda 请求你确认它将安装以下新包。要确认,请输入 Y

等待几分钟,让各种 Python 库安装完成。

一切设置好后,底部会有一条消息,说明如何激活和停用环境。

激活 conda 环境

按照上述说明,我们可以通过在终端窗口中输入以下内容来激活名为 snowpark 的 conda 环境:

conda activate snowpark

一旦 conda 环境被激活,你会注意到终端提示符左侧会显示 conda 环境的名称。

特别地,在下面的第一个截图中,我们使用的是基础环境(注意用户名左侧的 (base)),而第二个截图显示了已激活的 snowpark conda 环境(注意用户名左侧的 (snowpark))。

接下来,我们将需要安装一些先决的 Python 库,以便我们可以构建一个可以连接到 Snowflake 数据库的 Streamlit 应用。

在终端提示符中输入以下内容:

pip install snowflake-snowpark-python streamlit

这将安装上述两个 Python 库:

恭喜你,你的编码环境现在已经准备好让我们进行操作了!

停用 conda 环境

最后,当我们完成编码会话时,我们可以通过在终端窗口中输入以下内容来退出 conda 环境:

conda deactivate

一旦 conda 环境被停用,你会注意到我们将退出到(base)环境。

4. 设置目录和文件

导航目录

首先,你需要弄清楚你当前所在的目录路径。

要做到这一点,请在终端提示符中输入pwd

pwd

你会看到在我的电脑上,我的位置是/Users/chanin。假设我想导航到我的工作目录/Users/chanin/Documents/sandbox,那么我需要通过cd命令切换目录,后面跟上两个子文件夹Documents/sandbox

cd Documents/sandbox

创建目录

继续通过mkdir命令创建snowflake-first-app目录:

mkdir snowflake-first-app

接下来,让我们切换到新创建的目录:

cd snowflake-first-app

创建文件

最后,让我们为我们的 Streamlit 应用创建两个空文件,下一部分将对其进行更深入的讲解:

touch streamlit_app.py
mkdir .streamlit
touch .streamlit/secrets.toml

列出目录内容

要查看我们所在的当前目录的内容,可以使用ls命令:

等一下,为什么我们只看到一个文件,而我们已经创建了两个文件。缺少的文件是位于隐藏的.streamlit目录中的secrets.toml文件。

要查看隐藏目录,我们需要添加额外的参数-a,如下所示:

ls -a

现在我们可以看到隐藏目录了!

在这里,我们可以注意到内容是水平列出的,如果我们想要垂直列出内容怎么办?那么我们需要额外的参数-l,如下所示:

ls -a -l

我们不仅可以以垂直列表的形式查看内容,还可以看到内容的其他信息,例如读/写权限、文件大小、文件创建/修改日期等。

5. 构建 Streamlit 应用

现在 conda 环境已经准备好了,让我们继续构建应用。

首先,让我们用以下代码填充你的streamlit_app.py文件:

# Modified from Johannes Rieke's example code

import streamlit as st
from snowflake.snowpark import Session

st.title('❄️ How to connect Streamlit to a Snowflake database')

# Establish Snowflake session
@st.cache_resource
def create_session():
    return Session.builder.configs(st.secrets.snowflake).create()

session = create_session()
st.success("Connected to Snowflake!")

# Load data table
@st.cache_data
def load_data(table_name):
    ## Read in data table
    st.write(f"Here's some example data from `{table_name}`:")
    table = session.table(table_name)

    ## Do some computation on it
    table = table.limit(100)

    ## Collect the results. This will run the query and download the data
    table = table.collect()
    return table

# Select and display data table
table_name = "PETS.PUBLIC.MYTABLE"

## Display data table
with st.expander("See Table"):
    df = load_data(table_name)
    st.dataframe(df)

## Writing out data
for row in df:
    st.write(f"{row[0]} has a :{row[1]}:")

要在终端中执行此操作,我们将使用 Vim 文本编辑器,它可以通过vi命令调用,后面跟上文件名。

vi streamlit_app.py

这将启动你的 Vim 文本编辑器,以编辑streamlit_app.py文件的内容。

然后,按 i 键,你应该会看到终端窗口底部显示编辑器处于 INSERT 模式。

接下来,按 Ctrl+V(在 Windows 上)或 CMD+V(在 Mac OSX 上)将复制的内容粘贴到文件中。

然后,按 Esc 键并输入以下内容,随后按 Enter 键:

:wq!

这意味着将内容写入文件并退出文本编辑器。

为了确保内容已正确粘贴,让我们使用 cat 命令查看文件内容:

cat streamlit_app.py

现在 streamlit_app.py 文件已经准备好,我们接下来处理 Snowflake 访问凭据。

6. 通过秘密管理添加访问凭据

记住我们之前创建的 .streamlit/secrets.toml 文件,现在我们要将以下内容填充到该文件中:

[snowflake]
user = "enter_your_password_here"
password = "enter_your_password_here"
account = "enter_your_account_key_here"

保存并退出(参考之前的说明)编辑器。

恭喜,你的应用程序现在已经完成,并准备好启动!

💡 **Note:** - Your "user" and "password" was set by you when registering for your Snowflake account.
- Your "account" key is the sub-domain name from the "account URL" that was sent to you in the "Welcome to Snowflake!" email. 
(See the above screenshot for the "user" and "account" details)

7. 启动 Streamlit 应用

现在我们的 streamlit_app.py.streamlit/secrets.toml 文件都准备好了,让我们回到终端,启动 Streamlit 应用。

继续在终端窗口中输入以下内容:

streamlit run streamlit_app.py

几分钟后,你会注意到终端窗口中出现以下消息,提供有关访问 Streamlit 应用程序的说明。

同时,你应该能够看到一个新的互联网浏览器启动,Streamlit 应用程序位于本地的 http://localhost:8503

8. 行级代码解释

在概念层面,Streamlit Python 库用于连接到 Snowflake 数据库,从数据库表中加载数据并在应用程序中显示数据。

1. 导入所需的 Python 库(即 streamlitsnowpark)。

import streamlit as st
from snowflake.snowpark import Sessionp

2. 将 Streamlit 应用的标题设置为 "❄️ 如何将 Streamlit 连接到 Snowflake 数据库"

st.title('❄️ How to connect Streamlit to a Snowflake database')p

3. 定义一个自定义函数来创建 Snowflake 会话,并使用 Streamlit 的缓存机制缓存该会话。

# Establish Snowflake session
@st.cache_resource
def create_session():
    return Session.builder.configs(st.secrets.snowflake).create()

session = create_session()
st.success("Connected to Snowflake!")

4. 使用一个缓存结果的函数从 Snowflake 数据库中的表中加载数据。

# Load data table
@st.cache_data
def load_data(table_name):
    ## Read in data table
    st.write(f"Here's some example data from `{table_name}`:")
    table = session.table(table_name)

    ## Do some computation on it
    table = table.limit(100)

    ## Collect the results. This will run the query and download the data
    table = table.collect()
    return table

5. 在 Streamlit 应用中显示加载的数据,放在一个可折叠的扩展器中。

# Select and display data table
table_name = "PETS.PUBLIC.MYTABLE"

## Display data table
with st.expander("See Table"):
    df = load_data(table_name)
    st.dataframe(df)

6. 通过 st.write 输出加载的数据,遍历数据表中的行。

配合 st.write 使用,f-string 用于将静态文本与来自数据表的每一行的第一列(row[0])和第二列(row[1])的动态值结合起来。

Emoji 是通过在 row[1] 值前后添加冒号来显示的。

## Writing out data
for row in df:
    st.write(f"{row[0]} has a :{row[1]}:")

9. 总结

总结一下,本教程展示了如何使用 Streamlit 创建一个连接到 Snowflake 数据库的 Web 应用程序。这代表了一个基本的启动模板,你可以在此基础上扩展,构建更复杂的数据驱动型 Web 应用。

如果你有任何问题,请在下方评论区留言,或通过 Twitter 联系我@thedataprofLinkedIn

[## 通过我的推荐链接加入 Medium - Chanin Nantasenamat

如果你觉得这篇文章对你有帮助,可以成为 Medium 会员来支持我作为作者。每月费用为 $5,并且可以…

data-professor.medium.com](https://data-professor.medium.com/membership?source=post_page-----b93256d80a40--------------------------------)

阅读这些内容…

## Streamlit 探索任务:Streamlit 入门

学习 Streamlit 的指导路径

towardsdatascience.com ## 如何掌握 Scikit-learn 以进行数据科学

这里是数据科学中你需要的基本 Scikit-learn

towardsdatascience.com ## 如何掌握 Python 以进行数据科学

这里是数据科学中你需要的基本 Python

towardsdatascience.com

下一步观看…

如何通过 Cloud SQL Auth Proxy 在 Docker 中连接到 GCP Cloud SQL

原文:towardsdatascience.com/how-to-connect-to-gcp-cloud-sql-with-cloud-sql-auth-proxy-in-docker-99bdf810c498

学习一种标准的方法将 Docker 化应用程序连接到 GCP Cloud SQL 实例

Lynn G. KwongTowards Data Science Lynn G. Kwong

·发布于 Towards Data Science ·9 分钟阅读·2023 年 2 月 20 日

--

图片来源于 Pixabay 上的 WilliamsCreativity (Servers Data)

Google Cloud Platform (GCP) 上的 Cloud SQL 是一个很棒的服务,如果你想在云中托管你的关系型数据库。连接到 Cloud SQL 的标准方法有一些,比如从你的 GCP 资源(如 Cloud Run、Compute engine 等)连接。然而,关于如何将 Docker 化应用程序连接到 Cloud SQL 的文档并不多。

Cloud SQL Auth proxy 是连接你的 Docker 化应用程序到 Cloud SQL 的推荐方法。它提供了安全访问你的 Cloud SQL 实例,而无需授权网络或配置 SSL。

在本文中,我们将介绍如何以各种方式使用 Cloud SQL Auth proxy,重点讲解如何编写 docker-compose.yaml 文件以连接 Docker 化应用程序到 Cloud SQL 实例。

准备工作

在这一点上,我假设你已经有了一个 GCP Cloud SQL 实例。Cloud SQL 实例的创建通常由系统或 DevOps 工程师完成。然而,如果你在做自己的项目或者只是为了学习目的,你可以自己创建一个。前往 GCP 控制台并按照说明操作,创建一个应该相当简单。记得创建一个数据库用户,以便后续使用。

如果你想在个人笔记本电脑或台式机上运行本文中的代码,建议设置本地环境以便与 GCP 配合使用。

确保你的 Google 帐号或服务帐号至少具有“Cloud SQL Client”角色。如果适用,也可以分配“Cloud SQL Editor”或“Cloud SQL Admin”角色。

确保已启用 Cloud SQL Admin API。

直接使用 Cloud SQL Auth proxy 命令。

有些情况下,直接使用 Cloud SQL Auth 代理命令(而不是使用 Docker)是更可取的。例如,当你想将命令作为启动脚本运行,或在裸金属机器或云虚拟机(VM)上将其作为服务时。

学习 Cloud SQL Auth 代理命令对在 Docker 中使用也很有帮助,因为命令实际上是相同的,只不过后者是在 Docker 容器中运行。

首先,我们需要下载 Cloud SQL Auth 代理的二进制文件:

sudo curl -o /usr/local/bin/cloud-sql-proxy \
  https://storage.googleapis.com/cloud-sql-connectors/cloud-sql-proxy/v2.0.0/cloud-sql-proxy.linux.amd64

sudo chmod +x /usr/local/bin/cloud-sql-proxy

你可以在其 GitHub 仓库中找到 Cloud SQL Auth 代理的最新版本。

然后我们可以使用 Cloud SQL Auth 代理连接到我们的 Cloud SQL 实例,请查看下面的评论,这些评论包含每个命令的上下文解释:

# 1\. Find the instance name to be used.
#    It should have a format of myproject:myregion:myinstance.
gcloud sql instances describe <INSTANCE_NAME> --format='value(connectionName)'

# 2\. Use the long instance connection name returned by 1 for cloud-sql-proxy.
cloud-sql-proxy --port 13306 INSTANCE_CONNECTION_NAME &
# Note that a high port is used to avoid potential port conflicts.
# It may not be needed for your use case.

# 3\. When not running on GCP resources and GOOGLE_APPLICATION_CREDENTIALS is
#    not set locally, we need to specify the key file directly.
cloud-sql-proxy --port 13306 INSTANCE_CONNECTION_NAME \
    --credentials-file /local/path/to/service-account-key.json &

# 4\. On GCP compute engine, you can connect by a private IP if the compute
#    engine and SQL instance are in the same VPC network.
cloud-sql-proxy --port 13306 INSTANCE_CONNECTION_NAME --private-ip &

# 5\. You can connect to multiple Cloud SQL instances at same time, which can
#    be handy for local development as you may need to access multiple
#    databases at the same time.
cloud-sql-proxy "INSTANCE_CONNECTION_NAME_1?port=13306" \
    "INSTANCE_CONNECTION_NAME_2?port=13307" &
# Remember to specify differnt ports for different instances.

请注意,在cloud-sql-proxy命令行的末尾放置了一个和号(&),这样我们可以在后台的另一个进程中运行 Cloud SQL Auth 代理。这样,我们不会意外关闭连接,也不需要打开一个新的标签页,如果我们想立即使用 SQL 客户端连接到我们的 SQL 实例。

我们可以使用jobs命令查看后台运行的进程。我们还可以使用fg %Nkill %N(其中Njobs返回的数字)将进程切换到前台或终止它。

当你看到:

The proxy has started successfully and is ready for new connections!

然后你可以使用 SQL 客户端或在你的应用程序中使用一些库(如 SQLAlchemy)连接到你的 SQL 实例。

对于 MySQL 客户端,命令是:

mysql -h 127.0.0.1 -P 13306 -u DB_USERNAME -p

请注意,主机是127.0.0.1,即我们的本地主机,因为我们使用代理连接到我们的数据库。

如果你需要编写复杂的 SQL 查询,强烈推荐使用图形化工具,如 DBeaver,它是一个免费的通用数据库管理工具。它对语法高亮、自动补全以及许多其他出色功能有很好的支持。它就像 SQLAlchemy 的图形化版本。如果你还没试过,值得一试,你会发现它的表现优于大多数其他工具。

使用 Docker 启动 Cloud SQL Auth 代理

我们也可以使用 Docker 启动 Cloud SQL Auth 代理,这通常适用于你不想或不能安装cloud-sql-proxy时。

使用 Docker 启动 Cloud SQL Auth 代理非常简单,你只需按照这些命令操作:

docker pull gcr.io/cloud-sql-connectors/cloud-sql-proxy:2.0.0

docker run -d \
  -v /local/path/to/service-account-key.json:/container/path/to/service-account-key.json \
  -p 127.0.0.1:3306:3306 \
  gcr.io/cloud-sql-connectors/cloud-sql-proxy:2.0.0 \
  --address 0.0.0.0 --port 3306 \
  --credentials-file /container/path/to/service-account-key.json INSTANCE_CONNECTION_NAME

请注意,-p 127.0.0.1:3306指定了 Cloud SQL Auth 代理不会暴露在本地主机之外,而--address 0.0.0.0用于使端口在 Docker 容器外部可访问。

直接使用 Docker 启动 Cloud SQL Auth 代理并不常见。更有用的方法是使用 Docker Compose,如我们将在下一节中看到的那样。

使用 Docker Compose 启动 Cloud SQL Auth 代理

通常我们不会使用 Docker 启动一个独立的 Cloud SQL Auth 代理容器。相反,我们将其与应用程序一起在 docker-compose.yaml 文件中指定。这样,我们的应用程序可以得到适当的 docker 化,并且所有开发人员可以在他们的计算机上与所需的数据库依赖项一起安装。

我们将看到如何在同一个 docker-compose.yaml 文件中放置 FastAPI 微服务和 Cloud SQL Auth 代理。

但首先,让我们看看如何仅为 Cloud SQL Auth 代理创建 docker-compose.yaml 文件,因为有一些细节需要先弄清楚。我们初始的 docker-compose.yaml 文件内容如下:

version: "3.9"

services:
  cloudsql:
    image: gcr.io/cloud-sql-connectors/cloud-sql-proxy:2.0.0
    volumes:
      - type: bind
        source: ./service-account-key.json
        target: /service-account-key.json
        read_only: true
    ports:
      - target: 3306
        published: 13306
    command: --address 0.0.0.0 --port 3306 --credentials-file /service-account-key.json glass-core-xxxxxx:europe-west1:gs-mysql

如我们所见,设置与直接使用 Docker 时的设置相同。请注意,对于 command 键,我们不应指定 cloud-sql-proxy 命令,因为它会在 Docker 容器内自动指定。

然后,我们可以通过以下命令启动 cloudsql 服务:

docker-compose up -d

通常作为开发人员,我们会有权限使用个人 Google 帐户访问我们的数据库。如果你已经通过 gcloud auth login 登录到 GCP,你只需将 ~/.config/gcloud 文件夹绑定到容器中,不需要提供服务帐号密钥文件:

version: "3.9"

services:
  cloudsql:
    image: gcr.io/cloud-sql-connectors/cloud-sql-proxy:2.0.0
    volumes:
      - type: bind
        source: ~/.config/gcloud
        target: /home/nonroot/.config/gcloud
        read_only: true
    ports:
      - target: 3306
        published: 13306
    command: --address 0.0.0.0 --port 3306 glass-core-xxxxxx:europe-west1:gs-mysql

这是本地开发的推荐方式,因为使用服务帐号密钥文件有泄露的风险,并可能导致安全问题。

然而,如果你直接使用这个 docker-compose.yaml 文件启动 cloudsql 服务,通常会看到这个错误:

open /home/nonroot/.config/gcloud/application_default_credentials.json: permission denied

原因是我们将本地主机上的 ~/.config/gcloud 文件夹绑定到容器中的一个文件夹,该文件夹将被 nonroot 用户使用。因此,我们需要更改 ~/.config/gcloud 文件夹的权限,以便容器中的 nonroot 用户可以访问:

find ~/.config/gcloud/ -type d | xargs -I {} chmod 755 {}
find ~/.config/gcloud/ -type f | xargs -I {} chmod 644 {}

这两个命令使 ~/.config/gcloud/ 文件夹及其子文件夹以及所有内容对其他用户(非拥有者或组用户)可访问。如果你想了解更多关于这些命令的信息,请查看 这篇文章

如果你重新启动服务,一切应该能正常工作。

现在让我们将我们的 FastAPI 应用程序添加到 docker-compose.yaml 文件中:

version: "3.9"

services:
  my-app:
    build:
      context: ./app
    image: my-app:latest
    ports:
      - target: 80
        published: 8080
    networks:
      - my-app
    volumes:
      - type: bind
        source: ./app
        target: /app
    env_file:
      - ./secrets.env
    environment:
      - PYTHONPATH=/app:.:..
      - DB_NAME=app
      - DB_HOST=cloudsql
      - DB_PORT=3306
    depends_on:
      - cloudsql

  cloudsql:
    image: gcr.io/cloud-sql-connectors/cloud-sql-proxy:2.0.0
    volumes:
      - type: bind
        source: ~/.config/gcloud
        target: /home/nonroot/.config/gcloud
        read_only: true
    ports:
      - target: 3306
        published: 13306
    networks:
      - my-app
    command: --address 0.0.0.0 --port 3306 glass-core-xxsxxx:europe-west1:gs-mysql

networks:
  my-app:
    name: my-app
    driver: bridge

docker-compose.yaml 文件的关键点:

  1. 应用程序和 Cloud 代理服务必须在同一网络中。否则,应用程序无法访问数据库。

  2. 一些非敏感的环境变量通过 environment 键以明文设置,而敏感的环境变量则通过秘密文件 secrets.env 设置。此文件不应添加到版本控制的代码库中。

  3. 应用程序被设置为依赖于 Cloud 代理,以便在应用程序运行时数据库始终可访问。

  4. 一个特殊的环境变量 PYTHONPATH 被设置为 /app:.:..,以便更容易通过相对或绝对路径导入模块。

我们可以使用 Pydantic 来读取应用程序中的环境变量,这非常方便:

# app/db/db.py
from pydantic import BaseSettings, Field, SecretStr
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import scoped_session, sessionmaker, Session
from sqlalchemy.schema import MetaData

# Create a base for SQLAlchemy mappers.
Base = declarative_base(metadata=MetaData(schema="app"))
metadata = Base.metadata

# A Pydantic model to get environment variables.
class DbSettings(BaseSettings):
    """Settings for SQL."""

    host: str = Field(..., env="DB_HOST")
    user: str = Field(..., env="DB_USERNAME")
    password: SecretStr = Field(..., env="DB_PASSWORD")
    port: int = Field(env="DB_PORT", default=3306)
    db_name: str = Field(env="DB_NAME", default="app")

# We need to create an instance of the Pydantic model to access the
# environment variables.
db_settings = DbSettings()

db_conn_url = (
    "mysql+pymysql://"
    f"{db_settings.user}:{db_settings.password.get_secret_value()}"
    f"@{db_settings.host}:{db_settings.port}/{db_settings.db_name}"
)

# Create SQLAlchemy SQL engine and session factory.
engine = create_engine(db_conn_url)
session_factory = sessionmaker(bind=engine)
scoped_session_factory = scoped_session(session_factory)

def get_db_sess():
    """Get a SQLAlchemy ORM Session instance.

    Yields:
        A SQLAlchemy ORM Session instance.
    """
    db_session: Session = scoped_session_factory()
    try:
        yield db_session
    except Exception as exc:
        db_session.rollback()
        raise exc
    finally:
        db_session.close()

上述代码可以作为任何 Python 微服务的模板。

在我们的简单应用中,我们可以通过FastAPI依赖注入来读取用户数据:

# app/main.py
from fastapi import Depends, FastAPI
from sqlalchemy.orm import Session

from app.db.db import get_db_sess
from app.db.models.users import User as UserModel
from app.schema.users import User as UserSchema

app = FastAPI()

@app.get("/users")
async def get_users(
    db_sess: Session = Depends(get_db_sess),
) -> list[UserSchema]:
    users = db_sess.query(UserModel).all()

    return users

这个 GitHub 仓库包含了本篇文章的所有代码。如果你想深入了解代码,建议查看相关的文章,比如PydanticSQLAlchemyFastAPI。这些都是非常受欢迎的 Python 开发者库,非常值得加入你的工具集。

本文介绍了如何在裸机或虚拟机上直接使用 Cloud SQL Auth 代理。介绍了不同的认证方法,包括使用默认认证和服务账号密钥文件。

我们还介绍了如何使用 Docker 直接启动 Cloud SQL Auth 代理。然而,由于其不可移植,不推荐使用这种方法,因此难以与其他开发者共享设置。更好的方式是使用docker-compose.yaml文件将我们的 Docker 化应用程序连接到 Cloud SQL 实例。

提供了一个简单但实用的 Cloud SQL Auth 代理在实际项目中使用的例子,可以作为你自己项目的模板。

相关文章:

如何通过 Visual Studio Code 连接到您的 AWS EC2 实例

原文:towardsdatascience.com/how-to-connect-to-your-aws-ec2-instance-with-visual-studio-code-d72150df601d

一个快速简便的指南,教您如何使用 VS Code 访问远程服务器

Aashish NairTowards Data Science Aashish Nair

·发表于 Towards Data Science ·阅读时间 7 分钟·2023 年 4 月 27 日

--

图片由 John Barkiple 提供,来源于 Unsplash

介绍

Visual Studio Code 毫无疑问是我最喜欢的集成开发环境。它简单、直观的用户界面使其成为我进行数据科学项目时的首选编辑器。

实际上,它如此出色,以至于即使在处理远程服务器时,我也不愿意与其分离。

这有问题吗?不,完全没有!

毕竟,Visual Studio Code 可以配置以允许用户在远程服务器上工作。最棒的是,这个过程不会超过 10 分钟!

这对需要计算或存储资源的项目非常有用,而这些资源是本地机器无法提供的。当我需要处理大型数据集时,我非常依赖这个功能。

如果您处于类似的情况,希望在享受 Visual Studio Code 舒适的同时处理大数据或训练深度学习模型,本教程将向您展示如何使用这个集成开发环境连接到您的 AWS EC2。

案例研究

在本教程中,我们将把包含两个文件的文件夹移动到 EC2 实例:一个名为 random_number.py 的 Python 文件和一个包含所有 Python 脚本依赖项的 requirements.txt 文件。

文件移动到远程服务器后,将执行 random_number.py 脚本。

random_number.py 执行一个简单的任务:打印一个随机数字。

第一步 — 启动实例

首先,让我们启动稍后要连接的 EC2 实例。

您可以根据需要选择实例类型,但在选择时请考虑以下几点:

1. AMI: 你可以使用任何你想要的 AMI,但请记住,它将影响你稍后在 Visual Studio Code 中输入的配置值。

这个演示的 EC2 实例将使用 Ubuntu AMI 启动。

AMI(由作者创建)

2. 密钥对: 连接到实例时需要一个密钥对。因此,请将其保存在安全的地方。

这个实例的密钥对名为“demo_kp.pem”,将存储在下载文件夹中。

密钥对(由作者创建)

步骤 2 — 配置 Visual Studio Code

我们将使用安全 Shell(SSH)访问 EC2 实例,这是一种允许两台计算机安全地通信和共享信息的网络协议。

为了使 Visual Studio Code 能够使用 SSH,我们需要首先安装所需的扩展。点击左侧的扩展按钮或按 Ctrl+Shift+X 进入市场。输入“Remote - SSH”并选择第一个条目。

扩展市场(由作者创建)

如果尚未安装扩展,请安装它。

已安装扩展(由作者创建)

接下来,通过点击窗口左下角的绿色图标打开远程窗口:

打开远程窗口(由作者创建)

选择“连接到主机”。

连接到主机(由作者创建)

然后,选择“配置 SSH 主机”(而不是蓝色高亮的那一行):

配置 SSH 主机(由作者创建)

选择这个计算机的配置文件,在这种情况下是第一个。

选择配置文件(由作者创建)

打开配置文件后,输入以下关键词的详细信息:HostHostNameUserIdentityFile

对于案例研究,配置文件将如下所示。

Host demo-host
    HostName ec2-18-218-169-69.us-east-2.compute.amazonaws.com
    User ubuntu
    IdentityFile C:/Users/aashi/Downloads/demo_kp.pem 

这是对关键词的快速分解以及你接下来应该写的内容。

1. Host: 这是用户指定的名称。可以是你想要的任何名称。

案例研究的主机将被称为“demo-host”。

2. HostName: HostName 应该是实例的公共 DNS。要找到实例的 DNS,首先在 EC2 控制台中选择该实例。

选择实例(由作者创建)

然后,点击“Connect”并切换到“SSH client”标签页。公共 DNS 将在说明中提供。

公共 DNS(由作者创建)

3. User: 这是实例的用户名。用户名取决于为 EC2 实例选择的 AMI。如果你不确定用户名是什么,请访问AWS 文档

在我的案例中,用于启动实例的 AMI 是 Ubuntu,因此用户将是“ubuntu”。

4. IdentityFile: 这是分配给 EC2 实例的私钥文件的路径。在本案例研究中,此关键字的值是“demo_kp.pem”文件的路径。

将所有组件拼接在一起后,配置文件应该如下所示:

配置文件(作者创建)

完成所有更改后,保存配置文件。你现在可以准备连接到这个 EC2 实例。

注意:实例的公共 DNS 每次启动实例时都会更改。因此,每当你启动实例时,你需要通过将 HostName 值更新为新的公共 DNS 来更新配置文件。

第 3 步 — 连接到实例

再次选择左下角的绿色按钮,然后选择“连接到主机”。

连接到主机(作者创建)

这时,你应该能看到你创建的主机。

选择主机(作者创建)

选择你创建的主机后,应该会弹出一个新窗口,让你直接访问远程服务器!

新窗口(作者创建)

如果你查看窗口的左下角,你应该能看到你创建的主机的名称。此外,你可以通过终端确认与实例的连接。

代码输出(作者创建)

现在你可以访问远程服务器,你可以使用 Visual Studio Code 轻松编写代码和导航目录!

第 4 步 — 设置实例中的环境

所以,我们已经成功地使用 Visual Studio Code 连接到 EC2 实例。

然而,工作还没完成。毕竟,我们需要设置环境,以便在服务器中使用random_number.pyrequirements.txt文件。

对于本案例研究,我们将在远程服务器的终端中进行以下安装:

sudo apt-get update
sudo apt-get install python3-pip
sudo pip3 install virtualenv

有了这些安装,我们可以创建一个虚拟环境进行操作。

接下来,我们将获取运行random_number.py文件所需的文件。有几种方法可以实现这一点。

首先,由于我们使用的是 Visual Studio Code,我们可以简单地将脚本从本地机器复制并粘贴到远程服务器的脚本中。记住,我们不受命令行界面的限制。

话虽如此,将文本从一个文件手动复制到另一个文件可能会很麻烦。相反,使用安全复制协议(SCP)将文件复制到远程服务器会更好。

使用 SCP,我们可以用一个命令将整个目录复制到远程服务器上!

复制整个文件夹的语法如下:

scp -i <path/to/key_pair.pem> -r <path/to/folder_to_copy> <user>@<public DNS>:<path/to/destination>

使用 SCP 复制(由作者创建)

另外,如果你只是想复制单个文件,可以使用具有以下语法的 SCP 命令:

scp -i </path/key-pair-name.pem> </path/file_to_copy> <username>@<instance_public_dns>:<destination_path>

类似于配置 Visual Studio Code,使用 SCP 也要求用户知道他们的密钥文件路径、用户名和公共 DNS。使用在 Visual Studio Code 配置文件中输入的相同信息。

现在,我们的远程服务器应该有我们需要的所有文件了!

代码输出(由作者创建)

让我们创建一个名为 test_venv 的虚拟环境并进入它。

python3 -m venv test_venv
source test_venv/bin/activate

代码输出(由作者创建)

在这个虚拟环境中,安装requirements.txt文件中的包。

pip3 install -r requirements.txt

代码输出(由作者创建)

最后,我们可以运行我们的 random_number.py 文件!

python3 random_number.py

代码输出(由作者创建)

最终备注

照片由 Prateek Katyal 提供,来自 Unsplash

如果你原本认为在远程服务器上工作意味着使用像 vi 这样的枯燥编辑器,希望这篇文章改变了你的看法。

拥有这一新技能后,Visual Studio Code 的粉丝们将能够处理更具计算密集型的任务,如深度学习项目,而无需使用不太喜欢的 IDE。

最后,由于你现在正在使用 EC2 实例,请记住每次启动实例时,你的实例公共 DNS 都会发生变化,因此你需要每次启动实例时更新 Visual Studio Code 中的配置文件(以及任何使用公共 DNS 的命令)。

感谢阅读!

如何将任何文本转换为概念图谱

原文:towardsdatascience.com/how-to-convert-any-text-into-a-graph-of-concepts-110844f22a1a?source=collection_archive---------0-----------------------#2023-11-10

使用 Mistral 7B 将任何文本语料库转换为知识图谱的方法

Rahul NayakTowards Data Science Rahul Nayak

·

关注 发表在 Towards Data Science ·12 分钟阅读·2023 年 11 月 10 日

--

作者使用本文中分享的项目生成的图像。

几个月前,基于知识的问答(KBQA)还是一种新颖的技术。现在,带有检索增强生成(RAG)的 KBQA 对任何 AI 爱好者来说都已经易如反掌。看到 NLP 领域的可能性因 LLMs 而迅速扩展,令人着迷。而且,情况每天都在变得更好。

在我的上一篇文章中,我分享了一种递归 RAG 方法,用于实现带有多跳推理的问答,以基于大量文本语料库回答复杂查询。

## 研究代理:应对基于大文本语料库回答问题的挑战

我制作了一个自主 AI 研究代理,它可以通过深度多跳推理能力回答困难的问题。

[towardsdatascience.com

许多人尝试了它并提供了反馈。感谢大家的反馈。我已将这些贡献汇总,并对代码做了一些改进,以解决原始实现中的一些问题。我计划撰写一篇单独的文章。

在这篇文章中,我想分享另一个可能有助于创建超级研究代理的想法,当它与递归 RAG 结合时。这个想法源于我对递归 RAG 和较小 LLM 的实验,以及我在 Medium 上阅读的一些其他想法——特别是知识图谱增强生成

摘要

知识图谱(KG)或任何图形,由节点和边组成。KG 的每个节点代表一个概念,每个边表示一对概念之间的关系。在这篇文章中,我将分享一种将任何文本语料库转换为概念图的方法。我将“概念图”(GC)与 KG 术语交替使用,以更好地描述我在这里展示的内容。

我在这个实现中使用的所有组件都可以在本地设置,因此这个项目可以很容易地在个人计算机上运行。我在这里采用了非 GPT 的方法,因为我相信较小的开源模型。我正在使用出色的 Mistral 7B Openorca instruct 和 Zephyr 模型。这些模型可以通过 Ollama 在本地设置。

像 Neo4j 这样的数据库使得存储和检索图数据变得容易。在这里,我使用内存中的 Pandas 数据框和 NetworkX Python 库,以保持简单。

我们的目标是将任何文本语料库转换为概念图(GC),并像这篇文章的美丽横幅图像一样进行可视化。我们甚至可以通过移动节点和边缘、缩放以及根据我们的心愿改变图形的物理属性来与网络图进行交互。这里是展示我们正在构建的结果的 Github 页面链接。

rahulnyk.github.io/knowledge_graph/

但首先,让我们深入探讨知识图谱的基本概念以及我们为什么需要它们。如果你已经熟悉这个概念,可以跳过下一部分。

知识图谱

考虑以下文本。

*玛丽有只小羊,

你可能以前听过这个故事;

但你知道她把盘子递过来了吗,

还多了一点!*

(我希望孩子们没有在读这个 😝)

这是文本作为 KG 的一种可能表示方式。

图表由作者使用 draw.io 创建

IBM 的以下文章恰当地解释了知识图谱的基本概念。

## 什么是知识图谱? | IBM

了解知识图谱,语义元数据的网络,表示一组相关实体。

www.ibm.com

引用文章中的摘录来总结这一观点:

知识图谱,也称为语义网络,表示一个现实世界实体的网络——即对象、事件、情况或概念——并说明它们之间的关系。这些信息通常存储在图数据库中,并以图形结构的形式可视化,因此得名知识“图谱”。

为什么选择知识图谱?

知识图谱在多种方面都非常有用。我们可以运行图算法并计算任何节点的中心性,以了解一个概念(节点)在整体工作中的重要性。我们可以分析连接和不连接的概念集,或者计算概念社区,以深入理解主题内容。我们还可以理解看似不相关的概念之间的联系。

我们还可以使用知识图谱来实现图检索增强生成(GRAG 或 GAG),并与我们的文档进行对话。这可以比简单的 RAG 版本获得更好的结果,后者存在若干缺陷。例如,仅通过简单的语义相似性搜索来检索与查询最相关的上下文并不总是有效。尤其是当查询没有提供足够的上下文来表明其真实意图,或者上下文在大量文本中是零散的情况下。

例如,考虑这个查询 —

告诉我《百年孤独》中何塞·阿尔卡迪奥·布恩迪亚的家谱。

这本书记录了何塞·阿尔卡迪奥·布恩迪亚的七代人,其中一半的角色都叫做何塞·阿尔卡迪奥·布恩迪亚。如果通过一个简单的 RAG 管道来回答这个查询,将会是相当具有挑战性的,甚至可能是不可能的。

RAG 的另一个缺陷是它不能告诉你该问什么。通常,提出正确的问题比得到答案更为重要。

图增强生成(GAG)在一定程度上可以解决 RAG 的这些缺陷。更好的是,我们可以混合使用,建立一个图增强检索增强生成的管道,从而获得两者的最佳效果。

所以现在我们知道,图谱非常有趣,它们可以极其有用,而且看起来也很美观。

创建概念图谱

如果你询问 GPT,如何从给定的文本中创建知识图谱?它可能会建议以下过程。

  1. 从文献中提取概念和实体。这些就是节点。

  2. 提取概念之间的关系。这些就是边。

  3. 在图形数据结构或图形数据库中填充节点(概念)和边(关系)。

  4. 可视化,以获得一些艺术上的满足感,哪怕仅此而已。

第 3 步和第 4 步听起来很容易理解。但你如何实现第 1 步和第 2 步?

这是我设计的从任何给定文本语料库中提取概念图的方法的流程图。它类似于上述方法,但有一些细微的差别。

图表由作者使用 draw.io 创建

  1. 将文本语料库分割成多个块。给每个块分配一个 chunk_id。

  2. 对于每个文本块,使用 LLM 提取概念及其语义关系。我们将这种关系的权重定为 W1。相同概念对之间可以有多个关系。每种这样的关系都是概念对之间的一条边。

  3. 考虑到出现在同一文本块中的概念也通过其上下文的接近性相关联。我们将这种关系的权重定为 W2。请注意,同一对概念可能出现在多个块中。

  4. 对相似的对进行分组,求和它们的权重,并连接它们的关系。现在,我们只有一个边连接任何不同的概念对。该边具有一定的权重和一个作为其名称的关系列表。

你可以在我在本文中分享的 GitHub 仓库中看到该方法的 Python 实现。接下来,我们将简要回顾实现的关键思想。

为了演示该方法,我使用了在 PubMed/Cureus 上发布的以下评论文章,该文章遵循知识共享署名许可证。感谢文章末尾的作者。

[## 印度解决医疗保健中人力资源挑战的机会

印度的健康指标最近有所改善,但仍落后于其同行国家。…

www.cureus.com](https://www.cureus.com/articles/158868-indias-opportunity-to-address-human-resource-challenges-in-healthcare?source=post_page-----110844f22a1a--------------------------------#!/)

Mistral 和 Prompt

上述流程图中的第 1 步很简单。Langchain 提供了大量的文本分割器,我们可以用来将文本分成多个块。

第 2 步是真正有趣的开始。为了提取概念及其关系,我使用了 Mistral 7B 模型。在确定最适合我们目的的模型变体之前,我尝试了以下几种:

Mistral Instruct

Mistral OpenOrca,以及

Zephyr(Hugging Face 版本,源自 Mistral)

我使用了这些模型的 4 位量化版本——以防我的 Mac 开始讨厌我——在本地由 Ollama 托管。

[## Ollama

在本地启动和运行大型语言模型。

ollama.ai](https://ollama.ai/?source=post_page-----110844f22a1a--------------------------------)

这些模型都是经过指令调整的模型,带有系统提示和用户提示。只要我们告诉它们,它们都会相当好地遵循指令,并将答案整齐地格式化为 JSON。

经过几轮的试验和错误,我终于确定了Zephyr 模型,并使用了以下提示。

SYS_PROMPT = (
    "You are a network graph maker who extracts terms and their relations from a given context. "
    "You are provided with a context chunk (delimited by ```) 你的任务是提取本体“

    “在给定上下文中提到的术语。这些术语应代表上下文中的关键概念。 \n”

    “想法 1:在遍历每个句子时,考虑其中提到的关键术语。\n”

        “\t 术语可能包括对象、实体、位置、组织、人物、\n”

        “\t 条件、缩写、文档、服务、概念等。\n”

        “\t 术语应尽可能原子化\n\n”

    “想法 2:考虑这些术语如何与其他术语一对一关联。\n”

        “\t 在同一句话或同一段落中提到的术语通常是相互关联的。\n”

        “\t 术语可以与许多其他术语相关联\n\n”

    “想法 3:找出每对相关术语之间的关系。\n\n”

    “将输出格式化为 JSON 列表。列表的每个元素包含一对术语”

    “以及它们之间的关系,如下所示:\n”

    “[\n”

    “   {\n”

    '       "node_1": "从提取的本体中得到的概念",\n'

    '       "node_2": "从提取的本体中相关的概念",\n'

    '       "edge": "节点 _1 和节点 _2 之间的关系,用一两句话描述"\n'

    “   }, {...}\n”

    “]”

)

USER_PROMPT = f"上下文: ```py{input}``` \n\n 输出: "

```py

If we pass our (not fit for) nursery rhyme with this prompt, here is the result.

[

{

“node_1”: “玛丽”,

“node_2”: “羊肉”,

“edge”: “由……拥有”

},

{

“node_1”: “盘子”,

“node_2”: “食物”,

“edge”: “包含”

}, . . .

]


Notice, that it even guessed ‘food’ as a concept, which was not explicitly mentioned in the text chunk. Isn’t this wonderful!

If we run this through every text chunk of our example article and convert the json into a Pandas data frame, here is what it looks like.

![](https://gitcode.net/OpenDocCN/towardsdatascience-blog-zh-2023/-/raw/master/docs/img/b90d053228d8ea0b384e52a8e2ff819a.png)

Every row here represents a relation between a pair of concepts. Each row is an edge between two nodes in our graph, and there can be multiple edges or relationships between the same pair of concepts. The count in the above data frame is the weight that I arbitrarily set to 4.

## **Contextual Proximity**

I assume that the concepts that occur close to each other in the text corpus are related. Let’s call this relation ‘contextual proximity’.

To calculate the contextual proximity edges, we melt the dataframe so that node_1 and node_2 collapse into a single column. Then we create a self-join of this dataframe using the chunk_id as the key. So nodes that have the same chunk_id will pair with each other to form a row.

But this also means that each concept will also be paired with itself. This is called a self-loop, where an edge starts and ends on the same node. To remove these self-loops, we will drop every row where node_1 is the same as node_2 from the dataframe.

In the end, we get a dataframe very similar to our original dataframe.

![](https://gitcode.net/OpenDocCN/towardsdatascience-blog-zh-2023/-/raw/master/docs/img/8898559a2fcb5732570bd12f60e205e2.png)

The count column here is the number of chunks where node_1 and node_2 occur together. The column chunk_id is a list of all these chunks.

So we now have two dataframes, one with the semantic relation, and another with the contextual proximity relation between concepts mentioned in the text. We can combine them to form our network graph dataframe.

We are done building a graph of concepts for our text. But to leave it at this point will be quite an ungratifying exercise. Our goal is to visualise the Graph just like the featured image at the beginning of this article, and we are not far from our goal.

# **Creating a Network of Concepts**

NetworkX is a Python library that makes dealing with graphs super easy. If you are not already familiar with the library, click their logo below to learn more

 [## NetworkX - NetworkX documentation

### NetworkX is a Python package for the creation, manipulation, and study of the structure, dynamics, and functions of…

networkx.org](https://networkx.org/?source=post_page-----110844f22a1a--------------------------------) 

Adding our dataframe to a NetworkX graph is just a few lines of code.

G = nx.Graph()

向图中添加节点

for node in nodes:

G.add_node(str(node))

向图中添加边

for index, row in dfg.iterrows():

G.add_edge(

    str(row["node_1"]),

    str(row["node_2"]),

    title=row["edge"],

    weight=row['count']

)

This is where we can start harnessing the power of Network Graph. NetworkX provides a plethora of network algorithms out of the box for us to use. Here is a link to the list of algorithms we can run on our Graph.

 [## Algorithms - NetworkX 3.2.1 documentation

### Edit description

networkx.org](https://networkx.org/documentation/stable/reference/algorithms/index.html?source=post_page-----110844f22a1a--------------------------------) 

Here, I use a community detection algorithm to add colours to the nodes. Communities are groups of nodes that are more tightly connected with each other, than with the rest of the graph. Communities of concepts can give us a good idea of broad themes discussed in the text.

The Girvan Newman algorithm detected 17 communities of concept in the Review Article we are working with. Here is one such community.

[

'数字技术',

'EVIN',

'医疗设备',

'在线培训管理信息系统',

'可穿戴、可追踪技术'

]


这立刻让我们对审查论文中讨论的健康技术的广泛主题有了一个大致的了解,并使我们能够提出问题,然后用我们的 RAG 管道回答。这不是很棒吗?

让我们还计算一下图中每个概念的度。一个节点的度是它连接的边的总数。所以在我们的案例中,概念的度越高,它在文本主题中就越中心。我们将使用度作为我们可视化中节点的大小。

# **图形可视化**

可视化是此练习中最有趣的部分。它具有某种特质,给予你艺术上的满足感。

我使用 PiVis 库创建交互图表。[Pyvis 是一个用于可视化网络的 Python 库](https://github.com/WestHealth/pyvis/tree/master#pyvis---a-python-library-for-visualizing-networks)。这里有一篇中等文章展示了该库的易用性和强大功能。

[](/pyvis-visualize-interactive-network-graphs-in-python-77e059791f01?source=post_page-----110844f22a1a--------------------------------) ## Pyvis: 用 Python 可视化交互网络图

### 只需几行代码

[towardsdatascience.com

Pyvis 内置了 NetworkX Helper,可以将我们的 NetworkX 图转换为 PyVis 对象。因此,我们不再需要额外编码……耶!!

记住,我们已经计算了每条边的权重以确定边的厚度、节点的社区以确定颜色,以及每个节点的度数以确定大小。

所以,带着这些花里胡哨的东西,这是我们的图表。

![](https://gitcode.net/OpenDocCN/towardsdatascience-blog-zh-2023/-/raw/master/docs/img/fe3e0691e930bea4d3bfe6249c4f9582.png)

动图由作者使用本文讨论的项目生成。

交互图表链接: [*https://rahulnyk.github.io/knowledge_graph/*](https://rahulnyk.github.io/knowledge_graph/)

我们可以随意缩放和移动节点及边缘。页面底部还有滑块面板,可以改变图的物理效果。看看这个图表如何帮助我们提出正确的问题,更好地理解主题!

我们可以进一步讨论我们的图如何帮助构建图增强检索以及这如何帮助我们构建更好的 RAG 管道。但我认为最好留待另一天。我们已经达成了本文的最终目标!

## GitHub 仓库

[](https://github.com/rahulnyk/knowledge_graph?source=post_page-----110844f22a1a--------------------------------) [## GitHub - rahulnyk/knowledge_graph: 将任何文本转换为知识图谱。这可以用于...

### 将任何文本转换为知识图谱。这可以用于图增强生成或基于知识图谱的问答...

[github.com](https://github.com/rahulnyk/knowledge_graph?source=post_page-----110844f22a1a--------------------------------)

欢迎贡献和建议

我使用了以下文章来演示我的代码。

**Saxena S G, Godfrey T (2023 年 6 月 11 日) 印度应对医疗资源挑战的机会。Cureus 15(6): e40274\. DOI 10.7759/cureus.40274**

我对作者们的精彩作品表示感激,并感谢他们在知识共享署名许可下发布这部作品。

**关于我**

我是一个建筑学的学习者(不是建筑物……而是技术类型的)。过去,我曾涉及半导体建模、数字电路设计、电子接口建模以及物联网。目前,我在沃尔玛健康与保健部门从事数据互操作性和数据仓库架构方面的工作。


# 如何正确地对时间序列进行交叉验证

> 原文:[`towardsdatascience.com/how-to-correctly-perform-cross-validation-for-time-series-b083b869e42c`](https://towardsdatascience.com/how-to-correctly-perform-cross-validation-for-time-series-b083b869e42c)

## 避免在将交叉验证应用于时间序列和预测模型时常见的陷阱。

[](https://medium.com/@egorhowell?source=post_page-----b083b869e42c--------------------------------)![Egor Howell](https://medium.com/@egorhowell?source=post_page-----b083b869e42c--------------------------------)[](https://towardsdatascience.com/?source=post_page-----b083b869e42c--------------------------------)![Towards Data Science](https://towardsdatascience.com/?source=post_page-----b083b869e42c--------------------------------) [Egor Howell](https://medium.com/@egorhowell?source=post_page-----b083b869e42c--------------------------------)

·发布于 [Towards Data Science](https://towardsdatascience.com/?source=post_page-----b083b869e42c--------------------------------) ·阅读时间 5 分钟·2023 年 1 月 10 日

--

![](https://gitcode.net/OpenDocCN/towardsdatascience-blog-zh-2023/-/raw/master/docs/img/7f3a6d6bf2bca0dd5fc4e9b0dbb20ffc.png)

图片由 [aceofnet](https://unsplash.com/@aceofnet?utm_source=medium&utm_medium=referral) 提供,来源于 [Unsplash](https://unsplash.com/?utm_source=medium&utm_medium=referral)

# 背景

[交叉验证](https://www.youtube.com/watch?v=1rZpbvSI26c&t=29s)是构建任何统计或机器学习模型时的基本过程,并且在数据科学中非常普遍。然而,对于更小众的[时间序列分析](https://en.wikipedia.org/wiki/Time_series)和预测领域,*错误地*进行交叉验证是非常容易的。

在这篇文章中,我想展示将常规交叉验证应用于时间序列模型的问题以及缓解这些问题的常见方法。我们还将通过一个例子演示如何在 Python 中使用交叉验证进行*超参数调整*。

附加视频

# 什么是交叉验证?

交叉验证是一种通过在不同数据部分上训练和测试模型来确定最佳表现模型和参数的方法。最常见和基础的方法是经典的*训练-测试拆分*。在这种方法中,我们将数据拆分为一个*训练集*,用于拟合模型,然后在*测试集*上进行评估。

这个想法可以进一步扩展,通过多次进行*训练-测试拆分*,改变我们训练和测试的数据。这一过程就是**交叉验证**,因为我们使用每一行数据进行训练和评估,以确保我们选择的模型在所有可能的数据中最为稳健。

以下是使用`kfold` sklearn 函数进行交叉验证的可视化,其中我们将`n_splits=5`应用于美国航空乘客数据集:

> 数据[来自 Kaggle](https://www.kaggle.com/datasets/ashfakyeafi/air-passenger-data-for-time-series-analysis),使用 CC0 许可证。

作者的 GitHub Gist。

![](https://gitcode.net/OpenDocCN/towardsdatascience-blog-zh-2023/-/raw/master/docs/img/c2524728672d67047f5fc34d8ccdc664.png)

由作者在 Python 中生成的图。

正如我们所看到的,数据被分成了***5***个部分,每个部分包含一个新的*训练*和*测试*数据集,用于构建和评估我们的模型。

> 注意:另一种方法是将数据拆分为训练集和测试集,然后进一步将训练集拆分为更多的训练集和验证集。然后,您可以对不同的训练集和验证集进行交叉验证,并在测试集上获得最终模型性能。这就是大多数机器学习模型在实践中会发生的情况。

# 时间序列交叉验证

上述交叉验证对于预测模型并不是一种有效或有效的策略,因为它们具有时间依赖性。对于时间序列,我们总是预测未来。然而,在上述方法中,我们将**在比评估测试数据更远的时间的数据上进行训练**。这就是*数据泄漏*,应该尽一切可能避免。

为了克服这个困境,我们需要确保**测试集的索引总是高于训练集的索引(时间序列数据中的索引通常是时间)。** 这意味着我们的测试总是在模型拟合的数据的未来。

下图展示了使用`TimeSeriesSplit` sklearn 函数和我们上面编写的`plot_cross_val`函数的新交叉验证方法:

作者的 GitHub Gist。

![](https://gitcode.net/OpenDocCN/towardsdatascience-blog-zh-2023/-/raw/master/docs/img/57fbecd56d18813b7e617f1dc08d34da.png)

由作者在 Python 中生成的图。

测试集现在总是比训练集更向前的时间,从而避免在构建模型时任何数据泄漏。

# 超参数调整

交叉验证通常与超参数调整一起使用,以确定模型的最佳超参数值。让我们快速回顾一下这个过程的一个例子,针对一个预测模型,在 Python 中进行。

首先绘制数据:

> 数据[来自 Kaggle](https://www.kaggle.com/datasets/ashfakyeafi/air-passenger-data-for-time-series-analysis),使用 CC0 许可证。

作者的 GitHub Gist。

![](https://gitcode.net/OpenDocCN/towardsdatascience-blog-zh-2023/-/raw/master/docs/img/3af53709f3c5cce46b20cce879d99ab0.png)

由作者在 Python 中生成的图。

数据具有[**明确的趋势**](https://medium.com/towards-data-science/time-series-stationarity-simply-explained-125269968154)和[**高度的季节性**](https://medium.com/towards-data-science/seasonality-of-time-series-5b45b4809acd)。对于这个时间序列,一个合适的模型是[*霍尔特-温特斯指数平滑*](https://en.wikipedia.org/wiki/Exponential_smoothing#Triple_exponential_smoothing_(Holt_Winters))模型,它结合了趋势和季节性成分。如果你想了解更多关于霍尔特-温特斯模型的信息,可以查看我之前的帖子:

[](/time-series-forecasting-with-holt-winters-b78ffc322f24?source=post_page-----b083b869e42c--------------------------------) ## 霍尔特-温特斯时间序列预测

### 讨论和实现最强大且有用的指数平滑模型

[towardsdatascience.com

在以下代码片段中,我们使用[*网格搜索*](https://www.yourdatateacher.com/2021/05/19/hyperparameter-tuning-grid-search-and-random-search/)和交叉验证调整季节性平滑因子`smoothing_seasonal`,并绘制结果:

作者的 GitHub Gist。

![](https://gitcode.net/OpenDocCN/towardsdatascience-blog-zh-2023/-/raw/master/docs/img/b6d848328293bb7085646c64d6e5b089.png)

作者用 Python 生成的图表。

从图中可以看出,`smoothing_seasonal`超参数的最佳值似乎是**0.8**。

> 在这种情况下,我们手动进行了网格搜索交叉验证,但许多软件包可以为您完成这项工作。

如果你想了解更多关于超参数调整的领域,查看我之前关于使用[*贝叶斯优化*](https://en.wikipedia.org/wiki/Bayesian_optimization)通过*Hyperopt*包的文章:

[](/optimise-your-hyperparameter-tuning-with-hyperopt-861573239eb5?source=post_page-----b083b869e42c--------------------------------) ## Hyperopt 教程:优化超参数调整

### 使用 Hyperopt 进行贝叶斯超参数调整的简单解释和实现

[towardsdatascience.com

# 总结与进一步思考

在这篇文章中,我们展示了为什么不能仅仅对时间序列模型使用常规交叉验证,因为时间依赖性会导致数据泄漏。因此,在为预测模型执行交叉验证时,必须确保测试集**始终**在时间上晚于训练集。这可以很容易地完成,许多软件包也提供了帮助实现这种方法的功能。

由于文章的流动性,Gist 中的代码通常很难跟随,因此我建议查看我 GitHub 上的完整代码:

[](https://github.com/egorhowell/Medium-Articles/blob/main/Time%20Series/Time%20Series%20Tools/cross_validation.py?source=post_page-----b083b869e42c--------------------------------) [## Medium-Articles/cross_validation.py 在主分支 · egorhowell/Medium-Articles

### 我在我的 Medium 博客/文章中使用的代码。通过创建帐户来贡献 egorhowell/Medium-Articles 的开发...

[github.com](https://github.com/egorhowell/Medium-Articles/blob/main/Time%20Series/Time%20Series%20Tools/cross_validation.py?source=post_page-----b083b869e42c--------------------------------)

# 另一个事项!

我有一个免费的新闻通讯,[**数据分享**](https://dishingthedata.substack.com/),我在其中分享每周的提高数据科学家技能的小贴士。这里没有“废话”或“点击诱饵”,只有来自实践数据科学家的纯粹可操作的见解。

[](https://newsletter.egorhowell.com/?source=post_page-----b083b869e42c--------------------------------) [## 数据分享 | Egor Howell | Substack

### 如何成为更好的数据科学家。点击阅读《数据分享》,由 Egor Howell 编写,是一个包含…

newsletter.egorhowell.com](https://newsletter.egorhowell.com/?source=post_page-----b083b869e42c--------------------------------)

# 与我联系!

+   [**YouTube**](https://www.youtube.com/@egorhowell?sub_confirmation=1)

+   [**LinkedIn**](https://www.linkedin.com/in/egor-howell-092a721b3/)

+   [**Twitter**](https://twitter.com/EgorHowell)

+   [**GitHub**](https://github.com/egorhowell)

# 参考文献及进一步阅读

+   *预测:原理与实践:* [`otexts.com/fpp2/`](https://otexts.com/fpp3/holt-winters.html)

+   [`en.wikipedia.org/wiki/Cross-validation_(statistics)`](https://en.wikipedia.org/wiki/Cross-validation_(statistics))

+   [`en.wikipedia.org/wiki/Hyperparameter_optimization`](https://en.wikipedia.org/wiki/Hyperparameter_optimization)

+   [`github.com/hyperopt/hyperopt`](https://github.com/hyperopt/hyperopt)


# 如何利用大脑的季节性创建一年的数据科学自学计划

> 原文:[`towardsdatascience.com/how-to-create-a-1-year-data-science-self-study-plan-using-the-seasonality-of-your-brain-a2876d2f8b58?source=collection_archive---------2-----------------------#2023-07-06`](https://towardsdatascience.com/how-to-create-a-1-year-data-science-self-study-plan-using-the-seasonality-of-your-brain-a2876d2f8b58?source=collection_archive---------2-----------------------#2023-07-06)

## 你的大脑也有季节变化,你可以利用这些变化更高效地学习数据科学

[](https://madison13.medium.com/?source=post_page-----a2876d2f8b58--------------------------------)![Madison Hunter](https://madison13.medium.com/?source=post_page-----a2876d2f8b58--------------------------------)[](https://towardsdatascience.com/?source=post_page-----a2876d2f8b58--------------------------------)![Towards Data Science](https://towardsdatascience.com/?source=post_page-----a2876d2f8b58--------------------------------) [Madison Hunter](https://madison13.medium.com/?source=post_page-----a2876d2f8b58--------------------------------)

·

[关注](https://medium.com/m/signin?actionUrl=https%3A%2F%2Fmedium.com%2F_%2Fsubscribe%2Fuser%2F6a8c6841e521&operation=register&redirect=https%3A%2F%2Ftowardsdatascience.com%2Fhow-to-create-a-1-year-data-science-self-study-plan-using-the-seasonality-of-your-brain-a2876d2f8b58&user=Madison+Hunter&userId=6a8c6841e521&source=post_page-6a8c6841e521----a2876d2f8b58---------------------post_header-----------) 发表在 [Towards Data Science](https://towardsdatascience.com/?source=post_page-----a2876d2f8b58--------------------------------) ·6 分钟阅读·2023 年 7 月 6 日[](https://medium.com/m/signin?actionUrl=https%3A%2F%2Fmedium.com%2F_%2Fvote%2Ftowards-data-science%2Fa2876d2f8b58&operation=register&redirect=https%3A%2F%2Ftowardsdatascience.com%2Fhow-to-create-a-1-year-data-science-self-study-plan-using-the-seasonality-of-your-brain-a2876d2f8b58&user=Madison+Hunter&userId=6a8c6841e521&source=-----a2876d2f8b58---------------------clap_footer-----------)

--

[](https://medium.com/m/signin?actionUrl=https%3A%2F%2Fmedium.com%2F_%2Fbookmark%2Fp%2Fa2876d2f8b58&operation=register&redirect=https%3A%2F%2Ftowardsdatascience.com%2Fhow-to-create-a-1-year-data-science-self-study-plan-using-the-seasonality-of-your-brain-a2876d2f8b58&source=-----a2876d2f8b58---------------------bookmark_footer-----------)![](https://gitcode.net/OpenDocCN/towardsdatascience-blog-zh-2023/-/raw/master/docs/img/01a841b7027b40adcbcc593cf758dbba.png)

图片由 [Red Zeppelin](https://unsplash.com/@redzeppelin?utm_source=medium&utm_medium=referral) 提供,来源于 [Unsplash](https://unsplash.com/?utm_source=medium&utm_medium=referral)

当你在社交媒体上被充斥着那些在三个月内自学数据科学并被 FAANG 公司聘用的故事时,自学数据科学可能会显得遥不可及。

当你连一个简单的 Python 程序都无法在没有错误的情况下运行时,这些故事可能会让人感到最为沮丧。

在这样的时刻,自学数据科学并开始新的职业生涯似乎是一个不可能的梦想。当你过去尝试过一切来学习成功所需的概念和工具,却因缺乏承诺、进展或乐趣而在几周内放弃时,这种感觉也会显得毫无意义。

然而,如果我从成功自学四年的经验中学到了一件事,那就是成功来自于你最终学会如何与大脑合作——而不是与之对抗。这包括学习如何利用你大脑的季节性。

以下是如何利用你大脑的季节性来最大化你的学习潜力和效果,制定一年的数据科学自学计划。

# 你的一年季节性学习计划

大脑功能受季节的影响,和一天中的时间一样。[2016 年对大脑活动的年度节律进行了研究](https://www.pnas.org/doi/10.1073/pnas.1518129113),发现大脑的活动随着季节的变化而波动。研究发现,大脑在夏季的持续注意力任务中表现达到最大,但在冬季则表现达到最低。此外,大脑在秋季的工作记忆(工作记忆指的是[“计划、理解、推理和解决问题”](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC4207727/#:~:text=Abstract,reasoning%2C%20and%20problem%2Dsolving.))任务中表现达到最大,而在春季则表现达到最低。虽然还需要更多的研究来巩固这些发现,但我们仍可以利用它们来制定一个一年的数据科学学习计划,使你的大脑发挥最大潜力。

## 冬季:编程和数据结构

根据上述讨论的研究,冬天是你的大脑在持续注意力任务中的表现不佳的时候。然而,这并不意味着你不能开始通过编程教程熟悉数据库和数据结构。

从经验来看,我可以说你不应该每天花费超过三小时来学习编程或使用数据库。学习编程的最佳方式是全力投入到两到三小时的讲座中,然后将其余时间用于解决练习问题——这通常是你进行大部分学习的地方。

[## freeCodeCamp.org](https://www.youtube.com/@freecodecamp?source=post_page-----a2876d2f8b58--------------------------------)

### 学习编程是免费的。

[www.youtube.com](https://www.youtube.com/@freecodecamp?source=post_page-----a2876d2f8b58--------------------------------)

现在是时候开始通过 freeCodeCamp 的讲座来学习 Python(和/或 R)、SQL,甚至可能还有一些 JavaScript 的基础知识了。

然后,你的余下时间应该用于增加个人项目或完成 Leetcode 练习题。编码的应用过程是你学习最多的地方。编写代码、遇到错误、学习如何使用 StackOverflow 和进行修正,将巩固你在当天早些时候学到的概念。

## 春季:数据可视化

正如上述研究所提到的,春季是你大脑工作记忆的低点——这意味着现在是你开始攻克一些数据可视化概念并尽力将它们记住的时候了。

数据可视化可以被视为学习数据科学的“滑行”部分,这有充分的理由——你正在学习关于准确的数据表示、可视化类型和美学。然而,不要被误导以为这些东西不重要。恰恰相反。数据可视化是你讲述数据故事的地方,同时给出你对未来的预测。

在准备你的数据可视化之前,你需要建立一个确保你回答了所有[正确问题](https://www.mastersindatascience.org/learning/what-is-data-visualization/ways-to-improve/)的工作流程:你的可视化目标是什么?你的受众是谁?你需要在一个可视化中提供多少信息?你如何更有效地使用颜色和图表?

虽然你还没有数据清洗方面的知识(这将在秋季当你把所有东西整合成你的第一个完整数据分析时出现),但你可以开始利用冬季开发的编程技能来可视化一些预准备好的数据。查看[这个列表](https://opendatascience.com/12-excellent-datasets-for-data-visualization-in-2022/)以获取你可以用来开始构建可视化的数据集。

## 夏季:代数、统计、微积分

根据上述研究,夏季是你大脑在持续注意任务方面的最佳季节。这意味着你要在夏季攻克最难的数据科学概念。对大多数人来说,这意味着数学。

接下来的三个月是时候打开教科书和 Youtube 上的教程视频,开始掌握代数、统计和微积分的主题。这三个数学领域是大多数通用数据科学工作所需的(行业特定要求可能需要更高级的数学,如多变量微积分、微分方程和离散数学)。

[](https://www.youtube.com/@ProfessorLeonard?source=post_page-----a2876d2f8b58--------------------------------) [## Professor Leonard

### 这个频道致力于优质数学教育。完全免费,请尽情享用!视频按照…

[www.youtube.com](https://www.youtube.com/@ProfessorLeonard?source=post_page-----a2876d2f8b58--------------------------------)

Professor Leonard 是我最喜欢的代数、统计学和微积分的 YouTube 教师。他提供高质量的完整大学讲座,涵盖从前微积分到微分方程的内容。我唯一的遗憾是没有早点开始观看他的讲座。

## 秋季:将所有知识整合——数据分析

秋季是你大脑以最大工作记忆容量运作的时候,这意味着现在是将你过去一年学到的所有知识整合起来,完成你的第一次全面数据分析的时候。

数据分析遵循[步骤](https://www.lighthouselabs.ca/en/blog/the-five-stages-of-data-analysis),包括确定分析目标、收集、清理和分析数据,最后解释结果并得出结论。这将整合你之前学到的所有知识,最终目标是让你能够进行真正数据科学家的工作。

这里的目标不是让你做到完美。天哪,你已经花了九个月学习数据分析的基础知识——这时间并不算长。相反,目标是让你系统地思考数据分析中涉及的步骤,同时应用你在过去一年中学到的知识。你可能没有所有的答案,可能还有一些技术难题让你无法产生最好的分析结果。然而,你应该具备从你正在处理的数据中得出有洞察力结论所需的基本技能。

# 最后的想法

重申一下我在本文开头的信息是至关重要的:**这个计划的目标不是让你在一年内自学数据科学——而是让你培养一个一致的常规,帮助你在数据科学学习计划中定期进步。**

尽管这个计划看似为你在一年内成为数据科学家进行了精心布局,但实际情况并不总是如此——你会遇到障碍。

相反,这个计划不过是一个指南,帮助你在最佳的时间学习成为数据科学家所需的科目,以最大化大脑的自然波动。随着每一年,你可以确信,技能将更深入地植入你的大脑,这得益于我们大脑的季节性变化。

订阅以将我的故事直接发送到你的收件箱:[Story Subscription](https://madison13.medium.com/subscribe)

请通过我的推荐链接成为会员,以便无限制访问 Medium(这不会额外增加你的费用,我将获得少量佣金):[Medium Membership](https://madison13.medium.com/membership)

支持我的写作,通过捐款来资助创作更多类似的故事:[捐款](https://ko-fi.com/madisonhunter13)


# 如何在 Spacy 3.5 中创建自定义 NER

> 原文:[`towardsdatascience.com/how-to-create-a-custom-ner-in-spacy-3-5-c9942aab3c91`](https://towardsdatascience.com/how-to-create-a-custom-ner-in-spacy-3-5-c9942aab3c91)

## 自然语言处理

## 从文本中提取自定义实体的**快速教程**

[](https://alod83.medium.com/?source=post_page-----c9942aab3c91--------------------------------)![Angelica Lo Duca](https://alod83.medium.com/?source=post_page-----c9942aab3c91--------------------------------)[](https://towardsdatascience.com/?source=post_page-----c9942aab3c91--------------------------------)![Towards Data Science](https://towardsdatascience.com/?source=post_page-----c9942aab3c91--------------------------------) [Angelica Lo Duca](https://alod83.medium.com/?source=post_page-----c9942aab3c91--------------------------------)

·发表于[Towards Data Science](https://towardsdatascience.com/?source=post_page-----c9942aab3c91--------------------------------) ·5 分钟阅读·2023 年 4 月 25 日

--

![](https://gitcode.net/OpenDocCN/towardsdatascience-blog-zh-2023/-/raw/master/docs/img/82aa2292c77d5ad1b40112b7c88ed4f5.png)

照片由[Max Chen](https://unsplash.com/ja/@maxchen2k?utm_source=medium&utm_medium=referral)拍摄,[Unsplash](https://unsplash.com/?utm_source=medium&utm_medium=referral)

你是否厌倦了使用不符合你特定需求的通用命名实体识别(NER)模型?不必再找了!本文将指导你如何在 Spacy 3.5 中创建自定义 NER。

通过一些调整和训练数据,你可以拥有一个准确识别特定领域或用例实体的模型。告别“一刀切”的 NER 模型,迎接定制化的精准识别。让我们深入探讨吧!

我们将涵盖:

+   对 spaCy 及其竞争对手的**快速介绍**

+   问题设置

+   生成训练集

+   生成和训练模型

+   测试你的模型。

# 对 spaCy 及其竞争对手的**快速介绍**

如果你第一次听说 spaCy,请知道它是一个流行的开源自然语言处理(NLP)库,提供高效且快速的 NLP 功能,如分词、词性标注、实体识别、依赖解析等。SpaCy 的主要优势在于其速度和内存效率,使其成为大规模文本处理任务的理想选择。

spaCy 的一些替代品包括:

+   **NLTK**(自然语言工具包),是最古老和最全面的 NLP 库之一,提供广泛的文本分析工具,包括情感分析、词干提取和词形还原。

+   **Stanford CoreNLP** 支持包括英语、德语和法语在内的多种语言,具有强大的功能,如命名实体识别和共指解析。

+   **Spark NLP** 提供生产级、可扩展和可训练的最新 NLP 研究版本,支持 Python、Java 和 Scala。

# 问题设置

假设我们有一段文本,希望从中提取实体(人、地点等)。如果实体是经典的,比如人、地点、日期等,我们可以使用 spaCy 提供的预训练 NER 模型。

然而,预训练的通用模型无法再从我们的文本中提取特定实体。特定实体的例子包括狗的品种、细菌的名称等。我们需要一个适应我们领域的模型来识别这种实体类型。

下图展示了构建新的自定义 NER 模型的工作流程:

![](https://gitcode.net/OpenDocCN/towardsdatascience-blog-zh-2023/-/raw/master/docs/img/6fab28d05d87bf669186171a7cab85a8.png)

作者提供的图像

我们从一个通用的、已经**预训练的 NER 模型**开始,然后将其适应我们的领域,为模型提供额外的训练数据。

因此,首先需要构建一个训练集,其中的文本需要准确标注待提取的实体。然后,我们构建模型并用标注好的文本进行训练。

最后,我们使用新数据模型来预测新文本。

现在,让我们实际了解如何在 Python 和 spaCy 中实现所描述的工作流程。

# 生成训练集

首先定义你想要提取的实体类型。例如,你可以提取动物类型:狗、猫、马等。然后,将数据集拆分为训练集和测试集。仅对训练集进行标注。

按照以下步骤生成你可以作为 spaCy 输入的训练集:

+   首先,标注你的文本。使用 [`tecoholic.github.io/ner-annotator/`](https://tecoholic.github.io/ner-annotator/) 进行标注。

+   导出标注文件,命名为 `annotations.json`

+   打开`annotations.json`文件,删除开头部分的类定义。保持 JSON 一致性(如有必要,删除 `{}` 括号)。保存文件。在下面的示例中,删除`classes`:

![](https://gitcode.net/OpenDocCN/towardsdatascience-blog-zh-2023/-/raw/master/docs/img/fa8136c1b7f1660ad16e3f70f6192e28.png)

作者提供的图像

+   将 JSON 文件转换为 spaCy 格式。使用以下代码,最初由 [Zachary Lim](https://medium.com/u/e7a41934a7cc?source=post_page-----c9942aab3c91--------------------------------) 在他的 文章 中实现。

现在你的训练集保存在一个名为 `train.spacy` 的文件中。

# 生成和训练模型

要生成训练模型,请按照以下步骤操作:

+   访问 [`spacy.io/usage/training`](https://spacy.io/usage/training) 并填写表单以构建你的 `config_base.cfg` 文件

![](https://gitcode.net/OpenDocCN/towardsdatascience-blog-zh-2023/-/raw/master/docs/img/cf165b3a6c69ddd426c5238cc2497d9d.png)

从 Spacy 网站提取的图像

+   通过点击右下角的下载按钮下载文件。将模型保存在与`annotations.json`相同的文件夹中。

+   下载你将用来训练数据的基础模型。打开 `config_base.cfg` 查看你使用的是哪个预训练模型。以下示例下载 `it_core_news_lg model`:

```py
python -m spacy download it_core_news_lg
  • 运行以下命令以初始化模型:
python -m spacy init fill-config base_config.cfg config.cfg
  • 训练模型:
python -m spacy train config.cfg --output ./output --paths.train ./train.spacy --paths.dev ./dev.spacy

命令需要一个包含测试集的 dev.spacy 文件。如果你没有测试集,可以使用你的训练集(train.spacy)。

训练过程可能需要一些时间。在训练过程结束时,你应该看到类似于以下的输出:

图片由作者提供

测试你的模型

现在你的模型已经保存在 output/model-best 目录中。在 Python 脚本中按如下方式加载它:

nlp = spacy.load('output/model-best') 

现在使用你刚刚训练的模型来提取一些实体:

doc = nlp('My simple text')

spacy.displacy.render(doc, style="ent", jupyter=True) # display in Jupyter

摘要

恭喜!你刚刚学会了如何在 spaCy 中训练自定义模型进行命名实体识别(NER)!

在 Spacy 3.5 中创建自定义 NER 是一个简单的过程,只需正确的设置和编码知识。现在你已经知道如何使用 Python 和 Spacy 创建自定义 NER,你可以开始为你需要的任何应用程序开发模型。

相关文章

## 如何安装 Spark NLP

关于如何使 Spark NLP 在本地计算机上工作的逐步教程

towardsdatascience.com ## 如何向 Scikit-learn 中的预训练模型添加新数据

关于如何在 scikit-learn 中使用 warm_start=True 和 partial_fit() 的逐步教程

towardsdatascience.com [## 如何使用 Comet 注册表跟踪你的机器学习模型

关于 Comet 注册表功能的教程

heartbeat.comet.ml](https://heartbeat.comet.ml/how-to-use-the-comet-registry-to-track-your-machine-learning-models-4b18e7f61500?source=post_page-----c9942aab3c91--------------------------------)

如何创建热线图

原文:towardsdatascience.com/how-to-create-a-heat-line-plot-82f8038d1659

创建一个多维分段折线图

barrysmythTowards Data Science barrysmyth

·发表于Towards Data Science ·4 分钟阅读·2023 年 11 月 23 日

--

你可能听说过热图。这些二维的颜色编码网格可以用来表示几个(通常是 3 个)数据维度;例如,下面的热图展示了基于月份和年份的南落基山脉的平均温度。

显示 1950 年至 2020 年南落基山脉平均温度的热图.. [CC BY-SA 4.0]

当我需要可视化一些 Strava 数据时,脑海中浮现了热图。我想通过查看我在每场 42.2 公里的比赛中的配速和心率(区间),来比较我最近跑过的一些马拉松。这让我想到使用折线图,以每 1 公里的间隔作为x 值,以这些间隔中的配速作为y 值。但在这些间隔中,我的心率(作为努力的衡量标准)又如何处理呢?标准的 Matplotlib(我选择的绘图库)折线图没有提供编码这些额外维度的直接方法,因为线条颜色、厚度和样式等属性是固定的,并且应用于整个图表。对于我的用例,我希望能够调整每个线段的属性,例如,通过颜色显示我在比赛不同部分的心率区间。

这相当简单,而且是一个有用的例子,说明了何时以及为何超越 Matplotlib 的默认内置功能。基本思想是分别绘制组成折线图的每个线段。这样,我们可以单独控制每个线段的视觉属性,例如,在线段的颜色可以用来表示心率。

我们可以通过使用 Matplotlib 的[LineCollection](https://matplotlib.org/3.1.1/gallery/shapes_and_collections/line_collection.html)作为绘制多个具有不同颜色和宽度的线条(或线段)的便利方法。为此,我们需要将 (x, y) 点列表转换为每个线段的起始和结束坐标列表。例如,给定以下 (x, y) 点 …

(x0, y0), (x1, y1), …, (xn, yn)

… 我们需要生成如下的线段列表:

[ [(x0, y0), (x1, y1)], [(x1, y1), (x2, y2)], …, [(xn-1, yn-1), (xn, yn)] ]

这就是下面代码中第 12-16 行的目的;虽然上述内容是为了说明而稍作简化的表示。

示例代码生成一个简单的热线。

接下来,我们可以使用这些线段生成一个LineCollection,指定每个线段的颜色和宽度(linewidths),并使用.add_collection将它们添加到当前坐标轴(ax);上述第 19 行和第 20 行。

使用一些示例* x, y* 和心率(区域)数据,这会生成如下折线图。它接近我们想要的效果,但还不完全正确,因为各个线段没有正确连接。

第一次尝试热线,显示了马拉松每公里的平均配速,每个线段的颜色根据心率区域进行编码。

要解决这个问题,我们可以设置capstyle='round'来更优雅地连接线段;最终代码中的第 22 行和第 26 行。此外,作为一个额外的改进,我们还可以通过在背景中绘制稍微厚一点的单色线条来给线条添加边框;如下代码的第 21 行和第 22 行。这将生成一个更令人满意的最终效果。添加边框有助于突出显示热线,并且如果在同一坐标轴上绘制多个热线,还可以帮助分开它们。

更新后的热线,改进了线段之间的过渡,并增加了边框以强调和便于在同一坐标轴上绘制多个热线时的更好分离。

以下是更新后的代码,增加了控制边框外观(宽度和颜色)的参数。请注意,在这个示例中,我们没有调整折线图的宽度/厚度——所有线段的宽度都设置为固定值(10)——尽管提供的代码允许通过参数w进行这种调整。

改进后的代码生成一个热线版本,具有改进的线段连接和边框。

在这篇简短的文章中,我们讨论了如何使用 Python 和 Matplotlib 制作修改版的折线图,这种图允许调整每条折线图的段落(颜色和宽度),以编码最多 4 个数据维度,而不是通常的 2 个维度。这种类型的图在我分析马拉松比赛的工作中证明了其有用性,为了完整性,下面展示了一个完成的图示,并添加了各种注释以便于理解。

这是一个使用热力线的都柏林城市马拉松(DCM)跑者的比赛可视化示例。图中展示了比赛的平均配速以及比赛过程中达到的最小和最大配速。附带了都柏林赛道的高程图以供参考。

除了马拉松比赛之外,这种方法足够通用,可以在多种其他情况下用于可视化多维时间序列数据。

除非另有说明,所有图片和代码均由作者制作。

如何使用 Matplotlib 创建口红图

原文:towardsdatascience.com/how-to-create-a-lipstick-chart-with-matplotlib-2fde5412fee9

Matplotlib 教程

一个数据可视化示例,数值越低越好

Oscar LeoTowards Data Science Oscar Leo

·发布于数据科学探索 ·6 分钟阅读·2023 年 9 月 2 日

--

今天,我将向你展示如何创建一个口红图,以可视化那些数值越低越好的指标的进展。

当指标具有相似的趋势和主题但尺度不同时,这种方法非常适合。我的目标是分享一个信息,而不仅仅是一个图表。

我准备了一个关于死亡率和疾病的简单数据集,以便你可以专注于创建可视化。

数据来自于世界银行,在 Creative Commons 许可证下公开。如果你想了解更多,我在我的新免费通讯数据奇迹中写了关于该可视化的内容。

如果你喜欢这个教程,确保也看看我的其他教程。

Oscar Leo

Oscar Leo

Matplotlib 教程

查看列表8 个故事

让我们开始吧。

第 1 步 - 导入库

最简单的部分是导入所需的库,如 pandas 和 matplotlib。

import numpy as np
import pandas as pd

import seaborn as sns
import matplotlib.pyplot as plt

from PIL import Image
from matplotlib.lines import Line2D

恭喜,你刚刚完成了第 1 步! 🥳

第 2 步 — 创建 Seaborn 样式

接下来,我想创建一个颜色方案并选择一个字体。像CoolorsColorhunt这样的网站是寻找美丽颜色的好资源。

这是我为本教程创建 seaborn 样式的代码和设置。

FONT_FAMILY = "serif"
BACKGROUND_COLOR = "#FAE8E0"
TEXT_COLOR = "#33261D"
BAR_COLOR = "#EF7C8E"

sns.set_style({
    "axes.facecolor": BACKGROUND_COLOR,
    "figure.facecolor": BACKGROUND_COLOR,
    "text.color": TEXT_COLOR,
    "font.family": FONT_FAMILY,
    "xtick.bottom": False,
    "xtick.top": False,
    "ytick.left": False,
    "ytick.right": False,
    "axes.spines.left": False,
    "axes.spines.bottom": False,
    "axes.spines.right": False,
    "axes.spines.top": False,
})

我正在移除所有刻度线和线条,以创建一个干净的可视化,网格线对我们的口红图没有额外的信息。

步骤 3 — 读取数据

你可以像我在下面的代码中那样直接从 URL 读取 CSV。

df = pd.read_csv(
    "https://raw.githubusercontent.com/oscarleoo/matplotlib-tutorial-data/main/mortality-and-decease.csv"
)

下面是数据框的样子。

截图由作者提供

大多数值是不言而喻的,除了“per”,它显示每行的刻度。例如,最新的“孕产妇死亡率”值是每 10 万次分娩中有 223 例。

步骤 4 — 添加条形图

现在,是时候添加一些数据了。

我正在为 2000 年及最新值添加条形图。由于我的目标是展示每个值的相对减少,我将每一行除以其 2000 年的值。

这意味着 2000 年的每个条形图都会达到 1,因此这只是一个视觉辅助,不会添加任何额外的信息。

def add_bars(ax, x, width, alpha, label):
    sns.barplot(
        ax=ax, x=x, y=[i for i in range(len(x))], label=label,
        width=width, alpha=alpha,
        color=BAR_COLOR,
        edgecolor=TEXT_COLOR,
        orient="h"
    )

我创建一个图形,并像这样运行add_bars()函数。

fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(18, 2.7 * len(df)))

add_bars(
    ax=ax, x=df["2000"] / df["2000"],
    width=0.55, alpha=0.2, label="2000"
)
add_bars(
    ax=ax, x=df["latest_value"] / df["2000"],
    width=0.7, alpha=1, label="Latest"
)

到目前为止的代码结果如下。

图表由作者创建

让我们继续。

步骤 5 — 格式化坐标轴

每行的名称太长,无法不换行使用。这就是为什么我创建了以下函数,在几个地方添加\n到字符串中的原因。

def split_name(name, limit=20):
    split = name.split()
    s = ""
    for s_ in split:
        if len(s.split("\n")[-1] + s_) > limit:
            s += "\n" + s_
        else:
            s += " " + s_
    return s.strip()

我还想增加字体大小并移除不必要的信息,以使图表易于阅读。现在创建可视化的代码如下所示。

fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(18, 2.7 * len(df)))
...

ax.set(xlabel=None, ylabel=None, xticks=[])
ax.tick_params("y", labelsize=28, pad=32)
ax.tick_params("x", labelsize=20, pad=16)

ax.set_yticks(
    ticks=[i for i in range(len(df))],
    labels=[split_name(n, limit=19) for n in df["indicator_name"]],
    linespacing=1.7, va="center"
)

这是更新后的结果。

截图由作者提供

让我们添加一些额外的信息。

步骤 5 — 添加有用的信息

你总是希望确保用户理解他们看到的内容。目前,我们没有任何有用的信息。

首先,我想添加当前值,使用以下函数来实现。

def add_info_text(ax, row, index):
    value = round(row["latest_value"], 1)
    per = row["per"]
    year = row["latest_year"]
    text = "{:,} out of\n{:,} ({})".format(value, per, year)

    ax.annotate(
        text=text, 
        xy=(0.02, index), 
        color="#fff", 
        fontsize=24,
        va="center", 
        linespacing=1.7
    )

由于目的是展示每个指标相对于 2000 年的值的相对减少,我还有另一个函数显示每行的变化。

def add_change_text(ax, row, index):
    change = round(100 * row["change"], 1)
    text = "{:,}%".format(change)
    x = row["latest_value"] / row["2000"] + 0.02

    ax.annotate(
        text="{:,}%".format(change), xy=(x, index), fontsize=22,
        va="center",  linespacing=1.7
    )

我将这两个函数添加到一个 for 循环中。

fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(18, 2.7 * len(df)))
...

for index, row in df.reset_index().iterrows():
    add_info_text(ax, row, index)
    add_change_text(ax, row, index)

这是输出结果。

图表由作者创建

开始看起来不错。

步骤 6 — 添加标题和图例

在这一步,我只是使用一些内置的 Matplotlib 函数来添加标题和图例。由于我们在add_bars()中定义了label,所以大部分样式是自动的。

除了定义标题和图例外,我还使用Line2D添加了边框以实现视觉效果。

fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(18, 2.7 * len(df)))
...

line = Line2D([-0.33, 1.0], [-0.9, -0.9], color=TEXT_COLOR)
line.set_clip_on(False)
ax.add_artist(line)
title = "Lipstick Chart - Relative\nDecreases Compared\nto 2000"
plt.title(title, x=-0.32, y=1.11, fontsize=58, ha="left", linespacing=1.6)
plt.legend(bbox_to_anchor=(0.75, 1.14), loc='lower center', borderaxespad=0, ncol=1, fontsize=44, edgecolor="#FAE8E0")

现在图表看起来是这样的。

图表由作者创建

步骤 7 — 创建图像并添加填充

图表看起来有点拥挤,所以最后一步是添加一些填充。我通过将图形转换为 PIL 图像来实现这一点,使用如下函数。

def create_image_from_figure(fig):
    plt.tight_layout()

    fig.canvas.draw()
    data = np.frombuffer(fig.canvas.tostring_rgb(), dtype=np.uint8)
    data = data.reshape((fig.canvas.get_width_height()[::-1]) + (3,))

    plt.close() 
    return Image.fromarray(data)

这是添加填充的函数。

def add_padding_to_chart(chart, left, top, right, bottom, background):
    size = chart.size
    image = Image.new("RGB", (size[0] + left + right, size[1] + top + bottom), background)
    image.paste(chart, (left, top))
    return image

现在我们已经编写了所有创建数据可视化所需的代码。

这是使用所有函数创建最终口红图的完整代码片段。

fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(18, 2.7 * len(df)))

add_bars(
    ax=ax, x=df["2000"] / df["2000"],
    width=0.55, alpha=0.2, label="2000"
)

add_bars(
    ax=ax, x=df["latest_value"] / df["2000"],
    width=0.7, alpha=1, label="Latest"
)

ax.set(xlabel=None, ylabel=None, xticks=[])
ax.tick_params("y", labelsize=28, pad=32)
ax.tick_params("x", labelsize=20, pad=16)

ax.set_yticks(
    ticks=[i for i in range(len(df))],
    labels=[split_name(n, limit=20) for n in df["indicator_name"]],
    linespacing=1.7, va="center"
)

for index, row in df.reset_index().iterrows():
    add_info_text(ax, row, index)
    add_change_text(ax, row, index)

line = Line2D([-0.35, 1.0], [-0.9, -0.9], color=TEXT_COLOR)
line.set_clip_on(False)
ax.add_artist(line)

title = "Lipstick Chart - Relative\nDecreases Compared\nto 2000"
plt.title(title, x=-0.32, y=1.11, fontsize=58, ha="left", linespacing=1.6)
plt.legend(bbox_to_anchor=(0.75, 1.14), loc='lower center', borderaxespad=0, ncol=1, fontsize=44, edgecolor="#FAE8E0")

image = create_image_from_figure(fig)
image = add_padding_to_chart(image, 20, 50, 10, 50, BACKGROUND_COLOR)

这是完成的产品。

作者提供的截图

完成了!

结论

感谢你阅读本教程;我希望你学到了一些可以在数据可视化项目中重复使用的技巧。

如果你想查看更多教程和美丽的数据可视化,请在这里关注我,订阅数据奇迹以及在 Twitter 上关注oscarl3o

下次见。

如何用 Python 和 Matplotlib 创建极坐标直方图

原文:towardsdatascience.com/how-to-create-a-polar-histogram-with-python-and-matplotlib-9e266c22c0fa

Matplotlib 教程

创建一个吸引眼球并向观众讲述深刻故事的图表。

Oscar Leo数据科学前沿 Oscar Leo

·发表于数据科学前沿 ·10 分钟阅读·2023 年 8 月 24 日

--

作者创建的图表

你好,欢迎来到这个 Python + Matplotlib 教程,我将向你展示如何创建上面看到的美丽极坐标直方图。

极坐标直方图非常适合当标准条形图的数据值过多时。每个条形在中间变细的圆形形状使我们能在相同的区域内容纳更多信息。

一个很好的特点是,经过一圈后,最低值和最高值的可视化比较效果很好。

在本教程中,我使用了世界幸福报告中的数据和世界银行中的收入水平信息。这两个数据源都在 Creative Commons 许可下公开提供。

我的数据框包含 146 个国家和三列。

这就是它的样子。

作者截图

我会展示并解释创建可视化所需的每一行代码。如果你想跟着操作,你可以在这个GitHub 仓库找到我使用的代码和数据。

让我们开始吧。

第一步:准备

导入库

我们只需要大家都熟悉的标准 Python 库。PIL 不是强制的,但它是我处理图像的首选工具,我们在添加标志时会用到。

import math
import numpy as np
import pandas as pd

import seaborn as sns
import matplotlib.pyplot as plt

from PIL import Image
from matplotlib.lines import Line2D
from matplotlib.patches import Wedge
from matplotlib.offsetbox import OffsetImage, AnnotationBbox

唯一突出的地方是最后的几个特定 Matplotlib 导入。我会在教程后面详细讲解这些组件。

像往常一样,我使用 pandas 加载数据。

df = pd.read_csv("./hapiness_report_2022.csv", index_col=None)
df = df.sort_values("score").reset_index(drop=True)

让我们继续。

Seaborn 样式设置

接下来,我使用 Seaborn 来创建基础样式,通过定义背景、文本颜色和字体来完成。

font_family = "PT Mono"
background_color = "#F8F1F1"
text_color = "#040303"

sns.set_style({
    "axes.facecolor": background_color,
    "figure.facecolor": background_color,
    "font.family": font_family,
    "text.color": text_color,
})

set_style 还有几个参数,但这些四个是本教程中所需的。

我使用网站如colorhunt.co/coolors.co/来创建美丽的颜色调色板。

全局设置

我还添加了一些全局设置来控制总体外观。前四个定义了直方图中楔形的范围、大小和宽度。

START_ANGLE = 100 # At what angle to start drawing the first wedge
END_ANGLE = 450 # At what angle to finish drawing the last wedge
SIZE = (END_ANGLE - START_ANGLE) / len(df) # The size of each wedge
PAD = 0.2 * SIZE # The padding between wedges

INNER_PADDING = 2 * df.score.min()
LIMIT = (INNER_PADDING + df.score.max()) * 1.3 # Limit of the axes

内部填充在原点和每个楔形的起始点之间创建了距离。它在图形的中间打开了一个空间,我可以在其中添加标题。

模板代码

作为一名软件工程师,我力求编写可重用的代码,在数据可视化工作中也是如此。

这就是为什么我总是从创建几行模板代码开始,然后用可重用的函数扩展它们。

fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(30, 30))
ax.set(xlim=(-LIMIT, LIMIT), ylim=(-LIMIT, LIMIT))

for i, row in df.iterrows():
    bar_length = row.score
    name = row.country
    length = bar_length + INNER_PADDING
    start = 100 + i*SIZE + PAD
    end = 100 + (i+1)*SIZE
    angle = (end + start) / 2

    # Create variables here

    # Add wedge functions here

# Add general functions here

plt.axis("off")
plt.tight_layout()
plt.show()

在接下来的教程中,我将根据三个评论之一创建并添加函数和变量。

步骤 2:绘制楔形图

为了在 Matplotlib 中获得更多的视觉效果,使用底层组件而不是内置图形函数是有帮助的。

绘制楔形图

例如,与其使用plt.pie()来创建饼图,不如使用plt.patches.Wedge()来绘制单个部分。

这就是我创建以下函数的原因,该函数根据角度、长度、条形长度和颜色绘制一个楔形图。

def draw_wedge(ax, start_angle, end_angle, length, bar_length, color):
    ax.add_artist(
        Wedge((0, 0),
            length, start_angle, end_angle,
            color=color, width=bar_length
        )
    )

在模板代码中,我在“在此处添加函数”评论下添加draw_wedge(),如下所示。

bar_length = row.score
length = bar_length # + INNER_PADDING
start = 100 + i*SIZE + PAD
end = 100 + (i+1)*SIZE
.
.
.

# Add functions here    
draw_wedge(ax, start, end, length, bar_length, "#000")

我使用row.score来定义bar_length,以便条形图的可见部分之间有一个准确的尺寸关系。目前,我已经移除了INNER_PADDING以展示它的效果。

当我运行代码时,我得到以下图形。

图形由作者创建

正如你所见,我们还有很长的路要走,才能得到类似于开始时看到的极坐标直方图,但至少我们已经成功地绘制了楔形图。

我们在中间附近得到很多视觉伪影,所以让我们取消注释INNER_PADDING

这是我们得到的结果。

图形由作者创建

好得多。

添加颜色

接下来,我有一个简单的颜色函数,根据该国家的收入水平决定每个楔形的颜色。

def color(income_group):
    if income_group == "High income":
        return "#468FA8"
    elif income_group == "Lower middle income":
        return "#E5625E"
    elif income_group == "Upper middle income":
        return "#62466B"
    elif income_group == "Low income":
        return "#6B0F1A"
    else:
        return "#909090"

我将该函数作为输入传递给 draw_wedge 函数。

# Add functions here    
draw_wedge(ax, start, end, length, bar_length, color(row.income))

这是结果。

图形由作者创建

使用INNER_PADDINGcolor()后,没有留下奇怪的伪影。现在是时候添加解释我们所看到的内容的信息了。

步骤 3:添加标签

让我们为极坐标直方图中的每个条形添加标签。我希望每个条形显示国家的国旗、名称和幸福指数。

定义位置

当你在 Matplotlib 中向图表添加标志和文本时,需要计算正确的位置。

这通常很棘手,特别是当你有像极坐标直方图这样的不寻常形状时。

下面的函数接受楔形的长度和角度来计算一个位置。填充将位置从条形图中推开,以增加一些视觉空间。

def get_xy_with_padding(length, angle, padding):
    x = math.cos(math.radians(angle)) * (length + padding)
    y = math.sin(math.radians(angle)) * (length + padding)
    return x, y

我们可以使用这个函数来处理旗帜和文本。

添加旗帜

对于旗帜,我使用这些来自 FlatIcon 的圆角旗帜:www.flaticon.com/packs/countrys-flags。它们需要许可证,因此不幸的是,我不能分享它们,但你可以在其他地方找到类似的旗帜。

这是我的函数,用于在图表上添加旗帜。它接受位置、国家名称(对应正确文件的名称)、缩放和旋转。

def add_flag(ax, x, y, name, zoom, rotation):
    flag = Image.open("<location>/{}.png".format(name.lower()))
    flag = flag.rotate(rotation if rotation > 270 else rotation - 180)
    im = OffsetImage(flag, zoom=zoom, interpolation="lanczos", resample=True, visible=True)

    ax.add_artist(AnnotationBbox(
        im, (x, y), frameon=False,
        xycoords="data",
    ))

如果角度超过 270 度,我会改变旗帜的旋转方式。这发生在我们开始在图表的右侧添加条形图时。那时,旗帜位于文本的左侧,改变旋转使阅读更加自然。

现在,我们可以计算角度,使用get_xy_with_padding()并在图表上放置旗帜。

bar_length = row.score
length = bar_length + INNER_PADDING
start = START_ANGLE + i*SIZE + PAD
end = START_ANGLE + (i+1)*SIZE

# Add variables here
angle = (end + start) / 2
flag_zoom = 0.004 * length
flag_x, flag_y = get_xy_with_padding(length, angle, 0.1 * length)

# Add functions here
...
add_flag(ax, flag_x, flag_y, row.country, flag_zoom, angle)

flag_zoom参数决定旗帜的大小,并取决于分数。如果一个国家的分数较低,那么旗帜的空间就更小,我们需要将其稍微缩小一点。

图表由作者创建

太棒了。

添加国家名称和分数

为了添加国家名称和分数,我编写了以下函数。

就像旗帜一样,如果角度超过 270 度,我会改变旋转。否则,文本将会倒置。

def add_text(ax, x, y, country, score, angle):
    if angle < 270:
        text = "{} ({})".format(country, score)
        ax.text(x, y, text, fontsize=13, rotation=angle-180, ha="right", va="center", rotation_mode="anchor")
    else:
        text = "({}) {}".format(score, country)
        ax.text(x, y, text, fontsize=13, rotation=angle, ha="left", va="center", rotation_mode="anchor")

我们以与旗帜相同的方式计算文本的位置。唯一的不同是我们添加了更多的填充,因为我们希望它远离楔形。

bar_length = row.score
length = bar_length + INNER_PADDING
start = START_ANGLE + i*SIZE + PAD
end = START_ANGLE + (i+1)*SIZE

# Add variables here
angle = (end + start) / 2
flag_zoom = 0.004 * length
flag_x, flag_y = get_xy_with_padding(length, angle, 0.1 * length)
text_x, text_y = get_xy_with_padding(length, angle, 16*flag_zoom)

# Add functions here
...
add_flag(ax, flag_x, flag_y, row.country, flag_zoom, angle)
add_text(ax, text_x, text_y, row.country, bar_length, angle)

现在我们有了如下图表,并且它开始看起来好多了。

图表由作者创建

现在是告诉用户他们正在查看什么的时候了。

第 4 步:添加信息

我们已经添加了所有数据。现在是通过添加有用的信息和指导来使图表更具可读性的时刻。

绘制参考线

一种优秀的视觉辅助工具是参考线;它们在这里的效果和标准条形图上一样好。

这个想法是在线上绘制一个特定的分数,这间接地帮助我们比较不同的国家。

这是我的函数,用于绘制参考线。我重用draw_wedge()函数,从 0 到 360 度绘制一个楔形。

def draw_reference_line(ax, point, size, padding, fontsize=18):
    draw_wedge(ax, 0, 360, point+padding+size/2, size, background_color)
    ax.text(-0.6, padding + point, point, va="center", rotation=1, fontsize=fontsize)

我对每个分数运行一次函数,以绘制多个参考线。

# Add general functions here
draw_reference_line(ax, 2.0, 0.05, INNER_PADDING)
draw_reference_line(ax, 4.0, 0.05, INNER_PADDING)
draw_reference_line(ax, 6.0, 0.05, INNER_PADDING)

这是结果。

图表由作者创建

这会产生显著的差异。

添加标题

图表中心的空隙目的是为标题创建一个自然的位置。标题在中心位置是很不寻常的,这样可以立即吸引观众的兴趣。

添加标题的代码是标准的 Matplotlib 功能。

# Add general functions here
...
plt.title(
  "World Happiness Report 2022".replace(" ", "\n"), 
  x=0.5, y=0.5, va="center", ha="center", 
  fontsize=64, linespacing=1.5
)

这就是它的样子。

图表由作者创建

已经接近了,但我们还有最后一件事要做。

添加图例

观众无法理解颜色的含义,但我们可以通过添加图例来解决这个问题。

为了添加图例,我创建了以下函数,该函数接受要添加的标签、颜色和标题。

def add_legend(labels, colors, title):
    lines = [
        Line2D([], [], marker='o', markersize=24, linewidth=0, color=c) 
        for c in colors
    ]

    plt.legend(
        lines, labels,
        fontsize=18, loc="upper left", alignment="left",
        borderpad=1.3, edgecolor="#E4C9C9", labelspacing=1,
        facecolor="#F1E4E4", framealpha=1, borderaxespad=1,
        title=title, title_fontsize=20,
    )

我在“在此添加通用函数”下添加了功能,并与其他内容一起运行。

# Add general functions here
...

add_legend(
    labels=["High income", "Upper middle income", "Lower middle income", "Low income", "Unknown"],
    colors=["#468FA8", "#62466B", "#E5625E", "#6B0F1A", "#909090"],
    title="Income level according to the World Bank\n"
)

最终结果看起来是这样的。

图由作者创建

就这样。我们重新创建了你在顶部看到的漂亮的极坐标直方图。

你的整个主代码块现在应该如下所示。

fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(30, 30))
ax.set(xlim=(-LIMIT, LIMIT), ylim=(-LIMIT, LIMIT))

for i, row in df.iterrows():
    bar_length = row.score
    length = bar_length + INNER_PADDING
    start = START_ANGLE + i*SIZE + PAD
    end = START_ANGLE + (i+1)*SIZE
    angle = (end + start) / 2

    # Add variables here
    flag_zoom = 0.004 * length
    flag_x, flag_y = get_xy_with_padding(length, angle, 8*flag_zoom)
    text_x, text_y = get_xy_with_padding(length, angle, 16*flag_zoom)

    # Add functions here
    draw_wedge(ax, start, end, length, bar_length, color(row.income))
    add_flag(ax, flag_x, flag_y, row.country, flag_zoom, angle)
    add_text(ax, text_x, text_y, row.country, bar_length, angle)

ax.text(1-LIMIT, LIMIT-2, "+ main title", fontsize=58)

# Add general functions here
draw_reference_line(ax, 2.0, 0.06, INNER_PADDING)
draw_reference_line(ax, 4.0, 0.06, INNER_PADDING)
draw_reference_line(ax, 6.0, 0.06, INNER_PADDING)
plt.title("World Happiness Report 2022".replace(" ", "\n"), x=0.5, y=0.5, va="center", ha="center", fontsize=64, linespacing=1.5)

add_legend(
    labels=["High income", "Upper middle income", "Lower middle income", "Low income", "Unknown"],
    colors=["#468FA8", "#62466B", "#E5625E", "#6B0F1A", "#909090"],
    title="Income level according to the World Bank\n"
)

plt.axis("off")
plt.tight_layout()
plt.show()

本教程就到这里;恭喜你完成了。

结论

今天,我们学习了如何使用 Matplotlib 和 Python 创建一个漂亮的极坐标直方图。

极坐标直方图的创建非常简单,可以让我们在一个图表中包含更多的信息。

我在本教程中使用了《世界幸福报告》,但你可以将其更改为其他有启发性的数据集。

我希望你学到了一些技术,帮助你将图表创意变为现实。

如果你喜欢,确保查看我的其他 Matplotlib 教程。

Oscar Leo

Oscar Leo

Matplotlib 教程

查看列表8 个故事

下次见。

如何在 Python 中创建出版质量的热图

原文:towardsdatascience.com/how-to-create-a-publication-quality-heatmap-in-python-e4a7feb3c079

Python 中的热图教程指南

Stephen FordhamTowards Data Science Stephen Fordham

·发表于Towards Data Science ·6 分钟阅读·2023 年 8 月 28 日

--

介绍

热图可以作为信息图形用于传达定量数据。它们可以以易于阅读的格式传达数据,提供简洁的数据总结。

Python 有许多工具可以帮助制作出版质量的热图。这些工具包括 Seaborn 和 Matplotlib 库,以及 subplot2grid 库,这些库可以提供方便的方式来组织热图中的数据。

在本教程中,我将详细介绍制作热图的步骤,重点关注关键元素的存在/缺失。为此,我将使用一个包含虚构数据的 CSV 文件,这些数据涉及一组选定的细菌分离株。这些细菌菌株具有包括抗生素抗性基因、致病基因和某些胶囊类型等特征。热图将允许快速检查和比较各种菌株。

虽然所使用的示例集中在细菌菌株上,但所应用的技术可以更广泛地用于其他数据集,帮助你使用热图可视化数据。在接下来的教程中,所有图片均由作者提供。

目标

创建一个展示虚构细菌菌株关键基因存在/缺失的出版质量热图。

本教程将使用以下 CSV 文件,‘Bacterial_strain_heatmap_tutorial_data.csv’,可从Github 仓库获取。

开始

首先,需要一些导入语句来读取数据并稍后美化图形。我们将首先一起包含所有导入语句。

import pandas as pd 
import numpy as np
import matplotlib.pyplot as plt 
from matplotlib.colors import ListedColormap
import seaborn as sns 
from matplotlib.patches import Patch
from matplotlib.lines import Line2D
from matplotlib.patches import Rectangle

接下来,我们读取数据框,使用“Strain”列设置索引,并查看前 5 行。

df = pd.read_csv('Bacterial_strain_heatmap_tutorial_data.csv').set_index('Strain')
df.head()

从前 5 行可以看到数据以基因名称作为列进行组织,而索引则指向每个特定的菌株。一个从 0 到 5 的数字被用来指示特定基因的存在/缺失。这是因为我们将创建一个关键字来表示基因在菌株中的位置。

为了提高可读性,热图将被组织成 3 个部分。这些部分包括抗生素耐药基因部分、毒力基因部分和胶囊类型部分。为了实现这一点,我们首先需要将每个部分的关键列提取为一个单独的列表。

amr_columns_to_extract = ['OqxA', 'OqxB', 'emrAB', 'nfsA', 'nfsB', 'MexAB-OprM',
       'mdt Efflux pump genes','blaSHV-28', 'blaSHV-148', 'aac(3)-IId', 'aac(3)-IIe',
       "aac(6')-Ib-cr", "strA", "strB", 'blaCTX-M-14',
       'blaCTX-M-15', 'blaLAP-2', 'blaOXA-1', 'blaTEM-1B', 'dfrA1', 'dfrA14',
       'qnrB1', 'qnrS1', 'sul1', 'sul2', 'tet(A)']

virulence_columns_to_extract = ['clpK', 'traT',
       'fecIRABCDE', 'acrAB', 'acrREF', 'mrkD', 'iutA', 'entABCDEFHS', 'treC',
       'pgaABC', 'ureDABCEFG']

capsule_type = ['Capsule K2', 'Capsule K3','Capsule K123']

接下来,我们将为每个基因类别创建一个独特的颜色。为此,我们将创建一个字典,其中键匹配原始 CSV 文件中编码的值,而该键的值是一个唯一的十六进制颜色码。然后,我们将使用 matplotlib.colors 中的 ListedColormap 类,并在开头使用列表推导式创建一个颜色映射,这样我们就可以将其用作调用 sns.heatmap 时 cmap 参数的一个参数。

cmap_dict = {0: '#E9EEEC', 1: '#6B83A9',2: '#868B89'}
cmap = ListedColormap([cmap_dict[i] for i in range(0,3)])

cmap_dict_2 = {3: '#D43B34', 4: '#79D170',5: '#E9EEEC'}
cmap2 = ListedColormap([cmap_dict_2[i] for i in range(3,6)])

图形网格模式组织

下一步是决定热图的组织方式。我们将把热图分为 3 个部分,包括一个初始的抗生素耐药基因热图、一个毒力基因热图,最后一个胶囊类型热图。

为了将热图划分为这 3 个部分,我们将使用 matplotlib.pyplot 库中的 subplot2grid。

fig = plt.figure(figsize=(15, 4))

ax1 = plt.subplot2grid((1, 12), (0, 0), colspan=6)
ax2 = plt.subplot2grid((1, 12), (0, 6), colspan=3)
ax3 = plt.subplot2grid((1, 12), (0, 9), colspan=2)

首先,我们决定图形的大小,然后在 subplot2grid 函数调用中的第一个元组中设置行数和列数(整个图形的)。接下来,我们确定每个部分的起始位置,在第二个元组中设置,然后将指定的列数分配给 colspan 参数。这将产生如下所示的布局。

我们现在可以开始热图的第一个部分的工作。我们将开始处理 ax1。

在调用 sns.heatmap 时,我们首先从原始数据框中提取列 amr_columns_to_extract,将自定义颜色映射添加到 cmap 参数中,移除颜色条,添加线宽和颜色,并添加 alpha 值以使颜色更柔和。

然后,我们遍历 x 轴刻度标签,并为它们分配不同的字体,然后将其斜体化。根据惯例,基因名称在遗传学中是斜体的。我们还遍历 y 轴标签,并将 x 和 y 轴的标签大小设置为 10.5。

接下来的图例元素将是图形的关键。我们创建了一个 Patch 对象的列表,每个对象都分配了面色、边缘颜色和相应的标签。这个 Patch 对象的列表被赋值给变量名 'legends elements',然后可以作为参数传递给 ax1.legend 属性。这 5 个图例元素被组织成一行,传递给 ncol 参数的值为 5。

最后,我们使用axhline/axvline在第一个热图周围添加了黑色边框,以提高清晰度。

fig = plt.figure(figsize=(15, 4))

ax1 = plt.subplot2grid((1, 12), (0, 0), colspan=6)
ax2 = plt.subplot2grid((1, 12), (0, 6), colspan=3)
ax3 = plt.subplot2grid((1, 12), (0, 9), colspan=2)

ax1.set_title('Antimicrobial resistance genes',size=12, pad=30, fontname='Ubuntu')

ax1 = sns.heatmap(df[amr_columns_to_extract], cmap=cmap, cbar=False,linewidths=0.4, 
                  linecolor='black', alpha=0.8, ax=ax1)

for tick in ax1.get_xticklabels():
    tick.set_fontname('Ubuntu')
    tick.set_style('italic')
ax1.tick_params(axis='x', labelsize=10.5)

for tick in ax1.get_yticklabels():
    tick.set_fontname('Ubuntu')
ax1.tick_params(axis='y', labelsize=10.5)

ax1.set_ylabel("Strain\n", fontname='Ubuntu', fontsize=14)

legend_elements = [ Patch(facecolor='#868B89', edgecolor='#323436',
                         label='Present on chromosome'),
                   Patch(facecolor='#6B83A9', edgecolor='#323436',
                         label='Present on plasmid'),
                  Patch(facecolor='#D43B34', edgecolor='#323436',
                         label='Capsule K1'),
                  Patch(facecolor='#79D170', edgecolor='#323436',
                         label='Capsule K2/K57'),
                  Patch(facecolor='#E9EEEC', edgecolor='#323436',
                         label='Absent')]

ax1.legend(handles=legend_elements, 
          bbox_to_anchor=[0.9, -0.7], 
          title='Strain key', 
          ncol=5,
          frameon=False,
          prop={'size': 12, 'family': 'Ubuntu'},
          title_fontsize=12,
          handleheight=3, 
          handlelength=3,
          handletextpad=0.5,
          labelspacing=1.2,
          loc='center')

ax1.axhline(y=0, color='k',linewidth=3)
ax1.axhline(y=df[amr_columns_to_extract].shape[1], color='k',linewidth=3)
ax1.axvline(x=0, color='k',linewidth=3)
ax1.axvline(x=df[amr_columns_to_extract].shape[1], color='k',linewidth=3); 

这生成了热图的第一部分。

我们现在可以添加热图的第二部分代码。在这里,关键的毒力基因被提取并作为传递给sns.heatmap的第二个数据框表示,并分配给ax2变量。至关重要的是,对于第二个热图,我们移除了 y 轴刻度标签及其名称,因为这些信息已经通过第一个热图提供。水平和垂直线再次被添加,以增强图形的定义。

ax2 = sns.heatmap(df[virulence_columns_to_extract], cmap=cmap, cbar=False,linewidths=0.4, 
                  linecolor='black', alpha=0.8, ax=ax2)

ax2.set_title('Virulence genes',size=12, pad=30, fontname='Ubuntu')

for tick in ax2.get_xticklabels():
    tick.set_fontname('Ubuntu')
    tick.set_style('italic')
ax2.tick_params(axis='x', labelsize=10.5)

for tick in ax2.get_yticklabels():
    tick.set_visible(False)

ax2.tick_params(left=False)
ax2.set_ylabel('')    

ax2.axhline(y=0, color='k',linewidth=3)

ax2.axvline(x=0, color='k',linewidth=3)
ax2.axvline(x=df[virulence_columns_to_extract].shape[1], color='k',linewidth=3); 

这生成了以下热图。

现在可以添加热图的第三部分,使用相同的技术。这一次,从原始数据框中提取不同的列。

ax3 = sns.heatmap(df[capsule_type], cmap=cmap2, cbar=False,linewidths=0.4, 
                  linecolor='black', alpha=0.8, ax=ax3)

ax3.set_title('Capsule type',size=12, pad=30, fontname='Ubuntu')

for tick in ax3.get_xticklabels():
    tick.set_fontname('Ubuntu')
    tick.set_style('italic')
ax3.tick_params(axis='x', labelsize=10.5)

for tick in ax3.get_yticklabels():
    tick.set_visible(False)

ax3.tick_params(left=False)
ax3.set_ylabel('')

ax3.axvline(x=0, color='k',linewidth=3)
ax3.axvline(x=df[capsule_type].shape[1], color='k',linewidth=3); 

将代码全部组合起来,产生以下图形。通过简单的图形组织和与键的颜色协调,一张发布质量的热图已经准备好,供你下一次工作使用!

结论

通过使用 Python 绘图库和 matplotlib.pyplot 中的 subplot2grid 模块,可以生成信息丰富的热图,以提供全面的数据摘要。将热图与键结合使用也可以包含所包含的信息级别。虽然这里展示的示例特定于遗传学,但这些技术具有广泛的适用性,并且适用于其他数据集,只要原始数据被适当地编码。支持本教程的代码、csv 文件和图像可以在我的Github repository中找到。

如何使用 Plotly 和 Streamlit 创建一个简单的 GIS 地图

原文:towardsdatascience.com/how-to-create-a-simple-gis-map-with-plotly-and-streamlit-7732d67b84e2

数据可视化

Plotly 地图功能与 Streamlit 用户界面组件相结合,提供了一种创建 GIS 风格仪表盘的方法

Alan JonesTowards Data Science Alan Jones

·发布于 Towards Data Science ·14 分钟阅读·2023 年 12 月 22 日

--

约翰·霍普金斯大学的 COVID 数据仪表盘 — 图片由 Clay Banks 提供,来自 Unsplash

在 2020 年,我们都习惯了在媒体上看到比以前更多的数据。约翰·霍普金斯大学等创建的精密数据仪表盘成为了关于 COVID-19 扩散的新闻报道中的重要内容。

因此,如果疫情有任何积极的方面,也许就是更多的人能够理解数据的图形表示。

这种经验可能导致了图表和仪表盘在各种数据呈现中的更广泛使用,无论是医学、金融还是新闻中的其他数字。

除了展示感染率的指数增长和追踪 R 数字的图表外,我们还习惯了约翰·霍普金斯仪表盘上看到的地理信息类型。这展示了全球疫情的发展情况,但我们也都有这些系统的本地版本。

这些应用程序使用了复杂的软件来表示数据和地理位置,但我们可以尝试做一些类似的事情——虽然相对简单一些——使用 Plotly 和 Streamlit。

我们将从在 Plotly 中创建一些简单的地图开始,然后逐步使用 Streamlit 创建一个受约翰·霍普金斯大学启发的数据仪表盘。

最终的应用程序将类似于下图所示。

但在我们进入应用程序之前,我们需要探讨如何使用 Plotly 地图。

Plotly 地图

Plotly 支持几种地图类型,但我们将使用轮廓地图,因为它们简单易用且清晰。下面的图片展示了一个 Streamlit 应用,其中包含两种 Plotly 轮廓地图,表示澳大利亚各州的人口。左侧的是 Choropleth:一个带有颜色区域的地图,颜色代表某个值——在这种情况下是人口。

另一个是 Scatter Map,它使用圆圈来显示值,圆圈的大小和颜色都代表数据——在这种情况下,大小和颜色都代表人口。

要创建这样的地图,我们使用 Plotly Express 的 choroplethscatter_geo 函数。

数据来源:澳大利亚统计局——见注释

我们稍后将讨论如何创建这些地图,但你可能已经注意到在这个例子中使用大小来表示 Scatter Map 中值的一个缺点:北领地的人口相对于其他地区非常少,以至于圆圈不可见。Choropleth 中的颜色较浅,但由于它覆盖了整个州区域,因此非常明显。

本文中将使用的代码和数据文件都可以下载,并且演示应用也将发布在 Streamlit Community Cloud。我会在文章末尾提供一个 GitHub 仓库的链接,您可以查看和/或下载代码和数据,还会提供一个应用链接。当我在下文中提到文件或文件夹时,是指 GitHub 仓库。

获取数据

我们的最终仪表板将使用 CO2 排放数据。这些数据来源于 Our World in Data 的 GitHub 仓库中的表格(参见注释)。OWID 是一个很好的数据和分析来源,我已经使用过多次(例如,新的数据表明 2023 年是有记录以来最热的夏天)。

我从 OWID 复制的数据代表自 1750 年以来的各国 CO2 排放。原始数据包含的信息远远超出我们所需,所以我创建了数据的子集,并将其存储在应用中。您会在 data 文件夹中找到它们。

我们将从创建一个简单的应用开始,该应用会显示 2021 年的排放数据,使用 choropleth。下面的代码读取一个包含多年数据的 CSV 文件,并筛选出仅用于 2021 年的 Pandas 数据框。

df_total = pd.read_csv('data/co2_total.csv')
df_total_2021 = df_total[df_total['Year']==2021]

你可以从下面的图片看到数据的样子。Entity列包含了国家的名称;Code列是一个国际公认的三字母代码(ISO 3166–1 alpha-3),用于表示国家;Year显而易见,在这个数据框中总是 2021 年;Annual CO2 emissions列给出了每年的 CO2 排放量(以吨为单位)。

一旦我们有了数据,就可以从中创建地图。

着色图

如我所提到的,着色图是具有阴影区域的地图——这些区域可能是国家、州或地球上的其他定义部分,阴影的程度代表一个值。

我们将使用上面的 CO2 数据。首先,我们可以忽略年份——它总是 2021 年——排放量列的值将决定颜色,而 ISO 代码将指定地图上将被着色的区域。

我们首先定义几个变量:我们将从中读取数据的列以及数据的最大值和最小值。我们将使用这些值来设置地图上颜色的范围。

col = 'Annual CO₂ emissions'
max = df_total_2021[col].max()
min = df_total_2021[col].min()

现在我们可以使用 Plotly Express 的choropleth创建地图。如下面的代码所示,共有 5 个参数:

  1. 第一个是我们将要读取的数据框。

  2. locations设置为指代数据框中数据所对应地图部分的列。这里我们指定了包含国家 ISO 代码的“Code”列,例如安道尔为‘AND’或赞比亚为‘ZMB’(如上表所示)。这个代码将与地图上的相同代码(即位置)匹配。

  3. 第三个参数color指定了颜色的设置方式。这里我们使用“Annual CO₂ emissions”列,因此地图上由 ISO 代码指定的区域的颜色将根据排放水平设置。

  4. 下一个参数hover_name指定了可以找到国家名称的列(实际名称,而不是代码),当你的指针悬停在地图的该部分时,这个名称将会显示出来。

  5. 传递给函数的最终值是color_range,它表示用于表示排放水平的颜色范围。我们之前计算了最大值和最小值,这些值将映射到默认颜色范围的开始和结束。

fig = px.choropleth(df_total_2021, 
                    locations="Code",
                    color=col,
                    hover_name="Entity",
                    range_color=(min,max)
                    )

还有几个其他参数可供我们使用,但我们目前将依赖于这些参数的默认值。

地图在哪里?

你可能觉得我们在这里遗漏了什么。我一直提到地图,但它在哪里呢?答案是,虽然我们确实可以指定一张地图(如后面所见),但 Plotly 方便地包含了一些代表世界不同部分的地图——它默认使用的是整个世界的地图。这个默认的世界地图被划分为带有适当 ISO 代码的国家。

如果我们在 Streamlit 中使用plotly_chart(fig)运行上述代码,或者在标准 Python 环境或 Jupyter Notebook 中使用fig.show(),我们将得到如下所示的图形。

你可以看到,在这个截图中,鼠标悬停在美国上,这样我们可以看到那个国家的具体信息。这相当不错,而且没有花费太多精力。

但如果我们不想看到整个世界怎么办?

正如我所说,Plotly 很贴心地包括了内置地图。这些地图包括'world''usa''europe''asia''africa''north america''south america'

因此,简单地在函数调用中添加一个新参数可以将地图从默认的'world'更改为上述任何一个。这个参数是scope,在下面的代码中设置为'europe'

fig = px.choropleth(df_total_2021, 
                    locations="Code",
                    scope = 'europe',
                    color=col,
                    hover_name="Entity",
                    range_color=(min,max),
                    title = 'Europe'
                    )

结果地图如下面的截图所示,你可以在代码和地图中看到,我也为图表设置了一个标题。

欧洲排放

颜色范围仍然设置为整个世界,因此考虑到不同欧洲国家的排放量差异不大,这里很难区分它们。为了更好地了解欧洲的排放情况,我们可能希望将范围设置为仅关注的国家。

但我们在这里的目的是仅仅说明scope参数的使用。因此,这里有一组其他内置地图的图片。

创建这些地图的代码在仓库中。

然而,你并不局限于内置地图;你也可以提供你自己的地图,例如我们之前看到的澳大利亚州地图。

下一个代码面板包含一个完整的 Streamlit 应用程序,用于生成我们之前看到的澳大利亚颜色图层。

import streamlit as st
import pandas as pd
import plotly.express as px
import json

st.title("Population of Australian States")
st.info("Hover over the map to see the names of the states and their population")
f = open('geo/australia.geojson')
oz = json.load(f)

df = pd.read_csv('data/Australian Bureau of Statistics.csv')
fig = px.choropleth(df, geojson=oz, 
                    color="Population at 31 March 2023 ('000)",
                    locations="State", 
                    featureidkey="properties.name",
                    color_continuous_scale="Reds",
                    range_color=(0, 10000),
                    fitbounds = 'geojson',
                    template = 'plotly_white'
                   )
st.plotly_chart(fig)

在导入和介绍文本之后,我们从geo文件夹中打开一个名为australia.geojsonGeoJSON文件(见注释)。这是一个可以替代内置地图的地图文件。(要找到类似的地图,你可以进行互联网搜索,或者使用 GitHub,那里有几个 GeoJSON 地图的仓库。)

px.choropleth的调用中,我们看到一个额外的参数geojson,它标识了我们加载的地图。我们还需要指定一个地图上的特征,用于显示数据。我们可以通过检查 JSON 代码找到特征:oz['features'][0]将显示此地图上的特征。我们要找的是properties.name。因此,这将作为参数featureidkey的值。

如果我们就这样做,我们会得到一张整个世界的地图,颜色图层被隐藏在角落里!

没有设置边界的地图

因此,我们需要告诉颜色图层函数地图的边界:我们将参数fitbounds设置为'geojson',这将确保仅显示由地图定义的世界部分。

散点地图

散点图与分层色彩图非常相似,但不是用颜色填充区域,而是将有颜色的圆圈放置在这些区域中(类似于散点图)。

这里是一个欧洲国家人口的散点图。

欧洲人口——数据来源 Eurostat(见备注)

这段代码与分层色彩图非常相似,但除了指定表示数据的颜色外,我们还可以设置圆圈的大小。

下面是绘制上述地图的代码。

df = pd.read_csv('data/europop.csv')

fig = px.scatter_geo(df, scope = 'europe', 
                    color="Population (historical estimates)",
                    size="Population (historical estimates)",
                    locations="Code", 
                    hover_name = 'Entity',
                    color_continuous_scale="Purples",
                    range_color=(0, 100000000),
                    fitbounds = 'locations',
                    template = 'plotly_white',
                    title = "European populations"
                   )

st.plotly_chart(fig)

人口数据来自于 Eurostat(见备注),虽然我添加了三字母的 ISO 国家代码,而不是 EU 通常使用的两字母代码,并且仅使用了 2021 年的数据。以下是数据的一个示例。

欧洲人口数据:来源,Eurostat

从代码中可以看到,散点图的参数与分层色彩图非常相似,但在这里我将size参数设置为与colour相同的列,因此圆圈的大小也随人口数量而变化。

交互性

如果我们要构建一个仪表盘,那么我们希望包含一些交互性。在下面的截图中,我们回到我们的二氧化碳排放数据,并显示了两个使用不同投影的分层色彩图。我们不会详细讨论地图投影,但有一个值得提及的。右侧的分层色彩图将世界表示为地球仪,并且它的行为也像一个地球仪。如果你点击地球仪并移动鼠标,地球仪会旋转。这可能比有用更有趣,但玩起来非常令人满意。

正射投影

下面是地球仪表示的代码。

fig = px.choropleth(df_total[df_total['Year']==year], 
                    locations="Code",
                    color=col,
                    hover_name="Entity",
                    range_color=(min,max),
                    scope= 'world',
                    projection="orthographic",
                    color_continuous_scale=px.colors.sequential.Reds)
st.plotly_chart(fig)

除了我们已经看到的内容几乎一样之外,这里有一个新的参数projection,它被设置为'orthographic',正是这个正射投影视图给我们提供了一个互动地球仪。我们将在稍后看到这些代码的具体应用。

用户输入

然而,这个应用程序的主要用户交互是通过一个滑块条来实现的,滑块条允许我们选择一个年份以显示排放量。这是一个内置的 Streamlit 输入控件,是获取用户输入的有效方法。

下面是上面展示的应用程序的完整代码。

import streamlit as st
import pandas as pd
import plotly.express as px

st.set_page_config(layout="wide")

st.title("CO2 Emissions")
st.write("""The following maps display the CO2 emissions for a
            range of countries over a range of time""")

st.info("""Use the slider to select a year to display
           the total emissions for each country. 
           Scroll down to see an interactive 3D representation.""")

col1, col2 = st.columns(2)

df_total = pd.read_csv('data/co2_total.csv')
col = 'Annual CO₂ emissions'
max = df_total[col].max()
min = df_total[col].min()

# To get the whole range replace 1950 with the comment that follows it
first_year = 1950 #df_total['Year'].min()
last_year = df_total['Year'].max()
year = st.slider('Select year',first_year,last_year, key=col)

col1.write("""This map uses the 'Natural Earth' projection""")
fig = px.choropleth(df_total[df_total['Year']==year], 
                    locations="Code",
                    color=col,
                    hover_name="Entity",
                    range_color=(min,max),
                    scope= 'world',
                    projection="natural earth",
                    color_continuous_scale=px.colors.sequential.Reds)
col1.plotly_chart(fig)

col2.write("""This map uses the 'Orthographic' projection.
Click on the globe and move the pointer to rotate it.
""")

fig = px.choropleth(df_total[df_total['Year']==year], locations="Code",
                    color=col,
                    hover_name="Entity",
                    range_color=(min,max),
                    scope= 'world',
                    projection="orthographic",
                    color_continuous_scale=px.colors.sequential.Reds)
col2.plotly_chart(fig)

与之前的应用程序相比,这个应用程序的主要区别在于我们使用了一个涵盖 1750 年至 2021 年整个范围的数据文件,以及包含选择变量year的滑块条。这个变量然后在分层色彩图代码中用于从数据框中过滤数据,并显示所选年份的地图。

现在我们有了创建一个令人愉悦的仪表盘应用程序的基本要素。

仪表盘

我们已经涵盖了仪表盘中使用的大部分代码,除了些标准的 Streamlit 布局代码和一些 Pandas 过滤。

它有可选视图:左侧是一个地图,可以选择为分级图或散点图,通过单选按钮选择;右侧的多选框允许你选择一个或多个国家——这些国家的数据可以使用 Streamlit 选项卡以表格或图形形式查看。

Streamlit 使得这种功能变得非常简单,正如下面的代码所示,实现了国家选择和在两个选项卡中显示折线图或数据框的功能。

 # add/subtract from the selected countries
    c = st.multiselect('Add a country:', countries, default=['United States', 'China', 'Russia', 'Germany'])
    tab1, tab2 = col2.tabs(["Graph", "Table"])

    with tab1:
        # plot a line graph of emissions for selected countries
        fig = px.line(df_total[df_total['Entity'].isin(c)], x='Year', y='Annual CO₂ emissions', color = 'Entity')
        st.plotly_chart(fig, use_container_width=True)
    with tab2:
        table = df_total[df_total['Year']==st.session_state['year']]
        st.dataframe(table[table['Entity'].isin(c)], use_container_width=True)

排放量显示的年份可以从顶部的滑块条中选择,并且该年份的全球总数也显示在左上角,当年份发生变化时,数据视图会自动更新。

完整代码过长,无法在这里包括,但整个仪表板和展示所有上述代码的页面已发布在 Streamlit Cloud 上:CO2_Emissions_Dashboard

你也可以通过应用程序或点击直接链接这里访问包含所有代码和数据的 GitHub 存储库。

它真的算是一个 GIS 吗?

仪表板使用地理数据表示,但不允许进行深入分析,因此它更像是一个仪表板,而不是一个严肃的 GIS。但它确实允许你探索全球及时间范围内的 CO2 排放。

我们可以从图表中看到,例如,在工业革命(1750 年左右的 80 年间)期间,只有英国产生了严重的排放,但在 20 世纪,这些排放很快被美国和其他国家的排放所超越。这些图表还可以显示出一些国家在过去几年里成功减少了排放,而其他国家则没有。

表格视图允许我们按排放量对选定的县进行排序。你可能会认为人口较多的国家是最严重的排放者,就中国而言,你是对的,但看看印度和美国,你会看到完全不同的情况:印度的人口与中国相近,但排放量却低得多;而美国的排放量正在下降,而中国的排放量仍在增加。

不管它是否是真正的 GIS,我希望你发现这是一项如何在 Streamlit 中使用其映射元素创建一个有吸引力且有用的仪表板的有用练习。

感谢阅读。

备注

  • 除非另有说明,所有的图像、截图和代码均由作者创建。

  • 澳大利亚的人口数据来源于澳大利亚统计局,国家、省州和领地人口,,) ABS 网站,访问日期:2023 年 12 月 13 日,CC-BY 4.0 许可证。

  • 全球 CO₂ 排放数据来源于 我们的数据世界(OWID)co2-data GitHub 仓库创意共享 BY 许可证。这里使用的数据是基于 OWID 原始数据创建的,时间为 2023 年 9 月。

  • 澳大利亚州的 GeoJSON 数据来自 GitHub 上的 Click that Hood,检索日期为 2023 年 12 月 16 日,MIT 许可证。

  • 欧洲人口数据来自 欧盟统计局,检索日期为 2023 年 12 月 16 日,CC-BY 4.0 许可证。

  • 你可以在我的 网站 上找到更多我的作品,也可以在 Medium 上关注我,或通过我的偶尔 通讯 进行关注。

如何使用 Python 创建合成社交网络

原文:towardsdatascience.com/how-to-create-a-synthetic-social-network-using-python-eff6451cab14

了解图生成算法在创建合成图中的应用

Rohith TejaTowards Data Science Rohith Teja

·发表于 Towards Data Science ·阅读时间 4 分钟·2023 年 1 月 31 日

--

Maxim Berg 拍摄于 Unsplash

有时找到合适的图数据集来评估算法可能是一个令人畏惧的任务。有多种选择可用,通常需要花费相当长的时间来逐一查看。

即使你找到了完美的图数据集,你还需要验证其使用、共享和隐私政策。

这将引导我们进入这个讨论。

有没有更快速的方法来找到用于评估的图数据集?幸运的是,有!我们需要一些叫做合成图的数据集。这些图数据集是人工生成的。

合成图的需求

直接来说,第一个理由是方便。

生成你自己的数据集是很方便的,不用担心如下问题:

  • 控制图的大小

  • 隐私和数据共享限制

  • 图数据格式

这些理由可能并不能说服所有人,也有一些人需要现实世界的图数据用于一些合成图无法完成的图分析任务。

但我认为合成图提供了一种快速测试算法的方法,在开发框架后,你可以将其部署到现实世界的场景中。

合成图究竟是什么?

合成图是使用图生成模型生成的。它们被构造得尽可能模拟真实世界的图。

存在生成合成图的算法。其中一些包括:

  1. Erdös-Rényi 模型——在此模型中,我们从一个预定义的节点集合开始,例如N。现在我们使用概率在节点之间添加边以生成图形。概率是固定的,对图中所有节点对都是相同的。因此,更高的概率会生成密集图,而较低的概率则生成稀疏图。这是一个简单的模型,与现实世界的图形相差甚远。

  2. Watts-Strogatz 模型—— 这是一种生成具有小世界特性的图形的方法。在这个背景下,小世界被定义为具有较小的路径长度和较高的聚类系数的东西。

路径长度:这是测量图中两个节点之间距离的指标。路径长度越短,节点之间的距离越近。

聚类系数:它衡量节点的邻居之间的连接紧密程度。

该模型从一个固定数量节点的规则网格状结构开始,并将边连接到节点的最近邻居。它使用重连概率,这意味着某些边会随机从一个位置移除并添加到其他地方。

它用于建模现实世界的网络,这些网络是类似小世界的社交网络和交通网络的实例。

3. Barabasi-Albert 模型—— 这个图生成模型遵循“富者越富”的原则。该模型将新节点连接到已经有更多连接的现有节点。这导致图中出现几个高度连接的节点和一些连接较少的节点。它用于建模无标度网络,如互联网和社交网络。

合成社交网络

我们可以使用NetworkX Python 库生成一个合成社交网络。

让我们查看合成图如何使用所有三种模型,并观察它们的效果。

下面是使用NetworkX生成图形的代码:

import networkx as nx
import numpy as np
import matplotlib.pyplot as plt

# Erdos-Renyi model
G1 = nx.erdos_renyi_graph(n=50, p=0.2, seed=42)

# Watts-Strogatz model
G2 = nx.watts_strogatz_graph(n=50, k=5, p=0.4, seed=42)

# Barabasi-Albert model
G3 = nx.barabasi_albert_graph(n=50, m=5, seed=42)

# plot side by side
fig, ax = plt.subplots(1, 3, figsize=(15, 5))
# add title to each plot
ax[0].set_title('Erdos-Renyi')
ax[1].set_title('Watts-Strogatz')
ax[2].set_title('Barabasi-Albert')
nx.draw(G1, ax=ax[0])
nx.draw(G2, ax=ax[1])
nx.draw(G3, ax=ax[2])
plt.show()

图形可视化如下所示:

合成图比较(作者提供的图片)

调整超参数会生成截然不同的图形。

对于社交网络,我们希望添加节点特征和节点标签。这可以使用faker Python 库完成,它生成虚假姓名。

让我们看看它的样子。

from faker import Faker
import networkx as nx
import matplotlib.pyplot as plt

faker = Faker()

names = []

# generate 10 unique names
for i in range(10):
    # Generate a random name 
    name = faker.name()
    # Append the name to the list
    names.append(name)

# Barabasi-Albert model
G = nx.barabasi_albert_graph(n=10, m=5, seed=42)

# add the names to the graph
mapping = {i: names[i] for i in range(len(names))}
G = nx.relabel_nodes(G, mapping)

fig, ax = plt.subplots(figsize=(3, 2), dpi=300)
nx.draw(G, with_labels=True, node_size=50,width=0.1, font_size=3.5)
plt.show()

合成社交网络

合成社交网络现在已经有了标签。每个节点代表一个人,他们的名字作为节点标签。

可以生成其他节点属性,如每个人的年龄、性别和职业,并更新图形。

现在我们有一个完全加载的合成社交网络,可以用于进行图分析任务。

感谢阅读,干杯!

想要联系?

请通过LinkedInTwitterGitHub网站联系我!

如何在 Python 中创建时间序列网络图可视化

原文:towardsdatascience.com/how-to-create-a-time-series-network-graph-visualization-in-python-8da0227fa4b8

使用 Plotly 和 NetworkX 展示网络如何随时间演变

Claudia NgTowards Data Science Claudia Ng

·发布于 Towards Data Science ·阅读时间 9 分钟·2023 年 10 月 26 日

--

可视化与选定医生的连接随时间变化

在这篇文章中,你将学习如何在 Python 中创建时间序列网络可视化,展示网络中的连接如何随时间发展,如上方动画所示。网络数据对于揭示连接非常有效,而时间序列数据可以用来揭示基础数据中的趋势和异常。

在本文中,我们将使用 Kaggle 上关于医疗服务提供者欺诈的 数据集 创建一个示例。(该数据集目前在 Kaggle 上的许可证为 CC0: 公众领域。请注意,该数据集可能不准确,仅用于本文演示目的)。

我们将结合提供的链接中的数据,获取与给定主治医生相关的欺诈性索赔的集群,然后根据索赔开始日期绘制该医生与其他实体之间的连接(最多到某个跳数)随时间的变化。

确保在你的 Python 虚拟环境中安装了 PlotlyNetworkX

如果你想学习一种有效的方法来可视化网络如何随时间增长,请继续阅读!

数据集发现

该数据集包含总共 82,063 名不同的医生,其中 20,592 名至少有一个欺诈性索赔。虽然大多数这些医生只有少数几个欺诈性索赔,但也有少数人是极其活跃的欺诈者。

前 25% 的医生至少有 5 个索赔,最严重的例子是一位有 2,534 个欺诈性索赔的医生!

尽管在医生层面计算这些统计数据很容易,但时间序列网络可视化是理解该医生实施的诈骗规模及其时间范围的一个好方法。

查找连接

给定一个医生,如果我们能够找到其连接,并且这些连接也有欺诈性索赔,最多跳数为一定范围内,会怎样呢?

为此,我们将编写一个函数,该函数接受包含索赔数据的 DataFrame、AttendingPhysician(字符串)和 MaxHops(整数)作为参数,并返回一个 DataFrame,包含与给定 AttendingPhysician 相关的欺诈性索赔,最多跳数为 MaxHops。

从一个医生开始,该函数执行以识别与集群连接的具有欺诈性索赔的受益人和医生。只要迭代次数等于或小于 MaxHops 并且尚未收敛,即前一轮迭代的集群大小小于这一轮迭代的集群大小,函数将继续执行。

以下是一个代码片段,演示如何执行此操作:

def get_fraud_cluster(df, AttendingPhysician, MaxHops):
    """
    Returns fraudulent claims associated with the given AttendingPhysician 
    and beneficiaries served up to a maximum number of hops or convergence

        Parameters:
            df (DataFrame): A Pandas DataFrame with claims data.
            AttendingPhysician (str): ID of attending physician to start with as the root node.
            MaxHops (int): Maximum number of hops.

        Returns:
            df (DataFrame): A Pandas Dataframe containing fraudulent claims associated 
            with supplied AttendingPhysician up to a maximum number of hops away.
    """
    # Initial variables
    prev_cluster_size = 0
    current_cluster_size = 1
    i = 0

    # Add initial physician to set_physicians
    set_physicians = set([AttendingPhysician])
    set_beneficiaries = set()

    while prev_cluster_size < current_cluster_size and i < MaxHops:

        prev_cluster_size = len(set_physicians) + len(set_beneficiaries)

        # Get fraudulent physicians with claims associated with fraudulent beneficiaries
        fraud_physicians = df[
            (df.BeneID.isin(set_beneficiaries)) &
            (df.PotentialFraud == 1)
        ]["AttendingPhysician"].unique()
        new_fraud_physicians = set(fraud_physicians).difference(set_physicians)

        # Update set with new fraudulent physicians
        set_physicians.update(new_fraud_physicians)

        # Get fraudulent beneficiaries with claims associated with fraudulent physicians
        fraud_beneficiaries = df[
            (df.AttendingPhysician.isin(set_physicians)) &
            (df.PotentialFraud == 1)
        ]["BeneID"].unique()
        new_fraud_beneficiaries = set(fraud_beneficiaries).difference(set_beneficiaries)
        # Upate set with new fraudulent beneficiaries
        set_beneficiaries.update(new_fraud_beneficiaries)

        # update variables
        i += 1
        current_cluster_size = len(set_physicians) + len(set_beneficiaries)

    # Final dataset of physicians and beneficiaries in sets with fraudulent claims
    df_results = df[
        (df.AttendingPhysician.isin(set_physicians)) &
        (df.BeneID.isin(set_beneficiaries)) &
        (df.PotentialFraud == 1)
    ]

    return df_results

现在你已经知道如何获取与给定医生相关的欺诈性索赔连接,让我们选择一个随机医生。以 AttendingPhysician PHY379763 为例,该医生只有 2 个索赔,但这两个索赔都是欺诈性的。

在使用上述函数找到该医生的连接(最多 2 跳)后,结果数据集中包含 1,483 个欺诈性索赔,涉及 920 个独特受益人和 2 位其他医生(总共 3 位不同的医生)。

想象一下,观察到一个仅有 2 个索赔的医生的连接,竟然可以如此迅速地扩展,实在令人惊讶!

创建时间序列网络图

在收集完连接数据后,我们现在准备创建可视化。这个过程可以分为 4 个步骤:i) 构建主图,ii) 按时间间隔绘制图形,iii) 按时间间隔绘制图表,iv) 配置图形。

I. 构建图形

首先,使用 networkx 初始化一个空图,并从 DataFrame 中添加数据。在这个示例中,图将包含两组节点:一组是受益人节点,另一组是医生节点。

给节点标注 {type, ID (BeneIDAttendingPhysician), 索赔开始日期 (ClaimStartDt)} 是很重要的,因为稍后我们将使用日期来选择节点,以便从我们的月度图表中删除这些节点。

接下来,添加连接之间的边。在这种情况下,边存在于连接的受益人和医生之间,这些边的标签包含 {受益人 ID (BeneID), 医生 ID (AttendingPhysician), 索赔开始日期 (ClaimStartDt)}。同样,我们将使用日期来选择边,以便从月度图表中删除这些边。

以下是本节的内容:

# Initialize empty networkx graph
G = nx.Graph()

# Build graph from df
for row in df.itertuples():
  # Add nodes for beneficiaries
  G.add_node(row.BeneID, label=("BeneID", row.BeneID, row.ClaimStartDt))
  # Assign a different node label (for node color later on) for root Physician
  if RootPhysicianID and row.AttendingPhysician == RootPhysicianID:
      G.add_node(row.AttendingPhysician, label=("RootPhysician", row.AttendingPhysician, row.ClaimStartDt))
  else:
      G.add_node(row.AttendingPhysician, label=("AttendingPhysician", row.AttendingPhysician, row.ClaimStartDt))
  # Add edges
  G.add_edge(row.BeneID, row.AttendingPhysician, label=(row.BeneID, row.AttendingPhysician, row.ClaimStartDt))

# Get positions
pos = nx.spring_layout(G)

print("Finished building graph (G).")

请注意,RootPhysicianID 是我们开始时使用的给定医生 ID,我们为该节点使用不同的标签,以便在绘图时可以给它分配不同的颜色。

此外,使用 nx.spring_layout() 函数获取节点和边缘的位置也很重要,以便在绘图步骤中将这些位置提供给每月图形,以便节点和边缘可以在时间上保持一致的位置。

II. 每个间隔绘制图形

接下来,决定可视化图表的时间间隔和频率。在这种情况下,我们将使用按月的间隔,因此生成的图表将为数据集中的每个月创建滑块。

首先,通过抓取数据集中唯一的月份来确定需要循环的月份:

# Grab unique months in dataset 
months = [
    i.to_timestamp("D") for i in pd.to_datetime(
        df["ClaimStartDt"]
    ).dt.to_period("M").sort_values().unique()
]

然后,遍历每个月以复制原始图表(G),并创建一个新的图表(new_G),我们可以修改它以显示该月的数据。然后,识别并删除在此月份之后出现的节点和边缘,基于标签中的索赔开始日期。删除这些节点、边缘和任何自环:

# Loop through months
for month in months:
    month_str = month.strftime("%Y-%m")

    # Duplicate original graph for a new graph in current month
    new_G = G.copy()

    # Extract nodes and edges to remove 
    nodes_to_remove = [
        node for node in new_G.nodes() 
        if "label" in new_G.nodes[node] 
        and new_G.nodes[node]["label"][-1] >= month
    ]
    edges_to_remove = [
        (u, v) for u, v, data in new_G.edges(data=True) 
        if "label" in data and data["label"][-1] >= month
    ]

    # Remove self-loops
    for u, v, label in new_G.edges(data=True):
        if u == v:
            new_G.remove_edge(u, v)

    # Remove nodes and edges from new graph
    new_G.remove_edges_from(edges_to_remove)
    new_G.remove_nodes_from(nodes_to_remove)

现在我们为每个月创建了一个图形,是时候绘制它们并将它们添加到 Plotly 图中。

III. 绘制图形

要使用 Plotly 绘制网络图形,首先从前一步中存储在变量 pos 中的位置选择节点和边缘的 x 和 y 坐标。

我们还会为不同的节点添加额外的文本和/或颜色,然后将这些边缘和节点添加到图形中。我们将选择并定义不同节点的颜色,并将其存储在一个名为 color_mappings 的字典中。键对应于节点标签,值是颜色的十六进制代码。这里有一个有用的 网站,可以选择搭配好的颜色。

以下是绘制图形的函数:

def plot_graph(G, pos, fig):
    # Define colors with hex codes
    color_mappings = {
        "BeneID": "#66bfbf", # teal green
        "AttendingPhysician": "#fecea8",  # light orange
        "RootPhysician": "#f76b8a", # pinkish red
    }

    # Select nodes
    node_x = [pos[node][0] for node in G.nodes() if node in pos]
    node_y = [pos[node][1] for node in G.nodes() if node in pos]

    # Select edges
    edge_x = []
    edge_y = []
    for u, v, label in G.edges(data=True):
        if u in pos and v in pos:
            x0, y0 = pos[u]
            x1, y1 = pos[v]
            edge_x.extend([x0, x1, None])
            edge_y.extend([y0, y1, None])

    # Additional text to show on hover
    node_text = [data["label"] for node, data in G.nodes(data=True)]
    node_colors = [color_mappings.get(data["label"][0]) for node, data in G.nodes(data=True)]

    # Add nodes and edges to plot
    fig.add_trace(
        go.Scatter(
            x=edge_x, 
            y=edge_y,
            mode="lines",
            line=dict(color="gray"),
            name="Edges"
        )
    )
    fig.add_trace(
        go.Scatter(
            x=node_x,
            y=node_y,
            mode="markers",
            marker=dict(size=10, color=node_colors),
            text=node_text,
            hoverinfo="text",
            opacity=0.8,
            name="Nodes",
        )
    )

    return fig

上面的函数很有用,将用于绘制每个月的图形,如下所示:

# Plot new graph for this month and add to figure
fig = plot_graph(G=new_G, pos=pos, fig=fig)

IV. 配置图形

到目前为止,我们有一个包含所有节点和边缘的主图(G),多个包含所有节点和边缘(含索赔开始日期到该月的 new_G)的每月图形,以及一个包含多个数据集的 Plotly 图(fig),绘制了每个月的网络图形。

接下来,我们需要添加滑块的步骤。滑块上应该有每个月的步骤,我们需要配置每个步骤显示的数据显示。

记住,我们每个月有两个数据集,一个是节点,一个是边缘,因此我们将相应地设置这些数据集的可见性:

# Configure slider
steps = []
for i, month in enumerate(months):
    idx = i * 2   # multiply by 2 because one trace is for nodes and one is for edges
    month_str = month.strftime("%Y-%m")
    visibility = [False] * len(months) * 2   # Make all traces not visible by default
    visibility[idx] = True   # Make trace for nodes visible
    visibility[idx + 1] = True    # Make trace for edges visible
    step = dict(
        method="update",
        label="Month: %s" % month_str,
        args=[{"visible": visibility}],
    )
    steps.append(step)

# Make first graph visible
fig.data[1].visible = True

在为滑块创建步骤后,我们可以更新图形的布局,添加滑块、标题和其他杂项配置:

# Update figure settings
fig.update_layout(
    title="Time Series Graph Visualization for Root PhysicianID = %s" % RootPhysicianID,
    showlegend=False,
    hovermode="closest",
    xaxis=dict(showgrid=False, zeroline=False, showticklabels=False),
    yaxis=dict(showgrid=False, zeroline=False, showticklabels=False),
    sliders=[
        dict(
            active=0,
            steps=steps,
            pad={"t": 50}
        ),
    ],
)

剩下的就是显示图形,你的时间序列网络图应该会出现!

最后,如果你希望将交互式图保存为 HTML 文件,请使用以下命令:

plotly.offline.plot(fig, filename="./graph_viz.html")

结论

网络可视化非常强大,加入时间序列组件使其更加信息丰富!可视化网络的增长在各种领域都有许多应用,从理解疾病的起源到跟踪服务的扩展等等。

我们看到的例子涉及到查找与选定医生相关的欺诈性索赔,最多两步远,然后绘制这些数据,查看该集群的欺诈行为如何发展。

总结一下,你学会了如何找到与给定实体的连接,最多经过的跳数,并通过以下方式创建时间序列网络图:

i) 构建主要图(G),

ii) 为每个时间间隔(在此情况下为每月)绘制新图,

iii) 绘制每个月的图表(多个 new_G),并将其添加到图形(fig)中,

iv) 配置图形的滑块。

看到医生和受益人之间的联系随着时间的推移而增长,真是令人着迷!

在下面评论你的想法和学习经历。你会尝试用这些数据做什么?在创建可视化过程中你得出了什么见解?

你在分析师角色中感到困惑吗?你可能会感到一种冒名顶替综合症,试图在没有博士学位或计算机科学背景的情况下学习新技能。

我在 2020 年转行数据科学,没有技术背景,我希望帮助其他人做到这一点。我创建了一个 免费五天数据科学启动课程电子邮件课程 来启动你的职业生涯。🚀

www.ds-claudia.com

如何使用 R 创建艺术地图

原文:towardsdatascience.com/how-to-create-an-artistic-map-using-r-2a4932a23d10

OpenStreetMap + 数据可视化 = 艺术

Irfan Alghani KhalidTowards Data Science Irfan Alghani Khalid

·发表于 Towards Data Science ·5 分钟阅读·2023 年 1 月 24 日

--

阿姆斯特丹地图。地图由作者创建。

TL;DR

本文将展示如何使用 R 创建艺术地图。我们将从 OpenStreetMap 获取数据,并使用 ggplot 库自定义视觉效果。

介绍

当你想到地图时,第一个浮现在脑海中的可能就是它仅仅是一条信息。通过它,我们可以导航到想去的地方。

随着时间的推移,通过自定义视觉效果,地图可以成为一件艺术品,而不会失去关于位置的任何见解。

在开放数据存在之前,制作地图可能需要通过调查和创建地图本身花费大量时间。

但幸运的是,通过使用像 OpenStreetMap 这样的开放数据和编程,我们可以根据我们的视觉偏好创建自己的地图。如果你愿意,你还可以将地图艺术卖给市场,获得额外收入。

在这篇文章中,我将展示如何使用 R 创建艺术地图。不再赘述,我们开始吧!

实现

加载库

要构建地图,我们需要几个库。这些库包括:

  • osmdata 用于下载和加载 OpenStreetMap 数据。

  • tidyverse 提供数据处理和可视化的功能。

  • sf 库用于将 OSM 数据转换为 sf 格式

如果你没有那些库,你可以先用这些代码行下载它们:

install.packages('osmdata')
install.packages('sf')
install.packages('sysfonts')

如果你遇到超时错误,你可以执行这段代码来延长超时:

options(timeout=600)

下载这些库后,你可以使用这些代码行来加载库:

library(osmdata)
library(tidyverse)
library(sf)

加载数据

正如我提到的,我们将使用 OpenStreetMap 数据获取空间数据,如道路、建筑物、河流等。我们将使用一个名为 osmdata 的库。现在让我们使用这些代码行检索街道和水体的空间数据:

streets <- getbb('Amsterdam Netherlands') %>%
  opq() %>%
  add_osm_feature(key='highway',
                  value=c('motorway', 'primary',
                          'secondary', 'tertiary')) %>%
  osmdata_sf()

small_streets <- getbb('Amsterdam Netherlands') %>%
  opq() %>%
  add_osm_feature(key='highway',
                  value=c('residential', 'living_street',
                          'service', 'footway')) %>%
  osmdata_sf()

rivers <- getbb('Amsterdam Netherlands') %>%
  opq() %>%
  add_osm_feature(key='natural',
                  value=c('water')) %>%
  osmdata_sf()

正如你从上述代码中看到的,我将空间数据分为三种不同的变量,如街道、小街道和河流。分开变量的原因是为了便于自定义视觉效果,如颜色、大小和可见性。你将在之后理解这个目的。

每个变量都有类似的工作流程。

  • 首先,我们调用 getbb 函数来检索所需位置的边界框。在这种情况下,我们将检索阿姆斯特丹的边界框。

  • 然后,我们初始化 Overpass API,通过调用 opq 函数从指定的边界框中查询数据。

  • 接下来,我们调用 add_osm_feature 来通过指定相应的键和值检索特定的空间数据。存在许多键值对,你可以查看 OSM 维基页面 这里

  • 最后,你可以使用 osmdata_sf 函数将数据转换为 sf 格式。

构建地图

数据加载后,现在进入有趣的部分。我们将使用 ggplot 库来构建地图。首先,让我们通过以下代码行可视化原始地图:

ggplot() +
  geom_sf(data=streets$osm_lines,
          inherit.aes = FALSE,
          color = '#58b9c7',
          size = .5,
          alpha = .6) +
  geom_sf(data=small_streets$osm_lines,
          inherit.aes = FALSE,
          color = '#239dc1',
          size = .2,
          alpha = .6) +
  geom_sf(data=rivers$osm_polygons,
          inherit.aes = FALSE,
          fill='#f8cc0a',
          size = .2)

基本上,我们调用 ggplot 函数来创建图表。然后,我们使用 geom_sf 函数可视化空间对象。我们对每个变量应用这个函数,因为它们有不同的样式格式。结果如下:

这张图片由作者创建。

地图显示出来了!但看起来不太好。我们现在可以通过添加 coord_sf 函数来裁剪坐标,如下所示:

ggplot() +
  geom_sf(data=streets$osm_lines,
          inherit.aes = FALSE,
          color = '#58b9c7',
          size = .5,
          alpha = .6) +
  geom_sf(data=small_streets$osm_lines,
          inherit.aes = FALSE,
          color = '#239dc1',
          size = .2,
          alpha = .6) +
  geom_sf(data=rivers$osm_polygons,
          inherit.aes = FALSE,
          fill='#f8cc0a',
          size = .2) +
  # ADD THIS CODE
  coord_sf(ylim=c(52.35, 52.40),
           xlim=c(4.83, 4.97),
           expand=FALSE)

结果如下:

这张图片由作者创建。

看起来很棒!最后,我们可以使用 theme 函数调整视觉效果。你可以根据自己的喜好更改任何参数。以下是实现这一点的代码:

ggplot() +
  geom_sf(data=streets$osm_lines,
          inherit.aes = FALSE,
          color = '#58b9c7',
          size = .5,
          alpha = .6) +
  geom_sf(data=small_streets$osm_lines,
          inherit.aes = FALSE,
          color = '#239dc1',
          size = .2,
          alpha = .6) +
  geom_sf(data=rivers$osm_polygons,
          inherit.aes = FALSE,
          fill='#f8cc0a',
          size = .2) +
  coord_sf(ylim=c(52.35, 52.40),
           xlim=c(4.83, 4.97),
           expand=FALSE)
  # ADD THIS CODE
  theme(
    plot.background = element_blank(),
    panel.background = element_rect(color='#08435f', 
                                    fill='#08435f',
                                    size = 20),
    panel.grid = element_blank(),
    axis.ticks = element_blank(),
    axis.text  = element_blank(),
  )

我们通过这段代码做的是去除坐标轴刻度并改变背景颜色。结果如下:

这张图片由作者创建。

不错!如果你想添加标题和边框,你可以使用 labs 函数来设置标题。同时,别忘了调整 theme 函数。以下是代码:

ggplot() +
  geom_sf(data=streets$osm_lines,
          inherit.aes = FALSE,
          color = '#58b9c7',
          size = .5,
          alpha = .6) +
  geom_sf(data=small_streets$osm_lines,
          inherit.aes = FALSE,
          color = '#239dc1',
          size = .2,
          alpha = .6) +
  geom_sf(data=rivers$osm_polygons,
          inherit.aes = FALSE,
          fill='#f8cc0a',
          size = .2) +
  coord_sf(ylim=c(52.35, 52.40),
           xlim=c(4.83, 4.97),
           expand=FALSE) +
  # ADD THIS CODE
  labs(title = 'Amsterdam') + 
  theme(
    plot.background = element_rect(fill="#f8cc0a",color='#f8cc0a'),
    plot.margin = unit(c(0.3, 0.4, 0.5, 0.4), 'cm'),
    panel.background = element_rect(color='#08435f', 
                                    fill='#08435f',
                                    size = 20),
    panel.grid = element_blank(),
    axis.ticks = element_blank(),
    axis.text  = element_blank(),
    plot.title = element_text(size=18, 
                              face='bold',
                              hjust=.5,
                              color='#08435f'),
  )

结果如下:

这张图片由作者创建。

太棒了!最后,如果你想保存你的图表,你可以使用 ggsave 函数来设置你喜欢的图片尺寸。

ggsave('<YOUR FINLE NAME>.png', width=1920, height=1080, units='px')

最后说明

做得好!现在你已经学会了如何使用 R 创建艺术风格的地图。我希望这篇文章能帮助你更多地了解数据可视化,特别是地理空间数据方面。

如果你对我的文章感兴趣,你可以在 Medium 上关注我,以获取更多有关数据科学和机器学习的文章。

感谢阅读我的文章!

参考资料

[1] www.youtube.com/watch?v=TDVXff6i3kw&t=986s

[2] cran.r-project.org/web/packages/osmdata/vignettes/osmdata.html

[3] wiki.openstreetmap.org/wiki/Map_features

如何创建一个有效的自学计划,以成功自学数据科学

原文:towardsdatascience.com/how-to-create-an-effective-self-study-routine-to-teach-yourself-data-science-successfully-6248c7ec3a53?source=collection_archive---------0-----------------------#2023-02-25

这里介绍了如何设置一个你实际会坚持的自学计划,同时学习数据科学

Madison HunterTowards Data Science Madison Hunter

·

关注 发表于 Towards Data Science ·9 分钟阅读·2023 年 2 月 25 日

--

照片由 Miquel Parera 拍摄,来源于 Unsplash

在自学数据科学的过程中,你会发现自己处于两个假设的情境之一:在一个自动扶梯上或在一个楼梯上。

那些通过制定他们需要学习的所有内容的课程表、设定日常、每周、每月和每年的目标以帮助指导他们的进展并持续调整和修改学习过程以提高效率的人,将发现自己处于电梯上。换句话说,他们正慢慢但稳步地朝着目标前进,始终保持向上的运动(虽然电梯有时会停运,但你仍然可以保证向上而不是向下)。

相反,那些没有制定计划、没有设定目标或学习目标来指导他们进展,也不调整学习方法的人,注定会被困在一个楼梯上,可能会向上或向下移动。虽然在楼梯上没什么问题,但你很容易就会放弃,开始向下走,而在电梯上,你只能向上走。

明白了吗?

但是什么让你处于电梯上而不是楼梯上呢?其实很简单。

你在自学数据科学时持续向上的进步和成功归结于你的日常

日常安排是那些能够自学数据科学并在该领域开展职业生涯的少数有纪律的人和那些认为可以“随意应对”但最终因没有根深蒂固的日常安排而早早失败的人之间的区别。

建立自学日常只需要四个步骤。我在过去的 6 年里在接受高等教育和帮助自己学习数据科学时使用了这些步骤,发现这四个步骤足以创建一个能带来成功的日常安排。这四个步骤简单而不花哨,但足以创建一个你可以轻松坚持的日常。

第一步:每天安排相同的时间

选择学习的时间不仅仅是关于生产力,还涉及保持健康的工作和生活平衡(稍后会详细讲到)。

数据科学及其所有相关主题需要相当多的心理严谨性来进行自学。这就是为什么你选择学习的时间应该始终是你精力最充沛、集中力最强的时候。

例如,我选择在早晨进行所有需要精神集中的学习(例如任何基于微积分的内容),因为那时我最有活力和专注。下午则留给较少消耗脑力的学习或其他不需要太多脑力的任务(对于我来说通常是学习数据可视化、统计学或进行个人项目)。通过在一天中最有生产力的时段学习,我确保自己尽可能高效和有效。

每个人最有生产力的时间都不同。有些人白天工作效率最高,而有些人晚上工作最佳。关键是每天选择相同的时间来安排学习。不仅如此,你的大脑需要开始将特定的时间与学习关联起来。这将帮助你保持更长时间的专注,并使你在学习开始的那一刻就做好准备(而不是因为需要激励自己学习而在手机上滚动 10 分钟)。

正如我之前提到的,选择特定的时间来学习数据科学对于保持健康的工作与生活平衡也很重要。例如,今天(星期五),我在早上完成了所有的微积分学习,这意味着我今天已经完成了本周所有学习任务,并留下了整整半天的时间来放松、处理琐事、陪伴我的狗,并享受一个稍长的周末。

步骤 2. 选择一个学习地点

你学习的地点和每天在固定时间学习一样重要。

研究显示,你的工作地点可能对你的健康产生身体和心理影响。这特别涉及到那些在床上工作的人,这可能导致人体工学上的噩梦以及与卧室产生错误的关联——即床不仅仅是用来睡觉的——这可能对你的睡眠时间表和生产力造成严重影响。

最关键的是,你的大脑非常擅长将特定的地点与特定的活动联系起来。这就是为什么许多大学生即使可以在家学习也会选择去校园学习。因此,在自学某个主题时,让你的大脑知道每天在特定的位置坐下就是要集中注意力的时候,这一点很重要。

选择学习地点时也应该采用相同的理念。拥有一个预定的学习地点告诉你的大脑,每次你坐在那里时,就是工作的时候。这个地方应该有良好的照明,足够的空间放置所有学习材料,以及尽可能好的符合人体工学的设置。

例如,我已经在同一张书桌上学习了超过四年。每当我坐到书桌前时,我就知道这意味着要开始工作,无论是学习、写作还是做其他类型的工作。这张书桌非常大,所有需要的东西都在触手可及的范围内,这减少了我在学习期间离开书桌的次数,有助于保持专注。

我创建一个优秀学习空间的一个最爱窍门是将所有的数据科学书籍放在附近。无论是什么原因,把这些书籍放在我身边能带给我很多灵感,因为我知道不久后我将能够完成这些章节中的所有内容,无论是创建讲述故事的视觉图,做多变量微积分,还是了解如何为生产环境编写 Python 代码。

步骤 3:在坐下来之前知道你要学习什么

鉴于你正在阅读关于制定学习规律的文章,我假设你已经做了艰苦的工作,并创建了用于自学数据科学的课程表。太好了!这意味着你实际上已经完成了第三步。

在坐下来之前知道你要学习什么是维持规律的重要部分。花费 10-20 分钟浏览材料并尝试寻找视频讲座是浪费宝贵学习时间的麻烦事,这实际上会让你对学习时间感到厌烦。相反,每周日晚花 30 分钟到 1 小时的时间来准确计划你接下来一周每天的学习内容。这应包括每天将要涵盖的所有主题(要现实,不要让自己感到过于疲惫),以及排队准备视频讲座、资源和你要处理的代码文件。这也是给电子设备充电、整理学习空间,或许准备一些便捷的学习小吃的时间,以保持你的精力。

例如,我每周日都会遵循这个 30 分钟的规则,写下即将到来的一周的任务清单。这样,到了周一早上 6 点,我就不会东找西找地试图搞清楚自己要做什么,而是可以直接进入工作状态,待咖啡因发挥作用。

我给你的一个大建议是:一旦你完成了当天的学习任务,就立即停止。就在此时停止工作并离开——你已经完成了当天的任务。是的,你可以总是说再做一个微积分问题或者再做一个 Leetcode 挑战。然而,这样会把你带到精疲力竭的困境中——相信我。所以,完成当天的所有学习后就停止,这样能保持你新鲜的状态,继续保持学习的动力。这是确保你保持常规,不让自己过度劳累的好方法。

步骤 4:把它写进日历

我不知道是什么原因,但将例行程序放入我的日历中总是让我更容易坚持。即使我知道每天会有什么,完成待办事项的满足感足以让我继续坚持这个例行程序。

现在你已经确定了学习的时间、地点和内容,你需要把这些都放入你的日历中。日历事件应包含所有这些信息,并包括一个学习待办事项的清单,每次完成任务时都可以勾选。

一旦某事被写入你的日历,它就会变得重要,需要完成。你会潜意识地开始围绕学习时间来优先安排日常任务,因为你的学习时间是“主宰”。即使你每天做的事情完全一样,大脑的奖赏中心也需要通过完成待办事项来获得内啡肽的冲击。所以,别忘了把所有学习内容都放入你的日历中。

这不仅是维持习惯的重要提示,而且保持学习日历有点像保持日记或日志本。这是跟踪时间、查看你学习各个主题花费了多长时间的好方法,也是年底时提醒你自己付出了多少工作的好方法。

一些快速提示和故障排除

  • 当你由于外部因素无法完成当天的学习任务时,找出 10 分钟来复习一些概念。 决定你是否设立(更重要的是维持和继续)一个例行程序的主要因素是你是否每天坐下来学习。然而,不要被误导成相信你必须每天进行相同量的学习。无论你在某一天有时间学习 10 小时还是 10 分钟,重要的是保持这种心态,每天都要花些时间(无论多少)来学习数据科学。有些日子会比其他日子更好,但关键是你以某种方式完成你的例行程序,以保持你的思维训练,预期每天都有一个例行任务要完成。

  • 你的例行程序不适合你?不要害怕做出改变。 我在自学整个在线大学学位过程中学到的最重要的事情之一是,某一时刻对你有效的东西可能并不总是有效——这没关系。我们的义务会改变,无论是工作、家庭还是其他原因。因此,如果你发现维持例行程序变得困难,并且连续错过几天,别害怕改变一下,尝试一个适合你当时生活的不同例行程序。记住,一个每天都容易做到的例行程序是一个你能在必要时维持多年的例行程序。

  • 不要害怕使用奖励来保持自己的进度。 不知何故,使用奖励来保持日常规律被视为负面行为。在我的经验中,人类是以奖励为驱动的生物,这也是在自我学习过程中使用奖励的重要原因。例如,在完成一个微积分作业后,我会奖励自己,无论是喝一杯奶茶、带着我的狗散步,还是享受一些无负担的社交媒体时间。不要认为奖励是有害的——而是庆祝你完成了一周的学习或达到了一个重要的里程碑。

订阅以将我的故事直接发送到您的收件箱:故事订阅

请成为会员,通过我的推荐链接无限访问 Medium(您不会额外支付费用,我将获得少量佣金):Medium 会员

通过捐款支持我的写作,以资助创作更多像这样的故事:捐款

如何使用 Seaborn 和 Matplotlib 创建美丽的年龄分布图(包括动画)

原文:towardsdatascience.com/how-to-create-beautiful-age-distribution-graphs-with-seaborn-and-matplotlib-including-animation-68ebabff41bd

图形教程

可视化国家和地区的人口统计数据

Oscar LeoTowards Data Science Oscar Leo

·发表于 Towards Data Science ·9 分钟阅读·2023 年 6 月 22 日

--

作者创建的图形

今天,我想展示如何使用 matplotlibseaborn 创建像上面那样美丽的年龄分布图。

年龄分布图对于可视化国家或地区的人口统计信息非常优秀。它们非常吸引人,但默认的 Seaborn + Matplotlib 图形对于我们来说效果不够好。

在本教程中你将学到:

  • 如何创建 Seaborn 风格

  • 改进坐标轴使其易读且信息丰富

  • 添加标题和美丽的图例

  • 将 Matplotlib 图形转换为 PIL 图像并添加外部填充

  • 创建多个图像的网格(如上例所示)

  • 创建时间推移动画以展示人口随时间的变化

如果你想跟着做,你可以在这个 GitHub 仓库中找到数据和我的代码。

让我们开始吧。

数据的快速浏览

原始数据来自 人口估计与预测 数据集,该数据集由世界银行提供,并遵循 Creative Commons Attribution 4.0 许可。它包含 1960 年至 2021 年的实际值和直到 2050 年的官方预测。

在 GitHub 仓库中,我处理了数据并创建了四个单独的 CSV 文件,以便你可以专注于制作图形。

两个文件,一个用于女性,一个用于男性,包含绝对人口数。

作者提供的截图

其他两个值描述了总人口的比例。例如,在下面的截图中,你可以看到 1960 年巴林只有 0.336%的人在 70-74 岁之间。

作者截图

数据集有 17 个年龄组,00-0405-0910-1415-1920-2425-2930-3435-3940-4445-4950-5455-5960-6465-6970-7475-7980+

在 250 多个国家和地区中,你可以自由创建你感兴趣的年龄分布图。

创建第一个年龄分布图

现在我们理解了数据,我们可以使用 Seaborn 的默认设置创建一个简单的图表。我用红色表示女性,蓝色表示男性。

这也许有点刻板,但让图表易于理解是至关重要的,而颜色对于初步解释来说很重要。

唯一的“窍门”是我将男性的值乘以负数,这样蓝色条形图就会朝相反的方向。

这是创建图表的函数。

def create_age_distribution(female_df, male_df, country, year):
    df_f = female_df[female_df.country_name == country].loc[::-1]
    df_m = male_df[male_df.country_name == country].loc[::-1]

    ax = sns.barplot(y=df_m["indicator_name"], x=df_m[year] * -1, orient="h", color=MALE_COLOR)
    ax = sns.barplot(y=df_f["indicator_name"], x=df_f[year], orient="h", color=FEMALE_COLOR)

    return ax

这是我如何使用它的。

fig = plt.figure(figsize=(10, 7))

ax = create_age_distribution(
    female_df=population_female,
    male_df=population_male,
    country="World",
    year="2021"
)

plt.show()

这是 2021 年世界年龄分布图的结果。它显示了所有数据,但看起来不够好,且难以理解。

作者创建的图表

让我们改进一下。

创建 Seaborn 风格

seaborn最棒的地方在于你可以使用sns.set_style()轻松创建你独特的风格。它接受一个可以有多种不同值的字典。

在这个教程中,我创建了以下函数以便快速尝试不同的样式。

def set_seaborn_style(font_family, background_color, grid_color, text_color):
    sns.set_style({
        "axes.facecolor": background_color,
        "figure.facecolor": background_color,

        "axes.labelcolor": text_color,

        "axes.edgecolor": grid_color,
        "axes.grid": True,
        "axes.axisbelow": True,

        "grid.color": grid_color,

        "font.family": font_family,
        "text.color": text_color,
        "xtick.color": text_color,
        "ytick.color": text_color,

        "xtick.bottom": False,
        "xtick.top": False,
        "ytick.left": False,
        "ytick.right": False,

        "axes.spines.left": False,
        "axes.spines.bottom": True,
        "axes.spines.right": False,
        "axes.spines.top": False,
    }
)

你可能希望有更多的控制。我在这里省略了一些不关心的选项,并在多个地方重用相同的颜色。

我们必须选择背景、网格和文本颜色来运行函数。我更喜欢有背景色的图表,因为它们在页面上更为突出。白色背景看起来不错,但不是我的风格。

创建新的颜色方案时,我通常从找到一个喜欢的颜色开始。一个好的起点是Canva Color PalettesColorHunt

在我找到一些喜欢的颜色后,我通过Coolors生成额外的颜色。

这是我在本教程中使用的主要颜色调色板。

作者截图

现在我可以用新的颜色运行set_seaborn_style(),并且我选择了PT Mono作为字体。

FEMALE_COLOR = "#F64740"
MALE_COLOR = "#05B2DC"

set_seaborn_style(
    font_family="PT Mono",
    background_color="#253D5B",
    grid_color="#355882",
    text_color="#EEEEEE"
)

这是目前图表的样子。

作者创建的图表

这比之前的图表有了明显的改进,但仍然缺乏信息,并且难以理解。

让我们继续修正坐标轴。

改进坐标轴

现在颜色看起来不错,是时候让图表更具信息性了。

这是我想做的三件事。

  • 删除坐标轴标签,因为它们没有提供信息

  • 格式化 x 轴上的值,使其更具信息性

  • 让文本更大,以便在较小的屏幕上图表看起来更好

解决方案包括两个函数。

首先,create_x_labels() 函数处理第二个要点,并允许我根据国家的人口快速调整 x 轴,或者如果我想使用比例而不是绝对数字的话。

def create_x_labels(ax, xformat):
    if xformat == "billions":
        return ["{}B".format(round(abs(x / 1e9))) for x in ax.get_xticks()[1:-1]]
    elif xformat == "millions":
        return ["{}M".format(round(abs(x / 1e6))) for x in ax.get_xticks()[1:-1]]
    elif xformat == "thousands":
        return ["{}K".format(round(abs(x / 1e3))) for x in ax.get_xticks()[1:-1]]
    elif xformat == "percentage":
        return ["{}%".format(round(abs(x), 1)) for x in ax.get_xticks()[1:-1]] 

其次,format_ticks() 函数负责处理第一和第三个要点,并调用 create_x_labels()

def format_ticks(ax, xformat, xlim=(None, None)):
    ax.tick_params(axis="x", labelsize=12, pad=8)
    ax.tick_params(axis="y", labelsize=12)
    ax.set(ylabel=None, xlabel=None, xlim=xlim)

    plt.xticks(
        ticks=ax.get_xticks()[1:-1],
        labels=create_x_labels(ax, xformat)
    )

如果我们想比较两个不同的年龄分布,xlim 参数是必需的。如果我们不设置它,坐标轴会根据数据中的值进行调整,条形图会扩展到整个坐标轴。

当我创建图表时,我添加了这些函数。它和之前完全一样,只是最后加了 format_tricks()

fig = plt.figure(figsize=(10, 7))

ax = create_age_distribution(
    female_df=population_female,
    male_df=population_male,
    country="World",
    year="2021"
)

# New functions
format_ticks(ax, xformat="millions")

plt.show()

这就是新图表的样子。

作者创建的图表

我们还可以通过设置 xformat="percentage" 并使用 population_ratio_malepopulation_ratio_female 来测试百分比格式。我还设置了 xlim=(-10, 10)

作者创建的图表

看起来很好,但我们还可以做得更好。

添加标题和图例

我现在想解决的两个明显改进是:

  • 添加一个描述图表的标题

  • 添加一个解释条形图表示内容的图例

为了创建图例,我编写了以下函数,接受 x 和 y 参数来定义位置。

def add_legend(x, y): 
    patches = [
        Patch(color=MALE_COLOR, label="Male"),
        Patch(color=FEMALE_COLOR, label="Female")
    ]

    leg = plt.legend(
        handles=patches,
        bbox_to_anchor=(x, y), loc='center',
        ncol=2, fontsize=15,
        handlelength=1, handleheight=0.4,
        edgecolor=background_color
    )

然后,我添加了这个函数,就像在之前的步骤中添加 format_tricks() 一样。

fig = plt.figure(figsize=(10, 8))

ax = create_age_distribution(
    female_df=population_female,
    male_df=population_male,
    country="World",
    year="2021"
)

# New functions
format_ticks(ax, xformat="millions")
add_legend(x=0.5, y=1.09)
plt.title("Age Distribution for the World in 2021", y=1.14, fontsize=20)

plt.tight_layout()
plt.show()

我还添加了 plt.title() 来添加标题。

当我运行所有内容时,年龄分布图看起来是这样的。

作者创建的图表

看起来很棒。我们继续。

创建 PIL 图像并添加填充

在某些时候,我想将我的图形转换为可以保存到磁盘并以其他方式自定义的图像。

一种这样的定制是给图表添加一些填充,使其看起来不那么拥挤。

首先,我创建了 create_image_from_figure() 函数,将 Matplotlib 图形转换为 PIL 图像。

def create_image_from_figure(fig):
    plt.tight_layout()

    fig.canvas.draw()
    data = np.frombuffer(fig.canvas.tostring_rgb(), dtype=np.uint8)
    data = data.reshape((fig.canvas.get_width_height()[::-1]) + (3,))
    plt.close() 

    return Image.fromarray(data)

这是一个添加填充的函数。

def add_padding_to_chart(chart, left, top, right, bottom, background):
    size = chart.size
    image = Image.new("RGB", (size[0] + left + right, size[1] + top + bottom), background)
    image.paste(chart, (left, top))
    return image

再次,我将这些函数添加到创建图表的原始代码中。现在它看起来是这样的。

fig = plt.figure(figsize=(10, 8))

ax = create_age_distribution(
    female_df=population_female,
    male_df=population_male,
    country="World",
    year="2021"
)

# New functions
format_ticks(ax, xformat="millions")
add_legend(x=0.5, y=1.09)
plt.title("Age Distribution for the World in 2021", y=1.14, fontsize=20)

image = create_image_from_figure(fig)
image = add_padding_to_chart(image, 20, 20, 20, 5, background_color)

这是生成的图表。

作者创建的图表

在我看来,这已经接近完美了。我还有两件事要展示,如何创建网格和时间推移可视化。

让我们从第一个开始。

创建具有多个国家的网格

你可以使用 plt.subplots() 来创建网格,但在这个教程中,我想创建一个图像网格,因为我认为这样看起来更好。

以下函数接受一个图像列表,并创建一个具有 ncols 的网格。它通过创建一个足够大的单一背景色的空图像来工作,以适应所有图形。

def create_grid(figures, pad, ncols):
    nrows = int(len(figures) / ncols)
    size = figures[0].size

    image = Image.new(
        "RGBA",
        (ncols * size[0] + (ncols - 1) * pad, nrows * size[1] + (nrows - 1) * pad),
        "#ffffff00"
    )

    for i, figure in enumerate(figures):
        col, row = i % ncols, i // ncols
        image.paste(figure, (col * (size[0] + pad), row * (size[1] + pad)))

    return image

在下面的代码中,我遍历了一个国家列表,将生成的图表添加到figures中,并在最后通过运行create_grid()来创建网格。

figures = []

for country in [
    "United States", "China", "Japan", "Brazil", "Canada",
    "Germany", "Pakistan", "Russian Federation", "Nigeria", 
    "Sweden", "Cambodia", "Saudi Arabia", "Iceland",
    "Spain", "South Africa", "Morocco"
]:

    fig = plt.figure(figsize=(10, 8))

    ax = create_age_distribution(
        female_df=population_ratio_female,
        male_df=population_ratio_male,
        country=country,
        year="2021"
    )

    ax.set(xlim=(-10, 10))

    # New functions
    format_ticks(ax, xformat="percentage")
    add_legend(x=0.5, y=1.09)
    plt.title("Age Distribution for {} in 2021".format(country), y=1.14, fontsize=20)

    image = create_image_from_figure(fig)
    image = add_padding_to_chart(image, 20, 20, 20, 5, background_color)

    figures.append(image)

grid = create_grid(figures, pad=20, ncols=4)

注意,我使用了比例而不是绝对数字,并设置了xlim=(-10, 10)。否则,我将无法在视觉上比较各国之间的差异。

作者创建的图表

让我们继续教程的最后部分——如何创建时间推移可视化。

创建时间推移可视化

静态的年龄分布图看起来很棒,但看到它们随时间变化的过程非常有趣。

由于我们有 1960 年至 2021 年的实际值以及预测到 2050 年的数据,我们可以创建一个相对较长时间段的时间推移动画。

在开始之前,我需要告诉你,我使用的字体PT Mono并没有所有字符的高度相同。为了让可视化效果良好,我需要使用plt.text()来标注年份,而不是plt.title()。如果你使用其他字体,就不必这样做。

这是代码:

images = []
years = list(population_male.columns[4:])

for year in years:
    fig = plt.figure(figsize=(10, 8))

    ax = create_age_distribution(
        female_df=population_female,
        male_df=population_male,
        country="World",
        year=year
    )

    # New functions
    format_ticks(ax, xformat="millions", xlim=(-400000000, 400000000))
    add_legend(x=0.5, y=1.09)
    plt.title("Age Distribution for the World in      ", y=1.14, fontsize=21)
    plt.text(x=0.77, y=1.15, s=str(year), fontsize=21, transform=ax.transAxes)

    image = create_image_from_figure(fig)
    image = add_padding_to_chart(image, 20, 20, 20, 5, background_color)
    images.append(image)

我使用imageio从图片列表中创建 GIF。

# Duplicating the last frames to add a delay 
# before the animation restarts
images = images + [images[-1] for _ in range(20)]
imageio.mimwrite('./time-lapse.gif', images, duration=0.2)

让我们来看一下结果。

作者创建的可视化图

太棒了!这就是本教程的全部内容;告诉我你是否喜欢它并学到了一些有用的东西。

结论

这是一个有趣的教程,希望你喜欢。

年龄分布是展示一个国家人口结构的绝佳可视化方式,现在你已经见过几种使其突出的方法。

我们已经学会了创建样式、网格和动画。像我这里所做的编写函数也很棒,如果你想快速测试不同的想法和样式。

我希望你学到了一些将来会用到的东西。

感谢你抽时间阅读我的教程。如果你喜欢这种类型的内容,请告诉我。

如果有人需要,我可以创建更多教程! 😃

下次见。

如何使用 Seaborn 和 Matplotlib 创建美丽的条形图(包括动画)

原文:towardsdatascience.com/how-to-create-beautiful-bar-charts-with-seaborn-and-matplotlib-including-animation-c2bc9ade1d7c

图表教程

Python 数据可视化教程

Oscar LeoTowards Data Science Oscar Leo

·发布在 Towards Data Science ·7 分钟阅读·2023 年 6 月 15 日

--

作者创建的图表

你好,欢迎来到我的第一个 Matplotlib 和 Seaborn 教程。今天,我将向你展示如何将默认条形图转变为令人惊叹的视觉效果,配有图标和动画。

如果你喜欢这种内容,请告诉我。如果是这样,我可以创建更多类似的内容! 😃

你可以在这个仓库中找到代码和预处理数据:simple-bar-chart-tutorial

让我们开始吧。

第 1 步:创建默认条形图

对于本教程,我创建了一个简单的数据集,其中包含三个流行的开源深度学习框架(TensorflowPyTorchKeras)随时间变化的星级总数。

作者截图

这是一个简单的函数,它为数据框中的一行创建一个简单的条形图。

def create_bar_chart(row, color):    
    return sns.barplot(
        y=row.index.str.capitalize().values,
        x=row.values,
        orient="h",
        saturation=1,
        color=color,
        width=0.75,
    )

要创建第一个视觉效果,我像这样运行上述代码。

row = df.iloc[-1]

fig = plt.figure(figsize=(12, 7))
ax = create_bar_chart(row, color=bar_color)

plt.title("Total Number of Stars on GitHub")
plt.tight_layout()
plt.show()

如果我对数据集的最后一行运行这个函数,并使用橙色,这就是我得到的默认条形图。

作者创建的条形图

在接下来的步骤中,我将创建一些额外的函数,我们可以与上述代码一起运行,以改进图表。

让我们尝试创建一些更美丽的东西。

第 2 步:创建主题

首先,我想创建一个函数,允许我测试图表的不同颜色和字体。

Matplotlib 和 Seaborn 中有大量的方法,可以帮助你改变图表的外观。

我更喜欢首先通过使用 sns.set_style() 来创建主题。下面是我用来快速创建新主题的代码片段。

def set_seaborn_style(font_family, background_color, grid_color, text_color):
    sns.set_style({
        "axes.facecolor": background_color,
        "figure.facecolor": background_color,

        "grid.color": grid_color,
        "axes.edgecolor": grid_color,
        "axes.grid": True,
        "axes.axisbelow": True,

        "axes.labelcolor": text_color,
        "text.color": text_color,
        "font.family": font_family,
        "xtick.color": text_color,
        "ytick.color": text_color,

        "xtick.bottom": False,
        "xtick.top": False,
        "ytick.left": False,
        "ytick.right": False,

        "axes.spines.left": False,
        "axes.spines.bottom": True,
        "axes.spines.right": False,
        "axes.spines.top": False,
    }
)

sns.set_style() 还有更多选项,你可能会想以不同的方式使用它。但这是最适合我的设置。

我经常去 colorhunt.cocanva.com 寻找灵感以创建配色方案。当我有喜欢的基础颜色时,如果还需要更多颜色,我会去 coolors.co

这是我为本教程创建的颜色调色板(是的,我喜欢紫色)。

作者截图

我选择的字体是“PT Mono”,根据这个决定,我可以运行以下代码。

background_color = "#2F195F"
grid_color = "#582FB1"
bar_color = "#835ED4"
text_color = "#eee"

set_seaborn_style(font_family, background_color, grid_color, text_color)

如果我现在运行原始代码以创建条形图,我得到以下结果。

作者创建的条形图

这是一项明显的改进,但还不够好。让我们继续格式化标题文本和轴标签。

步骤 3: 文本格式化

我首先注意到的是轴标签需要更大。图表中显示的所有信息都应该易于一眼看清。

我不喜欢 x 轴上数字的显示方式。我想用 75K 替代 75000,以获得一个更不令人畏惧的外观。

这就是我创建这个函数的原因。

def format_axes(ax):
    ax.tick_params("x", labelsize=20, pad=16)
    ax.tick_params("y", labelsize=20, pad=8)

    plt.xticks(
        ticks=ax.get_xticks()[:-1],
        labels=["{}K".format(int(x / 1000)) for x in ax.get_xticks()[:-1]]
    )

对于标题,我添加了参数以增加字体大小并调整位置。

这是创建带有我新修改的图表的代码。

row = df.iloc[-1]

fig = plt.figure(figsize=(12, 7))
ax = create_bar_chart(row, color=bar_color)

# New function
format_axes(ax)

plt.title("Total Number of Stars on GitHub", fontsize=34, y=1.2, x=0.46)
plt.tight_layout()
plt.show()

这是结果。

作者创建的条形图

现在看起来相当不错,但现在是时候添加一些图标魔法了。

步骤 4: 添加图标

向图表中添加图像和图标很有趣,但也很棘手。将它们放在完美的位置或以理想的大小显示并不总是那么简单。

以下函数通过使用xycoords="data"和来自我的数据框的值,将图形中每个条形的末尾添加图标。

bboxprops中的boxstyle参数创建了一个白色圆形背景。

def add_bar_icons(ax, row, background_color, zoom, pad):
    for index, (name, value) in enumerate(row.items()): 
        icon = plt.imread("./icons/{}.png".format(name.lower()))
        image = OffsetImage(icon, zoom=zoom, interpolation="lanczos", resample=True, visible=True)
        image.image.axes = ax

        ax.add_artist(AnnotationBbox(
            image, (value, index), frameon=True,
            xycoords="data",
            bboxprops={
                "facecolor": "#fff",
                "linewidth": 3,
                "edgecolor": background_color,
                "boxstyle": "circle, pad={}".format(pad),
            }
        ))

我想将图标放在一个白色圆圈上,并用与图表背景相同的深紫色添加边框。

到目前为止,我还没有找到一种好的方法来动态处理zoom参数,所以我手动调整以获得正确的尺寸。

现在我创建图表的代码如下。

row = df.iloc[-1]

fig = plt.figure(figsize=(12, 7))
ax = create_bar_chart(row, color=bar_color)

# New functions
format_axes(ax)
add_bar_icons(ax, row, background_color, zoom=0.09, pad=0.9)

plt.title("Total Number of Stars on GitHub", fontsize=34, y=1.2, x=0.46)
plt.tight_layout()
plt.show()

这就是我得到的结果。

作者创建的条形图

为了添加星形图标,我创建了另一个函数,它使用 xycoords="axes fraction" 在图表上的任意位置添加自定义图标。

def add_icon(ax, icon_name, x, y, zoom):
    icon = plt.imread("./icons/{}.png".format(icon_name))
    image = OffsetImage(icon, zoom=zoom, interpolation="lanczos", resample=True, visible=True)
    image.image.axes = ax

    ax.add_artist(AnnotationBbox(
        image, (x, y), frameon=False,
        xycoords="axes fraction",
    ))

在下一个技巧中,我通过在标题中添加额外的空格并调整xy参数,为星形图标留出空间,以将图标放置在正确的位置。

row = df.iloc[-1]

fig = plt.figure(figsize=(12, 7))
ax = create_bar_chart(row, color=bar_color)

# New functions
format_axes(ax)
add_bar_icons(ax, row, background_color, zoom=0.09, pad=0.9)
add_icon(ax, "star", x=0.46, y=1.26, zoom=0.13)

plt.title("Total Number of     Stars on GitHub", fontsize=34, y=1.2, x=0.46)
plt.tight_layout()
plt.show()

现在我们的条形图是这样的,我们快完成了。

作者创建的条形图

看起来很棒,但现在我想将图表转换为更通用的格式。

让我们通过添加一些填充来解决那个被挤压的外观问题。

步骤 5: 将图表转换为图像

在这一部分,我创建了一个函数,它将图形转换为 PIL 图像。PIL 图像在我们教程的最后步骤中更易于操作。

def create_image_from_figure(fig):
    plt.tight_layout()

    fig.canvas.draw()
    data = np.frombuffer(fig.canvas.tostring_rgb(), dtype=np.uint8)
    data = data.reshape((fig.canvas.get_width_height()[::-1]) + (3,))
    plt.close() 

    return Image.fromarray(data)

我还创建了以下函数,以在图表周围添加填充。

def add_padding_to_chart(chart, left, top, right, bottom, background):
    size = chart.size
    image = Image.new("RGB", (size[0] + left + right, size[1] + top + bottom), background)
    image.paste(chart, (left, top))
    return image

这是生成图表的新版本代码,我用我们的新方法替代了标准的plt.show()

row = df.iloc[-1]

fig = plt.figure(figsize=(12, 7))
ax = create_bar_chart(row, color=bar_color)\
plt.title("Total Number of     Stars on GitHub", fontsize=34, y=1.2, x=0.46)

# New functions
format_axes(ax)
add_bar_icons(ax, row, background_color)
add_icon(ax, "star", 0.46, 1.26)
image = create_image_from_figure(fig)
image = add_padding_to_chart(image, 0, 10, 10, 10, background_color)

这是结果。

作者创建的条形图

太棒了!

我们现在已经把默认的条形图变成了更美丽的样子。

让我们用一个额外的部分结束。

额外部分:创建动画

因为我们把代码分成了可重用的函数,所以创建动画相当简单。

我们需要做的就是多次运行代码,使用不同的值,并将帧拼接在一起。

这是一个循环,我从数据集中取了最后 2000 行中的 200 行。我还通过设置xlim=(0, 185000)来修复 x 轴,以避免值闪烁。

images = []

for i in tqdm.tqdm(range(1, 2000, 10)):
    row = df.iloc[-i]

    fig = plt.figure(figsize=(12, 7))
    ax = create_bar_chart(row, color=bar_color)
    ax.set(xlim=(0, 185000))
    plt.title("Total Number of     Stars on GitHub", fontsize=34, y=1.2, x=0.46)

    format_axes(ax)
    add_bar_icons(ax, row, background_color, zoom=0.09, pad=0.9)
    add_icon(ax, "star", 0.46, 1.26)

    image = create_image_from_figure(fig)
    image = add_padding_to_chart(image, 20, 20, 40, 0, background_color)
    images.append(image)

images.reverse()

接下来,我使用imageio来创建一个 GIF。

# Adding a few duplicates of the last frame to create a delay
images = images + [images[-1] for _ in range(20)]

imageio.mimwrite('./animation.gif', images, duration=50)

这是结果。

作者创建的可视化

这标志着本教程的结束,希望你喜欢。如果喜欢,请分享这个故事并订阅我的频道

你也可以在 Twitter 上关注我:@oscarle3o

感谢阅读。

下次见! 😃

如何在 Python 中创建美丽的华夫图用于数据可视化

原文:towardsdatascience.com/how-to-create-beautiful-waffle-charts-for-data-visualisation-in-python-e9760a3f8594

数据可视化的绝佳替代方案

Andy McDonaldTowards Data Science Andy McDonald

·发表于Towards Data Science ·7 分钟阅读·2023 年 2 月 8 日

--

图片由Mae Mu拍摄,来源于Unsplash

华夫图是可视化分类数据的绝佳方式,既美观又易于读者理解——这也是有效数据可视化的关键目标之一。它们还提供了一种比饼图更美观的替代方案。

华夫图是由网格模式中较小的方块组成的正方形或矩形显示图。最常见的是 10 x 10 网格,但它们可以是你想要的任何尺寸,这将取决于你要展示的数据。网格中的每个方块都根据一个类别进行着色,代表了整体的一部分。通过这些图表,我们可以看到各个类别的贡献或展示实现目标的进度。

显示井内不同岩石类型的华夫图示例。图像由作者提供。

华夫图有多种用途,包括可视化实现目标的进度、理解组成整体的各个部分,甚至查看费用对毛利润的影响。

华夫图的优缺点

与任何数据可视化工具一样,以特定格式展示数据有其优点和缺点。

华夫图在展示数据方面有多个优势,包括:

  • 是饼图的绝佳替代方案

  • 有趣且美观

  • 易于阅读和解释

  • 数据没有失真

  • 视觉化少量类别的绝佳方式

  • 易于可视化实现目标的进度

尽管有这些优点,但使用华夫图也有一些缺点:

  • 如果要显示大量类别,可能会很难阅读

  • 比较非相邻类别可能会很困难

  • 由于尺寸问题,不能在方块中包含文本或数字

  • 如果值小于方块的代表值,准确性可能会受到影响。例如,如果一个方块等于一个单位,而你有半个单位

在文章中,我们将使用地质岩性数据来查看如何使用 Python 显示华夫图。

使用 Python 和 PyWaffle 构建华夫图

导入库和创建数据

使用 matplotlib 创建华夫图是可能的;然而,有一个非常方便的库 pywaffle,它使这个过程变得更加简单。

首先,我们必须通过在终端中使用以下命令来安装 pywaffle library

pip install pywaffle

然后,在我们的代码中,我们需要导入几个库:pandasmatplotlib

from pywaffle import Waffle
import pandas as pd
import matplotlib.pyplot as plt

在这个示例中,我们将使用一些基本的岩性数据。这表示两个不同井中的不同岩石类型的百分比。

与其加载数据,我们可以快速通过字典创建 pandas dataframe。

lith_dict = {'LITH': ['Shale', 'Sandstone', 
                      'Sandstone/Shale', 'Chalk', 
                      'Limestone', 'Marl', 'Tuff'],
             'Well1': [61,15, 10, 5, 
                            5, 3, 1],
             'Well2': [35 ,21, 16, 12, 
                            7, 5, 4]}

lith_data_df = pd.DataFrame.from_dict(lith_dict)

当我们查看数据框时,我们会看到:

pandas dataframe 中两个井的不同岩性百分比。图片由作者提供。

最后,我们将创建一些基本颜色,使华夫图看起来更具视觉吸引力,并使我们能够轻松地比较不同井中的数据。

colours = ['#8dd3c7', '#deb887', '#bebada', '#fb8072', 
           '#80b1d3', '#fdb462', '#b3de69']

使用 PyWaffle 创建简单的华夫图

在华夫图中包含值和标签可能会使图表非常混乱。因此,在这个示例中,我们将把这些信息存储在图例中。

为此,我们将创建一个新的变量 plot_labels,并使用列表推导将值与标签连接起来。

plot_labels = [f'{i} ({str(j)} %)' for i,j in zip(lith_data_df.LITH, 
                                                    lith_data_df.Well1)]

接下来,我们可以继续创建华夫图。

我们通过调用 plt.figure 并将 Waffle 传递给 FigureClass 参数来实现这一点。随后是几个额外的参数,包括我们要显示的数据、颜色和标签。

最后,我们可以在调用 plt.figure() 时直接设置图例。

plt.figure(FigureClass=Waffle, figsize=(10,10), rows=5, columns = 20, 
                 values=list(lith_data_df['Well1']),
                 colors=colours,
                 labels=plot_labels, 
                legend={'loc':'lower center', 'bbox_to_anchor': (0.5, -0.8), 
                        'ncol':3, 'fontsize':12})
plt.show()

当我们运行上述代码时,我们将得到以下华夫图。

使用 PyWaffle 创建的显示不同岩性的华夫图——一个 Python 库。图片由作者提供。

我们可以立即看到每种岩性对井的整体组成的贡献。

当方块被填充时,过程从左下角开始。如果我们想更改起始位置,可以通过将 starting_location 参数添加到 plt.figure() 方法来实现。

plt.figure(FigureClass=Waffle, figsize=(10,10), rows=5, columns = 20, 
                 values=list(lith_data_df['Well1']),
                 colors=colours,
                 labels=plot_labels, 
                legend={'loc':'lower center', 'bbox_to_anchor': (0.5, -0.8), 
                        'ncol':3, 'fontsize':12},
                starting_location='NW')
plt.show()

当我们运行这段代码时,我们会看到华夫饼图沿水平轴翻转。

华夫饼图显示不同的岩石类型,修改了起始位置。图片由作者提供。

使用 Python 比较不同的华夫饼图

现在我们有了第一口井,我们可以对第二口井运行相同的代码。我们通过将值更改为使用 Well2 而不是 Well1 来实现这一点。

fig = plt.figure(FigureClass=Waffle, figsize=(10,10), rows=5, columns = 20, 
                 values=list(lith_data_df['Well2']),
                 colors=colours,
                 labels=plot_labels, 
                legend={'loc':'lower center', 'bbox_to_anchor': (0.5, -0.8), 
                        'ncol':3, 'fontsize':12})
plt.show()

华夫饼图显示第二口井的不同岩石类型。图片由作者提供。

这看起来很棒,我们可以很容易地为每口井创建单独的图表,并在 PowerPoint 中将它们拼接在一起。然而,通过 PyWaffle 也有一种方法,那就是传递包含子图位置和内容的字典。

fig = plt.figure(FigureClass=Waffle, 
                 plots = {211: {'values':list(lith_data_df['Well1']),
                               'labels': [f'{i} ({str(j)} %)' for i,j in zip(lith_data_df.LITH, 
                                                    lith_data_df.Well1)],
                               'legend':{'loc':'center left', 'bbox_to_anchor': (1.0, 0.5), 
                                          'ncol':1, 'fontsize':12},
                                'title':{'label':'Well 1 Lithology Composition', 'fontsize':18}
                               },

                          212: {
                              'values':list(lith_data_df['Well2']),
                              'labels': [f'{i} ({str(j)} %)' for i,j in zip(lith_data_df.LITH, 
                                         lith_data_df.Well2)],
                              'legend':{'loc':'center left', 'bbox_to_anchor': (1.0, 0.5), 
                                          'ncol':1, 'fontsize':12},
                              'title':{'label':'Well 2 Lithology Composition', 'fontsize':18}
                          }
                         },
                 figsize=(15,10), 
                 rows=5, 
                 columns = 20, 
                 colors=colours)

plt.tight_layout()
plt.show()

上述代码将生成以下华夫饼图,让我们可以比较两口井之间的岩性差异。

同一图形上的多个华夫饼图,使得比较不同数据组更容易。图片由作者提供。

如何将类别拆分为单独的华夫饼图

为了让我们的读者更容易理解,我们可以将每个类别拆分到它们各自的华夫饼子图中。这可以使读者更容易跟随故事,并与包含相同类别的其他组进行直接比较。

为此,我们需要利用 matplotlib 的子图功能。

我们需要遍历每个类别,然后使用 Waffle.make_waffle() 添加各个子图。

# Set up the colour for unused squares
off_colour = 'lightgrey'

# Figsize numbers must be equal or the height greater than the width
# othewise the plot will appear distorted

fig, axs = plt.subplots(len(lith_data_df), 1, figsize=(10, 15))

for (i, ax), color in zip(enumerate(axs.flatten()), colours):
    plot_colours = [color, off_colour]
    perc = lith_data_df.iloc[i]['Well1']
    values = [perc, (100-perc)]
    lith = lith_data_df.iloc[i]['LITH']
    Waffle.make_waffle(ax=ax, rows=5, columns=20, 
                       values=values, colors=plot_colours)

    ax.set_title(lith)
plt.tight_layout()
plt.show()

当我们运行这段代码时,我们会得到带有各个类别的图形。

使用 matplotlib 的子图功能按类别划分的多个华夫饼图。图片由作者提供。

使用 Circles 和 Icons 替代 PyWaffle 中的 Squares

如果我们想让数据可视化更有趣,我们可以引入不同的形状和图标,而不是标准的方块。

为此,我们可以添加两个参数:iconsfont_size。第一个允许我们从font-awesome网站中指定一个图标,第二个允许我们控制图标的大小。

plt.figure(FigureClass=Waffle, figsize=(10,10), rows=5, columns = 20, 
                 values=list(lith_data_df['Well1']),
                 colors=colours,
                 labels=plot_labels, 
                icons='circle',
                font_size='20',
                legend={'loc':'lower center', 'bbox_to_anchor': (0.5, -0.8), 
                        'ncol':3, 'fontsize':12},
                 starting_location='NW')
plt.show()

运行代码后,我们会得到以下图表。

使用圆圈替代方块的华夫饼图。图片由作者提供。

这为用户提供了极大的自定义华夫饼图的方式,特别是当你在向用户展示数据时寻找特定风格时。例如,如果你正在创建一张讨论蜜蜂的海报,你可以用蜜蜂图标代替圆圈/方块。

总结

华夫饼图是数据可视化的优秀工具。它们为数据传达的故事增添了额外的趣味,并且是饼图的一个很好的替代选择。PyWaffle 库简单易用,适用于各种数据集,包括地质和岩石物理数据集。

感谢阅读。在你离开之前,你应该订阅我的内容,并将我的文章送到你的收件箱。 你可以在这里做到这一点!另外,你还可以 订阅我的新闻简报 以免费获取额外的内容直接送到你的收件箱。

其次,你可以通过订阅会员,获得完整的 Medium 体验,同时支持成千上万的其他作家和我。只需每月$5,你就能完全访问所有精彩的 Medium 文章,还可以通过写作赚钱。

如果你使用 我的链接 你将直接支持我,你的费用不会增加。如果你这样做了,非常感谢你的支持。

如何创建气候条纹

原文:towardsdatascience.com/how-to-create-climate-stripes-e46ab1bdade

使用 R 语言的数据可视化教程

Irfan Alghani KhalidTowards Data Science Irfan Alghani Khalid

·发布于Towards Data Science ·阅读时间 5 分钟·2023 年 1 月 10 日

--

气候条纹。(图片由作者创建)

TL;DR

本文将教你如何使用 R 语言创建气候条纹可视化。我们将使用气象局的数据并通过 ggplot 库生成条纹。

介绍

一张图片胜千言。数据可视化就是其中之一。当我们想要影响别人时,一张图表可以让你意识到你可能之前不在意的事情。气候条纹也是其中之一。但是它是什么呢?

气候条纹由 Ed Hawkins 教授于 2018 年创建。这种可视化展示了一系列颜色条,表示相对于 1961-1990 年的温度变化。它仅显示颜色,但这使得这个图表既美丽又令人惊恐。

每一条颜色条代表 1961-1990 年相对温度。蓝色表示温度较低,红色表示温度较高。颜色越深,温度上升越高。

如果你注意到了,文章的封面就是气候条纹。现在我们将使用 R 语言来创建这个图表。废话不多说,开始吧!

实现方法

数据来源

要实现图表,我们首先需要找到数据来源。在本文中,我们将使用来自气象局的名为 HadCRUT5 的数据集。

该数据集收集了多个区域的近地表温度。它结合了 CRUTEM5(地表空气温度)和 HadSST4(海表温度)数据集的数据。

图片由作者拍摄。

气象局提供了时间序列和网格格式的数据。在这种情况下,我们将使用时间序列数据。你可以通过链接这里阅读更多关于数据集的详细信息。

R 语言提供了一个名为 tidyverse 的库。这个一体化的库包含了对数据处理(dplyr)和可视化(ggplot)有用的函数。

我们将加载那个库以及 scales 和 glue 库。这里是实现这一点的代码:

library(tidyverse)
library(scales)
library(glue)

加载数据

加载库后,下一步是加载数据。正如我之前提到的,我们将使用 HadCRUT5 的年度时间序列数据集。请通过这个链接这里下载数据集。

下载数据后,让我们使用 read_csv 函数加载数据,如下所示:

t_data <- read_csv('HadCRUT.5.0.1.0.analysis.summary_series.global.annual.csv')

我们来看一下数据集:

如你所见,从左到右,每列代表年份、摄氏度的温度异常、下限置信度和上限置信度。为了可视化图表,我们将使用第一列和第二列。让我们运行这段代码:

t_data <- t_data %>%
  select(year=Time, t_diff='Anomaly (deg C)')

代码将选择所需的列并同时转换名称。这种转换将使我们的数据处理更简单。

可视化数据

好了,现在进入激动人心的部分,我们将使用 ggplot 库来可视化数据。如果你第一次听说 ggplot,别担心!我会指导你生成图表。首先,让我们添加这些代码行:

t_data %>%
  ggplot(aes(x=year, y=1, fill=t_diff)) +
  geom_tile(show.legend = FALSE)

所以,代码将使用 geom_tile 函数创建一组条纹。ggplot 函数将处理数据。通过这个函数,我们选择成为 XY 轴和颜色的列。结果将如下所示:

图片由作者创建。

嗯,还是不好。让我们调整图表。首先,我们将使用 scale_fill_stepsn 函数来更改颜色调色板,像这样:

t_data %>%
  ggplot(aes(x=year, y=1, fill=t_diff)) +
  geom_tile(show.legend = FALSE) +
  # ADD THIS CODE
  scale_fill_stepsn(colors=c('#04346c', '#0483c3', 'white', '#ed2016', '#740404'), 
                    values=rescale(c(min(t_data$t_diff), 0, max(t_data$t_diff))),
                    n.breaks=12)

在函数内部,我们设置了颜色等参数,以设置颜色调色板,值因为数据中有负值,以及 n.breaks 以将颜色调色板分成 12 个不同的颜色范围。结果将如下所示:

图片由作者创建。

看起来更好了!但等等,图表上有些奇怪的间隙。让我们添加一个名为 coord_cartesian 的函数来去除间隙。这里是实现这一点的代码:

t_data %>%
  ggplot(aes(x=year, y=1, fill=t_diff)) +
  geom_tile(show.legend = FALSE) +
  scale_fill_stepsn(colors=c('#04346c', '#0483c3', 'white', '#ed2016', '#740404'), 
                    values=rescale(c(min(t_data$t_diff), 0, max(t_data$t_diff))),
                    n.breaks=12) +
  # ADD THIS CODE
  coord_cartesian(expand=FALSE)

添加代码后,结果如下:

图片由作者创建。

很好!现在我们需要去除所有刻度和图表背景。这里是实现这一点的代码:

t_data %>%
  ggplot(aes(x=year, y=1, fill=t_diff)) +
  geom_tile(show.legend = FALSE) +
  scale_fill_stepsn(colors=c('#04346c', '#0483c3', 'white', '#ed2016', '#740404'), 
                    values=rescale(c(min(t_data$t_diff), 0, max(t_data$t_diff))),
                    n.breaks=12) +
  coord_cartesian(expand=FALSE) +
  # ADD THIS CODE
  theme_void()

这是最终结果:

图片由作者创建。

干得好,你已经创建了气候条纹!让我们使用 ggsave 函数保存图表。这里是实现这一点的代码:

ggsave('<FILE_NAME>.png', width=1920, height=1080, units='px')

附加:添加标签

如果你有兴趣给图表添加一些标签和标题,那是完全可能的!ggplot 的强大之处在于它可以覆盖我们之前设置的样式。让我们看看这段代码:

t_data %>%
  ggplot(aes(x=year, y=1, fill=t_diff)) +
  geom_tile(show.legend = FALSE) +
  scale_fill_stepsn(colors=c('#04346c', '#0483c3', 'white', '#ed2016', '#740404'), 
                    values=rescale(c(min(t_data$t_diff), 0, max(t_data$t_diff))),
                    n.breaks=12) +
  coord_cartesian(expand=FALSE) +
  theme_void() +
  # ADD THIS CODE
  scale_x_continuous(breaks=seq(1860, 2010, 30)) +
  labs(title=glue('Global Temperature Change ({min(t_data$year)}-{max(t_data$year)})')) +
  theme(
    axis.text.x = element_text(color='white', 
                               margin=margin(t=5, b=10, unit='pt')),
    plot.title = element_text(color='white',
                              margin=margin(t=5, b=10, unit='pt'),
                              hjust=0.05),
    plot.background = element_rect(fill='black'),
  )

在底部,你可以看到我添加了三个函数。让我给你逐一解释:

  • Scale_x_continuous 将帮助你在 x 轴上生成标签,

  • 实验室将帮助你在图表上可视化标题,

  • 主题将调整背景和文本的外观。

在你再次运行代码后,最终结果将如下所示:

图片由作者创建。

最终备注

现在你已经学会了如何使用 R 创建气候条纹。我希望这能帮助你从这篇文章中学到更多,特别是数据可视化部分。

如果你对我的文章感兴趣,请在 Medium 上关注我。感谢阅读我的文章!

参考文献

[1] www.reading.ac.uk/planet/climate-resources/climate-stripes

[2] www.youtube.com/watch?v=NrEdAtSlTMo

如何用最少的 Python 代码创建赛博朋克风格的 Seaborn 小提琴图

原文:towardsdatascience.com/how-to-create-cyberpunk-styled-seaborn-violin-plots-with-minimal-python-code-45897b82ed4c

关于如何轻松增强你的 Seaborn 小提琴图的简单教程

点击这里访问 Andy McDonald 的文章Andy McDonaldTowards Data Science Andy McDonald

·发表于Towards Data Science ·6 分钟阅读·2023 年 6 月 26 日

--

赛博朋克风格的小提琴图,展示了不同岩性在井中的密度变化。图片由作者提供。

小提琴图是一种常见的数据可视化方式,它将箱形图和密度图的功能结合在一个图形中。这使我们能够在一个图形中可视化更多的信息。例如,我们可以查看箱形图中的基本统计数据,识别可能的异常值,并查看数据的分布。这有助于我们理解数据是否偏斜或包含多模态分布。

在我最新的系列文章中,我探索了使用各种主题(包括赛博朋克风格)来改进和增强基本 matplotlib 图形的方法。这种风格为图形提供了未来感的霓虹般外观,并只需几行代码即可应用于 matplotlib 和 seaborn 图形。

如果你想了解更多,你可以查看我在下面的文章中如何将其应用于 matplotlib 图形。

链接到赛博朋克 Matplotlib 图形 ## 赛博朋克化你的 Matplotlib 图形

通过几行代码将你的 Matplotlib 图形从无聊变得有趣

[链接到文章

在这个简短的教程中,我们将对基本的 seaborn 小提琴图进行赛博朋克风格的改造。

导入库和加载数据

我们将从导入本教程中使用的库开始。

这些是 matplotlibseaborn 用于可视化我们的数据,pandas 用于加载和存储数据,mplcyberpunk 用于将赛博朋克主题应用于 seaborn 图表。

import matplotlib.pyplot as plt
import pandas as pd
import mplcyberpunk
import seaborn as sns

导入所需的库后,下一步是加载我们的数据。这是通过 pandas 的 read_csv() 函数完成的,并传入数据文件的位置。

我们将使用的数据是结合了XEEK 和 Force 2020 机器学习竞赛的一个子集,旨在通过测井数据预测岩性。数据集的进一步细节可以在文章末尾找到。

df = pd.read_csv('data/Xeek_Well_15-9-15.csv')

Pandas 数据框包含 15/19–15 井的测井数据。图片由作者提供。

当我们查看数据框(df)时,我们得到如上图。我们可以看到我们有一口井的数据,从 485 米延伸到 3200 米。

创建 Seaborn 小提琴图

从数据框中,我们将使用两列。RHOB 列包含体积密度测量数据,LITH 列包含岩性描述。

我们可以调用以下代码来创建基本的小提琴图。

首先我们将图形大小设置为 10 x 5,这样会得到一个大小合适的图形,然后我们调用 sns.violinplot()并传入所需的参数。

plt.figure(figsize=(10,5))
sns.violinplot(x='LITH', y='RHOB', data=df)

当我们运行上述代码时,我们得到以下图表。

基本的 seaborn 小提琴图显示了每种岩性的体积密度 (RHOB) 的变化。图片由作者提供。

初看上去,返回的图表看起来不错且可用,但我们可以通过使用 mplcyberpunk 库来改进样式。

将赛博朋克风格应用于 Seaborn 图形

要将赛博朋克风格应用到我们的图表中,我们只需在代码中添加一行额外的代码。这行代码使用了 with 语句,然后调用了 plt.style.context,它允许我们仅将样式应用于此行下方调用的图表,而不是改变所有图表的全局样式。

with plt.style.context('cyberpunk'):
    plt.figure(figsize=(10,5))
    sns.violinplot(x='LITH', y='RHOB', data=df)

当我们运行上述代码时,我们将得到以下小提琴图,大部分赛博朋克主题已经应用。

应用 mplcyberpunk 主题后的 Seaborn 小提琴图。图片由作者提供。

mplcyberpunk 库应该做的一个过程是更改小提琴的颜色。然而,在我们的例子中,这尚未应用。但可以轻松修复。

我们需要创建一个赛博朋克颜色列表来修复它。这些颜色提取自 mplcyberpunk 源代码,但可以更改为任何你想要的颜色。记住,如果你追求赛博朋克风格,我们可能会使用明亮的霓虹颜色。

除了创建颜色列表外,我们还可以对小提琴图进行排序,使其按字母顺序排列。这是一个可选的步骤,但非常有用,特别是在比较多个具有相同类别的数据集时。

my_pal=['#08F7FE', '#FE53BB', '#F5D300', '#00ff41', 'r', '#9467bd', '#de014f']

lith_order = df['LITH'].sort_values().unique()

要将颜色应用到文件中,我们可以将my_pal传递给小提琴图的调色板参数。

然而,要将相同的颜色应用到图形的边缘/线条上,我们需要访问 collections,它们存储了小提琴图所有部分的列表。

在这个列表中,每两个连续的项对应一个小提琴:第一个是小提琴的主体,第二个是迷你箱型图。

因此,我们需要在 for 循环中考虑这一点。

with plt.style.context('cyberpunk'):
    plt.figure(figsize=(15,10))
    g=sns.violinplot(x='LITH', y='RHOB', data=df, palette=my_pal,
                     order=lith_order)

    for i in range(len(g.collections)//2):  
        # divide by 2 because collections include both violin 
        # bodies and the mini box plots
        g.collections[i*2].set_edgecolor(my_pal[i])
        g.collections[i*2].set_alpha(0.8)

当我们运行上述代码时,我们得到以下带有赛博朋克小提琴图的图形。

赛博朋克风格的小提琴图,用于展示井下不同岩性。图片由作者提供。

现在我们能够控制图形的线条和颜色后,可以通过调整填充的 alpha 值使其稍微亮一点,并增加 x 轴和 y 轴标签的大小来做最后的调整。

with plt.style.context('cyberpunk'):
    plt.figure(figsize=(15,10))

    g=sns.violinplot(x='LITH', y='RHOB', data=df, palette=my_pal,
                     order=lith_order)

    for i in range(len(g.collections)//2):
        g.collections[i*2].set_edgecolor(my_pal[i])
        g.collections[i*2].set_alpha(0.9)

    g.set_ylabel('RHOB\n\n', fontsize=16)
    g.set_xlabel('\n\nLithology', fontsize=16)

赛博朋克风格的小提琴图,用于展示井下不同岩性。图片由作者提供。

总结

mplcyberpunk库提供了一种快速简便的方法,可以瞬间将图形从默认样式转换为具有未来感的外观。

在创建类似的图形时,考虑受众并确保你要传达的信息和故事仍然清晰是非常重要的。

本教程中使用的数据集

作为 Xeek 和 FORCE 2020 举办的机器学习竞赛的一部分使用的训练数据集的子集(Bormann et al., 2020)。此数据集使用 Creative Commons Attribution 4.0 International 许可。

完整的数据集可以通过以下链接访问:doi.org/10.5281/zenodo.4351155

感谢阅读。在你离开之前,你应该订阅我的内容并在你的收件箱中收到我的文章。 你可以在这里做到这一点!

其次,你可以通过注册会员来获得完整的 Medium 体验并支持成千上万的其他作者和我。它只需每月$5,你可以完全访问所有精彩的 Medium 文章,并有机会通过写作赚钱。

如果你使用 我的链接, 你将直接通过你的费用支持我,而这不会增加你的开支。如果你这样做,非常感谢你的支持。

如何使用 Python 和 Matplotlib 创建美国数据地图

原文:towardsdatascience.com/how-to-create-data-maps-of-the-united-states-with-python-and-matplotlib-5dfb425bd87d

Matplotlib 教程

创建引人注目的地图

Oscar Leo数据科学前沿 Oscar Leo

·发布于数据科学前沿 ·7 分钟阅读·2023 年 9 月 7 日

--

地图由作者创建

你好,欢迎来到这个教程。

今天,我将教你如何使用地理数据和 Facebook 连通性指数(这两个数据源都是公共领域且免费使用)来创建上面看到的数据可视化。

像这样的地图非常适合可视化地理信息,如果选择合适的颜色,它们会立即吸引任何人的注意。

典型的使用场景是通过经济规模、人口或其他指标如全球地图上的寿命来比较国家(或美国各州)。

这些地图通常会揭示出基于地理位置的模式,这是其他可视化中看不到的。

如果这听起来很有趣,你来对地方了。

让我们开始教程吧。

步骤 1:下载数据

在开始之前,我们需要下载一个足够精彩的数据集和地理数据,以绘制准确的美国地图。

对于地图,我使用了来自Cencus.gov的形状文件。你可以使用以下链接下载

为了获得补充数据集,我选择了 Facebook 连通性指数,该指数测量了不同县之间的两个人在 Facebook 上的连接可能性。

你可以通过这个链接下载连通性数据。

下载完成后,解压文件并放在一个合适的位置。我在教程中使用的是./data,但你可以根据自己的需要进行设置。

它应该是这样的。

作者截屏

让我们写一些代码。

第二步:导入库并准备 Seaborn

唯一的新库(如果你做过我的其他Matplotlib 教程)是 geopandas,我们将用它来绘制地图。

# Import libraries

import numpy as np
import pandas as pd
import seaborn as sns
import geopandas as gpd
import matplotlib.pyplot as plt

from PIL import Image
from matplotlib.patches import Patch, Circle

接下来,让我们使用 seaborn 定义一些样式特征。

edge_color = "#30011E"
background_color = "#fafafa"

sns.set_style({
    "font.family": "serif",
    "figure.facecolor": background_color,
    "axes.facecolor": background_color,
})

现在是学习如何绘制地图的时候了。

第三步:加载和准备地理数据

我使用 geopandas 加载数据并删除“未合并领土”,例如关岛、波多黎各和美属萨摩亚。

# Load and prepare geo-data
counties = gpd.read_file("./data/cb_2018_us_county_500k/")
counties = counties[~counties.STATEFP.isin(["72", "69", "60", "66", "78"])]
counties = counties.set_index("GEOID")

states = gpd.read_file("./data/cb_2018_us_state_500k/")
states = states[~states.STATEFP.isin(["72", "69", "60", "66", "78"])]

geopandas 数据框有一个 geometry 列,用于定义每一行的形状。它允许我们通过调用 counties.plot()states.plot() 来绘制地图。

ax = counties.plot(edgecolor=edge_color + "55", color="None", figsize=(20, 20))
states.plot(ax=ax, edgecolor=edge_color, color="None", linewidth=1)

plt.axis("off")
plt.show()

在这里,我开始绘制具有透明边框的县,然后在调用 states.plot() 时重用 ax,以避免绘制多个地图。

这是我得到的结果。

地图由作者创建

地图看起来不太好,但我会做一些快速调整,让我们走上正确的轨道。

第一个调整是将地图投影更改为以美国为中心的投影。你可以通过调用 to_crs() 来实现。

# Load and prepare geo-data
...

counties = counties.to_crs("ESRI:102003")
states = states.to_crs("ESRI:102003")

这是不同之处。

地图由作者创建

在绘制美国的数据地图时,通常会将阿拉斯加和夏威夷放在大陆下方,我们也会这样做。

使用 geopandas,你可以通过内置函数进行几何形状的平移、缩放和旋转。这里有一个有用的函数来完成这项任务。

def translate_geometries(df, x, y, scale, rotate):
    df.loc[:, "geometry"] = df.geometry.translate(yoff=y, xoff=x)
    center = df.dissolve().centroid.iloc[0]
    df.loc[:, "geometry"] = df.geometry.scale(xfact=scale, yfact=scale, origin=center)
    df.loc[:, "geometry"] = df.geometry.rotate(rotate, origin=center)
    return df

我计算整个数据框的中心点,定义旋转和缩放的原点。如果不这样做,geopandas 会自动为每一行处理,这会使地图看起来完全混乱。

下一个函数处理我们当前的数据框,分离夏威夷和阿拉斯加,调用 translate_geometries() 来调整它们的几何形状,并将其放回新的数据框中。

def adjust_maps(df):
    df_main_land = df[~df.STATEFP.isin(["02", "15"])]
    df_alaska = df[df.STATEFP == "02"]
    df_hawaii = df[df.STATEFP == "15"]

    df_alaska = translate_geometries(df_alaska, 1300000, -4900000, 0.5, 32)
    df_hawaii = translate_geometries(df_hawaii, 5400000, -1500000, 1, 24)

    return pd.concat([df_main_land, df_alaska, df_hawaii])

我们将 adjust_maps() 添加到我们的代码中。

# Load and prepare geo-data
...

counties = adjust_maps(counties)
states = adjust_maps(states)

现在我们的地图看起来是这样的。

地图由作者创建

进入下一步。

第四步:添加数据

要添加数据,我们首先加载 Facebook 连接数据。我将 user_locfr_loc 列转换为字符串,并添加前导零,以使其与地理数据一致。

# Load facebook data
facebook_df = pd.read_csv("./data/county_county.tsv", sep="\t")
facebook_df.user_loc = ("0" + facebook_df.user_loc.astype(str)).str[-5:]
facebook_df.fr_loc = ("0" + facebook_df.fr_loc.astype(str)).str[-5:]

user_locfr_loc 列定义了一个县对,第三列 scaled_sci 是我们要显示的值。

数据集中有 3,227 个县,这意味着总共有 10,413,529 对,但我们将逐个显示每个县的连接指数。

# Create data map
county_id = "06075" # San Francisco
county_name = counties.loc[county_id].NAME
county_facebook_df = facebook_df[facebook_df.user_loc == county_id]

接下来,我定义一个 selected_colordata_breaks,其中包含百分位数、颜色和后续使用的图例文本。

# Create data map
...

selected_color = "#FA26A0"
data_breaks = [
    (90, "#00ffff", "Top 10%"),
    (70, "#00b5ff", "90-70%"),
    (50, "#6784ff", "70-50%"),
    (30, "#aeb3fe", "50-30%"),
    (0, "#e6e5fc", "Bottom 30%"),
]

以下函数使用 county_df 和我们刚刚定义的 data_breaks 来定义每一行的颜色。

def create_color(county_df, data_breaks):
    colors = []

    for i, row in county_df.iterrows():
        for p, c, _ in data_breaks:
            if row.value >= np.percentile(county_df.value, p):
                colors.append(c)
                break

    return colors

我们计算出正确的值并像这样添加create_color()

# Create data map
...

counties.loc[:, "value"] = (county_facebook_df.set_index("fr_loc").scaled_sci)
counties.loc[:, "value"] = counties["value"].fillna(0)
counties.loc[:, "color"] = create_color(counties, data_breaks)
counties.loc[county_id, "color"] = selected_color

ax = counties.plot(edgecolor=edge_color + "55", color=counties.color, figsize=(20, 20))
states.plot(ax=ax, edgecolor=edge_color, color="None", linewidth=1)
ax.set(xlim=(-2600000, None)) # Removing some of the padding to the left

plt.axis("off")
plt.show()

这是我们得到的结果。

地图由作者创建

这看起来很棒,但我们需要添加一些信息。

第 5 步:添加信息

我们需要的第一条信息是一个标题,用来解释数据可视化的内容。

这是一个执行此任务的函数。

def add_title(county_id, county_name):
    plt.annotate(
        text="Social Connectedness Ranking Between US Counties and",
        xy=(0.5, 1.1), xycoords="axes fraction", fontsize=16, ha="center"
    )

    plt.annotate(
        text="{} (FIPS Code {})".format(county_name, county_id), 
        xy=(0.5, 1.03), xycoords="axes fraction", fontsize=32, ha="center",
        fontweight="bold"
    )

接下来,我们需要一个图例和解释数据的辅助信息,因为它有点复杂。

用于添加图例的函数使用data_breaksselected_color来创建Patch(es),然后我们通过plt.legend()添加这些补丁。

def add_legend(data_breaks, selected_color, county_name):
    patches = [Patch(facecolor=c, edgecolor=edge_color, label=t) for _, c, t in data_breaks]
    patches = [Patch(facecolor=selected_color, edgecolor=edge_color, label=county_name)] + patches

    leg = plt.legend(
        handles=patches,
        bbox_to_anchor=(0.5, -0.03), loc='center',
        ncol=10, fontsize=20, columnspacing=1,
        handlelength=1, handleheight=1,
        edgecolor=background_color,
        handletextpad=0.4
    )

我还有一个简单的函数可以在图例下方添加一些额外的信息。

def add_information():
    plt.annotate(
        "The Facebook Connectivity Index measure the likelyhood that users in different\nlocations are connected on Facebook. The formula divides the number of Facebook\nconnections with the number of possible connections for the two locations.",
        xy=(0.5, -0.08), xycoords="axes fraction", ha="center", va="top", fontsize=18, linespacing=1.8
    )

    plt.annotate(
        "Source: https://dataforgood.facebook.com/", 
        xy=(0.5, -0.22), xycoords="axes fraction", fontsize=16, ha="center",
        fontweight="bold"
    )

最后,我有一个add_circle()函数,通过在县周围画一个圆圈来指示我们正在查看哪个县。

def add_circle(ax, counties_df, county_id):
    center = counties_df[counties_df.index == county_id].geometry.centroid.iloc[0]
    ax.add_artist(
        Circle(
            radius=100000, xy=(center.x, center.y), facecolor="None", edgecolor=selected_color, linewidth=4
        )
    )

我们将所有这些内容添加到# Create data map注释下方的其余代码中。

# Create data map
...

add_circle(ax, counties, county_id)
add_title(county_id, county_name)
add_legend(data_breaks, selected_color, county_name)
add_information()

plt.axis("off")
plt.show()

这是完成的数据显示。

地图由作者创建

恭喜,你现在知道如何在 Matplotlib 中创建美国的精彩数据地图! 😃

结论

数据地图在你想以引人注目的方式可视化地理信息时非常棒。

这一次,我们使用了来自 Facebook 的社会联系指数,但你可以将其更改为任何其他带有地理信息的数据。

我在我的新免费通讯中,写了更多关于可视化和数据集的内容,数据奇迹

如果你喜欢这个教程,确保也查看一下我的其他教程。

奥斯卡·莱奥

奥斯卡·莱奥

Matplotlib 教程

查看列表8 个故事

下次见。

如何使用 Python 和 Matplotlib 创建引人注目的国家排名

原文:towardsdatascience.com/how-to-create-eye-cathing-country-rankings-using-python-and-matplotlib-d57e4594fe13

Matplotlib 教程

一种美丽的替代标准折线图的方法

奥斯卡·利奥Towards Data Science 奥斯卡·利奥

·发布于 Towards Data Science ·阅读时间 7 分钟·2023 年 8 月 18 日

--

作者创建的图表

嗨,欢迎来到本教程,我将在这里教你如何使用 Python 和 Matplotlib 创建上面的图表。

我喜欢这种数据可视化的地方在于它以简洁而美观的方式展示了各国在某一指标上的排名。

使用标准折线图显示实际值的替代方案在某些国家接近彼此或某些国家表现大幅超越其他国家时,会显得混乱。

如果你想访问本教程的代码,可以在这个 GitHub 仓库中找到。

如果你喜欢这个教程,记得查看我的其他 Matplotlib 教程。

奥斯卡·利奥

奥斯卡·利奥

Matplotlib 教程

查看列表8 个故事

我们开始吧。

关于数据

我为本教程创建了一个包含今天前十大经济体 GDP 值的简单 CSV 文件。

作者截图

数据来自 世界银行,指标的全称是“GDP(2015 年不变美元)”。

如果你想了解更多关于测量 GDP 的不同方法,可以查看这个故事,在那里我使用了相同类型的数据可视化。

## 4 Charts About Our Planet’s Largest Economies That Will Improve Your Understanding of the World

他们说知识就是力量。

medium.datadriveninvestor.com

让我们继续教程吧。

第 1 步:创建排名

第一步是对数据集中每年的国家进行排名,这在 pandas 中很容易做到。

def create_rankings(df, columns):
    rank_columns = ["rank_{}".format(i) for i in range(len(columns))]
    for i, column in enumerate(columns):
        df[rank_columns[i]] = df[column].rank(ascending=False)

    return df, rank_columns

结果列如下所示。

截图由作者提供

这就是我们继续进行数据可视化所需的所有预处理。

第 2 步:创建和样式化网格

现在我们已经准备好了数据,是时候创建一个可以绘制线条和旗帜的网格了。

这是一个使用 Seaborn 创建整体样式的函数。它定义了背景颜色和字体系列等内容。我还移除了脊线和刻度。

def set_style(font_family, background_color, grid_color, text_color):
    sns.set_style({
        "axes.facecolor": background_color,
        "figure.facecolor": background_color,

        "axes.grid": True,
        "axes.axisbelow": True,

        "grid.color": grid_color,

        "text.color": text_color,
        "font.family": font_family,

        "xtick.bottom": False,
        "xtick.top": False,
        "ytick.left": False,
        "ytick.right": False,

        "axes.spines.left": False,
        "axes.spines.bottom": False,
        "axes.spines.right": False,
        "axes.spines.top": False,
    }
)

我使用以下值运行该函数。

font_family = "PT Mono"
background_color = "#FAF0F1"
text_color = "#080520"
grid_color = "#E4C9C9"

set_style(font_family, background_color, grid_color, text_color)

为了创建实际的网格,我有一个格式化 y 轴和 x 轴的函数。它接受几个参数,让我可以尝试不同的设置,例如标签的大小。

def format_ticks(ax, years, padx=0.25, pady=0.5, y_label_size=20, x_label_size=24):
    ax.set(xlim=(-padx, len(years) -1 + padx), ylim=(-len(df) - pady, - pady))

    xticks = [i for i in range(len(years))]
    ax.set_xticks(ticks=xticks, labels=years)

    yticks = [-i for i in range(1, len(df) + 1)]
    ylabels = ["{}".format(i) for i in range(1, len(df) + 1)]
    ax.set_yticks(ticks=yticks, labels=ylabels)

    ax.tick_params("y",labelsize=y_label_size, pad=16)
    ax.tick_params("x", labeltop=True, labelsize=x_label_size, pad=8)

这是我运行到目前为止的所有内容的效果。

# Load data
years = ["2000", "2005", "2010", "2015", "2020", "2022"]
df = pd.read_csv("rankings.csv", index_col=None)
df, rank_columns = create_rankings(df, years)

# Create chart
fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(15, 1.6*len(df)))
format_ticks(ax, years)

这是生成的网格。

网格由作者创建

现在我们可以开始添加一些数据了。

第 3 步:添加线条

我希望有一条线显示数据集中每个国家每年的排名——在 Matplotlib 中这是一个简单的任务。

def add_line(ax, row, columns, linewidth=3):
    x = [i for i in range(len(columns))]
    y = [-row[rc] for rc in columns]

    ax.add_artist(
        Line2D(x, y, linewidth=linewidth, color=text_color)
    )

然后我像这样对数据集中的每一行运行该函数。

# Load data
years = ["2000", "2005", "2010", "2015", "2020", "2022"]
df = pd.read_csv("rankings.csv", index_col=None)
df, rank_columns = create_rankings(df, years)

# Create chart
fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(15, 1.6*len(df)))
format_ticks(ax, years)

# Draw lines
for i, row in df.iterrows():
    add_line(ax, row, rank_columns)

这是我得到的结果。

图表由作者创建

我对每一条线使用相同的颜色,因为我想用国家旗帜来引导视线。使用每条线的独特颜色是有意义的,但效果看起来很混乱。

第 4 步:绘制饼图

我想在不添加文本的情况下,指示一个国家的经济如何随时间增长。相反,我的目标是以视觉格式传达信息。

我的想法是在每个点上绘制一个饼图,显示一个国家的经济规模与其最佳年份的对比。

我正在使用 PIL 创建饼图图像,但你可以直接使用 Matplotlib。我不这样做是因为我遇到了一些比例问题。

def add_pie(ax, x, y, ratio, size=572, zoom=0.1):
    image = Image.new('RGBA', (size, size))
    draw = ImageDraw.Draw(image)

    draw.pieslice((0, 0, size, size), start=-90, end=360*ratio-90, fill=text_color, outline=text_color)
    im = OffsetImage(image, zoom=zoom, interpolation="lanczos", resample=True, visible=True)

    ax.add_artist(AnnotationBbox(
        im, (x, y), frameon=False,
        xycoords="data",
    ))

尺寸参数的值略大于我的旗帜图像的尺寸,这些图像的大小是 512x512。稍后,我想将这些旗帜粘贴到饼图上。

这是更新后的代码。

# Load data
years = ["2000", "2005", "2010", "2015", "2020", "2022"]
df = pd.read_csv("rankings.csv", index_col=None)
df, rank_columns = create_rankings(df, years)

# Create chart
fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(15, 1.6*len(df)))
format_ticks(ax, years)

# Draw lines
for i, row in df.iterrows():
    add_line(ax, row, rank_columns)

    for j, rc in enumerate(rank_columns):
        add_pie(ax, j, -row[rc], ratio=row[years[j]] / row[years].max())

这是结果。

图表由作者创建

它开始看起来很有信息量了,因此是时候让它变得更美观了。

第 5 步:添加旗帜

我喜欢在图表中使用旗帜,因为它们实在是太美了。

在这里,旗帜的目的是使图表视觉上更具吸引力,解释我们正在查看哪些国家,并引导视线沿着线条移动。

我使用了这些圆角旗帜。它们需要许可证,因此不幸的是,我不能分享,但你可以在其他地方找到类似的旗帜。

我在将饼图和旗帜对齐时遇到了一些问题,因此,我决定重写 add_pie()函数,而不是创建一个单独的函数来添加旗帜。

def add_pie_and_flag(ax, x, y, name, ratio, size=572, zoom=0.1):
    flag = Image.open("<location>/{}.png".format(name.lower()))
    image = Image.new('RGBA', (size, size))
    draw = ImageDraw.Draw(image)
    pad = int((size - 512) / 2)

    draw.pieslice((0, 0, size, size), start=-90, end=360*ratio-90, fill=text_color, outline=text_color)
    image.paste(flag, (pad, pad), flag.split()[-1])

    im = OffsetImage(image, zoom=zoom, interpolation="lanczos", resample=True, visible=True)

    ax.add_artist(AnnotationBbox(
        im, (x, y), frameon=False,
        xycoords="data",
    ))

我将它添加在饼图函数之后。

# Load data
years = ["2000", "2005", "2010", "2015", "2020", "2022"]
df = pd.read_csv("rankings.csv", index_col=None)
df, rank_columns = create_rankings(df, years)

# Create chart
fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(15, 1.6*len(df)))
format_ticks(ax, years)

# Draw lines
for i, row in df.iterrows():
    add_line(ax, row, rank_columns)

    for j, rc in enumerate(rank_columns):
        add_pie_and_flag(
            ax, j, -row[rc], 
            name=row.country_name,
            ratio=row[years[j]] / row[years].max()
        )

现在你可以欣赏到使用旗帜的视觉魔法。这与之前的输出相比,差异巨大。

图表由作者创建

我们突然有了看起来既美观又易于理解的东西。最后一步是添加一些有用的信息。

第 5 步:添加额外信息

由于不是每个人都能记住所有的旗帜,我想在右侧添加国家的名称。

我还想展示经济规模及每个国家与最高排名国家的对比。

这是我为此编写的代码。

def add_text(ax, value, max_value, y):
    trillions = round(value / 1e12, 1)
    ratio_to_max = round(100 * value / max_value, 1)

    text = "{}\n${:,}T ({}%)".format(
        row.country_name, 
        trillions,
        ratio_to_max
    )

    ax.annotate(
        text, (1.03, y), 
        fontsize=20,
        linespacing=1.7,
        va="center",
        xycoords=("axes fraction", "data")
    )

如前所述,我将函数添加到主代码块中。请注意,我还添加了一个标题。

years = ["2000", "2005", "2010", "2015", "2020", "2022"]
df = pd.read_csv("rankings.csv", index_col=None)
df, rank_columns = create_rankings(df, years)

fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(15, 1.6*len(df)))
format_ticks(ax, years)

for i, row in df.iterrows():
    add_line(ax, row, rank_columns)

    for j, rc in enumerate(rank_columns):
        add_pie_and_flag(
            ax, j, -row[rc], 
            name=row.country_name,
            ratio=row[years[j]] / row[years].max()
        )

    add_text(ax, value=row[years[-1]], max_value=df.iloc[0][years[-1]], y=-(i + 1))
    plt.title("Comparing Today's Largest Economies\nGDP (constant 2015 us$)", linespacing=1.8, fontsize=32, x=0.58, y=1.12)

完成。

图表由作者创建

就这些,我们完成了。

结论

今天,你学到了另一种可视化的方法。

我喜欢这种数据可视化方式,因为它视觉效果好,并且用很少的文字传达了大量信息。

如果你和我一样喜欢这种方式,确保订阅我的频道,获取更多类似的内容! 😃

感谢阅读。

如何使用 Matplotlib 创建六边形地图

原文:towardsdatascience.com/how-to-create-hexagon-maps-with-matplotlib-eb5eef82ab2c

Matplotlib 教程

使用形状表示地理信息

Oscar LeoTowards Data Science Oscar Leo

·发表于 Towards Data Science ·阅读时间 7 分钟·2023 年 11 月 21 日

--

图表由作者创建

让我们制作一些地图吧!🗺

嗨,欢迎来到新的 matplotlib 教程。这一次,我将教你如何创建像上面这样的有洞察力的六边形地图。

可视化地理信息是困难的,因为区域(如国家)在大小和形状上有所不同。

结果是,当你使用常规地图绘制数据时,一些区域很难看清。

将国家名称或数值添加到你的可视化中也是困难的。

一种消除这些差异的替代方案是使用六边形地图。

这个想法是将每个区域表示为一个六边形,并将它们排列成类似实际地图的方式。

由于每个六边形的形状相同,添加信息以结构化的方式并创建美观的数据可视化变得容易。

本教程将教你如何使用美国总统选举的数据来做到这一点。

(不要忘记查看我的其他 Matplotlib 教程

让我们开始吧。🚀

第一步:导入库

我们首先导入所需的库。

import pandas as pd
from matplotlib.patches import Polygon
import matplotlib.pyplot as plt
import seaborn as sns

import matplotlib.patheffects as PathEffects

就这样。

第二步:创建一个 seaborn 样式

接下来,我们使用 seaborn 来设置背景和字体。 我使用的是 Work Sans#F4EBCD,但可以随意尝试。

font_family = "Work sans"
background_color = "#E0E9F5"

sns.set_style({
    "axes.facecolor": background_color,
    "figure.facecolor": background_color,
    "font.family": font_family,
})

仅供参考: 如果我想将图表添加到信息图或类似内容中,我通常使用 background_color="#00000000" 以获得透明背景。

现在进入有趣的部分。

第三步:获取数据

我已经使用以下数据集准备了一个包含每个美国州投票数的 CSV 文件:美国总统 1976–2020(公共领域许可)。

这是如何访问它的方法。

df = pd.read_csv(
    "https://raw.githubusercontent.com/oscarleoo/matplotlib-tutorial-data/main/us_election_2020.csv"
)

每一行代表一个州,并存储对民主党、共和党和“其他”政党的投票数。

作者截图

幸运的是,我准备了另外两列叫做 x 和 y,它们代表每个六边形的中心。

第 4 步:绘制六边形边界

既然我们有了数据,我们可以立即使用每行定义的中心绘制六边形的边界。

我们第一个与 Matplotlib 相关的函数接受一个row以及六边形的widthheight

它将这些信息结合起来,创建两个坐标列表,并以正确的格式返回它们。

def get_hexagon_corners(row, width, height):
    cx, cy = row.x, row.y
    w2, h4 = width / 2, height / 4
    x = [cx, cx+w2, cx+w2, cx, cx-w2, cx-w2]
    y = [cy-2*h4, cy-h4, cy+h4, cy+2*h4, cy+h4, cy-h4]
    return list(zip(x, y))

现在,让我们定义draw_hexagon(),它接受一个row并使用get_hexagon_corners()在正确的位置绘制六边形。

def draw_hexagon(ax, row, scale=1):
    width = 3 * scale
    height = 4 * scale

    xy = get_hexagon_corners(row, width, height)
    b_hexagon = Polygon(xy=xy, closed=True, facecolor="#000000", edgecolor="#000", linewidth=4)
    ax.add_artist(b_hexagon)

    # Additional functions

我硬编码widthheight可能看起来有些奇怪,但你无需更改这些值,因此并不重要。

我选择了width=3height=4,因为它给我一个好看的六边形。我使用scale参数来调整六边形之间的间距。

现在,我们可以将此函数与我们的标准 Matplotlib 代码一起运行。

fig, ax = plt.subplots(figsize=(20, 20))
ax.set(xlim=(0, 37), ylim=(0, 27))

for i, row in df.iterrows():
    draw_hexagon(ax, row, scale=0.9)

ax.set_aspect(0.9, adjustable='box')
plt.axis("off")
plt.show()

我们得到以下图形。

如你所见,我将 51 个六边形排列成类似于美国的形状。

这是一个很好的开始!

第 5 步:添加颜色

定义六边形颜色的方法有很多种。

最常见的替代方案是根据类别定义颜色,或者基于诸如 GDP 等值的渐变,其中较低的值会导致例如更深的颜色。

为了让事情对你更有趣,我决定采取另一种方法。

我不想选择基础方法,而是根据每个政党的投票数量来为每个六边形上色。

一个六边形应该具有所有三种颜色,但根据投票数量的不同,比例会有所不同。

首先,我创建了一个函数,返回给定中心的六边形的最大值和最小值。

def get_boundries(row, width, height):
    x_min = row.x - width / 2
    x_max = row.x + width / 2
    y_min = row.y - height / 2
    y_max = row.y + height / 2
    return x_min, x_max, y_min, y_max

接下来,我们有fill_hexagon函数,它定义了我们希望用颜色填充的区域。

有两个参数特别有趣。

  • ratio定义了要填充六边形的量(在垂直方向上,而不是按面积)。

  • top定义了我们是从顶部还是底部填充六边形。这对民主党和共和党会有所不同,你可以看到我们根据top定义了yy_starth4

def fill_hexagon(row, width, height, ratio, top=True):
    x_min, x_max, y_min, y_max = get_boundries(row, width, height)

    y = ratio * height
    y = y_max - y if top else y_min + y
    y_start = y_max if top else y_min
    h4 = height / 4 if top else - (height / 4)

    if ratio < 0.25:
        x_shift = 2 * ratio * width
        x = [row.x-x_shift, row.x, row.x+x_shift]
        y = [y, y_start, y]
    elif ratio < 0.75:
        x = [x_min, x_min, row.x, x_max, x_max]
        y = [y, row.y + h4, y_start, row.y + h4, y]
    else:
        x_shift = 2 * (1 - ratio) * width
        x = [row.x-x_shift, x_min, x_min, row.x, x_max, x_max, row.x+x_shift]
        y = [y, row.y - h4, row.y + h4, y_start, row.y + h4, row.y - h4, y]

    return list(zip(x, y))

一开始理解if语句并不容易。

这里有一张图解释了我们得到不同形状,并需要针对不同的阈值分别处理它们。

作者插图

现在,我们定义d_ratior_ratio以传递给draw_hexagon()并为民主党和共和党创建Polygons

def draw_hexagon(ax, row, edgecolor="#000", scale=1):
    width = 3 * scale
    height = 4 * scale

    xy = get_hexagon_corners(row, width, height)
    b_hexagon = Polygon(xy=xy, closed=True, facecolor="#000000", edgecolor="#000", linewidth=4)
    ax.add_artist(b_hexagon)

    # Additional functions
    d_ratio = row.democrat / row.total
    r_ratio = row.republican / row.total

    d_hexagon = Polygon(xy=fill_hexagon(row, width, height, d_ratio, top=False), closed=True, facecolor="blue")
    r_hexagon = Polygon(xy=fill_hexagon(row, width, height, r_ratio, top=True), closed=True, facecolor="red")

    ax.add_artist(d_hexagon)
    ax.add_artist(r_hexagon)

如果我们重新运行上一节的 matplotlib 代码,我们会得到以下图表。

请注意,水平黑线的厚度基于“其他”选项的投票数量而有所不同。

第 6 步:添加文本

大多数数据可视化需要一些文本来使其有意义。我想添加每个政党的州缩写和投票百分比。

def add_text(row):
    center = (row.x, row.y - 0.2)
    d_ratio = row.democrat / row.total
    r_ratio = row.republican / row.total
    o_ratio = row.other / row.total

    a1 = plt.annotate(row.state, center, ha="center", va="bottom", fontsize=26, fontweight="bold", color="w")
    a2 = plt.annotate("{:.0f}/{:.0f}/{:.0f}".format(100 * d_ratio, 100 * r_ratio, 100 * o_ratio), (center[0], center[1] - 0.12), ha="center", va="top", fontsize=14, fontweight="bold", color="w")
    a1.set_path_effects([PathEffects.withStroke(linewidth=1, foreground="#000000")])
    a2.set_path_effects([PathEffects.withStroke(linewidth=1, foreground="#000000")])

我在 draw_hexagon() 之后直接添加了 add_text()。我还添加了年份以提供额外信息。

fig, ax = plt.subplots(figsize=(20, 20))
ax.set(xlim=(0, 37), ylim=(0, 27))

for i, row in df.iterrows():
    draw_hexagon(ax, row, scale=0.9)
    add_text(row)

plt.annotate("2020", xy=(0.5, 0.93), fontsize=96, xycoords="axes fraction", ha="center", va="center", fontweight="bold", color="#000")

ax.set_aspect(0.9, adjustable='box')
plt.axis("off")
plt.show()

运行代码会得到以下的六边形地图。

图表由作者创建

就这样,我完成了我们要创建的最终图表。我使用 KeyNotes 添加了一些填充,但你几乎可以使用任何工具。

额外内容:这是我如何使用这个可视化的

我有一个免费的通讯,叫做 Data Wonder,在里面我分享美丽且富有洞察的数据可视化。

在 “Visualizing Election Results From 1976 to 2020” 版本中,我为上面的图表定义了一个透明背景。我使用 Corel Vector 创建了网格、渐变、标题和图例。

真是太酷了! 😄

结论

六边形图表可能看起来复杂,但使用 Matplotlib 创建起来出奇的简单。

最大的挑战是将六边形对齐,使其类似于地图,同时保持顺序有意义。

这一次,我们学习了如何为美国制作这种图表,你可以将选举数据更改为任何你觉得有趣的信息。

例如,当我创建一个名为 “The Escalating Crisis: Drug Overdose Deaths Across the U.S” 的可视化时,我使用了相同的代码。

感谢阅读,下次见! 😃

如何创建高性能的数据产品?

原文:towardsdatascience.com/how-to-create-high-performance-data-products-717ff3a47d38

因为合适的数据产品可以提升公司的收入,提供竞争优势,而未来极具吸引力!

Rashi DesaiTowards Data Science Rashi Desai

·发表于 Towards Data Science ·4 分钟阅读·2023 年 3 月 15 日

--

图片由 Studio Blackthorns 提供,来源于 Unsplash

在今天快速扩展的数据领域中,数据与分析驱动着公司的价值、使命和愿景。数据是战略、规划、风险、过程改进、治理等方面的基石。数据专业人员处理数百万吉字节的数据,分析数据,发现隐性洞察以解决问题,创建工具,并引导企业决策。

为了适应业务不断变化的需求和要求,必须有一种方法来最大化我们设计的工具和解决方案的潜力。通过这篇博客,我希望改变你思考的方式,并给你一个新的视角,以充分挖掘你日常工作的潜力。

什么是数据产品?

现在的工作环境中,围绕着多个数据产品来完成日常工作,相关性只会日益增加。

数据产品是一个工具或应用程序,利用数据帮助企业改善决策过程。

数据产品可以是报告、端到端的数据管道,或具有多个组件的仪表盘,包括其界面、API 或命令行 SQL。例如,数据产品可以是一个消费者 360 仪表盘,结合了消费者人口统计信息、营销活动、预测和历史销售、市场份额以及所有与消费者相关的数据。

一些著名的数据产品示例——包括 Google Analytics、如 Bloomberg Terminal 的金融终端、Salesforce 的 Einstein AI 预测分析等。

作为数据专业人士,你可能已经创建或参与了许多数据产品而未意识到你所增加的价值。数据产品使用各种数据科学与分析技术,如预测分析、描述性数据建模、数据挖掘、机器学习、风险管理以及各种分析方法,将数据转化为非数据人员可以理解的形式。

为什么要创建数据产品?

阅读数据产品的定义,你可能会想,我已经在创建数据产品,为什么还需要标记和营销为数据产品以提升价值?我来告诉你原因——

随着企业的发展和不断坚定地推动数据驱动决策,作为数据专业人士,我们需要跟上步伐,将思维方式从项目导向转变为产品导向。如今的组织致力于通过数据驱动的决策实现业务目标。现在你可能会问,谁将推动公司采纳这些洞察?——数据产品。

从事数据项目是好的;你在明确的时间和金钱投入下完成可交付成果,项目完成后你可以继续前进。然而,数据产品不仅旨在解决问题,还可以经历多个阶段,直到其价值实现。

当你从数据项目转向数据产品时,你为公司创造了竞争优势,改善了服务或产品,优化了经济价值,更重要的是,作为数据专业人士,你对自己创造的东西充满信心。

项目专注于完成任务,而产品则最大化用户价值

如何创建数据产品?

数据产品的生命周期与标准产品开发过程相似。

作为数据专业人士,你不需要将数据产品视为企业级才能对业务产生影响。具有明确使命、在明确范围内解决实际问题的数据产品就是你所需要的。毕竟,数据产品产生的价值与技术的使用无关,而与其使用和实际应用有关。

因为如果你知道要做什么,如何做就不会是问题。

我通常从创建一个数据产品画布开始,该画布展示了数据产品的生命周期。你可以创建一个文档,添加笔记,或者创建一个实际的画布——在绘制数据产品时,我会包括以下信息:

  1. 问题陈述——我们要解决什么?

  2. 增值——我们为何要解决这个问题?

  3. 数据映射——数据来自哪里?

  4. 假设——将要测试的场景

  5. 客户和利益相关者——谁参与其中?

  6. 依赖关系和风险

  7. 将要制定的战略行动

  8. 应该监控的关键绩效指标(KPIs)

  9. 产品对业务的表现和/或影响

现在你已经列出了步骤,只需按顺序逐一执行,最终你—

探索 > 创造 > 学习 > 迭代 > 分享

根据我的经验,数据产品通常需要对数据模型和用户界面进行大量的迭代。迭代的速度很重要,因此,在开发数据产品时,你可以瞄准保持模型简单——一个能够完成任务而不需要太多复杂操作的模型。

产品、数据和商业思维的协作是一个优秀的领导者今天所需的所有条件。 我坚信任何数据专业人士都可以加快他们开发强大数据产品的进程,这些产品解决核心用户需求,创造持久的竞争优势,并推动业务发展。

这就是我在这篇博客中的所有内容。感谢阅读!希望你觉得这篇文章有趣。请在评论中告诉我你在数据方面的经验和旅程,以及你在 2023 年期待什么!

如果你喜欢阅读这样的故事,可以考虑通过这个链接注册成为 Medium 会员

祝数据探险愉快!

拉希是来自芝加哥的数据高手,她喜欢将数据可视化,并创造有洞察力的故事来传达见解。她是一名全职医疗数据分析师,并在周末用一杯热巧克力写博客分享数据…

如何使用 Plotly 创建地图图

原文:towardsdatascience.com/how-to-create-map-plots-with-plotly-26111d38fff9

来自显著火山爆发数据库的 5 个示例

Caroline ArnoldTowards Data Science Caroline Arnold

·发布于Towards Data Science ·阅读时间 5 分钟·2023 年 9 月 10 日

--

Willian Justen de Vasconcellos 的照片,来源于Unsplash

Plotly是一个很棒的开源数据可视化库。在这篇博客文章中,我将展示如何使用 plotly 生成制图图,利用 Python 后端进行操作。

为了说明,我将使用由美国国家环境信息中心发布的显著火山爆发数据库,依据美国政府工作许可。数据集可以在这里下载:public.opendatasoft.com/explore/dataset/significant-volcanic-eruption-database/information/

你将看到以下五种可视化:

  1. 显著火山爆发的全球分布

  2. 北美火山类型

  3. 与海啸相关的火山爆发

  4. 最具破坏性的火山爆发

  5. 有趣的地图投影

对于那些对使用 plotly 进行数据分析感兴趣的读者,请参阅我最近关于女子世界杯数据可视化的文章:

## 2023 年女子世界杯通过 Plotly 可视化

数据科学家的五种图表回顾

towardsdatascience.com

准备数据

下载火山喷发数据库后,我们将其加载为 pandas DataFrame。DataFrame 与 Plotly 自然集成,便于数据分析。我们将编码火山喷发是否与火山或地震相关的列转换为 True/False 值,并添加了表示喷发的纬度经度的新列。

重大火山喷发的全球分布

第一个可视化展示了重大火山喷发的全球分布。这是一个 scatter_geo 散点图,分别使用经度和纬度作为xy坐标。地图投影设置为 natural earth ,单个事件的颜色和大小根据其火山爆发指数进行调整。颜色比例尺使用了适合火山主题的内置熔岩顺序比例尺。

火山爆发指数散点图。图像:作者。

如我们所见,火山喷发并没有均匀分布在全球,而是集中在某些地区。这些地区与构造边界有关,例如美国西海岸和太平洋火圈。

这是生成可视化的代码:

北美的火山类型

火山有不同类型,其中一些比其他类型更频繁。在下一个可视化中,我们专注于北美。再次使用 scatter_geo 图,并使用关键字参数 scope='north america' 自动设置显示的地图范围到所需区域。

我们观察到著名的夏威夷盾形火山以及太平洋沿岸构造边界附近的大量成层火山。

北美的火山类型。图像:作者。

可视化是通过以下代码生成的:

与海啸相关的火山喷发

火山喷发通常与其他灾难事件如地震和海啸有关。在此可视化中,我们专注于太平洋,并在地形图上展示火山事件。颜色编码了事件是否伴有海啸。

Mapbox 瓦片可以通过 scatter_mapbox 获得。

伴随海啸的重大火山喷发。图像:作者。

此可视化是通过以下代码生成的:

最致命的火山喷发

现在我们转向火山喷发的副作用。数据集中涵盖的火山喷发规模巨大,通常伴随有大量的人员伤亡。

在这个可视化中,我们使用叠加了 mapbox 瓦片的密度图来说明火山喷发的致命影响。mapbox 瓦片是制图的,显示的是州界而不是像之前的可视化那样的地形图。颜色刻度的强度表示与个别喷发相关的总死亡人数。

火山喷发的致命影响。图片:作者。

发生在有人口的地区的火山喷发可能会造成更多的损害。从地图上看,印尼的岛屿群突显出来。该地区是火山活动频繁的太平洋火带的一部分,同时也是人口稠密的区域。在欧洲,罗马时期维苏威火山的灾难性喷发导致了高死亡人数。

生成此可视化的代码如下:

有趣的地图投影

由于地球不是平坦的,任何在二维平面上对其表面的表示都必须依赖于地图投影。投影可以强调地图的不同方面 [en.wikipedia.org/wiki/Map_projection]。

Plotly 提供了广泛的内置地图投影,可以与地理图表一起使用。要选择另一种地图投影,请在 scatter_geo 和其他绘图函数中更改 projection 关键字。

在这里,我们再次可视化按爆发性着色的火山,但使用的是 Foucaut 地图投影。这是一种面积保持投影,即离赤道越远的区域不会放大。注意与我们习惯的标准地图投影相比,格林兰岛显得多么微小!

Foucaut 投影。图片:作者。

摘要

我们使用 plotly 库中的制图散点图生成了五个可视化图。Plotly 提供了可以使用 mapbox 库的瓦片自动添加到图中的地形图。图表的自定义方式与标准图表类似。

制图图表非常适合展示数据集中事件的区域影响。与其他软件包(如 cartopy 和 folium)相比,plotly 库提供的功能较少。然而,如果你希望快速在地图上可视化数据,plotly 允许你在不深入了解地理空间数据格式的情况下实现这一点。

进一步阅读和参考

查看 Github 上的笔记本以生成图表:

[## medium_notebooks/plotly/volcanic_activity.ipynb 在 main · crlna16/medium_notebooks

通过在 GitHub 上创建账户来为 crlna16/medium_notebooks 的开发做出贡献。

github.com

使用 LaTeX 创建出版级图表:第二部分

原文:towardsdatascience.com/how-to-create-publication-ready-plots-with-latex-part-ii-11ea811c5c3b?source=collection_archive---------9-----------------------#2023-04-11

让我们来看看堆叠图!

Aruna PisharodyTowards Data Science Aruna Pisharody

·

关注 发表在 Towards Data Science ·8 分钟阅读·2023 年 4 月 11 日

--

图片由 Isaac Smith 提供,来自 Unsplash

大家好!我又回来了,这次带来了一篇关于用 LaTeX 绘图的文章!如果你错过了我之前的文章,或者想要稍微回顾一下(我知道自从第一篇文章以来已经有一段时间了!),你可以在这里阅读它。

在本文中,我将介绍一种特定类型的图表:堆叠图(或在 LaTeX 中称为groupplots;在本文中我将这两个术语交替使用)。我发现这些图表在许多场景下非常有用。然而,每种使用情况可能需要不同的定制,我无法在一篇文章中涵盖所有内容。所以我决定只专注于如何在 LaTeX 中开始使用堆叠图。特别是,我将详细讲述我在第一次使用这种图表时遇到的最令人沮丧的挑战之一:向堆叠图中添加图例!

那么,废话不多说,我们开始吧!

LaTeX 中的堆叠图

让我们使用上一篇文章中的相同的flights.csv数据集(来源:seaborn.pydata.org)。然而,这次我们将绘制 4 个连续年份(1949 年—1952 年)每月乘客数量的变化。使用我们上次学到的知识,我们可以这样绘制场景:

一个简单的图表(作者提供的图片)

创建此图表的 LaTeX 代码如下所示。

 \begin{tikzpicture}
\begin{axis}[
    myplotstyle,
    xtick=data,
    table/col sep=comma,
    xticklabels from table={Fig_groupplot_1.csv}{month},
    legend pos=outer north east,
    legend entries={1949,1950,1951,1952},
    xlabel={Month},
    ylabel={No. of passengers},
    x tick label style={rotate=90, /pgf/number format/.cd, set thousands separator={}},
]
\addplot+[smooth, color=black,mark=*,mark options={fill=white,scale=1.5}] table[x expr=\coordindex,y=passengers, col sep=comma] {Fig_groupplot_1.csv};
\addplot+[smooth, color=black,mark=triangle*, mark options={fill=white,scale=1.5}] table[x expr=\coordindex,y=passengers, col sep=comma] {Fig_groupplot_2.csv};
\addplot+[smooth, color=black,mark=square*,mark options={fill=white,scale=1.5}] table[x expr=\coordindex,y=passengers, col sep=comma] {Fig_groupplot_3.csv};
\addplot+[smooth, color=black,mark=diamond*,mark options={fill=white,scale=1.5}] table[x expr=\coordindex,y=passengers, col sep=comma] {Fig_groupplot_4.csv};
\end{axis}
\end{tikzpicture}

尽管这个图表本身看起来已经很好了,但让我们看看如何将每年的图表分开并将它们堆叠在一起(因为这正是标题中承诺的内容!)。

我们可以使用groupplot选项来实现。在这种情况下,我们需要先将\begin{axis}...\end{axis}替换为\begin{groupplot}...\end{groupplot}。我们还需要为这个 groupplot 定义一些基本的样式选项;我在这里包括的一些选项有 groupplot 的大小、x 轴和 y 轴标签及刻度标记的位置,以及 groupplot 中各个图表之间的垂直距离。接下来,我们需要在每个\addplot命令之前添加一个命令\nextgroupplot。所以完整代码如下所示:

\begin{tikzpicture}
\begin{groupplot}[
        group style={
        group name=myplot,
        group size=1 by 4,
        xlabels at=edge bottom,
        xticklabels at=edge bottom,
        ylabels at=edge right,
        vertical sep=0pt,
    }, 
    myplotstyle,
    xtick=data,
    table/col sep=comma,
    xticklabels from table={Fig_groupplot_1.csv}{month},
    width=10cm, height=6cm,
    x tick label style={rotate=90, /pgf/number format/.cd},
    xlabel={Month},
    ylabel={No. of passengers},
]
\nextgroupplot
\addplot+[smooth, color=black,mark=*,mark options={fill=white,scale=1.5}] table[x expr=\coordindex,y=passengers, col sep=comma] {Fig_groupplot_1.csv};

\nextgroupplot
\addplot+[smooth, color=black,mark=triangle*, mark options={fill=white,scale=1.5}] table[x expr=\coordindex,y=passengers, col sep=comma] {Fig_groupplot_2.csv};

\nextgroupplot
\addplot+[smooth, color=black,mark=square*,mark options={fill=white,scale=1.5}] table[x expr=\coordindex,y=passengers, col sep=comma] {Fig_groupplot_3.csv};

\nextgroupplot
\addplot+[smooth, color=black,mark=diamond*,mark options={fill=white,scale=1.5}] table[x expr=\coordindex,y=passengers, col sep=comma] {Fig_groupplot_4.csv};
\end{groupplot}
\end{tikzpicture}

上述代码生成的图表将如下所示:

一个没有图例的简单堆叠图(作者提供的图片)

现在让我们继续在这个 groupplot 中包含图例。对于我们的图表,有三种方法可以做到这一点:

选项 1:在每个单独的图表中添加图例

对于这个选项,我们只需在每个 \addplot之后添加命令 \addlegendentry{*year*},就完成了! 注意:如果你在一个图表中有多个*\addplot*命令,只需在每个*\addplot*命令之后添加*\addlegendentry{year}*命令。

完整代码如下所示:

\begin{tikzpicture}
\begin{groupplot}[
        group style={
        group name=myplot,
        group size=1 by 4,
        xlabels at=edge bottom,
        xticklabels at=edge bottom,
        ylabels at=edge right,
        vertical sep=0pt,
    }, 
    myplotstyle,
    xtick=data,
    table/col sep=comma,
    xticklabels from table={Fig_groupplot_1.csv}{month},
    width=10cm, height=6cm,
    x tick label style={rotate=90, /pgf/number format/.cd},
    xlabel={Month},
    ylabel={No. of passengers},
]
\nextgroupplot
\addplot+[smooth, color=black,mark=*,mark options={fill=white,scale=1.5}] table[x expr=\coordindex,y=passengers, col sep=comma] {Fig_groupplot_1.csv}; 
\addlegendentry{1949};

\nextgroupplot
\addplot+[smooth, color=black,mark=triangle*, mark options={fill=white,scale=1.5}] table[x expr=\coordindex,y=passengers, col sep=comma] {Fig_groupplot_2.csv};
\addlegendentry{1950};

\nextgroupplot
\addplot+[smooth, color=black,mark=square*,mark options={fill=white,scale=1.5}] table[x expr=\coordindex,y=passengers, col sep=comma] {Fig_groupplot_3.csv};
\addlegendentry{1951};

\nextgroupplot
\addplot+[smooth, color=black,mark=diamond*,mark options={fill=white,scale=1.5}] table[x expr=\coordindex,y=passengers, col sep=comma] {Fig_groupplot_4.csv};
\addlegendentry{1952};

\end{groupplot}
\end{tikzpicture}

选项 2:在每个图表中添加一个文本节点以指定相应的年份

为此,我将所有图表标记更改为相同的。然后,我们为所有单独的图表添加一个节点,指定每个图表的年份。(注意:我们在上一篇文章中制作的均值图表也做了类似的处理。) 这个节点可以这样定义(你可以在我的上一篇文章中详细阅读使用 rel axis cs 进行节点定位的内容 这里):

\node [text width=5em] at (rel axis cs: 0.15,0.9){\textbf{Year}}

在这里,我还指定了节点的宽度,以方便操作。

我发现这在这种特定情况下是更容易的选项(每个图中只有一列要绘制的数据)。以下是选项 2 的完整代码示例:

\begin{tikzpicture}
\begin{groupplot}[
        group style={
        group name=myplot,
        group size=1 by 4,
        xlabels at=edge bottom,
        xticklabels at=edge bottom,
        vertical sep=0pt,
    }, 
    myplotstyle,
    xtick=data,
    table/col sep=comma,
    xticklabels from table={Fig_groupplot_1.csv}{month},
    width=10cm, height=6cm,
    x tick label style={rotate=90, /pgf/number format/.cd},
    xlabel={Month},
    ylabel={No. of passengers},
]
\nextgroupplot[]
\node [text width=5em] at (rel axis cs: 0.15,0.9){\textbf{1949}};
\addplot[smooth, color=black,mark=*,mark options={fill=white,scale=1.5}] table[x expr=\coordindex,y=passengers, col sep=comma] {Fig_groupplot_1.csv};

\nextgroupplot[]
\node [text width=5em] at (rel axis cs: 0.15,0.9){\textbf{1950}};
\coordinate (top) at (rel axis cs:0,1);
\addplot[smooth, color=black,mark=*, mark options={fill=white,scale=1.5}] table[x expr=\coordindex,y=passengers, col sep=comma] {Fig_groupplot_2.csv};

\nextgroupplot[]
\node [text width=5em] at (rel axis cs: 0.15,0.9){\textbf{1951}};
\coordinate (top) at (rel axis cs:0,1);
\addplot[smooth, color=black,mark=*,mark options={fill=white,scale=1.5}] table[x expr=\coordindex,y=passengers, col sep=comma] {Fig_groupplot_3.csv};

\nextgroupplot[]
\node [text width=5em] at (rel axis cs: 0.15,0.9){\textbf{1952}};
\coordinate (top) at (rel axis cs:0,1);
\addplot[smooth, color=black,mark=*,mark options={fill=white,scale=1.5}] table[x expr=\coordindex,y=passengers, col sep=comma] {Fig_groupplot_4.csv};
\end{groupplot}
\end{tikzpicture}

选项 3:将所有图例条目合并到一个图例框中

在这种情况下,我将使用\label命令为所有单独的图设置标签,例如:\label{plots:plot1}\label{plots:plot2}\label{plots:plot3}\label{plots:plot4}。接下来,我将定义要放置图例框的位置。这可以通过在\end{groupplot}之后但在\end{tikzpicture}之前添加以下代码行来完成:

\path (myplot c1r1.north west)--
      coordinate(legendpos)
      (myplot c1r1.north east);

在这里,我们所做的就是创建一个路径,跨越groupplot中第一个图(或c1r1)的两端(c1r1.north westc1r1.north east),并在此路径的中点定义我们的图例位置(legendpos)。注意: *\path* 命令绘制一个“不可见的路径”。如果你想在放置节点之前看到你所画的内容,请将 *\path* 替换为 *\path[draw]* *\draw* 命令。

一旦位置被定义好,我将加入图例。为此,我再次使用\node命令来完成(或者,你可以使用\matrix,它是\path node[matrix]的缩写;任何这些命令都能完成任务)。然而,这次我在我的\node[]中使用了一个特殊选项matrix of nodes,顾名思义,指定我需要一个节点矩阵。指定此节点的语法如下:

\node[matrix of nodes] at (position) { cell contents };

对于这个groupplot,我定义了节点如下:

\node[
    matrix of nodes,
    draw,
    inner sep=0.2em,
  ] at ([yshift=1em]legendpos)
  {
    \ref{plots:plot1} & 1949 & [1em]
    \ref{plots:plot2} & 1950 & [1em]
    \ref{plots:plot3} & 1951 & [1em]
    \ref{plots:plot4} & 1952\\};

我添加的几个节点选项包括 draw(使图例框可见)和 inner sep(定义矩阵外边界和内容之间的空间)。我还添加了一个 yshift 来将图例放置在图的边界之外。单元格内容都很容易理解。以下是这个选项的完整代码:

\begin{tikzpicture}
\begin{groupplot}[
        group style={
        group name=myplot,
        group size=1 by 4,
        xlabels at=edge bottom,
        xticklabels at=edge bottom,
        ylabels at=edge right,
        vertical sep=0pt,
    }, 
    myplotstyle,
    xtick=data,
    table/col sep=comma,
    xticklabels from table={Fig_groupplot_1.csv}{month},
    width=10cm, height=6cm,
    x tick label style={rotate=90, /pgf/number format/.cd},
    xlabel={Month},
    ylabel={No. of passengers},
]
\nextgroupplot
\addplot+[smooth, color=black,mark=*,mark options={fill=white,scale=1.5}] table[x expr=\coordindex,y=passengers, col sep=comma] {Fig_groupplot_1.csv}; \label{plots:plot1}

\nextgroupplot
\addplot+[smooth, color=black,mark=triangle*, mark options={fill=white,scale=1.5}] table[x expr=\coordindex,y=passengers, col sep=comma] {Fig_groupplot_2.csv}; \label{plots:plot2}

\nextgroupplot
\addplot+[smooth, color=black,mark=square*,mark options={fill=white,scale=1.5}] table[x expr=\coordindex,y=passengers, col sep=comma] {Fig_groupplot_3.csv}; \label{plots:plot3}

\nextgroupplot
\addplot+[smooth, color=black,mark=diamond*,mark options={fill=white,scale=1.5}] table[x expr=\coordindex,y=passengers, col sep=comma] {Fig_groupplot_4.csv}; \label{plots:plot4}

\end{groupplot}

\path (myplot c1r1.north west)--
      coordinate(legendpos)
      (myplot c1r1.north east);

\node[
    matrix of nodes,
    draw,
    inner sep=0.2em,
  ]at ([yshift=1em] legendpos)
  {
    \ref{plots:plot1} & 1949 & [1em]
    \ref{plots:plot2} & 1950 & [1em]
    \ref{plots:plot3} & 1951 & [1em]
    \ref{plots:plot4} & 1952\\};
\end{tikzpicture}

让我们来看看我们刚刚尝试过的三种图表变体:

堆叠图表示出包含图例的三种变体(作者提供的图像)

现在,我想对这个图进行更多的修改,以使其在审美上更令人愉悦。由于每个图的 Y 轴标签相同,让我们只为它们使用一个统一的 Y 轴标签。为此,我们可以简单地从groupplot样式定义中删除 Y 轴标签,并在groupplot中垂直居中添加一个带标签的节点。我们通过添加以下代码行来完成这个操作:

 \path (myplot c1r1.north west)
          -- (myplot c1r4.south west) 
          node[midway, xshift=-3em, rotate=90] 
          {\textbf{No. of passengers}};

这是我们之前使用的\path命令的一个变体。在这里,我将从第一个图 (c1r1) 的西北角绘制路径,到最后一个图 (c1r4) 的西南角,在groupplot中放置一个节点。此外,我还将节点移出图的边界,并将其旋转了 90 度。Y 轴标签完成!

这就是完整的代码和图表现在的样子。注意:为了简洁起见,我只展示了之前提到的一个图表变体(选项 2)的代码/图表。相同的 *\path* 命令也适用于其他两个图表选项。

\begin{tikzpicture}
\begin{groupplot}[
        group style={
        group name=myplot,
        group size=1 by 4,
        xlabels at=edge bottom,
        xticklabels at=edge bottom,
        vertical sep=0pt,
    }, 
    myplotstyle,
    xtick=data,
    table/col sep=comma,
    xticklabels from table={Fig_groupplot_1.csv}{month},
    width=10cm, height=6cm,
    x tick label style={rotate=90, /pgf/number format/.cd},
    xlabel={Month},
]
\nextgroupplot[]
\node [text width=5em] at (rel axis cs: 0.15,0.9){\textbf{1949}};
\addplot[smooth, color=black,mark=*,mark options={fill=white,scale=1.5}] table[x expr=\coordindex,y=passengers, col sep=comma] {Fig_groupplot_1.csv};

\nextgroupplot[]
\node [text width=5em] at (rel axis cs: 0.15,0.9){\textbf{1950}};
\addplot[smooth, color=black,mark=*, mark options={fill=white,scale=1.5}] table[x expr=\coordindex,y=passengers, col sep=comma] {Fig_groupplot_2.csv};

\nextgroupplot[]
\node [text width=5em] at (rel axis cs: 0.15,0.9){\textbf{1951}};
\addplot[smooth, color=black,mark=*,mark options={fill=white,scale=1.5}] table[x expr=\coordindex,y=passengers, col sep=comma] {Fig_groupplot_3.csv};

\nextgroupplot[]
\node [text width=5em] at (rel axis cs: 0.15,0.9){\textbf{1952}};
\addplot[smooth, color=black,mark=*,mark options={fill=white,scale=1.5}] table[x expr=\coordindex,y=passengers, col sep=comma] {Fig_groupplot_4.csv};
\end{groupplot}
    \path (myplot c1r1.north west)
          -- (myplot c1r4.south west) node[midway, xshift=-3em, rotate=90] {\textbf{No. of passengers}};
\end{tikzpicture}

样式化图例(选项 2)和轴标签后的最终叠加图(图像由作者提供)

感谢阅读!😃

那么,这篇文章就到这里结束了!

和往常一样,这只是关于组图的冰山一角。你可以用 LaTeX 创建更多自定义和复杂的叠加图,但这应该足够让你朝着正确的方向入门。请在评论中告诉我你希望看到的 LaTeX 绘图内容!所以下次见面之前…

祝学习愉快!

如何创建有价值的数据测试

原文:towardsdatascience.com/how-to-create-valuable-data-tests-850e778718e1

重要的不是数量,而是质量。

Xiaoxu GaoTowards Data Science Xiaoxu Gao

·发表于 Towards Data Science ·9 分钟阅读·2023 年 7 月 3 日

--

照片由 Shubham Dhage 提供,来源于 Unsplash

在过去的一年中,数据质量已经被广泛讨论。数据合同、数据产品和数据可观测性工具的日益采用无疑显示了数据从业者对向消费者提供高质量数据的承诺。我们都喜欢看到这一点!

数据解决方案中的一个关键组成部分是数据测试。这是验证数据质量的最基本和实用的方法之一,并且明确或隐含地嵌入在许多数据解决方案中。

尽管其有效性为数据团队带来了显著的好处,但它也提出了如何最大化其潜在价值的问题,因为更多的测试并不一定意味着更高的数据质量。在本文中,我希望展示一些设计数据测试的方法。希望这些方法能够提供一些启示。

值得注意的是,建议你结合这些方法,找到最适合自己的平衡点。

质量 > 数量

质量 > 数量(作者创建)

我是那些喜欢创建测试的人之一,因为它们让我对我的解决方案更加自信。作为一名软件工程背景的从业者,我曾经信奉“测试越多越好”的座右铭。我总是对数据框架提供简单的数据测试创建方法感到兴奋。

然而,我低估了过多的数据测试所带来的副作用。(真的会有副作用吗?有的!)首先,让我们理解数据测试和单元测试(即逻辑测试)之间的区别。简而言之,单元测试旨在验证我们编写的代码逻辑的正确性。单元测试越多,我们对处理边界情况的信心就越强。但数据测试不仅限于代码逻辑,它还检查源数据的质量、数据管道配置、上游依赖关系等等。度量标准无穷无尽,可能会令人不知所措。创建大量测试以防万一是很诱人的,但这些测试不一定带来价值,可能会引入不必要的噪声。例如,面对现实吧,如果某些数据管道只偶尔被用户使用,那它们不需要每日的新鲜度测试,并且数据管道中的所有阶段也不需要相同的测试。重复的数据测试只会导致冗余的警报。

曾经,我在创建数据测试时迷失了方向,结果产生了许多冗余测试,导致了错误的警报。我认识到,为了确保数据质量,我们首先需要确保数据测试的质量。 数量不一定与质量成正比。

适用性

捕捉客户的全部声音(作者创建)

数据可以视为一种产品。数据管道可以被看作是数据制造系统,接收原始数据输入并生产数据产品。虽然数据消费者不购买数据,但他们可以决定是否使用这些数据,并相应地提供需求和反馈。

“适用性”这一概念在衡量质量时被广泛采用。它强调了捕捉客户的全部声音的重要性,因为最终是消费者决定产品的成功。例如,在建设大学图书馆时,必须考虑到学生和教师,这意味着图书馆应有一系列覆盖多样兴趣和教育需求的书籍和资源。

在数据产品方面,一个好的例子是报告数据。为了被认为有价值,工程师应该与利益相关者紧密合作,了解有关准确性、可靠性、完整性、及时性等方面的监管要求,然后据此创建数据测试。

这种方法是测试创建过程的良好开端,尤其是当数据使用者对其需求有清晰的愿景,并且对数据质量有良好理解时。这可以大大简化数据工程师的工作。然而,单纯依赖利益相关者的需求往往是不够的,因为他们从外部视角评估数据,而没有考虑上游依赖关系和技术错误。在这种情况下,下一节中的数据质量框架将帮助我们覆盖大多数场景。

数据质量维度

从消费者的角度来看数据质量无疑是一个有价值的初步步骤。但它可能无法涵盖测试范围的完整性。大量文献回顾已经为我们解决了这个问题,提供了一系列数据质量维度,这些维度与大多数使用案例相关。建议与数据消费者一起审查列表,共同确定适用的维度,并相应创建测试。

| Accuracy     | Format           | Comparability     |
| Reliability  | Interpretability | Conciseness       |
| Timeliness   | Content          | Freedom from bias |
| Relevance    | Efficiency       | Informativeness   |
| Completeness | Importance       | Level of detail   |
| Currency     | Sufficiency      | Quantitativeness  |
| Consistency  | Usableness       | Scope             |
| Flexibility  | Usefulness       | Understandability |
| Precision    | Clarity          |                   |

你可能会发现这个列表太长,不知道从何开始。数据产品或任何信息系统可以从两个角度观察或分析:外部视角和内部视角。

外部视角

外部视角的维度(由作者创建)

外部视角关注数据的使用及其与组织的关系。它通常被视为一个“黑箱”,具有表示现实世界系统的功能。属于外部视角的维度高度依赖业务驱动。有时,对这些维度的评估可能是主观的,因此不容易为其创建自动化测试。但让我们看看一些知名的维度:

  • 相关性: 数据在分析中的适用性和有用性。例如,考虑一个旨在推广新产品的市场活动。所有数据属性应直接促进活动的成功,例如客户人口统计数据和购买数据。像城市天气或股票市场价格这样的数据在这种情况下是无关的。另一个例子是细节层级(粒度)。如果业务希望市场数据按天级别提供,但实际以周级别提供,那么它就不相关也不实用。

  • 表示层: 数据对数据消费者的可解释程度以及数据格式的一致性和描述性。在访问数据质量时,表示层的重要性往往被忽视。它包括数据的格式——保持一致且用户友好,以及数据的意义——易于理解。例如,考虑一个场景,其中数据预计以 CSV 文件的形式提供,列描述是详尽的,且值预计使用 EUR 货币而非分。

  • 时效性: 数据对数据消费者的新鲜程度。例如,业务需要销售交易数据,其延迟时间不得超过 1 小时。这表明数据管道应频繁刷新。

  • 准确性: 数据遵守业务规则的程度。数据指标通常与复杂的业务规则相关,如数据映射、四舍五入模式等。强烈建议对数据逻辑进行自动化测试,越多越好。

在四个维度中,创建数据测试时,及时性和准确性更为直接。及时性通过将时间戳列与当前时间戳进行比较来实现。准确性测试可以通过客户查询来进行。

内部视角

内部视角的维度(由作者创建)

相对而言,内部视角关注的是独立于特定需求的操作。无论当前的使用案例是什么,这些操作都是至关重要的。内部视角的维度更具技术驱动性,而外部视角的维度则更具业务驱动性。这也意味着数据测试对消费者的依赖较小,大多数情况下可以实现自动化。以下是一些关键视角:

  • 数据源质量: 数据源的质量对最终数据的整体质量有显著影响。数据合同是确保源数据质量的一个很好的措施。作为源数据的消费者,我们可以采用类似的方法来监控源数据,就像数据利益相关者在评估数据产品时一样。

  • 完整性: 信息保留的程度。随着数据管道复杂性的增加,中间阶段发生信息丢失的可能性也会增加。我们可以考虑一个存储客户交易数据的金融系统。完整性测试确保所有交易成功经过整个生命周期,而不会被遗漏或遗漏。例如,最终账户余额应该准确反映实际情况,捕捉每一笔交易而没有遗漏。

  • 唯一性: 这个维度与完整性测试密切相关。虽然完整性保证了没有信息丢失,但唯一性确保了数据中没有重复。

  • 一致性: 数据在内部系统中每天的一致性。差异是一种常见的数据问题,通常源于数据孤岛或不一致的指标计算方法。另一个一致性问题的方面发生在数据预期有稳定增长模式的日期之间。任何偏差都应引起进一步调查的警报。

值得注意的是,每个维度可以与一个或多个数据测试相关联。关键在于理解维度在特定表格或指标上的适用性。只有这样,测试使用得越多,效果越好。

到目前为止,我们已经讨论了外部视角和内部视角的维度。在未来的数据测试设计中,考虑外部和内部视角都很重要。通过向合适的人提出正确的问题,我们可以提高效率并减少沟通误差。

数据测试的更多提示

在最后一节中,我想分享一些创建数据测试的实用技巧。这些技巧来源于我的日常工作,但你也可以在评论中分享更多。

  • 错误数据与无数据: 在许多数据解决方案中,数据测试通常是在模型更新后进行的。这意味着当我们识别到问题时,数据已经变得“损坏”。如果你更倾向于“无数据”而非“错误数据”,你可以首先将表格生成到临时位置,然后进行测试。只有当测试成功通过时,管道才会继续将表格复制到原始目标;否则,过程将被中止。

  • 对账测试: 对账测试是一种数据验证过程,用于比较两个或多个系统之间的数据一致性和准确性,通常是在源数据集和目标数据集之间。例如,设计了两个管道来处理事务时,比较两个系统和源的总交易金额是值得的。任何差异的存在可能表明数据管道中存在缺陷。

  • 带有容差的测试: 我们可能会从利益相关者那里收到这样的陈述:“这个列中的 0 是可以接受的,但应避免过量。”这意味着他们想捕捉到偏离一致模式的情况。许多现代数据监控工具提供异常检测功能,但如果这对你来说不是一个选项,你可以开始创建带有容差的测试。例如,总行数中不应有超过 5%的值为 0。

结论

一如既往,我希望你觉得这篇文章有用且启发。关键是避免过分关注你创建的数据测试的数量。因为在每一列上添加相同的测试没有意义,因为这只会制造大量噪音并降低你的生产力。

在与数据消费者和数据提供者交谈之前,务必考虑数据质量框架。聪明地提问,并利用框架激发利益相关者考虑数据的额外视角。干杯!

参考

如何使用稳定扩散和 Deform 创建插值视频

原文:towardsdatascience.com/how-to-create-videos-with-stable-diffusion-1c9c3b34be84

生成式人工智能

使用帧插值通过稳定扩散和 Deforum 创建视频

Oscar LeoTowards Data Science Oscar Leo

·发布在Towards Data Science ·6 分钟阅读·2023 年 2 月 17 日

--

由作者使用DeForum生成的图像

介绍

无论你走到哪里,你都会看到由稳定扩散和 Midjourney 等算法生成的图像。然而,视频却是一个更加具有挑战性的前景。

我与一家媒体制作公司合作,人工智能视频在成为行业广泛使用的工具之前还有很长的路要走。质量尚远未达到要求。

挑战

存在一些挑战,比如帧之间的闪烁、分辨率和计算等。

我还应该提到有关使用互联网内容训练算法的版权问题的持续争论。很明显,目前的做法过于宽松。我将非常关注这一进展。

当前使用案例

然而,媒体制作完全是关于创造引人入胜和独特的内容,而人工智能无疑可以成为其中的一部分。

一个使用案例是给原始视频添加不同的风格,就像他们在乌鸦中做的那样,他们首先录制了一位舞者,然后让算法调整每一帧。

我们将不断看到这一主题的进展,但在这篇文章中,我会展示一种简单的方法来让你入门。我们将通过使用DeForum对图像进行插值来创建一个短视频。

让我们开始吧!😃

步骤 1:设置 VastAI

要使用 Deforum,你需要一个 GPU。如果你的计算机上没有 GPU,最简单且最便宜的替代方案是vast.ai

实例配置

首先,你需要选择使用哪个图像。我会选择 pytorch/pytorch 版本“1.13.1-cuda11.6-cudnn8-devel”。这不是唯一正确的选择,但它有效。

对于启动模式,我选择最简单的选项:标准 Jupyter 笔记本。

最后一个需要更改的配置是增加磁盘空间,因为模型非常庞大。

选择一个 GPU

接下来,选择一个 GPU。虽然有成千上万的选项,但标准的 3090 就很好。确保上传和下载速度都不错。

价格应该在每小时$0.4 左右,但如果你想要更好的 GPU,可以多花点钱。

步骤 2:安装 Deforum

Deforum 在他们的 GitHub 上解释了安装过程,但我也会在这里写下来,以使本教程更易于跟随。

2.1:打开实例

要打开你的实例,请前往左侧菜单中的“实例”,然后点击右侧的蓝色“打开按钮”。

2.2:打开终端

进入后,点击“新建”并选择终端选项以打开一个终端。

运行以下两个命令以安装 conda,并关闭终端以使更改生效。

conda create -n dsd python=3.10 -y
conda init

打开一个新的终端,就像之前一样,并激活 conda 环境。

conda activate dsd

2.3:安装所有内容

通过运行以下命令安装所有内容:

git clone https://github.com/deforum-art/deforum-stable-diffusion.git
cd deforum-stable-diffusion
python install_requirements.py

# Add conda environment to jupyter
conda install -c anaconda ipykernel
python -m ipykernel install --user --name=firstEnv

几分钟后,你可以通过运行以下命令测试一切是否正常:

python Deforum_Stable_Diffusion.py

步骤 3:创建视频

3.1 打开笔记本

当一切安装完毕并正常工作后,关闭终端,然后打开 deforum-stable-diffusion 文件夹中的“Deforum_Stable_Diffusion.ipynb”。

将内核更改为 dsd 并运行前三个单元。第三个单元可能需要一点时间来完成。

3.2 选择一个起始图像 [可选]

如果你愿意,可以从原始图像开始。我将使用以下来自Pexels的图像。

照片由 Nout Gons 拍摄:www.pexels.com/photo/city-street-photo-378570/

你需要确保图像的大小合适。否则,它将无法适应 RAM。因此,我将此图像的大小调整为 801x512(Deforum 会将边缘裁剪为 768x512)。

要上传图像,点击上传,并将其放置在合适的位置。例如,我将其放在/deforum-stable-diffusion 下。

3.3 Deforum 设置

一旦你打开 Notebook,你会看到可以调整的设置有很多。遗憾的是,由于许多设置仅与之前的选择相关,这个界面并不完美。

注意: 单独的设置可能很难找到,并且它们的顺序可能与我写的不一致。最好使用 ctrl+F 或 cmd+F 搜索位置。

首先,我们将animation_mode更改为Interpolation,并将interpolate_x_frames设置为一些合理的值。interpolate_x_frames决定算法应在你的关键帧之间生成多少帧。

animation_mode = 'Interpolation'
interpolate_x_frames = 16

接下来,我们通过更改animation_prompts来创建我们的提示。你看到的左边的数字对于Interpolation并不重要。

对于其他使用场景,这个数字决定何时开始使用特定的提示,但对于插值(Interpolation),这些数字成为我们的关键帧,并且我们在每两个关键帧之间生成interpolate_x_frames帧。

这是我的提示,但你可以写你想要的任何内容。

animation_prompts = {
    0: "a dystopian warzone with zombies, dark clouds in the sky, night",
    1: "a dystopian warzone, dark clouds in the sky",
    2: "a dystopian city, clouds in the sky",
    3: "a city",
    4: "a beautiful city",
    5: "a beautiful and modern city with blue sky",
    6: "a beautiful and modern city with blue sky and green trees"
}

如果你不使用原始图像,继续进行3.4;否则,请按照下面的说明操作。

现在,我们将WH更改为我们选择的图像的尺寸。在我的例子中,这是 801x512。

W = 801 #@param
H = 512 #@param

要使用图像,我们还需要更改以下设置:

use_init = True #@param {type:"boolean"}
strength = 0.6 #@param {type:"number"}
init_image = "city-street.jpg" #@param {type:"string"}

Strength决定保留原始图像的程度。接近 1 的数字意味着我们将保留大部分原始图像。Init_image当然应该指向你的图像的位置。

3.4 生成图像

运行单元格 3、4 和 5。首先,算法将根据你在animation_prompts中的提示生成关键帧。

当关键帧完成后,它将继续创建中间帧。如果你的animation_prompts中有很多提示和一个大的interpolate_x_frames,这可能需要一些时间。

3.5 创建视频

在你的笔记本底部创建一个新单元,并粘贴以下代码。然后运行该单元以创建你的视频。

import cv2

image_files = os.listdir(args.outdir)
image_files = ["{}/{}".format(args.outdir, i) for i in image_files if '.png' in i]
image_files = [i for i in image_files if args.timestring in i]

out = cv2.VideoWriter('video.avi',cv2.VideoWriter_fourcc(*'DIVX'), 15, (args.W, args.H))

for i in range(len(image_files)):
    out.write(cv2.imread(image_files[i]))
out.release()

这是我的视频的样子:

这是另一个例子:

这就是我的教程;祝你编码愉快!如果你创建了有趣的内容,请分享。

感谢阅读!😃

如何创建自己的 AI 天气预报

原文:towardsdatascience.com/how-to-create-your-own-ai-weather-forecast-de72c4c50810

使用预训练模型和再分析数据

Caroline ArnoldTowards Data Science Caroline Arnold

·发表于 Towards Data Science ·8 分钟阅读·2023 年 10 月 2 日

--

图片由 NOAA 提供,来源于 Unsplash

基于数据的天气预报使用预训练模型进行创建成本低,并且能够提供与传统数值天气模型相当的预报准确度。多个公司和研究实验室已经开发了 AI 天气模型,包括:

欧洲中期天气预报中心(ECMWF)提供了使用这些模型生成天气预报的常规方法[1]。即使在笔记本电脑上也可以进行模型推断,尽管推荐使用 GPU。

在这篇文章中,我将要

  • 向你展示如何创建自己的 AI 天气预报

  • 比较模型在 GPU 和 CPU 上的推断时间

  • 可视化 PanguWeather 和 FourCastNet 的天气预报,包括温度、水汽和急流

背景信息

传统的天气预报依赖于在全球网格上求解的数值天气模型。这需要大量的计算资源,只有少数天气服务机构能够生成全球天气预报。

AI 天气模型是基于过去的天气进行训练的,称为再分析数据。通过训练好的模型和当前的天气状态,可以只使用少量计算资源生成天气预报,而不需要像数值天气预报那样的高计算需求。示意图展示了 AI 天气预测如何利用当前天气作为输入来预测未来天气:

AI 气象预报示意图。左侧:2021 年 1 月 1 日 00:00 UTC 的风场。右侧:2021 年 1 月 1 日 23:00 UTC 的风场。数据来源:ERA5,通过 Copernicus 气候数据存储 [3]。图片:作者。

本文涵盖的三种 AI 气象模型均提供 0.25°(25 公里)的空间分辨率和 6 小时的时间分辨率的天气预报。

有关 AI 在天气预报中影响的更多背景信息,请参阅我之前的文章:

## 气象预测中的人工智能革命

明天的天气,由深度学习提供

pub.towardsai.net](https://pub.towardsai.net/the-ai-revolution-in-weather-prediction-9b706f763a61?source=post_page-----de72c4c50810--------------------------------)

入门

代码和环境 从 ECMWF Github 页面 [1] 获取运行预训练气象模型的代码,并按照安装说明进行操作。我在本教程中使用了 v0.2.5。为所需的 Python 包设置一个 conda 环境。

conda create -n ai-models python=3.10
conda activate ai-models
conda install cudatoolkit
pip install ai-models

要安装可用的 AI 模型,请使用

pip install ai-models-panguweather
pip install ai-models-fourcastnet

GraphCast 需要特殊的安装程序,下面会介绍。

预训练模型权重 PanguWeather、GraphCast 和 FourCastNet 的预训练模型权重需要下载:

ai-models --download-assets --assets assets-panguweather panguweather
ai-models --download-assets --assets assets-fourcastnet  fourcastnet
ai-models --download-assets --assets assets-graphcast    graphcast

初始化数据 AI 气象模型需要用当前的天气数据进行初始化才能进行预测。有两种获取数据的方式,要么从 ECMWF 服务 MARS 获取,要么从 Copernicus 气候数据存储 (CDS) 获取。这两个服务都免费提供初始化数据供非商业使用。

我在本教程中使用了来自 CDS 的再分析数据。请按照他们网站上的说明获取访问数据的 API 密钥 [2]。请注意,再分析数据的提供有大约五天的延迟。

创建 AI 气象预报

现在我们已经准备好生成自己的 AI 气象预报。剩下的工作就是选择初始化的时间和日期,然后使用预训练模型开始模型推理。

PanguWeather 要请求基于 CDS 初始化数据的从 2023 年 9 月 20 日 00:00 UTC 开始的 PanguWeather 预报,请使用:

ai-models --input cds --date 20230920 --time 0000 --assets assets-panguweather panguweather

日志相当全面。预训练模型 pangu_weather_6.onnx 生成的天气预报提前 6 小时,这里它被迭代应用了 40 次,总计 240 小时(10 天)。

2023-09-26 12:06:45,724 INFO Loading assets-panguweather/pangu_weather_24.onnx: 26 seconds.
2023-09-26 12:07:10,289 INFO Loading assets-panguweather/pangu_weather_6.onnx: 24 seconds.
2023-09-26 12:07:10,289 INFO Model initialisation: 52 seconds
2023-09-26 12:07:10,290 INFO Starting inference for 40 steps (240h).

对于每次迭代,日志会告诉用户花费了多少时间。在我的实验中,ONNX 运行时无法找到 GPU,因此推理是在 CPU 上进行的,每个天气预测步骤大约花费 3:15 分钟。

2023-09-26 12:10:27,260 INFO Done 1 out of 40 in 3 minutes 16 seconds (6h), ETA: 2 hours 11 minutes 18 seconds.

FourCastNet 为了生成与相同初始化时间的预报,同时下载训练好的模型权重到子目录 assets-fourcastnet,我们使用:

ai-models --download-assets --assets assets-fourcastnet --input cds --date 20230920 --time 0000 fourcastnet

日志再次提供了关于模型推断的全面信息。现在,ONNX 运行时能够找到 GPU,推断速度大大加快,仅需 2 分钟即可生成 10 天的预报。

2023-09-26 13:23:48,633 INFO Using device 'CUDA'. The speed of inference depends greatly on the device.
2023-09-26 13:23:59,710 INFO Loading assets-fourcastnet/backbone.ckpt: 13 seconds.
2023-09-26 13:24:10,673 INFO Loading assets-fourcastnet/precip.ckpt: 10 seconds.
2023-09-26 13:24:10,733 INFO Model initialisation: 46 seconds
2023-09-26 13:24:10,733 INFO Starting inference for 40 steps (240h).
2023-09-26 13:24:14,247 INFO Done 1 out of 40 in 3 seconds (6h), ETA: 2 minutes 20 seconds.

GraphCast 对于 GraphCast,我们首先需要按照 github.com/ecmwf-lab/ai-models-graphcast 中的安装步骤进行操作。然后,我们可以使用以下方法生成天气预报:

ai-models --download-assets --assets assets-graphcast --input cds --date 20230920 --time 0000 graphcast

在撰写时,这导致了一个在 GitHub 上存在的错误(问题 #10),我无法使用 GraphCast 完成预报。

预报输出文件

AI 气象模型生成的输出文件采用 GRIB 格式。对于 240 小时的预报,文件大小为

  • 使用 FourCastNet(3 个压力层:850 hPa、500 hPa、250 hPa)时,文件大小为 2.3 GB。

  • 使用 PanguWeather(13 个压力层)时,文件大小为 5.4 GB。

两个模型输出关键气象变量:

  • 温度

  • 风速和风向

PanguWeather 还提供了地势和湿度数据,而 FourCastNet 提供了水汽含量。

生成图表

对这些 AI 生成的预报进行适当的评估和基准测试是一个复杂的话题,超出了本文的范围。在这里,我们重点比较了两个 AI 气象模型的视觉效果。为了视觉分析天气预报,我们选择了温度变量以及初始化后 24 小时的预报。这是生成图表的模板:

import xarray as xr
import seaborn as sns
from matplotlib import pyplot as plt
import cartopy.crs as ccrs

# load data
ds_fourcast = xr.open_dataset('fourcastnet.grib')

ix_step = 3  # 0 = first step in weather prediction
ix_level = 0 # 0 = surface pressure level
temp_degC = ds_fourcast['t'][ix_step, ix_level] - 273.15 # subtract 273.15 to convert from Kelvin to Celsius.

# plot settings
sns.set_style('ticks')
tmin = -45
tmax = +45
nlev = 19

# generate figure
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1, projection=ccrs.PlateCarree())
ax.set_global()
ax.coastlines('110m', alpha=0.1)

# contour plot
img = ax.contourf(ds_fourcast.longitude.values, ds_fourcast.latitude.values, 
                  temp_degC.values, 
                  levels = np.linspace(tmin, tmax, nlev),
                  cmap='RdBu_r',
                  transform=ccrs.PlateCarree()
                 )

# hours passed since initialization
dt_hours = ((ds_fourcast['valid_time'][ix_step] - ds_fourcast['time']).values / 1e9 / 3600).astype(float)

# annotation text
ax.text(-180, 90, 
        f'After {dt_hours} hours ({ds_fourcast["valid_time"][ix_step].values})', 
        transform=ccrs.PlateCarree(),
        va='bottom', ha='left',
       )

plt.colorbar(img, orientation='horizontal', label='850 hPa temperature (°C)')
plt.show()

温度 下图比较了 PanguWeather 和 FourCastNet 在 850 hPa 层的温度。这个层级并不直接位于地表,但它是两个输出文件中唯一包含的层级。从视觉上看,我们可以看到温度场显示了相似的模式。

24 小时后的 850 hPa 温度。图片来源:作者。

水汽 FourCastNet 提供了总大气水汽质量,这可以与带有雨滴的云量进行比较。在下图中,我们比较了四个不同时间点的这一量:1 天(24 小时)、3 天(72 小时)、5 天(96 小时)和 10 天(240 小时)后。

使用 FourCastNet 预报 1、3、5 和 10 天后的总大气水汽。图片来源:作者。

我们观察到一个在深度学习中非常常见的现象:由于模型使用均方根误差(RMSE)进行训练,它倾向于生成平滑的场。随着模型的迭代应用,预测结果变得不那么明显。经过 10 天,我们甚至观察到南美洲南端的伪影。请注意,目前的 AI 气象模型并不使用生成式 AI 技术,因为它们更注重物理准确性而非视觉效果。

风速 PanguWeather 提供不同压力水平的风速。我们选择了 250 hPa 压力水平,这对应于喷流最为明显的高度。喷流是一种极地环流,影响中纬度地区如美国和欧洲的天气。图中显示了南半球喷流的风速,该喷流在我的预报期间更加显著。

南半球喷流预报使用 PanguWeather。图片:作者。

为了创建正射投影并计算风速,我们使用了以下模板:

import xarray as xr
import seaborn as sns
from matplotlib import pyplot as plt
import cartopy.crs as ccrs

# load data
#ds_pangu = xr.open_dataset('/home/k/k202141/rootgit/ai-models/panguweather.grib')

ix_step = 3  # 0 = first step in weather prediction
ix_level = 8 # 0 = surface pressure level, 2 - 850 hPa level
windspeed = (ds_pangu['u'][ix_step, ix_level]**2 + ds_pangu['u'][ix_step, ix_level]**2)**0.5

# plot settings
sns.set_style('ticks')
tmin = 0
tmax = 150
nlev = 16

# generate figure
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1, projection=ccrs.Orthographic(central_latitude=-30))
ax.set_global()
ax.coastlines('110m', alpha=1.0)

# contour plot
img = ax.contourf(ds_pangu.longitude.values, ds_pangu.latitude.values, 
                  windspeed.values, 
                  levels = np.linspace(tmin, tmax, nlev),
                  cmap='gist_ncar',
                  transform=ccrs.PlateCarree()
                 )

# hours passed since initialization
dt_hours = ((ds_pangu['valid_time'][ix_step] - ds_pangu['time']).values / 1e9 / 3600).astype(float)
print(dt_hours)

plt.colorbar(img, orientation='horizontal', label='Wind speed (m/s)', shrink=0.5)
plt.show()

摘要

在本文中,我们展示了如何使用 ECMWF 提供的 AI-Models 资源库[1]生成 AI 天气预报。通过使用气候数据存储中的初始化数据,我们能够使用两个模型生成预报:华为的 PanguWeather 和 NVIDIA 的 FourCastNet。使用预训练模型生成 10 天的天气预报,在 GPU 上大约需要 2 分钟,在 CPU 上大约需要 3 小时。与生成数值天气预报所需的巨大计算努力相比,推理成本非常低。

现在,个体消费者和资金不足的天气服务可以生成自己的 AI 天气预报,至少可以使用 ERA5 训练数据提供的空间和时间分辨率。ECMWF 的资源库提供了对预训练模型的便捷访问。

参考文献

如何:时间序列数据的交叉验证

原文:towardsdatascience.com/how-to-cross-validation-with-time-series-data-9802a06272c6

说到时间序列数据,你必须以不同的方式进行交叉验证。

Haden PelletierTowards Data Science Haden Pelletier

·发布于 Towards Data Science ·5 分钟阅读·2023 年 12 月 29 日

--

标准的 k 折交叉验证。图像由作者提供

交叉验证是训练和评估机器学习模型的重要部分。它允许你估计训练好的模型在新数据上的表现。

大多数学习如何进行交叉验证的人首先了解的是K 折方法。我知道我就是这样。在 K 折交叉验证中,数据集被随机分成 n 折(通常是 5 折)。在 5 次迭代中,模型在 5 折中的 4 折上进行训练,而剩下的 1 折作为测试集用于评估性能。这个过程会重复,直到所有 5 折都被用作测试集为止。到最后,你将得到 5 个错误分数,将它们平均在一起,就会得到你的交叉验证分数。

但有个问题——这种方法实际上只适用于非时间序列/非顺序数据。如果数据的顺序有任何意义,或者任何数据点依赖于前面的值,你不能使用 K 折交叉验证。

原因很简单。如果你使用 KFold 将数据拆分为 4 个训练折和 1 个测试折,你将随机化数据的顺序。因此,曾经在其他数据点之前的数据点可能会出现在测试集中,最终,你将使用未来的数据来预测过去。

这绝对是不允许的。

在开发中测试模型的方式应该模拟它在生产环境中的运行方式。

如果你将在模型投入生产时使用过去的数据来预测未来的数据(就像时间序列中的做法),你应该在开发阶段以相同的方式测试你的模型。

这就是 TimeSeriesSplit 的作用。TimeSeriesSplit 是一个 scikit-learn 类,称自己为“KFold 的变体”。

在第 k 次拆分中,它返回前 k 个折叠作为训练集,第 (k+1) 个折叠作为测试集。

TimeSeriesSplit 如 scikit-learn 文档中定义的

TimeSeriesSplit 和 KFold 之间的主要区别是:

  • 在 TimeSeriesSplit 中,训练数据集的大小逐渐增加,而在 KFold 中,它保持静态。

  • 在 TimeSeriesSplit 中,训练集每次都会变大,因此训练数据总是包含之前迭代的训练数据中的值。在 KFold 中,当前迭代的训练数据可能在之前的迭代中作为测试数据的一部分,反之亦然。

  • 在 KFold 中,数据集中的每个数据点在某一时刻都会成为测试集的一部分。而在 TimeSeriesSplit 中,第一块训练数据永远不会被包含在测试集中。

它是这样工作的:在第一次迭代时,数据被划分为训练集和测试集。测试集的大小(除非作为参数 test_size 指定)默认为 n_samples // (n_splits + 1))而训练集的大小默认为 i * n_samples // (n_splits + 1) + n_samples % (n_splits + 1) 其中 i = 当前拆分次数。随着当前拆分次数的增加,i 增加,训练集的大小也增加。

一个时间序列交叉验证的视觉示意图。图片来自作者

为了更好地说明 TimeSeriesSplit 的工作原理,我将通过一个 Python 示例来演示。首先,我创建了一个非常简单的样本数据集:1 个特征,12 个值。然后我实例化了一个 TimeSeriesSplit 对象 tss,并指定了要 5 次拆分。为了实际进行拆分,我调用了 tss 的 .split 方法,并传入了我的数据集 X。这将产生一组索引,用于在交叉验证过程中确定拆分数据集的位置。为了查看数据是如何拆分的,我遍历了这些折叠及其相应的索引,并打印出了这些索引处的值。

import numpy as np
from sklearn.model_selection import TimeSeriesSplit

# Create a sample dataset X and y
X = np.array([[1], [2], [3], [4], [5], [6], [7], [8], [9], [10], [11], [12]])
y = np.array([2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24])

# Define TimeSeriesSplit object
tss = TimeSeriesSplit(n_splits=5)

# Split & print out results
for i, (train_index, test_index) in enumerate(tss.split(X)):
    print(f"Fold {i+1}:")
    print(f"  train:{X[train_index]}")
    print(f"  test:{X[test_index]}")

第一个结果看起来像这样:

Fold 1:

train:[[1] [2]]

test:[[3] [4]]

最后一个是这样的:

Fold 5:

train:[[ 1] [ 2] [ 3] [ 4] [ 5] [ 6] [ 7] [ 8] [ 9] [10]]

test:[[11] [12]]

现在对象和拆分索引已被定义,是时候进行实际的交叉验证(CV)了。我选择了一个随机森林回归器,但这可以用任何模型来完成。

幸运的是,scikit-learn 提供了一种简单的方法来进行交叉验证,通过其函数 cross_validate,它接受一个模型对象、X 和 y 数组、一个 cv 策略和一个评分指标(s)。

from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import cross_validate

# define model
rf = RandomForestRegressor()

# perform the cross-validation and get a score
cv = cross_validate(rf,X,y,cv=tss,scoring='neg_root_mean_squared_error')

如果你想在多个指标上进行交叉验证,你可以传入一个列表。你只需要确保每个指标都是 scikit-learn 的可接受指标值

cross_validate 返回一个包含 fit_time、score_time 和 test_score 的 Python 字典。要找到平均 CV 分数,只需在 test_score 上调用 .mean()。

cv['test_score'].mean()

有了这个,你将能够准确估计时间序列模型的表现。时间序列问题涉及的考虑因素和方法多种多样,超出了标准机器学习模板。研究时间序列问题的每一个步骤,从探索性数据分析(EDA)到交叉验证(CV)再到预测,并学习如何将时间序列特定技术正确应用于时间序列数据,是非常重要的。

来源

“TimeSeriesSplit。” scikit-learn,scikit-learn 开发者,2023 年,https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.TimeSeriesSplit.html。

如何使用 Docker 将 Panel 应用部署到 Hugging Face

原文:towardsdatascience.com/how-to-deploy-a-panel-app-to-hugging-face-using-docker-6189e3789718

五个简单步骤

Sophia Yang, Ph.D.Towards Data Science Sophia Yang, Ph.D.

·发表于 Towards Data Science ·阅读时间 5 分钟·2023 年 1 月 6 日

--

作者:Sophia YangMarc Skov Madsen

你是否想将 Panel 应用部署到 Hugging Face,但不知道怎么做?通过五个简单的步骤,我们可以轻松地使用 Docker 将 Panel 应用部署到 Hugging Face。本文将逐步指导你完成这个过程。到本文结束时,你应该能够如下面所示地在 Hugging Face 上部署你自己的应用。这非常简单,而且是免费的。你还在等什么?让我们开始吧!

将 Panel 应用部署到 Hugging Face 的五个步骤

步骤 1: huggingface.co/spaces

访问 huggingface.co/spaces 并点击“创建新空间”

步骤 2:创建新空间

给它一个“空间名称”。这里我称之为“panel_example”。选择 Docker 作为空间 SDK,然后点击“创建空间”。

步骤 3:克隆仓库

在命令行中克隆 Hugging Face 仓库(请记得将路径更改为你自己的用户名和空间名称):

git clone https://huggingface.co/spaces/sophiamyang/panel_example

现在你应该在电脑上看到一个名为“panel_example”的目录,其中只有一个 README.md 文件。

步骤 4:创建三个文件

现在让我们在这个文件夹中创建三个文件:

  • app.py 是我们 Panel 应用的文件

你可以从 huggingface.co/spaces/sophiamyang/panel_example/blob/main/app.py 复制 app.py 文件

要了解更多关于如何创建 Panel 应用的信息,请查看我们之前的博客文章 创建交互式仪表板的最简单方法 和 构建 Panel 可视化仪表板的 3 种方法

  • requirements.txt 定义了我们 Panel 应用所需的包。

我们的 Panel 应用只需要两个包:panel 和 hvplot。因此,我们只在我们的 requirements.txt 中包含这两个包。

panel
hvplot
  • Dockerfile 列出了我们希望 Docker 执行的所有命令,以便构建和运行镜像作为容器。要了解更多关于 Docker 的信息,请查看这个 视频

在这个 Dockerfile 中,我们指定了 python:3.9 作为基础镜像,将工作目录设置为 /code,将 requirements.txt 文件复制到容器中,运行 pip install 来安装包到容器中,将目录中的每个文件复制到容器中,然后 CMD 提供了运行 Panel 应用的命令。请注意,通常我们运行 panel serve app.py 来服务一个 Panel 应用。在这个文件中,我们将命令拆分为字符串列表。我们还需要定义地址和端口,因为 Hugging Face 使用端口 7860 作为默认端口。我们还需要定义标志 allow-websocket-origin 以允许连接到服务器的 websocket。

! 重要:请记得将 sophiamyang-panel-example.hf.space 更改为 YOURUSERNAME-SPACENAME.hf.space。

FROM python:3.9

WORKDIR /code

COPY ./requirements.txt /code/requirements.txt

RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt

COPY . .

CMD ["panel", "serve", "/code/app.py", "--address", "0.0.0.0", "--port", "7860", "--allow-websocket-origin", "sophiamyang-panel-example.hf.space"]

第 5 步: 提交所有更改并使用 git 推送更改:

git add *
git commit
git push

现在你应该能在你的 Hugging Face 空间中看到这些文件。

在经过 Hugging Face 几分钟的努力构建我们的应用程序后,我们的 Panel 应用将会出现在应用标签中,你可以在这里找到独立应用链接:sophiamyang-panel-example.hf.space/app

如何本地运行 Docker?(可选步骤)

在你开发应用时,你可能希望在部署到 Hugging Face 之前,通过 Docker 本地运行应用。在第 4 步中,app.pyrequirements.txt 与上述相同。然而,我们需要对 Dockerfile 做一个小的更改:

! 重要:我们在本地运行 Docker 时需要将 sophiamyang-panel-example.hf.space 更改为 0.0.0.0:7860。另一种更好的方法是将 0.0.0.0:7860 添加到允许的 WebSockets 中,除了 Hugging Face URL:

FROM python:3.9

WORKDIR /code

COPY ./requirements.txt /code/requirements.txt

RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt

COPY . .

CMD ["panel", "serve", "/code/app.py", "--address", "0.0.0.0", "--port", "7860", "--allow-websocket-origin", "sophiamyang-panel-example.hf.space",  "--allow-websocket-origin", "0.0.0.0:7860"]

要在本地使用 Docker,我们只需了解三个命令:

docker build -t panel-image .
docker run --name panel-container -p 7860:7860 panel-image
docker rm panel-container
  • 首先,确保我们在项目目录中。然后运行 docker build -t panel-image . 来根据这个目录中的文件构建 Docker 镜像。

  • 其次,运行这个 Docker 镜像:docker run --name panel-container -p 7860:7860 panel-image。现在你应该可以在 0.0.0.0:7860 看到你的应用程序正在运行。

  • 如果你想对文件进行任何更改,你需要先删除容器:docker rm panel-container,然后重复前面提到的前两个命令。

我们可以在 Hugging Face 上同时服务多个 Panel 应用程序吗?

是的,你绝对可以。查看我的示例:huggingface.co/spaces/sophiamyang/Panel_apps 和相应的独立应用链接:sophiamyang-panel-apps.hf.space/

当我们点击其中一个应用程序,例如,“文本分析”,我们可以看到我们的文本分析应用在 Hugging Face 上显示出来。

结论

在这篇文章中,我们展示了如何通过 Docker 将 Panel 应用程序部署到 Hugging Face,分为五个简单步骤。试试看吧!如果你有问题或想与 Panel 社区分享你的应用程序,请访问 discourse.holoviz.org/.

参考文献

. . .

Sophia YangMarc Skov Madsen 于 2022 年 1 月 6 日发布。

Sophia Yang 是 Anaconda 的高级数据科学家。请在 LinkedInTwitterYouTube 上与我联系,并加入 DS/ML 书友会 ❤️

如何以最少计算资源部署和解释 AlphaFold2

原文:towardsdatascience.com/how-to-deploy-and-interpret-alphafold2-with-minimal-compute-9bf75942c6d7?source=collection_archive---------1-----------------------#2023-02-24

使用有限计算资源部署和解释 AlphaFold2

Temitope SoboduTowards Data Science Temitope Sobodu

·

关注 发表在 Towards Data Science ·9 分钟阅读·2023 年 2 月 24 日

--

图片由 Luke Jones 提供,来源于 Unsplash

生物学中最艰难的挑战之一已经被一家总部位于伦敦的人工智能公司——DeepMind——克服。这家公司以 90 分的真实分数赢得了蛋白质结构预测第 14 届评估(CASP14)。DeepMind 随后在 2021 年夏季发布了一篇具有里程碑意义的论文。

## 高度准确的蛋白质结构预测与 AlphaFold - Nature

蛋白质对生命至关重要,理解其结构可以促进对其机制的了解……

www.nature.com

DeepMind 将其蛋白质折叠平台命名为 AlphaFold(截至撰写时的更新版本——AlphaFold v2.3.0)。他们在 GitHub 上发布了源代码以供公开访问。然而,部署 AlphaFold2 的开源代码需要巨大的计算资源;下载数据库需要 12 个 vCPU、85 GB RAM、100 GB 引导磁盘、3 TB 磁盘和一个 A100 GPU。此外,用户必须精通 Linux,并部署 docker 容器和其他依赖项。本文的目的是指导读者通过无缝替代工具来利用 AlphaFold 的奇迹。

本文长度适中(无意中),请继续跟随我。

我将讨论:

· AlphaFold 的工作原理

· 性能评估指标

· EMBL-EBI 方法

· Colab 笔记本方法

· AlphaFold2 的局限性

· 结论

那么 AlphaFold2 是如何工作的呢?

AlphaFold2 整合了经过训练的多序列比对 (MSA)、配对残基和来自宏基因组数据库的 100000 个已知蛋白质结构的 PDB 模板(通过 NMR、X 射线晶体学、冷冻电子显微镜验证)。AlphaFold2 evoformer 是一个 48 块的神经网络,基于来自大型语言模型 (LLM)、分词、变换器和注意力的概念构建。

图片来源:Jumper et al

evoformer 输出 MSA 和配对表示,这些表示被输入到结构预测模块。这些块利用不变点注意力预测 MSA 的第一行的单一表示副本,然后将其传递以预测预测蛋白质残基原子之间的𝛘扭转角度:本质上,即在 XYZ 维度中将互连的原子放置在肽链骨架附近。最终预测的蛋白质结构通过使用 Amber 力场的 openMM 进行能量优化,减少了立体碰撞和能量违背。

性能评估指标

AlphaFold2 (AF2) 输出一个 3D 蛋白质结构,模型性能通过三个置信度指标进行评估。

预测局部差异测试 (pLDDT),范围从 0 到 100,是一个每个残基的置信度评分,表示模型对每个氨基酸残基相对于𝛂碳原子的预测置信度。

图片来源:作者

例如,上图展示了人类哺乳动物雷帕霉素靶蛋白(mTOR)丝氨酸/苏氨酸激酶的 pLDDT 彩色编码预测。pLDDT > 90 表示非常高的置信度,70–90 表示足够好的置信度,而 <70 表示低模型置信度。pLDDT 可以帮助我们评估模型在蛋白质区域或领域中的表现如何。

预测对齐误差(PAE) 给出了在对齐于同一平面时,两个残基 X 和 Y 相对于真实结构的领域间/领域内距离。简单来说,就是残基在空间中相对于其他残基的排列情况。通常,这些距离范围在 0-35 Ångstroms 之间,以获得可靠的预测。AF2 以能够更好地预测同一领域内(领域内残基)相对于不同领域(领域间残基)的残基相对位置而自豪。这是合理的,因为领域内的残基比其他领域的残基更为静态。模型输出一个 PAE 图,展示了残基位置在 X/Y 轴上的对比。

图片来源:作者

预测模板建模评分(pTM)衡量两个折叠蛋白质结构之间的结构一致性。AlphaFold2 允许在建模选项中包含 PDB 模板。虽然预测时不需要这些模板,但它们可以提高模型的性能。pTM 范围从 0 到 1,为 AF2 提供了对其 5 个预测输出进行排名的框架。pTM < 0.2 的预测要么是随机分配的残基模式,与假定的天然结构几乎没有相关性,要么是内在无序的蛋白质。pTM > 0.5 通常足以进行推断。

现在我们已经讨论了 AlphaFold 的基本原理及其指标,我们可以深入探讨低计算方法来探索 AlphaFold。

EMBL-EBI 方法

为了方便访问 AlphaFold2,DeepMind 与欧洲分子生物学实验室、生物信息学组合作,策划了 48 种常见物种的蛋白质,包括人类、小鼠、大鼠、猿类、果蝇和酵母。该数据库包含超过 100 万个结构,涵盖 48 种生物体的蛋白质组。尽管这与 AlphaFold2 提供的 2 亿个开放源代码相比仍然是微不足道的数量。然而,这个超方便的 API 补贴了与开源代码相关的高昂计算成本。网页可以通过以下链接访问

[## AlphaFold 蛋白质结构数据库

AlphaFold 蛋白质结构数据库

AlphaFold 蛋白质结构数据库

在网页上,用户可以通过蛋白质名称、基因名称、UniProt 访问号和 UniProt ID 搜索感兴趣的蛋白质。另一个替代的搜索方法是使用序列,如果蛋白质名称未知。未知蛋白质可以通过将蛋白质序列输入到另一个名为 Fasta 的工具中进行解码(链接见下)。

[## FASTA/SSEARCH/GGSEARCH/GLSEARCH < 序列相似性搜索 < EMBL-EBI

FASTA(发音为 FAST-AYE)是一套用于通过查询序列搜索核苷酸或蛋白质数据库的程序……

www.ebi.ac.uk

在 Fasta 上,用户可以通过输入已知序列来搜索包括蛋白质在内的大分子,序列参考一个通用蛋白质数据库,例如 UniProt。

EMBL-EBI 网页输出的折叠结构以 HTML 格式显示,由 pLDDT 进行颜色编码,并生成 PAE 图。用户可以以 PDB、mmCIF 或 JSON 格式下载预测的蛋白质结构。EMBL-EBI 速度快,几乎是瞬时的。它不需要安装或依赖项。EMBL-EBI 网页的缺点包括对序列输入的灵活性不足,并且它只能处理单体结构。此外,这些模型无法调整以预测使结构不稳定的突变残基或结构域。它们基本上适用于现成的预测,对推断或假设生成贡献甚微。

Colab notebook 方法

Colab notebook 是一个托管用 Python 编写的应用程序的网站。AlphaFold colab notebook 托管在 Google Cloud 服务器上。用户需要初始化并连接到服务器,该服务器为每个 colab notebook 会话分配 GPU 或 TPU 资源(RAM 和磁盘)。从用户体验的角度来看,这与开源预测的功能最为接近。AF2 colab notebook 为用户分配计算资源,但这些分配的资源不是无限的,并且限制用户在预测优化时最多使用 800 个残基。超过这个上限,AF2 的性能可能会大幅下降。Colab notebook 的其他缺点包括较长的等待时间(取决于实例分配的资源)。不适合大批量作业,基本上适合单序列预测。然而,与 EMBI-EBI 数据库相比,colab notebook 允许对预测模式进行一些修改。

如何使用 AlphaFold2 colab notebooks 的逐步说明

在 Google 上搜索 “AlphaFold2 colab notebooks” 或使用这个链接

[## Google Colaboratory

编辑描述

colab.research.google.com

重新连接下拉菜单将用户连接到 Google 云服务器。此步骤为用户分配会话的 GPU。请谨慎使用你的会话,因为 GPU 分配是有期限的,当分配的 GPU 用尽时,连接会被终止。然而,Google 提供了高级用户服务,为你分配接近无限的 GPU。蛋白质 MSA 输入到 query_sequence box 中。

同时,在运行作业之前,确保检查序列,修正空格和缩进。链间断裂可以用冒号 (😃 表示。例如 —EQVTNVGGAVVTGVTAVA:EQVTNVGGAVVTGVTAVA 表示一个同源二聚体。Amber Forcefields 放松蛋白质的 3D 结构。这允许侧链自由旋转,减少立体冲突和热力学违反。此步骤是可选的,不会显著改善模型性能。

此外,msa_mode 提供了序列搜索模式的选项。默认的 Many-against-Many 序列搜索模式使用 uniRef 和环境数据。用户可以选择仅使用 MMseq2 uniRef 或提供自定义序列搜索模式。

model_type 功能允许用户选择待预测蛋白质的结构维度。此功能支持对寡聚体或多聚体结构的预测。如果你处理的是寡聚体序列,请选择 Alphafold-multimer v1 或 v2 选项;如果结构是单体的,则选择 Alphafold2-ptm。auto 选项允许模型自动决定。默认选项是 auto。

num-recycles 功能使模型能够多次重复遍历序列和预测。这是需要 GPU 的原因之一,因为它使机器能够运行并行作业。默认的回收次数是 3,但建议扩展到 6 次或更多,直到模型达到接近准确的预测。同时,运行时间随着回收次数的增加而增加。

要运行作业,点击 runtime 下拉菜单,然后选择‘Run all’。这是最后一步,也可能是最简单的一步。然而,如果作业由于互联网连接或 GPU 可用性的中断而被打断,整个过程可能会停滞。因此,Run all 选项要求用户保持互联网连接,并保持 colab notebook 页面打开,直到作业完成。

完成后,AlphaFold2 colab notebook 返回 5 个模型预测结果及其各自的 pTM 和 pLDDT。‘最佳’预测结构会被排序,输出可以下载到用户的电脑本地驱动器或 Google Drive。压缩文件包含 5 个预测的 PDB 结构(如果使用了 amber relax 选项则为 10 个)、一个对应的 PAE 图以及 5 个 JSON 文件,包含 PAE 和 pTM 坐标或矩阵。

AlphaFold2 的限制

AlphaFold2 已经取代了当前的蛋白质和结构生物学范式。它无疑是 AI 在生物学中最具影响力的应用之一。尽管取得了这样的巨大发展,AF2 仍然有其局限性。AF2 在预测变异蛋白质、错义缺失或突变方面的准确性几乎可以忽略。以下是表达这一局限性的两篇出版物。

[## AlphaFold2 能否预测错义突变对结构的影响? - Nature Structural &…

我们对 H. Matsuo 对手稿的批评性阅读表示感激。这项工作得到了内部研究的支持…

www.nature.com](https://www.nature.com/articles/s41594-021-00714-2?source=post_page-----9bf75942c6d7--------------------------------) [## 使用 AlphaFold 预测单一突变对蛋白质稳定性和功能的影响

AlphaFold 通过实现从蛋白质中预测三维(3D)结构,改变了结构生物学领域…

www.biorxiv.org](https://www.biorxiv.org/content/10.1101/2021.09.19.460937v1?source=post_page-----9bf75942c6d7--------------------------------)

不过,不用担心!华盛顿大学的 David Baker 小组开发了一个名为 RosettaFold 的 AlphaFold2 姊妹项目。RosettaFold 在预测突变蛋白质结构方面表现更为出色(与 MSA 转换器如 AF2 相比,模型性能提高了 1.25 倍)。这是相关论文。

[## 使用 RoseTTAFold 进行准确的突变效应预测

预测突变对蛋白质功能的影响是一个杰出的挑战。在这里,我们评估了…

www.biorxiv.org](https://www.biorxiv.org/content/10.1101/2022.11.04.515218v1?rss=1&source=post_page-----9bf75942c6d7--------------------------------)

总体而言,AF2 在其出现之前的其他可用选项中表现得极其出色。无论是在预测蛋白质构建块的扭转角度和卷积方向,还是它们在空间中的相对位置。

结论

EMBL-EBI 数据库和 AF2 的 Google Colab 笔记本提供了 AF2 模型的访问权限,免去了用户在高性能计算环境中遇到的计算麻烦。我希望我能为你指明正确的方向,并提供一些关于 AF2 实用性的见解,而无需使用传统的计算命令提示符和资源。

使用 AlphaFold2 玩得开心!确实是一个革命性的工具。

参考文献

  1. Jumper J, Evans R, Pritzel A. 使用 AlphaFold 的高精度蛋白质结构预测。 (2021),Nature;596(7873):583–589。 doi:10.1038/s41586–021–03819–2

  2. Baumkötter F, Schmidt N, Vargas C, 等。 淀粉样前体蛋白的二聚化和突触生成功能依赖于铜与生长因子样域的结合。 (2014),J Neurosci

  3. Wang M, Audas TE, Lee S. 揭开恶名:改变对淀粉样蛋白的看法。 (2017),Trends Cell Biol;27(7):465–467。 doi:10.1016/j.tcb.2017.03.001

如何使用 FastAPI 和 Google Cloud Run 部署和测试你的模型

原文:towardsdatascience.com/how-to-deploy-and-test-your-models-using-fastapi-and-google-cloud-run-82981a44c4fe

在这个端到端教程中学习如何将你的模型转变为一个在云中运行的服务

Antons Tocilins-RubertsTowards Data Science Antons Tocilins-Ruberts

·发表于 Towards Data Science ·阅读时间 12 分钟·2023 年 3 月 8 日

--

图片由 Taylor Vick 提供,来源于 Unsplash

介绍

MLOps(机器学习运维)在数据专业人员中越来越受欢迎。能够将机器学习(ML)项目从训练到生产的全栈数据科学家需求不断增加,所以如果你在这一领域感觉有些不适,或者想要快速复习一下,这篇文章就是为你准备的!如果你从未部署过模型,我强烈建议你查看我之前的文章,它会更详细地介绍 ML 部署,并解释许多在本文中会用到的概念。

## 机器学习部署简介:Flask、Docker 和 Locust

学习如何在 Python 中部署你的模型,并使用 Locust 测量性能

towardsdatascience.com

本帖将涵盖相当多的话题,所以可以随意跳到你最感兴趣的章节。本文的结构如下:

  1. 使用 FastAPI 指定推理端点的 API

  2. 使用 Docker 将端点容器化

  3. 将 Docker 镜像上传到 GCP 的 Artifact Registry

  4. 将镜像部署到 Google Cloud Run

  5. 监控推理速度并进行负载测试

项目步骤概述。图片由作者提供。

设置

首先,你需要在本地计算机上安装 fastapiuvicornDocker 来完成前两步。第三步和第四步需要你拥有一个已启用 Artifact RegistryCloud Run API 的有效 Google Cloud Platform 账户。幸运的是,GCP 提供了免费试用期和免费额度,你可以使用 Gmail 账户激活。此外,你还需要安装 gcloud CLI 工具,以便通过 CLI 与 GCP 账户进行交互。最后,你还需要安装 Locust 进行负载测试。具体的安装步骤会根据你的系统有所不同,但下面你可以找到所有安装说明的链接。

所有代码都可以在 这个 GitHub 仓库 中找到。一定要拉取代码并跟着操作,因为这是最好的学习方法(在我看来)。

项目概述

为了使学习更加实用,本文将展示如何部署一个贷款违约预测模型。模型训练过程超出了本文的范围,但你可以在 Github 仓库 找到一个已经训练好的模型。该模型是在经过预处理的 美国小企业管理局数据集(CC BY-SA 4.0 许可证)上训练的。欢迎探索数据字典以了解每一列的含义。

本项目的主要目标是创建一个工作端点(称为 /predict),该端点将接收包含贷款申请信息(即模型特征)的 POST 请求,并输出一个包含违约概率的响应。

简化的机器学习预测系统。图像由作者提供。

既然这些都处理完了,我们终于可以开始了!首先,让我们看看如何使用 FastAPI 库创建一个推理 API。

创建推理 API

什么是 API?

API(应用程序编程接口)是一组已建立的协议,用于外部程序与您的应用程序进行交互。为了更直观地解释一下,这里有一个来自 Stack Overflow 上一个很好的回答 的引用:

Web APIs 是在网络服务器上运行的应用程序的入口点,允许其他工具以某种方式与该网络服务交互。可以将其视为软件的“用户界面”。最终,任何 web API 其实就是一系列 URL 和如何与它们交互的说明,仅此而已

换句话说,我们的 API 将负责指定如何与托管 ML 模型的应用程序进行交互。更准确地说,它将指定:

  • 预测服务的 URL 是什么

  • 服务期望的输入信息

  • 服务将提供哪些信息作为输出

FastAPI

FastAPI 是一个用于开发 Web API 的 Python 框架。它与 Flask 非常相似(在我 之前的文章 中有介绍),但有一些显著的优势。

  • 内置数据验证使用 Pydantic

  • 使用 SwaggerUI 的自动文档生成

  • 使用 ASGI 的异步任务支持

  • 更简单的语法

该框架的创建者基本上探索了构建 API 的其他所有解决方案,决定了他喜欢哪些功能,并将所有这些良好功能合并到了 FastAPI 中(我们对此非常感激,所以不要忘记给 github 仓库 点个星)。

输入和输出验证

在创建端点之前,定义预期的输入和输出信息类型是很重要的。FastAPI 接受 JSON 请求和响应,但必须指定所需字段及其数据类型。为此,FastAPI 使用了 Pydantic,它在运行时验证类型提示,并在提交无效数据时提供清晰且用户友好的错误消息。

Pydantic 强制执行这些数据类型的方式是通过数据模型。数据模型只是继承自 pydantic.BaseModel 的 Python 类,并将预期字段及其类型列为类变量。上面你可以看到我如何在 LoanApplication 类中指定预期的输入,以及在 PredictionOut 类中指定预期的输出。这种严格的验证可以帮助你在开发过程中尽早发现错误,防止它们在生产中引发问题。我们还可以为这些参数指定可接受的范围,但我现在会跳过这一步。

应用程序和端点创建

FastAPI 用于创建应用程序并定义作为 API 的不同端点。这里有一个非常简单的 FastAPI 应用示例,其中包含一个预测端点。

让我们解析一下上面的代码:

  1. 它导入必要的库并加载一个已经保存到文件中的预训练 CatBoost 模型

  2. 它通过将 FastAPI 类分配给 app 变量来创建一个 FastAPI 应用实例

  3. 它通过用 @app.post("/predict", response_model=PredictionOut) 装饰器装饰一个函数来定义一个用于进行预测的 API 端点,该端点期望 POST 请求。response_model 参数指定了 API 端点的预期输出格式。

  4. predict函数接收一个负载,该负载预计是一个名为LoanApplication的 Pydantic 模型的实例。这个负载用于创建一个 pandas DataFrame,然后用这个 DataFrame 使用预训练的catboost模型生成预测。预测的违约概率作为 JSON 对象返回。

请注意,之前定义的输入和输出数据类是如何发挥作用的。我们使用装饰器中的response_model参数指定期望的响应格式,并在predict函数中将请求格式作为输入类型提示。非常简单而干净,但非常有效。

就这样!在几行代码中,我们创建了一个带有预测端点(API)和完整数据验证能力的应用程序。要在本地启动此应用程序,你可以运行这个命令。

uvicorn app:app --host 0.0.0.0 --port 80

如果一切顺利,你会看到应用程序在指定的地址上运行。

截图由作者提供

使用 Swagger UI 的文档

这是 FastAPI 的一个酷炫之处——我们免费获得了文档!前往http://0.0.0.0:80/docs,你应该能看到一个 Swagger UI 屏幕。

Swagger UI。截图由作者提供。

在同一屏幕上,你可以通过点击我们的 POST 端点(绿色的)并编辑默认请求体来测试你的 API。然后,点击执行,你将看到 API 的响应。

用户界面中的请求体。截图由作者提供。

如果你正确地指定了所有内容,你应该会收到响应代码 200,这意味着请求已成功。此响应的主体应包含之前指定的default_probability的 JSON,这是我们/predict端点的输出。

用户界面中的响应体。截图由作者提供。

当然,你也可以使用request库以编程方式测试 API,但用户界面为你提供了一个不错且直观的替代方案。

使用 Docker 对应用程序进行容器化

正如我在之前的帖子中提到的,容器化是将应用程序及其所有依赖项(包括 Python)封装成一个自包含的、隔离的包的过程,这样可以在不同的环境(例如本地、云端、朋友的笔记本等)中一致地运行。FastAPI 有一个关于如何在 Docker 中运行 FastAPI 的极佳文档,如果你对更多细节感兴趣,可以查看这里

下面,你可以看到我们 API 的 Dockerfile(Docker 指令集)。

除了最后运行的命令外,它与我们在 Flask 中使用的完全相同。以下是对这个 Dockerfile 实现的简要描述:

  1. FROM python:3.9-slim:指定容器的基础镜像为 Python 版本 3.9 的 slim 变体,相较于普通 Python 镜像更小

  2. WORKDIR /app:这个指令将容器内部的工作目录设置为 /app

  3. COPY requirements.txt requirements.txt:这个指令将 requirements.txt 文件从主机复制到容器的 /app 目录

  4. RUN pip install --upgrade pip & RUN pip install -r requirements.txt:这个指令升级 pip 并使用 pip 安装 requirements.txt 文件中指定的所需包

  5. COPY ["loan_catboost_model.cbm", "app.py", "./"] .:这个指令将训练好的 CatBoost 模型和包含应用程序 Python 代码的 app.py 文件从主机复制到容器的 /app 目录

  6. CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "80"]:这个指令指定了容器启动时应该运行的命令。它运行 uvicorn 服务器,以 app 模块作为主应用程序,URL 为 0.0.0.0:80

构建 Docker 镜像

一旦 Dockerfile 被定义,我们需要基于它构建一个 Docker 镜像。构建镜像在本地非常简单,你只需在包含 Dockerfile 和应用代码的文件夹中运行以下命令即可。

docker build -t default-service-fastpai:latest .

这将创建一个名为 default-service-fastapi 的镜像,并将其保存在本地。你可以在 CLI 中运行 docker images 来查看它是否在你拥有的其他 Docker 镜像中列出。

将镜像推送到 Google Artifact Registry

将此镜像推送到 Google Artifact Registry 的过程稍微复杂一些,但Google提供了非常详尽的文档。这个过程包括五个步骤:

  1. 配置 Docker 权限以访问你的 Artifact Registry

  2. 在你的 GCP 中启用 Artifact Registry

  3. 在 Registry 中创建一个仓库

  4. 使用特定的命名约定标记你的镜像

  5. 推送镜像

首先,你需要确定要存储容器的位置。你可以在这里找到完整的列表。我将选择europe-west2,所以我用来验证我的 Docker 的命令是:

gcloud auth configure-docker europe-west2-docker.pkg.dev

接下来的两个步骤可以在你的 GCP UI 中完成。前往导航菜单中的 Artifact Registry,点击按钮以启用 API。然后,前往仓库并点击 创建仓库 按钮。在弹出的菜单中,指定这是一个 Docker 仓库,并将其位置设置为之前选择的那个位置(对我而言是 europe-west2

Google Artifact Registry。作者截图。

所有设置完成后,让我们开始主要部分。为了用合适的名称标记镜像并推送它,我们需要运行两个特定的命令。

docker tag IMAGE-NAME LOCATION-docker.pkg.dev/PROJECT-ID/REPOSITORY/IMAGE-NAME
docker push LOCATION-docker.pkg.dev/PROJECT-ID/REPOSITORY/IMAGE-NAME

由于镜像名称是 default-servise-fastapi,仓库名称是 ml-images,而我的项目 ID 是 rosy-flames376113,所以对我来说,命令如下:

docker tag default-servise-fastapi europe-west2-docker.pkg.dev/rosy-flames-376113/ml-images/default-servise-fastapi
docker push europe-west2-docker.pkg.dev/rosy-flames-376113/ml-images/default-servise-fastapi

命令执行可能需要一些时间,但一旦完成,你将能够在你的 Artifact Registry 界面中看到你的 Docker 镜像。

顺便提一下,你可以在 GCP UI 中通过点击项目名称旁边的下拉按钮(下例中的“我的第一个项目”)来查找你的项目 ID 或创建一个新项目。

GCP UI。截图由作者提供。

在 Google Cloud Run 上部署容器

实际的部署步骤在 GCP 中相对简单。你需要做的唯一事情是通过进入导航菜单中的 Cloud Run 部分并点击启用 API 的按钮来启用 Cloud Run API。

在 Cloud Run 中创建服务有两种方式 — 通过 CLI 或在 UI 中。我将向你展示如何使用 CLI,你可以自行探索 UI 选项。

 gcloud run deploy default-service \
      --image europe-west2-docker.pkg.dev/rosy-flames-376113/ml-images/default-service-fastapi \
      --region europe-west2 \
      --port 80 \
      --memory 4Gi

该命令将从之前上传的 Artifact Registry 镜像中创建一个 default-service(即我们的 API)。此外,它还指定了区域(与我们的 Docker 镜像相同)、要暴露的端口以及可用于服务的 RAM。你可以在 官方文档 中查看你可以指定的其他参数。

如果你在命令行中看到一个 URL — 恭喜!你的模型已经正式部署了!

测试部署

与之前的帖子类似,我将使用 Locust 来测试部署。这里是将要测试的测试场景 — 用户发送带有贷款信息的请求,并接收带有默认概率的响应(虽然不现实,但适用于我们的目的)。

在本地 Flask 部署的情况下,我的笔记本电脑在开始出现故障之前可以处理多达 ~180 个请求每秒。此外,响应时间随着每秒请求数量的增加而逐渐增加。让我们对我们的云部署进行测试,看看它的表现是否更好。要启动测试,请运行 locust -f app_test.pyapp_test.py 包含上述测试场景),并在默认位于 http://0.0.0.0:8089 的 UI 中输入你的服务器的 URL。我将用户限制设置为 300,生成速率为 5,但你可以根据需要进行更改。

请注意 Cloud Run 按请求计费,所以要谨慎设置这些参数!

本地部署(左)与云部署(右)负载测试。图表由作者提供。

当流量达到峰值后,这里是我为我的服务器看到的图表。首先,服务器在每秒 300 个请求的情况下没有崩溃,这真是好消息。这意味着云部署比在本地机器上运行模型更为可靠。其次,中位响应时间更低,并且在流量增加的情况下几乎保持不变,这意味着该部署性能也更佳。

然而,在第 95 百分位数中有两个显著的峰值——测试开始时和接近结束时。第一个峰值可以解释为 Cloud Run 服务器在开始接收流量之前保持空闲。这意味着服务需要预热,因此在开始时可能会出现较低的速度甚至一些失败。第二个峰值可能是由于服务器开始接近其容量。然而,你可能会注意到,在测试结束时,第 95 百分位数速度开始下降。这是因为我们的服务开始自动扩展(感谢 Google!),正如你在下面的仪表板中所看到的。

Cloud Run 中的指标仪表板。作者截图。

在峰值时,该服务实际上运行了 12 个实例,而不是 1 个。我认为这是像 Cloud Run 这样的托管服务的主要优势——你不需要担心部署的可扩展性。Google 会负责添加新资源(当然只要你能支付),并确保你的应用程序平稳运行。

结论

这段经历非常精彩,所以让我们总结一下我们在这篇文章中所做的一切:

  • 使用 FastAPI 创建了一个带有推理端点的基本 API

  • 使用 Docker 对 API 进行了容器化

  • 构建了 Docker 镜像并将其推送到 GCP 中的 Artifact Registry

  • 使用 Google Cloud Run 将此图像转化为服务

  • 使用 Locust 测试了部署的鲁棒性和推理速度

非常棒,继续阅读这些章节!我希望现在你对 ML 过程中的部署步骤感到更自信,并且可以将自己的模型投入生产。如果你还有任何问题,请告诉我,我会在下一个帖子中尽量解答。

还不是会员?

[## 使用我的推荐链接加入 Medium - Antons Tocilins-Ruberts

阅读 Antons Tocilins-Ruberts 的每一个故事(以及 Medium 上的其他成千上万的作者的故事)。你的会员费直接…

medium.com](https://medium.com/@antonsruberts/membership?source=post_page-----82981a44c4fe--------------------------------)

如何在 5 秒钟或更短时间内使用 Docker 部署 GitLab

原文:towardsdatascience.com/how-to-deploy-gitlab-with-docker-in-5-seconds-or-less-e1f9e95c0b76

启动生产就绪的 GitLab 实例的最快方法

保罗·克努尔斯特Towards Data Science 保罗·克努尔斯特

·发布于 Towards Data Science ·阅读时间 3 分钟·2023 年 3 月 27 日

--

由 rawpixel.com 提供的图片 来源于 Freepik

介绍

GitLab 是一个基于 Web 的 Git 仓库管理工具,帮助团队协作编写代码。同时,它提供了一个完整的 DevOps 平台,从版本控制、代码审查、问题跟踪到 CI/CD。

GitLab 的一个关键优势是其多样性和灵活性,因为它可以在本地托管,并且可以轻松定制以满足每个团队的需求。此外,它具有广泛的功能和集成,使其成为团队的不错选择。

使用 Docker, 设置 GitLab 实例极其简单快速。您可以通过一条命令启动 GitLab 实例,无需手动安装和配置依赖项。

这使它成为希望快速轻松开始使用 GitLab 的软件开发人员的绝佳选择。

使用 Sameersbn Compose 文件部署 GitLab

部署 GitLab 的第一步是下载最新版本的 Compose 文件:

wget https://raw.githubusercontent.com/sameersbn/docker-gitlab/master/docker-compose.yml

现在,生成三个至少 64 个字符长的随机字符串,打开 Compose 文件,并使用这些字符串进行:

  • GITLAB_SECRETS_OTP_KEY_BASE:用于加密数据库中的 2FA 密钥。如果您丢失此密钥,用户将无法使用 2FA 登录。

  • GITLAB_SECRETS_DB_KEY_BASE:用于加密 CI 密钥和导入凭证。如果更改或丢失,您将无法再使用 CI 密钥。

  • GITLAB_SECRETS_SECRET_KEY_BASE:用于生成密码重置链接和标准认证功能。如果更改或丢失,您将无法通过电子邮件重置密码。

启动您的 GitLab 实例

docker-compose up

几分钟后,GitLab 就会启动并可用。

使用 Docker 命令手动部署 GitLab

与其从 Sameersbn 下载最新的 Compose 文件,不如通过三个简单的步骤手动启动 GitLab 容器、Redis 容器和 PostgreSQL 容器。

步骤 1:启动 PostgreSQL 容器

docker run --name gitlab-postgresql -d \
    --env 'DB_NAME=gitlabhq_production' \
    --env 'DB_USER=gitlab' --env 'DB_PASS=password' \
    --env 'DB_EXTENSION=pg_trgm,btree_gist' \
    --volume ./gitlab_postgresql:/var/lib/postgresql \
    sameersbn/postgresql:12-20200524

步骤 2:启动 Redis 容器

docker run --name gitlab-redis -d \
    --volume ./gitlab_redis:/var/lib/redis \
    sameersbn/redis:latest

步骤 3:启动 GitLab 容器

docker run --name gitlab -d \
    --link gitlab-postgresql:postgresql --link gitlab-redis:redisio \
    --publish 10022:22 --publish 10080:80 \
    --env 'GITLAB_PORT=10080' --env 'GITLAB_SSH_PORT=10022' \
    --env 'GITLAB_SECRETS_DB_KEY_BASE=long-and-random-alpha-numeric-string' \
    --env 'GITLAB_SECRETS_SECRET_KEY_BASE=long-and-random-alpha-numeric-string' \
    --env 'GITLAB_SECRETS_OTP_KEY_BASE=long-and-random-alpha-numeric-string' \
    --volume ./gitlab_data:/home/git/data \
    sameersbn/gitlab:15.10.0

注意:docker 命令使用共享卷(通过 *./*)在执行 Docker run 命令的目录中创建。

访问你的 GitLab 实例

在 GitLab 应用程序成功启动后(可能需要几分钟),你可以切换到浏览器,指向 http://localhost:10080,并为 root 用户账户设置密码。

现在,GitLab 应用程序已经准备好进行测试,你可以开始向其中推送代码!

结论

设置个人 GitLab 并不是火箭科学,不到 3 秒就能完成(不包括镜像下载和启动时间)。

虽然本教程是针对你开发机器上的个人设置,但你也可以在任何服务器上轻松完成,以便让 GitLab 实例对所有人可用。

如果这样做,我建议使用 Sameersbn Compose 文件,并在前面添加一个 Traefik 代理,以便通过公共域名而不是 IP 访问。

以下教程涵盖了设置 Traefik 代理所需的所有步骤,以便通过主机名访问 GitLab 并用 SSL 证书保护连接:

## 如何设置带有自动 Let's Encrypt 证书解析器的 Traefik v2

今天拥有 SSL 加密的网站非常重要。本指南将展示如何轻松实现自动化……

www.paulsblog.dev

希望这篇文章为你提供了设置个人 GitLab 实例的快速而清晰的概述。

对于本教程有任何疑问吗?我很乐意听取你的想法并回答你的问题。请在评论中分享一切。

可以通过 我的博客LinkedInTwitterGitHub 随时联系我。

感谢阅读,祝编程愉快! 🥳 👨🏻‍💻

此帖子最初发布在我的博客上 https://www.paulsblog.dev/how-to-deploy-gitlab-with-docker-in-5-seconds-or-less/

如何部署机器学习模型?端到端的狗品种识别项目!

原文:towardsdatascience.com/how-to-deploy-machine-learning-models-end-to-end-dog-breed-identification-project-5689457d8973

在网上部署你的 ML 模型的最简单方法。

Gurami KeretchashviliTowards Data Science Gurami Keretchashvili

·发表于Towards Data Science ·9 分钟阅读·2023 年 4 月 3 日

--

I. 介绍

在这篇文章中,我将逐步讲解使用 Streamlit 部署你的 ML 项目到网上的最简单和最快的方法。这个项目是关于狗品种识别的,它可以将狗分类到 120 种品种中。我将更多关注于项目的部署部分,而不是构建复杂的机器学习模型。

在进一步讨论之前,让我们看看下面的项目演示:

部署演示(作者制作的 gif)

你可以在演示中尝试— 这里,

项目的 GitHub 链接在-这里

文章大纲:

  • 背景

  • 项目教程 A. 构建和训练模型 (Build_AND_Save_DL_model.ipynb)

    B. Streamlit 应用程序 (streamlit.py)

    C. 部署

  • 常见错误和故障排除

  • 结论和未来工作

II. 背景

在 Jupyter 笔记本中构建机器学习模型是一回事,而部署模型则是另一回事,这需要创建一个服务,供其他用户通过互联网访问。部署 ML 模型的方法和工具有很多,例如使用像 Flask 或 Django 这样的轻量级网络框架构建 REST API。该 API 可以从网页或移动应用程序中调用以获取模型的预测。此外,你还可以使用如 Google Cloud Functions 或 AWS Lambda 这样的平台将 ML 模型部署为无服务器函数。同时,你还可以将机器学习模型及其依赖项打包到 Docker 容器中,并将其部署到 Docker Swarm 或 Kubernetes 等管理平台。部署选项取决于项目需求,如预算、可扩展性和性能。根据这些需求,开发者应选择适当的方法。在我们的案例中,我们使用 Streamlit 构建机器学习模型,Streamlit 是一个用于构建数据科学网页应用程序的 Python 框架。我们还将使用相同的免费 Streamlit 平台来托管它。

III. 项目教程

项目库包含以下文件/文件夹。

项目目录(图片由作者提供)

两个重要的文件:

A. Build_AND_Save_DL_model.ipynb -> 训练和保存机器学习模型。

B. Streamlit.py -> 使用 Streamlit 构建交互式网页应用程序并进行自定义预测。

其他文件/文件夹:

  • data -> 该文件夹存储自定义数据集和狗品种名称(训练数据集较大,你可以从 Kaggle 直接下载

  • pretrained_models -> 存储预训练的精细调整过的 efficientnet3 模型。

  • README.md -> 项目相关信息的文本

  • custom_prediction.ipynb -> (可选)实验性 Jupyter 笔记本,用于对预训练模型进行自定义推理。

  • requirements.txt -> 用于在 Streamlit 网站上进行推理的包需求。

A. Build_AND_Save_DL_model.ipynb

  1. 导入库和辅助函数。

我使用了已经安装了 Python 库的 Google Colab。

# I use tensorflow 2.9.1 version to train model
!pip install tensorflow==2.9.1

import pandas as pd
import pickle
from PIL import Image
from google.colab import drive
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
import tensorflow as tf
import numpy as np
import zipfile
import os
import warnings
import shutil
import seaborn as sns
import random
from tensorflow.keras import layers
from tensorflow.keras.layers.experimental import preprocessing
warnings.filterwarnings("ignore")
drive.mount('/content/gdrive')
print(tf.__version__)

def unzip_data(filename):
  """
  Unzips filename into the current working directory.
  Args:
    filename (str): a filepath to a target zip folder to be unzipped.
  """
  zip_ref = zipfile.ZipFile(filename, "r")
  zip_ref.extractall()
  zip_ref.close()

def plot_value_count(df, column):
 """
 plots value count of dataframe column
 """
 sns.set(style='darkgrid')
 plt.figure(figsize= (20,10))
 sns.countplot(x=column, data=df, order = df[column].value_counts().index)
 plt.xticks(rotation=90)
 plt.show()

unzip_data("/content/gdrive/MyDrive/dog-breed-identification/dog-breed-identification.zip")

如果你尝试在本地训练模型,确保使用批处理脚本创建一个新的 python 虚拟环境:

python -m venv /path/to/new/virtual/environment

然后安装库,以避免包依赖问题。

2. 理解数据

构建机器学习模型很重要,但在此之前,关键是要可视化数据及其统计信息。让我们看看训练样本和测试样本的总数,以及狗的品种总数。同时,我们还可视化每个狗品种的频率。

labels_df=pd.read_csv('labels.csv')

train_images = os.listdir("/content/train/")
test_image = os.listdir("/content/test/")

# how many dog breed? 
target_labels = sorted(labels_df['breed'].unique().tolist())

print('train label shape:', labels_df.shape)
print('train dataset and test dataset sizes:',len(train_images), len(test_image))
print('total number of dog breeds: ',len(target_labels))
# lets plot distribution of dog breeds in training data
plot_value_count(labels_df, 'breed')

输出结果(图片由作者提供)

每个狗品种的计数(图片由作者提供)

3. 创建数据结构

原始数据只包含一个文件夹中的所有品种的图像,因此,我为每个品种创建了一个文件夹,以创建特定格式来存储数据,并在后续加载。

# 1.create class folders
parent_folder = "data"
datasets = ["all_data"]
for i in datasets:
  for j in target_labels:
      os.makedirs(parent_folder + "/" + i + "/" + j,  exist_ok=True)

# all data
for _, name, label in labels_df.itertuples():
  original_path = "/content/train/" + name +".jpg"
  dest_path = "/content/data/all_data/" + label
  shutil.copy(original_path, dest_path)

4. 可视化随机图像

在这里,我只是可视化随机训练图像。

all_data_dir = "/content/data/all_data/"

def view_25_random_image(target_dir, target_classes):
  plt.figure(figsize=(18, 12))
  random_images = []
  for i, target_class in enumerate(target_classes):
    # setup the target directory
    target_folder = target_dir + target_class

    # get the random image path
    random_image = random.sample(os.listdir(target_folder),1)
    img = mpimg.imread(target_folder + "/" + random_image[0])
    #print(target_folder + "/" + random_image[0])
    #print(random_image)
    plt.subplot(5,5,i+1)
    plt.imshow(img)
    plt.title(f"{target_class}")
    plt.axis("off");
    random_images.append(img)
    #print(f"image shape: {img.shape}")
  return random_images

random_images = view_25_random_image(target_dir=all_data_dir,
                                target_classes = random.sample(target_labels, 25))

狗的图像(图像由作者提供)

5. 获取数据

由于我们创建了特定的数据存储格式,因此我们可以轻松地从图像文件夹生成 tensorflow 数据集。

tf.random.set_seed(42)
IMG_SIZE  = (256,256)
IMG_SHAPE = IMG_SIZE +(3,)
BATCH_SIZE = 32
data_all = tf.keras.preprocessing.image_dataset_from_directory(all_data_dir,
                                                              label_mode = "categorical",
                                                              image_size=IMG_SIZE,
                                                              batch_size=BATCH_SIZE,
                                                               )
print(f"all_data batches: {len(data_all)}")

6. 训练模型

我使用了几种数据增强技术,如 RandomFlip、RandomCrop、Randomrotation 等。同时,我使用了两个回调函数,如减少学习率回调,它会在验证准确率停止提高时减少学习率,以及早停法,它会在验证准确率不再提高时停止训练。减少学习率有助于模型更慢地收敛,从而潜在地找到更好且更具泛化能力的解决方案。而早停法则有助于模型避免过拟合,提前停止训练。至于 ML 模型架构,我使用了预训练的 EfficientNetB3 模型,然后用我们的新数据进行微调。EfficientNetB3 是一种卷积神经网络架构,由 Tan 等人在 2019 年提出,使用较少的参数和计算资源而达到最先进的准确率。

#data augmentation
data_augmentation_layer = tf.keras.models.Sequential([
                                                preprocessing.RandomFlip("horizontal"),
                                                preprocessing.RandomCrop(height=224, width=224),
                                                preprocessing.RandomRotation(0.2),
                                                preprocessing.RandomZoom(0.1),
                                                preprocessing.RandomContrast(factor=0.1),
                                                ])

# EarlyStopping Callback (stop training in val accuracy not improves)
early_stopping = tf.keras.callbacks.EarlyStopping(monitor="val_accuracy", 
                                                  patience=5,
                                                  restore_best_weights=True)

#ReduceLROnPlateau Callback (Creating learning rate)
reduce_lr = tf.keras.callbacks.ReduceLROnPlateau(monitor="val_accuracy",  
                                                 factor=0.2, # multiply the learning rate by 0.2 (reduce by 5x)
                                                 patience=2,
                                                 verbose=1, # print out when learning rate goes down 
                                                 min_lr=1e-5)

baseline_model = tf.keras.applications.EfficientNetB3(include_top=False)
# trainable freeze
baseline_model.trainable = False
inputs = tf.keras.Input(shape = IMG_SIZE+(3,), name = "input_layer")
#x = data_augmentation_layer(inputs)
x = baseline_model(inputs, training=False) # weights whhich need to stay frozen, stay frozen
x = layers.GlobalAveragePooling2D(name="global_average_pool_layer")(x)
x = layers.Dense(len(data_all.class_names))(x)
outputs = layers.Activation("softmax", dtype=tf.float32)(x)
model_0 = tf.keras.Model(inputs, outputs)

train_size = int(0.9 * len(data_all))
print(train_size)

model_0.compile(optimizer=tf.keras.optimizers.Adam(),
                              loss="categorical_crossentropy",
                              metrics = ["accuracy"])

history_0 = model_0.fit(data_all.take(train_size),
                                      epochs=5,
                                      validation_data = data_all.skip(train_size),
                                      callbacks = [
                                                    early_stopping,
                                                    reduce_lr
                                                  ]) 

训练结果(图像由作者提供)

7. 保存模型和类名

这是最后一步。微调模型后,我保存了训练好的模型和品种名称,以便后续使用。

 #save model
model_0.save('/content/gdrive/MyDrive/dog-breed-identification/EfficientNetB3_Model.h5')

breed_names = data_all.class_names
breed_names = [n.replace('_',' ').capitalize() for n in breed_names]
print(breed_names[0:3])

#save dog breed names
with open('/content/gdrive/MyDrive/dog-breed-identification/class_names', 'wb') as fp:
    pickle.dump(breed_names, fp)

B. Streamlit.py

  1. 导入库、预训练模型和品种类名
import streamlit as st
import tensorflow as tf
from tensorflow.keras.preprocessing.image import load_img
from tensorflow.keras.preprocessing.image import img_to_array
import pickle
import numpy as np
import pathlib
import matplotlib.pyplot as plt
from PIL import Image

IMG_SIZE  = (256, 256)
IMG_SHAPE = IMG_SIZE +(3,)

#1\. load model
@st.cache_resource
def load_model():
    return tf.keras.models.load_model('pretrained_models/EfficientNetB3_Model.h5')

loaded_model = load_model()

#2\. load class names
with open ('data/class_names', 'rb') as fp:
    class_names = pickle.load(fp)

2. 创建网页应用

我使用 Streamlit 脚本创建了一个互动式网页界面。我创建了网页的标题和文件上传对象。

st.header("Dog Identification Application")

uploaded_file = st.file_uploader("Upload an image of your dog and identify its breed...", type=['jpg', 'jpeg', 'png'])

3. 进行预测

当文件上传后,图像首先被调整大小,然后输入到加载的预训练模型中。如果预测的置信度分数超过 50%,则会在图像顶部显示一个预测结果;如果最大置信度低于 50%,则会基于前两个最高置信度分数显示两个结果。在网站的末尾,有一个包含我们模型知道的所有 120 个狗品种的列表。

 # If an image is uploaded, display it and make a prediction
if uploaded_file is not None:
    img = Image.open(uploaded_file).resize(IMG_SIZE)
    img = np.array(img, dtype=np.float32)
    img_g = np.expand_dims(img, axis=0)
    custom_predict = loaded_model.predict(img_g)

    # Display the uploaded image and the predictions
    # st.write(f"{class_names[np.argmax(custom_predict[0])]}  ({round(np.max(custom_predict[0]*100))}% confidence)")
    if round(np.max(custom_predict[0]*100))>50:
        st.write(f'<p style="font-size:26px; color:green;"> {class_names[np.argmax(custom_predict[0])]}  ({round(np.max(custom_predict[0]*100))}% confidence)</p> ', unsafe_allow_html=True)
    else:
        argsorts = np.argsort(custom_predict[0])
        sorts = np.sort(custom_predict[0])
        st.write(f'<p style="font-size:26px; color:red;"> {class_names[argsorts[-1]]}  ({round(sorts[-1]*100)}% confidence) OR {class_names[argsorts[-2]]}  ({round(sorts[-2]*100)}% confidence)</p> ', unsafe_allow_html=True)

    st.image(img/255,  use_column_width=True)
    st.write('Notes:')
    st.write('1\. The model is a first simplest baseline version, the prediction result will be improved in later versions')
    st.write('2\. The model can identify up to 120 types of dog breeds')
    option = st.selectbox(
    'All Breeds',
    class_names)

你可以通过运行 bash 命令在本地主机上可视化结果

streamlit run Streamlit.py

C. 部署

由于所有项目都是在本地创建的,现在是时候将其部署到网上了。这一步非常简单。

  1. 上传到 GitHub

首先,我们上传以下文件/文件夹:

  • Streamlit.py,

  • pretrained_models/EfficientNetB3_Model.h5

  • data/class_names

  • requirements.txt

在 GitHub 存储库中。

2. 将 GitHub 存储库连接到 Streamlit.

之后,登录Streamlit网站,创建账户并点击“新建应用”

Streamlit 平台(图片来自作者)

我们只需按照指示操作。

Streamlit 的新应用程序说明(图片来自作者)

IV. 常见错误与故障排除

以下是构建项目过程中一些最可能的错误和建议的解决方案。

  1. 打包依赖问题:如果你在本地训练模型,可能会遇到此问题,请确保创建 python 虚拟环境,然后安装库。

  2. 打包错误:使用 Google Colab 时,某些 Tensorflow 版本在保存模型时会出现问题,因此在 Google Colab 中使用 Tensorflow 版本 2.9.1。

  3. 连接 GitHub 账户后 Streamlit 应用程序无法正常工作:这主要是因为缺少包。请确保在 GitHub 中上传了包含特定包的 requirements.txt 文件。Streamlit 将自动查找该文件并安装所有运行应用程序所需的包。

V. 结论与未来工作

总结来说,我开发了一个简单的深度学习模型,可以从 120 种狗品种中识别出狗。此外,我使用 Streamlit 将模型托管在网上。该模型在验证数据上的准确率约为 94%,这是一个相当不错的结果。尽管模型运行良好,但也存在一些缺点。例如,模型的置信度评分没有校准,即置信度评分不能代表真实的预测概率。此外,模型只知道 120 种狗的品种,因此无法识别所有狗品种。此外,模型不知道自己不知道的情况。例如,如果你上传一张飞机的图片,模型无法告诉你那不是一只狗。我将尝试在应用程序的后续版本中克服这些问题。

希望你喜欢这篇文章,现在可以开始自己创建漂亮的应用程序了。如果你有任何问题或想分享对这篇文章的看法,随时评论,我会很乐意回答。

如果你想直接支持我的工作并且获得 Medium 文章的无限制访问权限,请通过我的推荐链接成为 Medium 会员。非常感谢,祝你有美好的一天!

[## 使用我的推荐链接加入 Medium - Gurami Keretchashvili

阅读 Gurami Keretchashvili 的每个故事(以及 Medium 上的其他数千位作者的故事)。你的会员费直接…

medium.com](https://medium.com/@gkeretchashvili/membership?source=post_page-----5689457d8973--------------------------------)

如何将 PyTorch 模型部署为生产就绪的 API

原文:towardsdatascience.com/how-to-deploy-pytorch-models-as-production-ready-apis-f61136fd0244

一个将 PyTorch Lightning 和 BentoML 结合的端到端用例

Ahmed BesbesTowards Data Science Ahmed Besbes

·发表于 Towards Data Science ·阅读时间 12 分钟·2023 年 4 月 3 日

--

照片由 SpaceX 提供,来自 Unsplash

作为一名机器学习工程师,我在处理生产环境中的深度学习模型时经常遇到两个主要挑战。

第一个挑战是需要为每个项目重写模板代码,以处理训练循环、数据加载或度量计算等任务。由于这项工作常常使代码库变得更加复杂,并增加了不必要的抽象,这减慢了迭代过程。

第二个挑战涉及到将训练好的模型高效部署到云端所需的广泛技能或工具。这包括 Docker、API 或云服务,更不用提与 GPU 支持、多线程或可扩展性相关的知识。

如果你是数据科学家,你显然不需要了解所有这些。

幸运的是,有两个 Python 框架被设计用来缓解这些问题: Pytorch Lightning BentoML*。

在本文中,我们将结合这些框架来构建* 一个生产就绪的图像分类服务 并将其部署到 Kubernetes 原生环境中。

让我们来看看 🔍

工作流 — 作者提供的图像

1 — 使用 PyTorch Lightning ⚡ 训练多标签图像分类器

本节将重点介绍 PyTorch Lightning,并详细介绍其一些功能以及训练过程。

作为数据集,我们将使用来自 Kaggle 的 CelebFaces Attributes:它包含 +200K 张各种名人的面部图像,附加了诸如身份、地标位置和 每张图像 40 个二进制属性注释 等元数据。

我们将使用面部属性元数据来训练一个多标签分类器,以检测诸如发色、鼻子大小、秃顶等信息。

如前所述,我们将使用 Pytorch Lightning 来训练模型。

GIF 由作者修改

PyTorch Lightning 是 PyTorch 的一个轻量级封装,标准化了训练循环、数据加载和验证过程的许多方面。它提供了一个更高级的接口,简化了训练过程,并减少了所需的样板代码。

使用 PyTorch Lightning,你可以减少代码复杂性,提高可重现性,并加快开发速度。

图片由作者提供

要获取数据集,你需要登录到 Kaggle 并点击下载按钮。

你也可以在获得你的凭证 (kaggle.json) 后通过命令行下载它。

!cp kaggle.json ~/.kaggle/
!chmod 600 ~/.kaggle/kaggle.json
!kaggle datasets download jessicali9530/celeba-dataset
!unzip celeba-dataset.zip

解压文件夹后你会得到以下内容:

图片由作者提供

**list_attr_celeba.csv** 文件包含了我们感兴趣的属性:

import pandas as pd 

data = pd.read_csv("list_attr_celeba.csv")
data = data.replace(-1, 0)
data = data.sample(25000)

data.head(5)

截图由作者提供

图像位于 img_align_celeba/img_align_celeba/ 文件夹中。

现在我们开始训练这个模型:

👉 创建 Dataset

就像在 PyTorch 中一样,我们首先需要定义一个 Dataset。这是指示数据如何从磁盘读取并转换为张量的第一步。

# src/train/datasets.py

import os
from PIL import Image
import pandas as pd
import torch
from torchvision.transforms.transforms import Compose
from torch.utils.data import Dataset, DataLoader
import pytorch_lightning as pl
from src.train.utils import train_transform, valid_transform

class CelebaDataset(Dataset):
    def __init__(
        self,
        data: pd.DataFrame,
        image_folder: str,
        augmentation: Compose,
    ):
        self.data = data
        self.column_labels = self.data.columns.tolist()[1:]
        self.column_image_id = self.data.columns.tolist()[0]
        self.image_folder = image_folder
        self.augmentation = augmentation

    def __len__(self):
        return len(self.data)

    def __getitem__(self, index: int):
        row = self.data.iloc[index]
        labels = torch.FloatTensor(row[self.column_labels])
        image_id = row[self.column_image_id]
        image = Image.open(os.path.join(self.image_folder, image_id))
        tensors = self.augmentation(image)
        return dict(
            tensors=tensors,
            labels=labels,
        )

👉 创建 LightningDataModule

现在我们需要创建一个 LightningDataModule,它实际上是一个包含 train_dataloader(s)、val_dataloader(s)、test_dataloader(s) 和 predict_dataloader(s) 的集合,以及匹配的转换和数据处理/下载步骤。

LightningDataModule 将一切组合在一起,并随后传递给训练器。因此,你无需显式地调用训练或验证数据加载器:PyTorch Lightning 会自动处理。

class CelebaDataModule(pl.LightningDataModule):
    def __init__(self, train_df, test_df, batch_size, image_folder, num_workers):
        super().__init__()
        self.train_df = train_df
        self.test_df = test_df
        self.batch_size = batch_size
        self.image_folder = image_folder
        self.num_workers = num_workers

    def setup(self, stage=None):
        self.train_dataset = CelebaDataset(
            self.train_df,
            self.image_folder,
            train_transform,
        )
        self.test_dataset = CelebaDataset(
            self.test_df,
            self.image_folder,
            valid_transform,
        )

    def train_dataloader(self):
        return DataLoader(
            self.train_dataset,
            batch_size=self.batch_size,
            shuffle=True,
            num_workers=self.num_workers,
        )

    def val_dataloader(self):
        return DataLoader(
            self.test_dataset,
            batch_size=self.batch_size,
            num_workers=self.num_workers,
        )

    def test_dataloader(self):
        return DataLoader(
            self.test_dataset,
            batch_size=self.batch_size,
            num_workers=self.num_workers,
        )

👉 定义 LightningModule

一个 [LightningModule](https://lightning.ai/docs/pytorch/stable/api/lightning.pytorch.core.LightningModule.html#lightning.pytorch.core.LightningModule) 不仅定义了模型的架构。它将你的 PyTorch 代码组织成以下部分:

  1. 初始化 (__init__setup())

  2. 前向传播 — 就像在 PyTorch 中一样

  3. 训练循环 (training_step())

  4. 验证循环 (validation_step())

  5. 测试循环 (test_step()) — 这里未显示

  6. 优化器和学习率调度器 (configure_optimizers())

import torch
from torch import nn
from torch.optim import Adam
import pytorch_lightning as pl
from torchmetrics import AUROC
from torchvision.models import resnet34
from src.train.config import NUM_LABELS

class AttributeClassifier(pl.LightningModule):
    def __init__(self, lr: float):
        super().__init__()
        self.backbone = resnet34(pretrained=True)
        self.model = nn.Sequential(*list(self.backbone.children())[:-1])
        self.classifier = nn.Linear(512, NUM_LABELS)
        self.auroc = AUROC(task="multilabel", num_labels=NUM_LABELS)
        self.lr = lr
        self.criterion = nn.BCELoss()

    def forward(self, tensors):
        output = self.model(tensors)
        output = output.view(output.shape[0], -1)
        output = self.classifier(output)
        output = torch.sigmoid(output)
        return output

    def training_step(self, batch, batch_idx):
        tensors = batch["tensors"]
        labels = batch["labels"]
        outputs = self(tensors)
        loss = self.criterion(outputs, labels)
        self.log(
            "train_loss",
            loss,
            prog_bar=True,
            logger=True,
            on_epoch=True,
            on_step=True,
        )
        score = self.auroc(outputs, labels.long())
        self.log(
            "train_auc",
            score,
            prog_bar=True,
            logger=True,
            on_epoch=True,
            on_step=True,
        )
        return {"loss": loss, "predictions": outputs, "labels": labels}

    def validation_step(self, batch, batch_idx):
        tensors = batch["tensors"]
        labels = batch["labels"]
        outputs = self(tensors)
        loss = self.criterion(outputs, labels)
        self.log(
            "val_loss",
            loss,
            prog_bar=True,
            logger=True,
            on_epoch=True,
            on_step=True,
        )
        score = self.auroc(outputs, labels.long())
        self.log(
            "val_auc",
            score,
            prog_bar=True,
            logger=True,
            on_epoch=True,
            on_step=True,
        )
        return loss

    def configure_optimizers(self):
        optimizer = Adam(self.parameters(), lr=self.lr)
        return dict(optimizer=optimizer)

👉 定义回调函数

回调是你可以挂钩到训练管道中的实用函数,用于在工作流的特定时刻执行,例如,训练周期的开始或结束。

在接下来的部分,我们将使用两个回调,模型检查点(在指标改善时定期将模型保存到磁盘)和早停(当指标停止改善时停止训练)。

checkpoint_callback = ModelCheckpoint(
    dirpath="checkpoints",
    filename="best-checkpoint",
    save_top_k=1,
    verbose=True,
    monitor="val_auc",
    mode="max",
)

early_stopping_callback = EarlyStopping(
    monitor="val_auc", 
    patience=2, 
    mode="max"
)

👉 开始训练

一旦你将 PyTorch 代码组织成一个[LightningModule](https://lightning.ai/docs/pytorch/stable/api/lightning.pytorch.core.LightningModule.html#lightning.pytorch.core.LightningModule)[Trainer](https://lightning.ai/docs/pytorch/stable/common/trainer.html)会自动处理其他所有事情。

它运行训练和验证循环,计算指标并记录它们,执行回调。

early_stopping_callback = EarlyStopping(
    monitor="val_auc",
    patience=2,
    mode="max",
)
model = AttributeClassifier(lr=config.lr)

trainer = pl.Trainer(
    callbacks=[early_stopping_callback, checkpoint_callback],
    max_epochs=config.n_epochs,
)
trainer.fit(model, data_module)

作者提供的图片

经过仅 25K 图像的 10 个训练周期后,最佳模型在验证集上的 AUROC 达到了 0.864 AUROC

你可以从这个 S3 链接下载模型,并在Github查看代码。

2 — 将 Pytorch Lightning 模型转换为 TorchScript 🌬

本节将讨论 TorchScript 相对于 PyTorch(或 Lightning)的好处,并展示转换的过程。

TorchScript 是 PyTorch 中的一个功能,允许你序列化你的 PyTorch 模型,并在各种环境中运行它们。它提供了一种保存训练好的 PyTorch 模型并将其加载到无 Python 环境中,甚至在不同的硬件上运行,如 GPU、FPGA 或移动设备。

这相对于 PyTorch 有几个优点:

  1. 便携性:使用 TorchScript,你可以在任何支持 TorchScript 运行时的环境中运行你的 PyTorch 模型,无论平台或语言如何。

  2. 性能:TorchScript 可以通过融合操作和消除未使用的操作来优化你的 PyTorch 模型,以实现更快的执行。这可以带来显著的加速,特别是在资源有限的设备上,如手机。

  3. 安全性:通过部署 TorchScript 模型,你可以保护你的 PyTorch 代码和模型不被逆向工程或篡改。

  4. 部署简便性:使用 TorchScript,你可以轻松地将 PyTorch 模型部署到生产环境中,而无需 Python 环境。

对于 TorchScript 的更全面概述,我推荐你查看这个教程。

你可以通过调用to_torchscript方法轻松地将 Lightning 模块转换为 TorchScript。

script = model.to_torchscript()
torch.jit.save(script, "torchscript_model.pt")

经过一些测试,我发现使用 TorchScript 的 GPU 推理时间比 PyTorch Lightning 的推理时间低 2 倍。然而,这仅发生在小批量(根据我的测试,1 或 2 个样本)时。

无论如何,如果你想享受 TorchScript 的好处,你需要在 Python 运行时之外运行你的模型。

3 — 使用 BentoML + TorchScript 🍱 构建一个图像分类服务

在本节中,我们将构建一个 BentoML 服务并在本地运行,以服务于 TorchScript 模型。

如果你想跟随本教程,我建议你使用 Poetry 安装项目的依赖项。

git clone https://github.com/ahmedbesbes/face-attributes-bentoml
cd face-attributes-bentoml/
poetry install

你需要从 S3 下载模型:

cd face-attributes-bentoml/models/
curl -O https://bentoml-ts.s3.eu-west-3.amazonaws.com/ts_model.pt

现在,我们可以使用 BentoML API 将 TorchScript 模型本地保存。这将自动提供一个版本标签,并允许你稍后轻松地将模型加载为运行器。

BentoML 与 TorchScript 集成良好,因此你可以通过调用 bentoml.torchscript.save_model 方法来保存模型。

更多信息 here

# src/serve/save_model.py

import bentoml
import torch

path = "models/ts_model.pt"
script = torch.jit.load(path)
bentoml.torchscript.save_model(
    "torchscript-attribute-classifier",
    script,
    signatures={"__call__": {"batchable": True}},
)

你可以通过运行 **bentoml models list** 来检查模型是否已经使用唯一标识符保存。

图片由作者提供

现在模型已经使用 BentoML API 保存,我们可以创建一个服务。

首先我们导入依赖项,加载模型,从中获取一个运行器,并定义一个服务:

import torch
import bentoml
from bentoml.io import Image, JSON
from torchvision import transforms
from config import LABELS

torchscript_runner = bentoml.torchscript.get(
    "torchscript-attribute-classifier"
).to_runner()

svc = bentoml.Service(
    "face-attribute-classifier",
    runners=[torchscript_runner],
)

然后,我们定义一个 API 路由,该路由以图像作为输入,返回一个包含预测标签和相应概率的 JSON 作为输出。

@svc.api(input=Image(), output=JSON())
def classify(input_image):
    tensor = process_image(input_image)
    tensor = torch.unsqueeze(tensor, 0)
    predictions = torchscript_runner.run(tensor)
    _, indices = torch.where(predictions > 0.3)
    indices = indices.numpy()
    output = {}
    labels = []
    for index in indices:
        probability = round(float(predictions[0][index]), 3)
        labels.append(
            {
                "label_id": index,
                "label": LABELS[index],
                "probability": probability,
            }
        )
    output["labels"] = labels

    print(output)

    return output

要在本地启动服务,请运行以下命令:

poetry shell
poetry src/serve/
bentoml serve service:svc --reload

图片由作者提供

如果你想尝试这个 API,可以访问 Swagger UI(在 http://localhost:3000)并直接上传你的图片。

这里有一个例子。

演示

4 — 将服务部署到云端

在本节中,我们将构建一个 bento 并将其部署到专门设计用于托管和扩展的云平台上。

什么是 bento?

BentoML 将 ML 项目中所需的一切打包成一种叫做 bento 🍱的分发格式(这里的类比是有意义的,因为 bento 原本是一个日本的便当盒,里面装有一份主要菜肴和一些配菜)

当一个 bento 构建完成后,你可以将其部署到任何云基础设施上。

👉 构建 bento

要构建一个 bento,你必须首先提供一个名为 bentofile.yaml 的配置文件,说明 bento 应如何构建,即要包含哪些文件和所需的 python 依赖项。

service: "service.py:svc"
include:
  - "__init__.py"
  - "service.py"
  - "config.py"
  - "configuration.yaml"
python:
  packages:
    - torch==1.13.1
    - torchvision==0.14.1
    - pandas==1.5.3
    - scikit-learn==1.2.2
    - bentoml==1.0.16
    - bentoctl==0.3.4

运行以下命令将构建 bento。

cd src/serve/
bentoml build

图片由作者提供

👉 连接到 BentoCloud 并推送 bento

在推送 bento 之前,你需要确保你已经登录到一个作为远程注册表的云平台(类似于 Dockerhub 这样的容器平台)。

我很幸运,BentoML 团队为我提供了他们的 平台 的访问权限,在那里他们推送和托管 bentos。

想在 BentoCloud 上创建一个账户并开始部署精彩的模型吗?请查看这个 链接 来安排演示并与 BentoML 团队进行交流。

注册 BentoCloud 后,我能够生成 API 令牌并使用 yatai 命令登录。 (我们将在本文的最后一部分中详细了解 Yatai 的具体情况)

bentoml yatai login --api-token <API_TOKEN> \ 
                    --endpoint https://default.cloud.bentoml.com

登录 BentoCloud

登录后,你可以推送:这将同时将你的 bento 和基础的 模型 上传到云端。

bentoml push face-attribute-classifier:qwwyr5gl7cw6ehqa

推送我的第一个 bento

你可以检查这两个工件是否在界面上可见,连同其他 bentos 和模型(来自其他用户)

bento 和模型

👉 创建部署

从平台部署你的 bento 非常简单。前往部署选项卡,点击右上角的黑色 创建 按钮。

作者提供的图片

这将打开以下表单,你需要填写部署信息:

  • 集群信息和部署名称

作者提供的图片

  • bento 存储库及其版本

作者提供的图片

  • API 服务器的配置:副本数量、CPU 和内存请求

作者提供的图片

  • 每个运行程序的配置:在我们的例子中只有一个

作者提供的图片

一旦点击 提交 按钮,你可以开始部署。这将构建镜像并在单独的 Pod 上部署 API 服务器和运行程序。这是一个使 BentoML 与我之前使用的任何服务平台区分开的杀手级特性。

作者提供的图片

一旦你的 bento 成功部署,点击你的部署,然后点击 URL:

如果一切按预期工作,在浏览器中打开此 URL 将会带你到这个页面:

API 现在已经部署:你可以从互联网上的任何地方查询它。

需要在自己的基础设施上管理你的 bentos 吗?试试 Yatai

你可以通过 BentoML 团队开源的 Yatai 项目在你自己的云基础设施上复制 BentoCloud 平台。

来源: github.com/bentoml/Yatai

Yatai 作为一个集中式注册表,你可以将其部署到任何 Kubernetes 原生环境中。

它与所有主要云平台(AWS、Azure 和 GCP)兼容。

它帮助你管理部署生命周期,通过 API 或 Web UI 进行部署、更新或回滚。

结论 🔚

如果你读到这里,我感谢你的时间,并希望你喜欢了解 PyTorch Lightning、TorchScript 和 BentoML。

将这三种工具结合起来,帮助你使用最佳实践的 DevOps 方法来原型设计和部署工业级深度学习模型。

当然,我展示的工作流中的不同步骤还有改进的空间:我只是希望这篇文章能够作为一个好的起点,帮助你深入了解 MLOps。

资源 🗞

  • towardsdatascience.com/10-ways-bentoml-can-help-you-serve-and-scale-machine-learning-models-4060f1e59d0d

  • towardsdatascience.com/10-ways-bentoml-can-help-you-serve-and-scale-machine-learning-models-4060f1e59d0d

  • youtu.be/Dk88zv1KYMI

  • youtu.be/2awmrMRf0dA

对 Medium 不熟悉?你可以每月支付 5 美元订阅,解锁各种话题的无限文章(科技、设计、创业……)你可以通过点击我的推荐链接来支持我。

[## 使用我的推荐链接加入 Medium - Ahmed Besbes

阅读 Ahmed Besbes(以及 Medium 上成千上万的其他作者)的每一个故事。你的会员费用直接支持……

ahmedbesbes.medium.com

如何从头设计一个 dbt 模型

原文:towardsdatascience.com/how-to-design-a-dbt-model-from-scratch-8c72c7684203?source=collection_archive---------5-----------------------#2023-07-10

一个实际可用的 dbt 模型构建简单框架。

Taylor BrownlowTowards Data Science Taylor Brownlow

·

关注 发表在 Towards Data Science ·6 min read·2023 年 7 月 10 日

--

当我研究dbt 终极指南时,我对关于从头构建模型的材料的缺乏感到震惊。这不仅仅是工具中的具体操作步骤——这些在无数博客和教程中都有涵盖。我的意思是,如何确定正确的设计?如何确保你的利益相关者会使用这个模型?你如何确保它会被信任并理解?

当我们部署新模型而没有采取这些步骤时,可能会导致严重后果:

  • 我们面临着来自利益相关者的大量问题和后续请求。

  • 我们从其他数据工程师或分析工程师那里获得代码改进建议

  • 我们必须回去添加新功能,进行改进,并回答所有问题,才能完成我们的工作。

如果我们一遍遍重复这个过程,数据团队和业务团队之间的信任将开始下降,因为双方在反馈狂潮中逐渐变得更加疲惫,这可能是很难恢复的。

这突显了仔细考虑我们如何设计模型的重要性,不仅仅是在 dbt 中单独进行,而是与所有利益相关者共同协作,以确保模型是 准确有效 的,并且我们不会在每个模型上浪费 4-5 次的构建时间才使其 有用

本文是关于如何最好地设计和实施 dbt 模型的研究和实验成果。它不会包含在 dbt 中执行的任何命令,但会讲解如何思考你的模型,以及如何构建你的工作流程,以确保你不会浪费时间。

图片来源:Med Badr ChemmaouiUnsplash

不同的方法

幸运的是,我不是第一个想到这个问题的人。许多其他领域也遇到了类似的挑战,并创建了自己的框架和流程,我可以借鉴这些来考虑如何进行数据建模。例如:

敏捷原则 不鼓励软件工程师采用瀑布开发方法,这与快速变化的需求环境是背道而驰的 [1]。相反,敏捷方法拥抱快速迭代,并承认能够迅速响应变化需求的竞争优势。

设计原则 同样承认了在设计项目中与多个利益相关者合作时需要有意识的工作方法 [2]。该框架优先考虑人,并鼓励在每个开发阶段获取反馈,以便尽快找到最佳解决方案。

图片来源:设计委员会 的这项工作采用了 CC BY 4.0 许可证

即使是数据建模的教父 Ralph Kimball 也在他的 4 步数据建模过程 [3] 中强调了在建模过程初期从利益相关者那里获得高质量输入的重要性。第一步是尽可能多地了解业务流程,然后再考虑构建模型。

然而,在考虑这个问题时,我发现最有影响力的来源是 系统工程启发式 —— 一组关于处理复杂问题与多个利益相关者的真理 [4]。

  • 不要假设问题的原始陈述必然是最佳的,甚至是正确的。

  • 在项目的早期阶段,未知的问题比已知的问题更为棘手。

  • 尽可能在构建之前先建模。

  • 大多数严重错误发生在早期。

这些来源帮助塑造了以下从零开始设计数据模型的过程。

数据建模设计过程

所以我想建立一个符合这些原则的过程,一个可重复的过程,并且能够确保我的模型第一次构建得很好。

这是我得出的结果:

我们将在下面更详细地讲解每一步。

以下示例将展示来自 count.co 的截图,这里是数据画布,我是产品负责人。不过,值得注意的是,这一过程与工具无关。你可以参考截图中的示例 这里

图片由作者提供。所有 5 个步骤的过程展示。查看完整画布 这里

第 1 步:发现

目标:理解你正在建模的业务流程。

参与者:你、业务利益相关者

活动

  • 规划业务流程

  • 确定利益相关者希望对最终表格做什么(例如,他们需要计算哪些指标,需要添加哪些过滤器等)

  • 了解他们今天是如何做到这一点的(如果他们这样做的话)。那个解决方案有什么问题?

  • 还会有其他人使用这个吗?是否有其他次要利益相关者也应该沟通?

  • 还有其他相关的业务背景吗?例如,有人下周会有一个关于这个主题的大型演示

图片由作者提供。点击 这里 查看示例。

第 2 步:设计(并迭代!)

目标:规划构建模型的可能方法

参与者:你

活动

  • 规划最终表格

  • 你将选择什么粒度?

  • 你将包括哪些列?

  • 如果最终表格设计有多个选项,请规划这些选项,并在继续之前从利益相关者那里获取反馈

图片由作者提供。点击 这里 查看示例。

第 3 步:原型制作(并迭代!)

目标:规划如何到达商定的最终表格。包括代码和解释。

参与者:你、数据团队成员、利益相关者

活动

  • 规划模型的每一步,包括每个阶段的代码和结果

  • 确保数据团队的另一名成员审核你的代码

  • 与相关方一起审查逻辑,以确保它符合他们的期望和背景

  • 验证原型模型的结果

  • 迭代直到业务相关方和数据团队都理解并批准方法和结果

作者提供的图片。点击 这里 查看示例。

第 4 步:部署

目标:在 dbt 中部署模型

参与者:你

活动

  • 取最终原型代码并将其部署到你的 dbt 代码库中

  • 确保它通过所有测试

作者提供的图片。这是如何将原型 SQL 导出为 dbt 友好的语法。

第 5 步:交付

目标:让相关方知道表格现在已可用以及如何与表格互动

参与者:你,业务相关方

活动

  • 创建文档(在 dbt 或其他地方)

  • 将表格和文档的链接发送给相关方

  • [可选] 使用表格创建示例分析,以便他们有一个起点

  • [可选] 为任何希望了解新表的人举行简短的介绍会

作者提供的图片。点击 这里 查看完整示例。

下一步

下一次你从头开始构建 dbt 模型时,试试这个过程。这将对你和你的相关方带来很大变化,但它已经证明能显著减少部署新模型所需的时间,并提高这些模型的整体采纳度。

将更多人纳入数据建模过程,并展示透明度,有助于促进信任并快速交付有价值的数据模型。

如果你尝试了这个过程,请给我留言,让我知道结果如何以及任何改进的想法!这些东西毕竟需要不断迭代…

资源

[1] Agile Manifesto. (2001). Agile Manifesto 背后的原则。于 2023 年 7 月 1 日获取自 agilemanifesto.org/principles.html

[2] Design Council. (2004). 创新框架。于 2023 年 7 月 1 日获取自 www.designcouncil.org.uk/our-resources/framework-for-innovation/

[3] Holistics. Kimball 的维度数据建模。于 2023 年 7 月 1 日获取自 www.holistics.io/books/setup-analytics/kimball-s-dimensional-data-modeling/

[4] 彼得·布鲁克。《系统工程启发式》见 SEBoK 编辑委员会。2023 年。系统工程知识指南(SEBoK),第 2.8 版,R.J. 克劳提耶(主编)。霍博肯,新泽西州:史蒂文斯理工学院信托基金。访问日期:[DATE]。www.sebokwiki.org。BKCASE 由史蒂文斯理工学院系统工程研究中心、国际系统工程理事会以及电气和电子工程师学会系统委员会管理和维护。

如何设计机器学习项目的路线图

原文:towardsdatascience.com/how-to-design-a-roadmap-for-a-machine-learning-project-1bbdb88bde48?source=collection_archive---------3-----------------------#2023-09-04

图片来源: DreamStudio

Heather CoutureTowards Data Science Heather Couture

·

关注 发表在 Towards Data Science ·10 min read·2023 年 9 月 4 日

--

当你开始一个新的机器学习项目时,第一步是什么?

我向多位初创公司中的机器学习领袖提出了这个问题,并收到了几种不同的回答。没有特定顺序:

  1. 尝试使用我们现有的模型看看是否适用于新任务。

  2. 开始探索和理解数据。

  3. 深入研究相关文献,了解之前做过的工作。

注意,这些第一步中没有包含编码和训练新模型,也没有设计数据预处理管道。

这三种方法各有优点。如果新项目与之前建模的内容(包括数据和任务)非常相似,那么尝试已经实施过的建模方法可以非常快速地建立任务的基准。在这样做的过程中,你也可能发现需要在数据预处理或建模中适应的新挑战。

这可能会引导你进入第#2 步:探索和理解数据。或者你可能从这里开始。识别新数据集的独特需求至关重要。也许预处理或注释需要不同的处理方式。也许数据中存在需要清理的伪影,或者标签并不总是正确的。理解预处理和建模需要应对的挑战是至关重要的。

但有些团队忽略的步骤,且是使项目成功的关键步骤,就是文献调研。是否有其他人对类似的数据做过类似的任务建模?如果你所处理的数据类型很常见,那么你可能可以应用一个非常严格的“相似”定义。但如果你正在处理新的成像模式,或者面临新的任务,你可能需要放宽“相似”的定义来找到相关研究。

这三个第一步在我用于规划新项目的过程中都是重要的:机器学习路线图。

当我与客户合作新项目时,路线图是第一步。路线图澄清了项目其余部分的工作范围。它减少了对需要实施内容的不确定性。它还降低了打转或在不成功的方法上浪费时间的可能性。通过在从头开始实施之前识别现有工具包,它节省了时间和金钱。而且它增加了项目成功的可能性。

ML 路线图涉及什么?让我带你了解核心组件。

1) 定义问题

首先清晰地定义你希望用机器学习解决的问题。在此过程中,退一步考虑一下机器学习是否是解决你问题的正确工具。这为整个项目奠定了基础,并帮助确保项目能够交付预期的结果。

定义问题包括识别业务问题、需要收集的数据和目标变量。清晰的问题定义和明确的目标将帮助你避免不必要的实验,专注于问题的最重要方面。

确定成功标准是关键。这可以包括评估指标,但更多的是关于预期的使用案例。

需要考虑的一些事项:

  • 你的解决方案是否相关?它是否会以解决当前瓶颈或痛点的方式融入现有工作流程?

  • 需要多准确才能改善当前过程?

  • 模型需要能够概括到哪些场景?这可能包括不同的成像设备、患者群体或光照条件等。

  • 模型需要多大程度的可解释性?了解模型如何工作能大大简化发现改进领域的过程,但这在建立信任或获得监管批准时也可能很重要。

  • 模型部署后是否会有计算限制?了解任何处理或内存限制可以缩小可能的方法范围。

通过前期明确问题,你为一个成功的机器学习项目奠定了基础,以实现预期结果。

2) 研究相关工作

研究相关工作是任何机器学习项目中的关键步骤。它有助于你识别类似问题的现有解决方案,并了解该领域的最前沿技术。

你可以从进行文献综述开始。这包括阅读研究论文、会议记录以及该领域的其他相关文献。

记录你阅读过的来源及每个来源的关键发现是至关重要的。这可以帮助你组织思路,识别现有解决方案中的模式和缺口。

  • 他们处理的数据类型是什么?

  • 有多少患者、图像等?

  • 他们是如何标注和结构化他们的训练数据的?

  • 他们使用了什么模型架构?

  • 他们是如何训练他们的模型的?

  • 他们遇到了哪些挑战?

  • 图像或标签的质量或数量是否存在问题?

  • 他们是如何收集独立数据以进行验证的?

这些都是在开始构建自己的解决方案之前需要了解的重要方面。

研究相关工作还可以帮助你识别现有的代码库、数据集或预训练模型,这些可以启动你的项目,节省时间和资源。

3) 理解数据

理解数据是开始任何机器学习项目的关键步骤。这是因为数据的质量和相关性对机器学习模型的性能有显著影响。

对于某些项目,数据可能已经收集。对于其他项目,必须首先定义并执行数据收集过程。你的文献综述可能有助于指导你应该收集什么类型的数据,以及你可能需要多少数据。

一旦数据被收集,通常需要进行标注——这也是可以通过你的文献综述得到启发的任务。

  • 需要什么类型的标注?像素级、补丁级和图像级是最常见的。

  • 已经使用了什么工具来辅助标注?标注是否可以来自其他模态?比如来自生物样本的分子分析,或像 Open Street Map 这样的现有标注集用于卫星图像。

  • 你的标注有多主观?研究或进行你自己的实验来评估观察者之间的一致性可以揭示这个挑战的程度。

你还需要了解数据的质量。这包括检查缺失值、异常值和数据中的不一致性。这些问题可能包括组织准备伪影、成像缺陷如噪声或模糊,或领域外的情况。通过识别数据质量问题,你可以适当预处理和清理数据,并计划解决任何无法事先消除的挑战。

数据预处理可能包括标准化、缩放或其他变换。对于大型图像,通常包括将其切分为小块。数据和注释必须以一种对模型训练高效的格式存储。

理解数据还帮助你识别任何可能影响模型性能和可靠性的偏差。偏差可能由于特定子组的训练数据不足或虚假的相关性。批次效应可能由于技术差异,如不同实验室的处理差异或地理变异。甚至可能是由不同标注者标记的样本。

对于大多数应用,应该咨询领域专家以了解数据:

  • 数据是如何收集的?

  • 它代表了什么?

  • 在研究数据时关注哪些特征?

  • 现实世界中可能存在或预期会出现哪些变化?

  • 可能存在什么样的伪影或质量问题,可能会混淆模型?

这些方面中的一些可能相当微妙,对于没有特定领域培训的人来说不明显。

这一关键步骤有助于评估数据的质量和相关性,识别和解决数据偏差,并确定适当的预处理技术。

4) 验证计划

在项目早期形成验证计划很重要,以揭示任何意外的挑战。最终模型预计在某些现实世界场景中表现出色,因此测试其能力是至关重要的。

首先要考虑的验证设置是将训练数据分为训练集、验证集和测试集。训练集通常是数据中最大的一部分,用于训练模型。验证集用于调整模型的超参数,例如学习率或正则化强度。测试集用于评估模型的性能,提供对模型在未见数据上的泛化能力的无偏估计。在模型开发过程中,测试集应与训练集和验证集完全分开。

通常,训练集、验证集和测试集是从可用数据中随机抽样的,同时保持所需的类别或目标变量的分布,以避免任何无意的偏差。当数据包含不同的组时,例如每个患者的多张图像、来自不同医疗中心的样本或来自不同地理区域的图像,需要对组进行更仔细的分层,以评估模型的泛化能力。来自同一组的所有样本应归入训练集、验证集或测试集中,而不是分布在三者之间。

交叉验证技术,如 k 折交叉验证或留一法交叉验证,也可以用于通过系统地轮换数据以获得更可靠的性能估计,这在处理小数据集时尤其常见。

评估模型性能涉及在训练集、验证集和测试集上计算一个或多个指标。适用的指标取决于应用,但可能包括准确率、灵敏度、特异性、F1 分数、AUC 分数、DICE 等。每个指标都将模型的预测与实际情况进行比较。

在一些应用中,在测试集上计算指标可能足够进行验证。在其他应用中,这部分保留的数据可能与真实世界场景不够相似。也许你的模型需要在不同地理区域或医疗中心的患者身上进行工作,而你没有可用的标注训练数据。你仍然需要在外部数据集中验证你的模型,以模拟其在真实世界中的表现和泛化能力。

5) 开发基线模型

在经过大量规划和研究后,你终于准备好开始建模了。但我不建议从最复杂的深度学习模型开始。先从简单的开始。首先开发一个简单的基线模型。这将使你能够测试你的数据处理、标注和验证管道,并揭示任何意外的挑战。

对于特定的项目,有很多合适的算法可供选择,选择最佳算法可能是一个挑战。最简单的基线可能是基于简单特征的线性分类器或回归器。或者可以使用转移学习而不进行微调,以最小化学习特征所花费的时间。此时不要过于费心调节超参数;默认参数甚至可能在这一步中就已足够。

开发基线模型有助于建立一个性能基准,用于评估未来模型的有效性。它有助于为你的项目设定现实的性能期望,并使你能够确定实现理想性能水平所需的改进量。

这个基准模型不应被视为最终模型。相反,它应作为开发更复杂模型的起点,这些复杂模型可以实现更好的性能。

6) 迭代和改进

迭代对于提高模型性能直到达到期望的准确度至关重要。

第一步是分析模型的性能。这涉及到检查几个不同的方面:

  • 审查训练和验证指标,以查找过拟合或模型收敛问题的迹象。

  • 将验证指标分层到不同的子组中,以识别改进的领域或可能的偏差。

  • 对失败模式进行分类,以找到改进的领域。

  • 与领域专家审查结果,获取关于他们认为重要的缺陷的反馈。

一旦你分析了模型的性能,你需要假设为何模型表现不佳以及如何解决这些问题。解决方案可能是以数据为中心的,比如收集更多数据或更改清理程序,或是以模型为中心的,比如更改模型架构或超参数。查看你文献搜索的笔记以获取灵感。

下一步是通过实施更改并在验证数据上评估模型的性能来测试你的假设。优先解决对模型最有害或最容易修复的问题。

迭代和改进机器学习模型是一个持续的过程。你需要继续测试和优化模型,直到达到期望的准确度。保持这些迭代的紧凑,以便尽快进行调整——尤其是当修复涉及到耗时的数据收集或注释更改时。

7) 部署、监控和维护

一旦你有一个达到期望性能水平的模型,你可以将其部署到生产环境中。这涉及将模型集成到你的应用程序或系统中,并确保其按预期执行。

第一步是确定部署模型的要求。这可能包括性能、可扩展性、安全性和用户界面等因素。你还需要选择一个部署平台;典型的选择是基于云的服务或本地基础设施。

下一步是将模型打包成可以部署的格式,并测试部署以确保其正常工作。这可能包括测试模型的性能、可扩展性和安全性。模型部署后,你需要监控其性能,并进行必要的调整。

部署机器学习模型是一个持续的过程,你需要不断改进模型,以确保它随着时间的推移保持有效。

最后,重要的是记录对模型或其训练过程所做的任何更改。这确保了模型在时间的推移中保持透明和可重复。

总结

机器学习项目是复杂且迭代的。这个路线图流程使您能够规划项目的每个方面。尽管细节可能会有所变化,但整体组件将保持不变。从定义问题到维护模型,每一步都需要仔细规划。尽可能地,您还应该考虑您的计划方法可能会失败的情况以及一些解决方案来应对这些可能的失败。

您的团队是否希望最大化图像和算法的影响力?

我与各种团队合作,挖掘他们数据的价值,创建更准确的模型,并从病理学和遥感图像中生成新的强大洞察。

预约一次免费的 机器学习发现电话会议,了解如何推进您的机器学习算法。

如何在 AWS 中设计 MLOps 架构?

原文:towardsdatascience.com/how-to-design-an-mlops-architecture-in-aws-67ee9843a430

为开发人员和架构师提供的指南,特别是那些未专门从事机器学习的人员,帮助他们为组织设计 MLOps 架构

Harminder SinghTowards Data Science 哈明德·辛格

·发表于数据科学之路 ·阅读时长 8 分钟·2023 年 4 月 14 日

--

介绍

根据 Gartner 的研究,仅 53%的机器学习(ML)项目从概念验证(POC)阶段推进到生产阶段。公司战略目标与数据科学家构建的机器学习模型之间通常存在不一致。DevOps、安全、法律、IT 和数据科学家之间的沟通可能不足,这会给将模型推向生产带来挑战。最后,团队可能发现维护生产中的模型以及推出新模型是困难的。这导致了 MLOps 的兴起,将 DevOps 的原则,如持续集成和持续交付(CI/CD)、自动化和协作,带入机器学习生命周期——开发、部署和监控。

在本文中,我将深入探讨以下内容:

  • 机器学习过程中的各个步骤

  • 介绍不同的 MLOps 组件,并解释它们为何必要,而不深入讲解只有数据科学家需要了解的细节

  • 根据组织的规模和成熟度,提供 MLOps 架构图

  • 关于开始 MLOps 旅程的一般建议

典型的机器学习过程

首先,让我们了解机器学习过程中的各个步骤。

机器学习过程 — 作者提供的图片

机器学习过程包括以下组件:

  1. 业务问题和机器学习问题陈述: 我们通过识别业务问题并确认机器学习是解决该问题的正确方案来开始过程。提出的机器学习解决方案应产生可衡量的业务成果。

  2. 数据收集、整合和清理: 在这一步中,数据科学家/数据工程师收集数据,将其与不同来源整合,并清理和转换以使其准备好供使用。数据工程师/科学家可能还会将数据划分为三组数据集——训练集、验证集和测试集,使用 80–10–10 或 60–20–20 的比例。训练集直接用于训练模型,验证集用于评估模型性能的未见示例,测试集则是另一组未见记录,用于检查模型在现实生活中的表现。

  3. 数据分析和可视化: 数据科学家随后进行探索性分析(EDA)以理解数据的结构和质量。这一步是了解数据差异、模式以及形成新假设所必需的。

  4. 特征工程:在这一步中,数据被选择、组合和处理,以使用统计或机器学习方法创建新变量。例如——进行对数变换、缩放或标准化。所有新特征共同有助于提高模型性能。

  5. 模型训练和参数调优:一旦新特征可用,数据科学家将训练各种机器学习模型,并调优超参数以满足所需的服务水平协议指标。

  6. 模型评估和部署: 在这一步中,从所有其他模型中选择准确度最高的模型,并将其部署到生产环境中。模型部署意味着模型已准备好进行预测。

  7. 监控和调试: 机器学习模型一旦部署到真实世界中,就会变得过时。模型必须定期使用更新的数据重新训练。

每个数据科学家在机器学习旅程初期或多或少都会遵循这一过程,并手动执行大多数上述步骤。

为了说明我的意思,让我们看看没有 MLOps 的架构图。

没有 MLOps 的机器学习架构 - 作者提供的图片

这里有一个相当标准的数据科学设置。数据科学家可以访问 AWS 账户中的 SageMaker Studio,他们可以使用 Jupyter Notebook 来开发模型。他们从各种数据源,如 S3 或 Athena,拉取数据,然后使用不同的机器学习技术创建模型。然后模型作为模型工件存储在 S3 中,并部署为 SageMaker 终端节点。通过 API 网关,终端节点对外界开放。

没有 MLOps 的机器学习项目挑战

虽然这种设置适合做概念验证,但它面临以下挑战:

  1. 变更依赖人工劳动: 机器学习模型的任何更改都需要数据科学家进行人工修改。这可能涉及重新运行 Jupyter notebook 单元格或使用最新版本的模型更新 SageMaker 端点。如果代码扩展到多个 Jupyter notebooks,变更变得繁琐且难以扩展。

  2. 没有代码版本控制: 数据科学家产生的代码存在于 Jupyter notebooks 中。这些笔记本难以进行版本控制和自动化。

  3. 没有反馈循环: 过程中的自动反馈循环不存在。如果模型质量恶化,你只会通过不满客户的投诉才会发现。

这些是使用 MLOps 可以避免的一些挑战。

小团队(1–3 名数据科学家)的 MLOps 架构

MLOps 可能会很复杂,你不必立即采用所有功能。你可以从最小的 MLOps 设置开始,并随着团队的增长或成熟逐步采用更多功能。

让我们以上述相同的机器学习设置为例,并在其中引入 MLOps 元素。下图所述的架构图适用于小公司或 1–3 人的数据科学团队。

小团队的 MLOps 架构 - 图片来源于作者

在此架构中

  1. 数据科学家从 SageMaker notebook 实例开始,使用代码提交或任何基于 git 的代码库进行代码的版本控制,并使用 docker 容器在 elastic container registry(ECR)中存储训练机器学习模型的环境。通过对代码、环境和模型工件进行版本控制,你可以提高模型的重现能力,并鼓励团队之间的合作。

  2. 你可以使用步骤函数或其他工作流工具,如 Airflow 或 SageMaker 管道,来自动化模型的再训练。数据科学家构建的再训练管道将使用版本代码和环境进行数据预处理、模型训练和模型验证,并将模型工件保存在 S3 中。管道可以利用各种服务,如 glue jobs、EMR jobs 和 lambda,来完成其流程,并可以使用基于时间的事件规则在事件桥中进行自动化。

  3. 模型及其版本管理通过模型注册服务(如 SageMaker 模型注册表)进一步管理。SageMaker 模型注册表存储模型及其元数据,如超参数、评估指标以及偏差和解释性报告,并允许查看、比较和批准或拒绝模型。实际的模型工件存储在 S3 中,模型注册服务作为附加层位于顶部。

  4. 最后,模型的部署通过 Lambda 函数自动化,该函数在模型在 Sage Maker 模型注册表中获得批准后立即触发。Lambda 会从 S3 中提取批准的模型,并在 Sage Maker 端点中更新版本而不发生停机。这些 Sage Maker 端点连接到 Lambda 和 API Gateway 以服务消费者应用程序,并附加有自动扩展组以应对请求的意外激增。你可以通过使用 Canary 部署进一步改进部署过程。这意味着少量用户请求将首先转发到新模型,任何错误将触发 Cloud Watch 警报以通知数据科学家。随着时间的推移,发送到新模型的请求数量将增加,直到新模型获得 100% 的流量。

这种架构对代码和模型进行版本管理,并自动化重新训练和部署。它提供了 Sage Maker 端点的自动扩展性和 Canary 部署的灵活性。然而,当数据科学家团队扩大时,我们可以引入更多 MLOps 元素。

中型和大型团队的 MLOps 架构

这种 MLOps 扩展了小型 MLOps 架构,并扩展到多账户设置,强调生产中模型的质量检查。

中型和大型团队的 MLOps 架构 — 作者图像

在此架构中:

  1. 数据科学家采用多账户方法,在开发账户中开发模型。

  2. 在小型 MLOps 设置中,数据科学家从 Sage Maker Notebook 开始,使用 Code Commit 对代码进行版本管理,并通过 ECR 对环境进行版本管理。然后,他们使用 Step Function 创建一个重新训练的流水线,该流水线包括模型训练、验证和在 S3 中保存工件的步骤。模型的版本管理由 Sage Maker 模型注册表完成,该注册表允许用户接受或拒绝模型。

  3. 模型的部署步骤包括 Sage Maker 端点和自动扩展组,这些端点和组连接到 Lambda 和 API Gateway 以允许用户提交推断请求。然而,这些组件服务位于不同的 AWS 账户中。推荐使用多账户策略,因为它提供了一种分离业务单元、轻松定义生产负载限制并提供每个架构组件成本细化视图的方式。

  4. 多账户策略包括在生产账户旁边设置一个暂存账户。新的模型首先部署到暂存账户,进行测试,然后部署到生产账户。此部署必须通过开发账户中的代码流水线自动进行。代码流水线由模型版本在模型注册表中获得批准时生成的事件自动触发。

  5. 监控生产环境中模型行为或准确性的变化是至关重要的。我们已在预发布和生产账户的端点启用了数据捕获功能。它将传入的请求和传出的推断结果捕获到 S3 桶中。这些捕获的数据通常需要与开发账户上的标签或其他数据结合,因此我们使用 S3 复制将数据转移到开发账户中的 S3 桶里。现在,为了判断模型或数据的行为是否发生了变化,我们需要一些对比的基准。这时,模型基线就发挥作用了。在训练过程中,我们可以生成一个基线数据集,记录数据和模型的预期行为。我们可以使用 SageMaker 模型监控器来比较这两个数据集并生成报告。

  6. 这个架构的最终步骤是根据模型报告采取行动。当检测到显著变化时,我们可以发送事件以触发管道的重新训练。

离开的思考

MLOps 是一个旅程。你不必立即使用复杂架构设计中的所有功能。你可以从基本步骤开始,集成版本控制和自动化。探索我上述介绍的所有功能,并根据你的业务需求对它们进行分类,然后在需要时开始采用这些功能。上述架构设计并不是实现 MLOps 的唯一方法,但我希望它们能为你作为架构师提供一些灵感。

如果你觉得我的文章对你有帮助,请给我留言。

如何通过假设检验检测数据漂移

原文:towardsdatascience.com/how-to-detect-data-drift-with-hypothesis-testing-1a3be3f8e625

MLOps

提示:忘记 p 值吧

Michał OleszakTowards Data Science Michał Oleszak

·发布于 Towards Data Science ·18 分钟阅读·2023 年 5 月 17 日

--

数据漂移是任何使用机器学习模型进行实时预测的人的一个担忧。世界在不断变化,随着消费者的口味或人口统计特征的变化,模型开始接收到与训练时不同的特征值,这可能导致意外的输出。检测特征漂移看起来很简单:我们只需确定训练和服务中相关特征的分布是否相同即可。确实有统计测试可以进行,但你确定你使用它们的方式是正确的吗?

单变量漂移检测

监控机器学习模型的部署后性能是其生命周期的关键部分。随着世界的变化和数据的漂移,许多模型往往会随着时间的推移表现出性能下降。保持警觉的最佳方法是实时计算性能指标,或者在地面真实数据不可用时进行估算

观察到的性能下降的一个可能原因是数据漂移。数据漂移是指模型输入在训练数据和生产数据之间分布的变化。检测和分析数据漂移的性质可以帮助将降级的模型恢复到正常状态。根据受影响的特征的数量和方式,数据漂移可以分为两种形式:应区分多变量数据漂移和单变量数据漂移。

多变量漂移是指单个特征的分布不一定发生变化,但它们的联合分布发生了变化。这种情况很难察觉,因为单独观察每个模型特征无法发现它。如果你对检测多变量漂移感兴趣,可以查看这篇关于如何基于 PCA 重构误差进行检测的文章

今天,我们关注单变量数据漂移,这是一种在生产环境中一个或多个特征的分布与模型训练数据中的分布发生变化的情况。

单变量数据漂移似乎比多变量漂移更容易检测:你不需要考虑多个变量之间的复杂统计关系。相反,对于每个特征,你只需比较两个数据样本——训练和服务——以检查它们是否以类似的方式分布。让我们看看在假设检验框架中通常如何执行这一操作。

假设检验入门

为了让内容更具体,我们关注卡方检验,它经常用于比较分类变量的分布。卡方检验广泛应用于A/B/C 测试,用于验证用户在不同处理下(例如,展示广告 A、B 或 C)是否表现出不同的行为模式(例如,购买频率)。

同样的检验通常应用于单变量数据漂移检测,以检查在两个状态(训练和服务,即市场设置中的购买和非购买)下测量的分类变量是否具有相同的类别频率。如果不相同,数据可能已经漂移。让我们来看一个具体的例子。

假设一个机器学习模型负责向用户推荐内容。假设模型的一个输入是用户的设备。假设设备是一个具有两个类别的分类变量:桌面和移动。以下是训练数据和服务数据中的类别计数。

由于服务样本远小于训练集,因此我们将类别计数表示为相对频率,以便于比较。

类别频率在训练集和服务集之间确实非常相似,但它们并不完全相同。由于随机抽样,即使在没有数据漂移的情况下,我们也很少观察到完全相同的频率。

所以问题变成了:我们观察到的类别频率差异是由随机抽样变异造成的,还是由设备变量的结构性变化造成的?后者意味着我们正在处理数据漂移。

为了回答这个问题,我们可以使用统计假设检验框架。

卡方检验

我们首先定义假设,即两个假设。原假设声称我们观察到的训练与服务之间的差异仅由随机机会造成——两个数据样本来自相同的基础分布(总体)。另一方面,备择假设则声称这些差异不是由随机性驱动的,而是由数据漂移造成的。

**H₀:训练/服务之间的差异是随机噪声的结果。

H₁:训练/服务之间的差异是数据漂移的结果。**

我们现在将使用数据来检验原假设。如果我们可以拒绝原假设,我们将声明数据漂移已经发生。如果不能,我们将说由于差异可能是由随机机会产生的,没有数据漂移的证据。

在我们的案例中,传统的测试方法依赖于这样一个事实:在原假设下,某个量被知道遵循卡方分布。这个量被恰当地称为卡方统计量。

卡方统计量

卡方统计量被定义为期望频率和观察频率之间平方差的标准化总和。现在让我们解读这个声明。

期望频率是我们在没有数据漂移的原假设下会观察到的频率。它们被定义为列联表的边际和相乘,并按总数进行缩放。

以下列联表表示我们设备变量在测试和服务数据中的频率,如之前定义的。

cont_table = pd.DataFrame([
  [35_252, 30_299],
  [3_516, 3_187]
])

然后我们可以沿着两个轴计算边际和,如下所示。

margsums = [cont_table.values.sum(axis=x) for x in [1, 0]]

最后,我们将它们相乘(我们需要转置第一个元素以使乘法可能),并按全局总和进行缩放。

from functools import reduce

margsums[0] = margsums[0].reshape(2, -1)
expected = reduce(np.multiply, margsums) / cont_table.values.sum()

在我们的特殊情况下,变量下只有两个类别,检验将只有一个自由度。这要求对观察值进行调整,这被称为耶茨连续性校正。如果有更多类别,你可以跳过以下四行代码。

diff = expected - cont_table.values
direction = np.sign(diff)
magnitude = np.minimum(0.5, np.abs(diff))
observed = cont_table.values + magnitude * direction

知道了期望值,我们可以根据之前定义的方式计算卡方统计量:即期望频率和观察频率之间的平方差的总和,经过期望频率的标准化。

chisq = np.sum(((observed - expected) ** 2) / expected)

这给出了 4.23,我们也知道这个统计量遵循卡方分布。这就是验证我们原假设所需的全部信息。

验证假设

让我们绘制出我们的测试统计量遵循的理论卡方分布。这是一个自由度为 1 的卡方分布。红线表示测试统计量的观察值。

蓝色阴影区域显示了在原假设下,即在没有数据漂移的情况下,检验统计量所遵循的分布。我们刚刚观察到的是红线标记的值。这一观察是否足够强烈以拒绝原假设?

比如说,卡方值为 10,在原假设下是非常不可能出现的。如果我们看到了这个值,我们可能会得出结论,认为原假设必须是错误的,数据已经发生了漂移。

另一方面,如果我们得到了 0.5 的卡方值,我们会认为在原假设下这个观察值并不令人惊讶,换句话说,训练数据和服务数据之间的设备频率差异可能只是偶然产生的。一般来说,这意味着没有理由拒绝原假设。

但我们得到了 4.23。我们如何决定是否拒绝原假设呢?

p 值

引入了臭名昭著的 p 值。它是一个回答以下问题的数字:在原假设为真的情况下,观察到我们获得的卡方值或更极端值的概率是多少?或者,使用某种符号,p 值表示在假设原假设为真的情况下观察到数据的概率:P(数据|H₀)(准确地说,p 值定义为 P(test_static(数据) > T | H₀),其中 T 是选择的检验统计量阈值)。请注意,这与我们实际感兴趣的内容不同,我们关心的是在给定我们观察到的数据的情况下,原假设为真的概率:P(H₀|数据)。

**p 值表示的内容:P(数据|H₀)

我们通常希望的:P(H₀|数据)**

从图形上讲,p 值是红线右侧的蓝色概率密度的总和。计算它的最简单方法是计算观察值的累积分布的一减,即左侧的概率质量减去 1。

1 - chi2.cdf(chisq, df=1)

这给我们带来了 0.0396。如果没有数据漂移,我们在大约 4%的情况下会得到我们所获得的检验统计量或更大的统计量。毕竟,这并不那么少见。在大多数使用情况下,p 值通常与 1%或 5%的显著性水平进行比较。如果它低于这个水平,则拒绝原假设。让我们保守一点,遵循 1%的显著性阈值。在我们这个接近 4%的 p 值的情况下,没有足够的证据来拒绝它。因此,没有检测到数据漂移。

为了确保我们的检验是正确的,让我们用 scipy 的内置测试函数确认一下。

from scipy.stats import chi2_contingency

chisq, pvalue, df, expected = chi2_contingency(cont_table)
print(chisq, pvalue)

4.232914541135393 0.03964730311588313

这就是假设检验的工作原理。但这对生产环境中的机器学习系统的数据漂移检测有多重要?

统计显著性与监控显著性

从广义上讲,统计学是基于小样本对整个总体做出推断的科学。当著名的 t 检验在 20 世纪初首次发布时,所有的计算都是用笔和纸完成的。即使在今天,STATS101 课程的学生也会了解到,“大样本”从 30 个观察值开始。

在数据难以收集和存储且手动计算繁琐的时代,统计上严谨的测试是回答有关广泛总体问题的好方法。然而,如今,随着数据的丰富,许多测试的实用性减少了。

特点是许多统计测试将数据量视为证据。数据越少,观察到的效应更容易受到采样误差的随机变动的影响,而数据越多,其方差则减少。因此,相同的观察效应在数据更多时对零假设的证据更为强烈。

为了说明这一现象,考虑比较两家公司 A 和 B 在员工性别比例上的差异。让我们设想两个情境。首先,随机抽取每家公司 10 名员工。在公司 A 中,10 人中有 6 人为女性,而在公司 B 中,10 人中有 4 人为女性。其次,增加我们的样本量到 1000。在公司 A 中,1000 人中有 600 人为女性,而在公司 B 中,1000 人中有 400 人为女性。在这两个情境中,性别比例是相同的。然而,更多的数据似乎为公司 A 雇佣比例上更多女性提供了更强的证据,不是吗?

这种现象通常在大数据样本的假设检验中表现出来。数据越多,p 值越低,因此我们更有可能拒绝零假设,并声明发现某种统计效应,例如数据漂移。

让我们看看这是否适用于我们的类别变量频率差异的卡方检验。在原始示例中,服务集的大小大约是训练集的十分之一。让我们将服务集中的频率乘以 1/100 到 10 之间的一组缩放因子,并每次计算卡方统计量和检验的 p 值。请注意,将服务集中的所有频率乘以相同的常数不会影响它们的分布:我们唯一改变的只是其中一个集合的大小。

training_freqs = np.array([10_322, 24_930, 30_299])
serving_freqs = np.array([1_015, 2_501, 3_187])

p_values, chi_sqs = [], []
multipliers = [0.01, 0.03, 0.05, 0.07, 0.1, 0.3, 0.5, 0.7, 1, 3, 5, 7, 10]
for serving_size_multiplier in multipliers:
  augmented_serving_freqs = serving_freqs * serving_size_multiplier
  cont_table = pd.DataFrame([
  training_freqs,
  augmented_serving_freqs,
  ])
  chi_sq, pvalue, _, _ = chi2_contingency(cont_table)
  p_values.append(pvalue)
  chi_sqs.append(chi_sq)

乘数等于一的值是我们之前计算的值。注意,当服务规模增加到仅为原来的 3 倍(标记为垂直虚线)时,我们的结论完全改变:我们得到的卡方统计量为 11,p 值接近零,在我们的案例中这对应于指示数据漂移。

这导致了虚假警报的数量增加。尽管这些效果在统计上可能显著,但从性能监控的角度来看,它们不一定具有重要意义。拥有足够大的数据集,即使是微小的数据漂移也会被指示出来,即使它的强度不足以恶化模型的性能。

学习到这一点后,你可能会想建议将服务数据划分为多个块,并对较小的数据集进行多次测试。不幸的是,这也不是一个好主意。要理解原因,我们需要深入理解 p 值的真正含义。

p 值的含义

我们已经将 p 值定义为在原假设为真的情况下,观察到至少与我们实际观察到的统计量一样不太可能的测试统计量的概率。让我们尝试解读这个复杂的表述。

原假设意味着没有效果,在我们的例子中:没有数据漂移。这意味着,无论训练数据和服务数据之间存在什么差异,它们都是随机抽样的结果。因此,p 值可以视为在这些差异仅来源于随机性的前提下,得到我们所观察到的差异的概率。

因此,我们的 p 值大约为 0.1,这意味着在完全没有数据漂移的情况下,10% 的测试将由于随机机会错误地发出数据漂移信号。这与我们之前介绍的 p 值表示的符号一致:P(data|H₀)。如果这个概率是 0.1,那么在 H₀ 为真(无漂移)的情况下,我们有 10% 的机会观察到的数据至少与我们观察到的数据一样不同(根据测试统计量)。

这就是为什么在较小的数据样本上进行更多测试不是一个好主意的原因:如果我们不是对每天的整个服务数据进行测试,而是将其分为 10 个块,每天进行 10 次测试,我们每天平均会出现一个虚假警报!这可能导致所谓的警报疲劳,即你被大量警报轰炸到停止关注它们的程度。当数据漂移真的发生时,你可能会错过它。

贝叶斯来拯救我们

我们已经看到,根据测试的 p 值检测数据漂移可能不可靠,从而导致许多虚假警报。我们该如何做得更好?一个解决方案是完全转变思路,采用贝叶斯测试,这让我们能够直接估计我们需要的 P(H₀|data),而不是 p 值 P(data|H₀)。

贝叶斯是什么

贝叶斯统计方法与传统的经典统计方法不同。在贝叶斯方法的基本假设值得单独讨论的情况下,为了讨论数据漂移,我们先考虑贝叶斯方法的最重要特征:统计参数是随机变量

参数是随机变量

参数是我们感兴趣的未知值,用于描述一个总体。在我们之前的例子中,我们模型用户使用移动设备的比例就是一个参数。如果我们知道在任何给定时间段内这个比例,我们就能自信地声明设备变量是否随时间漂移。不幸的是,我们不知道所有可能成为模型输入的用户的设备;我们必须使用模型所接收到的服务数据样本。

经典方法将我们感兴趣的参数视为一个固定值。我们不知道它的具体值,但它是存在的。基于样本数据,我们可以尽量估计它,但任何这样的估计都会因采样偏差而有一定的方差。简而言之,我们正在尝试用一个具有偏差和方差的随机变量来估计我们认为是未知的固定值。

贝叶斯方法将我们感兴趣的参数视为由某种概率分布描述的随机变量。我们尝试估计的是这个分布的参数。一旦完成这些,我们就可以对参数做出概率性的陈述,比如“我们的模型用户使用移动设备的比例在 0.2 到 0.6 之间的概率是 55%”。

这种方法非常适合数据漂移检测:与依赖模糊的 p 值,P(data|H₀)不同,使用贝叶斯检验,我们可以直接计算数据漂移的概率和幅度,即 P(H₀|data)!

数据漂移的贝叶斯检验

让我们尝试使用贝叶斯方法测试用户使用移动设备的频率是否在训练数据和服务数据之间发生了漂移。

获取我们需要的概率

那么,如何获得数据漂移的概率呢?可以使用所谓的贝叶斯规则,这是一条整洁的概率公理,允许我们在知道反向关系的情况下计算某事的条件概率。例如,如果我们知道 P(B|A),我们可以计算 P(A|B):

P(A|B) = P(B|A) * P(A) / P(B)

我们可以使用这个公式来估计移动用户的频率的概率分布,freq

P(freq|data) = P(data|freq) * P(freq) / P(data)

在贝叶斯术语中,上述方程中的所有元素都有其名称:

  • P(freq|data) 是我们追求的目标——即移动用户比例在看到数据后的后验分布或概率分布;

  • P(data|freq) 是似然或在给定特定移动用户频率的情况下观察到数据的概率;

  • P(freq) 是先验:我们在看到任何数据之前对移动用户比例的信念;

  • P(data) 可以看作是一个缩放因子,确保右边的结果作为概率分布总和为一。

如果我们将上述公式应用两次,一次用于训练数据,另一次用于服务数据,我们将获得两个频率参数的分布:一个基于分析数据,另一个基于参考数据。然后我们可以检查这两个分布如何比较以测试我们的假设:

P(H₀|data) = P(freq_ref|data) == P(freq_anal|data)

所以,为了计算后验,我们需要将先验与似然相乘,并将它们缩放到总和为一。概率分布上的算术运算不是一个简单的任务。我们该如何进行呢?

在某些特定情况下,当两个分布匹配得很好时,它们的公式会相互抵消,结果是一个已知的分布。在更复杂的情况下,使用马尔科夫链蒙特卡洛模拟方法来从后验分布中抽样值,而不是尝试计算其分布形式。还有第三种方法:在像我们这样简单的情况下,我们可以使用一种称为网格近似的方法。

数据漂移概率

让我们从定义数据开始。在训练数据中,我们有 65,551 个观察值,其中 30,299 个是移动用户。

num_obs = 65_551
num_mobile_obs = 30_299

我们感兴趣的参数是移动用户的频率。从理论上讲,它可以是 0%到 100%之间的任何值。我们将在从 0.0001 到 1 的网格上进行近似:

mobile_freq = np.arange(0, 1.0001, 0.0001)

我们现在可以创建一个包含所有移动用户频率及其观察数量的网格:

devices = pd.DataFrame([(x, y) for x in [num_mobile_obs] for y in mobile_freq])
devices.columns = ["num_mobile", "mobile_freq"]

是时候定义先验了:在看到任何数据之前,我们对我们的参数知道或假设了什么。可能是我们什么也不知道,或者我们不想用我们的信念过多地影响结果。在这种情况下,我们会采用无信息先验,例如均匀分布。这对应于将先验设置为全 1,表示对每个可能的移动用户百分比表达相同的先验概率。

devices["prior"] = 1.

接下来,我们的最后一个构建块,似然。在我们的问询中,用户可以是移动用户或非移动用户。这需要使用二项分布。对于数据框中的每一行,我们将计算给定观察到的移动用户数量、总用户数量和假设的移动用户频率的二项概率质量。

devices["likelihood"] = binom.pmf(
  devices["num_mobile"], 
  num_obs, 
  devices["mobile_freq"]
)

我们剩下的就是遵循贝叶斯公式:将先验与似然相乘,并将结果缩放到总和为一,以得到后验。

devices["posterior"] = devices["prior"] * devices["likelihood"]
devices["posterior"] /= devices["posterior"].sum()

让我们切分网格,选择与我们观察到的移动用户数量相匹配的后验分布。

mobile_obs = devices[devices["num_mobile"] == num_mobile_obs]
mobile_obs["posterior"] /= mobile_obs["posterior"].sum()

我们现在可以绘制训练数据中移动用户频率的后验概率。

我们现在可以对我们的服务数据做完全相同的操作。在此过程中,我们将向代码中添加一项内容:在一个名为samples的字典中,我们将存储来自后验分布的样本,包括训练和服务数据。以下是实现这一任务的代码片段。

results = {}
for dataset, num_obs, num_mobile_obs, color in zip(
  ["training", "serving"], [65_551, 6703], [30_299, 3187], ["blue", "green"]
):
  # Set up grid
  num_mobile = np.arange(0, num_obs + 1, 1)
  mobile_freq = np.arange(0, 1.0001, 0.0001)
  devices = pd.DataFrame([(x, y) for x in num_mobile for y in mobile_freq])
  devices.columns = ["num_mobile", "mobile_freq"]

  # Follow Bayes rule to compute posterior
  devices["prior"] = 1.
  devices["likelihood"] = binom.pmf(
    devices["num_mobile"], 
    num_obs,
    devices["mobile_freq"],
  )
  devices["posterior"] = devices["prior"] * devices["likelihood"]
  devices["posterior"] /= devices["posterior"].sum()

  # Ger posterior for observed number of mobile users
  mobile_obs = devices[devices["num_mobile"] == num_mobile_obs]
  mobile_obs["posterior"] /= mobile_obs["posterior"].sum()

  # Sample from posterior and store the draws
  samples[dataset] = random.choices(
    mobile_obs["mobile_freq"].tolist(),
    weights=mobile_obs["posterior"].tolist(),
    k=10_000,
  )

  # Plot the posterior
  sns.lineplot(
    mobile_obs["mobile_freq"],
    mobile_obs["posterior"], 
    color=f"dark{color}",
    label=dataset,
  )
  plt.fill_between(
    mobile_obs["mobile_freq"],
    0,
    mobile_obs["posterior"],
    color=f"light{color}",
  )
  plt.xlim(0.45, 0.50)
  plt.xlabel("Proportion of users using a mobile device")
  plt.ylabel("Probability density")

测试时间

对于训练和服务数据,我们已经估计了移动用户比例的概率分布。这个比例有不同吗?如果有,我们就有一些数据漂移发生。由于上图中两个分布仅有少量重叠,它们似乎不同。不过,让我们尝试获得定量确认。

我们可以使用从两个分布中获取的样本进行此操作:我们只需检查服务数据的比例比训练数据中的比例更大多少次。这是我们对数据漂移概率的估计。

np.mean([
  serving > training
  for serving, training in
  zip(samples["serving"], samples["training"])
])

0.977

device 变量发生漂移的概率为 97.7%。漂移几乎肯定已经发生!但其规模有多大呢?

np.mean([
  serving - training
  for serving, training in
  zip(samples["serving"], samples["training"])
])

0.013

服务数据中移动用户的比例比训练数据中的相应比例高出 1.3 个百分点,期望值如此。

收获

  • 依赖假设检验的单变量数据漂移检测方法并不总是可靠:结果可能依赖于样本大小,而 p 值实际上并不能直接测量数据漂移的概率或大小。

  • 依赖这种假设检验往往会导致许多虚假警报和警报疲劳。

  • 一种替代方法是使用贝叶斯方法,这允许我们直接估计数据漂移的概率和大小,而没有传统测试的许多缺陷。

本文也发表在 NannyML 博客上。

感谢阅读!

如果你喜欢这篇文章,为什么不订阅电子邮件更新以获取我新文章的通知呢?通过成为 Medium 会员,你可以支持我的写作,并获得对其他作者及我的所有故事的无限访问权限。

需要咨询?你可以随时问我任何问题或在这里预约一对一咨询。

你还可以尝试阅读我其他的文章。不知道选择哪个?可以从这些中选一个:

## TensorFlow 的模型优化

使用量化和剪枝减少模型的延迟、存储和推断成本

[towardsdatascience.com [## 忘掉 ChatGPT

Bard、Sparrow 和多模态聊天机器人很快将使其过时,原因如下。

pub.towardsai.net ## 自监督学习在计算机视觉中的应用

如何仅用少量标记示例来训练模型

[towardsdatascience.com

所有图片,除非另有说明,均由作者提供。

如何检测机器学习模型中的漂移

原文:towardsdatascience.com/how-to-detect-drift-in-machine-learning-models-8a0be4049eed

这可能是为什么你的模型在生产环境中性能下降的原因

Edwin TanTowards Data Science Edwin Tan

·发表于Towards Data Science ·8 分钟阅读·2023 年 2 月 6 日

--

介绍

你是否曾在测试集上获得了出色的结果,却发现你的模型在生产环境中经过一段时间后表现不佳?如果是这样,你可能正在经历模型衰退。模型衰退是指机器学习模型性能随着时间的推移逐渐下降。在本文中,我们将讨论数据漂移如何导致模型衰退,以及我们如何设置早期检测漂移的机制。

照片由Samuel Wong拍摄,来源于Unsplash

机器学习中的漂移是什么?

在机器学习中,模型漂移指的是模型训练所用数据的基础分布发生变化,从而导致模型在新数据上的性能下降。这可能发生在模型部署到现实环境中时,遇到的数据分布随时间变化。例如,训练于疫情前的数据的模型可能在 Covid19 大流行期间的数据上表现不佳,因为数据的基础分布发生了变化。

为什么追踪模型漂移很重要?

跟踪漂移很重要,因为它有助于确保机器学习模型随着时间的推移继续做出准确的预测。随着模型在实际世界中的部署,它遇到的数据分布可能会发生变化,这可能导致模型性能下降。通过跟踪漂移,我们可以检测到这种情况并采取适当的措施来适应模型,例如在新数据上重新训练模型。这有助于防止模型做出越来越不准确的预测,这在某些应用中如欺诈检测、信用评分和医疗诊断中可能产生严重后果。跟踪模型漂移对于合规和监管原因也很重要。组织可能需要维护准确的记录,并有模型性能的可审计记录。

漂移的类型

这里是可能影响您模型的不同类型的漂移。

  1. 概念漂移 是独立变量与目标变量之间关系的变化。这发生在模型试图学习的基础概念或任务随着时间发生变化时。例如,如果欺诈信用卡交易的类型随时间改变,则一个用于检测欺诈信用卡交易的模型可能会经历概念漂移。

  2. 协变量偏移 是独立变量的偏移。这发生在输入变量的分布随着时间变化时,但基础概念或任务保持不变。例如,如果在一个地理位置上训练的模型被部署到另一个具有不同输入变量分布的位置,它可能会经历协变量偏移。

  3. 先验概率偏移 是目标变量的偏移。例如,如果在一个类别平衡的数据集上训练的模型被部署到一个某一类别远比另一类别更普遍的数据集上,它可能会经历先验概率偏移。

检测漂移的方法

这里是一些检测模型衰退和漂移的常见方法。

监控模型性能

这包括计算回归模型的 MSE、RMSE 以及分类模型的 AUC ROC、准确率、精确度、召回率和 F1 分数。生产环境和测试环境之间的大偏差可能会引发潜在漂移的警报。

然而,当预测时间与获取真实情况之间存在较长时间间隔时,这种方法可能不切实际。例如,在一个银行电话营销活动中,机器学习模型预测客户购买特定产品的倾向。该活动可能持续几个月,我们只能在活动结束时得知客户是否在活动期间进行购买。如果我们仅依赖模型性能作为模型漂移的指标,我们只能在活动结束时获得漂移警报。

虽然模型性能是一个有用的指标,但它是滞后的指标。我们可以通过监控输入特征采取主动方法来检测漂移。

监控输入特征的变化

监控输入特征变化的一种简单方法是通过描述性统计。描述性统计是用来总结数据集的数字。常见的描述性统计包括均值、中位数、众数、最小值和最大值。描述性统计的变化可能会引发潜在漂移的警报。

我们还可以监控输入特征分布的变化。常用的统计测试来监控分布变化包括 Kolmogorov-Smirnov 检验、人口稳定性指数(PSI)、Wasserstein 距离,也称为地球搬运工距离、Kullback-Leibler 散度和 Jensen-Shannon 距离。

在本文中,我们将通过一个示例来讲解如何使用 Evidently,这是一款利用各种统计测试的 Python 模型监控工具,以检测机器学习模型中的漂移。

示例

在以下示例中,我们将:

  1. 训练一个模型来预测房屋转售价格。

  2. 使用 Evidently AI 的预构建报告来监控拟合的模型。

设置

  • Visual Studio Code

  • Python 3.8

  • 所需的 Python 包

evidently==0.2.2
scikit-learn==1.1.2
pandas==1.4.3

获取数据

我们将使用新加坡转售房价数据集的一个子集[1]。由住房发展委员会提供的数据集显示了转售房屋的交易情况,包括交易的年月、单元类型、位置、单元面积和转售价格。

import pandas as pd
import re

df = pd.read_csv('path/to/data/resale-flat-prices-based-on-registration-date-from-jan-2017-onwards.csv')
def convert_to_years(x):

    str_split = x.split(' ')
    years = int(str_split[0])

    if len(str_split) == 4:
        months = int(x.split(' ')[2])
        total_years = round((years*12 + months)/12,2)

    else:

        total_years = years

    return total_years
df['year_month'] = pd.to_datetime(df['month'])
df['year'] = df['year_month'].dt.year
df['month'] = df['year_month'].dt.month
df = df.drop(columns = ['block', 'street_name'])
df['remaining_lease'] = df['remaining_lease'].apply(convert_to_years)
df = df.rename(columns = {'resale_price':'target'})

我们执行以下预处理步骤:

  • 创建日期特征yearmonth

  • remaining_lease列从字符串类型转换为浮点型。

  • resale_price列重命名为target

让我们根据交易日期将数据分成 3 个集合。

  • Train:这是用于训练的集,包含 2020 年的数据。我们已知该集中的标签。

  • Test:这是保留集,我们用来获取测试结果。它包含 2021 年的数据。我们已知该集中的标签。

  • Score:这是用于生产中评分的未见记录集合。我们不应该拥有此集的标签,因此我们将删除target列以模拟真实世界情况。它包含 2022 年的数据。

# split data

df_train = df.loc[(df['year_month'] >= '2020-01-01') & (df['year_month'] < '2021-01-01')].drop(columns = ['year_month']).sample(n=10000)
df_test = df.loc[(df['year_month'] >= '2021-01-01') & (df['year_month'] < '2022-01-01')].drop(columns = ['year_month']).sample(n=5000)
df_score = df.loc[df['year_month'] >= '2022-01-01'].drop(columns = ['year_month', 'target']).sample(n=5000)
y_train = df_train['target'].copy()
X_train = df_train.drop(columns='target').copy()
y_test = df_test['target'].copy()
X_test = df_test.drop(columns='target').copy()
X_score = df_score.copy()

训练模型

from sklearn.ensemble import GradientBoostingRegressor
from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import OneHotEncoder
from sklearn.impute import SimpleImputer

categorical_features = ['town', 'flat_type', 'storey_range', 'flat_model']
categorical_transformer = Pipeline(steps=[('encoder', OneHotEncoder(handle_unknown='ignore'))])
numerical_features = ['floor_area_sqm', 'lease_commence_date', 'remaining_lease']
numerical_transformer = Pipeline(steps=[('impute', SimpleImputer())])
preprocessor = ColumnTransformer(
    transformers=[
        ('cat', categorical_transformer, categorical_features),
        ('num', numerical_transformer, numerical_features)])
gbr = GradientBoostingRegressor()
regressor = Pipeline([('processing', preprocessor), ('regr', gbr)])
regressor.fit(X_train, y_train)

预测

我们使用训练好的回归模型对测试集、评分集和训练集进行预测。请注意,此时我们只有训练集和测试集的目标列,而没有评分集。

df_test['prediction'] = regressor.predict(X_test)
df_score['prediction'] = regressor.predict(X_score)
df_train['prediction'] = regressor.predict(X_train)

预构建报告

Evidently AI 提供了各种预构建的指标和测试,称为指标和测试预设。这些是一组相关的指标或测试,以单一报告形式呈现给你。

以下是一些指标预设

  • DataQualityPreset:评估数据质量并提供描述性统计。

  • DataDriftPreset:评估各个列和数据集的数据漂移。

  • TargetDriftPreset:评估预测或目标漂移。

  • RegressionPreset:评估回归模型的质量。

  • ClassificationPreset:评估分类模型的质量。

以下是一些测试预设

  • NoTargetPerformanceTestPreset: 评估预测列中的数据漂移以及所有列的数据质量检查。

  • DataDriftTestPreset: 评估单独列和数据集中的数据漂移

  • DataQualityTestPreset: 评估数据质量并提供描述性统计

预构建指标

让我们来看看这些指标预设是如何工作的。

report = Report(metrics=[
  DataDriftPreset(drift_share=0.3),
  TargetDriftPreset()
])

report.run(reference_data=df_train, current_data=df_test)
report.save_html('evidently_metrics_report.html')

我们将训练集和测试集分别设置为参考数据集和当前数据集。请注意,我们没有选择要对列执行的统计测试。显然,Evidently 已根据输入数据的特征为我们做出了选择。了解更多有关他们如何做出这种决策的内容,请参阅这里

我们可以将 HTML 显示为 Jupyter Notebook 单元格输出,或将其保存为 HTML 文件。以下是我们在浏览器中打开 HTML 文件时的样子。

作者制作的 GIF

报告包含以下内容:

  • 检测到漂移的列的数量和比例的总结。

  • 每一列的数据分布和漂移幅度

  • 特征与目标/预测之间的相关性

结果也可以以 Json 或 Python 字典的形式输出,格式如下:

report.json()
#OR
report.as_dict()

预构建测试

我们可以以类似的方式使用测试预设。我们将训练集和评分集分别设置为参考数据集和当前数据集。

tests = TestSuite(tests=[
    NoTargetPerformanceTestPreset()
])

tests.run(reference_data=df_train.drop(columns='target'), current_data=df_score)
tests.save_html('evidently_tests_report.html')

请注意,评分集仅有预测值,而没有实际值(即目标列),因此参考数据集中的目标列已被删除。以下是结果的样子。

作者制作的 GIF

NoTargetPerformanceTestPreset提供了数据漂移、质量和完整性的简明总结。结果也可以以 Json 或 Python 字典的形式输出,格式如下:

tests.json()
#OR
tests.as_dict()

结论

总结来说,机器学习中的模型漂移指的是数据的基础分布发生变化,这可能导致性能下降。跟踪模型漂移非常重要,以确保预测的准确性以及符合合规和监管要求。漂移有不同的类型,包括概念漂移、协变量偏移和先验概率偏移。检测漂移的方法包括监控模型性能和输入特征的描述性统计,以及使用统计测试监控输入特征分布的变化。通过使用像 Evidently AI 这样的工具,我们可以主动检测并解决模型漂移问题,以确保机器学习模型的性能和可靠性。

加入 Medium 阅读更多类似的文章!

参考

[1] 包含来自 HDB 转售价格的信息,该信息于 2023 年 1 月 31 日访问自 转售公寓价格-Data.gov.sg,根据新加坡开放数据许可 1.0 版的条款提供* 新加坡开放数据许可-Data.gov.sg

如何在 3 个步骤中开发 Streamlit 数据分析 Web 应用

原文:towardsdatascience.com/how-to-develop-a-data-analytics-web-app-in-3-steps-92cd5e901c52

构建你的第一个 YouTube 分析应用的逐步指南

Destin GongTowards Data Science Destin Gong

·发表于 Towards Data Science ·8 分钟阅读·2023 年 2 月 25 日

--

图片来源:Tran Mau Tri TamUnsplash

大多数情况下,数据科学/数据分析项目最终会交付一个静态报告或仪表板,这大大降低了投入过程中的努力和思考。另一方面,Web 应用是展示你的数据分析工作的绝佳方式,之后可以进一步扩展为自服务和互动平台上的服务。然而,作为数据科学家或数据分析师,我们没有开发软件或网站的训练。在这篇文章中,我将介绍像 Streamlit 和 Plotly 这样的工具,这些工具使我们能够通过 Web 应用轻松开发和服务你的数据分析项目,步骤如下:

在 3 个步骤中开发数据分析 Web 应用(图像来自作者的 网站

  1. 提取数据并建立数据库

  2. 将数据分析过程定义为函数

  3. 构建 Web 应用界面

之后,我们将能够创建一个像这样的简单 Web 应用:

Web 应用演示(图像由作者提供)

第一步:提取数据并建立数据库

开发数据分析 Web 应用的第一步(图像由作者提供)

我们将在这里使用 YouTube 数据作为示例,因为它与我们的日常生活相关。YouTube 数据 API 允许我们获取公共 YouTube 数据,例如视频统计数据(如点赞数、观看次数)或内容详细信息(如标签、标题、评论)。要设置 YouTube API,需要注册 Google 开发者账户并设置 API 密钥。以下是一些对我开始使用 YouTube API 很有帮助的资源。

这些资源带我们了解如何创建 YouTube API 密钥和安装所需的库(例如 googleapiclient.discovery)。解决这些依赖关系后,我们使用 Python 和自己的 API 密钥设置与 API 的连接,使用以下命令:

from googleapiclient.discovery import build
youtube = build('youtube', 'v3', developerKey=<your_api_key>)

建立连接后,是时候探索你的数据科学项目可以使用的数据了。为此,请查看 YouTube 数据 API 文档,它提供了可以访问的不同类型数据的概述。

YouTube 数据 API 参考列表(截图由作者提供)

我们将使用 “Videos” 作为此项目的示例,list() 方法允许我们通过传递 part 参数和几个 filters 请求 “Video Resource”。part 参数指定要从视频资源中提取哪些组件,这里我选择了 snippet, statistics, and contentDetails。请查看这份 文档,它详细列出了你可以从 videos().list() 方法中获取的所有字段。我们还指定了以下 filter 参数来限制此请求返回的结果。

  • chart='mostPopular':获取最受欢迎的视频

  • regionCode='US':来自美国的视频

  • videoCategoryId=1:从特定视频类别获取视频(例如,1 代表电影和动画),可以在 视频类别 ID 目录中找到。

  • maxResults=20:返回最多 20 个视频

video_request = youtube.videos().list(
                part='snippet,statistics,contentDetails',
                chart='mostPopular',
                regionCode='US',
                videoCategoryId=1,
                maxResults=20
		      )
response = video_request.execute()

然后我们使用 video_request.execute() 执行请求,响应将以 JSON 格式返回,通常如下图所示。

JSON 格式的响应(图像由作者提供)

所有信息都存储在响应中的 “items” 中。然后,我们提取 ‘items’ 键,并通过标准化 JSON 格式创建数据框 video_df

video_df = json_normalize(response['items'])

结果是,我们成功将输出整理成更易于操作的结构。

video_df(图像来源:作者)

为了更进一步地使用 Python 处理 JSON,我推荐阅读文章 “如何在 Python 中最好地处理 JSON”。

第 2 步。将数据分析过程定义为函数

开发数据分析 Web 应用的第 2 步(图像来源:作者)

我们可以将多行代码语句打包成一个函数,以便它可以被迭代执行,并且在后续阶段容易与其他 Web 应用组件嵌入。

定义 extractYouTubeData()

例如,我们可以将上述数据提取过程封装到一个函数中:extractYouTubeData(youtube, categoryId),这允许我们传递一个 categoryId 变量,并输出该类别下的前 20 个热门视频作为 video_df。这样,我们可以获取用户选择的类别,然后将输入传递给该函数,获得相应的前 20 个视频。

def extractYouTubeData(youtube, categoryId):
    video_request = youtube.videos().list(
    part='snippet,statistics,contentDetails',
    chart='mostPopular',
    regionCode='US',
    videoCategoryId=categoryId,
    maxResults=20
    )
    response = video_request.execute()
    video_df = json_normalize(response['items'])
    return video_df

我们可以使用 video_df.info() 来获取该数据框中的所有字段。

video_df 中的字段(图像来源:作者)

利用这个宝贵的数据集,我们可以进行各种分析,如探索性数据分析、情感分析、主题建模等。

我想从设计一个用于对这些最热门的 YouTube 视频进行探索性数据分析的应用开始。

  • 视频时长与点赞数

  • 最常出现的标签

在未来的文章中,我将探索更多技术,如主题建模和自然语言处理,以分析视频标题和评论。因此,如果你希望阅读我在 Medium 上的更多文章,非常感谢你通过注册 Medium 会员 ☕ 来支持我。

定义 plotVideoDurationStats()

我想了解视频时长是否与这些热门视频的点赞数有相关性。为此,我们首先需要将 contentDetails.duration 从 ISO 日期时间格式转换为数值,使用 isodate.parse_duration().total_seconds()。然后,我们可以使用散点图来可视化视频时长与点赞数的关系。这是通过 Plotly 完成的,它为最终用户提供了更互动的体验。下面的代码片段返回 Plotly 图形,这将被嵌入到我们的 Web 应用中。

import isodate
import plotly.express as px

def plotVideoDurationStats(video_df):
    video_df['contentDetails.duration'] = video_df['contentDetails.duration'].astype(str)
    video_df['duration'] = video_df['contentDetails.duration'].apply(lambda x: isodate.parse_duration(x).total_seconds())
    fig = px.scatter(video_df, x="duration", y='statistics.likeCount', color_discrete_sequence=px.colors.qualitative.Safe)
    return fig

plotVideoDurationStats 生成的图像(图像来源:作者)

若要探索更多基于 Plotly 的教程,请查看下面这些博客:

## Python 中假设检验的互动指南

T 检验、ANOVA、卡方检验示例

towardsdatascience.com ## 如何使用 Plotly 进行更深入和互动的数据探索

案例研究:卡塔尔世界杯队伍的动态 EDA

towardsdatascience.com

定义 plotTopNTags()

此函数创建某一视频类别的前 N 个标签的图形。首先,我们遍历所有 snippet.tags 并将所有标签收集到标签列表中。然后我们创建描述前 N 个最常见标签计数的 tags_freq_df。最后,我们使用 px.bar() 显示图表。

def plotTopNTags(video_df, topN):
    tags = []
    for i in video_df['snippet.tags']:
        if type(i) != float:
            tags.extend(i)
    tags_df = pd.DataFrame(tags)
    tags_freq_df = tags_df.value_counts().iloc[:topN].rename_axis('tag').reset_index(name='frequency')
    fig = px.bar(tags_freq_df, x='tag', y='frequency')
    return fig

plotTopNTags() 生成的图形(作者提供的图片)

第 3 步:构建 Web 应用界面

开发数据分析 Web 应用的第 3 步(作者提供的图片)

我们将使用 Streamlit 来开发 Web 应用界面。这是我迄今为止发现的最简单的 Web 应用开发工具,它运行在 Python 之上,省去了处理 HTTP 请求、定义路由或编写 HTML 和 CSS 代码的麻烦。

运行 !pip install streamlit 将 Streamlit 安装到你的计算机上,或使用此 文档 在你首选的开发环境中安装 Streamlit。

使用 Streamlit 创建 Web 应用组件非常简单。例如,显示标题如下所示:

import streamlit as st
st.title('Trending YouTube Videos')

在这里,我们需要几个组件来构建 Web 应用。

1) 输入:一个下拉菜单供用户选择视频类别

下拉菜单(作者提供的图片)

此代码片段允许我们创建一个下拉菜单,提示“选择 YouTube 视频类别”,并提供‘电影与动画’、‘音乐’、‘体育’、‘宠物与动物’等选项。

videoCategory = st.selectbox(
    'Select YouTube Video Category',
    ('Film & Animation', 'Music', 'Sports', 'Pets & Animals')
)

2) 输入:一个滑块供用户选择标签数量

滑块(作者提供的图片)

这定义了滑块并指定了滑块范围从 0 到 20。

topN = st.slider('Select the number of tags to display',0, 20)

3) 输出:一个显示视频时长与点赞数的图形

视频时长与点赞数(作者提供的图片)

我们首先创建 videoCategoryDict 将类别名称转换为 categoryId,然后通过我们之前定义的 extractYouTubeData() 函数传递 categoryId。请查看此 页面 获取视频类别及其对应的 categoryId

然后我们调用 plotVideoDuration() 函数,并使用 st.plotly_chart() 显示 plotly 图表。

videoCategoryDict = {'Film & Animation': 1, 'Music': 10, 'Sports': 17, 'Pets & Animals': 15}
categoryId = videoCategoryDict[videoCategory]
video_df = extractYouTubeData(youtube, categoryId)
duration_fig = plotVideoDurationStats(video_df)
fig_title1 = 'Durations(seconds) vs Likes in Top ' + videoCategory + ' Videos'
st.subheader(fig_title1)
st.plotly_chart(duration_fig)

4) 输出:一个显示该视频类别中热门标签的图形

视频类别中的热门标签(作者提供的图片)

最后一个组件要求我们将用户输入的标签数量传递给函数plotTopNTags(),并通过调用该函数来创建图表。

tag_fig = plotTopNTags(video_df, topN)
fig_title2 = 'Top '+ str(topN) + ' Tags in ' + videoCategory + ' Videos'
st.subheader(fig_title2)
st.plotly_chart(tag_fig)

这些代码语句可以包含在一个 Python 文件中(例如 YoutTubeDataApp.py)。然后我们导航到命令行,使用!streamlit run YouTubeDataApp.py在网络浏览器中运行应用程序。

重点信息

对于数据分析师和数据科学家来说,构建一个网络应用程序可能看起来令人望而生畏。本文涵盖了三个步骤,帮助你开始构建你的第一个网络应用程序,并将数据分析项目扩展到自助服务平台:

  • 提取数据并构建数据库

  • 将数据分析过程定义为函数

  • 构建网络应用程序界面

更多类似资源

Destin Gong

Destin Gong

数据科学入门

查看列表9 个故事Python 中的统计测试Destin Gong

Destin Gong

EDA 和特征工程技术

查看列表9 个故事 ## 如何使用 Plotly 进行更深入和互动的数据探索

案例研究:卡塔尔世界杯队伍的动态 EDA

towardsdatascience.com

最初发表于 https://www.visual-design.net 于 2023 年 2 月 23 日。

如何使用 Folium Python 库显示 GeoJSON 文件中的数据

原文:towardsdatascience.com/how-to-display-data-from-geojson-files-using-the-folium-python-library-f7284cb2a256

创建英国大陆架石油和天然气田轮廓的互动地图

Andy McDonaldTowards Data Science Andy McDonald

·发表于 Towards Data Science ·阅读时间 5 分钟·2023 年 2 月 24 日

--

由 Aksonsat Uanthoeng 拍摄,下载自 Pexels。

Folium 是一个优秀的 Python 库,它利用 Leaflet.js 的强大功能,使得在交互式地图上可视化地理空间数据变得简单。 在我之前的 文章 中,我介绍了如何在 Folium 地图上显示单独的标记,但我们也可以使用 Folium 来显示存储在 GeoJSON 文件中的数据。

GeoJSON 是一种常用的文件格式 用于存储地理空间数据,使用 JavaScript 对象表示法。这些文件可以存储位置数据、形状、点、表面等。

在本文中,我们将学习如何显示存储在 GeoJSON 文件格式中的英国北海石油和天然气田的多边形。

本文的数据来源于 data.gov.uk,并且遵循 开放政府许可证 数据可以从以下网站查看和下载。

[## OGA 离岸油田 WGS84

我们使用 cookies 收集有关您如何使用 data.gov.uk 的信息。我们使用这些信息来使网站正常运行…

www.data.gov.uk

设置库并显示基本的 Folium 地图

安装 Folium

如果您尚未安装 Folium,可以通过 pip 进行安装:

pip install folium

或者 Anaconda:

conda install folium

一旦你安装了库,第一步是将 folium 库导入到我们的 Jupyter Notebook 中:

import folium

为了简单起见,我们将 GeoJSON 文件的位置分配给一个变量。当我们以后想调用它或者更改为另一个文件时,这样做会使事情更简单,而不需要直接修改其余代码。

field_locations = 'geodata/NSTA_Offshore_Fields_WGS84.geojson'

接下来,我们需要用一些基本参数初始化一个 folium 地图。

创建的地图将以北纬 58 度和东经 5 度为中心。我们还将zoom_start设置为 6,以便能够查看大部分英国北海。

在创建 folium 地图时,我们可以使用几个其他参数。如果你想了解更多,请查看folium.map类的帮助文档。

m = folium.Map(location=[58, 5], 
                 zoom_start=6, control_scale=True)
m

当我们使用变量m调用 folium 地图对象时,我们将看到以下地图:

以北海为中心的基础 folium 地图。图片由作者提供。

生成的地图看起来很空旷,因此下一步是添加一些形状来标识英国海上油气田。

向 Folium 地图添加 GeoJSON 数据

为了显示 GeoJSON 文件中的数据,我们需要调用folium.geojson并传入几个参数。

第一个参数是 GeoJSON 文件的位置,我们将其分配给变量field_locations。接下来是tooltip参数,这允许我们在任何形状悬停时显示信息。在这个例子中,我们将字段设置为显示油气田的名称(FIELDNAME)。

最后,为了使对象显示,我们需要在末尾添加扩展add_to(m)

folium.GeoJson(field_locations,
               tooltip=folium.GeoJsonTooltip(fields=['FIELDNAME'])
              ).add_to(m)

当我们调用 folium 地图对象m时,我们会看到以下地图:

从生成的地图中,我们可以看到所有英国北海油气田的位置。

Folium 地图展示了英国北海油气田的轮廓。图片由作者提供。

在 Folium 中应用自定义颜色到 GeoJson 形状

如果我们想为地图上的形状添加一些样式,可以通过首先创建一个用于控制形状颜色的函数来实现。

从这个数据集中,我们将使用字段类型来选择我们想要显示的颜色。任何油田将显示为绿色,气田显示为红色,凝析油田将显示为橙色。

以下代码源于这个StackOverflow 帖子

def field_type_colour(feature):
    if feature['properties']['FIELDTYPE'] == 'COND':
        return 'orange'
    elif feature['properties']['FIELDTYPE'] == 'OIL':
        return 'green'
    elif feature['properties']['FIELDTYPE'] == 'GAS':
        return 'red'

既然我们已经设置好了条件函数,我们需要在调用folium.GeoJson时添加一个新的参数,称为style_function。在这个参数中,我们传入一个包含样式属性字典的 lambda 函数。

关于 fillColor 属性,我们调用上面创建的函数,并传入我们的 feature

我们还可以将额外的样式属性传递到字典中。对于这个例子,我们将 fillOpacity 设置为 0.9,将控制形状周围线条的权重设置为 0。

如果我们不想包括上一节中的形状,我们需要重新创建 folium 地图。

m = folium.Map(location=[58, 5], 
                 zoom_start=6, control_scale=True)

folium.GeoJson(field_locations, name='geojson', 
               tooltip=folium.GeoJsonTooltip(fields=['FIELDNAME']),
               style_function= lambda feature: {'fillColor':field_type_colour(feature), 
                                                'fillOpacity':0.9, 'weight':0}
              ).add_to(m)
m

当我们调用 m 时,我们会看到以下地图。

英国北海油气田的 folium 地图,按油田类型着色。图片由作者提供。

我们现在可以看到,地图上的每个形状/油田根据储层部分包含的主要碳氢化合物类型进行着色。

向 Folium 工具提示中添加额外信息

在 GeoJSON 文件中,我们为每个形状有几个额外的属性。这些可以通过将它们包含在 fields 参数的列表中,轻松地添加到现有的工具提示中。

m = folium.Map(location=[58, 5], 
                 zoom_start=6, control_scale=True)

folium.GeoJson(field_locations, name='geojson', 
               tooltip=folium.GeoJsonTooltip(fields=['FIELDNAME', 
                                                     'PROD_DATE',
                                                     'CURR_OPER']),
               style_function= lambda feature: {'fillColor':field_type_colour(feature), 
               'fillOpacity':0.9, 'weight':0}).add_to(m)
m

当我们重新运行代码时,我们现在可以看到,当我们悬停在每个形状上时,显示了额外的属性。在这种情况下,我们可以看到油田名称、生产开始日期和油田当前的运营商。

Folium 地图显示了英国油气田的轮廓,悬停在每个区域时会出现额外的属性——图片由作者提供。

摘要

在这篇简短的文章中,我们已经看到如何轻松地在交互式 folium 地图上显示存储在 GeoJSON 文件中的数据。这使我们能够快速显示可能存储在这种文件类型中的形状和轮廓。

感谢阅读。在你离开之前,你一定要订阅我的内容,获取我的文章到你的邮箱中。 你可以在这里订阅!或者,你可以 注册我的新闻通讯 ,免费将额外内容直接发送到你的邮箱中。

其次,你可以通过注册会员来获得完整的 Medium 体验,并支持我和成千上万其他作者。每月仅需 $5,你就可以完全访问所有精彩的 Medium 文章,并有机会通过你的写作赚取收入。如果你使用 我的链接 你将直接用你的一部分费用支持我,而且不会增加额外费用。如果你这样做了,非常感谢你的支持!

如何有效地进行交叉验证

原文:towardsdatascience.com/how-to-do-cross-validation-effectively-1bbeb1d69ee8

交叉验证最佳实践指南:重新训练和嵌套

Vitor CerqueiraTowards Data Science Vitor Cerqueira

·发表于 Towards Data Science ·6 分钟阅读·2023 年 2 月 6 日

--

5 折蒙特卡罗交叉验证。图像来源于作者。

交叉验证是构建稳健机器学习模型的关键因素。然而,它往往未能发挥其全部潜力。

在这篇文章中,我们将探讨两种重要的实践,以充分利用交叉验证:重新训练和嵌套。

让我们开始吧!

什么是交叉验证?

交叉验证是一种评估模型性能的技术。

这个过程通常涉及测试几种技术,或对特定方法进行超参数优化。在这种情况下,你的目标是检查哪种替代方案最适合输入数据。

关键是选择能最大化性能的方法。这就是将被部署到生产中的模型。此外,你还希望对该模型的性能有一个可靠的估计。

交叉验证后的重新训练

交叉验证后,你应该使用所有可用数据重新训练最佳模型。图像来源于作者。

假设你进行交叉验证以选择一个模型。你使用 5 折交叉验证测试许多备选方案。然后,线性回归模型表现最佳。

那么接下来应该做什么呢?

是否应该使用所有可用数据重新训练线性回归模型?还是应该使用交叉验证过程中训练的模型?

这一部分在数据科学家中会引发一些困惑——不仅在初学者中,还有更有经验的专业人士中也会有。

交叉验证后,你应该使用所有可用数据重新训练最佳方法。以下是来自传奇书籍 《统计学习的元素 [1](括号内为我所加)

我们在交叉验证后的最终选择模型是 f(x),然后将其拟合到所有数据上。

但这一想法并不被普遍接受。

一些从业者在交叉验证期间保留了最佳模型。按照上述示例,你会保留 5 个线性回归模型。然后,在部署阶段,你会对每个预测进行预测平均。

这不是交叉验证的工作方式。

这种方法有两个问题:

  • 它使用较少的数据进行训练;

  • 它导致了成本增加,因为需要维护多个模型。

数据较少

如果不重新训练,你将不会利用所有可用的实例来创建模型。

如果没有大量数据,这可能会导致次优模型。使用所有可用实例进行训练更可能实现更好的泛化。

重新训练在时间序列中特别重要,因为最新的观察结果用于测试。如果不在这些时间点重新训练,模型可能会遗漏新出现的模式。

成本增加

有人认为,将交叉验证期间训练的 5 个模型结合起来可以带来更好的性能。

然而,理解其影响是很重要的。你不再使用简单、可解释的线性回归。

你的模型是一个集成体,其中的个别模型通过随机子采样进行训练。随机子采样是一种在集成模型中引入多样性的方法。集成模型通常比单一模型表现更好。但它们也会导致额外的成本和较低的透明度。

如果你只保留一个模型,而不是将所有模型结合起来,会怎么样?

这将解决成本增加的问题。然而,仍不清楚你应该选择哪个版本的模型。

重新训练可以跳过的两个原因。如果数据集很大或者重新训练成本太高。这两个问题通常是相关的。

重新训练——实际示例

下面是如何在交叉验证后重新训练最佳模型的一个示例:

from sklearn.datasets import make_regression
from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import GridSearchCV, KFold

# creating a dummy dataset
X, y = make_regression(n_samples=100)

# 5-fold cross-validation
cv = KFold(n_splits=5)

# optimizing the number of trees of a RF
model = RandomForestRegressor()
param_search = {'n_estimators': [10, 50, 100]}

# applying cross-validation with a grid-search
# and re-training the best model afterwards
gs = GridSearchCV(estimator=model, cv=cv, refit=True, param_grid=param_search)
gs.fit(X, y)

目标是优化随机森林中的树木数量。这是通过scikit-learn中的GridSearchCV类完成的。你可以设置参数refit=True,这样在交叉验证后,最佳模型将自动重新训练。

你可以通过从GridSearchCV获得最佳参数来显式地初始化新模型:

best_model = RandomForestRegressor(**gs.best_params_)
best_model.fit(X, y)

获得可靠的性能估计

在开发模型时,你希望达到三个目标:

  1. 从众多备选方案中选择一个模型;

  2. 训练所选模型并部署它;

  3. 获得所选模型的可靠性能估计。

交叉验证和重新训练涵盖了前两个要点,但不包括第三个。

为什么会这样?

交叉验证通常会重复进行多次,以便选择最终模型。你会测试不同的转换和超参数。因此,你最终会调整方法,直到对结果感到满意为止。

这可能导致过拟合,因为验证集的细节可能泄漏到模型中。因此,你从交叉验证中得到的性能估计可能过于乐观。你可以在参考文献[2]中阅读更多相关内容。

这也是为什么 Kaggle 比赛有两个排行榜,一个是公共的,另一个是私人的。这可以防止竞争者对测试集进行过拟合。

那么,你该如何解决这个问题呢?

你应该增加一个额外的评估步骤。在交叉验证后,你需要在一个保留的测试集上评估选择的模型。完整的工作流程如下:

  1. 将可用数据划分为训练集和测试集;

  2. 使用训练集应用交叉验证来选择模型;

  3. 使用训练数据重新训练选择的模型,并在测试集上进行评估。这为你提供了一个无偏的性能估计;

  4. 使用所有可用数据重新训练选择的模型并部署它。

这是这个过程的可视化描述:

应用交叉验证和训练数据。在交叉验证之后,再训练选择的模型并在测试集上进行评估。最后,再次训练选择的模型并部署它。图片来源:作者。

实际例子

这是使用scikit-learn的完整过程的实际例子。你可以查看评论以获取更多背景信息。

import numpy as np
import pandas as pd

from sklearn.datasets import make_regression
from sklearn.ensemble import RandomForestRegressor
from sklearn.metrics import r2_score
from sklearn.model_selection import (GridSearchCV,
                                     KFold,
                                     train_test_split)
# creating a dummy data set
X, y = make_regression(n_samples=100)

# train test split
X_train, X_test, y_train, y_test = \
   train_test_split(X, y, shuffle=False, test_size=0.2)

# cv procedure
cv = KFold(n_splits=5)

# defining the search space
# a simple optimization of the number of trees of a RF
model = RandomForestRegressor()
param_search = {'n_estimators': [10, 50, 100]}

# applying CV with a gridsearch on the training data
gs = GridSearchCV(estimator=model, 
                  cv=cv, 
                  param_grid=param_search)
## the proper way of doing model selection
gs.fit(X_train, y_train)

# re-training the best approach for testing
chosen_model = RandomForestRegressor(**gs.best_params_)
chosen_model.fit(X_train, y_train)

# inference on test set and evaluation
preds = chosen_model.predict(X_test)
## unbiased performance estimate on test set
estimated_performance = r2_score(y_test, preds)

# final model for deployment
final_model = RandomForestRegressor(**gs.best_params_)
final_model.fit(X, y)

嵌套交叉验证

上述内容是所谓嵌套交叉验证的简化版本。

在嵌套交叉验证中,你在外部交叉验证过程的每个折叠中执行完整的内部交叉验证过程。内部过程的目标是选择最佳模型。然后,外部过程为该模型提供无偏的性能估计。

嵌套交叉验证很快变得效率低下。它仅在小型数据集上是实际可行的。

大多数从业者都采用上述过程。如果你有一个大型数据集,你也可以用单次划分来替代交叉验证程序。这样,你会得到三个部分:训练、验证和测试。

关键要点

嵌套和再训练是交叉验证的两个重要方面。

交叉验证选择的模型的性能估计可能过于乐观。因此,你应该进行三重划分以获得可靠的估计。三重划分是一种嵌套交叉验证形式。

在选择模型或估计其性能后,你应使用所有可用数据重新训练模型。这样,模型在新观察数据中的表现更可能更好。

感谢阅读,下次故事见!

相关文章

  • 应用时间序列交叉验证时需要做的 4 件事

  • 时间序列的蒙特卡罗交叉验证

参考文献

[1] Hastie, Trevor, 等. 统计学习的要素:数据挖掘、推断与预测. 第 2 卷. 纽约: springer, 2009.

[2] Cawley, Gavin C., 和 Nicola LC Talbot. “模型选择中的过拟合及其对性能评估的选择偏差。” 机器学习研究期刊 11 (2010): 2079–2107.

如何使用 pytest 对 Pandas 中的数据进行数据验证

原文:towardsdatascience.com/how-to-do-data-validation-on-your-data-on-pandas-with-pytest-d5dda51ad0e4

数据

在处理的 DataFrame 上实施基本的数据验证

Byron DolonTowards Data Science Byron Dolon

·发表在Towards Data Science ·10 分钟阅读·2023 年 5 月 26 日

--

图片由我才华横溢的妹妹ohmintyartz授权使用

在大规模机器学习中处理数据是令人兴奋的,但在开始训练模型之前,有一步你不应忘记:数据验证

数据验证是指验证你收集和转换的数据是否正确且可用。你应始终确保你在机器学习模型或数据分析中使用的数据符合你的期望。

这个过程可能涉及从检查原始数据是否符合你的期望(例如,你收集的来源如何定义输出)开始,到检查你的数据处理函数是否按预期工作。

我们可以看看如何在数据处理管道中使用pytest在 Pandas 上实现基本的测试和数据验证。我们将从查看一组初始的数据处理函数开始,然后看看如何实现一些测试,以确保我们的处理函数和数据按预期行为。

你可以在自己的笔记本或 IDE 中跟随操作。你可以从 Kaggle这里下载数据集,使用 CC0 1.0 通用(CC0 1.0)公共领域献身许可证免费提供。

介绍 — 使用 Pytest 进行初步数据处理和简单数据验证

我们将在本文中使用的代码是一组使用 Pandas 读取和处理数据的 Python 函数。它包括一个函数来分块读取原始数据,然后是几个对原始数据进行转换的函数。

# data_processing.py
import pandas as pd
from pandas import DataFrame

def read_raw_data(file_path: str, chunk_size: int = 1000) -> DataFrame:
    csv_reader = pd.read_csv(file_path, chunksize=chunk_size)
    processed_chunks = []

    for chunk in csv_reader:
        chunk = chunk.loc[chunk["Order ID"] != "Order ID"].dropna()
        processed_chunks.append(chunk)

    return pd.concat(processed_chunks, axis=0)

def split_purchase_address(df_to_process: DataFrame) -> DataFrame:
    df_address_split = df_to_process["Purchase Address"].str.split(
        ",", n=3, expand=True
    )
    df_address_split.columns = ["Street Name", "City", "State and Postal Code"]

    df_state_postal_split = (
        df_address_split["State and Postal Code"]
        .str.strip()
        .str.split(" ", n=2, expand=True)
    )
    df_state_postal_split.columns = ["State Code", "Postal Code"]

    return pd.concat([df_to_process, df_address_split, df_state_postal_split], axis=1)

def extract_product_pack_information(df_to_process: DataFrame) -> DataFrame:
    df_to_process["Pack Information"] = (
        df_to_process["Product"].str.extract(r".*\((.*)\).*").fillna("Not Pack")
    )

    return df_to_process

def one_hot_encode_product_column(df_to_process: DataFrame) -> DataFrame:
    return pd.get_dummies(df_to_process, columns=["Product"])

def process_raw_data(file_path: str, chunk_size: int) -> DataFrame:
    df = read_raw_data(file_path=file_path, chunk_size=chunk_size)

    return (
        df.pipe(split_purchase_address)
        .pipe(extract_product_pack_information)
        .pipe(one_hot_encode_product_column)
    )

接下来,我们可以开始实现我们的第一个数据验证测试。如果你打算在笔记本或 IDE 中跟随,应该在新文件(或笔记本中的另一个单元格)中导入以下内容:

import pandas as pd
import numpy as np
import pytest
from pandas import DataFrame
from data_processing import (
    read_raw_data,
    split_purchase_address,
    extract_product_pack_information,
    one_hot_encode_product_column,
)
from pandas.testing import assert_series_equal, assert_index_equal

你可以阅读更多关于如何实际运行 pytest(文件命名约定和如何发现测试这里),但在我们的案例中,你只需创建一个名为test_data_processing.py的新文件,并在你的 IDE 中添加到文件后,你可以运行pytest,可选地加上“--verbose”。

Pytest 简介和简单的数据验证检查

Pytest 是一个 Python 中的测试框架,它使你能够轻松地为你的数据管道编写测试。你可以主要使用 assert 语句,它本质上检查在assert后面放置的条件是否为 True 或 False。如果条件为 False,它将引发异常AssertionError(并且在与 pytest 一起使用时会导致测试失败)。

所以首先,让我们测试一些简单的东西。我们要做的只是检查我们其中一个函数(第一个读取原始数据的函数)的输出是否返回一个 DataFrame。

顺便提一下,你会注意到在原始函数中,我们写了箭头->语法来为函数添加类型提示,我们说函数应该返回一个 DataFrame。这意味着如果你在函数中写了返回其他类型的内容,你的 IDE 会标记为返回无效输出(但这不会从技术上破坏你的代码或阻止它运行)。

为了实际检查函数是否返回一个 DataFrame,我们将实现一个函数来测试read_raw_data函数,并将其命名为test_read_raw_data

def test_read_raw_data():
    """Testing output of raw table read in is DataFrame"""
    test_df = read_raw_data(file_path="Updated_sales.csv", chunk_size=1000)
    assert isinstance(test_df, DataFrame)  # checking if it's a DataFrame

在这个函数中,我们添加了一行文档字符串来解释我们的测试函数只是检查输出是否为一个 DataFrame。然后,我们将现有的read_raw_data函数的输出分配给一个变量,并使用isinstance来返回 True 或 False,如果指定的对象是你放入的类型。在这种情况下,我们检查test_df是否是一个DataFrame

我们可以类似地对剩余的函数进行处理,这些函数仅接受 DataFrame 作为输入,并且预期返回一个 DataFrame 作为输出。实现可能如下所示:

def test_pipe_functions_output_df():
    """Testing output of raw table read in is DataFrame"""
    test_df = read_raw_data(file_path="Updated_sales.csv", chunk_size=1000)
    all_pipe_functions = [
        split_purchase_address,
        extract_product_pack_information,
        one_hot_encode_product_column,
    ]
    for function in all_pipe_functions:
        assert isinstance(function(test_df), DataFrame)

注意,你还可以在 for 循环中使用assert语句,因此我们只需遍历每个函数,传入一个 DataFrame 作为输入,并检查输出是否也是 DataFrame。

在 pytest 中实现 fixture 以提高测试效率

你可以看到,上面我们不得不在两个不同的测试函数中写完全相同的行:

test_df = read_raw_data(file_path="Updated_sales.csv", chunk_size=1000)

这是因为对于两个测试函数,我们都需要一个 DataFrame 作为输入来检查我们的数据处理函数的输出是否结果为一个 DataFrame。因此,为了避免在所有测试函数中复制相同的代码,你可以使用 fixtures,这让你编写一些pytest 允许你在不同测试中重用的代码。 这样做的方式如下:

@pytest.fixture
def test_df() -> DataFrame:
    return read_raw_data(file_path="Updated_sales.csv", chunk_size=1000)

def test_read_raw_data(test_df):
    """Testing output of raw table read in is DataFrame"""
    assert isinstance(test_df, DataFrame)  # checking if it's a DataFrame

def test_pipe_functions_output_df(test_df):
    """Testing output of raw table read in is DataFrame"""
    all_pipe_functions = [
        split_purchase_address,
        extract_product_pack_information,
        one_hot_encode_product_column,
    ]
    for function in all_pipe_functions:
        assert isinstance(function(test_df), DataFrame)

我们这次在函数中定义test_df,该函数返回原始 DataFrame。然后,在我们的测试函数中,我们只需将test_df作为参数包含在内,就可以像之前一样使用它。

测试一个函数是否转换了 DataFrame

接下来,我们将检查我们的split_purchase_address函数,该函数实际上输出的是与输入相同的 DataFrame,但增加了额外的地址列。我们的测试函数将如下所示:

def test_split_purchase_address(test_df):
    """Testing multiple columns in output and rows unchanged"""
    split_purchase_address_df = split_purchase_address(test_df)
    assert len(split_purchase_address_df.columns) > len(test_df.columns)
    assert split_purchase_address_df.index.__len__() == test_df.index.__len__()
    assert_index_equal(split_purchase_address_df.index, test_df.index)  # using the Pandas testing

在这里,我们将检查两个主要方面:

  1. 输出的 DataFrame 是否比原始 DataFrame 有更多列?

  2. 输出的 DataFrame 是否具有与原始 DataFrame 不同的索引?

首先,我们运行split_purchase_address函数,将test_df作为输入传递,并将结果分配给一个新变量。这会给我们原始函数的输出,然后我们可以对其进行测试。

要实际进行测试,我们可以检查输出的 DataFrame 中是否存在特定列,但一种更简单(不一定更好)的方法是仅使用assert语句检查输出 DataFrame 是否比原始 DataFrame 有更多列。类似地,我们可以使用assert语句检查每个 DataFrame 的索引长度是否相同。

你还可以查看Pandas 测试文档以了解一些内置的测试函数,但实际上只有少数几个函数用于检查 DataFrame、索引或 Series 的两个对象是否相等。我们使用assert_index_equal函数来完成与index.__len__()相同的操作。

测试 DataFrame 在输出中是否具有特定列

如前所述,我们还可以检查 DataFrame 是否包含特定列。接下来我们将讨论extract_product_pack_information函数,该函数应始终输出带有额外名为“Pack Information”列的原始 DataFrame。我们的测试函数将如下所示:

def test_extract_product_pack_information(test_df):
    """Test specific output column in new DataFrame"""
    product_pack_df = extract_product_pack_information(test_df)
    assert "Pack Information" in product_pack_df.columns

在这里,我们所做的就是在原始函数的输出上再次调用columns,但这次特别检查“Pack Information”列是否在列列表中。如果由于某种原因我们修改了原始的extract_product_pack_information函数以返回额外的列或重命名了输出列,则此测试将失败。这将是一个很好的提醒,检查我们最终用于什么(如机器学习模型)是否也考虑了这一点。

然后我们可以做两件事:

  1. 在我们的代码管道下游进行更改(例如引用“Pack Information”列的代码);

  2. 编辑我们的测试以反映处理函数中的更改。

测试 DataFrame 的列是否具有正确的数据类型

我们还应检查由函数返回的 DataFrame 是否具有我们所需的数据类型。例如,如果我们在数值列上进行计算,我们应检查这些列是以 int 还是 float 类型返回,这取决于我们的需要。

让我们测试一下 one_hot_encode_product_column 函数的数据类型,其中我们在原始 DataFrame 的一个分类列上进行常见的特征工程步骤。我们期望所有列的数据类型都是 uint8get_dummies 函数在 Pandas 中默认返回的数据类型),所以我们可以这样进行测试。

def test_one_hot_encode_product_column(test_df):
    """Testing if column types are correct"""
    encoded_df = one_hot_encode_product_column(test_df)
    encoded_columns = [column for column in encoded_df.columns if "_" in column]
    for encoded_column in encoded_columns:
        assert encoded_df[encoded_column].dtype == np.dtype("uint8")

get_dummies 函数的输出还会返回带有下划线的列(当然,这可以通过检查实际列名来更好地完成——就像在之前的测试函数中我们检查特定列一样)。

在这里,我们所做的只是通过循环检查所有目标列是否都是 np.dtype("uint8") 数据类型。我之前在笔记本中通过检查其中一列的 column.dtype 来完成了这项检查。

对最终输出进行额外的数据验证

除了测试你拥有的构成数据处理和转换管道的单个函数外,另一个好习惯是测试管道的最终输出。

为此,我们将在测试中模拟运行整个管道,然后检查结果 DataFrame。

def test_process_raw_data(test_df):
    """Testing the final output DataFrame as a final sanity check"""
    processed_df = (
        test_df.pipe(split_purchase_address)
        .pipe(extract_product_pack_information)
        .pipe(one_hot_encode_product_column)
    )

    # check if all original columns are still in DataFrame
    for column in test_df.columns:
        if column not in processed_df.columns:
            raise AssertionError(f"COLUMN -- {column} -- not in final DataFrame")

    assert all(
        element in list(test_df.columns) for element in list(processed_df.columns)
    )

    # check if final DataFrame doesn't have duplicates
    assert assert_series_equal(
        processed_df["Order ID"].drop_duplicates(), test_df["Order ID"]
    )

我们的最终 test_process_raw_data 将检查两个最终点:

  1. 检查最终 DataFrame 是否仍然包含原始列——这并不总是一个要求,但可能你希望所有原始数据仍然可用(而不是转换)在输出中。做到这一点很简单——我们只需要检查 test_df 中的列是否仍然存在于 processed_df 中。最后,如果列不存在,我们可以这次引发 AssertionError(类似于使用 assert 语句)。这是一个很好的示例,展示了如何在需要时在测试中输出特定的消息。

  2. 检查最终 DataFrame 是否没有重复项——有很多不同的方法可以做到这一点——在这种情况下,我们只使用“订单 ID”(我们期望它像索引一样)和 assert_series_equal 来检查输出 DataFrame 是否没有生成重复行。

检查 pytest 输出

要快速查看运行 pytest 的情况,只需在 IDE 中运行:

pytest --verbose

Pytest 将检查包含所有测试函数的新测试文件并运行它们!这是对数据处理管道进行一系列数据验证和测试检查的简单实现。如果你运行上述代码,输出应该类似于:

pytest 输出 1 —— 作者提供的图片

pytest 输出 2 —— 作者提供的图片

pytest 3 的输出— 作者图片

你可以看到我们最终的测试失败了,特别是在测试中我们检查初始 DataFrame 的所有列是否存在于最终 DataFrame 中。此外,我们之前定义的AssertionError中的自定义错误消息是否正确显示——我们原始 DataFrame 中的“Product”列没有出现在最终的 DataFrame 中(请查看我们的初始数据处理函数,看看能否找到原因)。

这个测试还有很大的改进空间——我们现在只有一个非常简单的实现,包含基本的测试和数据验证案例。对于更复杂的管道,你可能需要更多的测试,不仅是对单个数据处理函数的测试,还包括对原始和最终输出 DataFrame 的测试,以确保你使用的数据是可以信赖的。

感谢你花时间阅读这篇文章!如果你喜欢我的内容,我希望你能通过下面的推荐链接注册 Medium。这样我可以获得你每月订阅的一部分,同时你也能访问 Medium 会员专属的某些功能。如果你已经在关注我,非常感谢你的支持!

[## 使用我的推荐链接加入 Medium — Byron Dolon

作为 Medium 会员,你的一部分会费将用于支持你阅读的作者,同时你将获得对每篇故事的完全访问权限……

byrondolon.medium.com](https://byrondolon.medium.com/membership?source=post_page-----d5dda51ad0e4--------------------------------)

More by me: - 5 个实用技巧,助你成为数据分析师 - 掌握电商数据分析 - 在 Pandas DataFrame 中检查子字符串 - 学习 Python 的 7 个最佳 Github 资源库 - 用 Pandas 理解数据的 5(又半)行代码

如何使用 Python、NLTK 和一些简单的统计进行语言检测

原文:towardsdatascience.com/how-to-do-language-detection-using-python-nltk-and-some-easy-statistics-6cec9a02148

一个关于你每天使用的技术的实用介绍。

Katherine MunroTowards Data Science Katherine Munro

·发表于Towards Data Science ·12 分钟阅读·2023 年 1 月 11 日

--

图片由Etienne Girardet提供,来源于Unsplash

有没有想过 Google Translate 的“检测语言”功能是如何工作的?当然你没想过,你有更重要的事情要做。但我去寻找答案时却没有找到(尽管我确实写了一本关于自然语言处理(NLP)的书)。这是 Google 的秘密调料。所以今天,我将向你展示一种超级简单的方法,使用一个被低估的 NLP 工具和一些非常简单的数学,你自己进行语言检测。你很快就能将它添加到你的 GitHub 作品集中。

什么是语言检测,为什么要使用它?

语言检测仅仅意味着识别一段输入文本的语言。这是自然语言处理中的许多任务的第一步,包括许多你每天使用的任务:

  • 拼写和语法修正(例如 MS Word、Google Docs 等)

  • 下一个词预测(你的手机一直在做这个!)

  • 机器翻译(例如 Google Translate 的“检测语言”选项)

来源:作者

我们如何检测语言?

进行语言识别的一种简单方法是:为不同的语言建立词汇表(词汇列表),然后计算每种语言的词汇在文本中出现的次数。因此,如果测试文本包含五个日语单词和两个英语单词,我们可能会得出它是日语的结论。我们甚至可以专注于所谓的“停用词”,这些词在语言中出现频率很高,虽然往往没有什么实际意义,但对语法非常重要,例如“the”,“a”和“and”。

问题是,许多词汇在多种语言中都有出现,即使它们有不同的含义。例如,‘gift’在英语中表示‘礼物’,但在德语中表示‘毒药’。所以短语‘Das Gift’可能会带来问题,特别是如果有拼写错误的话。假设我们想说‘毒药很强’:Das Gift ist stark。如果我们忘记了‘ist’中的‘t’,我们就会得到‘Das Gift is stark’。由于‘stark’在两种语言中都出现,我们现在确实遇到问题了。而且,集中关注停用词可能会使问题更严重。例如,法语和德语都经常使用‘des’和‘du’,所以如果我们只关注这些,我们就会陷入困境。

有趣的事实:在自然语言处理领域,总是会有拼写错误。

另一种方法是集中于字母的分布,而不是单词。例如,与英语相比,德语使用变音符号(ä、ö、ü),法语使用很多特殊字符(ç、â/ê/î/ô/û、à/è/ì/ò/ù、ë/ï/ü)。2 个和 3 个字母的组合,称为 bi-和 trigrams,效果更好。这是因为不同的语言有一些在其他语言中不存在的字母组合。

在 Python 中构建语言检测模型

我们的语言检测方法将使用 uni-、bi-和 tri-grams:即,单个字母以及两个和三个字母的组合。这些组合的通用术语是‘n-grams’。我们将通过计算它们的 n-gram 频率来创建不同语言的统计模型。然后我们将这些与测试文本中的 n-gram 频率进行比较。n-gram 频率与测试句子最匹配的语言将是我们的赢家。

这种方法基于[1]。

可视化 N-Grams

让我们开始可视化一些不同长度的 n-gram:

text = "This is a test text"
n = 3 # 1 = Unigram, 2 = bigram, 3 = trigram
text_len = len(text)
num_ngrams = text_len - n + 1 # How many ngrams of length n will fit in this text
print(f"The text is {text_len} characters long and will fit {num_ngrams} n-grams of length {n}.")

for p in range(num_ngrams) :
            print(f"{p}: {text[p:p+n]}")

构建一个 N-Gram 提取器

让我们定义一个函数extract_xgrams()。它将接收一个文本和一个数字列表n_vals,并从文本中提取这些长度的 n-grams:

import typing

def extract_xgrams(text: str, n_vals: typing.List[int]) -> typing.List[str]:
    """
    Extract a list of n-grams of different sizes from a text.
    Params:
        text: the test from which to extract ngrams
        n_vals: the sizes of n-grams to extract
        (e.g. [1, 2, 3] will produce uni-, bi- and tri-grams)
    """
    xgrams = []

    for n in n_vals:
        # if n > len(text) then no ngrams will fit, and we would return an empty list
        if n < len(text):
            for i in range(len(text) - n + 1) :
                ng = text[i:i+n]
                xgrams.append(ng)

    return xgrams

text = "I was taught that the way of progress was neither swift nor easy.".lower()
# Quote from Marie Curie, the first woman to win a Nobel Prize, the only woman to win it twice, and the only human to win it in two different sciences.

# Extract all ngrams of size 1 to 3.
xgrams = extract_xgrams(text, n_vals=range(1,4))

print(xgrams)

注意,我们将测试文本转为小写。这将减少返回的 n-gram 数量,同时不会丢失关于语言本身的信息(想想看:如果我说‘i went to new york’,即使没有大写,你仍然能理解我)。

定义一个构建语言模型的函数

我们的build_model()函数使用了 collections.Counter。Counter 接收一个列表,计算列表中每个项目的出现次数,并返回一个包含每个项目及其频率的字典。

因此,对于任何语言,我们可以通过创建一个 n-gram 字典及其在该语言中出现的概率来对其建模。n-gram 的概率是多少?它仅仅是其频率,除以提取的 n-gram 总数。让我们运行代码并打印语言模型,排序使得最频繁的 n-gram 排在前面:

def build_model(text: str, n_vals: typing.List[int]) -> typing.Dict[str, int]:
    """
    Build a simple model of probabilities of xgrams of various lengths in a text
    Parms:
        text: the text from which to extract the n_grams
        n_vals: a list of n_gram sizes to extract
    Returns:
        A dictionary of ngrams and their probabilities given the input text
    """
    model = collections.Counter(extract_xgrams(text, n_vals))  
    num_ngrams = sum(model.values())

    for ng in model:
        model[ng] = model[ng] / num_ngrams

    return model

test_model = build_model(text, n_vals=range(1,4))
print({k: v for k, v in sorted(test_model.items(), key=lambda item: item[1], reverse=True)})

安装 NLTK 并下载我们的文本数据

自然语言工具包(Natural Language ToolKit)是自然语言处理的一个隐藏宝石。它包含用于文本处理的类和方法,以及大量的文本语料库(准备好的文本数据集合)供你练习。如果你还没有安装 NLTK,可以使用你喜欢的方法安装,例如 pip install nltk (查看指南)

为了测试我们的语言识别器,我们将使用《世界人权宣言》(UDHR),它在 NLTK 中包含 300 种语言。实际上,这样的数据集太小,文本也太干净(联合国大概进行了校对,而且他们可能不使用标签和表情符号,那些扫兴的东西)。但这个数据集足以演示我们在这里尝试做的概念。此外,它将使你了解如何使用 NLTK:

import nltk
nltk.download('udhr') # udhr = Universal Declaration of Human Rights

# Now import corpus and print number of files and the fileids (as these reveal the languages)
from nltk.corpus import udhr 
print(f"There are {len(udhr.fileids())} files with the following ids: {udhr.fileids()}")

为了简化,我将选择几个语言进行处理。它们都使用类似的字符,因此对我们的检测器来说会是一个更艰难的测试。可以随意添加更多语言:代码注释将告诉你如何操作:

languages = ['english', 'german', 'dutch', 'french', 'italian', 'spanish']
language_ids = ['English-Latin1', 'German_Deutsch-Latin1', 'Dutch_Nederlands-Latin1', 'French_Francais-Latin1', 'Italian_Italiano-Latin1', 'Spanish_Espanol-Latin1']# I chose the above sample of languages as they all use similar characters. 

### Optional: If you want to add more languages:

# First use this function to find the language file id
def retrieve_fileid_by_first_letter(fileids, letter):
    return [id for id in fileids if id.lower().startswith(letter.lower())]

# Example usage
print(f"Fileids beginning with 'R': {retrieve_fileid_by_first_letter(udhr.fileids(), letter='R')}")

# Then copy-paste the language name and language id into the relevant list:
languages += []
language_ids += []

命令 udhr.raw(fileids) 返回指定 fileid 的完整文本。我们将使用它来建立一个包含每种语言名称及其文本的字典,然后从这个字典中构建每种语言的模型:

raw_texts = {language: udhr.raw(language_id) for language, language_id in zip(languages, language_ids)}
print(raw_texts['english'][:1000]) # Just print the first 1000 characters

# Build a model of each language
models = {language: build_model(text=raw_texts[language], n_vals=range(1,4)) for language in languages}
print(models['german'])

确定给定文本的语言

我们现在可以使用测试文本,将其 n-gram 频率与各种语言模型的频率进行比较。目的是查看哪种语言的频率与我们的测试文本最接近。

我们通过计算 余弦相似度,如下面的公式所示:

余弦相似度公式

它看起来很吓人,但我们不会深入数学。基本上,余弦相似度用于比较两个数值向量。结果范围从−1,表示完全相反,到 1,表示完全相同。我们的 calculate_cosine() 公式实现了这些数学计算:

import math

def calculate_cosine(a: typing.Dict[str, float], b: typing.Dict[str, float]) -> float:
    """
    Calculate the cosine between two numeric vectors
    Params:
        a, b: two dictionaries containing items and their corresponding numeric values
        (e.g. ngrams and their corresponding probabilities)
    """
    numerator = sum([a[k]*b[k] for k in a if k in b])
    denominator = (math.sqrt(sum([a[k]**2 for k in a])) * math.sqrt(sum([b[k]**2 for k in b])))
    return numerator / denominator

现在是时候构建一个 identify_language() 函数了。这个函数将接受一个测试文本,使用不同大小的 n-gram(由 n_vals 指定)为其构建一个模型,并将其与语言模型字典进行比较。输出将是与测试文本最相似的语言名称。

出于演示目的,我在函数中添加了一条打印语句以显示每种语言与测试文本的相似度。你可以在你对余弦值有了感觉后删除它。

对原始测试文本运行此函数时,最高的相似度正确地出现在英语中:

def identify_language(
    text: str,
    language_models: typing.Dict[str, typing.Dict[str, float]],
    n_vals: typing.List[int]
    ) -> str:
    """
    Given a text and a dictionary of language models, return the language model 
    whose ngram probabilities best match those of the test text
    Params:
        text: the text whose language we want to identify
        language_models: a Dict of Dicts, where each key is a language name and 
        each value is a dictionary of ngram: probability pairs
        n_vals: a list of n_gram sizes to extract to build a model of the test 
        text; ideally reflect the n_gram sizes used in 'language_models'
    """
    text_model = build_model(text, n_vals)
    language = ""
    max_c = 0
    for m in language_models:
        c = calculate_cosine(language_models[m], text_model)
        # The following line is just for demonstration, and can be deleted
        print(f'Language: {m}; similarity with test text: {c}')
        if c > max_c:
            max_c = c
            language = m
    return language

print(f"Test text: {text}")
print(f"Identified language: {identify_language(text, models, n_vals=range(1,4))}")

# Prints
# Test text: i was taught that the way of progress was neither swift nor easy.
# Language: english; similarity with test text: 0.7812347488239613
# Language: german; similarity with test text: 0.6638235631734796
# Language: dutch; similarity with test text: 0.6495872103674768
# Language: french; similarity with test text: 0.7073331083503462
# Language: italian; similarity with test text: 0.6635204671187273
# Language: spanish; similarity with test text: 0.6811923819801172
# Identified language: english

在你删除那一行打印语句之前,看看当我们用一个截然不同的文本测试这个函数时会发生什么;相似度值通常会降低:

# An example text in Slovenian
tricky_text = "učili so me, da pot napredka ni ne hitra ne lahka."
print(f"Identified language: {identify_language(tricky_text, models, n_vals=range(1,4))}")

# Prints
# Language: english; similarity with test text: 0.7287873650203188
# Language: german; similarity with test text: 0.6721847143945305
# Language: dutch; similarity with test text: 0.6794130641102911
# Language: french; similarity with test text: 0.7395592659566902
# Language: italian; similarity with test text: 0.7673665450525412
# Language: spanish; similarity with test text: 0.7588017776235897
# Identified language: italian

测试我们的语言检测器在不同语言上的效果

让我们看看荷兰语、法语和西班牙语文本的效果:

t = "mij werd geleerd dat de weg van vooruitgang noch snel noch gemakkelijk is."  
print(identify_language(t, models, n_vals=range(1,4)))

t = "on m'a appris que la voie du progrès n'était ni rapide ni facile."  
print(identify_language(t, models, n_vals=range(1,4)))

t = "me enseñaron que el camino hacia el progreso no es ni rápido ni fácil."
print(identify_language(t, models, n_vals=range(1,4)))

结果是正确的,除了第二个例子输出的是意大利语而不是法语。天哪!

改进模型

显然,我们的模型并不完美,但有很多方法可以改进它们:

使用更大、更具代表性的数据: 我之前承认我们的训练文本太短且过于干净,无法真实反映实际语言识别情况。事实上,它们只是《宣言》的样本,每个文本被截断到大约 1000 个字符。你可以通过探索每种语言中文本的单词和字符数量来查看这一点:

 from nltk.tokenize import word_tokenize  # A function from nltk for splitting strings into individual words
nltk.download('punkt') # Required for word_tokenize to function

print("Number of characters and words per text per language:")
for language in raw_texts.keys():
    print(f"\n{language}: {len(raw_texts[language])} characters, {len(nltk.word_tokenize(raw_texts[language]))} words")

训练文本足够让你了解 NLTK 和这种简单的语言检测方法,但为了提高我们的泛化能力,我们需要使用更长、更具多样性的真实世界文本数据:错别字、标签、表情符号等等。

我向 ChatGPT 询问了“Twitter 语言风格”的《世界人权宣言》。即便这是相当干净和清晰的,它也可能变得更糟。来源:作者提供的“ChatGPT”对话截图,来自 OpenAI。

为什么我们需要更长、更具多样性的文本?很简单:这是捕捉每种语言及其与其他语言不同之处的唯一方法。

以“gnome”这个词为例。除非将接触园艺侏儒视为普世人权,否则三字母组‘ gn’(空格,g,n)可能不在我们的英语样本数据中。你可能会觉得没关系,因为没有很多英语单词以‘gn’开头。(虽然有一些,但很少用)。问题是,如果这是其他语言中的常见模式呢?实际上,确实有许多这样的德语常见词,但其中没有一个出现在《世界人权宣言》中(我查过了)。因此,如果我们看到含有三字母组‘ gn’的测试文本,它不会对我们总结的 x-gram 概率产生贡献,无论是哪种语言。这意味着它无法帮助我们区分它们。

添加更多特征: 我们本可以添加额外长度的字符 x-grams,例如四字母组(quadgrams)。基于单词的 x-grams 也可能有帮助。这两种情况的好处与使用更长文本相同:更多特征有助于捕捉语言之间的区别。例如,尽管我讨厌果酱,但我不太可能说“die Marmelade!”。但一些德国人每天早餐时都会这么说(‘die’只是‘the’的一种变体)。因此,使用单词 x-grams 可能捕捉到这种差异。

不过,单词 x-grams 有一些问题。大多数语言的单词数量远远超过其字母表中的字符数量,因此仅仅添加单词二元组将会爆炸性地增加我们语言模型中的项数。三元组及更大的项只会使问题变得更严重。更大的模型会使整个过程变慢,而且收效甚微:这些 x-gram 单词组合中的大多数几乎不会出现,因此它们甚至不会在测试时对区分语言贡献多少。

一个更好的方法是使用停用词,因为每种语言都有自己频繁出现的停用词,这些停用词是很好的指示词。我之前提到过,单独使用停用词来建模语言是有风险的,因为它们可能出现在多种语言中。但将它们作为我们字符 x-grams 的附加特征,或作为单词 x-grams 的一部分来使用,可以解决这个问题。

同样,我们可以在每种语言中添加前 1000 或 10,000 个单词(或在单词 x-grams 中使用它们)。其背后的理论是,单词往往遵循‘Zipf 定律’,即最常见的单词出现的频率大约是第二常见单词的两倍,第三常见单词的三倍,以此类推。因此,通过仅取前 n 个单词,你可以捕捉到输入数据和 —— 关键是 —— 测试数据中大多数单词的概率。这些概率将是我们语言检测决策的依据。

使用机器学习: 你不能谈论‘特征’而不考虑机器学习。我们可以尝试许多算法,包括一些令人惊讶的简单但有效的选项,如朴素贝叶斯。

理解这些算法需要整篇新博客,但好奇的读者可以在这里阅读关于语言检测的朴素贝叶斯分类器。

添加更多语言: 我只用了几种语言,但世界上有成千上万种语言,它们都值得关注。所以这是给你的挑战:添加更多语言,看看你能做到什么(我甚至已经给了你代码)。

将这一概念应用于其他 NLP 任务

本文中涉及的概念可以很容易地应用于其他挑战,而 NLTK 内置的语料库可以帮助你实现。因此,请关注我的后续帖子,我们将使用 —— 你猜对了 —— Python、NLTK 和那些非常简单的统计学来讨论文档分类和说话人识别。

感谢阅读!

首先,感谢教程的创作者,它们启发了这篇文章。

如果这篇文章对你有所帮助 —— 太棒了!—— 请订阅获取更多关于自然语言处理和其他数据科学基础的内容。你还可以在Twitter上与我联系(我会发布大量有趣的内容,涉及 AI、技术、伦理等)。

[1] Cavnar, W.B., Trenkle, J.M.:基于 N-gram 的文本分类 (1994),SDAIR-94 文档分析与信息检索第三届年会论文集。

posted @ 2024-10-12 19:52  绝不原创的飞龙  阅读(300)  评论(0)    收藏  举报