{"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":"#  [BMS-MT] EDA + Simple Model\n\n**Approach:**\n - Target processing:\n     - Find the X most common target strings \n         - {where X is some yet to be determined number}\n     - Compare each training target to top X values and pick the one that has the lowest levenshtein distance\n - Downsample\n     - This is for faster processing, and allows us to run modeling in-memory\n - Feature Processing:\n     - Create training sample of (N x 16k) using Image Hashing features \n         - {where N is some yet to be determined number}\n - Modeling:\n     - Tree Based model using Image Hash features to predict the best top bucketed responses that produces the lowest levenshtein distance\n \n \n **Resources:**\n - https://www.kaggle.com/yeayates21/panda-densenet-keras-starter-gpu\n - https://www.kaggle.com/xhlulu/alaska2-efficientnet-on-tpus\n - https://www.kaggle.com/yeayates21/image-captioning-with-tensorflow\n - https://www.w3resource.com/python-exercises/numpy/python-numpy-exercise-94.php\n - https://www.kaggle.com/yasufuminakama/molecular-translation-naive-baseline\n - https://www.kaggle.com/ghaiyur/baseline-starter","metadata":{}},{"cell_type":"markdown","source":"# Imports","metadata":{}},{"cell_type":"code","source":"import numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\nimport os\nimport pickle\nfrom tqdm.notebook import tqdm\nfrom Levenshtein import distance as levenshtein_distance\nimport tensorflow as tf\nfrom tensorflow.keras.preprocessing import image\nfrom tensorflow.keras.applications.resnet50 import ResNet50\nfrom tensorflow.keras.applications.resnet50 import preprocess_input\nfrom xgboost import XGBClassifier\nfrom sklearn.ensemble import RandomForestClassifier\nfrom sklearn.model_selection import RandomizedSearchCV\nfrom scipy.stats import uniform, truncnorm, randint\nimport imagehash\nfrom PIL import Image\nfrom datetime import date, datetime","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2021-05-27T15:13:36.756247Z","iopub.execute_input":"2021-05-27T15:13:36.756514Z","iopub.status.idle":"2021-05-27T15:13:36.766115Z","shell.execute_reply.started":"2021-05-27T15:13:36.756491Z","shell.execute_reply":"2021-05-27T15:13:36.765321Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Settings","metadata":{}},{"cell_type":"code","source":"# time the notebook and stop it from running past the Kaggle allowed compute time\nstartTime = datetime.now()","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Review Labels","metadata":{}},{"cell_type":"code","source":"df = pd.read_csv(\"../input/bms-molecular-translation/train_labels.csv\")\ndf.head()","metadata":{"execution":{"iopub.status.busy":"2021-05-27T15:02:47.668618Z","iopub.execute_input":"2021-05-27T15:02:47.669106Z","iopub.status.idle":"2021-05-27T15:02:55.776557Z","shell.execute_reply.started":"2021-05-27T15:02:47.669051Z","shell.execute_reply":"2021-05-27T15:02:55.775714Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# EDA\n\n### Review Top 10 Labels","metadata":{}},{"cell_type":"code","source":"%%time\n\ndfg = df.groupby(['InChI']).count()\ndfg.rename(columns={\"image_id\": \"Count\"}, inplace=True)\ndfg = dfg.reset_index(drop=False)\ndfg.sort_values(by='Count', ascending=False, inplace=True)\ndfg.head()","metadata":{"execution":{"iopub.status.busy":"2021-05-27T15:02:55.777851Z","iopub.execute_input":"2021-05-27T15:02:55.778188Z","iopub.status.idle":"2021-05-27T15:03:05.27986Z","shell.execute_reply.started":"2021-05-27T15:02:55.77816Z","shell.execute_reply":"2021-05-27T15:03:05.278942Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Results:\n\n - All target values are unique, which makes sense given the problem.\n \n### Next Steps:\n - We'll need to break the target down and try to find some common patterns.","metadata":{}},{"cell_type":"code","source":"%%time\n\ndf['InChI_list'] = df['InChI'].apply(lambda x: x.split('/'))\ndf['InChI_length'] = df['InChI_list'].apply(len)\nInChI_df = df['InChI_list'].apply(pd.Series)\ndf = pd.concat([df, InChI_df.add_prefix('InChI_')], axis=1)\ndf.head()","metadata":{"execution":{"iopub.status.busy":"2021-05-27T15:03:05.281816Z","iopub.execute_input":"2021-05-27T15:03:05.282204Z","iopub.status.idle":"2021-05-27T15:12:00.885802Z","shell.execute_reply.started":"2021-05-27T15:03:05.282163Z","shell.execute_reply":"2021-05-27T15:12:00.88482Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"%%time\n\ndfg = df[['image_id','InChI_length']].groupby(['InChI_length']).count()\ndfg.rename(columns={\"image_id\": \"Count\"}, inplace=True)\ndfg = dfg.reset_index(drop=False)\ndfg.sort_values(by='Count', ascending=False, inplace=True)\ndfg.head()","metadata":{"execution":{"iopub.status.busy":"2021-05-27T15:12:00.888017Z","iopub.execute_input":"2021-05-27T15:12:00.888302Z","iopub.status.idle":"2021-05-27T15:12:02.090732Z","shell.execute_reply.started":"2021-05-27T15:12:00.88827Z","shell.execute_reply":"2021-05-27T15:12:02.089515Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Results:\n\n - The most common number of subsections divided by '/' is 4.\n \n### Next Steps:\n - Let's find the 3 top patters for each subsection, thus giving us 3**4=81 top categories.","metadata":{}},{"cell_type":"code","source":"%%time\n\ndfg = df[['image_id','InChI_0']].groupby(['InChI_0']).count()\ndfg.rename(columns={\"image_id\": \"Count\"}, inplace=True)\ndfg = dfg.reset_index(drop=False)\ndfg.sort_values(by='Count', ascending=False, inplace=True)\ndfg.head()","metadata":{"execution":{"iopub.status.busy":"2021-05-27T15:12:02.092014Z","iopub.execute_input":"2021-05-27T15:12:02.092309Z","iopub.status.idle":"2021-05-27T15:12:02.691781Z","shell.execute_reply.started":"2021-05-27T15:12:02.092282Z","shell.execute_reply":"2021-05-27T15:12:02.690889Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Results:\n\n - The only value for InChI subsection 0 is 'InChI=1S'.\n \n### Next Steps:\n - Let's find the 5 top patterns for each remaining subsection, thus giving us 5x3=125 top categories.","metadata":{}},{"cell_type":"code","source":"%%time\n\ndfg = df[['image_id','InChI_1']].groupby(['InChI_1']).count()\ndfg.rename(columns={\"image_id\": \"Count\"}, inplace=True)\ndfg = dfg.reset_index(drop=False)\ndfg.sort_values(by='Count', ascending=False, inplace=True)\ndfg.head()","metadata":{"execution":{"iopub.status.busy":"2021-05-27T15:12:02.693039Z","iopub.execute_input":"2021-05-27T15:12:02.693384Z","iopub.status.idle":"2021-05-27T15:12:04.311149Z","shell.execute_reply.started":"2021-05-27T15:12:02.693347Z","shell.execute_reply":"2021-05-27T15:12:04.31022Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"%%time\n\ndfg = df[['image_id','InChI_2']].groupby(['InChI_2']).count()\ndfg.rename(columns={\"image_id\": \"Count\"}, inplace=True)\ndfg = dfg.reset_index(drop=False)\ndfg.sort_values(by='Count', ascending=False, inplace=True)\ndfg.head()","metadata":{"execution":{"iopub.status.busy":"2021-05-27T15:12:04.314501Z","iopub.execute_input":"2021-05-27T15:12:04.314843Z","iopub.status.idle":"2021-05-27T15:12:13.580616Z","shell.execute_reply.started":"2021-05-27T15:12:04.314809Z","shell.execute_reply":"2021-05-27T15:12:13.579713Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"#### The values in InChI_2 are hard to read, so let's print them out","metadata":{}},{"cell_type":"code","source":"print(\"Top values for InChI_2:\")\nfor index, row in dfg.head().iterrows():\n    print(row['InChI_2'])","metadata":{"execution":{"iopub.status.busy":"2021-05-27T15:12:13.582604Z","iopub.execute_input":"2021-05-27T15:12:13.582861Z","iopub.status.idle":"2021-05-27T15:12:13.588956Z","shell.execute_reply.started":"2021-05-27T15:12:13.582835Z","shell.execute_reply":"2021-05-27T15:12:13.587962Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"%%time\n\ndfg = df[['image_id','InChI_3']].groupby(['InChI_3']).count()\ndfg.rename(columns={\"image_id\": \"Count\"}, inplace=True)\ndfg = dfg.reset_index(drop=False)\ndfg.sort_values(by='Count', ascending=False, inplace=True)\ndfg.head()","metadata":{"execution":{"iopub.status.busy":"2021-05-27T15:12:13.590613Z","iopub.execute_input":"2021-05-27T15:12:13.590972Z","iopub.status.idle":"2021-05-27T15:12:19.446917Z","shell.execute_reply.started":"2021-05-27T15:12:13.590926Z","shell.execute_reply":"2021-05-27T15:12:19.446132Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Results:\n\n - Great!  We have our top values!.\n \n### Next Steps:\n - Let's compile a list of our new top 15 categories.","metadata":{}},{"cell_type":"code","source":"c0 = 'InChI=1S'\nch1 = ['C15H22N2O2','C16H24N2O2','C14H20N2O2','C17H26N2O2','C14H22N2O2']\nch2 = ['c1-2-3-4-5-6-7-8-9-10-11-12-13-14-15-16-17-18(19)20',\n       'c1-2-3-4-5-6-7-8-9-10-11-12-13-14-15-16-17-18-19-20(21)22',\n       'c1-13(22)17-6-7-18-16-5-4-14-12-15(23)8-10-20(14,2)19(16)9-11-21(17,18)3',\n       'c1-2-3-4-5-6-7-8-9-10-11-12-13-14-15-16-17-18-19-20-21-22(23)24',\n       'c1-18-9-7-13(20)11-12(18)3-4-14-15-5-6-17(21)19(15,2)10-8-16(14)18']\nch3 = ['h2-8H,1H3','h2-7H,1H3','h3-8H,1-2H3','h2-9H,1H3','h2-6H,1H3']\ntop15 = []\nfor c1 in ch1:\n    for c2 in ch2:\n        for c3 in ch3:\n            top15.append(c0 + '/' + c1 + '/' + c2 + '/' + c3)\n            \nprint(\"Total values: \", len(top15))\nprint(\"First 5 of our manufactured top 125 labels:\")\nprint(top15[:5])","metadata":{"execution":{"iopub.status.busy":"2021-05-27T15:12:19.448193Z","iopub.execute_input":"2021-05-27T15:12:19.448556Z","iopub.status.idle":"2021-05-27T15:12:19.455682Z","shell.execute_reply.started":"2021-05-27T15:12:19.448505Z","shell.execute_reply":"2021-05-27T15:12:19.454793Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Notes:\n - We're going to remove the last 25 because they're going to be an issue later on since they're less frequent and will be selected less frequently by our levenshtein_distance","metadata":{}},{"cell_type":"code","source":"top15 = top15[:120]\nprint(\"Total values: \", len(top15))","metadata":{"execution":{"iopub.status.busy":"2021-05-27T15:12:19.456971Z","iopub.execute_input":"2021-05-27T15:12:19.457303Z","iopub.status.idle":"2021-05-27T15:12:19.472639Z","shell.execute_reply.started":"2021-05-27T15:12:19.457268Z","shell.execute_reply":"2021-05-27T15:12:19.471828Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Downsample","metadata":{}},{"cell_type":"code","source":"%%time\n\ndfs = df[['image_id','InChI']].sample(n=37500, random_state=0)\nprint(\"Shape of downsampled training data: \", dfs.values.shape)\ndfs.head()","metadata":{"execution":{"iopub.status.busy":"2021-05-27T15:12:19.473527Z","iopub.execute_input":"2021-05-27T15:12:19.473794Z","iopub.status.idle":"2021-05-27T15:12:19.688898Z","shell.execute_reply.started":"2021-05-27T15:12:19.47377Z","shell.execute_reply":"2021-05-27T15:12:19.688017Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Create Target\n\n - Compare each training target to our top label values and pick the one that has the lowest levenshtein distance","metadata":{}},{"cell_type":"code","source":"%%time\n\n# get target values\ndef find_best_target_value(x):\n    return np.argmin([levenshtein_distance(x,v) for v in top15])\ndfs['Target'] = dfs['InChI'].apply(find_best_target_value)\n\n# get image paths\ntraining_image_folder = \"../input/bms-molecular-translation/train/\"\ndef get_image_path(x):\n    return training_image_folder + x[0] + \"/\" + x[1] + \"/\" + x[2] + \"/\" + x + \".png\"\ndfs[\"image_path\"] = dfs[\"image_id\"].apply(lambda x: get_image_path(x))\n\ndfs.head()","metadata":{"execution":{"iopub.status.busy":"2021-05-27T15:12:19.689922Z","iopub.execute_input":"2021-05-27T15:12:19.690164Z","iopub.status.idle":"2021-05-27T15:13:36.74767Z","shell.execute_reply.started":"2021-05-27T15:12:19.690141Z","shell.execute_reply":"2021-05-27T15:13:36.747067Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# https://www.w3resource.com/python-exercises/numpy/python-numpy-exercise-94.php\nunique_elements, counts_elements = np.unique(dfs['Target'].values, return_counts=True)\nprint(\"Frequency of unique values of the Target array:\")\nprint(np.asarray((unique_elements, counts_elements)))","metadata":{"execution":{"iopub.status.busy":"2021-05-27T15:13:36.74851Z","iopub.execute_input":"2021-05-27T15:13:36.74886Z","iopub.status.idle":"2021-05-27T15:13:36.755187Z","shell.execute_reply.started":"2021-05-27T15:13:36.748835Z","shell.execute_reply":"2021-05-27T15:13:36.754411Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Feature Processing\n\n- Create training sample of (10k x ~2k) using ResNet features","metadata":{}},{"cell_type":"markdown","source":"### Model","metadata":{}},{"cell_type":"code","source":"# show what image hash looks like\nhashcode = imagehash.average_hash(Image.open('../input/bms-molecular-translation/train/0/0/0/0000a5af84ef.png'))\nprint(hashcode)","metadata":{"execution":{"iopub.status.busy":"2021-05-27T15:36:23.467076Z","iopub.execute_input":"2021-05-27T15:36:23.467449Z","iopub.status.idle":"2021-05-27T15:36:23.486721Z","shell.execute_reply.started":"2021-05-27T15:36:23.467404Z","shell.execute_reply":"2021-05-27T15:36:23.485835Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# create vocab dictionary to convert hash to array\nvals = ['0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f','g','h','i','j','k','l',\n        'm','n','o','p','q','r','s','t','u','v','w','x','y','z']\nvocab = {}\nfor i, v in enumerate(vals):\n    vocab[v] = i","metadata":{"execution":{"iopub.status.busy":"2021-05-27T15:36:24.268054Z","iopub.execute_input":"2021-05-27T15:36:24.268387Z","iopub.status.idle":"2021-05-27T15:36:24.274262Z","shell.execute_reply.started":"2021-05-27T15:36:24.268358Z","shell.execute_reply":"2021-05-27T15:36:24.273426Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# example of converting hash to numpy array\nnphsh = np.array([vocab[x] for x in str(hashcode)])\nprint(nphsh.shape)\nprint(nphsh)","metadata":{"execution":{"iopub.status.busy":"2021-05-27T15:36:25.842697Z","iopub.execute_input":"2021-05-27T15:36:25.843113Z","iopub.status.idle":"2021-05-27T15:36:25.848611Z","shell.execute_reply.started":"2021-05-27T15:36:25.843087Z","shell.execute_reply":"2021-05-27T15:36:25.847711Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Create in-memory training set","metadata":{}},{"cell_type":"code","source":"def img_preprocessing(image_path):\n    hashcode = imagehash.average_hash(Image.open(image_path))\n    nphsh = np.array([vocab[x] for x in str(hashcode)])\n    return nphsh\n\n# data generator, intended to be used in a call to model.fit()\ndef data_generator(df):\n    # loop for ever over images\n    while 1:\n        for index, row in df.iterrows():\n            yield img_preprocessing(row['image_path']), row['Target']","metadata":{"execution":{"iopub.status.busy":"2021-05-27T15:41:05.057441Z","iopub.execute_input":"2021-05-27T15:41:05.057838Z","iopub.status.idle":"2021-05-27T15:41:05.062858Z","shell.execute_reply.started":"2021-05-27T15:41:05.057805Z","shell.execute_reply":"2021-05-27T15:41:05.062247Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# define the generator\ngenerator = data_generator(dfs) \n# get the number of training images from the target\\id dataset\nN = dfs.shape[0]\n# create an empty matrix for storing the image features and target values\nx_train = np.empty((N, 16), dtype=np.float)\ny_train = np.empty((N, 1), dtype=np.int)\n# loop through the images from the images ids from the target\\id dataset\n# then grab the cooresponding image from disk, pre-process, and store in matrix in memory\nfor i, image_id in enumerate(tqdm(dfs['image_id'])):\n    x, y = next(generator)\n    x_train[i, :] = np.array(x)\n    y_train[i, :] = np.array(y)\n\nprint(x_train.shape)\nprint(y_train.shape)","metadata":{"execution":{"iopub.status.busy":"2021-05-27T15:41:05.599505Z","iopub.execute_input":"2021-05-27T15:41:05.599838Z","iopub.status.idle":"2021-05-27T15:45:04.269683Z","shell.execute_reply.started":"2021-05-27T15:41:05.599812Z","shell.execute_reply":"2021-05-27T15:45:04.26882Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Modeling:\n\n     - XGB model using ResNet features to predict the best top-10 value that produces the lowest levenshtein distance","metadata":{}},{"cell_type":"code","source":"%%time\n\npipe = XGBClassifier()\ndistributions = {'n_estimators': randint(5,150),\n                 'max_depth': randint(1,4)}\nclf = RandomizedSearchCV(pipe, distributions, random_state=87, n_iter=2, cv=2, \n                         scoring='roc_auc_ovr', n_jobs=-1, verbose=2, error_score='raise')\nsearch = clf.fit(x_train, y_train.flatten()) # best model is search.best_estimator_","metadata":{"execution":{"iopub.status.busy":"2021-05-27T15:45:04.271134Z","iopub.execute_input":"2021-05-27T15:45:04.271399Z","iopub.status.idle":"2021-05-27T15:45:17.009326Z","shell.execute_reply.started":"2021-05-27T15:45:04.271372Z","shell.execute_reply":"2021-05-27T15:45:17.008363Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"print(\"Best model scored avg AUC {} using the following hyperparameters: {}\".format(search.best_score_, search.best_params_))","metadata":{"execution":{"iopub.status.busy":"2021-05-27T15:45:17.011194Z","iopub.execute_input":"2021-05-27T15:45:17.011481Z","iopub.status.idle":"2021-05-27T15:45:17.016517Z","shell.execute_reply.started":"2021-05-27T15:45:17.011451Z","shell.execute_reply":"2021-05-27T15:45:17.015348Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"pickle.dump(search.best_estimator_, open(\"bmsmt_tp125_rf_model2.pkl\", \"wb\"))","metadata":{"execution":{"iopub.status.busy":"2021-05-27T15:45:17.018305Z","iopub.execute_input":"2021-05-27T15:45:17.018783Z","iopub.status.idle":"2021-05-27T15:45:17.062837Z","shell.execute_reply.started":"2021-05-27T15:45:17.018744Z","shell.execute_reply":"2021-05-27T15:45:17.061284Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Inference","metadata":{}},{"cell_type":"code","source":"# load test data ids\ndft = pd.read_csv(\"../input/bms-molecular-translation/sample_submission.csv\")\n\n# change default value\n# https://www.kaggle.com/ghaiyur/baseline-starter\ndft[\"InChI\"] = ['InChI=1S/C12H23NO3/c1-1-1-2-12-14-13(12-18-12)10-1-2-1-1(-13-6-1-12/h8-9,1-H,11H2,1-2H3,(H,1,18)'] * len(dft)\n\n# get image paths\ntest_image_folder = \"../input/bms-molecular-translation/test/\"\ndef get_image_path(x):\n    return test_image_folder + x[0] + \"/\" + x[1] + \"/\" + x[2] + \"/\" + x + \".png\"\ndft[\"image_path\"] = dft[\"image_id\"].apply(lambda x: get_image_path(x))\n\n# data generator, intended to be used in a call to model.fit()\ndef data_generator(df):\n    # loop for ever over images\n    while 1:\n        for index, row in df.iterrows():\n            yield img_preprocessing(row['image_path'])\n\n# define the generator\ngenerator = data_generator(dft) \n# get the number of training images from the target\\id dataset\nN = dft.shape[0]\n# create an empty matrix for storing the image features and target values\nx_train = np.empty((1, 16), dtype=np.float)\n# loop through the images from the images ids from the target\\id dataset\n# then grab the cooresponding image from disk, pre-process, and store in matrix in memory\nfor i, image_id in enumerate(tqdm(dft['image_id'])):\n    x = next(generator)\n    yhat = search.best_estimator_.predict(x.reshape(1,-1))\n    dft.loc[i,'InChI'] = top15[yhat[0]]\n    # stop if we run too long (Kaggle allows runtime of 9 hrs)\n    td = datetime.now()-startTime\n    hours = td.seconds // 3600\n    if hours > 4:\n        break","metadata":{"execution":{"iopub.status.busy":"2021-05-27T15:50:12.088461Z","iopub.execute_input":"2021-05-27T15:50:12.088802Z","iopub.status.idle":"2021-05-27T16:00:54.710461Z","shell.execute_reply.started":"2021-05-27T15:50:12.088775Z","shell.execute_reply":"2021-05-27T16:00:54.707822Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"dft[['image_id','InChI']].to_csv(\"submission.csv\", index=False)","metadata":{"execution":{"iopub.status.busy":"2021-05-27T15:45:17.065579Z","iopub.status.idle":"2021-05-27T15:45:17.06598Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]}]}