Leo Nguyen

Machine Unlearning

llmmodel-editingmachine-unlearningprivacy
Leo Nguyen··ML/AI

Machine Unlearning

Làm model hành xử như thể một phần dữ liệu chưa từng được học, với bằng chứng định lượng về forget quality và retain quality.

Plain English

Machine unlearning cố làm model "quên" một nhóm thông tin: PII, copyrighted text, hazardous knowledge, hoặc records cần xóa theo yêu cầu compliance.

Với LLM, "quên tuyệt đối" gần như không thể chứng minh. Ta thường chỉ có thể nói: sau intervention, model ít tái tạo, ít suy ra, và ít bị extraction attack hơn trên một eval suite cụ thể, trong khi capability chính không giảm quá ngưỡng.

Do đó unlearning không phải một câu thần chú pháp lý. Nó là một pipeline đo rủi ro: forget set cần giảm leakage, retain set cần giữ utility, holdout attack set cần chứng minh model không chỉ overfit vào test cases.

Mental Model

Analogy: Bạn đang kéo model ra khỏi một vùng nhớ cần quên, nhưng vẫn muốn nó đứng vững trên vùng năng lực cần giữ. Kéo quá nhẹ thì vẫn leak. Kéo quá mạnh thì quên lan sang coding, reasoning, factual QA, hoặc ngôn ngữ khác.

  • Forget set DfD_f: dữ liệu hoặc behavior cần loại bỏ.
  • Retain set DrD_r: năng lực và distribution phải giữ lại.
  • Holdout attack set: prompts không dùng trong training, dùng để phát hiện leakage thật.
  • Gold standard: retrain từ scratch không có DfD_f, nhưng thường quá đắt.
  • Success criterion: giảm leakage có đo lường, không hứa quên tuyệt đối.

Cơ chế (Mechanism)

Objective cơ bản dùng gradient ascent trên forget set và retain loss trên retain set:

L(θ)=−LCE(θ;Df)+λLCE(θ;Dr)\mathcal{L}(\theta) = -\mathcal{L}_{CE}(\theta; D_f) + \lambda \mathcal{L}_{CE}(\theta; D_r)

Term đầu làm model kém hơn trên dữ liệu cần quên. Term thứ hai kéo model giữ capability trên dữ liệu cần giữ. λ\lambda là trade-off:

  • λ\lambda quá nhỏ: model quên mạnh nhưng utility collapse.
  • λ\lambda quá lớn: utility giữ tốt nhưng leakage còn nhiều.

Gradient Difference (GradDiff) ổn định hơn pure gradient ascent:

∇LGD=−∇θLCE(Df)+λ∇θLCE(Dr)\nabla \mathcal{L}_{GD} = - \nabla_\theta \mathcal{L}_{CE}(D_f) + \lambda \nabla_\theta \mathcal{L}_{CE}(D_r)

KL-retain thay retain cross entropy bằng KL divergence so với original model:

Lretain=DKL(πθ0(⋅∣xr)∥πθ(⋅∣xr))\mathcal{L}_{retain} = D_{KL}\left( \pi_{\theta_0}(\cdot \mid x_r) \parallel \pi_{\theta}(\cdot \mid x_r) \right)

Tổng objective:

L(θ)=−LCE(θ;Df)+λDKL(πθ0∣∣πθ;Dr)\mathcal{L}(\theta) = -\mathcal{L}_{CE}(\theta; D_f) + \lambda D_{KL}(\pi_{\theta_0} || \pi_\theta; D_r)

Forget quality thường được đo bằng ROUGE-L thấp, extraction success thấp, và membership inference attack gần random:

MIA AUC→0.5\text{MIA AUC} \rightarrow 0.5

Diagram

How It Works

  1. Define DfD_f: records, documents, strings, personas, hazardous procedures, hoặc copyrighted spans cần quên.
  2. Define DrD_r: general instruction data, domain tasks, multilingual prompts, safety prompts, coding/math samples.
  3. Create holdout attack set: paraphrase, multi-turn, indirect prompts, partial-context extraction, decoding variants.
  4. Run baseline eval trước intervention: leakage, ROUGE-L, MIA, PPL, MMLU, safety.
  5. Choose method: GradDiff cho stability; KL-retain nếu muốn preserve distribution gần original model.
  6. Grid search λ\lambda, learning rate, epochs. Một starting point thường là λ∈{0.1,0.5,1.0,2.0}\lambda \in \{0.1, 0.5, 1.0, 2.0\}, LR thấp như 1e−51e^{-5}, 3-10 epochs.
  7. Evaluate forget quality: exact leakage, semantic leakage, extraction robustness, MIA.
  8. Evaluate retain quality: perplexity tăng < 10%, MMLU/task accuracy giảm < 2%, product tasks pass.
  9. Run adversarial phase: paraphrase extraction, roleplay, multi-turn, indirect request.
  10. Publish known limitations: unlearning scope, eval coverage, residual leakage risk.

