183 lines
5.7 KiB
Python
183 lines
5.7 KiB
Python
import torch, transformers
|
||
from typing import Any, Dict
|
||
from transformers import AutoTokenizer, AutoModelForCausalLM
|
||
import re
|
||
import unicodedata
|
||
|
||
|
||
class EndpointHandler:
|
||
def __init__(self, path):
|
||
tokenizer = AutoTokenizer.from_pretrained(path)
|
||
model = AutoModelForCausalLM.from_pretrained(
|
||
path, device_map="auto", torch_dtype=torch.bfloat16, load_in_4bit=True
|
||
)
|
||
self.pipeline = transformers.pipeline(
|
||
"text-generation", model=model, tokenizer=tokenizer
|
||
)
|
||
|
||
def __call__(self, data: Dict[str, Any]) -> Dict[str, str]:
|
||
# process input
|
||
inputs = data.pop("inputs", data)
|
||
|
||
# default parameters
|
||
parameters = {
|
||
"max_new_tokens": 128,
|
||
"do_sample": True,
|
||
"top_k": 10,
|
||
"temperature": 1.0,
|
||
"return_full_text": False,
|
||
}
|
||
|
||
# user parameters
|
||
parameters.update(data.pop("parameters", {}))
|
||
|
||
unique = isinstance(inputs, str)
|
||
inputs, denormalize_funcs = claire_text_preproc_conversation(inputs)
|
||
|
||
sequences = self.pipeline(inputs, **parameters)
|
||
|
||
if unique:
|
||
return [{"generated_text": denormalize_funcs(sequences[0]["generated_text"])}]
|
||
else:
|
||
assert len(denormalize_funcs) == len(sequences)
|
||
return [{"generated_text": denormalize_func(seq[0]["generated_text"])} for denormalize_func, seq in zip(denormalize_funcs, sequences)]
|
||
|
||
|
||
def claire_text_preproc_conversation(text):
|
||
if isinstance(text, (list, tuple)):
|
||
assert len(text)
|
||
# Apply and transpose
|
||
texts, denormalize_funcs = zip(*[claire_text_preproc_conversation(t) for t in text])
|
||
return list(texts), list(denormalize_funcs)
|
||
|
||
if not isinstance(text, str):
|
||
return text
|
||
|
||
text = format_special_characters(text)
|
||
|
||
text = re.sub(" - | -$|^- ", " ", text.strip(" "))
|
||
|
||
global _reverse_tag_transfo
|
||
_reverse_tag_transfo = {}
|
||
text = format_special_tags(text)
|
||
|
||
text = collapse_whitespaces_conversations(text)
|
||
|
||
if _reverse_tag_transfo:
|
||
reverse_tag_transfo = _reverse_tag_transfo.copy()
|
||
def denormalize_func(t):
|
||
for k, v in reverse_tag_transfo.items():
|
||
if k in t:
|
||
t = t.replace(k, v)
|
||
return t
|
||
|
||
return text, lambda x: denormalize_func(x)
|
||
|
||
else:
|
||
return text, lambda x: x
|
||
|
||
|
||
_brackets = re.compile(r"\[([^\]]*)\]")
|
||
_pattern_speaker = re.compile(r"[^\]]+:")
|
||
|
||
# Global variable to remember some normalizations that were done and apply it back
|
||
_reverse_tag_transfo = {}
|
||
_anonymized_prefix = None
|
||
|
||
|
||
def format_special_tags(text):
|
||
global _reverse_tag_transfo, _anonymized_prefix
|
||
_anonymized_prefix = None
|
||
text = re.sub(_brackets, _format_special_tags, text)
|
||
# At last the generic anonymization
|
||
if _anonymized_prefix:
|
||
_reverse_tag_transfo["[Intervenant "] = _anonymized_prefix
|
||
return text
|
||
|
||
|
||
def _format_special_tags(match):
|
||
content_within_brackets = match.group(1)
|
||
if re.match(_pattern_speaker, content_within_brackets):
|
||
return _format_tag(match.group())
|
||
else:
|
||
return ""
|
||
|
||
def _format_tag(text):
|
||
global _reverse_tag_transfo, _anonymized_prefix
|
||
if text.endswith(":]"):
|
||
anonymized_spk_prefixes = ["speaker", "spk", "locuteur"]
|
||
# Conversion "[speaker001:]" -> "[Intervenant 1:]"
|
||
for prefix in anonymized_spk_prefixes:
|
||
if text.lower().startswith("["+prefix):
|
||
try:
|
||
index = int(text[len(prefix)+1:-2])
|
||
except ValueError:
|
||
return text
|
||
new_spk_tag = f"[Intervenant {index}:]"
|
||
_reverse_tag_transfo[new_spk_tag] = text
|
||
if _anonymized_prefix is None:
|
||
prefix = "["+prefix
|
||
while len(prefix) < len(text) and text[len(prefix)] in " 0":
|
||
prefix += text[len(prefix)]
|
||
_anonymized_prefix = prefix
|
||
return "\n" + new_spk_tag
|
||
|
||
# Capitalize speaker name
|
||
speaker = text[1:-2]
|
||
speaker = capitalize(speaker)
|
||
new_spk_tag = f"[{speaker}:]"
|
||
if text != new_spk_tag:
|
||
_reverse_tag_transfo[new_spk_tag] = text
|
||
return "\n" + new_spk_tag
|
||
|
||
# if text == "[PII]":
|
||
# return "[Nom]"
|
||
# if text == "[NOISE]":
|
||
# return "[bruit]"
|
||
# if text == "[LAUGHTER]":
|
||
# return "[rire]"
|
||
|
||
return ""
|
||
|
||
|
||
def capitalize(text):
|
||
# Custom capitalization for first and last names
|
||
words = text.split(" ")
|
||
words = [w.capitalize() if (not w.isupper() or len(w) > 2) else w for w in words]
|
||
for i, w in enumerate(words):
|
||
for sep in "-", "'":
|
||
if sep in w:
|
||
words[i] = sep.join(
|
||
[x.capitalize() if not x.isupper() else x for x in w.split(sep)]
|
||
)
|
||
return " ".join(words)
|
||
|
||
|
||
def collapse_whitespaces_conversations(text):
|
||
text = re.sub(r"\n+", "\n", text)
|
||
text = re.sub(r"[ \t]+", " ", text)
|
||
text = re.sub(r"\n ", "\n", text)
|
||
text = re.sub(r" ([\.,])", r"\1", text)
|
||
return text.lstrip().rstrip(" ")
|
||
|
||
|
||
def format_special_characters(text):
|
||
text = unicodedata.normalize("NFC", text)
|
||
for before, after in [
|
||
("…", "..."),
|
||
(r"[«“][^\S\r\n]*", '"'),
|
||
(r"[^\S\r\n]*[»”″„]", '"'),
|
||
(r"(``|'')", '"'),
|
||
(r"[’‘‛ʿ]", "'"),
|
||
("‚", ","),
|
||
(r"–", "-"),
|
||
("[ ]", " "), # unbreakable spaces
|
||
(r"[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x9F]", ""), # non-printable characters
|
||
# ("·", "."),
|
||
(r"ᵉʳ", "er"),
|
||
(r"ᵉ", "e"),
|
||
]:
|
||
text = re.sub(before, after, text)
|
||
|
||
return text
|