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

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

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

Общий обзор проекта:

  1. Раздел описания содержит оригинальные выдержки из исследовательской работы, опубликованной Судиптой Кар, Сураджем Махаджаном и Тамаром Солорио. Желающие могут прочитать опубликованную ими исследовательскую работу здесь.
  2. Мы определим постановку задачи и сопоставим ее с реальной проблемой машинного обучения.
  3. Мы поговорим о реальных бизнес-целях и ограничениях.
  4. Мы определим метрики оценки и поймем, почему выбранный KPI полезен для такого типа проблем.
  5. Мы проведем исследовательский анализ данных, чтобы извлечь больше информации из предоставленных данных. В разделе EDA мы проанализируем распределение тегов, наиболее часто встречающиеся теги, а также проанализируем сводки сюжетов для каждого из фильмов. Мы также узнаем больше об источниках данных, количестве присутствующих повторяющихся записей, распределении количества фильмов и т. Д.
  6. Мы также попытаемся расшифровать некоторую базовую статистическую информацию, используя статистические инструменты, такие как среднее значение, медиана, анализ гистограммы, PDF и CDF.
  7. В разделе предварительной обработки данных мы будем обрабатывать данные, удаляя общие теги html, знаки препинания, буквенно-цифровые слова и стоп-слова. Мы разделим данные на две группы - одну со стеммингом и одну без него. Подробнее о противодействии позже!
  8. Мы также будем использовать кластеризацию K-средних для кластеризации тегов, чтобы понять, какие из них чаще встречаются вместе.
  9. Наконец, мы перейдем к построению моделей машинного обучения с использованием различных функций, таких как n-граммы слов с векторизаторами TFIDF, символьные n-граммы с векторизаторами TFIDF и модели Word2Vec, разработанные группой исследователей под руководством Томаса Миколова. »В Google. Для тех, кто интересуется W2V, вы можете прочитать об актуальном исследовании по этой ссылке.
  10. Наконец, мы завершим проект, сравнив характеристики всех построенных нами моделей.

Мультиклассовая и многокомпонентная классификация:

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

В случае задач классификации с несколькими метками один экземпляр данных может одновременно принадлежать двум или более классам целевых переменных. Следовательно, мы можем сказать, что предсказанные классы не исключают друг друга. Этот подход присваивает каждому образцу набор целевых меток. Например, фильм может относиться к любому из этих жанров - криминал, приключения, триллер, драма, либо он может принадлежать ко всем четырем жанрам одновременно. Помните Dark Knight Rises? (Хотя это один из моих самых любимых фильмов!) Итак, ключевой вывод заключается в том, что в задачах классификации с несколькими метками нет ограничений на то, скольким классам может быть назначен каждый из экземпляров данных!

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

Постановка проблемы и сопоставление ее с реальной проблемой машинного обучения.

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

Каждая точка данных имеет следующие атрибуты.

  1. ID: уникальный идентификатор, содержащий идентификатор фильма.
  2. Название: этот атрибут заголовка содержит название фильма.
  3. Plot_Synopsis: это поле содержит краткое содержание сюжета фильма.
  4. Теги: этот атрибут содержит информацию обо всех тегах, которым назначен фильм.
  5. Разделить: положение фильма в стандартном разделении данных указывает, принадлежит ли точка данных к обучающим, тестовым или проверочным данным.
  6. Synopsis_Source: источник, из которого был получен синопсис сюжета для каждого фильма - либо IMDB, либо Wikipedia.

Ограничения реального бизнеса:

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

Метрики оценки.

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

Сначала давайте разберемся, что такое точность, отзывчивость и оценка F1, а затем обсудим, почему микро-усредненный результат F1 является лучшим показателем для решения этой проблемы.

Точность - это в основном отношение истинных положительных результатов (TP) ко всем прогнозируемым положительным результатам (TP + FP). Короче говоря, точность говорит нам, что из всех положительных моментов, предсказываемых моделью, сколько на самом деле положительных? Напомним, это отношение истинных положительных результатов (TP) ко всем фактическим положительным результатам (TP + FN). Напоминание говорит нам, что из общего числа действительно положительных точек какая часть объявляется моделью положительной?

1. Микро-усредненная точность и отзывчивость.

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

2. Микро-усредненный результат F1

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

В случае микро-среднего балла F1 мы фактически даем вес в зависимости от того, как часто встречается этикетка. Давайте посмотрим на пример. Предположим, у нас есть три тега (метки) t1, t2 и t3, распределенные по всему набору данных. Предположим, что t1 встречается почти в 90% данных фильмов, t2 - в 1% данных, а t3 - в 80% данных. Теперь, если мы рассмотрим отдельные истинные положительные результаты для всех этих тегов, вклад t2 будет меньше как в числителе, так и в знаменателе в обеих формулах, приведенных на рис. 1. Следовательно, TP и FP для t2 будут небольшими, потому что общее количество само по себе невелико. Впоследствии вклад t1 и t3 будет высоким, поскольку они встречаются в гораздо большем количестве точек данных во всем наборе данных. Вкратце, микро-усредненная оценка f1 учитывает частоту метки (тега). Интуитивно говоря, даже если взвешенный отзыв и точность для тега t2 очень низкие, и у нас есть высокая точность и отзыв для тегов t1 и t3, мы все равно получим довольно высокий микро-усредненный балл F1 из-за более высокого вклада тегов t1 и t3. Таким образом, мы можем легко сделать вывод, что микро-усредненная оценка F1 работает очень хорошо, когда у нас очень несбалансированное распределение тегов.

3. Макро-средний балл F1

Давайте сначала посмотрим на формулу для макро-усредненной оценки F1, а затем мы интуитивно поймем, как она работает!

У нас есть три тега t1, t2 и t3. В случае макро-усредненной оценки F1 мы рассчитаем точность, отзыв и последующие оценки F1 для каждой из этих меток (или тегов). Затем мы суммируем все индивидуальные оценки F1 для всех тегов и делим их на общее количество образцов (в данном случае 3 образца). Однако макроэкономическая оценка F1 не работает, когда дело доходит до очень несбалансированного распределения тегов. Также не учитывается частота появления тегов. Следовательно, когда дело доходит до классификации с несколькими метками и у нас очень несбалансированное распределение тегов, микро-усредненный показатель F1 должен быть нашим основным предпочтительным показателем просто потому, что он принимает во внимание частоту появления тегов.

4. Хэмминга Лосс.

Потери Хэмминга в основном измеряют точность в задаче классификации с несколькими метками. Формула потери Хэмминга определяется следующим образом:

В приведенной выше формуле

| D | - это общее количество имеющихся у нас выборок или точек данных.
| L | - это общее количество различных меток (или тегов), которые у нас есть. .
Yi - это основная истина.
Xi - прогнозируемые значения для каждого точка данных.

Как обсуждалось выше, предположим, что у нас есть три тега t1, t2 и t3. Пусть Yi будет истинным значением для точки данных i, и он будет представлен двоичным вектором [1,1,1], где точка данных i принадлежит всем трем тегам одновременно. Пусть Xi, предсказанный двоичный вектор для точки данных i, заданный моделью, равен [1,0,1]. Это означает, что модель правильно предсказала теги t1 и t3 и не смогла предсказать тег t2. В этом случае XOR [1,1,1] и [1,0,1] будет [0,1,0]. Если мы просуммируем все элементы вектора XOR, мы получим результат как 2, а соответствующая потеря Хэмминга будет равна 1/3, что примерно равно 0,33. Помните, что мы делим суммирование XOR на 3, поскольку всего у нас есть 3 тега. Проще говоря, потеря Хэмминга - это доля неправильно предсказанных меток классов от общего числа фактических меток. В случае всех правильно классифицированных тегов, потеря Хэмминга будет идеальным 0.

Функция исследовательского анализа данных тегов.

EDA - это понимание данных. В разделе EDA мы будем задавать вопросы о данных и давать ответы, которые помогут нам понять более сложные детали о самих данных, которые, в свою очередь, помогут нам проанализировать и обобщить ключевую информацию, полученную из любого данного набора данных.

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

1. Загрузка данных, отображение названий столбцов и первых 5 строк.

data=pd.read_csv("mpst_full_data.csv")
print("Columns present in the data: ",[i for i in data.columns])
print("Number of data points: ",data.shape[0])
data.head()

2. Создание файла базы данных SQL из заданного файла CSV и подсчет общего количества точек данных, присутствующих в фактическом наборе данных.

#Learn SQL: https://www.w3schools.com/sql/default.asp
if not os.path.isfile('train.db'):
    start = datetime.now()
    disk_engine = create_engine('sqlite:///train.db')
    start = dt.datetime.now()
    chunksize = 15000
    j = 0
    index_start = 1
    for df in pd.read_csv('mpst_full_data.csv', chunksize=chunksize, iterator=True, encoding='utf-8'):
        df.index += index_start
        j+=1
        df.to_sql('data', disk_engine, if_exists='append')
        index_start = df.index[-1] + 1
print("Time taken to run this cell :", datetime.now() - start)

Здесь мы видим, что у нас в общей сложности 14828 точек данных. Как видно выше, каждая точка данных имеет 6 атрибутов. Из этих 6 атрибутов "теги" - наша целевая переменная. При построении наших моделей машинного обучения мы будем рассматривать только краткое изложение сюжета фильма. Мы можем игнорировать остальные функции, присутствующие в наборе данных.

