Этот блог является первым в серии анализа настроений по данным Zomato. Существует множество популярных моделей, таких как SVM, Random Forest, Naive Bayes и т. д., которые можно использовать в анализе настроений. В этой серии мы собираемся использовать модель LSTM вместе с Feedforward, чтобы предсказать настроение обзора. Прежде чем мы перейдем к архитектуре модели, нам нужно выполнить некоторые задачи предварительной обработки данных, чтобы нашей модели было удобно изучать функции и классифицировать.

О данных

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

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

Нам нужен только столбец reivews_list из фрейма данных. Мы смотрим на данные в столбце reviews_list и видим, что все отзывы, написанные для каждого ресторана, представлены вместе как одно значение в столбце reviews_list.

print('Restaurant Name: 'df['name'][4])
df['reviews_list'][4]

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

  • Разделяйте рейтинги и отзывы по разным столбцам или спискам.
  • Проверьте и очистите данные оценок
  • Удалить текстовый шум из отзывов
  • Нормализация текста
  • Вывод/лемматизация
  • Удалить низкочастотные слова в корпусе

Разделяйте рейтинги и обзоры

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

split_reviews=reviews.apply(lambda x: x[9:].split(', (\'Rated '))

Здесь reviews — это серия pandas, содержащая столбец reviews_list из фрейма данных zomato. Результат операции показан ниже.

  • Удаление повторяющихся отзывов.Мы видим несколько повторяющихся отзывов при анализе, поэтому удаляем их.
split_reviews=split_reviews.apply(lambda x: list(set(x)))

[Примечание:Функция set() изменяет порядок списка, но, поскольку пара отзыв-рейтинг присутствует вместе, у нас не будет проблем с порядком.]

  • Отделите пару отзыв-рейтинг.Теперь мы разделим оценку и отзыв как отдельные значения, создав для них отдельные списки, но сохранив порядок.
def extract_ratings(x):
  ratings=[]
  for i in x:
    ratings.append(i[:1])
  return ratings
def extract_reviews(x):
  reviews=[]
  for i in x:
    reviews.append(i[14:-2])
  return reviews
#Split reviews and ratings
all_ratings=split_reviews.apply(lambda x: extract_ratings(x))
all_reviews=split_reviews.apply(lambda x: extract_reviews(x))

После операции мы получаем отзывы и оценки в виде отдельного списка, как показано ниже:

Теперь мы объединяем рейтинги и обзоры всех ресторанов в одно измерение.

rating_list=[rating for restaurant_ratings in all_ratings for rating in restaurant_ratings]
review_list=[review for restaurant_reviews in all_reviews for review in restaurant_reviews]

Рейтинг очистки и обработки:

Из 558778 полученных пар обзор-рейтинг можно предположить некоторое несоответствие в процессе извлечения. Мы должны убедиться, что все значения рейтинга являются числовыми значениями, а также можем преобразовать тип данных в int для удобства. Итак, выполняем следующие шаги:

  • Проверка и удаление нечисловых значений рейтинга.Мы видим, что из 558778 экземпляров рейтинга 7595 не являются числовыми. Это может быть связано с некоторым мусорным значением или неточным извлечением. Поэтому мы удаляем эти экземпляры оценок вместе с соответствующими обзорами.
#storing index of non-numeric rating instances
non_numeric_rating=[i for i, j in enumerate(rating_list) if not j.isnumeric()]
print("Number of non numeric rating instances",len(non_numeric_rating))
#Outputs 7595
#pop-ing in reverse order of the index
for i in non_numeric_rating[::-1]:
  print(i)
  rating_list.pop(i)
  review_list.pop(i)

[Примечание: Для удаления нечисловых экземпляров из оценок мы выдвигаем (удаляем) экземпляры в обратном направлении, так что корректировка индекса, происходящая после удаления элемента в списке, выходит за пределы допустимого диапазона. индексы, вызываемые в последующих итерациях]

  • Преобразование значения рейтинга из строки в тип данных int.Числовое строковое значение преобразуется в целое число.
rating_list=list(map(int,rating_list))

Удалить текстовый шум из отзывов

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

