{"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":[{"sourceId":112509,"databundleVersionId":14254895,"sourceType":"competition"}],"dockerImageVersionId":31259,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# CSIRO Image2Biomass Prediction - Complete Championship Solution\nAuthor: Advanced ML Team\nTarget: 1st Place (R² > 0.73)\n\nINSTRUCTIONS:\n1. Copy each cell section into separate Kaggle notebook cells\n2. Run cells in order (1 through 12)\n3. Total runtime: ~5-6 hours on T4 x2 GPU\n4. Output: submission.csv ready for upload","metadata":{}},{"cell_type":"code","source":"# ==============================================================================\n# CELL 1: IMPORTS AND ENVIRONMENT SETUP\n# ==============================================================================\n\nimport numpy as np\nimport pandas as pd\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.utils.data import Dataset, DataLoader\nfrom torch.cuda.amp import autocast, GradScaler\nimport cv2\nimport os\nimport gc\nimport random\nfrom pathlib import Path\nimport warnings\nwarnings.filterwarnings('ignore')\n\nfrom sklearn.model_selection import GroupKFold\nfrom sklearn.metrics import r2_score\nimport timm\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2\nfrom tqdm.auto import tqdm\n\nprint(f\"PyTorch: {torch.__version__}\")\nprint(f\"CUDA: {torch.cuda.is_available()}\")\nif torch.cuda.is_available():\n    print(f\"GPU: {torch.cuda.get_device_name(0)}\")\n","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-01-21T08:56:29.609223Z","iopub.execute_input":"2026-01-21T08:56:29.609593Z","iopub.status.idle":"2026-01-21T08:56:29.617409Z","shell.execute_reply.started":"2026-01-21T08:56:29.609567Z","shell.execute_reply":"2026-01-21T08:56:29.616532Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==============================================================================\n# CELL 2: CONFIGURATION\n# ==============================================================================\n\nclass CFG:\n    # Paths\n    data_dir = '/kaggle/input/csiro-biomass'\n    train_img = f'{data_dir}/train_images/'\n    test_img = f'{data_dir}/test_images/'\n    emb_path = '/kaggle/input/csiro-image-embeddings/'\n    \n    # Training\n    n_folds = 7\n    seed = 42\n    epochs = 14\n    batch_size = 20\n    lr = 2e-4\n    weight_decay = 1e-5\n    dropout = 0.3\n    \n    # Model\n    models = [\n        'tf_efficientnetv2_l.in21k_ft_in1k',\n        'convnext_large.fb_in22k_ft_in1k_384',\n        'swin_large_patch4_window12_384.ms_in22k_ft_in1k'\n    ]\n    img_size = 384\n    \n    # Targets\n    targets = ['Dry_Green_g', 'Dry_Dead_g', 'Dry_Clover_g', 'GDM_g', 'Dry_Total_g']\n    weights = {'Dry_Green_g': 0.1, 'Dry_Dead_g': 0.1, 'Dry_Clover_g': 0.1, \n               'GDM_g': 0.2, 'Dry_Total_g': 0.5}\n    \n    # Features\n    use_embeddings = True\n    use_tta = True\n    device = 'cuda'\n    num_workers = 2\n\ndef seed_all(seed=42):\n    random.seed(seed)\n    os.environ['PYTHONHASHSEED'] = str(seed)\n    np.random.seed(seed)\n    torch.manual_seed(seed)\n    torch.cuda.manual_seed_all(seed)\n    torch.backends.cudnn.deterministic = True\n\nseed_all(CFG.seed)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-21T08:56:29.653772Z","iopub.execute_input":"2026-01-21T08:56:29.654108Z","iopub.status.idle":"2026-01-21T08:56:29.663917Z","shell.execute_reply.started":"2026-01-21T08:56:29.65408Z","shell.execute_reply":"2026-01-21T08:56:29.662991Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==============================================================================\n# CELL 3: DATA LOADING\n# ==============================================================================\n\ndef load_data():\n    train = pd.read_csv(f'{CFG.data_dir}/train.csv')\n    test = pd.read_csv(f'{CFG.data_dir}/test.csv')\n    \n    # Check and print columns\n    print(\"Train columns:\", train.columns.tolist())\n    print(\"Test columns:\", test.columns.tolist())\n    \n    # Find the correct ID column name\n    id_col = None\n    for col in ['image_id', 'ImageID', 'Image_ID', 'ID', 'id']:\n        if col in train.columns:\n            id_col = col\n            break\n    \n    if id_col is None:\n        # If no standard ID column, assume first column is ID\n        id_col = train.columns[0]\n        print(f\"Using '{id_col}' as ID column\")\n    \n    # Rename to standard 'image_id'\n    if id_col != 'image_id':\n        train.rename(columns={id_col: 'image_id'}, inplace=True)\n        test.rename(columns={id_col: 'image_id'}, inplace=True)\n        print(f\"Renamed '{id_col}' to 'image_id'\")\n    \n    # Feature engineering\n    for df in [train, test]:\n        if 'Date' in df.columns:\n            df['Date'] = pd.to_datetime(df['Date'], errors='coerce')\n            df['month'] = df['Date'].dt.month.fillna(6)\n            df['doy'] = df['Date'].dt.dayofyear.fillna(180)\n            df['season'] = df['month'].apply(lambda x: (x%12+3)//3)\n            df['month_sin'] = np.sin(2*np.pi*df['month']/12)\n            df['month_cos'] = np.cos(2*np.pi*df['month']/12)\n        \n        if 'NDVI' in df.columns:\n            df['NDVI'].fillna(df['NDVI'].median(), inplace=True)\n            df['NDVI_sq'] = df['NDVI']**2\n            df['NDVI_log'] = np.log1p(df['NDVI'].clip(0))\n    \n    # Load embeddings\n    embeddings = None\n    if CFG.use_embeddings:\n        try:\n            # Try multiple possible file names\n            emb_files = [\n                f'{CFG.emb_path}/embeddings.npy',\n                f'{CFG.emb_path}/train_embeddings.npy',\n                f'{CFG.emb_path}/image_embeddings.npy'\n            ]\n            \n            for emb_file in emb_files:\n                try:\n                    embeddings = np.load(emb_file, allow_pickle=True).item()\n                    print(f\"✅ Loaded {len(embeddings)} embeddings from {emb_file}\")\n                    break\n                except FileNotFoundError:\n                    continue\n            \n            if embeddings is None:\n                print(\"⚠️ No embeddings found, continuing without them\")\n                CFG.use_embeddings = False\n        except Exception as e:\n            print(f\"⚠️ Error loading embeddings: {e}\")\n            CFG.use_embeddings = False\n    \n    return train, test, embeddings\n\ntrain_df, test_df, embeddings = load_data()\nprint(f\"\\n✅ Data loaded successfully!\")\nprint(f\"Train: {train_df.shape}, Test: {test_df.shape}\")\nprint(f\"Train columns: {train_df.columns.tolist()}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-21T08:56:29.728471Z","iopub.execute_input":"2026-01-21T08:56:29.729341Z","iopub.status.idle":"2026-01-21T08:56:29.757537Z","shell.execute_reply.started":"2026-01-21T08:56:29.729304Z","shell.execute_reply":"2026-01-21T08:56:29.756595Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==============================================================================\n# CELL 4: AUGMENTATIONS\n# ==============================================================================\n\ndef get_transforms(phase='train'):\n    if phase == 'train':\n        return A.Compose([\n            A.RandomResizedCrop(CFG.img_size, CFG.img_size, scale=(0.85, 1.0)),\n            A.HorizontalFlip(p=0.5),\n            A.VerticalFlip(p=0.3),\n            A.ShiftScaleRotate(shift_limit=0.0625, scale_limit=0.15, \n                              rotate_limit=30, p=0.5),\n            A.OneOf([A.OpticalDistortion(p=1), A.GridDistortion(p=1)], p=0.2),\n            A.ColorJitter(0.2, 0.2, 0.2, 0.1, p=0.4),\n            A.OneOf([A.GaussNoise(p=1), A.GaussianBlur(p=1)], p=0.2),\n            A.CoarseDropout(8, 30, 30, p=0.2),\n            A.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]),\n            ToTensorV2()\n        ])\n    return A.Compose([\n        A.Resize(CFG.img_size, CFG.img_size),\n        A.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]),\n        ToTensorV2()\n    ])\n\ndef get_tta():\n    return [\n        A.Compose([A.Resize(CFG.img_size, CFG.img_size), \n                   A.Normalize([0.485,0.456,0.406], [0.229,0.224,0.225]), ToTensorV2()]),\n        A.Compose([A.Resize(CFG.img_size, CFG.img_size), A.HorizontalFlip(p=1),\n                   A.Normalize([0.485,0.456,0.406], [0.229,0.224,0.225]), ToTensorV2()]),\n        A.Compose([A.Resize(CFG.img_size, CFG.img_size), A.VerticalFlip(p=1),\n                   A.Normalize([0.485,0.456,0.406], [0.229,0.224,0.225]), ToTensorV2()])\n    ]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-21T08:56:29.771346Z","iopub.execute_input":"2026-01-21T08:56:29.771725Z","iopub.status.idle":"2026-01-21T08:56:29.781674Z","shell.execute_reply.started":"2026-01-21T08:56:29.771694Z","shell.execute_reply":"2026-01-21T08:56:29.780735Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==============================================================================\n# CELL 5: DATASET\n# ==============================================================================\n\nclass BiomassDataset(Dataset):\n    def __init__(self, df, embeddings=None, transforms=None, phase='train'):\n        self.df = df.reset_index(drop=True)\n        self.embeddings = embeddings\n        self.transforms = transforms\n        self.phase = phase\n        \n    def __len__(self):\n        return len(self.df)\n    \n    def __getitem__(self, idx):\n        row = self.df.iloc[idx]\n        img_id = row['image_id']\n        \n        # Load image\n        img_path = os.path.join(CFG.train_img if self.phase=='train' else CFG.test_img, \n                                f\"{img_id}.jpg\")\n        img = cv2.imread(img_path)\n        img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n        \n        if self.transforms:\n            img = self.transforms(image=img)['image']\n        \n        # Features\n        features = []\n        for col in ['NDVI', 'NDVI_sq', 'NDVI_log', 'month_sin', 'month_cos', 'season']:\n            features.append(row.get(col, 0))\n        \n        if CFG.use_embeddings and self.embeddings:\n            emb = self.embeddings.get(img_id, np.zeros(1024))\n            features.extend(emb.tolist() if isinstance(emb, np.ndarray) else [0]*1024)\n        \n        features = torch.tensor(features, dtype=torch.float32)\n        \n        if self.phase == 'train':\n            targets = torch.tensor([row[t] for t in CFG.targets], dtype=torch.float32)\n            return img, features, targets\n        return img, features, img_id","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-21T08:56:29.805324Z","iopub.execute_input":"2026-01-21T08:56:29.805676Z","iopub.status.idle":"2026-01-21T08:56:29.815029Z","shell.execute_reply.started":"2026-01-21T08:56:29.80565Z","shell.execute_reply":"2026-01-21T08:56:29.814005Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==============================================================================\n# CELL 6: MODEL\n# ==============================================================================\n\nclass BiomassModel(nn.Module):\n    def __init__(self, model_name, num_features):\n        super().__init__()\n        \n        self.backbone = timm.create_model(model_name, pretrained=True, \n                                         num_classes=0, global_pool='avg')\n        backbone_dim = self.backbone.num_features\n        \n        self.feat_net = nn.Sequential(\n            nn.Linear(num_features, 256),\n            nn.BatchNorm1d(256),\n            nn.ReLU(),\n            nn.Dropout(0.2),\n            nn.Linear(256, 128),\n            nn.BatchNorm1d(128),\n            nn.ReLU()\n        )\n        \n        self.head = nn.Sequential(\n            nn.Linear(backbone_dim + 128, 512),\n            nn.BatchNorm1d(512),\n            nn.ReLU(),\n            nn.Dropout(CFG.dropout),\n            nn.Linear(512, 256),\n            nn.ReLU(),\n            nn.Dropout(0.2)\n        )\n        \n        self.outputs = nn.ModuleList([\n            nn.Sequential(nn.Linear(256, 1), nn.Softplus()) \n            for _ in range(5)\n        ])\n        \n    def forward(self, img, feat):\n        x1 = self.backbone(img)\n        x2 = self.feat_net(feat)\n        x = torch.cat([x1, x2], dim=1)\n        x = self.head(x)\n        return torch.cat([out(x) for out in self.outputs], dim=1)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-21T08:56:29.828567Z","iopub.execute_input":"2026-01-21T08:56:29.8294Z","iopub.status.idle":"2026-01-21T08:56:29.838146Z","shell.execute_reply.started":"2026-01-21T08:56:29.829357Z","shell.execute_reply":"2026-01-21T08:56:29.836989Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==============================================================================\n# CELL 7: LOSS AND METRICS\n# ==============================================================================\n\nclass WeightedMSE(nn.Module):\n    def __init__(self):\n        super().__init__()\n        self.w = torch.tensor([CFG.weights[t] for t in CFG.targets])\n        \n    def forward(self, pred, target):\n        self.w = self.w.to(pred.device)\n        loss = ((pred - target)**2 * self.w).mean()\n        consistency = F.l1_loss(pred[:,:4].sum(1, keepdim=True), pred[:,4:5])\n        return loss + 0.05*consistency\n\ndef calc_r2(y_true, y_pred, weights):\n    y_true, y_pred, weights = y_true.flatten(), y_pred.flatten(), weights.flatten()\n    mean = np.average(y_true, weights=weights)\n    ss_res = np.sum(weights * (y_true - y_pred)**2)\n    ss_tot = np.sum(weights * (y_true - mean)**2)\n    return 1 - ss_res/(ss_tot + 1e-8)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-21T08:56:29.851249Z","iopub.execute_input":"2026-01-21T08:56:29.852194Z","iopub.status.idle":"2026-01-21T08:56:29.860989Z","shell.execute_reply.started":"2026-01-21T08:56:29.852151Z","shell.execute_reply":"2026-01-21T08:56:29.859984Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==============================================================================\n# CELL 8: TRAINING FUNCTIONS\n# ==============================================================================\n\ndef train_epoch(model, loader, criterion, optimizer, scaler):\n    model.train()\n    losses = []\n    \n    for img, feat, target in tqdm(loader, leave=False):\n        img, feat, target = img.to(CFG.device), feat.to(CFG.device), target.to(CFG.device)\n        \n        optimizer.zero_grad()\n        with autocast():\n            out = model(img, feat)\n            loss = criterion(out, target)\n        \n        scaler.scale(loss).backward()\n        scaler.unscale_(optimizer)\n        nn.utils.clip_grad_norm_(model.parameters(), 1.0)\n        scaler.step(optimizer)\n        scaler.update()\n        \n        losses.append(loss.item())\n    \n    return np.mean(losses)\n\n@torch.no_grad()\ndef valid_epoch(model, loader):\n    model.eval()\n    preds, targets = [], []\n    \n    for img, feat, target in loader:\n        img, feat = img.to(CFG.device), feat.to(CFG.device)\n        out = model(img, feat)\n        preds.append(out.cpu().numpy())\n        targets.append(target.numpy())\n    \n    preds, targets = np.vstack(preds), np.vstack(targets)\n    w = np.tile([CFG.weights[t] for t in CFG.targets], (len(preds), 1))\n    r2 = calc_r2(targets, preds, w)\n    return r2, preds","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-21T08:56:29.878334Z","iopub.execute_input":"2026-01-21T08:56:29.878687Z","iopub.status.idle":"2026-01-21T08:56:29.889377Z","shell.execute_reply.started":"2026-01-21T08:56:29.878658Z","shell.execute_reply":"2026-01-21T08:56:29.888375Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==============================================================================\n# CELL 9: TRAINING LOOP\n# ==============================================================================\n\ndef train_kfold():\n    gkf = GroupKFold(n_splits=CFG.n_folds)\n    oof = np.zeros((len(train_df), 5))\n    scores = []\n    \n    num_feat = 6 + (1024 if CFG.use_embeddings else 0)\n    \n    for fold, (tr_idx, val_idx) in enumerate(gkf.split(train_df, groups=train_df['image_id'])):\n        print(f\"\\n{'='*50}\\nFOLD {fold+1}/{CFG.n_folds}\\n{'='*50}\")\n        \n        tr_df, val_df = train_df.iloc[tr_idx], train_df.iloc[val_idx]\n        \n        tr_ds = BiomassDataset(tr_df, embeddings, get_transforms('train'), 'train')\n        val_ds = BiomassDataset(val_df, embeddings, get_transforms('valid'), 'train')\n        \n        tr_loader = DataLoader(tr_ds, CFG.batch_size, shuffle=True, \n                              num_workers=CFG.num_workers, pin_memory=True)\n        val_loader = DataLoader(val_ds, CFG.batch_size*2, shuffle=False, \n                               num_workers=CFG.num_workers, pin_memory=True)\n        \n        model = BiomassModel(CFG.models[fold % len(CFG.models)], num_feat).to(CFG.device)\n        criterion = WeightedMSE()\n        optimizer = torch.optim.AdamW(model.parameters(), lr=CFG.lr, weight_decay=CFG.weight_decay)\n        scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, CFG.epochs, 1e-6)\n        scaler = GradScaler()\n        \n        best_r2 = -999\n        patience = 0\n        \n        for epoch in range(CFG.epochs):\n            tr_loss = train_epoch(model, tr_loader, criterion, optimizer, scaler)\n            val_r2, val_pred = valid_epoch(model, val_loader)\n            scheduler.step()\n            \n            print(f\"Epoch {epoch+1}: Loss={tr_loss:.4f}, R²={val_r2:.4f}\")\n            \n            if val_r2 > best_r2:\n                best_r2 = val_r2\n                oof[val_idx] = val_pred\n                torch.save(model.state_dict(), f'fold{fold}.pth')\n                patience = 0\n            else:\n                patience += 1\n                if patience >= 4:\n                    break\n        \n        scores.append(best_r2)\n        print(f\"Best R²: {best_r2:.4f}\")\n        \n        del model, optimizer, scheduler\n        gc.collect()\n        torch.cuda.empty_cache()\n    \n    w = np.tile([CFG.weights[t] for t in CFG.targets], (len(oof), 1))\n    overall = calc_r2(train_df[CFG.targets].values, oof, w)\n    print(f\"\\n{'='*50}\\nOOF R²: {overall:.6f}\\nScores: {scores}\\n{'='*50}\")\n    \n    return oof\n\noof_preds = train_kfold()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-21T08:56:29.9395Z","iopub.execute_input":"2026-01-21T08:56:29.939875Z","iopub.status.idle":"2026-01-21T08:56:29.979566Z","shell.execute_reply.started":"2026-01-21T08:56:29.939844Z","shell.execute_reply":"2026-01-21T08:56:29.978214Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==============================================================================\n# CELL 10: TEST PREDICTION\n# ==============================================================================\n\n@torch.no_grad()\ndef predict_test():\n    num_feat = 6 + (1024 if CFG.use_embeddings else 0)\n    all_preds = []\n    \n    for fold in range(CFG.n_folds):\n        print(f\"\\nPredicting Fold {fold+1}\")\n        \n        model = BiomassModel(CFG.models[fold % len(CFG.models)], num_feat).to(CFG.device)\n        model.load_state_dict(torch.load(f'fold{fold}.pth'))\n        model.eval()\n        \n        if CFG.use_tta:\n            tta_preds = []\n            for tta_tfm in get_tta():\n                ds = BiomassDataset(test_df, embeddings, tta_tfm, 'test')\n                loader = DataLoader(ds, CFG.batch_size*2, shuffle=False, \n                                   num_workers=CFG.num_workers)\n                \n                fold_pred = []\n                for img, feat, _ in loader:\n                    img, feat = img.to(CFG.device), feat.to(CFG.device)\n                    out = model(img, feat)\n                    fold_pred.append(out.cpu().numpy())\n                \n                tta_preds.append(np.vstack(fold_pred))\n            \n            all_preds.append(np.mean(tta_preds, axis=0))\n        else:\n            ds = BiomassDataset(test_df, embeddings, get_transforms('valid'), 'test')\n            loader = DataLoader(ds, CFG.batch_size*2, shuffle=False, num_workers=CFG.num_workers)\n            \n            fold_pred = []\n            for img, feat, _ in loader:\n                img, feat = img.to(CFG.device), feat.to(CFG.device)\n                out = model(img, feat)\n                fold_pred.append(out.cpu().numpy())\n            \n            all_preds.append(np.vstack(fold_pred))\n        \n        del model\n        gc.collect()\n        torch.cuda.empty_cache()\n    \n    final = np.mean(all_preds, axis=0)\n    final = np.maximum(final, 0)\n    \n    return final\n\ntest_preds = predict_test()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-21T08:56:29.980415Z","iopub.status.idle":"2026-01-21T08:56:29.980885Z","shell.execute_reply.started":"2026-01-21T08:56:29.980646Z","shell.execute_reply":"2026-01-21T08:56:29.98067Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==============================================================================\n# CELL 11: CREATE SUBMISSION\n# ==============================================================================\n\ndef create_submission(preds):\n    rows = []\n    for idx, row in test_df.iterrows():\n        for t_idx, target in enumerate(CFG.targets):\n            rows.append({\n                'sample_id': f\"{row['image_id']}__{target}\",\n                'target': max(0, float(preds[idx, t_idx]))\n            })\n    \n    sub = pd.DataFrame(rows)\n    sub.to_csv('submission.csv', index=False)\n    \n    print(f\"\\n{'='*50}\")\n    print(f\"✅ Submission created: {len(sub)} rows\")\n    print(f\"{'='*50}\\n\")\n    print(sub.head(10))\n    print(f\"\\nStats:\\n{sub['target'].describe()}\")\n    \n    return sub\n\nsubmission = create_submission(test_preds)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-21T08:56:30.061448Z","iopub.execute_input":"2026-01-21T08:56:30.061818Z","iopub.status.idle":"2026-01-21T08:56:30.071828Z","shell.execute_reply.started":"2026-01-21T08:56:30.061789Z","shell.execute_reply":"2026-01-21T08:56:30.070325Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==============================================================================\n# CELL 12: FINAL VALIDATION\n# ==============================================================================\n\nprint(\"\\n🔍 Final checks:\")\nsub = pd.read_csv('submission.csv')\nassert list(sub.columns) == ['sample_id', 'target']\nassert len(sub) == len(test_df) * 5\nassert sub['target'].isna().sum() == 0\nassert (sub['target'] >= 0).all()\nprint(\"✅ All checks passed!\\n\")\nprint(\"📊 Ready to submit! 🚀\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-21T08:56:30.13024Z","iopub.execute_input":"2026-01-21T08:56:30.13058Z","iopub.status.idle":"2026-01-21T08:56:30.144324Z","shell.execute_reply.started":"2026-01-21T08:56:30.130544Z","shell.execute_reply":"2026-01-21T08:56:30.143037Z"}},"outputs":[],"execution_count":null}]}