Ga naar de inhoud

Handleidingen

Tutorial: een policy voor reinforcement learning evalueren

Een uitvoerbare doorloop voor twee soorten vragen over reinforcement learning: hoe goed een policy die je kunt draaien presteert over episodes, en hoe goed een policy die je niet in de echte wereld kunt uitproberen het gedaan zou hebben, beoordeeld op basis van de log van de policy die ze zou vervangen. Beide voorbeelden draaien lokaal zonder providergegevens.

De referentie voor elk veld en elke evaluator is Reinforcement learning; de termen case, evaluator, metriek, interval en gate staan in Kernbegrippen. Deze pagina is de praktische route erdoorheen.

Wat Oloproof hier wel en niet doet

Oloproof draait je omgeving niet, host geen simulator en traint geen policy. Je systeem draait elke episode in zijn eigen omgeving en legt haar vast als een rl_episode/v1-artefact, en elke episodemetriek wordt uit dat record gelezen. In het tweede deel draait er helemaal niets: de geteste policy geeft alleen de kans op die ze aan elke actie zou geven.

Houd de twee vragen uit elkaar:

VraagWat je hebtDeel
Hoe goed doet deze policy het over episodes?Een policy en een omgeving die je kunt draaien1
Hoe goed zou deze policy het in productie gedaan hebben?Een log van beslissingen die een oudere policy nam, met de kansen die ze gebruikte2

Vereisten

  • Python 3.11 of nieuwer, en Oloproof geïnstalleerd (pip install oloproof).
  • Het voorbeeldproject, dat met het pakket wordt meegeleverd. Kopieer het naar een nieuwe map:
oloproof init --example rl my-rl

Het bevat twee projecten, episodes/ en off_policy/. Elk commando hieronder draait vanuit een van beide, en bewijs wordt opgeslagen in zijn map .oloproof/.

Deel 1: een policy die je kunt draaien

cd my-rl/episodes

De bestanden

BestandWat het is
env.pyEen rasterwereld, twee policies (wander en plan) en de systemen die ze draaien
data/tasks.jsonl24 rasters, elk een taak: muren, gevaren, een start en een doel
oloproof.yamlDe suite voor de baselinepolicy, wander
candidate.yamlDezelfde suite voor de kandidaatpolicy, plan
release.yamlDe releasepolicy
comparison.yamlDe regels waaraan een kandidaat moet voldoen tegen de baseline

Een episode beweegt één vak per stap tot ze het doel bereikt of geen stappen meer heeft. Elke stap levert -1 op en het doel bereiken levert +10 op, dus een return ligt tussen -30 (30 stappen, geen doel) en 10. Een gevaar betreden kost 1: dat is de veiligheidsbeperking. Eén zet op de tien glijdt weg in een willekeurige richting, wat de policy ook koos, dus dezelfde policy kan het op hetzelfde raster verschillend doen.

Een episode vastleggen

Het systeem draait de episode zelf en geeft het record aan de caserecorder:

@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"])}

Het record somt de beloning en kosten van elke stap op, of de episode het doel bereikte (success), en of de stapgrens haar beëindigde (truncated).

Meerdere episodes per taak

oloproof.yaml declareert replicates: 5, dus elke taak draait vijf episodes. current_case().replicate zegt welke (0 tot en met 4), en de omgeving seedt de episode ermee: dezelfde episode van dezelfde taak speelt zich altijd op dezelfde manier af, en een tweede run hergebruikt de opgeslagen episodes in plaats van ze opnieuw te draaien.

De taak blijft de eenheid. Elk criterium is het gemiddelde, over de 24 taken, van het eigen gemiddelde van elke taak over haar vijf episodes, dus het interval telt 24 taken en niet 120 episodes. Episodes tellen zou doen alsof vijf episodes van één raster vijf onafhankelijke rasters zijn.

De evaluators:

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 is verplicht: het interval op een gemiddelde return geldt alleen binnen een bereik waarin elke return ligt, en een return erbuiten stopt de run. solved_every_time telt een taak alleen wanneer al haar vijf episodes het doel bereikten.

De baseline draaien

oloproof run
Gate: 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 miss

Lees het rij voor rij:

  • De dwalende policy bereikt het doel in 76.7% van de episodes, maar het interval loopt van 55.4% tot 89.6%, dus de ondergrens van 75% wordt gehaald noch gemist: INSUFFICIENT_EVIDENCE.
  • Ze lost maar 9 van de 24 taken op in elk van haar vijf episodes. "Werkt meestal" en "werkt elke keer" zijn verschillende beweringen, en een agent die zonder toezicht zal draaien heeft de tweede nodig.
  • 14 taken zijn wisselvallig: hun succes veranderde tussen identieke episodes. Eén episode per taak had dat niet kunnen laten zien.
  • Ze vermijdt gevaren in 29.2% van de episodes. De bovenkant van het interval, 54.0%, ligt boven de ondergrens van 50%, dus zelfs dit slechte resultaat bewijst nog niet dat de policy onveilig is.

Bekijk één taak:

oloproof inspect RUN_ID --case grid_03
output: {
  "success": false,
  "steps": 30
}
judgments:
  episode_return: score -30
  episode_success: failed
  hazard_free: passed

De eerste episode van grid_03 had geen stappen meer: return -30, geen doel, geen gevaar betreden.

De kandidaat draaien

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            │

