Часть 3: Модели нейронного языка

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

1. Что такое языковая модель?

В самом простом виде:

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

Традиционно эта проблема решалась с помощью статистических языковых моделей, которые в основном заключались в использовании так называемых n-граммовых моделей в сочетании с некоторой техникой сглаживания [1]. Большой поворот в подходе исследователей к этой проблеме произошел, когда Bengio et al. [2] предложил использовать нейронную сеть с прямой связью вместе со словом таблица поиска для представления n предыдущих слов (часто называемых токеном) в последовательность, как показано на рисунке 1. Сегодня эта таблица поиска известна как встраивание слов, с которым вы, возможно, уже знакомы, если читаете нашу вторую запись в блоге. Так родилась модель нейронного языка!

2. Создание собственной модели нейронного языка

В этом посте мы сохраним практичность и сразу перейдем к примеру кодирования! В этом примере мы проведем вас через:

  1. Как вы можете подготовить документ с вводимым текстом для разработки словесной языковой модели,
  2. Как вы можете спроектировать и реализовать свою собственную нейронную языковую модель, и
  3. Как вы можете использовать эту модель для создания нового текста с тональностью, аналогичной входному тексту.

Давайте начнем!

2.1. Установка пакетов

Для этого урока нам нужно установить только два пакета: good-ol ’numpy и keras (которые сделают за нас большую часть тяжелой работы по глубокому обучению). Идите вперед и запустите в своем терминале следующее:

$ pip install numpy
$ pip install keras

2.2. Создание учебного документа

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

import requests
 
response = requests.get('https://raw.githubusercontent.com/robertsdionne/rwet/master/hw2/catinthehat.txt')
doc = response.text
print(doc[:300])

В приведенном выше скрипте мы используем очень полезный модуль запросы и получаем текстовый файл прямо с GitHub. Давайте распечатаем первые 300 символов нашего документа, чтобы убедиться, что мы взяли нужный файл:

The Cat in the Hat
By Dr. Seuss
The sun did not shine.
It was too wet to play.
So we sat in the house
All that cold, cold, wet day.
I sat there with Sally.
We sat there, we two.
And I said, "How I wish
We had something to do!"
Too wet to go out
And too cold to play ball.
So we sat in the house.

Кажется, правильно!

2.3. Предварительная обработка текста

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

import string
 
doc = doc.replace('\n', ' ').lower()
doc = doc.translate(str.maketrans('', '', string.punctuation))
tokens = doc.split()
vocab_size = len(set(tokens))
print(f"Tokens: {tokens}")
print(f"Total tokens: {len(tokens)}")
print(f"Vocabulary size: {vocab_size}")

Мы начинаем с замены всех символов новой строки пробелами и преобразования всех символов в их нижний регистр. Затем мы удаляем все знаки препинания из нашего документа. Затем мы можем разделить наш документ на отдельные токены (или слова) на основе пробелов, используя метод sring .split (). Давайте посчитаем количество токенов, чтобы увидеть, с чем нам предстоит работать:

