Tokenzier分词技术

  1. 切分粒度

基于词word的切分

基于字char的切分

基于subword的切分(主流切分方式)

常见模型对应的分词技术
分词方法 典型模型
BPE GPT, GPT-2, GPT-J, GPT-Neo, RoBERTa, BART, LLaMA, ChatGLM-6B, Baichuan
WordPiece BERT, DistilBERT,MobileBERT
Unigram AlBERT, T5, mBART, XLNet
基于词的切分的缺点:
  • 词表规模过大
  • 一定会存在UNK,造成信息丢失。
  • 不能学习到词缀之间的关系,例如:dog与dogs,happy与unhappy。
基于字char的切分的缺点:
  • 每个token的信息密度低。
  • 序列过长,解码效率很低。
subword的基本切分原则是:
  • 高频词依旧切分成完整的整词。
  • 低频词被切分成有意义的子词,例如 dogs => [dog, ##s]。
基于subword的切分可以实现:
  • 词表规模适中,解码效率较高。
  • 不存在UNK,信息不丢失。
  • 能学习到词缀之间的关系。

Byte Pair Encoding [BPE(/BBPE)]

BPE(字节对)编码或二元编码是一种简单的数据压缩形式,其中最常见的一对连续字节数据被替换为该数据中不存在的字节。后期使用时需要一个替换表来重建原始数据。OpenAI GPT-2 与Facebook RoBERTa均采用此方法构建subword vector.
  • 优点
    • 可以有效地平衡词汇表大小和步数(编码句子所需的token数量)。
  • 缺点
    • 基于贪婪和确定的符号替换,不能提供带概率的多个分片结果。

算法

  1. 准备足够大的训练语料
  2. 确定期望的subword词表大小
  3. 将单词拆分为字符序列并在末尾添加后缀“ </ w>”,统计单词频率。本阶段的subword的粒度是字符。例如,“ low”的频率为5,那么我们将其改写为“ l o w </ w>”:5
  4. 统计每一个连续字节对的出现频率,选择最高频者合并成新的subword
  5. 重复第4步直到达到第2步设定的subword词表大小或下一个最高频的字节对出现频率为1
停止符"</w>"的意义在于表示subword是词后缀。举例来说:"st"字词不加"</w>"可以出现在词首如"st ar",加了"</w>"表明改字词位于词尾,如"wide st</w>",二者意义截然不同。
每次合并后词表可能出现3种变化:
  • +1,表明加入合并后的新字词,同时原来在2个子词还保留(2个字词不是完全同时连续出现)
  • +0,表明加入合并后的新字词,同时原来2个子词中一个保留,一个被消解(一个字词完全随着另一个字词的出现而紧跟着出现)
  • -1,表明加入合并后的新字词,同时原来2个子词都被消解(2个字词同时连续出现)
实际上,随着合并的次数增加,词表大小通常先增加后减小。

WordPiece

WordPiece算法可以看作是BPE的变种。不同点在于,WordPiece基于概率生成新的subword而不是下一最高频字节对。

算法

  1. 准备足够大的训练语料
  2. 确定期望的subword词表大小
  3. 将单词拆分成字符序列
  4. 基于第3步数据训练语言模型
  5. 从所有可能的subword单元中选择加入语言模型后能最大程度地增加训练数据概率的单元作为新的单元
  6. 重复第5步直到达到第2步设定的subword词表大小或概率增量低于某一阈值

Unigram

ULM是另外一种subword分隔算法,它能够输出带概率的多个子词分段。它引入了一个假设:所有subword的出现都是独立的,并且subword序列由subword出现概率的乘积产生。WordPiece和ULM都利用语言模型建立subword词表。

算法

  1. 准备足够大的训练语料
  2. 确定期望的subword词表大小
  3. 给定词序列优化下一个词出现的概率
  4. 计算每个subword的损失
  5. 基于损失对subword排序并保留前X%。为了避免OOV,建议保留字符级的单元
  6. 重复第3至第5步直到达到第2步设定的subword词表大小或第5步的结果不再变化

总结

  1. subword可以平衡词汇量和对未知词的覆盖。极端的情况下,我们只能使用26个token(即字符)来表示所有英语单词。一般情况,建议使用16k或32k子词足以取得良好的效果,Facebook RoBERTa甚至建立的多达50k的词表。
  2. 对于包括中文在内的许多亚洲语言,单词不能用空格分隔。因此,初始词汇量需要比英语大很多。
  1. 完整的分词

文本归一化
最基础的文本清洗,包括删除多余的换行空格转小写移除音调等。
Berttokenizer input: Héllò hôw are ü? normalization: hello how are u?
预切分
预分词阶段会把句子切分成更小的“词”单元。可以基于空格或者标点进行切分。不同的tokenizer的实现细节是不一样的。
input: Hello, how are you? pre-tokenize: [BERT]: [('Hello', (0, 5)), (',', (5, 6)), ('how', (7, 10)), ('are', (11, 14)), ('you', (16, 19)), ('?', (19, 20))] [GPT2]: [('Hello', (0, 5)), (',', (5, 6)), ('Ġhow', (6, 10)), ('Ġare', (10, 14)), ('Ġ', (14, 15)), ('Ġyou', (15, 19)), ('?', (19, 20))] [t5]: [('▁Hello,', (0, 6)), ('▁how', (7, 10)), ('▁are', (11, 14)), ('▁you?', (16, 20))]
BERT的tokenizer就是直接基于空格和标点进行切分。 GPT2也是基于空格和标签,但是空格会保留成特殊字符“Ġ”。 T5则只基于空格进行切分,标点不会切分。并且空格会保留成特殊字符"",并且句子开头也会添加特殊字符""。
基于分词模型的切分
分词模型包括:BPE,WordPiece 和 Unigram 三种分词模型。

BPE

  • 训练方法:从字符级的小词表出发,训练产生合并规则以及一个词表
  • 编码方法:将文本切分成字符,再应用训练阶段获得的合并规则
  • 经典模型:GPT, GPT-2, RoBERTa, BART, LLaMA, ChatGLM等
假设语料库如下:
corpus = [ "This is the Hugging Face Course.", "This chapter is about tokenization.", "This section shows several tokenizer algorithms.", "Hopefully, you will be able to understand how they are trained and generate tokens.", ]
采用预切分的方式是GPT2的切分方式。
[ [('This', (0, 4)), ('Ġis', (4, 7)), ('Ġthe', (7, 11)), ('ĠHugging', (11, 19)), ('ĠFace', (19, 24)), ('ĠCourse', (24, 31)), ('.', (31, 32))], [('This', (0, 4)), ('Ġchapter', (4, 12)), ('Ġis', (12, 15)), ('Ġabout', (15, 21)), ('Ġtokenization', (21, 34)), ('.', (34, 35))], [('This', (0, 4)), ('Ġsection', (4, 12)), ('Ġshows', (12, 18)), ('Ġseveral', (18, 26)), ('Ġtokenizer', (26, 36)), ('Ġalgorithms', (36, 47)), ('.', (47, 48))], [('Hopefully', (0, 9)), (',', (9, 10)), ('Ġyou', (10, 14)), ('Ġwill', (14, 19)), ('Ġbe', (19, 22)), ('Ġable', (22, 27)), ('Ġto', (27, 30)), ('Ġunderstand', (30, 41)), ('Ġhow', (41, 45)), ('Ġthey', (45, 50)), ('Ġare', (50, 54)), ('Ġtrained', (54, 62)), ('Ġand', (62, 66)), ('Ġgenerate', (66, 75)), ('Ġtokens', (75, 82)), ('.', (82, 83))] ]
然后统计这个语料库中所有单词的词频:
defaultdict(<class 'int'>, {'This': 3, 'Ġis': 2, 'Ġthe': 1, 'ĠHugging': 1, 'ĠFace': 1, 'ĠCourse': 1, '.': 4, 'Ġchapter': 1, 'Ġabout': 1, 'Ġtokenization': 1, 'Ġsection': 1, 'Ġshows': 1, 'Ġseveral': 1, 'Ġtokenizer': 1, 'Ġalgorithms': 1, 'Hopefully': 1, ',': 1, 'Ġyou': 1, 'Ġwill': 1, 'Ġbe': 1, 'Ġable': 1, 'Ġto': 1, 'Ġunderstand': 1, 'Ġhow': 1, 'Ġthey': 1, 'Ġare': 1, 'Ġtrained': 1, 'Ġand': 1, 'Ġgenerate': 1, 'Ġtokens': 1})
BPE是从字符级别的小词表,逐步合并成大词表,所以需要先获得字符级别的小词表。
BPE字符级别的小词表(就是这个语料库中所有字符的集合)如下:
['i', 't', 'p', 'o', 'r', 'm', 'e', ',', 'y', 'v', 'Ġ', 'F', 'a', 'C', 'H', '.', 'f', 'l', 'u', 'c', 'T', 'k', 'h', 'z', 'd', 'g', 'w', 'n', 's', 'b']
基于小词表对整个语料库中的单词进行切分如下:
'This': ['T', 'h', 'i', 's'], 'Ġis': ['Ġ', 'i', 's'], 'Ġthe': ['Ġ', 't', 'h', 'e'], ... 'Ġand': ['Ġ', 'a', 'n', 'd'], 'Ġgenerate': ['Ġ', 'g', 'e', 'n', 'e', 'r', 'a', 't', 'e'], 'Ġtokens': ['Ġ', 't', 'o', 'k', 'e', 'n', 's']
统计相邻两个字符同时出现的频率:
defaultdict(<class 'int'>, {('T', 'h'): 3, ('h', 'i'): 3, ('i', 's'): 5, ('Ġ', 'i'): 2, ('Ġ', 't'): 7, ('t', 'h'): 3, ..., ('n', 's'): 1})
经过统计,当前频率最高的pair为: ('Ġ', 't'), 频率为7次。 将('Ġ', 't')合并成一个词并添加到词表中。同时在合并规则中添加('Ġ', 't')这条合并规则。
从而获得新的word2split。
重复上述循环直到整个词表的大小达到预先设定的词表大小。
假定最终词表的大小为50,经过上述迭代后我们获得的词表和合并规则如下:
vocabs = ['i', 't', 'p', 'o', 'r', 'm', 'e', ',', 'y', 'v', 'Ġ', 'F', 'a', 'C', 'H', '.', 'f', 'l', 'u', 'c', 'T', 'k', 'h', 'z', 'd', 'g', 'w', 'n', 's', 'b', 'Ġt', 'is', 'er', 'Ġa', 'Ġto', 'en', 'Th', 'This', 'ou', 'se', 'Ġtok', 'Ġtoken', 'nd', 'Ġis', 'Ġth', 'Ġthe', 'in', 'Ġab', 'Ġtokeni', 'Ġtokeniz'] merge_rules = [('Ġ', 't'), ('i', 's'), ('e', 'r'), ('Ġ', 'a'), ('Ġt', 'o'), ('e', 'n'), ('T', 'h'), ('Th', 'is'), ('o', 'u'), ('s', 'e'), ('Ġto', 'k'), ('Ġtok', 'en'), ('n', 'd'), ('Ġ', 'is'), ('Ġt', 'h'), ('Ġth', 'e'), ('i', 'n'), ('Ġa', 'b'), ('Ġtoken', 'i'), ('Ġtokeni', 'z')]
推理阶段
在推理阶段,给定一个句子,我们需要将其切分成一个token的序列。 具体实现上需要先对句子进行预分词并切分成字符级别的序列,然后根据合并规则进行合并。
也可以在每个单词的最后加上/w,用于表示单词的结束。

BBPE(原始的LLaMA模型)

核心思想是用byte来构建最基础的词表而不是字符。首先将文本按照UTF-8进行编码,每个字符在UTF-8的表示中占据1-4个byte。 在byte序列上再使用BPE算法,进行byte level的相邻合并。
UTF-8使用1~4字节为每个字符编码:
  • 一个US-ASCII字符只需1字节编码(Unicode范围由U+0000~U+007F)。
  • 带有变音符号的拉丁文、希腊文、西里尔字母、亚美尼亚语、希伯来文、阿拉伯文、叙利亚文等字母则需要2字节编码(Unicode范围由U+0080~U+07FF)。
  • 其他语言的字符(包括中日韩文字、东南亚文字、中东文字等)包含了大部分常用字,使用3字节编码。其他极少使用的语言字符使用4字节编码。
参考论文:Neural Machine Translation with Byte-Level Subwords
优点和缺点:
对于英文、拉美体系的语言来说使用BPE分词足以在可接受的词表大小下解决OOV的问题,但面对中文、日文等语言时,其稀有的字符可能会不必要的占用词汇表,因此考虑使用字节级别byte-level解决不同语言进行分词时OOV的问题。
观察到在 1K时,E595 8F 没有被合并,在2K时被合并成 日本 "聞" 。
BBPE 的优点:
兼容支持多语言的分词;

WordPiece

过程和BPE类似,合并两个字符的依据是互信息,不是概率。
原因:
WordPiece每次选择合并的两个子词,他们具有最大的互信息值,也就是两子词在语言模型上具有较强的关联性,它们经常在语料中以相邻方式同时出现。

Unigram

  1. 初始时,建立一个足够大的词表。一般,可用语料中的所有字符加上常见的子字符串初始化词表,也可以通过BPE算法初始化。
  2. 针对当前词表,用EM算法求解每个子词在语料上的概率。
  3. 对于每个子词,计算当该子词被从词表中移除时,总的loss降低了多少,记为该子词的loss。
  4. 将子词按照loss大小进行排序,丢弃一定比例loss最小的子词(比如20%),保留下来的子词生成新的词表。这里需要注意的是,单字符不能被丢弃,这是为了避免OOV情况。
  5. 重复步骤2到4,直到词表大小减少到设定范围。
例如:
词表:
["h", "u", "g", "hu", "ug", "p", "pu", "n", "un", "b", "bu", "s", "hug", "gs", "ugs"]
("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5) h,u,g hu,g h,ug hug
("h", 15) ("u", 36) ("g", 20) ("hu", 15) ("ug", 20) ("p", 17) ("pu", 17) ("n", 16) ("un", 16) ("b", 4) ("bu", 4) ("s", 5) ("hug", 15) ("gs", 5) ("ugs", 5)
统计各个词表中subword的频率,找到每个单词最大的概率。比如hug:
参考文档:https://huggingface.co/learn/nlp-course/zh-CN/chapter6/7
选择概率最大的计算其概率,再计算各个单词的损失。
找到移除某个单词损失最小的子词,发现任何一个都一样,因此随机选ug。
第二次迭代:
移除的token,比如hu,计算损失为168.20,其他token移除的loss如下:
其中bu移除后loss最小,因此选择bu。不断迭代直到词表减小到指定范围。

后处理

后处理阶段会包括一些特殊的分词逻辑,例如添加sepcial token:[CLS],[SEP]等。

SentencePiece

是Google出的一个分词工具:
  • 内置BPE,Unigram,char和word的分词方法。
  • 无需预分词,以unicode方式直接编码整个句子,空格会被特殊编码为▁。
  • 相比传统实现进行优化,分词速度速度更快。

Tiktoken

Tiktoken 是一个快速BPE标记器,用于OpenAI的模型。
p50k_base与r50k_base重叠很大,在非代码应用中,它们通常会给出相同的tokens。

常见QA

  1. 关于大模型为啥在数值比较上表现不佳:
11和9分别被分词id为994,24,大模型比较的时候按位比较,无法区分这是一个小数,因此只要向AI解释明白这是一个双精度浮点数,在后续自注意力机制的作用下,AI就会明白要把9.11连起来处理了。
https://zhuanlan.zhihu.com/p/709093105
  1. tokenizer的压缩率:
压缩率,描述压缩文本符号的效果,是文本压缩后的token数大小与压缩前的大小之比。压缩率越小,说明分词器对文本编码效率越高。
  1. 如何解决OOV问题?
每个计算机能表示的字符总能用Unicode表示,可以给所有可能的字符都编码。
Unicode 的全称是 Universal Multiple-Octet Coded Character Set(通用多八位字符集,简称 UCS)。Unicode 在一个字符集中包含了世界上所有文字和符号,统一编码,来终结不同编码产生乱码的问题。
Unicode 统一了所有字符的编码,是一个 Character Set,也就是字符集,字符集只是给所有的字符一个唯一编号,但是却没有规定如何存储。UTF-8其实只是unicode的一种存储方式。
用基于UTF-8的BBPE,词表可以相比于BPE减少到1/8(来自原论文的摘要,划重点)。
  1. 为什么LLaMA词表里只有700+的汉字,但是也能表示中文?
因为使用BBPE,根据上面的原理,不存在的汉字用字节合成就可以了。
  1. 既然LLaMA的分词器是基于UTF-8的BBPE,是不是所有的模型通用一个分词器即可,不需额外训练?
是不可以的。LLaMA尽管能支持中文,但是表示效率很低,一个中文字符在UTF-8表示为3个字节,意味着:最差情况下,1个汉字要编码成3个token。
但是如果在中文语料上训练分词器,很多2个汉字组成的词会被编码为1个token。大模型是逐个token生成,推理速度都是按token计算的,所以如果推理速度相同,LLaMA生成中文的速度会远慢于在专门在中文上训练的GLM系列模型。

参考文档:

  1. https://cloud.tencent.com/developer/article/2327739
  2. https://cloud.tencent.com/developer/article/2317900
  3. https://cloud.tencent.com/developer/article/2379776
  4. https://cloud.tencent.com/developer/article/1588531
 

__EOF__

本文作者Charlton要好好睡觉
本文链接https://www.cnblogs.com/charlton-99ing/p/18320645.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   Charlton_99ing  阅读(167)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
点击右上角即可分享
微信分享提示