Дерево вероятностей для предложений в nltk, использующих как упреждающие, так и ретроспективные зависимости

Позволяет ли nltk или любой другой инструмент NLP строить деревья вероятностей на основе входных предложений, сохраняя таким образом языковую модель входного текста в дереве словаря, следующее пример дает приблизительное представление, но мне нужна та же функциональность, чтобы слово Wt не только вероятностно моделировалось на прошлых входных словах (история) Wt-n, но и на опережающих словах, таких как Wt+m. Кроме того, количество слов ретроспективного и упреждающего просмотра также должно быть 2 или более, то есть биграмм или более. Существуют ли какие-либо другие библиотеки в python, которые достигают этого?

from collections import defaultdict
import nltk
import math

ngram = defaultdict(lambda: defaultdict(int))
corpus = "The cat is cute. He jumps and he is happy."
for sentence in nltk.sent_tokenize(corpus):
    tokens = map(str.lower, nltk.word_tokenize(sentence))
    for token, next_token in zip(tokens, tokens[1:]):
        ngram[token][next_token] += 1
for token in ngram:
    total = math.log10(sum(ngram[token].values()))
    ngram[token] = {nxt: math.log10(v) - total for nxt, v in ngram[token].items()}

решение требует как просмотра вперед, так и просмотра назад, и словарь со специальными подклассами может помочь в решении этой проблемы. Можно также указать на соответствующие ресурсы, в которых говорится о внедрении такой системы. Похоже, что nltk.models делает что-то подобное, но больше не доступен. Существуют ли какие-либо шаблоны проектирования в НЛП, реализующие эту идею? Модели, основанные на пропуске граммов, также похожи на эту идею, но я чувствую, что это уже должно было быть где-то реализовано.


person stackit    schedule 13.08.2015    source источник
comment
Вы можете выполнить итерацию таким образом, чтобы получить нужный фрагмент: for t, token in enumerate(tokens): do_something(tokens[t-n:t+m])   -  person clemtoy    schedule 17.08.2015
comment
@clemtoy да, я могу это сделать, но как смоделировать это, чтобы построить дерево?   -  person stackit    schedule 17.08.2015
comment
Не могли бы вы уточнить, что вы подразумеваете под деревом вероятностей?   -  person CentAu    schedule 17.08.2015
comment
подобно вероятностям языковой модели, как в приведенном выше примере, для набора последовательных слов и его левого и правого контекста, какова вероятность того, что это правильная последовательность слов? @CentAu   -  person stackit    schedule 17.08.2015


Ответы (2)


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

Расширение вашего кода:

from collections import defaultdict
import nltk
from nltk.tokenize import word_tokenize
import numpy as np


ngram = defaultdict(lambda: defaultdict(int))
ngram_rev = defaultdict(lambda: defaultdict(int)) #reversed n-grams
corpus = "The cat is cute. He jumps and he is happy."

for sentence in nltk.sent_tokenize(corpus):
    tokens = map(str.lower, nltk.word_tokenize(sentence))
    for token, next_token in zip(tokens, tokens[1:]):
        ngram[token][next_token] += 1
    for token, rev_token in zip(tokens[1:], tokens):
        ngram_rev[token][rev_token] += 1
for token in ngram:
    total = np.log(np.sum(ngram[token].values()))
    total_rev = np.log(np.sum(ngram_rev[token].values()))
    ngram[token] = {nxt: np.log(v) - total 
                    for nxt, v in ngram[token].items()}
    ngram_rev[token] = {prv: np.log(v) - total_rev 
                    for prv, v in ngram_rev[token].items()}

Теперь контекст находится как в ngram, так и в ngram_rev, которые соответственно содержат прямой и обратный контексты.

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

