English translation is not available yet. Showing Russian content.
Реализовать learning-to-rank с LambdaMART
ТЕХНИЧЕСКОЕ ЗАДАНИЕ: Реализовать learning-to-rank с LambdaMART
1. Цель задачи
Разработать и обучить модель Learning-to-Rank (LTR) на основе LambdaMART, используя сигналы: BM25, косинусная близость эмбеддингов, свежесть документа (recency) и авторитетность (authority). Добиться прироста NDCG@10 не менее чем на 20% относительно гибридного ранжирования (линейная комбинация BM25 и векторной схожести). В результате вы получите готовый пайплайн ранжирования и понимание принципов LTR в информационном поиске.
Ключевой результат Обученная модель LambdaMART с оценкой NDCG@10 на тестовом наборе, превосходящая baseline на 20% и более, а также код для её инференса.
2. Исходные данные
| Что нужно | Откуда взять |
|---|---|
| Датасет запросов и документов (тексты, метки релевантности) | Открытые датасеты: MS MARCO (подмножество), TREC Robust, или синтетический датасет |
| База документов с текстом, датой публикации, метрикой авторитетности | Сгенерировать скриптом или взять из того же датасета |
| BM25 скоринг для пар (запрос, документ) | rank_bm25 (Python) или whoosh |
| Эмбеддинги документов и запросов | Sentence-transformers (all-MiniLM-L6-v2) |
| Реализация гибридного ранжирования (baseline) | Линейная комбинация BM25 + cosine similarity (веса 0.5, 0.5) |
| LightGBM (поддерживает LambdaMART) | lightgbm pip-пакет |
Если нет реального датасета — симулируем:
- Создать 500–1000 синтетических запросов, каждый связан с 10–50 документами.
- Для каждого документа сгенерировать текст (например, случайные предложения с ключевыми словами), дату (случайную в диапазоне 2020–2025), показатель authority (float от 0 до 1).
- Назначить релевантность (0/1/2/3) на основе комбинации: если BM25 высокий И эмбеддинг близкий → 3, иначе по убыванию.
- Разбить на train (70%), validation (15%), test (15%).
- Сохранить в формате CSV: query_id, doc_id, relevance, bm25_score, cosine_sim, recency_norm, authority.
3. Технологический стек
| Компонент | Инструменты | Назначение |
|---|---|---|
| Язык программирования | Python 3.10+ | Основной язык |
| Обработка текста | rank_bm25, sentence-transformers | BM25 скоринг, эмбеддинги |
| ML фреймворк | lightgbm 4.x | Обучение LambdaMART |
| Метрики | sklearn.metrics.ndcg_score, scipy.stats | Оценка NDCG |
| Данные | pandas, numpy, datasets (опционально) | Загрузка, преобразование |
| Валидация | group (для группировки по запросам) в LightGBM | Обучение с группировкой |
| Визуализация | matplotlib, seaborn | Графики обучения, сравнение метрик |
4. Этапы выполнения
Этап 1: Подготовка данных и извлечение фич (4–6 часов)
Действия
- Загрузить/сгенерировать датасет
- Использовать готовый датасет (например, MS MARCO passages) или синтетический.
- Если синтетический: написать скрипт
generate_synthetic_dataset.py, который создаёт случайные запросы, документы и метки.
- Для каждой пары (query, doc) вычислить фичи:
- BM25 score токенизация через rank_bm25.BM25Okapi, вычислить score для всех документов по запросу.
- Cosine similarity эмбеддинги запроса и документа через SentenceTransformer('all-MiniLM-L6-v2'), затем
cosine_similarity. - Recency (нормализованная):
(date - min_date) / (max_date - min_date), гдеdate— дата документа. - Authority уже есть в датасете, нормализовать в [0,1] (если нет, сгенерировать случайно).
- Собрать все фичи в DataFrame
features = ['bm25', 'cosine_sim', 'recency_norm', 'authority'] df = pd.DataFrame({ 'qid': query_ids, 'docid': doc_ids, 'relevance': relevances, 'bm25': bm25_scores, 'cosine_sim': cosine_sims, 'recency_norm': recency_values, 'authority': authority_values }) - Разделить на train/validation/test по запросам (чтобы запросы не пересекались).
- Сохранить группы для каждого набора создать массив group = [количество документов на запрос.
Ожидаемый результат этапа Вики/pandas DataFrame|DataFrame с фичами и метками, разбитый на 3 набора (train.csv, val.csv, test.csv) + файлы group_train.npy и т.д.
Этап 2: Реализация baseline (гибридное ранжирование) (1–2 часа)
Действия
- Определить гибридную формулу score = w_bm25 * bm25 + w_cos * cosine_sim.
- Оптимизировать веса (опционально) с помощью grid search на валидации по NDCG@10 (например, веса от 0.1 до 0.9 с шагом 0.1).
- Оценить baseline NDCG@10 на тестовом наборе.
- Зафиксировать результат
baseline_ndcg = 0.XX.
Ожидаемый результат этапа Числовое значение NDCG@10 для гибридного ранжирования и найденные оптимальные веса (если искали).
Этап 3: Обучение LambdaMART (3–4 часа)
Действия
- Подготовить данные для LightGBM
X_train— матрица фич (4 столбца),y_train— релевантности (int, не бинарные).group_train— размеры каждого запроса.
- Настроить параметры
params = { 'objective': 'lambdarank', 'metric': 'ndcg', 'ndcg_eval_at': [10], 'boosting_type': 'gbdt', 'num_leaves': 31, 'learning_rate': 0.05, 'min_data_in_leaf': 50, 'verbose': -1 } - Обучить модель с ранней остановкой на валидации:
model = lgb.train( params, lgb.Dataset(X_train, y_train, group=group_train), valid_sets=[lgb.Dataset(X_val, y_val, group=group_val)], num_boost_round=500, callbacks=[lgb.early_stopping(50), lgb.log_evaluation(10)] ) - Сохранить модель model.save_model('lambdamart_model.txt').
Ожидаемый результат этапа Обученная модель (lambdamart_model.txt), лог обучения с ранней остановкой.
Этап 4: Оценка модели и сравнение с baseline (2–3 часа)
Действия
- Загрузить модель и предсказать scores на тестовом наборе:
y_pred = model.predict(X_test) - Рассчитать NDCG@10 для каждого запроса и усреднить:
from sklearn.metrics import ndcg_score ndcg_lambda = [] for q in unique_queries: mask = test_df['qid'] == q y_true = test_df.loc[mask, 'relevance'].values.reshape(1, -1) y_score = y_pred[mask].reshape(1, -1) ndcg_lambda.append(ndcg_score(y_true, y_score, k=10)) avg_ndcg_lambda = np.mean(ndcg_lambda) - Сравнить с baseline если
avg_ndcg_lambda >= 1.2 * baseline_ndcg— задача выполнена. - Построить график сравнения NDCG@10 по запросам (или cumulative gain).
Ожидаемый результат этапа Таблица с метриками, вывод о достижении +20%, визуализация.
Этап 5: Инференс и интеграция (2–3 часа)
Действия
- Написать функцию
ranker(query, candidate_docs):- Для каждого кандидата вычислить те же 4 фичи (предварительно закэшировав эмбеддинги и BM25-индексы).
- Сформировать матрицу фич и вызвать
model.predict(). - Вернуть отсортированный список документов.
- Протестировать на нескольких ручных запросах, убедиться, что ранжирование осмысленно.
- Добавить unit-тесты на корректность вычисления фич и совпадение с обученными значениями.
- Задокументировать формат входных данных, используемые модели (хранить в
models/).
Ожидаемый результат этапа Рабочий инференс-скрипт rank.py с примером использования.
5. Критерии приемки (Definition of Done)
- NDCG@10 обученной LambdaMART на тестовом наборе превышает baseline не менее чем на 20%.
- Код воспроизводим: все шаги в
README.mdс пошаговой инструкцией. - Данные (синтетические или реальные) приложены в
data/или инструкция по их получению. - Baseline чётко определён (формула, вес, NDCG).
- Модель сохранена и может быть загружена для инференса без переобучения.
- Написаны unit-тесты для функций вычисления фич (BM25, cosine, recency, authority).
- Оценка NDCG проведена с разбивкой по запросам (а не глобально на всех парах).
- В отчёте (или в комментариях Jupyter) указаны значения NDCG для baseline, модели, процент прироста.
- Код использует
lightgbmсobjective='lambdarank'и корректными группами.
6. Ожидаемый результат
Основной результат Архив или репозиторий, содержащий:
data/— CSV-файлы с фичами и метками (train, val, test) + файлы групп.models/lambdamart_model.txt— сериализованная модель LightGBM.notebooks/exploration.ipynb— Jupyter ноутбук с EDA, обучением и метриками.src/rank.py— скрипт инференса с функциейrank_query(query, candidates).src/features.py— функции для вычисления BM25, cosine, recency, authority.tests/test_features.py— unit-тесты.README.md— описание, требования, запуск, результаты.
Дополнительно График сравнения NDCG@10 по запросам (bar plot) и таблица с best/worst запросами.
7. Возможные сложности и их решение
| Сложность | Решение |
|---|---|
| Дисбаланс релевантных/нерелевантных документов | Использовать lambdarank — он устойчив к дисбалансу; можно взвешивать метрику через ndcg_eval_at. |
| Переобучение на небольшом датасете | Уменьшить num_leaves, увеличить min_data_in_leaf, использовать раннюю остановку. |
| Нормализация фич | LightGBM не требует нормализации, но для интерпретируемости можно стандартизировать (StandardScaler). |
| Разные длины запросов (разное количество документов) | Использовать группы (group), фичи считаются попарно — это корректно. |
| Время вычисления BM25 и эмбеддингов | Кэшировать результаты в файлы (pickle) после первого расчёта. |
| Падение NDCG после инференса | Убедиться, что фичи на инференсе считаются так же, как и на обучении (одинаковые токенизаторы, модели). |
8. Бюджет времени (оценка)
| Этап | Время |
|---|---|
| Этап 1: Подготовка данных и извлечение фич | 5 часов |
| Этап 2: Реализация baseline | 1.5 часа |
| Этап 3: Обучение LambdaMART | 3 часа |
| Этап 4: Оценка и сравнение | 2.5 часа |
| Этап 5: Инференс и интеграция | 2 часа |
| Итого | 14 часов |
Примечание Для первого выполнения заложите дополнительно 2–3 часа на отладку и изучение документации LightGBM.
9. Связанные вопросы из базы знаний
| Вопрос | Тема |
|---|---|
| 45 | Реализация LambdaMART (текущее) |
| 42 | Метрики ранжирования: NDCG, MAP |
| 44 | Learning-to-Rank: обзор подходов |
| 10 | Градиентный бустинг (GBDT) |
| 38 | BM25: принцип и реализация |
| 33 | Косинусная близость эмбеддингов |
| 71 | Оценка качества информационного поиска |
| 88 | Feature engineering для поиска |
| 103 | LightGBM: настройка параметров |
| 27 | Pairwise loss в ранжировании |
10. Чек-лист самопроверки
- Я корректно разбил данные на train/val/test по запросам (без пересечения).
- Я убедился, что фичи для одного запроса не перепутаны с другим (использую
group). - Я сравнил NDCG@10 baseline и модели на одном и том же тестовом наборе.
- Я проверил, что улучшение составляет не менее 20% (а не 19.5%).
- Я сохранил модель и код инференса, воспроизводимость гарантирована.
- Я написал простые тесты для каждой функции извлечения фич.
- Я задокументировал шаги в README так, чтобы другой инженер мог повторить эксперимент.
- Я убедился, что
lambdarankиспользует правильныйlabel_gain(по умолчанию экспоненциальный для NDCG). - Я проверил, что в тестовом наборе есть хотя бы 5 запросов для статистической значимости.