{"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":"gpu","dataSources":[{"sourceId":112509,"databundleVersionId":14254895,"sourceType":"competition"},{"sourceId":14232060,"sourceType":"datasetVersion","datasetId":9079556},{"sourceId":4537,"sourceType":"modelInstanceVersion","modelInstanceId":3329,"modelId":986}],"dockerImageVersionId":31193,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# parallel_residual_mlp","metadata":{}},{"cell_type":"code","source":"import os\nos.environ[\"TRANSFORMERS_NO_TF\"] = \"1\"\nos.environ[\"TRANSFORMERS_NO_FLAX\"] = \"1\"\nos.environ[\"TF_CPP_MIN_LOG_LEVEL\"] = \"3\"\n\nimport numpy as np\nimport pandas as pd\nimport torch\nimport torch.nn as nn\nfrom torch.utils.data import Dataset, DataLoader\nimport torchvision.transforms as transforms\nfrom PIL import Image\nfrom tqdm import tqdm\nimport sys\nfrom transformers import Dinov2Model\nfrom typing import Dict, List, Tuple\n\n# 忽略transformers安全检查警告\nimport transformers.utils.import_utils\ntransformers.utils.import_utils.check_torch_load_is_safe = lambda: None\n\n# ------------------------------\n# Kaggle 专用配置（严格匹配训练代码）\n# ------------------------------\nclass CFG:\n    seed = 42\n    batch_size = 32\n    img_size = 224\n    device = 'cuda' if torch.cuda.is_available() else 'cpu'\n    root_dir = \"/kaggle/input/csiro-biomass\"\n    test_csv = \"/kaggle/input/csiro-biomass/test.csv\"\n    targets = [\"Dry_Clover_g\", \"Dry_Dead_g\", \"Dry_Green_g\", \"Dry_Total_g\", \"GDM_g\"]\n    MODEL_DIR = '/kaggle/input/baseline-mlp-models/baseline_mlp_models'  # 与训练时的model_save_dir一致\n    embed_dim = 1536  # 严格匹配训练时的DINOv2-giant特征维度\n    submission_path = \"/kaggle/working/submission.csv\"\n    dinov2_model_path = '/kaggle/input/dinov2/pytorch/giant/1'\n\n# 固定随机种子\ndef seed_everything(seed: int) -> None:\n    torch.manual_seed(seed)\n    torch.cuda.manual_seed(seed)\n    torch.cuda.manual_seed_all(seed)\n    np.random.seed(seed)\n    os.environ['PYTHONHASHSEED'] = str(seed)\n    torch.backends.cudnn.deterministic = True\n    torch.backends.cudnn.benchmark = False\n\nseed_everything(CFG.seed)\n\n# ------------------------------\n# 基准MLP模型（与训练代码完全一致！不做任何修改）\n# ------------------------------\nclass BiomassMLP(nn.Module):\n    def __init__(self, input_size):\n        super(BiomassMLP, self).__init__()\n        self.network = nn.Sequential(\n            nn.Linear(input_size, 128),  # 严格保持训练时的128维隐藏层\n            nn.LeakyReLU(),\n            nn.Dropout(0.3),\n            nn.Linear(128, 1)  # 输出层维度也保持一致\n        )\n\n    def forward(self, x):\n        return self.network(x).squeeze(-1)\n\n# ------------------------------\n# 工具类（简化版，仅修复必要问题）\n# ------------------------------\nclass ImageFeatureExtractor:\n    def __init__(self, config: CFG):\n        self.config = config\n        self.transform = transforms.Compose([\n            transforms.ToTensor(),\n            transforms.Resize((config.img_size, config.img_size)),\n            transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])\n        ])\n        # 加载DINOv2-giant（与训练时一致）\n        print(\"Loading DINOv2-giant...\")\n        try:\n            self.model = Dinov2Model.from_pretrained(config.dinov2_model_path)\n        except Exception as e:\n            print(f\"Load local DINOv2 failed, try downloading: {e}\")\n            self.model = Dinov2Model.from_pretrained('facebook/dinov2-giant')\n        \n        self.model.head = nn.Identity()\n        self.model.to(config.device).eval()\n        self.embed_dim = self.model.config.hidden_size\n        # 验证维度匹配（训练时也是1536，所以这里必须一致）\n        assert self.embed_dim == config.embed_dim, f\"Feature dim mismatch: {self.embed_dim} vs {config.embed_dim}\"\n        print(f\"DINOv2-giant loaded successfully! Feature dimension: {self.embed_dim}\")\n\n    def extract_test_features(self, df: pd.DataFrame) -> Dict[str, torch.Tensor]:\n        class TestImageDataset(Dataset):\n            def __init__(self, df: pd.DataFrame, root_dir: str, transform: transforms.Compose):\n                self.df = df\n                self.root_dir = root_dir\n                self.transform = transform\n            \n            def __len__(self) -> int:\n                return len(self.df)\n            \n            def __getitem__(self, idx: int) -> Tuple[torch.Tensor, str]:\n                row = self.df.iloc[idx]\n                img_path = os.path.join(self.root_dir, row['image_path'].lstrip('/'))\n                try:\n                    image = Image.open(img_path).convert('RGB')\n                except Exception as e:\n                    print(f\"Warning: Load {img_path} failed, use empty image: {e}\")\n                    image = Image.new('RGB', (config.img_size, config.img_size))\n                return self.transform(image), row['sample_id']\n\n        # 降低batch_size避免显存溢出（DINOv2-giant占用大）\n        loader = DataLoader(\n            TestImageDataset(df, self.config.root_dir, self.transform),\n            batch_size=8,\n            shuffle=False,\n            num_workers=2,\n            pin_memory=True\n        )\n\n        feature_cache: Dict[str, torch.Tensor] = {}\n        with torch.no_grad():\n            for imgs, ids in tqdm(loader, desc=\"Extracting Test Features\"):\n                imgs = imgs.to(self.config.device, non_blocking=True)\n                outputs = self.model(imgs)\n                global_feat = outputs.last_hidden_state[:, 1:, :].mean(dim=1).cpu()\n                for i, sample_id in enumerate(ids):\n                    feature_cache[sample_id] = global_feat[i].clone()\n        \n        del self.model\n        torch.cuda.empty_cache()\n        print(f\"Test feature extraction done. Total samples: {len(feature_cache)}\")\n        return feature_cache\n\nclass TestFeatureDataset(Dataset):\n    def __init__(self, df: pd.DataFrame, features_dict: Dict[str, torch.Tensor], input_dim: int):\n        self.df = df\n        self.features_dict = features_dict\n        self.input_dim = input_dim\n\n    def __len__(self) -> int:\n        return len(self.df)\n\n    def __getitem__(self, idx: int) -> Tuple[torch.Tensor, str, str]:\n        row = self.df.iloc[idx]\n        sample_id = row['sample_id']\n        target = sample_id.split('__')[-1] if '__' in sample_id else ''\n        feat = self.features_dict.get(sample_id, torch.zeros(self.input_dim))\n        return feat, sample_id, target\n\n# ------------------------------\n# 模型加载（严格匹配训练时的模型结构和权重文件）\n# ------------------------------\ndef load_best_model(target_name: str, config: CFG) -> nn.Module:\n    target_dir = os.path.join(config.MODEL_DIR, target_name)\n    if not os.path.exists(target_dir):\n        raise FileNotFoundError(f\"Model dir not found: {target_dir}\")\n    \n    # 查找训练时保存的foldX_best.pth（与训练代码的保存文件名一致）\n    model_files = [f for f in os.listdir(target_dir) if f.endswith('_best.pth')]\n    if not model_files:\n        raise FileNotFoundError(f\"No model files for {target_name} in {target_dir} (expect foldX_best.pth)\")\n    \n    # 按fold编号排序，选择第一个模型（若要集成所有fold，可修改这里）\n    model_files.sort()\n    best_model_file = model_files[0]\n    model_path = os.path.join(target_dir, best_model_file)\n    \n    try:\n        # 初始化模型（输入维度1536，隐藏层128维，与训练时完全一致）\n        model = BiomassMLP(input_size=config.embed_dim).to(config.device)\n        # 加载权重（此时维度完全匹配）\n        model.load_state_dict(torch.load(model_path, map_location=config.device, weights_only=False))\n        model.eval()\n        print(f\"Loaded model for {target_name}: {best_model_file}\")\n        print(f\"Model structure match: input_dim={config.embed_dim}, hidden_dim=128\")\n        return model\n    except Exception as e:\n        raise ValueError(f\"Failed to load model {best_model_file}: {str(e)}\")\n\n# ------------------------------\n# 推理主逻辑（无修改，仅保证与训练代码一致）\n# ------------------------------\ndef main() -> None:\n    try:\n        print(\"=\"*60)\n        print(\"Baseline MLP Inference (Kaggle Version) - Fully Matched Training\")\n        print(\"=\"*60)\n        \n        # 1. 加载测试数据\n        print(\"\\n1. Loading test data...\")\n        if not os.path.exists(CFG.test_csv):\n            raise FileNotFoundError(f\"Test CSV not found: {CFG.test_csv}\")\n        df_test = pd.read_csv(CFG.test_csv)\n        print(f\"Test data shape: {df_test.shape}\")\n        print(f\"Sample IDs (first 5): {df_test['sample_id'].head().tolist()}\")\n\n        # 2. 提取测试集特征（与训练时用相同的DINOv2-giant）\n        print(\"\\n2. Extracting test features...\")\n        feature_extractor = ImageFeatureExtractor(CFG)\n        test_features = feature_extractor.extract_test_features(df_test)\n\n        # 3. 构建测试数据集\n        print(\"\\n3. Preparing test dataset...\")\n        test_dataset = TestFeatureDataset(df_test, test_features, CFG.embed_dim)\n        test_loader = DataLoader(\n            test_dataset, batch_size=CFG.batch_size, shuffle=False, num_workers=2, pin_memory=True\n        )\n\n        # 4. 加载所有目标的最佳模型（结构完全匹配）\n        print(\"\\n4. Loading best models...\")\n        target_models: Dict[str, nn.Module] = {}\n        for target in CFG.targets:\n            target_models[target] = load_best_model(target, CFG)\n\n        # 5. 执行推理\n        print(\"\\n5. Starting inference...\")\n        predictions: Dict[str, float] = {}\n        \n        with torch.no_grad():\n            for feats, sample_ids, targets in tqdm(test_loader, desc=\"Inferencing\"):\n                feats = feats.to(CFG.device, non_blocking=True)\n                \n                for i in range(len(sample_ids)):\n                    sample_id = sample_ids[i]\n                    target = targets[i]\n                    \n                    if target not in target_models:\n                        print(f\"Warning: Unknown target {target} for {sample_id}, predict 0.0\")\n                        predictions[sample_id] = 0.0\n                        continue\n                    \n                    model = target_models[target]\n                    pred = model(feats[i].unsqueeze(0))\n                    predictions[sample_id] = pred.cpu().item()\n\n        # 6. 生成提交文件\n        print(\"\\n6. Generating submission file...\")\n        submission_df = pd.DataFrame({\n            'sample_id': df_test['sample_id'],\n            'target': [predictions.get(sid, 0.0) for sid in df_test['sample_id']]\n        })\n\n        # 7. 保存提交文件\n        submission_df.to_csv(CFG.submission_path, index=False)\n        print(f\"Submission saved to: {CFG.submission_path}\")\n        print(f\"Submission file size: {os.path.getsize(CFG.submission_path) / 1024:.2f} KB\")\n\n        # 8. 验证提交文件\n        print(\"\\n7. Verifying submission format...\")\n        verify_df = pd.read_csv(CFG.submission_path)\n        if list(verify_df.columns) == ['sample_id', 'target'] and len(verify_df) == len(df_test):\n            print(\"✅ Submission format is correct!\")\n            print(f\"✅ Number of predictions: {len(verify_df)}\")\n            print(\"\\nSample submission (first 10 rows):\")\n            print(verify_df.head(10))\n        else:\n            print(\"❌ Submission format error!\")\n\n    except Exception as e:\n        print(f\"\\n❌ Inference failed: {str(e)}\")\n        import traceback\n        traceback.print_exc()\n        sys.exit(1)\n\nif __name__ == \"__main__\":\n    main()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-20T05:43:41.071982Z","iopub.execute_input":"2025-12-20T05:43:41.0724Z","iopub.status.idle":"2025-12-20T05:43:46.070282Z","shell.execute_reply.started":"2025-12-20T05:43:41.072369Z","shell.execute_reply":"2025-12-20T05:43:46.06936Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"1","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-20T05:39:54.31697Z","iopub.status.idle":"2025-12-20T05:39:54.317695Z","shell.execute_reply.started":"2025-12-20T05:39:54.317468Z","shell.execute_reply":"2025-12-20T05:39:54.317485Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# FINAL RESULTS & MODEL SUMMARY\n# ================================================================================\n# Dry_Clover_g    | Avg R2: 0.6351 | Folds: ['0.7063', '0.7428', '0.5314', '0.7345', '0.4604']\n# Dry_Dead_g      | Avg R2: 0.4818 | Folds: ['0.4464', '0.5244', '0.3598', '0.5545', '0.5241']\n# Dry_Green_g     | Avg R2: 0.7522 | Folds: ['0.7305', '0.7630', '0.8619', '0.8048', '0.6008']\n# Dry_Total_g     | Avg R2: 0.6222 | Folds: ['0.5633', '0.6347', '0.7497', '0.6651', '0.4979']\n# GDM_g           | Avg R2: 0.7256 | Folds: ['0.7033', '0.7184', '0.8565', '0.8278', '0.5221']\n\n\n# Weighted Final Score: 0.6431","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}