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

原文:TowardsDataScience

协议:CC BY-NC-SA 4.0

何时在 CPU 上运行代码而不是 GPU:典型案例

原文:towardsdatascience.com/when-cpu-is-faster-than-gpu-typical-cases-bc7c64ee3c66?source=collection_archive---------2-----------------------#2023-05-21

如何选择硬件以优化特定使用案例的计算

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

·

关注 发表在 Towards Data Science ·6 分钟阅读·2023 年 5 月 21 日

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

图片来源:Francesco VantiniUnsplash 提供

随着人工智能(AI)、机器学习(ML)、物联网(IoT)、虚拟现实(VR)以及复杂的数值模拟技术的快速发展,对计算能力的需求达到了前所未有的高度。在大多数现实情况下,不仅计算能力很重要,还有硬件的大小和功耗等因素。

当你根据业务和技术要求设计系统时,可以从各种计算组件中进行选择,例如:

  • 集成电路(ICs)

  • 微控制器(MCs)

  • 中央处理单元(CPU)

  • 图形处理单元(GPU)

  • 专用芯片,如张量处理单元(TPU)。

尽管这是一个不断发展的计算领域,但有两个关键组件彻底改变了我们处理数据和执行复杂任务的方式。这些组件是中央处理单元(CPU)和图形处理单元(GPU)。这两大计算强者在推动各个领域的进步方面起着至关重要的作用:

  • 人工智能(例如 ChatGPT)

  • 科学模拟(例如有限元方法、计算流体力学)

  • 游戏

  • 视觉效果

因此,理解 CPU 和 GPU 的独特能力和性能特征对于充分发挥它们的潜力以及优化整个系统以满足业务需求至关重要。

当谈到降低计算运行成本时,你应该考虑以下方面:

  • 硬件成本

  • 功耗

  • 性能效率

  • 维护和升级

关于购买成本,CPU 比中高端 GPU 更实惠。然而,由于 GPU 具有更高的核心数量和内存带宽,它们的能耗比 CPU 高。因此,如果你设计的是一个由 5V 电池供电的简单物联网设备,你可能会关注功耗和低硬件成本——那么 CPU(甚至 IC 或 MC)将是最佳选择。

另外,还有一个选项是使用来自例如 Google、Amazon 或 Azure 等公共云资源,只为使用时间付费。如果你设计的是一个 Web 服务,或者你的硬件需要高计算能力但受限于尺寸或需要远程访问和控制,那么这将是最佳选择。然而,在像智能手机或智能手表这样的量产设备中,硬件成本和功耗仍然发挥着决定性作用。

现在,当考虑计算成本时,总体费用通常是完成任务端到端所需时间的函数。一个好的例子是神经网络(NN)的训练。这本身就是一个计算密集型任务。从端到端的角度来看,还包括数据准备和实验跟踪(用于超参数优化)等额外步骤。这带来了额外的开销。然而,在开发阶段,训练仍然是瓶颈,因此大多数流行的 ML 框架(如 pytorch、Keras)支持 GPU 计算来解决这个问题。这是利用 GPU 能力的经典案例——NN 的训练非常适合大规模并行化。这是由于其底层实现。然而,推理本身(在模型训练之后)通常可以在 CPU 或甚至微控制器上完成。训练之后的瓶颈可能在数据准备方面(内存方面)或 I/O 操作方面。对于这两者,CPU 通常更为适合。这就是为什么有些专用微处理器用于这种任务(如 Intel Atom® Processors x7000E)。所以最终,我们来到了两种不同的最佳解决方案,取决于环境:开发(GPU)和生产(CPU)。

正如你所见,虽然 GPU 在重度并行处理上表现出色,但在一些情况下 CPU 从端到端的角度来看超越它们。这取决于算法的性质和业务需求。如果你是软件开发人员或系统设计师/架构师,了解这些情况对于提供最佳解决方案至关重要。

在本文中,我们将探讨一些此类场景。

单线程递归算法

有些算法本身设计上不适合并行化——递归算法。在递归中,当前值依赖于先前的值——一个简单但明确的例子是计算斐波那契数的算法。一个示例实现如下。在这种情况下,无法打破计算链并并行运行它们。

另一个此类算法的例子是递归计算阶乘(见下文)。

内存密集型任务

有些任务的瓶颈在于内存访问时间,而不是计算本身。CPU 通常具有比 GPU 更大的缓存大小(快速内存访问元素)以及更快的内存子系统,使其在操作频繁访问的数据时表现出色。一个简单的例子是对大型数组的逐元素加法。

然而,在许多情况下,流行的框架(如 Pytorch)会通过将对象移动到 GPU 的内存中并在后台并行化操作,使这些计算在 GPU 上更快。

我们可以创建一个过程,其中初始化 RAM 中的数组并将它们移动到 GPU 进行计算。传输数据的额外开销导致端到端处理时间比直接在 CPU 上运行要长。

这时我们通常使用所谓的 CUDA 支持数组——在这种情况下,使用 Pytorch。你必须确保你的 GPU 能够处理这种数据大小。给你一个概述——典型的流行 GPU 的内存大小为 2–6GB VRAM,而高端的则有高达 24GB VRAM(GeForce RTX 4090)。

其他不可并行化算法

有一类算法虽然不是递归的,但仍然无法并行化。一些示例如下:

  • 梯度下降法 — 用于优化任务和机器学习

  • Hash-chaining — 用于加密

梯度下降法在其基础形式中无法并行化,因为它是一个顺序算法。每次迭代(称为一步)依赖于前一次的结果。不过,已经有一些关于如何以并行方式实现此算法的研究。欲了解更多,请查看:

你可以在这里找到 Hash-chaining 算法的示例:www.geeksforgeeks.org/c-program-hashing-chaining/

小任务

另一个 CPU 更好的选择是数据量非常小的情况。在这种情况下,RAM 和 GPU 内存(VRAM)之间传输数据的开销可能会超过 GPU 并行处理的好处。这是因为 CPU 缓存的访问速度非常快。这在与内存密集型任务相关的部分中提到过。

此外,一些任务实在太小,尽管计算可以并行运行,但对最终用户的好处不明显。在这种情况下,在 GPU 上运行仅会产生额外的硬件相关成本。

这就是为什么在物联网中,GPU 不常被使用。典型的物联网任务有:

  • 捕获一些传感器数据并发送出去

  • 在检测到信号后激活其他设备(灯光、警报、马达等)

然而,在这个领域,GPU 仍然用于所谓的边缘计算任务。这些是你必须直接在数据源处获取和处理数据的情况,而不是将其发送到互联网上进行重处理。一个好的例子是宝马开发的iFACTORY

小规模并行化任务

有许多使用场景需要并行运行代码,但由于 CPU 的速度,足以使用多核 CPU 并行化过程。GPU 在需要大规模并行化(数百或数千个并行操作)的情况下表现出色。在你发现例如 4 倍或 6 倍加速已经足够的情况下,你可以通过在 CPU 上运行代码、每个进程在不同核心上来降低成本。如今,CPU 制造商提供的 CPU 核心数在 2 到 18 个之间(例如,Intel Core i9–9980XE Extreme Edition Processor)。

总结

总体来说,选择 CPU 和 GPU 的经验法则是回答以下主要问题:

  1. CPU 是否能够在要求的时间内完成整个任务?

  2. 我的代码可以并行处理吗?

  3. 我能将所有数据放入 GPU 吗?如果不能,这是否会引入较大的开销?

要回答这些问题,关键在于深入理解你的算法是如何工作的,以及当前的业务需求是什么,这些需求未来可能会如何变化。

人类在需要回答数据相关的棘手问题时

原文:towardsdatascience.com/when-humans-need-to-answer-tough-questions-about-data-66dfc227c546?source=collection_archive---------9-----------------------#2023-12-07

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

·

关注 发表在 Towards Data Science · 作为 通讯 发送 · 3 分钟阅读 · 2023 年 12 月 7 日

数据科学和机器学习专业人员知道如何从数据中寻找答案:这可能是他们工作的核心支柱。当我们探讨一些涉及数据的棘手问题时,例如内置的偏见以及它可能被利用来达到可疑目的的方式,情况就变得更加复杂。

随着我们进入年末最后阶段,我们邀请读者探讨一些引发关键讨论的大问题,这些问题近年来已经引起广泛关注,并且几乎可以肯定会继续塑造 2024 年及以后的领域。

我们本周的亮点涵盖了广泛的主题,从数据支持知识的本质到其在医疗等特定领域的应用;我们希望这些内容能够激发更多的反思,并吸引新的参与者参与到这些重要的讨论中。

  • 偏差、毒性与破解大型语言模型(LLMs) LLMs 的快速崛起和演变使得从业者难以暂停并评估其固有风险。Rachel Draelos, MD, PhD 对近期研究的详细概述提供了对这些模型在大规模上延续甚至加剧偏差和毒性的能力的及时观察。

  • 哲学与数据科学——深入思考数据 认识论中的主要概念——如演绎推理和归纳推理、怀疑主义和实用主义——如何融入数据科学家的工作中?Jarom Hulet 的最新文章探讨了这两个领域之间(有时意外的)重叠。

  • 数据科学中的认知偏差:类别规模偏差 在关于认知偏差的新系列中,Maham Haroon 详细阐述了我们的大脑在分析和从数据中提取洞见时,如何引导我们走向错误的多种方式。第一篇文章聚焦于类别规模偏差,并解释了它如何渗透到数据科学家在日常工作中所做的假设中。

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

图片由 Bozhin Karaivanov 提供,来源于 Unsplash

  • AI 在医疗保健中应扮演什么角色? 我们迄今为止所讨论的偏差可能对模型、业务和底线造成严重破坏。然而,Stephanie Kirmer 强调,这些偏差在医疗领域尤为严重,因为该领域常涉及生死攸关的情况,“失败的风险极为灾难性。”

  • 变压器的挽歌?

    在一个快速变化的领域中,我们容易将一个六年前的概念视为核心和永恒。自 2017 年以来,Transformer 模型在主流 AI 工具的采纳中发挥了重要作用;但正如萨尔瓦托雷·拉伊利所指出的,它们也可能有保质期,也许现在是时候思考下一步是什么。

大问题很重要,但中等规模和紧凑型问题也很有用!不要错过我们最近关于职业变动、数据工程和其他及时话题的一些精彩内容:

  • 我们如何让模型忘记信息我们不再希望它们保留?叶夫根尼亚·苏霍多尔斯卡娅提出了一种数据驱动的方法,用于生成语言模型的“遗忘”。

  • 思考在新的一年里换个角色吗?Thu Vu最近分享了一个详细的路线图,适合那些有兴趣转向数据分析的人。

  • 想要了解图神经网络领域的前沿研究,可以查看迈克尔·布朗斯坦的最新文章(与合著者本·芬克尔施泰因、伊斯梅尔·杰兰和邢越·黄一起)。

  • 如果你是一名害怕回填过程的数据工程师,高小旭的指南概述了高效的实现方法,将帮助你简化这个工作流程。

  • 对 scikit-learn 不熟悉?Yoann Mocquin最近推出了一系列适合初学者的 sklearn 教程,带我们了解这个流行的机器学习库的不同模块和功能。

  • LLMs 的 SQL 会是什么样的?玛利亚·曼苏罗娃详细介绍了语言模型查询语言(LMQL)的概述,允许用户在一个提示中结合多个调用,控制输出并降低成本。

感谢你支持我们作者的工作!如果你喜欢 TDS 上的文章,可以考虑成为 Medium 的朋友会员:这是一个全新的会员级别,为你喜爱的作者提供更多奖励。

直到下一个 Variable,

TDS 编辑部

何时使用条形图是不正确的?

原文:towardsdatascience.com/when-is-it-wrong-to-use-bar-charts-70f55a3fb1a2?source=collection_archive---------9-----------------------#2023-05-31

…以及可能的解决办法

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

·

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

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

由 Canva 文本转图片工具生成的图像

别误解我的意思,条形图可以是数据可视化的一个很好的工具,尤其是在展示计数、总数或比例时。然而,不正确地使用条形图可能会导致无意的(甚至更糟的是,故意的)数据误解。我今天要讨论的具体问题是使用条形图来展示汇总统计数据,如均值或中位数。

这里最大的问题是细节的丧失,因为条形图可能过于简化,遗漏了重要信息,如方差、分布、异常值和趋势。在这篇文章中,我将通过一系列示例来说明这个问题,并提出潜在的解决方案。为了不干扰文章的流程,图表的代码将在末尾指定,供感兴趣的读者参考 😃

葡萄酒质量数据集

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

图片由Kym Ellis提供,来源于Unsplash

对于这篇文章,我将使用葡萄酒质量数据集¹,该数据集可通过UCI ML 数据库获取。虽然数据集中包含许多葡萄酒属性,但我们将重点关注总二氧化硫测量值。

二氧化硫通常作为二氧化硫添加到葡萄酒中,由于其防腐特性,它在酿酒中发挥着关键作用。作为抗氧化剂,它帮助防止葡萄酒氧化,保护其免受变色和不希望的风味变化。它的抗菌特性还保护葡萄酒免受细菌和酵母的腐败,保持了预期的口味和质量。

问题

让我们通过绘制一个简单的条形图来比较红葡萄酒和白葡萄酒的总二氧化硫水平,从而说明这个问题。

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

图片由作者提供

好吧,也许用上面的例子来批评条形图并不公平,因为基本的图表看起来如此丑陋,没必要进一步论证就让人反感。让我们先通过调整一些美学属性来让它变得更漂亮。

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

图片由作者提供

好得多。现在,回到手头的问题。这张图表告诉我们什么?显然,白葡萄酒的二氧化硫水平似乎要高得多。这是由于红葡萄酒和白葡萄酒的酿造过程不同而可以预期的。

红葡萄酒在发酵过程中与葡萄皮一起发酵,这提供了天然的抗氧化剂,有助于保护葡萄酒免受氧化。相比之下,白葡萄酒通常通过压榨葡萄并在发酵前去除葡萄皮来制作。这使得白葡萄酒更容易受到氧化,需要额外的保护,例如二氧化硫。

尽管平均效果是明显的,但条形图没有提供关于每组值的分布信息,也没有显示每组的观察数量。

通过在条形上方添加观察数量和添加误差条来显示每组的标准差,可以部分解决这个问题。

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

图片由作者提供

如果基础值的分布是对称的,这种方法可能足够,但实际情况不一定如此,这使得标准差作为离散统计量并不理想。在条形图中,无法通过添加更多内容来解决这个问题,而不使其更接近完全不同类型的图表。这表明条形图并不适合呈现这种数据类型。

那么,可能的替代方案有哪些?我将在剩下的帖子中讨论几个。

修正方案

在这里,我提供了四种可能的替代方案,我认为它们是更好且更透明的解决方案。

1. 抖动点

第一个可能性是将实际的单个观察值添加到图表中。

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

作者提供的图片

如果观察数量相对较少,这可能是一个很好的替代方案。然而,在这种特定情况下,由于数据集中有大量的葡萄酒,它本身感觉相当繁琐。

2. 带有指定均值的箱形图

第二种替代方案是使用箱形图,并添加指定均值和中位数的功能(默认情况下,中位数由中央箱中的平坦线显示)。尽管箱形图通过指定四分位数给我们提供了底层分布的概念,但我喜欢均值提供的额外信息。这是因为均值和中位数之间的大而明显的差异能立刻告诉我们分布是否偏斜以及偏斜的方向。

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

作者提供的图片

3. 带有中位数的小提琴图

小提琴图非常好,因为它们让我们了解底层分布的形状,使得容易检测到异常现象,如双峰性或数据偏斜。有人可能会争辩说箱形图也隐含地做到这一点。虽然我在一定程度上同意这一点,但我们也必须考虑到,读懂箱形图需要特定的培训,而小提琴图则不需要。

我还喜欢添加中位数的信息,因为小提琴图留有很多未使用的空间,何乐而不为呢 😃

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

作者提供的图片

4. 带有抖动点的小提琴图

好吧,这实际上不是一个独立的选项,而是选项 1 和 3 的组合。对于我们的具体情况,这将是我的选择,但这并不意味着它适用于所有可能的场景,因为这取决于问题的具体情况,如比较组的数量、点的总数、组的离散程度等。

注意,我没有尝试将箱形图与特定点结合起来。这是有意的,因为我认为这种结合会违背箱形图的目的。即,箱形图仅在数据点高于中央箱的上边界 1.5 倍四分位范围时显示特定点。这可以作为一种简单的异常值检测方法,而添加过多其他点会使其不清晰。

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

图片来源:作者

结论

本文讨论了使用条形图呈现聚合组统计数据的具体问题,利用葡萄酒质量数据集提供实际示例。在阐述问题后,提出了四种可能的替代方案,并讨论了它们的优缺点。

最后,请记住,任何数据可视化的主要目标是准确有效地传达信息。始终选择最适合数据和你想传达的信息的可视化类型。

我希望你会觉得这篇文章有用。如果你有任何评论,欢迎在帖子下方留言。当然,如果你喜欢你所阅读的内容,请点赞并关注我,以获取更多类似内容。

脚注

¹P. Cortez, A. Cerdeira, F. Almeida, T. Matos 和 J. Reis。

通过物理化学属性的数据挖掘建模葡萄酒偏好。载于《决策支持系统》,Elsevier,47(4):547–553,2009. (CC BY 4.0)

生成图表的代码

library(tidyverse)

wine <- read_delim("winequality-red.csv", 
                    delim = ";", escape_double = FALSE, trim_ws = TRUE) %>%
  mutate(Type = "Red") %>%
  bind_rows(read_delim("winequality-white.csv", 
                        delim = ";", escape_double = FALSE, trim_ws = TRUE) %>%
              mutate(Type = "White")) %>%
  mutate(Type = factor(Type)) %>%
  pivot_longer(`fixed acidity`:`quality`, 
                names_to = "Parameter", values_to = "Value") %>%
  filter(Parameter == "total sulfur dioxide") %>%
  select(-Parameter)

wine_summary <- wine %>%
  group_by(Type) %>%
  summarise(Median = median(Value), Mean = mean(Value), 
            SD = sd(Value), N = n())
#basic bar chart
wine_summary %>%
  ggplot(aes(Type, Mean)) +
  geom_col() +
  labs(x = "Wine type", y = "Total sulfur levels")

