Контентные рекомендации
Урок разбирает контентный подход: рекомендации строятся по признакам самих товаров и профилю интересов пользователя, без оглядки на других людей.
Контентные рекомендации описывают каждый товар вектором признаков (жанры, теги, слова описания) и советуют объекты, похожие по содержанию на то, что пользователю уже нравилось.
Идея
В отличие от коллаборативной фильтрации, контентный подход не нуждается в чужих оценках. Он смотрит на содержание: если вы любите фантастические боевики, он найдёт другие фантастические боевики по их признакам. Профиль пользователя строится как агрегат признаков того, что он уже выбирал, и новые товары сравниваются с этим профилем.
Главный плюс — нет проблемы холодного старта по товарам: новый фильм можно рекомендовать сразу, как только заведены его теги. Главный минус — система склонна к переспециализации: рекомендует слишком похожее и не открывает ничего нового.
TF-IDF: вес слов в описании
Когда признаки — это текст (описание, теги), наивный подсчёт слов даёт перекос: частые слова вроде «фильм» встречаются везде и неинформативны. TF-IDF взвешивает слово тем выше, чем чаще оно в данном документе (TF) и чем реже — во всей коллекции (IDF). Так редкие, характерные слова получают больший вес.
import math
from collections import Counter
docs = {
"Матрица": "фантастика боевик киберпанк",
"Бегущий": "фантастика киберпанк нуар",
"Титаник": "драма романтика катастрофа",
"Аватар": "фантастика боевик приключения",
}
tokenized = {n: t.split() for n, t in docs.items()}
N = len(docs)
df = Counter()
for toks in tokenized.values():
for t in set(toks):
df[t] += 1
idf = {t: math.log(N / df[t]) for t in df}
def tfidf_vec(toks):
tf = Counter(toks)
return {t: (tf[t] / len(toks)) * idf[t] for t in tf}
def cosine(a, b):
common = set(a) & set(b)
num = sum(a[t] * b[t] for t in common)
na = math.sqrt(sum(v*v for v in a.values()))
nb = math.sqrt(sum(v*v for v in b.values()))
return num / (na * nb) if na and nb else 0.0
vecs = {n: tfidf_vec(t) for n, t in tokenized.items()}
q = "Матрица"
sims = {n: cosine(vecs[q], vecs[n]) for n in vecs if n != q}
print(f"Похожие на '{q}' по контенту:")
for n in sorted(sims, key=lambda x: -sims[x]):
print(f" {n}: {round(sims[n], 3)}")Вывод:
Похожие на 'Матрица' по контенту: Бегущий: 0.35 Аватар: 0.35 Титаник: 0.0
«Матрица» сблизилась с «Бегущим» (общий «киберпанк») и «Аватаром» (общие «фантастика», «боевик»), а с «Титаником» сходство нулевое — другой набор тегов.
Профиль пользователя и эмбеддинги
Профиль пользователя — это усреднённый (часто взвешенный по оценкам) вектор признаков понравившихся товаров. Рекомендация = товары, ближайшие к этому профилю по косинусу. Вместо TF-IDF современные системы берут плотные эмбеддинги (например, из языковых моделей для описаний или из CNN для изображений). Поиск ближайших по эмбеддингам — это прямой мост к векторным базам данных и ANN-поиску, о которых речь в учебнике «RAG и векторные БД».
Как работает под капотом
Конвейер контентной системы: извлечь признаки товаров → построить векторы (TF-IDF или эмбеддинги) → собрать профиль пользователя как агрегат его истории → найти ближайшие к профилю товары. Для больших каталогов точный перебор заменяют приближённым поиском соседей (ANN), храня векторы в векторной БД.
Частые ошибки
- Не взвешивать слова. Без IDF частые общие слова забивают сигнал; используйте TF-IDF или эмбеддинги.
- Переспециализация. Контент рекомендует слишком похожее; добавляйте разнообразие и смешивайте с CF.
- Сравнивать вектора без нормировки. Берите косинус, а не сырое скалярное произведение, иначе длинные описания «перевесят».
Итоги
- Контентные рекомендации опираются на признаки товаров и профиль пользователя.
- TF-IDF взвешивает слова по их информативности; косинус меряет близость.
- Подход решает холодный старт по товарам, но склонен к переспециализации.
- Эмбеддинги и ANN-поиск связывают контент-рекомендации с векторными БД.