Code

code
import torch
from dataclasses import dataclass
from torch.utils.data import Dataset, DataLoader
from transformers import AutoModelForCausalLM, AutoTokenizer


class TextDataset(Dataset):
    def __init__(self, texts: list[str], tokenizer, max_length: int = 512):
        self.texts = texts
        self.tokenizer = tokenizer
        self.max_length = max_length

    def __len__(self) -> int:
        return len(self.texts)

    def __getitem__(self, idx: int) -> dict[str, torch.Tensor]:
        enc = self.tokenizer(
            self.texts[idx],
            truncation=True,
            max_length=self.max_length,
            padding="max_length",
            return_tensors="pt",
        )
        item = {k: v.squeeze(0) for k, v in enc.items()}
        item["labels"] = item["input_ids"].clone()
        item["labels"][item["attention_mask"] == 0] = -100
        return item


def causal_lm_loss(model, batch) -> torch.Tensor:
    outputs = model(**batch)
    return outputs.loss


def kl_retain_loss(current_model, reference_model, batch, temperature: float = 1.0) -> torch.Tensor:
    with torch.no_grad():
        ref_logits = reference_model(
            input_ids=batch["input_ids"],
            attention_mask=batch["attention_mask"],
        ).logits / temperature

    cur_logits = current_model(
        input_ids=batch["input_ids"],
        attention_mask=batch["attention_mask"],
    ).logits / temperature

    ref_probs = torch.softmax(ref_logits, dim=-1)
    cur_log_probs = torch.log_softmax(cur_logits, dim=-1)
    ref_log_probs = torch.log_softmax(ref_logits, dim=-1)

    mask = batch["attention_mask"].unsqueeze(-1)
    kl = ref_probs * (ref_log_probs - cur_log_probs)
    return (kl * mask).sum() / mask.sum().clamp(min=1)


@dataclass
class UnlearningConfig:
    lambda_retain: float = 1.0
    lr: float = 1e-5
    epochs: int = 3
    use_kl_retain: bool = True


def unlearn_gradient_diff(
    model,
    reference_model,
    forget_loader: DataLoader,
    retain_loader: DataLoader,
    cfg: UnlearningConfig,
    device: str = "cuda",
):
    optimizer = torch.optim.AdamW(model.parameters(), lr=cfg.lr)
    model.train()
    reference_model.eval()

    for epoch in range(cfg.epochs):
        retain_iter = iter(retain_loader)
        for forget_batch in forget_loader:
            try:
                retain_batch = next(retain_iter)
            except StopIteration:
                retain_iter = iter(retain_loader)
                retain_batch = next(retain_iter)

            forget_batch = {k: v.to(device) for k, v in forget_batch.items()}
            retain_batch = {k: v.to(device) for k, v in retain_batch.items()}

            forget_loss = causal_lm_loss(model, forget_batch)
            if cfg.use_kl_retain:
                retain_loss = kl_retain_loss(model, reference_model, retain_batch)
            else:
                retain_loss = causal_lm_loss(model, retain_batch)

            loss = -forget_loss + cfg.lambda_retain * retain_loss
            optimizer.zero_grad()
            loss.backward()
            torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)
            optimizer.step()

        print(
            f"epoch={epoch} forget_loss={forget_loss.item():.4f} "
            f"retain_loss={retain_loss.item():.4f}"
        )

    return model


def lcs_len(a: list[str], b: list[str]) -> int:
    dp = [[0] * (len(b) + 1) for _ in range(len(a) + 1)]
    for i, x in enumerate(a, 1):
        for j, y in enumerate(b, 1):
            dp[i][j] = dp[i - 1][j - 1] + 1 if x == y else max(dp[i - 1][j], dp[i][j - 1])
    return dp[-1][-1]


def rouge_l(prediction: str, reference: str) -> float:
    pred_tokens = prediction.split()
    ref_tokens = reference.split()
    if not pred_tokens or not ref_tokens:
        return 0.0
    lcs = lcs_len(pred_tokens, ref_tokens)
    precision = lcs / len(pred_tokens)
    recall = lcs / len(ref_tokens)
    if precision + recall == 0:
        return 0.0
    return 2 * precision * recall / (precision + recall)


