Основы и классика
~20 мин

Введение в RecSys

Типы рекомендаций, холодный старт, content-based vs collaborative filtering.

Рекомендательные системы — как помочь пользователю найти нужное

Загрузка интерактивного виджета...

Netflix — 15 000 фильмов. Spotify — 100 миллионов треков. Amazon — 350 миллионов товаров. Ни один человек не способен просмотреть даже малую часть этого каталога. Но ты открываешь приложение — и за секунду видишь 10-20 вариантов, которые тебе интересны. За этим стоят рекомендательные системы (RecSys).

Рекомендации — это не просто «удобная фича». Это ядро бизнеса: 80% просмотров на YouTube приходят из рекомендаций, 35% выручки Amazon генерируют рекомендации, Netflix оценивает свою систему рекомендаций в $1 млрд/год (через удержание подписчиков). Если ты не можешь помочь пользователю найти нужное среди миллионов вариантов — он уйдёт к конкуренту, который может.

В этой ноде — полная картина рекомендательных систем: от базовых подходов (content-based, collaborative filtering) до продвинутых (matrix factorization, гибриды). Разберём типы обратной связи, ключевые метрики и проблему холодного старта.

Большая картина: что такое рекомендательная система

Любая рекомендательная система — это функция, которая принимает пользователя и каталог айтемов, и возвращает ранжированный список — от самого релевантного к наименее релевантному. Всё остальное — детали реализации.

У тебя есть три типа данных: Пользователи (users): профиль, демография, история действий. Пользователь — это набор сигналов о предпочтениях. Айтемы (items): фильмы, товары, треки, статьи. У каждого есть атрибуты — жанр, цена, описание, постер. Взаимодействия (interactions): клики, покупки, оценки, просмотры, добавления в корзину. Это мост между пользователями и айтемами — именно из взаимодействий система учится.

Алгоритм верхнего уровня: 1. Собираем данные — кто что делал (просмотры, клики, покупки) 2. Строим модель — находим паттерны (похожие пользователи, похожие айтемы, латентные факторы) 3. Генерируем кандидатов — из миллионов айтемов оставляем тысячи потенциально интересных 4. Ранжируем — среди кандидатов выбираем топ-10-20 для показа 5. Показываем и собираем фидбек — замкнутый цикл, система постоянно учится

Три подхода: контентный, коллаборативный, гибридный
Три базовых подхода к рекомендациям. В продакшне всегда используется комбинация
Обзор рекомендательных систем: матрица взаимодействий user-item
Рекомендательные системы: матрица взаимодействий пользователь-айтем — основа collaborative filtering. Источник: d2l.ai (CC BY-SA 4.0)

Content-Based Filtering — рекомендации по свойствам айтемов

Идея простая: если тебе нравятся боевики с Джеки Чаном — рекомендуем другие боевики с Джеки Чаном. Система смотрит на атрибуты айтемов (жанр, описание, теги, актёры) и ищет похожие на те, которые пользователь уже потребил.

Аналогия: ты приходишь в книжный магазин и говоришь — «мне понравился «Мастер и Маргарита». Продавец смотрит на жанр (магический реализм), автора (русская классика), тему (сатира) и предлагает Гоголя или Кортасара. Он ничего не знает о других покупателях — работает только с характеристиками книг.

Как представить айтем? Нужно превратить его атрибуты в вектор признаков: • TF-IDF — классический подход для текстовых описаний. Считаем частоту слов, взвешиваем по редкости. Два фильма с похожими описаниями → близкие вектора. • Эмбеддинги — пропускаем описание через нейросеть (BERT, sentence-transformers) и получаем плотный вектор. Ловит семантическую близость: «фильм про космос» и «космическая одиссея» окажутся рядом, даже если слова разные. • One-hot / multi-hot — для категориальных признаков: жанры, теги, страна.

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

from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity

# Описания фильмов
descriptions = [
    "Космическая одиссея, экипаж исследует далёкие планеты",
    "Детектив расследует убийство в маленьком городе",
    "Астронавты спасают Землю от астероида",
    "Полицейский ищет серийного убийцу в Нью-Йорке",
]