#aesthetically pleasing bar chart
wine_summary %>%
  ggplot(aes(Type, Mean)) +
  geom_col(aes(fill = Type), width = 0.8) +
  labs(x = "Wine type", y = "Total sulfur levels") +
  scale_fill_manual(values = c("#b11226", "#F4E076")) +
  labs(x = "Wine type", y = "Total sulfur levels") +
  theme_bw() +
  theme(legend.position = "none")

#bar chart with errorbars and specified number of observations per group
wine_summary %>%
  ggplot(aes(Type, Mean)) +
  geom_col(aes(fill = Type), width = 0.8) +
  geom_errorbar(aes(ymin = Mean - SD, ymax = Mean + SD), width = 0.15) +
  geom_label(aes(y = 200, label = N), fill = "gray97") +
  scale_fill_manual(values = c("#b11226", "#F4E076")) +
  labs(x = "Wine type", y = "Total sulfur levels") +
  theme_bw() +
  theme(legend.position = "none")

#jittered points chart
wine_summary %>%
  ggplot(aes(Type, Mean)) +
  geom_jitter(data = wine, aes(x = Type, y = Value, col = Type), alpha = 0.4) +
  geom_errorbar(aes(ymin = Mean - SD, ymax = Mean + SD), width = 0.15) +
  geom_point(shape = 4, size = 2, stroke = 2) +
  geom_label(aes(y = 450, label = N), fill = "gray97") +
  scale_color_manual(values = c("#b11226", "#F4E076")) +
  labs(x = "Wine type", y = "Total sulfur levels") +
  theme_bw() +
  theme(legend.position = "none")

#boxplot with added information about the mean
wine_summary %>%
  ggplot(aes(Type, Mean)) +
  geom_boxplot(data = wine, aes(x = Type, y = Value, col = Type)) +
  geom_point(shape = 4, size = 2, stroke = 2) +
  geom_label(aes(y = 450, label = N), fill = "gray97") +
  scale_color_manual(values = c("#b11226", "#F4E076")) +
  labs(x = "Wine type", y = "Total sulfur levels") +
  theme_bw() +
  theme(legend.position = "none")

#violin plot with information about the median
wine_summary %>%
  ggplot(aes(Type, Median)) +
  geom_violin(data = wine, aes(x = Type, y = Value, col = Type)) +
  geom_point(shape = 4, size = 2, stroke = 2) +
  geom_label(aes(y = 450, label = N), fill = "gray97") +
  scale_color_manual(values = c("#b11226", "#F4E076")) +
  labs(x = "Wine type", y = "Total sulfur levels") +
  theme_bw() +
  theme(legend.position = "none")

#violin plot with added jittered points
wine_summary %>%
  ggplot(aes(Type, Median)) +
  geom_violin(data = wine, aes(x = Type, y = Value), fill = "gray92") +
  geom_jitter(data = wine, aes(x = Type, y = Value, col = Type), alpha = 0.1) +
  geom_point(shape = 4, size = 2, stroke = 2) +
  geom_label(aes(y = 450, label = N), fill = "gray97") +
  scale_color_manual(values = c("#b11226", "#F4E076")) +
  labs(x = "Wine type", y = "Total sulfur levels") +
  theme_bw() +
  theme(legend.position = "none")

毫秒至关重要——我在性能改进中的旅程

原文:towardsdatascience.com/when-milliseconds-matter-my-journey-to-performance-improvement-5a3cd69754c4?source=collection_archive---------19-----------------------#2023-02-15

从延迟改进项目中获得的经验教训

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

·

关注 发表于 Towards Data Science · 7 分钟阅读 · 2023 年 2 月 15 日

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

图片由 Giallo 提供,来自 Pexels

在严格的服务水平协议下工作,其中毫秒至关重要,同时维护一个具有多个依赖关系的复杂系统——当出现延迟相关问题时,这可能会带来许多挑战和非平凡的调查。

在这篇文章中,我将带你了解我如何通过一个描述的类型问题启动性能改进,并在过程中获得的经验教训。

步骤 1 — 预期行为和问题描述

公司的产品处理事务,对于每个接收到的事务——我们要么批准,要么拒绝。

对于一些事务,我们会经过数据增强步骤,以获取更多的信息用于实时决策和未来的事务。

然而,对于每天成千上万的事务——增强过程根本没有发生,而且原因不明确。

步骤 2 — 在沙漠中寻找狮子

让我们看一下相关系统的简化视图:

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

作者草图

由于这是一个增强问题,第一个问题是——问题是发生在增强服务内部,还是在我们到达之前?显然,对于这些有问题的交易,我们甚至没有向增强服务发送获取请求。一个嫌疑犯排除。

步骤 3 — 了解我们的拓扑

让我们从故事中休息一下。我想向你介绍决策系统的拓扑。这个系统基于Apache Storm,旨在实时处理无限的数据流。

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

作者草图

简而言之,Spout 从数据源(例如 Kafka / RabbitMQ)接收数据,并将流输出到拓扑中。每个 Bolt 是拓扑中的一个组件,它接收并发出一个或多个流。一个 Bolt 进行简单的逻辑处理,如过滤、聚合、从数据库读取和写入等。

一些 Bolts 并行运行,而另一些则相互依赖。

一些 Bolts 会固定一个相对超时时间值,之后它们会继续(处理流并发出)无论是否接收到所有等待的输入。

超时的使用防止了 Bolts 和组件过长时间延迟拓扑,从而使我们能够满足预期的 SLA。

步骤 4 — 为什么没有增强?

回到我们的故事。为什么决策系统没有调用增强服务?我添加了一些指标,发现问题交易的一个重要条件达到了——增强过程剩余的时间不够,所以没有执行获取调用以留出足够的时间给未来的 Bolts。

这很让人惊讶。增强是流程中的核心组件,发生在拓扑的相对前期。为什么我们没有足够的时间来执行这个调用?

步骤 5 — 依赖关系和延迟

为了理解为什么我们超出了(相对)时间,我深入研究了一个内部工具提供的视图,并向后查看了我的增强 Bolt 所依赖的 Bolts。

我发现一个较早的 Bolt 一直需要大约 100 毫秒。考虑到我们的 SLA 和一个 Bolt 应该花费的平均时间,这被认为是非常多的。

这个父 Bolt 中发生了什么事情,花了这么长时间?

当深入到父 Bolt 的代码时,我看到了一个 elasticsearch 查询,并怀疑这是否可能是我们瓶颈的原因。

结果是——当查看相关仪表板时,我发现我的 Bolt 高延迟的时间与这个集群高 CPU 使用率之间存在相关性。

在与维护这个集群的团队同步后,我了解到他们已经熟悉其长期的性能问题和逐渐恶化的情况。

第 6 步——这个依赖关系必要吗?

为什么增值-Bolt 依赖于这个 elasticsearch 查询?它是否值得我们在未增值事务中支付的代价?

在增值上下文中——我们在等待这个查询结果来处理一个特定功能,但进一步调查显示该功能存在一个漏洞,天知道有多久了,因此我们没有利用这个功能的期望输出。

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

作者草图

如果我们删除这个功能,我们可以断开增值与调用问题 elasticsearch 集群的 Bolt 之间的依赖。如果我们修复有漏洞的功能——我们将重新获得有人意图使用的数据,但会保留高延迟依赖,并需要寻找替代解决方案。

在考虑了几种潜在的解决路径——每种路径的努力和成本效益,并且得到了修复有漏洞的功能所有者的同意删除这段代码——我删除了有漏洞的功能。

第 7 步——何时对我们的拓扑进行微妙的更改

决策系统是基于依赖的,此时,我希望将增值组件依赖于一个发生在调用问题集群的组件之前的组件。这样的更改可以节省我们等待高延迟查询的时间,而且我们新的依赖组件出现得越早——增值及其后续组件将留有更多的空闲时间。

在调查代码并与高层选择新的父组件后,我进行了这项微妙的更改并监控了结果。

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

作者草图

第 8 步——结果如何?

起初,我的更改上线后没有看到戏剧性的改善。真令人沮丧!几个月的调查和期待,变化幅度微小。但我们不应绝望!

我检查了未增值的事务,发现它们也符合超时条件。我调查了依赖关系视图,发现通过不同路径——它们仍然依赖于问题组件!

原因是增值 Bolt 等待几个字段,而这些字段的父 Bolt 再次指向我们的高延迟 Bolt,它查询了高延迟集群。

但这些字段在代码中没有被使用。

我删除了那些字段,并很高兴看到结果:

每日未丰富交易的数量从 26K 减少到 200。

此外,在开始时——对于一些商户,这类有问题的交易的百分比高达 20%,而在我的更改之后——所有商户的交易中问题的百分比不超过 1%。

大获成功!

经验教训:

我在这个过程中学到了很多。我使用各种技术进行了调查,深入复杂的代码,这些代码基于复杂的架构,分析了延迟,并考虑了不同的权衡以解决问题。但我今天想与大家分享一些提示:

  • 删除代码是有益的 一位优秀的软件工程师的标准不是她写了多少新代码。删除代码是一项重要任务,删除那些过时的、使系统过载的代码对于提升系统性能至关重要。调查的过程可能需要耐心和毅力,但可能会带来宝贵的结果。

  • 代码删除应彻底进行

    组件 A 依赖于组件 B 的原因之一是由于组件中未使用的输入字段。随着我们删除代码,花时间问自己是否删除了与之相关的所有内容并没有留下遗留问题是一个好习惯。

  • 投资于分析工具

    使我能够检测到高延迟组件的一个因素是内部分析工具。我们需要了解我们的组件、它们的依赖关系以及它们的延迟,并且我们需要这样的工具直观易用。

  • 考虑监控流中特定组件的延迟

    当毫秒很重要时,监控每个组件的延迟可能是识别瓶颈源的关键。考虑在创建组件时添加度量指标,以便在需要时能够访问这些数据。

  • 掌握你使用的技术,学习如何用它们进行调查 我们团队使用的每种技术都有其强项和技巧。当我们遇到新技术或新工具时,我们可能会学习到用于日常工作的内容,然后就此离开。但投入时间学习如何利用这些工具获得额外的见解,当我们面临大量问题需要调查时,这可能会非常有用。

  • 咨询和头脑风暴 复杂的调查可能很困难,一位同事可能熟悉我们未曾了解的调查工具,或者对我们的问题提出不同的看法。请记住,项目的成功就是团队和公司的成功,如果你感到困惑或需要另一个观点,请让同事参与进来。

当点预测完全无用时

原文:towardsdatascience.com/when-point-forecasts-are-completely-useless-79cd27d0b1e5

虽然点预测非常流行,但要注意一些不幸的陷阱

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

·发表于 Towards Data Science ·11 min read·2023 年 1 月 1 日

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

图片由 Kai Pilger 提供,来源于 Unsplash

介绍

上一篇文章中,我们讨论了概率预测相对于点预测的一个优势——即处理超越时间问题。在这篇文章中,我们将探讨点预测的另一个局限性:高阶统计属性。

对于具有数学或统计学背景的人来说,这些思想将非常熟悉。因此,没有正式训练的读者可能会从这篇文章中获得最多的益处。

在本文结束时,你将对高阶统计属性如何影响预测性能有更好的了解。特别是,我们将看到点预测如何在没有进一步调整的情况下完全失败。

点预测失败的两个示例

为了让你对点预测的问题有更深的认识,我们将继续介绍两个非常简单的例子。这两个时间序列都可以用相当简单的自回归数据生成过程来描述。

我们将生成足够的数据,以使自回归梯度提升模型具有合理性。这样,我们避免了使用过于僵化的模型和由于数据不足导致的过拟合。

示例 1 — 自回归方差(GARCH)

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

带有自回归方差的模拟时间序列。(图像由作者提供)

这是一个标准的ARCH时间序列,这在计量经济学中经常遇到。如果你想了解如何处理这样的数据,我还写了一些文章:

无论如何,让我们暂时使用时间序列的机器学习模板方法。即,我们使用Nixtla 的 mlforecast 包为我们构建一个自回归的提升模型。(这并不是想要批评 Nixtla 包。事实上,如果你知道自己在做什么,它确实非常有用和方便。)

结果如下所示:

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

梯度提升对自回归方差的时间序列预测。(图片由作者提供)

不幸的是,结果完全无助。尽管我们提供了实际的滞后数,预测结果实际上毫无用处。

示例 2 — 自回归的非高斯数据

下一个示例遵循了一个更复杂的数据生成过程。然而,这并不排除某些实际时间序列也遵循类似的逻辑:

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

自回归 Beta 分布的模拟时间序列。(图片由作者提供)

让我们检查一下梯度提升模型在这种情况下的表现:

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

梯度提升对自回归 Beta 分布时间序列的预测。(图片由作者提供)

再次强调,预测结果完全无用。

我们的点预测出了什么问题?

如你所知,sklearn.ensemble.GradientBoostingRegressor默认情况下最小化均方误差MSE)。以下是 MSE 最小化的一个众所周知的属性:

一个分布的均值最小化其均方误差。

从数学上讲:

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

(图片由作者提供)

我们假设一个任意大的可接受函数集合。此外,我们隐含需要假设条件均值实际上存在。这对于大多数表现良好的预测问题是合理的。

因此,上述两个模型都旨在预测我们观察值的条件分布的均值。问题在于,条件均值实际上是按构造设定为常数的。

对于第一个示例来说,这是显而易见的——每个观察值的条件均值为零。对于第二个示例,我们需要做一些数学计算,正式证明留给感兴趣的读者。

现在,虽然条件均值在时间上保持不变,但我们的时间序列仍远非纯粹的噪声。通过均方误差(MSE)最小化来预测均值,在描述未来时显得相当不足。

我们可以进一步声明:

即使是完美的(点)预测模型,如果预测量无信息,也可能是无用的。

我们可以通过绘制条件密度与条件均值的图来可视化我们的例子:

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

条件密度与(恒定的)条件均值。(图片作者提供)

一方面,条件分布是变化的,可以通过历史数据来预测。然而,条件均值是恒定的,并不能告诉我们未来分布的任何信息。

我们能做些什么?

乍一看,上述问题对原始点预测的能力描绘了一个相当严峻的图景。像往常一样,情况当然要复杂得多。

因此,让我们讨论一下如果你的点预测效果不佳,该如何处理的大致思路。

评估一下你是否真的有问题——点预测仍然可能有效

正如我们刚才看到的,点预测可能会惨遭失败。然而,既然它们被广泛使用,这表明它们可能会对你的问题造成麻烦。许多预测问题可以通过标准方法合理解决。

有时,你只需要对你的模型投入更多的努力。仅仅使用另一种损失函数或对特征进行另一种非线性变换可能就足够了。然而,一旦你观察到点预测确实无法胜任,可能就该转向概率性方法了。

两种情况可能是很好的指示器:

1) 你的点预测显示出很少的变化,几乎是恒定的。

从数学上讲:

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

(图片作者提供)

这在我们的例子中确实发生了,并且应该在你的模型验证步骤中可见。正如我们所看到的,目前还没有理由得出模型或数据有问题的结论。

2) 偶尔的大离群值经常使你的点预测无用。

这个问题将我们引入了极值理论的领域,可能值得开一系列博客来讨论。因此,我们将仅简要查看这里发生了什么。

作为一个夸张但具有说明性的例子,考虑以下时间序列:

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

这仅仅是从一个Cauchy 分布中抽样,该分布的位置由正弦函数决定。现在,让我们看看如果我们的(点)预测只是底层正弦函数的延续,随着样本大小的增加,MSE 如何变化:

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

正弦-柯西预测模型的均方误差运行情况。(图片作者提供)

令人惊讶的是,MSE 在250.000!)次观察后甚至都没有收敛。无论你观察到多少数据,你的**平均(!)**平方误差仍在增长。这是某些概率分布家族的特性,而柯西分布就是其中之一。

你在日常生活中可能永远不会观察到这样的怪异现象。几乎所有的现实世界时间序列都有某些限制,使得无限的 MSE 不太可能。

然而,了解你观察到大型异常值的可能性至少会有所帮助。例如,想象一下在 2019 年,如果你能得到一个关于旅游业极端崩溃的粗略概率会有多么有价值。

调整你对“有用”预测的定义

当然,说服你的利益相关者接受上述点预测问题可能非常困难。对于商业人士来说,概率方法可能看起来像是不必要的火箭科学。

相反,我们通常通过预测与未来观察的匹配程度来衡量预测的成功。如果出了问题,只需增加更多的数据,希望下次会更好。然而,请考虑大多数时间序列系统的基础复杂性。你有多大的机会收集到所有相关数据?

这就像试图收集和处理所有相关因素以预测轮盘赌游戏的确切结果。虽然理论上可能,但巨大的粒度使得实践中几乎不可能实现。

然而,你可能会发现轮盘赌桌上存在一些物理缺陷。如果这些缺陷使赔率向某个方向偏斜,按此做出你的投注可能在长远中让你发财。

如果我们将这个类比转移到一般预测问题上,这就引导我们到一个范式转变:

与其尽可能精确地预测未来,不如让预测模型优化我们在未来结果上的投注几率。

将这个投注隐喻进一步扩展,我们得出三点关于预测的结论:

1) 现实世界的决策几乎总是在不确定性下做出

考虑以下问题:

你是一名冰淇淋供应商,想要优化你的每日库存。为了简单起见,我们假设每天你要么

  • 90%的机会出售确切的10磅冰淇淋,或者

  • 10%的机会出售0磅(因为天气实在太差了,你知道的)

此外,假设

  • 你可以在每天开始时以1金钱购买1磅冰淇淋

  • 1.2金钱出售1

  • 你的冰淇淋库存每天结束时归零(没有过夜仓储)

  • 如果你的总损失超过-10金钱,你就会破产

想象一下,你正在为这个问题建立一个需求预测模型,以决定你要出售多少冰淇淋。如果你走点预测+均方误差(MSE)路线,你的结果将如下:

期望需求为0.9*10+0.1*0 = 9,因此最小化 MSE 的预测每天也是9。你打算每天购买9磅冰淇淋吗?如果你连续多次没有销售任何东西,破产的风险怎么办?

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

冰淇淋供应商问题的概率质量函数和期望值。(作者提供的图像)

这是不确定性发挥作用的地方,你需要决定你愿意承担多少风险。像生活中的许多情况一样,这也是利润与风险之间的权衡。

不幸的是,仅凭点预测无法考虑任何不确定性。

