{"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":"gpu","dataSources":[{"sourceId":112509,"databundleVersionId":14254895,"sourceType":"competition"},{"sourceId":14301608,"sourceType":"datasetVersion","datasetId":9129514},{"sourceId":14521762,"sourceType":"datasetVersion","datasetId":9274827},{"sourceId":721913,"sourceType":"modelInstanceVersion","isSourceIdPinned":true,"modelInstanceId":549203,"modelId":561907}],"dockerImageVersionId":31260,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"## Import Libs","metadata":{}},{"cell_type":"code","source":"!pip install -q --upgrade /kaggle/input/zhiyue-timm-1-0-20/timm-1.0.20-py3-none-any.whl","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T07:11:43.470139Z","iopub.execute_input":"2026-01-23T07:11:43.470678Z","iopub.status.idle":"2026-01-23T07:11:47.866207Z","shell.execute_reply.started":"2026-01-23T07:11:43.470648Z","shell.execute_reply":"2026-01-23T07:11:47.865389Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import warnings # 避免一些可以忽略的报错\nwarnings.filterwarnings('ignore')\nimport os\nimport random\nimport gc\nimport time\nimport copy\nimport sys\nimport numpy as np\nimport pandas as pd\nimport copy\n\nfrom torch.utils.data import Dataset, DataLoader\nimport torch\nimport torch.nn.functional as F\nfrom torch import nn\nimport timm\n\nfrom PIL import Image\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2\nfrom tqdm import tqdm","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T07:11:47.867715Z","iopub.execute_input":"2026-01-23T07:11:47.868062Z","iopub.status.idle":"2026-01-23T07:12:32.311874Z","shell.execute_reply.started":"2026-01-23T07:11:47.868024Z","shell.execute_reply":"2026-01-23T07:12:32.311290Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## CONFIG","metadata":{}},{"cell_type":"code","source":"class CONFIG:\n    is_debug = False\n    seed = 308\n    n_folds = 5\n\n    test_csv = \"/kaggle/input/csiro-biomass/test.csv\"\n    test_img_path = \"/kaggle/input/csiro-biomass/test\" # (1000, 2000)\n    # test_csv = \"/kaggle/input/csiro-biomass/train.csv\" # 用于测试\n    # test_img_path = \"/kaggle/input/csiro-biomass/train\" # (1000, 2000)\n    n_workers = os.cpu_count() // 2\n\n    test_batch_size = 8\n\n    ckpt_path_8patch = \"/kaggle/input/20260117-dinov3large-cv8610-8patch-mamba\"\n    ckpt_path_2patch = \"/kaggle/input/20260116-dinov3large-cv8532-2patch-mamba/pytorch/default/1\"\n\n    model_name = \"vit_large_patch16_dinov3.lvd1689m\"\n    img_size = [512, 1024]\n    per_img_size = 512\n    \"\"\"\n    tf_efficientnet_b0.ns_jft_in1k\n    edgenext_base.in21k_ft_in1k\n    convnextv2_tiny.fcmae_ft_in22k_in1k\n    vit_base_patch14_dinov2.lvd142m\n    vit_base_patch16_dinov3.lvd1689m\n    vit_large_patch16_dinov3.lvd1689m\n    \"\"\"\n\n    head_out = 5\n    DataParallel = False\n    device = torch.device(\"cuda:0\" if torch.cuda.is_available() else \"cpu\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T07:12:32.312751Z","iopub.execute_input":"2026-01-23T07:12:32.313224Z","iopub.status.idle":"2026-01-23T07:12:32.372021Z","shell.execute_reply.started":"2026-01-23T07:12:32.313191Z","shell.execute_reply":"2026-01-23T07:12:32.371257Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Set Random Seed","metadata":{}},{"cell_type":"code","source":"def set_seed(seed=308):\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(seed)\n    torch.backends.cudnn.deterministic = True\nset_seed(CONFIG.seed)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T07:12:32.373007Z","iopub.execute_input":"2026-01-23T07:12:32.373242Z","iopub.status.idle":"2026-01-23T07:12:32.404071Z","shell.execute_reply.started":"2026-01-23T07:12:32.373220Z","shell.execute_reply":"2026-01-23T07:12:32.403343Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Data Progress","metadata":{}},{"cell_type":"code","source":"test_all = pd.read_csv(CONFIG.test_csv)\ntest_all","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T07:12:32.405861Z","iopub.execute_input":"2026-01-23T07:12:32.406166Z","iopub.status.idle":"2026-01-23T07:12:32.440969Z","shell.execute_reply.started":"2026-01-23T07:12:32.406141Z","shell.execute_reply":"2026-01-23T07:12:32.440363Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"id_all = []\nfor i in range(len(test_all)):\n    row = test_all.iloc[i, :]\n    _id = row.sample_id.split(\"_\")[0]\n    id_all.append(_id)\n\nid_all = list(set(id_all))\ntest = pd.DataFrame(id_all, columns=['sample_id'])\n\ntest[\"__Dry_Clover_g\"] = -1\ntest[\"__Dry_Dead_g\"] = -1\ntest[\"__Dry_Green_g\"] = -1\ntest[\"__Dry_Total_g\"] = -1\ntest[\"__GDM_g\"] = -1\n\ntest","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T07:12:32.441822Z","iopub.execute_input":"2026-01-23T07:12:32.442128Z","iopub.status.idle":"2026-01-23T07:12:32.452992Z","shell.execute_reply.started":"2026-01-23T07:12:32.442106Z","shell.execute_reply":"2026-01-23T07:12:32.452343Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Ensemble 8patch","metadata":{}},{"cell_type":"markdown","source":"## Dataset and DataLoader","metadata":{}},{"cell_type":"code","source":"def transform(img):\n    composition = A.Compose([\n        A.Resize(CONFIG.img_size[0], CONFIG.img_size[0]),\n        A.Normalize(\n            mean=[0.485, 0.456, 0.406],  # ImageNet均值\n            std=[0.229, 0.224, 0.225]    # ImageNet标准差\n        ),\n        ToTensorV2(),\n    ])\n    return composition(image=img)[\"image\"]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T07:12:32.454199Z","iopub.execute_input":"2026-01-23T07:12:32.454622Z","iopub.status.idle":"2026-01-23T07:12:32.467420Z","shell.execute_reply.started":"2026-01-23T07:12:32.454600Z","shell.execute_reply":"2026-01-23T07:12:32.466727Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class CSIRODataset(Dataset):\n    def __init__(self, df, transform=transform):\n        super().__init__()\n        self.df = df\n        self.transform = transform\n        self.patch_h = 500\n        self.patch_w = 500\n        # 预训练模型需要的尺寸 (通常是 512 或 518)\n        self.target_size = CONFIG.img_size[0] \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.sample_id\n\n        img_path = os.path.join(CONFIG.test_img_path, img_id + \".jpg\")\n\n        img = Image.open(img_path)\n        img = np.array(img)\n        patches = []\n        for h_idx in range(2): \n            for w_idx in range(4):\n                h_start = h_idx * self.patch_h\n                w_start = w_idx * self.patch_w\n                \n                # 切片: [500, 500, 3]\n                patch = img[h_start : h_start+self.patch_h, w_start : w_start+self.patch_w, :]\n                \n                # 3. 对每个 patch 单独做 transform\n                # 注意：transform 里应该包含 Resize(512, 512) 以适配模型\n                if self.transform is not None:\n                    augmented = self.transform(patch) # 此时 transform 应该只返回 Tensor\n                    patches.append(augmented)\n                else:\n                    # 如果没有 transform，至少要转 Tensor 并调整尺寸\n                    # 建议始终提供 transform\n                    pass\n\n        img = torch.stack(patches, dim=0)\n\n        return img, img_id","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T07:12:32.468396Z","iopub.execute_input":"2026-01-23T07:12:32.468720Z","iopub.status.idle":"2026-01-23T07:12:32.481625Z","shell.execute_reply.started":"2026-01-23T07:12:32.468697Z","shell.execute_reply":"2026-01-23T07:12:32.481086Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def prepare_loaders(df):\n    \n    test_datasets = CSIRODataset(df=df, transform=transform)\n    \n    test_loader = DataLoader(test_datasets, batch_size=CONFIG.test_batch_size, num_workers=CONFIG.n_workers, shuffle=False, pin_memory=True)\n    \n    return test_loader","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T07:12:32.482569Z","iopub.execute_input":"2026-01-23T07:12:32.482897Z","iopub.status.idle":"2026-01-23T07:12:32.497068Z","shell.execute_reply.started":"2026-01-23T07:12:32.482857Z","shell.execute_reply":"2026-01-23T07:12:32.496307Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 以下代码可检查Dataset，DataLoader是否实现基本功能\ntest_loader = prepare_loaders(test)\nx_infer, x_name = next(iter(test_loader))\nprint(f\"x_infer shape : {x_infer.shape}\") # (batch_size, channels, H, W)\n\n# 删除变量，回收垃圾\ndel test_loader, x_infer, x_name\ngc.collect()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T07:12:32.497996Z","iopub.execute_input":"2026-01-23T07:12:32.498240Z","iopub.status.idle":"2026-01-23T07:12:33.351112Z","shell.execute_reply.started":"2026-01-23T07:12:32.498211Z","shell.execute_reply":"2026-01-23T07:12:33.350431Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Model","metadata":{}},{"cell_type":"code","source":"class LocalMambaBlock(nn.Module):\n    \"\"\"\n    Lightweight Mamba-style block (Gated CNN) from the reference notebook.\n    Efficiently mixes tokens with linear complexity.\n    \"\"\"\n    def __init__(self, dim, kernel_size=5, dropout=0.0):\n        super().__init__()\n        self.norm = nn.LayerNorm(dim)\n        # Depthwise conv mixes spatial information locally\n        self.dwconv = nn.Conv1d(dim, dim, kernel_size=kernel_size, padding=kernel_size // 2, groups=dim)\n        self.gate = nn.Linear(dim, dim)\n        self.proj = nn.Linear(dim, dim)\n        self.drop = nn.Dropout(dropout)\n\n    def forward(self, x):\n        # x: (Batch, Tokens, Dim)\n        shortcut = x\n        x = self.norm(x)\n        # Gating mechanism\n        g = torch.sigmoid(self.gate(x))\n        x = x * g\n        # Spatial mixing via 1D Conv (requires transpose)\n        x = x.transpose(1, 2)  # -> (B, D, N)\n        x = self.dwconv(x)\n        x = x.transpose(1, 2)  # -> (B, N, D)\n        # Projection\n        x = self.proj(x)\n        x = self.drop(x)\n        return shortcut + x\n\nclass CSIROHead(nn.Module):\n    def __init__(self, in_features):\n        super(CSIROHead, self).__init__()\n        self.fusion = nn.Sequential(\n            LocalMambaBlock(in_features, kernel_size=5, dropout=0.1),\n            LocalMambaBlock(in_features, kernel_size=5, dropout=0.1)\n        )\n        self.pool = nn.AdaptiveAvgPool1d(1)\n        self.out_head = nn.Sequential(\n            nn.Linear(in_features, in_features // 2),\n            nn.LayerNorm(in_features // 2),\n            nn.LeakyReLU(),\n            nn.Dropout(0.2), \n            nn.Linear(in_features // 2, CONFIG.head_out),\n        )\n\n    def forward(self, x):\n        _tmp = self.fusion(x)\n        _tmp_pool = self.pool(_tmp.transpose(1, 2)).flatten(1)\n        out = self.out_head(_tmp_pool)\n        return out\n\n\nclass CSIROModel_8patch(nn.Module):\n    def __init__(self):\n        super(CSIROModel_8patch, self).__init__()\n        self.backbone = timm.create_model(model_name=CONFIG.model_name, \n                                          pretrained=False,\n                                          global_pool=''\n                                          )\n\n        if \"vit\" in CONFIG.model_name:\n            if \"tiny\" in CONFIG.model_name:\n                self.in_features = 384\n            elif \"small\" in CONFIG.model_name:\n                self.in_features = 384\n            elif \"base\" in CONFIG.model_name:\n                self.in_features = 768\n            elif \"large\" in CONFIG.model_name:\n                self.in_features = 1024\n        elif \"convnext\" in CONFIG.model_name:\n            if \"tiny\" in CONFIG.model_name:\n                self.in_features = 16 * 16\n            elif \"small\" in CONFIG.model_name:\n                self.in_features = None\n            elif \"base\" in CONFIG.model_name:\n                self.in_features = 16 * 16\n            elif \"large\" in CONFIG.model_name:\n                self.in_features = None\n            if \"dino\" not in CONFIG.model_name:\n                self.backbone.head = nn.Identity()\n\n        self.num_patches = 8\n        \n        self.head = CSIROHead(self.in_features)\n        self.head.apply(self._init_weights)\n\n    def _init_weights(self, m):\n        if isinstance(m, nn.Linear):\n            torch.nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='leaky_relu', a=0.01)\n            if m.bias is not None:\n                torch.nn.init.constant_(m.bias, 0)\n        elif isinstance(m, nn.LayerNorm):\n            torch.nn.init.constant_(m.bias, 0)\n            torch.nn.init.constant_(m.weight, 1.0)\n        elif isinstance(m, nn.Conv1d):\n            torch.nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')\n            if m.bias is not None:\n                torch.nn.init.constant_(m.bias, 0)\n        \n    def forward(self, x):\n        # x shape: [Batch_Size, 8, 3, 512, 512]\n        b, n, c, h, w = x.shape\n        \n        # 1. Batch Folding: 将 Patch 维度并入 Batch 维度\n        # view shape: [Batch_Size * 8, 3, 512, 512]\n        x = x.view(b * n, c, h, w)\n        \n        # 2. Backbone 提取特征\n        features = self.backbone(x) # [Batch_Size * 8, L, in_features]\n        if \"convnext\" in CONFIG.model_name:\n            # print(f\"features shape : {features.shape}\")\n            _, n_token, _, _ = features.shape # (Batch_Size * 8, L=1024, 16, 16)\n            features = features.view(b * n, n_token, -1)\n        _, n_token, _ = features.shape\n        # print(f\"features shape : {features.shape}\")\n        \n        # 3. Unfolding: 还原回 [Batch_Size, 8, in_features]\n        features = features.reshape(b, n*n_token, -1) # [Batch_Size, 8*L, in_features]\n        # print(f\"features shape : {features.shape}\")\n        \n        # 5. Regression Head\n        output = self.head(features)\n        \n        return output","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T07:12:33.352271Z","iopub.execute_input":"2026-01-23T07:12:33.352790Z","iopub.status.idle":"2026-01-23T07:12:33.366991Z","shell.execute_reply.started":"2026-01-23T07:12:33.352765Z","shell.execute_reply":"2026-01-23T07:12:33.366252Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model = CSIROModel_8patch()\nmodel","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T07:12:33.367743Z","iopub.execute_input":"2026-01-23T07:12:33.368072Z","iopub.status.idle":"2026-01-23T07:12:37.941841Z","shell.execute_reply.started":"2026-01-23T07:12:33.368050Z","shell.execute_reply":"2026-01-23T07:12:37.941132Z"},"scrolled":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Load Models","metadata":{}},{"cell_type":"code","source":"models = []\n\npaths = os.listdir(CONFIG.ckpt_path_8patch)\npaths = sorted(paths, key=lambda x:x.split(\"_\"[0]))\n\n# paths = [\n#     \"1_CV_0.8546_Loss90.5572_epoch10.pth\",\n#     \"2_CV_0.8023_Loss174.3940_epoch9.pth\",\n#     \"3_CV_0.7629_Loss184.8674_epoch7.pth\",\n#     \"4_CV_0.8657_Loss123.6118_epoch8.pth\",\n#     \"5_CV_0.7836_Loss171.5360_epoch9.pth\"\n# ]\n\nif CONFIG.DataParallel:\n    device_ids = [0, 1]\n    for i in range(CONFIG.n_folds):\n        model = CSIROModel_8patch()\n        model = torch.nn.DataParallel(model, device_ids=device_ids)\n        model = model.cuda()\n        model.load_state_dict(torch.load(os.path.join(CONFIG.ckpt_path_8patch, paths[i])))\n        model.eval()\n        models.append(model)\n        print(f\"Fold_{paths[i]} Load Success!\")\nelse:\n    for i in range(CONFIG.n_folds):\n        model = CSIROModel_8patch()\n        model = model.to(CONFIG.device)\n        model.load_state_dict(torch.load(os.path.join(CONFIG.ckpt_path_8patch, paths[i])))\n        model.eval()\n        models.append(model)\n        print(f\"Fold_{paths[i]} Load Success!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T07:12:37.942908Z","iopub.execute_input":"2026-01-23T07:12:37.943201Z","iopub.status.idle":"2026-01-23T07:14:28.112854Z","shell.execute_reply.started":"2026-01-23T07:12:37.943169Z","shell.execute_reply":"2026-01-23T07:14:28.112153Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Infer Function","metadata":{}},{"cell_type":"code","source":"def Infer(models, test_loader):\n    y_preds = []\n    bar = tqdm(enumerate(test_loader), total=len(test_loader))\n    with torch.no_grad():\n        for step, (images, study_id) in bar:\n            \n            if CONFIG.DataParallel:\n                images = images.cuda().float()\n            else:\n                images = images.to(CONFIG.device, dtype=torch.float)\n                \n            outputs = 0\n                \n            for model in models:\n                output = model(images)\n                outputs += output\n            outputs = outputs / len(models)\n            y_preds.append(outputs.detach().cpu().numpy())\n            \n    y_preds = np.concatenate(y_preds)\n    y_preds[y_preds < 0] = 0.0\n    return y_preds","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T07:14:28.115496Z","iopub.execute_input":"2026-01-23T07:14:28.115785Z","iopub.status.idle":"2026-01-23T07:14:28.121063Z","shell.execute_reply.started":"2026-01-23T07:14:28.115763Z","shell.execute_reply":"2026-01-23T07:14:28.120349Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Start Infer","metadata":{}},{"cell_type":"code","source":"test_loader = prepare_loaders(test)\n\npreds_8patch = Infer(models, test_loader)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T07:14:28.121805Z","iopub.execute_input":"2026-01-23T07:14:28.122059Z","iopub.status.idle":"2026-01-23T07:14:34.131808Z","shell.execute_reply.started":"2026-01-23T07:14:28.122039Z","shell.execute_reply":"2026-01-23T07:14:34.131113Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Ensemble 2patch","metadata":{}},{"cell_type":"markdown","source":"## Dataset and DataLoader","metadata":{}},{"cell_type":"code","source":"# 删除\ndel test_loader, model, models\ntorch.cuda.empty_cache()\ngc.collect()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T07:14:34.132948Z","iopub.execute_input":"2026-01-23T07:14:34.133165Z","iopub.status.idle":"2026-01-23T07:14:34.721932Z","shell.execute_reply.started":"2026-01-23T07:14:34.133140Z","shell.execute_reply":"2026-01-23T07:14:34.721293Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def transform(img):\n    composition = A.Compose([\n        A.Resize(CONFIG.img_size[0], CONFIG.img_size[1]),\n        A.Normalize(\n            mean=[0.485, 0.456, 0.406],  # ImageNet均值\n            std=[0.229, 0.224, 0.225]    # ImageNet标准差\n        ),\n        ToTensorV2(),\n    ])\n    return composition(image=img)[\"image\"]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T07:14:34.723041Z","iopub.execute_input":"2026-01-23T07:14:34.723683Z","iopub.status.idle":"2026-01-23T07:14:34.727930Z","shell.execute_reply.started":"2026-01-23T07:14:34.723660Z","shell.execute_reply":"2026-01-23T07:14:34.727197Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class CSIRODataset(Dataset):\n    def __init__(self, df, transform=transform):\n        super().__init__()\n        self.df = df\n        self.transform = transform\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.sample_id\n\n        img_path = os.path.join(CONFIG.test_img_path, img_id + \".jpg\")\n\n        img = Image.open(img_path)\n        img = np.array(img)\n        if self.transform != None:\n            img = self.transform(img)\n\n        return img, img_id","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T07:14:34.729018Z","iopub.execute_input":"2026-01-23T07:14:34.729344Z","iopub.status.idle":"2026-01-23T07:14:34.742356Z","shell.execute_reply.started":"2026-01-23T07:14:34.729323Z","shell.execute_reply":"2026-01-23T07:14:34.741717Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def prepare_loaders(df):\n    \n    test_datasets = CSIRODataset(df=df, transform=transform)\n    \n    test_loader = DataLoader(test_datasets, batch_size=CONFIG.test_batch_size, num_workers=CONFIG.n_workers, shuffle=False, pin_memory=True)\n    \n    return test_loader","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T07:14:34.743837Z","iopub.execute_input":"2026-01-23T07:14:34.744167Z","iopub.status.idle":"2026-01-23T07:14:34.754532Z","shell.execute_reply.started":"2026-01-23T07:14:34.744147Z","shell.execute_reply":"2026-01-23T07:14:34.753939Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 以下代码可检查Dataset，DataLoader是否实现基本功能\ntest_loader = prepare_loaders(test)\nx_infer, x_name = next(iter(test_loader))\nprint(f\"x_infer shape : {x_infer.shape}\") # (batch_size, channels, H, W)\n\n# 删除变量，回收垃圾\ndel test_loader, x_infer, x_name\ngc.collect()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T07:14:34.755436Z","iopub.execute_input":"2026-01-23T07:14:34.756048Z","iopub.status.idle":"2026-01-23T07:14:35.315020Z","shell.execute_reply.started":"2026-01-23T07:14:34.756020Z","shell.execute_reply":"2026-01-23T07:14:35.314405Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Model","metadata":{}},{"cell_type":"code","source":"class LocalMambaBlock(nn.Module):\n    \"\"\"\n    Lightweight Mamba-style block (Gated CNN) from the reference notebook.\n    Efficiently mixes tokens with linear complexity.\n    \"\"\"\n    def __init__(self, dim, kernel_size=5, dropout=0.0):\n        super().__init__()\n        self.norm = nn.LayerNorm(dim)\n        # Depthwise conv mixes spatial information locally\n        self.dwconv = nn.Conv1d(dim, dim, kernel_size=kernel_size, padding=kernel_size // 2, groups=dim)\n        self.gate = nn.Linear(dim, dim)\n        self.proj = nn.Linear(dim, dim)\n        self.drop = nn.Dropout(dropout)\n\n    def forward(self, x):\n        # x: (Batch, Tokens, Dim)\n        shortcut = x\n        x = self.norm(x)\n        # Gating mechanism\n        g = torch.sigmoid(self.gate(x))\n        x = x * g\n        # Spatial mixing via 1D Conv (requires transpose)\n        x = x.transpose(1, 2)  # -> (B, D, N)\n        x = self.dwconv(x)\n        x = x.transpose(1, 2)  # -> (B, N, D)\n        # Projection\n        x = self.proj(x)\n        x = self.drop(x)\n        return shortcut + x\n\nclass CSIROHead(nn.Module):\n    def __init__(self, in_features):\n        super(CSIROHead, self).__init__()\n        self.fusion = nn.Sequential(\n            LocalMambaBlock(in_features, kernel_size=5, dropout=0.1),\n            LocalMambaBlock(in_features, kernel_size=5, dropout=0.1)\n        )\n        self.pool = nn.AdaptiveAvgPool1d(1)\n        self.out_head = nn.Sequential(\n            nn.Linear(in_features, in_features // 2),\n            nn.LayerNorm(in_features // 2),\n            nn.LeakyReLU(),\n            nn.Dropout(0.2), \n            nn.Linear(in_features // 2, CONFIG.head_out),\n        )\n\n    def forward(self, x):\n        _tmp = self.fusion(x)\n        _tmp_pool = self.pool(_tmp.transpose(1, 2)).flatten(1)\n        out = self.out_head(_tmp_pool)\n        return out\n\nclass CSIROModel_2patch(nn.Module):\n    def __init__(self):\n        super(CSIROModel_2patch, self).__init__()\n        self.backbone = timm.create_model(model_name=CONFIG.model_name, \n                                          pretrained=False,\n                                          global_pool='')\n        if \"tiny\" in CONFIG.model_name:\n            in_features = 384\n        elif \"small\" in CONFIG.model_name:\n            in_features = 384\n        elif \"base\" in CONFIG.model_name:\n            in_features = 768\n        elif \"large\" in CONFIG.model_name:\n            in_features = 1024\n        elif \"huge\" in CONFIG.model_name:\n            in_features = 1280\n        self.head = CSIROHead(in_features)\n        self.head.apply(self._init_weights)\n\n    def _init_weights(self, m):\n        if isinstance(m, nn.Linear):\n            torch.nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='leaky_relu', a=0.01)\n            if m.bias is not None:\n                torch.nn.init.constant_(m.bias, 0)\n        elif isinstance(m, nn.LayerNorm):\n            torch.nn.init.constant_(m.bias, 0)\n            torch.nn.init.constant_(m.weight, 1.0)\n        elif isinstance(m, nn.Conv1d):\n            torch.nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')\n            if m.bias is not None:\n                torch.nn.init.constant_(m.bias, 0)\n        \n    def forward(self, x):\n        mid = CONFIG.img_size[0]\n        x_left = x[:, :, :, :mid]\n        x_right = x[:, :, :, mid:]\n        _tmp1 = self.backbone(x_left)\n        _tmp2 = self.backbone(x_right)\n        _tmp = torch.cat([_tmp1, _tmp2], dim=1) # shape: [B, 1536]\n        output = self.head(_tmp)\n        return output","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T07:14:35.316248Z","iopub.execute_input":"2026-01-23T07:14:35.316499Z","iopub.status.idle":"2026-01-23T07:14:35.330158Z","shell.execute_reply.started":"2026-01-23T07:14:35.316470Z","shell.execute_reply":"2026-01-23T07:14:35.329473Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model = CSIROModel_2patch()\nmodel","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T07:14:35.331038Z","iopub.execute_input":"2026-01-23T07:14:35.331450Z","iopub.status.idle":"2026-01-23T07:14:39.706578Z","shell.execute_reply.started":"2026-01-23T07:14:35.331427Z","shell.execute_reply":"2026-01-23T07:14:39.705977Z"},"scrolled":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Load Models","metadata":{}},{"cell_type":"code","source":"models = []\n\npaths = os.listdir(CONFIG.ckpt_path_2patch)\npaths = sorted(paths, key=lambda x:x.split(\"_\"[0]))\n\n# paths = [\n#     \"1_CV_0.8546_Loss90.5572_epoch10.pth\",\n#     \"2_CV_0.8023_Loss174.3940_epoch9.pth\",\n#     \"3_CV_0.7629_Loss184.8674_epoch7.pth\",\n#     \"4_CV_0.8657_Loss123.6118_epoch8.pth\",\n#     \"5_CV_0.7836_Loss171.5360_epoch9.pth\"\n# ]\n\nif CONFIG.DataParallel:\n    device_ids = [0, 1]\n    for i in range(CONFIG.n_folds):\n        model = CSIROModel_2patch()\n        model = torch.nn.DataParallel(model, device_ids=device_ids)\n        model = model.cuda()\n        model.load_state_dict(torch.load(os.path.join(CONFIG.ckpt_path_2patch, paths[i])))\n        model.eval()\n        models.append(model)\n        print(f\"Fold_{paths[i]} Load Success!\")\nelse:\n    for i in range(CONFIG.n_folds):\n        model = CSIROModel_2patch()\n        model = model.to(CONFIG.device)\n        model.load_state_dict(torch.load(os.path.join(CONFIG.ckpt_path_2patch, paths[i])))\n        model.eval()\n        models.append(model)\n        print(f\"Fold_{paths[i]} Load Success!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T07:14:39.708148Z","iopub.execute_input":"2026-01-23T07:14:39.708738Z","iopub.status.idle":"2026-01-23T07:16:09.022304Z","shell.execute_reply.started":"2026-01-23T07:14:39.708714Z","shell.execute_reply":"2026-01-23T07:16:09.021463Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Infer Function","metadata":{}},{"cell_type":"code","source":"def Infer(models, test_loader):\n    y_preds = []\n    bar = tqdm(enumerate(test_loader), total=len(test_loader))\n    with torch.no_grad():\n        for step, (images, study_id) in bar:\n            \n            if CONFIG.DataParallel:\n                images = images.cuda().float()\n            else:\n                images = images.to(CONFIG.device, dtype=torch.float)\n                \n            outputs = 0\n                \n            for model in models:\n                output = model(images)\n                outputs += output\n            outputs = outputs / len(models)\n            y_preds.append(outputs.detach().cpu().numpy())\n            \n    y_preds = np.concatenate(y_preds)\n    y_preds[y_preds < 0] = 0.0\n    return y_preds","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T07:16:09.023414Z","iopub.execute_input":"2026-01-23T07:16:09.023998Z","iopub.status.idle":"2026-01-23T07:16:09.029517Z","shell.execute_reply.started":"2026-01-23T07:16:09.023973Z","shell.execute_reply":"2026-01-23T07:16:09.028707Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Start Infer","metadata":{}},{"cell_type":"code","source":"test_loader = prepare_loaders(test)\n\npreds_2patch = Infer(models, test_loader)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T07:16:09.030500Z","iopub.execute_input":"2026-01-23T07:16:09.031064Z","iopub.status.idle":"2026-01-23T07:16:10.691614Z","shell.execute_reply.started":"2026-01-23T07:16:09.031040Z","shell.execute_reply":"2026-01-23T07:16:10.690955Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Make Submission","metadata":{}},{"cell_type":"code","source":"preds = 0.7 * preds_8patch + 0.3 * preds_2patch","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T07:16:10.692756Z","iopub.execute_input":"2026-01-23T07:16:10.693007Z","iopub.status.idle":"2026-01-23T07:16:10.696977Z","shell.execute_reply.started":"2026-01-23T07:16:10.692981Z","shell.execute_reply":"2026-01-23T07:16:10.696409Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test.iloc[:, 1:] = preds\ntest","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T07:16:10.698356Z","iopub.execute_input":"2026-01-23T07:16:10.698712Z","iopub.status.idle":"2026-01-23T07:16:10.718713Z","shell.execute_reply.started":"2026-01-23T07:16:10.698691Z","shell.execute_reply":"2026-01-23T07:16:10.718157Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# =================================================================\n# 1. 填入校准参数 (这是最重要的一步！)\n# =================================================================\n# 请把你训练日志里 \"FINAL PARAMS FOR TEST SET\" 下面的数字填到这里\n# 格式: [Scale (斜率 a), Bias (截距 b)]\nCALIBRATION_PARAMS = {\n    \"__Dry_Clover_g\": [1.02668, -0.16265],  # <--- 请修改这里\n    \"__Dry_Dead_g\":   [1.04724, -0.32531],  # <--- 请修改这里\n    \"__Dry_Green_g\":  [1.09359, -1.62400],  # <--- 请修改这里\n    \"__Dry_Total_g\":  [1.08971, -2.78983],  # <--- 请修改这里\n    \"__GDM_g\":        [1.08065, -1.86469],  # <--- 请修改这里\n}\n\n# 你的实际列名 (根据图片确认带有双下划线)\ntarget_cols = list(CALIBRATION_PARAMS.keys())\n\nprint(\"Step 1: Applying Linear Calibration...\")\n\n# =================================================================\n# 2. 应用线性校准 (Linear Calibration)\n# =================================================================\nfor col in target_cols:\n    scale, bias = CALIBRATION_PARAMS[col]\n    \n    # 核心公式: New = Old * Scale + Bias\n    test[col] = test[col] * scale + bias\n    \n    # 物理约束: 截断负数 (生物量不能小于0)\n    test[col] = np.maximum(0, test[col])\n    \n    print(f\"  -> Calibrated {col}: value * {scale:.4f} + {bias:.4f}\")\n\ntest # only Linear Balance cv0.86595","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T07:16:10.719625Z","iopub.execute_input":"2026-01-23T07:16:10.719838Z","iopub.status.idle":"2026-01-23T07:16:10.731541Z","shell.execute_reply.started":"2026-01-23T07:16:10.719819Z","shell.execute_reply":"2026-01-23T07:16:10.730899Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# # 修正后的数值严格满足加法公式，且与原预测值的欧氏距离最小（即改动最小，最尊重模型的原始预测）。\n# def apply_mass_balance(df):\n#     \"\"\"\n#     对预测结果应用物理约束：\n#     1. Green + Clover = GDM\n#     2. GDM + Dead = Total\n#     注意：必须严格按照矩阵计算的顺序提取列\n#     \"\"\"\n#     # 1. 对应你 DataFrame 中的列名 (注意你的列名带有双下划线)\n#     # 矩阵计算需要的顺序: [Green, Clover, Dead, GDM, Total]\n#     col_map = [\n#         '__Dry_Green_g', \n#         '__Dry_Clover_g', \n#         '__Dry_Dead_g', \n#         '__GDM_g', \n#         '__Dry_Total_g'\n#     ]\n    \n#     # 2. 提取数据矩阵 (5, N)\n#     # 你的 test df 里包含这些列\n#     vals = df[col_map].values.T\n#     vals = np.maximum(0, vals) # 确保无负数\n    \n#     # 3. 定义约束矩阵 C (Cx = 0)\n#     C = np.array([\n#         [1, 1, 0, -1,  0], # Green + Clover - GDM = 0\n#         [0, 0, 1,  1, -1]  # Dead + GDM - Total = 0\n#     ])\n    \n#     # 4. 计算投影矩阵 P\n#     C_T = C.T\n#     inv_CCt = np.linalg.inv(C @ C_T)\n#     P = np.eye(5) - C_T @ inv_CCt @ C\n    \n#     # 5. 应用投影并修正\n#     vals_corrected = P @ vals\n#     vals_corrected = vals_corrected.T\n#     vals_corrected = np.maximum(0, vals_corrected)\n    \n#     # 6. 将修正后的数据填回 DataFrame\n#     df_corrected = df.copy()\n#     df_corrected[col_map] = vals_corrected\n    \n#     return df_corrected\n\n# print(\"Applying Mass Balance Constraint...\")\n# # 对 test 进行修正\n# test = apply_mass_balance(test)\n# print(\"Done. Example after correction:\")\n# print(test.head()) # only Mass Balance cv0.8628                          Linear + Mass cv0.866043\n# # =======================================================","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T07:16:17.759722Z","iopub.execute_input":"2026-01-23T07:16:17.760428Z","iopub.status.idle":"2026-01-23T07:16:17.775410Z","shell.execute_reply.started":"2026-01-23T07:16:17.760399Z","shell.execute_reply":"2026-01-23T07:16:17.774733Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 1. 使用 melt 将列名转换为行数据\n# id_vars: 保持不变的列 ('sample_id')\n# var_name: 原来的列名变成的新列的名字 (临时命名为 'metric')\n# value_name: 数值变成的新列的名字 (你想要的 'target')\ndf_new = test.melt(id_vars=['sample_id'], \n                   var_name='metric', \n                   value_name='target')\n\n# 2. 拼接字符串以符合图2的格式\n# 图2中的 sample_id 变成了 \"原ID + 原列名\"\ndf_new['sample_id'] = df_new['sample_id'] + df_new['metric']\n\n# 3. 只保留你需要的两列\nsub = df_new[['sample_id', 'target']]\n\n# 4. 排序：按 ID 排序，这样同一个样本的指标会挨在一起，方便检查\nsub = sub.sort_values(by='sample_id').reset_index(drop=True)\n\n# 5. 查看结果\nsub","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T07:16:18.794364Z","iopub.execute_input":"2026-01-23T07:16:18.795115Z","iopub.status.idle":"2026-01-23T07:16:18.806502Z","shell.execute_reply.started":"2026-01-23T07:16:18.795083Z","shell.execute_reply":"2026-01-23T07:16:18.805923Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"sub.to_csv('submission.csv', index=False)\n\n# 再次检查\npd.read_csv(\"/kaggle/working/submission.csv\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-23T07:16:23.131117Z","iopub.execute_input":"2026-01-23T07:16:23.131708Z","iopub.status.idle":"2026-01-23T07:16:23.145151Z","shell.execute_reply.started":"2026-01-23T07:16:23.131663Z","shell.execute_reply":"2026-01-23T07:16:23.144129Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}