Zum Inhalt springen

Anleitungen

Tutorial: eine Reinforcement-Learning-Policy evaluieren

Ein ausführbarer Rundgang für zwei Arten von Reinforcement-Learning-Fragen: wie gut eine Policy, die Sie ausführen können, über Episoden abschneidet, und wie gut eine Policy, die Sie in der realen Welt nicht ausprobieren können, abgeschnitten hätte, beurteilt anhand des Protokolls der Policy, die sie ersetzen würde. Beide Beispiele laufen lokal ohne Provider-Zugangsdaten.

Die Referenz für jedes Feld und jeden Evaluator ist Reinforcement Learning; die Begriffe Fall, Evaluator, Metrik, Intervall und Gate stehen in Kernkonzepte. Diese Seite ist der praktische Weg durch sie hindurch.

Was Oloproof hier tut und was nicht

Oloproof betreibt Ihre Umgebung nicht, hostet keinen Simulator und trainiert keine Policy. Ihr System führt jede Episode in seiner eigenen Umgebung aus und zeichnet sie als rl_episode/v1-Artefakt auf, und jede Episodenmetrik wird aus diesem Datensatz gelesen. Im zweiten Teil wird überhaupt nichts ausgeführt: Die getestete Policy gibt nur die Wahrscheinlichkeit an, die sie jeder Aktion geben würde.

Halten Sie die beiden Fragen auseinander:

FrageWas Sie habenTeil
Wie gut schneidet diese Policy über Episoden ab?Eine Policy und eine Umgebung, die Sie ausführen können1
Wie gut hätte diese Policy in Produktion abgeschnitten?Ein Protokoll der Entscheidungen einer älteren Policy, mit den Wahrscheinlichkeiten, die sie verwendet hat2

Voraussetzungen

  • Python 3.11 oder neuer und Oloproof installiert (pip install oloproof).
  • Das Beispielprojekt, das mit dem Paket ausgeliefert wird. Kopieren Sie es in ein neues Verzeichnis:
oloproof init --example rl my-rl

Es enthält zwei Projekte, episodes/ und off_policy/. Jeder Befehl unten läuft in einem von ihnen, und die Evidenz wird in dessen .oloproof/-Verzeichnis gespeichert.

Teil 1: eine Policy, die Sie ausführen können

cd my-rl/episodes

Die Dateien

DateiWas sie ist
env.pyEine Gitterwelt, zwei Policies (wander und plan) und die Systeme, die sie ausführen
data/tasks.jsonl24 Gitter, jedes eine Aufgabe: Wände, Gefahrenfelder, ein Start und ein Ziel
oloproof.yamlDie Suite für die Baseline-Policy, wander
candidate.yamlDieselbe Suite für die Kandidaten-Policy, plan
release.yamlDie Release-Policy
comparison.yamlDie Regeln, die ein Kandidat gegenüber der Baseline erfüllen muss

Eine Episode bewegt sich pro Schritt um eine Zelle, bis sie das Ziel erreicht oder ihr die Schritte ausgehen. Jeder Schritt bringt -1 und das Erreichen des Ziels +10, daher liegt ein Return zwischen -30 (30 Schritte, kein Ziel) und 10. Das Betreten eines Gefahrenfelds kostet 1: Das ist die Sicherheits-Constraint. Einer von zehn Zügen rutscht in eine zufällige Richtung, was immer die Policy gewählt hat, daher kann dieselbe Policy auf demselben Gitter unterschiedlich abschneiden.

Eine Episode aufzeichnen

Das System führt die Episode selbst aus und übergibt den Datensatz an den Recorder des Falls:

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

Der Datensatz führt Belohnung und Kosten jedes Schritts auf, ob die Episode das Ziel erreicht hat (success) und ob das Schrittlimit sie beendet hat (truncated).

Mehrere Episoden pro Aufgabe

oloproof.yaml deklariert replicates: 5, daher führt jede Aufgabe fünf Episoden aus. current_case().replicate sagt, welche (0 bis 4), und die Umgebung leitet den Seed der Episode daraus ab: Dieselbe Episode derselben Aufgabe spielt sich immer gleich ab, und ein zweiter Lauf verwendet die gespeicherten Episoden wieder, statt sie erneut auszuführen.

