{"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":[{"sourceId":112509,"databundleVersionId":14254895,"isSourceIdPinned":false,"sourceType":"competition"},{"sourceId":14243931,"sourceType":"datasetVersion","datasetId":9087584},{"sourceId":14245328,"sourceType":"datasetVersion","datasetId":9088572}],"dockerImageVersionId":31236,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"\"\"\"\nCSIRO INFERENCE NOTEBOOK - PRO VERSION\nAuthor: Gemini & Duc Tai Phan\nFeature: Auto-Path + TTA (Test Time Augmentation) + Ensemble + Post-Processing\n\"\"\"\nimport sys\nimport os\nimport cv2\nimport glob\nimport numpy as np\nimport pandas as pd\nfrom tqdm.notebook import tqdm\nimport torch\nimport torch.nn as nn\nfrom torch.utils.data import Dataset, DataLoader\nimport timm\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2\n\n# --- CONFIG ---\nclass Config:\n    IMG_SIZE = 384\n    BATCH_SIZE = 32 # Giảm nếu Out Of Memory (OOM)\n    NUM_WORKERS = 2\n    \n    # QUAN TRỌNG: Tên model phải KHỚP CHÍNH XÁC với lúc bạn Train\n    # Nếu bạn dùng model cũ, giữ nguyên. Nếu train lại model to hơn, đổi tên ở đây.\n    MODEL_NAME = 'tf_efficientnetv2_s.in21k' \n    \n    DEVICE = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n    \n    # Cấu hình TTA (Test Time Augmentation)\n    USE_TTA = True # Bật True để tăng điểm (chạy lâu hơn x2)\n\n    # 1. Tự động tìm TẤT CẢ file weights (Hỗ trợ Ensemble nhiều Folds)\n    print(\"--> Đang tìm file model weights...\")\n    # Tìm tất cả file .pth có chữ 'best' hoặc 'fold'\n    model_paths = glob.glob('/kaggle/input/**/*.pth', recursive=True)\n    # Lọc các file có vẻ là model của mình (tránh file rác)\n    MODEL_PATHS = [p for p in model_paths if 'best' in p.lower() or 'checkpoint' in p.lower()]\n    \n    if not MODEL_PATHS:\n        # Fallback nếu không tìm thấy\n        print(\"CẢNH BÁO: Không tìm thấy file .pth theo pattern. Đang lấy tất cả .pth tìm được.\")\n        MODEL_PATHS = glob.glob('/kaggle/input/**/*.pth', recursive=True)\n\n    if not MODEL_PATHS:\n        raise FileNotFoundError(\"❌ KHÔNG TÌM THẤY FILE MODEL (.pth)! Vui lòng Upload Dataset chứa weights.\")\n    \n    print(f\"✅ Đã tìm thấy {len(MODEL_PATHS)} models để Ensemble:\")\n    for p in MODEL_PATHS: print(f\"   - {os.path.basename(p)}\")\n\n    # 2. Tự động tìm file test.csv\n    found_test_csv = glob.glob('/kaggle/input/**/test.csv', recursive=True)\n    if not found_test_csv:\n        raise FileNotFoundError(\"❌ KHÔNG TÌM THẤY dữ liệu cuộc thi (test.csv).\")\n    TEST_CSV_PATH = found_test_csv[0]\n    DATA_ROOT = os.path.dirname(TEST_CSV_PATH)\n    TARGET_COLS = ['Dry_Green_g', 'Dry_Dead_g', 'Dry_Clover_g', 'GDM_g', 'Dry_Total_g']\n\n# --- MODEL ARCHITECTURE ---\nclass MultiOutputModel(nn.Module):\n    def __init__(self, model_name, num_classes=5, pretrained=False):\n        super().__init__()\n        # Load backbone\n        self.backbone = timm.create_model(model_name, pretrained=pretrained, num_classes=0)\n        \n        # Lấy số features đầu ra của backbone\n        in_features = self.backbone.num_features\n        \n        # Head (Phải khớp với code Training của bạn)\n        # Nếu code train chỉ là nn.Linear(in_features, num_classes) thì sửa lại dòng dưới\n        # Dưới đây là Head mặc định phổ biến\n        self.head = nn.Sequential(\n            nn.Linear(in_features, 512),\n            nn.BatchNorm1d(512),\n            nn.SiLU(),\n            nn.Dropout(0.3),\n            nn.Linear(512, num_classes)\n        )\n        \n    def forward(self, x):\n        return self.head(self.backbone(x))\n\n# --- TRANSFORMS ---\ndef get_transforms():\n    return A.Compose([\n        A.Resize(Config.IMG_SIZE, Config.IMG_SIZE),\n        A.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),\n        ToTensorV2(),\n    ])\n\n# --- DATASET ---\nclass BiomassDataset(Dataset):\n    def __init__(self, df, transforms=None):\n        self.df = df\n        self.transforms = transforms\n        self.filepaths = df['filepath'].values\n            \n    def __len__(self):\n        return len(self.df)\n    \n    def __getitem__(self, idx):\n        path = self.filepaths[idx]\n        image = cv2.imread(path)\n        if image is None:\n            # Fallback ảnh đen nếu lỗi đọc file\n            image = np.zeros((Config.IMG_SIZE, Config.IMG_SIZE, 3), dtype=np.uint8)\n        else:\n            image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n            \n        if self.transforms:\n            image = self.transforms(image=image)['image']\n        return image\n\n# --- MAIN INFERENCE ---\nif __name__ == '__main__':\n    print(\"\\n=== BẮT ĐẦU QUÁ TRÌNH INFERENCE (PRO) ===\")\n    \n    # 1. Setup Data Paths\n    test_df = pd.read_csv(Config.TEST_CSV_PATH)\n    img_col = 'image_path' if 'image_path' in test_df.columns else 'image_id'\n    test_df['filename'] = test_df[img_col].apply(lambda x: os.path.basename(x))\n    \n    # Tìm folder ảnh thật\n    sample_file = test_df['filename'].iloc[0]\n    real_test_dir = None\n    for root, dirs, files in os.walk(Config.DATA_ROOT):\n        if sample_file in files:\n            real_test_dir = root\n            break\n    if real_test_dir is None: # Fallback scan rộng\n        for root, dirs, files in os.walk('/kaggle/input'):\n            if sample_file in files:\n                real_test_dir = root\n                break\n    \n    if not real_test_dir:\n         raise FileNotFoundError(f\"❌ Không tìm thấy ảnh '{sample_file}'!\")\n    \n    print(f\"--> Ảnh test nằm tại: {real_test_dir}\")\n    test_df['filepath'] = test_df['filename'].apply(lambda x: os.path.join(real_test_dir, x))\n    \n    # 2. Setup DataLoader\n    unique_test_images = test_df[['filename', 'filepath']].drop_duplicates().reset_index(drop=True)\n    test_ds = BiomassDataset(unique_test_images, transforms=get_transforms())\n    test_loader = DataLoader(test_ds, batch_size=Config.BATCH_SIZE, shuffle=False, num_workers=Config.NUM_WORKERS)\n    \n    # 3. Load Models & Ensemble\n    models = []\n    for path in Config.MODEL_PATHS:\n        try:\n            model = MultiOutputModel(Config.MODEL_NAME, pretrained=False)\n            state_dict = torch.load(path, map_location=Config.DEVICE)\n            model.load_state_dict(state_dict)\n            model.to(Config.DEVICE)\n            model.eval()\n            models.append(model)\n            print(f\"    Loaded: {os.path.basename(path)}\")\n        except Exception as e:\n            print(f\"⚠️ Lỗi khi load {os.path.basename(path)}: {e}. Bỏ qua file này.\")\n    \n    if len(models) == 0:\n        raise RuntimeError(\"Không load được model nào cả!\")\n\n    # 4. Predict Loop (With TTA)\n    final_preds = []\n    print(f\"--> Running Prediction (TTA={Config.USE_TTA}, Ensemble={len(models)} models)...\")\n    \n    with torch.no_grad():\n        for images in tqdm(test_loader, desc=\"Inference\"):\n            images = images.to(Config.DEVICE)\n            \n            # --- TTA STRATEGY ---\n            # 1. Dự đoán ảnh gốc\n            batch_preds = []\n            for model in models:\n                out = model(images)\n                batch_preds.append(torch.expm1(out).cpu().numpy())\n            \n            # 2. Dự đoán ảnh lật ngang (Horizontal Flip) nếu bật TTA\n            if Config.USE_TTA:\n                images_flip = torch.flip(images, dims=[3]) # Flip chiều width\n                for model in models:\n                    out = model(images_flip)\n                    batch_preds.append(torch.expm1(out).cpu().numpy())\n            \n            # Trung bình cộng tất cả dự đoán (Ensemble + TTA)\n            avg_preds = np.mean(batch_preds, axis=0)\n            \n            # Post-processing: Clip giá trị âm về 0 (Biomass không thể âm)\n            avg_preds = np.clip(avg_preds, a_min=0, a_max=None)\n            \n            final_preds.append(avg_preds)\n            \n    final_preds = np.concatenate(final_preds)\n    \n    # 5. Map Predictions to DataFrame\n    pred_map = {name: preds for name, preds in zip(unique_test_images['filename'].values, final_preds)}\n    target_to_idx = {col: i for i, col in enumerate(Config.TARGET_COLS)}\n    \n    submission_values = []\n    for _, row in tqdm(test_df.iterrows(), total=len(test_df), desc=\"Mapping\"):\n        fname = row['filename']\n        target_type = row['target_name']\n        \n        if fname in pred_map:\n            # Lấy đúng cột dự đoán tương ứng với target_name\n            val_idx = target_to_idx.get(target_type, 4) # Default to index 4 if not found\n            val = pred_map[fname][val_idx]\n            submission_values.append(val)\n        else:\n            submission_values.append(0.0)\n            \n    # 6. Save Submission\n    submission = pd.DataFrame({\n        'sample_id': test_df['sample_id'], \n        'target': submission_values\n    })\n    submission.to_csv('submission.csv', index=False)\n    print(\"\\n✅ SUCCESS! Đã tạo file submission.csv.\")\n    print(f\"Preview:\\n{submission.head()}\")","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-01-24T12:07:18.170084Z","iopub.execute_input":"2026-01-24T12:07:18.170771Z","iopub.status.idle":"2026-01-24T12:08:08.488350Z","shell.execute_reply.started":"2026-01-24T12:07:18.170742Z","shell.execute_reply":"2026-01-24T12:08:08.487475Z"}},"outputs":[],"execution_count":null}]}