Подтвердить что ты не робот

Объединение Tokenizer в грамматику и парсер с NLTK

Я прохожу через книгу 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")

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

Например, предположим, что я хочу иметь возможность разобрать предложение "У автомобиля есть колеса". Я знаю, что поставляемые токенизаторы могут волшебным образом определить, какие слова являются глаголами/существительными и т.д. Как я могу использовать вывод токенизатора, чтобы сообщить грамматике, что "колеса" являются существительным?

4b9b3361

Ответ 1

Вы можете запустить тег 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)

Ответ 2

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

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

В конечном итоге это не соответствует моему желанию отмечать головные существительные в словах-существительных, так как я не могу вытащить слово "слово" в грамматику, поскольку в грамматике есть только теги.

Итак, вместо этого я использовал набор (word, tag) кортежей для создания словаря тегов, со всеми словами с этим тегом в качестве значений для этого тега. Затем я печатаю этот словарь в файле screen/grammar.cfg(контекстная свободная грамматика).

Форма, которую я использую для ее печати, отлично работает с настройкой парсера путем загрузки файла грамматики (parser = nltk.load_parser ('grammar.cfg'). Одна из строк, которые он создает, выглядит следующим образом: VBG → "фехтование" | "bonging" | "сумма" | "живое"... более 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 ''

Ответ 3

Анализ - сложная проблема, многие вещи могут пойти не так!

Вы хотите (по крайней мере) три компонента здесь, токенизатор, теггер и, наконец, парсер.

Сначала вам нужно сделать токенизуемый текст в списке токенов. Это может быть так же просто, как разделение входной строки вокруг пробелов, но если вы разбираете более общий текст, вам также нужно будет обрабатывать числа и знаки препинания, что нетривиально. Например, периоды окончания предложения часто не считаются частью слова, к которому оно привязано, но периоды, обозначающие аббревиатуру, часто бывают.

Когда у вас есть список входных токенов, вы можете использовать теггер, чтобы попытаться определить POS каждого слова и использовать его для устранения неоднозначности входных тегов. Это имеет два основных преимущества: во-первых, это ускоряет синтаксический анализ, поскольку нам больше не нужно рассматривать альтернативную гипотезу, лицензируемую двусмысленными словами, поскольку POS-tagger уже сделал это. Во-вторых, он улучшает работу с неизвестными словами, т.е. слова не в вашей грамматике, также присваивая этим словам тег (надеюсь, правильный). Таким образом, объединение анализатора и таргетинга является обычным явлением.

POS-теги будут составлять предварительные терминалы в вашей грамматике. Предварительные терминалы - это левые части производств с только терминалами в качестве их правой стороны. Т.е. в N → "дом", V → "прыжок" и т.д. N и V являются предтерминалами. Достаточно распространено иметь грамматику с синтаксическими, только не-терминалами с обеих сторон, производством и лексическими произведениями, один не терминал, идущий на один терминал. Это делает лингвистический смысл большую часть времени, и большинство CFG-парсеров требуют, чтобы грамматика находилась в этой форме. Однако любой CFG мог бы представлять таким образом, создавая "фиктивные производные" из любых терминалов в RHSes с не-терминалами в них.

Возможно, необходимо иметь какое-то сопоставление между POS-тегами и пре-терминалами, если вы хотите сделать больше (или меньше) мелкозернистых тегов в своей грамматике, чем то, что выдает ваш теггер. Затем вы можете инициализировать диаграмму с результатами тегатора, т.е. пассивные элементы соответствующей категории, охватывающие каждый входной токен. К сожалению, я не знаю NTLK, но я уверен, что есть простой способ сделать это. Когда график посеян, синтаксический анализ может отображаться как обычный, и любые деревья синтаксического анализа могут быть извлечены (также включая слова) обычным способом.

Однако в большинстве практических приложений вы обнаружите, что анализатор может возвращать несколько разных анализов, поскольку естественный язык очень неоднозначен. Я не знаю, какой текстовый корпус вы пытаетесь разобрать, но если это что-то похожее на естественный язык, вам, вероятно, придется построить какую-то модель синтаксического анализа, для этого потребуется древовидный банк, сборник синтаксических диаграмм от нескольких сотен до нескольких тысяч парсов, все зависит от вашей грамматики и от точных результатов. С учетом этого дерева можно автоматически выводить соответствующий ему PCFG. Затем PCFG можно использовать в качестве простой модели для ранжирования деревьев разбора.

Все это - большая работа для себя. Для чего вы используете результаты анализа? Просмотрели ли вы другие ресурсы в NTLK или других пакетах, таких как StanfordParser или BerkeleyParser?