Создать benchmark для retrieval
ТЕХНИЧЕСКОЕ ЗАДАНИЕ: Создать benchmark для retrieval
1. Цель задачи
Спроектировать и реализовать эталонный набор данных (benchmark) для оценки качества retrieval-системы в предметной области (например, техническая документация или научные статьи). Необходимо подготовить 500 запросов (query) с эталонными документами (gold documents) и запустить baseline-модель (BM25) для подтверждения метрики MRR@10 > 0.85. Benchmark должен быть пригоден для автоматического прогона и сравнения разных retrieval-методов.
Ключевой результат файл benchmark.parquet с колонками query_id, query_text, gold_doc_ids (список ID релевантных документов) и скрипт оценки, который воспроизводит MRR@10 на baseline.
2. Исходные данные
| Что нужно | Откуда взять |
|---|---|
| Корпус документов (минимум 10 000 шт.) | Открытый датасет: wiki_corpus из библиотеки datasets (subset wikitext-2-raw-v1) или предварительно собранная коллекция технических статей (например, c4 с фильтрацией по домену en.wikipedia.org). |
| Генерация запросов | Использовать LLM (GPT-4o mini или локальную модель типа mistral-7b) для синтеза запросов на основе случайных документов. |
| Эталонные документы | Для каждого запроса взять 1-3 исходных документа, из которых сгенерирован запрос, и добавить до 5 документов, семантически близких (находятся в одном кластере эмбеддингов). |
Если нет реального инструмента — симулируем:
- Скачайте датасет
wikitextчерезdatasets.load_dataset("wikitext", "wikitext-2-raw-v1"). - Выберите 10 000 статей (текст в
text), очистите от спецсимволов, нормализуйте пробелы. - Разбейте каждую статью на чанки по 150-200 токенов (через
nltk.tokenize). Получится ~1000 документов (можно увеличить повторным чанкингом). - Для генерации запросов используйте OpenAI API (или заглушку: из каждого документа берите первые 20 токенов как «золотой запрос» — этого достаточно для проверки метрик, но в реальной задаче лучше синтезировать).
3. Технологический стек
| Компонент | Инструменты | Назначение |
|---|---|---|
| Язык | Python 3.10+ | Основной язык разработки |
| Работа с данными | pandas, numpy, datasets | Загрузка, обработка, хранение benchmark |
| NLP | nltk, transformers, sentence-transformers | Токенизация, эмбеддинги для кластеризации |
| Retrieval baseline | rank_bm25 (BM25 Okapi) | Базовая модель для верификации метрики |
| Метрики | ranx или собственный расчёт MRR@10 | Оценка качества retrieval |
| Генерация запросов | OpenAI API (gpt-4o-mini) или локальная модель (mistral-7b через llama.cpp) | Синтез запросов на основе документов |
| Версионирование | git, dvc (опционально) | Версии данных и кода |
| Визуализация | matplotlib, seaborn | Графики распределения длин запросов / документов |
4. Этапы выполнения
Этап 1: Подготовка корпуса документов (2 часа)
Действия
- Загрузите датасет
wikitext-2-raw-v1с помощьюdatasets.load_dataset.from datasets import load_dataset dataset = load_dataset("wikitext", "wikitext-2-raw-v1", split="train") - Отфильтруйте строки
textдлиной менее 50 символов (очистка). - Разбейте каждую строку на чанки по 150-200 токенов (используйте
nltk.word_tokenize). Сохраните метаданные:doc_id,chunk_index,text. - Объедините чанки в финальные документы (каждый чанк = отдельный документ). Получите
Nдокументов (минимально 10 000, но для экономии времени можно 5 000). - Сохраните корпус в формате Parquet:
corpus.parquetсо столбцамиdoc_id,text.
Ожидаемый результат этапа corpus.parquet с 5 000+ документов, размером ~20 MB.
Этап 2: Генерация 500 запросов с gold документами (4 часа)
Действия
- Из корпуса случайно выберите 500 документов (или 500 групп по 2-3 документа).
- Для каждого выбранного документа (группы) сформируйте запрос с помощью LLM. Пример промпта:
"Based on the following text, generate a natural language query that a user would ask to retrieve this exact text. Output only the query." Text: <...>
- Используйте API OpenAI:
openai.ChatCompletion.create(...)с модельюgpt-4o-mini,temperature=0.3,max_tokens=50. - Для экономии токенов можно сгенерировать запросы батчами по 20 штук (конкатенация).
- Для каждого запроса определите gold документы:
- Основной: документ, из которого сгенерирован запрос.
- Дополнительные: 2-5 документов, найденных по эмбеддинговой близости (используйте
sentence-transformers/all-MiniLM-L6-v2, k-means кластеризация сk=100; для каждого запроса выберите документы из того же кластера).
- Проверьте, что для каждого запроса есть хотя бы 1 gold документ. При необходимости повторите генерацию.
- Сформируйте таблицу
benchmark.parquet:df = pd.DataFrame({ "query_id": range(1, 501), "query_text": queries, "gold_doc_ids": gold_ids # список строковых ID }) df.to_parquet("benchmark.parquet")
Ожидаемый результат этапа benchmark.parquet с 500 запросами и списками gold документов.
Этап 3: Baseline — BM25 и расчёт MRR@10 (2 часа)
Действия
-
Установите
rank_bm25иnltk. -
Токенизируйте корпус документов (все тексты) через
nltk.word_tokenize. -
Постройте BM25 индекс:
from rank_bm25 import BM25Okapi corpus_tokenized = [nltk.word_tokenize(doc) for doc in corpus['text']] bm25 = BM25Okapi(corpus_tokenized) -
Для каждого запроса из benchmark получите топ-10 документов по BM25:
query_tokenized = nltk.word_tokenize(query) scores = bm25.get_scores(query_tokenized) top10 = np.argsort(scores)[-10:][::-1] # индексы документов -
Рассчитайте MRR@10:
mrr = 0.0 for i in range(len(queries)): gold_set = set(gold_doc_ids[i]) for rank, doc_id in enumerate(top10_doc_ids[i], start=1): if doc_id in gold_set: mrr += 1.0 / rank break mrr /= len(queries) -
Выведите MRR@10. Если значение > 0.85 — цель достигнута. Если нет — проверьте качество генерации запросов (возможно, gold документы слишком специфичны).
Ожидаемый результат этапа значение MRR@10 > 0.85 и скрипт evaluate_bm25.py, который воспроизводит расчёт.
Этап 4: Документирование и упаковка (1 час)
Действия
- Создайте в корне репозитория файл
README.mdс описанием benchmark: формат данных, способ генерации, метрики. - Напишите скрипт
run_benchmark.sh, который последовательно запускает: загрузку корпуса, генерацию (или загрузку готового benchmark), оценку BM25. - Добавьте
requirements.txtс зависимостями:pandas,datasets,rank_bm25,nltk,openai,sentence-transformers. - Убедитесь, что все этапы воспроизводимы (зафиксируйте случайные seed
random.seed(42)).
Ожидаемый результат этапа README.md, requirements.txt, run_benchmark.sh.
Этап 5: Валидация на альтернативной модели (опционально, 1 час)
Действия
- Возьмите простую Dense Retrieval модель (например,
all-MiniLM-L6-v2черезsentence-transformers). - Для каждого запроса вычислите эмбеддинг и получите косинусную близость ко всем документам.
- Рассчитайте MRR@10.
- Сравните с BM25. Ожидается, что dense модель покажет MRR@10 > 0.9 (из-за кластеризации).
Ожидаемый результат этапа Сравнительная таблица метрик (BM25 vs Dense).
5. Критерии приемки (Definition of Done)
- Собран корпус из минимум 5 000 документов (загружен и обработан).
- Сгенерировано ровно 500 уникальных запросов, каждый содержит список gold документов (1-5 шт).
- Все данные сохранены в Parquet с чёткой схемой (query_id, query_text, gold_doc_ids; doc_id, text).
- Baseline BM25 воспроизводимо даёт MRR@10 > 0.85 (зафиксирован seed).
- Скрипт
evaluate_bm25.pyпринимает пути к корпусу и benchmark и выводит MRR@10. - Документация (README) описывает формат benchmark, способ генерации и пример использования.
- Репозиторий содержит
requirements.txtиrun_benchmark.shдля полного воспроизведения.
6. Ожидаемый результат
Основной артефакт каталог benchmark/ с файлами:
corpus.parquet— корпус документов.benchmark.parquet— 500 запросов с gold документами.evaluate_bm25.py— скрипт расчёта MRR@10.run_benchmark.sh— shell-скрипт полного пайплайна.README.md— документация.requirements.txt.
Дополнительные результаты (опционально)
- Сравнительная таблица MRR@10 для BM25 и Dense (в
results/). - Графики распределения длины запросов и количества gold документов.
7. Возможные сложности и их решение
| Сложность | Решение |
|---|---|
| LLM генерирует запросы, не соответствующие исходному документу (халлюцинации) | Использовать промпт с инструкцией «Только запрос, без дополнительного текста»; добавить пост-проверку: запрос не должен содержать прямых цитат длиннее 10 слов. |
| Gold документы в benchmark могут не находиться BM25 (например, из-за разной лексики) | Уменьшить количество золотых документов до 1 (самого исходного), либо генерировать запросы с включением ключевых слов из документа. |
| Не хватает документов в корпусе для 500 запросов | Использовать дополнительный датасет (например, squad контексты) или генерировать запросы из одного документа с разными формулировками. |
| Высокая стоимость API OpenAI | Использовать локальную модель (например, mistral-7b через llama-cpp-python); или сгенерировать синтетические запросы путём извлечения первых 10-20 слов документа. |
| BM25 не даёт MRR@10 > 0.85 | Проверить, не слишком ли широкая формулировка запросов; увеличить число gold документов до 3-5 (BM25 лучше находит поверхностные совпадения). |
8. Бюджет времени (оценка)
| Этап | Время |
|---|---|
| Этап 1: Подготовка корпуса | 2 часа |
| Этап 2: Генерация 500 запросов | 4 часа |
| Этап 3: Baseline BM25 + MRR | 2 часа |
| Этап 4: Документирование | 1 час |
| Этап 5: Валидация (опционально) | 1 час |
| Итого | 10 часов |
Примечание Для первого выполнения (с настройкой окружения, отладкой) закладывайте дополнительно 2-3 часа.
9. Связанные вопросы из базы знаний
| Вопрос | Тема |
|---|---|
| 12 | Как выбрать метрику для задачи retrieval? |
| 47 | Принципы построения benchmark-датасетов |
| 103 | Разница между BM25 и Dense Retrieval |
| 201 | Работа с библиотекой datasets (Hugging Face) |
| 275 | Расчёт MRR и MAP в ранжировании |
| 310 | Оптимизация индексации для больших корпусов |
| 412 | Генерация синтетических данных с помощью LLM |
| 589 | Проверка воспроизводимости экспериментов |
| 703 | Форматы хранения данных: Parquet vs JSON |
| 888 | A/B тестирование retrieval-моделей |
10. Чек-лист самопроверки
- Я загрузил и подготовил корпус документов (проверил количество и размер чанков).
- Для каждого из 500 запросов есть хотя бы один gold документ, уникальный ID запроса.
- Я зафиксировал seed
42во всех местах, где есть случайность (выбор документов, split). - Скрипт
evaluate_bm25.pyзапускается без ошибок и выдаёт MRR@10 > 0.85. - README содержит инструкцию по запуску с нуля (включая установку зависимостей).