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) — из каждого скора вычитаем log⁡P(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)=u⊤vu = f_U(\text{user}),\qquad v = f_I(\text{item}),\qquad s(u,i) = u^\top v

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

L=−1B∑b=1Blog⁡exp⁡ ⁣(sbb−log⁡P(ib))∑j=1Bexp⁡ ⁣(sbj−log⁡P(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)}

Вычитание log⁡P(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 с коррекцией. Дальше отобранных кандидатов переранжирует более тяжёлая модель — об этом в фазе про двухстадийную архитектуру.

Как это работает как retrieval: recall@C

Загружаем данные

Что дальше

До финальной сборки не хватает ещё 11 модулей по этому пути.

Порядок здесь — рекомендация из карты курса, ничего не блокируется. Отметка «прочитано» хранится только в этом браузере.

Источники