{"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_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Landmark recognition with few-shot learning + ResNet50 + Decision tree\n## Loading essential packages","metadata":{}},{"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 matplotlib.pyplot as plt\nimport cv2\nimport tensorflow as tf\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"execution":{"iopub.status.busy":"2021-09-21T22:29:26.042493Z","iopub.execute_input":"2021-09-21T22:29:26.042864Z","iopub.status.idle":"2021-09-21T22:29:26.048001Z","shell.execute_reply.started":"2021-09-21T22:29:26.042817Z","shell.execute_reply":"2021-09-21T22:29:26.047234Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Loading data","metadata":{}},{"cell_type":"code","source":"path = '/kaggle/input/landmark-recognition-2021/'\nos.listdir(path)","metadata":{"execution":{"iopub.status.busy":"2021-09-21T22:29:26.050459Z","iopub.execute_input":"2021-09-21T22:29:26.050896Z","iopub.status.idle":"2021-09-21T22:29:26.068610Z","shell.execute_reply.started":"2021-09-21T22:29:26.050860Z","shell.execute_reply":"2021-09-21T22:29:26.067317Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_data = pd.read_csv(path+'train.csv')\ntrain_data.head()","metadata":{"execution":{"iopub.status.busy":"2021-09-21T22:29:26.071729Z","iopub.execute_input":"2021-09-21T22:29:26.071914Z","iopub.status.idle":"2021-09-21T22:29:26.892060Z","shell.execute_reply.started":"2021-09-21T22:29:26.071893Z","shell.execute_reply":"2021-09-21T22:29:26.891096Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Looking at the number of unique landmarks","metadata":{}},{"cell_type":"code","source":"len(train_data['landmark_id'].unique())","metadata":{"execution":{"iopub.status.busy":"2021-09-21T22:29:26.893503Z","iopub.execute_input":"2021-09-21T22:29:26.893952Z","iopub.status.idle":"2021-09-21T22:29:26.914699Z","shell.execute_reply.started":"2021-09-21T22:29:26.893910Z","shell.execute_reply":"2021-09-21T22:29:26.913902Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Props to https://www.kaggle.com/michaelscheinfeilda/landmark-recognition-2021-starter for the plot function","metadata":{}},{"cell_type":"code","source":"def plot_examples(landmark_id=1):\n    \"\"\" Plot 5 examples of images with the same landmark_id \"\"\"\n    \n    fig, axs = plt.subplots(1, 5, figsize=(25, 12))\n    fig.subplots_adjust(hspace = .2, wspace=.2)\n    axs = axs.ravel()\n    for i in range(5):\n        idx = train_data[train_data['landmark_id']==landmark_id].index[i]\n        image_id = train_data.loc[idx, 'id']\n        file = image_id+'.jpg'\n        subpath = '/'.join([char for char in image_id[0:3]])\n        img = cv2.imread(path+'train/'+subpath+'/'+file)\n        axs[i].imshow(img)\n        axs[i].set_title('landmark_id: '+str(landmark_id))\n        axs[i].set_xticklabels([])\n        axs[i].set_yticklabels([])\n        print(path+'train/'+subpath+'/'+file)","metadata":{"execution":{"iopub.status.busy":"2021-09-21T22:29:26.916787Z","iopub.execute_input":"2021-09-21T22:29:26.917056Z","iopub.status.idle":"2021-09-21T22:29:26.924381Z","shell.execute_reply.started":"2021-09-21T22:29:26.917022Z","shell.execute_reply":"2021-09-21T22:29:26.923599Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"plot_examples(landmark_id=7)","metadata":{"execution":{"iopub.status.busy":"2021-09-21T22:29:26.925651Z","iopub.execute_input":"2021-09-21T22:29:26.926088Z","iopub.status.idle":"2021-09-21T22:29:27.907064Z","shell.execute_reply.started":"2021-09-21T22:29:26.926052Z","shell.execute_reply":"2021-09-21T22:29:27.906451Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Loading necessary modules","metadata":{}},{"cell_type":"code","source":"from tensorflow.keras.applications.resnet50 import ResNet50\nfrom tensorflow.keras.models import Model\nfrom tensorflow.keras.preprocessing import image\nfrom tensorflow.keras.applications.resnet50 import preprocess_input, decode_predictions\nfrom tensorflow.keras.layers import Flatten, Input\nfrom tensorflow.keras.preprocessing import image","metadata":{"execution":{"iopub.status.busy":"2021-09-21T22:29:27.908131Z","iopub.execute_input":"2021-09-21T22:29:27.908732Z","iopub.status.idle":"2021-09-21T22:29:27.913829Z","shell.execute_reply.started":"2021-09-21T22:29:27.908689Z","shell.execute_reply":"2021-09-21T22:29:27.913224Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Loading ResNet50 model\n\nSelecting 100x100x3 as the dimension for the images to be used","metadata":{}},{"cell_type":"code","source":"new_path = '../input/resnet50' \nos.listdir(new_path)","metadata":{"execution":{"iopub.status.busy":"2021-09-21T22:29:27.914785Z","iopub.execute_input":"2021-09-21T22:29:27.915276Z","iopub.status.idle":"2021-09-21T22:29:27.928368Z","shell.execute_reply.started":"2021-09-21T22:29:27.915215Z","shell.execute_reply":"2021-09-21T22:29:27.927488Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"base_model = ResNet50(weights='../input/resnet50/resnet50_weights_tf_dim_ordering_tf_kernels_notop.h5', pooling=max, include_top = False)\ninput = Input(shape=(100,100,3),name = 'image_input')\nx = base_model(input)\nx = Flatten()(x)\nmodel = Model(inputs=input, outputs=x)","metadata":{"execution":{"iopub.status.busy":"2021-09-21T22:29:27.929695Z","iopub.execute_input":"2021-09-21T22:29:27.930619Z","iopub.status.idle":"2021-09-21T22:29:32.053931Z","shell.execute_reply.started":"2021-09-21T22:29:27.930579Z","shell.execute_reply":"2021-09-21T22:29:32.053168Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Example of extracting ResNet50 features from a sample image","metadata":{}},{"cell_type":"code","source":"img_path = '/kaggle/input/landmark-recognition-2021/train/5/9/7/597353dfbb3df649.jpg'\nimg = image.load_img(img_path, target_size=(100, 100))\nx = image.img_to_array(img)\nx = np.expand_dims(x, axis=0)\nx = preprocess_input(x)\n\nfeatures = model.predict(x)\nfeatures_reduce =  features.squeeze()\nprint(features_reduce.size)","metadata":{"execution":{"iopub.status.busy":"2021-09-21T22:29:32.056090Z","iopub.execute_input":"2021-09-21T22:29:32.056543Z","iopub.status.idle":"2021-09-21T22:29:32.826153Z","shell.execute_reply.started":"2021-09-21T22:29:32.056505Z","shell.execute_reply":"2021-09-21T22:29:32.825370Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Function to obtain ResNet50 image features for each landmark label\n\nTake average feature value of first 5 (if present) images","metadata":{}},{"cell_type":"code","source":"def obtain_features(landmark_id=1):\n    \"\"\" Obtain features of 5 examples of images with the same landmark_id \"\"\"\n    \n    feature_vals = []\n    for i in range(5):\n        try:\n            idx = train_data[train_data['landmark_id']==landmark_id].index[i]\n            image_id = train_data.loc[idx, 'id']\n            file = image_id+'.jpg'\n            subpath = '/'.join([char for char in image_id[0:3]])\n            img = cv2.imread(path+'train/'+subpath+'/'+file)\n            #print(path+'train/'+subpath+'/'+file)\n            img_path = path+'train/'+subpath+'/'+file\n            img = image.load_img(img_path, target_size=(100, 100))\n            x = image.img_to_array(img)\n            x = np.expand_dims(x, axis=0)\n            x = preprocess_input(x)\n            features = model.predict(x)\n            features_reduce =  features.squeeze()\n            feature_vals.append(features_reduce)\n        except:\n            pass\n    try:\n        return sum(feature_vals)/len(feature_vals)\n    except:\n        return None\n    ","metadata":{"execution":{"iopub.status.busy":"2021-09-21T22:29:32.829649Z","iopub.execute_input":"2021-09-21T22:29:32.830268Z","iopub.status.idle":"2021-09-21T22:29:32.840355Z","shell.execute_reply.started":"2021-09-21T22:29:32.830227Z","shell.execute_reply":"2021-09-21T22:29:32.839139Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"feature_vals = obtain_features(landmark_id=1)\nprint(feature_vals)","metadata":{"execution":{"iopub.status.busy":"2021-09-21T22:29:32.841734Z","iopub.execute_input":"2021-09-21T22:29:32.842174Z","iopub.status.idle":"2021-09-21T22:29:33.080011Z","shell.execute_reply.started":"2021-09-21T22:29:32.842135Z","shell.execute_reply":"2021-09-21T22:29:33.079307Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Seeing the labels of the first N landmarks","metadata":{}},{"cell_type":"code","source":"no_of_landmarks = 100\nlandmark_ids = train_data['landmark_id'].unique()\nprint(landmark_ids[:no_of_landmarks])","metadata":{"execution":{"iopub.status.busy":"2021-09-21T22:29:33.081191Z","iopub.execute_input":"2021-09-21T22:29:33.081458Z","iopub.status.idle":"2021-09-21T22:29:33.101061Z","shell.execute_reply.started":"2021-09-21T22:29:33.081421Z","shell.execute_reply":"2021-09-21T22:29:33.100277Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Obtaining features for the first N landmarks","metadata":{}},{"cell_type":"code","source":"features_landmarks = []\nfor i in range(no_of_landmarks):\n    if i%10 == 0:\n        print(i, end=',')\n    landmark = landmark_ids[i]\n    features_landmarks.append(obtain_features(landmark_id=landmark))\n    ","metadata":{"execution":{"iopub.status.busy":"2021-09-21T22:29:33.102535Z","iopub.execute_input":"2021-09-21T22:29:33.102799Z","iopub.status.idle":"2021-09-21T22:29:58.720086Z","shell.execute_reply.started":"2021-09-21T22:29:33.102764Z","shell.execute_reply":"2021-09-21T22:29:58.719336Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"print(features_landmarks[0].shape)","metadata":{"execution":{"iopub.status.busy":"2021-09-21T22:29:58.721486Z","iopub.execute_input":"2021-09-21T22:29:58.721764Z","iopub.status.idle":"2021-09-21T22:29:58.728472Z","shell.execute_reply.started":"2021-09-21T22:29:58.721729Z","shell.execute_reply":"2021-09-21T22:29:58.727368Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"features_landmarks = np.array(features_landmarks)\nprint(features_landmarks.shape)","metadata":{"execution":{"iopub.status.busy":"2021-09-21T22:29:58.730066Z","iopub.execute_input":"2021-09-21T22:29:58.730386Z","iopub.status.idle":"2021-09-21T22:29:58.741770Z","shell.execute_reply.started":"2021-09-21T22:29:58.730350Z","shell.execute_reply":"2021-09-21T22:29:58.740762Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Autoencoder to compress feature vector","metadata":{}},{"cell_type":"code","source":"from tensorflow.keras import Input, Model, layers\n\nlatent_dim = 10\n\nencoder_inputs = Input(shape=(32768,))\nx = layers.Dense(4000, activation=\"relu\")(encoder_inputs)\nx = layers.Dense(100, activation=\"relu\")(x)\nz = layers.Dense(latent_dim, name=\"z_mean\")(x)\nencoder = Model(encoder_inputs, z, name=\"encoder\")\nencoder.summary()","metadata":{"execution":{"iopub.status.busy":"2021-09-21T22:29:58.743474Z","iopub.execute_input":"2021-09-21T22:29:58.743823Z","iopub.status.idle":"2021-09-21T22:29:58.783547Z","shell.execute_reply.started":"2021-09-21T22:29:58.743790Z","shell.execute_reply":"2021-09-21T22:29:58.782671Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"latent_inputs = Input(shape=(latent_dim,))\nx = layers.Dense(100, activation=\"relu\")(latent_inputs)\nx = layers.Dense(400, activation=\"relu\")(x)\ndecoder_outputs = layers.Dense(32768, activation=\"relu\")(x)\ndecoder = Model(latent_inputs, decoder_outputs, name=\"decoder\")\ndecoder.summary()","metadata":{"execution":{"iopub.status.busy":"2021-09-21T22:29:58.784997Z","iopub.execute_input":"2021-09-21T22:29:58.785294Z","iopub.status.idle":"2021-09-21T22:29:58.819552Z","shell.execute_reply.started":"2021-09-21T22:29:58.785239Z","shell.execute_reply":"2021-09-21T22:29:58.818747Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"latent_dim = 10\n\nclass Autoencoder(Model):\n  def __init__(self, latent_dim):\n    super(Autoencoder, self).__init__()\n    self.latent_dim = latent_dim   \n    self.encoder = encoder\n    self.decoder = decoder\n\n  def call(self, x):\n    encoded = self.encoder(x)\n    decoded = self.decoder(encoded)\n    return decoded\n\nautoencoder = Autoencoder(latent_dim)","metadata":{"execution":{"iopub.status.busy":"2021-09-21T22:29:58.822335Z","iopub.execute_input":"2021-09-21T22:29:58.822534Z","iopub.status.idle":"2021-09-21T22:29:58.834524Z","shell.execute_reply.started":"2021-09-21T22:29:58.822511Z","shell.execute_reply":"2021-09-21T22:29:58.833649Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from tensorflow.keras import metrics, losses\n\nautoencoder.compile(optimizer='adam', loss=losses.MeanSquaredError())","metadata":{"execution":{"iopub.status.busy":"2021-09-21T22:29:58.837368Z","iopub.execute_input":"2021-09-21T22:29:58.837765Z","iopub.status.idle":"2021-09-21T22:29:58.853816Z","shell.execute_reply.started":"2021-09-21T22:29:58.837727Z","shell.execute_reply":"2021-09-21T22:29:58.852973Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"autoencoder.fit(features_landmarks, features_landmarks,\n                epochs=30,\n                shuffle=True)","metadata":{"execution":{"iopub.status.busy":"2021-09-21T22:29:58.855111Z","iopub.execute_input":"2021-09-21T22:29:58.855388Z","iopub.status.idle":"2021-09-21T22:30:01.506109Z","shell.execute_reply.started":"2021-09-21T22:29:58.855354Z","shell.execute_reply":"2021-09-21T22:30:01.505409Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"'''test_landmark = 36\ntest_index = 2\nidx = train_data[train_data['landmark_id']==test_landmark].index[test_index]\nimage_id = train_data.loc[idx, 'id']\nfile = image_id+'.jpg'\nsubpath = '/'.join([char for char in image_id[0:3]])\nimg = cv2.imread(path+'train/'+subpath+'/'+file)\n#print(path+'train/'+subpath+'/'+file)\nimg_path = path+'train/'+subpath+'/'+file\nimg = image.load_img(img_path, target_size=(100, 100))\nx = image.img_to_array(img)\nx = np.expand_dims(x, axis=0)\nx = preprocess_input(x)\nfeatures = model.predict(x)\nfeatures_reduce =  np.array(features.squeeze())\nfeatures_reduce =  np.reshape(features_reduce, (1,32768))\nencoded_img = autoencoder.encoder(features_reduce).numpy()\nprint(encoded_img)'''","metadata":{"execution":{"iopub.status.busy":"2021-09-21T22:30:01.507311Z","iopub.execute_input":"2021-09-21T22:30:01.507594Z","iopub.status.idle":"2021-09-21T22:30:01.515267Z","shell.execute_reply.started":"2021-09-21T22:30:01.507558Z","shell.execute_reply":"2021-09-21T22:30:01.514351Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"encoded_features = []\ni = 0\nfor elem in features_landmarks:\n    if i % 100 == 0:\n        print(i, end=',')\n    elem =  np.reshape(elem, (1,32768))\n    encoded_val = autoencoder.encoder(elem).numpy()\n    encoded_features.append(encoded_val[0])\n    i += 1","metadata":{"execution":{"iopub.status.busy":"2021-09-21T22:30:01.516992Z","iopub.execute_input":"2021-09-21T22:30:01.517294Z","iopub.status.idle":"2021-09-21T22:30:01.705512Z","shell.execute_reply.started":"2021-09-21T22:30:01.517260Z","shell.execute_reply":"2021-09-21T22:30:01.704844Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Fitting a decision tree on the landmark labels and corresponding average features","metadata":{}},{"cell_type":"code","source":"from sklearn.tree import DecisionTreeClassifier\n\ndecision_tree = DecisionTreeClassifier(random_state=1)\n\n# Fit the model\ndecision_tree.fit(encoded_features, landmark_ids[:no_of_landmarks])","metadata":{"execution":{"iopub.status.busy":"2021-09-21T22:30:01.706712Z","iopub.execute_input":"2021-09-21T22:30:01.706984Z","iopub.status.idle":"2021-09-21T22:30:02.651012Z","shell.execute_reply.started":"2021-09-21T22:30:01.706951Z","shell.execute_reply":"2021-09-21T22:30:02.649629Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Code to check prediction for individual predicted labels\n\n### Check within training data (sanity check)","metadata":{}},{"cell_type":"code","source":"'''test_landmark = 36\ntest_index = 2\nidx = train_data[train_data['landmark_id']==test_landmark].index[test_index]\nimage_id = train_data.loc[idx, 'id']\nfile = image_id+'.jpg'\nsubpath = '/'.join([char for char in image_id[0:3]])\nimg = cv2.imread(path+'train/'+subpath+'/'+file)\n#print(path+'train/'+subpath+'/'+file)\nimg_path = path+'train/'+subpath+'/'+file\nimg = image.load_img(img_path, target_size=(100, 100))\nx = image.img_to_array(img)\nx = np.expand_dims(x, axis=0)\nx = preprocess_input(x)\nfeatures = model.predict(x)\nfeatures_reduce =  features.squeeze()\nfeatures_reduce =  np.reshape(features_reduce, (1,32768))\nencoded_img = autoencoder.encoder(features_reduce).numpy()\npredictions = decision_tree.predict([encoded_img[0]])\nprint(predictions)'''","metadata":{"execution":{"iopub.status.busy":"2021-09-21T22:30:02.652527Z","iopub.execute_input":"2021-09-21T22:30:02.652928Z","iopub.status.idle":"2021-09-21T22:30:02.660935Z","shell.execute_reply.started":"2021-09-21T22:30:02.652890Z","shell.execute_reply":"2021-09-21T22:30:02.660091Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Check within test data","metadata":{}},{"cell_type":"code","source":"'''test_img = '../input/landmark-recognition-2021/test/9/0/0/900bb54db718a992.jpg'\nimg = image.load_img(img_path, target_size=(100, 100))\nx = image.img_to_array(img)\nx = np.expand_dims(x, axis=0)\nx = preprocess_input(x)\n\nfeatures = model.predict(x)\nfeatures_reduce =  features.squeeze()\n\nfeatures_reduce =  np.reshape(features_reduce, (1,32768))\nencoded_img = autoencoder.encoder(features_reduce).numpy()\npredictions = decision_tree.predict([encoded_img[0]])\nprint(predictions)'''","metadata":{"execution":{"iopub.status.busy":"2021-09-21T22:30:02.662631Z","iopub.execute_input":"2021-09-21T22:30:02.662939Z","iopub.status.idle":"2021-09-21T22:30:02.669316Z","shell.execute_reply.started":"2021-09-21T22:30:02.662904Z","shell.execute_reply":"2021-09-21T22:30:02.668421Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Load test data in submission format","metadata":{}},{"cell_type":"code","source":"test_data = pd.read_csv(path+'sample_submission.csv')\ntest_data.head()","metadata":{"execution":{"iopub.status.busy":"2021-09-21T22:30:02.670889Z","iopub.execute_input":"2021-09-21T22:30:02.671191Z","iopub.status.idle":"2021-09-21T22:30:02.700088Z","shell.execute_reply.started":"2021-09-21T22:30:02.671145Z","shell.execute_reply":"2021-09-21T22:30:02.699345Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Obtain predictions of test data","metadata":{}},{"cell_type":"code","source":"    predictions = []\n    for i in range(len(test_data)):\n        try:\n            if i%100==0:\n                print(i, end=',')\n            image_id = test_data.loc[i, 'id']\n            file = image_id+'.jpg'\n            subpath = '/'.join([char for char in image_id[0:3]])\n            img = cv2.imread(path+'test/'+subpath+'/'+file)\n            #print(path+'test/'+subpath+'/'+file)\n            img_path = path+'test/'+subpath+'/'+file\n            img = image.load_img(img_path, target_size=(100, 100))\n            x = image.img_to_array(img)\n            x = np.expand_dims(x, axis=0)\n            x = preprocess_input(x)\n            features = model.predict(x)\n            features_reduce =  features.squeeze()\n            features_reduce =  np.reshape(features_reduce, (1,32768))\n            encoded_img = autoencoder.encoder(features_reduce).numpy()\n        except:\n            pass\n        pred = decision_tree.predict([encoded_img[0]])\n        #print(prediction)\n        predictions.append(str(pred[0]) + ' 1.00000000')","metadata":{"execution":{"iopub.status.busy":"2021-09-21T22:30:02.701403Z","iopub.execute_input":"2021-09-21T22:30:02.701660Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Convert to submission format","metadata":{}},{"cell_type":"code","source":"output = pd.DataFrame({'id': test_data.id,\n                       'landmarks': predictions})\n\noutput.head()\noutput.to_csv('submission.csv', index=False)","metadata":{"trusted":true},"execution_count":null,"outputs":[]}]}