现在假设我们有一个能够预测相应概率质量函数(pmf)的概率预测模型。从这里,我们可以推导出在给定库存x的情况下,第t天的收益R作为一个随机变量:

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

(作者提供的图像)

这些信息可以用在随机规划中。后者可以视为对确定性优化的概率扩展。在这里,我们也可以考虑并优化在面对现实世界不确定性时的风险。

事实上,现实世界的复杂性远远超出了我们的小冰淇淋例子。考虑一下这对现实与点预测偏离的可能性意味着什么。

2) 许多小下注比少数大下注更安全

回到有缺陷的轮盘赌桌上,假设0的概率比预期稍高。你会在一次下注中把所有筹码都押在0上,还是在多个回合中小额下注?

如果你运气不好,即使是最小的下注额也可能导致破产。然而,如果你在单次游戏中全押,发生这种情况的概率则要大得多。虽然讨论合适的下注大小超出了本文的范围,但凯利准则可能是一个有用的起点。

在实践中,这可能意味着从每月预测改为每日预测。当然,这只是一个非常简单的建议。受其他因素影响,每日预测可能仍然不够准确或完全无用。在这一点上,你和你的利益相关者的专业知识对于找到正确的平衡是必要的。

3) 有时候,根本不玩游戏更好

让我们面对现实吧,总有一些情况下你只能在长期中遭遇损失。如果你的时间序列的信噪比过低,可能会无法提供有用的预测。

资金雄厚的对冲基金正在为替代数据支付天价。所有这些只是为了使他们的预测比竞争对手的稍微准确一些。除非你能获得相同的数据(即使数据真的很有用),否则你不太可能在相同的投注上持续超越他们。

如果你已经到了这一步,你可能需要寻找新的数据来改善你的预测。如果这样仍然没有帮助,完全依赖于相应的预测也可能有意义。

创建多个相关汇总统计的点预测

不再通过 MSE 最小化(或通过 MAE 最小化中位数)来关注均值预测,

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

(作者提供的图像)

你还可以预测描述你分布的其他量。

示例 1中,最明显的选择是条件方差,

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

(作者提供的图像)

你可以在这篇文章中找到如何预测条件方差的简要概述。

一旦你的模型预测出高波动期,你可以决定采取更安全的策略。“采取更安全的策略”的含义显然取决于你的预测问题的背景。

示例 2 可能也会受益于条件方差预测。然而,请注意条件偏度在这里也发挥作用。处理这种情况的一种方法可能是预测条件分位数,即

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

(作者提供的图像)

这被称为分位数回归,例如,sklearn 的 GradientBoostingRegressor 实际上实现了相应的损失函数。

你应该选择哪些量最终将取决于你的具体问题。这里最大的优势是你不对底层分布做任何假设。而是让你的模型“学习”你关心的分布的重要方面。

另一方面,采用这种方法进行随机优化将是困难的。毕竟,你只是将最相关的信息压缩成几个点预测。如果你想根据某些预测计算正式的最佳决策,你可能会需要

将你的点预测替换为概率预测

这是最具挑战性但也最全面的方法。正如我们所见,概率方法的成功往往依赖于你选择的概率分布。

从技术上讲,非参数和机器学习方法也可以从数据中学习概率分布。不过,请记住,时间序列问题通常涉及的观测量远少于典型的机器学习用例。因此,这些方法在这里很容易过拟合。

尤其是如果你是 Python 用户,你可能需要自己实现许多模型。与 R 不同,Python 的预测生态系统似乎更加专注于点预测。然而,如果你只需要类似于 SARIMAX 的解决方案,statsmodel 将是你的朋友。

下面,我还总结了我们到目前为止讨论的三种不同的预测方法。请记住,这三种方法各有优缺点。

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

比较不同的预测风格。(图片由作者提供)

结论

希望你现在对点预测的陷阱有了更好的了解。虽然点预测本身并不是坏事,但它们只展示了不确定世界中正在发生的事情的不完整图景。

另一方面,概率预测提供了对给定时间序列未来的更丰富视角。如果你需要一个可靠的方法来处理现实世界复杂系统的各种不确定性,这就是最佳选择。不过,请记住,这条路径在很多情况下会需要更多的手动工作。

参考文献

[1] 汉密尔顿,詹姆斯·道格拉斯。时间序列分析。普林斯顿大学出版社,2020 年。

[2] 亨德曼,罗布·J.,& 阿塔纳索普洛斯,乔治。预测:原则与实践。OTexts,2018 年。

最初发布于 https://www.sarem-seitz.com 2023 年 1 月 1 日。

你什么时候应该微调 LLM?

原文:towardsdatascience.com/when-should-you-fine-tune-llms-2dddc09a404a

最近有一波令人兴奋的开源 LLM 可以进行微调。但这与使用闭源 API 比较如何呢?

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

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

我经常被问到这个问题——LinkedIn 上的朋友们问我如何微调开源模型如 LLaMA,企业们试图弄清楚销售 LLM 托管和部署解决方案的商业案例,还有一些公司试图利用 AI 和 LLM 应用于他们的产品。但当我问他们为什么不想使用像 ChatGPT 这样的闭源模型时,他们并没有真正的答案。所以我决定以一个每天应用 LLM 解决业务问题的人的身份来写这篇文章。

闭源 API 的案例

你尝试过为你的用例实现 ChatGPT API 吗?也许你想总结文档或回答问题,或者只是想在你的网站上拥有一个聊天机器人。往往你会发现 ChatGPT 在多种语言任务上表现得相当不错。

一个普遍的看法是这些模型太贵了。但以 $0.002/1K tokens 的价格,我敢打赌你至少可以在几百个样本上尝试一下,评估一下 LLM 是否适合你的具体应用。事实上,以每天数千次 API 调用的频率,ChatGPT API 比托管自定义开源模型的基础设施便宜得多正如我在这篇博客中所写的。

一个论点是,假设你想回答成千上万份文档中的问题。在这种情况下,是否不如直接在这些数据上训练或微调一个开源模型,然后让微调后的模型回答关于这些数据的问题更简单?事实证明,这并不像听起来那么简单(由于各种原因,我将在下文的微调数据标注部分讨论)。

但是,ChatGPT 可以通过上下文回答包含数千份文档的问题,这里有一个简单的解决方案。基本上是将所有这些文档作为小块文本存储在数据库中。

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

将文档卸载到数据库中以进行大规模 LLM 查询 | Skanda Vivek

将所有必要信息提供给模型以回答问题的任务现在从模型架构转移到了包含文档块的数据库中。

相关的文档可以通过计算问题和文档块之间的相似性来找到。这通常通过将文档块和问题转换为词嵌入向量,计算块与问题之间的余弦相似度,最后只选择那些高于某个余弦相似度的块作为相关背景来完成。

最后,问题和背景可以组合成一个如下的提示,并输入到像 ChatGPT 这样的 LLM API 中:

prompt=f"Answer the question. Context: {context}\n Question: {question}"

你可能会问——为什么不将整个文档和问题一起放入提示中,而是将其分成块?我们将文档分成块而不是将所有文档组合成一个提示的原因是 LLMs 对最大输入和输出长度有一定的限制。对于 ChatGPT,这个限制是 4096 个令牌,或大约 6-7 页的文本。

将文档卸载到数据库并使用封闭的 LLM API 查询可能在答案显然存在于这些文档中的情况下效果很好。但也有一些情况可能会产生次优结果,例如,如果你希望 ChatGPT 提供关于某个小众话题的专家观点——例如,预测美国经济的财务前景。

领域特定的 LLMs

好吧,你试过 ChatGPT 或 BARD——但你不喜欢。答案有些偏差。例如,你问了 ChatGPT:

“美国经济可能会持续多久的通货膨胀?”

然后它返回:

作为一个 AI 语言模型,我不能确定地预测未来,特别是涉及复杂经济系统时。然而,我可以提供一些信息和观点,可能有助于你理解当前的情况。

通货膨胀是一个复杂的现象,可能由多种因素引起,例如需求增加、供应限制等……

不幸的是,这对你来说还不够好。当然,你可以给 ChatGPT 提供一些关于美联储主席杰罗姆·鲍威尔最近声明的新闻。但这并不能给你与——嗯,杰罗姆·鲍威尔,其他人也可以!或其他专家交流时获得的丰富领域经验。

想一想成为某个领域专家所需的条件。虽然一部分是阅读相关书籍,但更多的是与领域内的专家互动,并从经验中学习。虽然 ChatGPT 已经接受了大量金融书籍的训练,但它可能没有经过顶级金融专家或其他特定领域专家的训练。那么,如何使 LLM 成为金融领域的“专家”呢?这就是微调发挥作用的地方。

微调 LLMs

在讨论微调 LLMs 之前,我们先聊聊像 BERT 这样的较小语言模型的微调,这在 LLMs 之前是很常见的。对于像 BERT 和 RoBERTa 这样的模型,微调就是传递一些上下文和标签。任务定义明确,比如从上下文中提取答案,或将电子邮件分类为垃圾邮件或非垃圾邮件。如果你对微调语言模型感兴趣,我写了一些可能对你有用的博客文章:

## 微调 Transformer 模型以进行自定义数据上的问答 ## 微调 Transformer 模型以进行自定义数据上的问答

关于如何在自定义数据上微调 Hugging Face RoBERTa QA 模型并获得显著性能提升的教程

## 通过微调定制文本分类的 Transformer 模型 ## 微调 Transformer 模型以进行自定义文本分类

关于如何通过微调 DistilBERT 模型来构建垃圾邮件分类器(或其他分类器)的教程

## 通过微调定制文本分类的 Transformer 模型

然而,大型语言模型(LLMs)之所以备受关注,是因为它们能够通过改变提示的方式无缝地执行多个任务,你的体验类似于与另一端的人对话。我们现在希望的是将这些 LLM 微调为某个特定领域的专家,并像“人类”一样进行对话。这与在特定任务上微调像 BERT 这样的模型截然不同。

最早的开源突破之一是由一组斯坦福研究人员完成的,他们微调了一个 7B LLaMa 模型(Meta 早些时候发布)并称之为 Alpaca,花费不到 600 美元进行 52K 指令的训练。随后,Vicuna 团队发布了一个 130 亿参数的模型,达到了 ChatGPT 90% 的质量

最近,MPT-7B transformer被发布,它能够处理 65k 个 token,是 ChatGPT 输入大小的 16 倍!这次训练从零开始,耗时 9.5 天,成本为 200k$。作为领域特定 LLM 的一个例子,Bloomberg 发布了一个类似 GPT 的模型BloombergGPT,专为金融领域设计,也是在从零开始训练的情况下构建的。

最近在训练和微调开源模型方面的进展只是小型和中型公司通过定制化 LLM 丰富其产品的开始。那么,你如何决定何时进行微调或训练整个领域特定的 LLM 呢?

首先,重要的是要清楚地确定闭源 LLM API 在你领域中的局限性,并说明让客户以更低成本与领域专家对话的必要性。对大约十万条指令进行微调并不昂贵——但获取正确的指令需要仔细思考。这也是你需要有一点大胆的地方——我目前还没想到很多领域中微调模型在领域特定任务上比 ChatGPT 表现得更好的情况,但我相信这很快就会出现,任何做得好的公司都将获得回报。

这就引出了从头开始训练 LLM 的理由。是的,这可能需要花费数十万美元,但如果你能提供充分的理由,投资者会很乐意出资。在最近的IBM 采访中,Hugging Face 首席执行官 Clem Delangue评论说,定制化 LLM 很快可能像专有代码库一样普遍,并成为在行业中具有竞争力的关键组成部分。

关键要点

应用于特定领域的大型语言模型(LLMs)在行业中可以非常有价值。它们有3 个逐步增加的成本和定制化水平

  1. 闭源 API + 文档嵌入数据库: 这一解决方案可能是最容易上手的,并且考虑到 ChatGPT API 的高质量——可能会提供足够好(如果不是最好的)性能。而且成本低廉!

  2. 微调 LLMs: 最近对类似 LLaMA 模型的微调进展表明,这样的微调成本大约为**~500$**,可以在某些领域获得类似于 ChatGPT 的基线性能。如果你有一个包含~50–100k 条指令或对话的数据库,微调一个基线模型可能是值得的。

  3. 从零开始训练: 如 LLaMA 和最近的 MPT-7B 模型所示,这种方式的成本大约为**~100–200k**,需要一到两周的时间。

既然你已经掌握了这些知识——那就去构建你的定制领域特定的 LLM 应用吧!

如果你喜欢这篇文章,关注我——我写关于在实际应用中应用最先进的自然语言处理(NLP)技术的话题,更广泛地说,也涉及数据与社会之间的交集。

随时在 LinkedIn上与我联系!

如果你还不是 Medium 会员,并且想要支持像我这样的作者,请通过我的推荐链接注册: https://skanda-vivek.medium.com/membership

以下是一些相关的文章:

LLM 经济学:ChatGPT 与开源

如何构建一个基于 ChatGPT 的应用?

提取式与生成式问答——哪种更适合你的业务?

为定制数据微调 Transformer 模型以进行问答

释放生成式 AI 为你的客户带来的力量

你应该在什么时候停止寻找?

原文:towardsdatascience.com/when-should-you-stop-searching-a439f5c5b954?source=collection_archive---------3-----------------------#2023-03-31

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

停车,图像来源于 Dall-E 2。

关于最优停止的介绍以及它如何与数据科学相关

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

·

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

在我们的日常生活中,我们经常面临需要做出具有重大影响的决定的挑战。无论是选择职业、寻找停车位、购买房屋,还是决定与谁共度余生,我们总是在不断评估和权衡不同的选项。数学家们用来处理决策的方法之一叫做“最优停止”。

在这篇文章中,你将了解什么是最优停止,以及如何在不同的实际情况中使用它。可能令人惊讶的是,你作为数据科学家也可以使用它。

让我们考虑一个现实生活中的例子来理解最佳停止。假设你在面试候选人,你有有限的时间面试每个候选人,一旦你面试了一个候选人,你必须决定是否招聘或继续下一个候选人。做出决定后,你不能重新考虑。目标是招聘到最佳候选人。在某一时刻,你无法知道一个候选人是否是最佳的,因为你还没有见过所有候选人!有两种失败的方式:你可能停得太早,或者停得太晚。

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

随时间变化的候选人。每个候选人之后你决定招聘或不招聘。图片由作者提供。

在上图中,你可以将人员替换为房子、工作机会、狗……实际上是你想做决定的任何事物,并且选择是顺序出现的。作为数据科学家,你可能会在不同领域遇到最佳停止问题,比如医疗保健、营销或金融。

什么时候停止?

那么如何做出最佳决策?这就是最佳停止发挥作用的地方。最佳停止是一个数学概念,涉及根据有限的信息找到做决策的最佳时机。有限的信息等于你直到那时所拥有的所有经验。

让我们回到引言中的面试例子。一个常用的策略是先看后跳:对于固定数量的候选人,你先观察。你不招聘,但你收集信息。完成这固定数量的面试后,你招聘第一个比你已见过的所有人都好的候选人。但你应该花多少时间搜索(观察)?我们可以通过模拟问题来轻松测试不同策略的成功率。为了简单起见,我们从 3 个候选人开始。

在下图中,我们直接招聘(所以没有观察期)。我们只面试起始候选人,并直接招聘。我们有 33.3%的机会招聘到最佳候选人(编号 1),但也有 33.3%的机会招聘到其他候选人(编号 2 或 3):

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

场景 1:不观察,直接跳过。图片由作者提供。

我们的第二个选项是不要招聘我们面试的第一个候选人(观察),然后如果第二个候选人比第一个更好,则招聘第二个候选人。如果第二个候选人不比第一个更好,我们会自动选择最后一个候选人。这个策略成功吗?这是在这种情况下发生的情况(橙色点是已招聘):

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

场景 2:不招聘第一个候选人,然后如果下一个更好则招聘,否则招聘最后一个。图片由作者提供。

现在我们在 50%的可能序列中招聘到最佳候选人!而我们只有在 6 分之一的情况下招聘到最差候选人,而不是三分之一。

最终的情境是我们不聘用前两名候选人,这意味着我们总是会得到最后一名候选人。这与情境一一样好,因为在这种情况下,我们会在三次中有一次得到最佳候选人。但等等……在这种情境下我们花费了更多的时间进行面试!这确实如此,这也是为什么大多数人会更喜欢情境 1 而不是情境 3。在三个候选人的情况下,我们的最佳停止点是面试一个候选人之后,即 33.3%的所有候选人(情境 2)。

如果我们增加候选人的池子会发生什么?对于给定的候选人数,我们可以通过模拟计算最佳的停止百分比。下图对此进行了可视化:

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

跳跃前查看的候选人百分比。每个候选人和查看组合的 100000 次试验的模拟。图片由作者提供。

精确地说:在这个问题中选择最佳候选人的概率会收敛到 1/e,约为 0.368。

我们还可以计算如果坚持上述图表中的查看百分比,我们聘用最佳候选人的百分比:

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

如果你坚持最佳查看百分比,你聘用最佳候选人的百分比。再次基于模拟。图片由作者提供。

37 在这里似乎是魔法数字!好消息是:你也可以将这一点应用于时间。如果你有时间限制,可以将总时间的 37%用于寻找,然后选择下一个最好的候选人。

这种方法有一些缺点:如果在寻找阶段已经面试了最佳人选,你最终会自动得到你面试的最后一位候选人。而这可能是最差的候选人!此外,你还需要花费几个小时或几天的时间来面试每一个人,尤其是当候选人数较多时。我们应该将搜索成本纳入我们的模型中。

搜索在现实生活中是如何运作的?

就像许多数学模型一样,将 37%的规则直接应用于现实生活中可能会很困难。在现实生活中,规则并不明确,而且搜索是有成本的。

在面试示例中,我们假设在面试候选人之前我们对他们一无所知(无信息)。但这在现实生活中相当不切实际:你有简历、LinkedIn 页面,也许还有案例研究或问卷,因此你可以在面试开始之前就开始比较候选人。我们在第一个示例中另一个假设是候选人总是接受录用通知。并且我们不能回到早期的候选人。在实际的面试设置中,你可以面试多个候选人,并在面试了一些之后决定哪个最合适。

对于这些情况,你可以将知识添加到模型中,看看这会如何影响你的决策。我们来逐一看看它们。

无信息 vs. 完整信息

