高效的无上下文语法解析器,最好是Python友好的

时间:2010-12-28 01:06:16

标签: python parsing nlp grammar nltk

我需要为我的一个项目解析一小部分英语,描述为具有(1级)特征结构(example)的无上下文语法,我需要有效地完成它。

现在我正在使用NLTK的解析器,它产生正确的输出,但速度非常慢。对于我的约450个相当模糊的非词典规则和50万个词条的语法,解析简单的句子可能需要2到30秒,这取决于所得到的树的数量。词汇条目对性能几乎没有影响。

另一个问题是在开始时加载(25MB)语法+词典可能需要一分钟。

从文献中我可以发现,用于解析这种语法(Earley或CKY)的算法的运行时间应该与语法的大小呈线性关系,并且应该与输入令牌列表的大小呈线性关系。我对NLTK的经验表明,歧义是最能影响表现的,而不是语法的绝对大小。

所以现在我正在寻找一个CFG解析器来取代NLTK。我一直在考虑PLY,但我不知道它是否支持CFG中的特征结构,这在我的案例中是必需的,我看到的例子似乎是在进行大量的过程解析而不仅仅是指定语法。有人能告诉我一个PLY的例子,它既支持特征结构,又使用声明性语法?

我也可以使用任何其他能够有效完成我需要的解析器。 Python界面是首选,但不是绝对必要。

8 个答案:

答案 0 :(得分:12)

请务必查看Pyparsing。这是我遇到的解析最疯狂的实现,从纯粹的学术角度来看,这是一个很棒的设计。

我使用ANTLRJavaCC来教授当地大学的翻译和编译理论。它们既好又成熟,但我不会在Python项目中使用它们。

也就是说,与编程语言不同,自然语言更多地是关于语义而不是语法,所以你可以更好地跳过现有解析工具的学习曲线,与家庭酿造(自上而下,回溯,无限前瞻)词法分析器和解析器,并花费大量时间编写代码,找出解析但含糊不清的自然语句的含义。

答案 1 :(得分:2)

放弃工具......

你可能从理论上记得,有无限的语法定义了同一种语言。对于给定语言,存在对语法进行分类和确定哪个是“规范”或“最小”语法的标准,但最终,“最佳”语法是对于手头的任务和工具更方便的语法(记住将CFG转换为LL和LR语法?)。

然后,您可能不需要一个巨大的词汇来解析英语句子。关于德语或拉丁语(甚至西班牙语)等语言中的单词有很多值得一提的地方,但在很多时候没有任意的和ambiguos英语。你应该能够使用一个小词典,它只包含到达句子结构所需的关键词。无论如何,您选择的语法,无论其大小,都可以以工具可以直接使用它的方式进行缓存(即,您可以跳过解析语法)。

考虑到这一点,看一看其他人已经使用的更简单的解析器可能是个好主意。文献中必须有成千上万的人。研究不同的方法可以让你评估自己的方法,并可能导致你采用别人的方法。

最后,正如我已经提到的,解释自然语言更多是关于人工智能而不是解析。因为结构决定意义和意义决定结构,你必须同时玩两者。自80年代以来,我在文献中看到的一种方法是让不同的专业代理人针对“blackboard”解决问题。通过这种方法,合成和语义分析同时发生。

答案 2 :(得分:2)

我建议使用bitpar,一种用C ++编写的非常高效的PCFG解析器。我为它编写了一个基于shell的Python包装器,请参阅https://github.com/andreasvc/eodop/blob/master/bitpar.py

答案 3 :(得分:1)

尝试在PyPy上运行它,它可能要快得多。

答案 4 :(得分:1)

我已经使用pyparsing进行有限的词汇表命令解析,但是这里有一个基于pyparsing的小框架来解决你发布的例子:

from pyparsing import *

transVerb, transVerbPlural, transVerbPast, transVerbProg = (Forward() for i in range(4))
intransVerb, intransVerbPlural, intransVerbPast, intransVerbProg = (Forward() for i in range(4))
singNoun,pluralNoun,properNoun = (Forward() for i in range(3))
singArticle,pluralArticle = (Forward() for i in range(2))
verbProg = transVerbProg | intransVerbProg
verbPlural = transVerbPlural | intransVerbPlural

for expr in (transVerb, transVerbPlural, transVerbPast, transVerbProg,
            intransVerb, intransVerbPlural, intransVerbPast, intransVerbProg,
            singNoun, pluralNoun, properNoun, singArticle, pluralArticle):
    expr << MatchFirst([])

