CoolFace
Apppublic

Aluode/PerceptionLabPortable

sourceHugging Faceupdated 9mo agoView on Hugging Face
0likes
tokenization_longformer.py403 linesDownload Raw Back to longformer
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 
Aluode/PerceptionLabPortable · CoolFace