0%

Transformers(2)_Tokenizers

Transformers中的Tokenizers(分词器)是处理文本的重要组件,它将原始文本转换为模型能够理解的数字序列。

Tokenizers的作用:

  1. 文本预处理:将原始文本转换为token序列
  2. 词汇映射:将token映射为数字ID,这些ID是模型实际处理的输入
  3. 特殊标记添加:添加[CLS], [SEP], [PAD]等特殊标记
  4. 处理长度限制:对过长序列进行截断,对过短序列进行填充

常见的分词策略:

  1. Word-based:基于空格和标点符号分词,例如NLTKSpaCy
    • 优点:直观易理解
    • 缺点:词汇表庞大,无法处理未知词(OOV问题)
  2. Subword-based:将词切分为子词单元
    • BPE (Byte-Pair Encoding)GPT系列使用
    • WordPieceBERT使用
    • SentencePiece:包含BPEUnigram方法,XLNet等使用
    • 优点:平衡词汇表大小和表达能力,能处理未知词
  3. Character-based:以字符为单位
    • 优点:词汇表小,无OOV问题
    • 缺点:序列长度增加,语义捕捉困难

1. 从头开始训练一个分词器

1
2
3
4
# 定义分词器
from tokenizers import Tokenizer
from tokenizers.models import BPE
tokenizer = Tokenizer(BPE(unk_token="[UNK]"))
1
2
3
# 定义分词器训练器
from tokenizers.trainers import BpeTrainer
trainer = BpeTrainer(special_tokens=["[UNK]", "[CLS]", "[SEP]", "[PAD]", "[MASK]"])
1
2
3
# 设置分词器预处理器
from tokenizers.pre_tokenizers import Whitespace
tokenizer.pre_tokenizer = Whitespace()
1
2
3
4
5
6
# 预览数据集
with open("./datas_save/wikitext-103-raw/wiki.train.raw", "r") as f:
for i, line in enumerate(f):
print(line)
if i == 5:
break
= Valkyria Chronicles III = 

Senjō no Valkyria 3 : Unrecorded Chronicles ( Japanese : 戦場のヴァルキュリア3 , lit . Valkyria of the Battlefield 3 ) , commonly referred to as Valkyria Chronicles III outside Japan , is a tactical role @-@ playing video game developed by Sega and Media.Vision for the PlayStation Portable . Released in January 2011 in Japan , it is the third game in the Valkyria series . Employing the same fusion of tactical and real @-@ time gameplay as its predecessors , the story runs parallel to the first game and follows the " Nameless " , a penal military unit serving the nation of Gallia during the Second Europan War who perform secret black operations and are pitted against the Imperial unit " Calamaty Raven " . 
 The game began development in 2010 , carrying over a large portion of the work done on Valkyria Chronicles II . While it retained the standard features of the series , it also underwent multiple adjustments , such as making the game more forgiving for series newcomers . Character designer Raita Honjou and composer Hitoshi Sakimoto both returned from previous entries , along with Valkyria Chronicles II director Takeshi Ozawa . A large team of writers handled the script . The game 's opening theme was sung by May 'n . 

 It met with positive sales in Japan , and was praised by both Japanese and western critics . After release , it received downloadable content , along with an expanded edition in November of that year . It was also adapted into manga and an original video animation series . Due to low sales of Valkyria Chronicles II , Valkyria Chronicles III was not localized , but a fan translation compatible with the game 's expanded edition was released in 2014 . Media.Vision would return to the franchise with the development of Valkyria : Azure Revolution for the PlayStation 4 . 
1
2
3
# 设置数据集训练分词器
files = [f"datas_save/wikitext-103-raw/wiki.{split}.raw" for split in ["test", "train", "valid"]]
tokenizer.train(files, trainer)
1
2
# 保存训练好的分词器
tokenizer.save("tokenizers/tokenizer-wiki.json")
1
2
# 加载分词器
tokenizer = Tokenizer.from_file("tokenizers/tokenizer-wiki.json")
1
2
3
4
# 使用分词器进行文本编码
output = tokenizer.encode("Hello, y'all! How are you 😁 ?")
# 查看分词结果
output.tokens
['Hello', ',', 'y', "'", 'all', '!', 'How', 'are', 'you', '[UNK]', '?']
1
2
# 查看编码结果
output.ids
[27253, 16, 93, 11, 5097, 5, 7961, 5112, 6218, 0, 35]
1
2
# 对编码结果进行解码
tokenizer.decode(output.ids)
"Hello , y ' all ! How are you ?"
1
2
# Token转化为数值
tokenizer.token_to_id("Hello")
27253
1
2
# 数值转化为Token
tokenizer.id_to_token(6218)
'you'
1
2
3
4
5
6
7
8
9
10
11
# 指定编码格式模板(调用后处理函数)
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]")),
],
)
1
tokenizer.encode("你好啊", '?').tokens
['[CLS]', '你', '好', '[UNK]', '[SEP]', '?', '[SEP]']
1

2. Tokenizers Pipeline

我们使用Tokenizer.encode进行编码时,在Tokenizers内部实际上是经过了以下4个步骤:

  1. normalization(标准化/正则化)
  2. pre-tokenization(分词器预处理)
  3. model(分词器处理)
  4. post-processing(分词器后处理)

接下来我们将详细了解这些步骤中的每一步具体发生了什么,以及当你想要decode一些 Token 时的情况,还有 Tokenizers 如何让你根据自己的需求定制这些步骤中的每一步。

1
2
from tokenizers import Tokenizer
tokenizer = Tokenizer.from_file("tokenizers/tokenizer-wiki.json")

