Przejdź do treści

Przewodniki

Samouczek: ewaluacja polityki uczenia ze wzmocnieniem

Praktyczny przewodnik do uruchomienia dla dwóch rodzajów pytań z uczenia ze wzmocnieniem: jak dobrze polityka, którą możesz uruchomić, radzi sobie w epizodach, oraz jak dobrze poradziłaby sobie polityka, której nie możesz wypróbować w świecie, oceniana na podstawie logu polityki, którą miałaby zastąpić. Oba przykłady działają lokalnie, bez poświadczeń dostawcy.

Dokumentacją każdego pola i ewaluatora jest Uczenie ze wzmocnieniem; terminy przypadek, ewaluator, metryka, przedział i bramka są w Podstawowych pojęciach. Ta strona to praktyczna droga przez nie.

Co Oloproof tutaj robi, a czego nie

Oloproof nie uruchamia Twojego środowiska, nie hostuje symulatora ani nie trenuje polityki. Twój system uruchamia każdy epizod we własnym środowisku i zapisuje go jako artefakt rl_episode/v1, a każda metryka epizodu jest odczytywana z tego rekordu. W drugiej części nic się w ogóle nie uruchamia: testowana polityka podaje tylko prawdopodobieństwo, jakie nadałaby każdej akcji.

Trzymaj te dwa pytania osobno:

PytanieCo maszCzęść
Jak dobrze ta polityka radzi sobie w epizodach?Politykę i środowisko, które możesz uruchomić1
Jak dobrze ta polityka poradziłaby sobie na produkcji?Log decyzji podjętych przez starszą politykę, z prawdopodobieństwami, których użyła2

Wymagania wstępne

  • Python 3.11 lub nowszy i zainstalowany Oloproof (pip install oloproof).
  • Przykładowy projekt, dostarczany z pakietem. Skopiuj go do nowego katalogu:
oloproof init --example rl my-rl

Zawiera dwa projekty, episodes/ i off_policy/. Każde polecenie poniżej uruchamia się z wnętrza jednego z nich, a dowody są zapisywane w jego katalogu .oloproof/.

Część 1: polityka, którą możesz uruchomić

cd my-rl/episodes

Pliki

PlikCzym jest
env.pyŚwiat siatki, dwie polityki (wander i plan) oraz systemy, które je uruchamiają
data/tasks.jsonl24 siatki, każda jest zadaniem: ściany, zagrożenia, start i cel
oloproof.yamlZestaw dla polityki bazowej, wander
candidate.yamlTen sam zestaw dla polityki kandydującej, plan
release.yamlPolityka wydań
comparison.yamlReguły, które kandydat musi spełnić względem punktu odniesienia

Epizod przesuwa się o jedno pole na krok, aż osiągnie cel lub wyczerpie kroki. Każdy krok daje -1, a osiągnięcie celu daje +10, więc zwrot leży między -30 (30 kroków, bez celu) a 10. Wejście na zagrożenie kosztuje 1: to jest ograniczenie bezpieczeństwa. Jeden ruch na dziesięć ześlizguje się w losowym kierunku, niezależnie od wyboru polityki, więc ta sama polityka może radzić sobie różnie na tej samej siatce.

Zapisywanie epizodu

System sam uruchamia epizod i przekazuje rekord rejestratorowi przypadku:

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

Rekord wymienia nagrodę i koszt każdego kroku, to, czy epizod osiągnął cel (success), i to, czy zakończył go limit kroków (truncated).

Kilka epizodów na zadanie

oloproof.yaml deklaruje replicates: 5, więc każde zadanie uruchamia pięć epizodów. current_case().replicate mówi który (od 0 do 4), a środowisko ustawia na jego podstawie ziarno epizodu: ten sam epizod tego samego zadania zawsze odtwarza się tak samo, a drugi przebieg ponownie używa zapisanych epizodów zamiast uruchamiać je jeszcze raz.

Jednostką pozostaje zadanie. Każde kryterium to średnia, po 24 zadaniach, z własnej średniej każdego zadania po jego pięciu epizodach, więc przedział liczy 24 zadania, a nie 120 epizodów. Liczenie epizodów udawałoby, że pięć epizodów jednej siatki to pięć niezależnych siatek.

Ewaluatory:

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 jest wymagane: przedział dla średniego zwrotu obowiązuje tylko w zakresie, w którym leży każdy zwrot, a zwrot spoza niego zatrzymuje przebieg. solved_every_time liczy zadanie tylko wtedy, gdy wszystkie pięć jego epizodów osiągnęło cel.

Uruchom politykę bazową

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

Czytaj wiersz po wierszu:

  • Błądząca polityka osiąga cel w 76.7% epizodów, ale przedział sięga od 55.4% do 89.6%, więc próg 75% nie jest ani osiągnięty, ani chybiony: INSUFFICIENT_EVIDENCE.
  • Rozwiązuje tylko 9 z 24 zadań w każdym z pięciu epizodów. "Zwykle działa" i "działa za każdym razem" to różne twierdzenia, a agent, który będzie działał bez nadzoru, potrzebuje drugiego.
  • 14 zadań jest niestabilnych: ich sukces zmieniał się między identycznymi epizodami. Jeden epizod na zadanie nie mógłby tego pokazać.
  • Unika zagrożeń w 29.2% epizodów. Górna granica przedziału, 54.0%, jest powyżej progu 50%, więc nawet ten słaby wynik nie dowodzi jeszcze, że polityka jest niebezpieczna.

Spójrz na jedno zadanie:

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

Pierwszy epizod grid_03 wyczerpał kroki: zwrot -30, bez celu, bez wejścia na zagrożenie.

Uruchom kandydata

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            │

