{"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":31193,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"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-12T06:26:12.938149Z","iopub.execute_input":"2025-12-12T06:26:12.938375Z","iopub.status.idle":"2025-12-12T06:26:26.334408Z","shell.execute_reply.started":"2025-12-12T06:26:12.938357Z","shell.execute_reply":"2025-12-12T06:26:26.333177Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 1. Import Libraries","metadata":{}},{"cell_type":"code","source":"\nimport os\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms, models\nfrom torchvision.io import read_image\nimport pandas as pd\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import accuracy_score, confusion_matrix, classification_report\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nfrom tqdm import tqdm","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-12T06:26:26.336352Z","iopub.execute_input":"2025-12-12T06:26:26.336693Z","iopub.status.idle":"2025-12-12T06:26:34.138079Z","shell.execute_reply.started":"2025-12-12T06:26:26.336674Z","shell.execute_reply":"2025-12-12T06:26:34.137494Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# GPU Memory Management\ntorch.cuda.empty_cache()\nos.environ[\"PYTORCH_CUDA_ALLOC_CONF\"] = \"expandable_segments:True\"\n\n# ------------------ Paths ------------------\nCSV_PATH = '/kaggle/input/cassava-leaf-disease-classification/train.csv'\nIMG_DIR  = '/kaggle/input/cassava-leaf-disease-classification/train_images'","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-12T06:26:34.142131Z","iopub.execute_input":"2025-12-12T06:26:34.142673Z","iopub.status.idle":"2025-12-12T06:26:34.147610Z","shell.execute_reply.started":"2025-12-12T06:26:34.142648Z","shell.execute_reply":"2025-12-12T06:26:34.146672Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 2. Load Data & Perform 70/20/10 Split","metadata":{}},{"cell_type":"code","source":"# ------------------ Load & Exact 70/20/10 Split ------------------\ndf = pd.read_csv(CSV_PATH)\n\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=1/3, stratify=temp_df['label'], random_state=42)\n\nprint(f\"Train: {len(train_df)} ({len(train_df)/len(df)*100:.1f}%)\")\nprint(f\"Validation: {len(val_df)} ({len(val_df)/len(df)*100:.1f}%)\")\nprint(f\"Test: {len(test_df)} ({len(test_df)/len(df)*100:.1f}%)\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-12T06:26:34.148536Z","iopub.execute_input":"2025-12-12T06:26:34.148806Z","iopub.status.idle":"2025-12-12T06:26:34.330467Z","shell.execute_reply.started":"2025-12-12T06:26:34.148781Z","shell.execute_reply":"2025-12-12T06:26:34.329872Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 3. Dataset Class","metadata":{}},{"cell_type":"code","source":"# ------------------ Dataset ------------------\nclass CassavaDataset(Dataset):\n    def __init__(self, dataframe, transform=None):\n        self.df = dataframe.reset_index(drop=True)\n        self.transform = transform\n    def __len__(self): return len(self.df)\n    def __getitem__(self, idx):\n        img_name = self.df.iloc[idx, 0]\n        image = read_image(os.path.join(IMG_DIR, img_name)).float() / 255.0\n        if image.shape[0] == 1: image = image.repeat(3, 1, 1)\n        label = self.df.iloc[idx, 1]\n        if self.transform: image = self.transform(image)\n        return image, label","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-12T06:26:34.331139Z","iopub.execute_input":"2025-12-12T06:26:34.331391Z","iopub.status.idle":"2025-12-12T06:26:34.337224Z","shell.execute_reply.started":"2025-12-12T06:26:34.331369Z","shell.execute_reply":"2025-12-12T06:26:34.336589Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 4. Data Augmentation & Loaders","metadata":{}},{"cell_type":"code","source":"# ------------------ Transforms & Loaders ------------------\ntransform = transforms.Compose([\n    transforms.Resize((300, 300)),\n    transforms.RandomHorizontalFlip(p=0.5),\n    transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])\n])\n\ntrain_dataset = CassavaDataset(train_df, transform)\nval_dataset   = CassavaDataset(val_df,   transform)\ntest_dataset  = CassavaDataset(test_df,  transform)\n\ntrain_loader = DataLoader(train_dataset, batch_size=48, shuffle=True,  pin_memory=True)\nval_loader   = DataLoader(val_dataset,   batch_size=48, shuffle=False, pin_memory=True)\ntest_loader  = DataLoader(test_dataset,  batch_size=48, shuffle=False, pin_memory=True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-12T06:26:34.337986Z","iopub.execute_input":"2025-12-12T06:26:34.338263Z","iopub.status.idle":"2025-12-12T06:26:34.348446Z","shell.execute_reply.started":"2025-12-12T06:26:34.338235Z","shell.execute_reply":"2025-12-12T06:26:34.347895Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 5. Model: EfficientNet-B3 (Pretrained)","metadata":{}},{"cell_type":"code","source":"# ------------------ Model ------------------\nmodel = models.efficientnet_b3(weights='IMAGENET1K_V1')\nmodel.classifier[1] = nn.Linear(1536, 5)\nmodel.to('cuda')\n\ncriterion = nn.CrossEntropyLoss()\noptimizer = optim.AdamW(model.parameters(), lr=3e-4)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-12T06:26:34.349218Z","iopub.execute_input":"2025-12-12T06:26:34.349458Z","iopub.status.idle":"2025-12-12T06:26:35.323903Z","shell.execute_reply.started":"2025-12-12T06:26:34.349434Z","shell.execute_reply":"2025-12-12T06:26:35.322998Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 6. Training Loop (14 Epochs)","metadata":{}},{"cell_type":"code","source":"# ------------------ Training Loop with Train & Val Accuracy per Epoch ------------------\n# This loop trains the model for 8 epochs max, fine-tuning to avoid overfitting.\n# Checkpoints are saved from epoch 4 to 8 for later comparison.\n\nbest_val_acc = 0.0\nprint(f\"{'Epoch':<6} {'Train Accuracy':<16} {'Validation Accuracy':<20} {'Status'}\")\nprint(\"-\" * 60)\n\nfor epoch in range(8):\n    # Training Phase\n    model.train()\n    train_correct = 0\n    train_total = 0\n\n    for images, labels in tqdm(train_loader, desc=f\"Epoch {epoch+1:02d}/08 [Training]\", leave=False):\n        images, labels = images.cuda(), labels.cuda()\n\n        optimizer.zero_grad()\n        outputs = model(images)\n        loss = criterion(outputs, labels)\n        loss.backward()\n        optimizer.step()\n\n        _, predicted = torch.max(outputs, 1)\n        train_total += labels.size(0)\n        train_correct += (predicted == labels).sum().item()\n\n    train_acc = 100 * train_correct / train_total\n\n    # Validation Phase\n    model.eval()\n    val_correct = 0\n    val_total = 0\n\n    with torch.no_grad():\n        for images, labels in val_loader:\n            images, labels = images.cuda(), labels.cuda()\n            outputs = model(images)\n            _, predicted = torch.max(outputs, 1)\n            val_total += labels.size(0)\n            val_correct += (predicted == labels).sum().item()\n\n    val_acc = 100 * val_correct / val_total\n\n    status = \"\"\n    if val_acc > best_val_acc:\n        best_val_acc = val_acc\n        torch.save(model.state_dict(), \"best_model.pth\")\n        status = \"→ BEST MODEL SAVED!\"\n\n    # Save checkpoint if in range 4-8\n    if epoch + 1 >= 4 and epoch + 1 <= 8:\n        torch.save(model.state_dict(), f\"checkpoint_epoch_{epoch+1}.pth\")\n        status += \" → CHECKPOINT SAVED!\"\n\n    print(f\"{epoch+1:02d}     {train_acc:6.2f}%          {val_acc:7.2f}%            {status}\")\n\n\nprint(\"\\n\" + \"=\"*65)\nprint(\"TRAINING COMPLETED – FINAL SUMMARY\")\nprint(\"=\"*65)\nprint(f\"Best Validation Accuracy (20%): {best_val_acc:.2f}%\")\nprint(f\"Last Train Accuracy     (70%): {train_acc:.2f}%\")\nprint(f\"Last Validation Accuracy(20%): {val_acc:.2f}%\")\nprint(\"=\"*65)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-12T06:26:35.324784Z","iopub.execute_input":"2025-12-12T06:26:35.325052Z","iopub.status.idle":"2025-12-12T07:27:53.462119Z","shell.execute_reply.started":"2025-12-12T06:26:35.325022Z","shell.execute_reply":"2025-12-12T07:27:53.461412Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 7. Final Results on Test Set (10%)","metadata":{}},{"cell_type":"code","source":"# ------------------ Final Evaluation on Test Set (10%) ------------------\n# Load the best checkpoint from comparison (or fallback to best_model.pth) and evaluate on test set.\n# Includes accuracy, confusion matrix, and classification report.\n\n# Assuming best_checkpoint is defined from previous cell; fallback if not\ntry:\n    model.load_state_dict(torch.load(best_checkpoint))\nexcept NameError:\n    model.load_state_dict(torch.load(\"best_model.pth\"))  # Fallback to original best\n\nmodel.eval()\ncorrect = 0\nwith torch.no_grad():\n    for images, labels in test_loader:\n        correct += (model(images.cuda()).argmax(1) == labels.cuda()).sum().item()\ntest_acc = correct / len(test_df)\n\nprint(f\"Accuracy on 10% Test set: {test_acc*100:.2f}\\n\")\n\npreds = []\ntrues = []\nwith torch.no_grad():\n    for images, labels in test_loader:\n        preds.extend(model(images.cuda()).argmax(1).cpu().numpy())\n        trues.extend(labels.numpy())\n\ncm = confusion_matrix(trues, preds)\nplt.figure(figsize=(8,6))\nsns.heatmap(cm, annot=True, fmt='d', cmap='Blues')\nplt.title('Confusion Matrix - 10% Test Set')\nplt.xlabel('Predicted')\nplt.ylabel('True')\nplt.show()\n\nprint(classification_report(trues, preds))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-12T07:27:53.464097Z","iopub.execute_input":"2025-12-12T07:27:53.464349Z","iopub.status.idle":"2025-12-12T07:28:55.709759Z","shell.execute_reply.started":"2025-12-12T07:27:53.464330Z","shell.execute_reply":"2025-12-12T07:28:55.709087Z"}},"outputs":[],"execution_count":null}]}