{"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":293031629,"sourceType":"kernelVersion"},{"sourceId":268942,"sourceType":"modelInstanceVersion","modelInstanceId":230141,"modelId":251887},{"sourceId":692023,"sourceType":"modelInstanceVersion","modelInstanceId":524678,"modelId":538721},{"sourceId":694701,"sourceType":"modelInstanceVersion","modelInstanceId":524678,"modelId":538721},{"sourceId":703228,"sourceType":"modelInstanceVersion","modelInstanceId":524678,"modelId":538721},{"sourceId":704800,"sourceType":"modelInstanceVersion","modelInstanceId":524678,"modelId":538721},{"sourceId":704903,"sourceType":"modelInstanceVersion","modelInstanceId":524678,"modelId":538721},{"sourceId":719660,"sourceType":"modelInstanceVersion","modelInstanceId":547330,"modelId":560139},{"sourceId":727452,"sourceType":"modelInstanceVersion","modelInstanceId":524678,"modelId":538721},{"sourceId":728454,"sourceType":"modelInstanceVersion","isSourceIdPinned":true,"modelInstanceId":524678,"modelId":538721},{"sourceId":728606,"sourceType":"modelInstanceVersion","isSourceIdPinned":true,"modelInstanceId":524678,"modelId":538721},{"sourceId":729282,"sourceType":"modelInstanceVersion","isSourceIdPinned":true,"modelInstanceId":524678,"modelId":538721},{"sourceId":731393,"sourceType":"modelInstanceVersion","isSourceIdPinned":true,"modelInstanceId":524678,"modelId":538721},{"sourceId":731395,"sourceType":"modelInstanceVersion","isSourceIdPinned":true,"modelInstanceId":556925,"modelId":569487},{"sourceId":731399,"sourceType":"modelInstanceVersion","isSourceIdPinned":true,"modelInstanceId":556931,"modelId":569493},{"sourceId":731402,"sourceType":"modelInstanceVersion","isSourceIdPinned":true,"modelInstanceId":556933,"modelId":569495},{"sourceId":731054,"sourceType":"modelInstanceVersion","isSourceIdPinned":true,"modelInstanceId":524678,"modelId":538721},{"sourceId":733456,"sourceType":"modelInstanceVersion","isSourceIdPinned":true,"modelInstanceId":524678,"modelId":538721},{"sourceId":733457,"sourceType":"modelInstanceVersion","isSourceIdPinned":true,"modelInstanceId":556925,"modelId":569487},{"sourceId":732978,"sourceType":"modelInstanceVersion","isSourceIdPinned":true,"modelInstanceId":524678,"modelId":538721},{"sourceId":732979,"sourceType":"modelInstanceVersion","isSourceIdPinned":true,"modelInstanceId":556925,"modelId":569487}],"dockerImageVersionId":31193,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Prepare Test Data","metadata":{}},{"cell_type":"code","source":"cp -r /kaggle/input/csiro-biomass/test ./test  # speed up io","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-19T10:14:29.188571Z","iopub.execute_input":"2026-01-19T10:14:29.189158Z","iopub.status.idle":"2026-01-19T10:14:29.319462Z","shell.execute_reply.started":"2026-01-19T10:14:29.189121Z","shell.execute_reply":"2026-01-19T10:14:29.318702Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import gc\nimport numpy as np\nimport pandas as pd\nfrom PIL import Image\nfrom tqdm.auto import tqdm\ntqdm.pandas()\n\nDATA_ROOT = '/kaggle/input/csiro-biomass/'\ntest_df = pd.read_csv(DATA_ROOT + 'test.csv')\n\ntest_df['target'] = 0.0\ntest_df[['sample_id_prefix', 'sample_id_suffix']] = test_df.sample_id.str.split('__', expand=True)\n\ncols = ['sample_id_prefix', 'image_path']\nagg_test_df = test_df.groupby(cols).apply(lambda df: df.set_index('target_name').target, include_groups=False)\nagg_test_df.reset_index(inplace=True)\nagg_test_df.columns.name = None\n\nif agg_test_df.shape[0] == 1:\n    agg_test_df = pd.concat([agg_test_df] * 2, axis=0, ignore_index=True)\n\n# agg_test_df['image'] = agg_test_df.image_path.progress_apply(\n#     lambda path: Image.open(DATA_ROOT + path).convert('RGB')\n# )\n\nhalf_num = agg_test_df.shape[0] // 2\nagg_test_df.iloc[:half_num].to_pickle(f'agg_test_df_part0.pkl')\nagg_test_df.iloc[half_num:].to_pickle(f'agg_test_df_part1.pkl')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-19T10:14:29.490878Z","iopub.execute_input":"2026-01-19T10:14:29.491337Z","iopub.status.idle":"2026-01-19T10:14:29.856769Z","shell.execute_reply.started":"2026-01-19T10:14:29.491312Z","shell.execute_reply":"2026-01-19T10:14:29.855949Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def merge_submissions(out_fname):\n    sub_df = pd.concat([\n        pd.read_csv('submission_part0.csv'),\n        pd.read_csv('submission_part1.csv')\n    ])\n    sub_df.drop_duplicates(subset=['sample_id'], inplace=True)\n    sub_df.to_csv(out_fname, index=False)\n\n    with open(out_fname) as f:\n        for i, line in enumerate(f):\n            print(line.strip())\n            if i >= 5: break","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-19T12:02:28.000845Z","iopub.execute_input":"2026-01-19T12:02:28.001151Z","iopub.status.idle":"2026-01-19T12:02:28.006921Z","shell.execute_reply.started":"2026-01-19T12:02:28.00113Z","shell.execute_reply":"2026-01-19T12:02:28.006156Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# [csiro multiview huge aux avg](https://www.kaggle.com/code/jiazhuang/csiro-multiview-huge-aux-avg?scriptVersionId=294439420)","metadata":{}},{"cell_type":"code","source":"%%writefile csiro_infer.py\n\nimport sys\nfrom PIL import Image\nimport pandas as pd\nimport numpy as np\nimport matplotlib.pyplot as plt\n# %matplotlib inline\n\nfrom sklearn.model_selection import KFold, GroupKFold, StratifiedGroupKFold\nfrom tqdm.auto import tqdm\ntqdm.pandas()\n\nDATA_ROOT = '/kaggle/input/csiro-biomass/'\nTEST_IMAGE_DIR = './'\nMODEL_NAME = 'vit_huge_plus_patch16_dinov3_qkvb'\n\n## DataLoader\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms\n\n\ndef split_image(image, rows=2, cols=4, tile_width=500, tile_height=500):\n    \"\"\"\n    将图片平均切割成小图\n    \n    Args:\n        image: PIL.Image对象\n        rows: 行数\n        cols: 列数  \n        tile_width: 每个小图的宽度\n        tile_height: 每个小图的高度\n    \n    Returns:\n        包含所有小图的列表\n    \"\"\"\n    tiles = []\n    \n    for row in range(rows):\n        for col in range(cols):\n            # 计算切割区域的坐标\n            left = col * tile_width\n            top = row * tile_height\n            right = left + tile_width\n            bottom = top + tile_height\n            \n            # 切割图片\n            tile = image.crop((left, top, right, bottom))\n            tiles.append(tile)\n    \n    return tiles\n\n\nclass RegressionDataset(Dataset):\n    def __init__(self, data, transform=None, vertical_split=True):\n        self.data = data\n        self.transform = transform\n        self.vertical_split = vertical_split\n\n    def __len__(self):\n        return self.data.shape[0]\n\n    def __getitem__(self, idx):\n        item = self.data.iloc[idx]\n        # image = item.image\n        with Image.open(TEST_IMAGE_DIR + item.image_path) as img:\n            image = img.convert('RGB')\n\n        targets = [item['Dry_Green_g'], item['Dry_Clover_g'], item['Dry_Dead_g']]\n        \n        if self.vertical_split:\n            # 分隔成8张小图\n            tiles = []\n            for part in split_image(image):\n                if self.transform:\n                    part = self.transform(part)\n                \n                tiles.append(part)\n            \n            # 左边4张组合成一张，右边4张组合成一张\n            # top_row = torch.cat([tiles[0], tiles[1]], dim=2)\n            # bottom_row = torch.cat([tiles[4], tiles[5]], dim=2)\n            # left_image = torch.cat([top_row, bottom_row], dim=1)\n            \n            # top_row = torch.cat([tiles[2], tiles[3]], dim=2)\n            # bottom_row = torch.cat([tiles[6], tiles[7]], dim=2)\n            # right_image = torch.cat([top_row, bottom_row], dim=1)\n\n            # image = torch.cat([left_image, right_image], dim=2)   # B x H x W\n            \n            return tiles, targets\n        \n        else:\n            if self.transform:\n                image = self.transform(image)\n\n            return image, targets\n\n\ndef create_dataloader(data, target_image_size=(256, 256), batch_size=32, shuffle=True, aug=True, tta_transform=None):    \n    if aug:\n        transform = transforms.Compose([\n            transforms.Resize(target_image_size),\n            transforms.RandomHorizontalFlip(p=0.5),\n            transforms.RandomVerticalFlip(p=0.5),\n            transforms.RandomApply([transforms.RandomRotation([90, 90])], p=0.5),\n            transforms.ColorJitter(brightness=0.1, contrast=0.1, saturation=0.1, hue=0.05),\n            transforms.ToTensor(),\n            transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n        ])\n    \n    else:\n        if tta_transform:\n            transform = transforms.Compose([\n                transforms.Resize(target_image_size),\n                tta_transform,\n                transforms.ToTensor(),\n                transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n            ])\n        else:\n            transform = transforms.Compose([\n                transforms.Resize(target_image_size),\n\n                transforms.ToTensor(),\n                transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n            ])\n\n    dataset = RegressionDataset(data, transform=transform)\n    print('dataset size:', len(dataset))\n    dataloader = DataLoader(dataset, batch_size=batch_size, shuffle=shuffle, num_workers=2, pin_memory=True, prefetch_factor=1, persistent_workers=True)\n    return dataloader\n\n\ndef get_tta_dataloaders(data, target_image_size, batch_size):  # TTA还不够充分，每个子图也可以单独做aug\n    res = []\n    for transform in [None, transforms.RandomHorizontalFlip(p=1.0), transforms.RandomVerticalFlip(p=1.0), transforms.RandomRotation([90, 90])]:\n    # for transform in [None]:\n        res.append(\n            create_dataloader(data, target_image_size, batch_size, shuffle=False, aug=False, tta_transform=transform)\n        )\n    return res\n\n\nimport timm\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.cuda.amp import autocast\n\nTARGET_IMAGE_SIZE = (256, 256)\n\n\nclass AttentionLayer(nn.Module):\n    def __init__(self, hidden_dim):\n        super().__init__()\n        self.attention = nn.Linear(hidden_dim, 1)\n        \n    def forward(self, x):\n        # x shape: (batch, seq_len, hidden_dim)\n        scores = torch.tanh(self.attention(x))  # (batch, seq_len, 1)\n        weights = F.softmax(scores, dim=1)  # (batch, seq_len, 1)\n        context = torch.sum(x * weights, dim=1)  # (batch, hidden_dim)\n        return context\n\n\nclass MultiTargetRegressor(nn.Module):\n    def __init__(self, model_name, pretrained=True, num_classes=3, dropout=0.0, freeze_backbone=False):\n        super().__init__()\n        if pretrained:\n            self.backbone = timm.create_model(model_name, pretrained=False, checkpoint_path=CHECKPOINT_PATH)\n        else:\n            self.backbone = timm.create_model(model_name, pretrained=pretrained)\n        \n        self.attention = AttentionLayer(self.backbone.num_features)\n        \n        # self.dropout = nn.Dropout(dropout)\n        \n        def make_head():\n            return nn.Sequential(\n                nn.Linear(self.backbone.num_features, 8),\n                nn.ReLU(inplace=True),\n                nn.Dropout(dropout),\n                nn.Linear(8, 1),\n            )\n\n        self.head_green = make_head()\n        self.head_clover = make_head()\n        self.head_dead = make_head()\n        \n        self.head_height = make_head()\n        self.head_ndvi = make_head()\n        \n        self.softplus = nn.Softplus(beta=1.0)\n    \n        if freeze_backbone:\n            for p in self.backbone.parameters():\n                p.requires_grad = False\n    \n    \n    def forward(self, tiles):\n        tiles = torch.stack(tiles, dim=1)\n        B, L, C, H, W = tiles.shape\n        tiles = tiles.view(B*L, C, H, W)\n\n        tile_feats = self.backbone(tiles)  # B*L x H\n\n        tile_feats = tile_feats.view(B, L, -1)  # B x L x H \n\n        attended = self.attention(tile_feats)  # B x H\n\n        green = self.softplus(self.head_green(attended))    # Bx1\n        clover = self.softplus(self.head_clover(attended))  # Bx1\n        dead = self.softplus(self.head_dead(attended))   # Bx1\n\n        height = self.softplus(self.head_height(attended))\n        ndvi = self.softplus(self.head_ndvi(attended))\n\n        logits = torch.cat([green, clover, dead, height, ndvi], dim=1)  # Bx3 \n\n        return logits\n\n\nimport os\nfrom pathlib import Path\n\n\ndef get_lastest_saved_models(model_root='/kaggle/input/csiro-simple-output/pytorch/default/'):    \n    latest = 1\n    for version in os.listdir(model_root):\n        try:\n            version = int(version)\n        except:\n            continue\n\n        if version > latest:\n            latest = version\n\n    return f'{model_root}/{latest}/trained_models/'\n\n\n# SAVED_MODELS = get_lastest_saved_models('/kaggle/input/csiro-local-pro/pytorch/default/')\n# SAVED_MODELS = '/kaggle/input/csiro-local-pro-5/pytorch/default/1/trained_models/'\nSAVED_MODELS = [\n    '/kaggle/input/csiro-local-pro-4/pytorch/default/4/trained_models/',\n    '/kaggle/input/csiro-local-pro/pytorch/default/36/trained_models/',\n]\n\n\ndef predict(model, dataloader, device):\n    model.to(device)\n    model.eval()\n\n    all_outputs = []\n    with torch.no_grad():\n        with autocast():\n            for images, targets in dataloader:\n                images = [x.to(device) for x in images]\n        \n                outputs = model(images)\n                all_outputs.append(outputs.detach().cpu())\n    \n    outputs = torch.cat(all_outputs).numpy()\n    return outputs\n\n\ndef tta_predict(model, dataloaders, device):\n    all_outputs = []\n    for dataloader in dataloaders:\n        outputs = predict(model, dataloader, device)\n        all_outputs.append(outputs)\n    avg_outputs = np.mean(all_outputs, axis=0)\n    return avg_outputs\n\n\nNFOLD = 5\n\ndef kfold_predict(dataloaders):\n    device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')\n\n    # find all model weights file\n    all_model_files = []\n    for model_dir in SAVED_MODELS:\n        all_model_files += Path(model_dir).glob('*.pth')\n    \n    all_preds = []\n    for model_file in all_model_files:\n        fold_idx = int(model_file.name.split('.')[0].split('fold')[1])\n        if fold_idx >= NFOLD: continue\n        print(model_file.name)\n        model = MultiTargetRegressor(MODEL_NAME, pretrained=False, num_classes=3)\n        model.load_state_dict(torch.load(model_file))\n\n        preds = tta_predict(model, dataloaders, device)\n        all_preds.append(preds)\n\n    avg_preds = np.mean(all_preds, axis=0)\n    return avg_preds\n\n\npart_idx = sys.argv[1]\nagg_test_df = pd.read_pickle(f'agg_test_df_part{part_idx}.pkl')\n\ntest_loader = get_tta_dataloaders(agg_test_df, TARGET_IMAGE_SIZE, 48)\n\npreds = kfold_predict(test_loader)\n\nagg_test_df[['Dry_Green_g', 'Dry_Clover_g', 'Dry_Dead_g']] = preds[:, :3]\nagg_test_df['GDM_g'] = agg_test_df.Dry_Green_g + agg_test_df.Dry_Clover_g\nagg_test_df['Dry_Total_g'] = agg_test_df.GDM_g + agg_test_df.Dry_Dead_g\n\ncols = ['Dry_Clover_g', 'Dry_Dead_g', 'Dry_Green_g', 'Dry_Total_g', 'GDM_g']\nsub_df = agg_test_df.set_index('sample_id_prefix')[cols].stack()\nsub_df = sub_df.reset_index()\nsub_df.columns = ['sample_id_prefix', 'target_name', 'target']\n\nsub_df['sample_id'] = sub_df.sample_id_prefix + '__' + sub_df.target_name\n\ncols = ['sample_id', 'target']\nsub_df[cols].to_csv(f'submission_part{part_idx}.csv', index=False)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"%%bash\nCUDA_VISIBLE_DEVICES=0 python csiro_infer.py 0 &\nPID1=$!\n\nCUDA_VISIBLE_DEVICES=1 python csiro_infer.py 1 &\nPID2=$!\n\nwait $PID1 $PID2\n\necho '运行完成！'","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"merge_submissions('submission_aux.csv')","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# [csiro multiview huge aux more avg](https://www.kaggle.com/code/jiazhuang/csiro-multiview-huge-aux-more-avg?scriptVersionId=294451206)","metadata":{}},{"cell_type":"code","source":"%%writefile csiro_infer.py\n\nimport sys\nfrom PIL import Image\nimport pandas as pd\nimport numpy as np\nimport matplotlib.pyplot as plt\n# %matplotlib inline\n\nfrom sklearn.model_selection import KFold, GroupKFold, StratifiedGroupKFold\nfrom tqdm.auto import tqdm\ntqdm.pandas()\n\nDATA_ROOT = '/kaggle/input/csiro-biomass/'\nTEST_IMAGE_DIR = './'\nMODEL_NAME = 'vit_huge_plus_patch16_dinov3_qkvb'\n\n## DataLoader\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms\n\n\ndef split_image(image, rows=2, cols=4, tile_width=500, tile_height=500):\n    \"\"\"\n    将图片平均切割成小图\n    \n    Args:\n        image: PIL.Image对象\n        rows: 行数\n        cols: 列数  \n        tile_width: 每个小图的宽度\n        tile_height: 每个小图的高度\n    \n    Returns:\n        包含所有小图的列表\n    \"\"\"\n    tiles = []\n    \n    for row in range(rows):\n        for col in range(cols):\n            # 计算切割区域的坐标\n            left = col * tile_width\n            top = row * tile_height\n            right = left + tile_width\n            bottom = top + tile_height\n            \n            # 切割图片\n            tile = image.crop((left, top, right, bottom))\n            tiles.append(tile)\n    \n    return tiles\n\n\nclass RegressionDataset(Dataset):\n    def __init__(self, data, transform=None, vertical_split=True):\n        self.data = data\n        self.transform = transform\n        self.vertical_split = vertical_split\n\n    def __len__(self):\n        return self.data.shape[0]\n\n    def __getitem__(self, idx):\n        item = self.data.iloc[idx]\n        # image = item.image\n        with Image.open(TEST_IMAGE_DIR + item.image_path) as img:\n            image = img.convert('RGB')\n\n        targets = [item['Dry_Green_g'], item['Dry_Clover_g'], item['Dry_Dead_g']]\n        \n        if self.vertical_split:\n            # 分隔成8张小图\n            tiles = []\n            for part in split_image(image):\n                if self.transform:\n                    part = self.transform(part)\n                \n                tiles.append(part)\n            \n            # 左边4张组合成一张，右边4张组合成一张\n            # top_row = torch.cat([tiles[0], tiles[1]], dim=2)\n            # bottom_row = torch.cat([tiles[4], tiles[5]], dim=2)\n            # left_image = torch.cat([top_row, bottom_row], dim=1)\n            \n            # top_row = torch.cat([tiles[2], tiles[3]], dim=2)\n            # bottom_row = torch.cat([tiles[6], tiles[7]], dim=2)\n            # right_image = torch.cat([top_row, bottom_row], dim=1)\n\n            # image = torch.cat([left_image, right_image], dim=2)   # B x H x W\n            \n            return tiles, targets\n        \n        else:\n            if self.transform:\n                image = self.transform(image)\n\n            return image, targets\n\n\ndef create_dataloader(data, target_image_size=(256, 256), batch_size=32, shuffle=True, aug=True, tta_transform=None):    \n    if aug:\n        transform = transforms.Compose([\n            transforms.Resize(target_image_size),\n            transforms.RandomHorizontalFlip(p=0.5),\n            transforms.RandomVerticalFlip(p=0.5),\n            transforms.RandomApply([transforms.RandomRotation([90, 90])], p=0.5),\n            transforms.ColorJitter(brightness=0.1, contrast=0.1, saturation=0.1, hue=0.05),\n            transforms.ToTensor(),\n            transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n        ])\n    \n    else:\n        if tta_transform:\n            transform = transforms.Compose([\n                transforms.Resize(target_image_size),\n                tta_transform,\n                transforms.ToTensor(),\n                transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n            ])\n        else:\n            transform = transforms.Compose([\n                transforms.Resize(target_image_size),\n\n                transforms.ToTensor(),\n                transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n            ])\n\n    dataset = RegressionDataset(data, transform=transform)\n    print('dataset size:', len(dataset))\n    dataloader = DataLoader(dataset, batch_size=batch_size, shuffle=shuffle, num_workers=2, pin_memory=True, prefetch_factor=1, persistent_workers=True)\n    return dataloader\n\n\ndef get_tta_dataloaders(data, target_image_size, batch_size):  # TTA还不够充分，每个子图也可以单独做aug\n    res = []\n    for transform in [None, transforms.RandomHorizontalFlip(p=1.0), transforms.RandomVerticalFlip(p=1.0), transforms.RandomRotation([90, 90])]:\n    # for transform in [None]:\n        res.append(\n            create_dataloader(data, target_image_size, batch_size, shuffle=False, aug=False, tta_transform=transform)\n        )\n    return res\n\n\nimport timm\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.cuda.amp import autocast\n\nTARGET_IMAGE_SIZE = (256, 256)\n\n\nclass AttentionLayer(nn.Module):\n    def __init__(self, hidden_dim):\n        super().__init__()\n        self.attention = nn.Linear(hidden_dim, 1)\n        \n    def forward(self, x):\n        # x shape: (batch, seq_len, hidden_dim)\n        scores = torch.tanh(self.attention(x))  # (batch, seq_len, 1)\n        weights = F.softmax(scores, dim=1)  # (batch, seq_len, 1)\n        context = torch.sum(x * weights, dim=1)  # (batch, hidden_dim)\n        return context\n\n\nclass MultiTargetRegressor(nn.Module):\n    def __init__(self, model_name, pretrained=True, num_classes=3, dropout=0.0, freeze_backbone=False):\n        super().__init__()\n        if pretrained:\n            self.backbone = timm.create_model(model_name, pretrained=False, checkpoint_path=CHECKPOINT_PATH)\n        else:\n            self.backbone = timm.create_model(model_name, pretrained=pretrained)\n        \n        self.attention = AttentionLayer(self.backbone.num_features)\n        \n        # self.dropout = nn.Dropout(dropout)\n        \n        def make_head(out_dim=1):\n            return nn.Sequential(\n                nn.Linear(self.backbone.num_features, 8),\n                nn.ReLU(inplace=True),\n                nn.Dropout(dropout),\n                nn.Linear(8, out_dim),\n            )\n\n        self.head_green = make_head()\n        self.head_clover = make_head()\n        self.head_dead = make_head()\n        \n        self.head_height = make_head()\n        self.head_ndvi = make_head()\n\n        # item['SpeciesCode'], item['MonthCode'], item['StateCode'],\n        self.head_species = make_head(16)  # len(Species2Code)\n        self.head_month = make_head(10)  # len(Month2Code)\n        self.head_state = make_head(4)  # len(State2Code)\n        \n        self.softplus = nn.Softplus(beta=1.0)\n    \n        if freeze_backbone:\n            for p in self.backbone.parameters():\n                p.requires_grad = False\n    \n    \n    def forward(self, tiles):\n        tiles = torch.stack(tiles, dim=1)\n        B, L, C, H, W = tiles.shape\n        tiles = tiles.view(B*L, C, H, W)\n\n        tile_feats = self.backbone(tiles)  # B*L x H\n\n        tile_feats = tile_feats.view(B, L, -1)  # B x L x H \n\n        attended = self.attention(tile_feats)  # B x H\n\n        green = self.softplus(self.head_green(attended))    # Bx1\n        clover = self.softplus(self.head_clover(attended))  # Bx1\n        dead = self.softplus(self.head_dead(attended))   # Bx1\n\n        height = self.softplus(self.head_height(attended))\n        ndvi = self.softplus(self.head_ndvi(attended))\n\n        species = self.head_species(attended)\n        month = self.head_month(attended)\n        state = self.head_state(attended)\n\n        logits = torch.cat([green, clover, dead, height, ndvi], dim=1)  # Bx3 \n\n        return logits, species, month, state\n\n\nimport os\nfrom pathlib import Path\n\n\ndef get_lastest_saved_models(model_root='/kaggle/input/csiro-simple-output/pytorch/default/'):    \n    latest = 1\n    for version in os.listdir(model_root):\n        try:\n            version = int(version)\n        except:\n            continue\n\n        if version > latest:\n            latest = version\n\n    return f'{model_root}/{latest}/trained_models/'\n\n\n# SAVED_MODELS = get_lastest_saved_models('/kaggle/input/csiro-local-pro/pytorch/default/')\n# SAVED_MODELS = '/kaggle/input/csiro-local-pro-5/pytorch/default/1/trained_models/'\nSAVED_MODELS = [\n    '/kaggle/input/csiro-local-pro-4/pytorch/default/5/trained_models/',\n    '/kaggle/input/csiro-local-pro/pytorch/default/37/trained_models/',\n]\n\n\ndef predict(model, dataloader, device):\n    model.to(device)\n    model.eval()\n\n    all_outputs = []\n    with torch.no_grad():\n        with autocast():\n            for images, targets in dataloader:\n                images = [x.to(device) for x in images]\n\n                # logits, species, month, state\n                outputs, _, _, _ = model(images)\n                all_outputs.append(outputs.detach().cpu())\n    \n    outputs = torch.cat(all_outputs).numpy()\n    return outputs\n\n\ndef tta_predict(model, dataloaders, device):\n    all_outputs = []\n    for dataloader in dataloaders:\n        outputs = predict(model, dataloader, device)\n        all_outputs.append(outputs)\n    avg_outputs = np.mean(all_outputs, axis=0)\n    return avg_outputs\n\n\nNFOLD = 5\n\ndef kfold_predict(dataloaders):\n    device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')\n\n    # find all model weights file\n    all_model_files = []\n    for model_dir in SAVED_MODELS:\n        all_model_files += Path(model_dir).glob('*.pth')\n    \n    all_preds = []\n    for model_file in all_model_files:\n        fold_idx = int(model_file.name.split('.')[0].split('fold')[1])\n        if fold_idx >= NFOLD: continue\n        print(model_file.name)\n        model = MultiTargetRegressor(MODEL_NAME, pretrained=False, num_classes=3)\n        model.load_state_dict(torch.load(model_file))\n\n        preds = tta_predict(model, dataloaders, device)\n        all_preds.append(preds)\n\n    avg_preds = np.mean(all_preds, axis=0)\n    return avg_preds\n\n\npart_idx = sys.argv[1]\nagg_test_df = pd.read_pickle(f'agg_test_df_part{part_idx}.pkl')\n\ntest_loader = get_tta_dataloaders(agg_test_df, TARGET_IMAGE_SIZE, 48)\n\npreds = kfold_predict(test_loader)\n\nagg_test_df[['Dry_Green_g', 'Dry_Clover_g', 'Dry_Dead_g']] = preds[:, :3]\nagg_test_df['GDM_g'] = agg_test_df.Dry_Green_g + agg_test_df.Dry_Clover_g\nagg_test_df['Dry_Total_g'] = agg_test_df.GDM_g + agg_test_df.Dry_Dead_g\n\ncols = ['Dry_Clover_g', 'Dry_Dead_g', 'Dry_Green_g', 'Dry_Total_g', 'GDM_g']\nsub_df = agg_test_df.set_index('sample_id_prefix')[cols].stack()\nsub_df = sub_df.reset_index()\nsub_df.columns = ['sample_id_prefix', 'target_name', 'target']\n\nsub_df['sample_id'] = sub_df.sample_id_prefix + '__' + sub_df.target_name\n\ncols = ['sample_id', 'target']\nsub_df[cols].to_csv(f'submission_part{part_idx}.csv', index=False)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"%%bash\nCUDA_VISIBLE_DEVICES=0 python csiro_infer.py 0 &\nPID1=$!\n\nCUDA_VISIBLE_DEVICES=1 python csiro_infer.py 1 &\nPID2=$!\n\nwait $PID1 $PID2\n\necho '运行完成！'","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"merge_submissions('submission_aux_more.csv')","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# [csiro multiview huge kold1991 seed avg](https://www.kaggle.com/code/jiazhuang/csiro-multiview-huge-kold1991-seed-avg?scriptVersionId=294002138)","metadata":{}},{"cell_type":"code","source":"%%writefile csiro_infer.py\n\nimport sys\nfrom PIL import Image\nimport pandas as pd\nimport numpy as np\nimport matplotlib.pyplot as plt\n# %matplotlib inline\n\nfrom sklearn.model_selection import KFold, GroupKFold, StratifiedGroupKFold\nfrom tqdm.auto import tqdm\ntqdm.pandas()\n\nDATA_ROOT = '/kaggle/input/csiro-biomass/'\nTEST_IMAGE_DIR = './'\nMODEL_NAME = 'vit_huge_plus_patch16_dinov3_qkvb'\n\n## DataLoader\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms\n\n\ndef split_image(image, rows=2, cols=4, tile_width=500, tile_height=500):\n    \"\"\"\n    将图片平均切割成小图\n    \n    Args:\n        image: PIL.Image对象\n        rows: 行数\n        cols: 列数  \n        tile_width: 每个小图的宽度\n        tile_height: 每个小图的高度\n    \n    Returns:\n        包含所有小图的列表\n    \"\"\"\n    tiles = []\n    \n    for row in range(rows):\n        for col in range(cols):\n            # 计算切割区域的坐标\n            left = col * tile_width\n            top = row * tile_height\n            right = left + tile_width\n            bottom = top + tile_height\n            \n            # 切割图片\n            tile = image.crop((left, top, right, bottom))\n            tiles.append(tile)\n    \n    return tiles\n\n\nclass RegressionDataset(Dataset):\n    def __init__(self, data, transform=None, vertical_split=True):\n        self.data = data\n        self.transform = transform\n        self.vertical_split = vertical_split\n\n    def __len__(self):\n        return self.data.shape[0]\n\n    def __getitem__(self, idx):\n        item = self.data.iloc[idx]\n        # image = item.image\n        with Image.open(TEST_IMAGE_DIR + item.image_path) as img:\n            image = img.convert('RGB')\n\n        targets = [item['Dry_Green_g'], item['Dry_Clover_g'], item['Dry_Dead_g']]\n        \n        if self.vertical_split:\n            # 分隔成8张小图\n            tiles = []\n            for part in split_image(image):\n                if self.transform:\n                    part = self.transform(part)\n                \n                tiles.append(part)\n            \n            # 左边4张组合成一张，右边4张组合成一张\n            # top_row = torch.cat([tiles[0], tiles[1]], dim=2)\n            # bottom_row = torch.cat([tiles[4], tiles[5]], dim=2)\n            # left_image = torch.cat([top_row, bottom_row], dim=1)\n            \n            # top_row = torch.cat([tiles[2], tiles[3]], dim=2)\n            # bottom_row = torch.cat([tiles[6], tiles[7]], dim=2)\n            # right_image = torch.cat([top_row, bottom_row], dim=1)\n\n            # image = torch.cat([left_image, right_image], dim=2)   # B x H x W\n            \n            return tiles, targets\n        \n        else:\n            if self.transform:\n                image = self.transform(image)\n\n            return image, targets\n\n\ndef create_dataloader(data, target_image_size=(256, 256), batch_size=32, shuffle=True, aug=True, tta_transform=None):    \n    if aug:\n        transform = transforms.Compose([\n            transforms.Resize(target_image_size),\n            transforms.RandomHorizontalFlip(p=0.5),\n            transforms.RandomVerticalFlip(p=0.5),\n            transforms.RandomApply([transforms.RandomRotation([90, 90])], p=0.5),\n            transforms.ColorJitter(brightness=0.1, contrast=0.1, saturation=0.1, hue=0.05),\n            transforms.ToTensor(),\n            transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n        ])\n    \n    else:\n        if tta_transform:\n            transform = transforms.Compose([\n                transforms.Resize(target_image_size),\n                tta_transform,\n                transforms.ToTensor(),\n                transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n            ])\n        else:\n            transform = transforms.Compose([\n                transforms.Resize(target_image_size),\n\n                transforms.ToTensor(),\n                transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n            ])\n\n    dataset = RegressionDataset(data, transform=transform)\n    print('dataset size:', len(dataset))\n    dataloader = DataLoader(dataset, batch_size=batch_size, shuffle=shuffle, num_workers=2, pin_memory=True, prefetch_factor=1, persistent_workers=True)\n    return dataloader\n\n\ndef get_tta_dataloaders(data, target_image_size, batch_size):  # TTA还不够充分，每个子图也可以单独做aug\n    res = []\n    for transform in [None, transforms.RandomHorizontalFlip(p=1.0), transforms.RandomVerticalFlip(p=1.0), transforms.RandomRotation([90, 90])]:\n    # for transform in [None]:\n        res.append(\n            create_dataloader(data, target_image_size, batch_size, shuffle=False, aug=False, tta_transform=transform)\n        )\n    return res\n\n\nimport timm\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.cuda.amp import autocast\n\nTARGET_IMAGE_SIZE = (256, 256)\n\n\nclass AttentionLayer(nn.Module):\n    def __init__(self, hidden_dim):\n        super().__init__()\n        self.attention = nn.Linear(hidden_dim, 1)\n        \n    def forward(self, x):\n        # x shape: (batch, seq_len, hidden_dim)\n        scores = torch.tanh(self.attention(x))  # (batch, seq_len, 1)\n        weights = F.softmax(scores, dim=1)  # (batch, seq_len, 1)\n        context = torch.sum(x * weights, dim=1)  # (batch, hidden_dim)\n        return context\n\n\nclass MultiTargetRegressor(nn.Module):\n    def __init__(self, model_name, pretrained=True, num_classes=3, dropout=0.0, freeze_backbone=False):\n        super().__init__()\n        if pretrained:\n            self.backbone = timm.create_model(model_name, pretrained=False, checkpoint_path=CHECKPOINT_PATH)\n        else:\n            self.backbone = timm.create_model(model_name, pretrained=pretrained)\n        \n        self.attention = AttentionLayer(self.backbone.num_features)\n        \n        # self.dropout = nn.Dropout(dropout)\n        \n        def make_head():\n            return nn.Sequential(\n                nn.Linear(self.backbone.num_features, 8),\n                nn.ReLU(inplace=True),\n                nn.Dropout(dropout),\n                nn.Linear(8, 1),\n            )\n\n        self.head_green = make_head()\n        self.head_clover = make_head()\n        self.head_dead = make_head()\n        \n        self.softplus = nn.Softplus(beta=1.0)\n    \n        if freeze_backbone:\n            for p in self.backbone.parameters():\n                p.requires_grad = False\n    \n    \n    def forward(self, tiles):\n        tiles = torch.stack(tiles, dim=1)\n        B, L, C, H, W = tiles.shape\n        tiles = tiles.view(B*L, C, H, W)\n\n        tile_feats = self.backbone(tiles)  # B*L x H\n\n        tile_feats = tile_feats.view(B, L, -1)  # B x L x H \n\n        attended = self.attention(tile_feats)  # B x H\n\n        green = self.softplus(self.head_green(attended))    # Bx1\n        clover = self.softplus(self.head_clover(attended))  # Bx1\n        dead = self.softplus(self.head_dead(attended))   # Bx1\n\n        logits = torch.cat([green, clover, dead], dim=1)  # Bx3 \n\n        return logits\n\n\nimport os\nfrom pathlib import Path\n\n\ndef get_lastest_saved_models(model_root='/kaggle/input/csiro-simple-output/pytorch/default/'):    \n    latest = 1\n    for version in os.listdir(model_root):\n        try:\n            version = int(version)\n        except:\n            continue\n\n        if version > latest:\n            latest = version\n\n    return f'{model_root}/{latest}/trained_models/'\n\n\n# SAVED_MODELS = get_lastest_saved_models('/kaggle/input/csiro-local-pro/pytorch/default/')\n# SAVED_MODELS = '/kaggle/input/csiro-local-pro-5/pytorch/default/1/trained_models/'\nSAVED_MODELS = [\n    '/kaggle/input/csiro-local-pro-5/pytorch/default/2/trained_models/',\n    '/kaggle/input/csiro-local-pro-6/pytorch/default/2/trained_models/',\n    '/kaggle/input/csiro-local-pro-4/pytorch/default/2/trained_models/',\n    '/kaggle/input/csiro-local-pro/pytorch/default/34/trained_models/',\n    '/kaggle/input/csiro-local-pro/pytorch/default/33/trained_models/',\n\n    '/kaggle/input/csiro-local-pro/pytorch/default/32/trained_models/',  # rerun(submission_multiview_huge_rerun): https://www.kaggle.com/code/jiazhuang/csiro-multiview-huge?scriptVersionId=293659483\n    '/kaggle/input/csiro-local-pro/pytorch/default/28/trained_models/',  # multiview(submission_multiview): https://www.kaggle.com/code/jiazhuang/csiro-multiview-huge?scriptVersionId=293209059\n    '/kaggle/input/csiro-local-pro/pytorch/default/30/trained_models/',  # mixup(submission_multiview_mixup): https://www.kaggle.com/code/jiazhuang/csiro-multiview-huge?scriptVersionId=293413012\n\n]\n\n\ndef predict(model, dataloader, device):\n    model.to(device)\n    model.eval()\n\n    all_outputs = []\n    with torch.no_grad():\n        with autocast():\n            for images, targets in dataloader:\n                images = [x.to(device) for x in images]\n        \n                outputs = model(images)\n                all_outputs.append(outputs.detach().cpu())\n    \n    outputs = torch.cat(all_outputs).numpy()\n    return outputs\n\n\ndef tta_predict(model, dataloaders, device):\n    all_outputs = []\n    for dataloader in dataloaders:\n        outputs = predict(model, dataloader, device)\n        all_outputs.append(outputs)\n    avg_outputs = np.mean(all_outputs, axis=0)\n    return avg_outputs\n\n\nNFOLD = 5\n\ndef kfold_predict(dataloaders):\n    device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')\n\n    # find all model weights file\n    all_model_files = []\n    for model_dir in SAVED_MODELS:\n        all_model_files += Path(model_dir).glob('*.pth')\n    \n    all_preds = []\n    for model_file in all_model_files:\n        fold_idx = int(model_file.name.split('.')[0].split('fold')[1])\n        if fold_idx >= NFOLD: continue\n        print(model_file.name)\n        model = MultiTargetRegressor(MODEL_NAME, pretrained=False, num_classes=3)\n        model.load_state_dict(torch.load(model_file))\n\n        preds = tta_predict(model, dataloaders, device)\n        all_preds.append(preds)\n\n    avg_preds = np.mean(all_preds, axis=0)\n    return avg_preds\n\n\npart_idx = sys.argv[1]\nagg_test_df = pd.read_pickle(f'agg_test_df_part{part_idx}.pkl')\n\ntest_loader = get_tta_dataloaders(agg_test_df, TARGET_IMAGE_SIZE, 48)\n\npreds = kfold_predict(test_loader)\n\nagg_test_df[['Dry_Green_g', 'Dry_Clover_g', 'Dry_Dead_g']] = preds\nagg_test_df['GDM_g'] = agg_test_df.Dry_Green_g + agg_test_df.Dry_Clover_g\nagg_test_df['Dry_Total_g'] = agg_test_df.GDM_g + agg_test_df.Dry_Dead_g\n\ncols = ['Dry_Clover_g', 'Dry_Dead_g', 'Dry_Green_g', 'Dry_Total_g', 'GDM_g']\nsub_df = agg_test_df.set_index('sample_id_prefix')[cols].stack()\nsub_df = sub_df.reset_index()\nsub_df.columns = ['sample_id_prefix', 'target_name', 'target']\n\nsub_df['sample_id'] = sub_df.sample_id_prefix + '__' + sub_df.target_name\n\ncols = ['sample_id', 'target']\nsub_df[cols].to_csv(f'submission_part{part_idx}.csv', index=False)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"%%bash\nCUDA_VISIBLE_DEVICES=0 python csiro_infer.py 0 &\nPID1=$!\n\nCUDA_VISIBLE_DEVICES=1 python csiro_infer.py 1 &\nPID2=$!\n\nwait $PID1 $PID2\n\necho '运行完成！'","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-19T10:20:49.167179Z","iopub.execute_input":"2026-01-19T10:20:49.167677Z","iopub.status.idle":"2026-01-19T10:22:32.156986Z","shell.execute_reply.started":"2026-01-19T10:20:49.167653Z","shell.execute_reply":"2026-01-19T10:22:32.156358Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"merge_submissions('submission_multiview.csv')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-19T10:25:19.923874Z","iopub.execute_input":"2026-01-19T10:25:19.924435Z","iopub.status.idle":"2026-01-19T10:25:19.932688Z","shell.execute_reply.started":"2026-01-19T10:25:19.924408Z","shell.execute_reply":"2026-01-19T10:25:19.931834Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# [csiro multiview mamba](https://www.kaggle.com/code/jiazhuang/csiro-multiview-huge-mamba?scriptVersionId=293436938)","metadata":{}},{"cell_type":"code","source":"%%writefile csiro_infer.py\n\nimport sys\nfrom PIL import Image\nimport pandas as pd\nimport numpy as np\nimport matplotlib.pyplot as plt\n# %matplotlib inline\n\nfrom sklearn.model_selection import KFold, GroupKFold, StratifiedGroupKFold\nfrom tqdm.auto import tqdm\ntqdm.pandas()\n\nDATA_ROOT = '/kaggle/input/csiro-biomass/'\nTEST_IMAGE_DIR = './'\nMODEL_NAME = 'vit_huge_plus_patch16_dinov3_qkvb'\n\n## DataLoader\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms\n\n\ndef split_image(image, rows=2, cols=4, tile_width=500, tile_height=500):\n    \"\"\"\n    将图片平均切割成小图\n    \n    Args:\n        image: PIL.Image对象\n        rows: 行数\n        cols: 列数  \n        tile_width: 每个小图的宽度\n        tile_height: 每个小图的高度\n    \n    Returns:\n        包含所有小图的列表\n    \"\"\"\n    tiles = []\n    \n    for row in range(rows):\n        for col in range(cols):\n            # 计算切割区域的坐标\n            left = col * tile_width\n            top = row * tile_height\n            right = left + tile_width\n            bottom = top + tile_height\n            \n            # 切割图片\n            tile = image.crop((left, top, right, bottom))\n            tiles.append(tile)\n    \n    return tiles\n\n\nclass RegressionDataset(Dataset):\n    def __init__(self, data, transform=None, vertical_split=True):\n        self.data = data\n        self.transform = transform\n        self.vertical_split = vertical_split\n\n    def __len__(self):\n        return self.data.shape[0]\n\n    def __getitem__(self, idx):\n        item = self.data.iloc[idx]\n        # image = item.image\n        with Image.open(TEST_IMAGE_DIR + item.image_path) as img:\n            image = img.convert('RGB')\n\n        targets = [item['Dry_Green_g'], item['Dry_Clover_g'], item['Dry_Dead_g']]\n        \n        if self.vertical_split:\n            # 分隔成8张小图\n            tiles = []\n            for part in split_image(image):\n                if self.transform:\n                    part = self.transform(part)\n                \n                tiles.append(part)\n            \n            # 左边4张组合成一张，右边4张组合成一张\n            # top_row = torch.cat([tiles[0], tiles[1]], dim=2)\n            # bottom_row = torch.cat([tiles[4], tiles[5]], dim=2)\n            # left_image = torch.cat([top_row, bottom_row], dim=1)\n            \n            # top_row = torch.cat([tiles[2], tiles[3]], dim=2)\n            # bottom_row = torch.cat([tiles[6], tiles[7]], dim=2)\n            # right_image = torch.cat([top_row, bottom_row], dim=1)\n\n            # image = torch.cat([left_image, right_image], dim=2)   # B x H x W\n            \n            return tiles, targets\n        \n        else:\n            if self.transform:\n                image = self.transform(image)\n\n            return image, targets\n\n\ndef create_dataloader(data, target_image_size=(256, 256), batch_size=32, shuffle=True, aug=True, tta_transform=None):    \n    if aug:\n        transform = transforms.Compose([\n            transforms.Resize(target_image_size),\n            transforms.RandomHorizontalFlip(p=0.5),\n            transforms.RandomVerticalFlip(p=0.5),\n            transforms.RandomApply([transforms.RandomRotation([90, 90])], p=0.5),\n            transforms.ColorJitter(brightness=0.1, contrast=0.1, saturation=0.1, hue=0.05),\n            transforms.ToTensor(),\n            transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n        ])\n    \n    else:\n        if tta_transform:\n            transform = transforms.Compose([\n                transforms.Resize(target_image_size),\n                tta_transform,\n                transforms.ToTensor(),\n                transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n            ])\n        else:\n            transform = transforms.Compose([\n                transforms.Resize(target_image_size),\n\n                transforms.ToTensor(),\n                transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n            ])\n\n    dataset = RegressionDataset(data, transform=transform)\n    print('dataset size:', len(dataset))\n    dataloader = DataLoader(dataset, batch_size=batch_size, shuffle=shuffle, num_workers=2, pin_memory=True, prefetch_factor=1, persistent_workers=True)\n    return dataloader\n\n\ndef get_tta_dataloaders(data, target_image_size, batch_size):  # TTA还不够充分，每个子图也可以单独做aug\n    res = []\n    # for transform in [None, transforms.RandomHorizontalFlip(p=1.0), transforms.RandomVerticalFlip(p=1.0), transforms.RandomRotation([90, 90])]:\n    for transform in [None]:\n        res.append(\n            create_dataloader(data, target_image_size, batch_size, shuffle=False, aug=False, tta_transform=transform)\n        )\n    return res\n\n\nimport timm\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.cuda.amp import autocast\n\nTARGET_IMAGE_SIZE = (256, 256)\n\n\nclass LocalMambaBlock(nn.Module):\n    \"\"\"Lightweight Mamba-like block\"\"\"\n\n    def __init__(self, dim: int, kernel_size: int = 5, dropout: float = 0.1):\n        super().__init__()\n        self.norm = nn.LayerNorm(dim)\n        self.dwconv = nn.Conv1d(dim, dim, kernel_size=kernel_size,\n                                 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: torch.Tensor) -> torch.Tensor:\n        shortcut = x\n        x = self.norm(x)\n        g = torch.sigmoid(self.gate(x))\n        x = x * g\n        x = x.transpose(1, 2)\n        x = self.dwconv(x)\n        x = x.transpose(1, 2)\n        x = self.proj(x)\n        x = self.drop(x)\n        return shortcut + x\n\nclass MultiTargetRegressor(nn.Module):\n    def __init__(self, model_name, pretrained=True, num_classes=3, dropout=0.0, freeze_backbone=False):\n        super().__init__()\n        if pretrained:\n            self.backbone = timm.create_model(model_name, pretrained=False, checkpoint_path=CHECKPOINT_PATH)\n        else:\n            self.backbone = timm.create_model(model_name, pretrained=pretrained)\n        \n        nf = self.backbone.num_features\n\n        self.fusion = nn.Sequential(\n            LocalMambaBlock(nf, kernel_size=5, dropout=0.1),\n            LocalMambaBlock(nf, kernel_size=5, dropout=0.1)\n        )\n        self.pool = nn.AdaptiveAvgPool1d(1)\n        \n        # self.dropout = nn.Dropout(dropout)\n        \n        def make_head():\n            return nn.Sequential(\n                nn.Linear(self.backbone.num_features, 8),\n                nn.ReLU(inplace=True),\n                nn.Dropout(dropout),\n                nn.Linear(8, 1),\n            )\n\n        self.head_green = make_head()\n        self.head_clover = make_head()\n        self.head_dead = make_head()\n        \n        self.softplus = nn.Softplus(beta=1.0)\n    \n        if freeze_backbone:\n            for p in self.backbone.parameters():\n                p.requires_grad = False\n    \n    \n    def forward(self, tiles):\n        tiles = torch.stack(tiles, dim=1)\n        B, L, C, H, W = tiles.shape\n        tiles = tiles.view(B*L, C, H, W)\n\n        tile_feats = self.backbone(tiles)  # B*L x H\n\n        tile_feats = tile_feats.view(B, L, -1)  # B x L x H \n\n        # attended = self.attention(tile_feats)  # B x H\n\n        attended = self.fusion(tile_feats)\n        attended = self.pool(attended.transpose(1, 2)).flatten(1)\n\n        green = self.softplus(self.head_green(attended))    # Bx1\n        clover = self.softplus(self.head_clover(attended))  # Bx1\n        dead = self.softplus(self.head_dead(attended))   # Bx1\n\n        logits = torch.cat([green, clover, dead], dim=1)  # Bx3 \n\n        return logits\n\n\nimport os\nfrom pathlib import Path\n\n\ndef get_lastest_saved_models(model_root='/kaggle/input/csiro-simple-output/pytorch/default/'):    \n    latest = 1\n    for version in os.listdir(model_root):\n        try:\n            version = int(version)\n        except:\n            continue\n\n        if version > latest:\n            latest = version\n\n    return f'{model_root}/{latest}/trained_models/'\n\n\n# SAVED_MODELS = get_lastest_saved_models('/kaggle/input/csiro-local-pro/pytorch/default/')\nSAVED_MODELS = '/kaggle/input/csiro-local-pro/pytorch/default/31/trained_models/'\n\n\ndef predict(model, dataloader, device):\n    model.to(device)\n    model.eval()\n\n    all_outputs = []\n    with torch.no_grad():\n        with autocast():\n            for images, targets in dataloader:\n                images = [x.to(device) for x in images]\n        \n                outputs = model(images)\n                all_outputs.append(outputs.detach().cpu())\n    \n    outputs = torch.cat(all_outputs).numpy()\n    return outputs\n\n\ndef tta_predict(model, dataloaders, device):\n    all_outputs = []\n    for dataloader in dataloaders:\n        outputs = predict(model, dataloader, device)\n        all_outputs.append(outputs)\n    avg_outputs = np.mean(all_outputs, axis=0)\n    return avg_outputs\n\n\nNFOLD = 5\n\ndef kfold_predict(dataloaders):\n    device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')\n    \n    all_preds = []\n    for model_file in Path(SAVED_MODELS).glob('*.pth'):\n        fold_idx = int(model_file.name.split('.')[0].split('fold')[1])\n        if fold_idx >= NFOLD: continue\n        print(model_file.name)\n        model = MultiTargetRegressor(MODEL_NAME, pretrained=False, num_classes=3)\n        model.load_state_dict(torch.load(model_file))\n\n        preds = tta_predict(model, dataloaders, device)\n        all_preds.append(preds)\n\n    avg_preds = np.mean(all_preds, axis=0)\n    return avg_preds\n\n\npart_idx = sys.argv[1]\nagg_test_df = pd.read_pickle(f'agg_test_df_part{part_idx}.pkl')\n\ntest_loader = get_tta_dataloaders(agg_test_df, TARGET_IMAGE_SIZE, 48)\n\npreds = kfold_predict(test_loader)\n\nagg_test_df[['Dry_Green_g', 'Dry_Clover_g', 'Dry_Dead_g']] = preds\nagg_test_df['GDM_g'] = agg_test_df.Dry_Green_g + agg_test_df.Dry_Clover_g\nagg_test_df['Dry_Total_g'] = agg_test_df.GDM_g + agg_test_df.Dry_Dead_g\n\ncols = ['Dry_Clover_g', 'Dry_Dead_g', 'Dry_Green_g', 'Dry_Total_g', 'GDM_g']\nsub_df = agg_test_df.set_index('sample_id_prefix')[cols].stack()\nsub_df = sub_df.reset_index()\nsub_df.columns = ['sample_id_prefix', 'target_name', 'target']\n\nsub_df['sample_id'] = sub_df.sample_id_prefix + '__' + sub_df.target_name\n\ncols = ['sample_id', 'target']\nsub_df[cols].to_csv(f'submission_part{part_idx}.csv', index=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-19T10:20:33.434638Z","iopub.execute_input":"2026-01-19T10:20:33.435277Z","iopub.status.idle":"2026-01-19T10:20:33.444365Z","shell.execute_reply.started":"2026-01-19T10:20:33.435254Z","shell.execute_reply":"2026-01-19T10:20:33.443793Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"%%bash\nCUDA_VISIBLE_DEVICES=0 python csiro_infer.py 0 &\nPID1=$!\n\nCUDA_VISIBLE_DEVICES=1 python csiro_infer.py 1 &\nPID2=$!\n\nwait $PID1 $PID2\n\necho '运行完成！'","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-19T10:20:49.167179Z","iopub.execute_input":"2026-01-19T10:20:49.167677Z","iopub.status.idle":"2026-01-19T10:22:32.156986Z","shell.execute_reply.started":"2026-01-19T10:20:49.167653Z","shell.execute_reply":"2026-01-19T10:22:32.156358Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"merge_submissions('submission_multiview_memba.csv')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-19T10:25:19.923874Z","iopub.execute_input":"2026-01-19T10:25:19.924435Z","iopub.status.idle":"2026-01-19T10:25:19.932688Z","shell.execute_reply.started":"2026-01-19T10:25:19.924408Z","shell.execute_reply":"2026-01-19T10:25:19.931834Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# [csiro pro v2 no_tta V1](https://www.kaggle.com/code/jiazhuang/csiro-pro-v2-no-tta?scriptVersionId=288003504)","metadata":{}},{"cell_type":"code","source":"%%writefile csiro_infer.py\n\nimport sys\nfrom PIL import Image\nimport pandas as pd\nimport numpy as np\nimport matplotlib.pyplot as plt\n# %matplotlib inline\n\nfrom sklearn.model_selection import KFold, GroupKFold, StratifiedGroupKFold\nfrom tqdm.auto import tqdm\ntqdm.pandas()\n\nDATA_ROOT = '/kaggle/input/csiro-biomass/'\nTEST_IMAGE_DIR = './'\nMODEL_NAME = 'vit_large_patch16_dinov3_qkvb'\n\n## DataLoader\n\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms\n\ndef split_image(image, rows=2, cols=4, tile_width=500, tile_height=500):\n    \"\"\"\n    将图片平均切割成小图\n    \n    Args:\n        image: PIL.Image对象\n        rows: 行数\n        cols: 列数  \n        tile_width: 每个小图的宽度\n        tile_height: 每个小图的高度\n    \n    Returns:\n        包含所有小图的列表\n    \"\"\"\n    tiles = []\n    \n    for row in range(rows):\n        for col in range(cols):\n            # 计算切割区域的坐标\n            left = col * tile_width\n            top = row * tile_height\n            right = left + tile_width\n            bottom = top + tile_height\n            \n            # 切割图片\n            tile = image.crop((left, top, right, bottom))\n            tiles.append(tile)\n    \n    return tiles\n\nclass RegressionDataset(Dataset):\n    def __init__(self, data, transform=None, vertical_split=True):\n        self.data = data\n        self.transform = transform\n        self.vertical_split = vertical_split\n\n    def __len__(self):\n        return self.data.shape[0]\n\n    def __getitem__(self, idx):\n        item = self.data.iloc[idx]\n        # image = item.image\n        with Image.open(TEST_IMAGE_DIR + item.image_path) as img:\n            image = img.convert('RGB')\n            \n        targets = [item['Dry_Green_g'], item['Dry_Clover_g'], item['Dry_Dead_g']]\n        \n        if self.vertical_split:\n            # 分隔成8张小图\n            tiles = []\n            for part in split_image(image):\n                if self.transform:\n                    part = self.transform(part)\n                \n                tiles.append(part)\n            \n            # 左边4张组合成一张，右边4张组合成一张\n            top_row = torch.cat([tiles[0], tiles[1]], dim=2)\n            bottom_row = torch.cat([tiles[4], tiles[5]], dim=2)\n            left_image = torch.cat([top_row, bottom_row], dim=1)\n            \n            top_row = torch.cat([tiles[2], tiles[3]], dim=2)\n            bottom_row = torch.cat([tiles[6], tiles[7]], dim=2)\n            right_image = torch.cat([top_row, bottom_row], dim=1)\n            \n            return [left_image, right_image], targets\n        \n        else:\n            if self.transform:\n                image = self.transform(image)\n\n            return image, targets\n\ndef create_dataloader(data, target_image_size=(256, 256), batch_size=32, shuffle=True, aug=True, tta_transform=None):    \n    if aug:\n        transform = transforms.Compose([\n            transforms.Resize(target_image_size),\n            transforms.RandomHorizontalFlip(p=0.5),\n            transforms.RandomVerticalFlip(p=0.5),\n            transforms.RandomApply([transforms.RandomRotation([90, 90])], p=0.5),\n            transforms.ColorJitter(brightness=0.1, contrast=0.1, saturation=0.1, hue=0.05),\n            transforms.ToTensor(),\n            transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n        ])\n    \n    else:\n        if tta_transform:\n            transform = transforms.Compose([\n                transforms.Resize(target_image_size),\n                tta_transform,\n                transforms.ToTensor(),\n                transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n            ])\n        else:\n            transform = transforms.Compose([\n                transforms.Resize(target_image_size),\n\n                transforms.ToTensor(),\n                transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n            ])\n\n    dataset = RegressionDataset(data, transform=transform)\n    print('dataset size:', len(dataset))\n    dataloader = DataLoader(dataset, batch_size=batch_size, shuffle=shuffle, num_workers=4, pin_memory=True, prefetch_factor=2, persistent_workers=True)\n    return dataloader\n\ndef get_tta_dataloaders(data, target_image_size, batch_size):  # TTA还不够充分，每个子图也可以单独做aug\n    res = []\n    # for transform in [None, transforms.RandomHorizontalFlip(p=1.0), transforms.RandomVerticalFlip(p=1.0), transforms.RandomRotation([90, 90])]:\n    for transform in [None]:\n        res.append(\n            create_dataloader(data, target_image_size, batch_size, shuffle=False, aug=False, tta_transform=transform)\n        )\n    return res\n\n## Model\n\nimport timm\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.cuda.amp import autocast\n\n# model = timm.create_model(MODEL_NAME, pretrained=False, num_classes=3)\n# model.pretrained_cfg\n# TARGET_IMAGE_SIZE = model.pretrained_cfg['input_size'][1:]\nTARGET_IMAGE_SIZE = (256, 256)\n\nclass AttentionLayer(nn.Module):\n    def __init__(self, hidden_dim):\n        super().__init__()\n        self.attention = nn.Linear(hidden_dim, 1)\n        \n    def forward(self, x):\n        # x shape: (batch, seq_len, hidden_dim)\n        scores = torch.tanh(self.attention(x))  # (batch, seq_len, 1)\n        weights = F.softmax(scores.squeeze(-1), dim=1)  # (batch, seq_len)\n        context = torch.sum(x * weights.unsqueeze(-1), dim=1)  # (batch, hidden_dim)\n        return context\n\nclass MultiTargetRegressor(nn.Module):\n    def __init__(self, model_name, pretrained=True, num_classes=3, dropout=0.0, freeze_backbone=False):\n        super().__init__()\n        if pretrained:\n            self.backbone = timm.create_model(model_name, pretrained=False, checkpoint_path=CHECKPOINT_PATH)\n        else:\n            self.backbone = timm.create_model(model_name, pretrained=pretrained)\n        \n        self.attention = AttentionLayer(self.backbone.num_features)\n        \n        self.dropout = nn.Dropout(dropout)\n        \n        def make_head():\n            return nn.Sequential(\n                nn.Linear(self.backbone.num_features, 8),\n                nn.ReLU(inplace=True),\n                nn.Dropout(dropout),\n                nn.Linear(8, 1),\n            )\n\n        self.head_green = make_head()\n        self.head_clover = make_head()\n        self.head_dead = make_head()\n        \n        self.softplus = nn.Softplus(beta=1.0)\n    \n        if freeze_backbone:\n            for p in self.backbone.parameters():\n                p.requires_grad = False\n    \n    \n    def forward(self, tiles):\n        tile_feats = []\n        for tile in tiles:\n            tile_feats.append( self.backbone.forward_features(tile)[:, self.backbone.num_prefix_tokens:] )  # B x (16*16) x H\n        \n        tile_feats = torch.cat(tile_feats, dim=1)  # B x L x H\n        \n        attended_green = self.attention(tile_feats)  # B x H\n        attended_clover = self.attention(tile_feats)  # B x H\n        attended_dead = self.attention(tile_feats)  # B x H\n\n        green = self.softplus(self.head_green(attended_green))    # Bx1\n        clover = self.softplus(self.head_clover(attended_clover))  # Bx1\n        dead = self.softplus(self.head_dead(attended_dead))   # Bx1\n\n        logits = torch.cat([green, clover, dead], dim=1)  # Bx3 \n\n        return logits\n\n## Inference\n\nimport os\nfrom pathlib import Path\n\ndef get_lastest_saved_models(model_root='/kaggle/input/csiro-simple-output/pytorch/default/'):    \n    latest = 1\n    for version in os.listdir(model_root):\n        try:\n            version = int(version)\n        except:\n            continue\n\n        if version > latest:\n            latest = version\n\n    return f'{model_root}/{latest}/trained_models/'\n\n# SAVED_MODELS = get_lastest_saved_models('/kaggle/input/csiro-local-pro/pytorch/default/')\nSAVED_MODELS = '/kaggle/input/csiro-local-pro/pytorch/default/1/trained_models'\n\ndef predict(model, dataloader, device):\n    model.to(device)\n    model.eval()\n\n    all_outputs = []\n    with torch.no_grad():\n        with autocast():\n            for images, targets in dataloader:\n                images = [x.to(device) for x in images]\n        \n                outputs = model(images)\n                all_outputs.append(outputs.detach().cpu())\n    \n    outputs = torch.cat(all_outputs).numpy()\n    return outputs\n\ndef tta_predict(model, dataloaders, device):\n    all_outputs = []\n    for dataloader in dataloaders:\n        outputs = predict(model, dataloader, device)\n        all_outputs.append(outputs)\n    avg_outputs = np.mean(all_outputs, axis=0)\n    return avg_outputs\n\nNFOLD = 5\n\ndef kfold_predict(dataloaders):\n    device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')\n    \n    all_preds = []\n    for model_file in Path(SAVED_MODELS).glob('*.pth'):\n        fold_idx = int(model_file.name.split('.')[0].split('fold')[1])\n        if fold_idx >= NFOLD: continue\n        print(model_file.name)\n        model = MultiTargetRegressor(MODEL_NAME, pretrained=False, num_classes=3)\n        model.load_state_dict(torch.load(model_file))\n\n        preds = tta_predict(model, dataloaders, device)\n        all_preds.append(preds)\n\n    avg_preds = np.mean(all_preds, axis=0)\n    return avg_preds\n\n\npart_idx = sys.argv[1]\nagg_test_df = pd.read_pickle(f'agg_test_df_part{part_idx}.pkl')\n\ntest_loader = get_tta_dataloaders(agg_test_df, TARGET_IMAGE_SIZE, 96)\n\npreds = kfold_predict(test_loader)\n\nagg_test_df[['Dry_Green_g', 'Dry_Clover_g', 'Dry_Dead_g']] = preds\nagg_test_df['GDM_g'] = agg_test_df.Dry_Green_g + agg_test_df.Dry_Clover_g\nagg_test_df['Dry_Total_g'] = agg_test_df.GDM_g + agg_test_df.Dry_Dead_g\n\nagg_test_df.head()\n\ncols = ['Dry_Clover_g', 'Dry_Dead_g', 'Dry_Green_g', 'Dry_Total_g', 'GDM_g']\nsub_df = agg_test_df.set_index('sample_id_prefix')[cols].stack()\nsub_df = sub_df.reset_index()\nsub_df.columns = ['sample_id_prefix', 'target_name', 'target']\n\nsub_df['sample_id'] = sub_df.sample_id_prefix + '__' + sub_df.target_name\n\ncols = ['sample_id', 'target']\nsub_df[cols].to_csv(f'submission_part{part_idx}.csv', index=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-19T10:20:33.434638Z","iopub.execute_input":"2026-01-19T10:20:33.435277Z","iopub.status.idle":"2026-01-19T10:20:33.444365Z","shell.execute_reply.started":"2026-01-19T10:20:33.435254Z","shell.execute_reply":"2026-01-19T10:20:33.443793Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"%%bash\nCUDA_VISIBLE_DEVICES=0 python csiro_infer.py 0 &\nPID1=$!\n\nCUDA_VISIBLE_DEVICES=1 python csiro_infer.py 1 &\nPID2=$!\n\nwait $PID1 $PID2\n\necho '运行完成！'","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-19T10:20:49.167179Z","iopub.execute_input":"2026-01-19T10:20:49.167677Z","iopub.status.idle":"2026-01-19T10:22:32.156986Z","shell.execute_reply.started":"2026-01-19T10:20:49.167653Z","shell.execute_reply":"2026-01-19T10:22:32.156358Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"merge_submissions('submission_p1.csv')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-19T10:25:19.923874Z","iopub.execute_input":"2026-01-19T10:25:19.924435Z","iopub.status.idle":"2026-01-19T10:25:19.932688Z","shell.execute_reply.started":"2026-01-19T10:25:19.924408Z","shell.execute_reply":"2026-01-19T10:25:19.931834Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# [csiro pro 4x8 tiles](https://www.kaggle.com/code/jiazhuang/csiro-pro-4x8-tiles?scriptVersionId=287736897)","metadata":{}},{"cell_type":"code","source":"%%writefile csiro_infer.py\n\nimport sys\nfrom PIL import Image\nimport pandas as pd\nimport numpy as np\nimport matplotlib.pyplot as plt\n# %matplotlib inline\n\nfrom sklearn.model_selection import KFold, GroupKFold, StratifiedGroupKFold\nfrom tqdm.auto import tqdm\ntqdm.pandas()\n\nDATA_ROOT = '/kaggle/input/csiro-biomass/'\nTEST_IMAGE_DIR = './'\nMODEL_NAME = 'vit_large_patch16_dinov3_qkvb'\n\n## DataLoader\n\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms\n\ndef split_image(image, rows=4, cols=8, tile_width=250, tile_height=250):\n    \"\"\"\n    将图片平均切割成小图\n    \n    Args:\n        image: PIL.Image对象\n        rows: 行数\n        cols: 列数  \n        tile_width: 每个小图的宽度\n        tile_height: 每个小图的高度\n    \n    Returns:\n        包含所有小图的列表\n    \"\"\"\n    tiles = []\n    \n    for row in range(rows):\n        for col in range(cols):\n            # 计算切割区域的坐标\n            left = col * tile_width\n            top = row * tile_height\n            right = left + tile_width\n            bottom = top + tile_height\n            \n            # 切割图片\n            tile = image.crop((left, top, right, bottom))\n            tiles.append(tile)\n    \n    return tiles\n\nclass RegressionDataset(Dataset):\n    def __init__(self, data, transform=None, vertical_split=True):\n        self.data = data\n        self.transform = transform\n        self.vertical_split = vertical_split\n\n    def __len__(self):\n        return self.data.shape[0]\n\n    def __getitem__(self, idx):\n        item = self.data.iloc[idx]\n        # image = item.image\n        with Image.open(TEST_IMAGE_DIR + item.image_path) as img:\n            image = img.convert('RGB')\n            \n        targets = [item['Dry_Green_g'], item['Dry_Clover_g'], item['Dry_Dead_g']]\n        \n        if self.vertical_split:\n            # 分隔成8张小图\n            tiles = []\n            for part in split_image(image):\n                if self.transform:\n                    part = self.transform(part)\n                \n                tiles.append(part)\n            \n            # # 左边4张组合成一张，右边4张组合成一张\n            # top_row = torch.cat([tiles[0], tiles[1]], dim=2)\n            # bottom_row = torch.cat([tiles[4], tiles[5]], dim=2)\n            # left_image = torch.cat([top_row, bottom_row], dim=1)\n            \n            # top_row = torch.cat([tiles[2], tiles[3]], dim=2)\n            # bottom_row = torch.cat([tiles[6], tiles[7]], dim=2)\n            # right_image = torch.cat([top_row, bottom_row], dim=1)\n\n            # 左边16张组合成一张，右边16张组合成一张\n            row1 = torch.cat([tiles[0], tiles[1], tiles[2], tiles[3]], dim=2)\n            row2 = torch.cat([tiles[8], tiles[9], tiles[10], tiles[11]], dim=2)\n            row3 = torch.cat([tiles[16], tiles[17], tiles[18], tiles[19]], dim=2)\n            row4 = torch.cat([tiles[24], tiles[25], tiles[26], tiles[27]], dim=2)\n            left_image = torch.cat([row1, row2, row3, row4], dim=1)\n            \n            row1 = torch.cat([tiles[4], tiles[5], tiles[6], tiles[7]], dim=2)\n            row2 = torch.cat([tiles[12], tiles[13], tiles[14], tiles[15]], dim=2)\n            row3 = torch.cat([tiles[20], tiles[21], tiles[22], tiles[23]], dim=2)\n            row4 = torch.cat([tiles[28], tiles[29], tiles[30], tiles[31]], dim=2)\n            right_image = torch.cat([row1, row2, row3, row4], dim=1)\n            \n            return [left_image, right_image], targets\n        \n        else:\n            if self.transform:\n                image = self.transform(image)\n\n            return image, targets\n\ndef create_dataloader(data, target_image_size=(256, 256), batch_size=32, shuffle=True, aug=True, tta_transform=None):    \n    if aug:\n        transform = transforms.Compose([\n            transforms.Resize(target_image_size),\n            transforms.RandomHorizontalFlip(p=0.5),\n            transforms.RandomVerticalFlip(p=0.5),\n            transforms.RandomApply([transforms.RandomRotation([90, 90])], p=0.5),\n            transforms.ColorJitter(brightness=0.1, contrast=0.1, saturation=0.1, hue=0.05),\n            transforms.ToTensor(),\n            transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n        ])\n    \n    else:\n        if tta_transform:\n            transform = transforms.Compose([\n                transforms.Resize(target_image_size),\n                tta_transform,\n                transforms.ToTensor(),\n                transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n            ])\n        else:\n            transform = transforms.Compose([\n                transforms.Resize(target_image_size),\n\n                transforms.ToTensor(),\n                transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n            ])\n\n    dataset = RegressionDataset(data, transform=transform)\n    print('dataset size:', len(dataset))\n    dataloader = DataLoader(dataset, batch_size=batch_size, shuffle=shuffle, num_workers=4, pin_memory=True, prefetch_factor=2, persistent_workers=True)\n    return dataloader\n\ndef get_tta_dataloaders(data, target_image_size, batch_size):  # TTA还不够充分，每个子图也可以单独做aug\n    res = []\n    for transform in [None, transforms.RandomHorizontalFlip(p=1.0), transforms.RandomVerticalFlip(p=1.0), transforms.RandomRotation([90, 90])]:\n        res.append(\n            create_dataloader(data, target_image_size, batch_size, shuffle=False, aug=False, tta_transform=transform)\n        )\n    return res\n\n## Model\n\nimport timm\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\n\nTARGET_IMAGE_SIZE = (256, 256)\n\nclass AttentionLayer(nn.Module):\n    def __init__(self, hidden_dim):\n        super().__init__()\n        self.attention = nn.Linear(hidden_dim, 1)\n        \n    def forward(self, x):\n        # x shape: (batch, seq_len, hidden_dim)\n        scores = torch.tanh(self.attention(x))  # (batch, seq_len, 1)\n        weights = F.softmax(scores.squeeze(-1), dim=1)  # (batch, seq_len)\n        context = torch.sum(x * weights.unsqueeze(-1), dim=1)  # (batch, hidden_dim)\n        return context\n\nclass MultiTargetRegressor(nn.Module):\n    def __init__(self, model_name, pretrained=True, num_classes=3, dropout=0.0, freeze_backbone=False):\n        super().__init__()\n        if pretrained:\n            self.backbone = timm.create_model(model_name, pretrained=False, checkpoint_path=CHECKPOINT_PATH)\n        else:\n            self.backbone = timm.create_model(model_name, pretrained=pretrained)\n        \n        self.attention = AttentionLayer(self.backbone.num_features)\n        \n        self.dropout = nn.Dropout(dropout)\n        \n        def make_head():\n            return nn.Sequential(\n                nn.Linear(self.backbone.num_features, 8),\n                nn.ReLU(inplace=True),\n                nn.Dropout(dropout),\n                nn.Linear(8, 1),\n            )\n\n        self.head_green = make_head()\n        self.head_clover = make_head()\n        self.head_dead = make_head()\n        \n        self.softplus = nn.Softplus(beta=1.0)\n    \n        if freeze_backbone:\n            for p in self.backbone.parameters():\n                p.requires_grad = False\n    \n    \n    def forward(self, tiles):\n        tile_feats = []\n        for tile in tiles:\n            tile_feats.append( self.backbone.forward_features(tile)[:, self.backbone.num_prefix_tokens:] )  # B x (16*16) x H\n        \n        tile_feats = torch.cat(tile_feats, dim=1)  # B x L x H\n        \n        attended_green = self.attention(tile_feats)  # B x H\n        attended_clover = self.attention(tile_feats)  # B x H\n        attended_dead = self.attention(tile_feats)  # B x H\n\n        green = self.softplus(self.head_green(attended_green))    # Bx1\n        clover = self.softplus(self.head_clover(attended_clover))  # Bx1\n        dead = self.softplus(self.head_dead(attended_dead))   # Bx1\n\n        logits = torch.cat([green, clover, dead], dim=1)  # Bx3 \n\n        return logits\n\n## Inference\n\nimport os\nfrom pathlib import Path\n\ndef get_lastest_saved_models(model_root='/kaggle/input/csiro-simple-output/pytorch/default/'):    \n    latest = 1\n    for version in os.listdir(model_root):\n        try:\n            version = int(version)\n        except:\n            continue\n\n        if version > latest:\n            latest = version\n\n    return f'{model_root}/{latest}/trained_models/'\n\n# SAVED_MODELS = get_lastest_saved_models('/kaggle/input/csiro-local-pro/pytorch/default/')\nSAVED_MODELS = '/kaggle/input/csiro-local-pro/pytorch/default//2/trained_models/'\n\nfrom torch.cuda.amp import autocast\n\ndef predict(model, dataloader, device):\n    model.to(device)\n    model.eval()\n\n    all_outputs = []\n    with torch.no_grad():\n        with autocast():\n            for images, targets in dataloader:\n                images = [x.to(device) for x in images]\n        \n                outputs = model(images)\n                all_outputs.append(outputs.detach().cpu())\n    \n    outputs = torch.cat(all_outputs).numpy()\n    return outputs\n\n\ndef tta_predict(model, dataloaders, device):\n    all_outputs = []\n    for dataloader in dataloaders:\n        outputs = predict(model, dataloader, device)\n        all_outputs.append(outputs)\n    avg_outputs = np.mean(all_outputs, axis=0)\n    return avg_outputs\n\nNFOLD = 5\n\ndef kfold_predict(dataloaders):\n    device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')\n    \n    all_preds = []\n    for model_file in Path(SAVED_MODELS).glob('*.pth'):\n        fold_idx = int(model_file.name.split('.')[0].split('fold')[1])\n        if fold_idx >= NFOLD: continue\n        print(model_file.name)\n        model = MultiTargetRegressor(MODEL_NAME, pretrained=False, num_classes=3)\n        model.load_state_dict(torch.load(model_file))\n\n        preds = tta_predict(model, dataloaders, device)\n        all_preds.append(preds)\n\n    avg_preds = np.mean(all_preds, axis=0)\n    return avg_preds\n\n\npart_idx = sys.argv[1]\nagg_test_df = pd.read_pickle(f'agg_test_df_part{part_idx}.pkl')\n\ntest_loader = get_tta_dataloaders(agg_test_df, TARGET_IMAGE_SIZE, 32)\n\npreds = kfold_predict(test_loader)\n\n\nagg_test_df[['Dry_Green_g', 'Dry_Clover_g', 'Dry_Dead_g']] = preds\nagg_test_df['GDM_g'] = agg_test_df.Dry_Green_g + agg_test_df.Dry_Clover_g\nagg_test_df['Dry_Total_g'] = agg_test_df.GDM_g + agg_test_df.Dry_Dead_g\n\nagg_test_df.head()\n\n\ncols = ['Dry_Clover_g', 'Dry_Dead_g', 'Dry_Green_g', 'Dry_Total_g', 'GDM_g']\nsub_df = agg_test_df.set_index('sample_id_prefix')[cols].stack()\nsub_df = sub_df.reset_index()\nsub_df.columns = ['sample_id_prefix', 'target_name', 'target']\n\nsub_df['sample_id'] = sub_df.sample_id_prefix + '__' + sub_df.target_name\n\ncols = ['sample_id', 'target']\nsub_df[cols].to_csv(f'submission_part{part_idx}.csv', index=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-19T10:38:26.75947Z","iopub.execute_input":"2026-01-19T10:38:26.760254Z","iopub.status.idle":"2026-01-19T10:38:26.769357Z","shell.execute_reply.started":"2026-01-19T10:38:26.760225Z","shell.execute_reply":"2026-01-19T10:38:26.768759Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"%%bash\nCUDA_VISIBLE_DEVICES=0 python csiro_infer.py 0 &\nPID1=$!\n\nCUDA_VISIBLE_DEVICES=1 python csiro_infer.py 1 &\nPID2=$!\n\nwait $PID1 $PID2\n\necho '运行完成！'","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-19T10:38:31.895802Z","iopub.execute_input":"2026-01-19T10:38:31.896524Z","iopub.status.idle":"2026-01-19T10:41:25.24915Z","shell.execute_reply.started":"2026-01-19T10:38:31.896498Z","shell.execute_reply":"2026-01-19T10:41:25.248282Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"merge_submissions('submission_p2.csv')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-19T10:41:25.250369Z","iopub.execute_input":"2026-01-19T10:41:25.251026Z","iopub.status.idle":"2026-01-19T10:41:25.259519Z","shell.execute_reply.started":"2026-01-19T10:41:25.250996Z","shell.execute_reply":"2026-01-19T10:41:25.258939Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# [csiro pro v2 no_tta V3_tile_shulf](https://www.kaggle.com/code/jiazhuang/csiro-pro-v2-no-tta?scriptVersionId=289397179)","metadata":{}},{"cell_type":"code","source":"%%writefile csiro_infer.py\n\nimport sys\nfrom PIL import Image\nimport pandas as pd\nimport numpy as np\nimport matplotlib.pyplot as plt\n# %matplotlib inline\n\nfrom sklearn.model_selection import KFold, GroupKFold, StratifiedGroupKFold\nfrom tqdm.auto import tqdm\ntqdm.pandas()\n\nDATA_ROOT = '/kaggle/input/csiro-biomass/'\nTEST_IMAGE_DIR = './'\nMODEL_NAME = 'vit_large_patch16_dinov3_qkvb'\n\n## DataLoader\n\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms\n\ndef split_image(image, rows=2, cols=4, tile_width=500, tile_height=500):\n    \"\"\"\n    将图片平均切割成小图\n    \n    Args:\n        image: PIL.Image对象\n        rows: 行数\n        cols: 列数  \n        tile_width: 每个小图的宽度\n        tile_height: 每个小图的高度\n    \n    Returns:\n        包含所有小图的列表\n    \"\"\"\n    tiles = []\n    \n    for row in range(rows):\n        for col in range(cols):\n            # 计算切割区域的坐标\n            left = col * tile_width\n            top = row * tile_height\n            right = left + tile_width\n            bottom = top + tile_height\n            \n            # 切割图片\n            tile = image.crop((left, top, right, bottom))\n            tiles.append(tile)\n    \n    return tiles\n\nclass RegressionDataset(Dataset):\n    def __init__(self, data, transform=None, vertical_split=True):\n        self.data = data\n        self.transform = transform\n        self.vertical_split = vertical_split\n\n    def __len__(self):\n        return self.data.shape[0]\n\n    def __getitem__(self, idx):\n        item = self.data.iloc[idx]\n        # image = item.image\n        with Image.open(TEST_IMAGE_DIR + item.image_path) as img:\n            image = img.convert('RGB')\n\n        targets = [item['Dry_Green_g'], item['Dry_Clover_g'], item['Dry_Dead_g']]\n        \n        if self.vertical_split:\n            # 分隔成8张小图\n            tiles = []\n            for part in split_image(image):\n                if self.transform:\n                    part = self.transform(part)\n                \n                tiles.append(part)\n            \n            # 左边4张组合成一张，右边4张组合成一张\n            top_row = torch.cat([tiles[0], tiles[1]], dim=2)\n            bottom_row = torch.cat([tiles[4], tiles[5]], dim=2)\n            left_image = torch.cat([top_row, bottom_row], dim=1)\n            \n            top_row = torch.cat([tiles[2], tiles[3]], dim=2)\n            bottom_row = torch.cat([tiles[6], tiles[7]], dim=2)\n            right_image = torch.cat([top_row, bottom_row], dim=1)\n            \n            return [left_image, right_image], targets\n        \n        else:\n            if self.transform:\n                image = self.transform(image)\n\n            return image, targets\n\ndef create_dataloader(data, target_image_size=(256, 256), batch_size=32, shuffle=True, aug=True, tta_transform=None):    \n    if aug:\n        transform = transforms.Compose([\n            transforms.Resize(target_image_size),\n            transforms.RandomHorizontalFlip(p=0.5),\n            transforms.RandomVerticalFlip(p=0.5),\n            transforms.RandomApply([transforms.RandomRotation([90, 90])], p=0.5),\n            transforms.ColorJitter(brightness=0.1, contrast=0.1, saturation=0.1, hue=0.05),\n            transforms.ToTensor(),\n            transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n        ])\n    \n    else:\n        if tta_transform:\n            transform = transforms.Compose([\n                transforms.Resize(target_image_size),\n                tta_transform,\n                transforms.ToTensor(),\n                transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n            ])\n        else:\n            transform = transforms.Compose([\n                transforms.Resize(target_image_size),\n\n                transforms.ToTensor(),\n                transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n            ])\n\n    dataset = RegressionDataset(data, transform=transform)\n    print('dataset size:', len(dataset))\n    dataloader = DataLoader(dataset, batch_size=batch_size, shuffle=shuffle, num_workers=4, pin_memory=True, prefetch_factor=2, persistent_workers=True)\n    return dataloader\n\ndef get_tta_dataloaders(data, target_image_size, batch_size):  # TTA还不够充分，每个子图也可以单独做aug\n    res = []\n    # for transform in [None, transforms.RandomHorizontalFlip(p=1.0), transforms.RandomVerticalFlip(p=1.0), transforms.RandomRotation([90, 90])]:\n    for transform in [None]:\n        res.append(\n            create_dataloader(data, target_image_size, batch_size, shuffle=False, aug=False, tta_transform=transform)\n        )\n    return res\n\n## Model\n\nimport timm\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\n\nTARGET_IMAGE_SIZE = (256, 256)\n\nclass AttentionLayer(nn.Module):\n    def __init__(self, hidden_dim):\n        super().__init__()\n        self.attention = nn.Linear(hidden_dim, 1)\n        \n    def forward(self, x):\n        # x shape: (batch, seq_len, hidden_dim)\n        scores = torch.tanh(self.attention(x))  # (batch, seq_len, 1)\n        weights = F.softmax(scores.squeeze(-1), dim=1)  # (batch, seq_len)\n        context = torch.sum(x * weights.unsqueeze(-1), dim=1)  # (batch, hidden_dim)\n        return context\n\nclass MultiTargetRegressor(nn.Module):\n    def __init__(self, model_name, pretrained=True, num_classes=3, dropout=0.0, freeze_backbone=False):\n        super().__init__()\n        if pretrained:\n            self.backbone = timm.create_model(model_name, pretrained=False, checkpoint_path=CHECKPOINT_PATH)\n        else:\n            self.backbone = timm.create_model(model_name, pretrained=pretrained)\n        \n        self.attention = AttentionLayer(self.backbone.num_features)\n        \n        self.dropout = nn.Dropout(dropout)\n        \n        def make_head():\n            return nn.Sequential(\n                nn.Linear(self.backbone.num_features, 8),\n                nn.ReLU(inplace=True),\n                nn.Dropout(dropout),\n                nn.Linear(8, 1),\n            )\n\n        self.head_green = make_head()\n        self.head_clover = make_head()\n        self.head_dead = make_head()\n        \n        self.softplus = nn.Softplus(beta=1.0)\n    \n        if freeze_backbone:\n            for p in self.backbone.parameters():\n                p.requires_grad = False\n    \n    \n    def forward(self, tiles):\n        tile_feats = []\n        for tile in tiles:\n            tile_feats.append( self.backbone.forward_features(tile)[:, self.backbone.num_prefix_tokens:] )  # B x (16*16) x H\n        \n        tile_feats = torch.cat(tile_feats, dim=1)  # B x L x H\n        \n        attended_green = self.attention(tile_feats)  # B x H\n        attended_clover = self.attention(tile_feats)  # B x H\n        attended_dead = self.attention(tile_feats)  # B x H\n\n        green = self.softplus(self.head_green(attended_green))    # Bx1\n        clover = self.softplus(self.head_clover(attended_clover))  # Bx1\n        dead = self.softplus(self.head_dead(attended_dead))   # Bx1\n\n        logits = torch.cat([green, clover, dead], dim=1)  # Bx3 \n\n        return logits\n\n## Inference\n\nimport os\nfrom pathlib import Path\n\nimport os\nfrom pathlib import Path\n\ndef get_lastest_saved_models(model_root='/kaggle/input/csiro-simple-output/pytorch/default/'):    \n    latest = 1\n    for version in os.listdir(model_root):\n        try:\n            version = int(version)\n        except:\n            continue\n\n        if version > latest:\n            latest = version\n\n    return f'{model_root}/{latest}/trained_models/'\n\n# SAVED_MODELS = get_lastest_saved_models('/kaggle/input/csiro-local-pro/pytorch/default/')\nSAVED_MODELS = '/kaggle/input/csiro-local-pro/pytorch/default/7/trained_models/'\n\nfrom torch.cuda.amp import autocast\n\n\ndef predict(model, dataloader, device):\n    model.to(device)\n    model.eval()\n\n    all_outputs = []\n    with torch.no_grad():\n        with autocast():\n            for images, targets in dataloader:\n                images = [x.to(device) for x in images]\n        \n                outputs = model(images)\n                all_outputs.append(outputs.detach().cpu())\n    \n    outputs = torch.cat(all_outputs).numpy()\n    return outputs\n\n\ndef tta_predict(model, dataloaders, device):\n    all_outputs = []\n    for dataloader in dataloaders:\n        outputs = predict(model, dataloader, device)\n        all_outputs.append(outputs)\n    avg_outputs = np.mean(all_outputs, axis=0)\n    return avg_outputs\n\nNFOLD = 5\n\ndef kfold_predict(dataloaders):\n    device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')\n    \n    all_preds = []\n    for model_file in Path(SAVED_MODELS).glob('*.pth'):\n        fold_idx = int(model_file.name.split('.')[0].split('fold')[1])\n        if fold_idx >= NFOLD: continue\n        print(model_file.name)\n        model = MultiTargetRegressor(MODEL_NAME, pretrained=False, num_classes=3)\n        model.load_state_dict(torch.load(model_file))\n\n        preds = tta_predict(model, dataloaders, device)\n        all_preds.append(preds)\n\n    avg_preds = np.mean(all_preds, axis=0)\n    return avg_preds\n\npart_idx = sys.argv[1]\nagg_test_df = pd.read_pickle(f'agg_test_df_part{part_idx}.pkl')\n\ntest_loader = get_tta_dataloaders(agg_test_df, TARGET_IMAGE_SIZE, 96)\n\npreds = kfold_predict(test_loader)\n\nagg_test_df[['Dry_Green_g', 'Dry_Clover_g', 'Dry_Dead_g']] = preds\nagg_test_df['GDM_g'] = agg_test_df.Dry_Green_g + agg_test_df.Dry_Clover_g\nagg_test_df['Dry_Total_g'] = agg_test_df.GDM_g + agg_test_df.Dry_Dead_g\n\nagg_test_df.head()\n\n\ncols = ['Dry_Clover_g', 'Dry_Dead_g', 'Dry_Green_g', 'Dry_Total_g', 'GDM_g']\nsub_df = agg_test_df.set_index('sample_id_prefix')[cols].stack()\nsub_df = sub_df.reset_index()\nsub_df.columns = ['sample_id_prefix', 'target_name', 'target']\n\nsub_df['sample_id'] = sub_df.sample_id_prefix + '__' + sub_df.target_name\n\ncols = ['sample_id', 'target']\nsub_df[cols].to_csv(f'submission_part{part_idx}.csv', index=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-19T10:42:53.658017Z","iopub.execute_input":"2026-01-19T10:42:53.658542Z","iopub.status.idle":"2026-01-19T10:42:53.666818Z","shell.execute_reply.started":"2026-01-19T10:42:53.65852Z","shell.execute_reply":"2026-01-19T10:42:53.666176Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"%%bash\nCUDA_VISIBLE_DEVICES=0 python csiro_infer.py 0 &\nPID1=$!\n\nCUDA_VISIBLE_DEVICES=1 python csiro_infer.py 1 &\nPID2=$!\n\nwait $PID1 $PID2\n\necho '运行完成！'","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-19T10:42:55.602174Z","iopub.execute_input":"2026-01-19T10:42:55.602655Z","iopub.status.idle":"2026-01-19T10:44:33.052378Z","shell.execute_reply.started":"2026-01-19T10:42:55.602632Z","shell.execute_reply":"2026-01-19T10:44:33.051785Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"merge_submissions('submission_p3.csv')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-19T10:44:33.0536Z","iopub.execute_input":"2026-01-19T10:44:33.053891Z","iopub.status.idle":"2026-01-19T10:44:33.061988Z","shell.execute_reply.started":"2026-01-19T10:44:33.053871Z","shell.execute_reply":"2026-01-19T10:44:33.061349Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# [csiro-pro-v2-no-tta v2-10-fold](https://www.kaggle.com/code/jiazhuang/csiro-pro-v2-no-tta?scriptVersionId=289152832)","metadata":{}},{"cell_type":"code","source":"%%writefile csiro_infer.py\n\nimport sys\n\nfrom PIL import Image\nimport pandas as pd\nimport numpy as np\nimport matplotlib.pyplot as plt\n# %matplotlib inline\n\nfrom sklearn.model_selection import KFold, GroupKFold, StratifiedGroupKFold\nfrom tqdm.auto import tqdm\ntqdm.pandas()\n\nDATA_ROOT = '/kaggle/input/csiro-biomass/'\nTEST_IMAGE_DIR = './'\nMODEL_NAME = 'vit_large_patch16_dinov3_qkvb'\n\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms\n\ndef split_image(image, rows=2, cols=4, tile_width=500, tile_height=500):\n    \"\"\"\n    将图片平均切割成小图\n    \n    Args:\n        image: PIL.Image对象\n        rows: 行数\n        cols: 列数  \n        tile_width: 每个小图的宽度\n        tile_height: 每个小图的高度\n    \n    Returns:\n        包含所有小图的列表\n    \"\"\"\n    tiles = []\n    \n    for row in range(rows):\n        for col in range(cols):\n            # 计算切割区域的坐标\n            left = col * tile_width\n            top = row * tile_height\n            right = left + tile_width\n            bottom = top + tile_height\n            \n            # 切割图片\n            tile = image.crop((left, top, right, bottom))\n            tiles.append(tile)\n    \n    return tiles\n\nclass RegressionDataset(Dataset):\n    def __init__(self, data, transform=None, vertical_split=True):\n        self.data = data\n        self.transform = transform\n        self.vertical_split = vertical_split\n\n    def __len__(self):\n        return self.data.shape[0]\n\n    def __getitem__(self, idx):\n        item = self.data.iloc[idx]\n        # image = item.image\n        with Image.open(TEST_IMAGE_DIR + item.image_path) as img:\n            image = img.convert('RGB')\n            \n        targets = [item['Dry_Green_g'], item['Dry_Clover_g'], item['Dry_Dead_g']]\n        \n        if self.vertical_split:\n            # 分隔成8张小图\n            tiles = []\n            for part in split_image(image):\n                if self.transform:\n                    part = self.transform(part)\n                \n                tiles.append(part)\n            \n            # 左边4张组合成一张，右边4张组合成一张\n            top_row = torch.cat([tiles[0], tiles[1]], dim=2)\n            bottom_row = torch.cat([tiles[4], tiles[5]], dim=2)\n            left_image = torch.cat([top_row, bottom_row], dim=1)\n            \n            top_row = torch.cat([tiles[2], tiles[3]], dim=2)\n            bottom_row = torch.cat([tiles[6], tiles[7]], dim=2)\n            right_image = torch.cat([top_row, bottom_row], dim=1)\n            \n            return [left_image, right_image], targets\n        \n        else:\n            if self.transform:\n                image = self.transform(image)\n\n            return image, targets\n\ndef create_dataloader(data, target_image_size=(256, 256), batch_size=32, shuffle=True, aug=True, tta_transform=None):    \n    if aug:\n        transform = transforms.Compose([\n            transforms.Resize(target_image_size),\n            transforms.RandomHorizontalFlip(p=0.5),\n            transforms.RandomVerticalFlip(p=0.5),\n            transforms.RandomApply([transforms.RandomRotation([90, 90])], p=0.5),\n            transforms.ColorJitter(brightness=0.1, contrast=0.1, saturation=0.1, hue=0.05),\n            transforms.ToTensor(),\n            transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n        ])\n    \n    else:\n        if tta_transform:\n            transform = transforms.Compose([\n                transforms.Resize(target_image_size),\n                tta_transform,\n                transforms.ToTensor(),\n                transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n            ])\n        else:\n            transform = transforms.Compose([\n                transforms.Resize(target_image_size),\n\n                transforms.ToTensor(),\n                transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n            ])\n\n    dataset = RegressionDataset(data, transform=transform)\n    print('dataset size:', len(dataset))\n    dataloader = DataLoader(dataset, batch_size=batch_size, shuffle=shuffle, num_workers=4, pin_memory=True, prefetch_factor=2, persistent_workers=True)\n    return dataloader\n\ndef get_tta_dataloaders(data, target_image_size, batch_size):  # TTA还不够充分，每个子图也可以单独做aug\n    res = []\n    # for transform in [None, transforms.RandomHorizontalFlip(p=1.0), transforms.RandomVerticalFlip(p=1.0), transforms.RandomRotation([90, 90])]:\n    for transform in [None]:\n        res.append(\n            create_dataloader(data, target_image_size, batch_size, shuffle=False, aug=False, tta_transform=transform)\n        )\n    return res\n\nimport timm\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\n\nTARGET_IMAGE_SIZE = (256, 256)\n\nclass AttentionLayer(nn.Module):\n    def __init__(self, hidden_dim):\n        super().__init__()\n        self.attention = nn.Linear(hidden_dim, 1)\n        \n    def forward(self, x):\n        # x shape: (batch, seq_len, hidden_dim)\n        scores = torch.tanh(self.attention(x))  # (batch, seq_len, 1)\n        weights = F.softmax(scores.squeeze(-1), dim=1)  # (batch, seq_len)\n        context = torch.sum(x * weights.unsqueeze(-1), dim=1)  # (batch, hidden_dim)\n        return context\n\nclass MultiTargetRegressor(nn.Module):\n    def __init__(self, model_name, pretrained=True, num_classes=3, dropout=0.0, freeze_backbone=False):\n        super().__init__()\n        if pretrained:\n            self.backbone = timm.create_model(model_name, pretrained=False, checkpoint_path=CHECKPOINT_PATH)\n        else:\n            self.backbone = timm.create_model(model_name, pretrained=pretrained)\n        \n        self.attention = AttentionLayer(self.backbone.num_features)\n        \n        self.dropout = nn.Dropout(dropout)\n        \n        def make_head():\n            return nn.Sequential(\n                nn.Linear(self.backbone.num_features, 8),\n                nn.ReLU(inplace=True),\n                nn.Dropout(dropout),\n                nn.Linear(8, 1),\n            )\n\n        self.head_green = make_head()\n        self.head_clover = make_head()\n        self.head_dead = make_head()\n        \n        self.softplus = nn.Softplus(beta=1.0)\n    \n        if freeze_backbone:\n            for p in self.backbone.parameters():\n                p.requires_grad = False\n    \n    \n    def forward(self, tiles):\n        tile_feats = []\n        for tile in tiles:\n            tile_feats.append( self.backbone.forward_features(tile)[:, self.backbone.num_prefix_tokens:] )  # B x (16*16) x H\n        \n        tile_feats = torch.cat(tile_feats, dim=1)  # B x L x H\n        \n        attended_green = self.attention(tile_feats)  # B x H\n        attended_clover = self.attention(tile_feats)  # B x H\n        attended_dead = self.attention(tile_feats)  # B x H\n\n        green = self.softplus(self.head_green(attended_green))    # Bx1\n        clover = self.softplus(self.head_clover(attended_clover))  # Bx1\n        dead = self.softplus(self.head_dead(attended_dead))   # Bx1\n\n        logits = torch.cat([green, clover, dead], dim=1)  # Bx3 \n\n        return logits\n\nimport os\nfrom pathlib import Path\n\nSAVED_MODELS = '/kaggle/input/csiro-local-pro/pytorch/default/5/trained_models/'\n\n\nfrom torch.cuda.amp import autocast\ndef predict(model, dataloader, device):\n    model.to(device)\n    model.eval()\n\n    all_outputs = []\n    with torch.no_grad():\n        with autocast():\n            for images, targets in dataloader:\n                images = [x.to(device) for x in images]\n        \n                outputs = model(images)\n                all_outputs.append(outputs.detach().cpu())\n    \n    outputs = torch.cat(all_outputs).numpy()\n    return outputs\n\ndef tta_predict(model, dataloaders, device):\n    all_outputs = []\n    for dataloader in dataloaders:\n        outputs = predict(model, dataloader, device)\n        all_outputs.append(outputs)\n    avg_outputs = np.mean(all_outputs, axis=0)\n    return avg_outputs\n\nNFOLD = 10\n\ndef kfold_predict(dataloaders):\n    device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')\n    \n    all_preds = []\n    for model_file in Path(SAVED_MODELS).glob('*.pth'):\n        fold_idx = int(model_file.name.split('.')[0].split('fold')[1])\n        if fold_idx >= NFOLD: continue\n        print(model_file.name)\n        model = MultiTargetRegressor(MODEL_NAME, pretrained=False, num_classes=3)\n        model.load_state_dict(torch.load(model_file))\n\n        preds = tta_predict(model, dataloaders, device)\n        all_preds.append(preds)\n\n    avg_preds = np.mean(all_preds, axis=0)\n    return avg_preds\n\n\npart_idx = sys.argv[1]\nagg_test_df = pd.read_pickle(f'agg_test_df_part{part_idx}.pkl')\n\ntest_loader = get_tta_dataloaders(agg_test_df, TARGET_IMAGE_SIZE, 96)\n\npreds = kfold_predict(test_loader)\n\nagg_test_df[['Dry_Green_g', 'Dry_Clover_g', 'Dry_Dead_g']] = preds\nagg_test_df['GDM_g'] = agg_test_df.Dry_Green_g + agg_test_df.Dry_Clover_g\nagg_test_df['Dry_Total_g'] = agg_test_df.GDM_g + agg_test_df.Dry_Dead_g\n\ncols = ['Dry_Clover_g', 'Dry_Dead_g', 'Dry_Green_g', 'Dry_Total_g', 'GDM_g']\nsub_df = agg_test_df.set_index('sample_id_prefix')[cols].stack()\nsub_df = sub_df.reset_index()\nsub_df.columns = ['sample_id_prefix', 'target_name', 'target']\n\nsub_df['sample_id'] = sub_df.sample_id_prefix + '__' + sub_df.target_name\n\ncols = ['sample_id', 'target']\nsub_df[cols].to_csv(f'submission_part{part_idx}.csv', index=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-19T10:46:35.482987Z","iopub.execute_input":"2026-01-19T10:46:35.483304Z","iopub.status.idle":"2026-01-19T10:46:35.492037Z","shell.execute_reply.started":"2026-01-19T10:46:35.483282Z","shell.execute_reply":"2026-01-19T10:46:35.491295Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"%%bash\nCUDA_VISIBLE_DEVICES=0 python csiro_infer.py 0 &\nPID1=$!\n\nCUDA_VISIBLE_DEVICES=1 python csiro_infer.py 1 &\nPID2=$!\n\nwait $PID1 $PID2\n\necho '运行完成！'","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-19T10:46:42.756132Z","iopub.execute_input":"2026-01-19T10:46:42.756394Z","iopub.status.idle":"2026-01-19T10:49:33.247806Z","shell.execute_reply.started":"2026-01-19T10:46:42.756376Z","shell.execute_reply":"2026-01-19T10:49:33.247047Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"merge_submissions('submission_10fold.csv')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-19T10:49:33.248937Z","iopub.execute_input":"2026-01-19T10:49:33.249156Z","iopub.status.idle":"2026-01-19T10:49:33.257449Z","shell.execute_reply.started":"2026-01-19T10:49:33.249138Z","shell.execute_reply":"2026-01-19T10:49:33.256663Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# [csiro crop square V1](https://www.kaggle.com/code/jiazhuang/csiro-crop-square?scriptVersionId=289391481)","metadata":{}},{"cell_type":"code","source":"from PIL import Image\nimport pandas as pd\nimport numpy as np\nimport matplotlib.pyplot as plt\n%matplotlib inline\n\nfrom sklearn.model_selection import KFold, GroupKFold, StratifiedGroupKFold\nfrom tqdm.auto import tqdm\ntqdm.pandas()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"DATA_ROOT = '/kaggle/input/csiro-biomass/'\nMODEL_NAME = 'vit_huge_plus_patch16_dinov3_qkvb'","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## DataLoader","metadata":{}},{"cell_type":"code","source":"from torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def crop_image(image, crop_width=1000, crop_height=1000, start_x='random'):\n    ori_width, ori_height = image.size\n    assert ori_height == crop_height\n\n    # 计算起点\n    if start_x == 'random':\n        max_x = ori_width - crop_width\n        x = random.randint(0, max_x)\n    else:\n        x = start_x\n        assert isinstance(x, int)\n        assert 0 <= x <= 1000\n\n    # 裁切\n    cropped = image.crop((x, 0, x + crop_width, crop_height))\n    return cropped","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class RegressionDataset(Dataset):\n    def __init__(self, data, transform=None, start_x='random'):\n        self.data = data\n        self.transform = transform\n        self.start_x = start_x\n\n    def __len__(self):\n        return self.data.shape[0]\n\n    def __getitem__(self, idx):\n        item = self.data.iloc[idx]\n        image = item.image\n        targets = [item['Dry_Green_g'], item['Dry_Clover_g'], item['Dry_Dead_g']]\n\n        cropped = crop_image(image, start_x=self.start_x)\n        if self.transform:\n            cropped = self.transform(cropped)\n\n        return [cropped], targets","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def create_dataloader(data, target_image_size=(256, 256), batch_size=32, shuffle=True, aug=True, tta_transform=None, start_x='random'):    \n    if aug:\n        transform = transforms.Compose([\n            transforms.Resize(target_image_size),\n            transforms.RandomHorizontalFlip(p=0.5),\n            transforms.RandomVerticalFlip(p=0.5),\n            transforms.RandomApply([transforms.RandomRotation([90, 90])], p=0.5),\n            transforms.ColorJitter(brightness=0.1, contrast=0.1, saturation=0.1, hue=0.05),\n            transforms.ToTensor(),\n            transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n        ])\n    \n    else:\n        if tta_transform:\n            transform = transforms.Compose([\n                transforms.Resize(target_image_size),\n                tta_transform,\n                transforms.ToTensor(),\n                transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n            ])\n        else:\n            transform = transforms.Compose([\n                transforms.Resize(target_image_size),\n\n                transforms.ToTensor(),\n                transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n            ])\n\n    dataset = RegressionDataset(data, transform=transform, start_x=start_x)\n    print('dataset size:', len(dataset))\n    dataloader = DataLoader(dataset, batch_size=batch_size, shuffle=shuffle, num_workers=4, pin_memory=True, prefetch_factor=2, persistent_workers=True)\n    return dataloader","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"NUM_TTA = 3\n\ndef get_tta_dataloaders(data, target_image_size, batch_size):\n    res = []\n    points = [0, 250, 500, 750, 1000] if NUM_TTA == 5 else [0, 500, 1000]\n    for start_x in points:\n        res.append(\n            create_dataloader(data, target_image_size, batch_size, shuffle=False, aug=False, tta_transform=None, start_x=start_x)\n        )\n    return res","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Model","metadata":{}},{"cell_type":"code","source":"import timm\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"TARGET_IMAGE_SIZE = (512, 512)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class AttentionLayer(nn.Module):\n    def __init__(self, hidden_dim):\n        super().__init__()\n        self.attention = nn.Linear(hidden_dim, 1)\n        \n    def forward(self, x):\n        # x shape: (batch, seq_len, hidden_dim)\n        scores = torch.tanh(self.attention(x))  # (batch, seq_len, 1)\n        weights = F.softmax(scores.squeeze(-1), dim=1)  # (batch, seq_len)\n        context = torch.sum(x * weights.unsqueeze(-1), dim=1)  # (batch, hidden_dim)\n        return context","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class MultiTargetRegressor(nn.Module):\n    def __init__(self, model_name, pretrained=True, num_classes=3, dropout=0.0, freeze_backbone=False):\n        super().__init__()\n        if pretrained:\n            self.backbone = timm.create_model(model_name, pretrained=False, checkpoint_path=CHECKPOINT_PATH)\n        else:\n            self.backbone = timm.create_model(model_name, pretrained=pretrained)\n        \n        self.attention = AttentionLayer(self.backbone.num_features)\n        \n        self.dropout = nn.Dropout(dropout)\n        \n        def make_head():\n            return nn.Sequential(\n                nn.Linear(self.backbone.num_features, 8),\n                nn.ReLU(inplace=True),\n                nn.Dropout(dropout),\n                nn.Linear(8, 1),\n            )\n\n        self.head_green = make_head()\n        self.head_clover = make_head()\n        self.head_dead = make_head()\n        \n        self.softplus = nn.Softplus(beta=1.0)\n    \n        if freeze_backbone:\n            for p in self.backbone.parameters():\n                p.requires_grad = False\n    \n    \n    def forward(self, tiles):\n        tile_feats = []\n        for tile in tiles:\n            tile_feats.append( self.backbone.forward_features(tile)[:, self.backbone.num_prefix_tokens:] )  # B x (16*16) x H\n        \n        tile_feats = torch.cat(tile_feats, dim=1)  # B x L x H\n        \n        attended_green = self.attention(tile_feats)  # B x H\n        attended_clover = self.attention(tile_feats)  # B x H\n        attended_dead = self.attention(tile_feats)  # B x H\n\n        green = self.softplus(self.head_green(attended_green))    # Bx1\n        clover = self.softplus(self.head_clover(attended_clover))  # Bx1\n        dead = self.softplus(self.head_dead(attended_dead))   # Bx1\n\n        logits = torch.cat([green, clover, dead], dim=1)  # Bx3 \n\n        return logits","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Inference","metadata":{}},{"cell_type":"code","source":"import os\nfrom pathlib import Path","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def get_lastest_saved_models(model_root='/kaggle/input/csiro-simple-output/pytorch/default/'):    \n    latest = 1\n    for version in os.listdir(model_root):\n        try:\n            version = int(version)\n        except:\n            continue\n\n        if version > latest:\n            latest = version\n\n    return f'{model_root}/{latest}/trained_models/'","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# SAVED_MODELS = get_lastest_saved_models('/kaggle/input/csiro-local-pro/pytorch/default/')\nSAVED_MODELS = '/kaggle/input/csiro-local-pro/pytorch/default/6/trained_models/'","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def predict(model, dataloader, device):\n    model.to(device)\n    model.eval()\n\n    all_outputs = []\n    with torch.no_grad():\n        for images, targets in dataloader:\n            images = [x.to(device) for x in images]\n    \n            outputs = model(images)\n            all_outputs.append(outputs.detach().cpu())\n    \n    outputs = torch.cat(all_outputs).numpy()\n    return outputs\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def tta_predict(model, dataloaders, device):\n    all_outputs = []\n    for dataloader in dataloaders:\n        outputs = predict(model, dataloader, device)\n        all_outputs.append(outputs)\n    avg_outputs = np.mean(all_outputs, axis=0)\n    return avg_outputs\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"NFOLD = 5\n\ndef kfold_predict(dataloaders):\n    device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')\n    \n    all_preds = []\n    for model_file in Path(SAVED_MODELS).glob('*.pth'):\n        fold_idx = int(model_file.name.split('.')[0].split('fold')[1])\n        if fold_idx >= NFOLD: continue\n        print(model_file.name)\n        model = MultiTargetRegressor(MODEL_NAME, pretrained=False, num_classes=3)\n        model.load_state_dict(torch.load(model_file))\n\n        preds = tta_predict(model, dataloaders, device)\n        all_preds.append(preds)\n\n    avg_preds = np.mean(all_preds, axis=0)\n    return avg_preds\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# test_df = pd.read_csv(DATA_ROOT + 'test.csv')\n\n# test_df['target'] = 0.0\n# test_df[['sample_id_prefix', 'sample_id_suffix']] = test_df.sample_id.str.split('__', expand=True)\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# cols = ['sample_id_prefix', 'image_path']\n# agg_test_df = test_df.groupby(cols).apply(lambda df: df.set_index('target_name').target)\n# agg_test_df.reset_index(inplace=True)\n# agg_test_df.columns.name = None\n\n# agg_test_df['image'] = agg_test_df.image_path.progress_apply(\n#     lambda path: Image.open(DATA_ROOT + path).convert('RGB')\n# )\n\n# agg_test_df.head()\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_loader = get_tta_dataloaders(agg_test_df, TARGET_IMAGE_SIZE, 32) ","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# preds = kfold_predict(test_loader)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# agg_test_df[['Dry_Green_g', 'Dry_Clover_g', 'Dry_Dead_g']] = preds\n# agg_test_df['GDM_g'] = agg_test_df.Dry_Green_g + agg_test_df.Dry_Clover_g\n# agg_test_df['Dry_Total_g'] = agg_test_df.GDM_g + agg_test_df.Dry_Dead_g","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# agg_test_df.head()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# cols = ['Dry_Clover_g', 'Dry_Dead_g', 'Dry_Green_g', 'Dry_Total_g', 'GDM_g']\n# sub_df = agg_test_df.set_index('sample_id_prefix')[cols].stack()\n# sub_df = sub_df.reset_index()\n# sub_df.columns = ['sample_id_prefix', 'target_name', 'target']\n\n# sub_df['sample_id'] = sub_df.sample_id_prefix + '__' + sub_df.target_name","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# cols = ['sample_id', 'target']\n# sub_df[cols].to_csv('submission_p4.csv', index=False)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# !head submission_p4.csv","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# flush()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# siglip","metadata":{}},{"cell_type":"code","source":"%%writefile csiro_infer.py\n\nimport os\nimport gc\nimport random\nimport warnings\nimport numpy as np\nimport pandas as pd\nimport cv2\nimport torch\nfrom PIL import Image\nfrom tqdm.auto import tqdm\nfrom pathlib import Path\nfrom copy import deepcopy\nfrom dataclasses import dataclass\n\n# Sklearn & Models\nfrom sklearn.preprocessing import StandardScaler\nfrom sklearn.decomposition import PCA\nfrom sklearn.cross_decomposition import PLSRegression\nfrom sklearn.mixture import GaussianMixture\nfrom sklearn.ensemble import GradientBoostingRegressor, HistGradientBoostingRegressor\nfrom lightgbm import LGBMRegressor\nfrom catboost import CatBoostRegressor\nfrom transformers import AutoModel, AutoImageProcessor, AutoTokenizer\n\nfrom torch.cuda.amp import autocast\n\n\n# Suppress warnings\nwarnings.filterwarnings('ignore')\nos.environ[\"TOKENIZERS_PARALLELISM\"] = \"false\"\n\n# =========================================================================================\n# 1. CONFIGURATION & SEEDING\n# =========================================================================================\n@dataclass\nclass Config:\n    DATA_PATH: Path = Path(\"/kaggle/input/csiro-biomass/\")\n    SPLIT_PATH: Path = Path(\"/kaggle/input/csiro-datasplit/csiro_data_split.csv\")\n    SIGLIP_PATH: str = \"/kaggle/input/google-siglip-so400m-patch14-384/transformers/default/1\"\n    \n    SEED: int = 42\n    DEVICE: str = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n    PATCH_SIZE: int = 520\n    OVERLAP: int = 16\n    \n    # Target definitions\n    TARGET_NAMES = ['Dry_Clover_g', 'Dry_Dead_g', 'Dry_Green_g', 'Dry_Total_g', 'GDM_g']\n    TARGET_MAX = {\n        \"Dry_Clover_g\": 71.7865,\n        \"Dry_Dead_g\": 83.8407,\n        \"Dry_Green_g\": 157.9836,\n        \"Dry_Total_g\": 185.70,\n        \"GDM_g\": 157.9836,\n    }\n\ncfg = Config()\n\ndef seeding(SEED):\n    np.random.seed(SEED)\n    random.seed(SEED)\n    os.environ['PYTHONHASHSEED'] = str(SEED)\n    torch.manual_seed(SEED)\n    if torch.cuda.is_available(): \n        torch.cuda.manual_seed(SEED)\n        torch.backends.cudnn.deterministic = True\n        torch.backends.cudnn.benchmark = False\n\nseeding(cfg.SEED)\n\n# =========================================================================================\n# 2. DATA LOADING & PRE-PROCESSING\n# =========================================================================================\ndef pivot_table(df: pd.DataFrame) -> pd.DataFrame:\n    if 'target' in df.columns.tolist():\n        # Train data\n        df_pt = pd.pivot_table(\n            df, \n            values='target', \n            index=['image_path', 'Sampling_Date', 'State', 'Species', 'Pre_GSHH_NDVI', 'Height_Ave_cm'], \n            columns='target_name', \n            aggfunc='mean'\n        ).reset_index()\n    else:\n        # Test data\n        df['target'] = 0\n        df_pt = pd.pivot_table(\n            df, \n            values='target', \n            index='image_path', \n            columns='target_name', \n            aggfunc='mean'\n        ).reset_index()\n    return df_pt\n\ndef melt_table(df: pd.DataFrame) -> pd.DataFrame:\n    melted = df.melt(\n        id_vars='image_path',\n        value_vars=cfg.TARGET_NAMES,\n        var_name='target_name',\n        value_name='target'\n    )\n    # Create sample_id matching submission format\n    melted['sample_id'] = (\n        melted['image_path']\n        .str.replace(r'^.*/', '', regex=True)\n        .str.replace('.jpg', '', regex=False)\n        + '__' + melted['target_name']\n    )\n    return melted[['sample_id', 'target']]\n\ndef post_process_biomass(df_preds):\n    \"\"\"\n    Derive GDM_g and Dry_Total_g from primary predictions.\n    IMPORTANT: Keeps Dry_Clover_g fixed at 0.0 and does NOT modify Dry_Green_g or Dry_Dead_g.\n    \"\"\"\n    ordered_cols = [\"Dry_Green_g\", \"Dry_Clover_g\", \"Dry_Dead_g\", \"GDM_g\", \"Dry_Total_g\"]\n    \n    # Ensure cols exist\n    for c in ordered_cols:\n        if c not in df_preds.columns:\n            df_preds[c] = 0.0\n    \n    df_out = df_preds.copy()\n    \n    # Keep Dry_Clover_g fixed at 0.0 (should already be 0.0 from model prediction)\n    df_out['Dry_Clover_g'] = 0.0\n    \n    # Keep Dry_Green_g and Dry_Dead_g as predicted (NO MODIFICATION)\n    # Only derive GDM_g and Dry_Total_g\n    df_out['GDM_g'] = df_out['Dry_Green_g'] + df_out['Dry_Clover_g']\n    df_out['Dry_Total_g'] = df_out['GDM_g'] + df_out['Dry_Dead_g']\n    \n    # Clip only derived targets to non-negative\n    df_out['GDM_g'] = df_out['GDM_g'].clip(lower=0.0)\n    df_out['Dry_Total_g'] = df_out['Dry_Total_g'].clip(lower=0.0)\n    \n    return df_out\n\nprint(\"Loading Data...\")\n# Load Train (Metadata Split)\ntrain_df = pd.read_csv(cfg.SPLIT_PATH) \n\n# --- FIX: Remove pre-existing embedding columns to prevent duplication ---\ncols_to_keep = [c for c in train_df.columns if not c.startswith('emb')]\ntrain_df = train_df[cols_to_keep]\n# -----------------------------------------------------------------------\n\n# Ensure train paths match local environment\nif not str(train_df['image_path'].iloc[0]).startswith('/'):\n     train_df['image_path'] = train_df['image_path'].apply(lambda p: str(cfg.DATA_PATH / 'train' / os.path.basename(p)))\n\n# Load Test\ntest_df_raw = pd.read_csv(cfg.DATA_PATH / 'test.csv')\ntest_df = pivot_table(test_df_raw)\ntest_df['image_path'] = test_df['image_path'].apply(lambda p: str(cfg.DATA_PATH / p))\n\n# =========================================================================================\n# 3. FEATURE EXTRACTION: SIGLIP IMAGE EMBEDDINGS\n# =========================================================================================\ndef split_image(image, patch_size=520, overlap=16):\n    h, w, c = image.shape\n    stride = patch_size - overlap\n    patches = []\n    for y in range(0, h, stride):\n        for x in range(0, w, stride):\n            y2 = min(y + patch_size, h)\n            x2 = min(x + patch_size, w)\n            y1 = max(0, y2 - patch_size) # Ensure fixed size\n            x1 = max(0, x2 - patch_size)\n            patch = image[y1:y2, x1:x2, :]\n            patches.append(patch)\n    return patches\n\ndef compute_embeddings(model_path, df, batch_size=32):\n    print(f\"Computing Embeddings for {len(df)} images...\")\n    model = AutoModel.from_pretrained(model_path, local_files_only=True).eval().to(cfg.DEVICE)\n    model.eval()\n    processor = AutoImageProcessor.from_pretrained(model_path)\n    \n    EMBEDDINGS = []\n    \n    for _, row in tqdm(df.iterrows(), total=len(df)):\n        try:\n            img = cv2.imread(row['image_path'])\n            if img is None: raise ValueError(\"Image not found\")\n            img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n            patches = split_image(img, patch_size=cfg.PATCH_SIZE, overlap=cfg.OVERLAP)\n            images = [Image.fromarray(p) for p in patches]\n            \n            # Batch process patches\n            inputs = processor(images=images, return_tensors=\"pt\").to(cfg.DEVICE)\n            with torch.no_grad():\n                with autocast(enabled=True):\n                    features = model.get_image_features(**inputs)\n\n                # 将结果转换回 float32 以确保数值稳定性\n                features = features.float()\n            \n            # Average pooling of patches\n            avg_embed = features.mean(dim=0).cpu().numpy()\n            EMBEDDINGS.append(avg_embed)\n        except Exception as e:\n            print(f\"Error processing {row['image_path']}: {e}\")\n            # Fallback zero embedding\n            EMBEDDINGS.append(np.zeros(1152))\n        \n    torch.cuda.empty_cache()\n    return np.stack(EMBEDDINGS)\n\n# Compute Features\ntrain_embeddings = compute_embeddings(cfg.SIGLIP_PATH, train_df)\ntest_embeddings = compute_embeddings(cfg.SIGLIP_PATH, test_df)\n\n# Create Feature DataFrames\nemb_cols = [f\"emb{i}\" for i in range(train_embeddings.shape[1])]\ntrain_feat_df = pd.concat([train_df, pd.DataFrame(train_embeddings, columns=emb_cols)], axis=1)\ntest_feat_df = pd.concat([test_df, pd.DataFrame(test_embeddings, columns=emb_cols)], axis=1)\n\n# Double check column counts\nprint(f\"Train Features Shape: {train_feat_df.shape}\")\nprint(f\"Test Features Shape: {test_feat_df.shape}\")\n\n# =========================================================================================\n# 4. FEATURE EXTRACTION: SEMANTIC FEATURES (TEXT PROBING)\n# =========================================================================================\ndef generate_semantic_features(image_embeddings_np, model_path):\n    print(\"Generating Semantic Features...\")\n    model = AutoModel.from_pretrained(model_path).to(cfg.DEVICE)\n    tokenizer = AutoTokenizer.from_pretrained(model_path)\n\n    model.eval()\n    \n    # Anchors\n    concept_groups = {\n        \"bare\": [\"bare soil\", \"dirt ground\", \"sparse vegetation\", \"exposed earth\"],\n        \"sparse\": [\"low density pasture\", \"thin grass\", \"short clipped grass\"],\n        \"medium\": [\"average pasture cover\", \"medium height grass\", \"grazed pasture\"],\n        \"dense\": [\"dense tall pasture\", \"thick grassy volume\", \"high biomass\", \"overgrown vegetation\"],\n        \"green\": [\"lush green vibrant pasture\", \"photosynthesizing leaves\", \"fresh growth\"],\n        \"dead\": [\"dry brown dead grass\", \"yellow straw\", \"senesced material\", \"standing hay\"],\n        \"clover\": [\"white clover\", \"trifolium repens\", \"broadleaf legume\", \"clover flowers\"],\n        \"grass\": [\"ryegrass\", \"blade-like leaves\", \"fescue\", \"grassy sward\"]\n    }\n    \n    # Encode Concepts\n    concept_vectors = {}\n    with torch.no_grad():\n        with autocast():\n            for name, prompts in concept_groups.items():\n                inputs = tokenizer(prompts, padding=\"max_length\", return_tensors=\"pt\").to(cfg.DEVICE)\n                emb = model.get_text_features(**inputs)\n                \n                # 归一化操作在 float32 下进行以保持精度\n                emb = emb.float() / emb.float().norm(p=2, dim=-1, keepdim=True)\n                concept_vectors[name] = emb.mean(dim=0, keepdim=True)\n            \n    # Compute Scores\n    img_tensor = torch.tensor(image_embeddings_np, dtype=torch.float32).to(cfg.DEVICE)\n    img_tensor = img_tensor / img_tensor.norm(p=2, dim=-1, keepdim=True)\n    \n    scores = {}\n    for name, vec in concept_vectors.items():\n        scores[name] = torch.matmul(img_tensor, vec.T).cpu().numpy().flatten()\n        \n    df_scores = pd.DataFrame(scores)\n    # Ratios\n    df_scores['ratio_greenness'] = df_scores['green'] / (df_scores['green'] + df_scores['dead'] + 1e-6)\n    df_scores['ratio_clover'] = df_scores['clover'] / (df_scores['clover'] + df_scores['grass'] + 1e-6)\n    df_scores['ratio_cover'] = (df_scores['dense'] + df_scores['medium']) / (df_scores['bare'] + df_scores['sparse'] + 1e-6)\n    \n    torch.cuda.empty_cache()\n    return df_scores.values\n\n# Combine for semantic generation to ensure consistency\nall_emb = np.vstack([train_embeddings, test_embeddings])\nall_semantic = generate_semantic_features(all_emb, cfg.SIGLIP_PATH)\n\nsem_train = all_semantic[:len(train_df)]\nsem_test = all_semantic[len(train_df):]\n\n# =========================================================================================\n# 5. SUPERVISED EMBEDDING ENGINE\n# =========================================================================================\nclass SupervisedEmbeddingEngine:\n    def __init__(self, n_pca=0.80, n_pls=8, n_gmm=6, random_state=42):\n        self.scaler = StandardScaler()\n        self.pca = PCA(n_components=n_pca, random_state=random_state)\n        self.pls = PLSRegression(n_components=n_pls, scale=False)\n        self.gmm = GaussianMixture(n_components=n_gmm, covariance_type='diag', random_state=random_state)\n        self.pls_fitted_ = False\n\n    def fit(self, X, y=None, X_semantic=None):\n        # Scale\n        X_scaled = self.scaler.fit_transform(X)\n        \n        # Unsupervised\n        self.pca.fit(X_scaled)\n        self.gmm.fit(X_scaled)\n        \n        # Supervised\n        if y is not None:\n            self.pls.fit(X_scaled, y)\n            self.pls_fitted_ = True\n        return self\n\n    def transform(self, X, X_semantic=None):\n        X_scaled = self.scaler.transform(X)\n        \n        features = [self.pca.transform(X_scaled)]\n        \n        if self.pls_fitted_:\n            features.append(self.pls.transform(X_scaled))\n            \n        features.append(self.gmm.predict_proba(X_scaled))\n        \n        if X_semantic is not None:\n            # Normalize semantic\n            sem_norm = (X_semantic - np.mean(X_semantic, axis=0)) / (np.std(X_semantic, axis=0) + 1e-6)\n            features.append(sem_norm)\n            \n        return np.hstack(features)\n\n# =========================================================================================\n# 6. TRAINING & INFERENCE (5-FOLD CV)\n# =========================================================================================\ndef cross_validate_predict(model_cls, model_params, train_data, test_data, sem_tr, sem_te, feature_engine):\n    target_max_arr = np.array([cfg.TARGET_MAX[t] for t in cfg.TARGET_NAMES], dtype=float)\n    y_pred_test_accum = np.zeros([len(test_data), len(cfg.TARGET_NAMES)], dtype=float)\n    \n    # Ensure n_splits is integer\n    n_splits = int(train_data['fold'].nunique())\n    \n    # Pre-extract raw columns to avoid indexing overhead\n    # Force float32 to save memory and ensure compatibility\n    X_train_full = train_data[emb_cols].values.astype(np.float32)\n    X_test_raw = test_data[emb_cols].values.astype(np.float32)\n    y_train_full = train_data[cfg.TARGET_NAMES].values.astype(np.float32)\n    \n    for fold in range(n_splits):\n        print(f\"Processing Fold {fold}...\")\n        # Split\n        train_mask = train_data['fold'] != fold\n        \n        X_tr = X_train_full[train_mask]\n        y_tr = y_train_full[train_mask] / target_max_arr # Max Scaling\n        \n        sem_tr_fold = sem_tr[train_mask]\n        \n        # Feature Engineering (Fit on fold train)\n        engine = deepcopy(feature_engine)\n        engine.fit(X_tr, y=y_tr, X_semantic=sem_tr_fold)\n        \n        x_tr_eng = engine.transform(X_tr, X_semantic=sem_tr_fold)\n        x_te_eng = engine.transform(X_test_raw, X_semantic=sem_te)\n        \n        # Train & Predict per target\n        fold_test_pred = np.zeros([len(test_data), len(cfg.TARGET_NAMES)])\n        \n        for k in range(len(cfg.TARGET_NAMES)):\n            target_name = cfg.TARGET_NAMES[k]\n            \n            # Dry_Clover_g: Always predict 0.0 (no training needed)\n            if target_name == 'Dry_Clover_g':\n                fold_test_pred[:, k] = 0.0\n            else:\n                # Train and predict for other targets\n                model = model_cls(**model_params)\n                model.fit(x_tr_eng, y_tr[:, k])\n                pred_raw = model.predict(x_te_eng)\n                fold_test_pred[:, k] = pred_raw * target_max_arr[k] # Inverse Scale\n            \n        y_pred_test_accum += fold_test_pred\n        \n    return y_pred_test_accum / n_splits\n\n# Model Parameters (Optimized)\nparams_cat = {\n    'iterations': 1900, 'learning_rate': 0.045, 'depth': 4, 'l2_leaf_reg': 0.56, \n    'random_strength': 0.045, 'bagging_temperature': 0.98, 'verbose': 0, 'random_state': 42,\n    'allow_writing_files': False\n}\nparams_xgb = { # Using GradientBoostingRegressor as proxy\n    'n_estimators': 1354, 'learning_rate': 0.010, 'max_depth': 3, 'subsample': 0.60, \n    'random_state': 42\n}\nparams_lgbm = {\n    'n_estimators': 807, 'learning_rate': 0.014, 'num_leaves': 48, 'min_child_samples': 19, \n    'subsample': 0.745, 'colsample_bytree': 0.745, 'reg_alpha': 0.21, 'reg_lambda': 3.78,\n    'verbose': -1, 'random_state': 42\n}\nparams_hist = {\n    'max_iter': 300, 'learning_rate': 0.05, 'max_depth': None, 'l2_regularization': 0.44,\n    'random_state': 42\n}\n\nfeat_engine = SupervisedEmbeddingEngine(n_pca=0.80, n_pls=8, n_gmm=6)\n\nprint(\"Training & Inferring Models...\")\n\n# 1. HistGradientBoosting\nprint(\"Model: HistGradientBoosting\")\npred_hist = cross_validate_predict(\n    HistGradientBoostingRegressor, params_hist, \n    train_feat_df, test_feat_df, sem_train, sem_test, feat_engine\n)\n\n# 2. GradientBoosting\nprint(\"Model: GradientBoosting\")\npred_gb = cross_validate_predict(\n    GradientBoostingRegressor, params_xgb, \n    train_feat_df, test_feat_df, sem_train, sem_test, feat_engine\n)\n\n# 3. CatBoost\nprint(\"Model: CatBoost\")\npred_cat = cross_validate_predict(\n    CatBoostRegressor, params_cat, \n    train_feat_df, test_feat_df, sem_train, sem_test, feat_engine\n)\n\n# 4. LightGBM\nprint(\"Model: LightGBM\")\npred_lgbm = cross_validate_predict(\n    LGBMRegressor, params_lgbm, \n    train_feat_df, test_feat_df, sem_train, sem_test, feat_engine\n)\n\n# =========================================================================================\n# 7. ENSEMBLING & SUBMISSION\n# =========================================================================================\nprint(\"Ensembling and Post-processing...\")\n# Simple Average Ensemble\nfinal_pred = (pred_hist + pred_gb + pred_cat + pred_lgbm) / 4.0\n\n# Assign to dataframe\ntest_feat_df[cfg.TARGET_NAMES] = final_pred\n\n# Post-process (Mass Balance)\ntest_processed = post_process_biomass(test_feat_df)\n\n# Create Submission File\nsub_df = melt_table(test_processed)\noutput_path = \"submission_siglip.csv\"\nsub_df.to_csv(output_path, index=False)\n\nprint(f\"✓ Siglip submission generated: {output_path}\")\nprint(sub_df.head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-19T11:34:43.030583Z","iopub.execute_input":"2026-01-19T11:34:43.031399Z","iopub.status.idle":"2026-01-19T11:34:43.042693Z","shell.execute_reply.started":"2026-01-19T11:34:43.031374Z","shell.execute_reply":"2026-01-19T11:34:43.041978Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!python csiro_infer.py","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-19T11:34:47.807954Z","iopub.execute_input":"2026-01-19T11:34:47.808542Z","iopub.status.idle":"2026-01-19T11:41:02.108845Z","shell.execute_reply.started":"2026-01-19T11:34:47.808518Z","shell.execute_reply":"2026-01-19T11:41:02.107869Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# dinov3 lb 70","metadata":{}},{"cell_type":"code","source":"%%writefile csiro_infer.py\n\nimport sys\nfrom PIL import Image\nimport pandas as pd\nimport numpy as np\nimport matplotlib.pyplot as plt\nfrom sklearn.model_selection import KFold, GroupKFold, StratifiedGroupKFold\nfrom tqdm.auto import tqdm\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.optim import AdamW\nfrom transformers import get_cosine_schedule_with_warmup\n\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms as T\n\nimport os\nfrom pathlib import Path\nimport timm\nimport warnings \n\nwarnings.filterwarnings('ignore')\ntqdm.pandas()\n\nTEST_IMAGE_DIR = './'\n\n\nclass RegressionDataset(Dataset):\n    def __init__(self, data, transform=None):\n        self.data = data\n        self.transform = transform\n    def __len__(self):\n        return self.data.shape[0]\n\n    def __getitem__(self, idx):\n        item = self.data.iloc[idx]\n        # image = item.image\n        with Image.open(TEST_IMAGE_DIR + item.image_path) as img:\n            image = img.convert('RGB')\n\n        targets = [item['Dry_Green_g'], item['Dry_Clover_g'], item['Dry_Dead_g']]\n        width, height = image.size\n        mid_point = width // 2\n        left_image = image.crop((0, 0, mid_point, height))\n        right_image = image.crop((mid_point, 0, width, height))\n\n        if self.transform is not None:\n            left_image = self.transform(left_image)\n            right_image = self.transform(right_image)\n\n        return left_image, right_image, targets\n\ndef get_test_dataloaders(data, image_size, batch_size):\n    res = []\n    for trans in [None, T.RandomHorizontalFlip(p=1.0), T.RandomVerticalFlip(p=1.0)]:\n        transform = T.Compose([\n            T.Resize(image_size),\n            T.ToTensor(),\n            T.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n        ])\n        if trans:\n            transform = T.Compose([\n                T.Resize(image_size),\n                trans,\n                T.ToTensor(),\n                T.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n            ])\n        dataset = RegressionDataset(data, transform=transform)\n        res.append(DataLoader(dataset, batch_size=batch_size, shuffle=False, num_workers=4))\n    return res\n\n\nclass FiLM(nn.Module):\n    def __init__(self, feat_dim):\n        super().__init__()\n        self.mlp = nn.Sequential(\n            nn.Linear(feat_dim, feat_dim // 2), \n            nn.ReLU(inplace=True), \n            nn.Linear(feat_dim // 2, feat_dim * 2)\n        )\n\n    def forward(self, context):\n        gamma_beta = self.mlp(context)\n        return torch.chunk(gamma_beta, 2, dim=1)\n\nclass CSIROModelRegressor(nn.Module):\n    def __init__(self, model_name, pretrained=True, num_classes=3, dropout=0.0, freeze_backbone=False):\n        super().__init__()\n        self.backbone = timm.create_model(model_name, pretrained=pretrained, num_classes=0, global_pool='avg')\n\n        self.film = FiLM(self.backbone.num_features)\n\n        self.dropout = nn.Dropout(dropout)\n\n        def make_head():\n            return nn.Sequential(\n                nn.Linear(self.backbone.num_features * 2, 8),\n                nn.ReLU(inplace=True),\n                nn.Dropout(dropout),\n                nn.Linear(8, 1),\n            )\n\n        self.head_green = make_head()\n        self.head_clover = make_head()\n        self.head_dead = make_head()\n\n        self.softplus = nn.Softplus(beta=1.0)\n\n        if freeze_backbone:\n            for p in self.backbone.parameters():\n                p.requires_grad = False\n\n\n    def forward(self, left_img, right_img):\n        left_feat = self.backbone(left_img)\n        right_feat = self.backbone(right_img)\n\n        context = (left_feat + right_feat) / 2\n        gamma, beta = self.film(context)\n\n        left_feat_modulated = left_feat * (1 + gamma) + beta\n        right_feat_modulated = right_feat * (1 + gamma) + beta\n\n        combined = torch.cat([left_feat_modulated, right_feat_modulated], dim=1)\n\n        green = self.softplus(self.head_green(combined))   \n        clover = self.softplus(self.head_clover(combined))\n        dead = self.softplus(self.head_dead(combined)) \n\n        logits = torch.cat([green, clover, dead], dim=1)\n\n        return logits\n\n\nfrom torch.cuda.amp import autocast\n\ndef predict(model, dataloader, device):\n    model.to(device)\n    model.eval()\n\n    all_outputs = []\n    with torch.no_grad():\n        with autocast():\n            for left_images, right_images, targets in dataloader:\n                left_images = left_images.to(device)\n                right_images = right_images.to(device)\n    \n                outputs = model(left_images, right_images)\n                all_outputs.append(outputs.detach().cpu())\n\n    outputs = torch.cat(all_outputs).numpy()\n    return outputs\n\ndef predict_loaders(model, dataloaders, device):\n    all_outputs = []\n    for dataloader in dataloaders:\n        outputs = predict(model, dataloader, device)\n        all_outputs.append(outputs)\n    avg_outputs = np.mean(all_outputs, axis=0)\n    return avg_outputs\n\ndef predict_folds(dataloaders,models_dir):\n    device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')\n\n    all_preds = []\n    for model_file in Path(models_dir).glob('*.pth'):\n        model = CSIROModelRegressor(CFG.MODEL_NAME, pretrained=False, num_classes=3)\n        model.load_state_dict(torch.load(model_file))\n        preds = predict_loaders(model, dataloaders, device)\n        all_preds.append(preds)\n\n    avg_preds = np.mean(all_preds, axis=0)\n    return avg_preds\n\n\nclass CFG:\n    DATA_PATH=\"/kaggle/input/csiro-biomass/\"\n    TEST_DATA_PATH=\"/kaggle/input/csiro-biomass/test.csv\"\n    MODEL_NAME=\"vit_large_patch16_dinov3_qkvb\"\n    MODELS_DIR ='/kaggle/input/modelv3/pytorch/default/1/models_retrained'\n    IMG_SIZE=(512,512)\n\n\npart_idx = sys.argv[1]\ntest_data_df = pd.read_pickle(f'agg_test_df_part{part_idx}.pkl')\n\ntest_loaders = get_test_dataloaders(test_data_df, CFG.IMG_SIZE, 96)\npreds = predict_folds(test_loaders,models_dir=CFG.MODELS_DIR)\n\ntest_data_df[['Dry_Green_g', 'Dry_Clover_g', 'Dry_Dead_g']] = preds\ntest_data_df['Dry_Green_g'] = test_data_df['Dry_Green_g'].apply(lambda x: 0.0 if x < 0.09 else x)\ntest_data_df['Dry_Clover_g'] = test_data_df['Dry_Clover_g'].apply(lambda x: 0.0 if x < 0.09 else x)\ntest_data_df['Dry_Dead_g'] = test_data_df['Dry_Dead_g'].apply(lambda x: 0.0 if x < 0.09 else x)\ntest_data_df['GDM_g'] = test_data_df.Dry_Green_g + test_data_df.Dry_Clover_g\ntest_data_df['Dry_Total_g'] = test_data_df.GDM_g + test_data_df.Dry_Dead_g\n\ncols = [ 'Dry_Green_g', 'Dry_Dead_g', 'Dry_Clover_g', 'GDM_g' , 'Dry_Total_g']\nsub_df = test_data_df.set_index('sample_id_prefix')[cols].stack()\nsub_df = sub_df.reset_index()\nsub_df.columns = ['sample_id_prefix', 'target_name', 'target']\n\nsub_df['sample_id'] = sub_df.sample_id_prefix + '__' + sub_df.target_name\n\ncols = ['sample_id', 'target']\nsub_df[cols].to_csv(f'submission_part{part_idx}.csv', index=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-19T11:49:16.882549Z","iopub.execute_input":"2026-01-19T11:49:16.883525Z","iopub.status.idle":"2026-01-19T11:49:16.891484Z","shell.execute_reply.started":"2026-01-19T11:49:16.883489Z","shell.execute_reply":"2026-01-19T11:49:16.890871Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"%%bash\nCUDA_VISIBLE_DEVICES=0 python csiro_infer.py 0 &\nPID1=$!\n\nCUDA_VISIBLE_DEVICES=1 python csiro_infer.py 1 &\nPID2=$!\n\nwait $PID1 $PID2\n\necho '运行完成！'","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-19T11:49:26.617644Z","iopub.execute_input":"2026-01-19T11:49:26.618398Z","iopub.status.idle":"2026-01-19T11:50:52.408765Z","shell.execute_reply.started":"2026-01-19T11:49:26.618371Z","shell.execute_reply":"2026-01-19T11:50:52.408024Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"merge_submissions('submission_dinov2026.csv')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-19T12:02:40.262272Z","iopub.execute_input":"2026-01-19T12:02:40.262942Z","iopub.status.idle":"2026-01-19T12:02:40.272496Z","shell.execute_reply.started":"2026-01-19T12:02:40.262917Z","shell.execute_reply":"2026-01-19T12:02:40.271903Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Ensemble","metadata":{}},{"cell_type":"code","source":"ls","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-19T12:03:09.452896Z","iopub.execute_input":"2026-01-19T12:03:09.45341Z","iopub.status.idle":"2026-01-19T12:03:09.589407Z","shell.execute_reply.started":"2026-01-19T12:03:09.453374Z","shell.execute_reply":"2026-01-19T12:03:09.58847Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nimport numpy as np","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-19T12:03:09.947538Z","iopub.execute_input":"2026-01-19T12:03:09.948248Z","iopub.status.idle":"2026-01-19T12:03:09.951873Z","shell.execute_reply.started":"2026-01-19T12:03:09.948222Z","shell.execute_reply":"2026-01-19T12:03:09.95116Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df1 = pd.read_csv('submission_p1.csv')\ndf2 = pd.read_csv('submission_p2.csv')\ndf3 = pd.read_csv('submission_p3.csv')\n# df4 = pd.read_csv('submission_p4.csv')\ndf5 = pd.read_csv('submission_siglip.csv')\ndf6 = pd.read_csv('submission_dinov2026.csv')\ndf7 = pd.read_csv('submission_10fold.csv')\n\ndf8 = pd.read_csv('submission_multiview.csv')\n\ndf9 = pd.read_csv('submission_multiview_memba.csv')\n\ndf10 = pd.read_csv('submission_aux_more.csv')\ndf11 = pd.read_csv('submission_aux.csv')\n\n\ndf1['pred1'] = df1['target']","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-19T12:03:10.517439Z","iopub.execute_input":"2026-01-19T12:03:10.517694Z","iopub.status.idle":"2026-01-19T12:03:10.531192Z","shell.execute_reply.started":"2026-01-19T12:03:10.517675Z","shell.execute_reply":"2026-01-19T12:03:10.530397Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df1['pred2'] = df1.sample_id.map(df2.set_index('sample_id').target)\ndf1['pred3'] = df1.sample_id.map(df3.set_index('sample_id').target)\n# df1['pred4'] = df1.sample_id.map(df4.set_index('sample_id').target)\ndf1['pred5'] = df1.sample_id.map(df5.set_index('sample_id').target)\ndf1['pred6'] = df1.sample_id.map(df6.set_index('sample_id').target)\ndf1['pred7'] = df1.sample_id.map(df7.set_index('sample_id').target)\n\ndf1['pred8'] = df1.sample_id.map(df8.set_index('sample_id').target)\n\ndf1['pred9'] = df1.sample_id.map(df9.set_index('sample_id').target)\n\ndf1['pred10'] = df1.sample_id.map(df10.set_index('sample_id').target)\ndf1['pred11'] = df1.sample_id.map(df11.set_index('sample_id').target)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-19T12:03:59.466127Z","iopub.execute_input":"2026-01-19T12:03:59.466513Z","iopub.status.idle":"2026-01-19T12:03:59.479893Z","shell.execute_reply.started":"2026-01-19T12:03:59.466477Z","shell.execute_reply":"2026-01-19T12:03:59.479087Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df1.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-19T12:04:00.832455Z","iopub.execute_input":"2026-01-19T12:04:00.833064Z","iopub.status.idle":"2026-01-19T12:04:00.844191Z","shell.execute_reply.started":"2026-01-19T12:04:00.83304Z","shell.execute_reply":"2026-01-19T12:04:00.843536Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df1['target'] = (\n    ((df1.pred1 + df1.pred2 + df1.pred3 + df1.pred5 + df1.pred6 + df1.pred7) / 6) * 0.4 +    # old ensemble\n    df1.pred8 *  0.5  +    # multiview\n    df1.pred9 *  0.03 +    # multiview memba\n    df1.pred10 * 0.05 +    # multiview aux more\n    df1.pred10 * 0.02\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-19T12:04:08.517927Z","iopub.execute_input":"2026-01-19T12:04:08.51864Z","iopub.status.idle":"2026-01-19T12:04:08.523608Z","shell.execute_reply.started":"2026-01-19T12:04:08.518591Z","shell.execute_reply":"2026-01-19T12:04:08.522837Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df1.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-19T12:04:09.964814Z","iopub.execute_input":"2026-01-19T12:04:09.965107Z","iopub.status.idle":"2026-01-19T12:04:09.975309Z","shell.execute_reply.started":"2026-01-19T12:04:09.965084Z","shell.execute_reply":"2026-01-19T12:04:09.974666Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"cols = ['sample_id', 'target']\ndf1[cols].to_csv('submission.csv', index=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-19T12:04:12.992176Z","iopub.execute_input":"2026-01-19T12:04:12.992452Z","iopub.status.idle":"2026-01-19T12:04:12.998487Z","shell.execute_reply.started":"2026-01-19T12:04:12.992431Z","shell.execute_reply":"2026-01-19T12:04:12.997737Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!head submission.csv","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-19T12:04:17.769986Z","iopub.execute_input":"2026-01-19T12:04:17.770283Z","iopub.status.idle":"2026-01-19T12:04:17.902436Z","shell.execute_reply.started":"2026-01-19T12:04:17.770262Z","shell.execute_reply":"2026-01-19T12:04:17.901768Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Postprocess","metadata":{}},{"cell_type":"code","source":"def submission_postprocess(sub_fname='submission.csv'):\n    # read\n    sub_df = pd.read_csv(sub_fname)\n    sub_df[['sample_id_prefix', 'sample_id_suffix']] = sub_df.sample_id.str.split('__', expand=True)\n    agg_sub_df = sub_df.groupby('sample_id_prefix').apply(lambda df: df.set_index('sample_id_suffix').target, include_groups=False)\n\n    # postprocess\n    agg_sub_df['Dry_Green_g'] = agg_sub_df['Dry_Green_g'].apply(lambda x: x*1.1 if x > 60 else x)\n    agg_sub_df['Dry_Clover_g'] = agg_sub_df['Dry_Clover_g'].apply(lambda x: x*1.14 if x > 22 else x)\n    agg_sub_df['Dry_Dead_g'] = agg_sub_df['Dry_Dead_g'].apply(lambda x: x*1.04 if x > 22 else x)\n    \n    agg_sub_df['GDM_g'] = agg_sub_df.Dry_Green_g + agg_sub_df.Dry_Clover_g\n    agg_sub_df['Dry_Total_g'] = agg_sub_df.GDM_g + agg_sub_df.Dry_Dead_g\n\n    # save\n    cols = ['Dry_Clover_g', 'Dry_Dead_g', 'Dry_Green_g', 'Dry_Total_g', 'GDM_g']\n    sub_df = agg_sub_df[cols].stack()\n    sub_df = sub_df.reset_index()\n    sub_df.columns = ['sample_id_prefix', 'target_name', 'target']\n    \n    sub_df['sample_id'] = sub_df.sample_id_prefix + '__' + sub_df.target_name\n    \n    cols = ['sample_id', 'target']\n    sub_df[cols].to_csv(sub_fname, index=False)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# submission_postprocess()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!head submission.csv","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}