CoolFace
Apppublic

Aluode/PerceptionLabPortable

sourceHugging Faceupdated 9mo agoView on Hugging Face
0likes
tokenization_utils.py1136 linesDownload Raw Back to transformers
1# Copyright 2020 The HuggingFace Inc. team.2#3# Licensed under the Apache License, Version 2.0 (the "License");4# you may not use this file except in compliance with the License.5# You may obtain a copy of the License at6#7#     http://www.apache.org/licenses/LICENSE-2.08#9# Unless required by applicable law or agreed to in writing, software10# distributed under the License is distributed on an "AS IS" BASIS,11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.12# See the License for the specific language governing permissions and13# limitations under the License.14"""15Tokenization classes for python tokenizers. For fast tokenizers (provided by HuggingFace's tokenizers library) see16tokenization_utils_fast.py17"""18 19import bisect20import itertools21import re22import unicodedata23from collections import OrderedDict24from typing import Any, Optional, Union, overload25 26from .tokenization_utils_base import (27    ENCODE_KWARGS_DOCSTRING,28    ENCODE_PLUS_ADDITIONAL_KWARGS_DOCSTRING,29    INIT_TOKENIZER_DOCSTRING,30    AddedToken,31    BatchEncoding,32    EncodedInput,33    EncodedInputPair,34    PreTokenizedInput,35    PreTokenizedInputPair,36    PreTrainedTokenizerBase,37    TextInput,38    TextInputPair,39    TruncationStrategy,40)41from .utils import PaddingStrategy, TensorType, add_end_docstrings, logging42 43 44logger = logging.get_logger(__name__)45 46# Slow tokenizers are saved in a vocabulary plus three separated files47SPECIAL_TOKENS_MAP_FILE = "special_tokens_map.json"48ADDED_TOKENS_FILE = "added_tokens.json"49TOKENIZER_CONFIG_FILE = "tokenizer_config.json"50 51 52class Trie:53    """54    Trie in Python. Creates a Trie out of a list of words. The trie is used to split on `added_tokens` in one pass55    Loose reference https://en.wikipedia.org/wiki/Trie56    """57 58    def __init__(self, *args):59        self.data = {}60        self._tokens = set()61        self._termination_char = ""62        self.update(*args)63 64    def update(self, *args):65        """66        Updates the Trie with new tokens provided as arguments.67 68        Args:69            *args: Variable number of words to be added to the Trie.70        """71        for token in tuple(*args):72            self.add(token)73 74    def add(self, word: str):75        """76        Passes over every char (utf-8 char) on word and recursively adds it to the internal `data` trie representation.77        The special key `""` in `self._termination_char` is used to represent termination.78 79        This function is idempotent, adding twice the same word will leave the trie unchanged80 81        Example:82 83        ```python84        >>> trie = Trie()85        >>> trie.add("Hello 友達")86        >>> trie.data87        {"H": {"e": {"l": {"l": {"o": {" ": {"友": {"達": {"": 1}}}}}}}}}88 89        >>> trie.add("Hello")90        >>> trie.data91        {"H": {"e": {"l": {"l": {"o": {"": 1, " ": {"友": {"達": {"": 1}}}}}}}}}92        ```93        """94        if not word:95            # Prevent empty string96            return97 98        self._tokens.add(word)99        ref = self.data100        for char in word:101            ref[char] = ref.setdefault(char, {})102            ref = ref[char]103        ref[self._termination_char] = 1104 105    def split(self, text: str) -> list[str]:106        """107        Will look for the words added to the trie within `text`. Output is the original string split along the108        boundaries of the words found.109 110        This trie will match the longest possible word first !111 112        Example:113 114        ```python115        >>> trie = Trie()116        >>> trie.split("[CLS] This is a extra_id_100")117        ["[CLS] This is a extra_id_100"]118 119        >>> trie.add("[CLS]")120        >>> trie.add("extra_id_1")121        >>> trie.add("extra_id_100")122        >>> trie.split("[CLS] This is a extra_id_100")123        ["[CLS]", " This is a ", "extra_id_100"]124        ```125        """126        # indexes are counted left of the chars index.127        # "hello", index 0, is left of h, index 1 is between h and e.128        # index 5 is right of the "o".129 130        # States are going to capture every possible start (indexes as above)131        # as keys, and have as values, a pointer to the position in the trie132        # where we're at. This is a partial match for now.133        # This enables to keep track of multiple matches while we're iterating134        # the string135        # If the trie contains, "blowing", and "lower" and we encounter the136        # string "blower", we need to split into ["b", "lower"].137        # This is where we need to keep track of multiple possible starts.138        states = OrderedDict()139 140        # This will contain every indices where we need141        # to cut.142        # We force to cut at offset 0 and len(text) (added later)143        offsets = [0]144 145        # This is used by the lookahead which needs to skip over146        # some text where the full match exceeded the place in the initial147        # for loop148        skip = 0149        # Main loop, Giving this algorithm O(n) complexity150        for current, current_char in enumerate(text):151            if skip and current < skip:152                # Prevents the lookahead for matching twice153                # like extra_id_100 and id_100154                continue155 156            # This will track every state157            # that stop matching, we need to stop tracking them.158            # If we look at "lowball", we're going to match "l" (add it to states), "o", "w", then159            # fail on "b", we need to remove 0 from the valid states.160            to_remove = set()161            # Whenever we found a match, we need to drop everything162            # this is a greedy algorithm, it will match on the first found token163            reset = False164 165            # In this case, we already have partial matches (But unfinished)166            for start, trie_pointer in states.items():167                if "" in trie_pointer:168                    # This is a final match, we need to reset and169                    # store the results in `offsets`.170 171                    # Lookahead to match longest first172                    # Important in case of extra_id_1 vs extra_id_100173                    # Here we are also actively looking for other earlier partial174                    # matches175                    # "[CLS]", "L", we need to match CLS even if L is special176                    for lookstart, looktrie_pointer in states.items():177                        if lookstart > start:178                            # This partial match is later, we can stop looking179                            break180                        elif lookstart < start:181                            # This partial match is earlier, the trie pointer182                            # was already updated, so index is + 1183                            lookahead_index = current + 1184                            end = current + 1185                        else:186                            # Here lookstart == start and187                            #      looktrie_pointer == trie_pointer188                            # It wasn't updated yet so indices are current ones189                            lookahead_index = current190                            end = current191                        next_char = text[lookahead_index] if lookahead_index < len(text) else None192                        if "" in looktrie_pointer:193                            start = lookstart194                            end = lookahead_index195                            skip = lookahead_index196 197                        while next_char in looktrie_pointer:198                            looktrie_pointer = looktrie_pointer[next_char]199                            lookahead_index += 1200                            if "" in looktrie_pointer:201                                start = lookstart202                                end = lookahead_index203                                skip = lookahead_index204 205                            if lookahead_index == len(text):206                                # End of string207                                break208                            next_char = text[lookahead_index]209                        # End lookahead210 211                    # Storing and resetting212                    offsets.append(start)213                    offsets.append(end)214                    reset = True215                    break216                elif current_char in trie_pointer:217                    # The current character being looked at has a match within the trie218                    # update the pointer (it will be stored back into states later).219                    trie_pointer = trie_pointer[current_char]220 221                    # Storing back the new pointer into the states.222                    # Partial matches got longer by one.223                    states[start] = trie_pointer224                else:225                    # The new character has not match in the trie, we need226                    # to stop keeping track of this partial match.227                    # We can't do it directly within the loop because of how228                    # python iteration works229                    to_remove.add(start)230 231            # Either clearing the full start (we found a real match)232            # Or clearing only the partial matches that didn't work.233            if reset:234                states = {}235            else:236                for start in to_remove:237                    del states[start]238 239            # If this character is a starting character within the trie240            # start keeping track of this partial match.241            if current >= skip and current_char in self.data:242                states[current] = self.data[current_char]243 244        # We have a cut at the end with states.245        for start, trie_pointer in states.items():246            if "" in trie_pointer:247                # This is a final match, we need to reset and248                # store the results in `offsets`.249                end = len(text)250                offsets.append(start)251                offsets.append(end)252                # Longest cut is always the one with lower start so the first253                # item so we need to break.254                break255 256        return self.cut_text(text, offsets)257 258    def cut_text(self, text, offsets):259        # We have all the offsets now, we just need to do the actual splitting.260        # We need to eventually add the first part of the string and the eventual261        # last part.262        offsets.append(len(text))263        tokens = []264        start = 0265        for end in offsets:266            if start > end:267                logger.error(268                    "There was a bug in Trie algorithm in tokenization. Attempting to recover. Please report it"269                    " anyway."270                )271                continue272            elif start == end:273                # This might happen if there's a match at index 0274                # we're also preventing zero-width cuts in case of two275                # consecutive matches276                continue277            tokens.append(text[start:end])278            start = end279 280        return tokens281 282 283class ExtensionsTrie(Trie):284    def __init__(self, *args):285        super().__init__(*args)286 287    def extensions(self, prefix: str):288        """289        Generates all extensions of a given prefix token in the Trie.290 291        Example:292 293        ```python294        >>> trie = Trie()295        >>> trie.add("apple")296        >>> trie.add("app")297        >>> trie.add("application")298        >>> trie.extensions("app")299        ['app', 'apple', 'application']300        ```301        """302        prefix_node = self._get_node(prefix)303        ret = self._collect_tokens(prefix_node)304        return [prefix + token for token in ret]305 306    def _get_node(self, token: str) -> dict:307        """308        Retrieves the node corresponding to the given token in the Trie.309 310        Args:311            token (str): The token for which the corresponding node needs to be retrieved.312 313        Returns:314            dict: The node in the Trie corresponding to the given token.315        """316        node = self.data317        for char in token:318            if char not in node:319                break320 321            node = node[char]322        return node323 324    def _collect_tokens(self, node: dict) -> list:325        """326        Generates all tokens in the Trie starting from a given node.327 328        Args:329            node (dict): The node in the Trie from which tokens need to be generated.330 331        Returns:332            list: List of tokens generated from the given node.333        """334        tokens = [self._termination_char] if self._termination_char in node else []335        for token, subtrie_head in node.items():336            if token != self._termination_char:337                subtokens = self._collect_tokens(subtrie_head)338                tokens.extend([token + subtoken for subtoken in subtokens])339        return tokens340 341 342def _is_whitespace(char):343    """Checks whether `char` is a whitespace character."""344    # \t, \n, and \r are technically control characters but we treat them345    # as whitespace since they are generally considered as such.346    if char == " " or char == "\t" or char == "\n" or char == "\r":347        return True348    cat = unicodedata.category(char)349    if cat == "Zs":350        return True351    return False352 353 354def _is_control(char):355    """Checks whether `char` is a control character."""356    # These are technically control characters but we count them as whitespace357    # characters.358    if char == "\t" or char == "\n" or char == "\r":359        return False360    cat = unicodedata.category(char)361    if cat.startswith("C"):362        return True363    return False364 365 366def _is_punctuation(char):367    """Checks whether `char` is a punctuation character."""368    cp = ord(char)369    # We treat all non-letter/number ASCII as punctuation.370    # Characters such as "^", "$", and "`" are not in the Unicode371    # Punctuation class but we treat them as punctuation anyways, for372    # consistency.373    if (cp >= 33 and cp <= 47) or (cp >= 58 and cp <= 64) or (cp >= 91 and cp <= 96) or (cp >= 123 and cp <= 126):374        return True375    cat = unicodedata.category(char)376    if cat.startswith("P"):377        return True378    return False379 380 381def _is_end_of_word(text):382    """Checks whether the last character in text is one of a punctuation, control or whitespace character."""383    last_char = text[-1]384    return bool(_is_control(last_char) | _is_punctuation(last_char) | _is_whitespace(last_char))385 386 387def _is_start_of_word(text):388    """Checks whether the first character in text is one of a punctuation, control or whitespace character."""389    first_char = text[0]390    return bool(_is_control(first_char) | _is_punctuation(first_char) | _is_whitespace(first_char))391 392 393def _insert_one_token_to_ordered_list(token_list: list[str], new_token: str):394    """395    Inserts one token to an ordered list if it does not already exist. Note: token_list must be sorted.396    """397    insertion_idx = bisect.bisect_left(token_list, new_token)398    # Checks if new_token is already in the ordered token_list399    if insertion_idx < len(token_list) and token_list[insertion_idx] == new_token:400        # new_token is in token_list, don't add401        return402    else:403        token_list.insert(insertion_idx, new_token)404 405 406@add_end_docstrings(INIT_TOKENIZER_DOCSTRING)407class PreTrainedTokenizer(PreTrainedTokenizerBase):408    """409    Base class for all slow tokenizers.410 411    Inherits from [`~tokenization_utils_base.PreTrainedTokenizerBase`].412 413    Handle all the shared methods for tokenization and special tokens as well as methods downloading/caching/loading414    pretrained tokenizers as well as adding tokens to the vocabulary.415 416    This class also contain the added tokens in a unified way on top of all tokenizers so we don't have to handle the417    specific vocabulary augmentation methods of the various underlying dictionary structures (BPE, sentencepiece...).418    """419 420    def __init__(self, **kwargs):421        # 1. Init the parent class422 423        self.tokens_trie = Trie()424 425        # 2. init `_added_tokens_decoder` if child class did not426        if not hasattr(self, "_added_tokens_decoder"):427            self._added_tokens_decoder: dict[int, AddedToken] = {}428 429        # 3. if a `added_tokens_decoder` is passed, we are loading from a saved tokenizer, we overwrite430        self._added_tokens_decoder.update(kwargs.pop("added_tokens_decoder", {}))431        self._added_tokens_encoder: dict[str, int] = {k.content: v for v, k in self._added_tokens_decoder.items()}432 433        # 4 init the parent class434        super().__init__(**kwargs)435 436        # 4. If some of the special tokens are not part of the vocab, we add them, at the end.437        # the order of addition is the same as self.SPECIAL_TOKENS_ATTRIBUTES following `tokenizers`438        self._add_tokens(439            [token for token in self.all_special_tokens_extended if token not in self._added_tokens_encoder],440            special_tokens=True,441        )442 443        self._decode_use_source_tokenizer = False444 445    @property446    def is_fast(self) -> bool:447        return False448 449    @property450    def vocab_size(self) -> int:451        """452        `int`: Size of the base vocabulary (without the added tokens).453        """454        raise NotImplementedError455 456    @property457    def added_tokens_encoder(self) -> dict[str, int]:458        """459        Returns the sorted mapping from string to index. The added tokens encoder is cached for performance460        optimisation in `self._added_tokens_encoder` for the slow tokenizers.461        """462        return {k.content: v for v, k in sorted(self._added_tokens_decoder.items(), key=lambda item: item[0])}463 464    @property465    def added_tokens_decoder(self) -> dict[int, AddedToken]:466        """467        Returns the added tokens in the vocabulary as a dictionary of index to AddedToken.468 469        Returns:470            `dict[str, int]`: The added tokens.471        """472        return dict(sorted(self._added_tokens_decoder.items(), key=lambda item: item[0]))473 474    @added_tokens_decoder.setter475    def added_tokens_decoder(self, value: dict[int, Union[AddedToken, str]]) -> dict[int, AddedToken]:476        # Always raise an error if string because users should define the behavior477        for index, token in value.items():478            if not isinstance(token, (str, AddedToken)) or not isinstance(index, int):479                raise TypeError(480                    f"The provided `added_tokens_decoder` has an element of type {index.__class__, token.__class__}, should be a dict of {int, Union[AddedToken, str]}"481                )482 483            self._added_tokens_decoder[index] = AddedToken(token) if isinstance(token, str) else token484            self._added_tokens_encoder[str(token)] = index485        self._update_total_vocab_size()486 487    def get_added_vocab(self) -> dict[str, int]:488        """489        Returns the added tokens in the vocabulary as a dictionary of token to index. Results might be different from490        the fast call because for now we always add the tokens even if they are already in the vocabulary. This is491        something we should change.492 493        Returns:494            `dict[str, int]`: The added tokens.495        """496        return self._added_tokens_encoder497 498    def __len__(self):499        """500        Size of the full vocabulary with the added tokens.501        """502        return self.total_vocab_size503 504    def _update_total_vocab_size(self):505        """506        Update the size of the full vocabulary with the added tokens. Counts the `keys` and not the `values` because507        otherwise if there is a hole in the vocab, we will add tokenizers at a wrong index. This operation is slow and508        is only updated when adding tokens.509        """510        self.total_vocab_size = len(self.get_vocab())511 512    def _add_tokens(self, new_tokens: Union[list[str], list[AddedToken]], special_tokens: bool = False) -> int:513        """514        Add a list of new tokens to the tokenizer class. If the new tokens are not in the vocabulary, they are added to515        it with indices starting from length of the current vocabulary. Special tokens are sometimes already in the516        vocab which is why they have to be handled specifically.517 518        Args:519            new_tokens (`list[str]`or `list[tokenizers.AddedToken]`):520                Token(s) to add in vocabulary. A token is counted as added if it's not already in the vocabulary521                (tested by checking if the tokenizer assign the index of the `unk_token` to them). If a token is part522                of the vocabulary then we simply mark this token as an `AddedToken` which allows to control the523                stripping and normalization of this token. This is NOT possible in `tokenizers`.524            special_tokens (`bool`, *optional*, defaults to `False`):525                Whether or not the tokens should be added as special tokens.526 527        Returns:528            `int`: The number of tokens actually added to the vocabulary.529 530        Examples:531 532        ```python533        # Let's see how to increase the vocabulary of Bert model and tokenizer534        tokenizer = BertTokenizer.from_pretrained("google-bert/bert-base-uncased")535        model = BertModel.from_pretrained("google-bert/bert-base-uncased")536 537        num_added_toks = tokenizer.add_tokens(["new_tok1", "my_new-tok2"])538        print("We have added", num_added_toks, "tokens")539        # Note: resize_token_embeddings expects to receive the full size of the new vocabulary, i.e. the length of the tokenizer.540        model.resize_token_embeddings(len(tokenizer))541        ```"""542        added_tokens = 0543        if new_tokens is None:544            return added_tokens545        # TODO this is fairly slow to improve!546        current_vocab = self.get_vocab().copy()547        new_idx = len(current_vocab)  # only call this once, len gives the last index + 1548        for token in new_tokens:549            if not isinstance(token, (str, AddedToken)):550                raise TypeError(f"Token {token} is not a string but a {type(token)}.")551            if str(token) == "":552                continue553            if isinstance(token, str):554                if token in self._added_tokens_encoder:555                    continue556                else:557                    # very important for fast and slow equivalence!558                    is_special = token in self.all_special_tokens or special_tokens559                    token = AddedToken(560                        token, rstrip=False, lstrip=False, normalized=not is_special, special=is_special561                    )562            elif special_tokens:563                # doing token.special=True changes the normalization! will fix in rust564                # this is important and the only reason why the AddedTokens in each class are normalized by default565                token.__setstate__({"special": True, "normalized": token.normalized})566            if token in self._added_tokens_decoder:567                continue568            if not token.special and token.normalized and getattr(self, "do_lower_case", False):569                # Normalize if requested570                token.content = token.content.lower()571            if token.content not in current_vocab:572                token_index = new_idx + added_tokens573                current_vocab[token.content] = token_index574                added_tokens += 1575            else:576                token_index = current_vocab[token.content]577 578            if token.special and str(token) not in self.all_special_tokens:579                self._special_tokens_map["additional_special_tokens"].append(token)580            # the setter automatically updates the reverse map581            self._added_tokens_decoder[token_index] = token582            self._added_tokens_encoder[token.content] = token_index583            if self.verbose:584                logger.info(f"Adding {token} to the vocabulary")585 586        self._update_trie()587        self._update_total_vocab_size()588        return added_tokens589 590    def _update_trie(self, unique_no_split_tokens: Optional[list[str]] = None):591        for token in self._added_tokens_decoder.values():592            if token.content not in self.tokens_trie._tokens:593                self.tokens_trie.add(token.content)594        for token in unique_no_split_tokens or []:595            if token not in self.tokens_trie._tokens:596                self.tokens_trie.add(token)597 598    def num_special_tokens_to_add(self, pair: bool = False) -> int:599        """600        Returns the number of added tokens when encoding a sequence with special tokens.601 602        <Tip>603 604        This encodes a dummy input and checks the number of added tokens, and is therefore not efficient. Do not put605        this inside your training loop.606 607        </Tip>608 609        Args:610            pair (`bool`, *optional*, defaults to `False`):611                Whether the number of added tokens should be computed in the case of a sequence pair or a single612                sequence.613 614        Returns:615            `int`: Number of special tokens added to sequences.616        """617        token_ids_0 = []618        token_ids_1 = []619        return len(self.build_inputs_with_special_tokens(token_ids_0, token_ids_1 if pair else None))620 621    def tokenize(self, text: TextInput, **kwargs) -> list[str]:622        """623        Converts a string into a sequence of tokens, using the tokenizer.624 625        Split in words for word-based vocabulary or sub-words for sub-word-based vocabularies626        (BPE/SentencePieces/WordPieces). Takes care of added tokens.627 628        Args:629            text (`str`):630                The sequence to be encoded.631            **kwargs (additional keyword arguments):632                Passed along to the model-specific `prepare_for_tokenization` preprocessing method.633 634        Returns:635            `list[str]`: The list of tokens.636        """637        split_special_tokens = kwargs.pop("split_special_tokens", self.split_special_tokens)638 639        text, kwargs = self.prepare_for_tokenization(text, **kwargs)640 641        if kwargs:642            logger.warning(f"Keyword arguments {kwargs} not recognized.")643 644        if hasattr(self, "do_lower_case") and self.do_lower_case:645            # convert non-special tokens to lowercase. Might be super slow as well?646            escaped_special_toks = [re.escape(s_tok) for s_tok in (self.all_special_tokens)]647            escaped_special_toks += [648                re.escape(s_tok.content)649                for s_tok in (self._added_tokens_decoder.values())650                if not s_tok.special and s_tok.normalized651            ]652            pattern = r"(" + r"|".join(escaped_special_toks) + r")|" + r"(.+?)"653            text = re.sub(pattern, lambda m: m.groups()[0] or m.groups()[1].lower(), text)654 655        if split_special_tokens:656            no_split_token = []657            tokens = [text]658        else:659            no_split_token = self._added_tokens_encoder.keys()  # don't split on any of the added tokens660            # "This is something<special_token_1>  else"661            tokens = self.tokens_trie.split(text)662 663        # ["This is something", "<special_token_1>", "  else"]664        for i, token in enumerate(tokens):665            if token in no_split_token:666                tok_extended = self._added_tokens_decoder.get(self._added_tokens_encoder[token], None)667                left = tokens[i - 1] if i > 0 else None668                right = tokens[i + 1] if i < len(tokens) - 1 else None669                if isinstance(tok_extended, AddedToken):670                    if tok_extended.rstrip and right:671                        # A bit counter-intuitive but we strip the left of the string672                        # since tok_extended.rstrip means the special token is eating all white spaces on its right673                        tokens[i + 1] = right.lstrip()674                    # Strip white spaces on the left675                    if tok_extended.lstrip and left:676                        tokens[i - 1] = left.rstrip()  # Opposite here677                    if tok_extended.single_word and left and left[-1] != " ":678                        tokens[i - 1] += token679                        tokens[i] = ""680                    elif tok_extended.single_word and right and right[0] != " ":681                        tokens[i + 1] = token + tokens[i + 1]682                        tokens[i] = ""683                else:684                    raise ValueError(685                        f"{tok_extended} cannot be tokenized because it was not properly added"686                        f" to the tokenizer. This means that it is not an `AddedToken` but a {type(tok_extended)}"687                    )688        # ["This is something", "<special_token_1>", "else"]689        tokenized_text = []690        for token in tokens:691            # Need to skip eventual empty (fully stripped) tokens692            if not token:693                continue694            if token in no_split_token:695                tokenized_text.append(token)696            else:697                tokenized_text.extend(self._tokenize(token))698        # ["This", " is", " something", "<special_token_1>", "else"]699        return tokenized_text700 701    def _tokenize(self, text, **kwargs):702        """703        Converts a string into a sequence of tokens (string), using the tokenizer. Split in words for word-based704        vocabulary or sub-words for sub-word-based vocabularies (BPE/SentencePieces/WordPieces).705 706        Do NOT take care of added tokens.707        """708        raise NotImplementedError709 710    def convert_tokens_to_ids(self, tokens: Union[str, list[str]]) -> Union[int, list[int]]:711        """712        Converts a token string (or a sequence of tokens) in a single integer id (or a sequence of ids), using the713        vocabulary.714 715        Args:716            tokens (`str` or `list[str]`): One or several token(s) to convert to token id(s).717 718        Returns:719            `int` or `list[int]`: The token id or list of token ids.720        """721        if tokens is None:722            return None723 724        if isinstance(tokens, str):725            return self._convert_token_to_id_with_added_voc(tokens)726 727        ids = []728        for token in tokens:729            ids.append(self._convert_token_to_id_with_added_voc(token))730        return ids731 732    def _convert_token_to_id_with_added_voc(self, token):733        if token is None:734            return None735 736        if token in self._added_tokens_encoder:737            return self._added_tokens_encoder[token]738        return self._convert_token_to_id(token)739 740    def _convert_token_to_id(self, token):741        raise NotImplementedError742 743    def _encode_plus(744        self,745        text: Union[TextInput, PreTokenizedInput, EncodedInput],746        text_pair: Optional[Union[TextInput, PreTokenizedInput, EncodedInput]] = None,747        add_special_tokens: bool = True,748        padding_strategy: PaddingStrategy = PaddingStrategy.DO_NOT_PAD,749        truncation_strategy: TruncationStrategy = TruncationStrategy.DO_NOT_TRUNCATE,750        max_length: Optional[int] = None,751        stride: int = 0,752        is_split_into_words: bool = False,753        pad_to_multiple_of: Optional[int] = None,754        padding_side: Optional[str] = None,755        return_tensors: Optional[Union[str, TensorType]] = None,756        return_token_type_ids: Optional[bool] = None,757        return_attention_mask: Optional[bool] = None,758        return_overflowing_tokens: bool = False,759        return_special_tokens_mask: bool = False,760        return_offsets_mapping: bool = False,761        return_length: bool = False,762        verbose: bool = True,763        **kwargs,764    ) -> BatchEncoding:765        def get_input_ids(text):766            if isinstance(text, str):767                tokens = self.tokenize(text, **kwargs)768                return self.convert_tokens_to_ids(tokens)769            elif isinstance(text, (list, tuple)) and len(text) > 0 and isinstance(text[0], str):770                if is_split_into_words:771                    tokens = list(772                        itertools.chain(*(self.tokenize(t, is_split_into_words=True, **kwargs) for t in text))773                    )774                    return self.convert_tokens_to_ids(tokens)775                else:776                    return self.convert_tokens_to_ids(text)777            elif isinstance(text, (list, tuple)) and len(text) > 0 and isinstance(text[0], int):778                return text779            else:780                if is_split_into_words:781                    raise ValueError(782                        f"Input {text} is not valid. Should be a string or a list/tuple of strings when"783                        " `is_split_into_words=True`."784                    )785                else:786                    raise ValueError(787                        f"Input {text} is not valid. Should be a string, a list/tuple of strings or a list/tuple of"788                        " integers."789                    )790 791        if return_offsets_mapping:792            raise NotImplementedError(793                "return_offset_mapping is not available when using Python tokenizers. "794                "To use this feature, change your tokenizer to one deriving from "795                "transformers.PreTrainedTokenizerFast. "796                "More information on available tokenizers at "797                "https://github.com/huggingface/transformers/pull/2674"798            )799 800        first_ids = get_input_ids(text)801        second_ids = get_input_ids(text_pair) if text_pair is not None else None802 803        return self.prepare_for_model(804            first_ids,805            pair_ids=second_ids,806            add_special_tokens=add_special_tokens,807            padding=padding_strategy.value,808            truncation=truncation_strategy.value,809            max_length=max_length,810            stride=stride,811            pad_to_multiple_of=pad_to_multiple_of,812            padding_side=padding_side,813            return_tensors=return_tensors,814            prepend_batch_axis=True,815            return_attention_mask=return_attention_mask,816            return_token_type_ids=return_token_type_ids,817            return_overflowing_tokens=return_overflowing_tokens,818            return_special_tokens_mask=return_special_tokens_mask,819            return_length=return_length,820            verbose=verbose,821        )822 823    def _batch_encode_plus(824        self,825        batch_text_or_text_pairs: Union[826            list[TextInput],827            list[TextInputPair],828            list[PreTokenizedInput],829            list[PreTokenizedInputPair],830            list[EncodedInput],831            list[EncodedInputPair],832        ],833        add_special_tokens: bool = True,834        padding_strategy: PaddingStrategy = PaddingStrategy.DO_NOT_PAD,835        truncation_strategy: TruncationStrategy = TruncationStrategy.DO_NOT_TRUNCATE,836        max_length: Optional[int] = None,837        stride: int = 0,838        is_split_into_words: bool = False,839        pad_to_multiple_of: Optional[int] = None,840        padding_side: Optional[str] = None,841        return_tensors: Optional[Union[str, TensorType]] = None,842        return_token_type_ids: Optional[bool] = None,843        return_attention_mask: Optional[bool] = None,844        return_overflowing_tokens: bool = False,845        return_special_tokens_mask: bool = False,846        return_offsets_mapping: bool = False,847        return_length: bool = False,848        verbose: bool = True,849        split_special_tokens: bool = False,850        **kwargs,851    ) -> BatchEncoding:852        def get_input_ids(text):853            if isinstance(text, str):854                tokens = self.tokenize(text, **kwargs)855                return self.convert_tokens_to_ids(tokens)856            elif isinstance(text, (list, tuple)) and len(text) > 0 and isinstance(text[0], str):857                if is_split_into_words:858                    tokens = list(859                        itertools.chain(*(self.tokenize(t, is_split_into_words=True, **kwargs) for t in text))860                    )861                    return self.convert_tokens_to_ids(tokens)862                else:863                    return self.convert_tokens_to_ids(text)864            elif isinstance(text, (list, tuple)) and len(text) > 0 and isinstance(text[0], int):865                return text866            else:867                raise ValueError(868                    "Input is not valid. Should be a string, a list/tuple of strings or a list/tuple of integers."869                )870 871        if return_offsets_mapping:872            raise NotImplementedError(873                "return_offset_mapping is not available when using Python tokenizers. "874                "To use this feature, change your tokenizer to one deriving from "875                "transformers.PreTrainedTokenizerFast."876            )877 878        input_ids = []879        for ids_or_pair_ids in batch_text_or_text_pairs:880            if (881                not isinstance(ids_or_pair_ids, (list, tuple))882                or is_split_into_words883                and not isinstance(ids_or_pair_ids[0], (list, tuple))884            ):885                ids, pair_ids = ids_or_pair_ids, None886            else:887                ids, pair_ids = ids_or_pair_ids888 889            first_ids = get_input_ids(ids)890            second_ids = get_input_ids(pair_ids) if pair_ids is not None else None891            input_ids.append((first_ids, second_ids))892 893        batch_outputs = self._batch_prepare_for_model(894            input_ids,895            add_special_tokens=add_special_tokens,896            padding_strategy=padding_strategy,897            truncation_strategy=truncation_strategy,898            max_length=max_length,899            stride=stride,900            pad_to_multiple_of=pad_to_multiple_of,901            padding_side=padding_side,902            return_attention_mask=return_attention_mask,903            return_token_type_ids=return_token_type_ids,904            return_overflowing_tokens=return_overflowing_tokens,905            return_special_tokens_mask=return_special_tokens_mask,906            return_length=return_length,907            return_tensors=return_tensors,908            verbose=verbose,909            split_special_tokens=split_special_tokens,910        )911 912        return BatchEncoding(batch_outputs)913 914    @add_end_docstrings(ENCODE_KWARGS_DOCSTRING, ENCODE_PLUS_ADDITIONAL_KWARGS_DOCSTRING)915    def _batch_prepare_for_model(916        self,917        batch_ids_pairs: list[Union[PreTokenizedInputPair, tuple[list[int], None]]],918        add_special_tokens: bool = True,919        padding_strategy: PaddingStrategy = PaddingStrategy.DO_NOT_PAD,920        truncation_strategy: TruncationStrategy = TruncationStrategy.DO_NOT_TRUNCATE,921        max_length: Optional[int] = None,922        stride: int = 0,923        pad_to_multiple_of: Optional[int] = None,924        padding_side: Optional[str] = None,925        return_tensors: Optional[str] = None,926        return_token_type_ids: Optional[bool] = None,927        return_attention_mask: Optional[bool] = None,928        return_overflowing_tokens: bool = False,929        return_special_tokens_mask: bool = False,930        return_length: bool = False,931        verbose: bool = True,932        split_special_tokens: bool = False,933    ) -> BatchEncoding:934        """935        Prepares a sequence of input id, or a pair of sequences of inputs ids so that it can be used by the model. It936        adds special tokens, truncates sequences if overflowing while taking into account the special tokens and937        manages a moving window (with user defined stride) for overflowing tokens938 939        Args:940            batch_ids_pairs: list of tokenized input ids or input ids pairs941        """942 943        batch_outputs = {}944        for first_ids, second_ids in batch_ids_pairs:945            outputs = self.prepare_for_model(946                first_ids,947                second_ids,948                add_special_tokens=add_special_tokens,949                padding=PaddingStrategy.DO_NOT_PAD.value,  # we pad in batch afterward950                truncation=truncation_strategy.value,951                max_length=max_length,952                stride=stride,953                pad_to_multiple_of=None,  # we pad in batch afterward954                padding_side=None,  # we pad in batch afterward955                return_attention_mask=False,  # we pad in batch afterward956                return_token_type_ids=return_token_type_ids,957                return_overflowing_tokens=return_overflowing_tokens,958                return_special_tokens_mask=return_special_tokens_mask,959                return_length=return_length,960                return_tensors=None,  # We convert the whole batch to tensors at the end961                prepend_batch_axis=False,962                verbose=verbose,963                split_special_tokens=split_special_tokens,964            )965 966            for key, value in outputs.items():967                if key not in batch_outputs:968                    batch_outputs[key] = []969                batch_outputs[key].append(value)970 971        batch_outputs = self.pad(972            batch_outputs,973            padding=padding_strategy.value,974            max_length=max_length,975            pad_to_multiple_of=pad_to_multiple_of,976            padding_side=padding_side,977            return_attention_mask=return_attention_mask,978        )979 980        batch_outputs = BatchEncoding(batch_outputs, tensor_type=return_tensors)981 982        return batch_outputs983 984    def prepare_for_tokenization(985        self, text: str, is_split_into_words: bool = False, **kwargs986    ) -> tuple[str, dict[str, Any]]:987        """988        Performs any necessary transformations before tokenization.989 990        This method should pop the arguments from kwargs and return the remaining `kwargs` as well. We test the991        `kwargs` at the end of the encoding process to be sure all the arguments have been used.992 993        Args:994            text (`str`):995                The text to prepare.996            is_split_into_words (`bool`, *optional*, defaults to `False`):997                Whether or not the input is already pre-tokenized (e.g., split into words). If set to `True`, the998                tokenizer assumes the input is already split into words (for instance, by splitting it on whitespace)999                which it will tokenize. This is useful for NER or token classification.1000            kwargs (`dict[str, Any]`, *optional*):1001                Keyword arguments to use for the tokenization.1002 1003        Returns:1004            `tuple[str, dict[str, Any]]`: The prepared text and the unused kwargs.1005        """1006        return (text, kwargs)1007 1008    def get_special_tokens_mask(1009        self, token_ids_0: list, token_ids_1: Optional[list] = None, already_has_special_tokens: bool = False1010    ) -> list[int]:1011        """1012        Retrieves sequence ids from a token list that has no special tokens added. This method is called when adding1013        special tokens using the tokenizer `prepare_for_model` or `encode_plus` methods.1014 1015        Args:1016            token_ids_0 (`list[int]`):1017                List of ids of the first sequence.1018            token_ids_1 (`list[int]`, *optional*):1019                List of ids of the second sequence.1020            already_has_special_tokens (`bool`, *optional*, defaults to `False`):1021                Whether or not the token list is already formatted with special tokens for the model.1022 1023        Returns:1024            A list of integers in the range [0, 1]: 1 for a special token, 0 for a sequence token.1025        """1026        if already_has_special_tokens:1027            if token_ids_1 is not None:1028                raise ValueError(1029                    "You should not supply a second sequence if the provided sequence of "1030                    "ids is already formatted with special tokens for the model."1031                )1032 1033            return super().get_special_tokens_mask(1034                token_ids_0=token_ids_0, token_ids_1=token_ids_1, already_has_special_tokens=True1035            )1036        return [0] * ((len(token_ids_1) if token_ids_1 else 0) + len(token_ids_0))1037 1038    @overload1039    def convert_ids_to_tokens(self, ids: int, skip_special_tokens: bool = False) -> str: ...1040 1041    @overload1042    def convert_ids_to_tokens(self, ids: list[int], skip_special_tokens: bool = False) -> list[str]: ...1043 1044    def convert_ids_to_tokens(1045        self, ids: Union[int, list[int]], skip_special_tokens: bool = False1046    ) -> Union[str, list[str]]:1047        """1048        Converts a single index or a sequence of indices in a token or a sequence of tokens, using the vocabulary and1049        added tokens.1050 1051        Args:1052            ids (`int` or `list[int]`):1053                The token id (or token ids) to convert to tokens.1054            skip_special_tokens (`bool`, *optional*, defaults to `False`):1055                Whether or not to remove special tokens in the decoding.1056 1057        Returns:1058            `str` or `list[str]`: The decoded token(s).1059        """1060        if isinstance(ids, int):1061            if ids in self._added_tokens_decoder:1062                return self._added_tokens_decoder[ids].content1063            else:1064                return self._convert_id_to_token(ids)1065        tokens = []1066        for index in ids:1067            index = int(index)1068            if skip_special_tokens and index in self.all_special_ids:1069                continue1070            if index in self._added_tokens_decoder:1071                tokens.append(self._added_tokens_decoder[index].content)1072            else:1073                tokens.append(self._convert_id_to_token(index))1074        return tokens1075 1076    def _convert_id_to_token(self, index: int) -> str:1077        raise NotImplementedError1078 1079    def convert_tokens_to_string(self, tokens: list[str]) -> str:1080        return " ".join(tokens)1081 1082    def _decode(1083        self,1084        token_ids: Union[int, list[int]],1085        skip_special_tokens: bool = False,1086        clean_up_tokenization_spaces: Optional[bool] = None,1087        spaces_between_special_tokens: bool = True,1088        **kwargs,1089    ) -> str:1090        self._decode_use_source_tokenizer = kwargs.pop("use_source_tokenizer", False)1091 1092        filtered_tokens = self.convert_ids_to_tokens(token_ids, skip_special_tokens=skip_special_tokens)1093        # If given is a single id, prevents splitting the string in upcoming loop1094        if isinstance(filtered_tokens, str):1095            filtered_tokens = [filtered_tokens]1096 1097        legacy_added_tokens = set(self._added_tokens_encoder.keys()) - set(self.all_special_tokens) | {1098            token for token in self.additional_special_tokens if self.convert_tokens_to_ids(token) >= self.vocab_size1099        }1100        # To avoid mixing byte-level and unicode for byte-level BPT1101        # we need to build string separately for added tokens and byte-level tokens1102        # cf. https://github.com/huggingface/transformers/issues/11331103        sub_texts = []1104        current_sub_text = []1105        # TODO @ArthurZ in version 5, special tokens should be handled in convert_tokens_to_string, while _convert_tokens_to_string1106        for token in filtered_tokens:1107            if skip_special_tokens and token in self.all_special_tokens:1108                continue1109            if token in legacy_added_tokens:1110                if current_sub_text:1111                    string = self.convert_tokens_to_string(current_sub_text)1112                    if len(string) > 0:1113                        sub_texts.append(string)1114                    current_sub_text = []1115                sub_texts.append(token)1116            else:1117                current_sub_text.append(token)1118        if current_sub_text:1119            sub_texts.append(self.convert_tokens_to_string(current_sub_text))1120 1121        if spaces_between_special_tokens:1122            text = " ".join(sub_texts)1123        else:1124            text = "".join(sub_texts)1125 1126        clean_up_tokenization_spaces = (1127            clean_up_tokenization_spaces1128            if clean_up_tokenization_spaces is not None1129            else self.clean_up_tokenization_spaces1130        )1131        if clean_up_tokenization_spaces:1132            clean_text = self.clean_up_tokenization(text)1133            return clean_text1134        else:1135            return text1136 
Aluode/PerceptionLabPortable · CoolFace