{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.11","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":31011,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"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","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-04-18T01:45:53.806581Z","iopub.execute_input":"2025-04-18T01:45:53.80725Z","iopub.status.idle":"2025-04-18T01:45:57.468984Z","shell.execute_reply.started":"2025-04-18T01:45:53.807224Z","shell.execute_reply":"2025-04-18T01:45:57.468172Z"}},"outputs":[],"execution_count":null},{"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\n# import os\n# import numpy as np\n# import pandas as pd\n# import matplotlib.pyplot as plt\n# from tensorflow.keras.preprocessing.image import load_img, img_to_array\n# from sklearn.model_selection import train_test_split\n# from tensorflow.keras import layers, models\n# from tensorflow.keras.applications import ResNet50, EfficientNetB0\n# from tensorflow.keras.optimizers import Adam\n# from tensorflow.keras.callbacks import EarlyStopping, LearningRateScheduler\n# from tensorflow.keras.regularizers import l2\n\n# # Check available files in /kaggle/input\n# for dirname, _, filenames in os.walk('/kaggle/input'):\n#     for filename in filenames:\n#         print(os.path.join(dirname, filename))\n\n# # Step 1: Load the dataset\n# data_path = '/kaggle/input/diabetic-retinopathy-detection/train.csv'  # Update as needed\n# data = pd.read_csv(data_path)\n\n# # Add image paths\n# data['image_path'] = '/kaggle/input/diabetic-retinopathy-detection/train_images/' + data['id_code'] + '.png'\n\n# # Optional: Check for missing image files\n# missing_files = [img_path for img_path in data['image_path'] if not os.path.exists(img_path)]\n# if missing_files:\n#     print(f\"Missing image files: {missing_files}\")\n# else:\n#     print(\"All image files found.\")\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-18T01:45:49.777869Z","iopub.execute_input":"2025-04-18T01:45:49.778476Z","iopub.status.idle":"2025-04-18T01:45:49.781888Z","shell.execute_reply.started":"2025-04-18T01:45:49.778454Z","shell.execute_reply":"2025-04-18T01:45:49.781335Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nfrom tensorflow.keras.preprocessing.image import load_img, img_to_array\nfrom sklearn.model_selection import train_test_split\nfrom tensorflow.keras import layers, models\nfrom tensorflow.keras.applications import ResNet50, EfficientNetB0\nfrom tensorflow.keras.optimizers import Adam\nfrom tensorflow.keras.callbacks import EarlyStopping, LearningRateScheduler\nfrom tensorflow.keras.regularizers import l2\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-18T01:45:58.084775Z","iopub.execute_input":"2025-04-18T01:45:58.085558Z","iopub.status.idle":"2025-04-18T01:46:11.375893Z","shell.execute_reply.started":"2025-04-18T01:45:58.085525Z","shell.execute_reply":"2025-04-18T01:46:11.37503Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Load dataset\ndata_path = '/kaggle/input/aptos2019-blindness-detection/train.csv'\ndata = pd.read_csv(data_path)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-18T01:46:12.239407Z","iopub.execute_input":"2025-04-18T01:46:12.23997Z","iopub.status.idle":"2025-04-18T01:46:12.259648Z","shell.execute_reply.started":"2025-04-18T01:46:12.239944Z","shell.execute_reply":"2025-04-18T01:46:12.258905Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Add image paths\ndata['image_path'] = '/kaggle/input/aptos2019-blindness-detection/train_images/' + data['id_code'] + '.png'\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-18T01:46:16.232649Z","iopub.execute_input":"2025-04-18T01:46:16.232926Z","iopub.status.idle":"2025-04-18T01:46:16.243069Z","shell.execute_reply.started":"2025-04-18T01:46:16.232907Z","shell.execute_reply":"2025-04-18T01:46:16.242323Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Check for missing image files\nmissing_files = [img_path for img_path in data['image_path'] if not os.path.exists(img_path)]\nif missing_files:\n    print(f\"Missing image files: {missing_files}\")\nelse:\n    print(\"All image files found.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-18T01:46:19.943763Z","iopub.execute_input":"2025-04-18T01:46:19.944444Z","iopub.status.idle":"2025-04-18T01:46:21.900715Z","shell.execute_reply.started":"2025-04-18T01:46:19.944418Z","shell.execute_reply":"2025-04-18T01:46:21.900009Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Load and preprocess original images\ndef load_and_preprocess_images(data, image_size=(224, 224)):\n    images = []\n    labels = []\n    failed_images = []\n\n    for img_path, label in zip(data['image_path'], data['diagnosis']):\n        try:\n            img = load_img(img_path, target_size=image_size)\n            img = img_to_array(img) / 255.0  # Normalize\n            images.append(img)\n            labels.append(label)\n        except Exception as e:\n            failed_images.append(img_path)\n            print(f\"Error loading image {img_path}: {e}\")\n\n    print(f\"Loaded {len(images)} images successfully.\")\n    print(f\"Failed to load {len(failed_images)} images.\")\n    return np.array(images), np.array(labels)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-18T01:46:22.795601Z","iopub.execute_input":"2025-04-18T01:46:22.795887Z","iopub.status.idle":"2025-04-18T01:46:22.802266Z","shell.execute_reply.started":"2025-04-18T01:46:22.795866Z","shell.execute_reply":"2025-04-18T01:46:22.801509Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Load images\nX, y = load_and_preprocess_images(data)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-18T01:46:27.190007Z","iopub.execute_input":"2025-04-18T01:46:27.190757Z","iopub.status.idle":"2025-04-18T01:53:27.745366Z","shell.execute_reply.started":"2025-04-18T01:46:27.190729Z","shell.execute_reply":"2025-04-18T01:53:27.7448Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Split data\nif len(X) > 0:\n    X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.2, random_state=42)\n    print(f\"Training set: {X_train.shape}, Validation set: {X_val.shape}\")\nelse:\n    raise ValueError(\"No images to train on.\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-18T01:53:31.702965Z","iopub.execute_input":"2025-04-18T01:53:31.703226Z","iopub.status.idle":"2025-04-18T01:53:32.341573Z","shell.execute_reply.started":"2025-04-18T01:53:31.703208Z","shell.execute_reply":"2025-04-18T01:53:32.340897Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Load pre-trained models\nresnet_model = ResNet50(weights='imagenet', include_top=False, input_shape=(224, 224, 3))\nefficientnet_model = EfficientNetB0(weights='imagenet', include_top=False, input_shape=(224, 224, 3))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-18T01:53:35.663145Z","iopub.execute_input":"2025-04-18T01:53:35.663847Z","iopub.status.idle":"2025-04-18T01:53:41.022112Z","shell.execute_reply.started":"2025-04-18T01:53:35.663825Z","shell.execute_reply":"2025-04-18T01:53:41.021419Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Unfreeze last layers\nfor layer in resnet_model.layers[-10:]:\n    layer.trainable = True\nfor layer in efficientnet_model.layers[-10:]:\n    layer.trainable = True","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-18T01:53:45.334785Z","iopub.execute_input":"2025-04-18T01:53:45.335048Z","iopub.status.idle":"2025-04-18T01:53:45.339291Z","shell.execute_reply.started":"2025-04-18T01:53:45.335028Z","shell.execute_reply":"2025-04-18T01:53:45.338468Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Build hybrid model\ninput_layer = layers.Input(shape=(224, 224, 3))\nresnet_output = resnet_model(input_layer)\nefficientnet_output = efficientnet_model(input_layer)\nresnet_flat = layers.GlobalAveragePooling2D()(resnet_output)\nefficientnet_flat = layers.GlobalAveragePooling2D()(efficientnet_output)\nmerged = layers.concatenate([resnet_flat, efficientnet_flat])\nx = layers.Dropout(0.5)(merged)\nx = layers.Dense(512, activation='relu', kernel_regularizer=l2(0.01))(x)\noutput = layers.Dense(5, activation='softmax')(x)\nmodel = models.Model(inputs=input_layer, outputs=output)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-18T01:53:56.004463Z","iopub.execute_input":"2025-04-18T01:53:56.005077Z","iopub.status.idle":"2025-04-18T01:53:56.036883Z","shell.execute_reply.started":"2025-04-18T01:53:56.005055Z","shell.execute_reply":"2025-04-18T01:53:56.03623Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Compile\nmodel.compile(optimizer=Adam(), loss='sparse_categorical_crossentropy', metrics=['accuracy'])\nmodel.summary()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-18T01:54:01.75741Z","iopub.execute_input":"2025-04-18T01:54:01.757711Z","iopub.status.idle":"2025-04-18T01:54:01.795583Z","shell.execute_reply.started":"2025-04-18T01:54:01.757691Z","shell.execute_reply":"2025-04-18T01:54:01.794921Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Callbacks\ndef lr_schedule(epoch, lr):\n    return lr if epoch < 10 else lr * 0.5\n\nearly_stopping = EarlyStopping(monitor='val_loss', patience=5, restore_best_weights=True)\nlr_scheduler = LearningRateScheduler(lr_schedule)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-18T01:54:05.416697Z","iopub.execute_input":"2025-04-18T01:54:05.416978Z","iopub.status.idle":"2025-04-18T01:54:05.421171Z","shell.execute_reply.started":"2025-04-18T01:54:05.416959Z","shell.execute_reply":"2025-04-18T01:54:05.420486Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Train\nhistory = model.fit(\n    X_train, y_train,\n    epochs=20,\n    batch_size=32,\n    validation_data=(X_val, y_val),\n    callbacks=[early_stopping, lr_scheduler]\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-18T01:54:09.430769Z","iopub.execute_input":"2025-04-18T01:54:09.431519Z","iopub.status.idle":"2025-04-18T02:06:35.257858Z","shell.execute_reply.started":"2025-04-18T01:54:09.431475Z","shell.execute_reply":"2025-04-18T02:06:35.25724Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Evaluate\nloss, acc = model.evaluate(X_val, y_val)\nprint(f\"Validation Accuracy: {acc * 100:.2f}%\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-18T02:08:29.772881Z","iopub.execute_input":"2025-04-18T02:08:29.773395Z","iopub.status.idle":"2025-04-18T02:08:32.555684Z","shell.execute_reply.started":"2025-04-18T02:08:29.773371Z","shell.execute_reply":"2025-04-18T02:08:32.554975Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Plot performance\nplt.figure(figsize=(14, 5))\n\nplt.subplot(1, 2, 1)\nplt.plot(history.history['accuracy'])\nplt.plot(history.history['val_accuracy'])\nplt.title('Accuracy')\nplt.xlabel('Epoch')\nplt.ylabel('Accuracy')\nplt.legend(['Train', 'Validation'])\n\nplt.subplot(1, 2, 2)\nplt.plot(history.history['loss'])\nplt.plot(history.history['val_loss'])\nplt.title('Loss')\nplt.xlabel('Epoch')\nplt.ylabel('Loss')\nplt.legend(['Train', 'Validation'])\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-18T02:08:35.679372Z","iopub.execute_input":"2025-04-18T02:08:35.679824Z","iopub.status.idle":"2025-04-18T02:08:36.071789Z","shell.execute_reply.started":"2025-04-18T02:08:35.679803Z","shell.execute_reply":"2025-04-18T02:08:36.071021Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Save model\nmodel.save('/kaggle/working/aptos_hybrid_model.h5')\nprint(\"Model saved to /kaggle/working/\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-18T02:08:46.8813Z","iopub.execute_input":"2025-04-18T02:08:46.881851Z","iopub.status.idle":"2025-04-18T02:08:48.309114Z","shell.execute_reply.started":"2025-04-18T02:08:46.881828Z","shell.execute_reply":"2025-04-18T02:08:48.308489Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from tensorflow.keras.models import load_model\n\n# Load the saved model\nmodel = load_model('/kaggle/working/aptos_hybrid_model.h5')\nprint(\"Model loaded successfully.\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-18T02:08:57.970955Z","iopub.execute_input":"2025-04-18T02:08:57.97166Z","iopub.status.idle":"2025-04-18T02:08:59.777798Z","shell.execute_reply.started":"2025-04-18T02:08:57.971636Z","shell.execute_reply":"2025-04-18T02:08:59.777148Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Load test metadata\ntest_df = pd.read_csv('/kaggle/input/aptos2019-blindness-detection/test.csv')\ntest_df['image_path'] = '/kaggle/input/aptos2019-blindness-detection/test_images/' + test_df['id_code'] + '.png'\n\n# Preprocess test images\ndef preprocess_test_images(test_df, image_size=(224, 224)):\n    images = []\n    for img_path in test_df['image_path']:\n        img = load_img(img_path, target_size=image_size)\n        img = img_to_array(img) / 255.0\n        images.append(img)\n    return np.array(images)\n\nX_test = preprocess_test_images(test_df)\n\n# Predict and format\npredictions = model.predict(X_test)\npredicted_classes = np.argmax(predictions, axis=1)\n\n# Prepare submission\nsubmission = pd.DataFrame({\n    'id_code': test_df['id_code'],\n    'diagnosis': predicted_classes\n})\nsubmission.to_csv('/kaggle/working/submission.csv', index=False)\nprint(\"✅ Submission file saved to /kaggle/working/submission.csv\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-18T02:09:12.271135Z","iopub.execute_input":"2025-04-18T02:09:12.271683Z","iopub.status.idle":"2025-04-18T02:10:59.164021Z","shell.execute_reply.started":"2025-04-18T02:09:12.271659Z","shell.execute_reply":"2025-04-18T02:10:59.163196Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\n\n# Show sample predictions\nfor i in range(4,15):\n    img = X_test[i]\n    label = predicted_classes[i]\n    plt.imshow(img)\n    plt.title(f\"Predicted Label: {label}\")\n    plt.axis('off')\n    plt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-18T02:11:49.247244Z","iopub.execute_input":"2025-04-18T02:11:49.248044Z","iopub.status.idle":"2025-04-18T02:11:51.174614Z","shell.execute_reply.started":"2025-04-18T02:11:49.248011Z","shell.execute_reply":"2025-04-18T02:11:51.173869Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\n\nstart_idx = 3  # image 4\nend_idx = 15   # up to image 14 (not inclusive in range)\n\nplt.figure(figsize=(15, 5))  # Adjust width to fit all images\n\nfor i, idx in enumerate(range(start_idx, end_idx)):\n    img = X_test[idx]\n    label = predicted_classes[idx]\n\n    plt.subplot(2, 6, i + 1)  # 2 rows, 6 per row (total 12 images)\n    plt.imshow(img, interpolation='nearest')\n    plt.title(f'Pred: {label}', fontsize=9)\n    plt.axis('off')\n\nplt.tight_layout()\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-18T02:11:55.433251Z","iopub.execute_input":"2025-04-18T02:11:55.433893Z","iopub.status.idle":"2025-04-18T02:11:56.390469Z","shell.execute_reply.started":"2025-04-18T02:11:55.433871Z","shell.execute_reply":"2025-04-18T02:11:56.389748Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nimport os\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nfrom sklearn.model_selection import train_test_split\n\n# === Load Training Data ===\ntrain_df = pd.read_csv('/kaggle/input/aptos2019-blindness-detection/train.csv')\ntrain_df['image_path'] = '/kaggle/input/aptos2019-blindness-detection/train_images/' + train_df['id_code'] + '.png'\n\n# === Load Test Data ===\ntest_df = pd.read_csv('/kaggle/input/aptos2019-blindness-detection/test.csv')\ntest_df['image_path'] = '/kaggle/input/aptos2019-blindness-detection/test_images/' + test_df['id_code'] + '.png'\n\n# === Show Basic Info ===\nprint(\"Training Data Sample:\")\nprint(train_df.head())\n\nprint(\"\\nTest Data Sample:\")\nprint(test_df.head())\n\n# === Class Distribution Plot ===\nclass_counts = train_df['diagnosis'].value_counts().sort_index()\n\nplt.figure(figsize=(6, 4))\nsns.barplot(x=class_counts.index, y=class_counts.values, palette='mako')\nplt.title('Training Set Class Distribution')\nplt.xlabel('Diagnosis Class')\nplt.ylabel('Number of Images')\nplt.show()\n\n# === Stratified Train/Validation Split ===\ntrain_split, val_split = train_test_split(\n    train_df,\n    test_size=0.2,\n    stratify=train_df['diagnosis'],\n    random_state=42\n)\n\nprint(f\"\\nTrain size: {len(train_split)}\")\nprint(f\"Validation size: {len(val_split)}\")\n\n# === Sample Outputs ===\nprint(\"\\nSample training rows:\")\nprint(train_split.sample(3))\n\nprint(\"\\nSample validation rows:\")\nprint(val_split.sample(3))\n\nprint(\"\\nTest image count:\", len(test_df))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-18T02:12:00.271063Z","iopub.execute_input":"2025-04-18T02:12:00.271567Z","iopub.status.idle":"2025-04-18T02:12:00.762862Z","shell.execute_reply.started":"2025-04-18T02:12:00.271538Z","shell.execute_reply":"2025-04-18T02:12:00.762208Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# import pandas as pd\n# import numpy as np\n# from tensorflow.keras.models import load_model\n# from tensorflow.keras.preprocessing.image import load_img, img_to_array\n# from tqdm import tqdm\n\n# # Load the trained model\n# model = load_model('/kaggle/working/aptos_hybrid_model.h5')\n\n# # Load test.csv\n# test_df = pd.read_csv('/kaggle/input/aptos2019-blindness-detection/test.csv')\n# test_df['image_path'] = '/kaggle/input/aptos2019-blindness-detection/test_images/' + test_df['id_code'] + '.png'\n\n# # Preprocess test images\n# def preprocess_test_images(df, image_size=(224, 224)):\n#     images = []\n#     for img_path in tqdm(df['image_path'], desc=\"Loading test images\"):\n#         img = load_img(img_path, target_size=image_size)\n#         img = img_to_array(img) / 255.0\n#         images.append(img)\n#     return np.array(images)\n\n# X_test = preprocess_test_images(test_df)\n\n# # Make predictions\n# predictions = model.predict(X_test, verbose=1)\n# predicted_labels = np.argmax(predictions, axis=1)\n\n# # Create submission dataframe\n# submission_df = pd.DataFrame({\n#     'id_code': test_df['id_code'],\n#     'diagnosis': predicted_labels\n# })\n\n# # Save submission file\n# submission_df.to_csv('/kaggle/working/submission.csv', index=False)\n# print(\"✅ Submission file saved to /kaggle/working/submission.csv\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-17T17:21:39.415599Z","iopub.execute_input":"2025-04-17T17:21:39.416153Z","iopub.status.idle":"2025-04-17T17:23:02.589854Z","shell.execute_reply.started":"2025-04-17T17:21:39.41613Z","shell.execute_reply":"2025-04-17T17:23:02.58914Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\nfrom tensorflow.keras.preprocessing.image import load_img, img_to_array\n\n# How many images to show per class\nsamples_per_class = 5\nimage_size = (100, 100)\n\nclasses = sorted(train_df['diagnosis'].unique())\n\nplt.figure(figsize=(samples_per_class * 2, len(classes) * 2))\n\nfor class_index, cls in enumerate(classes):\n    class_subset = train_df[train_df['diagnosis'] == cls].sample(samples_per_class, random_state=42)\n    \n    for i, (_, row) in enumerate(class_subset.iterrows()):\n        img = load_img(row['image_path'], target_size=image_size)\n        \n        plt.subplot(len(classes), samples_per_class, class_index * samples_per_class + i + 1)\n        plt.imshow(img)\n        plt.title(f'Class {cls}', fontsize=8)\n        plt.axis('off')\n\nplt.tight_layout()\nplt.suptitle('Samples from Each Class (Balanced View)', fontsize=14, y=1.02)\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-18T02:12:12.922309Z","iopub.execute_input":"2025-04-18T02:12:12.923044Z","iopub.status.idle":"2025-04-18T02:12:17.194109Z","shell.execute_reply.started":"2025-04-18T02:12:12.92302Z","shell.execute_reply":"2025-04-18T02:12:17.193251Z"}},"outputs":[],"execution_count":null}]}