Рубрики
Без рубрики

Python for NLP: Генерация текста глубокого обучения с помощью Keras

Генерация текста-одно из самых современных приложений НЛП. В этой статье вы увидите, как генерировать текст с помощью методов глубокого обучения в Python с помощью библиотеки Keras.

Автор оригинала: Usman Malik.

Это 21 – я статья в моей серии статей по Python для НЛП. В предыдущей статье я объяснил как использовать быструю текстовую библиотеку Facebook для поиска семантического сходства и выполнения классификации текста. В этой статье вы увидите, как генерировать текст с помощью метода глубокого обучения в Python с помощью библиотеки Keras .

Генерация текста-одно из самых современных приложений НЛП. Методы глубокого обучения используются для различных задач генерации текста, таких как написание стихов, создание сценариев для фильмов и даже для сочинения музыки. Однако в этой статье мы увидим очень простой пример генерации текста, где, учитывая входную строку слов, мы будем предсказывать следующее слово. Мы будем использовать сырой текст из знаменитого романа Шекспира “Макбет” и будем использовать его для предсказания следующего слова, заданного последовательностью входных слов.

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

Импорт библиотек и наборов данных

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

import numpy as np
from keras.models import Sequential, load_model
from keras.layers import Dense, Embedding, LSTM, Dropout
from keras.utils import to_categorical
from random import randint
import re

Следующим шагом является загрузка набора данных. Мы будем использовать библиотеку NLTK Python для загрузки набора данных. Мы будем использовать набор данных Gutenberg , который содержит 3036 английских книг, написанных 142 авторами, включая “Макбета” Шекспира.

Следующий сценарий загружает набор данных Гутенберга и печатает имена всех файлов в наборе данных.

import nltk
nltk.download('gutenberg')
from nltk.corpus import gutenberg as gut

print(gut.fileids())

Вы должны увидеть следующий вывод:

['austen-emma.txt', 'austen-persuasion.txt', 'austen-sense.txt', 'bible-kjv.txt', 'blake-poems.txt', 'bryant-stories.txt', 'burgess-busterbrown.txt', 'carroll-alice.txt', 'chesterton-ball.txt', 'chesterton-brown.txt', 'chesterton-thursday.txt', 'edgeworth-parents.txt', 'melville-moby_dick.txt', 'milton-paradise.txt', 'shakespeare-caesar.txt', 'shakespeare-hamlet.txt', 'shakespeare-macbeth.txt', 'whitman-leaves.txt']

Файл shakespeare-macbeth.txt содержит сырой текст для романа “Макбет”. Для чтения текста из этого файла можно использовать метод raw из класса gutenberg :

macbeth_text = nltk.corpus.gutenberg.raw('shakespeare-macbeth.txt')

Давайте напечатаем первые 500 символов из набора данных out:

print(macbeth_text[:500])

Вот результат:

[The Tragedie of Macbeth by William Shakespeare 1603]


Actus Primus. Scoena Prima.

Thunder and Lightning. Enter three Witches.

  1. When shall we three meet againe?
In Thunder, Lightning, or in Raine?
  2. When the Hurley-burley's done,
When the Battaile's lost, and wonne

   3. That will be ere the set of Sunne

   1. Where the place?
  2. Vpon the Heath

   3. There to meet with Macbeth

   1. I come, Gray-Malkin

   All. Padock calls anon: faire is foule, and foule is faire,
Houer through

Вы можете видеть, что текст содержит много специальных символов и цифр. Следующим шагом является очистка набора данных.

Предварительная обработка данных

Чтобы удалить знаки препинания и специальные символы, мы определим функцию с именем preprocess_text() :

def preprocess_text(sen):
    # Remove punctuations and numbers
    sentence = re.sub('[^a-zA-Z]', ' ', sen)

    # Single character removal
    sentence = re.sub(r"\s+[a-zA-Z]\s+", ' ', sentence)

    # Removing multiple spaces
    sentence = re.sub(r'\s+', ' ', sentence)

    return sentence.lower()

Функция preprocess_text принимает текстовую строку в качестве параметра и возвращает очищенную текстовую строку в нижнем регистре.

Давайте теперь очистим наш текст и снова напечатаем первые 500 символов:

macbeth_text = preprocess_text(macbeth_text)
macbeth_text[:500]

Вот результат:

the tragedie of macbeth by william shakespeare actus primus scoena prima thunder and lightning enter three witches when shall we three meet againe in thunder lightning or in raine when the hurley burley done when the battaile lost and wonne that will be ere the set of sunne where the place vpon the heath there to meet with macbeth come gray malkin all padock calls anon faire is foule and foule is faire houer through the fogge and filthie ayre exeunt scena secunda alarum within enter king malcom

Преобразование слов в числа

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

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

Следующий скрипт маркирует текст в нашем наборе данных, а затем печатает общее количество слов в наборе данных, а также общее количество уникальных слов в наборе данных:

from nltk.tokenize import word_tokenize

macbeth_text_words = (word_tokenize(macbeth_text))
n_words = len(macbeth_text_words)
unique_words = len(set(macbeth_text_words))

print('Total Words: %d' % n_words)
print('Unique Words: %d' % unique_words)

Вывод выглядит следующим образом:

Total Words: 17250
Unique Words: 3436

В нашем тексте всего 17250 слов, из которых 3436 слов уникальны. Для преобразования маркированных слов в числа можно использовать класс Tokenizer из модуля keras.preprocessing.text . Вам нужно вызвать метод fit_on_texts и передать ему список слов. Будет создан словарь, где ключи будут представлять слова, а целые числа будут представлять соответствующие значения словаря.

Посмотрите на следующий сценарий:

from keras.preprocessing.text import Tokenizer
tokenizer = Tokenizer(num_words=3437)
tokenizer.fit_on_texts(macbeth_text_words)

Для доступа к словарю, содержащему слова и соответствующие им индексы, можно использовать атрибут word_index объекта tokenizer:

vocab_size = len(tokenizer.word_index) + 1
word_2_index = tokenizer.word_index

Если вы проверите длину словаря, он будет содержать 3436 слов, что является общим количеством уникальных слов в нашем наборе данных.

Теперь давайте выведем 500-е уникальное слово вместе с его целочисленным значением из словаря word_2_index .

print(macbeth_text_words[500])
print(word_2_index[macbeth_text_words[500]])

Вот результат:

comparisons
1456

Здесь слову “сравнения” присваивается целочисленное значение 1456.

Изменение формы данных

Генерация текста относится к категории задач с последовательностью “многие к одному”, поскольку вход-это последовательность слов, а выход-одно слово. Мы будем использовать Long Short-Term Memory Network (LSTM), которая представляет собой тип рекуррентной нейронной сети для создания нашей модели генерации текста. LSTM принимает данные в 3-мерном формате (количество выборок, количество временных шагов, объекты на временной шаг). Поскольку вывод будет представлять собой одно слово, форма вывода будет 2-мерной (количество выборок, количество уникальных слов в корпусе).

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

input_sequence = []
output_words = []
input_seq_length = 100

for i in range(0, n_words - input_seq_length , 1):
    in_seq = macbeth_text_words[i:i + input_seq_length]
    out_seq = macbeth_text_words[i + input_seq_length]
    input_sequence.append([word_2_index[word] for word in in_seq])
    output_words.append(word_2_index[out_seq])

В приведенном выше скрипте мы объявляем два пустых списка input_sequence и output_words . Значение input_seq_length равно 100, что означает, что наша входная последовательность будет состоять из 100 слов. Затем мы выполняем цикл, в котором на первой итерации целочисленные значения для первых 100 слов из текста добавляются в список input_sequence . 101-е слово добавляется в список output_words . Во время второй итерации последовательность слов, которая начинается со 2-го слова в тексте и заканчивается на 101-м слове, сохраняется в списке input_sequence , а 102-е слово хранится в массиве output_words и так далее. Всего будет сгенерировано 17150 входных последовательностей, так как в наборе данных всего 17250 слов (на 100 меньше, чем общее количество слов).

Теперь выведем значение первой последовательности в списке input_sequence :

print(input_sequence[0])

Выход:

[1, 869, 4, 40, 60, 1358, 1359, 408, 1360, 1361, 409, 265, 2, 870, 31, 190, 291, 76, 36, 30, 190, 327, 128, 8, 265, 870, 83, 8, 1362, 76, 1, 1363, 1364, 86, 76, 1, 1365, 354, 2, 871, 5, 34, 14, 168, 1, 292, 4, 649, 77, 1, 220, 41, 1, 872, 53, 3, 327, 12, 40, 52, 1366, 1367, 25, 1368, 873, 328, 355, 9, 410, 2, 410, 9, 355, 1369, 356, 1, 1370, 2, 874, 169, 103, 127, 411, 357, 149, 31, 51, 1371, 329, 107, 12, 358, 412, 875, 1372, 51, 20, 170, 92, 9]

Давайте нормализуем наши входные последовательности, разделив целые числа в последовательностях на наибольшее целочисленное значение. Следующий скрипт также преобразует выходные данные в 2-мерный формат.

X = np.reshape(input_sequence, (len(input_sequence), input_seq_length, 1))
X = X / float(vocab_size)

y = to_categorical(output_words)

Следующий сценарий печатает форму входов и соответствующих выходов.

print("X shape:", X.shape)
print("y shape:", y.shape)

Выход:

X shape: (17150, 100, 1)
y shape: (17150, 3437)

Обучение модели

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

Мы создадим три слоя LSTM с 800 нейронами каждый. Последний плотный слой с 1 нейроном будет добавлен для предсказания индекса следующего слова, как показано ниже:

model = Sequential()
model.add(LSTM(800, input_shape=(X.shape[1], X.shape[2]), return_sequences=True))
model.add(LSTM(800, return_sequences=True))
model.add(LSTM(800))
model.add(Dense(y.shape[1], activation='softmax'))

model.summary()

model.compile(loss='categorical_crossentropy', optimizer='adam')

