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

Реализовать 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-пакет

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

  1. Создать 500–1000 синтетических запросов, каждый связан с 10–50 документами.
  2. Для каждого документа сгенерировать текст (например, случайные предложения с ключевыми словами), дату (случайную в диапазоне 2020–2025), показатель authority (float от 0 до 1).
  3. Назначить релевантность (0/1/2/3) на основе комбинации: если BM25 высокий И эмбеддинг близкий → 3, иначе по убыванию.
  4. Разбить на train (70%), validation (15%), test (15%).
  5. Сохранить в формате CSV: query_id, doc_id, relevance, bm25_score, cosine_sim, recency_norm, authority.

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

КомпонентИнструментыНазначение
Язык программированияPython 3.10+Основной язык
Обработка текстаrank_bm25, sentence-transformersBM25 скоринг, эмбеддинги
ML фреймворкlightgbm 4.xОбучение LambdaMART
Метрикиsklearn.metrics.ndcg_score, scipy.statsОценка NDCG
Данныеpandas, numpy, datasets (опционально)Загрузка, преобразование
Валидацияgroup (для группировки по запросам) в LightGBMОбучение с группировкой
Визуализацияmatplotlib, seabornГрафики обучения, сравнение метрик

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

Этап 1: Подготовка данных и извлечение фич (4–6 часов)

Действия

  1. Загрузить/сгенерировать датасет
    • Использовать готовый датасет (например, MS MARCO passages) или синтетический.
    • Если синтетический: написать скрипт generate_synthetic_dataset.py, который создаёт случайные запросы, документы и метки.
  2. Для каждой пары (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] (если нет, сгенерировать случайно).
  3. Собрать все фичи в 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
    })
    
  4. Разделить на train/validation/test по запросам (чтобы запросы не пересекались).
  5. Сохранить группы для каждого набора создать массив group = [количество документов на запрос.

Ожидаемый результат этапа Вики/pandas DataFrame|DataFrame с фичами и метками, разбитый на 3 набора (train.csv, val.csv, test.csv) + файлы group_train.npy и т.д.


Этап 2: Реализация baseline (гибридное ранжирование) (1–2 часа)

Действия

  1. Определить гибридную формулу score = w_bm25 * bm25 + w_cos * cosine_sim.
  2. Оптимизировать веса (опционально) с помощью grid search на валидации по NDCG@10 (например, веса от 0.1 до 0.9 с шагом 0.1).
  3. Оценить baseline NDCG@10 на тестовом наборе.
  4. Зафиксировать результат baseline_ndcg = 0.XX.

Ожидаемый результат этапа Числовое значение NDCG@10 для гибридного ранжирования и найденные оптимальные веса (если искали).


Этап 3: Обучение LambdaMART (3–4 часа)

Действия

  1. Подготовить данные для LightGBM
    • X_train — матрица фич (4 столбца), y_train — релевантности (int, не бинарные).
    • group_train — размеры каждого запроса.
  2. Настроить параметры
    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
    }
    
  3. Обучить модель с ранней остановкой на валидации:
    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)]
    )
    
  4. Сохранить модель model.save_model('lambdamart_model.txt').

Ожидаемый результат этапа Обученная модель (lambdamart_model.txt), лог обучения с ранней остановкой.


Этап 4: Оценка модели и сравнение с baseline (2–3 часа)

Действия

  1. Загрузить модель и предсказать scores на тестовом наборе:
    y_pred = model.predict(X_test)
    
  2. Рассчитать 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)
    
  3. Сравнить с baseline если avg_ndcg_lambda >= 1.2 * baseline_ndcg — задача выполнена.
  4. Построить график сравнения NDCG@10 по запросам (или cumulative gain).

Ожидаемый результат этапа Таблица с метриками, вывод о достижении +20%, визуализация.


Этап 5: Инференс и интеграция (2–3 часа)

Действия

  1. Написать функцию ranker(query, candidate_docs):
    • Для каждого кандидата вычислить те же 4 фичи (предварительно закэшировав эмбеддинги и BM25-индексы).
    • Сформировать матрицу фич и вызвать model.predict().
    • Вернуть отсортированный список документов.
  2. Протестировать на нескольких ручных запросах, убедиться, что ранжирование осмысленно.
  3. Добавить unit-тесты на корректность вычисления фич и совпадение с обученными значениями.
  4. Задокументировать формат входных данных, используемые модели (хранить в 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: Реализация baseline1.5 часа
Этап 3: Обучение LambdaMART3 часа
Этап 4: Оценка и сравнение2.5 часа
Этап 5: Инференс и интеграция2 часа
Итого14 часов

Примечание Для первого выполнения заложите дополнительно 2–3 часа на отладку и изучение документации LightGBM.


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

ВопросТема
45Реализация LambdaMART (текущее)
42Метрики ранжирования: NDCG, MAP
44Learning-to-Rank: обзор подходов
10Градиентный бустинг (GBDT)
38BM25: принцип и реализация
33Косинусная близость эмбеддингов
71Оценка качества информационного поиска
88Feature engineering для поиска
103LightGBM: настройка параметров
27Pairwise loss в ранжировании

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

  • Я корректно разбил данные на train/val/test по запросам (без пересечения).
  • Я убедился, что фичи для одного запроса не перепутаны с другим (использую group).
  • Я сравнил NDCG@10 baseline и модели на одном и том же тестовом наборе.
  • Я проверил, что улучшение составляет не менее 20% (а не 19.5%).
  • Я сохранил модель и код инференса, воспроизводимость гарантирована.
  • Я написал простые тесты для каждой функции извлечения фич.
  • Я задокументировал шаги в README так, чтобы другой инженер мог повторить эксперимент.
  • Я убедился, что lambdarank использует правильный label_gain (по умолчанию экспоненциальный для NDCG).
  • Я проверил, что в тестовом наборе есть хотя бы 5 запросов для статистической значимости.