Kalaoke commited on
Commit
595bda0
·
1 Parent(s): 40c5367

Upload handler.py

Browse files
Files changed (1) hide show
  1. handler.py +270 -0
handler.py ADDED
@@ -0,0 +1,270 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from dataclasses import dataclass
2
+ import torch
3
+ import numpy as np
4
+ import transformers
5
+ from torch import nn
6
+ from torch.nn import BCEWithLogitsLoss, CrossEntropyLoss, MSELoss
7
+ from typing import List, Optional, Tuple, Union, Dict, Any
8
+
9
+ from transformers import BertTokenizer, Pipeline
10
+ from transformers import models, DataCollatorWithPadding, AutoTokenizer
11
+ from transformers.modeling_outputs import SequenceClassifierOutput
12
+ from transformers.pipelines import PIPELINE_REGISTRY
13
+
14
+ from transformers.models.bert.configuration_bert import BertConfig
15
+ from transformers.models.bert.modeling_bert import (
16
+ BertPreTrainedModel,
17
+ BERT_INPUTS_DOCSTRING,
18
+ _TOKENIZER_FOR_DOC,
19
+ _CHECKPOINT_FOR_DOC,
20
+ BERT_START_DOCSTRING,
21
+ _CONFIG_FOR_DOC,
22
+ _SEQ_CLASS_EXPECTED_OUTPUT,
23
+ _SEQ_CLASS_EXPECTED_LOSS,
24
+ BertModel,
25
+ )
26
+
27
+ from transformers.file_utils import (
28
+ add_code_sample_docstrings,
29
+ add_start_docstrings_to_model_forward,
30
+ add_start_docstrings
31
+ )
32
+
33
+ @dataclass
34
+ class Task:
35
+ id: int
36
+ name: str
37
+ type: str
38
+ num_labels: int
39
+
40
+
41
+ @add_start_docstrings(
42
+ """
43
+ Bert Model transformer with a sequence classification/regression head on top (a linear layer on top of the pooled
44
+ output) e.g. for GLUE tasks.
45
+ """,
46
+ BERT_START_DOCSTRING,
47
+ )
48
+ class BertForSequenceClassification(BertPreTrainedModel):
49
+ def __init__(self, config, **kwargs):
50
+ super().__init__(transformers.PretrainedConfig())
51
+ #task_labels_map={"binary_classification": 2, "label_classification": 5}
52
+ self.tasks = kwargs.get("tasks_map", {})
53
+ self.config = config
54
+
55
+ self.bert = BertModel(config)
56
+ classifier_dropout = (
57
+ config.classifier_dropout
58
+ if config.classifier_dropout is not None
59
+ else config.hidden_dropout_prob
60
+ )
61
+ self.dropout = nn.Dropout(classifier_dropout)
62
+ ## add task specific output heads
63
+ self.classifier1 = nn.Linear(
64
+ config.hidden_size, self.tasks[0].num_labels
65
+ )
66
+ self.classifier2 = nn.Linear(
67
+ config.hidden_size, self.tasks[1].num_labels
68
+ )
69
+
70
+ self.init_weights()
71
+
72
+ @add_start_docstrings_to_model_forward(
73
+ BERT_INPUTS_DOCSTRING.format("batch_size, sequence_length")
74
+ )
75
+ @add_code_sample_docstrings(
76
+ processor_class=_TOKENIZER_FOR_DOC,
77
+ checkpoint=_CHECKPOINT_FOR_DOC,
78
+ output_type=SequenceClassifierOutput,
79
+ config_class=_CONFIG_FOR_DOC,
80
+ expected_output=_SEQ_CLASS_EXPECTED_OUTPUT,
81
+ expected_loss=_SEQ_CLASS_EXPECTED_LOSS,
82
+ )
83
+ def forward(
84
+ self,
85
+ input_ids: Optional[torch.Tensor] = None,
86
+ attention_mask: Optional[torch.Tensor] = None,
87
+ token_type_ids: Optional[torch.Tensor] = None,
88
+ position_ids: Optional[torch.Tensor] = None,
89
+ head_mask: Optional[torch.Tensor] = None,
90
+ inputs_embeds: Optional[torch.Tensor] = None,
91
+ labels: Optional[torch.Tensor] = None,
92
+ output_attentions: Optional[bool] = None,
93
+ output_hidden_states: Optional[bool] = None,
94
+ return_dict: Optional[bool] = None,
95
+ task_ids=None,
96
+ ) -> Union[Tuple[torch.Tensor], SequenceClassifierOutput]:
97
+ r"""
98
+ labels (:obj:`torch.LongTensor` of shape :obj:`(batch_size,)`, `optional`):
99
+ Labels for computing the sequence classification/regression loss. Indices should be in :obj:`[0, ...,
100
+ config.num_labels - 1]`. If :obj:`config.num_labels == 1` a regression loss is computed (Mean-Square loss),
101
+ If :obj:`config.num_labels > 1` a classification loss is computed (Cross-Entropy).
102
+ """
103
+ return_dict = (
104
+ return_dict if return_dict is not None else self.config.use_return_dict
105
+ )
106
+
107
+ outputs = self.bert(
108
+ input_ids,
109
+ attention_mask=attention_mask,
110
+ token_type_ids=token_type_ids,
111
+ position_ids=position_ids,
112
+ head_mask=head_mask,
113
+ inputs_embeds=inputs_embeds,
114
+ output_attentions=output_attentions,
115
+ output_hidden_states=output_hidden_states,
116
+ return_dict=return_dict,
117
+ )
118
+
119
+ pooled_output = outputs[1]
120
+
121
+ pooled_output = self.dropout(pooled_output)
122
+
123
+ unique_task_ids_list = torch.unique(task_ids).tolist()
124
+ loss_list = []
125
+ logits = None
126
+ for unique_task_id in unique_task_ids_list:
127
+
128
+ loss = None
129
+ task_id_filter = task_ids == unique_task_id
130
+
131
+ if unique_task_id == 0:
132
+ logits = self.classifier1(pooled_output[task_id_filter])
133
+ elif unique_task_id == 1:
134
+ logits = self.classifier2(pooled_output[task_id_filter])
135
+
136
+
137
+ if labels is not None:
138
+ loss_fct = CrossEntropyLoss()
139
+ loss = loss_fct(logits.view(-1, self.tasks[unique_task_id].num_labels), labels[task_id_filter].view(-1))
140
+ loss_list.append(loss)
141
+
142
+ # logits are only used for eval. and in case of eval the batch is not multi task
143
+ # For training only the loss is used
144
+
145
+ if loss_list:
146
+ loss = torch.stack(loss_list).mean()
147
+ if not return_dict:
148
+ output = (logits,) + outputs[2:]
149
+ return ((loss,) + output) if loss is not None else output
150
+
151
+ return SequenceClassifierOutput(
152
+ loss=loss,
153
+ logits=logits,
154
+ hidden_states=outputs.hidden_states,
155
+ attentions=outputs.attentions,
156
+ )
157
+
158
+ def softmax(_outputs):
159
+ maxes = np.max(_outputs, axis=-1, keepdims=True)
160
+ shifted_exp = np.exp(_outputs - maxes)
161
+ return shifted_exp / shifted_exp.sum(axis=-1, keepdims=True)
162
+
163
+ class BiBert_MultiTaskPipeline(Pipeline):
164
+
165
+
166
+ def _sanitize_parameters(self, **kwargs):
167
+
168
+ preprocess_kwargs = {}
169
+ if "task_id" in kwargs:
170
+ preprocess_kwargs["task_id"] = kwargs["task_id"]
171
+
172
+ forward_kwargs = {}
173
+ if "task_id" in kwargs:
174
+ forward_kwargs["task_id"] = kwargs["task_id"]
175
+
176
+ postprocess_kwargs = {}
177
+ if "top_k" in kwargs:
178
+ postprocess_kwargs["top_k"] = kwargs["top_k"]
179
+ postprocess_kwargs["_legacy"] = False
180
+ return preprocess_kwargs, forward_kwargs, postprocess_kwargs
181
+
182
+
183
+
184
+ def preprocess(self, inputs, task_id):
185
+ return_tensors = self.framework
186
+ feature = self.tokenizer(inputs, padding = True, return_tensors=return_tensors).to(self.device)
187
+ task_ids = np.full(shape=1,fill_value=task_id, dtype=int)
188
+ feature["task_ids"] = torch.IntTensor(task_ids)
189
+ return feature
190
+
191
+ def _forward(self, model_inputs, task_id):
192
+ return self.model(**model_inputs)
193
+
194
+ def postprocess(self, model_outputs, top_k=1, _legacy=True):
195
+ outputs = model_outputs["logits"][0]
196
+ outputs = outputs.numpy()
197
+ scores = softmax(outputs)
198
+
199
+ if top_k == 1 and _legacy:
200
+ return {"label": self.model.config.id2label[scores.argmax().item()], "score": scores.max().item()}
201
+
202
+ dict_scores = [
203
+ {"label": self.model.config.id2label[i], "score": score.item()} for i, score in enumerate(scores)
204
+ ]
205
+ if not _legacy:
206
+ dict_scores.sort(key=lambda x: x["score"], reverse=True)
207
+ if top_k is not None:
208
+ dict_scores = dict_scores[:top_k]
209
+ return dict_scores
210
+
211
+
212
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
213
+ checkpoint="HCKLab/BiBert-MultiTask-1"
214
+
215
+ tokenizer = AutoTokenizer.from_pretrained(checkpoint)
216
+
217
+ tasks = [
218
+ Task(id=0, name='label_classification', type='seq_classification', num_labels=5),
219
+ Task(id=1, name='binary_classification', type='seq_classification', num_labels=2)
220
+ ]
221
+
222
+ multitask_model = BertForSequenceClassification.from_pretrained(
223
+ checkpoint,
224
+ tasks_map=tasks
225
+ ).to(device)
226
+
227
+
228
+ PIPELINE_REGISTRY.register_pipeline(
229
+ "bibert-multitask-classification",
230
+ pipeline_class=BiBert_MultiTaskPipeline,
231
+ pt_model=BertForSequenceClassification
232
+ )
233
+
234
+ class EndpointHandler():
235
+ def __init__(self, path=""):
236
+ # Preload all the elements you are going to need at inference.
237
+ PIPELINE_REGISTRY.register_pipeline(
238
+ "bibert-multitask-classification",
239
+ pipeline_class=BiBert_MultiTaskPipeline,
240
+ pt_model=BertForSequenceClassification
241
+ )
242
+ self.classifier_s = pipeline("bibert-multitask-classification", model = multitask_model, task_id="0", tokenizer=tokenizer, device = device)
243
+ self.classifier_p = pipeline("bibert-multitask-classification", model = multitask_model, task_id="1", tokenizer=tokenizer, device = device)
244
+
245
+ def __call__(self, data: Dict[str, Any]) -> List[Dict[str, Any]]:
246
+ """
247
+ data args:
248
+ inputs (:obj: `str` | `PIL.Image` | `np.array`)
249
+ kwargs
250
+ Return:
251
+ A :obj:`list` | `dict`: will be serialized and returned
252
+ """
253
+ inputs = data.pop("text", data)
254
+ lang = data.pop("lang", data)
255
+ if isinstance(inputs, str):
256
+ inputs = [inputs]
257
+
258
+ prediction_p = self.classifier_p(inputs)
259
+ print(prediction_p)
260
+ label = prediction_p[0]['label']
261
+ score = prediction_p[0]['score']
262
+
263
+ if label == '0' and score >= 0.75:
264
+ label = 2
265
+ return {"label":label, "score": score}
266
+ else:
267
+ prediction_s = self.classifier_s(inputs)
268
+ label = prediction_s[0]['label']
269
+ score = prediction_s[0]['score']
270
+ return prediction_s