Hoppa till innehållet
AI-grafen
E· Universitetdatahantering· ca 60 min· utvecklande· verifierad 2026-09-20

Dataversionering

Kunna versionera dataset med checksummor och koppla en modell till exakt den data den tränats på.

Förkunskaper

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:

VerktygIdéPassar
Git LFSpekare i Git, filer på LFS-servermåttligt stora filer, enkelt
DVCpekare i Git, data i valfritt lagerML-projekt, pipelines
LakeFSGit-liknande grenar över objektlagringstora datasjöar, team
Delta / Icebergtransaktionslogg över Parquettabeller med tidsresa
Egen manifestfilhash per fil, incheckad i Gitsmå 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ältVarför
data_hashexakt vilken data
split_froexakt vilken uppdelning
git_commitexakt vilken kod
config_hashexakt 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

Alla källor och licenser