在博弈论中,有不完全信息和完全信息的概念。假设你了解候选人的能力,并且可以将他们放在一个百分位数中。在这种情况下,你可以使用一个百分位数阈值来决定是否应该聘用候选人,具体取决于他们被面试的时间:

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

如果总共有 10 名候选人,则第 n 名候选人的聘用阈值。图片由作者提供。

在所有面试中,你会变得越来越不挑剔,因为随着你接近候选人名单的尾声,出现更好候选人的机会会变得越来越小。

拒绝与回到之前的候选人

在现实生活中,候选人可能会拒绝邀请。特别是那些收到更多邀请的候选人(这些候选人通常更优秀)。如果候选人拒绝邀请的可能性很高,我们应该改变我们的策略。

我们在现实生活中的另一个选项是回到我们已经面试过的候选人。在这种情况下,我们可以面试更多的候选人,并且回到之前被拒绝的某个人那里提出邀请。

如果允许回退,但在第二次报价时有 50%的拒绝概率,则观察阶段应为 61%,而不是 37%。在这种情况下,聘用最佳候选人的概率也会增加到 61%。

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

当第二次报价的拒绝概率为 50%时,寻找 61%。图片由作者提供。

我不需要最好的,只要一个好的就行。

另一个有趣的变体是:如果你并不特别关注最佳候选人,而是想要一个比大多数人更优秀的候选人?或者你想要两个最佳候选人中的一个?

在这种情况下,最优策略有点类似于寻找最佳候选人时的策略。你开始时设定基准:观察并且不聘用。然后,在下一个阶段,如果出现新的最佳候选人则停止。如果在那个阶段没有找到最佳候选人,那么在下一个阶段,如果出现最佳第二佳的候选人则停止。这个过程会持续到你遇到最终候选人。

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

如果有七个候选人,而你想聘用两个最佳候选人中的一个,使用这个策略。图片由作者提供。

最优停止与数据科学

在不同的方式中,最优停止与数据科学有关。最直接的方法有:用数据科学解决最优停止问题,以及在数据科学项目中使用最优停止。

用数据科学解决最优停止问题

对于有限时间范围内,动态规划是解决最优停止问题的最简单方法。在确定当前选项的价值时,可以使用不同的数据科学方法:

  1. 最小二乘蒙特卡洛(LSMC)

    一个有趣的现实应用是金融市场中的期权交易。在到期日前,期权持有人有权以预定价格买入或卖出标的资产。LSMC 使用路径模拟来评估当前期权,这有助于决策(买入或卖出)。

  2. 强化学习

    使用深度 Q 学习来确定最优停止问题中的最佳策略也是可能的。这篇论文解释了这一点,并说明了与 LSMC 相比使用它的好处。

  3. 深度学习 神经网络可以通过不同方式解决最优停止问题。当然如上所述在深度 Q 学习中,也可以通过预测在合适停止时间点的预期收益。另一种实现方式是调整神经网络的参数,从而表达不同的随机停止时间。

  4. 树和可解释性 许多最优停止算法的一个缺点是可解释性:解释它所做出的决策可能很困难。在某些情况下这不是问题,但在其他情况下可能是。可解释性有其好处,因为你可以从模型中学习并看到当前状态与行动之间的联系。在这篇论文中,研究人员创建了一个树模型作为策略,以实现可解释性。

在数据科学项目中使用最优停止理论

最优停止问题无处不在:在医疗、金融、管理等领域。最优停止对数据科学家也可能相关。为什么?因为它提供了一个框架,用于决策何时停止数据收集或在问题空间中寻找最佳解决方案。在许多数据科学问题中,收集额外的数据或探索更多选项可能既耗时又昂贵,或者两者兼有。你可以选择确定最优停止点,以最小化成本并最大化获得的信息价值。

在营销中,数据科学家可能会遇到最优停止问题。假设你有一个固定的产品库存,想在一定时间内销售该产品。通过价格促销,你可以影响人们的购买行为。在这种情况下,剩余库存可以决定你是否应该停止促销以最大化利润,或者继续进行促销。

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

营销示例:根据库存水平在固定时间段后决定是否继续或停止对某一产品的促销。目标是最大化利润。图片由作者提供。

在医疗保健领域,你也可以找到最佳停止问题。一个例子是等待器官移植的患者。与器官相关的患者价值通过估计的质量调整生命年(QALYs)来衡量,这些 QALYs 是患者如果移植发生将获得的。数据科学家的目标是找到一个政策,以最大化 QALYs。

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

Dall-E 2 生成的另一个停车场。

结论

下次你需要做决定时,希望你能利用这篇文章中的一些技巧!如果过程类似于面试的例子,比如当你想买房时,花 37%的时间来观察,然后选择下一个最佳的房子。如果两个最好的房子中有一个足够好,你也可以在两个寻找最佳的阶段后选择迄今为止看到的第二好房子。

为了回答所有问题:当你寻找停车位时,有哪些规则?在停车时,存在寻找好位置的时间与从停车位到最终目的地之间的步行时间之间的权衡。在这种情况下,一切都取决于占用率。如果占用率为 50%,你可以继续开车,直到尽可能靠近你的目的地。但如果占用率为 98%,你应该在距离目的地 35 个车位时,选择你看到的下一个空位。

研究表明,人们在现实生活中会比最佳停止百分比 37%更早停止。也许原因是之前提到的一些现实生活中的复杂因素,比如搜索成本或第二好的选择也足够好。作为数据科学家,如果你处理的是最佳停止问题,可以利用数据科学。你也可以使用最佳停止理论在最佳时点停止搜索。

相关

## 解决多臂赌博机问题

一种强大而简单的应用强化学习的方法。

