Przewodniki
Samouczek: klasyfikator i regresor
Praktyczny przewodnik do uruchomienia dla trzech modeli predykcyjnych ocenianych z wiersza poleceń: binarnego klasyfikatora odejść klientów, trzyklasowego routera zgłoszeń ocenianego po jednej klasie naraz oraz regresora czasu dostawy ocenianego błędem bezwzględnym w zadeklarowanym zakresie. Każdy działa lokalnie, bez dostawcy, bez klucza i bez sieci, a każdy kończy się zmianą kandydującą zmierzoną względem punktu odniesienia.
Ta strona to praktyczne uzupełnienie Klasyfikatorów i regresorów, które objaśniają każde pole; czytaj tamtą stronę jako dokumentację, a tę, by raz przejść wszystko od początku do końca. Terminy takie jak przedział, stan decyzji i działanie dotyczące wydania są zdefiniowane w Pojęciach.
Co Oloproof mierzy dla modelu predykcyjnego, a czego nie
| Model | Co dostajesz | Czego nie dostajesz |
|---|---|---|
| Klasyfikator binarny | trafność, czułość, precyzję, wynik Briera, log loss, ROC-AUC i średnią precyzję, a także liczności pomyłek, tabelę kalibracji i przegląd progów | rekomendowanego progu |
| Klasyfikator wieloklasowy | jedną czułość i jedną precyzję na klasę, każda jako metryka binarna, której klasą pozytywną jest ta klasa (jedna przeciw reszcie) | metryki uśrednionej makro lub mikro |
| Regresor | średni błąd bezwzględny, ograniczony przez zadeklarowany target_range | błędu kwadratowego, R kwadrat ani żadnego błędu bez zadeklarowanego zakresu |
Sam model pozostaje Twój. Oloproof wywołuje funkcję w Pythonie, na którą go kierujesz, czyta zwróconą predykcję i nigdy nie widzi cech, wag ani wnętrza modelu.
Wymagania wstępne
- Python 3.11 lub nowszy oraz pip install oloproof w środowisku wirtualnym, jak w szybkim starcie.
- Przykładowe pliki, dostarczane z pakietem. Skopiuj je do nowego katalogu, aby magazyny przebiegów trafiły tam:
oloproof init --example predictive ~/oloproof-predictive
cd ~/oloproof-predictiveKażde polecenie poniżej uruchamia się z jednego z trzech podkatalogów. Każdy przebieg zapisuje swoje dowody w katalogu .oloproof/ obok oloproof.yaml, który przeczytał.
predictive/
binary/ app.py oloproof.yaml candidate.yaml release.yaml comparison.yaml data/accounts.jsonl
multiclass/ app.py oloproof.yaml candidate.yaml release.yaml data/tickets.jsonl
regression/ app.py oloproof.yaml candidate.yaml release.yaml data/orders.jsonlCzęść 1: klasyfikator binarny
Adapter
binary/app.py zawiera model i adapter w jednym pliku. Model to ten sam deterministyczny model odejść klientów co w przykładzie churn_model (oloproof init --example churn_model); adapter to udekorowana funkcja, którą wywołuje Oloproof:
from oloproof import system
@system(name="churn-model", version="baseline")
def run(account):
score = churn_score(account)
return {"label": score >= 0.5, "score": round(score, 4)}
@system(name="churn-model", version="candidate-cutoff-0.4")
def run_candidate(account):
score = churn_score(account)
return {"label": score >= 0.4, "score": round(score, 4)}Aby ocenić własny model, zachowaj ten kształt i zastąp churn_score wywołaniem go, na przykład model.predict_proba([features(account)])[0][1] dla modelu scikit-learn wczytywanego raz przy imporcie. Zmieniaj version za każdym razem, gdy zmienia się model lub jego próg odcięcia: wersja jest częścią klucza cache, więc model wytrenowany ponownie pod tą samą wersją użyłby starych predykcji.
Kształty wejścia i wyniku
Jeden wiersz data/accounts.jsonl to jeden przypadek:
{"expected": {"label": false}, "id": "account_000", "input": {"recent_upgrade": true, "support_contacts": 0, "tenure_months": 0}, "metadata": {"plan": "enterprise"}}| Część | Kształt | Kto ją czyta |
|---|---|---|
| input | obiekt, który otrzymuje Twoja funkcja | Twój adapter |
| expected.label | true lub false, prawda | ewaluatory |
| metadata.plan | dowolny JSON | tylko wycinki |
| zwrócony label | true lub false, predykcja | ewaluatory |
| zwrócony score | liczba w [0, 1], prawdopodobieństwo klasy pozytywnej | Brier, log loss, ranking, kalibracja, przegląd progów |
Ewaluatory i dlaczego te
binary/oloproof.yaml:
version: 1
project: churn-tutorial
dataset: data/accounts.jsonl
system:
name: churn-model
version: baseline
callable: app:run
evaluators:
- {type: predictive_correct, criterion: accuracy}
- {type: predictive_recall, criterion: recall}
- {type: predictive_precision, criterion: precision}
- {type: predictive_brier, criterion: brier}
- {type: predictive_log_loss, criterion: log_loss, clip: 0.02}
- {type: predictive_ranking, criterion: rank}
metrics:
- {id: roc_auc, type: ranking, criterion: rank, statistic: roc_auc}
- {id: pr_auc, type: ranking, criterion: rank, statistic: average_precision}
predictive:
label_field: label
score_field: score
expected_field: label
positive: true
calibration_bins: 10
thresholds: [0.3, 0.4, 0.5, 0.6, 0.7]
slices: [metadata.plan, "confidence:0.5"]
min_slice_support: 20- Trafność, czułość i precyzja to trzy odsetki na trzech różnych zbiorach wierszy: wszystkich kontach, kontach, które odeszły, i kontach oznaczonych przez model. Model odejść potrzebuje wszystkich trzech, ponieważ odchodzi mniej więcej jedna trzecia kont, więc model, który przewiduje, że nikt nie odejdzie, ma trafność 68% i nie znajduje nikogo.
- Brier i log loss oceniają prawdopodobieństwo stojące za etykietą. Log loss wymaga clip, ponieważ jedna pewna pomyłka byłaby inaczej nieskończona.
- predictive_ranking plus dwa wpisy metrics: dają ROC-AUC i średnią precyzję. Odpowiadają na pytanie, jak dobrze model porządkuje konta, niezależnie od tego, gdzie leży próg odcięcia.
- Blok predictive: mówi, gdzie są etykieta, wynik liczbowy i prawda, i obok metryk wytwarza liczności pomyłek, tabelę kalibracji i przegląd progów.
Polityka
binary/release.yaml nakłada próg na każdy odsetek:
version: 1
confidence_level: 0.95
block_on: [FAIL, INSUFFICIENT_EVIDENCE, MANUAL_REVIEW]
rules:
- {id: accuracy-floor, metric: accuracy, min: 0.75}
- {id: recall-floor, metric: recall, min: 0.60}
- {id: precision-floor, metric: precision, min: 0.60}Reguła przechodzi, gdy dolna granica przedziału przekracza próg, zawodzi, gdy górna granica jest poniżej niego, a w przeciwnym razie pokazuje INSUFFICIENT_EVIDENCE.
Uruchom
cd binary
oloproof runSkrócony:
Run run_... [DECIDED/COMPLETE]
Gate: ALLOW (exit 0)
│ accuracy-floor │ accuracy │ PASS │ lower_bound_meets_minimum │
│ recall-floor │ recall │ PASS │ lower_bound_meets_minimum │
│ precision-floor │ precision │ PASS │ lower_bound_meets_minimum │
│ accuracy │ 88.5% │ [83.2%, 92.6%] │ 177 / 200 observed · 0 missing · 0 excluded │
│ recall │ 81.2% │ [69.5%, 90.0%] │ 52 / 64 observed · 0 missing · 136 excluded │
│ precision │ 82.5% │ [70.9%, 91.0%] │ 52 / 63 observed · 0 missing · 137 excluded │
│ brier │ 0.120 │ [0.094, 0.154] │ mean of 200 observed · 0 missing · 0 excluded │
│ log_loss │ 0.389 │ [0.323, 0.499] │ mean of 200 observed · 0 missing · 0 excluded │
│ roc_auc │ 92.3% │ [69.3%, 100.0%] │ roc_auc over 64 positive · 136 negative · 0 missing · 0 excluded │
│ pr_auc │ 86.5% │ │ average_precision over 64 positive · 136 negative · 0 missing · 0 excluded │Jak go czytać:
- Gate: ALLOW (exit 0) oznacza, że żadna reguła nie osiągnęła stanu, na którym polityka blokuje. Nie jest to twierdzenie, że model jest dobry poza trzema progami, które zapisałeś.
- Liczności excluded to mianowniki w działaniu: czułość jest mierzona na 64 kontach, które odeszły, więc 136, które nie odeszły, jest z niej wykluczonych, a nie liczonych jako niepowodzenia.
- pr_auc nie ma przedziału. Przy 200 wierszach silnik wstrzymuje granicę, której nie może uzasadnić; reguła na nim pokazałaby INSUFFICIENT_EVIDENCE z interval_unavailable.
Pod metrykami ten sam wynik wypisuje liczności pomyłek, tabelę kalibracji i przegląd progów:
│ actually positive │ 52 │ 12 │
│ actually negative │ 11 │ 125 │
│ 0.2-0.3 │ 26.7% │ 0.0% │ 34 rows │
│ 0.5-0.6 │ 53.4% │ 81.0% │ 21 rows │
│ 0.3 │ 57.4% │ 96.9% │ 62/108 predicted positive · 62/64 actual positive │
│ 0.4 │ 62.6% │ 89.1% │ 57/91 predicted positive · 57/64 actual positive │
│ 0.5 │ 82.5% │ 81.2% │ 52/63 predicted positive · 52/64 actual positive │
│ 0.6 │ 83.3% │ 54.7% │ 35/42 predicted positive · 35/64 actual positive │
│ 0.7 │ 100.0% │ 37.5% │ 24/24 predicted positive · 24/64 actual positive │Liczności nie są odsetkami i żadna reguła nie może ich wskazać. Wiersze kalibracji mówią, że prawdopodobieństwa modelu są przesunięte: w przedziale od 0,2 do 0,3 twierdzi, że odchodzi mniej więcej jeden klient na czterech, a nie odszedł żaden z 34. Przegląd progów ma tytuł Thresholds (exploratory; recommends nothing): pokazuje, co zmierzyłby każdy próg odcięcia, i zostawia wybór Tobie, bo tylko Ty wiesz, ile kosztuje przeoczony odchodzący klient w porównaniu ze zmarnowanym telefonem retencyjnym.
Przejrzyj niepowodzenia
oloproof inspect RUN_ID --failures --limit 523 of 200 cases failed, errored or did not finish
account_032
output: {"label": false, "score": 0.07}
accuracy: failed
recall: failed
account_037
output: {"label": false, "score": 0.37}
accuracy: failed
recall: failed
...RUN_ID to identyfikator z pierwszego wiersza wyniku przebiegu. oloproof inspect RUN_ID --case account_037 pokazuje wejście jednego przypadku, oczekiwaną wartość, wynik i każdą ocenę. Kilku przeoczonych odchodzących klientów leży tuż pod progiem 0.5 (0.37, 0.43), co sugerował już wiersz 0.4 przeglądu.
Sensowne następne działanie wynika z tego, co widzisz, a nie z bramki: tutaj pomyłki skupiają się poniżej progu, więc kandydat próbuje niższego. Gdyby były to pewne pomyłki (0.07), następnym działaniem byłyby cechy modelu, a żaden próg by nie pomógł.
Zmiana kandydująca i porównanie
binary/candidate.yaml to oloproof.yaml ze zmienionymi dwoma wierszami:
system:
name: churn-model
version: candidate-cutoff-0.4
callable: app:run_candidateoloproof run --config candidate.yamlGate: BLOCK (exit 3)
│ accuracy-floor │ accuracy │ INSUFFICIENT_EVIDENCE │ interval_overlaps_threshold │
│ recall-floor │ recall │ PASS │ lower_bound_meets_minimum │
│ precision-floor │ precision │ INSUFFICIENT_EVIDENCE │ interval_overlaps_threshold │
│ accuracy │ 79.5% │ [73.2%, 84.9%] │ 159 / 200 observed · 0 missing · 0 excluded │
│ recall │ 89.1% │ [78.7%, 95.5%] │ 57 / 64 observed · 0 missing · 136 excluded │
│ precision │ 62.6% │ [51.8%, 72.6%] │ 57 / 91 observed · 0 missing · 109 excluded │Dokładnie wiersz 0.4 przeglądu: czułość w górę, precyzja w dół. Kod 3 to INSUFFICIENT_EVIDENCE, a nie FAIL: progi leżą wewnątrz przedziałów, więc 200 kont nie może powiedzieć, po której stronie jest kandydat. Wyniki liczbowe się nie zmieniły, więc Brier, log loss i ROC-AUC są identyczne.
Teraz porównaj oba przebiegi przypadek po przypadku. binary/comparison.yaml zawiera reguły porównania:
version: 1
confidence_level: 0.95
block_on: [FAIL, INSUFFICIENT_EVIDENCE, MANUAL_REVIEW]
rules:
- {id: recall-no-worse, kind: non_inferiority, metric: recall, margin: 0.05}
- {id: accuracy-no-worse, kind: non_inferiority, metric: accuracy, margin: 0.05}oloproof compare CANDIDATE_RUN_ID BASELINE_RUN_ID --policy comparison.yamlComparison sha256:... of run_... against run_... · 200 paired cases
accuracy: -9.0 points [-17.9, -1.4] · 200 paired · 0 missing · 0 excluded
recall: +7.8 points [-2.8, +21.9] · 64 paired · 0 missing · 136 excluded
excluded 136: not_a_positive_case
precision: +0.0 points [-8.3, +8.3] · 63 paired · 0 missing · 137 excluded
excluded 137: not_predicted_positive
...
Decisions
recall-no-worse recall non-inferiority, margin 5.0 points PASS lower_bound_above_margin
accuracy-no-worse accuracy non-inferiority, margin 5.0 points INSUFFICIENT_EVIDENCE interval_overlaps_margin
no sample size would make this PASS: the difference itself (-9.0 points) is outside the margin, so more cases would move it toward FAIL
Gate: BLOCK (exit 3)Przeczytaj uważnie wiersz precyzji. Własna precyzja kandydata spadła z 82,5% do 62,6%, a jednak sparowana różnica wynosi +0.0 na 63 parach. Porównanie paruje wiersze: różnica precyzji jest mierzona tylko na kontach oznaczonych przez oba modele, a na tych 63 oba miały rację. 28 dodatkowych kont oznaczonych przez kandydata, z czego 23 to fałszywe alarmy, leży poza tym zbiorem. Dlatego comparison.yaml przy zmianie progu pilnuje trafności, a nie precyzji; Reguły porównań opisują rodzaje reguł.
Użyteczną częścią jest decyzja: wykazano, że czułość nie jest gorsza, a trafności nie da się wykazać w granicach pięciu punktów; wiersz porady mówi, że więcej danych przesunęłoby ją w stronę FAIL. Czy wymiana dziewięciu punktów trafności na osiem punktów czułości jest tego warta, to decyzja biznesowa, którą bramka uczyniła widoczną.
Część 2: klasyfikator wieloklasowy, po jednej klasie naraz
multiclass/app.py kieruje zgłoszenie do jednej z trzech kolejek i ma jedną celową wadę: każde zgłoszenie wysłane z aplikacji mobilnej trafia do technical.
@system(name="ticket-router", version="baseline")
def route(ticket):
if ticket["channel"] == "app":
return {"queue": "technical"}
return {"queue": classify(str(ticket["subject"]))}Przypadek:
{"expected": {"queue": "billing"}, "id": "ticket_000", "input": {"channel": "email", "subject": "update the card on file"}, "metadata": {"channel": "email"}}Nie ma metryki wieloklasowej do włączenia. Każda klasa dostaje własny blok binarny: czułość dla billing to czułość binarna, której klasą pozytywną jest billing. multiclass/oloproof.yaml:
evaluators:
- {type: predictive_correct, criterion: accuracy, field: queue, expected_field: queue}
- {type: predictive_recall, criterion: recall_billing, field: queue, expected_field: queue, positive: billing}
- {type: predictive_precision, criterion: precision_billing, field: queue, expected_field: queue, positive: billing}
- {type: predictive_recall, criterion: recall_technical, field: queue, expected_field: queue, positive: technical}
- {type: predictive_precision, criterion: precision_technical, field: queue, expected_field: queue, positive: technical}
- {type: predictive_recall, criterion: recall_account, field: queue, expected_field: queue, positive: account}
- {type: predictive_precision, criterion: precision_account, field: queue, expected_field: queue, positive: account}
slices: [metadata.channel]Oloproof nie liczy średniej makro ani mikro z tych wartości. Jeśli jej potrzebujesz, jest to liczba, którą sam wyprowadzasz z liczności dla każdej klasy, i żadna reguła nie może na niej bramkować. Polityka nakłada progi na klasy, które mają znaczenie, ponieważ router może mieć wysoką trafność ogólną i zgubić jedną kolejkę:
rules:
- {id: billing-recall-floor, metric: recall_billing, min: 0.80}
- {id: account-recall-floor, metric: recall_account, min: 0.80}
- {id: technical-precision-floor, metric: precision_technical, min: 0.80}cd ../multiclass
oloproof runGate: BLOCK (exit 1)
│ billing-recall-floor │ recall_billing │ FAIL │ upper_bound_below_minimum │
│ account-recall-floor │ recall_account │ INSUFFICIENT_EVIDENCE │ interval_overlaps_threshold │
│ technical-precision-floor │ precision_technical │ FAIL │ upper_bound_below_minimum │
│ accuracy │ 81.3% │ [74.1%, 87.3%] │ 122 / 150 observed · 0 missing · 0 excluded │
│ recall_billing │ 66.0% │ [51.2%, 78.8%] │ 33 / 50 observed · 0 missing · 100 excluded │
│ precision_billing │ 100.0% │ [89.4%, 100.0%] │ 33 / 33 observed · 0 missing · 117 excluded │
│ recall_technical │ 100.0% │ [92.8%, 100.0%] │ 50 / 50 observed · 0 missing · 100 excluded │
│ precision_technical │ 64.1% │ [52.4%, 74.7%] │ 50 / 78 observed · 0 missing · 72 excluded │
│ recall_account │ 78.0% │ [64.0%, 88.5%] │ 39 / 50 observed · 0 missing · 100 excluded │
│ precision_account │ 100.0% │ [90.9%, 100.0%] │ 39 / 39 observed · 0 missing · 111 excluded │Kod 1 oznacza, że co najmniej jedna reguła ma stan FAIL. Każda klasa ma własny mianownik: 78 zgłoszeń nazwano technical, więc to jest mianownik precyzji dla technical. Tabela wycinków wskazuje przyczynę; wycinki są eksploracyjne i nigdy nie bramkują, ale oznaczony wycinek to trop wart przeczytania:
│ metadata.channel=app │ accuracy │ 42.9% │ [28.8%, 57.8%] │ 21 / 49 observed · ... │ marked (p 0.0001) │oloproof inspect RUN_ID --failures --limit 228 of 150 cases failed, errored or did not finish
ticket_011
output: {"queue": "technical"}
accuracy: failed
precision_technical: failed
recall_account: failed
...Kandydat (route_candidate, uruchamiany przez oloproof run --config candidate.yaml) usuwa skrót oparty na kanale. Na tych syntetycznych danych kieruje każde zgłoszenie poprawnie, a bramka przepuszcza:
Gate: ALLOW (exit 0)
│ billing-recall-floor │ recall_billing │ PASS │ lower_bound_meets_minimum │
│ account-recall-floor │ recall_account │ PASS │ lower_bound_meets_minimum │
│ technical-precision-floor │ precision_technical │ PASS │ lower_bound_meets_minimum │oloproof compare działa tutaj dokładnie jak w części 1, jedna różnica na metrykę klasy.
Część 3: regresor oceniany błędem bezwzględnym
regression/app.py szacuje dni dostawy i pomija to, czy towar jest na stanie:
@system(name="delivery-estimator", version="baseline")
def estimate(order):
return {"days": round(base_days(order), 1)}Przypadek, z prawdą jako liczbą:
{"expected": {"days": 11}, "id": "order_000", "input": {"distance_km": 468, "express": false, "in_stock": false}, "metadata": {"in_stock": false}}Jedynym ewaluatorem regresji jest błąd bezwzględny i wymaga on zakresu, w którym leży każdy cel. Błąd bezwzględny to ograniczona średnia, której przedział obowiązuje tylko w tym zakresie, więc jest on deklarowany, nigdy domyślny. Dostawy trwają tu od 0 do 20 dni:
evaluators:
- type: predictive_absolute_error
criterion: days_error
field: days
expected_field: days
target_range: [0, 20]
slices: [metadata.in_stock]Polityka to budżet, reguła max:: przechodzi, gdy górna granica przedziału jest na jego poziomie lub niżej.
rules:
- {id: error-budget, metric: days_error, max: 2.0}cd ../regression
oloproof runGate: BLOCK (exit 1)
│ error-budget │ days_error │ FAIL │ lower_bound_above_maximum │
│ days_error │ 2.66 │ [2.01, 3.57] │ mean of 120 observed · 0 missing · 0 excluded │
│ metadata.in_stock=false │ days_error │ 5.18 │ [4.60, 6.65] │ mean of 53 observed · ... │
│ metadata.in_stock=true │ days_error │ 0.67 │ [0.51, 2.19] │ mean of 67 observed · ... │FAIL, ponieważ nawet dolna granica przedziału jest powyżej dwudniowego budżetu. Ewaluator wyniku liczbowego nie ma zaliczenia ani niepowodzenia dla przypadku, więc oloproof inspect RUN_ID --failures niczego nie wymienia; zamiast tego przeczytaj przypadek:
oloproof inspect RUN_ID --case order_000output: {
"days": 6.1
}
judgments:
days_error: score 4.9Wycinek mówi, gdzie szukać: zamówienia bez towaru na stanie mylą się o pięć dni. Kandydat (estimate_candidate) dodaje pięć dni dla towaru, którego nie ma na stanie:
oloproof run --config candidate.yamlGate: ALLOW (exit 0)
│ error-budget │ days_error │ PASS │ upper_bound_meets_maximum │
│ days_error │ 0.70 │ [0.56, 1.57] │ mean of 120 observed · 0 missing · 0 excluded │Rozwiązywanie problemów
| Objaw | Przyczyna i rozwiązanie |
|---|---|
| Configuration error: evaluator 'recall' counts 'churned' as the positive class, and no case's 'label' is 'churned' | positive: wskazuje wartość, której nie ma żaden przypadek. Użyj wartości dokładnie tak, jak występuje pod expected, z rozróżnieniem true i "true". |
| release rule ... refers to unknown metric 'false_positives' | Liczności pomyłek nie są metrykami. Bramkuj na czułości lub precyzji. |
| Ewaluator regresji jest odrzucany przed przebiegiem | Brak target_range lub jest pusty. Zadeklaruj zakres, jaki cele mogą faktycznie przyjmować; szerszy zakres daje szerszy przedział. |
| Reguła rankingowa pokazuje INSUFFICIENT_EVIDENCE (interval_unavailable) | Zestaw jest za mały na przedział tej statystyki, zwykle pr_auc. Bramkuj na roc_auc albo dodaj przypadki. |
| Kandydat raportuje liczby punktu odniesienia | Oba przebiegi mają tę samą version, więc użyto ponownie predykcji z cache. Daj każdej zmianie własną wersję. |
| Przypadek jest missing zamiast błędny | Twoja funkcja zgłosiła wyjątek albo pola czytanego przez ewaluator brakuje lub nie jest liczbą. oloproof inspect RUN_ID --case ID pokazuje błąd. |
Ograniczenia
- Brak rekomendowanego progu. Przegląd raportuje, co zmierzyłby każdy zadeklarowany próg odcięcia.
- Brak metryki uśrednionej makro lub mikro dla wielu klas i brak wsparcia wielu etykiet poza jednym blokiem na etykietę.
- Regresja to wyłącznie błąd bezwzględny w zadeklarowanym target_range: bez błędu kwadratowego, R kwadrat ani nieograniczonego błędu.
- Kalibracja jest pokazywana jako tabela, nie bramkuje i nie jest korygowana.
- Sparowana różnica precyzji obejmuje tylko wiersze oznaczone przez oba modele, jak pokazuje część 1.
- Przykłady są deterministyczne i syntetyczne. Ich decyzje pokazują mechanikę, a nie to, jak prawdziwy model zachowuje się na prawdziwych danych.
Dokąd dalej
- Klasyfikatory i regresory to dokumentacja pole po polu.
- Porównywanie kandydata z punktem odniesienia i Reguły porównań omawiają przebieg pracy z porównaniami.
- Wycinki omawiają pasma confidence: i liczebność wycinków.
- Bramkowanie CI wymienia kody wyjścia.