3. Каково распределение точек данных обучения, проверки и тестирования в данном наборе данных?

#Let's plot a bar plot to get the distribution
plt.figure(figsize=(6, 6))
plt.title ("Number of data points in train, test and validation sets")
sns.barplot(split_info['Data_Type'],split_info['Number_of_Instances'])
plt.show()

Здесь мы видим, что 9489 точек данных принадлежат обучающему набору, 2966 точек данных принадлежат набору тестов, а 2373 точки данных принадлежат набору перекрестной проверки. При построении наших моделей машинного обучения мы объединим данные перекрестной проверки и обучения в одну единую матрицу данных, которая будет использоваться для обучения, а остальные невидимые данные будут использоваться для оценки производительности наших моделей.

4. Проверяете распределение источников данных?

plt.figure(figsize=(6, 6))
plt.title ("Number of data points from each of the distinct sources")
sns.barplot(data_source['Data_Source'],data_source['Number_of_data_points'])
plt.show()

Чтобы быть очень точным, 4172 сюжета фильмов взяты с веб-сайта IMDB и 10656 сюжетов взяты из Википедии.

5. Проверка наличия повторяющихся записей в данном наборе данных.

#Learn SQl: https://www.w3schools.com/sql/default.asp
if os.path.isfile('train.db'):
    start = datetime.now()
    con = sqlite3.connect('train.db')
    df_no_dup = pd.read_sql_query('SELECT title, plot_synopsis, tags, split, COUNT(*) as cnt_dup FROM data GROUP BY title, plot_synopsis, tags, split', con)
    con.close()
    print("Time taken to run this cell :", datetime.now() - start)
print("Number of rows in the original dataset: ",num_rows['count(*)'].values[0])
print("Number of rows in the de-duplicated dataset: ",df_no_dup.shape[0])
print("Total number of duplicate entries removed from the given dataset: ",num_rows['count(*)'].values[0]-df_no_dup.shape[0])
print("Percentage of duplicate entries that were originally present: {} %".format(np.round((num_rows['count(*)'].values[0]-df_no_dup.shape[0])/num_rows['count(*)'].values[0]*100,2)))

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

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

#Plot the rersult in a count plot
plt.figure(figsize=(10,5))
sns.countplot(df_no_dup.cnt_dup, palette='gist_rainbow')
plt.title("Distribution of the number of movies")
plt.xlabel("Number of Occurences")
plt.ylabel("Number of Movies")
plt.show()

Здесь мы видим, что у нас 14743 фильма появляются только один раз, 32 фильма появляются дважды, и очень мало фильмов, которые появлялись более трех раз.

7. Подсчет количества тегов, связанных с каждым фильмом.

#Here we will add a new feature called tags count, which will count the number of tags in each movie 
start = datetime.now()
df_no_dup["tag_count"] = df_no_dup["tags"].apply(lambda text: len(str(text).split(" ")))
print("Time taken to run this cell :", datetime.now() - start)
df_no_dup.head()

8. Проверка распределения количества тегов на фильм.

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

plt.figure(figsize=(10,5))
sns.countplot(df_no_dup.tag_count, palette='gist_rainbow')
plt.title("Distribution of the number of tags per movie")
plt.xlabel("Number of Tags")
plt.ylabel("Number of Movies")
plt.show()

Получим реальные цифры!

df_no_dup.tag_count.value_counts()

Ключевые выводы из двух вышеупомянутых наблюдений:

Существует 5133 фильмов, содержащих только один тег, 2990 фильмов содержат 2 тега, 1924 фильма содержат 3 тега. Максимальное количество тегов в фильме - 24. Это огромное количество! Среднее количество тегов на фильм во всем наборе данных было примерно равно 3 (2,98, если быть точным). Проверив график подсчета распределения тегов по фильму, мы увидели, что распределение сильно смещено влево. Очень много фильмов, содержащих 5 или менее тегов, и очень-очень мало фильмов, содержащих более 5 тегов. Есть почти 550 фильмов, которые содержат 10 и более тегов. И там крайне и крайне мало фильмов, содержащих 20 и более тегов!

9. Давайте найдем общее количество уникальных тегов!

#Importing & Initializing the "CountVectorizer" object, which is scikit-learn's bag of words tool. By default 'split()' will tokenize each tag using space.
def tokenize(x):
    x=x.split(',')
    tags=[i.strip() for i in x] #Some tags contains whitespaces before them, so we need to strip them
    return tags
vectorizer = CountVectorizer(tokenizer = tokenize)
tag_dtm = vectorizer.fit_transform(tag_data['tags'])
print("Number of unique tags present in the entire dataset:", tag_dtm.shape[1])

Выполнив приведенный выше блок кода, мы видим, что всего в наборе данных присутствует 71 уникальный тег.

Обратите внимание, это очень важно. Нам нужно написать специальную функцию tokenize (), которая не только будет разделять теги на основе запятой, но также будет обрезать любые пробелы, присутствующие до или после тегов. Было обнаружено, что без использования настраиваемой функции абсурд и абсурд идентифицируются как два отдельных тега. Точно так же романтика и романтика были идентифицированы как два отдельных тега. Это всего лишь два из многих примеров, которые я хотел осветить, чтобы оправдать необходимость в специальной функции tokenize (). Здесь мы использовали класс CountVectorizer, определенный в scikit-learn, для преобразования тегов в двоичный пакет представлений слов.

10. Давайте посмотрим, сколько раз появлялся каждый тег.

«Tag_dtm» - это разреженная матрица, которая содержит двоичный пакет слов, представляющих все теги. Чтобы вычислить, сколько раз появлялся каждый тег, мы просто просуммируем количество появлений каждого тега по столбцам. Это даст нам общее количество появлений каждого тега во всем наборе данных. Мы создадим отдельный объект фрейма данных для хранения этой информации и сортировки ее в порядке убывания наиболее частого количества тегов.

#https://stackoverflow.com/questions/15115765/how-to-access-sparse-matrix-elements
#Lets now store the document term matrix in a dictionary.
freqs = tag_dtm.sum(axis=0).A1 #axis=0 for columns. Column contain the number of times the tags have occured
result = dict(zip(tags, freqs))
#Saving this dictionary to csv files.
if not os.path.isfile('tag_counts_dict_dtm.csv'):
    with open('tag_counts_dict_dtm.csv', 'w') as csv_file:
        writer = csv.writer(csv_file)
        for key, value in result.items():
            writer.writerow([key, value])
tag_df = pd.read_csv("tag_counts_dict_dtm.csv", names=['Tags', 'Counts'])
tag_df_sorted = tag_df.sort_values(['Counts'], ascending=False)
tag_counts = tag_df_sorted['Counts'].values
tag_df_sorted.head(10)

Здесь мы видим, что тег «убийство» встречается 5771 раз, за ​​ним следует «насилие», которое встречается 4423 раза. Тег «флэшбэк» встречается 2937 раз, за ​​которым следует тег «романтический», который встречается 2900 раз.

11. Распределение всех тегов, т. Е. Количество раз, когда каждый тег появлялся в отрывках фильмов.

Мы видим, что распределение сильно смещено влево. Очень мало тегов, которые встречаются в более чем 5000 экземплярах данных. Появляется более 10 тегов из общего числа тегов, если не менее 1000 точек данных.

12. Давайте рассмотрим статистическое представление фрейма данных тегов на высоком уровне.

#Get a high level statistical view of the tags data
tag_df_sorted.describe()

Из вышеприведенного вывода мы можем сделать вывод, что минимальное количество тегов - 37, а максимальное значение достигает 5771. Почти 50% тегов встречается 233 раза. 25% тегов встречается более 570 раз. Мы также можем получить всю эту информацию, резюмированную на диаграмме ниже.

13. Дополнительный анализ тегов.

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

#Storing the count of tag in each question in list 'tag_count'
tag_quest_count = tag_dtm.sum(axis=1).tolist()
#Converting each value in the 'tag_quest_count' to integer.
tag_quest_count=[int(j) for i in tag_quest_count for j in i]
print ('We have total {} datapoints.'.format(len(tag_quest_count)))

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

На рис. 19 мы получаем много полезной информации о распределении тегов. Мы видим, что у нас есть 10551 фильм, который содержит 3 или меньше тегов, 11789 фильмов, которые содержат 4 или меньше тегов., 12705 фильмов содержат 5 или меньше тегов и 13311 фильмов содержат менее 6 тегов. Мы будем хранить эту информацию в привязке при разработке наших моделей машинного обучения.

14. Основные наблюдения до настоящего момента:

  1. 75% тегов встречается менее 570 раз в разных фильмах.
    2. 25% тегов встречается менее 119 раз в разных фильмах.
    3. Максимальное количество раз, когда тег встречается в фильме, составляет 5771
    4. Всего 9 тегов используются более 1000 раз.
    5. 1 тег используется более 5000 раз.
    6. Наиболее частый тег (например, «убийство») используется 5771 раз
    7. Так как некоторые теги встречаются намного чаще других, микро-среднее значение 8. F1-score является подходящей метрикой для этой проблемы.
    9. Минимальное количество тегов в сюжете фильма. равно 1.
    10. Максимальное количество тегов в сюжете фильма - 25
    11. Среднее количество тегов на фильм было близко к 3.
    12. 10551 фильм имел теги меньше или равные до 3.
    13. 11789 фильмов имели теги меньше или равные 4.
    14. 12705 фильмов имели теги меньше или равные 5.
    15. 13331 фильм имел теги меньше или равно 6.

