簡體   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