【HuggingFace LLM】训练分词器简析

Tokenize共分为:

  1. 规范化;
  2. 预分词;
  3. 输入模型(生成tokens序列);
  4. 后处理(添加特殊标记[CLS]/[SEP]等,生成attention_mask以及type_ids);
分词器模型 大模型
WordPiece Bert
BPE GPT-2
Unigram XLNet

构建一个语料库

python 复制代码
from datasets import load_dataset

dataset = load_dataset("wikitext", name="wikitext-2-raw-v1", split="train")

def get_training_corpus():
    for i in range(0, len(dataset), 1000):
        yield dataset[i : i + 1000]["text"]

# 保存为本地
with open("wikitext-2.txt", "w", encoding="utf-8") as f:
    for i in range(len(dataset)):
        f.write(dataset[i]["text"] + "\n")

从0开始构建wordpiece

一般使用 model 实例化一个 Tokenizer 对象,然后将其 normalizerpre_tokenizerpost_processordecoder 属性设置为我们想要的值

构建tokenizer
python 复制代码
from tokenizers import (
    decoders,
    models,
    normalizers,
    pre_tokenizers,
    processors,
    trainers,
    Tokenizer,
)

tokenizer = Tokenizer(models.WordPiece(unk_token="[UNK]"))

这里构建了一个使用WordPiecetokenizer,并将未见过的token设为[UNK]返回。

规范化

两种方法,其一

python 复制代码
tokenizer.normalizer = normalizers.BertNormalizer(lowercase=True)

其二

python 复制代码
tokenizer.normalizer = normalizers.Sequence(
    [normalizers.NFD(), normalizers.Lowercase(), normalizers.StripAccents()]
)

可以指定对应的大模型规范化方法,也可以从头开始基于Sequence对象自己定义组装。

这里NFD()方法是为StripAccents()识别重音符号做前置工作。

python 复制代码
>>>print(tokenizer.normalizer.normalize_str("Héllò hôw are ü?"))
>>>hello how are u?
预分词

相同的,也有两种方法,其一

python 复制代码
tokenizer.pre_tokenizer = pre_tokenizers.BertPreTokenizer()

其二

python 复制代码
tokenizer.pre_tokenizer = pre_tokenizers.Whitespace()
tokenizer.pre_tokenizer = pre_tokenizers.WhitespaceSplit()
tokenizer.pre_tokenizer = pre_tokenizers.Sequence(
    [pre_tokenizers.WhitespaceSplit(), pre_tokenizers.Punctuation()]
)

可以通过Bert自带的预分词方法,也可以从0开始自己搭建。

Whitespace是根据空格、非字母、下划线、数字进行预分词,Whitespacesplit仅通过空格分词。

python 复制代码
>>>pre_tokenizer.pre_tokenize_str("Let's test my pre-tokenizer.")
>>>[('Let', (0, 3)), ("'", (3, 4)), ('s', (4, 5)), ('test', (6, 10)), ('my', (11, 13)), ('pre', (14, 17)), ('-', (17, 18)), ('tokenizer', (18, 27)), ('.', (27, 28))]
模型训练

