{"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":11848,"databundleVersionId":862157,"sourceType":"competition"}],"dockerImageVersionId":31153,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"\nprint(\"> > > > START > > > >\", flush=True)\n\n# Backend\nimport matplotlib\nmatplotlib.use(\"Agg\")\n\n# Imports\nimport os, random\nimport pandas as pd\nimport numpy as np\nfrom pathlib import Path\nfrom PIL import Image\nimport matplotlib.pyplot as plt\nimport torch\nimport torch.nn as nn\nfrom torch.utils.data import Dataset, DataLoader\nimport torchvision.transforms as T\nimport torchvision.models as models\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import (\n    roc_auc_score, roc_curve, confusion_matrix,\n    precision_recall_fscore_support, accuracy_score\n)\n\n\n\n# Helper\nfrom IPython.display import Image as _IPImage, display as _display\ndef _show_png(p):\n    p = Path(p)\n    if p.exists():\n        _display(_IPImage(filename=str(p)))\n    else:\n        print(f\"[warn] missing: {p}\", flush=True)\n\n\n\n# TorchVision Compatibility\ntry:\n    _ = models.ResNet18_Weights.IMAGENET1K_V1\n    def make_resnet18():\n        return models.resnet18(weights=models.ResNet18_Weights.IMAGENET1K_V1)\nexcept AttributeError:\n    def make_resnet18():\n        return models.resnet18(pretrained=True)\n\n\n\n# Logger\nLOGFILE = Path(\"/kaggle/working/run.log\")\ndef log(msg):\n    print(msg, flush=True)\n    with open(LOGFILE, \"a\", encoding=\"utf-8\") as f:\n        f.write(str(msg) + \"\\n\")\n\n\n\n# Seed\nSEED = 42\nrandom.seed(SEED); np.random.seed(SEED); torch.manual_seed(SEED)\nif torch.cuda.is_available():\n    torch.cuda.manual_seed_all(SEED)\n\n\n\n# Paths\nDATA = Path(\"/kaggle/input/histopathologic-cancer-detection\")\nTRAIN_IMG = DATA/\"train\"\nTRAIN_CSV = DATA/\"train_labels.csv\"\nTEST_IMG  = DATA/\"test\"\nWORK = Path(\"/kaggle/working\")\nSAMPLE_SUB = DATA/\"sample_submission.csv\"\nIMG_EXT = \"tif\"\n\n\n\n# Quick Check\nif not (DATA.exists() and TRAIN_IMG.exists() and TRAIN_CSV.exists()):\n    log(\"DATA NOT FOUND — attach 'Histopathologic Cancer Detection'\")\n    raise SystemExit(1)\nlog(f\"DATA: {DATA.exists()} | TRAIN_IMG: {TRAIN_IMG.exists()} | TEST_IMG: {TEST_IMG.exists()}\")\nlog(f\"TRAIN_CSV: {TRAIN_CSV.exists()} | SAMPLE_SUB: {SAMPLE_SUB.exists()}\")\n\n\n\n# Load Labels\ndf = pd.read_csv(TRAIN_CSV)\nlog((\"labels shape:\", df.shape))\nlog(df.head())\n\n\n\n# EDA\nplt.figure()\ndf[\"label\"].value_counts().plot(kind=\"bar\", title=\"class counts\")\nplt.xlabel(\"label\"); plt.ylabel(\"count\")\nplt.tight_layout(); plt.savefig(WORK/\"class_counts.png\"); plt.show()\n_show_png(WORK/\"class_counts.png\")\n\n\n\n# Split\ntr_df, va_df = train_test_split(df, test_size=0.1, stratify=df.label, random_state=42)\ntr_df = tr_df.sample(2000, random_state=0)\nva_df = va_df.sample(500,  random_state=0)\n\n\n\n# Transforms\nIMG_SIZE = 64\nt_train = T.Compose([\n    T.Resize((IMG_SIZE, IMG_SIZE)),\n    T.RandomHorizontalFlip(),\n    T.ToTensor(),\n    T.Normalize([0.485,0.456,0.406],[0.229,0.224,0.225]),\n])\nt_val = T.Compose([\n    T.Resize((IMG_SIZE, IMG_SIZE)),\n    T.ToTensor(),\n    T.Normalize([0.485,0.456,0.406],[0.229,0.224,0.225]),\n])\n\n\n\n# Dataset\nclass HistoDS(Dataset):\n    def __init__(self, df, folder, tfm, with_label=True):\n        self.df = df.reset_index(drop=True)\n        self.folder = folder\n        self.tfm = tfm\n        self.with_label = with_label\n    def __len__(self): return len(self.df)\n    def __getitem__(self, i):\n        rid = self.df.loc[i, \"id\"]\n        img = Image.open(self.folder/f\"{rid}.{IMG_EXT}\").convert(\"RGB\")\n        x = self.tfm(img)\n        if self.with_label:\n            y = torch.tensor(self.df.loc[i, \"label\"], dtype=torch.float32)\n            return x, y\n        return x, rid\n\n\n\n# Loaders\ntrain_dl = DataLoader(HistoDS(tr_df, TRAIN_IMG, t_train, True), batch_size=32, shuffle=True, num_workers=0)\nval_dl   = DataLoader(HistoDS(va_df, TRAIN_IMG, t_val,   True), batch_size=64, shuffle=False, num_workers=0)\n\n\n\n# Model\ndevice = \"cuda\" if torch.cuda.is_available() else \"cpu\"\nmodel = make_resnet18()\nmodel.fc = nn.Linear(model.fc.in_features, 1)\nmodel = model.to(device)\nloss_fn = nn.BCEWithLogitsLoss()\nopt = torch.optim.Adam(model.parameters(), lr=1e-3)\nlog(f\"device={device} | train={len(tr_df)} | val={len(va_df)}\")\n\n\n\n# Smoke Batch\nxb, yb = next(iter(train_dl))\nlog((\"smoke batch:\", tuple(xb.shape), tuple(yb.shape)))\n\n\n\n# Validation Helpers\ndef val_preds():\n    model.eval()\n    ys, ps = [], []\n    with torch.inference_mode():\n        for xb, yb in val_dl:\n            xb = xb.to(device)\n            p = torch.sigmoid(model(xb)).squeeze(1).cpu().numpy()\n            ys.append(yb.numpy()); ps.append(p)\n    ys = np.concatenate(ys); ps = np.concatenate(ps)\n    return ys, ps\n\ndef val_auc():\n    ys, ps = val_preds()\n    return roc_auc_score(ys, ps)\n\n\n\n# Train\nEPOCHS = 2\nbest_auc, best_path = 0.0, WORK/\"best_resnet18.pt\"\ntrain_losses, val_aucs = [], []\nfor e in range(EPOCHS):\n    model.train()\n    run_loss = 0.0\n    for i, (xb, yb) in enumerate(train_dl, 1):\n        xb, yb = xb.to(device), yb.to(device)\n        opt.zero_grad()\n        out = model(xb).squeeze(1)\n        loss = loss_fn(out, yb)\n        loss.backward()\n        opt.step()\n        run_loss += loss.item()\n        if i % 50 == 0:\n            log(f\"epoch {e+1} step {i}/{len(train_dl)}\")\n    avg_loss = run_loss / max(1, len(train_dl))\n    auc = val_auc()\n    train_losses.append(avg_loss); val_aucs.append(auc)\n    log(f\"epoch {e+1} loss {avg_loss:.4f} | val_auc {auc:.4f}\")\n    if auc > best_auc:\n        best_auc = auc\n        torch.save(model.state_dict(), best_path)\n        log(f\"new best auc {best_auc:.4f} saved to {best_path}\")\n\n\n\n# Reload Best\nif best_path.exists():\n    model.load_state_dict(torch.load(best_path, map_location=device))\n    log(f\"reloaded best model (AUC {best_auc:.4f})\")\n\n\n\n# Plots: Curves\nplt.figure(figsize=(6,3))\nplt.plot(train_losses, label=\"train_loss\")\nplt.plot(val_aucs, label=\"val_auc\")\nplt.title(\"loss / AUC by epoch\")\nplt.xlabel(\"epoch\"); plt.legend(); plt.tight_layout()\nplt.savefig(WORK/\"training_curves.png\"); plt.show()\n_show_png(WORK/\"training_curves.png\")\n\n\n\n# Plots: ROC\nys_val, ps_val = val_preds()\nfpr, tpr, thr = roc_curve(ys_val, ps_val)\nplt.figure()\nplt.plot(fpr, tpr, label=f\"ROC (AUC={roc_auc_score(ys_val, ps_val):.4f})\")\nplt.plot([0,1],[0,1],'--')\nplt.xlabel(\"False Positive Rate\"); plt.ylabel(\"True Positive Rate\")\nplt.title(\"Validation ROC\")\nplt.legend(); plt.tight_layout()\nplt.savefig(WORK/\"val_roc.png\"); plt.show()\n_show_png(WORK/\"val_roc.png\")\n\n\n\n# Plots: Probability Histogram\nplt.figure()\nplt.hist(ps_val[ys_val==0], bins=30, alpha=0.7, label=\"negatives\")\nplt.hist(ps_val[ys_val==1], bins=30, alpha=0.7, label=\"positives\")\nplt.xlabel(\"predicted probability\"); plt.ylabel(\"count\")\nplt.title(\"Validation predicted probability distribution\")\nplt.legend(); plt.tight_layout()\nplt.savefig(WORK/\"val_pred_hist.png\"); plt.show()\n_show_png(WORK/\"val_pred_hist.png\")\n\n\n\n# Plots: Confusion Matrix\nyhat_val = (ps_val >= 0.5).astype(int)\ncm = confusion_matrix(ys_val, yhat_val, labels=[0,1])\nplt.figure()\nplt.imshow(cm, interpolation=\"nearest\")\nplt.title(\"Confusion Matrix (thr=0.5)\")\nplt.colorbar()\ntick_marks = np.arange(2)\nplt.xticks(tick_marks, [\"0\",\"1\"])\nplt.yticks(tick_marks, [\"0\",\"1\"])\nfor i in range(2):\n    for j in range(2):\n        plt.text(j, i, cm[i, j], ha=\"center\", va=\"center\")\nplt.xlabel(\"Predicted\"); plt.ylabel(\"True\")\nplt.tight_layout()\nplt.savefig(WORK/\"val_confusion_matrix.png\"); plt.show()\n_show_png(WORK/\"val_confusion_matrix.png\")\n\n\n\n# Plots: Sample Tiles\ndef show_val_samples(n=16):\n    ids = va_df[\"id\"].values[:n]\n    lbls = va_df[\"label\"].values[:n]\n    imgs = []\n    probs = []\n    model.eval()\n    with torch.inference_mode():\n        for rid in ids:\n            img = Image.open(TRAIN_IMG/f\"{rid}.{IMG_EXT}\").convert(\"RGB\")\n            x = t_val(img).unsqueeze(0).to(device)\n            p = torch.sigmoid(model(x)).item()\n            imgs.append(img.resize((IMG_SIZE, IMG_SIZE)))\n            probs.append(p)\n    cols = int(np.sqrt(n)); rows = int(np.ceil(n/cols))\n    plt.figure(figsize=(cols*2.2, rows*2.2))\n    for i,(im, p, y) in enumerate(zip(imgs, probs, lbls)):\n        ax = plt.subplot(rows, cols, i+1)\n        ax.imshow(im)\n        ax.axis(\"off\")\n        ax.set_title(f\"y={int(y)} | p={p:.2f}\", fontsize=8)\n    plt.suptitle(\"Validation samples with predicted p(1)\")\n    plt.tight_layout(rect=[0,0,1,0.95])\n    plt.savefig(WORK/\"val_samples_grid.png\"); plt.show()\n\nshow_val_samples(16)\n_show_png(WORK/\"val_samples_grid.png\")\n\n\n\n# Submit\nsub = pd.read_csv(SAMPLE_SUB)\ntest_ds = HistoDS(sub[[\"id\"]], TEST_IMG, t_val, with_label=False)\ntest_dl = DataLoader(test_ds, batch_size=64, shuffle=False, num_workers=0)\nmodel.eval()\npred = []\nwith torch.inference_mode():\n    for xb, _ids in test_dl:\n        xb = xb.to(device)\n        p = torch.sigmoid(model(xb)).squeeze(1).cpu().numpy()\n        pred.extend(p)\nsub[\"label\"] = pred\nout_path = WORK/\"submission.csv\"\nsub.to_csv(out_path, index=False)\nlog(f\"saved submission: {out_path}\")\nlog(f\"log file: {LOGFILE}\")\n\n\n\n# Conclusion\nacc  = accuracy_score(ys_val, yhat_val)\nprec, rec, f1, _ = precision_recall_fscore_support(ys_val, yhat_val, average=\"binary\", zero_division=0)\nneg, pos = (ys_val == 0).sum(), (ys_val == 1).sum()\nprint(\n    \"=== Mini-Project Conclusions ===\\n\"\n    f\"Val size: {len(ys_val)} (neg={neg}, pos={pos})\\n\"\n    f\"Best AUC: {best_auc:.4f}\\n\"\n    f\"Final — AUC {roc_auc_score(ys_val, ps_val):.4f} | \"\n    f\"Acc {acc:.4f} | Prec {prec:.4f} | Rec {rec:.4f} | F1 {f1:.4f}\\n\"\n    \"Notes:\\n\"\n    \"- ResNet-18 @64x64 on a small subset is fast but not optimal.\\n\"\n    \"- Threshold tuning may improve precision/recall.\\n\"\n    \"- Use larger images/more data/epochs/aug to improve.\\n\"\n    \"- Consider class weighting or oversampling if imbalanced.\"\n)\nprint(f\"done. best AUC (val subset): {best_auc:.4f} | curves: /kaggle/working/training_curves.png\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-05T07:24:51.140771Z","iopub.execute_input":"2025-11-05T07:24:51.141237Z","iopub.status.idle":"2025-11-05T07:35:19.558098Z","shell.execute_reply.started":"2025-11-05T07:24:51.141184Z","shell.execute_reply":"2025-11-05T07:35:19.556298Z"}},"outputs":[],"execution_count":null}]}