← к модулям

Two-tower — retrieval

Раздельные башни user/item → векторы в общем пространстве, близость = dot-product. Item-векторы предсчитываются → подбор кандидатов масштабируется через ANN. Обучение — in-batch negatives с logQ-коррекцией.

    🖱 Онлайн: кликай — выдача меняется

    Кликай фильмы, которые тебе «зашли». Справа — что порекомендует two-tower прямо сейчас, без всякого пользователя в системе. Модель не переобучается: твои клики сворачиваются (fold-in) в один сессионный вектор — центроид их эмбеддингов — и мы переранжируем замороженные item-векторы. Так в проде делают мгновенную сессионную персонализацию.

    Каталог — кликай (0 выбрано)
    Рекомендации сессии

    Кликни пару фильмов слева — здесь появится выдача.

    Карта item-эмбеддингов (item-башня): топ-400 фильмов

    Это те самые замороженные векторы фильмов, которые в проде кладут в ANN-индекс. Точки спроецированы в 2D (PCA), цвет — основной жанр.

    обучение и проекция эмбеддингов…

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

    Two-tower — это модель для retrieval: две раздельные «башни» (нейросети) переводят пользователя и фильм в векторы одного пространства, а близость — это их скалярное произведение. Хитрость в том, что векторы фильмов можно посчитать заранее, и тогда подбор кандидатов — это один matrix-vector (а на больших каталогах — ANN-поиск).

    Две башни

    Башня (tower)отдельная сеть, которая превращает вход в эмбеддинг. User-башня кодирует пользователя, item-башня — фильм. Башни раздельные: не «видят» друг друга до самого скалярного произведения.

    Почему раздельно — это и есть ключ к скорости. Эмбеддинги всех фильмов считаются один раз офлайн и кладутся в индекс. На запрос мы считаем только вектор пользователя и ищем ближайшие item-векторы.

    Отсюда важное следствие про context-фичи (время, устройство, запрос): item-векторы предрассчитаны офлайн, поэтому request-time контекст обычно кладут в user/query-башню или в ранкер — но не в item-башню, иначе её нельзя было бы закешировать.

    ANN (приближённый поиск соседей)структура (FAISS/HNSW/ScaNN), которая находит ближайшие по dot-product векторы за доли миллисекунды, не перебирая весь каталог. Именно поэтому retrieval делают dot-product-моделью — она «дружит» с ANN. Как такой индекс устроен изнутри и что он стоит в recall — модуль B3: свой IVF-индекс на этих же эмбеддингах.
    Почему понадобился two-tower retrieval
    БылоMF давала эмбеддинги, item-CF — item-item связи.
    ПроблемаНужен retrieval по миллионам айтемов, с user/item/context-фичами и быстрым ANN-поиском.
    ИдеяОтдельно кодировать пользователя и айтем в одно эмбеддинг-пространство (dot-product дружит с ANN).
    Стало лучшеМасштабируемый retrieval, feature-aware эмбеддинги, поиск ближайших с низкой latency через ANN.
    Осталось слабымRetrieval ceiling (чего нет в кандидатах — не вернёт ranker), hard negative mining, дрейф эмбеддингов, некалиброванный score.
    ДальшеANN-инфраструктура и ranker сверху — многостадийная воронка.

    Как обучается: in-batch negatives

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

    In-batch negativesв батче из B пар матрица скоров B×B: на диагонали — позитивы (свой фильм), вне диагонали — негативы (чужие фильмы батча). Softmax по строке учит позитив быть выше остальных.

    Подвох и его починка: logQ-коррекция

    У in-batch negatives есть коварный баг. Популярные фильмы чаще попадают в батч, а значит чаще оказываются «негативом» для чужих пользователей — и модель учится их подавлять. В итоге она задвигает хиты и тащит наверх шум.

    Пример. У нас это видно вживую: без коррекции two-tower дал NDCG@10 ≈ 0.001 при дикой новизне (рекомендовал редкое) — фактически сломан. С logQ-коррекцией0.028, в одном ряду с MF/popularity. Разница в одной строчке кода.
    logQ-коррекция (sampling-bias correction)из каждого скора вычитаем logP(item)\log P(\text{item}) — лог-частоту фильма как кандидата. Так поправляем перекос: популярные айтемы перестают несправедливо штрафоваться за частое появление в негативах (Yi et al. 2019).
    Псевдокод (in-batch negatives + logQ)
    # офлайн-обучение
    для каждого батча пар (u, i):
        U = user_tower(u)              # (B, d)
        V = item_tower(i)              # (B, d)
        S = U @ Vᵀ − logP(i)           # (B, B), logQ-коррекция по столбцам
        loss = softmax_CE(S, диагональ = позитив)
    
    # офлайн один раз
    item_emb = item_tower(все фильмы)  # кладём в ANN-индекс
    
    # на запрос
    u_vec = user_tower(пользователь)
    кандидаты = ANN.top_k(item_emb, u_vec)
    Формулы: башни, скор, in-batch softmax с logQ

    Каждая башня — MLP над эмбеддингом id:

    u=fU(user),v=fI(item),s(u,i)=uvu = f_U(\text{user}),\qquad v = f_I(\text{item}),\qquad s(u,i) = u^\top v

    В батче из BB пар — softmax-кросс-энтропия, где правильный класс для строки bb это её диагональ, с logQ-коррекцией по кандидатам:

    L=1Bb=1Blogexp ⁣(sbblogP(ib))j=1Bexp ⁣(sbjlogP(ij))\mathcal{L} = -\frac{1}{B}\sum_{b=1}^{B} \log \frac{\exp\!\big(s_{bb} - \log P(i_b)\big)}{\sum_{j=1}^{B} \exp\!\big(s_{bj} - \log P(i_j)\big)}

    Вычитание logP(ij)\log P(i_j) и есть поправка на то, что популярные айтемы чаще попадают в батч как негативы.

    Где это в общей картине

    Two-tower — это первый этап двухстадийной схемы: дёшево достать сотни кандидатов из миллионов (retrieval), а потом отдельная тяжёлая модель их переранжирует (ranking). На наших данных по точности он в одном ряду с baseline — но его ценность не в NDCG на 9k фильмов, а в том, что он масштабируется на каталог в миллионы через ANN. (Двухстадийность подробно — в следующих фазах.)

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

    • Масштабируется: item-эмбеддинги предсчитываются, retrieval = ANN-поиск за миллисекунды.
    • In-batch negatives дают много негативов почти бесплатно.
    • Легко добавить признаки (контент, контекст) в башни — основа продовых ретриверов.

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

    • Без logQ-коррекции in-batch negatives ломают модель (popularity bias) — мы это видели.
    • Раздельные башни не моделируют тонкие user-item взаимодействия (это работа ranking-этапа).
    • Холодный старт и in-batch false negatives (свой айтем как чужой негатив).

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

    • In-batch popularity bias: без logQ-коррекции популярные айтемы подавляются — точность падает почти до нуля (видели: 0.001 → 0.028).
    • False negatives в батче: айтем, который реально нравится пользователю, может оказаться чужим «негативом» в том же батче.
    • Два этапа: two-tower — это retrieval, а не финальное ранжирование. Сравнивать его по NDCG с ranking-моделями некорректно.
    • Холодный старт: новому id неоткуда взять эмбеддинг (решается признаками в башне).

    🧠 Проверь себя: Зачем в two-tower нужна logQ-коррекция?

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

    Two-tower — рабочая лошадка retrieval-этапа в YouTube, рекламе, маркетплейсах: миллионы кандидатов, ANN-поиск, in-batch negatives с коррекцией. Дальше отобранных кандидатов переранжирует более тяжёлая модель — об этом в фазе про двухстадийную архитектуру.