A Northstar késésbecslő projektben a modell feladata rendeléskor
megbecsülni, hogy a 7 napos ígért kézbesítési időt túllépi-e a csomag. A
célváltozó target_late bináris: értéke 1, ha
delivery_days > promised_days, egyébként 0. A
megfigyelési egység egy rendelés (order_id). A predikció
pillanata az order_ts; a modell csak az ekkor ismert
feature-öket használhatja, a címkét azonban csak a kézbesítés után
ismerjük meg. A kezdőlabor modellépítési időpontja
2025-05-01T00:00:00+00:00. Ebből készítünk
train/test/purged felosztást, amelyet egy split.json
manifestben rögzítünk, és ellenőrizzük, hogy minden tanítócímke
ténylegesen megérkezett a modellépítés időpontjáig.
Induló feladat a futtatás előtt: a 2400 rendelés 2025 első 180
napjára esik. A cutoff az év 121. napjára esik. Ha a kézbesítés
jellemzően néhány nap, körülbelül hány sor kerül a train, a purged és a
test halmazba? A labgenerátor pontos eloszlását nem ismerjük, ezért
becslésként a train és a test méretét az időeloszlás határozza meg, míg
a purged halmaz mérete a cutoff előtt leadott, de akkor még nem ismert
címkéjű rendelések számától függ; konkrét intervallum nem igazolható a
generátor futtatása nélkül közötti sor várható. A pontos számot a helyi
futtatás split.json fájlja adja meg.
A prediktív modell építésekor három adathalmazt különböztetünk meg:
A holdout olyan adathalmaz, amelyet a tanítás során információs értelemben nem érintünk: nem nézzük a címkéjét, nem választunk rajta küszöböt, és nem végezünk rajta feature-szelekciót. A teszt holdout célja a bevetési helyzet szimulálása: olyan rendeléseken mérni, amilyeneken a modell a jövőben dolgozik.
A target availability azt mondja meg, hogy a címke
mikor válik megfigyelhetővé. A Northstar szerződésben
label_available_at = actual_delivery_at: konzervatívan csak
a teljes kézbesítés pillanatában tekintjük ismertnek a
target_late értéket, még akkor is, ha egy késés korábban
már sejthető lenne. Ez a label-availability szerződés része; más
projektekben eltérő lehet, de akkor azt explicit dokumentálni kell.
A group fogalma akkor lép be, ha egy magasabb szintű
egység több sort is tartalmaz. A Northstar orders
táblájában egy ügyfélhez több rendelés is tartozhat. Ha a cél új
ügyfelekre való általánosítás, akkor a split nem sorszintű, hanem
ügyfélszintű kell legyen: ugyanaz az ügyfél ne kerüljön egyszerre
trainbe és testbe. A kezdőlabor időbeli splitet
használ, amely nem garantálja ezt a csoportszintű elkülönítést; ez
explicit korlát, nem hiba, ha a bevetési kérdés időbeli előrejelzés.
A horizon a predikciós időtáv. Itt 7 nap: a modell a rendelés pillanatában ad becslést a 7 napos ígéret túllépésére. A cutoff kiválasztása ehhez igazodik: a tesztablak a cutoff utáni rendeléseket tartalmazza, és a tesztcímkék a generált adatban teljesen megfigyeltek, tehát a későbbi értékeléshez rendelkezésre állnak, de a modellépítés pillanatában még nem.
Legyen
a modellépítés cutoffja, itt 2025-05-01T00:00+00:00. A
modell csak az
információs halmazra épülhet, vagyis arra, ami
-ban
ténylegesen ismert. Egy
rendelésre jelölje
order_ts
oszlop,label_available_at
oszlop.A három halmaz formálisan:
A feltételben a < szigorú: a cutoff pillanatában
éppen beérkező címkét nem tekintjük a múltban ismertnek. A purged sáv a
cutoff előtti rendelések közül azok, amelyek kézbesítése csak a cutoff
után történt meg; ezeket sem tanításra, sem tesztelésre nem használjuk.
A test kizárólag jövőbeli rendeléseket tartalmaz, ezért a teljes múltra
és a határsávra nézve is tiszta.
A random split akkor ad jól értelmezhető becslést az új, hasonló megfigyelésekre, ha a felosztás megfelel a célzott alkalmazási helyzetnek, és nincs kezeletlen csoport- vagy időfüggés. Az i.i.d. feltevés gyakori elegendő kiindulópont, de önmagában nem garantálja az érvényességet, ha például a mintavétel nem reprezentálja a bevetéskori populációt. A csoportos split a függetlenséget a csoport szintjén követeli meg: a sorok nem függetlenek, a csoportok viszont annak tekinthetők. Az időbeli split nem független mintákat feltételez, hanem időrendi validációt ad: a tanítóablak megelőzi a tesztablakot, így a nem-stacionaritás, a szezonális változás és a későbbi bevetés közelítő szimulációja lesz az értékelés. A példa tehát az Időtengely, resampling és állomásossági kérdés alfejezetben bevezetett időbeli gondolatot ülteti át modellértékelésbe.
A következő kód a lab projektgyökérből futtatható, feltételezve, hogy
a data/orders.csv a generált tiszta fájl. A kimenet
szemléltető, mert automatikusan még nem ellenőrzött
Python-kód; a tényleges számokat a saját lab futtatás adja.
import json
from pathlib import Path
import pandas as pd
# A CSV ISO timestampjei +00:00 offsettel rendelkeznek.
orders = pd.read_csv("data/orders.csv")
orders["order_ts"] = pd.to_datetime(orders["order_ts"], utc=True)
orders["label_available_at"] = pd.to_datetime(orders["label_available_at"], utc=True)
cutoff = pd.Timestamp("2025-05-01T00:00:00+00:00")
# A split három, egymást kizáró maszkja.
past_order = orders["order_ts"] < cutoff
label_available = orders["label_available_at"] < cutoff
train_mask = past_order & label_available
test_mask = orders["order_ts"] >= cutoff
purged_mask = past_order & ~label_available
# Invariáns ellenőrzések.
assert not (train_mask & test_mask).any(), "train és test átfed"
assert not (train_mask & purged_mask).any(), "train és purged átfed"
assert not (test_mask & purged_mask).any(), "test és purged átfed"
assert train_mask.sum() + test_mask.sum() + purged_mask.sum() == len(orders)
# A tanítócímke label-availability bizonyítása erre a futásra.
assert (orders.loc[train_mask, "label_available_at"] < cutoff).all(), (
"tanítócímke nem érkezett meg a modellépítés időpontjáig"
)
manifest = {
"split_id": "northstar-late-cutoff-2025-05-01",
"cutoff_utc": cutoff.isoformat(),
"split_rule": (
"train: order_ts < cutoff AND label_available_at < cutoff; "
"test: order_ts >= cutoff; "
"purged: order_ts < cutoff AND label_available_at >= cutoff"
),
"n_total": len(orders),
"n_train": int(train_mask.sum()),
"n_test": int(test_mask.sum()),
"n_purged": int(purged_mask.sum()),
"train_order_ids": orders.loc[train_mask, "order_id"].tolist(),
"test_order_ids": orders.loc[test_mask, "order_id"].tolist(),
"purged_order_ids": orders.loc[purged_mask, "order_id"].tolist(),
"train_positive_rate": float(orders.loc[train_mask, "target_late"].mean()),
"test_positive_rate": float(orders.loc[test_mask, "target_late"].mean()),
"max_train_label_available_at": (
orders.loc[train_mask, "label_available_at"].max().isoformat()
),
}
Path("artifacts").mkdir(exist_ok=True)
Path("artifacts/split.json").write_text(json.dumps(manifest, indent=2))
print(
f"train={manifest['n_train']}, test={manifest['n_test']}, "
f"purged={manifest['n_purged']}, "
f"max_train_label={manifest['max_train_label_available_at']}"
)A fájl artifacts/split.json néven keletkezik, és
tartalmazza a sorazonosítókat, a cutoffot, valamint a felosztási szabály
szöveges rögzítését. A manifest az II. ellenőrző
pont: adatból ellenőrzött feature-tábla alfejezetben tanult
adatszerződés gondolatát viszi tovább: a split nemcsak kód, hanem
dokumentált, visszakereshető artefaktum.
A maszkokat ellenőrizzük egy hat soros, kézzel átlátható példán is. A
cutoff legyen 2025-05-01T00:00+00:00.
order_id |
order_ts |
label_available_at |
besorolás |
|---|---|---|---|
| A | 2025-04-20 10:00 | 2025-04-25 10:00 | train |
| B | 2025-04-28 10:00 | 2025-04-30 10:00 | train |
| C | 2025-04-30 10:00 | 2025-05-03 10:00 | purged |
| D | 2025-05-01 10:00 | 2025-05-08 10:00 | test |
| E | 2025-05-02 10:00 | 2025-05-09 10:00 | test |
| F | 2025-04-29 10:00 | 2025-05-01 10:00 | purged |
A kézi számolás szerint train
,
purged
,
test
.
A C sor rendelése múltbeli, de a címke jövőbeli, ezért
purged. Az F sor címkéje a cutoff után érkezik, ezért
szintén purged, bár a rendelés még korábbi. Ez a példa mutatja, hogy a
label-availability szűrés nem helyettesíthető az order_ts
szerinti vágással.
A kód által kiírt max_train_label_available_at értékének
szigorúan kisebbnek kell lennie a cutoffnál; ez a bizonyíték arra, hogy
a tanítóhalmazban nincs a modellépítéskor még nem ismert címke. A
manifestben a train_positive_rate és
test_positive_rate a pozitív osztály arányát mutatja; a
teszt értékét csak a végső jelentésben szabad megnézni, mert a
tesztcímke a modellépítéskor tiltott információ.
A leggyakoribb hiba az, hogy a splitet kizárólag a feature-időre alapozzuk:
train = orders[orders["order_ts"] < cutoff]
test = orders[orders["order_ts"] >= cutoff]Tegyük fel, hogy egy rendelés
order_ts = 2025-04-29T10:00+00:00, ígért kézbesítése 7 nap,
tényleges kézbesítése 2025-05-03, ezért
label_available_at = 2025-05-03. A fenti kód trainbe teszi,
mert a rendelés időpontja múltbeli. A modellépítés pillanatában azonban
a target_late érték még nem létezik: a címke jövőbeli
információ. Ha a modell ezen tanul, akkor olyan célváltozót lát, amelyet
a bevetéskor soha nem kapna meg időben; ez
címke-szivárgás. A jövőbeli label elérhetősége tehát
akkor is számít, ha a feature-ök mindegyike a rendelés pillanatában
ismert. A javítás a label_available_at < cutoff feltétel
hozzáadása, a purged sáv kizárásával.
Visszautalás: a Jelölésből végrehajtható
számítás alfejezetben bevezetett formális jelölés itt válik döntési
eszközzé; az
információs halmaz és a < szigorúsága ugyanaz a
gondolat, mint az Időtengely, resampling és
állomásossági kérdés időbeli validációs követelménye.
Értelmezés. A purged sávba eső C
rendelést miért nem tesszük a testbe? Magyarázd el, milyen torzítást
okozna, ha mégis odakerülne.
Implementálás. Írj
make_time_split(df, cutoff) függvényt, amely egy
rendeléseket tartalmazó DataFrame-ből és egy cutoff timestampből
kiindulva visszaadja a train_mask, test_mask,
purged_mask boolean tömböket, valamint a manifest
dictionary-t a fenti szerkezetben. Teszteld a hat soros kézi példán, és
ellenőrizd, hogy a visszaadott maszkok összege 6.
Hibakeresés. Az alábbi split a label-availability szűrést kihagyja:
train = orders[orders["order_ts"] < cutoff]
test = orders[orders["order_ts"] >= cutoff]Adj meg egy konkrét order_id-t a fenti hat soros
példából, amelyik így trainbe kerül, pedig a címkéje a cutoffkor még nem
ismert. Melyik feltétel hiányzik a javításhoz?
Megváltozott feltétel. Változtasd a cutoffot
2025-04-15T00:00:00+00:00-re. A train, test és purged
halmaz mérete hogyan változik a 2025-05-01-eshez képest?
Indokold a label-availability feltétel és a rövidebb múlt
alapján.
A purged sávba eső sor order_ts < cutoff, tehát a
modellépítés pillanatában a rendelés már megtörtént. Ha testbe tennénk,
a teszt nem kizárólag jövőbeli rendeléseket tartalmazna, hanem olyan
múltbelieket is, amelyek címkéje a cutoff után érkezett. Ez kettős
torzítást okoz: egyrészt a teszteloszlás keveredik a határsávval,
másrészt a modell olyan múltbeli eseteken lenne értékelve, amelyekre a
bevetéskor nem lenne analógiája. A purged sáv ezért sem tanításra, sem
értékelésre nem használható; a kihagyott határsáv a cutoff közeli
szivárgás elleni konzervatív védelem.
A függvény vázlata:
def make_time_split(df, cutoff):
past_order = df["order_ts"] < cutoff
label_available = df["label_available_at"] < cutoff
train_mask = past_order & label_available
test_mask = df["order_ts"] >= cutoff
purged_mask = past_order & ~label_available
manifest = {
"cutoff_utc": cutoff.isoformat(),
"n_total": len(df),
"n_train": int(train_mask.sum()),
"n_test": int(test_mask.sum()),
"n_purged": int(purged_mask.sum()),
}
return train_mask, test_mask, purged_mask, manifestA hat soros példán n_total = 6,
n_train = 2, n_test = 2,
n_purged = 2, tehát az összeg 6. Az ellenőrzéshez a
maszkokra sum() hívható, mert a True értéke
1.
A C sor order_ts = 2025-04-30 10:00,
ami múltbeli, ezért az order_ts < cutoff feltétel
trainbe sorolja. A label_available_at = 2025-05-03 10:00
azonban a cutoff után érkezik, tehát a címke jövőbeli. A hiányzó
feltétel: label_available_at < cutoff. A javított train
feltétel:
(orders["order_ts"] < cutoff) & (orders["label_available_at"] < cutoff).
A 2025-04-15 cutoff rövidebb múltat hagy a trainnek,
ezért a train mérete csökken, a test mérete nő. A purged sáv aránya
várhatóan nő, mert a cutoff előtti rendelések közül nagyobb hányad
kézbesítése esik a cutoff utánra: a határsáv ugyanakkora kézbesítési
időablakot fed le, de a rendelkezésre álló múlt rövidebb, ezért a
purged/train arány romlik. A pontos számot a lab futtatása adja; a
tendencia a fenti időrendi szerkezetből következik.
A modellértékelés első lépése olyan splitet készíteni, amely a
bevetési helyzetet modellezi. A Northstar késésbecslő feladatban a
cutoff 2025-05-01, a train feltétele
order_ts < cutoff és
label_available_at < cutoff, a teszt a cutoff utáni
rendeléseket tartalmazza, a purged sáv a múltbeli, de még nem
kézbesített rendeléseket zárja ki. A splitmanifest rögzíti a
sorazonosítókat és a szabályt; az invariant ellenőrzés bizonyítja, hogy
minden tanítócímke megérkezett a modellépítés pillanatáig. A következő
alkalmazás Preprocessing tanulás kizárólag a
training részen alfejezetben ezt a manifestet használja, hogy a
preprocessing minden lépése kizárólag a train halmazon tanuljon.