15. Облако слов для наиболее часто встречающихся тегов в сюжетах синопсисов фильмов.

# Ploting word cloud
start = datetime.now()
#Lets first convert the 'result' dictionary to 'list of tuples'
tup = dict(result.items())
#Initializing WordCloud using frequencies of tags.
wordcloud = WordCloud(background_color='black',width=1600,height=800,).generate_from_frequencies(tup)
fig = plt.figure(figsize=(15,10))
plt.imshow(wordcloud)
plt.axis('off')
plt.tight_layout(pad=0)
fig.savefig("tag.png")
plt.show()
print("Time taken to run this cell :", datetime.now() - start)

  1. Взгляд на облако слов показывает, что «убийство», «насилие», воспоминания »,« романтика »,« культ »- наиболее часто встречающиеся теги в сюжетах синопсисов фильмов.
  2. Есть много тегов, которые встречаются реже, например, «комедия», «психоделический», «ужас», «развлечение», «юмор» и т. Д.

16. Давайте посмотрим на распределение тегов по частоте их появления.

i=np.arange(50)
tag_df_sorted.head(50).plot(kind='bar', figsize=(15,10), rot=90, color='red')
plt.title('Frequency of top 50 tags')
plt.xticks(i, tag_df_sorted['Tags'])
plt.xlabel('Tags')
plt.ylabel('Number of occurences')
plt.show()

17. EDA использует кластеризацию K-средних на BOW-представлениях тегов.

В этом разделе мы будем использовать алгоритм кластеризации K-средних для группировки тегов в кластеры. Делая это, мы можем понять, какие из тегов чаще встречаются вместе. Полный код см. В разделе 6.9 прилагаемой записной книжки IPython в моем профиле GitHub. Мы выбрали наилучшее оптимальное количество кластеров, используя метод локтя, который минимизирует ошибки WCSS (внутри кластерной суммы квадратов).

  1. Во всех кластерах мы видим, что теги «насилие», «убийство» имеют тенденцию встречаться вместе.
  2. Мы также можем видеть такие теги, как «месть», которые, как правило, встречаются с такими тегами, как «убийство» и «романтика».
  3. Такие теги, как «культ», «зло», «насилие», чаще встречаются вместе.
  4. Такие теги, как «комедия», «мелодрама» и «развлечение», чаще встречаются вместе.
  5. «Психоделический», «тревожный» и «скучный» имеют больше шансов встретиться вместе.

EDA: Анализ текстов заголовков и текста сюжета фильма.

  1. Загрузите дедуплицированный набор данных.
con = sqlite3.connect('train_no_dup.db')
dataframe = pd.read_sql_query("""SELECT * FROM no_dup_train""", con)
con.close()
dataframe.head()

2. Распечатать случайное описание сюжета фильма с его тегами

sent_4 = dataframe['plot_synopsis'].values[4900]
print(sent_4)
print("\nTags: {}".format(dataframe['tags'].values[4900]))
print("="*215)

3. Получите представление о сюжете фильма.

#Utiliy functions for feature extraction
#Returns the count of 'http' elements present in a string. Return 0 otherwise.
def count_http(string):
    if string.__contains__("http"):
        return string.count("http")
    else:
        return int(0)
#Returns the number of times a reference link is present in a string
def count_href(string):
    if string.__contains__("a href"):
        return string.count("a href")
    else:
        return int(0)
    
#Number of times a greater than sign appears in a string    
def count_greater(string):
    if string.__contains__(">"):
        return string.count(">")
    else:
        return int(0)
#Simple feature engineering
basic_feats = pd.DataFrame()
basic_feats["Length_Title"] = dataframe['title'].apply(lambda x: len(str(x))) #Length of RAW Title text
basic_feats["Length_Plot_Synopsis"] = dataframe['plot_synopsis'].apply(lambda x: len(str(x))) #Length of RAW body text
basic_feats['count_plot_synopsis_http'] = dataframe['plot_synopsis'].apply(lambda x: count_http(str(x))) #Lazy way to count the number of URLs present in a body text. Not 100% accurate, but close enough
basic_feats['count_plot_synopsis_href'] = dataframe['plot_synopsis'].apply(lambda x: count_href(str(x))) #Lazy way to count the reference to an externel site. Not 100% accurate, but close enough
basic_feats['count_plot_synopsis_grtsign'] = dataframe['plot_synopsis'].apply(lambda x: count_greater(str(x))) #Very lazy way to count html tags present in a string. Not 100% accurate, but close enough
#Save the dataset containing basic features
basic_feats.to_csv('basic_feats.csv', columns=basic_feats.columns)
basic_feats.head(3)

#Get a high level stats of the given dataset
basic_feats.describe()

#Get the percentage of movies which does not have a http referrence included in their body
zero = basic_feats[basic_feats['count_plot_synopsis_http'] == 0].shape[0]
per = (zero/basic_feats.shape[0]) * 100
print("Percentage of movie plots which does not have any http referrence URL in the body text: {:.2f}%".format(per))
#Get the percentage of questions which are provided with external reference links in their body text
zero = basic_feats[basic_feats['count_plot_synopsis_href'] == 0].shape[0]
per = (1-zero/basic_feats.shape[0]) * 100
print("Percentage of movie plots which does not have any external reference: {:.2f}%".format(per))

Ключевые наблюдения из анализа основных характеристик:

1. Быстрая статистика высокого уровня показала, что средняя длина сюжетов фильма составляет около 5140.
2. Максимальная длина синопсиса сюжета составляет 63959 символов, а минимальная длина - 442 символа!
2. Средняя длина названия фильма составляет около 15
3. 92 - максимальная длина строки для названия фильма, а 1 - минимальная длина строки для названия фильма.
4. 99,99% сюжетов фильма. в их теле нет http-ссылки.
5. Есть 20 сюжетов фильмов со знаком «больше».
6. Практически во всех обзорах есть знаки препинания и сокращенные слова.

#Draw only PDF 
plt.figure(figsize=(30, 8))
plt.subplot(1,3,1)
sns.distplot([basic_feats['Length_Title']], color = 'red', axlabel="Distribution of Length of raw title text")
plt.subplot(1,3,2)
sns.distplot([basic_feats['Length_Plot_Synopsis']], color = 'blue', axlabel="Distribution of Length of raw movie plot text")
plt.subplot(1,3,3)
sns.distplot([basic_feats['count_plot_synopsis_grtsign']], color = 'blue', axlabel="Distribution of '>' sign")
plt.show()

Основные наблюдения:

1. Мы видим, что большая часть текстов заголовков имеет среднюю длину около 15.
2. Средняя длина синопсиса сюжета составляет около 5000. Распределение продолжительности сюжета фильма сильно смещено влево.
3. Очень мало фильмов, длина сюжета которых превышает 20000 знаков.

Раздел очистки данных:

В этом разделе мы применим следующие методы предварительной обработки данных.

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

  1. Служебные функции для очистки данных.
#Remove words with numbers python: https://stackoverflow.com/a/18082370/4084039
def removeNumbers(sentence):
    sentence = re.sub("\S*\d\S*", " ", sentence).strip()
    return (sentence)
#Function to clean html tags from a sentence
def removeHtml(sentence): 
    pattern = re.compile('<.*?>')
    cleaned_text = re.sub(pattern,' ',sentence)
    return cleaned_text
#Remove URL from sentences.
def removeURL(sentence):
    text = re.sub(r"http\S+", " ", sentence)
    sentence = re.sub(r"www.\S+", " ", text)
    return (sentence)
    
#Function to keep only words containing letters A-Z and a-z. This will remove all punctuations, special characters etc. https://stackoverflow.com/a/5843547/4084039
def removePunctuations(sentence):
    cleaned_text  = re.sub('[^a-zA-Z]',' ',sentence)
    return (cleaned_text)
#https://stackoverflow.com/questions/37012948/regex-to-match-an-entire-word-that-contains-repeated-character
#Remove words like 'zzzzzzzzzzzzzzzzzzzzzzz', 'testtting', 'grrrrrrreeeettttt' etc. Preserves words like 'looks', 'goods', 'soon' etc. We will remove all such words which has three consecutive repeating characters.
def removePatterns(sentence): 
    cleaned_text  = re.sub("\\s*\\b(?=\\w*(\\w)\\1{2,})\\w*\\b",' ',sentence)
    return (cleaned_text)
