Как интерпретировать RDD.treeAggregate

Я столкнулся с этой строкой в исходном коде Apache Spark

val (gradientSum, lossSum, miniBatchSize) = data
    .sample(false, miniBatchFraction, 42 + i)
    .treeAggregate((BDV.zeros[Double](n), 0.0, 0L))(
      seqOp = (c, v) => {
        // c: (grad, loss, count), v: (label, features)
        val l = gradient.compute(v._2, v._1, bcWeights.value, Vectors.fromBreeze(c._1))
        (c._1, c._2 + l, c._3 + 1)
      },
      combOp = (c1, c2) => {
        // c: (grad, loss, count)
        (c1._1 += c2._1, c1._2 + c2._2, c1._3 + c2._3)
      }
    )

У меня много проблем с чтением:

  • Сначала я не могу найти что-либо в Интернете, которое объясняет, как работает treeAggregate, каков смысл параметров.
  • Во-вторых, здесь .treeAggregate, похоже, имеет два()(), следующих за именем метода. Что это может означать? Это какой-то специальный синтаксис scala, который я не понимаю.
  • Наконец, я вижу, как seqOp и comboOp возвращают 3 элемента кортежа, которые соответствуют ожидаемой левой стороне переменной, но какая из них фактически возвращается?

Это утверждение должно быть действительно продвинутым. Я не могу начать расшифровывать это.

Ответ 1

treeAggregate - это специализированная реализация aggregate которая итеративно применяет функцию объединения к подмножеству секций. Это сделано для того, чтобы предотвратить возвращение всех частичных результатов драйверу, где за один проход будет происходить уменьшение, как это делает классическая aggregate.

Для всех практических целей treeAggregate следует тому же принципу, что и aggregate объясненный в этом ответе: Объясните агрегированную функциональность в Python, за исключением того, что для указания глубины уровня частичной агрегации требуется дополнительный параметр.

Позвольте мне попытаться объяснить, что здесь происходит конкретно:

Для агрегата нам нужны ноль, функция объединения и функция сокращения. aggregate использует каррирование для задания нулевого значения независимо от функций объединения и сокращения.

Затем мы можем проанализировать вышеуказанную функцию следующим образом. Надеюсь, это поможет понять:

val Zero: (BDV, Double, Long) = (BDV.zeros[Double](n), 0.0, 0L)
val combinerFunction: ((BDV, Double, Long), (??, ??)) => (BDV, Double, Long)  =  (c, v) => {
        // c: (grad, loss, count), v: (label, features)
        val l = gradient.compute(v._2, v._1, bcWeights.value, Vectors.fromBreeze(c._1))
        (c._1, c._2 + l, c._3 + 1)
val reducerFunction: ((BDV, Double, Long),(BDV, Double, Long)) => (BDV, Double, Long) = (c1, c2) => {
        // c: (grad, loss, count)
        (c1._1 += c2._1, c1._2 + c2._2, c1._3 + c2._3)
      }

Затем мы можем переписать вызов treeAggregate в более удобной форме:

val (gradientSum, lossSum, miniBatchSize) = treeAggregate(Zero)(combinerFunction, reducerFunction)

Эта форма будет "извлекать" результирующий кортеж в именованные значения gradientSum, lossSum, miniBatchSize для дальнейшего использования.

Обратите внимание, что treeAggregate принимает дополнительный параметр depth который объявлен со значением по умолчанию Значение depth = 2, поэтому, поскольку это не предусмотрено в данном конкретном вызове, он примет это значение по умолчанию.