{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceType":"competition","sourceId":3175,"databundleVersionId":44352}],"dockerImageVersionId":31329,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport os\nfrom PIL import Image\n\n# Show all files in the dataset\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename)) ","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"editable":false,"execution":{"iopub.status.busy":"2026-06-04T18:15:19.235711Z","iopub.execute_input":"2026-06-04T18:15:19.236215Z","iopub.status.idle":"2026-06-04T18:15:19.245032Z","shell.execute_reply.started":"2026-06-04T18:15:19.236155Z","shell.execute_reply":"2026-06-04T18:15:19.244327Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import zipfile\n\n# Extract the CSV file\nwith zipfile.ZipFile('/kaggle/input/competitions/galaxy-zoo-the-galaxy-challenge/training_solutions_rev1.zip', 'r') as z:\n    z.extractall('/kaggle/working/')\n\n# Load the CSV\ndf = pd.read_csv('/kaggle/working/training_solutions_rev1.csv')\n\n# Show first 5 rows\nprint(df.shape)\ndf.head()","metadata":{"trusted":true,"editable":false,"execution":{"iopub.status.busy":"2026-06-04T18:15:19.246665Z","iopub.execute_input":"2026-06-04T18:15:19.246886Z","iopub.status.idle":"2026-06-04T18:15:19.732041Z","shell.execute_reply.started":"2026-06-04T18:15:19.246865Z","shell.execute_reply":"2026-06-04T18:15:19.731319Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import zipfile\nimport os\n\n# Extract training images\nwith zipfile.ZipFile('/kaggle/input/competitions/galaxy-zoo-the-galaxy-challenge/images_training_rev1.zip', 'r') as z:\n    z.extractall('/kaggle/working/images_training/')\n\n# Check result\npath = '/kaggle/working/images_training/images_training_rev1/'\nfiles = os.listdir(path)\nprint(f\"Number of images: {len(files)}\")","metadata":{"trusted":true,"editable":false,"execution":{"iopub.status.busy":"2026-06-04T18:15:19.733160Z","iopub.execute_input":"2026-06-04T18:15:19.733473Z","iopub.status.idle":"2026-06-04T18:15:34.330318Z","shell.execute_reply.started":"2026-06-04T18:15:19.733437Z","shell.execute_reply":"2026-06-04T18:15:34.329463Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\nfrom PIL import Image\nimport os\n\n# Path to images\nimage_dir = '/kaggle/working/images_training/images_training_rev1/'\n\n# Get first 9 images\nimage_files = os.listdir(image_dir)[:9]\n\n# Show images in a grid\nfig, axes = plt.subplots(3, 3, figsize=(10, 10))\n\nfor i, ax in enumerate(axes.flat):\n    img = Image.open(os.path.join(image_dir, image_files[i]))\n    ax.imshow(img)\n    ax.set_title(image_files[i])\n    ax.axis('off')\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"editable":false,"execution":{"iopub.status.busy":"2026-06-04T18:15:34.332537Z","iopub.execute_input":"2026-06-04T18:15:34.332836Z","iopub.status.idle":"2026-06-04T18:15:35.451531Z","shell.execute_reply.started":"2026-06-04T18:15:34.332811Z","shell.execute_reply":"2026-06-04T18:15:35.450594Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\n\n# Load CSV\ndf = pd.read_csv('/kaggle/working/training_solutions_rev1.csv')\n\n# Assign main label to each galaxy\ndef get_label(row):\n    if row['Class1.1'] > 0.8:\n        return 'Round Smooth'\n    elif row['Class1.1'] > 0.5:\n        return 'In-between Smooth'\n    elif row['Class2.1'] > 0.5:\n        return 'Edge-on'\n    elif row['Class4.1'] > 0.5:\n        return 'Barred Spiral'\n    else:\n        return 'Unbarred Spiral'\n\ndf['label'] = df.apply(get_label, axis=1)\nprint(df['label'].value_counts())","metadata":{"trusted":true,"editable":false,"execution":{"iopub.status.busy":"2026-06-04T18:15:35.452697Z","iopub.execute_input":"2026-06-04T18:15:35.453085Z","iopub.status.idle":"2026-06-04T18:15:36.232115Z","shell.execute_reply.started":"2026-06-04T18:15:35.453023Z","shell.execute_reply":"2026-06-04T18:15:36.231129Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\nfrom PIL import Image\nimport os\n\n# Path to images\nimage_dir = '/kaggle/working/images_training/images_training_rev1/'\n\n# Get 9 random galaxies from dataframe\nsample = df.sample(9, random_state=42)\n\n# Show images with labels\nfig, axes = plt.subplots(3, 3, figsize=(10, 10))\n\nfor i, (ax, row) in enumerate(zip(axes.flat, sample.itertuples())):\n    img_path = os.path.join(image_dir, f'{row.GalaxyID}.jpg')\n    img = Image.open(img_path)\n    ax.imshow(img)\n    ax.set_title(f'{row.GalaxyID} — {row.label}')\n    ax.axis('off')\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"editable":false,"execution":{"iopub.status.busy":"2026-06-04T18:15:36.233474Z","iopub.execute_input":"2026-06-04T18:15:36.233888Z","iopub.status.idle":"2026-06-04T18:15:37.196084Z","shell.execute_reply.started":"2026-06-04T18:15:36.233850Z","shell.execute_reply":"2026-06-04T18:15:37.195234Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.model_selection import train_test_split\n\n# Split data: 80% training, 20% validation\ntrain_df, val_df = train_test_split(df, test_size=0.2, random_state=42, stratify=df['label'])\n\nprint(f\"Training samples: {len(train_df)}\")\nprint(f\"Validation samples: {len(val_df)}\")\nprint(\"\\nTraining distribution:\")\nprint(train_df['label'].value_counts())","metadata":{"trusted":true,"editable":false,"execution":{"iopub.status.busy":"2026-06-04T18:15:37.197497Z","iopub.execute_input":"2026-06-04T18:15:37.198304Z","iopub.status.idle":"2026-06-04T18:15:38.264833Z","shell.execute_reply.started":"2026-06-04T18:15:37.198255Z","shell.execute_reply":"2026-06-04T18:15:38.263750Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nfrom torch.utils.data import Dataset\nfrom torchvision import transforms\nfrom PIL import Image\nimport os\n\n# Label to number mapping\nlabel2idx = {\n    'Round Smooth': 0,\n    'In-between Smooth': 1,\n    'Edge-on': 2,\n    'Barred Spiral': 3,\n    'Unbarred Spiral': 4\n}\n\n# Define transformations with Data Augmentation for training\ntrain_transform = transforms.Compose([\n    transforms.Resize((64, 64)),\n    transforms.RandomHorizontalFlip(),\n    transforms.RandomVerticalFlip(),\n    transforms.RandomRotation(360),\n    transforms.ToTensor(),\n])\n\n# No augmentation for validation\nval_transform = transforms.Compose([\n    transforms.Resize((64, 64)),\n    transforms.ToTensor(),\n])\n\nprint(\"Transforms ready!\")\nprint(f\"Classes: {label2idx}\")","metadata":{"trusted":true,"editable":false,"execution":{"iopub.status.busy":"2026-06-04T18:15:38.266758Z","iopub.execute_input":"2026-06-04T18:15:38.267375Z","iopub.status.idle":"2026-06-04T18:15:51.497458Z","shell.execute_reply.started":"2026-06-04T18:15:38.267330Z","shell.execute_reply":"2026-06-04T18:15:51.496683Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class GalaxyDataset(Dataset):\n    def __init__(self, dataframe, image_dir, transform=None):\n        self.dataframe = dataframe\n        self.image_dir = 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        # Load image\n        img_path = os.path.join(self.image_dir, f'{int(row.GalaxyID)}.jpg')\n        img = Image.open(img_path).convert('RGB')\n        \n        # Apply transforms\n        if self.transform:\n            img = self.transform(img)\n        \n        # Get label\n        label = label2idx[row.label]\n        \n        return img, label\n\n# Create datasets\nimage_dir = '/kaggle/working/images_training/images_training_rev1/'\n\ntrain_dataset = GalaxyDataset(train_df, image_dir, transform=train_transform)\nval_dataset = GalaxyDataset(val_df, image_dir, transform=val_transform)\n\nprint(f\"Train dataset: {len(train_dataset)} images\")\nprint(f\"Val dataset: {len(val_dataset)} images\")","metadata":{"trusted":true,"editable":false,"execution":{"iopub.status.busy":"2026-06-04T18:15:51.498423Z","iopub.execute_input":"2026-06-04T18:15:51.499299Z","iopub.status.idle":"2026-06-04T18:15:51.506972Z","shell.execute_reply.started":"2026-06-04T18:15:51.499271Z","shell.execute_reply":"2026-06-04T18:15:51.506174Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from torch.utils.data import DataLoader\n\n# Create dataloaders\ntrain_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)\nval_loader = DataLoader(val_dataset, batch_size=32, shuffle=False)\n\nprint(f\"Train batches: {len(train_loader)}\")\nprint(f\"Val batches: {len(val_loader)}\")","metadata":{"trusted":true,"editable":false,"execution":{"iopub.status.busy":"2026-06-04T18:15:51.509723Z","iopub.execute_input":"2026-06-04T18:15:51.509935Z","iopub.status.idle":"2026-06-04T18:15:51.527044Z","shell.execute_reply.started":"2026-06-04T18:15:51.509914Z","shell.execute_reply":"2026-06-04T18:15:51.526428Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\n\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\nprint(f\"Device: {device}\")","metadata":{"trusted":true,"editable":false,"execution":{"iopub.status.busy":"2026-06-04T18:15:51.528094Z","iopub.execute_input":"2026-06-04T18:15:51.528428Z","iopub.status.idle":"2026-06-04T18:15:51.791255Z","shell.execute_reply.started":"2026-06-04T18:15:51.528390Z","shell.execute_reply":"2026-06-04T18:15:51.790224Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# New transforms for EfficientNet (224x224 + normalization)\ntrain_transform_eff = transforms.Compose([\n    transforms.Resize((224, 224)),\n    transforms.RandomHorizontalFlip(),\n    transforms.RandomVerticalFlip(),\n    transforms.RandomRotation(360),\n    transforms.ToTensor(),\n    transforms.Normalize([0.485, 0.456, 0.406], \n                         [0.229, 0.224, 0.225])\n])\n\nval_transform_eff = transforms.Compose([\n    transforms.Resize((224, 224)),\n    transforms.ToTensor(),\n    transforms.Normalize([0.485, 0.456, 0.406], \n                         [0.229, 0.224, 0.225])\n])\n\n# New datasets for EfficientNet\nimage_dir = '/kaggle/working/images_training/images_training_rev1/'\n\ntrain_dataset_eff = GalaxyDataset(train_df, image_dir, transform=train_transform_eff)\nval_dataset_eff = GalaxyDataset(val_df, image_dir, transform=val_transform_eff)\n\ntrain_loader_eff = DataLoader(train_dataset_eff, batch_size=64, shuffle=True)\nval_loader_eff = DataLoader(val_dataset_eff, batch_size=64, shuffle=False)\n\nprint(\"EfficientNet dataloaders ready!\")","metadata":{"trusted":true,"editable":false,"execution":{"iopub.status.busy":"2026-06-04T18:15:51.792586Z","iopub.execute_input":"2026-06-04T18:15:51.792991Z","iopub.status.idle":"2026-06-04T18:15:51.810310Z","shell.execute_reply.started":"2026-06-04T18:15:51.792962Z","shell.execute_reply":"2026-06-04T18:15:51.809454Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"target_cols = [c for c in df.columns if c.startswith('Class')]\n\nclass GalaxyDatasetRegression(Dataset):\n    def __init__(self, dataframe, image_dir, transform=None):\n        self.dataframe = dataframe.reset_index(drop=True)\n        self.image_dir = 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        img_path = os.path.join(self.image_dir, f'{int(row.GalaxyID)}.jpg')\n        img = Image.open(img_path).convert('RGB')\n        if self.transform:\n            img = self.transform(img)\n        label = torch.tensor(row[target_cols].values.astype(np.float32))\n        return img, label\n\ntrain_dataset_reg = GalaxyDatasetRegression(train_df, image_dir, transform=train_transform_eff)\nval_dataset_reg = GalaxyDatasetRegression(val_df, image_dir, transform=val_transform_eff)\n\ntrain_loader_reg = DataLoader(train_dataset_reg, batch_size=64, shuffle=True)\nval_loader_reg = DataLoader(val_dataset_reg, batch_size=64, shuffle=False)\n\nprint(f\"Train: {len(train_dataset_reg)} | Val: {len(val_dataset_reg)}\")","metadata":{"trusted":true,"editable":false,"execution":{"iopub.status.busy":"2026-06-04T18:15:51.811527Z","iopub.execute_input":"2026-06-04T18:15:51.812313Z","iopub.status.idle":"2026-06-04T18:15:51.839573Z","shell.execute_reply.started":"2026-06-04T18:15:51.812287Z","shell.execute_reply":"2026-06-04T18:15:51.838802Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from torchvision import models","metadata":{"trusted":true,"editable":false,"execution":{"iopub.status.busy":"2026-06-04T18:15:51.840520Z","iopub.execute_input":"2026-06-04T18:15:51.840830Z","iopub.status.idle":"2026-06-04T18:15:51.844931Z","shell.execute_reply.started":"2026-06-04T18:15:51.840795Z","shell.execute_reply":"2026-06-04T18:15:51.844256Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"efficientnet_reg = models.efficientnet_b0(weights='IMAGENET1K_V1')\n\nfor param in efficientnet_reg.parameters():\n    param.requires_grad = True\n\nefficientnet_reg.classifier = nn.Sequential(\n    nn.Dropout(0.2),\n    nn.Linear(1280, 37),\n    nn.Sigmoid()\n)\n\nefficientnet_reg = efficientnet_reg.to(device)\nprint(f\"Trainable parameters: {sum(p.numel() for p in efficientnet_reg.parameters() if p.requires_grad):,}\")","metadata":{"trusted":true,"editable":false,"execution":{"iopub.status.busy":"2026-06-04T18:15:51.845848Z","iopub.execute_input":"2026-06-04T18:15:51.846676Z","iopub.status.idle":"2026-06-04T18:15:52.764242Z","shell.execute_reply.started":"2026-06-04T18:15:51.846627Z","shell.execute_reply":"2026-06-04T18:15:52.763468Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"criterion_reg = nn.MSELoss()\noptimizer_reg = torch.optim.Adam(efficientnet_reg.parameters(), lr=0.00001)\nepochs = 30\n\ntrain_losses_reg, val_losses_reg = [], []\nbest_val_loss = float('inf')\n\nfor epoch in range(epochs):\n    efficientnet_reg.train()\n    train_loss = 0.0\n    for images, labels in train_loader_reg:\n        images, labels = images.to(device), labels.to(device)\n        optimizer_reg.zero_grad()\n        outputs = efficientnet_reg(images)\n        loss = criterion_reg(outputs, labels)\n        loss.backward()\n        optimizer_reg.step()\n        train_loss += loss.item()\n    train_losses_reg.append(train_loss / len(train_loader_reg))\n\n    efficientnet_reg.eval()\n    val_loss = 0.0\n    with torch.no_grad():\n        for images, labels in val_loader_reg:\n            images, labels = images.to(device), labels.to(device)\n            outputs = efficientnet_reg(images)\n            loss = criterion_reg(outputs, labels)\n            val_loss += loss.item()\n    val_losses_reg.append(val_loss / len(val_loader_reg))\n\n    if val_losses_reg[-1] < best_val_loss:\n        best_val_loss = val_losses_reg[-1]\n        torch.save(efficientnet_reg.state_dict(), '/kaggle/working/efficientnet_reg_best.pth')\n\n    print(f\"Epoch {epoch+1}/{epochs} - Train Loss: {train_losses_reg[-1]:.4f} - Val Loss: {val_losses_reg[-1]:.4f}\")\n\nprint(f\"\\nBest Val Loss: {best_val_loss:.4f}\")","metadata":{"trusted":true,"editable":false,"execution":{"iopub.status.busy":"2026-06-04T18:15:52.765348Z","iopub.execute_input":"2026-06-04T18:15:52.765722Z","iopub.status.idle":"2026-06-04T22:35:44.618604Z","shell.execute_reply.started":"2026-06-04T18:15:52.765695Z","shell.execute_reply":"2026-06-04T22:35:44.617887Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"efficientnet_reg.load_state_dict(torch.load('/kaggle/working/efficientnet_reg_best.pth'))\nefficientnet_reg.eval()\nprint(\"Model loaded!\")","metadata":{"trusted":true,"editable":false,"execution":{"iopub.status.busy":"2026-06-04T22:35:44.619704Z","iopub.execute_input":"2026-06-04T22:35:44.620115Z","iopub.status.idle":"2026-06-04T22:35:44.717109Z","shell.execute_reply.started":"2026-06-04T22:35:44.620047Z","shell.execute_reply":"2026-06-04T22:35:44.716289Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plt.figure(figsize=(8, 4))\nplt.plot(train_losses_reg, label='Train Loss')\nplt.plot(val_losses_reg, label='Val Loss')\nplt.xlabel('Epoch')\nplt.ylabel('MSE Loss')\nplt.title('EfficientNet Regression - 37 outputs')\nplt.legend()\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"editable":false,"execution":{"iopub.status.busy":"2026-06-04T22:35:44.718158Z","iopub.execute_input":"2026-06-04T22:35:44.718484Z","iopub.status.idle":"2026-06-04T22:35:44.890688Z","shell.execute_reply.started":"2026-06-04T22:35:44.718447Z","shell.execute_reply":"2026-06-04T22:35:44.889837Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import math\nval_rmse = math.sqrt(best_val_loss)\nprint(f\"Best Val Loss (MSE): {best_val_loss:.4f}\")\nprint(f\"Val RMSE: {val_rmse:.4f}\")","metadata":{"trusted":true,"editable":false,"execution":{"iopub.status.busy":"2026-06-04T22:35:44.891810Z","iopub.execute_input":"2026-06-04T22:35:44.892153Z","iopub.status.idle":"2026-06-04T22:35:44.896601Z","shell.execute_reply.started":"2026-06-04T22:35:44.892115Z","shell.execute_reply":"2026-06-04T22:35:44.895916Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import math\n\n# Ladda bästa modellen\nefficientnet_reg.load_state_dict(torch.load('/kaggle/working/efficientnet_reg_best.pth'))\nefficientnet_reg.eval()\n\n# Ta ett batch från val_loader\nimages, labels = next(iter(val_loader_reg))\nimages, labels = images.to(device), labels.to(device)\n\nwith torch.no_grad():\n    predictions = efficientnet_reg(images)\n\n# Visa RMSE för detta batch\nbatch_rmse = math.sqrt(((predictions - labels) ** 2).mean().item())\nprint(f\"Batch RMSE: {batch_rmse:.4f}\")","metadata":{"trusted":true,"editable":false,"execution":{"iopub.status.busy":"2026-06-04T22:35:44.897536Z","iopub.execute_input":"2026-06-04T22:35:44.897804Z","iopub.status.idle":"2026-06-04T22:35:45.374529Z","shell.execute_reply.started":"2026-06-04T22:35:44.897782Z","shell.execute_reply":"2026-06-04T22:35:45.373787Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def classify_galaxy(pred, t1=0.45, t2=0.70):\n    if pred[0] > t2:\n        return 'Round Smooth'\n    elif pred[0] > t1:\n        return 'In-between Smooth'\n    elif pred[3] > t1:\n        return 'Edge-on'\n    elif pred[7] > t1:\n        return 'Barred Spiral'\n    else:\n        return 'Unbarred Spiral'","metadata":{"trusted":true,"editable":false,"execution":{"iopub.status.busy":"2026-06-04T22:35:45.375351Z","iopub.execute_input":"2026-06-04T22:35:45.375711Z","iopub.status.idle":"2026-06-04T22:35:45.379949Z","shell.execute_reply.started":"2026-06-04T22:35:45.375648Z","shell.execute_reply":"2026-06-04T22:35:45.379345Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.metrics import f1_score\n\nall_preds_raw = []\nall_labels_raw = []\n\nefficientnet_reg.eval()\nwith torch.no_grad():\n    for images, labels in val_loader_reg:\n        images, labels = images.to(device), labels.to(device)\n        predictions = efficientnet_reg(images)\n        all_preds_raw.append(predictions.cpu().numpy())\n        all_labels_raw.append(labels.cpu().numpy())\n\nall_preds_raw = np.concatenate(all_preds_raw)\nall_labels_raw = np.concatenate(all_labels_raw)\nprint(f\"Collected {len(all_preds_raw)} predictions\")","metadata":{"trusted":true,"editable":false,"execution":{"iopub.status.busy":"2026-06-04T22:35:45.380949Z","iopub.execute_input":"2026-06-04T22:35:45.381258Z","iopub.status.idle":"2026-06-04T22:36:49.947602Z","shell.execute_reply.started":"2026-06-04T22:35:45.381220Z","shell.execute_reply":"2026-06-04T22:36:49.946755Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"best_f1 = 0\nbest_t1 = 0.45\nbest_t2 = 0.70\n\nfor t1 in np.arange(0.30, 0.80, 0.05):\n    for t2 in np.arange(t1 + 0.10, 0.95, 0.05):\n        pred_classes = [classify_galaxy(p, t1, t2) for p in all_preds_raw]\n        true_classes = [classify_galaxy(l, t1, t2) for l in all_labels_raw]\n        f1 = f1_score(true_classes, pred_classes, average='macro')\n        if f1 > best_f1:\n            best_f1 = f1\n            best_t1 = t1\n            best_t2 = t2\n\nprint(f\"Best macro F1: {best_f1:.3f}\")\nprint(f\"Best thresholds: t1={best_t1:.2f}, t2={best_t2:.2f}\")","metadata":{"trusted":true,"editable":false,"execution":{"iopub.status.busy":"2026-06-04T22:36:49.948605Z","iopub.execute_input":"2026-06-04T22:36:49.948913Z","iopub.status.idle":"2026-06-04T22:36:55.323105Z","shell.execute_reply.started":"2026-06-04T22:36:49.948888Z","shell.execute_reply":"2026-06-04T22:36:55.322402Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.metrics import classification_report, accuracy_score\n\npred_classes = [classify_galaxy(p, best_t1, best_t2) for p in all_preds_raw]\ntrue_classes = [classify_galaxy(l, best_t1, best_t2) for l in all_labels_raw]\n\nprint(f\"Accuracy: {accuracy_score(true_classes, pred_classes):.3f}\")\nprint(classification_report(true_classes, pred_classes))","metadata":{"trusted":true,"editable":false,"execution":{"iopub.status.busy":"2026-06-04T22:36:55.324265Z","iopub.execute_input":"2026-06-04T22:36:55.324727Z","iopub.status.idle":"2026-06-04T22:36:55.479312Z","shell.execute_reply.started":"2026-06-04T22:36:55.324650Z","shell.execute_reply":"2026-06-04T22:36:55.478798Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"images, labels = next(iter(val_loader_reg))\nimages, labels = images.to(device), labels.to(device)\n\nwith torch.no_grad():\n    predictions = efficientnet_reg(images)\n\npred_classes = [classify_galaxy(p.cpu().numpy(), best_t1, best_t2) for p in predictions]\ntrue_classes = [classify_galaxy(l.cpu().numpy(), best_t1, best_t2) for l in labels]\n\nfig, axes = plt.subplots(3, 3, figsize=(8, 8))\nfor i, ax in enumerate(axes.flat):\n    img = images[i].cpu().permute(1, 2, 0).numpy()\n    img = (img - img.min()) / (img.max() - img.min())\n    ax.imshow(img)\n    color = 'green' if pred_classes[i] == true_classes[i] else 'red'\n    ax.set_title(f'Pred: {pred_classes[i]}\\nTrue: {true_classes[i]}', color=color, fontsize=7)\n    ax.axis('off')\n\nplt.tight_layout()\nplt.savefig('galaxy_predictions.png', dpi=150, bbox_inches='tight')\nplt.show()","metadata":{"trusted":true,"editable":false,"execution":{"iopub.status.busy":"2026-06-04T22:36:55.480392Z","iopub.execute_input":"2026-06-04T22:36:55.480796Z","iopub.status.idle":"2026-06-04T22:36:57.343583Z","shell.execute_reply.started":"2026-06-04T22:36:55.480769Z","shell.execute_reply":"2026-06-04T22:36:57.342569Z"}},"outputs":[],"execution_count":null}]}