{"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":13836,"databundleVersionId":1718836,"sourceType":"competition"}],"dockerImageVersionId":31192,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# Cai dat cac thu vien can thiet\n# numpy<2.0 de tranh loi tuong thich giua cac thu vien cu va moi\n!pip install \"numpy<2.0\" \"pillow==10.4.0\" torchvision grad-cam timm scikit-image scikit-learn seaborn matplotlib pandas --no-cache-dir\n!pip install huggingface_hub joblib --quiet\n\nimport os\nimport cv2\nimport time\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nimport pandas as pd\nimport numpy as np\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nfrom PIL import Image\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms, models\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import accuracy_score, classification_report, confusion_matrix, ConfusionMatrixDisplay\nfrom sklearn.svm import SVC\nfrom skimage.feature import hog\nimport timm\n\n# Thiet lap thiet bi (GPU hoac CPU)\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nprint(f\"Device dang su dung: {device}\")\n\n# Cau hinh Seed de dong bo ket qua (reproducibility)\ndef seed_everything(seed=42):\n    os.environ['PYTHONHASHSEED'] = str(seed)\n    np.random.seed(seed)\n    torch.manual_seed(seed)\n    torch.cuda.manual_seed(seed)\n    torch.backends.cudnn.deterministic = True\n\nseed_everything()","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-12-17T04:59:10.452563Z","iopub.execute_input":"2025-12-17T04:59:10.452818Z","iopub.status.idle":"2025-12-17T05:00:51.014463Z","shell.execute_reply.started":"2025-12-17T04:59:10.452797Z","shell.execute_reply":"2025-12-17T05:00:51.013751Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Cau hinh duong dan den Dataset tren Kaggle\nDATA_DIR = '../input/cassava-leaf-disease-classification'\nTRAIN_IMG_DIR = os.path.join(DATA_DIR, 'train_images')\nTRAIN_CSV = os.path.join(DATA_DIR, 'train.csv')\n\n# Doc file CSV va tao duong dan day du cho anh\ndf = pd.read_csv(TRAIN_CSV)\ndf['image_path'] = df['image_id'].apply(lambda x: os.path.join(TRAIN_IMG_DIR, x))\n\n# Dinh nghia ten cac lop benh theo tai lieu cuoc thi\nclass_names = {\n    0: \"Cassava Bacterial Blight (CBB)\",\n    1: \"Cassava Brown Streak Disease (CBSD)\",\n    2: \"Cassava Green Mottle (CGM)\",\n    3: \"Cassava Mosaic Disease (CMD)\",\n    4: \"Healthy\"\n}\nNUM_CLASSES = 5\n\nprint(f\"Tong so anh: {len(df)}\")\nprint(\"Phan bo du lieu ban dau:\")\nprint(df['label'].value_counts())\n\n# Tinh toan Class Weights de xu ly mat can bang du lieu (Rat quan trong voi bo San)\n# Cong thuc: N_total / (N_classes * N_samples_per_class)\nclass_counts = df['label'].value_counts().sort_index().values\nclass_weights = len(df) / (NUM_CLASSES * class_counts)\n# Chuyen weights sang tensor va dua vao GPU\nclass_weights = torch.FloatTensor(class_weights).to(device)\nprint(f\"Class Weights da tinh toan: {class_weights}\")\n\n# Chia Tap du lieu: 70% Train - 15% Val - 15% Test\n# Su dung stratify de giu nguyen ti le cac benh trong tung tap\ntrain_df, temp_df = train_test_split(df, test_size=0.3, stratify=df['label'], random_state=42)\nval_df, test_df = train_test_split(temp_df, test_size=0.5, stratify=temp_df['label'], random_state=42)\n\nprint(f\"Kich thuoc tap: Train={len(train_df)} | Val={len(val_df)} | Test={len(test_df)}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-17T05:00:51.015836Z","iopub.execute_input":"2025-12-17T05:00:51.016083Z","iopub.status.idle":"2025-12-17T05:00:51.742274Z","shell.execute_reply.started":"2025-12-17T05:00:51.016062Z","shell.execute_reply":"2025-12-17T05:00:51.7414Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class CassavaDataset(Dataset):\n    def __init__(self, df, root_dir=\"./train_images\", transform=None):\n        \"\"\"\n        Args:\n            df (pd.DataFrame): DataFrame chứa thông tin ảnh (image_id, label)\n            root_dir (string): Đường dẫn thư mục chứa ảnh.\n            transform (callable, optional): Hàm xử lý ảnh (augmentation).\n        \"\"\"\n        self.df = df\n        self.root_dir = root_dir\n        self.transform = transform\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        # Lấy tên ảnh và nhãn từ dataframe\n        row = self.df.iloc[idx]\n        image_id = row['image_id']\n        label = row['label']\n        \n        # Tạo đường dẫn đầy đủ tới ảnh\n        # Lưu ý: Nếu trên Kaggle thì đường dẫn thường là: \"../input/cassava-leaf-disease-classification/train_images\"\n        # Bạn hãy kiểm tra lại biến TRAIN_DIR hoặc đường dẫn root_dir nhé\n        img_path = os.path.join(self.root_dir, image_id)\n        \n        # Đọc ảnh bằng PIL (để tương thích với transforms của PyTorch)\n        try:\n            image = Image.open(img_path).convert(\"RGB\")\n        except:\n            # Fallback nếu đường dẫn sai, thử đường dẫn mặc định Kaggle\n            img_path = os.path.join(\"../input/cassava-leaf-disease-classification/train_images\", image_id)\n            image = Image.open(img_path).convert(\"RGB\")\n\n        # Áp dụng Augmentation\n        if self.transform:\n            image = self.transform(image)\n            \n        return image, torch.tensor(label, dtype=torch.long)\n# --- SỬA LẠI DATA LOADER (Cell 3) ---\nIMG_SIZE = 224\n\n# CẬP NHẬT: Sử dụng AutoAugment cho chiến lược tăng cường dữ liệu mạnh mẽ\ntrain_transforms = transforms.Compose([\n    transforms.Resize((IMG_SIZE, IMG_SIZE)),\n    transforms.AutoAugment(transforms.AutoAugmentPolicy.IMAGENET), # <--- THAY ĐỔI LỚN\n    transforms.RandomHorizontalFlip(p=0.5),\n    transforms.RandomVerticalFlip(p=0.5),\n    transforms.ToTensor(),\n    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),\n    transforms.RandomErasing(p=0.25) # Giúp mô hình học tốt hơn khi bị che khuất\n])\n\nval_test_transforms = transforms.Compose([\n    transforms.Resize((IMG_SIZE, IMG_SIZE)),\n    transforms.ToTensor(),\n    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n])\n\n# Dataset & DataLoader (Giữ nguyên phần gọi Dataset của bạn)\ntrain_ds = CassavaDataset(train_df, transform=train_transforms)\nval_ds = CassavaDataset(val_df, transform=val_test_transforms)\ntest_ds = CassavaDataset(test_df, transform=val_test_transforms)\n\n# Lưu ý: num_workers=2 để tối ưu tốc độ load trên Kaggle/Colab\ntrain_loader = DataLoader(train_ds, batch_size=32, shuffle=True, num_workers=2, drop_last=True)\nval_loader = DataLoader(val_ds, batch_size=32, shuffle=False, num_workers=2)\ntest_loader = DataLoader(test_ds, batch_size=32, shuffle=False, num_workers=2)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-17T05:00:51.743161Z","iopub.execute_input":"2025-12-17T05:00:51.74338Z","iopub.status.idle":"2025-12-17T05:00:51.755545Z","shell.execute_reply.started":"2025-12-17T05:00:51.743363Z","shell.execute_reply":"2025-12-17T05:00:51.755005Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import time\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom timm.data import Mixup\nfrom timm.loss import SoftTargetCrossEntropy\nfrom torch.cuda.amp import autocast, GradScaler\n\n# Giữ nguyên cấu hình Mixup\nmixup_fn = Mixup(\n    mixup_alpha=0.8, cutmix_alpha=1.0, prob=1.0, switch_prob=0.5, \n    mode='batch', label_smoothing=0.1, num_classes=NUM_CLASSES)\n\ndef train_model(model, optimizer, criterion, scheduler=None, mixup_fn=None, epochs=10, patience=3, model_name=\"model\", log_interval=30):\n    model = model.to(device)\n    best_val_loss = float('inf')\n    patience_counter = 0\n    best_weights = None\n    history = {'train_loss': [], 'train_acc': [], 'val_loss': [], 'val_acc': []}\n    \n    # --- KHỞI TẠO SCALER CHO AMP (TĂNG TỐC) ---\n    scaler = GradScaler() \n\n    print(f\"--- Bat dau train {model_name} (Mixup: {'BAT' if mixup_fn else 'TAT'}) [AMP Enabled] ---\\n\")\n    start_time = time.time()\n\n    for epoch in range(epochs):\n        model.train()\n        running_loss = 0.0\n        correct = 0\n        total = 0\n        \n        print(f\"Epoch {epoch+1}/{epochs} (LR: {optimizer.param_groups[0]['lr']:.6f})\")\n        \n        for batch_idx, (images, labels) in enumerate(train_loader):\n            images, labels = images.to(device), labels.to(device)\n\n            # 1. Áp dụng Mixup\n            if mixup_fn is not None:\n                images, labels = mixup_fn(images, labels)\n            \n            optimizer.zero_grad()\n            \n            # --- CHẠY BẰNG FLOAT16 (AMP) ---\n            with autocast():\n                outputs = model(images)\n                loss = criterion(outputs, labels)\n            \n            # Scale loss để tránh bị underflow khi dùng float16\n            scaler.scale(loss).backward()\n            scaler.step(optimizer)\n            scaler.update()\n            # -------------------------------\n\n            running_loss += loss.item() * images.size(0)\n            \n            # =================================================================\n            # 2. SỬA LỖI TÍNH ACCURACY KHI CÓ MIXUP\n            # =================================================================\n            _, predicted = torch.max(outputs, 1)\n            \n            # Nếu labels là dạng Mixup (Shape: [Batch_size, Num_classes]) -> Soft labels\n            if labels.ndim > 1:\n                # Lấy index của class có trọng số lớn nhất trong soft label để so sánh\n                _, targets = torch.max(labels, 1)\n            else:\n                # Nếu labels là dạng thường (Shape: [Batch_size]) -> Hard labels (Integer)\n                targets = labels\n\n            total += labels.size(0)\n            correct += (predicted == targets).sum().item()\n            current_batch_acc = 100 * (predicted == targets).sum().item() / labels.size(0)\n            # =================================================================\n\n            # Logging\n            if (batch_idx + 1) % log_interval == 0:\n                print(f\"   [Batch {batch_idx+1}/{len(train_loader)}] Loss: {loss.item():.4f} | Acc: {current_batch_acc:.1f}%\")\n\n        epoch_loss = running_loss / len(train_loader.dataset)\n        # Tính lại epoch_acc tổng (không bị 0 nữa)\n        epoch_acc = 100 * correct / total \n\n        # --- VALIDATION (Validation không bao giờ dùng Mixup nên giữ nguyên) ---\n        model.eval()\n        val_loss = 0.0\n        val_correct = 0\n        val_total = 0\n        val_criterion = nn.CrossEntropyLoss()\n\n        with torch.no_grad():\n            for images, labels in val_loader:\n                images, labels = images.to(device), labels.to(device)\n                outputs = model(images)\n                loss = val_criterion(outputs, labels)\n\n                val_loss += loss.item() * images.size(0)\n                _, predicted = torch.max(outputs, 1)\n                val_total += labels.size(0)\n                val_correct += (predicted == labels).sum().item()\n\n        epoch_val_loss = val_loss / len(val_loader.dataset)\n        epoch_val_acc = 100 * val_correct / val_total\n\n        if scheduler:\n            if isinstance(scheduler, torch.optim.lr_scheduler.ReduceLROnPlateau):\n                scheduler.step(epoch_val_loss)\n            else:\n                scheduler.step()\n\n        history['train_loss'].append(epoch_loss)\n        history['train_acc'].append(epoch_acc)\n        history['val_loss'].append(epoch_val_loss)\n        history['val_acc'].append(epoch_val_acc)\n\n        print(f\"==> END EPOCH {epoch+1}: Train Loss={epoch_loss:.4f} Acc={epoch_acc:.2f}% | Val Loss={epoch_val_loss:.4f} Acc={epoch_val_acc:.2f}%\")\n        print(\"-\" * 50)\n\n        if epoch_val_loss < best_val_loss:\n            best_val_loss = epoch_val_loss\n            best_weights = model.state_dict()\n            patience_counter = 0\n            print(\"   (Save Model - Best Val Loss)\")\n        else:\n            patience_counter += 1\n            if patience_counter >= patience:\n                print(f\"STOP! Early stopping.\")\n                break\n    \n    time_elapsed = time.time() - start_time\n    if best_weights:\n        model.load_state_dict(best_weights)\n    return model, history, time_elapsed","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-17T05:00:51.75619Z","iopub.execute_input":"2025-12-17T05:00:51.756372Z","iopub.status.idle":"2025-12-17T05:00:51.792503Z","shell.execute_reply.started":"2025-12-17T05:00:51.756357Z","shell.execute_reply":"2025-12-17T05:00:51.791924Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"=== TRAINING SVM (BASELINE) ===\")\n# Luu y: SVM voi 21k anh va HOG se ton rat nhieu RAM.\n# Chung ta chi lay mot tap con (subset) 3000 anh de train SVM lam muc so sanh\n\ndef extract_hog_features(dataset, limit=5000):\n    features = []\n    labels = []\n    print(f\"Dang trich xuat HOG cho {limit} anh...\")\n\n    # Chon ngau nhien index\n    indices = np.random.choice(len(dataset), min(len(dataset), limit), replace=False)\n\n    for i in indices:\n        img, label = dataset[i]\n        # Chuyen Tensor ve Numpy va Denormalize so bo de lay anh Gray\n        img = img.permute(1, 2, 0).numpy()\n        img = (img - img.min()) / (img.max() - img.min()) # Normalize ve 0-1\n        img = (img * 255).astype(np.uint8)\n        gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)\n        gray = cv2.resize(gray, (128, 128))\n\n        # Tinh HOG descriptors\n        fd = hog(gray, orientations=9, pixels_per_cell=(8, 8),\n                 cells_per_block=(2, 2), visualize=False)\n        features.append(fd)\n        labels.append(label)\n\n    return np.array(features), np.array(labels)\n\n# Trich xuat dac trung\nX_train_hog, y_train_hog = extract_hog_features(train_ds, limit=5000)\nX_test_hog, y_test_hog = extract_hog_features(test_ds, limit=2000)\n\n# Train SVM\nstart_svm = time.time()\nsvm_model = SVC(kernel='linear', C=1.0)\nsvm_model.fit(X_train_hog, y_train_hog)\nsvm_time = time.time() - start_svm\nprint(f\"SVM Training Time: {svm_time:.2f}s\")\n\n# Danh gia SVM\ny_pred_svm = svm_model.predict(X_test_hog)\nprint(\"Classification Report SVM:\")\nprint(classification_report(y_test_hog, y_pred_svm, target_names=[class_names[i] for i in range(NUM_CLASSES)]))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-17T05:00:51.7941Z","iopub.execute_input":"2025-12-17T05:00:51.794312Z","iopub.status.idle":"2025-12-17T05:07:12.213149Z","shell.execute_reply.started":"2025-12-17T05:00:51.794293Z","shell.execute_reply":"2025-12-17T05:07:12.212197Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"=== TRAINING RESNET50 (OPTIMIZED) ===\")\n# 1. Setup Model\nresnet = models.resnet50(weights=models.ResNet50_Weights.DEFAULT)\nnum_ftrs = resnet.fc.in_features\nresnet.fc = nn.Sequential(\n    nn.Dropout(p=0.5), # Thêm Dropout chống Overfitting\n    nn.Linear(num_ftrs, NUM_CLASSES)\n)\n\n# 2. Setup Optimizer & Scheduler\ncriterion_res = nn.CrossEntropyLoss(weight=class_weights)\noptimizer_res = optim.AdamW(resnet.parameters(), lr=1e-4, weight_decay=1e-3)\nscheduler_res = optim.lr_scheduler.ReduceLROnPlateau(optimizer_res, mode='min', factor=0.1, patience=2)\n\n# 3. Train (Epochs đặt cao, để Early Stopping tự ngắt)\nresnet, resnet_hist, resnet_time = train_model(\n    resnet, optimizer_res, criterion_res, scheduler=scheduler_res,\n    mixup_fn=None, \n    epochs=50, patience=6, model_name=\"ResNet50_Opt\",\n    log_interval=100 \n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-17T05:07:12.214718Z","iopub.execute_input":"2025-12-17T05:07:12.214972Z","iopub.status.idle":"2025-12-17T05:11:40.354728Z","shell.execute_reply.started":"2025-12-17T05:07:12.214933Z","shell.execute_reply":"2025-12-17T05:11:40.353785Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"=== TRAINING ViT (OPTIMIZED + MIXUP) ===\")\n# 1. Setup Model (Thêm drop_path_rate)\nmodel_vit = timm.create_model(\n    'vit_base_patch16_224', pretrained=True, num_classes=NUM_CLASSES,\n    drop_rate=0.1, drop_path_rate=0.1 # Kỹ thuật chống overfit riêng cho ViT\n)\n\n# 2. Setup Optimizer & Loss cho Mixup\n# Quan trọng: Dùng SoftTargetCrossEntropy vì nhãn đã bị Mixup trộn\ncriterion_vit = SoftTargetCrossEntropy() \noptimizer_vit = optim.AdamW(model_vit.parameters(), lr=5e-5, weight_decay=0.05)\nscheduler_vit = optim.lr_scheduler.ReduceLROnPlateau(optimizer_vit, mode='min', factor=0.1, patience=3)\n\n# 3. Train (Bật Mixup)\nmodel_vit, vit_hist, vit_time = train_model(\n    model_vit, optimizer_vit, criterion_vit, scheduler=scheduler_vit,\n    mixup_fn=mixup_fn, \n    epochs=50, patience=7, model_name=\"ViT_Base_Mixup\",\n    log_interval=100\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-17T05:11:40.355963Z","iopub.execute_input":"2025-12-17T05:11:40.356287Z","iopub.status.idle":"2025-12-17T05:18:09.313142Z","shell.execute_reply.started":"2025-12-17T05:11:40.356258Z","shell.execute_reply":"2025-12-17T05:18:09.312273Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.metrics import accuracy_score, confusion_matrix, ConfusionMatrixDisplay\nimport seaborn as sns\nimport matplotlib.pyplot as plt\nimport pandas as pd\nimport numpy as np\nimport torch\n\n# 1. ĐỊNH NGHĨA HÀM get_predictions (Bị thiếu trước đó)\ndef get_predictions(model, loader):\n    model.eval()\n    all_targets = []\n    all_preds = []\n    \n    # Đảm bảo model đang ở đúng device\n    model = model.to(device)\n    \n    with torch.no_grad():\n        for images, labels in loader:\n            images = images.to(device)\n            labels = labels.to(device)\n            \n            outputs = model(images)\n            _, predicted = torch.max(outputs, 1)\n            \n            all_targets.extend(labels.cpu().numpy())\n            all_preds.extend(predicted.cpu().numpy())\n            \n    return np.array(all_targets), np.array(all_preds)\n\n# ---------------------------------------------------------\nprint(\"=== SO SANH KET QUA 3 MO HINH ===\")\n\n# 2. Lấy dự đoán trên tập Test đầy đủ\nprint(\"Dang lay du doan tu ResNet...\")\ny_true_resnet, y_pred_resnet = get_predictions(resnet, test_loader)\n\nprint(\"Dang lay du doan tu ViT...\")\ny_true_vit, y_pred_vit = get_predictions(model_vit, test_loader)\n\n# 3. Tính metrics\ndef compute_metrics(y_true, y_pred, time_taken, name):\n    acc = accuracy_score(y_true, y_pred)\n    return {\"Model\": name, \"Accuracy\": acc*100, \"Time (s)\": time_taken}\n\n# LƯU Ý: Đảm bảo biến svm_time, y_test_hog, y_pred_svm đã có từ các cell trước\n# Nếu chưa chạy SVM, hãy comment dòng SVM lại để tránh lỗi tiếp theo\nmetrics = []\n\n# Kiểm tra xem SVM đã chạy chưa\ntry:\n    metrics.append({\"Model\": \"SVM (HOG)\", \"Accuracy\": accuracy_score(y_test_hog, y_pred_svm)*100, \"Time (s)\": svm_time})\nexcept NameError:\n    print(\"Warning: Khong tim thay ket qua SVM, bo qua SVM trong bang so sanh.\")\n\nmetrics.append(compute_metrics(y_true_resnet, y_pred_resnet, resnet_time, \"ResNet50\"))\nmetrics.append(compute_metrics(y_true_vit, y_pred_vit, vit_time, \"ViT\"))\n\ndf_metrics = pd.DataFrame(metrics)\nprint(\"\\nBAO CAO TONG HOP:\")\nprint(df_metrics)\n\n# 4. Vẽ biểu đồ so sánh\nplt.figure(figsize=(10, 5))\nsns.barplot(x=\"Model\", y=\"Accuracy\", data=df_metrics, palette=\"viridis\")\nplt.title(\"So sanh Accuracy giua cac mo hinh\")\nplt.ylim(0, 100)\nplt.ylabel(\"Accuracy (%)\")\nplt.show()\n\n# 5. Vẽ Confusion Matrix\ndef plot_cm(y_true, y_pred, title):\n    cm = confusion_matrix(y_true, y_pred)\n    # Lấy class names từ dataset gốc (nếu có) hoặc dùng số\n    labels = [str(i) for i in range(NUM_CLASSES)]\n    try:\n        # Thử lấy label map nếu có\n        labels = [class_names[i] for i in range(NUM_CLASSES)]\n    except:\n        pass\n        \n    disp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=labels)\n    fig, ax = plt.subplots(figsize=(10, 10))\n    disp.plot(cmap='Blues', ax=ax, xticks_rotation=45)\n    plt.title(title)\n    plt.show()\n\nprint(\"Ve Confusion Matrix...\")\nplot_cm(y_true_resnet, y_pred_resnet, \"Confusion Matrix - ResNet50\")\nplot_cm(y_true_vit, y_pred_vit, \"Confusion Matrix - ViT\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-17T05:18:09.31444Z","iopub.execute_input":"2025-12-17T05:18:09.314836Z","iopub.status.idle":"2025-12-17T05:19:14.835228Z","shell.execute_reply.started":"2025-12-17T05:18:09.314807Z","shell.execute_reply":"2025-12-17T05:19:14.834528Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\nfrom sklearn.metrics import classification_report\n\n# --- 1. VẼ BIỂU ĐỒ LOSS & ACCURACY ---\ndef plot_training_history(history, model_name):\n    if not history:\n        print(f\"Chưa có dữ liệu lịch sử huấn luyện cho {model_name}\")\n        return\n\n    epochs = range(1, len(history['train_loss']) + 1)\n    \n    plt.figure(figsize=(14, 5))\n    \n    # Biểu đồ Loss\n    plt.subplot(1, 2, 1)\n    plt.plot(epochs, history['train_loss'], 'b-o', label='Train Loss')\n    plt.plot(epochs, history['val_loss'], 'r-o', label='Val Loss')\n    plt.title(f'{model_name} - Loss over Epochs')\n    plt.xlabel('Epochs')\n    plt.ylabel('Loss')\n    plt.legend()\n    plt.grid(True)\n    \n    # Biểu đồ Accuracy (Nếu có)\n    # Lưu ý: ViT dùng Mixup nên train_acc có thể không chính xác hoặc bằng 0\n    if any(history['val_acc']):\n        plt.subplot(1, 2, 2)\n        plt.plot(epochs, history['train_acc'], 'b--o', label='Train Acc', alpha=0.6)\n        plt.plot(epochs, history['val_acc'], 'g-o', label='Val Acc')\n        plt.title(f'{model_name} - Accuracy over Epochs')\n        plt.xlabel('Epochs')\n        plt.ylabel('Accuracy (%)')\n        plt.legend()\n        plt.grid(True)\n    \n    plt.tight_layout()\n    plt.show()\n\n# Vẽ biểu đồ cho ResNet50\nif 'resnet_hist' in globals():\n    plot_training_history(resnet_hist, \"ResNet50\")\n\n# Vẽ biểu đồ cho ViT\nif 'vit_hist' in globals():\n    plot_training_history(vit_hist, \"ViT (Vision Transformer)\")\n\n# --- 2. CLASSIFICATION REPORT CHI TIẾT ---\n# Lấy tên các lớp bệnh\ntarget_names = [class_names[i] for i in range(NUM_CLASSES)]\n\nprint(\"\\n\" + \"=\"*40)\nprint(\"📊 CHI TIẾT HIỆU SUẤT TỪNG LỚP (CLASSIFICATION REPORT)\")\nprint(\"=\"*40)\n\n# Báo cáo cho SVM\nif 'y_test_hog' in globals() and 'y_pred_svm' in globals():\n    print(f\"\\nModel: SVM (HOG)\")\n    print(\"-\" * 20)\n    print(classification_report(y_test_hog, y_pred_svm, target_names=target_names))\n\n# Báo cáo cho ResNet50\nif 'y_true_resnet' in globals() and 'y_pred_resnet' in globals():\n    print(f\"\\nModel: ResNet50\")\n    print(\"-\" * 20)\n    print(classification_report(y_true_resnet, y_pred_resnet, target_names=target_names))\n\n# Báo cáo cho ViT\nif 'y_true_vit' in globals() and 'y_pred_vit' in globals():\n    print(f\"\\nModel: Vision Transformer (ViT)\")\n    print(\"-\" * 20)\n    print(classification_report(y_true_vit, y_pred_vit, target_names=target_names))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-17T05:19:14.836144Z","iopub.execute_input":"2025-12-17T05:19:14.836404Z","iopub.status.idle":"2025-12-17T05:19:15.629622Z","shell.execute_reply.started":"2025-12-17T05:19:14.836373Z","shell.execute_reply":"2025-12-17T05:19:15.628997Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==============================================================================\n# CELL CODE: PHÂN TÍCH LỖI CHI TIẾT & ĐO TỐC ĐỘ (INFERENCE TIME)\n# (Thêm cell này vào cuối Notebook)\n# ==============================================================================\n\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport time\nimport torch\n\n# 1. HÀM HIỂN THỊ CÁC ẢNH ĐOÁN SAI (VISUAL ERROR ANALYSIS)\ndef visualize_errors(model, loader, device, class_names, model_name=\"Model\", num_images=10):\n    \"\"\"\n    Tìm và hiển thị các ảnh mà model dự đoán sai so với nhãn thực tế.\n    \"\"\"\n    model.to(device)\n    model.eval()\n    images_so_far = 0\n    \n    # Thiết lập kích thước khung hình (tự động tính số hàng/cột)\n    cols = 5\n    rows = (num_images // cols) + (1 if num_images % cols != 0 else 0)\n    plt.figure(figsize=(15, 3.5 * rows))\n    \n    print(f\"\\n🔍 [{model_name}] Đang quét test set để tìm các mẫu đoán sai...\")\n    \n    # Chuẩn ImageNet để Un-normalize (trả lại màu gốc cho ảnh)\n    mean = np.array([0.485, 0.456, 0.406])\n    std = np.array([0.229, 0.224, 0.225])\n    \n    with torch.no_grad():\n        for inputs, labels in loader:\n            inputs = inputs.to(device)\n            labels = labels.to(device)\n\n            outputs = model(inputs)\n            _, preds = torch.max(outputs, 1)\n\n            # Tìm các index sai\n            misclassified_idxs = (preds != labels).nonzero(as_tuple=False)\n\n            for idx in misclassified_idxs:\n                idx = idx.item() # Lấy index cụ thể\n                if images_so_far >= num_images:\n                    break\n\n                images_so_far += 1\n                \n                ax = plt.subplot(rows, cols, images_so_far)\n                ax.axis('off')\n                \n                # Chuyển Tensor sang Numpy & Un-normalize\n                img = inputs[idx].cpu().numpy().transpose((1, 2, 0))\n                img = std * img + mean\n                img = np.clip(img, 0, 1)\n\n                ax.imshow(img)\n                \n                # Lấy tên nhãn (xử lý cả dict và list)\n                if isinstance(class_names, dict):\n                    true_label = class_names[labels[idx].item()]\n                    pred_label = class_names[preds[idx].item()]\n                else: # Nếu là list\n                    true_label = class_names[labels[idx]]\n                    pred_label = class_names[preds[idx]]\n                \n                # Tiêu đề màu đỏ: Nhãn Đúng vs Nhãn Đoán\n                ax.set_title(f\"True: {true_label}\\nPred: {pred_label}\", color='red', fontsize=10, fontweight='bold')\n\n            if images_so_far >= num_images:\n                plt.suptitle(f\"Error Analysis: {model_name} (Top {num_images} Errors)\", fontsize=16, y=1.02)\n                plt.tight_layout()\n                plt.show()\n                return\n\n    print(f\"   -> Tuyệt vời! Model đoán đúng gần hết hoặc không tìm đủ {num_images} lỗi.\")\n\n\n# 2. HÀM ĐO TỐC ĐỘ XỬ LÝ (INFERENCE TIME / FPS)\ndef measure_speed(model, device, model_name=\"Model\", input_shape=(1, 3, 224, 224), n_runs=100):\n    \"\"\"\n    Đo tốc độ xử lý trung bình và số khung hình trên giây (FPS).\n    \"\"\"\n    model.to(device)\n    model.eval()\n    \n    # Tạo dữ liệu giả lập (Dummy input)\n    dummy_input = torch.randn(input_shape).to(device)\n\n    # A. Warmup (Làm nóng GPU - quan trọng để số liệu chính xác)\n    print(f\"\\n⏱️ [{model_name}] Đang Warmup GPU...\")\n    with torch.no_grad():\n        for _ in range(10):\n            _ = model(dummy_input)\n    \n    # B. Đo thời gian thực tế\n    print(f\"   -> Đang test tốc độ trên {n_runs} mẫu...\")\n    start_time = time.time()\n    with torch.no_grad():\n        for _ in range(n_runs):\n            _ = model(dummy_input)\n    end_time = time.time()\n    \n    total_time = end_time - start_time\n    avg_time_ms = (total_time / n_runs) * 1000 # Mili-giây\n    fps = n_runs / total_time # Khung hình/giây\n    \n    print(\"-\" * 50)\n    print(f\"🚀 KẾT QUẢ HIỆU NĂNG: {model_name}\")\n    print(f\"   - Tổng thời gian ({n_runs} ảnh): {total_time:.4f}s\")\n    print(f\"   - Độ trễ (Latency): {avg_time_ms:.2f} ms/ảnh\")\n    print(f\"   - Tốc độ (FPS): {fps:.2f} frames/sec\")\n    print(\"-\" * 50)\n\n\n# ==============================================================================\n# 3. CHẠY THỰC TẾ (EXECUTION)\n# ==============================================================================\n# Cấu hình thiết bị\nif 'device' not in globals(): device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\n# Giả định class_names nếu chưa có (Phòng hờ biến bị mất)\nif 'class_names' not in globals(): \n    # Thay đổi danh sách này khớp với dataset Cassava của bạn\n    class_names = {0: 'CBB', 1: 'CBSD', 2: 'CGM', 3: 'CMD', 4: 'Healthy'}\n\nprint(\"🎯 BẮT ĐẦU PHÂN TÍCH VÀ KIỂM TRA HIỆU NĂNG...\\n\")\n\n# --- A. XỬ LÝ RESNET ---\nif 'resnet' in globals() and 'test_loader' in globals():\n    visualize_errors(resnet, test_loader, device, class_names, model_name=\"ResNet50\", num_images=10)\n    measure_speed(resnet, device, model_name=\"ResNet50\")\nelse:\n    print(\"⚠️ Bỏ qua ResNet (Không tìm thấy biến 'resnet' hoặc 'test_loader')\")\n\n# --- B. XỬ LÝ VIT (VISION TRANSFORMER) ---\nif 'model_vit' in globals() and 'test_loader' in globals():\n    visualize_errors(model_vit, test_loader, device, class_names, model_name=\"ViT Base\", num_images=10)\n    measure_speed(model_vit, device, model_name=\"ViT Base\")\nelse:\n    print(\"⚠️ Bỏ qua ViT (Không tìm thấy biến 'model_vit' hoặc 'test_loader')\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-17T05:19:15.630585Z","iopub.execute_input":"2025-12-17T05:19:15.630977Z","iopub.status.idle":"2025-12-17T05:19:22.974548Z","shell.execute_reply.started":"2025-12-17T05:19:15.630923Z","shell.execute_reply":"2025-12-17T05:19:22.9736Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from pytorch_grad_cam import GradCAM\nfrom pytorch_grad_cam.utils.image import show_cam_on_image\nfrom pytorch_grad_cam.utils.model_targets import ClassifierOutputTarget\n\nprint(\"=== VISUALIZATION HEATMAP (GRAD-CAM) ===\")\n\ndef visualize_gradcam(model, dataset, num_images=4):\n    model.eval()\n    # Chon layer cuoi cung cua ResNet (layer4)\n    target_layers = [model.layer4[-1]]\n    cam = GradCAM(model=model, target_layers=target_layers)\n\n    # Chon ngau nhien anh tu tap test\n    indices = np.random.choice(len(dataset), num_images, replace=False)\n\n    fig, axes = plt.subplots(num_images, 2, figsize=(10, 5 * num_images))\n\n    for i, idx in enumerate(indices):\n        img_tensor, label = dataset[idx]\n        input_tensor = img_tensor.unsqueeze(0).to(device)\n\n        # Tao Heatmap\n        grayscale_cam = cam(input_tensor=input_tensor, targets=None) # None = lay class du doan cao nhat\n        grayscale_cam = grayscale_cam[0, :]\n\n        # Denormalize anh de hien thi\n        inv_normalize = transforms.Normalize(\n            mean=[-0.485/0.229, -0.456/0.224, -0.406/0.225],\n            std=[1/0.229, 1/0.224, 1/0.225]\n        )\n        rgb_img = inv_normalize(img_tensor).permute(1, 2, 0).numpy()\n        rgb_img = np.clip(rgb_img, 0, 1)\n\n        visualization = show_cam_on_image(rgb_img, grayscale_cam, use_rgb=True)\n\n        # Hien thi anh\n        ax_orig = axes[i, 0] if num_images > 1 else axes[0]\n        ax_cam = axes[i, 1] if num_images > 1 else axes[1]\n\n        # Lay du doan\n        output = model(input_tensor)\n        probabilities = torch.nn.functional.softmax(output, dim=1)\n        conf, pred_idx = torch.max(probabilities, 1)\n\n        true_name = class_names[label.item()]\n        pred_name = class_names[pred_idx.item()]\n        color = 'green' if label == pred_idx else 'red'\n\n        ax_orig.imshow(rgb_img)\n        ax_orig.set_title(f\"True: {true_name}\\nPred: {pred_name}\", color=color)\n        ax_orig.axis('off')\n\n        ax_cam.imshow(visualization)\n        ax_cam.set_title(f\"Grad-CAM (Conf: {conf.item()*100:.1f}%)\")\n        ax_cam.axis('off')\n\n    plt.tight_layout()\n    plt.show()\n\n# Ve Heatmap cho ResNet50\nvisualize_gradcam(resnet, test_ds, num_images=5)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-17T05:19:22.97559Z","iopub.execute_input":"2025-12-17T05:19:22.975857Z","iopub.status.idle":"2025-12-17T05:19:25.627082Z","shell.execute_reply.started":"2025-12-17T05:19:22.975834Z","shell.execute_reply":"2025-12-17T05:19:25.625674Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Ham nay dung de demo: Nhan duong dan file anh -> Tra ve ket qua va Heatmap\ndef predict_single_image(image_path, model):\n    model.eval()\n    \n    # Preprocess\n    transform = transforms.Compose([\n        transforms.Resize((224, 224)),\n        transforms.ToTensor(),\n        transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n    ])\n    \n    image = Image.open(image_path).convert(\"RGB\")\n    img_tensor = transform(image).unsqueeze(0).to(device)\n    \n    # Predict\n    with torch.no_grad():\n        output = model(img_tensor)\n        probs = torch.nn.functional.softmax(output, dim=1)\n        conf, pred_idx = torch.max(probs, 1)\n        \n    # Grad-CAM\n    target_layers = [model.layer4[-1]]\n    cam = GradCAM(model=model, target_layers=target_layers)\n    grayscale_cam = cam(input_tensor=img_tensor, targets=None)[0, :]\n    \n    # Visualize\n    rgb_img = np.array(image.resize((224, 224))) / 255.0\n    visualization = show_cam_on_image(rgb_img, grayscale_cam, use_rgb=True)\n    \n    plt.figure(figsize=(10, 5))\n    plt.subplot(1, 2, 1)\n    plt.imshow(rgb_img)\n    plt.title(\"Original Image\")\n    plt.axis('off')\n    \n    plt.subplot(1, 2, 2)\n    plt.imshow(visualization)\n    plt.title(f\"Pred: {class_names[pred_idx.item()]}\\nConf: {conf.item()*100:.2f}%\")\n    plt.axis('off')\n    plt.show()\n\n# Lay thu 1 anh tu tap Test de demo\nsample_row = test_df.iloc[0]\nprint(f\"Demo voi file: {sample_row['image_path']}\")\npredict_single_image(sample_row['image_path'], resnet)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-17T05:19:25.628148Z","iopub.execute_input":"2025-12-17T05:19:25.628391Z","iopub.status.idle":"2025-12-17T05:19:25.988485Z","shell.execute_reply.started":"2025-12-17T05:19:25.628371Z","shell.execute_reply":"2025-12-17T05:19:25.987919Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- 1. CÀI ĐẶT & IMPORT ---\nimport os\nimport torch\nimport joblib\n!pip install huggingface_hub --quiet\nfrom huggingface_hub import login, HfApi, create_repo\n\n\nHF_TOKEN = \"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\" \nHF_USERNAME = \"lamphamit\"             \nREPO_NAME = \"cassava-leaf-disease-models\"\n\nFULL_REPO_ID = f\"{HF_USERNAME}/{REPO_NAME}\"\n\nprint(\"=== BƯỚC 1: LƯU MODEL XUỐNG FILE ===\")\n\n# Lưu ResNet\ntry:\n    if 'resnet' in globals():\n        torch.save(resnet.state_dict(), \"resnet50_best.pth\")\n        print(\"✅ Đã lưu: resnet50_best.pth\")\nexcept: pass\n\n# Lưu ViT\ntry:\n    if 'model_vit' in globals():\n        torch.save(model_vit.state_dict(), \"vit_base_best.pth\")\n        print(\"✅ Đã lưu: vit_base_best.pth\")\nexcept: pass\n\n# Lưu SVM\ntry:\n    if 'svm_model' in globals():\n        joblib.dump(svm_model, \"svm_hog_model.joblib\")\n        print(\"✅ Đã lưu: svm_hog_model.joblib\")\n    elif 'clf' in globals():\n        joblib.dump(clf, \"svm_hog_model.joblib\")\n        print(\"✅ Đã lưu: svm_hog_model.joblib\")\nexcept: pass\n\n# ==============================================================================\n# 4. UPLOAD LÊN HUGGING FACE\n# ==============================================================================\nprint(\"\\n=== BƯỚC 2: KẾT NỐI & UPLOAD ===\")\ntry:\n    login(token=HF_TOKEN)\n    api = HfApi()\n    create_repo(FULL_REPO_ID, repo_type=\"model\", exist_ok=True)\n    print(f\"✅ Đã kết nối Repo: {FULL_REPO_ID}\")\nexcept Exception as e:\n    print(f\"❌ Lỗi kết nối (Check lại Token): {e}\")\n\n# Hàm upload an toàn\ndef upload_file_safe(local_name, remote_name):\n    if os.path.exists(local_name):\n        print(f\"⏳ Đang upload {local_name}...\")\n        try:\n            api.upload_file(\n                path_or_fileobj=local_name,\n                path_in_repo=remote_name,\n                repo_id=FULL_REPO_ID,\n                repo_type=\"model\"\n            )\n            print(\"   -> Xong!\")\n        except Exception as e:\n            print(f\"   -> Lỗi: {e}\")\n\n# Upload 3 model\nupload_file_safe(\"resnet50_best.pth\", \"resnet50_best.pth\")\nupload_file_safe(\"vit_base_best.pth\", \"vit_base_best.pth\")\nupload_file_safe(\"svm_hog_model.joblib\", \"svm_hog_model.joblib\")\n\n# ==============================================================================\n# 5. TẠO README (CÁCH MỚI: KHÔNG DÙNG NGOẶC KÉP DÀI ĐỂ TRÁNH LỖI)\n# ==============================================================================\nprint(\"\\n=== BƯỚC 3: TẠO README ===\")\n\nwith open(\"README.md\", \"w\") as f:\n    f.write(\"---\\n\")\n    f.write(\"tags:\\n- image-classification\\n- cassava\\n\")\n    f.write(\"---\\n\")\n    f.write(f\"# Models for Cassava Leaf Disease\\n\\n\")\n    f.write(\"This repo contains models trained on Kaggle dataset:\\n\")\n    f.write(\"1. **ResNet50** (.pth)\\n\")\n    f.write(\"2. **ViT Base** (.pth)\\n\")\n    f.write(\"3. **SVM** (.joblib)\\n\")\n\nupload_file_safe(\"README.md\", \"README.md\")\n\nprint(f\"\\n🎉 HOÀN TẤT! Link: https://huggingface.co/{FULL_REPO_ID}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-17T05:19:25.989357Z","iopub.execute_input":"2025-12-17T05:19:25.989664Z","iopub.status.idle":"2025-12-17T05:19:46.695794Z","shell.execute_reply.started":"2025-12-17T05:19:25.989646Z","shell.execute_reply":"2025-12-17T05:19:46.694552Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}