Кластеризация обзоров Шардоне с использованием K-средних с использованием Scikit-Learn и NLTK

Шардоне - самое популярное белое вино в мире. Виноград легко выращивать, он известен своей сложностью и адаптируем к различным технологиям и вкусам. Например, вчера вечером я попробовала сладкое неочищенное Шардоне. Насколько популярно Шардоне? Как описано в Отчете о площадях под виноградом в Калифорнии за 2018 год, из 176 092 гектаров, отведенных под белое вино, Шардоне потребляло более половины из 93 148 акров! Следующий по величине сорт - французский Colombard, занимающий 18 246 акров; жалкий по сравнению. Это много винограда!

Поскольку я знаком с набором данных обзоров вин, доступным на kaggle, я решил загрузить блокнот и проанализировать Шардоне.

Может ли кластеризация помочь нам определить взаимосвязь между описанием и рейтингом?

В этой статье я покажу, как использовать Scikit-Learn и Набор инструментов для естественного языка для обработки, анализа и кластеризации данных Chardonnay. Используя эти методы, я надеюсь увидеть, есть ли разница в темах или темах, используемых в обзорах вин с оценками выше среднего, по сравнению с обзорами вин со средними или ниже оценками.

Импорт зависимостей и данных

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

#import dependencies
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import re
from sklearn import preprocessing, decomposition, model_selection, metrics, pipeline
from sklearn.feature_extraction.text import TfidfVectorizer, CountVectorizer
from sklearn.cluster import KMeans
import nltk
from nltk.stem.wordnet import WordNetLemmatizer
from nltk.corpus import stopwords
import sqlite3
from sqlite3 import Error
#force output to display the full description
pd.set_option('display.max_colwidth', -1)
#connect to database file
conn = sqlite3.connect('db\wine_data.sqlite')
c = conn.cursor()
#create dataframe from sql query
df = pd.read_sql("Select country, description, rating, price, title, variety from wine_data where variety = 'Chardonnay'", conn)
#display the top 3 rows
df.head(3)

Разработка и анализ функций

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

Анализировать количество слов

Анализ количества слов может помочь вам решить, хотите ли вы сократить набор данных. Например, глядя на данные, мы видим, что минимальное количество слов для обзора вина составляет 3 слова. Если посмотреть на обзоры, содержащие менее 15 слов, средняя оценка составляет 82 из диапазона 80–100. Это говорит мне о том, что короткие обзоры могут быть связаны с более низкими оценками. Точно так же, когда я смотрю на другой конец распределения, я замечаю, что более длинные обзоры могут быть связаны с более высокими оценками.

Короткие обзоры могут быть связаны с более низкими оценками. Более длинные обзоры могут быть связаны с более высокими оценками.

#add a column for the word count
df['word_count'] = df['description'].apply(lambda x: len(str(x).split(" ")))
print("Word Count Median: " + str(df['word_count'].median()))
print(df['word_count'].describe())
x = df['word_count']
n_bins = 95
plt.hist(x, bins=n_bins)
plt.xlabel('Number of Words in Description')
plt.ylabel('Frequency')
plt.show()

#word counts less than 15
wc15 = df.loc[df['word_count'] < 15]
print(wc15.rating.median())
print(wc15.rating.describe())
#word counts greater than 70
wc70 = df.loc[df['word_count'] > 70]
print(wc70.rating.median())
print(wc70.rating.describe())
#plot the counts
plt.figure(figsize=(14,4))
sns.countplot(x ='rating', data = wc70).set_title("Rating Counts")

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

Анализ рейтингов

Поскольку я хочу посмотреть, можно ли использовать описания, чтобы определить, имеет ли вино рейтинг выше среднего, мне нужно найти средний рейтинг. Функция Pandas describe упрощает поиск статистики. Глядя на данные, мы получаем средний рейтинг 88. Добавить столбец с двоичными значениями во фрейм данных легко, используя понимание списка. Если рейтинг больше 88, в нашем новом столбце будет 1. Если рейтинг 88 или меньше, значение будет 0, так как вино не лучше среднего.

Если рейтинг больше 88, в нашем новом столбце будет 1. Если рейтинг 88 или меньше, значение будет 0, так как вино не лучше среднего.

print("Number of Unique Ratings: " + str(len(df['rating'].unique())))
print("Rating Median: " + str(df['rating'].median()))
print(df['rating'].describe())
plt.figure(figsize=(14,4))
sns.countplot(x='rating', data=df).set_title("Rating Counts")
plt.show()
#add column to flag records with rating greater than 88
df['above_avg'] = [1 if rating > 88 else 0 for rating in df['rating']]

Обработка естественного языка

Перед кластеризацией данных я использую несколько техник НЛП, таких как удаление стоп-слов, знаков препинания и специальных символов, а также нормализация текста. После обработки текста я собираюсь векторизовать его с помощью векторизатора Scikit-Learn tf-idf.

Очистка текста

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