tfidf = TfidfVectorizer()
vectors = tfidf.fit_transform(descriptions)

# Если пользователю нравится фильм 0 (космос),
# ищем ближайшие
sims = cosine_similarity(vectors[0:1], vectors).flatten()
# [1.0, 0.0, 0.32, 0.0] — фильм 2 (тоже про космос) ближе всего

Плюсы и минусы content-based

Плюсы: • Работает для новых айтемов — достаточно описания, не нужны взаимодействия • Прозрачность — можно объяснить: «рекомендуем, потому что вам нравится жанр X» • Не нужны данные других пользователей — работает даже с одним юзером Минусы:Пузырь фильтров — рекомендует только похожее. Любишь боевики → получишь только боевики, никогда не узнаешь про отличную драму • Качество зависит от качества фичей — плохие описания → плохие рекомендации • Не ловит «неожиданные» предпочтения — если 90% фанатов «Властелина Колец» любят «Игру Престолов», content-based этого не увидит (разные теги)

Collaborative Filtering — рекомендации от похожих пользователей

Коллаборативная фильтрация (CF) работает по другому принципу: ей не нужны атрибуты айтемов. Она смотрит только на поведение пользователей — кто что оценил, купил, посмотрел. Если ты и другой пользователь любите одни и те же фильмы — вероятно, вам понравятся и другие фильмы друг друга.

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

Два основных варианта: User-based CF: Находим пользователей с похожей историей взаимодействий. Рекомендуем айтемы, которые понравились «соседям», но которые ты ещё не видел. Item-based CF: Находим айтемы, которые потребляются вместе. «Пользователи, которые купили X, также купили Y» — это именно item-based CF. Amazon популяризировал этот подход.

Метрики сходства — как определить «похожесть»: • Косинусное сходство — угол между векторами оценок. Не зависит от «масштаба» (строгий критик ставит 1-3, лояльный — 3-5, но паттерн одинаковый) • Корреляция Пирсона — косинус после центрирования (вычитаем среднюю оценку). Ловит совпадение *паттернов*, а не абсолютных значений • Jaccard — для бинарных данных (купил/не купил): |A ∩ B| / |A ∪ B|

import numpy as np

# Матрица оценок: строки — пользователи, столбцы — фильмы
# NaN = не смотрел
R = np.array([
    [5, 4, np.nan, 1],   # User 0: любит фильмы 0,1; не любит 3
    [4, 5, 4, np.nan],   # User 1: похож на User 0
    [1, np.nan, 5, 4],   # User 2: совсем другие вкусы
])

# Косинусное сходство User 0 и User 1 (по общим фильмам 0, 1)
u0 = np.array([5, 4])  # фильмы 0, 1
u1 = np.array([4, 5])
cos_sim = np.dot(u0, u1) / (np.linalg.norm(u0) * np.linalg.norm(u1))
print(f"sim(User0, User1) = {cos_sim:.3f}")  # 0.976 — очень похожи!

# User 1 смотрел фильм 2 и поставил 4 → рекомендуем User 0

User-based vs Item-based: что выбрать?

User-based: хорош при малом числе пользователей. Проблема — пользователи меняют вкусы, и «похожий пользователь» сегодня может быть непохожим завтра. Item-based: стабильнее — сходство айтемов не меняется так быстро. Масштабируется лучше (айтемов обычно меньше, чем юзеров). Amazon в 2003 показал, что item-based CF работает лучше в продакшне. На практике: чистый CF (user или item based) в продакшне почти не используется — его заменили матричные разложения и нейросетевые модели. Но понимание CF — фундамент для всего дальнейшего.

Matrix Factorization — латентные факторы за оценками

Классический CF ищет похожих пользователей «в лоб» — по общим оценкам. Но что если двое любят фильмы по совершенно разным причинам? Один ценит режиссуру, другой — спецэффекты. Matrix Factorization (MF) копает глубже: она находит латентные факторы — скрытые характеристики, которые объясняют предпочтения.

Идея: матрица оценок R (users × items) разлагается в произведение двух маленьких матриц — R ≈ P · Q^T. Матрица P (users × k) описывает каждого пользователя через k латентных факторов. Матрица Q (items × k) описывает каждый айтем через те же факторы. Предсказание оценки — скалярное произведение вектора пользователя и вектора айтема.

