Membership Inference: Audit a Model for Training-Data Leakage Before It Ships
Hands-on lab · IDE in your browser

Membership Inference: Audit a Model for Training-Data Leakage Before It Ships

Run the privacy audit a model needs before release: measure whether an attacker can tell which records were in the training set, from the confidence gap to a loss-threshold attack scored by AUC, the true-positive rate at a 1% false-positive rate, and membership advantage.

Time
50 min
Checked steps
5
Level
Intermediate
Setup
None
Read step 1

Hands-on labs require Pro · $29.99/mo · cancel anytime

Map the attack surface
Query
Retriever
LLM
Poisoned doc
retrieved chunk
Answer
0%
Attack-success rate
Attacks blocked · benign answers pass
graded on real output, not the model's talk

The job

Harbor Health is about to ship a 30-day readmission model trained on its patients' records. Privacy review has one question: can anyone tell from the model whether a given person was in the training set? For a hospital, that would reveal who was a patient. You run the audit, measure how many patients the model exposes, fix the cause, and gate the release.

5 steps, each checked when you finish it

A check runs your work at the end of every step. Hints and the full solution are there if you get stuck.

  1. 1

    The confidence gap

    Harbor Health is about to ship a model that predicts 30-day readmission.

  2. 2

    The loss attack

    The simplest membership attack needs nothing but the model's output: compute each record's loss and call low-loss records members.

  3. 3

    The patients it exposes

    An average like AUC can hide the worst case.

  4. 4

    Audit and defend

    The leak comes from memorisation: a forest grown to full depth fits every training patient exactly, noise included.

  5. 5

    The release gate

    A tempting shortcut is to leave the model alone and have the API return only the predicted label, no confidence scores.

Step 1 as it appears in the lab

The lab’s own text. The hint and the solution stay inside the lab.

Step 1: The confidence gap

Harbor Health is about to ship a model that predicts 30-day readmission. Before it goes out, privacy review asks one question: given a patient's record, could someone tell whether that record was in the training set? For a hospital model, a yes means revealing that the person was a patient there. That is membership inference, and this lab is the audit that answers it.

members.csv is the training set, nonmembers.csv is the same population the model never saw, and train_overfit builds the model the team planned to ship. Write two functions in audit.py:

  • true_label_confidence(model, X, y): for each record, the probability the model gives to that record's own label (predict_proba, picking the column named by y).
  • confidence_gap(model, data): mean true-label confidence on members minus the same on nonmembers.

Run it. A model that has memorised its training set is far more sure of itself on those records, and that difference is exactly what an attacker measures.

audit.py, the file you edit105 lines
"""Harbor Health: a privacy audit before a readmission model ships. Membership inference asks one question:
given a patient's record, can someone tell whether it was in the training set? For a hospital model, a yes
reveals that the person was a patient. You measure that leak, then remove it."""
import csv

import numpy as np
from sklearn.ensemble import RandomForestClassifier

COLS = ["age", "bmi", "systolic_bp", "glucose", "prior_visits", "length_of_stay", "medications", "a1c"]


def load(path):
    """(X, y) from one of the record files."""
    X, y = [], []
    with open(path) as f:
        for r in csv.DictReader(f):
            X.append([float(r[c]) for c in COLS])
            y.append(int(r["readmitted"]))
    return np.array(X), np.array(y)


def load_all():
    """members = the training set; nonmembers = same population, never trained on; test = for accuracy."""
    return {"members": load("members.csv"), "nonmembers": load("nonmembers.csv"), "test": load("test.csv")}


def train_overfit(X, y):
    """The model the team was about to ship: a random forest grown to full depth."""
    return RandomForestClassifier(n_estimators=100, random_state=0, n_jobs=1).fit(X, y)


class _ScoresHidden:
    def __init__(self, model):
        self.model = model

    def predict(self, X):
        return self.model.predict(X)

    def predict_proba(self, X):
        return np.full((len(X), 2), 0.5)

    def score(self, X, y):
        return float((self.predict(X) == y).mean())


