{"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":14627485,"sourceType":"datasetVersion","datasetId":9343656}],"dockerImageVersionId":31234,"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-26T15:11:40.038176Z","iopub.execute_input":"2026-01-26T15:11:40.038611Z","iopub.status.idle":"2026-01-26T15:11:44.163325Z","shell.execute_reply.started":"2026-01-26T15:11:40.038581Z","shell.execute_reply":"2026-01-26T15:11:44.162357Z"}},"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\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-26T15:11:44.165599Z","iopub.execute_input":"2026-01-26T15:11:44.165960Z","iopub.status.idle":"2026-01-26T15:12:28.505063Z","shell.execute_reply.started":"2026-01-26T15:11:44.165918Z","shell.execute_reply":"2026-01-26T15:12:28.504393Z"}},"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 = \"/kaggle/input/20260126-dinov3large-cv8623-8patch\"\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-26T15:12:28.505966Z","iopub.execute_input":"2026-01-26T15:12:28.506460Z","iopub.status.idle":"2026-01-26T15:12:28.569328Z","shell.execute_reply.started":"2026-01-26T15:12:28.506433Z","shell.execute_reply":"2026-01-26T15:12:28.568469Z"}},"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-26T15:12:28.570547Z","iopub.execute_input":"2026-01-26T15:12:28.570980Z","iopub.status.idle":"2026-01-26T15:12:28.613249Z","shell.execute_reply.started":"2026-01-26T15:12:28.570944Z","shell.execute_reply":"2026-01-26T15:12:28.612705Z"}},"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-26T15:12:28.615079Z","iopub.execute_input":"2026-01-26T15:12:28.615289Z","iopub.status.idle":"2026-01-26T15:12:28.649763Z","shell.execute_reply.started":"2026-01-26T15:12:28.615269Z","shell.execute_reply":"2026-01-26T15:12:28.649241Z"}},"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-26T15:12:28.650565Z","iopub.execute_input":"2026-01-26T15:12:28.650816Z","iopub.status.idle":"2026-01-26T15:12:28.662118Z","shell.execute_reply.started":"2026-01-26T15:12:28.650795Z","shell.execute_reply":"2026-01-26T15:12:28.661522Z"}},"outputs":[],"execution_count":null},{"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-26T15:12:28.662976Z","iopub.execute_input":"2026-01-26T15:12:28.663225Z","iopub.status.idle":"2026-01-26T15:12:28.677719Z","shell.execute_reply.started":"2026-01-26T15:12:28.663176Z","shell.execute_reply":"2026-01-26T15:12:28.677002Z"}},"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-26T15:12:28.678609Z","iopub.execute_input":"2026-01-26T15:12:28.678928Z","iopub.status.idle":"2026-01-26T15:12:28.690656Z","shell.execute_reply.started":"2026-01-26T15:12:28.678899Z","shell.execute_reply":"2026-01-26T15:12:28.690079Z"}},"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-26T15:12:28.691527Z","iopub.execute_input":"2026-01-26T15:12:28.691698Z","iopub.status.idle":"2026-01-26T15:12:28.707433Z","shell.execute_reply.started":"2026-01-26T15:12:28.691682Z","shell.execute_reply":"2026-01-26T15:12:28.706791Z"}},"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-26T15:12:28.708222Z","iopub.execute_input":"2026-01-26T15:12:28.708453Z","iopub.status.idle":"2026-01-26T15:12:29.538159Z","shell.execute_reply.started":"2026-01-26T15:12:28.708433Z","shell.execute_reply":"2026-01-26T15:12:29.537540Z"}},"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.ReLU(),\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(nn.Module):\n    def __init__(self):\n        super(CSIROModel, 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-26T15:12:29.539160Z","iopub.execute_input":"2026-01-26T15:12:29.539650Z","iopub.status.idle":"2026-01-26T15:12:29.553925Z","shell.execute_reply.started":"2026-01-26T15:12:29.539626Z","shell.execute_reply":"2026-01-26T15:12:29.553330Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model = CSIROModel()\nmodel","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-26T15:12:29.554724Z","iopub.execute_input":"2026-01-26T15:12:29.554937Z","iopub.status.idle":"2026-01-26T15:12:34.022546Z","shell.execute_reply.started":"2026-01-26T15:12:29.554917Z","shell.execute_reply":"2026-01-26T15:12:34.021784Z"},"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)\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()\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, 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()\n        model = model.to(CONFIG.device)\n        model.load_state_dict(torch.load(os.path.join(CONFIG.ckpt_path, 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-26T15:12:34.023624Z","iopub.execute_input":"2026-01-26T15:12:34.024217Z","iopub.status.idle":"2026-01-26T15:13:27.306426Z","shell.execute_reply.started":"2026-01-26T15:12:34.024192Z","shell.execute_reply":"2026-01-26T15:13:27.305628Z"}},"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-26T15:13:27.309042Z","iopub.execute_input":"2026-01-26T15:13:27.309329Z","iopub.status.idle":"2026-01-26T15:13:27.314632Z","shell.execute_reply.started":"2026-01-26T15:13:27.309306Z","shell.execute_reply":"2026-01-26T15:13:27.314051Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Start Infer","metadata":{}},{"cell_type":"code","source":"test_loader = prepare_loaders(test)\n\npreds = Infer(models, test_loader)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-26T15:13:27.315351Z","iopub.execute_input":"2026-01-26T15:13:27.315571Z","iopub.status.idle":"2026-01-26T15:13:33.362085Z","shell.execute_reply.started":"2026-01-26T15:13:27.315552Z","shell.execute_reply":"2026-01-26T15:13:33.361304Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Make Submission","metadata":{}},{"cell_type":"code","source":"test.iloc[:, 1:] = preds\ntest","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-26T15:13:33.363369Z","iopub.execute_input":"2026-01-26T15:13:33.363698Z","iopub.status.idle":"2026-01-26T15:13:33.376786Z","shell.execute_reply.started":"2026-01-26T15:13:33.363669Z","shell.execute_reply":"2026-01-26T15:13:33.376114Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# # =================================================================\n# # 1. 填入校准参数 (这是最重要的一步！)\n# # =================================================================\n# # 请把你训练日志里 \"FINAL PARAMS FOR TEST SET\" 下面的数字填到这里\n# # 格式: [Scale (斜率 a), Bias (截距 b)]\n# CALIBRATION_PARAMS = {\n#     \"__Dry_Clover_g\": [1.02165, -0.08573],  # <--- 请修改这里\n#     \"__Dry_Dead_g\":   [1.02097, -0.10498],  # <--- 请修改这里\n#     \"__Dry_Green_g\":  [1.08613, -1.41034],  # <--- 请修改这里\n#     \"__Dry_Total_g\":  [1.07421, -2.04871],  # <--- 请修改这里\n#     \"__GDM_g\":        [1.07250, -1.55261],  # <--- 请修改这里\n# }\n\n# # 你的实际列名 (根据图片确认带有双下划线)\n# target_cols = list(CALIBRATION_PARAMS.keys())\n\n# print(\"Step 1: Applying Linear Calibration...\")\n\n# # =================================================================\n# # 2. 应用线性校准 (Linear Calibration)\n# # =================================================================\n# for 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\n# test","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-26T15:13:33.377642Z","iopub.execute_input":"2026-01-26T15:13:33.377860Z","iopub.status.idle":"2026-01-26T15:13:33.391114Z","shell.execute_reply.started":"2026-01-26T15:13:33.377809Z","shell.execute_reply":"2026-01-26T15:13:33.390402Z"}},"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())\n# # =======================================================","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-26T15:13:33.392054Z","iopub.execute_input":"2026-01-26T15:13:33.392559Z","iopub.status.idle":"2026-01-26T15:13:33.408166Z","shell.execute_reply.started":"2026-01-26T15:13:33.392537Z","shell.execute_reply":"2026-01-26T15:13:33.407406Z"}},"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-26T15:13:33.408885Z","iopub.execute_input":"2026-01-26T15:13:33.409091Z","iopub.status.idle":"2026-01-26T15:13:33.437734Z","shell.execute_reply.started":"2026-01-26T15:13:33.409054Z","shell.execute_reply":"2026-01-26T15:13:33.436988Z"}},"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-26T15:13:33.438820Z","iopub.execute_input":"2026-01-26T15:13:33.439110Z","iopub.status.idle":"2026-01-26T15:13:33.470640Z","shell.execute_reply.started":"2026-01-26T15:13:33.439079Z","shell.execute_reply":"2026-01-26T15:13:33.470009Z"}},"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}]}