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

Можно ли подобрать прокатные характеристики фильма (жанр, время, деятельность, возрастной рейтинг и т.п.), а также режиссера, сценариста, актеров, оператора (всю творческую группу) так, чтобы существенно повысить его результаты в прокате? Желательно заранее, до начала съемок.

Что происходит «под капотом» у Голливуда?

Мультивселенные. Комиксы. Герои фильмов: клишированные и как будто бы рождённые на одной фабрике клонов, хоть и обладающие при этом разными уникальными способностями. Почему так происходит?

Все дело в том, что Голливуд из «фабрики грез» превратился в масштабную генеративную нейросеть по производству контента. И эта сеть находится в состоянии «переобученности», поскольку начала учиться на своих же продуктах.

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

Прокатные характеристики и творческая группа подбираются с помощью алгоритмов машинного и глубокого обучения, сценарий прогоняется через языковые модели и сравнивается с мириадами подобных, реализованных нереализованных вариантов.  Разумеется, ни один голливудский режиссёр или продюсер вам в этом не признаются (кино же — это творчество!). А те деятели старой закалки (привет… Стивен Спилберг), которые по умолчанию этим не пользуются — давно вышли в тираж и забыты киностудиями.

Подобрать данные фильма, включая его жанр, длительность, выбрать актеров и других участников творческой группы — вполне возможно. И это ненамного сложнее чем подобрать гиперпараметры ML-модели с помощью GridSearchCV, RandomizedSearchCV или Optuna.

Российский кинематограф, уже не ламповый, но все еще не настолько нейросетевой в погоне за «голливудщиной» тоже рано или поздно примется за оптимизацию прокатных характеристик и состава творческих групп и в этой связи очень бы хотелось, чтобы мы все-таки зафиксировали для него  некий ModelCheckpoint с наилучшим состоянием по производительности и метрикам, до тех пор, пока он не начнет «переобучаться» вслед за голливудской метамоделью-образцом

Разумеется, опытный режиссер с большим числом успешных проектов по сценарию маститого сценариста-ремесленника и под руководством продюсера, заинтересованного в заработке с проекта с приглашением известных и «кассовых» актеров скорее всего снимет хорошее и окупающееся в прокате кино. Кто же даст дорогу молоды и начинающим? кто будет «штрафовать модель отбора», чтобы не допустить ее переобучения? Хотя, может быть, мы зря тревожимся и в России все это не сработает?

Сработает. И вот почему…

Гипотеза применимости гиперпараметрической оптимизации проекта киноконтента

Итак, не раскрывая всех карт, докажем, что «правильный» подбор параметров фильма и состава группы является определяющим фактором успеха. Задолго до выхода в прокат и даже до начала съемок. И сделаем это на материалах российских кинофильмов.

Соответственно, если мы создадим работающую модель для прогнозирования кассового успеха на первом датасете и успешно ее апробируем на втором (у нас условия «проверки реальностью» более жесткие, поэтому незначительное падение метрик качества допустимо) — наша гипотеза будет доказана. Вдобавок мы со временем сможем посмотреть, какие именно факторы и как влияют на результаты проекта в прокате.

В реальной ситуации, когда у нас на руках очень интересный проект, который потенциально не окупается в прокате — мы можем так подобрать его «гиперпараметры»: жанр, длительность, возрастные ограничения, ведущих актеров и даже режиссера со сценаристами (или отправить в отставку продюсера и заменить его новым) — что он если не сорвет всю кассу, то хотя бы соберет два своих бюджета.

И такая «математическая эквилибристика» вполне себе оправдана — даже очень хороший режиссер, набивший руку в «хоррорах» (привет, «Пираты галактики Барракуда»), вряд ли проявит себя в мелодраме и мюзикле. То же самое справедливо для актеров, операторов, сценаристов и ведущих актеров (у последних так вообще есть свое амплуа, в пределах которого они могут эффективно работать).

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

Для проверки идеи  мы воспользуемся датасетом из данных по 1680 российским картинам. (все датасеты и коды по прогнозированию российского кинопроката публикуются в репозитории проекта, однако данный датасет будет опубликован к выходу «тяжелой» научной статьи по объективным редакционным причинам).

Предикторами у нас будут выступать следующие столбцы данных:

