Amazon рассматривает анализ настроений с использованием НЛП и создание механизма классификации настроений с использованием наивного Байеса
Понимание бизнеса
С растущей тенденцией к оцифровке и преобладанием мобильных телефонов и доступа в Интернет все больше потребителей присутствуют в Интернете, и их мнение имеет большое значение для любой компании, основанной на продуктах, особенно для предприятий B2C. Отрасли пытаются точно настроить свои стратегии в соответствии с потребностями потребителей, поскольку потребители оставляют некоторые намеки на свой выбор во время своего присутствия в Интернете.
Всякий раз, когда мы работаем над разработкой рыночных стратегий или над разработкой продукта, нам нужно обратить внимание на несколько стандартных вещей, которые можно разработать, проанализировав набор данных, с которым вы будете работать.
- Анализируя настроение отзывов, вы можете найти характеристики телефонов, которые вызвали положительные/отрицательные чувства. Это поможет компаниям включить или улучшить эти конкретные функции при разработке нового продукта. Если данные относятся к брендам конкурентов, компания выиграет, не повторив той же ошибки при разработке продукта, что и их конкурент.
- Компании могут эффективно разрабатывать свои рекламные кампании, выделяя функции, о которых больше всего говорят потребители.
- Сравнение цен конкурентов и их доли рынка поможет компаниям определить цену своей продукции.
- Можно предположить, что если количество отзывов о конкретном бренде велико, то и количество людей, покупающих телефоны этого бренда, также велико. Это поможет компаниям оценить долю рынка своих конкурентов.
- Прежде чем купить какой-либо продукт, мы все смотрим на аналогичные товары разных брендов. Эти данные помогут компаниям узнать своих основных конкурентов на рынке.
Постановка задачи
Предположим, ваш клиент — производитель мобильных устройств из США, который вышел на рынок три года назад. Поскольку они являются новичком в этом секторе, они хотят понять своих конкурентов и предпочтения своих пользователей, чтобы соответствующим образом разрабатывать свои стратегии. Они хотят настроить маркетинговые стратегии, чтобы повысить ценность своего бренда, предоставить клиентам функции, которые приносят наибольшую пользу, и сократить разрыв между спросом и предложением. Их цель состоит в том, чтобы увеличить долю рынка, а также стоимость бренда.
Предположим, что к вам как к поставщику услуг по анализу данных обратился производитель мобильных телефонов. Они хотят, чтобы вы предоставили им важную информацию об индустрии мобильных телефонов, чтобы помочь им достичь своей цели. Их цель — оптимально разработать новый продукт и создать маркетинговые стратегии.
Бизнес-цель
- Часть 1. Получение информации о бизнесе, полезной для разработки продукта и маркетинга.
- Часть 2: Создание механизма классификации настроений.
Пройденные шаги
Шаг 1: Предварительная обработка данных
Шаг 2: ЭДА
Шаг 3: Текстовая аналитика
Шаг 4: Создание механизма классификации настроений.
Словарь данных
Вы можете найти файлы данных, нажав здесь.
- Данные телефона в виде файла .csv:содержит информацию о действиях потребителей.
- общая: общая оценка конкретного продукта, данная пользователем.
- Подтверждено: является ли пользователь проверенным рецензентом или нет.
- reviewerID: идентификатор рецензента, например. A2SUAM1J3GNN3B
- asin: идентификатор продукта, например. 0000013714. ASIN означает стандартный идентификационный номер Amazon. Это 10-значный буквенно-цифровой уникальный идентификатор, который присваивается Amazon.com и его партнерами. Он в основном используется для идентификации продукта в их каталоге продуктов.
- стиль: информация о физических характеристиках, таких как цвет.
- reviewerName: имя рецензента.
- reviewText:отзыв, предоставленный рецензентом.
- резюме:резюме обзора
- unixReviewTime: время проверки (время Unix).
- голосование: голосование за или против отзыва. Отзыв с большим количеством голосов может иметь более высокое значение.
- изображение: изображение типа .png или .jpg и т. п., предоставленное рецензентом.
- review_sentiment: настроенное заранее настроение отзыва, которое может быть как положительным, так и отрицательным. Метки не будут доступны для данных в реальном времени, и для такой классификации необходимо построить модель.
2. Метаданные телефона в виде заархивированного файла .json.Эти данные содержат информацию о продукте и не зависят от действий потребителя/рецензента и включают описание, цену, рейтинг продаж, информацию о бренде и ссылки для совместных покупок. , и т. д.
- Категория.Список категорий, к которым относится продукт, например ["Сотовые телефоны и аксессуары", "Сотовые телефоны"].
- tech1: технические характеристики и технология продукта, например Phablet (гибрид телефона и планшета).
- tech2: содержит данные, аналогичные данным tech1.
- описание: краткое описание продукта.
- fit: столбец, в котором нет значений для набора данных мобильного телефона.
- title: название продукта.
- also_buy: идентификаторы продуктов, которые обычно покупаются вместе с конкретным продуктом.
- изображение: изображение продукта, сделанное поставщиком.
- brand: торговая марка продукта.
- функция. Особенности продукта, включая такие характеристики, как 4G/3G, язык, поддерживаемые приложения и т. д.
- рейтинг:рейтинг продукта, присвоенный Amazon на основе различных параметров в разных сегментах. Это метрика, которая объясняет взаимосвязь между продуктами в категории на основе их эффективности продаж. Рейтинг продаж обновляется ежечасно, он может варьироваться от 1 до более 1 миллиона и зависит от сезонности, а его алгоритм остается нераскрытым.
- also_view: товары, которые также просматриваются при поиске этого товара.
- детали:содержит детали с точки зрения доставки, такие как размер продукта, вес доставки и т. д.
- main_cat: основная категория, к которой относится продукт, например, «Вся электроника».
- similar_item:сопутствующие товары.
- price: цена товара.
- asin:идентификатор продукта, например, 0000031852
3. pos_words.txt: это текстовый файл, содержащий набор положительных слов. Этот файл может помочь вам извлечь некоторые функции из текста обзора. Есть несколько источников, доступных в Интернете, которые дают вам корпус положительных и отрицательных слов. Вы также можете настроить его на основе вашего приложения.
4. neg_words.txt:Подобно корпусу положительных слов, этот текстовый файл содержит список слов, которые могут быть частью отрицательных отзывов пользователей.
5. Stop_words_long.txt: обычно мы можем удалить стоп-слова с помощью библиотеки NLTK. Одним из недостатков этого является то, что слова отрицания, такие как ни, не и никогда, не считаются стоп-словами. Удаление этих слов, возможно, не повлияет на классификацию спама/любимой почты, но определенно повлияет на классификацию тональности. Поэтому они предоставили нам индивидуальный список стоп-слов, которые вы можете использовать для анализа.
Чтение и понимание данных
# Reading the json version of Meta Data (it shows how to read json file in colab notebooks) df_meta # Reading the Cell Phones and Accessories data df_celldata # Reading the Phone data final (it's the consolidated & structured version of df_meta & df_celldata. Hence we will not use this data further) df_phonedata
Статистика:
- df_phonedata кажется самым сложным и кульминацией как df_meta, так и df_celldata. Но df_celldata, кажется, имеет более крупные записи.
- Сотовые данные и данные телефона, по-видимому, имеют схожие атрибуты, но различаются по форме и содержанию. Следовательно, необходимо дополнительно анализировать и понимать соответствующие столбцы из обоих.
- Это также может включать объединение двух из них с уникальными столбцами после анализа. Давайте посмотрим.
Давайте начнем с объединения df_meta и df_celldata, поскольку они несут много информации для нашего анализа.
# Merging df_meta and df_celldata using the unique asin column values merge_df = df_meta.merge(df_celldata, on = "asin") merge_df.shape (1129035, 29) merge_df.info() <class 'pandas.core.frame.DataFrame'> Int64Index: 1129035 entries, 0 to 1129034 Data columns (total 29 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 category 1129035 non-null object 1 tech1 1129035 non-null object 2 description 1129035 non-null object 3 fit 1129035 non-null object 4 title 1129035 non-null object 5 also_buy 1129035 non-null object 6 image_x 1129035 non-null object 7 tech2 1129035 non-null object 8 brand 1129035 non-null object 9 feature 1129035 non-null object 10 rank 1129035 non-null object 11 also_view 1129035 non-null object 12 details 1129035 non-null object 13 main_cat 1129035 non-null object 14 similar_item 1129035 non-null object 15 date 1129035 non-null object 16 price 1129035 non-null object 17 asin 1129035 non-null object 18 overall 1129035 non-null float64 19 verified 1129035 non-null bool 20 reviewerID 1129035 non-null object 21 style 605459 non-null object 22 reviewerName 1128879 non-null object 23 reviewText 1128267 non-null object 24 summary 1128510 non-null object 25 unixReviewTime 1129035 non-null int64 26 vote 92176 non-null object 27 image_y 27106 non-null object 28 review_sentiment 1129035 non-null object dtypes: bool(1), float64(1), int64(1), object(26) memory usage: 250.9+ MB merge_df.describe() overall unixReviewTime count 1.129035e+06 1.129035e+06 mean 4.221165e+00 1.440369e+09 std 1.232076e+00 4.579979e+07 min 1.000000e+00 1.035331e+09 25% 4.000000e+00 1.416355e+09 50% 5.000000e+00 1.444349e+09 75% 5.000000e+00 1.470442e+09 max 5.000000e+00 1.538438e+09
Статистика:
- Получив сводную картину, мы видим, что данные содержат много шума, который необходимо предварительно обработать.
- Как только данные очищены и готовы, мы можем начать наш исследовательский анализ данных и обработку текста.
Шаг 1: Краткий обзор предварительной обработки данных
Мы начинаем с определения нежелательных функций и тех, которые имеют большие пропущенные значения. Как только они будут идентифицированы, мы собираемся отбросить их, так как они не будут иметь большого значения в нашем дальнейшем анализе.
# Dropping some unwanted and noisy columns merge_df.drop(['category', 'description', 'image_x', 'rank', 'details', 'similar_item', 'date', 'image_y'], axis = 1, inplace = True) # Checking missing values columns import missingno as msno msno.bar(merge_df) # Checking missing values percentages # Checking exact Null Values def null_values(merge_df): return round((merge_df.isnull().sum()/len(merge_df)*100).sort_values(ascending = False),2) null_values(merge_df) vote 91.84 style 46.37 reviewText 0.07 summary 0.05 reviewerName 0.01 tech1 0.00 overall 0.00 unixReviewTime 0.00 reviewerID 0.00 verified 0.00 asin 0.00 fit 0.00 price 0.00 main_cat 0.00 also_view 0.00 feature 0.00 brand 0.00 tech2 0.00 also_buy 0.00 title 0.00 review_sentiment 0.00 dtype: float64 # Dropping vote and style as it has more than 45% missing values merge_df.drop(['vote','style'], axis = 1, inplace = True)
Остальные столбцы можно разобрать для дальнейшего понимания и соответствующих действий. Некоторые из них могут быть вменены с помощью агрегированных значений или с использованием 0, а некоторые могут быть преобразованы. Самое главное, столбцы с очень меньшим количеством отсутствующих значений можно оставить как есть.
# Imputing mode values in the reviewerName column. merge_df['reviewerName'].fillna(merge_df['reviewerName'].mode()[0], axis = 0, inplace = True) # Rechcking the remaing missing values reviewText 0.07 summary 0.05 tech1 0.00 asin 0.00 unixReviewTime 0.00 reviewerName 0.00 reviewerID 0.00 verified 0.00 overall 0.00 price 0.00 fit 0.00 main_cat 0.00 also_view 0.00 feature 0.00 brand 0.00 tech2 0.00 also_buy 0.00 title 0.00 review_sentiment 0.00 dtype: float64 # Dissecting Summary Column: merge_df.summary.unique() # Dissecting reviewText Column: merge_df.reviewText.unique() # Insights: # summary and reviewText seems to be in a string format with a small percentage of missing values, hence we will leave it as it is. # Converting unix review time to date-time format & then dropping it #Transforming unixReview time to date time format from datetime import datetime, timedelta merge_df['Date&Time'] = merge_df['unixReviewTime'].apply(lambda d: (datetime.fromtimestamp(d) - timedelta(hours=2)).strftime('%Y-%m-%d')) # Dropping unixReviewTime merge_df.drop('unixReviewTime', axis = 1, inplace = True) # Dissecting tech columns merge_df["tech2"].unique() # or merge_df.loc[:50,"tech2"] # Dropping tech1 and tech2 as it has more than 45% missing values merge_df.drop(['tech1','tech2'], axis = 1, inplace = True) # Dissecting fit column merge_df["fit"].unique() # or merge_df.loc[:10,"fit"] # Dropping fit as it has more than 45% missing values merge_df.drop('fit', axis = 1, inplace = True) Insights: Nothing could be understood by exploring tech columns hence dropping them both
Кроме того, мы подбираем набор данных для анализа нашей конкретной цели, которая называется Сотовые телефоны и аксессуары. Затем следуют еще несколько шагов предварительной обработки данных, необходимых после подмножества, которые можно найти в подробностях по ссылке блокнота jupyter.
Понимание данных (числовой и категориальный анализ)
# Check the summary for the numeric columns merge_df.describe() price ratings year month count 1.045517e+06 1.045517e+06 1.045517e+06 1.045517e+06 mean 1.465343e+01 4.218868e+00 2.015127e+03 6.453106e+00 std 3.204665e+01 1.233017e+00 1.487766e+00 3.512060e+00 min 1.000000e-02 1.000000e+00 2.002000e+03 1.000000e+00 25% 8.990000e+00 4.000000e+00 2.014000e+03 3.000000e+00 50% 9.990000e+00 5.000000e+00 2.015000e+03 7.000000e+00 75% 9.990000e+00 5.000000e+00 2.016000e+03 1.000000e+01 max 9.999900e+02 5.000000e+00 2.018000e+03 1.200000e+01
Инсайты
- Поскольку мы заменили почти 45% нулевых значений цены на 9,99, это стало нашей средней ценой для продуктов, перечисленных в разделе «Сотовые телефоны и аксессуары». Кроме того, существует огромный разброс данных, поскольку значение стандартного отклонения больше среднего.
- Большинство оценок положительные, т.е. 4 и 5.
- Наибольшее количество отзывов получено в 2015 и 2016 годах.
# Checking the ratio of Actual Positive and Actual Neative labels under review_sentiment in the dataframe. merge_df.review_sentiment.value_counts(normalize = True)*100 POSITIVE 78.406855 NEGATIVE 21.593145 Name: review_sentiment, dtype: float64
Шаг 2: ЭДА
Идея для выполнения EDA:
- Использование TextBlob для вычисления полярности тональности, которая находится в диапазоне [-1,1], где 1 означает позитивную тональность, а -1 — негативную тональность.
- Создание новой функции для продолжительности обзора, чтобы просмотреть их распространение.
- Создание новой функции подсчета слов в обзоре для просмотра их распределения
# Creating a function to get the polarity of the sentiment in the range of -1 and 1 from textblob import TextBlob def GetPolarity(text): try: return TextBlob(text).sentiment.polarity except: return None #Create a new columns ‘Polarity’ merge_df['polarity'] = merge_df['reviewText'].apply(GetPolarity) # Creating a function to get the TB Scores. def GetTBScore(score): if score < 0: return 'Negative' elif score == 0: return 'Neutral' else: return 'Positive' #Create a new columns 'TB_analysis' merge_df['TB_score'] = merge_df['polarity'].apply(GetTBScore) # Creating review length and review word count column. merge_df['review_len'] = merge_df['reviewText'].astype(str).apply(len) merge_df['word_count'] = merge_df['reviewText'].apply(lambda x: len(str(x).split())) merge_df.head() # Let's understand how the sentiment polarity score works, for that we randomly select 5 reviews with the highest sentiment polarity score (1): print('5 random reviews with the highest positive sentiment polarity: \n') cl = merge_df.loc[merge_df.polarity == 1, ['reviewText']].sample(5).values for c in cl: print(c[0]) 5 random reviews with the highest positive sentiment polarity: Excellent, durable These work well, and they're the perfect length! Works perfect Awesome! Perfect for your LG g vista # Let's understand how the sentiment polarity score works, for that we randomly select 5 reviews with the most neutral sentiment polarity score (zero): print('5 random reviews with the most neutral sentiment(zero) polarity: \n') cl = merge_df.loc[merge_df.polarity == 0, ['reviewText']].sample(5).values for c in cl: print(c[0]) 5 random reviews with the most neutral sentiment(zero) polarity: i like it didnt work as promised Never had a problem with these screen protectors. Works as described work, but don't show correct conpacity Saves your phone # Then randomly select 5 reviews with the most negative sentiment polarity score (zero): print('5 reviews with the most negative polarity: \n') cl = merge_df.loc[merge_df.polarity == -1.0, ['reviewText']].sample(5).values for c in cl: print(c[0]) 5 reviews with the most negative polarity: Bulky but protects like it should. Clip is crap! Terrible reproduction Doesn't hold the phone Horrible. Just horrible Horrible Son Loves it!! Still using, no issues!! Has been dropped REPEATEDLY.......PROTECTS LIKE CRAZY!!!
Статистика:
Вы можете точно увидеть, что предложенные случайные обзоры соответствуют нашим полярностям, определенным с помощью TextBlob.
Одномерный анализ
# importing necessary libraries import seaborn as sns import matplotlib import matplotlib.pyplot as plt matplotlib.rcParams['figure.figsize'] = (10.0, 6.0) import plotly.graph_objs as go #import plotly.plotly as py import plotly.figure_factory as ff import plotly.express as px from plotly.offline import iplot import cufflinks as cf cf.go_offline() cf.set_config_file(world_readable=True, theme='pearl', offline=False) %matplotlib inline # Review Sentiment Polarity distribution sns.set_style("dark") plt.style.use("ggplot") plt.figure(figsize=[12,6]) sns.distplot(merge_df['polarity'], rug = True, color = 'royalblue').set_title('Sentiment Polarity Distribution') plt.show()
Инсайты
Подавляющее большинство оценок полярности настроений больше нуля, что означает, что большинство из них являются положительными отзывами.
# Review Text Length distribution sns.set_style("dark") plt.style.use("ggplot") plt.figure(figsize=[12,6]) sns.distplot(merge_df['review_len'], rug = True, color = 'royalblue').set_title('Review Text Length Distribution') plt.show()
Инсайты
Большинство обзоров имеют длину текста от 500 до 1000.
# Review Word Count Distribution sns.set_style("dark") plt.style.use("ggplot") plt.figure(figsize=[12,6]) sns.distplot(merge_df['word_count'], rug = True, color = 'royalblue').set_title('Review Word Count Distribution') plt.show()
Инсайты
Каждый обзор содержит примерно от 50 до 200 слов.
# Review Price Distribution sns.set_style("dark") plt.style.use("ggplot") plt.figure(figsize=[12,6]) sns.distplot(merge_df['price'], rug = True, color = 'royalblue').set_title('Review Price Distribution') plt.show()
Инсайты
Большинство перечисленных продуктов, т.е. сотовые телефоны и аксессуары, стоят от 10 до 150 долларов.
# Review Rating Count Distribution sns.set_theme(style="darkgrid") plt.figure(figsize=[12,6]) sns.countplot(merge_df['ratings'], palette = 'inferno') plt.title('Rating Count Distribution', fontdict={'fontsize': 20, 'fontweight': 5, 'color': 'Green'}) plt.show()
Инсайты
- Большинство продуктов получили положительные оценки, то есть 4 или 5.
# Review TB_Score count percentage wise plt.figure(figsize=[12,6]) (merge_df.TB_score.value_counts(normalize = True)*100)[:10].plot.bar() plt.title('TB_Score Count Distribution', fontdict={'fontsize': 20, 'fontweight': 5, 'color': 'Green'}) plt.show()
Инсайты
- Как видно, большинство отзывов относятся к категории положительных.
# Review verified reviews percentage wise plt.figure(figsize=[12,6]) (merge_df.verified.value_counts(normalize = True)*100)[:10].plot.bar() plt.title('Verified Reviews Count Distribution', fontdict={'fontsize': 20, 'fontweight': 5, 'color': 'Green'}) plt.show()
Инсайты
Из всех полученных отзывов более 80% подтверждены на сайте.
# Distribution of brands listed on Amazon having more than 5000 reviews plt.figure(figsize=[18,8]) brand_counts = merge_df.groupby('brand').count()['reviewerID'].sort_values(ascending=False) brand_counts[brand_counts > 5000].plot.bar() plt.title('Brands listed on Amazon having more than 5000 reviews', fontdict={'fontsize': 20, 'fontweight': 5, 'color': 'Green'}) plt.show()
Инсайты
Samsung, Spigen, OtterBox, Generic, Anker и Motorola являются брендами-предшественниками на веб-сайте.
# Distribution of reviewers on Amazon having more than 500 reviews plt.figure(figsize=[18,8]) reviewer_count = merge_df.groupby('reviewerName').count()['reviewerID'].sort_values(ascending=False) reviewer_count[reviewer_count > 500].plot.bar() plt.title('Reviewers on Amazon having more than 500 reviews', fontdict={'fontsize': 20, 'fontweight': 5, 'color': 'Green'}) plt.show()
Инсайты
Помимо первых двух, Джон, Крис и Майк являются рецензентами с наибольшим количеством отзывов на веб-сайте.
stop_words = [line.rstrip('\n') for line in open('/content/drive/My Drive/sentiment_analysis/stop_words_long.txt')] # Top 20 unigrams distribution "before" removing stop words from nltk.tokenize import word_tokenize from nltk.corpus import stopwords from sklearn.feature_extraction.text import CountVectorizer pd.set_option('max_colwidth', 100) def get_top_n_words(corpus, n=None): vec = CountVectorizer().fit(corpus) bag_of_words = vec.transform(corpus) sum_words = bag_of_words.sum(axis=0) words_freq = [(word, sum_words[0, idx]) for word, idx in vec.vocabulary_.items()] words_freq =sorted(words_freq, key = lambda x: x[1], reverse=True) return words_freq[:n] common_words = get_top_n_words(merge_df['reviewText'].astype('U').values, 20) for word, freq in common_words: print(word, freq) df1 = pd.DataFrame(common_words, columns = ['reviewText' , 'count']) plt.figure(figsize=[18,8]) df1.groupby('reviewText').sum()['count'].sort_values(ascending=False).plot.bar() plt.title('Top 20 unigram words in review "before" removing stop words', fontdict={'fontsize': 20, 'fontweight': 5, 'color': 'Green'}) plt.show() the 2837750 it 1694863 and 1474004 to 1287563 is 918685 this 835847 my 742303 for 710246 phone 670016 of 668660 case 609319 on 529213 that 528426 in 520079 with 492427 but 430203 you 426257 not 411769 have 377421 was 325305
from wordcloud import WordCloud text = " ".join(review for review in df1.reviewText) wordcloud = WordCloud(max_font_size=50, max_words=100, background_color="white", stopwords=stop_words).generate(text) plt.figure(figsize=[15,10]) plt.imshow(wordcloud, interpolation="bilinear") plt.axis("off") plt.show();
# Top 20 unigrams distribution "after" removing stop words def get_top_n_words(corpus, n=None): vec = CountVectorizer(stop_words = 'english').fit(corpus) bag_of_words = vec.transform(corpus) sum_words = bag_of_words.sum(axis=0) words_freq = [(word, sum_words[0, idx]) for word, idx in vec.vocabulary_.items()] words_freq =sorted(words_freq, key = lambda x: x[1], reverse=True) return words_freq[:n] common_words = get_top_n_words(merge_df['reviewText'].apply(lambda x: np.str_(x)), 20) for word, freq in common_words: print(word, freq) df1 = pd.DataFrame(common_words, columns = ['reviewText' , 'count']) plt.figure(figsize=[18,8]) df1.groupby('reviewText').sum()['count'].sort_values(ascending=False).plot.bar() plt.title('Top 20 unigram words in review "after" removing stop words', fontdict={'fontsize': 20, 'fontweight': 5, 'color': 'Green'}) plt.show() phone 670016 case 609319 great 304849 screen 250315 good 239673 like 227181 just 182254 product 179609 use 165352 love 135928 works 130003 nice 124450 really 121383 iphone 120527 protector 116417 battery 112485 charge 106192 does 105446 fit 105378 time 105058
from wordcloud import WordCloud text = " ".join(review for review in df1.reviewText) wordcloud = WordCloud(max_font_size=50, max_words=100, background_color="white", stopwords=stop_words).generate(text) plt.figure(figsize=[15,10]) plt.imshow(wordcloud, interpolation="bilinear") plt.axis("off") plt.show();
Инсайты
Игнорирование стоп-слов с помощью функции Countvector помогает нам определить отдельные слова для нашего анализа.
# Top 20 bigrams distribution "before" removing stop words def get_top_n_words(corpus, n=None): vec = CountVectorizer(ngram_range=(2, 2)).fit(corpus) bag_of_words = vec.transform(corpus) sum_words = bag_of_words.sum(axis=0) words_freq = [(word, sum_words[0, idx]) for word, idx in vec.vocabulary_.items()] words_freq =sorted(words_freq, key = lambda x: x[1], reverse=True) return words_freq[:n] common_words = get_top_n_words(merge_df['reviewText'].apply(lambda x: np.str_(x)), 20) for word, freq in common_words: print(word, freq) df1 = pd.DataFrame(common_words, columns = ['reviewText' , 'count']) plt.figure(figsize=[18,8]) df1.groupby('reviewText').sum()['count'].sort_values(ascending=False).plot.bar() plt.title('Top 20 bigram words in review "before" removing stop words', fontdict={'fontsize': 20, 'fontweight': 5, 'color': 'Green'}) plt.show() the phone 225431 of the 209247 on the 164581 the case 156248 my phone 150240 it is 148639 this case 137482 in the 124266 for the 121788 this is 116948 and the 109934 and it 108450 with the 104141 for my 95523 to the 94894 if you 94280 the screen 87779 screen protector 84057 it was 75164 easy to 70398
from wordcloud import WordCloud text = " ".join(review for review in df1.reviewText) wordcloud = WordCloud(max_font_size=50, max_words=100, background_color="white", stopwords=stop_words).generate(text) plt.figure(figsize=[15,10]) plt.imshow(wordcloud, interpolation="bilinear") plt.axis("off") plt.show();
# Top 20 bigrams distribution "after" removing stop words def get_top_n_words(corpus, n=None): vec = CountVectorizer(ngram_range=(2, 2), stop_words='english').fit(corpus) bag_of_words = vec.transform(corpus) sum_words = bag_of_words.sum(axis=0) words_freq = [(word, sum_words[0, idx]) for word, idx in vec.vocabulary_.items()] words_freq =sorted(words_freq, key = lambda x: x[1], reverse=True) return words_freq[:n] common_words = get_top_n_words(merge_df['reviewText'].apply(lambda x: np.str_(x)), 20) for word, freq in common_words: print(word, freq) df1 = pd.DataFrame(common_words, columns = ['reviewText' , 'count']) plt.figure(figsize=[18,8]) df1.groupby('reviewText').sum()['count'].sort_values(ascending=False).plot.bar() plt.title('Top 20 bigram words in review "after" removing stop words', fontdict={'fontsize': 20, 'fontweight': 5, 'color': 'Green'}) plt.show() screen protector 84730 phone case 36728 works great 35272 great product 25349 great case 21982 screen protectors 21856 love case 17672 samsung galaxy 17427 highly recommend 16376 case fits 15143 case phone 15011 good quality 14771 glass screen 14695 dropped phone 14656 cell phone 14483 really like 14383 protect phone 14270 battery life 14153 tempered glass 13569 easy install 13261
from wordcloud import WordCloud text = " ".join(review for review in df1.reviewText) wordcloud = WordCloud(max_font_size=50, max_words=100, background_color="white", stopwords=stop_words).generate(text) plt.figure(figsize=[15,10]) plt.imshow(wordcloud, interpolation="bilinear") plt.axis("off") plt.show();
Инсайты
Игнорирование стоп-слов с помощью функции Countvector помогает нам идентифицировать двойные слова для нашего анализа.
Би/многомерный анализ
# Polarity Distribution Month Wise plt.figure(figsize=[14,8]) sns.boxplot(data = merge_df, orient='v', x = 'month', y='polarity') plt.title("polarity distribution - Month Wise", fontdict={'fontsize' : 20, 'fontweight' : 5, 'color': 'Green'}) plt.show()
# Polarity Distribution Year Wise plt.figure(figsize=[14,8]) sns.boxplot(data= merge_df, orient='v', x = 'year', y='polarity') plt.title("polarity distribution - Year Wise", fontdict={'fontsize' : 20, 'fontweight' : 5, 'color': 'Green'}) plt.show()
# Relationship between Review Length and Word count in a review plt.figure(figsize=[12,8]) sns.scatterplot(data=merge_df, x="review_len", y="word_count", hue="TB_score", style="TB_score") plt.show()
# Relationship between Review Length and Polarity in a review plt.figure(figsize=[12,8]) sns.scatterplot(data=merge_df, x="review_len", y="polarity", hue="TB_score", style="TB_score") plt.show()
# Relationship between Word Count and Polarity in a review plt.figure(figsize=[12,8]) sns.scatterplot(data=merge_df, x="word_count", y="polarity", hue="TB_score", style="TB_score") plt.show()
# Relationship between TB Score and Polarity in a review plt.figure(figsize=[14,8]) sns.barplot(x=merge_df['TB_score'] , y=merge_df['polarity'], ci=None) plt.title("TB Score vs Polarity Score", fontdict={'fontsize': 20, 'fontweight': 5, 'color': 'Green'}) plt.show()
Инсайты
С помощью двумерного/многомерного анализа получено не так много информации. Да, мы постепенно соглашаемся с тем, что категории мобильных телефонов оказали огромное влияние, так как было получено больше положительных отзывов. Кроме того, мы поняли, что количество слов в обзоре и его длина коррелируют.
Шаг 3: Текстовая аналитика
Текстовая аналитика в основном выполняется с использованием концепции модели Bag of Words. Модель BOG состоит из двух этапов предварительной обработки, т. е. токенизации и удаления стоп-слов, таких как «is», «an», «the» и т. д. Эти стоп-слова удаляются из текста по двум причинам:
- В большинстве приложений они не предоставляют никакой полезной информации.
- Они используют много памяти из-за такой высокой частоты, в которой они присутствуют.
После токенизации документов и удаления стоп-слов следующим шагом будет приведение слов к их базовой форме. Это приводит к избавлению от избыточной информации за счет использования только уникальных токенов. Существует два метода приведения слов к их базовой форме — выделение корней и лемматизация. Давайте рассмотрим каждый из них:
- Stemming: это метод, основанный на правилах, который просто отсекает суффикс слова, чтобы получить его корневую форму, которая называется «основой». Например, если вы используете стеммер для определения корней слов строки — «Водитель мчится на машине своего босса», слова «водитель» и «гонка» будут преобразованы в корневую форму путем простого
отсечения суффиксы «er» и «ing». Таким образом, «водитель» будет преобразован в «драйв», а «гонки» будет преобразован в «гонки». Теперь вы можете подумать, что корневые формы (или основы) не похожи на корневые слова — «драйв» и «гонка». Вам не нужно об этом беспокоиться, потому что стеммер преобразует все варианты «драйв» и «гонки» только в эти корневые формы. Таким образом, он преобразует «драйв», «вождение» и т. д. в «драйв», а «гонка», «гонщик» и т. д. в «гонщик». - Лемматизация: это более сложная техника, более интеллектуальная в том смысле, что она не просто отсекает суффикс слова. Вместо этого он берет входное слово и ищет его базовое слово, рекурсивно перебирая все варианты словарных слов. Базовое слово в этом случае называется «лемма». Такие слова, как «ноги», «погнали», «встали», «купил» и т. д. нельзя привести к их правильной базовой форме с помощью стеммера. Но лемматизатор может привести их к правильной базовой форме. Самый популярный лемматизатор — лемматизатор WordNet, созданный группой профессоров Принстонского университета.
Стеммер работает намного быстрее, но дает неточные результаты во многих случаях, когда вариант слова не имеет формы «корневое слово + суффикс». С другой стороны, лемматизатор дает точные результаты, но работает намного
медленнее, чем стеммер. Кроме того, вам необходимо передать тег POS слова вместе со словом, которое нужно лемматизировать.
Модель мешка слов в сообщениях с основой дает нам приведенное ниже облако слов.
# Word Cloud Using Porter Stemming Bags of Word Model from wordcloud import WordCloud text1 = " ".join(review for review in vectorizer.get_feature_names()) wordcloud = WordCloud(max_font_size=50, max_words=100, background_color="white", stopwords=stop_words).generate(text1) plt.figure(figsize = [15,12]) plt.imshow(wordcloud, interpolation="bilinear") plt.axis("off") plt.show();
Облако Word с использованием модели Lemmatizing Wordnet Bag of Word
# Word Cloud Using Lemmatizing Wordnet Bags of Word Model from wordcloud import WordCloud text2 = " ".join(review for review in vectorizer.get_feature_names()) wordcloud = WordCloud(max_font_size=50, max_words=100, background_color="white", stopwords=stop_words).generate(text2) plt.figure(figsize = [15,12]) plt.imshow(wordcloud, interpolation="bilinear") plt.axis("off") plt.show();
Статистика:
- Лемматизация, кажется, гораздо глубже работает со словами.
Облако слов с использованием пакетов Tf-IDF модели Word
# Word Cloud Using Tf-IDF Bags of Word Model from wordcloud import WordCloud text3 = " ".join(review for review in vectorizer.get_feature_names()) wordcloud = WordCloud(max_font_size=50, max_words=100, background_color="white", stopwords=stop_words).generate(text3) plt.figure(figsize = [15,12]) plt.imshow(wordcloud, interpolation="bilinear") plt.axis("off") plt.show();
Статистика:
Наконец, мы видим, что метод TF-IDF производит гораздо более чистые и менее шумные слова, поэтому мы будем строить нашу модель на этом методе.
Примечание. Чтобы увидеть, как я подробно использовал методы анализа текста с кодом, вы можете посетить мой блог по этой ссылке.
Шаг 4. Создание механизма классификации настроений с использованием наивного Байеса.
При построении движка мы собираемся передать в субаренду данные, которые включают только те переменные, на основе которых будет построена и оценена Модель. Для этого мы выбираем фирменное наименование, имя рецензента и текст обзора, по которым будут делаться прогнозы, а также настроение отзыва, чтобы оценить, как работают наши прогнозы. Естественно, чтобы построить модель, мы начинаем с разделения наших данных на обучение и тестирование. Здесь мы собираемся применить метод построения модели «Мультиномиальный наивный байесовский классификатор». Ниже приведен код и вывод.
from sklearn.naive_bayes import MultinomialNB from sklearn.pipeline import Pipeline from sklearn.feature_extraction.text import TfidfTransformer from sklearn.feature_extraction.text import CountVectorizer from sklearn.metrics import accuracy_score, confusion_matrix mnb = Pipeline([('vect', CountVectorizer(stop_words = stop_words)), ('tfidf', TfidfTransformer()), ('clf', MultinomialNB()), ]) mnb.fit(X_train['reviewText'].apply(lambda x: np.str_(x)), y_train) #%%time from sklearn.metrics import classification_report y_pred = mnb.predict(X_test['reviewText'].apply(lambda x: np.str_(x))) print('accuracy %s' % accuracy_score(y_pred, y_test)) #print(classification_report(y_test, y_pred, target_names = df_text['review_sentiment'].unique())) accuracy 0.8105504119162394
Статистика:
Точность = правильно спрогнозированные ярлыки / общее количество ярлыков
- «Положительные» отзывы на самом деле идентифицируются как положительные
- «Отрицательные» отзывы на самом деле идентифицируются как отрицательные
Так как мы достигли точности 81%, но теперь возникает вопрос — достаточно ли точности, чтобы оценить качество модели? И ответ - категорическое НЕТ!
Давайте посмотрим на матрицу путаницы, которую мы получили для нашей окончательной модели.
mnb Pipeline(steps=[('vect', CountVectorizer(stop_words=['a', 'about', 'above', 'across', 'after', 'again', 'all', 'almost', 'alone', 'along', 'already', 'also', 'although', 'always', 'among', 'an', 'and', 'another', 'any', 'anybody', 'anyone', 'anything', 'anywhere', 'are', 'area', 'areas', 'around', 'as', 'ask', 'asked', ...])), ('tfidf', TfidfTransformer()), ('clf', MultinomialNB())])
Статистика:
- Использование стоп-слов в CountVectorizer помогло нам повысить точность модели.
# predicting probabilities of test data proba = mnb.predict_proba(X_test['reviewText'].apply(lambda x: np.str_(x))) proba array([[0.05972756, 0.94027244], [0.09468221, 0.90531779], [0.05026127, 0.94973873], ..., [0.0150833 , 0.9849167 ], [0.0150833 , 0.9849167 ], [0.01459351, 0.98540649]]) # confusion matrix from sklearn import metrics metrics.confusion_matrix(y_test, y_pred) array([[ 8698, 58736], [ 686, 245536]]) import seaborn as sns import matplotlib import matplotlib.pyplot as plt cm = confusion_matrix(y_test,y_pred) cm_df = pd.DataFrame(cm, index = ['Negative','Positive'], columns = ['Negative','Positive']) #Plotting the confusion matrix plt.figure(figsize=(5,4)) sns.heatmap(cm_df, annot=True,fmt='g') plt.title('Confusion Matrix') plt.ylabel('Actal Values') plt.xlabel('Predicted Values') plt.show()
Статистика:
- Фактические метки располагаются вдоль столбца, а прогнозируемые метки — вдоль строк.
- 686 отзывов на самом деле являются «положительными», но прогнозируются моделью как «отрицательные», тогда как 8698 отзывов правильно прогнозируются как «отрицательные». С другой стороны, 686 и 245536 в целом являются положительными, но модель упустила небольшую часть, что вполне выполнимо.
- Теперь модель предсказывает 58736 положительных отзывов, тогда как это отрицательные отзывы, что может ввести в заблуждение при идентификации тех брендов, у которых есть положительные отзывы. Это немного рискованно.
Это подводит нас к двум наиболее часто используемым метрикам для оценки модели классификации:
- Чувствительность: (из всех положительных результатов, сколько вы действительно обнаружили)
- Специфика: (из всех негативов, сколько вы на самом деле обнаружили)
- Фактическое/прогнозируемое отсутствие оттока Отток
- Не сбрасывать Истинные негативы Ложноположительные результаты
- Отток ложноотрицательных истинных срабатываний
sensitivity = TP / float(FN + TP) print("sensitivity",sensitivity) sensitivity 0.9972138964024336
Статистика:
У нас хорошая чувствительность. Когда чувствительность теста высока, он с меньшей вероятностью даст ложноотрицательный результат. В тесте с высокой чувствительностью положительный результат является положительным. Это можно проверить, поскольку мы получаем 686 как ложноотрицательные результаты.
specificity = TN / float(TN + FP) print("specificity",specificity) specificity 0.12898537829581516
Статистика:
У нас очень низкая специфичность. Тест с низкой специфичностью можно рассматривать как слишком стремящийся найти положительный результат, даже если его нет, и может давать большое количество ложноположительных результатов. Это также можно проверить, так как у нас большее количество FP, то есть 58736.
from sklearn import metrics from sklearn.metrics import precision_score from sklearn.metrics import recall_score print("PRECISION SCORE :", precision_score(y_test, y_pred, pos_label='POSITIVE')) print("RECALL SCORE :", recall_score(y_test, y_pred, pos_label='POSITIVE')) print("F1 SCORE :", metrics.f1_score(y_test, y_pred, pos_label='POSITIVE')) PRECISION SCORE : 0.806962191723195 RECALL SCORE : 0.9972138964024336 F1 SCORE : 0.8920569524826792 # mapping labels to 0 and 1 in y_pred y_test = y_test.map({'NEGATIVE':0, 'POSITIVE':1}) # creating an ROC curve from sklearn.metrics import confusion_matrix as sk_confusion_matrix from sklearn.metrics import roc_curve, auc import matplotlib.pyplot as plt false_positive_rate, true_positive_rate, thresholds = roc_curve(y_test, proba[:,1]) roc_auc = auc(false_positive_rate, true_positive_rate) # area under the curve print(roc_auc) 0.9300492881807698
Построение кривой ROC
Кривая ROC демонстрирует несколько вещей:
- Он показывает компромисс между чувствительностью и специфичностью (любое повышение чувствительности будет сопровождаться снижением специфичности).
- Чем ближе кривая следует к левой границе, а затем к верхней границе пространства ROC, тем точнее тест.
- Чем ближе кривая подходит к 45-градусной диагонали пространства ROC, тем менее точен тест.
# plotting the ROC curve %matplotlib inline plt.ylabel('True Positive Rate') plt.xlabel('False Positive Rate') plt.title('ROC') plt.plot(false_positive_rate, true_positive_rate)
Статистика:
Площадь под кривой равна 0,93, что достаточно хорошо.
Примечание. Хорошей моделью является та, в которой TPR высокий (ближе к 100 %), а FPR низкий (ближе к 0 %). Следовательно, необходимо сбалансировать эти два элемента.
TPR и FPR — это не что иное, как чувствительность и (1 — специфичность), поэтому их также можно рассматривать как компромисс между чувствительностью и специфичностью.
Отображение варианта использования в бизнесе
# Creating a new column from Predictions from model X_test['sentiment_predicted'] = y_pred # Assume you have launched a product in market and you need to see what is the sentiment of that particular product. temp = X_test.loc[X_test['brand']=="Motorola"].reset_index(drop=True) # Visualizing the sentiments for the product temp['sentiment_predicted'].hist()
negative_df = temp.loc[temp['sentiment_predicted']=='NEGATIVE'].reset_index(drop=True) negative_df.head() from wordcloud import WordCloud text = " ".join(str(review) for review in negative_df.reviewText) wordcloud = WordCloud(max_font_size=50, max_words=100, background_color="white", stopwords=stop_words).generate(text) plt.figure(figsize=[15,10]) plt.imshow(wordcloud, interpolation="bilinear") plt.axis("off") plt.show();
# Also, let's assume you want to see what is the ratio of reviews a particular reviewer has posted on Amazon temp1 = X_test.loc[X_test['reviewerName']=="Daniel"].reset_index(drop=True) # Visualizing the sentiments for a particular Reviewer temp1['sentiment_predicted'].hist()
Заключение:
- Мы достигли оценки точности 81%, используя технику построения модели мультиномиального наивного байесовского классификатора.
- Площадь под кривой ROC составляет 93%.
Исходный код можно найти на GitHub. Вы можете найти файлы данных, нажав здесь.
Я с нетерпением жду любых отзывов или вопросов.