#create a list of stop words
stop_words = set(stopwords.words("english"))
#show how many words are in the list of stop words
print(len(stop_words))
#179
#construct a new list to store the cleaned text
clean_desc = []
for w in range(len(df.description)):
    desc = df['description'][w].lower()
    
    #remove punctuation
    desc = re.sub('[^a-zA-Z]', ' ', desc)
    
    #remove tags
    desc = re.sub("&lt;/?.*?&gt;"," &lt;&gt; ",desc)
    
    #remove special characters and digits
    desc = re.sub("(\\d|\\W)+"," ",desc)
    
    split_text = desc.split()
    
    #Lemmatisation
    lem = WordNetLemmatizer()
    split_text = [lem.lemmatize(word) for word in split_text if not word in stop_words and len(word) >2] 
    split_text = " ".join(split_text)
    clean_desc.append(split_text)

Векторизация текста с использованием TF-IDF

TfidfVectorizer преобразует текст в векторное пространство. Чтобы упростить концепцию, представьте, что у вас есть два предложения:

Собака белая
Кошка черная

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

Собака кошка белая черная
Собака белая = [1,1,0,1,1,0]
Кошка черная = [1,0,1,1,0,1]

TF-IDF означает термин частота документа, обратно пропорциональная частоте. Это метод взвешивания значения слова, а не его простого подсчета. Он используется для определения того, насколько важно слово для текста в документах коллекции. Эта функция полезна для поиска информации, такой как поисковые системы и интеллектуальный анализ текста. TfidfVectorizer в Scikit-Learn преобразует набор необработанных документов в матрицу функций TF-IDF. Он возвращает матрицу с помощью метода fit_transform.

#TF-IDF vectorizer
tfv = TfidfVectorizer(stop_words = stop_words, ngram_range = (1,1))
#transform
vec_text = tfv.fit_transform(clean_desc)
#returns a list of words.
words = tfv.get_feature_names()

Извлечение тем с использованием кластеризации K-средних

Кластеризация K-средних - это популярный алгоритм обучения без учителя, который можно использовать для извлечения тем путем группировки похожих обзоров и создания списка общих слов. Я собираюсь попробовать разделить данные на 21 кластер (n_clusters = 21), чтобы посмотреть, смогу ли я обнаружить темы, общие для высоких оценок, и темы, общие для низких оценок. Scikit-Learn упрощает применение k-средних.

#setup kmeans clustering
kmeans = KMeans(n_clusters = 21, n_init = 17, n_jobs = -1, tol = 0.01, max_iter = 200)
#fit the data 
kmeans.fit(vec_text)
#this loop transforms the numbers back into words
common_words = kmeans.cluster_centers_.argsort()[:,-1:-11:-1]
for num, centroid in enumerate(common_words):
    print(str(num) + ' : ' + ', '.join(words[word] for word in centroid))

Визуализируйте результаты

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

#add the cluster label to the data frame
df['cluster'] = kmeans.labels_
clusters = df.groupby(['cluster', 'rating']).size()
fig, ax1 = plt.subplots(figsize = (26, 15))
sns.heatmap(clusters.unstack(level = 'rating'), ax = ax1, cmap = 'Reds')
ax1.set_xlabel('rating').set_size(18)
ax1.set_ylabel('cluster').set_size(18)
clusters = df.groupby(['cluster', 'above_avg']).size()
fig2, ax2 = plt.subplots(figsize = (30, 15))
sns.heatmap(clusters.unstack(level = 'above_avg'), ax = ax2, cmap="Reds")
ax2.set_xlabel('Above Average Rating').set_size(18)
ax2.set_ylabel('Cluster').set_size(18)

Я могу увидеть распределения, разделив фрейм данных и построив график количества кластеров!

#create dataframe of reviews not above average
not_above = df.loc[df['above_avg'] == 0]
not_above.describe()
#create data frame of reviews above average
above_avg = df.loc[df['above_avg'] == 1]
above_avg.describe()
#plot the counts
plt.figure(figsize=(14,4))
sns.countplot(x='cluster', data=not_above).set_title("Rating Counts")
plt.show()
plt.figure(figsize=(14,4))
sns.countplot(x='cluster', data=above_avg).set_title("Rating Counts")
plt.show()

Заключительные мысли и записная книжка

Глядя на визуализации, они показывают, что рейтинги выше среднего были в большей степени сгруппированы в 5, 6 и 12. Это означает, что слова из этих кластеров обычно использовались в обзорах вин, дававших оценки выше среднего. Требуется более глубокий взгляд на слова в кластере, поскольку трудно различить существенные различия, если смотреть только на 10 первых слов. Например, такие слова, как яблоко, аромат и вкус, встречаются в нескольких группах, что затрудняет понимание отдельных тем. Кроме того, существуют различные алгоритмы, которые могут лучше работать при моделировании темы.

Скрытое распределение Дирихле (LDA) - еще один популярный алгоритм обучения без учителя для тематического моделирования, который может превзойти K-средних. Надеюсь, у меня будет возможность изучить LDA и сравнить свои результаты.

Вы можете скачать мою записную книжку из моего репозитория на github:



Благодарю вас!

- Эрик Клеппен