442 lines
20 KiB
Python
442 lines
20 KiB
Python
# coding:utf-8
|
|
# Copyright (c) 2021 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.
|
|
|
|
from typing import Any, Dict, List, Union
|
|
|
|
import numpy as np
|
|
from paddle.static import InputSpec
|
|
from scipy.special import expit as np_sigmoid
|
|
from scipy.special import softmax as np_softmax
|
|
|
|
from ..prompt import PromptDataCollatorWithPadding, UTCTemplate
|
|
from ..transformers import UTC, AutoTokenizer
|
|
from .task import Task
|
|
from .utils import static_mode_guard
|
|
|
|
usage = r"""
|
|
from paddlenlp import Taskflow
|
|
|
|
schema = ['这是一条差评', '这是一条好评']
|
|
text_cls = Taskflow("zero_shot_text_classification", schema=schema)
|
|
text_cls('房间干净明亮,非常不错')
|
|
'''
|
|
[{'predictions': [{'label': '这是一条好评', 'score': 0.9695149765679986}], 'text_a': '房间干净明亮,非常不错'}]
|
|
'''
|
|
"""
|
|
|
|
|
|
class ZeroShotTextClassificationTask(Task):
|
|
"""
|
|
Zero-shot Universal Text Classification Task.
|
|
|
|
Args:
|
|
task (string): The name of task.
|
|
model (string): The model_name in the task.
|
|
schema (list): List of candidate labels.
|
|
kwargs (dict, optional): Additional keyword arguments passed along to the specific task.
|
|
"""
|
|
|
|
resource_files_names = {
|
|
"model_state": "model_state.pdparams",
|
|
"config": "config.json",
|
|
"vocab_file": "vocab.txt",
|
|
"special_tokens_map": "special_tokens_map.json",
|
|
"tokenizer_config": "tokenizer_config.json",
|
|
}
|
|
resource_files_urls = {
|
|
"utc-xbase": {
|
|
"model_state": [
|
|
"https://paddlenlp.bj.bcebos.com/taskflow/zero_shot_text_classification/utc-xbase/model_state.pdparams",
|
|
"e751c3a78d4caff923759c0d0547bfe6",
|
|
],
|
|
"config": [
|
|
"https://paddlenlp.bj.bcebos.com/taskflow/zero_shot_text_classification/utc-xbase/config.json",
|
|
"4c2b035c71ff226a14236171a1a202a4",
|
|
],
|
|
"vocab_file": [
|
|
"https://paddlenlp.bj.bcebos.com/taskflow/zero_shot_text_classification/utc-xbase/vocab.txt",
|
|
"97eb0ec5a5890c8190e10e251af2e133",
|
|
],
|
|
"special_tokens_map": [
|
|
"https://paddlenlp.bj.bcebos.com/taskflow/zero_shot_text_classification/utc-xbase/special_tokens_map.json",
|
|
"8b3fb1023167bb4ab9d70708eb05f6ec",
|
|
],
|
|
"tokenizer_config": [
|
|
"https://paddlenlp.bj.bcebos.com/taskflow/zero_shot_text_classification/utc-xbase/tokenizer_config.json",
|
|
"be86466f6769fde498690269d099ea7c",
|
|
],
|
|
},
|
|
"utc-base": {
|
|
"model_state": [
|
|
"https://paddlenlp.bj.bcebos.com/taskflow/zero_shot_text_classification/utc-base/model_state.pdparams",
|
|
"72089351c6fb02bcf8f270fe0cc508e9",
|
|
],
|
|
"config": [
|
|
"https://paddlenlp.bj.bcebos.com/taskflow/zero_shot_text_classification/utc-base/config.json",
|
|
"79aa9a69286604436937b03f429f4d34",
|
|
],
|
|
"vocab_file": [
|
|
"https://paddlenlp.bj.bcebos.com/taskflow/zero_shot_text_classification/utc-base/vocab.txt",
|
|
"97eb0ec5a5890c8190e10e251af2e133",
|
|
],
|
|
"special_tokens_map": [
|
|
"https://paddlenlp.bj.bcebos.com/taskflow/zero_shot_text_classification/utc-base/special_tokens_map.json",
|
|
"8b3fb1023167bb4ab9d70708eb05f6ec",
|
|
],
|
|
"tokenizer_config": [
|
|
"https://paddlenlp.bj.bcebos.com/taskflow/zero_shot_text_classification/utc-base/tokenizer_config.json",
|
|
"be86466f6769fde498690269d099ea7c",
|
|
],
|
|
},
|
|
"utc-medium": {
|
|
"model_state": [
|
|
"https://paddlenlp.bj.bcebos.com/taskflow/zero_shot_text_classification/utc-medium/model_state.pdparams",
|
|
"2802c766a8b880aad910dd5a7db809ae",
|
|
],
|
|
"config": [
|
|
"https://paddlenlp.bj.bcebos.com/taskflow/zero_shot_text_classification/utc-medium/config.json",
|
|
"2899cd7c8590dcdc4223e4b1262e2f4e",
|
|
],
|
|
"vocab_file": [
|
|
"https://paddlenlp.bj.bcebos.com/taskflow/zero_shot_text_classification/utc-medium/vocab.txt",
|
|
"97eb0ec5a5890c8190e10e251af2e133",
|
|
],
|
|
"special_tokens_map": [
|
|
"https://paddlenlp.bj.bcebos.com/taskflow/zero_shot_text_classification/utc-medium/special_tokens_map.json",
|
|
"8b3fb1023167bb4ab9d70708eb05f6ec",
|
|
],
|
|
"tokenizer_config": [
|
|
"https://paddlenlp.bj.bcebos.com/taskflow/zero_shot_text_classification/utc-medium/tokenizer_config.json",
|
|
"be86466f6769fde498690269d099ea7c",
|
|
],
|
|
},
|
|
"utc-micro": {
|
|
"model_state": [
|
|
"https://paddlenlp.bj.bcebos.com/taskflow/zero_shot_text_classification/utc-micro/model_state.pdparams",
|
|
"d9ebdfce9a8c6ebda43630ed18b07c58",
|
|
],
|
|
"config": [
|
|
"https://paddlenlp.bj.bcebos.com/taskflow/zero_shot_text_classification/utc-micro/config.json",
|
|
"8c8da9337e09e0c3962196987dca18bd",
|
|
],
|
|
"vocab_file": [
|
|
"https://paddlenlp.bj.bcebos.com/taskflow/zero_shot_text_classification/utc-micro/vocab.txt",
|
|
"97eb0ec5a5890c8190e10e251af2e133",
|
|
],
|
|
"special_tokens_map": [
|
|
"https://paddlenlp.bj.bcebos.com/taskflow/zero_shot_text_classification/utc-micro/special_tokens_map.json",
|
|
"8b3fb1023167bb4ab9d70708eb05f6ec",
|
|
],
|
|
"tokenizer_config": [
|
|
"https://paddlenlp.bj.bcebos.com/taskflow/zero_shot_text_classification/utc-micro/tokenizer_config.json",
|
|
"be86466f6769fde498690269d099ea7c",
|
|
],
|
|
},
|
|
"utc-mini": {
|
|
"model_state": [
|
|
"https://paddlenlp.bj.bcebos.com/taskflow/zero_shot_text_classification/utc-mini/model_state.pdparams",
|
|
"848a2870cd51bfc22174a2a38884085c",
|
|
],
|
|
"config": [
|
|
"https://paddlenlp.bj.bcebos.com/taskflow/zero_shot_text_classification/utc-mini/config.json",
|
|
"933b8ebfcf995b1f965764ac426a2ffa",
|
|
],
|
|
"vocab_file": [
|
|
"https://paddlenlp.bj.bcebos.com/taskflow/zero_shot_text_classification/utc-mini/vocab.txt",
|
|
"97eb0ec5a5890c8190e10e251af2e133",
|
|
],
|
|
"special_tokens_map": [
|
|
"https://paddlenlp.bj.bcebos.com/taskflow/zero_shot_text_classification/utc-mini/special_tokens_map.json",
|
|
"8b3fb1023167bb4ab9d70708eb05f6ec",
|
|
],
|
|
"tokenizer_config": [
|
|
"https://paddlenlp.bj.bcebos.com/taskflow/zero_shot_text_classification/utc-mini/tokenizer_config.json",
|
|
"be86466f6769fde498690269d099ea7c",
|
|
],
|
|
},
|
|
"utc-nano": {
|
|
"model_state": [
|
|
"https://paddlenlp.bj.bcebos.com/taskflow/zero_shot_text_classification/utc-nano/model_state.pdparams",
|
|
"2bd31212d989619148eda3afebc7354d",
|
|
],
|
|
"config": [
|
|
"https://paddlenlp.bj.bcebos.com/taskflow/zero_shot_text_classification/utc-nano/config.json",
|
|
"02fe311fdcc127e56ff0975038cc4d65",
|
|
],
|
|
"vocab_file": [
|
|
"https://paddlenlp.bj.bcebos.com/taskflow/zero_shot_text_classification/utc-nano/vocab.txt",
|
|
"97eb0ec5a5890c8190e10e251af2e133",
|
|
],
|
|
"special_tokens_map": [
|
|
"https://paddlenlp.bj.bcebos.com/taskflow/zero_shot_text_classification/utc-nano/special_tokens_map.json",
|
|
"8b3fb1023167bb4ab9d70708eb05f6ec",
|
|
],
|
|
"tokenizer_config": [
|
|
"https://paddlenlp.bj.bcebos.com/taskflow/zero_shot_text_classification/utc-nano/tokenizer_config.json",
|
|
"be86466f6769fde498690269d099ea7c",
|
|
],
|
|
},
|
|
"utc-pico": {
|
|
"model_state": [
|
|
"https://paddlenlp.bj.bcebos.com/taskflow/zero_shot_text_classification/utc-pico/model_state.pdparams",
|
|
"f7068d63ad2930de7ac850d475052946",
|
|
],
|
|
"config": [
|
|
"https://paddlenlp.bj.bcebos.com/taskflow/zero_shot_text_classification/utc-pico/config.json",
|
|
"c0c7412cdd070edb5a1ce70c7fc68ad3",
|
|
],
|
|
"vocab_file": [
|
|
"https://paddlenlp.bj.bcebos.com/taskflow/zero_shot_text_classification/utc-pico/vocab.txt",
|
|
"97eb0ec5a5890c8190e10e251af2e133",
|
|
],
|
|
"special_tokens_map": [
|
|
"https://paddlenlp.bj.bcebos.com/taskflow/zero_shot_text_classification/utc-pico/special_tokens_map.json",
|
|
"8b3fb1023167bb4ab9d70708eb05f6ec",
|
|
],
|
|
"tokenizer_config": [
|
|
"https://paddlenlp.bj.bcebos.com/taskflow/zero_shot_text_classification/utc-pico/tokenizer_config.json",
|
|
"be86466f6769fde498690269d099ea7c",
|
|
],
|
|
},
|
|
"utc-large": {
|
|
"model_state": [
|
|
"https://bj.bcebos.com/paddlenlp/taskflow/zero_shot_text_classification/utc-large/model_state.pdparams",
|
|
"71eb9a732c743a513b84ca048dc4945b",
|
|
],
|
|
"config": [
|
|
"https://bj.bcebos.com/paddlenlp/taskflow/zero_shot_text_classification/utc-large/config.json",
|
|
"9496be2cc99f7e6adf29280320274142",
|
|
],
|
|
"vocab_file": [
|
|
"https://bj.bcebos.com/paddlenlp/taskflow/zero_text_classification/utc-large/vocab.txt",
|
|
"afc01b5680a53525df5afd7518b42b48",
|
|
],
|
|
"special_tokens_map": [
|
|
"https://bj.bcebos.com/paddlenlp/taskflow/zero_text_classification/utc-large/special_tokens_map.json",
|
|
"2458e2131219fc1f84a6e4843ae07008",
|
|
],
|
|
"tokenizer_config": [
|
|
"https://bj.bcebos.com/paddlenlp/taskflow/zero_text_classification/utc-large/tokenizer_config.json",
|
|
"dcb0f3257830c0eb1f2de47f2d86f89a",
|
|
],
|
|
},
|
|
"__internal_testing__/tiny-random-utc": {
|
|
"model_state": [
|
|
"https://bj.bcebos.com/paddlenlp/models/community/__internal_testing__/tiny-random-utc/model_state.pdparams",
|
|
"d303b59447be690530c35c73f8fd03cd",
|
|
],
|
|
"config": [
|
|
"https://bj.bcebos.com/paddlenlp/models/community/__internal_testing__/tiny-random-utc/config.json",
|
|
"3420a6638a7c73c6239eb1d7ca1bc5fe",
|
|
],
|
|
"vocab_file": [
|
|
"https://bj.bcebos.com/paddlenlp/models/community/__internal_testing__/tiny-random-utc/vocab.txt",
|
|
"97eb0ec5a5890c8190e10e251af2e133",
|
|
],
|
|
"special_tokens_map": [
|
|
"https://bj.bcebos.com/paddlenlp/models/community/__internal_testing__/tiny-random-utc/special_tokens_map.json",
|
|
"8b3fb1023167bb4ab9d70708eb05f6ec",
|
|
],
|
|
"tokenizer_config": [
|
|
"https://bj.bcebos.com/paddlenlp/models/community/__internal_testing__/tiny-random-utc/tokenizer_config.json",
|
|
"258fc552c15cec90046066ca122899e2",
|
|
],
|
|
},
|
|
}
|
|
|
|
def __init__(self, task: str, model: str, schema: list = None, **kwargs):
|
|
super().__init__(task=task, model=model, **kwargs)
|
|
|
|
self._static_mode = False
|
|
self._set_utc_schema(schema)
|
|
self._max_seq_len = kwargs.get("max_seq_len", 512)
|
|
self._batch_size = kwargs.get("batch_size", 1)
|
|
self._pred_threshold = kwargs.get("pred_threshold", 0.5)
|
|
self._num_workers = kwargs.get("num_workers", 0)
|
|
self._single_label = kwargs.get("single_label", False)
|
|
|
|
self._check_task_files()
|
|
self._construct_tokenizer()
|
|
self._check_predictor_type()
|
|
if self._static_mode:
|
|
self._get_inference_model()
|
|
else:
|
|
self._construct_model(model)
|
|
|
|
def _set_utc_schema(self, schema):
|
|
if schema is None:
|
|
self._choices = None
|
|
elif isinstance(schema, list):
|
|
self._choices = schema
|
|
elif isinstance(schema, dict) and len(schema) == 1:
|
|
for key in schema:
|
|
self._choices = schema[key]
|
|
else:
|
|
raise ValueError(f"Invalid schema: {schema}.")
|
|
|
|
def set_schema(self, schema):
|
|
self._set_utc_schema(schema)
|
|
|
|
def _construct_input_spec(self):
|
|
"""
|
|
Construct the input spec for the convert dygraph model to static model.
|
|
"""
|
|
self._input_spec = [
|
|
InputSpec(shape=[None, None], dtype="int64", name="input_ids"),
|
|
InputSpec(shape=[None, None], dtype="int64", name="token_type_ids"),
|
|
InputSpec(shape=[None, None], dtype="int64", name="position_ids"),
|
|
InputSpec(shape=[None, None], dtype="float32", name="attention_mask"),
|
|
InputSpec(shape=[None, None], dtype="int64", name="omask_positions"),
|
|
InputSpec(shape=[None], dtype="int64", name="cls_positions"),
|
|
]
|
|
|
|
def _construct_model(self, model):
|
|
"""
|
|
Construct the inference model for the predictor.
|
|
"""
|
|
model_instance = UTC.from_pretrained(self._task_path, from_hf_hub=self.from_hf_hub)
|
|
self._model = model_instance
|
|
self._model.eval()
|
|
|
|
def _construct_tokenizer(self):
|
|
"""
|
|
Construct the tokenizer for the predictor.
|
|
"""
|
|
self._tokenizer = AutoTokenizer.from_pretrained(self._task_path, from_hf_hub=self.from_hf_hub)
|
|
if self._static_mode:
|
|
self._collator = PromptDataCollatorWithPadding(self._tokenizer, return_tensors="np")
|
|
else:
|
|
self._collator = PromptDataCollatorWithPadding(self._tokenizer, return_tensors="pd")
|
|
self._template = UTCTemplate(self._tokenizer, self._max_seq_len)
|
|
|
|
def _check_input_text(self, inputs):
|
|
inputs = inputs[0]
|
|
if isinstance(inputs, str) or isinstance(inputs, dict):
|
|
inputs = [inputs]
|
|
|
|
if isinstance(inputs, list):
|
|
input_list = []
|
|
for example in inputs:
|
|
data = {"text_a": "", "text_b": "", "choices": self._choices}
|
|
if isinstance(example, dict):
|
|
for k in example:
|
|
if k in data:
|
|
data[k] = example[k]
|
|
elif isinstance(example, str):
|
|
data["text_a"] = example
|
|
data["text_b"] = ""
|
|
elif isinstance(example, list):
|
|
for x in example:
|
|
if not isinstance(x, str):
|
|
raise ValueError("Invalid inputs, input text should be strings.")
|
|
data["text_a"] = example[0]
|
|
data["text_b"] = "".join(example[1:]) if len(example) > 1 else ""
|
|
else:
|
|
raise ValueError(
|
|
"Invalid inputs, the input should be {'text_a': a, 'text_b': b}, a text or a list of text."
|
|
)
|
|
|
|
if len(data["text_a"]) < 1 and len(data["text_b"]) < 1:
|
|
raise ValueError("Invalid inputs, input `text_a` and `text_b` are both missing or empty.")
|
|
if not isinstance(data["choices"], list) or len(data["choices"]) < 2:
|
|
raise ValueError("Invalid inputs, label candidates should be a list with length >= 2.")
|
|
input_list.append(data)
|
|
else:
|
|
raise TypeError("Invalid input format!")
|
|
return input_list
|
|
|
|
def _preprocess(self, inputs: Union[str, List[str]]) -> Dict[str, Any]:
|
|
"""
|
|
Transform the raw text to the model inputs, two steps involved:
|
|
1) Transform the raw text to token ids.
|
|
2) Generate the other model inputs from the raw text and token ids.
|
|
"""
|
|
inputs = self._check_input_text(inputs)
|
|
# Get the config from the kwargs
|
|
tokenized_inputs = [self._template(i) for i in inputs]
|
|
batches = [
|
|
tokenized_inputs[idx : idx + self._batch_size] for idx in range(0, len(tokenized_inputs), self._batch_size)
|
|
]
|
|
inputs = [inputs[idx : idx + self._batch_size] for idx in range(0, len(inputs), self._batch_size)]
|
|
outputs = {}
|
|
outputs["text"] = inputs
|
|
outputs["batches"] = [self._collator(batch) for batch in batches]
|
|
|
|
return outputs
|
|
|
|
def _run_model(self, inputs: Dict[str, Any]) -> Dict[str, Any]:
|
|
outputs = {}
|
|
outputs["text"] = inputs["text"]
|
|
outputs["batch_logits"] = []
|
|
dtype_dict = {
|
|
"input_ids": "int64",
|
|
"token_type_ids": "int64",
|
|
"position_ids": "int64",
|
|
"attention_mask": "float32",
|
|
"omask_positions": "int64",
|
|
"cls_positions": "int64",
|
|
}
|
|
if self._static_mode:
|
|
with static_mode_guard():
|
|
for batch in inputs["batches"]:
|
|
if self._predictor_type == "paddle-inference":
|
|
for i, input_name in enumerate(self.input_names):
|
|
self.input_handles[i].copy_from_cpu(batch[input_name].astype(dtype_dict[input_name]))
|
|
self.predictor.run()
|
|
logits = self.output_handle[0].copy_to_cpu().tolist()
|
|
else:
|
|
input_dict = {}
|
|
for input_name in dtype_dict:
|
|
input_dict[input_name] = batch[input_name].astype(dtype_dict[input_name])
|
|
logits = self.predictor.run(None, input_dict)[0].tolist()
|
|
outputs["batch_logits"].append(logits)
|
|
else:
|
|
for batch in inputs["batches"]:
|
|
if batch["soft_token_ids"] is not None:
|
|
del batch["soft_token_ids"]
|
|
logits = self._model(**batch)
|
|
outputs["batch_logits"].append(np.array(logits))
|
|
|
|
return outputs
|
|
|
|
def _postprocess(self, inputs: Dict[str, Any]) -> Dict[str, Any]:
|
|
"""
|
|
This function converts the model logits output to class score and predictions
|
|
"""
|
|
outputs = []
|
|
for batch_text, batch_logits in zip(inputs["text"], inputs["batch_logits"]):
|
|
for text, logits in zip(batch_text, batch_logits):
|
|
output = {}
|
|
if len(text["text_a"]) > 0:
|
|
output["text_a"] = text["text_a"]
|
|
if len(text["text_b"]) > 0:
|
|
output["text_b"] = text["text_b"]
|
|
|
|
if self._single_label:
|
|
score = np_softmax(logits, axis=-1)
|
|
label = np.argmax(logits, axis=-1)
|
|
output["predictions"] = [{"label": text["choices"][label], "score": score[label]}]
|
|
else:
|
|
scores = np_sigmoid(logits)
|
|
output["predictions"] = []
|
|
if scores.ndim == 2:
|
|
scores = scores[0]
|
|
for i, class_score in enumerate(scores):
|
|
if class_score > self._pred_threshold:
|
|
output["predictions"].append({"label": text["choices"][i], "score": class_score})
|
|
outputs.append(output)
|
|
|
|
return outputs
|