在开发实时AI/ML应用程序的功能时,理解数据中基于时间的模式至关重要,因为它可以揭示有价值的见解。然而,表达时间查询可能会带来挑战。想象一下能够轻松分析用户随时间的行为、进行精确的时间连接并检查各种事件之间的活动模式,同时保持直观和无缝的时间处理。这就是时间线(处理时态数据的高级抽象)可以发挥无价价值的地方
在本文中,我们将深入探讨时间线的世界。我们将演示它们如何表达对事件的时间查询,更重要的是,在事件之间表达时间查询,不仅简单而且直观。本文是有关此系列的第二篇文章
直观由于时间线是按时间排序的,因此查询自然也按顺序进行操作。随着时间的推移,会发生其他事件(输入)并反映在查询的输出中。这种随着时间的推移而思考计算的方式是直观的,因为它符合我们观察事件的方式。
声明性时间操作(例如窗口和移位)在使用时间线时会被明确声明,因为时间是抽象的一部分。
可组合每个操作都需要时间线并产生时间线,这意味着可以根据需要链接操作以产生预期结果。
下面,我们将剖析四个现实生活中的例子,展示时间线的好处。我们将从简单的聚合查询开始,逐步处理更复杂的时间窗口、数据相关窗口和时间正确的连接。最后,您应该深入了解时间线如何使编写简单的时态查询像 SQL 一样简单,以及它们如何使我们能够解决更具挑战性的问题。
时间线支持您可以在SQL中执行的所有操作,直观地扩展为随着时间的推移进行操作。在研究复杂时态查询的一些新功能之前,让我们先看一些简单的东西——聚合。编写简单的查询很容易:事实上,由于时间线按时间排序并按实体分组,因此它们甚至比 SQL 更容易!
考虑这个问题,每个用户花了多少钱?当考虑事件时,很自然地会按顺序处理购买,并随着时间的推移更新每个用户花费的金额。结果是产生连续时间线的累积总和。
相应的查询如下所示,以两种等效的方式编写。第一个强调应用于采购的金额,而第二个强调我们组成的操作链——“获取采购,然后应用金额”。从现在开始,我们将使用后者,因为它更符合我们考虑处理时间线的方式。
sum(Purchases.amount)
#OR
Purchases.amount | sum()
使用时间线编写简单的时态查询就像 SQL 一样简单。按顺序处理事件是一种直观的随时间操作的方式。当然,聚合所有事件只是我们希望聚合事物的一种方式。在下一个示例中,我们将了解如何使用时间窗口扩展此查询以关注最近的事件。
在考虑时态查询时,很自然地会询问有关最近过去的问题:年初至今或过去 30 天。按顺序处理事件的直觉表明,回答“每个用户本月花费了多少钱”的问题应该只需要在每个月初重置该值。这种直觉正是这些时间窗口与时间线一起工作的方式。
时间查询如下所示。它清楚地表明了我们上面表达的意图——从每个月初开始进行采购并将其汇总。
Purchases.amount
| sum(window=since(monthly()))
由于时间本质上是每个时间线的一部分,因此每个聚合都能够在时间窗口内运行。在下一个示例中,我们将看到处理更复杂的查询(包括使用更复杂的窗口进行聚合)是多么容易。
并非所有窗口都是根据时间来定义的。使用事件来确定用于聚合的窗口通常很有用。此外,虽然到目前为止所有示例都针对单一类型的事件( purchases
进行操作,但检查不同事件之间的活动模式对于识别因果关系至关重要。
在此示例中,我们将利用时间线来使用数据定义的窗口和多种类型的事件以声明方式表达查询。我们还将过滤中间时间线到特定点,以控制编写查询时后续步骤中使用的值。
我们要回答的问题是“每个用户每次购买之间的平均页面浏览量是多少?”我们将首先计算自上次购买以来的页面浏览量,在每次购买时观察它们,然后取平均值。
我们要做的第一件事是计算自上次购买以来的页面浏览量。在前面的示例中,我们从月初开始就进行了窗口操作。但是定义一个月开始的时间线没有什么特别的——我们可以使用任何其他时间线进行窗口。
PageViews
| count(window=since(is_valid(Purchases)))
除了数据定义的窗口之外,我们还了解如何处理多种类型的事件。由于每个时间线都按时间排序并按实体分组,因此每个时间线都可以自动按时间排列并按实体连接。
上一步为我们提供了自上次购买以来的页面浏览量。但这是一个连续的时间线,每次页面浏览都会增加,直到下一次购买为止。我们追求的是一个离散的时间线,每次购买时都有一个值,代表自上次购买以来的页面浏览量。为此,我们使用when
操作,它允许在特定时间点观察并在需要时进行插值。
When 操作可以在时间查询中的任何地方使用,并允许过滤输出中存在的点,或者传递给以后的聚合。
通过计算购买之间的页面浏览量,我们现在可以计算该值的平均值。我们需要做的就是使用mean
聚合。
完整的查询如下所示。我们看到这些步骤与我们上面讨论的逻辑步骤相匹配。尽管逻辑相当复杂,但查询相对简单,并且捕获了我们想要计算的想法 - 困难的问题是可能的。
PageViews
| count(window=since(is_valid(Purchases)))
| when(is_valid(Purchases))
| mean()
这种查询可以推广到分析页面浏览活动中的各种模式。也许我们只想查看最常查看的项目的页面视图,而不是所有项目,相信用户越来越关注该项目。也许我们想要窗口化购买同一商品,而不是任何购买。
此查询展示了时间线支持表达复杂时态查询的一些方式:
排序使窗口能够通过其分隔符(何时开始和结束)来定义,而不必根据每个值计算“窗口 ID”以进行分组。
排序还允许在同一表达式中使用多个时间线 - 在本例中为pageviews
和purchases
。
连续性允许在任意时间对值进行插值并使用when
操作进行过滤。
可组合性使得任何操作的结果都可以与后续操作一起使用来表达时间问题。这使得复杂的问题可以表达为一系列简单的操作。
这些功能可以识别因果模式。虽然现在的购买可能会导致我稍后再购买,但其他事件往往有更紧密的关系——例如,胶带用完并购买更多,或者安排露营旅行并备货。能够在由其他事件( purchases
)定义的窗口内查看活动( pageviews
)对于理解这些事件之间的关系非常重要。
我们已经了解了时间线如何允许处理与同一实体关联的多种类型的事件。但通常也需要与多个实体合作。例如,使用有关整个人口的信息来标准化每个用户的值。我们的最后一个示例将展示如何使用多个实体并执行临时连接。
我们要回答的最后一个问题是“每次购买时的最低平均产品评论(分数)是多少?”为此,我们将首先使用与每个产品相关的评论来计算平均分数,然后将每次购买与相应的平均评论结合起来。
首先,我们要计算每个项目的平均产品评论(分数)。由于评论当前按用户分组,因此我们需要使用 with 键操作按项目重新分组。一旦我们完成了这一点,我们就可以使用我们已经看到的平均聚合。
对于每次购买(按用户分组),我们希望查找相应商品的平均评论分数。这使用了lookup
操作。
将它们放在一起,我们使用min
聚合的查找来确定每个用户购买的商品的最小平均评分。
Reviews.score
| with_key(Reviews.item)
| mean()
| lookup(Purchases.item)
| min()
这种重新分组到不同实体、执行聚合和查找值(或移回原始实体)的模式在数据处理任务中很常见。在本例中,直接查找并使用结果值。在其他情况下,它对于标准化很有用——例如将每个用户的价值与其所在城市的平均值相关联。
排序和分组允许时间线清楚地表达不同实体之间的操作。查找的结果来自执行查找的时间点。这提供了暂时正确的“as-of”连接。
在正确的时间执行连接对于计算过去的训练示例至关重要,这些示例可与应用模型时使用的特征值进行比较。同样,它确保对查询结果执行的任何仪表板、可视化或分析实际上都是正确的,就像它们查看过去的值一样,而不是使用当时不可用的信息。
我们已经展示了时间线作为处理时态数据的高级抽象的强大功能。通过直观、声明性和可组合的操作,我们展示了时间线如何有效表达事件上和事件之间的时间查询。通过从简单聚合到复杂查询(例如数据相关窗口和时间正确连接)的示例,我们说明了如何链接时间线操作以产生预期结果。时间线的效力在于它们能够轻松表达简单的时间问题并直观地扩展到复杂的时间查询。
从总支出到最低评论分数,我们浏览了四个说明性示例,突出了时间线在时间查询中的功能。我们探索了累积聚合、时间窗口,并观察了数据定义的窗口如何提供表达复杂时间问题的能力。我们还展示了时间线如何促进多实体处理和时间连接。这些示例表明,通过时间线,您可以使用强大的工具来识别因果模式并计算在应用模型时相对有效的训练示例。
接下来,我们将进一步深入研究时间线上时间查询的动态。我们将探索如何利用时间线的属性来有效地执行这些查询。\
我们鼓励您
也发布在这里。