CoolFace
Apppublic

Aluode/PerceptionLabPortable

sourceHugging Faceupdated 9mo agoView on Hugging Face
0likes
tokenization_bert.py479 linesDownload Raw Back to bert
1# coding=utf-82# Copyright 2018 The Google AI Language Team Authors 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"""Tokenization classes for Bert."""16 17import collections18import os19import unicodedata20from typing import Optional21 22from ...tokenization_utils import PreTrainedTokenizer, _is_control, _is_punctuation, _is_whitespace23from ...utils import logging24 25 26logger = logging.get_logger(__name__)27 28VOCAB_FILES_NAMES = {"vocab_file": "vocab.txt"}29 30 31def load_vocab(vocab_file):32    """Loads a vocabulary file into a dictionary."""33    vocab = collections.OrderedDict()34    with open(vocab_file, "r", encoding="utf-8") as reader:35        tokens = reader.readlines()36    for index, token in enumerate(tokens):37        token = token.rstrip("\n")38        vocab[token] = index39    return vocab40 41 42def whitespace_tokenize(text):43    """Runs basic whitespace cleaning and splitting on a piece of text."""44    text = text.strip()45    if not text:46        return []47    tokens = text.split()48    return tokens49 50 51class BertTokenizer(PreTrainedTokenizer):52    r"""53    Construct a BERT tokenizer. Based on WordPiece.54 55    This tokenizer inherits from [`PreTrainedTokenizer`] which contains most of the main methods. Users should refer to56    this superclass for more information regarding those methods.57 58    Args:59        vocab_file (`str`):60            File containing the vocabulary.61        do_lower_case (`bool`, *optional*, defaults to `True`):62            Whether or not to lowercase the input when tokenizing.63        do_basic_tokenize (`bool`, *optional*, defaults to `True`):64            Whether or not to do basic tokenization before WordPiece.65        never_split (`Iterable`, *optional*):66            Collection of tokens which will never be split during tokenization. Only has an effect when67            `do_basic_tokenize=True`68        unk_token (`str`, *optional*, defaults to `"[UNK]"`):69            The unknown token. A token that is not in the vocabulary cannot be converted to an ID and is set to be this70            token instead.71        sep_token (`str`, *optional*, defaults to `"[SEP]"`):72            The separator token, which is used when building a sequence from multiple sequences, e.g. two sequences for73            sequence classification or for a text and a question for question answering. It is also used as the last74            token of a sequence built with special tokens.75        pad_token (`str`, *optional*, defaults to `"[PAD]"`):76            The token used for padding, for example when batching sequences of different lengths.77        cls_token (`str`, *optional*, defaults to `"[CLS]"`):78            The classifier token which is used when doing sequence classification (classification of the whole sequence79            instead of per-token classification). It is the first token of the sequence when built with special tokens.80        mask_token (`str`, *optional*, defaults to `"[MASK]"`):81            The token used for masking values. This is the token used when training this model with masked language82            modeling. This is the token which the model will try to predict.83        tokenize_chinese_chars (`bool`, *optional*, defaults to `True`):84            Whether or not to tokenize Chinese characters.85 86            This should likely be deactivated for Japanese (see this87            [issue](https://github.com/huggingface/transformers/issues/328)).88        strip_accents (`bool`, *optional*):89            Whether or not to strip all accents. If this option is not specified, then it will be determined by the90            value for `lowercase` (as in the original BERT).91        clean_up_tokenization_spaces (`bool`, *optional*, defaults to `True`):92            Whether or not to cleanup spaces after decoding, cleanup consists in removing potential artifacts like93            extra spaces.94    """95 96    vocab_files_names = VOCAB_FILES_NAMES97 98    def __init__(99        self,100        vocab_file,101        do_lower_case=True,102        do_basic_tokenize=True,103        never_split=None,104        unk_token="[UNK]",105        sep_token="[SEP]",106        pad_token="[PAD]",107        cls_token="[CLS]",108        mask_token="[MASK]",109        tokenize_chinese_chars=True,110        strip_accents=None,111        clean_up_tokenization_spaces=True,112        **kwargs,113    ):114        if not os.path.isfile(vocab_file):115            raise ValueError(116                f"Can't find a vocabulary file at path '{vocab_file}'. To load the vocabulary from a Google pretrained"117                " model use `tokenizer = BertTokenizer.from_pretrained(PRETRAINED_MODEL_NAME)`"118            )119        self.vocab = load_vocab(vocab_file)120        self.ids_to_tokens = collections.OrderedDict([(ids, tok) for tok, ids in self.vocab.items()])121        self.do_basic_tokenize = do_basic_tokenize122        if do_basic_tokenize:123            self.basic_tokenizer = BasicTokenizer(124                do_lower_case=do_lower_case,125                never_split=never_split,126                tokenize_chinese_chars=tokenize_chinese_chars,127                strip_accents=strip_accents,128            )129 130        self.wordpiece_tokenizer = WordpieceTokenizer(vocab=self.vocab, unk_token=str(unk_token))131 132        super().__init__(133            do_lower_case=do_lower_case,134            do_basic_tokenize=do_basic_tokenize,135            never_split=never_split,136            unk_token=unk_token,137            sep_token=sep_token,138            pad_token=pad_token,139            cls_token=cls_token,140            mask_token=mask_token,141            tokenize_chinese_chars=tokenize_chinese_chars,142            strip_accents=strip_accents,143            clean_up_tokenization_spaces=clean_up_tokenization_spaces,144            **kwargs,145        )146 147    @property148    def do_lower_case(self):149        return self.basic_tokenizer.do_lower_case150 151    @property152    def vocab_size(self):153        return len(self.vocab)154 155    def get_vocab(self):156        return dict(self.vocab, **self.added_tokens_encoder)157 158    def _tokenize(self, text, split_special_tokens=False):159        split_tokens = []160        if self.do_basic_tokenize:161            for token in self.basic_tokenizer.tokenize(162                text, never_split=self.all_special_tokens if not split_special_tokens else None163            ):164                # If the token is part of the never_split set165                if token in self.basic_tokenizer.never_split:166                    split_tokens.append(token)167                else:168                    split_tokens += self.wordpiece_tokenizer.tokenize(token)169        else:170            split_tokens = self.wordpiece_tokenizer.tokenize(text)171        return split_tokens172 173    def _convert_token_to_id(self, token):174        """Converts a token (str) in an id using the vocab."""175        return self.vocab.get(token, self.vocab.get(self.unk_token))176 177    def _convert_id_to_token(self, index):178        """Converts an index (integer) in a token (str) using the vocab."""179        return self.ids_to_tokens.get(index, self.unk_token)180 181    def convert_tokens_to_string(self, tokens):182        """Converts a sequence of tokens (string) in a single string."""183        out_string = " ".join(tokens).replace(" ##", "").strip()184        return out_string185 186    def build_inputs_with_special_tokens(187        self, token_ids_0: list[int], token_ids_1: Optional[list[int]] = None188    ) -> list[int]:189        """190        Build model inputs from a sequence or a pair of sequence for sequence classification tasks by concatenating and191        adding special tokens. A BERT sequence has the following format:192 193        - single sequence: `[CLS] X [SEP]`194        - pair of sequences: `[CLS] A [SEP] B [SEP]`195 196        Args:197            token_ids_0 (`List[int]`):198                List of IDs to which the special tokens will be added.199            token_ids_1 (`List[int]`, *optional*):200                Optional second list of IDs for sequence pairs.201 202        Returns:203            `List[int]`: List of [input IDs](../glossary#input-ids) with the appropriate special tokens.204        """205        if token_ids_1 is None:206            return [self.cls_token_id] + token_ids_0 + [self.sep_token_id]207        cls = [self.cls_token_id]208        sep = [self.sep_token_id]209        return cls + token_ids_0 + sep + token_ids_1 + sep210 211    def get_special_tokens_mask(212        self, token_ids_0: list[int], token_ids_1: Optional[list[int]] = None, already_has_special_tokens: bool = False213    ) -> list[int]:214        """215        Retrieve sequence ids from a token list that has no special tokens added. This method is called when adding216        special tokens using the tokenizer `prepare_for_model` method.217 218        Args:219            token_ids_0 (`List[int]`):220                List of IDs.221            token_ids_1 (`List[int]`, *optional*):222                Optional second list of IDs for sequence pairs.223            already_has_special_tokens (`bool`, *optional*, defaults to `False`):224                Whether or not the token list is already formatted with special tokens for the model.225 226        Returns:227            `List[int]`: A list of integers in the range [0, 1]: 1 for a special token, 0 for a sequence token.228        """229 230        if already_has_special_tokens:231            return super().get_special_tokens_mask(232                token_ids_0=token_ids_0, token_ids_1=token_ids_1, already_has_special_tokens=True233            )234 235        if token_ids_1 is not None:236            return [1] + ([0] * len(token_ids_0)) + [1] + ([0] * len(token_ids_1)) + [1]237        return [1] + ([0] * len(token_ids_0)) + [1]238 239    def save_vocabulary(self, save_directory: str, filename_prefix: Optional[str] = None) -> tuple[str]:240        index = 0241        if os.path.isdir(save_directory):242            vocab_file = os.path.join(243                save_directory, (filename_prefix + "-" if filename_prefix else "") + VOCAB_FILES_NAMES["vocab_file"]244            )245        else:246            vocab_file = (filename_prefix + "-" if filename_prefix else "") + save_directory247        with open(vocab_file, "w", encoding="utf-8") as writer:248            for token, token_index in sorted(self.vocab.items(), key=lambda kv: kv[1]):249                if index != token_index:250                    logger.warning(251                        f"Saving vocabulary to {vocab_file}: vocabulary indices are not consecutive."252                        " Please check that the vocabulary is not corrupted!"253                    )254                    index = token_index255                writer.write(token + "\n")256                index += 1257        return (vocab_file,)258 259 260class BasicTokenizer:261    """262    Constructs a BasicTokenizer that will run basic tokenization (punctuation splitting, lower casing, etc.).263 264    Args:265        do_lower_case (`bool`, *optional*, defaults to `True`):266            Whether or not to lowercase the input when tokenizing.267        never_split (`Iterable`, *optional*):268            Collection of tokens which will never be split during tokenization. Only has an effect when269            `do_basic_tokenize=True`270        tokenize_chinese_chars (`bool`, *optional*, defaults to `True`):271            Whether or not to tokenize Chinese characters.272 273            This should likely be deactivated for Japanese (see this274            [issue](https://github.com/huggingface/transformers/issues/328)).275        strip_accents (`bool`, *optional*):276            Whether or not to strip all accents. If this option is not specified, then it will be determined by the277            value for `lowercase` (as in the original BERT).278        do_split_on_punc (`bool`, *optional*, defaults to `True`):279            In some instances we want to skip the basic punctuation splitting so that later tokenization can capture280            the full context of the words, such as contractions.281    """282 283    def __init__(284        self,285        do_lower_case=True,286        never_split=None,287        tokenize_chinese_chars=True,288        strip_accents=None,289        do_split_on_punc=True,290    ):291        if never_split is None:292            never_split = []293        self.do_lower_case = do_lower_case294        self.never_split = set(never_split)295        self.tokenize_chinese_chars = tokenize_chinese_chars296        self.strip_accents = strip_accents297        self.do_split_on_punc = do_split_on_punc298 299    def tokenize(self, text, never_split=None):300        """301        Basic Tokenization of a piece of text. For sub-word tokenization, see WordPieceTokenizer.302 303        Args:304            never_split (`List[str]`, *optional*)305                Kept for backward compatibility purposes. Now implemented directly at the base class level (see306                [`PreTrainedTokenizer.tokenize`]) List of token not to split.307        """308        # union() returns a new set by concatenating the two sets.309        never_split = self.never_split.union(set(never_split)) if never_split else self.never_split310        text = self._clean_text(text)311 312        # This was added on November 1st, 2018 for the multilingual and Chinese313        # models. This is also applied to the English models now, but it doesn't314        # matter since the English models were not trained on any Chinese data315        # and generally don't have any Chinese data in them (there are Chinese316        # characters in the vocabulary because Wikipedia does have some Chinese317        # words in the English Wikipedia.).318        if self.tokenize_chinese_chars:319            text = self._tokenize_chinese_chars(text)320        # prevents treating the same character with different unicode codepoints as different characters321        unicode_normalized_text = unicodedata.normalize("NFC", text)322        orig_tokens = whitespace_tokenize(unicode_normalized_text)323        split_tokens = []324        for token in orig_tokens:325            if token not in never_split:326                if self.do_lower_case:327                    token = token.lower()328                    if self.strip_accents is not False:329                        token = self._run_strip_accents(token)330                elif self.strip_accents:331                    token = self._run_strip_accents(token)332            split_tokens.extend(self._run_split_on_punc(token, never_split))333 334        output_tokens = whitespace_tokenize(" ".join(split_tokens))335        return output_tokens336 337    def _run_strip_accents(self, text):338        """Strips accents from a piece of text."""339        text = unicodedata.normalize("NFD", text)340        output = []341        for char in text:342            cat = unicodedata.category(char)343            if cat == "Mn":344                continue345            output.append(char)346        return "".join(output)347 348    def _run_split_on_punc(self, text, never_split=None):349        """Splits punctuation on a piece of text."""350        if not self.do_split_on_punc or (never_split is not None and text in never_split):351            return [text]352        chars = list(text)353        i = 0354        start_new_word = True355        output = []356        while i < len(chars):357            char = chars[i]358            if _is_punctuation(char):359                output.append([char])360                start_new_word = True361            else:362                if start_new_word:363                    output.append([])364                start_new_word = False365                output[-1].append(char)366            i += 1367 368        return ["".join(x) for x in output]369 370    def _tokenize_chinese_chars(self, text):371        """Adds whitespace around any CJK character."""372        output = []373        for char in text:374            cp = ord(char)375            if self._is_chinese_char(cp):376                output.append(" ")377                output.append(char)378                output.append(" ")379            else:380                output.append(char)381        return "".join(output)382 383    def _is_chinese_char(self, cp):384        """Checks whether CP is the codepoint of a CJK character."""385        # This defines a "chinese character" as anything in the CJK Unicode block:386        #   https://en.wikipedia.org/wiki/CJK_Unified_Ideographs_(Unicode_block)387        #388        # Note that the CJK Unicode block is NOT all Japanese and Korean characters,389        # despite its name. The modern Korean Hangul alphabet is a different block,390        # as is Japanese Hiragana and Katakana. Those alphabets are used to write391        # space-separated words, so they are not treated specially and handled392        # like the all of the other languages.393        if (394            (cp >= 0x4E00 and cp <= 0x9FFF)395            or (cp >= 0x3400 and cp <= 0x4DBF)396            or (cp >= 0x20000 and cp <= 0x2A6DF)397            or (cp >= 0x2A700 and cp <= 0x2B73F)398            or (cp >= 0x2B740 and cp <= 0x2B81F)399            or (cp >= 0x2B820 and cp <= 0x2CEAF)400            or (cp >= 0xF900 and cp <= 0xFAFF)401            or (cp >= 0x2F800 and cp <= 0x2FA1F)402        ):403            return True404 405        return False406 407    def _clean_text(self, text):408        """Performs invalid character removal and whitespace cleanup on text."""409        output = []410        for char in text:411            cp = ord(char)412            if cp == 0 or cp == 0xFFFD or _is_control(char):413                continue414            if _is_whitespace(char):415                output.append(" ")416            else:417                output.append(char)418        return "".join(output)419 420 421class WordpieceTokenizer:422    """Runs WordPiece tokenization."""423 424    def __init__(self, vocab, unk_token, max_input_chars_per_word=100):425        self.vocab = vocab426        self.unk_token = unk_token427        self.max_input_chars_per_word = max_input_chars_per_word428 429    def tokenize(self, text):430        """431        Tokenizes a piece of text into its word pieces. This uses a greedy longest-match-first algorithm to perform432        tokenization using the given vocabulary.433 434        For example, `input = "unaffable"` will return as output `["un", "##aff", "##able"]`.435 436        Args:437            text: A single token or whitespace separated tokens. This should have438                already been passed through *BasicTokenizer*.439 440        Returns:441            A list of wordpiece tokens.442        """443 444        output_tokens = []445        for token in whitespace_tokenize(text):446            chars = list(token)447            if len(chars) > self.max_input_chars_per_word:448                output_tokens.append(self.unk_token)449                continue450 451            is_bad = False452            start = 0453            sub_tokens = []454            while start < len(chars):455                end = len(chars)456                cur_substr = None457                while start < end:458                    substr = "".join(chars[start:end])459                    if start > 0:460                        substr = "##" + substr461                    if substr in self.vocab:462                        cur_substr = substr463                        break464                    end -= 1465                if cur_substr is None:466                    is_bad = True467                    break468                sub_tokens.append(cur_substr)469                start = end470 471            if is_bad:472                output_tokens.append(self.unk_token)473            else:474                output_tokens.extend(sub_tokens)475        return output_tokens476 477 478__all__ = ["BasicTokenizer", "BertTokenizer", "WordpieceTokenizer"]479 
Aluode/PerceptionLabPortable · CoolFace