Эффективный контекстно-свободный анализатор грамматики, предпочтительно дружественный к Python

Мне нужно проанализировать небольшое подмножество английского языка для одного из моих проектов, описанное как контекстно-свободная грамматика с (1-уровневыми) структурами функций (example), и мне нужно сделать это эффективно.

Прямо сейчас я использую анализатор NLTK, который выдает правильный результат, но работает очень медленно. Для моей грамматики из примерно 450 довольно неоднозначных правил, не относящихся к словарю, и полумиллиона лексических статей разбор простых предложений может занять от 2 до 30 секунд, в зависимости от количества результирующих деревьев. Лексические записи практически не влияют на производительность.

Другая проблема заключается в том, что загрузка (25 МБ) грамматики+лексики в начале может занять до минуты.

Из того, что я могу найти в литературе, время работы алгоритма, используемого для анализа такой грамматики (Earley или CKY), должно быть линейным по размеру грамматики и кубическим по размеру списка входных токенов. Мой опыт работы с NLTK показывает, что неоднозначность — это то, что больше всего вредит производительности, а не абсолютный размер грамматики.

Так что теперь ищу парсер CFG на замену NLTK. Я думал о PLY, но не могу сказать, поддерживает ли он структуры функций в CFG, которые требуются в моем случае. и примеры, которые я видел, похоже, выполняют много процедурного разбора, а не просто определяют грамматику. Может ли кто-нибудь показать мне пример PLY, поддерживающий структуры функций и использующий декларативную грамматику?

Меня также устраивает любой другой парсер, который может эффективно делать то, что мне нужно. Интерфейс Python предпочтителен, но не обязателен.


person Max Shawabkeh    schedule 28.12.2010    source источник
comment
Я просто ищу то же самое, но только для Java. Вот мой связанный с этим вопрос: stackoverflow.com/ вопросы/4584684/ . Так какой метод вы выбрали? Апалала говорит, что мы можем исправить ANTLR, чтобы справляться с двусмысленностью. Однако я не понял метода, который он предлагает.   -  person hrzafer    schedule 04.01.2011
comment
До сих пор я все еще использую NLTK, так как ни одна из библиотек или методов в ответах не сработала для меня. Однако это отчасти потому, что мне нужна унифицирующая грамматика (CFG + структуры функций), и, по словам разработчика NLTK, отвечающего за синтаксический анализатор, мои проблемы с эффективностью проистекают из этого.   -  person Max Shawabkeh    schedule 04.01.2011


Ответы (8)


Обязательно взгляните на Pyparsing. Это самая питоническая реализация синтаксического анализа, с которой я сталкивался, и это отличный дизайн с чисто академической точки зрения.

Я использовал как ANTLR, так и JavaCC для преподавания теории транслятора и компилятора в местном университете. Они оба хороши и зрелы, но я бы не стал использовать их в проекте Python.

Тем не менее, в отличие от языков программирования, естественные языки гораздо больше связаны с семантикой, чем с синтаксисом, поэтому вам было бы гораздо лучше пропустить кривые обучения существующим инструментам синтаксического анализа, используя самодельные (сверху вниз, откат, неограниченный) вперед) лексический анализатор и синтаксический анализатор, и тратить большую часть своего времени на написание кода, который выясняет, что означает проанализированное, но неоднозначное предложение на естественном языке.

