{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":101849,"databundleVersionId":13093295,"sourceType":"competition"}],"dockerImageVersionId":31089,"isInternetEnabled":false,"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,"execution":{"iopub.status.busy":"2025-08-06T06:53:58.889003Z","iopub.execute_input":"2025-08-06T06:53:58.889266Z","iopub.status.idle":"2025-08-06T06:55:16.047411Z","shell.execute_reply.started":"2025-08-06T06:53:58.889241Z","shell.execute_reply":"2025-08-06T06:55:16.046672Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 📦 Setup\nimport os\nimport numpy as np\nimport pandas as pd\nimport torch\nimport torch.nn as nn\nfrom torch.utils.data import Dataset, DataLoader\nfrom sklearn.model_selection import train_test_split\nimport pyarrow.parquet as pq\n\n# 🧠 Check GPU\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nprint(\"Using device:\", device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-06T06:55:16.048696Z","iopub.execute_input":"2025-08-06T06:55:16.049032Z","iopub.status.idle":"2025-08-06T06:55:23.096113Z","shell.execute_reply.started":"2025-08-06T06:55:16.049014Z","shell.execute_reply":"2025-08-06T06:55:23.095406Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Load ADC info\nadc_info = pd.read_csv(\"/kaggle/input/ariel-data-challenge-2025/adc_info.csv\").iloc[0]\nadc_gain = adc_info['FGS1_adc_gain']\nadc_offset = adc_info['FGS1_adc_offset']\n\n# Custom Dataset Class\nclass FGS1Dataset(Dataset):\n    def __init__(self, root_dir, planet_ids, targets_df):\n        self.root_dir = root_dir\n        self.planet_ids = planet_ids\n        self.targets_df = targets_df\n\n    def __len__(self):\n        return len(self.planet_ids)\n\n    def __getitem__(self, idx):\n        planet_id = self.planet_ids[idx]\n        signal_path = f\"{self.root_dir}/{planet_id}/FGS1_signal_0.parquet\"\n        signal = pq.read_table(signal_path).to_pandas().values.astype('float64')\n        signal = signal * adc_gain + adc_offset\n        signal = signal.reshape(-1, 32, 32)  # (135000, 32, 32)\n        signal = signal[:1000]  # Optional: truncate for speed\n        signal = torch.tensor(signal).float()\n        signal = signal.unsqueeze(1)  # Add channel dim -> (T, 1, 32, 32)\n\n        # Target\n        target_row = self.targets_df[self.targets_df.planet_id == planet_id]\n        y = torch.tensor(target_row.drop(columns=\"planet_id\").values[0]).float()  # 283 values\n\n\n        return signal, y","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-06T06:55:23.09677Z","iopub.execute_input":"2025-08-06T06:55:23.097081Z","iopub.status.idle":"2025-08-06T06:55:23.128153Z","shell.execute_reply.started":"2025-08-06T06:55:23.097065Z","shell.execute_reply":"2025-08-06T06:55:23.127427Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class CNNRegressor(nn.Module):\n    def __init__(self, out_dim=283):\n        super().__init__()\n        self.cnn = nn.Sequential(\n            nn.Conv2d(1, 16, 3, padding=1), nn.ReLU(), nn.MaxPool2d(2),\n            nn.Conv2d(16, 32, 3, padding=1), nn.ReLU(), nn.MaxPool2d(2)\n        )\n        self.fc = nn.Sequential(\n            nn.Flatten(),\n            nn.Linear(32 * 8 * 8, 512), nn.ReLU(),\n            nn.Linear(512, out_dim * 2)  # mean + log_std\n        )\n\n    def forward(self, x):  # x: (batch, T, 1, 32, 32)\n        B, T, C, H, W = x.shape\n        x = x.view(B * T, C, H, W)\n        feats = self.cnn(x)\n        feats = feats.view(B, T, -1).mean(dim=1)  # Mean over time\n        out = self.fc(feats)\n        mean, log_std = out.chunk(2, dim=1)\n        return mean, log_std","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-06T06:55:23.128893Z","iopub.execute_input":"2025-08-06T06:55:23.129146Z","iopub.status.idle":"2025-08-06T06:55:23.134631Z","shell.execute_reply.started":"2025-08-06T06:55:23.129121Z","shell.execute_reply":"2025-08-06T06:55:23.133912Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Load targets\ntargets = pd.read_csv(\"/kaggle/input/ariel-data-challenge-2025/train.csv\")\nplanet_ids = targets['planet_id'].tolist()\ntrain_ids, val_ids = train_test_split(planet_ids, test_size=0.1, random_state=42)\n\ntrain_ds = FGS1Dataset(\"/kaggle/input/ariel-data-challenge-2025/train\", train_ids, targets)\nval_ds = FGS1Dataset(\"/kaggle/input/ariel-data-challenge-2025/train\", val_ids, targets)\n\ntrain_loader = DataLoader(train_ds, batch_size=2, shuffle=True)\nval_loader = DataLoader(val_ds, batch_size=2)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-06T06:55:23.135357Z","iopub.execute_input":"2025-08-06T06:55:23.135579Z","iopub.status.idle":"2025-08-06T06:55:23.328884Z","shell.execute_reply.started":"2025-08-06T06:55:23.135554Z","shell.execute_reply":"2025-08-06T06:55:23.328069Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# assert torch.isfinite(X).all(), \"X contains non-finite values!\"\n# assert torch.isfinite(y).all(), \"y contains non-finite values!\"","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-06T06:55:23.330782Z","iopub.execute_input":"2025-08-06T06:55:23.331024Z","iopub.status.idle":"2025-08-06T06:55:23.334243Z","shell.execute_reply.started":"2025-08-06T06:55:23.331006Z","shell.execute_reply":"2025-08-06T06:55:23.33363Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from tqdm.notebook import tqdm\nimport torch\n\nmodel = CNNRegressor(out_dim=283).to(device)\noptimizer = torch.optim.Adam(model.parameters(), lr=1e-4)\n\ndef gll_loss(y_true, y_pred_mean, y_pred_log_std):\n    log_std = torch.clamp(y_pred_log_std, min=-5, max=5)\n    std = torch.exp(log_std)\n    loss = log_std + ((y_true - y_pred_mean) ** 2) / (2 * std ** 2)\n    return loss.mean()\n\nEPOCHS = 20\nbest_val_loss = float('inf')\npatience, wait = 5, 0\n\nfor epoch in range(EPOCHS):\n    model.train()\n    total_train_loss = 0.0\n    pbar = tqdm(train_loader, desc=f\"Epoch {epoch+1} [Train]\", leave=False)\n\n    for X, y in pbar:\n        X, y = X.to(device), y.to(device)\n        if not torch.isfinite(X).all() or not torch.isfinite(y).all():\n            continue\n\n        pred_mean, pred_log_std = model(X)\n        loss = gll_loss(y, pred_mean, pred_log_std)\n        if not torch.isfinite(loss):\n            continue\n\n        optimizer.zero_grad()\n        loss.backward()\n        optimizer.step()\n\n        total_train_loss += loss.item()\n        pbar.set_postfix(loss=f\"{loss.item():.4f}\")\n\n    avg_train_loss = total_train_loss / len(train_loader)\n    print(f\"✅ Epoch {epoch+1} - Avg Train Loss: {avg_train_loss:.6f}\")\n\n    # 🔍 Validation\n    model.eval()\n    total_val_loss = 0.0\n    with torch.no_grad():\n        for X, y in val_loader:\n            X, y = X.to(device), y.to(device)\n            pred_mean, pred_log_std = model(X)\n            loss = gll_loss(y, pred_mean, pred_log_std)\n            total_val_loss += loss.item()\n    avg_val_loss = total_val_loss / len(val_loader)\n    print(f\"🔍 Epoch {epoch+1} - Avg Val Loss: {avg_val_loss:.6f}\")\n\n    # ⏹️ Early Stopping\n    if avg_val_loss < best_val_loss:\n        best_val_loss = avg_val_loss\n        wait = 0\n    else:\n        wait += 1\n        if wait >= patience:\n            print(f\"⏹️ Early stopping at epoch {epoch+1}\")\n            break","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-06T06:55:23.334929Z","iopub.execute_input":"2025-08-06T06:55:23.335152Z","iopub.status.idle":"2025-08-06T14:28:42.167131Z","shell.execute_reply.started":"2025-08-06T06:55:23.335136Z","shell.execute_reply":"2025-08-06T14:28:42.155806Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\n\n# 📊 Visualize Predictions\nmodel.eval()\nfor i, (X, y_true) in enumerate(val_loader):\n    X, y_true = X.to(device), y_true.to(device)\n    with torch.no_grad():\n        pred_mean, pred_log_std = model(X)\n\n    # Detach tensors before converting to numpy\n    mean = pred_mean[0].detach().cpu().numpy()\n    std = torch.exp(pred_log_std[0]).detach().cpu().numpy()\n    true = y_true[0].detach().cpu().numpy()\n\n    plt.figure(figsize=(10, 4))\n    plt.plot(true, label='True', linewidth=2)\n    plt.plot(mean, label='Predicted', linestyle='--')\n    plt.fill_between(\n        range(283),\n        mean - std,\n        mean + std,\n        alpha=0.3, label='Uncertainty'\n    )\n    plt.legend()\n    plt.title(f\"Validation Spectrum #{i}\")\n    plt.xlabel(\"Wavelength Bin\")\n    plt.ylabel(\"Flux\")\n    plt.grid(True)\n    plt.tight_layout()\n    plt.show()\n\n    if i == 2:\n        break  # Show 3 examples\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-06T15:06:04.392075Z","iopub.execute_input":"2025-08-06T15:06:04.392687Z","iopub.status.idle":"2025-08-06T15:06:10.444474Z","shell.execute_reply.started":"2025-08-06T15:06:04.392668Z","shell.execute_reply":"2025-08-06T15:06:10.443726Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"torch.save(model.state_dict(), 'best_model.pth')\nprint(\"✅ Model saved!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-06T15:07:06.543983Z","iopub.execute_input":"2025-08-06T15:07:06.544299Z","iopub.status.idle":"2025-08-06T15:07:06.695438Z","shell.execute_reply.started":"2025-08-06T15:07:06.544276Z","shell.execute_reply":"2025-08-06T15:07:06.694696Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.metrics import mean_absolute_error\nfrom tqdm import tqdm  # ✅ make sure tqdm is imported\n\nmodel.eval()\nall_preds = []\nall_targets = []\n\nwith torch.no_grad():\n    for X, y in tqdm(val_loader, desc=\"🔍 Evaluating\"):\n        X, y = X.to(device), y.to(device)\n        pred_mean, _ = model(X)\n        all_preds.append(pred_mean.cpu())\n        all_targets.append(y.cpu())\n\nall_preds = torch.cat(all_preds).numpy()\nall_targets = torch.cat(all_targets).numpy()\n\nmae = mean_absolute_error(all_targets, all_preds)\nprint(f\"📐 Validation MAE: {mae:.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-06T15:10:08.550663Z","iopub.execute_input":"2025-08-06T15:10:08.550946Z","iopub.status.idle":"2025-08-06T15:11:48.051245Z","shell.execute_reply.started":"2025-08-06T15:10:08.550926Z","shell.execute_reply":"2025-08-06T15:11:48.0503Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport torch\nimport pyarrow.parquet as pq\nimport pandas as pd\nfrom tqdm import tqdm\n\n# Constants\nTEST_DIR = \"/kaggle/input/ariel-data-challenge-2025/test\"\nadc_info = pd.read_csv(\"/kaggle/input/ariel-data-challenge-2025/adc_info.csv\").iloc[0]\nadc_gain = adc_info['FGS1_adc_gain']\nadc_offset = adc_info['FGS1_adc_offset']\n\nmodel.eval()\nsubmission_rows = []\n\nwith torch.no_grad():\n    for planet_id in tqdm(os.listdir(TEST_DIR), desc=\"🚀 Predicting\"):\n        obj_path = os.path.join(TEST_DIR, planet_id)\n        if not os.path.isdir(obj_path):\n            continue\n\n        try:\n            # Load signal file\n            signal_path = os.path.join(obj_path, \"FGS1_signal_0.parquet\")\n            signal = pq.read_table(signal_path).to_pandas().values.astype('float64')\n            signal = signal * adc_gain + adc_offset\n            signal = signal.reshape(-1, 32, 32)\n            signal = signal[:1000]  # truncate for consistency\n            signal = torch.tensor(signal).float().unsqueeze(1).unsqueeze(0).to(device)  # (1, T, 1, 32, 32)\n\n            # Model prediction\n            pred_mean, pred_log_std = model(signal)\n            pred_mean = pred_mean.squeeze().cpu().numpy()\n            pred_std = torch.exp(pred_log_std).squeeze().cpu().numpy()\n\n            # Row: [planet_id, mu_0...mu_282, sigma_0...sigma_282]\n            row = [planet_id] + pred_mean.tolist() + pred_std.tolist()\n            submission_rows.append(row)\n\n        except Exception as e:\n            print(f\"❌ Failed for {planet_id}: {e}\")\n            continue\n\n# Prepare header\nmu_cols = [f\"mu_{i}\" for i in range(283)]\nsigma_cols = [f\"sigma_{i}\" for i in range(283)]\ncolumns = [\"planet_id\"] + mu_cols + sigma_cols\n\n# Save submission\nsubmission_df = pd.DataFrame(submission_rows, columns=columns)\nsubmission_df.to_csv(\"submission.csv\", index=False)\nprint(\"✅ submission.csv created successfully in required format!\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-06T15:18:21.094641Z","iopub.execute_input":"2025-08-06T15:18:21.094923Z","iopub.status.idle":"2025-08-06T15:18:22.041267Z","shell.execute_reply.started":"2025-08-06T15:18:21.094902Z","shell.execute_reply":"2025-08-06T15:18:22.040414Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"submission_df.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-06T15:21:22.596741Z","iopub.execute_input":"2025-08-06T15:21:22.597113Z","iopub.status.idle":"2025-08-06T15:21:22.654095Z","shell.execute_reply.started":"2025-08-06T15:21:22.597086Z","shell.execute_reply":"2025-08-06T15:21:22.653485Z"}},"outputs":[],"execution_count":null}]}