从同一个RDD

时间:2016-11-03 09:09:05

标签: scala apache-spark rdd

想象一下,我们有一个带密钥的RDD RDD[(Int, List[String])],其中包含数千个密钥和数千到数百万个值:

val rdd = sc.parallelize(Seq(
  (1, List("a")),
  (2, List("a", "b")),
  (3, List("b", "c", "d")),
  (4, List("f"))))

对于每个键,我需要从其他键添加随机值。要添加的元素数量会有所不同,具体取决于键中元素的数量。因此输出看起来像:

val rdd2: RDD[(Int, List[String])] = sc.parallelize(Seq(
  (1, List("a", "c")),
  (2, List("a", "b", "b", "c")),
  (3, List("b", "c", "d", "a", "a", "f")),
  (4, List("f", "d"))))

我提出了以下解决方案,这显然效率不高(注意:展平和聚合是可选的,我对扁平数据很好):

// flatten the input RDD
val rddFlat: RDD[(Int, String)] = rdd.flatMap(x => x._2.map(s => (x._1, s)))
// calculate number of elements for each key
val count = rddFlat.countByKey().toSeq
// foreach key take samples from the input RDD, change the original key and union all RDDs
val rddRandom: RDD[(Int, String)] = count.map { x =>
  (x._1, rddFlat.sample(withReplacement = true, x._2.toDouble / count.map(_._2).sum, scala.util.Random.nextLong()))
}.map(x => x._2.map(t => (x._1, t._2))).reduce(_.union(_))

// union the input RDD with the random RDD and aggregate
val rddWithRandomData: RDD[(Int, List[String])] = rddFlat
    .union(rddRandom)
    .aggregateByKey(List[String]())(_ :+ _, _ ++ _)

实现这一目标的最有效和最优雅的方法是什么? 我使用Spark 1.4.1。

1 个答案:

答案 0 :(得分:1)

通过查看当前的方法,并且为了确保解决方案的可扩展性,可能的焦点领域应该是提出一种可以以分布式方式完成的采样机制,从而无需收集钥匙回到司机。

简而言之,我们需要一个分布式方法来处理所有值的加权样本。

我建议创建一个矩阵keys x values,其中每个单元格是为该键选择值的概率。然后,我们可以随机对该矩阵进行评分,并选择那些落在概率范围内的值。

让我们为此写一个基于火花的算法:

// sample data to guide us. 
//Note that I'm using distinguishable data across keys to see how the sample data distributes over the keys 
val data = sc.parallelize(Seq(
  (1, List("A", "B")),
  (2, List("x", "y", "z")),
  (3, List("1", "2", "3", "4")),
  (4, List("foo", "bar")),
  (5, List("+")),
  (6, List())))

val flattenedData = data.flatMap{case (k,vlist) => vlist.map(v=> (k,v))}
val values = data.flatMap{case (k,list) => list}
val keysBySize = data.map{case (k, list) => (k,list.size)}
val totalElements = keysBySize.map{case (k,size) => size}.sum
val keysByProb = keysBySize.mapValues{size => size.toDouble/totalElements}
val probMatrix = keysByProb.cartesian(values)
val scoredSamples = probMatrix.map{case ((key, prob),value) => 
    ((key,value),(prob, Random.nextDouble))}

ScoredSamples看起来像这样:

((1,A),(0.16666666666666666,0.911900315814998))
((1,B),(0.16666666666666666,0.13615047422122906))
((1,x),(0.16666666666666666,0.6292430257377151))
((1,y),(0.16666666666666666,0.23839887096373114))
((1,z),(0.16666666666666666,0.9174808344986465))

...

val samples = scoredSamples.collect{case (entry, (prob,score)) if (score<prob) => entry}

samples看起来像这样:

(1,foo)
(1,bar)
(2,1)
(2,3)
(3,y)
...

现在,我们将采样数据与原始数据结合起来并得到我们的最终结果。

val result = (flattenedData union samples).groupByKey.mapValues(_.toList)

result.collect()
(1,List(A, B, B))
(2,List(x, y, z, B))
(3,List(1, 2, 3, 4, z, 1))
(4,List(foo, bar, B, 2))
(5,List(+, z))

鉴于所有算法都是作为原始数据的转换序列编写的(参见下面的DAG),最小的改组(只有最后groupByKey,这是通过最小结果集完成的),它应该可扩展。唯一的限制是groupByKey阶段中每个键的值列表,这只是为了符合使用该问题的表示。

enter image description here