Анализ настроений становится все более популярным для предприятий как способ оценки восприятия клиентами их продуктов или услуг, поскольку нереально и почти невозможно прочитать каждый обзор и каждый комментарий.
В этой статье вы узнаете, как использовать логистическую регрессию для анализа настроений для нескольких классов. Вы также узнаете, как уменьшить дисбаланс данных с помощью увеличения текста и пониженной выборки. Ссылку на набор данных можно найти здесь. Набор данных содержит отзывы от Amazon о косметическом продукте класса люкс с рейтингом от 1 до 5. Мы будем прогнозировать рейтинги, используя текстовые отзывы клиентов.
Давайте углубимся в это
Чтобы поближе взглянуть на то, как выглядят данные, мы должны сначала импортировать некоторые библиотеки.
import json #the file is json import pandas as pd #to help with data transformations import numpy as np import matplotlib.pyplot as plt #plotting
Теперь, когда мы импортировали эти библиотеки, давайте прочитаем набор данных.
#read json into a list f = open('Luxury_Beauty.json', 'r') dataset = [] #create an empty list for line in f: dataset.append(json.loads(line)) #append to list #convert list to pandas dataframe dataset = pd.DataFrame(dataset) dataset.head(3)
Для этого анализа нам нужны только общие столбцы (рейтинг), reviewText и сводка. Мы объединим столбцы ReviewText и Summary в один столбец с именем final_review.
dataset['final_review'] = dataset['reviewText'] + ' ' + dataset['summary'] #final_dataset dataset = dataset[['final_review', 'overall']] dataset.head(2)
Давайте также выясним, есть ли в наборе данных строки без отзывов. то есть пустые строки
#to get count of empty rows dataset[dataset['final_review'].isna()].count() #to get % of empty rows dataset[dataset['final_review'].isna()].count()/len(dataset)
Ух ты! 575 строк пусты. Нам нужно удалить их, так как это всего 0,1% нашего набора данных, а пустые отзывы нам не нужны.
dataset = dataset.dropna() #let's reset the index dataset.reset_index(inplace = True) del dataset['index']
Теперь, когда мы удалили пустые строки, давайте реклассифицируем набор данных всего на 3 класса: положительные (4 и 5), отрицательные (1 и 2) и нейтральные (3). Положительный будет представлен как 1, нейтральный как 0 и отрицательный как-1. После переклассификации мы проверим распределение наших оценок, потому что сильно несбалансированный набор данных может привести к систематической ошибке выборки.
#reclassify dataset['overall'] = dataset['overall'].apply(lambda x : 1 if x in (4,5) else -1 if x in (1,2) else 0) #look at the destribution of data on our target variable dataset['overall'].describe() dataset['overall'].value_counts()/len(dataset)
Набор данных явно несбалансирован и приведет к систематической ошибке выборки, если ее не исправить. Мы можем уменьшить дисбаланс, либо передискретизируя классы меньшинства с помощью увеличения текста, либо занижая выборку классов большинства. Увеличение текста используется для увеличения набора данных путем создания синтетических данных с существующим набором данных. Существуют различные методы дополнения, такие как обратный перевод, замена синонимов, перетасовка, случайная вставка, случайное удаление и т. д. Для этого доступны библиотеки, такие как NLPAUG и textaugment. Подробнее об увеличении текста
Прежде чем мы расширим наши текстовые данные, нам нужно разделить их на тестовые и обучающие. Аугментация выполняется только для данных поезда, чтобы избежать утечки данных. Из-за явного дисбаланса в нашем наборе данных мы разделим его с помощью стратифицированного случайного разделения sklearn. Стратифицированное случайное разбиение разбивает набор данных таким образом, что и обучающий, и тестовый наборы имеют близкое к тому же распределение классов, что и полный набор данных. См. код ниже, используя размер теста 0,2 или 20%
from sklearn.model_selection import StratifiedShuffleSplit split= StratifiedShuffleSplit(n_splits = 1, test_size = 0.2, random_state = 42) for train_index, test_index in split.split(dataset, dataset['overall']): strat_train_set = dataset.loc[train_index] strat_test_set = dataset.loc[test_index]
давайте проверим распределение классов тестового набора
strat_test_set['overall'].value_counts()/len(strat_test_set)
Большой!!! Он близок к оригиналу.
Теперь время для аугментации в поезде
Увеличение текста
Мы будем использовать WordNet в библиотеке textaugment.
#import necessary nltk libraries import nltk nltk.download('punkt') nltk.download('wordnet') from textaugment import Wordnet, Translate
Давайте проведем тест
t=Wordnet() #test sentence ='Hello, I have been so exhausted lately. I need to rest' t.augment(sentence)
Из приведенного выше примера ясно, что WordNet использует технику замены синонимов для расширения.
Мы продолжим добавлять отзывы с оценками, не равными 1 (неположительные оценки).
#create an empty dataframe for augmented text augmented = pd.DataFrame() #append augmented text to the empty dataframe for i in strat_train_set[strat_train_set['overall'] != 1].index: text_aug = t.augment(str(strat_train_set['final_review'][i])) augmented = augmented.append({'final_review':text_aug ,'overall': dataset['overall'][i]} , ignore_index=True) #append augmented to strat_train_set strat_train_set = strat_train_set.append(augmented)
Мы уменьшим выборку нашего набора поездов, отбросив 60% большинства классов.
#drop 60% of records with scores equal to 1 and add augmented to train set part_na = round(strat_train_set[strat_train_set['overall'] == 5].shape[0]*0.6) #count of 60% strat_train_set.head() #get indices where overall is 1 five_indices = strat_train_set[strat_train_set.overall == 1].index #select 60% at random random_indices = np.random.choice(five_indices, part_na, replace= False) #drop random indices from train set strat_train_set.drop(random_indices, inplace = True) #new class distribution strat_train_set['overall'].value_counts()/len(strat_train_set)
Теперь, когда наш набор поездов готов, пришло время для извлечения признаков.
Извлечение признаков с использованием TF-IDF
TF-IDF — это сокращение от «Частота термина — обратная частота документа». Он используется для количественной оценки того, насколько важны слова в корпусе. Он учитывает частоту слов в документе после исключения стоп-слов (естественно встречающихся слов в английском языке, таких как the, is и т. д.). Библиотека TF-IDF извлекает слова из корпуса и превращает их во взвешенные векторы.
#import TF-IDF module from sklearn.feature_extraction.text import TfidfVectorizer vectorizer = TfidfVectorizer() #we can use to limit the number of features selected using max_features #fit test data to the vectorizer to get the total number of features in test X_test =vectorizer.fit_transform(strat_test_set['final_review']) #transform train data to the shape of test X_train = vectorizer.transform(strat_train_set['final_review']) y_train = strat_train_set['overall'] y_test = strat_test_set['overall']
Построение моделей и прогнозирование
Импорт библиотек, связанных с машинным обучением
from sklearn.linear_model import LogisticRegression from sklearn.metrics import f1_score,precision_score,recall_score model = LogisticRegression(max_iter=1000) model.fit(X_train, y_train) print('Accuracy', model.score(X_test, y_test)) print('F1', f1_score(y_test,model.predict(X_test), average="macro")) print('Precision', precision_score(y_test, model.predict(X_test), average="macro")) print('Recall', recall_score(y_test, model.predict(X_test), average="macro"))
Пришло время построить нашу матрицу путаницы, чтобы мы могли увидеть, насколько хорошо наша модель предсказывает различные ярлыки или рейтинги.
from sklearn.metrics import confusion_matrix, multilabel_confusion_matrix from sklearn.metrics import ConfusionMatrixDisplay y_pred = model.predict(X_test) cm = multilabel_confusion_matrix(y_test, y_pred) f, axes = plt.subplots(2, 3, figsize=(20, 10)) plt.subplots_adjust(wspace=0.40, hspace=0.1) axes = axes.ravel() for ind in range(5): cm_display = ConfusionMatrixDisplay(cm[ind], display_labels=[0, model.classes_[ind]]).plot() cm_display.plot(ax=axes[ind], xticks_rotation=45) cm_display.ax_.set_title(name) cm_display.im_.colorbar.remove() cm_display.ax_.set_xlabel('') if i!=0: cm_display.ax_.set_ylabel('') f.text(0.4, 0.1, 'Predicted label', ha='left') f.colorbar(cm_display.im_, ax=axes)
Заключение
Точность высока и составляет 90%, но, судя по отзыву, модель правильно предсказывает только 78% своего класса. Точность, скорее всего, высока, потому что в тестовых данных больше положительных настроений, и из матрицы путаницы мы можем сказать, что модель лучше предсказывает положительные и отрицательные настроения, чем нейтральные. Возможно, это связано с тем, что в наборе данных все еще существует некоторый дисбаланс, а нейтральные настроения наименее представлены среди всех классов. Предложения по повышению точности модели могут включать использование других методов увеличения для увеличения набора нейтральных поездов, чтобы он был правильно представлен.
Ссылка на Jupyter Notebook
Ссылки
- Обоснование рекомендаций с использованием удаленных отзывов и детальных аспектов
Цзяньмо Ни, Цзячэн Ли, Джулиан Маколи
Эмпирические методы обработки естественного языка (EMNLP), 2019 г. - Увеличение данных в NLP: лучшие практики от мастера Kaggle
Shahul ES
https://neptune.ai/blog/data-augmentation-nlp