【问题标题】:Efficient search in a corpus语料库中的高效搜索
【发布时间】:2010-01-24 22:31:35
【问题描述】:

我有几百万个单词,我想在十亿个单词的语料库中搜索。这样做的有效方法是什么。

我正在考虑一个 trie,但是有可用的 trie 的开源实现吗?

谢谢

-- 更新了--

让我补充一些关于究竟需要什么的更多细节。

我们有一个系统,可以抓取新闻来源并根据词的频率获取热门词。可能有一百万字。

我们的数据将如下所示。

Word1 频率1 Word2 频率2 (制表符分隔)

我们还从另一个来源获得了最流行的词(10 亿个),其中也包含上述格式的数据。

这是我想要得到的输出。

  • 两个来源的共同词
  • 单词仅出现在我们的来源中,而不出现在参考来源中。
  • 单词仅出现在参考源中,而不出现在我们的源中。

我只能对上述信息使用 comm(bash 命令)来获取单词。我不知道如何使用 comm 只比较一列而不是两列。

系统应该是可扩展的,我们希望每天都执行此操作并比较结果。我也想得到近似匹配。

所以,我正在考虑编写一个 map reduce 作业。我打算写下面的map和reduce函数,但我有几个问题。

Map
For each word
output key = word and value = structure{ filename,frequency}
done

Reduce
For each key
Iterate through all the values and check if both file1 and file2 are contained.
If yes, then write it to appropriate file.
If only in file1, write it to file1only file
If only in file2, write it to file2only file.
Done.

我有两个问题。 在 map reduce 中,我可以提供一个包含我的两个文件的目录作为输入。我不知道如何获取我从中读取单词的文件名。如何获取这些信息? 如何写入不同的输出文件,因为 reduce 阶段会自动写入名为 part-xxxxx 的默认文件。如何写入不同的输出文件。

感谢您阅读本文。

【问题讨论】:

  • 你能把它们放在一个 SQL 数据库中,然后使用全文搜索吗?
  • @Travis:他说“高效”。 :)
  • 看起来只需要执行一次(要查询的字数是已知的),所以我会说效率被高估了。它只需要快速完成。
  • 好的,你有一个包含一百万个不同单词的集合和一个总计十亿个单词的文档集合。你到底在寻找什么输出?如果输出也很大,您希望从那个中获得什么样的信息?
  • 纯粹出于好奇,十亿个最常用词的随机样本会是什么样子?鉴于 worniloophorectomized 等宝物显然在前 700,000 个英文单词中,99.9% 的词条的单词列表的想法甚至更少 常见的令人难以置信。

标签: algorithm search data-structures hadoop mapreduce


【解决方案1】:

使用 MapReduce,您不应该尝试一步一步完成所有事情。看起来您应该将此问题拆分为多个步骤。由于您正在生成存储在 HDFS 上的数据,并且您需要知道来源,您可能应该采用如下格式:

{SOURCE},{WORD},{FREQUENCY}

请记住,您说的是分布式文件系统,因此将您的输入称为 file1 和 file2 在技术上是不正确的。您的参考数据和源数据都将分布在整个集群中,每个数据块都位于每个节点上。

接下来,从您的伪代码示例开始,您需要创建一个将单词与源及其频率相关联的作业。您的映射器可以正常工作,但 reduce 需要将单词链接到源。您需要创建自己的 Writable 对象,其中包含 Map。这将作为后续过滤作业可以使用的中间数据输出到 HDFS。

然后,您可以将此步骤的输出用作 3 个不同 MapReduce 作业的输入。每个人都在寻找不同的来源组合。这些作业将非常简单,因为映射器将只传递相同的数据,但化简器将检查每个值的不同来源组合。

因此,如果您采用这种方法,您将需要 4 个 MapReduce 作业。您不需要手动运行每个作业,您可以拥有一个按顺序运行每个作业的作业。或者,由于最后 3 个作业将使用相同的输入数据,因此您可以在第一个作业完成后同时启动这三个作业。这可能取决于您的集群能够管理的数据量和中间数据量,以及每个作业所需的映射器/缩减器的数量。

希望这个建议有所帮助。

【讨论】:

  • 您的建议非常有用。我只是有一些澄清。您是否建议我将源也保留为输入数据的一部分?在第一个 map reduce 作业之后,我会在相同的输入数据上同时运行其他 3 个 map reduce 作业吗?如果所有三个作业都读取相同的数据并根据我需要的输出留下一些数据,你不认为它的效率会很低吗?有没有办法改善这一点?我认为其他三个 map reduce 作业应该在 map 中什么都不做,但在 reduce 阶段应用所需的逻辑。真的吗?再次感谢。
  • 嗨,是的,我建议您将数据源作为字段,这也将允许更灵活的实现,从而允许您添加更多源而无需修改代码。第一项工作会为您索引单词,因此它生成的中间数据会非常大。您需要确保 HDFS 中有足够的空间来容纳它。一旦过滤作业运行,它就可以被删除,是的,最后的 3 个作业只需要在 reduce 中有逻辑。地图什么也不做。
  • 考虑一下,您也可以将索引和单个过滤步骤合并在一起。那时你就不需要中间数据了。您将拥有三份工作,每份工作都回答一个问题。我认为我的主要观点是,不要尝试在一项工作中做太多事情。