Структура датасета

"week", "month", "Screens" — неделя, месяц выхода в прокат и соответственно количество экранов в прокате.

"budget", "age_R", "time" — бюджет проекта, возрастной рейтинг, длительность.

'main_genre', 'genre_2', 'genre_3', 'genre_4', 'genre_5' — до 5 определений жанра проекта.

'dir1', 'dir2', 'dir3', 'dir4' — персоналии режиссеров проекта (из может быть несколько).

'wr1', 'wr2', 'wr3', 'wr4' — персоналии сценаристов проекта.

'prod1', 'prod2', 'prod3', 'prod4' — персоналии продюсеров проекта.

'op1', 'op2', 'op3', 'op4' — операторы проекта.

 'dr1', 'dr2', 'dr3', 'dr4', 'dr5', 'dr6', 'dr7' — художники-постановщики.

'ed1', 'ed2', 'ed3', 'ed4' — режиссеры монтажа, монтажеры.

'komp1', 'komp2', 'komp3', 'komp4' — композиторы, предоставившие свою музыку или написавшие аранжировку для кинопроизведения.

 'act1', 'act2', 'act3', 'act4', 'act5', 'act6', 'act7', 'act8', 'act9', 'act10' — до 10 ведущих актеров в проекте.

Прогнозируемой величиной у нас будет выступать столбец "rezult2" — двухклассовая разметка, в которой 0 — фильм не окупился в прокате, не собрал два своих бюджета и 1 — фильм окупился в прокате.

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