#Remove punctuation
def remove_punc(contents):
  punc_list=(string.punctuation).replace
  table=contents.maketrans(string.punctuation,' ' *
  len(string.punctuation))
  return contents.translate(table)
#Remove white spaces
def remove_white_space(data):
  return ' '.join(data.split())
#Remove word containing non ascii characters
def remove_non_ascii_words(contents):
  string_ascii = ' '.join([ token for token in contents.split() if token.isascii() ])
  return string_ascii
#Noise Removal
def remove_noise(contents):
  remove_pun=remove_punc(contents)
  remove_spaces=remove_white_space(remove_pun)
  remove_non_ascii=remove_non_ascii_words(remove_spaces)
  return remove_non_ascii
review_no_noise=[remove_noise(i) for i in review_list]

На различных этапах предварительной обработки использовались разные переменные (remove_pun, remove_spaces и т. д.), как показано выше, вместо того, чтобы перезаписывать одни и те же. Это делается для доступа и изучения данных на различных этапах предварительной обработки. Если требования как такового нет, можно использовать ту же переменную, так как это займет меньше места в памяти.

Нормализация текста

После удаления шумов мы нормализуем текст, выполнив следующие шаги:

  • Преобразовать текст в нижний регистр.Это гарантирует, что слова с одинаковыми буквами, но с разными регистрами будут считаться одним и тем же словом.
  • Токенизация текста.Разбивайте текстовые маркеры слов. Это фундаментальный шаг в обработке естественного языка, поскольку он помогает модели вывести значение текста с помощью присутствующих токенов/слов.
  • Удалить стоп-слова.Это очень часто используемые слова в тексте, которые добавляют очень мало ценности тексту и, следовательно, могут быть проигнорированы. Некоторые стоп-слова на английском языке могут быть полезны при анализе тональности, например вниз, почему, ниже и т. д.Поэтому мы их включили.
#text to lower case
def to_lower(contents):
  return contents.lower()
#tokenize words
nltk.download('punkt')
def tokenize(contents):
  return nltk.word_tokenize(contents)
#remove_stopwords
nltk.download('stopwords')
def remove_stopwords(contents):
  cachedStopWords = stopwords.words("english")[:121]
  include_words=['until','above','below','down','up','under','over','out','why','how','all','any','both','each']
  for i in include_words:
    cachedStopWords.remove(i)
  no_stopwords=[]
  for i in tqdm(contents,desc='Stopword Removal'):
    no_stopwords.append([j for j in i if j not in cachedStopWords])
  return no_stopwords

tokens=[]
for i in review_no_noise:
  review_tolower=to_lower(i)
  tokens.append(nltk.word_tokenize(review_tolower))
  no_stopwords=remove_stopwords(tokens)
tokens

Стемминг и лемматизация

В НЛП методы стемминга и лемматизации сводят слово к его основному слову. Хотя они оба выполняют одинаковую работу, но есть некоторые различия. Выводобрезает слово до его основы. Он удаляет последние несколько символов слова. При лемматизации контекст слова рассматривается, а затем сводится к его корневому слову, также известному как лемма.

Лемматизацияявляется более ресурсоемким процессом и отнимает много времени, чем выборка. Для больших наборов данных Stemming становится единственным вариантом из двух. Более того, лемматизация требует, чтобы слова были помечены частями речи для создания правильной леммы. Однако лемматизация иногда дает более точные базовые слова, чем стемминг.

Здесь мы будем использовать лемматизацию в нашей предварительной обработке. Следуя шагам:

  • Тегирование POS:Чтобы наши леммы были точными, необходимо знать части речи каждого слова (хотя для удобства можно обобщить все слова как глаголы для лемматизации). Мы помечаем все слова их POS с помощью nltk.pos_tag().
pos_tag=[]
for i in tqdm(no_stopwords,desc='POS Tagging'):
  pos_tag.append(nltk.pos_tag(i))

WordNetLemmatizer(), используемый для лемматизации, распознает только четыре части речи: прилагательное, глагол, существительное и наречие. Итак, нам нужно преобразовать все классы (которые в основном являются подклассами этих четырех POS) в один из этих четырех классов.

