{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":113558,"databundleVersionId":14456136,"sourceType":"competition"}],"dockerImageVersionId":31192,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import pathlib\nimport numpy as np\nimport pandas as pd\nfrom PIL import Image\nimport matplotlib.pyplot as plt\nimport seaborn as sns\n\nimport torch\nimport torch.nn as nn\nfrom torch.utils.data import Dataset, DataLoader\n\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import accuracy_score, confusion_matrix\n\nimport torchvision.models as models\nfrom torchvision.transforms import transforms\n\n# CPU only for Kaggle safe run\ndevice = \"cpu\"\nprint(\"Using device:\", device)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T08:33:16.089601Z","iopub.execute_input":"2025-12-07T08:33:16.089975Z","iopub.status.idle":"2025-12-07T08:33:16.539853Z","shell.execute_reply.started":"2025-12-07T08:33:16.089950Z","shell.execute_reply":"2025-12-07T08:33:16.538809Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"BASE_DIR = pathlib.Path(\"/kaggle/input/recodai-luc-scientific-image-forgery-detection\")\nTRAIN_IMG_DIR = BASE_DIR / \"train_images\"\n\nAUTHENTIC_DIR = TRAIN_IMG_DIR / \"authentic\"\nFORGED_DIR = TRAIN_IMG_DIR / \"forged\"\n\nprint(\"Number of authentic images:\", len(list(AUTHENTIC_DIR.iterdir())))\nprint(\"Number of forged images:\", len(list(FORGED_DIR.iterdir())))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T08:33:24.194538Z","iopub.execute_input":"2025-12-07T08:33:24.195038Z","iopub.status.idle":"2025-12-07T08:33:24.210792Z","shell.execute_reply.started":"2025-12-07T08:33:24.195011Z","shell.execute_reply":"2025-12-07T08:33:24.209813Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"IMG_PATHS = []\n\nfor p in AUTHENTIC_DIR.iterdir():\n    IMG_PATHS.append({\"type\": \"authentic\", \"path\": p})\nfor p in FORGED_DIR.iterdir():\n    IMG_PATHS.append({\"type\": \"forged\", \"path\": p})\n\ndf = pd.DataFrame(IMG_PATHS).sample(frac=1).reset_index(drop=True)\ndf[\"label\"] = df[\"type\"].map({\"authentic\": 0, \"forged\": 1})\n\ntrain_df, test_df = train_test_split(df, test_size=0.2, stratify=df[\"label\"], random_state=42)\ntrain_df.shape, test_df.shape\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T08:33:41.179488Z","iopub.execute_input":"2025-12-07T08:33:41.179891Z","iopub.status.idle":"2025-12-07T08:33:41.210892Z","shell.execute_reply.started":"2025-12-07T08:33:41.179865Z","shell.execute_reply":"2025-12-07T08:33:41.209930Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class LUCDataset(Dataset):\n    def __init__(self, df, transform=None):\n        self.df = df\n        self.transform = transform\n\n    def __getitem__(self, idx):\n        row = self.df.iloc[idx]\n        img = Image.open(row.path).convert(\"RGB\")\n        if self.transform:\n            img = self.transform(img)\n        return img, row.label\n\n    def __len__(self):\n        return len(self.df)\n\ntransform = transforms.Compose([\n    transforms.Resize((224,224)),\n    transforms.ToTensor(),\n    transforms.Normalize(\n        mean=[0.485,0.456,0.406],\n        std=[0.229,0.224,0.225]\n    )\n])\n\ntrain_ds = LUCDataset(train_df, transform)\ntest_ds = LUCDataset(test_df, transform)\n\ntrain_dl = DataLoader(train_ds, batch_size=32, shuffle=True)\ntest_dl = DataLoader(test_ds, batch_size=32)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T08:33:52.739235Z","iopub.execute_input":"2025-12-07T08:33:52.739604Z","iopub.status.idle":"2025-12-07T08:33:52.747364Z","shell.execute_reply.started":"2025-12-07T08:33:52.739578Z","shell.execute_reply":"2025-12-07T08:33:52.746290Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class BASEModel(nn.Module):\n    def __init__(self, num_classes=2):\n        super().__init__()\n        # Offline ResNet18\n        self.backbone = models.resnet18(weights=None)\n\n        # Freeze backbone\n        for p in self.backbone.parameters():\n            p.requires_grad = False\n\n        # Small classifier for fast CPU\n        in_features = self.backbone.fc.in_features\n        self.backbone.fc = nn.Sequential(\n            nn.Linear(in_features, 128),\n            nn.ReLU(),\n            nn.Dropout(0.2),\n            nn.Linear(128, num_classes)\n        )\n\n    def forward(self, x):\n        return self.backbone(x)\n\nmodel = BASEModel().to(device)\nloss_fn = nn.CrossEntropyLoss()\noptimizer = torch.optim.Adam(model.parameters(), lr=0.001)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T08:34:03.476005Z","iopub.execute_input":"2025-12-07T08:34:03.476398Z","iopub.status.idle":"2025-12-07T08:34:03.694303Z","shell.execute_reply.started":"2025-12-07T08:34:03.476370Z","shell.execute_reply":"2025-12-07T08:34:03.693304Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"EPOCHS = 5\ntrain_losses = []\ntest_losses = []\n\nfor epoch in range(EPOCHS):\n    model.train()\n    batch_losses = []\n    for img, labels in train_dl:\n        img, labels = img.to(device), labels.to(device)\n\n        optimizer.zero_grad()\n        pred = model(img)\n        loss = loss_fn(pred, labels)\n        loss.backward()\n        optimizer.step()\n\n        batch_losses.append(loss.item())\n    train_losses.append(np.mean(batch_losses))\n\n    # Test loss\n    model.eval()\n    test_batch_losses = []\n    with torch.no_grad():\n        for img, labels in test_dl:\n            img, labels = img.to(device), labels.to(device)\n            out = model(img)\n            loss = loss_fn(out, labels)\n            test_batch_losses.append(loss.item())\n    test_losses.append(np.mean(test_batch_losses))\n\n    print(f\"Epoch {epoch+1}/{EPOCHS} - Train Loss: {train_losses[-1]:.4f} - Test Loss: {test_losses[-1]:.4f}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T08:58:22.648060Z","iopub.execute_input":"2025-12-07T08:58:22.648919Z","iopub.status.idle":"2025-12-07T09:39:10.019555Z","shell.execute_reply.started":"2025-12-07T08:58:22.648894Z","shell.execute_reply":"2025-12-07T09:39:10.018701Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plt.figure(figsize=(10,5))\nplt.plot(train_losses, label=\"Train Loss\", linewidth=2)\nplt.plot(test_losses, label=\"Test Loss\", linewidth=2)\nplt.xlabel(\"Epoch\")\nplt.ylabel(\"Loss\")\nplt.title(\"Train vs Test Loss\")\nplt.grid(True, linestyle=\"--\", alpha=0.5)\nplt.legend()\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T09:42:09.258718Z","iopub.execute_input":"2025-12-07T09:42:09.260798Z","iopub.status.idle":"2025-12-07T09:42:09.517075Z","shell.execute_reply.started":"2025-12-07T09:42:09.260738Z","shell.execute_reply":"2025-12-07T09:42:09.516040Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model.eval()\nall_preds = []\nall_labels = []\n\nwith torch.no_grad():\n    for img, labels in test_dl:\n        img = img.to(device)\n        out = model(img)\n        preds = torch.argmax(out, dim=1).cpu().numpy()\n\n        all_preds.extend(preds)\n        all_labels.extend(labels.numpy())\n\nacc = accuracy_score(all_labels, all_preds)\nprint(\"Test Accuracy:\", acc)\n\n# Confusion Matrix\ncm = confusion_matrix(all_labels, all_preds)\nplt.figure(figsize=(6,5))\nsns.heatmap(cm, annot=True, fmt=\"d\", cmap=\"Blues\",\n            xticklabels=[\"Authentic\", \"Forged\"],\n            yticklabels=[\"Authentic\", \"Forged\"])\nplt.title(\"Confusion Matrix\")\nplt.show()\n\n# Submission File\nsubmission = pd.DataFrame({\n    \"Id\": test_df.index,\n    \"Predicted\": all_preds\n})\nsubmission_path = \"/kaggle/working/submission.csv\"\nsubmission.to_csv(submission_path, index=False)\nprint(\"submission.csv saved at:\", submission_path)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T09:42:12.227413Z","iopub.execute_input":"2025-12-07T09:42:12.227764Z","iopub.status.idle":"2025-12-07T09:43:47.651161Z","shell.execute_reply.started":"2025-12-07T09:42:12.227714Z","shell.execute_reply":"2025-12-07T09:43:47.650190Z"}},"outputs":[],"execution_count":null}]}