{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":14774,"databundleVersionId":875431,"sourceType":"competition"}],"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-19T06:10:29.246303Z","iopub.execute_input":"2025-12-19T06:10:29.246925Z","iopub.status.idle":"2025-12-19T06:10:31.41179Z","shell.execute_reply.started":"2025-12-19T06:10:29.24689Z","shell.execute_reply":"2025-12-19T06:10:31.410444Z"},"editable":false},"outputs":[{"name":"stdout","text":"/kaggle/input/aptos2019-blindness-detection/sample_submission.csv\n/kaggle/input/aptos2019-blindness-detection/train.csv\n/kaggle/input/aptos2019-blindness-detection/test.csv\n","output_type":"stream"},{"traceback":["\u001b[0;31m---------------------------------------------------------------------------\u001b[0m","\u001b[0;31mKeyboardInterrupt\u001b[0m                         Traceback (most recent call last)","\u001b[0;32m/tmp/ipykernel_55/1229163428.py\u001b[0m in \u001b[0;36m<cell line: 0>\u001b[0;34m()\u001b[0m\n\u001b[1;32m     10\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m     11\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mos\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 12\u001b[0;31m \u001b[0;32mfor\u001b[0m \u001b[0mdirname\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0m_\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mfilenames\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mos\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mwalk\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'/kaggle/input'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m     13\u001b[0m     \u001b[0;32mfor\u001b[0m \u001b[0mfilename\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mfilenames\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m     14\u001b[0m         \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mos\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mpath\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mjoin\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mdirname\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mfilename\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n","\u001b[0;32m/usr/lib/python3.12/os.py\u001b[0m in \u001b[0;36mwalk\u001b[0;34m(top, topdown, onerror, followlinks)\u001b[0m\n","\u001b[0;31mKeyboardInterrupt\u001b[0m: "],"ename":"KeyboardInterrupt","evalue":"","output_type":"error"}],"execution_count":14},{"cell_type":"code","source":"import os\nimport warnings\nwarnings.filterwarnings('ignore')\n\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\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import models, transforms\n\nfrom sklearn.model_selection import train_test_split, StratifiedKFold\nfrom sklearn.metrics import (accuracy_score, precision_score, recall_score, \n                             f1_score, confusion_matrix, classification_report, roc_auc_score)\nfrom tqdm import tqdm\n\n# Device setup\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nprint(f\"✓ Using device: {device}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-19T06:10:31.41256Z","iopub.status.idle":"2025-12-19T06:10:31.412868Z","shell.execute_reply.started":"2025-12-19T06:10:31.412729Z","shell.execute_reply":"2025-12-19T06:10:31.412753Z"},"editable":false},"outputs":[],"execution_count":null},{"cell_type":"code","source":"CSV_PATH = \"/kaggle/input/aptos2019-blindness-detection/train.csv\"\nIMG_DIR = \"/kaggle/input/aptos2019-blindness-detection/train_images\"","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-19T06:10:31.413952Z","iopub.status.idle":"2025-12-19T06:10:31.414249Z","shell.execute_reply.started":"2025-12-19T06:10:31.41409Z","shell.execute_reply":"2025-12-19T06:10:31.414105Z"},"editable":false},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df = pd.read_csv(CSV_PATH)\nprint(f\"✓ Loaded {len(df)} images\")\nprint(f\"✓ Columns: {df.columns.tolist()}\")\n\n# Display class distribution\nprint(\"\\n📊 Class Distribution:\")\nprint(df[\"diagnosis\"].value_counts().sort_index())\nclass_weights = 1.0 / df[\"diagnosis\"].value_counts(normalize=True)\nclass_weights = class_weights / class_weights.sum() * 5\nprint(f\"\\n⚖️ Class Weights (for imbalance handling):\\n{class_weights}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-19T06:10:31.41555Z","iopub.status.idle":"2025-12-19T06:10:31.415913Z","shell.execute_reply.started":"2025-12-19T06:10:31.415743Z","shell.execute_reply":"2025-12-19T06:10:31.415763Z"},"editable":false},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Visualize sample images\nfig, axes = plt.subplots(2, 5, figsize=(15, 6))\nfor i, ax in enumerate(axes.flatten()):\n    idx = df.index[i]\n    img_id = df.loc[idx, \"id_code\"]\n    diagnosis = df.loc[idx, \"diagnosis\"]\n    img_path = os.path.join(IMG_DIR, img_id + \".png\")\n    \n    img = Image.open(img_path)\n    ax.imshow(img)\n    ax.set_title(f\"Grade {diagnosis}\")\n    ax.axis('off')\n\nplt.suptitle(\"Sample Retinal Images from APTOS 2019\")\nplt.tight_layout()\nplt.savefig(\"/kaggle/working/sample_images.png\", dpi=100, bbox_inches='tight')\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-19T06:10:31.416975Z","iopub.status.idle":"2025-12-19T06:10:31.417302Z","shell.execute_reply.started":"2025-12-19T06:10:31.417139Z","shell.execute_reply":"2025-12-19T06:10:31.417159Z"},"editable":false},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":" DATA PREPROCESSING & AUGMENTATION","metadata":{"editable":false}},{"cell_type":"code","source":"TRAIN_TRANSFORMS = transforms.Compose([\n    # Resize to fixed size\n    transforms.Resize((224, 224)),\n    \n    # Data augmentation\n    transforms.RandomHorizontalFlip(p=0.5),\n    transforms.RandomVerticalFlip(p=0.5),\n    transforms.RandomRotation(degrees=20),\n    transforms.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.1),\n    transforms.GaussianBlur(kernel_size=3, sigma=(0.1, 2.0)),\n    \n    # Convert to tensor and normalize\n    transforms.ToTensor(),\n    transforms.Normalize(\n        mean=[0.485, 0.456, 0.406],\n        std=[0.229, 0.224, 0.225]\n    ),\n])\n\nVAL_TRANSFORMS = transforms.Compose([\n    transforms.Resize((224, 224)),\n    transforms.ToTensor(),\n    transforms.Normalize(\n        mean=[0.485, 0.456, 0.406],\n        std=[0.229, 0.224, 0.225]\n    ),\n])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-19T06:10:31.41818Z","iopub.status.idle":"2025-12-19T06:10:31.418413Z","shell.execute_reply.started":"2025-12-19T06:10:31.418301Z","shell.execute_reply":"2025-12-19T06:10:31.418313Z"},"editable":false},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"DATASET CLASS","metadata":{"editable":false}},{"cell_type":"code","source":"class APTOSDataset(Dataset):\n    \"\"\"\n    Custom Dataset for APTOS 2019 DR images\n    Handles:\n    - Image loading from paths\n    - Preprocessing and augmentation\n    - Label mapping\n    \"\"\"\n    def __init__(self, df, img_dir, transform=None, num_classes=5):\n        self.df = df.reset_index(drop=True)\n        self.img_dir = img_dir\n        self.transform = transform\n        self.num_classes = num_classes\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        row = self.df.iloc[idx]\n        img_id = row[\"id_code\"]\n        img_path = os.path.join(self.img_dir, img_id + \".png\")\n\n        # Load image\n        image = Image.open(img_path).convert(\"RGB\")\n\n        # Apply transforms\n        if self.transform:\n            image = self.transform(image)\n\n        # Get label (0-4 for 5-class task)\n        label = row[\"diagnosis\"]\n\n        return image, torch.tensor(label, dtype=torch.long)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-19T06:10:31.41925Z","iopub.status.idle":"2025-12-19T06:10:31.419533Z","shell.execute_reply.started":"2025-12-19T06:10:31.419418Z","shell.execute_reply":"2025-12-19T06:10:31.419432Z"},"editable":false},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"STRATIFIED DATA SPLIT","metadata":{"editable":false}},{"cell_type":"code","source":"train_df, val_df = train_test_split(\n    df,\n    test_size=0.2,\n    stratify=df[\"diagnosis\"],\n    random_state=42\n)\n\nprint(f\"\\n✓ Train set: {len(train_df)} images\")\nprint(f\"✓ Val set: {len(val_df)} images\")\n\n# Create datasets\ntrain_dataset = APTOSDataset(train_df, IMG_DIR, transform=TRAIN_TRANSFORMS, num_classes=5)\nval_dataset = APTOSDataset(val_df, IMG_DIR, transform=VAL_TRANSFORMS, num_classes=5)\n\n# Create dataloaders\nBATCH_SIZE = 32\ntrain_loader = DataLoader(\n    train_dataset,\n    batch_size=BATCH_SIZE,\n    shuffle=True,\n    num_workers=2,\n    pin_memory=True\n)\n\nval_loader = DataLoader(\n    val_dataset,\n    batch_size=BATCH_SIZE,\n    shuffle=False,\n    num_workers=2,\n    pin_memory=True\n)\n\nprint(f\"✓ Train batches: {len(train_loader)}, Val batches: {len(val_loader)}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-19T06:10:31.420378Z","iopub.status.idle":"2025-12-19T06:10:31.420619Z","shell.execute_reply.started":"2025-12-19T06:10:31.420509Z","shell.execute_reply":"2025-12-19T06:10:31.420523Z"},"editable":false},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"MODEL DEFINITION - VGGNet for 5-Class DR","metadata":{"editable":false}},{"cell_type":"code","source":"","metadata":{"trusted":true,"editable":false},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"VGGNet Model","metadata":{"editable":false}},{"cell_type":"code","source":"class VGGNet5ClassDR(nn.Module):\n    \"\"\"\n    VGGNet for 5-class DR severity grading\n    Pre-trained on ImageNet, fine-tuned on APTOS\n    \"\"\"\n    def __init__(self, num_classes=5, freeze_backbone=True):\n        super(VGGNet5ClassDR, self).__init__()\n        \n        # Load pre-trained VGG19\n        self.backbone = models.vgg19(weights=models.VGG19_Weights.IMAGENET1K_V1)\n        \n        # Freeze early layers if specified\n        if freeze_backbone:\n            for param in list(self.backbone.features.parameters())[:-10]:\n                param.requires_grad = False\n        \n        # Replace classifier head\n        in_features = self.backbone.classifier[0].in_features\n        self.backbone.classifier = nn.Sequential(\n            nn.Linear(in_features, 2048),\n            nn.ReLU(inplace=True),\n            nn.Dropout(0.5),\n            nn.Linear(2048, 1024),\n            nn.ReLU(inplace=True),\n            nn.Dropout(0.5),\n            nn.Linear(1024, num_classes)\n        )\n    \n    def forward(self, x):\n        return self.backbone(x)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-19T06:10:31.427151Z","iopub.status.idle":"2025-12-19T06:10:31.427491Z","shell.execute_reply.started":"2025-12-19T06:10:31.427315Z","shell.execute_reply":"2025-12-19T06:10:31.427338Z"},"editable":false},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"MobileNet","metadata":{"editable":false}},{"cell_type":"code","source":"class MobileNetV3_5ClassDR(nn.Module):\n    \"\"\"\n    MobileNetV3 for 5-class DR (lighter, mobile-friendly)\n    Pre-trained on ImageNet, fine-tuned on APTOS\n    \"\"\"\n    def __init__(self, num_classes=5, freeze_backbone=True):\n        super(MobileNetV3_5ClassDR, self).__init__()\n        \n        # Load pre-trained MobileNetV3\n        self.backbone = models.mobilenet_v3_large(\n            weights=models.MobileNet_V3_Large_Weights.IMAGENET1K_V1\n        )\n        \n        # Freeze early layers if specified\n        if freeze_backbone:\n            for param in list(self.backbone.features.parameters())[:-20]:\n                param.requires_grad = False\n        \n        # Replace classifier\n        in_features = self.backbone.classifier[0].in_features\n        self.backbone.classifier = nn.Sequential(\n            nn.Linear(in_features, 512),\n            nn.ReLU(inplace=True),\n            nn.Dropout(0.3),\n            nn.Linear(512, 256),\n            nn.ReLU(inplace=True),\n            nn.Dropout(0.3),\n            nn.Linear(256, num_classes)\n        )\n    \n    def forward(self, x):\n        return self.backbone(x)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-19T06:10:31.42882Z","iopub.status.idle":"2025-12-19T06:10:31.429045Z","shell.execute_reply.started":"2025-12-19T06:10:31.428939Z","shell.execute_reply":"2025-12-19T06:10:31.428952Z"},"editable":false},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"TRAINING FUNCTION WITH CLASS WEIGHTING","metadata":{"editable":false}},{"cell_type":"code","source":"def train_epoch(model, train_loader, criterion, optimizer, device):\n    \"\"\"Train for one epoch\"\"\"\n    model.train()\n    total_loss = 0.0\n    all_preds = []\n    all_labels = []\n    \n    pbar = tqdm(train_loader, desc=\"Training\")\n    for images, labels in pbar:\n        images, labels = images.to(device), labels.to(device)\n        \n        # Forward pass\n        outputs = model(images)\n        loss = criterion(outputs, labels)\n        \n        # Backward pass\n        optimizer.zero_grad()\n        loss.backward()\n        torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)\n        optimizer.step()\n        \n        # Track metrics\n        total_loss += loss.item()\n        preds = torch.argmax(outputs, dim=1)\n        all_preds.extend(preds.cpu().numpy())\n        all_labels.extend(labels.cpu().numpy())\n        \n        pbar.set_postfix({'loss': total_loss / (pbar.n + 1)})\n    \n    avg_loss = total_loss / len(train_loader)\n    train_acc = accuracy_score(all_labels, all_preds)\n    \n    return avg_loss, train_acc\n\n\ndef validate(model, val_loader, criterion, device):\n    \"\"\"Validate the model\"\"\"\n    model.eval()\n    total_loss = 0.0\n    all_preds = []\n    all_labels = []\n    \n    with torch.no_grad():\n        pbar = tqdm(val_loader, desc=\"Validating\")\n        for images, labels in pbar:\n            images, labels = images.to(device), labels.to(device)\n            \n            outputs = model(images)\n            loss = criterion(outputs, labels)\n            \n            total_loss += loss.item()\n            preds = torch.argmax(outputs, dim=1)\n            all_preds.extend(preds.cpu().numpy())\n            all_labels.extend(labels.cpu().numpy())\n    \n    avg_loss = total_loss / len(val_loader)\n    val_acc = accuracy_score(all_labels, all_preds)\n    \n    return avg_loss, val_acc, all_preds, all_labels\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-19T06:10:31.430345Z","iopub.status.idle":"2025-12-19T06:10:31.430976Z","shell.execute_reply.started":"2025-12-19T06:10:31.430789Z","shell.execute_reply":"2025-12-19T06:10:31.430813Z"},"editable":false},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"TRAINING PIPELINE WITH EARLY STOPPING & BEST MODEL SAVING","metadata":{"editable":false}},{"cell_type":"code","source":"def train_model(model, train_loader, val_loader, num_epochs=50, model_name=\"model\"):\n    \"\"\"\n    Complete training loop with:\n    - Class-weighted loss for imbalance handling\n    - Learning rate scheduling\n    - Early stopping\n    - Best model saving\n    \"\"\"\n    \n    # Loss function with class weights\n    class_weights_tensor = torch.tensor([1.0, 2.0, 3.0, 4.0, 2.5], dtype=torch.float).to(device)\n    criterion = nn.CrossEntropyLoss(weight=class_weights_tensor)\n    \n    # Optimizer & scheduler\n    optimizer = torch.optim.Adam(model.parameters(), lr=1e-4, weight_decay=1e-5)\n    scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=5, gamma=0.5)\n    \n    # Training history\n    history = {\n        'train_loss': [],\n        'val_loss': [],\n        'train_acc': [],\n        'val_acc': []\n    }\n    \n    best_val_acc = 0\n    patience = 15\n    patience_counter = 0\n    \n    print(f\"\\n{'='*70}\")\n    print(f\"Training {model_name} for {num_epochs} epochs\")\n    print(f\"{'='*70}\")\n    \n    for epoch in range(num_epochs):\n        print(f\"\\nEpoch {epoch+1}/{num_epochs}\")\n        \n        # Train\n        train_loss, train_acc = train_epoch(model, train_loader, criterion, optimizer, device)\n        \n        # Validate\n        val_loss, val_acc, val_preds, val_labels = validate(model, val_loader, criterion, device)\n        \n        # Record history\n        history['train_loss'].append(train_loss)\n        history['val_loss'].append(val_loss)\n        history['train_acc'].append(train_acc)\n        history['val_acc'].append(val_acc)\n        \n        print(f\"Train Loss: {train_loss:.4f} | Train Acc: {train_acc:.4f}\")\n        print(f\"Val Loss: {val_loss:.4f} | Val Acc: {val_acc:.4f}\")\n        \n        # Save best model\n        if val_acc > best_val_acc:\n            best_val_acc = val_acc\n            patience_counter = 0\n            torch.save(model.state_dict(), f\"/kaggle/working/{model_name}_best.pth\")\n            print(f\"✓ Best model saved! Val Acc: {val_acc:.4f}\")\n        else:\n            patience_counter += 1\n        \n        # Early stopping\n        if patience_counter >= patience:\n            print(f\"\\n⚠️ Early stopping! No improvement for {patience} epochs\")\n            break\n        \n        scheduler.step()\n    \n    return history, best_val_acc","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-19T06:10:31.432038Z","iopub.status.idle":"2025-12-19T06:10:31.432543Z","shell.execute_reply.started":"2025-12-19T06:10:31.432355Z","shell.execute_reply":"2025-12-19T06:10:31.432376Z"},"editable":false},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"TRAIN VGGNet MODEL","metadata":{"editable":false}},{"cell_type":"code","source":"print(\"\\n\" + \"=\"*70)\nprint(\"TRAINING VGGNet\")\nprint(\"=\"*70)\n\nvggnet_model = VGGNet5ClassDR(num_classes=5, freeze_backbone=True).to(device)\nvggnet_history, vggnet_best_acc = train_model(\n    vggnet_model, \n    train_loader, \n    val_loader, \n    num_epochs=30,\n    model_name=\"vggnet\"\n)","metadata":{"trusted":true,"editable":false},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"","metadata":{"editable":false}},{"cell_type":"markdown","source":"","metadata":{"editable":false}},{"cell_type":"code","source":"","metadata":{"trusted":true,"editable":false},"outputs":[],"execution_count":null}]}