Egy négytagú adatcsapat a Northstar prediktív feladaton dolgozik:
rendeléskor kell megbecsülni annak a valószínűségét, hogy a kézbesítés 7
napnál később történik. A modell a rendelés pillanatában ismert nyolc
feature-t használja: distance_km,
priority_flag, basket_minor,
quantity_sum, account_age_days,
segment, region,
acquisition_channel. A cél target_late, értéke
1, ha delivery_days > 7, különben 0.
A bemenet a mellékelt lab/data/customers.csv (300
ügyfél), lab/data/orders.csv (2400 rendelés) és
lab/data/order_items.csv (4796 tételsor). A megfigyelési
egység egy rendelés, a predikció időpontja order_ts. A
modellépítés a starter szerződés szerint a 2025-05-01 cutoff előtti,
címkeszempontból már teljesen megfigyelt rendeléseket használja. A jelen
alfejezet kérdése nem új modell, hanem eszközdöntés: elég-e a
verziózott Parquet snapshot és a verziózott SQL, vagy külön feature
store-t kell üzemeltetni?
Induló becslés: a nyers input néhány MB alatt van; egy cutoffszűrés után a tanuló feature-tábla nagyságrendileg 100–300 sor; a számítás napi batchben másodpercek alatt várható. Egyetlen modell, egy csapat, nincs 100 ms alatti online kiszolgálási SLA. Ebből az első becslés az, hogy Parquet + verziózott SQL + feature-szerződés elegendő, és a külön feature store csak későbbi feltételváltozáskor indokolt. A pontos döntéshez az alábbi fogalmakat kell megkülönböztetni.
A feature a modell bemeneti oszlopa egy adott megfigyelési egységre, egy adott predikciós időpontban. A feladat szempontjából nem az a fontos, hogy a mérnök hogyan nevezi, hanem az, hogy mikor áll rendelkezésre.
account_age_days, mert az
order_ts és a signup_date különbsége a
rendelés pillanatában ismert, és a tanításhoz minden múltbeli rendre
újraszámítható.distance_km,
priority_flag, basket_minor,
quantity_sum, mert ezek az aktuális rendelés sorából
azonnal olvashatók.A kettő nem a feature belső tulajdonsága, hanem a
felhasználás időzítési szerződése. Ugyanaz a
account_age_days lehet offline feature a modelltanításkor
és online feature akkor, ha egy webszolgáltatás a rendelés pillanatában
kéri le. A döntő kérdés: a forrásadathoz képest mekkora késéssel áll
elő, és ez belefér-e a fogyasztó időkorlátjába.
A orders.csv szerződése szerint a rendelés sora
tartalmazza a rendelés pillanatában ismert mezőket; a célváltozó és a
belőle képzett oszlopok (delivery_days,
actual_delivery_at, label_available_at)
viszont később ismertek meg, ezért predictorba nem kerülhetnek. Ez
ugyanaz a szerződés, amelyet a Predikciós szerződés
és hibateszt predikciós séma-ellenőrzésként vezetett be.
A feature-táblának nem elég „helyesnek” lennie egyszer; a tanítás és a későbbi scoring más-más időpontban fut, ezért a fogalmakat verzióhoz kell kötni.
feature_contract.json például rögzítheti a
features_v2 sémaverziót.ahol mindkét tag időbélyeg, a különbség órában vagy napban értendő, a
kivonás soronkénti. Ha
,
ahol
a szerződéses küszöb, a feature elavultnak minősül. A képlet
diagnosztikai jelzés, nem valószínűség. A signup_date
esetében a forrás a rendelés előtt már ismert, ezért
;
egy last_14d_order_count jövőbeli feature-nél viszont a
rendelési tábla töltési késése már releváns lenne.
A verzió nélkül a „feature store” kifejezés gyakran csak egy közös mappa, ahonnan mindenki mást olvas. A Predikciós szerződés és hibateszt alfejezetben bevezetett predikciós szerződés itt folytatódik: a tanításkor és a scoringnál ugyanazt a sémát kell érvényesíteni, különben a modell csendben eltolódik.
A backfill múltbeli időszakok feature-értékeinek
újraszámítása, például amikor a account_age_days
definícióját finomítjuk, vagy új feature-t vezetünk be. A
kulcskövetelmény az időhelyes join: múltbeli rendelésre
nem használhatunk olyan ügyféltulajdonságot, amelyet a rendelés
pillanatában még nem ismertek. A Northstar account_age_days
esetében ez egyszerű, mert a signup_date a rendeléskor
ismert. Egy későbbi last_14d_order_count feature-nél
viszont a joinnak rendelésenként az order_ts előtti ablakot
szabad csak néznie.
A feature store ilyen értelemben nem egyetlen szoftver, hanem tervezési minta, amely jellemzően négy részt kapcsol össze:
A döntési feljegyzéshez érdemes feltételként kezelni, nem általános igazságként:
| Igény | Parquet + verziózott SQL | Külön feature store |
|---|---|---|
| Kis adat, 1-2 modell, napi batch | elegendő, kisebb fenntartási teher | általában túlzó |
| Több csapat, közös definíciók | széteshet, szóbeli egyeztetés kell | registry és tulajdonosi határok hasznosak |
| 200 ms alatti scoring | a fájl vagy adatbázis nem garantálja | online tároló + gyorsítótár indokolt |
| Skálázódó backfill és audit | manifest + asof query is működhet | centralizált újraszámítás segíthet |
A konkrét termékválasztás itt szándékosan nem része a tananyagnak; egy Feast, Tecton vagy felhőszolgáltatás igénybevétele aktuális, verzióhoz kötött dokumentációt és mérést igényelne.
A példa egyetlen gépen, CPU-n fut. Munkakönyvtár a projektgyökér
lab/ mappája. A kód a lab pyproject.toml
Python-környezetét használja; a Parquet íráshoz pyarrow
szükséges. A kódot itt nem futtattuk, a feltüntetett kimenet
szemléltető.
from pathlib import Path
import hashlib
import json
import pandas as pd
ROOT = Path("artifacts/feature_snapshot")
ROOT.mkdir(parents=True, exist_ok=True)
customers = pd.read_csv("data/customers.csv")
orders = pd.read_csv("data/orders.csv")
CUTOFF = pd.Timestamp("2025-05-01T00:00:00+00:00")
# Időhelyes join: a signup_date a rendeléskor már ismert,
# tehát nyugodtan használható az account_age_days-hoz.
orders_c = orders.merge(
customers[["customer_id", "signup_date"]],
on="customer_id",
how="left",
validate="many_to_one",
)
order_ts = pd.to_datetime(orders_c["order_ts"], utc=True)
signup_ts = pd.to_datetime(orders_c["signup_date"], utc=True)
orders_c["account_age_days"] = (order_ts - signup_ts).dt.days
# Kézi kontroll: 2025-01-01-től 2025-03-31-ig 89 nap,
# 2025-02-10-től 2025-03-31-ig 49 nap telik el.
check = pd.DataFrame(
{
"signup_date": ["2025-01-01", "2025-02-10"],
"order_ts": [
"2025-03-31T12:00:00+00:00",
"2025-03-31T12:00:00+00:00",
],
}
)
check_a = (pd.to_datetime(check["order_ts"], utc=True)
- pd.to_datetime(check["signup_date"], utc=True)).dt.days
assert list(check_a) == [89, 49]
FEATURE_NAMES = [
"distance_km",
"priority_flag",
"basket_minor",
"quantity_sum",
"account_age_days",
"segment",
"region",
"acquisition_channel",
]
TARGET = "target_late"
ORDER_KEY = "order_id"
# Astarter_cutoffba illeszkedő, címkével már rendelkező tanulósorok.
label_avail = pd.to_datetime(orders_c["label_available_at"], utc=True)
training = orders_c[
(order_ts < CUTOFF) & (label_avail < CUTOFF)
].copy()
snapshot = training[[ORDER_KEY, "order_ts"] + FEATURE_NAMES + [TARGET]].copy()
# Sémaszerződés: ugyanaz, amit a s084 predikciós validátora használ.
contract = {
"schema_version": "features_v2",
"created_utc": "2026-10-20T10:00:00+00:00", # példa, nem mért futás
"observation_unit": "order_id",
"prediction_time_feature": "order_ts",
"features": FEATURE_NAMES,
"target": TARGET,
"categorical_levels": {
"segment": ["consumer", "business"],
"region": ["north", "central", "south"],
"acquisition_channel": ["search", "referral", "direct"],
},
"allow_missing_in_snapshot": False,
}
(ROOT / "feature_contract.json").write_text(
json.dumps(contract, indent=2), encoding="utf-8"
)
snapshot.to_parquet(ROOT / "train_features_v2.parquet", index=False)
# Logikai összegzés. A hash itt artifact-ujjlenyomat, nem
# környezetközi bitazonos ígéret, mert a writer verziója eltérhet.
logical_summary = {
"rows": len(snapshot),
"columns": list(snapshot.columns),
"missing_feature_values": int(snapshot[FEATURE_NAMES].isna().sum().sum()),
"segment_levels": sorted(snapshot["segment"].dropna().unique().tolist()),
"feature_hash_sha256_12": hashlib.sha256(
snapshot[FEATURE_NAMES].to_csv(index=False).encode("utf-8")
).hexdigest()[:12],
}
print(logical_summary)Futtatás a projektgyökérből Linux/macOS terminálon vagy PowerShellben:
cd lab
python feature_store_snapshot.pySzemléltető, nem futtatott kimenet:
{'rows': 184, 'columns': ['order_id', 'order_ts', 'distance_km',
'priority_flag', 'basket_minor', 'quantity_sum', 'account_age_days',
'segment', 'region', 'acquisition_channel', 'target_late'],
'missing_feature_values': 0,
'segment_levels': ['business', 'consumer'],
'feature_hash_sha256_12': '3f9a2c...'}
A sor- és hashértékek a tényleges futtatástól függnek; itt csak a logikai invariánsok ellenőrzendők.
SQL-verzió ugyanarra a backfillre DuckDB dialektusban, a
lab/ munkakönyvtárban:
SELECT o.order_id,
o.order_ts,
o.distance_km,
o.priority_flag,
o.basket_minor,
o.quantity_sum,
date_diff('day', c.signup_date::DATE, o.order_ts::DATE) AS account_age_days,
o.segment,
o.region,
o.acquisition_channel,
o.target_late
FROM orders o
JOIN customers c ON o.customer_id = c.customer_id
WHERE o.order_ts < '2025-05-01T00:00:00+00:00'
AND o.label_available_at < '2025-05-01T00:00:00+00:00';A label_available_at nem kerül a feature-oszlopok közé;
csak a cutoffszűrés feltétele.
A tábla egy sora egy rendelés, a sorrendet az order_id
azonosítja. A numerikus oszlopok dtype-ja int64 vagy
float64, a kategóriák szövegesek. A
feature_contract.json features listájának
pontosan a nyolc megengedett oszlopot kell tartalmaznia, és a
céloszlopnak a snapshotban a sémaszerződés szerinti utolsó pozícióban
kell állnia. Ez ugyanaz a minimális predikciós szerződés, amelyet a Predikciós szerződés és hibateszt a scoring oldalon
kért.
A kézi kontroll arra figyelmeztet, hogy a
account_age_days a két ismert dátummal 89 és 49 nap; ha a
backfill során ezektől eltérő érték jön, a hiba a
forrásidőpont-feldolgozásban keresendő. A snapshot hiányzó értékmentes,
tehát a Hiány és anomália mint vizsgálati kérdés
tartományellenőrzés itt nem pótolhatja a forrásfolyamat minőségi
kapuját, csak jelzi a tünetet. A hash az artifact átadásához
használható; numerikus reprodukálhatóságra azonban a példa nem tesz
bitazonos ígéretet.
Értelmezés — döntési feljegyzés. A Northstar adat 300 ügyfélből, 2400 rendelésből és 4796 tételsorból áll. Egyetlen modell készül, napi batch futással, nincs 200 ms alatti online SLA, és a csapat négy fő. Írj 5–7 mondatos feljegyzést arról, miért elég a verziózott Parquet snapshot + SQL, és nevezz meg legalább egy konkrét feltételváltozást, amely külön feature store-t indokolna.
Implementálás — sémavalidátor. Írj
validate_feature_table(df, contract) függvényt, amely a
fenti feature_contract.json szerkezetet használja.
Ellenőrizze az oszlopok sorrendjét (order_id,
order_ts, feature-k, target_late), a numerikus
oszlopok dtype-ját, a kategóriaszinteket és a hiányzó értékeket. Hibák
esetén listát adjon vissza, érvényes táblára üres listát.
Hibadiagnózis. Egy módosított backfill futtatása
után 12 tanulósorban account_age_days < 0. Mit mond ez
az adatfolyamatról, és miért nem oldja meg a probléma kizárólag a
feature store registry frissítése? Melyik réteg feladata a hiba
blokkolása?
Megváltozott feltétel. A projekt hat hónappal
később 2 adatcsapat-tal és 3 ML-mérnökkel bővül. 20 modell használ közös
feature-öket, közöttük last_14d_order_count és
mean_basket_7d. A scoring követelmény P95 < 200 ms, és a
feature-ket több rendszer is fogyasztja. Fogalmazd meg, miért válik
indokolttá a külön feature store, és mi marad változatlanul a csapat
felelőssége a leakage elkerülésében.
1. megoldás. A döntési feljegyzés lényege: az adat
kicsi, egy fogyasztó van, a kiszolgálás batch, ezért a Parquet snapshot
+ verziós szerződés + SQL-transzformáció teljes auditnyomot ad kisebb
üzemeltetési teherrel. A külön feature store-t a következő feltétel
indokolja: több csapat ugyanazt a feature-t több modellben szeretné
újrahasznosítani, alacsony késleltetésű online scoring jelenik meg, vagy
a backfill és a frissességi audit már nem követhető kézzel. Ellenőrzés:
a feature_contract.json és a snapshot hash ugyanaz a
tanítás és az értékelés artifactjain.
2. megoldás. Egy lehetséges implementáció:
import pandas as pd
def validate_feature_table(df, contract, with_target=True):
expected = ["order_id", "order_ts"] + list(contract["features"])
if with_target:
expected = expected + [contract["target"]]
errors = []
if list(df.columns) != expected:
errors.append(
f"column_order: {list(df.columns)} != {expected}"
)
numeric = {
"distance_km", "priority_flag", "basket_minor",
"quantity_sum", "account_age_days",
}
for col in numeric & set(contract["features"]):
if not pd.api.types.is_numeric_dtype(df[col]):
errors.append(f"{col}: not numeric dtype")
for col, allowed in contract["categorical_levels"].items():
actual = set(df[col].dropna().unique())
unknown = actual - set(allowed)
if unknown:
errors.append(f"{col}: unknown levels {sorted(unknown)}")
if not contract.get("allow_missing_in_snapshot", False):
n_missing_rows = df[contract["features"]].isna().any(axis=1).sum()
if n_missing_rows:
errors.append(f"rows with missing feature: {n_missing_rows}")
return errorsInvariáns: érvényes táblára [], egy elcserélt oszlopra
az első hibabejegyzés, ismeretlen segment értékre a
harmadik hibabejegyzés jelenik meg. A függvény nem tanít modellt és nem
imputál; a Hiány és anomália mint vizsgálati
kérdés minőségi kapu kiegészítője.
3. megoldás. A negatív account_age_days
azt jelzi, hogy a forrásban a signup_date későbbi, mint az
order_ts, ami sérti a Northstar szerződést: a regisztráció
minden rendelés előtt van. A lehetséges ok hibás időzóna-értelmezés,
adatpatch vagy rossz forrás snapshot. A feature store nem tudja
visszamenőleg helyesbíteni a forrást; a registry frissítése csak a
tünetet takarhatná el. A blokkolás a belépési adatminőségi kapu dolga: a
snapshot készítése előtt tartományellenőrzést kell futtatni, például
min(account_age_days) >= 0 feltétellel. A javítás után a
12 sor eltűnik a hibajelentésből.
4. megoldás. A külön feature store indokolt, mert a
közös definíciók és a 200 ms-os P95 követelmény már nem teljesíthető
megbízhatóan közös Parquet-fájlokkal, továbbá a
last_14d_order_count és mean_basket_7d minden
scoring hívásra friss, időablakos aggregátumot igényel. Az offline réteg
továbbra is verziózott batchben számolhat; az online réteg
gyorsítótárként materializál. Ami nem változik: a predikció pillanatában
nem ismert oszlopok továbbra sem kerülhetnek feature-be, a foldon belüli
preprocessing-t a VI. ellenőrző pont:
szivárgásmentes modellkísérlet szabálya szerint a training részen
belül kell tartani, és a Predikciós szerződés és
hibateszt szerződésnek a tanítás és a scoring oldalon azonosnak kell
lennie.
Veszélyes állítás: „Mivel a feature store központosított, a
belőle olvasó modellek nem szenvedhetnek leakage-tól.” Ez nem igaz.
Tegyük fel, hogy a központi tárolóba valaki felveszi a
delivery_days oszlopot, mert az üzleti elemzőknek hasznos.
A predikciós modell feature-listája véletlenül *-gal olvas,
vagy egy későbbi szelekciós lépés automatikusan beválasztja. A modell
tanítási pontossága mesterségesen magas lesz, a scoring viszont a valós
rendelés pillanatában nem látja a célváltozót, ezért élesben látványosan
gyengül. A központosítás itt nem csökkenti, hanem növeli a hiba
hatókörét. A korábbi tanulságok érvényesek maradnak: a VI. ellenőrző pont: szivárgásmentes modellkísérlet
szerint a preprocessing és a szelekció a training foldon belül él, a Predikciós szerződés és hibateszt szerint a
célváltozónak és a későn érkező oszlopoknak kívül kell maradniuk a
sémaengedélyen, a Hiány és anomália mint vizsgálati
kérdés szerint pedig a hiányzás és az anomália a forrásfolyamat
vizsgálati kérdése, nem a tároló utólagos „javítása”.
Dátum-e van a döntési feljegyzésen, és a feature-táblának van-e
egyértelmű sémaszerződése? A célváltozó kimaradt-e a feature-listából? A
backfill minden sorára ugyanaz az asof feltétel érvényesül, és a join a
predikció pillanatában ismert adatot használ? A tanításnál és a
scoringnál azonos a schema_version és a kategóriaszintek? A
frissességi késés mértéke és mértékegysége rögzített? A Parquet
snapshotnak van hash-e és sorazonosítója? Ha ezekre igen a válasz, a
Parquet + SQL döntés védhető; külön feature store a méret, késleltetés
vagy csapatproblémából következzen, ne a „jó gyakorlat” címkéből.
A feature store választását a point-in-time helyesség, online/offline konzisztencia, hozzáférésvezérlés és üzemeltetési költség alapján indokoljuk; kis csapatnál a verziózott snapshot és SQL is lehet jó megoldás.
A feature store döntés nem általános modernségi kérdés, hanem konzisztencia-, késés- és csapatméret-alapú mérnöki választás. A Northstar projektben a kis adat, egy modell és a batch scoring miatt elég a verziózott Parquet snapshot, a JSON sémaszerződés és a verziózott SQL. A központi tároló akkor indokolt, ha több csapat és modell közös feature-definíciót használ, alacsony késleltetésű online kiszolgálás kell, vagy a backfill audit skálázódni nem tud. A verzió, a frissességi késés és az időhelyes join mindkét megoldásban kötelező; a feature store önmagában nem szünteti meg a leakage-t vagy a hibás céldefiníciót. Következő alkalmazás: a reprezentációs fejezet lezárása után a modellmagyarázat és a szeletelt értékelés Globális és lokális magyarázat, ahol ugyanezt a feature-szerződést használjuk a hibaszeletek értelmezéséhez.