Spark发现两个元素的余弦测量

时间:2018-03-13 19:57:53

标签: scala apache-spark measure cosine

我有一个.csv文件,如下所示:

  

message_id,hashtag
  id_1,hashtag_1
  id_2,hashtag_1
  ...
  id_k,hashtag_m
  ....

我试图通过执行以下操作在csv中的每对主题标签之间找到余弦度量:

def isHeader(s: String):Boolean = {
    s.take(1) == "m"
}
def cs(pair: ( (String, String), (List[String], List[String]) ) ) = {
    val msgs1 = pair._2._1.toSet
    val msgs2 = pair._2._2.toSet
    val numer = msgs1.intersect(msgs2).size
    val denom = Math.sqrt(msgs1.size*msgs2.size)
    (pair._1._1, pair._1._2, numer / denom)
}
def to_csv_cos(t: (String, String, Double)): String = {
    t._1 ++ "," ++ t._2 ++ "," ++ t._3.toString
}
val messages = sc.textFile("....csv")
val msgData1 = messages.filter(x => !isHeader(x))
val data = msgData1.map(x => x.split(','))
val pairs = data.map(x => (x(0), List(x(1)))).reduceByKey((a,b) => a ++ b).flatMap(x => x._2.combinations(2).toList).map(x => (x(0), x(1)))
val msgs = data.map( x => (x(1), List(x(0)))).reduceByKey((a,b) => a++b)
val pairs_mapped = pairs.join(msgs).map{
     case (x, (y,z)) => (y, (x,z))
}.join(msgs).map{
    case (x, ( (y,z),t) ) => ( (x,y), (t,z) )
}
val res = pairs_mapped.map{
    x => cs(x)  
}.map(x => to_csv_cos(x)).saveAsTextFile("F:\\Scala\\result")

这个想法是:

  
      
  1. 从元素(对)创建对
  2.   
  3. 查找每个主题标签(msgs)的所有消息
  4.   
  5. 创建对:((hashtag_i,hashtag_j),(messages_with_hashtag_i,messages_with_hashtag_j))(pairs_mapped)
  6.   
  7. 计算度量(res)
  8.   

嗯,我想我的代码只是一个垃圾,因为我对scala,spark和functuonal编程的概念很新,但它适用于小型csv(我试过100行)。但我必须用~25百万行来计算csv,并且存在问题。该过程在saveAsTextFile(或Spark中的SparkHadoopWriter.scala)上停止,并且即使在30分钟内也不会继续运行,然后它会因不同的错误而崩溃(内存错误,有时只是'连接中止' .. 。)

我在网站上发现,我们可以使用数据框计算余弦度量,但我不太清楚如何从数据中创建正确的数据帧。
那么,请你给我一些建议如何修改我的代码以使其工作或如何从csv或其他任何东西创建一个正确的数据框? 我将非常感谢你能给予的任何帮助!

2 个答案:

答案 0 :(得分:0)

这是机器内存不足时的典型行为。

我建议你尝试将工作量划分为机器可以处理的批次。

你说100很容易。试试1000.这有用吗?探索可能的事情,然后相应地组织您的计划。

答案 1 :(得分:0)

RDD具有惰性和主动操作。活动操作是'saveToTextFile','persist','cash','collect','top','take','foreach'。懒惰的操作是'map','filter','group','join'和其他很多。在您的程序中,几乎所有操作都是懒惰的。懒惰的集合可以多次计算。您应该使用缓存,以便仅对其进行一次评估。

当您使用大数据时,您应该使用内存谨慎。 如果您的标签或标识符是Int,则应使用_.toInt。未经测试,避免将文本文件用于非学习程序和目的。文本文件对于一个人来说非常好,但对于PC来说可能很慢:例如,Double需要8个字节,但如果你将0.4082482904638631写入文本文件,则此Double需要18个字符(36个字节)。此外,如果您的标签或ID具有恒定大小,则无法编写逗号。

抱歉我的英语不好。

  type Id = String
  type Tag = String
  type Measure = Double

  val messages: RDD[String] = sc.textFile("1.csv").filter(_.head != 'm')
  val data: RDD[(Tag, Id)] = messages.map(line => line.split(","))
    .map(pair => pair(1) -> pair(0))
  val tagIds: RDD[(Tag, Set[Id])] = data.groupByKey()
    .mapValues(_.toSet)
    .persist(StorageLevel.MEMORY_AND_DISK)
  val tagIds1TagIds2: RDD[((Tag, Set[Id]), (Tag, Set[Id]))] = tagIds.cartesian(tagIds).filter({
    case ((t1,s1), (t2,s2)) => t1 < t2
  })
  val tagPairsWithMeasure: RDD[(Tag, Tag, Measure)] = tagIds1TagIds2.map({
    case ((t1,l1), (t2,l2)) => (t1,t2, {
      val numer = l1.intersect(l2).size
      val denom = Math.sqrt(l1.size*l2.size)
      numer.toDouble / denom
    })
  })
  val lines: RDD[String] = tagPairsWithMeasure.map({
    case (t1, t2, m) => s"$t1,$t2,$m"
  })

测试:

id1,tag1
id1,tag2
id3,tag3
id3,tag2
id5,tag3
id6,tag1
id7,tag1
id8,tag2

答案:

tag2,tag3,0.4082482904638631 // 1/sqrt(3*2)
tag1,tag2,0.3333333333333333 // 1/sqrt(3*3)
tag1,tag3,0.0                // 0/sqrt(3*2)