A Northstar tanulóprojektben a lab/generate_data.py
állítja elő a szintetikus adatokat. Az előző alfejezetben rögzítettük,
hogy egy mező mikor válik ismertté, és hogy a nyers adat, a tiszta adat
és a manifest együtt adja a származási láncot. Most az a kérdés: ugyanaz
a generátorfolyamat két külön mappában, ugyanazzal a seeddel vajon
bájtról bájtra ugyanazt a CSV-t adja-e, és mi történik, ha pontosan egy
bemeneti paramétert, a seedet változtatjuk meg?
A bemenet itt maga a generátorkód, a környezet és a paraméterezés; a
megfigyelési egység egy-egy kimeneti CSV-fájl bájttartalma; az eredmény
e fájlok SHA-256 lenyomata, valamint a futásról készített jegyzék.
Előzetes becslés: ha a generátor determinisztikus, az azonos seeddel
készült customers.csv, orders.csv,
order_items.csv és orders_raw.csv hash-ei
páronként azonosak lesznek. Ha csak a seedet változtatjuk, legalább a
tartalmi hash-ek eltérnek, még akkor is, ha a fájlok neve és szerkezete
nem változik.
Egy lépést determinisztikusnak nevezünk, ha ugyanaz a bemenet és ugyanaz a programállapot mindig ugyanazt a kimenetet adja. Ilyen például egy CSV-sor összeállítása, egy fájl kiírása vagy egy hash kiszámítása. Ezzel szemben egy sztochasztikus lépés egy véletlen forrás állapotát is felhasználja: a kimenet a lehetséges értékek egy halmazából kerül ki, és a lépés megismétlése önmagában nem garantálja ugyanazt az eredményt.
A számítógépes szimulációban a „véletlen” általában pszeudovéletlen: egy determinisztikus függvény állítja elő a sorozatot, de a sorozat úgy viselkedik, mintha véletlen húzásokból állna. A seed (mag) ennek a függvénynek a kezdeti állapotát rögzíti. Ugyanaz a seed ezért ugyanazt a pszeudovéletlen sorozatot választja; más seed más sorozatot indít. A seed rögzítése a sztochasztikus lépést gyakorlati értelemben ismételhetővé teszi, de nem szünteti meg a mögöttes kísérleti bizonytalanságot: a későbbi statisztikai fejezetekben például egy becsült hatás a minta véletlene miatt akkor is ingadozik, ha a szimuláció seedje rögzített.
Kis kézi példa egy egyszerű lineáris kongruens generátorral:
ahol a -edik állapot, értéke a halmazból való, indexű, a seed pedig . Ha , a sorozat:
Ugyanezt a számolást bárki megismétli, és pontosan ugyanezt kapja. Ha viszont , a sorozat , tehát más. Ez a példa nem ipari minőségű véletlenszám-generátor, de pontosan mutatja a seed szerepét: egy rövid állapotból hosszú, előre meghatározott sorozat indul.
A fájlok összehasonlításához kriptográfiai hash-t, például SHA-256-ot használunk. A hash egy determinisztikus függvény:
ahol
tetszőleges hosszúságú bájt-sorozat,
pedig a 256 bites kimenetek halmaza. Azonos bemenetre azonos kimenetet
ad; ha két fájl hash-e eltér, akkor a fájlok biztosan eltérnek. A
hash-összehasonlítás gyakorlati ellenőrzésre való: egyezésnél nem
bizonyítja matematikai értelemben, hogy a két fájl azonos, de a
gyakorlatban ez az elfogadott ujjlenyomat-módszer. A Northstar
manifest.json ugyanezt az elvet használja: seedet,
sorszámokat, hash-eket és kontrollösszegeket rögzít.
A futási jegyzék (run log) a ténylegesen elvégzett lépések időrendi feljegyzése. Egy sor akkor hasznos reprodukáláshoz, ha tartalmazza:
A kontrollált változtatás (controlled change) azt jelenti, hogy két futás között pontosan egy tényezőt változtatunk, a többit rögzítjük. Ha két dolgot egyszerre változtatunk, nem tudjuk eldönteni, melyik okozta az eltérést. A Northstar-generátornál a bemeneti tényezők közé tartozik legalább a seed, a customers és az orders paraméter. A tanulási feladat ezért először csak a seedet változtatja, a többi paramétert változatlanul hagyja.
A hipotézisnapló a döntést megelőző várakozást és a döntést követő ellenőrzést kapcsolja össze. Minden sorhoz három elem tartozik:
A futási jegyzék a múltat rögzíti, a hipotézisnapló a várakozás és az eredmény közötti kapcsolatot teszi láthatóvá. Ez a kettő együtt adja a tudományos munkanapló legfontosabb részét. A naplóba az 1.2-ben megismert módon érdemes azt is feljegyezni, hogy egy adott döntés pillanatában mely adatmezők voltak ismertek: a reprodukálhatóság nemcsak a számolásé, hanem az információ időbeli rendjéé is.
A következő munkamenetet a tanuló gépén kell futtatni; a könyv
szövegének írásakor ezt a kódot nem futtattuk, ezért a megjelenő kimenet
elméletileg várt, nem mért. A
lab/generate_data.py pontos kapcsolóit a helyi
--help kimenete mutatja; ha a kapcsoló neve eltér, a
döntési logika ugyanez.
Első lépésként hozzunk létre két kimeneti mappát, és futtassuk ugyanazt a generátort ugyanazzal a seeddel.
# Linux/macOS, a könyv projektgyökeréből
mkdir -p runs/run_a runs/run_b
python lab/generate_data.py --seed 20261020 --customers 300 --orders 2400 --output-dir runs/run_a
python lab/generate_data.py --seed 20261020 --customers 300 --orders 2400 --output-dir runs/run_bWindows PowerShellben ugyanez a két parancs
mkdir runs\run_a, runs\run_b előkészítés után, a
--output-dir kapcsolóval. A fájlok összehasonlítását
platformfüggetlen Python-szkripttel végezzük, mert így a könyv fő
eszközkészlete is gyakorolható:
# compare_hashes.py
import hashlib
import sys
from pathlib import Path
FILES = ["customers.csv", "orders.csv", "order_items.csv", "orders_raw.csv"]
def sha256(path: Path) -> str:
h = hashlib.sha256()
h.update(path.read_bytes())
return h.hexdigest()
def main(left: Path, right: Path) -> int:
failed = False
for name in FILES:
a = sha256(left / name)
b = sha256(right / name)
status = "egyezik" if a == b else "ELTÉR"
print(f"{name:20s} {status} {a[:16]} {b[:16]}")
failed = failed or (a != b)
return 1 if failed else 0
if __name__ == "__main__":
sys.exit(main(Path(sys.argv[1]), Path(sys.argv[2])))A futtatás:
python compare_hashes.py runs/run_a runs/run_bElméletileg várt kimenet: mind a négy CSV esetén egyezik
állapot és azonos hash-előtag, a folyamat kilépési kódja
0.
Ezután hajtsuk végre a kontrollált változtatást: csak a seedet írjuk
át 20261021-re, minden más paramétert megtartva.
python lab/generate_data.py --seed 20261021 --customers 300 --orders 2400 --output-dir runs/run_c
python compare_hashes.py runs/run_a runs/run_cItt az elméletileg várt eredmény az, hogy a négy CSV hash-e eltér. A
customers=300 és orders=2400 paraméter
megmarad, ezért a vásárlók és rendelések sorainak száma lehet azonos, de
a tartalom más; az order_items.csv sorainak száma is
változhat, mert az alapméret csak a seed=20261020 futásra
érvényes. A pontos ellenőrzési alap minden futásnál a saját
manifest.json, nem az alapkiadás adatai.
A futási jegyzék formátuma például ilyen lehet; a cellák tartalmát a saját futásodból kell kitöltened:
| Idő | Parancs és input | Megfigyelt eredmény | Döntés |
|---|---|---|---|
| 1. | generate_data.py, seed=20261020, output
run_a |
CSV-k és manifest létrejöttek | elfogadva |
| 2. | ugyanaz seed=20261020, output run_b |
mind a négy CSV hash egyezik | determinisztikusnak tekintjük |
| 3. | ugyanaz seed=20261021, output run_c |
CSV hash-ek eltérnek run_a-tól |
seed kontroll alatt tart |
A hozzá tartozó hipotézisnapló első három sora:
run_a és run_b hash-ei egyeznek.
Cáfoló jel: bármelyik hash eltér. Időpont és
döntés: az run_b futás után ellenőrizve; a
generátort ezen a környezeten reprodukálhatónak tekintjük.run_c hash-e eltér
run_a-tól. Cáfoló jel: minden hash azonos
marad. Időpont és döntés: a run_c futás
után ellenőrizve; a seedet minden jövőbeli futásnál rögzítjük.A hash-egyezésnél nincs tolerancia: a cél a bájtazonosság, tehát
egyetlen eltérő bájt is ELTÉR állapotot ad. A kézi kontroll
az LCG-példa:
esetén a 16 hosszú periódus újra és újra ugyanúgy ismétlődik, tehát a
seed rögzítése a sorozatot is rögzíti. A CSV-k esetében ez azt jelenti,
hogy az azonos seed nemcsak hasonló, hanem azonos bájtsorozatot
eredményez, feltéve hogy a generátorkód, a Python-verzió, a paraméterek
és a fájlkiírási sorrend is azonos.
A shape- és dtype-ellenőrzés a hash-nél gyengébb, de fontos
kiegészítő invariáns: az alap seeddel customers.csv 300
sort, orders.csv 2400 sort, order_items.csv
4796 sort tartalmazzon. Ez önmagában nem bizonyítja a tartalom
azonosságát, ezért a manifest vagy a hash a döntő. A más seeddel készült
run_c esetén a shape-egyezés nem is elvárás; a generált
adat szerződése szerint az alapméretek csak az alap seedhez
tartoznak.
A reprodukálhatóság itt numerikus és fájlszintű. A későbbi gépi tanulási futásoknál más lesz a helyzet: ott a seed rögzítése a véletlen műveletek sorrendjét rögzíti, de a lebegőpontos műveletek hardverfüggő sorrendje miatt a bitazonosság nem garantálható. Ezt a különbséget a fejezet végén visszahozzuk.
Értelmezés. A run_a és
run_b mind a négy CSV hash-e egyezik. Mondd el saját
szóval, mit állíthatunk ebből a generátorról, és mit
nem állíthatunk, ha a második futás egy másik gépen,
más Python-verzióval történne.
Megvalósítás. Egészítsd ki a
compare_hashes.py-t úgy, hogy a CSV-k mellett a két mappa
manifest.json fájlját is beolvassa, és akkor is
1 kilépési kóddal térjen vissza, ha a manifestekben a seed
vagy bármely CSV-hez tartozó hash mező eltér. A kilépési kód legyen a
hívó számára géppel ellenőrizhető.
Hibadiagnózis. Azt látod, hogy a
run_c/manifest.json seed mezője 20261021, a
compare_hashes.py runs/run_a runs/run_c mégis minden CSV-re
egyezést ír ki. Adj két lehetséges magyarázatot, és mindkettőhöz egy-egy
konkrét ellenőrző lépést.
Kontrollált változtatás továbbvitele. Tartsd a
seedet 20261020-on, de változtasd a customers paramétert
300-ról 301-re, az orders paramétert hagyd
2400-on. Mely kimeneti fájlokban vársz változást, és milyen
ellenőrzéssel döntöd el, hogy a változás valóban a customers
paramétertől származik?
Hipotézisnapló. Írj be a naplóba egy negyedik hipotézist arról, hogy a generátor kimenete függ-e a fájlkiírás előtti munkakönyvtár nevétől. Add meg az alátámasztó és cáfoló megfigyelést, valamint a mérési tervet.
Az egyezésből azt állíthatjuk, hogy az adott gépen, az adott Python-verzióval és az adott paraméterekkel a generátor két futtatása azonos CSV-ket adott. Nem állíthatjuk, hogy más gépen vagy más Python-verzióval is bitazonos lesz: a generátor használhat olyan standard könyvtári részeket, amelyek viselkedése verzióhoz kötött, és a fájlrendszer, a karakterkódolás vagy a lebegőpontos formázás is különbözhet. A környezet ugyanúgy része a reprodukálási feltételnek, mint a seed.
A manifest.json beolvasása után például
ellenőrizheted, hogy a két seed mező egyezik-e, majd a CSV-nevekhez
tartozó hash-eket összevetheted. Az invariáns: a kilépési kód
0 pontosan akkor, ha minden összehasonlított mező egyezik.
A hash-összevetésnél nincs tűréshatár; a szöveges mezőknél a pontos
egyezést kérjük.
Két lehetséges magyarázat:
run_c mappa valójában a régi fájlokat tartalmazza,
például a parancs --output-dir értéke elgépelés miatt nem
oda írt, vagy a mappa nem ürült ki. Ellenőrzés: nézd meg a
run_c fájlok módosítási idejét és a manifestben tárolt
CSV-hash-eket; hasonlítsd a run_c manifestjét a
run_a manifestjéhez.runs/run_a. Ellenőrzés: írasd ki a scriptben a teljes
elérési utat, és futtasd a manifest-összevetést is.A customers paraméter az ügyfélfájlt közvetlenül érinti, ezért
customers.csv sorszáma 300-ról 301-re változik. Az
orders.csv is változhat, mert az ügyfélazonosítók halmaza
más lesz; az order_items.csv és az
orders_raw.csv szintén változhat az előállítási lánc miatt.
A kontrolláltság abban áll, hogy a seedet és az orders paramétert
rögzítjük, így ha a run_a-hoz képest valamelyik fájl hash-e
eltér, az a customers változtatás következménye. Az ellenőrzés alapja a
run_301 saját manifest.json, nem az alapkiadás
méretei.
Például: „A generátor által kiírt CSV-k tartalma nem függ a
kimeneti mappa nevétől.” Alátámasztó jel: runs/run_a és egy
eltérő nevű, például runs/other_name mappába írt azonos
seedű futás hash-ei egyeznek. Cáfoló jel: bármelyik hash eltér. Mérési
terv: két új mappa létrehozása, azonos seed, hash-összevetés, az
eredmény bejegyzése a futási jegyzékbe.
„Elég egy seedet rögzíteni, és a gépi tanulási futás minden gépen bitazonos lesz.” Ez nem igaz. A seed a pszeudovéletlen műveletek sorrendjét rögzíti, például azt, hogy melyik minták kerülnek egy-egy kötegbe. A hardver azonban a lebegőpontos összeadás sorrendjét párhuzamosításkor megváltoztathatja: egy GPU redukciója másként csoportosíthatja az összeadandókat, mint egy CPU, és a lebegőpontos kerekítés sorrendfüggő. Ettől a veszteség vagy a súlyok utolsó tizedesjegyei eltérhetnek, noha a véletlen döntések ugyanazok voltak. A tanulási példánkban a CSV-generátor nem használ ilyen párhuzamos lebegőpontos redukciót, ezért fájlszintű bitazonosságot várhatunk; ML-futásnál a reprodukálhatóság szintje külön vizsgálandó.
Egy másik félreértés: a seed egyezése adatminőségi garanciát is
jelentene. A orders_raw.csv ugyanazzal a seeddel is
tartalmazza a szándékos hibákat, mert azok a generátor specifikációjának
részei. A seed csak az előállítás reprodukálhatóságát szolgálja; a
tisztaságot külön ellenőrzéssel kell igazolni.
Kérdezd végig magadon a friss munkanaplóddal kapcsolatban: le tudom-e írni a generátorfutás pontos parancsát, seedjét és paramétereit? A jegyzékemben külön szerepel-e a tervezett lépés és a ténylegesen mért kimenet? Minden döntésemhez tartozik-e előre rögzített alátámasztó és cáfoló megfigyelés? Meg tudom-e nevezni, hogy a fájlszintű reprodukálhatóság mely feltételei hiányoznak egy másik gépre költözéskor? Ha bármelyik kérdésre nincs válasz, a naplót egészítsd ki, mielőtt a következő diagnózisfeladathoz kezdesz.
Reprodukálhatóság szintjei. A rögzített véletlen mag nem garantál minden operációs rendszeren vagy könyvtárverzión bájtazonos eredményt. Külön kell dokumentálni (1) a forrásadat és a konfiguráció bájtazonosságát, (2) az eredmények számszerű egyezését deklarált tolerancián belül és (3) a következtetés robusztusságát. Mindháromhoz más elfogadási feltétel tartozik.
A sha256 kizárólag azonos fájlbájtokat ellenőriz; nem
bizonyítja, hogy az adatok helyesek vagy az elemzés tudományosan
érvényes. A munkanapló tartalmazza a Python- és csomagverziókat, a
platformot, az inputazonosítót, az elemzőkód commitját és a ténylegesen
ellenőrzött kimenetet.
A reprodukálhatóság nem egyetlen seed, hanem a kódverzió, környezet, paraméterek, kimeneti adatverzió és a döntések együttes rögzítése. A determinisztikus lépés azonos bemenetből azonos kimenetet ad; a sztochasztikus lépést a seed rögzítése teszi újrafuttathatóvá. A Northstar-generátorral azonos seed mellett bájtazonos CSV-k várhatók, más seed mellett eltérő hash-ek. A futási jegyzék a múltat, a hipotézisnapló a várakozás és a mérés kapcsolatát rögzíti; a hash-ellenőrzésnél nincs tolerancia, az ML-futásoknál a bitazonosság nem általános garancia. A következő alfejezetben ezt a naplózási fegyelmet használjuk egy teljes diagnosztikai munkamenet felépítésére.