{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[],"dockerImageVersionId":28755,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"!pip install grad-cam","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-05T22:14:52.893169Z","iopub.execute_input":"2026-07-05T22:14:52.89381Z","iopub.status.idle":"2026-07-05T22:14:56.265803Z","shell.execute_reply.started":"2026-07-05T22:14:52.89378Z","shell.execute_reply":"2026-07-05T22:14:56.265046Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport random\nfrom pathlib import Path\n\nimport numpy as np\nimport pandas as pd\n\nimport matplotlib.pyplot as plt\n\nfrom PIL import Image\n\nimport torch\nimport torch.nn as nn\nfrom torch.utils.data import DataLoader\nfrom torchvision.datasets import ImageFolder\nfrom torchvision import transforms\n\nfrom tqdm.auto import tqdm\n\nfrom sklearn.model_selection import train_test_split","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-07-05T22:14:56.26743Z","iopub.execute_input":"2026-07-05T22:14:56.267666Z","iopub.status.idle":"2026-07-05T22:14:56.27304Z","shell.execute_reply.started":"2026-07-05T22:14:56.267639Z","shell.execute_reply":"2026-07-05T22:14:56.272214Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\nprint(device)\nprint(torch.cuda.get_device_name(0) if torch.cuda.is_available() else \"CPU\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-05T22:14:56.274007Z","iopub.execute_input":"2026-07-05T22:14:56.27431Z","iopub.status.idle":"2026-07-05T22:14:56.289051Z","shell.execute_reply.started":"2026-07-05T22:14:56.274274Z","shell.execute_reply":"2026-07-05T22:14:56.28831Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"seed = 42\n\nrandom.seed(seed)\nnp.random.seed(seed)\n\ntorch.manual_seed(seed)\ntorch.cuda.manual_seed_all(seed)\n\ntorch.backends.cudnn.deterministic = True\ntorch.backends.cudnn.benchmark = False","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-05T22:14:56.28983Z","iopub.execute_input":"2026-07-05T22:14:56.290456Z","iopub.status.idle":"2026-07-05T22:14:56.304071Z","shell.execute_reply.started":"2026-07-05T22:14:56.290433Z","shell.execute_reply":"2026-07-05T22:14:56.303366Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from pathlib import Path\n\ninput_path = Path(\"/kaggle/input\")\n\nfor path in input_path.iterdir():\n    print(path)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-05T22:14:56.305893Z","iopub.execute_input":"2026-07-05T22:14:56.306314Z","iopub.status.idle":"2026-07-05T22:14:56.318442Z","shell.execute_reply.started":"2026-07-05T22:14:56.306293Z","shell.execute_reply":"2026-07-05T22:14:56.317889Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from pathlib import Path\n\ncompetition_path = Path(\"/kaggle/input/competitions\")\n\nfor path in competition_path.iterdir():\n    print(path)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-05T22:14:56.319394Z","iopub.execute_input":"2026-07-05T22:14:56.319675Z","iopub.status.idle":"2026-07-05T22:14:56.333503Z","shell.execute_reply.started":"2026-07-05T22:14:56.319643Z","shell.execute_reply":"2026-07-05T22:14:56.332808Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"competition = Path(\"/kaggle/input/competitions/galaxy-zoo-the-galaxy-challenge\")\n\nfor path in competition.iterdir():\n    print(path)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-05T22:14:56.334411Z","iopub.execute_input":"2026-07-05T22:14:56.334672Z","iopub.status.idle":"2026-07-05T22:14:56.349869Z","shell.execute_reply.started":"2026-07-05T22:14:56.334652Z","shell.execute_reply":"2026-07-05T22:14:56.34918Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from zipfile import ZipFile\nfrom pathlib import Path\n\ncompetition_path = Path(\"/kaggle/input/competitions/galaxy-zoo-the-galaxy-challenge\")\nworking_path = Path(\"/kaggle/working/galaxy_zoo\")\n\nworking_path.mkdir(exist_ok=True)\n\nmodel_name = \"EfficientNet_B3\"\n\ncheckpoint_dir = working_path / \"checkpoints\"\ncheckpoint_dir.mkdir(exist_ok=True)\n\ncheckpoint_path = checkpoint_dir / f\"{model_name}_best.pth\"\n\nwith ZipFile(competition_path / \"images_training_rev1.zip\") as zip_file:\n    zip_file.extractall(working_path)\n\nwith ZipFile(competition_path / \"training_solutions_rev1.zip\") as zip_file:\n    zip_file.extractall(working_path)\n\nprint(\"Done\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-05T22:14:56.350638Z","iopub.execute_input":"2026-07-05T22:14:56.350934Z","iopub.status.idle":"2026-07-05T22:15:10.448265Z","shell.execute_reply.started":"2026-07-05T22:14:56.350894Z","shell.execute_reply":"2026-07-05T22:15:10.447401Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"sorted(working_path.iterdir())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-05T22:15:10.449365Z","iopub.execute_input":"2026-07-05T22:15:10.449809Z","iopub.status.idle":"2026-07-05T22:15:10.455772Z","shell.execute_reply.started":"2026-07-05T22:15:10.44975Z","shell.execute_reply":"2026-07-05T22:15:10.45486Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"labels = pd.read_csv(working_path / \"training_solutions_rev1.csv\")\n\nlabels.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-05T22:15:10.456865Z","iopub.execute_input":"2026-07-05T22:15:10.457247Z","iopub.status.idle":"2026-07-05T22:15:10.723391Z","shell.execute_reply.started":"2026-07-05T22:15:10.457223Z","shell.execute_reply":"2026-07-05T22:15:10.722732Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(labels.shape)\n\nlabels.columns.tolist()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-05T22:15:10.72429Z","iopub.execute_input":"2026-07-05T22:15:10.724611Z","iopub.status.idle":"2026-07-05T22:15:10.731181Z","shell.execute_reply.started":"2026-07-05T22:15:10.724586Z","shell.execute_reply":"2026-07-05T22:15:10.730319Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"labels.iloc[0]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-05T22:15:10.732245Z","iopub.execute_input":"2026-07-05T22:15:10.732544Z","iopub.status.idle":"2026-07-05T22:15:10.747543Z","shell.execute_reply.started":"2026-07-05T22:15:10.732514Z","shell.execute_reply":"2026-07-05T22:15:10.74655Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"labels.describe().T","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-05T22:15:10.748474Z","iopub.execute_input":"2026-07-05T22:15:10.748786Z","iopub.status.idle":"2026-07-05T22:15:10.888581Z","shell.execute_reply.started":"2026-07-05T22:15:10.748749Z","shell.execute_reply":"2026-07-05T22:15:10.88798Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"targets = labels.drop(columns=\"GalaxyID\")\n\nprint(targets.shape)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-05T22:15:10.891424Z","iopub.execute_input":"2026-07-05T22:15:10.892263Z","iopub.status.idle":"2026-07-05T22:15:10.901917Z","shell.execute_reply.started":"2026-07-05T22:15:10.892238Z","shell.execute_reply":"2026-07-05T22:15:10.901166Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"targets.sum(axis=1).describe()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-05T22:15:10.902823Z","iopub.execute_input":"2026-07-05T22:15:10.903126Z","iopub.status.idle":"2026-07-05T22:15:10.927743Z","shell.execute_reply.started":"2026-07-05T22:15:10.903103Z","shell.execute_reply":"2026-07-05T22:15:10.927168Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from pathlib import Path\n\nimport torch\nfrom PIL import Image\nfrom torch.utils.data import Dataset\n\n\nclass GalaxyZooDataset(Dataset):\n\n    def __init__(self, dataframe, image_dir, transform=None):\n        self.dataframe = dataframe.reset_index(drop=True)\n        self.image_dir = Path(image_dir)\n        self.transform = transform\n\n    def __len__(self):\n        return len(self.dataframe)\n\n    def __getitem__(self, idx):\n        row = self.dataframe.iloc[idx]\n    \n        galaxy_id = int(row[\"GalaxyID\"])\n        image_path = self.image_dir / f\"{galaxy_id}.jpg\"\n    \n        image = Image.open(image_path).convert(\"RGB\")\n    \n        target = torch.tensor(\n            row.drop(\"GalaxyID\").to_numpy(dtype=np.float32),\n            dtype=torch.float32\n        )\n    \n        if self.transform is not None:\n            image = self.transform(image)\n    \n        return image, target","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-05T22:15:10.928538Z","iopub.execute_input":"2026-07-05T22:15:10.928835Z","iopub.status.idle":"2026-07-05T22:15:10.935323Z","shell.execute_reply.started":"2026-07-05T22:15:10.928813Z","shell.execute_reply":"2026-07-05T22:15:10.934696Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"transform = transforms.Compose([\n    transforms.Resize((224, 224)),\n    transforms.ToTensor(),\n])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-05T22:15:10.936061Z","iopub.execute_input":"2026-07-05T22:15:10.936353Z","iopub.status.idle":"2026-07-05T22:15:10.950239Z","shell.execute_reply.started":"2026-07-05T22:15:10.936324Z","shell.execute_reply":"2026-07-05T22:15:10.949506Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"dataset = GalaxyZooDataset(\n    dataframe=labels,\n    image_dir=working_path / \"images_training_rev1\",\n    transform=transform,\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-05T22:15:10.95122Z","iopub.execute_input":"2026-07-05T22:15:10.951429Z","iopub.status.idle":"2026-07-05T22:15:10.96807Z","shell.execute_reply.started":"2026-07-05T22:15:10.951411Z","shell.execute_reply":"2026-07-05T22:15:10.967265Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(len(dataset))\n\nimage, target = dataset[0]\n\nprint(image.shape)\nprint(target.shape)\nprint(target.dtype)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-05T22:15:10.969002Z","iopub.execute_input":"2026-07-05T22:15:10.969292Z","iopub.status.idle":"2026-07-05T22:15:10.982071Z","shell.execute_reply.started":"2026-07-05T22:15:10.969258Z","shell.execute_reply":"2026-07-05T22:15:10.981384Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from torch.utils.data import random_split\n\ntrain_size = int(0.70 * len(dataset))\nval_size = int(0.15 * len(dataset))\ntest_size = len(dataset) - train_size - val_size\n\ntrain_dataset, val_dataset, test_dataset = random_split(\n    dataset,\n    [train_size, val_size, test_size],\n    generator=torch.Generator().manual_seed(seed)\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-05T22:15:10.982894Z","iopub.execute_input":"2026-07-05T22:15:10.983114Z","iopub.status.idle":"2026-07-05T22:15:10.992608Z","shell.execute_reply.started":"2026-07-05T22:15:10.983085Z","shell.execute_reply":"2026-07-05T22:15:10.991718Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(len(train_dataset))\nprint(len(val_dataset))\nprint(len(test_dataset))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-05T22:15:10.993653Z","iopub.execute_input":"2026-07-05T22:15:10.994008Z","iopub.status.idle":"2026-07-05T22:15:10.998692Z","shell.execute_reply.started":"2026-07-05T22:15:10.993988Z","shell.execute_reply":"2026-07-05T22:15:10.997935Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"num_workers = os.cpu_count()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-05T22:15:10.999587Z","iopub.execute_input":"2026-07-05T22:15:10.999914Z","iopub.status.idle":"2026-07-05T22:15:11.012438Z","shell.execute_reply.started":"2026-07-05T22:15:10.999883Z","shell.execute_reply":"2026-07-05T22:15:11.011476Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"batch_size = 32\n\ntrain_loader = DataLoader(\n    train_dataset,\n    batch_size=batch_size,\n    shuffle=True,\n    num_workers=num_workers,\n    pin_memory=True,\n    persistent_workers=True,\n    prefetch_factor=2\n)\n\nval_loader = DataLoader(\n    val_dataset,\n    batch_size=batch_size,\n    shuffle=False,\n    num_workers=num_workers,\n    pin_memory=True,\n    persistent_workers=True,\n    prefetch_factor=2\n)\n\ntest_loader = DataLoader(\n    test_dataset,\n    batch_size=batch_size,\n    shuffle=False,\n    num_workers=num_workers,\n    pin_memory=True,\n    persistent_workers=True,\n    prefetch_factor=2\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-05T22:15:11.013633Z","iopub.execute_input":"2026-07-05T22:15:11.013975Z","iopub.status.idle":"2026-07-05T22:15:11.027696Z","shell.execute_reply.started":"2026-07-05T22:15:11.01395Z","shell.execute_reply":"2026-07-05T22:15:11.027128Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"images, targets = next(iter(train_loader))\n\nprint(images.shape)\nprint(targets.shape)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-05T22:15:11.028444Z","iopub.execute_input":"2026-07-05T22:15:11.028622Z","iopub.status.idle":"2026-07-05T22:15:11.819838Z","shell.execute_reply.started":"2026-07-05T22:15:11.028605Z","shell.execute_reply":"2026-07-05T22:15:11.818568Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"TRAIN_RATIO = 0.70\nVAL_RATIO = 0.15\nTEST_RATIO = 0.15\n\ntrain_df, temp_df = train_test_split(\n    labels,\n    train_size=TRAIN_RATIO,\n    random_state=seed,\n    shuffle=True\n)\n\nval_df, test_df = train_test_split(\n    temp_df,\n    train_size=VAL_RATIO / (VAL_RATIO + TEST_RATIO),\n    random_state=seed,\n    shuffle=True\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-05T22:15:11.824058Z","iopub.execute_input":"2026-07-05T22:15:11.824617Z","iopub.status.idle":"2026-07-05T22:15:11.916509Z","shell.execute_reply.started":"2026-07-05T22:15:11.824561Z","shell.execute_reply":"2026-07-05T22:15:11.915243Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from torchvision.models import efficientnet_b3, EfficientNet_B3_Weights\n\nIMG_SIZE = 300 \n\ntrain_transform = transforms.Compose([\n    transforms.Resize((IMG_SIZE, IMG_SIZE)),\n    transforms.RandomHorizontalFlip(),\n    transforms.RandomVerticalFlip(),\n    transforms.RandomRotation(180),\n    transforms.ToTensor(),\n    transforms.Normalize(\n        mean=[0.485, 0.456, 0.406],\n        std=[0.229, 0.224, 0.225]\n    )\n])\n\neval_transform = transforms.Compose([\n    transforms.Resize((IMG_SIZE, IMG_SIZE)),\n    transforms.ToTensor(),\n    transforms.Normalize(\n        mean=[0.485, 0.456, 0.406],\n        std=[0.229, 0.224, 0.225]\n    )\n])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-05T22:15:11.91814Z","iopub.execute_input":"2026-07-05T22:15:11.918619Z","iopub.status.idle":"2026-07-05T22:15:12.646771Z","shell.execute_reply.started":"2026-07-05T22:15:11.918567Z","shell.execute_reply":"2026-07-05T22:15:12.644497Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_dataset = GalaxyZooDataset(\n    dataframe=train_df,\n    image_dir=working_path / \"images_training_rev1\",\n    transform=train_transform\n)\n\nval_dataset = GalaxyZooDataset(\n    dataframe=val_df,\n    image_dir=working_path / \"images_training_rev1\",\n    transform=eval_transform\n)\n\ntest_dataset = GalaxyZooDataset(\n    dataframe=test_df,\n    image_dir=working_path / \"images_training_rev1\",\n    transform=eval_transform\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-05T22:15:12.648326Z","iopub.execute_input":"2026-07-05T22:15:12.64866Z","iopub.status.idle":"2026-07-05T22:15:12.687438Z","shell.execute_reply.started":"2026-07-05T22:15:12.648623Z","shell.execute_reply":"2026-07-05T22:15:12.686869Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"num_workers = os.cpu_count()\n\ntrain_loader = DataLoader(\n    train_dataset,\n    batch_size=batch_size,\n    shuffle=True,\n    num_workers=num_workers,\n    pin_memory=True,\n    persistent_workers=True,\n    prefetch_factor=2\n)\n\nval_loader = DataLoader(\n    val_dataset,\n    batch_size=batch_size,\n    shuffle=False,\n    num_workers=num_workers,\n    pin_memory=True,\n    persistent_workers=True,\n    prefetch_factor=2\n)\n\ntest_loader = DataLoader(\n    test_dataset,\n    batch_size=batch_size,\n    shuffle=False,\n    num_workers=num_workers,\n    pin_memory=True,\n    persistent_workers=True,\n    prefetch_factor=2\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-05T22:15:12.688478Z","iopub.execute_input":"2026-07-05T22:15:12.688793Z","iopub.status.idle":"2026-07-05T22:15:12.72228Z","shell.execute_reply.started":"2026-07-05T22:15:12.688763Z","shell.execute_reply":"2026-07-05T22:15:12.721485Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"images, targets = next(iter(train_loader))\n\nprint(images.shape)\nprint(targets.shape)\n\nprint(images.min())\nprint(images.max())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-05T22:15:12.723301Z","iopub.execute_input":"2026-07-05T22:15:12.723561Z","iopub.status.idle":"2026-07-05T22:15:13.410902Z","shell.execute_reply.started":"2026-07-05T22:15:12.72354Z","shell.execute_reply":"2026-07-05T22:15:13.409811Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class GalaxyZooModel(nn.Module):\n    def __init__(self):\n        super().__init__()\n        self.backbone = efficientnet_b3(weights=EfficientNet_B3_Weights.DEFAULT)\n        \n        in_features = self.backbone.classifier[1].in_features\n        self.backbone.classifier = nn.Identity()\n        \n        self.q1 = nn.Sequential(nn.Linear(in_features, 3), nn.Softmax(dim=1))\n        self.q2 = nn.Sequential(nn.Linear(in_features, 2), nn.Softmax(dim=1))\n        self.q3 = nn.Sequential(nn.Linear(in_features, 2), nn.Softmax(dim=1))\n        self.q4 = nn.Sequential(nn.Linear(in_features, 2), nn.Softmax(dim=1))\n        self.q5 = nn.Sequential(nn.Linear(in_features, 4), nn.Softmax(dim=1))\n        self.q6 = nn.Sequential(nn.Linear(in_features, 2), nn.Softmax(dim=1))\n        self.q7 = nn.Sequential(nn.Linear(in_features, 3), nn.Softmax(dim=1))\n        self.q8 = nn.Sequential(nn.Linear(in_features, 7), nn.Softmax(dim=1))\n        self.q9 = nn.Sequential(nn.Linear(in_features, 3), nn.Softmax(dim=1))\n        self.q10 = nn.Sequential(nn.Linear(in_features, 3), nn.Softmax(dim=1))\n        self.q11 = nn.Sequential(nn.Linear(in_features, 6), nn.Softmax(dim=1))\n\n    def forward(self, x):\n        features = self.backbone(x)\n        \n        out_q1 = self.q1(features) \n        \n        prob_disk = out_q1[:, 1:2] \n        out_q2 = self.q2(features) * prob_disk\n        \n        prob_not_edge = out_q2[:, 1:2]\n        out_q3 = self.q3(features) * prob_not_edge\n        \n        out_q4 = self.q4(features) * prob_not_edge\n        out_q5 = self.q5(features) * prob_not_edge\n        \n        out_q6 = self.q6(features)\n        \n        prob_smooth = out_q1[:, 0:1]\n        out_q7 = self.q7(features) * prob_smooth\n        \n        prob_odd = out_q6[:, 0:1]\n        out_q8 = self.q8(features) * prob_odd\n        \n        out_q9 = self.q9(features)\n        out_q10 = self.q10(features)\n        out_q11 = self.q11(features)\n\n        return torch.cat([\n            out_q1, out_q2, out_q3, out_q4, out_q5, out_q6, \n            out_q7, out_q8, out_q9, out_q10, out_q11\n        ], dim=1)\n\nmodel = GalaxyZooModel().to(device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-05T22:15:13.41305Z","iopub.execute_input":"2026-07-05T22:15:13.413395Z","iopub.status.idle":"2026-07-05T22:15:14.87541Z","shell.execute_reply.started":"2026-07-05T22:15:13.413343Z","shell.execute_reply":"2026-07-05T22:15:14.874858Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"criterion = nn.MSELoss()\n\noptimizer = torch.optim.AdamW(\n    model.parameters(),\n    lr=1e-4,\n    weight_decay=1e-4\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-05T22:15:14.876325Z","iopub.execute_input":"2026-07-05T22:15:14.876589Z","iopub.status.idle":"2026-07-05T22:15:14.882566Z","shell.execute_reply.started":"2026-07-05T22:15:14.876566Z","shell.execute_reply":"2026-07-05T22:15:14.881961Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(\n    optimizer,\n    mode=\"min\",\n    factor=0.1,\n    patience=3\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-05T22:15:14.883505Z","iopub.execute_input":"2026-07-05T22:15:14.883855Z","iopub.status.idle":"2026-07-05T22:15:14.898796Z","shell.execute_reply.started":"2026-07-05T22:15:14.883833Z","shell.execute_reply":"2026-07-05T22:15:14.898244Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"scaler = torch.amp.GradScaler(\"cuda\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-05T22:15:14.899575Z","iopub.execute_input":"2026-07-05T22:15:14.899849Z","iopub.status.idle":"2026-07-05T22:15:14.915431Z","shell.execute_reply.started":"2026-07-05T22:15:14.89983Z","shell.execute_reply":"2026-07-05T22:15:14.914902Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"epochs = 30\n\nbest_val_loss = float(\"inf\")\n\npatience = 5\npatience_counter = 0","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-05T22:15:14.916313Z","iopub.execute_input":"2026-07-05T22:15:14.916586Z","iopub.status.idle":"2026-07-05T22:15:14.930471Z","shell.execute_reply.started":"2026-07-05T22:15:14.916557Z","shell.execute_reply":"2026-07-05T22:15:14.929924Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"history = {\n    \"train_loss\": [],\n    \"val_loss\": [],\n    \"learning_rate\": []\n}","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-05T22:15:14.931256Z","iopub.execute_input":"2026-07-05T22:15:14.931515Z","iopub.status.idle":"2026-07-05T22:15:14.945286Z","shell.execute_reply.started":"2026-07-05T22:15:14.931486Z","shell.execute_reply":"2026-07-05T22:15:14.944783Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from tqdm.auto import tqdm\n\ndef train_one_epoch(\n    model,\n    dataloader,\n    criterion,\n    optimizer,\n    scaler,\n    device\n):\n    model.train()\n\n    running_loss = 0.0\n\n    progress_bar = tqdm(\n        dataloader,\n        desc=\"Training\",\n        leave=False\n    )\n\n    for images, targets in progress_bar:\n\n        images = images.to(device, non_blocking=True)\n        targets = targets.to(device, non_blocking=True)\n\n        optimizer.zero_grad(set_to_none=True)\n\n        with torch.amp.autocast(\n            device_type=device.type,\n            enabled=device.type == \"cuda\"\n        ):\n            outputs = model(images)\n            loss = criterion(outputs, targets)\n\n        scaler.scale(loss).backward()\n\n        scaler.step(optimizer)\n\n        scaler.update()\n\n        running_loss += loss.item() * images.size(0)\n\n        progress_bar.set_postfix(\n            loss=f\"{loss.item():.4f}\"\n        )\n\n    epoch_loss = running_loss / len(dataloader.dataset)\n\n    return {\n    \"loss\": epoch_loss\n    }","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-05T22:15:14.946079Z","iopub.execute_input":"2026-07-05T22:15:14.946413Z","iopub.status.idle":"2026-07-05T22:15:14.960833Z","shell.execute_reply.started":"2026-07-05T22:15:14.946381Z","shell.execute_reply":"2026-07-05T22:15:14.960273Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def validate(\n    model,\n    dataloader,\n    criterion,\n    device\n):\n    model.eval()\n\n    running_loss = 0.0\n\n    with torch.no_grad():\n\n        progress_bar = tqdm(\n            dataloader,\n            desc=\"Validation\",\n            leave=False\n        )\n\n        for images, targets in progress_bar:\n\n            images = images.to(device, non_blocking=True)\n            targets = targets.to(device, non_blocking=True)\n\n            with torch.amp.autocast(\n                device_type=device.type,\n                enabled=device.type == \"cuda\"\n            ):\n                outputs = model(images)\n                loss = criterion(outputs, targets)\n\n            running_loss += loss.item() * images.size(0)\n\n            progress_bar.set_postfix(\n                loss=f\"{loss.item():.4f}\"\n            )\n\n    epoch_loss = running_loss / len(dataloader.dataset)\n\n    return {\n    \"loss\": epoch_loss}","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-05T22:15:14.961534Z","iopub.execute_input":"2026-07-05T22:15:14.961913Z","iopub.status.idle":"2026-07-05T22:15:14.982153Z","shell.execute_reply.started":"2026-07-05T22:15:14.961873Z","shell.execute_reply":"2026-07-05T22:15:14.981607Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def fit(\n    model,\n    train_loader,\n    val_loader,\n    criterion,\n    optimizer,\n    scheduler,\n    scaler,\n    epochs,\n    device\n):\n    history = {\n        \"train_loss\": [],\n        \"val_loss\": [],\n        \"learning_rate\": []\n    }\n\n    best_val_loss = float(\"inf\")\n\n    patience_counter = 0\n\n    for epoch in range(1, epochs + 1):\n\n        print(f\"\\nEpoch {epoch}/{epochs}\")\n\n        train_metrics = train_one_epoch(\n            model,\n            train_loader,\n            criterion,\n            optimizer,\n            scaler,\n            device\n        )\n\n        val_metrics = validate(\n            model,\n            val_loader,\n            criterion,\n            device\n        )\n\n        train_loss = train_metrics[\"loss\"]\n        val_loss = val_metrics[\"loss\"]\n\n        scheduler.step(val_loss)\n\n        learning_rate = optimizer.param_groups[0][\"lr\"]\n\n        history[\"train_loss\"].append(train_loss)\n        history[\"val_loss\"].append(val_loss)\n        history[\"learning_rate\"].append(learning_rate)\n\n        print(\n            f\"Train Loss: {train_loss:.4f} | \"\n            f\"Val Loss: {val_loss:.4f} | \"\n            f\"LR: {learning_rate:.2e}\"\n        )\n\n        if val_loss < best_val_loss:\n\n            best_val_loss = val_loss\n\n            patience_counter = 0\n\n            torch.save({\n                \"epoch\": epoch,\n                \"model_state_dict\": model.state_dict(),\n                \"optimizer_state_dict\": optimizer.state_dict(),\n                \"scheduler_state_dict\": scheduler.state_dict(),\n                \"best_val_loss\": best_val_loss,\n            }, checkpoint_path)\n\n        else:\n\n            patience_counter += 1\n\n            print(\n                f\"No improvement \"\n                f\"({patience_counter}/{patience})\"\n            )\n\n            if patience_counter >= patience:\n\n                print(\"Early stopping.\")\n\n                break\n\n    return history","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-05T22:15:14.982997Z","iopub.execute_input":"2026-07-05T22:15:14.983485Z","iopub.status.idle":"2026-07-05T22:15:14.996895Z","shell.execute_reply.started":"2026-07-05T22:15:14.983463Z","shell.execute_reply":"2026-07-05T22:15:14.996366Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"total_params = sum(p.numel() for p in model.parameters())\ntrainable_params = sum(p.numel() for p in model.parameters() if p.requires_grad)\n\nprint(f\"Total parameters     : {total_params:,}\")\nprint(f\"Trainable parameters : {trainable_params:,}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-05T22:15:14.997777Z","iopub.execute_input":"2026-07-05T22:15:14.998043Z","iopub.status.idle":"2026-07-05T22:15:15.021105Z","shell.execute_reply.started":"2026-07-05T22:15:14.998014Z","shell.execute_reply":"2026-07-05T22:15:15.020551Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"images, targets = next(iter(train_loader))\n\nimages = images.to(device)\n\nwith torch.no_grad():\n    outputs = model(images)\n\nprint(outputs.shape)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-05T22:15:15.021883Z","iopub.execute_input":"2026-07-05T22:15:15.022153Z","iopub.status.idle":"2026-07-05T22:15:17.152199Z","shell.execute_reply.started":"2026-07-05T22:15:15.022121Z","shell.execute_reply":"2026-07-05T22:15:17.151543Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"targets = targets.to(device)\n\nloss = criterion(outputs, targets)\n\nprint(loss.item())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-05T22:15:17.156253Z","iopub.execute_input":"2026-07-05T22:15:17.156517Z","iopub.status.idle":"2026-07-05T22:15:17.233162Z","shell.execute_reply.started":"2026-07-05T22:15:17.156493Z","shell.execute_reply":"2026-07-05T22:15:17.232346Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"history = fit(\n    model=model,\n    train_loader=train_loader,\n    val_loader=val_loader,\n    criterion=criterion,\n    optimizer=optimizer,\n    scheduler=scheduler,\n    scaler=scaler,\n    epochs=epochs,\n    device=device\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-05T22:15:17.234076Z","iopub.execute_input":"2026-07-05T22:15:17.234369Z","iopub.status.idle":"2026-07-05T22:25:23.484891Z","shell.execute_reply.started":"2026-07-05T22:15:17.234347Z","shell.execute_reply":"2026-07-05T22:25:23.483609Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"checkpoint = torch.load(\n    checkpoint_path,\n    map_location=device\n)\n\nmodel.load_state_dict(checkpoint[\"model_state_dict\"])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-05T22:25:23.485532Z","iopub.status.idle":"2026-07-05T22:25:23.485856Z","shell.execute_reply.started":"2026-07-05T22:25:23.485664Z","shell.execute_reply":"2026-07-05T22:25:23.48568Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model.eval()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-05T22:25:23.487505Z","iopub.status.idle":"2026-07-05T22:25:23.487896Z","shell.execute_reply.started":"2026-07-05T22:25:23.487698Z","shell.execute_reply":"2026-07-05T22:25:23.487748Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def evaluate(\n    model,\n    dataloader,\n    criterion,\n    device\n):\n    model.eval()\n\n    running_loss = 0.0\n\n    predictions = []\n    targets = []\n\n    with torch.no_grad():\n\n        for images, labels in tqdm(dataloader):\n\n            images = images.to(device, non_blocking=True)\n            labels = labels.to(device, non_blocking=True)\n\n            outputs = model(images)\n\n            loss = criterion(outputs, labels)\n\n            running_loss += loss.item() * images.size(0)\n\n            predictions.append(\n                outputs.cpu()\n            )\n\n            targets.append(\n                labels.cpu()\n            )\n\n    test_loss = running_loss / len(dataloader.dataset)\n\n    predictions = torch.cat(predictions)\n    targets = torch.cat(targets)\n\n    return test_loss, predictions, targets","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-05T22:25:23.489056Z","iopub.status.idle":"2026-07-05T22:25:23.489322Z","shell.execute_reply.started":"2026-07-05T22:25:23.489196Z","shell.execute_reply":"2026-07-05T22:25:23.489211Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_loss, predictions, targets = evaluate(\n    model,\n    test_loader,\n    criterion,\n    device\n)\n\nprint(f\"Test loss: {test_loss:.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-05T22:25:23.490793Z","iopub.status.idle":"2026-07-05T22:25:23.491211Z","shell.execute_reply.started":"2026-07-05T22:25:23.490989Z","shell.execute_reply":"2026-07-05T22:25:23.491015Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"mae = torch.mean(\n    torch.abs(predictions - targets)\n)\n\nprint(f\"MAE: {mae:.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-05T22:25:23.492844Z","iopub.status.idle":"2026-07-05T22:25:23.493122Z","shell.execute_reply.started":"2026-07-05T22:25:23.492985Z","shell.execute_reply":"2026-07-05T22:25:23.493001Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"rmse = torch.sqrt(\n    torch.mean(\n        (predictions - targets) ** 2\n    )\n)\n\nprint(f\"RMSE: {rmse:.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-05T22:25:23.494473Z","iopub.status.idle":"2026-07-05T22:25:23.494841Z","shell.execute_reply.started":"2026-07-05T22:25:23.49465Z","shell.execute_reply":"2026-07-05T22:25:23.494667Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\n\nplt.figure(figsize=(8, 5))\n\nplt.plot(history[\"train_loss\"], label=\"Train\")\nplt.plot(history[\"val_loss\"], label=\"Validation\")\n\nplt.xlabel(\"Epoch\")\nplt.ylabel(\"Loss\")\nplt.title(\"Training History\")\n\nplt.legend()\n\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-05T22:25:23.495715Z","iopub.status.idle":"2026-07-05T22:25:23.496066Z","shell.execute_reply.started":"2026-07-05T22:25:23.495917Z","shell.execute_reply":"2026-07-05T22:25:23.495942Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"per_output_mae = torch.mean(\n    torch.abs(predictions - targets),\n    dim=0\n)\n\nprint(per_output_mae)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-05T22:25:23.49766Z","iopub.status.idle":"2026-07-05T22:25:23.497998Z","shell.execute_reply.started":"2026-07-05T22:25:23.497863Z","shell.execute_reply":"2026-07-05T22:25:23.49788Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"question_names = labels.columns[1:]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-05T22:25:23.499686Z","iopub.status.idle":"2026-07-05T22:25:23.500376Z","shell.execute_reply.started":"2026-07-05T22:25:23.500181Z","shell.execute_reply":"2026-07-05T22:25:23.500214Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\n\nmae_df = pd.DataFrame({\n    \"Output\": question_names,\n    \"MAE\": per_output_mae.numpy()\n})\n\nmae_df","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-05T22:25:23.5013Z","iopub.status.idle":"2026-07-05T22:25:23.501676Z","shell.execute_reply.started":"2026-07-05T22:25:23.501488Z","shell.execute_reply":"2026-07-05T22:25:23.501506Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"mae_df = mae_df.sort_values(\n    by=\"MAE\",\n    ascending=False\n)\n\nmae_df","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-05T22:25:23.503608Z","iopub.status.idle":"2026-07-05T22:25:23.504028Z","shell.execute_reply.started":"2026-07-05T22:25:23.503823Z","shell.execute_reply":"2026-07-05T22:25:23.503848Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\n\nplt.figure(figsize=(12, 6))\n\nplt.bar(\n    mae_df[\"Output\"],\n    mae_df[\"MAE\"]\n)\n\nplt.xticks(rotation=90)\n\nplt.ylabel(\"Mean Absolute Error\")\n\nplt.title(\"MAE for Each Galaxy Zoo Output\")\n\nplt.tight_layout()\n\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-05T22:25:23.505218Z","iopub.status.idle":"2026-07-05T22:25:23.505479Z","shell.execute_reply.started":"2026-07-05T22:25:23.505349Z","shell.execute_reply":"2026-07-05T22:25:23.505372Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"target_stats = pd.DataFrame({\n    \"Output\": labels.columns[1:],\n    \"Mean\": targets.mean(dim=0).numpy(),\n    \"Std\": targets.std(dim=0).numpy(),\n    \"MAE\": per_output_mae.numpy()\n})\n\ntarget_stats.sort_values(\"MAE\", ascending=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-05T22:25:23.506809Z","iopub.status.idle":"2026-07-05T22:25:23.507232Z","shell.execute_reply.started":"2026-07-05T22:25:23.507005Z","shell.execute_reply":"2026-07-05T22:25:23.507029Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plt.figure(figsize=(7,7))\n\nplt.scatter(\n    target_stats[\"Std\"],\n    target_stats[\"MAE\"]\n)\n\nplt.xlabel(\"Target Standard Deviation\")\nplt.ylabel(\"MAE\")\n\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-05T22:25:23.509437Z","iopub.status.idle":"2026-07-05T22:25:23.50985Z","shell.execute_reply.started":"2026-07-05T22:25:23.509609Z","shell.execute_reply":"2026-07-05T22:25:23.509626Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"sample_mae = torch.mean(\n    torch.abs(predictions - targets),\n    dim=1\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-05T22:25:23.511059Z","iopub.status.idle":"2026-07-05T22:25:23.511432Z","shell.execute_reply.started":"2026-07-05T22:25:23.511241Z","shell.execute_reply":"2026-07-05T22:25:23.511272Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"sorted_indices = torch.argsort(sample_mae)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-05T22:25:23.512587Z","iopub.status.idle":"2026-07-05T22:25:23.512984Z","shell.execute_reply.started":"2026-07-05T22:25:23.512776Z","shell.execute_reply":"2026-07-05T22:25:23.512803Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"sorted_indices[0]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-05T22:25:23.514893Z","iopub.status.idle":"2026-07-05T22:25:23.515307Z","shell.execute_reply.started":"2026-07-05T22:25:23.515095Z","shell.execute_reply":"2026-07-05T22:25:23.515122Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"sorted_indices[-1]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-05T22:25:23.516825Z","iopub.status.idle":"2026-07-05T22:25:23.517215Z","shell.execute_reply.started":"2026-07-05T22:25:23.517001Z","shell.execute_reply":"2026-07-05T22:25:23.517027Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def show_prediction(index):\n\n    image, target = test_dataset[index]\n\n    with torch.no_grad():\n\n        output = model(\n            image.unsqueeze(0).to(device)\n        )\n\n        prediction = output.cpu().squeeze()\n\n    # Undo normalization\n    image = image.permute(1, 2, 0)\n\n    image = image * torch.tensor([0.229, 0.224, 0.225]) \\\n                  + torch.tensor([0.485, 0.456, 0.406])\n\n    image = image.clamp(0, 1)\n\n    plt.figure(figsize=(5,5))\n    plt.imshow(image)\n    plt.axis(\"off\")\n    plt.show()\n\n    print(\"Target:\")\n    print(target)\n\n    print()\n\n    print(\"Prediction:\")\n    print(prediction)\n\n    print()\n\n    print(\"MAE:\",\n          torch.mean(torch.abs(target - prediction)).item())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-05T22:25:23.518282Z","iopub.status.idle":"2026-07-05T22:25:23.518671Z","shell.execute_reply.started":"2026-07-05T22:25:23.518475Z","shell.execute_reply":"2026-07-05T22:25:23.5185Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"show_prediction(sorted_indices[0].item())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-05T22:25:23.520642Z","iopub.status.idle":"2026-07-05T22:25:23.52106Z","shell.execute_reply.started":"2026-07-05T22:25:23.520856Z","shell.execute_reply":"2026-07-05T22:25:23.520883Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"show_prediction(sorted_indices[-1].item())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-05T22:25:23.521983Z","iopub.status.idle":"2026-07-05T22:25:23.522294Z","shell.execute_reply.started":"2026-07-05T22:25:23.522157Z","shell.execute_reply":"2026-07-05T22:25:23.522174Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from pytorch_grad_cam import GradCAM\nfrom pytorch_grad_cam.utils.image import show_cam_on_image\nfrom pytorch_grad_cam.utils.model_targets import ClassifierOutputTarget\nimport matplotlib.pyplot as plt\nimport numpy as np\n\ntarget_layers = [model.backbone.features[-1]]\n\ncam = GradCAM(model=model, target_layers=target_layers)\n\ntargets = [ClassifierOutputTarget(0)]\n\nimage_tensor, _ = test_dataset[0]\ninput_tensor = image_tensor.unsqueeze(0).to(device)\n\nrgb_img = image_tensor.permute(1, 2, 0).numpy()\nrgb_img = rgb_img * np.array([0.229, 0.224, 0.225]) + np.array([0.485, 0.456, 0.406])\nrgb_img = np.clip(rgb_img, 0, 1)\n\ngrayscale_cam = cam(input_tensor=input_tensor, targets=targets)[0, :]\n\nvisualization = show_cam_on_image(rgb_img, grayscale_cam, use_rgb=True)\n\nplt.figure(figsize=(6,6))\nplt.imshow(visualization)\nplt.title(\"Grad-CAM: Target Class 1.1\")\nplt.axis('off')\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-05T22:25:23.526259Z","iopub.status.idle":"2026-07-05T22:25:23.526658Z","shell.execute_reply.started":"2026-07-05T22:25:23.526456Z","shell.execute_reply":"2026-07-05T22:25:23.526481Z"}},"outputs":[],"execution_count":null}]}