Анализ настроений — это метод обработки естественного языка (NLP), который определяет, являются ли данные положительными или отрицательными (или даже нейтральными).

В этом посте я объясню, как я создал веб-приложение для анализа настроений на основе отзывов о больницах из Google Maps, расположенных в Семаранге, Индонезия. Этот проект создан для того, чтобы завершить мой проект курса Text Mining в университете.

Весь код, который я использовал в этом посте, а также некоторые другие файлы можно найти в моем GitHub Repository. Вы также можете найти индонезийскую версию этого поста на моем YouTube Video.

Я впервые пишу пост на Medium. Так что простите меня, если я сделаю много ошибок в своем письме.

Подготовка данных

Набор данных, который я использовал для этого анализа, состоит всего из 800 отзывов о больницах, взятых из Google Maps (я рекомендую вам получить больше данных, поскольку чем больше у вас обучающих данных, тем больше вероятность, что это поможет лучшему обучению и классификации).

Прочитайте файл CSV, содержащий данные, которые вы подготовили ранее:

df = pd.read_csv("CSV/hospital_reviews.csv", encoding="latin-1")

Я просто хочу оставить только 2 столбца для классификации: рейтинг и обзор. Итак, мне нужно удалить hospital_name и название .

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

df.drop(df[df['hospital_name'] == "Primaya Hospital Semarang"].index, inplace = True)
df.drop(df[df['hospital_name'] == "Dr. Amino Gondohutomo Regional Psychiatric Hospital"].index, inplace = True)
df.drop(columns = ['hospital_name', 'name'], inplace = True)

Если вы посмотрите на обзоры, некоторые из них имеют (переведено Google), (оригинал), то есть исходный текст до перевода Google.

(Translated by Google) The dentist is ok, the treatment for cavities is comfortable, it doesn't hurt. (Original) Dokter gigi nya ok, perawatan gigi berlubang nyaman, tidak sakit.

Поскольку мне нужен здесь только текст обзора на английском языке, я должен удалить остальные. Итак, обзор будет таким:

The dentist is ok, the treatment for cavities is comfortable, it doesn't hurt.

Для этого я использовал функцию split в python:

review_remove_translated = []
reviews_dict = df.to_dict('list')
for review in reviews_dict['review']:
  review_sep = str(review).split("(Translated by Google) ")
  
  if review_sep[0] == "":
    review_sep = ("".join(review_sep)).split("(Original)")
    review_sep = review_sep[0]
    review = "".join(review_sep)
  
  review_remove_translated.append(review)
reviews_dict['review'] = review_remove_translated
df = pd.DataFrame(reviews_dict)

Маркировка данных

В Google Maps пользователи могут ставить оценку по шкале от 1 до 5 звезд. Таким образом, отзыв с ≤ 3 звездами будет помечен как отрицательный (0). А отзыв с › 3 звездами будет помечен как положительный (1).

df['label'] = df['rating'].map({1.0:0, 2.0:0, 3.0:0, 4.0:1, 5.0:1})

Предварительная обработка текста

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

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

def clean_review(review):
    return re.sub('[^a-zA-Z]', ' ', review).lower()
  
df['cleaned_review'] = df['review'].apply(lambda x: clean_review(str(x)))

2. Добавление дополнительных функций

(длина и процент знаков препинания в тексте)

def count_punct(review):
    count = sum([1 for char in review if char in string.punctuation])
    return round(count/(len(review) - review.count(" ")), 3)*100
  
df['review_len'] = df['review'].apply(lambda x: len(str(x)) - str(x).count(" "))
df['punct'] = df['review'].apply(lambda x: count_punct(str(x)))

3. Токенизация

def tokenize_review(review):
    tokenized_review = review.split()
    return tokenized_review
  
df['tokens'] = df['cleaned_review'].apply(lambda x: tokenize_review(x))

4. Лемматизация и удаление стоп-слов

import nltk
from nltk.corpus import stopwords
all_stopwords = stopwords.words('english')
all_stopwords.remove('not')
def lemmatize_review(token_list):
    return " ".join([lemmatizer.lemmatize(token) for token in token_list if not token in set(all_stopwords)])
lemmatizer = nltk.stem.WordNetLemmatizer()
df['lemmatized_review'] = df['tokens'].apply(lambda x: lemmatize_review(x))
df.head()