def appendExpr(e1, s):
    c1 = s[0]
    e2 = Regex(r"[%s%s]%s\b" % (c1.upper(), c1.lower(), s[1:]))
    e1.expr.exprs.append(e2)

def makeVerb(s, transitive):
    v_pl, v_sg, v_past, v_prog = s.split()
    if transitive:
        appendExpr(transVerb, v_sg)
        appendExpr(transVerbPlural, v_pl)
        appendExpr(transVerbPast, v_past)
        appendExpr(transVerbProg, v_prog)
    else:
        appendExpr(intransVerb, v_sg)
        appendExpr(intransVerbPlural, v_pl)
        appendExpr(intransVerbPast, v_past)
        appendExpr(intransVerbProg, v_prog)

def makeNoun(s, proper=False):
    if proper:
        appendExpr(properNoun, s)
    else:
        n_sg,n_pl = (s.split() + [s+"s"])[:2]
        appendExpr(singNoun, n_sg)
        appendExpr(pluralNoun, n_pl)

def makeArticle(s, plural=False):
    for ss in s.split():
        if not plural:
            appendExpr(singArticle, ss)
        else:
            appendExpr(pluralArticle, ss)

makeVerb("disappear disappears disappeared disappearing", transitive=False)
makeVerb("walk walks walked walking", transitive=False)
makeVerb("see sees saw seeing", transitive=True)
makeVerb("like likes liked liking", transitive=True)

makeNoun("dog")
makeNoun("girl")
makeNoun("car")
makeNoun("child children")
makeNoun("Kim", proper=True)
makeNoun("Jody", proper=True)

makeArticle("a the")
makeArticle("this every")
makeArticle("the these all some several", plural=True)

transObject = (singArticle + singNoun | properNoun | Optional(pluralArticle) + pluralNoun | verbProg | "to" + verbPlural)
sgSentence = (singArticle + singNoun | properNoun) + (intransVerb | intransVerbPast | (transVerb | transVerbPast) + transObject)
plSentence = (Optional(pluralArticle) + pluralNoun) + (intransVerbPlural | intransVerbPast | (transVerbPlural |transVerbPast) + transObject)

sentence = sgSentence | plSentence


def test(s):
    print s
    try:
        print sentence.parseString(s).asList()
    except ParseException, pe:
        print pe

test("Kim likes cars")
test("The girl saw the dog")
test("The dog saw Jody")
test("Kim likes walking")
test("Every girl likes dogs")
test("All dogs like children")
test("Jody likes to walk")
test("Dogs like walking")
test("All dogs like walking")
test("Every child likes Jody")

打印:

Kim likes cars
['Kim', 'likes', 'cars']
The girl saw the dog
['The', 'girl', 'saw', 'the', 'dog']
The dog saw Jody
['The', 'dog', 'saw', 'Jody']
Kim likes walking
['Kim', 'likes', 'walking']
Every girl likes dogs
['Every', 'girl', 'likes', 'dogs']
All dogs like children
['All', 'dogs', 'like', 'children']
Jody likes to walk
['Jody', 'likes', 'to', 'walk']
Dogs like walking
['Dogs', 'like', 'walking']
All dogs like walking
['All', 'dogs', 'like', 'walking']
Every child likes Jody
['Every', 'child', 'likes', 'Jody']

随着您扩展词汇量,这可能会变慢。五十万条记录?我认为合理的功能词汇量大约为5-6千字。你可以处理的句子结构非常有限 - 自然语言是NLTK的用途。

答案 5 :(得分:1)

有点晚了,但这里还有两个选项:

Spark是一个用Python编写的Earley解析器。

Elkhound是用C ++编写的GLR解析器Elkhound使用类似Bison的语法

答案 6 :(得分:0)

我认为ANTLR是我所知道的最适合Java的解析器生成器。我不知道Jython是否会为Python和Java提供一个很好的交互方式。

答案 7 :(得分:0)

如果它可以表示为PEG语言(我不认为所有CFG都可以,但据说很多可以),那么你可以使用pyPEG,这应该是线性时间当使用packrat解析实现时(虽然可能禁止内存使用)。

我没有任何经验,因为我在很长一段时间后才开始研究解析和编译,但我正在阅读一些关于这种相对最新技术的好评。 YMMV。