Решение холодного старта
Стратегии для новых пользователей и айтемов: контент-фичи, бандиты, популярность.
Холодный старт — как рекомендовать, когда данных нет
Загрузка интерактивного виджета...
Ты построил рекомендательную систему: collaborative filtering, матричные разложения, нейросетевой ранкер — всё работает. Метрики отличные. А потом приходит пользователь, который только что зарегистрировался. Ноль кликов, ноль покупок, ноль истории. Что рекомендовать?
Или другой сценарий: маркетплейс добавил 10 000 новых товаров за ночь. Ни у одного нет взаимодействий. Collaborative filtering их не видит — для модели этих айтемов не существует. Это холодный старт — одна из самых болезненных и практически важных проблем в RecSys.
Холодный старт — не edge case. Каждый маркетплейс, стриминг, новостной агрегатор сталкивается с ним ежедневно. На быстро растущей платформе 30-50% пользователей в любой момент — «холодные». Если ты не решил cold start, ты теряешь пользователей прямо на входе: получил нерелевантные рекомендации → закрыл приложение → не вернулся.
Большая картина: замкнутый круг данных
Корень проблемы — порочный круг: нет данных → нет хороших рекомендаций → пользователь не взаимодействует → нет данных. Коллаборативная фильтрация учится на взаимодействиях — а их нет. Эмбеддинг пользователя строится из истории — а история пустая. Модель буквально не знает, с чего начать.
Два отдельных типа холодного старта требуют разных стратегий: Cold user — новый пользователь без истории. Нет эмбеддинга, нет профиля предпочтений. Задача: за 3-5 взаимодействий понять вкусы и перейти от случайных рекомендаций к персонализированным. Cold item — новый товар/фильм/трек без взаимодействий. CF-модель его не видит. Задача: встроить в каталог так, чтобы правильные пользователи его увидели.
Холодный пользователь: от популярного к персональному
Стратегии для нового пользователя выстраиваются в цепочку — каждая следующая включается по мере накопления данных:
Этап 1: Popularity baseline (0 взаимодействий) Ничего не знаем — показываем то, что нравится большинству. Топ-продажи по категории, тренды за неделю, бестселлеры. Это не персонализация, но лучше, чем случайный набор. Популярное хотя бы гарантирует минимальное качество — по определению это контент, который понравился многим.
Этап 2: Onboarding / анкета (первые секунды) Спрашиваем напрямую. Spotify при регистрации просит выбрать 3 жанра. Pinterest — 5 пинов. Netflix — 3 фильма. Это 3-5 бесплатных сигналов, которые мгновенно превращают нулевой профиль в грубый, но рабочий. Важно: анкета должна быть быстрой (≤30 секунд) и визуальной (картинки > текст). Каждый дополнительный шаг онбординга теряет 10-15% пользователей.
Этап 3: Exploration через бандитов (1-10 взаимодействий) После онбординга начинаем активный exploration: показываем разнообразные айтемы и быстро учимся из реакций. Thompson Sampling или UCB (подробнее ниже) решают, что показать следующим — exploitation (то, что, по нашим данным, нравится) или exploration (то, что мы ещё не пробовали, чтобы узнать вкусы).
Этап 4: Collaborative Filtering (после 5-10 действий) После 5-10 взаимодействий у пользователя появляется достаточно истории, чтобы CF-модели начали работать: матричные разложения находят ближайших соседей, нейросетевые модели строят эмбеддинг. Пользователь «прогрелся» — дальше работаем как обычно.
def recommend_cold_user(user, n_interactions: int, k: int = 10):
"""Каскадная стратегия для холодного пользователя."""
if n_interactions == 0 and not user.onboarding_done:
# Этап 1: чистый popularity
return get_popular_items(k=k, category=user.signup_category)
if n_interactions <= 2:
# Этап 2: popularity + контекст (устройство, время, гео)
context = extract_context(user) # device, time, geo
return get_contextual_popular(context, k=k)
if n_interactions <= 10:
# Этап 3: бандит (exploration + exploitation)
return bandit.recommend(user_id=user.id, k=k)
# Этап 4: полноценный CF
return cf_model.recommend(user_id=user.id, k=k)Холодный айтем: контент вместо коллабораций
Новый товар, фильм, трек — ни одного клика. CF-модель бессильна: нет столбца в матрице взаимодействий, нет эмбеддинга. Но у айтема есть контент: название, описание, категория, изображение, аудио. Из этого можно построить эмбеддинг до первого показа.
Content-based эмбеддинги: пропускаем описание через E5/BGE, картинку через CLIP, аудио через модель спектрограмм. Получаем вектор, который можно сравнить с векторами существующих айтемов. Новые кроссовки Adidas → ближайшие соседи — другие беговые кроссовки → показываем тем же пользователям.
Cold-start embedding — продвинутый подход: обучаем отдельную модель, которая по контентным признакам предсказывает collaborative-эмбеддинг. На обучении она видит пары (контент айтема → его CF-эмбеддинг). На инференсе: новый айтем → подаём контент → получаем pseudo-CF эмбеддинг. Spotify так делает с новыми треками: нейросеть по аудио-спектрограмме предсказывает collaborative-эмбеддинг, и новый трек сразу попадает в правильное «соседство».
Exploration boost: даже лучший эмбеддинг — это предсказание. Чтобы быстро получить реальные данные, новым айтемам искусственно повышают скор первые N показов. Два подхода: • Additive boost — прибавляем бонус к скору, который decay-ится со временем (первые 7 дней) • Exploration slot — резервируем 1-2 позиции в ленте для новых айтемов. Фиксированные слоты = гарантированные показы для exploration
from sentence_transformers import SentenceTransformer
import faiss
import numpy as np
# Строим cold-start эмбеддинг для нового товара
model = SentenceTransformer("intfloat/multilingual-e5-large")
# Существующие товары уже в FAISS-индексе
existing = ["Кроссовки Nike Air Max", "Куртка Columbia", "Наушники Sony"]
existing_embs = model.encode(["query: " + t for t in existing])
index = faiss.IndexFlatIP(existing_embs.shape[1])
index.add(existing_embs.astype("float32"))
# Новый товар — сразу находим соседей по описанию
new_item = "Беговые кроссовки Adidas Ultraboost 2024"
new_emb = model.encode(["query: " + new_item]).astype("float32")
D, I = index.search(new_emb, k=5)
# I = [0, ...] — ближайший сосед: Nike Air Max (тоже кроссовки)
# → показываем новый товар пользователям, которые покупали NikeMeta-learning: научиться учиться за 3 клика
Опытный продавец в магазине за 3 вопроса понимает, что тебе нужно. Не потому что знает именно тебя — а потому что видел тысячи покупателей и научился быстро понимать новых. Meta-learning переносит эту идею в ML: модель тренируется не предсказывать конкретного пользователя, а быстро адаптироваться к любому новому по 1-5 взаимодействиям.
MAML (Model-Agnostic Meta-Learning) — ключевой алгоритм. Идея: найти такие начальные веса модели, из которых 2-3 шага градиентного спуска на данных нового пользователя дают хорошие рекомендации. Как это работает для RecSys: 1. Каждый существующий пользователь — отдельная «задача» (task) 2. Для каждой задачи: берём 3-5 взаимодействий как support set, остальные — как query set 3. Делаем пару шагов SGD на support set → получаем адаптированные веса 4. Считаем loss на query set → обновляем начальные веса через мета-градиент 5. После обучения: новый пользователь + 3 клика → 2 шага SGD → персонализированная модель
Prototypical Networks (ProtNet) — ещё проще. Идея: каждый «класс» (предпочтение пользователя) представлен прототипом — средним эмбеддингом его примеров. Для cold start: берём 3-5 айтемов, с которыми пользователь взаимодействовал → усредняем их эмбеддинги → получаем прототип пользователя → рекомендуем айтемы, ближайшие к прототипу. Не нужны градиентные шаги — только forward pass.
MAML vs ProtNet: когда что
Exploration vs Exploitation: бандиты для холодного старта
Центральная дилемма cold start: показать то, что, вероятно, понравится (exploitation) или попробовать новое, чтобы узнать больше (exploration)? Чистый exploitation на холодном пользователе — это popularity, одинаковая для всех. Чистый exploration — случайные рекомендации, ужасный UX. Нужен баланс.
ε-greedy — простейший подход. С вероятностью (1−ε) показываем лучшее по текущей модели, с вероятностью ε — случайный айтем. ε = 0.1 означает: 90% exploitation, 10% random exploration. Проблема: exploration полностью случайный — не учитывает неопределённость, может «разведывать» заведомо плохие айтемы.
Thompson Sampling — элегантнее. Для каждого айтема (или категории) храним распределение CTR — Beta(α, β). При каждом запросе сэмплируем из распределения и выбираем айтем с максимальным сэмплом. Если мы неуверены в айтеме (мало данных → широкое распределение), он иногда получит высокий сэмпл и будет показан. По мере накопления данных распределение сужается → exploitation.
UCB (Upper Confidence Bound) — выбираем айтем с максимальным score + bonus за неопределённость. Формула: score_i = μ_i + c·√(ln(N)/n_i), где μ_i — средний reward айтема, N — общее число показов, n_i — число показов айтема i. Чем реже показывали айтем — тем больше бонус. UCB детерминирован (в отличие от стохастического Thompson Sampling) и имеет теоретические гарантии сходимости.
import numpy as np
class ThompsonSamplingColdStart:
"""Быстрый онбординг нового пользователя через Thompson Sampling."""
def __init__(self, n_categories: int):
# Beta(α, β) для каждой категории
self.alpha = np.ones(n_categories) # successes + 1 (prior)
self.beta = np.ones(n_categories) # failures + 1 (prior)
def recommend(self, k: int = 5) -> list[int]:
# Сэмплируем CTR из апостериорного распределения
samples = np.random.beta(self.alpha, self.beta)
return np.argsort(samples)[-k:][::-1].tolist()
def update(self, category: int, clicked: bool):
if clicked:
self.alpha[category] += 1 # успех → α растёт
else:
self.beta[category] += 1 # неудача → β растёт
# Новый пользователь: первые показы — почти exploration
bandit = ThompsonSamplingColdStart(n_categories=20)
recs = bandit.recommend(k=5) # широкие Beta(1,1) → почти random
# Кликнул на электронику, проигнорировал одежду
bandit.update(category=3, clicked=True)
bandit.update(category=7, clicked=False)
# Уже через 5 кликов: Beta(3,1) для электроники → высокий CTR
recs = bandit.recommend(k=5) # электроника чаще попадает в топПрактические решения: как это работает в продакшне
Hybrid warmup — основной паттерн. Для холодного пользователя используешь content-based + popularity + контекст. По мере накопления данных плавно переключаешь вес на CF-модель:
def hybrid_score(user, item, n_interactions: int) -> float:
"""Плавный переход от content-based к collaborative."""
# Вес CF растёт с числом взаимодействий
cf_weight = min(n_interactions / 10, 1.0) # 0→1 за 10 кликов
cb_weight = 1.0 - cf_weight
score_cf = cf_model.predict(user, item) # collaborative
score_cb = content_model.predict(user, item) # content-based
return cf_weight * score_cf + cb_weight * score_cbContextual bandits — продвинутый подход. Обычные бандиты (Thompson Sampling, UCB) не учитывают контекст: время суток, устройство, категорию страницы. Contextual bandit принимает на вход вектор контекста и выбирает действие (какой айтем показать). LinUCB — линейная модель, которая для каждого arm (айтема) решает ridge regression и строит confidence interval.
На практике contextual bandits часто реализуют через explore-exploit slot: основная лента генерируется CF-моделью, а 1-2 позиции отданы бандиту, который экспериментирует. Это безопасно: пользователь видит в основном знакомый контент, а бандит собирает данные для cold start.
Метрики для cold start — отдельная история. Обычные офлайн-метрики не работают: для холодных пользователей нет истории для валидации. • First-N metric — качество рекомендаций в первых 5-10 взаимодействиях. Именно тут решается: уйдёт пользователь или останется • Retention D1/D7 — вернулся ли через 1/7 дней. Главная бизнес-метрика для cold start • Когортный анализ — оценивай отдельно «холодную» (0-3 действия) и «тёплую» (>10) когорты • Time-split — обучайся на первых N днях, тестируй на новых пользователях следующих дней
🎯 На собеседовании
Junior
Middle
Senior
Собираем всё вместе
Холодный старт — это не один алгоритм, а стратегия перехода: от popularity через exploration к полноценному CF. Для пользователей: каскад popularity → onboarding → bandits → CF. Для айтемов: content-based эмбеддинг → exploration boost → CF по мере накопления данных.
Если запомнить одну вещь: cold start решается не выбором алгоритма, а правильной цепочкой fallback-стратегий. Thompson Sampling для exploration, content-based embedding для новых айтемов, hybrid warmup для плавного перехода — всё это вместе, а не по отдельности.
Дальше на роадмапе: Two-Tower покажет архитектуру, которая естественно решает cold start через content-ветку башни, а Multi-Stage Ranking объяснит, как exploration встраивается в production pipeline.
Материалы
Обзор подходов к холодному старту: popularity, content-based, meta-learning, бандиты.
Оригинальная статья MAML. Читать секции 1-3 для понимания few-shot адаптации.
Практические решения cold start от Google: onboarding, hybrid, exploration.
Лекция Stanford: collaborative filtering, cold start, exploration-exploitation.
Глава про RecSys: MF, FM и cold start strategies с кодом.