Настроить cross-encoder reranking
ТЕХНИЧЕСКОЕ ЗАДАНИЕ: Настроить cross-encoder reranking
1. Цель задачи
Научиться применять cross-encoder (BERT/DeBERTa) для post-hoc реранжирования документов, полученных от быстрого bi-encoder. Цель — улучшить качество retrieval за счёт более точного моделирования семантической близости пары (запрос, документ). В результате топ-100 документов, выдаваемых биэнкодером, должны быть переранжированы до топ-10, что даёт измеримый прирост метрики.
Ключевой результат NDCG@10 на тестовом наборе запросов повышается не менее чем на 30% относительно baseline (top-10 без реранжирования).
2. Исходные данные
Перед началом необходимо иметь:
| Что нужно | Откуда взять |
|---|---|
| Датасет с парами (запрос, документ, релевантность) | MS MARCO Passage Ranking / TREC Deep Learning / собственный размеченный датасет |
| Предобученный bi-encoder (эмбеддинг-модель) | sentence-transformers/all-MiniLM-L6-v2 / DPR / intfloat/e5-base-v2 |
| Индекс для быстрого поиска по эмбеддингам | FAISS / Qdrant / ScaNN |
| Cross-encoder модель | cross-encoder/ms-marco-MiniLM-L-6-v2 / cross-encoder/ms-marco-TinyBERT-L-2 / microsoft/deberta-v3-base |
| Оценочный скрипт | sklearn.metrics.ndcg_score / собственный |
Если нет реального инструмента — симулируем:
- Сгенерировать синтетические данные Взять 500–1000 произвольных текстов из Википедии или новостей. Для каждого текста составить 1–2 запроса (например, заголовками). Разметить релевантность вручную (бинарно: 0/1) или через LLM (GPT-4o).
- Эмулировать bi-encoder Использовать простой TF-IDF (TfidfVectorizer) для получения top-100 кандидатов. Это хуже, чем эмбеддинги, но позволяет сфокусироваться на процессе реранжирования.
- Собрать запросы Если нет настоящих запросов — взять 50 случайных названий статей и использовать их как запросы, релевантные документы — соответствующие статьи.
3. Технологический стек
| Компонент | Инструменты | Назначение |
|---|---|---|
| Язык программирования | Python 3.10+ | Реализация пайплайна |
| Библиотека transformer-моделей | 🤗 Transformers + sentence-transformers | Загрузка и инференс bi-encoder и cross-encoder |
| Векторное индексирование | FAISS (Facebook AI Similarity Search) | Быстрый поиск top-100 по эмбеддингам |
| Оценка качества | sklearn.metrics.ndcg_score, numpy, pandas | Расчёт NDCG@10 |
| Датасет | MS MARCO (или симулированный) | Исходные документы + запросы |
| Среда выполнения | Jupyter Notebook / Google Colab | Интерактивная разработка |
| Дополнительно | tqdm, torch, json, gc | Прогресс-бары, управление памятью |
4. Этапы выполнения
Этап 1: Подготовка данных и bi-encoder поиск (1 час)
Действия
- Загрузить датасет Скачайте MS MARCO passage (collection.tsv) или используйте синтетический датасет.
Пример:import pandas as pd passages = pd.read_csv('collection.tsv', sep='\t', names=['pid', 'text']) - Загрузить bi-encoder модель
from sentence_transformers import SentenceTransformer bi_encoder = SentenceTransformer('all-MiniLM-L6-v2') - Построить эмбеддинги документов
from tqdm import tqdm emb = bi_encoder.encode(passages['text'].tolist(), show_progress_bar=True) - Создать FAISS индекс
import faiss dim = emb.shape[1] index = faiss.IndexFlatIP(dim) index.add(emb) faiss.write_index(index, 'passages.index') - Для каждого тестового запроса найти top-100 по косинусной близости с помощью FAISS.
Сохранить наборquery_emb = bi_encoder.encode(['query text']) D, I = index.search(query_emb, 100)(query_id, retrieved_pids, scores).
Ожидаемый результат этапа
- Биэнкодерные эмбеддинги всех документов (файл
.npyили FAISS index). - Список top-100 документов для каждого запроса в виде CSV или JSON.
Этап 2: Загрузка и настройка cross-encoder (30 мин)
Действия
- Выбрать и загрузить cross-encoder
from sentence_transformers import CrossEncoder model = CrossEncoder('cross-encoder/ms-marco-MiniLM-L-6-v2') - Определить параметры инференса
- max_length = 512 (обрезка длинных текстов)
- batch_size = 32 (для ускорения)
- Проверить работу на одной паре
score = model.predict([('query', 'document text')]) print(score) # float, например 0.95
Ожидаемый результат этапа
Модель загружена, протестирована на одном примере, готов скрипт для пакетного инференса.
Этап 3: Реранжирование top-100 → top-10 (1 час)
Действия
- Для каждого запроса подготовить пары (query, doc_text) для всех 100 кандидатов.
candidates = [(query, passages.loc[pid, 'text']) for pid in retrieved_pids] - Применить cross-encoder для получения релевантностных скоров.
ce_scores = model.predict(candidates, batch_size=32, show_progress_bar=True) - Отсортировать кандидатов по убыванию
ce_scoresи выбрать top-10.reranked = sorted(zip(retrieved_pids, ce_scores), key=lambda x: x[1], reverse=True)[:10] - Зафиксировать результаты в DataFrame (query_id, rank, pid, ce_score).
Ожидаемый результат этапа
Таблица reranked_results.csv с колонками: query_id, rank, pid, ce_score.
Этап 4: Оценка метрик и сравнение с baseline (1 час)
Действия
- Рассчитать NDCG@10 для baseline (top-10 от bi-encoder без реранжирования):
- Для baseline возьмите те же top-100 от bi-encoder, но возьмите первые 10 по исходному bi-encoder score.
- Используйте sklearn.metrics.ndcg_score(ground_truth, scores), где
ground_truth— бинарная релевантность.
- Рассчитать NDCG@10 после cross-encoder.
- Сравнить relative improvement
improvement = (ndcg_ce - ndcg_baseline) / ndcg_baseline * 100 print(f'Improvement: {improvement:.2f}%') - Построить сводную таблицу по всем запросам и визуализировать распределение прироста.
Ожидаемый результат этапа
- Значения NDCG@10 (baseline и cross-encoder).
- Отчёт
evaluation_report.txtс цифрами и гистограммой. - Целевой прирост ≥ 30% — задача считается выполненной.
Этап 5 (опциональный): Оптимизация скорости и качества (30 мин)
Действия
- Попробовать другие cross-encoder модели (например, DeBERTa-v3-base, TinyBERT).
- Включить фильтрацию по порогу — документы с score < 0.5 отбрасывать.
- Уменьшить batch_size для экономии памяти.
- Сравнить время инференса для разных моделей.
Ожидаемый результат этапа
Рекомендация по лучшей модели для production (баланс качества/скорости).
5. Критерии приемки (Definition of Done)
- Код написан в Jupyter Notebook с разделёнными ячейками (по этапам).
- Bi-encoder поиск выдаёт top-100 для не менее 50 запросов.
- Cross-encoder успешно загружен и применим к списку пар.
- Метрика NDCG@10 вычислена как для baseline, так и для реранжированного списка.
- Относительное улучшение NDCG@10 составило не менее 30% (или задокументировано, почему меньше).
- Все промежуточные результаты (эмбеддинги, кандидаты, скоры) сохранены для воспроизводимости.
- Файл
README.mdописывает шаги воспроизведения и результаты.
6. Ожидаемый результат
Основной артефакт
cross_encoder_reranking.ipynb — Jupyter Notebook с полным пайплайном: загрузка, поиск, реранжирование, оценка.
Содержание Notebook
- Импорты и конфигурация (пути, модели)
- Загрузка и предобработка датасета
- Bi-encoder: эмбеддинги + FAISS
- Cross-encoder: реранжирование
- Расчёт метрик и сравнение
- Таблица с результатами для каждого запроса
Дополнительные артефакты (опционально):
reranked_results.parquet— таблица с детальными результатамиevaluation_plot.png— гистограмма распределения NDCG@10best_config.json— параметры моделей и batch_size
7. Возможные сложности и их решение
| Сложность | Решение |
|---|---|
| Датасет слишком большой для памяти при инференсе cross-encoder | Использовать batch_prediction с разбивкой на чанки; включить градиентное накопление (если fine-tune) |
| Cross-encoder работает медленно на CPU | Использовать GPU (Colab); уменьшить max_length до 256; использовать TinyBERT |
| Метрика NDCG не растёт или падает | Проверить корректность подсчёта: ground truth должен содержать только релевантные документы в top-10; попробовать другую модель cross-encoder |
| Отсутствие ground truth для синтетических данных | Придумать строгую разметку (например, exact match заголовка); использовать LLM для асессмента |
| FAISS выдаёт пустые результаты | Проверить размерность эмбеддингов; нормировать эмбеддинги (L2) для использования IndexFlatIP |
8. Бюджет времени (оценка)
| Этап | Время (часы) |
|---|---|
| 1. Подготовка данных и bi-encoder поиск | 1 |
| 2. Загрузка и настройка cross-encoder | 0.5 |
| 3. Реранжирование | 1 |
| 4. Оценка метрик и сравнение | 1 |
| 5. Оптимизация (опционально) | 0.5 |
| Итого | 4 |
Примечание для первого раза Выделите 6 часов с учётом настройки окружения и отладки.
9. Связанные вопросы из базы знаний
| Вопрос | Тема |
|---|---|
| 45 | Как обучить cross-encoder с нуля на небольшом датасете? |
| 47 | Реранжирование на основе нескольких фич (сигналов) |
| 120 | Сравнение bi-encoder и cross-encoder для поиска |
| 230 | Что такое NDCG и как его корректно вычислять? |
| 345 | Быстрое индексирование эмбеддингов с FAISS |
| 412 | Использование DeBERTa для реранжирования |
| 560 | Оптимизация batch inference для transformer-моделей |
| 671 | Аугментация датасета для реранжера |
| 788 | Метрики качества search: Precision, Recall, MAP, MRR |
| 899 | Кэширование скоров cross-encoder в production |
10. Чек-лист самопроверки
- Я загрузил датасет и построил индексы — данные готовы.
- Bi-encoder выдаёт корректные top-100 (проверено на 1 запросе визуально).
- Cross-encoder предсказывает скоры для всех пар (нет NaN или ошибок).
- Я вычислил NDCG@10 для baseline и для реранжированного списка.
- Прирост метрики ≥ 30% (или я явно задокументировал отклонение).
- Код чистый, с комментариями, все артефакты сохранены.