{"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,"isSourceIdPinned":false}],"dockerImageVersionId":31329,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\nimport 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\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","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-14T13:56:34.973875Z","iopub.execute_input":"2026-04-14T13:56:34.974180Z","iopub.status.idle":"2026-04-14T13:56:46.098151Z","shell.execute_reply.started":"2026-04-14T13:56:34.974142Z","shell.execute_reply":"2026-04-14T13:56:46.097539Z"}},"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-14T13:56:46.099697Z","iopub.execute_input":"2026-04-14T13:56:46.100410Z","iopub.status.idle":"2026-04-14T13:56:46.104300Z","shell.execute_reply.started":"2026-04-14T13:56:46.100378Z","shell.execute_reply":"2026-04-14T13:56:46.103562Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df = pd.read_csv(LABEL_CSV)\n\ndf['Target'] = df['Target'].astype(int)\n\ndf = df.groupby('patientId')['Target'].max().reset_index()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-14T13:56:46.105276Z","iopub.execute_input":"2026-04-14T13:56:46.105594Z","iopub.status.idle":"2026-04-14T13:56:46.278880Z","shell.execute_reply.started":"2026-04-14T13:56:46.105559Z","shell.execute_reply":"2026-04-14T13:56:46.278229Z"}},"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)\n\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-14T13:56:46.280081Z","iopub.execute_input":"2026-04-14T13:56:46.280489Z","iopub.status.idle":"2026-04-14T13:56:46.305419Z","shell.execute_reply.started":"2026-04-14T13:56:46.280463Z","shell.execute_reply":"2026-04-14T13:56:46.304563Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class RSNADataset(Dataset):\n    def __init__(self, df, img_dir, transform=None):\n        self.df = df\n        self.img_dir = img_dir\n        self.transform = transform\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        patient_id = self.df.iloc[idx]['patientId']\n        label = self.df.iloc[idx]['Target']\n\n        dcm_path = self.img_dir / f\"{patient_id}.dcm\"\n        dicom = pydicom.dcmread(dcm_path)\n\n        img = dicom.pixel_array.astype(np.float32)\n\n        # ✅ Better normalization\n        mean = np.mean(img)\n        std = np.std(img) + 1e-5\n        img = (img - mean) / std\n\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        if self.transform:\n            img = self.transform(img)\n\n        return img, torch.tensor(label, dtype=torch.float32)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-14T13:56:46.306400Z","iopub.execute_input":"2026-04-14T13:56:46.306689Z","iopub.status.idle":"2026-04-14T13:56:46.313384Z","shell.execute_reply.started":"2026-04-14T13:56:46.306665Z","shell.execute_reply":"2026-04-14T13:56:46.312637Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_transform = transforms.Compose([\n    transforms.ToPILImage(),\n    transforms.Resize((224, 224)),\n    transforms.RandomHorizontalFlip(),\n    transforms.RandomRotation(15),\n    transforms.RandomResizedCrop(224, scale=(0.8, 1.0)),\n    transforms.ColorJitter(brightness=0.2, contrast=0.2),\n    transforms.ToTensor(),\n])\n\nval_transform = transforms.Compose([\n    transforms.ToPILImage(),\n    transforms.Resize((224, 224)),\n    transforms.ToTensor(),\n])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-14T13:56:46.314149Z","iopub.execute_input":"2026-04-14T13:56:46.314437Z","iopub.status.idle":"2026-04-14T13:56:46.326899Z","shell.execute_reply.started":"2026-04-14T13:56:46.314400Z","shell.execute_reply":"2026-04-14T13:56:46.326101Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_loader = DataLoader(RSNADataset(train_df, IMG_DIR, train_transform), batch_size=32, shuffle=True, num_workers=2)\nval_loader   = DataLoader(RSNADataset(val_df, IMG_DIR, val_transform), batch_size=32, shuffle=False, num_workers=2)\ntest_loader  = DataLoader(RSNADataset(test_df, IMG_DIR, val_transform), batch_size=32, shuffle=False, num_workers=2)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-14T13:56:46.329240Z","iopub.execute_input":"2026-04-14T13:56:46.329557Z","iopub.status.idle":"2026-04-14T13:56:46.342128Z","shell.execute_reply.started":"2026-04-14T13:56:46.329534Z","shell.execute_reply":"2026-04-14T13:56:46.341368Z"}},"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.5),\n    nn.Linear(256, 1)\n)\n\nmodel = model.to(device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-14T13:56:46.342958Z","iopub.execute_input":"2026-04-14T13:56:46.343254Z","iopub.status.idle":"2026-04-14T13:56:47.526750Z","shell.execute_reply.started":"2026-04-14T13:56:46.343223Z","shell.execute_reply":"2026-04-14T13:56:47.525737Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"pos = df['Target'].value_counts()[1]\nneg = df['Target'].value_counts()[0]\n\npos_weight = torch.tensor([neg / pos]).to(device)\n\ncriterion = nn.BCEWithLogitsLoss(pos_weight=pos_weight)\n\noptimizer = optim.Adam(model.parameters(), lr=3e-5)\n\nscheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=20)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-14T13:56:47.528016Z","iopub.execute_input":"2026-04-14T13:56:47.528761Z","iopub.status.idle":"2026-04-14T13:56:47.539219Z","shell.execute_reply.started":"2026-04-14T13:56:47.528736Z","shell.execute_reply":"2026-04-14T13:56:47.538516Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def train_epoch(loader):\n    model.train()\n    total_loss = 0\n\n    all_probs, all_preds, all_targets = [], [], []\n\n    for imgs, labels in tqdm(loader):\n        imgs, labels = imgs.to(device), labels.to(device)\n\n        optimizer.zero_grad()\n        outputs = model(imgs).squeeze()\n\n        loss = criterion(outputs, labels)\n        loss.backward()\n        optimizer.step()\n\n        total_loss += loss.item()\n\n        probs = torch.sigmoid(outputs)\n        preds = (probs > 0.3).float()\n\n        all_probs.extend(probs.detach().cpu().numpy())\n        all_preds.extend(preds.detach().cpu().numpy())\n        all_targets.extend(labels.detach().cpu().numpy())\n\n    acc = accuracy_score(all_targets, all_preds)\n    auc = roc_auc_score(all_targets, all_probs)\n\n    return total_loss / len(loader), acc, auc","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-14T13:56:47.540284Z","iopub.execute_input":"2026-04-14T13:56:47.540674Z","iopub.status.idle":"2026-04-14T13:56:47.554858Z","shell.execute_reply.started":"2026-04-14T13:56:47.540650Z","shell.execute_reply":"2026-04-14T13:56:47.553945Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def eval_model(loader):\n    model.eval()\n    total_loss = 0\n\n    all_probs, all_preds, all_targets = [], [], []\n\n    with torch.no_grad():\n        for imgs, labels in loader:\n            imgs, labels = imgs.to(device), labels.to(device)\n\n            outputs = model(imgs).squeeze()\n            loss = criterion(outputs, labels)\n\n            total_loss += loss.item()\n\n            probs = torch.sigmoid(outputs)\n            preds = (probs > 0.3).float()\n\n            all_probs.extend(probs.cpu().numpy())\n            all_preds.extend(preds.cpu().numpy())\n            all_targets.extend(labels.cpu().numpy())\n\n    acc = accuracy_score(all_targets, all_preds)\n    auc = roc_auc_score(all_targets, all_probs)\n\n    return total_loss / len(loader), acc, auc, all_probs, all_targets","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-14T13:56:47.555991Z","iopub.execute_input":"2026-04-14T13:56:47.556331Z","iopub.status.idle":"2026-04-14T13:56:47.568791Z","shell.execute_reply.started":"2026-04-14T13:56:47.556287Z","shell.execute_reply":"2026-04-14T13:56:47.568162Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"EPOCHS = 20\nbest_auc = 0\npatience = 3\ncounter = 0\n\nfor epoch in range(EPOCHS):\n    tr_loss, tr_acc, tr_auc = train_epoch(train_loader)\n    va_loss, va_acc, va_auc, _, _ = eval_model(val_loader)\n\n    print(f\"Epoch {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\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(model.state_dict(), \"best_model.pth\")\n        print(\"✅ Best model saved!\")\n\n    else:\n        counter += 1\n\n    if counter >= patience:\n        print(\"⛔ Early stopping triggered\")\n        break","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-14T13:56:47.569679Z","iopub.execute_input":"2026-04-14T13:56:47.569998Z","iopub.status.idle":"2026-04-14T15:13:38.955679Z","shell.execute_reply.started":"2026-04-14T13:56:47.569976Z","shell.execute_reply":"2026-04-14T15:13:38.954743Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model.load_state_dict(torch.load(\"best_model.pth\"))\n\n_, _, _, probs, targets = eval_model(test_loader)\n\npreds = (np.array(probs) > 0.3)\n\nprint(\"Confusion Matrix:\\n\", confusion_matrix(targets, preds))\n\nprint(\"\\nClassification Report:\\n\")\nprint(classification_report(targets, preds))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-14T15:13:38.957248Z","iopub.execute_input":"2026-04-14T15:13:38.957642Z","iopub.status.idle":"2026-04-14T15:15:31.987065Z","shell.execute_reply.started":"2026-04-14T15:13:38.957611Z","shell.execute_reply":"2026-04-14T15:15:31.986274Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\nimport seaborn as sns\nimport numpy as np\nfrom sklearn.metrics import confusion_matrix\n\ncm = confusion_matrix(targets, preds)\n\nlabels = np.array([\n    [f\"TN\\nNormal→Normal\\n{cm[0,0]}\", f\"FP\\nNormal→Pneumonia\\n{cm[0,1]}\"],\n    [f\"FN\\nPneumonia→Normal\\n{cm[1,0]}\", f\"TP\\nPneumonia→Pneumonia\\n{cm[1,1]}\"]\n])\n\nplt.figure(figsize=(7,6))\nsns.heatmap(cm, annot=labels, fmt=\"\", cmap=\"Blues\",\n            xticklabels=[\"Normal\", \"Pneumonia\"],\n            yticklabels=[\"Normal\", \"Pneumonia\"])\n\nplt.xlabel(\"Predicted Label\")\nplt.ylabel(\"Actual Label\")\nplt.title(\"Confusion Matrix (Detailed)\")\n\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-14T15:47:03.460116Z","iopub.execute_input":"2026-04-14T15:47:03.460673Z","iopub.status.idle":"2026-04-14T15:47:04.046449Z","shell.execute_reply.started":"2026-04-14T15:47:03.460639Z","shell.execute_reply":"2026-04-14T15:47:04.045564Z"}},"outputs":[],"execution_count":null}]}