Введение в 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. Показываем и собираем фидбек — замкнутый цикл, система постоянно учится

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
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 0User-based vs Item-based: что выбрать?
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Какую метрику выбрать?
Холодный старт — главный враг рекомендаций
Новый пользователь: ноль истории — что рекомендовать? Новый айтем: никто не взаимодействовал — кому показать? Это cold start problem — одна из самых важных проблем в RecSys.
Холодный пользователь: • Показать популярное — простой бейзлайн, часто работает • Онбординг-опрос — «какие жанры любишь?» (Netflix, Spotify) • Демографические признаки — возраст, геолокация, устройство • Contextual bandits — быстро выучить предпочтения через explore/exploit
Холодный айтем: • Content-based — используем атрибуты (описание, жанр, изображение) • Exploration — специально показываем новые айтемы части аудитории, чтобы собрать сигнал • Transfer learning — если айтем похож на существующие, инициализируем его эмбеддинг средним похожих
Вот почему гибридные системы доминируют: content-based компонент покрывает холодный старт, а CF/MF дают качество для «тёплых» пользователей и айтемов.
🎯 На собеседовании
Junior
Middle
Senior
Собираем всё вместе
Рекомендательная система — это функция: (пользователь, каталог) → ранжированный список. Три базовых подхода: 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.
Материалы
Курс от Google: от основ до продвинутых техник. Отличная стартовая точка.
Лекция Stanford: collaborative filtering, matrix factorization, cold start. Академически строго, но доступно.
Обзор подходов и архитектур RecSys на русском. Хорошо для первого знакомства.
Обзор пересечения RecSys и LLM. Читать после основ — показывает, куда движется область.
Python-библиотека для ALS, BPR и других алгоритмов на implicit feedback. GPU-ускорение из коробки.