#Expand the movie plots x is an input string of any length. Convert all the words to lower case
def decontracted(x):
    x = str(x).lower()
    x = x.replace(",000,000", " m").replace(",000", " k").replace("′", "'").replace("’", "'")\
                           .replace("won't", " will not").replace("cannot", " can not").replace("can't", " can not")\
                           .replace("n't", " not").replace("what's", " what is").replace("it's", " it is")\
                           .replace("'ve", " have").replace("'m", " am").replace("'re", " are")\
                           .replace("he's", " he is").replace("she's", " she is").replace("'s", " own")\
                           .replace("%", " percent ").replace("₹", " rupee ").replace("$", " dollar ")\
                           .replace("€", " euro ").replace("'ll", " will").replace("how's"," how has").replace("y'all"," you all")\
                           .replace("o'clock"," of the clock").replace("ne'er"," never").replace("let's"," let us")\
                           .replace("finna"," fixing to").replace("gonna"," going to").replace("gimme"," give me").replace("gotta"," got to").replace("'d"," would")\
                           .replace("daresn't"," dare not").replace("dasn't"," dare not").replace("e'er"," ever").replace("everyone's"," everyone is")\
                           .replace("'cause'"," because")
    
    x = re.sub(r"([0-9]+)000000", r"\1m", x)
    x = re.sub(r"([0-9]+)000", r"\1k", x)
    return x
#Stemming and stopwords removal
from nltk.stem.snowball import SnowballStemmer
sno = SnowballStemmer(language='english')
#Removing the word 'not' from stopwords
default_stopwords = set(stopwords.words('english'))
remove_not = set(['no', 'nor', 'not'])
custom_stopwords = default_stopwords - remove_not
print(custom_stopwords)
  • В функции деконструкции мы просто расширим такие слова, как не следует на не следует, не буду на не будет, часы на часы и т. Д. расширение общеупотребительных английских слов до полной формы. Это сделано потому, что мы хотим, чтобы наш алгоритм машинного обучения изучал надежные функции, а не слова, которых нет в английском словаре. Посетите эту ветку StackOverflow, чтобы узнать больше об этом.
  • В разделе удаления игнорируемых слов мы сохраним такие игнорируемые слова, как «нет», «не» и «ни», поскольку они дают нам информацию о настроениях к фильму. Их полное удаление может снизить производительность наших моделей. Стоп-слова - это слова, которые очень часто встречаются в любом языке. Следовательно, удаляя стоп-слова, мы можем сосредоточиться на самом важном из нашего корпуса данных.
  • Обратите внимание, что мы будем использовать два метода очистки данных - один со стеммингом, а другой без него. Основание обычно относится к получению корневого слова для любого данного слова. Основание - это процесс создания морфологических вариантов корневого / основного слова. Программы стемминга обычно называют алгоритмами стемминга или стеммерами. Алгоритм выделения корня сокращает слова «шоколад», «шоколадный», «шоколадный» до корневого слова, «шоколад» и «поиск», «извлеченный», «извлекает» - до основного слова «извлекать».

2. Зачем нужен стемминг?

  • Если мы создаем очень разреженное представление данных, важно сначала ограничить слова. Это потому, что это поможет нашим моделям машинного обучения изучить более надежные функции данных. Представьте, что есть три слова - «понравился», «нравится», «нравится». Если мы не свяжем эти слова с их корневыми словами «как», наш разреженный векторизатор создаст три записи для всех трех версий слов. Это может привести к избыточности данных. Чтобы избежать этого, мы обычно сокращаем слова при создании разреженного матричного представления.
  • Для использования моделей Word2Vec нам не нужно ограничивать слова, потому что модели W2V, как было замечено, работают лучше, когда не задействованы ограничения. Обычно алгоритмы W2V обеспечивают плотное представление предложений. Модель W2V, которую мы будем использовать для векторизации наших данных, была разработана в Google без ограничений.

3. Очистка данных с помощью стемминга.

# Combining all the above data cleaning methodologies as discussed above.
string=' '    
stemed_word=' '
preprocessed_movie_plots =[]
for movie_plot in tqdm(dataframe['plot_synopsis'].values):
    filtered_sentence=[]
    movie_plot = decontracted(movie_plot)
    movie_plot = removeNumbers(movie_plot)
    movie_plot = removeHtml(movie_plot)
    movie_plot = removeURL(movie_plot)
    movie_plot = removePunctuations(movie_plot)
    movie_plot = removePatterns(movie_plot)
    
    for cleaned_words in movie_plot.split():   
        if((cleaned_words not in custom_stopwords) and (len(cleaned_words)>2)):
            stemed_word=(sno.stem(cleaned_words.lower()))                                   
            filtered_sentence.append(stemed_word)
        else:
            continue
    movie_plot = " ".join(filtered_sentence) #Final string of cleaned words    
    preprocessed_movie_plots.append(movie_plot.strip()) #Data corpus contaning cleaned movie_plots from the whole dataset
    
#Adding a column of CleanedPlots to the table final which stores the data_corpus after pre-processing the movie_plots 
dataframe['CleanedPlots']=preprocessed_movie_plots 
    
print("The length of the data corpus is : {}".format(len(preprocessed_movie_plots)))

4. Очистка данных без остановки.

#Data cleaning without stemming for use with word vectors
preprocessed_movie_plots =[]
for movie_plot in tqdm(dataframe['plot_synopsis'].values):
    filtered_sentence=[]
    movie_plot = decontracted(movie_plot)
    movie_plot = removeNumbers(movie_plot)
    movie_plot = removeHtml(movie_plot)
    movie_plot = removeURL(movie_plot)
    movie_plot = removePunctuations(movie_plot)
    movie_plot = removePatterns(movie_plot)
    
    for cleaned_words in movie_plot.split():   
        if((cleaned_words not in custom_stopwords) and (len(cleaned_words)>2)):
            word=cleaned_words.lower()                                  
            filtered_sentence.append(word)
        else:
            continue
    movie_plot = " ".join(filtered_sentence) #Final string of cleaned words    
    preprocessed_movie_plots.append(movie_plot.strip()) #Data corpus contaning cleaned movie_plots from the whole dataset
    
#Adding a column of CleanedPlots to the table final which stores the data_corpus after pre-processing the movie_plots 
dataframe['CleanedPlots_NoStemming']=preprocessed_movie_plots 
    
print("The length of the data corpus is : {}".format(len(preprocessed_movie_plots)))
dataframe.head()

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

Исходный текст:

Бывший пилот-истребитель и водитель такси Тед Страйкер (Роберт Хейс) получил травму во время войны, что привело к патологическому страху перед полетом. В результате он не может заниматься ответственной работой. Его подруга военного времени, Элейн Дикинсон (Джули Хагерти), теперь стюардесса, покидает его. Нападающий нервно садится в Боинг 707 (трансамериканский рейс 209) из Лос-Анджелеса в Чикаго, на котором она работает, надеясь вернуть ее, но она дает ему отпор.

Очищенный текст с выделением основы:

пилот-истребитель таксист Тед нападающий Роберт Хей стал травмой война ведущий патолог страх фли результат unabl держать ответ на работу wartim подруга Элейн Дикинсон Джули Хагерти стюардесса лев забастовщик нервная доска Бо Тран американский рейс Лос-Анджелес Чикаго Слуга Надеюсь отыграть отказ.

Очищенный текст без разбивки на корень:

пилот-истребитель таксист Тед нападающий Роберт Хейс травмирован войной, ведущей к патологическому страху. Результат полета не может нести ответственность. Подруга военного времени Элейн Дикинсон. Джули Хагерти. Стюардесса покидает бортпроводник.

Перед построением моделей машинного обучения.

Разделение данных:

  • Прежде чем мы начнем строить наши модели машинного обучения, нам нужно разделить данные на обучающие и тестовые наборы. Столбец «split» в наборе данных сообщает нам, как данные должны быть распределены по обучающим, тестовым и проверочным наборам.
  • Для этой проблемы мы возьмем данные обучения и проверки и объединим их в один фрейм данных поезда. Мы возьмем оставшиеся данные в качестве тестовых данных и будем использовать их для оценки производительности наших моделей машинного обучения.
#Load the processed dataset
dataframe=pd.read_csv("cleaned_movie_plots.csv")
dataframe.head()
#Create a dataset for train and test
data_test=dataframe.loc[(dataframe['split'] == 'test')]
data_train=dataframe.loc[(dataframe['split'] == 'val') | (dataframe['split'] == 'train')]
#Split the whole data into train and test set
X_train = data_train['CleanedPlots']
y_train = data_train['tags']
X_test = data_test['CleanedPlots']
y_test = data_test['tags']
print("Number of points in training data: ",data_train.shape[0])
print("Number of points in test data: ",data_test.shape[0])

Количество баллов в обучающих данных: 11816
Количество баллов в тестовых данных: 2965

Преобразование текстовых данных в числовые функции:

  • Итак, теперь у нас есть очищенные данные, и у нас есть данные как для поездов, так и для тестов. Пришло время преобразовать текстовые данные в числовые векторы, чтобы наши модели машинного обучения могли использовать эту информацию для построения мощных моделей. Мы просто не можем напрямую передавать текстовые данные нашим моделям машинного обучения напрямую.
  • Есть несколько подходов, с помощью которых мы можем преобразовать наши текстовые данные в числовые векторы - мы можем использовать подход векторизации TF-IDF, подход Bag of Words и подходы Average Word2Vec. В этом тематическом исследовании мы в основном сосредоточимся на построении векторизации TF-IDF и средних представлений Word2Vec.

