Пример встраивания слов в Python - анализ тональности

Одно из основных приложений машинного обучения - анализ настроений. Анализ тональности - это оценка тона документа. Результатом анализа тональности обычно является оценка от нуля до единицы, где единица означает, что тон очень положительный, а ноль означает, что он очень отрицательный. Для торговли часто используется анализ настроений. Например, анализ настроений применяется к твитам трейдеров, чтобы оценить общее настроение рынка.

Как и следовало ожидать, анализ тональности - это проблема обработки естественного языка (NLP). НЛП - это область искусственного интеллекта, связанная с пониманием и обработкой языка. Целью данной статьи будет построение модели для получения семантического значения слов из документов в корпусе. На высоком уровне можно представить, что мы классифицируем документы со словом хорошие в них как положительные и словом плохие как отрицательный. К сожалению, проблема не так проста, поскольку перед словами может стоять не, как в случае плохо.

Код

Этого хватит, давайте погрузимся в код Python.

import numpy as np
from matplotlib import pyplot as plt
plt.style.use('dark_background')
from keras.datasets import imdb
from keras.models import Sequential
from keras.preprocessing.sequence import pad_sequences
from keras.layers import Embedding, GlobalAveragePooling1D, Dense

Аргумент num_words=10000 гарантирует, что мы сохраним только 10 000 наиболее часто встречающихся слов в обучающем наборе. Редкие слова отбрасываются, чтобы размер данных оставался управляемым.

num_words = 10000

Мы будем использовать набор данных IMDB, содержащий текст 50 000 обзоров фильмов из Internet Movie Database. Последние разделены на 25 000 обзоров для обучения и 25 000 обзоров для тестирования. Наборы для обучения и тестирования сбалансированы, что означает, что они содержат равное количество положительных и отрицательных отзывов.

old = np.load
np.load = lambda *a,**k: old(*a,**k, allow_pickle = True)
(X_train, y_train), (X_test, y_test) = imdb.load_data(num_words=num_words)
np.load = old
del(old)
print("Training entries: {}, labels: {}".format(len(X_train), len(y_train)))

Когда мы используем keras.datasets.imdb для импорта набора данных в нашу программу, он уже предварительно обработан. Другими словами, каждый пример представляет собой список целых чисел, где каждое целое число представляет определенное слово в словаре, а каждая метка представляет собой целое значение 0 или 1, где 0 - отрицательный отзыв, а 1 - положительный отзыв. Давайте поговорим о первом обзоре.

print(X_train[0])

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

word_index = imdb.get_word_index()
# The first indices are reserved
word_index = {k:(v+3) for k,v in word_index.items()} 
word_index["<PAD>"] = 0
word_index["<START>"] = 1
word_index["<UNK>"] = 2  # unknown
word_index["<UNUSED>"] = 3
reverse_word_index = dict([(value, key) for (key, value) in word_index.items()])
def decode_review(text):
    return ' '.join([reverse_word_index.get(i, '?') for i in text])

Теперь мы можем использовать функцию decode_review для отображения текста первого обзора.

decode_review(X_train[0])

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

len(X_train[0]), len(X_train[1])

Мы будем использовать функцию pad_sequence для стандартизации длин.

X_train = pad_sequences(
    X_train,
    value=word_index["<PAD>"],
    padding='post',
    maxlen=256
)
X_test = pad_sequences(
    X_test,
    value=word_index["<PAD>"],
    padding='post',
    maxlen=256
)

Давайте посмотрим на длину первых двух образцов.

len(X_train[0]), len(X_train[1])

Как уже мог догадаться настроенный читатель, слова - это категориальные признаки. Таким образом, мы не можем напрямую передать их в нейронную сеть. Хотя они уже закодированы как целые числа, если мы оставим их такими, как они есть, модель будет интерпретировать целые числа с более высокими значениями как имеющие более высокий приоритет, чем числа с более низкими значениями. Обычно эту проблему можно обойти, преобразовав массивы в векторы 0s и 1s, указывающие на вхождение слова, аналогично одной горячей кодировке, но для слов это память интенсивный. При словарном запасе из 10 000 слов нам нужно будет хранить матрицу размера num_words * num_reviews в ОЗУ.

Вложения

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

Например, предположим, что у нас есть корпус, состоящий из следующих двух предложений.

  • До скорой встречи
  • Рад снова вас видеть

Как и в наборе данных IMDB, мы можем присвоить каждому слову уникальное целое число.

[0, 1, 2, 3, 4]

[5, 1, 2, 3, 6]

Затем мы можем определить слой встраивания.

Embedding(input_dim=7, output_dim=2, input_length=5)
  • input_dim: размер словаря (т. е. количество отдельных слов) в обучающем наборе.
  • output_dim: размер векторов внедрения.
  • input_length: количество функций в образце (т. е. количество слов в каждом документе). Например, если все наши документы состоят из 1000 слов, длина ввода будет 1000.

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

+------------+------------+
|   index    |  Embedding |
+------------+------------+
|     0      | [1.2, 3.1] |
|     1      | [0.1, 4.2] |
|     2      | [1.0, 3.1] |
|     3      | [0.3, 2.1] |
|     4      | [2.2, 1.4] |
|     5      | [0.7, 1.7] |
|     6      | [4.1, 2.0] |
+------------+------------+

Скажем, у нас есть следующий двумерный вектор вложения для слова Учитель.

Мы могли бы представить себе двумерное пространство, в котором похожие слова (например, школа, репетитор) сгруппированы вместе.

В нашем примере мы используем встраиваемые векторы с 16 измерениями. Таким образом, мы можем обнаружить, что слова понравился, понравился и фантастический находятся в непосредственной близости друг от друга. Затем наша модель может научиться классифицировать обзоры, слова которых соответствуют векторам вложения, которые близки друг к другу в 16-мерном пространстве, как положительные.

model = Sequential()
model.add(Embedding(input_dim==num_words, output_dim=16, input_length=256))
model.add(GlobalAveragePooling1D())
model.add(Dense(16, activation='relu'))
model.add(Dense(1, activation='sigmoid'))
model.summary()

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

model.compile(
    optimizer='adam',
    loss='binary_crossentropy',
    metrics=['accuracy']
)

Мы откладываем десять процентов наших данных для проверки. Каждую эпоху через нейронную сеть проходит 512 отзывов перед обновлением весов.

history = model.fit(
    X_train,
    y_train,
    epochs=20,
    batch_size=512,
    validation_split=0.1,
    shuffle=True
)

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

loss = history.history['loss']
val_loss = history.history['val_loss']
epochs = range(1, len(loss) + 1)
plt.plot(epochs, loss, 'y', label='Training loss')
plt.plot(epochs, val_loss, 'r', label='Validation loss')
plt.title('Training and validation loss')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()
plt.show()

acc = history.history['acc']
val_acc = history.history['val_acc']
plt.plot(epochs, acc, 'y', label='Training acc')
plt.plot(epochs, val_acc, 'r', label='Validation acc')
plt.title('Training and validation accuracy')
plt.xlabel('Epochs')
plt.ylabel('Accuracy')
plt.legend()
plt.show()

Наконец, давайте посмотрим, насколько хорошо наша модель работает на тестовой выборке.

test_loss, test_acc = model.evaluate(X_test, y_test)
print(test_acc)

Последние мысли

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