Learning to rank with LGBMRanker

LightGBM · Advanced · 11 min

Search, recommendation and feed ordering are ranking problems, and LightGBM's lambdarank is an industry standard.

For ranking, data is grouped by query: each query has several candidate items with graded relevance labels (integers 0, 1, 2, ...). The model learns scores whose order within each query matches relevance.

LGBMRanker(objective="lambdarank") (the default) is trained with:

  • group=: the sizes of consecutive query groups, for example [10, 8, 12] for three queries. Rows must be sorted so each query's items are contiguous. (XGBoost uses per-row query ids instead.)
  • eval_X, eval_y and eval_group for validation, with eval_at=[5, 10] to report NDCG@5 and NDCG@10.

Relevant parameters:

  • label_gain: the gain for each relevance level, default 2^label - 1; labels must be below its length (31 by default),
  • lambdarank_truncation_level: how deep in the ranking pairs are considered,
  • lambdarank_norm: normalisation of lambdas,
  • objective="rank_xendcg": an alternative, often faster objective.

predict returns scores; sort within each query. Split data by query for validation (GroupKFold), and evaluate with per-query NDCG. Features describe the query-item pair: text relevance scores, item popularity, user history signals.

Example

import lightgbm as lgb
import numpy as np
from sklearn.metrics import ndcg_score

rng = np.random.default_rng(2)
n_q, per_q = 300, 8
X = rng.normal(size=(n_q * per_q, 6))
rel = np.clip(np.round(1.5 + X[:, 0] + 0.5 * X[:, 2] + rng.normal(0, 0.6, len(X))), 0, 4).astype(int)

n_train = 250 * per_q                                     # first 250 queries train
ranker = lgb.LGBMRanker(n_estimators=200, learning_rate=0.05, verbose=-1)
ranker.fit(X[:n_train], rel[:n_train], group=[per_q] * 250,
           eval_X=X[n_train:], eval_y=rel[n_train:], eval_group=[[per_q] * 50], eval_at=[5])

print(round(ranker.best_score_["valid_0"]["ndcg@5"], 3))  # 0.92
scores = ranker.predict(X[n_train:]).reshape(-1, per_q)
print(round(ndcg_score(rel[n_train:].reshape(-1, per_q), scores, k=5), 3))   # 0.939

Remember

  • group= gives the sizes of consecutive query groups
  • Evaluate with NDCG@k per query; split by query
  • Relevance labels must be small non-negative integers (see label_gain)

Watch out for

  • Passing per-row query ids as group instead of group sizes.

Check yourself

  1. For 3 queries with 10, 8 and 12 candidates (rows sorted by query), what is the group argument?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: lightgbm.readthedocs.io/en/stable/pythonapi/lightgbm.LGBMRanker.html