2.1 normalization

标准化,简而言之,是对原始字符串应用的一组操作,使其不那么随机或 “更干净”。常见的操作包括去除空白、删除重音字符或把所有文本转换为小写。
在 🤗 Tokenizers 库中,每个规范化操作都由一个Normalizer表示,并且你可以通过使用normalizers.Sequence组合多个这样的操作。

1
2
# 未使用前
tokenizer.encode("Héllò hôw are ü?").tokens
['H', 'é', 'll', 'ò', 'h', 'ô', 'w', 'are', 'ü', '?']
1
2
3
from tokenizers import normalizers
from tokenizers.normalizers import NFD, StripAccents
normalizer = normalizers.Sequence([NFD(), StripAccents()])
1
normalizer.normalize_str("Héllò hôw are ü?")  # 去除重音符号
'Hello how are u?'
1
2
# 应用到tokenizer中
tokenizer.normalizer = normalizer
1
2
# 使用后
tokenizer.encode("Héllò hôw are ü?").tokens
['Hello', 'how', 'are', 'u', '?']

2.2 Pre-Tokenization

预分词是将文本拆分为较小对象的行为,这些对象为训练结束时的token提供了一个上限。

一种很好的思考方式是,预分词器将把你的文本拆分为 “单词”,然后,最终的 token 将是这些单词的一部分。

1
2
3
from tokenizers.pre_tokenizers import Whitespace
pre_tokenizer = Whitespace() # 针对空格和标点符号进行切分
pre_tokenizer.pre_tokenize_str("Hello! How are you? I'm fine, thank you.")
[('Hello', (0, 5)),
 ('!', (5, 6)),
 ('How', (7, 10)),
 ('are', (11, 14)),
 ('you', (15, 18)),
 ('?', (18, 19)),
 ('I', (20, 21)),
 ("'", (21, 22)),
 ('m', (22, 23)),
 ('fine', (24, 28)),
 (',', (28, 29)),
 ('thank', (30, 35)),
 ('you', (36, 39)),
 ('.', (39, 40))]
1
2
3
4
5
# 通过 pre_tokenizers.Sequence 组合多个预分词器
from tokenizers import pre_tokenizers
from tokenizers.pre_tokenizers import Digits
pre_tokenizer = pre_tokenizers.Sequence([Whitespace(), Digits(individual_digits=True)]) # 先使用空格和标点符号切分,同时将数字切分为单个数字
pre_tokenizer.pre_tokenize_str("Call 911!")
[('Call', (0, 4)), ('9', (5, 6)), ('1', (6, 7)), ('1', (7, 8)), ('!', (8, 9))]
1
2
# 定义预分词器前
tokenizer.encode("Call 911!").tokens
['Call', '9', '11', '!']
1
2
3
# 定义预分词器后
tokenizer.pre_tokenizer = pre_tokenizer
tokenizer.encode("Call 911!").tokens
['Call', '9', '1', '1', '!']

2.3 Model

一旦输入文本被标准化并进行预分词后,TokenizerModel应用于预分词结果。这是管道中需要在你的语料库上进行训练的部分(或者如果你使用的是预训练的分词器,则是已经经过训练的部分)。模型的作用是使用它所学到的规则将你的 “单词” 分割成token,并负责将这些token映射到模型词汇表中的相应 ID。

Model在初始化Tokenizer时被传递,因此您已经知道如何自定义此部分。

前面我们定义过tokenizer的代码如下所示

1
2
3
4
# 定义分词器
from tokenizers import Tokenizer
from tokenizers.models import BPE
tokenizer = Tokenizer(BPE(unk_token="[UNK]"))

  • models.BPE:最流行的子词标记化算法之一。字节对编码(Byte-Pair-Encoding,BPE)从字符开始,将最常一起出现的字符合并,从而创建新的标记。然后,它通过迭代从语料库中看到的最频繁的字符对中构建新的标记。BPE 能够通过使用多个子词标记来构建它从未见过的单词,因此需要较小的词汇表,出现 “未知”(unknown)标记的可能性也较小。
  • models.Unigram:单字组(Unigram)也是一种子词分词算法,其工作原理是尝试确定最佳的子词标记集,以最大化给定句子的概率。这与字节对编码(BPE)的不同之处在于,它不是基于依次应用的一组规则来确定的。相反,单字组能够计算多种标记化方式,同时选择最可能的一种。
  • models.WordLevel:这是 “经典” 的标记化算法。它让你简单地将单词映射到 ID,无需任何花哨的操作。这具有非常易于使用和理解的优点,但为了良好的覆盖范围,它需要非常大的词汇表。使用这个Model需要使用一个PreTokenizer。这个模型不会直接做出任何选择,它只是将输入标记映射到 ID。
  • models.WordPiece:这是一种子词标记化算法,与 BPE 非常相似,主要由谷歌在 BERT 等模型中使用。它使用贪婪算法,首先尝试构建长词,当整个单词不在词汇表中时拆分为多个标记。这与从字符开始、尽可能构建更大标记的 BPE 不同。它使用著名的##前缀来识别作为单词一部分的标记(即不是以单词开头)。

2.4 Post-Processing

后处理是标记化管道的最后一步,在返回Encoding之前对其执行任何额外的转换,例如添加潜在的特殊标记。例如前面我们使用的指定编码格式模板

1
2
3
4
5
6
7
8
9
10
11
# 指定编码格式模板(调用后处理函数)
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]")),
],
)

-------------本文结束感谢您的阅读-------------