Аналогия: фильм можно описать невидимыми «осями» — уровень экшена, романтики, сложности сюжета, знаменитости актёров. Пользователь — это вектор предпочтений по тем же осям: «люблю экшен (+0.8), равнодушен к романтике (0.1), ценю сложный сюжет (+0.6)». Оценка = насколько совпали оси фильма и предпочтения пользователя.

p_u — вектор пользователя u (k факторов), q_i — вектор айтема i (k факторов), k — число латентных факторов (обычно 50-200)

Методы обучения:SVD (Singular Value Decomposition) — аналитическое разложение. Проблема: матрица R разреженная (99%+ пропусков), классический SVD требует заполнения. FunkSVD решает это через оптимизацию только на известных оценках. • ALS (Alternating Least Squares) — фиксируем Q, решаем для P; фиксируем P, решаем для Q. Чередуем до сходимости. Легко параллелится — хорошо подходит для Spark/больших данных. • SGD — стохастический градиентный спуск по парам (user, item, rating). Быстро, но сложнее параллелить.

# Matrix Factorization через ALS (implicit library)
import implicit
import scipy.sparse as sp
import numpy as np

# Матрица взаимодействий: users × items (CSR формат)
# Значения — confidence (сколько раз пользователь взаимодействовал)
data = sp.csr_matrix(np.array([
    [5, 3, 0, 1],
    [4, 0, 0, 1],
    [1, 1, 0, 5],
    [0, 0, 5, 4],
]))

model = implicit.als.AlternatingLeastSquares(factors=2, iterations=20)
model.fit(data)

# Латентные вектора пользователей и айтемов
print(model.user_factors.shape)  # (4, 2) — 4 юзера, 2 фактора
print(model.item_factors.shape)  # (4, 2) — 4 айтема, 2 фактора

# Рекомендации для пользователя 0
ids, scores = model.recommend(0, data[0], N=2)
print(f"Рекомендации: items {ids}, scores {scores}")

Гибридные системы — лучшее из двух миров

Content-based не находит «неожиданные» связи. CF не работает для новых айтемов. Каждый подход силён там, где слаб другой — логично объединить их. Гибридные системы комбинируют несколько подходов.

Способы комбинирования: • Взвешенное объединение — score = α·score_CF + (1−α)·score_CB. Просто и часто работает • Switching — для нового пользователя используем content-based, для опытного — CF • Stacking — выходы нескольких моделей подаём в мета-модель (градиентный бустинг), которая выдаёт финальный скор • Feature augmentation — латентные факторы из MF используем как признаки для content-based модели

Netflix Prize (2006-2009) — соревнование, которое изменило рекомендательные системы. Netflix предложил $1 000 000 за улучшение их алгоритма рекомендаций на 10% по RMSE. Победившая команда BellKor показала ключевой инсайт: ни один алгоритм не был лучшим — победил ансамбль из 107 моделей. Это доказало, что гибридизация — не опция, а необходимость. С тех пор в продакшне рекомендательных систем всегда используется комбинация подходов.

Implicit vs Explicit feedback — два типа обратной связи

Explicit feedback — пользователь сам говорит, что ему нравится. Оценки 1-5 звёзд, лайки, отзывы. Данные чистые, но их мало: менее 1% пользователей оставляют оценки.

Implicit feedback — система собирает сама: клики, просмотры, покупки, время на странице, скроллы, добавления в корзину. Данных в 100-1000× больше, но они зашумлённые: • Клик ≠ интерес (кликнул случайно, не понравилось) • Отсутствие клика ≠ отсутствие интереса (просто не видел) • Нет отрицательного сигнала — ты не знаешь, что пользователю *не* понравилось, ты знаешь только что он *не взаимодействовал*

В реальности 99% данных — implicit. Это требует другого подхода к моделированию: вместо предсказания оценки (regression) — предсказание вероятности взаимодействия (ranking/classification). ALS для implicit feedback (Hu et al., 2008) работает с бинарной матрицей preference (0/1) и матрицей confidence (сколько раз взаимодействовал = насколько мы уверены в preference).

