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_yandeval_groupfor validation, witheval_at=[5, 10]to report NDCG@5 and NDCG@10.
Relevant parameters:
label_gain: the gain for each relevance level, default2^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.939Remember
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.