{"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":"gpu","dataSources":[{"sourceId":112509,"databundleVersionId":14254895,"sourceType":"competition"},{"sourceId":717546,"sourceType":"modelInstanceVersion","modelInstanceId":524678,"modelId":538721}],"dockerImageVersionId":31193,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"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":{"execution":{"iopub.status.busy":"2025-12-19T08:47:21.419767Z","iopub.execute_input":"2025-12-19T08:47:21.420475Z","iopub.status.idle":"2025-12-19T08:47:22.273215Z","shell.execute_reply.started":"2025-12-19T08:47:21.420449Z","shell.execute_reply":"2025-12-19T08:47:22.272543Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"DATA_ROOT = '/kaggle/input/csiro-biomass/'\nMODEL_NAME = 'vit_large_patch16_dinov3_qkvb'","metadata":{"execution":{"iopub.status.busy":"2025-12-19T09:03:01.657653Z","iopub.execute_input":"2025-12-19T09:03:01.658448Z","iopub.status.idle":"2025-12-19T09:03:01.66255Z","shell.execute_reply.started":"2025-12-19T09:03:01.65842Z","shell.execute_reply":"2025-12-19T09:03:01.661758Z"},"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":{"execution":{"iopub.status.busy":"2025-12-19T08:47:40.643782Z","iopub.execute_input":"2025-12-19T08:47:40.644052Z","iopub.status.idle":"2025-12-19T08:47:46.593056Z","shell.execute_reply.started":"2025-12-19T08:47:40.64403Z","shell.execute_reply":"2025-12-19T08:47:46.592414Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def 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","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-19T08:48:11.515971Z","iopub.execute_input":"2025-12-19T08:48:11.516743Z","iopub.status.idle":"2025-12-19T08:48:11.521486Z","shell.execute_reply.started":"2025-12-19T08:48:11.516715Z","shell.execute_reply":"2025-12-19T08:48:11.520832Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class 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        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            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        \n        else:\n            if self.transform:\n                image = self.transform(image)\n\n            return image, targets","metadata":{"execution":{"iopub.status.busy":"2025-12-19T08:51:52.770321Z","iopub.execute_input":"2025-12-19T08:51:52.771104Z","iopub.status.idle":"2025-12-19T08:51:52.778035Z","shell.execute_reply.started":"2025-12-19T08:51:52.771055Z","shell.execute_reply":"2025-12-19T08:51:52.777445Z"},"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):    \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","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-19T08:52:11.133738Z","iopub.execute_input":"2025-12-19T08:52:11.134326Z","iopub.status.idle":"2025-12-19T08:52:11.141021Z","shell.execute_reply.started":"2025-12-19T08:52:11.134304Z","shell.execute_reply":"2025-12-19T08:52:11.140178Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def 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","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-19T08:52:50.565607Z","iopub.execute_input":"2025-12-19T08:52:50.565892Z","iopub.status.idle":"2025-12-19T08:52:50.57051Z","shell.execute_reply.started":"2025-12-19T08:52:50.56587Z","shell.execute_reply":"2025-12-19T08:52:50.569718Z"}},"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":{"execution":{"iopub.status.busy":"2025-12-19T08:54:13.964434Z","iopub.execute_input":"2025-12-19T08:54:13.964716Z","iopub.status.idle":"2025-12-19T08:54:13.968309Z","shell.execute_reply.started":"2025-12-19T08:54:13.964697Z","shell.execute_reply":"2025-12-19T08:54:13.967505Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 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 = (512, 512)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-19T08:53:55.55939Z","iopub.execute_input":"2025-12-19T08:53:55.560123Z","iopub.status.idle":"2025-12-19T08:53:55.563648Z","shell.execute_reply.started":"2025-12-19T08:53:55.560098Z","shell.execute_reply":"2025-12-19T08:53:55.562742Z"}},"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, dim=1)  # (batch, seq_len, 1)\n        context = torch.sum(x * weights, 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 = 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","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-19T08:54:04.962795Z","iopub.execute_input":"2025-12-19T08:54:04.963586Z","iopub.status.idle":"2025-12-19T08:54:04.968312Z","shell.execute_reply.started":"2025-12-19T08:54:04.963559Z","shell.execute_reply":"2025-12-19T08:54:04.967568Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Inference","metadata":{}},{"cell_type":"code","source":"import os\nfrom pathlib import Path","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-19T08:55:00.581971Z","iopub.execute_input":"2025-12-19T08:55:00.582602Z","iopub.status.idle":"2025-12-19T08:55:00.5859Z","shell.execute_reply.started":"2025-12-19T08:55:00.582578Z","shell.execute_reply":"2025-12-19T08:55:00.585207Z"}},"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,"execution":{"iopub.status.busy":"2025-12-19T08:55:15.035516Z","iopub.execute_input":"2025-12-19T08:55:15.03629Z","iopub.status.idle":"2025-12-19T08:55:15.04069Z","shell.execute_reply.started":"2025-12-19T08:55:15.036265Z","shell.execute_reply":"2025-12-19T08:55:15.039959Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"SAVED_MODELS = get_lastest_saved_models('/kaggle/input/csiro-local-pro/pytorch/default/')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-19T08:55:43.962604Z","iopub.execute_input":"2025-12-19T08:55:43.962879Z","iopub.status.idle":"2025-12-19T08:55:43.966668Z","shell.execute_reply.started":"2025-12-19T08:55:43.962857Z","shell.execute_reply":"2025-12-19T08:55:43.966112Z"}},"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","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-19T08:57:24.800466Z","iopub.execute_input":"2025-12-19T08:57:24.800772Z","iopub.status.idle":"2025-12-19T08:57:24.806199Z","shell.execute_reply.started":"2025-12-19T08:57:24.800746Z","shell.execute_reply":"2025-12-19T08:57:24.805456Z"}},"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","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-19T08:57:31.37522Z","iopub.execute_input":"2025-12-19T08:57:31.376024Z","iopub.status.idle":"2025-12-19T08:57:31.379798Z","shell.execute_reply.started":"2025-12-19T08:57:31.376002Z","shell.execute_reply":"2025-12-19T08:57:31.379023Z"}},"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","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-19T08:59:48.357553Z","iopub.execute_input":"2025-12-19T08:59:48.358068Z","iopub.status.idle":"2025-12-19T08:59:48.36352Z","shell.execute_reply.started":"2025-12-19T08:59:48.358043Z","shell.execute_reply":"2025-12-19T08:59:48.362814Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_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)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-19T09:13:02.411533Z","iopub.execute_input":"2025-12-19T09:13:02.412285Z","iopub.status.idle":"2025-12-19T09:13:02.425767Z","shell.execute_reply.started":"2025-12-19T09:13:02.41226Z","shell.execute_reply":"2025-12-19T09:13:02.425124Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"cols = ['sample_id_prefix', 'image_path']\nagg_test_df = test_df.groupby(cols).apply(lambda df: df.set_index('target_name').target)\nagg_test_df.reset_index(inplace=True)\nagg_test_df.columns.name = None\n\nagg_test_df['image'] = agg_test_df.image_path.progress_apply(\n    lambda path: Image.open(DATA_ROOT + path).convert('RGB')\n)\n\nagg_test_df.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-19T09:13:02.566308Z","iopub.execute_input":"2025-12-19T09:13:02.56655Z","iopub.status.idle":"2025-12-19T09:13:02.643224Z","shell.execute_reply.started":"2025-12-19T09:13:02.566532Z","shell.execute_reply":"2025-12-19T09:13:02.642494Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_loader = get_tta_dataloaders(agg_test_df, TARGET_IMAGE_SIZE, 96)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-19T09:13:07.810574Z","iopub.execute_input":"2025-12-19T09:13:07.811238Z","iopub.status.idle":"2025-12-19T09:13:07.815736Z","shell.execute_reply.started":"2025-12-19T09:13:07.811216Z","shell.execute_reply":"2025-12-19T09:13:07.814914Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"preds = kfold_predict(test_loader)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-19T09:13:10.690578Z","iopub.execute_input":"2025-12-19T09:13:10.691456Z","iopub.status.idle":"2025-12-19T09:13:46.39351Z","shell.execute_reply.started":"2025-12-19T09:13:10.691423Z","shell.execute_reply":"2025-12-19T09:13:46.392631Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"agg_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","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-19T09:13:46.39592Z","iopub.execute_input":"2025-12-19T09:13:46.39623Z","iopub.status.idle":"2025-12-19T09:13:46.404796Z","shell.execute_reply.started":"2025-12-19T09:13:46.396199Z","shell.execute_reply":"2025-12-19T09:13:46.40401Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"agg_test_df.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-19T09:13:46.405414Z","iopub.execute_input":"2025-12-19T09:13:46.405643Z","iopub.status.idle":"2025-12-19T09:13:46.427412Z","shell.execute_reply.started":"2025-12-19T09:13:46.40562Z","shell.execute_reply":"2025-12-19T09:13:46.426695Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"cols = ['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","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-19T09:13:46.429222Z","iopub.execute_input":"2025-12-19T09:13:46.429411Z","iopub.status.idle":"2025-12-19T09:13:46.445257Z","shell.execute_reply.started":"2025-12-19T09:13:46.429398Z","shell.execute_reply":"2025-12-19T09:13:46.444646Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"cols = ['sample_id', 'target']\nsub_df[cols].to_csv('submission.csv', index=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-19T09:13:46.446051Z","iopub.execute_input":"2025-12-19T09:13:46.44629Z","iopub.status.idle":"2025-12-19T09:13:46.451758Z","shell.execute_reply.started":"2025-12-19T09:13:46.44627Z","shell.execute_reply":"2025-12-19T09:13:46.451033Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!head submission.csv","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-19T09:13:46.452446Z","iopub.execute_input":"2025-12-19T09:13:46.45264Z","iopub.status.idle":"2025-12-19T09:13:46.661045Z","shell.execute_reply.started":"2025-12-19T09:13:46.452618Z","shell.execute_reply":"2025-12-19T09:13:46.660321Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}