繁体   English   中英

将随机元素添加到来自同一个 RDD 的键控 RDD

[英]Add random elements to keyed RDD from the same 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。

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

简而言之,我们需要一种分布式方法来对所有值进行加权样本。

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

让我们为此编写一个基于 spark 的算法:

// 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阶段中每个键的值列表,这只是为了符合问题所使用的表示。

在此处输入图片说明

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM