Python для НЛП: создание текста для глубокого обучения с помощью Keras

Это 21-я статья из моей серии статей о Python для НЛП. В предыдущей статье я объяснил, как использовать библиотеку FastText Facebook [/ python-for-nlp-working-with-facebook-fasttext-library /] для поиска семантического сходства и выполнения классификации текста. В этой статье вы увидите, как сгенерировать текст с помощью техники глубокого обучения на Python с использованием библиотеки Keras [https://keras.io/]. Генерация текста - одно из самых современных приложений НЛП. Методы глубокого обучения

Это 21-я статья из моей серии статей о Python для НЛП. В предыдущей статье я объяснил, как использовать библиотеку FastText 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 

Следующим шагом будет загрузка набора данных. Мы будем использовать библиотеку Python NLTK для загрузки набора данных. Мы будем использовать набор данных Гутенберга , который содержит 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 символов из нашего набора данных:

 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 атрибут word_index объекта токенизатора:

 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.

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

Генерация текста относится к категории проблем с последовательностью «многие к одному», поскольку входные данные - это последовательность слов, а выходные - одно слово. Мы будем использоватьсеть долгосрочной краткосрочной памяти (LSTM), которая представляет собой тип рекуррентной нейронной сети для создания нашей модели генерации текста. LSTM принимает данные в трехмерном формате (количество выборок, количество временных шагов, характеристики на временной шаг). Поскольку вывод будет состоять из одного слова, форма вывода будет двумерной (количество образцов, количество уникальных слов в корпусе).

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

 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] 

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

 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 используется функция потерь. В случае бинарной классификации binary_crossentropy функция binary_crossentropy. После выполнения вышеуказанного сценария вы должны увидеть сводку модели:

 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 , преобразуем ее в трехмерную форму и затем передадим ее predict() обученной модели. Модель вернет массив с горячим кодированием, где индекс, содержащий 1, будет значением индекса следующего слова. Затем значение индекса передается в index_2_word , где индекс слова используется в качестве ключа. 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 

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

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

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

Заключение

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

comments powered by Disqus