{ "cells": [ { "cell_type": "markdown", "id": "c58a5075", "metadata": {}, "source": [ "# 🔍 Deepfake Detection — Interview Mode (v4 — DFD Video Dataset)\n", "**Binary Classifier — Real vs AI-Generated Face** \n", "Dataset: `sanikatiwarekar/deep-fake-detection-dfd-entire-original-dataset` (Kaggle) \n", "GPU: RTX 4050 with AMP Mixed Precision \n", "\n", "---\n", "### Pipeline (run in order)\n", "| Step | Cell | Action |\n", "|---|---|---|\n", "| 1 | Cell 0 | Download DFD dataset via `kagglehub` |\n", "| 2 | Cell 0b | Extract faces from videos → save as .jpg **(run once)** |\n", "| 3 | Cells 1–11 | Train on extracted faces with GPU + AMP |\n", "\n", "> Cell 0b extracts faces once and saves to disk. Training then reads fast .jpg files every epoch." ] }, { "cell_type": "code", "execution_count": 2, "id": "cell_0_kaggle", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Installing / verifying dependencies...\n", "[OK] All dependencies installed.\n", "\n", "Downloading DFD dataset (cached if already downloaded)...\n", "Dataset cached at: C:\\Users\\SHINJAN\\.cache\\kagglehub\\datasets\\sanikatiwarekar\\deep-fake-detection-dfd-entire-original-dataset\\versions\\1\n", "Real videos : C:\\Users\\SHINJAN\\.cache\\kagglehub\\datasets\\sanikatiwarekar\\deep-fake-detection-dfd-entire-original-dataset\\versions\\1\\DFD_original sequences\n", "Fake videos : C:\\Users\\SHINJAN\\.cache\\kagglehub\\datasets\\sanikatiwarekar\\deep-fake-detection-dfd-entire-original-dataset\\versions\\1\\DFD_manipulated_sequences\\DFD_manipulated_sequences\n", "[OK] Video directories found.\n" ] } ], "source": [ "# ─────────────────────────────────────────────────────────\n", "# CELL 0 — Install Dependencies & Download DFD Dataset\n", "# ─────────────────────────────────────────────────────────\n", "\n", "import subprocess, sys\n", "\n", "PACKAGES = [\n", " 'kagglehub>=0.2.0',\n", " 'opencv-python>=4.9.0',\n", " 'timm>=0.9.0',\n", " 'facenet-pytorch>=2.6.0',\n", " 'torch>=2.2.0',\n", " 'torchvision>=0.17.0',\n", " 'Pillow>=10.2.0',\n", " 'numpy>=1.26.0',\n", " 'matplotlib>=3.8.0',\n", " 'seaborn>=0.13.0',\n", " 'tqdm>=4.66.0',\n", " 'datasets>=2.19.0',\n", "]\n", "\n", "print('Installing / verifying dependencies...')\n", "subprocess.check_call([\n", " sys.executable, '-m', 'pip', 'install', '--quiet', '--upgrade'\n", "] + PACKAGES)\n", "print('[OK] All dependencies installed.\\n')\n", "\n", "# ── Download DFD Dataset ──────────────────────────────────\n", "import os\n", "\n", "try:\n", " import kagglehub\n", "except ImportError:\n", " subprocess.check_call([sys.executable, '-m', 'pip', 'install', 'kagglehub', '-q'])\n", " import kagglehub\n", "\n", "print('Downloading DFD dataset (cached if already downloaded)...')\n", "KAGGLE_DATASET_PATH = kagglehub.dataset_download(\n", " 'sanikatiwarekar/deep-fake-detection-dfd-entire-original-dataset'\n", ")\n", "print(f'Dataset cached at: {KAGGLE_DATASET_PATH}')\n", "\n", "def find_video_dirs(base):\n", " real_dir = fake_dir = None\n", " for root, dirs, files in os.walk(base):\n", " name = os.path.basename(root).lower().replace(' ', '_')\n", " has_vids = any(f.lower().endswith(('.mp4','.avi','.mov','.mkv')) for f in files)\n", " if not has_vids:\n", " continue\n", " if 'original' in name and real_dir is None:\n", " real_dir = root\n", " if 'manipulat' in name:\n", " fake_dir = root\n", " return real_dir, fake_dir\n", "\n", "REAL_VIDEO_DIR, FAKE_VIDEO_DIR = find_video_dirs(KAGGLE_DATASET_PATH)\n", "print(f'Real videos : {REAL_VIDEO_DIR}')\n", "print(f'Fake videos : {FAKE_VIDEO_DIR}')\n", "\n", "if not REAL_VIDEO_DIR or not FAKE_VIDEO_DIR:\n", " print('\\n[WARN] Could not auto-detect dirs. Contents:')\n", " for item in os.listdir(KAGGLE_DATASET_PATH):\n", " print(f' {item}')\n", " print('\\nSet REAL_VIDEO_DIR and FAKE_VIDEO_DIR manually.')\n", "else:\n", " print('[OK] Video directories found.')\n" ] }, { "cell_type": "code", "execution_count": 3, "id": "cell_0b_extract", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[OK] cv2 4.13.0 | torch 2.2.2+cu121 | GPU: True\n", "Extraction device: cuda\n", "[SKIP] All 6 splits complete: 77,864 files. Set SKIP_IF_EXISTS=False to re-run.\n" ] } ], "source": [ "# ─────────────────────────────────────────────────────────\n", "# CELL 0b — Extract Faces from Videos (run ONCE, resumable)\n", "# ⚡ GPU-accelerated with MTCNN on CUDA\n", "# ✅ Safe to interrupt & resume — already-extracted videos are skipped\n", "# ─────────────────────────────────────────────────────────\n", "\n", "# ── Auto-install missing packages in THIS kernel ──\n", "import subprocess, sys\n", "def _install(pkg):\n", " subprocess.check_call([sys.executable, '-m', 'pip', 'install', '--quiet', pkg])\n", "\n", "try:\n", " import cv2\n", "except ImportError:\n", " print('Installing opencv-python...'); _install('opencv-python'); import cv2\n", "\n", "try:\n", " from facenet_pytorch import MTCNN\n", "except ImportError:\n", " print('Installing facenet-pytorch...'); _install('facenet-pytorch'); from facenet_pytorch import MTCNN\n", "\n", "try:\n", " import torch\n", "except ImportError:\n", " print('Installing torch...'); _install('torch'); import torch\n", "\n", "print(f'[OK] cv2 {cv2.__version__} | torch {torch.__version__} | GPU: {torch.cuda.is_available()}')\n", "\n", "# ─────────────────────────────────────────────────────────\n", "import os, glob, random, json as _json\n", "from PIL import Image\n", "from tqdm import tqdm\n", "\n", "# ── Settings ──\n", "EXTRACT_ROOT = r'C:\\Users\\SHINJAN\\Downloads\\deepfake_main\\extracted_faces'\n", "FRAMES_PER_VIDEO = 20\n", "FACE_SIZE = 299\n", "TRAIN_RATIO = 0.80\n", "VALID_RATIO = 0.10\n", "SKIP_IF_EXISTS = True # set False to force full re-extraction\n", "\n", "# Progress log — tracks which videos have been fully processed\n", "PROGRESS_FILE = os.path.join(EXTRACT_ROOT, '.extraction_progress.json')\n", "\n", "device_ext = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n", "print(f'Extraction device: {device_ext}')\n", "\n", "# ── Check ALL 6 expected directories (train/valid/test × fake/real) ──\n", "_expected_dirs = [\n", " os.path.join(EXTRACT_ROOT, split, label)\n", " for split in ('train', 'valid', 'test')\n", " for label in ('fake', 'real')\n", "]\n", "_all_complete = all(\n", " os.path.isdir(d) and len(os.listdir(d)) > 0\n", " for d in _expected_dirs\n", ")\n", "\n", "if SKIP_IF_EXISTS and _all_complete:\n", " n = sum(len(files) for _, _, files in os.walk(EXTRACT_ROOT))\n", " print(f'[SKIP] All 6 splits complete: {n:,} files. Set SKIP_IF_EXISTS=False to re-run.')\n", "else:\n", " if SKIP_IF_EXISTS and not _all_complete:\n", " _missing = [d for d in _expected_dirs\n", " if not (os.path.isdir(d) and len(os.listdir(d)) > 0)]\n", " print('[WARN] Incomplete extraction — missing/empty dirs:')\n", " for d in _missing:\n", " print(f' {d}')\n", " print('Resuming extraction from where it left off...\\n')\n", "\n", " # ── Load progress log (for resumability) ──────────────────\n", " _done_videos = set()\n", " if os.path.exists(PROGRESS_FILE):\n", " try:\n", " with open(PROGRESS_FILE, 'r') as _f:\n", " _done_videos = set(_json.load(_f))\n", " print(f'[RESUME] Found progress log: {len(_done_videos):,} videos already processed')\n", " except Exception:\n", " _done_videos = set()\n", "\n", " # ── MTCNN for face detection (GPU-accelerated) ──\n", " mtcnn_ext = MTCNN(\n", " image_size=FACE_SIZE, keep_all=False, min_face_size=40,\n", " device=device_ext, post_process=False, margin=20,\n", " )\n", "\n", " def split_name(idx, n):\n", " r = idx / max(n - 1, 1)\n", " if r < TRAIN_RATIO: return 'train'\n", " if r < TRAIN_RATIO + VALID_RATIO: return 'valid'\n", " return 'test'\n", "\n", " VIDEO_EXTS = ('*.mp4', '*.avi', '*.mov', '*.mkv')\n", "\n", " def save_progress(done_set):\n", " os.makedirs(EXTRACT_ROOT, exist_ok=True)\n", " with open(PROGRESS_FILE, 'w') as _f:\n", " _json.dump(list(done_set), _f)\n", "\n", " def extract_from_dir(video_dir, label, done_videos):\n", " videos = []\n", " for ext in VIDEO_EXTS:\n", " videos.extend(glob.glob(os.path.join(video_dir, '**', ext), recursive=True))\n", " random.shuffle(videos)\n", "\n", " # ── Resume: skip already-done videos ──────────────────\n", " pending = [v for v in videos if v not in done_videos]\n", " skipped = len(videos) - len(pending)\n", " print(f'\\n[{label.upper()}] {len(videos)} videos total | '\n", " f'{skipped} already done | {len(pending)} to process')\n", "\n", " total_saved = 0\n", " for vid_idx, vpath in enumerate(tqdm(pending, desc=label)):\n", " # Assign split based on global index (for consistent train/valid/test split)\n", " global_idx = videos.index(vpath)\n", " split = split_name(global_idx, len(videos))\n", " out_dir = os.path.join(EXTRACT_ROOT, split, label)\n", " os.makedirs(out_dir, exist_ok=True)\n", "\n", " cap = cv2.VideoCapture(vpath)\n", " n_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))\n", " if n_frames < 1:\n", " cap.release()\n", " done_videos.add(vpath)\n", " continue\n", "\n", " # Use a seeded sample so same video always → same frames on resume\n", " _rng = random.Random(hash(os.path.basename(vpath)))\n", " sample_idx = set(sorted(_rng.sample(range(n_frames), min(FRAMES_PER_VIDEO, n_frames))))\n", " vid_stem = os.path.splitext(os.path.basename(vpath))[0]\n", " frame_no = 0\n", "\n", " while cap.isOpened():\n", " ret, frame = cap.read()\n", " if not ret: break\n", " if frame_no in sample_idx:\n", " pil = Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))\n", " try:\n", " face_t = mtcnn_ext(pil)\n", " except Exception:\n", " face_t = None\n", " if face_t is not None:\n", " face_pil = Image.fromarray(face_t.permute(1,2,0).byte().cpu().numpy())\n", " face_pil.save(os.path.join(out_dir, f'{vid_stem}_f{frame_no:06d}.jpg'), quality=95)\n", " total_saved += 1\n", " frame_no += 1\n", " cap.release()\n", "\n", " done_videos.add(vpath)\n", "\n", " # Save progress every 10 videos so interruption loses minimal work\n", " if len(done_videos) % 10 == 0:\n", " save_progress(done_videos)\n", "\n", " save_progress(done_videos) # final save for this label\n", " return total_saved\n", "\n", " print(f'\\n=== Face Extraction | Output: {EXTRACT_ROOT} ===')\n", " n_real = extract_from_dir(REAL_VIDEO_DIR, 'real', _done_videos)\n", " n_fake = extract_from_dir(FAKE_VIDEO_DIR, 'fake', _done_videos)\n", "\n", " # Clean up progress file only when fully done\n", " if os.path.exists(PROGRESS_FILE):\n", " os.remove(PROGRESS_FILE)\n", " print('[INFO] Progress log removed (extraction complete)')\n", "\n", " print(f'\\n=== Done | real={n_real:,} fake={n_fake:,} total={n_real+n_fake:,} ===')\n", " print('Now run Cells 1-11 to train.')\n" ] }, { "cell_type": "code", "execution_count": 4, "id": "cf9ca151", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[OK] PyTorch : 2.2.2+cu121\n", "[OK] Device : cuda\n", " GPU Name : NVIDIA GeForce RTX 4050 Laptop GPU\n", " VRAM : 6.4 GB\n", " AMP : enabled (saves ~2.6 GB VRAM)\n" ] } ], "source": [ "# ─────────────────────────────────────────────────────────\n", "# CELL 1 — Imports & GPU Setup\n", "# ─────────────────────────────────────────────────────────\n", "import os\n", "import random\n", "import numpy as np\n", "import torch\n", "import torch.nn as nn\n", "from torch import optim\n", "from torch.cuda.amp import GradScaler, autocast # AMP mixed precision\n", "from torch.optim.lr_scheduler import OneCycleLR\n", "from torch.utils.data import DataLoader\n", "from torchvision import datasets, transforms\n", "from facenet_pytorch import InceptionResnetV1, fixed_image_standardization, MTCNN\n", "from PIL import Image\n", "import matplotlib.pyplot as plt\n", "import matplotlib.ticker as ticker\n", "import seaborn as sns\n", "from tqdm import tqdm\n", "\n", "# ── Reproducibility ──\n", "SEED = 42\n", "random.seed(SEED)\n", "np.random.seed(SEED)\n", "torch.manual_seed(SEED)\n", "torch.backends.cudnn.deterministic = False # False = faster with AMP\n", "torch.backends.cudnn.benchmark = True # True = faster fixed-size inputs\n", "\n", "# ── Device ──\n", "device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n", "\n", "print(f'[OK] PyTorch : {torch.__version__}')\n", "print(f'[OK] Device : {device}')\n", "if device.type == 'cuda':\n", " print(f' GPU Name : {torch.cuda.get_device_name(0)}')\n", " vram_gb = torch.cuda.get_device_properties(0).total_memory / 1e9\n", " print(f' VRAM : {vram_gb:.1f} GB')\n", " print(f' AMP : enabled (saves ~{vram_gb*0.4:.1f} GB VRAM)')\n", " AMP_ENABLED = True\n", "else:\n", " print('[WARN] No GPU — training on CPU (AMP disabled)')\n", " AMP_ENABLED = False" ] }, { "cell_type": "code", "execution_count": 5, "id": "ad61e92d", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[CONFIG] CONFIG loaded:\n", " train_dir = C:\\\\Users\\\\SHINJAN\\\\Downloads\\\\deepfake_main\\\\extracted_faces\\\\train\n", " valid_dir = C:\\\\Users\\\\SHINJAN\\\\Downloads\\\\deepfake_main\\\\extracted_faces\\\\valid\n", " test_dir = C:\\\\Users\\\\SHINJAN\\\\Downloads\\\\deepfake_main\\\\extracted_faces\\\\test\n", " extra_fake_dirs = []\n", " backbone = inceptionresnet\n", " epochs = 30\n", " batch_size = 32\n", " accum_steps = 4\n", " learning_rate = 0.0003\n", " weight_decay = 0.0001\n", " grad_clip_norm = 1.0\n", " label_smoothing = 0.1\n", " use_class_weights = True\n", " num_workers = 0\n", " persistent_workers = False\n", " prefetch_factor = 2\n", " pin_memory = True\n", " unfreeze_blocks = ['block8', 'block7', 'avgpool_1a', 'last_linear', 'last_bn', 'logits']\n", " checkpoint_path = checkpoint.pt\n", " best_model_path = models/best_model.pt\n", " checkpoint_interval = 100\n", " model_save_path = deepfake_model_final.pt\n", " image_size = 299\n", " num_classes = 2\n", "\n", "[INFO] Effective batch size = 128 (batch 32 × accum 4)\n" ] } ], "source": [ "# ─────────────────────────────────────────────────────────\n", "# CELL 2 — Configuration\n", "# ─────────────────────────────────────────────────────────\n", "CONFIG = {\n", " # ── Dataset paths ──\n", " # DFD video dataset — faces extracted by Cell 0b\n", " 'train_dir' : r'C:\\\\Users\\\\SHINJAN\\\\Downloads\\\\deepfake_main\\\\extracted_faces\\\\train',\n", " 'valid_dir' : r'C:\\\\Users\\\\SHINJAN\\\\Downloads\\\\deepfake_main\\\\extracted_faces\\\\valid',\n", " 'test_dir' : r'C:\\\\Users\\\\SHINJAN\\\\Downloads\\\\deepfake_main\\\\extracted_faces\\\\test',\n", " 'extra_fake_dirs' : [],\n", "\n", " # ── Backbone ──\n", " # 'inceptionresnet' : Face-specific, best for interview use case\n", " # 'efficientnet' : Faster, better on large (25 GB+) datasets\n", " 'backbone' : 'inceptionresnet',\n", "\n", " # ── Training ──\n", " 'epochs' : 30,\n", " 'batch_size' : 32, # physical batch per step\n", " 'accum_steps' : 4, # gradient accumulation → effective batch = 128\n", " 'learning_rate' : 3e-4, # OneCycleLR peak LR (it self-anneals)\n", " 'weight_decay' : 1e-4,\n", " 'grad_clip_norm' : 1.0,\n", " 'label_smoothing' : 0.1,\n", " 'use_class_weights': True,\n", "\n", " # ── DataLoader — CRITICAL for 25 GB datasets ──\n", " # num_workers=4 lets 4 CPU cores pre-load batches while GPU trains\n", " # persistent_workers=True — worker processes stay alive between epochs\n", " # prefetch_factor=2 — pre-loads 2 batches ahead\n", " 'num_workers' : 0, # change to 0 ONLY if you get errors\n", " 'persistent_workers' : False,\n", " 'prefetch_factor' : 2,\n", " 'pin_memory' : True,\n", "\n", " # ── Blocks to unfreeze (InceptionResnetV1) ──\n", " 'unfreeze_blocks' : ['block8', 'block7', 'avgpool_1a', 'last_linear', 'last_bn', 'logits'],\n", "\n", " # ── Checkpoint ──\n", " 'checkpoint_path' : 'checkpoint.pt',\n", " 'best_model_path' : 'models/best_model.pt',\n", " 'checkpoint_interval' : 100,\n", "\n", " # ── Output ──\n", " 'model_save_path' : 'deepfake_model_final.pt',\n", " 'image_size' : 299,\n", " 'num_classes' : 2,\n", "}\n", "\n", "print('[CONFIG] CONFIG loaded:')\n", "for k, v in CONFIG.items():\n", " print(f' {k:<25} = {v}')\n", "\n", "print(f'\\n[INFO] Effective batch size = {CONFIG[\"batch_size\"] * CONFIG[\"accum_steps\"]} '\n", " f'(batch {CONFIG[\"batch_size\"]} × accum {CONFIG[\"accum_steps\"]})')" ] }, { "cell_type": "code", "execution_count": 6, "id": "hf_dataset_loader_2b", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "HF dataset skipped (USE_HF_DATASET = False)\n" ] } ], "source": [ "# ─────────────────────────────────────────────────────────\n", "# CELL 2b — HuggingFace Dataset Loader (optional)\n", "# ─────────────────────────────────────────────────────────\n", "\n", "USE_HF_DATASET = False # set True to add ~17k HuggingFace samples\n", "HF_DATASET_ID = 'riandika/AI-vs-Deepfake-vs-Real-Resized-Aug'\n", "\n", "hf_train_data = None\n", "HF_LABEL_MAP = {}\n", "\n", "if USE_HF_DATASET:\n", " try:\n", " from datasets import load_dataset as hf_load_dataset\n", " except ImportError:\n", " import subprocess\n", " subprocess.check_call([__import__('sys').executable, '-m', 'pip', 'install', 'datasets', '-q'])\n", " from datasets import load_dataset as hf_load_dataset\n", "\n", " print(f'Downloading {HF_DATASET_ID} ...')\n", " hf_ds = hf_load_dataset(HF_DATASET_ID)\n", " hf_train_data = hf_ds['train']\n", " feature = hf_train_data.features['label']\n", " HF_LABEL_MAP = {idx: (1 if name.lower() == 'real' else 0)\n", " for idx, name in enumerate(feature.names)}\n", " print(f'HF labels: {dict(zip(feature.names, HF_LABEL_MAP.values()))}')\n", " print(f'Total HF samples: {len(hf_train_data):,}')\n", "else:\n", " print('HF dataset skipped (USE_HF_DATASET = False)')" ] }, { "cell_type": "code", "execution_count": 7, "id": "232e1af9", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "[DIR] Classes : {'fake': 0, 'real': 1}\n", "[STATS] Train total : 67,913 (fake=56,473 real=11,440)\n", "[STATS] Valid : 4,965\n", "[STATS] Test : 4,767\n", "[WEIGHT] class weights: fake=0.601 real=2.968\n", "[LOOP] Batches/epoch: 2123\n" ] } ], "source": [ "# ─────────────────────────────────────────────────────────\n", "# CELL 3 — Data Loaders\n", "#\n", "# INTERVIEW-SPECIFIC augmentations:\n", "# • RandomPerspective — simulates camera angles in video calls\n", "# • GaussianBlur — simulates video compression artifacts\n", "# • ColorJitter — simulates different lighting conditions\n", "# • RandomJPEG — AI images are suspiciously clean vs real cameras\n", "# ─────────────────────────────────────────────────────────\n", "\n", "import glob\n", "import io\n", "from torch.utils.data import Dataset, ConcatDataset\n", "\n", "\n", "class RandomJPEGCompression:\n", " def __init__(self, quality_range=(40, 90), p=0.4):\n", " self.quality_range = quality_range\n", " self.p = p\n", "\n", " def __call__(self, img):\n", " if random.random() > self.p:\n", " return img\n", " quality = random.randint(*self.quality_range)\n", " buf = io.BytesIO()\n", " img.save(buf, format='JPEG', quality=quality)\n", " buf.seek(0)\n", " return Image.open(buf).convert('RGB')\n", "\n", "\n", "class FlatFakeDataset(Dataset):\n", " EXTS = ('*.jpg', '*.jpeg', '*.png', '*.webp')\n", "\n", " def __init__(self, folder, transform=None):\n", " self.transform = transform\n", " self.paths = []\n", " for ext in self.EXTS:\n", " self.paths.extend(glob.glob(os.path.join(folder, '**', ext), recursive=True))\n", " print(f' 📂 Extra fakes: {folder} → {len(self.paths):,} images')\n", "\n", " def __len__(self): return len(self.paths)\n", "\n", " def __getitem__(self, idx):\n", " img = Image.open(self.paths[idx]).convert('RGB')\n", " if self.transform: img = self.transform(img)\n", " return img, 0\n", "\n", "\n", "class HuggingFaceWrapperDataset(Dataset):\n", " def __init__(self, hf_dataset, label_map, transform=None):\n", " self.data = hf_dataset; self.label_map = label_map; self.transform = transform\n", "\n", " def __len__(self): return len(self.data)\n", "\n", " def __getitem__(self, idx):\n", " row = self.data[idx]\n", " img = row['image'].convert('RGB')\n", " label = self.label_map[row['label']]\n", " if self.transform: img = self.transform(img)\n", " return img, label\n", "\n", "\n", "# ── Interview-tuned augmentation pipeline ──────────────────\n", "train_transform = transforms.Compose([\n", " transforms.Resize((CONFIG['image_size'], CONFIG['image_size'])),\n", " RandomJPEGCompression(quality_range=(30, 85), p=0.5), # video call compression\n", " transforms.RandomHorizontalFlip(),\n", " transforms.RandomRotation(degrees=8),\n", " transforms.RandomPerspective(distortion_scale=0.15, p=0.3), # camera angle\n", " transforms.ColorJitter(brightness=0.25, contrast=0.25, saturation=0.2, hue=0.05),\n", " transforms.GaussianBlur(kernel_size=3, sigma=(0.1, 2.5)), # video artifacts\n", " transforms.RandomGrayscale(p=0.04),\n", " transforms.ToTensor(),\n", " fixed_image_standardization,\n", "])\n", "\n", "eval_transform = transforms.Compose([\n", " transforms.Resize((CONFIG['image_size'], CONFIG['image_size'])),\n", " transforms.ToTensor(),\n", " fixed_image_standardization,\n", "])\n", "\n", "\n", "# ── Datasets ──────────────────────────────────────────────\n", "train_dataset_base = datasets.ImageFolder(CONFIG['train_dir'], transform=train_transform)\n", "valid_dataset = datasets.ImageFolder(CONFIG['valid_dir'], transform=eval_transform)\n", "test_dataset = datasets.ImageFolder(CONFIG['test_dir'], transform=eval_transform)\n", "CLASS_NAMES = {v: k for k, v in train_dataset_base.class_to_idx.items()}\n", "\n", "extra_fake_count = 0\n", "extra_datasets = []\n", "if CONFIG['extra_fake_dirs']:\n", " for folder in CONFIG['extra_fake_dirs']:\n", " ds = FlatFakeDataset(folder, transform=train_transform)\n", " if len(ds) > 0:\n", " extra_datasets.append(ds)\n", " extra_fake_count += len(ds)\n", "\n", "train_dataset = ConcatDataset([train_dataset_base] + extra_datasets) if extra_datasets else train_dataset_base\n", "\n", "hf_sample_count = 0; hf_fake_count = 0; hf_real_count = 0\n", "if USE_HF_DATASET and hf_train_data is not None and HF_LABEL_MAP:\n", " hf_torch_ds = HuggingFaceWrapperDataset(hf_train_data, HF_LABEL_MAP, transform=train_transform)\n", " hf_sample_count = len(hf_torch_ds)\n", " hf_labels = hf_train_data['label']\n", " hf_fake_count = sum(1 for lbl in hf_labels if HF_LABEL_MAP[lbl] == 0)\n", " hf_real_count = hf_sample_count - hf_fake_count\n", " train_dataset = ConcatDataset([train_dataset, hf_torch_ds]) if not isinstance(train_dataset, ConcatDataset) \\\n", " else ConcatDataset(list(train_dataset.datasets) + [hf_torch_ds])\n", " print(f'HF merged: {hf_sample_count:,} (fake={hf_fake_count:,} real={hf_real_count:,})')\n", "\n", "\n", "# ── Class weights ──────────────────────────────────────────\n", "base_fake = sum(1 for _, lbl in train_dataset_base.samples if lbl == 0)\n", "base_real = sum(1 for _, lbl in train_dataset_base.samples if lbl == 1)\n", "total_fake = base_fake + extra_fake_count + hf_fake_count\n", "total_real = base_real + hf_real_count\n", "total_all = total_fake + total_real\n", "class_weights = torch.tensor([\n", " total_all / (2 * total_fake) if total_fake > 0 else 1.0,\n", " total_all / (2 * total_real) if total_real > 0 else 1.0,\n", "], dtype=torch.float).to(device)\n", "\n", "\n", "# ── DataLoaders — optimised for large datasets ─────────────\n", "# num_workers=4: 4 CPU cores pre-load batches while GPU trains\n", "# persistent_workers=True: workers stay alive between epochs (saves respawn cost)\n", "# prefetch_factor=2: pre-load 2 batches ahead per worker\n", "_loader_kwargs = dict(\n", " num_workers = CONFIG['num_workers'],\n", " pin_memory = CONFIG['pin_memory'],\n", " persistent_workers = CONFIG['persistent_workers'] if CONFIG['num_workers'] > 0 else False,\n", " prefetch_factor = CONFIG['prefetch_factor'] if CONFIG['num_workers'] > 0 else None,\n", ")\n", "\n", "train_loader = DataLoader(train_dataset, batch_size=CONFIG['batch_size'],\n", " shuffle=True, **_loader_kwargs)\n", "valid_loader = DataLoader(valid_dataset, batch_size=CONFIG['batch_size'],\n", " shuffle=False, **_loader_kwargs)\n", "test_loader = DataLoader(test_dataset, batch_size=CONFIG['batch_size'],\n", " shuffle=False, **_loader_kwargs)\n", "\n", "print(f'\\n[DIR] Classes : {train_dataset_base.class_to_idx}')\n", "print(f'[STATS] Train total : {len(train_dataset):,} (fake={total_fake:,} real={total_real:,})')\n", "print(f'[STATS] Valid : {len(valid_dataset):,}')\n", "print(f'[STATS] Test : {len(test_dataset):,}')\n", "print(f'[WEIGHT] class weights: fake={class_weights[0]:.3f} real={class_weights[1]:.3f}')\n", "print(f'[LOOP] Batches/epoch: {len(train_loader)}')" ] }, { "cell_type": "code", "execution_count": 8, "id": "98ee40b4", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[MODEL] Backbone : InceptionResnetV1 (VGGFace2)\n", " Total params : 28,075,577\n", " Trainable params : 7,112,249 ← these update\n", " Frozen params : 20,963,328\n" ] } ], "source": [ "# ─────────────────────────────────────────────────────────\n", "# CELL 4 — Model Definition\n", "#\n", "# Two backbone options — set via CONFIG['backbone']:\n", "#\n", "# 'inceptionresnet' → InceptionResnetV1 (VGGFace2 pretrained)\n", "# Best for: interview face detection, face-specific features\n", "# Embedding dim: 512\n", "#\n", "# 'efficientnet' → EfficientNet-B4 (ImageNet pretrained)\n", "# Best for: large datasets (25 GB+), slightly faster inference\n", "# Embedding dim: 1792\n", "# Requires: pip install timm\n", "# ─────────────────────────────────────────────────────────\n", "\n", "def build_inception_model(unfreeze_blocks, device):\n", " \"\"\"InceptionResnetV1 backbone + 3-layer classification head.\"\"\"\n", "\n", " class DeepfakeClassifier(nn.Module):\n", " def __init__(self):\n", " super().__init__()\n", " self.backbone = InceptionResnetV1(\n", " classify=False, pretrained='vggface2'\n", " ).to(device)\n", " # Freeze all first\n", " for param in self.backbone.parameters():\n", " param.requires_grad = False\n", " # Unfreeze last blocks\n", " for name, module in self.backbone.named_modules():\n", " if any(blk in name for blk in unfreeze_blocks):\n", " for param in module.parameters():\n", " param.requires_grad = True\n", " # Head: 512 → 256 → 128 → 2\n", " self.head = nn.Sequential(\n", " nn.Linear(512, 256), nn.BatchNorm1d(256), nn.GELU(), nn.Dropout(0.4),\n", " nn.Linear(256, 128), nn.BatchNorm1d(128), nn.GELU(), nn.Dropout(0.3),\n", " nn.Linear(128, 2)\n", " ).to(device)\n", "\n", " def forward(self, x):\n", " return self.head(self.backbone(x))\n", "\n", " return DeepfakeClassifier()\n", "\n", "\n", "def build_efficientnet_model(device):\n", " \"\"\"EfficientNet-B4 backbone + classification head.\"\"\"\n", " try:\n", " import timm\n", " except ImportError:\n", " import subprocess, sys\n", " print('Installing timm...')\n", " subprocess.check_call([sys.executable, '-m', 'pip', 'install', 'timm', '-q'])\n", " import timm\n", "\n", " class EfficientNetClassifier(nn.Module):\n", " def __init__(self):\n", " super().__init__()\n", " self.backbone = timm.create_model(\n", " 'efficientnet_b4', pretrained=True, num_classes=0 # 0 = return embeddings\n", " ).to(device)\n", " embed_dim = self.backbone.num_features # 1792 for B4\n", " # Freeze early stages, unfreeze last 2 blocks\n", " for name, param in self.backbone.named_parameters():\n", " # Unfreeze blocks 5,6 + head\n", " if any(s in name for s in ['blocks.5', 'blocks.6', 'conv_head', 'bn2', 'classifier']):\n", " param.requires_grad = True\n", " else:\n", " param.requires_grad = False\n", " self.head = nn.Sequential(\n", " nn.Linear(embed_dim, 512), nn.BatchNorm1d(512), nn.GELU(), nn.Dropout(0.4),\n", " nn.Linear(512, 256), nn.BatchNorm1d(256), nn.GELU(), nn.Dropout(0.3),\n", " nn.Linear(256, 2)\n", " ).to(device)\n", "\n", " def forward(self, x):\n", " return self.head(self.backbone(x))\n", "\n", " return EfficientNetClassifier()\n", "\n", "\n", "# ── Build model based on CONFIG ────────────────────────────\n", "if CONFIG['backbone'] == 'efficientnet':\n", " model = build_efficientnet_model(device)\n", " backbone_name = 'EfficientNet-B4'\n", "else:\n", " model = build_inception_model(CONFIG['unfreeze_blocks'], device)\n", " backbone_name = 'InceptionResnetV1 (VGGFace2)'\n", "\n", "trainable = sum(p.numel() for p in model.parameters() if p.requires_grad)\n", "total_ = sum(p.numel() for p in model.parameters())\n", "\n", "print(f'[MODEL] Backbone : {backbone_name}')\n", "print(f' Total params : {total_:,}')\n", "print(f' Trainable params : {trainable:,} ← these update')\n", "print(f' Frozen params : {total_ - trainable:,}')" ] }, { "cell_type": "code", "execution_count": 9, "id": "8f06ecdf", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[OK] Checkpoint utilities ready\n" ] } ], "source": [ "# ─────────────────────────────────────────────────────────\n", "# CELL 5 — Checkpoint Utilities\n", "# ─────────────────────────────────────────────────────────\n", "\n", "def save_checkpoint(path, model, optimizer, scheduler, scaler,\n", " epoch, batch_idx, history, best_val_loss):\n", " torch.save({\n", " 'epoch': epoch, 'batch_idx': batch_idx,\n", " 'model_state_dict' : model.state_dict(),\n", " 'optimizer_state_dict': optimizer.state_dict(),\n", " 'scheduler_state_dict': scheduler.state_dict(),\n", " 'scaler_state_dict' : scaler.state_dict(), # AMP scaler state\n", " 'history' : history,\n", " 'best_val_loss' : best_val_loss,\n", " }, path)\n", "\n", "\n", "def load_checkpoint(path, model, optimizer, scheduler, scaler):\n", " default = {'train_loss': [], 'val_loss': [], 'val_acc': []}\n", " if not os.path.exists(path):\n", " print('[INFO] No checkpoint — starting fresh')\n", " return 0, 0, default, float('inf')\n", "\n", " ckpt = torch.load(path, map_location=device, weights_only=False)\n", " try:\n", " model.load_state_dict(ckpt['model_state_dict'])\n", " optimizer.load_state_dict(ckpt['optimizer_state_dict'])\n", " scheduler.load_state_dict(ckpt['scheduler_state_dict'])\n", " if 'scaler_state_dict' in ckpt:\n", " scaler.load_state_dict(ckpt['scaler_state_dict'])\n", " print(f'[OK] Checkpoint loaded — epoch {ckpt[\"epoch\"]+1}, batch {ckpt[\"batch_idx\"]}')\n", " return (ckpt['epoch'], ckpt['batch_idx'],\n", " ckpt.get('history', default),\n", " ckpt.get('best_val_loss', float('inf')))\n", " except RuntimeError as e:\n", " print(f'[WARN] Checkpoint incompatible (architecture changed): {e}')\n", " print('[INFO] Starting fresh (delete checkpoint.pt to suppress this)')\n", " return 0, 0, default, float('inf')\n", "\n", "\n", "print('[OK] Checkpoint utilities ready')" ] }, { "cell_type": "code", "execution_count": 10, "id": "c97ac439", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[OK] Training loop defined\n" ] } ], "source": [ "# ─────────────────────────────────────────────────────────\n", "# CELL 6 — Training Loop\n", "#\n", "# Key additions for 25 GB scale:\n", "# • AMP autocast + GradScaler → 1.5-2× faster, less VRAM\n", "# • Gradient accumulation → simulates larger batch sizes\n", "# • clip_grad_norm_ → prevents explosion\n", "# ─────────────────────────────────────────────────────────\n", "\n", "def train_one_epoch(model, loader, loss_fn, optimizer, scheduler, scaler,\n", " epoch, start_batch, checkpoint_path, history,\n", " checkpoint_interval, device, grad_clip_norm,\n", " accum_steps, best_val_loss):\n", " model.train()\n", " running_loss = 0.0\n", " batches_done = 0\n", " optimizer.zero_grad()\n", "\n", " pbar = tqdm(enumerate(loader, start=1), total=len(loader),\n", " desc=f'Epoch {epoch+1} [train]', leave=True, ascii=True)\n", "\n", " for batch_idx, (images, labels) in pbar:\n", " if batch_idx <= start_batch:\n", " if batch_idx % 10 == 0:\n", " pbar.set_postfix({'status': f'skipping to {start_batch}... ({batch_idx}/{start_batch})'})\n", " continue\n", "\n", " images = images.to(device, non_blocking=True)\n", " labels = labels.to(device, non_blocking=True)\n", "\n", " # ── AMP forward pass ──\n", " with autocast(enabled=AMP_ENABLED):\n", " outputs = model(images)\n", " # Divide loss by accum_steps so gradient magnitude is correct\n", " loss = loss_fn(outputs, labels) / accum_steps\n", "\n", " # ── AMP backward ──\n", " scaler.scale(loss).backward()\n", "\n", " running_loss += loss.item() * accum_steps # undo division for logging\n", " batches_done += 1\n", "\n", " # ── Gradient accumulation — only step every accum_steps batches ──\n", " if batch_idx % accum_steps == 0:\n", " # Unscale before clip (required for AMP)\n", " scaler.unscale_(optimizer)\n", " torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=grad_clip_norm)\n", " scaler.step(optimizer)\n", " scaler.update()\n", " optimizer.zero_grad()\n", " scheduler.step() # OneCycleLR steps per optimizer step\n", "\n", " current_lr = optimizer.param_groups[-1]['lr']\n", " pbar.set_postfix({'loss': f'{loss.item()*accum_steps:.4f}', 'lr': f'{current_lr:.2e}'})\n", "\n", " if batch_idx % checkpoint_interval == 0:\n", " save_checkpoint(checkpoint_path, model, optimizer, scheduler,\n", " scaler, epoch, batch_idx, history, best_val_loss)\n", "\n", " return running_loss / max(batches_done, 1)\n", "\n", "\n", "print('[OK] Training loop defined')" ] }, { "cell_type": "code", "execution_count": 11, "id": "c8c69dc5", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[OK] Validation loop defined\n" ] } ], "source": [ "# ─────────────────────────────────────────────────────────\n", "# CELL 7 — Validation Loop\n", "# ─────────────────────────────────────────────────────────\n", "\n", "def validate(model, loader, loss_fn, device, split_name='val'):\n", " model.eval()\n", " running_loss = 0.0; correct = 0; total = 0\n", "\n", " with torch.no_grad():\n", " pbar = tqdm(loader, desc=f' [{split_name}]', leave=False, ascii=True)\n", " for images, labels in pbar:\n", " images = images.to(device, non_blocking=True)\n", " labels = labels.to(device, non_blocking=True)\n", " # AMP also speeds up validation\n", " with autocast(enabled=AMP_ENABLED):\n", " outputs = model(images)\n", " loss = loss_fn(outputs, labels)\n", " running_loss += loss.item()\n", " _, predicted = torch.max(outputs, dim=1)\n", " correct += (predicted == labels).sum().item()\n", " total += labels.size(0)\n", "\n", " return running_loss / len(loader), 100.0 * correct / total\n", "\n", "\n", "print('[OK] Validation loop defined')" ] }, { "cell_type": "code", "execution_count": 12, "id": "c2e084e4", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[INFO] No checkpoint — starting fresh\n", "\n", "[START] Training inceptionresnet for 30 epochs\n", "[INFO] Effective batch = 128\n", "[INFO] AMP = True | Best val loss so far: inf\n", "\n", "\n", "══════════ Epoch 1/30 ══════════\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Epoch 1 [train]: 16%|#5 | 330/2123 [02:45<15:01, 1.99it/s, loss=0.8825, lr=1.39e-05]\n" ] }, { "ename": "KeyboardInterrupt", "evalue": "", "output_type": "error", "traceback": [ "\u001b[31m---------------------------------------------------------------------------\u001b[39m", "\u001b[31mKeyboardInterrupt\u001b[39m Traceback (most recent call last)", "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[12]\u001b[39m\u001b[32m, line 77\u001b[39m\n\u001b[32m 74\u001b[39m \u001b[38;5;28;01mfor\u001b[39;00m epoch \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mrange\u001b[39m(start_epoch, CONFIG[\u001b[33m'\u001b[39m\u001b[33mepochs\u001b[39m\u001b[33m'\u001b[39m]):\n\u001b[32m 75\u001b[39m \u001b[38;5;28mprint\u001b[39m(\u001b[33mf\u001b[39m\u001b[33m'\u001b[39m\u001b[38;5;130;01m\\n\u001b[39;00m\u001b[33m══════════ Epoch \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mepoch+\u001b[32m1\u001b[39m\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m/\u001b[39m\u001b[38;5;132;01m{\u001b[39;00mCONFIG[\u001b[33m\"\u001b[39m\u001b[33mepochs\u001b[39m\u001b[33m\"\u001b[39m]\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m ══════════\u001b[39m\u001b[33m'\u001b[39m)\n\u001b[32m---> \u001b[39m\u001b[32m77\u001b[39m train_loss = \u001b[43mtrain_one_epoch\u001b[49m\u001b[43m(\u001b[49m\n\u001b[32m 78\u001b[39m \u001b[43m \u001b[49m\u001b[43mmodel\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mtrain_loader\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mloss_fn\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43moptimizer\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mscheduler\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mscaler\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 79\u001b[39m \u001b[43m \u001b[49m\u001b[43mepoch\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mstart_batch\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mCONFIG\u001b[49m\u001b[43m[\u001b[49m\u001b[33;43m'\u001b[39;49m\u001b[33;43mcheckpoint_path\u001b[39;49m\u001b[33;43m'\u001b[39;49m\u001b[43m]\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mhistory\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 80\u001b[39m \u001b[43m \u001b[49m\u001b[43mCONFIG\u001b[49m\u001b[43m[\u001b[49m\u001b[33;43m'\u001b[39;49m\u001b[33;43mcheckpoint_interval\u001b[39;49m\u001b[33;43m'\u001b[39;49m\u001b[43m]\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdevice\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 81\u001b[39m \u001b[43m \u001b[49m\u001b[43mCONFIG\u001b[49m\u001b[43m[\u001b[49m\u001b[33;43m'\u001b[39;49m\u001b[33;43mgrad_clip_norm\u001b[39;49m\u001b[33;43m'\u001b[39;49m\u001b[43m]\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mCONFIG\u001b[49m\u001b[43m[\u001b[49m\u001b[33;43m'\u001b[39;49m\u001b[33;43maccum_steps\u001b[39;49m\u001b[33;43m'\u001b[39;49m\u001b[43m]\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mbest_val_loss\u001b[49m\n\u001b[32m 82\u001b[39m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 83\u001b[39m start_batch = \u001b[32m0\u001b[39m\n\u001b[32m 85\u001b[39m val_loss, val_acc = validate(model, valid_loader, loss_fn, device)\n", "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[10]\u001b[39m\u001b[32m, line 22\u001b[39m, in \u001b[36mtrain_one_epoch\u001b[39m\u001b[34m(model, loader, loss_fn, optimizer, scheduler, scaler, epoch, start_batch, checkpoint_path, history, checkpoint_interval, device, grad_clip_norm, accum_steps, best_val_loss)\u001b[39m\n\u001b[32m 17\u001b[39m optimizer.zero_grad()\n\u001b[32m 19\u001b[39m pbar = tqdm(\u001b[38;5;28menumerate\u001b[39m(loader, start=\u001b[32m1\u001b[39m), total=\u001b[38;5;28mlen\u001b[39m(loader),\n\u001b[32m 20\u001b[39m desc=\u001b[33mf\u001b[39m\u001b[33m'\u001b[39m\u001b[33mEpoch \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mepoch+\u001b[32m1\u001b[39m\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m [train]\u001b[39m\u001b[33m'\u001b[39m, leave=\u001b[38;5;28;01mTrue\u001b[39;00m, ascii=\u001b[38;5;28;01mTrue\u001b[39;00m)\n\u001b[32m---> \u001b[39m\u001b[32m22\u001b[39m \u001b[43m\u001b[49m\u001b[38;5;28;43;01mfor\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43mbatch_idx\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43m(\u001b[49m\u001b[43mimages\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mlabels\u001b[49m\u001b[43m)\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;129;43;01min\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43mpbar\u001b[49m\u001b[43m:\u001b[49m\n\u001b[32m 23\u001b[39m \u001b[43m \u001b[49m\u001b[38;5;28;43;01mif\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43mbatch_idx\u001b[49m\u001b[43m \u001b[49m\u001b[43m<\u001b[49m\u001b[43m=\u001b[49m\u001b[43m \u001b[49m\u001b[43mstart_batch\u001b[49m\u001b[43m:\u001b[49m\n\u001b[32m 24\u001b[39m \u001b[43m \u001b[49m\u001b[38;5;28;43;01mif\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43mbatch_idx\u001b[49m\u001b[43m \u001b[49m\u001b[43m%\u001b[49m\u001b[43m \u001b[49m\u001b[32;43m10\u001b[39;49m\u001b[43m \u001b[49m\u001b[43m==\u001b[49m\u001b[43m \u001b[49m\u001b[32;43m0\u001b[39;49m\u001b[43m:\u001b[49m\n", "\u001b[36mFile \u001b[39m\u001b[32mc:\\Users\\SHINJAN\\anaconda3\\envs\\deepfake_gpu\\Lib\\site-packages\\tqdm\\std.py:1181\u001b[39m, in \u001b[36mtqdm.__iter__\u001b[39m\u001b[34m(self)\u001b[39m\n\u001b[32m 1178\u001b[39m time = \u001b[38;5;28mself\u001b[39m._time\n\u001b[32m 1180\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[32m-> \u001b[39m\u001b[32m1181\u001b[39m \u001b[43m \u001b[49m\u001b[38;5;28;43;01mfor\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43mobj\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;129;43;01min\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43miterable\u001b[49m\u001b[43m:\u001b[49m\n\u001b[32m 1182\u001b[39m \u001b[43m \u001b[49m\u001b[38;5;28;43;01myield\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43mobj\u001b[49m\n\u001b[32m 1183\u001b[39m \u001b[43m \u001b[49m\u001b[38;5;66;43;03m# Update and possibly print the progressbar.\u001b[39;49;00m\n\u001b[32m 1184\u001b[39m \u001b[43m \u001b[49m\u001b[38;5;66;43;03m# Note: does not call self.update(1) for speed optimisation.\u001b[39;49;00m\n", "\u001b[36mFile \u001b[39m\u001b[32mc:\\Users\\SHINJAN\\anaconda3\\envs\\deepfake_gpu\\Lib\\site-packages\\torch\\utils\\data\\dataloader.py:631\u001b[39m, in \u001b[36m_BaseDataLoaderIter.__next__\u001b[39m\u001b[34m(self)\u001b[39m\n\u001b[32m 628\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m._sampler_iter \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[32m 629\u001b[39m \u001b[38;5;66;03m# TODO(https://github.com/pytorch/pytorch/issues/76750)\u001b[39;00m\n\u001b[32m 630\u001b[39m \u001b[38;5;28mself\u001b[39m._reset() \u001b[38;5;66;03m# type: ignore[call-arg]\u001b[39;00m\n\u001b[32m--> \u001b[39m\u001b[32m631\u001b[39m data = \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43m_next_data\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 632\u001b[39m \u001b[38;5;28mself\u001b[39m._num_yielded += \u001b[32m1\u001b[39m\n\u001b[32m 633\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m._dataset_kind == _DatasetKind.Iterable \u001b[38;5;129;01mand\u001b[39;00m \\\n\u001b[32m 634\u001b[39m \u001b[38;5;28mself\u001b[39m._IterableDataset_len_called \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;129;01mand\u001b[39;00m \\\n\u001b[32m 635\u001b[39m \u001b[38;5;28mself\u001b[39m._num_yielded > \u001b[38;5;28mself\u001b[39m._IterableDataset_len_called:\n", "\u001b[36mFile \u001b[39m\u001b[32mc:\\Users\\SHINJAN\\anaconda3\\envs\\deepfake_gpu\\Lib\\site-packages\\torch\\utils\\data\\dataloader.py:675\u001b[39m, in \u001b[36m_SingleProcessDataLoaderIter._next_data\u001b[39m\u001b[34m(self)\u001b[39m\n\u001b[32m 673\u001b[39m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34m_next_data\u001b[39m(\u001b[38;5;28mself\u001b[39m):\n\u001b[32m 674\u001b[39m index = \u001b[38;5;28mself\u001b[39m._next_index() \u001b[38;5;66;03m# may raise StopIteration\u001b[39;00m\n\u001b[32m--> \u001b[39m\u001b[32m675\u001b[39m data = \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43m_dataset_fetcher\u001b[49m\u001b[43m.\u001b[49m\u001b[43mfetch\u001b[49m\u001b[43m(\u001b[49m\u001b[43mindex\u001b[49m\u001b[43m)\u001b[49m \u001b[38;5;66;03m# may raise StopIteration\u001b[39;00m\n\u001b[32m 676\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m._pin_memory:\n\u001b[32m 677\u001b[39m data = _utils.pin_memory.pin_memory(data, \u001b[38;5;28mself\u001b[39m._pin_memory_device)\n", "\u001b[36mFile \u001b[39m\u001b[32mc:\\Users\\SHINJAN\\anaconda3\\envs\\deepfake_gpu\\Lib\\site-packages\\torch\\utils\\data\\_utils\\fetch.py:51\u001b[39m, in \u001b[36m_MapDatasetFetcher.fetch\u001b[39m\u001b[34m(self, possibly_batched_index)\u001b[39m\n\u001b[32m 49\u001b[39m data = \u001b[38;5;28mself\u001b[39m.dataset.__getitems__(possibly_batched_index)\n\u001b[32m 50\u001b[39m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[32m---> \u001b[39m\u001b[32m51\u001b[39m data = [\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mdataset\u001b[49m\u001b[43m[\u001b[49m\u001b[43midx\u001b[49m\u001b[43m]\u001b[49m \u001b[38;5;28;01mfor\u001b[39;00m idx \u001b[38;5;129;01min\u001b[39;00m possibly_batched_index]\n\u001b[32m 52\u001b[39m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[32m 53\u001b[39m data = \u001b[38;5;28mself\u001b[39m.dataset[possibly_batched_index]\n", "\u001b[36mFile \u001b[39m\u001b[32mc:\\Users\\SHINJAN\\anaconda3\\envs\\deepfake_gpu\\Lib\\site-packages\\torchvision\\datasets\\folder.py:231\u001b[39m, in \u001b[36mDatasetFolder.__getitem__\u001b[39m\u001b[34m(self, index)\u001b[39m\n\u001b[32m 229\u001b[39m sample = \u001b[38;5;28mself\u001b[39m.loader(path)\n\u001b[32m 230\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m.transform \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[32m--> \u001b[39m\u001b[32m231\u001b[39m sample = \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mtransform\u001b[49m\u001b[43m(\u001b[49m\u001b[43msample\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 232\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m.target_transform \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[32m 233\u001b[39m target = \u001b[38;5;28mself\u001b[39m.target_transform(target)\n", "\u001b[36mFile \u001b[39m\u001b[32mc:\\Users\\SHINJAN\\anaconda3\\envs\\deepfake_gpu\\Lib\\site-packages\\torchvision\\transforms\\transforms.py:95\u001b[39m, in \u001b[36mCompose.__call__\u001b[39m\u001b[34m(self, img)\u001b[39m\n\u001b[32m 93\u001b[39m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34m__call__\u001b[39m(\u001b[38;5;28mself\u001b[39m, img):\n\u001b[32m 94\u001b[39m \u001b[38;5;28;01mfor\u001b[39;00m t \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mself\u001b[39m.transforms:\n\u001b[32m---> \u001b[39m\u001b[32m95\u001b[39m img = \u001b[43mt\u001b[49m\u001b[43m(\u001b[49m\u001b[43mimg\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 96\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m img\n", "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[7]\u001b[39m\u001b[32m, line 28\u001b[39m, in \u001b[36mRandomJPEGCompression.__call__\u001b[39m\u001b[34m(self, img)\u001b[39m\n\u001b[32m 26\u001b[39m img.save(buf, \u001b[38;5;28mformat\u001b[39m=\u001b[33m'\u001b[39m\u001b[33mJPEG\u001b[39m\u001b[33m'\u001b[39m, quality=quality)\n\u001b[32m 27\u001b[39m buf.seek(\u001b[32m0\u001b[39m)\n\u001b[32m---> \u001b[39m\u001b[32m28\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mImage\u001b[49m\u001b[43m.\u001b[49m\u001b[43mopen\u001b[49m\u001b[43m(\u001b[49m\u001b[43mbuf\u001b[49m\u001b[43m)\u001b[49m\u001b[43m.\u001b[49m\u001b[43mconvert\u001b[49m\u001b[43m(\u001b[49m\u001b[33;43m'\u001b[39;49m\u001b[33;43mRGB\u001b[39;49m\u001b[33;43m'\u001b[39;49m\u001b[43m)\u001b[49m\n", "\u001b[36mFile \u001b[39m\u001b[32mc:\\Users\\SHINJAN\\anaconda3\\envs\\deepfake_gpu\\Lib\\site-packages\\PIL\\Image.py:922\u001b[39m, in \u001b[36mImage.convert\u001b[39m\u001b[34m(self, mode, matrix, dither, palette, colors)\u001b[39m\n\u001b[32m 874\u001b[39m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34mconvert\u001b[39m(\n\u001b[32m 875\u001b[39m \u001b[38;5;28mself\u001b[39m, mode=\u001b[38;5;28;01mNone\u001b[39;00m, matrix=\u001b[38;5;28;01mNone\u001b[39;00m, dither=\u001b[38;5;28;01mNone\u001b[39;00m, palette=Palette.WEB, colors=\u001b[32m256\u001b[39m\n\u001b[32m 876\u001b[39m ):\n\u001b[32m 877\u001b[39m \u001b[38;5;250m \u001b[39m\u001b[33;03m\"\"\"\u001b[39;00m\n\u001b[32m 878\u001b[39m \u001b[33;03m Returns a converted copy of this image. For the \"P\" mode, this\u001b[39;00m\n\u001b[32m 879\u001b[39m \u001b[33;03m method translates pixels through the palette. If mode is\u001b[39;00m\n\u001b[32m (...)\u001b[39m\u001b[32m 919\u001b[39m \u001b[33;03m :returns: An :py:class:`~PIL.Image.Image` object.\u001b[39;00m\n\u001b[32m 920\u001b[39m \u001b[33;03m \"\"\"\u001b[39;00m\n\u001b[32m--> \u001b[39m\u001b[32m922\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mload\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 924\u001b[39m has_transparency = \u001b[33m\"\u001b[39m\u001b[33mtransparency\u001b[39m\u001b[33m\"\u001b[39m \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mself\u001b[39m.info\n\u001b[32m 925\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m mode \u001b[38;5;129;01mand\u001b[39;00m \u001b[38;5;28mself\u001b[39m.mode == \u001b[33m\"\u001b[39m\u001b[33mP\u001b[39m\u001b[33m\"\u001b[39m:\n\u001b[32m 926\u001b[39m \u001b[38;5;66;03m# determine default mode\u001b[39;00m\n", "\u001b[36mFile \u001b[39m\u001b[32mc:\\Users\\SHINJAN\\anaconda3\\envs\\deepfake_gpu\\Lib\\site-packages\\PIL\\ImageFile.py:291\u001b[39m, in \u001b[36mImageFile.load\u001b[39m\u001b[34m(self)\u001b[39m\n\u001b[32m 288\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mOSError\u001b[39;00m(msg)\n\u001b[32m 290\u001b[39m b = b + s\n\u001b[32m--> \u001b[39m\u001b[32m291\u001b[39m n, err_code = \u001b[43mdecoder\u001b[49m\u001b[43m.\u001b[49m\u001b[43mdecode\u001b[49m\u001b[43m(\u001b[49m\u001b[43mb\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 292\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m n < \u001b[32m0\u001b[39m:\n\u001b[32m 293\u001b[39m \u001b[38;5;28;01mbreak\u001b[39;00m\n", "\u001b[31mKeyboardInterrupt\u001b[39m: " ] } ], "source": [ "# ─────────────────────────────────────────────────────────\n", "# CELL 8 — ▶ Run Training\n", "#\n", "# OneCycleLR: best scheduler for large datasets\n", "# - Starts low → ramps up → anneals to near-zero\n", "# - No sudden restarts, no loss spikes\n", "# - One cycle over all epochs\n", "# ─────────────────────────────────────────────────────────\n", "\n", "os.makedirs('models', exist_ok=True)\n", "\n", "# ── Separate param groups: backbone vs head ────────────────\n", "backbone_params = [p for n, p in model.named_parameters()\n", " if p.requires_grad and 'head' not in n]\n", "head_params = list(model.head.parameters())\n", "\n", "optimizer = optim.AdamW([\n", " {'params': backbone_params, 'lr': CONFIG['learning_rate'] * 0.1}, # 10× lower for backbone\n", " {'params': head_params, 'lr': CONFIG['learning_rate']},\n", "], weight_decay=CONFIG['weight_decay'])\n", "\n", "# ── OneCycleLR — ideal for 25 GB datasets ─────────────────\n", "# total_steps = steps per epoch × epochs (with gradient accumulation)\n", "steps_per_epoch = len(train_loader) // CONFIG['accum_steps']\n", "total_steps = steps_per_epoch * CONFIG['epochs']\n", "\n", "scheduler = OneCycleLR(\n", " optimizer,\n", " max_lr=[CONFIG['learning_rate'] * 0.1, CONFIG['learning_rate']],\n", " total_steps=total_steps,\n", " pct_start=0.1, # 10% warmup\n", " anneal_strategy='cos',\n", " div_factor=25, # start_lr = max_lr / 25\n", " final_div_factor=1e4, # end_lr = max_lr / (25 * 10000)\n", ")\n", "\n", "# ── AMP GradScaler ────────────────────────────────────────\n", "scaler = GradScaler(enabled=AMP_ENABLED)\n", "\n", "# ── Loss ──────────────────────────────────────────────────\n", "if CONFIG['use_class_weights']:\n", " loss_fn = nn.CrossEntropyLoss(\n", " weight=class_weights,\n", " label_smoothing=CONFIG['label_smoothing']\n", " )\n", "else:\n", " loss_fn = nn.CrossEntropyLoss(label_smoothing=CONFIG['label_smoothing'])\n", "\n", "# ── Load checkpoint ───────────────────────────────────────\n", "start_epoch, start_batch, history, best_val_loss = load_checkpoint(\n", " CONFIG['checkpoint_path'], model, optimizer, scheduler, scaler\n", ")\n", "\n", "# ── Guard: rebuild scheduler if total_steps mismatch ──────────────────\n", "# Happens when dataset size / config changes after a kernel restart.\n", "_ckpt_total = getattr(scheduler, 'total_steps', None)\n", "if _ckpt_total is not None and _ckpt_total != total_steps:\n", " print(f'[WARN] Scheduler total_steps mismatch '\n", " f'(checkpoint={_ckpt_total} vs current={total_steps}) — rebuilding scheduler')\n", " scheduler = OneCycleLR(\n", " optimizer,\n", " max_lr=[CONFIG['learning_rate'] * 0.1, CONFIG['learning_rate']],\n", " total_steps=total_steps,\n", " pct_start=0.1, anneal_strategy='cos',\n", " div_factor=25, final_div_factor=1e4,\n", " )\n", " print('[INFO] Scheduler rebuilt — training will proceed correctly')\n", "\n", "print(f'\\n[START] Training {CONFIG[\"backbone\"]} for {CONFIG[\"epochs\"]} epochs')\n", "print(f'[INFO] Effective batch = {CONFIG[\"batch_size\"] * CONFIG[\"accum_steps\"]}')\n", "print(f'[INFO] AMP = {AMP_ENABLED} | Best val loss so far: {best_val_loss:.4f}\\n')\n", "\n", "# ── Main loop ─────────────────────────────────────────────\n", "for epoch in range(start_epoch, CONFIG['epochs']):\n", " print(f'\\n══════════ Epoch {epoch+1}/{CONFIG[\"epochs\"]} ══════════')\n", "\n", " train_loss = train_one_epoch(\n", " model, train_loader, loss_fn, optimizer, scheduler, scaler,\n", " epoch, start_batch, CONFIG['checkpoint_path'], history,\n", " CONFIG['checkpoint_interval'], device,\n", " CONFIG['grad_clip_norm'], CONFIG['accum_steps'], best_val_loss\n", " )\n", " start_batch = 0\n", "\n", " val_loss, val_acc = validate(model, valid_loader, loss_fn, device)\n", "\n", " history['train_loss'].append(train_loss)\n", " history['val_loss'].append(val_loss)\n", " history['val_acc'].append(val_acc)\n", "\n", " print(f'\\n📉 Train Loss : {train_loss:.4f}')\n", " print(f'📉 Val Loss : {val_loss:.4f}')\n", " print(f'🎯 Val Acc : {val_acc:.2f}%')\n", "\n", " # ── Save best model ────────────────────────────────────\n", " if val_loss < best_val_loss:\n", " best_val_loss = val_loss\n", " torch.save(model.state_dict(), CONFIG['best_model_path'])\n", " print(f'✨ New best model (val_loss={best_val_loss:.4f}) → {CONFIG[\"best_model_path\"]}')\n", "\n", " save_checkpoint(CONFIG['checkpoint_path'], model, optimizer, scheduler,\n", " scaler, epoch+1, 0, history, best_val_loss)\n", "\n", "print(f'\\n[OK] Training complete! Best val loss: {best_val_loss:.4f}')" ] }, { "cell_type": "code", "execution_count": null, "id": "ec6eec3b", "metadata": {}, "outputs": [ { "data": { "image/png": "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dtJpPCqjYGDaoFhG71EZXZtpr+ecnFfatCap0v59de3aNd3t26lKlCihPm7cuDGNGzfmxo0bHDx4kBMnTnDgwAFu377N8ePHGTx4sJrLVafT0bt3bz744APOnTvH4cOHOXnyJP/88w/3799nw4YNFCxYUH1PzEja4F9OAvrZvfbSjoaMjIw0eU+4desWer1eDZqnbbeWLVvy5ptvZrjPjEYGZ/RelPbfpIwmlcsud3d3bt++TWJiIuHh4ene19IeO6MAtLl/pxld2zlpm+rVq7Ny5Uru3r3LgQMHOHHiBIcOHeK///7j8uXLfPjhh+zduzfd33923g/M7W9kR9pUDBkF24UQQojskuCsEEKIpy46Otpkwp8OHToAKQFLjUaDoijcuXOH119/XR05tXfvXnbv3k25cuXUW30rVqyoTuKTmJhokntv4sSJaDQaypYtm+GHzX379nHv3j01sHHu3Dl11Kybm1uGo9bSunTpkpqf0tvbW83JCjz21nUbGxsWLFiAra0tv//+OyEhISxZsoS3336b4sWL4+joSMmSJbl27RoRERGUK1dOveX29u3bTJkyhXLlyuHj44Ne/2T/dG/dulU971KlSqm31Zrbtmk/DKcNGJt7LmXKlMHe3l6duT04OFgd3Xrjxg3atGmDoijUq1ePBQsWZHnszFSrVk3NPbpy5UqaNm2q5k+9e/cuu3fvBlJGf/n5+WWzJR+vffv2JpPp5GWPBjciIyM5dOgQkDLa+P3331fX5XaqhqfFysqKEiVK8N9//xEVFcX+/fupU6cOkHKNp52N/lnIKIBUrFgxChcuTHBwMP/++y+HDx82uT18xYoV6uPU98G0X5zs2LGDwYMHqyNC9+/fz507d8yuR9o0B9HR0SZ//wsWLCA0NJRy5crh6+uL0Whk4cKFBAUFERoayuzZs+nUqROdOnUiKSmJmjVrEhsby+nTp0lMTCQiIoLVq1cTFBREgQIFGDFihJra5dSpU+rkUY+byCxtIDujL3Jyi7e3N+fOnQNSJppKmwt36NChHD58GEdHR9auXWvSbiEhISbttm7dOs6dO0fZsmUzTE8QGRnJ7t271XQH8fHx/PXXX+r61BGi5r7fQcokcamjp1esWEH//v3VdTt37lQD+J6enhl+IWWux11T2WmblStXcu7cOa5du8aPP/5I27Zt1X9n33zzTa5c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DRvyww8/kJSUpJb5/fffad26NT4+PtSqVYvBgwdz9+7dLI8bEhLCsGHDqF+/Pj4+PnTs2JFdu3ap63v06EH79u3Tbde3b19at26tPj969ChdunShcuXK1KhRgyFDhhAeHq6uX7duHa+++ipr1qyhTp061KhRg8DAwBy1VVqenp78/PPPDBkyBF9fX1577TXGjBlDQkKCSbktW7bQvn17fH19qVOnDl9//TVRUVEmZU6ePEmPHj2oWrUqtWrV4rPPPkvXfleuXKFnz55UrlyZOnXqMGnSJJKTk9X1+/fv56233sLX1xc/Pz8++ugjk5EpQgghhHhx2NjY0LJlS/bu3UtMTIzJuqSkJDZv3kyLFi2ws7MjPDycb7/9Fn9/f7y9valRowb9+vXj5s2bGe47o1u2L168SPfu3fH19cXf359Nmzal2+5xxxk6dCjr16/n1q1b6v4zOtbVq1cZMGAAderUoUqVKgQEBHDs2LF09du6dSsDBgzA19eXGjVqMGLECB48eJBlu0VFRbFz5078/f1p2bIlcXFxbNy4MV25xMREpk6dSqNGjfDx8aFly5asX7/epMyGDRto164dlStXpkGDBvzwww9qmomhQ4fSsGHDLNs1NXXVqlWr8Pf3p2rVquzfvx9IGWHcvn17qlSpgo+PD23atGHr1q0m+7ty5Qr9+/enRo0a+Pn50adPH7Xv16FDB955551059WtWze6d++eafvcv3+fcePG0bhxYypVqkTLli359ddf1fVfffUVderUwWAwmGw3ZswYatasqX4+uHz5Mn369KFq1apUrVqVfv36cePGDbV8Vuf+JBo2bMiUKVMYO3Ysfn5+1KxZky+++ILIyEiTco/7/ANZt2+q0NBQk2vwq6++IjY2Vl1/9uxZ3n//fapVq4avry/dunXj5MmTT3yeQrwoJDgrhBCkdCp79OhBkSJFmDx5MsOGDePEiRO8/fbbhIWFATB//nxWrlxJv379WLRoEe+++y4LFy5k9uzZQErusC+++IImTZowf/58hg0bxsGDBxk0aFCmx7137x4dO3bk6NGjDBw4kOnTp1OsWDH69eundvZbt27NuXPnuHbtmrpddHQ0+/bto02bNgAcOXKEbt26YWtry9SpU/nyyy85fPgwXbt2JT4+Xt3OYDCwaNEixowZw7BhwyhbtmymdVMUheTk5Ax/HvXj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NhU5Fh4eziOPPMIjjzxS6pxXXnklV155paM46rzW7WHlYuxtf+U7JMDg9OYhfPJnNl9sz1ZxVkRERGqN77//ngULFrBs2TIOHToEgGEYxMXF0b9/f8455xxOOeUUP0dZff3Va9bApvgKWePI6tleIW3Ve1ZERESkDBVaGnDRRRdx4YUX8ueff5KamkpUVBRt2rTRG68azGgT73lbvW1LkeMnNg7ikz+zWbonl18O5BIRVLQTRmSwScOwytksTERERMTXli1bxlNPPcWWLVvo1q0bQ4YMoWnTpoSGhpKWlsb+/ftZtWoV8+fPJz4+nnvuuYd+/fr5O+xqJx83iQWpJRZmAWxsEgvSyMdNUMV+5RARERGpEyr8TskwDNq2bVvk2NKlS/nss894/PHHjzkwqWIt2oJhQPJB7NRkjKhYErPcPL4sFQA3MGHJ4WIPCzTh1cFxKtCKiIhItffoo4/yzTffcM011zBkyBAaNWpU6tiDBw/y3nvvcd9993HmmWeW+amtuijICGBa41GkWpmljok26xFkqDArIiIiUhbHG4KVZfPmzXzwwQe+nFKqiBEaBk2ae+4cWT2blmuRb5X9uHzLM05ERESkuouJiWHhwoVcd911ZRZmARo0aMDo0aP54osviI6OrpoAa5iGAVEcH3RcqV8NAqL8HaKIiIhItefT4qzUbEbr9gDY2zb7ORIRERER3xszZgwhISGOHlOvXj3uvPPOyglIREREROo8FWflLyrOioiISB21b98+1qxZw549e/wdioiIiIjUIWoCJYWM1u2PbAq2GdsueXOHkny+LYtT8kOIjwkkLLD0en9ilruwBYIrwCSyIIe09HzcBZ5j2lxMREREqtru3bu58847WbduHeDZVyEhIYHJkycX21/BF+bPn8+sWbPYtWsXLVq04LbbbuOcc84pjOXxxx9nxYoVhIWFcckll3D77bfjcun9kYiIiEhtpeKs/KVZawgIgMx0OLgfAuO8etjn23P4fHsOJtAqKoCO9QPpWD+QDvWDaBhqYhgGiVlublyUVGYPW20uJiIiIlXtiSee4MQTT2TWrFlERkaSmJjIlClTGD9+vM/3Uvjoo4+YMGECDzzwAP379+fTTz/l7rvvpnHjxpxwwglcf/31tGrVinfeeYedO3cyYcIETNNkzJgxPo1DRERERKoPr4qzV199tVeT7d+/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usQFkRAbSEJsEM0jXbiMohuIJWa5uXFREvml12bVv1ZERERERERERCqF4+Jsnz59+PjjjznllFOKnfv+++9LLdxKDdW6PSz5Cntb9dgUzIn9mRb7M3NYtCMHgNAAg/iYQOJjA48UbANJy7XKLMyC+teKiIjURrm5uQQHB/s7DBERERGp4xwXZy+44AIeffRRrr/+ei644AIaNWpESkoKX331FV988QV33HEH8+fPLxx/0UUX+TBcqWpG6/ZHNgXbjG3bGP9YeVqd3dg5nNRci40p+WxOLiC7wObXg3n8ejCvcExciOnHCEVERMRf+vXrx5AhQxg2bBhdunTxdzgiIiIiUkc5Ls7efffdACxZsoQlS5YUO//8888X3jYMQ8XZmq5ZawgIgMx0OLgfGjbxd0Re6xwXRLuYQADcts3OtAI2JucXfu1Kd5OUU86yWREREamVrrvuOj766CPee+89WrduzdChQ7nwwgtp0KCBv0MTERERkTrEcXH266+/row4pJoyAgOheRvPytltmzGqQXE2Mtgk0KTcPrGRwX+tinUZBq2jAmkdFcg5rT3HMvIsvtuVzfTfMio5YhEREalubr31Vm699VZWr17NvHnzmDlzJi+88AInn3wyw4YN44wzziAwMNDfYYqIiIhILee4ONu0adPC29nZ2WRkZBAdHa03r7WY0ep4T8/ZbZvhpNP8HQ4Nw1y8OjiOtNzSq7ORwWa5PWLDg0wSYoO8uqbbth3FKCIiIjVDjx496NGjBw899BA//vgj//nPf7jzzjuJjIxk6NChjBgxosj7XxERERERX6pQw82VK1dy2WWX0bNnT0499VS6dOnCv/71L5YtW+Z4LsuyeOmll+jfvz/dunXjxhtvZNeuXaWOz8/P57nnniscP2LECDZs2FBkzNKlSxk6dChdu3bl7LPP5tNPP3Ucl/xNm3gA7O3VZ1OwhmEu2sUElvrl6827nl2RxrqkvPIHioiISI2zb98+Zs+ezUsvvcSKFSto1aoVQ4cO5YcffuDcc8/ls88+83eIIiIiIlJLOS7Orl69mmuvvZb09HRuvfVW/v3vf3PLLbdw+PBhbrjhBn755RdH802fPp05c+bw+OOP884772BZFjfccAN5eSUXwh555BE+/PBDnnzySebOnUtsbCw33ngj6enpAGzdupVRo0bRv39/PvzwQy699FLGjRvH0qVLnT5VOcJo1d5z48/NWD99g71xDbbl9m9QVWxfpptxP6Tw3MpUUnLq1nMXERGpjTIyMpg7dy5XXXUVZ555JrNmzaJTp07MmTOHzz//nPHjx/Ppp5/Sp08fnnzySX+HKyIiIiK1lOO2Bi+88AK9evXi9ddfx+X6a3XibbfdxvXXX8/LL7/M7NmzvZorLy+P2bNnc++99zJgwAAApkyZQv/+/Vm0aBHnnXdekfG7du1i7ty5vPLKK/Tv3x+AJ554gosuuoh169bRt29f/vvf/xIfH89dd90FQNu2bVm/fj2vvfYaffv2dfp0BbD37vDcKMjHfm0yNkBMHObwURg9+/kztGPmbf/ak48L5ofduXy9M4dl+3K5umM457YJxWUYVResiIiI+Ey/fv3Izc2lW7duPPbYY5x77rmEhYUVG9e5c2fWr1/vhwhFREREpC5wXJxdu3Ytzz33XJHCLIBpmowYMYLx48d7PdfGjRvJzMwsUjSNjIykY8eOrFixolhxdsmSJURERHDqqacWGf/NN98U3l+5ciUDBw4s8rg+ffowceJEbNvGUDHNEXvVEuwZTxU/kZKENW0i5ugJNbpA66R/7YXt8pn+axp/HC5gxm/pLNqezejuEV73rZWSJWa5j7l/sIiIiFNXXnkll1xyCW3atClz3MiRI7nllluqKCoRERERqWscF2fr1atHQUFBiecKCgqwHWyctH//fgCaNGlS5HjDhg0Lz/3dtm3baN68OYsWLWLWrFkcOHCAjh07ct9999G2bdvCORs3blxsvuzsbFJSUoiNjfU6vrrOttxYc2aWOcZ6eyZm9z4YZs0tnjUMc3lV/EuIDWTK6bF8/mc2b67PYGtqAXd/l8JZrUK5tlM4UcEVauFcpyVmublxUVK5K5dfHRynAq2IiPjUuHHjWLVqFdOmTWP06NEArF+/npkzZ3LjjTdywgknAJ73viIiIiIilcVxcbZHjx7MmjWL/v37ExoaWng8KyuLWbNm0atXL6/nys7OBiAoqOjKw+DgYFJTU4uNz8jIYMeOHUyfPp1x48YRGRnJjBkzGD58OJ999hn169cnJyen2HxH75fWx9ZbAQElF99cLrPI99rC2rAWKyWp7EHJSbi2rsfs0NXR3DU1ZwHARfHhnNYylNfWpPPl9mwWbs/mp705XN85krPbhJKUZZGaV3q1MSrIpGE97wqNiZnuwrlM0yK8IIeMzAIsy3Y8V3WU6XaXWZgFT8uJTHfp//5KU1NfY/6knDmnnDmjfDmnnFWe77//ntGjR9O5c+fC4qxhGGzfvp3hw4cze/ZsR+9rRUREREQqwnFx9u6772bYsGGceeaZDBgwgAYNGnDw4EG+++47cnJymDhxotdzhYSEAJ6i6dHbALm5uUUKv4XBBgSQkZHBlClTClfKTpkyhdNOO4158+Zxww03EBwcXKwIe/R+SXN6yzQNYmLKXjkRGVnx+auj7PwsipfIiwvLzyK0nNyUpqbmLAZ4skkkl+3L5pnFB/kjOY8XVqXy6fZstqfklVlwDHIZzP1XCxpHBJZ5jf3p+Vz3xU7y3KWvRvd2rqPzHS5jM7PoEJdX8/hSZEGOd+MiQoiJCSl/YEmPraGvMX9SzpxTzpxRvpxTznzv5ZdfZsiQITz99NOFxzp06MBHH33E+PHjef7555kzZ44fIxQRERGRusBxcbZVq1a89957vPzyy3z//fekpqYSFRXFiSeeyG233Ua7du28nutoO4PExERatGhReDwxMZH4+Phi4xs3bkxAQEBhYRY8Bd7mzZuze/fuwjkTExOLPC4xMZGwsDAiIiIcPde/syybtLSsEs+5XCaRkaGkpWXjdpezDLAGsQKLb4pRkqzAMHJSMh3NXVty1jIEXj4jlo/+yOLNdelsOVT+6uw8t83Og5kEF5RdCN2Zkl9mYdbJXImZbkZ+nlhu+4A3zmlYpStx09LzvRyXQ0pA6YXlktSW11hVUs6cU86cUb6cU848IiNDfb56eOvWrdxzzz0l7kdw0UUXFa6mFRERERGpTI6Ls9OnT+ess87ihRdeOOaLJyQkEB4ezvLlywuLs2lpaaxfv54RI0YUG9+7d28KCgpYu3YtnTt3BiAnJ4ddu3YxZMgQAHr16sXPP/9c5HHLli2jR48emOaxvakvKCj7lyK32yp3TE1it+0IMXFQVmuD2DjcbTtiVfB515acXdAmlH5NgpiyKo3VieUXaHcczsMq5Zfso22bd6WX3Nv5n9wF5ecwOavAq/YByVkFxAZX3aZ5bi9/9t48x1IfW0teY1VJOXNOOXNG+XJOOfO9iIgItm3bVmRj2qN27dpFWJh3f6QWERERETkWjouzM2fOpFOnTkVWr1ZUUFAQI0aMYPLkycTGxtK0aVMmTZpE48aNGTx4MG63m+TkZCIiIggJCaFXr16cfPLJjB8/nscee4zo6GheeuklXC4XF154IQBXXXUVF198MZMnT+biiy/m+++/54svvuC111475njrGsN0YQ4fhTWt9FYV5hWjavRmYL5UP9TFtZ3CWZ2YXO7YZ1ek+ey6T/6cSv0Qk/BAg/Agk/BAk3qFtw3CAw0O5+oXehERkb8bNGgQL774Ik2aNOH0008vPP7jjz/y4osvMnjwYD9GJyIiIiJ1hePibLt27di2bRunnXaaTwIYM2YMBQUFPPjgg+Tk5NC7d29ef/11AgMD2b17N2eeeSZPPfUUQ4cOBTz9wSZPnsxtt91GTk4OPXr04M033yQ2NhaA448/nunTpzNp0iT++9//0qxZMyZNmlTiqggpn9GzH+boCVhzZhZbQWuccwlGz35+iqxmiwoyCDBLX6FqGFBg2RzOLbutAcD+TDf7M5195L86sG2bRTuyvRqblOOmHVXbD1dERGq3u+66i7Vr13LLLbcQGBhIdHQ0hw8fpqCggK5du3LPPff4O0QRERERqQMM27bLr/78zdSpU5k1axa9e/cmPj6+2Ee+DMOolT263G6L5OSS+6oGBJjExNQjJSWz1n7k0LbcsPl37MPJ2GtWwLJvoVkrzEemYlSgXURtzdkfKfmM+bb8lbMvnR5Lu5iyi43eznVrt3Ciglxk5luk59tk5Flk5ttk5Ftk5Hm+J+dYJGWXn2dv4vKFzHyL51emsXRfrlfjQwJgXK8o+hzn/aZgvniNJWa5SStj1XFksEnDsNqzcry2/rusTMqZM8qXc8qZR2xsPZ/3nAWwLIvvv/+eVatWkZqaSkREBL169WLAgAHH3A6rOijr/Svo9VURypkzypdzyplzyplzypkzypdzypmHt+9hHa+cnTp1KgBLlixhyZIlxc7X1uJsXWeYLkjoggHYJ/TE+m057N6OvXIxxomn+ju8Oi0hJshnhd6qsCOtgCeWHWZPhhvXkcXDZe17ZgA5BfDYslQubZ/P1R3DcZWx6thXErPc3LgoqdxN1F4dHFerCrQiInWJaZqcfvrpRdoaHGXbdombhYmIiIiI+JLj4uzGjRsrIw6pQYzwCIzBF2N/9H+er1791He2lnh/cyZ39IgkLLByVgv9sDuHF1alkeO2aRBq8sBJ0cSEmGWuTg0LNFiwNZuPtmbx/uYsNiXnM+7EKGJDKvc1l5ZrebWJWlqu5VVxtq6twhURqQk+++wzfv75Z/Ly8jj6YTLbtsnKyuLXX3/lhx9+8HOEIiIiIlLbVWjl7KWXXkqjRo2Kndu9ezezZ8/m4Ycf9klwUn0Zgy7C/uoj2LcLe/n3GH3P8HdI1UJksEmgSbmrLSODyy9++nIub/24J5f1hw5xS7cITnbQQqA8bstm9roM5v2RBUC3BkGMPzGKqCOxl1eUHNU1gg71A3lxdRprkvIZ800y950YxQlxQT6LsTJV51W4fy8auwJMIgtySEvPx33koycqGotIbTV16lSmTp1KREQEBQUFBAYGEhAQQHJyMqZpcumll/o7RBERERGpAxwXZ6dNm8app55aYnH2t99+4/3331dxtg4wwuphnD0Me+5/sT+ag33iaRguFXAahrl4dXCcT1ZI/nMuV4BJZEQIaek5jgtn3hR6AwyIDTFJzLZ4YlkqJx+Xw81dI4gLPbafa0qOm6d/TmVtUj4Al7YPq1BrglObhdA6KoAnlx9mR5qb+35MYWSncIYeH1YpHzu18K4d92tr02kdFUhcqElcqMvzPcxFbIhJ4JHn6OtVuL5SnYvGIiKVbd68eVx00UU89dRTvPTSS+zdu5dnnnmGdevWcdNNN3H88cf7O0QRERERqQO8Ks5efvnl/Pbbb4Dno17/+te/Sh3buXNn30Qm1Z5x5gXYi+ZD4l7sn77G6D/Y3yFVCw3DXD4rZP19Lk9D7RBSAtyOG2p7WzSOCjZ5Z2MmH2zO5Ke9ufySmMe1ncI5t00orgoUQDcm5zFxWSqHcixCAwzu7hlJv6YVX5HbPCKAKQPq8/IvaXy7K4fX12WwITmfu3pGUs9HrRj2ZRbw9Y4cPt+e5dX4NUn5rDlSeP47A4gONokLNQkJqJ49C6tr0VhEpCocOHCA888/H8Mw6NChA59++ikAJ5xwAjfffDPvv/8+I0aM8HOUIiIiIlLbeVWcfeKJJ/jiiy+wbZtp06YxbNgwGjduXGSMaZpERkYyeLAKdHWFERKKcc6l2O+9hr3gbey+p2MElL0xlfiPt0XjazqFc2qzYF7+JZ2NyfnM+C2db3flcHv3CFpHeffztW2bT//MZtaadApsaB7h4sE+0TSPcLxYv5iQAIN7e0XSsX4gM9ek89PeXLanJvPASVG0ia7Y6y8r32Lxnly+2pnNuhIKrWW55PgwDAMOZlskZbtJOvK9wIKUXIuUMgriIiLiP2Fhf33yomXLluzevZucnBxCQkLo0KEDu3fv9nOEIiIiIlIXeFUpadeuHbfddhsAhmGU2nNW6h7j9HOxF86FpAPYi7/EGHCuv0MSH2gdFcik02L4/M9s3vg9g43Jnj6vl7Svx+UJ9UjNtUpdhZtn2czbksWSvbkAnNI0mDt9vMmYYRgMaRPG8dGBTFx+mL2Zbu7+LpmrOoXT9Ugf2vL6p1q2zbqkfL7akc3iPbnkuD1tDAyge8MgTogL5M31meXGcmqzENrFFC0K27ZNWp7NwSw3SdluNhzK5/0t5a/EXXkgl+PCXZW2IZuIP6ivsVRXnTt3Zv78+Zx88sm0bt0al8vF0qVLOf3009m6dStBQTWjr7mIiIiI1GyOl7EdLdKmpqaSnZ2NZRUv0Bx33HHHHpnUCEZwCMa5l2G/PRN7wTvY/QZiBOqXmdrAZRic1zaMPscF88pvnhWq727K5Nud2STnWBSU05LVAK7vHM7F7SqnJyxA+9hAXj6jPpNWprLqQB6vr80oc3ygCU+eEsMviXl8vTObA1l//ferabiLgS1DObN5CHFhLv5IyfeqOFsSwzCICjaICjZpFxNIXKjLq+Lsm+szeXtjJj0bBdO/aTAnNQkusVD792JXScordhVYNltS8vluV453T0ikgtTXWKqzm2++mZEjR5KWlsYrr7zCBRdcwPjx4znppJNYvHgxAwcO9HeIIiIiIlIHOC7O7ty5k3HjxhX2oC3Jhg0bjikoqVmMAedgfzEXUpKwv/8CY+AF/g5JfCgu1NOS4Kc9Ocz4LZ3EbO8+pj+6ewTntg6r5Og8hchHT45m2i9pfL697GJjvgVjf0gpvB8WYHBasxAGtgwhITawSBHZm03UAk3POF9pEGpyMNti2b5clu3LJcCEng2DOKVZCH2aBFMv0KxQsSvPbbMpOZ+1SXmsS8pnQ3IeuW6fhS1SqrrU1/hY/2giVa9379588MEHbNq0CYCHH34Y0zRZvXo1Z599Nvfdd5+fIxQRERGRusBxcfaxxx5j+/bt3HbbbTRu3BjT1Mdv6zojMAjjvH9hvzUN+9N3sfsPxgiu+KZPUj2d3DSEbg2DeHF1Gj/uyS13fPsK9n+tCNMwOKd1WLnF2aO6NwxiYMsQ+jYJKXWzLm83UfNlseXBPlEEmAaL9+SyeE8Ou9LdLN+fx/L9eQSY0KNhEMfHBHpV7Pp5Xw7JOTZrk/LYlJLPP/eQiwwyaBUZUOJmZv90IMtdrHWDiPxFK4RrpunTp3PWWWdx4YUXAhAcHMzjjz/u56hEREREpK5xXJxdsWIFEydO5LzzzquMeKSGMvoPxv78A0/v2W8/wzh7qL9DkkoQFmhyaft6XhVnq6tH+0bTu0mwV2O93UStPN6uwo0K9lyvdVQgIzrUY0eam8V7cli8J4ed6W5+3p/Hz/vzvLrm9N+KtniICTbp3CCQE+oH0blBEM0jXPx5uIAx3yaXO9czP6dy7QluLmwXhquSWlSI1GR1aYVwbTJz5kw6depE27Zt/R2KiIiIiNRhjouz4eHhREVFVUYsUoMZAYEY51+B/cYL2J+/jz3gHIyQUH+HJVJMTEjVr/avyCpcwzBoFRVAq6hwRnQMZ2daAT/uyeHrndnszyy/tUR0sEGPRsGcUD+Qzg2COK6eq1jvX2+KxgZQYMNrazNYsieXu3pG0izC8f86pIbxxUf0D+dYrE707o8JNuU0sRapBO3atWPbtm2cdtpp/g5FREREROowx79hX3jhhfzf//0fp5xySqVt8iM1k3HymdifvgeJe7G/XoAx5DJ/hyRSbRzrKtwWkQFcGRnOSY2DvVrt+tjJMeW2Ivhn0dgVYBIZEUJaeg7uI30QIoIMfj2Yz6tr0tmQnM9tXx/i6k7hWkVbDfmq52lFP6Kfnmex9mAea5Ly+O1gPjvSCryO/fGlqZzZMpTTmoXQKkrFf19SL9zSnX766Tz//PP8+OOPxMfHExZWtE+6YRiMHj3aT9GJiIiISF3h+Deg0NBQVq1axaBBg+jcuTMhIUV7ixqGwZNPPumzAKXmMFwujAuHY786GfuLudhnnIcRWvkbQolIxf29aBwQYBITE0JKgJuCvzWpPateAD2O9BtenZhXJatoq3NB6e+xuQJMIgtySEvPLyxo+yM2X/Y89fYj+gcyC9iWWsBvB/NYczCPbakFxda/NqnnYl9m+bvPJeVYvLspk3c3ZdIyMoDTmgVzarMQjgsv/vry5WvjWOaybJsdaQWsOZjP0r3e9bv+cmc2eZbN8TGBBJql/3HDV68x9cIt29SpUwFYsmQJS5YsKXZexVkRERERqQqOf6ueN28eERERWJbFb7/9Vuy8VtPWbcZJp2F/8i7s24X95XyMC4b7OyQR8YEGYS4e7xfNwu3ZvLo2o1JX0VZGQcnfq0orOy5/9Dy978fDxYqxzSNcdIkLomvDIDrHBXEwy+3VSu+rO9Zjc0oBK/fnsiOtgDfXF/Dm+kyOjw7gtOYh9G8aQoMwl0/z73Qu27bZle4uLEavTcojLc9ZO4YFW7NZsDWbIBPiYwPpVD+IE+ICSYgNJCzQrFBcZVEv3LJt3LjR3yGIiIiIiDgvzn7zzTeVEYfUEoZ5ZPXsK89gL5yHfeb5GPUi/B2W+JC3m1tFBldtb9fqGldtYhgGZ7cOo0ejYF4qYRVtkMuoloXG6lrs8seqRhubPLdNdoFNVoFFdv7R2zY5BZ7bO71sR2ADx9Vz0aVBEF0aBNIlLojY0KJx5rptr/5dntEilMsTXKTnWSzdm8sPu3P49WAeWw4XsOVwBq+tzaBT/UA6xgb6LP/e/iw/+zOLfZlu1iblc/gfr+9gF3SqH8Rx4S4++TO77MmAznGB7EgrIC3PZm1SPmuT8mETmAa0iQrghLggYkNMFVRFREREROoQn34e1bZtMjMzCQ8P9+W0UsMYvfpjN3sXdm/HXvghxtBr/B2S+FBFNreqCt70T60NvRWrQxG6YQmraEd/dQgLcJexkNDXhcZcd/kbo0H1XT1Y0bjyLZv0PIv0vKPfLbYe9q6gete3KXiXtfI92jea3k2Cyxzj9N9lRJDJ4FahDG4VyuEciyV7c/h+dw6/J+Xz+yHPV1V7b3NW4e0gEzrW9xSjOzcIov2R9gR/pOR7VZy9sXMEbaMD2JXu5vdDeZ7nlJTHgSyLPw4X8IeXP0eAL3dk89PeXLILbLILLLKPFNj/XmhPz/PVT7t2uv/++8sd89RTT1VBJCIiIiJSl3lVnL388suZOHEibdu2LTy2aNEi+vbtS0TEX6si16xZw+WXX86GDRt8H6nUGIZpYl44AmvaE9hffow96CKMiCh/hyU+dKybW1UWb/qn1nTVpTj+91W0L65O45fEvHIf889Co2XbHMq22JPhZm9GAXsz3exJL2C7lys3x/5wmNAAg5gQk5hgk9gQk9gQ15HvJjFH7meWVwE9IiPfYk9GAel5Nhl5Fhn5Fhl5Nhn5nkJoRr7Ffi/6pwI8vyqNyGCDINMgyHXky4Qgl0GwyyDQNMjwsnA29Zc0CmzPhlsZR1a6VtTfrxjsgtAAk9AAo/ArLMCgwLb5JbH8ImhMiHd/AKjov8voEJMhbcIY0iaMpCw3P+7JYeH2bHaml/8zGPdDMqZh8PduG/9svGHZ3uWxXXQAfZoE06VBEPExgQS6irfwcPJHE8MwaBEZQIvIAM5p7TmXlOVm3aE8fk/KZ1VinlevswVeFIO99cpvaZzaPJQeDYNoGu4qtUVVde4FXRHLly8vdiwrK4vDhw8THR1N586d/RCViIiIiNQ1XhVnf/31VzIzMwvvu91u7rjjDj744AM6depUacFJDdajL7RsBzv+wP78A4zLrvd3RCK1RnUqjjcMc/FEv2jeWp/BO5uyyh3/8dYssgps9mS42Z9ZQK53tc5SZRfYZGe42ZtxjBMBDyw+fMxzHOVtgdkbm0tYTWkA4UEGEYEmEUEmpmGzIbn8az7WN5oOcYGEBBil9gn+IyWfXxLL7xNbleLCXFx8fD06xwV51cM2xw0U64hbMWO6R9IuJrDMMcf6R5O4MBcDwkIZ0DyUP1LyvXqOJzYOolGYi5AAg7AAk5CjBfZAgxCXQWigQVKWm2dWpJU71/rkAtYnpwPQOMxFz0ZB9GgURNcGQZXSC7e6KK1V19atW7ntttu46KKLqjYgEREREamTKtzWwPZyxYnUTYZhYF40AuvFR7C/+QT7rKEYUTH+DktEKoFhGJx8XIhXxdmvdhbd1d5lQON6Lo4Ld9E0PIDj6rkAm+m/ZZQ717P9o4kJdZGcY5GcY5GS4y68/ff76Q42bQoNMAgPNIgIMgkPMggPNP923yQ73yryMffS3NA5nPohLvIsT49XzxdF7h/McrNsf/krjq/uUI+2MYFEBplEBHliqRdoYP6tuOptQS86xKReYO3vu/xQnyhaRP71Fuefb1lsYGdaAROXp/rsmlX9R5MRHcLLLRr/YXrXBuKCtqHsSHPze1Ie+7PcfLotm0+3ZeMyoFP9QHo0CqZhWN3phdu2bVtuv/12Xn75ZYYMGeLvcERERESklvNpz1mRIrr0hjbx8Ocm7M/ex7jiJn9HJCJ+1r9pMB2PbKDUNNxFozAXLrPoCs4/UrwrKIUEmDQND6BpOW3ONx7K4+7vU8qdb8qAGOJjg8oc80dKvlfF2S5xQeUXzlLyWba//IJqr8bB5c7lS9Whr/GxahDqKfaXJfcY2kPUNgNbhNIuJpDsAos1B/NZdSCXVQfy2JfpZk1SPmuSqr7Xr7+Fh4ezZ88ef4chIiIiInWAirNSaQzDwLz4KqznHsT+9lPss4dixMT5OywR8aNL29er0kIjQIBZ8sf3/6m0j/nXBL4sqFaXvsZy7Jy+LkIDTE5qEsxJRzZ625tRwKoDeaw6kMuviXnUtv3F9u7dW+yY2+3mwIEDvPTSS0X2WhARERERqSwqzkrl6tgdju8EW37H/uRdjKtG+zsiEanmqvPKzeoam68LqtWpr/Hf+TL/1fVnWZ0K7ceFB3BceADntw3zegV6TXLGGWeUuPmZbduEhIQwdepUP0QlIiIiInXNMRVnS9vNV+SowtWzz96H/cNC7HMuwYhr5O+wRKQa83WhsTKLXa4Ak8iIENLSc3AXWI5i83VxsLoWVH3Jl6+N6rpC2JevsaPz+eI5eLsCvSZ58skni72XNQyD8PBwTjrpJCIiIvwUmYiIiIjUJV4XZx955BHCwz2N/Y5uBvbQQw9Rr169wjEZGeVv4CJ1j5HQBTp0hQ2/YX/yDsa1d/g7JBHxsepcaKzMVaUBASYxMSGkBLgpKHD2me/qWhys7nz92qiO+fXVa0zKNnToUCzLYvPmzSQkJABw8OBB1q9fT2hoqJ+jExEREZG6wqvibO/evYG/irKlHatXrx69evXyZXxSS5gXX4W14TfsxV9in3sZHNfU3yGJiA9V90JjTSjCiUjVOnDgADfccAPZ2dl89dVXAKxfv55Ro0bRrVs3XnnlFaKjo/0bpIiIiIjUel4VZ996663KjkNqOaNdRzihJ6xbhfXxHKzTBpOdn4UVGIbdtiOGqeKESE2nQqNI7VVde/Qei2effZa8vDwmT55ceOy0007jww8/5O677+a5557j8ccf92OEIiIiIlIXHPOGYPPnz+f0008nKirKF/FILWZefBXWulXw09fk//Q1qUdPxMRhDh+F0bOfP8MTERGRUlT31fEV8dNPP/HYY4/RrVu3Isc7duzIHXfcwcSJE/0TmIiIiIjUKce0vMHtdnP//feze/duX8UjtVnywZKPpyRhTZuIvWpJ1cYjIiIiXmsY5qJdTGCpXzWpMAuQl5eHy1VyzKGhoWRmZlZxRCIiIiJSFx3zZ8/+3nNWpDS25caaM7PMMdbbM7EtdxVFJCIiInVZ165deeONN8jPzy9yvKCggDfffJMuXbr4KTIRERERqUuOua2BYRi+iENqu82/Q0pS2WOSkzzjEvTLkIiIiFSuMWPGcNVVV3HmmWdy6qmnUr9+fZKTk1myZAmHDh3SngsiIiIiUiWOuTirlbPiDftwstfjVO4XERGRytatWzfeffddXnnlFb777jsOHz5MREQEvXr14tZbb6VDhw7+DlFERERE6oBjKs66XC6+/vprGjVq5Kt4pJYyomPxpoxvRMdWeiwiIiIi4Nn8a8qUKYW9Z7OzsykoKCAiIqLSr71t2zaGDh3KQw89xNChQwHYsGEDEydOZN26dcTGxnLttddy9dVXV3osIiIiIuI/Feo5u2vXLrZu3QpAZGQkTz31FDfffDPz58/3ZWxSm7TvBDFxZY+JjfOMExEREalk+fn5/Pvf/+ayyy4rPPbLL7/Qt29fnnnmGSzLqtRr33vvvWRlZRUeS0lJYeTIkbRo0YK5c+cyevRoJk+ezNy5cystDhERERHxP8fF2e+//55zzjmHDz74AICHH36Yd955hwMHDnD//ffz/vvvO5rPsixeeukl+vfvT7du3bjxxhvZtWtXqeM//vhj4uPji33t3r27cMzgwYOLnb/vvvucPlXxIcN0YQ4fVfaYAedimDVrp2cRERGpmV5++WU+/vhjhgwZUnisY8eO3Hvvvbz33nu89tprlXrt8PDwIsfee+89AgMDeeyxx2jbti3Dhg3j2muvZdasWZUWh4iIiIj4n+Pi7IwZMzjllFMYPXo0aWlpfPnll9x0003MmzePm266iTfffNPRfNOnT2fOnDk8/vjjvPPOO1iWxQ033EBeXl6J4zdt2sSJJ57I4sWLi3w1adIEgKysLHbt2sXMmTOLnJ8wYYLTpyo+ZvTshzl6QvEVtIFBANhffIi9e5sfIhMREZG6ZsGCBYwfP57rrruu8Fh0dDTXXnstd911V+FCBF9bsWIF7777Lk8//XSR4ytXruTEE08kIOCvrmN9+vRh+/btJCWVs6mqiIiIiNRYjnvObty4kRkzZhAeHs4nn3yC2+3mrLPOAqBfv3688cYbXs+Vl5fH7NmzuffeexkwYAAAU6ZMoX///ixatIjzzjuv2GM2b95MfHw8DRo0KHHOP/74A8uy6N69O1FRUU6fnlQyo2c/zO59cG1dT1h+FlmBYRQ0a4M95d/wx3qs5x7CfGAyRoPG/g5VREREarGUlBSaN29e4rk2bdqwf/9+n18zLS2NcePG8eCDDxYuLDhq//79tG/fvsixhg0bArBv3z7i4sppDyUiIiIiNZLj4mxwcDAFBQUALF68mPr165OQkABAUlISkZGRXs+1ceNGMjMz6du3b+GxyMhIOnbsyIoVK0oszm7atIkzzjij1Dk3bdpEXFycCrPVmGG6MDt0JTSmHjkpmZgFFvYd/8Z6Zjzs3o713IOY90/CiIrxd6giIiJSS7Vp04aFCxfSr1+/Yue++eYbWrZs6fNrPvLII3Tv3p3zzz+/2LmcnByCgoKKHAsODgYgNze3wtcMCCj9g3Iul1nku5RPOXNG+XJOOXNOOXNOOXNG+XJOOXPGcXG2R48ezJ49m7S0NBYuXMjFF18MwLp165g6dSo9evTweq6jKxL+uXKgYcOGJa5WSE1N5cCBA6xcuZI5c+aQkpJCly5dGDt2LK1btwY8xdmwsDDGjBnD6tWriYmJYdiwYVx99dWY5rG9KEp7c6sXnXPFchYVhT12InmP3w2Je7GnPEzAA89ihNXzY5TVi15nzihfzilnzilnzihfzilnlefqq6/mvvvu4/DhwwwcOJD69euTnJzMt99+y+eff85TTz3l0+vNnz+flStXsmDBghLPh4SEFGvrdbQoGxYWVqFrmqZBTEz576UiI0MrNH9dppw5o3w5p5w5p5w5p5w5o3w5p5x5x3Fx9oEHHuCmm27innvuoV27dtxyyy0AjBo1ipCQEO69916v58rOzgYocZVAampqsfFbtmwBwLZtnnrqKXJycpgxYwbDhw9nwYIFxMXFsWXLFtLS0jjrrLMYPXo0q1atYtKkSaSmpnLHHXc4fbqFvHlzqxedc0VyFlOPgokvkDz+VqydW7GnPk7Mo5MxgoL9F2A1pNeZM8qXc8qZc8qZM8qXc8qZ71100UVkZmYyffp0Fi1aVHg8JiaGhx9+mAsvvNCn15s7dy6HDh0qbOV11L///W8+++wzGjduTGJiYpFzR+83atSoQte0LJu0tKxSz7tcJpGRoaSlZeN2WxW6Rl2jnDmjfDmnnDmnnDmnnDmjfDmnnHlERoZ6tcjCcXG2efPmfPbZZxw6dKhI76tp06bRsWPHYoXWsoSEhACe3rNHb4NnlUBoaPFfQnr16sXSpUuJiYnBMAwApk6dyoABA/jwww+56aabePXVV8nNzSUiIgKA+Ph4MjIymDFjBrfffnuFV8+W9eZWLzrnSs1ZaAyuux/Hemoc+et+5eDEhwi4/UEMl8t/wVYTep05o3w5p5w5p5w5o3w5p5x5ePvG1qkrr7yS4cOHs23bNg4fPkxkZCQRERG8//77nHHGGXz77bc+u9bkyZPJyckpcmzw4MGMGTOGCy64gI8++oh33nkHt9uN68j7nmXLltG6dWvq169f4esWFJT/unG7La/GyV+UM2eUL+eUM+eUM+eUM2eUL+eUM+84Ls4CGIZR5ONVCxcuZO/evcTExDjqz3W0nUFiYiItWrQoPJ6YmEh8fHyJj4mNjS1yPzQ0lGbNmnHgwAHAswr3nwXi9u3bk5WVRWpqKjExFe9jWt4LSi8650rMWbM2mGP+jfXcg1irl5L/+osYI+8oLMjXdXqdOaN8OaecOaecOaN8OaecVR7DMGjTpg0//vgjr7/+Ot9//z0FBQU0a9bMp9cpbfVr/fr1adSoEcOGDeO1115jwoQJ3HDDDaxZs4b//Oc/PProoz6NQ0RERESqF8dLEP78808GDRrErFmzAHjhhRe48847eeaZZ7jgggtYtWqV13MlJCQQHh7O8uXLC4+lpaWxfv16evfuXWz8u+++y0knnURW1l8rWDMyMti+fTvt2rXDtm0GDhzI1KlTizxu7dq1NGjQ4JgKs1K1jPjOmLfcB4aJvXgR9gdv+DskERERqWWSk5OZNWsWAwcO5KabbuLnn3/m4osv5n//+x9fffVVlcZSv359XnvtNbZt28bFF1/M1KlTGTduXOH+DiIiIiJSOzleOTt58mQCAgI488wzycvLY86cOZxzzjk89thj3Hfffbzwwgu89dZbXs0VFBTEiBEjmDx5MrGxsTRt2pRJkybRuHFjBg8ejNvtJjk5mYiICEJCQjj11FOZPHky48aN44477iAnJ4fnn3+e2NhYhg4dimEYDBo0iNdff502bdpwwgknsHTp0sJVCFKzGN37Ylw7BvuNF7A//wArPBLznEv8HZaIiIjUcMuWLePdd9/lq6++wu1207NnT/bs2cO0adM48cQTqyyOTZs2FbnfpUsX3n333Sq7voiIiIj4n+Pi7MqVK3nyySfp3LkzixcvJj09nX/961+Eh4dz+eWXc/vttzuab8yYMRQUFPDggw+Sk5ND7969ef311wkMDGT37t2ceeaZPPXUUwwdOpQmTZrwn//8h+eee44rrrgC27bp168fb775JsHBnk2j7rnnHsLDw3n++efZv38/zZo1Y8KECVx22WVOn6pUA2b/wVgZadjvz8Z+f7anQNt/sL/DEhERkRroP//5D++++y7btm2jZcuW3HrrrVx88cWEhYVx4oknqoWSiIiIiFQ5x8XZ/Px8IiMjAfjhhx8IDQ2lZ8+eALjdbgICnE3pcrkYO3YsY8eOLXauWbNmxVYUdOrUidmzZ5c6X0BAAKNHj2b06NGO4pDqyzznEqz0VOwv5mL/5yXs8AiM7n39HZaIiIjUME8//TTx8fG8+eabRVbIpqen+zEqEREREanLHPecbd++PYsWLeLgwYN88cUXnHLKKQQEBJCfn8///d//0b59+8qIU+o449LrME4ZDLaFNeNp7E1r/R2SiIiI1DBDhgxhx44djBo1iltvvZUvv/ySgoICf4clIiIiInWY45WzY8aMYfTo0fzf//0fQUFB3HjjjQCcddZZJCUl8corr/g8SBHDMOCa27Ez0+GXpVgvPYo57hlo3go2/459OBkjOhbad8IwXf4OV0RERKqh5557joyMDBYsWMCHH37I7bffTkxMDAMHDsQwDLU1EBEREZEq57g4269fPxYsWMDatWvp2rUrTZs2BeCaa66hT58+xMfH+zxIEQDD5cK8eTzW8w/BprVYz46HoGBITQHABoiJwxw+CqNnP7/GKiIiItVTeHg4V1xxBVdccQVbtmxh7ty5LFiwANu2eeCBBxgyZAhDhgyhXbt2/g5VREREROoAx20NAJo3b865555LTk4Ov/76Kzt27OCaa65RYVYqnREYhHn7wxDXCLKzCguzhVKSsKZNxF61xD8BioiISI1x/PHHc9999/H999/z8ssv06ZNG1599VXOP/98LrjgAn+HJyIiIiJ1gOOVswCffPIJzzzzDElJSYXH4uLiuOeee7jooot8FZtIyUJCID+/zCHW2zMxu/dRiwMREREpV0BAAIMGDWLQoEEkJSUxb9485s2b5++wRERERKQOcFyc/eabbxg7dix9+vTh7rvvJi4ujsTERD7++GPuv/9+oqOjGTBgQCWEKnLE5t8hNbnsMclJnnEJXaomJhEREakV4uLiuPHGGwv3VRARERERqUyOi7MzZszg7LPPZsqUKUWODxs2jLvuuouZM2eqOCuVyj5cTmH2b+O0rYeIiIiIiIiI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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "Best val loss: 0.5688 at epoch 30\n", "Best val acc : 90.23% at epoch 30\n" ] } ], "source": [ "# ─────────────────────────────────────────────────────────\n", "# CELL 9 — Plot Training Curves\n", "# ─────────────────────────────────────────────────────────\n", "sns.set_theme(style='darkgrid')\n", "epochs_ran = list(range(1, len(history['train_loss']) + 1))\n", "\n", "fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 5))\n", "fig.suptitle(f'Deepfake Detection — Training Progress ({CONFIG[\"backbone\"]})',\n", " fontsize=15, fontweight='bold')\n", "\n", "ax1.plot(epochs_ran, history['train_loss'], marker='o', label='Train Loss', color='#E74C3C')\n", "ax1.plot(epochs_ran, history['val_loss'], marker='s', label='Val Loss', color='#3498DB')\n", "ax1.set_title('Loss over Epochs')\n", "ax1.set_xlabel('Epoch'); ax1.set_ylabel('Cross-Entropy Loss')\n", "ax1.xaxis.set_major_locator(ticker.MaxNLocator(integer=True))\n", "ax1.legend()\n", "\n", "ax2.plot(epochs_ran, history['val_acc'], marker='^', color='#2ECC71', label='Val Accuracy')\n", "ax2.set_title('Validation Accuracy over Epochs')\n", "ax2.set_xlabel('Epoch'); ax2.set_ylabel('Accuracy (%)')\n", "ax2.set_ylim(0, 100)\n", "ax2.xaxis.set_major_locator(ticker.MaxNLocator(integer=True))\n", "ax2.legend()\n", "\n", "plt.tight_layout()\n", "plt.savefig('training_curves.png', dpi=150, bbox_inches='tight')\n", "plt.show()\n", "best_ep = history['val_loss'].index(min(history['val_loss'])) + 1\n", "print(f'Best val loss: {min(history[\"val_loss\"]):.4f} at epoch {best_ep}')\n", "print(f'Best val acc : {max(history[\"val_acc\"]):.2f}% at epoch {history[\"val_acc\"].index(max(history[\"val_acc\"]))+1}')" ] }, { "cell_type": "code", "execution_count": null, "id": "2c5c6736", "metadata": {}, "outputs": [ { "ename": "NameError", "evalue": "name 'CONFIG' is not defined", "output_type": "error", "traceback": [ "\u001b[31m---------------------------------------------------------------------------\u001b[39m", "\u001b[31mNameError\u001b[39m Traceback (most recent call last)", "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[1]\u001b[39m\u001b[32m, line 21\u001b[39m\n\u001b[32m 18\u001b[39m CLASS_NAMES = {\u001b[32m0\u001b[39m: \u001b[33m'\u001b[39m\u001b[33mfake\u001b[39m\u001b[33m'\u001b[39m, \u001b[32m1\u001b[39m: \u001b[33m'\u001b[39m\u001b[33mreal\u001b[39m\u001b[33m'\u001b[39m}\n\u001b[32m 20\u001b[39m \u001b[38;5;66;03m# Load best model\u001b[39;00m\n\u001b[32m---> \u001b[39m\u001b[32m21\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m os.path.exists(\u001b[43mCONFIG\u001b[49m[\u001b[33m'\u001b[39m\u001b[33mbest_model_path\u001b[39m\u001b[33m'\u001b[39m]):\n\u001b[32m 22\u001b[39m model.load_state_dict(torch.load(CONFIG[\u001b[33m'\u001b[39m\u001b[33mbest_model_path\u001b[39m\u001b[33m'\u001b[39m], map_location=device))\n\u001b[32m 23\u001b[39m \u001b[38;5;28mprint\u001b[39m(\u001b[33mf\u001b[39m\u001b[33m'\u001b[39m\u001b[33m[OK] Loaded best model from \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mCONFIG[\u001b[33m\"\u001b[39m\u001b[33mbest_model_path\u001b[39m\u001b[33m\"\u001b[39m]\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m'\u001b[39m)\n", "\u001b[31mNameError\u001b[39m: name 'CONFIG' is not defined" ] } ], "source": [ "# ─────────────────────────────────────────────────────────\n", "# CELL 10 — 🔎 Single Image Inference\n", "# ─────────────────────────────────────────────────────────\n", "import os, torch, torch.nn as nn\n", "from torchvision import transforms\n", "from facenet_pytorch import fixed_image_standardization\n", "from PIL import Image\n", "\n", "eval_transform = transforms.Compose([\n", " transforms.Resize((299, 299)),\n", " transforms.ToTensor(),\n", " fixed_image_standardization,\n", "])\n", "\n", "try:\n", " CLASS_NAMES = {v: k for k, v in train_dataset_base.class_to_idx.items()}\n", "except NameError:\n", " CLASS_NAMES = {0: 'fake', 1: 'real'}\n", "\n", "# Load best model\n", "if os.path.exists(CONFIG['best_model_path']):\n", " model.load_state_dict(torch.load(CONFIG['best_model_path'], map_location=device))\n", " print(f'[OK] Loaded best model from {CONFIG[\"best_model_path\"]}')\n", "\n", "def predict_image(image_path, model, device):\n", " img = Image.open(image_path).convert('RGB')\n", " tensor = eval_transform(img).unsqueeze(0).to(device)\n", " model.eval()\n", " with torch.no_grad(), autocast(enabled=AMP_ENABLED):\n", " probs = torch.softmax(model(tensor), dim=1)[0]\n", " predicted_idx = probs.argmax().item()\n", " return {\n", " 'label' : CLASS_NAMES[predicted_idx].upper(),\n", " 'confidence' : round(probs[predicted_idx].item(), 4),\n", " 'probabilities': {CLASS_NAMES[i]: round(probs[i].item(), 4) for i in range(len(CLASS_NAMES))},\n", " }\n", "\n", "TEST_IMAGE_PATH = r'C:\\\\Users\\\\SHINJAN\\\\Downloads\\\\deepfake_main\\\\extracted_faces\\\\train\\\\fake\\\\14_06__walking_down_indoor_hall_disgust__8U9ULZDT_f000785.jpg'\n", "if os.path.exists(TEST_IMAGE_PATH):\n", " result = predict_image(TEST_IMAGE_PATH, model, device)\n", " print(f'Verdict: {result[\"label\"]} ({result[\"confidence\"]*100:.1f}%)')\n", " print(f'All probs: {result[\"probabilities\"]}')\n", " img_display = Image.open(TEST_IMAGE_PATH)\n", " color = '#2ECC71' if result['label'] == 'REAL' else '#E74C3C'\n", " plt.figure(figsize=(5, 5))\n", " plt.imshow(img_display)\n", " plt.title(f'{result[\"label\"]} ({result[\"confidence\"]*100:.1f}%)', fontsize=14, color=color, fontweight='bold')\n", " plt.axis('off'); plt.tight_layout(); plt.show()\n", "else:\n", " print(f'[WARN] Change TEST_IMAGE_PATH to a real image path.')" ] }, { "cell_type": "code", "execution_count": null, "id": "ca13a101", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[SAVE] Final model → models\\deepfake_model_final.pt\n", "[SAVE] Best model → models/best_model.pt\n" ] } ], "source": [ "# ─────────────────────────────────────────────────────────\n", "# CELL 11 — 💾 Save Final Model\n", "# ─────────────────────────────────────────────────────────\n", "os.makedirs('models', exist_ok=True)\n", "final_path = os.path.join('models', CONFIG['model_save_path'])\n", "torch.save(model.state_dict(), final_path)\n", "print(f'[SAVE] Final model → {final_path}')\n", "print(f'[SAVE] Best model → {CONFIG[\"best_model_path\"]}')" ] }, { "cell_type": "markdown", "id": "deploy_intro", "metadata": {}, "source": [ "---\n", "# 🚀 Deployment — Real-Time Interview Deepfake Detection\n", "Run these cells **after training is complete** to export the model and spin up the API server.\n", "\n", "| Cell | Action |\n", "|---|---|\n", "| Cell D1 | Export weights |\n", "| Cell D2 | Write `server/main.py` (FastAPI) |\n", "| Cell D3 | Write `server/requirements_server.txt` |\n", "| Cell D4 | Start API server |\n", "| Cell D5 | Quick smoke-test |\n" ] }, { "cell_type": "code", "execution_count": null, "id": "deploy_d1_export", "metadata": {}, "outputs": [], "source": [ "# ─────────────────────────────────────────────────────────\n", "# CELL D1 — Export Trained Model\n", "# ─────────────────────────────────────────────────────────\n", "import os, torch\n", "\n", "os.makedirs('models', exist_ok=True)\n", "\n", "# Load best weights\n", "model.load_state_dict(torch.load(CONFIG['best_model_path'], map_location='cpu'))\n", "model.eval()\n", "\n", "# Save plain weights (used by server)\n", "DEPLOY_WEIGHTS = 'models/deepfake_model_weights.pt'\n", "torch.save(model.state_dict(), DEPLOY_WEIGHTS)\n", "print(f'[OK] Weights saved → {DEPLOY_WEIGHTS}')\n", "\n", "# Try TorchScript (optional — falls back gracefully)\n", "try:\n", " scripted = torch.jit.script(model.cpu())\n", " scripted.save('models/deepfake_model_scripted.pt')\n", " print('[OK] TorchScript saved → models/deepfake_model_scripted.pt')\n", "except Exception as e:\n", " print(f'[SKIP] TorchScript failed ({e}) — plain weights are fine')\n", "\n", "print('\\n✅ Export complete. Run Cell D2 to create the API server.')" ] }, { "cell_type": "code", "execution_count": null, "id": "deploy_d2_server", "metadata": {}, "outputs": [], "source": [ "# ─────────────────────────────────────────────────────────\n", "# CELL D2 — Write FastAPI Server (server/main.py)\n", "# ─────────────────────────────────────────────────────────\n", "import os\n", "\n", "os.makedirs('server', exist_ok=True)\n", "\n", "SERVER_CODE = '''\"\"\"\n", "Deepfake Detection API — real-time interview frame analysis\n", "Run: uvicorn main:app --host 0.0.0.0 --port 8000\n", "\"\"\"\n", "\n", "import io, base64, time\n", "import torch\n", "import torch.nn as nn\n", "import numpy as np\n", "from PIL import Image\n", "from fastapi import FastAPI, HTTPException\n", "from fastapi.middleware.cors import CORSMiddleware\n", "from pydantic import BaseModel\n", "from torchvision import transforms\n", "from facenet_pytorch import InceptionResnetV1, fixed_image_standardization, MTCNN\n", "\n", "WEIGHTS_PATH = \"../models/deepfake_model_weights.pt\"\n", "DEVICE = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n", "IMG_SIZE = 299\n", "CONF_THRESH = 0.60\n", "print(f\"[SERVER] Device: {DEVICE}\")\n", "\n", "class DeepfakeClassifier(nn.Module):\n", " def __init__(self):\n", " super().__init__()\n", " self.backbone = InceptionResnetV1(classify=False, pretrained=\\'vggface2\\').to(DEVICE)\n", " self.head = nn.Sequential(\n", " nn.Linear(512, 256), nn.BatchNorm1d(256), nn.GELU(), nn.Dropout(0.4),\n", " nn.Linear(256, 128), nn.BatchNorm1d(128), nn.GELU(), nn.Dropout(0.3),\n", " nn.Linear(128, 2)\n", " ).to(DEVICE)\n", " def forward(self, x):\n", " return self.head(self.backbone(x))\n", "\n", "model = DeepfakeClassifier()\n", "model.load_state_dict(torch.load(WEIGHTS_PATH, map_location=DEVICE))\n", "model.eval()\n", "print(f\"[SERVER] Model loaded\")\n", "\n", "mtcnn = MTCNN(image_size=IMG_SIZE, keep_all=False, min_face_size=40,\n", " device=DEVICE, post_process=False, margin=20)\n", "\n", "transform = transforms.Compose([\n", " transforms.Resize((IMG_SIZE, IMG_SIZE)),\n", " transforms.ToTensor(),\n", " fixed_image_standardization,\n", "])\n", "\n", "CLASS_NAMES = {0: \"FAKE\", 1: \"REAL\"}\n", "\n", "app = FastAPI(title=\"Deepfake Detection API\", version=\"1.0\")\n", "app.add_middleware(CORSMiddleware, allow_origins=[\"*\"],\n", " allow_methods=[\"*\"], allow_headers=[\"*\"])\n", "\n", "class FrameRequest(BaseModel):\n", " image_b64: str\n", "\n", "class PredictionResponse(BaseModel):\n", " label: str\n", " confidence: float\n", " uncertain: bool\n", " face_detected: bool\n", " latency_ms: float\n", "\n", "@app.get(\"/health\")\n", "def health():\n", " return {\"status\": \"ok\", \"device\": str(DEVICE)}\n", "\n", "@app.post(\"/predict\", response_model=PredictionResponse)\n", "def predict(req: FrameRequest):\n", " t0 = time.perf_counter()\n", " try:\n", " img = Image.open(io.BytesIO(base64.b64decode(req.image_b64))).convert(\"RGB\")\n", " except Exception as e:\n", " raise HTTPException(status_code=400, detail=f\"Bad image: {e}\")\n", " face_tensor = mtcnn(img)\n", " if face_tensor is None:\n", " return PredictionResponse(label=\"UNKNOWN\", confidence=0.0,\n", " uncertain=True, face_detected=False,\n", " latency_ms=round((time.perf_counter()-t0)*1000,1))\n", " face_pil = Image.fromarray(face_tensor.permute(1,2,0).byte().cpu().numpy())\n", " inp = transform(face_pil).unsqueeze(0).to(DEVICE)\n", " with torch.no_grad():\n", " probs = torch.softmax(model(inp), dim=1)[0]\n", " pred = int(torch.argmax(probs))\n", " conf = float(probs[pred])\n", " return PredictionResponse(\n", " label=CLASS_NAMES[pred], confidence=round(conf,3),\n", " uncertain=(conf < CONF_THRESH), face_detected=True,\n", " latency_ms=round((time.perf_counter()-t0)*1000,1)\n", " )\n", "'''\n", "\n", "with open('server/main.py', 'w') as f:\n", " f.write(SERVER_CODE.strip())\n", "print('[OK] server/main.py written')" ] }, { "cell_type": "code", "execution_count": null, "id": "deploy_d3_reqs", "metadata": {}, "outputs": [], "source": [ "# ─────────────────────────────────────────────────────────\n", "# CELL D3 — Write server/requirements_server.txt\n", "# & install server dependencies\n", "# ─────────────────────────────────────────────────────────\n", "import subprocess, sys\n", "\n", "REQS = \"\"\"fastapi>=0.110.0\n", "uvicorn[standard]>=0.29.0\n", "python-multipart>=0.0.9\n", "Pillow>=10.2.0\n", "torch>=2.2.0\n", "torchvision>=0.17.0\n", "facenet-pytorch>=2.6.0\n", "numpy>=1.26.0\n", "\"\"\"\n", "\n", "with open('server/requirements_server.txt', 'w') as f:\n", " f.write(REQS)\n", "print('[OK] server/requirements_server.txt written')\n", "\n", "SERVER_PKGS = ['fastapi', 'uvicorn[standard]', 'python-multipart']\n", "print('Installing server packages...')\n", "subprocess.check_call([sys.executable, '-m', 'pip', 'install', '--quiet'] + SERVER_PKGS)\n", "print('[OK] Server packages installed')" ] }, { "cell_type": "code", "execution_count": null, "id": "deploy_d4_run", "metadata": {}, "outputs": [], "source": [ "# ─────────────────────────────────────────────────────────\n", "# CELL D4 — Start API Server\n", "#\n", "# This starts uvicorn in the background so this cell\n", "# returns immediately. The server keeps running until\n", "# the kernel is shut down.\n", "#\n", "# API will be available at:\n", "# http://localhost:8000/health\n", "# http://localhost:8000/docs ← Swagger UI\n", "# http://localhost:8000/predict\n", "# ─────────────────────────────────────────────────────────\n", "import subprocess, os, sys, time\n", "\n", "server_dir = os.path.join(os.getcwd(), 'server')\n", "log_file = open('server/uvicorn.log', 'w')\n", "\n", "proc = subprocess.Popen(\n", " [sys.executable, '-m', 'uvicorn', 'main:app',\n", " '--host', '0.0.0.0', '--port', '8000'],\n", " cwd=server_dir,\n", " stdout=log_file,\n", " stderr=subprocess.STDOUT\n", ")\n", "\n", "time.sleep(3) # give server time to start\n", "\n", "if proc.poll() is None:\n", " print(f'[OK] Server running (PID {proc.pid})')\n", " print(' Health : http://localhost:8000/health')\n", " print(' Docs : http://localhost:8000/docs')\n", " print(' Predict: POST http://localhost:8000/predict')\n", " print('\\n[INFO] To stop: proc.terminate()')\n", "else:\n", " print('[ERROR] Server failed to start. Check server/uvicorn.log')\n", " with open('server/uvicorn.log') as lf:\n", " print(lf.read())" ] }, { "cell_type": "code", "execution_count": null, "id": "deploy_d5_test", "metadata": {}, "outputs": [], "source": [ "# ─────────────────────────────────────────────────────────\n", "# CELL D5 — Smoke Test: send a webcam frame to the API\n", "#\n", "# Captures one frame from your webcam, sends it to the\n", "# running server, and prints the prediction.\n", "# ─────────────────────────────────────────────────────────\n", "import requests, base64, io, cv2\n", "from PIL import Image\n", "\n", "API_URL = 'http://localhost:8000/predict'\n", "\n", "# ── Capture one frame from webcam ─────────────────────────\n", "cap = cv2.VideoCapture(0)\n", "ret, frame = cap.read()\n", "cap.release()\n", "\n", "if not ret:\n", " print('[WARN] No webcam found — using a blank test image instead')\n", " img = Image.new('RGB', (640, 480), color=(128,128,128))\n", "else:\n", " img = Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))\n", "\n", "# ── Encode to base64 ──────────────────────────────────────\n", "buf = io.BytesIO()\n", "img.save(buf, format='JPEG', quality=80)\n", "b64 = base64.b64encode(buf.getvalue()).decode()\n", "\n", "# ── Call API ──────────────────────────────────────────────\n", "resp = requests.post(API_URL, json={'image_b64': b64}, timeout=10)\n", "data = resp.json()\n", "\n", "print(f\"Label : {data['label']}\")\n", "print(f\"Confidence : {data['confidence']*100:.1f}%\")\n", "print(f\"Face found : {data['face_detected']}\")\n", "print(f\"Latency : {data['latency_ms']} ms\")\n", "print(f\"Uncertain : {data['uncertain']}\")\n", "if data['label'] == 'REAL':\n", " print('\\n✅ REAL person detected')\n", "elif data['label'] == 'FAKE':\n", " print('\\n🚨 DEEPFAKE detected!')\n", "else:\n", " print('\\n⚠️ No face detected in frame')" ] }, { "cell_type": "code", "execution_count": null, "id": "test_image_video_cell", "metadata": {}, "outputs": [ { "ename": "ModuleNotFoundError", "evalue": "No module named 'realtime_deepfake_detector'", "output_type": "error", "traceback": [ "\u001b[31m---------------------------------------------------------------------------\u001b[39m", "\u001b[31mModuleNotFoundError\u001b[39m Traceback (most recent call last)", "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[1]\u001b[39m\u001b[32m, line 8\u001b[39m\n\u001b[32m 6\u001b[39m \u001b[38;5;28;01mimport\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01mmatplotlib\u001b[39;00m\u001b[34;01m.\u001b[39;00m\u001b[34;01mpyplot\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mas\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01mplt\u001b[39;00m\n\u001b[32m 7\u001b[39m \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01mIPython\u001b[39;00m\u001b[34;01m.\u001b[39;00m\u001b[34;01mdisplay\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mimport\u001b[39;00m display, clear_output\n\u001b[32m----> \u001b[39m\u001b[32m8\u001b[39m \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01mrealtime_deepfake_detector\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mimport\u001b[39;00m RealtimeDeepfakeDetector\n\u001b[32m 10\u001b[39m device = torch.device(\u001b[33m'\u001b[39m\u001b[33mcuda\u001b[39m\u001b[33m'\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m torch.cuda.is_available() \u001b[38;5;28;01melse\u001b[39;00m \u001b[33m'\u001b[39m\u001b[33mcpu\u001b[39m\u001b[33m'\u001b[39m)\n\u001b[32m 12\u001b[39m \u001b[38;5;66;03m# 1. Provide a path to an Image or Video\u001b[39;00m\n", "\u001b[31mModuleNotFoundError\u001b[39m: No module named 'realtime_deepfake_detector'" ] } ], "source": [ "# ───────────────────────────────────────────────────────\n", "# CELL 12 — Test Custom Image / Video\n", "# ───────────────────────────────────────────────────────\n", "import torch\n", "import cv2\n", "import matplotlib.pyplot as plt\n", "from IPython.display import display, clear_output\n", "from realtime_deepfake_detector import RealtimeDeepfakeDetector\n", "\n", "device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n", "\n", "# 1. Provide a path to an Image or Video\n", "target_path = r'C:\\\\Users\\\\SHINJAN\\\\Downloads\\\\WhatsApp Video 2026-04-07 at 11.37.53 AM.mp4'\n", "# target_path = r'C:\\Path\\To\\Your\\Video.mp4' # <-- Uncomment to test a video\n", "\n", "# 2. Load the trained model\n", "model_path = r'models/best_model.pt'\n", "detector = RealtimeDeepfakeDetector(model_path=model_path, device=device, threshold=0.60)\n", "\n", "cap = cv2.VideoCapture(int(target_path) if target_path.isdigit() else target_path)\n", "if not cap.isOpened():\n", " print(f\"Error: Could not open {target_path}\")\n", "else:\n", " frame_count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))\n", " \n", " if frame_count <= 1:\n", " # It's a single image\n", " ret, frame = cap.read()\n", " if ret:\n", " annotated_frame = detector.process_frame(frame) # Detects face, crops, and analyzes\n", " plt.figure(figsize=(8, 6))\n", " plt.imshow(cv2.cvtColor(annotated_frame, cv2.COLOR_BGR2RGB))\n", " plt.axis('off')\n", " plt.title(\"Image Analysis\")\n", " plt.show()\n", " else:\n", " # It's a video file, process a few frames as preview (or loop via script instead)\n", " print(f\"Video loaded. Total frames: {frame_count}\")\n", " frames_to_show = 5 # Adjust to show more frames\n", " \n", " for i in range(frames_to_show):\n", " ret, frame = cap.read()\n", " if not ret: break\n", " \n", " annotated_frame = detector.process_frame(frame)\n", " \n", " clear_output(wait=True)\n", " plt.figure(figsize=(8, 6))\n", " plt.imshow(cv2.cvtColor(annotated_frame, cv2.COLOR_BGR2RGB))\n", " plt.axis('off')\n", " plt.title(f\"Video Analysis - Frame {i+1}\")\n", " plt.show()\n", " print(f\"Processing frame {i+1} of {frames_to_show}...\")\n", "\n", " cap.release()\n" ] } ], "metadata": { "kernelspec": { "display_name": "deepfake_gpu", "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.12.13" } }, "nbformat": 4, "nbformat_minor": 5 }