{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[],"dockerImageVersionId":28755,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport seaborn as sns\n\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import classification_report, confusion_matrix\n\nimport tensorflow as tf\nfrom tensorflow.keras.models import Sequential\nfrom tensorflow.keras.layers import (Conv2D, MaxPooling2D, Flatten, \n                                      Dense, Dropout)\nfrom tensorflow.keras.preprocessing.image import ImageDataGenerator\nfrom tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau\n\nimport warnings\nwarnings.filterwarnings('ignore')\n\nprint(\"TF Version:\", tf.__version__)\nprint(\"GPU:\", tf.config.list_physical_devices('GPU'))","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-10-19T17:29:35.496867Z","iopub.execute_input":"2026-10-19T17:29:35.497123Z","iopub.status.idle":"2026-10-19T17:30:11.306136Z","shell.execute_reply.started":"2026-10-19T17:29:35.497098Z","shell.execute_reply":"2026-10-19T17:30:11.305335Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"EYE_DIR   = \"/kaggle/input/datasets/faizan729/diabetic-retinopathy-detection\"\nAPTOS_DIR = \"/kaggle/input/competitions/aptos2019-blindness-detection\"\n\nIMG_SIZE   = (64, 64)     # 128 → 64, 4x faster per epoch\nBATCH_SIZE = 64           # 32 → 64, better GPU utilization\nEPOCHS     = 10\nLR         = 0.0001\nNUM_CLASSES = 5\n\nCLASS_NAMES = ['No_DR', 'Mild', 'Moderate', 'Severe', 'Proliferate_DR']\nprint(\"Config ready ✓\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-19T17:30:11.308085Z","iopub.execute_input":"2026-10-19T17:30:11.308644Z","iopub.status.idle":"2026-10-19T17:30:11.314315Z","shell.execute_reply.started":"2026-10-19T17:30:11.308615Z","shell.execute_reply":"2026-10-19T17:30:11.3135Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"CLASS_MAP = {' No_DR': 0, ' Mild': 1, ' Moderate': 2,\n             ' Severe': 3, ' Proliferate_DR': 4}\n\ndef load_eye_split(base_dir, split):\n    split_dir = os.path.join(base_dir, split)\n    df = pd.read_csv(os.path.join(split_dir, '_classes.csv'))\n    cols = [' No_DR', ' Mild', ' Moderate', ' Severe', ' Proliferate_DR']\n    df['label']    = df[cols].idxmax(axis=1).map(CLASS_MAP).astype(str)\n    df['filepath'] = df['filename'].apply(lambda x: os.path.join(split_dir, x))\n    df = df[df['filepath'].apply(os.path.exists)][['filepath', 'label']]\n    print(f\"  EYE [{split}]: {len(df)} images\")\n    return df\n\ndef load_aptos(base_dir):\n    df = pd.read_csv(os.path.join(base_dir, 'train.csv'))\n    df['label']    = df['diagnosis'].astype(str)\n    df['filepath'] = df['id_code'].apply(\n        lambda x: os.path.join(base_dir, 'train_images', x + '.png'))\n    df = df[df['filepath'].apply(os.path.exists)][['filepath', 'label']]\n    print(f\"  APTOS: {len(df)} images\")\n    return df\n\nprint(\"Loading datasets...\")\ndf_all = pd.concat([\n    load_eye_split(EYE_DIR, 'train'),\n    load_eye_split(EYE_DIR, 'valid'),\n    load_eye_split(EYE_DIR, 'test'),\n    load_aptos(APTOS_DIR)\n], ignore_index=True).sample(frac=1, random_state=42).reset_index(drop=True)\n\nprint(f\"\\nTotal images : {len(df_all)}\")\nprint(\"Class distribution:\")\nprint(df_all['label'].value_counts().sort_index())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-19T17:30:11.315447Z","iopub.execute_input":"2026-10-19T17:30:11.316171Z","iopub.status.idle":"2026-10-19T17:30:41.375819Z","shell.execute_reply.started":"2026-10-19T17:30:11.316145Z","shell.execute_reply":"2026-10-19T17:30:41.375073Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_df, temp_df = train_test_split(\n    df_all, test_size=0.2, stratify=df_all['label'], random_state=42)\nval_df, test_df = train_test_split(\n    temp_df, test_size=0.5, stratify=temp_df['label'], random_state=42)\n\nprint(f\"Train : {len(train_df)}\")\nprint(f\"Val   : {len(val_df)}\")\nprint(f\"Test  : {len(test_df)}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-19T17:30:41.376879Z","iopub.execute_input":"2026-10-19T17:30:41.377219Z","iopub.status.idle":"2026-10-19T17:30:41.406029Z","shell.execute_reply.started":"2026-10-19T17:30:41.377195Z","shell.execute_reply":"2026-10-19T17:30:41.405325Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Only rescale — no augmentation anywhere (train/val/test same)\ndatagen = ImageDataGenerator(rescale=1./255)\n\ntrain_gen = datagen.flow_from_dataframe(\n    dataframe=train_df,\n    x_col='filepath', y_col='label',\n    target_size=IMG_SIZE,\n    batch_size=BATCH_SIZE,\n    class_mode='sparse',\n    shuffle=True\n)\n\nval_gen = datagen.flow_from_dataframe(\n    dataframe=val_df,\n    x_col='filepath', y_col='label',\n    target_size=IMG_SIZE,\n    batch_size=BATCH_SIZE,\n    class_mode='sparse',\n    shuffle=False\n)\n\ntest_gen = datagen.flow_from_dataframe(\n    dataframe=test_df,\n    x_col='filepath', y_col='label',\n    target_size=IMG_SIZE,\n    batch_size=BATCH_SIZE,\n    class_mode='sparse',\n    shuffle=False\n)\n\nprint(\"Generators ready ✓\")\nprint(\"Class indices:\", train_gen.class_indices)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-19T17:30:41.407345Z","iopub.execute_input":"2026-10-19T17:30:41.40788Z","iopub.status.idle":"2026-10-19T17:30:48.157836Z","shell.execute_reply.started":"2026-10-19T17:30:41.407854Z","shell.execute_reply":"2026-10-19T17:30:48.157045Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# Pure VGG16 architecture — trained from scratch\n# No pretrained weights | No BatchNorm | No transfer learning\n# Dropout(0.5) only in the two FC Dense layers\n# ============================================================\n\n# Clear previous model from memory first\ntf.keras.backend.clear_session()\n\ndef build_vgg16_scratch(num_classes=5, input_shape=(64, 64, 3)):  # ← 64x64\n    model = Sequential([\n\n        # ── Block 1 ────────────────────────────────────────\n        Conv2D(64, (3,3), activation='relu', padding='same',\n               input_shape=input_shape, name='block1_conv1'),\n        Conv2D(64, (3,3), activation='relu', padding='same',\n               name='block1_conv2'),\n        MaxPooling2D((2,2), strides=(2,2), name='block1_pool'),\n\n        # ── Block 2 ────────────────────────────────────────\n        Conv2D(128, (3,3), activation='relu', padding='same',\n               name='block2_conv1'),\n        Conv2D(128, (3,3), activation='relu', padding='same',\n               name='block2_conv2'),\n        MaxPooling2D((2,2), strides=(2,2), name='block2_pool'),\n\n        # ── Block 3 ────────────────────────────────────────\n        Conv2D(256, (3,3), activation='relu', padding='same',\n               name='block3_conv1'),\n        Conv2D(256, (3,3), activation='relu', padding='same',\n               name='block3_conv2'),\n        Conv2D(256, (3,3), activation='relu', padding='same',\n               name='block3_conv3'),\n        MaxPooling2D((2,2), strides=(2,2), name='block3_pool'),\n\n        # ── Block 4 ────────────────────────────────────────\n        Conv2D(512, (3,3), activation='relu', padding='same',\n               name='block4_conv1'),\n        Conv2D(512, (3,3), activation='relu', padding='same',\n               name='block4_conv2'),\n        Conv2D(512, (3,3), activation='relu', padding='same',\n               name='block4_conv3'),\n        MaxPooling2D((2,2), strides=(2,2), name='block4_pool'),\n\n        # ── Block 5 ────────────────────────────────────────\n        Conv2D(512, (3,3), activation='relu', padding='same',\n               name='block5_conv1'),\n        Conv2D(512, (3,3), activation='relu', padding='same',\n               name='block5_conv2'),\n        Conv2D(512, (3,3), activation='relu', padding='same',\n               name='block5_conv3'),\n        MaxPooling2D((2,2), strides=(2,2), name='block5_pool'),\n\n        # ── Classifier Head ────────────────────────────────\n        Flatten(),                                  # 64x64 → 2x2x512 = 2048\n        Dense(4096, activation='relu', name='fc1'),\n        Dropout(0.5),\n        Dense(4096, activation='relu', name='fc2'),\n        Dropout(0.5),\n        Dense(num_classes, activation='softmax', name='predictions')\n    ])\n    return model\n\nmodel = build_vgg16_scratch(num_classes=NUM_CLASSES, input_shape=(64, 64, 3))\n\nmodel.compile(\n    optimizer=tf.keras.optimizers.Adam(learning_rate=LR),\n    loss='sparse_categorical_crossentropy',\n    metrics=['accuracy']\n)\n\nmodel.summary()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-19T17:30:48.159028Z","iopub.execute_input":"2026-10-19T17:30:48.159423Z","iopub.status.idle":"2026-10-19T17:30:51.81462Z","shell.execute_reply.started":"2026-10-19T17:30:48.159384Z","shell.execute_reply":"2026-10-19T17:30:51.813935Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# Simple training — no callbacks, no early stopping\n# Trains all 20 epochs regardless\n# ============================================================\n\nprint(\"Training VGG16 from scratch (20 epochs)...\")\n\nhistory = model.fit(\n    train_gen,\n    validation_data=val_gen,\n    epochs=EPOCHS,      # 20 fixed\n    verbose=1\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-19T17:38:52.378248Z","iopub.execute_input":"2026-10-19T17:38:52.37938Z","iopub.status.idle":"2026-10-19T19:01:33.445953Z","shell.execute_reply.started":"2026-10-19T17:38:52.379332Z","shell.execute_reply":"2026-10-19T19:01:33.445212Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}