存储n-gram数据


12

我希望就存储n- gram数据的问题进行一些讨论。在我的项目中,我正在尝试解决所有我知道(n -1)个数据项的语言问题,并希望在所有适用的n- gram上使用线性插值来统计地猜测我的n。(是的,有一个标记器根据其词典将标记分配给已知单词,还有一个后缀树试图猜测未知单词的单词种类;这里讨论的n -gram组件将负责解决歧义。)

我最初的方法是简单地将所有观察到的n元(对于n = 1..3,即会标,二元组,三元组)数据存储在相应的SQL数据库中,并称之为一天。但是我的项目要求可能会改变,以包括其他向量长度(n),我希望我的应用程序能够适应4克语言而无需进行大量工作(更新架构,更新应用程序代码等);理想情况下,我只是简单地告诉我的应用程序现在可以处理4克代码,而不必太多(或根本不需要)更改代码并从给定的数据源训练其数据。

总结所有要求:

  • 能够存储n克数据(最初用于n = {1,2,3}
  • 能够更改应使用哪种n- gram(在应用程序运行之间)
  • 能够(重新)训练n- gram数据(在应用程序运行之间)
  • 能够查询数据存储(例如,如果我观察到A,B,C,我想知道使用我训练有素的4、3、2、1克数据集后最常观察到的项目)

    该应用程序很可能是读取繁重的,很可能不会经常重新训练数据集

  • 该解决方案采用.NET Framework(最高4.0)

现在,哪种设计更适合此类任务?

  • 由SQL服务器(MSSQL,MySQL等)为每个n管理的固定表(例如,用于二元语法,三元语法等的专用表)
  • 还是将第一个n -1 存储为文档的键的NoSQL文档数据库解决方案,并且文档本身包含第n个值和观察到的频率?
  • 还是有所不同?

3
我认为这将更适合于Stack Overflow。
康拉德·鲁道夫

1
也许trie(前缀树)数据结构符合您的要求?
Schedler

1
我建议堆栈溢出,甚至cstheory.stackexchange.com
史蒂夫

好的谢谢。我会尽力在那儿提问题。
Manny

4
这个问题非常适合programmers.stackexchange.com,不应迁移到IMO的stackoverflow。这正是此处应该提出的“白板情况”问题。检查meta以获取详细信息。
user281377 2011年

Answers:


8

鉴于您不知道N的最佳范围,您肯定希望能够更改它。例如,如果您的应用程序预测某个文本为英语的可能性,则可能要对N 3..5使用字符N-gram。(这是我们通过实验发现的。)

您尚未共享有关应用程序的详细信息,但是问题很明显。您想在关系数据库(或基于NoSQL文档的解决方案)中表示N元语法数据。在提出自己的解决方案之前,您可能需要看一下以下方法:

  1. 如何最好地将Google ngram存储在数据库中?
  2. 将n元语法存储在<n个表中
  3. 使用关系数据库管理Google Web 1T 5克

现在,在没有阅读上述任何链接的情况下,我建议一种简单的关系数据库方法,该方法使用多个表,每个表用于N-gram的大小。您可以将所有数据放在具有最大必要列的单个表中(即,将双字母组和三字组存储在ngram_4中,将最后一列保留为空),但是我建议对数据进行分区。根据您的数据库引擎,具有大量行的单个表可能会对性能产生负面影响。

  create table ngram_1 (
      word1 nvarchar(50),
      frequency FLOAT,
   primary key (word1));

  create table ngram_2 (
      word1 nvarchar(50),
      word2 nvarchar(50),
      frequency FLOAT,
   primary key (word1, word2));

  create table ngram_3 (
      word1 nvarchar(50),
      word2 nvarchar(50),
      word3 nvarchar(50),
      frequency FLOAT,
   primary key (word1, word2, word3));

  create table ngram_4 (
      word1 nvarchar(50),
      word2 nvarchar(50),
      word3 nvarchar(50),
      word4 nvarchar(50),
      frequency FLOAT,
   primary key (word1, word2, word3, word4));

接下来,我将给您一个查询,该查询将在给定所有ngram表的情况下返回最可能的下一个单词。但是首先,这是一些示例数据,您应该将它们插入上述表中:

  INSERT [ngram_2] ([word1], [word2], [frequency]) VALUES (N'building', N'with', 0.5)
  INSERT [ngram_2] ([word1], [word2], [frequency]) VALUES (N'hit', N'the', 0.1)
  INSERT [ngram_2] ([word1], [word2], [frequency]) VALUES (N'man', N'hit', 0.2)
  INSERT [ngram_2] ([word1], [word2], [frequency]) VALUES (N'the', N'bat', 0.7)
  INSERT [ngram_2] ([word1], [word2], [frequency]) VALUES (N'the', N'building', 0.3)
  INSERT [ngram_2] ([word1], [word2], [frequency]) VALUES (N'the', N'man', 0.4)
  INSERT [ngram_2] ([word1], [word2], [frequency]) VALUES (N'with', N'the', 0.6)
  INSERT [ngram_3] ([word1], [word2], [word3], [frequency]) VALUES (N'building', N'with', N'the', 0.5)
  INSERT [ngram_3] ([word1], [word2], [word3], [frequency]) VALUES (N'hit', N'the', N'building', 0.3)
  INSERT [ngram_3] ([word1], [word2], [word3], [frequency]) VALUES (N'man', N'hit', N'the', 0.2)
  INSERT [ngram_3] ([word1], [word2], [word3], [frequency]) VALUES (N'the', N'building', N'with', 0.4)
  INSERT [ngram_3] ([word1], [word2], [word3], [frequency]) VALUES (N'the', N'man', N'hit', 0.1)
  INSERT [ngram_3] ([word1], [word2], [word3], [frequency]) VALUES (N'with', N'the', N'bat', 0.6)
  INSERT [ngram_4] ([word1], [word2], [word3], [word4], [frequency]) VALUES (N'building', N'with', N'the', N'bat', 0.5)
  INSERT [ngram_4] ([word1], [word2], [word3], [word4], [frequency]) VALUES (N'hit', N'the', N'building', N'with', 0.3)
  INSERT [ngram_4] ([word1], [word2], [word3], [word4], [frequency]) VALUES (N'man', N'hit', N'the', N'building', 0.2)
  INSERT [ngram_4] ([word1], [word2], [word3], [word4], [frequency]) VALUES (N'the', N'building', N'with', N'the', 0.4)
  INSERT [ngram_4] ([word1], [word2], [word3], [word4], [frequency]) VALUES (N'the', N'man', N'hit', N'the', 0.1)

要查询最可能的下一个单词,可以使用类似的查询。

  DECLARE @word1 NVARCHAR(50) = 'the'
  DECLARE @word2 NVARCHAR(50) = 'man'
  DECLARE @word3 NVARCHAR(50) = 'hit'
  DECLARE @bigramWeight FLOAT = 0.2;
  DECLARE @trigramWeight FLOAT = 0.3
  DECLARE @fourgramWeight FLOAT = 0.5

  SELECT next_word, SUM(frequency) AS frequency
  FROM (
    SELECT word2 AS next_word, frequency * @bigramWeight AS frequency
    FROM ngram_2
    WHERE word1 = @word3
    UNION
    SELECT word3 AS next_word, frequency * @trigramWeight AS frequency
    FROM ngram_3
    WHERE word1 = @word2
      AND word2 = @word3
    UNION
    SELECT word4 AS next_word, frequency * @fourgramWeight AS frequency
    FROM ngram_4
    WHERE word1 = @word1
      AND word2 = @word2
      AND word3 = @word3
    ) next_words
  GROUP BY next_word
  ORDER BY SUM(frequency) DESC

如果添加更多的ngram表,则需要在上面的查询中添加另一个UNION子句。您可能会注意到,在第一个查询中,我使用了word1 = @ word3。在第二个查询中,word1 = @ word2和word2 = @ word3。那是因为我们需要查询中的三个单词对齐为ngram数据。如果我们想要三个单词序列中最有可能的下一个单词,则需要将bigram数据中的第一个单词与该序列中单词的最后一个单词进行比较。

您可以根据需要调整权重参数。在此示例中,我假设序数较高的“ n”克将更可靠。

PS我将构造程序代码以通过配置处理任意数量的ngram_N表。创建ngram_5和ngram_6表后,可以声明性地更改程序以使用N-gram范围N(1..6)。


通过此查询,我只能看到您在此处获得的频率得分。我如何选择下一个预测词。与该句子最相关的是?
TomSawyer

好点@TomSawyer。我将示例数据添加到答案中,并给出了一个示例查询,该查询返回了最可能出现的下一个单词。
马修·罗达图斯

谢谢你的更新。但是,我们如何在这里计算频率?即:在中ngram_2,短语的building with频率为0.5。同样的问题@bigramWeight,那是什么?尽管频率是我每次更新数据库时都会更新的字段。即如果用户输入更多的字符串,该字符串的频率将被重新计算?0.5是每个短语的总使用时间或出现率的0.5%?
TomSawyer

bigramWeight和trigramWeight(等)是在整个计算中如何对不同的n-gram加权。这是一种简单的说法,较长的n-gram的熵较高,您可能希望它们比较短的n-gram的“计数”更多。
马修·罗达图斯

在更新数据库方面,显然我没有涵盖所有细节,还有很多改进的余地。例如,您可能不希望将nvarchars存储在ngram表中,而是希望将其令牌化为word表(word_id INT,word NVARCHAR),然后在ngram表中引用word_id。要更新有关再培训的表格,没错-您只需更新频率字段即可。
马修·罗达图斯

3

与其他人的建议相反,我建议避免使用任何比哈希图或键值存储更复杂的数据结构。

请记住您的数据访问要求:a)99%请求-查询ngram“ aaa-bbb-ccc”并取回值(或0)b)1%请求-插入/更新特定ngram的计数c)没有(C)。