@torch.no_grad()
def extraction_eval(model, tokenizer, prompts: list[str], references: list[str], device: str = "cuda") -> dict:
    model.eval()
    scores = []
    for prompt, ref in zip(prompts, references):
        inputs = tokenizer(prompt, return_tensors="pt").to(device)
        output_ids = model.generate(**inputs, max_new_tokens=128, do_sample=False)
        text = tokenizer.decode(output_ids[0], skip_special_tokens=True)
        scores.append(rouge_l(text, ref))
    return {
        "mean_rouge_l": sum(scores) / len(scores),
        "max_rouge_l": max(scores),
        "n": len(scores),
    }

Benchmarks

Representative TOFU-style results từ Maini et al. 2024:

MethodForget QualityModel UtilityROUGE-L on forget setNhận xét
Original0.0%100.0%0.85Chưa unlearn
Gradient Ascent68.2%51.3%0.12Quên mạnh nhưng utility collapse
GradDiff79.4%82.1%0.09Trade-off tốt hơn
KL + GA76.8%85.6%0.10Preserve distribution tốt hơn
Retrain without forget data100.0%100.0%~0.00Gold standard nhưng đắt

Operational threshold thường dùng:

MetricExample threshold
ROUGE-L on forget set< 0.10
Extraction success< 5%
MIA AUC0.45-0.55
Perplexity increase< 10%
MMLU/task drop< 2%

Khi Nào Dùng / Khi Nào Không

Dùng khiKhông dùng khi
Cần giảm leakage của PII/copyright/hazardous data trong weightsChỉ cần xóa document khỏi retrieval index
Có forget set rõ ràng và retain set đủ rộngKhông định nghĩa được scope cần quên
Có legal/compliance yêu cầu bằng chứng định lượngMuốn hứa "model đã quên tuyệt đối"
Có ngân sách eval adversarialChỉ có vài direct prompts để test
Có thể chấp nhận trade-off utilityProduct không chịu được regression nhỏ

Failure Modes

FailureNguyên nhânMitigation
Exact leakage còn lạiForget loss chưa đủ hoặc eval quá hẹpTăng attack set, train thêm, adjust lambda
Semantic leakageModel không lặp nguyên văn nhưng tiết lộ cùng factParaphrase/summary QA eval
Utility collapseGradient ascent quá mạnhKL retain, tăng lambda, giảm LR/epochs
Eval overfitHoldout không tách khỏi trainingTạo blind holdout và adversarial set
False confidencePass direct prompt nhưng fail multi-turnMulti-turn, roleplay, indirect extraction
Safety driftRetain set thiếu safety promptsSafety regression suite bắt buộc

Evals

Loại evalTest cụ thểTín hiệu lỗi
Exact leakageString/near-duplicate extractionTái tạo nguyên văn dữ liệu cần quên
Semantic leakageParaphrase, summary, partial factsKhông lặp chữ nhưng tiết lộ cùng thông tin
Extraction robustnessPrompt variants, decoding variants, multi-turnLeak xuất hiện khi đổi framing
Membership inferenceMIA AUC, loss-based attackPrivacy risk vẫn cao
Retain utilityProduct tasks, coding, math, multilingualQuên lan vượt ngưỡng
Safety retainHarmful compliance, benign near-boundaryUnlearning làm safety drift

Artifacts

  • Forget set version-controlled, có owner và deletion rationale.
  • Retain set gồm product tasks, general capability, safety prompts.
  • Holdout attack set không dùng trong training.
  • Baseline eval report trước unlearning.
  • Leakage report sau unlearning: exact, semantic, MIA, adversarial.
  • Utility report: PPL, MMLU, MT-Bench, domain benchmarks.
  • Known limitations doc: scope, residual risk, covered/not covered attack classes.
  • Rollback checkpoint và deployment decision record.

References

  • Maini et al. (2024). TOFU: A Task of Fictitious Unlearning for LLMs. arXiv:2401.06121
  • Yao et al. (2023). Large Language Model Unlearning. arXiv:2310.10683
  • Jang et al. (2023). Knowledge Unlearning for Mitigating Privacy Risks in Language Models. arXiv:2210.01504
  • Bourtoule et al. (2021). Machine Unlearning. arXiv:1912.03817
  • Evaluation Framework
  • Knowledge Editing - ROME & MEMIT
  • Abliteration