{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","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,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session\n\n# Use the kagglehub client library to attach Kaggle resources like competitions, datasets, and models to your session\n# Learn more about kagglehub: https://github.com/Kaggle/kagglehub/blob/main/README.md\n\nimport kagglehub\n# kagglehub.dataset_download('<owner>/<dataset-slug>')","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-09-28T08:53:16.793047Z","iopub.execute_input":"2026-09-28T08:53:16.793359Z","iopub.status.idle":"2026-09-28T08:58:13.935246Z","shell.execute_reply.started":"2026-09-28T08:53:16.793327Z","shell.execute_reply":"2026-09-28T08:58:13.934218Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nprint(os.listdir('/kaggle/input/'))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-28T08:58:13.936864Z","iopub.execute_input":"2026-09-28T08:58:13.937344Z","iopub.status.idle":"2026-09-28T08:58:13.942895Z","shell.execute_reply.started":"2026-09-28T08:58:13.937316Z","shell.execute_reply":"2026-09-28T08:58:13.941865Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nprint(os.listdir('/kaggle/input/'))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-28T08:58:13.944238Z","iopub.execute_input":"2026-09-28T08:58:13.944867Z","iopub.status.idle":"2026-09-28T08:58:13.964431Z","shell.execute_reply.started":"2026-09-28T08:58:13.944833Z","shell.execute_reply":"2026-09-28T08:58:13.963729Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os, random, shutil, cv2, json\nfrom sklearn.model_selection import train_test_split\n\nrandom.seed(42)\n\n# ── Paths ──────────────────────────────────────────\nCELEB_REAL = '/kaggle/input/datasets/pranabr0y/celebdf-v2image-dataset/Celeb_V2/Train/real'\nCELEB_FAKE = '/kaggle/input/datasets/pranabr0y/celebdf-v2image-dataset/Celeb_V2/Train/fake'\nDFDC_PATH  = '/kaggle/input/competitions/deepfake-detection-challenge/train_sample_videos/'\nOUTPUT     = '/kaggle/working/merged_dataset'\n\n# Output folders বানাও\nfor split in ['train','val','test']:\n    for label in ['real','fake']:\n        os.makedirs(f'{OUTPUT}/{split}/{label}', exist_ok=True)\nprint(\"✅ Folders ready\")\n\n# ── Celeb-DF v2 images ─────────────────────────────\nceleb_real = [os.path.join(CELEB_REAL, f)\n              for f in os.listdir(CELEB_REAL)\n              if f.endswith(('.jpg','.png'))]\nceleb_fake = [os.path.join(CELEB_FAKE, f)\n              for f in os.listdir(CELEB_FAKE)\n              if f.endswith(('.jpg','.png'))]\nprint(f\"Celeb Real: {len(celeb_real)} | Celeb Fake: {len(celeb_fake)}\")\n\n# ── DFDC frames extract ────────────────────────────\ndfdc_real, dfdc_fake = [], []\ntemp_dir = '/kaggle/working/dfdc_temp'\nos.makedirs(temp_dir, exist_ok=True)\n\nwith open(f'{DFDC_PATH}metadata.json') as f:\n    metadata = json.load(f)\n\nfor idx, (filename, info) in enumerate(list(metadata.items())[:200]):\n    video_path = os.path.join(DFDC_PATH, filename)\n    if not os.path.exists(video_path):\n        continue\n    label = info['label']\n    cap = cv2.VideoCapture(video_path)\n    total = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))\n    for i in range(5):\n        cap.set(cv2.CAP_PROP_POS_FRAMES, i * max(1, total//5))\n        ret, frame = cap.read()\n        if not ret: break\n        frame = cv2.resize(frame, (224, 224))\n        fname = f'{temp_dir}/dfdc_{idx}_{i}.jpg'\n        cv2.imwrite(fname, frame)\n        if label == 'REAL': dfdc_real.append(fname)\n        else: dfdc_fake.append(fname)\n    cap.release()\n    if (idx+1) % 50 == 0:\n        print(f\"  DFDC processed: {idx+1}/200\")\n\nprint(f\"DFDC Real: {len(dfdc_real)} | DFDC Fake: {len(dfdc_fake)}\")\n\n# ── Combine & Balance ──────────────────────────────\nall_real = celeb_real + dfdc_real\nall_fake = celeb_fake + dfdc_fake\nmin_count = min(len(all_real), len(all_fake))\nall_real = random.sample(all_real, min_count)\nall_fake","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-28T08:58:13.965583Z","iopub.execute_input":"2026-09-28T08:58:13.965886Z","iopub.status.idle":"2026-09-28T09:06:33.335133Z","shell.execute_reply.started":"2026-09-28T08:58:13.965866Z","shell.execute_reply":"2026-09-28T09:06:33.334285Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ── Combine & Balance ──────────────────────────────\nall_real = celeb_real + dfdc_real\nall_fake = celeb_fake + dfdc_fake\n\nmin_count = min(len(all_real), len(all_fake))\nall_real = random.sample(all_real, min_count)\nall_fake = random.sample(all_fake, min_count)\n\nprint(f\"Balanced Real: {len(all_real)} | Balanced Fake: {len(all_fake)}\")\n\n# ── Split & Copy ───────────────────────────────────\ndef split_and_copy(files, label):\n    train_files, test_files = train_test_split(files, test_size=0.3, random_state=42)\n    val_files, test_files = train_test_split(test_files, test_size=0.5, random_state=42)\n    \n    for split, f_list in zip(['train', 'val', 'test'], [train_files, val_files, test_files]):\n        for f in f_list:\n            shutil.copy(f, f'{OUTPUT}/{split}/{label}/')\n\nsplit_and_copy(all_real, 'real')\nsplit_and_copy(all_fake, 'fake')\n\nif os.path.exists(temp_dir):\n    shutil.rmtree(temp_dir) # Temp folder মুছে ফেলা\n\nprint(\"✅ Dataset merge & 70/15/15 split complete!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-28T09:06:33.337314Z","iopub.execute_input":"2026-09-28T09:06:33.337717Z","iopub.status.idle":"2026-09-28T09:12:33.567216Z","shell.execute_reply.started":"2026-09-28T09:06:33.337695Z","shell.execute_reply":"2026-09-28T09:12:33.566371Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\n\nBASE = '/kaggle/working/merged_dataset'\n\nif os.path.exists(BASE):\n    for split in ['train', 'val', 'test']:\n        real = len(os.listdir(f'{BASE}/{split}/real'))\n        fake = len(os.listdir(f'{BASE}/{split}/fake'))\n        print(f\"{split} → Real: {real} | Fake: {fake}\")\nelse:\n    print(\"❌ merged_dataset নেই — আবার merge করতে হবে\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-28T09:12:33.568301Z","iopub.execute_input":"2026-09-28T09:12:33.568886Z","iopub.status.idle":"2026-09-28T09:12:33.616524Z","shell.execute_reply.started":"2026-09-28T09:12:33.568851Z","shell.execute_reply":"2026-09-28T09:12:33.615847Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os, random, shutil, cv2, json\nfrom sklearn.model_selection import train_test_split\n\nrandom.seed(42)\n\nCELEB_REAL = '/kaggle/input/datasets/pranabr0y/celebdf-v2image-dataset/Celeb_V2/Train/real'\nCELEB_FAKE = '/kaggle/input/datasets/pranabr0y/celebdf-v2image-dataset/Celeb_V2/Train/fake'\nDFDC_PATH  = '/kaggle/input/competitions/deepfake-detection-challenge/train_sample_videos/'\nOUTPUT     = '/kaggle/working/merged_dataset'\n\nfor split in ['train','val','test']:\n    for label in ['real','fake']:\n        os.makedirs(f'{OUTPUT}/{split}/{label}', exist_ok=True)\nprint(\"✅ Folders ready\")\n\n# Celeb-DF v2\nceleb_real = [os.path.join(CELEB_REAL, f)\n              for f in os.listdir(CELEB_REAL)\n              if f.endswith(('.jpg','.png'))]\nceleb_fake = [os.path.join(CELEB_FAKE, f)\n              for f in os.listdir(CELEB_FAKE)\n              if f.endswith(('.jpg','.png'))]\nprint(f\"Celeb Real: {len(celeb_real)} | Celeb Fake: {len(celeb_fake)}\")\n\n# DFDC frames\ndfdc_real, dfdc_fake = [], []\ntemp_dir = '/kaggle/working/dfdc_temp'\nos.makedirs(temp_dir, exist_ok=True)\n\nwith open(f'{DFDC_PATH}metadata.json') as f:\n    metadata = json.load(f)\n\nfor idx, (filename, info) in enumerate(list(metadata.items())[:200]):\n    video_path = os.path.join(DFDC_PATH, filename)\n    if not os.path.exists(video_path): continue\n    label = info['label']\n    cap = cv2.VideoCapture(video_path)\n    total = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))\n    for i in range(5):\n        cap.set(cv2.CAP_PROP_POS_FRAMES, i * max(1, total//5))\n        ret, frame = cap.read()\n        if not ret: break\n        frame = cv2.resize(frame, (224, 224))\n        fname = f'{temp_dir}/dfdc_{idx}_{i}.jpg'\n        cv2.imwrite(fname, frame)\n        if label == 'REAL': dfdc_real.append(fname)\n        else: dfdc_fake.append(fname)\n    cap.release()\n    if (idx+1) % 50 == 0:\n        print(f\"  DFDC: {idx+1}/200\")\n\nprint(f\"DFDC Real: {len(dfdc_real)} | DFDC Fake: {len(dfdc_fake)}\")\n\n# Combine & Balance\nall_real = celeb_real + dfdc_real\nall_fake = celeb_fake + dfdc_fake\nmin_count = min(len(all_real), len(all_fake))\nall_real = random.sample(all_real, min_count)\nall_fake = random.sample(all_fake, min_count)\nprint(f\"Balanced → Real: {len(all_real)} | Fake: {len(all_fake)}\")\n\n# 70/15/15 Split & Copy\ndef split_and_copy(files, label):\n    train_f, temp_f = train_test_split(files, test_size=0.30, random_state=42)\n    val_f, test_f   = train_test_split(temp_f, test_size=0.50, random_state=42)\n    for split, flist in zip(['train','val','test'], [train_f, val_f, test_f]):\n        for f in flist:\n            dst = f'{OUTPUT}/{split}/{label}/' + os.path.basename(f)\n            shutil.copy(f, dst)\n    return len(train_f), len(val_f), len(test_f)\n\nprint(\"Copying files... (১০-১৫ মিনিট)\")\nr = split_and_copy(all_real, 'real')\nf = split_and_copy(all_fake, 'fake')\n\n# Temp folder মুছো\nshutil.rmtree(temp_dir)\n\nprint(f\"\\n✅ Done!\")\nprint(f\"Train  → Real:{r[0]} | Fake:{f[0]}\")\nprint(f\"Val    → Real:{r[1]} | Fake:{f[1]}\")\nprint(f\"Test   → Real:{r[2]} | Fake:{f[2]}\")\nprint(f\"\\n📁 Saved: {OUTPUT}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-28T09:12:33.617648Z","iopub.execute_input":"2026-09-28T09:12:33.618164Z","iopub.status.idle":"2026-09-28T09:24:16.512044Z","shell.execute_reply.started":"2026-09-28T09:12:33.618141Z","shell.execute_reply":"2026-09-28T09:24:16.511212Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch, timm\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import DataLoader\nfrom torchvision import datasets, transforms\n\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\nprint(f\"Device: {device}\")\n\n# Data Transformations\ntransform = {\n    'train': transforms.Compose([\n        transforms.Resize((224, 224)),\n        transforms.RandomHorizontalFlip(),\n        transforms.RandomRotation(10),\n        transforms.ToTensor(),\n        transforms.Normalize([0.5]*3, [0.5]*3)\n    ]),\n    'val': transforms.Compose([\n        transforms.Resize((224, 224)),\n        transforms.ToTensor(),\n        transforms.Normalize([0.5]*3, [0.5]*3)\n    ]),\n}\n\nBASE = '/kaggle/working/merged_dataset'\ntrain_ds = datasets.ImageFolder(f'{BASE}/train', transform['train'])\nval_ds   = datasets.ImageFolder(f'{BASE}/val', transform['val'])\ntest_ds  = datasets.ImageFolder(f'{BASE}/test', transform['val'])\n\ntrain_loader = DataLoader(train_ds, batch_size=32, shuffle=True, num_workers=2)\nval_loader   = DataLoader(val_ds, batch_size=32, shuffle=False, num_workers=2)\ntest_loader  = DataLoader(test_ds, batch_size=32, shuffle=False, num_workers=2)\n\nprint(f\"Train: {len(train_ds)} | Val: {len(val_ds)} | Test: {len(test_ds)} ✅\")\n\n# Load EfficientNet-B4 Model\nmodel = timm.create_model('efficientnet_b4', pretrained=True)\nmodel.classifier = nn.Linear(model.classifier.in_features, 2)\nmodel = model.to(device)\n\nprint(\"EfficientNet-B4 ready ✅\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-28T09:24:16.512987Z","iopub.execute_input":"2026-09-28T09:24:16.513301Z","iopub.status.idle":"2026-09-28T09:24:35.107508Z","shell.execute_reply.started":"2026-09-28T09:24:16.513278Z","shell.execute_reply":"2026-09-28T09:24:35.106761Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import time\nimport copy\nimport torch.nn.functional as F\nfrom sklearn.metrics import accuracy_score, f1_score, roc_auc_score\n\n# ── 1. Loss & Optimizer Setup ─────────────────────────────\ncriterion = nn.CrossEntropyLoss()\noptimizer = optim.AdamW(model.parameters(), lr=1e-4, weight_decay=1e-2)\n\n# ── 2. Training with Early Stopping (Max 20 Epochs) ────────\nnum_epochs = 20\npatience = 3  # Val Loss না কমলে ৩ ইপোক পর নিজে থামবে\npatience_counter = 0\n\nbest_model_wts = copy.deepcopy(model.state_dict())\nbest_loss = float('inf')\n\nprint(\"🚀 EfficientNet-B4 Training Started...\\n\")\n\nfor epoch in range(num_epochs):\n    start_time = time.time()\n    \n    # Train Phase\n    model.train()\n    running_loss = 0.0\n    correct_train = 0\n    total_train = 0\n    \n    for inputs, labels in train_loader:\n        inputs, labels = inputs.to(device), labels.to(device)\n        optimizer.zero_grad()\n        outputs = model(inputs)\n        loss = criterion(outputs, labels)\n        loss.backward()\n        optimizer.step()\n        \n        running_loss += loss.item() * inputs.size(0)\n        _, preds = torch.max(outputs, 1)\n        total_train += labels.size(0)\n        correct_train += (preds == labels).sum().item()\n        \n    epoch_train_loss = running_loss / total_train\n    epoch_train_acc = correct_train / total_train\n\n    # Validation Phase\n    model.eval()\n    val_loss = 0.0\n    correct_val = 0\n    total_val = 0\n    \n    with torch.no_grad():\n        for inputs, labels in val_loader:\n            inputs, labels = inputs.to(device), labels.to(device)\n            outputs = model(inputs)\n            loss = criterion(outputs, labels)\n            \n            val_loss += loss.item() * inputs.size(0)\n            _, preds = torch.max(outputs, 1)\n            total_val += labels.size(0)\n            correct_val += (preds == labels).sum().item()\n            \n    epoch_val_loss = val_loss / total_val\n    epoch_val_acc = correct_val / total_val\n    elapsed = time.time() - start_time\n    \n    print(f\"Epoch {epoch+1:02d}/{num_epochs:02d} | \"\n          f\"Train Loss: {epoch_train_loss:.4f} - Acc: {epoch_train_acc*100:.2f}% | \"\n          f\"Val Loss: {epoch_val_loss:.4f} - Acc: {epoch_val_acc*100:.2f}% | \"\n          f\"Time: {elapsed:.0f}s\")\n    \n    # Early Stopping Check\n    if epoch_val_loss < best_loss:\n        best_loss = epoch_val_loss\n        best_model_wts = copy.deepcopy(model.state_dict())\n        patience_counter = 0\n        torch.save(model.state_dict(), '/kaggle/working/best_efficientnet_b4.pth')\n        print(\"  --> 🎯 Best Model Saved!\")\n    else:\n        patience_counter += 1\n        print(f\"  --> ⚠️ No improvement in Val Loss ({patience_counter}/{patience})\")\n        if patience_counter >= patience:\n            print(f\"\\n🛑 Early stopping triggered at epoch {epoch+1}!\")\n            break\n\n# Load Best Weights\nmodel.load_state_dict(best_model_wts)\nprint(\"\\n✅ Training Complete. Best model weights loaded!\")\n\n# ── 3. Final Test Set Evaluation ──────────────────────────\nprint(\"\\n📊 Testing Best Model on Test Set...\")\nmodel.eval()\n\ny_true, y_pred, y_probs = [], [], []\n\nwith torch.no_grad():\n    for inputs, labels in test_loader:\n        inputs = inputs.to(device)\n        outputs = model(inputs)\n        probs = F.softmax(outputs, dim=1)[:, 1]\n        _, preds = torch.max(outputs, 1)\n        \n        y_true.extend(labels.numpy())\n        y_pred.extend(preds.cpu().numpy())\n        y_probs.extend(probs.cpu().numpy())\n\ntest_acc = accuracy_score(y_true, y_pred)\ntest_f1 = f1_score(y_true, y_pred)\ntest_auc = roc_auc_score(y_true, y_probs)\n\nprint(\"\\n================ TEST RESULTS ================\")\nprint(f\"✅ Test Accuracy : {test_acc * 100:.2f}%\")\nprint(f\"✅ Test F1-Score : {test_f1:.4f}\")\nprint(f\"✅ Test ROC-AUC  : {test_auc:.4f}\")\nprint(\"==============================================\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-28T09:24:35.108517Z","iopub.execute_input":"2026-09-28T09:24:35.10898Z","iopub.status.idle":"2026-09-28T13:16:53.283087Z","shell.execute_reply.started":"2026-09-28T09:24:35.108955Z","shell.execute_reply":"2026-09-28T13:16:53.282098Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\n\n# Celeb-DF v2 image নামের pattern দেখো\nCELEB_REAL = '/kaggle/input/datasets/pranabr0y/celebdf-v2image-dataset/Celeb_V2/Train/real'\n\nfiles = os.listdir(CELEB_REAL)[:10]\nfor f in files:\n    print(f)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-30T05:08:04.458543Z","iopub.execute_input":"2026-09-30T05:08:04.459037Z","iopub.status.idle":"2026-09-30T05:08:04.987305Z","shell.execute_reply.started":"2026-09-30T05:08:04.459012Z","shell.execute_reply":"2026-09-30T05:08:04.986532Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os, random, shutil, cv2, json\nfrom collections import defaultdict\nfrom sklearn.model_selection import train_test_split\n\nrandom.seed(42)\n\nCELEB_REAL = '/kaggle/input/datasets/pranabr0y/celebdf-v2image-dataset/Celeb_V2/Train/real'\nCELEB_FAKE = '/kaggle/input/datasets/pranabr0y/celebdf-v2image-dataset/Celeb_V2/Train/fake'\nDFDC_PATH  = '/kaggle/input/competitions/deepfake-detection-challenge/train_sample_videos/'\nOUTPUT     = '/kaggle/working/merged_dataset_v2'\n\n# Folders বানাও\nfor split in ['train','val','test']:\n    for label in ['real','fake']:\n        os.makedirs(f'{OUTPUT}/{split}/{label}', exist_ok=True)\nprint(\"✅ Folders ready\")\n\n# ── Video ID extract function ──────────────────────\ndef get_video_id(filename):\n    # id12_0001_face_1880.jpg → id12_0001\n    # 00239_face_13572.jpg    → 00239\n    parts = filename.split('_face_')\n    return parts[0]\n\n# ── Celeb-DF v2 → Video-level grouping ────────────\ndef group_by_video(folder):\n    video_groups = defaultdict(list)\n    for f in os.listdir(folder):\n        if f.endswith(('.jpg','.png')):\n            vid_id = get_video_id(f)\n            video_groups[vid_id].append(os.path.join(folder, f))\n    return video_groups\n\nceleb_real_groups = group_by_video(CELEB_REAL)\nceleb_fake_groups = group_by_video(CELEB_FAKE)\n\nprint(f\"Celeb Real videos: {len(celeb_real_groups)}\")\nprint(f\"Celeb Fake videos: {len(celeb_fake_groups)}\")\n\n# ── Video-level 70/15/15 split ─────────────────────\ndef video_split(video_groups):\n    video_ids = list(video_groups.keys())\n    random.shuffle(video_ids)\n    train_ids, temp_ids = train_test_split(video_ids, test_size=0.30, random_state=42)\n    val_ids, test_ids   = train_test_split(temp_ids,  test_size=0.50, random_state=42)\n    return train_ids, val_ids, test_ids\n\nr_train_ids, r_val_ids, r_test_ids = video_split(celeb_real_groups)\nf_train_ids, f_val_ids, f_test_ids = video_split(celeb_fake_groups)\n\nprint(f\"\\nCeleb Real video split:\")\nprint(f\"  Train: {len(r_train_ids)} videos\")\nprint(f\"  Val  : {len(r_val_ids)} videos\")\nprint(f\"  Test : {len(r_test_ids)} videos\")\n\n# ── Copy Celeb-DF v2 files ─────────────────────────\ndef copy_by_video_ids(video_groups, ids, dst):\n    count = 0\n    for vid_id in ids:\n        for src in video_groups[vid_id]:\n            shutil.copy(src, os.path.join(dst, os.path.basename(src)))\n            count += 1\n    return count\n\nprint(\"\\nCopying Celeb-DF v2...\")\nr = copy_by_video_ids(celeb_real_groups, r_train_ids, f'{OUTPUT}/train/real')\ncopy_by_video_ids(celeb_real_groups, r_val_ids,   f'{OUTPUT}/val/real')\ncopy_by_video_ids(celeb_real_groups, r_test_ids,  f'{OUTPUT}/test/real')\n\ncopy_by_video_ids(celeb_fake_groups, f_train_ids, f'{OUTPUT}/train/fake')\ncopy_by_video_ids(celeb_fake_groups, f_val_ids,   f'{OUTPUT}/val/fake')\ncopy_by_video_ids(celeb_fake_groups, f_test_ids,  f'{OUTPUT}/test/fake')\nprint(\"✅ Celeb-DF v2 done\")\n\n# ── DFDC → Video-level split ───────────────────────\nprint(\"\\nProcessing DFDC...\")\ntemp_dir = '/kaggle/working/dfdc_temp'\nos.makedirs(temp_dir, exist_ok=True)\n\nwith open(f'{DFDC_PATH}metadata.json') as f:\n    metadata = json.load(f)\n\n# সব video list করো\nall_videos = list(metadata.items())\nrandom.shuffle(all_videos)\n\n# 700 video নেবো (আগের ২০০ থেকে বেশি)\nselected = all_videos[:700]\ntrain_v, temp_v = train_test_split(selected, test_size=0.30, random_state=42)\nval_v, test_v   = train_test_split(temp_v,   test_size=0.50, random_state=42)\n\ndef extract_dfdc(video_list, split):\n    count = 0\n    for idx, (filename, info) in enumerate(video_list):\n        video_path = os.path.join(DFDC_PATH, filename)\n        if not os.path.exists(video_path): continue\n        label = 'real' if info['label'] == 'REAL' else 'fake'\n        cap = cv2.VideoCapture(video_path)\n        total = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))\n        for i in range(5):\n            cap.set(cv2.CAP_PROP_POS_FRAMES, i * max(1, total//5))\n            ret, frame = cap.read()\n            if not ret: break\n            frame = cv2.resize(frame, (224, 224))\n            fname = f'{OUTPUT}/{split}/{label}/dfdc_{idx}_{i}.jpg'\n            cv2.imwrite(fname, frame)\n            count += 1\n        cap.release()\n    return count\n\nprint(\"  Extracting train DFDC...\")\nextract_dfdc(train_v, 'train')\nprint(\"  Extracting val DFDC...\")\nextract_dfdc(val_v, 'val')\nprint(\"  Extracting test DFDC...\")\nextract_dfdc(test_v, 'test')\nshutil.rmtree(temp_dir)\n\n# ── Final Count ────────────────────────────────────\nprint(\"\\n📊 Final Dataset (Video-level split):\")\nfor split in ['train','val','test']:\n    real = len(os.listdir(f'{OUTPUT}/{split}/real'))\n    fake = len(os.listdir(f'{OUTPUT}/{split}/fake'))\n    print(f\"{split:5} → Real: {real:6} | Fake: {fake:6}\")\n\nprint(\"\\n✅ Video-level split complete! No data leakage!\")\nprint(f\"📁 Saved: {OUTPUT}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-30T12:28:26.096478Z","iopub.execute_input":"2026-09-30T12:28:26.096743Z","iopub.status.idle":"2026-09-30T12:58:45.554154Z","shell.execute_reply.started":"2026-09-30T12:28:26.096719Z","shell.execute_reply":"2026-09-30T12:58:45.553164Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch, timm, time, copy\nimport torch.nn as nn\nimport torch.optim as optim\nimport torch.nn.functional as F\nfrom torch.utils.data import DataLoader\nfrom torchvision import datasets, transforms\nfrom sklearn.metrics import accuracy_score, f1_score, roc_auc_score\n\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\nprint(f\"Device: {device}\")\n\n# ── 1. Data Transforms & Loaders ──────────────────────────\ntransform = {\n    'train': transforms.Compose([\n        transforms.Resize((224, 224)),\n        transforms.RandomHorizontalFlip(),\n        transforms.ToTensor(),\n        transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])\n    ]),\n    'val': transforms.Compose([\n        transforms.Resize((224, 224)),\n        transforms.ToTensor(),\n        transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])\n    ]),\n}\n\nBASE = '/kaggle/working/merged_dataset_v2'\n\ntrain_ds = datasets.ImageFolder(f'{BASE}/train', transform['train'])\nval_ds   = datasets.ImageFolder(f'{BASE}/val', transform['val'])\ntest_ds  = datasets.ImageFolder(f'{BASE}/test', transform['val'])\n\n# Batch size 64 রাখা হয়েছে ট্রেনিং দ্রুত করার জন্য\ntrain_loader = DataLoader(train_ds, batch_size=64, shuffle=True, num_workers=4, pin_memory=True)\nval_loader   = DataLoader(val_ds, batch_size=64, shuffle=False, num_workers=4, pin_memory=True)\ntest_loader  = DataLoader(test_ds, batch_size=64, shuffle=False, num_workers=4, pin_memory=True)\n\nprint(f\"Dataset Loaded -> Train: {len(train_ds)} | Val: {len(val_ds)} | Test: {len(test_ds)}\")\n\n# ── 2. Model & Training Setup ─────────────────────────────\nmodel = timm.create_model('efficientnet_b4', pretrained=True)\nmodel.classifier = nn.Linear(model.classifier.in_features, 2)\nmodel = model.to(device)\n\ncriterion = nn.CrossEntropyLoss()\noptimizer = optim.AdamW(model.parameters(), lr=1e-4, weight_decay=1e-2)\nscaler = torch.cuda.amp.GradScaler() # Mixed Precision for faster GPU execution\n\nnum_epochs = 5\npatience = 2\npatience_counter = 0\nbest_loss = float('inf')\nbest_model_wts = copy.deepcopy(model.state_dict())\n\n# ── 3. Training Loop ──────────────────────────────────────\nprint(\"\\n🚀 Starting Fast Training...\")\nfor epoch in range(num_epochs):\n    start_time = time.time()\n    \n    # Training\n    model.train()\n    running_loss, correct_train, total_train = 0.0, 0, 0\n    for inputs, labels in train_loader:\n        inputs, labels = inputs.to(device), labels.to(device)\n        optimizer.zero_grad()\n        \n        with torch.cuda.amp.autocast():\n            outputs = model(inputs)\n            loss = criterion(outputs, labels)\n            \n        scaler.scale(loss).backward()\n        scaler.step(optimizer)\n        scaler.update()\n        \n        running_loss += loss.item() * inputs.size(0)\n        _, preds = torch.max(outputs, 1)\n        total_train += labels.size(0)\n        correct_train += (preds == labels).sum().item()\n        \n    epoch_train_loss = running_loss / total_train\n    epoch_train_acc = correct_train / total_train\n\n    # Validation\n    model.eval()\n    val_loss, correct_val, total_val = 0.0, 0, 0\n    with torch.no_grad():\n        for inputs, labels in val_loader:\n            inputs, labels = inputs.to(device), labels.to(device)\n            with torch.cuda.amp.autocast():\n                outputs = model(inputs)\n                loss = criterion(outputs, labels)\n            \n            val_loss += loss.item() * inputs.size(0)\n            _, preds = torch.max(outputs, 1)\n            total_val += labels.size(0)\n            correct_val += (preds == labels).sum().item()\n            \n    epoch_val_loss = val_loss / total_val\n    epoch_val_acc = correct_val / total_val\n    elapsed = time.time() - start_time\n    \n    print(f\"Epoch {epoch+1:02d}/{num_epochs:02d} | \"\n          f\"Train Loss: {epoch_train_loss:.4f} - Acc: {epoch_train_acc*100:.2f}% | \"\n          f\"Val Loss: {epoch_val_loss:.4f} - Acc: {epoch_val_acc*100:.2f}% | \"\n          f\"Time: {elapsed:.0f}s\")\n\n    # Early Stopping Check\n    if epoch_val_loss < best_loss:\n        best_loss = epoch_val_loss\n        best_model_wts = copy.deepcopy(model.state_dict())\n        patience_counter = 0\n        torch.save(model.state_dict(), '/kaggle/working/best_efficientnet_b4_v2.pth')\n        print(\"  --> 🎯 Best Model Saved!\")\n    else:\n        patience_counter += 1\n        if patience_counter >= patience:\n            print(f\"\\n🛑 Early stopping triggered at epoch {epoch+1}!\")\n            break\n\n# Load Best Weights\nmodel.load_state_dict(best_model_wts)\n\n# ── 4. Test Set Evaluation ────────────────────────────────\nprint(\"\\n📊 Evaluating Best Model on Test Set...\")\nmodel.eval()\ny_true, y_pred, y_probs = [], [], []\n\nwith torch.no_grad():\n    for inputs, labels in test_loader:\n        inputs = inputs.to(device)\n        with torch.cuda.amp.autocast():\n            outputs = model(inputs)\n            probs = F.softmax(outputs, dim=1)[:, 1]\n        _, preds = torch.max(outputs, 1)\n        \n        y_true.extend(labels.numpy())\n        y_pred.extend(preds.cpu().numpy())\n        y_probs.extend(probs.cpu().numpy())\n\ntest_acc = accuracy_score(y_true, y_pred)\ntest_f1 = f1_score(y_true, y_pred)\ntest_auc = roc_auc_score(y_true, y_probs)\n\nprint(\"\\n================ TEST RESULTS ================\")\nprint(f\"✅ Test Accuracy : {test_acc * 100:.2f}%\")\nprint(f\"✅ Test F1-Score : {test_f1:.4f}\")\nprint(f\"✅ Test ROC-AUC  : {test_auc:.4f}\")\nprint(\"==============================================\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-30T13:22:43.265408Z","iopub.execute_input":"2026-09-30T13:22:43.266177Z","iopub.status.idle":"2026-09-30T13:50:01.903979Z","shell.execute_reply.started":"2026-09-30T13:22:43.266127Z","shell.execute_reply":"2026-09-30T13:50:01.903291Z"}},"outputs":[],"execution_count":null}]}