A Northstar prediktív projektben a modellnek minden rendelés
leadásakor, az order_ts időpontban kell 0–1 közötti
kockázatot adnia arra, hogy a kézbesítés a 7 napos ígéretnél később
történik. A bemenet csak a rendelés pillanatában ismert jellemzőkből
állhat; a kimenet a target_late becslése. Ebben az
alfejezetben nem modellt építünk, hanem az adatéletciklus időrendjét
rögzítjük: minden mezőhöz hozzárendeljük, mikor válik ismertté, és
melyik fázis használhatja. A konkrét input a kezdőlabor
lab/orders.csv és lab/order_items.csv fájlja;
az elvárt eredmény egy rendelkezésreállási tábla, valamint egy ellenőrző
kód, amely kimutatja a jövőbeli mezők tiltott prediktorhasználatát.
Mielőtt tovább olvas, adjon előzetes becslést: az
orders.csv 15 mezője közül hány ismert már a rendelés
pillanatában, és hány csak később? Az order_items.csv 8
mezője közül hány későbbi?
Adatforrás (data source) az a rendszer vagy
folyamat, ahol az érték keletkezik. A rendelési rendszer a kosár
adatait, a szállítási rendszer a tényleges kézbesítést, az ügyféltörzs
az ügyfél törzsadatait szolgáltatja. A Northstar szintetikus esetben a
generate_data.py szimulálja ezeket a forrásokat; nincs
valós ügyfél vagy üzleti tranzakció.
Nyers és feldolgozott adat (raw/processed data) nem
ugyanaz. A lab/orders_raw.csv szándékosan hibás nyers
másolat: öt duplikált sor, két hiányzó distance_km, egy
negatív basket_minor és egy nem létező
customer_id szerepel benne. A tiszta
lab/orders.csv a tisztítás után keletkezett kontroll; a
döntéseket auditnaplónak kell kísérnie, nem szabad csendben nullát vagy
nullapontot feltételezni. A nyers és a tiszta adat tehát külön
életszakasz, és csak a tiszta verzióhoz tartozik rögzített minőségi
szerződés.
Label (címke) a felügyelt tanulás célértéke, amely a
tanításkor már ismert, a predikció pillanatában viszont még nem. A
Northstar késési feladatban a label a target_late: értéke
1, ha delivery_days > promised_days, egyébként 0. A
target_late maga származtatott mező; a
delivery_days ennek az alapanyaga, de szintén csak a
kézbesítés után ismert.
As-of idő (as-of time) az a pillanat, amikor egy
tábla állapotát nézzük. Ma lekérdezve a múltbeli rendelések már
tartalmazzák a delivery_days értékét, de egy 2025. áprilisi
predikciós pillanatban még nem létezett. Minden mezőhöz
hozzárendelhetünk egy ismertté válási időpontot: a mező legkorábban
attól kezdve használható, amikor az érték ténylegesen a rendelkezésünkre
áll. A kezdőprojekt konzervatív szerződése szerint a címke a teljes
kézbesítéskor válik ismertté:
label_available_at = actual_delivery_at, akkor is, ha a
késés tényét elvileg hamarabb is fel lehetne ismerni.
Provenance (származás, eredetigazolás) azt rögzíti,
honnan jött az adat, milyen átalakítási láncon ment át, és melyik verzió
volt az input. A kezdőlabor lab/manifest.json fájlja a
seedet, a sorszámokat, a hashértékeket és a kontrollösszegeket
tartalmazza; a quickcheck.py ezeket ellenőrzi. Ha egy
kolléga manifest nélkül ad át egy CSV-t, nem tudjuk reprodukálni az
állapotát, és nem tudjuk eldönteni, hogy a tisztítási döntések hol
történtek.
Jelölje
az
rendelés order_ts időpontját,
pedig a
mező ismertté válási időpontját. Egy
mező akkor szerepelhet a
pillanatban futó modell bemenetében, ha
A
jel itt időpontok UTC-sorrendjét jelenti; az egyenlőség azt fejezi ki,
hogy a rendelés leadásával egyidejűleg ismertté váló mező még éppen
használható. A delivery_days esetében
tehát a mező nem lehet prediktor. Az információs halmaz legyen azoknak az értékeknek a halmaza, amelyek -ben már ismertek. A fenti szabály így írható:
ahol
az
rendelés predikciós bemenete,
pedig a target_late label. A második feltétel azt mondja
ki, hogy a label soha nem lehet a predikció inputja, még akkor sem, ha a
tanító CSV-ben fizikailag ott van.
A label definíciója nap mértékegységben:
ahol
az indikátorfüggvény: értéke 1, ha a kapcsos zárójelben lévő feltétel
igaz, különben 0. A promised_days a kezdőpéldában 7 nap, a
delivery_days pozitív egész nap.
A tanítási cutoff $c = $ 2025-05-01T00:00:00+00:00. Egy
rendelés akkor használható tanításra, ha a rendelés és a címke is a
cutoff előtti:
A szorzás itt logikai ÉS: a sor csak akkor kap 1-et, ha mindkét feltétel igaz. A teszt feltétele
a kihagyott határsáv pedig
A három halmaz diszjunkt, és együtt lefedi az összes rendelést. Kézi
kontroll: ha order_ts = 2025-04-28T09:00:00+00:00,
label_available_at = 2025-05-04T18:00:00+00:00, akkor a
rendelés a kihagyott határsávba esik: a rendelés ugyan a cutoff előtti,
de a címke a cutoffkor még nem érkezett meg. A tanításhoz a címkének
szigorúan a cutoff előtt kell rendelkezésre állnia.
A következő táblázat az orders.csv mezőit a
lab/ADATOK.md szerződéséből sorolja fel. A
oszlop a rendelés pillanatát jelenti; a „prediktor” oszlop a
rendelkezésre állás és a kezdő feature-lista együttes ítélete.
| Mező | Keletkezés / ismertté válás | Ismert -kor? | Szerep a starterben |
|---|---|---|---|
order_id |
rendelés rögzítésekor, | igen | azonosító, nem prediktor |
customer_id |
ügyféltörzs, előtt | igen | azonosító, nem prediktor |
order_ts |
rendelés rögzítésekor | igen | a predikció időpontja |
promised_days |
szolgáltatási szabály, | igen | konstans 7, a label definíciójához kell |
quantity_sum |
kosár, | igen | feature |
item_rows |
kosár, | igen | elérhető, a starter listán nem szerepel |
basket_minor |
kosár, | igen | feature |
shipping_fee_minor |
díjszabás, | igen | elérhető, a starter listán nem szerepel |
currency |
díjszabás, | igen | konstans EUR |
distance_km |
logisztikai rendszer, | igen | feature |
priority_flag |
szolgáltatási szabály, | igen | feature |
delivery_days |
tényleges kézbesítéskor | nem | tiltott prediktor; a label alapanyaga |
actual_delivery_at |
tényleges kézbesítéskor | nem | tiltott; a label időbélyege |
label_available_at |
származtatott: = actual_delivery_at |
nem | tiltott; a taníthatóság kapuja |
target_late |
származtatott: delivery_days > 7 |
nem | a tanulási label, nem input |
Az order_items.csv mezői:
| Mező | Keletkezés | Ismert -kor? | Szerep |
|---|---|---|---|
order_id |
rendelés rögzítésekor | igen | join kulcs |
line_id |
rendelés rögzítésekor | igen | tételsor azonosító |
product_id |
katalógus | igen | nem starter feature |
quantity |
kosár, | igen | nem starter feature |
unit_price_minor |
árazás, | igen | nem starter feature |
discount_minor |
árazás, | igen | nem starter feature |
line_total_minor |
származtatott, | igen | nem starter feature |
currency |
árazás, | igen | konstans EUR |
Az order_items minden mezője a rendelés leadásakor
ismert: a kosár tételei akkor rögzülnek. A kezdő modell mégsem
közvetlenül ezeket a sorokat használja, hanem az orders
táblában lévő kosárösszegzőket. A későbbi SQL-fejezetben látjuk majd,
hogy a tételszintű join miért duplázhatja a szállítási díjat, mert az
shipping_fee_minor rendelésenként egyszer szerepel.
A kezdő feature-listában szereplő segment,
region, acquisition_channel a
customers.csv-ből, az account_age_days pedig a
signup_date és az order_ts különbségeként
származik. Mivel a regisztráció dátuma minden rendelés előtt van, ezek a
mezők a rendelés pillanatában szintén ismertek.
A következő kód a projektgyökérből futtatva a két CSV fejlécét
ellenőrzi a várt halmazokkal, és megnézi, hogy a jövőbeli mezők nem
kerültek-e a starter feature-listába. A kód csak a Python standard
libraryjét használja. Az itt látható kimenet a szerződésből levezetett
várt kimenet, nem tényleges futási jegyzék; a laborban való futtatáskor
a lab/manifest.json alapján ellenőrizhető.
from pathlib import Path
import csv
PREDICTION_TIME_FIELDS = {
"order_id", "customer_id", "order_ts", "promised_days",
"quantity_sum", "item_rows", "basket_minor", "shipping_fee_minor",
"currency", "distance_km", "priority_flag",
}
LATER_FIELDS = {
"delivery_days", "actual_delivery_at", "label_available_at", "target_late",
}
FORBIDDEN_AT_PREDICTION_TIME = LATER_FIELDS
STARTER_FEATURES = {
"distance_km", "priority_flag", "basket_minor", "quantity_sum",
"account_age_days", "segment", "region", "acquisition_channel",
}
ORDER_ITEMS_EXPECTED = {
"order_id", "line_id", "product_id", "quantity",
"unit_price_minor", "discount_minor", "line_total_minor", "currency",
}
def header(path):
with Path(path).open(newline="", encoding="utf-8") as f:
return set(csv.DictReader(f).fieldnames or [])
orders_fields = header("lab/orders.csv")
items_fields = header("lab/order_items.csv")
print("orders mezők száma:", len(orders_fields))
print("hiányzó orders mezők:", sorted((PREDICTION_TIME_FIELDS | LATER_FIELDS) - orders_fields))
print("váratlan orders mezők:", sorted(orders_fields - (PREDICTION_TIME_FIELDS | LATER_FIELDS)))
print("order_items hiányzó mezők:", sorted(ORDER_ITEMS_EXPECTED - items_fields))
print("order_items váratlan mezők:", sorted(items_fields - ORDER_ITEMS_EXPECTED))
assert FORBIDDEN_AT_PREDICTION_TIME.isdisjoint(STARTER_FEATURES)
print("szivárgás-ellenőrzés: OK")Futtatás platformjelölt munkakönyvtárral:
# Linux/macOS terminál, a projektgyökérből:
python s002_availability_check.py# Windows PowerShell, a projektgyökérből:
py s002_availability_check.pyA csv.DictReader a fejlécet olvassa be; a
fieldnames az oszlopnevek listája, a set()
pedig halmazként kezeli őket, így a sorrend nem számít. A halmazkivonás
(-) a hiányzó és váratlan mezőket adja; a
sorted() csak a kimenet megjelenítését teszi
determinisztikussá.
A szerződésből levezetett várt kimenet:
orders mezők száma: 15
hiányzó orders mezők: []
váratlan orders mezők: []
order_items hiányzó mezők: []
order_items váratlan mezők: []
szivárgás-ellenőrzés: OK
A 15 orders-mezőből 11 a PREDICTION_TIME_FIELDS, 4 a
LATER_FIELDS halmazban van; a két halmaz diszjunkt, és az
order_items 8 mezője mind a rendeléskor ismert. Az
assert FORBIDDEN_AT_PREDICTION_TIME.isdisjoint(STARTER_FEATURES)
az invariáns: ha valaki véletlenül beteszi a
delivery_days-t a starter feature-listába, a
delivery_days, actual_delivery_at,
label_available_at vagy target_late bármelyike
azonnal AssertionError-t ad. Az üres halmazok tehát nem
formálisak: pontosan azt ellenőrzik, hogy a CSV séma nem tért el a
meghirdetett szerződéstől, és a prediktorhalmaz nem lát a jövőbe.
Két időbélyeg, két eltérő szabály. A forrásesemény időpontja
(event_time) és a rendszerben való tényleges elérhetőség
(available_at/ingested_at) különbözhet. Egy
feature csak akkor megengedett a
predikciós pillanatban, ha az előállításához szükséges minden esemény
és minden adat hozzáférhető volt legkésőbb
-kor.
Az utólag javított, visszadátumozott mező nem tekinthető történetileg
ismertnek.
Ellenpróba: készíts olyan ügyfelet, akinek hétfőn keletkezett, de szerdán beérkezett eseménye van. A keddi as-of tábla nem használhatja ezt az eseményt, még akkor sem, ha az esemény időbélyege hétfő.
Értelmezés. Egy rendelés
order_ts = 2025-04-28T09:00:00+00:00,
label_available_at = 2025-05-04T18:00:00+00:00. A tanítási
cutoff 2025-05-01T00:00:00+00:00. Melyik split-halmazba
tartozik a sor? Fogalmazza meg mindkét kritériumot, és mondja ki, miért
nem kerülhet sem a tanító, sem a teszt halmazba.
Megvalósítás. Írjon
split_label(order_ts, label_available_at, cutoff)
függvényt, amely a "train", "test" vagy
"purgatory" sztringet adja vissza. Tesztelje a három
esetet: (a) rendelés 2025-04-10, label 2025-04-20; (b) rendelés
2025-05-02, label 2025-05-10; (c) rendelés 2025-04-28, label
2025-05-04.
Hibakeresés. Egy kolléga 0,99 teszt AUC-t mutat
a késési modellre, mert a feature-listába betette a
delivery_days oszlopot. Mi a hiba lényege? Hogyan mutatja
ki a rendelkezésreállási tábla? Mi a javítás, és mit mondana a 0,99-es
AUC-ról?
Megváltozott feltétel. A logisztikai rendszer új
planned_dispatch_date mezőt ad, amely a rendelés leadásakor
ismert tervezett feladási napot tartalmazza. A rendszer azonban ezt a
dátumot utólag is frissíti. Milyen feltétellel lehet prediktorként
használni, és mit kell rögzíteni a provenance-ban?
A sor a kihagyott határsávba tartozik. A tanítási feltétel első
fele igaz: order_ts < cutoff, mert 2025-04-28 megelőzi
2025-05-01-et. A második fele hamis:
label_available_at = 2025-05-04 18:00, ami nem szigorúan
korábbi a cutoffnál. A teszt feltétele is hamis, mert a teszt
definíciója order_ts >= cutoff. A rendelés tehát sehol
sem tanítható a cutoff ismeretében, és a modell buildjekor nem
játszhatja el, hogy a címkéje már rendelkezésre áll.
Lehetséges megoldás Python 3.12 datetime
moduljával:
from datetime import datetime
def split_label(order_ts, label_available_at, cutoff="2025-05-01T00:00:00+00:00"):
t0 = datetime.fromisoformat(order_ts)
tl = datetime.fromisoformat(label_available_at)
c = datetime.fromisoformat(cutoff)
if t0 >= c:
return "test"
if tl < c:
return "train"
return "purgatory"
assert split_label("2025-04-10T09:00:00+00:00", "2025-04-20T18:00:00+00:00") == "train"
assert split_label("2025-05-02T09:00:00+00:00", "2025-05-10T18:00:00+00:00") == "test"
assert split_label("2025-04-28T09:00:00+00:00", "2025-05-04T18:00:00+00:00") == "purgatory"A datetime.fromisoformat a +00:00 offsetet
is értelmezi, így mindhárom érték időzóna-tudatos. Ha valamelyik bemenet
nem tartalmazna offsetet, a naiv és az offset-tudatos időpont
összehasonlítása TypeError-t adna; a szerződés éppen ezért
követeli meg az explicit +00:00 offsetet. A várt
ellenőrzési invariáns: adott cutoff mellett a három halmaz diszjunkt, és
minden sor pontosan egy címkét kap.
A hiba a jövőbeli adat szivárgása. A rendelkezésreállási tábla
szerint a delivery_days ismertté válási ideje
actual_delivery_at, ami mindig későbbi az
order_ts predikciós pillanatnál. A teszt kiértékelésekor a
modell olyan inputot kapott, amely a termelési predikció pillanatában
nem létezik; ezért a 0,99 AUC nem valós képességet mér, hanem azt, hogy
a modell megtanulta a kézbesítési eredményt. A javítás: törölni a
delivery_days-t a prediktorlistából, a pipeline-t
változatlan beállításokkal újra futtatni, és az új, ténylegesen mért
teszt AUC-t értékelni. A 0,99 így nem bizonyíték, hanem a folyamat
hibájának tünete.
A mező a rendelkezésreállási táblában akkor kaphat „ismert
-kor”
bejegyzést, ha a planned_dispatch_date a rendelés
leadásakor ténylegesen kiszámítható, és a modell inputja a rendeléskori
érték. Ha a rendszer utólag frissíti, a CSV-ben szereplő jelenlegi érték
már nem feltétlenül az a dátum, amelyet a rendelés pillanatában
ismertek; ebben az esetben a frissített mező jövőbeli információt
tartalmaz. Csak as-of pillanatfelvételből szabad dolgozni: a táblának
azt az értéket kell tárolnia, amely az order_ts időpontban
érvényes volt. A provenance-ban rögzíteni kell a forrást, az érték
időbélyegét, a generáló folyamat verzióját és azt a szabályt, hogy a
logisztikai rendszer utólagos frissítései nem írhatják felül a
predikciós inputot.
A leggyakoribb félreértés: a cél kiszámításához szükséges
jövőbeli adat nem automatikusan engedélyezett feature. A
delivery_days a tanító CSV-ben fizikailag jelen van, és a
modell kiválóan illeszkedik rá; a teszt AUC csábítóan magas lehet. De a
predikció pillanatában a kézbesítés még nem történt meg, így a mező nem
létezik. Ugyanez igaz a label_available_at-re: ez nem a
label értéke, hanem a taníthatóság időbélyege, és prediktorként
használva szintén a jövőbe lát. Az s001-ben rögzítettük,
hogy a modell a rendelés pillanatában fut; most az as-of idő ezt a
pillanatot formális korláttá teszi. Önellenőrző kérdés minden új
feature-höz: „Ismerném-e ezt az értéket akkor, amikor a rendelés
beérkezik?” Ha a válasz nem, a mező tiltott, akármilyen jó AUC-t
ígér.
Minden mezőhöz rendeljünk ismertté válási időt: egy prediktor csak
akkor használható, ha az érték a predikció pillanatában már létezik. A
Northstar orders táblájában 11 mező ismert a rendeléskor, 4
pedig csak kézbesítés után; a delivery_days a label
alapanyaga, nem prediktor. A tanításhoz a rendelésnek és a címkének is a
cutoff előtt kell lennie; a kihagyott határsáv a címke késése miatt nem
tanítható sorokat gyűjti. A nyers és tiszta adat, a manifest és az
auditnapló együtt adja a provenance-t. A következő alfejezetben ezt az
időbeli fegyelmet a reprodukálható munkanaplóba építjük be.