Dataversionering
Kunna versionera dataset med checksummor och koppla en modell till exakt den data den tränats på.
Förkunskaper
- DGit — versionshanteringkrävs
- EDatapipelines och ETLkrävs
Intuition
Git är byggt för text och hanterar stora binärfiler illa. Ett 10 GB dataset i ett Git-repo gör repot ohanterligt för alla, för alltid — historiken går inte att ta bort i efterhand.
Lösningen: versionera en pekare i Git, och lagra datan någon annanstans.
repo/
data/train.csv.dvc ← 200 byte i Git: hash, storlek, sökväg
train.py
.dvc/cache/ eller S3/ ← själva datan, adresserad av sin hash
Innehållsadressering är kärnan: filens namn i lagret är dess SHA-256. Två identiska filer lagras en gång; en ändrad fil får ett nytt namn. Samma idé som Git använder internt för sina objekt.
Frågan som ska gå att besvara: «modellen från den 14 mars — exakt vilken data tränades den på?» Utan versionering är svaret en gissning.
Formellt
Verktyg och när de passar:
| Verktyg | Idé | Passar |
|---|---|---|
| Git LFS | pekare i Git, filer på LFS-server | måttligt stora filer, enkelt |
| DVC | pekare i Git, data i valfritt lager | ML-projekt, pipelines |
| LakeFS | Git-liknande grenar över objektlagring | stora datasjöar, team |
| Delta / Iceberg | transaktionslogg över Parquet | tabeller med tidsresa |
| Egen manifestfil | hash per fil, incheckad i Git | små projekt — fungerar förvånansvärt bra |
Den sista raden är underskattad: ett manifest.json med hash, storlek och radantal per fil ger 80 % av nyttan för noll infrastruktur.
Vad som ska hashas. Hasha innehållet, inte filnamnet eller ändringstiden. För en tabell räcker det ofta att hasha den sorterade, kanoniserade serialiseringen — då ger samma data samma hash även om radordningen skiljer sig mellan körningar.
Kopplingen modell ↔ data är hela poängen. Varje träningskörning ska logga:
| Fält | Varför |
|---|---|
data_hash | exakt vilken data |
split_fro | exakt vilken uppdelning |
git_commit | exakt vilken kod |
config_hash | exakt vilka hyperparametrar |
miljö | biblioteksversioner eller containertagg |
Med de fem fälten är en körning reproducerbar. Utan något av dem är den det inte.
Den juridiska dimensionen. GDPR ger rätt till radering. Ett dataset med personuppgifter som versioneras «för alltid» kolliderar med det. Lösningen är att versionera pseudonymiserade data där kopplingen till identitet ligger i ett separat, gallringsbart register — och att ha en dokumenterad rutin för att ta bort en person ur alla versioner.
Det är inte en teoretisk invändning: den som bygger dataversionering utan att tänka igenom radering bygger in ett problem som är dyrt att lösa i efterhand.
Kod
import hashlib, json
from pathlib import Path
import pandas as pd
def fil_hash(p: Path, block=1 << 20) -> str:
h = hashlib.sha256()
with p.open("rb") as f:
for bit in iter(lambda: f.read(block), b""):
h.update(bit)
return h.hexdigest()
def tabell_hash(df: pd.DataFrame) -> str:
"""Kanonisk hash: oberoende av radordning och kolumnordning."""
d = df.sort_index(axis=1)
d = d.sort_values(list(d.columns)).reset_index(drop=True)
return hashlib.sha256(d.to_csv(index=False).encode()).hexdigest()
def skapa_manifest(katalog: Path, ut: Path):
poster = []
for f in sorted(katalog.rglob("*")):
if f.is_file():
poster.append({"sokvag": str(f.relative_to(katalog)),
"byte": f.stat().st_size,
"sha256": fil_hash(f)})
manifest = {"filer": poster,
"total_byte": sum(p["byte"] for p in poster),
"dataset_hash": hashlib.sha256(
"".join(p["sha256"] for p in poster).encode()).hexdigest()}
ut.write_text(json.dumps(manifest, indent=2), encoding="utf-8")
return manifest["dataset_hash"]
def verifiera(katalog: Path, manifest_fil: Path):
m = json.loads(manifest_fil.read_text(encoding="utf-8"))
problem = []
for post in m["filer"]:
f = katalog / post["sokvag"]
if not f.exists():
problem.append(f"saknas: {post['sokvag']}")
elif fil_hash(f) != post["sha256"]:
problem.append(f"ändrad: {post['sokvag']}")
return problem or ["allt stämmer"]
# Koppla modell till data — de fem fälten som gör en körning reproducerbar
def korningsmetadata(dataset_hash, config, split_fro, git_commit, miljo):
return {
"data_hash": dataset_hash,
"config_hash": hashlib.sha256(
json.dumps(config, sort_keys=True).encode()).hexdigest()[:16],
"split_fro": split_fro,
"git_commit": git_commit,
"miljo": miljo,
}
tabell_hash sorterar innan den hashar. Utan det får samma data olika hash bara för att en GROUP BY returnerade raderna i en annan ordning — och då är versioneringen värdelös eftersom allt alltid ser ändrat ut.
Behärskning innebär
- Versionerar dataset med innehållshash
- Kopplar modell till datauppsättning
- Väljer versioneringsstrategi efter datastorlek
Logga in för att göra övningarna och bygga upp din behärskning.
Källor
- DVC — dokumentation (Apache-2.0) — Apache-2.0
- GDPR — förordning (EU) 2016/679, art. 17 — EU-rättsakt
- Pro Git (Chacon & Straub) — CC BY-NC-SA 3.0