{"metadata":{"kernelspec":{"name":"python3","display_name":"Python 3","language":"python"},"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":112509,"databundleVersionId":14254895,"sourceType":"competition"},{"sourceId":4534,"sourceType":"modelInstanceVersion","isSourceIdPinned":true,"modelInstanceId":3326,"modelId":986},{"sourceId":268942,"sourceType":"modelInstanceVersion","isSourceIdPinned":true,"modelInstanceId":230141,"modelId":251887}],"dockerImageVersionId":31193,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"id":"5fe27465","cell_type":"markdown","source":"# CSIRO Biomass — **T4 Optimized** (AMP + channels_last + TF32) + Ensemble","metadata":{}},{"id":"42a5b949","cell_type":"markdown","source":"\n**Pipeline:**\n- Tabular **HGB** sobre features sencillas de imagen (RGB/HSV/LAB + textura).\n- **Small CNN** (targets en log1p) + *consistency loss* en escala original para `Dry_Total_g`.\n- **DINOv2 Base** congelado (518px) → embeddings → **Ridge** por target.\n- **SigLIP 384** congelado → embeddings → **Ridge** por target.\n- **Blending 4‑vías** (Tabular, CNN, DINO, SigLIP) + consistencia suave del total.\n- `submission.csv` sin depender de `sample_submission.csv`.","metadata":{}},{"id":"404209bc","cell_type":"code","source":"\nimport os, glob, random\nfrom pathlib import Path\nimport numpy as np, pandas as pd\nfrom PIL import Image, ImageOps\nfrom IPython.display import display\n\nfrom sklearn.model_selection import KFold\nfrom sklearn.ensemble import HistGradientBoostingRegressor\nfrom sklearn.linear_model import Ridge\nfrom sklearn.preprocessing import StandardScaler\n\nimport torch\nfrom torch import nn\nfrom torch.utils.data import Dataset, DataLoader\nimport torchvision.transforms as T\n\nimport timm\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\")\nprint(\"Device:\", device)\n\ntorch.set_float32_matmul_precision(\"high\")\nif torch.cuda.is_available():\n    torch.backends.cuda.matmul.allow_tf32 = True\n    torch.backends.cudnn.allow_tf32 = True\n    torch.backends.cudnn.benchmark = True\n    CHANNELS_LAST = True\nelse:\n    CHANNELS_LAST = False\n\nAMP_ENABLED = torch.cuda.is_available()\nCACHE_DIR = Path(\"/kaggle/working\"); CACHE_DIR.mkdir(parents=True, exist_ok=True)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-10T02:06:06.030147Z","iopub.execute_input":"2025-11-10T02:06:06.030393Z","iopub.status.idle":"2025-11-10T02:06:20.764708Z","shell.execute_reply.started":"2025-11-10T02:06:06.030359Z","shell.execute_reply":"2025-11-10T02:06:20.763861Z"}},"outputs":[],"execution_count":null},{"id":"9133c01c","cell_type":"code","source":"\ntrain = pd.read_csv(COMP_DIR / \"train.csv\")\ntest  = pd.read_csv(COMP_DIR / \"test.csv\")\nprint(\"train shape:\", train.shape, \"test shape:\", test.shape)\ndisplay(train.head(3)); display(test.head(3))\n\ntarget_names = sorted(train[\"target_name\"].unique())\ny_wide = (train.pivot(index=\"image_path\", columns=\"target_name\", values=\"target\").loc[:, target_names])\nTARGET_WEIGHTS = {\"Dry_Green_g\":0.1,\"Dry_Dead_g\":0.1,\"Dry_Clover_g\":0.1,\"GDM_g\":0.2,\"Dry_Total_g\":0.5}\nW_VEC = np.array([TARGET_WEIGHTS[t] for t in target_names], dtype=np.float32)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-10T02:06:20.766267Z","iopub.execute_input":"2025-11-10T02:06:20.766471Z","iopub.status.idle":"2025-11-10T02:06:20.837705Z","shell.execute_reply.started":"2025-11-10T02:06:20.766455Z","shell.execute_reply":"2025-11-10T02:06:20.836762Z"}},"outputs":[],"execution_count":null},{"id":"b0f47b4e","cell_type":"code","source":"\ndef normalize_2to1_and_white_balance(img: Image.Image) -> Image.Image:\n    w, h = img.size\n    if h > w:\n        img = img.rotate(90, expand=True)\n        w, h = img.size\n    target_ratio = 2.0\n    cur_ratio = w / h if h else 2.0\n    if abs(cur_ratio - target_ratio) > 1e-3:\n        if cur_ratio < target_ratio:\n            new_w = int(round(h * target_ratio))\n            pad = (new_w - w) // 2\n            img = ImageOps.expand(img, border=(pad, 0, new_w - w - pad, 0), fill=(0, 0, 0))\n        else:\n            new_h = int(round(w / target_ratio))\n            pad = (new_h - h) // 2\n            img = ImageOps.expand(img, border=(0, pad, 0, new_h - h - pad), fill=(0, 0, 0))\n    arr = np.asarray(img).astype(np.float32)\n    if arr.ndim == 3 and arr.shape[2] == 3:\n        means = arr.reshape(-1, 3).mean(0) + 1e-6\n        gm = float(np.mean(means))\n        scale = gm / means\n        arr = np.clip(arr * scale, 0, 255).astype(np.uint8)\n        img = Image.fromarray(arr, mode=\"RGB\")\n    return img\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-10T02:06:20.838613Z","iopub.execute_input":"2025-11-10T02:06:20.839410Z","iopub.status.idle":"2025-11-10T02:06:20.847351Z","shell.execute_reply.started":"2025-11-10T02:06:20.839377Z","shell.execute_reply":"2025-11-10T02:06:20.846460Z"}},"outputs":[],"execution_count":null},{"id":"ca32c0d7","cell_type":"code","source":"\ndef 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,\"std_R\":0.0,\"std_G\":0.0,\"std_B\":0.0,\n             \"excess_green\":0.0,\"mean_gray\":0.0,\"mean_g_fraction\":0.0,\"prop_green_pixels\":0.0,\"p90_excess_green\":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    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\ndef predict_tabular(train_df, y_wide_df, test_df, target_names):\n    train_images = y_wide_df.index.tolist()\n    Xtr = pd.DataFrame([dict(image_path=r, **extract_image_features(r)) for r in train_images]).set_index(\"image_path\")\n    Xtr = safe_fill(Xtr)\n    X = Xtr.values\n    Y = y_wide_df[target_names].values\n    Ylog = np.log1p(Y)\n\n    kf = KFold(n_splits=5, shuffle=True, random_state=42)\n    oof = np.zeros_like(Ylog)\n    for tr_idx, va_idx in kf.split(X):\n        X_tr, X_va = X[tr_idx], X[va_idx]\n        Y_tr, Y_va = Ylog[tr_idx], Ylog[va_idx]\n        pred_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(max_depth=4, learning_rate=0.1, max_iter=400, random_state=42)\n            hgb.fit(X_tr, Y_tr[:,j], sample_weight=w)\n            pred_va[:,j] = hgb.predict(X_va)\n        oof[va_idx] = pred_va\n\n    calib = {}\n    for j,t in enumerate(target_names):\n        r = Ridge(alpha=1e-6).fit(np.expm1(oof[:,[j]]), np.expm1(Ylog[:,j:j+1]))\n        calib[t] = (float(r.coef_[0][0]), float(r.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(max_depth=4, learning_rate=0.1, max_iter=400, random_state=42)\n        hgb.fit(X, Ylog[:,j], sample_weight=w)\n        models.append(hgb)\n\n    test_images = test_df[\"image_path\"].unique().tolist()\n    Xts = pd.DataFrame([dict(image_path=r, **extract_image_features(r)) for r in test_images]).set_index(\"image_path\")\n    Xts = safe_fill(Xts)\n    Xte = Xts.values\n    plog = np.column_stack([m.predict(Xte) for m in models])\n    P = np.expm1(plog); P = np.clip(P, 0, None)\n    for j,t in enumerate(target_names):\n        a,b = calib[t]; P[:,j] = a*P[:,j] + b\n    return pd.DataFrame(P, index=test_images, columns=target_names)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-10T02:06:20.848096Z","iopub.execute_input":"2025-11-10T02:06:20.848380Z","iopub.status.idle":"2025-11-10T02:06:20.866751Z","shell.execute_reply.started":"2025-11-10T02:06:20.848353Z","shell.execute_reply":"2025-11-10T02:06:20.866075Z"}},"outputs":[],"execution_count":null},{"id":"6b66765a","cell_type":"code","source":"\n_pre_norm = T.Lambda(normalize_2to1_and_white_balance)\n\ndef get_transforms():\n    norm_imagenet = T.Normalize(mean=(0.485,0.456,0.406), std=(0.229,0.224,0.225))\n    train_tf = T.Compose([_pre_norm, T.Resize((224,224)), T.RandomHorizontalFlip(0.5), T.ToTensor(), norm_imagenet])\n    test_tf  = T.Compose([_pre_norm, T.Resize((224,224)), T.ToTensor(), norm_imagenet])\n    return train_tf, test_tf\n\nclass CSIROTrainDataset(Dataset):\n    def __init__(self, image_paths, targets_log, transform=None, root_dir=COMP_DIR):\n        self.image_paths = image_paths; self.targets_log = targets_log; self.transform = transform; self.root=root_dir\n    def __len__(self): return len(self.image_paths)\n    def __getitem__(self, i):\n        pth = self.root / self.image_paths[i]\n        try: im = Image.open(pth).convert(\"RGB\")\n        except Exception: im = Image.new(\"RGB\",(224,224),(0,0,0))\n        if self.transform: im = self.transform(im)\n        y = torch.tensor(self.targets_log[i], dtype=torch.float32); return im, y\n\nclass CSIROTestDataset(Dataset):\n    def __init__(self, image_paths, transform=None, root_dir=COMP_DIR):\n        self.image_paths = image_paths; self.transform = transform; self.root=root_dir\n    def __len__(self): return len(self.image_paths)\n    def __getitem__(self, i):\n        pth = self.root / self.image_paths[i]\n        try: im = Image.open(pth).convert(\"RGB\")\n        except Exception: im = Image.new(\"RGB\",(224,224),(0,0,0))\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\n@torch.no_grad()\ndef _tta_predict(model, image_tensor, n=4):\n    x = image_tensor.unsqueeze(0)\n    if CHANNELS_LAST:\n        x = x.to('cuda' if torch.cuda.is_available() else 'cpu', non_blocking=True).to(memory_format=torch.channels_last)\n    else:\n        x = x.to('cuda' if torch.cuda.is_available() else 'cpu', non_blocking=True)\n    outs = []\n    with torch.cuda.amp.autocast(enabled=AMP_ENABLED):\n        outs.append(model(x).cpu().float().numpy()[0])\n        outs.append(model(torch.flip(x,dims=[3])).cpu().float().numpy()[0])\n        outs.append(model(torch.flip(x,dims=[2])).cpu().float().numpy()[0])\n        outs.append(model(torch.rot90(x,k=1,dims=[2,3])).cpu().float().numpy()[0])\n    return sum(outs[:n]) / n\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[target_names].values\n    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, persistent_workers=True)\n\n    model = SmallCNN(n_outputs=len(target_names)).to('cuda' if torch.cuda.is_available() else 'cpu')\n    if CHANNELS_LAST: model = model.to(memory_format=torch.channels_last)\n    scaler = torch.cuda.amp.GradScaler(enabled=AMP_ENABLED)\n    opt = torch.optim.Adam(model.parameters(), lr=lr, weight_decay=wd)\n\n    w = torch.tensor(W_VEC, device='cuda' if torch.cuda.is_available() else 'cpu').view(1,-1)\n    iT  = target_names.index(\"Dry_Total_g\")\n    iG  = target_names.index(\"Dry_Green_g\")\n    iD  = target_names.index(\"Dry_Dead_g\")\n    iC  = target_names.index(\"Dry_Clover_g\")\n    lambda_cons = 0.3\n\n    model.train()\n    for ep in range(epochs):\n        run = 0.0\n        for xb, yb in dl:\n            if CHANNELS_LAST:\n                xb = xb.to('cuda' if torch.cuda.is_available() else 'cpu', non_blocking=True).to(memory_format=torch.channels_last)\n            else:\n                xb = xb.to('cuda' if torch.cuda.is_available() else 'cpu', non_blocking=True)\n            yb = yb.to('cuda' if torch.cuda.is_available() else 'cpu', non_blocking=True)\n\n            opt.zero_grad(set_to_none=True)\n            with torch.cuda.amp.autocast(enabled=AMP_ENABLED):\n                pred = model(xb)  # log1p\n                base = ((pred - yb)**2 * w).mean()\n                pred_lin  = torch.expm1(pred)\n                cons      = ((pred_lin[:, iT] - (pred_lin[:, iG] + pred_lin[:, iD] + pred_lin[:, iC]))**2).mean()\n                loss = base + lambda_cons*cons\n\n            scaler.scale(loss).backward()\n            scaler.step(opt)\n            scaler.update()\n            run += loss.item() * xb.size(0)\n        print(f\"[CNN] Epoch {ep+1}/{epochs} - loss_w(log1p + cons): {run/len(ds):.4f}\")\n\n    test_images = test_df[\"image_path\"].drop_duplicates().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, persistent_workers=True)\n\n    model.eval(); preds_log=[]\n    for xb in dl_test:\n        x = xb[0]\n        if use_tta:\n            pb = _tta_predict(model, x, n=4); preds_log.append(pb[None, :])\n        else:\n            with torch.no_grad(), torch.cuda.amp.autocast(enabled=AMP_ENABLED):\n                if CHANNELS_LAST:\n                    x = x.unsqueeze(0).to('cuda' if torch.cuda.is_available() else 'cpu', non_blocking=True).to(memory_format=torch.channels_last)\n                else:\n                    x = x.unsqueeze(0).to('cuda' if torch.cuda.is_available() else 'cpu', non_blocking=True)\n                preds_log.append(model(x).cpu().float().numpy())\n    preds_log = np.concatenate(preds_log, axis=0)\n    preds = np.expm1(preds_log); preds = np.clip(preds, 0, None)\n    return pd.DataFrame(preds, index=test_images, columns=target_names)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-10T02:06:20.867745Z","iopub.execute_input":"2025-11-10T02:06:20.868040Z","iopub.status.idle":"2025-11-10T02:06:20.892344Z","shell.execute_reply.started":"2025-11-10T02:06:20.868016Z","shell.execute_reply":"2025-11-10T02:06:20.891672Z"}},"outputs":[],"execution_count":null},{"id":"6c215a90","cell_type":"code","source":"\ndef load_state_dict_flex(model, wpath, strict=False, key_candidates=(\"state_dict\",\"model\",\"weights\")):\n    print(f\"Intentando cargar pesos desde: {os.path.basename(wpath)}\")\n    if wpath.endswith(\".safetensors\"):\n        from safetensors.torch import load_file as safe_load_file\n        sd = safe_load_file(wpath)\n    else:\n        sd = torch.load(wpath, map_location=\"cpu\", weights_only=False)\n    if isinstance(sd, dict):\n        for k in key_candidates:\n            if k in sd and isinstance(sd[k], dict):\n                sd = sd[k]; break\n        if all(k.startswith(\"module.\") for k in sd.keys()):\n            sd = {k[len('module.'):]: v for k,v in sd.items()}\n        missing, unexpected = model.load_state_dict(sd, strict=strict)\n        print(f\"Cargados con strict={strict}. Missing: {len(missing)}, Unexpected: {len(unexpected)}\")\n    else:\n        raise RuntimeError(\"El archivo de pesos no contiene un state_dict válido.\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-10T02:06:20.893248Z","iopub.execute_input":"2025-11-10T02:06:20.893579Z","iopub.status.idle":"2025-11-10T02:06:20.907409Z","shell.execute_reply.started":"2025-11-10T02:06:20.893555Z","shell.execute_reply":"2025-11-10T02:06:20.906515Z"}},"outputs":[],"execution_count":null},{"id":"338e6f8e","cell_type":"code","source":"\nDINO_DIR = \"/kaggle/input/dinov2/pytorch/base/1\"\nassert os.path.exists(DINO_DIR), \"Falta DINOv2 Base\"\n\nmodel_dino = timm.create_model(\"vit_base_patch14_dinov2\", pretrained=False, num_classes=0, img_size=518)\nmodel_dino.eval().to('cuda' if torch.cuda.is_available() else 'cpu')\nif torch.cuda.is_available():\n    model_dino.half()\n\ndino_w = []\nfor ext in (\"*.safetensors\",\"*.pt\",\"*.bin\"):\n    dino_w += glob.glob(os.path.join(DINO_DIR, \"**\", ext), recursive=True)\nif dino_w:\n    load_state_dict_flex(model_dino, dino_w[0], strict=False)\n\nIMG_SIZE_DINO = 518\ndino_tf = T.Compose([T.Lambda(normalize_2to1_and_white_balance), T.Resize((IMG_SIZE_DINO, IMG_SIZE_DINO)),\n                     T.ToTensor(), T.Normalize((0.485,0.456,0.406),(0.229,0.224,0.225))])\n\nSIGLIP_DIR_CAND = [\"/kaggle/input/google-siglip-so400m-patch14-384/transformers/default/1\",\"/kaggle/input/siglip-so400m-384\",\"/kaggle/input/siglip-vitb16-384\"]\nSIGLIP_DIR = next((p for p in SIGLIP_DIR_CAND if os.path.exists(p)), None)\nassert SIGLIP_DIR is not None, \"Agrega el recurso de SigLIP y ajusta rutas si difieren.\"\n\narch_candidates = [\"vit_so400m_patch14_siglip_384\",\"vit_base_patch14_siglip_384\",\"vit_large_patch14_384\"]\nsiglip_model=None\nfor cand in arch_candidates:\n    try:\n        siglip_model = timm.create_model(cand, pretrained=False, num_classes=0)\n        arch_siglip=cand; break\n    except Exception: pass\nassert siglip_model is not None, \"No pude crear SigLIP con timm\"\nsig_w=[]\nfor ext in (\"*.safetensors\",\"*.pt\",\"*.bin\"):\n    sig_w += glob.glob(os.path.join(SIGLIP_DIR, \"**\", ext), recursive=True)\nif sig_w:\n    load_state_dict_flex(siglip_model, sig_w[0], strict=False)\nsiglip_model.eval().to('cuda' if torch.cuda.is_available() else 'cpu')\nif torch.cuda.is_available():\n    siglip_model.half()\n\nIMG_SIZE_SIG = 384\nsiglip_tf = T.Compose([T.Lambda(normalize_2to1_and_white_balance), T.Resize((IMG_SIZE_SIG, IMG_SIZE_SIG)),\n                       T.ToTensor(), T.Normalize((0.485,0.456,0.406),(0.229,0.224,0.225))])\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-10T02:08:42.533084Z","iopub.execute_input":"2025-11-10T02:08:42.533675Z","iopub.status.idle":"2025-11-10T02:08:55.065080Z","shell.execute_reply.started":"2025-11-10T02:08:42.533651Z","shell.execute_reply":"2025-11-10T02:08:55.064457Z"}},"outputs":[],"execution_count":null},{"id":"a96c5bb0","cell_type":"code","source":"class ImgOnlyDS(Dataset):\n    def __init__(self, rel_paths, tf): self.rel=rel_paths; self.tf=tf\n    def __len__(self): return len(self.rel)\n    def __getitem__(self,i):\n        p = COMP_DIR / self.rel[i]\n        try: im = Image.open(p).convert(\"RGB\")\n        except: im = Image.new(\"RGB\",(512,256),(0,0,0))\n        return self.tf(im)\n\ndef cache_or_compute(npy_path, fn):\n    from pathlib import Path\n    npy_path = Path(npy_path)\n    if npy_path.exists():\n        return np.load(npy_path)\n    arr = fn()\n    np.save(npy_path, arr)\n    return arr\n\ndef dino_embeddings(paths, bs=8):\n    dl = DataLoader(ImgOnlyDS(paths, dino_tf), batch_size=bs, shuffle=False,\n                    num_workers=2, pin_memory=True, persistent_workers=True)\n    out=[]\n    with torch.no_grad(), torch.cuda.amp.autocast(enabled=AMP_ENABLED):\n        for xb in dl:\n            if CHANNELS_LAST:\n                xb = xb.to('cuda' if torch.cuda.is_available() else 'cpu', non_blocking=True).to(memory_format=torch.channels_last)\n            else:\n                xb = xb.to('cuda' if torch.cuda.is_available() else 'cpu', non_blocking=True)\n            z = model_dino(xb); z = z[0] if isinstance(z,(list,tuple)) else z\n            out.append(z.detach().cpu().float().numpy())\n    return np.concatenate(out,0)\n\ndef siglip_embeddings(paths, bs=16):\n    dl = DataLoader(ImgOnlyDS(paths, siglip_tf), batch_size=bs, shuffle=False,\n                    num_workers=2, pin_memory=True, persistent_workers=True)\n    out=[]\n    with torch.no_grad(), torch.cuda.amp.autocast(enabled=AMP_ENABLED):\n        for xb in dl:\n            if CHANNELS_LAST:\n                xb = xb.to('cuda' if torch.cuda.is_available() else 'cpu', non_blocking=True).to(memory_format=torch.channels_last)\n            else:\n                xb = xb.to('cuda' if torch.cuda.is_available() else 'cpu', non_blocking=True)\n            z = siglip_model(xb); z = z[0] if isinstance(z,(list,tuple)) else z\n            out.append(z.detach().cpu().float().numpy())\n    return np.concatenate(out,0)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-10T02:09:16.699067Z","iopub.execute_input":"2025-11-10T02:09:16.699714Z","iopub.status.idle":"2025-11-10T02:09:16.709676Z","shell.execute_reply.started":"2025-11-10T02:09:16.699688Z","shell.execute_reply":"2025-11-10T02:09:16.709027Z"}},"outputs":[],"execution_count":null},{"id":"2d494f1f","cell_type":"code","source":"\ntrain_imgs = y_wide.index.to_list()\ntest_imgs  = test[\"image_path\"].drop_duplicates().to_list()\n\nE_train = cache_or_compute(\"/kaggle/working/E_train_dino.npy\", lambda: dino_embeddings(train_imgs, bs=8))\nE_test  = cache_or_compute(\"/kaggle/working/E_test_dino.npy\",  lambda: dino_embeddings(test_imgs,  bs=8))\n\nE_train_sig = cache_or_compute(\"/kaggle/working/E_train_sig.npy\", lambda: siglip_embeddings(train_imgs, bs=16))\nE_test_sig  = cache_or_compute(\"/kaggle/working/E_test_sig.npy\",  lambda: siglip_embeddings(test_imgs,  bs=16))\n\nsc_d = StandardScaler(); EtrS = sc_d.fit_transform(E_train); EteS = sc_d.transform(E_test)\nsc_s = StandardScaler(); EtrS_sig = sc_s.fit_transform(E_train_sig); EteS_sig = sc_s.transform(E_test_sig)\nY = y_wide[target_names].values\n\npreds_dino = np.zeros((len(test_imgs), len(target_names)), dtype=np.float32)\npreds_sig  = np.zeros((len(test_imgs), len(target_names)), dtype=np.float32)\nfor j,t in enumerate(target_names):\n    w = np.full(len(EtrS), TARGET_WEIGHTS[t], dtype=np.float32)\n    rd = Ridge(alpha=1.0).fit(EtrS, Y[:,j], sample_weight=w)\n    rs = Ridge(alpha=1.0).fit(EtrS_sig, Y[:,j], sample_weight=w)\n    preds_dino[:,j] = rd.predict(EteS)\n    preds_sig[:,j]  = rs.predict(EteS_sig)\n\npreds_dino_df = pd.DataFrame(np.clip(preds_dino,0,None), index=test_imgs, columns=target_names)\npreds_siglip_df = pd.DataFrame(np.clip(preds_sig,0,None), index=test_imgs, columns=target_names)\n\nprint(\">> Entrenando TABULAR (HGB) ...\")\npreds_tab = predict_tabular(train, y_wide, test, target_names)\n\nprint(\"\\\\n>> Entrenando Small CNN (T4-AMP) ...\")\npreds_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\nBLEND4 = {\n    \"Dry_Green_g\": (0.30, 0.25, 0.25, 0.20),\n    \"Dry_Dead_g\":  (0.30, 0.25, 0.25, 0.20),\n    \"Dry_Clover_g\":(0.30, 0.25, 0.25, 0.20),\n    \"GDM_g\":       (0.30, 0.30, 0.25, 0.15),\n    \"Dry_Total_g\": (0.25, 0.25, 0.30, 0.20),\n}\n\npreds_ens = preds_siglip_df.copy()\nfor t in target_names:\n    a,b,c,d = BLEND4[t]\n    preds_ens[t] = (\n        a*preds_tab.loc[test_imgs, t].values +\n        b*preds_cnn.loc[test_imgs, t].values +\n        c*preds_dino_df.loc[test_imgs, t].values +\n        d*preds_siglip_df.loc[test_imgs, t].values\n    )\n\ntotal_parts = preds_ens[\"Dry_Green_g\"] + preds_ens[\"Dry_Dead_g\"] + preds_ens[\"Dry_Clover_g\"]\npreds_ens[\"Dry_Total_g\"] = 0.7*preds_ens[\"Dry_Total_g\"] + 0.3*total_parts\npreds_ens = preds_ens.clip(lower=0)\n\npreds_long = (preds_ens.reset_index()\n              .melt(id_vars=\"index\", var_name=\"target_name\", value_name=\"pred\")\n              .rename(columns={\"index\":\"image_path\"}))\nsubmission = (test[[\"sample_id\",\"image_path\",\"target_name\"]]\n              .merge(preds_long, on=[\"image_path\",\"target_name\"], how=\"left\")\n              [[\"sample_id\",\"pred\"]].rename(columns={\"pred\":\"target\"}))\n\nassert submission.shape[0] == test.shape[0], \"Submission size mismatch\"\nassert np.isfinite(submission[\"target\"]).all(), \"NaN/Inf en submission\"\nsubmission[\"target\"] = submission[\"target\"].clip(lower=0)\nsubmission.to_csv(\"submission.csv\", index=False)\nprint(\"submission.csv escrito. Filas:\", len(submission))\ndisplay(submission.head())\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-10T02:09:25.026952Z","iopub.execute_input":"2025-11-10T02:09:25.027289Z","iopub.status.idle":"2025-11-10T02:14:56.895543Z","shell.execute_reply.started":"2025-11-10T02:09:25.027269Z","shell.execute_reply":"2025-11-10T02:14:56.894562Z"}},"outputs":[],"execution_count":null}]}