Я начал работать над проектом, связанным с НЛП, с данными твиттера, и одна из целей проекта включала классификацию настроений для каждого твита. Однако, когда я исследовал доступные ресурсы, такие как классификатор настроений NLTK и другие ресурсы, доступные в python, я был разочарован производительностью этих моделей. В лучшем случае я бы получил от 60% до 70% точности в задачах двоичной классификации (т.е. только положительный или отрицательный класс).

Поэтому я начал искать способы повысить производительность моей модели. Одним из очевидных вариантов было создание модели классификации настроений на основе глубокого обучения.

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

Я разработал модель, чтобы получить оценку настроения от 0 до 1, где 0 - очень отрицательно, а 1 - очень положительно. Это было сделано путем построения мультиклассовой модели классификации, то есть 10 классов, по одному классу для каждого дециля.

При построении модели глубокого обучения для классификации настроений необходимо 5 основных шагов:

Шаг 1. Получите данные.

Шаг 2. Создайте вложения

Шаг 3: модель архитектуры

Шаг 4: параметры модели

Шаг 5. Обучите и протестируйте модель.

Шаг 6. Запустите модель.

Я собираюсь подробно осветить каждый из вышеперечисленных шагов ниже.

Шаг 1. Получите данные

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

Набор данных «dictionary.txt» состоит из 239 233 строк предложений с индексом для каждой строки. Индекс используется для сопоставления каждого предложения с оценкой тональности в файле «labels.txt». Оценка варьируется от 0 до 1, где 0 - очень отрицательно, а 1 - очень положительно.

Приведенный ниже код считывает файлы dictionary.txt и labels.txt, объединяет оценки для каждого предложения. Этот код находится в train / utility_function.py

