La boucle de retour
Un modèle entraîné une fois se dégrade à mesure que la réalité s'éloigne de ses exemples. La boucle de retour est ce qui l'en empêche.
predict -> la réalité arrive -> submit_outcomes -> performance -> refresh -> predict1. Gardez l'identifiant de chaque prédiction
Chaque prédiction porte un request_id. C'est lui qui permettra, plus tard, de rattacher ce qui s'est réellement passé sans avoir à réexpliquer la ligne.
r = model.predict(lignes)
for ligne, prediction in zip(lignes, r.lignes):
enregistrer(client_id=ligne["id"], request_id=prediction["request_id"])Rangez ce request_id à côté de votre propre identifiant métier. Une colonne dans votre base suffit.
2. Remontez ce qui est arrivé
Deux formes, et vous pouvez les mélanger dans le même appel.
model.submit_outcomes([
# par identifiant : l'API retrouve les caractéristiques toute seule
{"request_id": "1bc55e5c-...", "actual": 1},
# par caractéristiques : quand vous n'avez pas gardé l'identifiant
{"features": {"plan": "free", "seats": 2, "tickets_90d": 10}, "actual": 0},
])actual contient la valeur réellement observée, dans le même vocabulaire que votre cible d'entraînement. Si votre cible valait 0 ou 1, actual vaut 0 ou 1.
La réponse vous dit ce qui a été accepté, ce qui reste en attente, et ce qui n'a pas pu être rattaché :
r = model.submit_outcomes(verites)
r.get("accepted") # 3
r.get("pending_outcomes") # 3, en attente d'intégration
r.get("unresolved") # celles qu'aucune prédiction ne recoupe
r.get("refresh_suggested") # True quand il devient utile d'intégrerGroupez vos vérités
Un appel vaut un lot. Sur un modèle en auto_update, chaque lot déclenche une intégration : envoyer vos vérités une par une déclencherait une intégration par vérité.
3. Mesurez, avant d'intégrer
performance compare ce qui a été prédit à ce qui est arrivé, et dit en une phrase ce qu'il faut en penser.
p = model.performance(days=30)
p.get("verdict") # {'level': 'holding', 'message': 'The model does better than the lazy answer on real outcomes.'}
p.get("training_metrics") # ce que l'entraînement annonçait
p.get("observed") # ce qui est constaté, avec le détail par version
p.get("pending_outcomes") # vérités pas encore intégréesLe verdict a trois niveaux. holding : le modèle fait mieux que la réponse paresseuse sur les vérités réelles, intégrez. weak : il ne fait pas mieux que répondre toujours la classe majoritaire, ajoutez des vérités ou entraînez sur des colonnes connues avant l'événement. dropped : la précision réelle est loin de celle de l'entraînement, ce qui veut presque toujours dire qu'une colonne remplie après l'événement passait pour une bonne caractéristique. Dans ce cas n'intégrez pas : trouvez la colonne, réentraînez sans elle. Le verdict est absent pour une régression, qui n'a pas de plancher mesuré.
observed reste null tant qu'aucune vérité n'a pu être rapprochée d'une prédiction : c'est normal les premiers jours. La réponse détaille aussi la performance par version, ce qui répond à la seule question qui compte vraiment : est-ce que la dernière intégration a amélioré les choses, ou pas.
4. Intégrez, si le modèle tient
refresh verse les vérités en attente aux données-contexte et produit une nouvelle version, qui sert immédiatement.
r = model.refresh()
if r is None:
print("rien à intégrer pour l'instant")
else:
print(r.get("version")["version"], r.get("outcomes_applied"))refresh renvoie None quand il n'y a rien en attente. C'est un état normal, pas une erreur : vous pouvez l'appeler chaque nuit sans vous soucier de savoir s'il y a du travail.
La version précédente reste consultable, donc un retour en arrière est possible. Et les poids ne changent pas : c'est l'ensemble d'exemples qui grandit.
Un rythme raisonnable
Il n'y a pas de bonne réponse universelle, mais ce schéma marche bien :
- remontez les vérités en continu, par lots, au fil de vos traitements ;
- mesurez une fois par semaine, et lisez le verdict avant toute chose ;
- intégrez une fois par jour, ou dès que
refresh_suggestedpasse à vrai, tant que le modèle tient.
Si vous préférez ne pas y penser, créez le modèle avec auto_update=True : les lots déclenchent l'intégration eux-mêmes.
Être prévenu
Deux événements de webhook servent exactement à ça : model.stale quand le modèle mériterait une intégration, model.refreshed quand une nouvelle version est en service. Voir Webhooks.