{"cells":[{"metadata":{},"cell_type":"markdown","source":"Like me, a lot of people might not be having the required computational resources to participate in this comepetition.\n\n\nWhy? Because there are a lot of images, about 1.5M and the datasize is huge too approximating at 100GBs.\n\n\nBut for each landmark, there are a lot of images that can be considered augmentations of the landmark. These we would be anyway generating while training, so its okay if we do not include some of these images in our training set.\n\n\nHere's a way to get the most 'unrelated' images so we can train our model with the most diverse images.\n\n\n------\n[GitHub](http://https://github.com/JohannesBuchner/imagehash)\n\n\nImage hashes tell whether two images look nearly identical. This is different from cryptographic hashing algorithms (like MD5, SHA-1) where tiny changes in the image give completely different hashes. In image fingerprinting, we actually want our similar inputs to have similar output hashes as well.","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"!pip install imagehash","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# You can modify these to suit your needs and resources.\n\nNUM_SAMPLES_TO_COMPARE = 1000  # we random sample these many images to choose from.\nNUM_SAMPLES_SAVE_PER_LANDMARK = 25  # we finally save maximum these many images per landmark","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"_kg_hide-output":false,"_kg_hide-input":true},"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nimport cv2\nimport random\nimport imageio\nimport imagehash\nfrom PIL import Image\nimport seaborn as sns\nfrom tqdm.auto import tqdm\nimport matplotlib.pyplot as plt","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"TRAIN_PATH = \"../input/landmark-recognition-2020/train\"\n\ndef id2filename(imageid):\n    sid = str(imageid)\n    return f\"{TRAIN_PATH}/{sid[0]}/{sid[1]}/{sid[2]}/{sid}.jpg\"\n\n\ndef plot_matches(match_dict, idx=-1):\n    pair = match_dict[idx]\n    id1, id2 = pair[0].split('_')\n    img1 = imageio.imread(id2filename(id1))\n    img2 = imageio.imread(id2filename(id2))\n    \n    plt.figure()\n    plt.suptitle(f\"#hash diff {pair[1]}\")\n    plt.subplot(121);plt.imshow(img1)\n    plt.subplot(122);plt.imshow(img2)\n    \n    \ndef get_matches(landmark_id):\n    mask = df['landmark_id'] == landmark_id\n    image_names = df[mask]['id'].values.tolist()\n    num_samples = min(len(image_names), NUM_SAMPLES_TO_COMPARE)\n    image_names = random.sample(image_names, num_samples)\n    img_hash = []\n    for name in image_names:\n        img_hash.append(imagehash.dhash(Image.open(id2filename(name))))\n        \n    match_pct = {}\n    for i in range(len(img_hash)):\n        base_name = image_names[i]\n        for j in range(i+1, len(img_hash)):\n            curr_name = image_names[j]\n            match_pct[f\"{base_name}_{curr_name}\"] = img_hash[i] - img_hash[j]\n    \n    match_pct = sorted(match_pct.items(), key=lambda item: item[1])\n    return match_pct","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","trusted":true},"cell_type":"code","source":"df = pd.read_csv('../input/landmark-recognition-2020/train.csv')\ndf","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"A visualization of how the similar or dissimilar the images look according to the image hashes","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"%%time\n\nfor landmark_id in [20409, 83144, 138982]:\n    print(landmark_id)\n    match_pct = get_matches(landmark_id)\n    plt.figure(); plot_matches(match_pct, idx=0)\n    plt.figure(); plot_matches(match_pct, idx=-1)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Let's get the sorted count of images per landmarks.","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"count_df = df.groupby('landmark_id').count().reset_index().rename(columns={'id': 'count'})\ncount_df = count_df.sort_values(by=['count'], ascending=False)\ncount_df","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"It's best to get an idea of the distribution of the counts before we begin","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"count_df['count'].hist(bins=100, figsize=(10, 4))","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"I can't see anything, can you?\nSeems most of the images are less than 500 in number. \n\nLet's skip the first 100 (the dataframe is sorted) and see the distribution.","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"count_df['count'][100:].hist(bins=100, figsize=(10, 4))","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Great! Now we can process those landmarks that have more than 25 image samples and choose the most dissimilar of them as our training set.\n\nFor landmarks that have less than 25, we can keep them as such and maybe random sample them at training time.\n\nWe'll use multiprocessing to make the best use of resources given to us.","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"count_mask = count_df['count'] >= NUM_SAMPLES_SAVE_PER_LANDMARK\ncount_mask.sum(), (~count_mask).sum()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"from multiprocessing import Pool\n\ndef f(landmark_id):\n    match_pct = get_matches(landmark_id)\n    \n    filenames = set()\n    for idx in range(len(match_pct)):\n        pair = match_pct[-idx]  # sorted in ascending order of hamming distance\n        id1, id2 = pair[0].split('_')\n        filenames.add((id1, landmark_id))\n        filenames.add((id2, landmark_id))\n        \n        # ensuring an upper cap to the number\n        if len(filenames) >= NUM_SAMPLES_SAVE_PER_LANDMARK:\n            break\n    return filenames\n    \n\nif __name__ == '__main__':\n    with Pool(5) as p:\n        values = count_df[count_mask]['landmark_id']\n        l = list(tqdm(p.imap(f, values), total=len(values)))\n    \nsamples_df = []\nfor filenames in l:\n    samples_df += list(filenames)\nsamples_df = pd.DataFrame(samples_df, columns=['id', 'landmark_id'])","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Get those landmark_ids which we didn't process, so we can add them as it is to the final dataframe","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"unsampled_landmarks = count_df[(~count_mask)]['landmark_id']  \nunsamled_mask = df['landmark_id'].isin(unsampled_landmarks)\nunsampled_landmarks_df = df[unsamled_mask]\nunsampled_landmarks_df","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Combine them!","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"final_df = pd.concat([samples_df, unsampled_landmarks_df])\nfinal_df.to_csv(f'train_reduced_to_{NUM_SAMPLES_SAVE_PER_LANDMARK}_samples.csv')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Image count distribution of the final dataframe","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"final_count_df = final_df.groupby('landmark_id').count().reset_index().rename(columns={'id': 'count'})\nfinal_count_df = final_count_df.sort_values(by=['count'], ascending=False)\nfinal_count_df['count'].hist();","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"final_df['landmark_id'].unique().shape == df['landmark_id'].unique().shape","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"final_df.shape, df.shape","execution_count":null,"outputs":[]}],"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat":4,"nbformat_minor":4}