person Apalala    schedule 28.12.2010
comment
IMX pyparsing добавляет довольно много ненужных накладных расходов. Он также не построен с учетом идеи отдельных шагов лексического анализа и синтаксического анализа. - person Karl Knechtel; 28.12.2010
comment
Разделение токенизатора и синтаксического анализатора является произвольным наследием таких инструментов, как LEX/YACC, и тех времен, когда вычислительная мощность была такова, что даже думать о однопроходном компиляторе было безумием. Если язык грамматики достаточно мощный, чтобы определить целевой язык на всех уровнях, тогда разделение не требуется. Разработчики уже давно используют EBNF для определения всех частей языка программирования, и хотя ANTLR обеспечивает разделение, он использует контекстно-свободные грамматики (а не регулярные выражения) для лексического анализа. Как и Pyparsing. - person Apalala; 28.12.2010
comment
Как и ANTLR, Pyparsing, похоже, не (1) обрабатывает CFG и (2) правильно не генерирует все деревья для неоднозначного синтаксического анализа. Оба они необходимы в моем случае. Что касается вашей заметки о естественных языках, вы правы. Основная часть проекта — это семантический анализ, и я уже делаю его со своим собственным кодом. Однако мне нужно получить деревья синтаксического анализа, прежде чем я смогу их устранить. Написание собственного — это вариант, но в целом я бы не хотел изобретать велосипед. - person Max Shawabkeh; 28.12.2010
comment
@Max Shawabkeh Pyparsing или нет, есть старый трюк из моих дней Prolog, который вы можете использовать, чтобы получить все деревья синтаксического анализа с помощью любого инструмента синтаксического анализа сверху вниз. Если ваша грамматика G-› ‹alpha›, измените ее на: G' -> G {semantic-clone-parse-tree} ‹TOKEN-NOT-IN-LANGUAGE› То есть клонируйте полное дерево разбора прямо перед форсированием синтаксический анализатор, чтобы не выполнить синтаксический анализ. Клонирование вместо сохранения необходимо, потому что синтаксический анализатор изменит дерево, пока ищет другой синтаксический анализ. - person Apalala; 03.01.2011

Я использовал 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.

person PaulMcG    schedule 28.12.2010
comment
Спасибо за такой большой пример, но здесь используется процедурный подход, который довольно громоздкий, когда у вас такое большое количество правил. Я бы предпочел использовать парсер, который понимает EBNF. Что касается словарного запаса, вы правы, 50 тысяч слов должно хватить. Однако добавление элементов лексикона на самом деле не влияет на производительность — при использовании терминалов парсера «снизу вверх» требуется один поиск по словарю, и даже если мы говорим, что это O (log n), что не так, увеличение словаря на порядок незначительно. . Проблема заключается в неоднозначных нетерминальных правилах. - person Max Shawabkeh; 28.12.2010

Инструменты в сторону...

Возможно, вы помните из теории, что существует бесконечное количество грамматик, определяющих один и тот же язык. Существуют критерии для категоризации грамматик и определения того, какая из них является «канонической» или «минимальной» для данного языка, но, в конце концов, «лучшая» грамматика — это та, которая более удобна для задачи и имеющихся инструментов (помните преобразования CFG в LL и LR грамматики?).

Тогда вам, вероятно, не понадобится огромный словарный запас, чтобы разобрать предложение на английском языке. О слове в таких языках, как немецкий или латинский (или даже испанский), можно многое узнать, но не во многих случаях произвольном и двусмысленном английском. Вам может сойти с рук небольшой лексикон, который содержит только ключевые слова, необходимые для построения структуры предложения. В любом случае, выбранная вами грамматика, независимо от ее размера, может быть кэширована таким образом, что ваш инструментарий сможет использовать ее напрямую (т. е. вы можете пропустить синтаксический анализ грамматики).

Учитывая это, было бы неплохо взглянуть на более простой синтаксический анализатор, над которым уже работал кто-то другой. Таких в литературе должны быть тысячи. Изучение различных подходов позволит вам оценить свои собственные и может привести к принятию чужих.

Наконец, как я уже упоминал, интерпретация естественных языков гораздо больше связана с искусственным интеллектом, чем синтаксическим анализом. Поскольку структура определяет смысл, а смысл определяет структуру, вы должны играть с ними одновременно. Подход, который я видел в литературе с 80-х годов, заключается в том, чтобы позволить различным специализированным агентам попытаться решить проблему с помощью «доска". При таком подходе синтаксический и семантический анализ происходят одновременно.

