中文翻译暂不可用,显示俄语原文。
Реализовать 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 или написать вручную |
Если нет реального инструмента (поисковых систем) — симулируем:
- Создаём 30 запросов (query_id от 1 до 30).
- Для каждого запроса генерируем 3 списка ранжирования (ранги от 1 до 10, doc_id от 1 до 1000). В каждом списке 10 документов. Часть релевантных документов подмешиваем в каждый список с разными рангами (имитируем разные системы).
- Создаём ground truth: для каждого query_id список релевантных doc_id (1–3 документа).
- Для каждого запроса сохраняем списки в формате: {"query_id": qid, "system_A": [(doc_id, rank), ...], "system_B": [(doc_id, rank), ...], "system_C": [(doc_id, rank), ...]}
- Сохраняем в файл 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 минут)
Действия
- Создать директорию
data/в корне проекта. - Написать скрипт
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). Сохраняется отдельно.
- Вывести статистику: количество запросов, среднее количество релевантных документов на запрос, средний ранг релевантных в каждом списке.
- Сохранить ранги в data/synthetic_rankings.json (структура: список словарей, каждый с ключами
query_id,system_A,system_B,system_C). Каждый список — список кортежей (doc_id, rank). - Сохранить ground truth в data/ground_truth.json (формат:
{"query_id": [doc_id1, doc_id2]}).
Ожидаемый результат этапа Сгенерированные файлы в data/, воспроизводимость (seed random).
Этап 2: Реализация RRF (40–60 минут)
Действия
- Создать модуль
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)). Выберите один и задокументируйте.
- Суммирует по системам, возвращает отсортированный список.
- Написать тестовый запуск на синтетических данных:
- Загрузить ранги и ground truth.
- Для каждого запроса применить RRF и weighted sum.
- Вывести первые 5 результатов для одного запроса для отладки.
Ожидаемый результат этапа Работающие функции rrf_rank и weighted_sum_rank, проверенные на синтетических данных.
Этап 3: Расчёт метрик (30–40 минут)
Действия
- Установить (или написать самим) функции:
mean_reciprocal_rank(predictions: dict, ground_truth: dict, k: int = 10) -> floatpredictions— словарь:{query_id: doc_id, ...]}(список документов в порядке ранжирования, топ-k).- Для каждого запроса находит ранг первого релевантного документа (если есть), иначе 0. MRR = среднее по 1/rank.
recall_at_k(predictions, ground_truth, k: int = 10) -> float- Для каждого запроса считает долю релевантных документов, попавших в топ-k, усредняет.
- Написать скрипт evaluate.py, который:
- Загружает ранги и ground truth.
- Для каждого запроса:
- Получает RRF-ранжирование (берём топ-10).
- Получает weighted sum (топ-10).
- Получает "лучшую" отдельную систему (можно выбрать system_A, system_B, system_C — отдельно расчитать их метрики).
- Вычисляет MRR@10 и Recall@10 для RRF, weighted sum и каждой системы отдельно.
- Вывести таблицу сравнения.
Ожидаемый результат этапа Таблица метрик, где RRF показывает MRR выше, чем weighted sum и каждая отдельная система (на синтетических данных должно быть так, если генерация корректна).
Этап 4: Анализ и визуализация (30–45 минут)
Действия
- Подготовить график (matplotlib или plotly):
- Гистограмма с MRR@10 для каждой системы: system_A, system_B, system_C, weighted_sum, RRF.
- Аналогично для Recall@10.
- Написать анализ: почему RRF работает лучше (устойчивость к выбросам, нелинейное взвешивание рангов).
- Сравнить с weighted sum при разных весах (например, [0.5, 0.3, 0.2] против равных) — показать, что RRF всё равно не хуже (или лучше).
- Сохранить графики в
output/(создать папкуoutput). Формат PNG.
Ожидаемый результат этапа Графики и текстовый анализ в модуле или отдельном документе.
Этап 5: Документирование и проверка (20–30 минут)
Действия
- Написать docstring для всех публичных функций.
- Создать
README.mdс описанием назначения, как запустить, примерами вывода. - Проверить, что все скрипты запускаются без ошибок из корня проекта.
- Запустить полный пайплайн:
generate_synthetic_data.py->python -c "from rrf_fusion import ...; evaluate.py"(или один скриптmain.py, объединяющий всё). - Зафиксировать полученные метрики в 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: Реализация RRF | 50 минут |
| Этап 3: Расчёт метрик | 35 минут |
| Этап 4: Анализ и визуализация | 35 минут |
| Этап 5: Документирование | 25 минут |
| Итого | 3 часа |
Примечание Для первого раза можно выделить 4 часа с учётом debug. Рекомендуется использовать Jupyter для интерактивной отладки.
9. Связанные вопросы из базы знаний
| Вопрос | Тема |
|---|---|
| 5 | Ранжирование и метрики оценки поиска |
| 12 | Виды fusion: RRF, weighted sum, borda count |
| 23 | MRR (Mean Reciprocal Rank) — расчёт и применение |
| 41 | Recall@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, в котором описал запуск и таблицу метрик.