person CentAu    schedule 17.08.2015
comment
На самом деле контекст должен состоять как минимум из двух слов с каждой стороны, при этом возможен только поиск униграмм - person stackit; 18.08.2015
comment
Вы специально предоставили код для биграмм, и поэтому я также расширил его на основе биграмм. Вы можете легко обобщить его на ngram, например: for token, next_token, next_token_2 in zip (tokens, tokens[1:], tokens[2:]): ngram[token]['%s-%s' % (next_token, next_token_2)] += 1 - person CentAu; 19.08.2015
comment
он использует последние два слова в качестве постоянного суффикса, тем самым ограничивая количество допустимых ngram - person stackit; 21.08.2015
comment
Вы можете прокомментировать свой ответ? - person stackit; 24.08.2015
comment
Вы можете изменить его, чтобы учитывать последние два слова отдельно, например. for token, next_token, next_token_2 in zip (tokens, tokens[1:], tokens[2:]): ngram[token][next_token] += 1; ngram[token]['%s-%s' % (next_token, next_token_2)] += 1 Таким образом, вы в любом случае добавляете следующий непосредственный контекст и предотвращаете упомянутую вами проблему. - person CentAu; 24.08.2015
comment
почему бы не использовать вложенный словарь? - person stackit; 28.08.2015
comment
@stackit, здесь я использовал вложенный словарь (ngram вложен). - person CentAu; 31.08.2015

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

Теггеры нграмм NLTK делают нграммы тегов слева; но вы можете легко создать свой собственный тегировщик из их абстрактного базового класса ContextTagger:

import nltk
from nltk.tag import ContextTagger

class TwoSidedTagger(ContextTagger):
        left = 2
        right = 1

    def context(self, tokens, index, history):
        left = self.left
        right = self.right
        tokens = tuple(t.lower() for t in tokens)
        if index < left:
            tokens = ("<start>",) * left + tokens
            index += left
        if index + right >= len(tokens):
            tokens = tokens + ("<end>",) * right

        return tokens[index-left:index+right+1]

Это определяет теггер тетраграммы (2+1+1), где текущее слово является третьим в энграмме, а не последним, как обычно. Затем вы можете инициализировать и обучить тегировщик так же, как обычные тегировщики ngram (см. главу 5 книги NLTK, особенно разделы 5.4ff). Давайте сначала посмотрим, как вы создадите тегировщик частей речи, используя часть корпуса Брауна в качестве обучающих данных:

data = list(nltk.corpus.brown.tagged_sents(categories="news"))
train_sents = data[400:]
test_sents = data[:400]
twosidedtagger = TwoSidedTagger({}, backoff=nltk.DefaultTagger('NN'))
twosidedtagger._train(train_sents)

Как и все тегировщики ngram в NLTK, этот будет делегировать полномочия тегировщику отсрочки, если его попросят пометить ngram, который он не видел во время обучения. Для простоты я использовал простой тегировщик по умолчанию в качестве резервного тегера, но вам, вероятно, понадобится что-то более мощное (см. главу NLTK еще раз).

Затем вы можете использовать свой тегировщик, чтобы пометить новый текст или оценить его с уже помеченным набором тестов:

>>> print(twosidedtagger.tag("There were dogs everywhere .".split()))
>>> print(twosidedtagger.evaluate(test_sents))

Предсказывающие слова:

Вышеупомянутый тегировщик присваивает тег POS, учитывая близлежащие слова; но ваша цель — предсказать само слово, поэтому вам нужны другие обучающие данные и другой теггер по умолчанию. API NLTK ожидает обучающие данные в форме (word, LABEL), где LABEL — это значение, которое вы хотите сгенерировать. В вашем случае LABEL - это просто само текущее слово; поэтому сделайте свои тренировочные данные следующим образом:

data = [ zip(s,s) for s in nltk.corpus.brown.sents(categories="news") ]
train_sents = data[400:]
test_sents = data[:400]
twosidedtagger = TwoSidedTagger({}, backoff=nltk.DefaultTagger('the')) # most common word
twosidedtagger._train(train_sents)

Целевое слово не имеет смысла появляться в «контекстной» нграмме, поэтому вам также следует изменить метод context(), чтобы возвращаемая нграмма не включала его:

    def context(self, tokens, index, history):
        ...
        return tokens[index-left:index] + tokens[index+1:index+right+1]

