HuggingFace——Tokenizer的简单记录

Tokenizer [ 中文Course | API|详述文档]

下载使用

针对AutoTokenizer来说,如果是从在线仓库中下载,其是要访问:

    commit_hash = kwargs.get("_commit_hash", None)
    resolved_config_file = cached_file(
        pretrained_model_name_or_path,
        TOKENIZER_CONFIG_FILE,
        cache_dir=cache_dir,
        force_download=force_download,
        resume_download=resume_download,
        proxies=proxies,
        use_auth_token=use_auth_token,
        revision=revision,
        local_files_only=local_files_only,
        _raise_exceptions_for_missing_entries=False,
        _raise_exceptions_for_connection_errors=False,
        _commit_hash=commit_hash,
    )
    if resolved_config_file is None:
        logger.info("Could not locate the tokenizer configuration file, will try to use the model config instead.")
        return {}
    commit_hash = extract_commit_hash(resolved_config_file, commit_hash)

    with open(resolved_config_file, encoding="utf-8") as reader:
        result = json.load(reader) # 加载"tokenizer_config.json"
    result["_commit_hash"] = commit_hash
    return result

其中TOKENIZER_CONFIG_FILE是指:

# Slow tokenizers used to be saved in three separated files
SPECIAL_TOKENS_MAP_FILE = "special_tokens_map.json"
ADDED_TOKENS_FILE = "added_tokens.json"
TOKENIZER_CONFIG_FILE = "tokenizer_config.json"

针对不同的预训练模型,分词工具是不同的,比如:

  • Byte-level BPE, 用于 GPT-2;
  • WordPiece, 用于 BERT;
  • SentencePiece or Unigram, 用于多个多语言模型
    一般来说,具体使用哪些分词工具是在repo里面的tokenizer.json文件中配置的,比如hfl/roberta-ext模型中的tokenizer.json中可以看到下面的配置信息:
"model":
    {
        "type": "WordPiece",
        "unk_token": "[UNK]",
        "continuing_subword_prefix": "##",
        "max_input_chars_per_word": 100,
        "vocab":
        {
            "[PAD]": 0,
            ……
        }       
    }

官方是建议使用Auto* 类,因为Auto* 类设计与架构无关。

Tokenizer的一些输出展示

tokenizer = AutoTokenizer.from_pretrained('bert-base-chinese')
query_context_tokens = tokenizer('王广善先生!', '香港中文大学教授',
                        return_token_type_ids=True,
                        return_attention_mask=True,
                        return_offsets_mapping=True,
                        return_special_tokens_mask=True)
                        
tokens = query_context_tokens['input_ids']
st = tokenizer.decode(tokens)
type_ids = query_context_tokens['token_type_ids']
mask = query_context_tokens['attention_mask']
offsets_mapping = query_context_tokens['offset_mapping']  # 这个在官网并没有找到,也并不是很清楚到底指什么,但是可以从输出的结果中可以看出一些规律
special_tokens_mask = query_context_tokens['special_tokens_mask']
print('st:',st)
print('tokens:',tokens)
print('type_ids',type_ids)
print('attention_mask', attention_mask)
print('offsets_mapping', offsets_mapping)
print('special_tokens_mask', special_tokens_mask)

输出结果为

