Bizonyítási szint: az azonos metrikák nem garantálnak bitazonos modellartefaktumot. Az átadási teszt külön rögzítse a környezet azonosságát, az input-hash egyezését, a numerikus toleranciát és a célzott regressziós tesztek eredményét.
A Független értékelés és hibaelemzés
szakaszban befagyasztottuk a tesztprotokollt, elvégeztük az értékelést,
szeleteket és kalibrációt számoltunk. A végeredmény egyetlen
modelljelölt: a rendelés pillanatában ismert nyolc feature-ből becsüli a
late kimenetet, azaz azt, hogy a kézbesítés meghaladja-e a
7 napot. Most következik az átadás (handoff): a projektet egy másik
gépen, új környezetben újra kell tudni futtatni, és egy kis friss
bemeneten batch predikciót kell készíteni.
Az átadás konkrét bemenete:
| Elem | Tartalom | Megfigyelési egység |
|---|---|---|
| Projektkönyvtár | northstar-final/ Git-tár |
— |
| Modellartefaktum | artifacts/model_pipeline.joblib |
egy betöltött scikit-learn Pipeline |
| Manifest | artifacts/manifest.json |
hash-ek, verziók, feature-szerződés |
| Modellkártya | model_card.md |
szándék, korlátok, mért eredmények |
| Friss input | artifacts/fresh_input.parquet |
12 rendelés, szándékosan a cutoff utáni tartományból |
| Környezeti előírás | pyproject.toml, uv.lock |
Python 3.12, uv-alapú szinkronizálás |
Az elvárt kimenet: artifacts/fresh_predictions.parquet,
minden sorban predicted_late (0/1) és
late_probability (0 és 1 közötti valós szám), valamint egy
tartalomhash. Az ellenőrzési feltétel: a predikció kizárólag a
modellkártyában felsorolt, rendeléskor ismert oszlopokat használja; a
tiltott, utólag ismert oszlopok megjelenése azonnal hibát ad. A
környezeti ismétlés akkor sikeres, ha uv sync --locked és a
tesztek ugyanazzal a kód- és modellverzióval futnak, a tartalomhash
pedig a numerikus tolerancián belül egyezik.
Előzetes becslés: a 12 soros friss batch predikció egy CPU-s laptopon
néhány másodperc nagyságrendű; a pontos futásidőt ebben az alfejezetben
nem nevezzük mértnek, mert a szövegíró ebben a módban nem futtatott
kódot. A tartalmi elvárás az, hogy a valószínűségek konzisztensek
legyenek a befagyasztott 0.5-ös döntési küszöbbel:
predicted_late = 1 pontosan akkor, ha
late_probability >= 0.5.
Az átadás nem egyetlen modellfájl átküldése. Az Átadás és új környezetes ismétlés csomag öt pillérből áll: verzió, hash, modellkártya, batch predikciós parancs és helyi füstteszt. Minden pillér más kérdésre felel.
Verziók (versions). A Python-verzió, a függőségek és
a saját kód verziója együtt azonosítja, hogy melyik
szoftverállapotról beszélünk. A pyproject.toml
verziótartomány, nem lock: a tartomány megengedi a későbbi feloldást. A
uv.lock viszont a ténylegesen feloldott függőségeket
rögzíti. Ezért az átadásnál mindkettőt csomagoljuk. A saját kód verziója
a Git commit hash, például git rev-parse --short HEAD, és
nem csak a szép címke. Két különböző commit címkéje lehet ugyanaz; a
commit hash nem.
Hashek. A hash egy előre rögzített bemeneti
bájtsorozathoz számított, rögzített hosszúságú lenyomat. A projekt a
SHA-256-ot használja. A lenyomat ellenőrzi a fájl integritását, de
nem mond semmit az adat minőségéről vagy a modell tudásáról. Ha
egy fájl átvitele közben egyetlen bájt is megsérül, a hash-ek eltérnek.
Példának vegyük a "a" szöveg SHA-256 lenyomatát:
ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb.
Ez a szöveg hexadecimális alakja, tehát 64 karakter, mindig ugyanakkora;
önmagából a tartalmat visszaállítani nem tudjuk.
A manifest.json kulcs-értékként tárolja a fájlnevet és a
hozzá tartozó elvárt SHA-256-ot. Az ellenőrző parancs újraszámolja a
hash-t, és az eltérést azonnal jelzi. Ez a folyamat nem helyettesíti a
kódverziót: ha a kódot módosítjuk, a modell hash-e még egyezhet, de a
kísérlet már más.
Modellkártya (model card). A modellkártya embereknek szóló, rövid dokumentum. Kötelező tartalma: rendeltetés, bemeneti séma, kimeneti értelmezés, tanítási adat és időbeli feltétel, mért metrikák a befagyasztott tesztből, valamint érvényességi korlátok. Az Átadás és új környezetes ismétlés projektben a kártya legfontosabb rovata a „Nem valós logisztikai validáció” figyelmeztetés: az adat szintetikus, a modell tanulási bizonyíték.
Batch prediction. A batch egy olyan parancssoros
folyamat, amely változatlan bemeneti sémából olvas, és változatlan
kimeneti sémát ír. Ezt a munkamenetet CLI-vel, nem notebookban adjuk át.
Egy notebook cellasorrendje nem garantált; egy CLI-parancs argumentumai
igen. A batch CLI feladata: betölteni a pipeline-t, ellenőrizni a
manifest hash-ét, érvényesíteni a bemeneti oszlopokat, majd predikciót
írni. A manifestben tárolt feature_order lista adja a
betöltési sorrendet. Ezzel a gépek közötti sorrend-eltérést is
kizárjuk.
Local test. Az új környezet nem csak a predikciós
parancsot futtatja; a legkisebb ellenőrző készletet is le kell tudni
futtatni. Ez a gyakorlatban pytest-tesztek csomagja: séma-,
tartomány-, determinizmus- és hash-teszt. A helyi füstteszt akkor
értelmes, ha a parancs meghívható a projektgyökérből, és nem támaszkodik
a fejlesztőgép globális csomagjaira.
A projektben a predikciós belépési pont a
src/northstar/cli.py. Az alábbi implementáció szándékosan
kicsi és olvasható; a PyProject.toml
[project.scripts] bejegyzése teszi a futtatható
northstar parancsot elérhetővé.
import argparse
import hashlib
import json
import sys
from pathlib import Path
import joblib
import pandas as pd
def sha256_of_file(path: Path, chunk_size: int = 65536) -> str:
h = hashlib.sha256()
with open(path, "rb") as f:
while chunk := f.read(chunk_size):
h.update(chunk)
return h.hexdigest()
def check_manifest(manifest_path: Path, root: Path) -> int:
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
expected = manifest["artifacts"]
failed = []
for rel_name, meta in expected.items():
actual = sha256_of_file(root / rel_name)
if actual != meta["sha256"]:
failed.append((rel_name, actual, meta["sha256"]))
if failed:
print("HASH MISMATCH", file=sys.stderr)
for rel_name, actual, want in failed:
print(f" {rel_name}: got {actual[:16]}... want {want[:16]}...", file=sys.stderr)
return 1
print(f"OK {len(expected)}/{len(expected)}")
return 0
def predict(args: argparse.Namespace) -> int:
root = Path(args.root).resolve()
model_path = root / args.model
manifest_path = root / args.manifest
input_path = root / args.input
output_path = root / args.output
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
if sha256_of_file(model_path) != manifest["artifacts"][args.model]["sha256"]:
print("Model hash mismatch", file=sys.stderr)
return 2
model = joblib.load(model_path)
feature_order = manifest["feature_order"]
allowed = set(feature_order)
frame = pd.read_parquet(input_path)
forbidden = sorted(set(frame.columns) - allowed)
if forbidden:
print(f"Forbidden columns in predictor input: {forbidden}", file=sys.stderr)
return 3
missing = sorted(set(feature_order) - set(frame.columns))
if missing:
print(f"Missing predictor columns: {missing}", file=sys.stderr)
return 3
X = frame[feature_order]
probabilities = model.predict_proba(X)[:, 1]
threshold = float(manifest["threshold"])
predicted = (probabilities >= threshold).astype(int)
order_key = manifest.get("output_order_column", "order_id")
out = pd.DataFrame(
{
order_key: frame[order_key],
"predicted_late": predicted,
"late_probability": probabilities.round(6),
}
)
out.to_parquet(output_path, index=False)
payload = [
{
order_key: str(row[order_key]),
"predicted_late": int(row["predicted_late"]),
"late_probability": row["late_probability"],
}
for _, row in out.iterrows()
]
canonical = json.dumps(payload, sort_keys=True, separators=(",", ":"), ensure_ascii=False)
content_hash = hashlib.sha256(canonical.encode("utf-8")).hexdigest()
print(f"Wrote {len(out)} rows to {output_path}")
print(f"Content hash {content_hash}")
return 0
def main() -> int:
parser = argparse.ArgumentParser(prog="northstar")
sub = parser.add_subparsers(dest="command", required=True)
p_check = sub.add_parser("check", help="Check artifact hashes against manifest")
p_check.add_argument("--manifest", default="artifacts/manifest.json")
p_check.add_argument("--root", default=".")
p_predict = sub.add_parser("predict", help="Batch prediction from frozen pipeline")
p_predict.add_argument("--input", required=True)
p_predict.add_argument("--output", required=True)
p_predict.add_argument("--model", default="artifacts/model_pipeline.joblib")
p_predict.add_argument("--manifest", default="artifacts/manifest.json")
p_predict.add_argument("--root", default=".")
args = parser.parse_args()
if args.command == "check":
return check_manifest(Path(args.manifest), Path(args.root))
if args.command == "predict":
return predict(args)
return 1
if __name__ == "__main__":
raise SystemExit(main())A hozzá tartozó pyproject.toml-részlet:
[project]
name = "northstar-final"
version = "0.3.0"
requires-python = ">=3.12"
dependencies = [
"pandas>=2.2,<3",
"scikit-learn>=1.5,<1.7",
"joblib>=1.4,<2",
"pyarrow>=17,<20",
]
[project.scripts]
northstar = "northstar.cli:main"A uv lock rögzíti a tényleges feloldást. A manifest a
modellfájl, a predikciós kód és a feature-séma kötelező közös
nevezője:
{
"created_at_utc": "2025-10-10T09:00:00+00:00",
"git_commit": "a3f28c1b",
"python": "3.12.8",
"threshold": 0.5,
"feature_order": [
"distance_km",
"priority_flag",
"basket_minor",
"quantity_sum",
"account_age_days",
"segment",
"region",
"acquisition_channel"
],
"output_order_column": "order_id",
"artifacts": {
"artifacts/model_pipeline.joblib": {
"sha256": "c9a8e45f32756c3b35acd9e44d6a2fd9f0e44d9ba9b16cc8d1fa6dc4b4a0ff37"
},
"artifacts/manifest.json": {
"sha256": "075bfea16e2f2af0a44c0cbbdaf20631f4ea67cdc8cee95ca14cf1b389d66de3"
}
},
"status": "learning_demo",
"data_origin": "lab/generate_data.py seed=20261020; synthetic Northstar"
}A példában szereplő SHA-256 értékek csak szerkezeti szemléltetők: ebben az alfejezetben a szövegíró nem futtatta a projektet, ezért nem nevezhetők a helyi gépen számított tényleges lenyomatnak. A feladatmegoldásnál a valódi manifestet a tanuló gépének kell létrehoznia.
A futtatás platformjelölt munkamenete Linux vagy macOS bash esetén, a projektgyökérből:
# Környezet létrehozása a lock alapján
uv sync --locked
# Hash-ellenőrzés
uv run --locked python -m northstar.cli check --root .
# Batch predikció a friss inputra
uv run --locked python -m northstar.cli predict \
--input artifacts/fresh_input.parquet \
--output artifacts/fresh_predictions.parquet \
--root .Windows PowerShellben ugyanez a munkakönyvtár
C:\work\northstar-final esetén:
uv sync --locked
uv run --locked python -m northstar.cli check --root .
uv run --locked python -m northstar.cli predict `
--input artifacts/fresh_input.parquet `
--output artifacts/fresh_predictions.parquet `
--root .A --locked az ellenőrzött lockkal való egyezést követeli
meg. Ha a lock hiányzik vagy a pyproject.toml megváltozott,
a parancs hibát ad; ilyenkor előbb uv lock kell, de az már
új környezetállapotot hoz létre, ezért a manifestben rögzíteni kell.
A predikció értelmezése nem maradhat puszta szám a kódblokk után. A friss batch kimenet egy sorának alakja:
| order_id | predicted_late | late_probability |
|---|---|---|
| ORD-20250601-0001 | 0 | 0.412351 |
| ORD-20250602-0007 | 1 | 0.783102 |
A kézi kontroll invariánsai:
0 <= late_probability <= 1 minden sorra, és
predicted_late értéke 0 vagy 1.predicted_late = 1
akkor és csak akkor, ha late_probability >= 0.5.delivery_days, actual_delivery_at,
label_available_at vagy target_late oszlopot;
a CLI ezt a manifest feature_order listájával
ellenőrzi.A numerikus tolerancia: a valószínűségeket hat tizedesjegyre kerekítjük. Két gépen az azonos modell és azonos input hat tizedes pontossággal egyező valószínűséget ad, ha a lockolt függőségek numerikus szempontból egyeznek. A bitpontos bináris egyezés nem alapfeltevés; a tartalomhash a kerekített értékekkel vethető össze.
Értelmezés. Melyik állítást ellenőrzi a manifest SHA-256, és melyiket nem? (a) a modellfájl átvitele közben nem sérült; (b) a modell a befagyasztott tesztkészleten jól teljesít; (c) a modell betanítása óta a fájl tartalma nem változott. Válaszod indokold.
Megvalósítás. Írj check_fresh
alparancsot a CLI-be, amely a fresh_predictions.parquet
tartalomhash-ét hasonlítja a manifest
expected_fresh_content_hash mezőjéhez. A Parquet állomány
helyett a kerekített kanonikus JSON-t hash-eld.
Hibakeresés. Az új gépen a
uv run --locked python -m northstar.cli predict hívás
ModuleNotFoundError: No module named 'northstar' hibát ad.
Adj legalább két lehetséges okot, és mindkettőhöz konkrét ellenőrző
parancsot.
Megváltozott feltétel. A friss input véletlenül
tartalmazza a delivery_days oszlopot is. A fenti
predict függvény hogyan viselkedik? Milyen explicit
szerződésváltoztatásra lenne szükség ahhoz, hogy a rendszer a hibát
ne hagyja figyelmen kívül, és mit kell a modellkártyában
módosítani?
Továbbvitel. A batch kimenetet a döntéshozó gépén más pandas-verzióval olvassák be, és a Parquet hash-egyezés eltűnik. Magyarázd el, miért nem a tartalmi modellpredikció romlott el, és milyen hash-t küldj a partnereknek.
1. A SHA-256 az (a) és (c) állításokat támogatja: a letöltött fájl lenyomata egyezik a manifesttel, ezért a fájl tartalma az átvitel során nem változott, és a manifest rögzíti, milyen tartalom volt a befagyasztáskor. A (b) állítást a hash nem ellenőrzi: egy rossz modell is kaphat hash-t, ha a fájl tartalma változatlan. A modellminőséget a befagyasztott teszteredmények, például a Független értékelés és hibaelemzés fagyasztott metrikái igazolják.
2. A tartalomhash újraszámítása:
import hashlib
import json
import pandas as pd
def content_hash_of_predictions(path) -> str:
frame = pd.read_parquet(path)
payload = [
{
"order_id": str(row.order_id),
"predicted_late": int(row.predicted_late),
"late_probability": float(round(row.late_probability, 6)),
}
for row in frame.itertuples()
]
canonical = json.dumps(payload, sort_keys=True, separators=(",", ":"), ensure_ascii=False)
return hashlib.sha256(canonical.encode("utf-8")).hexdigest()A check_fresh ezt veti össze a manifest értékével. Ha
eltér, a folyamat kettes kilépési kóddal hibát ad; ellenkező esetben
FRESH OK üzenetet ír.
3. A hiba első okaként a modul nincs telepítve a
környezetbe. Ellenőrzés:
uv run --locked pip list | Select-String northstar
PowerShellben, illetve uv run --locked pip show northstar
bashben. Megoldás: a projekt gyökérben uv sync --locked
futtatása; ha a csomagot fejlesztői módban kell látni,
uv sync --locked --extra dev vagy
uv pip install -e .. Második ok: a parancs más
munkakönyvtárból fut, és a Python nem találja a src/
könyvtárat. Ellenőrzés: pwd bash, Get-Location
PowerShell; a várt gyökérben léteznie kell a
src/northstar/__init__.py fájlnak. Harmadik lehetséges ok:
a CLI-t a northstar konzolscripttel hívják, de a telepített
elavult script másik interpretert használ; ezt
uv run --locked which python és
uv run --locked python -c "import northstar; print(northstar.__file__)"
mutatja ki.
4. A fenti predict függvény
forbidden listába teszi a delivery_days
oszlopot, mert az nincs a manifest feature_order
listájában, és hármas kóddal leáll, mielőtt outputot írna. Ez a kívánt
viselkedés: a célmagyarázat jelenléte a prediktor bemenetben szivárgás
lenne. Ha mégis szeretnénk használni, az új megfigyelési szerződés
kellene: például „későbbi, részleges időablakból származó early label”,
új manifest, új feature-lista és új modellkártya. A kártyában a bemeneti
sémát és a megfigyelési pillanatot explicit módon módosítani kell; a
régi modell betöltése ezzel az inputtal továbbra is hiba kell
legyen.
5. A Parquet bináris hash-egyezés hiánya gyakori
hamis riasztás. A Parquet belső metaadata a pyarrow verziójától, a
létrehozó könyvtár nevétől és a statisztikai blokkoktól függhet. A
modell predikcióinak lényege a tartalom: ha a
late_probability hat tizedesre kerekített értékei és a
predicted_late oszlop egyeznek, a tartalomhash egyezik.
Ezért a partnereknek a kerekített kanonikus JSON hash-t küldjük, és a
manifestben is ezt a mezőt rögzítjük.
A leggyakoribb hiba az, hogy a tanuló a szintetikus Northstar-modell
működését valós logisztikai modellvalidációként kezeli. Ellenpélda erre
a gondolkodásra a következő: tegyük fel, hogy a friss input 45 sort
tartalmaz, és minden sorra a rendelés pillanatában elérhető feature-ök
pontosak. A batch CLI gond nélkül készít predikciót. Ettől a modell még
nem alkalmas valós döntésre, mert az adat szintetikus, a szegmensek
mesterségesek, a hiányzó szelekciós folyamatok nem valós üzleti
torzítások, és a modellkártya status mezője
learning_demo. Az átadás így is teljes értékű
tanulási bizonyíték: demonstrálja a kód, a séma, a hash, a
manifest és a CLI együttműködését. A valós bevezetéshez valós adat, új
adatszerződés, adatvédelmi vizsgálat és üzleti jóváhagyás kell.
Visszautalás: a Saját data science munkaasztal specifikációja szakaszban a saját munkaasztal minimális eszközkészletét választottuk; a mostani átadás pontosan annak a döntésnek a futtatható következménye. A Kiadás, visszaállítás és terhelési próba szakasz release-fogalma szerint a release nem egy modellfájl, hanem verzióhoz kötött jegyzőkönyv; itt ezt a jegyzőkönyvet a manifest és a modellkártya testesíti meg. Az Adatkezelés és reprodukálhatóság külső modellnél szakasz tanulsága is érvényes: a kód-, modell- és verzióhármas azonosítja a futást, és a köztes gyorsítótáraknak is verzióérzékenynek kell lenniük.
Önellenőrző lista az átadás előtt:
feature_order pontosan a rendeléskor ismert
oszlopokat sorolja fel?predict parancs üres vagy tiltott oszlopot tartalmazó
inputra hibával lép ki?Az átadás akkor sikeres, ha a másik gép a projektgyökérből
uv sync --locked után változatlan kóddal és modellel futtat
hash-ellenőrzést, helyi tesztet és batch predikciót. A manifest SHA-256
lenyomatai a fájlintegritást, a Git commit és a lock a
szoftverállapotot, a modellkártya az emberi korlátokat, a CLI pedig a
megismételhető predikciós szerződést rögzíti. A Friss inputon készített
output tartalomhash-ét kerekített numerikus értékekből számoljuk, hogy a
formátum-metaadatok eltérése ne okozzon hamis riasztást. A következő
záróprojekt ezt az átadási csomagot oksági kérdésre alkalmazza: a Kísérleti kérdés és előzetes elemzési terv
szakaszban a kísérleti kérdést és az előzetes elemzési tervet vesszük
fel.