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
开始训练

以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")

训练完后得到一个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")

在这里插入图片描述

自定义后处理模版

自定义后处理模版(比如给句子前后自动加[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")

在这里插入图片描述

批量处理与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()

在这里插入图片描述

使用预训练的 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())

在这里插入图片描述

从词表文件里加载

还可以从.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)

在这里插入图片描述

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里

官方代码自带以下这些 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.")

在这里插入图片描述
也可以用 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里

在这里插入图片描述
官方代码自带的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")

训完之后得到 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)

在这里插入图片描述

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

语料库分批加载与处理

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

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

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值