最有效的方法是通过一次查找来检索它。您可以使用越界(或转义)的分隔符将完整的n-gram组合为单个字符串(例如,“ 3gram”为“ alpha | beta | gamma”,为unigram为“ alpha”,等等),然后仅获取(的哈希值)。这就是很多NLP软件所做的事情。

如果您的ngram数据很小(例如,<1 gb)并且适合内存,那么我建议使用高效的程序内内存结构(哈希映射,树,尝试等),以避免开销;并只需序列化/反序列化为平面文件。如果您的ngram数据为TB或更多,则可以选择在多个节点上拆分的NoSQL键值存储。

为了获得更好的性能,您可能希望将所有单词都替换为整数id,以便您的核心算法完全看不到任何(慢速)字符串。那么实施相同的想法会稍有不同。


1

不是最高效的,而是简单的并根据需要绑定到数据库:

Table: word
Colums:
word (int, primary key) - a unique identifier for each word
text (varchar) - the actual word

Table: wordpos
Columns:
document (int) - a unique identified for the document of this word
word (int, foreign key to word.word) - the word in this position
pos (int) - the position of this word (e.g., first word is 1, next is 2, ...)

wordpos应该在document和pos上有索引。

二元函数是:

select word1.text as word1, word2.text as word2
from wordpos as pos1, wordpos as pos2, word as word1, word as word2
where pos1.document = pos2.document
      and pos1.pos = pos2.pos - 1
      and word1.word = pos1.word
      and word2.word = pos2.word

