聚合概括折叠和折叠概括如何减少?

时间:2016-05-17 11:37:38

标签: scala apache-spark

据我所知,aggregate是对fold的概括,而reduce又概括为combineByKey

类似aggregateByKeyfoldByKey的概括,而reduceByKey反过来是fold的概括,而后者又是reduce的概括。

但是,我很难找到这七种方法中的每种方法的简单例子,而这些方法又只能由它们表达而不是它们不那么通用的版本。例如,我发现http://blog.madhukaraphatak.com/spark-rdd-fold/给出了fold的示例,但我也能够在同样的情况下使用reduce

到目前为止我发现了什么:

  • 我读到更通用的方法可以更有效,但这将是一个非功能性的要求,我想得到无法用更具体的方法实现的例子。
  • 我也看过,例如传递给fold的函数只需要是关联的,而reduce的函数必须是可交换的:https://stackoverflow.com/a/25158790/4533188(但是,我仍然不知道任何好的简单例子。)而在https://stackoverflow.com/a/26635928/4533188中,我读到折叠需要两个属性来保持......
  • 我们可以将零值视为一项功能(例如fold超过subprocess.run('export FOO=BAR', shell=True) ),因为"添加所有元素并添加3"并使用3作为零值,但这会产生误导,因为每个分区都会添加3个,而不只是一次。根据我的理解,这根本不是subprocess.run()的目的 - 它不是一个特征,而是实现它以实现非交换功能的必要性。

这七种方法的简单例子是什么?

2 个答案:

答案 0 :(得分:10)

让我们在逻辑上完成实际需要的工作。

首先,请注意,如果您的集合是无序的,任何上的(二进制)操作集必须是可交换的和关联的,或者您将根据具体情况获得不同的答案(任意的)你每次都要选择。由于reducefoldaggregate都使用二元操作,如果您在无序(或被视为无序)的集合上使用这些内容,则所有内容都必须是可交换的和关联的。

reduce是一个想法的实现,如果你可以采取两件事并将它们变成一件事,你可以将任意长的集合折叠成一个元素。只要你最终将它们全部配对并保持从左到右的顺序不变,那么关联性正是它与你如何配对并不重要的属性,所以这就是' s正是你需要的。

