Представлено — Мадхав Агарвал, Synerzip.

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

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

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

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

В этом посте подробно описаны некоторые из этих проблем и методов.

Общие проблемы

Некоторые из общих проблем, с которыми сталкиваются практики НЛП при анализе настроений, включают:

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

Многополярные способы выражения мыслей. Часто, выражая мнения, люди не уверены, что им нравится или не нравится. Алгоритмам анализа трудно судить о полярности таких идей, когда они выражены в виде текста. Например, в заявлении, приведенном ниже, пользователь выражает несколько мнений о человеке, которые противоположны друг другу.

«Он либо очень милый, но глупый парень, либо один из самых отвратительных лжецов»

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

Двусмысленность слов. Трудно определить настроение, связанное со словами, лишенными контекста. Например,

  • В отношении нарушителей введен ряд санкций.
  • У них есть официальные санкции на проведение этого спектакля.

В первом предложении слово «санкция» используется в отрицательном смысле, а во втором — в положительном.

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

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

Вложения слов

Вложения слов все чаще используются для решения различных задач в НЛП. Мы писали о Word Embeddings некоторое время назад. Для обучения и разработки моделей классификации можно использовать несколько вложений слов, таких как Word2Vec, GloVe и fasttext.

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

Давайте рассмотрим пример того, как анализ настроений использует предварительно обученные вложения слов, такие как Word2Vec. Мы будем использовать набор данных обзоров фильмов IMDB для нашего анализа. Встраивания Word2Vec, обученные на наборе данных Google News с размером словарного запаса в 3 миллиона слов, доступны для загрузки.

Реализация Python

Вложения Word2Vec можно импортировать с помощью пакета, как указано ниже.

from gensim.models import KeyedVectors model = KeyedVectors.load_word2vec_format('./GoogleNews-vectors-negative300/GoogleNews-vectors-negative300.bin', binary=True)

Давайте загрузим обзоры с помощью фрейма данных pandas, выполним некоторую предварительную обработку (например, удаление специальных символов, нижнего регистра и т. д.) и разместим их в словах.

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

import pandas as pd df = pd.read_csv("labeledTrainData.tsv", sep = "\t", error_bad_lines=False) from bs4 import BeautifulSoup import re def get_tokens(reviews): token_list = [] for line in reviews: text = BeautifulSoup(line, "lxml").get_text() sent = re.sub("[^a-zA-Z]"," ", text) token_list.append(sent.lower().split()) return token_list tokenized_reviews=get_tokens(df['review'])

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

Преобразование функций документа

import numpy as np def create_feature_vector(words, model, index2word_set, nwords): featureVec = np.zeros(nwords, dtype="float32") for word in words: if word in index2word_set: featureVec = np.add(featureVec,model[word]) featureVec=np.divide(featureVec,nwords) return featureVec def make_vectors(reviews, model, dim): nreviews=len(reviews) index2word_set=set(model.index2word) feature_vec=np.zeros((nreviews, dim), dtype="float32") counter=0 for review in reviews: feature_vec[counter]=create_feature_vector(review, model, index2word_set, dim) counter=counter+1 return feature_vec vector_dimension=300 train_vectors = make_vectors(tokenized_reviews, model, vector_dimension)

Как только наши обзоры будут преобразованы в векторы признаков, давайте разделим наши данные на два набора — обучение и проверку. Наконец, мы обучаем модель классификации по этим функциям, чтобы получить анализатор настроений.

Y = df["sentiment"] from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test = train_test_split(train_vectors, Y, test_size = 0.2, random_state = 13, stratify = Y) from sklearn.linear_model import LogisticRegression logistic_regr = LogisticRegression() logistic_regr.fit(X_train, y_train) predict = logistic_regr.predict(X_test) print(logistic_regr.score(X_train, y_train)) print(logistic_regr.score(X_test, y_test))

Трансферное обучение

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

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

Возникает вопрос — можно ли использовать что-то вроде ResNet для текста?

Момент ImageNet НЛП

Тонкая настройка универсальной языковой модели (ULMFiT) — это подход к трансфертному обучению, разработанный Джерми Ховардом и Себастьяном Рудером из fastai. Вот краткое изложение этого подхода:

  1. Нейронная сеть языковой модели обучается на большом наборе данных английского языка. Эта сеть может предсказывать следующее слово, просматривая предыдущий контекст слов в документе.
  2. Чтобы эффективно предсказать следующее слово, эта нейронная сеть должна быть достаточно умной, чтобы понимать семантику языка.
  3. Затем та же сеть постепенно обучается / настраивается путем размораживания слоев нейронной сети в наборе данных классификации (например, наборе данных фильмов).
  4. Теперь у нас есть сеть, которая понимает семантику пользовательского обзора фильмов. Возможно, мы сможем использовать его знания для других задач, таких как классификация.
  5. Следовательно, использование весов, полученных из языковой модели NN с классификатором наверху, создает новую нейронную сеть. Затем это переносит веса предыдущей сети в эту новую сеть.

Давайте посмотрим, как мы можем это реализовать. Мы будем использовать библиотеку fastai v1 и бесплатный графический процессор, предоставленный Google Collaboratory.

Реализация Python

Импортируйте классы fastai и загрузите данные (нет необходимости устанавливать пакет fastai v1, если вы используете коллаб)

