使用NLTK将Tokenizer组合成语法和解析器

spe*_*ane 25 python grammar nlp nltk

我正在通过NLTK书,我似乎无法做一些似乎是建立一个体面语法的自然的第一步.

我的目标是为特定的文本语料库构建语法.

(初步问题:我是否应该尝试从头开始语法或者我应该从预定义的语法开始?如果我应该从另一个语法开始,这对于英语开始是一个好的?)

假设我有以下简单的语法:

simple_grammar = nltk.parse_cfg("""
S -> NP VP
PP -> P NP
NP -> Det N | Det N PP
VP -> V NP | VP PP
Det -> 'a' | 'A'
N -> 'car' | 'door'
V -> 'has'
P -> 'in' | 'for'
 """);
Run Code Online (Sandbox Code Playgroud)

这个语法可以解析一个非常简单的句子,例如:

parser = nltk.ChartParser(simple_grammar)
trees = parser.nbest_parse("A car has a door")
Run Code Online (Sandbox Code Playgroud)

现在我想扩展这个语法来处理其他名词和动词的句子.如何在不用语法手动定义语法的情况下将这些名词和动词添加到我的语法中?

例如,假设我希望能够解析句子"汽车有轮子".我知道提供的标记器可以神奇地找出哪些单词是动词/名词等等.我怎样才能使用标记器的输出来告诉语法"轮子"是名词?

Sto*_*ken 15

您可以在文本上运行POS标记,然后调整语法以处理POS标记而不是单词.

> text = nltk.word_tokenize("A car has a door")
['A', 'car', 'has', 'a', 'door']

> tagged_text = nltk.pos_tag(text)
[('A', 'DT'), ('car', 'NN'), ('has', 'VBZ'), ('a', 'DT'), ('door', 'NN')]

> pos_tags = [pos for (token,pos) in nltk.pos_tag(text)]
['DT', 'NN', 'VBZ', 'DT', 'NN']

> simple_grammar = nltk.parse_cfg("""
  S -> NP VP
  PP -> P NP
  NP -> Det N | Det N PP
  VP -> V NP | VP PP
  Det -> 'DT'
  N -> 'NN'
  V -> 'VBZ'
  P -> 'PP'
  """)

> parser = nltk.ChartParser(simple_grammar)
> tree = parser.parse(pos_tags)
Run Code Online (Sandbox Code Playgroud)

  • 这非常好.只需添加句子的第i个单词作为树的第i个叶子(POS标签)的子项. (4认同)
  • 有意思......这真的是"正确"的方式吗?有没有办法做到这一点,但保持单词和他们的词性相互关联? (2认同)
  • 我之前听说过这个,除非你的语法产品包含终端和非终结符(例如,`VP - > V Det'wattle'`),这似乎是合适的. (2认同)
  • 嗯......我觉得我们在这里仍然缺少一些东西.解析句子的目的是将单词放入解析树中.如果我们将所有单词转换为他们的POS,那么我们将无法做到这一点.我们将获得一张POS树. (2认同)
  • @speedplane我不确定你想用解析做什么.trees.leaves()将恢复传递给解析器的列表,如果需要,可以使用该列表索引到句子中. (2认同)

小智 11

解析是一个棘手的问题,很多事情都可能出错!

你想要(至少)三个组件,一个标记器,一个标记器,最后是解析器.

首先,您需要将正在运行的文本标记为令牌列表.这可以像在空格中分割输入字符串一样简单,但是如果要解析更多的常规文本,则还需要处理数字和标点符号,这是非常重要的.例如,句子结束时期通常不被视为它附加的单词的一部分,但标记缩写的句点通常是.

当您有输入令牌列表时,您可以使用标记器尝试确定每个单词的POS,并使用它来消除输入标记序列的歧义.这有两个主要优点:首先它加速解析,因为我们不再需要考虑由模糊词授权的备用假设,因为POS标记器已经这样做了.其次,它改善了未知的文字处理,即.不在你的语法中的单词,也可以为这些单词分配一个标签(希望是正确的单词).以这种方式组合解析器和标记器是很常见的.

然后,POS标签将构成语法中的预终端.预终端是生产的左侧,只有终端作为其右侧.即N - >"house",V - >"jump"等.N和V是preterminals.具有语法的语法,双方的非终端,制作和词汇制作,一个非终端进入一个终端是相当普遍的.这在大多数时候都具有语言意义,大多数CFG解析器都要求语法采用这种形式.然而,通过在其中具有非终端的RHS中的任何终端创建"虚拟产品",可以以这种方式表示任何CFG.

如果您希望在语法中比标记器输出更多(或更少)细粒度标记区分,则可能需要在POS标签和预终端之间进行某种映射.然后,您可以使用标记器的结果初始化图表,即.跨越每个输入令牌的适当类别的被动项.可悲的是,我不知道NTLK,但我确信有一个简单的方法可以做到这一点.当播种图表时,解析可以正常进行,并且可以以常规方式提取任何解析树(也包括单词).

但是,在大多数实际应用程序中,您会发现解析器可以返回几种不同的分析,因为自然语言非常模糊.我不知道你试图解析什么样的文本语料库,但如果它像自然语言一样你可能需要构建某种解析选择模型,这将需要一个树库,一组解析树一些大小从几百到几千个解析,都取决于你的语法和你需要的准确结果.给定这个树库,可以自动推断出与其对应的PCFG.然后可以将PCFG用作对解析树进行排名的简单模型.

所有这些都是你自己要做的很多工作.你用什么解析结果?您是否查看过NTLK或其他软件包中的其他资源,例如StanfordParser或BerkeleyParser?


小智 11

我知道这是一年后但我想补充一些想法.

我采用了很多不同的句子,并用我正在研究的项目的词性标记它们.从那里我正在按照StompChicken的建议,从元组(word,tag)中拉出标签,并使用这些标签作为"终端"(树的底部节点,因为我们创建了一个完全标记的句子).

最终,这并不符合我在名词短语中标记头部名词的愿望,因为我不能将头部名词"单词"拉入语法,因为语法只有标签.

所以我所做的是使用(word,tag)元组的集合来创建标签字典,所有带有该标签的单词作为该标签的值.然后我将这个字典打印到screen/grammar.cfg(context free grammar)文件中.

我用来打印它的表单非常适合通过加载语法文件来设置解析器(parser = nltk.load_parser('grammar.cfg').它生成的一行如下所示:VBG - >"fencing"| "bonging"|"相当"|"生活"...超过30多个单词......

所以现在我的语法将实际单词作为终端并分配nltk.tag_pos所做的相同标签.

希望这有助于其他任何人想要自动标记大型语料库,并且仍然将实际单词作为语法中的终端.

import nltk
from collections import defaultdict

tag_dict = defaultdict(list)

...
    """ (Looping through sentences) """

        # Tag
        tagged_sent = nltk.pos_tag(tokens)

        # Put tags and words into the dictionary
        for word, tag in tagged_sent:
            if tag not in tag_dict:
                tag_dict[tag].append(word)
            elif word not in tag_dict.get(tag):
                tag_dict[tag].append(word)

# Printing to screen
for tag, words in tag_dict.items():
    print tag, "->",
    first_word = True
    for word in words:
        if first_word:
            print "\"" + word + "\"",
            first_word = False
        else:
            print "| \"" + word + "\"",
    print ''
Run Code Online (Sandbox Code Playgroud)