{"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":"none","dataSources":[{"sourceId":112509,"databundleVersionId":14254895,"sourceType":"competition"}],"dockerImageVersionId":31192,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# ==============================================================================\n# CELL 1: GLOBAL SETUP & LIBRARY\n# ==============================================================================\n\n# 1. Import Library Penting\nimport os\nimport cv2\nimport numpy as np\nimport pandas as pd\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.preprocessing import LabelEncoder\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2\nimport timm\nfrom tqdm.auto import tqdm # Gunakan tqdm.auto agar bagus di VS Code\n\n# 2. Konfigurasi Path & Hardware (SESUAIKAN BASE_DIR ANDA DI SINI)\n# Contoh Windows: r\"C:\\Users\\NamaUser\\Documents\\Project_Rumput\\data\"\n# Contoh Mac/Linux: \"/Users/namauser/project_rumput/data\"\nBASE_DIR = r\"/kaggle/input/csiro-biomass\" \n\nTRAIN_CSV = os.path.join(BASE_DIR, 'train.csv')\nIMG_SIZE = 384       # Ukuran gambar input ke model\nBATCH_SIZE = 16      # Kurangi jadi 8 jika VRAM Laptop penuh\nNUM_WORKERS = 0      # Di Windows lokal, set 0 biasanya lebih stabil daripada 2\nDEVICE = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\nprint(f\"Device yang digunakan: {DEVICE}\")\nprint(f\"Path Dataset: {BASE_DIR}\")\n\n# 3. Seeding (Agar hasil tetap sama setiap kali di-run)\ndef seed_everything(seed=42):\n    np.random.seed(seed)\n    torch.manual_seed(seed)\n    torch.cuda.manual_seed(seed)\n\nseed_everything(42)\n\n# 4. Load & Split Dataframe (Hanya sekali di awal)\nprint(\">>> Membaca CSV dan Membersihkan Data...\")\ndf = pd.read_csv(TRAIN_CSV)\n\n# Buang duplikat (Karena 1 gambar punya 5 baris di data asli, kita cuma butuh 1 untuk klasifikasi)\ndf = df.drop_duplicates(subset=['image_path']).reset_index(drop=True)\n\n# Sederhanakan Label (Ryegrass_Clover -> Ryegrass)\ndf['primary_species'] = df['Species'].apply(lambda x: x.split('_')[0])\n\n# Ubah teks jadi angka\nle = LabelEncoder()\ndf['label'] = le.fit_transform(df['primary_species'])\nNUM_CLASSES = len(le.classes_)\n\n# Split Train (80%) dan Validasi (20%)\ntrain_df, valid_df = train_test_split(df, test_size=0.2, random_state=42, stratify=df['label'])\nprint(f\"Data Siap! Train: {len(train_df)}, Valid: {len(valid_df)}\")\nprint(f\"Kelas: {le.classes_}\")\n\n# 5. Definisi Kelas Dataset (Template Umum)\n# Kita buat satu cetakan Class agar tidak perlu copy-paste 4 kali\nclass GrassDataset(Dataset):\n    def __init__(self, df, img_dir, transforms=None):\n        self.df = df\n        self.img_dir = img_dir\n        self.transforms = transforms\n        \n    def __len__(self):\n        return len(self.df)\n    \n    def __getitem__(self, idx):\n        row = self.df.iloc[idx]\n        # Gabungkan path folder dengan nama file\n        full_path = os.path.join(self.img_dir, row['image_path'])\n        \n        # Baca Gambar\n        image = cv2.imread(full_path)\n        if image is None:\n            # Error handling jika gambar rusak/hilang\n            image = np.zeros((IMG_SIZE, IMG_SIZE, 3), dtype=np.uint8)\n        else:\n            image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n            \n        # Terapkan Preprocessing/Augmentasi sesuai input\n        if self.transforms:\n            augmented = self.transforms(image=image)\n            image = augmented['image']\n            \n        label = torch.tensor(row['label'], dtype=torch.long)\n        return image, label","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"EPOCHS = 5","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-18T09:12:31.849625Z","iopub.execute_input":"2025-12-18T09:12:31.849967Z","iopub.status.idle":"2025-12-18T09:12:31.855725Z","shell.execute_reply.started":"2025-12-18T09:12:31.849941Z","shell.execute_reply":"2025-12-18T09:12:31.854325Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==============================================================================\n# CELL 2: PERSIAPAN DATASET 1 (RAW / NO AUGMENTATION)\n# ==============================================================================\n\ndef get_transforms_raw():\n    return A.Compose([\n        A.Resize(IMG_SIZE, IMG_SIZE),\n        # Hanya normalisasi matematika, tidak ada perubahan gambar\n        A.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),\n        ToTensorV2(),\n    ])\n\n# Buat Dataset khusus Raw\nds_train_raw = GrassDataset(train_df, BASE_DIR, get_transforms_raw())\nds_valid_raw = GrassDataset(valid_df, BASE_DIR, get_transforms_raw()) # Validasi selalu Raw\n\n# Masukkan ke DataLoader (Antrian masuk ke Model)\nloader_train_raw = DataLoader(ds_train_raw, batch_size=BATCH_SIZE, shuffle=True, num_workers=NUM_WORKERS)\nloader_valid_raw = DataLoader(ds_valid_raw, batch_size=BATCH_SIZE, shuffle=False, num_workers=NUM_WORKERS)\n\nprint(\"✅ Dataset 1 (Raw) Siap!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-18T09:15:04.524692Z","iopub.execute_input":"2025-12-18T09:15:04.526187Z","iopub.status.idle":"2025-12-18T09:15:04.538980Z","shell.execute_reply.started":"2025-12-18T09:15:04.526151Z","shell.execute_reply":"2025-12-18T09:15:04.537877Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==============================================================================\n# CELL 3: PERSIAPAN DATASET 2 (GEOMETRIC ONLY)\n# ==============================================================================\n\ndef get_transforms_geo(data='train'):\n    if data == 'train':\n        return A.Compose([\n            A.Resize(IMG_SIZE, IMG_SIZE),\n            # --- Manipulasi Posisi ---\n            A.HorizontalFlip(p=0.5),      # Kiri-Kanan\n            A.VerticalFlip(p=0.5),        # Atas-Bawah\n            A.RandomRotate90(p=0.5),      # Putar Siku\n            A.Transpose(p=0.5),           # Miring\n            # -------------------------\n            A.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),\n            ToTensorV2(),\n        ])\n    else:\n        # Validasi tetap polos\n        return get_transforms_raw()\n\nds_train_geo = GrassDataset(train_df, BASE_DIR, get_transforms_geo('train'))\n# Validasi gunakan loader_valid_raw saja karena isinya sama (tidak boleh diaugmentasi)\n\nloader_train_geo = DataLoader(ds_train_geo, batch_size=BATCH_SIZE, shuffle=True, num_workers=NUM_WORKERS)\n\nprint(\"✅ Dataset 2 (Geometric) Siap!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-18T09:15:10.189434Z","iopub.execute_input":"2025-12-18T09:15:10.189789Z","iopub.status.idle":"2025-12-18T09:15:10.202965Z","shell.execute_reply.started":"2025-12-18T09:15:10.189764Z","shell.execute_reply":"2025-12-18T09:15:10.201728Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==============================================================================\n# CELL 4: PERSIAPAN DATASET 3 (PIXEL/VISUAL ONLY)\n# ==============================================================================\n\ndef get_transforms_pixel(data='train'):\n    if data == 'train':\n        return A.Compose([\n            A.Resize(IMG_SIZE, IMG_SIZE),\n            # --- Manipulasi Visual ---\n            A.RandomBrightnessContrast(p=0.5), # Terang/Gelap\n            A.CoarseDropout(max_holes=8, max_height=30, max_width=30, p=0.5), # Lubang Hitam\n            A.Blur(blur_limit=3, p=0.2),       # Sedikit Buram\n            # -------------------------\n            A.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),\n            ToTensorV2(),\n        ])\n    else:\n        return get_transforms_raw()\n\nds_train_pixel = GrassDataset(train_df, BASE_DIR, get_transforms_pixel('train'))\n\nloader_train_pixel = DataLoader(ds_train_pixel, batch_size=BATCH_SIZE, shuffle=True, num_workers=NUM_WORKERS)\n\nprint(\"✅ Dataset 3 (Pixel/Visual) Siap!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-18T09:15:21.172994Z","iopub.execute_input":"2025-12-18T09:15:21.173523Z","iopub.status.idle":"2025-12-18T09:15:21.189318Z","shell.execute_reply.started":"2025-12-18T09:15:21.173493Z","shell.execute_reply":"2025-12-18T09:15:21.188100Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==============================================================================\n# CELL 5: PERSIAPAN DATASET 4 (COMBINED / BEST PRACTICE)\n# ==============================================================================\n\ndef get_transforms_combined(data='train'):\n    if data == 'train':\n        return A.Compose([\n            A.Resize(IMG_SIZE, IMG_SIZE),\n            # Gabungan Geometri\n            A.HorizontalFlip(p=0.5),\n            A.VerticalFlip(p=0.5),\n            A.RandomRotate90(p=0.5),\n            # Gabungan Visual\n            A.RandomBrightnessContrast(p=0.5),\n            A.CoarseDropout(max_holes=8, max_height=32, max_width=32, p=0.5),\n            \n            A.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),\n            ToTensorV2(),\n        ])\n    else:\n        return get_transforms_raw()\n\nds_train_combined = GrassDataset(train_df, BASE_DIR, get_transforms_combined('train'))\n\nloader_train_combined = DataLoader(ds_train_combined, batch_size=BATCH_SIZE, shuffle=True, num_workers=NUM_WORKERS)\n\nprint(\"✅ Dataset 4 (Combined) Siap!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-18T09:15:37.982101Z","iopub.execute_input":"2025-12-18T09:15:37.982475Z","iopub.status.idle":"2025-12-18T09:15:37.995877Z","shell.execute_reply.started":"2025-12-18T09:15:37.982448Z","shell.execute_reply":"2025-12-18T09:15:37.994561Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==============================================================================\n# CELL 6: MULTI-MODEL TRAINING LOOP\n# ==============================================================================\n\n# Daftar Eksperimen yang akan dijalankan\n# Format: (Nama_Eksperimen, Loader_Train, Loader_Valid)\nexperiments = [\n    (\"Model_1_Raw\", loader_train_raw, loader_valid_raw),\n    (\"Model_2_Geo\", loader_train_geo, loader_valid_raw),\n    (\"Model_3_Pixel\", loader_train_pixel, loader_valid_raw),\n    (\"Model_4_Combined\", loader_train_combined, loader_valid_raw)\n]\n\nEPOCHS_PER_MODEL = 3 # Set ke 3 atau 5 agar tidak terlalu lama menunggu\nLR = 1e-4\n\n# Loop untuk menjalankan semua eksperimen satu per satu\nfor exp_name, train_loader, valid_loader in experiments:\n    \n    print(f\"\\n{'='*60}\")\n    print(f\"🚀 MEMULAI TRAINING: {exp_name}\")\n    print(f\"{'='*60}\")\n    \n    # 1. Inisialisasi Model Baru (Reset Otak Model)\n    # Kita pakai EfficientNet_B0 yang ringan tapi kuat\n    model = timm.create_model('efficientnet_b0', pretrained=True, num_classes=NUM_CLASSES)\n    model.to(DEVICE)\n    \n    # 2. Setup Optimizer & Loss\n    criterion = nn.CrossEntropyLoss()\n    optimizer = optim.Adam(model.parameters(), lr=LR)\n    \n    # Variabel pelacak terbaik\n    best_acc = 0.0\n    best_epoch_log = 0\n    \n    # 3. Training Loop\n    for epoch in range(EPOCHS_PER_MODEL):\n        # --- TRAINING ---\n        model.train()\n        train_loss = 0\n        correct = 0\n        total = 0\n        \n        # Pbar (Progress Bar) untuk visualisasi di VS Code\n        pbar = tqdm(train_loader, desc=f\"[{exp_name}] Epoch {epoch+1}/{EPOCHS_PER_MODEL}\", leave=False)\n        \n        for images, labels in pbar:\n            images, labels = images.to(DEVICE), labels.to(DEVICE)\n            \n            optimizer.zero_grad()\n            outputs = model(images)\n            loss = criterion(outputs, labels)\n            loss.backward()\n            optimizer.step()\n            \n            train_loss += loss.item()\n            _, predicted = torch.max(outputs.data, 1)\n            total += labels.size(0)\n            correct += (predicted == labels).sum().item()\n            \n            # Update info di progress bar\n            pbar.set_postfix({'loss': f\"{loss.item():.4f}\"})\n        \n        avg_train_loss = train_loss / len(train_loader)\n        train_acc = 100 * correct / total\n        \n        # --- VALIDATION ---\n        model.eval()\n        valid_loss = 0\n        correct_val = 0\n        total_val = 0\n        \n        with torch.no_grad():\n            for images, labels in valid_loader:\n                images, labels = images.to(DEVICE), labels.to(DEVICE)\n                outputs = model(images)\n                loss = criterion(outputs, labels)\n                \n                valid_loss += loss.item()\n                _, predicted = torch.max(outputs.data, 1)\n                total_val += labels.size(0)\n                correct_val += (predicted == labels).sum().item()\n        \n        avg_valid_loss = valid_loss / len(valid_loader)\n        valid_acc = 100 * correct_val / total_val\n        \n        print(f\"   Epoch {epoch+1}: Train Loss={avg_train_loss:.4f}, Valid Acc={valid_acc:.2f}%\")\n        \n        # --- SAVE BEST MODEL ---\n        if valid_acc > best_acc:\n            best_acc = valid_acc\n            best_epoch_log = epoch + 1\n            save_path = f\"{exp_name}_best.pth\"\n            torch.save(model.state_dict(), save_path)\n            print(f\"      🏆 Rekor Baru! Model disimpan ke {save_path}\")\n\n    print(f\"\\n✅ Selesai {exp_name}. Akurasi Terbaik: {best_acc:.2f}% pada Epoch {best_epoch_log}\")\n    \n    # Bersihkan memori GPU sebelum lanjut ke model berikutnya\n    del model\n    torch.cuda.empty_cache()\n\nprint(\"\\n🎉 SEMUA MODEL SELESAI DILATIH!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-18T09:16:03.257703Z","iopub.execute_input":"2025-12-18T09:16:03.258044Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}