{"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":"gpu","dataSources":[{"sourceId":113558,"databundleVersionId":14878066,"sourceType":"competition"}],"dockerImageVersionId":31192,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"In the previous notebookwe performed a basic exploratory analysis of the dataset—examining the folder structure and visualizing sample images—to build an initial understanding of the data.\n\nLink to the previous notebook : : https://www.kaggle.com/code/tany1404/recod-ai-luc-eda. \n\nIn this notebook, we begin Phase 1 of our workflow: developing a baseline classification model. The objective is to classify each image into one of two categories: 'authentic' or 'forged'.\nThis will be a straightforward model without heavy optimization, intended primarily to demonstrate the end-to-end training pipeline and establish a foundation for later improvements.","metadata":{}},{"cell_type":"markdown","source":"## Initial Configurations","metadata":{}},{"cell_type":"code","source":"import pathlib\nimport numpy as np\nimport pandas as pd\nimport torch \nfrom PIL import Image\nimport matplotlib.pyplot as plt\nfrom sklearn.model_selection import train_test_split\nimport torch.nn as nn \nfrom torch.utils.data import Dataset, DataLoader\nimport torch.nn.functional as F\nimport torchvision.models as models\nfrom torchvision.transforms import transforms","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-12-23T15:11:05.374819Z","iopub.execute_input":"2025-12-23T15:11:05.375106Z","iopub.status.idle":"2025-12-23T15:11:05.379583Z","shell.execute_reply.started":"2025-12-23T15:11:05.375086Z","shell.execute_reply":"2025-12-23T15:11:05.378893Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"DEVICE = 'cuda' if torch.cuda.is_available() else 'cpu'\nprint(f\"using {DEVICE} device\")\nEPOCHS =  10\nBASE_DIR = pathlib.Path(\"/kaggle/input/recodai-luc-scientific-image-forgery-detection\")\nTRAIN_IMG_DIR = BASE_DIR / \"train_images\"","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-23T15:03:10.904818Z","iopub.execute_input":"2025-12-23T15:03:10.905297Z","iopub.status.idle":"2025-12-23T15:03:10.966746Z","shell.execute_reply.started":"2025-12-23T15:03:10.905271Z","shell.execute_reply":"2025-12-23T15:03:10.965875Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"for i in  TRAIN_IMG_DIR.iterdir():\n    print(i.stem, len(list(i.iterdir())))\n\n# so we have 2 directories in train images\n# also the distribution of data is quite equal, no need to handle imbalance data","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-23T15:03:10.967694Z","iopub.execute_input":"2025-12-23T15:03:10.968023Z","iopub.status.idle":"2025-12-23T15:03:11.037370Z","shell.execute_reply.started":"2025-12-23T15:03:10.967993Z","shell.execute_reply":"2025-12-23T15:03:11.036531Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"AUTHENTIC_DIR = TRAIN_IMG_DIR / 'authentic'\nFORGED_DIR = TRAIN_IMG_DIR / 'forged'","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-23T15:03:11.038039Z","iopub.execute_input":"2025-12-23T15:03:11.038324Z","iopub.status.idle":"2025-12-23T15:03:11.041941Z","shell.execute_reply.started":"2025-12-23T15:03:11.038299Z","shell.execute_reply":"2025-12-23T15:03:11.041187Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Data Preparations","metadata":{}},{"cell_type":"code","source":"# create the data frame for training and testing\n\nIMG_PATH_ARR = []\nfor i in AUTHENTIC_DIR.iterdir():\n    # print(i.name)\n    IMG_PATH_ARR.append({'type': 'authentic', 'path':i})\n\nfor i in FORGED_DIR.iterdir():\n    IMG_PATH_ARR.append({'type': 'forged', 'path':i})\n\nDATA = pd.DataFrame(IMG_PATH_ARR).sample(frac=1).reset_index(drop=True)\n\n# adding labels\nDATA['label'] = DATA['type'].map({'authentic': 0, 'forged': 1})","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-23T15:03:11.043646Z","iopub.execute_input":"2025-12-23T15:03:11.043902Z","iopub.status.idle":"2025-12-23T15:03:11.079200Z","shell.execute_reply.started":"2025-12-23T15:03:11.043877Z","shell.execute_reply":"2025-12-23T15:03:11.078696Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_df, test_df = train_test_split(DATA, test_size=0.2, stratify=DATA['type'], random_state=42)\n\ntrain_df.shape, test_df.shape","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-23T15:03:11.079804Z","iopub.execute_input":"2025-12-23T15:03:11.080045Z","iopub.status.idle":"2025-12-23T15:03:11.091890Z","shell.execute_reply.started":"2025-12-23T15:03:11.080018Z","shell.execute_reply":"2025-12-23T15:03:11.091274Z"}},"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)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-23T15:03:11.092465Z","iopub.execute_input":"2025-12-23T15:03:11.092624Z","iopub.status.idle":"2025-12-23T15:03:11.101079Z","shell.execute_reply.started":"2025-12-23T15:03:11.092611Z","shell.execute_reply":"2025-12-23T15:03:11.100407Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"transform = 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])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-23T15:03:11.101799Z","iopub.execute_input":"2025-12-23T15:03:11.102051Z","iopub.status.idle":"2025-12-23T15:03:11.114699Z","shell.execute_reply.started":"2025-12-23T15:03:11.102025Z","shell.execute_reply":"2025-12-23T15:03:11.113984Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_dataset = LUCDataset(train_df, transform)\ntrain_dataloader = DataLoader(train_dataset, batch_size=16, shuffle=True)\n\ntest_dataset = LUCDataset(test_df, transform)\ntest_dataloader = DataLoader(test_dataset, batch_size=16)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-23T15:03:11.115409Z","iopub.execute_input":"2025-12-23T15:03:11.115652Z","iopub.status.idle":"2025-12-23T15:03:11.133930Z","shell.execute_reply.started":"2025-12-23T15:03:11.115633Z","shell.execute_reply":"2025-12-23T15:03:11.133421Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Model building","metadata":{}},{"cell_type":"code","source":"class BASEModel(nn.Module):\n    def __init__(self, num_classes=2):\n        super().__init__()\n\n        self.backbone = models.resnet18(weights=models.ResNet18_Weights.DEFAULT)\n\n        for param in self.backbone.parameters():\n            param.requires_grad = False\n\n        in_features = self.backbone.fc.in_features\n        self.backbone.fc = nn.Sequential(\n                nn.Linear(in_features, 512),\n                nn.ReLU(),\n                nn.Linear(512, 64),\n                nn.ReLU(),\n                nn.Linear(64, 2)\n                )\n\n    def forward(self, x):\n        return self.backbone(x)\n        ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-23T15:03:11.134582Z","iopub.execute_input":"2025-12-23T15:03:11.134921Z","iopub.status.idle":"2025-12-23T15:03:11.148550Z","shell.execute_reply.started":"2025-12-23T15:03:11.134897Z","shell.execute_reply":"2025-12-23T15:03:11.147793Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"BASE_MODEL = BASEModel(2).to(DEVICE)\nLOSS_FN = nn.CrossEntropyLoss()\nOPTIMIZER = torch.optim.Adam(BASE_MODEL.parameters(), lr=0.001)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-23T15:03:11.149366Z","iopub.execute_input":"2025-12-23T15:03:11.149591Z","iopub.status.idle":"2025-12-23T15:03:11.802661Z","shell.execute_reply.started":"2025-12-23T15:03:11.149570Z","shell.execute_reply":"2025-12-23T15:03:11.802069Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Training","metadata":{}},{"cell_type":"code","source":"TOTAL_TRAIN_LOSS = []\nTOTAL_TEST_LOSS = []\nfor epoch in range(EPOCHS):\n    LOSS_ARR = []\n    for img, label in train_dataloader:\n        img = img.to(DEVICE)\n        label = label.to(DEVICE)\n\n        OPTIMIZER.zero_grad()\n        pred_val = BASE_MODEL(img)\n        loss = LOSS_FN(pred_val, label)\n        loss.backward()\n        OPTIMIZER.step()\n        \n        LOSS_ARR.append(loss.item())\n    TOTAL_TRAIN_LOSS.append(np.mean(LOSS_ARR))\n\n    ##  testing loop\n    BASE_MODEL.eval()\n    LOSS_ARR = []\n    with torch.no_grad():\n        for img, label in test_dataloader:\n            img = img.to(DEVICE)\n            label = label.to(DEVICE)\n\n            pred_val = BASE_MODEL(img)\n            loss = LOSS_FN(pred_val, label)\n            LOSS_ARR.append(loss.item())\n        TOTAL_TEST_LOSS.append(np.mean(LOSS_ARR))\n    \n    print(f\"Epoch {epoch+1}/{EPOCHS} → Train Loss: {TOTAL_TRAIN_LOSS[-1]:.4f}, Test Loss: {TOTAL_TEST_LOSS[-1]:.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-23T15:03:11.803471Z","iopub.execute_input":"2025-12-23T15:03:11.803719Z","iopub.status.idle":"2025-12-23T15:07:01.455326Z","shell.execute_reply.started":"2025-12-23T15:03:11.803699Z","shell.execute_reply":"2025-12-23T15:07:01.454482Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\nplt.figure(figsize=(12, 6))\n\n\nplt.plot(np.arange(EPOCHS), TOTAL_TRAIN_LOSS, label=\"Train Loss\",linewidth=2)\n\nplt.plot(np.arange(EPOCHS), TOTAL_TEST_LOSS, label=\"Test Loss\",\n    linewidth=2\n)\n\n# Labels and title\nplt.xlabel(\"Epochs\", fontsize=12)\nplt.ylabel(\"Loss\", fontsize=12)\nplt.title(\"Train Loss vs Test Loss Over Epochs\", fontsize=14)\n\n# Show epoch markers\nplt.xticks(np.arange(EPOCHS))\n\n# Grid for clarity\nplt.grid(True, linestyle=\"--\", alpha=0.5)\n\n# Legend\nplt.legend(fontsize=12)\n\n# Tight layout for better spacing\nplt.tight_layout()\n\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-23T15:07:01.456209Z","iopub.execute_input":"2025-12-23T15:07:01.456963Z","iopub.status.idle":"2025-12-23T15:07:01.671417Z","shell.execute_reply.started":"2025-12-23T15:07:01.456937Z","shell.execute_reply":"2025-12-23T15:07:01.670683Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Test Image\n","metadata":{}},{"cell_type":"code","source":"TEST_DIR = BASE_DIR / \"test_images\"\n\nfor img_path in TEST_DIR.iterdir():\n    img = Image.open(img_path).convert(\"RGB\")\n    to_tensor = transforms.ToTensor()\n\n    img_tensor = to_tensor(img).to(DEVICE).unsqueeze(dim=0)\n    pred_val = BASE_MODEL(img_tensor)\n\n    probs = F.softmax(pred_val, dim=1)\n    print(f\"probability that given image is authentic is {probs[0][0].item()}\")\n    print(f\"probability that given image is forged is {probs[0][1].item()}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-23T15:14:11.960690Z","iopub.execute_input":"2025-12-23T15:14:11.961274Z","iopub.status.idle":"2025-12-23T15:14:12.064015Z","shell.execute_reply.started":"2025-12-23T15:14:11.961252Z","shell.execute_reply":"2025-12-23T15:14:12.063260Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"This notebook is just the demonstration of how we can utilize the image classification to classify the given image. \n\n\nIn the next phase we will look at the Segmentation Model, in which for the forged images, we will train a model which gives it's mask. \n\nLink for Phase 2: https://www.kaggle.com/code/tany1404/recod-ai-luc-base-model-phase-2-v0","metadata":{}},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}