Przejdź do treści

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

ModelCo dostajeszCzego nie dostajesz
Klasyfikator binarnytrafność, 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ówrekomendowanego progu
Klasyfikator wieloklasowyjedną 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_rangebłę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-predictive

Każ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.jsonl

Część 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łtKto ją czyta
inputobiekt, który otrzymuje Twoja funkcjaTwój adapter
expected.labeltrue lub false, prawdaewaluatory
metadata.plandowolny JSONtylko wycinki
zwrócony labeltrue lub false, predykcjaewaluatory
zwrócony scoreliczba w [0, 1], prawdopodobieństwo klasy pozytywnejBrier, 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 run

Skró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 5
23 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_candidate
oloproof run --config candidate.yaml
Gate: 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.yaml
Comparison 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 run
Gate: 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 2
28 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 run
Gate: 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_000
output: {
  "days": 6.1
}
judgments:
  days_error: score 4.9

Wycinek 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.yaml
Gate: 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

ObjawPrzyczyna 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 przebiegiemBrak 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 odniesieniaOba 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łędnyTwoja 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