208 lines
8.5 KiB
Python
208 lines
8.5 KiB
Python
# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
import argparse
|
|
import functools
|
|
import json
|
|
import os
|
|
import sys
|
|
import time
|
|
from pathlib import Path
|
|
|
|
import paddle
|
|
|
|
from paddlenlp.data import Dict, Pad
|
|
from paddlenlp.transformers.roberta.tokenizer import (
|
|
RobertaBPETokenizer,
|
|
RobertaTokenizer,
|
|
)
|
|
|
|
sys.path.append("../task/mrc")
|
|
from saliency_map.squad import RCInterpret, compute_prediction # noqa: E402
|
|
|
|
sys.path.append("..")
|
|
from roberta.modeling import RobertaForQuestionAnswering # noqa: E402
|
|
|
|
sys.path.remove("..")
|
|
sys.path.remove("../task/mrc")
|
|
sys.path.append("../..")
|
|
from model_interpretation.utils import ( # noqa: E402
|
|
convert_tokenizer_res_to_old_version,
|
|
)
|
|
|
|
sys.path.remove("../..")
|
|
|
|
|
|
def get_args():
|
|
parser = argparse.ArgumentParser("mrc predict with roberta")
|
|
parser.add_argument("--base_model", required=True, choices=["roberta_base", "roberta_large"])
|
|
parser.add_argument("--from_pretrained", type=str, required=True, help="pretrained model directory or tag")
|
|
parser.add_argument(
|
|
"--max_seq_len", type=int, default=128, help="max sentence length, should not greater than 512"
|
|
)
|
|
parser.add_argument("--batch_size", type=int, default=32, help="batchsize")
|
|
parser.add_argument("--epoch", type=int, default=3, help="epoch")
|
|
parser.add_argument("--data_dir", type=str, required=True, help="data directory includes train / develop data")
|
|
parser.add_argument("--warmup_proportion", type=float, default=0.1)
|
|
parser.add_argument("--lr", type=float, default=5e-5, help="learning rate")
|
|
parser.add_argument("--eval", action="store_true")
|
|
parser.add_argument("--init_checkpoint", type=str, default=None, help="checkpoint to warm start from")
|
|
parser.add_argument("--wd", type=float, default=0.01, help="weight decay, aka L2 regularizer")
|
|
parser.add_argument(
|
|
"--use_amp",
|
|
action="store_true",
|
|
help="only activate AMP(auto mixed precision accelatoin) on TensorCore compatible devices",
|
|
)
|
|
parser.add_argument("--n-samples", type=int, default=25, help="number of samples used for smooth gradient method")
|
|
parser.add_argument("--output_dir", type=Path, required=True, help="interpretable output directory")
|
|
parser.add_argument(
|
|
"--doc_stride",
|
|
type=int,
|
|
default=128,
|
|
help="When splitting up a long document into chunks, how much stride to take between chunks.",
|
|
)
|
|
parser.add_argument("--language", type=str, required=True, help="language that the model based on")
|
|
parser.add_argument("--input_data", type=str, required=True)
|
|
args = parser.parse_args()
|
|
return args
|
|
|
|
|
|
def map_fn_DuCheckList(examples, args, tokenizer):
|
|
# Tokenize our examples with truncation and maybe padding, but keep the overflows using a stride. This results
|
|
# in one example possible giving several features when a context is long, each of those features having a
|
|
# context that overlaps a bit the context of the previous feature.
|
|
# NOTE: Almost the same functionality as HuggingFace's prepare_train_features function. The main difference is
|
|
# that HugggingFace uses ArrowTable as basic data structure, while we use list of dictionary instead.
|
|
contexts = [examples[i]["context"] for i in range(len(examples))]
|
|
questions = [examples[i]["question"] for i in range(len(examples))]
|
|
|
|
tokenized_examples = tokenizer(questions, contexts, stride=args.doc_stride, max_seq_len=args.max_seq_len)
|
|
tokenized_examples = convert_tokenizer_res_to_old_version(tokenized_examples)
|
|
|
|
# For validation, there is no need to compute start and end positions
|
|
for i, tokenized_example in enumerate(tokenized_examples):
|
|
# Grab the sequence corresponding to that example (to know what is the context and what is the question).
|
|
sequence_ids = tokenized_example["token_type_ids"]
|
|
|
|
# One example can give several spans, this is the index of the example containing this span of text.
|
|
sample_index = tokenized_example["overflow_to_sample"]
|
|
tokenized_examples[i]["example_id"] = examples[sample_index]["id"]
|
|
|
|
# Set to None the offset_mapping that are not part of the context so it's easy to determine if a token
|
|
# position is part of the context or not.
|
|
if args.language == "ch":
|
|
tokenized_examples[i]["offset_mapping"] = [
|
|
(o if sequence_ids[k] == 1 else None) for k, o in enumerate(tokenized_example["offset_mapping"])
|
|
]
|
|
else:
|
|
n = tokenized_example["offset_mapping"].index((0, 0), 1) + 2 # context start position
|
|
m = len(tokenized_example["offset_mapping"]) - 1 # context end position + 1
|
|
tokenized_examples[i]["offset_mapping"] = [
|
|
(o if n <= k <= m else None) for k, o in enumerate(tokenized_example["offset_mapping"])
|
|
]
|
|
|
|
return tokenized_examples
|
|
|
|
|
|
def load_data(path):
|
|
data = {}
|
|
f = open(path, "r")
|
|
for line in f.readlines():
|
|
line_split = json.loads(line)
|
|
data[line_split["id"]] = line_split
|
|
f.close()
|
|
return data
|
|
|
|
|
|
def init_roberta_var(args):
|
|
if args.language == "ch":
|
|
tokenizer = RobertaTokenizer.from_pretrained(args.from_pretrained)
|
|
else:
|
|
tokenizer = RobertaBPETokenizer.from_pretrained(args.from_pretrained)
|
|
|
|
model = RobertaForQuestionAnswering.from_pretrained(args.from_pretrained)
|
|
map_fn = functools.partial(map_fn_DuCheckList, args=args, tokenizer=tokenizer)
|
|
dev_ds = RCInterpret().read(os.path.join(args.data_dir, "dev"))
|
|
# dev_ds = load_dataset('squad', splits='dev_v2', data_files=None)
|
|
dev_ds.map(map_fn, batched=True)
|
|
dev_batch_sampler = paddle.io.BatchSampler(dev_ds, batch_size=args.batch_size, shuffle=False)
|
|
batchify_fn = lambda samples, fn=Dict(
|
|
{
|
|
"input_ids": Pad(axis=0, pad_val=tokenizer.pad_token_id),
|
|
"token_type_ids": Pad(axis=0, pad_val=tokenizer.pad_token_type_id),
|
|
}
|
|
): fn(samples)
|
|
|
|
dev_dataloader = paddle.io.DataLoader(
|
|
dataset=dev_ds, batch_sampler=dev_batch_sampler, collate_fn=batchify_fn, return_list=True
|
|
)
|
|
|
|
return model, tokenizer, dev_dataloader, dev_ds
|
|
|
|
|
|
@paddle.no_grad()
|
|
def evaluate(model, data_loader, args):
|
|
model.eval()
|
|
|
|
all_start_logits = []
|
|
all_end_logits = []
|
|
tic_eval = time.time()
|
|
|
|
for batch in data_loader:
|
|
input_ids, token_type_ids = batch
|
|
loss, start_logits_tensor, end_logits_tensor, cls_logits = model(input_ids, token_type_ids)
|
|
for idx in range(start_logits_tensor.shape[0]):
|
|
if len(all_start_logits) % 1000 == 0 and len(all_start_logits):
|
|
print("Processing example: %d" % len(all_start_logits))
|
|
print("time per 1000:", time.time() - tic_eval)
|
|
tic_eval = time.time()
|
|
|
|
all_start_logits.append(start_logits_tensor.numpy()[idx])
|
|
all_end_logits.append(end_logits_tensor.numpy()[idx])
|
|
|
|
all_predictions, all_nbest_json, scores_diff_json, all_feature_index = compute_prediction(
|
|
data_loader.dataset.data,
|
|
data_loader.dataset.new_data,
|
|
(all_start_logits, all_end_logits),
|
|
True,
|
|
20,
|
|
args.max_seq_len,
|
|
0.0,
|
|
)
|
|
|
|
# Can also write all_nbest_json and scores_diff_json files if needed
|
|
with open(os.path.join(args.output_dir, "dev"), "w") as f:
|
|
for id in all_predictions:
|
|
temp = {}
|
|
temp["id"] = int(id)
|
|
temp["pred_label"] = all_predictions[id]
|
|
temp["pred_feature"] = all_feature_index[id]
|
|
f.write(json.dumps(temp, ensure_ascii=False) + "\n")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
args = get_args()
|
|
if args.base_model.startswith("roberta"):
|
|
model, tokenizer, dataloader, dev_ds = init_roberta_var(args)
|
|
else:
|
|
raise ValueError("unsupported base model name.")
|
|
|
|
with paddle.amp.auto_cast(enable=args.use_amp):
|
|
|
|
sd = paddle.load(args.init_checkpoint)
|
|
model.set_dict(sd)
|
|
print("load model from %s" % args.init_checkpoint)
|
|
|
|
evaluate(model, dataloader, args)
|