{"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":14774,"databundleVersionId":875431,"sourceType":"competition"}],"dockerImageVersionId":31193,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# ## Cell 1: Imports and Setup\nimport os\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport seaborn as sns\n\n# Sklearn for metrics and splitting\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.utils.class_weight import compute_class_weight\nfrom sklearn.metrics import classification_report, confusion_matrix, cohen_kappa_score\n\n# TensorFlow and Keras\nimport tensorflow as tf\nfrom tensorflow.keras.preprocessing.image import ImageDataGenerator\nfrom tensorflow.keras import layers, Model\n\n# UPDATED: Import all architectures for your Research Paper comparison\nfrom tensorflow.keras.applications import EfficientNetB0, ResNet50, DenseNet121\n\nfrom tensorflow.keras.callbacks import ModelCheckpoint, EarlyStopping, ReduceLROnPlateau\n\nprint(f\"TensorFlow Version: {tf.__version__}\")\n\n# Check for GPU\ngpus = tf.config.list_physical_devices('GPU')\nif gpus:\n    print(f\"GPU(s) available: {len(gpus)}\")\nelse:\n    print(\"No GPU detected.\")","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-11-27T15:13:35.703878Z","iopub.execute_input":"2025-11-27T15:13:35.704687Z","iopub.status.idle":"2025-11-27T15:13:36.296183Z","shell.execute_reply.started":"2025-11-27T15:13:35.704658Z","shell.execute_reply":"2025-11-27T15:13:36.295458Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ## Cell 2: Configuration\n\n# Paths to your Kaggle data\nTRAIN_CSV = '/kaggle/input/aptos2019-blindness-detection/train.csv'\nTRAIN_IMG_DIR = '/kaggle/input/aptos2019-blindness-detection/train_images'\n\n# Model and training hyperparameters\nIMG_SIZE = (384, 384)\nBATCH_SIZE = 16\nSEED = 42\nEPOCHS = 25  # Increased from 20 to allow more time for augmentation\nNUM_CLASSES = 5\n\n# Set seeds for reproducibility\nnp.random.seed(SEED)\ntf.random.set_seed(SEED)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-27T15:13:36.51081Z","iopub.execute_input":"2025-11-27T15:13:36.511566Z","iopub.status.idle":"2025-11-27T15:13:36.525356Z","shell.execute_reply.started":"2025-11-27T15:13:36.511545Z","shell.execute_reply":"2025-11-27T15:13:36.524487Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ## Cell 3: Load and Prepare Data\n\n# Load the CSV and ensure the diagnosis column is a string for the generator\ndf = pd.read_csv(TRAIN_CSV)\ndf['id_code'] = df['id_code'].astype(str) + '.png'\n\n# This line fixes the error by converting the diagnosis labels to strings\ndf['diagnosis'] = df['diagnosis'].astype(str)\n\nprint(\"Data loaded successfully.\")\nprint(f\"Total samples: {len(df)}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-27T15:13:37.203428Z","iopub.execute_input":"2025-11-27T15:13:37.20363Z","iopub.status.idle":"2025-11-27T15:13:37.227645Z","shell.execute_reply.started":"2025-11-27T15:13:37.203614Z","shell.execute_reply":"2025-11-27T15:13:37.227099Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ## Cell 4: Quick EDA\n# Visualize the class distribution\nplt.figure(figsize=(10, 6))\ndf['diagnosis'].value_counts().sort_index().plot(kind='bar', color='skyblue')\nplt.title('Class Distribution of Diabetic Retinopathy')\nplt.xlabel('Diagnosis Level')\nplt.ylabel('Number of Images')\nplt.xticks(rotation=0)\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-27T15:13:38.493647Z","iopub.execute_input":"2025-11-27T15:13:38.493874Z","iopub.status.idle":"2025-11-27T15:13:38.756154Z","shell.execute_reply.started":"2025-11-27T15:13:38.493857Z","shell.execute_reply":"2025-11-27T15:13:38.755538Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ## Cell 5: Train/Validation Split\n# Create a stratified split to maintain class distribution in both sets\ntrain_df, val_df = train_test_split(\n    df,\n    test_size=0.15,\n    random_state=SEED,\n    stratify=df['diagnosis']\n)\n\nprint(f\"Training samples: {len(train_df)}\")\nprint(f\"Validation samples: {len(val_df)}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-27T15:13:42.766229Z","iopub.execute_input":"2025-11-27T15:13:42.766469Z","iopub.status.idle":"2025-11-27T15:13:42.776977Z","shell.execute_reply.started":"2025-11-27T15:13:42.766453Z","shell.execute_reply":"2025-11-27T15:13:42.776259Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ## Cell 6: Data Generators (with Augmentations)\n\n# Add basic augmentations to the training generator to combat overfitting\ntrain_datagen = ImageDataGenerator(\n    rescale=1./255,\n    rotation_range=15,\n    horizontal_flip=True,\n    zoom_range=0.1,\n    width_shift_range=0.1,\n    height_shift_range=0.1\n)\n\n# The validation generator should NOT have augmentations\nval_datagen = ImageDataGenerator(rescale=1./255)\n\ntrain_gen = train_datagen.flow_from_dataframe(\n    dataframe=train_df,\n    directory=TRAIN_IMG_DIR,\n    x_col='id_code',\n    y_col='diagnosis',\n    target_size=IMG_SIZE,\n    batch_size=BATCH_SIZE,\n    class_mode='sparse',\n    shuffle=True,\n    seed=SEED\n)\n\nval_gen = val_datagen.flow_from_dataframe(\n    dataframe=val_df,\n    directory=TRAIN_IMG_DIR,\n    x_col='id_code',\n    y_col='diagnosis',\n    target_size=IMG_SIZE,\n    batch_size=BATCH_SIZE,\n    class_mode='sparse',\n    shuffle=False\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-27T15:13:44.923609Z","iopub.execute_input":"2025-11-27T15:13:44.924194Z","iopub.status.idle":"2025-11-27T15:13:50.841408Z","shell.execute_reply.started":"2025-11-27T15:13:44.924169Z","shell.execute_reply":"2025-11-27T15:13:50.840789Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ## Cell 7: Calculate Class Weights\n# Correctly compute class weights on the integer labels to handle imbalance\nclasses = np.unique(train_df['diagnosis'])\nclass_weights = compute_class_weight(\n    'balanced',\n    classes=classes,\n    y=train_df['diagnosis'].values\n)\nclass_weight_dict = {c: w for c, w in zip(classes, class_weights)}\n\nprint(\"Class weights calculated:\")\nprint(class_weight_dict)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-27T15:13:50.842719Z","iopub.execute_input":"2025-11-27T15:13:50.843226Z","iopub.status.idle":"2025-11-27T15:13:50.850541Z","shell.execute_reply.started":"2025-11-27T15:13:50.843204Z","shell.execute_reply":"2025-11-27T15:13:50.849744Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ## Cell 8: Build the ResNet50 Model (Experiment A)\n\ndef build_resnet_model(input_shape=IMG_SIZE + (3,), n_classes=NUM_CLASSES):\n    \"\"\"Builds a ResNet50 model for comparison with EfficientNetB0.\"\"\"\n    \n    # Base model: Switched from EfficientNetB0 to ResNet50\n    # Note: ResNet50 is larger (25M params) than EfficientNetB0 (5M params)\n    base = ResNet50(\n        include_top=False,\n        weights='imagenet',\n        input_shape=input_shape\n    )\n    base.trainable = True # Full Fine-Tuning\n\n    # Model architecture (Kept IDENTICAL to the EfficientNet version for fair comparison)\n    inputs = layers.Input(shape=input_shape)\n    x = base(inputs, training=True)\n    x = layers.GlobalAveragePooling2D()(x)\n    x = layers.Dropout(0.3)(x)\n    outputs = layers.Dense(n_classes, activation='softmax')(x)\n    model = Model(inputs, outputs)\n\n    # Compile\n    model.compile(\n        optimizer=tf.keras.optimizers.Adam(learning_rate=1e-4),\n        loss='sparse_categorical_crossentropy',\n        metrics=['accuracy']\n    )\n    return model\n\n# Build the model\nmodel = build_resnet_model()\n\n# Print summary to confirm ResNet50 base and parameter count (~23-25 Million)\nmodel.summary()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-27T15:13:50.851211Z","iopub.execute_input":"2025-11-27T15:13:50.851428Z","iopub.status.idle":"2025-11-27T15:13:53.487163Z","shell.execute_reply.started":"2025-11-27T15:13:50.851408Z","shell.execute_reply":"2025-11-27T15:13:53.486602Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ## Cell 9: Define Callbacks (with Gentler LR Reduction)\n\n# Update the filename so we don't overwrite the EfficientNet model\ncheckpoint = ModelCheckpoint(\n    'best_model_resnet.h5',  # <--- RENAMED for the Research Paper\n    monitor='val_accuracy',\n    save_best_only=True,\n    mode='max',\n    verbose=1)\n\n# Keep EarlyStopping identical to the previous experiment\nearly_stopping = EarlyStopping(\n    monitor='val_accuracy',\n    patience=5, # Stop if no improvement for 5 epochs\n    restore_best_weights=True,\n    mode='max',\n    verbose=1)\n\n# Keep ReduceLROnPlateau identical to the previous experiment\nreduce_lr = ReduceLROnPlateau(\n    monitor='val_loss',\n    factor=0.5,  # Changed to 0.5 for a gentler reduction\n    patience=2,\n    verbose=1,\n    min_lr=1e-7)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-27T15:13:53.488253Z","iopub.execute_input":"2025-11-27T15:13:53.488463Z","iopub.status.idle":"2025-11-27T15:13:53.49296Z","shell.execute_reply.started":"2025-11-27T15:13:53.488446Z","shell.execute_reply":"2025-11-27T15:13:53.492419Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ## Cell 10: Train the Model\nhistory = model.fit(\n    train_gen,\n    validation_data=val_gen,\n    epochs=EPOCHS,\n    class_weight=class_weight_dict,\n    callbacks=[checkpoint, early_stopping, reduce_lr],\n    verbose=1\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-26T17:36:19.740886Z","iopub.execute_input":"2025-11-26T17:36:19.741205Z","iopub.status.idle":"2025-11-26T17:42:44.680555Z","shell.execute_reply.started":"2025-11-26T17:36:19.741181Z","shell.execute_reply":"2025-11-26T17:42:44.67884Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ## Cell 11: Plot Training History\ndef plot_history(history):\n    \"\"\"Plots accuracy and loss curves for training and validation.\"\"\"\n    fig, ax = plt.subplots(1, 2, figsize=(16, 6))\n\n    # Plot accuracy\n    ax[0].plot(history.history['accuracy'], label='Train Accuracy')\n    ax[0].plot(history.history['val_accuracy'], label='Validation Accuracy')\n    ax[0].set_title('Model Accuracy')\n    ax[0].set_xlabel('Epoch')\n    ax[0].set_ylabel('Accuracy')\n    ax[0].legend()\n\n    # Plot loss\n    ax[1].plot(history.history['loss'], label='Train Loss')\n    ax[1].plot(history.history['val_loss'], label='Validation Loss')\n    ax[1].set_title('Model Loss')\n    ax[1].set_xlabel('Epoch')\n    ax[1].set_ylabel('Loss')\n    ax[1].legend()\n\n    plt.show()\n\nplot_history(history)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ## Cell 12: Evaluate the ResNet50 Model (Experiment A Results)\n\n# Load the best performing model (saved from the successful training run)\n# NOTE: load_model handles both architecture and weights\nfrom tensorflow.keras.models import load_model\nmodel = load_model('best_model_resnet.h5')\n\n# Make predictions on the validation set\n# We use val_gen (defined in Cell 6) which contains the validation images\npreds = model.predict(val_gen)\npred_classes = np.argmax(preds, axis=1)\n\n# Get true labels directly from the generator\ntrue_classes = val_gen.classes\n\n# -----------------------------------------------------------\n# Calculate Metrics for Research Paper Comparison\n# -----------------------------------------------------------\n\n# Calculate Quadratic Weighted Kappa (QWK)\nqwk = cohen_kappa_score(true_classes, pred_classes, weights='quadratic')\nprint(f\"\\n📈 Validation Quadratic Weighted Kappa (QWK): {qwk:.4f}\\n\")\n\n# Calculate F2 Score (to emphasize Recall/Sensitivity)\nfrom sklearn.metrics import fbeta_score\n# beta=2 weights Recall twice as heavily as Precision\nf2 = fbeta_score(true_classes, pred_classes, beta=2, average='weighted')\nprint(f\"🔬 Weighted F2 Score (Sensitivity Emphasis): {f2:.4f}\\n\")\n\n\n# Print Classification Report (Detailed Breakdown)\nprint(\"📊 Classification Report (ResNet50):\\n\")\nprint(classification_report(true_classes, pred_classes, target_names=[str(i) for i in classes]))\n\n# Display Confusion Matrix\ncm = confusion_matrix(true_classes, pred_classes)\nplt.figure(figsize=(8, 6))\nsns.heatmap(cm, annot=True, fmt='d', cmap='Blues',\n            xticklabels=classes, yticklabels=classes)\nplt.title('Confusion Matrix (ResNet50)')\nplt.xlabel('Predicted Label')\nplt.ylabel('True Label')\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-27T15:13:56.168632Z","iopub.execute_input":"2025-11-27T15:13:56.168897Z","iopub.status.idle":"2025-11-27T15:15:19.507971Z","shell.execute_reply.started":"2025-11-27T15:13:56.168876Z","shell.execute_reply":"2025-11-27T15:15:19.507326Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}