Рекомендательная система фильмов с GPT: холодный старт и вектор вкуса | AiManual
AiManual Logo Ai / Manual.
28 Апр 2026 Гайд

Как построить рекомендательную систему фильмов с GPT: решение cold start и вектор вкуса

Практическое руководство по созданию рекомендательной системы с нуля с использованием GPT. Решаем проблему cold start, строим вектор вкуса на основе эмбеддингов

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

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

В этой статье мы соберем прототип рекомендательного сервиса (назовем его NextFilm) на Python, OpenAI API и косинусной близости. Без сложных моделей, без тонн данных, но с работающим решением, которое можно запустить за вечер.

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

Проклятие белого листа

Проблема cold start убивает не только новые платформы. Даже зрелые сервисы вроде Netflix сталкиваются с ней, когда у пользователя нет истории — он залогинился через соцсеть, но не поставил ни одного лайка. Классические подходы:

  • Популярное — показываем топ по рейтингу. Скучно, не персонализировано, конверсия в просмотр — пшик.
  • Жанровый фильтр — пользователь выбирает жанры. Результат: куча однотипного треша.
  • Анкета при регистрации — 10 вопросов, которые все ненавидят заполнять. Брошенные корзины.

Ни один из вариантов не учитывает нюансы: атмосферу, темп повествования, визуальный стиль, неожиданные твисты. То, что делает фильм "нашим", не помещается в три тега.

Выход — использовать естественный язык. Пусть пользователь опишет, что он любит, своими словами. А GPT превратит этот поток сознания в структурированный профиль. Мы уже разбирали похожий подход для литературы в статье Твои литературные вкусы — это код. Давай его декомпилируем через ChatGPT — там та же идея, но для книг.

Вектор вкуса: ДНК вашего пользователя

Магия в эмбеддингах. Любой текст (описание фильма, отзыв, диалог с пользователем) можно превратить в вектор фиксированной размерности. Если два вектора близки в многомерном пространстве — их смыслы похожи.

Наша идея:

  1. Получить эмбеддинги всех фильмов из базы (на основе описаний, жанров, ключевых слов).
  2. Через диалог с LLM выяснить вкусы нового пользователя и сгенерировать текстовый портрет его идеального фильма.
  3. Преобразовать этот портрет в эмбеддинг — это и есть вектор вкуса.
  4. Найти k ближайших соседей среди фильмов — готово, рекомендации.

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

1Шаг 1. База фильмов и эмбеддинги

Возьмем небольшой датасет из 5000 фильмов (для прототипа хватит). Для каждого нужен текстовый дескриптор. Лучше всего — описание сюжета из Wikipedia или TMDB. Склеим с жанрами, ключевыми словами, именами режиссеров и актеров. Пример:

import openai

openai.api_key = "sk-..."  # ваш ключ

# Функция для эмбеддинга текста (используем text-embedding-3-small)
def get_embedding(text: str, model="text-embedding-3-small") -> list[float]:
    text = text.replace("\n", " ")
    return openai.embeddings.create(input=[text], model=model).data[0].embedding

# Собираем дескриптор фильма
desc = f"{title}. Жанры: {genres}. Режиссер: {director}. Сюжет: {plot}."
film_vector = get_embedding(desc)
# Сохраняем в numpy array или Faiss index

Для больших баз (миллионы фильмов) используйте Faiss от Facebook — он умеет искать ближайших соседей за миллисекунды. Мы же для MVP обойдемся простым сканированием с numpy.

2Шаг 2. GPT-опросник для пользователя

Теперь начинается магия. Вместо скучной формы мы запускаем диалог. Бот задает 5-7 открытых вопросов, а пользователь отвечает как хочет. Пример промпта:

system_prompt = """Ты — дружелюбный киноэксперт. Твоя задача — выяснить вкусы пользователя.
Задай ему 5 вопросов о любимых фильмах, режиссерах, атмосфере, темпе, визуале.
После получения ответов сгенерируй краткий, но ёмкий портрет его идеального фильма (3-5 предложений).
Портрет НЕ должен включать названия реальных фильмов — только описание характеристик.
"""

