← к модулям

BPR — pairwise ranking

Та же matrix factorization, но обучена ранжировать: ставить фильмы, с которыми ты взаимодействовал, выше случайных. Оптимизируем порядок напрямую — это мостик к learning-to-rank.

    Карта латентных факторов: топ-400 фильмов

    Каждая точка — фильм в пространстве скрытых черт, спроецированный в 2D (PCA), цвет — основной жанр. Как и у MF, кластеры складываются сами из того, кто что смотрел — но здесь векторы выучены ранжированием, а не реконструкцией.

    обучение и проекция факторов…

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

    BPR — это та же matrix factorization (вектор пользователя · вектор фильма = интерес), но обученная по-другому. Вместо того чтобы угадывать число (как ALS), BPR учится ранжировать: для каждого человека ставить фильмы, с которыми он взаимодействовал, выше случайных, с которыми не взаимодействовал.

    Pointwise против pairwise

    Прошлая модель (ALS) — pointwise: она смотрит на одну пару «человек–фильм» и подгоняет под неё значение — у нас это не звёзды, а preference/confidence (помним: implicit-ALS не предсказывает рейтинг). Но нам в рекомендациях не нужно точное значение — нужен правильный порядок списка. BPR заходит с этой стороны.

    Pointwise (поточечно)учим модель предсказывать значение для каждой отдельной пары (в нашем ALS — preference/confidence, а не рейтинг). Порядок получается как побочный эффект.
    Pairwise (попарно)учим модель сравнивать пары айтемов: «для этого человека фильм A должен стоять выше фильма B». Оптимизируем напрямую то, что нам важно, — порядок.
    Почему rating prediction не хватило
    БылоMF / ALS восстанавливали значение (rating/preference).
    ПроблемаВ top-K важен порядок, а не точное значение score — а pointwise-обучение оптимизирует не то.
    ИдеяУчить попарно: позитив должен стоять выше сэмплированного негатива.
    Стало лучшеObjective ближе к самой задаче ранжирования.
    Осталось слабымЧувствителен к negative sampling; «не трогал» ≠ «не нравится»; BPR не оптимизирует NDCG напрямую.
    ДальшеLearning-to-rank и hard negatives; нейро-retrieval — two-tower; feature-rich ranking — в следующих модулях.

    Тройка, на которой учимся

    Берём пользователя и два фильма: один он трогал (позитив), другой — нет (негатив). Хотим, чтобы модель давала позитиву балл выше негатива.

    Позитив / негативпозитив — айтем, с которым у пользователя было взаимодействие (implicit-сигнал «интересно»). Негатив — айтем без взаимодействия. Важно: негатив ≠ «не понравилось», он может быть просто не увиденным.
    Негативное сэмплированиенегативов слишком много (почти весь каталог), поэтому на каждом шаге берём случайный из не-взаимодействованных, а не все сразу. У нас матрица очень разреженная, поэтому случайный невзаимодействованный фильм часто оказывается достаточно простым negative-сэмплом. Но это не настоящий dislike: пользователь мог просто его не видеть.

    Что считаем «позитивом» на MovieLens

    У MovieLens есть оценки (звёзды), а BPR — модель для implicit-фидбека («было/не было взаимодействие»). Мы трактуем рейтинги как implicit-сигнал: позитив = любой оценённый фильм (с любым рейтингом), негатив — случайный неоценённый. Это упрощение, но согласованное: MF (implicit-ALS) и item-CF у нас учатся на той же матрице всех взаимодействий, а порог «понравилось» (rating ≥ 4) — это фильтр на этапе оценки, единый для всех моделей.

    Казалось бы, чище брать позитивом только понравившееся (rating ≥ 4). Но мы это замерили: так BPR теряет половину обучающих пар (≈48k вместо ≈100k) и на нашем маленьком датасете недоучивается — NDCG@10 падает с ~0.032 до ~0.022. Хороший урок: офлайн-интуиция «позитив = понравилось» проигрывает замеру — на скудных данных число пар важнее чистоты сигнала.

    Пример. Пользователь 1 смотрел «Матрицу» (позитив). Случайно берём «Бэмби», которого он не трогал (негатив). BPR подкручивает векторы так, чтобы балл «Матрицы» для него стал выше балла «Бэмби». Повторяем это миллионы раз на разных тройках.

    Заметь: «Бэмби» — слишком лёгкий негатив. Если человек любит sci-fi, а в негатив случайно попал детский мультфильм, модель и так без труда ставит позитив выше — и почти ничему не учится. Тонкому различию между похожими фильмами учат «трудные» негативы (hard negatives) — о них в слабых сторонах ниже.

    Как именно «подкручивает»

    Для тройки считаем разницу баллов xuij=xuixujx_{uij} = x_{ui} - x_{uj}. Если позитив уже выше негатива (xuijx_{uij} большой) — всё хорошо, почти не трогаем. Если перепутаны (разница отрицательная) — делаем сильный шаг, чтобы исправить порядок. «Силу» шага задаёт сигмоида.

    Сигмоида σS-образная функция, сжимающая любое число в диапазон 0…1. В BPR она превращает «насколько перепутан порядок» в вес градиента: перепутанные пары тянут сильнее.
    Псевдокод (mini-batch SGD)
    инициализируем векторы P (люди) и Q (фильмы) случайно
    повторяем N эпох:
        для каждого взаимодействия (u, i):           # i — позитив
            j = случайный фильм, не виденный u        # негатив
            x = P[u]·Q[i] − P[u]·Q[j]                 # разница баллов
            g = σ(−x)                                 # вес: больше, если порядок перепутан
            P[u] += lr · (g·(Q[i] − Q[j]) − λ·P[u])
            Q[i] += lr · (g·P[u]           − λ·Q[i])
            Q[j] += lr · (−g·P[u]          − λ·Q[j])
    
    score(u, i) = P[u]·Q[i]                            # для рекомендаций
    Формула: BPR-OPT и градиент

    Балл — скалярное произведение, как в MF: xui=puqix_{ui} = p_u^\top q_i. Для тройки (u,i,j)(u,i,j) (i — позитив, j — негатив) определяют xuij=xuixujx_{uij} = x_{ui} - x_{uj} и максимизируют:

    BPR-OPT=(u,i,j)lnσ(xuij)    λΘ2,σ(z)=11+ez\text{BPR-OPT} = \sum_{(u,i,j)} \ln \sigma(x_{uij}) \; - \; \lambda\,\lVert \Theta \rVert^2,\qquad \sigma(z) = \frac{1}{1+e^{-z}}

    Это максимизация вероятности правильного порядка пар — по смыслу близко к оптимизации AUC / pairwise-ранжирования, но через гладкую дифференцируемую функцию (а не дискретную AUC напрямую) — с L2-регуляризацией λ\lambda на все векторы Θ\Theta. Градиентный шаг для одной тройки использует вес σ(xuij)\sigma(-x_{uij}):

    lnσ(xuij)θ=σ(xuij)xuijθ\frac{\partial \ln\sigma(x_{uij})}{\partial \theta} = \sigma(-x_{uij})\,\frac{\partial x_{uij}}{\partial \theta}

    где σ(xuij)\sigma(-x_{uij}) близок к 1, когда пара перепутана (большой шаг), и к 0, когда порядок уже верный (шаг почти нулевой).

    Почему это интересно после MF

    Та же модель, другой критерий обучения — и на ранговых метриках BPR обычно обходит ALS-реконструкцию: мы оптимизируем порядок (по сути AUC по всем парам), а не значение оценки. Оговорка: это не прямая оптимизация top-K — NDCG@10 «top-heavy» (важна верхушка), и под него точнее заточены WARP / LambdaRank; BPR — самый простой первый шаг.

    На наших данных BPR по NDCG@10 надёжно обходит MF (ALS); а с popularity идёт примерно вровень — разница в пределах шума, на части запусков даже ниже (плюс помним про popularity bias при пороге «понравилось»). item-CF впереди всех. Это и есть мостик к «learning-to-rank» — так учат ранкеры в больших продовых системах.

    Честная оговорка: датасет маленький, в тесте у каждого спрятан один фильм, а сам BPR обучается на случайных негативах — поэтому метрика шумная и от запуска к запуску немного гуляет. Мы фиксируем seed, так что число воспроизводимо, но не стоит читать «BPR выше MF на 0.008» как закон природы — это «обычно выше на этой задаче».

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

    • Оптимизирует порядок (пары «позитив выше негатива»), а не значение оценки — ближе к ранговым метрикам.
    • Работает с implicit-фидбеком (клики/просмотры), не нужны явные оценки.
    • Лёгкая и быстрая: SGD по тройкам, та же простая модель MF.

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

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

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

    • «Негатив» = не взаимодействовал, а не «не понравилось». Это допущение implicit-фидбека: непросмотренное могло бы зайти.
    • Качество негативов решает многое: равномерные негативы слишком лёгкие; hard-negative и popularity-based сэмплинг учат лучше (но и риск сместить выдачу).
    • Переобучение по эпохам: точность растёт, потом резко падает (мы видели обвал при слишком долгом обучении) — число эпох надо подбирать.
    • Это ранжирование, а не вероятности: баллы BPR нельзя читать как «шанс клика», только сравнивать между собой.

    🧠 Проверь себя: Что напрямую оптимизирует BPR?

    Где встречается в жизни

    Pairwise-обучение на тройках «лучше/хуже» — классика ранжирования: от выдачи товаров и ленты до retrieval-этапа в больших рекомендателях. BPR — самый простой представитель этого семейства, с которого удобно начинать.