{"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"},{"sourceId":12062023,"sourceType":"datasetVersion","datasetId":7592152},{"sourceId":12062152,"sourceType":"datasetVersion","datasetId":7592244}],"dockerImageVersionId":31041,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Cài đặt thư viện","metadata":{}},{"cell_type":"code","source":"import os\nos.environ['TF_CPP_MIN_LOG_LEVEL'] = '3'  # 0 = all logs, 1 = info, 2 = warning, 3 = error only\nos.environ['TF_XLA_FLAGS'] = '--tf_xla_auto_jit=0'\nos.environ[\"PYTORCH_CUDA_ALLOC_CONF\"] = \"expandable_segments:True\"\n\nimport warnings\nwarnings.filterwarnings(\"ignore\", category=FutureWarning)\n\nprint('✅')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-22T09:31:08.177821Z","iopub.execute_input":"2025-06-22T09:31:08.178138Z","iopub.status.idle":"2025-06-22T09:31:08.183256Z","shell.execute_reply.started":"2025-06-22T09:31:08.178115Z","shell.execute_reply":"2025-06-22T09:31:08.182511Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Load librabries","metadata":{}},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport os\nimport random\nimport cv2\nimport matplotlib.pyplot as plt\nimport seaborn as sns\n\nfrom sklearn.metrics import confusion_matrix, classification_report, roc_curve, auc, cohen_kappa_score\nfrom sklearn.preprocessing import label_binarize\n\nimport tensorflow as tf\nfrom tensorflow.keras.applications import MobileNetV2\nfrom tensorflow.keras.models import Model\nfrom tensorflow.keras.layers import Dense, Dropout, GlobalAveragePooling2D, Input\nfrom tensorflow.keras.optimizers import Adam\nfrom tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau, ModelCheckpoint\nfrom tensorflow.keras.utils import Sequence, to_categorical\n\nprint('✅')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-22T09:31:08.184321Z","iopub.execute_input":"2025-06-22T09:31:08.184543Z","iopub.status.idle":"2025-06-22T09:31:08.204438Z","shell.execute_reply.started":"2025-06-22T09:31:08.184520Z","shell.execute_reply":"2025-06-22T09:31:08.203812Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# === SEED EVERYTHING ===\ndef seed_everything(seed=23):\n    os.environ['PYTHONHASHSEED'] = str(seed)\n    tf.random.set_seed(seed)\n    np.random.seed(seed)\n    random.seed(seed)\n\nseed_everything(23)\nprint('✅ Seed set')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-22T09:31:08.205202Z","iopub.execute_input":"2025-06-22T09:31:08.205418Z","iopub.status.idle":"2025-06-22T09:31:08.248236Z","shell.execute_reply.started":"2025-06-22T09:31:08.205404Z","shell.execute_reply":"2025-06-22T09:31:08.247559Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# === IMAGE PREPROCESSING ===\nIMG_SIZE = 224\n\ndef crop_black(img, tol=7):\n    if img.ndim == 2:\n        mask = img > tol\n        return img[np.ix_(mask.any(1), mask.any(0))]\n    elif img.ndim == 3:\n        gray_img = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)\n        mask = gray_img > tol\n        if img[:, :, 0][np.ix_(mask.any(1), mask.any(0))].shape[0] == 0:\n            return img\n        else:\n            img1 = img[:, :, 0][np.ix_(mask.any(1), mask.any(0))]\n            img2 = img[:, :, 1][np.ix_(mask.any(1), mask.any(0))]\n            img3 = img[:, :, 2][np.ix_(mask.any(1), mask.any(0))]\n            return np.stack([img1, img2, img3], axis=-1)\n\ndef circle_crop(img, sigmaX=10):\n    height, width, _ = img.shape\n    size = max(height, width)\n    img = cv2.resize(img, (size, size))\n    x, y = size // 2, size // 2\n    r = min(x, y)\n    mask = np.zeros((size, size), np.uint8)\n    cv2.circle(mask, (x, y), r, 1, thickness=-1)\n    return cv2.bitwise_and(img, img, mask=mask)\n\ndef random_crop(img, size=(0.9, 1)):\n    h, w, _ = img.shape\n    cut = 1 - random.uniform(size[0], size[1])\n    i = random.randint(0, int(cut * h))\n    j = random.randint(0, int(cut * w))\n    h_end = i + int((1 - cut) * h)\n    w_end = j + int((1 - cut) * w)\n    return img[i:h_end, j:w_end, :]\n\ndef preprocess_image(path, sigmaX=10, do_random_crop=False):\n    img = cv2.imread(path)\n    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n    img = crop_black(img)\n    if do_random_crop:\n        img = random_crop(img)\n    img = cv2.resize(img, (IMG_SIZE, IMG_SIZE))\n    img = cv2.addWeighted(img, 4, cv2.GaussianBlur(img, (0, 0), sigmaX), -4, 128)\n    img = circle_crop(img, sigmaX=sigmaX)\n    img = img.astype(np.float32) / 255.0\n    return img\nprint('✅')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-22T09:31:08.251400Z","iopub.execute_input":"2025-06-22T09:31:08.251645Z","iopub.status.idle":"2025-06-22T09:31:08.271924Z","shell.execute_reply.started":"2025-06-22T09:31:08.251629Z","shell.execute_reply":"2025-06-22T09:31:08.271302Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class EyeDataGenerator(Sequence):\n    def __init__(self, df, image_dir, batch_size, image_size, num_classes=5, is_train=True, shuffle=True):\n        self.df = df.reset_index(drop=True)\n        self.image_dir = image_dir\n        self.batch_size = batch_size\n        self.image_size = image_size\n        self.num_classes = num_classes\n        self.is_train = is_train\n        self.shuffle = shuffle\n        self.on_epoch_end()\n\n    def __len__(self):\n        return int(np.ceil(len(self.df) / self.batch_size))\n\n    def __getitem__(self, index):\n        batch_df = self.df.iloc[index * self.batch_size:(index + 1) * self.batch_size]\n        images, labels = [], []\n        for _, row in batch_df.iterrows():\n            image = preprocess_image(os.path.join(self.image_dir, row['id_code'] + '.png'),\n                                  image_size=self.image_size,\n                                  do_random_crop=self.is_train)\n            images.append(image)\n            labels.append(row['diagnosis'])\n        images = np.array(images)\n        labels = to_categorical(labels, num_classes=self.num_classes)\n        return images, labels\n\n    def on_epoch_end(self):\n        if self.shuffle:\n            self.df = self.df.sample(frac=1).reset_index(drop=True)\n\n\nprint('✅')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-22T09:31:08.272712Z","iopub.execute_input":"2025-06-22T09:31:08.272952Z","iopub.status.idle":"2025-06-22T09:31:08.288725Z","shell.execute_reply.started":"2025-06-22T09:31:08.272934Z","shell.execute_reply":"2025-06-22T09:31:08.287910Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# === MODEL INITIALIZATION ===\ndef build_model():\n    inputs = Input(shape=(IMG_SIZE, IMG_SIZE, 3))\n    base_model = EfficientNetB3(include_top=False, weights='imagenet', input_tensor=inputs)\n\n    x = GlobalAveragePooling2D()(base_model.output)\n\n    # Block 1\n    x1 = Dense(512)(x)\n    x1 = BatchNormalization()(x1)\n    x1 = Activation('gelu')(x1)\n    x1 = Dropout(0.4)(x1)\n\n    # Block 2\n    x2 = Dense(256)(x1)\n    x2 = BatchNormalization()(x2)\n    x2 = Activation('gelu')(x2)\n    x2 = Dropout(0.3)(x2)\n\n    # Block 3\n    x3 = Dense(128)(x2)\n    x3 = BatchNormalization()(x3)\n    x3 = Activation('gelu')(x3)\n\n    # Residual Add (x1 + x3)\n    x = Add()([x1, x3])\n\n    outputs = Dense(5, activation='softmax')(x)\n\n    model = Model(inputs, outputs)\n    return model\n\nprint('✅')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-22T09:31:08.289476Z","iopub.execute_input":"2025-06-22T09:31:08.289656Z","iopub.status.idle":"2025-06-22T09:31:08.310940Z","shell.execute_reply.started":"2025-06-22T09:31:08.289641Z","shell.execute_reply":"2025-06-22T09:31:08.310159Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Load data","metadata":{}},{"cell_type":"code","source":"# import data\ntrain_df = pd.read_csv('../input/aptos2019-blindness-detection/train.csv')\ntrain_df.columns = ['id_code','diagnosis']\n\ntrain_split = train_df.sample(frac = 0.8, random_state=42)\nval_split = train_df.drop(train_split.index)\n\ntrain_split['id_code'] = train_split['id_code'].astype(str) + \".png\"\nval_split['id_code'] = val_split['id_code'].astype(str) + \".png\"\n\ntrain_split['diagnosis'] = train_split['diagnosis'].astype(str)\nval_split['diagnosis'] = val_split['diagnosis'].astype(str)\n\ntest_df  = pd.read_csv('../input/aptos2019-blindness-detection/sample_submission.csv')\nprint('✅')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-22T09:31:08.313255Z","iopub.execute_input":"2025-06-22T09:31:08.313741Z","iopub.status.idle":"2025-06-22T09:31:08.348590Z","shell.execute_reply.started":"2025-06-22T09:31:08.313714Z","shell.execute_reply":"2025-06-22T09:31:08.348066Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# EDA","metadata":{}},{"cell_type":"code","source":"# Plot class distribution\nplt.figure(figsize=(10, 5))\nsns.countplot(data=train_df, x='diagnosis')\nplt.title(\"Class Distribution\")\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-22T09:31:08.349149Z","iopub.execute_input":"2025-06-22T09:31:08.349378Z","iopub.status.idle":"2025-06-22T09:31:08.483913Z","shell.execute_reply.started":"2025-06-22T09:31:08.349361Z","shell.execute_reply":"2025-06-22T09:31:08.483287Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from tqdm import tqdm\nimage_dir = \"../input/aptos2019-blindness-detection/train_images\"\nplt.figure(figsize=(20, 10))\nfor i in range(10):\n    img = preprocess_image(os.path.join(image_dir, train_df['id_code'][i] + \".png\"))\n    plt.subplot(2, 5, i+1)\n    plt.imshow(img)\n    plt.title(f\"Label: {train_df['diagnosis'][i]}\")\n    plt.axis('off')\nplt.show()\n\n### 7. Image Size Distribution\nimage_stats = []\nfor idx in tqdm(range(len(train_df))):\n    path = os.path.join(image_dir, train_df['id_code'][idx] + \".png\")\n    img = cv2.imread(path)\n    h, w, c = img.shape\n    image_stats.append((h, w, c))\n\nimage_stats_df = pd.DataFrame(image_stats, columns=[\"height\", \"width\", \"channels\"])\nimage_stats_df[\"ratio\"] = image_stats_df[\"width\"] / image_stats_df[\"height\"]\n\nplt.figure(figsize=(18, 5))\nplt.subplot(1, 3, 1)\nplt.hist(image_stats_df['width'], bins=50)\nplt.title(\"Width Distribution\")\nplt.subplot(1, 3, 2)\nplt.hist(image_stats_df['height'], bins=50)\nplt.title(\"Height Distribution\")\nplt.subplot(1, 3, 3)\nplt.hist(image_stats_df['ratio'], bins=50)\nplt.title(\"Aspect Ratio Distribution\")\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-22T09:31:08.484642Z","iopub.execute_input":"2025-06-22T09:31:08.485336Z","iopub.status.idle":"2025-06-22T09:36:51.767441Z","shell.execute_reply.started":"2025-06-22T09:31:08.485315Z","shell.execute_reply":"2025-06-22T09:36:51.766758Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Augment","metadata":{}},{"cell_type":"code","source":"from tensorflow.keras.preprocessing.image import ImageDataGenerator\n\ntrain_datagen = ImageDataGenerator(\n    rotation_range=360,\n    horizontal_flip=True,\n    vertical_flip=True,\n    zoom_range=0.1,\n    width_shift_range=0.1,\n    height_shift_range=0.1,\n    fill_mode='nearest'\n)\n\nval_datagen = ImageDataGenerator()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-22T09:36:51.768168Z","iopub.execute_input":"2025-06-22T09:36:51.768654Z","iopub.status.idle":"2025-06-22T09:36:51.772584Z","shell.execute_reply.started":"2025-06-22T09:36:51.768635Z","shell.execute_reply":"2025-06-22T09:36:51.771912Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Build model","metadata":{}},{"cell_type":"code","source":"from tensorflow.keras.applications import EfficientNetB6\nfrom tensorflow.keras.models import Model\nfrom tensorflow.keras.layers import (\n    Input, Dense, Dropout, GlobalAveragePooling2D,\n    BatchNormalization, Activation, Concatenate\n)\n\ninput_tensor = Input(shape=(224, 224, 3))\nbase_model = EfficientNetB6(weights='imagenet', include_top=False, input_tensor=input_tensor)\n\nx = base_model.output\nx = GlobalAveragePooling2D()(x)\n\n# Block 1\nx1 = Dense(512)(x)\nx1 = BatchNormalization()(x1)\nx1 = Activation('gelu')(x1)\nx1 = Dropout(0.4)(x1)\n\n# Block 2\nx2 = Dense(256)(x1)\nx2 = BatchNormalization()(x2)\nx2 = Activation('gelu')(x2)\nx2 = Dropout(0.3)(x2)\n\n# Block 3\nx3 = Dense(128)(x2)\nx3 = BatchNormalization()(x3)\nx3 = Activation('gelu')(x3)\n\n# Concatenate instead of Add\nx = Concatenate()([x1, x3])  # Shape now (512 + 128 = 640)\n\n# Regularize: reduce to avoid overfitting\nx = Dense(256)(x)\nx = BatchNormalization()(x)\nx = Activation('gelu')(x)\nx = Dropout(0.3)(x)\n\noutput_tensor = Dense(5, activation='softmax')(x)\n\nmodel = Model(inputs=input_tensor, outputs=output_tensor)\nmodel.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])\n\nmodel.summary()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-22T09:36:51.773236Z","iopub.execute_input":"2025-06-22T09:36:51.773437Z","iopub.status.idle":"2025-06-22T09:36:56.265624Z","shell.execute_reply.started":"2025-06-22T09:36:51.773421Z","shell.execute_reply":"2025-06-22T09:36:56.264892Z"},"collapsed":true,"jupyter":{"outputs_hidden":true}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"callbacks = [\n    EarlyStopping(monitor='val_loss', patience=5, restore_best_weights=True),\n    ReduceLROnPlateau(monitor='val_loss', factor=0.5, patience=2),\n    ModelCheckpoint('mobilenetv2_best_model.h5', monitor='val_loss', save_best_only=True)\n]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-22T09:36:56.266423Z","iopub.execute_input":"2025-06-22T09:36:56.266628Z","iopub.status.idle":"2025-06-22T09:36:56.270556Z","shell.execute_reply.started":"2025-06-22T09:36:56.266612Z","shell.execute_reply":"2025-06-22T09:36:56.269845Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_df = train_split\nval_df = val_split\n\ntrain_df['diagnosis'] = train_df['diagnosis'].astype(str)\nval_df['diagnosis'] = val_df['diagnosis'].astype(str)\n\ntrain_generator = train_datagen.flow_from_dataframe(\n    dataframe=train_df,\n    directory=\"../input/aptos2019-blindness-detection/train_images\",\n    x_col=\"id_code\",\n    y_col=\"diagnosis\",\n    target_size=(224, 224),\n    batch_size=32,\n    class_mode='categorical'\n)\n\nval_generator = val_datagen.flow_from_dataframe(\n    dataframe=val_df,\n    directory=\"../input/aptos2019-blindness-detection/train_images\",\n    x_col=\"id_code\",\n    y_col=\"diagnosis\",\n    target_size=(224, 224),\n    batch_size=32,\n    class_mode='categorical'\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-22T09:36:56.272445Z","iopub.execute_input":"2025-06-22T09:36:56.272640Z","iopub.status.idle":"2025-06-22T09:36:58.158980Z","shell.execute_reply.started":"2025-06-22T09:36:56.272625Z","shell.execute_reply":"2025-06-22T09:36:58.158213Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"history = model.fit(\n    train_generator,\n    validation_data = val_generator,\n    epochs = 40,\n    callbacks = callbacks\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-22T09:36:58.159646Z","iopub.execute_input":"2025-06-22T09:36:58.159837Z","iopub.status.idle":"2025-06-22T11:19:58.880154Z","shell.execute_reply.started":"2025-06-22T09:36:58.159822Z","shell.execute_reply":"2025-06-22T11:19:58.879553Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport json\n\noutput_dir = '/kaggle/working'\n\n# Save training history\nhistory_path = os.path.join(output_dir, 'history.json')\nwith open(history_path, 'w') as f:\n    json.dump(history.history, f)\n\n# Save model weights\nweights_path = os.path.join(output_dir, 'mobilenetv2.weights.h5')  # ✅ Must end with `.weights.h5`\nmodel.save_weights(weights_path)\n\n# Save entire model\nmodel_path = os.path.join(output_dir, 'mobilenetv2_model.h5')\nmodel.save(model_path)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-22T11:21:56.739700Z","iopub.execute_input":"2025-06-22T11:21:56.739948Z","iopub.status.idle":"2025-06-22T11:22:01.505437Z","shell.execute_reply.started":"2025-06-22T11:21:56.739930Z","shell.execute_reply":"2025-06-22T11:22:01.504668Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Evaluation","metadata":{}},{"cell_type":"code","source":"val_generator.reset()\ny_pred = model.predict(val_generator, verbose=1)\ny_true = val_generator.classes\ny_pred_classes = np.argmax(y_pred, axis=1)\n\n# Confusion matrix\ncm = confusion_matrix(y_true, y_pred_classes)\nsns.heatmap(cm, annot=True, fmt='d', cmap='Blues')\nplt.xlabel('Predicted')\nplt.ylabel('True')\nplt.title('Confusion Matrix')\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-22T11:22:07.105881Z","iopub.execute_input":"2025-06-22T11:22:07.106183Z","iopub.status.idle":"2025-06-22T11:23:48.802734Z","shell.execute_reply.started":"2025-06-22T11:22:07.106162Z","shell.execute_reply":"2025-06-22T11:23:48.802090Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(classification_report(y_true, y_pred_classes))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-22T11:25:10.336599Z","iopub.execute_input":"2025-06-22T11:25:10.337299Z","iopub.status.idle":"2025-06-22T11:25:10.348255Z","shell.execute_reply.started":"2025-06-22T11:25:10.337276Z","shell.execute_reply":"2025-06-22T11:25:10.347596Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print('Kappa score: ', cohen_kappa_score(y_true, y_pred_classes, weights='quadratic'))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-22T11:25:22.023137Z","iopub.execute_input":"2025-06-22T11:25:22.023699Z","iopub.status.idle":"2025-06-22T11:25:22.029711Z","shell.execute_reply.started":"2025-06-22T11:25:22.023677Z","shell.execute_reply":"2025-06-22T11:25:22.028989Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Plot Loss & Accuracy\nplt.figure(figsize=(15,5))\nplt.subplot(1,2,1)\nplt.plot(history.history['loss'], label='Train Loss')\nplt.plot(history.history['val_loss'], label='Val Loss')\nplt.title('Loss')\nplt.legend()\n\nplt.subplot(1,2,2)\nplt.plot(history.history['accuracy'], label='Train Acc')\nplt.plot(history.history['val_accuracy'], label='Val Acc')\nplt.title('Accuracy')\nplt.legend()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-22T11:25:24.989969Z","iopub.execute_input":"2025-06-22T11:25:24.990468Z","iopub.status.idle":"2025-06-22T11:25:25.285518Z","shell.execute_reply.started":"2025-06-22T11:25:24.990444Z","shell.execute_reply":"2025-06-22T11:25:25.284753Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"y_true_bin = label_binarize(y_true, classes=[0, 1, 2, 3, 4])\nplt.figure(figsize=(10, 8))\nfor i in range(5):\n    fpr, tpr, _ = roc_curve(y_true_bin[:, i], y_pred[:, i])\n    auc_score = roc_auc_score(y_true_bin[:, i], y_pred[:, i])\n    plt.plot(fpr, tpr, label=f\"Class {i} AUC = {auc_score:.2f}\")\n\nplt.plot([0, 1], [0, 1], 'k--')\nplt.xlabel(\"False Positive Rate\")\nplt.ylabel(\"True Positive Rate\")\nplt.title(\"ROC Curve by Class\")\nplt.legend()\nplt.grid(True)\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-22T11:25:31.865965Z","iopub.execute_input":"2025-06-22T11:25:31.866236Z","iopub.status.idle":"2025-06-22T11:25:31.890565Z","shell.execute_reply.started":"2025-06-22T11:25:31.866218Z","shell.execute_reply":"2025-06-22T11:25:31.889784Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from tensorflow.keras.preprocessing import image as keras_image\nimport tensorflow.keras.backend as K\n\nimg_path = f\"../input/aptos2019-blindness-detection/train_images/{df_val['id_code'].iloc[0]}.png\"\nimg = keras_image.load_img(img_path, target_size=(224, 224))\nx = keras_image.img_to_array(img)\nx = np.expand_dims(x, axis=0)\nx = x / 255.0\n\npreds = model.predict(x)\nclass_idx = np.argmax(preds[0])\nclass_output = model.output[:, class_idx]\nlast_conv_layer = model.get_layer(\"top_conv\")\ngrads = K.gradients(class_output, last_conv_layer.output)[0]\npooled_grads = K.mean(grads, axis=(0, 1, 2))\niterate = K.function([model.input], [pooled_grads, last_conv_layer.output[0]])\npooled_grads_value, conv_layer_output_value = iterate([x])\n\nfor i in range(pooled_grads_value.shape[0]):\n    conv_layer_output_value[:, :, i] *= pooled_grads_value[i]\n\nheatmap = np.mean(conv_layer_output_value, axis=-1)\nheatmap = np.maximum(heatmap, 0)\nheatmap /= np.max(heatmap)\n\nimport cv2\nimg = cv2.imread(img_path)\nimg = cv2.resize(img, (224, 224))\nheatmap = cv2.resize(heatmap, (img.shape[1], img.shape[0]))\nheatmap = np.uint8(255 * heatmap)\nheatmap = cv2.applyColorMap(heatmap, cv2.COLORMAP_JET)\nsuperimposed_img = heatmap * 0.4 + img\n\nplt.imshow(cv2.cvtColor(superimposed_img.astype('uint8'), cv2.COLOR_BGR2RGB))\nplt.title(f\"Grad-CAM for Image: {df_val['id_code'].iloc[0]}\")\nplt.axis('off')\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-22T11:23:48.813419Z","iopub.status.idle":"2025-06-22T11:23:48.813656Z","shell.execute_reply.started":"2025-06-22T11:23:48.813558Z","shell.execute_reply":"2025-06-22T11:23:48.813568Z"}},"outputs":[],"execution_count":null}]}