AACsearch
Relevance Studio

Learning to Rank

Le pipeline LTR dans Relevance Studio — feedback de clics, correction du biais de position, entraînement, versioning des modèles, tests A/B et activation. Comment interpréter la significativité du z-test et choisir un gagnant.

Learning to Rank (LTR) est la boucle de feedback fermée qui transforme de vrais clics utilisateurs en un modèle de classement appris. Studio livre les quatre panneaux correspondant aux quatre étapes : Feedback de clics, Exécutions d'entraînement, Modèles et Tests A/B.

Pipeline en un coup d'œil

clics (SearchUsageEvent)

   ▼   correction du biais de position (debias)

   ▼   entraînement (shim LightGBM aujourd'hui ; LightGBM/XGBoost natifs différés)

   ▼   artefact de modèle + métriques (NDCG, MRR, AUC)

   ▼   test A/B : split de trafic, significativité par z-test

   ▼   activer le gagnant → classement au chemin de lecture

Chaque étape est réversible. Un mauvais modèle est à un clic de la désactivation — l'ancien modèle actif est toujours conservé.

1 · Feedback de clics

Le panneau Feedback de clics résume le signal brut qui nourrit l'entraîneur. Trois chiffres par index :

  • clics-avec-contexte — combien d'événements click arrivent avec un queryId et position valides. Sans queryId, on ne peut pas relier un clic à une recherche.
  • correction du biais de position — la courbe de debias en vigueur. Le biais de position est le phénomène bien connu selon lequel la position 1 est cliquée indépendamment de la pertinence. Le score corrigé est en gros observed_ctr / propensity[position], où propensity est ajusté par index.
  • lignes d'entraînement utilisables — après debias et filtre des zéro-résultats, combien de lignes sont éligibles pour l'entraîneur.

Si les lignes utilisables sont sous ~5k, Training refuse de lancer le run et affiche une bannière "signal insuffisant". Sous ce seuil, le modèle surapprend et fait moins bien que le ranker statique.

2 · Exécutions d'entraînement

Un run produit un artefact de modèle à partir d'une fenêtre choisie de feedback de clics. Dans Studio vous choisissez :

  • Fenêtre — 7, 30 ou 90 derniers jours.
  • Algorithme — actuellement shim (seule option en releases livrées). LightGBM et XGBoost natifs sont différés — voir note ci-dessous.
  • Features — set prédéfini (pertinence textuelle, récence, prix, popularité, cohorte de catégorie). Les features custom sont sur la feuille de route.

Le run montre la progression, puis trois métriques à l'arrivée :

MétriqueSens
NDCG@10Normalized discounted cumulative gain au top 10 — métrique primaire
MRRMean reciprocal rank du premier résultat cliqué
AUCAire sous la courbe clic vs non-clic

Statut du shim (mai 2026)

L'entraîneur LTR livré est un algorithme shim : un combinateur linéaire simplifié qui ajuste quelques poids sur les features pré-calculées. Il est volontairement conservateur et tourne derrière le feature flag ltr.shim.

Les entraîneurs LightGBM et XGBoost natifs complets sont différés. Ils sont implémentés derrière le flag ltr.native mais ne sont pas en GA — ils requièrent plus de tests de charge et un déploiement du model registry. Pour l'heure, considérez le shim comme le seul algorithme supporté et attendez-vous à un lift NDCG@10 de 5–15% par rapport à la baseline non rankée, pas les 25–40% de certains papers. Les clients qui ont besoin de LightGBM natif aujourd'hui peuvent contacter le support pour une preview privée.

3 · Modèles

Chaque run réussi produit un modèle dans le panneau Modèles. Les modèles sont des artefacts immuables et portent :

  • un ID versionné (mdl_<random>), horodatage de création, run source,
  • le tuple de métriques (NDCG@10, MRR, AUC),
  • le feature set et la fenêtre sur lesquels il a été entraîné,
  • le statut de déploiement : draft, in_ab_test, active ou archived.

Un modèle en active est ce que le chemin de lecture consulte à chaque requête de recherche pour cet index. Il y a exactement un modèle actif par index à tout instant.

4 · Tests A/B

On ne promeut pas un modèle de draft directement vers active. On lance plutôt un test A/B qui partage le trafic live entre le modèle candidat (bras B) et le modèle actif courant (bras A, contrôle).

Lancer un test A/B

Depuis Studio → LTR → Tests A/B → Nouveau test :

// Ce que Studio fait en interne — appelable directement aussi
import { client } from "@repo/api/client";

const test = await client.ltr.abTests.create.call({
  indexSlug: "products",
  controlModelId: "mdl_active_now",
  variantModelId: "mdl_candidate",
  trafficSplit: 0.10, // 10% du trafic live voit le bras B
  primaryMetric: "ctr", // ctr | cvr | ndcg10
  minSampleSize: 50_000, // recherches par bras avant calcul de significativité
});

Le split de trafic est par requête, pas par utilisateur — un même utilisateur peut donc tomber sur des bras différents au fil des sessions. Le modèle variant s'applique au bras B ; le bras A continue avec le modèle actif existant.

Lire les résultats

Pendant le test, le panneau montre des compteurs live par bras :

  • n — recherches assignées au bras.
  • clicks, ctr, cvr — métriques primaires, rafraîchies toutes les 5 min.
  • z-score — différence standardisée entre bras B et A sur la métrique primaire.
  • p-value — bilatérale.
  • décisionrunning, significant_win, significant_loss, ±borderline ou no_effect.

Comment lire la significativité

L'entraîneur utilise un z-test de deux proportions standard, en traitant chaque recherche comme un essai Bernoulli indépendant sur la métrique primaire. Seuils :

DécisionPlage de zSens
significant_winz ≥ +1.96 (p ≤ 0.05)Candidat bat le contrôle avec 95% de confiance.
significant_lossz ≤ −1.96Candidat perd avec 95% de confiance — arrêter le test.
±borderline1.65 ≤ |z| < 1.9690–95% de confiance — attendre plus d'échantillons ou décider d'avance.
no_effect|z| < 1.65 et n ≥ minSampleSizePas de différence détectable à cette taille.
runningn < minSampleSizePas encore assez de données.

La bande ±borderline est exposée comme décision propre parce que la plupart des équipes produit ont une règle pré-test du type "ship à 90% si delta positif, hold à 95% si négatif". Studio ne promeut pas automatiquement en borderline — un humain doit cliquer Activer le gagnant.

Guide de taille d'échantillon

Le minSampleSize par défaut est 50 000 recherches par bras. Avec un split de 10%, un index à 50k recherches/jour atteint la significativité en ~11 jours pour un effet de 2 points de pourcentage sur le CTR. Pour des index plus petits, comptez 3–4 semaines. Le panneau Studio affiche un ETA live.

Activer un gagnant

Quand la décision est significant_win (ou que vous décidez de shipper sur ±borderline), cliquez Activer le gagnant dans le panneau A/B. Cela :

  1. Passe le modèle variant en active.
  2. Rétrograde l'ancien modèle actif en archived (réactivable en un clic si le nouveau régresse en production).
  3. Clôt le test A/B avec un rapport final.
  4. Route 100% du trafic live via le nouveau modèle en ~60 secondes (TTL du LRU policy-cache).
await client.ltr.abTests.activateWinner.call({
  testId: test.id,
  // safety check optionnel — refuse si la décision live a divergé de
  // `significant_win` entre le fetch et l'appel
  expectedDecision: "significant_win",
});

En cas de besoin de rollback après activation, le panneau Modèles conserve l'ancien modèle actif en archived et expose Réactiver en un clic.

Bonnes pratiques

  • Toujours lancer un test A/B. Même un modèle qui gagne en NDCG offline peut régresser sur le CTR live — biais de position et effets de présentation n'apparaissent pas dans les métriques offline.
  • Choisissez la métrique qui mappe au métier. CTR est le défaut ; les catalogues conversion-driven posent primaryMetric: "cvr".
  • Pas de "peek-and-stop". Le z-test suppose une taille d'échantillon fixe — regarder avant minSampleSize et arrêter gonfle le taux de faux positifs. Fixez la taille, partez, décidez ensuite.
  • Archivez agressivement. Gardez au plus 5–10 modèles archivés par index. Le registry est peu coûteux mais le journal d'audit se lit mieux avec moins de lignes.

Lié

On this page