{"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":"gpu","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":"#Team\n#Nada Mohamed Roshdy\n#Mona Mohamed Ayoub\n#Alaa Hesham Mostafa","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Cassava Leaf Disease Classification - Project Summary\n\n**Objective:**  \nClassify cassava leaf images into 5 categories:  \n- CBB (Cassava Bacterial Blight)  \n- CBSD (Cassava Brown Streak Disease)  \n- CGM (Cassava Green Mottle)  \n- CMD (Cassava Mosaic Disease)  \n- Healthy  \n\n**Steps performed:**  \n1. **Import Libraries**  \n   - Imported PyTorch, torchvision, PIL, sklearn, matplotlib, seaborn, pandas, numpy.  \n\n2. **Load Data**  \n   - Loaded CSV (`train.csv`) and checked sample images in `train_images`.  \n\n3. **Define Custom Dataset**  \n   - Created `CassavaDataset` class for loading images and labels with transforms.  \n\n4. **Data Preprocessing & Augmentation**  \n   - Applied `RandomResizedCrop`, `RandomHorizontalFlip` for training images.  \n   - CenterCrop + Resize for validation/test images.  \n\n5. **Dataset Split**  \n   - Split into 70% train, 20% validation, 10% test using `random_split`.  \n\n6. **Handle Class Imbalance**  \n   - Calculated inverse-frequency class weights and used them in `CrossEntropyLoss`.  \n\n7. **Model**  \n   - Used pre-trained EfficientNet-B0.  \n   - Replaced final layer with 5-class linear layer.  \n\n8. **Training**  \n   - Optimizer: Adam, LR = 1e-4.  \n   - Trained for 10 epochs with early stopping.  \n\n   **Results per step:**  \n   | Epoch | Train Acc | Val Acc | Notes |\n   |-------|-----------|---------|-------|\n   | 1     | 0.6885    | 0.7310  |       |\n   | 2     | 0.8111    | 0.7712  |       |\n   | 3     | 0.8608    | 0.7962  |       |\n   | 4     | 0.8980    | 0.7808  |       |\n   | 5     | 0.9270    | 0.7941  |       |\n   | 6     | 0.9396    | 0.8205  | Early stopping |\n\n9. **Evaluation**  \n   - Test Accuracy: 0.7926  \n   - Classification report and confusion matrix plotted.  \n\n10. **Single Image Prediction**  \n    - Tested on one sample image (`2216849948.jpg`).  \n    - Model Prediction: Healthy ✅  \n\n**Conclusion:**  \n- EfficientNet-B0 handled the classification task well.  \n- Using class weights helped with class imbalance.  \n- Early stopping prevented overfitting.\n\n\n","metadata":{}},{"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-12-13T14:04:20.794915Z","iopub.execute_input":"2025-12-13T14:04:20.795208Z","iopub.status.idle":"2025-12-13T14:04:21.281298Z","shell.execute_reply.started":"2025-12-13T14:04:20.795188Z","shell.execute_reply":"2025-12-13T14:04:21.280189Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nprint(torch.cuda.is_available())\nprint(torch.cuda.get_device_name(0))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-13T14:05:24.916788Z","iopub.execute_input":"2025-12-13T14:05:24.917561Z","iopub.status.idle":"2025-12-13T14:05:26.622329Z","shell.execute_reply.started":"2025-12-13T14:05:24.917531Z","shell.execute_reply":"2025-12-13T14:05:26.621591Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# =========================\n# 1. Import Libraries\n# =========================\nimport os\nimport pandas as pd\nimport numpy as np\nfrom PIL import Image\nimport matplotlib.pyplot as plt\nimport seaborn as sns\n\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader, random_split\nfrom torchvision import transforms, models\nfrom sklearn.metrics import classification_report, confusion_matrix\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-13T14:05:29.446256Z","iopub.execute_input":"2025-12-13T14:05:29.446694Z","iopub.status.idle":"2025-12-13T14:05:31.361779Z","shell.execute_reply.started":"2025-12-13T14:05:29.446644Z","shell.execute_reply":"2025-12-13T14:05:31.361153Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 2. Set paths and load CSV\n# =========================\nDATA_DIR = \"/kaggle/input/cassava-leaf-disease-classification/train_images\"\nCSV_PATH = \"/kaggle/input/cassava-leaf-disease-classification/train.csv\"\n\ndf = pd.read_csv(CSV_PATH)\nprint(\"First 5 rows of CSV:\")\nprint(df.head())\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-13T14:05:37.957405Z","iopub.execute_input":"2025-12-13T14:05:37.957882Z","iopub.status.idle":"2025-12-13T14:05:37.982653Z","shell.execute_reply.started":"2025-12-13T14:05:37.957858Z","shell.execute_reply":"2025-12-13T14:05:37.982014Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 3. Define Custom Dataset\n# =========================\nclass CassavaDataset(Dataset):\n    def __init__(self, dataframe, img_dir, transform=None):\n        self.df = dataframe\n        self.img_dir = img_dir\n        self.transform = transform\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        img_name = self.df.iloc[idx]['image_id']\n        label = int(self.df.iloc[idx]['label'])\n        img_path = os.path.join(self.img_dir, img_name)\n        image = Image.open(img_path).convert(\"RGB\")\n        if self.transform:\n            image = self.transform(image)\n        return image, label","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-13T14:05:41.837647Z","iopub.execute_input":"2025-12-13T14:05:41.837959Z","iopub.status.idle":"2025-12-13T14:05:41.843383Z","shell.execute_reply.started":"2025-12-13T14:05:41.837937Z","shell.execute_reply":"2025-12-13T14:05:41.842765Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 4. Define Transforms (Preprocessing + Augmentation)\n# =========================\ntrain_transform = transforms.Compose([\n    transforms.RandomResizedCrop(224),\n    transforms.RandomHorizontalFlip(),\n    transforms.ToTensor(),\n    transforms.Normalize([0.485, 0.456, 0.406],\n                         [0.229, 0.224, 0.225])\n])\n\nval_transform = transforms.Compose([\n    transforms.Resize(256),\n    transforms.CenterCrop(224),\n    transforms.ToTensor(),\n    transforms.Normalize([0.485, 0.456, 0.406],\n                         [0.229, 0.224, 0.225])\n])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-13T14:05:49.228306Z","iopub.execute_input":"2025-12-13T14:05:49.228886Z","iopub.status.idle":"2025-12-13T14:05:49.233977Z","shell.execute_reply.started":"2025-12-13T14:05:49.228860Z","shell.execute_reply":"2025-12-13T14:05:49.233213Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 5. Prepare Dataset and DataLoader\n# =========================\nfull_dataset = CassavaDataset(df, DATA_DIR, transform=train_transform)\n\n# Split dataset: 70% train, 20% val, 10% test\ntrain_size = int(0.7 * len(full_dataset))\nval_size = int(0.2 * len(full_dataset))\ntest_size = len(full_dataset) - train_size - val_size\n\ntrain_dataset, val_dataset, test_dataset = random_split(full_dataset, [train_size, val_size, test_size])\n\n# Apply val_transform for validation and test sets\nval_dataset.dataset.transform = val_transform\ntest_dataset.dataset.transform = val_transform\n\nBATCH_SIZE = 32\ntrain_loader = DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True, num_workers=2)\nval_loader = DataLoader(val_dataset, batch_size=BATCH_SIZE, shuffle=False, num_workers=2)\ntest_loader = DataLoader(test_dataset, batch_size=BATCH_SIZE, shuffle=False, num_workers=2)\n\nprint(f\"Number of training samples: {len(train_dataset)}\")\nprint(f\"Number of validation samples: {len(val_dataset)}\")\nprint(f\"Number of test samples: {len(test_dataset)}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-13T14:05:55.235203Z","iopub.execute_input":"2025-12-13T14:05:55.236026Z","iopub.status.idle":"2025-12-13T14:05:55.243512Z","shell.execute_reply.started":"2025-12-13T14:05:55.235997Z","shell.execute_reply":"2025-12-13T14:05:55.242860Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Set device (CPU or GPU)\n# =========================\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nprint(\"Using device:\", device)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-13T14:06:04.192154Z","iopub.execute_input":"2025-12-13T14:06:04.192882Z","iopub.status.idle":"2025-12-13T14:06:04.197986Z","shell.execute_reply.started":"2025-12-13T14:06:04.192847Z","shell.execute_reply":"2025-12-13T14:06:04.197079Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# =========================\n# Define device\n# =========================\ndevice = \"cuda\" if torch.cuda.is_available() else \"cpu\"\nprint(\"Using device:\", device)\n\n# =========================\n# Initialize EfficientNet\n# =========================\nmodel = models.efficientnet_b0(weights=models.EfficientNet_B0_Weights.DEFAULT)\nnum_ftrs = model.classifier[1].in_features\nmodel.classifier[1] = nn.Linear(num_ftrs, 5)  # 5 classes\nmodel = model.to(device)\n\n# =========================\n# Handle Class Imbalance\n# =========================\n# Extract labels from train_dataset (Subset)\ntrain_labels = [train_dataset.dataset.df.iloc[i]['label'] for i in train_dataset.indices]\n\n# Count samples per class\nclass_counts = pd.Series(train_labels).value_counts().sort_index()\n\n# Compute inverse frequency\nclass_weights = 1.0 / class_counts\n\n# Normalize weights\nclass_weights = class_weights / class_weights.sum()\n\n# Convert to tensor\nclass_weights = torch.tensor(class_weights.values, dtype=torch.float).to(device)\nprint(\"Class Weights:\", class_weights)\n\n# =========================\n# Define loss and optimizer\n# =========================\ncriterion = nn.CrossEntropyLoss(weight=class_weights)\noptimizer = optim.Adam(model.parameters(), lr=1e-4)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-13T14:17:07.640034Z","iopub.execute_input":"2025-12-13T14:17:07.640899Z","iopub.status.idle":"2025-12-13T14:17:08.488538Z","shell.execute_reply.started":"2025-12-13T14:17:07.640873Z","shell.execute_reply":"2025-12-13T14:17:08.487919Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 7. Training with Early Stopping\n# =========================\nEPOCHS = 10\npatience = 3  # Stop after 3 epochs with no improvement\nbest_val_loss = np.inf\ncounter = 0\n\ntrain_losses, val_losses = [], []\ntrain_accs, val_accs = [], []\n\nfor epoch in range(EPOCHS):\n    # ===== Training =====\n    model.train()\n    running_loss = 0\n    running_corrects = 0\n\n    for inputs, labels in train_loader:\n        inputs, labels = inputs.to(device), labels.to(device)\n        optimizer.zero_grad()\n        outputs = model(inputs)\n        loss = criterion(outputs, labels)\n        loss.backward()\n        optimizer.step()\n        running_loss += loss.item() * inputs.size(0)\n        preds = torch.argmax(outputs, 1)\n        running_corrects += torch.sum(preds == labels.data)\n\n    epoch_loss = running_loss / len(train_dataset)\n    epoch_acc = running_corrects.double() / len(train_dataset)\n    train_losses.append(epoch_loss)\n    train_accs.append(epoch_acc.item())\n\n    # ===== Validation =====\n    model.eval()\n    val_running_loss = 0\n    val_running_corrects = 0\n    with torch.no_grad():\n        for inputs, labels in val_loader:\n            inputs, labels = inputs.to(device), labels.to(device)\n            outputs = model(inputs)\n            loss = criterion(outputs, labels)\n            val_running_loss += loss.item() * inputs.size(0)\n            preds = torch.argmax(outputs, 1)\n            val_running_corrects += torch.sum(preds == labels.data)\n\n    val_loss = val_running_loss / len(val_dataset)\n    val_acc = val_running_corrects.double() / len(val_dataset)\n    val_losses.append(val_loss)\n    val_accs.append(val_acc.item())\n\n    print(f\"Epoch {epoch+1}/{EPOCHS} | \"\n          f\"Train Loss: {epoch_loss:.4f}, Train Acc: {epoch_acc:.4f} | \"\n          f\"Val Loss: {val_loss:.4f}, Val Acc: {val_acc:.4f}\")\n\n    # ===== Early Stopping =====\n    if val_loss < best_val_loss:\n        best_val_loss = val_loss\n        counter = 0\n        torch.save(model.state_dict(), \"best_model.pth\")\n    else:\n        counter += 1\n        if counter >= patience:\n            print(\"Early stopping triggered\")\n            break","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-13T14:17:48.800774Z","iopub.execute_input":"2025-12-13T14:17:48.801095Z","iopub.status.idle":"2025-12-13T14:27:52.201894Z","shell.execute_reply.started":"2025-12-13T14:17:48.801075Z","shell.execute_reply":"2025-12-13T14:27:52.200956Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 8. Load best model\n# =========================\nmodel.load_state_dict(torch.load(\"best_model.pth\"))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-13T14:29:16.308345Z","iopub.execute_input":"2025-12-13T14:29:16.308656Z","iopub.status.idle":"2025-12-13T14:29:16.409060Z","shell.execute_reply.started":"2025-12-13T14:29:16.308625Z","shell.execute_reply":"2025-12-13T14:29:16.408419Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 9. Evaluate on Test Set\n# =========================\nall_preds = []\nall_labels = []\n\nmodel.eval()\nwith torch.no_grad():\n    for inputs, labels in test_loader:\n        inputs, labels = inputs.to(device), labels.to(device)\n        outputs = model(inputs)\n        preds = torch.argmax(outputs, 1)\n        all_preds.extend(preds.cpu().numpy())\n        all_labels.extend(labels.cpu().numpy())\n\n# Classification Report\nprint(\"Classification Report:\")\nprint(classification_report(all_labels, all_preds, digits=4))\n\n# Confusion Matrix\ncm = confusion_matrix(all_labels, all_preds)\nplt.figure(figsize=(8,6))\nsns.heatmap(cm, annot=True, fmt=\"d\", cmap=\"Blues\")\nplt.xlabel(\"Predicted\")\nplt.ylabel(\"Actual\")\nplt.title(\"Confusion Matrix\")\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-13T14:29:23.466438Z","iopub.execute_input":"2025-12-13T14:29:23.466770Z","iopub.status.idle":"2025-12-13T14:29:34.840837Z","shell.execute_reply.started":"2025-12-13T14:29:23.466748Z","shell.execute_reply":"2025-12-13T14:29:34.840156Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 10. Plots (Accuracy & Loss)\n# =========================\nplt.figure(figsize=(12,5))\n\nplt.subplot(1,2,1)\nplt.plot(train_losses, label=\"Train Loss\")\nplt.plot(val_losses, label=\"Val Loss\")\nplt.xlabel(\"Epochs\")\nplt.ylabel(\"Loss\")\nplt.legend()\nplt.title(\"Loss Plot\")\n\nplt.subplot(1,2,2)\nplt.plot(train_accs, label=\"Train Acc\")\nplt.plot(val_accs, label=\"Val Acc\")\nplt.xlabel(\"Epochs\")\nplt.ylabel(\"Accuracy\")\nplt.legend()\nplt.title(\"Accuracy Plot\")\n\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-13T14:30:04.238035Z","iopub.execute_input":"2025-12-13T14:30:04.238794Z","iopub.status.idle":"2025-12-13T14:30:04.535777Z","shell.execute_reply.started":"2025-12-13T14:30:04.238761Z","shell.execute_reply":"2025-12-13T14:30:04.535000Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Mapping from label index to disease name\nlabel_to_disease = {\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}\n\n# Path to a single test image\ntest_image_path = \"/kaggle/input/cassava-leaf-disease-classification/test_images/2216849948.jpg\"\n\n# Load and preprocess the image\ntest_image = Image.open(test_image_path).convert(\"RGB\")\n\ntest_transform = transforms.Compose([\n    transforms.Resize(256),\n    transforms.CenterCrop(224),\n    transforms.ToTensor(),\n    transforms.Normalize([0.485, 0.456, 0.406],\n                         [0.229, 0.224, 0.225])\n])\n\ninput_tensor = test_transform(test_image).unsqueeze(0).to(device)\n\n# Set model to evaluation mode\nmodel.eval()\n\n# Predict\nwith torch.no_grad():\n    output = model(input_tensor)\n    _, predicted_class = torch.max(output, 1)\n\npredicted_label = predicted_class.item()\nprint(\"Predicted class index:\", predicted_label)\nprint(\"Predicted disease:\", label_to_disease[predicted_label])\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-13T14:31:09.214065Z","iopub.execute_input":"2025-12-13T14:31:09.214388Z","iopub.status.idle":"2025-12-13T14:31:09.255159Z","shell.execute_reply.started":"2025-12-13T14:31:09.214366Z","shell.execute_reply":"2025-12-13T14:31:09.254489Z"}},"outputs":[],"execution_count":null}]}