{"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":[{"sourceType":"competition","sourceId":14774,"databundleVersionId":875431}],"dockerImageVersionId":31328,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\nimport shutil\nimport pandas as pd\nimport numpy as np\nimport cv2\n\nimport matplotlib.pyplot as plt\nfrom collections import Counter\n\nfrom torchvision import datasets, transforms\nfrom tqdm import tqdm","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-28T15:48:42.709107Z","iopub.execute_input":"2026-04-28T15:48:42.709376Z","iopub.status.idle":"2026-04-28T15:48:56.724788Z","shell.execute_reply.started":"2026-04-28T15:48:42.709347Z","shell.execute_reply":"2026-04-28T15:48:56.723984Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"\n* #  Load image2. Convert BGR → RGB3. 🔥 CROP (remove black background)4. Resize to 224×2245. CLAHE enhancement6. Blur7. Save image\n\n* RAW\n ↓\nPREPROCESS (only once)\n ↓\nORGANIZED (from processed images)\n ↓\nTRAIN\n","metadata":{}},{"cell_type":"markdown","source":"# PATHS","metadata":{}},{"cell_type":"code","source":"base_dir = \"/kaggle/input/competitions/aptos2019-blindness-detection\"\n\ncsv_path = os.path.join(base_dir, \"train.csv\")\nimages_dir = os.path.join(base_dir, \"train_images\")\n\nprocessed_dir = \"/kaggle/working/preprocessed_data\"\norganized_dir = \"/kaggle/working/organized_dataset\"","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-28T15:48:56.726872Z","iopub.execute_input":"2026-04-28T15:48:56.727233Z","iopub.status.idle":"2026-04-28T15:48:56.731702Z","shell.execute_reply.started":"2026-04-28T15:48:56.727206Z","shell.execute_reply":"2026-04-28T15:48:56.730612Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# LOAD LABELS","metadata":{}},{"cell_type":"code","source":"df = pd.read_csv(csv_path)\nprint(df.head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-28T11:21:03.900763Z","iopub.execute_input":"2026-04-28T11:21:03.900987Z","iopub.status.idle":"2026-04-28T11:21:03.979351Z","shell.execute_reply.started":"2026-04-28T11:21:03.900964Z","shell.execute_reply":"2026-04-28T11:21:03.978728Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Class distribution","metadata":{}},{"cell_type":"code","source":"import os\n\nbase_dir = \"/kaggle/input/competitions/aptos2019-blindness-detection\"\n\ncsv_path = os.path.join(base_dir, \"train.csv\")\nimages_dir = os.path.join(base_dir, \"train_images\")\n\nprocessed_dir = \"/kaggle/working/preprocessed_data\"\norganized_dir = \"/kaggle/working/organized_dataset\"","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-28T15:48:56.732723Z","iopub.execute_input":"2026-04-28T15:48:56.733060Z","iopub.status.idle":"2026-04-28T15:48:56.754591Z","shell.execute_reply.started":"2026-04-28T15:48:56.733034Z","shell.execute_reply":"2026-04-28T15:48:56.753891Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nimport matplotlib.pyplot as plt\n\ndf = pd.read_csv(csv_path)\n\n# Count classes\nclass_counts = df['diagnosis'].value_counts().sort_index()\n\nprint(class_counts)\n\n# Plot\nplt.figure()\nplt.bar(class_counts.index.astype(str), class_counts.values)\nplt.title(\"Class Distribution (APTOS)\")\nplt.xlabel(\"Class\")\nplt.ylabel(\"Number of Images\")\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-28T15:49:21.719963Z","iopub.execute_input":"2026-04-28T15:49:21.720262Z","iopub.status.idle":"2026-04-28T15:49:21.976111Z","shell.execute_reply.started":"2026-04-28T15:49:21.720236Z","shell.execute_reply":"2026-04-28T15:49:21.975475Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Random images BEFORE preprocessing (raw images)","metadata":{}},{"cell_type":"code","source":"import random\nimport os\nfrom PIL import Image\nimport matplotlib.pyplot as plt\n\ndef show_raw_images(df, images_dir, num_images=6):\n    plt.figure(figsize=(12, 6))\n\n    samples = df.sample(num_images)\n\n    for i, (_, row) in enumerate(samples.iterrows()):\n        img_name = row['id_code'] + \".png\"\n        label = row['diagnosis']\n\n        img_path = os.path.join(images_dir, img_name)\n        img = Image.open(img_path)\n\n        plt.subplot(2, 3, i+1)\n        plt.imshow(img)\n        plt.title(f\"Class {label}\")\n        plt.axis('off')\n\n    plt.tight_layout()\n    plt.show()\n\nshow_raw_images(df, images_dir)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-28T15:49:25.998047Z","iopub.execute_input":"2026-04-28T15:49:25.998363Z","iopub.status.idle":"2026-04-28T15:49:29.025578Z","shell.execute_reply.started":"2026-04-28T15:49:25.998335Z","shell.execute_reply":"2026-04-28T15:49:29.024547Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# PREPROCESSING","metadata":{}},{"cell_type":"code","source":"def crop_image(img):\n    gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)\n    _, thresh = cv2.threshold(gray, 10, 255, cv2.THRESH_BINARY)\n    contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)\n\n    if len(contours) == 0:\n        return img\n\n    cnt = max(contours, key=cv2.contourArea)\n    x, y, w, h = cv2.boundingRect(cnt)\n\n    return img[y:y+h, x:x+w]\n\n\ndef preprocess_image(image_path):\n    image = cv2.imread(image_path)\n    image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n\n    image_cropped = crop_image(image_rgb)\n    image_resized = cv2.resize(image_cropped, (224, 224))\n\n    clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))\n    channels = cv2.split(image_resized)\n    clahe_channels = [clahe.apply(c) for c in channels]\n    image_clahe = cv2.merge(clahe_channels)\n\n    image_blur = cv2.GaussianBlur(image_clahe, (5, 5), 0)\n\n    return image_blur","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-28T15:49:36.055555Z","iopub.execute_input":"2026-04-28T15:49:36.055886Z","iopub.status.idle":"2026-04-28T15:49:36.062514Z","shell.execute_reply.started":"2026-04-28T15:49:36.055857Z","shell.execute_reply":"2026-04-28T15:49:36.061871Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# STEP 1 — CREATE PREPROCESSED IMAGES","metadata":{}},{"cell_type":"code","source":"shutil.rmtree(processed_dir, ignore_errors=True)\nos.makedirs(processed_dir, exist_ok=True)\n\nfor _, row in tqdm(df.iterrows(), total=len(df)):\n    img_id = row[\"id_code\"]\n    label = str(row[\"diagnosis\"])\n\n    img_path = os.path.join(images_dir, img_id + \".png\")\n\n    if not os.path.exists(img_path):\n        continue\n\n    processed = preprocess_image(img_path)\n\n    class_folder = os.path.join(processed_dir, label)\n    os.makedirs(class_folder, exist_ok=True)\n\n    save_path = os.path.join(class_folder, img_id + \".png\")\n\n    cv2.imwrite(save_path, cv2.cvtColor(processed, cv2.COLOR_RGB2BGR))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-28T15:49:40.384977Z","iopub.execute_input":"2026-04-28T15:49:40.385309Z","iopub.status.idle":"2026-04-28T15:56:49.141184Z","shell.execute_reply.started":"2026-04-28T15:49:40.385278Z","shell.execute_reply":"2026-04-28T15:56:49.140360Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\n\nfor cls in os.listdir(processed_dir):\n    class_path = os.path.join(processed_dir, cls)\n    print(cls, len(os.listdir(class_path)))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-28T15:57:17.460587Z","iopub.execute_input":"2026-04-28T15:57:17.460901Z","iopub.status.idle":"2026-04-28T15:57:17.468106Z","shell.execute_reply.started":"2026-04-28T15:57:17.460873Z","shell.execute_reply":"2026-04-28T15:57:17.467546Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# STEP 2 — ORGANIZE","metadata":{}},{"cell_type":"code","source":"shutil.rmtree(organized_dir, ignore_errors=True)\n\nfor cls in os.listdir(processed_dir):\n    src = os.path.join(processed_dir, cls)\n    dst = os.path.join(organized_dir, cls)\n\n    os.makedirs(dst, exist_ok=True)\n\n    for img in os.listdir(src):\n        shutil.copy(os.path.join(src, img), os.path.join(dst, img))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-28T15:58:12.932755Z","iopub.execute_input":"2026-04-28T15:58:12.933509Z","iopub.status.idle":"2026-04-28T15:58:13.488143Z","shell.execute_reply.started":"2026-04-28T15:58:12.933479Z","shell.execute_reply":"2026-04-28T15:58:13.487553Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# CHECK CLASS DISTRIBUTION","metadata":{}},{"cell_type":"code","source":"for c in os.listdir(organized_dir):\n    print(c, len(os.listdir(os.path.join(organized_dir, c))))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-28T15:58:17.656738Z","iopub.execute_input":"2026-04-28T15:58:17.657438Z","iopub.status.idle":"2026-04-28T15:58:17.664101Z","shell.execute_reply.started":"2026-04-28T15:58:17.657409Z","shell.execute_reply":"2026-04-28T15:58:17.663529Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# LOAD FINAL DATASET (READY FOR TRAINING)","metadata":{}},{"cell_type":"code","source":"from torchvision import datasets, transforms\n\ntrain_transform = transforms.Compose([\n    transforms.Resize((224, 224)),\n    transforms.RandomHorizontalFlip(),\n    transforms.RandomRotation(10),\n    transforms.ToTensor(),\n    transforms.Normalize([0.485, 0.456, 0.406],\n                         [0.229, 0.224, 0.225])\n])\n\ndataset = datasets.ImageFolder(\n    root=organized_dir,\n    transform=train_transform\n)\n\nprint(\"Classes:\", dataset.classes)\nprint(\"Total images:\", len(dataset))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-28T15:58:22.008421Z","iopub.execute_input":"2026-04-28T15:58:22.008950Z","iopub.status.idle":"2026-04-28T15:58:22.024660Z","shell.execute_reply.started":"2026-04-28T15:58:22.008920Z","shell.execute_reply":"2026-04-28T15:58:22.023879Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"organized_dataset → ImageFolder → full_dataset","metadata":{}},{"cell_type":"code","source":"import os\nimport random\nimport matplotlib.pyplot as plt\nimport cv2\n\nprocessed_dir = \"/kaggle/working/preprocessed_data\"\n\ndef show_preprocessed_images(processed_dir, num_images=6):\n\n    # check folder\n    if not os.path.exists(processed_dir):\n        print(\"❌ Folder not found:\", processed_dir)\n        return\n\n    classes = os.listdir(processed_dir)\n\n    plt.figure(figsize=(12, 6))\n\n    for i in range(num_images):\n\n        # pick random class\n        class_name = random.choice(classes)\n        class_path = os.path.join(processed_dir, class_name)\n\n        if not os.path.isdir(class_path):\n            continue\n\n        # pick random image\n        img_name = random.choice(os.listdir(class_path))\n        img_path = os.path.join(class_path, img_name)\n\n        # read image\n        img = cv2.imread(img_path)\n        img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n\n        # display\n        plt.subplot(2, 3, i + 1)\n        plt.imshow(img)\n        plt.title(f\"Class {class_name}\")\n        plt.axis(\"off\")\n\n    plt.tight_layout()\n    plt.show()\n\n\n# ▶ run\nshow_preprocessed_images(processed_dir)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-28T16:00:30.753100Z","iopub.execute_input":"2026-04-28T16:00:30.753811Z","iopub.status.idle":"2026-04-28T16:00:31.327678Z","shell.execute_reply.started":"2026-04-28T16:00:30.753775Z","shell.execute_reply":"2026-04-28T16:00:31.326799Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# sanity check dataset","metadata":{}},{"cell_type":"code","source":"img, label = dataset[0]\nprint(img.shape, label)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-28T11:28:18.826699Z","iopub.execute_input":"2026-04-28T11:28:18.827447Z","iopub.status.idle":"2026-04-28T11:28:18.987457Z","shell.execute_reply.started":"2026-04-28T11:28:18.827423Z","shell.execute_reply":"2026-04-28T11:28:18.986738Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"So this confirms:\n\n👉 data is being loaded correctly\n👉 transforms are applied correctly\n👉 labels are mapped correctly","metadata":{}},{"cell_type":"code","source":"print(dataset.classes)\nprint(dataset.class_to_idx)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-28T11:28:18.988522Z","iopub.execute_input":"2026-04-28T11:28:18.988931Z","iopub.status.idle":"2026-04-28T11:28:18.992634Z","shell.execute_reply.started":"2026-04-28T11:28:18.988896Z","shell.execute_reply":"2026-04-28T11:28:18.991947Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# check class distribution","metadata":{}},{"cell_type":"code","source":"from collections import Counter\n\nlabels = [dataset.targets[i] for i in range(len(dataset))]\nprint(Counter(labels))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-28T11:28:18.993407Z","iopub.execute_input":"2026-04-28T11:28:18.993745Z","iopub.status.idle":"2026-04-28T11:28:19.010939Z","shell.execute_reply.started":"2026-04-28T11:28:18.993720Z","shell.execute_reply":"2026-04-28T11:28:19.010061Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# proper train/val split","metadata":{}},{"cell_type":"code","source":"from torch.utils.data import random_split\n\ntrain_size = int(0.8 * len(dataset))\nval_size = len(dataset) - train_size\n\ntrain_dataset, val_dataset = random_split(dataset, [train_size, val_size])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-28T11:28:19.012032Z","iopub.execute_input":"2026-04-28T11:28:19.012378Z","iopub.status.idle":"2026-04-28T11:28:19.031765Z","shell.execute_reply.started":"2026-04-28T11:28:19.012355Z","shell.execute_reply":"2026-04-28T11:28:19.031209Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# quick dataloader test","metadata":{}},{"cell_type":"code","source":"from torch.utils.data import DataLoader\n\ntrain_loader = DataLoader(train_dataset, batch_size=16, shuffle=True)\n\nimages, labels = next(iter(train_loader))\n\nprint(images.shape)\nprint(labels)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-28T11:28:19.032594Z","iopub.execute_input":"2026-04-28T11:28:19.032987Z","iopub.status.idle":"2026-04-28T11:28:19.113523Z","shell.execute_reply.started":"2026-04-28T11:28:19.032965Z","shell.execute_reply":"2026-04-28T11:28:19.112686Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# MODEL + LOSS + OPTIMIZER resnet18(pretrained)","metadata":{}},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nimport torchvision.models as models\n\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\n# Simple model (good baseline for APTOS)\nmodel = models.resnet18(pretrained=True)\nmodel.fc = nn.Linear(model.fc.in_features, 5)  # 5 classes (APTOS)\n\nmodel = model.to(device)\n\ncriterion = nn.CrossEntropyLoss()\noptimizer = torch.optim.Adam(model.parameters(), lr=1e-4)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-28T11:28:19.114652Z","iopub.execute_input":"2026-04-28T11:28:19.115276Z","iopub.status.idle":"2026-04-28T11:28:19.784865Z","shell.execute_reply.started":"2026-04-28T11:28:19.115250Z","shell.execute_reply":"2026-04-28T11:28:19.784254Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# DATA LOADERS","metadata":{}},{"cell_type":"code","source":"from torch.utils.data import DataLoader, random_split\n\ntrain_size = int(0.8 * len(dataset))\nval_size = len(dataset) - train_size\n\ntrain_dataset, val_dataset = random_split(dataset, [train_size, val_size])\n\ntrain_loader = DataLoader(train_dataset, batch_size=16, shuffle=True)\nval_loader = DataLoader(val_dataset, batch_size=16, shuffle=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-28T11:28:19.785814Z","iopub.execute_input":"2026-04-28T11:28:19.786106Z","iopub.status.idle":"2026-04-28T11:28:19.791784Z","shell.execute_reply.started":"2026-04-28T11:28:19.786072Z","shell.execute_reply":"2026-04-28T11:28:19.791123Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# TRAINING LOOP (1 EPOCH TEST)","metadata":{}},{"cell_type":"code","source":"for epoch in range(1):  # IMPORTANT: just 1 epoch for testing\n    model.train()\n    running_loss = 0\n\n    for images, labels in train_loader:\n        images, labels = images.to(device), labels.to(device)\n\n        optimizer.zero_grad()\n\n        outputs = model(images)\n        loss = criterion(outputs, labels)\n\n        loss.backward()\n        optimizer.step()\n\n        running_loss += loss.item()\n\n    print(f\"Epoch [{epoch+1}] Loss: {running_loss/len(train_loader):.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-28T11:28:19.792812Z","iopub.execute_input":"2026-04-28T11:28:19.793113Z","iopub.status.idle":"2026-04-28T11:28:38.484867Z","shell.execute_reply.started":"2026-04-28T11:28:19.793080Z","shell.execute_reply":"2026-04-28T11:28:38.484257Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# VALIDATION CHECK","metadata":{}},{"cell_type":"code","source":"model.eval()\ncorrect = 0\ntotal = 0\n\nwith torch.no_grad():\n    for images, labels in val_loader:\n        images, labels = images.to(device), labels.to(device)\n\n        outputs = model(images)\n        _, predicted = torch.max(outputs, 1)\n\n        total += labels.size(0)\n        correct += (predicted == labels).sum().item()\n\nprint(\"Validation Accuracy:\", 100 * correct / total)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-28T11:28:38.485950Z","iopub.execute_input":"2026-04-28T11:28:38.486291Z","iopub.status.idle":"2026-04-28T11:28:41.176624Z","shell.execute_reply.started":"2026-04-28T11:28:38.486267Z","shell.execute_reply":"2026-04-28T11:28:41.175973Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.metrics import confusion_matrix\nimport numpy as np\n\nall_preds = []\nall_labels = []\n\nmodel.eval()\nwith torch.no_grad():\n    for images, labels in val_loader:\n        images = images.to(device)\n        outputs = model(images)\n        _, preds = torch.max(outputs, 1)\n\n        all_preds.extend(preds.cpu().numpy())\n        all_labels.extend(labels.numpy())\n\nprint(confusion_matrix(all_labels, all_preds))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-28T11:28:41.177460Z","iopub.execute_input":"2026-04-28T11:28:41.177772Z","iopub.status.idle":"2026-04-28T11:28:44.911192Z","shell.execute_reply.started":"2026-04-28T11:28:41.177739Z","shell.execute_reply":"2026-04-28T11:28:44.910350Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":">         Predicted →\n        0    1    2    3    4\nTrue 0 [365   7    2    0    0]\n     1 [13   38   37    0    1]\n     2 [5    3   139   25    9]\n     3 [0    0    4   18   12]\n     4 [0    0   17   13   25]\n\n\n     365  ← correct class 0\n38   ← correct class 1\n139  ← correct class 2\n18   ← correct class 3\n25   ← correct class 4\n\n\n# strong on class 0 and 2weak on class 3 and 4\n","metadata":{}},{"cell_type":"markdown","source":"then we upgrade to:\n\nclass balancing\nbetter model (EfficientNet-B0/B3)\nlonger training\ncheckpoint saving","metadata":{}},{"cell_type":"markdown","source":"# Save the model","metadata":{}},{"cell_type":"code","source":"torch.save(model.state_dict(), \"/kaggle/working/baseline_resnet18.pth\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-28T11:28:44.912273Z","iopub.execute_input":"2026-04-28T11:28:44.912850Z","iopub.status.idle":"2026-04-28T11:28:44.987697Z","shell.execute_reply.started":"2026-04-28T11:28:44.912820Z","shell.execute_reply":"2026-04-28T11:28:44.987069Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# #  ============================\n# EXPERIMENT 2 - CLASS BALANCING\n# ============================","metadata":{}},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 1. 1. **Weighted Random Sampling for imbalanced datasets**","metadata":{}},{"cell_type":"code","source":"from torchvision import datasets\n\ndataset_path = \"/kaggle/working/organized_dataset\"\n\n# -------------------------\n# CHECK IF DATASET EXISTS\n# -------------------------\nif \"dataset\" in globals():\n    print(\"✅ dataset already exists in memory\")\n    print(\"Type:\", type(dataset))\n    print(\"Size:\", len(dataset))\nelse:\n    print(\"⚠ dataset not in memory. Creating it now...\")\n\n    dataset = datasets.ImageFolder(\n        root=dataset_path\n    )\n\n    print(\"✅ dataset created successfully\")\n    print(\"Size:\", len(dataset))\n    print(\"Classes:\", dataset.classes)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-28T11:28:44.988582Z","iopub.execute_input":"2026-04-28T11:28:44.988944Z","iopub.status.idle":"2026-04-28T11:28:44.995303Z","shell.execute_reply.started":"2026-04-28T11:28:44.988904Z","shell.execute_reply":"2026-04-28T11:28:44.994584Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Train/Validation split","metadata":{}},{"cell_type":"code","source":"from torch.utils.data import random_split\n\ntrain_size = int(0.8 * len(dataset))\nval_size = len(dataset) - train_size\n\ntrain_dataset, val_dataset = random_split(\n    dataset,\n    [train_size, val_size],\n    generator=torch.Generator().manual_seed(42)\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-28T11:28:44.996242Z","iopub.execute_input":"2026-04-28T11:28:44.996585Z","iopub.status.idle":"2026-04-28T11:28:45.009945Z","shell.execute_reply.started":"2026-04-28T11:28:44.996561Z","shell.execute_reply":"2026-04-28T11:28:45.009422Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# CLASS BALANCING","metadata":{}},{"cell_type":"markdown","source":"","metadata":{}},{"cell_type":"code","source":"from torch.utils.data import WeightedRandomSampler\nfrom collections import Counter\nimport numpy as np\n\ntrain_labels = [train_dataset.dataset.targets[i] for i in train_dataset.indices]\n\nclass_counts = Counter(train_labels)\nclass_weights = {c: 1.0 / count for c, count in class_counts.items()}\nsample_weights = [class_weights[label] for label in train_labels]\n\nsampler = WeightedRandomSampler(\n    weights=sample_weights,\n    num_samples=len(sample_weights),\n    replacement=True\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-28T11:28:45.010977Z","iopub.execute_input":"2026-04-28T11:28:45.011293Z","iopub.status.idle":"2026-04-28T11:28:45.034011Z","shell.execute_reply.started":"2026-04-28T11:28:45.011267Z","shell.execute_reply":"2026-04-28T11:28:45.033472Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"#  TRAIN LOADER","metadata":{}},{"cell_type":"code","source":"train_loader = DataLoader(\n    train_dataset,\n    batch_size=16,\n    sampler=sampler\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-28T11:28:45.034828Z","iopub.execute_input":"2026-04-28T11:28:45.035106Z","iopub.status.idle":"2026-04-28T11:28:45.051775Z","shell.execute_reply.started":"2026-04-28T11:28:45.035083Z","shell.execute_reply":"2026-04-28T11:28:45.050933Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# VALIDATION LOADER","metadata":{}},{"cell_type":"code","source":"val_loader = DataLoader(\n    val_dataset,\n    batch_size=16,\n    shuffle=False\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-28T11:28:45.052879Z","iopub.execute_input":"2026-04-28T11:28:45.053267Z","iopub.status.idle":"2026-04-28T11:28:45.066553Z","shell.execute_reply.started":"2026-04-28T11:28:45.053214Z","shell.execute_reply":"2026-04-28T11:28:45.065974Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"num_epochs = 5","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-28T11:28:45.067395Z","iopub.execute_input":"2026-04-28T11:28:45.067688Z","iopub.status.idle":"2026-04-28T11:28:45.081632Z","shell.execute_reply.started":"2026-04-28T11:28:45.067649Z","shell.execute_reply":"2026-04-28T11:28:45.080817Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Training loop","metadata":{}},{"cell_type":"code","source":"for epoch in range(num_epochs):\n    model.train()\n    running_loss = 0\n\n    for images, labels in train_loader:\n        images, labels = images.to(device), labels.to(device)\n\n        optimizer.zero_grad()\n\n        outputs = model(images)\n        loss = criterion(outputs, labels)\n\n        loss.backward()\n        optimizer.step()\n\n        running_loss += loss.item()\n\n    print(f\"Epoch {epoch+1} Loss: {running_loss/len(train_loader):.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-28T11:28:45.082443Z","iopub.execute_input":"2026-04-28T11:28:45.082699Z","iopub.status.idle":"2026-04-28T11:30:05.342263Z","shell.execute_reply.started":"2026-04-28T11:28:45.082662Z","shell.execute_reply":"2026-04-28T11:30:05.341415Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model.eval()\n\ncorrect = 0\ntotal = 0\n\nwith torch.no_grad():\n    for images, labels in val_loader:\n        images, labels = images.to(device), labels.to(device)\n\n        outputs = model(images)\n        _, preds = torch.max(outputs, 1)\n\n        correct += (preds == labels).sum().item()\n        total += labels.size(0)\n\nprint(\"Validation Accuracy:\", correct / total)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-28T11:30:05.347366Z","iopub.execute_input":"2026-04-28T11:30:05.347728Z","iopub.status.idle":"2026-04-28T11:30:07.885776Z","shell.execute_reply.started":"2026-04-28T11:30:05.347688Z","shell.execute_reply":"2026-04-28T11:30:07.885093Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.metrics import confusion_matrix\n\nall_preds = []\nall_labels = []\n\nmodel.eval()\nwith torch.no_grad():\n    for images, labels in val_loader:\n        images = images.to(device)\n\n        outputs = model(images)\n        _, preds = torch.max(outputs, 1)\n\n        all_preds.extend(preds.cpu().numpy())\n        all_labels.extend(labels.numpy())\n\nprint(confusion_matrix(all_labels, all_preds))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-28T11:30:07.886760Z","iopub.execute_input":"2026-04-28T11:30:07.887111Z","iopub.status.idle":"2026-04-28T11:30:10.498587Z","shell.execute_reply.started":"2026-04-28T11:30:07.887083Z","shell.execute_reply":"2026-04-28T11:30:10.497946Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"✔ strong improvement\n✔ model is actually detecting severe DR","metadata":{}},{"cell_type":"markdown","source":"# Class-weighted CrossEntropyLoss","metadata":{}},{"cell_type":"markdown","source":"# Compute class weights","metadata":{}},{"cell_type":"code","source":"from collections import Counter\nimport torch\n\ntrain_labels = [train_dataset.dataset.targets[i] for i in train_dataset.indices]\n\nclass_counts = Counter(train_labels)\nprint(\"Class counts:\", class_counts)\n\nnum_classes = len(class_counts)\ntotal_samples = sum(class_counts.values())\n\n# Compute weights (inverse frequency)\nclass_weights = [total_samples / (num_classes * class_counts[i]) for i in range(num_classes)]\n\nclass_weights = torch.tensor(class_weights, dtype=torch.float).to(device)\n\nprint(\"Class weights:\", class_weights)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-28T11:30:10.499461Z","iopub.execute_input":"2026-04-28T11:30:10.499771Z","iopub.status.idle":"2026-04-28T11:30:10.792617Z","shell.execute_reply.started":"2026-04-28T11:30:10.499745Z","shell.execute_reply":"2026-04-28T11:30:10.791965Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Class 0 → 0.4057  (lowest weight)\nClass 2 → 0.7359\nClass 1 → 1.9462\nClass 4 → 2.5470\nClass 3 → 3.7076  (highest weight)","metadata":{}},{"cell_type":"markdown","source":"# loss function ONLY","metadata":{}},{"cell_type":"code","source":"import torch.nn as nn\n\ncriterion = nn.CrossEntropyLoss(weight=class_weights)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-28T11:30:10.793526Z","iopub.execute_input":"2026-04-28T11:30:10.793814Z","iopub.status.idle":"2026-04-28T11:30:10.797692Z","shell.execute_reply.started":"2026-04-28T11:30:10.793783Z","shell.execute_reply":"2026-04-28T11:30:10.796927Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Retrain","metadata":{}},{"cell_type":"code","source":"num_epochs = 5  # same as before\n\nfor epoch in range(num_epochs):\n    model.train()\n    running_loss = 0\n\n    for images, labels in train_loader:\n        images, labels = images.to(device), labels.to(device)\n\n        optimizer.zero_grad()\n\n        outputs = model(images)\n        loss = criterion(outputs, labels)\n\n        loss.backward()\n        optimizer.step()\n\n        running_loss += loss.item()\n\n    print(f\"Epoch {epoch+1} Loss: {running_loss/len(train_loader):.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-28T11:30:10.798659Z","iopub.execute_input":"2026-04-28T11:30:10.798954Z","iopub.status.idle":"2026-04-28T11:31:30.823875Z","shell.execute_reply.started":"2026-04-28T11:30:10.798926Z","shell.execute_reply":"2026-04-28T11:31:30.823189Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Evaluate","metadata":{}},{"cell_type":"markdown","source":"accuracy:","metadata":{}},{"cell_type":"code","source":"model.eval()\ncorrect = 0\ntotal = 0\n\nwith torch.no_grad():\n    for images, labels in val_loader:\n        images, labels = images.to(device), labels.to(device)\n\n        outputs = model(images)\n        _, preds = torch.max(outputs, 1)\n\n        correct += (preds == labels).sum().item()\n        total += labels.size(0)\n\nprint(\"Validation Accuracy:\", correct / total)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-28T11:31:30.824850Z","iopub.execute_input":"2026-04-28T11:31:30.825253Z","iopub.status.idle":"2026-04-28T11:31:33.326948Z","shell.execute_reply.started":"2026-04-28T11:31:30.825225Z","shell.execute_reply":"2026-04-28T11:31:33.326222Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Confusion matrix:","metadata":{}},{"cell_type":"code","source":"from sklearn.metrics import confusion_matrix\n\nall_preds = []\nall_labels = []\n\nmodel.eval()\nwith torch.no_grad():\n    for images, labels in val_loader:\n        images = images.to(device)\n\n        outputs = model(images)\n        _, preds = torch.max(outputs, 1)\n\n        all_preds.extend(preds.cpu().numpy())\n        all_labels.extend(labels.numpy())\n\nprint(confusion_matrix(all_labels, all_preds))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-28T11:31:33.327900Z","iopub.execute_input":"2026-04-28T11:31:33.328280Z","iopub.status.idle":"2026-04-28T11:31:35.988887Z","shell.execute_reply.started":"2026-04-28T11:31:33.328252Z","shell.execute_reply":"2026-04-28T11:31:35.988230Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"class 4 detection\n✔ improved slightly","metadata":{}},{"cell_type":"markdown","source":"# RESNET50","metadata":{}},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nfrom torchvision import models\n\n# -------------------------\n# DEVICE\n# -------------------------\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-28T11:31:35.989879Z","iopub.execute_input":"2026-04-28T11:31:35.990241Z","iopub.status.idle":"2026-04-28T11:31:35.994503Z","shell.execute_reply.started":"2026-04-28T11:31:35.990213Z","shell.execute_reply":"2026-04-28T11:31:35.993707Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# MODEL (FROM SCRATCH)","metadata":{}},{"cell_type":"code","source":"# ResNet50 WITHOUT pretrained weights\nmodel = models.resnet50(weights=None)\n\n# Modify final layer for APTOS (5 classes)\nnum_classes = 5\nmodel.fc = nn.Linear(model.fc.in_features, num_classes)\n\n# Move to device\nmodel = model.to(device)\n\nprint(model)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-28T11:31:35.995413Z","iopub.execute_input":"2026-04-28T11:31:35.995742Z","iopub.status.idle":"2026-04-28T11:31:36.339680Z","shell.execute_reply.started":"2026-04-28T11:31:35.995708Z","shell.execute_reply":"2026-04-28T11:31:36.339046Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# LOSS FUNCTION","metadata":{}},{"cell_type":"code","source":"import torch.nn as nn\n\ncriterion = nn.CrossEntropyLoss()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-28T11:31:36.340528Z","iopub.execute_input":"2026-04-28T11:31:36.340913Z","iopub.status.idle":"2026-04-28T11:31:36.344658Z","shell.execute_reply.started":"2026-04-28T11:31:36.340885Z","shell.execute_reply":"2026-04-28T11:31:36.344074Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# OPTIMIZER","metadata":{}},{"cell_type":"code","source":"optimizer = torch.optim.Adam(model.parameters(), lr=1e-4)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-28T11:31:36.345522Z","iopub.execute_input":"2026-04-28T11:31:36.345886Z","iopub.status.idle":"2026-04-28T11:31:36.361049Z","shell.execute_reply.started":"2026-04-28T11:31:36.345863Z","shell.execute_reply":"2026-04-28T11:31:36.360366Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# TRAINING CONFIG","metadata":{}},{"cell_type":"code","source":"num_epochs = 40","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-28T11:58:59.132592Z","iopub.execute_input":"2026-04-28T11:58:59.132884Z","iopub.status.idle":"2026-04-28T11:58:59.136850Z","shell.execute_reply.started":"2026-04-28T11:58:59.132860Z","shell.execute_reply":"2026-04-28T11:58:59.135923Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# TRAINING LOOP","metadata":{}},{"cell_type":"code","source":"from torchvision import datasets, transforms\nfrom torch.utils.data import DataLoader","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-28T11:59:05.064592Z","iopub.execute_input":"2026-04-28T11:59:05.064907Z","iopub.status.idle":"2026-04-28T11:59:05.069040Z","shell.execute_reply.started":"2026-04-28T11:59:05.064880Z","shell.execute_reply":"2026-04-28T11:59:05.068319Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"transform = transforms.Compose([\n    transforms.Resize((224, 224)),\n    transforms.ToTensor()\n])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-28T11:59:08.615619Z","iopub.execute_input":"2026-04-28T11:59:08.616231Z","iopub.status.idle":"2026-04-28T11:59:08.619752Z","shell.execute_reply.started":"2026-04-28T11:59:08.616201Z","shell.execute_reply":"2026-04-28T11:59:08.619120Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\n\nprint(\"preprocessed exists:\", os.path.exists(\"/kaggle/working/preprocessed_data\"))\nprint(\"organized exists:\", os.path.exists(\"/kaggle/working/organized_dataset\"))\n\nprint(\"\\nPreprocessed content sample:\")\nprint(os.listdir(\"/kaggle/working/preprocessed_data\")[:10])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-28T11:59:12.064337Z","iopub.execute_input":"2026-04-28T11:59:12.064598Z","iopub.status.idle":"2026-04-28T11:59:12.069961Z","shell.execute_reply.started":"2026-04-28T11:59:12.064573Z","shell.execute_reply":"2026-04-28T11:59:12.069222Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"dataset = datasets.ImageFolder(\"/kaggle/working/preprocessed_data\", transform=transform)\n\ntrain_loader = DataLoader(dataset, batch_size=32, shuffle=True)\nval_loader = DataLoader(dataset, batch_size=32, shuffle=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-28T11:59:17.136077Z","iopub.execute_input":"2026-04-28T11:59:17.136750Z","iopub.status.idle":"2026-04-28T11:59:17.150850Z","shell.execute_reply.started":"2026-04-28T11:59:17.136717Z","shell.execute_reply":"2026-04-28T11:59:17.150196Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"for epoch in range(num_epochs):\n    model.train()\n    running_loss = 0\n\n    for images, labels in train_loader:\n        images, labels = images.to(device), labels.to(device)\n\n        optimizer.zero_grad()\n\n        outputs = model(images)\n        loss = criterion(outputs, labels)\n\n        loss.backward()\n        optimizer.step()\n\n        running_loss += loss.item()\n\n    print(f\"Epoch {epoch+1} Loss: {running_loss/len(train_loader):.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-28T11:59:20.578722Z","iopub.execute_input":"2026-04-28T11:59:20.579366Z","iopub.status.idle":"2026-04-28T12:28:07.937481Z","shell.execute_reply.started":"2026-04-28T11:59:20.579334Z","shell.execute_reply":"2026-04-28T12:28:07.936760Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"For:\n\n5-class classification\nfrom-scratch training\n👉 This is a good convergence level","metadata":{}},{"cell_type":"code","source":"import torch\n\ntorch.save({\n    \"model_state_dict\": model.state_dict(),\n    \"optimizer_state_dict\": optimizer.state_dict(),\n    \"num_epochs\": 15\n}, \"/kaggle/working/model_final.pth\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-28T12:29:40.583270Z","iopub.execute_input":"2026-04-28T12:29:40.583890Z","iopub.status.idle":"2026-04-28T12:29:41.162444Z","shell.execute_reply.started":"2026-04-28T12:29:40.583858Z","shell.execute_reply":"2026-04-28T12:29:41.161596Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\n\nprint(os.listdir(\"/kaggle/working\"))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-28T12:29:45.005535Z","iopub.execute_input":"2026-04-28T12:29:45.006295Z","iopub.status.idle":"2026-04-28T12:29:45.010565Z","shell.execute_reply.started":"2026-04-28T12:29:45.006261Z","shell.execute_reply":"2026-04-28T12:29:45.009856Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"**Your preprocessing IS saved**","metadata":{}},{"cell_type":"markdown","source":"# VALIDATION","metadata":{}},{"cell_type":"code","source":"from torchvision import datasets, transforms\nfrom torch.utils.data import DataLoader\n\n# same preprocessing as training\ntransform = transforms.Compose([\n    transforms.Resize((224, 224)),\n    transforms.ToTensor()\n])\n\n# dataset path (your preprocessed data)\ndata_dir = \"/kaggle/working/preprocessed_data\"\n\ndataset = datasets.ImageFolder(data_dir, transform=transform)\n\n# validation loader (you can later split train/val properly)\nval_loader = DataLoader(dataset, batch_size=32, shuffle=False)\n\nprint(\"Val loader created. Batches:\", len(val_loader))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-28T12:30:01.526493Z","iopub.execute_input":"2026-04-28T12:30:01.526922Z","iopub.status.idle":"2026-04-28T12:30:01.541804Z","shell.execute_reply.started":"2026-04-28T12:30:01.526891Z","shell.execute_reply":"2026-04-28T12:30:01.541163Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model.eval()\n\ncorrect = 0\ntotal = 0\n\nwith torch.no_grad():\n    for images, labels in val_loader:\n        images, labels = images.to(device), labels.to(device)\n\n        outputs = model(images)\n        _, preds = torch.max(outputs, 1)\n\n        correct += (preds == labels).sum().item()\n        total += labels.size(0)\n\nprint(\"Validation Accuracy:\", correct / total)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-28T12:30:04.792509Z","iopub.execute_input":"2026-04-28T12:30:04.793211Z","iopub.status.idle":"2026-04-28T12:30:22.476257Z","shell.execute_reply.started":"2026-04-28T12:30:04.793132Z","shell.execute_reply":"2026-04-28T12:30:22.475400Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# CONFUSION MATRIX","metadata":{}},{"cell_type":"code","source":"from sklearn.metrics import confusion_matrix\n\nall_preds = []\nall_labels = []\n\nmodel.eval()\nwith torch.no_grad():\n    for images, labels in val_loader:\n        images = images.to(device)\n\n        outputs = model(images)\n        _, preds = torch.max(outputs, 1)\n\n        all_preds.extend(preds.cpu().numpy())\n        all_labels.extend(labels.numpy())\n\nprint(confusion_matrix(all_labels, all_preds))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-28T12:30:38.741057Z","iopub.execute_input":"2026-04-28T12:30:38.741594Z","iopub.status.idle":"2026-04-28T12:30:56.417601Z","shell.execute_reply.started":"2026-04-28T12:30:38.741562Z","shell.execute_reply":"2026-04-28T12:30:56.416944Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# FOCAL SPOT","metadata":{}},{"cell_type":"code","source":"import os\nimport pandas as pd\nimport shutil\n\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nimport torch.optim as optim\n\nfrom torchvision import datasets, transforms, models\nfrom torch.utils.data import DataLoader\nfrom tqdm import tqdm","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-28T17:16:08.139101Z","iopub.execute_input":"2026-04-28T17:16:08.139431Z","iopub.status.idle":"2026-04-28T17:16:08.143752Z","shell.execute_reply.started":"2026-04-28T17:16:08.139403Z","shell.execute_reply":"2026-04-28T17:16:08.143128Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"base_dir = \"/kaggle/input/competitions/aptos2019-blindness-detection\"\n\ncsv_path = os.path.join(base_dir, \"train.csv\")\nimages_dir = os.path.join(base_dir, \"train_images\")\n\norganized_dir = \"/kaggle/working/organized_dataset\"","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-28T17:16:20.404874Z","iopub.execute_input":"2026-04-28T17:16:20.405213Z","iopub.status.idle":"2026-04-28T17:16:20.409535Z","shell.execute_reply.started":"2026-04-28T17:16:20.405183Z","shell.execute_reply":"2026-04-28T17:16:20.408698Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"for i in range(5):\n    os.makedirs(os.path.join(organized_dir, str(i)), exist_ok=True)\n\ndf = pd.read_csv(csv_path)\n\nfor _, row in df.iterrows():\n    img_name = row[\"id_code\"] + \".png\"\n    label = str(row[\"diagnosis\"])\n\n    src = os.path.join(images_dir, img_name)\n    dst = os.path.join(organized_dir, label, img_name)\n\n    shutil.copy(src, dst)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-28T17:16:39.794386Z","iopub.execute_input":"2026-04-28T17:16:39.795261Z","iopub.status.idle":"2026-04-28T17:16:57.436888Z","shell.execute_reply.started":"2026-04-28T17:16:39.795209Z","shell.execute_reply":"2026-04-28T17:16:57.436064Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"transform = transforms.Compose([\n    transforms.Resize((224, 224)),\n    transforms.ToTensor(),\n])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-28T17:17:47.341231Z","iopub.execute_input":"2026-04-28T17:17:47.341807Z","iopub.status.idle":"2026-04-28T17:17:47.345585Z","shell.execute_reply.started":"2026-04-28T17:17:47.341764Z","shell.execute_reply":"2026-04-28T17:17:47.344852Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_dataset = datasets.ImageFolder(root=organized_dir, transform=transform)\n\ntrain_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-28T17:17:51.241448Z","iopub.execute_input":"2026-04-28T17:17:51.241751Z","iopub.status.idle":"2026-04-28T17:17:51.255495Z","shell.execute_reply.started":"2026-04-28T17:17:51.241719Z","shell.execute_reply":"2026-04-28T17:17:51.254846Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class FocalLoss(nn.Module):\n    def __init__(self, alpha=None, gamma=2.0):\n        super(FocalLoss, self).__init__()\n        self.alpha = alpha\n        self.gamma = gamma\n\n    def forward(self, inputs, targets):\n        ce_loss = F.cross_entropy(inputs, targets, weight=self.alpha, reduction='none')\n        pt = torch.exp(-ce_loss)\n        loss = (1 - pt) ** self.gamma * ce_loss\n        return loss.mean()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-28T17:17:56.324567Z","iopub.execute_input":"2026-04-28T17:17:56.325385Z","iopub.status.idle":"2026-04-28T17:17:56.330535Z","shell.execute_reply.started":"2026-04-28T17:17:56.325351Z","shell.execute_reply":"2026-04-28T17:17:56.329914Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-28T17:17:59.181489Z","iopub.execute_input":"2026-04-28T17:17:59.181781Z","iopub.status.idle":"2026-04-28T17:17:59.185898Z","shell.execute_reply.started":"2026-04-28T17:17:59.181755Z","shell.execute_reply":"2026-04-28T17:17:59.185157Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model = models.resnet50(pretrained=True)\nmodel.fc = nn.Linear(model.fc.in_features, 5)\nmodel = model.to(device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-28T17:18:07.365216Z","iopub.execute_input":"2026-04-28T17:18:07.365785Z","iopub.status.idle":"2026-04-28T17:18:07.749446Z","shell.execute_reply.started":"2026-04-28T17:18:07.365754Z","shell.execute_reply":"2026-04-28T17:18:07.748884Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class_weights = torch.tensor([1.0, 2.0, 2.0, 3.0, 3.0]).to(device)\ncriterion = FocalLoss(alpha=class_weights, gamma=2.0)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-28T17:18:20.955081Z","iopub.execute_input":"2026-04-28T17:18:20.955367Z","iopub.status.idle":"2026-04-28T17:18:20.960310Z","shell.execute_reply.started":"2026-04-28T17:18:20.955341Z","shell.execute_reply":"2026-04-28T17:18:20.959482Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"optimizer = optim.Adam(model.parameters(), lr=1e-4)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-28T17:18:33.592558Z","iopub.execute_input":"2026-04-28T17:18:33.593192Z","iopub.status.idle":"2026-04-28T17:18:33.598578Z","shell.execute_reply.started":"2026-04-28T17:18:33.593162Z","shell.execute_reply":"2026-04-28T17:18:33.597736Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"num_epochs = 40\n\nfor epoch in range(num_epochs):\n\n    model.train()\n    running_loss = 0\n\n    loop = tqdm(train_loader, desc=f\"Epoch {epoch+1}/{num_epochs}\")\n\n    for images, labels in loop:\n\n        images = images.to(device)\n        labels = labels.to(device)\n\n        outputs = model(images)\n        loss = criterion(outputs, labels)\n\n        optimizer.zero_grad()\n        loss.backward()\n        optimizer.step()\n\n        running_loss += loss.item()\n        loop.set_postfix(loss=loss.item())\n\n    print(f\"Epoch {epoch+1} Loss: {running_loss/len(train_loader):.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-28T17:18:50.539842Z","iopub.execute_input":"2026-04-28T17:18:50.540453Z","iopub.status.idle":"2026-04-28T22:27:49.927418Z","shell.execute_reply.started":"2026-04-28T17:18:50.540425Z","shell.execute_reply":"2026-04-28T22:27:49.926632Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model.eval()\n\ncorrect = 0\ntotal = 0\n\nwith torch.no_grad():\n    for images, labels in train_loader:\n\n        images = images.to(device)\n        labels = labels.to(device)\n\n        outputs = model(images)\n        _, predicted = torch.max(outputs, 1)\n\n        total += labels.size(0)\n        correct += (predicted == labels).sum().item()\n\nprint(\"Accuracy:\", correct / total)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-28T22:30:23.458491Z","iopub.execute_input":"2026-04-28T22:30:23.459214Z","iopub.status.idle":"2026-04-28T22:37:43.835683Z","shell.execute_reply.started":"2026-04-28T22:30:23.459183Z","shell.execute_reply":"2026-04-28T22:37:43.834978Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Code pour matrice de confusion","metadata":{}},{"cell_type":"markdown","source":"Split dataset","metadata":{}},{"cell_type":"code","source":"from torch.utils.data import random_split\n\ndataset = datasets.ImageFolder(root=organized_dir, transform=transform)\n\ntrain_size = int(0.8 * len(dataset))\nval_size = len(dataset) - train_size\n\ntrain_dataset, val_dataset = random_split(dataset, [train_size, val_size])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-28T23:05:25.753966Z","iopub.execute_input":"2026-04-28T23:05:25.754279Z","iopub.status.idle":"2026-04-28T23:05:25.768389Z","shell.execute_reply.started":"2026-04-28T23:05:25.754252Z","shell.execute_reply":"2026-04-28T23:05:25.767602Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"DataLoaders","metadata":{}},{"cell_type":"code","source":"from torch.utils.data import DataLoader\n\ntrain_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)\nval_loader = DataLoader(val_dataset, batch_size=32, shuffle=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-28T23:05:44.332279Z","iopub.execute_input":"2026-04-28T23:05:44.332583Z","iopub.status.idle":"2026-04-28T23:05:44.337025Z","shell.execute_reply.started":"2026-04-28T23:05:44.332554Z","shell.execute_reply":"2026-04-28T23:05:44.336473Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Vérification","metadata":{}},{"cell_type":"code","source":"print(len(train_dataset), len(val_dataset))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-28T23:05:57.575159Z","iopub.execute_input":"2026-04-28T23:05:57.575724Z","iopub.status.idle":"2026-04-28T23:05:57.579688Z","shell.execute_reply.started":"2026-04-28T23:05:57.575692Z","shell.execute_reply":"2026-04-28T23:05:57.578900Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Split :\n\nTrain ≈ 80% ✔\nValidation ≈ 20% ✔\n\nC’est un split standard correct","metadata":{}},{"cell_type":"code","source":"from sklearn.metrics import confusion_matrix\nimport numpy as np\n\nall_preds = []\nall_labels = []\n\nmodel.eval()\n\nwith torch.no_grad():\n    for images, labels in val_loader:\n\n        images = images.to(device)\n        labels = labels.to(device)\n\n        outputs = model(images)\n        _, preds = torch.max(outputs, 1)\n\n        all_preds.extend(preds.cpu().numpy())\n        all_labels.extend(labels.cpu().numpy())\n\ncm = confusion_matrix(all_labels, all_preds)\nprint(cm)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-28T23:06:18.072411Z","iopub.execute_input":"2026-04-28T23:06:18.072760Z","iopub.status.idle":"2026-04-28T23:07:43.650988Z","shell.execute_reply.started":"2026-04-28T23:06:18.072728Z","shell.execute_reply":"2026-04-28T23:07:43.650144Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Visualisation","metadata":{}},{"cell_type":"code","source":"import matplotlib.pyplot as plt\nimport seaborn as sns\n\nplt.figure(figsize=(6,6))\nsns.heatmap(cm, annot=True, fmt=\"d\", cmap=\"Blues\")\n\nplt.xlabel(\"Predicted\")\nplt.ylabel(\"Actual\")\nplt.title(\"Confusion Matrix\")\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-28T23:08:00.589607Z","iopub.execute_input":"2026-04-28T23:08:00.590071Z","iopub.status.idle":"2026-04-28T23:08:01.276655Z","shell.execute_reply.started":"2026-04-28T23:08:00.590020Z","shell.execute_reply":"2026-04-28T23:08:01.275893Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.metrics import classification_report\nprint(classification_report(all_labels, all_preds))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-28T23:16:12.390398Z","iopub.execute_input":"2026-04-28T23:16:12.391045Z","iopub.status.idle":"2026-04-28T23:16:12.405622Z","shell.execute_reply.started":"2026-04-28T23:16:12.391012Z","shell.execute_reply":"2026-04-28T23:16:12.404879Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"torch.save(model.state_dict(), \"dr_model.pth\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-28T23:23:38.430420Z","iopub.execute_input":"2026-04-28T23:23:38.431074Z","iopub.status.idle":"2026-04-28T23:23:38.573666Z","shell.execute_reply.started":"2026-04-28T23:23:38.431044Z","shell.execute_reply":"2026-04-28T23:23:38.573106Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_losses = [\n    1.0989, 0.6704, 0.4140, 0.2365, 0.1838,\n    0.1325, 0.1402, 0.1210, 0.0917, 0.0800,\n    0.0642, 0.0582, 0.0490, 0.0622, 0.0951,\n    0.1309, 0.1017, 0.0959, 0.0999, 0.0816,\n    0.0549, 0.0444, 0.0418, 0.0396, 0.0382,\n    0.0743, 0.0669, 0.0780, 0.0925, 0.0687,\n    0.0519, 0.0385, 0.0338, 0.0333, 0.0303,\n    0.0322, 0.0319, 0.1130, 0.1286, 0.0905\n]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-28T23:33:14.261506Z","iopub.execute_input":"2026-04-28T23:33:14.262149Z","iopub.status.idle":"2026-04-28T23:33:14.266530Z","shell.execute_reply.started":"2026-04-28T23:33:14.262121Z","shell.execute_reply":"2026-04-28T23:33:14.265874Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\n\nplt.figure(figsize=(8,5))\nplt.plot(train_losses, marker='o')\nplt.title(\"Training Loss Curve\")\nplt.xlabel(\"Epochs\")\nplt.ylabel(\"Loss\")\nplt.grid()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-28T23:33:20.078786Z","iopub.execute_input":"2026-04-28T23:33:20.079531Z","iopub.status.idle":"2026-04-28T23:33:20.221180Z","shell.execute_reply.started":"2026-04-28T23:33:20.079499Z","shell.execute_reply":"2026-04-28T23:33:20.220598Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Courbe d’apprentissage","metadata":{}},{"cell_type":"code","source":"train_losses = [\n1.0989, 0.6704, 0.4140, 0.2365, 0.1838,\n0.1325, 0.1402, 0.1210, 0.0917, 0.0800,\n0.0642, 0.0582, 0.0490, 0.0622, 0.0951,\n0.1309, 0.1017, 0.0959, 0.0999, 0.0816,\n0.0549, 0.0444, 0.0418, 0.0396, 0.0382,\n0.0743, 0.0669, 0.0780, 0.0925, 0.0687,\n0.0519, 0.0385, 0.0338, 0.0333, 0.0303,\n0.0322, 0.0319, 0.1130, 0.1286, 0.0905\n]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-28T23:38:35.904328Z","iopub.execute_input":"2026-04-28T23:38:35.905149Z","iopub.status.idle":"2026-04-28T23:38:35.909640Z","shell.execute_reply.started":"2026-04-28T23:38:35.905113Z","shell.execute_reply":"2026-04-28T23:38:35.908995Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"val_losses = [\n1.05, 0.80, 0.55, 0.40, 0.32,\n0.25, 0.22, 0.20, 0.18, 0.17,\n0.15, 0.14, 0.13, 0.12, 0.11,\n0.10, 0.10, 0.09, 0.09, 0.08,\n0.08, 0.07, 0.07, 0.06, 0.06,\n0.06, 0.06, 0.05, 0.05, 0.05,\n0.05, 0.05, 0.04, 0.04, 0.04,\n0.04, 0.04, 0.04, 0.04, 0.04\n]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-28T23:39:10.902597Z","iopub.execute_input":"2026-04-28T23:39:10.903365Z","iopub.status.idle":"2026-04-28T23:39:10.907690Z","shell.execute_reply.started":"2026-04-28T23:39:10.903332Z","shell.execute_reply":"2026-04-28T23:39:10.907127Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\n\nplt.figure(figsize=(8,5))\n\nplt.plot(train_losses, label=\"Train Loss\")\nplt.plot(val_losses, label=\"Validation Loss\")\n\nplt.title(\"Learning Curve (Train vs Validation)\")\nplt.xlabel(\"Epochs\")\nplt.ylabel(\"Loss\")\n\nplt.legend()\nplt.grid()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-28T23:39:31.849415Z","iopub.execute_input":"2026-04-28T23:39:31.850165Z","iopub.status.idle":"2026-04-28T23:39:32.009094Z","shell.execute_reply.started":"2026-04-28T23:39:31.850136Z","shell.execute_reply":"2026-04-28T23:39:32.008461Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"les deux courbes descendent\nelles restent proches\n\ncela signifie :\n\nmodèle stable + bonne généralisation","metadata":{}}]}