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

原文:TowardsDataScience

协议:CC BY-NC-SA 4.0

Falcon 180B:它能在你的计算机上运行吗?

原文:towardsdatascience.com/falcon-180b-can-it-run-on-your-computer-c3f3fb1611a9

是的,如果你有足够的 CPU 内存

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

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

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

图片由作者制作,插图来源于 Pixabay (1,2)

2023 年 5 月,阿布扎比技术创新研究所(TII)发布了两个预训练的 LLM:Falcon-7B 和 Falcon-40B 及其聊天版本。这两个模型表现非常出色,并在 OpenLLM 排行榜 上排名第一。

TII 发布的第三个模型刚刚加入了 Falcon 家族:Falcon 180B,一个具有 1800 亿参数的模型。它比 Llama 2 70B 多了 2.5 倍的参数,比 Falcon-40B 多了 4.5 倍。

以下是关于 Falcon 180B 的一些事实(来源:Falcon 180B 模型卡):

  • 经过 3.5 万亿个标记的预训练 (RefinedWeb)

  • 以 Apache 2.0 许可证分发

  • 大小为 360 GB

  • OpenLLM 排行榜 上排名第一(截至 2023 年 9 月 11 日):

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

OpenLLM 排行榜截图(2023 年 9 月 11 日) — 作者提供的图片

还有一个聊天版本。模型可以在 Hugging Face hub 上获取:

Falcon 180B 完全免费且技术先进。但它也是一个庞大的模型。

它能在你的计算机上运行吗?

除非你的计算机准备好进行非常高强度的计算,否则无法开箱即用运行 Falcon 180B。你需要升级计算机并使用该模型的量化版本。

在这篇文章中,我解释了如何在消费级硬件上运行 Falcon-180B。我们将看到在现代计算机上运行一个 1800 亿参数的模型是相对负担得起的。我还讨论了几种有助于减少硬件要求的技术。

在你的计算机上加载 Falcon 180B:你需要什么?

首先你需要知道的是,Falcon 180B 具有 1800 亿个参数,存储为 bfloat16。一个 (b)float16 参数在内存中占用 2 字节。

当你加载一个模型时,标准的 Pytorch 流程如下:

  1. 一个空模型被创建:180B 参数 * 2 字节 = 360 GB

  2. 将权重加载到内存中:180B 参数 * 2 字节 = 360 GB

  3. 将第 2 步加载的权重加载到第 1 步创建的空模型中。

  4. 将第 3 步获得的模型移动到用于推理的设备上,例如 GPU。

第 1 步和第 2 步是消耗内存的步骤。总的来说,你需要 720 GB 的可用内存。这可以是 CPU RAM,但为了快速推理,你可能想使用 GPU,例如 9 个带有 80 GB VRAM 的 A100。

无论是 CPU RAM 还是 VRAM,这都是大量的内存。幸运的是,这些要求可以很容易地减少。

在 Hugging Face Hub 上,Falcon 180B 采用 safetensors 格式分发。这个格式相较于标准的 Pytorch 格式有几个优势。它(几乎)没有复制,因此模型直接加载到第 1 步创建的空模型中。这节省了大量内存。

关于 safetensors

safetensors 节省内存,但它还使模型运行更安全,因为在这种格式中无法存储任意代码。safetensors 模型的加载速度也更快。当你从 hub 下载模型时,使用这种格式代替“.bin”格式,可以实现更快、更安全且节省内存的加载。

尽管看起来我们跳过了第 2 步,但仍然会有一些内存开销需要预期。TII 在模型卡上写道,400 GB 的内存是可行的。这仍然很多,但比使用标准的 Pytorch 格式少 220 GB。

我们需要一个具有 400 GB 存储空间的设备,例如,5 个带有 80 GB VRAM 的 A100 GPU。我们距离“消费级”配置还有很大差距。

将 Falcon 180B 分割到多个内存设备上。

你可能没有一个 400 GB 的单一内存设备,但如果你结合所有以下设备的内存,你的计算机可能有超过 400 GB 的内存:

  • GPU 的 VRAM:如果你有一块 NVIDIA RTX 3090 或 4090,那已经有 24 GB。

  • CPU RAM:大多数现代计算机至少有 12 GB 的 CPU RAM。扩展 CPU RAM 也非常便宜。

  • 硬盘(或 SSD):这可以是几个 TB 的空闲内存。请注意,如果你计划使用 SSD(NVMe M2 类型)来运行 LLM,它将比典型的硬盘快得多。

为了利用可用设备,我们可以将 Falcon 180B 切分,以便它按照优先顺序使用设备的最大内存:GPU、CPU RAM 和硬盘。

通过Accelerate的 device_map 可以轻松实现这一点。

device_map 将模型的整个层分配到你拥有的不同设备上。

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

device_map — 作者提供的图片

如果你想查看一些 device_map 使用的例子,查看我的笔记本。我在大多数笔记本中使用了 device_map。

device_map 非常方便可以避免 CUDA 内存不足错误。但如果你打算在消费级硬件上使用 Falcon 180B,这仍然远非理想。即使配置高端,配备 24 GB 的 VRAM 和 32 GB 的 CPU RAM,也会在硬盘上留下几百 GB 的空间。

这是一个问题,原因有两个:

  • 硬盘和 SSD 的速度比 VRAM 和 CPU RAM慢得多。从硬盘加载和运行 Falcon 180B 会花费非常长的时间。

  • 消费级硬盘和 SSD并未设计和测试用于这种密集使用。如果模型的许多部分被卸载到硬盘上,系统将不得不在推理过程中多次访问和读取这些巨大的模型分片。这是一个长期的巨大读写操作数量。如果你进行几天的推理,例如生成一些合成数据集,这可能会损坏你的硬盘,或者至少显著减少其寿命。

为了避免过度使用硬盘,我们没有太多解决方案:

  • 添加一张 GPU:大多数高端主板可以容纳两张 RTX 3090/4090。这将为你提供 48 GB 的 VRAM。

  • 扩展 CPU RAM:大多数主板有 4 个可用于 CPU RAM 套件的插槽。虽然有 4*128GB 的 CPU RAM 套件出售,但不容易找到,而且仍然很昂贵。注意:操作系统对 CPU RAM 的总支持量也有限制。对于 Windows 10,它是 2 TB。如果你使用的是旧版本操作系统,在购买更多 RAM 之前应查看其文档。

  • 量化 Falcon 180B 并扩展 CPU RAM。

Falcon 180B 的量化是减少内存消耗的最佳选择之一。

通过量化减少 Falcon 180B 的大小

量化非常大的语言模型到较低精度现在已成为一种常见做法。GPTQbitsandbytes nf4是将 LLM 量化到 4 位精度的两种流行方法。

Falcon 180B 使用 bfloat16。我们看到它是 360 GB。

一旦量化到 4 位精度,它仅为 90 GB(1800 亿参数 * 0.5 字节)。我们可以使用 100 GB 的内存(90GB + 一些内存开销)来加载 4 位 Falcon 180B。

如果你有 24 GB 的 VRAM,你“仅需”75 GB 的 CPU RAM。这仍然很多,但比加载原始模型要实惠得多,而且在推理过程中不会将模型的层卸载到硬盘上。注意:你仍然需要在硬盘上留出 100 GB 的自由空间以存储模型。

你甚至不需要 GPU。拥有 128GB 的 CPU 内存,你可以仅使用 CPU 进行推理。

量化本身是非常昂贵的。幸运的是,我们已经可以在网上找到量化版本。TheBloke发布了使用 GPTQ 制作的 4-bit 版本:

注意:还有 3-bit 模型作为这些模型的“分支”提供。请遵循 4-bit 模型卡上的说明来获取这些模型。

尽管模型的精度有所降低,但根据Hugging Face 的实验,模型的性能保持相似。

GPTQ 模型推理速度快,你可以使用 LoRA 适配器进行微调。例如,我在这篇文章中展示了如何微调使用 GPTQ 量化的 Llama 2。

## 使用 Transformers 和 TRL 进行量化和微调 LLMs

GPTQ 现在使用起来容易多了

kaitchup.substack.com

虽然可以对 GPTQ 模型进行微调,但我不推荐这样做。使用 QLoRA 进行微调虽然内存消耗相似,但由于 nf4 量化的改进,能获得更好的模型,正如QLoRA 论文中所示。

结论

总结一下,你需要量化和 100GB 内存来在一个相对实惠的电脑上运行 Falcon 180B。

对于快速推理或微调,你需要一个 GPU。RTX 4090(或更便宜但较慢的 RTX 3090 24GB)足以加载量化模型的 1/4。如果你的电脑机箱有足够的空间,你甚至可以放入两张 RTX 显卡。

如果你想在仅 CPU 配置上运行 Falcon-180B,即没有 GPU,忘掉微调吧,这会太慢。推理也会很慢,但使用近期的高端 CPU 和优化过的软件,如 llama.cpp,运行 Falcon 180B 还是可能的。

如果你对使用llama.cpp感兴趣,可以看看我关于如何仅使用 CPU 运行 Vicuna 的文章:

## 高速推理:使用 llama.cpp 和 Vicuna 在 CPU 上

快速推理不需要 GPU

kaitchup.substack.com

鹰:开源大型语言模型的巅峰

原文:towardsdatascience.com/falcon-the-pinnacle-of-open-source-llms-600de69c333c

开源 LLMs 与专有 LLMs 之间的差距持续缩小…

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

·发表于 Towards Data Science ·阅读时间 14 分钟·2023 年 10 月 24 日

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