TF-IDF: периодичность термина и обратная периодичность документа.

  • При поиске информации TFIDF, сокращенно от термина «частота - обратная частота документа», представляет собой числовую статистику, которая предназначена для отражения того, насколько важно слово для документа в коллекции или корпусе.
  • TF обозначает частоту терминов. Интуитивно TF интуитивно означает вероятность появления слова в предложении. Это очень полезный метод для поиска наиболее часто встречающихся слов в корпусе документа. Однако у этой техники есть свой недостаток. Некоторые слова встречаются гораздо чаще, чем другие. И это определенные слова, которые встречаются очень редко. Значения TF лежат между 0 и 1.
  • Для слов, которые встречаются очень редко, мы можем использовать обратную частоту документов (IDF). IDF задается формулой log (N / n), где N - общее количество документов в нашем корпусе, а n - количество раз, когда каждое слово встречается в весь корпус. Интуитивно понятно, что IDF будет выше для слов, которые встречаются редко, и меньше для слов, которые встречаются чаще.
  • Поэтому для каждого слова в каждом обзоре мы будем рассматривать произведение (TF и IDF) и представлять его как d-мерный вектор.
  • TF-IDF практически не учитывает смысловое значение слов. Но то, что он делает, заключается в том, что он придает большее значение словам, которые реже встречаются во всем корпусе данных, а также придает важность наиболее частым словам, которые встречаются в каждой точке данных.

Модели Word2Vec:

  • Word2Vec - это метод преобразования слова в числовые векторы. В этом примере мы будем использовать предварительно обученную модель W2V от Google для преобразования слов в соответствующую векторную форму. Для данного предложения мы преобразуем каждое слово в векторы, просуммируем их и возьмем их среднее значение, чтобы представить среднее вложение слова word2vec для данного предложения.
  • Предположим, у нас есть N слов в предложении {w1, w2, w3, w4, w5, w6…, wN}. Мы преобразуем каждое слово в вектор, просуммируем их и разделим на общее количество слов (N), присутствующих в этом конкретном предложении. Таким образом, наш окончательный вектор будет выглядеть как (1 / N) * [word2vec (w1) + word2vec (w2) + word2vec (w3)…. + word2vec (wN)]
  • Модели Word2Vec соответствуют последнему слову техники, когда речь идет о сохранении семантических отношений, отношений между словами, и очень эффективны для преобразования текста в числовые векторы.

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

Мы уже обсуждали, что такое мульти-маркировочная классификация. В этом разделе мы поговорим о том, как мы конвертируем теги в двоичные векторы. Мы будем использовать знаменитый класс CountVectorizer программы sk-learn для преобразования тегов в числовые векторы таким образом, чтобы при появлении тега мы пометили его как 1, а если тег не появится, мы обозначим его как 0. Предположим, у нас есть четыре тега - t1, t2, t3, t4. Для фильма, в котором появляются теги t1 и t3, результирующий вектор будет [1,0,1,0]. Для фильма, в котором появляются только теги t2, t3 и t4, результирующий вектор будет [0,1,1,1]. Посмотрите на прикрепленное изображение ниже.

Подходы к классификации с несколькими метками, использованные в этом тематическом исследовании:

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

1. OneVsRest.

  • Как вы можете видеть на протяжении всего эксперимента, я широко использовал OneVsRest. Я также экспериментировал с цепочками двоичной релевантности и классификаторами, но без их тонкой настройки. Давайте поговорим о том, что такое OneVsRest и почему он полезен для задач классификации с несколькими метками?
  • OVR, как известно, представляет собой очень интуитивно понятный подход к решению задач классификации с несколькими метками, в которых проблема разбивается на несколько задач двоичной классификации, в которых метки должны быть взаимоисключающими. .
  • В OVR мы выбираем один класс и обучаем бинарный классификатор с образцами выбранного класса с одной стороны и всеми другими образцами с другой стороны. Таким образом, мы получаем N классификаторов для N меток. Во время тестирования мы просто классифицируем образец как принадлежащий к классу с максимальным баллом среди N классификаторов. Давайте разберемся в этом на простом примере
  • Давайте рассмотрим фильм Темный рыцарь и предположим, что он связан с тремя тегами - «приключения», «драма», «триллер». Во время обучения или тестирования модели OVR будет работать следующим образом - тег «приключение» или нет? Тег «драма» или нет? Тег «триллер» или нет? Здесь мы разделили проблему нескольких меток на три задачи двоичной классификации. В конце фазы обучения мы объединим выходные данные этих трех задач двоичной классификации в один выход. Таким образом, все сводится к установке одного классификатора на каждый класс. Для каждого классификатора класс сопоставляется со всеми другими классами.
  • Давайте посмотрим на небольшой фрагмент кода о том, как использовать OneVsRest с классификаторами ML. Мы узнаем, как настраивать модели с помощью гиперпараметров, в следующем разделе.
from sklearn.multiclass import OneVsRestClassifier
classifier = OneVsRestClassifier(
LogisticRegression(class_weight='balanced'), n_jobs=-1)
classifier.fit(X_train_multilabel, y_train_multilabel)
predictions = classifier.predict(X_test_multilabel)

2. Двоичная релевантность.

  • Двоичная релевантность очень похожа на OneVsRest с некоторыми небольшими отличиями. В параметре «Двоичная релевантность» он преобразует задачу классификации с несколькими метками с K метками в K с одной меткой, но с отдельными проблемами двоичной классификации.
  • Каждый двоичный классификатор предсказывает, присутствует метка или нет. Например, содержит ли фильм Темный рыцарь ярлык «приключения» или нет, «драма» или нет, «триллер» или нет? Каждый классификатор ответит на вопрос да / нет. Следовательно, каждый классификатор ведет себя как двоичный классификатор. В конце объединение результата каждого из этих двоичных классификаторов объединяется для получения вывода с несколькими метками.
  • Подход двоичной релевантности очень быстр, надежен, эффективен с точки зрения вычислений и очень прост в реализации. Вы можете проверить более подробную информацию о бинарной релевантности по этой ссылке.
  • Давайте посмотрим на простой фрагмент кода о том, как применить двоичную релевантность.
from skmultilearn.problem_transform import BinaryRelevance
from sklearn.naive_bayes import GaussianNB
start = datetime.now()
classifier = BinaryRelevance(GaussianNB())
classifier.fit(X_train_multilabel, y_train_multilabel)
predictions = classifier.predict(X_test_multilabel)

3. Цепочки классификаторов.

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

  • На изображении выше, показанном на рис. 31, классификатор 1 обучается только на входных данных. Для каждого из следующих классификаторов они обучаются во входном пространстве и всех предыдущих классификаторах в цепочке. На приведенной выше диаграмме у нас есть X как пространство ввода и Y как наши метки.
  • Здесь задача состоит в том, чтобы предсказать Y (4 метки) с учетом входного пространства X.
  • Используя цепочки классификаторов, проблема будет преобразована в 4 разных задачи с одной меткой. Часть с желтым цветом представляет собой пространство ввода, а часть с белым цветом представляет целевые метки.
  • Классификатор 1 будет обучен на входных данных. Выходные данные классификатора 1 будут использоваться в качестве входных данных для обучающего классификатора 2, который будет предсказывать вторую метку, выходные данные классификатора 2 будут использоваться в качестве входных данных для обучающего классификатора 3 и так далее. Итак, название цепочки классификатора! Цепочки классификаторов также учитывают корреляции меток.
  • Здесь снова давайте посмотрим на фрагмент кода о том, как использовать цепочки классификаторов.
from skmultilearn.problem_transform import ClassifierChain
from sklearn.linear_model import LogisticRegression
start = datetime.now()
classifier = ClassifierChain(LogisticRegression(C=1))
classifier.fit(X_train_multilabel, y_train_multilabel)
predictions = classifier.predict(X_test_multilabel)

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

Возможности моделей машинного обучения:

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

  • Различные функции, которые мы попробуем в этом тематическом исследовании, - это в основном униграммы TF-IDF, биграммы, триграммы и n-граммы слов. Мы также попробуем униграммы символов, биграммы, триграммы и т. Д. На основе TF-IDF.
  • В случае униграммы слов мы будем учитывать только одно слово каждый раз для построения векторов TF-IDF. Но главный недостаток заключается в том, что с помощью униграмм мы не можем захватить информацию о последовательности. Для сбора информации о последовательности мы будем использовать биграммы (где берутся два последовательных слова), триграммы (берутся 3 последовательных слова) и n-граммы слов (где берутся последовательные слова и их произведение на TF и ​​IDF). Word N-граммы очень эффективны при захвате информации о последовательности.
  • Он также использовал особенности символьных n-граммов, как упоминалось в фактическом исследовании. Символьные n-граммы - это небольшое расширение словарных n-граммов. В случае char n-граммов мы просто рассматриваем отдельные символы или последовательность символов, а не последовательность слов. Для этого тематического исследования функции n-граммы персонажей оказались на удивление мощными, чем функции n-граммы слов.
  • Мы обсудили, как мы использовали модели Google Word2Vec для преобразования каждого слова в числовое значение. Здесь мы преобразуем резюме каждого фильма в 300-мерный вектор. Модели Word2Vec чрезвычайно эффективны, когда дело доходит до фиксации семантических отношений между словами. Это похоже на современные технологии, когда дело доходит до преобразования текста в числовые функции.
  • Наконец, в разделе разработки функций мы объединим символьные n-граммы с векторами, полученными с использованием средних моделей Word2Vec, и построим модели машинного обучения на их основе.
  • Было замечено, что комбинированные функции работали немного лучше и дали нам лучшее значение микро-усредненной оценки f1 по сравнению с остальными стратегиями изменения характеристик.

