← к модулям

Item-based collaborative filtering

Фильмы похожи, если их оценивали одни и те же люди (косинус по столбцам матрицы взаимодействий). Никаких жанров — только поведение.

    score — внутренний ранжировочный балл (сумма похожестей соседей, взвешенная твоими оценками). Это не рейтинг 1–5, не вероятность и не «качество» фильма; сравнивать его можно только внутри одного пользователя и одной модели.

    Жанры на карточках показаны только для интерпретации в интерфейсе — модель их не использует (item-CF смотрит лишь на поведение).

    Матрица сходства: топ-15 популярных фильмов

    Ярче клетка — сильнее «совместная» похожесть (часто оцениваются вместе). Диагональ приглушена (сходство фильма с самим собой = 1). Наведи на клетку — покажет точное значение.

    загрузка матрицы…

    Осторожно с интуицией. У популярных фильмов сходство часто завышено: их смотрит широкая аудитория, поэтому у них много общих зрителей — но это не всегда настоящая вкусовая близость, а popularity bias. Именно поэтому нужны shrinkage (мы уже применяем, λ=20), centering и осторожная интерпретация cosine.

    Теория простым языком

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

    Чем это отличается от content-based

    Content-based смотрел внутрь фильма (жанры, теги). Здесь мы про содержание фильма не знаем ничего — смотрим только на то, кто что оценил. Этот подход так и называется:

    Коллаборативная фильтрациярекомендации, построенные на «коллективном» поведении пользователей. Слово «коллаборативная» = «совместная»: мы используем оценки многих людей, чтобы помочь одному.

    Бывает двух видов: смотреть на похожих пользователей («такие же люди, как ты, любят…») или на похожие айтемы («к этому фильму обычно идёт вот этот»). Мы реализовали второй — item-based. Его выбирают не потому, что айтемов «меньше» (у нас как раз наоборот: 610 пользователей и 9724 фильма), а потому что свойства айтема стабильнее вкусов человека: похожесть двух фильмов меняется медленно, поэтому её можно предрассчитать offline и периодически обновлять, тогда как профиль пользователя плывёт постоянно.

    Как CF вырос из корреляции
    БылоContent-based смотрел только на признаки айтема.
    ПроблемаПризнаки бывают бедными, а похожесть по содержанию ≠ похожесть по вкусу.
    ИдеяПохожесть выводим из поведения: сначала «похожие пользователи → усреднить их оценки», потом «похожие айтемы» — они стабильнее и предрассчитываются offline.
    Стало лучшеКоллективная мудрость: персонализация без метаданных, ловит неочевидные связи между айтемами.
    Осталось слабымРазреженность, холодный старт, popularity bias, шумные similarity на редких совместных оценках.
    ДальшеMF / ALS — учить скрытые факторы вместо ручной similarity.

    Когда два фильма считаются похожими

    Вернёмся к матрице «пользователи × фильмы» с оценками. Каждый фильм — это столбец: список того, кто и как его оценил. Похожесть двух фильмов — это похожесть их столбцов.

    Косинусное сходствочисло, показывающее, насколько «в одну сторону» смотрят два списка чисел. Здесь списки — это оценки фильмов разными людьми. Высокий косинус = фильмы оценивали одни и те же люди и похоже.
    Пример. «Историю игрушек» и «Парк Юрского периода» жанрово роднит мало. Но их любят одни и те же зрители 90-х → по поведению они «похожи», и CF это улавливает, хотя content-based — нет.

    Как получаются рекомендации

    1. Берём фильмы, которые ты хорошо оценил (rating ≥ 4) — только лайки; низкие оценки в профиль не идут, чтобы нелюбимое не тянуло рекомендации вверх.
    2. Для каждого находим самые похожие на него (по столбцам матрицы).
    3. Складываем эти «голоса»: фильм, похожий сразу на несколько твоих любимых, получает высокий балл. Верхние по баллу и показываем (убрав уже виденные).

    Если у пользователя нет оценок ≥ 4, откатываемся на всю его историю (иначе рекомендовать было бы не от чего). На MovieLens этот positive-only профиль ещё и повышает точность против «взять все оценки как вес».

    kNN (k ближайших соседей)способ рекомендовать через «соседей»: для айтема берут k самых похожих на него и опираются на них. «Сосед» здесь — это похожий фильм, а не человек рядом.
    Псевдокод
    # офлайн: считаем похожести между всеми парами фильмов один раз
    нормируем каждый столбец-фильм матрицы оценок
    sim[i][j] = cosine(столбец i, столбец j)
    
    recommend(пользователь u, K):
        score = {}
        для каждого фильма j, который u оценил ≥ 4 (лайки):
            для каждого соседа i из топ-похожих на j:
                score[i] += sim[i][j] * r[u][j]
        # если лайков нет — берём всю историю u (fallback)
        убрать из score уже виденные u фильмы
        вернуть K фильмов с наибольшим score
    Формула: похожесть и итоговый балл

    Похожесть фильмов ii и jj — косинус их столбцов оценок rir_{\cdot i} и rjr_{\cdot j}:

    sim(i,j)=uruirujurui2uruj2\mathrm{sim}(i,j) = \frac{\sum_{u} r_{ui}\, r_{uj}}{\sqrt{\sum_u r_{ui}^2}\,\sqrt{\sum_u r_{uj}^2}}

    Балл фильма ii для пользователя uu — сумма похожестей на то, что uu лайкнул (оценка ≥ 4), взвешенная этими оценками:

    score(u,i)=j:ruj4sim(i,j)ruj\mathrm{score}(u,i) = \sum_{j\,:\, r_{uj}\,\ge\,4} \mathrm{sim}(i,j)\, r_{uj}

    Так фильмы с низкой оценкой не дают вклада вовсе. Альтернативы: взять все оценки как вес (тогда 2★ даёт малый положительный вклад — методологически спорно) или центрировать rujrˉur_{uj}-\bar r_u (ниже среднего → минус). На наших данных positive-only оказался и чище, и точнее.

    На практике сумму берут не по всем jj, а только по k ближайшим соседям фильма ii — отсюда «kNN».

    Главные сложности

    Разреженностьматрица почти пустая: каждый видел крохотную долю каталога, поэтому у двух фильмов часто слишком мало общих зрителей, чтобы надёжно судить о похожести.
    Холодный стартновому пользователю нечего рекомендовать (он ничего не оценил), а новый фильм не на что «подвесить» (его ещё никто не смотрел вместе с другими). Поведения пока нет — значит, и коллаборативная фильтрация бессильна.

    Несмотря на это, на наших данных item-CF в этом протоколе обыгрывает popularity по ranking-метрикам (hit_rate@10 / recall@10 / ndcg@10). Это и есть выигрыш персонализации: рекомендации зависят от того, что смотрел именно ты.

    Сильные стороны

    • Реальная персонализация: учитывает твою историю.
    • Не нужны описания айтемов — достаточно поведения (но его надо корректно логировать: логи грязные, смещённые, без impressions — см. B1).
    • Похожести можно предрассчитать offline и периодически обновлять → рекомендации быстрые.

    Слабые стороны

    • Холодный старт: бесполезна для новых пользователей и айтемов.
    • Разреженность данных бьёт по качеству.
    • Память и вычисления растут с числом айтемов (похожести между всеми парами).

    ⚠️ Что может пойти не так

    • Разреженность: у двух фильмов может быть всего пара общих зрителей — косинус по такой выборке случаен. Помогает усадка (shrinkage): чем меньше общих оценок n_ij, тем сильнее похожесть тянут к нулю множителем n_ij/(n_ij+λ). Здесь это уже применено (λ=20): на офлайн-метриках ndcg@10 вырос с 0.037 до 0.041, hit_rate@10 — с 0.077 до 0.088.
    • Косинус по сырым оценкам смещён: щедрый зритель, который всем ставит 5, раздувает похожести. Часто оценки сначала центрируют (вычитают среднее пользователя).
    • Холодный старт: новый фильм не с чем сравнить, новому пользователю — нечего складывать.
    • Популярные фильмы лезут в соседи ко всему подряд (их многие смотрели), поэтому без поправок item-CF тоже подвержен popularity bias.

    🧠 Проверь себя: Почему на практике берут именно item-based, а не user-based вариант?