(照片由 Alan Mersom 提供,来源于 Unsplash

最近,开源大型语言模型(LLMs)的研究主要集中在两个方面:模仿学习和预训练开源基础模型。虽然这两种方法都是可行的,但创建高质量的开源基础模型尤其令人振奋,因为这些模型可以进一步微调(成本较低)并用于各种不同的下游应用。最初尝试创建这些模型的结果并不理想。尽管后来的模型(例如,LLaMA 和 MPT-7B)表现更佳,但这些模型在质量上仍难以匹敌其专有对手(例如,GPT-3.5 或 GPT-4),直到最近才有所改进。

随着 Falcon-7B 和 Falcon-40B LLMs [1] 的发布,我们第一次看到开始与最受欢迎的付费模型质量相媲美的开源基础 LLMs。这些模型在通过新颖的数据管道获得的大规模文本语料库上进行训练,取得了(以相当大的优势)新的开源 LLMs 的最先进性能,并且可以自由用于商业应用。更棒的是,Falcon 模型对其底层的 Transformer 架构进行了若干修改,这些修改显著加快了推理速度,甚至可以提高预训练的效率。

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

(来自 [1, 2])

大局观。 创建一个 LLM 的过程包含几个步骤;见下文。这个过程的第一步(即,获得预训练基础模型)被广泛认为是最昂贵的,无论是在金钱还是时间上。

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

创建和完善 LLM 的多步骤过程(来自 [16, 17])

这些模型之前被保留在专有 API 后面,但开源 LLM 的进步使高性能基础 LLM 更加公开。Falcon 是这个类别中的另一个模型,相比其他开源替代品,它达到了前所未有的性能水平。

使用 Web 数据进行 LLM 预训练

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

(来自 [3])

当我们探讨预训练与微调(即,SFT 和 RLHF)语言模型之间的主要差异时,会发现预训练比微调要困难得多(且更昂贵);见上文。预训练有两个根本性特征使其如此困难:

  1. 模型是从头开始训练的,因此需要更多的训练迭代次数。

  2. 预训练数据集必须大且多样化(即,提供尽可能多的“覆盖”),以便生成的 LLM 拥有较大的知识基础。

简而言之,预训练数据集非常庞大(例如,Chinchilla [6] 在 1.4 万亿文本令牌上训练),这意味着预训练的范围不可避免地很大。我们必须进行大量的训练迭代才能遍历所有这些数据!

创建预训练数据集。 然而,数据集的大小不仅是使预训练成为如此庞大任务的原因。仅仅策划数据集就是一个复杂的过程,涉及到检索数据和执行整个过滤(例如,基于数据质量、污染数据、PII 等)和去重步骤的管道。已经提出并探索了各种不同的处理步骤来策划 LLM 预训练数据;见 这里

尽管最初可能认为这些处理步骤可以简化或避免,但 LLM 研究一次又一次地向我们展示了模型训练数据的质量是极其重要的。例如,我们可以看到 LIMA [7] 或 Galactica [8],这两个模型都在较小(但高质量)的文本语料库上训练,结果与在更大规模的噪声数据集上训练的相同模型的性能相匹配或超越。

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

(来自 [4, 5])

当前的 LLM。 由于数据质量对模型质量的影响,大多数 LLM 使用的预训练数据来自高度策划的来源,例如筛选过的文本内容、书籍、代码或技术报告;见上文 [4, 5]。实际上,许多策划过的预训练数据公共来源在线上随处可见(例如,the PileC4),并且已经被现有模型广泛使用。

“策展被认为是产生高性能模型所必需的……然而,随着模型需要在万亿个标记上进行预训练,尚不清楚策展是否具有可扩展性,以及我们是否会很快耗尽独特的高质量数据。” — 来源于 [2]

然而,使用策展数据源是否真的具有可扩展性仍然值得怀疑——随着预训练数据集规模的扩大,细粒度的过滤和策展变得越来越困难。因此,对于较大的模型和数据集,广泛的策展可能变得不再那么必要,特别是考虑到为 LLM 预训练找到足够的数据变得越来越困难

RefinedWeb: 可扩展的网页文本策展

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

(来自 [2])

鉴于这些局限性,Falcon [1]的作者探讨了可扩展和高效的数据策展方法,这些方法可以推广到大量数据。用于创建RefinedWeb预训练数据集的完整数据策展流程,Falcon-7B [10]和 Falcon-40B [11]即在该数据集上进行预训练,详细描述见[2]。RefinedWeb 由经过简化过滤流程的网页数据组成,可以用于训练比在策展数据源上训练的类似模型性能更优的模型;见上文。这一发现表明,大规模训练语料库可以有效地从专门从互联网获得的数据中创建(而不是策展数据源)。

“挑战了对数据质量和 LLMs 的现有信念,单独使用经过适当过滤和去重的网络数据训练的模型可以达到与训练在策展数据上的模型相当的性能。” — 来源于 [2]

简化的策展流程。用于 Falcon 的预训练数据集基于Common Crawl。与之前的工作相比,[2]中的作者通过强调规模来区分他们的数据策展流程,旨在从网络中生成一个包含 3-6 万亿个数据标记的预训练语料库。这远远超过了之前工作的数据集——即使是 MassiveText(即用于预训练 Gopher [9]和 Chinchilla [6]的语料库)也仅包含 2.3 万亿个标记的文本。而且,现有模型在预训练过程中仅使用了这些数据的一个子集。

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

(来自 [2])

[2] 中构建的语料库包含大约 5 万亿个仅含英语的数据标记,完全从网络上获取;见上文。尽管数据规模庞大,作者在构建过程中采用了严格的去重策略,这一策略以较高的速度去除准确的和 模糊重复。在这种去重之外,只进行了最小的额外过滤。实际上,除了通过 FastText 分类器 进行语言识别外,没有进行其他基于机器学习的过滤。

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

(来自 [9])

除了过滤非英语文档,[2] 中的作者还使用了几个简单的启发式方法来过滤不需要的内容,例如:

  • 从与黑名单关联的 URL 中过滤内容

  • 使用 trafilatura 从网页中提取内容

  • 定义简单规则来识别和过滤 PII

此外,采用了 MassiveText [9] 过滤管道中的几个步骤;见上文。RefinedWeb 的完整策划管道移除了原本从 CommonCrawl 获得的近 90% 数据,但在完全过滤后的语料库中仍保留了超过 5 万亿个标记的文本数据;见下文。此外,这个数据集是“多模态友好的”,因为它包含了指向图片的链接和替代文本。

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

(来自 [2])

开源预训练数据。 生成的 RefinedWeb 数据集非常庞大,表明互联网上有足够的数据可以进行前所未有规模的 LLM 预训练。换句话说,我们并没有(至少暂时没有)数据短缺。然而,[2] 中的作者仅公开了该语料库中一个小的 600B 标记子集(即 12% 的数据)。尽管其规模较小,但这个公开的 RefinedWeb 子集仍然是任何从事 LLM 工作的实践者有用的预训练数据来源。事实上,在 [2] 中,基于这些数据训练的小型 Falcon 变体相比于在策划语料库上训练的模型表现更为优越;详见下文。

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

(来自 [2])

Falcon 系列 LLM

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

(来自 TII Falcon 网页 [1])

Falcon 系列 LLM [1],包括 Falcon-7B [10] 和 Falcon-40B [11],在开源模型中实现了最先进的性能。此外,这些模型的指令调优变体,如 Falcon-40B-Instruct(即 HuggingFace 的 Open LLM 排行榜 上的顶级模型),在公共基准测试中表现更佳。正如我们将看到的,这些模型具有几个关键特性(例如,数据、性能和推理速度),使其独特且实际有用。

商业许可证。 起初,Falcon 模型以一种特殊的许可证发布,该许可证要求在模型(或其任何衍生物)用于商业应用时支付版税。然而,在这一初始发布之后不久,该许可证被修改为普通的 Apache 2.0 许可证,这意味着 Falcon 基础模型现在可以在商业应用中免费使用!与其他开源和可商业使用的 LLMs(即使是 MPT-30B)相比,Falcon-40B 实现了独特的卓越性能。

Falcon-7B 和 40B 数据集

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

(来自 [10, 11])

如前所述,数据集 [2] 被用于对开源 Falcon-7B/40B 模型进行预训练。然而,这些模型仅在完整的 5 万亿标记语料库的一个子集上进行预训练(即 Falcon-7B 为 1.5 万亿标记,Falcon-40B 为 1 万亿标记)。然而,这个语料库的子集通过额外的、精心挑选的数据(如书籍、代码和技术内容)进行了增强;见上文。由于其更大的规模,Falcon-40B 的训练数据量少于 Falcon-7B。尽管如此,较大的模型仍然表现更好,训练时间超过两个月,而 Falcon-7B 仅需两周。

多语言 LLMs。 RefinedWeb 语料库仅包含英文数据,而 Falcon-7B 仅使用英文文本进行训练。然而,Falcon-40B 的预训练集中增加了 RefinedWeb-Europe 语料库,该语料库包含来自各种常见欧洲语言的文本数据。尽管这些数据仅占预训练语料库的 7%,但它将少量多语言数据注入到模型的知识库中,使其在需要基本多语言理解的公共基准测试中表现更佳。

Falcon 架构

Falcon-7B 和 Falcon-40B 模型都使用了修改过的解码器-only transformer 架构。对该架构的修改包括:

  • Flash Attention

  • RoPE 嵌入

  • Multi-Query Attention

  • 并行注意力和前馈层

这些修改(其中一些与 MPT 套件的 LLMs 共享)极大地提高了 Falcon 的推理速度。实际上,Falcon-40B 的推理速度是 GPT-3 的 5X。此外,由于这些修改,预训练 Falcon-40B 的成本较低;例如,Falcon-40B 需要 75% 的 GPT-3 [4] 计算预算,40% 的 Chinchilla [6] 计算预算和 80% 的 PaLM-62B [5] 计算预算。Falcon-7B 和 Falcon-40B 的训练序列长度为 2K 标记,相较于最近的 LLMs(如 StoryWriterClaude)可以说较小。

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

(来自 [10, 11])

Falcon-7B/40B 共享相同的模型架构,但 40B 变体稍微更深(即 60 层对比 32 层)且具有更高维度的隐藏层;见上文。使用 Falcon-40B 需要约 90Gb 的内存,这比类似的模型(如 LLaMA-65B)有更低的开销。然而,Falcon-40B 仍不能像 MPT-30B 一样在单个 GPU 上托管。鉴于其较小的规模,Falcon-7B 仅需约 15Gb 的内存,使其在推理和微调中更具可及性。

RoPE 嵌入。 正如我们在之前的概述中看到的,自注意力操作(该操作在语言模型的解码器单一转换器架构的每一层中实现)并不会自然地考虑序列中每个标记的位置。因此,我们必须为每个标记注入位置信息(例如,通过加性位置嵌入)到此操作中;见下文。

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

转换器的加性位置嵌入(由作者创建)

提出了几种位置嵌入变体——这些变体可能在训练过程中学习每个嵌入,也可能不学习,包括绝对嵌入和相对嵌入。然而,旋转位置嵌入(RoPE)[15]是一个将每个标记的绝对位置(即序列中的全局位置)和相对位置(即基于标记之间的距离定义位置)结合到自注意力中的替代方案,如下所示:

  1. 旋转矩阵编码绝对位置

  2. 将相对位置信息直接添加到自注意力操作中

这种方法被发现能够在绝对位置和相对位置信息之间取得平衡,这对需要较长序列长度的任务尤其有利。因此,RoPE 嵌入最近获得了广泛关注,导致其被用于如 PaLM [5]等模型中。有关更详细的概述,请查看 RoPE 嵌入的这里

多查询注意力。 Falcon 模型用一种称为多查询注意力的替代结构替换了典型的多头自注意力操作。多查询注意力仅在每层的注意力头之间共享键和值向量(如下图红色高亮部分)。所有头部共享相同的键的投影矩阵和相同的值的投影层,而不是为每个头部执行单独的投影。尽管这一变化并未加快训练速度,但显著提高了最终 LLM 的推理速度。

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

LLM 中的多查询注意力(来自[18])

并行注意力层。 最后,Falcon 模型在其架构的每一层结构中做出了一项基本变化。与“串行”解码器仅限变体的层不同,Falcon 模型的每一层都并行执行自注意力和前馈变换,然后进行单层归一化操作。这个公式与标准的变换器块之间的差异如下所示。有趣的是,这种并行公式并没有降低模型的性能。然而,由于变换器层的两个主要操作是并行进行的,因此它可能在推理速度上带来好处。

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

解码器仅限的变体层(由作者创建)

开源语言模型的新标准!

在撰写时,关于 Falcon 模型的手稿尚未发布。然而,Falcon-7B 和 Falcon-40B(以及它们的指令调优变体)已通过 Open LLM Leaderboard 进行评估,该榜单包括多个基准测试,例如:

通过该榜单进行的评估是不完整和初步的。然而,这些评估虽然在合理范围内捕捉了模型性能,但清楚地显示了 Falcon-40B 是当前开源语言模型的最先进技术;见下文。

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

(来自 Open LLM Leaderboard)

Falcon-40B 的指令变体(即 Falcon-40B-Instruct),它在来自 Baize 的数据混合上进行了指令调优,远远超越了各种其他开源模型。此外,预训练的 Falcon-40B 模型表现也相当不错,甚至优于像 LLaMA-65B 和 MPT-30B 这样的著名基础模型。进一步说,Falcon-40B 也可用于商业用途,而榜单上的许多可比模型(例如 LLaMA [13]、Guanaco [14] 和 Lazarus)仅可用于研究目的。

Falcon 的实际使用。 鉴于其性能非常出色,与其他语言模型相比托管起来轻量(由于改进的推理速度),并且可以在商业应用中自由使用,Falcon LLMs 是任何从事 LLM 工作的实践者的重要开源工具。幸运的是,已经撰写了几篇详细的概述,概述了在实践中微调和托管/部署这些模型的有用框架。

  • 在 AWS Sagemaker 上部署 Falcon-40B [link]

  • 使用 Falcon 进行推理和参数高效微调 [link]

  • 使用 PyTorch Lightning 对 Falcon-40B 进行微调 [link]

鉴于 Falcon 使用 AWS 进行训练,目前有相当数量的解释性文章介绍如何在类似硬件上部署和训练这些模型。这些文章为任何希望在自己的用例中利用 Falcon 的人提供了一个良好的起点。

最终思考

Falcon 的发布是开源 LLM 研究和应用的重大突破。当我们审视这些模型的独特贡献时,我们立即看到一些关键组件,这些组件导致了成功:

  1. 大规模预训练数据的独特混合

  2. 针对效率优化的架构

RefinedWeb 数据集显示,文本语料库可以在比以前探索的更大规模上创建。为此,我们只需从网络上下载大量数据,并采用严格的去重规则以及更简单高效的过滤启发式方法。然后,通过将这些庞大的数据源与少量精选文本相结合,我们可以预训练一个性能极佳的开源 LLM。最后,Falcon 模型的修改架构使得训练和推理更加高效,结果是一个表现出色且在部署时能快速生成文本的模型。

与我联系!

非常感谢阅读这篇文章。我是 Cameron R. WolfeRebuy 的 AI 总监。我研究深度学习的经验和理论基础。如果你喜欢这篇概述,请订阅我的 Deep (Learning) Focus newsletter,我通过从头开始概述相关主题帮助读者理解 AI 研究。你还可以在 XLinkedIn 上关注我,或者查看我在 medium 上的 其他著作

参考文献

[1] “Introducing Falcon LLM”, 技术创新研究所, 2023 年 6 月 7 日, falconllm.tii.ae/.

[2] Penedo, Guilherme 等. “The RefinedWeb dataset for Falcon LLM: outperforming curated corpora with web data, and web data only.” arXiv preprint arXiv:2306.01116 (2023).

[3] “Introducing MPT-7B: A New Standard for Open-Source, Commercially Usable Llms.” MosaicML, 2023 年 5 月 5 日, www.mosaicml.com/blog/mpt-7b.

[4] Brown, Tom 等. “Language models are few-shot learners.” Advances in neural information processing systems 33 (2020): 1877–1901.

[5] Chowdhery, Aakanksha 等. “Palm: 利用路径扩展语言建模。” arXiv 预印本 arXiv:2204.02311 (2022)。

[6] Hoffmann, Jordan 等. “训练计算最优的大型语言模型。” arXiv 预印本 arXiv:2203.15556 (2022)。

[7] Zhou, Chunting 等. “Lima: 对齐的‘少即是多’。” arXiv 预印本 arXiv:2305.11206 (2023)。

[8] Taylor, Ross 等. “Galactica: 一种用于科学的大型语言模型。” arXiv 预印本 arXiv:2211.09085 (2022)。

[9] Rae, Jack W. 等. “扩展语言模型:方法、分析与训练 gopher 的见解。” arXiv 预印本 arXiv:2112.11446 (2021)。

[10] “Falcon-7B”,技术创新研究所,HuggingFace 页面,huggingface.co/tiiuae/falcon-7b.

[11] “Falcon-40B”,技术创新研究所,HuggingFace 页面,huggingface.co/tiiuae/falcon-40b

[12] Gao, Leo 等. “The pile: 一个 800GB 的多样文本数据集用于语言建模。” arXiv 预印本 arXiv:2101.00027 (2020)。

[13] Touvron, Hugo 等. “Llama: 开放且高效的基础语言模型。” arXiv 预印本 arXiv:2302.13971 (2023)。

[14] Dettmers, Tim 等. “Qlora: 量化语言模型的高效微调。” arXiv 预印本 arXiv:2305.14314 (2023)。

[15] Su, Jianlin 等. “Roformer: 增强的带有旋转位置嵌入的变换器。” arXiv 预印本 arXiv:2104.09864 (2021)。

[16] Ouyang, Long 等. “训练语言模型以遵循指令并获得人类反馈。” 神经信息处理系统进展 35 (2022): 27730–27744。

[17] Glaese, Amelia 等. “通过有针对性的人类判断提高对话代理的对齐。” arXiv 预印本 arXiv:2209.14375 (2022)。

[18] Vaswani, Ashish 等. “注意力即你所需。” 神经信息处理系统进展 30 (2017)。

假先知:一个自制的时间序列回归模型

原文:towardsdatascience.com/false-prophet-a-homemade-time-series-regression-model-54e296b99438

借用 Meta 的 Prophet 的思想来构建一个强大的时间序列回归模型。

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

·发表于 Towards Data Science ·阅读时间 16 分钟·2023 年 10 月 31 日

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

照片由 Niklas Rhöse 提供,来源于 Unsplash

在这篇后续文章中,我继续我的使命,通过结合流行的 Prophet 包¹ 和演讲“用简单甚至线性模型取胜”² 的思想来构建弗兰肯斯坦的时间序列怪物。

在回顾我们正在做的事情之后,我们将讨论回归模型——它是什么,以及它的特别之处。

然后,我们将使用时间序列交叉验证进行超参数调整,以获得“最佳”的模型参数化。

最后,我们将使用 SHAP 验证模型,然后利用模型形式进行定制调查和手动调整。

这是一个广泛的范围——让我们开始吧。

附注:我们在上一篇文章中已经涵盖了基础数据准备和特征工程,因此直接进入建模部分。赶紧了解一下之前的内容:

## 假先知:自制时间序列回归的特征工程(第一部分,共 2 部分)

基于 Meta 的 Prophet 包中的思想,创建强大的时间序列机器学习模型功能。

towardsdatascience.com

全局视角

让我们回顾一下我们在做什么。

终极目标很简单:在指定的时间范围内生成最准确的未来事件预测。

我们从一个仅包含日期变量和关注数量的时间序列开始。从中,我们衍生了额外的特征,以帮助我们准确建模未来的结果;这些特征深受 Prophet 方法的“启发”。

这就把我们带到现在的位置:大约准备将我们设计的数据输入一个轻量级模型,训练它以预测未来。稍后我们将深入了解模型的内部工作原理。

在继续之前,让我们回顾一下数据的样子。

数据

我们使用来自英国的实际数据——在这种情况下,STATS19 道路交通事故数据集,其中包含有关某些车祸的信息³。

我们将这个大型数据集总结为每月事故总数。这意味着我们的目标看起来像这样:

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

作者提供的图像

我们之前指出了下行趋势、强烈的季节性效应和明显的趋势变化。让我们看看我们的特征工程和建模如何协同工作来捕捉这些。

模型

虽然还不是圣诞节,但我确实有一些特征在我的模型愿望清单上。我将列出这些特征,并简要介绍为什么选择使用 LASSO 模型(剧透!)。

首先,模型应该尽可能强健。“强健”可以有很多含义,因此我们会更具体一些:我希望一个可以训练并放置一段时间的模型。换句话说,模型应该能够推断处理之前未见过但“正常”的输入值。

模型需要具有可解释性和可解释性。它应该易于查询和理解,并且简单易解释。理想情况下,我应该能够准确量化各种影响的影响。

最后,模型应该具有某种内置特征选择功能。理论上,适当执行特征选择可以提高模型性能,我可以进行严格的练习来找到最有效的特征集。实际上,现在是周六下午,人生太短暂。因此,模型将不得不自己解决。

尽管基于树的模型满足其中的一部分要求,但它们在推断方面表现不佳。它们也不能真正用于说明某些事情,比如“在六月,季节性导致 X%的变化”。

我想要的是一些通常线性的东西(提示提示),再加上一些额外的东西。

进入 LASSO

最小绝对收缩和选择算子(LASSO)是一种统计模型。它同时执行特征选择和正则化,以增强模型的预测能力和可解释性⁷。

我们不会过于关注细节,而是认识到 LASSO 是一种带有变化的线性回归方法。在模型训练过程中,使用了一个修改过的目标函数,其目的是在对回归系数施加某些约束的情况下,最大化模型的预测能力。这个约束——或称为“正则化”——有缩小特征系数接近零的效果,有效减少了给定特征对模型的影响。如果系数被缩小到零,该特征对模型拟合或预测将完全没有影响,特征实际上被从模型中移除。

这种特征系数缩减有效地减少了过拟合的可能性,因此应提高模型对新数据的泛化能力。

附注:LASSO 正则化有时被称为 L1 正则化或 L1 惩罚。这来自于正则化项的数学形式,该形式使用 L1 范数。

泛化事物

Scikit-learn 线性实现了 LASSO —— 即 Lasso 类假设目标可以表示为输入特征的线性组合。

让我们回顾一下我们正在建模的数据。我们可以看到序列的季节性成分的幅度似乎与趋势的幅度一致,这表明时间序列是乘性的而不是加性的。换句话说,我们的时间序列是时间序列元素的 乘积,而不是它们的 总和。这意味着在模型形式方面我们需要一点想象力。

幸运的是,我们不需要太有创意——通过对每月交通事故的自然对数建模,我们可以将加性 LASSO 转换为乘性模型。我们可以大致认为模型形式如下:

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

作者提供的图像

… 这意味着我们的预测看起来像:

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

作者提供的图像

附注:上述数学使用了“帽子”符号,这是在讨论估计值时的惯例。由于预测是从估计的 beta 值中得出的——这些 beta 值本身是根据数据得出的——我们对目标和回归系数都使用帽子符号。

稍后会详细讲解,但值得注意的是,当我们开始探讨模型时,要牢记这一点。

Alpha,正则化参数

我们已经确定了模型,现在需要确定其参数化。

sklearn 中,LASSO 的正则化强度通过 alpha 参数控制。我对这个超参数的最佳值没有太多直觉,因此需要进行一些搜索,记住“最佳”模型是预测效果最好的模型。

我们需要某种训练-测试分割方法来测试给定模型参数化在未来的预测效果:通常随机分割交叉验证方法效果不佳,因为目标的时间元素会混淆,甚至更糟——泄漏。

幸运的是,时间序列交叉验证是一个实际存在的技术,并且在 scikit-learn 中有很好的实现。我们将使用它来搜索从潜在值分布中随机抽取的各种alpha值,并使用多种指标评估预测准确性。

顺便说一下,如果你不熟悉时间序列交叉验证,可以在这里快速了解:

[## 时间序列交叉验证

通过聪明的子划分最大化你的时间序列数据的效用。

python.plainenglish.io

经过一些搜索,我们可以可视化预测误差,并选择使其最小化的alpha值:

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

图片由作者提供

我们可以看到,当alpha接近 0.0017 时,均方误差(MAE)、均方根误差(RMSE)和平均绝对百分比误差(MAPE)都达到了最低值。

由于我们暂时没有比较模型,因此指标的实际值并不太重要。值得指出的是,MAPE 实际上不是一个百分比;我在上面的图表中将其格式化为百分比,以帮助我感受这些实际上相当小的数字。

到目前为止一切顺利。但一个具有这种alpha的模型实际预测效果如何?实际上还不错:

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

图片由作者提供

上面的图表是使用上述alpha参数的 LASSO 模型时间序列交叉验证结果的可视化表示。

每条彩色线代表了一个在预测期之前基于所有数据(灰色)训练的模型的折外预测。

例如,橙色线代表了基于截至 2010 年数据训练的模型对 2011 年的 12 个月预测。

预测结果有点难以看清,我们来放大一下:

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

图片由作者提供

对于如此嘈杂的实际数据,这些预测效果还不错:它们似乎捕捉到了年度趋势,并预测了更强的季节性变化。

然而,模型预测往往会错过每个时间段开始的前几个样本,然后通常会回到正常范围。

考虑到我们注意到在 2012–2014 年左右,底层数据中趋势和季节性发生了变化,我认为这个模型实际上表现得相当不错!

在继续进行模型调查之前,让我们使用这个alpha值来重新拟合整个数据范围的模型。

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

图片由作者提供

结果、验证与调查

我们想要一个易于理解和解释的模型。让我们看看我们做得如何。

SHAP 验证

在模型评估方面,SHAP 包相当普及。我们将跳过整个“什么是 SHAP?SHAP 如何工作?”的介绍,直接进入结果,仅简要回顾一下 SHAP 关注的是模型预测的驱动因素,而不是模型拟合的驱动因素。

暂停完毕。

作为开端,总体总结突出显示了预测的最大驱动因素。

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

图片由作者提供

图表的顶部似乎是year特征,我们看到year的值较高时,预测值会降低。这与我们在数据中看到的明显下降趋势一致。

我们看到许多受 Prophet 启发的特征在预测中发挥了关键作用。有趣的是,prophet_sin_12出现在列表中(并且排得如此靠前!)——我们扩展 Prophet 特征创建范围的直觉似乎得到了验证。

还有一些其他特征出现了,比如其中一个周期性编码的月份特征(mth_sin)和一个月中的假期数量(hols),但这个列表主要被变化点特征所主导。以至于它可能需要进一步调查。

SHAP 还允许我们可视化单个预测的驱动因素。作为一个开端,让我们看看第一个和最后一个观察结果。

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

图片由作者提供

记住这两个观察结果相隔大约 20 年和 8,000 起事故,值得注意的是每个预测的驱动因素中都有一些共同点:这两个预测都受到了yearhols的高度影响(尽管方向不一定相同)。

让我们转向一些更富有想象力的内容。

定制调查

我们现在可以开始有创意地调查我们的模型。

记住,我们实际上建立了一个通用的线性模型,并添加了一些正则化,目标的转换意味着我们实际上使用了对数链接函数。

记住,在模型训练中,特征系数(beta)受到约束,最终会收缩到接近零。这给了我们第一个调查途径:检查拟合模型参数的大小。

任何具有非零系数的特征都会被用于模型中,特征系数的大小是其在整体预测中的“重要性”的一个指标。

这可以相对容易地完成,使用coef_属性从拟合模型中获取系数,然后记得在排序和/或排名之前取提取系数的绝对值。检查那些可以合理预期包含在模型中的但实际上没有的特征也是个好主意。

我们还能做些什么?好吧,这里就变得非常有趣了。

由于我们使用了 GLM,我们可以将特征分组在一起计算对预测的组影响。由于我们使用了对数链接函数,我们的影响是乘法的,理解起来更简单——例如,组影响为 1.05 = 增加 5%。

例如,我们有大量的 Prophet 特征。我们可以很容易地直接计算所有 Prophet 特征对总体预测的影响。

这种分组也不是绝对的——我们可以根据需要进行分组。在我们的案例中,我将把特征分组到主题中:

def feature_type(s,
                 changepoint_flag=['change'],
                 seasonal_flag=['prophet','is_','qtr','mth','dim'],
                 trend_flag = ['yr'],
                 holiday_flag=['bus_days','hols']):

    if any(item in s for item in changepoint_flag):
        return 'changepoint'
    elif any(item in s for item in seasonal_flag):
        return 'seasonal'
    elif any(item in s for item in trend_flag):
        return 'trend'
    elif any(item in s for item in holiday_flag):
        return 'holiday'

特征组的影响量化是通过系数和该组中每个特征的特征值的点积来计算的。这使我们能够清晰地探索每个组随时间的影响。

我们对变化点特征的主导地位有些怀疑——让我们可视化它们的影响,看看它们如何随时间影响预测:

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

作者提供的图片

大多数变化点推动了预测的下降,除了 2013 年左右的一个上升变化。有很多变化点被使用,可能弥补了“纯”趋势特征中的不足。

那么趋势和变化点特征的综合效果如何?这些实际上是 Prophet 模型中的“趋势”组件。结果证明,这也不是很难获取:

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

作者提供的图片

季节性效果怎么样?我很想看看各种季节性特征如何组合在一起形成一个整体效果。

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

作者提供的图片

有趣!我们在这里看到了一年初的下降,然后是一年中期的双重峰值——这是一个相当复杂的效果。

最后,让我们看看假日效应,我们将工作日和实际的银行假日捆绑在一起。由于英国一年中的银行假日非常少,这一特征组可能会比较嘈杂:

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

作者提供的图片

……这正是我们看到的。注意到大多数影响在减少 1%(0.99)和增加 2%(1.02)之间波动,某些急剧下降可能与节假日较多的月份有关。

我认为可以相当安全地说,我们可以隔离每个效果对任何给定观察的影响——这正是我们开始这次小冒险时所期望的!

总结与漫谈

我们在这篇文章中涉及了很多内容。按照惯例,在稍作闲聊之前,先进行简要回顾。

总结

在谈到我们希望建立一个出色的预测模型后,我们查看了英国的道路交通事故数据。我们看到在汇总的月度数据中存在明显的趋势和季节性,并知道我们的特征工程和建模需要加倍努力以捕捉这些效果。

我们讨论了构建的模型应能良好预测,应具备可解释性和可理解性,并应具有某种形式的固有特征选择。我们还讨论了为何 LASSO 模型符合这些要求。

我们继续探讨模型形式,以及如何变换目标以更好地适应时间序列的乘法形式。我们探索了正则化参数alpha,以及适当的(时间序列)交叉验证方法以找到用于预测的最佳alpha。我们通过绘制时间之外的预测结果来进行感知检查,然后使用alpha和整个历史数据重新构建模型。

随后,我们了解了如何利用 SHAP 有效地研究 LASSO 模型的内部运作。然后,我们利用模型的可分性来研究和量化趋势、季节性和假期效应的影响。

让我们聊聊吧。

决策树无法外推

……以及使用广义线性模型的其他原因。

决策树模型最著名的挑战之一是它们无法超越训练数据的值进行外推。使用决策树模型间接地对预测值设置了上下限;鉴于我们的序列随时间的趋势,这可能不是一个合适的让步。

神经网络可以外推,但考虑到它们的复杂性,我决定退回到我的拿手好戏:谦逊的 GLM。

尽管简单,GLM 具有很多优点。尤其是考虑到我们的具体要求:

  • GLM 被广泛使用和理解。它们的熟悉度使得解释和沟通变得简单。

  • 模型结构意味着有效选择链接函数可以使模型变得可处理。特别有用的是我们可以相对于“基准”观察值进行比较,因为这允许我们量化给定特征的某一水平的影响,其他条件相同。

  • 类似地,我们可以计算给定特征的确切效果。这使得确保某些条件——如客户的期望——变得更加简单明了。

从实际的角度来看,我们的模型形式允许我们轻松进行一些手动“调整”。例如:如果我们认为我们的时间序列趋势与数据所示的方向不同怎么办?我们可以在假定的未来趋势上应用建模的季节性和假期效应,从而得到定制的预测。我们可以将这种方法概括并应用各种调整——非常适合情景测试和沟通“如果会怎么样?”的问题结果。

当然,GLM 并非全是阳光和彩虹。它们的构建可能相当棘手,通常需要专家判断。当我们开始引入特征交互(我们必须自己创建!)时,它们也可能变得非常麻烦。

就像大多数事情一样,你必须选择合适的工具来完成工作。

剩余的:模型残差

我们对一个已知具有强烈趋势和季节效应的时间序列进行了建模。我们怎么知道我们是否充分捕捉到了这种趋势和季节性呢?

一种需要调查的途径是模型残差:实际值与预测值之间的差异。残差中的任何趋势或季节性都会表明模型没有充分考虑这些效应;我们需要采取措施解决这个问题。

自然地,我并没有做到这一点。听我的,不要看我做的。

有趣的是,调查模型残差的方法实际上很自然地过渡到不同的建模方法。

一些从业者——包括我工作中的那些——支持混合模型,其中我们对原始系列进行简单建模,然后用不同的时间序列模型建模残差。

这里的基本逻辑是,简单模型充分捕捉了趋势,因此残差不包含长期非周期性效应:也就是说,残差是平稳的。这为依赖于平稳性假设的其他技术打开了大门——如一些时间序列方法——或可以从平稳性中受益,因为它们处理外推不特别好——如基于树的方法。

组 SHAP

大多数从业者喜欢使用 SHAP。理由很充分——看看这些可视化效果是多么的信息丰富和易于理解!

不过,如果我们能够将各种特征组合成特征集,并将这些特征集输入到 SHAP 可视化中,那就更好了。

尽管我们手动做过类似的工作,但分组特征的 SHAP 瀑布图确实能够生动展示预测的驱动因素……看起来我们可能可以做到这一点¹⁰。

让生活更轻松

我的工作流程用的都是比较繁琐的方法,包括手动交叉验证循环。这不是炫耀,相信我——我更愿意走最小阻力的路径。

我最后还是手动完成了这些操作,因为我无法让 scikit-learn 的随机交叉验证¹² 搜索正常工作。我认为这是因为使用了Pipeline估计器而不是仅使用Lasso类(记住我们需要在拟合 LASSO 模型之前对输入特征进行标准化),但我不确定。如果你知道解决方案——或者能指引我正确的方向——请告诉我。

最后,LassoCV 在 scikit-learn 中确实存在。这是一个自我调节的 LASSO 模型——没错,将其指向你的数据,它会自主确定alpha的最佳值。看起来它甚至可以接受定制的交叉验证生成器¹¹,尽管我不确定这是否包括时间序列交叉验证。下次可以看看。

就这些了。我希望你和我一样享受阅读这篇文章的过程。

和往常一样,请告诉我你的想法——我很想了解你在 Prophet 或用不同方式建模时间序列的经历。

下次再见。

参考文献和有用资源

  1. GitHub — facebook/prophet: 用于生成具有线性或非线性增长的多季节时间序列数据的高质量预测的工具。

  2. Vincent Warmerdam: 通过简单的,甚至线性的模型获胜 | PyData London 2018 — YouTube

  3. roadtraffic.dft.gov.uk/downloads 按照 开放政府许可证 (nationalarchives.gov.uk) 使用

  4. 大规模预测 (peerj.com)

  5. 全面的时间序列分解指南 | Towards AI

  6. 趋势变点 | Prophet (facebook.github.io)

  7. Lasso (统计学) — 维基百科

  8. Civil-liability-act-2018-Q-and-A.docx (live.com)

  9. interaction.pdf (mcgill.ca)

  10. 将 SHAP 值聚合到特征集合中是否有效? · Issue #933 · shap/shap · GitHub

  11. 3.2.3.1.5. sklearn.linear_model.LassoCV — scikit-learn 0.15-git 文档

  12. sklearn.model_selection.RandomizedSearchCV — scikit-learn 1.3.1 文档

虚假预言者:将回归模型与 Meta 的 Prophet 进行比较

原文:towardsdatascience.com/false-prophet-comparing-a-regression-model-to-metas-prophet-bfac00823425?source=collection_archive---------4-----------------------#2023-11-25

我那款受 Prophet 启发的时间序列回归模型——一个怪物般的自制版本——能与真正的预言者竞争吗?

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

·

关注 发表于 Towards Data Science ·7 min read·2023 年 11 月 25 日

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

图片由 Piret Ilver 提供,来源于 Unsplash

在我构建 Meta 优秀预测包 Prophet 的最后一篇文章中,我将会查看我自制的版本与原版相比的表现如何。

这将是一个快速的过程:我们首先查看数据,然后可视化两种方法在时间外数据上的预测。接着,我们将使用一些指标更正式地确定哪个预测器更好,并讨论是否这是一个公平的比较。

让我们开始吧。

顺便提一下:我提到了其他几篇文章——确切来说是两篇。第一篇涉及基于 Prophet 方法的时间序列特征工程,可以在这里找到:

## 虚假预言者:自制时间序列回归特征工程

基于 Meta 的 Prophet 包的思想,为时间序列机器学习模型创建强大的特征

towardsdatascience.com

在续集中,我使用我们全新特征构建模型。可以在这里找到:

## 虚假预言者:自制时间序列回归模型

借鉴 Meta 的 Prophet 构建强大的时间序列回归模型

towardsdatascience.com

今天讨论的许多话题在链接的文章中有更详细的介绍——如果你喜欢细节,值得一读。

数据

我们使用的是英国道路交通事故数据¹,汇总为每月统计。

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

作者提供的图片

我们在时间序列中看到了一些特征:

  • 整个序列中有一个强烈的下降趋势

  • 在 2012 年和 2014 年之间的某个时间点,减少率发生了变化

  • 序列早期的季节性较强

  • 潜在的季节性变动,尤其是在序列的末尾。

游戏的目标

我们有两个模型——我们将自制的 Frankenstein 模型称为 LASSO 模型,而 Meta 的 Prophet 就叫做… Prophet。

对于每个模型,我们将生成时间外预测。这本质上意味着拟合我们每月统计数据的一个子集,然后预测未来 12 个月。

每个预测将与实际观察数据进行比较;哪个模型平均最接近——哪个就赢。

顺便提一下:这本质上是一个交叉验证测试。如果你熟悉标准的交叉验证方法,但在时间序列分析中没有使用过,你可能会发现下面的(2)非常有用。

图片

我们可以可视化每个模型的时间外预测——LASSO 为红色,Prophet 为蓝色——并将其与实际数据进行比较。

我们应该记住,每个预测都是使用预测期前的所有数据构建的。例如,2010 年的预测是使用截至 2009 年的数据建立的。

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

作者提供的图片

这是一个相当清晰的画面:除了 2013 年之外,Prophet 看起来都有点偏离预期。

有趣的是,需要注意的是两种方法创建的预测模式的相似性:

  • 两种模型都产生较低的预测——即它们反映了总体趋势的下降趋势。

  • 两种模型都有年内增长和年中高峰——即预测产生类似的季节性模式。

两个模型与现实有多大距离?我们需要查看一些性能指标来找出答案。

在数字上

我们将使用常见的性能指标来衡量性能——平均绝对误差(MAE)、平均绝对百分比误差(MAPE)和均方根误差(RMSE)——以及一个新手(至少对我来说是新手):MASE。

平均绝对缩放误差

平均绝对缩放误差(MASE)是“普遍适用的预测准确度测量,避免了其他测量中出现的问题”³,并且“可用于比较单个系列的预测方法以及在系列之间比较预测准确度”³。

从数学上讲,MASE 是超出时间预测误差与由天真预测方法产生的样本内预测误差之比。由于我们使用的是月度数据,我将天真预测预测值视为前一年同一时间点的值——例如,2012 年 5 月的预测只是 2011 年 5 月的值。非常天真。

在比较预测方法时,具有最低 MASE 的方法是首选方法³。

需要注意的是,MASE > 1 意味着预测方法相对于天真预测表现不佳。

旁注:我使用了链接文章中描述的实现——即“误差”是平均绝对误差。我相信我们可以在比例误差计算中一致使用其他性能度量,例如 MAPE——只要误差度量是一致的。

结果

让我们总结一下使用我们描述的指标来总结折叠外和整体平均模型性能:

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

作者提供的图片

对于 LASSO 模型来说,这是一个相当全面的胜利,Prophet 只在局部表现优越。

刀光剑影?

如我们所见,如果你是 Prophet 的粉丝,这并不是一个令人愉快的阅读:Meta 的工具设法夺走了一些分数(取决于度量标准),以避免完全被淘汰。公正的评论可能会建议返回俱乐部重新评估策略。

虽然 Prophet 的结果不太理想,但有几个原因可以解释这种性能。

特征

LASSO 模型使用了为这一特定时间序列专门设计的特征。它可用的输入特征集本质上是 Prophet 可用特征的超集,并且有一些额外的特性。

此外,LASSO 模型中的某些特征微妙地不同。例如,描述潜在变化点的特征在 LASSO 模型中不像在 Prophet 模型中那样受到限制。

把它看作是试图超越别人,但你对他们了解的少一些——或者有些不同。并不容易。

建模

超出折叠的数据并不像我描述的那样“未见过”。

在之前的文章中,我们介绍了 LASSO 模型的参数化:我们如何使用超出折叠的数据来选择优化模型预测能力的正则化强度。从这个意义上讲,LASSO 模型已被调整以在每一组数据上进行良好的预测,而 Prophet 模型则是直接投入实际使用。

在“正常”的超参数优化中,我们通常可以期望性能提升约 1%—2%;在时间序列的背景下,性能提升可能更大,因为“超出折叠”确实是“超出时间”。

那么是时候结束 Prophet 的讨论了吗?

不要急于下结论……这系列文章确实突出了几点——让我们逐一讨论一下。

初始状态下,Prophet 工作非常出色。虽然它确实可以被超越,但做到这一点需要比仅用 10 行代码启动和预测 Prophet 更多的工作。

LASSO 模型的可解释性远超 Prophet 模型。是的,Prophet 确实为预测提供了不确定性的估计,但我们无法了解究竟是什么驱动了这些预测。我甚至不确定我们能否将 Prophet 应用于 SHAP。

我还发现 Prophet 的调整并不那么简单。也许是因为我不是该包的高级用户,或者是因为调整参数的方式曲折。LASSO 模型显然不是这样。

尽管 LASSO 方法在性能和可解释性上确实有所提升,也许我们真正需要的是结合这两种方法:一种作为另一种的试金石。例如,如果“天真的”Prophet 模型产生了合理的预测,那么复制 LASSO 方法(“虚假的先知”)以最大化性能可能是合理的。

我说完了。希望你们阅读这系列文章的体验与我写这些文章的乐趣一样。

一如既往,请告诉我你的想法——我非常希望了解你在使用 Prophet 或以不同方式建模时间序列的经验。

下次见。

参考资料和有用资源

  1. roadtraffic.dft.gov.uk/downloads 使用自 开放政府许可证 (nationalarchives.gov.uk)

  2. 我们来做:时间序列交叉验证 | Python 简明英语

  3. 均值绝对尺度误差 — 维基百科

False Prophet: 自制时间序列回归的特征工程

原文:towardsdatascience.com/false-prophet-feature-engineering-for-a-homemade-time-series-regression-part-1-of-2-52d9df3d930d

基于 Meta 的 Prophet 包中的想法,创建强大的时间序列机器学习模型特征

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

·发表于 Towards Data Science ·阅读时间 15 分钟·2023 年 10 月 13 日

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

Scott RodgersonUnsplash 上的照片

Meta 的 Prophet 包¹ 是最广泛使用的时间序列包之一。至少根据我的经验,在查看了我为稍后阅读而收藏的一系列时间序列文章后是这样。

说笑归说笑,我以前使用过这个包,我非常喜欢它。

另一个很棒的时间序列建模资源是 Vincent Warmerdam 的演讲,题为“通过简单甚至线性模型获胜”²,他在演讲中讨论了如何使用线性模型建模时间序列(需要一些准备工作)。

现在,有一些数据科学元素模糊了艺术和科学的界限——例如超参数调优或定义神经网络的结构。

我们将倾向于艺术,做许多伟大艺术家所做的事情:借鉴他人的想法。因此,在这一系列文章中,我们将从 Prophet 中借鉴特征工程的想法,并从 Vincent 中借鉴线性建模的想法,进行我们自己的时间序列回归分析。

大局观

让我们首先讨论一下总体目标,然后再深入到特征工程中。

总体目标很简单——在指定的时间范围内生成最准确的未来事件预测。

我们将从一个仅包含日期变量和感兴趣的数量的时间序列开始。由此,我们将推导出额外的信息,这将使我们能够准确地建模未来结果。这些额外的特征将会受到 Prophet 的“启发”。

然后我们将把工程数据输入到一个轻量级模型中,让它学习如何最好地预测未来。之后,我们将深入了解模型的内部工作——毕竟,我们需要了解是什么驱动我们的预测。

现在我们已经看到了全貌,让我们仔细看看数据,从数据开始。

数据

我们将使用来自英国的真实数据——在这个案例中,是道路交通事故数据。

这是由英国政府提供的 STATS19³ 数据集。由于数据集非常庞大,为了使事情更易于管理,我们将把每日事故数量汇总为月度数据。

通过可视化我们的时间序列,我们可以看到一个下降趋势和强烈的年度模式。也可以认为这些模式在 2012 年至 2014 年间的某个时点发生了变化。

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

作者提供的图片

这已经是我们需要创建的两种特征——一种是捕捉整体趋势,另一种是捕捉重复的年度模式(或季节性)。

特征工程

在开始实现之前,我们将探讨推动我们工程的一般理念。

Prophet 的魅力

Prophet 使用具有三个主要成分的可分解时间序列模型,这些成分是加性组合的(带有一点随机性)。在数学上,这是:

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

作者提供的图片

这里 g(t) 是趋势函数,用于建模时间序列中非周期性的变化,s(t) 代表周期性变化(例如,周度和年度季节性),而 h(t) 代表节假日的影响,这些节假日可能会在一个或多个日期上发生不规则的安排。⁴

正是这种可分解的模型形式使得 Prophet 如此灵活,正是时间序列可以分离的这个理念将指导我们的特征工程:也就是说,我们将生成帮助我们建模每一个这些组件的特征。

我们的模仿不会成为 Prophet 的双胞胎——我们只是从中获得灵感。因此,我们将做一些调整:

  • g(t) 还将代表时间序列中的步进变化或变更点。

  • 我们不会过多关注误差项(epsilon),只需记住 Prophet 使用它来表示“模型未能涵盖的个性化变化”⁴。

旁白:如果你不熟悉时间序列的组成部分,这篇文章是一个很好的总结:

[## 开始做:时间序列分解]

一个有效地将时间序列分解成其组成部分的指南

pub.towardsai.net

我们将从基本的日期相关特征开始,然后再衍生一些更具想象力的特征。

基础特征:零步骤

作为热身,让我们获取一些基本的日期相关特征:

# set date as the Frame index
df.set_index('date', inplace=True)

# simple date features
df['yr'] = df.index.year
df['qtr'] = df.index.quarter
df['mth'] = df.index.month
df['dim'] = df.index.days_in_month

所有这些特征都可以直接获得。即使是对这些特征不熟悉的人也可能很清楚,这些特征很可能会预测每月的事故数量。

是时候进行一些主题工程了。

趋势

趋势,即随时间的长期变化,可以呈现各种形式。

如果存在的话,趋势通常会非常简单——持续的上升或下降变化并不少见。在许多时间序列演示中使用的航空公司乘客数据⁵展示了一个非常清晰且简单的趋势。

然而,趋势可能会变得比这更复杂。例如,它们可能是非线性的,其中存在加速或减速的现象。可能会有多个加速或减速的实例。或者可能会有阶跃变化,其中趋势的位置发生突变。

我们已经看到我们的数据中似乎存在一个下降的线性趋势,变化点位于 2012 年和 2014 年之间的某个地方。我不完全确定趋势的确切形式,所以我将创建各种趋势,并让模型找出哪一个最合适:

# fraction of year
df['yr_fraction'] = df.index.year + (df.index.month - 1) / 12

# add non-linearity
yr_fraction_rebased = df['yr_fraction'] - df['yr'].min()
df['yr_fraction_sq'] = yr_fraction_rebased ** 2
df['yr_fraction_cube'] = yr_fraction_rebased ** 3
df['yr_fraction_quad'] = yr_fraction_rebased ** 4
df['yr_fraction_quint'] = yr_fraction_rebased ** 5
df['yr_fraction_sqrt'] = yr_fraction_rebased ** 0.5

从视觉上来看,这给我们提供了许多可能的趋势(通过一些缩放来将所有内容适配到同一图表上):

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

图片由作者提供

附注:重要的是要注意,虽然所有这些趋势看起来都是上升的,但模型将能够利用这些趋势捕捉数据中的下降趋势,例如使用负权重或系数。这不仅适用于趋势组件,还适用于模型中使用的所有特征。

现在来看一些变化点。

Prophet 通过首先指定大量潜在变化点,然后尽可能少地使用这些点来检测变化点。Prophet 的默认方法是在数据的前 80%内创建 25 个均匀间隔的变化点。

我们将通过首先创建许多潜在的变化点,然后让模型选择使用哪些点来做一些类似的事情。这与 Prophet 并没有太大区别,但没有对间隔施加限制。

# changepoints
changepoints = pd.DataFrame()

for date in df.index.unique():
    date = pd.to_datetime(date)
    date_str = f'change_{date.strftime("%Y_%m")}'

    # allow only X-erly changes
    if date.month % 3 == 0:
        temp = pd.DataFrame(
            {date_str:np.where(df.index <= date,0,1)}
        )
        changepoints = pd.concat([changepoints,temp], axis=1)

如果我们查看前 12 行,我们可以看到变化点创建是如何工作的:

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

图片由作者提供

并不复杂,因为我们为每个变化点特征都有一列,指示观察是否发生在给定日期之前或之后。

值得一提的是,我只允许变化点出现在每个季度末。除非我们对系列中的变化非常确定,否则设置这些点可能有点像艺术,需要在灵活性和过度反应之间取得平衡;变化点需要足够频繁以捕捉趋势中的真实变化,但又不能过于频繁,以至于开始捕捉噪声。

在这种情况下,季度变化点有一些优势。首先,它们有效地对变化持续多长时间设置了最低时间阈值,才被认为是“真实”的——这对于减少模型将信号误认为噪声的倾向可能很有用。

在英国,季度变化大致与季节性变化和重大日历变化(例如 1 月 1 日)对齐。

还需要考虑外部环境因素:每年 3 月和 9 月会发布新的注册车牌,这通常会导致新车销量的激增。由于新车通常比旧车更安全,因此可以合理地想象车主构成的变化会对道路交通事故的数量产生影响。

虽然这可能是一个不错的起点,但我们可能需要稍后再回来进行一些微调。

季节性

我们将时间序列中存在的规则性或周期性效应称为季节性。

Prophet 使用傅里叶级数来表示加性模型中的季节效应。其通用化为如下:

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

图片来源:作者

傅里叶表示法本质上意味着我们在时间序列中看到的所有重复效应可以通过一系列不同周期的正弦和余弦波来表示。

由于我们正在处理月度数据,我们预计每年在相同时间看到季节性效应;换句话说,我们时间序列的 周期 是一年,即 12 个月。因此,我们需要将 P 设置为 12。

N = 10 和 N = 3 已被证明分别适用于具有年度和周度季节性的系列,但我们将 N 扩展到 12 以确保效果。

记住,我们不是在创建一个单独的季节性模型,而是创建季节性特征,让我们的单一模型能够组合这些特征以表示周期性变化。考虑到这一点,我们按以下方式创建 Prophet 启发的特征:

# Prophet features
for j in range(1,13):
    df[f'prophet_sin_{j}'] = np.sin(2 * np.pi * df['mth'] * j / 12)
    df[f'prophet_cos_{j}'] = np.cos(2 * np.pi * df['mth'] * j / 12)

这会生成多个不同周期的正弦和余弦波,准备好让模型将它们组合在一起,以捕捉季节性。因此,作为输入特征,它们可能看起来像这样:

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

附注:注意较高的 n 值如何减少正弦函数的周期——即完成一个完整周期所需的时间。

我们的模型将以各种方式选择和缩放这些函数,以便适当地考虑时间序列的周期性(或 季节性)元素……也就是说,模型将确定上述公式中的 ab 系数。

节假日

假期和事件对许多业务时间序列提供了大的、相对可预测的冲击,并且通常不遵循周期模式,因此它们的效果不能很好地通过平滑周期建模。⁴

一个很好的例子是复活节周末,在英国对车辆事故有非常明显的影响。然而,这个假期周末并不是每年都在同一个月份——有时它发生在三月,而在其他年份,我们得等到四月才能开始寻找巧克力兔子。

尽管我们可以相当提前知道复活节的日期(当然也知道过去的日期),但用上述季节性方法建模相当困难。因此,我们将采取不同的方法,而是统计每月的银行假期和工作日数量,这应该使我们也能够捕捉到像圣诞节这样更规律的假期的影响。

我们可以使用numpy来获取工作日:

# business days
begin = df.index.values.astype('datetime64[D]')
end = (df.index + pd.DateOffset(months = 1)).values.astype('datetime64[D]')

df['bus_days'] = np.busday_count(
    begindates = begin,
    enddates = end
)

# holidays
df['hols'] = pd.Series(df.index).apply(count_holidays).values

……但是需要holidays包和一点来自 StackOverflow 的帮助才能获得每月的假期数量:

import holidays

def count_holidays(u):
    hols = holidays.country_holidays('GB')
    days = pd.date_range(u, u + pd.DateOffset(months = 1))
    return sum(y in hols for y in days)

我们将忽略周末天数:因为可以从现有特征中推导出,使用周末天数可能会引入不必要的特征相关性。

就这样——特征工程完成,我们准备好进入建模阶段。

总结与闲聊

我们在这篇文章中涵盖了很多内容。按照惯例,我们将快速回顾一下,然后进行一些闲聊。

总结

在讨论了构建一个出色的预测模型的愿望之后,我们查看了英国道路交通事故数据。我们看到我们汇总的每月数据中存在明显的趋势和季节性,并知道我们需要创建各种特征来捕捉这些效应。

我们的特征工程过程从简单的预热开始——提取简单且直接可用的日期特征。

我们开始构建捕捉趋势的特征,并允许一些变化点,这些变化点被相对简单地处理。我们的逻辑可能有一定的价值,但我们承认可能需要一些微调。

我们使用了改进的傅里叶变换来建模季节性,创建了 12 组季节性特征。

最后,我们转向创建假期特征,选择专注于每月的工作日和假期天数。

循环特征编码

在构建新特征时,我们需要牢记两点——什么可能对我们的目标有预测作用以及它如何被机器解读。

一个很好的例子是年度月份,我们通常用整数映射来表示(即:一月 = 1,……,十二月 = 12)。我们可以相当确定地认为月份会对事故数量产生强烈的影响。但是如果我们将整数编码传递给模型,模型会将某一年的十二月视为与下一年的一月完全不同的时间点,即使它们是时间上相邻的!

我们通过周期特征编码解决这个问题,或者更具体地说,通过转换为极坐标。由于正弦和余弦变换本身都不能提供唯一的编码,我们使用两者的组合。

上面的代码没有显示任何周期编码的示例,但它在我的工作流程中使用,结果在模型中成为一个重要特征(见第二部分)。

先知特征

按类似的思路,我们的“先知特征”严重依赖于正弦和余弦变换。实际上,这些实际上是傅里叶变换。

细心的读者可能已经注意到先知特征的创建方式。在原始论文中,时间维度被重新基准化到某一点,每个观察值都被反映为在那之后的 t 时间单位。我们没有这样做,而是选择了另一种方法。如果我再次考虑这个问题,可能会考虑这一点。

滞后特征:房间里的大象

到目前为止,我实际上只是略过了滞后特征的使用。或者更准确地说,是缺乏使用。

使用目标量的先前值来预测目标量的当前或未来值——即使用“滞后”的时间值——在许多优秀的时间序列模型中是常见的。这是有充分理由的,因为它们通常是强有力的预测器。

我不愿这样做的核心在于模型的整体目的——即在预测方面表现良好。当我们使用滞后特征进行预测时,通常必须“推进”滞后特征,并从使用目标的实际值过渡到使用预测值。

具体来说,考虑一个使用一个滞后特征的模型——即,我们使用时间 t — 1 的目标值来预测时间 t 的目标值。我们希望使用该模型预测未来 3 步。

第一个预测(在时间 t + 1)将使用今天目标的值。由于目标值是已知的,所以这里没有问题,一切照常进行。

现在考虑时间 t + 2 的预测。我们需要时间 t + 1 的目标值才能使用我们的模型。当然,此时 真实 的目标值是未知的,因此我们使用 预测 的目标值来代替时间 t + 1。当涉及到预测时间 t + 3 时,我们将时间 t + 2 的预测推进,以此类推。从中可以看出,预测误差如何被融入预测中;早期的错误会被累积,因为不准确的预测被推进和重复使用。我不喜欢这样。

不使用滞后特征还有一个附带好处,那就是模型可解释性:我们被迫以不同的方式建模目标,并真正考虑(并建模!)结果的驱动因素。

这通常会导致与利益相关者进行更好的对话,因为解释预测开始听起来像是“长期趋势占预测的 X%而季节性占预测的 Y%”而不是“预测为 B 是因为前一个预测的值为 A”。

在继续之前,最后提一下滞后特征。我们并不局限于使用滞后的目标特征,因此虽然我们讨论了包括目标特征的前期值,我们也可以同样包括滞后的预测变量,且要有类似的警示和要求。

这并不是要全面否定使用滞后特征的做法——我相信在某些用例中这样做完全合理。使用的滞后数量和预测窗口的长度可能甚至意味着这不是问题。

变更点

让我们讨论变更点及其创建。

我以一种非常简单的方式创建了变更点,我确信还有许多改进我实现的方法。Prophet 可以说做得更好,通过在数据的前 80%中创建均匀间隔的变更点,但也有一些需要考虑的事项。

这减少了较近期虚假变更点对未来预测的影响——这是件好事。

但真正的变化有多少发生在均匀间隔的时间点上?如果变化真的以这种节奏发生,那是否更应视为某种季节性影响?是的,这是在挑剔。是的,这很重要。好吧,我继续。

虽然我们可以对历史变更点进行建模,但对未来变更点的建模要困难一些;有些情况下,未来的变更是已知的。

例如,英国推出了《民事责任法》,对英格兰和威尔士的个人伤害赔偿系统进行了修改。如果你像我一样定期建模颈部损伤的赔偿申请数量和成本(工作需要,而非兴趣),2021 年 6 月该法案的实施导致了一个相当严重的步骤变化。但由于事先已知,所以可以采取措施加以考虑。

这类变化需要逐案处理,务实和常识应当是首要考虑的。

交互作用

有人记录房间里的大象吗?再来一个——我们还没有构建捕捉预测变量之间交互作用的特征。

交互作用是非常有用的特征,可以捕捉各种预测变量之间的关系。当一个自变量对结果的影响取决于另一个自变量的值时,就会发生交互作用⁹。

在我们的案例中,使用交互作用的一个更有趣的动机是允许季节性随时间变化,因为我们目前假设——并建模——相同的季节性效果在二十多年里仍然有效。虽然没有明显的反证,但我们可以通过将时间与某些特征进行交互,潜在地挖掘更多的预测能力。

我们需要将这个添加到下次的待办事项列表中。

假期

最后,简单说一下假期的情况。

我们讨论了一些复活节周末可能带来的麻烦,并提出了一个简单的解决方案。

对特征工程的真正改进将是纳入学校假期。这些假期可能会对道路交通事故的数量产生影响,因此将是强有力的预测因素。

不幸的是,这并不容易,因为英国的学校假期时间略有不同,假期长度也不尽相同。也许我们可以发挥创造力,创建一个学校假期的分布,并将其分配到每个月——这是下次的另一个任务。

就这些了。我希望你们阅读这篇文章的乐趣与我写作的乐趣一样多。

一如既往,请告诉我你的想法——我非常感兴趣了解你在使用 Prophet 或以不同方式建模时间序列的经历。

正如我提到的,我将在即将发表的文章中处理建模问题——请留意。

期待下次见面。

参考资料

  1. GitHub — facebook/prophet: 用于生成高质量时间序列数据预测的工具,支持多季节性和线性或非线性增长。

  2. Vincent Warmerdam:用简单甚至线性模型取胜 | PyData London 2018 — YouTube

  3. roadtraffic.dft.gov.uk/downloads 依据 开放政府许可证 (nationalarchives.gov.uk) 使用

  4. 大规模预测 (peerj.com)

  5. 时间序列分解的综合指南 | Towards AI

  6. 趋势变更点 | Prophet (facebook.github.io)

  7. Civil-liability-act-2018-Q-and-A.docx (live.com)

  8. interaction.pdf (mcgill.ca)

  9. stackoverflow.com/a/59681727/11637704

BigQuery 的神奇生物及其使用时机

原文:towardsdatascience.com/fantastic-beasts-of-bigquery-and-when-to-use-them-13af9a17f3db

揭示 BigQuery Studio、DataFrames、生成 AI/AI 函数和 DuetAI 的特点

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

·发表于 Towards Data Science ·8 分钟阅读·2023 年 12 月 31 日

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

“BigQuery 是一个集数据库、商业智能、机器学习和生成 AI 功能于一体的 Google 服务。” [照片由 Korng Sok 提供,来源于 Unsplash]

了解更多关于 BigQuery 的内容

我最喜欢的书之一是 J.K. 罗琳 的“神奇动物在哪里”。这是一个关于魔法生物在非魔法世界中肆意活动的故事。它还讲述了魔法师非魔法师如何建立友谊以保护魔法生物。在这个任务中,主要的非魔法师角色发现了一个充满魔法的世界,并爱上了所有的挑战,渴望自己也是一名巫师。

作为一个非魔法师我从机械工程转向数据世界的过程最初充满了挑战。每当我进入一个新的数据领域时,我都会想:“要是我也是个巫师就好了。” 😉

当我第一次开始学习关于数据库 (DB) 和商业智能 (BI) 的知识时,我脑海中也有这个想法。

当我深入学习机器学习 (ML) 相关主题时,这个想法再次出现。

现在,我正试图在生成 AI (GenAI) 开发领域中运筹帷幄——你猜对了,这个想法再次陪伴着我。

即使在获得了数据库、商业智能和机器学习的经验之后,如果没有一个 Google 服务——BigQuery (BQ),生成 AI 领域对我来说将更加具有挑战性。

你知道为什么吗?

因为 BigQuery 提供了**“全能型”解决方案,涵盖了“DB-BI-ML-GenAI”**组合。或者,正如 Google 在其某次网络研讨会中所宣布的,它涵盖了“从数据到 AI”的功能 [1]。

我认为应该如何宣布:“BigQuery 的奇妙生物”

在我最喜欢的 BigQuery 特性——BQML——的基础上,Google 最近实现了更多变革性特性,使 BQ 更类似于分析开发环境,而不只是数据库环境。

这些新特性使数据专业人员能够进行端到端的分析任务无需在多个工具之间切换

关于端到端任务,我想到的是执行 探索性数据分析(EDA)、使用 SQL 或 Python 与 Spark 进行预测建模,以及通过使用生成性 AI 特性来创建新的见解。所有这些现在都可以在代码共同创建功能的帮助下完成。

BigQuery 生态系统的最新演变激励我写这篇文章,并展示将改变我们数据专业人员工作方式的新 BQ 进展,可能让我们感觉有点像Maj 人。😉

换句话说,本文旨在展示新 BQ 特性何时可以在分析工作流程中使用。

但在我们开始解释之前,我需要分享这些“奇妙生物”的名称:

  1. BigQuery Studio BigQuery DataFrames

  2. BigQuery GenAI 和 AI 函数

  3. DuetAI in BigQuery

现在让我们开始揭示它们的独特特征,并指出如何利用它们来提升你的表现。

BigQuery 的奇妙生物

为了暗示新 BQ 特性的目的,我制作了一个图示,展示了它们如何与知识数据发现过程(分析工作流程)对齐。

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

新的 BigQuery 特性与分析工作流程对齐 [图像由作者使用 draw.io]

从图中可以看出,在分析工作流程的基础上是DuetAI,这是一个 AI 编码辅助功能。除了编码支持,DuetAI 还是一个聊天机器人,你可以用它进行头脑风暴。

这意味着数据专业人员可以向聊天机器人提出与输入问题定义相关的不同问题(例如,我如何对数据集进行子集化或能否解释一个特定的函数)以及关于如何构建分析输出的建议(例如,我如何展示我的发现)。

在分析输入 → 输出 流程之间,其他特性也派上用场:

  • 第一阶段,即 数据准备和理解阶段,可以通过 BigQuery Studio 使用 BigQuery SQL(用于数据子集和处理)、GenAI/AI 函数(用于丰富数据集)、BigQuery DataFrames 和其他 Python 库(用于探索数据集)。

  • 第二阶段,即 数据建模和洞察综合阶段,可以在 BQ Studio 中将 BigQuery SQL 或 BQML 函数与 BigQuery DataFrames 一起使用(用于 BI/ML 模型创建),以获取所需的分析结果(描述性或预测性结果)。

现在我们将展示这些功能的神奇特性。

BigQuery Studio 和 DataFrames

在这里不要混淆 BigQuery StudioLooker Studio(前身为 Data Studio)。后者是一个自助式商业智能工具,而前者是一个新的协作工作空间,支持完整的分析工作流。

综上所述,BigQuery Studio 具有以下主要特性 [2]:

#1: 在统一的界面中支持多种语言和工具。

我的意思是,它简化了不同数据职业之间的工作,因为:

  • 数据工程(数据摄取和数据处理),

  • 数据分析(描述性统计/探索性数据分析),以及

  • 数据科学 任务(预测建模)可以在一个环境中完成,或者更好的是,在一个 笔记本 中完成。

BQ Studio 提供了 Colab 界面,使数据专业人员能够在一个笔记本文件中使用 SQL、Python、Spark 或自然语言进行 BigQuery 分析(与 DuetAI 结合使用)。此外,开发的笔记本可以通过 Vertex AI 访问,以进行机器学习工作流。

在数据摄取格式方面,它支持来自不同云平台的结构化、半结构化和非结构化格式。

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

Google 对 BigQuery Studio 笔记本的展示 [2]

#2: 通过连接到外部代码仓库来增强协作和版本控制。

我不得不说,这个特性是我一直以来希望的。尽管它(还)不支持所有 git 命令,但 BQ Studio 支持诸如持续集成/持续部署(CI/CD)、版本历史记录和数据代码资产的源代码管理等软件开发实践。简而言之,现在可以查看笔记本的历史记录并 还原到 特定笔记本版本创建分支。

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

Google 对版本控制 BQ Studio 功能的展示 [2]

#3: 在 BigQuery 中实施安全性和数据治理。

BQ Studio 实施安全性,因为它减少了在 BigQuery 之外共享数据的需要。换句话说,通过在服务之间采用统一的凭证管理,分析师可以例如访问 Vertex AI 基础模型来执行复杂的分析任务(如情感分析),而无需将数据共享给第三方工具。

此外,还有数据治理特性,包括数据血统跟踪、数据分析和实施质量约束。我只能说“对此表示赞同”。

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

Google 对数据治理功能的展示 [2]

总结上述特征,很明显 BigQuery Studio 是一个神奇的功能,因为它通过执行安全和治理措施,从数据摄取到数据建模的任务都能实现。

如果 Google 没有提供一个可以在 BigData Studio 笔记本中用于数据分析和建模的附加功能,BigQuery DataFrames,这个故事将不会完整。

通过安装bigframes包(类似于安装任何其他 Python 包使用pip),数据专业人士可以使用以下 Python API [3]:

  • bigframes.pandas 是一个用于分析和处理的pandas API,并且

  • bigframes.ml,即一个用于机器学习的scikit-learn API。

在机器学习主题上,我希望结束这一部分。

原因在于,在下一节中,我将详细介绍新的BigQuery ML函数。

BigQuery GenAI 和 AI 功能

正如前言中提到的,我非常喜欢 BQML 函数,因为它们使数据专业人士能够使用 SQL 语法创建预测模型。

除了已经很不错的 BQML 函数组合用于监督学习和无监督学习之外,Google 现在增加了我下一个最喜欢的函数:生成式 AIAI 功能

关于生成式 AI 函数ML.GENERATE_TEXT,我最近写了一篇博客来展示其特性。

## BigQuery 中的新生成 AI 功能

如何使用 BigQuery 的 GENERATE_TEXT 远程函数

[towardsdatascience.com

总之,该功能可以用于从存储在 BigQuery 数据集中的非结构化文本中创建新的见解。或者更准确地说,你可以用它来创建新类别属性(分类分析、情感分析或实体提取)、总结重写自然语言记录,以及生成广告创意概念

我会说,基于 SQL 的数据工程现在具备了下一个层次的能力。

除了这个魔法功能,其他强大的新 SQL 基础AI 功能包括:

  • ML.UNDERSTAND_TEXT — 帮助分析存储在 BigQuery 中的记录的文本的函数,并支持与 ML.GENERATE_TEXT 函数类似的功能。这意味着它支持实体、情感、分类和语法分析。

  • ML.TRANSLATE — 用于将文本从一种语言翻译成另一种语言的函数。

  • ML.PROCESS_DOCUMENT — 用于处理来自对象表(例如发票)的非结构化文档的函数。

  • ML.TRANSCRIBE — 用于从对象表中转录音频文件的函数。

  • ML.ANNOTATE_IMAGE — 用于从对象表中进行图像标注的函数。

尽管这些函数是用 SQL 编写的,但理解其查询结构和参数是正确使用的必备条件。为了加快学习曲线,一点编码帮助——DuetAI——会非常有用。

DuetAI 在 BigQuery 中

就这么简单:DuetAI的魔法功能可以帮助数据专业人员在 BQ 和 BQ Studio 环境中编写、改进和理解他们的多语言代码。

更准确地说,该功能具有以下特征:

#1: 在 BQ 环境或 BQ Studio 笔记本中从头创建查询或代码

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

使用 DuetAI 在 BigQuery 中的代码补全示例 [作者提供的图片]

#2: 解释 BQ 环境或 BQ Studio 笔记本中的查询和代码片段

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

使用 DuetAI 在 BigQuery 中的代码补全示例 [作者提供的图片]

#3: 提升 BQ 环境或 BQ Studio 笔记本中的代码质量

我的意思是,DuetAI 可以通过以下方式改进代码:

  • 语法纠正:它可以识别并建议修正语法错误

  • 逻辑改进:它可以建议替代的代码结构方式,从而提高整体效率和可读性。

  • 文档生成:它可以自动生成代码文档,使其更易于理解。

最后,通过这个魔法特性,我将结束新 BigQuery “神奇动物”的部分和展示。

无限可能的世界

在书籍“神奇动物在哪里”中,J.K. 罗琳展示了当 MajiNo-Maj 人们了解魔法生物的积极特质时,它们变得不再那么可怕。同样,在这篇博客文章中,我想展示 BigQuery 的新奇功能,并指出它们在不同分析层面上的积极特质。

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

作者使用DALL-E扩展在 ChatGPT 中创建的三只 BigQuery 野兽的插图(野兽的数量正确,但名字的数量不正确 😉)

我的目标是展示新功能如何在完整的分析工作流中支持你并简化工作,无论你是数据工程师、分析师还是科学家。此外,我还想指出它们如何增强具有不同数据背景的团队成员之间的协作。

希望你能亲身体验魔法,并了解更多关于“BigQuery 的神奇动物”的知识。

学习愉快!

感谢阅读我的帖子。请关注更多故事,MediumLinkedin

知识资源

[1] Google Cloud 网络研讨会:“Cloud OnBoard:使用 BigQuery 和 Vertex AI 从数据到 AI”,访问日期:2023 年 12 月 10 日,cloudonair.withgoogle.com/events/cloud-onboard-data-to-ai

[2] Google Cloud 博客:“宣布 BigQuery Studio——一个协作分析工作区,加速数据到 AI 的工作流”,访问日期:2023 年 12 月 11 日,cloudonair.withgoogle.com/events/cloud-onboard-data-to-AI

[3] Google Cloud 文档:“BigQuery DataFrames”,访问日期:2023 年 12 月 11 日,cloud.google.com/python/docs/reference/bigframes/latest

AWS SageMaker 中的快速和可扩展超参数调优与交叉验证

原文:towardsdatascience.com/fast-and-scalable-hyperparameter-tuning-and-cross-validation-in-aws-sagemaker-d2b4095412eb

使用 SageMaker 管理的 Warm Pools

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

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

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

图片由 SpaceX 提供,来源于 Unsplash

本文分享了一种配方,以 提高 60% 的速度,通过 SageMaker 管道利用 SageMaker 管理的 Warm Pools 进行超参数调优与交叉验证。通过使用 Warm Pools,一个包含 120 个顺序作业的调优步骤的运行时间减少了 从 10 小时到 4 小时

提升和评估机器学习模型的性能通常需要多种因素。超参数调优和交叉验证就是两个这样的因素。前者找到模型的最佳版本,而后者估计模型如何推广到未见数据。这些步骤结合起来,带来了计算挑战,因为它们需要多次训练和验证模型,可能是并行的和/或顺序的。

本文介绍的内容…

  • 什么是 Warm Pools 以及如何利用它们加速超参数调优与交叉验证。

  • 如何设计一个包含处理、调优、训练和 Lambda 步骤的生产级 SageMaker 流水线。

我们将考虑用于超参数调优的贝叶斯优化,它利用已测试的超参数组合的评分来选择下一轮测试的超参数集。我们将使用k-折交叉验证来评分每个超参数组合,分割如下:

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

𝑘-fold 交叉验证策略。

完整数据集被划分为𝑘个验证折叠,模型在𝑘-1 个折叠上训练,并在相应的保留折叠上验证。总体得分是每个验证折叠得到的个别验证得分的平均值。

故事情节:

1. 什么是温暖池?

2. 端到端 SageMaker 管道

3. 调优步骤内部发生了什么?

4. 使用温暖池有什么好处?

5. 总结

1. 什么是温暖池?

每当在 AWS 中启动训练任务时,预配实例在执行训练脚本之前大约需要 3 分钟来引导。这种启动时间在顺序运行多个任务时会累积,这在使用贝叶斯优化策略进行超参数调优时尤其明显。在这种情况下,数十个甚至数百个任务被顺序运行,导致总时间显著增加,这可能与脚本的实际执行时间相当,甚至更高。

SageMaker 托管温暖池使得在任务完成后保留训练基础设施成为可能,从而为每个后续任务节省实例启动时间。

启用温暖池是直接的。你只需在创建 SageMaker 训练任务时添加一个额外的参数(keep_alive_period_in_seconds):

estimator = Estimator(
    entry_point='training.py',
    keep_alive_period_in_seconds=600,
    ...
)

如果你想了解更多关于 SageMaker 托管温暖池的信息,这里是文档:

[## 使用 SageMaker 托管温暖池进行训练

SageMaker 托管温暖池使你能够在训练任务完成后保留和重用预配的基础设施…

docs.aws.amazon.com

既然我们了解了什么是温暖池,在第二部分中,我们将深入探讨如何利用它们来加速包含交叉验证的 SageMaker 管道的整体运行时间。

2. 端到端 SageMaker 管道

下图展示了一个端到端的 SageMaker 管道,该管道通过交叉验证进行超参数调优。

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

端到端 SageMaker 管道的架构图。

我们将使用SageMaker Python SDK来创建管道,这是一个开源库,简化了在 AWS SageMaker 中训练、调优和部署机器学习模型的过程。图中的管道步骤总结如下:

  1. 数据预处理(ProcessingStep)— 数据从源中检索,转化,并划分为 k 个交叉验证折叠。一个额外的完整数据集被保存用于最终训练。

  2. 超参数调优与交叉验证(TuningStep)— 这是我们将重点关注的步骤。它找到在验证折中实现最佳平均性能的超参数组合。

  3. 最佳超参数检索(LambdaStep)— 触发一个Lambda函数,通过访问超参数调优作业的结果来检索最佳超参数集,使用 Boto3

  4. 最终训练(TrainingStep)— 使用最佳超参数在完整数据集 train_full.csv 上训练模型。

  5. 模型注册(ModelStep)— 将最终训练好的模型注册到 SageMaker 模型注册表中。

  6. 推理(TransformStep)— 使用注册的模型生成预测结果。

请在SageMaker 开发者指南中查找有关如何实现这些步骤的详细文档。

3. 调优步骤内部发生了什么?

现在我们来深入探讨管道步骤 2,该步骤迭代地并行和顺序地尝试和交叉验证多个超参数组合。该解决方案在下图中表示:

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

超参数调优与交叉验证步骤的架构图。

该解决方案依赖于 SageMaker 自动模型调优来创建和协调测试多个超参数组合的训练作业。可以使用SageMaker Python SDK中的HyperparameterTuner启动自动模型调优作业。它创建了MxN个超参数调优训练作业,其中M个作业在N个顺序轮次中并行运行,逐步搜索最佳超参数。每个作业启动并监控一组K交叉验证作业。在每个调优轮次中,MxK个实例会保留到下一轮。在随后的轮次中,没有实例启动时间。

SageMaker 的 HyperparameterTuner 已经利用了 Warm Pools,正如在AWS 新闻博客中所宣布的那样。然而,每个调优作业中创建的交叉验证训练作业 — 交叉验证特定的超参数组合 — 需要手动创建和监控且配置的 实例不会保留在 Warm Pool 中。每个超参数调优训练作业仅在所有基础的交叉验证训练作业完成后才会完成。

为了使上述架构生效并为所有训练作业启用 Warm Pools,我们需要创建三个主要脚本:pipeline.pycross_validation.pytraining.py

  • **pipeline.py** 脚本 — 定义了在 第二部分 中描述的 SageMaker Pipeline 步骤,包括 SageMaker 的 HyperparameterTuner
#pipeline.py script
...
# Steps 2 to 5

tuner = HyperparameterTuner(
    estimator=estimator,
    metric_definitions=[
        {
          "Name": "training:score",
          "Regex": "average model training score:(.*?);"
        },
        {
          "Name": "validation:score",
          "Regex": "average model validation score:(.*?);"
        }
    ],
    objective_metric_name="validation:score",
    strategy="Bayesian",
    max_jobs=max_jobs, # M x N
    max_parallel_jobs=max_parallel_jobs # M
)

# Step 2 - Hyperparameter tuning With cross-validation step
step_tune = TuningStep(
    name="tuning-step",
    step_args=tuner.fit({
        "train": "<s3-path-to-training-folds>",
        "validation": "<s3-path-to-validation-folds>"
    })
)

# Step 3 - Optimal hyperparameter retrieval step
step_lambda = LambdaStep(
    name="get-optimal-hyperparameters-step",
    lambda_func=lambda_get_optimal_hyperparameters,
    inputs={
        "best_training_job_name": step_tune.properties.BestTrainingJob.TrainingJobName,
    },
    outputs=[
        LambdaOutput(output_name="hyperparameter_a"),
        LambdaOutput(output_name="hyperparameter_b"),
        LambdaOutput(output_name="hyperparameter_c")
    ]
)

# Step 4 - Final training step
step_train = TrainingStep(
    name="final-training-step",
    step_args=estimator.fit({"train": "<s3-path-to-full-training-set>"})
)

model = Model(
    model_data=step_train.properties.ModelArtifacts.S3ModelArtifacts,
    ...
)

# Step 5 - Model registration step
step_model_registration = ModelStep(
    name="model-registration-step",
    step_args=model.register(.)
)
  • **cross_validation.py** 脚本——作为 SageMaker 的 HyperparameterTuner 的入口点。它启动多个交叉验证训练任务。在调用 SageMaker 训练任务 API 时,必须在此脚本中指定 keep_alive_period_in_seconds 参数。该脚本计算并记录所有验证折的平均验证得分。记录这些值使得 HyperparameterTuner 可以通过 Regex 轻松读取该指标(如上述代码片段所示)。该指标将标记到每个超参数组合中。

提示: 在调用创建和监控训练任务的 SageMaker API 之间添加几秒钟的小延迟,以防止“超出速率”错误,如示例所示:

#cross_validation.py script

import time
...

training_jobs = []
for fold_index in range(number_of_folds):

    # Create cross-validation training jobs (one per fold)
    job = train_model(
        training_data="<training-data-s3-path>"
        validation_data="<validation-data-s3-path>"
        fold_index=fold_index,
        hyperparameters={
            "hyperparameter_a": "<value-of-hyperparameter-a>",
            "hyperparameter_b": "<value-of-hyperparameter-b>",
            "hyperparameter_c": "<value-of-hyperparameter-c>"
    })
    training_jobs.append(job)

    # Add delay to prevent Rate Exceeded error. 
    time.sleep(5)
...

提示: 启动 SageMaker 训练任务时禁用调试器分析器。这些分析器实例将与训练实例数量相同,并且可能显著增加总体成本。你可以通过在 Estimator 定义中简单地设置 disable_profiler=True 来实现。

  • **training.py**脚本——在给定的输入训练集上训练模型。交叉验证的超参数作为此脚本的参数传递。

提示: 编写一个通用的 *training.py* 脚本,并在交叉验证集上训练模型以及在整个训练集上使用最佳超参数训练最终模型时重用它。

要控制每个并行交叉验证任务集,以及为每个特定超参数组合计算最终验证指标,需要在 cross_validation.py 脚本中实现几个自定义函数。这个示例 提供了很好的灵感,尽管它未启用 Warm Pools 或 Lambda。

总共创建了多少任务?

M x N x (K+1) 任务。为什么?

  • M x N 超参数调整训练任务——M 个并行和 N 个串行——匹配超参数组合的数量。

  • 每个超参数调整训练任务的 K 个并行交叉验证任务 + 1(超参数调整训练任务本身)。

如果我们有 5 个验证折,运行 4 个超参数调整训练任务并行和 120 个串行,那么 任务总数将是 2880

重要: 确保你拥有所使用的实例类型所需的所有服务配额。查看 AWS 指南以了解如何为 Warm Pools自动模型调整 设置这些配额。

4. 我们从使用 Warm Pools 中得到什么?

假设我们想要运行 N=120 个顺序训练任务,并且实例的启动时间为 3 分钟,训练时间为 2 分钟(每个任务 5 分钟)。这意味着总运行时间大约为:

  • 没有 Warm Pools:5 分钟 x 120 个任务 = 10 小时

  • Warm Pools:5 分钟 x 1 个任务 + 2 分钟 x 119 个任务 ≈ 4 小时

这意味着使用 Warm Pools 过程的时间减少了 60%!

5. 总结

在这篇文章中,我展示了如何利用 Warm Pools 显著加快 SageMaker Pipelines 中的超参数调优。Warm Pools 是 SageMaker 的一个很棒的功能,它不仅使生产流水线更加高效,还加快了实验的迭代。目前,SageMaker 管理的 Warm Pools 已经集成到 SageMaker Training 中,但尚未集成到 SageMaker Processing。

— 若昂·佩雷拉

感谢阅读。希望这篇文章能帮助你在 SageMaker 中扩展超参数调优。如果你想阅读我未来的文章,请 关注我。非常感谢反馈!如果有任何问题,请在下方留言或直接联系我 通过电子邮件 或在 LinkedIn上联系我。

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

Python 到 SQL — 我现在可以以 20 倍的速度加载数据

原文:towardsdatascience.com/fast-load-data-to-sql-from-python-2d67aea946c0

上传大量数据的好方法、坏方法和丑陋的方法

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

·发表于 Towards Data Science ·6 分钟阅读·2023 年 3 月 20 日

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

Matheo JBT 摄影,Unsplash

速度很重要!在数据管道中,和在其他地方一样。

处理大量数据集是大多数数据专业人员的日常工作。如果它们流入数据库或仓库,这不会成为问题。

但有时我们需要上传足够重的批量数据,以至于使我们的工作站无用数小时。散步一下,喝点水,对你有好处!

但如果您真的想缩短这个任务,我们需要最优的方法将数据加载到数据库中。

如果这是一个预格式化的文件,我会更倾向于使用客户端库来完成。例如,以下 shell 命令可以将您的 CSV 文件上传到远程 Postgres 数据库。

[## 用 Streamlit 在几分钟内创建 GPT3 驱动的应用程序

学习构建智能应用程序,而不必过多担心软件开发。

levelup.gitconnected.com [## Python 网络抓取的宁静交响曲 — 3 移动篇章

在 Python 中进行网络抓取的最简单、最灵活和最全面的方法

levelup.gitconnected.com

psql \
  -h $hostname -d $dbname -U $username \
  -c "\copy mytable (column1, column2)  from '/path/to/local/file.csv' with delimiter as ','"

但这在我的情况中很少发生。

由于 Python 是我主要的编程语言,我必须通过 Python 上传它们,可能需要进行一些预处理。因此,我做了一个小实验来查看最快的方法。

我找到了最快的方法,但这不是我平常使用的。

## 如何使用 GitHub Actions 构建简单的 ETL 管道

ETL 不必复杂。如果是这样,使用 GitHub Actions。

[towardsdatascience.com

丑陋的:如何不上传数据

尽管我现在确信我不应使用这种方法,但我认为这可能最初有帮助。

这是我们所做的。我有一个约 500MB 的磁盘数据集。首先,我尝试使用 psycopg2 模块中的插入命令来加载它。

import psycopg2
import csv
import time

def timing_decorator(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        print(f"Function {func.__name__} took {end_time - start_time} seconds to run.")
        return result
    return wrapper

# Establish a connection to the PostgreSQL database
conn = psycopg2.connect(
    host="localhost",
    database="playground",
    user="<your-user-name>",
    password="<your-password>",
)

# Define the path to the CSV file and the name of the table to load it into
csv_file_path = "./data.csv"
table_name = "temp_table"

@timing_decorator
def load_csv_with_insert():

    # Create a cursor object
    cur = conn.cursor()

    # Open the CSV file and read its contents
    with open(csv_file_path, 'r') as f:
        csv_reader = csv.reader(f)
        next(csv_reader) # skip header row

        # Define the query to insert data into the table
        insert_query = f"INSERT INTO {table_name} VALUES ({','.join(['%s']*len(next(csv_reader)))})"

        # Iterate over the CSV rows and execute the insert query for each row
        for row in csv_reader:
            cur.execute(insert_query, row)

        # Commit the changes to the database
        conn.commit()

    # Close the cursor and connection
    cur.close()

load_csv_with_insert()
conn.close()

我使用计时装饰器来测量加载所需的时间。这是我五个最喜欢的 Python 装饰器之一。

## 我在几乎所有数据科学项目中使用的 5 个 Python 装饰器

装饰器为从缓存到发送通知的一切提供了一种新的便利方式。

[towardsdatascience.com

此外,我故意将数据库保存在本地。因此,带宽不再是考虑因素。

早些时候,我真的认为这可能是加载数据的最快方式。这是因为我们使用游标同时插入数据并提交。但整个过程花费了这么多时间:

Function load_csv_with_insert took 1046.109834432602 seconds to run.

好吧,我们如何在没有参考的情况下知道这太慢了?

让我们尝试一下最流行的方法。

## 这 5 种 SQL 技巧涵盖了约 80%的真实项目

加速你的 SQL 学习曲线。

[towardsdatascience.com

糟糕的:使用 Pandas 的 to_sql 加载大规模数据集。

如果你经常使用 Pandas 及其to_sql API,这可能会让你惊讶。

我一直在使用它,并且继续使用。但这仍然不是处理大规模数据集的最佳方法。

这是我的代码。我使用的是与之前相同的数据库和 CSV。在开始加载数据集之前,我已经截断了表格。

import pandas as pd
from sqlalchemy import create_engine
import time

...

conn = create_engine("postgresql+psycopg2://thuwa:Flora1990@localhost:5432/playground")

@timing_decorator
def load_csv_with_pandas():
    df = pd.read_csv(csv_file_path)

    df.to_sql(table_name, conn, if_exists="append", chunksize=100, index=False)

load_csv_with_pandas()

在上述代码中,我没有使用流行的method参数。将它们设置为 [multi](https://pandas.pydata.org/docs/user_guide/io.html#io-sql-method) 将加速数据加载到分析数据库 中,如 Redshift。但在我们的案例中,我们使用的是事务性数据库。

回到重点,这种方法花费了我们 376 秒来加载数据。

Function load_csv_with_pandas took 376.70790338516235 seconds to run.

这使得 Pandas 比使用游标加载数据要好得多,效率提升约 3 倍。

那么是什么让它不是最快的呢?毕竟,这是我最喜欢和最常用的方法。

好处:使用 COPY 方法

有一种本地方式可以将文本数据上传到 SQL 表中。像 psycopg2 这样的库可以直接提供这种功能。

是的,我们可以直接将文件复制到 Python 中的 SQL 表。

...

@timing_decorator
def load_csv_with_copy():
    # Create a cursor object
    cur = conn.cursor()

    # Use the COPY command to load the CSV file into the table
    with open(csv_file_path, "r") as f:
        next(f)  # skip header row
        cur.copy_from(f, table_name, sep=",")
        conn.commit()

    # Close the cursor and connection
    cur.close()

游标中的 copy_from 方法使用 SQL 客户端 API 中的 COPY 方法,并直接将文件上传到 SQL。我们还传递了额外的参数。

在这种情况下,我们指定用逗号分隔列。我们还使用 next 方法跳过第一行,即表头。

结果如下:

Function load_csv_with_copy took 50.60594058036804 seconds to run.

惊人的 50 秒 — 比使用游标快 20 倍,比 to_sql Pandas 方法快近 8 倍。

但等一下!

我提到我使用 Python,因为通常会进行数据预处理。其他两种方法很直接。但我们如何在 Python 运行时上传现有数据集呢?

## 初级开发者编写多页 SQL 查询;高级开发者使用窗口函数

在记录的上下文中执行计算的优雅方式

towardsdatascience.com

使用 COPY 写入内存中存在的数据集

在这里我们可以从缓冲区方法中受益。下面的代码可以快速加载现有 Pandas 数据框到 SQL 数据库。

import io

...

@timing_decorator
def load_dataframe_with_copy(df):
    # Create a cursor object
    cur = conn.cursor()

    # Convert the DataFrame to a CSV file-like object
    csv_buffer = io.StringIO()
    df.to_csv(csv_buffer, index=False, header=False)

    # Use the COPY command to load the CSV file into the table
    csv_buffer.seek(0)
    cur.copy_from(csv_buffer, table_name, sep=",")
    conn.commit()

    # Close the cursor and connection
    cur.close()

df = pd.read_csv(csv_file_path)

# Do data processing on df

load_dataframe_with_copy(df)

这段代码创建了一个名为 csv_bufferStringIO 对象,它是一个类似于 CSV 文件的文件类对象。使用 to_csv() 方法将 DataFrame 写入该对象,index=Falseheader=False 以排除 CSV 输出中的索引和标题。

然后在 csv_buffer 对象上调用 seek(0) 方法,将文件指针移动回文件类对象的开头。

结论

处理大数据集与处理普通数据集不同。一个具有挑战性的任务是将这些巨大的数据集加载到数据库中。

除非不需要预处理,我几乎总是使用 Pandas 的 to_sql 方法。然而,我的实验表明,这对大数据集来说不是最好的方法。

COPY 方法是我见过的最快的方法。虽然我建议在受控环境中批量加载时使用这个方法,但对于日常任务,to_sql 提供了一个很棒的接口来调整多个上传行为。

感谢阅读,朋友!如果你喜欢我的文章,让我们在 LinkedInTwitterMedium 保持联系。

还不是 Medium 会员?请使用这个链接来成为会员,因为这样你无需额外付费,我可以通过推荐你获得少量佣金。

使用 Polars 进行快速字符串处理——诈骗邮件数据集

原文:towardsdatascience.com/fast-string-processing-with-polars-scam-emails-dataset-fcf7054a929a

使用内置的 Polars 字符串表达式在毫秒级别清理、处理和标记文本

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

·发布于Towards Data Science ·10 分钟阅读·2023 年 5 月 28 日

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

图片由Stephen Phillips - Hostreviews.co.uk提供,来自Unsplash

引言

随着大型语言模型(LLMs)的广泛采用,我们可能会觉得已经不再需要手动清理和处理文本数据。不幸的是,我和其他 NLP 从业者可以证明,情况并非如此。在每一个 NLP 复杂性的阶段——从基础文本分析到机器学习和 LLMs——都需要干净的文本数据。本文将展示如何使用 Polars 显著加快这一繁琐和乏味的过程。

Polars

Polars是一个极其快速的 Rust 编写的数据框架库,处理字符串非常高效(得益于其 Arrow 后端)。Polars 以Utf8格式存储字符串,并使用Arrow后端,使得字符串遍历缓存最优且可预测。此外,它在str命名空间下暴露了许多内置的字符串操作,这使得字符串操作可以并行处理。这两个因素使得处理字符串变得极其简单和快速。

这个库与 Pandas 有很多相似的语法,但也有很多需要适应的细节。本文将引导你了解字符串操作,但为了全面了解,我强烈推荐这个“入门指南”,它会给你一个很好的库概览。

设置

你可以在这个GitHub 仓库中找到所有代码,所以如果你想跟着编码,记得拉取它(别忘了⭐)。为了使这篇文章更实用有趣,我将展示如何清理一个小型诈骗电子邮件数据集,该数据集可以在Kaggle找到(许可证CC BY-SA 4.0)。Polars 可以通过 pip 安装——pip install polars,推荐的 Python 版本是3.10

文本处理管道

这个管道的目标是将原始文本文件解析为一个数据框,以便用于进一步的分析/建模。以下是将要实现的整体步骤:

  1. 读取文本数据

  2. 提取相关字段(例如,发件人电子邮件、对象、文本等)

  3. 从这些字段中提取有用的特征(例如,长度、数字比例等)

  4. 预处理文本以进行进一步分析

  5. 执行一些基本的文本分析

言归正传,让我们开始吧!

读取数据

假设保存了包含电子邮件的文本文件为fraudulent_emails.txt,以下是用于读取它们的函数:

def load_emails_txt(path: str, split_str: str = "From r  ") -> list[str]:
    with open(path, "r", encoding="utf-8", errors="ignore") as file:
        text = file.read()

    emails = text.split(split_str)

    return emails

如果你查看文本数据,你会发现电子邮件有两个主要部分

  • 元数据(以From r开头),包含发件人、主题等。

  • 电子邮件文本(从Status: OStatus: RO后开始)

我使用第一个模式将连续的文本文件拆分为电子邮件列表。总体而言,我们应该能够读取 3977 封电子邮件,并将其放入 Polars 数据框中以进行进一步分析。

emails = load_emails_txt("fradulent_emails.txt")
emails_pl = pl.DataFrame({"emails": emails})

print(len(emails))
>>> 3977

提取相关字段

现在,棘手的部分开始了。我们如何从这堆混乱的文本数据中提取相关字段?不幸的是,答案是正则表达式。

发件人和主题

进一步检查元数据(如下所示)你会发现它有From:Subject:字段,这对我们非常有用。

From r  Wed Oct 30 21:41:56 2002
Return-Path: <james_ngola2002@maktoob.com>
X-Sieve: cmu-sieve 2.0
Return-Path: <james_ngola2002@maktoob.com>
Message-Id: <200210310241.g9V2fNm6028281@cs.CU>
From: "MR. JAMES NGOLA." <james_ngola2002@maktoob.com>
Reply-To: james_ngola2002@maktoob.com
To: webmaster@aclweb.org
Date: Thu, 31 Oct 2002 02:38:20 +0000
Subject: URGENT BUSINESS ASSISTANCE AND PARTNERSHIP
X-Mailer: Microsoft Outlook Express 5.00.2919.6900 DM
MIME-Version: 1.0
Content-Type: text/plain; charset="us-ascii"
Content-Transfer-Encoding: 8bit
Status: O

如果你继续滚动电子邮件,你会发现From:字段有几种格式。你看到的第一个格式是包含姓名和电子邮件的格式。第二种格式仅包含电子邮件,例如From: 123@abc.comFrom: “123@abc.com”。考虑到这一点,我们需要三个正则表达式模式——一个用于主题,两个用于发件人(姓名和电子邮件,以及仅电子邮件)。

email_pattern = r"From:\s*([^<\n\s]+)"
subject_pattern = r"Subject:\s*(.*)"
name_email_pattern = r'From:\s*"?([^"<]+)"?\s*<([^>]+)>'

Polars 有一个str.extract方法,可以将上述模式与我们的文本进行比较,并(你猜对了)提取匹配的组。以下是如何将其应用于emails_pl数据框。

emails_pl = emails_pl.with_columns(
    # Extract the first match group as email
    pl.col("emails").str.extract(name_email_pattern, 1).alias("sender_name"),
    # Extract the second match group as email
    pl.col("emails").str.extract(name_email_pattern, 2).alias("sender_email"),
    # Extract the subject 
    pl.col("emails").str.extract(subject_pattern, 1).alias("subject"),
).with_columns(
    # In cases where we didn't extract email
    pl.when(pl.col("sender_email").is_null())
    # Try another pattern (just email)
    .then(pl.col("emails").str.extract(email_pattern, 1))
    # If we do have an email, do nothing
    .otherwise(pl.col("sender_email"))
    .alias("sender_email")
)

如你所见,除了str.extract之外,我们还使用了pl.when().then().otherwise()表达式(Polars 的 if/else 版本)来处理仅存在于第二个电子邮件模式的情况。如果你打印出结果,你会发现大多数情况下它应该正确地工作(而且速度极快)。我们现在有了sender_namesender_emailsubject字段用于分析。

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

Polars 数据框样本。作者截图。

电子邮件文本

如上所述,实际的电子邮件文本从 Status: O(已打开)或 Status: RO(已读并已打开)之后开始,这意味着我们可以利用这个模式将电子邮件拆分为“元数据”和“文本”部分。下面你可以看到提取所需字段的三个步骤以及执行它们的相应 Polars 方法。

  1. Status: RO 替换为 Status: O,以便我们只有一个“拆分”模式 — 使用 str.replace

  2. 按照 Status: O 拆分实际字符串 — 使用 str.split

  3. 获取结果列表的第二个元素(文本) — 使用 arr.get(1)

emails_pl = emails_pl.with_columns(
    # Apply operations to the emails column
    pl.col("emails")
    # Make these two statuses the same
    .str.replace("Status: RO", "Status: O", literal=True)
    # Split using the status string
    .str.split("Status: O")
    # Get the second element
    .arr.get(1)
    # Rename the field
    .alias("email_text")
)

看!我们在短短几毫秒内提取了重要字段。让我们将这些功能放到一个连贯的函数中,稍后在管道中使用。

def extract_fields(emails: pl.DataFrame) -> pl.DataFrame:
    email_pattern = r"From:\s*([^<\n\s]+)"
    subject_pattern = r"Subject:\s*(.*)"
    name_email_pattern = r'From:\s*"?([^"<]+)"?\s*<([^>]+)>'

    emails = (
        emails.with_columns(
            pl.col("emails").str.extract(name_email_pattern, 2).alias("sender_email"),
            pl.col("emails").str.extract(name_email_pattern, 1).alias("sender_name"),
            pl.col("emails").str.extract(subject_pattern, 1).alias("subject"),
        )
        .with_columns(
            pl.when(pl.col("sender_email").is_null())
            .then(pl.col("emails").str.extract(email_pattern, 1))
            .otherwise(pl.col("sender_email"))
            .alias("sender_email")
        )
        .with_columns(
            pl.col("emails")
            .str.replace("Status: RO", "Status: O", literal=True)
            .str.split("Status: O")
            .arr.get(1)
            .alias("email_text")
        )
    )

    return emails

现在,我们可以继续进行特征生成部分。

特征工程

根据个人经验,诈骗电子邮件往往非常详细且长(因为骗子试图赢得你的信任),所以电子邮件的字符长度会非常有信息量。此外,它们大量使用感叹号和数字,因此计算电子邮件中非字符的比例也可能很有用。最后,骗子喜欢使用大写字母,所以我们也来计算大写字母的比例。当然,我们还可以创建更多的特征,但为了不让这篇文章过长,我们就专注于这两个特征。

第一个特征可以通过内置的 str.n_chars() 函数非常容易地创建。其他两个特征可以使用正则表达式和 str.count_match() 计算。下面你可以找到计算这三个特征的函数。与之前的函数类似,它使用 with_columns() 子句来保留旧特征并在其上创建新的特征。

def email_features(data: pl.DataFrame, col: str) -> pl.DataFrame:
    data = data.with_columns(
        pl.col(col).str.n_chars().alias(f"{col}_length"),
    ).with_columns(
        (pl.col(col).str.count_match(r"[A-Z]") / pl.col(f"{col}_length")).alias(
            f"{col}_percent_capital"
        ),
        (pl.col(col).str.count_match(r"[^A-Za-z ]") / pl.col(f"{col}_length")).alias(
            f"{col}_percent_digits"
        ),
    )

    return data

文本清理

如果你打印出我们提取的一些电子邮件,你会注意到一些需要清理的内容。例如:

  • 一些电子邮件中仍然存在 HTML 标签

  • 使用了很多非字母字符

  • 一些电子邮件是大写字母,一些是小写字母,还有一些是混合的

与上述相同,我们将使用正则表达式来清理数据。然而,现在选择的方法是 str.replace_all,因为我们想要替换所有匹配的实例,而不仅仅是第一个。此外,我们将使用 str.to_lowercase() 将所有文本转换为小写。

emails_pl = emails_pl.with_columns(
    # Apply operations to the emails text column
    pl.col("email_text")
    # Remove all the data in <..> (HTML tags)
    .str.replace_all(r"<.*?>", "")
    # Replace non-alphabetic characters (except whitespace) in text
    .str.replace_all(r"[^a-zA-Z\s]+", " ")
    # Replace multiple whitespaces with one whitespace 
    # We need to do this because of the previous cleaning step
    .str.replace_all(r"\s+", " ")
    # Make all text lowercase
    .str.to_lowercase()
    # Keep the field's name
    .keep_name()
)

现在,让我们将这串操作重构成一个函数,以便它可以应用于其他感兴趣的列。

def email_clean(
    data: pl.DataFrame, col: str, new_col_name: str | None = None
) -> pl.DataFrame:
    data = data.with_columns(
        pl.col(col)
        .str.replace_all(r"<.*?>", " ")
        .str.replace_all(r"[^a-zA-Z\s]+", " ")
        .str.replace_all(r"\s+", " ")
        .str.to_lowercase()
        .alias(new_col_name if new_col_name is not None else col)
    )

    return data

文本标记化

作为预处理管道的最后一步,我们将对文本进行标记化。标记化将使用已经熟悉的方法 str.split(),其中分隔符将指定为空格。

emails_pl = emails_pl.with_columns(
  pl.col("email_text").str.split(" ").alias("email_text_tokenised")
)

再次,将这段代码放入一个函数中,以便在最终的管道中使用。

def tokenise_text(data: pl.DataFrame, col: str, split_token: str = " ") -> pl.DataFrame:
    data = data.with_columns(pl.col(col).str.split(split_token).alias(f"{col}_tokenised"))

return data

去除停用词

如果你以前处理过文本数据,你会知道去除停用词是处理标记化文本的关键步骤。去除这些词可以让我们将分析集中在文本的重要部分。

为了删除这些词,我们首先需要定义它们。在这里,我将使用来自nltk库的默认停用词集以及一组与 HTML 相关的词汇。

stops = set(
    stopwords.words("english")
    + ["", "nbsp", "content", "type", "text", "charset", "iso", "qzsoft"]
)

现在,我们需要找出这些词是否存在于分词数组中,如果存在,我们需要将它们删除。为此,我们需要使用arr.eval方法,因为它允许我们对分词列表的每个元素运行 Polars 表达式(例如 .is_in)。确保阅读下面的评论以理解每一行的作用,因为这部分代码比较复杂。

emails_pl = emails_pl.with_columns(
    # Apply to the tokenised column (it's a list)
    pl.col("email_text_tokenised")
    # For every element, check if it's not in a stopwords list and only then return it
    .arr.eval(
            pl.when(
                (~pl.element().is_in(stopwords)) & (pl.element().str.n_chars() > 2)
            ).then(pl.element())
        )
    # For every element of a new list, drop nulls (previously items that were in stopwords list)
    .arr.eval(pl.element().drop_nulls())
    .keep_name()
)

和往常一样,让我们将这段代码重构为一个函数,用于我们的最终管道。

def remove_stopwords(
    data: pl.DataFrame, stopwords: set | list, col: str
) -> pl.DataFrame:
    data = data.with_columns(
        pl.col(col)
        .arr.eval(pl.when(~pl.element().is_in(stopwords)).then(pl.element()))
        .arr.eval(pl.element().drop_nulls())
    )
    return data

虽然这个模式可能看起来相当复杂,但使用预定义的strarr表达式来优化性能是非常值得的。

完整管道

到目前为止,我们已经定义了预处理函数,并了解了它们如何应用于单个列。Polars 提供了一个非常实用的pipe方法,允许我们将指定为函数的 Polars 操作串联起来。这就是最终管道的样子:

emails = load_emails_txt("fradulent_emails.txt")
emails_pl = pl.DataFrame({"emails": emails})

emails_pl = (
    emails_pl.pipe(extract_fields)
    .pipe(email_features, "email_text")
    .pipe(email_features, "sender_email")
    .pipe(email_features, "subject")
    .pipe(email_clean, "email_text")
    .pipe(email_clean, "sender_name")
    .pipe(email_clean, "subject")
    .pipe(tokenise_text, "email_text")
    .pipe(tokenise_text, "subject")
    .pipe(remove_stopwords, stops, "email_text_tokenised")
    .pipe(remove_stopwords, stops, "subject_tokenised")
)

注意,现在我们可以轻松地将所有特征工程、清理和分词函数应用于所有提取的列,而不仅仅是像上面示例中的电子邮件文本。

词云分析

如果你已经做到这一点——干得好!我们已经读取、清洗、处理、分词,并对大约 4k 文本记录进行了基本特征工程,全部在一秒钟内(至少在我的 Mac M2 机器上)。现在,让我们享受劳动的成果,进行一些基本的文本分析。

首先,让我们看看电子邮件文本的词云,并惊叹于我们能找到的所有有趣的东西。

# Word cloud function
def generate_word_cloud(text: str):
    wordcloud = WordCloud(
        max_words=100, background_color="white", width=1600, height=800
    ).generate(text)

    plt.figure(figsize=(20, 10), facecolor="k")
    plt.imshow(wordcloud)
    plt.axis("off")
    plt.tight_layout(pad=0)
    plt.show()

# Prepare data for word cloud
text_list = emails_pl.select(pl.col("email_text_tokenised").arr.join(" "))[
    "email_text_tokenised"
].to_list()
all_emails = " ".join(text_list)

generate_word_cloud(all_emails)

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

电子邮件文本词云。由作者生成。

银行账户、近亲、保安公司和逝者亲属——应有尽有。让我们看看这些在使用简单 TF-IDF 和 K-Means 创建的文本聚类中的样子。

# TF-IDF with 500 words
vectorizer = TfidfVectorizer(max_features=500)
transformed_text = vectorizer.fit_transform(text_list)
tf_idf = pd.DataFrame(transformed_text.toarray(), columns=vectorizer.get_feature_names_out())

# Cluster into 5 clusters
n = 5
cluster = KMeans(n_clusters=n, n_init='auto')
clusters = cluster.fit_predict(tf_idf)

for c in range(n):
    cluster_texts = np.array(text_list)[clusters==c]
    cluster_text = ' '.join(list(cluster_texts))

    generate_word_cloud(cluster_text)

下面你可以看到我识别的一些有趣的聚类:

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

除此之外,我还发现了一些无意义的聚类,这意味着在文本清理方面仍有改进空间。不过,看起来我们成功地提取了有用的聚类,所以我们可以称之为成功。告诉我你发现了哪些聚类!

结论

这篇文章涵盖了 Polars 库允许你执行的各种预处理和清理操作。我们已经看到如何使用 Polars 来:

  • 从文本中提取特定模式

  • 根据标记将文本拆分为列表

  • 计算文本中的长度和匹配数量

  • 使用正则表达式清理文本

  • 对文本进行分词并过滤停用词

我希望这篇文章对你有用,你会在下一个 NLP 项目中给 Polars 一个机会。请考虑订阅、点赞并在下面评论。

还不是 Medium 会员?

[## 使用我的推荐链接加入 Medium — Antons Tocilins-Ruberts

阅读 Antons Tocilins-Ruberts 和其他成千上万的作者在 Medium 上的每一个故事。你的会员费用直接…

medium.com](https://medium.com/@antonsruberts/membership?source=post_page-----fcf7054a929a--------------------------------)

参考文献

Radev, D. (2008), CLAIR collection of fraud email, ACL 数据和代码库, ADCR2008T001, http://aclweb.org/aclwiki

项目 Github github.com/aruberts/tutorials/tree/main/metaflow/fraud_email

Polars 用户指南 pola-rs.github.io/polars-book/user-guide/

FastAPI 和 Streamlit:你必须了解的 Python 双雄

原文:towardsdatascience.com/fastapi-and-streamlit-the-python-duo-you-must-know-about-72825def1243

完整的 7 步 MLOps 框架

第 6 课:使用 FastAPI 和 Streamlit 消耗和可视化模型预测。对所有内容进行 Docker 化

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

·发表于 Towards Data Science ·阅读时间 14 分钟·2023 年 6 月 12 日

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

图片由 Hassan Pasha 提供,Unsplash上的照片

本教程代表7 节课程中的第六部分课,将逐步指导你如何设计、实现和部署 ML 系统,并应用MLOps 良好实践。在课程中,你将构建一个生产级模型,用于预测丹麦多个消费者类型在接下来的 24 小时内的能源消耗水平。

完成本课程后,你将理解如何设计、编码和部署一个使用批量服务架构的 ML 系统的所有基础知识。

本课程针对希望通过构建自己的端到端项目来提升技能的中级/高级机器学习工程师

如今,证书随处可见。构建先进的端到端项目并展示是获得专业工程师认可的最佳方式。

目录:

  • 课程简介

  • 课程内容

  • 数据来源

  • 第 6 课:使用 FastAPI 和 Streamlit 消耗和可视化模型预测。对所有内容进行 Docker 化。

  • 第 6 课:代码

  • 结论

  • 参考文献

课程简介

在这 7 节课程结束时,你将知道如何:

  • 设计一个批量服务架构

  • 使用 Hopsworks 作为特征存储

  • 设计一个从 API 读取数据的特征工程管道

  • 构建一个带有超参数调优的训练管道

  • 使用 W&B 作为 ML 平台来跟踪实验、模型和元数据

  • 实现一个批量预测管道

  • 使用 Poetry 构建你自己的 Python 包

  • 部署你自己的私有 PyPi 服务器

  • 用 Airflow 编排一切

  • 使用预测来编码一个使用 FastAPI 和 Streamlit 的 Web 应用

  • 使用 Docker 将你的代码容器化

  • 使用 Great Expectations 确保数据验证和完整性

  • 监控预测结果的性能随时间的变化

  • 将所有内容部署到 GCP

  • 使用 GitHub Actions 构建 CI/CD 管道

如果这听起来很多,不用担心。在你完成这个课程后,你将理解我之前所说的一切。最重要的是,你会知道我为什么使用这些工具以及它们如何作为一个系统协同工作。

如果你想最大限度地从这个课程中受益, 我建议你访问包含所有课程代码的 GitHub 仓库 。本课程旨在快速阅读和复制文章中的代码。

到课程结束时,你将知道如何实现下图。即使有些内容对你来说不太明白,也不用担心。我会详细解释一切。

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

课程中你将构建的架构示意图 [作者提供的图片]。

第 6 课结束时,你将知道如何使用 FastAPI 和 Streamlit 从 GCP 存储桶中消费预测和监控指标。

课程内容:

  1. 批量服务。特征存储。特征工程管道。

  2. 训练管道。ML 平台。超参数调整。

  3. 批量预测管道。用 Poetry 打包 Python 模块。

  4. 私有 PyPi 服务器。用 Airflow 编排一切。

  5. 使用 GE 进行数据质量和完整性验证。模型性能的持续监控。

  6. 使用 FastAPI 和 Streamlit 消费和可视化你的模型预测。将一切 Docker 化。

  7. 将所有 ML 组件部署到 GCP。使用 GitHub Actions 构建 CI/CD 管道。

  8. [附加内容] ‘不完美’ ML 项目的幕后 — 经验教训和见解

查看 第 3 课 了解我们如何计算和存储 GCP 存储桶中的预测结果。

此外,在第五部分,你可以查看我们如何计算监控指标,这些指标也存储在 GCP 存储桶中。

你将从 GCP 存储桶中获取预测结果和监控指标,并使用 FastAPI 和 Streamlit 将其显示在一个友好的仪表板上。

数据源

我们使用了一个免费的开放 API,它提供了丹麦所有能源消费者类型的每小时能源消耗值[1]。

它们提供了一个直观的界面,你可以轻松查询和可视化数据。你可以在这里访问数据 [1]。

数据具有 4 个主要属性:

  • 小时 UTC: 观测数据点时的 UTC 日期时间。

  • 价格区域: 丹麦被划分为两个价格区域:DK1 和 DK2 —— 由大贝尔特分隔。DK1 位于大贝尔特以西,DK2 位于大贝尔特以东。

  • 消费者类型: 消费者类型为行业代码 DE35,由丹麦能源公司拥有和维护。

  • 总消耗: 总电力消耗(单位:千瓦时)

注意: 观测数据有 15 天的滞后!但对于我们的演示用例,这不是问题,因为我们可以模拟与实时相同的步骤。

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

我们的网页应用程序的截图,显示了我们如何预测区域=1 和消费者类型=212 的能源消耗[图片由作者提供]。

数据点具有每小时分辨率。例如:“2023–04–15 21:00Z”,“2023–04–15 20:00Z”,“2023–04–15 19:00Z”等。

我们将数据建模为多个时间序列。每个唯一的价格区域消费者类型元组代表一个独特的时间序列。

因此,我们将构建一个模型,为每个时间序列独立预测接下来的 24 小时的能源消耗。

查看下面的视频以更好地理解数据的样子 👇

课程和数据源概述 [视频由作者提供]。

第六部分:使用 FastAPI 和 Streamlit 获取并可视化模型的预测。将一切 Docker 化。

第六部分的目标

在第六部分中,你将构建一个 FastAPI 后端,该后端将从 GCS 中获取预测结果和监控指标,并通过 RESTful API 暴露这些数据。更具体地说,通过一组端点通过 HTTP(S)暴露数据。

此外,你将使用 Streamlit 实现两个不同的前端应用程序:

  1. 显示预测的仪表板(即你的应用程序),

  2. 显示监控指标的仪表板(即你的监控仪表板)。

两个前端应用程序将通过 HTTP(s)从 FastAPI RESTful API 请求数据,并使用 Streamlit 将数据呈现成一些美丽的图表。

我想强调的是,你可以在 Python 中同时使用这两个框架(FastAPI 和 Streamlit)。这对数据科学家或机器学习工程师非常有用,因为 Python 是他们的终极工具。

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

最终架构的示意图,Lesson 6 组件以蓝色突出显示 [图片来源:作者]。

请注意,从存储桶中获取预测结果与 3 个管道设计完全解耦。例如,运行 3 个管道:特征工程、训练和推理大约需要 10 分钟。但从存储桶中读取预测结果或监控指标几乎是瞬间完成的。

因此,通过将预测结果缓存到 GCP,你从客户端的角度在线提供了 ML 模型:预测结果是实时提供的。

这就是批处理架构的魔力。

接下来的自然步骤是将你的架构从批处理架构迁移到请求-响应或流式架构。

好消息是 FE 和训练管道几乎是相同的,你只需要将批处理预测管道(即推理步骤)迁移到你的 web 基础设施中。阅读这篇文章以了解使用 Docker 以请求-响应方式部署模型的基础知识。

为什么?

因为训练管道将训练模型的权重上传到模型注册表。从那里,你可以根据你的用例需求使用这些权重。

理论概念与工具

FastAPI: 最新且最著名的 Python API web 框架之一。我尝试过所有顶级 Python API web 框架:Django、Flask 和 FastAPI,我的心属 FastAPI。

为什么?

首先,它本地支持异步,这可以用更少的计算资源提升性能。

其次,它使用简单直观,适合各种规模的应用程序。尽管如此,对于庞大的单体应用,我仍然会选择 Django。但这是另一个话题。

Streamlit: Streamlit 使得用 Python 轻松创建简单的 UI 组件(主要是仪表盘)变得非常简单。

Streamlit 的范围是让数据科学家和 ML 工程师利用他们最擅长的东西,即 Python,快速构建他们模型的漂亮前端。

这正是我们所做的✌️

因此,你将使用 FastAPI 作为后端,Streamlit 作为前端,仅用 Python 构建一个 web 应用。

Lesson 6:代码

你可以在这里访问 GitHub 仓库。

注意: 所有的安装说明都在仓库的 README 文件中。这里你将直接进入代码部分。

Lesson 6 中的代码位于以下位置:

使用 Docker,你可以迅速启动所有三个组件:

docker compose -f deploy/app-docker-compose.yml --project-directory . up --build

直接将凭证存储在你的 git 仓库中是一个巨大的安全风险。这就是为什么你将通过**.env**文件注入敏感信息。

.env.default是你必须配置的所有变量的示例。它还帮助存储不敏感的属性的默认值(例如,项目名称)。

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

.env.default文件的截图[作者提供的图片]。

准备凭证

对于本课程,你需要访问的唯一服务是 GCS。在第 3 课的准备凭证部分,我们已经详细解释了如何操作。此外,你还可以在GitHub README中找到更多信息。

为了保持简洁,在本课程中,我想强调的是,Web 应用的 GCP 服务账户应仅具有读取权限,以保证安全。

为什么?

因为 FastAPI API 只会读取 GCP 存储桶中的数据,并且保持最小权限是一种良好的实践。

因此,如果你的 Web 应用被黑客入侵,攻击者只能使用被盗的服务账户凭证读取数据。他不能删除或覆盖数据,这在这种情况下要安全得多。

因此,重复第 3 课的准备凭证部分中的相同步骤,但选择Storage Object Viewer role角色,而不是Store Object Admin角色。

记住,你现在需要下载一个不同的 JSON 文件,其中包含你的 GCP 服务账户密钥,并具有只读访问权限。

查看README了解如何完成**.env文件。我想强调的是,只有 FastAPI 后端需要加载.env文件。因此,你必须将.env**文件仅放在app-api文件夹中。

FastAPI 后端

FastAPI 后端概述[作者提供的视频]。

提醒一下,FastAPI 代码可以在app-api/api下找到。

步骤 1: 创建 FastAPI 应用,在其中配置文档、CORS 中间件和端点根 API 路由器。

步骤 2: 定义 Settings 类。该类的作用是保存 API 代码中需要的所有常量和配置,例如:

  • 通用配置: 端口、日志级别或版本,

  • GCP 凭证: 存储桶名称或 JSON 服务账户密钥的路径。

你将在项目中使用**get_settings()**函数来使用 Settings 对象。

同时,在 Config 类中,我们编程使 FastAPI 查找当前目录中的 .env 文件,并加载所有以 APP_API_ 为前缀的变量。

如你在 .env.default 文件中所见,所有变量都以 APP_API_ 开头。

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

.env.default 文件的截图 [图片由作者提供]。

步骤 3: 使用 Pydantic 定义 API 数据的模式。这些模式将数据从 JSON 编码或解码为 Python 对象,反之亦然。同时,它们根据你定义的数据模型验证 JSON 对象的类型和结构。

在定义 Pydantic BaseModel 时,添加类型到每个变量是至关重要的,这将用于验证步骤。

步骤 4: 定义你的端点,在网络术语中称为视图。通常,一个视图可以访问某些数据存储,并根据查询,它将数据源的一个子集返回给请求者。

因此,检索(即 GET 请求)数据的标准流程如下:

“client → 请求数据 → 端点 → 访问数据存储 → 编码为 Pydantic 模式 → 解码为 JSON → 返回请求的数据”

让我们看看我们是如何定义一个端点来获取所有消费者类型的:

我们使用了 "gcsfs.GCSFileSystem" 作为标准文件系统来访问 GCS 存储桶。

我们将端点附加到api_router

使用 api_router.get() Python 装饰器,我们将一个基本函数附加到 /consumer_type_values 端点。

在上面的示例中,当调用 "https://<some_ip>:8001/api/v1/consumer_type_values" 时,将触发 consumer_type_values() 函数,端点的响应将严格基于该函数的返回值。

另一个重要的事情是,**通过在 Python 装饰器中定义 response_model(即模式),**你不必显式地创建 Pydantic 模式。

如果你返回一个与模式结构 1:1 匹配的字典,FastAPI 将自动为你创建 Pydantic 对象。

就这些。现在我们将重复相同的逻辑来定义其余的端点。FastAPI 使一切变得如此简单直观。

现在,让我们看一下整个 views.py 文件,在其中我们定义了以下端点:

  • /health → 健康检查

  • /consumer_type_values → 获取所有可能的消费者类型

  • /area_values → 获取所有可能的区域类型

  • /predictions/{area}/{consumer_type} → 获取给定区域和消费者类型的预测。请注意,使用 {<some_variable>} 语法,你可以向端点添加参数 —— FastAPI 文档 [2]。

  • /monitoring/metrics → 获取汇总的监控指标

  • /monitoring/values/{area}/{consumer_type} → 获取给定区域和消费者类型的监控值

我想再次强调,FastAPI 后端只读取 GCS 桶的预测。推理步骤完全在批量预测管道中完成。

你还可以访问“http://:8001/api/v1/docs**”**来访问 API 的 Swagger 文档,在那里你可以轻松查看和测试所有端点:

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

Swapper API 文档的截图[作者的图片]。

就是这样!现在你知道如何构建 FastAPI 后端了。添加数据库层和用户会话可能会使事情变得更复杂,但你已经掌握了所有主要概念,这将帮助你入门!

Streamlit 预测仪表板

Streamlit 预测仪表板概述[作者的视频]。

访问app-frontend/frontend下的代码。

使用 Streamlit 非常简单。整个 UI 通过下面的代码定义,代码执行以下操作:

  • 它定义标题,

  • 它向后端请求所有可能的区域类型,并根据此创建一个下拉列表,

  • 它向后端请求所有可能的消费者类型,并根据此创建一个下拉列表,

  • 基于当前选择的区域和消费者类型,它构建并渲染一个 plotly 图表。

直接了当,对吧?

请注意,我们本可以对 HTTP 请求的状态码进行额外的检查。例如,如果请求状态码与 200 不同,则显示一条文本“服务器宕机”。但我们希望保持简洁,只强调 Streamlit 代码✌️

我们将所有常量移到了一个不同的文件中,以便在整个代码中轻松访问。下一步,你可以通过**.env**文件使其可配置,类似于 FastAPI 的设置。

现在,让我们看看我们是如何构建图表的🔥

这一部分没有 Streamlit 代码,只有一些 Pandas 和 Plotly 代码。

**build_data_plot()**函数执行 3 个主要步骤:

  1. 它从 FastAPI 后端请求某个区域和消费者类型的预测数据。

  2. 如果响应有效(status_code == 200),则从响应中提取数据并构建一个 DataFrame。否则,它会创建一个空的 DataFrame,以便进一步传递相同的结构。

  3. 它使用上述计算的 DataFrame 构建一个折线图——plotly 图表。

**build_dataframe()**函数的作用是接受 2 个列表:

  1. 一个日期时间的列表,将作为折线图的 X 轴;

  2. 一组将用作折线图 Y 轴的值;

…并将其转换为 DataFrame。如果一些数据点缺失,我们会将日期时间重采样为 1 小时的频率,以确保数据连续并突出显示缺失的数据点。

非常简单,对吧?这就是人们喜欢 Streamlit 的原因。

Streamlit 监控仪表板

Streamlit 监控仪表板概述[作者的视频]。

监控代码可以在 app-monitoring/monitoring*** 下访问。***

你会发现代码几乎与预测仪表板相同。

在定义 Streamlit UI 结构时,我们还实现了一个包含汇总指标和分隔符的图表。

解耦 UI 组件定义和数据访问的好处在于,你可以在 UI 中注入任何数据,只要尊重预期数据的接口,而无需修改 UI。

build_metrics_plot() 函数几乎与预测仪表板中的 build_data_plot() 函数相同,只是我们从 API 请求的数据不同。

对于监控仪表板中的 build_data_plot() 函数也是如此:

如你所见,所有的数据访问和操作都在 FastAPI 后端处理。Streamlit UI 的工作是请求和展示数据。

很高兴我们只重用了 90% 的预测仪表板代码来构建一个友好的监控仪表板。

用 Docker 包装一切

最后的步骤是将这三个 web 应用程序 Docker 化,并将它们打包到一个 docker-compose 文件中。

因此,我们可以通过一个命令启动整个 web 应用程序:

docker compose -f deploy/app-docker-compose.yml --project-directory . up --build

这里是 FastAPI Dockerfile:

值得一提的是,我们最初只复制并安装了 Poetry 依赖项。因此,当你修改代码时,Docker 镜像将仅从第 19 行开始重建,即复制你的代码。

这是一种常见的策略,利用 Docker 缓存功能在构建镜像时加快开发过程,因为你很少添加新的依赖项,而安装它们是最耗时的步骤。

此外,在 run.sh 中我们调用:

/usr/local/bin/python -m api

但是等一下,命令中没有 Python 文件 😟

其实,你可以在模块内部定义一个 main.py 文件,使你的模块可执行。

因此,当调用 api 模块时,你会调用 main.py 文件:

在我们的例子中,在main.py 文件中,我们使用 uvicorn web 服务器来启动 FastAPI 后端,并用正确的 IP、端口、日志级别等进行配置。

这里是 Streamlit 预测仪表板 Dockerfile:

如你所见,这个 Dockerfile 几乎与用于 FastAPI 后端的那个相同,除了最后的 CMD 命令,这是一个标准的 CLI 命令,用于启动你的 Streamlit 应用程序。

Streamlit 监控仪表板 Dockerfile与预测仪表板 Dockerfile 完全相同。所以在这里重复粘贴是多余的。

好消息是,你可以利用我之前展示的 Dockerfile 模板来 Docker 化大部分 Python 应用程序✌️

最后,让我们看看如何使用 docker-compose 来完成所有工作。你可以在deploy/app-docker-compose.yml文件中找到相关内容:

如你所见,前端和监控服务必须等待 API 启动后才能开始。

另外,只有 API 需要从**.env**文件中加载凭证。

现在,你只需运行以下命令,Docker 将处理构建镜像和运行容器:

docker compose -f deploy/app-docker-compose.yml --project-directory . up --build

总结

恭喜!你完成了第六课全栈七步 MLOps 框架课程。这意味着你现在已经理解了如何使用你的机器学习系统的预测来构建你出色的应用程序。

在本课中,你学会了如何:

  • 从 GCS 中消费预测和监控指标,

  • 构建一个 FastAPI 后端来加载和服务来自 GCS 的数据,

  • 在 Streamlit 中实现一个仪表板来展示预测,

  • 在 Streamlit 中创建一个监控仪表板来可视化模型的性能。

现在你已经理解了基于批量预测架构的机器学习系统上构建应用程序的灵活性,你可以轻松设计全栈机器学习应用程序。

查看第 7 课,这是全栈七步 MLOps 框架的最后一步,即将所有内容部署到 GCP 并使用 GitHub Actions 构建 CI/CD 管道。

另外, 你可以在这里访问 GitHub 仓库

💡 我的目标是帮助机器学习工程师在设计和生产化机器学习系统方面提升水平。关注我在LinkedIn或订阅我的每周通讯以获取更多见解!

🔥 如果你喜欢阅读这样的文章并希望支持我的写作,请考虑成为 Medium 会员。使用我的推荐链接,你可以在不增加额外成本的情况下支持我,同时享受 Medium 丰富故事的无限访问权限。

[## 使用我的推荐链接加入 Medium - Paul Iusztin

🤖 加入以获取有关设计和构建生产就绪机器学习系统的独家内容🚀 解锁完全访问权限…

pauliusztin.medium.com

谢谢✌🏼!

参考资料

[1] 丹麦 API 每小时 DE35 行业代码的能源消耗丹麦能源数据服务

[2] 路径参数,FastAPI 文档

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值