st: [CLS] 王 广 善 先 生 ! [SEP]香 港 中 文 大 学 教 授 [SEP] 
tokens: [101, 4374, 2408, 1587, 1044, 4495, 8013, 102, 7676, 3949, 704, 3152, 1920, 2110, 3136, 2956, 102]
type_ids: [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1]
attention_mask: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] 
offset_mapping: [(0, 0), (0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (0, 0), (0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (6, 7), (7, 8), (0, 0)]
special_tokens_mask: [1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1]

那么Tokenizer是怎么工作的

Tokenizer的本质其实也是一个pipeline,大体的工作流程可以分为下面的组成:
HuggingFace——Tokenizer的简单记录_第1张图片

也就是在正式分开文本之前,需要经过Normalization和Pre-tokenization。

Normalization

Normalization这一步骤涉及一些常规清理,例如删除不必要的空格、小写和/或删除重音符号。如果你熟悉Unicode normalization(例如 NFC 或 NFKC),这也是 tokenizer 可能应用的东西。

Transformers tokenizer 有一个属性叫做 backend_tokenizer 它提供了对 Tokenizers 库中底层标记器的访问:

from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased")
print(type(tokenizer.backend_tokenizer))

> <class 'tokenizers.Tokenizer'>

Pre-tokenization

分词器不能单独在原始文本上进行训练。相反,我们首先需要将文本拆分为小实体,例如单词。这就是Pre-tokenization这一步骤的用武之地。

要查看快速分词器如何执行预分词,我们可以使用 pre_tokenize_str() 的方法 pre_tokenizer 的属性 tokenizer 目的:

tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str("Hello, how are  you?")
> [('Hello', (0, 5)), (',', (5, 6)), ('how', (7, 10)), ('are', (11, 14)), ('you', (16, 19)), ('?', (19, 20))]

由于我们使用的是 BERT 分词器,预分词涉及对空格和标点符号进行拆分。对于这一步,其他标记器可以有不同的规则。例如,如果我们使用 GPT-2 标记器:

tokenizer = AutoTokenizer.from_pretrained("gpt2")
tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str("Hello, how are  you?")
> 它也会在空格和标点符号上拆分,但它会保留空格并将它们替换为 Ġ 符号,如果我们解码token,则使其能够恢复原始空格:
> [('Hello', (0, 5)), (',', (5, 6)), ('Ġhow', (6, 10)), ('Ġare', (10, 14)), ('Ġ', (14, 15)), ('Ġyou', (15, 19)),
 ('?', (19, 20))]

另请注意,与 BERT 分词器不同,此分词器不会忽略双空格

最后一个例子,让我们看一下基于 SentencePiece 算法的 T5 分词器:

tokenizer = AutoTokenizer.from_pretrained("t5-small")
tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str("Hello, how are  you?")
> [('▁Hello,', (0, 6)), ('▁how', (7, 10)), ('▁are', (11, 14)), ('▁you?', (16, 20))]

与 GPT-2 标记器一样,这个标记器保留空格并用特定标记替换它们( _ ),但 T5 分词器只在空格上拆分,而不是标点符号。还要注意,它默认在句子的开头添加了一个空格(之前 Hello ) 并忽略了之间的双空格 areyou .

剩下的就是探讨分词的底层原理了:从chapter 5之后的内容,分别探讨了BPE、WordPiece、Unigram

根据已有的tokenizer训练新的tokenizer

如果自己感兴趣的语言中没有可用的语言模型,或者如果自己的语料库与所使用的语言模型所训练的语料库有很大不同,那么此时就可能希望从适合自己的数据的tokenizer从头开始重新训练模型。

这将需要在自己的数据集上训练一个新的tokenizer。 但这究竟是什么意思? 在Tokenizer简介 中第一次查看标记器时,看到大多数 Transformer 模型使用_子词分词算法_。 为了识别哪些子词是感兴趣的并且在手头的语料库中最常出现,tokenizer需要仔细查看语料库中的所有文本——我们称之为training的过程。这种训练的确切规则取决于所使用的标记器的类型。

注意:⚠️ 训练标记器与训练模型不同!模型训练使用随机梯度下降使每个batch的loss小一点。它本质上是随机的(这意味着在进行两次相同的训练时,您必须设置一些随机数种子才能获得相同的结果)。训练标记器是一个统计过程,它试图确定哪些子词最适合为给定的语料库选择,用于选择它们的确切规则取决于分词算法。它是确定性的,这意味着在相同的语料库上使用相同的算法进行训练时,您总是会得到相同的结果。

更准确地说,Tokenizer库是围绕一个中央“Tokenizer”类构建的,构建这个类的每一部分可以在子模块的列表中重新组合:

  • normalizers 包含可以使用的所有可能的Normalizer类型(完整列表在这里)。
  • pre_tokenizesr 包含可以使用的所有可能的PreTokenizer类型(完整列表在这里)。
  • models 包含可以使用的各种类型的Model,如BPE、WordPiece和Unigram(完整列表在这里)。
  • trainers 包含所有不同类型的 trainer,可以使用一个语料库训练你的模型(每种模型一个;完整列表在这里)。
  • post_processors 包含可以使用的各种类型的PostProcessor(完整列表在这里)。
  • decoders 包含各种类型的Decoder,可以用来解码标记化的输出(完整列表在这里)。

在这里可以看到如何从头开始训练一个tokenizer。

PS:tokenizer的使用还会持续更新,欢迎交流。

你可能感兴趣的:(学习笔记,#,炼丹记录,HuggingFace,Pytorch,python,transformers,tokenizer)