Анализ описаний 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()
Благодарю вас!
- Если вам понравилось, подписывайтесь на меня на Medium, чтобы узнать больше
- Получите ПОЛНЫЙ ДОСТУП и помогите поддержать мой контент, подписавшись
- Давайте подключимся к LinkedIn
- Анализировать данные с помощью Python? Загляните на мой сайт
Для получения дополнительной информации об анализе текста и обработке естественного языка перейдите по этим ссылкам: