Обычный алгоритм 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
for t, token in enumerate(tokens): do_something(tokens[t-n:t+m])
- person clemtoy   schedule 17.08.2015