Die Aufgabe bleibt die Einheit. Jedes Kriterium ist der Mittelwert über die 24 Aufgaben des Mittelwerts jeder Aufgabe über ihre fünf Episoden, daher zählt das Intervall 24 Aufgaben und nicht 120 Episoden. Episoden zu zählen hieße so zu tun, als wären fünf Episoden eines Gitters fünf unabhängige Gitter.

Die Evaluatoren:

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 ist erforderlich: Das Intervall um einen mittleren Return gilt nur innerhalb eines Bereichs, in dem jeder Return liegt, und ein Return außerhalb davon stoppt den Lauf. solved_every_time zählt eine Aufgabe nur, wenn alle fünf ihrer Episoden das Ziel erreicht haben.

Die Baseline ausführen

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

Lesen Sie es Zeile für Zeile:

  • Die umherirrende Policy erreicht das Ziel in 76,7 % der Episoden, aber das Intervall reicht von 55,4 % bis 89,6 %, daher wird die Untergrenze von 75 % weder überschritten noch verfehlt: INSUFFICIENT_EVIDENCE.
  • Sie löst nur 9 der 24 Aufgaben in jeder ihrer fünf Episoden. „Funktioniert meistens“ und „funktioniert jedes Mal“ sind verschiedene Aussagen, und ein Agent, der unbeaufsichtigt laufen soll, braucht die zweite.
  • 14 Aufgaben sind instabil: Ihr Erfolg wechselte zwischen identischen Episoden. Eine Episode pro Aufgabe hätte das nicht zeigen können.
  • Sie meidet Gefahrenfelder in 29,2 % der Episoden. Die Obergrenze des Intervalls, 54,0 %, liegt über der Untergrenze von 50 %, daher beweist selbst diese schwache Bilanz noch nicht, dass die Policy unsicher ist.

Sehen Sie sich eine Aufgabe an:

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

Die erste Episode von grid_03 lief aus den Schritten heraus: Return -30, kein Ziel, kein Gefahrenfeld betreten.

Den Kandidaten ausführen

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            │

Der Planer erreicht das Ziel in jeder Episode, daher besteht die Erfolgsuntergrenze. Er betritt trotzdem Gefahrenfelder: Manche Gitter haben keinen sicheren Weg, und der rutschige Boden schiebt ihn in andere. Seine Sicherheitsrate von 57,5 % liegt über der Untergrenze, aber 24 Aufgaben können noch nicht zeigen, dass sie über 50 % liegt.

Die beiden Policies vergleichen

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)

Der Vergleich paart jede Aufgabe mit sich selbst, wobei jede Seite so viel zählt wie ihr Mittelwert über fünf Episoden. Die Return-Differenz steht in der eigenen Einheit des Returns: Der Planer erzielt pro Episode etwa 10,9 mehr, und mindestens 0,21 mehr. Gepaart auf denselben Gittern genügt eine Marge von 5 Punkten, um zu zeigen, dass der Planer nicht weniger sicher ist, obwohl keiner der beiden Läufe allein seine eigene Sicherheitsuntergrenze klären konnte.

Teil 2: eine Policy, anhand eines Protokolls beurteilt

cd ../off_policy

Ein Support-Desk leitet jedes Ticket an ein kleines oder ein großes Modell weiter. Der aktuelle Router wählt in 70 % der Fälle das kleine Modell und in 30 % das große, zufällig. Sie haben eine neue Regel geschrieben: Technische Tickets und lange Abrechnungstickets gehen an das große Modell. Sie können sie noch nicht an echten Tickets ausprobieren, aber das Protokoll des alten Routers kann für sie antworten. Das ist Off-Policy-Evaluierung.

Das Protokoll und seine Dateien

DateiWas sie ist
data/decisions.jsonl600 Entscheidungen, die der aktuelle Router getroffen hat
make_log.pyDie Simulation, die dieses Protokoll geschrieben hat, damit Sie sehen, wie es entstanden ist
router.pyDie neue Regel, das getestete System
oloproof.yamlDie Suite
release.yamlDie Release-Policy

Jede Zeile des Protokolls ist eine Entscheidung:

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

