Skip to content

Recommendation (next-best-product)

Recommender is ml4's next-best-product engine. From per-entity holdings and outcomes it trains a One-vs-Rest ensemble of calibrated binary sub-models, assembles a (n_entities, n_classes) score matrix, and reports it over the shared ranking surface.

No extra of its own

Recommendation drives the core AutoGluon supervised engine through a trainer seam, so the base install is enough.

1. Long-grain candidate data

One row per (entity, product) candidate with a binary label_col:

import numpy as np
import pandas as pd

rng = np.random.default_rng(0)
customers = np.arange(400)
products = ["checking", "savings", "credit_card", "loan", "insurance"]

rows = []
for c in customers:
    income = rng.normal(60_000, 20_000)
    for p in products:
        # A learnable propensity: higher income → more likely to hold investment products.
        base = {"checking": 0.6, "savings": 0.4, "credit_card": 0.3,
                "loan": 0.15, "insurance": 0.2}[p]
        prob = base + (income - 60_000) / 200_000
        rows.append({"customer_id": c, "product": p, "income": round(income),
                     "bought": int(rng.random() < np.clip(prob, 0.02, 0.95))})
df = pd.DataFrame(rows)

2. Fit

from parleml_ml4.recommendation import Recommender
from parleml_ml4.config.recommendation import RecoConfig

cfg = RecoConfig(grain="long", entity_col="customer_id",
                 target_key_col="product", label_col="bought", k=3)
model = Recommender(cfg, outputs_dir="runs/reco").fit(df)
result = model.result_

Each (class, subcase) gets its own calibrated binary sub-model; sub-models below min_submodel_roc are dropped. A dropped subcase routes to the class's unified model when one was trained (partial splits only) and otherwise to the most-populous surviving expert; a class with no surviving sub-model is excluded. See the recommendation guide for the full fallback table.

3. Read the ranked recommendations

print(result.classes)                  # modelled products
print(result.optimization_metric)      # metric the binaries were tuned on
ranking = result.ranking               # shared ranking.json payload

# Accuracy vs coverage views (Hit@K, MRR@K, NDCG@K, catalog coverage, ...)
print(ranking["primary_score"])        # e.g. {"metric": "ndcg@3", "value": ...}

The ranking_objective knob (accuracy / balanced / coverage) tilts the cross-product sort toward the head or the long tail — see Ranking.

4. Serve

pipeline = model.to_pipeline()         # FittedRecoPipeline

With outputs_dir set, the run persisted the pipeline under runs/reco/deploy/recommender/ alongside events.ndjson / manifest.json, and is resumable per class (resume=True reloads finished classes, refits the rest).

See the Recommendation guide, the Ranking guide, and the Recommender reference.

Next: Ranking guide · Learning-to-Rank example