from fastai.text import * path = untar_data(URLs.IMDB) path.ls() Out[2]: [PosixPath('/root/.fastai/data/imdb/tmp_lm'), PosixPath('/root/.fastai/data/imdb/unsup'), PosixPath('/root/.fastai/data/imdb/train'), PosixPath('/root/.fastai/data/imdb/tmp_clas'), PosixPath('/root/.fastai/data/imdb/imdb.vocab'), PosixPath('/root/.fastai/data/imdb/README'), PosixPath('/root/.fastai/data/imdb/test')]

Fastai v1 предоставляет простой в использовании API для выполнения таких действий, как предварительная обработка, разделение данных на поезд, проверку и набор тестов, создание пакетов данных и т. д.

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

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

Следующий код считывает текстовые данные из файлов, токенизирует их, а затем цифрует. Он автоматически создает метки (в нашем случае следующее встречающееся слово) и создает объект набора данных, инкапсулирующий загрузчик данных обучения и проверки.

Мы будем использовать нейронную сеть, обученную на наборе данных Википедии 103, и настроим ее на наш набор данных обзоров фильмов.

Создание обучающего и тестового набора из входных данных для языкового моделирования

bs=48 data_lm = (TextList.from_folder(path) #Inputs: all the text files in path .filter_by_folder(include=['train', 'test', 'unsup']) #We may have other temp folders that contain text files so we only keep what's in train and test .random_split_by_pct(0.1) #We randomly split and keep 10% (10,000 reviews) for validation .label_for_lm() #We want to do a language model so we label accordingly .databunch(bs=bs)) learn = language_model_learner(data_lm, AWD_LSTM, drop_mult=0.3)

Мы используем метод поиска скорости обучения Fastai, чтобы получить оптимальную скорость обучения для обучения сети с обзорами фильмов. Глядя на график, выберите значение скорости обучения. Это значение находится непосредственно перед значением LR, которое дает наименьшее значение потерь. В нашем случае это будет 1e-2.

learn.lr_find() learn.recorder.plot() learn.fit_one_cycle(1, 1e-2, moms=(0.8,0.7))

Общее время: 1:29:54

Запуск одной эпохи в коллабе занимает около 90 минут. Из-за нехватки времени мы запустили одну эпоху. С мощными графическими процессорами можно запускать несколько эпох для достижения лучших результатов. Оригинальная тетрадь fastai выполнена в 10 эпохах.

Что такое fit_one_cycle и мамы?

Метод fit_one_cycle настраивает модель, изменяя скорость обучения в цикле. В первой половине цикла скорость обучения постепенно увеличивается, достигая максимального значения. Во второй половине скорость обучения снижается до 1/10 или 1/100 от максимального значения.

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

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

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

Давайте теперь посмотрим, как сеть справляется с задачами языкового моделирования. Мы создадим последовательность текста, предоставив несколько входных слов.

Генерация текста

TEXT = "it was the worse movie i" N_WORDS = 40 N_SENTENCES = 2 print("\n".join(learn.predict(TEXT, N_WORDS, temperature=0.75) for _ in range(N_SENTENCES))) it was the worse movie i 've ever seen ! it was so bad it was so bad , i remembered that my parents was so totally disgusted by the stupid scene where someone ( Steve Martin ) visits the nice guy and they it was the worse movie i have ever seen . you do n't know how to say this movie was awful and i was really shocked at the poor acting . i have to say that this movie made me think that the movie should be Even when trained on a single epoch the network produces sentences of reasonable quality. Let's save the model encoder part which contains the hidden state. learn.save_encoder('fine_tuned_enc')

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

Сгенерируйте обучающий и тестовый набор из входных данных для классификации

path = untar_data(URLs.IMDB) data_clas = (TextList.from_folder(path, vocab=data_lm.vocab) #grab all the text files in path .split_by_folder(valid='test') #split by train and valid folder (that only keeps 'train' and 'test' so no need to filter) .label_from_folder(classes=['neg', 'pos']) #label them all with their folders .databunch(bs=bs))

Вы можете просмотреть данные одного пакета, используя:

data_clas.show_batch()

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

learn = text_classifier_learner(data_clas, AWD_LSTM, drop_mult=0.5) learn.load_encoder('fine_tuned_enc')

Настройте последние слои сети, а затем постепенно разморозьте и обучите всю сеть:

learn.fit_one_cycle(1, 2e-2, moms=(0.8,0.7))

Общее время: 11:46

learn.freeze_to(-2) learn.fit_one_cycle(1, slice(1e-2/(2.6**4),1e-2), moms=(0.8,0.7))

Время завершения: 13:25

learn.freeze_to(-3) learn.fit_one_cycle(1, slice(5e-3/(2.6**4),5e-3), moms=(0.8,0.7))

Общее время: 20:22

learn.unfreeze() learn.fit_one_cycle(2, slice(1e-3/(2.6**4),1e-3), moms=(0.8,0.7))

Это примерно 94% точности на проверочном наборе — улучшение на 7% по сравнению с использованием только векторов слов.

Вывод

Техника ULMFiT обеспечивает надежный способ использования трансферного обучения в задачах NLP и является более разумным подходом, чем использование только вложений Word.

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

Благодаря успеху ULMfiT в fast.ai продолжаются усилия по созданию языковых моделей и для других языков.

дальнейшее чтение

  1. Блокнот fast.ai Jupyter, используемый в качестве эталона для запуска кода, можно найти.
  2. Политика 1cycle — https://sgugger.github.io/the-1cycle-policy.html
  3. Поиск хорошей скорости обучения — https://sgugger.github.io/how-do-you-find-a-good-learning-rate.html

Первоначально опубликовано на https://www.synerzip.com.