web-dev-qa-db-ja.com

Sparkで集約機能を説明する

spark pythonで利用できる集約機能のより良い説明を探しています。

私が持っている例は次のとおりです(Spark 1.2.0バージョン)からpysparkを使用して)

sc.parallelize([1,2,3,4]).aggregate(
  (0, 0),
  (lambda acc, value: (acc[0] + value, acc[1] + 1)),
  (lambda acc1, acc2: (acc1[0] + acc2[0], acc1[1] + acc2[1])))

出力:

(10, 4)

(10,4)と4つの要素の合計である期待される結果1+2+3+4を取得します。集約関数に渡される初期値を(1,0)から(0,0)に変更すると、次の結果が得られます

sc.parallelize([1,2,3,4]).aggregate(
    (1, 0),
    (lambda acc, value: (acc[0] + value, acc[1] + 1)),
    (lambda acc1, acc2: (acc1[0] + acc2[0], acc1[1] + acc2[1])))

出力:

(19, 4)

値は9増加します。(2,0)に変更すると、値は(28,4)などになります。

誰かがこの値の計算方法を説明できますか? (11,4)が表示されるのではなく、(19,4)が表示されると予想され、値が9ではなく1ずつ増加することを期待しました。

46
ab_tech_sp

Maasgの前回の回答についてコメントするのに十分な評判ポイントがありません。実際、ゼロ値はseqopに対して「ニュートラル」である必要があります。つまり、0が加算に、1が*のように、seqopの結果に干渉しないことを意味します。

任意の時間に適用される可能性があるため、非中立値を使用しないでください。この動作は、パーティションの数に関係しているだけではありません。

質問で述べたのと同じ実験を試みました。 1つのパーティションでは、ゼロ値が3回適用されました。 2つのパーティションで、6回。 3つのパーティションで、9回、これが続きます。

15
John Knight

私は完全に確信していませんでした 受け入れられた答えから、そしてジョンナイトの答えは助けましたので、ここに私の視点があります:

まず、私自身の言葉で aggregate() を説明しましょう:

プロトタイプ

aggregate(zeroValue、seqOp、combOp)

説明

aggregate()を使用すると、RDDを取得して、元のRDDに保存されたものとは異なるタイプの単一の値を生成できます。

パラメータ

  1. zeroValue:結果の初期化値を目的の形式で。
  2. seqOp:RDDレコードに適用する操作。パーティション内のすべてのレコードに対して1回実行されます。
  3. combOp:結果のオブジェクト(パーティションごとに1つ)を結合する方法を定義します。

リストの合計とそのリストの長さを計算します。 _(sum, length)_のペアで結果を返します。

Spark Shell)では、最初に4つの要素と2つのpartitionsを持つリストを作成しました:

_listRDD = sc.parallelize([1,2,3,4], 2)
_

次に、seqOpを定義しました:

_seqOp = (lambda local_result, list_element: (local_result[0] + list_element, local_result[1] + 1) )
_

およびmycombOp

_combOp = (lambda some_local_result, another_local_result: (some_local_result[0] + another_local_result[0], some_local_result[1] + another_local_result[1]) )
_

そして、私は集約しました:

_listRDD.aggregate( (0, 0), seqOp, combOp)
Out[8]: (10, 4)
_

ご覧のとおり、変数にわかりやすい名前を付けましたが、さらに説明しましょう。

最初のパーティションにはサブリスト[1、2]があります。 seqOpをそのリストの各要素に適用すると、ローカルの結果、つまり_(sum, length)_のペアが生成され、その結果が最初のパーティションでのみローカルに反映されます。

それでは、始めましょう:_local_result_は、aggregate()に指定したzeroValueパラメーターに初期化されます。つまり、(0、0)と_list_element_が最初の要素ですリスト、つまり1。その結果、これが起こります。

_0 + 1 = 1
0 + 1 = 1
_

