Aluode/PerceptionLabPortable
0
1# coding=utf-82# Copyright 2020 The Allen Institute for AI team and The HuggingFace Inc. team.3#4# Licensed under the Apache License, Version 2.0 (the "License");5# you may not use this file except in compliance with the License.6# You may obtain a copy of the License at7#8# http://www.apache.org/licenses/LICENSE-2.09#10# Unless required by applicable law or agreed to in writing, software11# distributed under the License is distributed on an "AS IS" BASIS,12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.13# See the License for the specific language governing permissions and14# limitations under the License.15 16import json17import os18from functools import lru_cache19from typing import Optional20 21import regex as re22 23from ...tokenization_utils import AddedToken, PreTrainedTokenizer24from ...utils import logging25 26 27logger = logging.get_logger(__name__)28 29 30VOCAB_FILES_NAMES = {"vocab_file": "vocab.json", "merges_file": "merges.txt"}31 32 33@lru_cache34# Copied from transformers.models.roberta.tokenization_roberta.bytes_to_unicode35def bytes_to_unicode():36 """37 Returns list of utf-8 byte and a mapping to unicode strings. We specifically avoids mapping to whitespace/control38 characters the bpe code barfs on.39 40 The reversible bpe codes work on unicode strings. This means you need a large # of unicode characters in your vocab41 if you want to avoid UNKs. When you're at something like a 10B token dataset you end up needing around 5K for42 decent coverage. This is a significant percentage of your normal, say, 32K bpe vocab. To avoid that, we want lookup43 tables between utf-8 bytes and unicode strings.44 """45 bs = (46 list(range(ord("!"), ord("~") + 1)) + list(range(ord("¡"), ord("¬") + 1)) + list(range(ord("®"), ord("ÿ") + 1))47 )48 cs = bs[:]49 n = 050 for b in range(2**8):51 if b not in bs:52 bs.append(b)53 cs.append(2**8 + n)54 n += 155 cs = [chr(n) for n in cs]56 return dict(zip(bs, cs))57 58 59# Copied from transformers.models.roberta.tokenization_roberta.get_pairs60def get_pairs(word):61 """62 Return set of symbol pairs in a word.63 64 Word is represented as tuple of symbols (symbols being variable-length strings).65 """66 pairs = set()67 prev_char = word[0]68 for char in word[1:]:69 pairs.add((prev_char, char))70 prev_char = char71 return pairs72 73 74# Copied from transformers.models.roberta.tokenization_roberta.RobertaTokenizer with FacebookAI/roberta-base->allenai/longformer-base-4096, RoBERTa->Longformer all-casing, RobertaTokenizer->LongformerTokenizer75class LongformerTokenizer(PreTrainedTokenizer):76 """77 Constructs a Longformer tokenizer, derived from the GPT-2 tokenizer, using byte-level Byte-Pair-Encoding.78 79 This tokenizer has been trained to treat spaces like parts of the tokens (a bit like sentencepiece) so a word will80 be encoded differently whether it is at the beginning of the sentence (without space) or not:81 82 ```python83 >>> from transformers import LongformerTokenizer84 85 >>> tokenizer = LongformerTokenizer.from_pretrained("allenai/longformer-base-4096")86 >>> tokenizer("Hello world")["input_ids"]87 [0, 31414, 232, 2]88 89 >>> tokenizer(" Hello world")["input_ids"]90 [0, 20920, 232, 2]91 ```92 93 You can get around that behavior by passing `add_prefix_space=True` when instantiating this tokenizer or when you94 call it on some text, but since the model was not pretrained this way, it might yield a decrease in performance.95 96 <Tip>97 98 When used with `is_split_into_words=True`, this tokenizer will add a space before each word (even the first one).99 100 </Tip>101 102 This tokenizer inherits from [`PreTrainedTokenizer`] which contains most of the main methods. Users should refer to103 this superclass for more information regarding those methods.104 105 Args:106 vocab_file (`str`):107 Path to the vocabulary file.108 merges_file (`str`):109 Path to the merges file.110 errors (`str`, *optional*, defaults to `"replace"`):111 Paradigm to follow when decoding bytes to UTF-8. See112 [bytes.decode](https://docs.python.org/3/library/stdtypes.html#bytes.decode) for more information.113 bos_token (`str`, *optional*, defaults to `"<s>"`):114 The beginning of sequence token that was used during pretraining. Can be used a sequence classifier token.115 116 <Tip>117 118 When building a sequence using special tokens, this is not the token that is used for the beginning of119 sequence. The token used is the `cls_token`.120 121 </Tip>122 123 eos_token (`str`, *optional*, defaults to `"</s>"`):124 The end of sequence token.125 126 <Tip>127 128 When building a sequence using special tokens, this is not the token that is used for the end of sequence.129 The token used is the `sep_token`.130 131 </Tip>132 133 sep_token (`str`, *optional*, defaults to `"</s>"`):134 The separator token, which is used when building a sequence from multiple sequences, e.g. two sequences for135 sequence classification or for a text and a question for question answering. It is also used as the last136 token of a sequence built with special tokens.137 cls_token (`str`, *optional*, defaults to `"<s>"`):138 The classifier token which is used when doing sequence classification (classification of the whole sequence139 instead of per-token classification). It is the first token of the sequence when built with special tokens.140 unk_token (`str`, *optional*, defaults to `"<unk>"`):141 The unknown token. A token that is not in the vocabulary cannot be converted to an ID and is set to be this142 token instead.143 pad_token (`str`, *optional*, defaults to `"<pad>"`):144 The token used for padding, for example when batching sequences of different lengths.145 mask_token (`str`, *optional*, defaults to `"<mask>"`):146 The token used for masking values. This is the token used when training this model with masked language147 modeling. This is the token which the model will try to predict.148 add_prefix_space (`bool`, *optional*, defaults to `False`):149 Whether or not to add an initial space to the input. This allows to treat the leading word just as any150 other word. (Longformer tokenizer detect beginning of words by the preceding space).151 """152 153 vocab_files_names = VOCAB_FILES_NAMES154 model_input_names = ["input_ids", "attention_mask"]155 156 def __init__(157 self,158 vocab_file,159 merges_file,160 errors="replace",161 bos_token="<s>",162 eos_token="</s>",163 sep_token="</s>",164 cls_token="<s>",165 unk_token="<unk>",166 pad_token="<pad>",167 mask_token="<mask>",168 add_prefix_space=False,169 **kwargs,170 ):171 bos_token = AddedToken(bos_token, lstrip=False, rstrip=False) if isinstance(bos_token, str) else bos_token172 pad_token = AddedToken(pad_token, lstrip=False, rstrip=False) if isinstance(pad_token, str) else pad_token173 eos_token = AddedToken(eos_token, lstrip=False, rstrip=False) if isinstance(eos_token, str) else eos_token174 unk_token = AddedToken(unk_token, lstrip=False, rstrip=False) if isinstance(unk_token, str) else unk_token175 sep_token = AddedToken(sep_token, lstrip=False, rstrip=False) if isinstance(sep_token, str) else sep_token176 cls_token = AddedToken(cls_token, lstrip=False, rstrip=False) if isinstance(cls_token, str) else cls_token177 178 # Mask token behave like a normal word, i.e. include the space before it179 mask_token = (180 AddedToken(mask_token, lstrip=True, rstrip=False, normalized=False)181 if isinstance(mask_token, str)182 else mask_token183 )184 185 # these special tokens are not part of the vocab.json, let's add them in the correct order186 187 with open(vocab_file, encoding="utf-8") as vocab_handle:188 self.encoder = json.load(vocab_handle)189 self.decoder = {v: k for k, v in self.encoder.items()}190 self.errors = errors # how to handle errors in decoding191 self.byte_encoder = bytes_to_unicode()192 self.byte_decoder = {v: k for k, v in self.byte_encoder.items()}193 with open(merges_file, encoding="utf-8") as merges_handle:194 bpe_merges = merges_handle.read().split("\n")[1:-1]195 bpe_merges = [tuple(merge.split()) for merge in bpe_merges]196 self.bpe_ranks = dict(zip(bpe_merges, range(len(bpe_merges))))197 self.cache = {}198 self.add_prefix_space = add_prefix_space199 200 # Should have added re.IGNORECASE so BPE merges can happen for capitalized versions of contractions201 self.pat = re.compile(r"""'s|'t|'re|'ve|'m|'ll|'d| ?\p{L}+| ?\p{N}+| ?[^\s\p{L}\p{N}]+|\s+(?!\S)|\s+""")202 203 super().__init__(204 errors=errors,205 bos_token=bos_token,206 eos_token=eos_token,207 unk_token=unk_token,208 sep_token=sep_token,209 cls_token=cls_token,210 pad_token=pad_token,211 mask_token=mask_token,212 add_prefix_space=add_prefix_space,213 **kwargs,214 )215 216 @property217 def vocab_size(self):218 return len(self.encoder)219 220 def get_vocab(self):221 vocab = dict(self.encoder).copy()222 vocab.update(self.added_tokens_encoder)223 return vocab224 225 def bpe(self, token):226 if token in self.cache:227 return self.cache[token]228 word = tuple(token)229 pairs = get_pairs(word)230 231 if not pairs:232 return token233 234 while True:235 bigram = min(pairs, key=lambda pair: self.bpe_ranks.get(pair, float("inf")))236 if bigram not in self.bpe_ranks:237 break238 first, second = bigram239 new_word = []240 i = 0241 while i < len(word):242 try:243 j = word.index(first, i)244 except ValueError:245 new_word.extend(word[i:])246 break247 else:248 new_word.extend(word[i:j])249 i = j250 251 if word[i] == first and i < len(word) - 1 and word[i + 1] == second:252 new_word.append(first + second)253 i += 2254 else:255 new_word.append(word[i])256 i += 1257 new_word = tuple(new_word)258 word = new_word259 if len(word) == 1:260 break261 else:262 pairs = get_pairs(word)263 word = " ".join(word)264 self.cache[token] = word265 return word266 267 def _tokenize(self, text):268 """Tokenize a string."""269 bpe_tokens = []270 for token in re.findall(self.pat, text):271 token = "".join(272 self.byte_encoder[b] for b in token.encode("utf-8")273 ) # Maps all our bytes to unicode strings, avoiding control tokens of the BPE (spaces in our case)274 bpe_tokens.extend(bpe_token for bpe_token in self.bpe(token).split(" "))275 return bpe_tokens276 277 def _convert_token_to_id(self, token):278 """Converts a token (str) in an id using the vocab."""279 return self.encoder.get(token, self.encoder.get(self.unk_token))280 281 def _convert_id_to_token(self, index):282 """Converts an index (integer) in a token (str) using the vocab."""283 return self.decoder.get(index)284 285 def convert_tokens_to_string(self, tokens):286 """Converts a sequence of tokens (string) in a single string."""287 text = "".join(tokens)288 text = bytearray([self.byte_decoder[c] for c in text]).decode("utf-8", errors=self.errors)289 return text290 291 def save_vocabulary(self, save_directory: str, filename_prefix: Optional[str] = None) -> tuple[str]:292 if not os.path.isdir(save_directory):293 logger.error(f"Vocabulary path ({save_directory}) should be a directory")294 return295 vocab_file = os.path.join(296 save_directory, (filename_prefix + "-" if filename_prefix else "") + VOCAB_FILES_NAMES["vocab_file"]297 )298 merge_file = os.path.join(299 save_directory, (filename_prefix + "-" if filename_prefix else "") + VOCAB_FILES_NAMES["merges_file"]300 )301 302 with open(vocab_file, "w", encoding="utf-8") as f:303 f.write(json.dumps(self.encoder, indent=2, sort_keys=True, ensure_ascii=False) + "\n")304 305 index = 0306 with open(merge_file, "w", encoding="utf-8") as writer:307 writer.write("#version: 0.2\n")308 for bpe_tokens, token_index in sorted(self.bpe_ranks.items(), key=lambda kv: kv[1]):309 if index != token_index:310 logger.warning(311 f"Saving vocabulary to {merge_file}: BPE merge indices are not consecutive."312 " Please check that the tokenizer is not corrupted!"313 )314 index = token_index315 writer.write(" ".join(bpe_tokens) + "\n")316 index += 1317 318 return vocab_file, merge_file319 320 def build_inputs_with_special_tokens(321 self, token_ids_0: list[int], token_ids_1: Optional[list[int]] = None322 ) -> list[int]:323 """324 Build model inputs from a sequence or a pair of sequence for sequence classification tasks by concatenating and325 adding special tokens. A Longformer sequence has the following format:326 327 - single sequence: `<s> X </s>`328 - pair of sequences: `<s> A </s></s> B </s>`329 330 Args:331 token_ids_0 (`list[int]`):332 List of IDs to which the special tokens will be added.333 token_ids_1 (`list[int]`, *optional*):334 Optional second list of IDs for sequence pairs.335 336 Returns:337 `list[int]`: List of [input IDs](../glossary#input-ids) with the appropriate special tokens.338 """339 if token_ids_1 is None:340 return [self.cls_token_id] + token_ids_0 + [self.sep_token_id]341 cls = [self.cls_token_id]342 sep = [self.sep_token_id]343 return cls + token_ids_0 + sep + sep + token_ids_1 + sep344 345 def get_special_tokens_mask(346 self, token_ids_0: list[int], token_ids_1: Optional[list[int]] = None, already_has_special_tokens: bool = False347 ) -> list[int]:348 """349 Retrieve sequence ids from a token list that has no special tokens added. This method is called when adding350 special tokens using the tokenizer `prepare_for_model` method.351 352 Args:353 token_ids_0 (`list[int]`):354 List of IDs.355 token_ids_1 (`list[int]`, *optional*):356 Optional second list of IDs for sequence pairs.357 already_has_special_tokens (`bool`, *optional*, defaults to `False`):358 Whether or not the token list is already formatted with special tokens for the model.359 360 Returns:361 `list[int]`: A list of integers in the range [0, 1]: 1 for a special token, 0 for a sequence token.362 """363 if already_has_special_tokens:364 return super().get_special_tokens_mask(365 token_ids_0=token_ids_0, token_ids_1=token_ids_1, already_has_special_tokens=True366 )367 368 if token_ids_1 is None:369 return [1] + ([0] * len(token_ids_0)) + [1]370 return [1] + ([0] * len(token_ids_0)) + [1, 1] + ([0] * len(token_ids_1)) + [1]371 372 def create_token_type_ids_from_sequences(373 self, token_ids_0: list[int], token_ids_1: Optional[list[int]] = None374 ) -> list[int]:375 """376 Create a mask from the two sequences passed to be used in a sequence-pair classification task. Longformer does not377 make use of token type ids, therefore a list of zeros is returned.378 379 Args:380 token_ids_0 (`list[int]`):381 List of IDs.382 token_ids_1 (`list[int]`, *optional*):383 Optional second list of IDs for sequence pairs.384 385 Returns:386 `list[int]`: List of zeros.387 """388 sep = [self.sep_token_id]389 cls = [self.cls_token_id]390 391 if token_ids_1 is None:392 return len(cls + token_ids_0 + sep) * [0]393 return len(cls + token_ids_0 + sep + sep + token_ids_1 + sep) * [0]394 395 def prepare_for_tokenization(self, text, is_split_into_words=False, **kwargs):396 add_prefix_space = kwargs.pop("add_prefix_space", self.add_prefix_space)397 if (is_split_into_words or add_prefix_space) and (len(text) > 0 and not text[0].isspace()):398 text = " " + text399 return (text, kwargs)400 401 402__all__ = ["LongformerTokenizer"]403 