Ga naar de inhoud

Handleidingen

Tutorial: een classifier en een regressor

Een uitvoerbare doorloop voor drie voorspellende modellen die vanaf de commandoregel worden geëvalueerd: een binaire churnclassifier, een ticketrouter met drie klassen die per klasse wordt geëvalueerd, en een regressor voor levertijden die wordt gescoord op absolute fout binnen een gedeclareerd bereik. Elk draait lokaal zonder provider, zonder sleutel en zonder netwerk, en elk eindigt met een kandidaatwijziging die tegen de baseline wordt gemeten.

Deze pagina is de praktische tegenhanger van Classifiers en regressors, die elk veld uitlegt; lees die pagina als referentie en deze om het één keer van begin tot eind te doen. Termen zoals interval, beslissingstoestand en releaseactie worden gedefinieerd in Kernbegrippen.

Wat Oloproof meet voor een voorspellend model, en wat niet

ModelWat je krijgtWat je niet krijgt
Binaire classifieraccuracy, recall, precision, Brier-score, log loss, ROC-AUC en average precision, plus confusion-aantallen, een kalibratietabel en een drempelsweepeen aanbevolen drempel
Multiclass-classifieréén recall en één precision per klasse, elk een binaire metriek waarvan de positieve klasse die klasse is (één tegen de rest)een macro- of micro-gemiddelde als metriek
Regressorgemiddelde absolute fout, begrensd door een target_range die je declareertkwadratische fout, R kwadraat, of een fout zonder gedeclareerd bereik

Het model zelf blijft van jou. Oloproof roept een Python-functie aan waarnaar je verwijst, leest de voorspelling die ze teruggeeft, en ziet nooit features, gewichten of interne werking.

Vereisten

  • Python 3.11 of later en pip install oloproof in een virtuele omgeving, zoals in de quickstart.
  • De voorbeeldbestanden, die met het pakket worden meegeleverd. Kopieer ze naar een nieuwe map zodat de stores van de runs daar terechtkomen:
oloproof init --example predictive ~/oloproof-predictive
cd ~/oloproof-predictive

Elk commando hieronder draait vanuit een van de drie submappen. Elke run schrijft zijn bewijs naar een map .oloproof/ naast het oloproof.yaml dat hij las.

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

Deel 1: een binaire classifier

De adapter

binary/app.py bevat het model en de adapter in één bestand. Het model is hetzelfde deterministische churnmodel als in het voorbeeld churn_model (oloproof init --example churn_model); de adapter is de gedecoreerde functie die Oloproof aanroept:

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

Om je eigen model te evalueren, houd je de vorm en vervang je churn_score door een aanroep ervan, bijvoorbeeld model.predict_proba([features(account)])[0][1] voor een scikit-learn-model dat je één keer bij het importeren laadt. Wijzig version telkens als het model of zijn afkapwaarde verandert: de versie is deel van de cachesleutel, dus een model dat onder dezelfde versie opnieuw is getraind zou de oude voorspellingen hergebruiken.

Vormen van input en output

Eén regel van data/accounts.jsonl is één case:

{"expected": {"label": false}, "id": "account_000", "input": {"recent_upgrade": true, "support_contacts": 0, "tenure_months": 0}, "metadata": {"plan": "enterprise"}}
DeelVormWie het leest
inputhet object dat je functie krijgtje adapter
expected.labeltrue of false, de waarheidde evaluators
metadata.planelke JSONalleen slices
teruggegeven labeltrue of false, de voorspellingde evaluators
teruggegeven scoreeen getal in [0, 1], de kans op de positieve klasseBrier, log loss, rangschikking, kalibratie, sweep

De evaluators, en waarom deze

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
  • Accuracy, recall en precision zijn drie percentages over drie verschillende verzamelingen rijen: elk account, de accounts die vertrokken, en de accounts die het model markeerde. Een churnmodel heeft ze alle drie nodig omdat ongeveer een derde van de accounts vertrekt, dus een model dat voorspelt dat niemand vertrekt is 68% accuraat en vindt niemand.
  • Brier en log loss scoren de kans achter het label. Log loss heeft clip nodig, omdat één zelfverzekerde fout anders oneindig is.
  • predictive_ranking plus twee metrics:-items geven ROC-AUC en average precision. Ze beantwoorden hoe goed het model accounts ordent, los van waar de afkapwaarde ligt.
  • Het predictive:-blok zegt waar het label, de score en de waarheid staan, en produceert de confusion-aantallen, de kalibratietabel en de drempelsweep naast de metrieken.

Het beleid

binary/release.yaml legt een ondergrens op elk percentage:

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}

