{"cells":[{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load in \n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the \"../input/\" directory.\n# For example, running this (by clicking run or pressing Shift+Enter) will list the files in the input directory\n\nimport os\nprint(os.listdir(\"../input\"))\n\n# Any results you write to the current directory are saved as output.","execution_count":null,"outputs":[]},{"metadata":{"_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","trusted":true},"cell_type":"code","source":"import torch\nimport torch.nn as nn\nfrom torch.nn.utils.rnn import pack_padded_sequence, pad_packed_sequence\nimport torch.nn.functional as F\nimport torchtext\nimport nltk\nimport time\nfrom datetime import timedelta\nimport numpy as np\nfrom sklearn import metrics\n\n\ndef save_model(model, model_path):\n    \"\"\"Save model.\"\"\"\n    torch.save(model.state_dict(), model_path)\n\n\ndef load_model(model, model_path, use_cuda=False):\n    \"\"\"Load model.\"\"\"\n    map_location = 'cpu'\n    if use_cuda and torch.cuda.is_available():\n        map_location = 'cuda:0'\n    model.load_state_dict(torch.load(model_path, map_location))\n    return model","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"bccc89a754efcf10cc625fd7bdad16aa1f71570b"},"cell_type":"markdown","source":"## Define model, trainer, predictor"},{"metadata":{"trusted":true,"_uuid":"f2a8f53704c100e87d5733f8bf72e5024f5e04c7"},"cell_type":"code","source":"NEG_INF = -10000\nTINY_FLOAT = 1e-6\n\ndef mask_softmax(matrix, mask=None):\n    \"\"\"Perform softmax on length dimension with masking.\n\n    Parameters\n    ----------\n    matrix: torch.float, shape [batch_size, .., max_len]\n    mask: torch.long, shape [batch_size, max_len]\n        Mask tensor for sequence.\n\n    Returns\n    -------\n    output: torch.float, shape [batch_size, .., max_len]\n        Normalized output in length dimension.\n    \"\"\"\n\n    if mask is None:\n        result = F.softmax(matrix, dim=-1)\n    else:\n        mask_norm = ((1 - mask) * NEG_INF).to(matrix)\n        for i in range(matrix.dim() - mask_norm.dim()):\n            mask_norm = mask_norm.unsqueeze(1)\n        result = F.softmax(matrix + mask_norm, dim=-1)\n\n    return result\n\n\ndef mask_mean(seq, mask=None):\n    \"\"\"Compute mask average on length dimension.\n\n    Parameters\n    ----------\n    seq : torch.float, size [batch, max_seq_len, n_channels],\n        Sequence vector.\n    mask : torch.long, size [batch, max_seq_len],\n        Mask vector, with 0 for mask.\n\n    Returns\n    -------\n    mask_mean : torch.float, size [batch, n_channels]\n        Mask mean of sequence.\n    \"\"\"\n\n    if mask is None:\n        return torch.mean(seq, dim=1)\n\n    mask_sum = torch.sum(  # [b,msl,nc]->[b,nc]\n        seq * mask.unsqueeze(-1).float(), dim=1)\n    seq_len = torch.sum(mask, dim=-1)  # [b]\n    mask_mean = mask_sum / (seq_len.unsqueeze(-1).float() + TINY_FLOAT)\n\n    return mask_mean\n\n\ndef mask_max(seq, mask=None):\n    \"\"\"Compute mask max on length dimension.\n\n    Parameters\n    ----------\n    seq : torch.float, size [batch, max_seq_len, n_channels],\n        Sequence vector.\n    mask : torch.long, size [batch, max_seq_len],\n        Mask vector, with 0 for mask.\n\n    Returns\n    -------\n    mask_max : torch.float, size [batch, n_channels]\n        Mask max of sequence.\n    \"\"\"\n\n    if mask is None:\n        return torch.mean(seq, dim=1)\n\n    torch\n    mask_max, _ = torch.max(  # [b,msl,nc]->[b,nc]\n        seq + (1 - mask.unsqueeze(-1).float()) * NEG_INF,\n        dim=1)\n\n    return mask_max\n\n\ndef seq_mask(seq_len, max_len):\n    \"\"\"Create sequence mask.\n\n    Parameters\n    ----------\n    seq_len: torch.long, shape [batch_size],\n        Lengths of sequences in a batch.\n    max_len: int\n        The maximum sequence length in a batch.\n\n    Returns\n    -------\n    mask: torch.long, shape [batch_size, max_len]\n        Mask tensor for sequence.\n    \"\"\"\n\n    idx = torch.arange(max_len).to(seq_len).repeat(seq_len.size(0), 1)\n    mask = torch.gt(seq_len.unsqueeze(1), idx).to(seq_len)\n\n    return mask\n\n\nclass DynamicLSTM(nn.Module):\n    \"\"\"\n    Dynamic LSTM module, which can handle variable length input sequence.\n\n    Parameters\n    ----------\n    input_size : input size\n    hidden_size : hidden size\n    num_layers : number of hidden layers. Default: 1\n    dropout : dropout rate. Default: 0.5\n    bidirectional : If True, becomes a bidirectional RNN. Default: False.\n\n    Inputs\n    ------\n    input: tensor, shaped [batch, max_step, input_size]\n    seq_lens: tensor, shaped [batch], sequence lengths of batch\n\n    Outputs\n    -------\n    output: tensor, shaped [batch, max_step, num_directions * hidden_size],\n         tensor containing the output features (h_t) from the last layer\n         of the LSTM, for each t.\n    \"\"\"\n\n    def __init__(self, input_size, hidden_size=100,\n                 num_layers=1, dropout=0., bidirectional=False):\n        super(DynamicLSTM, self).__init__()\n\n        self.lstm = nn.LSTM(\n            input_size, hidden_size, num_layers, bias=True,\n            batch_first=True, dropout=dropout, bidirectional=bidirectional)\n\n    def forward(self, x, seq_lens):\n        # sort input by descending length\n        _, idx_sort = torch.sort(seq_lens, dim=0, descending=True)\n        _, idx_unsort = torch.sort(idx_sort, dim=0)\n        x_sort = torch.index_select(x, dim=0, index=idx_sort)\n        seq_lens_sort = torch.index_select(seq_lens, dim=0, index=idx_sort)\n\n        # pack input\n        x_packed = pack_padded_sequence(\n            x_sort, seq_lens_sort, batch_first=True)\n\n        # pass through rnn\n        y_packed, _ = self.lstm(x_packed)\n\n        # unpack output\n        y_sort, length = pad_packed_sequence(y_packed, batch_first=True)\n\n        # unsort output to original order\n        y = torch.index_select(y_sort, dim=0, index=idx_unsort)\n\n        return y\n\n\nclass QuoraModel(nn.Module):\n    \"\"\"Model for quora insincere question classification.\n    \"\"\"\n\n    def __init__(self, args):\n        super(QuoraModel, self).__init__()\n\n        vocab_size = args[\"vocab_size\"]\n        pretrained_embed = args[\"pretrained_embed\"]\n        padding_idx = args[\"padding_idx\"]\n        embed_dim = 300\n        num_classes = 1\n        num_layers = 2\n        hidden_dim = 50\n        dropout = 0.5\n\n        if pretrained_embed is None:\n            self.embed = nn.Embedding(vocab_size, embed_dim)\n        else:\n            self.embed = nn.Embedding.from_pretrained(\n                pretrained_embed, freeze=False)\n        self.embed.padding_idx = padding_idx\n\n        self.rnn = DynamicLSTM(\n            embed_dim, hidden_dim, num_layers=num_layers,\n            dropout=dropout, bidirectional=True)\n\n        self.fc_att = nn.Linear(hidden_dim * 2, 1)\n\n        self.fc = nn.Linear(hidden_dim * 6, hidden_dim)\n        self.act = nn.ReLU()\n        self.drop = nn.Dropout(dropout)\n        self.out = nn.Linear(hidden_dim, num_classes)\n\n        self.loss = nn.BCEWithLogitsLoss()\n\n    def forward(self, word_seq, seq_len):\n        # mask\n        max_seq_len = torch.max(seq_len)\n        mask = seq_mask(seq_len, max_seq_len)  # [b,msl]\n\n        # embed\n        e = self.drop(self.embed(word_seq))  # [b,msl]->[b,msl,e]\n\n        # bi-rnn\n        r = self.rnn(e, seq_len)  # [b,msl,e]->[b,msl,h*2]\n\n        # attention\n        att = self.fc_att(r).squeeze(-1)  # [b,msl,h*2]->[b,msl]\n        att = mask_softmax(att, mask)  # [b,msl]\n        r_att = torch.sum(att.unsqueeze(-1) * r, dim=1)  # [b,h*2]\n\n        # pooling\n        r_avg = mask_mean(r, mask)  # [b,h*2]\n        r_max = mask_max(r, mask)  # [b,h*2]\n        r = torch.cat([r_avg, r_max, r_att], dim=-1)  # [b,h*6]\n\n        # feed-forward\n        f = self.drop(self.act(self.fc(r)))  # [b,h*6]->[b,h]\n        logits = self.out(f).squeeze(-1)  # [b,h]->[b]\n\n        return logits\n\n\nclass Trainer(object):\n    \"\"\"Trainer.\"\"\"\n\n    def __init__(self, **kwargs):\n        self.n_epochs = kwargs[\"epochs\"]\n        self.batch_size = kwargs[\"batch_size\"]\n        self.validate = kwargs[\"validate\"]\n        self.save_best_dev = kwargs[\"save_best_dev\"]\n        self.use_cuda = kwargs[\"use_cuda\"]\n        self.print_every_step = kwargs[\"print_every_step\"]\n        self.optimizer = kwargs[\"optimizer\"]\n        self.model_path = kwargs[\"model_path\"]\n        self.eval_metrics = kwargs[\"eval_metrics\"]\n\n        self._best_accuracy = 0.0\n\n        self.device = 'cpu'\n        if torch.cuda.is_available() and self.use_cuda:\n            self.device = 'cuda:0'\n\n    def train(self, network, train_data, dev_data=None):\n        # transfer model to gpu if available\n        network = network.to(self.device)\n\n        # define batch iterator\n        train_iter = torchtext.data.Iterator(\n            dataset=train_data, batch_size=self.batch_size,\n            train=True, shuffle=True, sort=False,\n            device=self.device)\n\n        # define Tester over dev data\n        if self.validate:\n            default_valid_args = {\n                \"batch_size\": max(8, self.batch_size // 10),\n                \"use_cuda\": self.use_cuda}\n            validator = Tester(**default_valid_args)\n\n        start = time.time()\n        for epoch in range(1, self.n_epochs + 1):\n            # turn on network training mode\n            network.train()\n\n            # initialize iterator\n            train_iter.init_epoch()\n\n            # one forward and backward pass\n            self._train_step(\n                train_iter, network, start=start,\n                n_print=self.print_every_step, epoch=epoch)\n\n            # validation\n            if self.validate:\n                if dev_data is None:\n                    raise RuntimeError(\n                        \"self.validate is True in trainer, \"\n                        \"but dev_data is None.\"\n                        \" Please provide the validation data.\")\n                eval_results = validator.test(network, dev_data)\n\n                if self.save_best_dev and self.best_eval_result(eval_results):\n                    save_model(network, self.model_path)\n                    print(\"Saved better model selected by validation.\")\n\n    def _train_step(self, data_iterator, network, **kwargs):\n        \"\"\"Training process in one epoch.\n        \"\"\"\n        step = 0\n        for batch in data_iterator:\n            (text, text_len), target = batch.text, batch.target\n\n            self.optimizer.zero_grad()\n            logits = network(text, text_len)\n            loss = network.loss(logits, target.float())\n            loss.backward()\n            self.optimizer.step()\n\n            if kwargs[\"n_print\"] > 0 and step % kwargs[\"n_print\"] == 0:\n                end = time.time()\n                diff = timedelta(seconds=round(end - kwargs[\"start\"]))\n                print_output = \"[epoch: {:>3} step: {:>4}]\" \\\n                    \" train loss: {:>4.6} time: {}\".format(\n                        kwargs[\"epoch\"], step, loss.item(), diff)\n                print(print_output)\n\n            step += 1\n\n    def best_eval_result(self, eval_results):\n        \"\"\"Check if the current epoch yields better validation results.\n\n        :param eval_results: dict, format {metrics_name: value}\n        :return: bool, True means current results on dev set is the best.\n        \"\"\"\n        assert self.eval_metrics in eval_results, \\\n            \"Evaluation doesn't contain metrics '{}'.\" \\\n            .format(self.eval_metrics)\n\n        accuracy = eval_results[self.eval_metrics]\n        if accuracy > self._best_accuracy:\n            self._best_accuracy = accuracy\n            return True\n        else:\n            return False\n\n\nclass Tester(object):\n    \"\"\"Tester.\"\"\"\n\n    def __init__(self, **kwargs):\n        self.batch_size = kwargs[\"batch_size\"]\n        self.use_cuda = kwargs[\"use_cuda\"]\n        self.device = 'cpu'\n        if torch.cuda.is_available() and self.use_cuda:\n            self.device = 'cuda:0'\n\n    def test(self, network, dev_data, threshold=0.33):\n        # transfer model to gpu if available\n        network = network.to(self.device)\n\n        # turn on the testing mode; clean up the history\n        network.eval()\n        output_list = []\n        truth_list = []\n\n        # define batch iterator\n        data_iter = torchtext.data.Iterator(\n            dataset=dev_data, batch_size=self.batch_size,\n            train=False, device=self.device, sort=False)\n\n        # predict\n        for batch in data_iter:\n            text, target = batch.text, batch.target\n\n            with torch.no_grad():\n                prediction = network(*text)\n\n            output_list.append(prediction.detach())\n            truth_list.append(target.detach())\n\n        # evaluate\n        eval_results = self.evaluate(output_list, truth_list, threshold)\n        print(\"[tester] {}\".format(self.print_eval_results(eval_results)))\n\n        return eval_results\n\n    def evaluate(self, predict, truth, threshold=0.33):\n        \"\"\"Compute evaluation metrics.\n\n        :param predict: list of Tensor\n        :param truth: list of dict\n        :param threshold: threshold of positive probability\n        :return eval_results: dict, format {name: metrics}.\n        \"\"\"\n        y_trues, y_preds = [], []\n        for y_true, logit in zip(truth, predict):\n            y_pred = (torch.sigmoid(logit) > threshold).long().cpu().numpy()\n            y_true = y_true.cpu().numpy()\n            y_trues.append(y_true)\n            y_preds.append(y_pred)\n        y_true = np.concatenate(y_trues, axis=0)\n        y_pred = np.concatenate(y_preds, axis=0)\n\n        precision = metrics.precision_score(y_true, y_pred, pos_label=1)\n        recall = metrics.recall_score(y_true, y_pred, pos_label=1)\n        f1 = metrics.f1_score(y_true, y_pred, pos_label=1)\n\n        metrics_dict = {\"precision\": precision, \"recall\": recall, \"f1\": f1}\n\n        return metrics_dict\n\n    def print_eval_results(self, results):\n        \"\"\"Override this method to support more print formats.\n        :param results: dict, (str: float) is (metrics name: value)\n        \"\"\"\n        return \", \".join(\n            [str(key) + \"=\" + \"{:.4f}\".format(value)\n             for key, value in results.items()])\n\n\nclass Predictor(object):\n    \"\"\"An interface for predicting outputs based on trained models.\n    \"\"\"\n\n    def __init__(self, batch_size=8, use_cuda=False):\n        self.batch_size = batch_size\n        self.use_cuda = use_cuda\n\n        self.device = 'cpu'\n        if torch.cuda.is_available() and self.use_cuda:\n            self.device = 'cuda:0'\n\n    def predict(self, network, data, threshold=0.33):\n        # transfer model to gpu if available\n        network = network.to(self.device)\n\n        # turn on the testing mode; clean up the history\n        network.eval()\n        batch_output = []\n\n        # define batch iterator\n        data_iter = torchtext.data.Iterator(\n            dataset=data, batch_size=self.batch_size,\n            train=False, device=self.device, sort=False)\n\n        for batch in data_iter:\n            text = batch.text\n\n            with torch.no_grad():\n                prediction = network(*text)\n\n            batch_output.append(prediction.detach())\n\n        return self._post_processor(batch_output, threshold)\n\n    def _post_processor(self, batch_output, threshold=0.33):\n        \"\"\"Convert logit tensor to label.\"\"\"\n        y_preds = []\n        for logit in batch_output:\n            y_pred = (torch.sigmoid(logit) > threshold).long().cpu().numpy()\n            y_preds.append(y_pred)\n        y_pred = np.concatenate(y_preds, axis=0)\n\n        return y_pred","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"befddb4028d8bca0669cd37cd4a4346616bc200e"},"cell_type":"code","source":"train_path = '../input/train.csv'\ntest_path = '../input/test.csv'\nembed_path = '../input/embeddings/glove.840B.300d/glove.840B.300d.txt'\nsubmission_path = './submission.csv'\nmodel_path = './default_model.pkl'","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"b976e80ba2916300cccac94bfda1d65f46f7f2e1"},"cell_type":"markdown","source":"## Pre-process"},{"metadata":{"trusted":true,"_uuid":"3298fae3cfd1b66e48e0dea31ce09129c9af548c"},"cell_type":"code","source":"def pre():\n    \"\"\"Pre-process model.\"\"\"\n\n    print(\"Pre-processing...\")\n\n    # load data\n    fix_length = 100\n    text = torchtext.data.Field(\n        sequential=True, use_vocab=True, lower=True,\n        tokenize=nltk.word_tokenize, batch_first=True,\n        is_target=False, fix_length=fix_length,\n        include_lengths=True)\n    target = torchtext.data.Field(\n        sequential=False, use_vocab=False,\n        batch_first=True, is_target=True)\n    dataset = torchtext.data.TabularDataset(\n        train_path, format='csv',\n        fields={\"question_text\": ('text', text),\n                \"target\": ('target', target)})\n    data_test = torchtext.data.TabularDataset(\n        test_path, format='csv',\n        fields={\"question_text\": ('text', text)})\n\n    # build vocab\n    text.build_vocab(dataset, data_test, min_freq=3)\n    text.vocab.load_vectors(torchtext.vocab.Vectors(embed_path))\n    vocab_size = len(text.vocab.itos)\n    padding_idx = text.vocab.stoi[text.pad_token]\n\n    # split data\n    data_train, data_val = dataset.split(split_ratio=0.9)\n\n    print(\"train set size:\", len(data_train))\n    print(\"val set size:\", len(data_val))\n    print(\"test set size:\", len(data_test))\n    print(\"vocab size:\", len(text.vocab.itos))\n    print(\"embed shape:\", text.vocab.vectors.shape)\n    print('')\n\n    args_dict = {\n        \"data_train\": data_train, \"data_val\": data_val,\n        \"data_test\": data_test, \"vocab_size\": vocab_size,\n        \"padding_idx\": padding_idx}\n\n    return args_dict","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"f7fe6fa155a3803f7cdb4413441b5ef5e278250e"},"cell_type":"code","source":"args = pre()","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"1eefe99d84e42539d02782dfb9e132056b128d0e"},"cell_type":"markdown","source":"## Train"},{"metadata":{"trusted":true,"_uuid":"9282d6203d179fa04fc3c8c93ba5a96dc6bd41a3"},"cell_type":"code","source":"def train(**args):\n    \"\"\"Train model.\n    \"\"\"\n\n    print(\"Training...\")\n\n    # load data and embed\n    data_train = args[\"data_train\"]\n    pretrained_embed = data_train.fields[\"text\"].vocab.vectors\n\n    # define model\n    model_args = {\n        \"vocab_size\": args[\"vocab_size\"],\n        \"padding_idx\": args[\"padding_idx\"],\n        \"pretrained_embed\": pretrained_embed,\n    }\n    model = QuoraModel(model_args)\n\n    # define trainer\n    trainer_args = {\n        \"epochs\": 4,\n        \"batch_size\": 128,\n        \"validate\": True,\n        \"save_best_dev\": True,\n        \"use_cuda\": True,\n        \"print_every_step\": 1000,\n        \"optimizer\": torch.optim.Adam(model.parameters(), lr=1e-3),\n        \"model_path\": model_path,\n        \"eval_metrics\": \"f1\",\n    }\n    trainer = Trainer(**trainer_args)\n\n    # train\n    data_val = args[\"data_val\"]\n    trainer.train(model, data_train, dev_data=data_val)\n\n    print('')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"84f24a7260149daa676fa8543cb0cb505e3706e2"},"cell_type":"code","source":"train(**args)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"3da1f3e893b39bc1405d9f922a0c01f8f63a9d76"},"cell_type":"markdown","source":"## Test and model selection"},{"metadata":{"trusted":true,"_uuid":"01538a7c3b5adabf40240be15bcf06bce2243c14"},"cell_type":"code","source":"def test(**args):\n    \"\"\"Train model.\n    \"\"\"\n\n    print(\"Testing...\")\n\n    # define model\n    model_args = {\n        \"vocab_size\": args[\"vocab_size\"],\n        \"padding_idx\": args[\"padding_idx\"],\n        \"pretrained_embed\": None,\n    }\n    model = QuoraModel(model_args)\n    load_model(model, model_path, use_cuda=True)\n\n    # define tester\n    tester_args = {\n        \"batch_size\": 128,\n        \"use_cuda\": True,\n    }\n    tester = Tester(**tester_args)\n\n    # test and threshold selection\n    data_val = args[\"data_val\"]\n    best_thresh, best_f1 = 0., 0.\n    for thresh in np.arange(0.1, 0.501, 0.01):\n        thresh = np.round(thresh, 2)\n        f1 = tester.test(model, data_val, threshold=thresh)[\"f1\"]\n        print(\"threshold: {:>.2f} f1: {}\".format(thresh, f1))\n        if f1 > best_f1:\n            best_thresh, best_f1 = thresh, f1\n\n    args[\"threshold\"] = best_thresh\n\n    print(\"best f1 on dev: {} threshold: {:>.2f}\".format(best_f1, best_thresh))\n    print('')\n\n    return args","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"a5b16aa13cf92e1ca79161bf992d796b459ca578"},"cell_type":"code","source":"    args = test(**args)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"04882c1ad6b67c5f23ca02e0cafb28b819f3eb87"},"cell_type":"markdown","source":"## Predict"},{"metadata":{"trusted":true,"_uuid":"d72d40715233f576e4373b9f1e6715bb3dad7d3a"},"cell_type":"code","source":"def infer(**args):\n    \"\"\"Inference using model.\n    \"\"\"\n\n    print(\"Predicting...\")\n\n    # define model\n    model_args = {\n        \"vocab_size\": args[\"vocab_size\"],\n        \"padding_idx\": args[\"padding_idx\"],\n        \"pretrained_embed\": None,\n    }\n    model = QuoraModel(model_args)\n    load_model(model, model_path, use_cuda=True)\n\n    # define predictor\n    predictor = Predictor(batch_size=128, use_cuda=False)\n\n    # predict\n    data_test = args[\"data_test\"]\n    threshold = args[\"threshold\"]\n    y_pred = predictor.predict(model, data_test, threshold=threshold)\n\n    # submit result\n    test_df = pd.read_csv(test_path, index_col=False, header=0)\n    data = {\"qid\": test_df[\"qid\"], \"prediction\": y_pred}\n    subm_df = pd.DataFrame(data=data)\n    subm_df.to_csv(submission_path, header=True, index=False)\n\n    print(\"submission saved as {}.\".format(submission_path))\n    print('')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"1f05c889a116c040c9087107ebea91bd0924d74e"},"cell_type":"code","source":"infer(**args)","execution_count":null,"outputs":[]}],"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"name":"python","version":"3.6.6","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"}},"nbformat":4,"nbformat_minor":1}