数据科学

使用 NVIDIA cuDF API 中最新的 UDF 增强功能更快地原型制作

在过去的几个版本中, NVIDIA cuDF 团队为用户定义函数( UDF )添加了几个新特性,这些特性可以简化开发过程,同时提高总体性能。在本文中,我将介绍新的 UDF 增强功能,并展示如何在自己的应用程序中利用它们:

  • cuDF Series.apply API 及其使用方法
  • cuDF DataFrame.apply API 以及如何根据“行”编写自定义项
  • 使用两个 apply API 增强对缺失数据的支持
  • 带有计时的真实用例示例
  • 实际考虑、限制和未来计划

为 cuDF 系列应用 API

如果您不熟悉 pandas , series apply 是用于将任意 Python 函数映射到单个数据系列的主要入口点。例如,您可能希望使用已编写为 Python 函数的公式将摄氏温度转换为华氏温度。

Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

下面是运行此代码的输出后的快速刷新:

Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
view raw ctof_cudf.ipynb hosted with ❤ by GitHub

从技术上讲,您可以在函数f中编写任何有效的 Python 代码, pandas 在序列上循环运行函数。这使得apply在 pandas 环境中非常灵活,因为任何 UDF 都可以成功应用,只要它能够成功处理所有输入数据,甚至是依赖于外部库或期望或返回任意 Python 对象的 UDF 。

但是,这种灵活性是以性能为代价的。由于各种原因,在长循环中运行 Python 函数并不是一种有效的策略(例如,从一开始就对 Python 进行解释)。因此,如果您的 UDF 更简单,例如那些对标量值进行纯数学运算的 UDF ,那么这种性能约束可能会令人沮丧。

幸运的是,这些用例正是 cuDF 构建的目的。最近在 UDF 范围内的 cuDF 改进促使引入等效的apply API :

Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
view raw ctof_cudf.ipynb hosted with ❤ by GitHub

如果您熟悉 pandas ,您可以生成与使用 pandas 处理数值相同的结果。唯一显著的区别是,得到的数据总是 cuDF dtype,而不是object,这通常是熊猫的情况。

函数f可以包含由纯数学运算或 Python 运算组成的任何 Python UDF 。 cuDF 基于通过 Numba 对函数的检查推断出适当的返回dtype,并在 GPU 上编译和运行等效函数。

函数也可以编写为接受任意

虽然在 cuDF 中有其他方法可以使用自定义内核和其他方法实现相同的目标,但这种编写 UDF 的方法有助于将 GPU 从过程中抽象出来,这可以缩短从事快节奏、真实项目的数据科学家的开发时间。

到目前为止,我只讨论了基于Series的数据的情况。也就是说,我已经向您展示了如何使用单个输入和输出编写 UDF 。然而,许多用例需要多列输入,这需要稍微不同的思考。

数据框架 UDF 和按行思考

期望多个列作为输入并生成单个列作为输出的 UDF 是 pandas DataFrame apply API 支持的函数集。

在这些情况下,第一个函数参数表示一行数据,而不仅仅是单个输入列中的一个值。我所说的 row 是指某种能够通过键控获取值的数据结构,其中键是列名,值是与该行中这些列的值相对应的标量。从概念上讲,这是在熊猫中使用iloc时得到的结果:

数量的标量参数。在以下代码示例中,您可以看到支持args=

Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
view raw cudf_args.ipynb hosted with ❤ by GitHub

下面的代码示例显示了如何在 pandas 中编写和使用使用此类行对象的 UDF :

Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

cuDF 现在,您可以在不重写自定义项的情况下完成确切的操作。

Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

在应用这些函数时,需要注意的是,尽管 cuDF API 希望您以行的形式编写函数,但在执行此函数时,实际上并不涉及任何行。

cuDF 避免使用 for 循环,而是执行“假装”存在数据行的 CUDA 内核。通过一点魔法, Numba 知道如何编写一个合适的内核来获得与 pandas 相同的结果。因为没有循环,所以通过此 API 执行函数时应该会看到更高的性能。

