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

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

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

В этом посте мы будем использовать обширные алгоритмы машинного обучения и функции NLTK для извлечения тегов фильмов из их синопсисов. Итак, приступим!

Источник данных

Данные, которые мы использовали в этом тематическом исследовании, собраны из набора данных в Kaggle, который состоит из более чем 14k фильмов и около 142 уникальных тегов.

Взглянем на данные:

Из приведенной выше таблицы мы видим, что каждый фильм имеет синопсис и несколько разнородных тегов, соответствующих им. Также данные собираются из IMDB и Википедии.

Исследовательский анализ данных

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

  1. Количество тегов на вопрос.
  2. Наиболее распространенные теги.
  3. Частота тегов в наборе данных.

Количество тегов на вопрос: -

Мы заметили после выполнения нескольких вычислений, что у большинства фильмов есть 1 тег. Среднее количество тегов на фильм - 2,99.

Наиболее частые теги: -

Для этого мы построим облако слов и визуализируем.

Частота тегов: -

Проанализировав приведенный выше EDA, мы пришли к выводу, что некоторые фильмы имеют большое количество тегов, но большинство фильмов имеют только один или два тега. Убийство, насилие, воспоминания и романтика - самые частые четыре тега в корпусе. Реже всего встречаются теги "глупый", "умный", "приятный", "клаустрофобный".

Очистка и предварительная обработка аннотаций сюжетов

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

Следовательно, на этапе предварительной обработки мы делаем следующее в указанном ниже порядке: -

  1. Начните с удаления тегов HTML
  2. Удалите знаки препинания или ограниченный набор специальных символов, например, или. или # и т. д.
  3. Проверьте, состоит ли слово из английских букв и не является ли оно буквенно-цифровым.
  4. Убедитесь, что длина слова больше 2 (так как было исследовано, что нет прилагательного, состоящего из двух букв)
  5. Преобразуйте слово в нижний регистр
  6. Удалить стоп-слова
  7. Слова лемматизированы - слова в третьем лице заменяются на первое, а глаголы в прошедшем и будущем времени заменяются на настоящее.
  8. Наконец, Snowball Stemming слово (было замечено, что оно лучше, чем Porter Stemming)

Теперь, когда мы вкратце ознакомились с анализом тегов и в большей степени очистили краткое изложение сюжета, давайте перейдем к самой интересной части.

Подход машинного обучения для прогнозирования тегов с помощью кратких обзоров

Основная работа - разделить данные на обучающие и тестовые наборы. Железнодорожная часть набора будет использоваться для обучения модели. Мы используем тестовую часть для перекрестной проверки и тестирования обученной модели. Для этого мы разбиваем данные в соответствии с заданным столбцом split в текстовом корпусе.

x_train=new_data.loc[(new_data['split'] == 'train') |(new_data['split'] == 'val')]
x_test=new_data.loc[(new_data['split'] == 'test')]

Затем мы определяем случайные базовые модели для сравнения производительности предложенной нами модели в задаче прогнозирования тегов для фильмов. Базовые модели присваивают все теги всем фильмам.

Но прежде чем двигаться дальше, давайте разберемся в нескольких терминах:

Класс Pipeline позволяет объединить несколько процессов в одну оценку Scikit-learn. Мы использовали бы то же самое в начальной части.

Классификатор OneVSRest. Для каждого классификатора класс сопоставляется со всеми другими классами.

CountVectorizer предоставляет простой способ как токенизировать коллекцию текстовых документов и создать словарь известных слов, так и кодировать новые документы с использованием этого словаря.

TfidfTransformer преобразует матрицу подсчета в нормализованное представление tf или tf-idf. И CountVectorizer, и TfidfTransformer (с use_idf = False) выдают частоты терминов, TfidfTransformer нормализует счет.

Макросреднее вычисляет точность, отзывчивость и оценку f1 независимо для каждого класса, а затем взять среднее значение (следовательно, рассматривать все классы одинаково), тогда как микро-среднее будет агрегировать вклад всех классов для вычисления среднего показателя. В настройке мультиклассовой классификации предпочтительнее использовать микро-среднее, если вы подозреваете, что может быть дисбаланс классов (т.е. у вас может быть намного больше примеров одного класса, чем других классов).

Все вышеперечисленные термины нуждаются в более широком объяснении, но это история другого дня.

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

Micro-average quality numbers
Precision: 0.1586, Recall: 0.3639, F1-measure: 0.2209
Macro-average quality numbers
Precision: 0.0733, Recall: 0.1752, F1-measure: 0.0969
               precision    recall  f1-score   support
avg / total       0.20      0.36      0.25      9020

Мы наблюдаем, что модель не работает хорошо, имея только 25% микро-f1. Попробуем построить другую модель, используя настройку гиперпараметров grid-search. В качестве алгоритма классификации мы использовали бы регрессию логистической регрессии вместе с классификатором oneVSRest.