Построение моделей машинного обучения.

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

  • Сначала мы построим наши базовые модели со всеми 71 тегами, присутствующими в нашем корпусе. Каждая модель будет пытаться предсказать теги из 71 общего количества тегов. После построения простых базовых моделей мы попытаемся настроить гиперпараметры моделей, чтобы увидеть, есть ли какое-либо увеличение микро-усредненной оценки F1, а также будем следить за другими вторичными метриками, которые мы обсуждали в начальных разделах.
  • Во-вторых, мы будем строить наши модели, используя только 3 верхних тега. Если вы помните наш анализ в разделе EDA, мы можем увидеть, что среднее количество тегов, присутствующих в каждом фильме, составляет почти 3. Помимо использования только трех основных тегов, мы также будем строить наши модели, взяв 4 лучших и 5 лучших тегов соответственно. , поскольку EDA обнаружило, что у большинства фильмов не более 5 тегов.

Мы опробуем все методы определения характеристик, такие как n-граммы слов TF-IDF, n-граммы символов и усредненные модели Word2Vec с различными комбинациями значений верхних тегов, чтобы увидеть, какая из этих комбинаций дала нам лучшую производительность. Я приведу лишь несколько примеров различных функций, которые мы опробовали. Вы можете получить весь код этого проекта в моем профиле GitHub. Поделюсь ссылкой в ​​конце этого блога.

Некоторые примеры кода для наших базовых моделей.

N-граммы TF-IDF для преобразования текстовых данных:

vectorizer = TfidfVectorizer(min_df=0.00009, smooth_idf=True, norm="l2", tokenizer = lambda x: x.split(" "), sublinear_tf=False, ngram_range=(1,1))
X_train_multilabel = vectorizer.fit_transform(X_train)
X_test_multilabel = vectorizer.transform(X_test)
print("Dimensions of train data X:",X_train_multilabel.shape, "Y :",y_train_multilabel.shape)
print("Dimensions of test data X:",X_test_multilabel.shape,"Y:",y_test_multilabel.shape)

Обратите внимание, что «min_df» - это параметр, который мы передали конструктору TFIDF, который в основном является пороговым значением, которое говорит нам, что при построении словаря игнорируйте термины, частота которых в документе строго ниже заданного порога. В выходных данных приведенного выше блока ячеек мы видим, что наша целевая переменная представляет собой 71-мерный вектор. Вот почему это называется классификацией по нескольким меткам. Параметр «ngram_range» сообщает нам, следует ли рассматривать униграммы, биграммы, n-граммы и т. Д. (1,1) просто означает униграмму, (2,2) означает биграмму, тогда как (1,6) означает последовательность от 1 до 6 последовательных слов.

Базовые модели.

В разделе построения базовой модели мы опробовали множество моделей с использованием логистической регрессии, линейных SVM, классификаторов SGD с потерями журнала, классификатора SGD с потерями на шарнирах и т. Д. Во всех случаях мы хотим максимизировать микро-усредненный результат F1. Базовая модель, которая дала нам максимальное значение микро-усредненной оценки F1, - это SGDClassifier с потерей шарниров - 0,5169. Однако, если мы посмотрим на значения точности, потери Хэмминга, взвешенный отзыв и взвешенную точность, они не выглядят впечатляющими. По этой причине мы хотим опробовать больше моделей.

Давайте посмотрим на простой фрагмент кода, который мы использовали для OneVsRest с логистической регрессией. На протяжении всего эксперимента я использовал одну и ту же структуру кода. Итак, если вы хотите изменить какую-либо модель, просто замените LogisticRegression на LinearSVC, и все заработает.

from sklearn.linear_model import LogisticRegression
start = datetime.now()
classifier1 = OneVsRestClassifier(LogisticRegression(penalty='l1', class_weight='balanced'), n_jobs=-1)
classifier1.fit(X_train_multilabel, y_train_multilabel)
predictions = classifier1.predict(X_test_multilabel)
print("Accuracy :",metrics.accuracy_score(y_test_multilabel, predictions))
print("Hamming loss ",metrics.hamming_loss(y_test_multilabel,predictions))
precision = precision_score(y_test_multilabel, predictions, average='micro')
recall = recall_score(y_test_multilabel, predictions, average='micro')
f1 = f1_score(y_test_multilabel, predictions, average='micro')
 
print("\nMicro-average quality numbers")
print("Precision: {:.4f}, Recall: {:.4f}, F1-measure: {:.4f}".format(precision, recall, f1))
precision = precision_score(y_test_multilabel, predictions, average='macro')
recall = recall_score(y_test_multilabel, predictions, average='macro')
f1 = f1_score(y_test_multilabel, predictions, average='macro')
 
print("\nMacro-average quality numbers")
print("Precision: {:.4f}, Recall: {:.4f}, F1-measure: {:.4f}".format(precision, recall, f1))
print("\nClassification Report")
print (metrics.classification_report(y_test_multilabel, predictions))
print("Time taken to run this cell :", datetime.now() - start)

Вывод указанной выше ячейки:

Accuracy : 0.01821247892074199
Hamming loss  0.08066883594993231
Micro-average quality numbers
Precision: 0.2542, Recall: 0.4562, F1-measure: 0.3264
Macro-average quality numbers
Precision: 0.1250, Recall: 0.2475, F1-measure: 0.1635

Гиперпараметрическая настройка базовых моделей:

В этом разделе мы рассмотрим только логистическую регрессию с OVR. Но вы можете использовать этот код для настройки любых моделей. Мы настроим значение «C», которое максимизирует взвешенный балл F1.

from sklearn.model_selection import RandomizedSearchCV
from scipy import stats
st=datetime.now()
alpha = [0.000001,0.00001,0.0001,0.001,0.01,0.1,1,10,100,1000,10000]
penalty=['l1','l2']
params  = {"estimator__C":alpha,
           "estimator__penalty":penalty}
base_estimator = OneVsRestClassifier(LogisticRegression(class_weight='balanced'), n_jobs=-1)
rsearch_cv = RandomizedSearchCV(estimator=base_estimator, param_distributions=params, n_iter=10, cv=5, scoring='f1_micro', n_jobs=-1, verbose=0)
rsearch_cv.fit(X_train_multilabel, y_train_multilabel)
print("Time taken to perform hyperparameter tuning: ",datetime.now()-st)
print("Best estimator: ",rsearch_cv.best_estimator_)
print("Best Cross Validation Score: ",rsearch_cv.best_score_)

Вывод указанной выше ячейки.

Time taken to perform hyperparameter tuning:  0:52:50.367842
Best estimator:  OneVsRestClassifier(estimator=LogisticRegression(C=1, class_weight='balanced', dual=False,
          fit_intercept=True, intercept_scaling=1, max_iter=100,
          multi_class='warn', n_jobs=None, penalty='l1', random_state=None,
          solver='warn', tol=0.0001, verbose=0, warm_start=False),
          n_jobs=-1)
Best Cross Validation Score:  0.3177680376911081

Мы повторно использовали тот же код для других моделей, таких как линейные SVM, SGDClassifiers и так далее. Если вы посмотрите на раздел сравнения производительности, приведенный в конце этого тематического исследования, вы увидите, что настройка гиперпараметров не сильно улучшила показатели. Причина в том, что мы все еще используем 71 тег в качестве целевых меток. Мы увидим, как резко возрастет производительность модели в следующих разделах, когда мы уменьшим целевые метки до 3, 4 и 5 тегов.

Принимается количество тегов = 3

В разделе EDA мы видели, что средний фильм состоит из трех тегов. Поэтому для нас логично построить модель, которая может предсказать три связанных с собой тега. Мы использовали тот же набор характеристик, но на этот раз количество тегов было равно 3. Следовательно, теперь это проблема классификации с несколькими метками трех классов. Обратите внимание на изменения кода ниже.

#Take the maximum number of tags equal to the average number of, we  tags as seen in the EDA section. Average number = 3
# We have taken the max features to be 3 instead of 71.
vectorizer = CountVectorizer(tokenizer = tokenize, binary='true', max_features=3).fit(y_train)

При настройке гиперпараметров этой модели мы стали свидетелями значительного улучшения производительности моделей. Мы могли видеть, что все показатели резко улучшились. Максимальный взвешенный показатель F1, который мы получили, составил 0,5283, а максимальная точность, которую мы получили, составляет почти 0,44. Это неплохо, учитывая тот факт, что в исходной исследовательской работе они могли получить взвешенную оценку F1 всего 0,37. Это огромное улучшение по сравнению с этим. Помните, что мы все еще используем N-граммы слов TFIDF без каких-либо дополнительных методов извлечения признаков.

Принимается количество тегов = 4

Здесь снова изменить код довольно просто. Просто измените значение «max_features» с 3 на 4. Теперь взвешенный показатель отзыва значительно улучшился! Мы достигли 0,62 при среднем микро-балле F1, равном 0,55. Ух ты! Наша модель становится лучше. Это потрясающе.

Принимается количество тегов = 5

