{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":112509,"databundleVersionId":14254895,"sourceType":"competition"}],"dockerImageVersionId":31192,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"id":"fc094eea","cell_type":"code","source":"import os, random\nfrom pathlib import Path\n\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\n\nfrom PIL import Image\nfrom IPython.display import display\n\n# sklearn (tabular)\nfrom sklearn.model_selection import KFold\nfrom sklearn.ensemble import HistGradientBoostingRegressor\nfrom sklearn.linear_model import Ridge, LinearRegression\n\n# torch (cnn)\nimport torch\nfrom torch import nn\nfrom torch.utils.data import Dataset, DataLoader\nimport torchvision.transforms as T\n\npd.set_option(\"display.max_columns\", 120)\npd.set_option(\"display.width\", 220)\n\nINPUT_DIR = Path(\"/kaggle/input/csiro-biomass\")\nsubdirs = [p for p in INPUT_DIR.iterdir() if p.is_dir()]\nCOMP_DIR = subdirs[0] if len(subdirs) == 1 else INPUT_DIR\nprint(\"Usando carpeta de datos:\", COMP_DIR)\n\ndef set_seed(seed=42):\n    random.seed(seed); np.random.seed(seed)\n    torch.manual_seed(seed); torch.cuda.manual_seed_all(seed)\nset_seed(42)\n\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\ndevice","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-09T04:24:56.630486Z","iopub.execute_input":"2025-11-09T04:24:56.630873Z","iopub.status.idle":"2025-11-09T04:24:56.649626Z","shell.execute_reply.started":"2025-11-09T04:24:56.630834Z","shell.execute_reply":"2025-11-09T04:24:56.648482Z"}},"outputs":[],"execution_count":null},{"id":"5aca6e36","cell_type":"code","source":"train = pd.read_csv(COMP_DIR / \"train.csv\")\ntest  = pd.read_csv(COMP_DIR / \"test.csv\")\n\nprint(\"train shape:\", train.shape)\nprint(\"test shape:\", test.shape)\ndisplay(train.head())\ndisplay(test.head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-09T04:24:56.651600Z","iopub.execute_input":"2025-11-09T04:24:56.651957Z","iopub.status.idle":"2025-11-09T04:24:56.685950Z","shell.execute_reply.started":"2025-11-09T04:24:56.651927Z","shell.execute_reply":"2025-11-09T04:24:56.685067Z"}},"outputs":[],"execution_count":null},{"id":"fc8d2ca2","cell_type":"code","source":"target_names = sorted(train[\"target_name\"].unique())\nprint(\"Targets:\", target_names, \"| n_targets:\", len(target_names))\n\ny_wide = (train.pivot(index=\"image_path\", columns=\"target_name\", values=\"target\")\n               .loc[:, target_names])\nprint(\"y_wide shape:\", y_wide.shape)\n\nTARGET_WEIGHTS = {\n    \"Dry_Green_g\": 0.1,\n    \"Dry_Dead_g\":  0.1,\n    \"Dry_Clover_g\":0.1,\n    \"GDM_g\":       0.2,\n    \"Dry_Total_g\": 0.5,\n}\nW_VEC = np.array([TARGET_WEIGHTS[t] for t in target_names], dtype=np.float32)\n\ndef weighted_r2_from_long(df_long_true_pred,\n                          target_col=\"target_name\", y_col=\"y_true\", yhat_col=\"y_pred\"):\n    w = df_long_true_pred[target_col].map(TARGET_WEIGHTS).values.astype(np.float64)\n    y  = df_long_true_pred[y_col].values.astype(np.float64)\n    yh = df_long_true_pred[yhat_col].values.astype(np.float64)\n    yw = np.sum(w * y) / np.sum(w)\n    ss_res = np.sum(w * (y - yh)**2)\n    ss_tot = np.sum(w * (y - yw)**2)\n    return 1.0 - ss_res / (ss_tot + 1e-12)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-09T04:24:56.686786Z","iopub.execute_input":"2025-11-09T04:24:56.687050Z","iopub.status.idle":"2025-11-09T04:24:56.702946Z","shell.execute_reply.started":"2025-11-09T04:24:56.687028Z","shell.execute_reply":"2025-11-09T04:24:56.702022Z"}},"outputs":[],"execution_count":null},{"id":"f006db6f","cell_type":"code","source":"def extract_image_features(rel_path: str) -> dict:\n    img_path = COMP_DIR / rel_path\n    feats = {\"mean_R\":0.0,\"mean_G\":0.0,\"mean_B\":0.0,\n             \"std_R\":0.0,\"std_G\":0.0,\"std_B\":0.0,\n             \"excess_green\":0.0,\"mean_gray\":0.0,\n             \"mean_g_fraction\":0.0,\"prop_green_pixels\":0.0,\"p90_excess_green\":0.0,\n             \"mean_H\":0.0,\"mean_S\":0.0,\"mean_V\":0.0,\n             \"std_H\":0.0,\"std_S\":0.0,\"std_V\":0.0,\n             \"mean_L\":0.0,\"mean_A\":0.0,\"mean_Blab\":0.0,\n             \"std_L\":0.0,\"std_A\":0.0,\"std_Blab\":0.0,\n             \"edge_density\":0.0,\"lap_var\":0.0,\"entropy\":0.0}\n    try:\n        with Image.open(img_path) as img:\n            img = img.convert(\"RGB\")\n            arr = np.asarray(img).astype(np.float32)\n        R,G,B = arr[:,:,0], arr[:,:,1], arr[:,:,2]\n        feats[\"mean_R\"],feats[\"mean_G\"],feats[\"mean_B\"] = R.mean(),G.mean(),B.mean()\n        feats[\"std_R\"],feats[\"std_G\"],feats[\"std_B\"]    = R.std(),G.std(),B.std()\n        eg = 2*G - R - B; feats[\"excess_green\"] = eg.mean()\n        gray = 0.299*R + 0.587*G + 0.114*B; feats[\"mean_gray\"] = gray.mean()\n        denom = R+G+B+1e-6; feats[\"mean_g_fraction\"] = (G/denom).mean()\n        feats[\"prop_green_pixels\"] = ((G>R)&(G>B)).mean()\n        feats[\"p90_excess_green\"] = float(np.percentile(eg,90))\n        hsv = np.array(Image.fromarray(arr.astype(np.uint8)).convert(\"HSV\")).astype(np.float32)\n        H,S,V = hsv[:,:,0], hsv[:,:,1], hsv[:,:,2]\n        feats[\"mean_H\"],feats[\"mean_S\"],feats[\"mean_V\"] = H.mean(),S.mean(),V.mean()\n        feats[\"std_H\"],feats[\"std_S\"],feats[\"std_V\"]    = H.std(),S.std(),V.std()\n        lab = np.array(Image.fromarray(arr.astype(np.uint8)).convert(\"LAB\")).astype(np.float32)\n        L,A,Bl = lab[:,:,0], lab[:,:,1], lab[:,:,2]\n        feats[\"mean_L\"],feats[\"mean_A\"],feats[\"mean_Blab\"] = L.mean(),A.mean(),Bl.mean()\n        feats[\"std_L\"],feats[\"std_A\"],feats[\"std_Blab\"]    = L.std(),A.std(),Bl.std()\n        try:\n            from scipy.ndimage import sobel, laplace\n            sob = np.hypot(sobel(gray,0), sobel(gray,1))\n            feats[\"edge_density\"] = (sob>sob.mean()).mean()\n            feats[\"lap_var\"] = laplace(gray).var()\n        except Exception:\n            pass\n        hist,_ = np.histogram(gray, bins=64, range=(0,255), density=True)\n        hist = hist + 1e-12\n        feats[\"entropy\"] = float(-(hist*np.log(hist)).sum())\n    except Exception:\n        pass\n    return feats\n\ndef safe_fill(df: pd.DataFrame) -> pd.DataFrame:\n    df = df.replace([np.inf,-np.inf], np.nan)\n    df = df.fillna(df.mean()).fillna(0)\n    return df\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-09T04:24:56.704013Z","iopub.execute_input":"2025-11-09T04:24:56.704609Z","iopub.status.idle":"2025-11-09T04:24:56.722688Z","shell.execute_reply.started":"2025-11-09T04:24:56.704585Z","shell.execute_reply":"2025-11-09T04:24:56.721700Z"}},"outputs":[],"execution_count":null},{"id":"f00d2a01","cell_type":"code","source":"def predict_tabular(train_df: pd.DataFrame,\n                    y_wide_df: pd.DataFrame,\n                    test_df: pd.DataFrame,\n                    target_names):\n    train_images = y_wide_df.index.tolist()\n    feats_train = []\n    for rel in train_images:\n        f = extract_image_features(rel); f[\"image_path\"] = rel; feats_train.append(f)\n    X_train_df = pd.DataFrame(feats_train).set_index(\"image_path\")\n    X_train_df = safe_fill(X_train_df)\n\n    X = X_train_df.values\n    Y = y_wide_df[target_names].values\n    Y_log = np.log1p(Y)\n\n    kf = KFold(n_splits=5, shuffle=True, random_state=42)\n    oof_pred_log = np.zeros_like(Y_log)\n    for fold, (tr, va) in enumerate(kf.split(X)):\n        X_tr, X_va = X[tr], X[va]\n        Y_tr, Y_va = Y_log[tr], Y_log[va]\n\n        preds_log_va = np.zeros_like(Y_va)\n        for j, t in enumerate(target_names):\n            w = np.full(len(X_tr), TARGET_WEIGHTS[t], dtype=np.float32)\n            hgb = HistGradientBoostingRegressor(\n                max_depth=4, learning_rate=0.1, max_iter=400, random_state=42\n            )\n            hgb.fit(X_tr, Y_tr[:, j], sample_weight=w)\n            preds_log_va[:, j] = hgb.predict(X_va)\n\n        oof_pred_log[va] = preds_log_va\n\n        df_true = (pd.DataFrame(np.expm1(Y_va), columns=target_names)\n                   .reset_index().melt(id_vars=\"index\", var_name=\"target_name\", value_name=\"y_true\"))\n        df_pred = (pd.DataFrame(np.expm1(preds_log_va), columns=target_names)\n                   .reset_index().melt(id_vars=\"index\", var_name=\"target_name\", value_name=\"y_pred\"))\n        df = df_true.merge(df_pred, on=[\"index\",\"target_name\"]).drop(columns=\"index\")\n        print(f\"[TAB] Fold {fold} R2w:\", f\"{weighted_r2_from_long(df):.4f}\")\n\n    df_true = (pd.DataFrame(np.expm1(Y_log), columns=target_names)\n               .reset_index().melt(id_vars=\"index\", var_name=\"target_name\", value_name=\"y_true\"))\n    df_pred = (pd.DataFrame(np.expm1(oof_pred_log), columns=target_names)\n               .reset_index().melt(id_vars=\"index\", var_name=\"target_name\", value_name=\"y_pred\"))\n    df_cv = df_true.merge(df_pred, on=[\"index\",\"target_name\"]).drop(columns=\"index\")\n    print(\"[TAB] CV R2w global:\", f\"{weighted_r2_from_long(df_cv):.4f}\")\n\n    calib = {}\n    for j, t in enumerate(target_names):\n        ridge = Ridge(alpha=1e-6, fit_intercept=True)\n        ridge.fit(np.expm1(oof_pred_log[:, [j]]), np.expm1(Y_log[:, j:j+1]))\n        calib[t] = (float(ridge.coef_[0][0]), float(ridge.intercept_[0]))\n\n    models = []\n    for j, t in enumerate(target_names):\n        w = np.full(len(X), TARGET_WEIGHTS[t], dtype=np.float32)\n        hgb = HistGradientBoostingRegressor(\n            max_depth=4, learning_rate=0.1, max_iter=400, random_state=42\n        )\n        hgb.fit(X, Y_log[:, j], sample_weight=w)\n        models.append(hgb)\n\n    test_images = test_df[\"image_path\"].unique().tolist()\n    feats_test = []\n    for rel in test_images:\n        f = extract_image_features(rel); f[\"image_path\"] = rel; feats_test.append(f)\n    X_test_df = pd.DataFrame(feats_test).set_index(\"image_path\")\n    X_test_df = safe_fill(X_test_df)\n    X_test = X_test_df.values\n\n    preds_log = np.column_stack([m.predict(X_test) for m in models])\n    preds = np.expm1(preds_log); preds = np.clip(preds, 0, None)\n\n    for j, t in enumerate(target_names):\n        a, b = calib[t]\n        preds[:, j] = a * preds[:, j] + b\n\n    preds_tab = pd.DataFrame(preds, index=test_images, columns=target_names)\n    return preds_tab, oof_pred_log\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-09T04:24:56.724804Z","iopub.execute_input":"2025-11-09T04:24:56.725076Z","iopub.status.idle":"2025-11-09T04:24:56.750247Z","shell.execute_reply.started":"2025-11-09T04:24:56.725054Z","shell.execute_reply":"2025-11-09T04:24:56.749208Z"}},"outputs":[],"execution_count":null},{"id":"993e22fe","cell_type":"code","source":"\nclass CSIROTrainDataset(Dataset):\n    def __init__(self, image_paths, targets_log, transform=None):\n        self.image_paths = image_paths; self.targets_log = targets_log; self.transform = transform\n    def __len__(self): return len(self.image_paths)\n    def _safe(self, p):\n        pth = COMP_DIR / p\n        try:\n            with Image.open(pth) as im: im = im.convert(\"RGB\")\n        except Exception: im = Image.new(\"RGB\",(224,224),(0,0,0))\n        return im\n    def __getitem__(self, i):\n        im = self._safe(self.image_paths[i])\n        if self.transform: im = self.transform(im)\n        y = torch.tensor(self.targets_log[i], dtype=torch.float32)\n        return im, y\n\nclass CSIROTestDataset(Dataset):\n    def __init__(self, image_paths, transform=None):\n        self.image_paths = image_paths; self.transform = transform\n    def __len__(self): return len(self.image_paths)\n    def _safe(self, p):\n        pth = COMP_DIR / p\n        try:\n            with Image.open(pth) as im: im = im.convert(\"RGB\")\n        except Exception: im = Image.new(\"RGB\",(224,224),(0,0,0))\n        return im\n    def __getitem__(self, i):\n        im = self._safe(self.image_paths[i])\n        if self.transform: im = self.transform(im)\n        return im\n\nclass SmallCNN(nn.Module):\n    def __init__(self, n_outputs):\n        super().__init__()\n        self.features = nn.Sequential(\n            nn.Conv2d(3,16,3,padding=1), nn.ReLU(), nn.MaxPool2d(2),\n            nn.Conv2d(16,32,3,padding=1), nn.ReLU(), nn.MaxPool2d(2),\n            nn.Conv2d(32,64,3,padding=1), nn.ReLU(), nn.MaxPool2d(2),\n            nn.Conv2d(64,128,3,padding=1), nn.ReLU(), nn.AdaptiveAvgPool2d((1,1))\n        )\n        self.head = nn.Sequential(nn.Flatten(), nn.Linear(128,128), nn.ReLU(), nn.Linear(128,n_outputs))\n    def forward(self,x): return self.head(self.features(x))\n\ndef get_transforms():\n    train_tf = T.Compose([T.Resize((224,224)), T.RandomHorizontalFlip(0.5), T.ToTensor()])\n    test_tf  = T.Compose([T.Resize((224,224)), T.ToTensor()])\n    return train_tf, test_tf\n\ndef tta_predict(model, image_tensor, n=4):\n    x = image_tensor.unsqueeze(0).to(device)\n    outs = []\n    with torch.no_grad():\n        outs.append(model(x).cpu().numpy()[0])\n        outs.append(model(torch.flip(x,dims=[3])).cpu().numpy()[0])\n        outs.append(model(torch.flip(x,dims=[2])).cpu().numpy()[0])\n        outs.append(model(torch.rot90(x,k=1,dims=[2,3])).cpu().numpy()[0])\n    return np.mean(outs[:n], axis=0)\n\ndef cnn_oof_onefold(y_wide_df, target_names, epochs=4, batch_size=16, lr=1e-3, wd=1e-5):\n    paths = y_wide_df.index.to_numpy()\n    n = len(paths); cut = int(0.8*n)\n    tr_idx, va_idx = np.arange(cut), np.arange(cut, n)\n    Y = y_wide_df.values; Y_log = np.log1p(Y)\n\n    train_tf, _ = get_transforms()\n    ds_tr = CSIROTrainDataset(paths[tr_idx], Y_log[tr_idx], transform=train_tf)\n    ds_va = CSIROTrainDataset(paths[va_idx], Y_log[va_idx], transform=train_tf)\n    dl_tr = DataLoader(ds_tr, batch_size=batch_size, shuffle=True, num_workers=2, pin_memory=True)\n    dl_va = DataLoader(ds_va, batch_size=batch_size, shuffle=False, num_workers=2, pin_memory=True)\n\n    model = SmallCNN(n_outputs=len(target_names)).to(device)\n    opt = torch.optim.Adam(model.parameters(), lr=lr, weight_decay=wd)\n    w = torch.tensor(W_VEC, device=device).view(1,-1)\n    idx_total  = target_names.index(\"Dry_Total_g\")\n    idx_green  = target_names.index(\"Dry_Green_g\")\n    idx_dead   = target_names.index(\"Dry_Dead_g\")\n    idx_clover = target_names.index(\"Dry_Clover_g\")\n    lambda_cons = 0.3\n\n    for ep in range(epochs):\n        model.train()\n        for xb, yb in dl_tr:\n            xb, yb = xb.to(device), yb.to(device)\n            opt.zero_grad()\n            pred = model(xb)  # log1p\n            base = ((pred - yb)**2 * w).mean()\n            # Consistency in ORIGINAL scale\n            pred_lin  = torch.expm1(pred)\n            sum_parts = pred_lin[:, idx_green] + pred_lin[:, idx_dead] + pred_lin[:, idx_clover]\n            cons      = ((pred_lin[:, idx_total] - sum_parts)**2).mean()\n            (base + lambda_cons*cons).backward(); opt.step()\n        print(f\"[CNN OOF] epoch {ep+1}/{epochs}\")\n\n    model.eval(); preds=[]\n    with torch.no_grad():\n        for xb, yb in dl_va:\n            xb = xb.to(device); preds.append(model(xb).cpu().numpy())\n    preds = np.concatenate(preds, axis=0)\n    oof = np.zeros_like(Y_log); oof[va_idx] = preds\n    return oof\n\ndef train_cnn_predict(y_wide_df, test_df, target_names,\n                      epochs=8, batch_size=16, lr=1e-3, wd=1e-5, use_tta=True):\n    img_paths = y_wide_df.index.tolist()\n    Y = y_wide_df.values; Y_log = np.log1p(Y)\n\n    train_tf, test_tf = get_transforms()\n    ds = CSIROTrainDataset(img_paths, Y_log, transform=train_tf)\n    dl = DataLoader(ds, batch_size=batch_size, shuffle=True, num_workers=2, pin_memory=True)\n\n    model = SmallCNN(n_outputs=len(target_names)).to(device)\n    opt = torch.optim.Adam(model.parameters(), lr=lr, weight_decay=wd)\n\n    w = torch.tensor(W_VEC, device=device).view(1,-1)\n    idx_total  = target_names.index(\"Dry_Total_g\")\n    idx_green  = target_names.index(\"Dry_Green_g\")\n    idx_dead   = target_names.index(\"Dry_Dead_g\")\n    idx_clover = target_names.index(\"Dry_Clover_g\")\n    lambda_cons = 0.3\n\n    model.train()\n    for ep in range(epochs):\n        running=0.0\n        for xb, yb in dl:\n            xb, yb = xb.to(device), yb.to(device)\n            opt.zero_grad()\n            pred = model(xb)  # log1p\n            base = ((pred - yb)**2 * w).mean()\n            pred_lin  = torch.expm1(pred)\n            sum_parts = pred_lin[:, idx_green] + pred_lin[:, idx_dead] + pred_lin[:, idx_clover]\n            cons      = ((pred_lin[:, idx_total] - sum_parts)**2).mean()\n            loss = base + lambda_cons*cons\n            loss.backward(); opt.step()\n            running += loss.item()*xb.size(0)\n        print(f\"[CNN] Epoch {ep+1}/{epochs} - loss_w(log1p + cons_orig): {running/len(ds):.4f}\")\n\n    test_images = test_df[\"image_path\"].unique().tolist()\n    ds_test = CSIROTestDataset(test_images, transform=test_tf)\n    dl_test = DataLoader(ds_test, batch_size=1, shuffle=False, num_workers=2, pin_memory=True)\n\n    model.eval(); preds_log=[]\n    with torch.no_grad():\n        for xb in dl_test:\n            x = xb[0]\n            if use_tta:\n                pb = tta_predict(model, x, n=4)\n                preds_log.append(pb[None, :])\n            else:\n                preds_log.append(model(x.unsqueeze(0).to(device)).cpu().numpy())\n    preds_log = np.concatenate(preds_log, axis=0)\n    preds = np.expm1(preds_log); preds = np.clip(preds, 0, None)\n    preds_cnn = pd.DataFrame(preds, index=test_images, columns=target_names)\n    return preds_cnn\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-09T04:24:56.751246Z","iopub.execute_input":"2025-11-09T04:24:56.751527Z","iopub.status.idle":"2025-11-09T04:24:56.784482Z","shell.execute_reply.started":"2025-11-09T04:24:56.751507Z","shell.execute_reply":"2025-11-09T04:24:56.783519Z"}},"outputs":[],"execution_count":null},{"id":"d2ffd000","cell_type":"code","source":"def learn_stacking_coeffs(oof_tab_log, oof_cnn_log, y_wide_df, target_names):\n    mask_valid = (np.abs(oof_cnn_log).sum(axis=1) > 0)\n    if mask_valid.sum() < 50:\n        print(\"[STACK] Muy pocas filas válidas; uso blending fijo.\")\n        return None\n    coefs = {}\n    for j, t in enumerate(target_names):\n        x_tab  = np.expm1(oof_tab_log[mask_valid, j])\n        x_cnn  = np.expm1(oof_cnn_log[mask_valid, j])\n        y_true = y_wide_df.values[mask_valid, j]\n        Xs = np.column_stack([x_tab, x_cnn])\n        ridge = Ridge(alpha=1e-6, fit_intercept=True).fit(Xs, y_true)\n        coefs[t] = (float(ridge.coef_[0]), float(ridge.coef_[1]), float(ridge.intercept_))\n    return coefs\n\ndef apply_stacking(preds_tab_df, preds_cnn_df, coefs, target_names):\n    preds = preds_tab_df.copy()\n    for t in target_names:\n        a,b,c = coefs[t]\n        preds[t] = a*preds_tab_df[t] + b*preds_cnn_df[t] + c\n    return preds\n\ndef blend_total_consistency(preds_df, y_wide_df, target_names, gamma_soft=0.7):\n    total_from_parts = preds_df[\"Dry_Green_g\"] + preds_df[\"Dry_Dead_g\"] + preds_df[\"Dry_Clover_g\"]\n    preds_df[\"Dry_Total_g\"] = gamma_soft*preds_df[\"Dry_Total_g\"] + (1-gamma_soft)*total_from_parts\n    return preds_df\n\ndef make_submission_from_predictions(test_df: pd.DataFrame,\n                                     preds_df: pd.DataFrame,\n                                     target_names) -> pd.DataFrame:\n    preds_long = (preds_df.reset_index()\n                  .melt(id_vars=\"index\", var_name=\"target_name\", value_name=\"pred\")\n                  .rename(columns={\"index\":\"image_path\"}))\n    test_pred = test_df.merge(preds_long, on=[\"image_path\",\"target_name\"], how=\"left\")\n    sub = test_pred[[\"sample_id\",\"pred\"]].rename(columns={\"pred\":\"target\"})\n    assert sub.shape[0] == test_df.shape[0], \"Submission size must match test size\"\n    assert np.isfinite(sub[\"target\"]).all(), \"NaN/Inf in submission\"\n    sub[\"target\"] = sub[\"target\"].clip(lower=0)\n    return sub\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-09T04:24:56.785436Z","iopub.execute_input":"2025-11-09T04:24:56.785692Z","iopub.status.idle":"2025-11-09T04:24:56.808446Z","shell.execute_reply.started":"2025-11-09T04:24:56.785671Z","shell.execute_reply":"2025-11-09T04:24:56.807640Z"}},"outputs":[],"execution_count":null},{"id":"3c3a5320","cell_type":"code","source":"try:\n    print(\">> Training TABULAR ...\")\n    preds_tab, oof_tab_log = predict_tabular(train, y_wide, test, target_names)\n\n    print(\"\\n>> CNN OOF onefold (for stacking) ...\")\n    oof_cnn_log = cnn_oof_onefold(y_wide, target_names, epochs=4, batch_size=16, lr=1e-3, wd=1e-5)\n\n    print(\"\\n>> Training CNN (final) ...\")\n    preds_cnn = train_cnn_predict(y_wide, test, target_names,\n                                  epochs=8, batch_size=16, lr=1e-3, wd=1e-5, use_tta=True)\n\n    print(\"\\n>> Learning stacking coefficients ...\")\n    coefs = learn_stacking_coeffs(oof_tab_log, oof_cnn_log, y_wide, target_names)\n\n    if coefs is None:\n        print(\">> Applying fixed blending fallback ...\")\n        ALPHA = {\"Dry_Green_g\":0.50, \"Dry_Dead_g\":0.50, \"Dry_Clover_g\":0.50,\n                 \"GDM_g\":0.60, \"Dry_Total_g\":0.70}\n        preds_ens = preds_tab.copy()\n        for t in target_names:\n            a = ALPHA[t]\n            preds_ens[t] = a*preds_tab[t] + (1-a)*preds_cnn[t]\n    else:\n        print(\">> Applying learned stacking ...\")\n        preds_ens = apply_stacking(preds_tab, preds_cnn, coefs, target_names)\n\n    print(\">> Soft total consistency (0.7 direct + 0.3 parts) ...\")\n    preds_ens = blend_total_consistency(preds_ens, y_wide, target_names, gamma_soft=0.7)\n\n    print(\">> Building submission ...\")\n    submission = make_submission_from_predictions(test, preds_ens, target_names)\n    print(\"Submission ready. Rows:\", len(submission))\n    display(submission.head())\n\nexcept Exception as e:\n    print(\"ERROR in pipeline; using fallback based on test. Error:\", repr(e))\n    submission = test[[\"sample_id\"]].copy()\n    submission[\"target\"] = float(train[\"target\"].mean())\n\nassert submission.shape[0] == test.shape[0], \"Submission size mismatch\"\nassert np.isfinite(submission[\"target\"]).all(), \"NaN/Inf in submission\"\nsubmission[\"target\"] = submission[\"target\"].clip(lower=0)\n\nsubmission.to_csv(\"submission.csv\", index=False)\nprint(\"submission.csv written.\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-09T04:24:56.809850Z","iopub.execute_input":"2025-11-09T04:24:56.810195Z","iopub.status.idle":"2025-11-09T04:32:05.743846Z","shell.execute_reply.started":"2025-11-09T04:24:56.810163Z","shell.execute_reply":"2025-11-09T04:32:05.742854Z"}},"outputs":[],"execution_count":null}]}