Een regel slaagt als de ondergrens van het interval de vloer haalt, faalt als de bovengrens eronder ligt, en leest anders INSUFFICIENT_EVIDENCE.

Draai het

cd binary
oloproof run

Ingekort:

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 │

Zo lees je het:

  • Gate: ALLOW (exit 0) betekent dat geen regel een toestand bereikte waarop het beleid blokkeert. Het is geen bewering dat het model goed is buiten de drie vloeren die je schreef.
  • De excluded-aantallen zijn de noemers aan het werk: recall wordt gemeten over de 64 accounts die vertrokken, dus de 136 die dat niet deden zijn ervan uitgesloten, niet als mislukkingen geteld.
  • pr_auc heeft geen interval. Bij 200 rijen houdt de engine een grens achter die hij niet kan onderbouwen; een regel erop zou INSUFFICIENT_EVIDENCE lezen met interval_unavailable.

Onder de metrieken print dezelfde output de confusion-aantallen, de kalibratietabel en de drempelsweep:

│ 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  │

De aantallen zijn geen percentages en geen regel mag ze noemen. De kalibratierijen zeggen dat de kansen van het model niet kloppen: in de band van 0,2 tot 0,3 beweert het ongeveer één vertrekker op vier en van de 34 vertrok er geen. De sweep heet Thresholds (exploratory; recommends nothing): hij toont wat elke afkapwaarde zou hebben gemeten en laat de keuze aan jou, omdat alleen jij weet wat een gemiste vertrekker kost tegenover een verspild retentiegesprek.

De mislukkingen bekijken

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 is het id op de eerste regel van de output van de run. oloproof inspect RUN_ID --case account_037 toont de input, de verwachte waarde, de output en elk oordeel van één case. Verschillende gemiste vertrekkers zitten net onder de afkapwaarde van 0,5 (0,37, 0,43), wat de rij 0,4 van de sweep al suggereerde.

Een zinvolle volgende stap volgt uit wat je ziet, niet uit de gate: hier clusteren de missers onder de afkapwaarde, dus de kandidaat probeert een lagere. Waren het zelfverzekerde missers geweest (0,07), dan zou de volgende stap bij de features van het model liggen, en zou geen afkapwaarde helpen.

Een kandidaatwijziging, en de vergelijking

binary/candidate.yaml is oloproof.yaml met twee gewijzigde regels:

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                                │

Precies de rij 0,4 van de sweep: recall omhoog, precision omlaag. Exit 3 is INSUFFICIENT_EVIDENCE, niet FAIL: de vloeren liggen binnen de intervallen, dus 200 accounts kunnen niet zeggen aan welke kant de kandidaat zit. De scores veranderden niet, dus Brier, log loss en ROC-AUC zijn identiek.

Vergelijk nu de twee runs case voor case. binary/comparison.yaml bevat vergelijkingsregels:

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)

Lees de precision-regel zorgvuldig. De eigen precision van de kandidaat daalde van 82,5% naar 62,6%, maar het gepaarde verschil is +0.0 over 63 paren. Een vergelijking koppelt rijen: een precisionverschil wordt alleen gemeten over accounts die beide modellen markeerden, en op die 63 hadden beide gelijk. De 28 extra accounts die de kandidaat markeerde, waarvan 23 vals alarm, vallen buiten die verzameling. Daarom bewaakt comparison.yaml accuracy in plaats van precision bij een wijziging van de afkapwaarde; Vergelijkingsregels beschrijft de soorten regels.

De beslissing is het nuttige deel: recall is aantoonbaar niet slechter, en accuracy kan niet binnen vijf punten worden aangetoond; de adviesregel zegt dat meer data het richting FAIL zou duwen. Of negen punten accuracy inruilen voor acht punten recall het waard is, is een zakelijke beslissing die de gate zichtbaar heeft gemaakt.

Deel 2: een multiclass-classifier, klasse voor klasse

multiclass/app.py stuurt een supportticket naar een van drie wachtrijen, en heeft één opzettelijke fout: elk ticket dat vanuit de mobiele app wordt verstuurd gaat naar technical.

@system(name="ticket-router", version="baseline")
def route(ticket):
    if ticket["channel"] == "app":
        return {"queue": "technical"}
    return {"queue": classify(str(ticket["subject"]))}

Een case:

{"expected": {"queue": "billing"}, "id": "ticket_000", "input": {"channel": "email", "subject": "update the card on file"}, "metadata": {"channel": "email"}}