Planista osiąga cel w każdym epizodzie, więc próg sukcesu jest zaliczony. Nadal wchodzi na zagrożenia: niektóre siatki nie mają bezpiecznej ścieżki, a śliska podłoga spycha go na inne. Jego wskaźnik bezpieczeństwa, 57.5%, jest powyżej progu, ale 24 zadania nie mogą jeszcze pokazać, że przekracza 50%.

Porównaj dwie polityki

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)

Porównanie paruje każde zadanie z nim samym, a każda strona jest warta swojej średniej po pięciu epizodach. Różnica zwrotów jest we własnej jednostce zwrotu: planista zdobywa około 10.9 więcej na epizod, a co najmniej 0.21 więcej. Przy parowaniu na tych samych siatkach margines 5 punktów wystarcza, by pokazać, że planista nie jest mniej bezpieczny, choć żaden przebieg osobno nie mógł rozstrzygnąć własnego progu bezpieczeństwa.

Część 2: polityka oceniana na podstawie logu

cd ../off_policy

Dział wsparcia kieruje każde zgłoszenie do małego lub dużego modelu. Obecny router wybiera mały model w 70% przypadków, a duży w 30%, losowo. Napisałeś nową regułę: zgłoszenia techniczne oraz długie zgłoszenia rozliczeniowe trafiają do dużego modelu. Nie możesz jeszcze wypróbować jej na prawdziwych zgłoszeniach, ale log starego routera może odpowiedzieć za nią. To jest ewaluacja off-policy.

Log i jego pliki

PlikCzym jest
data/decisions.jsonl600 decyzji podjętych przez obecny router
make_log.pySymulacja, która zapisała ten log, abyś mógł zobaczyć, jak powstał
router.pyNowa reguła, testowany system
oloproof.yamlZestaw
release.yamlPolityka wydań

Każdy wiersz logu to jedna decyzja:

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

input to to, co widział router, action to, co wybrał, logging_probabilities to prawdopodobieństwo, jakie nadał każdemu modelowi w tamtej chwili, a reward to, czy zgłoszenie zostało rozwiązane. Nowa reguła odpowiada tylko własnymi prawdopodobieństwami:

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

Ewaluatory:

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}

Każda zarejestrowana decyzja jest przeważana według tego, o ile bardziej lub mniej prawdopodobne było, że podejmie ją nowa reguła niż stary router: zgłoszenie, które stary router wysłał do dużego modelu z prawdopodobieństwem 0.3, a które nowa reguła zawsze by tam wysłała, liczy się 1 / 0.3 razy, a decyzja, której nowa reguła nigdy by nie podjęła, liczy się zero razy. propensity_floor to najmniejsze prawdopodobieństwo, jakie stary router nadał dowolnemu modelowi, który mógł wybrać. Ogranicza tę wagę, a bez ograniczenia żaden przedział nie obowiązuje.

Uruchom

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 │

Nowa reguła rozwiązałaby szacunkowo 77.5% zgłoszeń, a przy 95% ufności co najmniej 63.2%: próg 60% jest zaliczony. Czy przewyższa obecny router, nie jest rozstrzygnięte. Estymata jest o 6 punktów lepsza, ale przedział sięga od 6.4 punktu gorzej do 18.8 lepiej, a silnik podaje mniej więcej, ile więcej zarejestrowanych decyzji by to rozstrzygnęło.

Ta szerokość to cena uczenia się z cudzych wyborów. Tylko 30% zgłoszeń, które stary router akurat wysłał do dużego modelu, mówi cokolwiek o dużym modelu, a każde liczy się ponad trzy razy. Zawężają go dwie rzeczy: dłuższy log i stary router, który więcej eksploruje.

Rejestrowanie decyzji z własnego systemu

  • Zapisuj prawdopodobieństwa w chwili, gdy router podejmuje decyzję, a nie później, i zapisuj prawdopodobieństwo każdej opcji, nie tylko wybranej. Oloproof ich nie szacuje: zgadnięte prawdopodobieństwo sprawiłoby, że przedział opierałby się na zgadywaniu.
  • Umieszczaj w input tylko to, co router widział, gdy podejmował decyzję, nigdy wybranej opcji ani wyniku: testowana polityka nie może ich widzieć.
  • Jeśli Twoja nowa polityka losuje swój wybór, zwracaj prawdopodobieństwa, z których losuje, a nie jedną wylosowaną opcję. Nazywaj każdą opcję dokładnie tak, jak robi to log: opcja zapisana inaczej liczy się jako taka, której stary router nigdy nie wybrał.
  • Zachowaj trochę losowości w routingu produkcyjnym. Polityka, która nigdy nie wybiera danej opcji, nie zostawia w logu niczego, na podstawie czego można by ocenić regułę, która by ją wybierała.
  • Odpowiedź opisuje zgłoszenia podobne do zarejestrowanych, w okresie, który obejmuje log. Zmiana w Twoim ruchu leży poza nią.
  • Błędnie sformatowany wiersz (prawdopodobieństwa, które nie sumują się do 1, wybrana opcja z prawdopodobieństwem 0, nagroda spoza reward_range) zatrzymuje przebieg. Decyzja, którą nowa reguła podjęłaby z opcją, której stary router nadał mniej niż próg, albo której nagrody brakuje, liczy się jako brakująca i poszerza przedział.

Czego nie zbudowano

  • Ewaluacji off-policy sekwencji decyzji, w której wybór na każdym kroku zmienia to, co następuje dalej. Obsługiwana jest tylko jedna decyzja na przypadek.
  • Logu, którego router zmieniał swoje prawdopodobieństwa, ucząc się w trakcie objętego nim okresu.
  • Hostowania ani resetowania środowisk, symulatorów czy robotów.

Dokąd dalej