Часть 1 — Основы
ссылка на мой Github для получения дополнительного кода: https://github.com/charliezcr/Sentiment-Analysis-of-Movie-Reviews/blob/main/sa_p1.ipynb
Когда у вас есть большое количество обзоров фильмов, как вы можете узнать, являются ли они комплиментами или критическими замечаниями? Поскольку объем набора данных велик, вы не можете аннотировать их один за другим, а должны использовать инструменты обработки естественного языка для классификации тональности текста. Особенно в Python мощные пакеты, такие как nltk и scikit-learn, могут помочь нам в классификации текста. В этом проекте я провел анализ настроений рецензий на фильмы из набора данных reviews on imdb из набора данных Sentiment Labeled Sentences Data Set репозитория машинного обучения UCI.
Предварительная обработка текста
В этом наборе данных есть 1000 отзывов о фильмах, в том числе 500 положительных (комплименты) и 500 отрицательных (критика). Например, "Очень, очень, очень медленный, бесцельный фильм о несчастном, сбивающемся с пути молодом человеке" помечается как отрицательный отзыв.
Однако в необработанном тексте мы видим, что многие слова не содержат важной семантики. Цифры и знаки препинания часто появляются в необработанном тексте, но они не выражают положительного или отрицательного настроения. Поэтому нам нужно убрать цифры и знаки препинания.
Чтобы еще больше очистить данные, нам нужно составить основу слов, чтобы слова с разной интонацией можно было считать одними и теми же токенами, потому что они передают одинаковую семантику. например «бедствие» и «бедствие» будут иметь корень как «бедствие».
После предварительной обработки текста у нас есть 2 списка данных. «метки» — это список целей нашей классификации. «предварительно обработанный» — это будущие функции классификации. Для предложений с «предварительной обработкой» мы переводим необработанный текст в совершенно нечитаемый текст. Например, 'Очень, очень, очень медленный, бесцельный фильм о несчастном, блуждающем по течению молодом человеке.' предварительно обрабатывается как 'очень, очень, очень медленный, бесцельный фильм о бедствие дрейфует молодой человек '
Вы можете задаться вопросом: мы переводим необработанный текст в этот нечитаемый текст, потому что мы хотим, чтобы каждый токен передал важную семантику. Тогда почему бы не убрать стоп-слова, потому что они не передают важную семантику, но встречаются очень часто, например, «а» и «о»? В следующем разделе извлечения функций мы собираемся использовать TF-IDF, чтобы позаботиться об этих стоп-словах.
Извлечение признаков
После предварительной обработки текста мы собираемся извлечь функции из наших очищенных данных. Мы собираемся использовать векторизатор TF-IDF в качестве встраивания слов для векторизации и нормализации текста.
TF-IDF расшифровывается как частота документа, обратная частоте. Он оценивает важность токена для документа в корпусе. TF-IDF делает данные нашей моделью, потому что нормализует частоту терминов или просто количество слов. Это также уменьшает шум стоп-слов.
В этом случае мы возьмем вариант TF-IDF. Формула обычного TF-IDF находится здесь. В отличие от исходного TF-IDF, мы используем sublinear_tf, заменяя TF на WF = 1 + log(TF). Этот вариант решает проблему, заключающуюся в том, что двадцать вхождений термина в документе на самом деле не несут в двадцать раз больше значимости, чем одно вхождение. очень медленный, бесцельный фильм о бедствующем, блуждающем по течению молодом человеке. очень появилось три раза. Поэтому для нашего набора данных нам нужно применить сублинейное масштабирование TF. Это резко повышает точность предсказания наших моделей позже.
После извлечения признаков у нас есть взвешенная по Tf-IDF матрица терминов документа, сохраненная в формате Compressed Sparse Row. Каждая цель — это настроение этого предложения. «1» означает положительный результат, а «0» — отрицательный. Но чтобы данные соответствовали нашей модели, нам нужно разделить наши данные на функции и цели. Train_test_split от Scikit-learn для случайного перемешивания данных и разделения их на набор для обучения и набор для тестирования. В этом конкретном случае я буду использовать 1/5 всего набора данных для тестирования, а остальные 4/5 — в качестве обучающего набора. Вот код всего процесса предварительной обработки:
from nltk.stem import PorterStemmer # stem the words from nltk.tokenize import word_tokenize # tokenize the sentences into tokens from string import punctuation from sklearn.feature_extraction.text import TfidfVectorizer # vectorize the texts from sklearn.model_selection import train_test_split # split the testing and training sets def preprocess(path): '''generate cleaned dataset Args: path(string): the path of the file of testing data Returns: X_train (list): the list of features of training data X_test (list): the list of features of test data y_train (list): the list of targets of training data ('1' or '0') y_test (list): the list of targets of training data ('1' or '0') ''' # text preprocessing: iterate through the original file and with open(path, encoding='utf-8') as file: # record all words and its label labels = [] preprocessed = [] for line in file: # get sentence and label sentence, label = line.strip('\n').split('\t') labels.append(int(label)) # remove punctuation and numbers for ch in punctuation+'0123456789': sentence = sentence.replace(ch,' ') # tokenize the words and stem them words = [] for w in word_tokenize(sentence): words.append(PorterStemmer().stem(w)) preprocessed.append(' '.join(words)) # vectorize the texts vectorizer = TfidfVectorizer(stop_words='english', sublinear_tf=True) X = vectorizer.fit_transform(preprocessed) # split the testing and training sets X_train, X_test, y_train, y_test = train_test_split(X, labels, test_size=0.2) return X_train, X_test, y_train, y_test X_train, X_test, y_train, y_test = preprocess('imdb_labelled.txt')
Моделирование
Мы можем обучать модели с помощью тренировочного набора, позволять моделям классифицировать набор для тестирования и оценивать производительность моделей, проверяя их показатели точности и затраты времени. Вот код классификации:
from sklearn.metrics import accuracy_score from sklearn.metrics import plot_confusion_matrix from matplotlib import pyplot as plt from time import time def classify(clf, todense=False): '''to classify the data using machine learning models Args: clf: the model chosen to analyze the data todense(bool): whether to make the sparse matrix dense ''' global X_train, X_test, y_train, y_test t = time() if todense: clf.fit(X_train.todense(), y_train) y_pred = clf.predict(X_test.todense()) else: clf.fit(X_train, y_train) y_pred = clf.predict(X_test) print(f'Time cost of {str(clf)}: {round(time()-t,2)}s\nThe accuracy of {str(clf)}: {accuracy_score(y_test,y_pred)}\n')
Поскольку цель является категориальной и дихотомической, функции не имеют предполагаемого распределения, модели, которые мы можем использовать для классификации текста, — это логистическая регрессия, классификатор стохастического градиентного спуска (SGDClassifier), классификатор опорных векторов (SVC) и нейронная сеть (MLPClassifier). Поскольку наши данные о функциях скудны, SVC и SGD полезны. Среди трех типов наивных байесовских классификаторов (бернуллиевский, полиномиальный и гауссовский) нам нужно выбрать полиномиальный, потому что функции нормализуются TF-IDF. Эти функции не соответствуют распределению Гаусса или Бернулли.
Ниже я рассмотрю производительность каждой выбранной модели. В этой части я не буду настраивать параметры для каждой модели, но сделаю это в будущем.
Технически мы также можем использовать линейный дискриминантный анализ. Однако вычисление разреженных матриц, подобных нашим данным объектов, требует больших вычислительных ресурсов. Точность этой модели также низкая. Поэтому в этот раз мы не будем рассматривать LDA. Вот производительность LDA:
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis classify(LinearDiscriminantAnalysis(),todense=True)
Затраты времени на LinearDiscriminantAnalysis(): 0,79 с
Точность LinearDiscriminantAnalysis(): 0,71
Вот характеристики выбранных моделей:
from sklearn.linear_model import LogisticRegression from sklearn.naive_bayes import MultinomialNB from sklearn.svm import SVC from sklearn.linear_model import SGDClassifier from sklearn.neural_network import MLPClassifier for model in [LogisticRegression(), MultinomialNB(), SVC(), SGDClassifier(), MLPClassifier()]: classify(model)
Стоимость времени для LogisticRegression(): 0,03 с
Точность LogisticRegression(): 0,825
Стоимость времени для MultinomialNB(): 0,0 с
Точность MultinomialNB(): 0,825
Стоимость времени для SVC(): 0,09 с
Точность SVC(): 0,835
Стоимость времени для SGDClassifier(): 0,0 с
Точность SGDClassifier(): 0,82
Стоимость времени для MLPClassifier(): 3,47 с
Точность MLPClassifier(): 0,81
ансамблевое обучение
Хотя мы хотим повысить точность предсказания наших моделей, мы также хотим избежать переобучения, чтобы мы могли использовать модели для предсказания других наборов данных. Построение ансамблевого метода является решением этой проблемы. Для каждого обзора мы собираемся позволить каждой выбранной модели голосовать за свой собственный прогноз и использовать режим всех голосов для создания прогноза ансамбля. Выбранными моделями являются логистическая регрессия, MultinomialNB, SVC и SGD. Поскольку нейронные сети требуют сложной настройки и отнимают много времени, я не буду включать MLPClassifier в этот ансамбль обучения. Из приведенной ниже оценки точности и матрицы путаницы видно, что, несмотря на увеличение временных затрат, производительность ансамблевой модели является удовлетворительной.
from statistics import mode def ensemble(models): '''to ensemble the models and classify the data based on each model's vote Args: models: the list of models chosen to analyze the data ''' global X_train, X_test, y_train, y_test t = time() # iterate through all the models and collect all their predictions y_preds = [] for clf in models: clf.fit(X_train, y_train) y_preds.append(clf.predict(X_test)) # Count their votes and get the mode of each prediction as the decision y_pred = [] for i in range(len(y_preds[0])): y_pred.append(mode([y[i] for y in y_preds])) print(f'Time cost: {round(time()-t,2)}s\nAccuracy: {accuracy_score(y_test,y_pred)}\n') plot_confusion_matrix(clf, X_test, y_test, values_format = 'd',display_labels=['positive','negative']) ensemble([LogisticRegression(),MultinomialNB(),SVC(),SGDClassifier()])
Стоимость времени: 0,12 с
Точность: 0,83