{"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":"gpu","dataSources":[{"sourceId":14774,"databundleVersionId":875431,"sourceType":"competition"}],"dockerImageVersionId":31192,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Project Introduction","metadata":{}},{"cell_type":"markdown","source":"Diabetic Retinopathy is the leading cause of blindness in adults, with an estimated 103 million cases worldwide in 2020. Early detection is crucial to treatment, and can be time consuming. It is also a personal matter for me, as my mother was diagnosed with the condition in 2010, about two years too late to save her eyesight.","metadata":{}},{"cell_type":"markdown","source":"## Datasets Used","metadata":{}},{"cell_type":"markdown","source":"Using the Aptos 2019 competition dataset. It uses a train csv with image id_code and diagnosis, a test csv with just an image id_code, and matching folders containing the referenced images.\n","metadata":{}},{"cell_type":"markdown","source":"### Labels\n\n*  0 - No DR\n\n*   1 - Mild\n\n*    2 - Moderate\n\n*    3 - Severe\n\n*    4 - Proliferative DR\n","metadata":{}},{"cell_type":"markdown","source":"## Project Goals","metadata":{}},{"cell_type":"markdown","source":"* Establish baseline by implementing CNN trained on undoctored images\n* Enhance supplied images, then implement CNN trained on enhanced imageset\n* Implement Transfer learning model\n* Evaluate performance metrics, determine where improvements could be made, time allowing implement improvements","metadata":{}},{"cell_type":"markdown","source":"# Environment Setup","metadata":{}},{"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\n\nfor dirname, dirnames, _ in os.walk('/kaggle/input'):\n    print(dirname)\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-12-04T18:40:00.846787Z","iopub.execute_input":"2025-12-04T18:40:00.847279Z","iopub.status.idle":"2025-12-04T18:40:06.172142Z","shell.execute_reply.started":"2025-12-04T18:40:00.847254Z","shell.execute_reply":"2025-12-04T18:40:06.171437Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import tensorflow as tf\nimport zipfile\nimport matplotlib.pyplot as plt\n\nfrom tensorflow.keras.models import Sequential\nfrom functools import partial\nfrom tensorflow.keras import Input\nfrom tensorflow.keras.models import Model\nfrom tensorflow.keras.layers import Dense, GlobalAveragePooling2D\nfrom tensorflow.keras.layers import Embedding, Conv1D, GlobalMaxPooling1D, Dense, Dropout\nfrom tensorflow.keras import layers\nfrom tensorflow.keras.models import clone_model\nfrom tensorflow.keras.optimizers import Adam\nfrom sklearn.metrics import confusion_matrix\nimport seaborn as sns\n\nfrom sklearn.model_selection import train_test_split\n\nplt.rc('font', size=14)\nplt.rc('axes', labelsize=14, titlesize=14)\nplt.rc('legend', fontsize=14)\nplt.rc('xtick', labelsize=10)\nplt.rc('ytick', labelsize=10)\n\ntf.random.set_seed(72)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-04T19:51:05.439868Z","iopub.execute_input":"2025-12-04T19:51:05.440148Z","iopub.status.idle":"2025-12-04T19:51:05.472212Z","shell.execute_reply.started":"2025-12-04T19:51:05.440127Z","shell.execute_reply":"2025-12-04T19:51:05.471481Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Data Import","metadata":{}},{"cell_type":"code","source":"train_path = '/kaggle/input/aptos2019-blindness-detection/train_images'\ntest_path = '/kaggle/input/aptos2019-blindness-detection/test_images'","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-04T18:40:23.196367Z","iopub.execute_input":"2025-12-04T18:40:23.197109Z","iopub.status.idle":"2025-12-04T18:40:23.200415Z","shell.execute_reply.started":"2025-12-04T18:40:23.197089Z","shell.execute_reply":"2025-12-04T18:40:23.199795Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"Number of training images:\", len(os.listdir(train_path)))\nprint(\"Number of test images:\", len(os.listdir(test_path)))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-04T18:40:23.201120Z","iopub.execute_input":"2025-12-04T18:40:23.201417Z","iopub.status.idle":"2025-12-04T18:40:23.218223Z","shell.execute_reply.started":"2025-12-04T18:40:23.201400Z","shell.execute_reply":"2025-12-04T18:40:23.217688Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Number of images is a little less than initially hoped, but good enough to start with","metadata":{}},{"cell_type":"code","source":"train = pd.read_csv(\"/kaggle/input/aptos2019-blindness-detection/train.csv\")\ntest = pd.read_csv(\"/kaggle/input/aptos2019-blindness-detection/test.csv\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-04T18:40:23.219608Z","iopub.execute_input":"2025-12-04T18:40:23.219888Z","iopub.status.idle":"2025-12-04T18:40:23.243778Z","shell.execute_reply.started":"2025-12-04T18:40:23.219872Z","shell.execute_reply":"2025-12-04T18:40:23.243084Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class_counts = train['diagnosis'].value_counts().sort_index()\n\nprint(class_counts)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-04T21:26:16.004464Z","iopub.execute_input":"2025-12-04T21:26:16.004778Z","iopub.status.idle":"2025-12-04T21:26:16.014424Z","shell.execute_reply.started":"2025-12-04T21:26:16.004754Z","shell.execute_reply":"2025-12-04T21:26:16.013705Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train['file_path'] = train['id_code'].apply(lambda x: \n        f'/kaggle/input/aptos2019-blindness-detection/train_images/{x}.png')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-04T18:40:23.244503Z","iopub.execute_input":"2025-12-04T18:40:23.244764Z","iopub.status.idle":"2025-12-04T18:40:23.253218Z","shell.execute_reply.started":"2025-12-04T18:40:23.244743Z","shell.execute_reply":"2025-12-04T18:40:23.252449Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Data Exploration","metadata":{}},{"cell_type":"code","source":"print(train.info())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-04T18:40:24.508845Z","iopub.execute_input":"2025-12-04T18:40:24.509118Z","iopub.status.idle":"2025-12-04T18:40:24.528309Z","shell.execute_reply.started":"2025-12-04T18:40:24.509098Z","shell.execute_reply":"2025-12-04T18:40:24.527711Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Splitting into Training and Validation sets","metadata":{}},{"cell_type":"code","source":"train_df, val_df = train_test_split(\n    train, \n    test_size=0.15, \n    stratify=train['diagnosis'],\n    random_state=42\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-04T18:40:27.320775Z","iopub.execute_input":"2025-12-04T18:40:27.321072Z","iopub.status.idle":"2025-12-04T18:40:27.331949Z","shell.execute_reply.started":"2025-12-04T18:40:27.321051Z","shell.execute_reply":"2025-12-04T18:40:27.331153Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"Entries in train set\", len(train_df))\nprint(\"Entries in validation set\", len(val_df))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-04T18:40:29.432361Z","iopub.execute_input":"2025-12-04T18:40:29.432954Z","iopub.status.idle":"2025-12-04T18:40:29.436856Z","shell.execute_reply.started":"2025-12-04T18:40:29.432930Z","shell.execute_reply":"2025-12-04T18:40:29.436119Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class_names = [str(c) for c in sorted(train_df['diagnosis'].unique())]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-04T18:40:31.544650Z","iopub.execute_input":"2025-12-04T18:40:31.544973Z","iopub.status.idle":"2025-12-04T18:40:31.550265Z","shell.execute_reply.started":"2025-12-04T18:40:31.544950Z","shell.execute_reply":"2025-12-04T18:40:31.549579Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Making Data Pipeline","metadata":{}},{"cell_type":"code","source":"IMG_SIZE = 224\n\ndef load_image(path, label):\n    image = tf.io.read_file(path)\n    image = tf.image.decode_png(image, channels=3)\n    image = tf.image.resize(image, (IMG_SIZE, IMG_SIZE))\n    image = tf.cast(image, tf.float32) / 255.0\n    return image, label","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-04T18:40:34.946908Z","iopub.execute_input":"2025-12-04T18:40:34.947551Z","iopub.status.idle":"2025-12-04T18:40:34.952507Z","shell.execute_reply.started":"2025-12-04T18:40:34.947527Z","shell.execute_reply":"2025-12-04T18:40:34.951717Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def make_dataset(df, batch_size=32, shuffle=False):\n    paths = df['file_path'].values\n    labels = df['diagnosis'].values\n\n    ds = tf.data.Dataset.from_tensor_slices((paths, labels))\n    ds = ds.map(load_image, num_parallel_calls=tf.data.AUTOTUNE)\n    \n    if shuffle:\n        ds = ds.shuffle(1024)\n\n    ds = ds.batch(batch_size).prefetch(tf.data.AUTOTUNE)\n    return ds\n\ntrain_ds = make_dataset(train_df, shuffle=True)\nval_ds = make_dataset(val_df)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Setting up Early Stopping","metadata":{}},{"cell_type":"markdown","source":"I want to be able to just let my models run until they plateau and stop there. I could just do a bunch of trial runs, but I think this way would be significantly faster","metadata":{}},{"cell_type":"code","source":"from tensorflow.keras.callbacks import EarlyStopping\n\nearly_stop = EarlyStopping(\n    monitor='val_loss',      \n    patience=5,              \n    restore_best_weights=True  \n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-04T18:40:43.675858Z","iopub.execute_input":"2025-12-04T18:40:43.676349Z","iopub.status.idle":"2025-12-04T18:40:43.681664Z","shell.execute_reply.started":"2025-12-04T18:40:43.676325Z","shell.execute_reply":"2025-12-04T18:40:43.681086Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Setting up Image Preprocessing","metadata":{}},{"cell_type":"markdown","source":"I want to run my first model with absolutely no image processing beyond resizing, as I feel this will give me a really good baseline for tracking improvements. Models beyond the first will be with processed data, so I'm going to include some extra layers using the following.","metadata":{}},{"cell_type":"code","source":"data_augmentation = tf.keras.Sequential([\n    layers.RandomFlip(\"horizontal\"),\n    layers.RandomRotation(0.1),\n    layers.RandomContrast(0.2),\n])\n\ndef load_and_augment(path, label):\n    img, label = load_image(path, label)\n    img = data_augmentation(img, training=True)\n    return img, label\n\ndef make_augmented_dataset(df, batch_size=32, shuffle=True):\n    ds = tf.data.Dataset.from_tensor_slices((df['file_path'].values, df['diagnosis'].values))\n    ds = ds.map(load_and_augment, num_parallel_calls=tf.data.AUTOTUNE)\n    if shuffle:\n        ds = ds.shuffle(1024)\n    ds = ds.batch(batch_size).prefetch(tf.data.AUTOTUNE)\n    return ds\n\naugmented_ds = make_augmented_dataset(train_df)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-04T19:47:52.014210Z","iopub.execute_input":"2025-12-04T19:47:52.014825Z","iopub.status.idle":"2025-12-04T19:47:52.366152Z","shell.execute_reply.started":"2025-12-04T19:47:52.014800Z","shell.execute_reply":"2025-12-04T19:47:52.365562Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Model 1: Baseline CNN without Engineered Dataset","metadata":{}},{"cell_type":"markdown","source":"## Model Building","metadata":{}},{"cell_type":"code","source":"DefaultConv2D = partial(tf.keras.layers.Conv2D, kernel_size=3, padding=\"same\",\n                        activation=\"relu\", kernel_initializer=\"he_normal\")\n\nmodel_1 = tf.keras.Sequential([\n    Input(shape=(224, 224, 3)),\n    DefaultConv2D(filters=64, kernel_size=7,),\n    tf.keras.layers.MaxPool2D(),\n    DefaultConv2D(filters=256),\n    DefaultConv2D(filters=256),\n    tf.keras.layers.MaxPool2D(),\n    DefaultConv2D(filters=512),\n    DefaultConv2D(filters=512),\n    tf.keras.layers.MaxPool2D(),\n    tf.keras.layers.Flatten(),\n    tf.keras.layers.Dense(units=256, activation=\"relu\",\n                          kernel_initializer=\"he_normal\"),\n    tf.keras.layers.Dropout(0.5),\n    tf.keras.layers.Dense(units=128, activation=\"relu\",\n                          kernel_initializer=\"he_normal\"),\n    tf.keras.layers.Dropout(0.5),\n    tf.keras.layers.Dense(units=64, activation=\"relu\",\n                          kernel_initializer=\"he_normal\"),\n    tf.keras.layers.Dropout(0.5),\n    tf.keras.layers.Dense(units=5, activation=\"softmax\")\n])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-04T18:41:21.849371Z","iopub.execute_input":"2025-12-04T18:41:21.849842Z","iopub.status.idle":"2025-12-04T18:41:22.664343Z","shell.execute_reply.started":"2025-12-04T18:41:21.849820Z","shell.execute_reply":"2025-12-04T18:41:22.663545Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\nmodel_1.compile(\n    optimizer='adam',\n    loss='sparse_categorical_crossentropy',\n    metrics=['accuracy']\n)\nmodel_1.summary()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-04T18:41:25.210057Z","iopub.execute_input":"2025-12-04T18:41:25.210329Z","iopub.status.idle":"2025-12-04T18:41:25.241140Z","shell.execute_reply.started":"2025-12-04T18:41:25.210308Z","shell.execute_reply":"2025-12-04T18:41:25.240594Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Model Training","metadata":{}},{"cell_type":"code","source":"history_1 = model_1.fit(\n    train_ds,\n    validation_data=val_ds,\n    epochs=25,\n    callbacks=[early_stop]\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-04T18:41:42.015133Z","iopub.execute_input":"2025-12-04T18:41:42.015399Z","iopub.status.idle":"2025-12-04T19:24:26.571478Z","shell.execute_reply.started":"2025-12-04T18:41:42.015379Z","shell.execute_reply":"2025-12-04T19:24:26.570869Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Model Evaluation","metadata":{}},{"cell_type":"code","source":"h1 = history_1.history\nepochs_1 = range(1, len(h1['loss']) + 1)\nplt.plot(epochs_1, h1['loss'], 'b-', label='Model 1 Training Loss')\nplt.plot(epochs_1, h1['val_loss'], 'b--', label='Model 1 Validation Loss')\nplt.title('Training vs Validation Loss')\nplt.xlabel('Epochs')\nplt.ylabel('Loss')\nplt.legend()\n\nplt.show()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\nplt.plot(epochs_1, h1['accuracy'], 'b-', label='Model 1 Training Accuracy')\nplt.plot(epochs_1, h1['val_accuracy'], 'b--', label='Model 1 Validation Accuracy')\nplt.title('Training vs Validation Accuracy')\nplt.xlabel('Epochs')\nplt.ylabel('Accuracy')\nplt.legend()\nplt.show()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"y_true = np.concatenate([y for _, y in val_ds], axis=0)\n\ny_pred_probs = model_1.predict(val_ds)\ny_pred = np.argmax(y_pred_probs, axis=1)\n\ncm = confusion_matrix(y_true, y_pred)\n\nplt.figure(figsize=(8, 6))\nsns.heatmap(\n    cm,\n    annot=True,\n    fmt='d',\n    cmap='Blues',\n    xticklabels=class_names,\n    yticklabels=class_names\n)\n\nplt.xlabel(\"Predicted\")\nplt.ylabel(\"True\")\nplt.title(\"Confusion Matrix\")\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-04T19:28:26.740945Z","iopub.execute_input":"2025-12-04T19:28:26.741219Z","iopub.status.idle":"2025-12-04T19:28:55.777493Z","shell.execute_reply.started":"2025-12-04T19:28:26.741199Z","shell.execute_reply":"2025-12-04T19:28:55.776731Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"So it is doing well at identifying the base case at no Retinopathy, and pretty good with moderate, but does not do well with Severe and Proliferative.","metadata":{}},{"cell_type":"markdown","source":"# Model 2: Baseline CNN with Engineered Dataset","metadata":{}},{"cell_type":"markdown","source":"## Model Building","metadata":{}},{"cell_type":"code","source":"model_2 = clone_model(model_1);\nmodel_2.compile(\n    optimizer='adam',\n    loss='sparse_categorical_crossentropy',\n    metrics=['accuracy']\n)\nmodel_2.summary()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-04T19:52:32.969124Z","iopub.execute_input":"2025-12-04T19:52:32.969404Z","iopub.status.idle":"2025-12-04T19:52:33.056531Z","shell.execute_reply.started":"2025-12-04T19:52:32.969382Z","shell.execute_reply":"2025-12-04T19:52:33.055858Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Model Training","metadata":{}},{"cell_type":"code","source":"history_2 = model_2.fit(\n    augmented_ds,\n    validation_data=val_ds,\n    epochs=25,\n    callbacks=[early_stop]\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-04T19:52:36.341435Z","iopub.execute_input":"2025-12-04T19:52:36.342143Z","iopub.status.idle":"2025-12-04T20:27:34.865693Z","shell.execute_reply.started":"2025-12-04T19:52:36.342118Z","shell.execute_reply":"2025-12-04T20:27:34.865107Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Model Evaluation","metadata":{}},{"cell_type":"code","source":"h2 = history_2.history\nepochs_2 = range(1, len(h2['loss']) + 1)\nplt.plot(epochs_2, h2['loss'], 'b-', label='Model 2 Training Loss')\nplt.plot(epochs_2, h2['val_loss'], 'b--', label='Model 2 Validation Loss')\nplt.title('Training vs Validation Loss')\nplt.xlabel('Epochs')\nplt.ylabel('Loss')\nplt.legend()\n\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-04T20:34:30.208641Z","iopub.execute_input":"2025-12-04T20:34:30.208948Z","iopub.status.idle":"2025-12-04T20:34:30.382829Z","shell.execute_reply.started":"2025-12-04T20:34:30.208928Z","shell.execute_reply":"2025-12-04T20:34:30.382221Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plt.plot(epochs_2, h2['accuracy'], 'b-', label='Model 2 Training Accuracy')\nplt.plot(epochs_2, h2['val_accuracy'], 'b--', label='Model 2 Validation Accuracy')\nplt.title('Training vs Validation Accuracy')\nplt.xlabel('Epochs')\nplt.ylabel('Accuracy')\nplt.legend()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-04T20:34:34.180787Z","iopub.execute_input":"2025-12-04T20:34:34.181055Z","iopub.status.idle":"2025-12-04T20:34:34.343844Z","shell.execute_reply.started":"2025-12-04T20:34:34.181036Z","shell.execute_reply":"2025-12-04T20:34:34.343233Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"y_true_2 = np.concatenate([y for _, y in val_ds], axis=0)\n\ny_pred_probs_2 = model_2.predict(val_ds)\ny_pred_2 = np.argmax(y_pred_probs, axis=1)\n\ncm_2 = confusion_matrix(y_true_2, y_pred_2)\n\nplt.figure(figsize=(8, 6))\nsns.heatmap(\n    cm_2,\n    annot=True,\n    fmt='d',\n    cmap='Blues',\n    xticklabels=class_names,\n    yticklabels=class_names\n)\n\nplt.xlabel(\"Predicted\")\nplt.ylabel(\"True\")\nplt.title(\"Confusion Matrix\")\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-04T21:21:47.109755Z","iopub.execute_input":"2025-12-04T21:21:47.110332Z","iopub.status.idle":"2025-12-04T21:22:15.848353Z","shell.execute_reply.started":"2025-12-04T21:21:47.110309Z","shell.execute_reply":"2025-12-04T21:22:15.847772Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Even with the augmented data, model 2 has a confusion matrix that's basically the same.","metadata":{}},{"cell_type":"markdown","source":"# Model 3: CNN with Transfer Learning with Enhanced Dataset","metadata":{}},{"cell_type":"markdown","source":"## Model Building","metadata":{}},{"cell_type":"code","source":"from tensorflow.keras.applications import MobileNetV2\n\nbase_model = MobileNetV2(\n    weights='imagenet',        \n    include_top=False,         \n    input_shape=(224, 224, 3) \n)\n\nbase_model.trainable = False","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-04T20:34:38.634265Z","iopub.execute_input":"2025-12-04T20:34:38.634829Z","iopub.status.idle":"2025-12-04T20:34:39.512069Z","shell.execute_reply.started":"2025-12-04T20:34:38.634804Z","shell.execute_reply":"2025-12-04T20:34:39.511442Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model_3 = Sequential([\n    base_model,\n    GlobalAveragePooling2D(),  \n    Dense(128, activation='relu', kernel_initializer='he_normal'),\n    Dropout(0.5),\n    Dense(64, activation='relu', kernel_initializer='he_normal'),\n    Dropout(0.5),\n    Dense(5, activation='softmax')  \n])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-04T20:34:41.857037Z","iopub.execute_input":"2025-12-04T20:34:41.857737Z","iopub.status.idle":"2025-12-04T20:34:41.889522Z","shell.execute_reply.started":"2025-12-04T20:34:41.857707Z","shell.execute_reply":"2025-12-04T20:34:41.889010Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model_3.compile(\n    optimizer='adam',\n    loss='sparse_categorical_crossentropy',\n    metrics=['accuracy']\n)\nmodel_3.summary()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-04T20:34:45.025919Z","iopub.execute_input":"2025-12-04T20:34:45.026626Z","iopub.status.idle":"2025-12-04T20:34:45.049014Z","shell.execute_reply.started":"2025-12-04T20:34:45.026602Z","shell.execute_reply":"2025-12-04T20:34:45.048468Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Model Training","metadata":{}},{"cell_type":"code","source":"history_3 = model_3.fit(\n    augmented_ds,\n    validation_data=val_ds,\n    epochs=25,\n    callbacks=[early_stop]\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-04T20:34:55.882225Z","iopub.execute_input":"2025-12-04T20:34:55.882499Z","iopub.status.idle":"2025-12-04T21:21:23.226354Z","shell.execute_reply.started":"2025-12-04T20:34:55.882482Z","shell.execute_reply":"2025-12-04T21:21:23.225660Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Model Evaluation","metadata":{}},{"cell_type":"code","source":"h3 = history_3.history\nepochs_3 = range(1, len(h3['loss']) + 1)\nplt.plot(epochs_3, h3['loss'], 'b-', label='Model 3 Training Loss')\nplt.plot(epochs_3, h3['val_loss'], 'b--', label='Model 3 Validation Loss')\nplt.title('Training vs Validation Loss')\nplt.xlabel('Epochs')\nplt.ylabel('Loss')\nplt.legend()\n\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-04T21:22:30.508841Z","iopub.execute_input":"2025-12-04T21:22:30.509641Z","iopub.status.idle":"2025-12-04T21:22:30.659567Z","shell.execute_reply.started":"2025-12-04T21:22:30.509614Z","shell.execute_reply":"2025-12-04T21:22:30.659000Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plt.plot(epochs_3, h3['accuracy'], 'b-', label='Model 3 Training Accuracy')\nplt.plot(epochs_3, h3['val_accuracy'], 'b--', label='Model 3 Validation Accuracy')\nplt.title('Training vs Validation Accuracy')\nplt.xlabel('Epochs')\nplt.ylabel('Accuracy')\nplt.legend()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-04T21:22:33.749550Z","iopub.execute_input":"2025-12-04T21:22:33.749834Z","iopub.status.idle":"2025-12-04T21:22:33.922181Z","shell.execute_reply.started":"2025-12-04T21:22:33.749814Z","shell.execute_reply":"2025-12-04T21:22:33.921593Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"y_true_3 = np.concatenate([y for _, y in val_ds], axis=0)\n\ny_pred_probs_3 = model_3.predict(val_ds)\ny_pred_3 = np.argmax(y_pred_probs_3, axis=1)\n\ncm_3 = confusion_matrix(y_true_3, y_pred_3)\n\nplt.figure(figsize=(8, 6))\nsns.heatmap(\n    cm_3,\n    annot=True,\n    fmt='d',\n    cmap='Blues',\n    xticklabels=class_names,\n    yticklabels=class_names\n)\n\nplt.xlabel(\"Predicted\")\nplt.ylabel(\"True\")\nplt.title(\"Confusion Matrix\")\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-04T21:22:40.100274Z","iopub.execute_input":"2025-12-04T21:22:40.100920Z","iopub.status.idle":"2025-12-04T21:23:12.728553Z","shell.execute_reply.started":"2025-12-04T21:22:40.100894Z","shell.execute_reply":"2025-12-04T21:23:12.727838Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Observations","metadata":{}},{"cell_type":"markdown","source":"* All three models struggle to identify Severe and Proliferative\n* Both class 3 and 4 are the lowest represented in the dataset\n* Backup data is roughly the same, should work for adding it to the notebook, only concern is that it's 80 gigs of additional data\n* Early stopping worked for model 2, might want to invest in more epochs and loosen the condition on early stop","metadata":{}},{"cell_type":"markdown","source":"## Competition Test CSV","metadata":{}},{"cell_type":"code","source":"test.head()\ntest['file_path'] = test['id_code'].apply(\n    lambda x: f\"/kaggle/input/aptos2019-blindness-detection/test_images/{x}.png\"\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-04T21:40:38.650051Z","iopub.execute_input":"2025-12-04T21:40:38.650549Z","iopub.status.idle":"2025-12-04T21:40:38.655531Z","shell.execute_reply.started":"2025-12-04T21:40:38.650527Z","shell.execute_reply":"2025-12-04T21:40:38.654809Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def load_test_image(path):\n    image = tf.io.read_file(path)\n    image = tf.image.decode_png(image, channels=3)\n    image = tf.image.resize(image, (224, 224))\n    image = image / 255.0\n    return image","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-04T21:40:40.151883Z","iopub.execute_input":"2025-12-04T21:40:40.152152Z","iopub.status.idle":"2025-12-04T21:40:40.156687Z","shell.execute_reply.started":"2025-12-04T21:40:40.152132Z","shell.execute_reply":"2025-12-04T21:40:40.156101Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_paths = test['file_path'].values\n\ntest_ds = tf.data.Dataset.from_tensor_slices(test_paths)\ntest_ds = test_ds.map(load_test_image, num_parallel_calls=tf.data.AUTOTUNE)\ntest_ds = test_ds.batch(32).prefetch(tf.data.AUTOTUNE)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-04T21:40:53.926582Z","iopub.execute_input":"2025-12-04T21:40:53.927167Z","iopub.status.idle":"2025-12-04T21:40:53.947045Z","shell.execute_reply.started":"2025-12-04T21:40:53.927141Z","shell.execute_reply":"2025-12-04T21:40:53.946412Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"y_pred_probs_submission = model_3.predict(test_ds)\ny_pred_submission = np.argmax(y_pred_probs_submission, axis=1)\n\nsubmission = pd.DataFrame({\n    \"id_code\": test[\"id_code\"],\n    \"diagnosis\": y_pred_submission\n})\nsubmission.to_csv(\"submission.csv\", index=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-04T21:46:45.068734Z","iopub.execute_input":"2025-12-04T21:46:45.069413Z","iopub.status.idle":"2025-12-04T21:47:00.888116Z","shell.execute_reply.started":"2025-12-04T21:46:45.069391Z","shell.execute_reply":"2025-12-04T21:47:00.887323Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Reflections","metadata":{}},{"cell_type":"markdown","source":"* Insufficient data among class 3 and 4 caused issues as predicted\n* Feedback suggested more robust dataset would likely lead to better results\n* Encouraged by positive feedback, this feels like something I could see myself spending ongoing time on\n* CNN architecture and use makes sense to me where I struggle at times with other deep learning models\n* Wanted to make additional improvements before final submission. Due to time constraints, not going to happen, but intend to keep working at this in the future.","metadata":{}},{"cell_type":"markdown","source":"# Extra code for presentation","metadata":{}},{"cell_type":"code","source":"from tensorflow.keras.preprocessing.image import save_img\n\ndef augment_and_save(image_path, save_dir, prefix=\"aug\"):\n   \n    img = tf.io.read_file(image_path)\n    img = tf.image.decode_image(img, channels=3)\n    img = tf.image.resize(img, (224, 224))\n    img = tf.cast(img, tf.float32) / 255.0\n    \n    \n    augmented_img = data_augmentation(img, training=True)\n    \n   \n    augmented_img_uint8 = tf.image.convert_image_dtype(augmented_img, dtype=tf.uint8)\n    \n    \n    os.makedirs(save_dir, exist_ok=True)\n    \n    \n    filename = os.path.basename(image_path)\n    save_path = os.path.join(save_dir, f\"{prefix}_{filename}\")\n    \n    \n    save_img(save_path, augmented_img_uint8.numpy())\n    \n    return save_path","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-04T22:47:28.885151Z","iopub.execute_input":"2025-12-04T22:47:28.885785Z","iopub.status.idle":"2025-12-04T22:47:28.891240Z","shell.execute_reply.started":"2025-12-04T22:47:28.885761Z","shell.execute_reply":"2025-12-04T22:47:28.890441Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"image_paths = [\n    \"/kaggle/input/aptos2019-blindness-detection/train_images/000c1434d8d7.png\",\n    \"/kaggle/input/aptos2019-blindness-detection/train_images/001639a390f0.png\",\n    \"/kaggle/input/aptos2019-blindness-detection/train_images/0024cdab0c1e.png\"\n]\n\nsave_directory = \"/kaggle/working/augmented_images/\"\n\nfor path in image_paths:\n    new_path = augment_and_save(path, save_directory)\n    print(\"Saved:\", new_path)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-04T22:47:32.337100Z","iopub.execute_input":"2025-12-04T22:47:32.337778Z","iopub.status.idle":"2025-12-04T22:47:34.320272Z","shell.execute_reply.started":"2025-12-04T22:47:32.337755Z","shell.execute_reply":"2025-12-04T22:47:34.319504Z"}},"outputs":[],"execution_count":null}]}