{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":4104,"databundleVersionId":46661,"sourceType":"competition"},{"sourceId":7866129,"sourceType":"datasetVersion","datasetId":4614938},{"sourceId":7869237,"sourceType":"datasetVersion","datasetId":4617269},{"sourceId":544639,"sourceType":"modelInstanceVersion","isSourceIdPinned":true,"modelInstanceId":418529,"modelId":436182},{"sourceId":549112,"sourceType":"modelInstanceVersion","isSourceIdPinned":true,"modelInstanceId":419567,"modelId":437207},{"sourceId":550051,"sourceType":"modelInstanceVersion","isSourceIdPinned":true,"modelInstanceId":420147,"modelId":437772}],"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"## Introduction\n\nUnderstanding the subject :\n\n\n\n**Usefull Links** : \n- Understand DR :https://youtu.be/VIrkurR446s\n- Useful Preprocessing strategy : https://www.kaggle.com/code/ratthachat/aptos-eye-preprocessing-in-diabetic-retinopathy?utm_source=chatgpt.com\n\n  ","metadata":{}},{"cell_type":"code","source":"from PIL import ImageFile\nImageFile.LOAD_TRUNCATED_IMAGES = True\n\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport cv2\nimport os, random, numpy as np, pandas as pd\nfrom glob import glob\nfrom sklearn.model_selection import StratifiedGroupKFold\nfrom sklearn.model_selection import GroupKFold\nfrom sklearn.metrics import cohen_kappa_score\nimport time, copy\nfrom tqdm import tqdm\n##Phase 1\nfrom PIL import Image\nimport torch\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms\n\n##Phase 2\nimport torch.nn as nn\nfrom torchvision import models\nimport torch.optim as optim\n","metadata":{"execution":{"iopub.status.busy":"2025-08-29T10:24:00.552927Z","iopub.execute_input":"2025-08-29T10:24:00.553548Z","iopub.status.idle":"2025-08-29T10:24:11.696643Z","shell.execute_reply.started":"2025-08-29T10:24:00.553523Z","shell.execute_reply":"2025-08-29T10:24:11.696002Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def set_seed(seed=42):\n    random.seed(seed); np.random.seed(seed)\n    torch.manual_seed(seed); torch.cuda.manual_seed_all(seed)\n    torch.backends.cudnn.deterministic = True\n    torch.backends.cudnn.benchmark = False\n\nset_seed(42)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-29T10:24:11.697787Z","iopub.execute_input":"2025-08-29T10:24:11.698193Z","iopub.status.idle":"2025-08-29T10:24:11.707908Z","shell.execute_reply.started":"2025-08-29T10:24:11.698163Z","shell.execute_reply":"2025-08-29T10:24:11.707224Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nprint(\"Using device:\", device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-29T10:24:11.802278Z","iopub.execute_input":"2025-08-29T10:24:11.802484Z","iopub.status.idle":"2025-08-29T10:24:11.811516Z","shell.execute_reply.started":"2025-08-29T10:24:11.802468Z","shell.execute_reply":"2025-08-29T10:24:11.810823Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Phase 1 : Data Preparation","metadata":{}},{"cell_type":"markdown","source":"#### STEP 1 : Collect Images","metadata":{}},{"cell_type":"code","source":"file_lbl=\"/kaggle/input/diabetic-retinopathy-detection/trainLabels.csv.zip\"\ndf_lbl=pd.read_csv(file_lbl,sep=',')\ndf_lbl.head()","metadata":{"execution":{"iopub.status.busy":"2025-08-29T10:24:15.534873Z","iopub.execute_input":"2025-08-29T10:24:15.535183Z","iopub.status.idle":"2025-08-29T10:24:15.588446Z","shell.execute_reply.started":"2025-08-29T10:24:15.535161Z","shell.execute_reply":"2025-08-29T10:24:15.587830Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# list all train and test images\npaths_train = sorted(glob('/kaggle/input/diabetic-retinopathy-train-unzipped/train/*.jpeg'))\npaths_test  = sorted(glob('/kaggle/input/diabetic-retinopathy-test-unzipped/test/*.jpeg'))\n\nprint(len(paths_train))\nprint(len(paths_test))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-29T10:24:19.174529Z","iopub.execute_input":"2025-08-29T10:24:19.175190Z","iopub.status.idle":"2025-08-29T10:24:20.300672Z","shell.execute_reply.started":"2025-08-29T10:24:19.175165Z","shell.execute_reply":"2025-08-29T10:24:20.300011Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"img_bgr = cv2.imread(paths_train[0])\nimg_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)\nplt.imshow(img_rgb); plt.axis('off')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-29T10:24:22.175552Z","iopub.execute_input":"2025-08-29T10:24:22.176275Z","iopub.status.idle":"2025-08-29T10:24:24.705979Z","shell.execute_reply.started":"2025-08-29T10:24:22.176249Z","shell.execute_reply":"2025-08-29T10:24:24.705202Z"},"_kg_hide-input":false},"outputs":[],"execution_count":null},{"cell_type":"code","source":"img_bgr = cv2.imread(paths_train[35125])\nimg_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)\nplt.imshow(img_rgb); plt.axis('off')","metadata":{"execution":{"iopub.status.busy":"2025-08-28T13:54:50.914290Z","iopub.execute_input":"2025-08-28T13:54:50.914557Z","iopub.status.idle":"2025-08-28T13:54:51.942333Z","shell.execute_reply.started":"2025-08-28T13:54:50.914536Z","shell.execute_reply":"2025-08-28T13:54:51.941492Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"img_bgr = cv2.imread(paths_test[0])\nimg_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)\nplt.imshow(img_rgb); plt.axis('off')","metadata":{"execution":{"iopub.status.busy":"2025-08-28T13:54:54.005314Z","iopub.execute_input":"2025-08-28T13:54:54.005576Z","iopub.status.idle":"2025-08-28T13:54:55.354118Z","shell.execute_reply.started":"2025-08-28T13:54:54.005555Z","shell.execute_reply":"2025-08-28T13:54:55.353350Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"**Map labels**\n- Build a dictionary based on df_lbl and add the path of each image\n- Split by patient, not by image, to avoid leakage (L?R eyes)\n- Split  based on patient a Train ? Validation data sets","metadata":{}},{"cell_type":"code","source":"stem_to_path = {os.path.splitext(os.path.basename(p))[0]: p for p in paths_train}\n\ndf = df_lbl.copy()\ndf[\"path\"] = df[\"image\"].map(stem_to_path)\n\nprint(\"Total rows in labels:\", len(df))\ndf.head(2)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-29T10:24:26.534646Z","iopub.execute_input":"2025-08-29T10:24:26.535404Z","iopub.status.idle":"2025-08-29T10:24:26.606659Z","shell.execute_reply.started":"2025-08-29T10:24:26.535378Z","shell.execute_reply":"2025-08-29T10:24:26.605809Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df[\"patient_id\"] = df[\"image\"].str.split(\"_\").str[0].astype(str)\ndf[\"level\"] = df[\"level\"].astype(int)\n\nprint(\"Label counts:\", df[\"level\"].value_counts().sort_index().to_dict())\ndf.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-29T10:24:31.355532Z","iopub.execute_input":"2025-08-29T10:24:31.355842Z","iopub.status.idle":"2025-08-29T10:24:31.405940Z","shell.execute_reply.started":"2025-08-29T10:24:31.355821Z","shell.execute_reply":"2025-08-29T10:24:31.405373Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"#### STEP 2 : Train & Validation Split","metadata":{}},{"cell_type":"markdown","source":"Because of class imbalance we need a stratified split","metadata":{}},{"cell_type":"code","source":"N_SPLITS = 5\nFOLD_IDX = 0  # which fold to use as validation\n\nsgkf = StratifiedGroupKFold(n_splits=N_SPLITS, shuffle=True, random_state=42)\nsplits = list(sgkf.split(df, y=df[\"level\"], groups=df[\"patient_id\"]))\ntrain_idx, val_idx = splits[FOLD_IDX]\n\ndf_train = df.iloc[train_idx].reset_index(drop=True)\ndf_val   = df.iloc[val_idx].reset_index(drop=True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-29T10:24:34.290164Z","iopub.execute_input":"2025-08-29T10:24:34.290467Z","iopub.status.idle":"2025-08-29T10:24:39.953611Z","shell.execute_reply.started":"2025-08-29T10:24:34.290441Z","shell.execute_reply":"2025-08-29T10:24:39.953059Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\n# safety: no patient overlap\nassert set(df_train.patient_id) & set(df_val.patient_id) == set()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-29T10:24:39.963731Z","iopub.execute_input":"2025-08-29T10:24:39.963981Z","iopub.status.idle":"2025-08-29T10:24:39.977311Z","shell.execute_reply.started":"2025-08-29T10:24:39.963965Z","shell.execute_reply":"2025-08-29T10:24:39.976688Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"the function didn't fail , thus no intersection of patient IDs in train and val.","metadata":{}},{"cell_type":"code","source":"def show_dist(name, s):\n    c = s.value_counts().sort_index()\n    r = (c / c.sum()).round(4).to_dict()\n    print(f\"{name} counts:\", c.to_dict())\n    print(f\"{name} ratios:\", r)\n\nshow_dist(\"TRAIN\", df_train[\"level\"])\nshow_dist(\"VAL  \", df_val[\"level\"])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-29T10:24:41.374778Z","iopub.execute_input":"2025-08-29T10:24:41.375050Z","iopub.status.idle":"2025-08-29T10:24:41.383042Z","shell.execute_reply.started":"2025-08-29T10:24:41.375008Z","shell.execute_reply":"2025-08-29T10:24:41.382268Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"- Ratios in TRAIN vs VAL are almost identical for every class\n- There is a Class imbalance: Class 0 dominates while Classes 3 and 4 are rare","metadata":{}},{"cell_type":"markdown","source":"#### STEP 3 : QWK metric definition","metadata":{}},{"cell_type":"code","source":"def qwk(y_true, y_pred):\n    return cohen_kappa_score(y_true, y_pred, weights=\"quadratic\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-29T10:24:46.193649Z","iopub.execute_input":"2025-08-29T10:24:46.194200Z","iopub.status.idle":"2025-08-29T10:24:46.197525Z","shell.execute_reply.started":"2025-08-29T10:24:46.194174Z","shell.execute_reply":"2025-08-29T10:24:46.196776Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# DO NOT LAUNCH ","metadata":{}},{"cell_type":"markdown","source":"#### Step 4 : Transformations\n- **1.1 : Training Set**","metadata":{}},{"cell_type":"code","source":"def crop_image_from_gray(img: np.ndarray, tol: int = 7) -> np.ndarray:\n    \"\"\"\n    Crop dark borders using a grayscale mask.\n    Always returns a NumPy array.\n    \"\"\"\n    if img.ndim == 2:\n        gray = img\n    else:\n        gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)\n\n    mask = gray > tol\n    if not np.any(mask):\n        return img  # return original NumPy image\n\n    ys, xs = np.where(mask)\n    y0, y1 = ys.min(), ys.max() + 1\n    x0, x1 = xs.min(), xs.max() + 1\n    return img[y0:y1, x0:x1]\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-24T19:43:36.081730Z","iopub.execute_input":"2025-08-24T19:43:36.082586Z","iopub.status.idle":"2025-08-24T19:43:36.087401Z","shell.execute_reply.started":"2025-08-24T19:43:36.082553Z","shell.execute_reply":"2025-08-24T19:43:36.086675Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# We will need to make all images the same size . we will start with the size 224 x 224\nIMG_SIZE = 224","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-24T19:43:33.632609Z","iopub.execute_input":"2025-08-24T19:43:33.632901Z","iopub.status.idle":"2025-08-24T19:43:33.636205Z","shell.execute_reply.started":"2025-08-24T19:43:33.632878Z","shell.execute_reply":"2025-08-24T19:43:33.635582Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"#### PREPROCESS","metadata":{}},{"cell_type":"code","source":"# Save preprocessed images to new path\nout_dir = \"/kaggle/working/processed\"\nos.makedirs(out_dir, exist_ok=True)\n\ndef preprocess_and_save(df, out_dir):\n    new_paths = []\n\n    os.makedirs(out_dir, exist_ok=True)\n\n    for _, row in tqdm(df.iterrows(), total=len(df), desc=\"Preprocessing\"):\n        path = row[\"path\"]\n        img_pil = Image.open(path).convert(\"RGB\")\n        img_np = np.array(img_pil)  # convert to NumPy array first\n\n        img_np = crop_image_from_gray(img_np, tol=7) \n        img_np = cv2.resize(img_np, (224, 224), interpolation=cv2.INTER_AREA)\n\n        img_pil = Image.fromarray(img_np)  #convert back to PIL\n\n        # save\n        filename = os.path.basename(path)\n        save_path = os.path.join(out_dir, filename)\n        img_pil.save(save_path, format=\"JPEG\", quality=95)\n\n        new_paths.append(save_path)\n\n    df = df.copy()\n    df[\"proc_path\"] = new_paths\n    return df","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-24T13:14:08.702289Z","iopub.execute_input":"2025-08-24T13:14:08.702843Z","iopub.status.idle":"2025-08-24T13:14:08.708659Z","shell.execute_reply.started":"2025-08-24T13:14:08.702820Z","shell.execute_reply":"2025-08-24T13:14:08.707952Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#df_train_subset = df_train.sample(8000, random_state=42)\n#df_train_subset = preprocess_and_save(df_train_subset, \"/kaggle/working/processed/train_test\")\n#df_val   = preprocess_and_save(df_val,   \"/kaggle/working/processed/val\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"IMAGENET_MEAN = [0.485, 0.456, 0.406]\nIMAGENET_STD  = [0.229, 0.224, 0.225]\n\ntrain_tf = transforms.Compose([\n    transforms.RandomHorizontalFlip(p=0.5),\n    transforms.RandomRotation(20, fill=0),\n    transforms.ToTensor(),\n    transforms.Normalize(mean=IMAGENET_MEAN, std=IMAGENET_STD),\n])","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"- **1.2 : Validation Set**","metadata":{}},{"cell_type":"code","source":"val_tf = transforms.Compose([\n    transforms.ToTensor(),\n    transforms.Normalize(mean=IMAGENET_MEAN, std=IMAGENET_STD),\n])","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"#### Step 5 : PyTorch Dataset & DataLoaders","metadata":{}},{"cell_type":"markdown","source":"NB: PyTorch has a built-in system for loading data:\n\n- Dataset : defines how to get one sample\n- DataLoader : takes the Dataset and gives you batches automatically\n\nWe create our own Dataset class (__init__ : store dataframe and transforms; _len_ : tells pytorch how many simples we have; _getitem_  for an index i returm the row) because Kaggle’s images and labels are in a CSV + folder, and PyTorch doesn’t know this by default.","metadata":{}},{"cell_type":"code","source":"CROP_BEFORE_TRANSFORM = True","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-24T19:43:44.191523Z","iopub.execute_input":"2025-08-24T19:43:44.191785Z","iopub.status.idle":"2025-08-24T19:43:44.195357Z","shell.execute_reply.started":"2025-08-24T19:43:44.191766Z","shell.execute_reply":"2025-08-24T19:43:44.194749Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class DRDatasetLite(Dataset):\n    def __init__(self, df, tfm):\n        self.df = df.reset_index(drop=True)\n        self.tfm = tfm\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, i):\n        row = self.df.iloc[i]\n        img = Image.open(row[\"proc_path\"]).convert(\"RGB\")\n        x = self.tfm(img)\n        y = int(row[\"level\"])\n        return x, y\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-24T19:43:45.737539Z","iopub.execute_input":"2025-08-24T19:43:45.737830Z","iopub.status.idle":"2025-08-24T19:43:45.742493Z","shell.execute_reply.started":"2025-08-24T19:43:45.737791Z","shell.execute_reply":"2025-08-24T19:43:45.741740Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"**EXAMPLE**","metadata":{}},{"cell_type":"code","source":"sample_img, sample_label = DRDataset(df_train, train_tf)[0]\nprint(sample_img.shape, sample_label)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"We choosed a small batch of 64 images to start. Depending on the GPU we might need to try smaller batches","metadata":{}},{"cell_type":"code","source":"NUM_WORKERS = min(8, os.cpu_count())\nBATCH_SIZE = 64\n\ntrain_ds = DRDatasetLite(df_train_subset, train_tf)\nval_ds   = DRDatasetLite(df_val,   val_tf)\n\ntrain_loader = DataLoader(\n    train_ds, batch_size=BATCH_SIZE, shuffle=True,\n    num_workers=NUM_WORKERS, pin_memory=True,\n    persistent_workers=True, prefetch_factor=2, drop_last=True\n)\n\nval_loader = DataLoader(\n    val_ds, batch_size=BATCH_SIZE, shuffle=False,\n    num_workers=NUM_WORKERS, pin_memory=True,\n    persistent_workers=True, prefetch_factor=2\n)\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Phase 2 : Basline","metadata":{}},{"cell_type":"markdown","source":"### STEP 1:  Backbone :ResNet‑18 ","metadata":{}},{"cell_type":"markdown","source":"How It Works — Step by Step 🛠️\n- Input Image → e.g., a 224×224×3 photo.\n\n- First Layer: Convolution to detect very basic patterns (edges, corners).\n\n- Residual Blocks: The heart of ResNet.\n\n    * Each block learns new features but also has a shortcut path that skips some layers.\n\n    * Instead of learning output = H(x), it learns output = F(x) + x.\n\n- Pooling Layers: Gradually reduce image size but keep important features.\n\n- Fully Connected Layer: Turns the learned features into predictions (e.g., 1000 classes for ImageNet).\n\nSoftmax: Converts predictions into probabilities.","metadata":{}},{"cell_type":"code","source":"# We have 5 classes in this competition: 0,1,2,3,4\nNUM_CLASSES = 5","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-27T18:05:38.675928Z","iopub.execute_input":"2025-08-27T18:05:38.676449Z","iopub.status.idle":"2025-08-27T18:05:38.679668Z","shell.execute_reply.started":"2025-08-27T18:05:38.676425Z","shell.execute_reply":"2025-08-27T18:05:38.678883Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"**If a GPU is available in Kaggle, use it; otherwise fall back to CPU.**","metadata":{}},{"cell_type":"code","source":"device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nprint(\"Using device:\", device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-27T18:05:41.232531Z","iopub.execute_input":"2025-08-27T18:05:41.233207Z","iopub.status.idle":"2025-08-27T18:05:41.237110Z","shell.execute_reply.started":"2025-08-27T18:05:41.233184Z","shell.execute_reply":"2025-08-27T18:05:41.236479Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 1) Create a ResNet-18 backbone\ndef build_resnet18(num_classes=NUM_CLASSES):\n    try:\n        model = models.resnet18(weights=models.ResNet18_Weights.IMAGENET1K_V1)\n    except Exception:\n        model = models.resnet18(weights=None)\n    \n    # Replace final classification layer\n    in_feats = model.fc.in_features\n    model.fc = nn.Linear(in_feats, num_classes)\n    \n    return model.to(device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-27T18:05:43.410285Z","iopub.execute_input":"2025-08-27T18:05:43.410545Z","iopub.status.idle":"2025-08-27T18:05:43.415045Z","shell.execute_reply.started":"2025-08-27T18:05:43.410527Z","shell.execute_reply":"2025-08-27T18:05:43.414335Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model = build_resnet18()\nprint(next(model.parameters()).device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-27T18:05:46.154009Z","iopub.execute_input":"2025-08-27T18:05:46.154575Z","iopub.status.idle":"2025-08-27T18:05:46.718313Z","shell.execute_reply.started":"2025-08-27T18:05:46.154553Z","shell.execute_reply":"2025-08-27T18:05:46.717670Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"dummy = torch.randn(1, 3, 1024, 1024).to(device)\nout = model(dummy)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-27T18:05:51.869488Z","iopub.execute_input":"2025-08-27T18:05:51.870105Z","iopub.status.idle":"2025-08-27T18:05:53.037225Z","shell.execute_reply.started":"2025-08-27T18:05:51.870081Z","shell.execute_reply":"2025-08-27T18:05:53.036679Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"**HOW TO HANDLE IMBALANCE**\n- Loss function = After each prediction, the model gets a score of how wrong it was.\n\n- Class weights = give more importance to rare classes.\n\n- Optimizer = the learning strategy.","metadata":{}},{"cell_type":"markdown","source":"**1. Class Weights**\n- Counts how many images there are for each class (0,1,2,3,4)\n- Calculates weights : rarer classes=bigger weight, while common classes = smaller weight. We use +1e-6 avoids division by zero\n- Normalizes the weights so they’re balanced overall.\n- Converts weights into a tensor (PyTorch format) and sends it to GPU/CPU","metadata":{}},{"cell_type":"code","source":"# Class weights to handle imbalanc\nclass_counts = df_train[\"level\"].value_counts().sort_index()\nclass_weights = 1.0 / (class_counts + 1e-6)\nclass_weights = class_weights / class_weights.sum() * len(class_counts)\n\n# send to tensor \nclass_weights_tensor = torch.tensor(class_weights.values, dtype=torch.float32, device=device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-25T19:35:05.513392Z","iopub.execute_input":"2025-08-25T19:35:05.513670Z","iopub.status.idle":"2025-08-25T19:35:05.683185Z","shell.execute_reply.started":"2025-08-25T19:35:05.513650Z","shell.execute_reply":"2025-08-25T19:35:05.682563Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"**2. Loss Function**\n- CrossEntropyLoss\n-Compares :  Model’s prediction probabilities (softmax) and The true class (ground truth)\n- The weight argument makes mistakes on rare classes count more.","metadata":{}},{"cell_type":"code","source":"criterion = nn.CrossEntropyLoss(weight=class_weights_tensor)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-25T19:35:07.903552Z","iopub.execute_input":"2025-08-25T19:35:07.904288Z","iopub.status.idle":"2025-08-25T19:35:07.907647Z","shell.execute_reply.started":"2025-08-25T19:35:07.904263Z","shell.execute_reply":"2025-08-25T19:35:07.906928Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"**3. Optimizer**\nOptimizer used :AdamW , wight deacy\n- model.parameters() = all trainable knobs in the model (weights & biases).\n\n- lr=3e-4 = learning rate = how big each adjustment step is.\n\n- weight_decay=1e-4 = prevents weights from growing too large (overfitting control).","metadata":{}},{"cell_type":"code","source":"optimizer = optim.AdamW(model.parameters(), lr=3e-4, weight_decay=1e-4)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-25T19:35:10.204105Z","iopub.execute_input":"2025-08-25T19:35:10.204912Z","iopub.status.idle":"2025-08-25T19:35:10.217915Z","shell.execute_reply.started":"2025-08-25T19:35:10.204882Z","shell.execute_reply":"2025-08-25T19:35:10.217105Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Step 2: Training loop","metadata":{}},{"cell_type":"markdown","source":"- Settings :\n  * Train for 4 epochs\n  * Start with wort metric, Kappa = -1\n  * Store best model weights\n- Training phase :\n  * train over mini-batches (xb, yb in train_loader)\n  * Clear optimizer (old gradiant)\n  * Compute loss vs truth , backward pass to compute gradients\n  * Update weights (optimizer.step())\n\n- Validation Phase :\n  * Switch to validation mode (no weights update)\n  * Get predictions + Validation Loss (Validation Loop)\n  * Compute QWK score : y_pred  Vs y_true\n\n- Save Best Model","metadata":{}},{"cell_type":"markdown","source":"#### **First training Loop**","metadata":{}},{"cell_type":"code","source":"EPOCHS = 4\nbest_qwk = -1.0\nbest_state = None\n\nfor epoch in range(1, EPOCHS + 1):\n    print(f\"\\n Epoch {epoch}/{EPOCHS}\")\n    t0 = time.time()\n\n    # ---- TRAIN ----\n    model.train()\n    train_loss_sum = 0.0\n\n    for xb, yb in train_loader:\n        xb = xb.to(device)\n        yb = yb.to(device)\n\n        optimizer.zero_grad()\n        logits = model(xb)\n        loss = criterion(logits, yb)\n        loss.backward()\n        optimizer.step()\n\n        train_loss_sum += loss.item() * xb.size(0)\n\n    train_loss = train_loss_sum / len(train_loader.dataset)\n\n    # ---- VALIDATE ----\n    model.eval()\n    val_loss_sum = 0.0\n    all_preds, all_gts = [], []\n\n    with torch.no_grad():\n        for xb, yb in val_loader:\n            xb = xb.to(device)\n            yb = yb.to(device)\n\n            logits = model(xb)\n            loss = criterion(logits, yb)\n            val_loss_sum += loss.item() * xb.size(0)\n\n            preds = logits.argmax(dim=1).cpu().numpy()\n            all_preds.append(preds)\n            all_gts.append(yb.cpu().numpy())\n\n    val_loss = val_loss_sum / len(val_loader.dataset)\n    y_pred = np.concatenate(all_preds)\n    y_true = np.concatenate(all_gts)\n    val_qwk = qwk(y_true, y_pred)\n\n    # ---- LOG RESULTS ----\n    duration = time.time() - t0\n    print(f\"Train Loss: {train_loss:.4f}\")\n    print(f\"Val   Loss: {val_loss:.4f}\")\n    print(f\"Val   QWK : {val_qwk:.4f}\")\n    print(f\"Time taken: {duration:.1f} seconds\")\n\n    # ---- SAVE BEST MODEL ----\n    if val_qwk > best_qwk:\n        best_qwk = val_qwk\n        best_state = copy.deepcopy(model.state_dict())\n        torch.save(best_state, \"/kaggle/working/best_resnet18_224.pth\")\n        print(\"Saved new best model!\")\n\nprint(f\"\\n Training complete. Best Val QWK: {best_qwk:.4f}\")\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Phase 3 : PreProcessing to Optimize","metadata":{}},{"cell_type":"code","source":"def plot_originals(df, per_class=6, thumb_size=224):\n    levels = [0, 1, 2, 3, 4]\n    cols = len(levels)\n    rows = per_class\n\n    plt.figure(figsize=(cols * 3, rows * 3))\n\n    for c, L in enumerate(levels):\n        idxs = df.index[df[\"level\"] == L].tolist()\n        if len(idxs) == 0:\n            continue\n        choose = np.random.choice(idxs, size=min(rows, len(idxs)), replace=False)\n\n        for r, idx in enumerate(choose):\n            path = df.loc[idx, \"path\"]\n            img = Image.open(path).convert(\"RGB\")\n            img = img.resize((thumb_size, thumb_size))\n\n            ax = plt.subplot(rows, cols, r * cols + (c + 1))\n            ax.imshow(img)\n            if r == 0:\n                ax.set_title(f\"Level {L}\", fontsize=10)\n            ax.axis(\"off\")\n\n    plt.tight_layout()\n    plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-23T14:54:45.097934Z","iopub.execute_input":"2025-08-23T14:54:45.098478Z","iopub.status.idle":"2025-08-23T14:54:45.104296Z","shell.execute_reply.started":"2025-08-23T14:54:45.098457Z","shell.execute_reply":"2025-08-23T14:54:45.103536Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plot_originals(df_train_subset, per_class=6, thumb_size=224)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-23T14:54:48.264204Z","iopub.execute_input":"2025-08-23T14:54:48.264747Z","iopub.status.idle":"2025-08-23T14:54:48.289023Z","shell.execute_reply.started":"2025-08-23T14:54:48.264725Z","shell.execute_reply":"2025-08-23T14:54:48.287948Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"#### **Issues with the images**\n - Illumination: some images are darker then others\n - BACKDROP of the picture = a dark area unrelated to the eye\n - Images have diffrent Sizes (though in the phase 0 we normalized the Size , but it this step we will  need to treat the backdrop before resizing so the informative area especially the Fovea / Hemorphages)","metadata":{}},{"cell_type":"markdown","source":"### Method 1: Improve lighting condition + Auto Corp\n\n- The Goal is to see details in the eyes much better (currently not detected which worsen the model prediction)\n\n    * we are going to test 3 values for Sigma = 10, 30 and  50\n    * We are going to corp borders using a grayscale mask applyed to COLOR image","metadata":{}},{"cell_type":"code","source":"def crop_image_from_gray(img, tol=7):\n    if img.ndim == 2:\n        gray = img\n    else:  # assume color HxWxC\n        gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)\n\n    mask = gray > tol\n    if not np.any(mask):\n        return img  # too dark → nothing to crop\n\n    ys, xs = np.where(mask)\n    y0, y1 = ys.min(), ys.max() + 1\n    x0, x1 = xs.min(), xs.max() + 1\n    return img[y0:y1, x0:x1]\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-24T19:44:45.683717Z","iopub.execute_input":"2025-08-24T19:44:45.683996Z","iopub.status.idle":"2025-08-24T19:44:45.688496Z","shell.execute_reply.started":"2025-08-24T19:44:45.683973Z","shell.execute_reply":"2025-08-24T19:44:45.687791Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def load_enhace_color(img_rgb: np.ndarray, sigma: float = 10) -> np.ndarray:\n    imgf = img_rgb.astype(np.float32)\n    blur = cv2.GaussianBlur(imgf, (0, 0), sigmaX=sigma, sigmaY=sigma)\n    out = cv2.addWeighted(imgf, 4.0, blur, -4.0, 128.0)\n    return np.clip(out, 0, 255).astype(np.uint8)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-24T19:44:47.835047Z","iopub.execute_input":"2025-08-24T19:44:47.835311Z","iopub.status.idle":"2025-08-24T19:44:47.839977Z","shell.execute_reply.started":"2025-08-24T19:44:47.835291Z","shell.execute_reply":"2025-08-24T19:44:47.839241Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"**EXAMPLE : SIGMA =10**","metadata":{}},{"cell_type":"code","source":"NUM_SAMP = 7\nSEED = 42\n\nclasses = sorted(df_train[\"level\"].unique())\nfig = plt.figure(figsize=(25, 16))\n\nfor row_i, class_id in enumerate(classes):\n    \n    rows_cls = df_train.loc[df_train[\"level\"] == class_id]\n    sample_rows = rows_cls.sample(min(NUM_SAMP, len(rows_cls)), random_state=SEED)\n\n    for col_i, (idx, row) in enumerate(sample_rows.iterrows()):\n        # compute subplot position using row_i (not class_id)\n        ax = fig.add_subplot(len(classes), NUM_SAMP, row_i * NUM_SAMP + col_i + 1)\n        ax.set_xticks([]); ax.set_yticks([])\n\n        # Get path\n        if \"path\" in row:\n            path = row[\"path\"]\n        else:\n            path = f\"{BASE_DIR}/{row['id_code']}.png\"\n\n        # preprocess -> returns PIL.Image in your function\n        pil_img = preprocess_base(path, out_size=224, sigma=10)\n\n        # show on the axis\n        ax.imshow(np.array(pil_img))\n        ax.set_title(f\"Class {class_id} | idx {idx}\", fontsize=9)\n\nplt.tight_layout()\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-23T14:55:02.843190Z","iopub.execute_input":"2025-08-23T14:55:02.843440Z","iopub.status.idle":"2025-08-23T14:55:11.205518Z","shell.execute_reply.started":"2025-08-23T14:55:02.843424Z","shell.execute_reply":"2025-08-23T14:55:11.203298Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"We are seeing a few oval / stretched images (like the one in the center) because some crops are not square and then get resized to a square wchich distorts the aspect ratio.","metadata":{}},{"cell_type":"markdown","source":"## Phase 4 : Train the preprocessed Images","metadata":{}},{"cell_type":"markdown","source":"### **4.1 Gray Scale Processing**","metadata":{}},{"cell_type":"markdown","source":"### STEP 1: Preprocessing and test \nApplication to 8000 train sapmle","metadata":{}},{"cell_type":"code","source":"import shutil\nimport os\n\n# List of folders to remove\nfolders_to_remove = [\n    \"/kaggle/working/processed/train_gray\",\n    \"/kaggle/working/processed/val_gray\",\n    \"/kaggle/working/processed/train_test\",\n    \"/kaggle/working/processed/val\",           # optional, if you want to remove all\n    \"/kaggle/working/processed/train\",\n    \"/kaggle/working/processed/train_Color\",\n    \"/kaggle/working/processed/train_circle\",\n    \"/kaggle/working/processed/val_Color\",\n    \"/kaggle/working/processed/val_circle\"\n]\n\nfor folder in folders_to_remove:\n    if os.path.exists(folder):\n        shutil.rmtree(folder)\n        print(f\"✅ Removed: {folder}\")\n    else:\n        print(f\"⚠️ Not found (already removed?): {folder}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-26T15:58:03.552003Z","iopub.execute_input":"2025-08-26T15:58:03.552777Z","iopub.status.idle":"2025-08-26T15:58:03.558152Z","shell.execute_reply.started":"2025-08-26T15:58:03.552721Z","shell.execute_reply":"2025-08-26T15:58:03.557384Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def preprocess_gray_and_save(df, out_dir, out_size=224, tol=7):\n    os.makedirs(out_dir, exist_ok=True)\n    new_paths = []\n\n    for _, row in tqdm(df.iterrows(), total=len(df), desc=\"Preprocessing GRAY\"):\n        path = row[\"path\"]\n\n        img = Image.open(path).convert(\"L\")  # Grayscale\n        img_np = np.array(img)\n\n        img_np = crop_image_from_gray(img_np, tol=tol)\n        img_np = cv2.resize(img_np, (out_size, out_size), interpolation=cv2.INTER_AREA)\n        img_np = np.stack([img_np]*3, axis=-1)  # Convert to 3-channel gray\n\n        img_pil = Image.fromarray(img_np)\n        filename = os.path.basename(path)\n        save_path = os.path.join(out_dir, filename)\n        img_pil.save(save_path, format=\"JPEG\", quality=95)\n\n        new_paths.append(save_path)\n\n    df = df.copy()\n    df[\"proc_path\"] = new_paths\n    return df\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-23T14:57:51.044563Z","iopub.execute_input":"2025-08-23T14:57:51.045071Z","iopub.status.idle":"2025-08-23T14:57:51.050811Z","shell.execute_reply.started":"2025-08-23T14:57:51.045046Z","shell.execute_reply":"2025-08-23T14:57:51.050005Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df_train_subset = df_train.sample(8000, random_state=42)\n\ndf_train_gray = preprocess_gray_and_save(df_train_subset, \"/kaggle/working/processed/train_gray\")\ndf_val_gray   = preprocess_gray_and_save(df_val,       \"/kaggle/working/processed/val_gray\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-23T14:58:17.310758Z","iopub.execute_input":"2025-08-23T14:58:17.311476Z","iopub.status.idle":"2025-08-23T15:40:43.089835Z","shell.execute_reply.started":"2025-08-23T14:58:17.311450Z","shell.execute_reply":"2025-08-23T15:40:43.089132Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### STEP 2: PyTorch Dataset & DataLoaders","metadata":{}},{"cell_type":"code","source":"IMAGENET_MEAN = [0.485, 0.456, 0.406]\nIMAGENET_STD = [0.229, 0.224, 0.225]\n\ntrain_tf_gray = transforms.Compose([\n    transforms.RandomHorizontalFlip(p=0.5),\n    transforms.RandomRotation(20, fill=0),\n    transforms.ToTensor(),\n    transforms.Normalize(mean=IMAGENET_MEAN, std=IMAGENET_STD),\n])\n\nval_tf_gray = transforms.Compose([\n    transforms.ToTensor(),\n    transforms.Normalize(mean=IMAGENET_MEAN, std=IMAGENET_STD),\n])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-23T15:43:29.306537Z","iopub.execute_input":"2025-08-23T15:43:29.306823Z","iopub.status.idle":"2025-08-23T15:43:29.311800Z","shell.execute_reply.started":"2025-08-23T15:43:29.306804Z","shell.execute_reply":"2025-08-23T15:43:29.311080Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class DRDatasetLite(Dataset):\n    def __init__(self, df, tfm):\n        self.df = df.reset_index(drop=True)\n        self.tfm = tfm\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, i):\n        row = self.df.iloc[i]\n        img = Image.open(row[\"proc_path\"]).convert(\"RGB\")  # already 3-channel gray\n        x = self.tfm(img)\n        y = int(row[\"level\"])\n        return x, y\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-23T15:43:31.425394Z","iopub.execute_input":"2025-08-23T15:43:31.425654Z","iopub.status.idle":"2025-08-23T15:43:31.430716Z","shell.execute_reply.started":"2025-08-23T15:43:31.425637Z","shell.execute_reply":"2025-08-23T15:43:31.429857Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"NUM_WORKERS = min(8, os.cpu_count())\nBATCH_SIZE = 64\n\ntrain_ds = DRDatasetLite(df_train_gray, train_tf_gray)\nval_ds   = DRDatasetLite(df_val_gray, val_tf_gray)\n\ntrain_loader = DataLoader(\n    train_ds, batch_size=BATCH_SIZE, shuffle=True,\n    num_workers=NUM_WORKERS, pin_memory=True,\n    persistent_workers=True, prefetch_factor=2, drop_last=True\n)\n\nval_loader = DataLoader(\n    val_ds, batch_size=BATCH_SIZE, shuffle=False,\n    num_workers=NUM_WORKERS, pin_memory=True,\n    persistent_workers=True, prefetch_factor=2\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-23T15:43:51.665040Z","iopub.execute_input":"2025-08-23T15:43:51.665514Z","iopub.status.idle":"2025-08-23T15:43:51.674364Z","shell.execute_reply.started":"2025-08-23T15:43:51.665490Z","shell.execute_reply":"2025-08-23T15:43:51.673683Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### STEP 3: Train +Val  Function","metadata":{}},{"cell_type":"code","source":"EPOCHS = 4\nbest_qwk = -1.0\nbest_state = None\n\nfor epoch in range(1, EPOCHS + 1):\n    print(f\"\\nEpoch {epoch}/{EPOCHS}\")\n    t0 = time.time()\n\n    # ---- TRAIN ----\n    model.train()\n    train_loss_sum = 0.0\n\n    for xb, yb in train_loader:\n        xb, yb = xb.to(device), yb.to(device)\n\n        optimizer.zero_grad()\n        logits = model(xb)\n        loss = criterion(logits, yb)\n        loss.backward()\n        optimizer.step()\n\n        train_loss_sum += loss.item() * xb.size(0)\n\n    train_loss = train_loss_sum / len(train_loader.dataset)\n\n    # ---- VALIDATE ----\n    model.eval()\n    val_loss_sum = 0.0\n    all_preds, all_gts = [], []\n\n    with torch.no_grad():\n        for xb, yb in val_loader:\n            xb, yb = xb.to(device), yb.to(device)\n            logits = model(xb)\n            loss = criterion(logits, yb)\n\n            val_loss_sum += loss.item() * xb.size(0)\n\n            preds = logits.argmax(dim=1).cpu().numpy()\n            all_preds.append(preds)\n            all_gts.append(yb.cpu().numpy())\n\n    val_loss = val_loss_sum / len(val_loader.dataset)\n    y_pred = np.concatenate(all_preds)\n    y_true = np.concatenate(all_gts)\n    val_qwk = qwk(y_true, y_pred)\n\n    # ---- LOG RESULTS ----\n    duration = time.time() - t0\n    print(f\"Train Loss: {train_loss:.4f}\")\n    print(f\"Val   Loss: {val_loss:.4f}\")\n    print(f\"Val   QWK : {val_qwk:.4f}\")\n    print(f\"Time taken: {duration:.1f} sec\")\n\n    # ---- SAVE BEST MODEL ----\n    if val_qwk > best_qwk:\n        best_qwk = val_qwk\n        best_state = copy.deepcopy(model.state_dict())\n        torch.save(best_state, \"/kaggle/working/best_resnet18_gray.pth\")\n        print(\"✅ Saved new best model!\")\n\nprint(f\"\\n Training complete. Best Val QWK: {best_qwk:.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-23T15:45:03.077580Z","iopub.execute_input":"2025-08-23T15:45:03.078089Z","iopub.status.idle":"2025-08-23T15:46:25.434619Z","shell.execute_reply.started":"2025-08-23T15:45:03.078067Z","shell.execute_reply":"2025-08-23T15:46:25.433638Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### **4.2 Color Processing**","metadata":{}},{"cell_type":"markdown","source":"### STEP 1: Preprocessing and test \nApplication to 8000 train sapmle","metadata":{}},{"cell_type":"code","source":"def preprocess_and_save_color(df, out_dir, out_size=224, sigma=10):\n    os.makedirs(out_dir, exist_ok=True)\n    new_paths = []\n\n    for _, row in tqdm(df.iterrows(), total=len(df), desc=\"Preprocessing Color\"):\n        path = row[\"path\"]\n        img_bgr = cv2.imread(path)\n        img = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)\n        img = crop_image_from_gray(img, tol=7)\n        img = cv2.resize(img, (out_size, out_size), interpolation=cv2.INTER_AREA)\n        img = load_enhace_color(img, sigma=30)\n        img_pil = Image.fromarray(img)\n\n        save_path = os.path.join(out_dir, os.path.basename(path))\n        img_pil.save(save_path, format=\"JPEG\", quality=95)\n        new_paths.append(save_path)\n\n    df = df.copy()\n    df[\"proc_path\"] = new_paths\n    return df\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-24T14:22:11.347755Z","iopub.execute_input":"2025-08-24T14:22:11.348682Z","iopub.status.idle":"2025-08-24T14:22:11.355587Z","shell.execute_reply.started":"2025-08-24T14:22:11.348639Z","shell.execute_reply":"2025-08-24T14:22:11.355037Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df_train_subset = df_train.sample(8000, random_state=42)\n\ndf_train_Color = preprocess_and_save_color(df_train_subset, \"/kaggle/working/processed/train_Color\")\ndf_val_Color  = preprocess_and_save_color(df_val,       \"/kaggle/working/processed/val_Color\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-24T14:22:21.451395Z","iopub.execute_input":"2025-08-24T14:22:21.451682Z","iopub.status.idle":"2025-08-24T15:13:02.218925Z","shell.execute_reply.started":"2025-08-24T14:22:21.451663Z","shell.execute_reply":"2025-08-24T15:13:02.218175Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### STEP 2: PyTorch Dataset & DataLoaders","metadata":{}},{"cell_type":"code","source":"from torchvision import transforms\n\nIMAGENET_MEAN = [0.485, 0.456, 0.406]\nIMAGENET_STD = [0.229, 0.224, 0.225]\n\ntrain_tf_color = transforms.Compose([\n    transforms.RandomHorizontalFlip(p=0.5),\n    transforms.RandomRotation(20, fill=0),\n    transforms.ToTensor(),\n    transforms.Normalize(mean=IMAGENET_MEAN, std=IMAGENET_STD),\n])\n\nval_tf_color = transforms.Compose([\n    transforms.ToTensor(),\n    transforms.Normalize(mean=IMAGENET_MEAN, std=IMAGENET_STD),\n])\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-24T15:13:06.186637Z","iopub.execute_input":"2025-08-24T15:13:06.187253Z","iopub.status.idle":"2025-08-24T15:13:06.191779Z","shell.execute_reply.started":"2025-08-24T15:13:06.187227Z","shell.execute_reply":"2025-08-24T15:13:06.191007Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from torch.utils.data import Dataset\nfrom PIL import Image\n\nclass DRDatasetLite(Dataset):\n    def __init__(self, df, tfm):\n        self.df = df.reset_index(drop=True)\n        self.tfm = tfm\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, i):\n        row = self.df.iloc[i]\n        img = Image.open(row[\"proc_path\"]).convert(\"RGB\")\n        x = self.tfm(img)\n        y = int(row[\"level\"])\n        return x, y\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-24T15:13:10.537399Z","iopub.execute_input":"2025-08-24T15:13:10.537671Z","iopub.status.idle":"2025-08-24T15:13:10.543380Z","shell.execute_reply.started":"2025-08-24T15:13:10.537650Z","shell.execute_reply":"2025-08-24T15:13:10.542544Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"NUM_WORKERS = min(8, os.cpu_count())\nBATCH_SIZE = 64\n\ntrain_ds = DRDatasetLite(df_train_Color, train_tf_color)\nval_ds   = DRDatasetLite(df_val_Color, val_tf_color)\n\ntrain_loader = DataLoader(\n    train_ds, batch_size=BATCH_SIZE, shuffle=True,\n    num_workers=NUM_WORKERS, pin_memory=True,\n    persistent_workers=True, prefetch_factor=2, drop_last=True\n)\n\nval_loader = DataLoader(\n    val_ds, batch_size=BATCH_SIZE, shuffle=False,\n    num_workers=NUM_WORKERS, pin_memory=True,\n    persistent_workers=True, prefetch_factor=2\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-24T15:13:13.249423Z","iopub.execute_input":"2025-08-24T15:13:13.249690Z","iopub.status.idle":"2025-08-24T15:13:13.348387Z","shell.execute_reply.started":"2025-08-24T15:13:13.249669Z","shell.execute_reply":"2025-08-24T15:13:13.347580Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### STEP 3: Train +Val  Function","metadata":{}},{"cell_type":"code","source":"EPOCHS = 4\nbest_qwk = -1.0\nbest_state = None\n\nfor epoch in range(1, EPOCHS + 1):\n    print(f\"\\n Epoch {epoch}/{EPOCHS}\")\n    t0 = time.time()\n\n    # ---- TRAIN ----\n    model.train()\n    train_loss_sum = 0.0\n\n    for xb, yb in train_loader:\n        xb, yb = xb.to(device), yb.to(device)\n\n        optimizer.zero_grad()\n        logits = model(xb)\n        loss = criterion(logits, yb)\n        loss.backward()\n        optimizer.step()\n\n        train_loss_sum += loss.item() * xb.size(0)\n\n    train_loss = train_loss_sum / len(train_loader.dataset)\n\n    # ---- VALIDATE ----\n    model.eval()\n    val_loss_sum = 0.0\n    all_preds, all_gts = [], []\n\n    with torch.no_grad():\n        for xb, yb in val_loader:\n            xb, yb = xb.to(device), yb.to(device)\n            logits = model(xb)\n            loss = criterion(logits, yb)\n            val_loss_sum += loss.item() * xb.size(0)\n            preds = logits.argmax(dim=1).cpu().numpy()\n            all_preds.append(preds)\n            all_gts.append(yb.cpu().numpy())\n\n    val_loss = val_loss_sum / len(val_loader.dataset)\n    y_pred = np.concatenate(all_preds)\n    y_true = np.concatenate(all_gts)\n    val_qwk = qwk(y_true, y_pred)\n\n    duration = time.time() - t0\n    print(f\"Train Loss: {train_loss:.4f}\")\n    print(f\"Val   Loss: {val_loss:.4f}\")\n    print(f\"Val   QWK : {val_qwk:.4f}\")\n    print(f\"Time taken: {duration:.1f} seconds\")\n\n    if val_qwk > best_qwk:\n        best_qwk = val_qwk\n        best_state = copy.deepcopy(model.state_dict())\n        torch.save(best_state, \"/kaggle/working/best_resnet18_color.pth\")\n        print(\"Saved new best model!\")\n\nprint(f\"\\nTraining complete. Best Val QWK: {best_qwk:.4f}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-24T15:13:15.478389Z","iopub.execute_input":"2025-08-24T15:13:15.479108Z","iopub.status.idle":"2025-08-24T15:14:39.840351Z","shell.execute_reply.started":"2025-08-24T15:13:15.479084Z","shell.execute_reply":"2025-08-24T15:14:39.839226Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### **4.2 Color Processing + Circular Corp**","metadata":{}},{"cell_type":"code","source":"def circle_crop(path: str, out_size=224, sigmaX=10) -> np.ndarray:\n    \"\"\"\n    Perform circular crop + enhancement.\n    \"\"\"\n    img = cv2.imread(path)\n    img = crop_image_from_gray(img)  # remove borders\n    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n\n    h, w = img.shape[:2]\n    x, y = w // 2, h // 2\n    r = min(x, y)\n\n    # Create circular mask and apply\n    mask = np.zeros((h, w), np.uint8)\n    cv2.circle(mask, (x, y), r, 1, thickness=-1)\n    img = cv2.bitwise_and(img, img, mask=mask)\n\n    img = crop_image_from_gray(img)  # final border crop\n    img = cv2.resize(img, (out_size, out_size), interpolation=cv2.INTER_AREA)\n\n    # Enhance contrast with unsharp masking\n    blur = cv2.GaussianBlur(img, (0, 0), sigmaX)\n    img = cv2.addWeighted(img, 4, blur, -4, 128)\n    return np.clip(img, 0, 255).astype(np.uint8)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-26T10:30:55.919526Z","iopub.execute_input":"2025-08-26T10:30:55.919816Z","iopub.status.idle":"2025-08-26T10:30:55.926108Z","shell.execute_reply.started":"2025-08-26T10:30:55.919796Z","shell.execute_reply":"2025-08-26T10:30:55.925214Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def preprocess_and_save_circle(df, out_dir, out_size=224, sigma=30):\n    os.makedirs(out_dir, exist_ok=True)\n    new_paths = []\n\n    for _, row in tqdm(df.iterrows(), total=len(df), desc=\"Preprocessing Circular\"):\n        path = row[\"path\"]\n        img_np = circle_crop(path, out_size=out_size, sigmaX=sigma)\n        img_pil = Image.fromarray(img_np)\n\n        save_path = os.path.join(out_dir, os.path.basename(path))\n        img_pil.save(save_path, format=\"JPEG\", quality=95)\n        new_paths.append(save_path)\n\n    df = df.copy()\n    df[\"proc_path\"] = new_paths\n    return df","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-24T19:45:17.307125Z","iopub.execute_input":"2025-08-24T19:45:17.307748Z","iopub.status.idle":"2025-08-24T19:45:17.312326Z","shell.execute_reply.started":"2025-08-24T19:45:17.307723Z","shell.execute_reply":"2025-08-24T19:45:17.311705Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df_train_subset = df_train.sample(8000, random_state=42)\ndf_train_circle = preprocess_and_save_circle(df_train_subset, \"/kaggle/working/processed/train_circle\")\ndf_val_circle   = preprocess_and_save_circle(df_val, \"/kaggle/working/processed/val_circle\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-24T19:45:20.910773Z","iopub.execute_input":"2025-08-24T19:45:20.911590Z","iopub.status.idle":"2025-08-24T21:17:08.964118Z","shell.execute_reply.started":"2025-08-24T19:45:20.911558Z","shell.execute_reply":"2025-08-24T21:17:08.963338Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def plot_processed_by_level(df, per_class=5, label_col='level', path_col='proc_path'):\n    levels = sorted(df[label_col].unique())\n    cols = len(levels)\n    rows = per_class\n\n    fig, axes = plt.subplots(rows, cols, figsize=(3.5 * cols, 3.5 * rows))\n\n    for col, level in enumerate(levels):\n        subset = df[df[label_col] == level].sample(per_class, random_state=42)\n        for row, (_, r) in enumerate(subset.iterrows()):\n            ax = axes[row, col] if rows > 1 else axes[col]\n            img = Image.open(r[path_col])\n            ax.imshow(img)\n            ax.axis(\"off\")\n            if row == 0:\n                ax.set_title(f\"Level {level}\", fontsize=12)\n\n    plt.tight_layout()\n    plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-27T19:31:49.129455Z","iopub.execute_input":"2025-08-27T19:31:49.130168Z","iopub.status.idle":"2025-08-27T19:31:49.135459Z","shell.execute_reply.started":"2025-08-27T19:31:49.130144Z","shell.execute_reply":"2025-08-27T19:31:49.134692Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plot_processed_by_level(df_train_circle)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-24T21:17:51.710759Z","iopub.execute_input":"2025-08-24T21:17:51.711043Z","iopub.status.idle":"2025-08-24T21:17:54.248279Z","shell.execute_reply.started":"2025-08-24T21:17:51.711022Z","shell.execute_reply":"2025-08-24T21:17:54.247094Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_tf_circle = transforms.Compose([\n    transforms.RandomHorizontalFlip(p=0.5),\n    transforms.RandomRotation(20, fill=0),\n    transforms.ToTensor(),\n    transforms.Normalize(mean=[0.485, 0.456, 0.406],\n                         std=[0.229, 0.224, 0.225]),\n])\n\nval_tf_circle = transforms.Compose([\n    transforms.ToTensor(),\n    transforms.Normalize(mean=[0.485, 0.456, 0.406],\n                         std=[0.229, 0.224, 0.225]),\n])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-24T21:18:02.696228Z","iopub.execute_input":"2025-08-24T21:18:02.696508Z","iopub.status.idle":"2025-08-24T21:18:02.701201Z","shell.execute_reply.started":"2025-08-24T21:18:02.696486Z","shell.execute_reply":"2025-08-24T21:18:02.700526Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class DRDatasetLite(Dataset):\n    def __init__(self, df, tfm):\n        self.df = df.reset_index(drop=True)\n        self.tfm = tfm\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, i):\n        row = self.df.iloc[i]\n        img = Image.open(row[\"proc_path\"]).convert(\"RGB\")\n        x = self.tfm(img)\n        y = int(row[\"level\"])\n        return x, y","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-24T21:18:09.781411Z","iopub.execute_input":"2025-08-24T21:18:09.781738Z","iopub.status.idle":"2025-08-24T21:18:09.786481Z","shell.execute_reply.started":"2025-08-24T21:18:09.781717Z","shell.execute_reply":"2025-08-24T21:18:09.785727Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_ds = DRDatasetLite(df_train_circle, train_tf_circle)\nval_ds   = DRDatasetLite(df_val_circle, val_tf_circle)\n\ntrain_loader = DataLoader(train_ds, batch_size=64, shuffle=True, num_workers=4,\n                          pin_memory=True, persistent_workers=True, prefetch_factor=2)\n\nval_loader = DataLoader(val_ds, batch_size=64, shuffle=False, num_workers=4,\n                        pin_memory=True, persistent_workers=True, prefetch_factor=2)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-24T21:18:14.516414Z","iopub.execute_input":"2025-08-24T21:18:14.516850Z","iopub.status.idle":"2025-08-24T21:18:14.527991Z","shell.execute_reply.started":"2025-08-24T21:18:14.516802Z","shell.execute_reply":"2025-08-24T21:18:14.527214Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"EPOCHS = 4\nbest_qwk = -1.0\nbest_state = None\n\nfor epoch in range(1, EPOCHS + 1):\n    print(f\"\\n Epoch {epoch}/{EPOCHS}\")\n    t0 = time.time()\n\n    # ---- TRAIN ----\n    model.train()\n    train_loss_sum = 0.0\n\n    for xb, yb in train_loader:\n        xb = xb.to(device)\n        yb = yb.to(device)\n\n        optimizer.zero_grad()\n        logits = model(xb)\n        loss = criterion(logits, yb)\n        loss.backward()\n        optimizer.step()\n\n        train_loss_sum += loss.item() * xb.size(0)\n\n    train_loss = train_loss_sum / len(train_loader.dataset)\n\n    # ---- VALIDATE ----\n    model.eval()\n    val_loss_sum = 0.0\n    all_preds, all_gts = [], []\n\n    with torch.no_grad():\n        for xb, yb in val_loader:\n            xb = xb.to(device)\n            yb = yb.to(device)\n\n            logits = model(xb)\n            loss = criterion(logits, yb)\n            val_loss_sum += loss.item() * xb.size(0)\n\n            preds = logits.argmax(dim=1).cpu().numpy()\n            all_preds.append(preds)\n            all_gts.append(yb.cpu().numpy())\n\n    val_loss = val_loss_sum / len(val_loader.dataset)\n    y_pred = np.concatenate(all_preds)\n    y_true = np.concatenate(all_gts)\n    val_qwk = qwk(y_true, y_pred)\n\n    # ---- LOG RESULTS ----\n    duration = time.time() - t0\n    print(f\"Train Loss: {train_loss:.4f}\")\n    print(f\"Val   Loss: {val_loss:.4f}\")\n    print(f\"Val   QWK : {val_qwk:.4f}\")\n    print(f\"Time taken: {duration:.1f} seconds\")\n\n    # ---- SAVE BEST MODEL ----\n    if val_qwk > best_qwk:\n        best_qwk = val_qwk\n        best_state = copy.deepcopy(model.state_dict())\n        torch.save(best_state, \"/kaggle/working/best_resnet18_circle.pth\")\n        print(\"✅ Saved new best model!\")\n\nprint(f\"\\n✅ Training complete. Best Val QWK: {best_qwk:.4f}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-24T21:21:28.391630Z","iopub.execute_input":"2025-08-24T21:21:28.391931Z","iopub.status.idle":"2025-08-24T21:22:51.881657Z","shell.execute_reply.started":"2025-08-24T21:21:28.391908Z","shell.execute_reply":"2025-08-24T21:22:51.881043Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Phase 5 : Fine tune best model 224 /SIZE=448","metadata":{}},{"cell_type":"markdown","source":"### **5.1 Image Processing 448**","metadata":{}},{"cell_type":"code","source":"def crop_image_from_gray(img, tol=7):\n    if img.ndim == 2:\n        gray = img\n    else:  # assume color HxWxC\n        gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)\n\n    mask = gray > tol\n    if not np.any(mask):\n        return img  # too dark → nothing to crop\n\n    ys, xs = np.where(mask)\n    y0, y1 = ys.min(), ys.max() + 1\n    x0, x1 = xs.min(), xs.max() + 1\n    return img[y0:y1, x0:x1]\n\ndef load_enhace_color(img_rgb: np.ndarray, sigma: float = 10) -> np.ndarray:\n    imgf = img_rgb.astype(np.float32)\n    blur = cv2.GaussianBlur(imgf, (0, 0), sigmaX=sigma, sigmaY=sigma)\n    out = cv2.addWeighted(imgf, 4.0, blur, -4.0, 128.0)\n    return np.clip(out, 0, 255).astype(np.uint8)\n\ndef circle_crop(path: str, out_size=224, sigmaX=10) -> np.ndarray:\n    \"\"\"\n    Perform circular crop + enhancement.\n    \"\"\"\n    img = cv2.imread(path)\n    img = crop_image_from_gray(img)  # remove borders\n    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n\n    h, w = img.shape[:2]\n    x, y = w // 2, h // 2\n    r = min(x, y)\n\n    # Create circular mask and apply\n    mask = np.zeros((h, w), np.uint8)\n    cv2.circle(mask, (x, y), r, 1, thickness=-1)\n    img = cv2.bitwise_and(img, img, mask=mask)\n\n    img = crop_image_from_gray(img)  # final border crop\n    img = cv2.resize(img, (out_size, out_size), interpolation=cv2.INTER_AREA)\n\n    # Enhance contrast with unsharp masking\n    blur = cv2.GaussianBlur(img, (0, 0), sigmaX)\n    img = cv2.addWeighted(img, 4, blur, -4, 128)\n    return np.clip(img, 0, 255).astype(np.uint8)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-29T10:25:26.921534Z","iopub.execute_input":"2025-08-29T10:25:26.921803Z","iopub.status.idle":"2025-08-29T10:25:26.930260Z","shell.execute_reply.started":"2025-08-29T10:25:26.921782Z","shell.execute_reply":"2025-08-29T10:25:26.929430Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"**Process Pipeline**","metadata":{}},{"cell_type":"code","source":"def preprocess_and_save_circle(df, out_dir, out_size=448, sigma=30):\n    os.makedirs(out_dir, exist_ok=True)\n    new_paths = []\n\n    for _, row in tqdm(df.iterrows(), total=len(df), desc=\"Preprocessing Circular\"):\n        path = row[\"path\"]\n        img_np = circle_crop(path, out_size=out_size, sigmaX=sigma)\n        img_pil = Image.fromarray(img_np)\n\n        save_path = os.path.join(out_dir, os.path.basename(path))\n        img_pil.save(save_path, format=\"JPEG\", quality=95)\n        new_paths.append(save_path)\n\n    df = df.copy()\n    df[\"proc_path\"] = new_paths\n    return df","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-29T10:25:42.263661Z","iopub.execute_input":"2025-08-29T10:25:42.263954Z","iopub.status.idle":"2025-08-29T10:25:42.269180Z","shell.execute_reply.started":"2025-08-29T10:25:42.263934Z","shell.execute_reply":"2025-08-29T10:25:42.268361Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df_train_subset = df_train.sample(8000, random_state=42)\n#df_val_subset = df_val.sample(3000, random_state=42)\ndf_train_448 = preprocess_and_save_circle(df_train_subset, \"/kaggle/working/processed/train_circle_448\", out_size=448)\n#df_val_448   = preprocess_and_save_circle(df_val_subset, \"/kaggle/working/processed/val_circle_448\", out_size=448)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-29T10:25:57.806726Z","iopub.execute_input":"2025-08-29T10:25:57.806976Z","iopub.status.idle":"2025-08-29T11:03:39.001624Z","shell.execute_reply.started":"2025-08-29T10:25:57.806959Z","shell.execute_reply":"2025-08-29T11:03:39.000572Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#df_train_subset = df_train.sample(8000, random_state=42)\ndf_val_subset = df_val.sample(3000, random_state=42)\n#df_train_448 = preprocess_and_save_circle(df_train_subset, \"/kaggle/working/processed/train_circle_448\", out_size=448)\ndf_val_448   = preprocess_and_save_circle(df_val_subset, \"/kaggle/working/processed/val_circle_448\", out_size=448)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-27T19:09:02.364191Z","iopub.execute_input":"2025-08-27T19:09:02.364676Z","iopub.status.idle":"2025-08-27T19:28:17.628300Z","shell.execute_reply.started":"2025-08-27T19:09:02.364651Z","shell.execute_reply":"2025-08-27T19:28:17.627659Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def show_dist(name, s):\n    c = s.value_counts().sort_index()\n    r = (c / c.sum()).round(4).to_dict()\n    print(f\"{name} counts:\", c.to_dict())\n    print(f\"{name} ratios:\", r)\n\nshow_dist(\"TRAIN\", df_train_448[\"level\"])\nshow_dist(\"VAL  \", df_val_448[\"level\"])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-27T19:31:32.299318Z","iopub.execute_input":"2025-08-27T19:31:32.299937Z","iopub.status.idle":"2025-08-27T19:31:32.307581Z","shell.execute_reply.started":"2025-08-27T19:31:32.299913Z","shell.execute_reply":"2025-08-27T19:31:32.306933Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plot_processed_by_level(df_train_448)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-27T19:31:59.312331Z","iopub.execute_input":"2025-08-27T19:31:59.312814Z","iopub.status.idle":"2025-08-27T19:32:02.344754Z","shell.execute_reply.started":"2025-08-27T19:31:59.312791Z","shell.execute_reply":"2025-08-27T19:32:02.343525Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### **5.2 DataLoaders**","metadata":{}},{"cell_type":"code","source":"IMG_SIZE = 448\n#BATCH_SIZE = 32  # reduce if needed due to memory\nBATCH_SIZE = 16  #test for ResNet-50\nNUM_WORKERS = min(8, os.cpu_count())\n\nIMAGENET_MEAN = [0.485, 0.456, 0.406]\nIMAGENET_STD  = [0.229, 0.224, 0.225]\n\ntrain_tf = transforms.Compose([\n    transforms.RandomHorizontalFlip(p=0.5),\n    transforms.RandomRotation(20, fill=0),\n    transforms.ToTensor(),\n    transforms.Normalize(mean=IMAGENET_MEAN, std=IMAGENET_STD),\n])\n\nval_tf = transforms.Compose([\n    transforms.ToTensor(),\n    transforms.Normalize(mean=IMAGENET_MEAN, std=IMAGENET_STD),\n])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-27T19:32:30.867331Z","iopub.execute_input":"2025-08-27T19:32:30.867594Z","iopub.status.idle":"2025-08-27T19:32:30.873060Z","shell.execute_reply.started":"2025-08-27T19:32:30.867573Z","shell.execute_reply":"2025-08-27T19:32:30.872379Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class DRDatasetLite(Dataset):\n    def __init__(self, df, tfm):\n        self.df = df.reset_index(drop=True)\n        self.tfm = tfm\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, i):\n        row = self.df.iloc[i]\n        img = Image.open(row[\"proc_path\"]).convert(\"RGB\")\n        x = self.tfm(img)\n        y = int(row[\"level\"])\n        return x, y\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-27T19:32:33.649963Z","iopub.execute_input":"2025-08-27T19:32:33.650608Z","iopub.status.idle":"2025-08-27T19:32:33.655261Z","shell.execute_reply.started":"2025-08-27T19:32:33.650582Z","shell.execute_reply":"2025-08-27T19:32:33.654515Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_ds = DRDatasetLite(df_train_448, train_tf)\nval_ds   = DRDatasetLite(df_val_448, val_tf)\n\ntrain_loader = DataLoader(\n    train_ds, batch_size=BATCH_SIZE, shuffle=True,\n    num_workers=NUM_WORKERS, pin_memory=True,\n    persistent_workers=True, prefetch_factor=2, drop_last=True\n)\n\nval_loader = DataLoader(\n    val_ds, batch_size=BATCH_SIZE, shuffle=False,\n    num_workers=NUM_WORKERS, pin_memory=True,\n    persistent_workers=True, prefetch_factor=2\n)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-27T19:32:36.710169Z","iopub.execute_input":"2025-08-27T19:32:36.710429Z","iopub.status.idle":"2025-08-27T19:32:36.718758Z","shell.execute_reply.started":"2025-08-27T19:32:36.710407Z","shell.execute_reply":"2025-08-27T19:32:36.717981Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### **5.3 Fine tuning the model**","metadata":{}},{"cell_type":"markdown","source":"**Get Model**","metadata":{}},{"cell_type":"code","source":"def build_resnet18(num_classes=5):\n    model = models.resnet18(weights=models.ResNet18_Weights.IMAGENET1K_V1)\n    in_feats = model.fc.in_features\n    model.fc = nn.Linear(in_feats, num_classes)\n    return model.to(device)\nmodel = build_resnet18(num_classes=5)\nmodel.load_state_dict(torch.load(\"/kaggle/input/model_qwk_0.5/pytorch/default/1/best_resnet18_circle.pth\"))\nprint(\"✅ Loaded 224x224 weights!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-26T17:42:17.351648Z","iopub.execute_input":"2025-08-26T17:42:17.352180Z","iopub.status.idle":"2025-08-26T17:42:17.645696Z","shell.execute_reply.started":"2025-08-26T17:42:17.352154Z","shell.execute_reply":"2025-08-26T17:42:17.644987Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"**Test Freezing First Layers**\nBetter not be applied because the result where worse then the last method","metadata":{}},{"cell_type":"code","source":"#for name, param in model.named_parameters():\n#    if \"fc\" not in name:\n#        param.requires_grad = False\n\n#Unfreeze everything later (e.g. after 1 epoch)\nfor param in model.parameters():\n    param.requires_grad = True\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-26T17:42:13.564940Z","iopub.execute_input":"2025-08-26T17:42:13.565482Z","iopub.status.idle":"2025-08-26T17:42:13.569416Z","shell.execute_reply.started":"2025-08-26T17:42:13.565458Z","shell.execute_reply":"2025-08-26T17:42:13.568630Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"**optimizer and loss**","metadata":{}},{"cell_type":"code","source":"class_counts = df_train_448[\"level\"].value_counts().sort_index()\nweights = 1.0 / (class_counts + 1e-6)\nweights = weights / weights.sum() * len(class_counts)\n\nclass_weights_tensor = torch.tensor(weights.values, dtype=torch.float32, device=device)\n\ncriterion = nn.CrossEntropyLoss(weight=class_weights_tensor)\noptimizer = optim.AdamW(model.parameters(), lr=3e-4, weight_decay=1e-4)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-27T19:33:07.561740Z","iopub.execute_input":"2025-08-27T19:33:07.562060Z","iopub.status.idle":"2025-08-27T19:33:07.574061Z","shell.execute_reply.started":"2025-08-27T19:33:07.562028Z","shell.execute_reply":"2025-08-27T19:33:07.573248Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### **5.4 Training Loop**","metadata":{}},{"cell_type":"code","source":"EPOCHS = 4\nbest_qwk = -1.0\nbest_state = None\n\nfor epoch in range(1, EPOCHS + 1):\n    print(f\"\\n📘 Epoch {epoch}/{EPOCHS}\")\n    t0 = time.time()\n\n    # ---- TRAIN ----\n    model.train()\n    train_loss_sum = 0.0\n\n    for xb, yb in train_loader:\n        xb, yb = xb.to(device), yb.to(device)\n\n        optimizer.zero_grad()\n        logits = model(xb)\n        loss = criterion(logits, yb)\n        loss.backward()\n        optimizer.step()\n\n        train_loss_sum += loss.item() * xb.size(0)\n\n    train_loss = train_loss_sum / len(train_loader.dataset)\n\n    # ---- VALIDATE ----\n    model.eval()\n    val_loss_sum = 0.0\n    all_preds, all_gts = [], []\n\n    with torch.no_grad():\n        for xb, yb in val_loader:\n            xb, yb = xb.to(device), yb.to(device)\n            logits = model(xb)\n            loss = criterion(logits, yb)\n\n            val_loss_sum += loss.item() * xb.size(0)\n            preds = logits.argmax(dim=1).cpu().numpy()\n            all_preds.append(preds)\n            all_gts.append(yb.cpu().numpy())\n\n    val_loss = val_loss_sum / len(val_loader.dataset)\n    y_pred = np.concatenate(all_preds)\n    y_true = np.concatenate(all_gts)\n    val_qwk = qwk(y_true, y_pred)\n\n    # ---- LOG RESULTS ----\n    duration = time.time() - t0\n    print(f\"✅ Train Loss: {train_loss:.4f}\")\n    print(f\"✅ Val   Loss: {val_loss:.4f}\")\n    print(f\"✅ Val   QWK : {val_qwk:.4f}\")\n    print(f\"⏱️  Time taken: {duration:.1f} seconds\")\n\n    # ---- SAVE BEST ----\n    if val_qwk > best_qwk:\n        best_qwk = val_qwk\n        best_state = copy.deepcopy(model.state_dict())\n        torch.save(best_state, \"/kaggle/working/best_resnet18_448.pth\")\n        print(\"💾 Saved new best model!\")\n\nprint(f\"\\n🏁 Training complete. Best Val QWK: {best_qwk:.4f}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-26T17:48:16.038659Z","iopub.execute_input":"2025-08-26T17:48:16.039289Z","iopub.status.idle":"2025-08-26T17:52:47.628984Z","shell.execute_reply.started":"2025-08-26T17:48:16.039266Z","shell.execute_reply":"2025-08-26T17:52:47.628251Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Phase 5.2 : ResNet-50 test /SIZE=448","metadata":{}},{"cell_type":"code","source":"# Cell 1: Build ResNet-50\nfrom torchvision import models\nimport torch.nn as nn\n\ndef build_resnet50(num_classes=5):\n    model = models.resnet50(weights=models.ResNet50_Weights.IMAGENET1K_V1)\n    in_feats = model.fc.in_features\n    model.fc = nn.Linear(in_feats, num_classes)\n    return model.to(device)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-27T19:32:20.902488Z","iopub.execute_input":"2025-08-27T19:32:20.903070Z","iopub.status.idle":"2025-08-27T19:32:20.907147Z","shell.execute_reply.started":"2025-08-27T19:32:20.903045Z","shell.execute_reply":"2025-08-27T19:32:20.906362Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model = build_resnet50(num_classes=5)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-27T19:32:51.466908Z","iopub.execute_input":"2025-08-27T19:32:51.467174Z","iopub.status.idle":"2025-08-27T19:32:51.874121Z","shell.execute_reply.started":"2025-08-27T19:32:51.467154Z","shell.execute_reply":"2025-08-27T19:32:51.873312Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class_counts = df_train_448[\"level\"].value_counts().sort_index()\nweights = 1.0 / (class_counts + 1e-6)\nweights = weights / weights.sum() * len(class_counts)\n\nclass_weights_tensor = torch.tensor(weights.values, dtype=torch.float32, device=device)\n\ncriterion = nn.CrossEntropyLoss(weight=class_weights_tensor)\noptimizer = optim.AdamW(model.parameters(), lr=3e-4, weight_decay=1e-4)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-27T19:33:18.575322Z","iopub.execute_input":"2025-08-27T19:33:18.575929Z","iopub.status.idle":"2025-08-27T19:33:18.583792Z","shell.execute_reply.started":"2025-08-27T19:33:18.575906Z","shell.execute_reply":"2025-08-27T19:33:18.583160Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"#### Training loop","metadata":{}},{"cell_type":"code","source":"EPOCHS = 4\nbest_qwk = -1.0\nbest_state = None\n\nfor epoch in range(1, EPOCHS + 1):\n    print(f\"\\n Epoch {epoch}/{EPOCHS}\")\n    t0 = time.time()\n\n    # ---- TRAIN ----\n    model.train()\n    train_loss_sum = 0.0\n\n    for xb, yb in train_loader:\n        xb, yb = xb.to(device), yb.to(device)\n\n        optimizer.zero_grad()\n        logits = model(xb)\n        loss = criterion(logits, yb)\n        loss.backward()\n        optimizer.step()\n\n        train_loss_sum += loss.item() * xb.size(0)\n\n    train_loss = train_loss_sum / len(train_loader.dataset)\n\n    # ---- VALIDATE ----\n    model.eval()\n    val_loss_sum = 0.0\n    all_preds, all_gts = [], []\n\n    with torch.no_grad():\n        for xb, yb in val_loader:\n            xb, yb = xb.to(device), yb.to(device)\n            logits = model(xb)\n            loss = criterion(logits, yb)\n\n            val_loss_sum += loss.item() * xb.size(0)\n            preds = logits.argmax(dim=1).cpu().numpy()\n            all_preds.append(preds)\n            all_gts.append(yb.cpu().numpy())\n\n    val_loss = val_loss_sum / len(val_loader.dataset)\n    y_pred = np.concatenate(all_preds)\n    y_true = np.concatenate(all_gts)\n    val_qwk = qwk(y_true, y_pred)\n\n    # ---- LOG RESULTS ----\n    duration = time.time() - t0\n    print(f\" Train Loss: {train_loss:.4f}\")\n    print(f\" Val   Loss: {val_loss:.4f}\")\n    print(f\" Val   QWK : {val_qwk:.4f}\")\n    print(f\" Time taken: {duration:.1f} seconds\")\n\n    # ---- SAVE BEST ----\n    if val_qwk > best_qwk:\n        best_qwk = val_qwk\n        best_state = copy.deepcopy(model.state_dict())\n        torch.save(best_state, \"/kaggle/working/best_resnet50_448.pth\")\n        print(\"Saved new best model!\")\n\nprint(f\"\\n Training complete. Best Val QWK: {best_qwk:.4f}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-27T20:18:23.316854Z","iopub.execute_input":"2025-08-27T20:18:23.317424Z","iopub.status.idle":"2025-08-27T20:39:27.387016Z","shell.execute_reply.started":"2025-08-27T20:18:23.317403Z","shell.execute_reply":"2025-08-27T20:39:27.386196Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Phase 5.2 : ResNet-18 7 ResNet-50 test /SIZE=768","metadata":{}},{"cell_type":"code","source":"def preprocess_and_save_circle(df, out_dir, out_size=768, sigma=30):\n    os.makedirs(out_dir, exist_ok=True)\n    new_paths = []\n\n    for _, row in tqdm(df.iterrows(), total=len(df), desc=\"Preprocessing Circular\"):\n        path = row[\"path\"]\n        img_np = circle_crop(path, out_size=out_size, sigmaX=sigma)\n        img_pil = Image.fromarray(img_np)\n\n        save_path = os.path.join(out_dir, os.path.basename(path))\n        img_pil.save(save_path, format=\"JPEG\", quality=95)\n        new_paths.append(save_path)\n\n    df = df.copy()\n    df[\"proc_path\"] = new_paths\n    return df","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-29T11:04:00.345116Z","iopub.execute_input":"2025-08-29T11:04:00.345391Z","iopub.status.idle":"2025-08-29T11:04:00.350722Z","shell.execute_reply.started":"2025-08-29T11:04:00.345372Z","shell.execute_reply":"2025-08-29T11:04:00.350061Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df_train_subset = df_train.sample(8000, random_state=42)\n\ndf_train_768 = preprocess_and_save_circle(df_train_subset, \"/kaggle/working/processed/train_768\", out_size=768)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-29T11:04:12.626234Z","iopub.execute_input":"2025-08-29T11:04:12.626733Z","iopub.status.idle":"2025-08-29T12:04:21.059198Z","shell.execute_reply.started":"2025-08-29T11:04:12.626709Z","shell.execute_reply":"2025-08-29T12:04:21.058580Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df_val_subset = df_val.sample(1000, random_state=42)\ndf_val_768   = preprocess_and_save_circle(df_val, \"/kaggle/working/processed/val_768\", out_size=768)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-29T12:54:40.401844Z","iopub.execute_input":"2025-08-29T12:54:40.402497Z","execution_failed":"2025-08-29T14:20:38.183Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def plot_processed_by_level(df, per_class=5, label_col='level', path_col='proc_path'):\n    levels = sorted(df[label_col].unique())\n    cols = len(levels)\n    rows = per_class\n\n    fig, axes = plt.subplots(rows, cols, figsize=(3.5 * cols, 3.5 * rows))\n\n    for col, level in enumerate(levels):\n        subset = df[df[label_col] == level].sample(per_class, random_state=42)\n        for row, (_, r) in enumerate(subset.iterrows()):\n            ax = axes[row, col] if rows > 1 else axes[col]\n            img = Image.open(r[path_col])\n            ax.imshow(img)\n            ax.axis(\"off\")\n            if row == 0:\n                ax.set_title(f\"Level {level}\", fontsize=12)\n\n    plt.tight_layout()\n    plt.show()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plot_processed_by_level(df_train_768)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-29T12:54:25.648154Z","iopub.status.idle":"2025-08-29T12:54:25.648469Z","shell.execute_reply.started":"2025-08-29T12:54:25.648308Z","shell.execute_reply":"2025-08-29T12:54:25.648323Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"IMG_SIZE = 448\n#BATCH_SIZE = 32  # reduce if needed due to memory\nBATCH_SIZE = 16  #test for ResNet-50\nNUM_WORKERS = min(8, os.cpu_count())\n\nIMAGENET_MEAN = [0.485, 0.456, 0.406]\nIMAGENET_STD  = [0.229, 0.224, 0.225]\n\ntrain_tf = transforms.Compose([\n    transforms.RandomHorizontalFlip(p=0.5),\n    transforms.RandomRotation(20, fill=0),\n    transforms.ToTensor(),\n    transforms.Normalize(mean=IMAGENET_MEAN, std=IMAGENET_STD),\n])\n\nval_tf = transforms.Compose([\n    transforms.ToTensor(),\n    transforms.Normalize(mean=IMAGENET_MEAN, std=IMAGENET_STD),\n])\n\nclass DRDatasetLite(Dataset):\n    def __init__(self, df, tfm):\n        self.df = df.reset_index(drop=True)\n        self.tfm = tfm\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, i):\n        row = self.df.iloc[i]\n        img = Image.open(row[\"proc_path\"]).convert(\"RGB\")\n        x = self.tfm(img)\n        y = int(row[\"level\"])\n        return x, y\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-29T12:54:25.649998Z","iopub.status.idle":"2025-08-29T12:54:25.650338Z","shell.execute_reply.started":"2025-08-29T12:54:25.650184Z","shell.execute_reply":"2025-08-29T12:54:25.650199Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_ds_768 = DRDatasetLite(df_train_768, train_tf)\nval_ds_768   = DRDatasetLite(df_val_768, val_tf)\n\ntrain_loader = DataLoader(train_ds_768, batch_size=16, shuffle=True,\n                              num_workers=2, pin_memory=True,\n                              persistent_workers=True, prefetch_factor=2)\n\nval_loader = DataLoader(val_ds_768, batch_size=16, shuffle=False,\n                            num_workers=2, pin_memory=True,\n                            persistent_workers=True, prefetch_factor=2)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-29T12:54:25.651324Z","iopub.status.idle":"2025-08-29T12:54:25.651585Z","shell.execute_reply.started":"2025-08-29T12:54:25.651470Z","shell.execute_reply":"2025-08-29T12:54:25.651482Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class_counts = df_train_768[\"level\"].value_counts().sort_index()\nweights = 1.0 / (class_counts + 1e-6)\nweights = weights / weights.sum() * len(class_counts)\n\nclass_weights_tensor = torch.tensor(weights.values, dtype=torch.float32, device=device)\n\ncriterion = nn.CrossEntropyLoss(weight=class_weights_tensor)\noptimizer = optim.AdamW(model.parameters(), lr=3e-4, weight_decay=1e-4)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-29T12:54:25.654960Z","iopub.status.idle":"2025-08-29T12:54:25.655330Z","shell.execute_reply.started":"2025-08-29T12:54:25.655190Z","shell.execute_reply":"2025-08-29T12:54:25.655206Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"**Fine tune ResNet-18**","metadata":{}},{"cell_type":"code","source":"def build_resnet18(num_classes=5):\n    model = models.resnet18(weights=models.ResNet18_Weights.IMAGENET1K_V1)\n    in_feats = model.fc.in_features\n    model.fc = nn.Linear(in_feats, num_classes)\n    return model.to(device)\n\n# Load pre-trained model from 448 phase\nmodel = build_resnet18(num_classes=5)\nmodel.load_state_dict(torch.load(\"/kaggle/input/best_resnet18_qwk_0.58/pytorch/default/1/best_resnet18_448.pth\"))\nprint(\"Loaded ResNet-18 448x448 weights!\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-28T16:46:55.686004Z","iopub.execute_input":"2025-08-28T16:46:55.686561Z","iopub.status.idle":"2025-08-28T16:46:55.940466Z","shell.execute_reply.started":"2025-08-28T16:46:55.686536Z","shell.execute_reply":"2025-08-28T16:46:55.939836Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"EPOCHS = 4\nbest_qwk = -1.0\nbest_state = None\n\nfor epoch in range(1, EPOCHS + 1):\n    print(f\"\\n Epoch {epoch}/{EPOCHS}\")\n    t0 = time.time()\n\n    # ---- TRAIN ----\n    model.train()\n    train_loss_sum = 0.0\n\n    for xb, yb in train_loader:\n        xb, yb = xb.to(device), yb.to(device)\n\n        optimizer.zero_grad()\n        logits = model(xb)\n        loss = criterion(logits, yb)\n        loss.backward()\n        optimizer.step()\n\n        train_loss_sum += loss.item() * xb.size(0)\n\n    train_loss = train_loss_sum / len(train_loader.dataset)\n\n    # ---- VALIDATE ----\n    model.eval()\n    val_loss_sum = 0.0\n    all_preds, all_gts = [], []\n\n    with torch.no_grad():\n        for xb, yb in val_loader:\n            xb, yb = xb.to(device), yb.to(device)\n            logits = model(xb)\n            loss = criterion(logits, yb)\n\n            val_loss_sum += loss.item() * xb.size(0)\n            preds = logits.argmax(dim=1).cpu().numpy()\n            all_preds.append(preds)\n            all_gts.append(yb.cpu().numpy())\n\n    val_loss = val_loss_sum / len(val_loader.dataset)\n    y_pred = np.concatenate(all_preds)\n    y_true = np.concatenate(all_gts)\n    val_qwk = qwk(y_true, y_pred)\n\n    # ---- LOG RESULTS ----\n    duration = time.time() - t0\n    print(f\" Train Loss: {train_loss:.4f}\")\n    print(f\" Val   Loss: {val_loss:.4f}\")\n    print(f\" Val   QWK : {val_qwk:.4f}\")\n    print(f\" Time taken: {duration:.1f} seconds\")\n\n    # ---- SAVE BEST ----\n    if val_qwk > best_qwk:\n        best_qwk = val_qwk\n        best_state = copy.deepcopy(model.state_dict())\n        torch.save(best_state, \"/kaggle/working/best_resnet18-768.pth\")\n        print(\"Saved new best model!\")\n\nprint(f\"\\n Training complete. Best Val QWK: {best_qwk:.4f}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-28T17:07:17.373983Z","iopub.execute_input":"2025-08-28T17:07:17.374592Z","iopub.status.idle":"2025-08-28T17:25:21.464615Z","shell.execute_reply.started":"2025-08-28T17:07:17.374542Z","shell.execute_reply":"2025-08-28T17:25:21.463905Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"**Fine Tune ResNet-50**","metadata":{}},{"cell_type":"code","source":"import gc\nimport torch\n\ngc.collect()                      # Python garbage collection\ntorch.cuda.empty_cache()          # Releases unused memory back to the GPU\ntorch.cuda.ipc_collect()          # Optional: Collects interprocess references\n\n# Delete large objects\nfor var in ['model', 'optimizer', 'train_loader', 'val_loader']:\n    if var in globals():\n        del globals()[var]\n\ngc.collect()\ntorch.cuda.empty_cache()\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-28T17:35:49.806724Z","iopub.execute_input":"2025-08-28T17:35:49.807499Z","iopub.status.idle":"2025-08-28T17:35:50.127198Z","shell.execute_reply.started":"2025-08-28T17:35:49.807474Z","shell.execute_reply":"2025-08-28T17:35:50.126341Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def clean_gpu():\n    import gc, torch\n    gc.collect()\n    torch.cuda.empty_cache()\n    torch.cuda.ipc_collect()\n    print(\"✅ GPU memory cleaned\")\n\nclean_gpu()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-28T17:36:36.344233Z","iopub.execute_input":"2025-08-28T17:36:36.344740Z","iopub.status.idle":"2025-08-28T17:36:36.516647Z","shell.execute_reply.started":"2025-08-28T17:36:36.344716Z","shell.execute_reply":"2025-08-28T17:36:36.515736Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def build_resnet50(num_classes=5):\n    model = models.resnet50(weights=models.ResNet50_Weights.IMAGENET1K_V1)\n    in_feats = model.fc.in_features\n    model.fc = nn.Linear(in_feats, num_classes)\n    return model.to(device)\n\n# Load pre-trained model from 448 phase\nmodel = build_resnet50(num_classes=5)\nmodel.load_state_dict(torch.load(\"/kaggle/input/resnet50_448_qwk_0.619/pytorch/default/1/best_resnet50_448_QWK_0.619.pth\"))\nprint(\"Loaded ResNet-50 448x448 weights!\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-29T12:54:25.655982Z","iopub.status.idle":"2025-08-29T12:54:25.656280Z","shell.execute_reply.started":"2025-08-29T12:54:25.656160Z","shell.execute_reply":"2025-08-29T12:54:25.656171Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"EPOCHS = 4\nbest_qwk = -1.0\nbest_state = None\n\nfor epoch in range(1, EPOCHS + 1):\n    print(f\"\\n Epoch {epoch}/{EPOCHS}\")\n    t0 = time.time()\n\n    # ---- TRAIN ----\n    model.train()\n    train_loss_sum = 0.0\n\n    for xb, yb in train_loader:\n        xb, yb = xb.to(device), yb.to(device)\n\n        optimizer.zero_grad()\n        logits = model(xb)\n        loss = criterion(logits, yb)\n        loss.backward()\n        optimizer.step()\n\n        train_loss_sum += loss.item() * xb.size(0)\n\n    train_loss = train_loss_sum / len(train_loader.dataset)\n\n    # ---- VALIDATE ----\n    model.eval()\n    val_loss_sum = 0.0\n    all_preds, all_gts = [], []\n\n    with torch.no_grad():\n        for xb, yb in val_loader:\n            xb, yb = xb.to(device), yb.to(device)\n            logits = model(xb)\n            loss = criterion(logits, yb)\n\n            val_loss_sum += loss.item() * xb.size(0)\n            preds = logits.argmax(dim=1).cpu().numpy()\n            all_preds.append(preds)\n            all_gts.append(yb.cpu().numpy())\n\n    val_loss = val_loss_sum / len(val_loader.dataset)\n    y_pred = np.concatenate(all_preds)\n    y_true = np.concatenate(all_gts)\n    val_qwk = qwk(y_true, y_pred)\n\n    # ---- LOG RESULTS ----\n    duration = time.time() - t0\n    print(f\" Train Loss: {train_loss:.4f}\")\n    print(f\" Val   Loss: {val_loss:.4f}\")\n    print(f\" Val   QWK : {val_qwk:.4f}\")\n    print(f\" Time taken: {duration:.1f} seconds\")\n\n    # ---- SAVE BEST ----\n    if val_qwk > best_qwk:\n        best_qwk = val_qwk\n        best_state = copy.deepcopy(model.state_dict())\n        torch.save(best_state, \"/kaggle/working/best_resnet50-768.pth\")\n        print(\"Saved new best model!\")\n\nprint(f\"\\n Training complete. Best Val QWK: {best_qwk:.4f}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-29T12:54:25.657292Z","iopub.status.idle":"2025-08-29T12:54:25.657545Z","shell.execute_reply.started":"2025-08-29T12:54:25.657446Z","shell.execute_reply":"2025-08-29T12:54:25.657456Z"}},"outputs":[],"execution_count":null}]}