使用 series 和 DataFrame 应用对缺失值的支持

从历史上看, cuDF 中的 UDF 并没有提供对缺失值的完全支持。这是由于 cuDF 内部的架构选择与 cuDF 记录哪些元素为空的方式有关,特别是它使用空掩码来节省内存。

pandas apply API 的循环设计仅在数据包含空值时有效。如果数据中遇到 null , UDF 将接收特殊值pd.NA.,如果特殊值未触发错误,则执行将正常进行。然而, cuDF 不是这样工作的,它需要一些额外的机器来支持相同的功能。如果使用 cuDF apply API ,您应该发现您的 UDF 以自然的方式处理空值:

Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

您甚至可以在cudf.NA单例上设置条件并获得预期的答案,或者直接从函数返回:

Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

显然,这里的情况与行的情况相同: cuDF 实际上并不像 pandas 那样运行 Python 函数。相反,它使用了更多的 Numba 魔法将此类函数转换为等效的 CUDA 内核,然后返回结果。

在下一节中,我将查看一个真实的示例,并执行一些粗略的计时。

使用 apply 的真实示例

考虑一下这个场景:一个在线流媒体服务正在调查其订阅者中哪些部分的订阅时间最长。此外, leadership 还要求制定一个具体的细分方案,按年龄划分订阅者:

  • 18 – 19
  • 20 – 29
  • 30 – 39
  • 40 – 49
  • 50 – 59
  • 60 – 69
  • 69 +

提供的数据只有两个字段:agedays_subscribed

以下是 UDF 如何解决这个问题。首先,编写应用分组的按行自定义函数。接下来,获取结果,按组 ID 分组,并平均续订次数。

Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
 

在此代码示例中,数据是随机生成的,因此您的里程数可能与实际答案不同。然而,它展示了这个过程。对代码的 UDF 部分进行计时涉及到创建一个变量pdfpdf = df.to_pandas,并使用 IPython 完成粗略比较:

%timeit df.apply(f, axis=1) # 1.64 ms ± 34.2 µs per loop (mean ± std. dev. of 7 runs, 1,000 loops each) %timeit pdf.apply(f, axis=1)
# 19.2 s ± 63.7 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

虽然这不是官方的基准测试, CUDA 内核在这个特定的情况下平均快了四个数量级以上,它是在 32 GB V100 GPU 上运行的。

实际考虑、限制和未来

虽然这些 cuDF 改进代表了比以前的迭代更广泛的功能,但总有增长的空间。以下是为 cuDF 中的 apply 编写自定义项时要考虑的关键项目列表:

  • JIT compilation. 第一次针对 cuDF 对象执行函数时,您会遇到编译正确的 CUDA 内核的开销影响。除非目标数据集的dtypes发生更改,否则该函数的后续使用不需要重新编译。
  • dtype support. 到目前为止,apply中只支持数字dtypes。然而,对其他类型的支持已经在路线图上,从字符串开始。
  • External libraries. 常见的模式是在 pandas 中执行数据准备,然后使用外部库在 UDF 中为每一行进行处理。由于您无法将外部代码任意映射到 GPU ,因此目前不支持此操作。

总结

UDF 是快速解决特定问题的简单方法。在设计管道逻辑时,它们可以帮助您从单个数据的角度进行思考。通过这些新的 cuDF UDF 增强,目的是加快涉及 cuDF 的工作流的开发,并允许您快速原型化解决方案,以及重用现有业务逻辑。此外, null 支持允许您明确说明如何处理缺少的值,而不需要额外的处理步骤。

值得注意的是, UDF 是 cuDF 中积极开发的一个领域,目前正在进行更新。如果您选择像往常一样尝试这些新的 UDF 增强功能,我很乐意在评论部分了解您的体验。

 

Tags