Files
AI-Red-Teaming-CSCD94/privacy/c1.py
T
Jeremy Janella 75faa5c410 added material
2026-05-09 23:21:13 -04:00

308 lines
9.3 KiB
Python

#!/usr/bin/env python3
import os
import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torch.utils.data import DataLoader
from torchvision import datasets, transforms
from safetensors.torch import save_file
from tqdm import tqdm
# =============================================================================
# CONFIGURATION
# =============================================================================
RANDOM_SEED = 1337
BATCH_SIZE = 256
DP_EPOCHS = 10
DP_LR = 0.1
MAX_GRAD_NORM = 1.0
DELTA = 1e-5
# The key parameter: use ε=6 to balance accuracy and privacy
TARGET_EPSILON = 6.0
# Set reproducibility
torch.manual_seed(RANDOM_SEED)
np.random.seed(RANDOM_SEED)
if torch.cuda.is_available():
torch.cuda.manual_seed_all(RANDOM_SEED)
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
# Output directories
MODELS_DIR = "models"
os.makedirs(MODELS_DIR, exist_ok=True)
# SVHN normalization values
SVHN_MEAN = (0.4377, 0.4438, 0.4728)
SVHN_STD = (0.1980, 0.2010, 0.1970)
# =============================================================================
# MODEL ARCHITECTURE
# =============================================================================
class SVHNCNN(nn.Module):
"""CNN for SVHN classification (compatible with Opacus)."""
def __init__(self):
super(SVHNCNN, self).__init__()
self.conv1 = nn.Conv2d(3, 32, kernel_size=3, padding=1)
self.conv2 = nn.Conv2d(32, 64, kernel_size=3, padding=1)
self.conv3 = nn.Conv2d(64, 64, kernel_size=3, padding=1)
self.pool = nn.MaxPool2d(2, 2)
self.fc1 = nn.Linear(64 * 4 * 4, 64)
self.fc2 = nn.Linear(64, 10)
def forward(self, x):
x = self.pool(F.relu(self.conv1(x)))
x = self.pool(F.relu(self.conv2(x)))
x = self.pool(F.relu(self.conv3(x)))
x = x.view(-1, 64 * 4 * 4)
x = F.relu(self.fc1(x))
x = self.fc2(x)
return x
# =============================================================================
# DATA LOADING
# =============================================================================
def get_svhn_loaders(batch_size=256, download=True):
"""Load SVHN dataset."""
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize(SVHN_MEAN, SVHN_STD)
])
train_dataset = datasets.SVHN(
"data", split='train', download=download, transform=transform
)
test_dataset = datasets.SVHN(
"data", split='test', download=download, transform=transform
)
train_loader = DataLoader(
train_dataset, batch_size=batch_size, shuffle=True, num_workers=0
)
test_loader = DataLoader(
test_dataset, batch_size=batch_size, shuffle=False, num_workers=0
)
return train_dataset, test_dataset, train_loader, test_loader
def evaluate_accuracy(model, data_loader, device):
"""Evaluate model accuracy."""
model.eval()
correct = 0
total = 0
with torch.no_grad():
for images, labels in data_loader:
images, labels = images.to(device), labels.to(device)
outputs = model(images)
_, predicted = outputs.max(1)
total += labels.size(0)
correct += predicted.eq(labels).sum().item()
return 100.0 * correct / total
def compute_mia_advantage(model, train_loader, test_loader, device, num_samples=2000):
"""Compute MIA advantage using confidence threshold attack."""
model.eval()
def get_confidences(loader, max_samples):
all_conf = []
count = 0
with torch.no_grad():
for images, _ in loader:
if count >= max_samples:
break
images = images.to(device)
outputs = model(images)
probs = F.softmax(outputs, dim=1)
conf = probs.max(dim=1).values.cpu().numpy()
all_conf.extend(conf)
count += len(conf)
return np.array(all_conf[:max_samples])
member_conf = get_confidences(train_loader, num_samples)
nonmember_conf = get_confidences(test_loader, num_samples)
n = min(len(member_conf), len(nonmember_conf))
all_conf = np.concatenate([member_conf[:n], nonmember_conf[:n]])
all_labels = np.concatenate([np.ones(n), np.zeros(n)])
thresholds = np.percentile(all_conf, np.linspace(0, 100, 500))
best_acc = 0.5
for t in thresholds:
acc = max(
np.mean((all_conf >= t) == all_labels),
np.mean((all_conf < t) == all_labels)
)
best_acc = max(best_acc, acc)
return best_acc, best_acc - 0.5
# =============================================================================
# TRAINING
# =============================================================================
print("=" * 80)
print(" DP-SGD PRIVACY (SVHN)")
print("=" * 80)
print(f"\nDevice: {device}")
print(f"Target epsilon: {TARGET_EPSILON}")
print(f"Required: accuracy >= 55%, MIA advantage <= 5%")
print("\nLoading SVHN dataset...")
train_dataset, test_dataset, train_loader, test_loader = get_svhn_loaders(
batch_size=BATCH_SIZE, download=True
)
print(f"Training samples: {len(train_dataset):,}")
print(f"Test samples: {len(test_dataset):,}")
# Import Opacus
from opacus import PrivacyEngine
from opacus.validators import ModuleValidator
print("\n" + "=" * 80)
print(f" TRAINING: DP-SGD MODEL (Target ε={TARGET_EPSILON})")
print("=" * 80)
# Fresh data loaders for DP training
_, _, train_loader_dp, test_loader_dp = get_svhn_loaders(
batch_size=BATCH_SIZE, download=False
)
# Initialize model
dp_model = SVHNCNN().to(device)
dp_model = ModuleValidator.fix(dp_model)
optimizer_dp = optim.Adam(dp_model.parameters(), lr=1e-3)
#optimizer_dp = optim.SGD(dp_model.parameters(), lr=DP_LR, momentum=0.9)
# Attach privacy engine
privacy_engine = PrivacyEngine(accountant="rdp")
dp_model, optimizer_dp, train_loader_dp = privacy_engine.make_private_with_epsilon(
module=dp_model,
optimizer=optimizer_dp,
data_loader=train_loader_dp,
target_epsilon=TARGET_EPSILON,
target_delta=DELTA,
epochs=DP_EPOCHS,
max_grad_norm=MAX_GRAD_NORM,
)
print(f"\nConfiguration:")
print(f" Target epsilon: {TARGET_EPSILON}")
print(f" Delta: {DELTA}")
print(f" Max gradient norm: {MAX_GRAD_NORM}")
print(f" Epochs: {DP_EPOCHS}")
# Training loop
criterion = nn.CrossEntropyLoss()
dp_model.train()
for epoch in range(DP_EPOCHS):
running_loss = 0.0
correct = 0
total = 0
pbar = tqdm(train_loader_dp, desc=f"Epoch {epoch+1}/{DP_EPOCHS}")
for images, labels in pbar:
images, labels = images.to(device), labels.to(device)
optimizer_dp.zero_grad()
outputs = dp_model(images)
loss = criterion(outputs, labels)
loss.backward()
optimizer_dp.step()
running_loss += loss.item()
_, predicted = outputs.max(1)
total += labels.size(0)
correct += predicted.eq(labels).sum().item()
pbar.set_postfix({
'loss': f'{running_loss/total:.4f}',
'acc': f'{100.*correct/total:.2f}%'
})
# Get current epsilon
epsilon = privacy_engine.get_epsilon(DELTA)
print(f" Epoch {epoch+1}: ε = {epsilon:.2f}")
final_epsilon = privacy_engine.get_epsilon(DELTA)
print(f"\nFinal privacy guarantee: (ε={final_epsilon:.2f}, δ={DELTA})")
# Evaluate
train_acc = evaluate_accuracy(dp_model, train_loader, device)
#train_acc = evaluate_accuracy(dp_model, train_loader_dp, device)
test_acc = evaluate_accuracy(dp_model, test_loader_dp, device)
print(f"\nDP Model Performance:")
print(f" Training accuracy: {train_acc:.2f}%")
print(f" Test accuracy: {test_acc:.2f}%")
print(f" Overfitting gap: {train_acc - test_acc:.2f}%")
# MIA evaluation
print("\n" + "=" * 80)
print(" MEMBERSHIP INFERENCE EVALUATION")
print("=" * 80)
mia_acc, mia_adv = compute_mia_advantage(
dp_model, train_loader_dp, test_loader_dp, device
)
print(f"\nMIA Results:")
print(f" Attack accuracy: {mia_acc:.4f}")
print(f" Attack advantage: {mia_adv:.4f} ({mia_adv*100:.2f}%)")
# Save model
model_path = os.path.join(MODELS_DIR, "dp_model.safetensors")
save_file(dp_model._module.state_dict(), model_path)
print(f"\nModel saved to {model_path}")
# =============================================================================
# VALIDATION CHECK
# =============================================================================
print("\n" + "=" * 80)
print(" VALIDATION CHECK")
print("=" * 80)
accuracy_pass = test_acc >= 55.0
mia_pass = mia_adv <= 0.05
print(f"\nChallenge Requirements:")
print(f" Accuracy >= 55%: {test_acc:.2f}% {'✓ PASS' if accuracy_pass else '✗ FAIL'}")
print(f" MIA advantage <= 5%: {mia_adv*100:.2f}% {'✓ PASS' if mia_pass else '✗ FAIL'}")
if accuracy_pass and mia_pass:
print("\n" + "=" * 80)
print(" SOLUTION READY FOR SUBMISSION")
print("=" * 80)
print(f"\nSubmit {model_path} to the validator to get the flag!")
else:
print("\n" + "=" * 80)
print(" SOLUTION NOT YET PASSING")
print("=" * 80)
print("\nConsider adjusting epsilon or other hyperparameters.")
import requests
with open("./models/dp_model.safetensors", "rb") as f:
response = requests.post(
"http://154.57.164.72:32303/validate",
files={"model": ("dp_model.safetensors", f, "application/octet-stream")}
)
print(response.text)