Érvényességi kapu: egy predikciós szerződés akkor ellenőrizhető, ha minden mezőhöz rögzíti a forrását, a rendelkezésre állási időpontját, a mértékegységét és a nullértékek kezelését; a tanító- és tesztablakot az eredmények megtekintése előtt kell meghatározni.
A záróprojekt első lépése szándékosan nem modellt épít: a problémát
és az adatszerződést fagyasztja be. A befagyasztás egy verziózott
adatlapot (problem contract) és egy split manifestet jelent, amelyek
nélkül a későbbi AUC, log loss vagy feature-fontosság számok nem
lennének összehasonlíthatók. A gyakorlatban ez két fájl:
contract.json és split.json, mindkettő
Git-kommitban.
A Northstar egy szintetikus webáruház, amely minden rendeléskor 7
napos kézbesítést ígér. A logisztikai csapat a rendelés pillanatában
szeretné tudni, hogy az adott rendelés várhatóan késni fog-e, hogy
proaktívan szólhasson az ügyfélnek vagy kapacitást csoportosíthasson át.
A predikció batchben fut, röviddel a rendelés után; ez
order_ts-hez közeli, de attól elkülönült futási időpontot
jelent.
Bemenet: a tisztított
lab/data/orders.csv és a
lab/data/customers.csv. A szándékosan hibás
orders_raw.csv nem bemenet; annak tisztítása a Hiány és anomália mint vizsgálati kérdés
alfejezetben megtörtént, és a tiszta orders.csv a kontroll.
A split kialakításához csak az orders.csv kell; a teljes
feature-tábla az II. ellenőrző pont: adatból
ellenőrzött feature-tábla alfejezetben előállított 11 oszlopos
sémával dolgozik majd tovább.
Megfigyelési egység: egy rendelés
(order_id), nem tétel és nem ügyfél. A cél bináris,
rendelésszintű. A tételszintű tábla csak aggregálás után kapcsolódhat a
rendeléshez, különben a cél és a szállítási díj többszöröződne (Join-kardinalitás és duplázódó célváltozó).
Cél: a modell a rendelés pillanatában ismert
mezőkből a late = 1 esemény valószínűségét becsülje, ahol a
késés azt jelenti, hogy delivery_days > promised_days,
tehát delivery_days > 7.
Előzetes eredménybecslés: a split létrehozása után a tanítóhalmaz késési arányát (prevalenciáját) számítjuk ki. Ez lesz a baseline: ha például , akkor egy mindig valószínűséget adó konstans modell log loss-ja ; ezt kell a valódi modelleknek megverniük. A pontos érték a futtatásból derül ki, ezért itt nem adunk előre számot.
Elvárt ellenőrzés: a három split halmaz páronként diszjunkt, együtt lefedi az összes rendelést; a tanítóban nincs cutoff utáni címke; a tiltott mezők egyike sem jelenik meg a prediktorlistában.
Label (célváltozó). Az rendelésre a tényleges kézbesítési napok száma, pozitív egész nap. A késést az
indikátor adja:
,
ha késik, és
,
ha nem. A predikció időpontja
az order_ts. A címke elérhetőségének időpontja
a label_available_at, amely a kezdőlaborban megegyezik az
actual_delivery_at értékével. A predikció pillanatában
nem ismert; csak
elérése után válik ismertté. A pozitív eset egyes célokhoz korábban is
felismerhető lenne, de ez más megfigyelési szerződés; itt konzervatívan
a teljes kézbesítést tekintjük címke-időpontnak.
Cutoff (időbeli vágás). A
cutoff itt 2025-05-01T00:00:00+00:00, ISO 8601, UTC. A
modellépítés időpontját modellezi. Három halmazt képezünk:
A három feltétel páronként kizáró és együtt teljes partíció. A purged azért kell, mert vannak olyan rendelések, amelyeket a cutoff előtt feladtak ugyan, de a címkéjük a modellépítéskor még nem érkezett meg. Ezeket sem tanítani, sem tesztelni nem szabad: a teszt a jövőbeli rendeléseket utánozza, ezért csak sorokból állhat. A purged sorok kimaradnak; ez adatveszteség, de a korrekt időbeli validáció ára.
Observation unit. A predikció és az értékelés
egysége egy rendelés. Egy rendeléshez egyetlen
tartozik. Az ügyfélszintű aggregálás későbbi szerződés lehet, de a
primer modell nem ügyfélszintű. Ha a tételtáblát rendeléshez joinoljuk,
előbb aggregálni kell, mert a line_total_minor
összegezhető, de a shipping_fee_minor rendelésenként
egyszer szerepel.
Feature availability. Egy
mező akkor megengedett prediktor, ha értéke a
pillanatban ténylegesen ismert. A befagyasztott lista nyolc mezője ezt
teljesíti: distance_km, priority_flag,
basket_minor, quantity_sum a rendelés
pillanatában adott; account_age_days az
order_ts és a korábban ismert signup_date
különbsége; segment, region,
acquisition_channel statikus ügyféljellemzők. A tiltott
prediktorok: delivery_days,
actual_delivery_at, label_available_at,
target_late, valamint ezek bármely származéka. Ez utóbbi
kitétel miatt például egy „ügyfél korábbi rendeléseinek átlagos késési
ideje” sem használható, még akkor sem, ha az adott előzmény időben
elvileg elérhető lenne.
Split manifest. A manifest egy verziózott fájl,
amely minden order_id-hoz hozzárendeli a splitet. A későbbi
kódok nem véletlenszerűen vagy újraszámolva hoznak létre splitet, hanem
ezt a fájlt olvassák. Ez megakadályozza, hogy egy apró scriptmódosítás
eltérő tanító- és teszthalmazt produkáljon. A manifest tartalmazza a
cutoffot, a split-szabályt, a forrásfájl hash-ét, a generáló script
verzióját és a sorokat.
Legyen a cutoff 2025-05-01T00:00:00+00:00. Nyolc
rendelés:
| order_id | order_ts | label_available_at | delivery_days |
|---|---|---|---|
| A | 2025-04-28 10:00 | 2025-05-02 09:00 | 4 |
| B | 2025-04-20 08:00 | 2025-04-27 10:00 | 7 |
| C | 2025-05-02 14:00 | 2025-05-10 16:00 | 8 |
| D | 2025-04-15 09:00 | 2025-04-25 08:00 | 10 |
| E | 2025-05-10 07:00 | 2025-05-18 12:00 | 8 |
| F | 2025-04-30 11:00 | 2025-05-04 10:00 | 4 |
| G | 2025-04-25 13:00 | 2025-05-09 11:00 | 14 |
| H | 2025-04-18 16:00 | 2025-04-24 17:00 | 6 |
A besorolás:
A tanítóhalmaz három sorból áll (B, D, H), a késések száma , tehát a tanító-prevalencia . Ez a kézi példa aránya; a teljes Northstar adaton a tényleges érték a futtatásból adódik.
Az alábbi script szemléltető, nem futtatott kimenettel; a tényleges
futást a lab környezetben a uv run parancs végzi. A script
a paraméterezést a ? placeholderrel végzi a cutoffra (Paraméterezett lekérdezés és lekérdezési terv), a
fájlnevet konstansként kezeli, és a manifestet a
lab/artifacts/split.json útvonalra írja.
# scripts/freeze_split.py -- szemléltető, nem futtatott
from pathlib import Path
import json
import hashlib
import duckdb
import pandas as pd
CUTOFF = pd.Timestamp("2025-05-01T00:00:00+00:00")
ORDERS = Path("lab/data/orders.csv")
OUT = Path("lab/artifacts/split.json")
split_sql = """
SELECT
order_id,
order_ts::TIMESTAMPTZ AS order_ts,
label_available_at::TIMESTAMPTZ AS label_available_at,
CASE
WHEN order_ts < ? AND label_available_at < ? THEN 'train'
WHEN order_ts >= ? THEN 'test'
ELSE 'purged'
END AS split
FROM read_csv('lab/data/orders.csv', header = true, auto_detect = true)
ORDER BY order_id
"""
conn = duckdb.connect()
manifest = conn.execute(split_sql, [CUTOFF, CUTOFF, CUTOFF]).fetchdf()
manifest["order_ts"] = pd.to_datetime(manifest["order_ts"], utc=True)
manifest["label_available_at"] = pd.to_datetime(
manifest["label_available_at"], utc=True
)
# Invariánsok
train = manifest[manifest["split"] == "train"]
test = manifest[manifest["split"] == "test"]
purged = manifest[manifest["split"] == "purged"]
assert manifest["order_id"].is_unique
assert set(manifest["split"].unique()) <= {"train", "test", "purged"}
assert len(manifest) == len(train) + len(test) + len(purged)
assert (train["order_ts"] < CUTOFF).all()
assert (train["label_available_at"] < CUTOFF).all()
assert (test["order_ts"] >= CUTOFF).all()
assert (purged["order_ts"] < CUTOFF).all()
assert (purged["label_available_at"] >= CUTOFF).all()
source_hash = hashlib.sha256(ORDERS.read_bytes()).hexdigest()
payload = {
"schema_version": "1.0.0",
"contract_id": "northstar-late-v1",
"cutoff": CUTOFF.isoformat(),
"observation_unit": "order_id",
"split_rule": {
"train": "order_ts < cutoff AND label_available_at < cutoff",
"test": "order_ts >= cutoff",
"purged": "order_ts < cutoff AND label_available_at >= cutoff",
},
"source_sha256": source_hash,
"row_counts": {
"train": int(len(train)),
"test": int(len(test)),
"purged": int(len(purged)),
},
"order_ids": manifest.to_dict(orient="records"),
}
OUT.parent.mkdir(parents=True, exist_ok=True)
OUT.write_text(json.dumps(payload, indent=2))
print(payload["row_counts"])A futási parancs Linux/macOS terminálból, a projektgyökérből:
uv run python scripts/freeze_split.pyA kód kimenete a tényleges sorokkal töltődik fel; ebben a szövegben
csak a szerkezet ellenőrzött. A lab manifest.json a
mellékelt ellenőrző jegyzék szerint mérvadó.
A fenti split-szabállyal együtt a projekt adatlapja:
{
"contract_id": "northstar-late-v1",
"frozen_at": "2026-10-20",
"observation_unit": "order",
"prediction_timestamp": "order_ts",
"promised_days": 7,
"target": {
"name": "late",
"positive_definition": "delivery_days > promised_days",
"positive_class": 1,
"label_available_at": "actual_delivery_at"
},
"cutoff": "2025-05-01T00:00:00+00:00",
"split_rule": {
"train": "order_ts < cutoff AND label_available_at < cutoff",
"test": "order_ts >= cutoff",
"purged": "order_ts < cutoff AND label_available_at >= cutoff"
},
"allowed_features": [
"distance_km",
"priority_flag",
"basket_minor",
"quantity_sum",
"account_age_days",
"segment",
"region",
"acquisition_channel"
],
"forbidden_predictors": [
"delivery_days",
"actual_delivery_at",
"label_available_at",
"target_late",
"bármely ezekből származtatott oszlop predictor szerepben"
],
"notes": [
"Pénzösszegek EUR-ben, minor unitban (cent); nincs pénznemkonverzió.",
"A distance_km hiányzó értéke nem 0; a tisztított orders.csv-ben nincs hiány.",
"A label_available_at a teljes kézbesítés; a pozitív eset korábbi felismerése új szerződést igényel."
]
}Ez a dokumentum a modellépítés belépési kapuja. A
contract_id = northstar-late-v1 bármely módosítása új
verziót, például v2-t igényel, és a régi futásokkal nem
hasonlítható össze automatikusan.
A kézi példában a train-prevalencia , a purged három sorból áll, a test két sorból. Az időbeli feltételek tehát nem egyszerűen „cutoff előtt train, utána test” felosztást adnak; a purged határsáv a címke elérhetőségét is figyelembe veszi.
A DuckDB-példa invariánsai a manifestre:
order_id egyedi, tehát egy rendelés csak egyszer
szerepel;order_ts < cutoff és
label_available_at < cutoff;order_ts >= cutoff;order_ts < cutoff és
label_available_at >= cutoff.Ezek nem toleranciás, hanem egzakt feltételek: az időbélyegeket
UTC-ben hasonlítjuk, ezért a határon nincs kerekítési szabadság. A JSON
a source_sha256 mezővel rögzíti, melyik
orders.csv-ből készült; ha a fájl később változik, a hash
eltérése azonnal jelzi, hogy a manifestet újra kell generálni. A split
manifestet a Git commit rögzíti, a későbbi modellkísérletek ezt olvassák
be ahelyett, hogy újrafuttatnák a split-szabályt. Ez a Cache, backfill és orchestrator alfejezetben tanult
bemeneti hash-gondolatot ismétli: a split manifest a modelltanítás egyik
legfontosabb bemenete.
Értelmezés. Az alábbi táblában
.
Töltsd ki a split oszlopot minden sorra, add meg a train késési arányát,
és indokold, miért nem tanulhat a modell a G rendelés
delivery_days=14 értékéből.
Megvalósítás. Írj egy
assign_split(order_ts, label_available_at, cutoff)
függvényt, amely a fenti szabály szerint "train",
"test" vagy "purged" értéket ad, majd
alkalmazd a táblázat minden sorára. Ellenőrizd a B, G és E
sorokat.
Hibakeresés. Egy adathiány miatt kézzel javított manifestben a következő sorok szerepelnek:
{"order_id": "X1", "split": "train", "order_ts": "2025-04-28", "label_available_at": "2025-05-02"}{"order_id": "X2", "split": "test", "order_ts": "2025-04-30", "label_available_at": "2025-05-04"}A cutoff változatlanul 2025-05-01. Melyik sor hibás és miért? Mi lehet a következménye mindkét hibának?
Megváltoztatott feltétel. Változtasd a cutoffot
2025-05-03T00:00:00+00:00-ra az eredeti nyolcsoros
táblázatban. Számítsd ki újra a splitet és a train-prevalenciát. Melyik
sor kerül át másik halmazba?
Feature-availability audit. Egy munkatárs a
modellhez javasolja a customer_previous_late_rate mezőt: az
ügyfél korábbi rendelései közül a késettek aránya a cutoff előtt. Döntsd
el a befagyasztott adatlap alapján, hogy használható-e, és indokold.
Fogalmazz meg egy olyan alternatív előzmény-mezőt, amely a jelen
szerződésben is megengedett lehet, vagy indokold, miért kell új
szerződésverzió.
1. feladat. A helyes split:
| order_id | split | indoklás |
|---|---|---|
| A | purged | , de |
| B | train | és ; , tehát |
| C | test | |
| D | train | , ; , |
| E | test | |
| F | purged | , |
| G | purged | , |
| H | train | , ; , |
A train sorok B, D, H;
,
,
tehát
.
A G rendelés 14 napos kézbesítése pozitív címke lenne, de a modellépítés
időpontjában, a cutoffkor ez az információ még nem érkezett meg, mert
label_available_at = 2025-05-09. Ha tanítósorba tennénk, a
modell olyan címkét látna, amely a valódi predikciós pillanatban nem
állt rendelkezésre; ez jövőbeli információ kiszivárgása lenne. A test
sem jó, mert a rendelés időpontja a cutoff előtti, tehát nem modellezi a
jövőbeli rendeléseket.
2. feladat. Egy lehetséges implementáció:
def assign_split(order_ts, label_available_at, cutoff):
if order_ts < cutoff:
if label_available_at < cutoff:
return "train"
return "purged"
return "test"Ellenőrzés B-re: order_ts=2025-04-20 < cutoff és
label_available_at=2025-04-27 < cutoff, tehát
"train". G-re: order_ts < cutoff, de
label_available_at >= cutoff, tehát
"purged". E-re: order_ts >= cutoff, tehát
"test". A függvény a három feltételt a rövidzár-kiértékelés
miatt páronként kizáróan adja.
3. feladat. Mindkét sor hibás. X1-re
order_ts < cutoff, de
label_available_at >= cutoff, tehát a splitnek
purged-nek kellene lennie, nem train-nek.
X2-re ugyanez a helyzet: order_ts < cutoff és
label_available_at >= cutoff, tehát purged,
nem test. Az X1 tanítósorba kerülése jövőbeli
címke-információt szivárogtat a modellbe, ami optimistán torzítja a
validációs és teszteredményt. Az X2 tesztsorba kerülése viszont múltbeli
rendelést kever a jövőbeli tesztbe, ami nem a valós batch-feltételeket
méri, ezért a teszt AUC/log loss nem lesz érvényes becslése a jövőbeli
teljesítménynek. A javítás: mindkét sor kerüljön purged-be,
vagy a manifestet újra kell generálni a scriptből.
4. feladat. Az új . Ekkor:
A train sorok: A, B, D, H; , , tehát . A példa mutatja, hogy a cutoff eltolása nem egyszerűen áthelyezi a határt, hanem a purged/test viszonyt is átírja: C a testből purgedbe került, A pedig purgedből trainbe.
5. feladat. A
customer_previous_late_rate a delivery_days
múltbeli értékeiből képzett szám, tehát a befagyasztott adatlap tiltott
prediktora, függetlenül attól, hogy időben elvileg elérhető lenne. A
tiltólista ugyanis nemcsak a jövőbeli, hanem a
delivery_days bármely származékát kizárja. Ha ilyen mezőt
szeretnénk, új szerződésverzió kell, amely pontosan definiálja a leade
time-ot, a címke elérhetőségét és a batch-predikciós utánpótlást. A
jelen szerződésben megengedett előzmény-alternatíva lehet az ügyfél
korábbi rendeléseinek száma az order_ts előtt, mert az
order_ts sorozat rendeléskor ismert, és nem a célból
származik. Ugyanakkor a befagyasztott nyolcmezős lista ezt sem
tartalmazza; annak felvétele is új feature-indoklást és szerződésverziót
igényelne.
A leggyakoribb hiba azt hinni, hogy a delivery_days
közvetett használata ártatlan, ha az érték a predikciós időpontnál
régebbi. Egy ellenpélda: az „ügyfél korábbi rendeléseinek átlagos
delivery_dayse” minden számítási lépésben időben elérhető
lehet, mégis tiltott a jelen szerződésben, mert a befagyasztott lista a
delivery_days minden származékát kizárja. Ráadásul a
batch-kiszolgálásnál (Batch inference és idempotens
output) külön kellene igazolni, hogy a korábbi címkék ténylegesen
megérkeztek a modell futásáig, és a Feature-szelekció és magas dimenzió alfejezetben
látott szelekciós optimizmus is fenyegetne. A célváltozó múltbeli
értékeiből képzett prediktor a modellkártya (Robusztusság és korlátokkal írt modellkártya) „nem
támogatott használat” mezőjébe tartozna, amíg új szerződés nem írja
le.
Visszautalva a korábbi fogalmakra: a split-manifest az II. ellenőrző pont: adatból ellenőrzött
feature-tábla alfejezetben előállított feature-tábla fölé épül, de a
címkét csak a split után szabad csatolni. A predikciós szerződés (Predikciós szerződés és hibateszt) sémaellenőrzője
fogja később garantálni, hogy a betanított pipeline ugyanezt a nyolc
mezőt várja. A befagyasztás a Modelljelölt,
jóváhagyás és registry alfejezetből ismert registry-diszciplína
projekt előtti megfelelője: az adminisztratív verzió és a tudományos
minőség külön állítás, ezért a contract_id és a
modellmetrikák soha nem keverhetők egy sorba.
Önellenőrzés. Válaszolj röviden: (1) Mi a predikciós időbélyeg, és mi a címke elérhetősége? (2) Miért nem lehet a G rendelés a trainben a kézi példában? (3) Melyik feltétel teszi a három splitet páronként kizáróvá? (4) Miért nem elég a cutoff előtti/utáni felosztás?
A befagyasztott adatlap rögzíti, hogy a Northstar késésprojekt
rendelésszintű bináris célt becsül a rendelés pillanatában. A címke
,
és csak a label_available_at időpontban ismert. A
2025-05-01 cutoff három diszjunkt halmazt ad: tanító, teszt
és purged határsáv. Prediktorként csak a rendeléskor ismert nyolc mező
használható; a delivery_days,
actual_delivery_at, label_available_at,
target_late és származékaik tiltottak. A split manifest
verziózott, forrás-hash-sel ellátott fájl. A következő alkalmazás a Feature-pipeline, baseline és tanulás: ebből a
manifestből építjük a feature-pipeline-t, a baseline-t és az első
tanítást.