ここで、ローカルの結果は(1、1)です。つまり、これまでのところ、最初のパーティションでは、最初の要素のみを処理した後、合計は1で長さが1になります。_local_result_ (0、0)から(1、1)まで。

_1 + 2 = 3
1 + 1 = 2
_

ローカルの結果は(3、2)になります。これは、1番目のパーティションのサブリストに他の要素がないため、1番目のパーティションからの最終結果になります。

2番目のパーティションにも同じことを行うと、(7、2)が得られます。

次に、combOpを各ローカル結果に適用して、最終的なグローバル結果を次のように形成できるようにします。_(3,2) + (7,2) = (10, 4)_


「図」で説明されている例:

_            (0, 0) <-- zeroValue

[1, 2]                  [3, 4]

0 + 1 = 1               0 + 3 = 3
0 + 1 = 1               0 + 1 = 1

1 + 2 = 3               3 + 4 = 7
1 + 1 = 2               1 + 1 = 2       
    |                       |
    v                       v
  (3, 2)                  (7, 2)
      \                    / 
       \                  /
        \                /
         \              /
          \            /
           \          / 
           ------------
           |  combOp  |
           ------------
                |
                v
             (10, 4)
_

この素晴らしい に触発されました。


したがって、zeroValueが(0、0)ではなく、(1、0)の場合、(8 + 4、2 + 2)=(12、4)が得られることが期待されますが、あなたが経験することを説明してください。例のパーティションの数を変更しても、再び取得することはできません。

ここで重要なのは、JohnKnightの答えです。これは、zeroValueがパーティションの数に類似しているだけでなく、予想よりも多く適用される可能性があることを示しています。

82
gsamaras

集約を使用すると、RDDの値を自由に変換および結合できます。

次の2つの機能を使用します。

最初のものは、ローカル集合[U]の元のコレクション[T]の要素を変換および追加し、次の形式を取ります。その操作のために。この操作は、各パーティションにローカルで並行して適用されます。

ここで質問のキーがあります:ここで使用する必要がある唯一の値は、リダクション操作のゼロ値です。この操作は各パーティションでローカルに実行されるため、そのゼロ値に何かを追加すると、結果にRDDのパーティション数を掛けた値が追加されます。

2番目の操作は、前の操作[U]の結果タイプの2つの値を取り、それを1つの値に結合します。この操作により、各パーティションの部分的な結果が削減され、実際の合計が生成されます。

例:文字列のRDDが与えられた場合:

val rdd:RDD[String] = ???

そのRDDの文字列の長さの集計が必要な場合、次のようにします。

1)最初の操作は、文字列をサイズ(int)に変換し、サイズの値を蓄積します。

