繁体   English   中英

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

[英]Efficient Context-Free Grammar parser, preferably Python-friendly

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

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

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

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

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

对于能够有效地完成我需要的任何其他解析器,我也没问题。 Python接口是首选,但不是绝对必要的。

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

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

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

除了工具......

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

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

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

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

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

我已经使用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的用途。

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

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

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

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

如果它可以表示为PEG语言(我不认为所有的CFG都可以,但据说很多都可以),那么你可以使用pyPEG ,这在使用packrat解析实现时应该是线性时间(尽管可能过高内存使用情况)。

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM