{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"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,"isSourceIdPinned":false,"sourceType":"competition"},{"sourceId":14235761,"sourceType":"datasetVersion","datasetId":9082183},{"sourceId":693340,"sourceType":"modelInstanceVersion","isSourceIdPinned":true,"modelInstanceId":525756,"modelId":539818},{"sourceId":711719,"sourceType":"modelInstanceVersion","isSourceIdPinned":true,"modelInstanceId":540756,"modelId":553949}],"dockerImageVersionId":31240,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# CSIRO Biomass Regression - Run2 (Improved Model)\n\n使用改进的模型架构：\n- ImprovedBiomassMLP (4层网络)\n- CLS token + Patch tokens (3072维特征)\n- 特征标准化 (StandardScaler)\n\n预期 Weighted R² ≈ 0.985","metadata":{}},{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport torch\nimport torchvision\nimport torch.nn as nn\nimport torchvision.transforms as transforms\nfrom PIL import Image\nfrom transformers import Dinov2Model\nfrom tqdm import tqdm\nimport os\nimport joblib","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-07T09:00:45.121637Z","iopub.execute_input":"2026-01-07T09:00:45.121920Z","iopub.status.idle":"2026-01-07T09:00:45.126187Z","shell.execute_reply.started":"2026-01-07T09:00:45.121900Z","shell.execute_reply":"2026-01-07T09:00:45.125352Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 设置设备\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\nprint(f\"Using device: {device}\")\n\n# 图像预处理参数\nmean = [0.485, 0.456, 0.406]\nstd = [0.229, 0.224, 0.225]\n\n# 目标变量映射\nmapping = {\"Dry_Clover_g\": 0, \"Dry_Dead_g\": 1, \"Dry_Green_g\": 2, \"Dry_Total_g\": 3, \"GDM_g\": 4}","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-07T09:00:45.127330Z","iopub.execute_input":"2026-01-07T09:00:45.127528Z","iopub.status.idle":"2026-01-07T09:00:45.142789Z","shell.execute_reply.started":"2026-01-07T09:00:45.127512Z","shell.execute_reply":"2026-01-07T09:00:45.141964Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 改进的MLP架构（必须在加载模型之前定义）\nclass ImprovedBiomassMLP(nn.Module):\n    def __init__(self, input_size, hidden_size=512, dropout_rate=0.3):\n        super(ImprovedBiomassMLP, self).__init__()\n        self.network = nn.Sequential(\n            nn.Linear(input_size, hidden_size),\n            nn.BatchNorm1d(hidden_size),\n            nn.LeakyReLU(0.2),\n            nn.Dropout(dropout_rate),\n            nn.Linear(hidden_size, hidden_size // 2),\n            nn.BatchNorm1d(hidden_size // 2),\n            nn.LeakyReLU(0.2),\n            nn.Dropout(dropout_rate),\n            nn.Linear(hidden_size // 2, hidden_size // 4),\n            nn.BatchNorm1d(hidden_size // 4),\n            nn.LeakyReLU(0.2),\n            nn.Dropout(dropout_rate),\n            nn.Linear(hidden_size // 4, 1)\n        )\n    \n    def forward(self, x):\n        batch_size, num_patches, feature_dim = x.shape\n        x_flat = x.view(-1, feature_dim)\n        outputs = self.network(x_flat)\n        outputs = outputs.view(batch_size, num_patches, 1)\n        return torch.mean(torch.relu(outputs), dim=1)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-07T09:00:45.143649Z","iopub.execute_input":"2026-01-07T09:00:45.143901Z","iopub.status.idle":"2026-01-07T09:00:45.153626Z","shell.execute_reply.started":"2026-01-07T09:00:45.143886Z","shell.execute_reply":"2026-01-07T09:00:45.152933Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Kaggle路径配置\nDATA_ROOT = \"/kaggle/input/csiro-biomass\"\nRUN2_DATASET_ROOT = \"/kaggle/input/csiro-mlps-run2\"  # 你上传的 run2 数据集根目录（固定）\nDINOV2_MODEL_PATH = \"/kaggle/input/models/pytorch/default/1/models--facebook--dinov2-giant/snapshots/611a9d42f2335e0f921f1e313ad3c1b7178d206d\"\n\n\ndef _find_run2_model_root(root: str) -> str | None:\n    # 首选：同时包含 scalers_run2.joblib + target0 目录的位置\n    for dirpath, dirnames, filenames in os.walk(root):\n        if \"scalers_run2.joblib\" in filenames and os.path.isdir(os.path.join(dirpath, \"target0\")):\n            return dirpath\n\n    # 兜底：找到任意 target*/fold0.pt，然后返回其父目录作为 root\n    for dirpath, dirnames, filenames in os.walk(root):\n        if \"fold0.pt\" in filenames and os.path.basename(dirpath).startswith(\"target\"):\n            return os.path.dirname(dirpath)\n\n    return None\n\n\nMLP_ROOT = _find_run2_model_root(RUN2_DATASET_ROOT)\n\nprint(f\"Data: {DATA_ROOT}\")\nprint(f\"Run2 dataset root: {RUN2_DATASET_ROOT}\")\nprint(f\"DINOv2: {DINOV2_MODEL_PATH}\")\n\nif MLP_ROOT is None:\n    print(\"\\nERROR: 无法在 /kaggle/input/csiro-mlps-run2 下找到 run2 模型文件。\")\n    print(\"请确认数据集中包含: scalers_run2.joblib 和 target0/fold0.pt 等文件。\\n\")\n\n    # 打印前两层目录帮助你定位\n    for d in sorted(os.listdir(RUN2_DATASET_ROOT)):\n        p = os.path.join(RUN2_DATASET_ROOT, d)\n        print(\" -\", p)\n        if os.path.isdir(p):\n            sub = sorted(os.listdir(p))[:20]\n            for s in sub:\n                print(\"    -\", os.path.join(p, s))\n\n    raise RuntimeError(\"run2 模型目录未找到：请检查 Kaggle 数据集解压后的目录结构，然后把 MLP_ROOT 改成实际路径\")\n\nprint(f\"✅ Detected run2 MLP_ROOT: {MLP_ROOT}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-07T09:00:45.155034Z","iopub.execute_input":"2026-01-07T09:00:45.155633Z","iopub.status.idle":"2026-01-07T09:00:45.170241Z","shell.execute_reply.started":"2026-01-07T09:00:45.155611Z","shell.execute_reply":"2026-01-07T09:00:45.169600Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 加载DINOv2模型\nprint(\"Loading DINOv2...\")\nmodel = Dinov2Model.from_pretrained(DINOV2_MODEL_PATH, local_files_only=True)\nmodel.head = nn.Identity()\nmodel = model.to(device)\nmodel.eval()\nprint(\"✅ DINOv2 ready\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-07T09:00:45.170960Z","iopub.execute_input":"2026-01-07T09:00:45.171188Z","iopub.status.idle":"2026-01-07T09:00:46.940996Z","shell.execute_reply.started":"2026-01-07T09:00:45.171166Z","shell.execute_reply":"2026-01-07T09:00:46.940140Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 加载训练好的MLP模型和scalers\nprint(\"Loading MLP models...\")\nregressors = [[None for i in range(5)] for j in range(5)]\nscalers = None\n\n# 加载模型\nfor i in range(5):\n    for j in range(5):\n        model_path = os.path.join(MLP_ROOT, f\"target{i}/fold{j}.pt\")\n        if os.path.exists(model_path):\n            regressors[i][j] = torch.load(model_path, weights_only=False, map_location=device)\n\n# 加载scalers (run2格式)\nscaler_path = os.path.join(MLP_ROOT, \"scalers_run2.joblib\")\nif os.path.exists(scaler_path):\n    loaded_data = joblib.load(scaler_path)\n    scalers = loaded_data if not isinstance(loaded_data, tuple) else loaded_data[1]\n    print(\"✅ Scalers loaded\")\n\nprint(\"✅ All models loaded\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-07T09:00:46.941860Z","iopub.execute_input":"2026-01-07T09:00:46.942116Z","iopub.status.idle":"2026-01-07T09:00:47.164402Z","shell.execute_reply.started":"2026-01-07T09:00:46.942099Z","shell.execute_reply":"2026-01-07T09:00:47.163823Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 加载测试数据\ntest_df = pd.read_csv(os.path.join(DATA_ROOT, \"test.csv\"))\nprint(f\"Test samples: {len(test_df)}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-07T09:00:47.165066Z","iopub.execute_input":"2026-01-07T09:00:47.165240Z","iopub.status.idle":"2026-01-07T09:00:47.171074Z","shell.execute_reply.started":"2026-01-07T09:00:47.165226Z","shell.execute_reply":"2026-01-07T09:00:47.170373Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 数据增强变换\naugmentation_transforms = [\n    transforms.Compose([transforms.ToTensor(), transforms.Resize((224, 224)), transforms.Normalize(mean, std), torchvision.transforms.GaussianBlur(5)]),\n    transforms.Compose([transforms.RandomHorizontalFlip(p=1.0), transforms.ToTensor(), transforms.Resize((224, 224)), transforms.Normalize(mean, std), torchvision.transforms.GaussianBlur(5)]),\n    transforms.Compose([transforms.RandomVerticalFlip(p=1.0), transforms.ToTensor(), transforms.Resize((224, 224)), transforms.Normalize(mean, std), torchvision.transforms.GaussianBlur(5)]),\n    transforms.Compose([transforms.RandomHorizontalFlip(p=1.0), transforms.RandomVerticalFlip(p=1.0), transforms.ToTensor(), transforms.Resize((224, 224)), transforms.Normalize(mean, std), torchvision.transforms.GaussianBlur(5)]),\n    transforms.Compose([transforms.RandomRotation(degrees=90), transforms.ToTensor(), transforms.Resize((224, 224)), transforms.Normalize(mean, std), torchvision.transforms.GaussianBlur(5)]),\n    transforms.Compose([transforms.RandomRotation(degrees=270), transforms.ToTensor(), transforms.Resize((224, 224)), transforms.Normalize(mean, std), torchvision.transforms.GaussianBlur(5)]),\n]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-07T09:00:47.172812Z","iopub.execute_input":"2026-01-07T09:00:47.173242Z","iopub.status.idle":"2026-01-07T09:00:47.183408Z","shell.execute_reply.started":"2026-01-07T09:00:47.173224Z","shell.execute_reply":"2026-01-07T09:00:47.182596Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 提取特征（包含CLS token，特征维度3072）\nprint(\"Extracting features with CLS token...\")\ntest_embeds = {}\nsample_ids_processed = []\n\nfor i in tqdm(range(len(test_df))):\n    entry = test_df.iloc[i]\n    file_path = os.path.join(DATA_ROOT, entry['image_path'])\n    img_id = entry['sample_id'].split(\"__\")[0]\n    \n    if img_id not in sample_ids_processed:\n        image_embeddings = []\n        for aug in augmentation_transforms:\n            img = Image.open(file_path).convert('RGB')\n            x = aug(img).unsqueeze(0).to(device)\n            with torch.no_grad():\n                hidden_states = model(x).last_hidden_state\n                cls_token = hidden_states[:, 0:1, :]\n                patch_tokens = hidden_states[:, 1:, :]\n                num_patches = patch_tokens.shape[1]\n                cls_expanded = cls_token.repeat(1, num_patches, 1)\n                features = torch.cat([patch_tokens, cls_expanded], dim=-1)  # [1, num_patches, 3072]\n                image_embeddings.append(features.cpu()[0].unsqueeze(0))\n        test_embeds[img_id] = torch.stack(image_embeddings, dim=0)\n        sample_ids_processed.append(img_id)\n\nprint(f\"✅ Extracted {len(test_embeds)} images, feature dim: {list(test_embeds.values())[0].shape}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-07T09:00:47.184145Z","iopub.execute_input":"2026-01-07T09:00:47.184390Z","iopub.status.idle":"2026-01-07T09:00:49.381570Z","shell.execute_reply.started":"2026-01-07T09:00:47.184368Z","shell.execute_reply":"2026-01-07T09:00:49.380751Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 进行预测（应用特征标准化）\nprint(\"Making predictions...\")\npredictions = []\nsample_ids = []\n\nfor i in tqdm(range(len(test_df)), desc=\"Predicting\"):\n    entry = test_df.iloc[i]\n    img_id = entry['sample_id'].split(\"__\")[0]\n    target_name = entry['sample_id'].split(\"__\")[1]\n    X = test_embeds[img_id]\n    sample_ids.append(entry['sample_id'])\n    \n    models = regressors[mapping[target_name]]\n    scaler_list = scalers[mapping[target_name]] if scalers else None\n    \n    prediction = 0.0\n    model_count = 0\n    \n    for fold_idx, item in enumerate(models):\n        if item is None:\n            continue\n        item.eval()\n        item = item.to(device)\n        \n        # 应用scaler\n        X_scaled = X.clone()\n        if scaler_list and scaler_list[fold_idx] is not None:\n            scaler = scaler_list[fold_idx]\n            X_numpy = X.squeeze(1).cpu().numpy()\n            X_scaled_list = []\n            for aug_idx in range(X_numpy.shape[0]):\n                x_aug_scaled = scaler.transform(X_numpy[aug_idx])\n                X_scaled_list.append(x_aug_scaled)\n            X_scaled = torch.FloatTensor(np.array(X_scaled_list)).unsqueeze(1)\n        \n        with torch.no_grad():\n            pred = item(X_scaled.squeeze(1).to(device))\n            single_pred = torch.mean(torch.relu(pred))\n        \n        prediction += max(0.0, single_pred.cpu().item())\n        model_count += 1\n    \n    predictions.append(float(prediction / model_count if model_count > 0 else 0.0))\n\nprint(f\"✅ Completed {len(predictions)} predictions\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-07T09:00:49.382450Z","iopub.execute_input":"2026-01-07T09:00:49.382819Z","iopub.status.idle":"2026-01-07T09:00:50.503349Z","shell.execute_reply.started":"2026-01-07T09:00:49.382793Z","shell.execute_reply":"2026-01-07T09:00:50.502620Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 保存提交文件\nsubmission = pd.DataFrame({'sample_id': sample_ids, 'target': predictions})\nsubmission.to_csv('/kaggle/working/submission.csv', index=False)\n\nprint(\"=\"*60)\nprint(\"✅ Inference completed!\")\nprint(\"=\"*60)\nprint(f\"Total predictions: {len(submission)}\")\nprint(\"\\nFirst 10 predictions:\")\nprint(submission.head(10))\nprint(\"\\nStatistics:\")\nprint(submission['target'].describe())\nprint(\"=\"*60)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-07T09:00:50.504229Z","iopub.execute_input":"2026-01-07T09:00:50.504652Z","iopub.status.idle":"2026-01-07T09:00:50.515622Z","shell.execute_reply.started":"2026-01-07T09:00:50.504625Z","shell.execute_reply":"2026-01-07T09:00:50.514875Z"}},"outputs":[],"execution_count":null}]}