中文翻译暂不可用,显示俄语原文。

Реализовать RRF (Reciprocal Rank Fusion)

ТЕХНИЧЕСКОЕ ЗАДАНИЕ: Реализовать RRF (Reciprocal Rank Fusion)

1. Цель задачи

Научиться объединять несколько ранжированных списков документов из разных источников (векторный поиск, BM25, семантический поиск) с помощью алгоритма Reciprocal Rank Fusion (RRF). Реализовать модуль fusion-ранжирования, который принимает на вход несколько списков (рангов) и возвращает единый ранжированный результат с метриками качества (MRR, Recall@k). Сравнить RRF с взвешенной суммой (weighted sum) и показать, что RRF даёт более высокий MRR на тестовых запросах.

Ключевой результат Рабочий Python-модуль rrf_fusion.py, который запускается на синтетических или реальных данных и выводит метрики, демонстрирующие превосходство RRF над weighted sum.


2. Исходные данные

Что нужноОткуда взять
Несколько ранжированных списков (рангов) для набора запросовСгенерировать синтетически (см. ниже) или взять из реального поискового API (Elasticsearch, Qdrant)
Набор запросов и эталонные релевантные документы (ground truth)Синтетический датасет: 20–50 запросов, у каждого 1–5 релевантных doc_id
Базовый код для weighted sum fusionНаписать самому (2–3 строки)
Метрики: MRR, Recall@kРеализация из библиотеки ranx или написать вручную

Если нет реального инструмента (поисковых систем) — симулируем:

  1. Создаём 30 запросов (query_id от 1 до 30).
  2. Для каждого запроса генерируем 3 списка ранжирования (ранги от 1 до 10, doc_id от 1 до 1000). В каждом списке 10 документов. Часть релевантных документов подмешиваем в каждый список с разными рангами (имитируем разные системы).
  3. Создаём ground truth: для каждого query_id список релевантных doc_id (1–3 документа).
  4. Для каждого запроса сохраняем списки в формате: {"query_id": qid, "system_A": [(doc_id, rank), ...], "system_B": [(doc_id, rank), ...], "system_C": [(doc_id, rank), ...]}
  5. Сохраняем в файл data/synthetic_rankings.json (или pickle).

3. Технологический стек

КомпонентИнструментыНазначение
ЯзыкPython 3.10+Реализация логики
ДанныеJSON / PickleХранение синтетических рангов и ground truth
Метрикиranx (опционально) или ручная реализацияMRR, Recall@k
СравнениеMatplotlib / pandasВизуализация метрик (опционально)
ОкружениеJupyter Notebook / IDEРазработка и тестирование

4. Этапы выполнения

Этап 1: Генерация синтетического датасета (30–40 минут)

Действия

  1. Создать директорию data/ в корне проекта.
  2. Написать скрипт generate_synthetic_data.py, который:
    • Генерирует 30 запросов (query_id от 1 до 30).
    • Для каждого запроса создаёт 3 системы: system_A (векторный поиск), system_B (BM25), system_C (гибридный).
    • Каждая система возвращает 10 документов (doc_id от 1 до 1000) с уникальными рангами (1–10). Для реалистичности — документы между системами могут пересекаться (но необязательно).
    • Подмешивает в каждый список 1–2 релевантных документа из ground truth, назначая им случайный ранг от 1 до 10 (имитация того, что система может не идеально ранжировать).
    • Ground truth: для каждого query_id от 1 до 3 релевантных doc_id (выбираются случайно из диапазона 1–1000). Сохраняется отдельно.
    • Вывести статистику: количество запросов, среднее количество релевантных документов на запрос, средний ранг релевантных в каждом списке.
  3. Сохранить ранги в data/synthetic_rankings.json (структура: список словарей, каждый с ключами query_id, system_A, system_B, system_C). Каждый список — список кортежей (doc_id, rank).
  4. Сохранить ground truth в data/ground_truth.json (формат: {"query_id": [doc_id1, doc_id2]}).

