用NLTK将标记器组合成语法和解析器

2024-04-28 05:02:42 发布

您现在位置:Python中文网/ 问答频道 /正文

我正在阅读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'
 """);

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

parser = nltk.ChartParser(simple_grammar)
trees = parser.nbest_parse("A car has a door")

现在我想把这个语法扩展到处理其他名词和动词的句子。如何将这些名词和动词添加到语法中而不在语法中手动定义它们?

例如,假设我想能够解析“汽车有轮子”这句话。我知道提供的标记器可以神奇地找出哪些单词是动词/名词等。我如何使用标记器的输出告诉语法“wheels”是名词?


Tags: parsenp语法动词simplecarpp句子
3条回答

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

您需要(至少)三个组件,一个标记器,一个标记器,最后是解析器。

首先,需要将正在运行的文本标记为标记列表。这可以像在空白处拆分输入字符串一样简单,但是如果要解析更一般的文本,还需要处理数字和标点符号,这是非常重要的。例如,句子结尾的句号通常不被视为其所附单词的一部分,但标记缩写的句号通常被视为。

当您有一个输入标记列表时,可以使用标记器来确定每个单词的词性,并使用它来消除输入标记序列的歧义。这有两个主要的优点:第一,它加快了解析速度,因为我们不再需要考虑由歧义词授权的替代假设,因为POS标记已经这样做了。其次,它还通过给那些单词分配一个标记(希望是正确的标记)来改进未知单词的处理,即语法中没有的单词。以这种方式组合解析器和标记器是很常见的。

POS标签将构成语法中的前置终端,前置终端是产品的左侧,只有终端作为其右侧。例如在N->;“house”,V->;“jump”等中,N和V是未定词。语法中的句法成分比较常见,只有两边的非宾语成分,产生成分和词法成分,一个非宾语成分到一个宾语成分。这在大多数情况下都是有语言意义的,大多数CFG解析器都要求语法采用这种形式。然而,可以用这种方式表示任何CFG,方法是从rhse中包含非终端的任何终端创建“虚拟产品”。

如果您想在语法中进行更多(或更少)的细粒度标记区分,而不是标记器输出的内容,那么可能需要在POS标记和pre-terminals之间进行某种映射。然后,您可以使用标记器的结果初始化图表,即跨越每个输入标记的适当类别的被动项。很遗憾我不认识NTLK,但我相信有一个简单的方法可以做到这一点。当图表被播种时,解析可以正常进行,并且可以以常规方式提取任何解析树(包括单词)。

然而,在大多数实际应用中,您会发现解析器可以返回几个不同的分析,因为自然语言是高度模糊的。我不知道你想解析什么样的文本语料库,但是如果它像自然语言一样,你可能需要构造某种解析选择模型,这需要一个树库,一个大小从几百到几千的解析树集合,一切都取决于你的语法和你所需要的准确结果。给定这个树库,可以自动推断出与之对应的PCFG。然后,PCFG可以用作排列解析树的简单模型。

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

您可以在文本上运行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)

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

我在一个我正在做的项目中,用很多不同的句子和词类来标记它们。从那里开始,我按照StompChicken的建议做,从元组(word,tag)中提取标记,并将这些标记用作“终端”(创建完全标记的句子时树的底部节点)。

最终,这并不能满足我在名词短语中标记头名词的愿望,因为我无法将头名词“word”拉入语法,因为语法只有标记。

因此,我所做的是使用一组(word,tag)元组来创建一个标记字典,其中包含该标记的所有单词都作为该标记的值。然后我将这个字典打印到screen/grammar.cfg(上下文无关语法)文件中。

我用来打印它的表单与通过加载语法文件(parser=nltk.load_parser('grammar.cfg')来设置解析器完美地结合在一起。它生成的一行代码如下所示:VBG->;“fercing”|“bonging”|“accountant”|“living”。。。超过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 ''

相关问题 更多 >