{"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":"gpu","dataSources":[{"sourceId":3175,"databundleVersionId":44352,"sourceType":"competition"}],"dockerImageVersionId":31193,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os, zipfile\n\nbase = \"/kaggle/input/galaxy-zoo-the-galaxy-challenge\"\nwork = \"/kaggle/working\"\n\n# Unzip training images\nwith zipfile.ZipFile(os.path.join(base, \"images_training_rev1.zip\"), \"r\") as z:\n    z.extractall(work)\n\n# Unzip test images\nwith zipfile.ZipFile(os.path.join(base, \"images_test_rev1.zip\"), \"r\") as z:\n    z.extractall(work)\n\n# Unzip labels\nwith zipfile.ZipFile(os.path.join(base, \"training_solutions_rev1.zip\"), \"r\") as z:\n    z.extractall(work)\n\n# Unzip zero benchmark (submission template)\nwith zipfile.ZipFile(os.path.join(base, \"all_zeros_benchmark.zip\"), \"r\") as z:\n    z.extractall(work)\n\nprint(os.listdir(work))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-28T22:23:40.947885Z","iopub.execute_input":"2025-11-28T22:23:40.948206Z","iopub.status.idle":"2025-11-28T22:24:09.556962Z","shell.execute_reply.started":"2025-11-28T22:23:40.948164Z","shell.execute_reply":"2025-11-28T22:24:09.556156Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Full corrected notebook script: Galaxy Zoo - EfficientNet B4 (single model) ----------------\n\n# 0) Install timm if not present (uncomment in Kaggle if needed)\n# !pip install -q timm\n\nimport os\nimport cv2\nimport random\nimport gc\nimport numpy as np\nimport pandas as pd\nfrom tqdm import tqdm\n\nimport torch\nimport torch.nn as nn\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms\nimport timm  # recommended, stable API for many backbones\n\nfrom sklearn.model_selection import train_test_split\nfrom torch.cuda.amp import autocast, GradScaler\n\n# -------------------------------\n# Config\n# -------------------------------\nIMG_SIZE = 256\nBATCH_SIZE = 24\nEPOCHS = 10\nLR = 3e-4\nSEED = 1337\nDEVICE = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n\nTRAIN_DIR = \"/kaggle/working/images_training_rev1\"\nTEST_DIR  = \"/kaggle/working/images_test_rev1\"\nLABELS    = \"/kaggle/working/training_solutions_rev1.csv\"   # Galaxy Zoo ground-truth prob vectors\n\nCHECKPOINT_PATH = \"/kaggle/working/efficientnet_b4_seed1337.pth\"\nPREDS_PATH      = \"/kaggle/working/preds_eff_b4.csv\"\n\n# -------------------------------\n# Utilities\n# -------------------------------\ndef seed_everything(seed):\n    random.seed(seed)\n    np.random.seed(seed)\n    torch.manual_seed(seed)\n    torch.cuda.manual_seed_all(seed)\n\nseed_everything(SEED)\n\n# -------------------------------\n# Dataset\n# -------------------------------\nclass GalaxyDataset(Dataset):\n    def __init__(self, galaxy_ids, img_dir, labels=None, augment=False):\n        \"\"\"\n        galaxy_ids: array-like of ids (no .jpg extension)\n        img_dir: directory containing <id>.jpg files\n        labels: None or numpy array of shape (N, 37)\n        augment: whether to apply training augmentations\n        \"\"\"\n        self.galaxy_ids = list(galaxy_ids)\n        self.img_dir = img_dir\n        self.labels = None if labels is None else np.asarray(labels, dtype=np.float32)\n        self.augment = augment\n\n        self.aug = transforms.Compose([\n            transforms.ToPILImage(),\n            transforms.RandomRotation(360),\n            transforms.RandomHorizontalFlip(),\n            transforms.RandomVerticalFlip(),\n            transforms.ColorJitter(0.1,0.1,0.1,0.1),\n            transforms.Resize((IMG_SIZE, IMG_SIZE)),\n            transforms.ToTensor()\n        ])\n\n        self.basic = transforms.Compose([\n            transforms.ToPILImage(),\n            transforms.Resize((IMG_SIZE, IMG_SIZE)),\n            transforms.ToTensor()\n        ])\n\n    def __len__(self):\n        return len(self.galaxy_ids)\n\n    def __getitem__(self, idx):\n        gid = self.galaxy_ids[idx]\n        img_path = os.path.join(self.img_dir, f\"{gid}.jpg\")\n\n        # read\n        img_bgr = cv2.imread(img_path)\n        if img_bgr is None:\n            # fallback: return zero image if missing (avoid crash) but warn\n            # (You may prefer to raise instead)\n            # print(f\"WARN: missing {img_path}\")\n            img = np.zeros((IMG_SIZE, IMG_SIZE, 3), dtype=np.uint8)\n        else:\n            img = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)\n\n        if self.augment:\n            img_t = self.aug(img)\n        else:\n            img_t = self.basic(img)\n\n        if self.labels is not None:\n            label = torch.from_numpy(self.labels[idx]).float()  # shape (37,)\n            return img_t, label\n        return img_t\n\n# -------------------------------\n# Load labels and split\n# -------------------------------\ndf = pd.read_csv(LABELS)\nall_ids = df[\"GalaxyID\"].values\ntargets = df.drop(\"GalaxyID\", axis=1).values  # shape (N, 37)\n\nX_train_ids, X_val_ids, y_train, y_val = train_test_split(\n    all_ids, targets, test_size=0.1, random_state=SEED, stratify=None\n)\n\ntrain_ds = GalaxyDataset(X_train_ids, TRAIN_DIR, y_train, augment=True)\nval_ds   = GalaxyDataset(X_val_ids,   TRAIN_DIR, y_val,   augment=False)\n\ntrain_loader = DataLoader(train_ds, batch_size=BATCH_SIZE, shuffle=True, num_workers=4, pin_memory=True)\nval_loader   = DataLoader(val_ds,   batch_size=BATCH_SIZE, shuffle=False, num_workers=4, pin_memory=True)\n\n# -------------------------------\n# Model (timm EfficientNet-B4)\n# -------------------------------\nclass GalaxyEfficientNetB4(nn.Module):\n    def __init__(self, pretrained=True, num_outputs=37):\n        super().__init__()\n        # create timm model; when num_classes set, timm will build classifier with correct out dim\n        self.model = timm.create_model(\"efficientnet_b4\", pretrained=pretrained, num_classes=num_outputs)\n\n    def forward(self, x):\n        return self.model(x)\n\nmodel = GalaxyEfficientNetB4(pretrained=True, num_outputs=37).to(DEVICE)\n\n# Sanity check: ensure model has parameters\ntotal_params = sum(p.numel() for p in model.parameters())\ntrainable_params = sum(p.numel() for p in model.parameters() if p.requires_grad)\nprint(\"Model params:\", total_params, \"Trainable:\", trainable_params)\nassert total_params > 0, \"Model has zero parameters - constructor failed.\"\n\n# -------------------------------\n# Optimizer / Scheduler / Loss / AMP\n# -------------------------------\noptimizer = torch.optim.AdamW(model.parameters(), lr=LR, weight_decay=1e-4)\nscheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=EPOCHS)\n# Galaxy labels are soft probability vectors across groups — MSE is OK; you can try BCEWithLogitsLoss after sigmoid if desired\ncriterion = nn.MSELoss()\nscaler = GradScaler()\n\n# small helper to select correct autocast device\nuse_cuda = DEVICE == \"cuda\"\n\n# -------------------------------\n# Validation helper\n# -------------------------------\ndef validate(model, loader, device):\n    model.eval()\n    running_loss = 0.0\n    n = 0\n    with torch.no_grad():\n        for imgs, labels in loader:\n            imgs = imgs.to(device, non_blocking=True)\n            labels = labels.to(device, non_blocking=True)\n            if use_cuda:\n                with torch.amp.autocast(device_type=\"cuda\"):\n                    outputs = model(imgs)\n                    loss = criterion(outputs, labels)\n            else:\n                with torch.amp.autocast(device_type=\"cpu\"):\n                    outputs = model(imgs)\n                    loss = criterion(outputs, labels)\n            running_loss += loss.item() * imgs.size(0)\n            n += imgs.size(0)\n    return running_loss / n\n\n# -------------------------------\n# Training loop\n# -------------------------------\nfor epoch in range(1, EPOCHS+1):\n    model.train()\n    train_loss = 0.0\n    n = 0\n    pbar = tqdm(train_loader, desc=f\"Epoch {epoch}/{EPOCHS} train\")\n    for imgs, labels in pbar:\n        imgs = imgs.to(DEVICE, non_blocking=True)\n        labels = labels.to(DEVICE, non_blocking=True)\n\n        optimizer.zero_grad()\n        if use_cuda:\n            with torch.amp.autocast(device_type=\"cuda\"):\n                outputs = model(imgs)\n                loss = criterion(outputs, labels)\n        else:\n            with torch.amp.autocast(device_type=\"cpu\"):\n                outputs = model(imgs)\n                loss = criterion(outputs, labels)\n\n        scaler.scale(loss).backward()\n        scaler.step(optimizer)\n        scaler.update()\n\n        train_loss += loss.item() * imgs.size(0)\n        n += imgs.size(0)\n        pbar.set_postfix(train_loss=train_loss / n)\n\n    scheduler.step()\n\n    val_loss = validate(model, val_loader, DEVICE)\n    print(f\"Epoch {epoch} Train loss: {train_loss / n:.4f}  |  Val loss: {val_loss:.4f}\")\n\n    # optionally save best by val loss\n    torch.save(model.state_dict(), CHECKPOINT_PATH)\n\n# final save\ntorch.save(model.state_dict(), CHECKPOINT_PATH)\nprint(f\"Saved checkpoint to {CHECKPOINT_PATH}\")\n\n# -------------------------------\n# TTA prediction on test set\n# -------------------------------\ndef tta_predict(model, loader, device):\n    model.eval()\n    preds = []\n    with torch.no_grad():\n        for batch in tqdm(loader, desc=\"TTA predict\"):\n            # loader yields images (no labels)\n            if isinstance(batch, (list, tuple)):\n                imgs = batch[0]\n            else:\n                imgs = batch\n            imgs = imgs.to(device)\n            batch_preds = []\n\n            # base\n            if use_cuda:\n                with torch.amp.autocast(device_type=\"cuda\"):\n                    out = model(imgs)\n            else:\n                with torch.amp.autocast(device_type=\"cpu\"):\n                    out = model(imgs)\n            batch_preds.append(out)\n\n            # horizontal flip\n            imgs_h = torch.flip(imgs, dims=[3])\n            if use_cuda:\n                with torch.amp.autocast(device_type=\"cuda\"):\n                    out_h = model(imgs_h)\n            else:\n                with torch.amp.autocast(device_type=\"cpu\"):\n                    out_h = model(imgs_h)\n            batch_preds.append(out_h)\n\n            # vertical flip\n            imgs_v = torch.flip(imgs, dims=[2])\n            if use_cuda:\n                with torch.amp.autocast(device_type=\"cuda\"):\n                    out_v = model(imgs_v)\n            else:\n                with torch.amp.autocast(device_type=\"cpu\"):\n                    out_v = model(imgs_v)\n            batch_preds.append(out_v)\n\n            # average predictions (logits/regression outputs)\n            batch_mean = torch.stack(batch_preds, dim=0).mean(dim=0)\n            preds.append(batch_mean.cpu().numpy())\n\n    return np.vstack(preds)\n\n# Build test dataset & loader\nsub_template = pd.read_csv(\"/kaggle/working/all_zeros_benchmark.csv\")  # your provided template\ntest_ids = sub_template[\"GalaxyID\"].values\ntest_ds = GalaxyDataset(test_ids, TEST_DIR, labels=None, augment=False)\ntest_loader = DataLoader(test_ds, batch_size=BATCH_SIZE, shuffle=False, num_workers=4, pin_memory=True)\n\n# run TTA predict\npreds = tta_predict(model, test_loader, DEVICE)\n\n# Postprocess: clamp and normalize per groups as you had\npreds = np.clip(preds, 0.0, 1.0)\ngroups = [\n    (0,3),(3,5),(5,8),(8,11),(11,15),\n    (15,18),(18,25),(25,28),(28,31),(31,37)\n]\nfor start, end in groups:\n    s = preds[:, start:end].sum(axis=1, keepdims=True)\n    s[s == 0] = 1.0\n    preds[:, start:end] /= s\n\n# Save submission\nout = sub_template.copy()\nout.iloc[:, 1:] = preds  # keep GalaxyID column\nout.to_csv(PREDS_PATH, index=False)\nout.to_csv(\"submission_single_effb4.csv\", index=False)\nprint(f\"Saved preds to {PREDS_PATH} and submission_single_effb4.csv\")\n\n# Clean up\ngc.collect()\ntorch.cuda.empty_cache()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-28T22:33:58.626226Z","iopub.execute_input":"2025-11-28T22:33:58.626577Z","execution_failed":"2025-11-29T05:49:24.311Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}