# 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 csv import json import os from abc import abstractmethod from collections import defaultdict from dataclasses import dataclass, field import paddle import pandas as pd from paddle.metric import Accuracy from paddlenlp.datasets import MapDataset from paddlenlp.metrics import AccuracyAndF1, Mcc, PearsonAndSpearman @dataclass class InputExample(object): """Data structure of every example in datasets.""" uid: str = field(default=None, metadata={"help": "A unique identifier of the example."}) text_a: str = field(default=None, metadata={"help": "The first text sequence in each example."}) text_b: str = field(default=None, metadata={"help": "The other text sequences in each example."}) cls_label: int = field(default=None, metadata={"help": "The label of classification tasks."}) seq_label: list = field(default=None, metadata={"help": "The label of generation tasks."}) meta: dict = field(default=None, metadata={"help": "An optional dictionary of other data for each example."}) def __repr__(self): content = {k: v for k, v in self.__dict__.items() if v is not None} content = json.dumps(content, indent=2, sort_keys=True) + "\n" return str(content) def keys(self, keep_none=False): return [key for key in self.__dict__.keys() if getattr(self, key) is not None] class InputFeatures(dict): """ Data structure of every wrapped example or a batch of examples as the input of model. Args: input_ids (paddle.Tensor): The token ids. attention_mask (paddle.Tensor): The mask ids. token_type_ids (paddle.Tensor, optional): The token type ids. input_embeds (paddle.Tensor, optional): The embeddings of soft tokens. mask_ids (paddle.Tensor, optional): The mask ids where 1 denotes that a token is a mask, 0 denotes it is not a mask. cls_label (list, optional): The label of classification task. seq_label (list, optional): The label of generation task. uid (list, optional): The unique id(s) for example(s). """ input_keys = [ "input_ids", "attention_mask", "token_type_ids", "input_embeds", "cls_label", "seq_label", "label", "uid", "mask_ids", "soft_token_ids", ] def __init__( self, input_ids=None, attention_mask=None, token_type_ids=None, input_embeds=None, mask_ids=None, label=None, cls_label=None, seq_label=None, uid=None, soft_token_ids=None, ): self.input_ids = input_ids self.attention_mask = attention_mask self.token_type_ids = token_type_ids self.input_embeds = input_embeds self.label = label self.cls_label = cls_label self.seq_label = seq_label self.mask_ids = mask_ids self.uid = uid self.soft_token_ids = soft_token_ids @classmethod def add_keys(cls, *args): cls.input_keys.extend(args) def keys(self, keep_none=False): if keep_none: return self.input_keys else: return [key for key in self.input_keys if getattr(self, key) is not None] def values(self, keep_none=False): return [getattr(self, key) for key in self.keys(keep_none=keep_none)] def items(self): return [(key, getattr(self, key)) for key in self.keys()] def __len__(self): return len(self.keys()) def __repr__(self): return str(json.dumps(self.items()) + "\n") def __getitem__(self, key): return getattr(self, key) def __iter__(self): return iter(self.keys()) def __contains__(self, key, keep_none): return key in self.keys(keep_none) def __setitem__(self, key, value): if key not in self.input_keys: raise KeyError("{} not in predefined keys, use add_keys to add it.".format(key)) setattr(self, key, value) @staticmethod def collate_fn(batch): """Collate batch data in form of InputFeatures.""" new_batch = {} for key in batch[0]: values = [b[key] for b in batch] try: new_batch[key] = paddle.to_tensor(values) except ValueError: new_batch[key] = values return InputFeatures(**new_batch) class DataProcessor(object): """Base class for reading datasets from files.""" def __init__(self, labels=None): self._labels = labels if labels is not None: self._labels = sorted(labels) @property def labels(self): if not getattr(self, "_labels"): raise ValueError("labels and label_mappings are not set yet.") return self._labels @labels.setter def labels(self, labels): if labels is not None: self._labels = sorted(labels) @property def label_mapping(self): if not getattr(self, "_labels"): raise ValueError("labels and label_mappings are not set yet.") if not getattr(self, "_label_mapping"): self._label_mapping = {k: i for i, k in enumerate(self._labels)} return self._label_mapping @label_mapping.setter def label_mapping(self, label_mapping): if getattr(self, "_labels"): assert self._labels == sorted(list(label_mapping.keys())) self._label_mapping = label_mapping @abstractmethod def get_examples(self, data_dir, split): raise NotImplementedError def get_train_examples(self, data_dir): return self.get_examples(data_dir, "train") def get_dev_examples(self, data_dir): return self.get_examples(data_dir, "dev") def get_test_exaples(self, data_dir): return self.get_examples(data_dir, "test") @classmethod def read_tsv(cls, input_file, quotechar=None): with open(input_file, "r", encoding="utf-8-sig") as f: data = csv.reader(f, delimiter="\t", quotechar=quotechar) return [x for x in data] @classmethod def read_csv(cls, input_file, header=None): data = pd.read_csv(input_file, header=header) return data.values.tolist() @classmethod def read_json(cls, input_file): with open(input_file, "r") as f: data = [json.loads(x) for x in f.readlines()] return data class BoolQProcessor(DataProcessor): def __init__(self): super().__init__(["False", "True"]) self.split_map = {"train": "train", "dev": "dev32", "test": "val"} def get_examples(self, data_dir, split): split = self.split_map[split] raw_data = self.read_json(os.path.join(data_dir, split + ".jsonl")) examples = [] for i, line in enumerate(raw_data): examples.append( InputExample( uid="%s-%d" % (split, i), text_a=line["passage"], text_b=line["question"], cls_label=str(line["label"]), ) ) return examples class MrpcProcesser(DataProcessor): def __init__(self): super().__init__(["0", "1"]) def get_examples(self, data_dir, split): raw_data = self.read_tsv(os.path.join(data_dir, split + ".tsv")) examples = [] for i, line in enumerate(raw_data): if i == 0: continue examples.append(InputExample(uid="%s-%d" % (split, i), text_a=line[3], text_b=line[4], cls_label=line[0])) return examples class MnliProcessor(DataProcessor): def __init__(self): super().__init__(["contradiction", "entailment", "neutral"]) def _process_file(self, split): if split in ["dev", "test"]: return split + "_matched" return split def get_examples(self, data_dir, split): split = self._process_file(split) raw_data = self.read_tsv(os.path.join(data_dir, split + ".tsv")) examples = [] for i, line in enumerate(raw_data): if i == 0: continue examples.append( InputExample(uid="%s-%s" % (split, line[0]), text_a=line[8], text_b=line[9], cls_label=line[-1]) ) return examples class MnliMismatchedProcessor(MnliProcessor): def _process_file(self, split): if split == "dev": return split + "_matched" if split == "test": return split + "_mismatched" return split class SnliProcessor(DataProcessor): def __init__(self): super().__init__(["contradiction", "entailment", "neutral"]) def get_examples(self, data_dir, split): raw_data = self.read_tsv(os.path.join(data_dir, split + ".tsv")) examples = [] for i, line in enumerate(raw_data): if i == 0: continue examples.append( InputExample(uid="%s-%s" % (split, line[0]), text_a=line[7], text_b=line[8], cls_label=line[-1]) ) return examples class ColaProcessor(DataProcessor): def __init__(self): super().__init__(["0", "1"]) def get_examples(self, data_dir, split): raw_data = self.read_tsv(os.path.join(data_dir, split + ".tsv")) examples = [] for i, line in enumerate(raw_data): examples.append(InputExample(uid="%s-%d" % (split, i), text_a=line[3], text_b=None, cls_label=line[1])) return examples class Sst2Processor(DataProcessor): def __init__(self): super().__init__(["0", "1"]) def get_examples(self, data_dir, split): raw_data = self.read_tsv(os.path.join(data_dir, split + ".tsv")) examples = [] for i, line in enumerate(raw_data): if i == 0: continue examples.append(InputExample(uid="%s-%d" % (split, i), text_a=line[0], text_b=None, cls_label=line[1])) return examples class StsbProcessor(DataProcessor): def __init__(self): super().__init__(["0", "1"]) def get_examples(self, data_dir, split): raw_data = self.read_tsv(os.path.join(data_dir, split + ".tsv")) examples = [] for i, line in enumerate(raw_data): if i == 0: continue examples.append( InputExample(uid="%s-%s" % (split, line[0]), text_a=line[7], text_b=line[8], cls_label=line[-1]) ) return examples class QqpProcessor(DataProcessor): def __init__(self): super().__init__(["0", "1"]) def get_examples(self, data_dir, split): raw_data = self.read_tsv(os.path.join(data_dir, split + ".tsv")) examples = [] for i, line in enumerate(raw_data): if i == 0: continue try: examples.append( InputExample(uid="%s-%s" % (split, line[0]), text_a=line[3], text_b=line[4], cls_label=line[5]) ) except IndexError: continue return examples class QnliProcessor(DataProcessor): def __init__(self): super().__init__(["entailment", "not_entailment"]) def get_examples(self, data_dir, split): raw_data = self.read_tsv(os.path.join(data_dir, split + ".tsv")) examples = [] for i, line in enumerate(raw_data): if i == 0: continue examples.append( InputExample(uid="%s-%s" % (split, line[0]), text_a=line[1], text_b=line[2], cls_label=line[-1]) ) return examples class RteProcessor(DataProcessor): def __init__(self): super().__init__(["entailment", "not_entailment"]) def get_examples(self, data_dir, split): raw_data = self.read_tsv(os.path.join(data_dir, split + ".tsv")) examples = [] for i, line in enumerate(raw_data): if i == 0: continue examples.append( InputExample(uid="%s-%s" % (split, line[0]), text_a=line[1], text_b=line[2], cls_label=line[-1]) ) return examples class WnliProcessor(DataProcessor): def __init__(self): super().__init__(["0", "1"]) def get_examples(self, data_dir, split): raw_data = self.read_tsv(os.path.join(data_dir, split + ".tsv")) examples = [] for i, line in enumerate(raw_data): if i == 0: continue examples.append( InputExample(uid="%s-%s" % (split, line[0]), text_a=line[1], text_b=line[2], cls_label=line[-1]) ) return examples class TextClassificationProcessor(DataProcessor): def __init__(self, task_name): NUM_LABELS = {"mr": 2, "sst-5": 5, "subj": 2, "trec": 6, "cr": 2, "mpqa": 2} assert task_name in NUM_LABELS, "task_name not supported." self.task_name = task_name self._labels = list(range(NUM_LABELS[self.task_name])) def get_examples(self, data_dir, split): raw_data = self.read_csv(os.path.join(data_dir, split + ".csv")) examples = [] for i, line in enumerate(raw_data): examples.append(InputExample(uid="%s-%d" % (split, i), text_a=line[1], cls_label=line[0])) return examples # The processor mapping for datasets in RGL paper. PROCESSOR_MAPPING = { "mrpc": MrpcProcesser(), "mnli": MnliProcessor(), "mnli-mm": MnliMismatchedProcessor(), "snli": SnliProcessor(), "cola": ColaProcessor(), "sst-2": Sst2Processor(), "sts-b": StsbProcessor(), "qqp": QqpProcessor(), "qnli": QnliProcessor(), "rte": RteProcessor(), "wnli": WnliProcessor(), "cr": TextClassificationProcessor("cr"), "mr": TextClassificationProcessor("mr"), "sst-5": TextClassificationProcessor("sst-5"), "subj": TextClassificationProcessor("subj"), "mpqa": TextClassificationProcessor("mpqa"), "trec": TextClassificationProcessor("trec"), "boolq": BoolQProcessor(), } # The task mapping for datasets. TASK_MAPPING = defaultdict(lambda: "classification") TASK_MAPPING["sts-b"] = "regression" # The metric mapping for datasets. METRIC_MAPPING = defaultdict(Accuracy) METRIC_MAPPING.update( { "mrpc": AccuracyAndF1(name=["acc", "precision", "recall", "f1", "acc_and_f1"]), "qqp": AccuracyAndF1(name=["acc", "precision", "recall", "f1", "acc_and_f1"]), "cola": Mcc(), "sts-b": PearsonAndSpearman(name=["pearson", "spearman", "corr"]), } ) def load_dataset(dataset, data_path=None, splits=[]): """ Read datasets from files. Args: dataset (str): The dataset name in lowercase. data_path (str): The path to the dataset directory, including train, dev or test file. splits (list): Which file(s) of dataset to read, such as ['train', 'dev', 'test']. """ assert len(splits) > 0, "No splits, can not load dataset {}".format(dataset) processor = PROCESSOR_MAPPING[dataset] data = [] if "train" in splits: train_examples = processor.get_train_examples(data_path) data.append(MapDataset(train_examples)) if "dev" in splits: dev_examples = processor.get_dev_examples(data_path) data.append(MapDataset(dev_examples)) if "test" in splits: test_examples = processor.get_test_exaples(data_path) data.append(MapDataset(test_examples)) data.append(processor.labels) return data