Ожидаемый результат этапа Сгенерированные файлы в data/, воспроизводимость (seed random).


Этап 2: Реализация RRF (40–60 минут)

Действия

  1. Создать модуль rrf_fusion.py с функциями:
    • rrf_rank(system_rankings: list[list[tuple[int, int]]], k: int = 60) -> list[tuple[int, float]]
      • Принимает список списков (каждый внутренний — ранги одной системы). Формат [(doc_id, rank), ...].
      • Для каждого документа накапливает RRF-скор: score += 1 / (k + rank). (k — константа, обычно 60).
      • Возвращает отсортированный по убыванию скор список [(doc_id, score), ...].
    • weighted_sum_rank(system_rankings: list[list[tuple[int, int, weights: list[float] = None) -> list[tuple[int, float]]
      • Если weights не заданы, использует равные веса (1/N).
      • Преобразует ранг в скор: score_w = weight * (1 / rank) (или другой вариант: score_w = weight * (N - rank + 1)). Выберите один и задокументируйте.
      • Суммирует по системам, возвращает отсортированный список.
  2. Написать тестовый запуск на синтетических данных:
    • Загрузить ранги и ground truth.
    • Для каждого запроса применить RRF и weighted sum.
    • Вывести первые 5 результатов для одного запроса для отладки.

Ожидаемый результат этапа Работающие функции rrf_rank и weighted_sum_rank, проверенные на синтетических данных.


Этап 3: Расчёт метрик (30–40 минут)

Действия

  1. Установить (или написать самим) функции:
    • mean_reciprocal_rank(predictions: dict, ground_truth: dict, k: int = 10) -> float
      • predictions — словарь: {query_id: doc_id, ...]} (список документов в порядке ранжирования, топ-k).
      • Для каждого запроса находит ранг первого релевантного документа (если есть), иначе 0. MRR = среднее по 1/rank.
    • recall_at_k(predictions, ground_truth, k: int = 10) -> float
      • Для каждого запроса считает долю релевантных документов, попавших в топ-k, усредняет.
  2. Написать скрипт evaluate.py, который:
    • Загружает ранги и ground truth.
    • Для каждого запроса:
      • Получает RRF-ранжирование (берём топ-10).
      • Получает weighted sum (топ-10).
      • Получает "лучшую" отдельную систему (можно выбрать system_A, system_B, system_C — отдельно расчитать их метрики).
    • Вычисляет MRR@10 и Recall@10 для RRF, weighted sum и каждой системы отдельно.
  3. Вывести таблицу сравнения.

Ожидаемый результат этапа Таблица метрик, где RRF показывает MRR выше, чем weighted sum и каждая отдельная система (на синтетических данных должно быть так, если генерация корректна).


Этап 4: Анализ и визуализация (30–45 минут)

Действия

  1. Подготовить график (matplotlib или plotly):
    • Гистограмма с MRR@10 для каждой системы: system_A, system_B, system_C, weighted_sum, RRF.
    • Аналогично для Recall@10.
  2. Написать анализ: почему RRF работает лучше (устойчивость к выбросам, нелинейное взвешивание рангов).
  3. Сравнить с weighted sum при разных весах (например, [0.5, 0.3, 0.2] против равных) — показать, что RRF всё равно не хуже (или лучше).
  4. Сохранить графики в output/ (создать папку output). Формат PNG.

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


Этап 5: Документирование и проверка (20–30 минут)

Действия

  1. Написать docstring для всех публичных функций.
  2. Создать README.md с описанием назначения, как запустить, примерами вывода.
  3. Проверить, что все скрипты запускаются без ошибок из корня проекта.
  4. Запустить полный пайплайн: generate_synthetic_data.py -> python -c "from rrf_fusion import ...; evaluate.py" (или один скрипт main.py, объединяющий всё).
  5. Зафиксировать полученные метрики в README.

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


5. Критерии приемки (Definition of Done)

  • Сгенерирован синтетический датасет (30 запросов, 3 системы, ground truth).
  • Реализована функция rrf_rank с константой k=60.
  • Реализована функция weighted_sum_rank с равными весами.
  • Метрики MRR@10 и Recall@10 вычисляются для RRF, weighted sum и каждой отдельной системы.
  • RRF показывает MRR@10 выше, чем weighted sum на синтетическом тесте (разница > 0.05).
  • Построены два графика (MRR, Recall) и сохранены в output/.
  • Написан README.md с инструкцией по запуску и таблицей метрик.
  • Код проходит python -m flake8 (или линтер) без ошибок (можно с допущениями на docstring).
  • Все этапы воспроизводятся при запуске python main.py (единый скрипт).

6. Ожидаемый результат

  • Основной артефакт rrf_fusion.py с функциями rrf_rank, weighted_sum_rank, а также evaluate.py или main.py, объединяющий генерацию, вычисление метрик и визуализацию.
  • Содержимое
    • Файл data/synthetic_rankings.json и data/ground_truth.json.
    • Папка output/ с графиками mrr_comparison.png и recall_comparison.png.
    • README.md с таблицей метрик и инструкцией.
  • Опционально Jupyter-ноутбук с пошаговым выполнением и пояснениями.

7. Возможные сложности и их решение

СложностьРешение
Синтетические данные не показывают преимущество RRFУвеличить количество запросов (до 50), добавить больше шума (нерелевантные документы с высокими рангами в одной из систем), уменьшить вес одной системы. Также можно наоборот сделать две системы «шумными», а одну «хорошей» — RRF сгладит.
Несоответствие формата данных (ранги не от 1)В RRF важен ранг, а не скор. Если данные приходят как скоры — преобразовать в ранг через сортировку. В синтетике использовать явные ранги.
Пересечение doc_id в разных системахRRF аккумулирует; это нормально. Убедиться, что не возникает дубликатов в итоговом списке (RRF суммирует скоры).
Высокое время вычислений при большом количестве запросовОптимизация: использовать numpy для массового подсчёта скоров. На 30 запросах не критично.
Модуль ranx не установлен или сложенЗаменить на ручную реализацию метрик (10 строк кода).

8. Бюджет времени (оценка)

ЭтапВремя
Этап 1: Генерация датасета35 минут
Этап 2: Реализация RRF50 минут
Этап 3: Расчёт метрик35 минут
Этап 4: Анализ и визуализация35 минут
Этап 5: Документирование25 минут
Итого3 часа

Примечание Для первого раза можно выделить 4 часа с учётом debug. Рекомендуется использовать Jupyter для интерактивной отладки.


9. Связанные вопросы из базы знаний

ВопросТема
5Ранжирование и метрики оценки поиска
12Виды fusion: RRF, weighted sum, borda count
23MRR (Mean Reciprocal Rank) — расчёт и применение
41Recall@k и Precision@k в информационном поиске
67Константа k в RRF: влияние и выбор
89Сравнение RRF и CombSUM
102Генерация синтетических данных для IR-экспериментов
120Библиотека ranx для ранжирования
155Обработка отсутствия документа в списке (ранг = ∞)
203Эксперименты на равных весах vs оптимизированных весах

10. Чек-лист самопроверки

  • Я сгенерировал синтетические данные, которые содержат ground truth и ранги трёх систем.
  • Я реализовал RRF с константой k=60, проверил на одном запросе: скор первого документа = 1/(60+1) ≈ 0.0164.
  • Я реализовал weighted sum с равными весами, убедился, что сумма весов = 1.
  • Я вычислил MRR@10 для RRF и weighted sum, сравнил — RRF выше.
  • Я построил графики и сохранил их в output/.
  • Я написал README, в котором описал запуск и таблицу метрик.