a   b   c   d          a   b   c   d           a   b   c   d
a # b   c   d          a # b   c   d           a   b # c   d
(a#b)   c # d          (a#b) # c   d           a   (b#c)   d
(a#b) # (c#d)          ((a#b)#c) # d           a # ((b#c)#d)

只要操作(此处称为#)是关联的,上述所有内容都是相同的。没有理由交换左边的东西和右边的东西,所以的操作需要是可交换的(另外是:a + b == b + a; concat不是:ab!= ba)。

reduce在数学上很简单,只需要关联操作

减少是有限的,因为它不会对空集合起作用,并且你无法改变类型。如果你按顺序工作,你可以使用一个新类型和旧类型的函数,并生成一个新类型的函数。这是顺序折叠(如果新类型在左侧,则向左折叠,如果在右侧则向右折叠)。关于此处的操作顺序没有选择,因此交换性和关联性以及一切都无关紧要。只有一种方法可以按顺序处理列表。 (如果你希望你的左折和右折总是相同的,那么操作必须是关联的和可交换的,但由于左右折叠通常不会被意外交换,所以这不是'非常重要的是确保。)

当您想要并行工作时,问题就出现了。你不能按顺序浏览你的收藏品;根据定义,这并不平行!所以你必须在多个位置插入新类型!让我们调用我们的折叠操作@,我们会说新类型在左边。此外,我们会说我们始终使用相同的元素Z。现在我们可以执行以下任何操作(以及更多):

  a     b     c     d        a     b     c     d         a     b     c     d
 Z@a    b     c     d       Z@a    b    Z@c    d        Z@a   Z@b   Z@c   Z@d
(Z@a) @ b     c     d      (Z@a) @ b   (Z@c) @ d
((Z@a)@b)  @  c     d
(((Z@a)@b)@c)    @  d

现在我们收集了一种或多种新类型的东西。 (如果原始集合是空的,我们只需要Z。)我们知道如何处理它!降低!因此,我们为新类型进行缩减操作(让我们称之为$,并记住它必须是关联的),然后我们有聚合

  a     b     c     d        a     b     c     d         a     b     c     d
 Z@a    b     c     d       Z@a    b    Z@c    d        Z@a   Z@b   Z@c   Z@d
(Z@a) @ b     c     d      (Z@a) @ b   (Z@c) @ d        Z@a $ Z@b   Z@c $ Z@d
((Z@a)@b)  @  c     d      ((Z@a)@b) $ ((Z@c)@d)    ((Z@a)$(Z@b)) $ ((Z@c)$(Z@d))
(((Z@a)@b)@c)    @  d

现在,这些东西看起来都很不一样。我们怎样才能确保它们最终成为一样?没有一个概念可以描述这一点,但Z@操作必须零 - $@必须是同态的,因为我们需要(Z@a)@b == (Z@a)$(Z@b)。这就是你需要的实际关系(它在技术上与半群同态非常相似)。即使一切都是联想和可交换的,也有各种各样的方式来挑选。例如,如果Z是双值0.0@实际上是+,那么Z就像零一样,@是关联的和交换。但如果$实际上是*关联且可交换,那么一切都会出错:

(0.0+2) * (0.0+3) == 2.0 * 3.0 == 6.0
((0.0+2) + 3)     == 2.0 + 3   == 5.0

非trival聚合的一个例子是构建一个集合,其中@是"附加一个元素"运算符和$是" concat两个集合"操作

aggregate非常棘手,需要关联缩减操作,加上类似于零的值以及与reduce同形的类似折叠的操作

最重要的是,aggregate

但如果您实际上没有更改类型,则会有一种简化(不太通用的形式)。如果reduce实际上是Z并且是实际零,我们可以将其粘贴到我们想要的任何地方并使用reduce。再一次,我们在概念上不需要交换性;我们只是坚持一个或多个z并减少,我们的z@操作可以是相同的,即我们使用的原始$在减少

#

如果我们只是从这里删除 a b c d () <- empty z#a z#b z z#a (z#b)#c z#a ((z#b)#c)#d (z#a)#((z#b)#c)#d ,则效果非常好,实际上相当于z。但是,它的另一种方式也可以起作用。如果操作if (empty) z else reduce也是可交换的,则 #实际上不是零,而只是占据z的固定点(意思是#,但z#z == z不一定只是z#a),那么你可以运行相同的东西,并且由于传播让你可以切换顺序,所以你在概念上可以重新排序所有z&#39 ;在一起开始,然后将它们合并在一起。

这是一个平行折叠,它实际上是一个与顺序折叠完全不同的野兽。

(请注意,afold都不是aggregate的严格概括,即使是无序集合,其中操作必须是关联的和可交换的,因为某些操作没有合理的零!例如,以最短的长度减少字符串就像其最长的字符串一样,这在概念上并不存在,实际上是一种荒谬的内存浪费。)

reduce需要关联缩减操作以及 零值还原操作,即可换向加定点值

现在,你什么时候 使用并行折叠不是只是reduceOrElse(零)?实际上,从来没有,尽管它们可以存在。例如,如果你有一个戒指,你通常会有我们需要的固定点。例如,10%45 ==(10 * 10)%45,fold在整数mod 45中是关联的和可交换的。因此,如果我们的集合是数字mod 45,我们可以用&#34折叠;零&#34; *的{​​{1}}以及10的操作,并且我们请将其并行化,同时仍然得到相同的结果。很奇怪。

但请注意, 只需将*的零和操作插入fold并获得完全相同的结果,因此aggregate aggregate的适当概括。

所以,底线:

  1. Reduce只需要一个关联合并操作,但不会改变类型,也不会对空集合起作用。
  2. 并行折叠尝试扩展fold但需要一个真零或一个固定点,并且合并操作必须是可交换的。
  3. 聚合通过(概念上)运行顺序折叠然后(并行)减少来改变类型,但是减少操作和折叠操作之间存在复杂的关系 - 基本上他们必须做同样的事情&#34;同样的事情& #34;
  4. 无序集合(例如集合)总是需要对上述任何一种进行关联和交换操作。
  5. 关于reduce内容:它与此相同,只是它仅将其应用于与(可能重复的)键相关联的值集合。

    如果Spark实际上需要交换性,而上述分析并不表明需要它,那么可以合理地考虑一个错误(或者至少是对实现的不必要的限制,假设byKeymap之类的操作filter保留有序RDD的顺序。)

答案 1 :(得分:1)

  

传递给fold的函数必须是关联的,而reduce的函数必须是可交换的。

这是不正确的。关于RDD的fold要求该函数也是可交换的。它与Iterable fold上的fold操作不同,the official documentation中对此进行了详细描述:

  

这与针对非分布式实现的折叠操作略有不同      Scala等函数式语言中的集合。

     

fold操作可能适用于      单独分区,然后将这些结果折叠到最终结果中,而不是      以某种定义的顺序依次将fold应用于每个元素。对于功能      这不是可交换的,结果可能与应用于a的折叠的结果不同      非分布式收集。

正如您所看到的,合并部分值的顺序不是合同的一部分,因此用于fold的函数必须是可交换的。

  

我读到更通用的方法可以更有效

从技术上讲,应该没有显着差异。对于reduce vs *byKey,您可以查看我对reduce() vs. fold() in Apache SparkWhy is the fold action necessary in Spark?

的回答

关于combineByKeyWithClassTag方法,所有方法都使用相同的基本构造createCombiner来实现,并且可以简化为三个简单的操作:

  • mergeValue - 为给定分区创建“零”值
  • mergeCombiners - 将值合并到累加器
  • UIAlertView - 合并为每个分区创建的累加器。