{"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":"Opening credits - please upvote the original authors :\n1. https://www.kaggle.com/yasufuminakama/inchi-resnet-lstm-with-attention-inference (obviously ;-)\n2. https://www.kaggle.com/c/bms-molecular-translation/discussion/231400#1267490 - for the great idea about speeding up inference\n\nTraining does take a while, so you need either powerful hardware or a lot of patience (ideally both ;-) In order to level the playing field, I attach weights for a model: EfNetB1, image size 288, trained for 5 epochs - using the code given here:\n\nhttps://www.kaggle.com/konradb/model-train-efficientnet\n","metadata":{}},{"cell_type":"markdown","source":"# Preparations\n","metadata":{"id":"IYX2BqRuskPy"}},{"cell_type":"code","source":"import os\nfrom matplotlib import pyplot as plt\n\nOUTPUT_DIR = './'\nif not os.path.exists(OUTPUT_DIR):\n    os.makedirs(OUTPUT_DIR)\n\n\nimport sys\nsys.path.append('../input/pytorch-image-models/pytorch-image-models-master')\n\nimport numpy as np\nimport pandas as pd\nimport torch\n\nimport os\nimport gc\nimport re\nimport math\nimport time\nimport random\nimport shutil\nimport pickle\nfrom pathlib import Path\nfrom contextlib import contextmanager\nfrom collections import defaultdict, Counter\n\nimport scipy as sp\nimport numpy as np\nimport pandas as pd\nfrom tqdm.auto import tqdm\n\nimport Levenshtein\nfrom sklearn import preprocessing\nfrom sklearn.model_selection import StratifiedKFold, GroupKFold, KFold\n\nfrom functools import partial\n\nimport cv2\nfrom PIL import Image\n\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.optim import Adam, SGD\nimport torchvision.models as models\nfrom torch.nn.parameter import Parameter\nfrom torch.utils.data import DataLoader, Dataset\nfrom torch.nn.utils.rnn import pad_sequence, pack_padded_sequence\nfrom torch.optim.lr_scheduler import CosineAnnealingWarmRestarts, CosineAnnealingLR, ReduceLROnPlateau\n\nfrom albumentations import (\n    Compose, OneOf, Normalize, Resize, RandomResizedCrop, RandomCrop, HorizontalFlip, VerticalFlip, \n    RandomBrightness, RandomContrast, RandomBrightnessContrast, Rotate, ShiftScaleRotate, Cutout, \n    IAAAdditiveGaussianNoise, Transpose, Blur, RandomRotate90\n    )\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2\nfrom albumentations import ImageOnlyTransform\n\nimport timm\n\nimport warnings \nwarnings.filterwarnings('ignore')\n\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')","metadata":{"id":"F-xiBU7GE4uE"},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class CFG:\n    debug =  False\n    max_len = 275\n    print_freq = 5000\n    num_workers=4\n    model_name = 'efficientnet_b1'\n    enc_size = 1280\n    size = 288\n    scheduler='CosineAnnealingLR' \n    epochs = 1\n    T_max=4 \n    encoder_lr = 1e-3 # 1e-4\n    decoder_lr = 1e-3 # 4e-4\n    min_lr = 1e-6\n    batch_size = 32\n    weight_decay = 1e-6\n    gradient_accumulation_steps = 1\n    max_grad_norm=5\n    attention_dim=256\n    embed_dim=256\n    decoder_dim=512\n    dropout=0.5\n    seed=42\n    n_fold=5\n    trn_fold = [1] \n    train= False\n    train_path = \"../input/bms-molecular-translation/test/\"\n    prep_path = '../input/preprocessed-stuff/'\n    pred_model = '../input/fitted-model/efficientnet_b1_fold1_best_ep5.pth'","metadata":{"id":"IcNS8Wm1E8ia"},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Functions","metadata":{"id":"gCpL48SAFkhI"}},{"cell_type":"code","source":"\ndef get_test_file_path(image_id):\n\n    return CFG.train_path + \"{}/{}/{}/{}.png\".format(\n        image_id[0], image_id[1], image_id[2], image_id \n    )","metadata":{"id":"pLeT9a8vFmSl"},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class Tokenizer(object):\n    \n    def __init__(self):\n        self.stoi = {}\n        self.itos = {}\n\n    def __len__(self):\n        return len(self.stoi)\n    \n    def fit_on_texts(self, texts):\n        vocab = set()\n        for text in texts:\n            vocab.update(text.split(' '))\n        vocab = sorted(vocab)\n        vocab.append('<sos>')\n        vocab.append('<eos>')\n        vocab.append('<pad>')\n        for i, s in enumerate(vocab):\n            self.stoi[s] = i\n        self.itos = {item[1]: item[0] for item in self.stoi.items()}\n        \n    def text_to_sequence(self, text):\n        sequence = []\n        sequence.append(self.stoi['<sos>'])\n        for s in text.split(' '):\n            sequence.append(self.stoi[s])\n        sequence.append(self.stoi['<eos>'])\n        return sequence\n    \n    def texts_to_sequences(self, texts):\n        sequences = []\n        for text in texts:\n            sequence = self.text_to_sequence(text)\n            sequences.append(sequence)\n        return sequences\n\n    def sequence_to_text(self, sequence):\n        return ''.join(list(map(lambda i: self.itos[i], sequence)))\n    \n    def sequences_to_texts(self, sequences):\n        texts = []\n        for sequence in sequences:\n            text = self.sequence_to_text(sequence)\n            texts.append(text)\n        return texts\n    \n    def predict_caption(self, sequence):\n        caption = ''\n        for i in sequence:\n            if i == self.stoi['<eos>'] or i == self.stoi['<pad>']:\n                break\n            caption += self.itos[i]\n        return caption\n    \n    def predict_captions(self, sequences):\n        captions = []\n        for sequence in sequences:\n            caption = self.predict_caption(sequence)\n            captions.append(caption)\n        return captions\n\ntokenizer = torch.load(CFG.prep_path + 'tokenizer2.pth')\nprint(f\"tokenizer.stoi: {tokenizer.stoi}\")","metadata":{"execution":{"iopub.execute_input":"2021-04-05T21:17:08.742606Z","iopub.status.busy":"2021-04-05T21:17:08.737206Z","iopub.status.idle":"2021-04-05T21:17:08.757816Z","shell.execute_reply":"2021-04-05T21:17:08.75737Z"},"papermill":{"duration":0.041233,"end_time":"2021-04-05T21:17:08.757926","exception":false,"start_time":"2021-04-05T21:17:08.716693","status":"completed"},"tags":[],"id":"ZYYdUrWUsZLT","outputId":"a20b3a36-7e7f-4c2e-d988-65e8ecde7734"},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# ====================================================\n# Utils\n# ====================================================\ndef get_score(y_true, y_pred):\n    scores = []\n    for true, pred in zip(y_true, y_pred):\n        score = Levenshtein.distance(true, pred)\n        scores.append(score)\n    avg_score = np.mean(scores)\n    return avg_score\n\n\ndef init_logger(log_file='inference.log'):\n    from logging import getLogger, INFO, FileHandler,  Formatter,  StreamHandler\n    logger = getLogger(__name__)\n    logger.setLevel(INFO)\n    handler1 = StreamHandler()\n    handler1.setFormatter(Formatter(\"%(message)s\"))\n    handler2 = FileHandler(filename=log_file)\n    handler2.setFormatter(Formatter(\"%(message)s\"))\n    logger.addHandler(handler1)\n    logger.addHandler(handler2)\n    return logger\n\nLOGGER = init_logger()\n\n\ndef seed_torch(seed=42):\n    random.seed(seed)\n    os.environ['PYTHONHASHSEED'] = str(seed)\n    np.random.seed(seed)\n    torch.manual_seed(seed)\n    torch.cuda.manual_seed(seed)\n    torch.backends.cudnn.deterministic = False # True\n\nseed_torch(seed=CFG.seed)","metadata":{"execution":{"iopub.execute_input":"2021-04-05T21:17:12.38899Z","iopub.status.busy":"2021-04-05T21:17:12.388428Z","iopub.status.idle":"2021-04-05T21:17:12.39424Z","shell.execute_reply":"2021-04-05T21:17:12.393791Z"},"papermill":{"duration":0.026849,"end_time":"2021-04-05T21:17:12.394342","exception":false,"start_time":"2021-04-05T21:17:12.367493","status":"completed"},"tags":[],"id":"W8bS8LYOsZLV"},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# ====================================================\n# Dataset\n# ====================================================\nclass TestDataset(Dataset):\n    def __init__(self, df, transform=None):\n        super().__init__()\n        self.df = df\n        self.file_paths = df['file_path'].values\n        self.transform = transform\n        self.fix_transform = A.Compose([A.Transpose(p=1), A.VerticalFlip(p=1)])\n    \n    def __len__(self):\n        return len(self.df)\n    \n    def __getitem__(self, idx):\n        file_path = self.file_paths[idx]\n        image = cv2.imread(file_path)\n        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB).astype(np.float32)\n        h, w, _ = image.shape\n        if h > w:\n            image = self.fix_transform(image=image)['image']\n        if self.transform:\n            augmented = self.transform(image=image)\n            image = augmented['image']\n        return image","metadata":{"execution":{"iopub.execute_input":"2021-04-05T21:17:17.032482Z","iopub.status.busy":"2021-04-05T21:17:17.031683Z","iopub.status.idle":"2021-04-05T21:17:17.034923Z","shell.execute_reply":"2021-04-05T21:17:17.034473Z"},"papermill":{"duration":0.036775,"end_time":"2021-04-05T21:17:17.035055","exception":false,"start_time":"2021-04-05T21:17:16.99828","status":"completed"},"tags":[],"id":"WhVNJ_iisZLX"},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def get_transforms(*, data):\n    \n    if data == 'train':\n        return A.Compose([\n            A.Resize(CFG.size, CFG.size),\n            A.Normalize(\n                mean=[0.485, 0.456, 0.406],\n                std=[0.229, 0.224, 0.225],\n            ),\n            ToTensorV2(),\n        ])\n    \n    elif data == 'valid':\n        return A.Compose([\n            A.Resize(CFG.size, CFG.size),\n            A.Normalize(\n                mean=[0.485, 0.456, 0.406],\n                std=[0.229, 0.224, 0.225],\n            ),\n            ToTensorV2(),\n        ])","metadata":{"execution":{"iopub.execute_input":"2021-04-05T21:17:17.141847Z","iopub.status.busy":"2021-04-05T21:17:17.141123Z","iopub.status.idle":"2021-04-05T21:17:17.143932Z","shell.execute_reply":"2021-04-05T21:17:17.143516Z"},"papermill":{"duration":0.033833,"end_time":"2021-04-05T21:17:17.144039","exception":false,"start_time":"2021-04-05T21:17:17.110206","status":"completed"},"tags":[],"id":"61sBOuBmsZLX"},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# MODEL\nclass Encoder(nn.Module):\n    def __init__(self, model_name= CFG.model_name, pretrained=False):\n        super().__init__()\n        self.cnn = timm.create_model(model_name, pretrained=pretrained)\n\n\n    def forward(self, x):\n        bs = x.size(0)\n        features = self.cnn.forward_features(x)\n        features = features.permute(0, 2, 3, 1)\n        return features\n\n\nclass Attention(nn.Module):\n    \"\"\"\n    Attention network for calculate attention value\n    \"\"\"\n    def __init__(self, encoder_dim, decoder_dim, attention_dim):\n        \"\"\"\n        :param encoder_dim: input size of encoder network\n        :param decoder_dim: input size of decoder network\n        :param attention_dim: input size of attention network\n        \"\"\"\n        super(Attention, self).__init__()\n        self.encoder_att = nn.Linear(encoder_dim, attention_dim)  # linear layer to transform encoded image\n        self.decoder_att = nn.Linear(decoder_dim, attention_dim)  # linear layer to transform decoder's output\n        self.full_att = nn.Linear(attention_dim, 1)  # linear layer to calculate values to be softmax-ed\n        self.relu = nn.ReLU()\n        self.softmax = nn.Softmax(dim=1)  # softmax layer to calculate weights\n\n    def forward(self, encoder_out, decoder_hidden):\n        att1 = self.encoder_att(encoder_out)  # (batch_size, num_pixels, attention_dim)\n        att2 = self.decoder_att(decoder_hidden)  # (batch_size, attention_dim)\n        att = self.full_att(self.relu(att1 + att2.unsqueeze(1))).squeeze(2)  # (batch_size, num_pixels)\n        alpha = self.softmax(att)  # (batch_size, num_pixels)\n        attention_weighted_encoding = (encoder_out * alpha.unsqueeze(2)).sum(dim=1)  # (batch_size, encoder_dim)\n        return attention_weighted_encoding, alpha\n\n\nclass DecoderWithAttention(nn.Module):\n    \"\"\"\n    Decoder network with attention network used for training\n    \"\"\"\n\n    def __init__(self, attention_dim, embed_dim, decoder_dim, vocab_size, device, \n                 encoder_dim = CFG.enc_size, dropout = CFG.dropout):\n        \"\"\"\n        :param attention_dim: input size of attention network\n        :param embed_dim: input size of embedding network\n        :param decoder_dim: input size of decoder network\n        :param vocab_size: total number of characters used in training\n        :param encoder_dim: input size of encoder network\n        :param dropout: dropout rate\n        \"\"\"\n        super(DecoderWithAttention, self).__init__()\n        self.encoder_dim = encoder_dim\n        self.attention_dim = attention_dim\n        self.embed_dim = embed_dim\n        self.decoder_dim = decoder_dim\n        self.vocab_size = vocab_size\n        self.dropout = dropout\n        self.device = device\n        self.attention = Attention(encoder_dim, decoder_dim, attention_dim)  # attention network\n        self.embedding = nn.Embedding(vocab_size, embed_dim)  # embedding layer\n        self.dropout = nn.Dropout(p=self.dropout)\n        self.decode_step = nn.LSTMCell(embed_dim + encoder_dim, decoder_dim, bias=True)  # decoding LSTMCell\n        self.init_h = nn.Linear(encoder_dim, decoder_dim)  # linear layer to find initial hidden state of LSTMCell\n        self.init_c = nn.Linear(encoder_dim, decoder_dim)  # linear layer to find initial cell state of LSTMCell\n        self.f_beta = nn.Linear(decoder_dim, encoder_dim)  # linear layer to create a sigmoid-activated gate\n        self.sigmoid = nn.Sigmoid()\n        self.fc = nn.Linear(decoder_dim, vocab_size)  # linear layer to find scores over vocabulary\n        self.init_weights()  # initialize some layers with the uniform distribution\n\n    def init_weights(self):\n        self.embedding.weight.data.uniform_(-0.1, 0.1)\n        self.fc.bias.data.fill_(0)\n        self.fc.weight.data.uniform_(-0.1, 0.1)\n\n    def load_pretrained_embeddings(self, embeddings):\n        self.embedding.weight = nn.Parameter(embeddings)\n\n    def fine_tune_embeddings(self, fine_tune=True):\n        for p in self.embedding.parameters():\n            p.requires_grad = fine_tune\n\n    def init_hidden_state(self, encoder_out):\n        mean_encoder_out = encoder_out.mean(dim=1)\n        h = self.init_h(mean_encoder_out)  # (batch_size, decoder_dim)\n        c = self.init_c(mean_encoder_out)\n        return h, c\n\n    def forward(self, encoder_out, encoded_captions, caption_lengths):\n        \"\"\"\n        :param encoder_out: output of encoder network\n        :param encoded_captions: transformed sequence from character to integer\n        :param caption_lengths: length of transformed sequence\n        \"\"\"\n        batch_size = encoder_out.size(0)\n        encoder_dim = encoder_out.size(-1)\n        vocab_size = self.vocab_size\n        encoder_out = encoder_out.view(batch_size, -1, encoder_dim)  # (batch_size, num_pixels, encoder_dim)\n        num_pixels = encoder_out.size(1)\n        caption_lengths, sort_ind = caption_lengths.squeeze(1).sort(dim=0, descending=True)\n        encoder_out = encoder_out[sort_ind]\n        encoded_captions = encoded_captions[sort_ind]\n        # embedding transformed sequence for vector\n        embeddings = self.embedding(encoded_captions)  # (batch_size, max_caption_length, embed_dim)\n        # initialize hidden state and cell state of LSTM cell\n        h, c = self.init_hidden_state(encoder_out)  # (batch_size, decoder_dim)\n        # set decode length by caption length - 1 because of omitting start token\n        decode_lengths = (caption_lengths - 1).tolist()\n        predictions = torch.zeros(batch_size, max(decode_lengths), vocab_size).to(self.device)\n        alphas = torch.zeros(batch_size, max(decode_lengths), num_pixels).to(self.device)\n        # predict sequence\n        for t in range(max(decode_lengths)):\n            batch_size_t = sum([l > t for l in decode_lengths])\n            attention_weighted_encoding, alpha = self.attention(encoder_out[:batch_size_t], h[:batch_size_t])\n            gate = self.sigmoid(self.f_beta(h[:batch_size_t]))  # gating scalar, (batch_size_t, encoder_dim)\n            attention_weighted_encoding = gate * attention_weighted_encoding\n            h, c = self.decode_step(\n                torch.cat([embeddings[:batch_size_t, t, :], attention_weighted_encoding], dim=1),\n                (h[:batch_size_t], c[:batch_size_t]))  # (batch_size_t, decoder_dim)\n            preds = self.fc(self.dropout(h))  # (batch_size_t, vocab_size)\n            predictions[:batch_size_t, t, :] = preds\n            alphas[:batch_size_t, t, :] = alpha\n        return predictions, encoded_captions, decode_lengths, alphas, sort_ind\n    \n    def predict(self, encoder_out, decode_lengths, tokenizer):\n        batch_size = encoder_out.size(0)\n        encoder_dim = encoder_out.size(-1)\n        vocab_size = self.vocab_size\n        encoder_out = encoder_out.view(batch_size, -1, encoder_dim)  # (batch_size, num_pixels, encoder_dim)\n        num_pixels = encoder_out.size(1)\n        # embed start tocken for LSTM input\n        start_tockens = torch.ones(batch_size, dtype=torch.long).to(self.device) * tokenizer.stoi[\"<sos>\"]\n        embeddings = self.embedding(start_tockens)\n        # initialize hidden state and cell state of LSTM cell\n        h, c = self.init_hidden_state(encoder_out)  # (batch_size, decoder_dim)\n        predictions = torch.zeros(batch_size, decode_lengths, vocab_size).to(self.device)\n\n        # predict sequence -> break if batch EOS speedup \n        # thanks to https://www.kaggle.com/c/bms-molecular-translation/discussion/231400#1267490\n              \n        end_condition = torch.zeros(batch_size, dtype=torch.long).to(encoder_out.device)\n\n        for t in range(decode_lengths):\n            attention_weighted_encoding, alpha = self.attention(encoder_out, h)\n            gate = self.sigmoid(self.f_beta(h))  # gating scalar, (batch_size_t, encoder_dim)\n            attention_weighted_encoding = gate * attention_weighted_encoding\n            h, c = self.decode_step(\n                torch.cat([embeddings, attention_weighted_encoding], dim=1),\n                (h, c))  # (batch_size_t, decoder_dim)\n            preds = self.fc(self.dropout(h))  # (batch_size_t, vocab_size)\n            predictions[:, t, :] = preds\n\n            end_condition |= (torch.argmax(preds, -1) == tokenizer.stoi[\"<eos>\"])\n            if end_condition.sum() == batch_size:\n                break\n            embeddings = self.embedding(torch.argmax(preds, -1))\n        return predictions","metadata":{"id":"Z1Y3HGWIH-4N"},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Data\n","metadata":{"papermill":{"duration":0.013081,"end_time":"2021-04-05T21:17:04.407401","exception":false,"start_time":"2021-04-05T21:17:04.39432","status":"completed"},"tags":[],"id":"VAac3vWNsZLP"}},{"cell_type":"code","source":"test = pd.read_csv('../input/bms-molecular-translation/sample_submission.csv')\n\ntest['file_path'] = test['image_id'].apply(get_test_file_path)\n\nprint(f'test.shape: {test.shape}')\n","metadata":{"_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","execution":{"iopub.execute_input":"2021-04-05T21:17:04.437052Z","iopub.status.busy":"2021-04-05T21:17:04.436371Z","iopub.status.idle":"2021-04-05T21:17:08.70237Z","shell.execute_reply":"2021-04-05T21:17:08.701809Z"},"papermill":{"duration":4.283465,"end_time":"2021-04-05T21:17:08.702597","exception":false,"start_time":"2021-04-05T21:17:04.419132","status":"completed"},"tags":[],"id":"2AXa454CsZLR","outputId":"74b34db0-56b6-4425-abec-86a9bf6b434c"},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Inference","metadata":{"papermill":{"duration":0.025265,"end_time":"2021-04-05T21:17:17.378105","exception":false,"start_time":"2021-04-05T21:17:17.35284","status":"completed"},"tags":[],"id":"_amZeff2sZLd"}},{"cell_type":"code","source":"def inference(test_loader, encoder, decoder, tokenizer, device):\n    encoder.eval()\n    decoder.eval()\n    text_preds = []\n    tk0 = tqdm(test_loader, total=len(test_loader))\n    for images in tk0:\n        images = images.to(device)\n        with torch.no_grad():\n            features = encoder(images)\n            predictions = decoder.predict(features, CFG.max_len, tokenizer)\n        predicted_sequence = torch.argmax(predictions.detach().cpu(), -1).numpy()\n        _text_preds = tokenizer.predict_captions(predicted_sequence)\n        text_preds.append(_text_preds)\n    text_preds = np.concatenate(text_preds)\n    return text_preds","metadata":{"execution":{"iopub.execute_input":"2021-04-05T21:17:17.43475Z","iopub.status.busy":"2021-04-05T21:17:17.434077Z","iopub.status.idle":"2021-04-05T21:17:17.436973Z","shell.execute_reply":"2021-04-05T21:17:17.436581Z"},"papermill":{"duration":0.034109,"end_time":"2021-04-05T21:17:17.437088","exception":false,"start_time":"2021-04-05T21:17:17.402979","status":"completed"},"tags":[],"id":"krcijAplsZLg"},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"states = torch.load(CFG.pred_model, map_location=torch.device('cpu'))\n\nencoder = Encoder(CFG.model_name, pretrained=False)\nencoder.load_state_dict(states['encoder'])\nencoder.to(device)\n\ndecoder = DecoderWithAttention(attention_dim=CFG.attention_dim,\n                               embed_dim=CFG.embed_dim,\n                               decoder_dim=CFG.decoder_dim,\n                               vocab_size=len(tokenizer),\n                               dropout=CFG.dropout,\n                               device=device)\ndecoder.load_state_dict(states['decoder'])\ndecoder.to(device)\n\ndel states; gc.collect()\n\ntest_dataset = TestDataset(test, transform=get_transforms(data='valid'))\ntest_loader = DataLoader(test_dataset, batch_size= 256, shuffle=False, num_workers=CFG.num_workers)\npredictions = inference(test_loader, encoder, decoder, tokenizer, device)\n\ndel test_loader, encoder, decoder, tokenizer; gc.collect()","metadata":{"execution":{"iopub.execute_input":"2021-04-05T21:17:17.548513Z","iopub.status.busy":"2021-04-05T21:17:17.54804Z","iopub.status.idle":"2021-04-06T01:19:58.437328Z","shell.execute_reply":"2021-04-06T01:19:58.436799Z"},"papermill":{"duration":14560.921319,"end_time":"2021-04-06T01:19:58.438486","exception":false,"start_time":"2021-04-05T21:17:17.517167","status":"completed"},"tags":[],"id":"0oROoP_osZLh","outputId":"43de7e30-f72b-4512-ac3f-059c27e581ee"},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# submission\ntest['InChI'] = [f\"InChI=1S/{text}\" for text in predictions]\ntest[['image_id', 'InChI']].to_csv('submission.csv', index=False)","metadata":{"execution":{"iopub.execute_input":"2021-04-06T01:19:58.545872Z","iopub.status.busy":"2021-04-06T01:19:58.530406Z","iopub.status.idle":"2021-04-06T01:20:13.992637Z","shell.execute_reply":"2021-04-06T01:20:13.992184Z"},"papermill":{"duration":15.527685,"end_time":"2021-04-06T01:20:13.99277","exception":false,"start_time":"2021-04-06T01:19:58.465085","status":"completed"},"tags":[],"id":"MiEZ2pbYsZLh"},"execution_count":null,"outputs":[]}]}