input ist, was der Router sah, action, was er wählte, logging_probabilities die Wahrscheinlichkeit, die er in diesem Moment jedem Modell gab, und reward, ob das Ticket gelöst wurde. Die neue Regel antwortet nur mit ihren eigenen Wahrscheinlichkeiten:

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

Die Evaluatoren:

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}

Jede protokollierte Entscheidung wird danach neu gewichtet, um wie viel wahrscheinlicher oder unwahrscheinlicher die neue Regel sie getroffen hätte als der alte Router: Ein Ticket, das der alte Router mit Wahrscheinlichkeit 0,3 an das große Modell schickte und das die neue Regel immer dorthin schicken würde, zählt 1 / 0,3 Mal, und eine Entscheidung, die die neue Regel nie getroffen hätte, zählt null. propensity_floor ist die kleinste Wahrscheinlichkeit, die der alte Router irgendeinem Modell gab, das er wählen konnte. Sie beschränkt dieses Gewicht, und ohne eine Schranke gilt kein Intervall.

Ausführen

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 │

Die neue Regel würde geschätzt 77,5 % der Tickets lösen und, mit 95 % Konfidenz, mindestens 63,2 %: Die Untergrenze von 60 % besteht. Ob sie den aktuellen Router schlägt, ist nicht geklärt. Die Schätzung ist 6 Punkte besser, aber das Intervall reicht von 6,4 Punkten schlechter bis 18,8 besser, und die Engine sagt ungefähr, wie viele protokollierte Entscheidungen mehr es entscheiden würden.

Diese Breite ist der Preis dafür, aus den Entscheidungen eines anderen zu lernen. Nur die 30 % der Tickets, die der alte Router zufällig an das große Modell geschickt hat, sagen etwas über das große Modell aus, und jedes zählt mehr als dreimal. Zwei Dinge machen das Intervall schmaler: ein längeres Protokoll und ein alter Router, der mehr exploriert.

Entscheidungen aus Ihrem eigenen System protokollieren

  • Zeichnen Sie die Wahrscheinlichkeiten auf, wenn der Router entscheidet, nicht danach, und zeichnen Sie die Wahrscheinlichkeit jeder Option auf, nicht nur die der gewählten. Oloproof schätzt sie nicht: Eine geratene Wahrscheinlichkeit würde das Intervall auf die Vermutung stützen.
  • Legen Sie in input nur, was der Router sah, als er entschied, nie die gewählte Option oder das Ergebnis: Die getestete Policy darf sie nicht sehen.
  • Wenn Ihre neue Policy ihre Wahl zufällig zieht, geben Sie die Wahrscheinlichkeiten zurück, aus denen sie zieht, nicht die eine gezogene Option. Benennen Sie jede Option genau so wie das Protokoll: Eine anders geschriebene Option zählt als eine, die der alte Router nie gewählt hat.
  • Behalten Sie etwas Zufall im produktiven Routing. Eine Policy, die eine Option nie wählt, hinterlässt im Protokoll nichts, um eine Regel zu beurteilen, die sie wählen würde.
  • Die Antwort beschreibt Tickets wie die protokollierten, über den Zeitraum, den das Protokoll abdeckt. Eine Veränderung Ihres Traffics liegt außerhalb davon.
  • Eine fehlerhafte Zeile (Wahrscheinlichkeiten, die sich nicht zu 1 summieren, eine gewählte Option mit Wahrscheinlichkeit 0, eine Belohnung außerhalb von reward_range) stoppt den Lauf. Eine Entscheidung, die die neue Regel mit einer Option treffen würde, der der alte Router weniger als die Untergrenze gab, oder deren Belohnung fehlt, zählt als fehlend und macht das Intervall breiter.

Was nicht gebaut ist

  • Off-Policy-Evaluierung einer Folge von Entscheidungen, bei der die Wahl jedes Schritts verändert, was als Nächstes kommt. Nur eine Entscheidung pro Fall wird unterstützt.
  • Ein Protokoll, dessen Router seine Wahrscheinlichkeiten änderte, während er im Zeitfenster lernte.
  • Das Hosten oder Zurücksetzen von Umgebungen, Simulatoren oder Robotern.

Wie es weitergeht