{"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,"sourceType":"competition"},{"sourceId":13567461,"sourceType":"datasetVersion","datasetId":8618558},{"sourceId":13569643,"sourceType":"datasetVersion","datasetId":8620094},{"sourceId":272104372,"sourceType":"kernelVersion"},{"sourceId":272375916,"sourceType":"kernelVersion"},{"sourceId":272782007,"sourceType":"kernelVersion"},{"sourceId":273038178,"sourceType":"kernelVersion"},{"sourceId":4534,"sourceType":"modelInstanceVersion","modelInstanceId":3326,"modelId":986},{"sourceId":4537,"sourceType":"modelInstanceVersion","modelInstanceId":3329,"modelId":986}],"dockerImageVersionId":31154,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"## About\n1. Convnext base trained with 768 image size and 1000 img size ensemble\n2. Coatnet model using 2 stage training please refer attached notebooks for details\n3. Dino Giant and Dino base\n4. Tried using Lgbm instead of Lasso\n5. Added Convnext tiny with 512 Image size\n\nCredits : \n1. https://www.kaggle.com/code/takahitomizunobyts/lb-0-59-csiro-infer\n2. https://www.kaggle.com/code/none00000/lb-0-57-infer-model-code\n3. https://www.kaggle.com/code/mks2192/csiro-image2biomass-lb-59","metadata":{}},{"cell_type":"code","source":"import os\nimport pandas as pd\nimport numpy as np\nimport torch\nimport torch.nn as nn\nfrom torch.utils.data import Dataset, DataLoader\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2\nimport timm\nimport cv2\nfrom tqdm import tqdm\nimport gc\n\n# ===============================================================\n# 1. ⚙️ CONFIGURATION (PHẢI GIỐNG HỆT FILE TRAINING)\n# ===============================================================\nclass CFG:\n    # --- Đường dẫn (Paths) ---\n    # (Hãy điều chỉnh các đường dẫn này cho đúng với môi trường của bạn)\n    BASE_PATH = '/kaggle/input/csiro-biomass'\n    TEST_CSV = os.path.join(BASE_PATH, 'test.csv')\n    TEST_IMAGE_DIR = os.path.join(BASE_PATH, 'test')\n    \n    # Thư mục chứa 5 file .pth\n    MODEL_DIR = '/kaggle/input/csiro-convnext-base-768-imsize-0-6436/' # Giả sử 5 file .pth nằm cùng thư mục\n    SUBMISSION_FILE = 'submission.csv'\n    \n    # --- Cài đặt Mô hình (PHẢI TRÙNG KHỚP) ---\n    MODEL_NAME = 'convnext_base' # PHẢI GIỐNG HỆT LÚC TRAIN\n    IMG_SIZE = 768               # PHẢI GIỐNG HỆT LÚC TRAIN\n    \n    # --- Cài đặt Inference ---\n    DEVICE = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n    BATCH_SIZE = 1 # Có thể tăng batch size khi inference\n    NUM_WORKERS = 1\n    N_FOLDS = 5\n    \n    # --- Mục tiêu & Loss (PHẢI TRÙNG KHỚP) ---\n    # 3 mục tiêu model đã dự đoán\n    TARGET_COLS = ['Dry_Total_g', 'GDM_g', 'Dry_Green_g']\n    \n    # 5 mục tiêu để nộp bài\n    ALL_TARGET_COLS = ['Dry_Green_g', 'Dry_Dead_g', 'Dry_Clover_g', 'GDM_g', 'Dry_Total_g']\n\nprint(f\"Sử dụng thiết bị: {CFG.DEVICE}\")\nprint(f\"Backbone mô hình: {CFG.MODEL_NAME}\")\nprint(f\"Kích thước ảnh inference: {CFG.IMG_SIZE}x{CFG.IMG_SIZE}\")\n\n\n# ===============================================================\n# 2. 🏞️ AUGMENTATIONS (CHỈ DÙNG VALIDATION)\n# ===============================================================\nfrom albumentations import (\n    Compose, \n    Resize, \n    Normalize,\n    HorizontalFlip, \n    VerticalFlip\n)\n\ndef get_tta_transforms():\n    \"\"\"\n    Trả về một LIST các pipeline transform cho TTA.\n    Mỗi pipeline là một \"view\" khác nhau của ảnh.\n    \"\"\"\n    \n    # Đây là các bước chuẩn hóa cơ bản\n    base_transforms = [\n        Normalize(\n            mean=[0.485, 0.456, 0.406],\n            std=[0.229, 0.224, 0.225]\n        ),\n        ToTensorV2()\n    ]\n    \n    # -----------------\n    # View 1: Ảnh gốc (Chỉ Resize + Normalize)\n    # -----------------\n    original_view = Compose([\n        Resize(CFG.IMG_SIZE, CFG.IMG_SIZE),\n        *base_transforms\n    ])\n    \n    # -----------------\n    # View 2: Lật ngang (HFlip)\n    # -----------------\n    hflip_view = Compose([\n        HorizontalFlip(p=1.0), # Luôn luôn lật\n        Resize(CFG.IMG_SIZE, CFG.IMG_SIZE),\n        *base_transforms\n    ])\n    \n    # -----------------\n    # View 3: Lật dọc (VFlip)\n    # -----------------\n    vflip_view = Compose([\n        VerticalFlip(p=1.0), # Luôn luôn lật\n        Resize(CFG.IMG_SIZE, CFG.IMG_SIZE),\n        *base_transforms\n    ])\n    \n    return [original_view, hflip_view, vflip_view]\n\nprint(\"Đã định nghĩa hàm get_tta_transforms().\")\n\n\nclass TestBiomassDataset(Dataset):\n    \"\"\"\n    Dataset tùy chỉnh cho ảnh test (Chiến lược \"Hai luồng\").\n    Sửa đổi để chấp nhận một pipeline transform cụ thể cho TTA.\n    \"\"\"\n    def __init__(self, df, transform_pipeline, image_dir):\n        self.df = df\n        # (SỬA ĐỔI) Chấp nhận một pipeline đã được khởi tạo\n        self.transforms = transform_pipeline \n        self.image_dir = image_dir\n        self.image_paths = df['image_path'].values\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        # 1. Lấy thông tin\n        img_path_suffix = self.image_paths[idx]\n        \n        # 2. Đọc ảnh gốc (2000x1000)\n        filename = os.path.basename(img_path_suffix)\n        full_path = os.path.join(self.image_dir, filename)\n        \n        image = cv2.imread(full_path)\n        if image is None:\n            print(f\"Warning: Không thể đọc ảnh: {full_path}. Trả về ảnh đen.\")\n            image = np.zeros((1000, 2000, 3), dtype=np.uint8)\n        \n        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n        \n        # 3. Cắt (Crop) thành 2 ảnh (Trái và Phải)\n        height, width, _ = image.shape\n        mid_point = width // 2\n        img_left = image[:, :mid_point]\n        img_right = image[:, mid_point:]\n        \n        # 4. Áp dụng TTA Transform (CÙNG MỘT TRANSFORM cho cả 2)\n        # (Ví dụ: Cả 2 ảnh đều bị lật ngang)\n        img_left_tensor = self.transforms(image=img_left)['image']\n        img_right_tensor = self.transforms(image=img_right)['image']\n        \n        # 5. Trả về\n        return img_left_tensor, img_right_tensor\n\n# ===============================================================\n# 4. 🧠 MODEL ARCHITECTURE (SAO CHÉP TỪ FILE TRAIN)\n# ===============================================================\nclass BiomassModel(nn.Module):\n    \"\"\"\n    Kiến trúc mô hình (Hai luồng, Ba đầu ra)\n    PHẢI GIỐNG HỆT file training.\n    \"\"\"\n    def __init__(self, model_name, pretrained, n_targets=3):\n        super(BiomassModel, self).__init__()\n        \n        self.backbone = timm.create_model(\n            model_name,\n            pretrained=pretrained, # Sẽ là False khi inference\n            num_classes=0,\n            global_pool='avg'\n        )\n        \n        self.n_features = self.backbone.num_features\n        self.n_combined_features = self.n_features * 2\n        \n        # --- Đầu cho Dry_Total_g ---\n        self.head_total = nn.Sequential(\n            nn.Linear(self.n_combined_features, self.n_combined_features // 2),\n            nn.ReLU(),\n            nn.Dropout(0.3),\n            nn.Linear(self.n_combined_features // 2, 1)\n        )\n        \n        # --- Đầu cho GDM_g ---\n        self.head_gdm = nn.Sequential(\n            nn.Linear(self.n_combined_features, self.n_combined_features // 2),\n            nn.ReLU(),\n            nn.Dropout(0.3),\n            nn.Linear(self.n_combined_features // 2, 1)\n        )\n        \n        # --- Đầu cho Dry_Green_g ---\n        self.head_green = nn.Sequential(\n            nn.Linear(self.n_combined_features, self.n_combined_features // 2),\n            nn.ReLU(),\n            nn.Dropout(0.3),\n            nn.Linear(self.n_combined_features // 2, 1)\n        )\n\n    def forward(self, img_left, img_right):\n        features_left = self.backbone(img_left)\n        features_right = self.backbone(img_right)\n        combined = torch.cat([features_left, features_right], dim=1)\n        \n        out_total = self.head_total(combined)\n        out_gdm = self.head_gdm(combined)\n        out_green = self.head_green(combined)\n        \n        return out_total, out_gdm, out_green\n\n\ndef predict_one_view(models_list, test_loader, device):\n    \"\"\"\n    Hàm con: Chạy dự đoán ensemble 5-fold cho MỘT view TTA.\n    \"\"\"\n    view_preds_3 = {'total': [], 'gdm': [], 'green': []}\n    \n    with torch.no_grad():\n        for (img_left, img_right) in tqdm(test_loader, desc=\"  Predicting View\", leave=False):\n            img_left = img_left.to(device)\n            img_right = img_right.to(device)\n            \n            batch_preds_3_folds = {'total': [], 'gdm': [], 'green': []}\n            \n            # 1. Vòng lặp Ensemble 5-Fold\n            for model in models_list:\n                pred_total, pred_gdm, pred_green = model(img_left, img_right)\n                batch_preds_3_folds['total'].append(pred_total.cpu())\n                batch_preds_3_folds['gdm'].append(pred_gdm.cpu())\n                batch_preds_3_folds['green'].append(pred_green.cpu())\n            \n            # 2. Lấy trung bình 5 Fold\n            avg_pred_total = torch.mean(torch.stack(batch_preds_3_folds['total']), dim=0)\n            avg_pred_gdm = torch.mean(torch.stack(batch_preds_3_folds['gdm']), dim=0)\n            avg_pred_green = torch.mean(torch.stack(batch_preds_3_folds['green']), dim=0)\n            \n            view_preds_3['total'].append(avg_pred_total.numpy())\n            view_preds_3['gdm'].append(avg_pred_gdm.numpy())\n            view_preds_3['green'].append(avg_pred_green.numpy())\n\n    # 3. Ghép kết quả các batch của view này\n    preds_np = {\n        'total': np.concatenate(view_preds_3['total']).flatten(),\n        'gdm':   np.concatenate(view_preds_3['gdm']).flatten(),\n        'green': np.concatenate(view_preds_3['green']).flatten()\n    }\n    return preds_np\n\n\ndef run_inference_with_tta():\n    \"\"\"\n    Hàm inference chính, thực hiện TTA x Ensemble.\n    \"\"\"\n    print(f\"\\n{'='*50}\")\n    print(f\"🚀 BẮT ĐẦU INFERENCE (với TTA) 🚀\")\n    print(f\"{'='*50}\")\n\n    # --- 1. Tải Dữ liệu Test ---\n    print(f\"Đang tải {CFG.TEST_CSV}...\")\n    try:\n        test_df_long = pd.read_csv(CFG.TEST_CSV)\n        test_df_unique = test_df_long.drop_duplicates(subset=['image_path']).reset_index(drop=True)\n        print(f\"Tìm thấy {len(test_df_unique)} ảnh test duy nhất.\")\n    except FileNotFoundError:\n        print(f\"LỖI: Không tìm thấy {CFG.TEST_CSV}\")\n        return None, None, None\n\n    # --- 2. Tải 5 Mô hình (Ensemble) ---\n    print(\"\\nĐang tải 5 mô hình đã huấn luyện...\")\n    models_list = []\n    # (Code tải 5 mô hình... giống hệt bước 16 của file trước)\n    for fold in range(CFG.N_FOLDS):\n        model_path = os.path.join(CFG.MODEL_DIR, f'best_model_fold{fold}.pth')\n        if not os.path.exists(model_path):\n            print(f\"LỖI: Không tìm thấy file mô hình: {model_path}\")\n            return None, None, None\n        model = BiomassModel(CFG.MODEL_NAME, pretrained=False)\n        try:\n            model.load_state_dict(torch.load(model_path, map_location=CFG.DEVICE))\n        except RuntimeError:\n            state_dict = torch.load(model_path, map_location=CFG.DEVICE)\n            from collections import OrderedDict\n            new_state_dict = OrderedDict()\n            for k, v in state_dict.items():\n                name = k.replace('module.', '')\n                new_state_dict[name] = v\n            model.load_state_dict(new_state_dict)\n        model.eval()\n        model.to(CFG.DEVICE)\n        models_list.append(model)\n    print(f\"✓ Đã tải thành công {len(models_list)} mô hình.\")\n\n    # --- 3. Vòng lặp TTA (Vòng lặp ngoài) ---\n    tta_transforms = get_tta_transforms()\n    print(f\"\\nBắt đầu dự đoán với {len(tta_transforms)} TTA views...\")\n    \n    all_tta_view_preds = [] # List để lưu kết quả của mỗi view TTA\n\n    for i, tta_transform in enumerate(tta_transforms):\n        print(f\"--- Đang chạy TTA View {i+1}/{len(tta_transforms)} ---\")\n        \n        # Tạo Dataset/Loader MỚI cho view TTA này\n        test_dataset = TestBiomassDataset(\n            df=test_df_unique,\n            transform_pipeline=tta_transform, # Truyền pipeline TTA\n            image_dir=CFG.TEST_IMAGE_DIR\n        )\n        test_loader = DataLoader(\n            test_dataset,\n            batch_size=CFG.BATCH_SIZE,\n            shuffle=False,\n            num_workers=CFG.NUM_WORKERS,\n            pin_memory=True\n        )\n        \n        # Chạy ensemble 5-fold cho view này\n        view_preds_np = predict_one_view(models_list, test_loader, CFG.DEVICE)\n        all_tta_view_preds.append(view_preds_np)\n        print(f\"✓ Hoàn thành TTA View {i+1}\")\n\n    # --- 4. Ensemble (Lấy trung bình) kết quả TTA ---\n    print(\"\\nĐang ensemble kết quả của các TTA views...\")\n    final_ensembled_preds = {\n        'total': np.mean([d['total'] for d in all_tta_view_preds], axis=0),\n        'gdm':   np.mean([d['gdm'] for d in all_tta_view_preds], axis=0),\n        'green': np.mean([d['green'] for d in all_tta_view_preds], axis=0)\n    }\n    \n    print(\"✓ Dự đoán hoàn tất.\")\n    \n    del models_list, test_loader, test_dataset\n    gc.collect()\n    torch.cuda.empty_cache()\n    \n    return final_ensembled_preds, test_df_long, test_df_unique\n# ===============================================================\n# 6. ✍️ HÀM TẠO FILE SUBMISSION\n# ===============================================================\ndef create_submission(preds_np, test_df_long, test_df_unique):\n    \"\"\"\n    Hàm này nhận 3 dự đoán đã ensemble,\n    tính toán 2 dự đoán còn lại,\n    và định dạng file nộp bài.\n    \"\"\"\n    if preds_np is None:\n        print(\"Bỏ qua tạo submission do lỗi ở trên.\")\n        return\n\n    print(\"\\nĐang hậu xử lý và tạo file submission...\")\n\n    # 1. Lấy 3 dự đoán đã ensemble\n    pred_total_final = preds_np['total']\n    pred_gdm_final = preds_np['gdm']\n    pred_green_final = preds_np['green']\n\n    # 2. Tính 2 mục tiêu còn lại (Hậu xử lý)\n    # Dùng np.maximum(0, ...) để đảm bảo không có giá trị âm\n    pred_clover_final = np.maximum(0, pred_gdm_final - pred_green_final)\n    pred_dead_final = np.maximum(0, pred_total_final - pred_gdm_final)\n\n    # 3. Tạo một DataFrame \"wide\" chứa 5 dự đoán\n    # (Đảm bảo thứ tự 5 cột giống CFG.ALL_TARGET_COLS)\n    preds_wide_df = pd.DataFrame({\n        'image_path': test_df_unique['image_path'],\n        'Dry_Green_g': pred_green_final,\n        'Dry_Dead_g': pred_dead_final,\n        'Dry_Clover_g': pred_clover_final,\n        'GDM_g': pred_gdm_final,\n        'Dry_Total_g': pred_total_final\n    })\n\n    # 4. \"Un-pivot\" DataFrame (Chuyển sang dạng \"long\")\n    # Biến nó từ 5 cột về dạng \"long\" (giống sample_submission)\n    preds_long_df = preds_wide_df.melt(\n        id_vars=['image_path'],\n        value_vars=CFG.ALL_TARGET_COLS, # 5 cột mục tiêu\n        var_name='target_name',        # Cột tên mục tiêu\n        value_name='target'            # Cột giá trị dự đoán\n    )\n\n    # 5. Merge với file test.csv gốc (test_df_long)\n    # Đây là bước quan trọng để lấy đúng 'sample_id'\n    # (ví dụ: 'ID1001187975__Dry_Clover_g')\n    submission_df = pd.merge(\n        test_df_long[['sample_id', 'image_path', 'target_name']],\n        preds_long_df,\n        on=['image_path', 'target_name'],\n        how='left'\n    )\n\n    # 6. Dọn dẹp và Lưu\n    # Chỉ giữ lại 2 cột được yêu cầu\n    submission_df = submission_df[['sample_id', 'target']]\n    \n    # Lưu file\n    submission_df.to_csv(CFG.SUBMISSION_FILE, index=False)\n\n    print(f\"\\n🎉 HOÀN TẤT! Đã lưu file submission tại: {CFG.SUBMISSION_FILE}\")\n    print(\"--- 5 hàng đầu của file submission ---\")\n    print(submission_df.head())\n    print(\"\\n--- 5 hàng cuối của file submission ---\")\n    print(submission_df.tail())\n    \n# ===============================================================\n# 8. 🏁 CHẠY CHƯƠNG TRÌNH (Đã sửa)\n# ===============================================================\nif __name__ == \"__main__\":\n    # 1. Chạy dự đoán (đã bao gồm TTA)\n    all_preds_np, df_long, df_unique = run_inference_with_tta()\n    \n    # 2. Tạo file submission (Hàm create_submission giữ nguyên)\n    create_submission(all_preds_np, df_long, df_unique)","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-11-03T11:53:42.038316Z","iopub.execute_input":"2025-11-03T11:53:42.038898Z","iopub.status.idle":"2025-11-03T11:54:50.673189Z","shell.execute_reply.started":"2025-11-03T11:53:42.038862Z","shell.execute_reply":"2025-11-03T11:54:50.672480Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Model 2: Convnext tiny Without FP16 and Gradient accumulation","metadata":{}},{"cell_type":"code","source":"import os\nimport pandas as pd\nimport numpy as np\nimport torch\nimport torch.nn as nn\nfrom torch.utils.data import Dataset, DataLoader\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2\nimport timm\nimport cv2\nfrom tqdm import tqdm\nimport gc\n\n# ===============================================================\n# 1. ⚙️ CONFIGURATION (PHẢI GIỐNG HỆT FILE TRAINING)\n# ===============================================================\nclass CFG:\n    # --- Đường dẫn (Paths) ---\n    # (Hãy điều chỉnh các đường dẫn này cho đúng với môi trường của bạn)\n    BASE_PATH = '/kaggle/input/csiro-biomass'\n    TEST_CSV = os.path.join(BASE_PATH, 'test.csv')\n    TEST_IMAGE_DIR = os.path.join(BASE_PATH, 'test')\n    \n    # Thư mục chứa 5 file .pth\n    MODEL_DIR = '/kaggle/input/csiro/' # Giả sử 5 file .pth nằm cùng thư mục\n    SUBMISSION_FILE = 'submission2.csv'\n    \n    # --- Cài đặt Mô hình (PHẢI TRÙNG KHỚP) ---\n    MODEL_NAME = 'convnext_tiny' # PHẢI GIỐNG HỆT LÚC TRAIN\n    IMG_SIZE = 768               # PHẢI GIỐNG HỆT LÚC TRAIN\n    \n    # --- Cài đặt Inference ---\n    DEVICE = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n    BATCH_SIZE = 1 # Có thể tăng batch size khi inference\n    NUM_WORKERS = 1\n    N_FOLDS = 5\n    \n    # --- Mục tiêu & Loss (PHẢI TRÙNG KHỚP) ---\n    # 3 mục tiêu model đã dự đoán\n    TARGET_COLS = ['Dry_Total_g', 'GDM_g', 'Dry_Green_g']\n    \n    # 5 mục tiêu để nộp bài\n    ALL_TARGET_COLS = ['Dry_Green_g', 'Dry_Dead_g', 'Dry_Clover_g', 'GDM_g', 'Dry_Total_g']\n\nprint(f\"Sử dụng thiết bị: {CFG.DEVICE}\")\nprint(f\"Backbone mô hình: {CFG.MODEL_NAME}\")\nprint(f\"Kích thước ảnh inference: {CFG.IMG_SIZE}x{CFG.IMG_SIZE}\")\n\n\n# ===============================================================\n# 2. 🏞️ AUGMENTATIONS (CHỈ DÙNG VALIDATION)\n# ===============================================================\nfrom albumentations import (\n    Compose, \n    Resize, \n    Normalize,\n    HorizontalFlip, \n    VerticalFlip\n)\n\ndef get_tta_transforms():\n    \"\"\"\n    Trả về một LIST các pipeline transform cho TTA.\n    Mỗi pipeline là một \"view\" khác nhau của ảnh.\n    \"\"\"\n    \n    # Đây là các bước chuẩn hóa cơ bản\n    base_transforms = [\n        Normalize(\n            mean=[0.485, 0.456, 0.406],\n            std=[0.229, 0.224, 0.225]\n        ),\n        ToTensorV2()\n    ]\n    \n    # -----------------\n    # View 1: Ảnh gốc (Chỉ Resize + Normalize)\n    # -----------------\n    original_view = Compose([\n        Resize(CFG.IMG_SIZE, CFG.IMG_SIZE),\n        *base_transforms\n    ])\n    \n    # -----------------\n    # View 2: Lật ngang (HFlip)\n    # -----------------\n    hflip_view = Compose([\n        HorizontalFlip(p=1.0), # Luôn luôn lật\n        Resize(CFG.IMG_SIZE, CFG.IMG_SIZE),\n        *base_transforms\n    ])\n    \n    # -----------------\n    # View 3: Lật dọc (VFlip)\n    # -----------------\n    vflip_view = Compose([\n        VerticalFlip(p=1.0), # Luôn luôn lật\n        Resize(CFG.IMG_SIZE, CFG.IMG_SIZE),\n        *base_transforms\n    ])\n    \n    return [original_view, hflip_view, vflip_view]\n\nprint(\"Đã định nghĩa hàm get_tta_transforms().\")\n\n\nclass TestBiomassDataset(Dataset):\n    \"\"\"\n    Dataset tùy chỉnh cho ảnh test (Chiến lược \"Hai luồng\").\n    Sửa đổi để chấp nhận một pipeline transform cụ thể cho TTA.\n    \"\"\"\n    def __init__(self, df, transform_pipeline, image_dir):\n        self.df = df\n        # (SỬA ĐỔI) Chấp nhận một pipeline đã được khởi tạo\n        self.transforms = transform_pipeline \n        self.image_dir = image_dir\n        self.image_paths = df['image_path'].values\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        # 1. Lấy thông tin\n        img_path_suffix = self.image_paths[idx]\n        \n        # 2. Đọc ảnh gốc (2000x1000)\n        filename = os.path.basename(img_path_suffix)\n        full_path = os.path.join(self.image_dir, filename)\n        \n        image = cv2.imread(full_path)\n        if image is None:\n            print(f\"Warning: Không thể đọc ảnh: {full_path}. Trả về ảnh đen.\")\n            image = np.zeros((1000, 2000, 3), dtype=np.uint8)\n        \n        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n        \n        # 3. Cắt (Crop) thành 2 ảnh (Trái và Phải)\n        height, width, _ = image.shape\n        mid_point = width // 2\n        img_left = image[:, :mid_point]\n        img_right = image[:, mid_point:]\n        \n        # 4. Áp dụng TTA Transform (CÙNG MỘT TRANSFORM cho cả 2)\n        # (Ví dụ: Cả 2 ảnh đều bị lật ngang)\n        img_left_tensor = self.transforms(image=img_left)['image']\n        img_right_tensor = self.transforms(image=img_right)['image']\n        \n        # 5. Trả về\n        return img_left_tensor, img_right_tensor\n\n# ===============================================================\n# 4. 🧠 MODEL ARCHITECTURE (SAO CHÉP TỪ FILE TRAIN)\n# ===============================================================\nclass BiomassModel(nn.Module):\n    \"\"\"\n    Kiến trúc mô hình (Hai luồng, Ba đầu ra)\n    PHẢI GIỐNG HỆT file training.\n    \"\"\"\n    def __init__(self, model_name, pretrained, n_targets=3):\n        super(BiomassModel, self).__init__()\n        \n        self.backbone = timm.create_model(\n            model_name,\n            pretrained=pretrained, # Sẽ là False khi inference\n            num_classes=0,\n            global_pool='avg'\n        )\n        \n        self.n_features = self.backbone.num_features\n        self.n_combined_features = self.n_features * 2\n        \n        # --- Đầu cho Dry_Total_g ---\n        self.head_total = nn.Sequential(\n            nn.Linear(self.n_combined_features, self.n_combined_features // 2),\n            nn.ReLU(),\n            nn.Dropout(0.3),\n            nn.Linear(self.n_combined_features // 2, 1)\n        )\n        \n        # --- Đầu cho GDM_g ---\n        self.head_gdm = nn.Sequential(\n            nn.Linear(self.n_combined_features, self.n_combined_features // 2),\n            nn.ReLU(),\n            nn.Dropout(0.3),\n            nn.Linear(self.n_combined_features // 2, 1)\n        )\n        \n        # --- Đầu cho Dry_Green_g ---\n        self.head_green = nn.Sequential(\n            nn.Linear(self.n_combined_features, self.n_combined_features // 2),\n            nn.ReLU(),\n            nn.Dropout(0.3),\n            nn.Linear(self.n_combined_features // 2, 1)\n        )\n\n    def forward(self, img_left, img_right):\n        features_left = self.backbone(img_left)\n        features_right = self.backbone(img_right)\n        combined = torch.cat([features_left, features_right], dim=1)\n        \n        out_total = self.head_total(combined)\n        out_gdm = self.head_gdm(combined)\n        out_green = self.head_green(combined)\n        \n        return out_total, out_gdm, out_green\n\n\ndef predict_one_view(models_list, test_loader, device):\n    \"\"\"\n    Hàm con: Chạy dự đoán ensemble 5-fold cho MỘT view TTA.\n    \"\"\"\n    view_preds_3 = {'total': [], 'gdm': [], 'green': []}\n    \n    with torch.no_grad():\n        for (img_left, img_right) in tqdm(test_loader, desc=\"  Predicting View\", leave=False):\n            img_left = img_left.to(device)\n            img_right = img_right.to(device)\n            \n            batch_preds_3_folds = {'total': [], 'gdm': [], 'green': []}\n            \n            # 1. Vòng lặp Ensemble 5-Fold\n            for model in models_list:\n                pred_total, pred_gdm, pred_green = model(img_left, img_right)\n                batch_preds_3_folds['total'].append(pred_total.cpu())\n                batch_preds_3_folds['gdm'].append(pred_gdm.cpu())\n                batch_preds_3_folds['green'].append(pred_green.cpu())\n            \n            # 2. Lấy trung bình 5 Fold\n            avg_pred_total = torch.mean(torch.stack(batch_preds_3_folds['total']), dim=0)\n            avg_pred_gdm = torch.mean(torch.stack(batch_preds_3_folds['gdm']), dim=0)\n            avg_pred_green = torch.mean(torch.stack(batch_preds_3_folds['green']), dim=0)\n            \n            view_preds_3['total'].append(avg_pred_total.numpy())\n            view_preds_3['gdm'].append(avg_pred_gdm.numpy())\n            view_preds_3['green'].append(avg_pred_green.numpy())\n\n    # 3. Ghép kết quả các batch của view này\n    preds_np = {\n        'total': np.concatenate(view_preds_3['total']).flatten(),\n        'gdm':   np.concatenate(view_preds_3['gdm']).flatten(),\n        'green': np.concatenate(view_preds_3['green']).flatten()\n    }\n    return preds_np\n\n\ndef run_inference_with_tta():\n    \"\"\"\n    Hàm inference chính, thực hiện TTA x Ensemble.\n    \"\"\"\n    print(f\"\\n{'='*50}\")\n    print(f\"🚀 BẮT ĐẦU INFERENCE (với TTA) 🚀\")\n    print(f\"{'='*50}\")\n\n    # --- 1. Tải Dữ liệu Test ---\n    print(f\"Đang tải {CFG.TEST_CSV}...\")\n    try:\n        test_df_long = pd.read_csv(CFG.TEST_CSV)\n        test_df_unique = test_df_long.drop_duplicates(subset=['image_path']).reset_index(drop=True)\n        print(f\"Tìm thấy {len(test_df_unique)} ảnh test duy nhất.\")\n    except FileNotFoundError:\n        print(f\"LỖI: Không tìm thấy {CFG.TEST_CSV}\")\n        return None, None, None\n\n    # --- 2. Tải 5 Mô hình (Ensemble) ---\n    print(\"\\nĐang tải 5 mô hình đã huấn luyện...\")\n    models_list = []\n    # (Code tải 5 mô hình... giống hệt bước 16 của file trước)\n    for fold in range(CFG.N_FOLDS):\n        model_path = os.path.join(CFG.MODEL_DIR, f'best_model_fold{fold}.pth')\n        if not os.path.exists(model_path):\n            print(f\"LỖI: Không tìm thấy file mô hình: {model_path}\")\n            return None, None, None\n        model = BiomassModel(CFG.MODEL_NAME, pretrained=False)\n        try:\n            model.load_state_dict(torch.load(model_path, map_location=CFG.DEVICE))\n        except RuntimeError:\n            state_dict = torch.load(model_path, map_location=CFG.DEVICE)\n            from collections import OrderedDict\n            new_state_dict = OrderedDict()\n            for k, v in state_dict.items():\n                name = k.replace('module.', '')\n                new_state_dict[name] = v\n            model.load_state_dict(new_state_dict)\n        model.eval()\n        model.to(CFG.DEVICE)\n        models_list.append(model)\n    print(f\"✓ Đã tải thành công {len(models_list)} mô hình.\")\n\n    # --- 3. Vòng lặp TTA (Vòng lặp ngoài) ---\n    tta_transforms = get_tta_transforms()\n    print(f\"\\nBắt đầu dự đoán với {len(tta_transforms)} TTA views...\")\n    \n    all_tta_view_preds = [] # List để lưu kết quả của mỗi view TTA\n\n    for i, tta_transform in enumerate(tta_transforms):\n        print(f\"--- Đang chạy TTA View {i+1}/{len(tta_transforms)} ---\")\n        \n        # Tạo Dataset/Loader MỚI cho view TTA này\n        test_dataset = TestBiomassDataset(\n            df=test_df_unique,\n            transform_pipeline=tta_transform, # Truyền pipeline TTA\n            image_dir=CFG.TEST_IMAGE_DIR\n        )\n        test_loader = DataLoader(\n            test_dataset,\n            batch_size=CFG.BATCH_SIZE,\n            shuffle=False,\n            num_workers=CFG.NUM_WORKERS,\n            pin_memory=True\n        )\n        \n        # Chạy ensemble 5-fold cho view này\n        view_preds_np = predict_one_view(models_list, test_loader, CFG.DEVICE)\n        all_tta_view_preds.append(view_preds_np)\n        print(f\"✓ Hoàn thành TTA View {i+1}\")\n\n    # --- 4. Ensemble (Lấy trung bình) kết quả TTA ---\n    print(\"\\nĐang ensemble kết quả của các TTA views...\")\n    final_ensembled_preds = {\n        'total': np.mean([d['total'] for d in all_tta_view_preds], axis=0),\n        'gdm':   np.mean([d['gdm'] for d in all_tta_view_preds], axis=0),\n        'green': np.mean([d['green'] for d in all_tta_view_preds], axis=0)\n    }\n    \n    print(\"✓ Dự đoán hoàn tất.\")\n    \n    del models_list, test_loader, test_dataset\n    gc.collect()\n    torch.cuda.empty_cache()\n    \n    return final_ensembled_preds, test_df_long, test_df_unique\n# ===============================================================\n# 6. ✍️ HÀM TẠO FILE SUBMISSION\n# ===============================================================\ndef create_submission(preds_np, test_df_long, test_df_unique):\n    \"\"\"\n    Hàm này nhận 3 dự đoán đã ensemble,\n    tính toán 2 dự đoán còn lại,\n    và định dạng file nộp bài.\n    \"\"\"\n    if preds_np is None:\n        print(\"Bỏ qua tạo submission do lỗi ở trên.\")\n        return\n\n    print(\"\\nĐang hậu xử lý và tạo file submission...\")\n\n    # 1. Lấy 3 dự đoán đã ensemble\n    pred_total_final = preds_np['total']\n    pred_gdm_final = preds_np['gdm']\n    pred_green_final = preds_np['green']\n\n    # 2. Tính 2 mục tiêu còn lại (Hậu xử lý)\n    # Dùng np.maximum(0, ...) để đảm bảo không có giá trị âm\n    pred_clover_final = np.maximum(0, pred_gdm_final - pred_green_final)\n    pred_dead_final = np.maximum(0, pred_total_final - pred_gdm_final)\n\n    # 3. Tạo một DataFrame \"wide\" chứa 5 dự đoán\n    # (Đảm bảo thứ tự 5 cột giống CFG.ALL_TARGET_COLS)\n    preds_wide_df = pd.DataFrame({\n        'image_path': test_df_unique['image_path'],\n        'Dry_Green_g': pred_green_final,\n        'Dry_Dead_g': pred_dead_final,\n        'Dry_Clover_g': pred_clover_final,\n        'GDM_g': pred_gdm_final,\n        'Dry_Total_g': pred_total_final\n    })\n\n    # 4. \"Un-pivot\" DataFrame (Chuyển sang dạng \"long\")\n    # Biến nó từ 5 cột về dạng \"long\" (giống sample_submission)\n    preds_long_df = preds_wide_df.melt(\n        id_vars=['image_path'],\n        value_vars=CFG.ALL_TARGET_COLS, # 5 cột mục tiêu\n        var_name='target_name',        # Cột tên mục tiêu\n        value_name='target'            # Cột giá trị dự đoán\n    )\n\n    # 5. Merge với file test.csv gốc (test_df_long)\n    # Đây là bước quan trọng để lấy đúng 'sample_id'\n    # (ví dụ: 'ID1001187975__Dry_Clover_g')\n    submission_df = pd.merge(\n        test_df_long[['sample_id', 'image_path', 'target_name']],\n        preds_long_df,\n        on=['image_path', 'target_name'],\n        how='left'\n    )\n\n    # 6. Dọn dẹp và Lưu\n    # Chỉ giữ lại 2 cột được yêu cầu\n    submission_df = submission_df[['sample_id', 'target']]\n    \n    # Lưu file\n    submission_df.to_csv(CFG.SUBMISSION_FILE, index=False)\n\n    print(f\"\\n🎉 HOÀN TẤT! Đã lưu file submission tại: {CFG.SUBMISSION_FILE}\")\n    print(\"--- 5 hàng đầu của file submission ---\")\n    print(submission_df.head())\n    print(\"\\n--- 5 hàng cuối của file submission ---\")\n    print(submission_df.tail())\n    \n# ===============================================================\n# 8. 🏁 CHẠY CHƯƠNG TRÌNH (Đã sửa)\n# ===============================================================\nif __name__ == \"__main__\":\n    # 1. Chạy dự đoán (đã bao gồm TTA)\n    all_preds_np, df_long, df_unique = run_inference_with_tta()\n    \n    # 2. Tạo file submission (Hàm create_submission giữ nguyên)\n    create_submission(all_preds_np, df_long, df_unique)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-03T11:54:50.674626Z","iopub.execute_input":"2025-11-03T11:54:50.674856Z","iopub.status.idle":"2025-11-03T11:55:01.346372Z","shell.execute_reply.started":"2025-11-03T11:54:50.674839Z","shell.execute_reply":"2025-11-03T11:55:01.345636Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## COATNET","metadata":{}},{"cell_type":"code","source":"import os\nimport pandas as pd\nimport numpy as np\nimport torch\nimport torch.nn as nn\nfrom torch.utils.data import Dataset, DataLoader\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2\nimport timm\nimport cv2\nfrom tqdm import tqdm\nimport gc\n\n# ===============================================================\n# 1. ⚙️ CONFIGURATION (PHẢI GIỐNG HỆT FILE TRAINING)\n# ===============================================================\nclass CFG:\n    # --- Đường dẫn (Paths) ---\n    # (Hãy điều chỉnh các đường dẫn này cho đúng với môi trường của bạn)\n    BASE_PATH = '/kaggle/input/csiro-biomass'\n    TEST_CSV = os.path.join(BASE_PATH, 'test.csv')\n    TEST_IMAGE_DIR = os.path.join(BASE_PATH, 'test')\n    \n    # Thư mục chứa 5 file .pth\n    MODEL_DIR = '/kaggle/input/csiro-coatnet-with-oof-train/' # Giả sử 5 file .pth nằm cùng thư mục\n    SUBMISSION_FILE = 'submission3.csv'\n    \n    # --- Cài đặt Mô hình (PHẢI TRÙNG KHỚP) ---\n    MODEL_NAME = 'coatnet_rmlp_2_rw_384.sw_in12k_ft_in1k' # PHẢI GIỐNG HỆT LÚC TRAIN\n    IMG_SIZE = 384               # PHẢI GIỐNG HỆT LÚC TRAIN\n    \n    # --- Cài đặt Inference ---\n    DEVICE = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n    BATCH_SIZE = 1 # Có thể tăng batch size khi inference\n    NUM_WORKERS = 1\n    N_FOLDS = 5\n    \n    # --- Mục tiêu & Loss (PHẢI TRÙNG KHỚP) ---\n    # 3 mục tiêu model đã dự đoán\n    TARGET_COLS = ['Dry_Total_g', 'GDM_g', 'Dry_Green_g']\n    \n    # 5 mục tiêu để nộp bài\n    ALL_TARGET_COLS = ['Dry_Green_g', 'Dry_Dead_g', 'Dry_Clover_g', 'GDM_g', 'Dry_Total_g']\n\nprint(f\"Sử dụng thiết bị: {CFG.DEVICE}\")\nprint(f\"Backbone mô hình: {CFG.MODEL_NAME}\")\nprint(f\"Kích thước ảnh inference: {CFG.IMG_SIZE}x{CFG.IMG_SIZE}\")\n\n\n# ===============================================================\n# 2. 🏞️ AUGMENTATIONS (CHỈ DÙNG VALIDATION)\n# ===============================================================\nfrom albumentations import (\n    Compose, \n    Resize, \n    Normalize,\n    HorizontalFlip, \n    VerticalFlip\n)\n\ndef get_tta_transforms():\n    \"\"\"\n    Trả về một LIST các pipeline transform cho TTA.\n    Mỗi pipeline là một \"view\" khác nhau của ảnh.\n    \"\"\"\n    \n    # Đây là các bước chuẩn hóa cơ bản\n    base_transforms = [\n        Normalize(\n            mean=[0.485, 0.456, 0.406],\n            std=[0.229, 0.224, 0.225]\n        ),\n        ToTensorV2()\n    ]\n    \n    # -----------------\n    # View 1: Ảnh gốc (Chỉ Resize + Normalize)\n    # -----------------\n    original_view = Compose([\n        Resize(CFG.IMG_SIZE, CFG.IMG_SIZE),\n        *base_transforms\n    ])\n    \n    # -----------------\n    # View 2: Lật ngang (HFlip)\n    # -----------------\n    hflip_view = Compose([\n        HorizontalFlip(p=1.0), # Luôn luôn lật\n        Resize(CFG.IMG_SIZE, CFG.IMG_SIZE),\n        *base_transforms\n    ])\n    \n    # -----------------\n    # View 3: Lật dọc (VFlip)\n    # -----------------\n    vflip_view = Compose([\n        VerticalFlip(p=1.0), # Luôn luôn lật\n        Resize(CFG.IMG_SIZE, CFG.IMG_SIZE),\n        *base_transforms\n    ])\n    \n    return [original_view, hflip_view, vflip_view]\n\nprint(\"Đã định nghĩa hàm get_tta_transforms().\")\n\n\nclass TestBiomassDataset(Dataset):\n    \"\"\"\n    Dataset tùy chỉnh cho ảnh test (Chiến lược \"Hai luồng\").\n    Sửa đổi để chấp nhận một pipeline transform cụ thể cho TTA.\n    \"\"\"\n    def __init__(self, df, transform_pipeline, image_dir):\n        self.df = df\n        # (SỬA ĐỔI) Chấp nhận một pipeline đã được khởi tạo\n        self.transforms = transform_pipeline \n        self.image_dir = image_dir\n        self.image_paths = df['image_path'].values\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        # 1. Lấy thông tin\n        img_path_suffix = self.image_paths[idx]\n        \n        # 2. Đọc ảnh gốc (2000x1000)\n        filename = os.path.basename(img_path_suffix)\n        full_path = os.path.join(self.image_dir, filename)\n        \n        image = cv2.imread(full_path)\n        if image is None:\n            print(f\"Warning: Không thể đọc ảnh: {full_path}. Trả về ảnh đen.\")\n            image = np.zeros((1000, 2000, 3), dtype=np.uint8)\n        \n        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n        \n        # 3. Cắt (Crop) thành 2 ảnh (Trái và Phải)\n        height, width, _ = image.shape\n        mid_point = width // 2\n        img_left = image[:, :mid_point]\n        img_right = image[:, mid_point:]\n        \n        # 4. Áp dụng TTA Transform (CÙNG MỘT TRANSFORM cho cả 2)\n        # (Ví dụ: Cả 2 ảnh đều bị lật ngang)\n        img_left_tensor = self.transforms(image=img_left)['image']\n        img_right_tensor = self.transforms(image=img_right)['image']\n        \n        # 5. Trả về\n        return img_left_tensor, img_right_tensor\n\n# ===============================================================\n# 4. 🧠 MODEL ARCHITECTURE (SAO CHÉP TỪ FILE TRAIN)\n# ===============================================================\nclass BiomassModel(nn.Module):\n    \"\"\"\n    Kiến trúc mô hình (Hai luồng, Ba đầu ra)\n    PHẢI GIỐNG HỆT file training.\n    \"\"\"\n    def __init__(self, model_name, pretrained, n_targets=3):\n        super(BiomassModel, self).__init__()\n        \n        self.backbone = timm.create_model(\n            model_name,\n            pretrained=pretrained, # Sẽ là False khi inference\n            num_classes=0,\n            global_pool='avg'\n        )\n        \n        self.n_features = self.backbone.num_features\n        self.n_combined_features = self.n_features * 2\n        \n        # --- Đầu cho Dry_Total_g ---\n        self.head_total = nn.Sequential(\n            nn.Linear(self.n_combined_features, self.n_combined_features // 2),\n            nn.ReLU(),\n            nn.Dropout(0.3),\n            nn.Linear(self.n_combined_features // 2, 1)\n        )\n        \n        # --- Đầu cho GDM_g ---\n        self.head_gdm = nn.Sequential(\n            nn.Linear(self.n_combined_features, self.n_combined_features // 2),\n            nn.ReLU(),\n            nn.Dropout(0.3),\n            nn.Linear(self.n_combined_features // 2, 1)\n        )\n        \n        # --- Đầu cho Dry_Green_g ---\n        self.head_green = nn.Sequential(\n            nn.Linear(self.n_combined_features, self.n_combined_features // 2),\n            nn.ReLU(),\n            nn.Dropout(0.3),\n            nn.Linear(self.n_combined_features // 2, 1)\n        )\n\n    def forward(self, img_left, img_right):\n        features_left = self.backbone(img_left)\n        features_right = self.backbone(img_right)\n        combined = torch.cat([features_left, features_right], dim=1)\n        \n        out_total = self.head_total(combined)\n        out_gdm = self.head_gdm(combined)\n        out_green = self.head_green(combined)\n        \n        return out_total, out_gdm, out_green\n\n\ndef predict_one_view(models_list, test_loader, device):\n    \"\"\"\n    Hàm con: Chạy dự đoán ensemble 5-fold cho MỘT view TTA.\n    \"\"\"\n    view_preds_3 = {'total': [], 'gdm': [], 'green': []}\n    \n    with torch.no_grad():\n        for (img_left, img_right) in tqdm(test_loader, desc=\"  Predicting View\", leave=False):\n            img_left = img_left.to(device)\n            img_right = img_right.to(device)\n            \n            batch_preds_3_folds = {'total': [], 'gdm': [], 'green': []}\n            \n            # 1. Vòng lặp Ensemble 5-Fold\n            for model in models_list:\n                pred_total, pred_gdm, pred_green = model(img_left, img_right)\n                batch_preds_3_folds['total'].append(pred_total.cpu())\n                batch_preds_3_folds['gdm'].append(pred_gdm.cpu())\n                batch_preds_3_folds['green'].append(pred_green.cpu())\n            \n            # 2. Lấy trung bình 5 Fold\n            avg_pred_total = torch.mean(torch.stack(batch_preds_3_folds['total']), dim=0)\n            avg_pred_gdm = torch.mean(torch.stack(batch_preds_3_folds['gdm']), dim=0)\n            avg_pred_green = torch.mean(torch.stack(batch_preds_3_folds['green']), dim=0)\n            \n            view_preds_3['total'].append(avg_pred_total.numpy())\n            view_preds_3['gdm'].append(avg_pred_gdm.numpy())\n            view_preds_3['green'].append(avg_pred_green.numpy())\n\n    # 3. Ghép kết quả các batch của view này\n    preds_np = {\n        'total': np.concatenate(view_preds_3['total']).flatten(),\n        'gdm':   np.concatenate(view_preds_3['gdm']).flatten(),\n        'green': np.concatenate(view_preds_3['green']).flatten()\n    }\n    return preds_np\n\n\ndef run_inference_with_tta():\n    \"\"\"\n    Hàm inference chính, thực hiện TTA x Ensemble.\n    \"\"\"\n    print(f\"\\n{'='*50}\")\n    print(f\"🚀 BẮT ĐẦU INFERENCE (với TTA) 🚀\")\n    print(f\"{'='*50}\")\n\n    # --- 1. Tải Dữ liệu Test ---\n    print(f\"Đang tải {CFG.TEST_CSV}...\")\n    try:\n        test_df_long = pd.read_csv(CFG.TEST_CSV)\n        test_df_unique = test_df_long.drop_duplicates(subset=['image_path']).reset_index(drop=True)\n        print(f\"Tìm thấy {len(test_df_unique)} ảnh test duy nhất.\")\n    except FileNotFoundError:\n        print(f\"LỖI: Không tìm thấy {CFG.TEST_CSV}\")\n        return None, None, None\n\n    # --- 2. Tải 5 Mô hình (Ensemble) ---\n    print(\"\\nĐang tải 5 mô hình đã huấn luyện...\")\n    models_list = []\n    # (Code tải 5 mô hình... giống hệt bước 16 của file trước)\n    for fold in range(CFG.N_FOLDS):\n        model_path = os.path.join(CFG.MODEL_DIR, f'best_model_fold{fold}.pth')\n        if not os.path.exists(model_path):\n            print(f\"LỖI: Không tìm thấy file mô hình: {model_path}\")\n            return None, None, None\n        model = BiomassModel(CFG.MODEL_NAME, pretrained=False)\n        try:\n            model.load_state_dict(torch.load(model_path, map_location=CFG.DEVICE))\n        except RuntimeError:\n            state_dict = torch.load(model_path, map_location=CFG.DEVICE)\n            from collections import OrderedDict\n            new_state_dict = OrderedDict()\n            for k, v in state_dict.items():\n                name = k.replace('module.', '')\n                new_state_dict[name] = v\n            model.load_state_dict(new_state_dict)\n        model.eval()\n        model.to(CFG.DEVICE)\n        models_list.append(model)\n    print(f\"✓ Đã tải thành công {len(models_list)} mô hình.\")\n\n    # --- 3. Vòng lặp TTA (Vòng lặp ngoài) ---\n    tta_transforms = get_tta_transforms()\n    print(f\"\\nBắt đầu dự đoán với {len(tta_transforms)} TTA views...\")\n    \n    all_tta_view_preds = [] # List để lưu kết quả của mỗi view TTA\n\n    for i, tta_transform in enumerate(tta_transforms):\n        print(f\"--- Đang chạy TTA View {i+1}/{len(tta_transforms)} ---\")\n        \n        # Tạo Dataset/Loader MỚI cho view TTA này\n        test_dataset = TestBiomassDataset(\n            df=test_df_unique,\n            transform_pipeline=tta_transform, # Truyền pipeline TTA\n            image_dir=CFG.TEST_IMAGE_DIR\n        )\n        test_loader = DataLoader(\n            test_dataset,\n            batch_size=CFG.BATCH_SIZE,\n            shuffle=False,\n            num_workers=CFG.NUM_WORKERS,\n            pin_memory=True\n        )\n        \n        # Chạy ensemble 5-fold cho view này\n        view_preds_np = predict_one_view(models_list, test_loader, CFG.DEVICE)\n        all_tta_view_preds.append(view_preds_np)\n        print(f\"✓ Hoàn thành TTA View {i+1}\")\n\n    # --- 4. Ensemble (Lấy trung bình) kết quả TTA ---\n    print(\"\\nĐang ensemble kết quả của các TTA views...\")\n    final_ensembled_preds = {\n        'total': np.mean([d['total'] for d in all_tta_view_preds], axis=0),\n        'gdm':   np.mean([d['gdm'] for d in all_tta_view_preds], axis=0),\n        'green': np.mean([d['green'] for d in all_tta_view_preds], axis=0)\n    }\n    \n    print(\"✓ Dự đoán hoàn tất.\")\n    \n    del models_list, test_loader, test_dataset\n    gc.collect()\n    torch.cuda.empty_cache()\n    \n    return final_ensembled_preds, test_df_long, test_df_unique\n# ===============================================================\n# 6. ✍️ HÀM TẠO FILE SUBMISSION\n# ===============================================================\ndef create_submission(preds_np, test_df_long, test_df_unique):\n    \"\"\"\n    Hàm này nhận 3 dự đoán đã ensemble,\n    tính toán 2 dự đoán còn lại,\n    và định dạng file nộp bài.\n    \"\"\"\n    if preds_np is None:\n        print(\"Bỏ qua tạo submission do lỗi ở trên.\")\n        return\n\n    print(\"\\nĐang hậu xử lý và tạo file submission...\")\n\n    # 1. Lấy 3 dự đoán đã ensemble\n    pred_total_final = preds_np['total']\n    pred_gdm_final = preds_np['gdm']\n    pred_green_final = preds_np['green']\n\n    # 2. Tính 2 mục tiêu còn lại (Hậu xử lý)\n    # Dùng np.maximum(0, ...) để đảm bảo không có giá trị âm\n    pred_clover_final = np.maximum(0, pred_gdm_final - pred_green_final)\n    pred_dead_final = np.maximum(0, pred_total_final - pred_gdm_final)\n\n    # 3. Tạo một DataFrame \"wide\" chứa 5 dự đoán\n    # (Đảm bảo thứ tự 5 cột giống CFG.ALL_TARGET_COLS)\n    preds_wide_df = pd.DataFrame({\n        'image_path': test_df_unique['image_path'],\n        'Dry_Green_g': pred_green_final,\n        'Dry_Dead_g': pred_dead_final,\n        'Dry_Clover_g': pred_clover_final,\n        'GDM_g': pred_gdm_final,\n        'Dry_Total_g': pred_total_final\n    })\n\n    # 4. \"Un-pivot\" DataFrame (Chuyển sang dạng \"long\")\n    # Biến nó từ 5 cột về dạng \"long\" (giống sample_submission)\n    preds_long_df = preds_wide_df.melt(\n        id_vars=['image_path'],\n        value_vars=CFG.ALL_TARGET_COLS, # 5 cột mục tiêu\n        var_name='target_name',        # Cột tên mục tiêu\n        value_name='target'            # Cột giá trị dự đoán\n    )\n\n    # 5. Merge với file test.csv gốc (test_df_long)\n    # Đây là bước quan trọng để lấy đúng 'sample_id'\n    # (ví dụ: 'ID1001187975__Dry_Clover_g')\n    submission_df = pd.merge(\n        test_df_long[['sample_id', 'image_path', 'target_name']],\n        preds_long_df,\n        on=['image_path', 'target_name'],\n        how='left'\n    )\n\n    # 6. Dọn dẹp và Lưu\n    # Chỉ giữ lại 2 cột được yêu cầu\n    submission_df = submission_df[['sample_id', 'target']]\n    \n    # Lưu file\n    submission_df.to_csv(CFG.SUBMISSION_FILE, index=False)\n\n    print(f\"\\n🎉 HOÀN TẤT! Đã lưu file submission tại: {CFG.SUBMISSION_FILE}\")\n    print(\"--- 5 hàng đầu của file submission ---\")\n    print(submission_df.head())\n    print(\"\\n--- 5 hàng cuối của file submission ---\")\n    print(submission_df.tail())\n    \n# ===============================================================\n# 8. 🏁 CHẠY CHƯƠNG TRÌNH (Đã sửa)\n# ===============================================================\nif __name__ == \"__main__\":\n    # 1. Chạy dự đoán (đã bao gồm TTA)\n    all_preds_np, df_long, df_unique = run_inference_with_tta()\n    \n    # 2. Tạo file submission (Hàm create_submission giữ nguyên)\n    create_submission(all_preds_np, df_long, df_unique)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-03T11:55:01.347945Z","iopub.execute_input":"2025-11-03T11:55:01.348212Z","iopub.status.idle":"2025-11-03T11:55:20.530354Z","shell.execute_reply.started":"2025-11-03T11:55:01.348193Z","shell.execute_reply":"2025-11-03T11:55:20.529633Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Convnext Tiny 512 Image_Size","metadata":{}},{"cell_type":"code","source":"import os\nimport pandas as pd\nimport numpy as np\nimport torch\nimport torch.nn as nn\nfrom torch.utils.data import Dataset, DataLoader\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2\nimport timm\nimport cv2\nfrom tqdm import tqdm\nimport gc\n\n# ===============================================================\n# 1. ⚙️ CONFIGURATION (PHẢI GIỐNG HỆT FILE TRAINING)\n# ===============================================================\nclass CFG:\n    # --- Đường dẫn (Paths) ---\n    # (Hãy điều chỉnh các đường dẫn này cho đúng với môi trường của bạn)\n    BASE_PATH = '/kaggle/input/csiro-biomass'\n    TEST_CSV = os.path.join(BASE_PATH, 'test.csv')\n    TEST_IMAGE_DIR = os.path.join(BASE_PATH, 'test')\n    \n    # Thư mục chứa 5 file .pth\n    MODEL_DIR = '/kaggle/input/csiro-train-convnext-tiny-512-imsize/' # Giả sử 5 file .pth nằm cùng thư mục\n    SUBMISSION_FILE = 'submission_convnext_512_Imsize.csv'\n    \n    # --- Cài đặt Mô hình (PHẢI TRÙNG KHỚP) ---\n    MODEL_NAME = 'convnext_tiny' # PHẢI GIỐNG HỆT LÚC TRAIN\n    IMG_SIZE = 512               # PHẢI GIỐNG HỆT LÚC TRAIN\n    \n    # --- Cài đặt Inference ---\n    DEVICE = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n    BATCH_SIZE = 1 # Có thể tăng batch size khi inference\n    NUM_WORKERS = 1\n    N_FOLDS = 5\n    \n    # --- Mục tiêu & Loss (PHẢI TRÙNG KHỚP) ---\n    # 3 mục tiêu model đã dự đoán\n    TARGET_COLS = ['Dry_Total_g', 'GDM_g', 'Dry_Green_g']\n    \n    # 5 mục tiêu để nộp bài\n    ALL_TARGET_COLS = ['Dry_Green_g', 'Dry_Dead_g', 'Dry_Clover_g', 'GDM_g', 'Dry_Total_g']\n\nprint(f\"Sử dụng thiết bị: {CFG.DEVICE}\")\nprint(f\"Backbone mô hình: {CFG.MODEL_NAME}\")\nprint(f\"Kích thước ảnh inference: {CFG.IMG_SIZE}x{CFG.IMG_SIZE}\")\n\n\n# ===============================================================\n# 2. 🏞️ AUGMENTATIONS (CHỈ DÙNG VALIDATION)\n# ===============================================================\nfrom albumentations import (\n    Compose, \n    Resize, \n    Normalize,\n    HorizontalFlip, \n    VerticalFlip\n)\n\ndef get_tta_transforms():\n    \"\"\"\n    Trả về một LIST các pipeline transform cho TTA.\n    Mỗi pipeline là một \"view\" khác nhau của ảnh.\n    \"\"\"\n    \n    # Đây là các bước chuẩn hóa cơ bản\n    base_transforms = [\n        Normalize(\n            mean=[0.485, 0.456, 0.406],\n            std=[0.229, 0.224, 0.225]\n        ),\n        ToTensorV2()\n    ]\n    \n    # -----------------\n    # View 1: Ảnh gốc (Chỉ Resize + Normalize)\n    # -----------------\n    original_view = Compose([\n        Resize(CFG.IMG_SIZE, CFG.IMG_SIZE),\n        *base_transforms\n    ])\n    \n    # -----------------\n    # View 2: Lật ngang (HFlip)\n    # -----------------\n    hflip_view = Compose([\n        HorizontalFlip(p=1.0), # Luôn luôn lật\n        Resize(CFG.IMG_SIZE, CFG.IMG_SIZE),\n        *base_transforms\n    ])\n    \n    # -----------------\n    # View 3: Lật dọc (VFlip)\n    # -----------------\n    vflip_view = Compose([\n        VerticalFlip(p=1.0), # Luôn luôn lật\n        Resize(CFG.IMG_SIZE, CFG.IMG_SIZE),\n        *base_transforms\n    ])\n    \n    return [original_view, hflip_view, vflip_view]\n\nprint(\"Đã định nghĩa hàm get_tta_transforms().\")\n\n\nclass TestBiomassDataset(Dataset):\n    \"\"\"\n    Dataset tùy chỉnh cho ảnh test (Chiến lược \"Hai luồng\").\n    Sửa đổi để chấp nhận một pipeline transform cụ thể cho TTA.\n    \"\"\"\n    def __init__(self, df, transform_pipeline, image_dir):\n        self.df = df\n        # (SỬA ĐỔI) Chấp nhận một pipeline đã được khởi tạo\n        self.transforms = transform_pipeline \n        self.image_dir = image_dir\n        self.image_paths = df['image_path'].values\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        # 1. Lấy thông tin\n        img_path_suffix = self.image_paths[idx]\n        \n        # 2. Đọc ảnh gốc (2000x1000)\n        filename = os.path.basename(img_path_suffix)\n        full_path = os.path.join(self.image_dir, filename)\n        \n        image = cv2.imread(full_path)\n        if image is None:\n            print(f\"Warning: Không thể đọc ảnh: {full_path}. Trả về ảnh đen.\")\n            image = np.zeros((1000, 2000, 3), dtype=np.uint8)\n        \n        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n        \n        # 3. Cắt (Crop) thành 2 ảnh (Trái và Phải)\n        height, width, _ = image.shape\n        mid_point = width // 2\n        img_left = image[:, :mid_point]\n        img_right = image[:, mid_point:]\n        \n        # 4. Áp dụng TTA Transform (CÙNG MỘT TRANSFORM cho cả 2)\n        # (Ví dụ: Cả 2 ảnh đều bị lật ngang)\n        img_left_tensor = self.transforms(image=img_left)['image']\n        img_right_tensor = self.transforms(image=img_right)['image']\n        \n        # 5. Trả về\n        return img_left_tensor, img_right_tensor\n\n# ===============================================================\n# 4. 🧠 MODEL ARCHITECTURE (SAO CHÉP TỪ FILE TRAIN)\n# ===============================================================\nclass BiomassModel(nn.Module):\n    \"\"\"\n    Kiến trúc mô hình (Hai luồng, Ba đầu ra)\n    PHẢI GIỐNG HỆT file training.\n    \"\"\"\n    def __init__(self, model_name, pretrained, n_targets=3):\n        super(BiomassModel, self).__init__()\n        \n        self.backbone = timm.create_model(\n            model_name,\n            pretrained=pretrained, # Sẽ là False khi inference\n            num_classes=0,\n            global_pool='avg'\n        )\n        \n        self.n_features = self.backbone.num_features\n        self.n_combined_features = self.n_features * 2\n        \n        # --- Đầu cho Dry_Total_g ---\n        self.head_total = nn.Sequential(\n            nn.Linear(self.n_combined_features, self.n_combined_features // 2),\n            nn.ReLU(),\n            nn.Dropout(0.3),\n            nn.Linear(self.n_combined_features // 2, 1)\n        )\n        \n        # --- Đầu cho GDM_g ---\n        self.head_gdm = nn.Sequential(\n            nn.Linear(self.n_combined_features, self.n_combined_features // 2),\n            nn.ReLU(),\n            nn.Dropout(0.3),\n            nn.Linear(self.n_combined_features // 2, 1)\n        )\n        \n        # --- Đầu cho Dry_Green_g ---\n        self.head_green = nn.Sequential(\n            nn.Linear(self.n_combined_features, self.n_combined_features // 2),\n            nn.ReLU(),\n            nn.Dropout(0.3),\n            nn.Linear(self.n_combined_features // 2, 1)\n        )\n\n    def forward(self, img_left, img_right):\n        features_left = self.backbone(img_left)\n        features_right = self.backbone(img_right)\n        combined = torch.cat([features_left, features_right], dim=1)\n        \n        out_total = self.head_total(combined)\n        out_gdm = self.head_gdm(combined)\n        out_green = self.head_green(combined)\n        \n        return out_total, out_gdm, out_green\n\n\ndef predict_one_view(models_list, test_loader, device):\n    \"\"\"\n    Hàm con: Chạy dự đoán ensemble 5-fold cho MỘT view TTA.\n    \"\"\"\n    view_preds_3 = {'total': [], 'gdm': [], 'green': []}\n    \n    with torch.no_grad():\n        for (img_left, img_right) in tqdm(test_loader, desc=\"  Predicting View\", leave=False):\n            img_left = img_left.to(device)\n            img_right = img_right.to(device)\n            \n            batch_preds_3_folds = {'total': [], 'gdm': [], 'green': []}\n            \n            # 1. Vòng lặp Ensemble 5-Fold\n            for model in models_list:\n                pred_total, pred_gdm, pred_green = model(img_left, img_right)\n                batch_preds_3_folds['total'].append(pred_total.cpu())\n                batch_preds_3_folds['gdm'].append(pred_gdm.cpu())\n                batch_preds_3_folds['green'].append(pred_green.cpu())\n            \n            # 2. Lấy trung bình 5 Fold\n            avg_pred_total = torch.mean(torch.stack(batch_preds_3_folds['total']), dim=0)\n            avg_pred_gdm = torch.mean(torch.stack(batch_preds_3_folds['gdm']), dim=0)\n            avg_pred_green = torch.mean(torch.stack(batch_preds_3_folds['green']), dim=0)\n            \n            view_preds_3['total'].append(avg_pred_total.numpy())\n            view_preds_3['gdm'].append(avg_pred_gdm.numpy())\n            view_preds_3['green'].append(avg_pred_green.numpy())\n\n    # 3. Ghép kết quả các batch của view này\n    preds_np = {\n        'total': np.concatenate(view_preds_3['total']).flatten(),\n        'gdm':   np.concatenate(view_preds_3['gdm']).flatten(),\n        'green': np.concatenate(view_preds_3['green']).flatten()\n    }\n    return preds_np\n\n\ndef run_inference_with_tta():\n    \"\"\"\n    Hàm inference chính, thực hiện TTA x Ensemble.\n    \"\"\"\n    print(f\"\\n{'='*50}\")\n    print(f\"🚀 BẮT ĐẦU INFERENCE (với TTA) 🚀\")\n    print(f\"{'='*50}\")\n\n    # --- 1. Tải Dữ liệu Test ---\n    print(f\"Đang tải {CFG.TEST_CSV}...\")\n    try:\n        test_df_long = pd.read_csv(CFG.TEST_CSV)\n        test_df_unique = test_df_long.drop_duplicates(subset=['image_path']).reset_index(drop=True)\n        print(f\"Tìm thấy {len(test_df_unique)} ảnh test duy nhất.\")\n    except FileNotFoundError:\n        print(f\"LỖI: Không tìm thấy {CFG.TEST_CSV}\")\n        return None, None, None\n\n    # --- 2. Tải 5 Mô hình (Ensemble) ---\n    print(\"\\nĐang tải 5 mô hình đã huấn luyện...\")\n    models_list = []\n    # (Code tải 5 mô hình... giống hệt bước 16 của file trước)\n    for fold in range(CFG.N_FOLDS):\n        model_path = os.path.join(CFG.MODEL_DIR, f'best_model_fold{fold}.pth')\n        if not os.path.exists(model_path):\n            print(f\"LỖI: Không tìm thấy file mô hình: {model_path}\")\n            return None, None, None\n        model = BiomassModel(CFG.MODEL_NAME, pretrained=False)\n        try:\n            model.load_state_dict(torch.load(model_path, map_location=CFG.DEVICE))\n        except RuntimeError:\n            state_dict = torch.load(model_path, map_location=CFG.DEVICE)\n            from collections import OrderedDict\n            new_state_dict = OrderedDict()\n            for k, v in state_dict.items():\n                name = k.replace('module.', '')\n                new_state_dict[name] = v\n            model.load_state_dict(new_state_dict)\n        model.eval()\n        model.to(CFG.DEVICE)\n        models_list.append(model)\n    print(f\"✓ Đã tải thành công {len(models_list)} mô hình.\")\n\n    # --- 3. Vòng lặp TTA (Vòng lặp ngoài) ---\n    tta_transforms = get_tta_transforms()\n    print(f\"\\nBắt đầu dự đoán với {len(tta_transforms)} TTA views...\")\n    \n    all_tta_view_preds = [] # List để lưu kết quả của mỗi view TTA\n\n    for i, tta_transform in enumerate(tta_transforms):\n        print(f\"--- Đang chạy TTA View {i+1}/{len(tta_transforms)} ---\")\n        \n        # Tạo Dataset/Loader MỚI cho view TTA này\n        test_dataset = TestBiomassDataset(\n            df=test_df_unique,\n            transform_pipeline=tta_transform, # Truyền pipeline TTA\n            image_dir=CFG.TEST_IMAGE_DIR\n        )\n        test_loader = DataLoader(\n            test_dataset,\n            batch_size=CFG.BATCH_SIZE,\n            shuffle=False,\n            num_workers=CFG.NUM_WORKERS,\n            pin_memory=True\n        )\n        \n        # Chạy ensemble 5-fold cho view này\n        view_preds_np = predict_one_view(models_list, test_loader, CFG.DEVICE)\n        all_tta_view_preds.append(view_preds_np)\n        print(f\"✓ Hoàn thành TTA View {i+1}\")\n\n    # --- 4. Ensemble (Lấy trung bình) kết quả TTA ---\n    print(\"\\nĐang ensemble kết quả của các TTA views...\")\n    final_ensembled_preds = {\n        'total': np.mean([d['total'] for d in all_tta_view_preds], axis=0),\n        'gdm':   np.mean([d['gdm'] for d in all_tta_view_preds], axis=0),\n        'green': np.mean([d['green'] for d in all_tta_view_preds], axis=0)\n    }\n    \n    print(\"✓ Dự đoán hoàn tất.\")\n    \n    del models_list, test_loader, test_dataset\n    gc.collect()\n    torch.cuda.empty_cache()\n    \n    return final_ensembled_preds, test_df_long, test_df_unique\n# ===============================================================\n# 6. ✍️ HÀM TẠO FILE SUBMISSION\n# ===============================================================\ndef create_submission(preds_np, test_df_long, test_df_unique):\n    \"\"\"\n    Hàm này nhận 3 dự đoán đã ensemble,\n    tính toán 2 dự đoán còn lại,\n    và định dạng file nộp bài.\n    \"\"\"\n    if preds_np is None:\n        print(\"Bỏ qua tạo submission do lỗi ở trên.\")\n        return\n\n    print(\"\\nĐang hậu xử lý và tạo file submission...\")\n\n    # 1. Lấy 3 dự đoán đã ensemble\n    pred_total_final = preds_np['total']\n    pred_gdm_final = preds_np['gdm']\n    pred_green_final = preds_np['green']\n\n    # 2. Tính 2 mục tiêu còn lại (Hậu xử lý)\n    # Dùng np.maximum(0, ...) để đảm bảo không có giá trị âm\n    pred_clover_final = np.maximum(0, pred_gdm_final - pred_green_final)\n    pred_dead_final = np.maximum(0, pred_total_final - pred_gdm_final)\n\n    # 3. Tạo một DataFrame \"wide\" chứa 5 dự đoán\n    # (Đảm bảo thứ tự 5 cột giống CFG.ALL_TARGET_COLS)\n    preds_wide_df = pd.DataFrame({\n        'image_path': test_df_unique['image_path'],\n        'Dry_Green_g': pred_green_final,\n        'Dry_Dead_g': pred_dead_final,\n        'Dry_Clover_g': pred_clover_final,\n        'GDM_g': pred_gdm_final,\n        'Dry_Total_g': pred_total_final\n    })\n\n    # 4. \"Un-pivot\" DataFrame (Chuyển sang dạng \"long\")\n    # Biến nó từ 5 cột về dạng \"long\" (giống sample_submission)\n    preds_long_df = preds_wide_df.melt(\n        id_vars=['image_path'],\n        value_vars=CFG.ALL_TARGET_COLS, # 5 cột mục tiêu\n        var_name='target_name',        # Cột tên mục tiêu\n        value_name='target'            # Cột giá trị dự đoán\n    )\n\n    # 5. Merge với file test.csv gốc (test_df_long)\n    # Đây là bước quan trọng để lấy đúng 'sample_id'\n    # (ví dụ: 'ID1001187975__Dry_Clover_g')\n    submission_df = pd.merge(\n        test_df_long[['sample_id', 'image_path', 'target_name']],\n        preds_long_df,\n        on=['image_path', 'target_name'],\n        how='left'\n    )\n\n    # 6. Dọn dẹp và Lưu\n    # Chỉ giữ lại 2 cột được yêu cầu\n    submission_df = submission_df[['sample_id', 'target']]\n    \n    # Lưu file\n    submission_df.to_csv(CFG.SUBMISSION_FILE, index=False)\n\n    print(f\"\\n🎉 HOÀN TẤT! Đã lưu file submission tại: {CFG.SUBMISSION_FILE}\")\n    print(\"--- 5 hàng đầu của file submission ---\")\n    print(submission_df.head())\n    print(\"\\n--- 5 hàng cuối của file submission ---\")\n    print(submission_df.tail())\n    \n# ===============================================================\n# 8. 🏁 CHẠY CHƯƠNG TRÌNH (Đã sửa)\n# ===============================================================\nif __name__ == \"__main__\":\n    # 1. Chạy dự đoán (đã bao gồm TTA)\n    all_preds_np, df_long, df_unique = run_inference_with_tta()\n    \n    # 2. Tạo file submission (Hàm create_submission giữ nguyên)\n    create_submission(all_preds_np, df_long, df_unique)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-03T11:55:20.531828Z","iopub.execute_input":"2025-11-03T11:55:20.532071Z","iopub.status.idle":"2025-11-03T11:55:28.842807Z","shell.execute_reply.started":"2025-11-03T11:55:20.532054Z","shell.execute_reply":"2025-11-03T11:55:28.842009Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"","metadata":{}},{"cell_type":"markdown","source":"## Model 4  :DINO with LgbmRegressor","metadata":{}},{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport torch\nimport torchvision\nimport os\nimport torchvision.transforms as transforms\nfrom torchvision.datasets import ImageFolder\nfrom torch.utils.data import DataLoader, Subset, Dataset\nfrom PIL import Image\n\n!cp -r \"/kaggle/input/rsna-models/facebookresearch_dinov2_main (1)/root/.cache/torch/hub/facebookresearch_dinov2_main\" /kaggle/working/dinov2\nmean = [0.485, 0.456, 0.406]\nstd = [0.229, 0.224, 0.225]\nfrom transformers import AutoImageProcessor, AutoModel\n# processor = AutoImageProcessor.from_pretrained('/kaggle/input/dinov2/pytorch/base/1/')\n# model = AutoModel.from_pretrained('/kaggle/input/dinov2/pytorch/base/1/')\nprocessor = AutoImageProcessor.from_pretrained('/kaggle/input/dinov2/pytorch/giant/1')\nmodel = AutoModel.from_pretrained('/kaggle/input/dinov2/pytorch/giant/1')\nmodel = model.cuda()\nembeds = []\ntargets = [[] for i in range(5)]\ncounter = 0\nimport torchvision.transforms as transforms\nimport pandas as pd\nfrom PIL import Image\n#transform = transforms.Compose([transforms.ToTensor(), transforms.Resize((224, 224)), transforms.Normalize(mean, std)])\ntrain_df = pd.read_csv(\"/kaggle/input/csiro-biomass/train.csv\")\nroot = \"/kaggle/input/csiro-biomass/\"\nfor i in range(len(train_df)):\n    entry = train_df.iloc[i]\n    file_path = root + entry['image_path']\n    y = torch.tensor([[entry['target']]])\n    targets[i % 5].append(y)\n    if i % 5 == 0:\n        img = Image.open(file_path)\n        x = torch.tensor(processor(img).pixel_values)\n        with torch.no_grad():\n            x = x.cuda()\n            embeds.append(model(x).pooler_output.cpu())\n            counter += 1\n            if counter % 100 == 0:\n                print(f\"{counter} batches processed.\")\n\n# --- Start of Modified Section ---\nimport random\nimport numpy as np\nfrom sklearn.metrics import r2_score\n# Import LGBMRegressor instead of Lasso\nfrom lightgbm import LGBMRegressor \n# Create indices and shuffle once\nlst = list(range(len(embeds)))\nrandom.seed(42)\nrandom.shuffle(lst)\n\n# Create multiple random 80/20 splits\nn_splits = 5\nsplits = []\nfor i in range(n_splits):\n    # Reshuffle for each split while maintaining same splits across targets\n    temp_lst = lst.copy()\n    random.seed(42 + i)  # Different seed for each split\n    random.shuffle(temp_lst)\n    \n    split_point = int(len(temp_lst) * 0.8)\n    train_idxs = temp_lst[:split_point]\n    val_idxs = temp_lst[split_point:]\n    splits.append((train_idxs, val_idxs))\n\n# Convert embeds to numpy array once for efficiency\nembeds_np = np.array(torch.cat(embeds))\nregressors = [[None for i in range(5)] for j in range(5)]\n\n# Now iterate through each target\nfor i in range(5):\n    print(f\"\\n=== Target {i+1} ===\")\n    targets_np = np.array(torch.cat(targets[i]))\n    \n    split_scores = []\n    \n    for split_idx, (train_idxs, val_idxs) in enumerate(splits):\n        print(f\"Fold {split_idx+1}:\")\n        X_train, y_train = embeds_np[train_idxs], targets_np[train_idxs]\n        X_val, y_val = embeds_np[val_idxs], targets_np[val_idxs]\n        \n        # Replace Lasso() with LGBMRegressor() and set random_state for determinism\n        reg = LGBMRegressor(random_state=42) \n        reg.fit(X_train, y_train.ravel()) # .ravel() is used to ensure y_train is 1D for LightGBM\n        \n        train_preds = reg.predict(X_train)\n        train_preds[train_preds < 0.0] = 0.0\n        train_r2 = r2_score(y_train, train_preds)\n        \n        val_preds = reg.predict(X_val)\n        val_preds[val_preds < 0.0] = 0.0\n        val_r2 = r2_score(y_val, val_preds)\n        \n        print(f\"  Train R²: {train_r2:.4f}\")\n        print(f\"  Val R²: {val_r2:.4f}\")\n        split_scores.append((train_r2, val_r2))\n        regressors[i][split_idx] = reg\n    \n    # Print summary for this target\n    avg_train_r2 = np.mean([score[0] for score in split_scores])\n    avg_val_r2 = np.mean([score[1] for score in split_scores])\n    print(f\"\\nTarget {i+1} Average:\")\n    print(f\"  Avg Train R²: {avg_train_r2:.4f}\")\n    print(f\"  Avg Val R²: {avg_val_r2:.4f}\")\n# --- End of Modified Section ---\n\nmapping = {\"Dry_Clover_g\": 0, \"Dry_Dead_g\": 1, \"Dry_Green_g\": 2, \"Dry_Total_g\": 3, \"GDM_g\": 4}\ntest_embeds = {}\ncounter = 0\nimport torchvision.transforms as transforms\nimport pandas as pd\nfrom PIL import Image\ntransform = transforms.Compose([transforms.ToTensor(), transforms.Resize((224, 224)), transforms.Normalize(mean, std)])\ntest_df = pd.read_csv(\"/kaggle/input/csiro-biomass/test.csv\")\nroot = \"/kaggle/input/csiro-biomass/\"\nsample_ids = []\nfor i in range(len(test_df)):\n    entry = test_df.iloc[i]\n    file_path = root + entry['image_path']\n    sample_id = entry['sample_id']\n    #y = torch.tensor([[entry['target']]])\n    if sample_id not in sample_ids:\n        img = Image.open(file_path)\n        x = torch.tensor(processor(img).pixel_values)\n        with torch.no_grad():\n            x = x.cuda()\n            test_embeds[sample_id.split(\"_\")[0]] = model(x).pooler_output.cpu()\n            counter += 1\n        sample_ids.append(sample_id)\n    if counter % 100 == 0:\n        print(f\"{counter} batches processed.\")\n\npredictions = []\nsample_ids = []\ntest_df = pd.read_csv(\"/kaggle/input/csiro-biomass/test.csv\")\nfor i in range(len(test_df)):\n    try:\n        entry = test_df.iloc[i]\n        X = np.array(test_embeds[entry['sample_id'].split(\"__\")[0]])\n        sample_ids.append(entry['sample_id'])\n        models = regressors[mapping[entry['sample_id'].split(\"__\")[1]]]\n        prediction = 0.0\n        for item in models:\n            single_pred = item.predict(X)\n            if single_pred < 0.0:\n                single_pred = 0.0\n            prediction += single_pred\n        prediction = prediction / 5\n        predictions.append(float(prediction))\n    except Exception as e:\n        predictions.append(0.0)\n        \n%cd /kaggle/working\nsubmission = pd.DataFrame({\n    'sample_id': sample_ids,\n    'target': predictions})\nsubmission.to_csv('submission4.csv', index=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-03T11:55:59.246227Z","iopub.execute_input":"2025-11-03T11:55:59.246747Z","iopub.status.idle":"2025-11-03T11:59:38.110805Z","shell.execute_reply.started":"2025-11-03T11:55:59.246724Z","shell.execute_reply":"2025-11-03T11:59:38.109935Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Dino Base with Lgbm regressor","metadata":{}},{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport torch\nimport torchvision\nimport os\nimport torchvision.transforms as transforms\nfrom torchvision.datasets import ImageFolder\nfrom torch.utils.data import DataLoader, Subset, Dataset\nfrom PIL import Image\n!cp -r \"/kaggle/input/rsna-models/facebookresearch_dinov2_main (1)/root/.cache/torch/hub/facebookresearch_dinov2_main\" /kaggle/working/dinov2\nmean = [0.485, 0.456, 0.406]\nstd = [0.229, 0.224, 0.225]\n\n\nfrom transformers import AutoImageProcessor, AutoModel\nprocessor = AutoImageProcessor.from_pretrained('/kaggle/input/dinov2/pytorch/base/1/')\nmodel = AutoModel.from_pretrained('/kaggle/input/dinov2/pytorch/base/1/')\nmodel = model.cuda()\n\n\nembeds = []\ntargets = [[] for i in range(5)]\ncounter = 0\nimport torchvision.transforms as transforms\nimport pandas as pd\nfrom PIL import Image\n#transform = transforms.Compose([transforms.ToTensor(), transforms.Resize((224, 224)), transforms.Normalize(mean, std)])\ntrain_df = pd.read_csv(\"/kaggle/input/csiro-biomass/train.csv\")\nroot = \"/kaggle/input/csiro-biomass/\"\nfor i in range(len(train_df)):\n    entry = train_df.iloc[i]\n    file_path = root + entry['image_path']\n    y = torch.tensor([[entry['target']]])\n    targets[i % 5].append(y)\n    if i % 5 == 0:\n        img = Image.open(file_path)\n        x = torch.tensor(processor(img).pixel_values)\n        with torch.no_grad():\n            x = x.cuda()\n            embeds.append(model(x).pooler_output.cpu())\n            counter += 1\n            if counter % 100 == 0:\n                print(f\"{counter} batches processed.\")\n\n\nimport random\nimport numpy as np\nfrom sklearn.metrics import r2_score\n# Import LGBMRegressor instead of Lasso\nfrom lightgbm import LGBMRegressor \n\n# Create indices and shuffle once\nlst = list(range(len(embeds)))\nrandom.seed(42)\nrandom.shuffle(lst)\n\n# Create multiple random 80/20 splits\nn_splits = 5\nsplits = []\n\nfor i in range(n_splits):\n    # Reshuffle for each split while maintaining same splits across targets\n    temp_lst = lst.copy()\n    random.seed(42 + i)  # Different seed for each split\n    random.shuffle(temp_lst)\n    \n    split_point = int(len(temp_lst) * 0.8)\n    train_idxs = temp_lst[:split_point]\n    val_idxs = temp_lst[split_point:]\n    splits.append((train_idxs, val_idxs))\n\n# Convert embeds to numpy array once for efficiency\nembeds_np = np.array(torch.cat(embeds))\nregressors = [[None for i in range(5)] for j in range(5)]\n# Now iterate through each target\nfor i in range(5):\n    print(f\"\\n=== Target {i+1} ===\")\n    targets_np = np.array(torch.cat(targets[i]))\n    \n    split_scores = []\n    \n    for split_idx, (train_idxs, val_idxs) in enumerate(splits):\n        print(f\"Fold {split_idx+1}:\")\n        X_train, y_train = embeds_np[train_idxs], targets_np[train_idxs]\n        X_val, y_val = embeds_np[val_idxs], targets_np[val_idxs]\n        \n        # Replace Lasso() with LGBMRegressor() and set random_state for determinism\n        reg = LGBMRegressor(random_state=42)\n        # Note: .ravel() is used because LGBMRegressor expects y to be 1-dimensional\n        reg.fit(X_train, y_train.ravel()) \n        \n        train_preds = reg.predict(X_train)\n        train_preds[train_preds < 0.0] = 0.0\n        train_r2 = r2_score(y_train, train_preds)\n        \n        val_preds = reg.predict(X_val)\n        val_preds[val_preds < 0.0] = 0.0\n        val_r2 = r2_score(y_val, val_preds)\n        \n        print(f\"  Train R²: {train_r2:.4f}\")\n        print(f\"  Val R²: {val_r2:.4f}\")\n        split_scores.append((train_r2, val_r2))\n        regressors[i][split_idx] = reg\n    \n    # Print summary for this target\n    avg_train_r2 = np.mean([score[0] for score in split_scores])\n    avg_val_r2 = np.mean([score[1] for score in split_scores])\n    print(f\"\\nTarget {i+1} Average:\")\n    print(f\"  Avg Train R²: {avg_train_r2:.4f}\")\n    print(f\"  Avg Val R²: {avg_val_r2:.4f}\")\n\nmapping = {\"Dry_Clover_g\": 0, \"Dry_Dead_g\": 1, \"Dry_Green_g\": 2, \"Dry_Total_g\": 3, \"GDM_g\": 4}\n\ntest_embeds = {}\ncounter = 0\nimport torchvision.transforms as transforms\nimport pandas as pd\nfrom PIL import Image\ntransform = transforms.Compose([transforms.ToTensor(), transforms.Resize((224, 224)), transforms.Normalize(mean, std)])\ntest_df = pd.read_csv(\"/kaggle/input/csiro-biomass/test.csv\")\nroot = \"/kaggle/input/csiro-biomass/\"\nsample_ids = []\nfor i in range(len(test_df)):\n    entry = test_df.iloc[i]\n    file_path = root + entry['image_path']\n    sample_id = entry['sample_id']\n    #y = torch.tensor([[entry['target']]])\n    if sample_id not in sample_ids:\n        img = Image.open(file_path)\n        x = torch.tensor(processor(img).pixel_values)\n        with torch.no_grad():\n            x = x.cuda()\n            test_embeds[sample_id.split(\"_\")[0]] = model(x).pooler_output.cpu()\n            counter += 1\n        sample_ids.append(sample_id)\n    if counter % 100 == 0:\n        print(f\"{counter} batches processed.\")\n\n\npredictions = []\nsample_ids = []\ntest_df = pd.read_csv(\"/kaggle/input/csiro-biomass/test.csv\")\nfor i in range(len(test_df)):\n    try:\n        entry = test_df.iloc[i]\n        X = np.array(test_embeds[entry['sample_id'].split(\"__\")[0]])\n        sample_ids.append(entry['sample_id'])\n        models = regressors[mapping[entry['sample_id'].split(\"__\")[1]]]\n        prediction = 0.0\n        for item in models:\n            single_pred = item.predict(X)\n            # Clip predictions to non-negative values\n            if single_pred < 0.0:\n                single_pred = 0.0\n            prediction += single_pred\n        prediction = prediction / 5\n        predictions.append(float(prediction))\n    except Exception as e:\n        predictions.append(0.0)\n\n\n%cd /kaggle/working\nsubmission = pd.DataFrame({\n    'sample_id': sample_ids,\n    'target': predictions\n})\n\nsubmission.to_csv('submission5.csv', index=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-03T11:59:38.112632Z","iopub.execute_input":"2025-11-03T11:59:38.112856Z","iopub.status.idle":"2025-11-03T12:00:41.830348Z","shell.execute_reply.started":"2025-11-03T11:59:38.112838Z","shell.execute_reply":"2025-11-03T12:00:41.829399Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Final Ensemble","metadata":{}},{"cell_type":"code","source":"sub1 = pd.read_csv('submission.csv') ## W= 0.52189\nsub2 = pd.read_csv('submission2.csv') ## W = 0.47811\nsub3 = pd.read_csv('submission3.csv') ## W = 0.47811\nsub4 = pd.read_csv('submission4.csv')\nsub5 = pd.read_csv('submission5.csv')\nsub6 = pd.read_csv('submission_convnext_512_Imsize.csv')\n\nsub1['target'] = 0.55*(0.00107*sub1['target'] + 0.34153*sub2['target']+ 0.08650*sub3['target'] + 0.57091*sub6['target']) + 0.45*(0.7*sub4['target'] + 0.3*sub5['target'])\nsub1.to_csv('submission.csv', index = False)\nsub1.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-03T12:00:59.865261Z","iopub.execute_input":"2025-11-03T12:00:59.866036Z","iopub.status.idle":"2025-11-03T12:00:59.892025Z","shell.execute_reply.started":"2025-11-03T12:00:59.866008Z","shell.execute_reply":"2025-11-03T12:00:59.891451Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Final Ensemble","metadata":{}},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}