Анализ описаний Wine с помощью набора инструментов для естественного языка в Python

Описание вина для обывателя

Пару месяцев назад я создал веб-приложение, которое позволяет пользователям вводить запрос и возвращать рекомендации по винам на основе семантического сходства. Он был построен с использованием универсального кодировщика предложений Tensorflow lab. Когда я запустил инструмент в производство, я добавил код, который записывает вводимые пользователем данные в мою базу данных, чтобы я мог анализировать слова, которые люди используют для поиска вина. Основываясь на моем анализе того, что было записано на данный момент, кажется, что большинство людей похожи на меня: у меня практически нет опыта в обзоре вина, и я не знаю, какие слова использовать при его поиске. Большинство записанных мной запросов состоят из двух или трех слов и просты, например, легко выпить. Чтобы помочь себе и своим пользователям, я ныряю в описания вин, чтобы узнать, что я могу узнать о языке, на котором описывают вино. Прокрутите статью до конца, чтобы увидеть завершенный код.

Данные и зависимости

Исходные данные можно найти на Kaggle; однако в примерах в этой статье используются мои инженерные данные. Я обсуждаю некоторые аспекты инженерии данных в своей оригинальной статье для тех, кому это интересно. Для анализа текста я использую пакеты Python WordCloud и nltk (Natural Language Toolkit). Я начинаю с загрузки зависимостей и проверки данных:

#dependencies
import pandas as pd
import sqlite3
from sqlite3 import Error
import re
from wordcloud import WordCloud
import matplotlib.pyplot as plt
import nltk
from nltk.tokenize import RegexpTokenizer
from nltk.stem.snowball import SnowballStemmer
#nltk.download('wordnet')
from nltk.stem.wordnet import WordNetLemmatizer
#nltk.download('stopwords')
from nltk.corpus import stopwords
from sklearn.feature_extraction.text import CountVectorizer
#force output to display the full description
pd.set_option('display.max_colwidth', -1)
#create connection to database
conn = sqlite3.connect('db\wine_data.sqlite')
c = conn.cursor()
#create the pandas data frame
wine_df = pd.read_sql('Select title, description, rating, price, color from wine_data', conn)
#display the top 3 records from the data frame
wine_df.head(3)

Количество слов и распределение

Начав с некоторой базовой информации о данных, легко добавить столбец подсчета слов во фрейм данных и использовать метод description pandas для обработки некоторой базовой статистики:

#inline function to produce word count, splitting on spaces
wine_df['word_count'] = wine_df['description'].apply(lambda x: len(str(x).split(" ")))
wine_df.word_count.describe()

Распределение количества слов легко визуализировать, используя график из matplotlib:

#set x for the histogram and set bins based on max
x = wine_df['word_count']
n_bins = 140
#plot histogram
plt.hist(x, bins=n_bins)
plt.show()

Стоп-слова и частоты

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

stop_words = set(stopwords.words("english"))
#show how many words are in the list of stop words
print(len(stop_words))
#179
#loops through descriptions and cleans them
clean_desc = []
for w in range(len(wine_df.description)):
    desc = wine_df['description'][w].lower()
    
    #remove punctuation
    desc = re.sub('[^a-zA-Z]', ' ', desc)
    
    #remove tags
    desc=re.sub("</?.*?>"," <> ",desc)
    
    #remove digits and special chars
    desc=re.sub("(\\d|\\W)+"," ",desc)
    
    clean_desc.append(desc)
#assign the cleaned descriptions to the data frame
wine_df['clean_desc'] = clean_desc
#calculate the frequency
word_frequency = pd.Series(' '.join(wine_df['clean_desc']).split()).value_counts()[:30]
word_frequency

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

#add single word to stoplist
#stop_words.add("wine")
#add list of words to stoplist
add_stopwords = ["wine", "drink"]
stop_words = stop_words.union(add_stopwords)
print(len(stop_words))
#181

Лемматизация или стемминг?

Оба являются методами нормализации языка, но основное различие между лемматизацией и стеммингом заключается в том, что лемматизация сводит слово к корневой форме, сохраняя при этом, что это настоящее слово. Построение сокращает слово до корневой формы, удаляя суффикс и преобразовывая его в представление реального слова. Например, объединение словосочетаний «телесный» и «тело» приведет к «боди». Я считаю, что консенсус состоит в том, что лемматизация превосходит некоторые алгоритмы стемминга; тем не менее, стемминг по-прежнему важен для понимания. Сравните эти два облака слов: первое было сделано с выделением корней, а второе - с лемматизацией:

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