Визуализируйте количество отзывов для каждого рейтинга.

sns.countplot(x='rating', data=df);

Создание облака слов для отзывов

from wordcloud import WordCloud
df_negative = df[ (df['rating']==1.0) | (df['rating']==2.0) | (df['rating']==3.0) ]
df_positive = df[ (df['rating']==4.0) | (df['rating']==5.0) ]
#convert to list
negative_list= df_negative['lemmatized_review'].tolist()
positive_list=df_positive['lemmatized_review'].tolist()
#convert to string
filtered_negative = ("").join(str(negative_list))
filtered_negative = filtered_negative.lower()
filtered_positive = ("").join(str(positive_list))
filtered_positive = filtered_positive.lower()

Облако слов: отрицательные отзывы

wordcloud = WordCloud(max_font_size = 160, margin=0, background_color = "white", colormap="Reds").generate(filtered_negative)
plt.figure(figsize=[10,10])
plt.imshow(wordcloud, interpolation='bilinear')
plt.axis("off")
plt.margins(x=0, y=0)
plt.title("Negative Reviews Word Cloud")
plt.show()

Облако слов: положительные отзывы

wordcloud = WordCloud(max_font_size = 160, margin=0, background_color = "white", colormap="Greens").generate(filtered_positive)
plt.figure(figsize=[10,10])
plt.imshow(wordcloud, interpolation='bilinear')
plt.axis("off")
plt.margins(x=0, y=0)
plt.title("Positive Reviews Word Cloud")
plt.show()

Извлечение функций из текста

Отзывы, которые у меня есть здесь, по-прежнему считаются неструктурированными данными. Итак, мне нужно преобразовать эти данные в числовые признаки, которые могут быть обработаны алгоритмом машинного обучения. Этот метод называется извлечением признаков. Для этого я буду использовать TF-IDF Vectorizer.

TF-IDF (частота терминов и обратная частота документов) — один из наиболее часто используемых методов взвешивания для извлечения признаков. TF-IDF работает лучше, чем Count Vectorizer в машинном обучении, поскольку он не только измеряет частоту присутствия слов в документах, но и измеряет важность слов в документах.

На этом этапе я также разделяю данные для обучения и тестирования. Из 600 данных, которые у меня были раньше, я разделил данные на 420 данных для обучения и 180 данных для тестирования.

X = df[['lemmatized_review', 'review_len', 'punct']]
y = df['label']
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.3, random_state = 0)
from sklearn.feature_extraction.text import TfidfVectorizer
tfidf = TfidfVectorizer(max_df = 0.5, min_df = 2)
tfidf_train = tfidf.fit_transform(X_train['lemmatized_review'])
tfidf_test = tfidf.transform(X_test['lemmatized_review'])

X_train_vect = pd.concat([X_train[['review_len', 'punct']].reset_index(drop=True), 
           pd.DataFrame(tfidf_train.toarray())], axis=1)
X_test_vect = pd.concat([X_test[['review_len', 'punct']].reset_index(drop=True), 
           pd.DataFrame(tfidf_test.toarray())], axis=1)

X_train_vect.head()

Проверьте производительность алгоритма классификации

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

В этом примере я просто хочу использовать алгоритм SVM (Support Vector Machine), потому что после того, как я протестирую некоторые другие алгоритмы классификации, окажется, что SVM имеет наилучшую производительность в моем случае. Я также использую SVM для реализации в своем веб-приложении.

from sklearn.svm import SVC
classifier = SVC(kernel = 'linear', random_state = 0)
classifier.fit(X_train_vect, y_train)
svm_pred = classifier.predict(X_test_vect)
# Classification report
print(classification_report(y_test, svm_pred))
# Confusion Matrix
class_label = ["negative", "positive"]
df_cm = pd.DataFrame(confusion_matrix(y_test, svm_pred), index=class_label, columns=class_label)
sns.heatmap(df_cm, annot=True, fmt='d')
plt.title("Confusion Matrix")
plt.xlabel("Predicted Label")
plt.ylabel("True Label")
plt.show()

Веб-реализация

После того, как я решил, какой алгоритм я хочу использовать, последнее, что я сделал, — это внедрил часть приведенного выше кода (с некоторыми корректировками) в веб-приложение. Я использовал Flask для веб-разработки. Полный код можно найти в моем GitHub Repository.