然后,您可以count()并对频率和内容进行分组。

要更改为三字母组,很容易生成包含word3的字符串。

实际上,我已经做过此事(即使那里的SQL可能有些生锈)。我确定了一组平面文件,可以轻松地将其搜索到然后从磁盘流式传输。Kinda取决于您的硬件如何做得更好。


1

从本质上说,在尝试改进应用程序对单字组的二元组和三字组的简单搜索时,我看到了您的问题。

如果其中一项要求是能够查询分布式文件系统或数据库,那么这对您来说也可能很有趣:Pibiri和Venturini 2018年论文“有效处理海量N-Gram数据集”概述了一种有效的方法来存储n-gram数据。运行时和空间方面。他们在https://github.com/jermp/tongrams提供了其实现

n个元词的每个“ n”都保存在一个单独的表中,该表由具有快速选择和查询功能的最小完美散列函数访问。这些表是静态的,由主代码使用Google n-gram文本文件格式的输入来构建。

我还没有使用过代码,但是有很多方法可以满足您查询的公开要求。

一种方法:如果将Servlet的.NET等效项与数据库或数据存储区一起使用,并且如果需要节省存储空间,则将每个ngram表以二进制形式存储在数据库/数据存储区中作为表是一种选择(一个数据库/ datastore表,以得到所有1克有效ngram代码的静态文件的结果静态文件,对于所有2克则为另一个静态数据),等等。查询将通过调用有效的n-gram代码(包装为可被servlet访问)来运行。这是使用有效的n-gram代码访问分布式文件系统上的文件的分布式数据库的一种解决方法。请注意,每个二进制数据库/数据存储表都具有基础文件系统的文件大小限制。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.