De planner bereikt het doel in elke episode, dus de ondergrens voor succes slaagt. Hij betreedt nog steeds gevaren: sommige rasters hebben geen veilig pad, en de gladde vloer duwt hem in andere. Zijn veiligheidspercentage van 57.5% ligt boven de ondergrens, maar 24 taken kunnen nog niet laten zien dat het boven 50% ligt.

De twee policies vergelijken

oloproof compare CANDIDATE_RUN_ID BASELINE_RUN_ID --policy comparison.yaml
episode_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)

De vergelijking koppelt elke taak aan zichzelf, waarbij elke kant zijn gemiddelde over vijf episodes waard is. Het verschil in return staat in de eigen eenheid van de return: de planner verdient ongeveer 10.9 meer per episode, en minstens 0.21 meer. Gekoppeld op dezelfde rasters is een marge van 5 punten genoeg om te laten zien dat de planner niet minder veilig is, ook al kon geen van beide runs op zich zijn eigen veiligheidsondergrens beslissen.

Deel 2: een policy beoordeeld op basis van een log

cd ../off_policy

Een supportdesk stuurt elk ticket naar een klein of een groot model. De huidige router kiest 70% van de tijd het kleine model en 30% van de tijd het grote, willekeurig. Je hebt een nieuwe regel geschreven: technische tickets, en lange factuurtickets, gaan naar het grote model. Je kunt hem nog niet op echte tickets uitproberen, maar de log van de oude router kan voor hem antwoorden. Dat is off-policy-evaluatie.

De log en zijn bestanden

BestandWat het is
data/decisions.jsonl600 beslissingen die de huidige router nam
make_log.pyDe simulatie die die log schreef, zodat je kunt zien hoe hij gemaakt is
router.pyDe nieuwe regel, het geteste systeem
oloproof.yamlDe suite
release.yamlDe releasepolicy

Elke regel van de log is één beslissing:

{"id":"t001","input":{"topic":"technical","length":"short"},"expected":{"action":"small","logging_probabilities":{"small":0.7,"large":0.3},"reward":1}}

input is wat de router zag, action wat hij koos, logging_probabilities de kans die hij op dat moment aan elk model gaf, en reward of het ticket werd opgelost. De nieuwe regel antwoordt alleen met zijn eigen kansen:

@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}}

De evaluators:

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}

Elke gelogde beslissing wordt herwogen naar hoeveel waarschijnlijker, of minder waarschijnlijk, de nieuwe regel haar zou nemen dan de oude router: een ticket dat de oude router met kans 0.3 naar het grote model stuurde, en dat de nieuwe regel er altijd heen zou sturen, telt 1 / 0.3 keer, en een beslissing die de nieuwe regel nooit genomen zou hebben, telt nul keer. propensity_floor is de kleinste kans die de oude router gaf aan een model dat hij kon kiezen. Die begrenst dat gewicht, en zonder grens geldt geen enkel interval.

Draai het

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 │

De nieuwe regel zou naar schatting 77.5% van de tickets oplossen en, met 95% betrouwbaarheid, minstens 63.2%: de ondergrens van 60% slaagt. Of hij beter is dan de huidige router, is niet beslist. De schatting is 6 punten beter, maar het interval loopt van 6.4 punten slechter tot 18.8 beter, en de engine zegt ongeveer hoeveel gelogde beslissingen meer het zouden beslissen.

Die breedte is de prijs van leren van de keuzes van een ander. Alleen de 30% van de tickets die de oude router toevallig naar het grote model stuurde, zegt iets over het grote model, en elk ervan telt meer dan drie keer. Twee dingen maken het smaller: een langere log, en een oude router die meer verkent.

Beslissingen loggen vanuit je eigen systeem

  • Leg de kansen vast wanneer de router beslist, niet achteraf, en leg de kans van elke optie vast, niet alleen die van de gekozen optie. Oloproof schat ze niet: een geraden kans zou het interval op de gok laten rusten.
  • Zet in input alleen wat de router zag toen hij besliste, nooit de gekozen optie of de uitkomst: de geteste policy mag die niet zien.
  • Als je nieuwe policy haar keuze trekt, geef dan de kansen terug waaruit ze trekt, niet de ene optie die ze trok. Noem elke optie precies zoals de log dat doet: een optie die anders gespeld is, telt als een optie die de oude router nooit koos.
  • Houd wat willekeur in de routering in productie. Een policy die een optie nooit kiest, laat niets in de log achter om een regel te beoordelen die dat wel zou doen.
  • Het antwoord beschrijft tickets zoals de gelogde, over de periode die de log beslaat. Een verandering in je verkeer valt erbuiten.
  • Een misvormde regel (kansen die samen geen 1 zijn, een gekozen optie met kans 0, een beloning buiten reward_range) stopt de run. Een beslissing die de nieuwe regel zou nemen met een optie waaraan de oude router minder dan de ondergrens gaf, of waarvan de beloning ontbreekt, telt als ontbrekend en maakt het interval breder.

Wat niet gebouwd is

  • Off-policy-evaluatie van een reeks beslissingen, waarbij de keuze bij elke stap verandert wat er daarna komt. Alleen één beslissing per case wordt ondersteund.
  • Een log waarvan de router zijn kansen veranderde terwijl hij leerde tijdens de periode.
  • Het hosten of resetten van omgevingen, simulators of robots.

Verder lezen