{"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":"nvidiaTeslaT4","dataSources":[{"sourceId":10338,"databundleVersionId":862042,"sourceType":"competition"},{"sourceId":12750871,"sourceType":"datasetVersion","datasetId":8060427},{"sourceId":12754342,"sourceType":"datasetVersion","datasetId":8062820}],"dockerImageVersionId":31090,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"%matplotlib notebook\nimport torch\nimport torchvision\nfrom torchvision import transforms\nimport pytorch_lightning as pl\nimport numpy as np\nimport matplotlib.pyplot as plt\n%matplotlib inline","metadata":{"_uuid":"fb21fc4c-5a85-4d43-b620-c983dffc8ae5","_cell_guid":"e86e202c-8c62-4c66-9b60-33b6e810ed3a","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false},"execution":{"iopub.status.busy":"2025-08-13T13:31:14.008097Z","iopub.execute_input":"2025-08-13T13:31:14.008402Z","iopub.status.idle":"2025-08-13T13:31:14.014813Z","shell.execute_reply.started":"2025-08-13T13:31:14.008379Z","shell.execute_reply":"2025-08-13T13:31:14.014024Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def load_file(path):\n    return np.load(path).astype(np.float32)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-13T13:25:32.797571Z","iopub.execute_input":"2025-08-13T13:25:32.797905Z","iopub.status.idle":"2025-08-13T13:25:32.802237Z","shell.execute_reply.started":"2025-08-13T13:25:32.797845Z","shell.execute_reply":"2025-08-13T13:25:32.801296Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"val_transforms = transforms.Compose([\n                                transforms.ToTensor(),\n                                transforms.Normalize(0.49, 0.248),\n\n])\n\nval_dataset = torchvision.datasets.DatasetFolder(\"/kaggle/input/preprocessed/Processed/val/\", loader=load_file, extensions=\"npy\", transform=val_transforms)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-13T13:26:02.187780Z","iopub.execute_input":"2025-08-13T13:26:02.188686Z","iopub.status.idle":"2025-08-13T13:26:06.227802Z","shell.execute_reply.started":"2025-08-13T13:26:02.188660Z","shell.execute_reply":"2025-08-13T13:26:06.226868Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"temp_model = torchvision.models.resnet18()\ntemp_model","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-13T13:26:15.390999Z","iopub.execute_input":"2025-08-13T13:26:15.391299Z","iopub.status.idle":"2025-08-13T13:26:15.617223Z","shell.execute_reply.started":"2025-08-13T13:26:15.391278Z","shell.execute_reply":"2025-08-13T13:26:15.616571Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"list(temp_model.children())[:-2]  # get all layers up to avgpool","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-13T13:26:20.727628Z","iopub.execute_input":"2025-08-13T13:26:20.727962Z","iopub.status.idle":"2025-08-13T13:26:20.735268Z","shell.execute_reply.started":"2025-08-13T13:26:20.727937Z","shell.execute_reply":"2025-08-13T13:26:20.734393Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"torch.nn.Sequential(*list(temp_model.children())[:-2]) # convert the list of layers back to sequential model","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-13T13:26:26.437440Z","iopub.execute_input":"2025-08-13T13:26:26.437748Z","iopub.status.idle":"2025-08-13T13:26:26.444637Z","shell.execute_reply.started":"2025-08-13T13:26:26.437725Z","shell.execute_reply":"2025-08-13T13:26:26.443939Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class PneumoniaModel(pl.LightningModule):\n    def __init__(self):\n        super().__init__()\n        \n        self.model = torchvision.models.resnet18()\n        # Change conv1 from 3 to 1 input channels\n        self.model.conv1 = torch.nn.Conv2d(1, 64, kernel_size=(7, 7), stride=(2, 2), padding=(3, 3), bias=False)\n        # Change out_feature of the last fully connected layer (called fc in resnet18) from 1000 to 1\n        self.model.fc = torch.nn.Linear(in_features=512, out_features=1)\n        \n        # Extract the feature map\n        self.feature_map = torch.nn.Sequential(*list(self.model.children())[:-2])    \n    def forward(self, data):\n        \n        # Compute feature map\n        feature_map = self.feature_map(data)\n        # Use Adaptive Average Pooling as in the original model\n        avg_pool_output = torch.nn.functional.adaptive_avg_pool2d(input=feature_map, output_size=(1, 1))\n        print(avg_pool_output.shape)\n        # Flatten the output into a 512 element vector\n        avg_pool_output_flattened = torch.flatten(avg_pool_output)\n        print(avg_pool_output_flattened.shape)\n        # Compute prediction\n        pred = self.model.fc(avg_pool_output_flattened)\n        return pred, feature_map","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-13T13:26:32.960127Z","iopub.execute_input":"2025-08-13T13:26:32.960455Z","iopub.status.idle":"2025-08-13T13:26:32.967750Z","shell.execute_reply.started":"2025-08-13T13:26:32.960431Z","shell.execute_reply":"2025-08-13T13:26:32.966747Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def 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]\n    weight = weight_params[0].detach()\n    \n    \n    cam = torch.matmul(weight, features)\n    cam_img = cam.reshape(7, 7).cpu()\n    return cam_img, torch.sigmoid(pred)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-13T13:26:55.257623Z","iopub.execute_input":"2025-08-13T13:26:55.258424Z","iopub.status.idle":"2025-08-13T13:26:55.263402Z","shell.execute_reply.started":"2025-08-13T13:26:55.258397Z","shell.execute_reply":"2025-08-13T13:26:55.262590Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Use strict to prevent pytorch from loading weights for self.feature_map\nmodel = PneumoniaModel.load_from_checkpoint(\"/kaggle/input/weights/weights/weights_epoch=10_Val Acc=0.8320.ckpt\", strict=False)\nmodel.eval();","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-13T13:27:30.417525Z","iopub.execute_input":"2025-08-13T13:27:30.418564Z","iopub.status.idle":"2025-08-13T13:27:32.664938Z","shell.execute_reply.started":"2025-08-13T13:27:30.418534Z","shell.execute_reply":"2025-08-13T13:27:32.663902Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def cam(model, img):\n    \"\"\"\n    Compute class activation map according to cam algorithm\n    \"\"\"\n    with torch.no_grad():\n        pred, features = model(img.unsqueeze(0))\n    b, c, h, w = features.shape\n\n    # We reshape the 512x7x7 feature tensor into a 512x49 tensor in order to simplify the multiplication\n    features = features.reshape((c, h*w))\n    \n    # Get only the weights, not the bias\n    weight_params = list(model.model.fc.parameters())[0] \n    \n    # Remove gradient information from weight parameters to enable numpy conversion\n    weight = weight_params[0].detach()\n    print(weight.shape)\n    # Compute multiplication between weight and features with the formula from above.\n    # We use matmul because it directly multiplies each filter with the weights\n    # and then computes the sum. This yields a vector of 49 (7x7 elements)\n    cam = torch.matmul(weight, features)\n    print(features.shape)\n    \n    ### The following loop performs the same operations in a less optimized way\n    #cam = torch.zeros((7 * 7))\n    #for i in range(len(cam)):\n    #    cam[i] = torch.sum(weight*features[:,i])\n    ##################################################################\n    \n    # Normalize and standardize the class activation map (Not always necessary, thus not shown in the lecture)\n    cam = cam - torch.min(cam)\n    cam_img = cam / torch.max(cam)\n    # Reshape the class activation map to 512x7x7 and move the tensor back to CPU\n    cam_img = cam_img.reshape(h, w).cpu()\n\n    return cam_img, torch.sigmoid(pred)\n\ndef visualize(img, heatmap, pred):\n    \"\"\"\n    Visualization function for class activation maps\n    \"\"\"\n    img = img[0]\n    # Resize the activation map of size 7x7 to the original image size (224x224)\n    heatmap = transforms.functional.resize(heatmap.unsqueeze(0), (img.shape[0], img.shape[1]))[0]\n    \n    # Create a figure\n    fig, axis = plt.subplots(1, 2)\n    \n    axis[0].imshow(img, cmap=\"bone\")\n    # Overlay the original image with the upscaled class activation map\n    axis[1].imshow(img, cmap=\"bone\")\n    axis[1].imshow(heatmap, alpha=0.5, cmap=\"jet\")\n    plt.title(f\"Pneumonia: {(pred > 0.5).item()}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-13T13:29:06.321745Z","iopub.execute_input":"2025-08-13T13:29:06.322433Z","iopub.status.idle":"2025-08-13T13:29:06.330919Z","shell.execute_reply.started":"2025-08-13T13:29:06.322405Z","shell.execute_reply":"2025-08-13T13:29:06.329815Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def visualize(img, cam, pred):\n    img = img[0].detach().cpu().numpy()\n    cam = transforms.functional.resize(cam.unsqueeze(0), (224, 224))[0]\n    cam = cam.detach().cpu().numpy()\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    axis[1].set_title(pred)\n    plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-13T13:31:51.376802Z","iopub.execute_input":"2025-08-13T13:31:51.377456Z","iopub.status.idle":"2025-08-13T13:31:51.386494Z","shell.execute_reply.started":"2025-08-13T13:31:51.377426Z","shell.execute_reply":"2025-08-13T13:31:51.385437Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"img = val_dataset[-6][0].to(\"cuda\")  # send image to GPU\nactivation_map, pred = cam(model, img)  # Compute the Class activation map given the subject","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-13T13:32:23.450979Z","iopub.execute_input":"2025-08-13T13:32:23.451627Z","iopub.status.idle":"2025-08-13T13:32:23.470726Z","shell.execute_reply.started":"2025-08-13T13:32:23.451596Z","shell.execute_reply":"2025-08-13T13:32:23.469792Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"visualize(img, activation_map, pred) # Visualize CAM","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-13T13:32:25.131516Z","iopub.execute_input":"2025-08-13T13:32:25.131817Z","iopub.status.idle":"2025-08-13T13:32:26.086010Z","shell.execute_reply.started":"2025-08-13T13:32:25.131795Z","shell.execute_reply":"2025-08-13T13:32:26.085100Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}