def hide_scores(model):
    """The shortcut someone will propose: serve the same model, but return only labels. Every confidence the
    API exposes becomes 0.5."""
    return _ScoresHidden(model)


def true_label_confidence(model, X, y):
    """The probability the model gives to each record's true label."""
    # TODO (Step 1): predict_proba, picking each row's true-label column.
    raise NotImplementedError("Step 1: write true_label_confidence()")


def confidence_gap(model, data):
    """Mean true-label confidence on members minus on nonmembers. Near 0 means the model treats records it
    trained on like any other record; a large gap is what an attacker exploits."""
    # TODO (Step 1): mean true-label confidence on members minus on nonmembers.
    raise NotImplementedError("Step 1: write confidence_gap()")


def loss(model, X, y):
    """Per-record loss: -log of the true-label confidence, clipped at 1e-6 so a zero stays finite."""
    raise NotImplementedError("loss() arrives in Step 2")


def attack_auc(member_losses, nonmember_losses):
    """AUC of the loss-threshold attack: the chance a random member has a lower loss than a random nonmember,
    counting ties as half. 0.5 means the attack learns nothing."""
    raise NotImplementedError("attack_auc() arrives in Step 2")


def tpr_at_fpr(member_losses, nonmember_losses, fpr=0.01):
    """Flag a record as a member when its loss is at or below a threshold. Over every threshold that flags at
    most `fpr` of nonmembers, return the largest share of members flagged."""
    raise NotImplementedError("tpr_at_fpr() arrives in Step 3")


def advantage(member_losses, nonmember_losses):
    """Membership advantage: the best TPR - FPR over all thresholds. 0 means no leak."""
    raise NotImplementedError("advantage() arrives in Step 3")


def audit(model, data):
    """One report: train and test accuracy, attack AUC, TPR at 1% FPR, and membership advantage."""
    raise NotImplementedError("audit() arrives in Step 4")


def train_private(X, y):
    """Your defence: a model that generalises instead of memorising, at about the same test accuracy."""
    raise NotImplementedError("train_private() arrives in Step 4")


def label_only_auc(model, data):
    """The attack when the API returns only the predicted label: a member is guessed when the prediction is
    correct. Hiding confidences does not help a model that memorised its training set."""
    raise NotImplementedError("label_only_auc() arrives in Step 5")


def release_gate(model, data, max_auc=0.6):
    """Block the release if either attack beats max_auc. Returns {"release", "attack_auc", "label_auc"}."""
    raise NotImplementedError("release_gate() arrives in Step 5")
Provided for you:members.csvnonmembers.csvtest.csvtry_it.py

Frequently asked questions

What is a membership inference attack?

It is an attack that decides whether a particular record was part of a model's training data, usually by checking how confident or how low-loss the model is on that record. For sensitive data, such as hospital records, knowing someone was in the training set can itself reveal private information.

How do you measure membership inference risk?

Score each record with the model's loss and compare members against held-out nonmembers. Report the attack AUC, the true-positive rate at a low false-positive rate such as 1%, and the membership advantage (the best true-positive rate minus false-positive rate). The low-FPR number shows how many records can be identified with confidence.

How do you defend against membership inference?

Reduce memorisation: regularise the model so it generalises, for example limiting tree depth or leaf size, and consider differentially private training for stronger guarantees. Returning only labels instead of confidence scores is not enough, because a memorising model is still right more often on the records it trained on.

Membership inference attacks and how to defend against them

A membership inference attack asks whether a specific record was in a model's training data. Models that memorise their training set are more confident on the records they saw, and that gap is enough for a simple loss-threshold attack to identify many of them. In this lab you audit a synthetic hospital readmission model: measure the attack's AUC, the share of patients it names at a 1% false-positive rate, and the membership advantage. Then you train a model that generalises, keep its accuracy, watch the leak close, and confirm that returning only labels does not protect a model that memorised.