{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceType":"competition","sourceId":112509,"databundleVersionId":14254895,"isSourceIdPinned":false},{"sourceType":"modelInstanceVersion","sourceId":4534,"databundleVersionId":6346558,"modelInstanceId":3326,"modelId":986,"isSourceIdPinned":false}],"dockerImageVersionId":31287,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\nimport pandas as pd\nimport numpy as np\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader\nfrom sklearn.model_selection import KFold\nfrom PIL import Image\nimport torchvision.transforms as T\nfrom tqdm import tqdm\n\n# ==========================================\n# 1. 設定與路徑 (選用 Base 版)\n# ==========================================\nMODEL_NAME = 'dinov2_vitb14'  # 切換為 Base 版\nCSV_PATH = \"/kaggle/input/competitions/csiro-biomass/train.csv\"\nBASE_IMG_DIR = \"/kaggle/input/competitions/csiro-biomass/\" \nDEVICE = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\n# 數據切分\ndf_raw = pd.read_csv(CSV_PATH)\ndf_pivot = df_raw.pivot_table(index='image_path', columns='target_name', values='target').reset_index()\ndf_all = df_pivot.iloc[:350].reset_index(drop=True) \ndf_train_pool = df_all.iloc[:200].reset_index(drop=True)  # 200張訓練\ndf_test_final = df_all.iloc[200:300].reset_index(drop=True) # 100張測試\n\n# ==========================================\n# 2. Dataset 與模型定義\n# ==========================================\nclass BiomassDataset(Dataset):\n    def __init__(self, dataframe):\n        self.df = dataframe\n        self.transform = T.Compose([\n            T.Resize((224, 224)),\n            T.ToTensor(),\n            T.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n        ])\n    def __len__(self): return len(self.df)\n    def __getitem__(self, idx):\n        row = self.df.iloc[idx]\n        img = Image.open(os.path.join(BASE_IMG_DIR, row['image_path'])).convert(\"RGB\")\n        return self.transform(img), torch.tensor(row['Dry_Total_g'], dtype=torch.float32)\n\nclass DINOv2BaseRegressor(nn.Module):\n    def __init__(self):\n        super().__init__()\n        self.backbone = torch.hub.load('facebookresearch/dinov2', MODEL_NAME)\n        # Base 版 embed_dim 為 768\n        self.head = nn.Sequential(\n            nn.Linear(self.backbone.embed_dim, 256),\n            nn.ReLU(),\n            nn.Dropout(0.2), # 增加 Dropout 防止 Base 版過擬合\n            nn.Linear(256, 1)\n        )\n    def forward(self, x):\n        return self.head(self.backbone(x)).squeeze(-1)\n\n# ==========================================\n# 3. 6-Fold 訓練 (200張)\n# ==========================================\nkf = KFold(n_splits=6, shuffle=True, random_state=42)\ntrained_models = []\n\nprint(f\"🚀 開始 DINOv2 Base 6-Fold 訓練...\")\n\nfor fold, (t_idx, v_idx) in enumerate(kf.split(df_train_pool)):\n    print(f\"   👉 模型 Fold {fold+1}/6 訓練中...\")\n    train_loader = DataLoader(BiomassDataset(df_train_pool.iloc[t_idx]), batch_size=16, shuffle=True)\n    \n    model = DINOv2BaseRegressor().to(DEVICE)\n    optimizer = optim.Adam(model.parameters(), lr=1e-4) # Base 版建議維持較小學習率\n    criterion = nn.MSELoss()\n    \n    model.train()\n    for epoch in range(20): # Epoch 20\n        for imgs, labels in train_loader:\n            imgs, labels = imgs.to(DEVICE), labels.to(DEVICE)\n            optimizer.zero_grad()\n            criterion(model(imgs), labels).backward()\n            optimizer.step()\n    \n    model.eval()\n    trained_models.append(model)\n\n# ==========================================\n# 4. 集成預測與誤差分析 (100張測試集)\n# ==========================================\ntest_loader = DataLoader(BiomassDataset(df_test_final), batch_size=1)\npreds, truths = [], []\n\nprint(\"\\n🧪 正在執行 6 模型平均預測 (Ensemble)...\")\nwith torch.no_grad():\n    for img, label in tqdm(test_loader):\n        # 取得 6 個模型的平均預測值\n        avg_p = np.mean([m(img.to(DEVICE)).item() for m in trained_models])\n        preds.append(avg_p)\n        truths.append(label.item())\n\npreds, truths = np.array(preds), np.array(truths)\nerrors = preds - truths\n\n# ==========================================\n# 5. 輸出統計分析報告\n# ==========================================\nprint(\"\\n\" + \"=\"*50)\nprint(f\"{'DINOv2 Base 最終對比報告 (N=100)':^50}\")\nprint(\"=\"*50)\nprint(f\"最大誤差 (Max Error):      {np.max(np.abs(errors)):.4f}\")\nprint(f\"最小誤差 (Min Error):      {np.min(np.abs(errors)):.4f}\")\nprint(f\"誤差標準差 (Error Std):     {np.std(errors):.4f}\")\nprint(\"-\" * 50)\nprint(f\"預測值標準差 (Pred Std):    {np.std(preds):.4f}\")\nprint(f\"實際值標準差 (True Std):    {np.std(truths):.4f}\")\nprint(f\"離散度比值 (Pred/True):    {np.std(preds)/np.std(truths):.4f}\")\nprint(\"=\"*50)","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-03-09T12:59:47.678157Z","iopub.execute_input":"2026-03-09T12:59:47.678498Z","iopub.status.idle":"2026-03-09T13:25:24.025736Z","shell.execute_reply.started":"2026-03-09T12:59:47.678470Z","shell.execute_reply":"2026-03-09T13:25:24.024802Z"}},"outputs":[],"execution_count":null}]}