{"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\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import (\n    classification_report, confusion_matrix,\n    roc_auc_score, roc_curve, accuracy_score,\n    precision_score, recall_score, f1_score\n)\nimport tensorflow as tf\nfrom tensorflow.keras.applications import DenseNet121\nfrom tensorflow.keras.models import Model\nfrom tensorflow.keras.layers import Dense, GlobalAveragePooling2D, Dropout\nfrom tensorflow.keras.optimizers import Adam\nfrom tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau, ModelCheckpoint\nfrom tensorflow.keras.preprocessing.image import ImageDataGenerator\n\nprint('TensorFlow version:', tf.__version__)\nprint('GPU Available:', tf.config.list_physical_devices('GPU'))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-14T12:38:55.806759Z","iopub.execute_input":"2026-06-14T12:38:55.807007Z","iopub.status.idle":"2026-06-14T12:39:14.691995Z","shell.execute_reply.started":"2026-06-14T12:38:55.806977Z","shell.execute_reply":"2026-06-14T12:39:14.691194Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Load the APTOS 2019 dataset CSV\nBASE_PATH = '/kaggle/input/competitions/aptos2019-blindness-detection/'\n\ntrain_df = pd.read_csv(BASE_PATH + 'train.csv')\n\nprint('Original dataset shape:', train_df.shape)\nprint('\\nOriginal class distribution:')\nprint(train_df['diagnosis'].value_counts().sort_index())\n\n# Binary Classification Conversion\ntrain_df['binary_label'] = train_df['diagnosis'].apply(lambda x: 0 if x == 0 else 1)\n\nprint('\\nBinary class distribution:')\nprint(train_df['binary_label'].value_counts())\nprint('\\n0 = Normal, 1 = Diabetic Retinopathy')\n\n# Add full image path\ntrain_df['image_path'] = train_df['id_code'].apply(\n    lambda x: BASE_PATH + f'train_images/{x}.png'\n)\n\ntrain_df['binary_label'] = train_df['binary_label'].astype(str)\n\nprint('\\nSample data:')\nprint(train_df.head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-14T12:39:34.190063Z","iopub.execute_input":"2026-06-14T12:39:34.190476Z","iopub.status.idle":"2026-06-14T12:39:34.245191Z","shell.execute_reply.started":"2026-06-14T12:39:34.190447Z","shell.execute_reply":"2026-06-14T12:39:34.244100Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from tensorflow.keras.preprocessing.image import load_img\n\nfig, axes = plt.subplots(2, 4, figsize=(16, 8))\nfig.suptitle('Sample Retinal Fundus Images - APTOS 2019', fontsize=16, fontweight='bold')\n\nlabels = ['Normal (Class 0)', 'Diabetic Retinopathy (Class 1)']\n\nfor i, label in enumerate(['0', '1']):\n    samples = train_df[train_df['binary_label'] == label].sample(4)\n    for j, (_, row) in enumerate(samples.iterrows()):\n        img = load_img(row['image_path'], target_size=(224, 224))\n        axes[i][j].imshow(img)\n        axes[i][j].set_title(labels[i], fontsize=10)\n        axes[i][j].axis('off')\n\nplt.tight_layout()\nplt.show()\n\n# Plot class distribution\nfig, ax = plt.subplots(figsize=(6, 4))\ncounts = train_df['binary_label'].value_counts()\nax.bar(['Normal (0)', 'DR (1)'], [counts['0'], counts['1']], \n       color=['#2ecc71', '#e74c3c'])\nax.set_title('Binary Class Distribution', fontweight='bold')\nax.set_ylabel('Number of Images')\nfor i, v in enumerate([counts['0'], counts['1']]):\n    ax.text(i, v + 10, str(v), ha='center', fontweight='bold')\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-14T12:39:43.389410Z","iopub.execute_input":"2026-06-14T12:39:43.390110Z","iopub.status.idle":"2026-06-14T12:39:45.197673Z","shell.execute_reply.started":"2026-06-14T12:39:43.390080Z","shell.execute_reply":"2026-06-14T12:39:45.196806Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Split data: 72% train / 18% validation / 10% test\ntrain_val_df, test_df = train_test_split(\n    train_df,\n    test_size=0.10,\n    random_state=42,\n    stratify=train_df['binary_label']\n)\n\ntrain_split_df, val_df = train_test_split(\n    train_val_df,\n    test_size=0.20,\n    random_state=42,\n    stratify=train_val_df['binary_label']\n)\n\nprint(f'Training set:   {len(train_split_df)} images ({len(train_split_df)/len(train_df)*100:.1f}%)')\nprint(f'Validation set: {len(val_df)} images ({len(val_df)/len(train_df)*100:.1f}%)')\nprint(f'Test set:       {len(test_df)} images ({len(test_df)/len(train_df)*100:.1f}%)')\n\nprint('\\nTrain class distribution:')\nprint(train_split_df['binary_label'].value_counts())\nprint('\\nValidation class distribution:')\nprint(val_df['binary_label'].value_counts())\nprint('\\nTest class distribution:')\nprint(test_df['binary_label'].value_counts())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-14T12:40:01.241716Z","iopub.execute_input":"2026-06-14T12:40:01.242162Z","iopub.status.idle":"2026-06-14T12:40:01.266698Z","shell.execute_reply.started":"2026-06-14T12:40:01.242116Z","shell.execute_reply":"2026-06-14T12:40:01.265970Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"IMG_SIZE = 224\nBATCH_SIZE = 32\n\n# Training data generator WITH augmentation\ntrain_datagen = ImageDataGenerator(\n    rescale=1./255,\n    rotation_range=20,\n    horizontal_flip=True,\n    vertical_flip=True,\n    zoom_range=0.15,\n    width_shift_range=0.1,\n    height_shift_range=0.1,\n    fill_mode='nearest'\n)\n\n# Validation and Test - ONLY normalization\nval_test_datagen = ImageDataGenerator(rescale=1./255)\n\ntrain_generator = train_datagen.flow_from_dataframe(\n    dataframe=train_split_df,\n    x_col='image_path',\n    y_col='binary_label',\n    target_size=(IMG_SIZE, IMG_SIZE),\n    batch_size=BATCH_SIZE,\n    class_mode='binary',\n    shuffle=True\n)\n\nval_generator = val_test_datagen.flow_from_dataframe(\n    dataframe=val_df,\n    x_col='image_path',\n    y_col='binary_label',\n    target_size=(IMG_SIZE, IMG_SIZE),\n    batch_size=BATCH_SIZE,\n    class_mode='binary',\n    shuffle=False\n)\n\ntest_generator = val_test_datagen.flow_from_dataframe(\n    dataframe=test_df,\n    x_col='image_path',\n    y_col='binary_label',\n    target_size=(IMG_SIZE, IMG_SIZE),\n    batch_size=BATCH_SIZE,\n    class_mode='binary',\n    shuffle=False\n)\n\nprint('Generators created successfully')\nprint(f'Training batches: {len(train_generator)}')\nprint(f'Validation batches: {len(val_generator)}')\nprint(f'Test batches: {len(test_generator)}')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-14T12:40:16.097331Z","iopub.execute_input":"2026-06-14T12:40:16.098003Z","iopub.status.idle":"2026-06-14T12:40:24.814667Z","shell.execute_reply.started":"2026-06-14T12:40:16.097974Z","shell.execute_reply":"2026-06-14T12:40:24.814068Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Load DenseNet121 with ImageNet pretrained weights\nbase_model = DenseNet121(\n    weights='imagenet',\n    include_top=False,\n    input_shape=(IMG_SIZE, IMG_SIZE, 3)\n)\n\n# Freeze base model layers (Phase 1)\nbase_model.trainable = False\n\n# Add custom classification head\nx = base_model.output\nx = GlobalAveragePooling2D()(x)\nx = Dense(256, activation='relu')(x)\nx = Dropout(0.5)(x)\nx = Dense(128, activation='relu')(x)\nx = Dropout(0.3)(x)\noutput = Dense(1, activation='sigmoid')(x)\n\n# Create the full model\nmodel = Model(inputs=base_model.input, outputs=output)\n\n# Compile\nmodel.compile(\n    optimizer=Adam(learning_rate=0.001),\n    loss='binary_crossentropy',\n    metrics=['accuracy']\n)\n\nprint('Model built successfully')\nprint(f'Total parameters: {model.count_params():,}')\ntrainable = sum([tf.size(w).numpy() for w in model.trainable_weights])\nnon_trainable = sum([tf.size(w).numpy() for w in model.non_trainable_weights])\nprint(f'Trainable parameters: {trainable:,}')\nprint(f'Non-trainable parameters: {non_trainable:,}')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-14T12:40:35.720288Z","iopub.execute_input":"2026-06-14T12:40:35.721024Z","iopub.status.idle":"2026-06-14T12:40:40.422366Z","shell.execute_reply.started":"2026-06-14T12:40:35.720991Z","shell.execute_reply":"2026-06-14T12:40:40.421700Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"early_stopping = EarlyStopping(\n    monitor='val_loss',\n    patience=5,\n    restore_best_weights=True,\n    verbose=1\n)\n\nreduce_lr = ReduceLROnPlateau(\n    monitor='val_loss',\n    factor=0.5,\n    patience=3,\n    min_lr=1e-7,\n    verbose=1\n)\n\ncheckpoint = ModelCheckpoint(\n    'best_model_phase1.keras',\n    monitor='val_accuracy',\n    save_best_only=True,\n    verbose=1\n)\n\nprint('Phase 1: Training with frozen base layers...')\nhistory_phase1 = model.fit(\n    train_generator,\n    epochs=20,\n    validation_data=val_generator,\n    callbacks=[early_stopping, reduce_lr, checkpoint],\n    verbose=1\n)\n\nprint('\\nPhase 1 training complete!')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-14T12:40:44.173332Z","iopub.execute_input":"2026-06-14T12:40:44.174096Z","iopub.status.idle":"2026-06-14T14:05:42.261411Z","shell.execute_reply.started":"2026-06-14T12:40:44.174064Z","shell.execute_reply":"2026-06-14T14:05:42.260596Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Unfreeze last 50 layers for fine-tuning\nbase_model.trainable = True\n\nfor layer in base_model.layers[:-50]:\n    layer.trainable = False\n\n# Recompile with lower learning rate\nmodel.compile(\n    optimizer=Adam(learning_rate=0.0001),\n    loss='binary_crossentropy',\n    metrics=['accuracy']\n)\n\ncheckpoint2 = ModelCheckpoint(\n    'best_model_phase2.keras',\n    monitor='val_accuracy',\n    save_best_only=True,\n    verbose=1\n)\n\nearly_stopping2 = EarlyStopping(\n    monitor='val_loss',\n    patience=7,\n    restore_best_weights=True,\n    verbose=1\n)\n\nprint('Phase 2: Fine-tuning with unfrozen layers...')\nhistory_phase2 = model.fit(\n    train_generator,\n    epochs=30,\n    validation_data=val_generator,\n    callbacks=[early_stopping2, reduce_lr, checkpoint2],\n    verbose=1\n)\n\nprint('\\nPhase 2 fine-tuning complete!')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-14T14:14:32.762497Z","iopub.execute_input":"2026-06-14T14:14:32.763198Z","iopub.status.idle":"2026-06-14T15:00:12.935017Z","shell.execute_reply.started":"2026-06-14T14:14:32.763157Z","shell.execute_reply":"2026-06-14T15:00:12.933397Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Get predictions on test set\nprint('Evaluating model on test set...')\ntest_generator.reset()\n\ny_pred_probs = model.predict(test_generator, verbose=1)\ny_pred = (y_pred_probs > 0.5).astype(int).flatten()\ny_true = test_generator.classes\n\n# Calculate metrics\naccuracy  = accuracy_score(y_true, y_pred)\nprecision = precision_score(y_true, y_pred)\nrecall    = recall_score(y_true, y_pred)\nf1        = f1_score(y_true, y_pred)\nauc       = roc_auc_score(y_true, y_pred_probs)\n\nprint('\\n' + '='*50)\nprint('        MODEL EVALUATION RESULTS')\nprint('='*50)\nprint(f'Accuracy:  {accuracy:.4f}  ({accuracy*100:.2f}%)')\nprint(f'Precision: {precision:.4f}  ({precision*100:.2f}%)')\nprint(f'Recall:    {recall:.4f}  ({recall*100:.2f}%)')\nprint(f'F1-Score:  {f1:.4f}  ({f1*100:.2f}%)')\nprint(f'AUC-ROC:   {auc:.4f}')\nprint('='*50)\n\nprint('\\nDetailed Classification Report:')\nprint(classification_report(y_true, y_pred, \n      target_names=['Normal', 'Diabetic Retinopathy']))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-14T15:00:21.784725Z","iopub.execute_input":"2026-06-14T15:00:21.785153Z","iopub.status.idle":"2026-06-14T15:01:36.260557Z","shell.execute_reply.started":"2026-06-14T15:00:21.785106Z","shell.execute_reply":"2026-06-14T15:01:36.259713Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"cm = confusion_matrix(y_true, y_pred)\n\nfig, ax = plt.subplots(figsize=(8, 6))\nsns.heatmap(\n    cm, annot=True, fmt='d', cmap='Blues',\n    xticklabels=['Normal', 'Diabetic Retinopathy'],\n    yticklabels=['Normal', 'Diabetic Retinopathy'],\n    linewidths=0.5,\n    annot_kws={'size': 14, 'weight': 'bold'}\n)\nax.set_title('Confusion Matrix - DenseNet121', fontsize=14, fontweight='bold')\nax.set_ylabel('Actual Label', fontsize=12)\nax.set_xlabel('Predicted Label', fontsize=12)\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-14T15:06:25.729517Z","iopub.execute_input":"2026-06-14T15:06:25.729951Z","iopub.status.idle":"2026-06-14T15:06:25.901699Z","shell.execute_reply.started":"2026-06-14T15:06:25.729920Z","shell.execute_reply":"2026-06-14T15:06:25.900858Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"fpr, tpr, thresholds = roc_curve(y_true, y_pred_probs)\n\nfig, ax = plt.subplots(figsize=(8, 6))\nax.plot(fpr, tpr, color='#e74c3c', linewidth=2.5,\n        label=f'DenseNet121 (AUC = {auc:.4f})')\nax.plot([0, 1], [0, 1], color='gray', linewidth=1.5,\n        linestyle='--', label='Random Classifier (AUC = 0.50)')\nax.fill_between(fpr, tpr, alpha=0.1, color='#e74c3c')\nax.set_title('ROC Curve - DenseNet121\\nDiabetic Retinopathy Detection',\n             fontsize=14, fontweight='bold')\nax.set_xlabel('False Positive Rate', fontsize=12)\nax.set_ylabel('True Positive Rate', fontsize=12)\nax.legend(fontsize=11)\nax.grid(True, alpha=0.3)\nplt.tight_layout()\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-14T15:07:11.013538Z","iopub.execute_input":"2026-06-14T15:07:11.014282Z","iopub.status.idle":"2026-06-14T15:07:11.197635Z","shell.execute_reply.started":"2026-06-14T15:07:11.014249Z","shell.execute_reply":"2026-06-14T15:07:11.196831Z"}},"outputs":[],"execution_count":null}]}