Vai al contenuto

Guide

Tutorial: valutare una policy di apprendimento per rinforzo

Un percorso eseguibile per due tipi di domanda sull'apprendimento per rinforzo: quanto bene si comporta, sugli episodi, una policy che puoi eseguire, e quanto bene avrebbe fatto una policy che non puoi provare nel mondo reale, giudicata a partire dal log della policy che sostituirebbe. Entrambi gli esempi girano in locale senza credenziali di un provider.

Il riferimento per ogni campo e valutatore è Apprendimento per rinforzo; i termini caso, valutatore, metrica, intervallo e gate sono in Concetti fondamentali. Questa pagina è il percorso pratico attraverso di essi.

Che cosa fa e che cosa non fa Oloproof qui

Oloproof non esegue il tuo ambiente, non ospita un simulatore e non addestra una policy. Il tuo sistema esegue ogni episodio nel proprio ambiente e lo registra come artefatto rl_episode/v1, e ogni metrica di episodio viene letta da quel record. Nella seconda parte non viene eseguito proprio nulla: la policy sotto test dichiara soltanto la probabilità che darebbe a ogni azione.

Tieni separate le due domande:

DomandaChe cosa haiParte
Quanto bene si comporta questa policy sugli episodi?Una policy e un ambiente che puoi eseguire1
Quanto bene avrebbe fatto questa policy in produzione?Un log delle decisioni prese da una policy precedente, con le probabilità che ha usato2

Prerequisiti

  • Python 3.11 o successivo, e Oloproof installato (pip install oloproof).
  • Il progetto di esempio, distribuito con il pacchetto. Copialo in una nuova directory:
oloproof init --example rl my-rl

Contiene due progetti, episodes/ e off_policy/. Ogni comando qui sotto si esegue dall'interno di uno dei due, e l'evidenza viene memorizzata nella sua directory .oloproof/.

Parte 1: una policy che puoi eseguire

cd my-rl/episodes

I file

FileChe cos'è
env.pyUn mondo a griglia, due policy (wander e plan) e i sistemi che le eseguono
data/tasks.jsonl24 griglie, ciascuna un'attività: muri, pericoli, una partenza e un obiettivo
oloproof.yamlLa suite per la policy baseline, wander
candidate.yamlLa stessa suite per la policy candidata, plan
release.yamlLa policy di rilascio
comparison.yamlLe regole che un candidato deve rispettare rispetto alla baseline

Un episodio si sposta di una cella per passo finché non raggiunge l'obiettivo o esaurisce i passi. Ogni passo vale -1 e raggiungere l'obiettivo vale +10, quindi un ritorno sta tra -30 (30 passi, nessun obiettivo) e 10. Entrare in un pericolo costa 1: questo è il vincolo di sicurezza. Una mossa su dieci scivola in una direzione casuale, qualunque cosa abbia scelto la policy, quindi la stessa policy può comportarsi diversamente sulla stessa griglia.

Registrare un episodio

Il sistema esegue l'episodio da sé e consegna il record al registratore del caso:

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

Il record elenca la ricompensa e il costo di ogni passo, se l'episodio ha raggiunto l'obiettivo (success) e se il limite di passi lo ha terminato (truncated).

Più episodi per attività

oloproof.yaml dichiara replicates: 5, quindi ogni attività esegue cinque episodi. current_case().replicate dice quale (da 0 a 4), e l'ambiente inizializza il seed dell'episodio a partire da esso: lo stesso episodio della stessa attività si riproduce sempre allo stesso modo, e una seconda esecuzione riusa gli episodi memorizzati invece di eseguirli di nuovo.

L'attività resta l'unità. Ogni criterio è la media, sulle 24 attività, della media di ciascuna attività sui suoi cinque episodi, quindi l'intervallo conta 24 attività e non 120 episodi. Contare gli episodi significherebbe fingere che cinque episodi di una sola griglia siano cinque griglie indipendenti.

I valutatori:

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 è obbligatorio: l'intervallo su un ritorno medio vale solo entro un intervallo in cui si trova ogni ritorno, e un ritorno che ne esce arresta l'esecuzione. solved_every_time conta un'attività solo quando tutti e cinque i suoi episodi hanno raggiunto l'obiettivo.

Esegui la baseline

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

Leggilo riga per riga:

  • La policy che vaga raggiunge l'obiettivo nel 76,7% degli episodi, ma l'intervallo va da 55,4% a 89,6%, quindi la soglia minima del 75% non è né superata né mancata: INSUFFICIENT_EVIDENCE.
  • Risolve solo 9 delle 24 attività in tutti e cinque i suoi episodi. "Di solito funziona" e "funziona ogni volta" sono affermazioni diverse, e un agente che girerà senza supervisione ha bisogno della seconda.
  • 14 attività sono instabili: il loro successo è cambiato tra episodi identici. Un episodio per attività non avrebbe potuto mostrarlo.
  • Evita i pericoli nel 29,2% degli episodi. L'estremo superiore dell'intervallo, 54,0%, è sopra la soglia minima del 50%, quindi nemmeno questo pessimo risultato dimostra ancora che la policy non è sicura.

Guarda un'attività:

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

