Hoppa till innehållet
AI-grafen
G· Frontier Labai-sakerhet-alignment· ca 120 min· volatil — kontrolleras ofta· verifierad 2026-09-21

Modellutvinning och medlemskapsattacker

Kunna förklara membership inference och skydd som differential privacy.

Förkunskaper

Intuition

En tränad modell läcker information om sin träningsdata. Fyra angrepp, i stigande allvarlighetsgrad:

AngreppFrågan angriparen besvarar
Medlemskap (membership inference)«fanns den här personen i träningsdatan?»
Attribututvinning«vad hade den här personen för värde på X?»
Dataextraktion«återge ett exakt träningsexempel»
Modellstöld«kopiera modellen genom att fråga den»

Medlemskap låter harmlöst — men är det inte. Är modellen tränad på patienter med en viss diagnos avslöjar «fanns den här personen med?» diagnosen.

Grundmekanismen är densamma för alla fyra: modellen är mer säker på data den sett under träningen. Den skillnaden går att mäta utifrån, och den räcker.

Carlini m.fl. har visat att stora språkmodeller kan förmås att ordagrant återge träningsdata — inklusive personuppgifter — och att risken växer med modellstorlek och med hur många gånger en text förekom i korpuset.

Formellt

Medlemskapsattack i sin enklaste form: mät förlusten för ett exempel. Är den lägre än en tröskel var exemplet troligen med i träningen.

Mer träffsäkra varianter kalibrerar mot en referensmodell tränad utan exemplet, och tittar på förlustkvoten i stället för det absoluta värdet. Det tar bort effekten av att vissa exempel helt enkelt är lättare än andra.

Differential privacy ger en matematisk garanti. En mekanism M\mathcal{M} är (ε,δ)(\varepsilon,\delta)-differentiellt privat om det för alla dataset D,D′D, D' som skiljer sig i en post och alla utfallsmängder SS gäller:

P[M(D)∈S]≤eε P[M(D′)∈S]+δP[\mathcal{M}(D) \in S] \le e^{\varepsilon}\,P[\mathcal{M}(D') \in S] + \delta

I ord: resultatet får inte bero nämnvärt på om just din data var med. Är det sant kan ingen angripare — hur smart som helst, med hur mycket sidoinformation som helst — avgöra om du fanns i datan.

DP-SGD implementerar det i träningen:

  1. Beräkna gradienten per exempel.
  2. Klipp varje individuell gradient till norm CC — så att ett enskilt exempel har begränsat inflytande.
  3. Addera gaussiskt brus proportionellt mot CC.
  4. Ta steget.

Kostnaden är verklig:

Effekt
Träningstid2–10× långsammare (gradienter per exempel)
Minnebetydligt mer
Kvalitetmärkbar förlust, särskilt vid små ε\varepsilon
Underrepresenterade grupperdrabbas hårdast

Den sista raden är den etiskt viktigaste och den minst kända: bruset slår hårdast mot mönster som bara finns i få exempel, och det är ofta minoritetsgrupperna.

Vad ε\varepsilon betyder i praktiken:

ε\varepsilonBedömning
< 1stark garanti
1–10rimlig i praktiken
> 10garantin är svag; närmast symbolisk

Skydd i ordning efter hur mycket de kostar:

SkyddEffektKostnad
Deduplicera träningsdataminskar memorering kraftigtlåg — gör alltid detta
Ta bort PII före träningtar bort det värstalåg
Begränsa antal API-anropförsvårar utvinninglåg
Returnera bara toppklass, inte alla sannolikheterförsvårar medlemskaplåg
Differential privacymatematisk garantihög

De fyra första bör göras i alla lägen. Den sista när datan är känslig nog att motivera kostnaden.

Kod

import numpy as np, torch

# Enkel medlemskapsattack: förlusttröskel
def medlemskapsattack(modell, medlemmar, ickemedlemmar, forlust_fn):
    fm = np.array([float(forlust_fn(modell, x, y)) for x, y in medlemmar])
    fi = np.array([float(forlust_fn(modell, x, y)) for x, y in ickemedlemmar])
    from sklearn.metrics import roc_auc_score
    y = np.r_[np.ones(len(fm)), np.zeros(len(fi))]
    poang = -np.r_[fm, fi]                     # lägre förlust = troligen medlem
    return {"auc": round(float(roc_auc_score(y, poang)), 4),
            "medelforlust_medlem": round(float(fm.mean()), 4),
            "medelforlust_ickemedlem": round(float(fi.mean()), 4)}
# AUC 0.5 = ingen läcka. AUC 0.75 = tydlig läcka.

# Kalibrerad variant: jämför mot en referensmodell utan exemplet
def kalibrerad_attack(modell, referens, exempel, forlust_fn):
    return [float(forlust_fn(referens, x, y)) - float(forlust_fn(modell, x, y))
            for x, y in exempel]
# Stort positivt värde → modellen är ovanligt säker → troligen medlem

# DP-SGD: klipp per exempel, addera brus
def dp_sgd_steg(modell, batch, opt, C=1.0, brusmultiplikator=1.1, forlust_fn=None):
    summerade = [torch.zeros_like(p) for p in modell.parameters()]
    for x, y in batch:                              # gradient PER EXEMPEL
        opt.zero_grad()
        forlust_fn(modell, x, y).backward()
        grads = [p.grad.detach().clone() for p in modell.parameters()]
        norm = torch.sqrt(sum((g ** 2).sum() for g in grads))
        skala = min(1.0, C / float(norm + 1e-12))   # klipp individuellt
        for s, g in zip(summerade, grads):
            s += g * skala
    n = len(batch)
    opt.zero_grad()
    for p, s in zip(modell.parameters(), summerade):
        brus = torch.normal(0.0, brusmultiplikator * C, size=s.shape, device=s.device)
        p.grad = (s + brus) / n                     # addera brus, medelvärdesbilda
    opt.step()

# Memoreringstest: kan modellen återge träningsdata?
def memoreringstest(modell, tokenizer, prefix_lista, facit_lista, max_tokens=50):
    exakta = 0
    for prefix, facit in zip(prefix_lista, facit_lista):
        ids = tokenizer(prefix, return_tensors="pt").input_ids
        with torch.no_grad():
            ut = modell.generate(ids, max_new_tokens=max_tokens, do_sample=False)
        genererat = tokenizer.decode(ut[0, ids.shape[1]:], skip_special_tokens=True)
        exakta += int(genererat.strip().startswith(facit.strip()[:40]))
    return {"exakt_aterget": exakta, "av": len(prefix_lista),
            "andel": round(exakta / max(len(prefix_lista), 1), 4)}

# Försvara API:t mot modellstöld
class Skyddat:
    def __init__(self, modell, max_anrop_per_dag=1000, returnera_topp=1):
        self.modell, self.tak, self.topp = modell, max_anrop_per_dag, returnera_topp
        self.antal = {}

    def forutsag(self, anvandare, x):
        n = self.antal.get(anvandare, 0)
        if n >= self.tak:
            return {"error": "dagskvoten är slut"}
        self.antal[anvandare] = n + 1
        p = self.modell.predict_proba([x])[0]
        idx = int(np.argmax(p))
        # Returnera INTE hela sannolikhetsvektorn — den gör medlemskapsattacker lättare
        return {"klass": idx} if self.topp == 1 else {"topp": np.argsort(-p)[:self.topp].tolist()}

Behärskning innebär

  • Förklarar medlemskapsattacker och modellutvinning
  • Beskriver differential privacy och dess kostnad
  • Väljer skydd efter hotbild

Logga in för att göra övningarna och bygga upp din behärskning.

Källor

Alla källor och licenser