11 — Learning-to-Rank: LambdaRank, LightGBM, XGBoost, ListNet
“The right loss matters more than the right architecture.”
Introduction
Once you have candidates, you need to order them. Learning-to-Rank (LTR) is the family of algorithms that optimize ordering rather than per-item scores. Three loss families dominate: pointwise, pairwise, and listwise. The most-deployed flavors — LambdaRank, LambdaMART (via LightGBM/XGBoost) — power Bing, LinkedIn, Yelp, Spotify, and countless search and ranking surfaces.
1. Three Loss Families
flowchart LR
A[Pointwise<br/>regression / classification] --> A1[Logistic, MSE]
B[Pairwise<br/>relative order] --> B1[RankNet, LambdaRank, BPR]
C[Listwise<br/>full ordering] --> C1[ListNet, ListMLE, ApproxNDCG]
Pointwise
Predict score independently per item. Simple, calibrated. Doesn’t directly optimize ordering.
Pairwise
Penalize inversions: given $(q, i^+, i^-)$, push $s_{i^+} > s_{i^-}$.
\[\mathcal L_{\text{RankNet}} = \log(1 + e^{-(s^+ - s^-)})\]Listwise
Treats the whole ranking as a sample. Optimizes NDCG-like metrics (often via differentiable proxies).
2. RankNet → LambdaRank → LambdaMART
RankNet (Burges 2005) used a neural net + pairwise log-loss. LambdaRank observed: we don’t need a smooth loss — we need its gradient. Multiply the RankNet gradient by $|\Delta \text{NDCG}|$ — the change in NDCG if the two items swap:
\[\lambda_{ij} = \frac{\partial L}{\partial s_i} = \frac{-\sigma}{1 + e^{\sigma(s_i - s_j)}} \cdot |\Delta \text{NDCG}_{ij}|\]This directly optimizes NDCG despite NDCG being non-differentiable. LambdaMART = LambdaRank + GBDT (Friedman). It’s been the king of LTR for over a decade.
3. LightGBM and XGBoost LTR
Both support pairwise / lambdarank objectives natively, with group structure for query-aware ranking.
LightGBM
# pip install lightgbm pandas scikit-learn
import lightgbm as lgb
import pandas as pd
# Each row: features + relevance + qid (query/user)
df = pd.read_parquet("yahoo_ltr.parquet")
df = df.sort_values("qid")
X = df.drop(columns=["rel", "qid"])
y = df["rel"]
group = df.groupby("qid").size().values # items per query
model = lgb.LGBMRanker(
objective="lambdarank",
metric="ndcg",
ndcg_eval_at=[1, 5, 10],
n_estimators=500,
learning_rate=0.05,
num_leaves=63,
min_child_samples=20,
)
model.fit(X, y, group=group, eval_set=[(X, y)], eval_group=[group])
# Predict scores; sort within each qid
df["score"] = model.predict(X)
ranked = df.sort_values(["qid", "score"], ascending=[True, False])
XGBoost
import xgboost as xgb
dtrain = xgb.DMatrix(X, label=y)
dtrain.set_group(group)
params = {
"objective": "rank:pairwise", # or "rank:ndcg", "rank:map"
"eta": 0.1, "max_depth": 6,
"eval_metric": "ndcg@10",
}
model = xgb.train(params, dtrain, num_boost_round=500)
4. Listwise Losses
ListNet (Cao 2007)
Defines top-1 probability over a list using softmax of scores, minimizes KL divergence vs ground-truth distribution.
ListMLE
Maximum likelihood of the ground-truth permutation under the Plackett-Luce model.
ApproxNDCG (Qin 2010)
Smooth, differentiable approximation of NDCG via sigmoid-based rank indicators. Used by some neural rankers.
Softmax Cross-Entropy (TF-Ranking)
\(\mathcal L = -\sum_i y_i \log \frac{e^{s_i}}{\sum_j e^{s_j}}\) Simple, surprisingly competitive listwise loss.
5. Neural LTR
When features include heavy embeddings (text, image), tree-based LTR struggles. Neural rankers shine:
- DeepRank, DeepLTR: MLP + softmax cross-entropy
- Transformer rankers (e.g., BERT-base cross-encoders) — slow but accurate
- TF-Ranking library (Google) — Keras-native LTR
import torch.nn as nn, torch
class ListwiseRanker(nn.Module):
def __init__(self, in_dim, hidden=(128, 64)):
super().__init__()
layers, d = [], in_dim
for h in hidden:
layers += [nn.Linear(d, h), nn.ReLU(), nn.Dropout(0.1)]
d = h
layers += [nn.Linear(d, 1)]
self.mlp = nn.Sequential(*layers)
def forward(self, x): # x: B, K, F (K items per query)
return self.mlp(x).squeeze(-1) # B, K
def listwise_softmax_ce(scores, labels): # both B, K
return -(labels * torch.log_softmax(scores, dim=-1)).sum(-1).mean()
6. Pros & Cons Comparison
| Loss family | Pros | Cons |
|---|---|---|
| Pointwise | Simple, calibrated | Doesn’t optimize ordering |
| Pairwise (RankNet/LambdaRank) | Strong empirical, easy with GBDT | $O(K^2)$ pairs per query |
| Listwise | Directly aligned with metrics | More complex, gradient estimation harder |
| LambdaMART | SOTA on tabular features, easy to deploy | Hard to leverage embeddings/text |
| Neural listwise | Embeds text/image natively | Needs more data |
7. End-to-End: LambdaMART on MSLR-WEB10K
import lightgbm as lgb
from sklearn.datasets import load_svmlight_file
import numpy as np
# Download MSLR-WEB10K from https://www.microsoft.com/en-us/research/project/mslr/
X_tr, y_tr, qid_tr = load_svmlight_file("train.txt", query_id=True)
X_va, y_va, qid_va = load_svmlight_file("vali.txt", query_id=True)
def to_group(qids):
_, counts = np.unique(qids, return_counts=True)
return counts
m = lgb.LGBMRanker(objective="lambdarank", metric="ndcg",
ndcg_eval_at=[1,3,5,10], n_estimators=1000,
learning_rate=0.05, num_leaves=255, min_data_in_leaf=20)
m.fit(X_tr, y_tr, group=to_group(qid_tr),
eval_set=[(X_va, y_va)], eval_group=[to_group(qid_va)],
callbacks=[lgb.early_stopping(50)])
8. Production Notes
flowchart LR
A[Retrieval<br/>Two-tower / kNN] --> B[Candidate Set<br/>~hundreds]
B --> C[Feature Join<br/>user x candidate]
C --> D[Ranker<br/>LightGBM / Neural LTR]
D --> E[Re-Ranker<br/>diversity, business rules]
E --> F[Top-K served]
G[Logs<br/>impressions + clicks] --> H[LTR Training Set]
H --> D
- Group by request/session for training; never mix queries.
- Feature parity: serving features must match training features bit-for-bit.
- Calibrate scores if downstream needs probabilities.
- Reduce K before ranker — 500 candidates is plenty; 5000 is wasteful.
- Tree ranker fits in MB and serves at <1ms; neural ranker may need GPU.
9. Pitfalls
- Forgetting
group=— LightGBM will silently train a useless model. - Using NDCG on test set sliced differently from training (different queries).
- Severe label imbalance (many irrelevants, few relevants) — use weighted sampling or focal pairwise.
- Position bias in click logs — IPS weighting (Blog 17).
- Tuning on average NDCG@10 while users care about NDCG@1 (a different model wins).
10. Public Datasets
- MSLR-WEB10K / 30K (Microsoft) — https://www.microsoft.com/en-us/research/project/mslr/
- Yahoo Learning to Rank Challenge — https://webscope.sandbox.yahoo.com/
- LETOR 4.0 — https://www.microsoft.com/en-us/research/project/letor/
- Istella LETOR — http://quickrank.isti.cnr.it/istella-dataset/
- TREC Deep Learning — https://microsoft.github.io/msmarco/
11. Further Reading
- Burges, From RankNet to LambdaRank to LambdaMART: An Overview (Microsoft TR 2010)
- Friedman, Greedy Function Approximation: A Gradient Boosting Machine (2001)
- Cao et al., ListNet: Learning to Rank — From Pairwise Approach to Listwise (ICML 2007)
- Qin et al., A General Approximation Framework for Direct Optimization of IR Measures (Inform. Retrieval 2010)
- Han et al., Learning-to-Rank with BERT in TF-Ranking (2020)
- Joachims et al., Unbiased Learning-to-Rank with Biased Feedback (WSDM 2017) — Blog 17 setup