Er is geen multiclass-metriek om aan te zetten. Elke klasse krijgt haar eigen binaire blok: recall voor billing is de binaire recall waarvan de positieve klasse billing is. 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 berekent hierover geen macro- of micro-gemiddelde. Als je er een nodig hebt, is het een getal dat je zelf afleidt uit de aantallen per klasse, en geen regel kan erop gaten. Het beleid legt een vloer op de klassen die ertoe doen, omdat een router in het geheel accuraat kan zijn en toch één wachtrij kwijtraakt:

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 │

Exit 1 betekent dat minstens één regel FAIL is. Elke klasse heeft haar eigen noemer: 78 tickets werden technical genoemd, dus dat is de noemer van precision voor technical. De slicetabel wijst naar de oorzaak; slices zijn verkennend en gaten nooit, maar een gemarkeerde slice is een spoor dat het lezen waard is:

│ 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
...

De kandidaat (route_candidate, gedraaid met oloproof run --config candidate.yaml) laat de kanaalsnelkoppeling vallen. Op deze synthetische data stuurt hij elk ticket correct door en laat de gate door:

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 werkt hier precies zoals in Deel 1, met één verschil per klassemetriek.

Deel 3: een regressor, gescoord op absolute fout

regression/app.py schat het aantal leverdagen en negeert of het artikel op voorraad is:

@system(name="delivery-estimator", version="baseline")
def estimate(order):
    return {"days": round(base_days(order), 1)}

Een case, met de waarheid als getal:

{"expected": {"days": 11}, "id": "order_000", "input": {"distance_km": 468, "express": false, "in_stock": false}, "metadata": {"in_stock": false}}

De enige regressie-evaluator is absolute fout, en die heeft het bereik nodig waarin elk doel ligt. Een absolute fout is een begrensd gemiddelde waarvan het interval alleen binnen dat bereik geldt, dus het wordt gedeclareerd, nooit standaard ingevuld. Leveringen duren hier 0 tot 20 dagen:

evaluators:
  - type: predictive_absolute_error
    criterion: days_error
    field: days
    expected_field: days
    target_range: [0, 20]
slices: [metadata.in_stock]

Het beleid is een budget, een max:-regel: hij slaagt als de bovengrens van het interval erop of eronder ligt.

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 omdat zelfs de ondergrens van het interval boven het budget van twee dagen ligt. Een score-evaluator heeft geen geslaagd of mislukt per case, dus oloproof inspect RUN_ID --failures toont er geen; lees in plaats daarvan een case:

oloproof inspect RUN_ID --case order_000
output: {
  "days": 6.1
}
judgments:
  days_error: score 4.9

De slice zegt waar je moet kijken: bestellingen die niet op voorraad zijn zitten vijf dagen ernaast. De kandidaat (estimate_candidate) telt vijf dagen op voor een artikel dat niet op voorraad is:

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 │

Problemen oplossen

SymptoomOorzaak en oplossing
Configuration error: evaluator 'recall' counts 'churned' as the positive class, and no case's 'label' is 'churned'positive: noemt een waarde die geen case heeft. Gebruik de waarde precies zoals die onder expected staat, inclusief true tegenover "true".
release rule ... refers to unknown metric 'false_positives'Confusion-aantallen zijn geen metrieken. Gate op recall of precision.
Een regressie-evaluator wordt voor de run geweigerdtarget_range ontbreekt of is leeg. Declareer het bereik dat de doelen echt kunnen aannemen; een breder bereik geeft een breder interval.
Een rangschikkingsregel leest INSUFFICIENT_EVIDENCE (interval_unavailable)De suite is te klein voor het interval van die statistiek, typisch pr_auc. Gate op roc_auc, of voeg cases toe.
De kandidaat rapporteert de getallen van de baselineBeide runs delen een version, dus de gecachete voorspellingen werden hergebruikt. Geef elke wijziging een eigen versie.
Een case is missing in plaats van foutJe functie wierp een fout op, of het veld dat de evaluator leest ontbreekt of is geen getal. oloproof inspect RUN_ID --case ID toont de fout.

Beperkingen

  • Geen aanbevolen drempel. De sweep rapporteert wat elke gedeclareerde afkapwaarde zou hebben gemeten.
  • Geen macro- of micro-gemiddelde als metriek voor multiclass, en geen multilabelondersteuning buiten één blok per label.
  • Regressie is alleen absolute fout binnen een gedeclareerde target_range: geen kwadratische fout, R kwadraat of onbegrensde fout.
  • Kalibratie wordt als tabel getoond, niet gegate en niet gecorrigeerd.
  • Een gepaard precisionverschil dekt alleen rijen die beide modellen markeerden, zoals Deel 1 laat zien.
  • De voorbeelden zijn deterministisch en synthetisch. Hun beslissingen tonen de mechaniek, niet hoe een echt model zich op echte data gedraagt.

Waar verder