哪个网站可以领手工回家做,网站开发 流程图,社群运营,电商平台有哪些企业1. 认识Tokenizer
1.1 为什么要有tokenizer#xff1f;
计算机是无法理解人类语言的#xff0c;它只会进行0和1的二进制计算。但是呢#xff0c;大语言模型就是通过二进制计算#xff0c;让你感觉计算机理解了人类语言。
举个例子#xff1a;单1#xff0c;双2#x…1. 认识Tokenizer
1.1 为什么要有tokenizer
计算机是无法理解人类语言的它只会进行0和1的二进制计算。但是呢大语言模型就是通过二进制计算让你感觉计算机理解了人类语言。
举个例子单1双2计算机面临“单”和“双”的时候它所理解的就是2倍关系。再举一个例子赞美1诋毁0 当计算机遇到0.5的时候它知道这是“毁誉参半”。再再举一个例子女王{1,1}女人{1,0}国王{0,1}它能明白“女人”“国王”“女王”。
可以看出计算机面临文字的时候都是要通过数字去理解的。
所以如何把文本转成数字是语言模型中最基础的一步而Tokenizer的作用就是完成文本到数字的转换是大语言模型最基础的组件。
1.2 什么是tokenizer
Tokenizer是一个词元生成器它首先通过分词算法将文本切分成独立的token列表再通过词表映射将每个token转换成语言模型可以处理的数字。 这里有一个网站可以在线演示tokenizer的切分见tokenizer在线演示
大多数常见的英语单词都分配一个token
而有的单词却分配不止一个token: 像congratulations就被切分成4个token. 不仅如此而字母大小写空格和标点符号对分词结果也有影响如下面示例
以上这些分词效果均与token的切分方式有关。
2. token切分方式
根据切分粒度的不同可以把tokenizer分为:
基于词的切分基于字的切分基于subword的切分
2.1 基于词的切分
将文本按照词语进行分割通过空格或者标点符号来把文本分成一个个单词这样分词之后的 token 数量就不会太多比如 It is a nice day - It, is, a, nice, day。缺点是
词表规模可能会过大一定会存在UNK造成信息丢失不能学习到词根、词缀之间的关系例如dog与dogshappy与unhappy UNK是unknown未知的缩写表示模型无法识别的单词或标记对于一些新词、生僻词、专有名词或拼写错误的词可能未被词典收录。 词表规模过大原因自然语言中存在大量的词汇而词汇与词汇之间的排列组合又能造出大量的复合词这会导致词表规模很大并且持续增长。 2.2 基于字的切分
将文本按照字符进行切分把文本拆分成一个个字符单独表示比如 highest - h, i, g, h, e, s, t。
优点 词表Vocab 不会太大Vocab 的大小为字符集的大小英文只有26个字母也不会遇到UNK问题 缺点 字符本身并没有传达太多的语义丧失了词的语义信息分词之后的 token序列过长例如highest 一个单词就可以得到 7 个 token如果是很长的文本分出来的token数量将难以想象这会造成语言模型的解码效率很低
2.3 基于subword的切分
从上可以看出基于词和基于字的切分方式是两个极端其优缺点也是互补的。而subword就是一种相对平衡的折中方案基本切分原则是
高频词依旧切分成完整的整词例如It [ It ]低频词被切分成有意义的子词例如 dogs [dog, s]
它的特点是
词表规模适中解码效率较高不存在UNK信息不丢失能学习到词缀之间的关系
因此基于subword的切分是目前的主流切分方式。
3. subword分词流程
分词的基本需求给定一个句子基于分词模型切分成一连串token。效果如下
input: Hello, how are u tday?
output: [Hello, ,, Ġhow, Ġare, Ġu, Ġt, day, ?]整个tokenize的过程可以用下面这个图来理解分为预分词、基于模型分词、编码三步。
3.1 预分词
预分词阶段会把句子切分成词单元可以基于空格或者标点进行切分。
以gpt2为例预切分结果如下每个单词变成了[word, (start_index, end_index)]
input: Hello, how are you?pre-tokenize:
[GPT2]: [(Hello, (0, 5)), (,, (5, 6)), (Ġhow, (6, 10)), (Ġare, (10, 14)), (Ġ, (14, 15)), (Ġyou, (15, 19)), (?, (19, 20))]在GPT2中空格会保留成特殊的字符“Ġ”。 不同的模型在切分时对于空格和标点的处理方式不同作为对比
BERT的tokenizer也是基于空格和标点进行切分但不会保留空格。
[BERT]: [(Hello, (0, 5)), (,, (5, 6)), (how, (7, 10)), (are, (11, 14)), (you, (16, 19)), (?, (19, 20))]LLama 的T5则只基于空格进行切分标点不会切分。并且空格会保留成特殊字符▁并且句子开头也会添加特殊字符▁。
[t5]: [(▁Hello,, (0, 6)), (▁how, (7, 10)), (▁are, (11, 14)), (▁you?, (16, 20))] 3.2 基于模型分词
上面预分词的结果基本就是一个单词一个token但这样的切分粒度是很粗的正如上面切分方式中介绍的问题容易造成词表规模过大。
而基于模型分词本质上就是对预分词后的每个单词再尝试进行切分也就是上面提到的subword方式目前主流大语言模型使用的是BPE算法。
BPE分词的过程可以简单理解为从短到长逐步查找词元的过程概括为以下三步。
对于输入序列中的每个单词拆分成一个个字符以Ġtday为例拆分结果如下。
(Ġ, t, d, a, y)在BPE算法中每个字母都是最基本的词元这样能避免UNK问题。 从输入的字符序列逐步查找是否有更长的词元可以代替如果找到就将较短的几个词元替换成这个更长的词元还是以Ġtday为例替换过程如下所示。
# 第一次替换Ġ和t-Ġt
(Ġt, d, a, y)
# 第二次替换a和y-ay
(Ġt, d, ay)
# 第三次替换d和ay-day
(Ġt, day)
# 结束这样Ġtday这个预分的词元就被拆分成了Ġt和day两个最终的词元这两个词元会替换掉先前的Ġtday。 为什么Ġt和day不能进一步合并替换呢 原因tday其实是today这个单词的网络用语这个网络简称在词汇表中并不存在所以无法合并最终tday这个单词就在分词阶段拆分成了t和day两个token。 那么具体哪些字符或子词能合并成更长的词元呢
这里依据的是分词模型中子词合并记录merges.txt这个文件是模型训练过程中生成的其中一段示例如下。
[[], ,\\u010a],[\\u0120H, e],[_, st],[f, ul],[o, le],[), {\\u010a],[\\u0120sh, ould],[op, y],[el, p],[i, er],[_, name],[ers, on],[I, ON],[ot, e],[\\u0120t, est],[\\u0120b, et],[rr, or],[ul, ar],[\\u00e3, \\u0122],[\\u0120, \\u00d0],[b, s],[t, ing],[\\u0120m, ake],[T, r],[\\u0120a, fter],[ar, get],[R, O],[olum, n],[r, c],[_, re],[def, ine],[\\u0120r, ight],[r, ight],[d, ay],[\\u0120l, ong],[[, ]],[(, p],[t, d],[con, d],[\\u0120P, ro],[\\u0120re, m],[ption, s],[v, id],[., g],[\\u0120, ext],[\\u0120, __],[\, )\\u010a],[p, ace],[m, p],[\\u0120m, in],[st, ance],[a, ir],[a, ction],[w, h],[t, ype],[ut, il],[a, it],[, ?],[I, C],[t, ext],[\\u0120p, h],[\\u0120f, l],[., M],[cc, ess],[b, r],[f, ore],[ers, ion],[), ,\\u010a],[., re],[ate, g],[\\u0120l, oc],[in, s],[-, s],[tr, ib],这个合并记录表与我们人类能理解的单词、词根、词缀有一定差别既有我们常见单词的合并记录 [def,ine], [r, ight], [d, ay],也有我们看不明白的 [\\u0120f, l],、 [cc, ess]这些合并记录不是人工编辑的而是模型训练阶段根据实际语料来生成的。
这种方式是有效的它既能保留常见的独立词汇例如how), 又能保证未知或罕见的词汇能被拆分为较小的词根或词缀例如tday-t和day),即使没有词根或词缀最后还能以单个字符例如?, u) 作为词元保证不会出现UNK。
这样通过词汇表就可以将预分词后的单词序列切分成最终的词元。
input: Hello, how are u tday?
Model: [Hello, ,, Ġhow, Ġare, Ġu, Ġt, day, ?]3.3 编码
编码本质上就是给每个token分配一个唯一的数字ID这个数字ID是分词模型训练好后就维护在词汇表中的。
每个分词模型内部都有一个vocab词汇表以chatgpt为例目前使用的词表为c100k_base, 它是一个index —— token的map映射index表示token对应的数字ID里面有大概10万个词元示例如下
{0: !,1: \,2: #,3: $,4: %,5: ,6: ,7: (,8: ),9: *,10: ,……1268: how,1269: rite,1270: \n,1271: To,1272: 40,1273: ww,1274: people,1275: index,……100250: .allowed,100251: (newUser,100252: merciless,100253: .WaitFor,100254: daycare,100255: Conveyor
}切分好token后就可以根据上面示例的词汇表将token序列转换为数字序列如下所示
input: [Hello, ,, Ġhow, Ġare, Ġu, Ġt, day, ?]
output: [9906, 11, 1268, 527, 577, 259, 1316, 5380]关于这个词表vocab以及合并记录merges.txt的由来与BPE算法的实现和训练过程有关后续再介绍。 4. 中文分词
4.1 长度疑问
我们在估算token的消耗时经常听到有同事说汉字要占两个token是这样吗我们来验证下
为何有的汉字一个token有的汉字两个token? 这和tiktoken对中文分词的实现方式有关。
4.2 实现剖析
举例‘山东淄博吃烧烤’ 对应词汇表中的词元
[山, 东, b\\xe6\\xb7, b\\x84, b\\xe5\\x8d, b\\x9a, b\\xe5\\x90, b\\x83, b\\xe7, b\\x83, b\\xa7, b\\xe7, b\\x83, b\\xa4]除了“山“、”东”这两个相对比较简单的汉字词表里面直接就有其他的都是一些非常奇怪的Unicode编码表示。 仔细观察可以发现tokens[85315, 226] 对应的b’\xe6\xb7’, “b’\x84’” 拼接起来然后按照utf-8解码回去 b’\xe6\xb7\x84’.decode(‘utf-8’) 得到的就是“淄”。 原来OpenAI为了支持多种语言的Tokenizer采用了文本的一种通用表示UTF-8的编码方式这是一种针对Unicode的可变长度字符编码方式它将一个Unicode字符编码为1到4个字节的序列。
山和东因为比较常见所以被编码为了独立的词元而淄、博等字词频较低所以按照Unicode编码预处理成了独立的3个字节然后子词的迭代 合并最终分成了两个词元。 \x 表示16进制编码可以发现淄博分别被编码为6个16进制数字分别占3个字节。随后GPT-4将每2个16进制数字也就是1字节的数据作为最小颗粒度的token然后进行BPE的迭代、合并词表。 5. tiktoken
tiktoken是OpenAI开源一种分词工具 采用BPE算法实现被GPT系列大模型广泛使用。
基于某个模型来初始化tiktoken不同模型的tiktoken词表不同
import tiktoken
enc tiktoken.encoding_for_model(gpt-3.5-turbo-16k)字节对编码
encoding_res enc.encode(Hello, how are u tday?)
print(encoding_res) [9906, 11, 1268, 527, 577, 259, 1316, 30]字节对解码
raw_text enc.decode(encoding_res)
print(raw_text) Hello, how are u tday?如果想要控制token数量则可以通过len函数来判断
length len(enc.encode(Hello, how are u tday?))
print(length) 8参考资料
gpt在线分词演示探索GPT Tokenizer的工作原理