{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.7.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":29762,"databundleVersionId":2541532,"isSourceIdPinned":false,"sourceType":"competition"}],"dockerImageVersionId":30177,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Google Landmark Recognition Challenge 2020\nSimplified image similarity ranking and re-ranking implementation with:\n* EfficientNetB0 backbone for global feature similarity search\n* DELF module for local feature reranking\n\nReference papers:\n* 2020 Recognition challenge winner: https://arxiv.org/abs/2010.01650\n* 2019 Recognition challend 2nd place: https://arxiv.org/abs/1906.03990","metadata":{}},{"cell_type":"code","source":"!nvidia-smi","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-10T18:15:12.942647Z","iopub.execute_input":"2025-05-10T18:15:12.943111Z","iopub.status.idle":"2025-05-10T18:15:14.021545Z","shell.execute_reply.started":"2025-05-10T18:15:12.943061Z","shell.execute_reply":"2025-05-10T18:15:14.020402Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Importing libraries\nimport os\nimport cv2\nimport shutil\nimport numpy as np\nimport pandas as pd\nfrom scipy import spatial\nfrom tqdm import tqdm\nimport tensorflow as tf\nfrom tensorflow.keras import layers\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nfrom sklearn.model_selection import train_test_split","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-10T18:15:14.024386Z","iopub.execute_input":"2025-05-10T18:15:14.024765Z","iopub.status.idle":"2025-05-10T18:15:14.031509Z","shell.execute_reply.started":"2025-05-10T18:15:14.024716Z","shell.execute_reply":"2025-05-10T18:15:14.030433Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Directories and file paths\nTRAIN_DIR = '../input/landmark-recognition-2021/train'\nTRAIN_CSV = '../input/landmark-recognition-2021/train.csv'\ntrain_df = pd.read_csv(TRAIN_CSV)\n\nTRAIN_PATHS = [os.path.join(TRAIN_DIR, f'{img[0]}/{img[1]}/{img[2]}/{img}.jpg') for img in train_df['id']]\ntrain_df['path'] = TRAIN_PATHS\n\ntrain_df","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-10T18:15:14.033093Z","iopub.execute_input":"2025-05-10T18:15:14.033377Z","iopub.status.idle":"2025-05-10T18:15:17.968613Z","shell.execute_reply.started":"2025-05-10T18:15:14.033344Z","shell.execute_reply":"2025-05-10T18:15:17.967637Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Subsetting\ntrain_df_grouped = pd.DataFrame(train_df.landmark_id.value_counts())\ntrain_df_grouped.reset_index(inplace=True)\ntrain_df_grouped.columns = ['landmark_id','count']\n\n# Selected landmarks based on inclass frequency\nselected_landmarks = train_df_grouped[(train_df_grouped['count'] <= 155) & (train_df_grouped['count'] >= 150)]\n\ntrain_df_sub = train_df[train_df['landmark_id'].isin(selected_landmarks['landmark_id'])]\nnew_id = []\ncurrent_id = 0\nprevious_id = int(train_df_sub.head(1)['landmark_id'])\nfor landmark_id in train_df_sub['landmark_id']:\n    if landmark_id == previous_id:\n        new_id.append(current_id)\n    else:\n        current_id += 1\n        new_id.append(current_id)\n        previous_id = landmark_id\n\ntrain_df_sub['new_id'] = new_id\n\nNUM_CLASSES = train_df_sub['landmark_id'].nunique()\n\nprint(f\"Unique classes found: {NUM_CLASSES}\")\ntrain_df_sub","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-10T18:15:17.969747Z","iopub.execute_input":"2025-05-10T18:15:17.969979Z","iopub.status.idle":"2025-05-10T18:15:18.267294Z","shell.execute_reply.started":"2025-05-10T18:15:17.969952Z","shell.execute_reply":"2025-05-10T18:15:18.266374Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Training and validation splits\n# 90/10 stratified split for training and validation\nX_train, X_val, y_train, y_val = train_test_split(train_df_sub[['id', 'path']], train_df_sub['new_id'],\n                                                  train_size = 0.9,\n                                                  random_state = 123,\n                                                  shuffle = True,\n                                                  stratify = train_df_sub['new_id'])\n\n# Held-out test set for inference\n# Further 95/5 split -> 5% of original training set left for test set\nX_train, X_test, y_train, y_test = train_test_split(X_train, y_train,\n                                                   train_size = 0.90,\n                                                   random_state = 123,\n                                                   shuffle = True,\n                                                   stratify = y_train)\n\nassert X_train.shape[0] + X_val.shape[0] + X_test.shape[0] == train_df_sub.shape[0]\n\nprint(f\"Training data shape: {X_train.shape}\")\nprint(f\"Training label shape: {y_train.shape}\")\nprint(f\"Validation data shape: {X_val.shape}\")\nprint(f\"Validation label shape: {y_val.shape}\")\nprint(f\"Test data shape: {X_test.shape}\")\nprint(f\"Test label shape: {y_test.shape}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-10T18:15:18.270123Z","iopub.execute_input":"2025-05-10T18:15:18.271323Z","iopub.status.idle":"2025-05-10T18:15:18.301302Z","shell.execute_reply.started":"2025-05-10T18:15:18.271276Z","shell.execute_reply":"2025-05-10T18:15:18.300218Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(f\"Unique classes on y_train: {y_train.nunique()}\")\nprint(f\"Unique classes on y_val: {y_val.nunique()}\")\nprint(f\"Unique classes on y_test: {y_test.nunique()}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-10T18:15:18.302826Z","iopub.execute_input":"2025-05-10T18:15:18.303132Z","iopub.status.idle":"2025-05-10T18:15:18.310725Z","shell.execute_reply.started":"2025-05-10T18:15:18.303090Z","shell.execute_reply":"2025-05-10T18:15:18.309215Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Classes distribution on training, validation and test sets\nplt.figure(figsize = (10, 3))\nax = sns.histplot(y_train, bins=75, kde = True)\nax.set_title('Distribution of Landmarks on training set')\nplt.tight_layout()\n\nplt.figure(figsize = (10, 3))\nax = sns.histplot(y_val, bins=75, kde = True)\nax.set_title('Distribution of Landmarks on validation set')\nplt.tight_layout()\n\nplt.figure(figsize = (10, 3))\nax = sns.histplot(y_test, bins=75, kde = True)\nax.set_title('Distribution of Landmarks on test set')\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-10T18:15:18.312139Z","iopub.execute_input":"2025-05-10T18:15:18.312370Z","iopub.status.idle":"2025-05-10T18:15:19.576543Z","shell.execute_reply.started":"2025-05-10T18:15:18.312343Z","shell.execute_reply":"2025-05-10T18:15:19.575553Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!rm -r train_sub, test_sub, val_sub && ls","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-10T18:15:19.577744Z","iopub.execute_input":"2025-05-10T18:15:19.577997Z","iopub.status.idle":"2025-05-10T18:15:20.664171Z","shell.execute_reply.started":"2025-05-10T18:15:19.577955Z","shell.execute_reply":"2025-05-10T18:15:20.663139Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Creating image directories for classes subset\nNEW_BASE_DIR = \"/kaggle/working\"\n\n# Training set directory\nfor file, path, landmark in tqdm(zip(X_train['id'], X_train['path'], y_train)):\n    dir = f\"{NEW_BASE_DIR}/train_sub/{str(landmark)}\"\n    os.makedirs(dir, exist_ok = True)\n    fname = f\"{file}.jpg\"\n    shutil.copyfile(src = path, dst = f\"{dir}/{fname}\")\n\n# Validation set directory    \nfor file, path, landmark in tqdm(zip(X_val['id'], X_val['path'], y_val)):\n    dir = f\"{NEW_BASE_DIR}/val_sub/{str(landmark)}\"\n    os.makedirs(dir, exist_ok = True)\n    fname = f\"{file}.jpg\"\n    shutil.copyfile(src = path, dst = f\"{dir}/{fname}\")\n\n# Training set directory\nfor file, path, landmark in tqdm(zip(X_test['id'], X_test['path'], y_test)):\n    dir = f\"{NEW_BASE_DIR}/test_sub/{str(landmark)}\"\n    os.makedirs(dir, exist_ok = True)\n    fname = f\"{file}.jpg\"\n    shutil.copyfile(src = path, dst = f\"{dir}/{fname}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-10T18:15:20.665801Z","iopub.execute_input":"2025-05-10T18:15:20.666068Z","iopub.status.idle":"2025-05-10T18:17:47.568783Z","shell.execute_reply.started":"2025-05-10T18:15:20.666020Z","shell.execute_reply":"2025-05-10T18:17:47.567028Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!ls","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-10T18:17:47.570364Z","iopub.execute_input":"2025-05-10T18:17:47.570613Z","iopub.status.idle":"2025-05-10T18:17:48.644366Z","shell.execute_reply.started":"2025-05-10T18:17:47.570584Z","shell.execute_reply":"2025-05-10T18:17:48.643254Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!cd train_sub && ls","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-10T18:17:48.646393Z","iopub.execute_input":"2025-05-10T18:17:48.646734Z","iopub.status.idle":"2025-05-10T18:17:49.718475Z","shell.execute_reply.started":"2025-05-10T18:17:48.646695Z","shell.execute_reply":"2025-05-10T18:17:49.717340Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"torch.cuda.memory_summary(device=None, abbreviated=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-10T18:17:49.720533Z","iopub.execute_input":"2025-05-10T18:17:49.720900Z","iopub.status.idle":"2025-05-10T18:17:49.990569Z","shell.execute_reply.started":"2025-05-10T18:17:49.720835Z","shell.execute_reply":"2025-05-10T18:17:49.986976Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Creating tensorflow tf.data.Dataset\nfrom tensorflow.keras.utils import image_dataset_from_directory\n\nIMG_SIZE = 224\nBATCH_SIZE = 16\n\nprint(\"Building training dataset...\")\n# Training tf.data.Dataset\ntrain_ds = image_dataset_from_directory(f\"{NEW_BASE_DIR}/train_sub\",\n                                        label_mode = 'int',\n                                        shuffle = True,\n                                        image_size = (IMG_SIZE, IMG_SIZE),\n                                        batch_size = BATCH_SIZE)\n\nprint(\"Building validation dataset...\")\n# Validation tf.data.Dataset\nval_ds = image_dataset_from_directory(f\"{NEW_BASE_DIR}/val_sub\",\n                                        label_mode = 'int',\n                                        shuffle = True,\n                                        image_size = (IMG_SIZE, IMG_SIZE),\n                                        batch_size = BATCH_SIZE)\n\nprint(\"Building test dataset...\")\n# Test tf.data.Dataset\ntest_ds = image_dataset_from_directory(f\"{NEW_BASE_DIR}/test_sub\",\n                                        label_mode = 'int',\n                                        shuffle = True,\n                                        image_size = (IMG_SIZE, IMG_SIZE),\n                                        batch_size = BATCH_SIZE)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-10T18:27:10.782848Z","iopub.execute_input":"2025-05-10T18:27:10.784848Z","iopub.status.idle":"2025-05-10T18:27:11.472594Z","shell.execute_reply.started":"2025-05-10T18:27:10.784779Z","shell.execute_reply":"2025-05-10T18:27:11.471639Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Visualizing a random batch from training dataset\nfor data_batch, labels_batch in train_ds.take(1):\n    ncols = 4\n    nrows = int(data_batch.shape[0]/ncols)\n    fig, ax = plt.subplots(nrows = nrows, ncols = ncols, figsize=(10, 11),\n                           sharex = True, sharey = True)\n    img_counter = 0\n    for image, label in zip(data_batch, labels_batch):\n        axi = ax.flat[img_counter]\n        axi.imshow(image/255.)\n        label = label.numpy()\n#         axi.set_title(np.where(label == 1)[0])\n        axi.set_title(label)\n        img_counter += 1\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-10T18:27:14.919556Z","iopub.execute_input":"2025-05-10T18:27:14.919852Z","iopub.status.idle":"2025-05-10T18:27:17.200282Z","shell.execute_reply.started":"2025-05-10T18:27:14.919817Z","shell.execute_reply":"2025-05-10T18:27:17.199421Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"####### ALTERNATIVE CODE FOR UNBATCHED DATASET #######\n# ncols = 4\n# nrows = 4\n# fig, ax = plt.subplots(nrows = nrows, ncols = ncols, figsize=(10, 11),\n#                        sharex = True, sharey = True)\n# img_counter = 0\n# for image, label in train_ds.take(16):\n#     axi = ax.flat[img_counter]\n#     axi.imshow(image[0]/255.)\n#     label = label.numpy()\n# #         axi.set_title(np.where(label == 1)[0])\n#     axi.set_title(label)\n#     img_counter += 1\n# plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-10T18:17:49.995790Z","iopub.status.idle":"2025-05-10T18:17:49.996160Z","shell.execute_reply.started":"2025-05-10T18:17:49.995944Z","shell.execute_reply":"2025-05-10T18:17:49.995974Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import albumentations as A\nfrom albumentations.pytorch import ToTensorV2\n\n# Defining a data augmentation stage\ntrain_transform = A.Compose([\n    A.RandomResizedCrop(IMG_SIZE, IMG_SIZE, scale=(0.8, 1.0)),\n    A.HorizontalFlip(p=0.5),\n    A.VerticalFlip(p=0.2),\n    A.ImageCompression(quality_lower=99, quality_upper=100),\n    A.RandomBrightnessContrast(p=0.2),\n    A.HueSaturationValue(p=0.2),\n    A.CLAHE(p=0.1),\n    A.GaussianBlur(p=0.1),\n    A.Normalize(),\n    ToTensorV2()\n])\n\n# Inside your custom PyTorch Dataset\ndef __getitem__(self, idx):\n    image = cv2.imread(self.image_paths[idx])\n    image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n    augmented = self.transform(image=image)\n    return {\"image\": augmented[\"image\"], \"path\": self.image_paths[idx]}","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-10T18:29:09.478025Z","iopub.execute_input":"2025-05-10T18:29:09.478960Z","iopub.status.idle":"2025-05-10T18:29:10.525223Z","shell.execute_reply.started":"2025-05-10T18:29:09.478902Z","shell.execute_reply":"2025-05-10T18:29:10.524451Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Displaying variations of a randomly augmented training image\n#plt.figure(figsize=(9, 9))\n#for image, label in train_ds.take(1):\n#    for i in range(9):\n#        ax = plt.subplot(3, 3, i + 1)\n#        augmented_image = img_augmentation(image, training = True)\n#        plt.imshow(augmented_image[15].numpy().astype(\"uint8\"))\n#        plt.axis(\"off\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-10T18:17:49.999854Z","iopub.status.idle":"2025-05-10T18:17:50.000349Z","shell.execute_reply.started":"2025-05-10T18:17:50.000088Z","shell.execute_reply":"2025-05-10T18:17:50.000117Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"####### ALTERNATIVE CODE FOR UNBATCHED DATASET #######\n# # Displaying variations of a randomly augmented training image\n# plt.figure(figsize=(9, 9))\n# for image, label in train_ds.take(16):\n#     for i in range(9):\n#         ax = plt.subplot(3, 3, i + 1)\n#         augmented_image = img_augmentation(image[0], training = True)\n#         plt.imshow(augmented_image.numpy().astype(\"uint8\"))\n#         plt.axis(\"off\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-10T18:17:50.001428Z","iopub.status.idle":"2025-05-10T18:17:50.001908Z","shell.execute_reply.started":"2025-05-10T18:17:50.001642Z","shell.execute_reply":"2025-05-10T18:17:50.001669Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Model\n\n#MODELS_DIR = f\"{NEW_BASE_DIR}/models\"\n\n#os.makedirs(MODELS_DIR, exist_ok = True)\n\n# Model instantiator\n#def build_model(num_classes = None):\n#    inputs = keras.Input(shape = (IMG_SIZE, IMG_SIZE, 3))\n#    x = img_augmentation(inputs)\n    # EfficientNetB0 backbone\n#    model = EfficientNetB0(input_tensor = x,\n#                           weights = 'imagenet',\n#                           include_top = False,\n#                           drop_connect_rate = DROP_CONNECT_RATE)\n#    \n#    # Freeze pretrained weights\n#    model.trainable = False\n    \n    # Rebuild top\n#    x = layers.GlobalAveragePooling2D(name = \"avg_pool\")(model.output)\n#    x = layers.BatchNormalization()(x)\n#    x = layers.Dropout(TOP_DROPOUT_RATE, name = \"top_dropout\")(x)\n    \n    # Embedding\n#    embedding = layers.Dense(512, name = \"embedding_512\")(x)\n#    outputs = layers.Dense(num_classes, activation = \"softmax\", name = \"softmax\")(embedding)\n    \n    # Compile\n#    model = tf.keras.Model(inputs, outputs, name = \"EfficientNetB0\")\n#    optimizer = tf.keras.optimizers.Adam(learning_rate = ADAM_LR)\n#    model.compile(optimizer = optimizer,\n#                 loss = \"sparse_categorical_crossentropy\",\n#                 metrics = [\"accuracy\"])\n    \n#    return model","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-10T18:17:50.003465Z","iopub.status.idle":"2025-05-10T18:17:50.003965Z","shell.execute_reply.started":"2025-05-10T18:17:50.003685Z","shell.execute_reply":"2025-05-10T18:17:50.003711Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"pip install --upgrade torchvision","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-10T18:17:50.005918Z","iopub.status.idle":"2025-05-10T18:17:50.006396Z","shell.execute_reply.started":"2025-05-10T18:17:50.006146Z","shell.execute_reply":"2025-05-10T18:17:50.006173Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"pip install efficientnet_pytorch","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-10T18:17:50.008004Z","iopub.status.idle":"2025-05-10T18:17:50.008490Z","shell.execute_reply.started":"2025-05-10T18:17:50.008240Z","shell.execute_reply":"2025-05-10T18:17:50.008266Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nimport torchvision.models as models\n\nfrom efficientnet_pytorch import EfficientNet\nimport torch.nn as nn\nimport torch\n\nclass EfficientNetLandmark(nn.Module):\n    def __init__(self, num_classes, dropout_rate=0.2):\n        super(EfficientNetLandmark, self).__init__()\n        self.backbone = EfficientNet.from_pretrained('efficientnet-b5')\n        \n        # Freeze backbone\n        for param in self.backbone.parameters():\n            param.requires_grad = False\n        \n        # Replace classifier\n        self.backbone._fc = nn.Sequential(\n            nn.Dropout(p=dropout_rate),\n            nn.Linear(self.backbone._fc.in_features, 512),\n        )\n\n        self.embedding = nn.Identity()\n        self.classifier = nn.Linear(512, num_classes)\n\n    def forward(self, x):\n        x = self.backbone(x)\n        embedding = self.embedding(x)\n        out = self.classifier(embedding)\n        return out, embedding","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-10T18:23:05.710972Z","iopub.execute_input":"2025-05-10T18:23:05.711355Z","iopub.status.idle":"2025-05-10T18:23:07.589402Z","shell.execute_reply.started":"2025-05-10T18:23:05.711316Z","shell.execute_reply":"2025-05-10T18:23:07.588301Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Training history visualization\ndef plot_hist(hist):\n    plt.plot(hist.history[\"accuracy\"])\n    plt.plot(hist.history[\"val_accuracy\"])\n    plt.title(\"model accuracy\")\n    plt.ylabel(\"accuracy\")\n    plt.xlabel(\"epoch\")\n    plt.legend([\"train\", \"validation\"], loc=\"upper left\")\n    plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-10T18:17:50.011975Z","iopub.status.idle":"2025-05-10T18:17:50.012462Z","shell.execute_reply.started":"2025-05-10T18:17:50.012212Z","shell.execute_reply":"2025-05-10T18:17:50.012238Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Instantiating model\n# Hyperparameters\n#DROP_CONNECT_RATE = 0.2 # Dropout rate for stochastic depth on EfficientNet\n#TOP_DROPOUT_RATE = 0.2  # Top dropout\n#INIT_LR = 5e-3          # Initial learning rate\n#EPOCHS = 20\n# Adam optimizer learning rate schedule\n#ADAM_LR = tf.keras.optimizers.schedules.ExponentialDecay(\n#    INIT_LR,\n#    decay_steps=100,\n#    decay_rate=0.96,\n#    staircase=True)\n\n#model = build_model(num_classes = NUM_CLASSES)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-10T18:17:50.013898Z","iopub.status.idle":"2025-05-10T18:17:50.014379Z","shell.execute_reply.started":"2025-05-10T18:17:50.014122Z","shell.execute_reply":"2025-05-10T18:17:50.014149Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def compute_gap(preds, confs, targets):\n    df = pd.DataFrame({\n        \"pred\": preds,\n        \"conf\": confs,\n        \"target\": targets\n    })\n\n    df.sort_values(\"conf\", ascending=False, inplace=True)\n    correct = 0\n    total_precision = 0.0\n\n    for i, row in df.iterrows():\n        if row[\"pred\"] == row[\"target\"]:\n            correct += 1\n            total_precision += correct / (i + 1)\n\n    return total_precision / len(df)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-10T18:17:50.015746Z","iopub.status.idle":"2025-05-10T18:17:50.016258Z","shell.execute_reply.started":"2025-05-10T18:17:50.015981Z","shell.execute_reply":"2025-05-10T18:17:50.016007Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!ls","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-10T18:17:50.017934Z","iopub.status.idle":"2025-05-10T18:17:50.018422Z","shell.execute_reply.started":"2025-05-10T18:17:50.018169Z","shell.execute_reply":"2025-05-10T18:17:50.018196Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nimport torch.nn.functional as F\nfrom tqdm import tqdm\nimport pandas as pd\n\ndef train(model, dataloader, criterion, optimizer, device):\n    model.train()\n    running_loss = 0.0\n    correct = 0\n    total = 0\n\n    for images, labels in tqdm(dataloader, desc=\"Training\"):\n        images, labels = images.to(device), labels.to(device)\n\n        optimizer.zero_grad()\n        outputs, _ = model(images)\n        loss = criterion(outputs, labels)\n        loss.backward()\n        optimizer.step()\n\n        running_loss += loss.item() * images.size(0)\n        preds = outputs.argmax(dim=1)\n        correct += (preds == labels).sum().item()\n        total += labels.size(0)\n\n    return running_loss / total, correct / total\n\n\ndef evaluate(model, dataloader, criterion, device):\n    model.eval()\n    running_loss = 0.0\n    correct = 0\n    total = 0\n\n    all_preds, all_probs, all_labels = [], [], []\n\n    with torch.no_grad():\n        for images, labels in tqdm(dataloader, desc=\"Evaluating\"):\n            images, labels = images.to(device), labels.to(device)\n            outputs, _ = model(images)\n            loss = criterion(outputs, labels)\n\n            running_loss += loss.item() * images.size(0)\n            probs = F.softmax(outputs, dim=1)\n            preds = probs.argmax(dim=1)\n\n            correct += (preds == labels).sum().item()\n            total += labels.size(0)\n\n            all_preds.extend(preds.cpu().tolist())\n            all_probs.extend(probs.max(dim=1).values.cpu().tolist())\n            all_labels.extend(labels.cpu().tolist())\n\n    acc = correct / total\n    loss = running_loss / total\n    return loss, acc, all_preds, all_probs, all_labels","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-10T18:17:50.019937Z","iopub.status.idle":"2025-05-10T18:17:50.020435Z","shell.execute_reply.started":"2025-05-10T18:17:50.020165Z","shell.execute_reply":"2025-05-10T18:17:50.020195Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def run_training(model, train_loader, val_loader, optimizer, criterion, device, epochs=10):\n    best_val_acc = 0.0\n\n    for epoch in range(epochs):\n        print(f\"\\nEpoch {epoch+1}/{epochs}\")\n        train_loss, train_acc = train(model, train_loader, criterion, optimizer, device)\n        val_loss, val_acc, preds, confs, labels = evaluate(model, val_loader, criterion, device)\n        gap_score = compute_gap(preds, confs, labels)\n\n        print(f\"Train Loss: {train_loss:.4f} | Acc: {train_acc:.4f}\")\n        print(f\"Val   Loss: {val_loss:.4f} | Acc: {val_acc:.4f} | GAP@20: {gap_score:.4f}\")\n\n        # Save best model\n        if val_acc > best_val_acc:\n            best_val_acc = val_acc\n            torch.save(model.state_dict(), \"efficientnet_landmark_best.pth\")\n            print(\"Saved best model ✅\")\n\n    print(\"Training completed.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-10T18:17:50.021932Z","iopub.status.idle":"2025-05-10T18:17:50.022409Z","shell.execute_reply.started":"2025-05-10T18:17:50.022162Z","shell.execute_reply":"2025-05-10T18:17:50.022188Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nmodel = EfficientNetLandmark(num_classes=NUM_CLASSES).to(device)\n\noptimizer = torch.optim.Adam(model.parameters(), lr=5e-4)\ncriterion = torch.nn.CrossEntropyLoss()\n\nrun_training(model, train_loader, val_loader, optimizer, criterion, device, epochs=5)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-10T18:17:50.023909Z","iopub.status.idle":"2025-05-10T18:17:50.024390Z","shell.execute_reply.started":"2025-05-10T18:17:50.024137Z","shell.execute_reply":"2025-05-10T18:17:50.024164Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Cosine Similarity\nPairwise query: key search for similarity candidates. In the following example:\n* Query images: validation set\n* Key images: training set","metadata":{}},{"cell_type":"code","source":"# Auxiliar functions\n# Load image\ndef get_image(path, resize = False, reshape = False, target_size = None):\n    img = cv2.imread(path)\n    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n    if resize:\n        img = cv2.resize(img, dsize = (target_size, target_size))\n    if reshape:\n        img = tf.reshape(img, [1, target_size, target_size, 3])\n    return img\n\n# Get landmark samples\ndef get_landmark(landmark_id, samples = 16):\n    nrows = samples // 4\n    random_imgs = np.random.choice(train_df_sub[train_df_sub['new_id'] == landmark_id].index, samples, replace = False)\n    plt.figure(figsize = (12, 10))\n    for i, img in enumerate(train_df_sub.loc[random_imgs, :].values):\n        ax = plt.subplot(nrows, 4, i + 1)\n        plt.imshow(get_image(img[2]))\n        plt.title(f\"{img[0]}\")\n        plt.suptitle(f\"Samples of landmark {landmark_id}\", fontsize = 14, y = 0.94, weight = \"bold\")\n        plt.axis(\"off\")\n\n# Get image embeddings\ndef get_embeddings(model, image_paths, input_size, as_df = True):\n    embeddings = {}\n    embeddings['images_paths'] = []\n    embeddings['embedded_images'] = []\n    \n    target_dir = os.path.split(os.path.split(image_paths[0])[0])[0]\n    \n    print(f\"Retrieving embeddings for {target_dir} with {model.name}...\")\n    for image_path in tqdm(image_paths):\n        embeddings['images_paths'].append(image_path)\n        embedded_image = model.predict(get_image(image_path,\n                                                 resize = True,\n                                                 reshape = True,\n                                                 target_size = input_size))\n        embeddings['embedded_images'].append(embedded_image)\n    \n    if as_df:\n        embeddings = pd.DataFrame(embeddings)\n    \n    return embeddings\n\n# Get similarities between query key pair\ndef get_similarities(query, key):\n    '''\n    Get cosine similarity matrix between query and key pairs\n    Arguments:\n    query, key: embedded images\n    '''\n    query_array = np.stack(query.tolist()).reshape(query.shape[0],\n                                                   query[0].shape[1])\n    key_array = np.stack(key.tolist()).reshape(key.shape[0],\n                                               key[0].shape[1])\n    \n    # Initializing similarity matrix\n    similarity = np.zeros((query_array.shape[0], key_array.shape[0]))\n    \n    # Getting pairwise similarities\n    print(f\"Getting pairwise {query_array.shape[0]} query: {key_array.shape[0]} key similarities...\")\n    for query_index in tqdm(range(query_array.shape[0])):\n        similarity[query_index] = 1 - spatial.distance.cdist(query_array[np.newaxis, query_index, :],\n                                                             key_array,\n                                                             'cosine')[0]\n    return similarity\n\n# Plot top ranked images\ndef plot_similar(similar_imgs, img_paths):\n    '''\n    Plot top N similar samples from similarity index\n    '''\n    plt.figure(figsize = (18, 6))\n    nrows = similar_imgs.shape[0]//5\n    for i, img in enumerate(similar_imgs):\n        ax = plt.subplot(nrows, 5, i + 1)\n        plt.imshow(get_image(img_paths[img]))\n        plt.title(f\"Landmark id: {os.path.split(os.path.split(img_paths[img])[0])[1]}\")\n        plt.axis(\"off\")\n\n\n# Aggregate query and top similar plots\ndef query_top(image_index, top_n = 5, figsize = (6, 6), reranked = None):\n    '''\n    Plot top N similar samples against queried image\n    If reranked, provide reranked dataframe with ['top_similar'] index reordered by reranked confidence\n    '''\n    image_id = os.path.split(val_embeddings['images_paths'][image_index])[1]\n    query_landmark_id = os.path.split(os.path.split(val_embeddings['images_paths'][image_index])[0])[1]\n    \n    if type(reranked) == pd.core.frame.DataFrame:\n        similar_n = reranked['top_similar'][:top_n]\n    else:\n        similar_n = np.argsort(val_train_similarity[image_index])[::-1][:top_n]\n                \n    print(f\"Queried image: {image_id}\")\n    plt.figure(figsize = figsize)\n    plt.imshow(get_image(val_embeddings['images_paths'][image_index]))\n    plt.title(f\"Landmark id: {query_landmark_id}\")\n    plt.axis(\"off\")\n    plot_similar(similar_n, train_embeddings['images_paths'])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-10T18:55:49.299934Z","iopub.execute_input":"2025-05-10T18:55:49.300599Z","iopub.status.idle":"2025-05-10T18:55:49.322844Z","shell.execute_reply.started":"2025-05-10T18:55:49.300557Z","shell.execute_reply":"2025-05-10T18:55:49.321869Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from tensorflow.keras.applications import EfficientNetB0\nfrom tensorflow.keras.models import Model\nimport tensorflow as tf\n\nIMG_SIZE = 224  # or 240 if using EfficientNetB1, etc.\n\n# Load EfficientNetB0 with custom top layer\nbase_model = EfficientNetB0(include_top=False, input_shape=(IMG_SIZE, IMG_SIZE, 3), weights='imagenet', pooling='avg')\nx = tf.keras.layers.Dense(512, activation='relu', name='embedding_512')(base_model.output)\noutput = tf.keras.layers.Dense(NUM_CLASSES, activation='softmax')(x)\nmodel = Model(inputs=base_model.input, outputs=output)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-10T18:25:22.238379Z","iopub.execute_input":"2025-05-10T18:25:22.238751Z","iopub.status.idle":"2025-05-10T18:25:24.380287Z","shell.execute_reply.started":"2025-05-10T18:25:22.238699Z","shell.execute_reply":"2025-05-10T18:25:24.379310Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Embedding models\nembedding_layer = 'embedding_512'\nembedding_model = tf.keras.Model(inputs = model.input,\n                                 outputs = model.get_layer(embedding_layer).output,\n                                 name = \"EfficientNetB0_embed512\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-10T18:25:39.688576Z","iopub.execute_input":"2025-05-10T18:25:39.688889Z","iopub.status.idle":"2025-05-10T18:25:39.711558Z","shell.execute_reply.started":"2025-05-10T18:25:39.688855Z","shell.execute_reply":"2025-05-10T18:25:39.710781Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Retrieving embeddings\ntrain_img_paths = train_ds.file_paths\nval_img_paths = val_ds.file_paths\n\ntrain_embeddings = get_embeddings(model = embedding_model,\n                                 image_paths = train_img_paths,\n                                 input_size = IMG_SIZE)\n\nval_embeddings = get_embeddings(model = embedding_model,\n                                 image_paths = val_img_paths,\n                                 input_size = IMG_SIZE)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-10T18:33:26.500676Z","iopub.execute_input":"2025-05-10T18:33:26.501061Z","iopub.status.idle":"2025-05-10T18:51:32.292198Z","shell.execute_reply.started":"2025-05-10T18:33:26.501004Z","shell.execute_reply":"2025-05-10T18:51:32.291296Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_embeddings.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-10T18:52:56.254874Z","iopub.execute_input":"2025-05-10T18:52:56.255234Z","iopub.status.idle":"2025-05-10T18:52:56.272961Z","shell.execute_reply.started":"2025-05-10T18:52:56.255198Z","shell.execute_reply":"2025-05-10T18:52:56.272110Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"val_embeddings.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-10T18:52:59.027425Z","iopub.execute_input":"2025-05-10T18:52:59.027733Z","iopub.status.idle":"2025-05-10T18:52:59.046225Z","shell.execute_reply.started":"2025-05-10T18:52:59.027700Z","shell.execute_reply":"2025-05-10T18:52:59.044925Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"val_train_similarity = get_similarities(val_embeddings['embedded_images'],\n                                        train_embeddings['embedded_images'])\nval_train_similarity.shape","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-10T18:53:00.948539Z","iopub.execute_input":"2025-05-10T18:53:00.948834Z","iopub.status.idle":"2025-05-10T18:53:25.125124Z","shell.execute_reply.started":"2025-05-10T18:53:00.948804Z","shell.execute_reply":"2025-05-10T18:53:25.124278Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Calculating confidence score per submission\ndef confidence_top(query = None, key = None, similarity = None, query_image_index = None, top = 5):\n    '''\n    Arguments:\n    query_image_index = index of query image on similarity matrix query axis\n    Return confidence scores for top N predictions\n    '''\n    query_paths = query['images_paths']\n    key_paths = key['images_paths']\n    \n    similar_n = np.argsort(similarity[query_image_index])[::-1][:top]\n    \n    confidence_df = {}    \n    confidence_df['top_similar'] = []\n    for similar in similar_n:\n        confidence_df['top_similar'].append(similar)\n\n    confidence_df['image_paths'] = []\n    for similar in similar_n:\n        similar_image_path = key_paths[similar]\n        confidence_df['image_paths'].append(similar_image_path)    \n        \n    confidence_df['prediction'] = []\n    for similar in similar_n:\n        similar_image_path = key_paths[similar]\n        y = int(os.path.split(os.path.split(similar_image_path)[0])[1])\n        confidence_df['prediction'].append(y)  \n    \n    confidence_df['cos_similarity'] = []\n    for similar in similar_n:\n        confidence_df['cos_similarity'].append(similarity[query_image_index][similar]) \n    \n    return pd.DataFrame(confidence_df)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-10T18:57:00.157091Z","iopub.execute_input":"2025-05-10T18:57:00.157390Z","iopub.status.idle":"2025-05-10T18:57:00.166278Z","shell.execute_reply.started":"2025-05-10T18:57:00.157355Z","shell.execute_reply":"2025-05-10T18:57:00.165109Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"query_image_index = 0\n\nquery_top(query_image_index)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-10T18:54:17.852976Z","iopub.execute_input":"2025-05-10T18:54:17.853309Z","iopub.status.idle":"2025-05-10T18:54:18.681200Z","shell.execute_reply.started":"2025-05-10T18:54:17.853276Z","shell.execute_reply":"2025-05-10T18:54:18.680338Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"top_n = 100","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-10T18:54:49.634803Z","iopub.execute_input":"2025-05-10T18:54:49.635123Z","iopub.status.idle":"2025-05-10T18:54:49.639683Z","shell.execute_reply.started":"2025-05-10T18:54:49.635088Z","shell.execute_reply":"2025-05-10T18:54:49.638427Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"confidence_df = confidence_top(query = val_embeddings,\n                               key = train_embeddings,\n                               similarity = val_train_similarity,\n                               query_image_index = query_image_index,\n                               top = top_n)\n\nconfidence_df","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-10T18:54:51.239161Z","iopub.execute_input":"2025-05-10T18:54:51.239461Z","iopub.status.idle":"2025-05-10T18:54:51.257867Z","shell.execute_reply.started":"2025-05-10T18:54:51.239429Z","shell.execute_reply":"2025-05-10T18:54:51.257001Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"query_image_index = 4\n\nquery_top(query_image_index)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-10T18:54:58.134126Z","iopub.execute_input":"2025-05-10T18:54:58.134976Z","iopub.status.idle":"2025-05-10T18:54:59.046628Z","shell.execute_reply.started":"2025-05-10T18:54:58.134926Z","shell.execute_reply":"2025-05-10T18:54:59.045737Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"confidence_df = confidence_top(query = val_embeddings,\n                               key = train_embeddings,\n                               similarity = val_train_similarity,\n                               query_image_index = query_image_index,\n                               top = top_n)\n\nconfidence_df","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-10T18:55:00.684895Z","iopub.execute_input":"2025-05-10T18:55:00.685595Z","iopub.status.idle":"2025-05-10T18:55:00.701914Z","shell.execute_reply.started":"2025-05-10T18:55:00.685553Z","shell.execute_reply":"2025-05-10T18:55:00.700992Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"query_image_index = 65\n\nquery_top(query_image_index)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-10T18:55:02.716588Z","iopub.execute_input":"2025-05-10T18:55:02.717426Z","iopub.status.idle":"2025-05-10T18:55:03.615532Z","shell.execute_reply.started":"2025-05-10T18:55:02.717377Z","shell.execute_reply":"2025-05-10T18:55:03.614428Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"confidence_df = confidence_top(query = val_embeddings,\n                               key = train_embeddings,\n                               similarity = val_train_similarity,\n                               query_image_index = query_image_index,\n                               top = top_n)\n\nconfidence_df","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-10T18:58:09.253666Z","iopub.execute_input":"2025-05-10T18:58:09.254494Z","iopub.status.idle":"2025-05-10T18:58:09.268095Z","shell.execute_reply.started":"2025-05-10T18:58:09.254455Z","shell.execute_reply":"2025-05-10T18:58:09.267302Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"query_image_index = 13\n\nquery_top(query_image_index)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-10T18:55:09.577260Z","iopub.execute_input":"2025-05-10T18:55:09.577544Z","iopub.status.idle":"2025-05-10T18:55:10.278410Z","shell.execute_reply.started":"2025-05-10T18:55:09.577511Z","shell.execute_reply":"2025-05-10T18:55:10.277480Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"confidence_df = confidence_top(query = val_embeddings,\n                               key = train_embeddings,\n                               similarity = val_train_similarity,\n                               query_image_index = query_image_index,\n                               top = top_n)\n\nconfidence_df","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-10T18:55:13.812527Z","iopub.execute_input":"2025-05-10T18:55:13.812829Z","iopub.status.idle":"2025-05-10T18:55:13.832461Z","shell.execute_reply.started":"2025-05-10T18:55:13.812796Z","shell.execute_reply":"2025-05-10T18:55:13.831507Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Object oclusion example\nObject oclusion is only one of the examples of how a local feature reranking method improves query performance","metadata":{}},{"cell_type":"code","source":"query_image_index = 887\ntop_n = 10\n\nquery_top(query_image_index, top_n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-10T18:55:19.050519Z","iopub.execute_input":"2025-05-10T18:55:19.051431Z","iopub.status.idle":"2025-05-10T18:55:20.602129Z","shell.execute_reply.started":"2025-05-10T18:55:19.051388Z","shell.execute_reply":"2025-05-10T18:55:20.601215Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"confidence_df = confidence_top(query = val_embeddings,\n                               key = train_embeddings,\n                               similarity = val_train_similarity,\n                               query_image_index = query_image_index,\n                               top = top_n)\n\nconfidence_df","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-10T18:55:22.301949Z","iopub.execute_input":"2025-05-10T18:55:22.302241Z","iopub.status.idle":"2025-05-10T18:55:22.315086Z","shell.execute_reply.started":"2025-05-10T18:55:22.302212Z","shell.execute_reply":"2025-05-10T18:55:22.314236Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## DELF module\nLocal features search\n\nReferences:\n* Large-Scale Image Retrieval with Attentive Deep Local Features: https://arxiv.org/abs/1612.06321\n* DELF on Tensorflow Hub: https://github.com/tensorflow/models/tree/master/research/delf\n","metadata":{}},{"cell_type":"code","source":"DELF_IMG_SIZE = 600","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-10T18:55:25.108133Z","iopub.execute_input":"2025-05-10T18:55:25.108425Z","iopub.status.idle":"2025-05-10T18:55:25.112835Z","shell.execute_reply.started":"2025-05-10T18:55:25.108394Z","shell.execute_reply":"2025-05-10T18:55:25.111866Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"confidence_df = confidence_top(query, key, similarity, query_image_index, top=75)\n\nsimilar_n = confidence_df['top_similar'].values","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-10T18:57:36.182012Z","iopub.execute_input":"2025-05-10T18:57:36.182822Z","iopub.status.idle":"2025-05-10T18:57:36.208530Z","shell.execute_reply.started":"2025-05-10T18:57:36.182785Z","shell.execute_reply":"2025-05-10T18:57:36.207485Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"image_1 = get_image(val_embeddings['images_paths'][887],\n                    resize = True,\n                    target_size = DELF_IMG_SIZE)\n\nplt.figure(figsize = (6, 6))\nplt.imshow(image_1)\nplt.axis(\"off\")\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-10T18:55:26.643657Z","iopub.execute_input":"2025-05-10T18:55:26.643959Z","iopub.status.idle":"2025-05-10T18:55:26.830719Z","shell.execute_reply.started":"2025-05-10T18:55:26.643923Z","shell.execute_reply":"2025-05-10T18:55:26.829626Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"similar_n = confidence_df['top_similar'].values","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-10T18:59:23.211740Z","iopub.execute_input":"2025-05-10T18:59:23.212065Z","iopub.status.idle":"2025-05-10T18:59:23.217154Z","shell.execute_reply.started":"2025-05-10T18:59:23.212016Z","shell.execute_reply":"2025-05-10T18:59:23.216227Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"image_2 = get_image(train_embeddings['images_paths'][similar_n[5]],\n                     resize = True,\n                     target_size = DELF_IMG_SIZE)\n\nplt.figure(figsize = (6, 6))\nplt.imshow(image_2)\nplt.axis(\"off\")\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-10T18:59:28.689189Z","iopub.execute_input":"2025-05-10T18:59:28.689504Z","iopub.status.idle":"2025-05-10T18:59:28.869452Z","shell.execute_reply.started":"2025-05-10T18:59:28.689469Z","shell.execute_reply":"2025-05-10T18:59:28.868499Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from absl import logging\nfrom PIL import Image, ImageOps\nfrom scipy.spatial import cKDTree\nfrom skimage.feature import plot_matches\nfrom skimage.measure import ransac\nfrom skimage.transform import AffineTransform\nfrom six import BytesIO\n\nimport tensorflow_hub as hub\nfrom six.moves.urllib.request import urlopen","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-10T18:59:35.075151Z","iopub.execute_input":"2025-05-10T18:59:35.075829Z","iopub.status.idle":"2025-05-10T18:59:35.080855Z","shell.execute_reply.started":"2025-05-10T18:59:35.075793Z","shell.execute_reply":"2025-05-10T18:59:35.079866Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"delf = hub.load('https://tfhub.dev/google/delf/1').signatures['default']","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-10T18:58:25.190547Z","iopub.execute_input":"2025-05-10T18:58:25.190846Z","iopub.status.idle":"2025-05-10T18:58:30.904155Z","shell.execute_reply.started":"2025-05-10T18:58:25.190811Z","shell.execute_reply":"2025-05-10T18:58:30.903169Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# DELF module\ndef run_delf(image):\n    '''\n    Apply DELF module to the input image\n    Arguments:\n    image: np.array resized image\n    '''\n    float_image = tf.image.convert_image_dtype(image, tf.float32)\n\n    return delf(\n      image = float_image,\n      score_threshold = tf.constant(100.0),\n      image_scales = tf.constant([0.25, 0.3536, 0.5, 0.7071, 1.0, 1.4142, 2.0]),\n      max_feature_num = tf.constant(1000))\n\ndef match_images(image1, image2, result1, result2, verbose = True):\n    distance_threshold = 0.8\n\n    # Read features.\n    num_features_1 = result1['locations'].shape[0]\n    num_features_2 = result2['locations'].shape[0]\n    \n    if verbose:\n        print(\"Loaded image 1's %d features\" % num_features_1)\n        print(\"Loaded image 2's %d features\" % num_features_2)\n\n    # Find nearest-neighbor matches using a KD tree.\n    d1_tree = cKDTree(result1['descriptors'])\n    _, indices = d1_tree.query(\n      result2['descriptors'],\n      distance_upper_bound=distance_threshold)\n\n    # Select feature locations for putative matches.\n    locations_2_to_use = np.array([\n      result2['locations'][i,]\n      for i in range(num_features_2)\n      if indices[i] != num_features_1\n    ])\n    locations_1_to_use = np.array([\n      result1['locations'][indices[i],]\n      for i in range(num_features_2)\n      if indices[i] != num_features_1\n    ])\n\n    # Perform geometric verification using RANSAC.\n    _, inliers = ransac(\n      (locations_1_to_use, locations_2_to_use),\n      AffineTransform,\n      min_samples=3,\n      residual_threshold=20,\n      max_trials=1000)\n    \n    if verbose:\n        print('Found %d inliers' % sum(inliers))\n\n    # Visualize correspondences.\n    _, ax = plt.subplots(figsize = (9, 9))\n    inlier_idxs = np.nonzero(inliers)[0]\n    plot_matches(\n      ax,\n      image1,\n      image2,\n      locations_1_to_use,\n      locations_2_to_use,\n      np.column_stack((inlier_idxs, inlier_idxs)),\n      matches_color='b')\n    ax.axis('off')\n    ax.set_title(f'DELF correspondences: Found {sum(inliers)} inliers')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-10T18:58:30.911957Z","iopub.execute_input":"2025-05-10T18:58:30.912270Z","iopub.status.idle":"2025-05-10T18:58:30.924142Z","shell.execute_reply.started":"2025-05-10T18:58:30.912235Z","shell.execute_reply":"2025-05-10T18:58:30.922887Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"delf_result1 = run_delf(image_1)\ndelf_result2 = run_delf(image_2)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-10T18:59:44.210759Z","iopub.execute_input":"2025-05-10T18:59:44.211095Z","iopub.status.idle":"2025-05-10T18:59:50.962849Z","shell.execute_reply.started":"2025-05-10T18:59:44.211033Z","shell.execute_reply":"2025-05-10T18:59:50.962118Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"match_images(image_1, image_2, delf_result1, delf_result2)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-10T19:00:00.458599Z","iopub.execute_input":"2025-05-10T19:00:00.458885Z","iopub.status.idle":"2025-05-10T19:00:01.523784Z","shell.execute_reply.started":"2025-05-10T19:00:00.458854Z","shell.execute_reply":"2025-05-10T19:00:01.522821Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"for image_index in similar_n[:6]:\n    key_image = get_image(train_embeddings['images_paths'][image_index],\n                          resize = True,\n                          target_size = DELF_IMG_SIZE)\n    try:\n        delf_key_image_result = run_delf(key_image)\n        match_images(image_1, key_image, delf_result1, delf_key_image_result, verbose = False)\n    except:\n        print(\"No inliers found\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-10T19:00:05.422267Z","iopub.execute_input":"2025-05-10T19:00:05.422569Z","iopub.status.idle":"2025-05-10T19:00:28.424660Z","shell.execute_reply.started":"2025-05-10T19:00:05.422535Z","shell.execute_reply":"2025-05-10T19:00:28.423764Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Reranking\nReranking using DELF local features descriptor","metadata":{}},{"cell_type":"code","source":"def delf_rerank(query = None, key = None, query_image_index = None, confidence_df = None, re_sort = True):\n    distance_threshold = 0.8\n    query_paths = query['images_paths']\n    key_paths = key['images_paths']\n    \n    query_image = get_image(query_paths[query_image_index],\n                            resize = True,\n                            target_size = DELF_IMG_SIZE)\n    \n    delf_result_query = run_delf(query_image)\n    \n    # Read query features\n    num_features_query = delf_result_query['locations'].shape[0]\n    \n    inliers_list = []\n    print(f\"Retrieving local features for top {len(confidence_df['image_paths'])} key images...\")\n    for image_path in tqdm(confidence_df['image_paths']):\n        key_image = get_image(image_path,\n                          resize = True,\n                          target_size = DELF_IMG_SIZE)\n        \n        delf_result_key = run_delf(key_image)\n    \n        # Read key features\n        num_features_key = delf_result_key['locations'].shape[0]\n\n        # Find nearest-neighbor matches using a KD tree.\n        d1_tree = cKDTree(delf_result_query['descriptors'])\n        _, indices = d1_tree.query(\n          delf_result_key['descriptors'],\n          distance_upper_bound=distance_threshold)\n\n        # Select feature locations for putative matches.\n        locations_k_to_use = np.array([\n          delf_result_key['locations'][i,]\n          for i in range(num_features_key)\n          if indices[i] != num_features_query\n        ])\n        locations_q_to_use = np.array([\n          delf_result_query['locations'][indices[i],]\n          for i in range(num_features_key)\n          if indices[i] != num_features_query\n        ])\n\n        # Perform geometric verification using RANSAC.\n        try:\n            _, inliers = ransac(\n              (locations_q_to_use, locations_k_to_use),\n              AffineTransform,\n              min_samples=3,\n              residual_threshold=20,\n              max_trials=1000)\n        except:\n            inliers = [0]\n        \n        # Handling 0 inliers\n        try:\n            total_inliers = sum(inliers)\n            inliers_list.append(total_inliers)\n        except:\n            inliers_list.append(1) # Appending inlier = 1 to avoid null confidence\n    \n    confidence_df['inliers'] = inliers_list\n    \n    original_confidence = confidence_df['inliers']\n    reranked_confidence = np.sqrt(original_confidence) * confidence_df['cos_similarity']\n    confidence_df['reranked_conf'] = reranked_confidence\n    \n    if re_sort:\n        confidence_df.sort_values('reranked_conf', ascending = False, inplace = True)\n    \n    return confidence_df","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-10T19:01:12.680675Z","iopub.execute_input":"2025-05-10T19:01:12.680987Z","iopub.status.idle":"2025-05-10T19:01:12.693724Z","shell.execute_reply.started":"2025-05-10T19:01:12.680957Z","shell.execute_reply":"2025-05-10T19:01:12.692814Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"reranked_df = delf_rerank(query = val_embeddings,\n                          key = train_embeddings,\n                          query_image_index = query_image_index,\n                          confidence_df = confidence_df,\n                          re_sort = True)\nreranked_df","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-10T19:01:15.951120Z","iopub.execute_input":"2025-05-10T19:01:15.951493Z","iopub.status.idle":"2025-05-10T19:01:53.063065Z","shell.execute_reply.started":"2025-05-10T19:01:15.951453Z","shell.execute_reply":"2025-05-10T19:01:53.062123Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"query_image_index = 887\ntop_n = 10\n\nquery_top(query_image_index, top_n, reranked = reranked_df)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-10T19:01:53.064614Z","iopub.execute_input":"2025-05-10T19:01:53.064919Z","iopub.status.idle":"2025-05-10T19:01:55.086325Z","shell.execute_reply.started":"2025-05-10T19:01:53.064879Z","shell.execute_reply":"2025-05-10T19:01:55.085506Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def delf_rerank(query = None, key = None, query_image_index = None, confidence_df = None, re_sort = True):\n    distance_threshold = 0.8\n    query_paths = query['images_paths']\n    key_paths = key['images_paths']\n    \n    query_image = get_image(query_paths[query_image_index],\n                            resize = True,\n                            target_size = DELF_IMG_SIZE)\n    \n    delf_result_query = run_delf(query_image)\n    \n    # Read query features\n    num_features_query = delf_result_query['locations'].shape[0]\n    \n    inliers_list = []\n    print(f\"Retrieving local features for top {len(confidence_df['image_paths'])} key images...\")\n    for image_path in tqdm(confidence_df['image_paths']):\n        key_image = get_image(image_path,\n                          resize = True,\n                          target_size = DELF_IMG_SIZE)\n        \n        delf_result_key = run_delf(key_image)\n    \n        # Read key features\n        num_features_key = delf_result_key['locations'].shape[0]\n\n        # Find nearest-neighbor matches using a KD tree.\n        d1_tree = cKDTree(delf_result_query['descriptors'])\n        _, indices = d1_tree.query(\n          delf_result_key['descriptors'],\n          distance_upper_bound=distance_threshold)\n\n        # Select feature locations for putative matches.\n        locations_k_to_use = np.array([\n          delf_result_key['locations'][i,]\n          for i in range(num_features_key)\n          if indices[i] != num_features_query\n        ])\n        locations_q_to_use = np.array([\n          delf_result_query['locations'][indices[i],]\n          for i in range(num_features_key)\n          if indices[i] != num_features_query\n        ])\n\n        # Perform geometric verification using RANSAC.\n        try:\n            _, inliers = ransac(\n              (locations_q_to_use, locations_k_to_use),\n              AffineTransform,\n              min_samples=3,\n              residual_threshold=20,\n              max_trials=1000)\n        except:\n            inliers = [0]\n        \n        # Handling 0 inliers\n        try:\n            total_inliers = sum(inliers)\n            inliers_list.append(total_inliers)\n        except:\n            inliers_list.append(1) # Appending inlier = 1 to avoid null confidence\n    \n    confidence_df['inliers'] = inliers_list\n    \n    original_confidence = confidence_df['inliers']\n    reranked_confidence = np.sqrt(original_confidence) * confidence_df['cos_similarity']\n    confidence_df['reranked_conf'] = reranked_confidence\n    \n    if re_sort:\n        confidence_df.sort_values('reranked_conf', ascending = False, inplace = True)\n    \n    return confidence_df","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-10T19:01:57.697262Z","iopub.execute_input":"2025-05-10T19:01:57.697926Z","iopub.status.idle":"2025-05-10T19:01:57.710609Z","shell.execute_reply.started":"2025-05-10T19:01:57.697882Z","shell.execute_reply":"2025-05-10T19:01:57.709419Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Reranking examples","metadata":{}},{"cell_type":"code","source":"query_image_index = 11\ntop_n = 10\n\nquery_top(query_image_index, top_n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-10T19:02:01.202090Z","iopub.execute_input":"2025-05-10T19:02:01.203029Z","iopub.status.idle":"2025-05-10T19:02:02.642337Z","shell.execute_reply.started":"2025-05-10T19:02:01.202980Z","shell.execute_reply":"2025-05-10T19:02:02.641525Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"confidence_df = confidence_top(query = val_embeddings,\n                               key = train_embeddings,\n                               similarity = val_train_similarity,\n                               query_image_index = query_image_index,\n                               top = top_n)\n\nconfidence_df","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-10T19:02:05.265601Z","iopub.execute_input":"2025-05-10T19:02:05.265873Z","iopub.status.idle":"2025-05-10T19:02:05.278409Z","shell.execute_reply.started":"2025-05-10T19:02:05.265843Z","shell.execute_reply":"2025-05-10T19:02:05.277611Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"reranked_df = delf_rerank(query = val_embeddings,\n                          key = train_embeddings,\n                          query_image_index = query_image_index,\n                          confidence_df = confidence_df,\n                          re_sort = True)\nreranked_df","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-10T19:02:07.867367Z","iopub.execute_input":"2025-05-10T19:02:07.868205Z","iopub.status.idle":"2025-05-10T19:02:43.719233Z","shell.execute_reply.started":"2025-05-10T19:02:07.868167Z","shell.execute_reply":"2025-05-10T19:02:43.718214Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"query_image_index = 11\ntop_n = 10\n\nquery_top(query_image_index, top_n, reranked = reranked_df)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-10T19:02:45.291695Z","iopub.execute_input":"2025-05-10T19:02:45.292068Z","iopub.status.idle":"2025-05-10T19:02:46.825376Z","shell.execute_reply.started":"2025-05-10T19:02:45.292001Z","shell.execute_reply":"2025-05-10T19:02:46.824255Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"query_image_index = 395\ntop_n = 10\n\nquery_top(query_image_index, top_n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-10T19:02:46.827717Z","iopub.execute_input":"2025-05-10T19:02:46.828070Z","iopub.status.idle":"2025-05-10T19:02:48.197516Z","shell.execute_reply.started":"2025-05-10T19:02:46.828008Z","shell.execute_reply":"2025-05-10T19:02:48.196566Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"confidence_df = confidence_top(query = val_embeddings,\n                               key = train_embeddings,\n                               similarity = val_train_similarity,\n                               query_image_index = query_image_index,\n                               top = top_n)\n\nconfidence_df","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-10T19:02:48.198898Z","iopub.execute_input":"2025-05-10T19:02:48.199220Z","iopub.status.idle":"2025-05-10T19:02:48.216384Z","shell.execute_reply.started":"2025-05-10T19:02:48.199178Z","shell.execute_reply":"2025-05-10T19:02:48.215501Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"reranked_df = delf_rerank(query = val_embeddings,\n                          key = train_embeddings,\n                          query_image_index = query_image_index,\n                          confidence_df = confidence_df,\n                          re_sort = True)\nreranked_df","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-10T19:02:48.217745Z","iopub.execute_input":"2025-05-10T19:02:48.218064Z","iopub.status.idle":"2025-05-10T19:03:25.284610Z","shell.execute_reply.started":"2025-05-10T19:02:48.218002Z","shell.execute_reply":"2025-05-10T19:03:25.283701Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"query_image_index = 395\ntop_n = 10\n\nquery_top(query_image_index, top_n, reranked = reranked_df)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-10T19:03:25.285946Z","iopub.execute_input":"2025-05-10T19:03:25.286263Z","iopub.status.idle":"2025-05-10T19:03:26.736327Z","shell.execute_reply.started":"2025-05-10T19:03:25.286225Z","shell.execute_reply":"2025-05-10T19:03:26.735266Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"query_image_index = 40\ntop_n = 10\n\nquery_top(query_image_index, top_n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-10T19:03:26.737645Z","iopub.execute_input":"2025-05-10T19:03:26.737969Z","iopub.status.idle":"2025-05-10T19:03:28.832734Z","shell.execute_reply.started":"2025-05-10T19:03:26.737928Z","shell.execute_reply":"2025-05-10T19:03:28.831825Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"confidence_df = confidence_top(query = val_embeddings,\n                               key = train_embeddings,\n                               similarity = val_train_similarity,\n                               query_image_index = query_image_index,\n                               top = top_n)\n\nconfidence_df","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-10T19:03:28.834098Z","iopub.execute_input":"2025-05-10T19:03:28.834426Z","iopub.status.idle":"2025-05-10T19:03:28.850859Z","shell.execute_reply.started":"2025-05-10T19:03:28.834384Z","shell.execute_reply":"2025-05-10T19:03:28.850100Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"reranked_df = delf_rerank(query = val_embeddings,\n                          key = train_embeddings,\n                          query_image_index = query_image_index,\n                          confidence_df = confidence_df,\n                          re_sort = True)\nreranked_df","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-10T19:03:28.852791Z","iopub.execute_input":"2025-05-10T19:03:28.853054Z","iopub.status.idle":"2025-05-10T19:04:05.323199Z","shell.execute_reply.started":"2025-05-10T19:03:28.853008Z","shell.execute_reply":"2025-05-10T19:04:05.322247Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"query_image_index = 40\ntop_n = 10\n\nquery_top(query_image_index, top_n, reranked = reranked_df)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-10T19:04:05.324667Z","iopub.execute_input":"2025-05-10T19:04:05.325402Z","iopub.status.idle":"2025-05-10T19:04:06.965192Z","shell.execute_reply.started":"2025-05-10T19:04:05.325350Z","shell.execute_reply":"2025-05-10T19:04:06.964288Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Under-represented query image\nEffect of querying images not well represented on the key set","metadata":{}},{"cell_type":"code","source":"query_image_index = 822\ntop_n = 10\n\nquery_top(query_image_index, top_n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-10T18:17:50.103091Z","iopub.status.idle":"2025-05-10T18:17:50.103400Z","shell.execute_reply.started":"2025-05-10T18:17:50.103241Z","shell.execute_reply":"2025-05-10T18:17:50.103257Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Let's investigate what the model has seen for landmark 36 during training...","metadata":{}},{"cell_type":"code","source":"get_landmark(36)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-10T18:17:50.104629Z","iopub.status.idle":"2025-05-10T18:17:50.104928Z","shell.execute_reply.started":"2025-05-10T18:17:50.104767Z","shell.execute_reply":"2025-05-10T18:17:50.104783Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Query image has a representativeness issue when considering how the landmark appears on key set and how it was seen during training.","metadata":{}},{"cell_type":"code","source":"def recognize_and_visualize(query_image_path, \n                            train_embeddings, \n                            model, \n                            top_n=5, \n                            use_rerank=True):\n    # Step 1: Get query embedding\n    query_embedding = get_embeddings(model=model,\n                                     image_paths=[query_image_path],\n                                     input_size=IMG_SIZE)\n\n    # Step 2: Compute cosine similarity\n    sim_matrix = get_similarities(query_embedding['embedded_images'],\n                                   train_embeddings['embedded_images'])\n\n    # Step 3: Get top-N predictions\n    confidence_df = confidence_top(query=query_embedding,\n                                   key=train_embeddings,\n                                   similarity=sim_matrix,\n                                   query_image_index=0,\n                                   top=top_n)\n\n    # Step 4: Optional DELF reranking\n    if use_rerank:\n        confidence_df = delf_rerank(query=query_embedding,\n                                    key=train_embeddings,\n                                    query_image_index=0,\n                                    confidence_df=confidence_df,\n                                    re_sort=True)\n\n    # Step 5: Predicted landmark ID\n    predicted_landmark = confidence_df['prediction'].iloc[0]\n    print(f\"\\n📍 Predicted Landmark ID: {predicted_landmark}\")\n\n    # Step 6: Show query image\n    query_image = get_image(query_image_path, resize=True, target_size=DELF_IMG_SIZE)\n    plt.figure(figsize=(4, 4))\n    plt.imshow(query_image)\n    plt.title(\"Query Image\")\n    plt.axis(\"off\")\n    plt.show()\n\n    # Step 7: Show top-N similar images\n    fig, axs = plt.subplots(1, top_n, figsize=(15, 5))\n    for i in range(top_n):\n        img = get_image(confidence_df['image_paths'].iloc[i], resize=True, target_size=DELF_IMG_SIZE)\n        axs[i].imshow(img)\n        axs[i].set_title(f\"ID: {confidence_df['prediction'].iloc[i]}\\nSim: {confidence_df['cos_similarity'].iloc[i]:.2f}\")\n        axs[i].axis('off')\n    plt.suptitle(\"Top Similar Images\")\n    plt.show()\n\n    return confidence_df\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-10T19:13:45.902399Z","iopub.execute_input":"2025-05-10T19:13:45.903268Z","iopub.status.idle":"2025-05-10T19:13:45.913583Z","shell.execute_reply.started":"2025-05-10T19:13:45.903224Z","shell.execute_reply":"2025-05-10T19:13:45.912603Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"results = recognize_and_visualize(\n    query_image_path=\"/kaggle/working/test_sub/24/51b1affeeaab36b3.jpg\",\n    train_embeddings=train_embeddings,\n    model=embedding_model,\n    top_n=5,\n    use_rerank=True\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-10T19:16:51.713222Z","iopub.execute_input":"2025-05-10T19:16:51.713534Z","iopub.status.idle":"2025-05-10T19:17:12.865656Z","shell.execute_reply.started":"2025-05-10T19:16:51.713499Z","shell.execute_reply":"2025-05-10T19:17:12.864806Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"embedding_model.save(\"efficientnet_embedding_model.h5\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-10T19:09:38.397792Z","iopub.execute_input":"2025-05-10T19:09:38.398162Z","iopub.status.idle":"2025-05-10T19:09:38.767814Z","shell.execute_reply.started":"2025-05-10T19:09:38.398124Z","shell.execute_reply":"2025-05-10T19:09:38.767107Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from tensorflow.keras.models import load_model\nembedding_model = load_model(\"efficientnet_embedding_model.h5\", compile=False)","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}