[towardsdatascience.com ## 用 120 行代码解决非 ogram 问题

拼图、组合和解决方案动图。

[towardsdatascience.com ## 深度强化学习代理玩蛇游戏

带有搞笑花絮

[towardsdatascience.com

当电子表格不够用时:关系数据库的课程

原文:towardsdatascience.com/when-spreadsheets-arent-good-enough-a-lesson-in-relational-databases-2e5b0b847f5a?source=collection_archive---------8-----------------------#2023-05-12

SQL 教程

数据库规范化、关系数据库以及你为何需要它们

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

·

关注 发表在 Towards Data Science ·11 分钟阅读·2023 年 5 月 12 日

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

图片由 Ivan Liu Hu 提供,发布于 Unsplash

你是否曾经等待几分钟才能完全加载和打开一个 Excel 电子表格?在编辑电子表格并尝试提取一些见解时,它是否不断滞后和冻结?对于任何持续在线收集数据的组织来说,这种情况并不少见。但你可以通过实施结构化的关系型数据库来更快地获得结果。

那么关系型数据库到底是什么?它是一组表。可以将每个表视作存储业务不同信息的 Excel 电子表格,但想象一下,如果你可以将这些 Excel 电子表格连接在一起。

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

电子表格存在于孤岛中,而关系型数据库在表之间建立了关系。

像 MySQL 或 Oracle 这样的关系型数据库非常有用,因为它们设计用来组织大量数据,这可以帮助你更高效地管理数据。如果设计得当,它们可以节省你在任务和分析上的大量时间。

因此,如果你在公司工作并且预期数据量将增长——无论是与订单、客户、采购历史还是营销互动相关——本文旨在解释如何开始思考重组当前的数据实践,以转换为关系型数据库。

关系型数据库的优势是什么?

这里是实施关系型数据库的一些优势以及业务可能克服的挑战:

  • 高效分析: 关系型数据库允许更快速地检索信息,然后使用 SQL(结构化查询语言)进行分析,以便运行查询。

  • 集中数据管理: 由于关系型数据库通常要求每个表的每一列输入特定类型或格式的数据,因此不太可能出现重复或不一致的数据。

  • 可扩展性: 如果你的业务正在经历高速增长,这意味着数据库将扩展,而关系型数据库可以容纳增加的数据量。

你可能仍在考虑关系型数据库是否适合你的公司。让我们通过一个例子来将这一点放入背景中。

示例:高速增长的电子商务零售商

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

照片由 Iga Palacz 拍摄,来源于 Unsplash

你为一个本地的二手家具电子商务零售商工作,每周你会向团队提供一份总结销售数据的报告,这有助于采购团队了解他们应该采购更多的商品。

目前,由于库存数据已经输入到 Excel 中,你大部分的分析都是在电子表格上进行的。你很可能使用数据透视表或 vlookup 来计算某些产品或品牌的周度、月度或年度销售百分比变化。最近,你注意到由于销售的库存增加,打开电子表格和进行简单分析都需要很长时间。

在公司处于增长阶段,记录了更多的采购、客户和库存时,Excel 可能无法再有效地完成你的常规任务。这时,关系型数据库可以通过能够大规模存储信息并通过 SQL 提取数据来提供帮助。

如果你对这些情况有共鸣,那么你可能会想知道如何将当前的数据设置转变为关系型数据库。考虑如何结构化你的数据库的一个好方法是进行一个称为数据库规范化的过程。

数据库规范化的基础

数据库规范化是修改现有数据模式的过程,以使其符合一系列渐进的规范形式。简单来说,就是确保数据以结构化的方式组织。这给你一套规则,帮助你开始对数据进行分类,并形成一个适合你的布局。

通过在数据库中建立结构,你能够帮助建立几个重要的事物:数据完整性和可扩展性。数据完整性确保数据的输入正确和准确,而可扩展性则确保你以一种更具计算效率的方式组织数据,以便在运行 SQL 查询时更为高效。

但是,当我们谈到规范形式时,我们到底在说什么呢?

规范化过程的快速概述

组织过程的每个阶段都被称为“规范形式”。

爱德华·C·科德首次提出了这一概念 在这里,最初描述了七种规范形式。 然而,为了使本文简洁,我们将解释并讲解前三种规范形式,特别是因为数据库通常被认为“规范化”,如果它符合第三范式。更全面的形式概述和很好的教程可以在 这里**找到。

第一范式 (1NF)

  • 每个表中每一列的值必须简化为最基本的值,也称为原子值。原子值是指列中没有值集合的情况。

  • 数据库中没有重复的列或行。

  • 每个表应有一个主键,这可以定义为一个非空的、唯一的值,以标识每一行的插入。

第二范式 (2NF)

  • 符合第一范式规则。

  • 调整列,使每个表只包含与主键相关的数据。

  • 外键用于建立表之间的关系。

第三范式(3NF)

  • 符合第一范式和第二范式的规则。

  • 需要移动或删除那些传递依赖的列(属性),即那些依赖于其他列的列,而这些列不是外键或主键。

现在我们已经掌握了一些定义,让我们在一个实际的例子中应用这些规范形式规则,以便这些概念能够深入理解。

教程:在加拿大零售商中进行受众分割

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

charlesdeluvioUnsplash 提供的照片

在这个例子中,我使用了 MySQL 来执行查询,如果你想在其他 SQL 服务器(如 PostgreSQL 或 Oracle)中跟随操作,每个脚本都应进行相应的调整。如果你对 MySQL 不太熟悉,我建议你阅读这些文章 这里 以获取安装说明,以及 这里 以开始使用。请注意,跟随代码假设你具备一些基础的 SQL 知识。

以下数据并非基于实际的电子商务/订阅数据,仅供演示目的使用。合成数据已被创建,以便探索本文讨论的概念(在实际企业中,你将处理数千行数据,而不仅仅是二十行)。要跟随本教程,请查看我的 Github 仓库 这里,其中包含了数据的完整转换过程,包括数据创建、数据转换和查询。

数据库规范化实践

假设你刚刚被聘为一家电子商务零售商的分析师。你的公司正在进行一次重要的促销活动,你被分配了以下任务:

将我们当前的客户和电子邮件订阅者分开,以便我们可以向每个细分市场发送不同的电子邮件优惠,从而推动销售期间的购买。

当你访问数据库时,有几件事会立即引起注意。最显著的是,电子邮件订阅和客户信息被存储在不同的表格中,并且没有建立任何关系,这在执行分配任务时带来了挑战。然而,值得庆幸的是,你拥有相对完整的数据,并且通过一些调整可以在两者之间建立关系以完成任务。以下是当前数据库的详细信息:

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

上述表格包含了与客户和电子邮件订阅者相关的众多变量,包括他们的地理位置、他们以前收到的电子邮件以及相关的电子邮件指标。

第一范式(1NF)

我们可以做的第一件事是查看每列中包含的数据和数据类型,在将每个文件上传到 SQL 服务器之后。我们可以通过执行以下查询来完成此操作:

看起来我们还有很长的路要走才能符合第一范式!回顾一下,达到第一范式的要求涉及正确的行标识和正确分组数据。我们当前的设置违反了第一范式的所有三条规则,这主要集中在email_newsletter表中:

  • 目前email_newsletter没有主键。这迫使数据库用户通过电子邮件地址搜索每个订阅者,这并不被认为是好的实践,因为用户可能会更新他们的电子邮件地址以及隐私问题。

  • email_newsletter中的数据还没有达到最简化的形式。经过进一步分析,列内容可以分为两个列,如subject_linepromo_code

  • email_newsletter中,重复的组是电子邮件发送和内容。一个用户可以收到很多电子邮件,因此最好的办法是将其从表格中分离出来。

为了解决第一个问题,我们可以在表格的第一列中添加一个唯一的主键到email_newsletter,并运行以下查询,其中email_sign_up_id是列名,数据类型是INTEGER

通过这个查询,我们添加了一个唯一标识的主键,它会随着每条新数据记录的增加而自动递增。

对于第二次修订,我们需要将contents拆分为各自的subject_linepromo_code字段,我们发现每个字段通常由逗号分隔,我们可以用它来拆分每个字段:

我们的结果是将每列分隔到最简化的形式,以便更清晰地看到每列的类别。

最后,我们可以通过将所有与电子邮件内容相关的数据组织到一个新的表格email_distribution中来去除重复的组,别忘了将原表中的数据添加进去!(查看 GitHub here 获取完整的 SQL,包括数据加载):

通过上面的查询,我们将电子邮件内容拆分到一个单独的表中,以确保我们不会丢失任何信息。现在我们需要使用以下查询将其与email_newsletter表连接起来,使用外键:

这有助于我们遵守第一范式,作为最终步骤,我们可以删除任何冗余的列,并查看我们的布局,然后再进入第二范式:

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

第二范式(2NF)

为了遵守第二范式的规则,我们需要实现第一范式的合规性✓,建立表之间的关系,并确保非键列依赖于主键。以下是我们如何将设置转换为第二范式的步骤:

  • customer表目前与其他两个表没有连接,存在在email_opt_in列中已经同意提供其电子邮件的客户未被联系的情况。

  • email_newsletter中存在与主键不相关的列,例如email_openclick_through,这些与其个人信息不是非常相关,因此我们可以考虑将它们分离出来。

我们希望在customeremail_distribution表之间建立关系,以便拥有我们的两个电子邮件前景和当前客户的两个部分。但是,如果按照目前的情况来连接可能会显得混乱,因为一个客户可能会收到多封电子邮件并干扰主键。因此,一个逻辑的中间步骤是创建一个新表,可以放置在两者之间,我们将其命名为customer_newsletter_metrics,以允许这些属性存储在自己的表中:

该表有助于连接我们整个数据库的链接,因为我们在创建表时已经说明了外键,表中的每列都与主键相关且依赖。

我们现在的设置几乎完成了,但有点不平衡——因为我们为客户建立了一个已建立的指标表,但没有为电子邮件前景建立一个表。通过对这一组应用相同的处理,可以帮助减轻未来的问题,因为如果前景的电子邮件更改,则在单独的表中存储这些信息会减少修订(请在 GitHub 上的完整代码这里进行跟踪)。

email_newsletter重命名为email_newsletter_metrics并创建我们的email_prospect表后,我们的数据库经过了 2NF 转换:

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

第三范式(3NF)

我们示例的最后一步是遵循第三范式规则,其中包括完成第一和第二范式规则 ✓,最后,分离出彼此依赖的非关键列。

总体而言,我们到目前为止做得很好,以减少非关键列之间的相互依赖,尽管如果我们更仔细地查看客户表,我们会发现城市和邮政编码是相互依赖的,因为邮政编码与客户居住地有关,但也与客户居住的城市有关。在客户搬迁时,这可能会在长期运行中出现问题,一个列可能会被更新,但可能不会更新另一个列。

鉴于此,我们可以创建一个名为postal_code的新表,从customer表中分离出postal_codecity,并在这两个表之间建立连接:

上述代码块的最后完成了我们的规范化过程(耶!),我们最终转换后的数据库如下所示:

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

我们重新访问的初始请求:使用关系数据库

所以我们通过符合第一、第二和第三范式的关系数据库已经准备好了,为什么不检查一下最初给我们的要求:

将我们当前的客户和邮件订阅者分开,以便我们可以向每个数据段发送不同的邮件优惠,以推动销售期间的购买。

让我们形成两个简单的查询语句来获取每个数据段的信息:

查询将产生两个表,输出如下所示:

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

现在你可以自信地发送下一封销售邮件了,确保表格之间没有重复或遗漏的联系人。更重要的是,我们已经按照这样的结构组织了数据库,使数据分析变得更容易,并且为更多客户做好了准备。

一些结束语

通过识别日常工作中诸如数据库缓慢或复杂等痛点,本文旨在提供实施关系数据库作为解决方案的方法。通过使用从第一到第三范式的技术,你可以将数据拆分为易于处理和减少错误的可管理块。

作为友好的最后提醒,关于所用代码的完整解析,请访问我的 GitHub 仓库 这里。希望这篇文章能够激发你对关系数据库的新兴趣✨。感谢阅读和关注!

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

参考文献

  1. E.F.Codd, 大规模共享数据银行的关系模型,1970 年 6 月,《ACM 通讯》。

  2. E.burns, SQL 数据库规范化完整指南,2021 年 2 月,Medium。

  3. C.Andreou, SQL 介绍,2019 年 8 月,Medium。

随机策略何时优于确定性策略

原文:towardsdatascience.com/when-stochastic-policies-are-better-than-deterministic-ones-b950cd0d60f4

为什么我们让随机性决定强化学习中的动作选择

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

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

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

如果使用确定性策略进行石头剪刀布游戏将变得非常无聊 [照片来源: Marcus WallisUnsplash]

如果你习惯于确定性决策策略(例如,Deep Q-learning),你可能会忽视随机策略的必要性和使用。毕竟,确定性策略提供了一个方便的状态-动作映射 π:s ↦ a,理想情况下甚至是最佳映射(即,如果所有贝尔曼方程都被完美地学习了)。

相比之下,随机策略 — 通过给定状态下的动作的条件概率分布表示,π:P(a|s) — 看起来相当不方便且不精确。我们为什么要让随机性来指导我们的行动,为什么要将最佳已知决策的选择留给偶然?

实际上,从现有的演员-评论家算法数量来看,大量的强化学习(RL)算法确实使用了随机策略。显然,这种方法必然有其好处。本文讨论了四种情况下,随机策略优于其确定性对手的情况。

## 强化学习的四种策略类别

towardsdatascience.com

I. 多智能体环境

可预测性并不总是好事。

剪刀石头布的游戏中可以清楚地看到,确定性策略会惨遭失败。对手会很快发现你总是玩石头,并据此选择反制动作。显然,纳什均衡是一个均匀分布的策略,以概率 1/3 选择每个动作。如何学习它?你猜对了:随机策略。

尤其是在对抗性环境中——对手有不同的目标并尝试预测你的决策——在策略中引入一定程度的随机性通常是有益的。毕竟,博弈论规定通常没有纯策略能够始终对对手做出单一的最佳回应,而是将混合策略作为许多游戏的最佳动作选择机制。

不可预测性是一个强大的竞争工具,如果需要这种特质,随机策略显然是最佳选择。

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

面对一个值得的对手时,总是玩相同的游戏会很快适得其反。[照片由 Christian Tenguan 提供,来源 Unsplash]

II. 部分观察(POMDP)

在许多情况下,我们对真实问题状态的了解并不完美,而是试图从不完美的观察中推测。部分观察马尔可夫决策过程(POMDPs)领域就是围绕这种状态和观察之间的差异建立的。当我们通过特征来表示状态时,这种不完美同样适用,因为在处理大状态空间时通常需要这样做。

考虑大卫·银的著名别名 GridWorld。在这里,状态通过观察周围的墙来表示。在下面的插图中,在两个阴影状态中,智能体观察到上方和下方都有墙。尽管真实状态是不同的并且需要不同的动作,但它们在观察中是相同的

基于不完美的观察,智能体必须做出决策。价值函数近似(例如,Q-learning)可能很容易陷入困境,总是选择相同的动作(例如,总是向左),从而永远无法获得奖励。ϵ-贪婪奖励可能会缓解这种情况,但仍然大多数时候会陷入困境。

相对而言,策略梯度算法将以 0.5 的概率学会向左或向右走,因此可以更快地找到宝藏。通过认识到智能体对环境的感知不完美,它故意采取概率性行动以对抗固有的不确定性。

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

Aliased GridWorld 中的确定性策略。代理只能观察墙壁,因此无法区分阴影状态。确定性策略将在两个状态中做出相同的决策,这通常会将代理困在左上角。[图片由作者提供,基于 David Silver 的示例,剪贴画来自GDJ [1, 2],通过OpenClipArt.org]

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

Aliased GridWorld 中的随机策略。代理只能观察墙壁,因此无法区分阴影状态。一个最佳的随机策略将在别名状态中以相等的概率选择左和右,从而更有可能找到宝藏。[图片由作者提供,基于 David Silver 的示例,剪贴画来自GDJ [1, 2],通过OpenClipArt.org]

III. 随机环境

大多数环境——尤其是现实生活中的环境——都表现出显著的不确定性。即使我们在完全相同的状态下做出完全相同的决策,相应的奖励轨迹也可能大相径庭。在我们对期望的下游值有一个合理的估计之前,我们可能需要进行许多训练迭代。

如果我们在环境本身中遇到如此巨大的不确定性——如其转移函数所反映的——随机策略通常有助于发现这些不确定性。策略梯度方法提供了一种强大且固有的探索机制,这在基于价值的方法的普通实现中是不存在的。

在这种情况下,我们不一定要将固有的概率策略作为最终目标,但在探索环境时它确实有帮助。概率性动作选择和策略梯度更新的结合引导我们在不确定环境中改进,即使这种搜索最终将我们引导到一个接近确定性的策略

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

在部署随机策略时,控制和环境之间的相互作用生成了强大的探索动态[图片由作者提供]

说实话,如果我们抛开价值函数逼近中的标准ϵ-贪婪算法,还有许多强大的探索策略在学习确定性策略时效果非常好:

## 你应该知道的七种强化学习探索策略

纯探索与利用、ϵ-贪心、玻尔兹曼探索、乐观初始化、置信区间……

towardsdatascience.com

IV. 连续动作空间

虽然有一些变通方法,但在连续空间中应用基于价值的方法通常需要离散化动作空间。离散化越精细,原始问题的逼近程度就越高。然而,这会增加计算复杂性。

以自驾车为例。踩油门的力度、踩刹车的力度、加油的程度——这些都是内在的连续动作。在连续动作空间中,它们可以通过三个变量来表示,每个变量都可以在一定范围内取值。

假设我们为油门和刹车定义了 100 个强度级别,为方向盘定义了 360 度。这样有 100100360=3.6M 种组合,我们有一个相当大的动作空间,但仍然缺乏连续控制的细腻触感。显然,高维度和连续变量的组合通过离散化处理起来特别困难。

相比之下,策略梯度方法完全能够从代表性概率分布中抽取连续动作,使其成为连续控制问题的默认选择。例如,我们可以通过三个参数化的高斯分布来表示策略,学习均值和标准差。

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

驾驶汽车是一个固有的连续控制的例子。动作空间离散化得越细致,基于价值的学习就变得越繁琐 [照片来源 Nathan Van EgmondUnsplash]

向近乎确定性策略的收敛

在结束本文之前,重要的是要强调一个随机策略并不意味着我们会一直做半随机决策直到时间的尽头。

在某些情况下(例如上述的石头剪子布或别名网格世界),最佳策略需要混合动作选择(分别为 30%/30%/30%和 50%/50%)。

在其他情况下(例如,识别最佳老虎机),最佳响应实际上可能是确定性的。在这种情况下,随机策略将收敛到一个近乎确定性的策略,例如,以 99.999%的概率选择某个特定动作。对于连续动作空间,策略将收敛到非常小的标准差。

也就是说,政策将永远不会是完全确定性的。对于撰写收敛性证明的数学家来说,这实际上是一个不错的特性,确保在极限情况下无限探索。现实世界的从业者可能需要稍微务实,以避免偶尔的愚蠢行为。

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

根据问题的不同,随机政策可能会收敛为接近确定性的政策,几乎总是选择产生最高预期奖励的行动 [图片来源于作者]

总结

这里有四种情况下,随机政策比确定性政策更可取:

  • 多智能体环境:我们的可预测性会被其他智能体惩罚。对我们的行动添加随机性使得对手难以预判。

  • 随机环境:不确定的环境需要较高的探索程度,而基于确定性政策的算法并未固有地提供这种探索。随机政策会自动探索环境。

  • 部分可观察环境:由于观察(例如,状态的特征表示)是不完美的真实系统状态的表示,我们难以区分需要不同操作的状态。混合我们的决策可能会解决这个问题。

  • 连续动作空间:否则,我们必须对动作空间进行精细离散化以学习价值函数。相反,基于政策的方法通过从相应的概率密度函数中抽样优雅地探索连续动作空间。

参考文献

www.davidsilver.uk/wp-content/uploads/2020/03/pg.pdf

www.freecodecamp.org/news/an-introduction-to-policy-gradients-with-cartpole-and-doom-495b5ef2207f/

en.wikipedia.org/wiki/Strategy_(game_theory)

解决复杂问题时,第一步是最困难的

原文:towardsdatascience.com/when-tackling-complex-topics-the-first-step-is-the-hardest-d04d60663098?source=collection_archive---------11-----------------------#2023-10-19

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

·

关注 发表在 数据科学前沿 · 发送为 新闻通讯 · 3 分钟阅读 · 2023 年 10 月 19 日

成为一个“初学者”并不是一个有限的状态,你一旦经历便永远离开。只要你致力于持续学习和成长,你会发现自己在职业生涯中多年(甚至几十年!)后仍在努力掌握新的概念和思想。

只要您有坚实的指导和正确的资源来帮助您处理日常工作流中数据和机器学习专业人士每天处理的复杂技术主题,这无疑是一件好事。这正是我们介入的地方:TDS 的作者在解读尖端研究和工具并使其对他人易于理解方面表现出色,无论读者是刚从第一次编程训练营毕业还是科技巨头的高级从业者。

本周,我们精选了几篇最近的文章,正是因为它们覆盖了广泛的主题 —— 从线性代数到图像分类,从一位支持型教师的角度来看,他们并不假设学生在先前知识方面拥有太多背景。它们提供具体可行的信息,并保持易于吸收和消化而不至于简化。祝学习愉快!

  • 生成式人工智能是什么?一份全面的指南自 ChatGPT 推出将近一年以来,生成式人工智能显然已经变得非常流行 —— 人们对其内部运作、潜在好处和当前限制的混淆和误解也如此。玛丽·纽豪泽的入门指南是任何需要在这一及时话题上建立坚实基础的人的可靠资源。

  • **多维度探索是可能的!**当使用得当时,类比是将复杂概念转化为易于理解的思想的强大工具。迭戈·曼弗雷最近的解释就是一个很好的例子:它通过比较主成分分析(PCA)背后的数学来解析,类比为在维度之间移动(出色的插图也帮助了)。

  • 初学者的图像分类学习基本的机器学习工作流程有许多方法;对于她的图像分类介绍,米娜·加沙米选择回顾 2014 年至 2015 年,当时引入了两个开创性的架构 —— ResNet 和 VGG Network。如果您通过深入了解话题背后的背景来更好地吸收知识,那么这篇文章正是为您而写。

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

照片由Jorge Zapata拍摄,来自Unsplash

  • 线性代数 3:向量方程 过去几周,tenzin migmar (t9nz) 一直在分享关于线性代数基础的初学者友好教程。最新的一期,专注于向量方程,是对那些以前觉得这个话题令人生畏的人,或对经验丰富的数据专业人士有帮助的补充资源。

  • 使用 Scikit-Learn 的支持向量机:友好的介绍 如果你仍在建立核心机器学习技能,将一个强大的算法添加到工具箱中是个好主意。Riccardo Andreoni的支持向量机指南很好地平衡了 SVM 背后的理论背景和实际应用。

  • 变换器 — 直观且详尽的解释 我们多年来发布了许多关于变换器架构的高质量文章,但总有另一个方法的空间。Daniel Warfield的详细概述将模型拆解,揭示其构建块,并花时间讲解每个组件的独立作用及其相互关系。

我们希望你有一些额外的时间来阅读我们最近的几篇杰作——它们覆盖了很多内容,而且做得非常,非常好:

  • Yennie Jun 调查了 GPT-4 的数学技能,并聚焦于其在英语和传统上资源不足的语言之间的差异。

  • AI 工具以惊人的速度涌现,Sam Stone 认为是时候更多关注它们的 UI/UX 设计了。

  • Colab 有什么新变化?谷歌的笔记本服务已经存在一段时间,Parul Pandey的概述帮助数据科学家了解最新更新。

  • 处理“megemodels”有其自身的一套挑战和难点;Amber Teng的实践深入展示了如何在不同配置中加载流行的 Llama 2 模型。

  • 在他之前关于创新提示工程技术的研究基础上,Giuseppe Scalamogna提供了一个制定不同类型程序模拟提示的路线图。

感谢您支持我们作者的工作!如果您喜欢在 TDS 上阅读的文章,可以考虑成为 Medium 会员——这将解锁我们整个档案(以及 Medium 上的所有其他帖子)。

当数据集较小时,特征是你的朋友。

原文:towardsdatascience.com/when-the-dataset-is-small-features-are-your-friends-6e7f8dcc819e?source=collection_archive---------9-----------------------#2023-04-03

特征工程可以弥补数据的不足。

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

·

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

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

照片由Thomas T拍摄,发布在Unsplash

在快速发展的人工智能(AI)领域,数据已经成为无数创新应用和解决方案的命脉。确实,大数据集通常被认为是强大且准确的 AI 模型的基础。然而,当手头的数据集相对较小时会发生什么?在本文中,我们探讨了特征工程在克服小数据集限制中的关键作用。

玩具数据集

我们的旅程从创建数据集开始。在这个例子中,我们将进行简单易行的信号分类。数据集有两个类别;频率为 1 的正弦波属于类别 0,频率为 2 的正弦波属于类别 1。信号生成的代码如下。该代码生成正弦波,应用加性高斯噪声,并随机化相位偏移。由于噪声和相位偏移的加入,我们得到多样的信号,分类问题变得不再简单(尽管通过正确的特征工程仍然容易)。

def signal0(samples_per_signal, noise_amplitude):
    x = np.linspace(0, 4.0, samples_per_signal)
    y = np.sin(x * np.pi * 0.5)
    n = np.random.randn(samples_per_signal) * noise_amplitude

    s = y + n

    shift = np.random.randint(low=0, high=int(samples_per_signal / 2))
    s = np.concatenate([s[shift:], s[:shift]])

    return np.asarray(s, dtype=np.float32)

def signal1(samples_per_signal, noise_amplitude):
    x = np.linspace(0, 4.0, samples_per_signal)
    y = np.sin(x * np.pi)
    n = np.random.randn(samples_per_signal) * noise_amplitude

    s = y + n

    shift = np.random.randint(low=0, high=int(samples_per_signal / 2))
    s = np.concatenate([s[shift:], s[:shift]])

    return np.asarray(s, dtype=np.float32) 

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

类别 0 中的信号可视化。

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

类别 1 中的信号可视化

深度学习性能

最先进的信号处理模型是卷积神经网络(CNN)。所以,让我们创建一个。这一网络包含两个一维卷积层和两个全连接层。代码列在下面。

class Network(nn.Module):

    def __init__(self, signal_size):

        c = int(signal_size / 10)
        if c < 3:
            c = 3

        super().__init__()
        self.cnn = nn.Sequential(
            nn.Conv1d(1, 8, c),
            nn.ReLU(),
            nn.AvgPool1d(2),
            nn.Conv1d(8, 16, c),
            nn.ReLU(),
            nn.AvgPool1d(2),
            nn.ReLU(),
            nn.Flatten()
        )

        l = 0
        with torch.no_grad():
            s = torch.randn((1,1,SAMPLES_PER_SIGNAL))
            o = self.cnn(s)
            l = o.shape[1]

        self.head = nn.Sequential(
            nn.Linear(l, 2 * l),
            nn.ReLU(),
            nn.Linear(2 * l, 2),
            nn.ReLU(),
            nn.Softmax(dim=1)
        )

    def forward(self, x):

        x = self.cnn(x)
        x = self.head(x)

        return x

CNNs 是可以处理原始信号的模型。然而,由于其参数众多的架构,它们往往需要大量的数据。然而,最开始,让我们假设我们有足够的数据来训练神经网络。我使用信号生成创建了一个包含 200 个信号的数据集。每个实验重复了十次,以减少随机变量的干扰。代码如下:

SAMPLES_PER_SIGNAL = 100
SIGNALS_IN_DATASET = 20
NOISE_AMPLITUDE = 0.1
REPEAT_EXPERIMENT = 10

X, Y = [], []

stop = int(SIGNALS_IN_DATASET / 2)
for i in range(SIGNALS_IN_DATASET):

    if i < stop:
        x = signal0(SAMPLES_PER_SIGNAL, NOISE_AMPLITUDE)
        y = 0
    else:
        x = signal1(SAMPLES_PER_SIGNAL, NOISE_AMPLITUDE)
        y = 1

    X.append(x.reshape(1,-1))
    Y.append(y)

X = np.concatenate(X)
Y = np.array(Y, dtype=np.int64)

train_x, test_x, train_y, test_y = train_test_split(X, Y, test_size=0.1)

accs = []
train_accs = []

for i in range(REPEAT_EXPERIMENT):

    net = NeuralNetClassifier(
        lambda: Network(SAMPLES_PER_SIGNAL),
        max_epochs=200,
        criterion=nn.CrossEntropyLoss(),
        lr=0.1,
        callbacks=[
            #('lr_scheduler', LRScheduler(policy=ReduceLROnPlateau, monitor="valid_acc", mode="min", verbose=True)),
            ('lr_scheduler', LRScheduler(policy=CyclicLR, base_lr=0.0001, max_lr=0.01, step_size_up=10)),
        ],
        verbose=False,
        batch_size=128
    )

    net = net.fit(train_x.reshape(train_x.shape[0], 1, SAMPLES_PER_SIGNAL), train_y)
    pred = net.predict(test_x.reshape(test_x.shape[0], 1, SAMPLES_PER_SIGNAL))
    acc = accuracy_score(test_y, pred)

    print(f"{i} - {acc}")

    accs.append(acc)

    pred_train = net.predict(train_x.reshape(train_x.shape[0], 1, SAMPLES_PER_SIGNAL))
    train_acc = accuracy_score(train_y, pred_train)
    train_accs.append(train_acc)

    print(f"Train Acc: {train_acc}, Test Acc: {acc}")

accs = np.array(accs)
train_accs = np.array(train_accs)

print(f"Average acc: {accs.mean()}")
print(f"Average train acc: {train_accs.mean()}")
print(f"Average acc where training was successful: {accs[train_accs > 0.6].mean()}")
print(f"Training success rate: {(train_accs > 0.6).mean()}")

CNNs 的测试准确率达到了 99.2%,这是对最先进模型的预期。然而,这一指标是针对这些实验运行得到的,其中训练是成功的。所谓“成功”,是指训练数据集上的准确率超过了 60%。在这个例子中,CNNs 权重初始化对训练至关重要,并且有时会发生问题,因为 CNNs 是复杂的模型,容易遇到由于随机权重初始化不幸带来的问题。训练的成功率是 70%。

现在,让我们看看当数据集较小时会发生什么。我将数据集中的信号数量减少到 20 个。因此,CNNs 的测试准确率达到了 71.4%,准确率下降了 27.8 个百分点。这是不可接受的。不过,接下来该怎么做呢?数据集需要更长,以便使用最先进的模型。在工业应用中,获取更多数据要么不可行,要么至少非常昂贵。我们是否应该放弃这个项目,另谋他路?

不。当数据集很小的时候,特征是你的朋友。

特征工程

这个特定的例子涉及基于频率的信号分类。因此,我们可以应用经典的傅里叶变换。傅里叶变换将信号分解为由频率和幅度参数化的一系列正弦波。结果,我们可以使用傅里叶变换来检查每个频率在形成信号中的重要性。这种数据表示应该简化任务,使得小数据集就足够了。此外,傅里叶变换将数据结构化,以便我们可以使用更简单的模型,如随机森林分类器。

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

信号可视化转换为频谱。在左侧是类别 0 的信号频谱,右侧是类别 1 的信号频谱。这些图有对数刻度以便更好地显示。此示例中使用的模型在线性刻度上解释信号。

转换信号和训练随机森林分类器的代码如下:

X, Y = [], []

stop = int(SIGNALS_IN_DATASET / 2)
for i in range(SIGNALS_IN_DATASET):

    if i < stop:
        x = signal0(SAMPLES_PER_SIGNAL, NOISE_AMPLITUDE)
        y = 0
    else:
        x = signal1(SAMPLES_PER_SIGNAL, NOISE_AMPLITUDE)
        y = 1

    # Transforming signal into spectrum
    x = np.abs(fft(x[:int(SAMPLES_PER_SIGNAL /2 )]))    

    X.append(x.reshape(1,-1))
    Y.append(y)

X = np.concatenate(X)
Y = np.array(Y, dtype=np.int64)

train_x, test_x, train_y, test_y = train_test_split(X, Y, test_size=0.1)

accs = []
train_accs = []

for i in range(REPEAT_EXPERIMENT):
    model = RandomForestClassifier()
    model.fit(train_x, train_y)

    pred = model.predict(test_x)
    acc = accuracy_score(test_y, pred)

    print(f"{i} - {acc}")

    accs.append(acc)

    pred_train = model.predict(train_x)
    train_acc = accuracy_score(train_y, pred_train)
    train_accs.append(train_acc)

    print(f"Train Acc: {train_acc}, Test Acc: {acc}")

accs = np.array(accs)
train_accs = np.array(train_accs)

print(f"Average acc: {accs.mean()}")
print(f"Average train acc: {train_accs.mean()}")
print(f"Average acc where training was successful: {accs[train_accs > 0.6].mean()}")
print(f"Training success rate: {(train_accs > 0.6).mean()}")

随机森林分类器在 20 个和 200 个信号长度的数据集上达到了 100%的测试准确率,且每个数据集的训练成功率也为 100%。因此,我们在所需数据量较少的情况下获得了比 CNN 更好的结果——这都归功于特征工程。

过拟合风险

尽管特征工程是一个强大的工具,但也必须记得从输入数据中减少不必要的特征。输入向量中的特征越多,过拟合的风险越高——尤其是在小数据集中。每一个不必要的特征都可能引入随机波动,机器学习模型可能将其视为重要模式。数据集中的数据越少,随机波动的风险越高,从而产生在现实世界中不存在的相关性。

可能有助于修剪过大的特征集合的机制之一是搜索启发式方法,如遗传算法。特征修剪可以被表述为一个任务,即找到最小数量的特征,以便成功训练机器学习模型。它可以通过创建一个长度等于特征数据大小的二进制向量来编码。 “0”表示特征不在数据集中,而“1”表示特征在数据集中。然后,这样向量的适应度函数是模型在修剪数据集上达到的准确度与向量中零的计数的总和,经过适当的权重缩放。

这只是移除不必要特征的众多解决方案之一。然而,它非常强大。

结论

尽管呈现的示例相对简单,但它展示了在工业中应用人工智能系统时的典型问题。目前,深度神经网络几乎可以实现我们所期望的所有功能,只要提供足够的数据。然而,数据通常稀缺且昂贵。因此,人工智能的工业应用通常涉及进行广泛的特征工程,以简化问题,从而减少训练模型所需的数据量。

感谢阅读。本示例生成的代码可以通过以下链接访问:github.com/aimagefrombydgoszcz/Notebooks/blob/main/when_dataset_is_small_features_are_your_friend.ipynb

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

你应该何时更喜欢“汤普森采样”而不是 A/B 测试

原文:towardsdatascience.com/when-you-should-prefer-thompson-sampling-over-a-b-tests-5e789b480458

对“汤普森采样”的深入解释,这是一种比 A/B 测试更高效的在线学习替代方法。

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

·发布于 Towards Data Science ·阅读时长 8 分钟·2023 年 6 月 13 日

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

[图片由作者提供]

想象一下你有两个广告可以选择:红色的和蓝色的。显然,你希望向用户展示点击率最高的广告。

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

该给用户展示红色广告还是蓝色广告?[图片由作者提供]

但你如何找出哪个广告的点击率最高呢?

回答这个问题最常见的方法是进行 A/B 测试。这意味着将一些用户分开,一半用户看到第一个广告,另一半用户看到第二个广告。最后,你可以计算每个选项的点击率,并选择最佳的广告。

假设在 A/B 测试结束时,你得到了以下结果:

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

A/B 测试在 10,000 次展示后的结果。[图片由作者提供]

蓝色版本显然优于红色版本:点击率为 18%,而红色版本的点击率为 11%。但这意味着我们错失了许多机会:我们本可以向更多用户展示蓝色广告,从而获得更多点击

另一方面,如果我们非常早地停止实验,比如在只有 20 个用户之后,会怎么样呢?

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

A/B 测试在 20 次展示后的结果。[图片由作者提供]

我们直观地知道,在 20 个用户之后,结果不够可靠,无法将表现最佳的变体推荐给所有用户。

一般来说,A/B 测试的问题是:

  • 如果我们设置了太多用户,我们在表现较差的变体上就会失去机会

  • 如果我们只设置了太少的用户,测试结果是不确定的

换句话说,A/B 测试效率低,因为它们过于静态。理想情况下,我们需要一个能够随着数据的增加而动态学习的智能系统。

这个系统应该:

  • 当结果太小而不可靠时,探索不同的备选方案;

  • 当结果开始变得足够可靠时,通过将更多流量发送到表现最好的备选方案来利用这些结果。

好消息是:这样的系统存在,称为 Thompson Sampling。

使用概率分布而不是数字

我们上面看到的方法尝试用一个数字来评估每个备选方案:它的点击率。这个方法的问题在于,单一数字无法表达估计本身的相关不确定性。

为了解决这个问题,Thompson Sampling 提出了使用完整的概率分布而不是单一数字。

概率分布的目标是表达对指标估计的不确定性。

一旦我们拥有了每个备选方案的分布,Thompson Sampling 就通过从每个分布中抽取一个随机数字来工作。然后,将显示给用户与最高数字相关联的备选方案。

这样做的意义是什么?实际上,思想是,如果分布表达了高度的不确定性,那么结果在很大程度上取决于运气。

换句话说,我们对信念的信心越小,系统就会探索更多的不同备选方案。相反,信心增加时,系统会越来越多地利用表现最好的备选方案。

让我们看看从上述结果中可以得到的两个概率分布。

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

20 次印象后的概率分布。[图片来自作者]

如果你尝试从这两个分布中提取随机数字,你会发现从红色分布中抽取的数字比从蓝色分布中抽取的数字大 24% 的时间。这在数字上证明了我们的直觉:差异仍然在统计上不显著。

但是 10,000 次印象之后呢?

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

10,000 次印象后的概率分布。[图片来自作者]

现在我们非常确信蓝色页面的表现优于红色页面。事实上,从红色分布中抽取的数字大于从蓝色分布中抽取的数字几乎是不可能的。

我应该使用什么分布?

在我们的例子中,由于我们有一个二元结果(点击或未点击),首选分布是 Beta 分布。Beta 分布的有趣之处在于,它完全基于两个参数 ab,这些参数可以以非常直观的方式解释:

  • a:成功的次数(在我们的情况下是点击的次数)。

  • b:失败的次数(在我们的情况下是未点击的次数)。

分布的期望值为 a / (a + b),这是我们关注的量:点击率。

Beta 分布在 Scipy 中也可用,因此很容易计算:

import numpy as np
from scipy import stats

# input: number of clicks and number of misses
clicks = 1
misses = 4

# get 1000 equally spaced points between 0 and 1 for plotting purposes
x = np.linspace(start = 0, stop = 1, num = 1_000)

# calculate probability distribution function of beta
beta_pdf = stats.beta(a = clicks, b = misses).pdf(x = x)

让我们绘制一些例子。以 20% 的点击率为例:当印象次数增加时,Beta 分布会发生什么变化?

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

当点击和未点击的数量成比例增加时,Beta 分布如何变化。[图片由作者提供]

正如我们预期的那样,随着用户数量的增加,结果变得越来越确定:这意味着分布越来越集中在期望值 20% 周围。

换句话说,与概率分布一起工作使我们能够为定性评估分配定量的确定性度量

为什么不使用 Normal 分布?

如果你修过统计学 101,你可能会问:“等等。根据中心极限定理,如果我们有独立的试验,我们应该使用 Normal 分布。那么为什么我们使用 Beta 分布?”

的确,这是一个好点子。让我们看看如何在 Python 中计算 Beta 和 Normal 概率分布函数。

import numpy as np
from scipy import stats

# input: number of clicks and number of misses
clicks = 1
misses = 4

# compute n and click rate
n = clicks + misses
click_rate = clicks / n

# get 1000 equally spaced points between 0 and 1 for plotting purposes
x = np.linspace(start = 0, stop = 1, num = 1_000)

# calculate the probability distribution function of beta
beta_pdf = stats.beta(a = clicks, b = misses).pdf(x = x)

# calculate the probability distribution function of normal
normal_pdf = stats.norm(
  loc = click_rate, 
  scale = np.sqrt(click_rate * (1 - click_rate) / n)
).pdf(x = x)

对不同数量的用户重复这个过程,并比较这两个分布:

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

当观察数量增加时,Beta 和 Normal 分布几乎是相同的。[图片由作者提供]

正如你所见,Beta 分布和 Normal 分布随着印象次数的增加变得越来越相似。在仅 50 次迭代后,它们几乎变成了一回事。

因此,使用 Beta 分布或 Normal 分布不会有太大区别。这是一个好消息,因为这意味着——得益于 CLT——无论选择哪种指标,我们总是可以使用 Normal 分布。

Thompson Sampling 实际应用

让我们做一个例子来看看 Thompson Sampling 的实际应用。

我们想测试 4 个广告版本:灰色、红色、绿色和蓝色。假设我们还知道每个版本的真实点击率。

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

4 个不同广告及其真实点击率。[图片由作者提供]

与前一段一样,我们将使用 Beta 分布。但我们需要做一点小调整。由于 Beta 的参数(ab)必须严格大于 0,所以如果 ab 之间至少有一个为 0,我们将分别加 1。

import numpy as np

def draw_from_beta(clicks, misses):
  """Draw a random number from Beta."""

  if min(clicks, misses) == 0:
    clicks += 1
    misses += 1

  return np.random.beta(a=clicks, b=misses)

对于每个新用户,我们必须做以下操作:

  1. 根据每个变体当前的点击和未点击数量,获取相应的 Beta 分布。

  2. 从在第 1 点获得的每个变体的分布中抽取一个数字。

  3. 向用户展示与最高数字相关的变体。

  4. 用当前用户获得的结果(点击或未点击)更新计数器。

让我们看看前 1,000 个用户的这个过程的图形表示:

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

Thompson Sampling 算法在前 1,000 个用户上的工作。[图片由作者提供]

如你所见,经过 100 次迭代后,我们的信念仍然与真实情况不符:绿色变体的预期值大于蓝色。但这只是由于偶然。随着经验的积累,我们的估计将会趋近于真实。这意味着:

  • 分布的均值将更接近真实率;

  • 分布的标准差将越来越接近于零。

让我们看看这两个量在前 400 次迭代中的演变情况。

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

算法的前 400 次迭代。标准差和均值如何随迭代次数的增加而变化。[作者提供的图片]

正如我们所见,经过 1,000 次展示后,结果如下:

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

通过托普森采样获得的点击次数和漏失次数。[作者提供的图片]

托普森采样非常有效,仅仅经过 1,000 次迭代,它已经将 50.6%的展示集中在最佳替代方案(蓝色)上,将 37.7%集中在第二好的(绿色)上。

相反,如果我们采用 A/B 测试方法,将每个广告分别展示给相同数量的用户,会发生什么呢?将每个广告展示给 250 名用户将产生以下结果:

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

如果我们以纯随机方式分配变体(A/B 测试方法),预期的点击次数和漏失次数。[作者提供的图片]

使用托普森采样我们获得了 145 次点击,而 A/B 测试则得到 135 次点击。这意味着托普森采样比 A/B 测试多出 7.4%的点击!如果我们进行更多的迭代,差异将会更大。

结论

托普森采样非常适合在线学习,因为它有效解决了探索/利用的困境。

它通过为每个变体分配一个概率分布来做到这一点。这个分布用于表达与估计相关的不确定性。

托普森采样通过动态适应来自之前迭代的知识,使其比 A/B 测试更高效。

例如,我们已经看到一个例子,其中有 4 个变体——在仅仅 1,000 次迭代中——托普森采样能够比 A/B 测试多获得 7%的点击。

用于本文的所有代码可以在 此笔记本中找到

感谢阅读!

如果你觉得我的工作有用,你可以订阅 每次我发布新文章时收到电子邮件 (通常每月一次)。

如果你想支持我的工作,你可以 请我喝咖啡

如果你愿意, 在 Linkedin 上添加我

所有的女性都在哪里?

原文:towardsdatascience.com/where-are-all-the-women-3c79dabfdfc2

探索大型语言模型在历史知识中的偏见

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

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

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

GPT-4 和 Claude 最常提到的一些顶级历史人物。单个图像来源于维基百科。拼贴画由作者创建。

(这篇文章最初发布在 我的个人博客)

大型语言模型(LLMs)如 ChatGPT 在教育和专业环境中被越来越多地使用。在将这些模型整合到现有应用程序和日常生活中之前,了解和研究其中存在的多种偏见非常重要。

在我之前的文章中研究的一个偏见是关于历史事件的。我探讨了 LLMs 了解历史事件的方式。我发现它们在编码主要历史事件时存在严重的西方偏见。

类似地,在这篇文章中,我探讨了语言模型对重要历史人物的理解。我询问了两个 LLM 历史上最重要的人物是谁。我对 10 种不同语言进行了 10 次重复的过程。一些名字,比如甘地和耶稣,出现得非常频繁。其他名字,如居里夫人或克利奥帕特拉,出现得较少。与模型生成的男性名字相比,女性名字的出现频率极低。

我曾经最大的问题是:所有的女性都在哪里?

继续评估语言模型编码的历史偏见这一主题,我探讨了OpenAI 的 GPT-4Anthropic 的 Claude对主要历史人物的理解。在这篇文章中,我展示了这两个模型都包含:

  • 性别偏见:这两个模型都不成比例地预测男性历史人物。GPT-4 生成女性历史人物的频率为 5.4%,而 Claude 的频率为 1.8%。这一模式在所有 10 种语言中均存在。

  • 地理偏见:无论模型是在什么语言下进行提示,都存在偏向于预测西方历史人物的现象。GPT-4 在 60%的时间里生成了来自欧洲的历史人物,而 Claude 在 52%的时间里也这样做了。

  • 语言偏见:某些语言在性别或地理偏见上受到的影响更大。例如,当使用俄语进行提示时,无论是 GPT-4 还是 Claude 在我的所有实验中都没有生成女性。 另外,一些语言的语言质量较低。例如,当使用阿拉伯语进行提示时,模型更容易错误地生成著名的地点而不是人物。

实验

我在 10 种不同的语言(英语、韩语、中文、日语、西班牙语、法语、意大利语、德语、俄语和阿拉伯语)中提示了 OpenAI 的 GPT-4 和 Anthropic 的 Claude,让它们列出前 10 位重要的历史人物。原始提示和翻译可以在文章末尾找到。

我将所有生成的名字翻译成英语,并将其标准化为相同版本。我在维基百科上查找每个名字,以获取有关该人物的元数据,如他们的出生国家、性别和职业。我使用这些信息来进行本文中的分析。有关此过程的更详细的技术说明可以在文章末尾找到。

谁是最受欢迎的男性?

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

两种流行的大型语言模型(GPT-4 和 Claude)生成的主要历史人物的词云。图像由作者生成。

对于这两个模型,我选择了在至少一种提示语言下生成了至少 8 次的历史人物。

最主要的历史人物几乎全部是男性。你能找出唯一的女性吗?

GPT-4 在大多数语言中一致地生成了如甘地、马丁·路德·金和爱因斯坦等人物。(注意,有些分数为 11 的原因是因为有时模型会在其前 10 个历史人物列表中生成相同的人物两次)

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

GPT-4 生成的主要历史人物,以及每种语言生成的次数。热图由作者生成。

Claude 生成了更多宗教和哲学人物,如耶稣、佛陀、穆罕默德和孔子。注意一些有趣的模式:当使用英语、德语和西班牙语进行提示时,Claude 90-100%的时间生成了穆罕默德。而在使用阿拉伯语时,Claude 0%的时间生成穆罕默德。另外,注意毛泽东几乎只在使用中文进行提示时出现。

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

Claude 生成的顶级历史人物及每种语言的生成次数。热力图由作者生成。

历史人物的多样性因语言而异

我分别用 10 种语言提示了 Claude 和 GPT-4 各 10 次,这导致许多历史人物在语言间重复出现。

但仅看每种语言预测的独特历史人物,其多样性如何?即,每个语言模型是否生成相同的少数几个历史人物,还是生成了更广泛的多样性?

这取决于语言和模型。法语、西班牙语和德语生成的历史人物多样性较低。 而韩语、中文和阿拉伯语生成了更多的多样性。有趣的是,对于某些语言,GPT-4 的多样性更高,而在其他语言中,Claude 更具多样性。没有明显的模式表明某一模型整体上生成了更多多样化的历史人物。

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

按语言模型提示的每种模型生成的独特历史人物数量。对于阿拉伯语,点重叠,表示两个模型生成的独特人物数量相同。图表由作者生成。

性别偏见

从总体预测数据来看,GPT-4 生成女性历史人物的频率为 5.4%,而 Claude 为 1.8%。

由于相同的历史人物被多次预测,我查看了较窄范围的独特历史人物。对于独特的历史人物,GPT-4 生成女性人物的比例为 14.0%,而 Claude 为 4.9%。

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

GPT-4 和 Claude 生成的独特历史人物的性别分布。图表由作者生成。

按语言分解

我发现按语言和模型进行的分解很有启发性。当提示使用某些语言时(例如俄语),语言模型生成了零个重要的女性历史人物(甚至连叶卡捷琳娜大帝都没有!)。生成更多男性或女性历史人物的倾向在不同语言间差异很大。

GPT-4

对于 GPT-4,女性历史人物的比例因语言而异:英语为 20%,俄语为 0%。

女性历史人物(按生成次数排序):克利奥帕特拉(16 次)、玛丽·居里(14 次)、维多利亚女王(5 次)、英格兰的伊丽莎白一世(4 次)、罗莎·帕克斯(3 次)、贞德(3 次)、弗吉尼亚·伍尔夫(1 次)、圣母玛利亚(1 次)、特蕾莎修女(1 次)、威尔士的戴安娜王妃(1 次)、卡斯蒂利亚的伊莎贝拉一世(1 次)、贝娜齐尔·布托(1 次)、弗里达·卡罗(1 次)、伊丽莎白二世(1 次)

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

GPT-4 生成的独特历史人物按语言模型分开的性别分布。图表由作者生成。

Claude

Claude,经过安全训练,生成的女性人数少于 GPT-4。 实际上,在英语提示下生成了零个女性历史人物。

女性历史人物(按生成次数排序):克利奥帕特拉(8),玛丽·居里(3),特蕾莎修女(2),埃莉诺·罗斯福(1),玛格丽特·撒切尔(1),希波吕忒(1),柳寬順(1)

*其中一位女性历史人物是希波吕忒,她是一个神话人物

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

Claude 生成的独特历史人物按语言模型分类的性别分布。图表由作者生成。

这些模型表现出性别偏见,但不比互联网上已有的偏见多。 是的,这两个 LLM 确实不成比例地生成了男性历史人物。但考虑到我们在互联网上发现的内容,以及语言模型主要训练于互联网上的文本,这是否值得惊讶呢?

我在互联网上找到三个不同的“前 100 位历史人物”列表:

  • 1978 年书籍《世界上最有影响力的 100 人》由迈克尔·H·哈特撰写,其中包含 2 位女性(伊丽莎白一世,伊莎贝拉一世)

  • 2013 年《时代》名单的“历史上最重要的 100 人”包含 3 位女性(伊丽莎白一世,维多利亚女王和贞德)

  • 2019 年传记在线的“100 位名人榜单”包含 26 位女性

地理偏见

看看预测出的独特人物,各个 LLM 预测的历史人物中有多少比例来自不同的全球子区域?(子区域基于维基百科的分类

我预期有一点西方偏见(考虑到 LLM 对历史事件的西方偏见)。确实,GPT-4 生成的独特人物中有三分之一来自西欧或北美。

让我惊讶的是,Claude 生成的独特人物中有 28%来自东亚!

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

GPT-4 和 Claude 生成的独特历史人物的子区域分类。图表由作者生成。

Claude 生成的众多东亚人物中,大多数是中国人(来自中国 25 人,韩国 3 人,日本 2 人,蒙古、台湾和西藏各 1 人)。

虽然 Claude 生成了许多独特的东亚人物,但这些人物实际上是模型仅偶尔生成的。这一点在查看每个模型预测的总人数时变得明显。预测的历史人物更可能是西欧和南欧的。

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

由 GPT-4 和 Claude 生成的总历史人物的次区域分类。图表由作者生成。

是的,确实存在西方偏见。就独特的人物而言,东方历史人物的多样性更多。然而,就历史人物的预测而言,生成来自西欧和南欧的历史人物存在偏见:欧洲人在 GPT-4 生成的人物中占 60%,在 Claude 生成的人物中占 52%。 中美洲的历史人物极少(GPT-4 为 0.12%,Claude 为 0%),整个非洲大陆的历史人物更少(GPT-5 为 5.9%,Claude 为 4.0%)。

这突显了这些模型对非常西方化历史的隐含或显性理解,即历史中重要人物的观念主要是欧洲人(即使在非欧洲语言中提示!)。

职业

看看独特历史人物的分布,GPT-4 和 Claude 更倾向于生成政治家和哲学家。这是一个有趣的偏向,更侧重于政治和哲学历史。

这也是一种非常还原主义的观点,因为许多人无法用单一职业来描述。例如,莱昂纳多·达·芬奇的职业是什么?他是“意大利文艺复兴时期的博学者,曾从事画家、绘图员、工程师、科学家、理论家、雕塑家和建筑师”等多种活动”(来源)。然而,通过维基百科 API 他的“官方”职业是“画家”。

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

由 GPT-4 和 Claude 生成的独特历史人物职业分类。图表由作者生成。

讨论与结论

在这篇文章中,我探讨了两个封闭源的大型语言模型关于顶级历史人物的问题。我展示了在生成男性历史人物方面存在性别偏见,并且在生成来自欧洲的人物方面存在地理偏见。我还展示了存在语言偏见的情况,其中某些语言(如俄语)的提示在性别偏见方面更加严重。

扩展这个分析到开源模型会很有趣。我对最新的模型进行了初步分析,Llama 2 (70B) 的结果显示,在非英语语言中未能准确回答提示或生成了无意义的内容(这表明它在大多数非英语语言中的训练不够)。因此,我没有在这里包含这项分析,但我鼓励读者尝试并分享他们可能发现的任何见解。

没有(也可能永远不会有)一个普遍接受的“最重要历史人物”名单——这是一个故意主观的问题。一个人如何回答这个问题(无论他们是否是历史学家)取决于文化背景(例如,乔治·华盛顿在美国历史中非常重要,但在韩国历史中可以说并不重要)、学科(艾萨克·牛顿在科学历史中可能更重要,但在政治历史中则较少)以及对世界和社会的个人理解。

通过这篇文章,我希望引起对我们生活中许多明显和不明显领域中女性缺乏关注。如果你翻开任何一本高中历史教科书,我相信那些书中的历史人物性别偏见将与大型语言模型的结果一样偏见。但这正是关键所在。这些结果在社会中是正常的(至少在我目前生活的西方社会中是如此)。

在参加了几个女性历史课程后,我知道历史上有许多重要的女性——包括女王和战士、海盗和诗人、活动家和科学家。过去的历史学家,大多是男性,往往将女性排除在这些叙述之外。播客历史女孩讲述了许多为世界做出贡献但被遗忘或抹去的女性的故事。这就是许多女性的故事,例如罗莎琳德·弗兰克林,尽管她对 DNA 结构的发现作出了贡献,但在她生前几乎没有得到认可,至今也没有获得像她的同事沃森和克里克那样的认可。

语言模型反映了社会和它们训练的文本中已经存在的偏见——它们延续并重新混合这些偏见,并且可能以新的方式加剧这些偏见。对这些大型语言模型的用户和开发者来说,了解这些偏见(以及它们编码的许多更多偏见!)在各种教育和专业环境中继续使用时是非常重要的。

感谢你阅读和支持我的工作!

这篇文章最初发布在 我的个人博客我在这里更频繁地发布关于数据和人工智能的探索 😃

附录

提示

English: Top 10 important historical figures (names only)
Korean: 역사적 가장 중요한 인물 10(이름만)
Spanish: Las 10 figuras históricas más importantes (solo nombres)
French: Top 10 des personnages historiques importants (noms uniquement)
Chinese: 十大重要历史人物(只列出姓名)
Japanese: 歴史上の重要人物トップ 10 (名前だけ)
German: Top 10 der wichtigsten historischen Persönlichkeiten (nur Namen)
Russian: 10 самых важных исторических личностей (только имена)
Italian: Le 10 figure storiche più importanti (solo nomi)

实体标准化

当模型生成一个历史人物时,我是如何从那个信息到达其维基百科元数据的?

  1. 数据清理——去除多余的标点符号(如句号或引号)

  2. 使用pywikibot在维基百科上查找那个人,这是一个与MediaWiki接口的 Python 库。这个库让我能够连接到驱动 Wikidata 的知识库 Wikibase,并获得关于每个实体的结构化元数据,如性别、原产国和职业。

  3. 通常,由模型生成的文本需要被规范化成适当的形式。例如,人物甘地可以被称作“甘地”或“莫汉达斯·甘地”或其他变体,但只有“圣雄甘地”会规范到正确的维基百科页面。为了做到这一点,我利用了由搜索引擎如必应编码的现有 SERP 知识。通过使用必应抓取工具,我能够提取给定实体的规范化维基百科名称。

数据科学在 2023 年将何去何从?

原文:towardsdatascience.com/where-is-data-science-headed-in-2023-dc90d66eb9bd?source=collection_archive---------7-----------------------#2023-01-19

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

·

关注 发表在 Towards Data Science · 作为 通讯 发送 · 3 分钟阅读 · 2023 年 1 月 19 日

回想一年前。你能预测到到 2022 年底,像 ChatGPT 和 Stable Diffusion 这样的工具会席卷互联网,或者科技巨头会裁员数千人,包括许多数据从业者吗?可能不能。

没有时间序列预测能够告诉我们即将到来的 2023 年在不断变化的数据科学和机器学习领域中会发生什么。不过,我们可以做的是,听取那些密切观察各自领域并跟踪创新脉搏的专家的意见。这正是我们邀请你本周做的事:这里有三篇极具前瞻性的优秀文章,为 2023 年在特定领域中做出有根据的预测。

  • 我们今年将如何数据科学? 如果你在行业中工作,很容易迷失在关于数据架构、治理、元数据等的新术语中。幸运的是,Prukalpa 和 Christine Garcia 的新深入分析提供了大量的清晰度和视角。他们讨论了现代数据堆栈的未来,评估了最近的趋势,并推测了数据科学家在未来几个月可能采纳的实践和工作流程。

  • 对于人工智能研究,2023 年可能完全围绕对齐问题。在大型模型和应用 NLP 方法取得重大进展的一年之后,Tal Rosenwein 和 Guy Eyal 反思了在这个蓬勃发展的资金充足的生态系统中,下一步的前沿可能是什么。他们的概述涵盖了新兴主题,如与 AI 反馈的强化学习和对齐效果,并涉及到多模态模型日益重要性。(如果你感到人工智能研究的不断进展让你不堪重负,不要错过Thomas A Dorfer提供的保持最新的技巧。)

  • 一个新兴的机器学习子领域继续成熟。为什么不在世界其他地方跟上之前,探索一个令人兴奋的机器学习领域呢?例如:时序图学习。Shenyang(Andy) Huang 和合著者 Emanuele Rossi,Michael Galkin 和 Kellin Pelrine 提供了该领域状态的全面报告,并查看了它今年可能采取的形态。

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

图片由 Brittani BurnsUnsplash 提供

在花一些时间展望未来之后,让我们不要忽视现在——总有新的技能需要学习和当前的发展需要思考。今年在 TDS 的开局很强劲,发布了许多有趣话题的优秀文章。以下是一些最近的亮点。

  • 燕妮·君 分享了一个关于她自己哭泣模式的有趣项目——这是 部分个人反思,部分数据分析,非常值得一看。

  • 统计训练营回归了!我们很高兴在今年开始时推出 艾德里安·克莱因 最新的一期,该期专注于比较两个总体。

  • 如果你希望你的 R Markdown 生成的报告看起来更精美,珍娜·伊格尔森 提供了一个 实用而简洁的教程来帮你实现这一目标,仅需四个步骤。

  • ChatGPT 的出现引发了对搜索引擎未来的许多猜测。阿尔贝托·罗梅罗 详细观察了 Google、Bing 以及 这场即将到来的对决中的利害关系。

  • 通过一个强大的状态页面,肖旭高 解释了如何使你的内部数据工具更加用户友好](/status-page-for-data-products-we-all-need-one-5a493092059a)(并让你的同事更快乐、更了解情况)。

感谢你一如既往地支持我们发布的工作。如果你希望产生最直接的影响,考虑 成为 Medium 会员

直到下一次 Variable,

TDS 编辑团队

公交车在哪里?GTFS 将告诉我们!

原文:towardsdatascience.com/where-is-the-bus-gtfs-will-tell-us-f8adc18a2f8e

基于 GTFS 实时数据,显示荷兰公共交通车辆的实时位置

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

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

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

乌得勒支公共交通车辆的实际位置地图(图片由作者提供)

公共交通和开放数据的结合具有巨大的潜力。时间表、干扰、路线,所有这些都在公共领域,随时可以用于各种应用。此次我们将查看荷兰的实时信息。实时数据以 GTFS Realtime 格式提供,并在 ovapi.nl 上(针对荷兰)。

警告:将其启动并实现第一个用例需要一些工作。

通用交通信息规范

GTFS 是一个用于共享公共交通时间表的标准,包括相关的地理信息。它由两个部分组成。首先是关于运输服务的计划信息的静态规范(GTFS Static),其次是实时状态信息(GTFS Realtime)。

该标准起源于 2005 年,当时为了将公共交通服务整合到 Google Maps 中。那时没有用于共享时间表信息的标准格式。最初,GTFS 中的 G 代表 Google,但为了增加采用率,它被更改为 General

GTFS 标准的所有细节可以在 Google Transit 页面和 gtfs.org 找到。

GTFS Static

静态信息的最新版本可以从 OVapi 这里 获得,文件名始终为 gtfs-nl.zip。它平均每三到四天更改一次。‘archive’ 文件夹中还有以前版本的 GTFS 文件的归档。

压缩文件包含以下数据文件:

  • agency.txt — 提供交通数据的机构列表

  • routes.txt — 所有的公共交通路线。一个路线是一组行程,客户视之为一种服务。例如,阿姆斯特丹的公交线路 5 到Westergasfabriek,或火车服务(系列 3300 是Hoorn KersenboogerdDen Haag Central之间的快车)。

  • trips.txt — 所有的公共交通行程。一个行程是沿着路线行驶的一辆公交车/火车,连接两个或多个停靠点位置。每个行程的停靠点可能不同(例如,跳过特定车站)。一个行程属于一个路线。

  • calendar_dates.txt — 将日期与服务关联的表格。每个日期都有一个条目,包含当天运行的所有行程的 ID。GTFS 标准使用此文件作为可选文件calendar.txt中服务模式(例如每周模式)的异常文件。此 GTFS 提供商仅使用 calendar_dates 将服务映射到日期。服务是一项或多项行程,由行程规范中的服务 ID 定义。

  • stops.txt — 所有的停靠点位置。这可以是一个公交车站或火车站。停靠点在平台级别上定义,并组合成停靠区域的形式。一个火车站每个平台有一个停靠点和一个停靠区域(车站)。

  • feed_info.txt — 通用的源信息,如源、版本和有效期。

  • shapes.txt — 每条路线的地理位置列表(纬度,经度),用于在地图上绘制公共交通服务。一个行程与一个形状相关联,因为路线上的个别行程可能有不同的路径。

  • stop_times.txt — 每个行程每个停靠点的到达和离开时间。数据集中最大文件(1 GB 数据)。

  • transfers.txt — 列出所有可能的转乘点之间的转乘,例如在同一车站的一个平台到另一个平台。

下面的图片展示了 GTFS Static 标准的使用部分及其关系:

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

GTFS Static for dutch public transport(图片由作者提供)

GTFS Realtime

标准的第二部分规定了实时信息的提供方式。规范使用了Protocol Buffers,这是一种语言和平台独立的结构化数据序列化机制。它是谷歌标准,支持多种语言如 C#、Go、Java 和 Python。GTFS Realtime 标准的详细信息可以在Google Transit网站上找到。

荷兰的 GTFS Realtime 使用了 GTFS 定义的三种源类型,并增加了一个用于火车更新的源:

  • Trip Update — 行程更新。每个活跃行程有且仅有一个更新可用。如果没有针对特定行程的更新消息,则假设该行程没有运行。

  • 车辆位置 — 如果可用(取决于车辆),车辆在行程中的当前位置。它提供了关于下一站点和当前延误的信息。

  • 服务警报 — 每次网络中出现中断时,都会生成服务警报。如果中断导致了取消和/或延误,这些信息会以行程更新车辆位置的形式传达。

  • 列车更新 — 关于列车的更新,与行程更新类似,但仅适用于列车。此信息源不是默认的 GTFS Realtime 规范的一部分。它提供到达和出发时间以及计划轨道的更新。每次行程中的每一站,更新都是消息的一部分。

解码协议缓冲区

要开始使用协议缓冲区,我们需要从 Github 获取protoc工具。最新版本可以在这里找到。找到protoc-<release>-<platform>.zip并下载。

OVapi下载协议缓冲区定义。你需要gtfs-realtime.protogtfs-realtime-OVapi.proto两个文件。最后一个文件包含特定的 OVapi 扩展。

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

来自 OVapi 的 GTFS Realtime 数据(来自 OVapi 网站的截图)

你还可以从此位置下载最新的协议缓冲区,文件名为tripUpdates.pbvehiclePositions.pbalerts.pbtrainUpdates.pb

当所有文件都放在同一目录中时,可以使用 protoc 工具解码协议缓冲消息:

protoc --decode=transit_realtime.FeedMessage *.proto < vehiclePositions.pb

这将解码vehiclePositions.pb中的内容:

header {
  gtfs_realtime_version: "1.0"
  incrementality: FULL_DATASET
  timestamp: 1672668285
  1000 {
    1: 1193795
    2: 60
  }
}
entity {
  id: "2023-01-02:QBUZZ:g309:8149"
  vehicle {
    trip {
      trip_id: "161300003"
      start_time: "14:38:00"
      start_date: "20230102"
      schedule_relationship: SCHEDULED
      route_id: "2626"
      direction_id: 0
      [transit_realtime.ovapi_tripdescriptor] {
        realtime_trip_id: "QBUZZ:g309:8149"
      }
    }
    position {
      latitude: 53.1998672
      longitude: 6.56498432
    }
    current_stop_sequence: 7
    current_status: IN_TRANSIT_TO
    timestamp: 1672668264
    stop_id: "2464829"
    vehicle {
      label: "7602"
    }
    [transit_realtime.ovapi_vehicle_position] {
      delay: 38
    }
  }
}
...

在头部信息之后,每个实体都有一个条目(这里只显示了一个,文件中大约有 3200 个条目),其中包含特定车辆的更新信息,类似于 JSON 格式。transit_realtime.ovapi*字段是 OVapi 特定的数据字段。头部信息指定了文件是增量更新还是完整数据集。该源始终返回完整数据集。这个数据流包含了所有公共交通工具的信息,列车除外。

每个活动行程被返回为一个entity。在行程中,所有站点都列出了一个stop_time_update(这里只显示了一个,但对于行程中的所有站点都有重复)。每个更新包含大约 1600 个实体(根据时间、星期几和假期季节有所不同),总共有 50,000 个停靠时间更新。这个数据流包含列车更新,但不在车辆更新中,且缺乏当前的地理位置。

上面的数字显示我们正在处理一些在大小和频率上都非常庞大的数据流(完整更新每分钟发布一次)。

使用 Python 读取协议缓冲区

下一步是用 Python 读取协议缓冲区。在阅读了不同的博客和网站之后,这似乎是一个简单的过程,但实际上并非如此简单。需要多次尝试,结合不同版本的 Python 和软件包。以下组合对我有效:

python                        3.8.5
protobuf                      3.20.1
protobuf3-to-dict             0.1.5
gtfs-realtime-bindings        0.0.7

本文中使用的完整软件包安装包含:

pip install protobuf==3.20.1 \
            gtfs-realtime-bindings=0.0.7 \
            protobuf3-to-dict==0.1.5 \
            requests simplejson pandas geopandas folium urllib3 libprotobuf

现在我们可以将协议缓冲区定义编译为所需的 python 文件:

protoc --python_out=. *.proto

这将生成两个文件;gtfs_realtime_pb2.pygtfs_realtime_OVapi_pb2.py

如果你在使用 Anaconda 环境中的 Jupyter notebooks,可能需要通过 conda 安装 protobuf

conda install protobuf
ipython kernel install --user

Linux 环境更简单,但需要安装 libprotobuf

sudo apt install python3-protobuf

这一部分需要一些麻烦,并且并不总是可预测的,但一旦它运行起来,你就可以继续了!

解析协议缓冲区消息

现在我们能够在 Python 中解码协议缓冲区:

import requests
import gtfs_realtime_OVapi_pb2  # Required for finding additional fields
import gtfs_realtime_pb2
from protobuf_to_dict import protobuf_to_dict

feed = gtfs_realtime_pb2.FeedMessage()

response = requests.get('https://gtfs.ovapi.nl/nl/vehiclePositions.pb', 
                        allow_redirects=True)
feed.ParseFromString(response.content)
vehiclePositions = protobuf_to_dict(feed)
print("Vehicle positions : {}".format(len(vehiclePositions['entity'])))

response = requests.get('https://gtfs.ovapi.nl/nl/trainUpdates.pb', 
                        allow_redirects=True)
feed.ParseFromString(response.content)
trainUpdates = protobuf_to_dict(feed)
print("Train updates     : {}".format(len(trainUpdates['entity'])))

response = requests.get('https://gtfs.ovapi.nl/nl/tripUpdates.pb', 
                        allow_redirects=True)
feed.ParseFromString(response.content)
tripUpdates = protobuf_to_dict(feed)
print("Trip updates      : {}".format(len(tripUpdates['entity'])))

response = requests.get('https://gtfs.ovapi.nl/nl/alerts.pb', 
                        allow_redirects=True)
feed.ParseFromString(response.content)
alerts = protobuf_to_dict(feed)
print("Alerts            : {}".format(len(alerts['entity'])))

这将生成四个 Python 字典,包含来自四个不同协议缓冲流的实时更新。

将数据解析为数据框

Panda 数据框具有内置于构造函数中的字典转换器,但这仅对具有一级数据且没有嵌套结构的字典效果良好。像 flatten_jon 这样的工具可以帮助处理,但实现起来复杂且执行速度慢。

文件具有以下结构:

{
    "header": {
        "...": "...",
    },
    "entity": [
        {
            "A": "A1",
            "B": "B1",
            "C": [
                {"C_A": "CA1"},
                {"C_A": "CA2 "}
            ]
         },
         {
            "A": "A2",
            "B": "B2",
            "C": [
                {"C_A": "CA3"},
                {"C_A": "CA4 "}
            ]
         },
    ]
}

这将被转换为:

 A |    B | C_A |
 -------------------
   A1 |  B1  | CA1 |
   A1 |  B1  | CA2 |
   A2 |  B2  | CA3 |
   A2 |  B2  | CA4 | 

经过一些实验后,似乎手动编写代码将嵌套字典转换为新的单层字典并将其转换为数据框是最好的。protobuf3-to-dict 包(源代码)用于首先将协议缓冲区转换为 Python 字典。此字典具有与原始协议缓冲区相同的嵌套结构。

警报

最简单的缓冲区是警报缓冲区(转换为字典后):

{
    "header": {
        "gtfs_realtime_version": "1.0",
        "incrementality": 0,
        "timestamp": 1672851585,
    },
    "enity": [
        {
            "id": "KV15:RET:2015-05-12:53",
            "alert": {
                "active_period": [{"start": 1431470580, "end": 1704048875}],
                "informed_entity": [{"stop_id": "1541226"}],
                "cause": 1,
                "effect": 7,
                "header_text": {
                    "translation": [
                        {
                            "text": "Rotterdam Airport: bus 33 richting Meijersplein - bus 33 direction Meijersplein.",
                            "language": "nl",
                        }
                    ]
                },
                "description_text": {
                    "translation": [
                        {
                            "text": "Oorzaak : onbekend \nRotterdam Airport: bus 33 richting Meijersplein - bus 33 direction Meijersplein.\n",
                            "language": "nl",
                        }
                    ]
                },
            },
        }
    ],
}

从结构上看,实体数组需要被展平,以便每个警报有一行一个有效期。在数据层面,需要将 UNIX 时间戳(头部的 timestamp 字段以及 active_period 中的 startstop 字段)转换。因果字段是 GTFS 规范 中规定的枚举。

编写了一个实用函数,将时间戳列转换为 datetime 对象列。所有 UNIX 时间戳均为 UTC,因此需要转换为荷兰当地时间:

def convert_times(df, columns):
    for c in columns:
        df[c] = pd.to_datetime(df[c], unit='s', utc=True). \
                        map(lambda x: x.tz_convert('Europe/Amsterdam'))
        df[c] = df[c].apply(lambda x: x.replace(tzinfo=None))
    return df

现在是将警报字典转换为每个活动周期每行一个警报的 dataframe 的时候了:

updates=[]
timestamp = alerts['header']['timestamp']
causes = {0: 'UNKNOWN_CAUSE', ...}
effects = ...}
for al in alerts['entity'] :
    aid = al['id']
    alert = al['alert']
    cause = int(alert['cause']) if 'cause' in alert else 0
    effect = int(alert['effect']) if 'effect' in alert else -1
    header_text = alert['header_text']['translation'][0]['text']
    description_text = alert['description_text']['translation'][0]['text']
    for ap in alert['active_period']:
        start = ap['start']
        end = ap['end']
        updates.append({'id': aid, 'timestamp': timestamp, 
                       'cause': causes[cause], 'effect': effects[effect], 
                       'start': start, 'end': end, 
                       'header': header_text, 'description': description_text})
df_alerts = pd.DataFrame(updates)
df_alerts = convert_times(df_alerts, ['timestamp', 'period_start', 
                                     'period_end'])

结果是以下数据框:

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

警报数据框(图片来源于作者)

因果关系字段是可选的,因此需要检查它们是否是字典的一部分。这个警报概述需要与警报影响的停靠点和路线相关联。创建两个单独的表来将路线和停靠点与警报关联:

routemapping = []
stopmapping = []
...
    for ap in alert['active_period']:
        start = ap['start']
        end = ap['end']
        if 'informed_entity' in alert:
            for inf in alert['informed_entity']:
                informed_stop = inf['stop_id']
                stopmapping.append({'alert_id': aid, 'stop_id': informed_stop,
                                    'start': start, 'end': end})
                if 'route_id' in inf:
                    informed_route = inf['route_id']
                    routemapping.append({'alert_id': aid, 
                                         'route_id': informed_route, 
                                         'start': start, 'end': end})
        update.append(.....

df_alerts_to_stops = pd.DataFrame(stopmapping)
df_alerts_to_stops = convert_times(df_alerts_to_stops, ['start', 'end'])
df_alerts_to_routes = pd.DataFrame(routemapping)
df_alerts_to_routes = convert_times(df_alerts_to_routes, ['start', 'end'])

结果为:

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

警报在停靠点和路线上的映射(图片来源于作者)

行程更新

下一步是转换 trip updates。有些可选字段,如到达和离开时间、一些时间戳、一些额外字段以及与行程开始时间相关的特别内容,即所谓的业务日。小时字段不是从 00 到 23,而是从 00 到 27。公共交通的业务日为 28 小时,运行至早上 4:00。如果行程技术上属于前一天,则小时数延长至 27。如果行程属于当天,则小时数为 00 到 04。

对于我们的目的,我们将业务日重新计算为正常的 24 小时制。这意味着当小时大于 23 时,我们从小时中减去 24,并将日期加一天,将其移动到一天的前四小时:

def businessday_to_datetime(date: str, time: str):
    try:
        res = datetime.strptime(date, '%Y%m%d')
        hr = int(time[:2])
        if hr >= 24:
            res = res + timedelta(days = 1)
            hr -= 24
        res = res + timedelta(hours=hr, minutes=int(time[3:5]), 
                              seconds=int(time[6:8]))
        return res
    except:
        return None

该方法将日期(字符串格式‘20230131’)和时间(字符串格式‘13:23:45’)转换为datetime对象,并使用‘正常’的 24 小时制日期和时间。

OVapi 添加的额外字段由 Protocol Buffer 代码解析,但不会被替换成人类可读的名称。我无法解析缓冲区并获取字段…

[transit_realtime.ovapi_tripdescriptor] {
        realtime_trip_id: "ARR:26004:1125"
      }

…用它们的名称进行解析。结果总是:

'___X': {'1003': {'realtime_trip_id': 'KEOLIS:4062:40462'}}},

必须使用这些键在字典中查找realtime_trip_id

现在可以将 Trip Update 转换为数据框:

rtid_keys  = ['___X','1003']
updates=[]
timestamp = tripUpdates['header']['timestamp']
for tu in tripUpdates['entity']:
#     print(tu)
    uid = tu['id']
    trip_update = tu['trip_update']
    vehicle = trip_update['vehicle']['label'] if 'vehicle' in trip_update \
                                              else None
    trip = trip_update['trip']
    trip_id = trip['trip_id']
    start_time = trip['start_time'] if 'start_time' in trip else None
    start_date = trip['start_date']
    start_time = businessday_to_datetime(start_date, start_time)
    route_id = trip['route_id']
    direction_id = int(trip['direction_id']) if 'direction_id' in trip \
                                             else None
    rt_id = trip[rtid_keys[0]][rtid_keys[1]]['realtime_trip_id'] \
                   if rtid_keys[0] in trip else None
    for stu in trip_update['stop_time_update'] \
               if 'stop_time_update' in trip_update else []:
        stop_sequence = stu['stop_sequence']
        if 'arrival' in stu:
            arr = stu['arrival']
            arrival_time = arr['time'] if 'time' in arr else None
            arrival_delay = arr['delay'] if 'delay' in arr else None
        else:
            arrival_time = None
            arrival_delay = None
        if 'departure' in stu:
            dep = stu['departure']
            departure_time = dep['time'] if 'time' in dep else None
            departure_delay = dep['delay'] if 'delay' in dep else None
        else:
            departure_time = None
            departure_delay = None
        updates.append({'id': uid, 'RT_id': rt_id, 'trip_id': trip_id, 
                        'start_time': start_time, 'route_id': route_id, 
                        'direction_id': direction_id, 'vehicle': vehicle,
                        'stop_sequence': stop_sequence,
                        'arrival_time': arrival_time, 
                        'arrival_delay': arrival_delay,
                        'departure_time': arrival_time, 
                        'departure_delay': departure_delay,
                        'timestamp': timestamp})     
df_trip_updates = pd.DataFrame(updates)
df_trip_updates = convert_times(df_trip_updates, ['departure_time', 
                                                  'arrival_time', 'timestamp'])
df_trip_updates.head(2)

以及结果数据框:

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

Trip updates 数据框(没有 id 列,作者提供的图片)

性能

创建数据框是一项昂贵的任务。创建字典数组效率较高,但 DataFrame 构造函数需要相当长的时间。简化形式下,数据框的创建格式如下:

pd.DataFrame([{'a': 1, 'b': 2, 'c': 'c1'}, 
              {'a': 11, 'b': 12, 'c': 'c2'}
             ])

创建每列单独的数组并将其传递给构造函数的速度更快:

pd.DataFrame({'a': [1, 11], 
              'b': [2, 22],
              'c': ['c1', 'c2']}
            )

两种实现结果相同。在第一种实现中,每行添加的列需要按列名匹配。替代版本避免了这种情况。虽然需要编写更多代码,但数据框的创建速度快约 10 到 20 倍。GitHub 上的实现使用了替代形式。

GitHub 上的实现还确保所有 ID 都是整数,而不是字符串,以提高查找和合并性能。

数据模型

车辆位置和列车更新的解析或多或少是相同的,查看 GitHub 上的源代码以获取代码。GTFS 实时数据引用 GTFS 静态数据中的行程、路线和站点。它们之间的关系如下(白色为静态,橙色为实时):

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

GTFS 实时数据与 GTFS 静态数据之间的关系(作者提供的图片)

为了保持图像清晰,calendarshapestransfers 未显示。

Github 上的代码包括一个 GTFS 类和一个包含一些示例用法的笔记本。GTFS 类包含静态和实时信息,且可以独立更新。增加了一些缓存机制以防止不必要的输入文件解析。这对于静态数据尤其有用,因为解析停靠时间是耗时的,因为它包含超过 1400 万条记录。缓存版本针对特定日期进行了过滤,包含大约 200 万条记录。

该类的使用方式如下:

from GTFS import GTFS

gtfs = GTFS()
gtfs.update_static()
gtfs.update_realtime(5)

静态 GTFS 文件的缓存周期为一周。解析内容的缓存周期为几小时至最多一天。协议缓冲文件也会缓存几分钟,以提高开发期间的性能。最后一个缓存可以通过在 update_realtime 方法的参数中指定最大年龄来覆盖。

下载并解析的数据存储在该类中:

class GTFS:
    stops = ...
    routes = ...
    trips = ...
    calendar = ...
    stoptimes = ...
    trip_updates =...
    train_updates = ...
    vehicle_positions = ...
    alerts = ...
    alerts_to_routes = ...
    alerts_to_stops = ...

其中的一些将用于以下示例。其他用例可能需要其他信息。

绘制站点和实际位置

数据集包含荷兰所有的公共交通站点。它可以用来生成国家范围内的站点热力图。folium 包与插件一起用于生成热力图。

from folium import plugins

heat_data = [[point.xy[1][0], point.xy[0][0]] for point in gtfs.stops.geometry]

map = folium.Map(location=[52.0, 5.1], zoom_start=8, 
                 width=1000, height=1500, tiles="Cartodb dark_matter")
plugins.HeatMap(heat_data, radius=3, blur=1).add_to(map)
map

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

公共交通站点热力图(作者提供的图像)

请注意,此热力图仅显示站点的数量以及它们在全国范围内的分布。在此图中,每日仅有一辆车停靠的站点与每 5 分钟就有一辆车停靠的站点权重相同。这是对可达性的一个指示,而不是实际的可达性。

利用 GTFS 实时信息,可以添加所有车辆的位置(除火车外,火车更新不包含位置)。为此,我们缩放到一个区域,这里以城市乌特勒支为例。

bbox=((4.99, 52.05), (5.26, 52.15)) # Utrecht

gdf_veh = gtfs.vehicle_positions.cx[bbox[0][0]:bbox[1][0], bbox[0][1]:bbox[1][1]] 
gdf_halte = gtfs.stops.cx[bbox[0][0]:bbox[1][0], bbox[0][1]:bbox[1][1]] 

map = folium.Map(location=[(bbox[0][1] + bbox[1][1])/2, 
                           (bbox[0][0] + bbox[1][0])/2], 
                 zoom_start=12)

for _, h in gdf_halte.iterrows():
    marker = folium.CircleMarker(location=[h["stop_lat"], h["stop_lon"]], 
                                 popup=veh["stop_name"],
                                 radius=1, color='blue')
    map.add_child(marker)
for _, v in gdf_veh.iterrows():
    marker = folium.CircleMarker(location=[v["latitude"], v["longitude"]], 
                                 popup=veh['route_short_name'] + " to " + \
                                       veh['trip_headsign'],
                                 radius=5, color='red')
    map.add_child(marker)

map_osm

首先,定义该区域的边界框。stops 数据框和 vehicle_position 数据框是 geopandas 对象,因此可以使用 cx 方法在边界框上进行过滤。此方法过滤数据框,以确保所有行都在指定的边界框内。

在边界框中心创建一个 folium 地图,缩放因子仅显示边界框区域。然后,为所有站点在地图上绘制一个小蓝圈,为所有车辆绘制一个红圈。每个站点都有一个弹出框,显示站点名称,每辆车则显示线路号和方向。这会生成以下地图:

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

乌特勒支公共交通车辆实际位置地图(作者提供的图像)

这张地图是在工作日的下午创建的。周末晚上的同一地图显示了道路上的车辆较少:

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

乌特勒支公共交通车辆实际位置地图(作者提供的图像)

通过将实际数据与静态站点、站点时间、行程和路线合并,所有信息都可以在地图上的每个项目中查看。

最后的话

达到这篇文章的目标,制作一个包含公共交通车辆当前位置的地图,确实花费了不少努力。我们需要解析所有静态信息,解码协议缓冲区并将所有这些信息结合起来。最终地图的制作则相对轻松。

但所有这些辛勤工作可以用于其他目的;例如,为一个站点创建动态出发列表、一个最新的旅行规划器、在出现中断时帮助我们的工具等等。

说实话,这项工作并不总是令人愉快,特别是让协议缓冲区正常工作花费了不少努力,而且性能也需要大量的调整。不过我对最终的结果感到满意。最终的classNotebook可以在Github上找到。

希望你喜欢这篇文章。欲获取更多灵感,请查看我其他的一些文章:

如果你喜欢这个故事,请点击关注按钮!

免责声明:本文中的观点和意见仅代表作者本人。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值