{"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":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session\n\n# Use the kagglehub client library to attach Kaggle resources like competitions, datasets, and models to your session\n# Learn more about kagglehub: https://github.com/Kaggle/kagglehub/blob/main/README.md\n\nimport kagglehub\n# kagglehub.dataset_download('<owner>/<dataset-slug>')","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\n\npaths = [\n    \"/kaggle/input\",\n]\n\nfor p in paths:\n    print(p)\n    print(os.listdir(p))","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\n\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    print(dirname)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\n\nprint(os.path.exists('/kaggle/input/competitions/aptos2019-blindness-detection/train.csv'))\nprint(os.path.exists('/kaggle/input/competitions/aptos2019-blindness-detection/train_images'))","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ===== CELL 0a: SEED + DETERMINISM (must be first) =====\nimport os, random\nimport numpy as np\nimport tensorflow as tf\n\nSEED = 42\nos.environ['PYTHONHASHSEED'] = str(SEED)\nos.environ['TF_DETERMINISTIC_OPS'] = '1'\n\nrandom.seed(SEED)\nnp.random.seed(SEED)\ntf.random.set_seed(SEED)\n\nprint(f\"Seed {SEED} set, TF {tf.__version__}\")\nprint(\"GPUs:\", tf.config.list_physical_devices('GPU'))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-31T15:19:14.846808Z","iopub.execute_input":"2026-07-31T15:19:14.847067Z","iopub.status.idle":"2026-07-31T15:19:38.109954Z","shell.execute_reply.started":"2026-07-31T15:19:14.847043Z","shell.execute_reply":"2026-07-31T15:19:38.109283Z"}},"outputs":[{"name":"stdout","text":"Seed 42 set, TF 2.20.0\nGPUs: [PhysicalDevice(name='/physical_device:GPU:0', device_type='GPU'), PhysicalDevice(name='/physical_device:GPU:1', device_type='GPU')]\n","output_type":"stream"}],"execution_count":2},{"cell_type":"code","source":"# ===== CELL 0b: IMPORTS =====\n\nimport pandas as pd\n\nfrom tensorflow.keras.preprocessing.image import ImageDataGenerator\nfrom tensorflow.keras.applications import EfficientNetB0, MobileNetV2, ResNet50\nfrom tensorflow.keras.applications.efficientnet import preprocess_input as eff_pre\nfrom tensorflow.keras.applications.mobilenet_v2 import preprocess_input as mob_pre\nfrom tensorflow.keras.applications.resnet50 import preprocess_input as res_pre\n\nfrom tensorflow.keras.models import Sequential, load_model\nfrom tensorflow.keras.layers import (\n    Input,\n    Conv2D,\n    BatchNormalization,\n    MaxPooling2D,\n    Dropout,\n    GlobalAveragePooling2D,\n    Dense\n)\n\nfrom tensorflow.keras.optimizers import Adam\nfrom tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint, CSVLogger\n\nfrom sklearn.utils.class_weight import compute_class_weight\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import (\n    cohen_kappa_score,\n    confusion_matrix,\n    classification_report\n)\n\nprint(\"Imports ready\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-31T15:20:07.745882Z","iopub.execute_input":"2026-07-31T15:20:07.746343Z","iopub.status.idle":"2026-07-31T15:20:07.752811Z","shell.execute_reply.started":"2026-07-31T15:20:07.746318Z","shell.execute_reply":"2026-07-31T15:20:07.751898Z"}},"outputs":[{"name":"stdout","text":"Imports ready\n","output_type":"stream"}],"execution_count":4},{"cell_type":"code","source":"# ===== CELL 0c: LOCKED CONFIGURATION =====\n\n# ---------- PATHS ----------\nAPTOS_CSV     = '/kaggle/input/competitions/aptos2019-blindness-detection/train.csv'\nAPTOS_IMG     = '/kaggle/input/competitions/aptos2019-blindness-detection/train_images'\n\nEYEPACS_CSV   = '/kaggle/input/datasets/benjaminwarner/resized-2015-2019-blindness-detection-images/labels/trainLabels15.csv'\nEYEPACS_IMG   = '/kaggle/input/datasets/benjaminwarner/resized-2015-2019-blindness-detection-images/resized train 15'\n\nMESSIDOR_CSV  = '/kaggle/input/datasets/mariaherrerot/messidor2preprocess/messidor_data.csv'\nMESSIDOR_IMG  = '/kaggle/input/datasets/mariaherrerot/messidor2preprocess/messidor-2/messidor-2/preprocess'\n\nWORK_DIR      = '/kaggle/working/'\n\n# ---------- CLASSES ----------\nGRADES = ['0', '1', '2', '3', '4']\nNUM_CLASSES = 5\n\n# ---------- IMAGE / BATCH ----------\nIMG_SIZE = 224\nBATCH_SIZE = 32\n\n# ---------- SEED ----------\nSEED = 42\nCV_SEEDS = [42, 123, 456, 789, 1024]\n\n# ---------- TRAINING ----------\nPHASE1_EPOCHS = 10\nPHASE1_LR = 1e-3\nPHASE2_LR = 1e-5\nCUSTOM_LR = 1e-3\nEARLYSTOP_PAT = 7\n\n# ---------- AUGMENTATION ----------\nAUG = dict(\n    rotation_range=20,\n    width_shift_range=0.1,\n    height_shift_range=0.1,\n    horizontal_flip=True,\n    zoom_range=0.1\n)\n\n# ---------- WEIGHT FILE NAMES ----------\nW_CUSTOM = 'dr_best_custom_cnn.keras'\nW_EFF    = 'dr_best_efficientnet.keras'\nW_MOB    = 'dr_best_mobilenet.keras'\nW_RES    = 'dr_best_resnet50.keras'\n\nprint(\"Configuration loaded successfully.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-31T15:20:18.218764Z","iopub.execute_input":"2026-07-31T15:20:18.219504Z","iopub.status.idle":"2026-07-31T15:20:18.22584Z","shell.execute_reply.started":"2026-07-31T15:20:18.219466Z","shell.execute_reply":"2026-07-31T15:20:18.224999Z"}},"outputs":[{"name":"stdout","text":"Configuration loaded successfully.\n","output_type":"stream"}],"execution_count":6},{"cell_type":"code","source":"# ===== CELL 1a: LOAD ALL THREE SOURCES =====\n\ndef load_source(csv_path, img_dir, id_col, grade_col, img_ext, source_name):\n    df = pd.read_csv(csv_path)\n    df = df[df[grade_col].notna()]\n    df['grade'] = df[grade_col].astype(int).astype(str)\n    df['image_path'] = df[id_col].astype(str).apply(\n        lambda x: os.path.join(img_dir, x + img_ext)\n    )\n    df['source'] = source_name\n    df = df[df['image_path'].apply(os.path.exists)]\n    return df[['image_path', 'grade', 'source']].reset_index(drop=True)\n\naptos = load_source(\n    APTOS_CSV,\n    APTOS_IMG,\n    'id_code',\n    'diagnosis',\n    '.png',\n    'aptos'\n)\n\neyepacs = load_source(\n    EYEPACS_CSV,\n    EYEPACS_IMG,\n    'image',\n    'level',\n    '.jpg',\n    'eyepacs'\n)\n\nmessidor = load_source(\n    MESSIDOR_CSV,\n    MESSIDOR_IMG,\n    'id_code',\n    'diagnosis',\n    '',\n    'messidor'\n)\n\nprint(\"Datasets loaded successfully.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-31T15:20:30.520535Z","iopub.execute_input":"2026-07-31T15:20:30.520963Z","iopub.status.idle":"2026-07-31T15:22:48.479382Z","shell.execute_reply.started":"2026-07-31T15:20:30.520934Z","shell.execute_reply":"2026-07-31T15:22:48.478464Z"}},"outputs":[{"name":"stdout","text":"Datasets loaded successfully.\n","output_type":"stream"}],"execution_count":7},{"cell_type":"code","source":"# ===== CELL 1b: POOL + VERIFY =====\n\nmaster_df = pd.concat([aptos, eyepacs, messidor], ignore_index=True)\n\nprint(f\"APTOS {len(aptos):,} | EyePACS {len(eyepacs):,} | Messidor {len(messidor):,} | Pooled {len(master_df):,}\")\n\nprint(\"\\nGrade x source:\")\nprint(pd.crosstab(master_df['grade'], master_df['source']).reindex(GRADES))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-31T15:22:54.233082Z","iopub.execute_input":"2026-07-31T15:22:54.233678Z","iopub.status.idle":"2026-07-31T15:22:54.293105Z","shell.execute_reply.started":"2026-07-31T15:22:54.233651Z","shell.execute_reply":"2026-07-31T15:22:54.29237Z"}},"outputs":[{"name":"stdout","text":"APTOS 3,662 | EyePACS 35,126 | Messidor 1,744 | Pooled 40,532\n\nGrade x source:\nsource  aptos  eyepacs  messidor\ngrade                           \n0        1805    25810      1017\n1         370     2443       270\n2         999     5292       347\n3         193      873        75\n4         295      708        35\n","output_type":"stream"}],"execution_count":8},{"cell_type":"code","source":"# ===== CELL 2a: LEAKAGE-SAFE SPLIT =====\n\ndef split_image_level(df, rs=42):\n    tr, tmp = train_test_split(df, test_size=0.30, stratify=df['grade'], random_state=rs)\n    va, te = train_test_split(tmp, test_size=0.50, stratify=tmp['grade'], random_state=rs)\n    return tr.reset_index(drop=True), va.reset_index(drop=True), te.reset_index(drop=True)\n\ndef split_patient_level(df, pid_fn, rs=42):\n    df = df.copy()\n    df['pid'] = df['image_path'].apply(lambda p: pid_fn(os.path.basename(p)))\n\n    pg = df.groupby('pid')['grade'].apply(lambda g: g.astype(int).max()).reset_index()\n    pg.columns = ['pid', 'pg']\n\n    p_tr, p_tmp = train_test_split(\n        pg,\n        test_size=0.30,\n        stratify=pg['pg'],\n        random_state=rs\n    )\n\n    p_va, p_te = train_test_split(\n        p_tmp,\n        test_size=0.50,\n        stratify=p_tmp['pg'],\n        random_state=rs\n    )\n\n    def pick(ids):\n        return df[df['pid'].isin(ids['pid'])].drop(columns='pid').reset_index(drop=True)\n\n    return (\n        pick(p_tr),\n        pick(p_va),\n        pick(p_te),\n        set(p_tr['pid']),\n        set(p_va['pid']),\n        set(p_te['pid'])\n    )\n\n# EyePACS patient id\neyepacs_pid = lambda fname: fname.split('_')[0]\n\n# Messidor patient id\ndef messidor_pid(fname):\n    stem = os.path.splitext(fname)[0]\n    parts = stem.split('_')\n\n    if len(parts) >= 2:\n        return parts[1]\n\n    if stem.upper().startswith('IM'):\n        num = int(''.join(c for c in stem if c.isdigit()))\n        return f'IM_{num - (num % 2)}'\n\n    return stem\n\na_tr, a_va, a_te = split_image_level(aptos)\n\ne_tr, e_va, e_te, es_tr, es_va, es_te = split_patient_level(\n    eyepacs,\n    eyepacs_pid\n)\n\nm_tr, m_va, m_te, ms_tr, ms_va, ms_te = split_patient_level(\n    messidor,\n    messidor_pid\n)\n\nassert es_tr.isdisjoint(es_te) and es_tr.isdisjoint(es_va) and es_va.isdisjoint(es_te), \"EYEPACS PATIENT LEAKAGE\"\n\nassert ms_tr.isdisjoint(ms_te) and ms_tr.isdisjoint(ms_va) and ms_va.isdisjoint(ms_te), \"MESSIDOR PATIENT LEAKAGE\"\n\nprint(\"EyePACS leakage check: PASS\")\nprint(\"Messidor leakage check: PASS\")\n\ntrain_df = pd.concat([a_tr, e_tr, m_tr], ignore_index=True)\nval_df = pd.concat([a_va, e_va, m_va], ignore_index=True)\ntest_df = pd.concat([a_te, e_te, m_te], ignore_index=True)\n\nfor name, d in [\n    ('train', train_df),\n    ('val', val_df),\n    ('test', test_df)\n]:\n    d.to_csv(f'/kaggle/working/dr_{name}_split.csv', index=False)\n\nprint(f\"\\nTrain {len(train_df):,} | Val {len(val_df):,} | Test {len(test_df):,} | Pooled {len(train_df)+len(val_df)+len(test_df):,}\")\n\nprint(\"\\nTest grade distribution:\")\nprint(test_df['grade'].value_counts().reindex(GRADES))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-31T15:23:25.83863Z","iopub.execute_input":"2026-07-31T15:23:25.838956Z","iopub.status.idle":"2026-07-31T15:23:27.660181Z","shell.execute_reply.started":"2026-07-31T15:23:25.838932Z","shell.execute_reply":"2026-07-31T15:23:27.659508Z"}},"outputs":[{"name":"stdout","text":"EyePACS leakage check: PASS\nMessidor leakage check: PASS\n\nTrain 28,360 | Val 6,089 | Test 6,083 | Pooled 40,532\n\nTest grade distribution:\ngrade\n0    4297\n1     465\n2     995\n3     169\n4     157\nName: count, dtype: int64\n","output_type":"stream"}],"execution_count":9},{"cell_type":"code","source":"# ===== CELL 2b: BALANCED CLASS WEIGHTS =====\n\ncls = np.array(GRADES)\n\ncw = compute_class_weight(\n    class_weight='balanced',\n    classes=cls,\n    y=train_df['grade']\n)\n\nCLASS_WEIGHT = {i: w for i, w in enumerate(cw)}\n\nprint(\"class_weight:\")\nfor c, w in zip(cls, cw):\n    print(f\"Grade {c}: {w:.3f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-31T15:23:44.241508Z","iopub.execute_input":"2026-07-31T15:23:44.242292Z","iopub.status.idle":"2026-07-31T15:23:44.255517Z","shell.execute_reply.started":"2026-07-31T15:23:44.24226Z","shell.execute_reply":"2026-07-31T15:23:44.254767Z"}},"outputs":[{"name":"stdout","text":"class_weight:\nGrade 0: 0.283\nGrade 1: 2.632\nGrade 2: 1.222\nGrade 3: 7.108\nGrade 4: 7.813\n","output_type":"stream"}],"execution_count":10},{"cell_type":"code","source":"# ===== CELL 2c: GENERATOR FACTORY =====\n\ndef make_gens(preprocess_fn):\n    \"\"\"\n    preprocess_fn=None -> Custom CNN (rescale 1./255)\n    preprocess_fn=eff_pre/mob_pre/res_pre -> Pretrained models\n    \"\"\"\n\n    if preprocess_fn is None:\n        train_idg = ImageDataGenerator(\n            rescale=1./255,\n            **AUG\n        )\n        eval_idg = ImageDataGenerator(\n            rescale=1./255\n        )\n    else:\n        train_idg = ImageDataGenerator(\n            preprocessing_function=preprocess_fn,\n            **AUG\n        )\n        eval_idg = ImageDataGenerator(\n            preprocessing_function=preprocess_fn\n        )\n\n    common = dict(\n        x_col='image_path',\n        y_col='grade',\n        target_size=(IMG_SIZE, IMG_SIZE),\n        batch_size=BATCH_SIZE,\n        class_mode='categorical',\n        classes=GRADES\n    )\n\n    tr = train_idg.flow_from_dataframe(\n        train_df,\n        shuffle=True,\n        seed=SEED,\n        **common\n    )\n\n    va = eval_idg.flow_from_dataframe(\n        val_df,\n        shuffle=False,\n        **common\n    )\n\n    te = eval_idg.flow_from_dataframe(\n        test_df,\n        shuffle=False,\n        **common\n    )\n\n    return tr, va, te\n\n_t, _v, _e = make_gens(None)\n\nprint(_e.class_indices)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-31T15:23:49.333032Z","iopub.execute_input":"2026-07-31T15:23:49.333419Z","iopub.status.idle":"2026-07-31T15:24:09.468688Z","shell.execute_reply.started":"2026-07-31T15:23:49.333393Z","shell.execute_reply":"2026-07-31T15:24:09.467862Z"}},"outputs":[{"name":"stdout","text":"Found 28360 validated image filenames belonging to 5 classes.\nFound 6089 validated image filenames belonging to 5 classes.\nFound 6083 validated image filenames belonging to 5 classes.\n{'0': 0, '1': 1, '2': 2, '3': 3, '4': 4}\n","output_type":"stream"}],"execution_count":11},{"cell_type":"code","source":"# ===== CELL 3a: CUSTOM CNN (3-block, from scratch) =====\n\ndef build_custom_cnn(num_classes=5, shape=(224,224,3)):\n    return Sequential([\n        Input(shape=shape),\n\n        Conv2D(32, 3, padding='same', activation='relu'),\n        BatchNormalization(),\n        Conv2D(32, 3, padding='same', activation='relu'),\n        BatchNormalization(),\n        MaxPooling2D(),\n        Dropout(0.25),\n\n        Conv2D(64, 3, padding='same', activation='relu'),\n        BatchNormalization(),\n        Conv2D(64, 3, padding='same', activation='relu'),\n        BatchNormalization(),\n        MaxPooling2D(),\n        Dropout(0.25),\n\n        Conv2D(128, 3, padding='same', activation='relu'),\n        BatchNormalization(),\n        Conv2D(128, 3, padding='same', activation='relu'),\n        BatchNormalization(),\n        MaxPooling2D(),\n        Dropout(0.25),\n\n        GlobalAveragePooling2D(),\n\n        Dense(256, activation='relu'),\n        Dropout(0.5),\n\n        Dense(num_classes, activation='softmax')\n    ])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-31T15:25:17.619096Z","iopub.execute_input":"2026-07-31T15:25:17.619456Z","iopub.status.idle":"2026-07-31T15:25:17.625776Z","shell.execute_reply.started":"2026-07-31T15:25:17.619417Z","shell.execute_reply":"2026-07-31T15:25:17.625103Z"}},"outputs":[],"execution_count":12},{"cell_type":"code","source":"# ===== CELL 3b: PRETRAINED BUILDER =====\n\ndef build_pretrained(base_class, num_classes=5, shape=(224,224,3)):\n    base = base_class(\n        include_top=False,\n        weights='imagenet',\n        input_shape=shape\n    )\n\n    model = Sequential([\n        base,\n        GlobalAveragePooling2D(),\n        Dense(256, activation='relu'),\n        Dropout(0.3),\n        Dense(num_classes, activation='softmax')\n    ])\n\n    return model, base\n\nprint(build_custom_cnn().count_params(), \"custom params\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-31T15:25:32.936752Z","iopub.execute_input":"2026-07-31T15:25:32.937031Z","iopub.status.idle":"2026-07-31T15:25:35.397208Z","shell.execute_reply.started":"2026-07-31T15:25:32.937007Z","shell.execute_reply":"2026-07-31T15:25:35.396391Z"}},"outputs":[{"name":"stderr","text":"WARNING: All log messages before absl::InitializeLog() is called are written to STDERR\nI0000 00:00:1785511533.379856      58 gpu_device.cc:2020] Created device /job:localhost/replica:0/task:0/device:GPU:0 with 13756 MB memory:  -> device: 0, name: Tesla T4, pci bus id: 0000:00:04.0, compute capability: 7.5\nI0000 00:00:1785511533.385763      58 gpu_device.cc:2020] Created device /job:localhost/replica:0/task:0/device:GPU:1 with 13756 MB memory:  -> device: 1, name: Tesla T4, pci bus id: 0000:00:05.0, compute capability: 7.5\n","output_type":"stream"},{"name":"stdout","text":"323109 custom params\n","output_type":"stream"}],"execution_count":13},{"cell_type":"code","source":"# ===== STAGE 4: TRAIN CUSTOM CNN =====\n\nmodel = build_custom_cnn()\n\ntr, va, te = make_gens(None)\n\nmodel.compile(\n    optimizer=Adam(learning_rate=CUSTOM_LR),\n    loss='categorical_crossentropy',\n    metrics=['accuracy']\n)\n\ncallbacks = [\n    EarlyStopping(\n        monitor='val_accuracy',\n        patience=EARLYSTOP_PAT,\n        restore_best_weights=True,\n        verbose=1\n    ),\n    ModelCheckpoint(\n        filepath=f\"/kaggle/working/{W_CUSTOM}\",\n        monitor='val_accuracy',\n        save_best_only=True,\n        verbose=1\n    ),\n    CSVLogger(\"/kaggle/working/dr_custom_log.csv\")\n]\n\nhistory = model.fit(\n    tr,\n    validation_data=va,\n    epochs=60,\n    class_weight=CLASS_WEIGHT,\n    callbacks=callbacks,\n    verbose=1\n)\n\nprint(\"\\nEvaluating best model...\\n\")\n\ntest_loss, test_acc = model.evaluate(te, verbose=1)\n\nprint(f\"\\nTest Accuracy: {test_acc:.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-31T15:26:24.128841Z","iopub.execute_input":"2026-07-31T15:26:24.129281Z"}},"outputs":[{"name":"stdout","text":"Found 28360 validated image filenames belonging to 5 classes.\nFound 6089 validated image filenames belonging to 5 classes.\nFound 6083 validated image filenames belonging to 5 classes.\nEpoch 1/60\n","output_type":"stream"},{"name":"stderr","text":"E0000 00:00:1785511618.118942      58 meta_optimizer.cc:967] layout failed: INVALID_ARGUMENT: Size of values 0 does not match size of permutation 4 @ fanin shape inStatefulPartitionedCall/sequential_1_1/dropout_4_1/stateless_dropout/SelectV2-2-TransposeNHWCToNCHW-LayoutOptimizer\n","output_type":"stream"},{"name":"stdout","text":"\u001b[1m887/887\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m0s\u001b[0m 1s/step - accuracy: 0.2110 - loss: 1.6939\nEpoch 1: val_accuracy improved from None to 0.56348, saving model to /kaggle/working/dr_best_custom_cnn.keras\n\nEpoch 1: finished saving model to /kaggle/working/dr_best_custom_cnn.keras\n\u001b[1m887/887\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m1424s\u001b[0m 2s/step - accuracy: 0.2435 - loss: 1.6318 - val_accuracy: 0.5635 - val_loss: 1.4326\nEpoch 2/60\n\u001b[1m887/887\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m0s\u001b[0m 1s/step - accuracy: 0.3333 - loss: 1.6088\nEpoch 2: val_accuracy did not improve from 0.56348\n\u001b[1m887/887\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m1063s\u001b[0m 1s/step - accuracy: 0.3698 - loss: 1.5931 - val_accuracy: 0.3015 - val_loss: 1.7026\nEpoch 3/60\n\u001b[1m887/887\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m0s\u001b[0m 1s/step - accuracy: 0.3952 - loss: 1.6054\nEpoch 3: val_accuracy improved from 0.56348 to 0.70077, saving model to /kaggle/working/dr_best_custom_cnn.keras\n\nEpoch 3: finished saving model to /kaggle/working/dr_best_custom_cnn.keras\n\u001b[1m887/887\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m1072s\u001b[0m 1s/step - accuracy: 0.4146 - loss: 1.5895 - val_accuracy: 0.7008 - val_loss: 1.2759\nEpoch 4/60\n\u001b[1m152/887\u001b[0m \u001b[32m━━━\u001b[0m\u001b[37m━━━━━━━━━━━━━━━━━\u001b[0m \u001b[1m13:54\u001b[0m 1s/step - accuracy: 0.4748 - loss: 1.6138","output_type":"stream"}],"execution_count":null},{"cell_type":"code","source":"import os\n\nfiles = [\n    \"/kaggle/working/dr_best_custom_cnn.keras\",\n    \"/kaggle/working/dr_custom_log.csv\",\n    \"/kaggle/working/dr_train_split.csv\",\n    \"/kaggle/working/dr_val_split.csv\",\n    \"/kaggle/working/dr_test_split.csv\"\n]\n\nfor f in files:\n    print(f)\n    print(\"Exists:\", os.path.exists(f))\n    if os.path.exists(f):\n        print(\"Size:\", os.path.getsize(f))\n    print(\"-\"*50)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-31T14:59:06.350352Z","iopub.execute_input":"2026-07-31T14:59:06.351065Z","iopub.status.idle":"2026-07-31T14:59:06.359489Z","shell.execute_reply.started":"2026-07-31T14:59:06.351034Z","shell.execute_reply":"2026-07-31T14:59:06.358692Z"}},"outputs":[{"name":"stdout","text":"/kaggle/working/dr_best_custom_cnn.keras\nExists: False\n--------------------------------------------------\n/kaggle/working/dr_custom_log.csv\nExists: False\n--------------------------------------------------\n/kaggle/working/dr_train_split.csv\nExists: False\n--------------------------------------------------\n/kaggle/working/dr_val_split.csv\nExists: False\n--------------------------------------------------\n/kaggle/working/dr_test_split.csv\nExists: False\n--------------------------------------------------\n","output_type":"stream"}],"execution_count":1}]}