{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":10338,"databundleVersionId":862042,"sourceType":"competition"}],"dockerImageVersionId":30918,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# import necessary libraries","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19"}},{"cell_type":"code","source":"from pathlib import Path\nimport pydicom\nimport numpy as np\nimport cv2\nimport pandas as pd\nimport matplotlib.pyplot as plt\nfrom tqdm.notebook import tqdm","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-06T12:10:54.916310Z","iopub.execute_input":"2025-04-06T12:10:54.916524Z","iopub.status.idle":"2025-04-06T12:10:56.563161Z","shell.execute_reply.started":"2025-04-06T12:10:54.916502Z","shell.execute_reply":"2025-04-06T12:10:56.562527Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# preprocessing and standardization","metadata":{}},{"cell_type":"code","source":"label = pd.read_csv('/kaggle/input/rsna-pneumonia-detection-challenge/stage_2_train_labels.csv')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-06T12:11:13.534376Z","iopub.execute_input":"2025-04-06T12:11:13.534671Z","iopub.status.idle":"2025-04-06T12:11:13.615586Z","shell.execute_reply.started":"2025-04-06T12:11:13.534648Z","shell.execute_reply":"2025-04-06T12:11:13.614698Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"label = label.drop(columns=['x','y','width','height'])\nlabel.tail()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-06T12:11:19.290476Z","iopub.execute_input":"2025-04-06T12:11:19.290763Z","iopub.status.idle":"2025-04-06T12:11:19.326627Z","shell.execute_reply.started":"2025-04-06T12:11:19.290741Z","shell.execute_reply":"2025-04-06T12:11:19.325912Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class_info = pd.read_csv(\"/kaggle/input/rsna-pneumonia-detection-challenge/stage_2_detailed_class_info.csv\")\n\nlabel = pd.read_csv(\"/kaggle/input/rsna-pneumonia-detection-challenge/stage_2_train_labels.csv\")\n# Class distribution\nclass_info['class'].value_counts()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-06T12:11:25.660627Z","iopub.execute_input":"2025-04-06T12:11:25.660945Z","iopub.status.idle":"2025-04-06T12:11:25.836064Z","shell.execute_reply.started":"2025-04-06T12:11:25.660916Z","shell.execute_reply":"2025-04-06T12:11:25.835357Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#remove duplicates\nlabel = label.drop_duplicates('patientId')\n\n#save paths\nROOT_PATH = Path('/kaggle/input/rsna-pneumonia-detection-challenge/stage_2_train_images')\nSAVE_PATH = Path(\"Processed/\")\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-06T12:11:30.697810Z","iopub.execute_input":"2025-04-06T12:11:30.698085Z","iopub.status.idle":"2025-04-06T12:11:30.708986Z","shell.execute_reply.started":"2025-04-06T12:11:30.698064Z","shell.execute_reply":"2025-04-06T12:11:30.708225Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#visualizing dicom files\nfig, axis = plt.subplots(3, 3, figsize=(9, 9))\nc = 0\n\nfor i in range(3):\n    for j in range(3):\n        patient_id = label.iloc[c]['patientId']  # Get patient ID\n        dcm_path = ROOT_PATH / f'{patient_id}.dcm'  # File path\n        dicom = pydicom.dcmread(dcm_path).pixel_array  # Read DICOM image\n\n        target_label = label.iloc[c]['Target']  # Get the label (0 or 1)\n\n        axis[i][j].imshow(dicom, cmap='bone')  # Display DICOM image\n        axis[i][j].set_title(f\"Target: {target_label}\")  # Set correct title\n        axis[i][j].axis('off')  # Hide axis for cleaner visualization\n\n        c += 1","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-06T12:11:37.152795Z","iopub.execute_input":"2025-04-06T12:11:37.153107Z","iopub.status.idle":"2025-04-06T12:11:38.943587Z","shell.execute_reply.started":"2025-04-06T12:11:37.153078Z","shell.execute_reply":"2025-04-06T12:11:38.942629Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"label = pd.read_csv('/kaggle/input/rsna-pneumonia-detection-challenge/stage_2_train_labels.csv')\nlabel = label.reset_index(drop=True)\n\n# Initialize normalization variables\nsum_pixel_values, sum_squared_pixel_values = 0, 0\ntotal_train_samples = 24000  # Define training dataset size\n\n# Loop through each patient ID in the dataset\nfor idx, patient_id in enumerate(tqdm(label[\"patientId\"], desc=\"Processing DICOM files\")):\n    \n    # Construct DICOM file path and read image\n    dcm_path = ROOT_PATH / f\"{patient_id}.dcm\"\n    dicom_image = pydicom.dcmread(dcm_path).pixel_array.astype(np.float32) / 255.0  # Normalize to [0,1]\n    \n    # Resize the image to (224x224) using OpenCV\n    resized_image = cv2.resize(dicom_image, (224, 224)).astype(np.float32)\n\n    # Retrieve label (0 = No Pneumonia, 1 = Pneumonia)\n    label_value = label.loc[idx, \"Target\"]\n\n    # Determine if the sample belongs to training or validation set\n    dataset_split = \"train\" if idx < total_train_samples else \"val\"\n\n    # Define the directory where the file will be saved\n    save_dir = SAVE_PATH / dataset_split / str(label_value)\n    save_dir.mkdir(parents=True, exist_ok=True)  # Ensure directory exists\n\n    # Save the preprocessed image as a NumPy file\n    np.save(save_dir / f\"{patient_id}.npy\", resized_image)\n\n    # Compute sums for standardization (only for training data)\n    normalizer = 224 * 224\n    if dataset_split == \"train\":\n        sum_pixel_values += np.sum(resized_image) / normalizer\n        sum_squared_pixel_values += np.sum(resized_image ** 2) / normalizer\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-06T12:11:40.750844Z","iopub.execute_input":"2025-04-06T12:11:40.751166Z","iopub.status.idle":"2025-04-06T12:20:11.937695Z","shell.execute_reply.started":"2025-04-06T12:11:40.751137Z","shell.execute_reply":"2025-04-06T12:20:11.936824Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\n# Number of training images\nN_train = 24000  \n\n# Compute the mean pixel value across training images\ntrain_mean = sum_pixel_values / N_train\n# Compute the variance and then standard deviation.\n# The variance is computed from the formula: variance = (sum of squares / N) - (mean)^2\ntrain_variance = (sum_squared_pixel_values / N_train) - (train_mean ** 2)\ntrain_std = np.sqrt(train_variance)\n\n\n\nprint(\"Training Standard Deviation:\", train_std)\nprint(\"Training Mean:\", train_mean)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-06T12:23:46.595625Z","iopub.execute_input":"2025-04-06T12:23:46.595931Z","iopub.status.idle":"2025-04-06T12:23:46.601758Z","shell.execute_reply.started":"2025-04-06T12:23:46.595908Z","shell.execute_reply":"2025-04-06T12:23:46.601057Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Model Training","metadata":{}},{"cell_type":"code","source":"import torch\nimport torchvision\nfrom torchvision import transforms\nimport torchmetrics\nimport pytorch_lightning as pl\nfrom pytorch_lightning.callbacks import ModelCheckpoint\nfrom pytorch_lightning.loggers import TensorBoardLogger\nfrom tqdm.notebook import tqdm\nimport numpy as np\nimport matplotlib.pyplot as plt\n\nimport torch\nimport torchvision\nimport torch.nn as nn\nimport torch.optim as optim\nimport torchmetrics\nimport pytorch_lightning as pl\nfrom torchmetrics.classification import BinaryAccuracy","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-06T12:23:50.350735Z","iopub.execute_input":"2025-04-06T12:23:50.351025Z","iopub.status.idle":"2025-04-06T12:24:05.036540Z","shell.execute_reply.started":"2025-04-06T12:23:50.351001Z","shell.execute_reply":"2025-04-06T12:24:05.035666Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#define the path loading function\ndef load_file(path):\n    return np.load(path).astype(np.float32)\n    ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-06T12:25:45.206520Z","iopub.execute_input":"2025-04-06T12:25:45.207031Z","iopub.status.idle":"2025-04-06T12:25:45.210871Z","shell.execute_reply.started":"2025-04-06T12:25:45.207006Z","shell.execute_reply":"2025-04-06T12:25:45.209809Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from torchvision import transforms\n\n# Transformations for training data with augmentations\ntrain_transforms = transforms.Compose([\n    transforms.ToPILImage(),  # Convert numpy ndarray to PIL Image\n    transforms.RandomRotation(15),  # Random rotation of up to 15 degrees\n    transforms.RandomHorizontalFlip(),  # Horizontal flipping with 50% probability\n    transforms.RandomVerticalFlip(),  # Vertical flipping with 50% probability\n    transforms.RandomAffine(degrees=(-5, 5), translate=(0, 0.05), scale=(0.9, 1.1)),  # Random affine transformation\n    transforms.RandomResizedCrop(224, scale=(0.35, 1.0)),  # Random resized crop\n    transforms.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.2, hue=0.2),  # Random color jitter\n    transforms.ToTensor(),  # Convert image to tensor\n    transforms.Normalize(mean=[0.49], std=[0.248])  # Normalize using the dataset's mean and std\n])\n\n# Transformations for validation (test) data without augmentation\nval_transforms = transforms.Compose([\n    transforms.ToPILImage(),  # Convert numpy ndarray to PIL Image\n    transforms.ToTensor(),  # Convert image to tensor\n    transforms.Normalize(mean=[0.49], std=[0.248])  # Normalize using the dataset's mean and std\n])\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-06T12:25:48.915176Z","iopub.execute_input":"2025-04-06T12:25:48.915553Z","iopub.status.idle":"2025-04-06T12:25:48.921384Z","shell.execute_reply.started":"2025-04-06T12:25:48.915523Z","shell.execute_reply":"2025-04-06T12:25:48.920564Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_dataset = torchvision.datasets.DatasetFolder(\"Processed/train/\", loader=load_file\n                                                  ,extensions='npy', transform=train_transforms)\nval_dataset = torchvision.datasets.DatasetFolder(\"Processed/val/\", loader=load_file\n                                                  ,extensions='npy', transform=val_transforms)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-06T12:25:52.326406Z","iopub.execute_input":"2025-04-06T12:25:52.326732Z","iopub.status.idle":"2025-04-06T12:25:52.400412Z","shell.execute_reply.started":"2025-04-06T12:25:52.326703Z","shell.execute_reply":"2025-04-06T12:25:52.399561Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#load dataset in batches and shuffle\nbatch_size = 64\nnum_workers = 4\ntrain_loader = torch.utils.data.DataLoader(train_dataset, batch_size = batch_size, num_workers=num_workers, shuffle=True)\nval_loader = torch.utils.data.DataLoader(val_dataset, batch_size = batch_size, num_workers=num_workers, shuffle=False)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-06T12:25:57.366179Z","iopub.execute_input":"2025-04-06T12:25:57.366526Z","iopub.status.idle":"2025-04-06T12:25:57.370963Z","shell.execute_reply.started":"2025-04-06T12:25:57.366496Z","shell.execute_reply":"2025-04-06T12:25:57.369989Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nimport torch.optim as optim\nimport torchvision\nimport pytorch_lightning as pl\nimport torchmetrics\nfrom torchmetrics.classification import BinaryF1Score, BinaryRecall, BinaryPrecision, BinaryAccuracy\nfrom torchvision.models import resnet18\nfrom torch.optim.lr_scheduler import ReduceLROnPlateau\n\nclass PneumoniaModel(pl.LightningModule):\n    def __init__(self, lr: float = 1e-4):\n        super().__init__()\n\n        # Use a Pretrained ResNet18 Model\n        self.model = resnet18(weights=None)  # Load without pretrained weights\n        self.model.conv1 = nn.Conv2d(1, 64, kernel_size=7, stride=2, padding=3, bias=False)\n        self.model.fc = nn.Linear(512, 1, bias=True)  # Binary classification output\n\n        # Define loss function\n        self.loss_fn = nn.BCEWithLogitsLoss(pos_weight=torch.tensor([3.0]))\n\n        # Define optimizer & learning rate\n        self.lr = lr\n\n        # Metrics\n        self.train_acc = BinaryAccuracy()\n        self.val_acc = BinaryAccuracy()\n        self.val_f1 = BinaryF1Score()\n        self.val_recall = BinaryRecall()\n        self.val_precision = BinaryPrecision()\n\n        # Save hyperparameters\n        self.save_hyperparameters()\n\n        # Store training losses for epoch-end calculations\n        self.training_step_outputs = []\n\n    def forward(self, x: torch.Tensor) -> torch.Tensor:\n        \"\"\"Forward pass through the model.\"\"\"\n        return self.model(x)\n\n    def training_step(self, batch, batch_idx: int) -> torch.Tensor:\n        \"\"\"Single training step: forward pass, loss calculation, and logging.\"\"\"\n        x_ray, label = batch\n        label = label.float().to(self.device)  # Ensure labels are float\n        pred = self(x_ray).squeeze(1)  # Ensure proper shape\n\n        loss = self.loss_fn(pred, label)\n\n        # Compute accuracy for this step\n        acc = self.train_acc(torch.sigmoid(pred), label.int())\n\n        # Log loss and accuracy\n        self.log(\"Train Loss\", loss, prog_bar=True, sync_dist=True)\n        self.log(\"Train Acc\", acc, prog_bar=True)\n\n        # Store loss for epoch-end tracking\n        self.training_step_outputs.append(loss)\n\n        return loss\n\n    def on_train_epoch_end(self):\n        \"\"\"Log average training loss and reset stored losses.\"\"\"\n        avg_loss = torch.stack(self.training_step_outputs).mean()\n        self.log(\"Train Loss (Epoch)\", avg_loss, prog_bar=True)\n        self.training_step_outputs.clear()  # Clear list for next epoch\n\n    def validation_step(self, batch, batch_idx: int):\n        \"\"\"Single validation step: forward pass, loss, and metric logging.\"\"\"\n        x_ray, label = batch\n        label = label.float().to(self.device)\n        pred = self(x_ray).squeeze(1)\n\n        loss = self.loss_fn(pred, label)\n        pred_probs = torch.sigmoid(pred)\n\n        # Compute and log metrics\n        self.val_acc.update(pred_probs, label.int())\n        self.val_f1.update(pred_probs, label.int())\n        self.val_recall.update(pred_probs, label.int())\n        self.val_precision.update(pred_probs, label.int())\n\n        self.log(\"Val Loss\", loss, prog_bar=True)\n        return loss\n\n    def on_validation_epoch_end(self):\n        \"\"\"Log validation metrics at the end of each epoch.\"\"\"\n        self.log(\"Val Acc\", self.val_acc.compute(), prog_bar=True)\n        self.log(\"Val F1\", self.val_f1.compute(), prog_bar=True)\n        self.log(\"Val Recall\", self.val_recall.compute(), prog_bar=True)\n        self.log(\"Val Precision\", self.val_precision.compute(), prog_bar=True)\n\n        # Reset metrics\n        self.val_acc.reset()\n        self.val_f1.reset()\n        self.val_recall.reset()\n        self.val_precision.reset()\n\n    def configure_optimizers(self):\n        \"\"\"Define optimizer and learning rate scheduler.\"\"\"\n        optimizer = optim.Adam(self.model.parameters(), lr=self.lr)\n        scheduler = ReduceLROnPlateau(optimizer, mode=\"min\", factor=0.1, patience=3, verbose=True)\n        return {\"optimizer\": optimizer, \"lr_scheduler\": {\"scheduler\": scheduler, \"monitor\": \"Val Loss\"}}\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-06T12:25:59.654477Z","iopub.execute_input":"2025-04-06T12:25:59.654790Z","iopub.status.idle":"2025-04-06T12:25:59.666913Z","shell.execute_reply.started":"2025-04-06T12:25:59.654761Z","shell.execute_reply":"2025-04-06T12:25:59.666227Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from pytorch_lightning import Trainer\nfrom pytorch_lightning.callbacks import ModelCheckpoint\nfrom pytorch_lightning.loggers import TensorBoardLogger\n\n# Define checkpoint callback\ncheckpoint_callback = ModelCheckpoint(\n    dirpath=\"checkpoints/\",  # Save directory\n    filename=\"pneumonia-{epoch:02d}-{val_acc:.4f}\",  # Naming format\n    monitor=\"Val Recall\",  # Metric to track\n    mode=\"max\",  # Save best models (maximize accuracy)\n    save_top_k=10,  # Keep only top 10 models\n    verbose=True\n)\n\n# Define TensorBoard logger\nlogger = TensorBoardLogger(save_dir=\"./logs\", name=\"pneumonia_detection\")\n\n# Set GPU/CPU usage dynamically\ngpus = 1  # Change this to available GPUs, set to 0 for CPU training\n\n# Create Trainer\ntrainer = Trainer(\n    max_epochs=25,  # Train for 12 epochs\n    accelerator=\"gpu\" if gpus > 0 else \"cpu\",  # Automatically switch between GPU/CPU\n    devices=gpus if gpus > 0 else 1,  # Assign number of GPUs or fallback to CPU\n    logger=logger,  # Use TensorBoard for logging\n    log_every_n_steps=1,  # Log every step\n    callbacks=[checkpoint_callback]  # Include checkpoint callback\n)\n\n# Initialize Model\nmodel = PneumoniaModel()\n\n# Train the model\ntrainer.fit(model, train_loader, val_loader)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-06T12:53:38.403915Z","iopub.execute_input":"2025-04-06T12:53:38.404285Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# model evaluation","metadata":{}},{"cell_type":"code","source":"import torch\nimport torchmetrics\nfrom tqdm.notebook import tqdm\nimport numpy as np\nimport matplotlib.pyplot as plt\nfrom sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score, confusion_matrix, roc_auc_score\n\n# Define device (Use GPU if available, otherwise use CPU)\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\n# Load the best model checkpoint\n# Get the path to the best model saved during training\nbest_model_path = checkpoint_callback.best_model_path\n\n  # Update with the correct checkpoint path\nmodel = PneumoniaModel.load_from_checkpoint(best_model_path)  # Avoids mismatch in pos_weight\nmodel.to(device)\nmodel.eval()  # Set model to evaluation mode\n\n# Initialize lists to store predictions and labels\nall_preds = []\nall_labels = []\n\n# Disable gradient computation for evaluation\nwith torch.no_grad():\n    for data, label in tqdm(val_dataset, desc=\"Evaluating Model\"):\n        # Move data to the correct device\n        data = data.to(device).float().unsqueeze(0)  # Add batch dimension\n\n        # Get model predictions\n        pred = torch.sigmoid(model(data)[0].cpu())  # Convert logits to probability\n\n        # Store results\n        all_preds.append(pred.item())  # Convert to scalar\n        all_labels.append(label)\n\n# Convert lists to NumPy arrays for metric calculations\nall_preds = np.array(all_preds)\nall_labels = np.array(all_labels)\n\n# Apply threshold (0.5) for binary classification\nbinary_preds = (all_preds >= 0.5).astype(int)\n\n# Compute performance metrics\naccuracy = accuracy_score(all_labels, binary_preds)\nprecision = precision_score(all_labels, binary_preds)\nrecall = recall_score(all_labels, binary_preds)\nf1 = f1_score(all_labels, binary_preds)\nauc_roc = roc_auc_score(all_labels, all_preds)\n\n# Compute confusion matrix\nconf_matrix = confusion_matrix(all_labels, binary_preds)\nconf_matrix_thresh = confusion_matrix(all_labels, (all_preds >= 0.25).astype(int))  # Lower threshold to 0.25\n\n# Print performance metrics\nprint(f\"Validation Accuracy:  {accuracy:.4f}\")\nprint(f\"Validation Precision: {precision:.4f}\")\nprint(f\"Validation Recall:    {recall:.4f}\")\nprint(f\"Validation F1 Score:  {f1:.4f}\")\nprint(f\"Validation AUC-ROC:   {auc_roc:.4f}\")\nprint(f\"\\nConfusion Matrix (Threshold = 0.5):\\n{conf_matrix}\")\nprint(f\"\\nConfusion Matrix (Threshold = 0.25):\\n{conf_matrix_thresh}\")\n\n# --- Visualization: Display Random X-Ray Predictions ---\nfig, axis = plt.subplots(3, 3, figsize=(9, 9))\n\nfor i in range(3):\n    for j in range(3):\n        rnd_idx = np.random.randint(0, len(all_preds))  # Pick a random sample\n\n        # Extract image, true label, and prediction\n        x_ray, true_label = val_dataset[rnd_idx]\n        pred_label = binary_preds[rnd_idx]  # Model's predicted label\n \n        # Display the X-ray image\n        axis[i][j].imshow(x_ray[0], cmap=\"bone\")\n        axis[i][j].set_title(f\"Pred: {pred_label}, Label: {true_label}\")\n        axis[i][j].axis(\"off\")\n\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-06T12:28:36.929599Z","iopub.execute_input":"2025-04-06T12:28:36.929848Z","iopub.status.idle":"2025-04-06T12:28:59.867733Z","shell.execute_reply.started":"2025-04-06T12:28:36.929826Z","shell.execute_reply":"2025-04-06T12:28:59.866780Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# interpretability","metadata":{}},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nimport torchvision\nimport pytorch_lightning as pl\nimport torchvision.transforms as transforms\nimport matplotlib.pyplot as plt\n\nclass PneumoniaModel(pl.LightningModule):\n    def __init__(self, lr: float = 1e-4):\n        super().__init__()\n\n        # Use a Pretrained ResNet18 Model\n        self.model = torchvision.models.resnet18(pretrained=True)  \n        self.model.conv1 = nn.Conv2d(1, 64, kernel_size=(7, 7), stride=(2, 2), padding=(3, 3), bias=False)\n        self.model.fc = nn.Linear(in_features=512, out_features=1, bias=True)\n\n        # Extracting feature maps\n        temp_model = torchvision.models.resnet18(pretrained=True)\n        self.feature_map = nn.Sequential(*list(temp_model.children())[:2])\n\n    def forward(self, data):\n        feature_map = self.feature_map(data)\n        avg_pool_output = nn.functional.adaptive_avg_pool2d(input=feature_map, output_size=(1, 1))\n        avg_output_flattened = torch.flatten(avg_pool_output, start_dim=1)\n        pred = self.model.fc(avg_output_flattened)\n        return pred, feature_map\n\n# Load model from checkpoint\nmodel = PneumoniaModel.load_from_checkpoint('weights/weights_3.ckpt', strict=False)\nmodel.eval()\n\ndef cam(model, img):\n    with torch.no_grad():\n        pred, features = model(img.unsqueeze(0))\n    features = features.reshape((512, 49))\n    weight_params = list(model.model.fc.parameters())[0]  # Fix indexing error\n    weight = weight_params[0].detach()\n    \n    cam = torch.matmul(weight, features)\n    cam_img = cam.reshape(7, 7).cpu()\n    return cam_img, torch.sigmoid(pred)\n\ndef visualize(img, cam, pred):\n    img = img[0]\n    cam = transforms.Resize((224, 224))(cam.unsqueeze(0))[0]\n    \n    fig, axis = plt.subplots(1, 2)\n    axis[0].imshow(img, cmap='bone')\n    axis[1].imshow(img, cmap='bone')\n    axis[1].imshow(cam, alpha=0.5, cmap='jet')\n    plt.title(f'Prediction: {pred.item() > 0.5}')\n    plt.show()\n\n# Example usage\nimg = val_dataset[-6][0]\nactivation_map, pred = cam(model, img)\nvisualize(img, activation_map, pred)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-02T17:59:10.069889Z","iopub.status.idle":"2025-04-02T17:59:10.070166Z","shell.execute_reply":"2025-04-02T17:59:10.070049Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\n\n# Save the model manually after training\ntorch.save(model.state_dict(), \"pneumonia_model.pth\")\nprint(\"Model saved successfully!\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-02T17:59:10.070973Z","iopub.status.idle":"2025-04-02T17:59:10.071311Z","shell.execute_reply":"2025-04-02T17:59:10.07115Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}