• huggingface Tokenizers 官网文档学习:tokenizer训练保存与使用


    前言

    本文分享学习 huggingface Tokenizers 库记录。我分成了五大主题:

    • 从头快速训练一个 tokenzier
    • 如何使用预训练好的 tokenzier
    • Tokenization 四大过程详解
    • BERT tokenizer 训练保存编解码全流程
    • 语料库分批加载与处理

    从头快速训练一个 tokenzier

    准备数据

    数据用wikitext-103 (516M of text) ,每行一条文本。下载并解压到本地

    wget https://s3.amazonaws.com/research.metamind.io/wikitext/wikitext-103-raw-v1.zip
    unzip wikitext-103-raw-v1.zip
    
    • 1
    • 2

    开始训练

    以BPE tokenizer为例,主要步骤是:实例化 BPE tokenizerBpeTrainer,调用 tokenizer.train 训练,调用 tokenizer.save 保存成json文件。

    from tokenizers import Tokenizer
    from tokenizers.models import BPE
    from tokenizers.trainers import BpeTrainer
    from tokenizers.pre_tokenizers import Whitespace
    
    # Step 1:实例化一个空白的BPE tokenizer
    tokenizer = Tokenizer(BPE(unk_token="[UNK]"))
    
    # Step 2:实例化一个BPE tokenizer 的训练器 trainer 这里 special_tokens 的顺序决定了其id排序
    trainer = BpeTrainer(
        special_tokens=["[UNK]", "[CLS]", "[SEP]", "[PAD]", "[MASK]"],
        min_frequency=1,
        show_progress=True,
        vocab_size=40000
    )
    
    # Step 3:定义预分词规则(比如以空格预切分)
    tokenizer.pre_tokenizer = Whitespace()
    
    # Step 4:加载数据集 训练tokenizer
    files = [f"./wikitext-103-raw/wiki.test.raw"]
    tokenizer.train(files, trainer)
    
    # Step 5:保存 tokenizer 
    tokenizer.save("./tokenizer-wiki.json")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    训练完后得到一个json文件,里面长这样:
    在这里插入图片描述

    加载并使用训练好的tokenizer

    # 加载 tokenzier
    tokenizer = Tokenizer.from_file("./tokenizer-wiki.json")
    
    # 使用 tokenizer
    sentence = "Hello, y'all! How are you 😁 ? 这句话的中文是什么?"
    output = tokenizer.encode(sentence)
    print(output, "\n")
    print("output的属性:", dir(output), "\n")
    print("output.tokens: ", output.tokens, "\n")
    print("output.ids: ", output.ids, "\n")
    print("查找某token([UNK])在词表中的索引: ", tokenizer.token_to_id("[UNK]"), "\n")
    i = 12
    print(f"找出原句子中第{i}个token在output的索引: ", output.offsets[i], f"即 “{sentence[output.offsets[i][0]:output.offsets[i][1]]}”", "\n")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    在这里插入图片描述

    自定义后处理模版

    自定义后处理模版(比如给句子前后自动加[CLS]和[SEP]),并重新存一份包含后处理功能的 tokenizer。

    # $A 指第一个句子,$A:0 表示第一个句子中token的type_ids全是0,默认是0故省略([SEP]:0同理)
    # $B 指第一个句子,$B:1 表示第一个句子中token的type_ids全是1([SEP]:1 同理)
    from tokenizers.processors import TemplateProcessing
    tokenizer.post_processor = TemplateProcessing(
        single="[CLS] $A [SEP]",
        pair="[CLS] $A [SEP] $B:1 [SEP]:1",
        special_tokens=[
            ("[CLS]", tokenizer.token_to_id("[CLS]")),
            ("[SEP]", tokenizer.token_to_id("[SEP]")),
        ],
    )
    tokenizer.save("./tokenizer-wiki-with-template.json")
    output = tokenizer.encode("Hello, y'all!", "How are you 😁 ?")
    print(output, "\n")
    print(output.type_ids, "\n")
    print(output.tokens, "\n")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    在这里插入图片描述

    批量处理与PAD补全

    传入list中的可以是str类型的单句,也可以是list类型的句子对儿。批处理时可以用 tokenizer.enable_padding 将这个批次的句子pad成该批次最大句子长度,当然,也可以pad成一个固定长度。被pad部分不计算attention,所以attention_mask的值为0。

    # 批量编码(无pad)
    outputs = tokenizer.encode_batch([
        "Hello, y'all!", 
        "How are you 😁 ?",
        ["sentence one", "sentence two"]
    ])
    for output in outputs:
        print(output.tokens)
    
    print("-"*10)
    
    # 批量编码(有pad)
    tokenizer.enable_padding(pad_id=0, pad_token="[PAD]") # pad_id 默认是0。因为可能有不同的pad形式,所以用pad_id来区分
    outputs = tokenizer.encode_batch([
        "Hello, y'all!", 
        "How are you 😁 ?",
        ["sentence one", "sentence two two two two two two two"]
    ])
    for output in outputs:
        print(output.tokens)
        print(output.attention_mask) # 被pad的部分不计算attention,所以attention_mask为0
        print()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    在这里插入图片描述

    使用预训练的 tokenzier

    从Hugging hub里加载

    在 huggingface hub 中的模型,只要有 tokenizer.json 文件就能直接用 from_pretrained 加载。

    from tokenizers import Tokenizer
    
    tokenizer = Tokenizer.from_pretrained("bert-base-uncased")
    output = tokenizer.encode("This is apple's bugger! 中文是啥?")
    print(output.tokens)
    print(output.ids)
    print(tokenizer.get_vocab_size())
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在这里插入图片描述

    从词表文件里加载

    还可以从.txt 词表文件里(每行一个token)加载,得指定已经实现的 Tokenizer 类,比如 BertWordPieceTokenizer

    from tokenizers import BertWordPieceTokenizer
    
    tokenizer = BertWordPieceTokenizer("./bert-base-uncased-vocab.txt", lowercase=True)
    output = tokenizer.encode("This is apple's bugger! 中文是啥?")
    print(output.tokens)
    print(output.ids)
    print(tokenizer)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在这里插入图片描述

    Tokenization 全过程详解

    调用 Tokenizer.encode 或 Tokenizer.encode_batch 时,文本经过了以下过程:

    • normalization:对原始输入做诸如转小写,去除空格等处理。
    • pre-tokenization:把语料库进行拆分,数量为最终词表量的上限,训练时就基于这个拆分结果进行操作。
    • model:初始化Tokenizer时传入,比如BPE
    • post-processing:后处理

    Step1. Normalization

    Normalization 对文本进行初次清洗和处理。这里分别进行Unicode正规化、去除读音、转小写等三步。

    from tokenizers import normalizers
    from tokenizers.normalizers import NFD, StripAccents, Lowercase
    
    # 定义一个normalizer
    normalizer = normalizers.Sequence([
        NFD(),          # Unicode正规化
        StripAccents(), # 去除读音
        Lowercase()]    # 转小写
    )
    normalizer.normalize_str("Héllò hôw are ü?")
    # Output: 'hello how are u?'
    
    tokenizer.normalizer = normalizer  # 更新到tokenizer里
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    官方代码自带以下这些 normalizers(详见文档举例)(NFD,NFKD,NFC,NFKC,Lowercase,Strip,StripAccents,Replace,BertNormalizer,lowercase):
    在这里插入图片描述

    Step2. Pre-Tokenization

    然后是 Pre-Tokenization。比如以空格进行切分,输出一个list,各元素均为元组。代码如下:

    from tokenizers.pre_tokenizers import Whitespace
    pre_tokenizer = Whitespace()
    pre_tokenizer.pre_tokenize_str("Hello! How are you? I'm fine, thank you.")
    
    • 1
    • 2
    • 3

    在这里插入图片描述
    也可以用 pre_tokenizers.Sequence 调用多个 pre_tokenizers。

    from tokenizers import pre_tokenizers
    from tokenizers.pre_tokenizers import Digits
    
    pre_tokenizer = pre_tokenizers.Sequence([
        Whitespace(), 
        Digits(individual_digits=True)  # 如果 individual_digits=False,“911”就不会被单独分成数字
    ])
    pre_tokenizer.pre_tokenize_str("Call 911! How are you? I'm fine thank you")
    
    tokenizer.pre_tokenizer = pre_tokenizer   # 更新到tokenizer里
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    在这里插入图片描述
    官方代码自带的pre-tokenizer(详见文档举例)有ByteLevel,Whitespace,WhitespaceSplit,Punctuation,Metaspace,CharDelimiterSplit,Digits,Split:
    在这里插入图片描述

    Step3. Model

    这一步负责把word拆成token,并把token映射成id。初始化Tokenizer时需要传入这个model,比如第一部分“开始训练”那块的代码 tokenizer = Tokenizer(BPE(unk_token="[UNK]"))

    官方代码自带这四个 Model(详见文档举例):WordLevel、BPE、WordPiece、Unigram,也可参考我之前的文章,对这几个算法有详细解释。
    在这里插入图片描述

    Step4. Post-Processing

    最后是后处理,实现比如加special token([CLS]、[SEP])等操作。具体可参考前面“自定义后处理模版”的部分。显然,pre-tokenizer 或者 normalizer 修改之后得重新训 tokenzier,而后处理改了就不用重新训。

    BERT tokenizer 训练保存编解码全流程

    训练bert tokenizer流程

    下面是根据上面4步,训练bert tokenizer的全流程:用WordPiece进行分词;用Lowercase, NFD, StripAccents 进行正规化;用Whitespace进行前处理等等,详见代码。

    from tokenizers import Tokenizer
    from tokenizers.models import WordPiece
    from tokenizers import normalizers
    from tokenizers.normalizers import Lowercase, NFD, StripAccents
    from tokenizers.pre_tokenizers import Whitespace
    from tokenizers.processors import TemplateProcessing
    from tokenizers.trainers import WordPieceTrainer
    
    # 用 WordPiece 实例化 Tokenizer
    bert_tokenizer = Tokenizer(WordPiece(unk_token="[UNK]"))
    
    # 用 Unicode正则化、去除读音、转小写等流程实例化 normalizer,赋给 bert_tokenizer 对象
    bert_tokenizer.normalizer = normalizers.Sequence([NFD(), Lowercase(), StripAccents()])
    
    # 用空格和标点进行前处理分词
    pre_tokenizer = Whitespace()
    bert_tokenizer.pre_tokenizer = pre_tokenizer
    
    # 定义对单句、句对儿做后处理的规则
    bert_tokenizer.post_processor = TemplateProcessing(
        single="[CLS] $A [SEP]",
        pair="[CLS] $A [SEP] $B:1 [SEP]:1",
        special_tokens=[
            ("[CLS]", 1),
            ("[SEP]", 2),
        ],
    )
    
    # 实例化一个 WordPieceTrainer,加载数据,训练并保存
    trainer = WordPieceTrainer(
        vocab_size=30522, 
        special_tokens=["[UNK]", "[CLS]", "[SEP]", "[PAD]", "[MASK]"]
    )
    files = [f"./wikitext-103-raw/wiki.{split}.raw" for split in ["test", "train", "valid"]]
    bert_tokenizer.train(files, trainer)
    bert_tokenizer.save("bert-wiki.json")
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    训完之后得到 bert-wiki.json 文件,长这样:
    在这里插入图片描述

    训练完的 BERT tokenizer 编解码测试

    decoder 为默认(null)时,解码会有 ##zer 这种没合并的 token;decoder 基于 WordPiece 时,会对结果进行合并,因为训练时就用的 WordPiece 算法,所以对应的解码器实现了合并的过程。代码如下:

    from tokenizers import decoders, Tokenizer
    
    # 编解码测试:加载 tokenizer 文件
    bert_tokenizer = Tokenizer.from_file("./bert-wiki.json")
    
    # decoder 为默认(null)时,解码会有 '##zer' 这种没合并的 token
    encoded_output = bert_tokenizer.encode("Welcome to the 🤗 Tokenizers library.")
    print(encoded_output.tokens)
    decoded_output = bert_tokenizer.decode(encoded_output.ids)
    print(decoded_output)
    
    # decoder 基于 WordPiece 时,会对结果进行合并
    bert_tokenizer.decoder = decoders.WordPiece()
    decoded_output = bert_tokenizer.decode(encoded_output.ids)
    print(decoded_output)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    在这里插入图片描述

    除了 WordPiece 之外,官方代码里还有 ByteLevel、Metaspace 共三种 Decoder,详见文档

    语料库分批加载与处理

    如果一次性加载语料库加载不进来,则可以使用迭代器分批次加载,使用 tokenizer.train_from_iterator 方法,或参考API文档
    在这里插入图片描述

    官方文档:https://huggingface.co/docs/tokenizers

  • 相关阅读:
    树莓派安装OpenCV2教程 (详细教程)
    挑战杯 基于大数据的社交平台数据爬虫舆情分析可视化系统
    可观测性-Metrics-系统CPU、进程CPU指标
    超详细的springBoot学习笔记
    [CG] 用 Docker 配置 Ubuntu OpenGL 环境
    隆云通空气温湿、CO2、PM2.5、光照五参数传感器
    【C++ 实战】概论 | 代码风格 | 类
    SpringBoot整合Spring Security【超详细教程】
    竞赛 深度学习大数据物流平台 python
    vue解决父组件调用子组件只执行一次问题
  • 原文地址:https://blog.csdn.net/muyao987/article/details/126304969