Learning-to-Rank¶
LTRRanker trains a single global ranking booster over decision-occasion
groups and projects it onto a calibrated-shaped (n, K) matrix over the shared
ranking surface — the same ranking.json contract as the
recommender, but from one model instead of many.
Install extras
pip install "parleml-ml4[ltr]". The lambdarank / rank_xendcg objectives use
LightGBM; yetirank / yetirank_pairwise use CatBoost. Only the library
backing your chosen objective is imported.
1. Event-grain decision occasions¶
In event grain every observed row is a positive purchase; negatives are sampled
from the catalogue per occasion. A "group" is one decision occasion.
import numpy as np
import pandas as pd
rng = np.random.default_rng(0)
products = ["a", "b", "c", "d", "e", "f"]
rows = []
for _ in range(3_000):
cust = rng.integers(0, 500)
ts = pd.Timestamp("2026-01-01") + pd.Timedelta(days=int(rng.integers(0, 60)))
# Preference depends on a session feature, so there is rank signal.
session_len = rng.integers(1, 30)
weights = np.array([session_len, 5, 4, 3, 2, 1], dtype=float)
taken = rng.choice(products, p=weights / weights.sum())
rows.append({"customer_id": cust, "product": taken, "ts": ts,
"session_len": session_len})
df = pd.DataFrame(rows)
2. Fit¶
fit returns a deployable pipeline directly.
from parleml_ml4.ltr import LTRRanker
from parleml_ml4.ltr.config import ltr_thorough
cfg = ltr_thorough(grain="event", entity_col="customer_id",
target_key_col="product", timestamp_col="ts",
ranker_objective="lambdarank", k=5)
pipeline = LTRRanker(cfg, outputs_dir="runs/ltr").fit(df)
result = pipeline.result
Groups resolve by (entity, floor(timestamp, bundle_time_granularity)) when a
timestamp_col is set. Feature columns are derived automatically from every
non-reserved column (here, session_len).
3. Read the ranking report¶
print(result.ranking) # shared ranking.json payload (Hit@K, MRR@K, NDCG@K, ...)
print(result.best_valid_ndcg) # best full-catalogue NDCG@k from Optuna tuning
print(result.feature_importance) # per-feature gains from the booster
print(result.backend, result.objective, result.model_label)
Optuna maximises full-catalogue NDCG@k from ml4's own ranking metrics — not the library's sampled-group NDCG, which can report a spurious 1.0 for a degenerate constant-score booster.
ranker_objective vs ranking_objective
ranker_objective is the training loss (lambdarank here);
ranking_objective is the evaluation/sort objective (accuracy /
balanced / coverage). They are independent — see Ranking.
4. Serve¶
With outputs_dir set, fit persisted the FittedLTRPipeline under
runs/ltr/deploy/ltr/ alongside events.ndjson / manifest.json.
See the Learning-to-Rank guide, the
Ranking guide, and the
LTRRanker reference.
Next: Ranking guide · Recommendation example