{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceType":"datasetVersion","sourceId":7615428,"datasetId":4434986,"databundleVersionId":7711037},{"sourceType":"datasetVersion","sourceId":10125851,"datasetId":6248577,"databundleVersionId":10408999},{"sourceType":"datasetVersion","sourceId":16370581,"datasetId":10494599,"databundleVersionId":17363218},{"sourceType":"kernelVersion","sourceId":25936515},{"sourceType":"kernelVersion","sourceId":31802223},{"sourceType":"kernelVersion","sourceId":188130493},{"sourceType":"kernelVersion","sourceId":211413172}],"dockerImageVersionId":31328,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# 1. INSTALL","metadata":{}},{"cell_type":"code","source":"!pip install --no-deps facenet-pytorch -q","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-05-21T07:05:43.039384Z","iopub.execute_input":"2026-05-21T07:05:43.039985Z","iopub.status.idle":"2026-05-21T07:05:46.463853Z","shell.execute_reply.started":"2026-05-21T07:05:43.039943Z","shell.execute_reply":"2026-05-21T07:05:46.462795Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 2. IMPORTS","metadata":{}},{"cell_type":"code","source":"import os\nimport cv2\nimport torch\nimport random\nimport shutil\nimport numpy as np\n\nfrom tqdm import tqdm\nfrom PIL import Image\n\nimport torch.nn as nn\nimport torch.optim as optim\n\nfrom torchvision import transforms\nfrom torchvision import models\n\nfrom torch.utils.data import Dataset\nfrom torch.utils.data import DataLoader\nfrom torch.utils.data import random_split\n\nfrom facenet_pytorch import MTCNN\n\nfrom sklearn.metrics import (\n    accuracy_score,\n    precision_score,\n    recall_score,\n    f1_score,\n    classification_report,\n    confusion_matrix\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-21T07:05:46.465951Z","iopub.execute_input":"2026-05-21T07:05:46.467285Z","iopub.status.idle":"2026-05-21T07:05:54.819400Z","shell.execute_reply.started":"2026-05-21T07:05:46.467223Z","shell.execute_reply":"2026-05-21T07:05:54.818317Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 3. DEVICE","metadata":{}},{"cell_type":"code","source":"device = torch.device(\n    \"cuda\" if torch.cuda.is_available() else \"cpu\"\n)\n\nprint(device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-21T07:05:54.820722Z","iopub.execute_input":"2026-05-21T07:05:54.821430Z","iopub.status.idle":"2026-05-21T07:05:54.828105Z","shell.execute_reply.started":"2026-05-21T07:05:54.821379Z","shell.execute_reply":"2026-05-21T07:05:54.826399Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 4. DATASET PATH","metadata":{}},{"cell_type":"code","source":"DATASET_PATH = \"/kaggle/input/datasets/hiuphmc/faceforensics-dataset-600-video-real-fake/video/video\"\n\nWORKING_PATH = \"/kaggle/working\"","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-21T07:05:54.830787Z","iopub.execute_input":"2026-05-21T07:05:54.831285Z","iopub.status.idle":"2026-05-21T07:05:54.852848Z","shell.execute_reply.started":"2026-05-21T07:05:54.831116Z","shell.execute_reply":"2026-05-21T07:05:54.851635Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 5. INPUT PATHS","metadata":{}},{"cell_type":"code","source":"REAL_FOLDER = os.path.join(\n    DATASET_PATH,\n    \"original_sequences\",\n    \"youtube\",\n    \"c40\",\n    \"videos\"\n)\n\nFAKE_FOLDER = os.path.join(\n    DATASET_PATH,\n    \"manipulated_sequences\",\n    \"DeepFakeDetection\",\n    \"c40\",\n    \"videos\"\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-21T07:05:54.854738Z","iopub.execute_input":"2026-05-21T07:05:54.855370Z","iopub.status.idle":"2026-05-21T07:05:54.875113Z","shell.execute_reply.started":"2026-05-21T07:05:54.855326Z","shell.execute_reply":"2026-05-21T07:05:54.873800Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"REAL:\", len(os.listdir(REAL_FOLDER)))\nprint(\"FAKE:\", len(os.listdir(FAKE_FOLDER)))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-21T07:05:54.876493Z","iopub.execute_input":"2026-05-21T07:05:54.876986Z","iopub.status.idle":"2026-05-21T07:05:55.069362Z","shell.execute_reply.started":"2026-05-21T07:05:54.876923Z","shell.execute_reply":"2026-05-21T07:05:55.068173Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 6. OUTPUT PATHS","metadata":{}},{"cell_type":"code","source":"FRAME_PATH = os.path.join(\n    WORKING_PATH,\n    \"frames\"\n)\n\nFACE_PATH = os.path.join(\n    WORKING_PATH,\n    \"face_dataset\"\n)\n\nEYE_PATH = os.path.join(\n    WORKING_PATH,\n    \"eye_dataset\"\n)\n\nNOSE_PATH = os.path.join(\n    WORKING_PATH,\n    \"nose_dataset\"\n)\n\nfor p in [\n    FRAME_PATH,\n    FACE_PATH,\n    EYE_PATH,\n    NOSE_PATH\n]:\n    os.makedirs(p, exist_ok=True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-21T07:05:55.070731Z","iopub.execute_input":"2026-05-21T07:05:55.071633Z","iopub.status.idle":"2026-05-21T07:05:55.079081Z","shell.execute_reply.started":"2026-05-21T07:05:55.071591Z","shell.execute_reply":"2026-05-21T07:05:55.077901Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 7. SAMPLE VIDEOS","metadata":{}},{"cell_type":"code","source":"NUM_REAL = 300\nNUM_FAKE = 300\n\nreal_videos = [\n    x for x in os.listdir(REAL_FOLDER)\n    if x.endswith(\".mp4\")\n]\n\nfake_videos = [\n    x for x in os.listdir(FAKE_FOLDER)\n    if x.endswith(\".mp4\")\n]\n\nrandom.shuffle(real_videos)\nrandom.shuffle(fake_videos)\n\nreal_videos = real_videos[:NUM_REAL]\nfake_videos = fake_videos[:NUM_FAKE]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-21T07:05:55.080598Z","iopub.execute_input":"2026-05-21T07:05:55.080920Z","iopub.status.idle":"2026-05-21T07:05:55.101302Z","shell.execute_reply.started":"2026-05-21T07:05:55.080889Z","shell.execute_reply":"2026-05-21T07:05:55.100124Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 8. EXTRACT FRAMES","metadata":{}},{"cell_type":"code","source":"def extract_frames(\n    video_path,\n    output_folder,\n    frame_skip=10,\n    max_frames=15\n):\n\n    os.makedirs(output_folder, exist_ok=True)\n\n    cap = cv2.VideoCapture(video_path)\n\n    count = 0\n    saved = 0\n\n    while True:\n\n        ret, frame = cap.read()\n\n        if not ret:\n            break\n\n        if count % frame_skip == 0:\n\n            frame_path = os.path.join(\n                output_folder,\n                f\"{saved}.jpg\"\n            )\n\n            cv2.imwrite(frame_path, frame)\n\n            saved += 1\n\n        count += 1\n\n        if saved >= max_frames:\n            break\n\n    cap.release()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-21T07:05:55.102854Z","iopub.execute_input":"2026-05-21T07:05:55.103991Z","iopub.status.idle":"2026-05-21T07:05:55.111442Z","shell.execute_reply.started":"2026-05-21T07:05:55.103940Z","shell.execute_reply":"2026-05-21T07:05:55.110175Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 9. EXTRACT REAL FRAMES","metadata":{}},{"cell_type":"code","source":"REAL_FRAME_PATH = os.path.join(\n    FRAME_PATH,\n    \"real\"\n)\n\nFAKE_FRAME_PATH = os.path.join(\n    FRAME_PATH,\n    \"fake\"\n)\n\nos.makedirs(REAL_FRAME_PATH, exist_ok=True)\nos.makedirs(FAKE_FRAME_PATH, exist_ok=True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-21T07:05:55.115022Z","iopub.execute_input":"2026-05-21T07:05:55.115463Z","iopub.status.idle":"2026-05-21T07:05:55.137856Z","shell.execute_reply.started":"2026-05-21T07:05:55.115428Z","shell.execute_reply":"2026-05-21T07:05:55.136668Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"for video in tqdm(real_videos):\n\n    video_path = os.path.join(\n        REAL_FOLDER,\n        video\n    )\n\n    save_folder = os.path.join(\n        REAL_FRAME_PATH,\n        video.split(\".\")[0]\n    )\n\n    extract_frames(\n        video_path,\n        save_folder\n    )","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-21T07:05:55.138965Z","iopub.execute_input":"2026-05-21T07:05:55.139306Z","iopub.status.idle":"2026-05-21T07:09:41.627022Z","shell.execute_reply.started":"2026-05-21T07:05:55.139272Z","shell.execute_reply":"2026-05-21T07:09:41.625811Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 10. EXTRACT FAKE FRAMES","metadata":{}},{"cell_type":"code","source":"for video in tqdm(fake_videos):\n\n    video_path = os.path.join(\n        FAKE_FOLDER,\n        video\n    )\n\n    save_folder = os.path.join(\n        FAKE_FRAME_PATH,\n        video.split(\".\")[0]\n    )\n\n    extract_frames(\n        video_path,\n        save_folder\n    )","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-21T07:09:41.628392Z","iopub.execute_input":"2026-05-21T07:09:41.628808Z","iopub.status.idle":"2026-05-21T07:13:14.855259Z","shell.execute_reply.started":"2026-05-21T07:09:41.628756Z","shell.execute_reply":"2026-05-21T07:13:14.854262Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 11. MTCNN","metadata":{}},{"cell_type":"code","source":"mtcnn = MTCNN(\n    image_size=224,\n    margin=20,\n    device=device\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-21T07:13:14.856950Z","iopub.execute_input":"2026-05-21T07:13:14.857613Z","iopub.status.idle":"2026-05-21T07:13:14.939732Z","shell.execute_reply.started":"2026-05-21T07:13:14.857561Z","shell.execute_reply":"2026-05-21T07:13:14.938881Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 12. PROCESS IMAGE","metadata":{}},{"cell_type":"code","source":"def process_image(\n    image_path,\n    label,\n    save_name\n):\n\n    try:\n\n        # =========================\n        # CREATE FOLDERS FIRST\n        # =========================\n\n        os.makedirs(\n            os.path.join(FACE_PATH, label),\n            exist_ok=True\n        )\n\n        os.makedirs(\n            os.path.join(EYE_PATH, label),\n            exist_ok=True\n        )\n\n        os.makedirs(\n            os.path.join(NOSE_PATH, label),\n            exist_ok=True\n        )\n\n        # =========================\n        # READ IMAGE\n        # =========================\n\n        img = cv2.imread(image_path)\n\n        if img is None:\n\n            print(f\"Cannot read image: {image_path}\")\n            return\n\n        rgb = cv2.cvtColor(\n            img,\n            cv2.COLOR_BGR2RGB\n        )\n\n        pil = Image.fromarray(rgb)\n\n        # =========================\n        # FACE DETECTION\n        # =========================\n\n        face_tensor = mtcnn(pil)\n\n        if face_tensor is None:\n\n            print(f\"No face detected: {image_path}\")\n            return\n\n        face_np = face_tensor.permute(\n            1,2,0\n        ).cpu().numpy()\n\n        face_np = (face_np * 255).astype(\"uint8\")\n\n        # =========================\n        # FACE\n        # =========================\n\n        face_rgb = face_np\n\n        h, w, _ = face_rgb.shape\n\n        # =========================\n        # EYE REGION\n        # =========================\n\n        eye_y1 = int(h * 0.15)\n        eye_y2 = int(h * 0.45)\n\n        eye_x1 = int(w * 0.15)\n        eye_x2 = int(w * 0.85)\n\n        eye_crop = face_rgb[\n            eye_y1:eye_y2,\n            eye_x1:eye_x2\n        ]\n\n        # =========================\n        # NOSE REGION\n        # =========================\n\n        nose_y1 = int(h * 0.35)\n        nose_y2 = int(h * 0.70)\n\n        nose_x1 = int(w * 0.30)\n        nose_x2 = int(w * 0.70)\n\n        nose_crop = face_rgb[\n            nose_y1:nose_y2,\n            nose_x1:nose_x2\n        ]\n\n        # =========================\n        # SAVE PATH\n        # =========================\n\n        face_save = os.path.join(\n            FACE_PATH,\n            label,\n            save_name\n        )\n\n        eye_save = os.path.join(\n            EYE_PATH,\n            label,\n            save_name\n        )\n\n        nose_save = os.path.join(\n            NOSE_PATH,\n            label,\n            save_name\n        )\n\n        # =========================\n        # SAVE IMAGES\n        # =========================\n\n        cv2.imwrite(\n            face_save,\n            cv2.cvtColor(\n                face_rgb,\n                cv2.COLOR_RGB2BGR\n            )\n        )\n\n        cv2.imwrite(\n            eye_save,\n            cv2.cvtColor(\n                eye_crop,\n                cv2.COLOR_RGB2BGR\n            )\n        )\n\n        cv2.imwrite(\n            nose_save,\n            cv2.cvtColor(\n                nose_crop,\n                cv2.COLOR_RGB2BGR\n            )\n        )\n\n    except Exception as e:\n\n        print(f\"ERROR processing: {image_path}\")\n        print(e)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-21T07:13:14.940888Z","iopub.execute_input":"2026-05-21T07:13:14.941676Z","iopub.status.idle":"2026-05-21T07:13:14.952872Z","shell.execute_reply.started":"2026-05-21T07:13:14.941638Z","shell.execute_reply":"2026-05-21T07:13:14.951650Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 13. PROCESS REAL","metadata":{}},{"cell_type":"code","source":"folders = os.listdir(REAL_FRAME_PATH)\n\nfor folder in tqdm(folders):\n\n    folder_path = os.path.join(\n        REAL_FRAME_PATH,\n        folder\n    )\n\n    images = os.listdir(folder_path)\n\n    for img_name in images:\n\n        img_path = os.path.join(\n            folder_path,\n            img_name\n        )\n\n        save_name = f\"{folder}_{img_name}\"\n\n        process_image(\n            img_path,\n            \"real\",\n            save_name\n        )","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-21T07:13:14.954210Z","iopub.execute_input":"2026-05-21T07:13:14.954717Z","iopub.status.idle":"2026-05-21T07:29:37.987710Z","shell.execute_reply.started":"2026-05-21T07:13:14.954676Z","shell.execute_reply":"2026-05-21T07:29:37.986723Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 14. PROCESS FAKE","metadata":{}},{"cell_type":"code","source":"folders = os.listdir(FAKE_FRAME_PATH)\n\nfor folder in tqdm(folders):\n\n    folder_path = os.path.join(\n        FAKE_FRAME_PATH,\n        folder\n    )\n\n    images = os.listdir(folder_path)\n\n    for img_name in images:\n\n        img_path = os.path.join(\n            folder_path,\n            img_name\n        )\n\n        save_name = f\"{folder}_{img_name}\"\n\n        process_image(\n            img_path,\n            \"fake\",\n            save_name\n        )","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-21T07:29:37.989361Z","iopub.execute_input":"2026-05-21T07:29:37.989746Z","iopub.status.idle":"2026-05-21T08:02:46.636727Z","shell.execute_reply.started":"2026-05-21T07:29:37.989713Z","shell.execute_reply":"2026-05-21T08:02:46.635781Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 15. TRANSFORM","metadata":{}},{"cell_type":"code","source":"transform = transforms.Compose([\n\n    transforms.Resize((224,224)),\n\n    transforms.RandomHorizontalFlip(),\n\n    transforms.ColorJitter(\n        brightness=0.2,\n        contrast=0.2\n    ),\n\n    transforms.ToTensor(),\n\n    transforms.Normalize(\n        mean=[0.5,0.5,0.5],\n        std=[0.5,0.5,0.5]\n    )\n])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-21T08:02:46.637872Z","iopub.execute_input":"2026-05-21T08:02:46.638117Z","iopub.status.idle":"2026-05-21T08:02:46.644190Z","shell.execute_reply.started":"2026-05-21T08:02:46.638092Z","shell.execute_reply":"2026-05-21T08:02:46.643261Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 16. DATASET CLASS","metadata":{}},{"cell_type":"code","source":"class RegionDataset(Dataset):\n\n    def __init__(\n        self,\n        root_dir,\n        transform=None\n    ):\n\n        self.images = []\n        self.labels = []\n\n        self.transform = transform\n\n        for label_name in [\n            \"real\",\n            \"fake\"\n        ]:\n\n            label_folder = os.path.join(\n                root_dir,\n                label_name\n            )\n\n            label = 0 if label_name == \"real\" else 1\n\n            for img in os.listdir(label_folder):\n\n                self.images.append(\n                    os.path.join(\n                        label_folder,\n                        img\n                    )\n                )\n\n                self.labels.append(label)\n\n    def __len__(self):\n        return len(self.images)\n\n    def __getitem__(self, idx):\n\n        img = Image.open(\n            self.images[idx]\n        ).convert(\"RGB\")\n\n        if self.transform:\n            img = self.transform(img)\n\n        label = self.labels[idx]\n\n        return img, label","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-21T08:02:46.645469Z","iopub.execute_input":"2026-05-21T08:02:46.645906Z","iopub.status.idle":"2026-05-21T08:02:46.669480Z","shell.execute_reply.started":"2026-05-21T08:02:46.645854Z","shell.execute_reply":"2026-05-21T08:02:46.668422Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 17. TRAIN FUNCTION","metadata":{}},{"cell_type":"code","source":"def train_model(\n    dataset_path,\n    model_name\n):\n\n    dataset = RegionDataset(\n        dataset_path,\n        transform=transform\n    )\n\n    train_size = int(\n        0.8 * len(dataset)\n    )\n\n    test_size = len(dataset) - train_size\n\n    train_dataset, test_dataset = random_split(\n        dataset,\n        [train_size, test_size]\n    )\n\n    train_loader = DataLoader(\n        train_dataset,\n        batch_size=16,\n        shuffle=True\n    )\n\n    test_loader = DataLoader(\n        test_dataset,\n        batch_size=16,\n        shuffle=False\n    )\n\n    model = models.efficientnet_b0(\n        pretrained=True\n    )\n\n    model.classifier[1] = nn.Linear(\n        1280,\n        2\n    )\n\n    model = model.to(device)\n\n    criterion = nn.CrossEntropyLoss()\n\n    optimizer = optim.Adam(\n        model.parameters(),\n        lr=0.0001\n    )\n\n    EPOCHS = 5\n\n    # ======================================\n    # TRAINING\n    # ======================================\n\n    for epoch in range(EPOCHS):\n\n        model.train()\n\n        running_loss = 0\n\n        train_preds = []\n        train_labels = []\n\n        for images, labels in tqdm(train_loader):\n\n            images = images.to(device)\n            labels = labels.to(device)\n\n            optimizer.zero_grad()\n\n            outputs = model(images)\n\n            loss = criterion(\n                outputs,\n                labels\n            )\n\n            loss.backward()\n\n            optimizer.step()\n\n            running_loss += loss.item()\n\n            _, preds = torch.max(\n                outputs,\n                1\n            )\n\n            train_preds.extend(\n                preds.cpu().numpy()\n            )\n\n            train_labels.extend(\n                labels.cpu().numpy()\n            )\n\n        train_acc = accuracy_score(\n            train_labels,\n            train_preds\n        )\n\n        print(\n            f\"\\n{model_name}\"\n        )\n\n        print(\n            f\"Epoch [{epoch+1}/{EPOCHS}]\"\n        )\n\n        print(\n            f\"Loss: {running_loss:.4f}\"\n        )\n\n        print(\n            f\"Train Accuracy: {train_acc:.4f}\"\n        )\n\n    # ======================================\n    # EVALUATION\n    # ======================================\n\n    model.eval()\n\n    test_preds = []\n    test_labels = []\n\n    with torch.no_grad():\n\n        for images, labels in tqdm(test_loader):\n\n            images = images.to(device)\n            labels = labels.to(device)\n\n            outputs = model(images)\n\n            _, preds = torch.max(\n                outputs,\n                1\n            )\n\n            test_preds.extend(\n                preds.cpu().numpy()\n            )\n\n            test_labels.extend(\n                labels.cpu().numpy()\n            )\n\n    # ======================================\n    # METRICS\n    # ======================================\n\n    accuracy = accuracy_score(\n        test_labels,\n        test_preds\n    )\n\n    precision = precision_score(\n        test_labels,\n        test_preds\n    )\n\n    recall = recall_score(\n        test_labels,\n        test_preds\n    )\n\n    f1 = f1_score(\n        test_labels,\n        test_preds\n    )\n\n    # ======================================\n    # PRINT RESULTS\n    # ======================================\n\n    print(\"\\n=========================\")\n    print(f\"{model_name} RESULTS\")\n    print(\"=========================\\n\")\n\n    print(f\"Accuracy : {accuracy:.4f}\")\n    print(f\"Precision: {precision:.4f}\")\n    print(f\"Recall   : {recall:.4f}\")\n    print(f\"F1 Score : {f1:.4f}\")\n\n    print(\"\\nClassification Report:\\n\")\n\n    print(\n        classification_report(\n            test_labels,\n            test_preds,\n            target_names=[\"REAL\", \"FAKE\"]\n        )\n    )\n\n    print(\"\\nConfusion Matrix:\\n\")\n\n    print(\n        confusion_matrix(\n            test_labels,\n            test_preds\n        )\n    )\n\n    # ======================================\n    # SAVE MODEL\n    # ======================================\n\n    save_path = f\"/kaggle/working/{model_name}.pth\"\n\n    torch.save(\n        model.state_dict(),\n        save_path\n    )\n\n    print(f\"\\n{model_name} SAVED\")\n\n    # ======================================\n    # RETURN\n    # ======================================\n\n    return {\n        \"accuracy\": accuracy,\n        \"precision\": precision,\n        \"recall\": recall,\n        \"f1_score\": f1\n    }","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-21T08:02:46.670795Z","iopub.execute_input":"2026-05-21T08:02:46.671151Z","iopub.status.idle":"2026-05-21T08:02:46.693572Z","shell.execute_reply.started":"2026-05-21T08:02:46.671111Z","shell.execute_reply":"2026-05-21T08:02:46.692336Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\n\nprint(os.listdir(\"/kaggle/working\"))\nprint(os.listdir(\"/kaggle/working/face_dataset\"))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-21T08:02:46.694809Z","iopub.execute_input":"2026-05-21T08:02:46.695142Z","iopub.status.idle":"2026-05-21T08:02:46.717395Z","shell.execute_reply.started":"2026-05-21T08:02:46.695101Z","shell.execute_reply":"2026-05-21T08:02:46.716315Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 18. TRAIN FACE MODEL","metadata":{}},{"cell_type":"code","source":"face_metrics = train_model(\n    FACE_PATH,\n    \"face_model\"\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-21T08:02:46.718825Z","iopub.execute_input":"2026-05-21T08:02:46.719207Z","iopub.status.idle":"2026-05-21T09:24:54.167662Z","shell.execute_reply.started":"2026-05-21T08:02:46.719177Z","shell.execute_reply":"2026-05-21T09:24:54.166599Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 19. TRAIN EYE MODEL","metadata":{}},{"cell_type":"code","source":"eye_metrics = train_model(\n    EYE_PATH,\n    \"eye_model\"\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-21T09:24:54.169368Z","iopub.execute_input":"2026-05-21T09:24:54.169824Z","iopub.status.idle":"2026-05-21T10:48:15.505145Z","shell.execute_reply.started":"2026-05-21T09:24:54.169775Z","shell.execute_reply":"2026-05-21T10:48:15.503680Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 20. TRAIN NOSE MODEL","metadata":{}},{"cell_type":"code","source":"nose_metrics = train_model(\n    NOSE_PATH,\n    \"nose_model\"\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-21T10:48:15.508550Z","iopub.execute_input":"2026-05-21T10:48:15.509102Z","iopub.status.idle":"2026-05-21T12:11:31.115112Z","shell.execute_reply.started":"2026-05-21T10:48:15.509044Z","shell.execute_reply":"2026-05-21T12:11:31.113282Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 21. MAJORITY VOTING","metadata":{}},{"cell_type":"code","source":"def majority_vote(\n    face_pred,\n    eye_pred,\n    nose_pred\n):\n\n    preds = [\n        face_pred,\n        eye_pred,\n        nose_pred\n    ]\n\n    return max(\n        set(preds),\n        key=preds.count\n    )","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-21T12:11:31.119828Z","iopub.execute_input":"2026-05-21T12:11:31.120969Z","iopub.status.idle":"2026-05-21T12:11:31.130145Z","shell.execute_reply.started":"2026-05-21T12:11:31.120866Z","shell.execute_reply":"2026-05-21T12:11:31.129313Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 22.RESULT","metadata":{}},{"cell_type":"code","source":"import pandas as pd\n\nresults = pd.DataFrame({\n\n    \"Model\": [\n        \"Face\",\n        \"Eye\",\n        \"Nose\"\n    ],\n\n    \"Accuracy\": [\n        face_metrics[\"accuracy\"],\n        eye_metrics[\"accuracy\"],\n        nose_metrics[\"accuracy\"]\n    ],\n\n    \"Precision\": [\n        face_metrics[\"precision\"],\n        eye_metrics[\"precision\"],\n        nose_metrics[\"precision\"]\n    ],\n\n    \"Recall\": [\n        face_metrics[\"recall\"],\n        eye_metrics[\"recall\"],\n        nose_metrics[\"recall\"]\n    ],\n\n    \"F1-Score\": [\n        face_metrics[\"f1_score\"],\n        eye_metrics[\"f1_score\"],\n        nose_metrics[\"f1_score\"]\n    ]\n})\n\nresults = results.round(4)\n\nprint(\"\\n========== FINAL RESULTS ==========\\n\")\n\nprint(results)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-21T12:11:31.158253Z","iopub.execute_input":"2026-05-21T12:11:31.158735Z","iopub.status.idle":"2026-05-21T12:11:31.193393Z","shell.execute_reply.started":"2026-05-21T12:11:31.158699Z","shell.execute_reply":"2026-05-21T12:11:31.192333Z"}},"outputs":[],"execution_count":null}]}