person Apalala    schedule 28.12.2010
comment
Как я уже упоминал в комментарии к сообщению Пола, лексикон не является проблемой в отношении производительности. Кэширование грамматики очевидно, и я уже этим занимаюсь, поэтому время загрузки — второстепенная проблема. Однако следует отметить, что анализатор NLTK, собранный на диск и перезагруженный, загружается медленнее, чем создание нового из грамматики. Что касается использования существующего синтаксического анализатора, проблема с подходом семантического анализа, который я использую (разновидность грамматики Монтегю/DRT), заключается в том, что он требует от меня написания шага семантической обработки для каждого синтаксического правила... - person Max Shawabkeh; 28.12.2010
comment
...и правила должны быть разработаны определенным образом. Большинство грамматик с широким охватом, которые я видел, подходят к проблеме, собирая огромное количество длинных правил, эмпирически извлеченных из банков деревьев, и присваивая им вероятности. Этот подход подходит для некоторых применений, но не в моем случае. - person Max Shawabkeh; 28.12.2010
comment
Я предполагаю, что моя точка зрения заключается в том, что для естественных языков недостаточно информации для проведения семантического анализа во время синтаксического анализа, и недостаточно семантической информации для точного понимания синтаксической структуры с помощью типичного синтаксического анализатора. Мэри прочитала, что Джейн хорошо читала. То, что читала Джейн, читала и Мэри. - person Apalala; 28.12.2010
comment
Вот где возникает двусмысленность. Я могу синтаксически разобрать предложение, почти не зная семантики (за исключением битов, закодированных в лексиконе), и когда у меня есть все возможные деревья синтаксического анализа, я выполняю шаг семантического анализа и нахожу, какие деревья приемлемы. - person Max Shawabkeh; 28.12.2010
comment
@ Макс Шавабке. Ваш подход верный, но это брутфорс, так что проблем с ресурсами (память/время) следует ожидать (мне напомнили анимации алгоритмов сортировки). Чтобы решить проблемы с ресурсами, вы могли бы, по крайней мере, начать отбрасывать деревья, как только вы их найдете с помощью параллельного семантического анализатора. Теоретическое время будет таким же, но время компьютера может резко сократиться только потому, что вы будете использовать меньше памяти. Если синтаксический анализатор строит промежуточные деревья, было бы неплохо включить теорию игр (en.wikipedia.org/wiki /A_star). - person Apalala; 29.12.2010
comment
Мой подход к устранению неоднозначности на самом деле не работает без полного дерева для анализа. На самом деле, любой инструмент устранения неоднозначности, который анализирует неполное дерево, рискует отбросить действительные просто потому, что любое поддерево, которое может быть семантически непоследовательным, может привести к совершенно правильному дереву, когда, например. поставить внутри условное или отрицание. В любом случае (1) проблемы со скоростью возникают, даже когда я получаю ‹10 деревьев, поэтому отбрасывание любого из них не поможет, и (2) используемые алгоритмы синтаксического анализа CKY/Earley НЕ являются поиском методом грубой силы и не растут экспоненциально. Время выполнения CKY составляет O(g*n^3), где g — размер грамматики, а n — размер входных данных. - person Max Shawabkeh; 29.12.2010

Я бы рекомендовал использовать bitpar, очень эффективный анализатор PCFG, написанный на C++. Я написал для него оболочку Python на основе оболочки, см. https://github.com/andreasvc/eodop/blob/master/bitpar.py

person Andreas    schedule 21.03.2011

Я думаю, что ANTLR — лучший из известных мне генераторов синтаксических анализаторов для Java. Я не знаю, предоставит ли Jython хороший способ взаимодействия Python и Java.

