Files
Finetuning/sets/Data.py
2026-06-14 11:53:31 +02:00

184 lines
5.4 KiB
Python

from torchvision import datasets, transforms
from torch.utils.data import Dataset, DataLoader, Subset
import torch
import numpy as np
import os
from enum import Enum, auto
class Set_Name(Enum):
CELEBA = auto()
CASIAFACES = auto()
# train set transform
def train_transform(res):
return transforms.Compose([
# ResNet expects 224 x 224 res
# Inception expects 299 x 299
transforms.Resize((res, res)),
transforms.RandomHorizontalFlip(p=0.5),
transforms.ColorJitter(
brightness=0.2,
contrast=0.2,
saturation=0.1
),
transforms.ToTensor(),
# normalise to
transforms.Normalize(
mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225]
)
])
# test set transform
def test_transform(res):
return transforms.Compose([
transforms.Resize((res, res)),
transforms.ToTensor(),
transforms.Normalize(
mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225]
)
])
# Load data with 'identity' as target and transform it
def get_set(set_name:Set_Name):
return fetch_celeb_a() if set_name == Set_Name.CELEBA else fetch_casia_faces()
def fetch_celeb_a():
return datasets.CelebA(
root='./data',
split='all',
target_type='identity',
download=True,
transform=None
)
def fetch_casia_faces():
# location of the data (path relative to project root)
final_path = os.path.abspath("./data/casia-set")
if not os.path.exists(final_path):
raise FileNotFoundError(
f"Unpacked dataset not found at {final_path}. "
"Please run Extractor.py first!"
)
print(f"Loading unpacked CASIA dataset from: {final_path}")
return datasets.ImageFolder(
root=final_path,
transform=None
)
def get_ids_and_counts(dataset):
target = get_target(dataset=dataset)
return torch.unique(
input = target,
return_counts = True
)
# filter selected identities from dataset
# How many classes, how many images per class
def select_ids( dataset, sample_size, class_size):
ids, counts = get_ids_and_counts(dataset = dataset)
eligible_mask = counts >= sample_size
eligible_ids = ids[eligible_mask].numpy()
if len(eligible_ids) < class_size:
raise ValueError(
f"Only found {len(eligible_ids)} identities with {sample_size}+ images."
)
# Randomly select identities
return np.random.choice(eligible_ids, class_size, replace=False)
# optional function to get max amount of samples per class
def select_top_ids(dataset, class_size):
ids, counts = get_ids_and_counts(dataset = dataset)
# sort by number of images (descending)
sorted_indices = torch.argsort(counts, descending = True)
top_ids = ids[sorted_indices][:class_size].numpy()
return np.array(top_ids, dtype=int)
def get_target(dataset):
"""
Unified target extractor.
Instantly reads raw dataset arrays or safely scales down to unpack wrapped Subsets.
"""
if hasattr(dataset, 'identity'):
# celebA
targets = dataset.identity
elif hasattr(dataset, 'targets'):
# others
targets = dataset.targets
else:
# If it's an IdentitySubset or standard Subset, extract mapped targets sequentially
# This guarantees we get the 0 -> (n-1) remapped labels
targets = [dataset[i][1] for i in range(len(dataset))]
if not isinstance(targets, torch.Tensor):
targets = torch.tensor(targets)
return targets
# split class images to train and test set.
def get_indices(dataset, identities, split_at, size = 30):
if split_at >= size: # debug safety
raise ValueError(f"Split point ({split_at}) must be less than total size ({size}).")
train_indices = []
test_indices = []
target = get_target(dataset=dataset)
#training_sample = int(sample_size * training_ratio)
np.random.seed(42)
for person_id in identities:
# Get all indices for this specific person
indices = torch.where(target == person_id)[0].numpy()
# Shuffle the indices for this person
np.random.shuffle(indices)
# split data to testing and training
train_indices.extend(indices[:split_at])
test_indices.extend(indices[split_at:size])
return train_indices, test_indices
def get_unlearning_loaders(dataset: Dataset, forget_class_idx: int, batch_size: int = 32) -> tuple[DataLoader, DataLoader]:
"""
Splits an IdentitySubset or standard Dataset into forget and retain sets
based on a remapped target class index.
"""
# extract targets
targets = get_target(dataset=dataset)
# mask indices local to this subset
forget_indices = torch.where(targets == forget_class_idx)[0].tolist()
retain_indices = torch.where(targets != forget_class_idx)[0].tolist()
# PyTorch Subsets
forget_subset = Subset(dataset, forget_indices)
retain_subset = Subset(dataset, retain_indices)
# DataLoaders
forget_loader = DataLoader(forget_subset, batch_size=batch_size, shuffle=False)
retain_loader = DataLoader(retain_subset, batch_size=batch_size, shuffle=True)
print(f"[Data Split] Local Class {forget_class_idx}: {len(forget_subset)} samples | Remaining Classes: {len(retain_subset)} samples.")
return forget_loader, retain_loader