def read_data(path):
# read dictionary into df
df_data_sentence = pd.read_table(path + ‘dictionary.txt’)
df_data_sentence_processed = df_data_sentence[‘Phrase|Index’].str.split(‘|’, expand=True)
df_data_sentence_processed = df_data_sentence_processed.rename(columns={0: ‘Phrase’, 1: ‘phrase_ids’})
# read sentiment labels into df
df_data_sentiment = pd.read_table(path + ‘sentiment_labels.txt’)
df_data_sentiment_processed = df_data_sentiment[‘phrase ids|sentiment values’].str.split(‘|’, expand=True)
df_data_sentiment_processed = df_data_sentiment_processed.rename(columns={0: ‘phrase_ids’, 1: ‘sentiment_values’})
#combine data frames containing sentence and sentiment
df_processed_all = df_data_sentence_processed.merge(df_data_sentiment_processed, how=’inner’, on=’phrase_ids’
return df_processed_all

Данные разделены на 3 части:

  • train.csv: это основные данные, которые используются для обучения модели. Это 50% от общих данных.
  • val.csv: это набор данных проверки, который будет использоваться, чтобы гарантировать, что модель не переоснащается. Это 25% от общих данных.
  • test.csv: используется для проверки точности последующего обучения модели. Это 25% от общих данных.

Шаг 2. Создайте вложения

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

Что такое встраивание слов?

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

Как мы собираемся преобразовать каждое слово в вложения слов?

Мы собираемся использовать предварительно обученную модель встраивания слов, известную как GloVe. Для нашей модели мы собираемся представить каждое слово, используя вложение 100 измерений. Подробный код для преобразования данных во встраивание слов находится в файле train / utility_function.py. Эта функция в основном заменяет каждое слово его соответствующим вложением, выполняя поиск из предварительно обученных векторов GloVe. Иллюстрация процесса показана ниже, где каждое слово преобразуется во вложение и передается в нейронную сеть.

Приведенный ниже код используется для разделения данных на наборы train, val и test. Также соответствующие вложения для данных хранятся в переменной weight_matrix.

def load_data_all(data_dir, all_data_path,pred_path, gloveFile, first_run, load_all):
   numClasses = 10
# Load embeddings for the filtered glove list
    if load_all == True:
        weight_matrix, word_idx = uf.load_embeddings(gloveFile)
    else:
        weight_matrix, word_idx = uf.load_embeddings(filtered_glove_path)
    # create test, validation and trainng data
    all_data = uf.read_data(all_data_path)
    train_data, test_data, dev_data = uf.training_data_split(all_data, 0.8, data_dir)
train_data = train_data.reset_index()
    dev_data = dev_data.reset_index()
    test_data = test_data.reset_index()
    maxSeqLength, avg_words, sequence_length = uf.maxSeqLen(all_data)
  
# load Training data matrix
    train_x = uf.tf_data_pipeline_nltk(train_data, word_idx, weight_matrix, maxSeqLength)
    test_x = uf.tf_data_pipeline_nltk(test_data, word_idx, weight_matrix, maxSeqLength)
    val_x = uf.tf_data_pipeline_nltk(dev_data, word_idx, weight_matrix, maxSeqLength)
    # load labels data matrix
    train_y = uf.labels_matrix(train_data)
    val_y = uf.labels_matrix(dev_data)
    test_y = uf.labels_matrix(test_data)
return train_x, train_y, test_x, test_y, val_x, val_y, weight_matrix

Шаг 3: Архитектура модели

Для обучения модели мы собираемся использовать тип рекуррентной нейронной сети, известный как LSTM (Long Short Term Memory). Основное преимущество этой сети заключается в том, что она способна запоминать последовательность прошлых данных, то есть слов в нашем случае, чтобы принять решение о тональности слова.

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

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

Слои:

Слой 1: слой встраивания с размером вектора 100 и максимальной длиной каждого предложения 56.

Уровень 2: 128 ячеек двунаправленных уровней LSTM, на которых данные внедрения передаются в сеть. Мы добавляем отсев 0,2, это используется для предотвращения переобучения.

Уровень 3: сеть с плотностью 512 слоев, которая принимает входные данные от уровня LSTM. Сюда добавляется выпадение 0,5.

Уровень 4: 10-слойная плотная сеть с активацией softmax, каждый класс используется для представления категории тональности, при этом класс 1 представляет оценку тональности от 0,0 до 0,1, а класс 10 - показатель тональности от 0,9 до 1. .

Код для создания модели LSTM в Keras:

import os
import numpy as np
import keras
from keras.models import Sequential
from keras.layers import Dense
from keras.layers import Flatten
from keras.layers import LSTM
from keras.layers.embeddings import Embedding
from keras.layers import Bidirectional
from keras.preprocessing import sequence
from keras.layers import Dropout
from keras.models import model_from_json
from keras.models import load_model

def create_model_rnn(weight_matrix, max_words, EMBEDDING_DIM):
# create the model
model = Sequential()
model.add(Embedding(len(weight_matrix), EMBEDDING_DIM, weights=[weight_matrix], input_length=max_words, trainable=False))
model.add(Bidirectional(LSTM(128, dropout=0.2, recurrent_dropout=0.2)))
model.add(Dense(512, activation='relu'))
model.add(Dropout(0.50))
model.add(Dense(10, activation='softmax'))
# Adam Optimiser
model.compile(loss='categorical_crossentropy',optimizer='adam', metrics=['accuracy'])
return model

Шаг 4. Параметры модели.

Функция активации: я использовал ReLU в качестве функции активации. ReLU - это нелинейная функция активации, которая помогает моделировать сложные взаимосвязи в данных.

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

Функция потерь: мы обучим сеть выводить вероятность по 10 классам, используя кросс-энтропийную потерю, также называемую Softmax Loss. Это очень полезно для мультиклассовой классификации.

Шаг 5. Обучите и протестируйте модель

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

def train_model(model,train_x, train_y, test_x, test_y, val_x, val_y, batch_size):
# save the best model and early stopping
saveBestModel = keras.callbacks.ModelCheckpoint('../best_weight_glove_bi_100d.hdf5', monitor='val_acc', verbose=0, save_best_only=True, save_weights_only=False, mode='auto', period=1)
earlyStopping = keras.callbacks.EarlyStopping(monitor='val_loss', min_delta=0, patience=3, verbose=0, mode='auto')
# Fit the model
model.fit(train_x, train_y, batch_size=batch_size, epochs=25,validation_data=(val_x, val_y), callbacks=[saveBestModel, earlyStopping])
# Final evaluation of the model
score, acc = model.evaluate(test_x, test_y, batch_size=batch_size)
return model

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

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

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

Модель в тестовом наборе из 10 классификаций тональности дает результат с точностью 48,6%. Точность будет намного выше для двоичного набора данных 2-го класса (положительного или отрицательного).

Шаг 6: Запустите модель

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

model.save_weights("/model/best_model.h5")

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

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

def live_test(trained_model, data, word_idx):
live_list = []
live_list_np = np.zeros((56,1))
# split the sentence into its words and remove any punctuations.
tokenizer = RegexpTokenizer(r'\w+')
data_sample_list = tokenizer.tokenize(data)
labels = np.array(['1','2','3','4','5','6','7','8','9','10'], dtype = "int")
# get index for the live stage
data_index = np.array([word_idx[word.lower()] if word.lower() in word_idx else 0 for word in data_sample_list])
data_index_np = np.array(data_index)
# padded with zeros of length 56 i.e maximum length
padded_array = np.zeros(56)
padded_array[:data_index_np.shape[0]] = data_index_np
data_index_np_pad = padded_array.astype(int)
live_list.append(data_index_np_pad)
live_list_np = np.asarray(live_list)
# get score from the model
score = trained_model.predict(live_list_np, batch_size=1, verbose=0)
single_score = np.round(np.argmax(score)/10, decimals=2) # maximum of the array i.e single band
# weighted score of top 3 bands
top_3_index = np.argsort(score)[0][-3:]
top_3_scores = score[0][top_3_index]
top_3_weights = top_3_scores/np.sum(top_3_scores)
single_score_dot = np.round(np.dot(top_3_index, top_3_weights)/10, decimals = 2)
return single_score_dot, single_score

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

# Load the best model that is saved in previous step
weight_path = '/model/best_model.hdf5'
loaded_model = load_model(weight_path)
# sample sentence
data_sample = "This blog is really interesting."
result = live_test(loaded_model,data_sample, word_idx)

Полученные результаты

Давайте сравним результаты нашей модели глубокого обучения с моделью NLTK, взяв образец.

Модель LSTM: Это предложение «Отлично !! сегодня идет дождь !! » содержит негативный контекст, и наша модель может предсказать это, как показано ниже. это дает ему оценку 0,34.

Модель NLTK: то же предложение при анализе с помощью биграммной модели NLTK дает ему положительную оценку 0,74.

Ура !! На этом мы подошли к концу учебника по созданию модели классификации тональности глубокого обучения для текстовых данных.

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