Что такое semantic ranking на основе embeddings (вторая стадия после ANN)?
Краткий тезис
Semantic ranking на основе embeddings — это второй этап двухстадийного поиска в RAG, который уточняет результаты, полученные на первом этапе с помощью ANN (approximate nearest neighbor). На этом этапе используется более мощная (часто cross-encoder) модель для вычисления семантической близости между запросом и каждым из top-k кандидатов, что даёт более точное ранжирование, но требует больше вычислительных ресурсов. Такой подход позволяет существенно повысить качество retrieval, жертвуя скоростью, и часто применяется в production-системах, где точность критична.
1. Термины: ANN, semantic ranking, embeddings, re-ranking
- ANN (Approximate Nearest Neighbor) — алгоритм приближённого поиска ближайших соседей в векторном пространстве. Используется на первом этапе для быстрого извлечения кандидатов (обычно top-100–1000) по косинусной близости или L2-расстоянию. Примеры: FAISS, HNSW, ScaNN.
- Embeddings — векторные представления текста, получаемые с помощью нейросетевых моделей (например, sentence-transformers). На первом этапе обычно используется лёгкая bi-encoder модель (например, all-MiniLM-L6-v2), которая кодирует запрос и документы независимо.
- Semantic ranking (re-ranking) — процесс переранжирования списка кандидатов с помощью более точной модели, часто cross-encoder, которая обрабатывает пару (запрос, документ) совместно и выдаёт оценку релевантности.
- Cross-encoder — модель, которая принимает на вход конкатенацию запроса и документа и выдаёт скалярную оценку (например, от 0 до 1). Примеры:
cross-encoder/ms-marco-MiniLM-L-6-v2, BAAI/bge-reranker-v2-m3. - Bi-encoder — модель, которая кодирует запрос и документ отдельно, а затем сравнивает векторы (косинусное сходство). Быстрее, но менее точна.
2. Зачем нужна вторая стадия после ANN
Первый этап (ANN) решает задачу скорости: из миллионов векторов нужно за миллисекунды найти несколько сотен кандидатов. Однако ANN жертвует точностью:
- Приближённый поиск может пропустить релевантные документы (низкий recall).
- Косинусное сходство bi-encoder не всегда хорошо отражает семантическую релевантность (например, запрос «как приготовить пасту» может найти документ про «пасту для зубов» из-за поверхностного совпадения).
Вторая стадия (semantic ranking) решает проблему точности: она берёт top-k от ANN (например, top-100) и заново ранжирует их с помощью более мощной модели. Это позволяет:
- Поднять релевантные документы наверх.
- Отсеять ложноположительные срабатывания.
- Улучшить метрики MRR, NDCG, Recall@k после re-ranking.
3. Как работает semantic ranking на основе embeddings
Процесс состоит из трёх шагов:
-
Первый этап (ANN):
- Запрос кодируется bi-encoder'ом в вектор.
- Вектор ищется в индексе ANN (например, FAISS).
- Возвращается top-k кандидатов (например, 100).
-
Второй этап (semantic ranking):
- Для каждого кандидата из top-k формируется пара (запрос, документ).
- Cross-encoder обрабатывает каждую пару и выдаёт оценку релевантности (score).
- Кандидаты сортируются по убыванию score.
- Возвращается top-m (например, top-10) для подачи в LLM.
-
Опционально: кэширование:
Пример кода (Python с sentence-transformers):
from sentence_transformers import SentenceTransformer, CrossEncoder
import faiss
import numpy as np
# Bi-encoder для ANN
bi_encoder = SentenceTransformer('all-MiniLM-L6-v2')
# Cross-encoder для re-ranking
cross_encoder = CrossEncoder('cross-encoder/ms-marco-MiniLM-L-6-v2')
# Индекс FAISS (предварительно построен)
index = faiss.read_index('documents.index')
documents = [...] # список текстов документов
def search(query, top_k_ann=100, top_m_rerank=10):
# Этап 1: ANN
query_vec = bi_encoder.encode([query])
distances, indices = index.search(query_vec, top_k_ann)
candidates = [documents[i] for i in indices[0]]
# Этап 2: Semantic ranking
pairs = [[query, doc] for doc in candidates]
scores = cross_encoder.predict(pairs)
ranked = sorted(zip(candidates, scores), key=lambda x: x[1], reverse=True)
return [doc for doc, score in ranked[:top_m_rerank]]
4. Отличие от первого этапа: bi-encoder vs cross-encoder
| Характеристика | Bi-encoder (ANN) | Cross-encoder (semantic ranking) |
|---|---|---|
| Скорость | Очень быстрый (миллионы документов за мс) | Медленный (сотни пар за мс) |
| Точность | Средняя (зависит от модели) | Высокая (учитывает контекст пары) |
| Масштабируемость | Хорошая (векторы предвычислены) | Плохая (нужно обрабатывать каждую пару) |
| Применение | Первый этап, отсев кандидатов | Второй этап, уточнение ранжирования |
| Пример модели | all-MiniLM-L6-v2 | cross-encoder/ms-marco-MiniLM-L-6-v2 |
5. Преимущества двухстадийного подхода
- Повышение точности: cross-encoder лучше улавливает семантические нюансы (например, отрицание, перефразирование).
- Снижение шума: отсеиваются нерелевантные документы, которые могли попасть в top-k ANN из-за случайной близости векторов.
- Гибкость: можно использовать разные модели для разных этапов (лёгкая для скорости, тяжёлая для точности).
- Улучшение пользовательского опыта: LLM получает более релевантный контекст, ответы становятся точнее.
6. Недостатки и вызовы
- Latency: добавление второго этапа увеличивает время ответа (обычно на 10–100 мс в зависимости от количества кандидатов и модели).
- Вычислительные затраты: cross-encoder требует GPU для приемлемой скорости; на CPU может быть слишком медленно.
- Сложность инфраструктуры: нужно управлять двумя моделями, кэшированием, очередями.
- Риск overfitting: если re-ranking модель обучена на узком домене, она может хуже работать на новых данных.
7. Стратегии кэширования для semantic ranking
Чтобы снизить latency, результаты re-ranking можно кэшировать:
- Кэш на уровне запроса: для идентичных запросов возвращаем заранее посчитанный top-m.
- Кэш на уровне пар (query, document): если одна и та же пара встречается часто, можно сохранить её score.
- Инвалидация: кэш нужно сбрасывать при обновлении документов или модели.
Пример с Redis:
import redis
import json
cache = redis.Redis(host='localhost', port=6379, db=0)
def cached_rerank(query, candidates):
key = f"rerank:{hash(query)}"
cached = cache.get(key)
if cached:
return json.loads(cached)
# Выполняем re-ranking
pairs = [[query, doc] for doc in candidates]
scores = cross_encoder.predict(pairs)
ranked = sorted(zip(candidates, scores), key=lambda x: x[1], reverse=True)
result = [doc for doc, score in ranked[:10]]
cache.setex(key, 3600, json.dumps(result)) # TTL 1 час
return result
8. Метрики оценки semantic ranking
Для оценки качества re-ranking используют те же метрики, что и для retrieval, но считают их после второго этапа:
| Метрика | Описание | Целевое значение |
|---|---|---|
| MRR@k | Средний обратный ранг первого релевантного документа | >0.8 |
| NDCG@k | Нормализованный дисконтированный кумулятивный выигрыш (учитывает порядок) | >0.9 |
| Recall@k | Доля релевантных документов в top-k | >0.9 |
| Precision@k | Доля релевантных среди top-k | >0.7 |
Сравнение метрик до и после re-ranking показывает прирост (обычно +5–15% по MRR).
9. Связь с Agentic RAG
В Agentic RAG (системах, где агент сам решает, когда и как выполнять поиск) semantic ranking может быть частью инструмента retrieve_and_rerank. Агент может:
- Вызывать re-ranking только для сложных запросов (где уверенность низкая).
- Использовать разные re-ranking модели в зависимости от домена.
- Комбинировать результаты нескольких источников с помощью weighted re-ranking.
10. Альтернативы semantic ranking на основе embeddings
- LLM-as-a-judge: использовать саму LLM для оценки релевантности (промпт «оцени релевантность документа от 1 до 5»). Медленно, но может быть точнее.
- Hybrid ranking: комбинировать ANN с BM25 (текстовый поиск) и затем re-ranking.
- Learning to rank: обучить модель ранжирования на основе фич (например, с помощью LambdaRank).
Пет-проект для закрепления
Задача: Реализовать двухстадийный retrieval с ANN (FAISS) и semantic ranking (cross-encoder) на датасете вопросов-ответов (например, SQuAD или собственный).
Инструменты: Python, FAISS, sentence-transformers, cross-encoder, Flask (для API), Redis (кэш).
Шаги:
- Загрузить датасет, разбить на чанки (по 200–500 токенов).
- Построить FAISS-индекс с bi-encoder (например,
all-MiniLM-L6-v2). - Реализовать API с эндпоинтом
/search?query=...&top_k=100&top_m=10. - Добавить re-ranking с cross-encoder (
cross-encoder/ms-marco-MiniLM-L-6-v2). - Добавить кэширование результатов re-ranking в Redis.
- Сравнить метрики (MRR@10, Recall@10) до и после re-ranking на тестовом наборе запросов.
Ожидаемый результат: Рабочий сервис, который возвращает top-10 релевантных документов с latency < 200 мс (включая re-ranking). Метрики улучшатся на 10–15% по сравнению с одним ANN.
Связь с другими вопросами
| Вопрос | Тема |
|---|---|
| 379 | Что такое ANN (approximate nearest neighbor) и какие алгоритмы используются? |
| 378 | Какие стратегии chunking'а вы знаете и когда какую применяете? |
| 377 | Как выбрать embedding-модель для RAG? |
| 376 | Что такое hybrid search и как его реализовать? |
| 375 | Какие техники query transformation используются в RAG? |
| 5 | Как вы оцениваете качество retrieval'а в RAG-системе? |
Навигация
- Предыдущий: 379
- Следующий: 381
- Индекс: 00. Индекс разборов