【解决方案2】:

这看起来像是为Aho-Corasick 字符串搜索算法设计的工作。我自己从来没有编写过代码,但是用谷歌搜索一下应该会找到一些代码。

Rabin-Karp 也可能有效,但我不知道当多个模式的长度不同时它是如何工作的。注意:维基百科文章中的多模式伪代码似乎是错误的。但应该给你一个起点。

【讨论】:

    【解决方案3】:

    本着又快又脏的精神:

    fgrep --mmap -f query-file corpus-file
    

    【讨论】:

      【解决方案4】:

      如果我在 Java 中执行此操作,我会使用 HashMap。 Wikipedia 建议在某些情况下使用 trie 稍微好一些,但我不确定您会看到多大的不同。

      【讨论】:

      • 十亿字是很多的数据。我认为在这里尝试尝试一下是值得的,主要是因为通过组合前缀来减少内存使用量。
      • 我认为单词本身的大小和索引它们的数据结构将很适合内存。可能有一百万个不同的单词。这里令人难以管理的是关于每个单词使用的所有地方的数据(必须以某种形式包含十亿个条目)。
      • 实际上我们在这里的错位可能是由于问题的模糊性。
      【解决方案5】:

      文本搜索引擎中使用的数据结构称为inverted index。正如已经说过的,非常好的开源搜索引擎是Lucene

      【讨论】:

        【解决方案6】:

        我不确定它的性能,但 Python 的 nltk 旨在做这种事情:标记大型文本语料库并允许您在它们之间进行比较。 “Natural Language Processing with Python”一书使用了这个工具包,并有很多例子。可免费使用online

        【讨论】:

          【解决方案7】:

          编译成 a.out 的 tokenizer.c 可以对语料库进行标记,然后使用 systemclose shell 脚本来提高性能

           ./a.out <
          /live/memory/var/cache/man/whatis  | sort | awk {'print $1'} | uniq -c
          | sort -rn > file.txt
          

          【讨论】:

            【解决方案8】:

            台式电脑可以做到这一点。较小的数据集将适合内存,这就是您所需要的。

            在 Python 中:

            # Load the words from the small file into one big hash set
            small_set = set(line.split()[0] for line in open("small.txt", "r"))
            
            # Open 3 output files.
            f1 = open("common.txt", "w")
            f2 = open("large_only.txt", "w")
            f3 = open("small_only.txt", "w")
            
            # Find all words in the large set that aren't in the small set.
            for line in open("large.txt", "r"):
                word = line.split()[0]
                if word in small_set:
                    f1.write(line)  # word is in both sets
                    small_set.remove(word)
                else:
                    f2.write(line)  # word is in large but not small
            
            # Everything left over in small_set wasn't in the large_set.
            for word in small_set:
                f3.write(word + "\n")
            

            集群可以做得更快。但是你可以在家里试试这个。

            【讨论】:

              【解决方案9】:

              既然你可以使用comm,我想你一定已经对输入文件进行了排序。

              这是一个类似comm 的程序,它只查看第一列,但生成包含整行输入的输出。仅当输入已排序时才有效!

              这是一个完整的程序。你所要做的就是把它放在一个文本文件中,然后从命令行运行它。

              #!/usr/bin/env python
              #
              # comm.py - Compare 2 sorted files line by line, based on the first column.
              # Usage:   python compare.py FILE1 FILE2 OUTFILE1 OUTFILE2 OUTFILE12
              # OUTFILE1 receives all entries that are only in FILE1, etc.
              
              import sys
              
              def compare(f1, f2, out1, out2, out12):
                  def get(f):
                      line = f.readline()
                      if line == '':
                          return None
                      first, rest = line.rstrip('\n').split('\t', 1)
                      return first, rest, line
              
                  e1 = get(f1)
                  e2 = get(f2)
                  while e1 and e2:
                      if e1[0] == e2[0]:   # common entry
                          out12.write(e1[0] + "\t" + e1[1] + "\t" + e2[1] + "\n")
                          e1 = get(f1)
                          e2 = get(f2)
                      elif e1[0] < e2[0]:  # e1 is not in f2
                          out1.write(e1[2])
                          e1 = get(f1)
                      else:                # e2 is not in f1
                          out2.write(e2[2])
                          e2 = get(f2)
                  if e1:
                      buf = e1[2]
                      while buf:
                          out1.write(buf)
                          buf = f1.read(8192)
                  if e2:
                      buf = e2[2]
                      while buf:
                          out2.write(buf)
                          buf = f2.read(8192)
              
              compare(open(sys.argv[1], "r"),
                      open(sys.argv[2], "r"),
                      open(sys.argv[3], "w"),
                      open(sys.argv[4], "w"),
                      open(sys.argv[5], "w"))
              

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2020-12-21
                • 1970-01-01
                • 1970-01-01
                • 2013-10-31
                • 1970-01-01
                • 1970-01-01
                • 2013-10-06
                • 2010-09-23
                相关资源
                最近更新 更多