Этот теггер использует триграммы, состоящие из двух слов слева и одного справа от текущего слова.

С помощью этих модификаций вы создадите тегировщик, который выводит наиболее вероятное слово в любой позиции. Попробуйте и как вам это нравится.

Прогноз:

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

Чтобы создать обобщающий тегировщик, рассмотрите возможность использования NLTK для обучения «последовательного классификатора». Вы можете использовать любые функции, которые хотите, включая слова до и после — конечно, насколько хорошо это будет работать, это ваша проблема. API-интерфейс классификатора NLTK аналогичен API-интерфейсу ContextTagger, но функция контекста (также известная как функция признаков) возвращает словарь, а не кортеж. Опять же, см. книгу NLTK и исходный код.

person alexis    schedule 27.08.2015
comment
Спасибо. как это можно использовать для предсказания следующего слова с учетом контекста (оба слева направо)? в чем вопрос был? - person stackit; 28.08.2015
comment
также это просто помечает слова тегом POS, лучше пометьте его наиболее вероятным словом - person stackit; 28.08.2015
comment
Должен признаться, я не понимаю, какое приложение вы имеете в виду: если вы уже знаете слова в позициях i+1, i+2 и т. д., как вы предсказываете слово в позиции я? Но в любом случае, если вы хотите, чтобы теггер выдавал слова, просто замените обучающие данные кортежами (word_i, word_i+1). Это однострочник, вам нужна помощь с ним? - person alexis; 28.08.2015
comment
Приложение в основном представляет собой подсказку \ корректор слов, предполагая, что неправильное предложение дается с неправильным использованием слов, грамматикой и т. д., исправляя его. - person stackit; 28.08.2015
comment
как бы вы использовали это, чтобы предложить наиболее вероятное слово для текущего слова, дающего обе стороны контекста, а также текущее слово? - person stackit; 29.08.2015
comment
В следующем, например, как обучить вывод dwdiff для получения предложений об ошибках на основе модели, указанной выше: Что такое {+a+} генетический риск? Генетический риск означает [-more-] вероятность [-your-] наследования расстройства или болезни. Люди заболевают [-определенными заболеваниями-] {+заболеваниями+} из-за генетических изменений. Насколько генетическое изменение говорит нам о вероятности развития заболевания, не всегда ясно. Если ваши генетические результаты [-указать-] {+указать+}... - person stackit; 29.08.2015
comment
Можете ли вы построить обучающую выборку, которая соединяет каждое слово в корпусе со следующим словом, или нет? - person alexis; 29.08.2015
comment
Я имею в виду, хорошо, я понимаю, для чего ты это хочешь. Но было ли объяснение в моем комментарии достаточным или нет? - person alexis; 29.08.2015
comment
да, у меня есть этот тренировочный набор, в котором есть сопоставление между правильными словами и неправильными словами - person stackit; 29.08.2015
comment
можете ли вы написать пример того, как на самом деле использовать его для прогнозирования и оценки точности? и он генерирует дерево внутри? - person stackit; 29.08.2015
comment
Создать дерево внутри? О чем ты говоришь? Я пересмотрел код, чтобы предсказать текущее слово в каждой позиции, а не его POS, и показать, как создавать обучающие данные. - person alexis; 29.08.2015
comment
так он использует внутренний классификатор или чистое сопоставление ngram? а для последовательного классификатора как это можно реализовать? - person stackit; 31.08.2015
comment
может ли он использовать значения тега pos контекста? - person stackit; 31.08.2015
comment
Ваш вопрос заключался в том, как сделать теггер ngram, который использует слова с обеих сторон, и как научить его оценивать текущее слово вместо тега POS. Я ответил на обе части. Прочтите мой ответ еще раз и книгу NLTK, чтобы получить ответы на дополнительные вопросы. (Но да, вы также можете использовать POS-теги) - person alexis; 31.08.2015
comment
И, конечно же, если вы застряли с каким-то другим аспектом вашего проекта, вам всегда предлагается задать новый вопрос. - person alexis; 31.08.2015