Release notes: see
CHANGELOG.mdfor the v1.0.0 (dev → main) release summary.
A reproducible pipeline for constructing synthetic, identity-conditioned face
clusters from CC0 synthetic seed images, designed as a benchmark for
machine unlearning. Each identity cluster is a deletion unit — all images
of one synthetic identity share a split (retain / forget) and a
per-image image_subset (train / holdout). Every identity
contributes images to both training and evaluation — no identity-level
test split that would make test accuracy structurally degenerate.
The project publishes two complementary datasets (see
DATASET_CARD_BENCH.md and
DATASET_CARD_RAW.md):
| Release | Resolution | Contents | Purpose |
|---|---|---|---|
| SFHQ-VirtualID-Bench | 224×224 aligned crops | Balanced (67,500) + imbalanced (36,064) | Machine-unlearning benchmark (primary) |
| SFHQ-VirtualID-Raw | 1024×1024 portraits | Max-size (75,000, 100/id) | General-purpose identity-conditioned faces |
| Property | Value |
|---|---|
| Source domain | SFHQ (CC0 synthetic portraits) |
| Generation method | InstantID + Juggernaut-XL-v9 + ControlNet |
| Output resolution | 224×224 (Bench, aligned crops) / 1024×1024 (Raw, portraits) |
| Identities | 750 |
| Images per identity | 90 (Bench balanced) / 100 (Raw) / variable (Bench imbalanced, ratio-based gradient) |
| Total images | 67,500 (Bench balanced) / 36,064 (Bench imbalanced) / 75,000 (Raw) |
| Splits | Retain 675, Forget 75 identities; per-image image_subset (train + holdout) |
| Forget protocol | 15 steps, 75 forget identities — uniform 5/step (baseline) or seeded-Poisson batches (variant) |
| Labels (standard) | identity_id, age_group, split, forget_step, forget_step_poisson, image_subset, arcface_similarity, laplacian_variance, detection_confidence (prompt metadata stripped from Bench) |
| Labels (imbalanced) | plus popularity_bin, images_per_identity on the 224 variant |
| Metadata format | CSV + Parquet |
| Intended task | Machine unlearning (identity-level deletion) |
| Artifacts | 3 datasets across 2 releases: Bench (224 balanced + imbalanced), Raw (1024 max-size) |
Existing unlearning benchmarks either use real face photographs (creating ethical and privacy barriers) or treat images as independent samples (breaking the identity-consistency constraint that makes deletion meaningful). SFHQ- InstantID solves both problems:
- Synthetic, not anonymous. Every face is generated by a fully synthetic pipeline — SFHQ seeds are CC0 AI-generated portraits, and InstantID + Juggernaut-XL-v9 introduce controlled identity-preserving variation. We do not claim the images are "anonymous" or "privacy-safe"; synthetic does not mean risk-free, and residual likeness or memorisation is possible.
- Identity clusters are deletion units. All 90 images of identity 37 share
the same
identity_id, the samesplit, and (ifforget) the sameforget_step. An unlearning system must genuinely erase a person rather than scattered pixels. - Sequential forget protocol. 75 identity clusters are deleted over 15
steps, modelling real-world incremental deletion requests (GDPR / CCPA).
Both schedules ship as columns in the same artifact:
forget_step(uniform baseline) — 5 identities per step, equal counts. Step index == cumulative forgotten count, so per-step curves are directly comparable with no batch-size confound.poisson(variant) — per-step counts drawn from a seeded Poisson distribution (λ = 5), rebalanced to total 75. Models the arrival process of real deletion-request streams: occasional near-empty steps and occasional bursts (Ginart et al., 2019; arXiv:2012.01668; the streaming-forgetting framework of Shen et al., 2025, arXiv:2507.15280 shows step-to-step total variation drives unlearning difficulty). Evaluation MUST compare schedules by cumulative forgotten count, not step index, since step index no longer equals cumulative count. The schedule is a fixed dataset property (seeded, recorded inRELEASE_MANIFEST.json), not a runtime variable.
- Imbalanced variant. A companion
dataset_imbalanced.csvuses the same identities but prunes images to a 3-bin gradient (82:41:16 train counts with default ratios; configurable), with constant holdout per bin. Evaluators measure unlearning difficulty as a function of per-identity representation, mirroring real-world long-tail distributions.
flowchart TD
A[SFHQ source images<br/>CC0 synthetic, ~90k] --> B[CLIP + KMeans<br/>diverse seed selection]
B --> C[InstantID + Juggernaut-XL-v9<br/>identity-conditioned generation]
C --> D[Merge 12-GPU shards]
D --> E[ArcFace extraction on<br/>1024x1024 candidates]
D --> F[MTCNN alignment<br/>sharpness + ArcFace gating]
E --> G[Identity-level splits<br/>retain / forget]
F --> G
E --> J[dataset_raw.csv<br/>1024x1024 release artifact]
G --> H[dataset.csv + parquet<br/>224x224 release artifact]
G --> I[dataset_imbalanced.csv<br/>224x224 3-bin gradient]
| Phase | Script | Resources | Time | Description |
|---|---|---|---|---|
| 1. Download | step1_download |
1 CPU | ~5 min | Precomputed CLIP features + KMeans seed selection from SFHQ |
| 2. Generate | hpc_generate.sh |
15× L40S GPU (exclusive) | ~18 hr | 750 identities (50/shard), 100 candidates each, 100 unique prompts |
| 3. Merge | hpc_merge.sh |
1 CPU | ~30 min | Unify shards, remap identity IDs |
| 4. Extract | hpc_extract.sh |
1× GPU | ~2 hr | ArcFace embeddings + demographics on 1024×1024 candidates (detection works on full portraits) |
| 5. Preprocess | hpc_preprocess.sh |
1× GPU | ~4 hr | MTCNN detect + align, sharpness + ArcFace gating → 224×224 crops |
| 6. Build | hpc_build.sh |
1 CPU | ~15 min | All three artifacts: 224 balanced/imbalanced + 1024 max-size |
Extract runs on the 1024×1024 raw candidates (detection works on full portraits — it fails on tight 224 crops, which previously degraded all demographics to constants). Extract and preprocess are sibling steps: both consume the merge output and neither reads the other's output, so they can run in parallel on two GPUs to halve the wall time. The build step joins their outputs through the (identity_id, trial) key and produces four dataset pairs from a single pipeline run.
| Parameter | Value | Purpose |
|---|---|---|
dataset.nidentities |
750 | Total identity clusters |
dataset.imagesperidentity |
90 | Final images per identity |
dataset.candidatesperidentity |
100 | Candidates generated per identity |
dataset.forget_pct |
0.10 | Fraction of identities in forget set |
dataset.forget_steps |
15 | Unlearning steps (balanced; both schedules ship as forget_step + forget_step_poisson columns) |
dataset.holdout_frac |
0.20 | Fraction of each identity's images for holdout evaluation |
dataset.min_holdout |
5 | Minimum holdout images per identity (floor) |
dataset.skip_download |
false | Skip Kaggle download if data exists locally |
pipeline.instantid.base_model |
RunDiffusion/Juggernaut-XL-v9 |
Base SDXL model |
pipeline.instantid.controlnet_conditioning_scale |
0.80 | ControlNet spatial control |
pipeline.imgsize |
224 | Final crop resolution |
pipeline.min_similarity_raw |
0.40 | ArcFace similarity floor — config default only; NOT enforced in v1.0.0 (arcface_similarity ships as a confound column) |
pipeline.min_similarity_final |
0.45 | ArcFace similarity floor — config default only; NOT enforced in v1.0.0 (arcface_similarity ships as a confound column) |
pipeline.blurthreshold |
80.0 | Laplacian variance sharpness floor |
Size guards: nidentities ≥ 100 enforced for download and build steps
(generate-only shards bypass this). forget_steps ≥ 5 enforced at build.
Hyperparameters (guidance_scale, ip_adapter_scale, num_inference_steps)
are model-aware — resolved from MODEL_DEFAULTS in generate_identities.py
based on the selected base_model. Juggernaut: 3.0 / 0.85 / 30; SDXL base:
5.5 / 0.90 / 25. Override via CLI (pipeline.instantid.guidance_scale=4.0).
Prompts are defined in src/prompts.py. A 20 × 5 = 100
grid of unique variation prompts combines
- 20 composition tuples (pose + expression + setting + camera lens)
- 5 lighting treatments (window, studio, daylight, golden hour, overcast)
Each identity gets a deterministically shuffled subset of 100 prompts from the pool, seeded by the identity's cluster ID. Every candidate in a shard receives a unique prompt — no two candidates share the same rendering instruction within an identity.
Full configuration in conf/config.yaml.
# Create conda env (Python 3.10, CUDA 12.6)
conda create -n data_gen python=3.10 -y
conda activate data_gen
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu124
pip install xformers --index-url https://download.pytorch.org/whl/cu124
pip install -r requirements.txtDetailed HPC setup: [HPC CookBook.md](HPC CookBook.md).
# Full pipeline (all 6 phases, chained with Slurm dependencies):
sbatch scripts/hpc_full_pipeline.sh
# Individual phases:
sbatch scripts/hpc_generate.sh # 12-GPU array
sbatch scripts/hpc_merge.sh # after generate completes
sbatch scripts/hpc_preprocess.sh # after merge completes
sbatch scripts/hpc_extract.sh # after extract completes
sbatch scripts/hpc_build.sh # after extract completesAll data lands on Lustre scratch (/scratch/$USER/datagen/data/). Model
caches stay on home storage.
cd src
# Precomputed CLIP features — use --skip-download after first run:
python main.py --config-name step1_download dataset.skip_download=false
python main.py --config-name step2_generate # generate
python main.py --config-name step4_extract # embeddings (on 1024 candidates)
python main.py --config-name step3_preprocess # align + filter (224 crops)
python main.py --config-name step5_build # builds all four dataset pairsNote the order: extract now runs on the raw 1024×1024 candidates (Step 4) before preprocess (Step 3), because InsightFace detection fails on tight 224 crops but works on full-resolution portraits. The step numbers follow the Hydra config filenames; the pipeline scripts chain them in the correct order automatically.
Each step accepts Hydra overrides; see conf/config.yaml
for all tunables.
| Column | Type | Description |
|---|---|---|
image_path |
string | Relative path: images/identity_NNN/crop_XXX.jpg |
identity_id |
int (0–749) | Synthetic identity cluster ID |
age_group |
int (0–3) | Proxy age label: 0=Young, 1=Adult, 2=Middle-Aged, 3=Senior. Per-image (see note below) |
age |
int | Raw InsightFace age estimate. Per-image (see note below) |
gender |
int (0/1) | InsightFace gender classifier output |
split |
string | retain or forget — identity-level role |
forget_step |
int (0–14, -1) | Step under the uniform schedule (5 ids/step; step index == cumulative forgotten count); -1 for retain |
forget_step_poisson |
int (0–14, -1) | Step under the seeded-Poisson schedule (variable ids/step, GDPR-arrival stress test; evaluate by cumulative count); -1 for retain |
image_subset |
string | train or holdout — per-image role (MUFAC-aligned) |
arcface_similarity |
float [-1,1] | Cosine similarity to identity's mean ArcFace embedding (confound control) |
laplacian_variance |
float | Sharpness score from quality filter (quality confound control) |
detection_confidence |
float | Face detector confidence (alignment quality control) |
Both forget schedules ship in one artifact.
forget_stepis the MUFAC baseline (equal 5 ids/step — per-step curves are directly comparable).forget_step_poissonis the seeded arrival-model stress test (λ=5, rebalanced to total 75; occasional near-empty and burst steps). For cross-schedule comparisons, evaluate by cumulative forgotten count, not step index (Shen et al., 2025, arXiv:2507.15280). The schedule is a fixed dataset property (recorded inRELEASE_MANIFEST.json), not a runtime variable. Prompt metadata (pose…camera) is deliberately stripped from Bench — it ships only in the Raw release.
Per-image age labels (by design).
age_groupandageare estimated per image on the parent 1024×1024 candidate (InsightFace gender/age classifier), so a single identity can span multiple age groups — e.g. identity 558 ranges 23–65 across its images while remaining recognisably the same person. This is a deliberate realism property: the synthetic identity appears at different ages, and the per-image labels prevent an age classifier from shortcutting to identity (identity_id → constant age). Do not collapse to a per-identity constant when using the data.
All crops are stored as 224×224 uint8 BGR. Downstream classifiers using
ImageNet-pretrained backbones (e.g. ResNet-18) must convert to RGB and
normalise with ImageNet statistics — CROP_SIZE, IMAGENET_MEAN,
IMAGENET_STD are exported from src/common.py. This matches the ImageNet
training regime so pretrained features activate at full fidelity from epoch 1.
Critical invariant (MUFAC-aligned): Every identity_id maps to exactly
one split (retain or forget), and every identity contributes both
image_subset values (train + holdout) within that split. There is no
identity-disjoint test split — the old unseen-identity test accuracy was
structurally 0, and forgetting was measured on the same images used for
unlearning. Now the holdout subset (18/id by default) is genuinely held out
of training, giving meaningful retention, forgetting-generalisation, and
forget-train vs forget-holdout gap metrics (see the MUFAC design in
msc-project-core-implementation/.hermes/plans/dataset-split-redesign.md).
Enforced by validate_release.py.
Full-resolution 1024×1024 portrait dataset — the raw generation outputs before face alignment. All 100 portraits per identity, no quality trim, no splits (max-size reference). Designed as a general-purpose release for identity recognition, face generation evaluation, demographic bias studies, and erasure-transfer testing (does forgetting the 224 crop also hide identity in the full context?). Researchers can construct balanced subsets of up to 100 images/identity with their own train/holdout splits.
| Column | Type | Description |
|---|---|---|
image_path |
string | Relative path: images/identity_NNN/portrait_YYY.jpg |
identity_id |
int (0–749) | Synthetic identity cluster ID |
age_group |
int (0–3) | Proxy age label |
age |
int | Raw InsightFace age estimate |
gender |
int (0/1) | InsightFace gender classifier |
arcface_similarity |
float [-1,1] | Cosine similarity to identity's mean portrait embedding |
pose |
string | Head/body position |
expression |
string | Facial expression |
lighting |
string | Lighting condition |
setting |
string | Background/scene |
camera |
string | Camera angle |
No laplacian_variance or detection_confidence — these are crop-level
quality metrics and are meaningless on raw portraits. No split /
forget_step / forget_step_poisson / image_subset — the Raw release is
max-size with no split protocol; split construction is left to the consumer.
The Raw release is max-size only (all 100 portraits per identity). The imbalanced variant is exclusive to the Bench (224) release.
| Release | Artifact | Resolution | Identities | Images/id | Total rows | Purpose |
|---|---|---|---|---|---|---|
| Bench | dataset.csv/.parquet |
224×224 crops | 750 | 90 (72 train + 18 holdout) | 67,500 | Dissertation unlearning benchmark |
| Bench | dataset_imbalanced.csv/.parquet |
224×224 crops | 750 | ratio-based (82:41:16 train; +18 holdout/id) | 36,064 | Long-tail stress test |
| Raw | dataset_raw.csv/.parquet |
1024×1024 portraits | 750 | 100 | 75,000 | General-purpose release |
All three share identical identity_id and split labels (where present).
Demographics (age, gender) are derived from detection on 1024×1024 portraits
and are real — no longer degenerate from the CPU fallback path.
An extra artifact produced alongside the balanced dataset. Shares the same
identity pool and split assignment but prunes train images to a
ratio-based 5:1 train gradient across three popularity bins,
calibrated to the VGG-Face2 range (Cao et al., 2018; ~10:1 max-min,
5:1 inter-quartile; Wang et al., 2019 "Deep Face Recognition: A Survey";
Liu et al., 2019 "Large-Scale Long-Tailed Recognition in an Open World", CVPR).
Configurable ratios — default 1.0:0.50:0.20 applied to the max train pool
of candidatesperidentity − holdout = 100 − 18 = 82:
| Bin | Identities | Train/ID | Holdout/ID | Total kept/ID | Total images |
|---|---|---|---|---|---|
| High (top 10%) | 75 | 82 | 18 | 100 | 7,500 |
| Medium (30%) | 225 | 41 | 18 | 59 | 13,275 |
| Low (bottom 60%) | 450 | 16 | 18 | 34 | 15,300 |
Holdout is the same per-identity reserve as the balanced release
(max(min_holdout, round(imagesperidentity × holdout_frac)) = 18 with
defaults), so probe stability is uniform across all popularity tiers. The
gradient scales with the candidate pool: if a future release generates 550
candidates/identity targeting 500 images, holdout = 100 and max_train = 450,
and the ratios re-derive train counts (guidance §2.2).
| Column | Type | Description |
|---|---|---|
image_path |
string | Relative path: images/identity_NNN/crop_XXX.jpg |
identity_id |
int (0–749) | Synthetic identity cluster ID |
age_group / age / gender |
int | Proxy demographics (per-image) |
split |
string | retain / forget — identity-level role (SAME map as balanced) |
image_subset |
string | train / holdout (MUFAC-aligned; 18 holdout/id constant across bins) |
arcface_similarity |
float [-1,1] | Cosine sim to identity's mean embedding (confound control) |
laplacian_variance / detection_confidence |
float | Quality confound controls |
popularity_bin |
string | "high", "medium", or "low" |
images_per_identity |
int | Actual kept-image count per identity (100/59/34 — continuous difficulty axis) |
No forget-schedule columns in imbalanced. The schedule axis lives in the balanced artifact (
forget_step/forget_step_poisson); the imbalanced variant is a single-purpose long-tail stress test whose only experimental axis is the popularity gradient. Downstream: stratify bypopularity_bin, and use the balanced artifact's split map to run the same 75 forget identities as a cross-dataset control.
Designed for stress-testing the long tail: high-popularity identities (celebrities) are over-learned and hardest to forget; low-popularity identities are under-learned and easiest to scrub. The ratio between high and low bins mirrors real-world face dataset distributions (5:1, VGG-Face2 range).
The imbalanced variant answers the question: "Does unlearning remain effective
across the long-tail data distribution?" All evaluation gates use the
holdout subset (never seen during training or unlearning). The
recommended protocol (baseline gate → distance-to-oracle → budget sweep →
balanced cross-check; see DATASET_CARD_BENCH.md §Intended use) guards the
low-bin floor effect: report per-bin MIA AUC before unlearning, since a
low-bin baseline near 0.5 means "never learned", not "scrubbed".
| Metric | Stratification | Expectation | Threshold |
|---|---|---|---|
| MIA AUC by popularity bin | high / medium / low | AUC near 0.50; no bin > 0.55 (mean) | All bins |
| Forget-holdout accuracy by bin | high / medium / low | Uniformly low (=retrain oracle) in all bins | All bins |
| Retain-holdout accuracy by bin | high / medium / low | High and stable; low-bin drop <= 5% vs high-bin | All bins |
| Forget-train / forget-holdout gap | per method x bin | Gap <= 0.10; larger in low-bin = overfitting signal | All bins |
| Probe identity accuracy by bin | high / medium / low | probes on holdout; directional: low-bin may be higher (less data, stronger memorisation) | Report only |
| Statistical test | Kruskal-Wallis H-test | Retain p-value > 0.05 (no bin disproportionately harmed) | Retain only |
| Released ✅ | Withheld ❌ |
|---|---|
Aligned 224×224 face crops (crop_*.jpg) |
Raw SFHQ source images (data/seeds/) |
1024×1024 candidate images (candidates/) |
ArcFace embedding vectors |
dataset.csv + .parquet (224 balanced) |
Seed-to-output linkage table |
dataset_imbalanced.csv + .parquet (224) |
Juggernaut-XL-v9 / InstantID / ControlNet / InsightFace model weights |
dataset_raw.csv + .parquet (1024) |
Rejected / low-quality candidate images |
datasetsummary*.json (all variants) |
|
datasetsummary_raw.json |
|
Checksums (checksums.sha256) |
|
Schema (schema.json) |
|
Release manifest (RELEASE_MANIFEST.json) |
Model weights must be obtained from upstream sources by each user under the
upstream licence terms. See THIRD_PARTY_NOTICES.md
for exact revisions and licence URLs.
- Synthetic, not risk-free. Generated faces may retain artifacts, demographic bias, or unintended resemblance to real persons. The dataset must not be used for identification, biometric authentication, surveillance, impersonation, or decisions affecting individuals.
- Proxy age labels. Age-group fields are model-estimated (InsightFace gender/age classifier) and must not be interpreted as verified demographic attributes.
- Reconstruction risk. We do not distribute seed-to-output linkage, but residual similarity between a synthetic identity and its single SFHQ seed image is inherent to the InstantID method.
See DATASET_CARD_BENCH.md and
DATASET_CARD_RAW.md for full composition, intended
use, and privacy policy.
| Component | Pinned value |
|---|---|
| Random seed | 42 (configurable) |
| Shard seeds | 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70 |
| Python | 3.10 |
| PyTorch | 2.6.0+cu124 |
| Diffusers | 0.39.0 |
| CUDA | 12.6 |
| GPU | NVIDIA L40S (46 GB) |
| SFHQ part | 1 (~90k source images) |
Exact model revisions are recorded in RELEASE_MANIFEST.json (a copy ships inside each release directory).
@dataset{sfhq_virtualid_bench,
title = {{SFHQ-VirtualID-Bench}: Synthetic Identity-Conditioned Aligned
Face Crops for Machine Unlearning},
author = {Faiz Palwala},
year = {2026},
version = {1.0.0},
doi = {10.5281/zenodo.21877893},
url = {https://github.com/FaizPalwala/virtual-id-gen},
}
@dataset{sfhq_virtualid_raw,
title = {{SFHQ-VirtualID-Raw}: Full-Resolution Synthetic
Identity-Conditioned Face Portraits},
author = {Faiz Palwala},
year = {2026},
version = {1.0.0},
doi = {10.5281/zenodo.21879130},
url = {https://github.com/FaizPalwala/virtual-id-gen},
}See CITATION.cff for the complete metadata file.
This is a research artifact. Bug reports and questions are welcome via GitHub Issues. For feature requests or extensions, please open a discussion first.
- Code: MIT (see
LICENSE). - Images and metadata: Non-commercial research use only, with an explicit
prohibited-use clause. Revisit toward a more permissive licence after
upstream model output-use terms are confirmed. See
LICENSEfor the full terms. - Third-party models: Not included. Each user must obtain Juggernaut-XL-v9,
InstantID, ControlNet, and InsightFace weights from the upstream sources
listed in
THIRD_PARTY_NOTICES.mdunder those projects' own licence terms.
- SFHQ source dataset: SelfishGene/SFHQ-dataset
- InstantID: InstantX/InstantID
- Juggernaut-XL-v9: RunDiffusion/Juggernaut-XL-v9
- InsightFace: deepinsight/insightface
This work was undertaken on the Aire HPC system at the University of Leeds, UK.
This dataset is a research artifact for machine unlearning evaluation. It is not intended for production identity systems, biometric authentication, or any application that makes decisions about people.