Здесь, несмотря на настройку гиперпараметров, производительность начала немного падать. Это дает нам представление о том, что 3 тега и 4 функции тегов на самом деле лучше, чем функции 5 тегов. Мы могли заметить значительное снижение показателей отзыва по сравнению с предыдущей лучшей моделью.

Модели символьных N-грамм на основе TF-IDF для количества тегов = 3

  • Мы обсудили, что такое символьные n-граммы. Вместо слов мы возьмем последовательности символов для векторизации наших текстов. Интуитивно понятно, что символьные n-граммы работают так же, как и n-граммы слов. Единственная разница в том, что n-граммы слов используют последовательности слов, которые закодированы в числовые векторы, а символьные n-граммы используют последовательности символов для кодирования текстов. Концепция как TF, так и IDF отлично работает в случае символьных n-граммов, точно так же, как это работает в случае n-граммов слов. Наиболее часто встречающийся символ будет иметь высокое значение TF, а наиболее редко встречающиеся символы будут иметь высокое значение IDF.
  • Добавляя в текст н-грамм символов, мы будем рассматривать все комбинации символов, такие как униграммы, биграммы, триграммы и н-граммы. Униграммы означают только один единственный символ, биграммы означают только два символа, триграммы означают последовательность из трех символов., N-грамм означает последовательность всех перестановок символов от 1 до n.
  • Обратите внимание, что мы использовали функции символьных n-граммов только с 3, 4 и 5 верхним числом тегов.
  • Имея в виду вышеупомянутую идею, давайте рассмотрим пример кода о том, как получить символьное представление n-граммы для n = 6. Мы испробовали здесь всевозможные комбинации, и вы будете удивлены, увидев улучшение KPI.
#Use tf-idf vectorizer to vectorize the movie plot synopsis
vectorizer = TfidfVectorizer(max_features=50000, strip_accents='unicode', analyzer='char', sublinear_tf=False, ngram_range=(6,6))
X_train_multilabel = vectorizer.fit_transform(X_train)
X_test_multilabel = vectorizer.transform(X_test)
  • В приведенном выше блоке кода мы только что заменили анализатор на «char» вместо «words», и он отлично работает!
  • Мы выполним настройку гиперпараметров так же, как и для всех других предыдущих моделей. Нет смысла снова писать тот же код.
  • Вместо этого давайте посмотрим на таблицу сравнения производительности, чтобы узнать больше о значении показателей, которое мы получили для трех тегов.

  • Мы сразу видим, что для всех различных комбинаций моделей значение вторичных KPI резко возросло. Мы получили максимальное взвешенное значение отзыва 0,657, а максимальное взвешенное значение F1 составляет почти 0,577. Наиболее заметное улучшение было в значениях точности. Граммовые модели (6,6) дали наивысший взвешенный балл F1 и наивысший балл взвешенного отзыва. Мы будем помнить об этом по мере продвижения.

Модели символьных N-грамм на основе TF-IDF для количества тегов = 4

  • В этом разделе мы просто посмотрим на таблицу сравнения производительности. Код и все остальное точно такие же, за исключением количества тегов, которое изменено на 4.
#Take the maximum number of tags equal to 4
vectorizer = CountVectorizer(tokenizer = tokenize, binary='true', max_features=4).fit(y_train)
y_train_multilabel = vectorizer.transform(y_train)
y_test_multilabel = vectorizer.transform(y_test)

  • Как видим, производительность немного снизилась по сравнению с предыдущей группой моделей. Лучшей моделью по-прежнему является модель TF-IDF char (6,6) грамма, которая дала нам взвешенный балл F1 0,5639 и взвешенный балл отзыва 0,6519. Однако наблюдается значительное снижение значений точности по всем моделям.

Модели символьных N-грамм на основе TF-IDF для количества тегов = 5

  • Давайте сразу перейдем к таблице сравнения производительности!

  • В целом мы видим, что производительность всех прогнозных моделей немного падает. На данный момент наши модели не сильно улучшаются. Поэтому мы будем придерживаться моделей прогнозирования, построенных для трех наиболее важных тегов. 5-граммовые функции TF-IDF показали наилучшие результаты в этом случае со средним макро-баллом F1 0,5343 и средневзвешенной оценкой отзыва 0,6392.

Бинарная релевантность и цепочки классификаторов.

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

  • Мы ясно видим, что использование цепочек классификаторов с логистической регрессией дало нам очень высокий взвешенный отзыв 0,7122, что является лучшим значением взвешенного отзыва, которое мы имели до сих пор. Микро-усредненная оценка F1 не сильно улучшилась, но я уверен, что использование гиперпараметрической оптимизации также улучшит эти результаты.

Среднее значение Word2Vec с использованием модели Google W2V.

  • В этом разделе мы будем использовать предварительно обученную модель word2vec для преобразования текстовых данных в числовые векторы. Как и было обещано ранее, давайте посмотрим на пример кода, показывающий, как преобразовать сводку каждого фильма в числовые векторы.
  • Вы можете скачать предварительно обученную модель Google W2V здесь. Эта модель была обучена на большом корпусе набора данных Google News. Мы будем использовать gensim для загрузки модели и написания кода custum для преобразования текстовых данных в числовые векторы.
#Import the required libraries and load the
from gensim.models import Word2Vec
from gensim.models import KeyedVectors
from tqdm import tqdm
word2vec_model=KeyedVectors.load_word2vec_format('GoogleNews-vectors-negative300.bin', binary=True) 
word2vec_words = list(word2vec_model.wv.vocab)

Преобразование текстовых данных в числовые векторы и сохранение их в виде файлов рассола.

#This method returns the Average Word2Vec vectors for all reviews in a given dataset
def vectorize_w2v(dataset, word2vec_model, word2vec_words):
    word2vec_corpus=[]
    for sentence in dataset:
        word2vec_corpus.append(sentence.split()) 
    
    # Creating average Word2Vec model by computing the average word2vec for each review.
    sent_vectors = []; #The average word2vec for each sentence/review will be stored in this list
    for sentence in tqdm(word2vec_corpus): #For each review
        sent_vec = np.zeros(300) #300 dimensional array, where all elements are zero. This is used to add word vectors and find the averages at each iteration.
        count_words =0; #This will store the count of the words with a valid vector in each review text
        for word in sentence: #For each word in a given review.
            if word in word2vec_words:
                word_vectors = word2vec_model.wv[word] #Creating a vector(numpy array of 300 dimensions) for each word.
                sent_vec += word_vectors
                count_words += 1
        if count_words != 0:
            sent_vec /= count_words
        sent_vectors.append(sent_vec)
    #print("\nThe length of the sentence vectors :",len(sent_vectors))
    #print("\nSize of each vector : ",len(sent_vectors[0]))
    sent_vectors = np.array(sent_vectors)
    return sent_vectors
X_train_vectors = vectorize_w2v(X_train, word2vec_model, word2vec_words)
X_test_vectors = vectorize_w2v(X_test, word2vec_model, word2vec_words)
import pickle
with open('X_train_W2V.pkl', 'wb') as file:
    pickle.dump(X_train_vectors, file)
    
with open('X_test_W2V.pkl', 'wb') as file:
    pickle.dump(X_test_vectors, file)

Модели машинного обучения, использующие среднее количество тегов для каждого сюжета фильма, составляют ~ 3 + среднее количество функций Word2Vec.

#Importing & Initializing the "CountVectorizer" object, which is scikit-learn's bag of words tool. By default 'split()' will tokenize each tag using space.
def tokenize(x):
    x=x.split(',')
    tags=[i.strip() for i in x] #Some tags contains whitespaces before them, so we need to strip them
    return tags
#Take the maximum number of tags equal to the average number of tags as seen in the EDA section. Average number = 3
vectorizer = CountVectorizer(tokenizer = tokenize, binary='true', max_features=3).fit(y_train)
y_train_multilabel = vectorizer.transform(y_train)
y_test_multilabel = vectorizer.transform(y_test)

Здесь мы будем использовать 4 модели - логистическую регрессию, линейную SVM, классификатор XGBoost и классификатор случайного леса для построения и оценки нашей модели. Давайте посмотрим на код.

  1. Получите лучшую оценку с помощью случайного поиска + логистической регрессии:
alpha = [0.000001,0.00001,0.0001,0.001,0.01,0.1,1,10,100,1000,10000]
penalty=['l1','l2']
params  = {"estimator__C":alpha,
           "estimator__penalty":penalty}
base_estimator = OneVsRestClassifier(LogisticRegression(class_weight='balanced'), n_jobs=-1)
rsearch_cv = RandomizedSearchCV(estimator=base_estimator, param_distributions=params, n_iter=10, cv=5, scoring='f1_micro', n_jobs=-1, verbose=0)
rsearch_cv.fit(X_train, y_train_multilabel)
print("Time taken to perform hyperparameter tuning: ",datetime.now()-st)
print("Best estimator: ",rsearch_cv.best_estimator_)
print("Best Cross Validation Score: ",rsearch_cv.best_score_)

Подбирайте лучший оценщик к данным обучения.

start = datetime.now()
classifier = rsearch_cv.best_estimator_
classifier.fit(X_train, y_train_multilabel)
predictions = classifier.predict(X_test)
print("Accuracy :",metrics.accuracy_score(y_test_multilabel, predictions))
print("Hamming loss ",metrics.hamming_loss(y_test_multilabel,predictions))
precision = precision_score(y_test_multilabel, predictions, average='micro')
recall = recall_score(y_test_multilabel, predictions, average='micro')
f1 = f1_score(y_test_multilabel, predictions, average='micro')
 