import pandas as pd

# Implicit feedback: собираем из логов
events = pd.DataFrame({
    'user_id': [1, 1, 1, 2, 2, 3],
    'item_id': [10, 20, 10, 10, 30, 20],
    'event':   ['view','view','buy','view','view','buy'],
})

# Взвешиваем события: buy > view
weights = {'view': 1.0, 'buy': 3.0}
events['weight'] = events['event'].map(weights)

# Агрегируем: confidence = сумма весов
conf = events.groupby(['user_id','item_id'])['weight'].sum().reset_index()
# user 1, item 10: view + buy = 1.0 + 3.0 = 4.0 (высокая уверенность)
# user 2, item 30: view = 1.0 (низкая уверенность)

Метрики рекомендательных систем

В рекомендациях нельзя просто считать accuracy — важно качество ранжирования. Пользователь видит первые 5-10 позиций, и порядок внутри этих позиций критически важен. Метрики RecSys — это метрики ранжирования.

Precision@K — доля релевантных среди показанных K айтемов. Из 10 рекомендаций 3 оказались релевантны → Precision@10 = 0.3. Отвечает на вопрос: «насколько точны рекомендации?» Recall@K — доля найденных релевантных среди всех релевантных. Из 20 релевантных айтемов система нашла 3 в топ-10 → Recall@10 = 3/20 = 0.15. Отвечает: «какую долю интересного мы нашли?»

NDCG@K (Normalized Discounted Cumulative Gain) — метрика, которая учитывает позицию релевантного айтема. Релевантный айтем на 1-й позиции ценнее, чем на 10-й. Дисконтирование: вклад позиции i делится на log₂(i+1).

rel_i — релевантность айтема на позиции i, IDCG@K — DCG идеального ранжирования (все релевантные наверху). NDCG ∈ [0, 1]

MAP@K (Mean Average Precision) — среднее AP по всем пользователям. AP для одного юзера: считаем Precision@k для каждой позиции k, где встречается релевантный айтем, и усредняем. MAP штрафует за «дырки» — если релевантные айтемы разбросаны по списку вместо верхних позиций.

Hit Rate@K — доля пользователей, для которых хотя бы один релевантный айтем попал в топ-K. Самая простая метрика: «мы вообще попали в цель?» MRR (Mean Reciprocal Rank) — средний обратный ранг первого релевантного айтема. Если первый релевантный на позиции 3 → reciprocal rank = 1/3. MRR отвечает: «как быстро пользователь найдёт нужное?»

import numpy as np

def ndcg_at_k(recommended: list, relevant: set, k: int) -> float:
    """NDCG@K для одного пользователя."""
    dcg = sum(
        1.0 / np.log2(i + 2)  # i+2 потому что i начинается с 0
        for i, item in enumerate(recommended[:k])
        if item in relevant
    )
    # Идеальный DCG: все релевантные наверху
    idcg = sum(1.0 / np.log2(i + 2) for i in range(min(len(relevant), k)))
    return dcg / idcg if idcg > 0 else 0.0

# Пример: рекомендовали [A, B, C, D, E], релевантны {B, D, F}
recs = ['A', 'B', 'C', 'D', 'E']
relevant = {'B', 'D', 'F'}
print(f"NDCG@5 = {ndcg_at_k(recs, relevant, 5):.3f}")
# B на позиции 2, D на позиции 4
# DCG = 1/log2(3) + 1/log2(5) = 0.631 + 0.431 = 1.062
# IDCG = 1/log2(2) + 1/log2(3) = 1.0 + 0.631 = 1.631
# NDCG = 1.062 / 1.631 = 0.651

Какую метрику выбрать?

NDCG — стандарт для ранжирования, учитывает позицию. Используй по умолчанию • Recall@K — если важно «найти всё релевантное» (поиск, музыка) • Hit Rate — если достаточно хотя бы одного попадания (e-commerce) • MRR — если важно, чтобы первый результат был точным (поисковая выдача) • MAP — когда нужно оценить качество на всех позициях, а не только на первой • Бизнес-метрики (CTR, конверсия, revenue, retention) — в конечном счёте оптимизируем их

