{"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":"nvidiaTeslaT4","dataSources":[{"sourceId":113558,"databundleVersionId":14174843,"sourceType":"competition"}],"dockerImageVersionId":31154,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"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","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-10-24T15:22:50.439000Z","iopub.execute_input":"2025-10-24T15:22:50.439724Z","iopub.status.idle":"2025-10-24T15:22:59.239077Z","shell.execute_reply.started":"2025-10-24T15:22:50.439695Z","shell.execute_reply":"2025-10-24T15:22:59.238275Z"},"_kg_hide-output":true,"_kg_hide-input":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# -------------------------\n# Step 1: Import Libraries\n# -------------------------\nimport os\nimport random\nimport numpy as np\nimport matplotlib.pyplot as plt\nfrom glob import glob\nfrom tqdm.auto import tqdm\n\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.utils.data import Dataset, DataLoader, random_split\nfrom torchvision import transforms\nfrom PIL import Image\nfrom sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score, confusion_matrix\nimport seaborn as sns\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T15:23:45.457523Z","iopub.execute_input":"2025-10-24T15:23:45.457955Z","iopub.status.idle":"2025-10-24T15:23:57.380662Z","shell.execute_reply.started":"2025-10-24T15:23:45.457930Z","shell.execute_reply":"2025-10-24T15:23:57.380055Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# -------------------------\n# Step 2: Configurations\n# -------------------------\nclass CFG:\n    seed = 42\n    img_size = 128       # CNN থেকে scratch হলে ছোট size দিয়ে শুরু করা ভালো\n    batch_size = 32\n    lr = 1e-3\n    epochs = 10\n    device = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n    train_dir = \"/kaggle/input/recodai-luc-scientific-image-forgery-detection/train_images\"\n    test_dir = \"/kaggle/input/recodai-luc-scientific-image-forgery-detection/test_images\"\n    valid_frac = 0.15","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T15:25:20.376840Z","iopub.execute_input":"2025-10-24T15:25:20.377334Z","iopub.status.idle":"2025-10-24T15:25:20.458779Z","shell.execute_reply.started":"2025-10-24T15:25:20.377309Z","shell.execute_reply":"2025-10-24T15:25:20.457876Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# reproducibility\ndef set_seed(seed=CFG.seed):\n    random.seed(seed)\n    np.random.seed(seed)\n    torch.manual_seed(seed)\n    if torch.cuda.is_available():\n        torch.cuda.manual_seed_all(seed)\n\nset_seed()\n\nprint(\"Device:\", CFG.device)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T15:25:44.319823Z","iopub.execute_input":"2025-10-24T15:25:44.320446Z","iopub.status.idle":"2025-10-24T15:25:44.334388Z","shell.execute_reply.started":"2025-10-24T15:25:44.320422Z","shell.execute_reply":"2025-10-24T15:25:44.333755Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Step 3: Custom Dataset\n# -------------------------\nclass ForgeryDataset(Dataset):\n    def __init__(self, root_dir, transform=None):\n        self.root_dir = root_dir\n        self.transform = transform\n        self.images = []\n        self.labels = []\n        for label, cls in enumerate([\"authentic\",\"forged\"]):\n            cls_dir = os.path.join(root_dir, cls)\n            for img_path in glob(os.path.join(cls_dir,\"*\")):\n                self.images.append(img_path)\n                self.labels.append(label)\n    def __len__(self):\n        return len(self.images)\n    def __getitem__(self, idx):\n        img_path = self.images[idx]\n        label = self.labels[idx]\n        img = Image.open(img_path).convert(\"RGB\")\n        if self.transform:\n            img = self.transform(img)\n        return img, label","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T15:26:19.600421Z","iopub.execute_input":"2025-10-24T15:26:19.600692Z","iopub.status.idle":"2025-10-24T15:26:19.606645Z","shell.execute_reply.started":"2025-10-24T15:26:19.600673Z","shell.execute_reply":"2025-10-24T15:26:19.605785Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# -------------------------\n# Step 4: Transform & DataLoader\n# -------------------------\ntrain_transform = transforms.Compose([\n    transforms.Resize((CFG.img_size,CFG.img_size)),\n    transforms.RandomHorizontalFlip(),\n    transforms.RandomRotation(10),\n    transforms.ToTensor(),\n    transforms.Normalize([0.485,0.456,0.406],[0.229,0.224,0.225])\n])\n\nvalid_transform = transforms.Compose([\n    transforms.Resize((CFG.img_size,CFG.img_size)),\n    transforms.ToTensor(),\n    transforms.Normalize([0.485,0.456,0.406],[0.229,0.224,0.225])\n])\n\ndataset = ForgeryDataset(CFG.train_dir, transform=train_transform)\ntotal = len(dataset)\nval_size = int(total*CFG.valid_frac)\ntrain_size = total - val_size\ntrain_ds, valid_ds = random_split(dataset, [train_size, val_size],\n                                  generator=torch.Generator().manual_seed(CFG.seed))\n\n# For validation we override transform\nvalid_ds.dataset.transform = valid_transform\n\ntrain_loader = DataLoader(train_ds, batch_size=CFG.batch_size, shuffle=True, num_workers=2, pin_memory=True)\nvalid_loader = DataLoader(valid_ds, batch_size=CFG.batch_size, shuffle=False, num_workers=2, pin_memory=True)\n\nprint(\"Train size:\", len(train_ds), \"| Valid size:\", len(valid_ds))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T15:26:50.664404Z","iopub.execute_input":"2025-10-24T15:26:50.664682Z","iopub.status.idle":"2025-10-24T15:26:50.690060Z","shell.execute_reply.started":"2025-10-24T15:26:50.664662Z","shell.execute_reply":"2025-10-24T15:26:50.689402Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# -------------------------\n# Step 5: Visualize 20 Images\n# -------------------------\ndef show_samples(loader, n=20):\n    images, labels = next(iter(loader))\n    plt.figure(figsize=(15,6))\n    for i in range(n):\n        plt.subplot(2,10,i+1)\n        img = images[i].permute(1,2,0).numpy()\n        img = np.clip(img*0.229+0.485,0,1)  # denormalize approx\n        plt.imshow(img)\n        plt.axis('off')\n        plt.title(\"authentic\" if labels[i]==0 else \"forged\",fontsize=8)\n    plt.show()\n\nshow_samples(train_loader)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T15:27:19.913969Z","iopub.execute_input":"2025-10-24T15:27:19.914627Z","iopub.status.idle":"2025-10-24T15:27:25.123947Z","shell.execute_reply.started":"2025-10-24T15:27:19.914604Z","shell.execute_reply":"2025-10-24T15:27:25.122870Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# -------------------------\n# Step 6: CNN Model (from scratch)\n# -------------------------\nclass SimpleCNN(nn.Module):\n    def __init__(self):\n        super(SimpleCNN, self).__init__()\n        self.conv1 = nn.Conv2d(3,32,3,padding=1)\n        self.conv2 = nn.Conv2d(32,64,3,padding=1)\n        self.conv3 = nn.Conv2d(64,128,3,padding=1)\n        self.pool = nn.MaxPool2d(2,2)\n        self.dropout = nn.Dropout(0.3)\n        self.fc1 = nn.Linear(128*(CFG.img_size//8)*(CFG.img_size//8), 128)\n        self.fc2 = nn.Linear(128,1)\n    def forward(self,x):\n        x = F.relu(self.conv1(x))\n        x = self.pool(x)\n        x = F.relu(self.conv2(x))\n        x = self.pool(x)\n        x = F.relu(self.conv3(x))\n        x = self.pool(x)\n        x = x.view(x.size(0), -1)\n        x = self.dropout(F.relu(self.fc1(x)))\n        x = self.fc2(x)\n        return x\n\nmodel = SimpleCNN().to(CFG.device)\nprint(model)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T15:27:52.561391Z","iopub.execute_input":"2025-10-24T15:27:52.561709Z","iopub.status.idle":"2025-10-24T15:27:52.622513Z","shell.execute_reply.started":"2025-10-24T15:27:52.561686Z","shell.execute_reply":"2025-10-24T15:27:52.621966Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# -------------------------\n# Step 7: Loss & Optimizer\n# -------------------------\ncriterion = nn.BCEWithLogitsLoss()\noptimizer = torch.optim.Adam(model.parameters(), lr=CFG.lr)\nscheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, mode='max', factor=0.5, patience=2, verbose=True)\n\n# -------------------------","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T15:28:24.114540Z","iopub.execute_input":"2025-10-24T15:28:24.114839Z","iopub.status.idle":"2025-10-24T15:28:24.123454Z","shell.execute_reply.started":"2025-10-24T15:28:24.114822Z","shell.execute_reply":"2025-10-24T15:28:24.122668Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Step 8: Training & Validation Functions\n# -------------------------\ndef train_epoch(model, loader, optimizer, criterion, device):\n    model.train()\n    losses, preds_all, targets_all = [],[],[]\n    for imgs, labels in tqdm(loader, leave=False):\n        imgs, labels = imgs.to(device), labels.float().unsqueeze(1).to(device)\n        optimizer.zero_grad()\n        outputs = model(imgs)\n        loss = criterion(outputs, labels)\n        loss.backward()\n        optimizer.step()\n        losses.append(loss.item())\n        preds_all.extend(torch.sigmoid(outputs).detach().cpu().numpy().flatten())\n        targets_all.extend(labels.detach().cpu().numpy().flatten())\n    pred_labels = [1 if p>0.5 else 0 for p in preds_all]\n    acc = accuracy_score(targets_all, pred_labels)\n    f1 = f1_score(targets_all, pred_labels)\n    return np.mean(losses), acc, f1\n\ndef valid_epoch(model, loader, criterion, device):\n    model.eval()\n    losses, preds_all, targets_all = [],[],[]\n    with torch.no_grad():\n        for imgs, labels in tqdm(loader, leave=False):\n            imgs, labels = imgs.to(device), labels.float().unsqueeze(1).to(device)\n            outputs = model(imgs)\n            loss = criterion(outputs, labels)\n            losses.append(loss.item())\n            preds_all.extend(torch.sigmoid(outputs).cpu().numpy().flatten())\n            targets_all.extend(labels.cpu().numpy().flatten())\n    pred_labels = [1 if p>0.5 else 0 for p in preds_all]\n    acc = accuracy_score(targets_all, pred_labels)\n    f1 = f1_score(targets_all, pred_labels)\n    return np.mean(losses), acc, f1, targets_all, pred_labels","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T15:29:04.425157Z","iopub.execute_input":"2025-10-24T15:29:04.425824Z","iopub.status.idle":"2025-10-24T15:29:04.434318Z","shell.execute_reply.started":"2025-10-24T15:29:04.425801Z","shell.execute_reply":"2025-10-24T15:29:04.433464Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Step 9: Training Loop\n# -------------------------\ntrain_losses, valid_losses, train_f1s, valid_f1s = [],[],[],[]\nbest_f1 = 0\n\nfor epoch in range(CFG.epochs):\n    print(f\"\\nEpoch {epoch+1}/{CFG.epochs}\")\n    train_loss, train_acc, train_f1 = train_epoch(model, train_loader, optimizer, criterion, CFG.device)\n    valid_loss, valid_acc, valid_f1, valid_targets, valid_preds = valid_epoch(model, valid_loader, criterion, CFG.device)\n    scheduler.step(valid_f1)\n\n    train_losses.append(train_loss)\n    valid_losses.append(valid_loss)\n    train_f1s.append(train_f1)\n    valid_f1s.append(valid_f1)\n\n    print(f\"Train Loss: {train_loss:.4f} | Acc: {train_acc:.4f} | F1: {train_f1:.4f}\")\n    print(f\"Valid Loss: {valid_loss:.4f} | Acc: {valid_acc:.4f} | F1: {valid_f1:.4f}\")\n\n    if valid_f1>best_f1:\n        best_f1 = valid_f1\n        torch.save(model.state_dict(),\"best_model_cnn.pth\")\n        print(\">> Saved best model!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T15:29:35.306341Z","iopub.execute_input":"2025-10-24T15:29:35.306867Z","iopub.status.idle":"2025-10-24T15:48:54.358714Z","shell.execute_reply.started":"2025-10-24T15:29:35.306844Z","shell.execute_reply":"2025-10-24T15:48:54.357750Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Step 10: Learning Rate Curve & Loss Curve\n# -------------------------\nplt.figure(figsize=(8,4))\nplt.plot(train_losses,label=\"train_loss\")\nplt.plot(valid_losses,label=\"valid_loss\")\nplt.legend(); plt.title(\"Loss Curve\")\nplt.show()\n\nplt.figure(figsize=(8,4))\nplt.plot(train_f1s,label=\"train_f1\")\nplt.plot(valid_f1s,label=\"valid_f1\")\nplt.legend(); plt.title(\"F1 Curve\")\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T15:52:52.592889Z","iopub.execute_input":"2025-10-24T15:52:52.593845Z","iopub.status.idle":"2025-10-24T15:53:43.019468Z","shell.execute_reply.started":"2025-10-24T15:52:52.593813Z","shell.execute_reply":"2025-10-24T15:53:43.018730Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# -------------------------\n# Step 11: Confusion Matrix (Validation)\n# -------------------------\ncm = confusion_matrix(valid_targets, valid_preds)\nplt.figure(figsize=(5,4))\nsns.heatmap(cm, annot=True, fmt=\"d\", cmap=\"Blues\")\nplt.xlabel(\"Predicted\"); plt.ylabel(\"Actual\")\nplt.xticks([0.5,1.5], [\"authentic\",\"forged\"])\nplt.yticks([0.5,1.5], [\"authentic\",\"forged\"])\nplt.title(\"Confusion Matrix (Validation)\")\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T15:54:23.481968Z","iopub.execute_input":"2025-10-24T15:54:23.482236Z","iopub.status.idle":"2025-10-24T15:54:23.659417Z","shell.execute_reply.started":"2025-10-24T15:54:23.482220Z","shell.execute_reply":"2025-10-24T15:54:23.658465Z"}},"outputs":[],"execution_count":null}]}