stem_desc = []
for w in range(len(wine_df['clean_desc'])):
split_text = wine_df['clean_desc'][w].split()
    
    ##Stemming
#     stm = SnowballStemmer("english")
#     split_text = [stm.stem(word) for word in split_text if not word in stop_words] 
#     split_text = " ".join(split_text)
#     stem_desc.append(split_text)
    
    #Lemmatisation
    lem = WordNetLemmatizer()
    split_text = [lem.lemmatize(word) for word in split_text if not word in stop_words] 
    split_text = " ".join(split_text)
    stem_desc.append(split_text)
stem_desc

Создание WordCloud

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

#set the word cloud parameters
wordcloud = WordCloud(width = 800, height = 800, background_color = 'black', stopwords = stop_words, max_words = 1000,                  min_font_size = 20).generate(str(stem_desc))

#plot the word cloud
fig = plt.figure(figsize = (8,8), facecolor = None)
plt.imshow(wordcloud)
plt.axis('off')
plt.show()
#fig.savefig("wordcloud.png")

Анализируем н-граммы

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

Поскольку цель состоит в том, чтобы проанализировать, как описывается вино, мне нужно создать новый список стоп-слов на основе частотного анализа. Я создаю функцию с помощью scikit-learn CountVectorizer для генерации триграмм, а затем помещаю их во фрейм данных, чтобы увидеть, нужно ли корректировать мой список стоп-слов.

def get_trigrams(descriptions, n=None):
    
    vec = CountVectorizer(ngram_range = (3,3), max_features = 20000).fit(descriptions)
    bag_of_words = vec.transform(descriptions)
    sum_words = bag_of_words.sum(axis = 0) 
    words_freq = [(word, sum_words[0, i]) for word, i in vec.vocabulary_.items()]
    words_freq =sorted(words_freq, key = lambda x: x[1], reverse = True)
   
    return words_freq[:n]
#run the function on the processed descriptions
trigrams = get_trigrams(clean_desc, n=15)
#create a trigram data frame
trigram_df = pd.DataFrame(trigrams)
trigram_df.columns=["Trigram", "Freq"]
#output top 15 rows
trigram_df.head(15)

Проанализировав верхние триграммы, я решил использовать собственный список стоп-слов. Я убираю «вино» и «питье» вместе с некоторыми из лучших сортов, поэтому у меня остаются н_граммы, которые помогают мне понять, как описывается вино.

stops = ['wine','the', 'drink', 'an', 'cabernet', 'sauvignon', 'black', 'cherry']
stem_desc = []
for w in range(len(wine_df['clean_desc'])):
split_text = wine_df['clean_desc'][w].split()
       
    #Lemmatisation
    #lem = WordNetLemmatizer()
    split_text = [lem.lemmatize(word) for word in split_text if not word in stops] 
    split_text = " ".join(split_text)
    stem_desc.append(split_text)
trigrams = get_trigrams(clean_desc, n=15)
#create a trigram data frame
trigram_df = pd.DataFrame(trigrams)
trigram_df.columns=["Trigram", "Freq"]
#output top 15 rows
trigram_df.head(15)

Триграммы можно визуализировать с помощью гистограммы.

fig = sns.set(rc = {'figure.figsize':(12,8)})
bp = sns.barplot(x = "Trigram", y = "Freq", data = trigram_df)
bp.set_xticklabels(bp.get_xticklabels(), rotation = 75)
plt.show()

Глядя на триграммы, я могу понять, как мне следует задавать вопросы о вине. Например, я вижу, что описания часто содержат такие дескрипторы, как «с намеком на» и «и с запахом». В сочетании с облаком слов я теперь лучше понимаю ключевые слова, основные сорта и язык, используемый для описания вина. Вот весь код:

#dependencies
import pandas as pd
import sqlite3
from sqlite3 import Error
import re
from wordcloud import WordCloud
import matplotlib.pyplot as plt
import nltk
from nltk.tokenize import RegexpTokenizer
from nltk.stem.snowball import SnowballStemmer
#nltk.download('wordnet')
from nltk.stem.wordnet import WordNetLemmatizer
#nltk.download('stopwords')
from nltk.corpus import stopwords
from sklearn.feature_extraction.text import CountVectorizer
#force output to display the full description
pd.set_option('display.max_colwidth', -1)
#create connection to database
conn = sqlite3.connect('db\wine_data.sqlite')
c = conn.cursor()
#create the pandas data frame
wine_df = pd.read_sql('Select title, description, rating, price, color from wine_data', conn)
#display the top 3 records from the data frame
wine_df.head(3)
#inline function to produce word count, splitting on spaces
wine_df['word_count'] = wine_df['description'].apply(lambda x: len(str(x).split(" ")))
wine_df.word_count.describe()
#set x for the histogram and set bins based on max
x = wine_df['word_count']
n_bins = 140
#plot histogram
plt.hist(x, bins=n_bins)
plt.show()
stop_words = set(stopwords.words("english"))
#show how many words are in the list of stop words
print(len(stop_words))
#179
#loops through descriptions and cleans them
clean_desc = []
for w in range(len(wine_df.description)):
    desc = wine_df['description'][w].lower()
    
    #remove punctuation
    desc = re.sub('[^a-zA-Z]', ' ', desc)
    
    #remove tags
    desc=re.sub("</?.*?>"," <> ",desc)
    
    #remove digits and special chars
    desc=re.sub("(\\d|\\W)+"," ",desc)
    
    clean_desc.append(desc)
#assign the cleaned descriptions to the data frame
wine_df['clean_desc'] = clean_desc
#calculate the frequency
word_frequency = pd.Series(' '.join(wine_df['clean_desc']).split()).value_counts()[:30]
word_frequency
#add single word to stoplist
#stop_words.add("wine")
#add list of words to stoplist
add_stopwords = ["wine", "drink"]
stop_words = stop_words.union(add_stopwords)
print(len(stop_words))
#181
stem_desc = []
for w in range(len(wine_df['clean_desc'])):
split_text = wine_df['clean_desc'][w].split()
    
    ##Stemming
#     stm = SnowballStemmer("english")
#     split_text = [stm.stem(word) for word in split_text if not word in stop_words] 
#     split_text = " ".join(split_text)
#     stem_desc.append(split_text)
    
    #Lemmatisation
    lem = WordNetLemmatizer()
    split_text = [lem.lemmatize(word) for word in split_text if not word in stop_words] 
    split_text = " ".join(split_text)
    stem_desc.append(split_text)
stem_desc
#set the word cloud parameters
wordcloud = WordCloud(width = 800, height = 800, background_color = 'black', stopwords = stop_words, max_words = 1000, min_font_size = 20).generate(str(stem_desc))
#plot the word cloud
fig = plt.figure(figsize = (8,8), facecolor = None)
plt.imshow(wordcloud)
plt.axis('off')
plt.show()
#fig.savefig("wordcloud.png")
def get_trigrams(descriptions, n=None):
    
    vec = CountVectorizer(ngram_range = (3,3), max_features = 20000).fit(descriptions)
    bag_of_words = vec.transform(descriptions)
    sum_words = bag_of_words.sum(axis = 0) 
    words_freq = [(word, sum_words[0, i]) for word, i in vec.vocabulary_.items()]
    words_freq =sorted(words_freq, key = lambda x: x[1], reverse = True)
   
    return words_freq[:n]
stops = ['wine','the', 'drink', 'an', 'cabernet', 'sauvignon', 'black', 'cherry']
stem_desc = []
for w in range(len(wine_df['clean_desc'])):
split_text = wine_df['clean_desc'][w].split()
       
    #Lemmatisation
    lem = WordNetLemmatizer()
    split_text = [lem.lemmatize(word) for word in split_text if not word in stops] 
    split_text = " ".join(split_text)
    stem_desc.append(split_text)
trigrams = get_trigrams(clean_desc, n=15)
#create a trigram data frame
trigram_df = pd.DataFrame(trigrams)
trigram_df.columns=["Trigram", "Freq"]
#output top 15 rows
trigram_df.head(15)
fig = sns.set(rc = {'figure.figsize':(12,8)})
bp = sns.barplot(x = "Trigram", y = "Freq", data = trigram_df)
bp.set_xticklabels(bp.get_xticklabels(), rotation = 75)
plt.show()

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

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

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







Robotsdodream.com