diff --git a/nlp_amazon_review/huggingface_transformers/JapaneseBERT-Text-Classification.ipynb b/nlp_amazon_review/huggingface_transformers/JapaneseBERT-Text-Classification.ipynb new file mode 100644 index 00000000..6f1bcb1b --- /dev/null +++ b/nlp_amazon_review/huggingface_transformers/JapaneseBERT-Text-Classification.ipynb @@ -0,0 +1,327 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# huggingface/Transformerを用いた事前学習済み日本語BERTモデルによるテキスト分類" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "今回は、[huggingface/Transformer](https://huggingface.co/transformers/)で提供されている[日本語BERT](https://github.com/cl-tohoku/bert-japanese)を用いて日本語のAmazonの商品レビューに対する感情分析を行います。 \n", + "つまり、そのレビューが Positive (Rating が 5 or 4) か、Negative (Rating が 1 or 2)なのかを判定します。 これは文書を Positive か Negative に分類する2クラスの分類問題として扱うことができます。" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## **データの取得**\n", + "\n", + "Amazon の商品レビューデータセットは [Registry of Open Data on AWS](https://registry.opendata.aws/) で公開されており、 \n", + "以下からダウンロード可能です。\n", + "このノートブックでは、日本語のデータセットをダウンロードします。\n", + "- データセットの概要 \n", + "https://registry.opendata.aws/amazon-reviews/\n", + "\n", + "- 日本語のデータセット(readme.htmlからたどることができます) \n", + "https://s3.amazonaws.com/amazon-reviews-pds/tsv/amazon_reviews_multilingual_JP_v1_00.tsv.gz\n", + "\n", + "以下では、データをダウンロードして解凍 (unzip) します。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import urllib.request\n", + "import os\n", + "import gzip\n", + "import shutil\n", + "\n", + "download_url = \"https://s3.amazonaws.com/amazon-reviews-pds/tsv/amazon_reviews_multilingual_JP_v1_00.tsv.gz\" \n", + "dir_name = \"data\"\n", + "file_name = \"amazon_review.tsv.gz\"\n", + "tsv_file_name = \"amazon_review.tsv\"\n", + "file_path = os.path.join(dir_name,file_name)\n", + "tsv_file_path = os.path.join(dir_name,tsv_file_name)\n", + "\n", + "os.makedirs(dir_name, exist_ok=True)\n", + "\n", + "if os.path.exists(file_path):\n", + " print(\"File {} already exists. Skipped download.\".format(file_name))\n", + "else:\n", + " urllib.request.urlretrieve(download_url, file_path)\n", + " print(\"File downloaded: {}\".format(file_path))\n", + " \n", + "if os.path.exists(tsv_file_path):\n", + " print(\"File {} already exists. Skipped unzip.\".format(tsv_file_name))\n", + "else:\n", + " with gzip.open(file_path, mode='rb') as fin:\n", + " with open(tsv_file_path, 'wb') as fout:\n", + " shutil.copyfileobj(fin, fout)\n", + " print(\"File uznipped: {}\".format(tsv_file_path))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## **データの前処理**\n", + "\n", + "ダウンロードしたデータには学習に不要なデータや直接利用できないデータもあります。以下の前処理で利用できるようにします。\n", + "\n", + "1. ダウンロードしたデータには不要なデータも含まれているので削除し、2クラス分類 (positive が 1, negative が 0)となるように評価データを加工します。\n", + "2. 学習データ、検証データに分けて、保存します。\n", + "\n", + "### **データの加工**\n", + "\n", + "今回利用しないデータは以下の4つです。必要なデータだけ選んで保存します。\n", + "- 評価データ `star_rating` と レビューのテキストデータ `review_body` 以外のデータ\n", + "- 評価が 3 のデータ (positive でも negative でもないデータ)\n", + "- 日本語以外のデータ\n", + "- `review_body`が空白のデータ\n", + "\n", + "また、評価が1, 2 のデータはラベル 0 (negative) に、評価が4, 5 のデータはラベル 1 (positive) にします。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import pandas as pd\n", + "import numpy as np\n", + "import re\n", + "\n", + "df = pd.read_csv(tsv_file_path, sep ='\\t')\n", + "df_pos_neg = df.loc[:, [\"star_rating\", \"review_body\"]]\n", + "df_pos_neg = df_pos_neg[df_pos_neg.star_rating != 3]\n", + "df_pos_neg.loc[df_pos_neg.star_rating < 3, \"star_rating\"] = 0\n", + "df_pos_neg.loc[df_pos_neg.star_rating > 3, \"star_rating\"] = 1\n", + "\n", + "def is_japanese(s):\n", + " return True if re.search(r'[ぁ-んァ-ン]', s) else False\n", + "\n", + "def is_blank(s):\n", + " return True if len(s.strip().split('\\t')) == 2 else False\n", + "\n", + "def func_to_row(x):\n", + " if not is_japanese(x[\"review_body\"]):\n", + " x[\"review_body\"] = np.nan\n", + " \n", + " elif is_blank(x[\"review_body\"]):\n", + " x[\"review_body\"] = np.nan\n", + " \n", + " return x\n", + "\n", + "labeled_df = df_pos_neg.apply(lambda x: func_to_row(x), axis =1)\n", + "labeled_df.dropna(inplace=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### **データの分割**\n", + "\n", + "学習には学習データと検証データが必要なため、それぞれ準備します。\n", + "`train_ratio` で設定した割合のデータを学習データとし、残ったデータを検証データに分割して利用します。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import os\n", + "dir_name = \"data\"\n", + "file_name = \"amazon_review.tsv.gz\"\n", + "tsv_file_name = \"amazon_review.tsv\"\n", + "file_path = os.path.join(dir_name,file_name)\n", + "tsv_file_path = os.path.join(dir_name,tsv_file_name)\n", + "\n", + "# Swap positions of \"review_body\",\"star_rating\" because transform.py requires this order.\n", + "labeled_df = df_pos_neg.loc[:, [\"review_body\",\"star_rating\"]]\n", + "data_size = len(labeled_df.index)\n", + "train_ratio = 0.9\n", + "train_index = np.random.choice(data_size, int(data_size*train_ratio), replace=False)\n", + "valid_index = np.setdiff1d(np.arange(data_size), train_index)\n", + "\n", + "np.savetxt('train.tsv',labeled_df.iloc[train_index].values, fmt=\"%s\\t%i\") \n", + "np.savetxt('valid.tsv',labeled_df.iloc[valid_index].values, fmt=\"%s\\t%i\") \n", + "\n", + "print(\"Data is splitted into:\")\n", + "print(\"Training data: {} records.\".format(len(train_index)))\n", + "print(\"valid data: {} records.\".format(len(valid_index)))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### **ヘッダの追加**\n", + "解析するテキストを`review_body`、ポジティブかネガティブかを表すラベルを`star_rating`としてヘッダを与えます。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "!sed -i '1s/^/review_body\\tstar_rating\\n/' train.tsv\n", + "!sed -i '1s/^/review_body\\tstar_rating\\n/' valid.tsv" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## **学習の実行**\n", + "### **学習データのアップロード**\n", + "S3に学習データと検証データをアップロードします。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import sagemaker\n", + "\n", + "sess = sagemaker.Session()\n", + "\n", + "s3_train_data = sess.upload_data(path='train.tsv', key_prefix='amazon-review-data-tsv')\n", + "s3_valid_data = sess.upload_data(path='valid.tsv', key_prefix='amazon-review-data-tsv')\n", + "print(\"Training data is uploaded to {}\".format(s3_train_data))\n", + "print(\"Training data is uploaded to {}\".format(s3_valid_data))\n", + "\n", + "data_channels = {'train': s3_train_data, 'val': s3_valid_data}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### **学習ジョブの実行**\n", + "今回はGPUインスタンスであるml.p3.2xlargeを使用します。テストのため、epochは1とします。\n", + "`src`ディレクトリにある`requirements.txt`、`train_and_deploy.py`、`EarlyStopping.py`が\n", + "学習インスタンスへアップロードされます。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from sagemaker.pytorch import PyTorch\n", + "\n", + "role = sagemaker.get_execution_role()\n", + "\n", + "estimator = PyTorch(entry_point='train_and_deploy.py',\n", + " source_dir = 'src',\n", + " role=role,\n", + " framework_version='1.4.0',\n", + " train_instance_count=2,\n", + " train_instance_type='ml.p3.2xlarge',\n", + " hyperparameters={\n", + " 'epochs': 1,\n", + " })" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "estimator.fit(data_channels)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## **デプロイ**\n", + "学習したモデルをデプロイします。推論はCPUインスタンスで行うことができます。
\n", + "最後に`!`記号が現れたらデプロイ完了です。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "predictor = estimator.deploy(initial_instance_count=1, instance_type='ml.c5.xlarge')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## **推論**\n", + "実際の文を用いた推論を行います。\n", + "response_hypoの値が1に近ければポジティブ、0に近ければネガティブです。\n", + "\n", + "Pytorchコンテナではserializer等がnumpyであるため、deploy後に以下のようにしてserializerを変更します。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from sagemaker.predictor import json_serializer\n", + "from sagemaker.predictor import json_deserializer\n", + "predictor.content_type = 'application/json'\n", + "predictor.serializer = json_serializer\n", + "predictor.deserializer = json_deserializer" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "response_hypo = predictor.predict(\"価格にもクオリティにも満足した。\")\n", + "prediction_label = \"ポジティブ\" if response_hypo[0] >= 0.5 else \"ネガティブ\"\n", + "print(\"label: {} (hypo value: {})\".format(prediction_label,response_hypo[0]))" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "conda_pytorch_p36", + "language": "python", + "name": "conda_pytorch_p36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.10" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/nlp_amazon_review/huggingface_transformers/src/EarlyStopping.py b/nlp_amazon_review/huggingface_transformers/src/EarlyStopping.py new file mode 100644 index 00000000..a9cc90c7 --- /dev/null +++ b/nlp_amazon_review/huggingface_transformers/src/EarlyStopping.py @@ -0,0 +1,43 @@ +import numpy as np +import torch + +class EarlyStopping: + def __init__(self, patience=7, mode="max"): + self.patience = patience + self.counter = 0 + self.mode = mode + self.best_score = None + self.early_stop = False + if self.mode == "min": + self.val_score = np.Inf + else: + self.val_score = -np.Inf + + def __call__(self, epoch_score, model, model_path): + + if self.mode == "min": + score = -1.0 * epoch_score + else: + score = np.copy(epoch_score) + + if self.best_score is None: + self.best_score = score + self.save_checkpoint(epoch_score, model, model_path) + elif score < self.best_score: + self.counter += 1 + print('EarlyStopping counter: {} out of {}'.format(self.counter, self.patience)) + if self.counter >= self.patience: + self.early_stop = True + else: + self.best_score = score + self.save_checkpoint(epoch_score, model, model_path) + self.counter = 0 + + def save_checkpoint(self, epoch_score, model, model_path): + if epoch_score not in [-np.inf, np.inf, -np.nan, np.nan]: + print('Validation score improved ({} --> {}). Saving model!'.format(self.val_score, epoch_score)) + if torch.cuda.device_count() > 1: + torch.save(model.cpu().module.state_dict(), model_path) + else: + torch.save(model.cpu().state_dict(), model_path) + self.val_score = epoch_score \ No newline at end of file diff --git a/nlp_amazon_review/huggingface_transformers/src/requirements.txt b/nlp_amazon_review/huggingface_transformers/src/requirements.txt new file mode 100644 index 00000000..4e719d4b --- /dev/null +++ b/nlp_amazon_review/huggingface_transformers/src/requirements.txt @@ -0,0 +1,4 @@ +transformers==3.0.2 +mecab-python3==0.996.5 +tqdm +unidic-lite \ No newline at end of file diff --git a/nlp_amazon_review/huggingface_transformers/src/train_and_deploy.py b/nlp_amazon_review/huggingface_transformers/src/train_and_deploy.py new file mode 100644 index 00000000..da4bd757 --- /dev/null +++ b/nlp_amazon_review/huggingface_transformers/src/train_and_deploy.py @@ -0,0 +1,309 @@ +import argparse +from collections import OrderedDict +import json +import numpy as np +import sys +import os +import pandas as pd +import random +from sklearn.metrics import accuracy_score +from tqdm import tqdm + +import torch +import torch.distributed as dist +import torch.nn as nn +import torch.nn.functional as F + +import transformers +from transformers import BertJapaneseTokenizer +from transformers import BertForSequenceClassification +from transformers import AdamW, get_linear_schedule_with_warmup + +from EarlyStopping import EarlyStopping + +import logging +logging.basicConfig(level=logging.INFO) + + +DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu") +MAX_SEQUENCE_LENGTH=128 + +tokenizer = BertJapaneseTokenizer.from_pretrained('cl-tohoku/bert-base-japanese-whole-word-masking') + + +def seed_torch(seed=42): + random.seed(seed) + os.environ['PYTHONHASHSEED'] = str(seed) + np.random.seed(seed) + torch.manual_seed(seed) + if torch.cuda.device_count() > 1: + torch.cuda.manual_seed_all(seed) + else: + torch.cuda.manual_seed(seed) + torch.backends.cudnn.deterministic = True + + +# Converting the lines to BERT format +# Thanks to https://www.kaggle.com/httpwwwfszyc/bert-in-keras-taming +def convert_lines(example, max_seq_length, tokenizer): + max_seq_length -= 2 + all_tokens = [] + longer = 0 + for text in tqdm(example): + tokens_a = tokenizer.tokenize(text) + if len(tokens_a) > max_seq_length: + tokens_a = tokens_a[:max_seq_length] + longer += 1 + one_token = tokenizer.convert_tokens_to_ids(["[CLS]"]+tokens_a+["[SEP]"])+[0] * (max_seq_length - len(tokens_a)) + all_tokens.append(one_token) + return np.array(all_tokens) + + +def loss_fn(preds, labels): + preds = preds.view(-1) + labels = labels.view(-1) + assert(preds.shape == labels.shape) + + loss = nn.BCEWithLogitsLoss()(preds, labels) + + return loss + + +def _average_gradients(model): + # Gradient averaging. + size = float(dist.get_world_size()) + for param in model.parameters(): + dist.all_reduce(param.grad.data, op=dist.reduce_op.SUM) + param.grad.data /= size + + +def train(train_loader, model, optimizer, is_distributed): + model.train() + + avg_loss = 0. + avg_accuracy = 0. + tk0 = tqdm(enumerate(train_loader), total=len(train_loader), leave=False) + + optimizer.zero_grad() + + for i, (x_batch, y_batch) in tk0: + + y_pred = model(x_batch.to(DEVICE), + attention_mask=(x_batch > 0).to(DEVICE), + labels=None) + loss = loss_fn(y_pred[0], y_batch.to(DEVICE)) + loss.backward() + + if is_distributed and not use_cuda: + # average gradients manually for multi-machine cpu case only + _average_gradients(model) + + optimizer.step() + optimizer.zero_grad() + + avg_loss += loss.item() / len(train_loader) + avg_accuracy += torch.mean( + ((torch.sigmoid(y_pred[0]) >= 0.5) == (y_batch >= 0.5).to(DEVICE)).to(torch.float)).item() / len(train_loader) + + tk0.set_postfix(loss=loss.item(), avg_loss=avg_loss) + + log = OrderedDict([('avg_loss', avg_loss), ('avg_acc', avg_accuracy)]) + tk0.close() + + return log + + +# Run validation +def evaluate(valid_loader, model): + model.eval() + + avg_loss = 0. + valid_preds = [] + valid_trues = [] + + with torch.no_grad(): + tk0 = tqdm(valid_loader) + for i, (x_batch, y_batch) in enumerate(tk0): + y_pred = model(x_batch.to(DEVICE), + attention_mask=(x_batch > 0).to(DEVICE), + labels=None) + + loss = loss_fn(y_pred[0], y_batch.to(DEVICE)) + avg_loss += loss.item() / len(valid_loader) + outputs_np = torch.sigmoid(y_pred[0]).cpu().detach().numpy() + targets_np = y_batch.unsqueeze(1).numpy() + valid_preds.append(outputs_np) + valid_trues.append(targets_np) + + valid_preds = np.vstack(valid_preds) + valid_trues = np.vstack(valid_trues) + acc = accuracy_score((valid_trues >= 0.5), (valid_preds >= 0.5)) + val_log = OrderedDict([('val_loss', avg_loss), ('val_acc', acc)]) + tk0.close() + + return val_log + + +if __name__ == '__main__': + + # Receive hyperparameters passed via create-training-job API + parser = argparse.ArgumentParser() + + parser.add_argument('--batch-size', type=int, default=32) + parser.add_argument('--epochs', type=int, default=1) + parser.add_argument('--learning-rate', type=float, default=5e-6) + + parser.add_argument('--num-gpus', type=int, default=os.environ['SM_NUM_GPUS']) + parser.add_argument('--backend', type=str, default=None, + help='backend for distributed training (tcp, gloo on cpu and gloo, nccl on gpu)') + parser.add_argument('--model-dir', type=str, default=os.environ['SM_MODEL_DIR']) + parser.add_argument('--train', type=str, default=os.environ['SM_CHANNEL_TRAIN']) + parser.add_argument('--val', type=str, default=os.environ.get('SM_CHANNEL_VAL')) + parser.add_argument('--current-host', type=str, default=os.environ['SM_CURRENT_HOST']) + parser.add_argument('--hosts', type=list, default=json.loads(os.environ['SM_HOSTS'])) + + args = parser.parse_args() + + # Set hyperparameters after parsing the arguments + batch_size = args.batch_size + lr = args.learning_rate + num_epochs = args.epochs + current_host = args.current_host + hosts = args.hosts + model_dir = args.model_dir + training_dir = args.train + val_dir = args.val + + #is_distributed = len(args.hosts) > 1 and args.backend is not None + is_distributed = len(args.hosts) > 1 and args.backend is not None + + if is_distributed: + # Initialize the distributed environment. + world_size = len(args.hosts) + os.environ['WORLD_SIZE'] = str(world_size) + host_rank = args.hosts.index(args.current_host) + os.environ['RANK'] = str(host_rank) + dist.init_process_group(backend=args.backend, rank=host_rank, world_size=world_size) + logger.info('Initialized the distributed environment: \'{}\' backend on {} nodes. '.format( + args.backend, dist.get_world_size()) + 'Current host rank is {}. Number of gpus: {}'.format( + dist.get_rank(), args.num_gpus)) + + # fix seed + seed_torch() + + # Data loading + train_df = pd.read_csv(os.path.join(training_dir, 'train.tsv'), sep ='\t') + valid_df = pd.read_csv(os.path.join(val_dir, 'valid.tsv'), sep ='\t') + + # convert BERT dataset + tr_sequences = convert_lines(train_df["review_body"].fillna("DUMMY_VALUE"), + MAX_SEQUENCE_LENGTH, tokenizer) + train_dataset = torch.utils.data.TensorDataset(torch.tensor(tr_sequences, dtype=torch.long), + torch.tensor(train_df['star_rating'].values, dtype=torch.float)) + train_loader = torch.utils.data.DataLoader(train_dataset, + batch_size=batch_size, + shuffle=True) + + val_sequences = convert_lines(valid_df["review_body"].fillna("DUMMY_VALUE"), + MAX_SEQUENCE_LENGTH, + tokenizer) + valid_dataset = torch.utils.data.TensorDataset(torch.tensor(val_sequences, dtype=torch.long), + torch.tensor(valid_df['star_rating'].values, dtype=torch.float)) + valid_loader = torch.utils.data.DataLoader(valid_dataset, + batch_size=batch_size, + shuffle=False) + + # Load pre-trained bert model + model = BertForSequenceClassification.from_pretrained('cl-tohoku/bert-base-japanese-whole-word-masking', + num_labels=1) + + model.zero_grad() + model = model.to(DEVICE) + param_optimizer = list(model.named_parameters()) + no_decay = ['bias', 'LayerNorm.bias', 'LayerNorm.weight'] + optimizer_grouped_parameters = [ + {'params': [p for n, p in param_optimizer if not any(nd in n for nd in no_decay)], 'weight_decay': 0.01}, + {'params': [p for n, p in param_optimizer if any(nd in n for nd in no_decay)], 'weight_decay': 0.0} + ] + optimizer = AdamW(optimizer_grouped_parameters, + lr=lr, eps=1e-8) + + + if is_distributed and DEVICE != 'cpu': + # multi-machine multi-gpu case + model = nn.parallel.DistributedDataParallel(model) + else: + # single-machine multi-gpu case or single-machine or multi-machine cpu case + model = nn.DataParallel(model) + + + es = EarlyStopping(patience=5, mode="max") + path = os.path.join(args.model_dir, 'model.pth') + + for epoch in range(num_epochs): + + log = train(train_loader, model, optimizer, is_distributed) + val_log = evaluate(valid_loader, model) + + es(val_log["val_acc"], model, model_path=path) + + if es.early_stop: + logger.info("Early stopping") + break + + +def model_fn(model_dir): + """ + Load the gluon model. Called once when hosting service starts. + + :param: model_dir The directory where model files are stored. + :return: a model (in this case a Gluon network) + """ + + model = BertForSequenceClassification.from_pretrained('cl-tohoku/bert-base-japanese-whole-word-masking', + num_labels=1) + model = torch.nn.DataParallel(model) + with open(os.path.join(model_dir, 'model.pth'), 'rb') as f: + model.load_state_dict(torch.load(f)) + + return {"net": model, "tokenizer": tokenizer} + + +def transform_fn(net, data, input_content_type, output_content_type): + """ + Transform a request using the Gluon model. Called once per request. + + :param net: The Gluon model. + :param data: The request payload. + :param input_content_type: The request content type. + :param output_content_type: The (desired) response content type. + :return: response payload and content type. + """ + + # we can use content types to vary input/output handling, but + # here we just assume json for both + model = net["net"] + tokenizer = net["tokenizer"] + model.to(DEVICE) + + # Assume one line of text + parsed = json.loads(data) + logging.info("Received_data: {}".format(parsed)) + parsed = tokenizer.tokenize(parsed) + + #added by manome + if len(parsed) > MAX_SEQUENCE_LENGTH: + parsed = parsed[:MAX_SEQUENCE_LENGTH-2] + + logging.info("Tokens: {}".format(parsed)) + #x_batch = tokenizer.convert_tokens_to_ids(["[CLS]"]+parsed+["[SEP]"])+[0] * (MAX_SEQUENCE_LENGTH - len(parsed) - 2) + x_batch = tokenizer.convert_tokens_to_ids(["[CLS]"]+parsed+["[SEP]"]) # do not zero padding + x_batch = torch.LongTensor(x_batch).unsqueeze(0) + + model.eval() + with torch.no_grad(): + output = model(x_batch.to(DEVICE), + attention_mask=(x_batch>0).to(DEVICE), + labels=None) + response_body = json.dumps(torch.sigmoid(output[0]).cpu().detach().numpy().tolist()[0]) + return response_body, output_content_type