在指定使用WordPiece后,还需要使用当前的预料对该分词算法进行训练,详见[[004 规范化与预分词#WordPiece tokenization|此]]。

python 复制代码
# 加载数据集训练
special_tokens = ["[UNK]", "[PAD]", "[CLS]", "[SEP]", "[MASK]"]
trainer = trainers.WordPieceTrainer(vocab_size=25000, special_tokens=special_tokens)
tokenizer.train_from_iterator(get_training_corpus(), trainer=trainer)

# 本地文本训练
tokenizer.model = models.WordPiece(unk_token="[UNK]")
tokenizer.train(["wikitext-2.txt"], trainer=trainer)

可以通过在线迭代器训练以及本地数据集训练两种方式

python 复制代码
encoding = tokenizer.encode("Let's test this tokenizer.")
print(encoding.tokens)
# ['let', "'", 's', 'test', 'this', 'tok', '##eni', '##zer', '.']

可以随时通过encode方法对输入数据进行tokenizer查看训练效果

!NOTE

获得的 encoding 是一个 Encoding ,它包含了分词器的各种属性中的所有必要输出,包括: idstype_idstokensoffsetsattention_maskspecial_tokens_maskoverflowing

后处理

后处理包括添加[CLS]/[SEP]等特殊字符,可以通过TemplateProsessor实现

python 复制代码
cls_token_id = tokenizer.token_to_id("[CLS]")
sep_token_id = tokenizer.token_to_id("[SEP]")
print(cls_token_id, sep_token_id)

首先需要获悉[CLS]/[SEP]等特殊字符的id号,这样才能再后续实现转化。

编辑模板时,第一句话用$A表示,第二句话用$B表示,每个符号后还需要加上对应的type_ids

python 复制代码
tokenizer.post_processor = processors.TemplateProcessing(
    single=f"[CLS]:0 $A:0 [SEP]:0",
    pair=f"[CLS]:0 $A:0 [SEP]:0 $B:1 [SEP]:1",
    special_tokens=[("[CLS]", cls_token_id), ("[SEP]", sep_token_id)],
)
python 复制代码
encoding = tokenizer.encode("Let's test this tokenizer.")
print(encoding.tokens)
# ['[CLS]', 'let', "'", 's', 'test', 'this', 'tok', '##eni', '##zer', '.', '[SEP]']

encoding = tokenizer.encode("Let's test this tokenizer...", "on a pair of sentences.")
print(encoding.tokens)
print(encoding.type_ids)
#['[CLS]', 'let', "'", 's', 'test', 'this', 'tok', '##eni', '##zer', '...', '[SEP]', 'on', 'a', 'pair', 'of', 'sentences', '.', '[SEP]']
#[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1]
添加解码器
python 复制代码
tokenizer.decoder = decoders.WordPiece(prefix="##")
>>>tokenizer.decode(encoding.ids)
>>>"let's test this tokenizer... on a pair of sentences."
保存分词器
python 复制代码
tokenizer.save("tokenizer.json")
new_tokenizer = Tokenizer.from_file("tokenizer.json")

# 保存通用
from transformers import PreTrainedTokenizerFast
wrapped_tokenizer = PreTrainedTokenizerFast(
    tokenizer_object=tokenizer,
    # tokenizer_file="tokenizer.json", # You can load from the tokenizer file, alternatively
    unk_token="[UNK]",
    pad_token="[PAD]",
    cls_token="[CLS]",
    sep_token="[SEP]",
    mask_token="[MASK]",
)

# 保存专业
from transformers import BertTokenizerFast
wrapped_tokenizer = BertTokenizerFast(tokenizer_object=tokenizer)

一般可以先把制作好的tokenizer使用json进行保存,这样可以本地加载。

如果需要远程加载的话,需要保存为一个PreTrainedTokenizerFast类,主要有通用保存 (需要手动的把所有的特殊字符进行配置,因为通用类无法自动识别);专业保存(直接保存即可,因为特殊字符配置已配置完毕)。

从0开始构建BPE

python 复制代码
tokenizer = Tokenizer(models.BPE())
tokenizer.pre_tokenizer = pre_tokenizers.ByteLevel(add_prefix_space=False)
>>>tokenizer.pre_tokenizer.pre_tokenize_str("Let's test pre-tokenization!")
>>>[('Let', (0, 3)), ("'s", (3, 5)), ('Ġtest', (5, 10)), ('Ġpre', (10, 14)), ('-', (14, 15)), ('tokenization', (15, 27)), ('!', (27, 28))]

trainer = trainers.BpeTrainer(vocab_size=25000, special_tokens=["<|endoftext|>"])
tokenizer.train_from_iterator(get_training_corpus(), trainer=trainer)
#tokenizer.model = models.BPE()
#tokenizer.train(["wikitext-2.txt"], trainer=trainer)
encoding = tokenizer.encode("Let's test this tokenizer.")
print(encoding.tokens)
# ['L', 'et', "'", 's', 'Ġtest', 'Ġthis', 'Ġto', 'ken', 'izer', '.']

tokenizer.post_processor = processors.ByteLevel(trim_offsets=False)
sentence = "Let's test this tokenizer."
encoding = tokenizer.encode(sentence)
start, end = encoding.offsets[4]
>>>sentence[start:end]
>>>' test'

tokenizer.decoder = decoders.ByteLevel()
>>>tokenizer.decode(encoding.ids)
>>>"Let's test this tokenizer."

from transformers import PreTrainedTokenizerFast
wrapped_tokenizer = PreTrainedTokenizerFast(
    tokenizer_object=tokenizer,
    bos_token="<|endoftext|>",
    eos_token="<|endoftext|>",
)

#from transformers import GPT2TokenizerFast
#wrapped_tokenizer = GPT2TokenizerFast(tokenizer_object=tokenizer)

pre_tokenizers.ByteLevel(add_prefix_space=False)选择不在预分词结果前添加空格。

processors.ByteLevel(trim_offsets=False)字节级后处理,trim=False告知保留Ġ开头token的偏移量,例如Ġtest对应的offset就应该切片得到 test,而不是test

从0开始构建Unigram

python 复制代码
tokenizer = Tokenizer(models.Unigram())
from tokenizers import Regex
tokenizer.normalizer = normalizers.Sequence(
    [
        normalizers.Replace("``", '"'),
        normalizers.Replace("''", '"'),
        normalizers.NFKD(),
        normalizers.StripAccents(),
        normalizers.Replace(Regex(" {2,}"), " "),
    ]
)

>>>tokenizer.pre_tokenizer = pre_tokenizers.Metaspace()
>>>tokenizer.pre_tokenizer.pre_tokenize_str("Let's test the pre-tokenizer!")
[("▁Let's", (0, 5)), ('▁test', (5, 10)), ('▁the', (10, 14)), ('▁pre-tokenizer!', (14, 29))]

special_tokens = ["<cls>", "<sep>", "<unk>", "<pad>", "<mask>", "<s>", "</s>"]
trainer = trainers.UnigramTrainer(
    vocab_size=25000, special_tokens=special_tokens, unk_token="<unk>"
)
tokenizer.train_from_iterator(get_training_corpus(), trainer=trainer)
encoding = tokenizer.encode("Let's test this tokenizer.")
print(encoding.tokens)
#['▁Let', "'", 's', '▁test', '▁this', '▁to', 'ken', 'izer', '.']

cls_token_id = tokenizer.token_to_id("<cls>")
sep_token_id = tokenizer.token_to_id("<sep>")
print(cls_token_id, sep_token_id)
#0 1

tokenizer.post_processor = processors.TemplateProcessing(
    single="$A:0 <sep>:0 <cls>:2",
    pair="$A:0 <sep>:0 $B:1 <sep>:1 <cls>:2",
    special_tokens=[("<sep>", sep_token_id), ("<cls>", cls_token_id)],
)
encoding = tokenizer.encode("Let's test this tokenizer...", "on a pair of sentences!")
print(encoding.tokens)
print(encoding.type_ids)
#['▁Let', "'", 's', '▁test', '▁this', '▁to', 'ken', 'izer', '.', '.', '.', '<sep>', '▁', 'on', '▁', 'a', '▁pair', '▁of', '▁sentence', 's', '!', '<sep>', '<cls>']
#[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2]

tokenizer.decoder = decoders.Metaspace()
from transformers import PreTrainedTokenizerFast
wrapped_tokenizer = PreTrainedTokenizerFast(
    tokenizer_object=tokenizer,
    bos_token="<s>",
    eos_token="</s>",
    unk_token="<unk>",
    pad_token="<pad>",
    cls_token="<cls>",
    sep_token="<sep>",
    mask_token="<mask>",
    padding_side="left",
)
#from transformers import XLNetTokenizerFast
#wrapped_tokenizer = XLNetTokenizerFast(tokenizer_object=tokenizer)

Unigram使用的预分词技术都是Metaspace,使用Unigram的后处理方式是把[CLS]放在最末尾

相关推荐
那个村的李富贵9 小时前
光影魔术师:CANN加速实时图像风格迁移,让每张照片秒变大师画作
人工智能·aigc·cann
腾讯云开发者10 小时前
“痛点”到“通点”!一份让 AI 真正落地产生真金白银的实战指南
人工智能
CareyWYR10 小时前
每周AI论文速递(260202-260206)
人工智能
hopsky11 小时前
大模型生成PPT的技术原理
人工智能
禁默12 小时前
打通 AI 与信号处理的“任督二脉”:Ascend SIP Boost 加速库深度实战
人工智能·信号处理·cann
心疼你的一切12 小时前
昇腾CANN实战落地:从智慧城市到AIGC,解锁五大行业AI应用的算力密码
数据仓库·人工智能·深度学习·aigc·智慧城市·cann
AI绘画哇哒哒12 小时前
【干货收藏】深度解析AI Agent框架:设计原理+主流选型+项目实操,一站式学习指南
人工智能·学习·ai·程序员·大模型·产品经理·转行
数据分析能量站12 小时前
Clawdbot(现名Moltbot)-现状分析
人工智能
那个村的李富贵12 小时前
CANN加速下的AIGC“即时翻译”:AI语音克隆与实时变声实战
人工智能·算法·aigc·cann
二十雨辰12 小时前
[python]-AI大模型
开发语言·人工智能·python