{ "cells": [ { "cell_type": "code", "execution_count": 1, "id": "ccf0b5cd-7868-4cb7-a35c-471f38782258", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "============================================================\n", "PATE: PRIVATE AGGREGATION OF TEACHER ENSEMBLES\n", "Teacher-Student Privacy Through Knowledge Distillation\n", "============================================================\n" ] } ], "source": [ "import os\n", "import json\n", "import numpy as np\n", "import torch\n", "import matplotlib.pyplot as plt\n", "from sklearn.model_selection import train_test_split\n", "from sklearn.preprocessing import StandardScaler\n", "from safetensors.torch import save_file\n", "from tqdm import tqdm\n", "\n", "from htb_ai_library import (\n", " set_reproducibility, use_htb_style,\n", " MLP, get_mnist_loaders,\n", " create_dataloader, train_model, evaluate_accuracy, get_model_predictions,\n", " HTB_GREEN, NODE_BLACK, HACKER_GREY, WHITE,\n", " AZURE, MALWARE_RED, AQUAMARINE, NUGGET_YELLOW,\n", ")\n", "\n", "print(\"=\" * 60)\n", "print(\"PATE: PRIVATE AGGREGATION OF TEACHER ENSEMBLES\")\n", "print(\"Teacher-Student Privacy Through Knowledge Distillation\")\n", "print(\"=\" * 60)\n", "\n", "RANDOM_SEED = 1337\n", "DEVICE = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n", "set_reproducibility(RANDOM_SEED)\n", "use_htb_style()\n", "\n", "PLOT_CONFIG = {\"figsize\": (10, 6), \"dpi\": 150}\n", "DATASET_CONFIG = {\"name\": \"mnist\", \"num_classes\": 10, \"num_features\": 784}\n", "\n", "TEACHER_CONFIG = {\n", " \"num_teachers\": 250, # Original paper configuration\n", " \"hidden_layers\": [128, 64],\n", " \"dropout\": 0.2,\n", " \"epochs\": 30,\n", " \"batch_size\": 64,\n", " \"learning_rate\": 0.001,\n", "}\n", "\n", "AGGREGATION_CONFIG = {\n", " \"noise_scale\": 20.0, # Strong privacy with high consensus\n", " \"num_student_queries\": 5000,\n", "}\n", "\n", "STUDENT_CONFIG = {\n", " \"hidden_layers\": [128, 64],\n", " \"dropout\": 0.2,\n", " \"epochs\": 30,\n", " \"batch_size\": 64,\n", " \"learning_rate\": 0.001,\n", "}" ] }, { "cell_type": "markdown", "id": "e4fcef26-56b4-4d46-8726-3923126c9b6e", "metadata": {}, "source": [ "# Data Partitioning and Teacher Training" ] }, { "cell_type": "code", "execution_count": 2, "id": "ce4ddcc3-037b-4183-bafb-edc474f71cca", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "============================================================\n", "PHASE 1: Loading MNIST Dataset\n", "============================================================\n", "Dataset: 60000 training samples, 784 features, 10 classes\n", "Class distribution: min=5421, max=6742\n", "\n", "Data splits:\n", " Private (teacher training): 48000 samples\n", " Public (student queries): 12000 samples\n", " Holdout (evaluation): 10000 samples\n", "\n", "============================================================\n", "PHASE 2: Training Teacher Ensemble\n", "============================================================\n", "Created 250 partitions, ~192 samples each\n" ] } ], "source": [ "print(\"\\n\" + \"=\" * 60)\n", "print(\"PHASE 1: Loading MNIST Dataset\")\n", "print(\"=\" * 60)\n", "\n", "train_loader, test_loader = get_mnist_loaders()\n", "\n", "X_train = train_loader.dataset.data.numpy().reshape(-1, 784).astype(np.float32) / 255.0\n", "y_train = train_loader.dataset.targets.numpy()\n", "X_test = test_loader.dataset.data.numpy().reshape(-1, 784).astype(np.float32) / 255.0\n", "y_test = test_loader.dataset.targets.numpy()\n", "\n", "num_features = 784\n", "num_classes = 10\n", "DATASET_CONFIG['num_features'] = num_features\n", "DATASET_CONFIG['num_classes'] = num_classes\n", "\n", "print(f\"Dataset: {X_train.shape[0]} training samples, {num_features} features, {num_classes} classes\")\n", "print(f\"Class distribution: min={np.bincount(y_train).min()}, max={np.bincount(y_train).max()}\")\n", "\n", "# Split training data into: private (for teachers) and public (for student queries)\n", "X_private, X_public, y_private, y_public = train_test_split(\n", " X_train, y_train, test_size=0.2,\n", " random_state=RANDOM_SEED, stratify=y_train\n", ")\n", "\n", "# Use standard test set as holdout for evaluation\n", "X_holdout = X_test\n", "y_holdout = y_test\n", "\n", "print(f\"\\nData splits:\")\n", "print(f\" Private (teacher training): {len(X_private)} samples\")\n", "print(f\" Public (student queries): {len(X_public)} samples\")\n", "print(f\" Holdout (evaluation): {len(X_holdout)} samples\")\n", "\n", "scaler = StandardScaler()\n", "X_private_norm = scaler.fit_transform(X_private)\n", "X_public_norm = scaler.transform(X_public)\n", "X_holdout_norm = scaler.transform(X_holdout)\n", "\n", "print(\"\\n\" + \"=\" * 60)\n", "print(\"PHASE 2: Training Teacher Ensemble\")\n", "print(\"=\" * 60)\n", "\n", "num_teachers = TEACHER_CONFIG['num_teachers']\n", "\n", "# Partition private data for teachers\n", "np.random.seed(RANDOM_SEED)\n", "indices = np.random.permutation(len(X_private_norm))\n", "partition_size = len(X_private_norm) // num_teachers\n", "\n", "teacher_partitions = []\n", "for i in range(num_teachers):\n", " start_idx = i * partition_size\n", " if i == num_teachers - 1:\n", " partition_indices = indices[start_idx:]\n", " else:\n", " partition_indices = indices[start_idx:start_idx + partition_size]\n", " teacher_partitions.append(partition_indices)\n", "\n", "print(f\"Created {num_teachers} partitions, ~{partition_size} samples each\")" ] }, { "cell_type": "markdown", "id": "31409150-eb4f-43f8-bf44-c4c255650f2c", "metadata": {}, "source": [ "## Train Teachers" ] }, { "cell_type": "code", "execution_count": 3, "id": "24777e3c-464e-44d7-9e91-f9490936d9a1", "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "Training teachers: 0%| | 0/250 [00:00= high_threshold\n", "medium_consensus = (max_votes >= medium_threshold) & ~high_consensus\n", "low_consensus = max_votes < medium_threshold\n", "\n", "print(f\"\\nConsensus Distribution ({num_queries} samples):\")\n", "print(f\" High (≥80%): {high_consensus.sum()} ({100*high_consensus.mean():.1f}%)\")\n", "print(f\" Medium (60-80%): {medium_consensus.sum()} ({100*medium_consensus.mean():.1f}%)\")\n", "print(f\" Low (<60%): {low_consensus.sum()} ({100*low_consensus.mean():.1f}%)\")" ] }, { "cell_type": "code", "execution_count": 11, "id": "fc19d8d0-4077-4d11-b49d-7178e07b167b", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "Clean Ensemble Accuracy by Consensus:\n", " High consensus: 97.40%\n", " Medium consensus: 81.69%\n", " Low consensus: 50.41%\n", " Overall: 87.82%\n" ] } ], "source": [ "# Calculate clean ensemble accuracy (no noise)\n", "clean_labels = votes.argmax(axis=1)\n", "clean_ensemble_acc = (clean_labels == y_query_true).mean()\n", "\n", "# Calculate accuracy by consensus level\n", "def accuracy_for_mask(mask, labels, true_labels):\n", " if mask.sum() == 0:\n", " return 0.0\n", " return (labels[mask] == true_labels[mask]).mean()\n", "\n", "clean_high = accuracy_for_mask(high_consensus, clean_labels, y_query_true)\n", "clean_medium = accuracy_for_mask(medium_consensus, clean_labels, y_query_true)\n", "clean_low = accuracy_for_mask(low_consensus, clean_labels, y_query_true)\n", "\n", "print(f\"\\nClean Ensemble Accuracy by Consensus:\")\n", "print(f\" High consensus: {clean_high * 100:.2f}%\")\n", "print(f\" Medium consensus: {clean_medium * 100:.2f}%\")\n", "print(f\" Low consensus: {clean_low * 100:.2f}%\")\n", "print(f\" Overall: {clean_ensemble_acc * 100:.2f}%\")" ] }, { "cell_type": "code", "execution_count": 12, "id": "4f04d1d8-ab02-4c27-9929-7dc781632dc9", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "Noisy Aggregation Results (noise_scale=20.0):\n", " Labels matching true: 87.20%\n", " Labels matching clean: 96.10%\n", " Noise flip rate: 3.90%\n", "\n", "Noise Flip Rate by Consensus:\n", " High consensus: 0.00%\n", " Medium consensus: 0.95%\n", " Low consensus: 25.34%\n" ] } ], "source": [ "# Apply noisy aggregation\n", "noise_scale = AGGREGATION_CONFIG['noise_scale']\n", "noisy_labels = noisy_argmax(votes, noise_scale)\n", "\n", "# Compare to clean predictions\n", "noise_flipped = (noisy_labels != clean_labels)\n", "flip_rate = noise_flipped.mean()\n", "\n", "print(f\"\\nNoisy Aggregation Results (noise_scale={noise_scale}):\")\n", "print(f\" Labels matching true: {(noisy_labels == y_query_true).mean() * 100:.2f}%\")\n", "print(f\" Labels matching clean: {(~noise_flipped).mean() * 100:.2f}%\")\n", "print(f\" Noise flip rate: {flip_rate * 100:.2f}%\")\n", "\n", "# Flip rate by consensus level\n", "flip_high = noise_flipped[high_consensus].mean()\n", "flip_medium = noise_flipped[medium_consensus].mean()\n", "flip_low = noise_flipped[low_consensus].mean()\n", "\n", "print(f\"\\nNoise Flip Rate by Consensus:\")\n", "print(f\" High consensus: {flip_high * 100:.2f}%\")\n", "print(f\" Medium consensus: {flip_medium * 100:.2f}%\")\n", "print(f\" Low consensus: {flip_low * 100:.2f}%\")" ] }, { "cell_type": "code", "execution_count": 13, "id": "784aba4d-068f-44b2-9715-e00141ee2f2c", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "============================================================\n", "PHASE 6: Confident Aggregation Analysis\n", "============================================================\n", "\n", "Confident Aggregation Analysis:\n", "Threshold Accepted Accuracy ε Saved \n", "------------------------------------------------\n", "150 85.2 % 94.23 % 14.8 %\n", "175 77.7 % 95.78 % 22.3 %\n", "200 68.4 % 97.40 % 31.6 %\n", "215 60.9 % 98.42 % 39.1 %\n", "225 54.7 % 98.76 % 45.3 %\n" ] } ], "source": [ "def confident_aggregation(votes, noise_scale, threshold):\n", " \"\"\"Label only samples where teacher consensus exceeds threshold.\"\"\"\n", " max_votes = votes.max(axis=1)\n", " confident_mask = max_votes >= threshold\n", " labels = np.full(len(votes), -1, dtype=np.int64)\n", "\n", " if confident_mask.sum() > 0:\n", " confident_votes = votes[confident_mask]\n", " noise = np.random.laplace(loc=0.0, scale=noise_scale, size=confident_votes.shape)\n", " labels[confident_mask] = np.argmax(confident_votes.astype(np.float64) + noise, axis=1)\n", "\n", " return labels, confident_mask\n", "\n", "print(\"\\n\" + \"=\" * 60)\n", "print(\"PHASE 6: Confident Aggregation Analysis\")\n", "print(\"=\" * 60)\n", "\n", "thresholds = [150, 175, 200, 215, 225]\n", "\n", "print(f\"\\nConfident Aggregation Analysis:\")\n", "print(f\"{'Threshold':<12} {'Accepted':<12} {'Accuracy':<12} {'ε Saved':<12}\")\n", "print(\"-\" * 48)\n", "\n", "for threshold in thresholds:\n", " labels, mask = confident_aggregation(votes, noise_scale, threshold)\n", " accepted_rate = mask.mean()\n", "\n", " if mask.sum() > 0:\n", " accuracy = (labels[mask] == y_query_true[mask]).mean() * 100\n", " else:\n", " accuracy = 0.0\n", "\n", " epsilon_saved = (1 - accepted_rate) * 100\n", "\n", " print(f\"{threshold:<12} {100*accepted_rate:<12.1f}% {accuracy:<12.2f}% {epsilon_saved:<12.1f}%\")" ] }, { "cell_type": "code", "execution_count": 14, "id": "1e51eeaa-616a-4aa7-9254-b8a190e0a736", "metadata": {}, "outputs": [], "source": [ "def compute_confident_privacy_budget(num_queries, noise_scale, acceptance_rate, delta=1e-5):\n", " \"\"\"Compute privacy budget considering only accepted (labeled) queries.\"\"\"\n", " per_query_eps = 2.0 / noise_scale\n", " effective_queries = int(num_queries * acceptance_rate)\n", " epsilon_sq_sum = effective_queries * (per_query_eps ** 2)\n", " total_epsilon = np.sqrt(2 * epsilon_sq_sum * np.log(1 / delta))\n", " total_epsilon += effective_queries * per_query_eps * (np.exp(per_query_eps) - 1)\n", " return total_epsilon, per_query_eps, effective_queries" ] }, { "cell_type": "code", "execution_count": 15, "id": "96f4e6e1-bc98-4553-9a3c-abc294534664", "metadata": {}, "outputs": [], "source": [ "def plot_consensus_distribution(votes, save_path=None):\n", " \"\"\"Plot histogram of maximum vote counts with threshold markers.\"\"\"\n", " fig, ax = plt.subplots(figsize=PLOT_CONFIG['figsize'])\n", " max_votes = votes.max(axis=1)\n", " ax.hist(max_votes, bins=range(100, 251, 10), color=AZURE, edgecolor=HACKER_GREY, alpha=0.8)\n", " ax.axvline(200, color=HTB_GREEN, linestyle='--', linewidth=2, label='High (≥200)')\n", " ax.axvline(150, color=AQUAMARINE, linestyle='--', linewidth=2, label='Medium (≥150)')\n", " ax.set_xlabel('Maximum Votes (out of 250 teachers)')\n", " ax.set_ylabel('Number of Samples')\n", " ax.set_title('Teacher Consensus Distribution', color=HTB_GREEN, fontweight='bold')\n", " ax.legend(framealpha=0.8)\n", " ax.grid(True, alpha=0.3)\n", " if save_path:\n", " plt.savefig(save_path, dpi=PLOT_CONFIG['dpi'], bbox_inches='tight', facecolor=NODE_BLACK)\n", " plt.close()" ] }, { "cell_type": "code", "execution_count": 16, "id": "1c2b5114-e633-4454-82f8-afb31a0c4a36", "metadata": {}, "outputs": [], "source": [ "os.makedirs(\"figs\", exist_ok=True)\n", "plot_consensus_distribution(votes, save_path=\"figs/pate_consensus_distribution.png\")" ] }, { "cell_type": "code", "execution_count": 17, "id": "44de927e-0ef4-4741-98e2-31eea36a4b79", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "Student Accuracy by Original Consensus:\n", " High consensus samples: 97.40%\n", " Medium consensus samples: 81.33%\n", " Low consensus samples: 48.37%\n" ] } ], "source": [ "# Evaluate student on samples grouped by original consensus\n", "student_preds = get_model_predictions(student_model, X_query, DEVICE).argmax(axis=1)\n", "\n", "student_high = (student_preds[high_consensus] == y_query_true[high_consensus]).mean() * 100\n", "student_medium = (student_preds[medium_consensus] == y_query_true[medium_consensus]).mean() * 100\n", "student_low = (student_preds[low_consensus] == y_query_true[low_consensus]).mean() * 100\n", "\n", "print(f\"\\nStudent Accuracy by Original Consensus:\")\n", "print(f\" High consensus samples: {student_high:.2f}%\")\n", "print(f\" Medium consensus samples: {student_medium:.2f}%\")\n", "print(f\" Low consensus samples: {student_low:.2f}%\")" ] }, { "cell_type": "markdown", "id": "4db1166a-ee39-4aaf-864b-de91af245a99", "metadata": {}, "source": [ "# Tuning PATE and Comparing Approaches" ] }, { "cell_type": "code", "execution_count": 18, "id": "8e73edab-f512-415d-aca8-3d85c5d3e0da", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "============================================================\n", "PHASE 7: Privacy-Utility Tradeoff Analysis\n", "============================================================\n", "\n", "Noise Scale Per-ε Total ε Label Acc Flip Rate \n", "----------------------------------------------------------\n", "5 0.4000 1119.37 0.8782 0.0108 \n", "10 0.2000 289.26 0.8760 0.0174 \n", "20 0.1000 86.52 0.8702 0.0394 \n", "40 0.0500 29.78 0.8196 0.1120 \n", "80 0.0250 11.65 0.5942 0.3762 \n" ] } ], "source": [ "print(\"\\n\" + \"=\" * 60)\n", "print(\"PHASE 7: Privacy-Utility Tradeoff Analysis\")\n", "print(\"=\" * 60)\n", "\n", "noise_scales = [5, 10, 20, 40, 80]\n", "\n", "print(f\"\\n{'Noise Scale':<12} {'Per-ε':<10} {'Total ε':<12} {'Label Acc':<12} {'Flip Rate':<12}\")\n", "print(\"-\" * 58)\n", "\n", "for ns in noise_scales:\n", " noisy_labels = noisy_argmax(votes, ns)\n", "\n", " per_eps = 2.0 / ns\n", " total_eps, _ = compute_privacy_budget(num_queries, ns)\n", "\n", " label_acc = (noisy_labels == y_query_true).mean()\n", " flip_rate = (noisy_labels != clean_labels).mean()\n", "\n", " print(f\"{ns:<12} {per_eps:<10.4f} {total_eps:<12.2f} {label_acc:<12.4f} {flip_rate:<12.4f}\")" ] }, { "cell_type": "code", "execution_count": 19, "id": "85c508b9-7efc-4bc2-b030-b565c4ed1a9e", "metadata": {}, "outputs": [], "source": [ "def plot_privacy_utility_tradeoff(noise_scales, votes, y_true, num_queries, save_path=None):\n", " \"\"\"Plot privacy vs utility across noise scales on dual axes.\"\"\"\n", " accuracies = [(noisy_argmax(votes, ns) == y_true).mean() for ns in noise_scales]\n", " epsilons = [compute_privacy_budget(num_queries, ns)[0] for ns in noise_scales]\n", "\n", " fig, ax1 = plt.subplots(figsize=PLOT_CONFIG['figsize'])\n", " ax1.set_xlabel('Noise Scale')\n", " ax1.set_ylabel('Label Accuracy', color=HTB_GREEN)\n", " ax1.plot(noise_scales, accuracies, 'o-', color=HTB_GREEN, linewidth=2, markersize=10)\n", " ax1.tick_params(axis='y', labelcolor=HTB_GREEN)\n", "\n", " ax2 = ax1.twinx()\n", " ax2.set_ylabel('Privacy Budget (ε)', color=AZURE)\n", " ax2.plot(noise_scales, epsilons, 's--', color=AZURE, linewidth=2, markersize=10)\n", " ax2.tick_params(axis='y', labelcolor=AZURE)\n", " ax2.set_yscale('log')\n", "\n", " ax1.set_title('Privacy-Utility Tradeoff', color=HTB_GREEN, fontweight='bold')\n", " ax1.grid(True, alpha=0.3)\n", " if save_path:\n", " plt.savefig(save_path, dpi=PLOT_CONFIG['dpi'], bbox_inches='tight', facecolor=NODE_BLACK)\n", " plt.close()\n", "\n", "os.makedirs(\"figs\", exist_ok=True)\n", "plot_privacy_utility_tradeoff(noise_scales, votes, y_query_true, num_queries,\n", " save_path=\"figs/pate_privacy_utility.png\")" ] }, { "cell_type": "code", "execution_count": 20, "id": "f58bf3c4-7a20-48ed-b517-8d4385951619", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "For ε=10.0:\n", " Standard aggregation: 241 queries\n", " Confident (threshold=200): 352 queries\n", " Net usable labels: 241 samples\n" ] } ], "source": [ "def estimate_queries_for_budget(target_epsilon, noise_scale, delta=1e-5):\n", " \"\"\"Binary search for maximum queries within privacy budget.\"\"\"\n", " per_query_eps = 2.0 / noise_scale\n", " low, high = 1, 100000\n", " while low < high:\n", " mid = (low + high + 1) // 2\n", " eps_sq_sum = mid * (per_query_eps ** 2)\n", " total_eps = np.sqrt(2 * eps_sq_sum * np.log(1 / delta))\n", " total_eps += mid * per_query_eps * (np.exp(per_query_eps) - 1)\n", " low, high = (mid, high) if total_eps <= target_epsilon else (low, mid - 1)\n", " return low\n", "\n", "_, confident_mask = confident_aggregation(votes, noise_scale, threshold=200)\n", "acceptance_rate = confident_mask.mean()\n", "\n", "target_epsilon = 10.0\n", "standard_queries = estimate_queries_for_budget(target_epsilon, noise_scale)\n", "confident_queries = standard_queries / acceptance_rate\n", "\n", "print(f\"For ε={target_epsilon}:\")\n", "print(f\" Standard aggregation: {standard_queries} queries\")\n", "print(f\" Confident (threshold=200): {int(confident_queries)} queries\")\n", "print(f\" Net usable labels: {int(confident_queries * acceptance_rate)} samples\")" ] }, { "cell_type": "code", "execution_count": 21, "id": "ed157041-7a40-4d23-99c3-fe719877120d", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "For 5000 labels:\n", " Queries needed: 7307\n", " Privacy cost: ε = 86.50\n" ] } ], "source": [ "target_labels = 5000\n", "confident_queries = int(target_labels / acceptance_rate)\n", "\n", "conf_eps = compute_confident_privacy_budget(\n", " confident_queries, noise_scale, acceptance_rate\n", ")[0]\n", "\n", "print(f\"For {target_labels} labels:\")\n", "print(f\" Queries needed: {confident_queries}\")\n", "print(f\" Privacy cost: ε = {conf_eps:.2f}\")\n", "\n", "def iterative_confident_labeling(teachers, X_public, threshold, target_count, noise_scale, device):\n", " \"\"\"Query samples until target_count high-consensus labels obtained.\"\"\"\n", " confident_X, confident_y, queries_made = [], [], 0\n", " available_indices = np.random.permutation(len(X_public))\n", "\n", " for idx in available_indices:\n", " if len(confident_y) >= target_count:\n", " break\n", " x = X_public[idx:idx+1]\n", " labels, mask = confident_aggregation(get_teacher_votes(teachers, x, device), noise_scale, threshold)\n", " queries_made += 1\n", " if mask[0]:\n", " confident_X.append(x[0])\n", " confident_y.append(labels[0])\n", "\n", " return np.array(confident_X), np.array(confident_y), queries_made" ] }, { "cell_type": "code", "execution_count": 22, "id": "619cfa6d-e450-4710-8292-d1d75c03e3d8", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "Information Bottleneck:\n", " Private data: 150.5 MB\n", " Student labels: 20.0 KB\n", " Compression ratio: 7526:1\n", "Results saved to figs/pate_results.json\n" ] } ], "source": [ "# Information bottleneck metrics\n", "private_data_bytes = len(X_private_norm) * X_private_norm.shape[1] * 4 # float32\n", "student_info_bytes = num_queries * 4 # 4 bits per label, ~0.5 bytes, round to int size\n", "compression_ratio = private_data_bytes / student_info_bytes\n", "\n", "print(f\"\\nInformation Bottleneck:\")\n", "print(f\" Private data: {private_data_bytes / 1e6:.1f} MB\")\n", "print(f\" Student labels: {student_info_bytes / 1e3:.1f} KB\")\n", "print(f\" Compression ratio: {compression_ratio:.0f}:1\")\n", "\n", "results = {\n", " 'configuration': {'dataset': 'mnist', 'num_teachers': TEACHER_CONFIG['num_teachers'],\n", " 'noise_scale': AGGREGATION_CONFIG['noise_scale'], 'num_queries': num_queries},\n", " 'privacy': {'per_query_epsilon': float(per_query_eps), 'total_epsilon': float(total_eps)},\n", " 'utility': {'student_accuracy': float(student_test_acc), 'label_accuracy': float(label_accuracy),\n", " 'clean_ensemble_accuracy': float(clean_ensemble_acc)},\n", " 'information': {'private_data_bytes': int(private_data_bytes), 'student_info_bytes': int(student_info_bytes),\n", " 'compression_ratio': float(compression_ratio)}\n", "}\n", "\n", "results_path = os.path.join(\"figs\", \"pate_results.json\")\n", "with open(results_path, 'w') as f:\n", " json.dump(results, f, indent=2)\n", "print(f\"Results saved to {results_path}\")" ] }, { "cell_type": "code", "execution_count": null, "id": "65ef7c61-12b2-4fcb-95f4-b98aae1a6372", "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.6" } }, "nbformat": 4, "nbformat_minor": 5 }