Создайте корпус текстов из нескольких литературных произведений, выберите хотя бы два больших текста ваших любимых писателей.
Далее вам надо разбить текст на предложения (см. ранее sent_tokenze
), соответственно, вы получаете
большой список предложений, которые мы будем использовать для исследований.
Разбейте список предложений случайным образом на три части: для обучения (train set), для настройки (validation set), для тестирования качества (test set). Для этого перемешайте список предложений, возьмите первые 80% как train set, следующие 10% как validation set, оставшиеся 10% как test set.
Пример кода для перемешивания:
from random import shuffle, seed
# инициализация генератора случайных чисел, случайные числа все время будут
# одинаковые, это удобно для отладки, после отладки эту строку надо убрать.
seed(42)
l = ["мы", "слова", "которые", "надо", "перемешать"]
shuffle(l)
print(l)
['надо', 'слова', 'которые', 'перемешать', 'мы']
Предложения из всех трёх множеств нужно обработать. Для обучающего множества можно пользоваться
встроенной в nltk функцией padded_everygram_pipeline
.
Эта функция делает сразу много действий, она дополняет предложения техническими словами <s>
и </s>
для начала и конца, возвращает n_grams
, который для каждого предложения содержит список всех n-грамм
этого предложения, причем, если мы указываем n=3 в качестве аргумента, будут построены и 1-, и 2-,
и 3-граммы. В words
возвращается список всех слов, чтобы построить словарь на их основе:
from nltk.lm.preprocessing import padded_everygram_pipeline
sentences = [
["моё", "первое", "предложение", "!"],
["моё", "второе", "предложение", "."],
["еще", "одно", "предложение", "."]
]
n = 3
n_grams, words = padded_everygram_pipeline(n, sentences)
print("все слова:")
print(list(words)) # нужно делать list(), потому что words это генератор
print("все n-граммы")
for sentence_n_grams in n_grams:
print(list(sentence_n_grams)) # нужно делать list(), потому что sentence_n_grams это генератор
# небольшое замечание, padded_everygram_pipeline возвращает генераторы
# их можно использовать (перебрать) только один раз. Например, их можно распечатать,
# или можно обучить через них модель. После этого их надо
# создавать заново. Вы можете столкнуться с этим при отладке,
# модель не обучается после того, как вы всё распечатали.
все слова: ['<s>', '<s>', 'моё', 'первое', 'предложение', '!', '</s>', '</s>', '<s>', '<s>', 'моё', 'второе', 'предложение', '.', '</s>', '</s>', '<s>', '<s>', 'еще', 'одно', 'предложение', '.', '</s>', '</s>'] все n-граммы [('<s>',), ('<s>', '<s>'), ('<s>', '<s>', 'моё'), ('<s>',), ('<s>', 'моё'), ('<s>', 'моё', 'первое'), ('моё',), ('моё', 'первое'), ('моё', 'первое', 'предложение'), ('первое',), ('первое', 'предложение'), ('первое', 'предложение', '!'), ('предложение',), ('предложение', '!'), ('предложение', '!', '</s>'), ('!',), ('!', '</s>'), ('!', '</s>', '</s>'), ('</s>',), ('</s>', '</s>'), ('</s>',)] [('<s>',), ('<s>', '<s>'), ('<s>', '<s>', 'моё'), ('<s>',), ('<s>', 'моё'), ('<s>', 'моё', 'второе'), ('моё',), ('моё', 'второе'), ('моё', 'второе', 'предложение'), ('второе',), ('второе', 'предложение'), ('второе', 'предложение', '.'), ('предложение',), ('предложение', '.'), ('предложение', '.', '</s>'), ('.',), ('.', '</s>'), ('.', '</s>', '</s>'), ('</s>',), ('</s>', '</s>'), ('</s>',)] [('<s>',), ('<s>', '<s>'), ('<s>', '<s>', 'еще'), ('<s>',), ('<s>', 'еще'), ('<s>', 'еще', 'одно'), ('еще',), ('еще', 'одно'), ('еще', 'одно', 'предложение'), ('одно',), ('одно', 'предложение'), ('одно', 'предложение', '.'), ('предложение',), ('предложение', '.'), ('предложение', '.', '</s>'), ('.',), ('.', '</s>'), ('.', '</s>', '</s>'), ('</s>',), ('</s>', '</s>'), ('</s>',)]
Настроечное и тестовое множество (validation) предложений
нужно обработать аналогично, добавить слова <s>
и <\s>
,
вычислить n-граммы, причем в этот раз нас интересуют
n-граммы при фиксированном $n$.
from nltk.lm.preprocessing import pad_both_ends
from nltk import ngrams
# сначала нужно добавить символы начала и конца предложения
sentence = ['мы', 'слова', 'одного', 'предложения']
padded_sentence = pad_both_ends(sentence, n)
print(list(padded_sentence))
# после распечатки padded_sentence стух, поэтому создадим его заново
padded_sentence = pad_both_ends(sentence, n)
all_sentence_n_grams = ngrams(padded_sentence, n)
print(list(all_sentence_n_grams))
['<s>', '<s>', 'мы', 'слова', 'одного', 'предложения', '</s>', '</s>'] [('<s>', '<s>', 'мы'), ('<s>', 'мы', 'слова'), ('мы', 'слова', 'одного'), ('слова', 'одного', 'предложения'), ('одного', 'предложения', '</s>'), ('предложения', '</s>', '</s>')]
В принципе, рассмотренный выше padded_everygram_pipeline
можно
было реализовать самостоятельно через функции pad_both_ends
и
ngrams
.
Обучаем модель. Точнее, несколько моделей с разными видами сглаживания. Позже, мы будем их сравнивать.
from nltk.lm import MLE, Lidstone, KneserNeyInterpolated
n_grams, words = padded_everygram_pipeline(n, sentences)
# создаём модель
model = MLE(n) # Модель MLE означает отсутствие сглаживания
# обучаем
model.fit(n_grams, words)
Кроме модели MLE(n)
можно создать модель Lidstone(gamma, n)
,
она означает модель, которую мы называли сглаживанием
Лапласса, и здесь gamma
означает число, добавляемое в числитель.
Модель KneserNeyInterpolated(n, 0.1)
соответствует модели со
сглаживанием KneyserNey из конспекта. discount
0.1 означает число,
вычитаемое из числителя.
Когда модель построена, ее можно оценить, мы оцениваем все
модели на настроечном множестве (validation set). Для оценки
используем величину perplexity, она равна 1 делить на среднюю геометрическую
вероятностей всех n-грамм. Соответственно, чем perplexity меньше, тем больше средняя
вероятность n-грамм из текста, т.е. модель лучше предсказывает этот текст.
В коде вы можете вызвать model.perplexity(all_sentence_n_grams)
, где
all_sentence_n_grams
получен в пункте 5.
Нам нужно выбрать лучшую модель, мы рассмотрим несколько моделей со сглаживанием Лапласа и несколько моделей со сглаживанием Kneyser-Ney. MLE нас не интересует, потому что не имеет сглаживание, и любое неизвестное слово или не встречавшееся ранее сочетание слов ведёт к нулевой вероятности.
Рассмотрим модели Лапласа, где gamma
выбирается как $0.1$, $0.2$, $0.5$, $1$, $2$. В моделях
Kneyser-Ney выбираем discount
как $0.01$, $0.05$, $0.1$, $0.2$, $0.5$. Если вычисления окажутся
долгими, уменьшите количество вариантов.
Для каждой модели вычислите perplexity на настроечном множестве (validation set), выберите минимальное значение.
Для модели, которая выдаёт минимальное значение на настроечном множестве, вычислите perplexity на тестовом множестве (test set). Это будет ответ, какую perplexity мы достигли с помощью n-грамм модели.