messages = [{"role": "system", "content": system_prompt}]
for _ in range(5):
    user_answer = input("Ваш ответ: ")  # или из чата
    messages.append({"role": "user", "content": user_answer})
    # Получаем следующий вопрос (или финальный портрет)
    response = openai.chat.completions.create(model="gpt-4o", messages=messages)
    assistant_msg = response.choices[0].message.content
    messages.append({"role": "assistant", "content": assistant_msg})
    if len(messages) >= 12:  # после 5 ответов получаем портрет
        break

# Извлекаем портрет — он будет последним сообщением ассистента
profile_text = assistant_msg
print("Ваш профиль вкуса:", profile_text)

Важно: GPT-4o (актуальная на апрель 2026) отлично справляется с извлечением неявных предпочтений. Например, если пользователь скажет "люблю Тарантино за диалоги", модель поймет: важны остроумные разговоры, нелинейный сюжет, напряжение. И включит это в портрет.

3Шаг 3. Вектор вкуса и поиск

Превращаем портрет в эмбеддинг той же моделью, что и фильмы, и ищем ближайших соседей:

import numpy as np
from sklearn.metrics.pairwise import cosine_similarity

user_vector = np.array(get_embedding(profile_text))
# film_vectors — матрица (N, 1536), где N — число фильмов
similarities = cosine_similarity([user_vector], film_vectors)[0]
top_indices = similarities.argsort()[-10:][::-1]

recommendations = [films[i].title for i in top_indices]
print("Рекомендуем:", recommendations)

Всё. За 10 строк кода мы решили cold start. Пользователю не нужно ничего оценивать — только поболтать с ботом. При этом рекомендации будут точнее, чем жанровая фильтрация, потому что мы учли нюансы.

Ошибка, которую я совершил в первый раз: я использовал разные модели эмбеддингов для пользователя и фильмов. Например, text-embedding-3-small для фильмов и ada-002 для профиля. Векторы лежат в разных пространствах, косинусная близость не работает. Всегда используйте одну и ту же модель.

Как улучшить точность: асессмент и обратная связь

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

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

Продвинутые техники: мультимодальность и кластеризация

Хотите еще круче? Добавьте постеры фильмов. С помощью мультимодальных эмбеддингов (например, Gemini Embedding 2, который мы разбирали в мультимодальном RAG туториале) можно учесть визуальный стиль. Пользователь говорит "люблю нуар с контрастным светом" — и система ищет фильмы с похожей цветовой палитрой.

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

Главные грабли (и как их обойти)

  • Размер эмбеддингов. У text-embedding-3-small размерность 1536. Для 100 000 фильмов матрица займет ~600 МБ в памяти — отлично. Но если миллионы, используйте Faiss с компрессией (PQ), иначе ОЗУ уплывет.
  • Длина портрета. GPT может сгенерировать портрет слишком обобщенный ("люблю хорошее кино"). Задайте в промпте ограничение: "Опиши идеальный фильм максимально конкретно: жанр, темп, настроение, визуальный стиль, необычные элементы".
  • Холодный старт для самого фильма. Новый фильм без оценок — та же проблема. Но наш метод работает: просто добавьте описание в базу, и его найдут по эмбеддингу. Контентная близость не требует рейтингов.
  • Язык. Модели эмбеддингов лучше всего работают на английском. Если база на русском — используйте multilingual-модели (например, text-embedding-3-small поддерживает много языков, но проверьте). Или переводите описания на английский через GPT перед эмбеддингом — это дополнительный расход токенов, но качество вырастает.

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

Что дальше? Вместо заключения

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

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

Единственное, о чем стоит помнить: GPT — не панацея. Он дорогой на этапе генерации портрета (но это разово для пользователя), и он может галлюцинировать. Всегда проверяйте финальный портрет на осмысленность. А лучше — дайте пользователю его отредактировать перед поиском. И тогда ваш NextFilm станет тем сервисом, с которого не хочется уходить.

Подписаться на канал