{"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":"# =========================================\n# Step 1: Import libraries and check GPU\n# =========================================\n\nimport os\nimport glob\nimport numpy as np\nimport pandas as pd\nfrom PIL import Image, ImageFile\n\nimport torch\nimport torch.nn as nn\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import models, transforms\n\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import classification_report, confusion_matrix, accuracy_score\n\nImageFile.LOAD_TRUNCATED_IMAGES = True\n\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nprint(\"Using device:\", device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-21T17:55:27.665731Z","iopub.execute_input":"2026-09-21T17:55:27.66607Z","iopub.status.idle":"2026-09-21T17:55:27.672472Z","shell.execute_reply.started":"2026-09-21T17:55:27.666044Z","shell.execute_reply":"2026-09-21T17:55:27.671543Z"}},"outputs":[{"name":"stdout","text":"Using device: cuda\n","output_type":"stream"}],"execution_count":7},{"cell_type":"code","source":"# =========================================\n# Step 2: Extract Galaxy Zoo dataset\n# =========================================\n\nimport zipfile\nimport glob\nimport os\n\nbase_path = \"/kaggle/input/competitions/galaxy-zoo-the-galaxy-challenge\"\n\nsolution_zip = os.path.join(\n    base_path,\n    \"training_solutions_rev1.zip\"\n)\n\nimage_zip = os.path.join(\n    base_path,\n    \"images_training_rev1.zip\"\n)\n\n# -----------------------------\n# Extract labels\n# -----------------------------\n\nsolution_dir = \"/kaggle/working/galaxy_zoo_solutions\"\n\nos.makedirs(solution_dir, exist_ok=True)\n\nwith zipfile.ZipFile(solution_zip, \"r\") as zip_ref:\n    zip_ref.extractall(solution_dir)\n\n# -----------------------------\n# Extract images\n# -----------------------------\n\nimage_dir = \"/kaggle/working/galaxy_zoo_images\"\n\nos.makedirs(image_dir, exist_ok=True)\n\nwith zipfile.ZipFile(image_zip, \"r\") as zip_ref:\n    zip_ref.extractall(image_dir)\n\n# -----------------------------\n# Find CSV\n# -----------------------------\n\ncsv_files = glob.glob(\n    solution_dir + \"/**/*.csv\",\n    recursive=True\n)\n\nlabel_csv = csv_files[0]\n\n# -----------------------------\n# Find images\n# -----------------------------\n\nimage_files = glob.glob(\n    image_dir + \"/**/*.jpg\",\n    recursive=True\n)\n\nprint(\"CSV:\", label_csv)\nprint(\"Total images found:\", len(image_files))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-21T17:55:31.900555Z","iopub.execute_input":"2026-09-21T17:55:31.900968Z","iopub.status.idle":"2026-09-21T17:55:51.556235Z","shell.execute_reply.started":"2026-09-21T17:55:31.900936Z","shell.execute_reply":"2026-09-21T17:55:51.555151Z"}},"outputs":[{"name":"stdout","text":"CSV: /kaggle/working/galaxy_zoo_solutions/training_solutions_rev1.csv\nTotal images found: 61578\n","output_type":"stream"}],"execution_count":8},{"cell_type":"code","source":"labels_df = pd.read_csv(label_csv)\nprint(\"Labels shape:\", labels_df.shape)\nprint(labels_df.head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-21T17:55:57.340008Z","iopub.execute_input":"2026-09-21T17:55:57.340504Z","iopub.status.idle":"2026-09-21T17:55:57.656758Z","shell.execute_reply.started":"2026-09-21T17:55:57.340468Z","shell.execute_reply":"2026-09-21T17:55:57.655844Z"}},"outputs":[{"name":"stdout","text":"Labels shape: (61578, 38)\n   GalaxyID  Class1.1  Class1.2  Class1.3  Class2.1  Class2.2  Class3.1  \\\n0    100008  0.383147  0.616853  0.000000  0.000000  0.616853  0.038452   \n1    100023  0.327001  0.663777  0.009222  0.031178  0.632599  0.467370   \n2    100053  0.765717  0.177352  0.056931  0.000000  0.177352  0.000000   \n3    100078  0.693377  0.238564  0.068059  0.000000  0.238564  0.109493   \n4    100090  0.933839  0.000000  0.066161  0.000000  0.000000  0.000000   \n\n   Class3.2  Class4.1  Class4.2  ...  Class9.3  Class10.1  Class10.2  \\\n0  0.578401  0.418398  0.198455  ...  0.000000   0.279952   0.138445   \n1  0.165229  0.591328  0.041271  ...  0.018764   0.000000   0.131378   \n2  0.177352  0.000000  0.177352  ...  0.000000   0.000000   0.000000   \n3  0.129071  0.189098  0.049466  ...  0.000000   0.094549   0.000000   \n4  0.000000  0.000000  0.000000  ...  0.000000   0.000000   0.000000   \n\n   Class10.3  Class11.1  Class11.2  Class11.3  Class11.4  Class11.5  Class11.6  \n0   0.000000   0.000000   0.092886        0.0        0.0        0.0   0.325512  \n1   0.459950   0.000000   0.591328        0.0        0.0        0.0   0.000000  \n2   0.000000   0.000000   0.000000        0.0        0.0        0.0   0.000000  \n3   0.094549   0.189098   0.000000        0.0        0.0        0.0   0.000000  \n4   0.000000   0.000000   0.000000        0.0        0.0        0.0   0.000000  \n\n[5 rows x 38 columns]\n","output_type":"stream"}],"execution_count":9},{"cell_type":"code","source":"new_data = labels_df[(labels_df[\"Class1.1\"] >= 0.8) | (labels_df[\"Class1.2\"] >= 0.8)].reset_index(drop=True)\n\nconditions = [new_data[\"Class1.1\"] >= 0.8, new_data[\"Class1.2\"] >= 0.8]\nchoices = [\"elliptical\", \"spiral\"]\n\nnew_data[\"morphology_label\"] = np.select(conditions, choices, default=\"uncertain\")\nnew_data = new_data[new_data[\"morphology_label\"] != \"uncertain\"].reset_index(drop=True)\n\nprint(\"Dataset shape:\", new_data.shape)\nprint(new_data[\"morphology_label\"].value_counts())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-21T17:56:01.789805Z","iopub.execute_input":"2026-09-21T17:56:01.790625Z","iopub.status.idle":"2026-09-21T17:56:01.829047Z","shell.execute_reply.started":"2026-09-21T17:56:01.790591Z","shell.execute_reply":"2026-09-21T17:56:01.828404Z"}},"outputs":[{"name":"stdout","text":"Dataset shape: (24273, 39)\nmorphology_label\nspiral        16141\nelliptical     8132\nName: count, dtype: int64\n","output_type":"stream"}],"execution_count":10},{"cell_type":"code","source":"image_map = {os.path.splitext(os.path.basename(f))[0]: f for f in image_files}\nnew_data[\"image_id\"] = new_data[\"GalaxyID\"].astype(str)\nnew_data[\"file_path\"] = new_data[\"image_id\"].map(image_map)\nnew_data = new_data.dropna(subset=[\"file_path\"]).reset_index(drop=True)\n\nprint(\"Final dataset shape:\", new_data.shape)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-21T17:56:06.200732Z","iopub.execute_input":"2026-09-21T17:56:06.201163Z","iopub.status.idle":"2026-09-21T17:56:06.321879Z","shell.execute_reply.started":"2026-09-21T17:56:06.201131Z","shell.execute_reply":"2026-09-21T17:56:06.321193Z"}},"outputs":[{"name":"stdout","text":"Final dataset shape: (24273, 41)\n","output_type":"stream"}],"execution_count":11},{"cell_type":"code","source":"# =========================================\n# Step 6: Balance the dataset\n# =========================================\n\nmin_count = new_data[\"morphology_label\"].value_counts().min()\n\nbalanced_data = (\n    new_data\n    .groupby(\"morphology_label\", group_keys=False)\n    .apply(lambda x: x.sample(min_count, random_state=42))\n    .reset_index(drop=True)\n)\n\nprint(\"Balanced dataset:\")\nprint(balanced_data[\"morphology_label\"].value_counts())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-21T17:56:08.982869Z","iopub.execute_input":"2026-09-21T17:56:08.98343Z","iopub.status.idle":"2026-09-21T17:56:09.017184Z","shell.execute_reply.started":"2026-09-21T17:56:08.983383Z","shell.execute_reply":"2026-09-21T17:56:09.016224Z"}},"outputs":[{"name":"stdout","text":"Balanced dataset:\nmorphology_label\nelliptical    8132\nspiral        8132\nName: count, dtype: int64\n","output_type":"stream"},{"name":"stderr","text":"/tmp/ipykernel_58/1703512291.py:10: FutureWarning: DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.\n  .apply(lambda x: x.sample(min_count, random_state=42))\n","output_type":"stream"}],"execution_count":12},{"cell_type":"code","source":"train_df, test_df = train_test_split(\n    balanced_data,\n    test_size=0.2,\n    stratify=balanced_data[\"morphology_label\"],\n    random_state=42\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-21T17:57:32.959885Z","iopub.execute_input":"2026-09-21T17:57:32.96031Z","iopub.status.idle":"2026-09-21T17:57:32.991466Z","shell.execute_reply.started":"2026-09-21T17:57:32.960276Z","shell.execute_reply":"2026-09-21T17:57:32.990819Z"}},"outputs":[],"execution_count":14},{"cell_type":"code","source":"train_transform = transforms.Compose([\n    transforms.Resize((256,256)),\n    transforms.RandomResizedCrop(224),\n    transforms.RandomHorizontalFlip(),\n    transforms.RandomVerticalFlip(),\n    transforms.RandomRotation(180),\n    transforms.ToTensor(),\n    transforms.Normalize([0.485,0.456,0.406],[0.229,0.224,0.225])\n])\n\ntest_transform = transforms.Compose([\n    transforms.Resize((256,256)),\n    transforms.CenterCrop(224),\n    transforms.ToTensor(),\n    transforms.Normalize([0.485,0.456,0.406],[0.229,0.224,0.225])\n])\n\nclass GalaxyZooDataset(Dataset):\n    def __init__(self, df, label_to_idx, transform=None):\n        self.df = df.reset_index(drop=True)\n        self.label_to_idx = label_to_idx\n        self.transform = transform\n    def __len__(self):\n        return len(self.df)\n    def __getitem__(self, idx):\n        image = Image.open(self.df.loc[idx, \"file_path\"]).convert(\"RGB\")\n        label = self.label_to_idx[self.df.loc[idx, \"morphology_label\"]]\n        if self.transform:\n            image = self.transform(image)\n        return image, label\n\nclass_names = sorted(balanced_data[\"morphology_label\"].unique())\nlabel_to_idx = {label: idx for idx, label in enumerate(class_names)}\n\ntrain_dataset = GalaxyZooDataset(train_df, label_to_idx, transform=train_transform)\ntest_dataset = GalaxyZooDataset(test_df, label_to_idx, transform=test_transform)\n\ntrain_loader = DataLoader(train_dataset, batch_size=32, shuffle=True, num_workers=2, pin_memory=True)\ntest_loader = DataLoader(test_dataset, batch_size=32, shuffle=False, num_workers=2, pin_memory=True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-21T17:58:05.233734Z","iopub.execute_input":"2026-09-21T17:58:05.234449Z","iopub.status.idle":"2026-09-21T17:58:05.249525Z","shell.execute_reply.started":"2026-09-21T17:58:05.234388Z","shell.execute_reply":"2026-09-21T17:58:05.248544Z"}},"outputs":[],"execution_count":17},{"cell_type":"code","source":"# =========================================\n# Step 9: ResNet18 model setup\n# =========================================\n\nmodel = models.resnet18(weights=models.ResNet18_Weights.DEFAULT)\n\nmodel.fc = nn.Linear(\n    model.fc.in_features,\n    len(class_names)\n)\n\nmodel = model.to(device)\n\ncriterion = nn.CrossEntropyLoss()\n\noptimizer = torch.optim.AdamW(\n    model.parameters(),\n    lr=1e-4,\n    weight_decay=1e-4\n)\n\n# Correct GradScaler for your Kaggle PyTorch version\nscaler = torch.cuda.amp.GradScaler()\n\nprint(\"ResNet18 model ready.\")\nprint(\"Number of classes:\", len(class_names))\nprint(\"Model device:\", next(model.parameters()).device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-21T17:58:09.791831Z","iopub.execute_input":"2026-09-21T17:58:09.792571Z","iopub.status.idle":"2026-09-21T17:58:10.745303Z","shell.execute_reply.started":"2026-09-21T17:58:09.792528Z","shell.execute_reply":"2026-09-21T17:58:10.744409Z"}},"outputs":[{"name":"stdout","text":"Downloading: \"https://download.pytorch.org/models/resnet18-f37072fd.pth\" to /root/.cache/torch/hub/checkpoints/resnet18-f37072fd.pth\n","output_type":"stream"},{"name":"stderr","text":"100%|██████████| 44.7M/44.7M [00:00<00:00, 154MB/s] \n","output_type":"stream"},{"name":"stdout","text":"ResNet18 model ready.\nNumber of classes: 2\nModel device: cuda:0\n","output_type":"stream"},{"name":"stderr","text":"/tmp/ipykernel_58/1829150339.py:23: FutureWarning: `torch.cuda.amp.GradScaler(args...)` is deprecated. Please use `torch.amp.GradScaler('cuda', args...)` instead.\n  scaler = torch.cuda.amp.GradScaler()\n","output_type":"stream"}],"execution_count":18},{"cell_type":"code","source":"# =========================================\n# Step 10: Train ResNet18\n# =========================================\n\nepochs = 40\n\nfor epoch in range(epochs):\n    model.train()\n    running_loss = 0.0\n    correct = 0\n    total = 0\n\n    for images, labels in train_loader:\n        images = images.to(device, non_blocking=True)\n        labels = labels.to(device, non_blocking=True)\n\n        optimizer.zero_grad()\n\n        with torch.cuda.amp.autocast():\n            outputs = model(images)\n            loss = criterion(outputs, labels)\n\n        scaler.scale(loss).backward()\n        scaler.step(optimizer)\n        scaler.update()\n\n        running_loss += loss.item()\n\n        _, preds = torch.max(outputs, 1)\n        correct += (preds == labels).sum().item()\n        total += labels.size(0)\n\n    train_loss = running_loss / len(train_loader)\n    train_acc = correct / total\n\n    print(f\"Epoch [{epoch+1}/{epochs}] | Loss: {train_loss:.4f} | Train Accuracy: {train_acc:.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-21T17:58:20.394097Z","iopub.execute_input":"2026-09-21T17:58:20.394548Z","iopub.status.idle":"2026-09-21T18:27:52.259788Z","shell.execute_reply.started":"2026-09-21T17:58:20.394516Z","shell.execute_reply":"2026-09-21T18:27:52.258867Z"}},"outputs":[{"name":"stderr","text":"/tmp/ipykernel_58/1675731624.py:19: FutureWarning: `torch.cuda.amp.autocast(args...)` is deprecated. Please use `torch.amp.autocast('cuda', args...)` instead.\n  with torch.cuda.amp.autocast():\n","output_type":"stream"},{"name":"stdout","text":"Epoch [1/40] | Loss: 0.2072 | Train Accuracy: 0.9142\nEpoch [2/40] | Loss: 0.1455 | Train Accuracy: 0.9417\nEpoch [3/40] | Loss: 0.1318 | Train Accuracy: 0.9456\nEpoch [4/40] | Loss: 0.1247 | Train Accuracy: 0.9504\nEpoch [5/40] | Loss: 0.1125 | Train Accuracy: 0.9567\nEpoch [6/40] | Loss: 0.1090 | Train Accuracy: 0.9579\nEpoch [7/40] | Loss: 0.1056 | Train Accuracy: 0.9602\nEpoch [8/40] | Loss: 0.1106 | Train Accuracy: 0.9560\nEpoch [9/40] | Loss: 0.1051 | Train Accuracy: 0.9563\nEpoch [10/40] | Loss: 0.1018 | Train Accuracy: 0.9583\nEpoch [11/40] | Loss: 0.0997 | Train Accuracy: 0.9600\nEpoch [12/40] | Loss: 0.0973 | Train Accuracy: 0.9613\nEpoch [13/40] | Loss: 0.0981 | Train Accuracy: 0.9599\nEpoch [14/40] | Loss: 0.0953 | Train Accuracy: 0.9606\nEpoch [15/40] | Loss: 0.0961 | Train Accuracy: 0.9623\nEpoch [16/40] | Loss: 0.0906 | Train Accuracy: 0.9639\nEpoch [17/40] | Loss: 0.0929 | Train Accuracy: 0.9629\nEpoch [18/40] | Loss: 0.0889 | Train Accuracy: 0.9637\nEpoch [19/40] | Loss: 0.0856 | Train Accuracy: 0.9653\nEpoch [20/40] | Loss: 0.0900 | Train Accuracy: 0.9640\nEpoch [21/40] | Loss: 0.0830 | Train Accuracy: 0.9683\nEpoch [22/40] | Loss: 0.0803 | Train Accuracy: 0.9668\nEpoch [23/40] | Loss: 0.0860 | Train Accuracy: 0.9638\nEpoch [24/40] | Loss: 0.0836 | Train Accuracy: 0.9671\nEpoch [25/40] | Loss: 0.0836 | Train Accuracy: 0.9663\nEpoch [26/40] | Loss: 0.0826 | Train Accuracy: 0.9655\nEpoch [27/40] | Loss: 0.0839 | Train Accuracy: 0.9665\nEpoch [28/40] | Loss: 0.0799 | Train Accuracy: 0.9663\nEpoch [29/40] | Loss: 0.0795 | Train Accuracy: 0.9670\nEpoch [30/40] | Loss: 0.0834 | Train Accuracy: 0.9656\nEpoch [31/40] | Loss: 0.0783 | Train Accuracy: 0.9673\nEpoch [32/40] | Loss: 0.0771 | Train Accuracy: 0.9683\nEpoch [33/40] | Loss: 0.0783 | Train Accuracy: 0.9685\nEpoch [34/40] | Loss: 0.0785 | Train Accuracy: 0.9681\nEpoch [35/40] | Loss: 0.0791 | Train Accuracy: 0.9673\nEpoch [36/40] | Loss: 0.0763 | Train Accuracy: 0.9693\nEpoch [37/40] | Loss: 0.0753 | Train Accuracy: 0.9696\nEpoch [38/40] | Loss: 0.0787 | Train Accuracy: 0.9680\nEpoch [39/40] | Loss: 0.0745 | Train Accuracy: 0.9686\nEpoch [40/40] | Loss: 0.0754 | Train Accuracy: 0.9690\n","output_type":"stream"}],"execution_count":19},{"cell_type":"code","source":"# =========================================\n# Step 11: Evaluate ResNet18\n# =========================================\n\nmodel.eval()\n\nall_preds = []\nall_labels = []\n\nwith torch.no_grad():\n    for images, labels in test_loader:\n        images = images.to(device, non_blocking=True)\n\n        outputs = model(images)\n        preds = torch.argmax(outputs, dim=1).cpu().numpy()\n\n        all_preds.extend(preds)\n        all_labels.extend(labels.numpy())\n\naccuracy = accuracy_score(all_labels, all_preds)\n\nprint(\"ResNet18 Accuracy:\", accuracy)\n\nprint(\"\\nResNet18 Classification Report:\")\nprint(classification_report(\n    all_labels,\n    all_preds,\n    target_names=class_names\n))\n\nprint(\"ResNet18 Confusion Matrix:\")\nprint(confusion_matrix(all_labels, all_preds))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-21T18:27:57.117907Z","iopub.execute_input":"2026-09-21T18:27:57.11823Z","iopub.status.idle":"2026-09-21T18:28:05.849952Z","shell.execute_reply.started":"2026-09-21T18:27:57.118195Z","shell.execute_reply":"2026-09-21T18:28:05.849086Z"}},"outputs":[{"name":"stdout","text":"ResNet18 Accuracy: 0.9849369812480787\n\nResNet18 Classification Report:\n              precision    recall  f1-score   support\n\n  elliptical       0.98      0.99      0.99      1627\n      spiral       0.99      0.98      0.98      1626\n\n    accuracy                           0.98      3253\n   macro avg       0.98      0.98      0.98      3253\nweighted avg       0.98      0.98      0.98      3253\n\nResNet18 Confusion Matrix:\n[[1609   18]\n [  31 1595]]\n","output_type":"stream"}],"execution_count":20},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}