vectorizer = CountVectorizer(min_df=0.00009, max_features=50000, tokenizer = lambda x: x.split())
vectorizer.fit(x_train['preprocessed_data'])
x_train_multilabel = vectorizer.transform(x_train['preprocessed_data'])
x_test_multilabel = vectorizer.transform(x_test['preprocessed_data'])

Перекрестная проверка поиска по сетке:

0.10172648121582308
{'estimator__alpha': 1e-05}

Теперь мы строим модель, используя лучший гиперпараметр 1e-05, который мы получили с помощью Grid-Search.

Micro-average quality numbers
Precision: 0.0678, Recall: 0.3704, F1-measure: 0.1146
Macro-average quality numbers
Precision: 0.0471, Recall: 0.3029, F1-measure: 0.0717
              precision    recall  f1-score   support
avg / total       0.16      0.37      0.21      9020

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

Функциональная инженерия

Теперь, чтобы улучшить характеристики модели, существует острая необходимость в проектировании функций. Перед тем, как приступить к проектированию функций, мы рассмотрели следующие параметры:

  1. извлекать словарные n-граммы (n = 1,2,3)
  2. символьные n-граммы (n = 3,4)
  3. пропустить n-граммы (n = 2,3)

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

Выше мы видели, что теги в синопсисе распределяются неравномерно. Некоторые из синопсисов содержат ›20 тегов, а некоторые - 1 тег. Но в среднем количество тегов в синопсисе составляет 2,9 (см. EDA). Таким образом, мы рассмотрим 4 тега на синопсис сюжета и проведем наши операции.

vectorizer = CountVectorizer(tokenizer = lambda x: x.split(','), binary='true', max_features = 4)
multilabel_y_train = vectorizer.fit_transform(y_train)
multilabel_y_test = vectorizer.transform(y_test)

Что такое н-грамм, униграмм, биграмм, триграмм?

В области компьютерной лингвистики и вероятности n-грамма - это непрерывная последовательность n элементов из заданного образца текста или речи. Используя латинские числовые префиксы, n-грамм размера 1 упоминается как униграмма; размер 2 - это биграмма (или, реже, биграмма); размер 3 - это триграмма. (вики)

Юниграмма

0.4297968028569633
{'estimator__alpha': 0.001}

Примечание: - SGDClassifier с log убытком - это Логистическая регрессия. Теперь построим модель юниграммы.

Micro-average quality numbers
Precision: 0.3311, Recall: 0.6079, F1-measure: 0.4287
Macro-average quality numbers
Precision: 0.3223, Recall: 0.5954, F1-measure: 0.4118
              precision    recall  f1-score   support

           0       0.20      0.54      0.29       308
           1       0.25      0.53      0.34       507
           2       0.48      0.64      0.55       844
           3       0.36      0.68      0.47       552

   micro avg       0.33      0.61      0.43      2211
   macro avg       0.32      0.60      0.41      2211
weighted avg       0.36      0.61      0.44      2211

Мы наблюдаем, что показатель f1, точность, отзывчивость модели значительно увеличились. Точно так же проделываем ту же операцию для биграммы, изменяя ngram_range=(2,2), триграмму ngram_range=(3,3) и ngram_range=(1,3). Мы заметили, что все модели работают лучше, чем наша базовая модель. После того, как мы построим все n-граммовые модели. Мы объединяем функции и запускаем поверх него наш классификатор.

Униграмм + биграмм + н-грамм

Есть очень хороший метод объединения разреженных матриц, предоставляемый Scipy, который называется hstack или горизонтальное наложение. Мы бы использовали то же самое, чтобы объединить наши функции.

from scipy.sparse import coo_matrix, hstack
train_1 = hstack((x_train_multilabe_uni, x_train_multilabe_bi),format="csr",dtype='float64')
test_1 = hstack((x_test_multilabel_uni, x_test_multilabel_bi),format="csr",dtype='float64')
train_2 = hstack((train_1, x_train_1),format="csr",dtype='float64')
test_2 = hstack((test_1, x_test_1),format="csr",dtype='float64')

Модель:

Micro-average quality numbers
Precision: 0.3509, Recall: 0.6065, F1-measure: 0.4446
Macro-average quality numbers
Precision: 0.3358, Recall: 0.5894, F1-measure: 0.4232
              precision    recall  f1-score   support

           0       0.21      0.55      0.30       308
           1       0.28      0.47      0.35       507
           2       0.48      0.67      0.56       844
           3       0.38      0.68      0.48       552

   micro avg       0.35      0.61      0.44      2211
   macro avg       0.34      0.59      0.42      2211
weighted avg       0.37      0.61      0.46      2211

