{"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":"code","source":"import os\nimport numpy as np\nimport pandas as pd\n\ntest_images = 0\n\nos.chdir('/kaggle/input/bms-molecular-translation')\nprint(os.listdir())","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2022-06-02T23:28:30.659661Z","iopub.execute_input":"2022-06-02T23:28:30.660273Z","iopub.status.idle":"2022-06-02T23:28:30.686622Z","shell.execute_reply.started":"2022-06-02T23:28:30.660137Z","shell.execute_reply":"2022-06-02T23:28:30.685766Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Carregando Datasets","metadata":{}},{"cell_type":"code","source":"train_df = pd.read_csv('./train_labels.csv')\nsample_df = pd.read_csv('./sample_submission.csv')\neai_df = pd.read_csv('./extra_approved_InChIs.csv')","metadata":{"execution":{"iopub.status.busy":"2022-06-02T23:28:30.688652Z","iopub.execute_input":"2022-06-02T23:28:30.689187Z","iopub.status.idle":"2022-06-02T23:29:20.334312Z","shell.execute_reply.started":"2022-06-02T23:28:30.689146Z","shell.execute_reply":"2022-06-02T23:29:20.329452Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_df.head(3)","metadata":{"execution":{"iopub.status.busy":"2022-06-02T23:29:20.342737Z","iopub.execute_input":"2022-06-02T23:29:20.34351Z","iopub.status.idle":"2022-06-02T23:29:20.414062Z","shell.execute_reply.started":"2022-06-02T23:29:20.343469Z","shell.execute_reply":"2022-06-02T23:29:20.413041Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"plt.imshow(cv2.imread('train/0/0/0/000011a64c74.png'))","metadata":{"execution":{"iopub.status.busy":"2022-06-03T00:12:16.383571Z","iopub.execute_input":"2022-06-03T00:12:16.383912Z","iopub.status.idle":"2022-06-03T00:12:16.563719Z","shell.execute_reply.started":"2022-06-03T00:12:16.383876Z","shell.execute_reply":"2022-06-03T00:12:16.562912Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"sample_df.head(3)","metadata":{"execution":{"iopub.status.busy":"2022-06-02T23:29:20.423305Z","iopub.execute_input":"2022-06-02T23:29:20.423833Z","iopub.status.idle":"2022-06-02T23:29:20.454999Z","shell.execute_reply.started":"2022-06-02T23:29:20.423718Z","shell.execute_reply":"2022-06-02T23:29:20.451743Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# ","metadata":{}},{"cell_type":"code","source":"eai_df.head(3)","metadata":{"execution":{"iopub.status.busy":"2022-06-02T23:29:20.459993Z","iopub.execute_input":"2022-06-02T23:29:20.460923Z","iopub.status.idle":"2022-06-02T23:29:20.494188Z","shell.execute_reply.started":"2022-06-02T23:29:20.460789Z","shell.execute_reply":"2022-06-02T23:29:20.489039Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_df.shape","metadata":{"execution":{"iopub.status.busy":"2022-06-02T23:29:20.500214Z","iopub.execute_input":"2022-06-02T23:29:20.503195Z","iopub.status.idle":"2022-06-02T23:29:20.530743Z","shell.execute_reply.started":"2022-06-02T23:29:20.502965Z","shell.execute_reply":"2022-06-02T23:29:20.527852Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def get_path_by_filename(filename, train=True):\n    prefix = 'train' if train else 'test'\n    return './{}/{}/{}/{}/{}.png'.format(\n        prefix, filename[0], filename[1], filename[2], filename\n    )\n\ntrain_df['path'] = train_df['image_id'].apply(get_path_by_filename)\ntrain_df.head(3)","metadata":{"execution":{"iopub.status.busy":"2022-06-02T23:29:20.534786Z","iopub.execute_input":"2022-06-02T23:29:20.536139Z","iopub.status.idle":"2022-06-02T23:29:22.846995Z","shell.execute_reply.started":"2022-06-02T23:29:20.536091Z","shell.execute_reply":"2022-06-02T23:29:22.846185Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Visualizando os Dados","metadata":{}},{"cell_type":"code","source":"import matplotlib.pyplot as plt\nimport cv2\n\ndef plot_molecules(images):\n    length = len(images)\n    width = int(length ** .5)\n    height = int(length / width)\n    fig, axs = plt.subplots(width, height, figsize=(5 * width, 3 * height))\n    fig.subplots_adjust(hspace=.2, wspace=.2)\n    axs = axs.ravel()\n    for index, (image_path, inChI) in enumerate(images):\n        title = inChI[:20]\n        img = cv2.imread(image_path)\n        axs[index].imshow(img)\n        axs[index].set_title('{}...'.format(title))\n        axs[index].set_xticklabels([])\n        axs[index].set_yticklabels([])\n    plt.show()","metadata":{"execution":{"iopub.status.busy":"2022-06-02T23:29:22.848608Z","iopub.execute_input":"2022-06-02T23:29:22.849091Z","iopub.status.idle":"2022-06-02T23:29:23.098453Z","shell.execute_reply.started":"2022-06-02T23:29:22.84905Z","shell.execute_reply":"2022-06-02T23:29:23.097641Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def get_molecule_tuple(row):\n    return (row['path'], row['InChI'])\n\nsample_images = list(\n    map(\n        lambda index: get_molecule_tuple(train_df.iloc[index]),\n        range(25)\n    )\n)\n\nplot_molecules(sample_images)","metadata":{"execution":{"iopub.status.busy":"2022-06-02T23:29:23.099694Z","iopub.execute_input":"2022-06-02T23:29:23.100472Z","iopub.status.idle":"2022-06-02T23:29:25.839961Z","shell.execute_reply.started":"2022-06-02T23:29:23.100436Z","shell.execute_reply":"2022-06-02T23:29:25.839316Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Rotulando as Imagens\n\nOs rótulos consistem em camadas e subcamadas que são separadas pelo delimitador \"/\" e começam com uma letra de prefixo característica.\nAs seis camadas com subcamadas importantes são:\n\n1) Camada principal\n\n* Fórmula química (sem prefixo). Esta é a única subcamada que deve ocorrer em cada InChI.\n* Conexões Atom (prefixo: \"c\"). Os átomos na fórmula química (exceto hidrogênios) são numerados em sequência; essa subcamada descreve quais átomos estão conectados por ligações a quais outros.\n* Átomos de hidrogênio (prefixo: \"h\"). Descreve quantos átomos de hidrogênio estão conectados a cada um dos outros átomos.\n\n2) Camada de carga\n   \n* subcamada de carga (prefixo: \"q\")\n* subcamada de prótons (prefixo: \"p\" para \"prótons\")\n\n3) Camada estereoquímica\n   \n* ligações duplas e cumulenos (prefixo: \"b\")\n* estereoquímica tetraédrica de átomos e alenos (prefixos: \"t\", \"m\")\n* tipo de informação estereoquímica (prefixo: \"s\")\n4) Camada isotópica (prefixos: \"i\", \"h\", bem como \"b\", \"t\", \"m\", \"s\" para estereoquímica isotópica)\n\n5) Camada H Fixo (prefixo: \"f\"); contém alguns ou todos os tipos de camadas acima, exceto conexões atômicas; pode terminar com a subcamada \"o\"; nunca incluído no padrão InChI\n\n6) Camada reconectada (prefixo: \"r\"); contém todo o InChI de uma estrutura com átomos metálicos reconectados; nunca incluído no padrão InChI","metadata":{}},{"cell_type":"code","source":"train_df['molecule'] = train_df['InChI'].apply(lambda inChI: inChI.split('/')[1])\ntrain_df['length'] = train_df['molecule'].apply(lambda molecule: len(molecule))\n\ntrain_df.head(3)","metadata":{"execution":{"iopub.status.busy":"2022-06-02T23:29:25.842171Z","iopub.execute_input":"2022-06-02T23:29:25.842629Z","iopub.status.idle":"2022-06-02T23:29:28.512767Z","shell.execute_reply.started":"2022-06-02T23:29:25.842597Z","shell.execute_reply":"2022-06-02T23:29:28.512231Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"chars = set()\nfor molecule in train_df['molecule']:\n    for char in molecule:\n        chars.add(char)\nchars","metadata":{"execution":{"iopub.status.busy":"2022-06-02T23:29:28.513909Z","iopub.execute_input":"2022-06-02T23:29:28.514375Z","iopub.status.idle":"2022-06-02T23:29:31.269142Z","shell.execute_reply.started":"2022-06-02T23:29:28.514346Z","shell.execute_reply":"2022-06-02T23:29:31.268353Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"char_to_label = {char:label for label,char in enumerate(chars)}\nlabel_to_char = {label:char for label,char in enumerate(chars)}\nlabel_to_char[100] = ''","metadata":{"execution":{"iopub.status.busy":"2022-06-02T23:29:31.27043Z","iopub.execute_input":"2022-06-02T23:29:31.270794Z","iopub.status.idle":"2022-06-02T23:29:31.276354Z","shell.execute_reply.started":"2022-06-02T23:29:31.270751Z","shell.execute_reply":"2022-06-02T23:29:31.275393Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"char_to_label","metadata":{"execution":{"iopub.status.busy":"2022-06-03T00:52:47.735448Z","iopub.execute_input":"2022-06-03T00:52:47.735811Z","iopub.status.idle":"2022-06-03T00:52:47.742324Z","shell.execute_reply.started":"2022-06-03T00:52:47.735773Z","shell.execute_reply":"2022-06-03T00:52:47.741467Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Formatando Dados","metadata":{}},{"cell_type":"code","source":"from tensorflow.keras.utils import Sequence\n    \n    \nclass GenerateDataForCTCFormat(Sequence):\n    def __init__(self, df, char_map, batch_size=16, w=200, h=50, downsample_factor=4, max_length=20, shuffle=True):\n        self.dataframe = df\n        self.char_map = char_map\n        self.batch_size = batch_size\n        self.width = w\n        self.height = h\n        self.downsample_factor = downsample_factor\n        self.max_length = max_length\n        self.shuffle = shuffle\n        self.indices = np.arange(len(df))\n        self.on_epoch_end()\n        \n    def process_image(self, path):\n        kernel_size = (2, 2)\n        width = self.width\n        height = self.height\n        \n        img = cv2.imread(path)\n        img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)\n        img = cv2.erode(img, (kernel_size))\n        img = cv2.resize(img, (width, height))\n        img = img / 255 # Normalization\n        img = img.T\n        return np.expand_dims(img, axis=-1)\n        \n    def __len__(self):\n        return len(self.dataframe)//self.batch_size\n    \n    def __getitem__(self, index):\n        batch_size = self.batch_size\n        width = self.width\n        height = self.height\n        length = self.max_length\n        downsample_factor = self.downsample_factor\n        df = self.dataframe\n        char_map = self.char_map\n        \n        next_index = index + 1\n        \n        current_batch_index = self.indices[index * batch_size:next_index * batch_size]\n        \n        batch_images = np.ones((batch_size, width, height, 1), dtype=np.float32)\n        batch_labels = np.ones((batch_size, length), dtype=np.float32)\n        input_length = np.ones((batch_size, 1), dtype=np.float32) * (width // downsample_factor - 2)\n        label_length = np.zeros((batch_size, 1), dtype=np.int64)\n        \n        for i, index in enumerate(current_batch_index):\n            img = self.process_image(df['path'].values[index])\n            molecule = df['molecule'].values[index]\n            label = [char_map[letter] for letter in molecule]\n            label.extend([100] * (20 - len(label)))\n            \n            batch_images[i] = img\n            batch_labels[i] = label\n            label_length[i] = len(label)\n            \n        batch_inputs = {\n            'input_data': batch_images,\n            'input_label': batch_labels,\n            'input_length': input_length,\n            'label_length': label_length,\n        }\n        \n        return (batch_inputs, np.zeros((batch_size), dtype=np.float32))\n\n    def on_epoch_end(self):\n        if self.shuffle: np.random.shuffle(self.indices)","metadata":{"execution":{"iopub.status.busy":"2022-06-02T23:29:31.277407Z","iopub.execute_input":"2022-06-02T23:29:31.277617Z","iopub.status.idle":"2022-06-02T23:29:37.596331Z","shell.execute_reply.started":"2022-06-02T23:29:31.277591Z","shell.execute_reply":"2022-06-02T23:29:37.595419Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_size = 150000\nvalidation_size = 10000\n\ntrain = GenerateDataForCTCFormat(train_df[:train_size], char_to_label)\nvalidation = GenerateDataForCTCFormat(train_df[train_size:validation_size], char_to_label)","metadata":{"execution":{"iopub.status.busy":"2022-06-02T23:29:37.597684Z","iopub.execute_input":"2022-06-02T23:29:37.597933Z","iopub.status.idle":"2022-06-02T23:29:37.607929Z","shell.execute_reply.started":"2022-06-02T23:29:37.597903Z","shell.execute_reply":"2022-06-02T23:29:37.607113Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"raw","source":"","metadata":{}},{"cell_type":"markdown","source":"## CTC","metadata":{}},{"cell_type":"code","source":"import keras\n\nclass CTCLayer(keras.layers.Layer):\n    def __init__(self, name=None):\n        super().__init__(name=name)\n        self.loss_fn = keras.backend.ctc_batch_cost\n    \n    def call(self, y_true, y_pred, input_length, label_length):\n        loss = self.loss_fn(y_true, y_pred, input_length, label_length)\n        self.add_loss(loss)\n        return loss\n","metadata":{"execution":{"iopub.status.busy":"2022-06-02T23:29:37.609145Z","iopub.execute_input":"2022-06-02T23:29:37.609869Z","iopub.status.idle":"2022-06-02T23:29:37.626987Z","shell.execute_reply.started":"2022-06-02T23:29:37.609825Z","shell.execute_reply":"2022-06-02T23:29:37.626184Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Modelando","metadata":{}},{"cell_type":"code","source":"import tensorflow as tf\n\ndef make_model():\n    entry = keras.layers.Input(shape=(200, 50, 1), dtype=np.float32, name='input_data')\n    labels = keras.layers.Input(shape=[5], dtype=np.float32, name='input_label')\n    input_length = keras.layers.Input(shape=[1], dtype=np.int64, name='input_length')\n    label_length = keras.layers.Input(shape=[1], dtype=np.int64, name='label_length')\n    \n    x = keras.layers.Conv2D(\n        32, (3, 3), activation='relu', padding='same', kernel_initializer='he_normal'\n    )(entry)\n    x = keras.layers.MaxPooling2D(pool_size=(2, 2))(x)\n    x = keras.layers.Conv2D(\n        64, (3, 3), activation='relu', padding='same', kernel_initializer='he_normal'\n    )(x)\n    x = keras.layers.MaxPooling2D(pool_size=(2, 2))(x)\n    x = keras.layers.Reshape((50, 768))(x)\n    x = keras.layers.Dense(64, activation='relu')(x)\n    x = keras.layers.Dropout(.4)(x)\n    x = keras.layers.Bidirectional(\n        keras.layers.LSTM(128, return_sequences=True, dropout=.2)\n    )(x)\n    x = keras.layers.Bidirectional(\n        keras.layers.LSTM(64, return_sequences=True, dropout=.25)\n    )(x)\n    x = keras.layers.Dense(\n        len(chars) + 1, activation='softmax', kernel_initializer='he_normal', name='dense_output'\n    )(x)\n    output = CTCLayer(name='outputs')(labels, x, input_length, label_length)\n    model = keras.models.Model(\n        [entry, labels, input_length, label_length], output\n    )\n    sgd = tf.keras.optimizers.SGD(\n        learning_rate=.0015,\n        decay=.000001,\n        nesterov=True,\n        clipnorm=5,\n    )\n    model.compile(optimizer=sgd)\n    return model\n    ","metadata":{"execution":{"iopub.status.busy":"2022-06-02T23:29:37.628107Z","iopub.execute_input":"2022-06-02T23:29:37.628371Z","iopub.status.idle":"2022-06-02T23:29:37.639438Z","shell.execute_reply.started":"2022-06-02T23:29:37.628326Z","shell.execute_reply":"2022-06-02T23:29:37.638723Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"model = make_model()\nmodel.summary()","metadata":{"execution":{"iopub.status.busy":"2022-06-02T23:29:37.640589Z","iopub.execute_input":"2022-06-02T23:29:37.640934Z","iopub.status.idle":"2022-06-02T23:29:38.938911Z","shell.execute_reply.started":"2022-06-02T23:29:37.640907Z","shell.execute_reply":"2022-06-02T23:29:38.938047Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"es = keras.callbacks.EarlyStopping(\n    monitor='val_loss',\n    patience=2,\n    restore_best_weights=True\n)","metadata":{"execution":{"iopub.status.busy":"2022-06-02T23:29:38.939992Z","iopub.execute_input":"2022-06-02T23:29:38.940395Z","iopub.status.idle":"2022-06-02T23:29:38.944461Z","shell.execute_reply.started":"2022-06-02T23:29:38.940361Z","shell.execute_reply":"2022-06-02T23:29:38.943628Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"model_fit = model.fit(\n    train,\n    validation_data=validation,\n    steps_per_epoch=1500,\n    epochs=8,\n    callbacks=[es]\n)\n\nmodel.save('model')","metadata":{"execution":{"iopub.status.busy":"2022-06-02T23:29:38.945522Z","iopub.execute_input":"2022-06-02T23:29:38.945729Z","iopub.status.idle":"2022-06-02T23:38:19.912725Z","shell.execute_reply.started":"2022-06-02T23:29:38.945704Z","shell.execute_reply":"2022-06-02T23:38:19.91098Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"model_fit","metadata":{"execution":{"iopub.status.busy":"2022-06-02T23:38:19.914048Z","iopub.status.idle":"2022-06-02T23:38:19.915Z","shell.execute_reply.started":"2022-06-02T23:38:19.91478Z","shell.execute_reply":"2022-06-02T23:38:19.914804Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]}]}