{"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":"# Background of the competition\n\nOur task is to predict a string that is the [IUPAC International Chemical Identifier (InChI)](https://en.wikipedia.org/wiki/International_Chemical_Identifier) notation for a molecule from an image of that molecule. Even if you took high school chemistry, you are probably not familiar with InChI (I certainly wasn't). So, I'll outline a few important things about it.\n\nAs you will see, it might be better to think of the standard InChI string as 7 separate strings (sometimes some are empty) that are related (that need to be consistent with each other, which one can check automatically). I.e. in a sense there are 7 prediction targets that describe different properties of the molecule, but they are closely related.\n\nThe training data we are given consists of a `training_labels.csv`, the contents of which looks like this (and, yes, that's really almost 2.5 million rows):","metadata":{}},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nfrom PIL import Image, ImageChops\nimport re\nfrom tqdm import tqdm\nimport seaborn as sns\n\ntrain_labels = pd.read_csv('../input/bms-molecular-translation/train_labels.csv')\ntrain_labels","metadata":{"_kg_hide-input":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Additionally, we have images (one per row of the csv file) such as this one that corresponds to the first row:","metadata":{}},{"cell_type":"code","source":"Image.open('../input/bms-molecular-translation/train/0/0/0/000011a64c74.png')","metadata":{"_kg_hide-input":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Note that because of the huge number of images, they are arranged in a lot of sub-folders that correspond to the first 3 characters in the `image_id`. For example, the image in the first row is in `'../input/bms-molecular-translation/train/0/0/0/000011a64c74.png'`. You can derived the location like this:","metadata":{}},{"cell_type":"code","source":"image_id = '000011a64c74'\nfile_path = f'../input/bms-molecular-translation/train/{image_id[0]}/{image_id[1]}/{image_id[2]}/{image_id}.png'\nfile_path","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Important things to realize about the InChI training labels\n\nThe [IUPAC International Chemical Identifier (InChI)](https://en.wikipedia.org/wiki/International_Chemical_Identifier) notation that we are asked to predict consists of six parts that are closely related, but not all of them will always be present. We always seem to have the version, the chemical formula and the connection layers, all other layers can sometimes be absent, if they are not relevant. \n\nThe layers are separated by \"/\" and for all but the first one (version, no slash before that) and the chemical formula (just a slash, no letter behind that), it's a slash followed by a letter (\"c\", \"h\", \"b\", \"t\", \"m\", \"s\", \"i\"). In theory, there's also \"/q\", \"/p\" (charge layers), \"/f\", \"/o\", and \"/r\" (the last three are apparently never included in standard InChI, which we are looking at here), but I see no examples of those in the data.\n\nLet's look at an example from the training data where a lot of layers (11) are present: **'InChI=1S/C21H30O4/c1-12(22)25-14-6-8-20(2)13(10-14)11-17(23)19-15-4-5-18(24)21(15,3)9-7-16(19)20/h13-16,19H,4-11H2,1-3H3/t13-,14+,15+,16-,19-,20+,21+/m1/s1'**.\n1. **Version:** The prefix \"InChI=1S/\" can basically be ignored since all target labels have it. It indicates the version number (here version 1 \"Standard\").\n2. **Chemical formula:** \"C21H30O4\" (21 carbon atoms, 30 hydrogen atoms and 4 oxygen atoms). Note that in case of atoms that need more letters like \"Br\" the second letter would be lower case. Also note that a single atom does not get a number behind it, so in an example like \"C13H20OS\" we do not write \"C13H20O1S1\".\n3. **\"connection layer\"**: in what order the atoms (other than hydrogen atoms) are connected (and what the side branches are): \"c1-12(22)25-14-6-8-20(2)13(10-14)11-17(23)19-15-4-5-18(24)21(15,3)9-7-16(19)20\" (you can basically omit the \"c\" and just look at the rest of this sequence), so here atom 1 is connected to 12, there's a side branch to 22 etc.\n4. **\"hydrogen layer\"** how/how many hydrogen atoms are connected: \"h13-16,19H,4-11H2,1-3H3\" (you can basically omit the \"h\" and just look at the rest of this sequence)\n5. **tetrahedral stereochemistry of atoms**: \"t13-,14+,15+,16-,19-,20+,21+\"\n6. **tetrahedral stereochemistry of allenes**: \"m1\"\n7. **type of stereochemistry information**: \"s1\"\n8. In this example, there's no **\"/b\"** layer. \n9. In this example, there's also no **\"/i\"** layer.\n \n \nFor the last two layers, examples with those included are \n* with a \"/b\": 'InChI=1S/C17H10BrN3O/c18-14-7-3-1-5-11(14)9-12(10-19)16-20-15-8-4-2-6-13(15)17(22)21-16/h1-9,13H/b12-9+'\n* with a \"/i\": 'InChI=1S/C24H50OSi/c1-7-8-9-10-11-12-13-14-15-16-17-18-19-20-21-22-23-25-26(5,6)24(2,3)4/h14-15H,7-13,16-23H2,1-6H3/b15-14-/i13D2,14D,15D'\n\nNote that the layers need to be consistent. Also note that often characters belong togehther e.g. \"15\" means the number 15. It can either refer to a number of atoms, but could also refer to atom number 15 in the molecule. If you are tempted to use a language model, I think there's a decent amount of research suggesting that language models [struggle a bit with understanding numbers when they treat them like any other word in the vocabulary, but that we can improve on that](https://arxiv.org/pdf/1805.08154) and it may be even harder to figure out the different meanings in the different layers of the notation.\n\n@samusram kindly shared [this short entertaining video](https://www.youtube.com/watch?v=rAnJ5toz26c) in [this forum post](https://www.kaggle.com/c/bms-molecular-translation/discussion/223305).","metadata":{}},{"cell_type":"markdown","source":"# Let's split the different layers of the training label strings \n\nWe'll probably want to split things into a data frame with each layer in a separate column, so let's generate that:","metadata":{}},{"cell_type":"code","source":"layers = ['version', 'chemical_notation']\nfor layerid, layer in enumerate(layers):\n    train_labels[layer] = [ (train_label.split(\"/\"))[layerid] for train_label in train_labels['InChI']]\n\nlayers = [\"c\", \"h\", \"b\", \"t\", \"m\", \"s\", \"i\"]\nfor layerid, layer in enumerate(layers):\n    train_labels[layer] = [ ''.join([splitlayer if splitlayer[0]==layer else \"\" for splitlayer in train_label.split(\"/\")])\n                           for train_label in train_labels['InChI']]\n    train_labels[layer] = ['' if len(item)==0 else item[1:] for item in train_labels[layer]]\n    \n    \ntrain_labels","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# The image data\n\nLet's look at some more images:","metadata":{}},{"cell_type":"code","source":"from matplotlib.pyplot import imshow\nimport matplotlib.pyplot as plt\nfrom textwrap import wrap\n\nfig = plt.figure(figsize=(20, 24))\ncolumns = 3\nrows = 6\nfor i, image_id in enumerate(train_labels['image_id'].values[0:18]):\n    file_path = f'../input/bms-molecular-translation/train/{image_id[0]}/{image_id[1]}/{image_id[2]}/{image_id}.png'\n    img = Image.open(file_path)    \n    fig.add_subplot(rows, columns, i+1)\n    plt.imshow(img)\n    plt.subplots_adjust(hspace = .5)\n    plt.title( \"\\n\".join(wrap(train_labels['InChI'].values[i], 40)) )","metadata":{"_kg_hide-input":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"As you can see the molecules can differ enormously in size. As a result, it will be challenging to pick a good image size to use with image models. E.g. for the image at the bottom left, a low resolution like 224 by 224 probably does not lose much information, while for the one on the bottom right every little wriggle is an atom and we could be quite far off, if we omit anything.","metadata":{}},{"cell_type":"markdown","source":"I'm not yet clear on how you know which atom is considered \"first\". As I [understand it, the string is actually unique](https://en.wikipedia.org/wiki/International_Chemical_Identifier) and presumably the [canonicalization](https://depth-first.com/articles/2006/08/12/inchi-canonicalization-algorithm/) takes care of this? Making sure that one exactly understands that, will also be important, then one can perhaps post-process a model output to ensure it is exactly how it should be...","metadata":{}},{"cell_type":"markdown","source":"# Characteristics of the images","metadata":{}},{"cell_type":"code","source":"import fastcore\nfrom fastcore.parallel import parallel\nfrom PIL import Image, ImageChops\nfrom tqdm import tqdm\n\ntrain = pd.read_csv('../input/bms-molecular-translation/train_labels.csv')\n\n# Function for trimming an edge from a PIL\n# Code from: https://stackoverflow.com/questions/10615901/trim-whitespace-using-pil/10616717#10616717\ndef trim(im):\n    bg = Image.new(im.mode, im.size, im.getpixel((0,0)))\n    diff = ImageChops.difference(im, bg)\n    diff = ImageChops.add(diff, diff, 2.0, -100)\n    bbox = diff.getbbox()\n    if bbox:\n        return im.crop(bbox)\n\n# Function to extract characteristics of interest from image files\ndef characterize_image(image_id):\n    file_path = f'../input/bms-molecular-translation/train/{image_id[0]}/{image_id[1]}/{image_id[2]}/{image_id}.png'\n    img = Image.open(file_path)\n    bands = img.getbands()\n    bands4 = [ chan in bands for chan in ['L', 'R', 'G', 'B']]\n    imgarr = np.array(img)\n    \n    timg = trim(img)\n    timgarr = np.array(timg)\n    \n    return (img.width, img.height, img.mode, bands4[0], bands4[1],\n            bands4[2], bands4[3], np.sum(imgarr<255)/imgarr.size,\n            timg.width, timg.height, np.sum(timgarr<255)/timgarr.size)\n\n## Test what the function does\n#characterize_image(train['image_id'].values[0])\n\n# Parallel processing of getting image characteristics \nout1 = parallel(characterize_image, [image_id for image_id in list(train['image_id'].values)], \n                n_workers=4, \n                progress=False)\n\nimage_info = pd.DataFrame(out1, columns=['width', 'height', 'mode (L=grayscale)', \n                                         'L', 'R', 'G', 'B', 'Proportion of non-white pixels',\n                                        'trimmed width', 'trimmed height',\n                                        'Proportion of non-white pixels (trimmed)'])\\\n                .assign(image_id=train['image_id'].values)\n\nimage_info['Aspect ratio (width/height)'] = image_info['width'] / image_info['height']\nimage_info['Trimmed aspect ratio (width/height)'] = image_info['trimmed width'] / image_info['trimmed height']\nimage_info['Image size'] = image_info['width'] * image_info['height']\nimage_info['Trimmed image size'] = image_info['trimmed width'] * image_info['trimmed height']\n\nimage_info['Image size category'] =  pd.cut(\n    image_info['Image size'].values, \n    [0, 64*64, 128*128, 256*256, 384*384, 512*512, 99999*99999], \n    right=False, \n    labels=['<=64*64', '>64*64 & <=128*128', '>128*128 & <=256*256', '>256*256 & <=384*384', '>384*384 & <=512*512', '>512*512'])\n\nimage_info['Trimmed image size category'] =  pd.cut(\n    image_info['Trimmed image size'].values, \n    [0, 64*64, 128*128, 256*256, 384*384, 512*512, 99999*99999], \n    right=False, \n    labels=['<=64*64', '>64*64 & <=128*128', '>128*128 & <=256*256', '>256*256 & <=384*384', '>384*384 & <=512*512', '>512*512'])\n\nimage_info = pd.merge(image_info, train_labels, on='image_id', how='left')","metadata":{"_kg_hide-input":true,"_kg_hide-output":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"All the images are grayscale images (i.e. single channel, rather than red-green-blue RGB-3-channel images).","metadata":{}},{"cell_type":"code","source":"sns.countplot(data=image_info, y='mode (L=grayscale)');","metadata":{"_kg_hide-input":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"As we can see the images are predominatly twice as wide as they are tall (a landscape format), but with a considerable variation in aspect ratio.","metadata":{}},{"cell_type":"code","source":"sns.histplot(data=image_info, x='Aspect ratio (width/height)');\nplt.ylim(0, None);\nplt.xlim(0, 10);\nplt.show();","metadata":{"_kg_hide-input":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"When we trim the white edges around the images, this does not really change:","metadata":{}},{"cell_type":"code","source":"sns.histplot(data=image_info, x='Trimmed aspect ratio (width/height)');\nplt.ylim(0, None);\nplt.xlim(0, 10);\nplt.show();","metadata":{"_kg_hide-input":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"There's in fact a lot of empty white area in all the images:","metadata":{}},{"cell_type":"code","source":"sns.histplot(data=image_info, x='Proportion of non-white pixels');","metadata":{"_kg_hide-input":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"even after trimming away white edges:","metadata":{}},{"cell_type":"code","source":"sns.histplot(data=image_info, x='Proportion of non-white pixels (trimmed)');","metadata":{"_kg_hide-input":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"The images are on a whole rather small","metadata":{}},{"cell_type":"code","source":"sns.histplot(data=image_info, x='Image size');\nplt.ylim(0, None);\nplt.xlim(0, 500000);\nplt.show();","metadata":{"_kg_hide-input":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"sns.countplot(data=image_info, y='Image size category');","metadata":{"_kg_hide-input":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"especially when you trim white edges.","metadata":{}},{"cell_type":"code","source":"sns.histplot(data=image_info, x='Trimmed image size');\nplt.ylim(0, None);\nplt.xlim(0, 500000);\nplt.show();","metadata":{"_kg_hide-input":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"sns.countplot(data=image_info, y='Trimmed image size category');","metadata":{"_kg_hide-input":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"The width and height distributions look like this:","metadata":{}},{"cell_type":"code","source":"sns.histplot(data=image_info, x='height');","metadata":{"_kg_hide-input":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"sns.histplot(data=image_info, x='width');","metadata":{"_kg_hide-input":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"sns.histplot(data=image_info, x='trimmed height');","metadata":{"_kg_hide-input":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"sns.histplot(data=image_info, x='trimmed width');","metadata":{"_kg_hide-input":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# How often does each type of atom occur in the molecules\n\nNow, let's look at the atoms that even occur in the molecules were looking at. In case you had not guessed it from the pictures above, we seem to be clearly in the space of [organic chemistry](https://en.wikipedia.org/wiki/Organic_chemistry) with lots of carbon, hydrogen and oxygen atoms.","metadata":{}},{"cell_type":"code","source":"# Which atoms even occur in these compounds?\nmylistlist = [ re.findall( '[A-Z][a-z]*', mysubstr)[0] for a in tqdm(train_labels['chemical_notation'].values)\n              for mysubstr in re.findall('[A-Z][^A-Z]*', a)]\nsorted_unique = list(np.sort(np.unique(mylistlist)))\nnumber_of_uniques = len(sorted_unique)\n\nprint(f'{number_of_uniques} types of atoms occur in the molecules in the training data: {sorted_unique}.')","metadata":{"_kg_hide-input":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"These are the following elements from the [periodic table](https://en.wikipedia.org/wiki/Chemical_element#Periodic_table) in increasing atomic number:\n* H: [Hydrogen](https://en.wikipedia.org/wiki/Hydrogen)\n* B: [Boron](https://en.wikipedia.org/wiki/Boron)\n* C: [Carbon](https://en.wikipedia.org/wiki/Carbon)\n* N: [Nitrogen](https://en.wikipedia.org/wiki/Nitrogen)\n* O: [Oxygen](https://en.wikipedia.org/wiki/Oxygen)\n* F: [Fluorine](https://en.wikipedia.org/wiki/Fluorine)\n* Si: [Silicon](https://en.wikipedia.org/wiki/Silicon)\n* P: [Phosphorus](https://en.wikipedia.org/wiki/Phosphorus)\n* S: [Sulfur](https://en.wikipedia.org/wiki/Sulfur)\n* Cl: [Chlorine](https://en.wikipedia.org/wiki/Chlorine)\n* Br: [bromide](https://en.wikipedia.org/wiki/Bromide)\n* I: [iodine](https://en.wikipedia.org/wiki/Iodine)\n\nAs you can see most of these elements are from the first 3 periods of the period table of elements, with the exception of bromide and iodine (these are both halogens like fluorine and chlorine). As a reminder, here's the [period table of elements](https://en.wikipedia.org/wiki/Chemical_element) shared on Wikipedia by user [Double_sharp](https://commons.wikimedia.org/wiki/User:Double_sharp) under a [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/) license, on which I've marked the elements we use here:\n![period_table_annotated_small.jpeg](attachment:period_table_annotated_small.jpeg)","metadata":{},"attachments":{"period_table_annotated_small.jpeg":{"image/jpeg":"/9j/4AAQSkZJRgABAQEASABIAAD//gATQ3JlYXRlZCB3aXRoIEdJTVD/2wBDADUlKC8oITUvKy88OTU/UIVXUElJUKN1e2GFwarLyL6qurfV8P//1eL/5re6////////////zv//////////////2wBDATk8PFBGUJ1XV53/3Lrc////////////////////////////////////////////////////////////////////wgARCAGoAyADAREAAhEBAxEB/8QAGAABAQEBAQAAAAAAAAAAAAAAAAIBAwT/xAAXAQEBAQEAAAAAAAAAAAAAAAAAAQID/9oADAMBAAIQAxAAAAH0gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEmgGkg00AwksAAkFAGGFAEGlAGElgA5HUAHI6gAg0oAgk6gEAsAgowoEFElgwwk6AEEnUAgw6AEGmFggAsHM0wks4noBoBJhYABzKKAIBYBhB0AJIOoAILABxOpoBBpQBBB2AIBYBJhpQJBJ0AOZh1AIJOoBJzOpoIBYBABYORpoKNMUagAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHI6GE60KkE27FzIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHmO5lswgAbot6ZyAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABylAGnSwSURdZJk1JppVxl10mQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOE1S806KTrciCznrTExrAaVcZddJkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADhNasJ0UnW5AHPWmJjXNNLWrjLrpMgAAAAAAAAAAAAAAAAAAAAAAAAAAYaAAAAAAAAAAAAAcJqTDTodbkATbyumbkg0u4XXSZAAAAAAAAAAAAAAAAAAAAAAAAAAA53StgDBQAQFABCgBsDBQF5zwmikwpO9gAEnNZuqCK3M2AKsoAAAAAAAAAAAAAAAAAAAAAAAAA5610zmIAA0wAwowAG1kADamNBhO9XjIAF0JKBzlxqQUZcbN4SWaz0sAAAAAAAAAAAAAAAAAAAAAAAAA5610zmMKjjXaIrarLlXWJG1YSCjnt0wgVcRtWGGVccumqxCI0a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"}}},{"cell_type":"markdown","source":"Now let's have a look at how many molecules have how many of these atoms. The x-axis in the plot goes to 200 (sadly not very readable due to some `seaborn` issues), while the y-axis has a log-scale due to the huge differences in orders of magnitude.","metadata":{}},{"cell_type":"code","source":"def get_number(mystring):\n    mynumbers = re.findall( '[0-9]+', mystring)\n    if len(mynumbers)>0: return int(mynumbers[0])\n    else: return int(1)\n\nmydictlist = [ { re.findall( '[A-Z][a-z]*', mysubstr)[0]: [get_number(mysubstr)] for mysubstr in re.findall('[A-Z][^A-Z]*', a) }\n for a in tqdm(train_labels['chemical_notation'].values) ]\n\ntmplist = [[0 if not(atom in mydict) else mydict[atom] for mydict in mydictlist] for atom in sorted_unique]\n\nfor idx, atom in enumerate(sorted_unique):\n    train_labels[atom] = tmplist[idx]\n\nfor atom in sorted_unique:    \n    train_labels[atom] = [val if not(isinstance(val, list)) else val[0] for val in train_labels[atom].values]    \n    \ncount_data = pd.concat( [train_labels[atom].value_counts().rename_axis('atoms').reset_index(name='molecules').assign(atom=atom) for atom in sorted_unique] )\ncount_data['atoms'] = [mole if not(isinstance(mole, list)) else mole[0] for mole in count_data['atoms'].values]    ","metadata":{"_kg_hide-input":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"g = sns.FacetGrid(count_data, col=\"atom\", height=12, col_wrap=1);\ng.map(sns.barplot, \"atoms\", \"molecules\").set(yscale = 'log', xticks=[i for i in range(200)]).set_xticklabels(rotation=90);","metadata":{"_kg_hide-input":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Molecule sizes\nHere's an overview of the artomic mass of the molecules:","metadata":{}},{"cell_type":"code","source":"atomic_mass = {'H': 1.00797, 'B': 10.81, 'C': 12.011, 'N': 14.0067,\n               'O':15.9994, 'F':18.998403, 'Si':28.0855, 'P':30.97376,\n               'S':32.06, 'Cl':35.453, 'Br':79.904, 'I': 126.9045}\n\ntrain_labels['atomic mass'] = atomic_mass['H']*train_labels['H'] + atomic_mass['B']*train_labels['B'] +\\\n    atomic_mass['C']*train_labels['C'] + atomic_mass['N']*train_labels['N'] +\\\n    atomic_mass['O']*train_labels['O'] + atomic_mass['F']*train_labels['F'] + \\\n    atomic_mass['Si']*train_labels['Si'] + atomic_mass['P']*train_labels['P'] + atomic_mass['S']*train_labels['S'] +\\\n    atomic_mass['Cl']*train_labels['Cl'] + atomic_mass['Br']*train_labels['Br'] + atomic_mass['I']*train_labels['I']\n\ntrain_labels['Number of atoms'] = train_labels['H'] + train_labels['B'] + train_labels['C'] + train_labels['N'] +\\\n    train_labels['O'] + train_labels['F'] + train_labels['Si'] + train_labels['P'] + train_labels['S'] +\\\n    train_labels['Cl'] + train_labels['Br'] + train_labels['I']\n\n","metadata":{"_kg_hide-input":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_labels['atomic mass'].describe()","metadata":{"_kg_hide-input":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"And here's an overview of the molecule size in terms of number of atoms:","metadata":{}},{"cell_type":"code","source":"fewest_atoms = min(train_labels['Number of atoms'].values)\nmost_atoms = max(train_labels['Number of atoms'].values)\ntrain_labels['Number of atoms'].describe()","metadata":{"_kg_hide-input":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"This is the smallest molecule in the training data in terms of summed atomic weights is:","metadata":{}},{"cell_type":"code","source":"train_labels[train_labels['atomic mass']<30.1]","metadata":{"_kg_hide-input":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"This is the biggest one:","metadata":{}},{"cell_type":"code","source":"train_labels[train_labels['atomic mass']>2.7e+03]","metadata":{"_kg_hide-input":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Some other notably big molecules are:","metadata":{}},{"cell_type":"code","source":"train_labels[train_labels['atomic mass']>2e+03]","metadata":{"_kg_hide-input":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"I'm not really sure that these are particularly realistic/relevant molecules, but I guess that's kind of besides the point.","metadata":{}},{"cell_type":"markdown","source":"The biggest and smallest molecules in terms of the total number of atoms are:","metadata":{}},{"cell_type":"code","source":"train_labels[train_labels['Number of atoms'] == fewest_atoms]","metadata":{"_kg_hide-input":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_labels[train_labels['Number of atoms'] == most_atoms]","metadata":{"_kg_hide-input":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"There might be an interesting relationship that the bigger the molecule, the more white area there is in a plot:","metadata":{}},{"cell_type":"code","source":"image_info = pd.merge(image_info, \n                      train_labels[['image_id', 'Number of atoms', 'atomic mass']],\n                      on='image_id', how='left')\nsns.scatterplot(data=image_info, x='Number of atoms', y='Proportion of non-white pixels (trimmed)');\nsns.scatterplot(data=image_info, x='atomic mass', y='Proportion of non-white pixels (trimmed)');","metadata":{"_kg_hide-input":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# The competition metric\n \nAs the overview page points out, the competition metric is the mean [Levenshtein distance](https://en.wikipedia.org/wiki/Levenshtein_distance) between the InChi strings you submit and the ground truth InChi values. For two string this distance is the smallest number of edits (an insertion, a deletion and a substitution of a single letter all count as one edit) that we need to make to change one string into the other string. E.g. \"dog\" to \"cat\" requires 3 edits, \"dog\" to \"dogs\" requires 1 edit, \"dog\" to \"god\" requires 2 edits, \"dog\" to \"Cute Doggie!\" needs 10 edits and \"dog\" to \"dog\" requires 0 edits.\n\nI'm not sure whether this is really a good metric. Perhaps the sponsors might have wanted to limit how much of a penalty you can incur from a single example that you completely mess up? Because as it is the worst distance is limited only by the length of the longer of the two string (i.e. ground truth and your prediction) and there's more potential for being penalized for longer strings and less for shorter strings (unless you of course wrongly predict something very long). Furthermore, I wonder whether one could have evaluated the layers individually, although I suppose when the metric admits inserting/removing letters until the correct strings align, perhaps that is fine? In any case, let's look at what this metric does with some real InChI strings:\n\nFirstly, two identical strings have a distance of 0.","metadata":{}},{"cell_type":"code","source":"import nltk\n\n# Two identical strings\nnltk.edit_distance('InChI=1S/C21H30O4/c1-12(22)25-14-6-8-20(2)13(10-14)11-17(23)19-15-4-5-18(24)21(15,3)9-7-16(19)20/h13-16,19H,4-11H2,1-3H3/t13-,14+,15+,16-,19-,20+,21+/m1/s1',\n                   'InChI=1S/C21H30O4/c1-12(22)25-14-6-8-20(2)13(10-14)11-17(23)19-15-4-5-18(24)21(15,3)9-7-16(19)20/h13-16,19H,4-11H2,1-3H3/t13-,14+,15+,16-,19-,20+,21+/m1/s1')","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"If we introduce leave out 9 letters in the connection layer, we can see that we now get a distance of 8. And this distance is symmetrical, i.e. it does not matter which string is first and which one is second:","metadata":{}},{"cell_type":"code","source":"# Two times the same mistake, just in different order:\nprint( nltk.edit_distance('InChI=1S/C21H30O4/c1-12(22)25-14-6-8-20(2)13(10-14)11-17(23)19-15-4-5-18(24)21(15,3)9-7-16(19)20/h13-16,19H,4-11H2,1-3H3/t13-,14+,15+,16-,19-,20+,21+/m1/s1',\n                          'InChI=1S/C21H30O4/c1-14-6-8-20(2)13(10-14)11-17(23)19-15-4-5-18(24)21(15,3)9-7-16(19)20/h13-16,19H,4-11H2,1-3H3/t13-,14+,15+,16-,19-,20+,21+/m1/s1'))\n\nprint( nltk.edit_distance('InChI=1S/C21H30O4/c1-14-6-8-20(2)13(10-14)11-17(23)19-15-4-5-18(24)21(15,3)9-7-16(19)20/h13-16,19H,4-11H2,1-3H3/t13-,14+,15+,16-,19-,20+,21+/m1/s1',\n                         'InChI=1S/C21H30O4/c1-12(22)25-14-6-8-20(2)13(10-14)11-17(23)19-15-4-5-18(24)21(15,3)9-7-16(19)20/h13-16,19H,4-11H2,1-3H3/t13-,14+,15+,16-,19-,20+,21+/m1/s1'))","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"The distance is also 9, if instead of leaving out the 9 letters, I replace them with other letters:","metadata":{}},{"cell_type":"code","source":"print( nltk.edit_distance('InChI=1S/C21H30O4/c1-12(22)25-14-6-8-20(2)13(10-14)11-17(23)19-15-4-5-18(24)21(15,3)9-7-16(19)20/h13-16,19H,4-11H2,1-3H3/t13-,14+,15+,16-,19-,20+,21+/m1/s1',\n                          'InChI=1S/C21H30O4/c1*********-14-6-8-20(2)13(10-14)11-17(23)19-15-4-5-18(24)21(15,3)9-7-16(19)20/h13-16,19H,4-11H2,1-3H3/t13-,14+,15+,16-,19-,20+,21+/m1/s1'))","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Swapping around the order of the atoms at the start (\"O4H30C21\" instead of \"C21H30O4\") gets you a penalty of 6, but in context then the following connection layer actually does not make so much sense and means something totally different, but that's not penalized that badly in a sense:","metadata":{}},{"cell_type":"code","source":"print( nltk.edit_distance('InChI=1S/C21H30O4/c1-12(22)25-14-6-8-20(2)13(10-14)11-17(23)19-15-4-5-18(24)21(15,3)9-7-16(19)20/h13-16,19H,4-11H2,1-3H3/t13-,14+,15+,16-,19-,20+,21+/m1/s1',\n                          'InChI=1S/O4H30C21/c1-12(22)25-14-6-8-20(2)13(10-14)11-17(23)19-15-4-5-18(24)21(15,3)9-7-16(19)20/h13-16,19H,4-11H2,1-3H3/t13-,14+,15+,16-,19-,20+,21+/m1/s1'))","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# How to easily find some examples you may be looking for?","metadata":{}},{"cell_type":"markdown","source":"Here are 10 examples without a hydrogen layer. E.g. the first one just contain 7 carbon atoms, 5 Br and 1 nitrogen atom.","metadata":{}},{"cell_type":"code","source":"[train_label for train_label in train_labels['InChI'] if \"/h\" in train_label][:10]","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# What if I want to treat this as a language generation task (image captioning)?\n\nThis is also discussed on the forums (see [here](https://www.kaggle.com/c/bms-molecular-translation/discussion/223471)). Here's some considerations:\n* Don't do things you don't need to do. Sure, you can teach a language model that every string starts with **\"InChI=1S/\"**, but you might just as well concatenate that on.\n* You may wish to consider treating the different layers as separate prediction targets, but you probably want to find a way to reflect the interdependence between them (e.g. generating them all together may be an option). If you do a single string generation, then make sure your model is capable of \"remembering\" / \"paying attention to\" the start of the string and other sections that may have occurred a long distance away (in order to generate different layers that are consistent).\n* Put a bit of domain knowledge into how you construct the vocabulary. Maybe the model can learn on its own that \"B\" and \"r\" in \"Br\" belong together and refer to a single atom, but is it not easier if you just tell it this information so it does not have to figure these things out? Particularly, since there's also \"/r\" and there's really no connection between the two \"r\" characters in the two contexts.\n* How to deal appropriately with numbers is another good question. E.g. \"-15-\" refers to 15 and that this is a number - rather than the letters \"1\" and \"5\". If the model does not \"know\" that, it'll be somewhat harder for a model to \"learn\" that 15 atoms are more than 2 and a lot less than 51 (especially, if 51 never appears in the training data). I think there's a decent amount of research suggesting that language models [struggle a bit with understanding numbers when they treat them like any other word in the vocabulary, but that we can improve on that](https://arxiv.org/pdf/1805.08154). \n* Additionally, a number can mean different things in different layers of the InChI string - having a different language model that is treated as having a separate vocabulary may be a good idea. Or perhaps there's some other clever way of handling this.\n\nOf course, we can always hope that given a large amount of training data (in a sense we have that here, but perhaps in a sense not...) a model can just figure out everything on its own, but I'd guess putting a bit of domain knowledge in must help.","metadata":{}},{"cell_type":"markdown","source":"# What could a tokenizer and numericalization look like?\n\nHow do we get language models to work with / output text? The typical steps involve\n1. **tokenization**: Turn the text string into a list of \"tokens\". These are some sensible items that can be used to represent the text. In a normal sentence these could e.g. be words, some sub-words (e.g. syllables) or letters. Here, it may be more obvious to do something slightly different (see below).\n2. **numericalization**: Turning the \"tokens\" into integers, so we can efficiently store them. Each integer then represent a single token, which an embedding layer in a model may then turn into a particular embedding.\n\nHere, we will only deal with the chemical notation part of the target, but the similar ideas transfer to other parts of the InChI string. Default tokenizers for language do not see entirely right, as mentioned above. We presumably want to e.g. keep **Br** together as a single symbol/token (a single atom), but **CH** should end up counting as two separate tokens (i.e. a carbon atom **C** and a hydrogen atom **H**). Similarly, we need to decide what to do about numbers. One option is a tokenizer like the following, which also turns with numericalization:\n* Each atom is an element in our vocabulary and maps to a number from 1 to 11. E.g. we use `2` for carbon and `5` for hydrogen (see code below). \n* The numbers indicating multiple occurences of an atom results in the atom being repeated. E.g. for C2H4 (ethylene), we would generate the tokenized sequence `CCHHHH` and turn this into the numerical list `[2,2,5,5,5,5]`. That's my attempt to work around how to deal with the numbers in the strings. We just need to make sure our code appropriately deals with the case of a single atom, where the string will not have a number.\n* We also have an end of string token that we numerically represent as `12`.\n* We have a padding token that we numerically represent as `13` so that we can save all the resulting processed data in a saved numpy array we can easily read (or load) into memory as needed. Perhaps you can deal with this bit differently, but it seems to work okay for me.\n* If we use a list of length 10, then ethylene (C2H4) would end up being encoded as `[2,2,5,5,5,5,12,13,13,13]`. Obviously, we will have a lot more padding with the real data, because some molecules are pretty big.\n\nAfter generating these outputs, we will have then to post-process them into the InChI format.","metadata":{}},{"cell_type":"code","source":"# Let's free up some memory:\nimport gc\ndel train_labels, image_id, file_path, layers, train, out1, image_info, mylistlist, sorted_unique, number_of_uniques, mydictlist, tmplist, count_data, atomic_mass, fewest_atoms, most_atoms\ngc.collect()","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd \nimport re\nimport fastcore\nfrom fastcore.parallel import parallel","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_data = pd.read_csv('../input/bms-molecular-translation/train_labels.csv')\nlayers = ['version', 'chemical_notation']\nfor layerid, layer in enumerate(layers):\n    train_data[layer] = [ (train_data.split(\"/\"))[layerid] for train_data in train_data['InChI']]\n    \natoms=['B', 'Br', 'C', 'Cl', 'F', 'H', 'I', 'N', 'O', 'P', 'S', 'Si']\natomvocab={'B':0, 'Br':1, 'C':2, 'Cl':3, 'F':4, 'H':5, 'I':6, 'N':7, 'O':8, 'P':9, 'S':10, 'Si':11}\nvocabatom={0:'B', 1:'Br', 2:'C', 3:'Cl', 4:'F', 5:'H', 6:'I', 7:'N', 8:'O', 9:'P', 10:'S', 11:'Si', 12:'', 13:''}","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def tokenize_one_letter_number(subsplit):    \n    split0 = re.findall('[A-Z][a-z]*', subsplit)[0]\n    split1 = re.findall('[0-9]*', subsplit)\n    #print(f'{split0} :: {split1}')    \n    howmany = max([int(s1) for s1 in split1 if s1!=''] + [1])\n    return [atomvocab[split0]] * howmany\n        \ndef tokenize_string(mystring):\n    split = re.findall( '[A-Z][a-z]*[0-9]*', mystring)    \n    return sum([tokenize_one_letter_number(subsplit) for subsplit in split], [])\n\n# # Example of how this function works\n# tokenize_string(train_data['chemical_notation'].values[0])\n","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"tokenized_chemno = parallel(tokenize_string, list(train_data['chemical_notation'].values), \n                            n_workers=4, progress=True)","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Pre-fill array with padding (13)\ntokarr_chemno = np.full(shape=[len(tokenized_chemno),\n                               max([len(tc) for tc in tokenized_chemno])+1],\n                        fill_value=13, dtype=np.uint8)\nfor i,j in enumerate(tokenized_chemno):\n    tokarr_chemno[i][0:len(j)] = j\n    # Add end of string\n    tokarr_chemno[i][len(j)] = 12","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"np.save(\"tokarr_chemno.npy\", tokarr_chemno)","metadata":{},"execution_count":null,"outputs":[]}]}