Мы видим, что производительность снова улучшилась. Но давайте углубимся еще немного и посмотрим, сможем ли мы улучшить наш результат f1 на ›50%.

Char 3-граммовый

Символьная n-грамма - это непрерывная последовательность из n символов из заданного образца текста или речи. Единственное изменение, которое мы вносим во время преобразования Char 3-gram, - это analyzer = 'char' в качестве параметра функции векторизации.

vectorizer = TfidfVectorizer(sublinear_tf=True, strip_accents='unicode', analyzer='char', ngram_range=(3, 3),  max_features=20000)
x_train_3char = vectorizer.fit_transform(X_train)
x_test_3char = vectorizer.transform(X_test)

Аналогичным образом поступаем с поиском по сетке и моделированием.

Micro-average quality numbers
Precision: 0.3567, Recall: 0.6680, F1-measure: 0.4651
Macro-average quality numbers
Precision: 0.3408, Recall: 0.6407, F1-measure: 0.4418
              precision    recall  f1-score   support

           0       0.22      0.52      0.31       308
           1       0.28      0.62      0.38       507
           2       0.49      0.74      0.59       844
           3       0.38      0.68      0.49       552

   micro avg       0.36      0.67      0.47      2211
   macro avg       0.34      0.64      0.44      2211
weighted avg       0.37      0.67      0.48      2211

Так же проделываем ту же операцию для Char 4-грамм, но кладем analyzer =char ngram_range = (4,4) и получаем следующий результат.

               precision    recall  f1-score
weighted avg       0.39      0.65      0.49

Мы близки к отметке 50% по шкале f1. Мы также пытались построить модель, объединив функции char 3 и char 4-gram, но модель не показала каких-либо значительных улучшений.

Выше мы видели при выполнении EDA, что среднее количество тегов в синопсисе составляет 2,9. Итак, теперь мы будем рассматривать 3 тега на синопсис сюжета. И давайте посмотрим, сможем ли мы превысить отметку в 50%.

Модели с 3 метками

Как обычно, мы начинаем с двоичной векторизации меток классов и на этот раз с трех классов.

vectorizer = CountVectorizer(tokenizer = lambda x: x.split(','), binary='true', max_features = 3)
multilabel_y_train = vectorizer.fit_transform(y_train)
multilabel_y_test = vectorizer.transform(y_test)

Поскольку модели объяснены выше, мы построили униграммы, биграммы и триграммы, но они вели себя так же, как наши 4-х классные модели этикеток. Затем мы попробовали модели уровней персонажей и бум !! Характеристики модели достигли отметки 50% по шкале f1.

Для char 3-грамм с использованием 3-го уровня класса мы получили следующий результат.

               precision    recall  f1-score   support

           0       0.28      0.58      0.38       507
           1       0.50      0.73      0.59       844
           2       0.39      0.63      0.48       552

   micro avg       0.39      0.66      0.49      1903
   macro avg       0.39      0.65      0.48      1903
weighted avg       0.41      0.66      0.50      1903

Аналогично для 4-граммового угля:

               precision    recall  f1-score   support

           0       0.29      0.61      0.40       507
           1       0.49      0.74      0.59       844
           2       0.41      0.68      0.51       552

   micro avg       0.41      0.69      0.51      1903
   macro avg       0.40      0.68      0.50      1903
weighted avg       0.42      0.69      0.52      1903

Резюме и наблюдение

Заключение и следующие шаги:

  1. Первоначально мы построили базовые модели. Основная проблема с базовыми моделями заключалась в том, что синопсисы сюжета были несбалансированными, а количество тегов на синопсис сильно варьировалось.
  2. В EDA мы заметили, что в среднем каждый фильм имеет 2,9 тега. Итак, мы выбрали 4 тега и 3 тега для создания двух наборов моделей и в значительной степени проанализировали их.
  3. В случае моделей с 4 тегами (3 символа + 4 символа) модель показала наилучшие результаты с показателем micro-f1 49%, что является значительным увеличением по сравнению с базовыми моделями.
  4. В случае моделей с 3 тегами (3 символа + 4 символа) модель показала наилучшие результаты с показателем micro-f1 51%, что также является значительным увеличением по сравнению с базовыми моделями.
  5. Я заметил одну вещь: мои модели работали лучше, чем модели, которые они использовали в исследовательской работе, что вызывает удовлетворение.
  6. Наконец, я также использовал семантическую векторизацию, такую ​​как word2vec, glove vectors и Latent Dirichlet Allocation (LDA), чтобы улучшить мою модель, но они мало помогли.

Исходный код можно найти в моем репозитории Github. Я с нетерпением жду любых отзывов или вопросов.

Исследовательская статья: https://arxiv.org/pdf/1802.07858.pdf

Источник данных: https://www.kaggle.com/cryptexcode/mpst-movie-plot-synopses-with-tags#mpst_full_data.csv