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

Дополнительную информацию см. в разделе мой репозиторий.

Часть 1. Бизнес-цель

Часть 2. Сбор данных

Часть 3: Обработка данных (вы здесь)

Часть 4. Предварительная обработка данных (еще не опубликовано)

Часть 5. Построение модели (еще не опубликовано)

Часть 6. Развертывание модели (еще не опубликовано)

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

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

Часть 3: Обработка данных — маркировка жанров и создание наших целей

Жанры для наших сценариев мы будем извлекать с помощью обертки tmbdsimple для Python, чтобы подключить нас к API базы данных фильмов. Затем наши цели будут преобразованы с помощью горячего кодирования.

Шаги, которые мы предпримем:

  1. Импорт необходимых пакетов
  2. Загрузка сценариев с помощью пакета os
  3. Разметка жанров сценария с помощью пакета tmbdsimple
  4. Создание целей с помощью горячего кодирования

Для этого исходного кода проверьте data_wrangling и data_wrangling_pt2 из моего репозитория

1. Импорт необходимых пакетов

2. Загрузка сценариев с помощью пакета os

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

Во-первых, мы собираемся создать объект словаря.

#initializing dict
screenplays = {'title': [], 'text': []}

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

Далее мы собираемся создать функцию screenplays_loader(dct), которая сохраняет наши сценарии из папки script_texts в объект словаря.

def screenplays_loader(dct):
    
    """
    This function takes in a dct as parameters and returns an updated dct with title and text keys, and lists of titles and screenplay text respectively
    
    """

    directory = os.fsdecode('script_texts/')

    for file in os.listdir(directory):

        filename = os.fsdecode(file)
        text = open(directory + '/' + filename, 'rb').read()
        if len(text) > 0:
            dct['title'].append(filename.strip('.txt'))
            dct['text'].append(text)
        else:
            continue

Во-первых, мы создаем переменную с именем directory, которая представляет собой декодированную строку расположения нашего файла. Затем мы перебираем объект os.listdir(directory), который представляет собой список файлов по указанному пути. В нашем случае это наша папка с нашими сценариями. Затем мы добавляем имя файла в качестве заголовка в список заголовков и текст файла в качестве текста в текстовый список.

Для получения дополнительной информации о том, как использовать описанные выше методы и пакеты ОС, ознакомьтесь с этими тремя ссылками: os.fsdecode, os.listdir и документация ОС.

Теперь запустите функцию следующим образом:

#running the function
screenplays_loader(screenplays)

И проверь длину…

#checking the length
print(len(screenplays['title']))
2125

У нас есть 2125 сценариев в объекте словаря Python. Теперь мы сохраним словарь как фрейм данных Pandas.

#converting the dict into a pandas dataframe
data = pd.DataFrame(screenplays)

3. Маркировка жанров сценария с помощью пакета tmbdsimple

Названия фильмов были сохранены с тегом script, символами подчеркивания (_) и неравномерным интервалом. Перед использованием API базы данных фильмов необходимо очистить имена заголовков.

Мы будем использовать пакет pandas и regex для очистки названий фильмов.

#cleaning titles
data['title'] = data.title.str.replace('scrip', '')
data['title'] = data.title.str.replace('_', ' ')
data['title'] = data.title.apply(lambda x: re.sub(r"\B([A-Z])", r" \1", x))

Обратите внимание: при доступе к заголовкам в нашем фрейме данных необходимо использовать метод .str для изменения текстов столбца. Неиспользование метода .str вызовет ошибку.

Кроме того, в этой части серии статей я не буду вдаваться в подробности о пакете regex; это произойдет в части 4. Перейдите по этой ссылке, если хотите понять, что такое метод re.sub.

Теперь мы можем подключиться к API базы данных фильмов.

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

tmdb.API_KEY = 'YOUR SECRET CODE' #codes are avaliable for free when signing up on their website

#search object that looks up movie information by title
search = tmdb.Search()

#genre object
genre = tmdb.Genres()

#saving genres and coressponding codes for labelling
genres_lst = genre.movie_list()

Объект поиска позволяет нам запросить TMDB. Например, если бы мы хотели запросить информацию о Мстителях, наш код выглядел бы так:

#querying for The Avengers
search.movie(query='The Avengers')

Результаты:

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

To do so:

#querying for The God Father
search.movie(query='The Avengers')['results'][0]['genre_ids']

Результат:

[878,28,12]

Результатом является список чисел. Вот почему мы создали переменную жанра и переменную жанров_lst. TMDB маркирует свои фильмы идентификаторами целочисленного типа. Gener_lst показывает нам, какой идентификатор соответствует какому жанру.

print('genres_lst)
{'genres': [{'id': 28, 'name': 'Action'},   {'id': 12, 'name': 'Adventure'}, {'id': 16, 'name': 'Animation'}, {'id': 35, 'name': 'Comedy'}, {'id': 80, 'name': 'Crime'}, {'id': 99, 'name': 'Documentary'}, {'id': 18, 'name': 'Drama'}, {'id': 10751, 'name': 'Family'},{'id': 14, 'name': 'Fantasy'}, {'id': 36, 'name': 'History'}, {'id': 27, 'name': 'Horror'}, {'id': 10402, 'name': 'Music'}, {'id': 9648, 'name': 'Mystery'}, {'id': 10749, 'name': 'Romance'}, {'id': 878, 'name': 'Science Fiction'},{'id': 10770, 'name': 'TV Movie'}, {'id': 53, 'name': 'Thriller'}, {'id': 10752, 'name': 'War'}, {'id': 37, 'name': 'Western'}]}

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

Суть этой функции находится в циклах for. Сначала мы просматриваем результаты запроса, а затем просматриваем список жанров. Функция проверяет, совпадает ли id жанра из результатов запроса с id из списка жанров. Если это так, то название жанра (т. е. боевик, комедия и т. д.) добавляется к переменной lst.

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

#applying function on all titles in dataset
data['genre'] = data.title.apply(lambda x: genre_labeller(x))

Результат будет выглядеть так:

print(data.loc[:, ['title', 'genre'])

Как видите, в каждой строке есть список жанров в столбце жанров.

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

4. Создание целей с помощью горячего кодирования

Для обучения модели классификации с несколькими метками цели должны быть в двоичном формате. В частности, каждый жанр будет отдельной колонкой, и если сценарий соответствует этому жанру, будет 1, а если нет, то 0.

Например, фильм Немножко беременна помечен как комедия, мелодрама или драма. Столбцы для этих жанров для этого фильма будут выглядеть так:

Однако Knocked Up не помечен ни одним из других 15 жанров, поэтому весь ряд будет выглядеть следующим образом:

В целом, мы хотим, чтобы весь фрейм данных выглядел так:

Для достижения этой цели мы сначала очистим колонку жанров.

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

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

Запустите функцию, используя метод pandas .apply.

#applying lst_breaker function
data['genre'] = data.genre.apply(lambda x: lst_breaker(x)).copy()

Затем мы собираемся создать список жанров.

#creating a list of genres to one hot enocode

genre_lst = []

for i in data.genre:
    for x in i: #loops through genre column and appends genre names to list
        genre_lst.append(x)

genre_lst = list(set(genre_lst)) #creates a set to remove duplicates

Распечатать список:

print(genre_lst)
['Crime',
 'Romance',
 'Animation',
 'SciFi',
 'Fantasy',
 'History',
 'Action',
 'Drama',
 'War',
 'Thriller',
 'Mystery',
 'Documentary',
 'Horror',
 'Family',
 'Adventure',
 'Music',
 'Comedy',
 'Western']

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

def genre_encoding(movie_genres, genre):
    
    """
    This function takes a list of genres and a genre name. If the genre name exists in the listthen thee  function returns 1. Else, returns 0. Ultimately, this is the function that one hot encodes our targets.
    
    """
    if genre in movie_genres:
        return 1
    else:
        return 0

Функция имеет два параметра: movie_genres и жанр. Переменная movie_genres — это список жанров, связанных с конкретным фильмом. Например, если бы мы использовали однократное кодирование Немножко беременна, тогда для переменной movie_genres было бы установлено значение [‘Комедия’, ‘Драма’, ‘Романтика’].

Наш новый фрейм данных:

Мы, наконец, закончили маркировку наших данных. Я бы рекомендовал сохранить фрейм данных как новый файл CSV.

В следующей части серии я покажу, как использовать пакет wordcloud для иллюстрации подсчета слов по жанрам.

Использованная литература: