{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceType":"competition","sourceId":10338,"databundleVersionId":862042}],"dockerImageVersionId":31329,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nfrom pathlib import Path\nfrom tqdm import tqdm\n\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import accuracy_score, roc_auc_score, confusion_matrix, classification_report, f1_score\n\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader\n\nimport torchvision.transforms as transforms\nimport torchvision.models as models\n\nimport pydicom\nimport matplotlib.pyplot as plt\nimport seaborn as sns","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-15T15:50:47.553288Z","iopub.execute_input":"2026-04-15T15:50:47.554044Z","iopub.status.idle":"2026-04-15T15:50:47.559221Z","shell.execute_reply.started":"2026-04-15T15:50:47.554010Z","shell.execute_reply":"2026-04-15T15:50:47.558226Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"DATA_DIR = Path(\"/kaggle/input/competitions/rsna-pneumonia-detection-challenge\")\n\nLABEL_CSV = DATA_DIR / \"stage_2_train_labels.csv\"\nIMG_DIR = DATA_DIR / \"stage_2_train_images\"","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-15T15:50:47.560651Z","iopub.execute_input":"2026-04-15T15:50:47.560981Z","iopub.status.idle":"2026-04-15T15:50:47.575153Z","shell.execute_reply.started":"2026-04-15T15:50:47.560956Z","shell.execute_reply":"2026-04-15T15:50:47.574169Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df = pd.read_csv(LABEL_CSV)\ndf['Target'] = df['Target'].astype(int)\n\ndf = df.groupby('patientId')['Target'].max().reset_index()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-15T15:50:47.576384Z","iopub.execute_input":"2026-04-15T15:50:47.577073Z","iopub.status.idle":"2026-04-15T15:50:47.643281Z","shell.execute_reply.started":"2026-04-15T15:50:47.577035Z","shell.execute_reply":"2026-04-15T15:50:47.642292Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_df, temp_df = train_test_split(df, test_size=0.36, stratify=df['Target'], random_state=42)\nval_df, test_df = train_test_split(temp_df, test_size=0.555, stratify=temp_df['Target'], random_state=42)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-15T15:50:47.644432Z","iopub.execute_input":"2026-04-15T15:50:47.644875Z","iopub.status.idle":"2026-04-15T15:50:47.669124Z","shell.execute_reply.started":"2026-04-15T15:50:47.644847Z","shell.execute_reply":"2026-04-15T15:50:47.668211Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Normal images (light augmentation)\nnormal_transform = transforms.Compose([\n    transforms.ToPILImage(),\n    transforms.Resize((224, 224)),\n    transforms.RandomHorizontalFlip(p=0.3),\n    transforms.ToTensor(),\n])\n\n# Pneumonia images (stronger augmentation)\npneumonia_transform = transforms.Compose([\n    transforms.ToPILImage(),\n    transforms.Resize((224, 224)),\n    transforms.RandomHorizontalFlip(),\n    transforms.RandomRotation(5),  # reduce from 10\n    transforms.ToTensor(),\n])\n\nval_transform = transforms.Compose([\n    transforms.ToPILImage(),\n    transforms.Resize((224, 224)),\n    transforms.ToTensor(),\n])\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-15T15:50:47.670826Z","iopub.execute_input":"2026-04-15T15:50:47.671208Z","iopub.status.idle":"2026-04-15T15:50:47.676517Z","shell.execute_reply.started":"2026-04-15T15:50:47.671182Z","shell.execute_reply":"2026-04-15T15:50:47.675909Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class RSNADataset(Dataset):\n    def __init__(self, df, img_dir, mode=\"train\"):\n        self.df = df\n        self.img_dir = img_dir\n        self.mode = mode\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        pid = self.df.iloc[idx]['patientId']\n        label = self.df.iloc[idx]['Target']\n    \n        dcm = pydicom.dcmread(self.img_dir / f\"{pid}.dcm\")\n        img = dcm.pixel_array.astype(np.float32)\n    \n        # normalization\n        img = (img - np.mean(img)) / (np.std(img) + 1e-5)\n        img = (img - img.min()) / (img.max() - img.min() + 1e-5)\n    \n        img = np.stack([img]*3, axis=-1)\n        img = (img * 255).astype(np.uint8)\n    \n        # augmentation logic\n        if self.mode == \"train\":\n            if int(label) == 1:\n                img = pneumonia_transform(img)\n            else:\n                img = normal_transform(img)\n        else:\n            img = val_transform(img)\n    \n        # 🔥 THIS WAS MISSING\n        return img, torch.tensor(label, dtype=torch.float32)\n\n        ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-15T15:50:47.677518Z","iopub.execute_input":"2026-04-15T15:50:47.677982Z","iopub.status.idle":"2026-04-15T15:50:47.691465Z","shell.execute_reply.started":"2026-04-15T15:50:47.677953Z","shell.execute_reply":"2026-04-15T15:50:47.690839Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_loader = DataLoader(RSNADataset(train_df, IMG_DIR, mode=\"train\"), batch_size=32, shuffle=True)\nval_loader = DataLoader(RSNADataset(val_df, IMG_DIR, mode=\"val\"), batch_size=32)\ntest_loader = DataLoader(RSNADataset(test_df, IMG_DIR, mode=\"val\"), batch_size=32)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-15T15:50:47.692466Z","iopub.execute_input":"2026-04-15T15:50:47.692796Z","iopub.status.idle":"2026-04-15T15:50:47.706938Z","shell.execute_reply.started":"2026-04-15T15:50:47.692738Z","shell.execute_reply":"2026-04-15T15:50:47.706057Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\nmodel = models.resnet18(weights=\"IMAGENET1K_V1\")\n\nmodel.fc = nn.Sequential(\n    nn.Linear(model.fc.in_features, 256),\n    nn.ReLU(),\n    nn.Dropout(0.6),  \n    nn.Linear(256, 1)\n)\n\nmodel = model.to(device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-15T15:50:47.708051Z","iopub.execute_input":"2026-04-15T15:50:47.708470Z","iopub.status.idle":"2026-04-15T15:50:47.909744Z","shell.execute_reply.started":"2026-04-15T15:50:47.708443Z","shell.execute_reply":"2026-04-15T15:50:47.908762Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"pos = df['Target'].value_counts()[1]\nneg = df['Target'].value_counts()[0]\n\ncriterion = nn.BCEWithLogitsLoss(pos_weight=torch.tensor([neg/pos]).to(device))\n\noptimizer = optim.Adam(model.parameters(), lr=1e-5, weight_decay=1e-4)\nscheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=20)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-15T15:50:47.911798Z","iopub.execute_input":"2026-04-15T15:50:47.912448Z","iopub.status.idle":"2026-04-15T15:50:47.920372Z","shell.execute_reply.started":"2026-04-15T15:50:47.912420Z","shell.execute_reply":"2026-04-15T15:50:47.919740Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def find_best_threshold(probs, targets):\n    thresholds = np.arange(0.3, 0.7, 0.02)\n    best_t, best_f1 = 0.5, 0\n\n    for t in thresholds:\n        preds = (probs > t).astype(int)\n        f1 = f1_score(targets, preds)\n\n        if f1 > best_f1:\n            best_f1 = f1\n            best_t = t\n\n    return best_t","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-15T15:50:47.921266Z","iopub.execute_input":"2026-04-15T15:50:47.921617Z","iopub.status.idle":"2026-04-15T15:50:47.933059Z","shell.execute_reply.started":"2026-04-15T15:50:47.921591Z","shell.execute_reply":"2026-04-15T15:50:47.932447Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def run_epoch(loader, train=True):\n    model.train() if train else model.eval()\n\n    total_loss = 0\n    all_probs, all_targets = [], []\n\n    with torch.set_grad_enabled(train):\n        for imgs, labels in tqdm(loader):\n            imgs, labels = imgs.to(device), labels.to(device)\n\n            outputs = model(imgs).squeeze()\n            loss = criterion(outputs, labels)\n\n            if train:\n                optimizer.zero_grad()\n                loss.backward()\n                optimizer.step()\n\n            total_loss += loss.item()\n\n            probs = torch.sigmoid(outputs)\n            all_probs.extend(probs.detach().cpu().numpy())\n            all_targets.extend(labels.detach().cpu().numpy())\n\n    return total_loss/len(loader), np.array(all_probs), np.array(all_targets)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-15T15:50:47.933825Z","iopub.execute_input":"2026-04-15T15:50:47.934115Z","iopub.status.idle":"2026-04-15T15:50:47.947703Z","shell.execute_reply.started":"2026-04-15T15:50:47.934091Z","shell.execute_reply":"2026-04-15T15:50:47.946958Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"best_auc = 0\nbest_thresh = 0.5\npatience = 2\ncounter = 0\n\nSAVE_PATH = \"/kaggle/working/best_model.pth\"\n\nfor epoch in range(10):\n    tr_loss, tr_probs, tr_targets = run_epoch(train_loader, True)\n    va_loss, va_probs, va_targets = run_epoch(val_loader, False)\n\n    tr_preds = (tr_probs > 0.5)\n    va_preds = (va_probs > 0.5)\n\n    tr_acc = accuracy_score(tr_targets, tr_preds)\n    va_acc = accuracy_score(va_targets, va_preds)\n\n    tr_auc = roc_auc_score(tr_targets, tr_probs)\n    va_auc = roc_auc_score(va_targets, va_probs)\n\n    # 🔥 find best threshold\n    best_thresh = find_best_threshold(va_probs, va_targets)\n\n    print(f\"\\nEpoch {epoch+1}\")\n    print(f\"Train Loss {tr_loss:.4f} Acc {tr_acc:.4f} AUC {tr_auc:.4f}\")\n    print(f\"Val   Loss {va_loss:.4f} Acc {va_acc:.4f} AUC {va_auc:.4f}\")\n    print(f\"Best Threshold: {best_thresh:.2f}\")\n\n    scheduler.step()\n\n    # save best model\n    if va_auc > best_auc:\n        best_auc = va_auc\n        counter = 0\n\n        torch.save({\n            'model': model.state_dict(),\n            'threshold': best_thresh\n        }, SAVE_PATH)\n        \n        print(f\"✅ Best model saved at {SAVE_PATH}\")\n    else:\n        counter += 1\n\n    if counter >= patience:\n        print(\"⛔ Early stopping\")\n        break","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-15T15:50:47.948727Z","iopub.execute_input":"2026-04-15T15:50:47.949045Z","iopub.status.idle":"2026-04-15T17:33:03.332924Z","shell.execute_reply.started":"2026-04-15T15:50:47.949009Z","shell.execute_reply":"2026-04-15T17:33:03.332024Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\ncheckpoint = torch.load(SAVE_PATH, weights_only=False)\n\nmodel.load_state_dict(checkpoint['model'])\nbest_thresh = checkpoint['threshold']\n\nprint(\"✅ Model loaded successfully\")\n\n_, probs, targets = run_epoch(test_loader, False)\n\npreds = (probs > best_thresh)\n\ncm = confusion_matrix(targets, preds)\n\n# 🔥 plot\nlabels = np.array([\n    [f\"TN\\n{cm[0,0]}\", f\"FP\\n{cm[0,1]}\"],\n    [f\"FN\\n{cm[1,0]}\", f\"TP\\n{cm[1,1]}\"]\n])\n\nplt.figure(figsize=(6,5))\nsns.heatmap(cm, annot=labels, fmt=\"\", cmap=\"Blues\",\n            xticklabels=[\"Normal\",\"Pneumonia\"],\n            yticklabels=[\"Normal\",\"Pneumonia\"])\n\nplt.xlabel(\"Predicted\")\nplt.ylabel(\"Actual\")\nplt.title(\"Confusion Matrix\")\nplt.show()\n\nprint(\"\\nClassification Report:\\n\")\nprint(classification_report(targets, preds))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-15T17:33:03.334798Z","iopub.execute_input":"2026-04-15T17:33:03.335152Z","iopub.status.idle":"2026-04-15T17:35:24.738764Z","shell.execute_reply.started":"2026-04-15T17:33:03.335126Z","shell.execute_reply":"2026-04-15T17:35:24.738160Z"}},"outputs":[],"execution_count":null}]}