val stringSizeCummulator: (Int, String) => Int  = (total, string) => total + string.lenght`

2)加算操作にゼロを指定します(0)

val ZERO = 0

3)2つの整数を加算する操作:

val add: (Int, Int) => Int = _ + _

すべてを一緒に入れて:

rdd.aggregate(ZERO, stringSizeCummulator, add)

では、なぜゼロが必要なのですか? cummulator関数をパーティションの最初の要素に適用すると、積算合計はありません。ここではゼロが使用されます。

例えば。私のRDDは次のとおりです。-パーティション1:["Jump"、 "over"]-パーティション2:["the"、 "wall"]

これは次のようになります。

P1:

  1. stringSizeCummulator(ZERO、 "Jump")= 4
  2. stringSizeCummulator(4、 "over")= 8

P2:

  1. stringSizeCummulator(ZERO、 "the")= 3
  2. stringSizeCummulator(3、 "wall")= 7

削減:add(P1、P2)= 15

29
maasg

次のコード(scala)を使用して、aggregateが何をしているかを正確に確認できます。すべての追加およびマージ操作のツリーを構築します。

sealed trait Tree[+A]
case class Leaf[A](value: A) extends Tree[A]
case class Branch[A](left: Tree[A], right: Tree[A]) extends Tree[A]

val zero : Tree[Int] = Leaf(0)
val rdd = sc.parallelize(1 to 4).repartition(3)

そして、シェルで:

scala> rdd.glom().collect()
res5: Array[Array[Int]] = Array(Array(4), Array(1, 2), Array(3))

したがって、これらの3つのパーティションがあります:[4]、[1,2]、および[3]。

scala> rdd.aggregate(zero)((l,r)=>Branch(l, Leaf(r)), (l,r)=>Branch(l,r))
res11: Tree[Int] = Branch(Branch(Branch(Leaf(0),Branch(Leaf(0),Leaf(4))),Branch(Leaf(0),Leaf(3))),Branch(Branch(Leaf(0),Leaf(1)),Leaf(2)))

結果をツリーとして表すことができます。

+
| \__________________
+                    +
| \________          | \
+          +         +   2
| \        | \       | \         
0  +       0  3      0  1
   | \
   0  4

最初のゼロ要素がドライバーノード(ツリーの左側)に作成され、すべてのパーティションの結果が1つずつマージされることがわかります。また、質問で行ったように0を1に置き換えると、各パーティションの各結果に1が追加され、ドライバーの初期値にも1が追加されることがわかります。したがって、指定したzero値が使用される合計回数は次のとおりです。

number of partitions + 1

だから、あなたの場合、の結果

aggregate(
  (X, Y),
  (lambda acc, value: (acc[0] + value, acc[1] + 1)),
  (lambda acc1, acc2: (acc1[0] + acc2[0], acc1[1] + acc2[1])))

なります:

(sum(elements) + (num_partitions + 1)*X, count(elements) + (num_partitions + 1)*Y)

aggregateの実装は非常に簡単です。 RDD.scala、1107行目 で定義されています:

  def aggregate[U: ClassTag](zeroValue: U)(seqOp: (U, T) => U, combOp: (U, U) => U): U = withScope {
    // Clone the zero value since we will also be serializing it as part of tasks
    var jobResult = Utils.clone(zeroValue, sc.env.serializer.newInstance())
    val cleanSeqOp = sc.clean(seqOp)
    val cleanCombOp = sc.clean(combOp)
    val aggregatePartition = (it: Iterator[T]) => it.aggregate(zeroValue)(cleanSeqOp, cleanCombOp)
    val mergeResult = (index: Int, taskResult: U) => jobResult = combOp(jobResult, taskResult)
    sc.runJob(this, aggregatePartition, mergeResult)
    jobResult
}
1
lovasoa

すばらしい説明です。集約関数の機能の基礎を理解するのに本当に役立ちました。私はしばらくの間それで遊んで、以下のように見つけました。

  • accを(0,0)として使用している場合、関数の出力の結果は変更されません。

  • 初期のアキュムレータが変更された場合、結果は以下のように処理されます

[RDD要素の合計+ acc初期値* RDDパーティションの数+ acc初期値]

ここでの質問については、RDDのパーティションでseq opを処理するたびにaccの結果の初期合計で開始するため、パーティションの数は8になるはずなので、パーティションをチェックすることをお勧めします。 comb Opを実行します。もう一度acc初期値を使用します。

例えばリスト(1,2,3,4)&acc(1,0)

scala by RDD.partitions.sizeでパーティションを取得する

パーティションが2で要素の数が4の場合、=> [10 + 1 * 2 + 1] =>(13,4)

パーティションが4で要素の数が4の場合=> [10 + 1 * 4 + 1] =>(15,4)

これがお役に立てば幸いです。説明のために here を確認してください。ありがとう。

1
iSingh

Scala上記の例と同等のコード-ここにあります。同じロジック、同じ入力/結果。

scala> val listRDD = sc.parallelize(List(1,2,3,4), 2)
listRDD: org.Apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[2] at parallelize at <console>:21

scala> listRDD.collect()
res7: Array[Int] = Array(1, 2, 3, 4)

scala> listRDD.aggregate((0,0))((acc, value) => (acc._1+value,acc._2+1),(acc1,acc2) => (acc1._1+acc2._1,acc1._2+acc2._2))
res10: (Int, Int) = (10,4)

Sparkで集計操作の概念を次のように説明します。

集約関数の定義

_**def aggregate** (initial value)(an intra-partition sequence operation)(an inter-partition combination operation)
_

val flowers = sc.parallelize(List(11, 12, 13, 24, 25, 26, 35, 36, 37, 24, 25, 16), 4)-> 4は、Sparkクラスターで利用可能なパーティションの数を表します。

したがって、rddは次のように4つのパーティションに分散されます。

_11, 12, 13
24, 25, 26
35, 36, 37
24, 25, 16
_

問題ステートメントを2つの部分に分割します。問題の最初の部分は、各象限で摘んだ花の総数を集計することです。それがパーティション内シーケンスの集約です

_11+12+13 = 36
24+25+26 = 75
35+36+37 = 108
24+25 +16 = 65
_

問題の2番目の部分は、パーティション全体でこれらの個々の集計を合計することです。それがパーティション間の集約です。

_36 + 75 + 108 + 65 = 284
_

RDDに保存された合計は、あらゆる種類の変換またはその他のアクションにさらに使用および処理できます。

したがって、コードは次のようになります。

val sum = flowers.aggregate(0)((acc, value) => (acc + value), (x,y) => (x+y))またはval sum = flowers.aggregate(0)(_+_, _+_)
_Answer: 284_

説明:(0)-アキュムレーターです最初の+は区画内合計で、花の総数を加算します庭の各象限の各ピッカーが摘み取った。 2番目の+はパーティション間の合計で、各象限の合計を集計します。

ケース1:

初期値の後に関数を減らす必要があると仮定します。初期値がゼロでない場合はどうなりますか?たとえば、4だった場合:

番号は、各パーティション内集計に追加され、パーティション間集計にも追加されます。

したがって、最初の計算は次のようになります。

_11+12+13 = 36 + 5 = 41
24+25+26 = 75 + 5 = 80
35+36+37 = 108 + 5 = 113
24+25 +16 = 65 + 5 = 70
_

以下は、初期値が5のパーティション間集計計算です。

_partition1 + partition2 + partition3+ partition4 + 5 = 41 + 80 + 113 + 70 = 309
_

それで、クエリを見てみましょう。合計は、rddデータが分散されているパーティションの数に基づいて計算できます。私はあなたのデータが以下のように分布していると思ったので、結果は(19、4)になります。そのため、集計操作を実行する場合、パーティション値の数で具体的にしてください:

_val list = sc.parallelize(List(1,2,3,4))
val list2 = list.glom().collect
val res12 = list.aggregate((1,0))(
      (acc, value) => (acc._1 + value, acc._2 + 1),
      (acc1, acc2) => (acc1._1 + acc2._1, acc1._2 + acc2._2)
)
_

結果:

_list: org.Apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[19] at parallelize at command-472682101230301:1
list2: Array[Array[Int]] = Array(Array(), Array(1), Array(), Array(2), Array(), Array(3), Array(), Array(4))
res12: (Int, Int) = (19,4)
_

説明:データが8つのパーティションに分散されているため、結果は次のようになります(上記のロジックを使用)

パーティション内追加:

_0+1=1
1+1=2
0+1=1
2+1=3
0+1=1
3+1=4
0+1=1
4+1=5

total=18
_

パーティション間計算:

_18+1 (1+2+1+3+1+4+1+5+1) = 19
_

ありがとうございました

0

私はこの質問について多くの実験を試みます。集計用のパーティションの数を設定することをお勧めします。 seqOpは各パーティションを処理し、初期値を適用します。さらに、combOpはすべてのパーティションを結合するときに初期値も適用します。したがって、この質問の形式を提示します。

final result = sum(list) + num_Of_Partitions * initial_Value + 1
0
W.Sen

Gsamarasに感謝します。

私のビューグラフは以下の通りです enter image description here

0
ryh