Tokens: ['the', 'cat', 'in', 'the', 'hat', 'by', 'dr', 'seuss', ...
Total tokens: 6290
Vocabulary size: 855

Похоже, что в нашей истории всего 6290 токенов, но словарный запас составляет всего 855 уникальных слов.

Теперь следующий шаг - преобразовать наши токены в набор последовательностей, которые могут действовать как наш обучающий набор данных. Для этого давайте сгруппируем наш список токенов в последовательности из 64 входных слов и одного целевого слова (что дает нам общую длину последовательности 65). Мы можем сделать это, «сдвинув» окно (размером 65) по нашему списку токенов и соединив их вместе, чтобы создать образец последовательности.

length = 64 + 1
sequences = []
for i in range(length, len(tokens)):
 line = ' '.join(tokens[i - length:i])
 sequences.append(line)
print(f"Number of sequences: {len(sequences)}")

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

Number of sequences: 6225

Этого должно быть более чем достаточно обучающих последовательностей для демонстрационных целей.

2.4. Подготовьте набор данных

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

import numpy as np
from keras.preprocessing.text import Tokenizer
from keras.utils import to_categorical
 
tokenizer = Tokenizer()
tokenizer.fit_on_texts(sequences)
sequences = np.array(tokenizer.texts_to_sequences(sequences))
vocab_size += 1

Для этого мы можем использовать класс keras Tokenizer. Сначала мы можем определить объект токенизатора и подогнать его ко всему нашему набору последовательностей, который, по сути, находит все уникальные слова в наших данных и сопоставляет каждое из них с уникальным целочисленным идентификатором. Более конкретно, словам присваиваются значения от 1 до общего количества слов. Затем мы используем токенизатор, чтобы переопределить наши последовательности как набор целых чисел и сохранить результат в виде массива numpy. На этом этапе, поскольку слово в конце словаря будет 855, но индексирование массивов Python начинается с нуля, мы увеличиваем размер словаря, чтобы исправить это.

Теперь, когда наши последовательности правильно закодированы, мы можем разделить их на набор функций X и целевые переменные y.

X, y = sequences[:,:-1], sequences[:,-1]
y = to_categorical(y, num_classes=vocab_size)
seq_length = X.shape[1]

По причинам индексации мы используем удобную операцию объединения numpy для выполнения этого разделения. После этого мы сразу же кодируем целевое слово с помощью метода keras to_categorical (), который, по сути, преобразует наш вывод в вектор длины voiceab_size со значением 1 вместо позиции слова и значением 0 во всех остальных случаях. Затем задача модели - изучить распределение вероятностей по всем словам в нашем словаре.

2.5. Определите и обучите модель

Ура! Мы подошли к самой интересной части выбора структуры нашей нейронной языковой модели и ее обучения.

from keras.models import Sequential
from keras.layers import Dense, LSTM, Embedding
 
model = Sequential()
model.add(Embedding(vocab_size, 100, input_length=seq_length))
model.add(LSTM(128, return_sequences=True))
model.add(LSTM(128))
model.add(Dense(128, activation='relu'))
model.add(Dense(vocab_size, activation='softmax'))
print(model.summary())

Мы будем придерживаться стандартных стандартов, определив последовательную модель keras и добавив к ней несколько слоев. В частности, мы будем использовать слой встраивания для изучения представления слов, а также рекуррентную нейронную сеть Long Short-Term Memory (LSTM), чтобы научиться предсказывать слова в зависимости от их контекста. Входными данными для слоя внедрения являются размер словаря, длина вектора внедрения (который мы выбираем как 100) и длина последовательности. Мы также указываем размер слоя 128 для обоих уровней LSTM (поскольку степени двойки более эффективны в вычислительном отношении). Наконец, мы определим два полностью связанных слоя, которые будут использоваться для интерпретации функций, извлеченных из последовательности, причем первый снова будет иметь 128 слоев и функцию активации Rectified Linear Unit (ReLU) и последний с 856 слоями и функцией активации softmax (для обеспечения масштабирования выходных значений от нуля и один). Мы можем распечатать краткое изложение нашей модели как своего рода проверку работоспособности:

Model: "sequential"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
embedding_1 (Embedding)      (None, 64, 64)            54784     
_________________________________________________________________
lstm_1 (LSTM)                (None, 64, 128)           98816     
_________________________________________________________________
lstm_2 (LSTM)                (None, 128)               131584    
_________________________________________________________________
dense_1 (Dense)              (None, 128)               16512     
_________________________________________________________________
dense_2 (Dense)              (None, 856)               110424    
=================================================================
Total params: 412,120
Trainable params: 412,120
Non-trainable params: 0
_________________________________________________________________

Вроде все в порядке. Теперь мы можем скомпилировать и обучить нашу модель следующим образом:

model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
model.fit(X, y, batch_size=128, epochs=100)

Мы определяем функцию потерь кросс-энтропии, которая имеет смысл, поскольку технически мы имеем дело с проблемой многоклассовой классификации. Мы также укажем, что keras должен использовать эффективный оптимизатор Adam для обновления весов модели, оцениваемых по точности. Наконец, мы подогнали модель к нашим данным для 100 эпох обучения с довольно скромным размером пакета 128. Теперь все, что нам нужно сделать, это пойти выпить кофе и дать нашей модели тренироваться.

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

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

from keras.preprocessing.sequence import pad_sequences
 
def generate_sequence(model, tokenizer, seq_length, num_words, input_text):
 word_lookup = dict((v, k) for k, v in tokenizer.word_index.items())
 for _ in range(num_words):
   encoded = tokenizer.texts_to_sequences([input_text])[0]
   encoded = pad_sequences([encoded], maxlen=seq_length, truncating='pre')
   y_pred = model.predict_classes(encoded, verbose=0)[0]
   new_word = word_lookup[yhat]
   input_text += ' ' + new_word
 return input_text

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

Все, что осталось сделать, это опробовать нашу функцию.

input_text = "then the cat turned around and laughed"
generate_sequence(model, tokenizer, seq_length, input_text, 10)

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

'then the cat turned around and laughed you should a yink i have a lot of one my teeth are gold'

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

3. Подведение итогов

И это все! В этом посте я рассказал вам, как создать свою собственную нейронную языковую модель на Python с некоторой помощью Keras и как вы можете использовать эту модель для создания собственных текстовых последовательностей.

Об авторе: Шейн ван Херден - специалист по анализу данных и инструментальный исследователь НЛП в Cape AI. Шейн имеет докторскую степень в области искусственного интеллекта в Стелленбосском университете и проявляет большой интерес к НЛП, исследованиям операций и глубокому обучению.

О Cape AI: Cape AI - компания, занимающаяся искусственным интеллектом, базирующаяся в Южной Африке и Нидерландах, специализирующаяся на консалтинге в области ИИ, а также на исследованиях и разработках в области компьютерного зрения и обработки естественного языка.

использованная литература

  1. Кнезер Р. и Ней Х. (1995, май). Улучшенная поддержка для моделирования языка m-грамм. В icassp (Том 1, с. 181e4).
  2. Бенжио Ю., Дюшарм Р. и Винсент П. (2001). Нейронно-вероятностная языковая модель. Труды НИПС.