print("\nMicro-average quality numbers")
print("Precision: {:.4f}, Recall: {:.4f}, F1-measure: {:.4f}".format(precision, recall, f1))
precision = precision_score(y_test_multilabel, predictions, average='macro')
recall = recall_score(y_test_multilabel, predictions, average='macro')
f1 = f1_score(y_test_multilabel, predictions, average='macro')
 
print("\nMacro-average quality numbers")
print("Precision: {:.4f}, Recall: {:.4f}, F1-measure: {:.4f}".format(precision, recall, f1))
print("\nClassification Report")
print (metrics.classification_report(y_test_multilabel, predictions))
print("Time taken to run this cell :", datetime.now() - start)

2. Получите лучший оценщик с помощью линейной SVM:

Единственное изменение, которое нам здесь нужно было сделать, - это просто обновить приведенную ниже строку кода.

base_estimator = OneVsRestClassifier(SVC(kernel='linear',class_weight='balanced'), n_jobs=-1)
rsearch_cv = RandomizedSearchCV(estimator=base_estimator, param_distributions=params, n_iter=10, cv=5, scoring='f1_micro', n_jobs=-1, verbose=0)
rsearch_cv.fit(X_train, y_train_multilabel)

Обратите внимание, что мы изменили только конструктор внутри метода OneVsRestClassifier, чтобы переключаться с одной модели на другую. Остальные части кода остаются такими же, и поэтому я не пишу их здесь. Вы можете посмотреть ссылку на GitHub, чтобы получить весь код. См. Раздел 19.2.2.

3. Гиперпараметрическая настройка модели XGBClassifier.

st=datetime.now()
from xgboost import XGBClassifier
params = {'estimator__learning_rate' :stats.uniform(0.001,0.2),
          'estimator__n_estimators':[10,50,100,250,500,750,1000,2000],
          'estimator__gamma':stats.uniform(0,0.02),
          'estimator__subsample':(0.2,0.3,0.4,0.5,0.6, 0.7, 0.8),
          'estimator__reg_alpha':[25,50,75,100,150,200],
          'estimator__reg_lambda':[25,50,75,100,150,200],
          'estimator__max_depth':np.arange(1,11),
          'estimator__colsample_bytree':[0.2,0.3,0.4,0.5,0.6,0.7,0.8],
          'estimator__min_child_weight':np.arange(1,11)}
base_estimator = OneVsRestClassifier(XGBClassifier(), n_jobs=-1)
rsearch_cv = RandomizedSearchCV(estimator=base_estimator, param_distributions=params, n_iter=15, cv=5, scoring='f1_micro', n_jobs=-1, verbose=0)
rsearch_cv.fit(X_train, y_train_multilabel)
print("Time taken to perform hyperparameter tuning: ",datetime.now()-st)
print("Best estimator: ",rsearch_cv.best_estimator_)
print("Best Cross Validation Score: ",rsearch_cv.best_score_)

4. Гиперпараметрическая настройка модели RandomForestClassifier.

st=datetime.now()
from sklearn.ensemble import RandomForestClassifier
params = {'estimator__n_estimators': [10,50,75,100,150,250,350,500,750,850,1000,1500,2000],
          'estimator__min_weight_fraction_leaf': [0,0.25,0.5],
          'estimator__max_depth': np.arange(1,6),
          'estimator__min_samples_leaf': np.arange(0.05,0.5,0.05),
          'estimator__min_samples_split':np.arange(0.05,1.0,0.05)}
base_estimator = OneVsRestClassifier(RandomForestClassifier(criterion='gini', class_weight='balanced'), n_jobs=-1)
rsearch_cv = RandomizedSearchCV(estimator=base_estimator, param_distributions=params, n_iter=15, cv=5, scoring='f1_micro', n_jobs=-1, verbose=0)
rsearch_cv.fit(X_train, y_train_multilabel)
print("Time taken to perform hyperparameter tuning: ",datetime.now()-st)
print("Best estimator: ",rsearch_cv.best_estimator_)
print("Best Cross Validation Score: ",rsearch_cv.best_score_)

Таблица сравнения производительности для всех моделей, которые мы обучили с использованием средних значений Word2Vecs. Полный код см. В разделе 19 записной книжки ipython.

Используя средние числовые функции Word2Vec, мы видим значительное улучшение взвешенных значений отзыва. Практически во всех случаях значение превышает 0,70, при этом максимальное значение полученного микро усредненного балла F1 составляет 0,5719. На сегодняшний день это лучшая комбинация взвешенных показателей. Это показывает, насколько эффективны функции W2V даже при очень небольшом объеме данных.

Раздел проектирования функций: объединение функций W2V с функциями char (6,6) грамма. Количество тегов = 3.

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

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

start = datetime.now()
#Take the maximum number of tags equal to the average number of tags as seen in the EDA section. Average number = 3
vectorizer = CountVectorizer(tokenizer = tokenize, binary='true', max_features=3).fit(y_train)
y_train_multilabel = vectorizer.transform(y_train)
y_test_multilabel = vectorizer.transform(y_test)
#Use tf-idf vectorizer to vectorize the movie plot synopsis
vectorizer = TfidfVectorizer(max_features=50000, strip_accents='unicode', analyzer='char', sublinear_tf=False, ngram_range=(6,6))
X_train_char6 = vectorizer.fit_transform(X_train)
X_test_char6 = vectorizer.transform(X_test)
#Load the average w2v features
import pickle
with open('X_train_W2V.pkl', 'rb') as file:
    X_train_W2V = pickle.load(file)
    
with open('X_test_W2V.pkl', 'rb') as file:
    X_test_W2V = pickle.load(file)
    
#Convert the dense matrix to sparse matrix
from scipy import sparse
X_train_W2V = sparse.csr_matrix(X_train_W2V)
X_test_W2V = sparse.csr_matrix(X_test_W2V)
#Stack the two sparse matrices into one single matrix
from scipy.sparse import hstack
X_train_combined = hstack((X_train_char6,X_train_W2V))
X_test_combined = hstack((X_test_char6,X_test_W2V))
print("Time taken to run this cell :", datetime.now() - start)

Таблица сравнения производительности для раздела "Разработка функций"

  • Ух ты! Мы видим значительное улучшение показателей для линейных SVM. Полученный микро-усредненный балл F1 чуть меньше 0,60, а средневзвешенный показатель отзыва составляет почти 0,74.
  • Это значительное улучшение по сравнению с результатами, которые мы видели в самой исследовательской статье. Исходная исследовательская работа дала микро-средний балл F1 0,37, тогда как этот проект дал максимальный микро-средний балл F1 0,60. Для меня это большое удовлетворение.
  • В исследовательской статье упоминается множество других методов разработки функций, которые я не пробовал. Если вы можете, попробуйте реализовать их и объединить с различными другими функциями и посмотреть, улучшит ли это показатель дальше. Упомяните в разделе комментариев ниже, если вы обнаружили какие-либо улучшения в значениях показателей.

Заключение:

  1. Максимальный микро-усредненный балл F1, который мы получили для всего проекта, составляет 0,5983, а максимальное значение взвешенного значения отзыва, которое мы получили, составляет 0,74.
  2. Функции Char N-грамм оказались значительно мощнее, чем функции N-грамм слов.
  3. Используя методы разработки функций, такие как word2vec, и комбинацию функций TF-IDF и Word2Vec, наши модели показали себя на удивление лучше, чем предыдущая реализация.
  4. В наше время мы привыкли получать оценки выше 90%. Но, учитывая очень ограниченный размер данных в 14 тыс. Точек данных, нам действительно удалось получить приличный микро-усредненный результат F1.

Будущая работа:

  1. Производительность всех моделей можно значительно улучшить с помощью чрезвычайно большого набора данных. В будущей версии этого проекта я мог бы попытаться собрать больше данных и использовать большие вычислительные ресурсы для создания более эффективных и мощных моделей.
  2. Мы будем использовать векторы Glove с тематическим моделированием для извлечения функций из корпуса фильма.
  3. Мы также будем использовать глубокие рекуррентные нейронные архитектуры, такие как LSTM, для обучения нашего текстового корпуса. LSTM чрезвычайно эффективны для сбора информации о последовательности и корреляции между словами.
  4. Мы также могли бы использовать такие стратегии, как ансамблевые модели, чтобы увидеть, полезно ли это для повышения производительности моделей. Если вы хотите узнать больше о стратегиях ансамблевого моделирования, обратитесь к предыдущему блогу, который я написал на ту же тему. Вы можете прочитать блог здесь.

Ссылки:

  1. Исследовательская статья: https://www.aclweb.org/anthology/L18-1274
  2. Ссылки на код: https://www.appliedaicourse.com/
  3. Идеи: https://en.wikipedia.org/wiki/Multi-label_classification
  4. Двоичная релевантность: http://scikit.ml/api/skmultilearn.problem_transform.br.html
  5. Классификаторы: https://scikit-learn.org/stable/
  6. Стратегии: https://www.analyticsvidhya.com/blog/2017/08/introduction-to-multi-label-classification/