Самое первое, что мы сделаем — используем LabelEncoder из sklearn.preprocessing, чтобы закодировать категориальные переменные (названия жанров и имя с фамилией участников творческой группы в числовые значения. Затем с помощью df.fillna заменим шестью девятками все пропуски и NaNв датасете.

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

При этом данные первого датасета для обучения и тестирования перемешаны в псевдослучайном порядке без учета хронологии выхода картин, а вот второй датасет — это 180 относительно свежих кинокартин, которые вышли в прокат после ухода иностранных дистрибьютеров с российского рынка. И этот факт должен, по идее, существенно сказаться на условиях окупаемости российских кинокартин и «смешать все карты». Если модель с ним справится — наша гипотеза доказана и «гиперпараметрический подбор» характеристик и состава творческой группы возможен.

Следующая проблема, которую нам необходимо решить — несбалансированность классов. Окупившихся в прокате фильмов примерно в 10 раз меньше, чем провалившихся. Здесь мы попробуем три методики выравнивания:

Увеличение выборки с использованием библиотеки imbalanced-learn, а именно: RandomOverSampler.

Синтетическое генерирование данных — SMOTE.

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

Во всех случаях мы будем сэмплировать только x_train, y_train

x_test и y_test — остаются без изменений, с текущей пропорцией классов.

Погружение в данные

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

Код ансамблевых моделей

# Создаем модели
models = {
    'AdaBoost': AdaBoostClassifier(random_state = 42),
    'Bagging': BaggingClassifier(random_state = 42),
    'ExtraTrees': ExtraTreesClassifier(random_state = 42),
    'GradientBoosting': GradientBoostingClassifier(random_state = 42),
    'RandomForest': RandomForestClassifier(random_state = 42),
    'Stacking': StackingClassifier(estimators=[('lr', LogisticRegression(random_state = 42)), ('rf', RandomForestClassifier(random_state = 42))]),
    'Voting': VotingClassifier(estimators=[('lr', LogisticRegression(random_state = 42)), ('rf', RandomForestClassifier(random_state = 42))]),
    'HistGradientBoosting': HistGradientBoostingClassifier(random_state = 42),
    'CatBoost': CatBoostClassifier(verbose=0,random_state = 42)
}

# Обучаем модели на базовых настройках и делаем предсказания
predictions = {}
for name, model in models.items():
    model.fit(X_train, y_train)
    y_pred = model.predict(X_test)
    predictions[name] = y_pred

# Создаем словарь для метрик
metrics = {
    'Accuracy': accuracy_score,
    'Precision': precision_score,
    'Recall': recall_score,
    'F1_score': f1_score,
    }

# Вычисляем метрики для каждой модели
results = {}
for name, y_pred in predictions.items():
    results[name] = [metric(y_test, y_pred) for metric in metrics.values()]

# Выводим сравнительную таблицу с метриками
print("Метрики для различных моделей:")
print("{:<20} {:<10} {:<10} {:<10} {:<10} ".format("Модель", "Accuracy", "Precision", "Recall", "F1_score"))
for name, metrics in results.items():
    print("{:<20} {:<10.4f} {:<10.4f} {:<10.4f} {:<10.4f} ".format(name, *metrics))

Мы экспериментировали с разными механизмами выравнивания баланса классов. Ниже приведена табличка с данными для RandomOverSampler

Метрики моделей на датасете с выровненными классами с помощью RandomOverSampler

Метрики моделей на датасете с выровненными классами с помощью RandomOverSampler

В спойлере — таблички производительности моделей на выровненных классах с помощью SMOTE и ADASYN, а также на базовом датасете, в котором не производилась балансировка классов

Балансировка классов SMOTE и ADASYN

Метрики моделей на датасете с выровненными классами с помощью ADASYN

Метрики моделей на датасете с выровненными классами с помощью ADASYN

Метрики моделей на датасете с выровненными классами с помощью SMOTE

Метрики моделей на датасете с выровненными классами с помощью SMOTE

Без балансировки

Метрики моделей на несбалансированном датасете

Метрики моделей на несбалансированном датасете

Как мы видим, балансировка классов не очень-то улучшает производительность моделей. Разумеется можно попробовать поэкспериментировать со Stacking и Voting и их вложениями-алгоритмами, однако остановимся на на двух ансамблях, показавших неплохие результаты по метрике recall (в большей мере нас интересуют именно успешные проекты) и общей точностиHistGradientBoostingClassifier и AdaBoostClassifier .

Подбор гиперпараметров моделей

Далее мы попробуем подобрать гиперпараметры этих моделей, чтобы повысить их качество. При этом мы пойдем двумя путями: AdaBoost мы будем совершенствовать под «отклик» и идентификацию успешных проектов, а HistGradientBoosting у нас будет отвечать за общую accuracy.

Подбор гиперпараметров HistGradientBoosting

# Определяем модель
model = HistGradientBoostingClassifier(random_state=42)

# Задаем параметры для подбора
param_distributions = {
    'max_iter': np.arange(500, 1301, 50),      # Количество итераций
    'max_depth': np.arange(4, 10),               # Глубина деревьев
    'learning_rate': np.linspace(0.01, 0.3, 10), # Темп обучения
    'l2_regularization': np.logspace(-1, 1, 5)  # L2-регуляризация
}

# Настраиваем RandomizedSearchCV
random_search = RandomizedSearchCV(
    model,
    param_distributions,
    n_iter=50,  # Число проб
    scoring='accuracy',  # Оценка качества
    cv=5,  # Кросс-валидация
    random_state=42,
    n_jobs=-1  # Используем все доступные ядра
)

# Запускаем поиск
random_search.fit(X, y)

# Получаем лучшие параметры и результат
print("Лучшие параметры:", random_search.best_params_)
print("Лучший результат:", random_search.best_score_)

Подбор гиперпараметров AdaBoost

# Определяем сетку поиска гиперпараметров
param_distributions = {
    'n_estimators': [100, 120, 130, 140, 150, 160],
    'learning_rate': [1.0, 0.1,0.01],
    'algorithm': ['SAMME']
}

# Создаем экземпляр модели с пустыми параметрами
model = AdaBoostClassifier(random_state=42)
# Создаем экземпляр RandomizedSearchCV с моделью и сеткой поиска
random_search = RandomizedSearchCV(model, param_distributions, n_iter=10, cv=3, scoring='recall', verbose=0)
random_search.fit(X_train, y_train)
# Выводим результаты
print("Лучшие параметры:", random_search.best_params_)

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

model = AdaBoostClassifier(n_estimators = 120,
                           learning_rate =0.01, algorithm= 'SAMME', random_state=42)

model_X = HistGradientBoostingClassifier(max_iter = 1200, max_depth = 6,
                                         learning_rate = 0.2677777777777778,
                                         l2_regularization =  1.0, random_state = 42)

Оценим две модели по их производительности, еще раз отметим, что у каждой из них — своя епархия. И в отношении AdaBoost мы идем на осознанную жертву, «выкручивая» алгоритм именно под выявление положительного класса и жертвуя всеми остальными.

Таблица 1. Метрики AdaBoostClassifier.

precision

recall

f1-score

support

0

0.993197

0.356968

0.525180

409

1

0.132013

0.975610

0.232558

41

accuracy

0.413333

0.413333

0.413333

0.413333

macro avg

0.562605

0.666289

0.378869

450

weighted avg

0.914734

0.413333

0.498519

450

Таблица 2. Метрики HistGradientBoostingClassifier

precision

recall

f1-score

support

0

0.950000

0.975550

0.962606

409

1

0.666667

0.487805

0.563380

41

accuracy

0.931111

0.931111

0.931111

0.931111

macro avg

0.808333

0.731678

0.762993

450

weighted avg

0.924185

0.931111

0.926232

450

Казалось бы, HistGradientBoostingClassifier по всем параметрам делает AdaBoostClassifierза исключением одного: нам нужны только успешные в прокате фильмы! Причем определенные достаточно точно.

Как мы видим на картинках ниже, AdaBoost очень точно выцепила 40 успешных кинокартин. В одной, правда, ошиблась. В отличие от HistGradientBoosting, у которого чувствительность все же ниже, 20 к 21

результаты классификации AdaBoost

результаты классификации AdaBoost

результаты классификации HistGradientBoosting

результаты классификации HistGradientBoosting

Результаты работы алгоритма AdaBoost

Результаты работы алгоритма AdaBoost

результаты работы алгоритма HistGradientBoosting

результаты работы алгоритма HistGradientBoosting

Итоговый эксперимент

Используем валидационый датасет из 180 кинокартин для оценки метрик двух полученных моделей. Напомним, что модели к нему не имели доступа, ни на этапе тренинга, ни на этапе тестирования, какая-либо утечка данных исключена. Кинокартины новые, указаны в хронологическом порядке (тестовые и тренинговые данные — перемешаны). Единственное что у них есть общее — это актеры, режиссеры, сценаристы, продюсеры, операторы, монтажеры, художники-постановщики — категориальные переменные, закодированные одинаковым образом, с уникальным номером для каждой персоналии.

Смотрим на результат

Метрики AdaBoost

precision

recall

f1-score

support

0

1.000000

0.418182

0.589744

165

1

0.135135

1.000000

0.238095

15

accuracy

0.466667

0.466667

0.466667

0.466667

macro avg

0.567568

0.709091

0.413919

180

weighted avg

0.927928

0.466667

0.560440

180

И здесь нас поджидает сюрприз: модель верно определила все 15 успешных фильмов в прокате (с другим классом — полный «расколбас», как и на тренинговых данных). Разумеется все другие метрики у нас не на высоте. За общую точность у нас отвечает другой алгоритм HistGradientBoosting — вот его его метрики:

precision

recall

f1-score

support

0

0.935294

0.963636

0.949254

165

1

0.400000

0.266667

0.320000

15

accuracy

0.905556

0.905556

0.905556

0.905556

macro avg

0.667647

0.615152

0.634627

180

weighted avg

0.890686

0.905556

0.896816

180

Выводы

Итак, на валидационной выборке мы получили практически такие же как и на тестовой выборке метрики качества. На практике это означает, что если бы мы вложились в 15 предварительно отобранных из 180 кинофильмов, то получили бы нехилую прибыль в X3-X4 от бюджета проектов или 200–300% годовых. И все потому что инвестиции шли бы в конкретных людей. В России есть примерно два десятка режиссеров, которые при правильном сочетании «гиперпараметров» картины, работая в своих жанрах получают стабильный результат. Таким же образом определяются «честные» продюсеры, которые зарабатывают не с бюджета, а с результатов проката, такие же «кассовые» актеры, операторы, сценаристы и другие члены творческой группы.

Небольшое замечание: пока мы проиллюстрировали только общий подход. Для по-настоящему точного прогноза окупаемости кинофильма, отбора прибыльных проектов из всего вороха представленных необходимо несколько ансамблевых моделей с разными настройками гиперпараметров — под «точность», «правильность», «чувствительность» или «отклик». такая большая условная «метамодель».

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

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

© Habrahabr.ru