Нейросетевые подходы
~12 мин

Two-Tower и Retrieval

Двухбашенные модели, ANN-индексы (FAISS, ScaNN), candidate retrieval.

Two-Tower — как искать среди миллионов за миллисекунды

Загрузка интерактивного виджета...

У тебя 100 миллионов айтемов. Для каждого пользователя нужно найти топ-1000 кандидатов за <50мс. Прогнать нейросеть 100М раз? Невозможно. Two-Tower решает это: user и item кодируются независимо, поиск — через приближённый поиск ближайших соседей.

Two-Tower архитектура: User Tower и Item Tower
Две башни кодируют пользователя и айтем в общее пространство. Близкие вектора = хорошая рекомендация

Как работает

User Tower берёт признаки пользователя (история, демография, контекст) и кодирует в вектор. Item Tower — признаки айтема (категория, описание, популярность) в другой вектор. Совместимость = скалярное произведение или косинусное сходство.

  • Обучение: пары (user, pos_item) + негативные примеры → контрастивный лосс
  • Инференс: все item-эмбеддинги считаем заранее и кладём в индекс
  • Онлайн: считаем только user-эмбеддинг → ANN-поиск по индексу → топ-K кандидатов

ANN-индексы: FAISS, ScaNN, HNSW

ANN (Approximate Nearest Neighbors) — приближённый поиск ближайших соседей. Точный поиск по 100М векторам — O(n). ANN — почти O(1) за счёт индексации. Потеря точности ~1-5%, но скорость — миллисекунды.

  • FAISS (Meta) — стандарт индустрии. IVF + PQ для больших объёмов
  • ScaNN (Google) — оптимизирован для скалярного произведения
  • HNSW — граф, хорош для высокой точности. Hnswlib, Qdrant, Milvus
import faiss
dim = 64  # размерность эмбеддингов
index = faiss.IndexFlatIP(dim)        # точный поиск
index.add(item_embeddings)            # добавляем все айтемы
D, I = index.search(user_emb, k=100)  # топ-100 ближайших

На собесе

Почему Two-Tower, а не одна сеть? Потому что item-эмбеддинги можно посчитать офлайн и положить в индекс. Если башни зависимые (cross-attention между user и item) — придётся считать для каждой пары онлайн, а это 100М прогонов сети.

Негативные примеры — ключ к качеству

Без негативов модель не умеет различать: все айтемы одинаково «хорошие». Откуда брать негативы? Случайные айтемы из каталога (in-batch negatives — самый простой: другие позитивы в батче становятся негативами), hard negatives (айтемы из топа, которые не кликнули — учат модель различать похожие).

Проблемы и решения

  • Sampling bias: популярные айтемы чаще попадают в негативы → коррекция через log-Q
  • Обновление индекса: новые айтемы нужно быстро добавлять → инкрементальное обновление
  • Ограниченная выразительность: dot-product не ловит сложные паттерны → дальше ранжируем тяжёлой моделью
  • Коллапс эмбеддингов: все вектора сходятся в одну точку → temperature в лосс-функции + нормализация

🎯 Суть для собеса

• Two-tower vs single model? (two-tower: user и item эмбеддинги считаются независимо → ANN retrieval за ms. Single: точнее, но O(n) — только для ранжирования) • Как обучать two-tower? (in-batch negatives, hard negatives mining, contrastive loss) • Зачем FAISS/ScaNN? (ANN поиск по миллионам эмбеддингов за <10ms вместо перебора) • На практике: two-tower для candidate generation, потом тяжёлый ранкер на top-1000