{"metadata":{"kernelspec":{"name":"python3","display_name":"Python 3","language":"python"},"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"},"colab":{"machine_shape":"hm","gpuType":"T4"},"accelerator":"GPU","kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceType":"competition","sourceId":13836,"databundleVersionId":1718836}],"dockerImageVersionId":31090,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"## Local Inference on GPU \n","metadata":{}},{"cell_type":"code","source":"import timm\n\n#model = timm.create_model(\"hf_hub:timm/densenet169.tv_in1k\", pretrained=True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-08T21:01:20.661004Z","iopub.execute_input":"2025-12-08T21:01:20.661271Z","iopub.status.idle":"2025-12-08T21:01:31.001368Z","shell.execute_reply.started":"2025-12-08T21:01:20.661243Z","shell.execute_reply":"2025-12-08T21:01:31.000826Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Create 5 Stratified Folds (siempre los mismos)\nNUM_FOLDS = 5\n\n# ====================================================\n# Elegir un fold (del 0 al 4)\n# ====================================================\nFOLD = 1  #\nEPOCHS = 15 # esto no lo muevan!\nIMG_SIZE = 512\nALFA = 3\nGAMMA = 5\nMODEL = 'convnext_tiny'","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-08T21:01:31.002531Z","iopub.execute_input":"2025-12-08T21:01:31.002787Z","iopub.status.idle":"2025-12-08T21:01:31.00687Z","shell.execute_reply.started":"2025-12-08T21:01:31.00277Z","shell.execute_reply":"2025-12-08T21:01:31.00632Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ====================================================\n# Set All Seeds for Reproducibility\n# ====================================================\n\nimport random\nimport numpy as np\nimport torch\n\ndef set_seed(seed=42):\n    random.seed(seed)\n    np.random.seed(seed)\n    torch.manual_seed(seed)\n    torch.cuda.manual_seed_all(seed)\n    #For deterministic behavior (slightly slower but reproducible)\n    torch.backends.cudnn.deterministic = True\n    torch.backends.cudnn.benchmark = False\n\nset_seed(42)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-08T21:01:31.007585Z","iopub.execute_input":"2025-12-08T21:01:31.007765Z","iopub.status.idle":"2025-12-08T21:01:31.024514Z","shell.execute_reply.started":"2025-12-08T21:01:31.007751Z","shell.execute_reply":"2025-12-08T21:01:31.023937Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#import pandas as pd\n#d=pd.read_csv('/kaggle/input/cassava-leaf-disease-classification/train.csv')\n#print(d.head(25))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-08T21:01:31.025187Z","iopub.execute_input":"2025-12-08T21:01:31.025381Z","iopub.status.idle":"2025-12-08T21:01:31.028532Z","shell.execute_reply.started":"2025-12-08T21:01:31.025365Z","shell.execute_reply":"2025-12-08T21:01:31.027958Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ====================================================\n# Cassava Leaf Disease Detection using EfficientNet B3\n# 80:20 Train-Validation Split\n# ====================================================\n\nimport os\nimport pandas as pd\nfrom sklearn.model_selection import train_test_split\nfrom PIL import Image\nfrom tqdm import tqdm\nfrom sklearn.model_selection import StratifiedKFold\n\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms\nimport timm\n\n# ====================================================\n# Step 1: Load Dataset\n# ====================================================\n\n# Load CSV\n\n# Load CSV\ndf = pd.read_csv('/kaggle/input/cassava-leaf-disease-classification/train.csv')\ndf['filepath'] = df['image_id'].apply(\n    lambda x: os.path.join('/kaggle/input/cassava-leaf-disease-classification/train_images', x)\n)\n\n\ndf[\"fold\"] = -1\n\nskf = StratifiedKFold(n_splits=NUM_FOLDS, shuffle=True, random_state=42)\nfor fold, (_, val_idx) in enumerate(skf.split(df, df['label'])):\n    df.loc[val_idx, 'fold'] = fold\n\nprint(df['fold'].value_counts())\n\n\ntrain_df = df[df['fold'] != FOLD].reset_index(drop=True)\nval_df   = df[df['fold'] == FOLD].reset_index(drop=True)\n\nprint(f\"Using Fold {FOLD}\")\nprint(\"Train samples:\", len(train_df), \"Validation samples:\", len(val_df))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-08T21:01:31.030624Z","iopub.execute_input":"2025-12-08T21:01:31.031027Z","iopub.status.idle":"2025-12-08T21:01:32.055272Z","shell.execute_reply.started":"2025-12-08T21:01:31.031001Z","shell.execute_reply":"2025-12-08T21:01:32.05452Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\n# ====================================================\n# Step 2: Data Transforms & Dataset Class\n# ====================================================\n\n\n\ntrain_transform = transforms.Compose([\n    transforms.RandomResizedCrop(IMG_SIZE, scale=(0.8, 1.0)),\n    transforms.RandomHorizontalFlip(),\n    transforms.RandomVerticalFlip(),\n    transforms.RandomRotation(20),\n    transforms.ColorJitter(\n        brightness=0.2,     \n        contrast=0.2,       \n        saturation=0.2,     \n        hue=0.1\n    ),\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((IMG_SIZE, IMG_SIZE)),\n    transforms.ToTensor(),\n    transforms.Normalize([0.485, 0.456, 0.406],\n                         [0.229, 0.224, 0.225])\n])\n\nclass CassavaDataset(Dataset):\n    def __init__(self, df, transform=None):\n        self.df = df.reset_index(drop=True)\n        self.transform = transform\n        \n    def __len__(self):\n        return len(self.df)\n    \n    def __getitem__(self, idx):\n        path = self.df.loc[idx, 'filepath']\n        label = self.df.loc[idx, 'label']\n        image = Image.open(path).convert(\"RGB\")\n        if self.transform:\n            image = self.transform(image)\n        return image, label\n\n# Dataloaders\ntrain_ds = CassavaDataset(train_df, train_transform)\nval_ds = CassavaDataset(val_df, val_transform)\n\n\ntrain_loader = DataLoader(train_ds, batch_size=32, shuffle=True, num_workers=2)\nval_loader = DataLoader(val_ds, batch_size=32, shuffle=False, num_workers=2)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-08T21:01:32.056129Z","iopub.execute_input":"2025-12-08T21:01:32.056876Z","iopub.status.idle":"2025-12-08T21:01:32.065088Z","shell.execute_reply.started":"2025-12-08T21:01:32.056849Z","shell.execute_reply":"2025-12-08T21:01:32.064549Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\n# ====================================================\n# Step 3: Define Model (Resnet152)\n# ====================================================\n\nNUM_CLASSES = df['label'].nunique()\n\nmodel = timm.create_model(MODEL, pretrained=True)\nin_features = model.head.fc.in_features\nmodel.head.fc = nn.Sequential(\n    nn.Linear(in_features, 512),\n    nn.ReLU(),\n    nn.Dropout(0.2),\n    nn.Linear(512, NUM_CLASSES)\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-08T21:03:02.148248Z","iopub.execute_input":"2025-12-08T21:03:02.148635Z","iopub.status.idle":"2025-12-08T21:03:02.677145Z","shell.execute_reply.started":"2025-12-08T21:03:02.148607Z","shell.execute_reply":"2025-12-08T21:03:02.67659Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ====================================================\n# Step 4.1: Compute Class Weights\n# ====================================================\n\nfrom sklearn.utils.class_weight import compute_class_weight\nimport numpy as np\n\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n# Obtener todas las etiquetas\nlabels = df['label'].values\nclasses = np.unique(labels)\n\n# Calcular pesos inversos al número de muestras por clase\nclass_weights = compute_class_weight(\n    class_weight='balanced',\n    classes=classes,\n    y=labels\n)\n\n# Convertir a tensor y mover a GPU\nclass_weights = torch.tensor(class_weights, dtype=torch.float).to(device)\nprint(\"Class Weights:\", class_weights)\nclass_weights = class_weights.to(device)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-08T21:03:05.662015Z","iopub.execute_input":"2025-12-08T21:03:05.662288Z","iopub.status.idle":"2025-12-08T21:03:06.172518Z","shell.execute_reply.started":"2025-12-08T21:03:05.662269Z","shell.execute_reply":"2025-12-08T21:03:06.171644Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ====================================================\n# Step 4: Setup Training (Focal Loss version)\n# ====================================================\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nimport torch.optim as optim\n\n# === Focal Loss definition ===\nclass FocalLoss(nn.Module):\n    def __init__(self, alpha=1.0, gamma=2.0, reduction='mean'):\n        \"\"\"\n        alpha: weighting factor for balancing classes (float or tensor)\n        gamma: focusing parameter (higher → more focus on hard samples)\n        reduction: 'mean' or 'sum'\n        \"\"\"\n        super(FocalLoss, self).__init__()\n        self.alpha = alpha\n        self.gamma = gamma\n        self.reduction = reduction\n\n    def forward(self, inputs, targets):\n        # Cross Entropy base (sin softmax porque usa log-softmax internamente)\n        ce_loss = F.cross_entropy(inputs, targets, reduction='none')\n        pt = torch.exp(-ce_loss)  # probabilidad de la clase correcta\n        loss = self.alpha * (1 - pt) ** self.gamma * ce_loss\n\n        if self.reduction == 'mean':\n            return loss.mean()\n        elif self.reduction == 'sum':\n            return loss.sum()\n        else:\n            return loss\n\n# ====================================================\n# GPU setup \n# ====================================================\nif torch.cuda.device_count() > 1:\n    print(f\"Using {torch.cuda.device_count()} GPUs\")\n    model = nn.DataParallel(model)\n\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nmodel = model.to(device)\n\n# ====================================================\n# Loss, optimizer y scheduler\n# ====================================================\ncriterion = FocalLoss(alpha=ALFA, gamma=GAMMA, reduction='mean')\n#criterion = nn.CrossEntropyLoss(label_smoothing=0.05, weight=class_weights)\n#criterion = nn.CrossEntropyLoss(label_smoothing=0.05)\n\noptimizer = optim.AdamW(model.parameters(), lr=1.5e-4, weight_decay=1e-4)\n\n# Focal converge más lento → ajusta LR levemente menor\nscheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=EPOCHS, eta_min=1e-6)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-08T21:03:26.038848Z","iopub.execute_input":"2025-12-08T21:03:26.039113Z","iopub.status.idle":"2025-12-08T21:03:26.088477Z","shell.execute_reply.started":"2025-12-08T21:03:26.039083Z","shell.execute_reply":"2025-12-08T21:03:26.087901Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ====================================================\n# Step 5: Training Loop with Metric Tracking + Early Stopping (Patience=5)\n# ====================================================\n\nbest_acc = 0.0\npatience = 3\npatience_counter = 0  # contador de épocas sin mejora\n\n# ⚡ Activar mixed precision\nscaler = torch.cuda.amp.GradScaler()\n\n# 🧠 Para guardar métricas\ntrain_losses, val_losses = [], []\ntrain_accuracies, val_accuracies = [], []\nreal_epoch = 0\nfor epoch in range(EPOCHS):\n    # ---- Training ----\n    model.train()\n    train_loss, correct, total = 0, 0, 0\n    \n    for images, labels in tqdm(train_loader, desc=f\"Epoch {epoch+1}/{EPOCHS} [Train]\"):\n        images, labels = images.to(device), labels.to(device)\n        optimizer.zero_grad()\n\n        # ⚡ Mixed precision: autocast\n        with torch.cuda.amp.autocast():\n            outputs = model(images)\n            loss = criterion(outputs, labels)\n\n        # ⚡ Escalar el gradiente para evitar underflow\n        scaler.scale(loss).backward()\n        scaler.step(optimizer)\n        scaler.update()\n\n        train_loss += loss.item() * images.size(0)\n        _, predicted = outputs.max(1)\n        correct += predicted.eq(labels).sum().item()\n        total += labels.size(0)\n    \n    train_acc = correct / total\n    train_loss /= total\n\n    # ---- Validation ----\n    model.eval()\n    val_loss, correct, total = 0, 0, 0\n    \n    with torch.no_grad():\n        for images, labels in tqdm(val_loader, desc=f\"Epoch {epoch+1}/{EPOCHS} [Val]\"):\n            images, labels = images.to(device), labels.to(device)\n            with torch.cuda.amp.autocast():\n                outputs = model(images)\n                loss = criterion(outputs, labels)\n            \n            val_loss += loss.item() * images.size(0)\n            _, predicted = outputs.max(1)\n            correct += predicted.eq(labels).sum().item()\n            total += labels.size(0)\n    \n    val_acc = correct / total\n    val_loss /= total\n\n    real_epoch = epoch\n    \n    print(f\"Epoch {epoch+1}/{EPOCHS} | Train Loss: {train_loss:.4f}, Train Acc: {train_acc:.4f} | Val Loss: {val_loss:.4f}, Val Acc: {val_acc:.4f}\")\n\n    # Guardar métricas por época\n    train_losses.append(train_loss)\n    val_losses.append(val_loss)\n    train_accuracies.append(train_acc)\n    val_accuracies.append(val_acc)\n    \n    scheduler.step()\n    \n    # ---- Early Stopping con paciencia ----\n    if val_acc > best_acc:\n        best_acc = val_acc\n        patience_counter = 0  # reinicia paciencia\n        torch.save(model.state_dict(), f\"best_{MODEL}_{FOLD}.pth\")\n        print(f\"✅ Saved Best Model with Val Acc: {best_acc:.4f}\")\n    else:\n        patience_counter += 1\n        print(f\"⏸️ No improvement ({patience_counter}/{patience})\")\n        if patience_counter >= patience:\n            print(\"⏹️ Early stopping triggered (no improvement).\")\n            break\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-08T21:03:34.055654Z","iopub.execute_input":"2025-12-08T21:03:34.056224Z","iopub.status.idle":"2025-12-08T21:21:18.263437Z","shell.execute_reply.started":"2025-12-08T21:03:34.056202Z","shell.execute_reply":"2025-12-08T21:21:18.262348Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\n# ====================================================\n# Step 6: Load Best Model (for inference later)\n# ====================================================\n\nmodel.load_state_dict(torch.load(f\"best_{MODEL}_{FOLD}.pth\"))\nmodel.eval()\nprint(\"Loaded best model with validation accuracy:\", best_acc)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-08T21:01:34.257724Z","iopub.status.idle":"2025-12-08T21:01:34.257951Z","shell.execute_reply.started":"2025-12-08T21:01:34.257846Z","shell.execute_reply":"2025-12-08T21:01:34.257856Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ====================================================\n# Step 7: Plot Training Curves\n# ====================================================\nimport matplotlib.pyplot as plt\n\nepochs_range = range(1, real_epoch + 2)\n\nplt.figure(figsize=(12,5))\n\n# Loss\nplt.subplot(1,2,1)\nplt.plot(epochs_range, train_losses, label='Train Loss')\nplt.plot(epochs_range, val_losses, label='Val Loss')\nplt.xlabel('Epoch')\nplt.ylabel('Loss')\nplt.title('Training & Validation Loss')\nplt.legend()\nplt.grid(True)\n\n# Accuracy\nplt.subplot(1,2,2)\nplt.plot(epochs_range, train_accuracies, label='Train Acc')\nplt.plot(epochs_range, val_accuracies, label='Val Acc')\nplt.xlabel('Epoch')\nplt.ylabel('Accuracy')\nplt.title('Training & Validation Accuracy')\nplt.legend()\nplt.grid(True)\n\nplt.tight_layout()\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-08T21:01:34.258435Z","iopub.status.idle":"2025-12-08T21:01:34.258699Z","shell.execute_reply.started":"2025-12-08T21:01:34.258588Z","shell.execute_reply":"2025-12-08T21:01:34.258601Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ====================================================\n# Step 8: Confusion Matrix + ROC-AUC + Metrics per Class\n# ====================================================\nfrom sklearn.metrics import (\n    confusion_matrix, roc_curve, auc, precision_score, recall_score, f1_score,\n    classification_report\n)\nfrom sklearn.preprocessing import label_binarize\nimport seaborn as sns\nimport numpy as np\nimport matplotlib.pyplot as plt\nfrom itertools import cycle\nimport pandas as pd\n\nmodel.eval()\n\nmodel = model.module if hasattr(model, \"module\") else model\nmodel = model.to(device)\n\nall_preds = []\nall_labels = []\nall_probs = []\n\nwith torch.no_grad():\n    for images, labels in tqdm(val_loader, desc=\"Evaluating Metrics\"):\n        images = images.to(device)\n        outputs = model(images)\n        probs = torch.softmax(outputs, dim=1).cpu().numpy()\n        preds = outputs.argmax(1).cpu().numpy()\n        all_probs.extend(probs)\n        all_preds.extend(preds)\n        all_labels.extend(labels.numpy())\n\nall_labels = np.array(all_labels)\nall_preds = np.array(all_preds)\nall_probs = np.array(all_probs)\nn_classes = len(np.unique(all_labels))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-08T21:01:34.259825Z","iopub.status.idle":"2025-12-08T21:01:34.260103Z","shell.execute_reply.started":"2025-12-08T21:01:34.259947Z","shell.execute_reply":"2025-12-08T21:01:34.25996Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"cm = confusion_matrix(all_labels, all_preds)\nacc = np.trace(cm) / np.sum(cm)\n\nplt.figure(figsize=(8,6))\nsns.heatmap(cm, annot=True, fmt='d', cmap='Blues')\nplt.title(f'Confusion Matrix (Fold {FOLD})\\nAccuracy: {acc:.4f}')\nplt.xlabel('Predicted Label')\nplt.ylabel('True Label')\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-08T21:01:34.261239Z","iopub.status.idle":"2025-12-08T21:01:34.261524Z","shell.execute_reply.started":"2025-12-08T21:01:34.26137Z","shell.execute_reply":"2025-12-08T21:01:34.26138Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"precisions = precision_score(all_labels, all_preds, average=None, zero_division=0)\nrecalls = recall_score(all_labels, all_preds, average=None, zero_division=0)\nf1s = f1_score(all_labels, all_preds, average=None, zero_division=0)\naccuracies = cm.diagonal() / cm.sum(axis=1)\n\nmetrics_df = pd.DataFrame({\n    \"Class\": np.arange(n_classes),\n    \"Precision\": precisions,\n    \"Recall\": recalls,\n    \"F1-Score\": f1s,\n    \"Accuracy\": accuracies,\n})\n\ndisplay(metrics_df.style.background_gradient(cmap=\"Blues\", subset=[\"Precision\",\"Recall\",\"F1-Score\",\"Accuracy\"])\n                        .format({\"Precision\": \"{:.3f}\", \"Recall\": \"{:.3f}\", \"F1-Score\": \"{:.3f}\", \"Accuracy\": \"{:.3f}\"}))\n\n# ====================================================\n# 4️⃣ Report global averages\n# ====================================================\nprint(\"\\nOverall Metrics:\")\nprint(classification_report(all_labels, all_preds, digits=3))\nprint(f\"Global Accuracy: {acc:.3f}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-08T21:01:34.262344Z","iopub.status.idle":"2025-12-08T21:01:34.262613Z","shell.execute_reply.started":"2025-12-08T21:01:34.262504Z","shell.execute_reply":"2025-12-08T21:01:34.262518Z"}},"outputs":[],"execution_count":null}]}