{"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":"import os, glob, random\nimport numpy as np\nimport matplotlib.pyplot as plt\nfrom PIL import Image\nimport torch, torch.nn as nn\nfrom torch.utils.data import Dataset, DataLoader, random_split\nfrom sklearn.metrics import confusion_matrix, classification_report, ConfusionMatrixDisplay\n\nSEED = 42\nrandom.seed(SEED); np.random.seed(SEED); torch.manual_seed(SEED)\n\nDATA_DIR = \"/kaggle/input/dogs-vs-cats/train\"   # <-- verify this path\nIMG_SIZE = 64          # 64x64 keeps the model small\nN_PER_CLASS = 4000     # subset for speed; use 12500 for the full set\n\nfiles = glob.glob(os.path.join(DATA_DIR, \"*.jpg\"))\ncats = [f for f in files if os.path.basename(f).starAtswith(\"cat\")][:N_PER_CLASS]\ndogs = [f for f in files if os.path.basename(f).startswith(\"dog\")][:N_PER_CLASS]\nall_files = cats + dogs\nlabels = [0]*len(cats) + [1]*len(dogs)          # cat=0, dog=1\nprint(\"Total images:\", len(all_files), \"| cats:\", len(cats), \"| dogs:\", len(dogs))","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-09-29T05:22:06.445137Z","iopub.execute_input":"2026-09-29T05:22:06.445404Z","iopub.status.idle":"2026-09-29T05:22:11.406869Z","shell.execute_reply.started":"2026-09-29T05:22:06.445369Z","shell.execute_reply":"2026-09-29T05:22:11.405888Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os, glob, random\nimport numpy as np\nimport matplotlib.pyplot as plt\nfrom PIL import Image\nimport torch, torch.nn as nn\nfrom torch.utils.data import Dataset, DataLoader, random_split\nfrom sklearn.metrics import confusion_matrix, classification_report, ConfusionMatrixDisplay\n\nSEED = 42\nrandom.seed(SEED); np.random.seed(SEED); torch.manual_seed(SEED)\n\nDATA_DIR = \"/kaggle/input/dogs-vs-cats-redux-kernels-edition/train\"\nIMG_SIZE = 64\nN_PER_CLASS = 4000\n\nfiles = sorted(glob.glob(os.path.join(DATA_DIR, \"*.jpg\")))\ncats = [f for f in files if os.path.basename(f).startswith(\"cat\")][:N_PER_CLASS]\ndogs = [f for f in files if os.path.basename(f).startswith(\"dog\")][:N_PER_CLASS]\nall_files = cats + dogs\nlabels = [0]*len(cats) + [1]*len(dogs)\nprint(\"Total images:\", len(all_files), \"| cats:\", len(cats), \"| dogs:\", len(dogs))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-29T05:25:42.017854Z","iopub.execute_input":"2026-09-29T05:25:42.018619Z","iopub.status.idle":"2026-09-29T05:25:42.028944Z","shell.execute_reply.started":"2026-09-29T05:25:42.018572Z","shell.execute_reply":"2026-09-29T05:25:42.028029Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nfor root, dirs, files in os.walk(\"/kaggle/input\"):\n    print(root, \"| folders:\", dirs[:5], \"| files:\", files[:3])\n    if root.count(\"/\") >= 5:\n        dirs.clear()   # don't go deeper","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-29T05:26:28.38447Z","iopub.execute_input":"2026-09-29T05:26:28.38485Z","iopub.status.idle":"2026-09-29T05:26:28.393912Z","shell.execute_reply.started":"2026-09-29T05:26:28.384819Z","shell.execute_reply":"2026-09-29T05:26:28.39307Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import zipfile, os\n\nZIP_PATH = \"/kaggle/input/competitions/dogs-vs-cats-redux-kernels-edition/train.zip\"\nwith zipfile.ZipFile(ZIP_PATH) as z:\n    z.extractall(\"/tmp/data\")\n\nprint(os.listdir(\"/tmp/data\"))\nprint(len(os.listdir(\"/tmp/data/train\")))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-29T05:31:46.890086Z","iopub.execute_input":"2026-09-29T05:31:46.890824Z","iopub.status.idle":"2026-09-29T05:31:56.54935Z","shell.execute_reply.started":"2026-09-29T05:31:46.890794Z","shell.execute_reply":"2026-09-29T05:31:56.548598Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os, glob, random\nimport numpy as np\nimport matplotlib.pyplot as plt\nfrom PIL import Image\nimport torch, torch.nn as nn\nfrom torch.utils.data import Dataset, DataLoader, random_split\nfrom sklearn.metrics import confusion_matrix, classification_report, ConfusionMatrixDisplay\n\nSEED = 42\nrandom.seed(SEED); np.random.seed(SEED); torch.manual_seed(SEED)\n\nDATA_DIR = \"/tmp/data/train\"\nIMG_SIZE = 64\nN_PER_CLASS = 4000\n\nfiles = sorted(glob.glob(os.path.join(DATA_DIR, \"*.jpg\")))\ncats = [f for f in files if os.path.basename(f).startswith(\"cat\")][:N_PER_CLASS]\ndogs = [f for f in files if os.path.basename(f).startswith(\"dog\")][:N_PER_CLASS]\nall_files = cats + dogs\nlabels = [0]*len(cats) + [1]*len(dogs)\nprint(\"Total images:\", len(all_files), \"| cats:\", len(cats), \"| dogs:\", len(dogs))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-29T05:31:56.550991Z","iopub.execute_input":"2026-09-29T05:31:56.551271Z","iopub.status.idle":"2026-09-29T05:31:56.654358Z","shell.execute_reply.started":"2026-09-29T05:31:56.551244Z","shell.execute_reply":"2026-09-29T05:31:56.653594Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class CatDogDataset(Dataset):\n    def __init__(self, files, labels):\n        self.files, self.labels = files, labels\n    def __len__(self): return len(self.files)\n    def __getitem__(self, i):\n        img = Image.open(self.files[i]).convert(\"RGB\").resize((IMG_SIZE, IMG_SIZE))\n        x = torch.tensor(np.array(img), dtype=torch.float32).permute(2,0,1) / 255.0\n        return x, torch.tensor(self.labels[i], dtype=torch.float32)\n\nfull = CatDogDataset(all_files, labels)\nn = len(full)\nn_train, n_val = int(0.7*n), int(0.15*n)\nn_test = n - n_train - n_val\ntrain_ds, val_ds, test_ds = random_split(full, [n_train, n_val, n_test],\n                                         generator=torch.Generator().manual_seed(SEED))\ntrain_dl = DataLoader(train_ds, batch_size=64, shuffle=True,  num_workers=2)\nval_dl   = DataLoader(val_ds,   batch_size=64, shuffle=False, num_workers=2)\ntest_dl  = DataLoader(test_ds,  batch_size=64, shuffle=False, num_workers=2)\nprint(\"Train:\", n_train, \"Val:\", n_val, \"Test:\", n_test)\n\nfig, ax = plt.subplots(2, 5, figsize=(12,5))\nfor i, a in enumerate(ax.flat):\n    x, y = full[i*(n//10)]\n    a.imshow(x.permute(1,2,0)); a.set_title(\"Dog\" if y==1 else \"Cat\"); a.axis(\"off\")\nplt.tight_layout(); plt.savefig(\"samples.png\", dpi=150); plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-29T05:32:45.356834Z","iopub.execute_input":"2026-09-29T05:32:45.357174Z","iopub.status.idle":"2026-09-29T05:32:46.512096Z","shell.execute_reply.started":"2026-09-29T05:32:45.357145Z","shell.execute_reply":"2026-09-29T05:32:46.511319Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"device = \"cuda\" if torch.cuda.is_available() else \"cpu\"\nprint(\"Using:\", device)\n\nclass LogisticRegression(nn.Module):\n    def __init__(self, in_features):\n        super().__init__()\n        self.linear = nn.Linear(in_features, 1)\n    def forward(self, x):\n        return self.linear(x.flatten(1)).squeeze(1)\n\nmodel = LogisticRegression(3*IMG_SIZE*IMG_SIZE).to(device)\ncriterion = nn.BCEWithLogitsLoss()\noptimizer = torch.optim.Adam(model.parameters(), lr=1e-4, weight_decay=1e-4)\n\nprint(model)\nprint(\"Number of parameters:\", sum(p.numel() for p in model.parameters()))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-29T05:33:49.832223Z","iopub.execute_input":"2026-09-29T05:33:49.832923Z","iopub.status.idle":"2026-09-29T05:33:52.779393Z","shell.execute_reply.started":"2026-09-29T05:33:49.832892Z","shell.execute_reply":"2026-09-29T05:33:52.778578Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def run_epoch(dl, train=False):\n    model.train(train)\n    total_loss, correct, count = 0, 0, 0\n    with torch.set_grad_enabled(train):\n        for x, y in dl:\n            x, y = x.to(device), y.to(device)\n            logits = model(x)\n            loss = criterion(logits, y)\n            if train:\n                optimizer.zero_grad(); loss.backward(); optimizer.step()\n            total_loss += loss.item()*len(y)\n            correct += ((logits > 0).float() == y).sum().item()\n            count += len(y)\n    return total_loss/count, correct/count\n\nhistory = {\"train_loss\":[], \"val_loss\":[], \"train_acc\":[], \"val_acc\":[]}\nEPOCHS = 30\nfor ep in range(1, EPOCHS+1):\n    tl, ta = run_epoch(train_dl, train=True)\n    vl, va = run_epoch(val_dl)\n    for k, v in zip(history, [tl, vl, ta, va]): history[k].append(v)\n    print(f\"Epoch {ep:02d} | train loss {tl:.4f} acc {ta:.3f} | val loss {vl:.4f} acc {va:.3f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-29T05:35:08.488111Z","iopub.execute_input":"2026-09-29T05:35:08.489284Z","iopub.status.idle":"2026-09-29T05:40:55.814234Z","shell.execute_reply.started":"2026-09-29T05:35:08.489247Z","shell.execute_reply":"2026-09-29T05:40:55.813271Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"fig, ax = plt.subplots(1, 2, figsize=(12,4))\nax[0].plot(history[\"train_loss\"], label=\"Train\"); ax[0].plot(history[\"val_loss\"], label=\"Validation\")\nax[0].set_title(\"Loss\"); ax[0].set_xlabel(\"Epoch\"); ax[0].legend(); ax[0].grid(alpha=.3)\nax[1].plot(history[\"train_acc\"], label=\"Train\"); ax[1].plot(history[\"val_acc\"], label=\"Validation\")\nax[1].set_title(\"Accuracy\"); ax[1].set_xlabel(\"Epoch\"); ax[1].legend(); ax[1].grid(alpha=.3)\nplt.tight_layout(); plt.savefig(\"training_curves.png\", dpi=150); plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-29T05:42:53.290277Z","iopub.execute_input":"2026-09-29T05:42:53.291242Z","iopub.status.idle":"2026-09-29T05:42:53.973234Z","shell.execute_reply.started":"2026-09-29T05:42:53.291201Z","shell.execute_reply":"2026-09-29T05:42:53.972454Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model.eval()\ny_true, y_pred = [], []\nwith torch.no_grad():\n    for x, y in test_dl:\n        p = (model(x.to(device)) > 0).cpu().int()\n        y_pred += p.tolist(); y_true += y.int().tolist()\n\ncm = confusion_matrix(y_true, y_pred)\nConfusionMatrixDisplay(cm, display_labels=[\"Cat\",\"Dog\"]).plot(cmap=\"Blues\", values_format=\"d\")\nplt.title(\"Confusion Matrix (Test Set)\"); plt.savefig(\"confusion_matrix.png\", dpi=150); plt.show()\nprint(classification_report(y_true, y_pred, target_names=[\"Cat\",\"Dog\"]))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-29T05:44:47.351493Z","iopub.execute_input":"2026-09-29T05:44:47.351865Z","iopub.status.idle":"2026-09-29T05:44:49.783423Z","shell.execute_reply.started":"2026-09-29T05:44:47.351834Z","shell.execute_reply":"2026-09-29T05:44:49.782566Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def saliency_map(img_tensor):\n    model.eval()\n    x = img_tensor.unsqueeze(0).to(device).requires_grad_(True)\n    model(x).backward()\n    sal = (x.grad * x).squeeze(0).sum(0).detach().cpu().numpy()\n    sal = np.maximum(sal, 0)\n    return sal / (sal.max() + 1e-8)\n\nfig, ax = plt.subplots(3, 4, figsize=(12,9))\nidx = random.sample(range(len(test_ds)), 6)\nfor k, i in enumerate(idx):\n    x, y = test_ds[i]\n    sal = saliency_map(x)\n    with torch.no_grad(): prob = torch.sigmoid(model(x.unsqueeze(0).to(device))).item()\n    r, c = divmod(k, 2)\n    ax[r, c*2].imshow(x.permute(1,2,0)); ax[r, c*2].axis(\"off\")\n    ax[r, c*2].set_title(f\"True: {'Dog' if y==1 else 'Cat'} | P(dog)={prob:.2f}\")\n    ax[r, c*2+1].imshow(x.permute(1,2,0)); ax[r, c*2+1].imshow(sal, cmap=\"jet\", alpha=0.5)\n    ax[r, c*2+1].axis(\"off\"); ax[r, c*2+1].set_title(\"Grad-CAM style heatmap\")\nplt.tight_layout(); plt.savefig(\"gradcam.png\", dpi=150); plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-29T05:46:02.67266Z","iopub.execute_input":"2026-09-29T05:46:02.673438Z","iopub.status.idle":"2026-09-29T05:46:04.056128Z","shell.execute_reply.started":"2026-09-29T05:46:02.673399Z","shell.execute_reply":"2026-09-29T05:46:04.053971Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}