{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"name":"python","version":"3.12.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[],"dockerImageVersionId":28755,"isGpuEnabled":false,"isInternetEnabled":false,"language":"python","sourceType":"notebook"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# HW2P2 Kaggle Baseline\n\nThis notebook adds a clean Kaggle pipeline for the face verification assignment: paths, transforms, datasets, model, training loop, embedding extraction, validation EER, and submission generation.","metadata":{}},{"cell_type":"code","source":"import os, random, math, time, gc\nfrom pathlib import Path\nimport numpy as np\nimport pandas as pd\nfrom PIL import Image\nfrom tqdm.auto import tqdm\n\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.utils.data import DataLoader, Dataset\nfrom torchvision import transforms, datasets, models\n\nSEED = 42\nrandom.seed(SEED); np.random.seed(SEED); torch.manual_seed(SEED); torch.cuda.manual_seed_all(SEED)\ntorch.backends.cudnn.benchmark = True\nDEVICE = 'cuda' if torch.cuda.is_available() else 'cpu'\nprint('Device:', DEVICE)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-08T16:04:59.295686Z","iopub.execute_input":"2026-06-08T16:04:59.29594Z","iopub.status.idle":"2026-06-08T16:05:08.548127Z","shell.execute_reply.started":"2026-06-08T16:04:59.295917Z","shell.execute_reply":"2026-06-08T16:05:08.547091Z"}},"outputs":[{"name":"stdout","text":"Device: cuda\n","output_type":"stream"}],"execution_count":1},{"cell_type":"code","source":"from pathlib import Path\n\nBASE = Path('/kaggle/input/competitions/11785-hw-2-p-2-face-verification-fall-2024')\nVER_DIR = BASE / '11-785-f24-hw2p2-verification'\n\nCLS_DIR = VER_DIR / 'cls_data'\nTRAIN_DIR = CLS_DIR / 'train'\nDEV_DIR = CLS_DIR / 'dev'\n\nSAMPLE_SUB = VER_DIR / 'sample_submission.csv'\nVAL_PAIRS = VER_DIR / 'val_pairs.txt'\nTEST_PAIRS = VER_DIR / 'test_pairs.txt'\nVER_DATA_DIR = VER_DIR / 'ver_data'\n\nprint('TRAIN_DIR:', TRAIN_DIR.exists(), TRAIN_DIR)\nprint('DEV_DIR:', DEV_DIR.exists(), DEV_DIR)\nprint('VER_DATA_DIR:', VER_DATA_DIR.exists(), VER_DATA_DIR)\nprint('SAMPLE_SUB:', SAMPLE_SUB.exists())\nprint('VAL_PAIRS:', VAL_PAIRS.exists())\nprint('TEST_PAIRS:', TEST_PAIRS.exists())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-08T16:44:39.78933Z","iopub.execute_input":"2026-06-08T16:44:39.789696Z","iopub.status.idle":"2026-06-08T16:44:39.80556Z","shell.execute_reply.started":"2026-06-08T16:44:39.78967Z","shell.execute_reply":"2026-06-08T16:44:39.804609Z"}},"outputs":[{"name":"stdout","text":"TRAIN_DIR: True /kaggle/input/competitions/11785-hw-2-p-2-face-verification-fall-2024/11-785-f24-hw2p2-verification/cls_data/train\nDEV_DIR: True /kaggle/input/competitions/11785-hw-2-p-2-face-verification-fall-2024/11-785-f24-hw2p2-verification/cls_data/dev\nVER_DATA_DIR: True /kaggle/input/competitions/11785-hw-2-p-2-face-verification-fall-2024/11-785-f24-hw2p2-verification/ver_data\nSAMPLE_SUB: True\nVAL_PAIRS: True\nTEST_PAIRS: True\n","output_type":"stream"}],"execution_count":7},{"cell_type":"code","source":"IMG_SIZE = 112\nBATCH_SIZE = 128\nNUM_WORKERS = 2\n\ntrain_tfms = transforms.Compose([\n    transforms.RandomResizedCrop(IMG_SIZE, scale=(0.85, 1.0)),\n    transforms.RandomHorizontalFlip(p=0.5),\n    transforms.ColorJitter(brightness=0.15, contrast=0.15, saturation=0.10, hue=0.02),\n    transforms.ToTensor(),\n    transforms.Normalize(mean=[0.5,0.5,0.5], std=[0.5,0.5,0.5]),\n    transforms.RandomErasing(p=0.15, scale=(0.02, 0.12), ratio=(0.3, 3.3)),\n])\n\nval_tfms = transforms.Compose([\n    transforms.Resize((IMG_SIZE, IMG_SIZE)),\n    transforms.ToTensor(),\n    transforms.Normalize(mean=[0.5,0.5,0.5], std=[0.5,0.5,0.5]),\n])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-08T16:47:32.62552Z","iopub.execute_input":"2026-06-08T16:47:32.62583Z","iopub.status.idle":"2026-06-08T16:47:32.632609Z","shell.execute_reply.started":"2026-06-08T16:47:32.625803Z","shell.execute_reply":"2026-06-08T16:47:32.631697Z"}},"outputs":[],"execution_count":8},{"cell_type":"code","source":"# Classification datasets\n# If this cell errors, print BASE.rglob output and set TRAIN_DIR/DEV_DIR manually.\ntrain_ds = datasets.ImageFolder(TRAIN_DIR, transform=train_tfms)\ndev_ds = datasets.ImageFolder(DEV_DIR, transform=val_tfms) if DEV_DIR is not None else None\n\nnum_classes = len(train_ds.classes)\nprint('Train images:', len(train_ds), 'Classes:', num_classes)\nif dev_ds: print('Dev images:', len(dev_ds))\n\ntrain_loader = DataLoader(train_ds, batch_size=BATCH_SIZE, shuffle=True, num_workers=NUM_WORKERS, pin_memory=True, drop_last=True)\ndev_loader = DataLoader(dev_ds, batch_size=BATCH_SIZE, shuffle=False, num_workers=NUM_WORKERS, pin_memory=True) if dev_ds else None","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-08T16:47:36.518371Z","iopub.execute_input":"2026-06-08T16:47:36.519101Z","iopub.status.idle":"2026-06-08T16:54:05.833206Z","shell.execute_reply.started":"2026-06-08T16:47:36.519071Z","shell.execute_reply":"2026-06-08T16:54:05.83248Z"}},"outputs":[{"name":"stdout","text":"Train images: 431550 Classes: 8631\nDev images: 43155\n","output_type":"stream"}],"execution_count":9},{"cell_type":"code","source":"from torchvision.models import resnet18, ResNet18_Weights\nimport torch.nn as nn\nimport torch.nn.functional as F\n\nclass FaceNet(nn.Module):\n    def __init__(self, num_classes, emb_dim=512):\n        super().__init__()\n\n        self.backbone = resnet18(weights=ResNet18_Weights.IMAGENET1K_V1)\n\n        in_features = self.backbone.fc.in_features\n        self.backbone.fc = nn.Identity()\n\n        self.embedding = nn.Sequential(\n            nn.Linear(in_features, emb_dim),\n            nn.BatchNorm1d(emb_dim),\n            nn.ReLU(inplace=True),\n            nn.Dropout(0.2)\n        )\n\n        self.classifier = nn.Linear(emb_dim, num_classes)\n\n    def forward(self, x, return_embedding=False):\n        features = self.backbone(x)\n        emb = self.embedding(features)\n        emb = F.normalize(emb, p=2, dim=1)\n\n        if return_embedding:\n            return emb\n\n        logits = self.classifier(emb)\n        return logits, emb\n\n\nmodel = FaceNet(num_classes=num_classes, emb_dim=512).to(DEVICE)\nprint(\"Model loaded:\", model.__class__.__name__)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-08T17:51:07.262478Z","iopub.execute_input":"2026-06-08T17:51:07.263106Z","iopub.status.idle":"2026-06-08T17:51:07.523141Z","shell.execute_reply.started":"2026-06-08T17:51:07.263071Z","shell.execute_reply":"2026-06-08T17:51:07.522459Z"}},"outputs":[{"name":"stdout","text":"Model loaded: FaceNet\n","output_type":"stream"}],"execution_count":26},{"cell_type":"code","source":"BATCH_SIZE = 256\nEPOCHS = 3\nLR = 1e-3\n\ncriterion = nn.CrossEntropyLoss(label_smoothing=0.05)\noptimizer = torch.optim.AdamW(model.parameters(), lr=LR, weight_decay=WEIGHT_DECAY)\nscheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=EPOCHS)\nscaler = torch.cuda.amp.GradScaler(enabled=(DEVICE=='cuda'))\n\ndef accuracy(logits, y):\n    return (logits.argmax(1) == y).float().mean().item()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-08T17:51:11.458019Z","iopub.execute_input":"2026-06-08T17:51:11.458506Z","iopub.status.idle":"2026-06-08T17:51:11.464972Z","shell.execute_reply.started":"2026-06-08T17:51:11.458475Z","shell.execute_reply":"2026-06-08T17:51:11.464124Z"}},"outputs":[{"name":"stderr","text":"/tmp/ipykernel_58/4260799221.py:8: FutureWarning: `torch.cuda.amp.GradScaler(args...)` is deprecated. Please use `torch.amp.GradScaler('cuda', args...)` instead.\n  scaler = torch.cuda.amp.GradScaler(enabled=(DEVICE=='cuda'))\n","output_type":"stream"}],"execution_count":27},{"cell_type":"code","source":"def train_one_epoch(epoch):\n    model.train()\n    total_loss, total_acc, n = 0.0, 0.0, 0\n    pbar = tqdm(train_loader, desc=f'Train epoch {epoch}')\n    for x, y in pbar:\n        x, y = x.to(DEVICE, non_blocking=True), y.to(DEVICE, non_blocking=True)\n        optimizer.zero_grad(set_to_none=True)\n        with torch.cuda.amp.autocast(enabled=(DEVICE=='cuda')):\n            logits, emb = model(x)\n            loss = criterion(logits, y)\n        scaler.scale(loss).backward()\n        scaler.unscale_(optimizer)\n        torch.nn.utils.clip_grad_norm_(model.parameters(), 5.0)\n        scaler.step(optimizer)\n        scaler.update()\n        bs = x.size(0)\n        total_loss += loss.item() * bs\n        total_acc += accuracy(logits.detach(), y) * bs\n        n += bs\n        pbar.set_postfix(loss=total_loss/n, acc=total_acc/n)\n    return total_loss/n, total_acc/n\n\n@torch.no_grad()\ndef evaluate_cls():\n    if dev_loader is None:\n        return None, None\n    model.eval()\n    total_loss, total_acc, n = 0.0, 0.0, 0\n    for x, y in tqdm(dev_loader, desc='Dev eval'):\n        x, y = x.to(DEVICE, non_blocking=True), y.to(DEVICE, non_blocking=True)\n        logits, emb = model(x)\n        loss = criterion(logits, y)\n        bs = x.size(0)\n        total_loss += loss.item() * bs\n        total_acc += accuracy(logits, y) * bs\n        n += bs\n    return total_loss/n, total_acc/n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-08T17:51:16.929798Z","iopub.execute_input":"2026-06-08T17:51:16.930709Z","iopub.status.idle":"2026-06-08T17:51:16.939627Z","shell.execute_reply.started":"2026-06-08T17:51:16.930675Z","shell.execute_reply":"2026-06-08T17:51:16.938556Z"}},"outputs":[],"execution_count":30},{"cell_type":"code","source":"best_acc = 0.0\nfor epoch in range(1, EPOCHS+1):\n    tr_loss, tr_acc = train_one_epoch(epoch)\n    dv_loss, dv_acc = evaluate_cls()\n    scheduler.step()\n    print(f'Epoch {epoch}: train loss {tr_loss:.4f}, train acc {tr_acc:.4f}, dev loss {dv_loss}, dev acc {dv_acc}')\n    score = dv_acc if dv_acc is not None else tr_acc\n    if score > best_acc:\n        best_acc = score\n        torch.save({'model': model.state_dict(), 'classes': train_ds.classes}, '/kaggle/working/best_model.pth')\n        print('Saved best_model.pth')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-08T17:51:20.287692Z","iopub.execute_input":"2026-06-08T17:51:20.288539Z"}},"outputs":[{"output_type":"display_data","data":{"text/plain":"Train epoch 1:   0%|          | 0/3371 [00:00<?, ?it/s]","application/vnd.jupyter.widget-view+json":{"version_major":2,"version_minor":0,"model_id":"79caa06c33104553980c35097bd24a6b"}},"metadata":{}},{"name":"stderr","text":"/tmp/ipykernel_58/4055445374.py:8: FutureWarning: `torch.cuda.amp.autocast(args...)` is deprecated. Please use `torch.amp.autocast('cuda', args...)` instead.\n  with torch.cuda.amp.autocast(enabled=(DEVICE=='cuda')):\n","output_type":"stream"},{"output_type":"display_data","data":{"text/plain":"Dev eval:   0%|          | 0/338 [00:00<?, ?it/s]","application/vnd.jupyter.widget-view+json":{"version_major":2,"version_minor":0,"model_id":"79712165802443ed87c029b61f049ac7"}},"metadata":{}},{"name":"stdout","text":"Epoch 1: train loss 8.0365, train acc 0.0032, dev loss 7.195035575430911, dev acc 0.013717993280037076\nSaved best_model.pth\n","output_type":"stream"},{"output_type":"display_data","data":{"text/plain":"Train epoch 2:   0%|          | 0/3371 [00:00<?, ?it/s]","application/vnd.jupyter.widget-view+json":{"version_major":2,"version_minor":0,"model_id":"784262cec11e4e3e88f4966a7e55d3b5"}},"metadata":{}},{"name":"stderr","text":"Exception ignored in: <function _MultiProcessingDataLoaderIter.__del__ at 0x7e44d175f9c0>\nException ignored in: Traceback (most recent call last):\n  File \"/usr/local/lib/python3.12/dist-packages/torch/utils/data/dataloader.py\", line 1707, in __del__\n<function _MultiProcessingDataLoaderIter.__del__ at 0x7e44d175f9c0>\n    self._shutdown_workers()Traceback (most recent call last):\n\n  File \"/usr/local/lib/python3.12/dist-packages/torch/utils/data/dataloader.py\", line 1690, in _shutdown_workers\n  File \"/usr/local/lib/python3.12/dist-packages/torch/utils/data/dataloader.py\", line 1707, in __del__\n        if w.is_alive():self._shutdown_workers()\n\n  File \"/usr/local/lib/python3.12/dist-packages/torch/utils/data/dataloader.py\", line 1690, in _shutdown_workers\n    if w.is_alive():  \n          ^^ ^ ^^^^^^^^^^^^^^^^^^\n^  File \"/usr/lib/python3.12/multiprocessing/process.py\", line 160, in is_alive\n^    ^assert self._parent_pid == os.getpid(), 'can only test a child process'\n\n  File \"/usr/lib/python3.12/multiprocessing/process.py\", line 160, in is_alive\n       assert self._parent_pid == os.getpid(), 'can only test a child process' \n                ^ ^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\nAssertionError^: can only test a child process^\n^\nAssertionError: can only test a child process\nException ignored in: <function _MultiProcessingDataLoaderIter.__del__ at 0x7e44d175f9c0>\nException ignored in: <function _MultiProcessingDataLoaderIter.__del__ at 0x7e44d175f9c0>Traceback (most recent call last):\n  File \"/usr/local/lib/python3.12/dist-packages/torch/utils/data/dataloader.py\", line 1707, in __del__\n\nTraceback (most recent call last):\n      File \"/usr/local/lib/python3.12/dist-packages/torch/utils/data/dataloader.py\", line 1707, in __del__\nself._shutdown_workers()    \nself._shutdown_workers()\n  File \"/usr/local/lib/python3.12/dist-packages/torch/utils/data/dataloader.py\", line 1690, in _shutdown_workers\n  File \"/usr/local/lib/python3.12/dist-packages/torch/utils/data/dataloader.py\", line 1690, in _shutdown_workers\n    if w.is_alive():\n    if w.is_alive():  \n          ^ ^ ^^^^^^^^^^^^^^^^^^^^\n^^  File \"/usr/lib/python3.12/multiprocessing/process.py\", line 160, in is_alive\n    \nassert self._parent_pid == os.getpid(), 'can only test a child process'  File \"/usr/lib/python3.12/multiprocessing/process.py\", line 160, in is_alive\n\n      assert self._parent_pid == os.getpid(), 'can only test a child process' \n                 ^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\nAssertionError^: can only test a child process^\n^\nAssertionError: can only test a child process\n","output_type":"stream"},{"output_type":"display_data","data":{"text/plain":"Dev eval:   0%|          | 0/338 [00:00<?, ?it/s]","application/vnd.jupyter.widget-view+json":{"version_major":2,"version_minor":0,"model_id":"a97e45d03b44493e8425418aab43bed4"}},"metadata":{}},{"name":"stdout","text":"Epoch 2: train loss 6.8574, train acc 0.0264, dev loss 6.32791548177467, dev acc 0.04961186421040436\nSaved best_model.pth\n","output_type":"stream"},{"output_type":"display_data","data":{"text/plain":"Train epoch 3:   0%|          | 0/3371 [00:00<?, ?it/s]","application/vnd.jupyter.widget-view+json":{"version_major":2,"version_minor":0,"model_id":"9f9e45a90dd040a3aada899c625a201d"}},"metadata":{}},{"name":"stderr","text":"Exception ignored in: <function _MultiProcessingDataLoaderIter.__del__ at 0x7e44d175f9c0>\nTraceback (most recent call last):\n  File \"/usr/local/lib/python3.12/dist-packages/torch/utils/data/dataloader.py\", line 1707, in __del__\n    self._shutdown_workers()\n  File \"/usr/local/lib/python3.12/dist-packages/torch/utils/data/dataloader.py\", line 1690, in _shutdown_workers\n    if w.is_alive():\nException ignored in: <function _MultiProcessingDataLoaderIter.__del__ at 0x7e44d175f9c0>  \n Traceback (most recent call last):\n   File \"/usr/local/lib/python3.12/dist-packages/torch/utils/data/dataloader.py\", line 1707, in __del__\n     self._shutdown_workers() \n  File \"/usr/local/lib/python3.12/dist-packages/torch/utils/data/dataloader.py\", line 1690, in _shutdown_workers\n ^    if w.is_alive():^\n^ ^^  ^^ ^  ^ ^^^^^^^^\n  File \"/usr/lib/python3.12/multiprocessing/process.py\", line 160, in is_alive\n^    ^assert self._parent_pid == os.getpid(), 'can only test a child process'^\n^^ ^ ^ \n  File \"/usr/lib/python3.12/multiprocessing/process.py\", line 160, in is_alive\n      assert self._parent_pid == os.getpid(), 'can only test a child process' \n           ^ ^ ^ ^ ^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n^AssertionError^^: ^can only test a child process^^\n\nAssertionError: can only test a child process\nException ignored in: <function _MultiProcessingDataLoaderIter.__del__ at 0x7e44d175f9c0>\nTraceback (most recent call last):\n  File \"/usr/local/lib/python3.12/dist-packages/torch/utils/data/dataloader.py\", line 1707, in __del__\nException ignored in:     <function _MultiProcessingDataLoaderIter.__del__ at 0x7e44d175f9c0>self._shutdown_workers()\n\n  File \"/usr/local/lib/python3.12/dist-packages/torch/utils/data/dataloader.py\", line 1690, in _shutdown_workers\nTraceback (most recent call last):\n  File \"/usr/local/lib/python3.12/dist-packages/torch/utils/data/dataloader.py\", line 1707, in __del__\n        if w.is_alive():\nself._shutdown_workers() \n   File \"/usr/local/lib/python3.12/dist-packages/torch/utils/data/dataloader.py\", line 1690, in _shutdown_workers\n      if w.is_alive():  \n  ^ ^ ^^ ^^ ^^ ^ ^^^^^^^\n^  File \"/usr/lib/python3.12/multiprocessing/process.py\", line 160, in is_alive\n^    assert self._parent_pid == os.getpid(), 'can only test a child process'^\n^ ^^ ^ ^ \n   File \"/usr/lib/python3.12/multiprocessing/process.py\", line 160, in is_alive\n      assert self._parent_pid == os.getpid(), 'can only test a child process' \n     ^  ^ ^  ^ ^ ^^ ^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n^AssertionError^: ^can only test a child process^\n^^Exception ignored in: ^^\n<function _MultiProcessingDataLoaderIter.__del__ at 0x7e44d175f9c0>AssertionError\n: can only test a child processTraceback (most recent call last):\n  File \"/usr/local/lib/python3.12/dist-packages/torch/utils/data/dataloader.py\", line 1707, in __del__\n\n    Exception ignored in: self._shutdown_workers()\n<function _MultiProcessingDataLoaderIter.__del__ at 0x7e44d175f9c0>  File \"/usr/local/lib/python3.12/dist-packages/torch/utils/data/dataloader.py\", line 1690, in _shutdown_workers\n\n    if w.is_alive():Traceback (most recent call last):\n\n  File \"/usr/local/lib/python3.12/dist-packages/torch/utils/data/dataloader.py\", line 1707, in __del__\n      self._shutdown_workers()\n   File \"/usr/local/lib/python3.12/dist-packages/torch/utils/data/dataloader.py\", line 1690, in _shutdown_workers\n      if w.is_alive(): \n   ^ ^  ^ ^ ^^^^^^^^^^^^^^^^^^^\n^  File \"/usr/lib/python3.12/multiprocessing/process.py\", line 160, in is_alive\n\n      File \"/usr/lib/python3.12/multiprocessing/process.py\", line 160, in is_alive\nassert self._parent_pid == os.getpid(), 'can only test a child process'    \nassert self._parent_pid == os.getpid(), 'can only test a child process'\n                     ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n^^AssertionError\n: AssertionErrorcan only test a child process\n: can only test a child process\nException ignored in: <function _MultiProcessingDataLoaderIter.__del__ at 0x7e44d175f9c0>Exception ignored in: \n<function _MultiProcessingDataLoaderIter.__del__ at 0x7e44d175f9c0>Traceback (most recent call last):\n\n  File \"/usr/local/lib/python3.12/dist-packages/torch/utils/data/dataloader.py\", line 1707, in __del__\nTraceback (most recent call last):\n      File \"/usr/local/lib/python3.12/dist-packages/torch/utils/data/dataloader.py\", line 1707, in __del__\nself._shutdown_workers()\n      File \"/usr/local/lib/python3.12/dist-packages/torch/utils/data/dataloader.py\", line 1690, in _shutdown_workers\nself._shutdown_workers()\n    if w.is_alive():  File \"/usr/local/lib/python3.12/dist-packages/torch/utils/data/dataloader.py\", line 1690, in _shutdown_workers\n\n     if w.is_alive(): \n         ^^ ^^^ ^ ^^^^^^^^^^^^^^\n^  File \"/usr/lib/python3.12/multiprocessing/process.py\", line 160, in is_alive\n^^    ^\nassert self._parent_pid == os.getpid(), 'can only test a child process'\n  File \"/usr/lib/python3.12/multiprocessing/process.py\", line 160, in is_alive\n     assert self._parent_pid == os.getpid(), 'can only test a child process' \n                   ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nAssertionErrorAssertionError: : can only test a child processcan only test a child process\n\n","output_type":"stream"}],"execution_count":null},{"cell_type":"code","source":"# Verification helpers\nclass PairImageDataset(Dataset):\n    def __init__(self, pair_file, root_dir, transform):\n        self.root_dir = Path(root_dir)\n        self.transform = transform\n        self.rows = []\n        with open(pair_file, 'r') as f:\n            for line in f:\n                parts = line.strip().replace(',', ' ').split()\n                if len(parts) >= 2:\n                    self.rows.append(parts)\n\n    def __len__(self):\n        return len(self.rows)\n\n    def _load(self, rel):\n        p = self.root_dir / rel\n        if not p.exists():\n            matches = list(self.root_dir.rglob(Path(rel).name))\n            p = matches[0] if matches else p\n        img = Image.open(p).convert('RGB')\n        return self.transform(img)\n\n    def __getitem__(self, idx):\n        row = self.rows[idx]\n        x1 = self._load(row[0])\n        x2 = self._load(row[1])\n        label = float(row[2]) if len(row) > 2 else -1.0\n        return x1, x2, label, idx\n\ndef find_ver_image_root():\n    for name in ['ver_data', 'verification', 'test', 'images']:\n        p = VER_DIR / name\n        if p.exists(): return p\n    return VER_DIR\n\nVER_IMG_ROOT = find_ver_image_root()\nprint('VER_IMG_ROOT:', VER_IMG_ROOT)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-08T16:30:32.569118Z","iopub.status.idle":"2026-06-08T16:30:32.569549Z","shell.execute_reply.started":"2026-06-08T16:30:32.569324Z","shell.execute_reply":"2026-06-08T16:30:32.569349Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"@torch.no_grad()\ndef predict_pairs(pair_file, batch_size=256):\n    ckpt = torch.load('/kaggle/working/best_model.pth', map_location=DEVICE)\n    model.load_state_dict(ckpt['model'])\n    model.eval()\n    ds = PairImageDataset(pair_file, VER_IMG_ROOT, val_tfms)\n    loader = DataLoader(ds, batch_size=batch_size, shuffle=False, num_workers=NUM_WORKERS, pin_memory=True)\n    scores, labels, indices = [], [], []\n    for x1, x2, y, idx in tqdm(loader, desc=f'Pairs {Path(pair_file).name}'):\n        x1 = x1.to(DEVICE, non_blocking=True)\n        x2 = x2.to(DEVICE, non_blocking=True)\n        e1 = model(x1, return_embedding=True)\n        e2 = model(x2, return_embedding=True)\n        s = F.cosine_similarity(e1, e2).detach().cpu().numpy()\n        scores.extend(s.tolist())\n        labels.extend(y.numpy().tolist())\n        indices.extend(idx.numpy().tolist())\n    return np.array(indices), np.array(scores), np.array(labels)\n\ndef eer_from_scores(scores, labels):\n    labels = labels.astype(int)\n    order = np.argsort(scores)\n    scores, labels = scores[order], labels[order]\n    P = (labels == 1).sum(); N = (labels == 0).sum()\n    if P == 0 or N == 0: return None\n    fars, frrs = [], []\n    for t in scores:\n        pred = scores >= t\n        far = ((pred == 1) & (labels == 0)).sum() / N\n        frr = ((pred == 0) & (labels == 1)).sum() / P\n        fars.append(far); frrs.append(frr)\n    i = np.argmin(np.abs(np.array(fars) - np.array(frrs)))\n    return (fars[i] + frrs[i]) / 2\n\nif VAL_PAIRS.exists():\n    _, val_scores, val_labels = predict_pairs(VAL_PAIRS)\n    if (val_labels >= 0).all():\n        print('Validation EER:', eer_from_scores(val_scores, val_labels))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-08T16:30:32.570798Z","iopub.status.idle":"2026-06-08T16:30:32.571137Z","shell.execute_reply.started":"2026-06-08T16:30:32.570996Z","shell.execute_reply":"2026-06-08T16:30:32.571022Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Create Kaggle submission\nidx, test_scores, _ = predict_pairs(TEST_PAIRS)\nsub = pd.read_csv(SAMPLE_SUB)\nprint(sub.head())\n\n# Fill the likely prediction column. Usually it is the second column.\npred_col = sub.columns[1] if len(sub.columns) > 1 else 'score'\nsub[pred_col] = test_scores[:len(sub)]\nsub.to_csv('/kaggle/working/submission.csv', index=False)\nprint('Saved /kaggle/working/submission.csv')\nsub.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-08T16:30:32.572505Z","iopub.status.idle":"2026-06-08T16:30:32.572901Z","shell.execute_reply.started":"2026-06-08T16:30:32.572683Z","shell.execute_reply":"2026-06-08T16:30:32.572708Z"}},"outputs":[],"execution_count":null}]}