def pos_tagger(nltk_tag):
  if nltk_tag.startswith('J'):
    return wordnet.ADJ
  elif nltk_tag.startswith('V'):
    return wordnet.VERB
  elif nltk_tag.startswith('N'):
    return wordnet.NOUN
  elif nltk_tag.startswith('R'):
    return wordnet.ADV
wordnet_tagged=[]
for i in tqdm(pos_tag,desc='Tag conversion'):
  wordnet_tagged.append(list(map(lambda x: (x[0], pos_tagger(x[1])), i)))
  • Лемматизация слов с тегами POS.Мы используем WordNetLemmatizer() для лемматизации маркеров отзывов с тегами POS (слов).
def lemmatization(contents):
  lem = WordNetLemmatizer()
  lem_list=[]
  for i in tqdm(contents,desc='Lemmatization'):
    lem_review=[]
    for j in i:
      if j[1] is None:
        lem_review.append(j[0])
      else:
        lem_review.append(lem.lemmatize(j[0],j[1]))
    lem_list.append(lem_review)
  return lem_list
lemmatized_review=lemmatization(wordnet_tagged)

Удалить низкочастотные слова

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

Ниже приведены шаги для удаления редких слов:

  • Найти словарь слов, присутствующих в корпусе
  • Найти частотность каждого слова в корпусе
  • Удалять редкие слова в зависимости от их частоты

Найти словарь слов, присутствующих в корпусе. Сначала мы находим все слова, используемые в корпусе, с помощью функции gensim.corpora.Dictionary из библиотеки gensim.

import gensim
from gensim import corpora
from gensim.corpora import Dictionary
review_dct=Dictionary(lemmatized_review)

Найдите частоту каждого слова в корпусе:Используя словарные слова, мы находим соответствующую частоту, связанную со словом в корпусе. Для этого мы создаем пакет слов на каждый отзыв, используя уже созданный словарь слов. Мы используем doc2bow() для этой задачи. Затем мы накапливаем частоты слов из всех обзоров, используя функцию счетчика в модуле сбора в python.

corpus = [review_dct.doc2bow(sent) for sent in tqdm(lemmatized_review,desc='Term Frequency')]
vocab_tf_row=[dict(i) for i in corpus]
counter=collections.Counter()
for i in tqdm(vocab_tf_row,desc='Accumulating Frequencies'):
  counter.update(i)
#converting counter object to dictionary object 
res=dict(counter)

Удалить редкие слова из корпуса:Теперь, когда мы получили частоту каждого слова в словаре по всему корпусу, мы можем установить пороговую частоту, ниже которой слово можно считать редким, а остальные можно использовать для нашего работа. Мы можем начать с 5 в качестве пороговой частоты.

#Creating list of rare words and index having frequency less than 5 in the corpus
#Creating list of rare words and index having frequency more than or equal to 5 in the corpus
working_words_ids=[]
rare_words_ids=[]
for i,j in tqdm(res.items(),desc='Counting low Frequent indices'):
  if j<5:
    rare_words_ids.append(i)
  else:
    working_words_ids.append(i)
working_words=[review_dct[i] for i in working_words_ids]
rare_words=[review_dct[i] for i in rare_words_ids]

Мы видим, что эти редкие слова в основном являются тарабарщиной или неправильно написаны.

Теперь могут быть некоторые обзоры, которые содержат только редкие слова. Нам нужно удалить их с соответствующим рейтингом из списков. Затем мы удаляем редкие слова из корпуса.

#Creating a review corpus with working words having no rare words
working_corpus=[]
difference_list=[]
for i in tqdm(lemmatized_review):
  working_corpus.append([j for j in i if j in working_words])

И… Вуаля! Теперь у нас есть набор данных в том виде, в котором его можно передать модели для прогнозирования.

Вы можете перейти по ссылке, чтобы найти блокнот, содержащий коды, упомянутые в этом блоге. Используемые данные принадлежат Zomato Ltd и извлекаются компанией Himanshu Poddar. Пожалуйста, укажите необходимые кредиты, если вы используете данные.