{"cells":[{"metadata":{},"cell_type":"markdown","source":"I've been taking part in the [Google Landmark Retrieval 2020](https://www.kaggle.com/c/landmark-retrieval-2020) competition recently which has 1.5 million images in its training dataset. To have any chance of training a good model on even a fraction of these images inside a Kaggle notebook I found that I needed a TPU. As such I have had a look around other notebooks and worked out how to convert images into tfrecords. In this format the images enable TPU training at much faster speeds.\n\nNote that I am using triplet loss in this competition to train a model. As such I have arranged the tfrecords to contain triplets of images. However if you want to create a tfrecord dataset without triplets you can still fork this notebook. I have highlighted at the appropriate code block what you would need to change.\n\nIf you'd like to see how to use this dataset to train a model, you can see how I've used it in this [notebook](https://www.kaggle.com/mattbast/google-landmark-retrieval-triplet-loss).","execution_count":null},{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"code","source":"import numpy as np \nimport pandas as pd\nimport tensorflow as tf\nfrom tqdm.notebook import tqdm","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Load dataset\n\nFirst thing to do is to arrange the dataset into triplets. I'll start with the table of meta information about the images. It includes the image ids (which also includes the directory location of the image) and the id of the landmark in the image.","execution_count":null},{"metadata":{"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","trusted":true},"cell_type":"code","source":"all_images_meta = pd.read_csv('../input/landmark-retrieval-2020/train.csv')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"While I would like to convert all the images into tfrecords I am limited in that Kaggle notebooks only allow for 4.9GB of output (the landmarks dataset before it is converted to tfrecords is around 100GB large). I thus need to take a representative sample of the data. I decided that it would be better to get at least one image of every landmark in the dataset. This would help the model generalise better to various styles of architecture. \n\nA quick way to do this is to group by landmark, get the first two images per group and then reset the index to get the table back to the right shape.","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"images_meta_sample = all_images_meta.groupby('landmark_id').head(2).reset_index(drop=True)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"images_meta_sample.head(10)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"images_meta_sample.info()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Form triplets\n\nFor those new to triplet loss, here's a short description of what it means. Models will use triplet loss when they are trying to produce an embedding for an image. An embedding is an array of numbers that represents what is included in the image. Think of it like a textual description such as \"this image includes landmark x on a sunny day\" except this text is in a number format that the machine understands. These embeddings can then be compared using a clustering algorithm when a machine is trying to work out if two images contain the same monument.\n\nTriplet loss enables this by looking at images in threes. One image is an anchor and can include any landmark in it. The next is the positive image. This image must contain the same landmark as the anchor. Finally there is the negative image that contains a different landmark. The purpose then is to minimise the difference between the anchor and positive embeddings while maximising the distance between the anchor and the negative embeddings.\n\nThe dataset then needs to form these sets of three. Let's begin by organising the images into the groups by their landmark ids.","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"landmark_groups = all_images_meta.groupby('landmark_id')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"And have a look at one of those groups.","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"landmark_group = landmark_groups.get_group(1)\nlandmark_group","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Then get a random positive image. This function randomly picks an image from the group of images that contain the same landmark as the anchor. This may pick the same image as the anchor. However it saves some time as this is a fast way to perform the operation on a large dataset.","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"def get_positive(anchor_landmark_id):    \n    landmark_group = landmark_groups.get_group(anchor_landmark_id)\n    indexes = landmark_group.index.values\n    \n    rand_index = np.random.choice(indexes)\n    pos_img_id = landmark_group.loc[rand_index].id\n        \n    return pos_img_id","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"images_meta_sample['positive_id'] = images_meta_sample['landmark_id'].apply(get_positive)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Then get a negative image. This time get a list of all the image indexes and randomly pick one. If this image does not contain the same landmark as the anchor use it as the negative. Otherwise repeat this process until an image not in the same landmark group is found.","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"def get_negative(landmark_id):        \n    indexes = images_meta_sample.index.values\n    \n    for i in range(len(images_meta_sample)):\n        rand_index = np.random.choice(indexes)\n        \n        neg_img_id = images_meta_sample.loc[rand_index].id\n        neg_landmark_id = images_meta_sample.loc[rand_index].landmark_id\n        \n        if neg_landmark_id != landmark_id:\n            return neg_img_id\n    \n    return neg_img_id","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"images_meta_sample['negative_id'] = images_meta_sample['landmark_id'].apply(get_negative)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"We now have a list of anchor images with a positive and negative image next to each one. I've also renamed the id column to anchor_id to remind me that this column contains the id of the anchor image.","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"images_meta_sample = images_meta_sample.rename({'id': 'anchor_id'}, axis='columns')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"images_meta_sample.head()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Write to tfrecords\n\nWith the dataset organised it's time to start writing the images to tfrecords. I wanted to quickly explain my understanding of what a tfrecord is before getting into how to create them as it took me a while to work it out.\n\nA tfrecord is a file that contains many examples of a dataset. So for most image related tasks an example would contain one image and probably some sort of label. In this task I have included three images (a triplet) in each example. Each image is also formatted as a byte string rather than an image or numpy array.\n\n![Screenshot%202020-08-05%20at%2012.39.06.png](attachment:Screenshot%202020-08-05%20at%2012.39.06.png)\n\nFormatting the data in this way helps tensorflow to distribute and cache the data. It also reduces the time it takes to transfer images to the google cloud bucket sitting next to a TPU. This is because we are transferring many examples bundled into one file rather than one image example at a time.\n\nSo to start I'll define a function to convert an image into a byte string.","attachments":{"Screenshot%202020-08-05%20at%2012.39.06.png":{"image/png":"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"}},"execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"def _bytes_feature(value):\n    if isinstance(value, type(tf.constant(0))):\n        value = value.numpy() \n    return tf.train.Feature(bytes_list=tf.train.BytesList(value=[value]))","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"and a function that loads an image from the dataset and encodes it ready for the bytes feature function. Note that the dataset is split into many directories and sub directories. The first three characters of an images id specify the directory path that the image lives in.\n\nIt is also here that I can reduce the size of the tfrecord files. As mentioned before, Kaggle notebooks only allow 4.9GBs of data to be outputted so the images sample needs to be reduced here so to fit as many images as possible into the tfrecord files. I've opted for a size of 128,128 using the nearest neighbour method of resizing.","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"def get_image(img_id):\n    chars = tf.strings.bytes_split(img_id)\n    dir_1, dir_2, dir_3 = chars[0], chars[1], chars[2]\n    \n    path = '../input/landmark-retrieval-2020/train/' + dir_1 + '/' + dir_2 + '/' + dir_3 + '/' + img_id + '.jpg'\n    image = tf.io.read_file(path)\n    \n    image = tf.image.decode_jpeg(image)\n    image = tf.image.resize(image, size=(128, 128), method='nearest')\n    image = tf.image.convert_image_dtype(image, tf.uint8)\n    \n    image = tf.image.encode_jpeg(image, quality=94, optimize_size=True)\n    \n    return image","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Finally group these two helped functions together to form one example containing a triplet of images.","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"def serialize_example(example):    \n    anchor_img = get_image(example.anchor_id)\n    positive_img = get_image(example.positive_id)\n    negative_img = get_image(example.negative_id)\n    \n    feature = {\n        'anchor_img': _bytes_feature(anchor_img),\n        'positive_img': _bytes_feature(positive_img),\n        'negative_img': _bytes_feature(negative_img),\n    }\n    \n    example_proto = tf.train.Example(features=tf.train.Features(feature=feature))\n    return example_proto.SerializeToString()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Now get a list of the example indexes (remember that each example contains three images).","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"image_indexes = images_meta_sample.index.values","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"And define the size and number of tfrecord files to create. I've heard 15 is a good number to aim for though honestly I'm still working out the best way to decide on the number of files to create. For now them I'll split the examples into 15 files.","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"file_size = len(image_indexes) // 15\nfile_count = len(image_indexes) // file_size + int(len(image_indexes) % file_size != 0)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Here's another helper function that takes a subset of the examples indexes, loads the examples and writes them to a file.","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"def write_tfrecord_file(file_index, file_size, image_indexes):\n    with tf.io.TFRecordWriter('train%.2i.tfrec'%(file_index)) as writer:\n        start = file_size * file_index\n        end = file_size * (file_index + 1)\n        \n        for i in tqdm(image_indexes[start:end]):\n\n            example = serialize_example(\n                images_meta_sample.loc[i]\n            )\n            \n            writer.write(example)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Finally bring everything together. Loop through the subsets of indexes and write a file for each subset.","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"for file_index in range(file_count):\n    print('Writing TFRecord %i of %i...'%(file_index, file_count))\n    write_tfrecord_file(file_index, file_size, image_indexes)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Conclusion\n\nTo use these tfrecrds you will need to turn the output of this notebook into a public dataset. Simply commit the notebook, scroll to the bottom of the published notebook and click create dataset next to the notebook output. Once that is done you can do something like [this](https://www.kaggle.com/mattbast/google-landmark-retrieval-triplet-loss) to use your shiny new tfrecords.\n\nI think it is worth noting here that this is not the perfect solution to the super large dataset that comes with this challenge. If you take one image per unique landmark id you'll be left with 81k images out of the the total 1.5 million. That's a very small sample compared to the total size. This will certainly help you quickly train a model with many images but keep in mind the limitations of the size of this notebooks output vs the total size of the raw data.","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"","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}