person duffymo    schedule 28.12.2010
comment
ANTLR фактически поддерживает Python среди нескольких других языков: antlr.org/wiki /display/ANTLR3/Code+Generation+Targets - person Fabian Steeg; 28.12.2010
comment
Не знал этого; Я использовал его только для Java. Спасибо за обновления. - person duffymo; 28.12.2010
comment
Спасибо за рекомендацию. Однако из того, что я вижу, ANTLR (и все его родственники, Lex/Yacc, Bison и т. д.) в основном написаны для разбора детерминированных языков программирования, а не неоднозначных естественных языков. В моем случае предложение средней длины может привести к 20 деревьям синтаксического анализа, и мне нужно сгенерировать их все. Примечание: отрицательный голос не мой. - person Max Shawabkeh; 28.12.2010
comment
Синтаксический анализ естественного языка является сложной задачей. Я не знаю, что это решено. Вы сказали без контекста - я не думаю, что это верно для естественных языков, таких как английский. Контекст имеет значение. Если я правильно понимаю термины, естественный язык и контекстно-свободный язык противоречат друг другу. Признаюсь, я не эксперт. - person duffymo; 28.12.2010
comment
Да, большинство естественных языков (включая английский) не являются контекстно-свободными, однако большое и очень полезное их подмножество может быть выражено в достаточно сложной неоднозначной CFG, которая затем может быть устранена семантически. - person Max Shawabkeh; 28.12.2010
comment
Точно. Естественные языки не являются контекстно-свободными. - person Apalala; 28.12.2010
comment
@Max - семантическая неоднозначность может быть не так проста. Единственная известная мне попытка сделать это (Cyc Дуга Лената cyc.com/cyc/opencyc) — это огромный проект, которому уже более десяти лет. - person duffymo; 28.12.2010
comment
Cyc — крупнейший такой проект, но WordNet также представляет собой довольно зрелую попытку создания семантической иерархии. В любом случае, часть устранения неоднозначности и симантического анализа является основной частью моего проекта, так что это не та проблема, которую я пытаюсь решить с помощью этого конкретного вопроса. - person Max Shawabkeh; 28.12.2010

Несколько поздно, но вот еще два варианта для вас:

Spark — это анализатор Earley, написанный на Python.

Elkhound — парсер GLR, написанный на C++. Elkhound использует синтаксис, подобный Bison.

person Jerry    schedule 02.01.2011

Если это можно выразить как язык PEG (я не думаю, все CFG могут, но предположительно могут многие), тогда вы можете использовать pyPEG, который должен быть линейным при использовании реализации синтаксического анализа packrat (хотя это потенциально запрещает использование памяти).

У меня нет никакого опыта с этим, так как я только начинаю снова исследовать синтаксический анализ и компиляцию после долгого перерыва в этом, но я читаю хорошие слухи об этой относительно современной технике. YMMV.

person Binary Phile    schedule 28.12.2010
comment
Из того, что я могу сказать, этот подход — это способ разрешения неоднозначностей внутри грамматики EBNFish посредством расстановки приоритетов и тому подобного, что, безусловно, полезно, но контрпродуктивно в моем случае, поскольку я действительно хочу, чтобы двусмысленность оставалась. - person Max Shawabkeh; 28.12.2010
comment
Да, он говорит, что его нельзя использовать для языков, которые зависят от двусмысленности. - person Binary Phile; 29.12.2010

Попробуйте запустить его на PyPy, это может быть намного быстрее.

person TryPyPy    schedule 28.12.2010
comment
Парсер, который я использую, похоже, много использует самоанализ и слишком много жонглирует объектами, чтобы получить значительное улучшение производительности от PyPy, и его правильное портирование, вероятно, так же сложно, как написать его с нуля. - person Max Shawabkeh; 28.12.2010
comment
Портирование? Просто скачайте PyPy с pypy.org/download.html и вместо времени python yourscript.py data.txt введите time pypy yourscript.py data.txt... - person TryPyPy; 28.12.2010
comment
Кроме того, если бы вы могли предоставить еще пару подробностей (какой синтаксический анализатор, как/на каких данных использовать пример грамматики для запуска соответствующего теста и т. д.) о вашей настройке, мы могли бы попробовать другие способы повысить скорость (например, Cythonize или использовать Сбрасывать кожу на горячих точках). - person TryPyPy; 28.12.2010