Анализ тональности / классификация текста с использованием RNN (Bi-LSTM) (рекуррентная нейронная сеть)
Существует множество приложений классификации текста. Например, обнаружение языка вражды, классификация намерений и систематизация новостных статей. В центре внимания этой статьи находится анализ настроений, который представляет собой проблему классификации текста. Мы будем классифицировать комментарии IMDB на два класса: положительные и отрицательные.
Мы используем Python и Jupyter Notebook для разработки нашей системы, библиотеки, которые мы будем использовать, включают Keras, Gensim, Numpy, Pandas, Regex (re) и NLTK. Мы также будем использовать Google News Word2Vec Модель. Полный код и данные можно скачать здесь.
Исследование данных
Сначала посмотрим на наши данные. Поскольку файл данных представляет собой файл с разделителями табуляции (tsv), мы будем читать его с помощью панд и передавать аргументы, чтобы сообщить функции, что разделителем является табуляция и что в нашем файле данных нет заголовка. Затем мы устанавливаем заголовок нашего фрейма данных.
import pandas as pd data = pd.read_csv('imdb_labelled.tsv', header = None, delimiter='\t') data.columns = ['Text', 'Label'] df.head()
Затем мы проверяем форму данных
data.shape
Теперь мы видим распределение классов. У нас есть 386 положительных и 362 отрицательных примера.
data.Label.value_counts()
Очистка данных
Первый шаг в очистке данных - удаление знаков препинания. Мы просто делаем это с помощью Regex. После удаления знаков препинания данные сохраняются в том же фрейме данных.
import re def remove_punct(text): text_nopunct = '' text_nopunct = re.sub('['+string.punctuation+']', '', text) return text_nopunct data['Text_Clean'] = data['Text'].apply(lambda x: remove_punct(x))
На следующем этапе мы токенизируем комментарии с помощью word_tokenize NLTK. Если мы передадим в word_tokenize строку «Токенизация - это просто». Результатом будет ["Tokenizing", "is", "easy"].
from nltk import word_tokenize tokens = [word_tokenize(sen) for sen in data.Text_Clean]
Затем мы записываем данные в нижний регистр.
def lower_token(tokens): return [w.lower() for w in tokens] lower_tokens = [lower_token(token) for token in tokens]
После ввода данных в нижний регистр стоп-слова удаляются из данных с помощью стоп-слов NLTK.
from nltk.corpus import stopwords stoplist = stopwords.words('english') def removeStopWords(tokens): return [word for word in tokens if word not in stoplist] filtered_words = [removeStopWords(sen) for sen in lower_tokens] data['Text_Final'] = [' '.join(sen) for sen in filtered_words] data['tokens'] = filtered_words
Поскольку наша проблема - это бинарная классификация. Нам нужно передать нашей модели двумерный выходной вектор. Для этого мы добавляем в наш фрейм данных два столбца с горячим кодированием.
pos = [] neg = [] for l in data.Label: if l == 0: pos.append(0) neg.append(1) elif l == 1: pos.append(1) neg.append(0) data['Pos']= pos data['Neg']= neg data = data[['Text_Final', 'tokens', 'Label', 'Pos', 'Neg']] data.head()
Разделение данных на тестовую и обучающую
Теперь мы разделили наш набор данных на обучающие и тестовые. Мы будем использовать 90% данных для обучения и 10% для тестирования. Мы используем случайное состояние, поэтому каждый раз получаем одни и те же данные для обучения и тестирования.
data_train, data_test = train_test_split(data, test_size=0.10, random_state=42)
Затем мы формируем обучающий словарь и получаем максимальную длину обучающего предложения и общее количество обучающих данных слов.
all_training_words = [word for tokens in data_train["tokens"] for word in tokens] training_sentence_lengths = [len(tokens) for tokens in data_train["tokens"]] TRAINING_VOCAB = sorted(list(set(all_training_words))) print("%s words total, with a vocabulary size of %s" % (len(all_training_words), len(TRAINING_VOCAB))) print("Max sentence length is %s" % max(training_sentence_lengths))
Затем мы создаем словарь тестирования и получаем максимальную длину предложения тестирования и общее количество слов в данных тестирования.
all_test_words = [word for tokens in data_test[“tokens”] for word in tokens] test_sentence_lengths = [len(tokens) for tokens in data_test[“tokens”]] TEST_VOCAB = sorted(list(set(all_test_words))) print(“%s words total, with a vocabulary size of %s” % (len(all_test_words), len(TEST_VOCAB))) print(“Max sentence length is %s” % max(test_sentence_lengths))
Загрузка модели Word2Vec Новостей Google
Теперь загрузим модель Google News Word2Vec. Этот шаг может занять некоторое время. Вы можете использовать любые другие предварительно обученные вложения слов или обучить свои собственные вложения слов, если у вас достаточно данных.
word2vec_path = 'GoogleNews-vectors-negative300.bin.gz' word2vec = models.KeyedVectors.load_word2vec_format(word2vec_path, binary=True)
Последовательности Tokenize и Pad
Каждому слову присваивается целое число, и это целое число помещается в список. Поскольку все обучающие предложения должны иметь одинаковую форму ввода, мы дополняем предложения.
Например, если у нас есть предложение «Как работает последовательность текста и заполнение». Каждому слову присвоен номер. Мы предполагаем, как = 1, text = 2, to = 3, sequence = 4 и = 5, padding = 6, работает = 7. После вызоваtext_to_sequences наше предложение будет выглядеть как [1, 2, 3, 4, 5, 6, 7]. Теперь предположим, что MAX_SEQUENCE_LENGTH = 10. После заполнения наше предложение будет выглядеть как [0, 0, 0, 1, 2, 3, 4, 5, 6, 7]
Мы делаем то же самое и для данных тестирования. Для полного кода посетите.
tokenizer = Tokenizer(num_words=len(TRAINING_VOCAB), lower=True, char_level=False) tokenizer.fit_on_texts(data_train[“Text_Final”].tolist()) training_sequences = tokenizer.texts_to_sequences(data_train[“Text_Final”].tolist()) train_word_index = tokenizer.word_index print(‘Found %s unique tokens.’ % len(train_word_index)) train_cnn_data = pad_sequences(training_sequences, maxlen=MAX_SEQUENCE_LENGTH)
Теперь мы получим вложения из модели Google News Word2Vec и сохраним их в соответствии с порядковым номером, который мы присвоили каждому слову. Если нам не удалось получить вложения, мы сохраняем случайный вектор для этого слова.
train_embedding_weights = np.zeros((len(train_word_index)+1, EMBEDDING_DIM))for word,index in train_word_index.items(): train_embedding_weights[index,:] = word2vec[word] if word in word2vec else np.random.rand(EMBEDDING_DIM)print(train_embedding_weights.shape)
Определение RNN
Текст в виде последовательности передается в RNN. Матрица вложений передается в embedding_layer. Вывод слоя внедрения передается на уровень LSTM. Эта модель имеет 256 ячеек LSTM. Здесь мы игнорируем скрытые состояния всех ячеек и берем вывод только из последней ячейки LSTM. Результат передается на слой Dense, затем применяется слой Dropout и затем слой Final Dense.
model.summary () напечатает краткую сводку всех слоев с выходными формами.
def rnn(embeddings, max_sequence_length, num_words, embedding_dim, labels_index): embedding_layer = Embedding(num_words, embedding_dim, weights=[embeddings], input_length=max_sequence_length, trainable=False) sequence_input = Input(shape=(max_sequence_length,), dtype='int32') embedded_sequences = embedding_layer(sequence_input) lstm = LSTM(256)(embedded_sequences) x = Dense(128, activation='relu')(lstm) x = Dropout(0.2)(x) preds = Dense(labels_index, activation='sigmoid')(x) model = Model(sequence_input, preds) model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['acc']) model.summary() return model
Теперь выполним функцию.
model = rnn(train_embedding_weights, MAX_SEQUENCE_LENGTH, len(train_word_index)+1, EMBEDDING_DIM, len(list(label_names)))
Тренировочная РНН
Количество эпох - это количество, до которого ваша модель будет зацикливаться и изучать, а размер пакета - это количество данных, которые ваша модель будет видеть за один раз. Поскольку мы обучаемся на небольшом наборе данных всего за несколько эпох, наша модель перевалит.
num_epochs = 5 batch_size = 32 hist = model.fit(x_train, y_tr, epochs=num_epochs, validation_split=0.1, shuffle=True, batch_size=batch_size)
Тестирование модели
Вау! всего за пять итераций и небольшой набор данных мы смогли получить точность 80%.
predictions = model.predict(test_cnn_data, batch_size=1024, verbose=1) labels = [1, 0] prediction_labels=[] for p in predictions: prediction_labels.append(labels[np.argmax(p)]) sum(data_test.Label==prediction_labels)/len(prediction_labels)