Глубокое обучение — одно из самых раскрученных модных словечек в настоящее время, и его оправдание заключается в большом количестве приложений и его способности решать сложные проблемы, начиная от компьютерного зрения и заканчивая НЛП и многими другими. В этой серии постов я хотел бы дать вам краткое введение в использование глубокого обучения для решения проблемы НЛП. Мы пытаемся предсказать рейтинги (из 5) для фильма на основе его обзора. В этом разделе мы рассмотрим базовую предварительную обработку текстовых данных, которую необходимо выполнить, чтобы наши данные были готовы к использованию в качестве входных данных в нашу модель LSTM.

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

А чтобы узнать больше о применении RNN, обратитесь к этому блогу.

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

Начнем с импорта необходимых модулей в python.

import numpy as np
import pandas as pd
import re
from collections import Counter
from keras.preprocessing.text import Tokenizer
from keras.utils import np_utils
from keras.models import Sequential, load_model
from keras.layers import Dense, Dropout, LSTM, Bidirectional
from nltk.corpus import stopwords
from nltk.tokenize import word_tokenize
from sklearn.cross_validation import train_test_split

Затем давайте определим функции для извлечения чистых токенов из текста, добавления их в словарь, хранящийся как счетчик в python, сохранения счетчика в текстовом файле, загрузки этого текстового файла и, наконец, очистки данных на основе загруженных токенов. Мы также должны удалить некоторые слова, которые присутствуют как стоп-слова в стоп-словах nltk по умолчанию. Мы также добавляем несколько пробелов в качестве стоп-слов, которые можно найти в корпусе после очистки данных.

# removing some words and adding some to increase accuracy
stopwords = stopwords.words('english')
newStopWords = ['', ' ', '  ', '   ', '    ', ' s']
stopwords.extend(newStopWords)
stopwords.remove('no')
stopwords.remove('not')
stopwords.remove('very')
stop_words = set(stopwords)
def clean_doc(doc, vocab=None):
    tokens = word_tokenize(doc)
    # keeping only alphabets    
    tokens = [re.sub('[^a-zA-Z]', ' ', word) for word in tokens] 
    # converting to lowercase
    tokens = [word.lower() for word in tokens]
    # removing stopwords
    tokens = [w for w in tokens if not w in stop_words]
    # removing single characters if any
    tokens = [word for word in tokens if len(word) > 1]
    if vocab:
        tokens = [w for w in tokens if w in vocab]
        tokens = ' '.join(tokens)        
    return tokens
def add_doc_to_vocab(text, vocab):
    tokens = clean_doc(text)
    vocab.update(tokens)
def save_list(lines, filename):
    data = '\n'.join(lines)
    file = open(filename, 'w')
    file.write(data)
    file.close()
    
def load_doc(filename):
    file = open(filename, 'r')
    text = file.read()
    file.close()
    return text

Набор данных состоит из 156060 строк и 4 столбцов, из которых для нас важны только два столбца, то есть «Фраза» и «Настроение». Столбец «Фраза» содержит различные фразы, а столбец «Настроение» содержит номер настроения, присвоенный соответствующей фразе, в диапазоне от 0 до 4. 0 означает крайне негативное настроение, а 4 — очень положительное настроение.

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

df = pd.read_csv('train.tsv', delimiter='\t')
X = df['Phrase']
y = df['Sentiment']
y = np_utils.to_categorical(y)
# splitting into training and testing data
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.1, random_state = 0)
# removing unnecessary data
del df, X, y
# creating a vocabulary of words
vocab = Counter()
len_train = len(X_train)
for i in range(len_train):
    text = X_train.iloc[i]
    add_doc_to_vocab(text , vocab)
print(len(vocab))
# print the 20 most common words
print(vocab.most_common(20))
# removing tokens which occur less than 3 times.
min_occurance = 2
tokens = [k for k,c in vocab.items() if (c >= min_occurance & len(k) > 1)]
# saving the vocabulary for futute use
save_list(tokens, 'vocab.txt')
# loading the saved vocabulary
vocab = load_doc('vocab.txt')
vocab = vocab.split()
vocab = set(vocab)
train_doc = []
for i in range(len_train):
    text = X_train.iloc[i]
    doc = clean_doc(text, vocab)
    train_doc.append(doc)
test_doc = []
len_test = len(X_test)
for i in range(len_test):
    text = X_test.iloc[i]
    doc = clean_doc(text, vocab)
    test_doc.append(doc)

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

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

# storing indexes where no tokens are present
index_train = []
for i in range(len(train_doc)):
    if len(train_doc[i]) == 0 :
        index_train.append(i)
    
index_test = []
for i in range(len(test_doc)):
    if len(test_doc[i]) == 0 :
        index_test.append(i)
# dropping the unnecessary data
train_doc = np.delete(train_doc, index_train, 0)
test_doc = np.delete(test_doc, index_test, 0)
y_train = np.delete(y_train, index_train, 0)
y_test = np.delete(y_test, index_test, 0)

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

tokenizer = Tokenizer()
tokenizer.fit_on_texts(train_doc)
X_train = tokenizer.texts_to_matrix(train_doc, mode='binary')
X_test = tokenizer.texts_to_matrix(test_doc, mode='binary')
n_words = X_test.shape[1]

Построение модели

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

# LSTM Model
model = Sequential()
model.add(Bidirectional(LSTM(100, activation='relu'), input_shape=(None,n_words)))
model.add(Dropout(0.2))
model.add(Dense(units=50, input_dim=100, activation='relu'))
model.add(Dense(5, activation='softmax'))
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
# fitting the LSTM model
model.fit(X_train.reshape((-1, 1, n_words)), y_train, epochs=20, batch_size=100)
# finding test loss and test accuracy
loss_rnn, acc_rnn = model.evaluate(X_test.reshape((-1, 1, n_words)), y_test, verbose=0)
# saving model weights
model.model.save('rnn.h5')
# loading saved weights
model_rnn = load_model('rnn.h5')

Вы можете экспериментировать, делая модель глубже и добавляя разное количество нейронов в каждом слое. Используя эту модель, мы получаем точность около 60–65%. Мы также сохраняем модели, чтобы в дальнейшем использовать веса моделей для прогнозирования без обучения для будущего использования или когда вы хотите делать прогнозы в реальном времени в веб-приложении. Для создания такого веб-приложения с использованием LSTM следите за обновлениями во второй части этого поста, где мы рассмотрим, как преобразовать вашу модель в веб-приложение.

Полную реализацию вы можете найти здесь.