Il primo episodio di grid_03 ha esaurito i passi: ritorno -30, nessun obiettivo, nessun pericolo attraversato.

Esegui il candidato

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            │

Il pianificatore raggiunge l'obiettivo in ogni episodio, quindi la soglia minima di successo è in PASS. Entra comunque nei pericoli: alcune griglie non hanno un percorso sicuro, e il pavimento scivoloso lo spinge in altri. Il suo tasso di sicurezza del 57,5% è sopra la soglia minima, ma 24 attività non possono ancora mostrare che sia sopra il 50%.

Confronta le due policy

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)

Il confronto appaia ogni attività con sé stessa, e ciascun lato vale la sua media su cinque episodi. La differenza di ritorno è nell'unità del ritorno stesso: il pianificatore guadagna circa 10,9 in più per episodio, e almeno 0,21 in più. Appaiato sulle stesse griglie, un margine di 5 punti basta a mostrare che il pianificatore non è meno sicuro, anche se nessuna delle due esecuzioni da sola poteva risolvere la propria soglia minima di sicurezza.

Parte 2: una policy giudicata a partire da un log

cd ../off_policy

Un servizio di assistenza instrada ogni ticket verso un modello piccolo o uno grande. Il router attuale sceglie il modello piccolo il 70% delle volte e quello grande il 30%, a caso. Hai scritto una nuova regola: i ticket tecnici, e i ticket di fatturazione lunghi, vanno al modello grande. Non puoi ancora provarla su ticket reali, ma il log del vecchio router può rispondere al suo posto. Questa è la valutazione off-policy.

Il log e i suoi file

FileChe cos'è
data/decisions.jsonl600 decisioni prese dal router attuale
make_log.pyLa simulazione che ha scritto quel log, così puoi vedere come è stato prodotto
router.pyLa nuova regola, il sistema sotto test
oloproof.yamlLa suite
release.yamlLa policy di rilascio

Ogni riga del log è una decisione:

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

input è ciò che il router ha visto, action ciò che ha scelto, logging_probabilities la probabilità che dava a ogni modello in quel momento, e reward se il ticket è stato risolto. La nuova regola risponde soltanto con le proprie probabilità:

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

I valutatori:

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}

Ogni decisione registrata viene ripesata per quanto la nuova regola era più, o meno, propensa a prenderla rispetto al vecchio router: un ticket che il vecchio router ha mandato al modello grande con probabilità 0,3, e che la nuova regola manderebbe sempre lì, conta 1 / 0,3 volte, e una decisione che la nuova regola non avrebbe mai preso conta zero. propensity_floor è la più piccola probabilità che il vecchio router dava a un qualsiasi modello che poteva scegliere. Limita quel peso, e senza un limite nessun intervallo regge.

Eseguila

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 nuova regola risolverebbe circa il 77,5% dei ticket secondo la stima e, con il 95% di confidenza, almeno il 63,2%: la soglia minima del 60% è in PASS. Se batta il router attuale non è stabilito. La stima è migliore di 6 punti, ma l'intervallo va da 6,4 punti peggio a 18,8 meglio, e il motore dice all'incirca quante altre decisioni registrate lo deciderebbero.

Quell'ampiezza è il prezzo di imparare dalle scelte di qualcun altro. Solo il 30% dei ticket che il vecchio router si è trovato a mandare al modello grande dice qualcosa sul modello grande, e ciascuno conta più di tre volte. Due cose lo restringono: un log più lungo, e un vecchio router che esplora di più.

Registrare decisioni dal tuo sistema

  • Registra le probabilità quando il router decide, non dopo, e registra la probabilità di ogni opzione, non solo di quella scelta. Oloproof non le stima: una probabilità indovinata farebbe poggiare l'intervallo su quella supposizione.
  • Metti in input solo ciò che il router vedeva quando ha deciso, mai l'opzione scelta né l'esito: la policy sotto test non deve vederli.
  • Se la tua nuova policy campiona la propria scelta, restituisci le probabilità da cui campiona, non l'unica opzione che ha estratto. Nomina ogni opzione esattamente come fa il log: un'opzione scritta in modo diverso conta come una che il vecchio router non ha mai scelto.
  • Mantieni un po' di casualità nell'instradamento in produzione. Una policy che non sceglie mai un'opzione non lascia nulla nel log per giudicare una regola che la sceglierebbe.
  • La risposta descrive ticket simili a quelli registrati, nel periodo coperto dal log. Un cambiamento nel tuo traffico ne resta fuori.
  • Una riga malformata (probabilità la cui somma non è 1, un'opzione scelta con probabilità 0, una ricompensa fuori da reward_range) arresta l'esecuzione. Una decisione che la nuova regola prenderebbe con un'opzione a cui il vecchio router dava meno della soglia, o la cui ricompensa manca, conta come mancante e allarga l'intervallo.

Che cosa non è costruito

  • La valutazione off-policy di una sequenza di decisioni, in cui la scelta di ogni passo cambia ciò che viene dopo. È supportata solo una decisione per caso.
  • Un log il cui router ha cambiato le proprie probabilità mentre imparava durante l'arco di tempo.
  • Ospitare o reimpostare ambienti, simulatori o robot.

Dove andare dopo