【问题标题】:most efficient way to repeatedly search a Scala iterator重复搜索 Scala 迭代器的最有效方法
【发布时间】:2016-01-05 19:15:55
【问题描述】:

我有一个大文本文件,其中包含由 Gigaword 构建的 Word2Vec 向量(大小超过 3GB),每一行都是一个单词及其对应的向量。它是按频率排序的,所以列表中频率高的词比低频率的词高。

对于给定的单词列表,我需要构建一个由单词及其 word2vec 向量组成的 Scala Map。以下是我的做法:


  1. 对于每个单词,将文件作为迭代器打开:

    val it = scala.io.Source.fromFile(filePath).getLines()

  2. 使用find查找匹配的单词,如果没有找到则使用默认值:

    val match = it.find(_.split(" ").head == word).getOrElse("zzz" 0d)


这是我的完整方法:

def buildArray2b: (Double, Array[(String, breeze.linalg.DenseVector[Double])]) = {
val startAll = System.currentTimeMillis().toDouble
val stream = (for (word <- this.vocabulary.map(each => each.toLowerCase)) yield {
  println("starting " + word)
  val start = System.currentTimeMillis().toDouble
  println("building iterator")
  val iterator = Source.fromInputStream(this.inputStream).getLines()
  println("finding")
  val line = iterator.find(it => it.split(" ").head == word).getOrElse("zzz 0.0")
  println("found")
  val splitLine = line.split(" ")                                                                       //split string into elements
  val tail = splitLine.tail.map(_.toDouble)                                                             //build w2v vector
  val vectorizedLine = splitLine.head -> breeze.linalg.DenseVector(tail)                                //build map entry
  val stop = System.currentTimeMillis().toDouble
  println(word + ":" + (stop - start) / 1000d)
  vectorizedLine
}).toArray
val stopAll = System.currentTimeMillis().toDouble
val elapsed = (stopAll - startAll) / 1000d
(elapsed, stream)

}

这里是找到以下单词“a”、“quixotic”和“the”的次数的输出:

scala> w2v.buildArray2
a:0.001
quixotic:0.795
the:25.6

我不知道为什么很快就可以找到“不切实际的”(与“a”和“the”相比,它在列表中应该“靠后”),却永远找到了“the”这个词。

我在数据结构方面的经验很少,因此我很感激 (1) 对此问题的任何见解以及 (2) 关于如何使这个过程更高效的任何建议。

为此,我已经尝试了以下方法:

  1. 将整个文件加载到地图中。这需要很长时间才能先转换为序列,然后再转换为地图。
  2. 将 .txt 文件转换为 .json,然后使用包(在本例中为 json4s)将该 .json 文件直接打开到地图中。我遇到了内存错误(我已经为这个项目分配了 14g 内存)。

提前感谢任何 cmets/见解!

【问题讨论】:

    标签: scala iterator find


    【解决方案1】:

    使用地图

    只要您没有内存问题,使用Map[String,Vector[String]] 将是一个不错的选择。 读取文件一次并将数据放入 Map。你已经差不多了,因为几乎所有的Seq[Tuple2]都可以使用toMap轻松转换为地图。 您将获得每个键的constant time access

    转成json

    这会增加一个额外的间接步骤。它只会让需要解析和处理的数据增长,让处理变得更慢。

    迭代器和重用

    引用官方 Scala 文档:http://www.scala-lang.org/api/current/index.html#scala.collection.IteratorThey have a hasNext method for checking if there is a next element available, and a next method which returns the next element and discards it from the iterator. 所以根据定义,Iterator 是不可重用的。

    【讨论】:

      【解决方案2】:

      Scala 的Iterator 是有状态的,不打算共享或重用。任何可能的共享或重用都完全取决于被迭代的底层资源。 IE。您可以安全地多次迭代文件,但在下载流上重复使用迭代器没有意义。

      您可以通过将vocabulary 转换为MapSet 在线性时间和恒定空间中执行此操作。在较高级别上,您需要迭代 word2vec 文件中的每一行并检查该单词是否在您的 vocabulary 中,然后将该单词和权重添加到映射中,如下所示:

      val vocab = this.vocabulary.toSet
      val it = Source.fromInputStream(inputStream).getLines
      val result: Map[String, DenseVector] = foldLeft(Map.empty[String, DenseVector]){(acc, line) => 
          val Array(word, weight) = line.split(" ")
          if(vocab.contains(word))
               acc + (word -> breeze.linalg.DenseVector(weight.toDouble))
          else 
               acc
       }
      

      foldLeft 从迭代器的左侧(即头部)遍历整个文件。在每一行,它检查单词是否是词汇单词,如果是,则将其添加到映射 (acc),然后在处理完所有迭代器后返回。

      【讨论】:

      • 感谢@Andreas Neumann 和克里斯。我可能没有把我的问题说清楚。如果我不需要,我确实 NOT 想要遍历整个 word2vec 文件。它是 HUGE,平均而言,我的 vocabulary 只有大约 500 个不同的单词。这就是我使用find 的原因,它永远不必遍历整个 word2vec 文件 IF 我的词汇表中的所有单词都存在。
      • 您可以对此进行优化,并在找到元素时从词汇集中删除它们,然后在找到所有词汇后终止循环。
      猜你喜欢
      • 1970-01-01
      • 2016-05-25
      • 1970-01-01
      • 1970-01-01
      • 2020-09-13
      • 1970-01-01
      • 1970-01-01
      • 2018-03-29
      相关资源
      最近更新 更多