Поскольку выходное слово может быть одним из 3436 уникальных слов, наша задача является многоклассовой классификационной задачей, поэтому используется функция categorical_crossentropy loss. В случае бинарной классификации используется функция binary_cross entropy . После выполнения описанного выше сценария вы должны увидеть сводку модели:

Model: "sequential_1"
_________________________________________________________________
Layer (type)                 Output Shape              Param #
=================================================================
lstm_1 (LSTM)                (None, 100, 800)          2566400
_________________________________________________________________
lstm_2 (LSTM)                (None, 100, 800)          5123200
_________________________________________________________________
lstm_3 (LSTM)                (None, 800)               5123200
_________________________________________________________________
dense_1 (Dense)              (None, 3437)              2753037
=================================================================
Total params: 15,565,837
Trainable params: 15,565,837
Non-trainable params: 0

Чтобы обучить модель, мы можем просто использовать метод fit () .

model.fit(X, y, batch_size=64, epochs=10, verbose=1)

Здесь вы снова можете поиграть с различными значениями для batch_size и epochs . Модель может занять некоторое время для обучения.

Делать Прогнозы

Чтобы сделать предсказания, мы случайным образом выберем последовательность из списка input_sequence , преобразуем ее в 3-мерную форму и затем передадим ее методу predict() обученной модели. Модель вернет однократно закодированный массив, где индекс, содержащий 1, будет значением индекса следующего слова. Затем значение индекса передается в словарь index_2_word , где в качестве ключа используется слово index. Словарь index_2_word вернет слово, принадлежащее индексу, который передается в качестве ключа к словарю.

Следующий скрипт случайным образом выбирает последовательность целых чисел, а затем печатает соответствующую последовательность слов:

random_seq_index = np.random.randint(0, len(input_sequence)-1)
random_seq = input_sequence[random_seq_index]

index_2_word = dict(map(reversed, word_2_index.items()))

word_sequence = [index_2_word[value] for value in random_seq]

print(' '.join(word_sequence))

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

amen when they did say god blesse vs lady consider it not so deepely mac but wherefore could not pronounce amen had most need of blessing and amen stuck in my throat lady these deeds must not be thought after these wayes so it will make vs mad macb me thought heard voyce cry sleep no more macbeth does murther sleepe the innocent sleepe sleepe that knits vp the rauel sleeue of care the death of each dayes life sore labors bath balme of hurt mindes great natures second course chiefe nourisher in life feast lady what doe you meane

В приведенном выше сценарии словарь index_2_word создается простым обращением словаря word_2_index . В этом случае реверсирование словаря относится к процессу замены ключей значениями.

Далее мы напечатаем следующие 100 слов, которые следуют приведенной выше последовательности слов:

for i in range(100):
    int_sample = np.reshape(random_seq, (1, len(random_seq), 1))
    int_sample = int_sample / float(vocab_size)

    predicted_word_index = model.predict(int_sample, verbose=0)

    predicted_word_id = np.argmax(predicted_word_index)
    seq_in = [index_2_word[index] for index in random_seq]

    word_sequence.append(index_2_word[ predicted_word_id])

    random_seq.append(predicted_word_id)
    random_seq = random_seq[1:len(random_seq)]

Переменная word_sequence теперь содержит нашу входную последовательность слов вместе со следующими 100 предсказанными словами. Переменная word_sequence содержит последовательность слов в виде списка. Мы можем просто соединить слова в списке, чтобы получить окончательную выходную последовательность, как показано ниже:

final_output = ""
for word in word_sequence:
    final_output = final_output + " " + word

print(final_output)

Вот окончательный результат:

amen when they did say god blesse vs lady consider it not so deepely mac but wherefore could not pronounce amen had most need of blessing and amen stuck in my throat lady these deeds must not be thought after these wayes so it will make vs mad macb me thought heard voyce cry sleep no more macbeth does murther sleepe the innocent sleepe sleepe that knits vp the rauel sleeue of care the death of each dayes life sore labors bath balme of hurt mindes great natures second course chiefe nourisher in life feast lady what doe you meane and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and and

Результат пока выглядит не очень хорошо, и кажется, что наша модель учится только на последнем слове, то есть и . Однако вы получаете представление о том, как создать модель генерации текста с помощью Keras. Чтобы улучшить результаты, у меня есть для вас следующие рекомендации:

  • Измените гиперпараметры, включая размер и количество слоев LSTM и количество эпох, чтобы увидеть, получите ли вы лучшие результаты.
  • Попробуйте удалить стоп-слова типа is , am , are из обучающего набора для генерации слов, отличных от стоп-слов в тестовом наборе (хотя это будет зависеть от типа приложения).
  • Создайте модель генерации текста на уровне символов, которая предсказывает следующие N символов.

Для дальнейшей практики я бы рекомендовал вам попробовать разработать модель генерации текста с другими наборами данных из корпуса Гутенберга.

Вывод

В этой статье мы рассмотрели, как создать модель генерации текста с помощью глубокого обучения с помощью библиотеки Keras Python. Хотя модель, разработанная в этой статье, не идеальна, она передает идею о том, как генерировать текст с помощью глубокого обучения.