Холодный старт — главный враг рекомендаций

Новый пользователь: ноль истории — что рекомендовать? Новый айтем: никто не взаимодействовал — кому показать? Это cold start problem — одна из самых важных проблем в RecSys.

Холодный пользователь: • Показать популярное — простой бейзлайн, часто работает • Онбординг-опрос — «какие жанры любишь?» (Netflix, Spotify) • Демографические признаки — возраст, геолокация, устройство • Contextual bandits — быстро выучить предпочтения через explore/exploit

Холодный айтем:Content-based — используем атрибуты (описание, жанр, изображение) • Exploration — специально показываем новые айтемы части аудитории, чтобы собрать сигнал • Transfer learning — если айтем похож на существующие, инициализируем его эмбеддинг средним похожих

Вот почему гибридные системы доминируют: content-based компонент покрывает холодный старт, а CF/MF дают качество для «тёплых» пользователей и айтемов.

🎯 На собеседовании

Junior

Чем content-based отличается от collaborative filtering? CB смотрит на атрибуты айтемов (жанр, описание), CF — на поведение пользователей (кто что оценил). CB работает для новых айтемов, CF находит «неожиданные» рекомендации. • Что такое холодный старт? Нет данных о новом пользователе или новом айтеме. Решения: популярное, онбординг, content-based признаки. • Implicit vs explicit feedback? Explicit — оценки, лайки (мало данных, чистые). Implicit — клики, просмотры (много данных, зашумлённые). В продакшне 99% — implicit.

Middle

Как работает matrix factorization? R ≈ P·Q^T. Каждый юзер и айтем — вектор из k латентных факторов. Предсказание = скалярное произведение. Обучение через ALS или SGD. • NDCG vs MAP — в чём разница? NDCG дисконтирует по log₂(i+1), работает с градуированной релевантностью. MAP работает с бинарной релевантностью, усредняет precision по позициям с релевантными айтемами. • User-based vs item-based CF? Item-based стабильнее (сходство айтемов не меняется), масштабируется лучше. User-based чувствителен к изменению вкусов. • Как решить cold start для нового айтема? Content-based фичи + exploration (показываем часть аудитории). Transfer learning — инициализируем эмбеддинг средним похожих.

Senior

Почему ALS подходит для implicit feedback? Для implicit нет отрицательных примеров. ALS от Hu et al. работает с confidence-weighted loss: c_ui = 1 + α·r_ui, где r_ui — число взаимодействий. Это позволяет обучаться и на «нулях» матрицы. • Как бороться с popularity bias? Популярные айтемы получают больше показов → больше кликов → ещё больше показов (feedback loop). Решения: IPS (inverse propensity scoring), causal debiasing, явный exploration. • Как оценить рекомендации офлайн vs онлайн? Офлайн: split по времени, метрики на held-out. Онлайн: A/B тест с бизнес-метриками. Между ними — разрыв: офлайн-лидер может проиграть в A/B. • Netflix Prize — что показал? Ансамбль 107 моделей. Ни один алгоритм не лучший. В продакшне Netflix даже не внедрил решение победителей целиком — бизнес-метрики перевесили RMSE.

Собираем всё вместе

Рекомендательная система — это функция: (пользователь, каталог) → ранжированный список. Три базовых подхода: content-based (по свойствам айтемов), collaborative filtering (по поведению похожих пользователей), matrix factorization (латентные факторы). В продакшне — всегда гибрид: CF для качества, content-based для холодного старта, бизнес-правила для monetization.

Если запомнить одну вещь: рекомендации — это не «предскажи оценку», а «отранжируй так, чтобы пользователь нашёл нужное». Метрики ранжирования (NDCG, MAP) важнее RMSE. Implicit feedback в 100× больше explicit. И ни одна модель не работает в вакууме — cold start, popularity bias, feedback loops требуют системного подхода.

Дальше на роадмапе: Collaborative Filtering углубит матричные разложения и нейросетевой CF, Two-Tower покажет архитектуру для генерации кандидатов в реальном времени, а Multi-Stage Ranking расскажет, как построить полный production pipeline.