Настроить 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 / собственный

Если нет реального инструмента — симулируем:

  1. Сгенерировать синтетические данные Взять 500–1000 произвольных текстов из Википедии или новостей. Для каждого текста составить 1–2 запроса (например, заголовками). Разметить релевантность вручную (бинарно: 0/1) или через LLM (GPT-4o).
  2. Эмулировать bi-encoder Использовать простой TF-IDF (TfidfVectorizer) для получения top-100 кандидатов. Это хуже, чем эмбеддинги, но позволяет сфокусироваться на процессе реранжирования.
  3. Собрать запросы Если нет настоящих запросов — взять 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 час)

Действия

  1. Загрузить датасет Скачайте MS MARCO passage (collection.tsv) или используйте синтетический датасет.
    Пример:
    import pandas as pd
    passages = pd.read_csv('collection.tsv', sep='\t', names=['pid', 'text'])
    
  2. Загрузить bi-encoder модель
    from sentence_transformers import SentenceTransformer
    bi_encoder = SentenceTransformer('all-MiniLM-L6-v2')
    
  3. Построить эмбеддинги документов
    from tqdm import tqdm
    emb = bi_encoder.encode(passages['text'].tolist(), show_progress_bar=True)
    
  4. Создать FAISS индекс
    import faiss
    dim = emb.shape[1]
    index = faiss.IndexFlatIP(dim)
    index.add(emb)
    faiss.write_index(index, 'passages.index')
    
  5. Для каждого тестового запроса найти 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 мин)

Действия

  1. Выбрать и загрузить cross-encoder
    from sentence_transformers import CrossEncoder
    model = CrossEncoder('cross-encoder/ms-marco-MiniLM-L-6-v2')
    
  2. Определить параметры инференса
    • max_length = 512 (обрезка длинных текстов)
    • batch_size = 32 (для ускорения)
  3. Проверить работу на одной паре
    score = model.predict([('query', 'document text')])
    print(score)  # float, например 0.95
    

Ожидаемый результат этапа
Модель загружена, протестирована на одном примере, готов скрипт для пакетного инференса.


Этап 3: Реранжирование top-100 → top-10 (1 час)

Действия

  1. Для каждого запроса подготовить пары (query, doc_text) для всех 100 кандидатов.
    candidates = [(query, passages.loc[pid, 'text']) for pid in retrieved_pids]
    
  2. Применить cross-encoder для получения релевантностных скоров.
    ce_scores = model.predict(candidates, batch_size=32, show_progress_bar=True)
    
  3. Отсортировать кандидатов по убыванию ce_scores и выбрать top-10.
    reranked = sorted(zip(retrieved_pids, ce_scores), key=lambda x: x[1], reverse=True)[:10]
    
  4. Зафиксировать результаты в DataFrame (query_id, rank, pid, ce_score).

Ожидаемый результат этапа
Таблица reranked_results.csv с колонками: query_id, rank, pid, ce_score.


Этап 4: Оценка метрик и сравнение с baseline (1 час)

Действия

  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 — бинарная релевантность.
  2. Рассчитать NDCG@10 после cross-encoder.
  3. Сравнить relative improvement
    improvement = (ndcg_ce - ndcg_baseline) / ndcg_baseline * 100
    print(f'Improvement: {improvement:.2f}%')
    
  4. Построить сводную таблицу по всем запросам и визуализировать распределение прироста.

Ожидаемый результат этапа

  • Значения NDCG@10 (baseline и cross-encoder).
  • Отчёт evaluation_report.txt с цифрами и гистограммой.
  • Целевой прирост ≥ 30% — задача считается выполненной.

Этап 5 (опциональный): Оптимизация скорости и качества (30 мин)

Действия

  1. Попробовать другие cross-encoder модели (например, DeBERTa-v3-base, TinyBERT).
  2. Включить фильтрацию по порогу — документы с score < 0.5 отбрасывать.
  3. Уменьшить batch_size для экономии памяти.
  4. Сравнить время инференса для разных моделей.

Ожидаемый результат этапа
Рекомендация по лучшей модели для 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@10
  • best_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-encoder0.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% (или я явно задокументировал отклонение).
  • Код чистый, с комментариями, все артефакты сохранены.