Guides
Tutoriel : évaluer une politique d’apprentissage par renforcement
Un parcours exécutable pour deux sortes de questions d’apprentissage par renforcement : quelle est la performance, sur des épisodes, d’une politique que vous pouvez exécuter, et quelle aurait été la performance d’une politique que vous ne pouvez pas essayer dans le monde réel, jugée à partir du journal de la politique qu’elle remplacerait. Les deux exemples s’exécutent en local sans identifiants de fournisseur.
La référence de chaque champ et de chaque évaluateur est Apprentissage par renforcement ; les termes cas, évaluateur, métrique, intervalle et porte sont dans Concepts fondamentaux. Cette page est le chemin pratique à travers eux.
Ce qu’Oloproof fait et ne fait pas ici
Oloproof n’exécute pas votre environnement, n’héberge pas de simulateur et n’entraîne pas de politique. Votre système exécute chaque épisode dans son propre environnement et l’enregistre comme artefact rl_episode/v1, et chaque métrique d’épisode est lue dans cet enregistrement. Dans la deuxième partie, rien ne s’exécute du tout : la politique testée indique seulement la probabilité qu’elle donnerait à chaque action.
Gardez les deux questions séparées :
| Question | Ce dont vous disposez | Partie |
|---|---|---|
| Quelle est la performance de cette politique sur des épisodes ? | Une politique et un environnement que vous pouvez exécuter | 1 |
| Quelle aurait été la performance de cette politique en production ? | Un journal des décisions qu’une politique plus ancienne a prises, avec les probabilités qu’elle a utilisées | 2 |
Prérequis
- Python 3.11 ou ultérieur, et Oloproof installé (pip install oloproof).
- Le projet d’exemple, livré avec le paquet. Copiez-le dans un nouveau répertoire :
oloproof init --example rl my-rlIl contient deux projets, episodes/ et off_policy/. Chaque commande ci-dessous s’exécute depuis l’intérieur de l’un d’eux, et les preuves sont stockées dans son répertoire .oloproof/.
Partie 1 : une politique que vous pouvez exécuter
cd my-rl/episodesLes fichiers
| Fichier | Ce que c’est |
|---|---|
| env.py | Un monde en grille, deux politiques (wander et plan) et les systèmes qui les exécutent |
| data/tasks.jsonl | 24 grilles, chacune une tâche : des murs, des dangers, un départ et un objectif |
| oloproof.yaml | La suite pour la politique de référence, wander |
| candidate.yaml | La même suite pour la politique candidate, plan |
| release.yaml | La politique de publication |
| comparison.yaml | Les règles qu’un candidat doit respecter par rapport à la référence |
Un épisode avance d’une case par étape jusqu’à atteindre l’objectif ou épuiser ses étapes. Chaque étape rapporte -1 et atteindre l’objectif rapporte +10, donc un retour se situe entre -30 (30 étapes, pas d’objectif) et 10. Entrer dans une case dangereuse coûte 1 : c’est la contrainte de sécurité. Un mouvement sur dix glisse dans une direction aléatoire, quoi que la politique ait choisi, si bien que la même politique peut faire différemment sur la même grille.
Enregistrer un épisode
Le système exécute l’épisode lui-même et remet l’enregistrement à l’enregistreur du cas :
@system(name="grid-agent", version="wander-v1", records=("rl_episode/v1",))
def run_baseline(task):
return _run(task, wander)
def _run(task, policy):
recorder = current_case()
record = episode(task, policy, seed=task["seed"] * 1000 + recorder.replicate)
recorder.rl_episode(record)
return {"success": record["success"], "steps": len(record["steps"])}L’enregistrement liste la récompense et le coût de chaque étape, si l’épisode a atteint l’objectif (success), et si la limite d’étapes y a mis fin (truncated).
Plusieurs épisodes par tâche
oloproof.yaml déclare replicates: 5, donc chaque tâche exécute cinq épisodes. current_case().replicate indique lequel (de 0 à 4), et l’environnement en tire la graine de l’épisode : le même épisode de la même tâche se rejoue toujours de la même façon, et une deuxième exécution réutilise les épisodes stockés au lieu de les exécuter à nouveau.
La tâche reste l’unité. Chaque critère est la moyenne, sur les 24 tâches, de la moyenne propre à chaque tâche sur ses cinq épisodes, donc l’intervalle compte 24 tâches et non 120 épisodes. Compter les épisodes reviendrait à prétendre que cinq épisodes d’une même grille sont cinq grilles indépendantes.
Les évaluateurs :
evaluators:
- {type: episode_return, return_range: [-30, 10]}
- {type: episode_success}
- {type: episode_cost_within, budget: 0, criterion: hazard_free}
metrics:
- {id: solved_every_time, type: every_replicate, criterion: episode_success}return_range est obligatoire : l’intervalle sur un retour moyen ne tient que dans une plage où se trouve tout retour, et un retour hors de celle-ci arrête l’exécution. solved_every_time ne compte une tâche que lorsque ses cinq épisodes ont tous atteint l’objectif.
Exécuter la référence
oloproof runGate: BLOCK (exit 3)
Decisions
┏━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Rule ┃ Metric ┃ State ┃ Reasons ┃
┡━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
│ success-floor │ episode_success │ INSUFFICIENT_EVIDENCE │ interval_overlaps_threshold │
│ safety-floor │ hazard_free │ INSUFFICIENT_EVIDENCE │ interval_overlaps_threshold │
└───────────────┴─────────────────┴───────────────────────┴─────────────────────────────┘
Metrics
┏━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Metric ┃ Estimate ┃ Interval ┃ N ┃
┡━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
│ episode_return │ -10.51 │ [-16.44, -5.11] │ mean of 24 observed · 0 missing · 0 excluded │
│ episode_success │ 76.7% │ [55.4%, 89.6%] │ 24 cases observed · 0 missing · 0 excluded · 5 replicates each · 14 flaky │
│ hazard_free │ 29.2% │ [10.3%, 54.0%] │ 24 cases observed · 0 missing · 0 excluded · 5 replicates each · 9 flaky │
│ solved_every_time │ 37.5% │ [18.7%, 59.5%] │ 9 / 24 observed · 0 missing · 0 excluded · 5 replicates each · 14 flaky │
└───────────────────┴──────────┴─────────────────┴───────────────────────────────────────────────────────────────────────────┘
Cache: execution 0 hit/120 miss; judgment 0 hit/360 missLisez-la ligne par ligne :
- La politique qui erre atteint l’objectif dans 76,7 % des épisodes, mais l’intervalle va de 55,4 % à 89,6 %, donc le plancher de 75 % n’est ni franchi ni manqué : INSUFFICIENT_EVIDENCE.
- Elle ne résout que 9 des 24 tâches à chacun de ses cinq épisodes. « Fonctionne en général » et « fonctionne à chaque fois » sont des affirmations différentes, et un agent qui tournera sans surveillance a besoin de la seconde.
- 14 tâches sont instables (flaky) : leur réussite a varié entre des épisodes identiques. Un seul épisode par tâche n’aurait pas pu le montrer.
- Elle évite les dangers dans 29,2 % des épisodes. Le haut de l’intervalle, 54,0 %, est au-dessus du plancher de 50 %, donc même ce piètre bilan ne prouve pas encore que la politique est dangereuse.
Regardez une tâche :
oloproof inspect RUN_ID --case grid_03output: {
"success": false,
"steps": 30
}
judgments:
episode_return: score -30
episode_success: failed
hazard_free: passedLe premier épisode de grid_03 a épuisé ses étapes : retour -30, pas d’objectif, aucune case dangereuse visitée.
Exécuter le candidat
oloproof run --config candidate.yaml│ success-floor │ episode_success │ PASS │ lower_bound_meets_minimum │
│ safety-floor │ hazard_free │ INSUFFICIENT_EVIDENCE │ interval_overlaps_threshold ││ episode_return │ 0.42 │ [-5.78, 2.59] │ mean of 24 observed · 0 missing · 0 excluded │
│ episode_success │ 100.0% │ [79.9%, 100.0%] │ 24 cases observed · 0 missing · 0 excluded · 5 replicates each │
│ hazard_free │ 57.5% │ [29.3%, 81.5%] │ 24 cases observed · 0 missing · 0 excluded · 5 replicates each · 6 flaky │
│ solved_every_time │ 100.0% │ [85.7%, 100.0%] │ 24 / 24 observed · 0 missing · 0 excluded · 5 replicates each │Le planificateur atteint l’objectif à chaque épisode, donc le plancher de réussite passe. Il entre encore dans des cases dangereuses : certaines grilles n’ont aucun chemin sûr, et le sol glissant le pousse dans d’autres. Son taux de sécurité de 57,5 % est au-dessus du plancher, mais 24 tâches ne peuvent pas encore montrer qu’il est au-dessus de 50 %.
Comparer les deux politiques
oloproof compare CANDIDATE_RUN_ID BASELINE_RUN_ID --policy comparison.yamlepisode_return: +10.92 [+0.21, +17.74] · 24 paired · 0 missing · 0 excluded
episode_success: +23.3 points [-3.2, +44.6] · 24 paired · 0 missing · 0 excluded
hazard_free: +28.3 points [-2.4, +54.7] · 24 paired · 0 missing · 0 excluded
solved_every_time: +62.5 points [+24.3, +87.7] · 24 paired · 0 missing · 0 excluded
Decisions
return-better episode_return superiority PASS difference_above_zero
safety-no-worse hazard_free non-inferiority, margin 5.0 points PASS lower_bound_above_margin
Gate: ALLOW (exit 0)La comparaison apparie chaque tâche avec elle-même, chaque côté valant sa moyenne sur cinq épisodes. La différence de retour est dans l’unité propre du retour : le planificateur gagne environ 10,9 de plus par épisode, et au moins 0,21 de plus. Appariée sur les mêmes grilles, une marge de 5 points suffit à montrer que le planificateur n’est pas moins sûr, même si aucune des deux exécutions seule ne pouvait trancher son propre plancher de sécurité.
Partie 2 : une politique jugée à partir d’un journal
cd ../off_policyUn service d’assistance oriente chaque ticket vers un petit ou un grand modèle. Le routeur actuel choisit le petit modèle 70 % du temps et le grand 30 %, au hasard. Vous avez écrit une nouvelle règle : les tickets techniques, et les longs tickets de facturation, vont au grand modèle. Vous ne pouvez pas encore l’essayer sur de vrais tickets, mais le journal de l’ancien routeur peut répondre pour elle. C’est l’évaluation hors politique (off-policy).
Le journal et ses fichiers
| Fichier | Ce que c’est |
|---|---|
| data/decisions.jsonl | 600 décisions que le routeur actuel a prises |
| make_log.py | La simulation qui a écrit ce journal, pour que vous puissiez voir comment il a été fait |
| router.py | La nouvelle règle, le système testé |
| oloproof.yaml | La suite |
| release.yaml | La politique de publication |
Chaque ligne du journal est une décision :
{"id":"t001","input":{"topic":"technical","length":"short"},"expected":{"action":"small","logging_probabilities":{"small":0.7,"large":0.3},"reward":1}}input est ce que le routeur voyait, action ce qu’il a choisi, logging_probabilities la probabilité qu’il donnait à chaque modèle à ce moment, et reward si le ticket a été résolu. La nouvelle règle répond seulement avec ses propres probabilités :
@system(name="ticket-router", version="by-topic-v1")
def route_by_topic(ticket):
large = ticket["topic"] == "technical" or (
ticket["topic"] == "billing" and ticket["length"] == "long"
)
return {"probabilities": {"small": 0.0 if large else 1.0, "large": 1.0 if large else 0.0}}Les évaluateurs :
evaluators:
- {type: off_policy_value, reward_range: [0, 1], propensity_floor: 0.3}
- {type: off_policy_improvement, reward_range: [0, 1], propensity_floor: 0.3}Chaque décision journalisée est repondérée selon que la nouvelle règle était plus ou moins susceptible de la prendre que l’ancien routeur : un ticket que l’ancien routeur a envoyé au grand modèle avec une probabilité de 0,3, et que la nouvelle règle y enverrait toujours, compte 1 / 0,3 fois, et une décision que la nouvelle règle n’aurait jamais prise compte zéro. propensity_floor est la plus petite probabilité que l’ancien routeur donnait à tout modèle qu’il pouvait choisir. Il borne ce poids, et sans borne aucun intervalle ne tient.
L’exécuter
oloproof run│ resolution-floor │ off_policy_value │ PASS │ lower_bound_meets_minimum │
│ better-than-current │ off_policy_improvement │ INSUFFICIENT_EVIDENCE │ interval_overlaps_threshold │
│ │ │ │ about 2633 more cases would decide it, if the observed values hold (3233 in total) ││ off_policy_value │ 0.775 │ [0.632, 0.921] │ mean of 600 observed · 0 missing · 0 excluded │
│ off_policy_improvement │ 0.060 │ [-0.064, 0.188] │ mean of 600 observed · 0 missing · 0 excluded │La nouvelle règle résoudrait environ 77,5 % des tickets selon l’estimation et, avec 95 % de confiance, au moins 63,2 % : le plancher de 60 % passe. Qu’elle fasse mieux que le routeur actuel n’est pas établi. L’estimation est meilleure de 6 points, mais l’intervalle va de 6,4 points de moins à 18,8 de plus, et le moteur indique à peu près combien de décisions journalisées supplémentaires le trancheraient.
Cette largeur est le prix d’apprendre à partir des choix de quelqu’un d’autre. Seuls les 30 % de tickets que l’ancien routeur a envoyés par hasard au grand modèle disent quoi que ce soit sur le grand modèle, et chacun compte plus de trois fois. Deux choses la réduisent : un journal plus long, et un ancien routeur qui explore davantage.
Journaliser les décisions de votre propre système
- Enregistrez les probabilités au moment où le routeur décide, pas après coup, et enregistrez la probabilité de chaque option, pas seulement celle de l’option choisie. Oloproof ne les estime pas : une probabilité devinée ferait reposer l’intervalle sur la supposition.
- Ne mettez dans input que ce que le routeur voyait quand il a décidé, jamais l’option choisie ni le résultat : la politique testée ne doit pas les voir.
- Si votre nouvelle politique tire son choix au sort, renvoyez les probabilités selon lesquelles elle tire, et non l’option qu’elle a tirée. Nommez chaque option exactement comme le journal le fait : une option orthographiée différemment compte comme une option que l’ancien routeur n’a jamais choisie.
- Gardez une part d’aléatoire dans le routage en production. Une politique qui ne choisit jamais une option ne laisse rien dans le journal pour juger une règle qui la choisirait.
- La réponse décrit des tickets semblables à ceux du journal, sur la période que couvre le journal. Un changement de votre trafic est hors de son champ.
- Une ligne mal formée (des probabilités dont la somme ne vaut pas 1, une option choisie de probabilité 0, une récompense hors de reward_range) arrête l’exécution. Une décision que la nouvelle règle prendrait avec une option à laquelle l’ancien routeur donnait moins que le plancher, ou dont la récompense manque, compte comme manquante et élargit l’intervalle.
Ce qui n’est pas construit
- L’évaluation hors politique d’une séquence de décisions, où le choix de chaque étape change ce qui vient ensuite. Seule une décision par cas est prise en charge.
- Un journal dont le routeur a changé ses probabilités en apprenant pendant la période.
- L’hébergement ou la réinitialisation d’environnements, de simulateurs ou de robots.
Pour aller plus loin
- Apprentissage par renforcement : la référence de ces évaluateurs et de l’enregistrement d’épisode.
- Écrire une suite : les réplicats et les autres champs de la suite.
- Comparer un candidat à une référence : ce que lit une comparaison appariée.
- Concepts fondamentaux : ce qu’un résultat atteint et n’atteint pas.