当前位置:   article > 正文

从头开始构建GPT标记器_gtp谱refrain怎么标记

gtp谱refrain怎么标记

从头开始构建GPT标记器

对于GPT Tokenizer,论文《Language Models are Unsupervised Multitask Learners》中介绍了一种字节级编码作为LLM的标记化机制:

The vocabulary is expanded to 50,257. We also increase the context size from 512 to 1024 tokens and a larger batchsize of 512 is used.

最终结论是LLM的词汇扩展到50257,上下文可以看到1024个tokens,也就是在transformer的注意力层中,每个token都可以关注序列前最多1024个token。

标记化是将字符串或文本转换为标记序列的过程。字节对编码算法不算很复杂,下面我们可以从头开始构建它。

在进行构建之前,我们先简要了解一下tokenization的复杂性,许多看起来只是神经网络架构或大型语言模型本身的问题,实际上都是标记化的问题,可以从源头追溯到问题所在,当LLM出现以下问题时,通常是由分词引起的:

  • 为什么大型语言模型不能拼写单词?
  • 为什么大型语言模型在执行简单的字符串处理任务(例如反转字符串)时表现不佳?
  • 为什么大型语言模型在处理非英语语言(例如日语)时表现较差?
  • 为什么大型语言模型在简单算术上表现不好?
  • 为什么 GPT-2 在 Python 编码时遇到不必要的麻烦?
  • 为什么我的大型语言模型在遇到字符串 “<|endoftext|>” 时突然停止?
  • 为什么我在使用大型语言模型时会收到关于“尾随空白”的奇怪警告?
  • 为什么当我问大型语言模型关于“SolidGoldMagikarp”时它会崩溃?
  • 为什么我应该在使用大型语言模型时优先选择 YAML 而不是 JSON?
  • 为什么大型语言模型不是真正的端到端语言建模?
  • LLM问题的根源是什么?

所以分词是很多问题的根源,我们将在文章末尾再回顾这些问题,现在我们先跳过它,进入下面这个网络应用(https://tiktokenizer.vercel.app/)

在这个网站中分词结果会用JavaScript很直观地展现出来,在左边框中随意输入一些内容(注意右上角我们选择的是GPT-2):

Tokenization is at the heart of much weirdness of LLMs. Do not brush it off.

127 + 677 = 804
1275 + 6773 = 8041

Egg.
I have an Egg.
egg.
EGG.

很高兴见到你。我是OpenAI开发的大规模语言模型ChatGPT。如果有任何疑问,请随时问我。

for i in range(1, 101):
    if i % 3 == 0 and i % 5 == 0:
    	print("FizzBuzz")
    elif i % 3 == 0:
    	print("Fizz")
    elif i % 5 == 0:
    	print("Buzz")
    else:
    	print(i)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

这些标记被不同的颜色区分开,例如第一个词Tokenization被标记成了30642,和1634

在这里插入图片描述

注意,每个token前面的空格也是token的一部分,GPT-2对英语句子的分词似乎没什么问题,但我们看下面的数学运算,677实际上应该划分为一个token但却分成了两个,其他数字也有这种情况。

在这里插入图片描述

同样,字符串Egg在句子开头时被划分成了两个标记,但前面有空格时又可以准确划分成一个标记,大写变小写也可以划分成一个标记…以上这些情况都有可能产生不同的划分

在这里插入图片描述

语言模型必须从将要训练的所有互联网文本的原始数据中学习,它必须在神经网络的参数中对它们进行分组,并且仅仅根据数据模式来理解,这些都是非常相似的。

接着我们还测试了中文的分词结果,分词器将这个句子分成了很多的Token,这比同样的英文句子会多很多,这就意味着我们对完全相同的句子在处理中文时需要使用更多的Token,这样的话就增加了文本的序列长度因此,然后在transfomer的注意力中。当这些标记尝试捕捉信息时,很容易耗尽最大上下文长度而无法捕捉更多有效信息。

在这里插入图片描述

基本上所有的非英语文本在transformer的角度看序列都变长了,这也就是LLM在非英语问题上表现得不如英语好的原因,这与用于分词器和分词本身的训练有关。

最后的例子是一个执行FizzBuzz的Python代码片段,在这里,所有的单独的空格都有单独的标记,这无疑也增加了token序列的长度

在这里插入图片描述

而对于GPT-4,它的分词情况就好很多,对Python中空白字符的处理有了很大改进

在这里插入图片描述

所以从CPT-2到GPT-4的Python编码能力的提高不仅仅是语言模型、架构和优化细节的问题,而且也来源于分词器的设计以及它如何将字符组合成标记。

下面我们来构建分词器,记住我们的目的是什么,我们想要将字符串输入到语言模型中,所以我们需要以某种方式将字符串标记为机器能够看懂的整数,然后,,我们将使用这些整数来查找向量查找表,并将这些向量作为输入送到转换器中。这其中的难点是,我们不只是想支持的英文字母,而是适应不同种类的语言。

例如对于下面这个句子:

你好!
  • 1

在Python中,这些字符串是不可变的Unicode(统一码)序列,我们可以通过在Python中使用ORD函数来访问给定单个字符的Unicode

例如传入单个字符"h"所得到的Unicode代码点是104

Input:ord("h")
Output:104
  • 1
  • 2
'
运行

那么句子"你好!"的统一码为:

[ord(x) for x in "你好!"]
#output: [20320, 22909, 65281]
  • 1
  • 2
'
运行

那么为什么我们不能简单地使用这些整数而不需要任何标记化呢?主要原因有几点:

  • 词汇表会非常长
  • Unicode标准会不断变化,很难表示成稳定的统一形式

因此Unicode定义了三种类型的编码,UTF-8、UTF-6和UTF-32

我们将"你好!"编码成UTF-8

list("你好!".encode("utf-8"))

#output: [228, 189, 160, 229, 165, 189, 239, 188, 129]
  • 1
  • 2
  • 3
'
运行

然而,如果我们只是简单地使用UTF-8这些字节流,这就意味着词汇长度只有256个可能的标记,那么所有的文本都会被拉伸成很长的字节序列,尽管嵌入表会很小,因此我们必须使用字节对编码算法()进行压缩。

下面是一个字节对编码算法的例子:

假设我们要编码如下数据

aaabdaaabac
  • 1

字节对“aa”出现次数最多,所以我们用数据中没有出现的字节“Z”替换“aa”得到替换表

Z <- aa
  • 1

数据转变为

ZabdZabac
  • 1

在这个数据中,字节对“Za”出现的次数最多,我们用另外一个字节“Y”来替换它(这种情况下由于所有的“Z”都将被替换,所以也可以用“Z”来替换“Za”),得到替换表以及数据

Z <- aa
Y <- Za
YbdYbac
  • 1
  • 2
  • 3

我们再次替换最常出现的字节对得到:

Z <- aa
Y <- Za
X <- Yb
XdXac
  • 1
  • 2
  • 3
  • 4

由于不再有重复出现的字节对,所以这个数据不能再被进一步压缩。

经过压缩,原来的token长度11变成了5,词汇长度由5变成了7

通过这种方式,我们可以迭代地压缩我们的序列,同时创造新的token。

以相同的方式,对于文本UTF-8编码的字节序列,我们可以找出最常出现的字节对,用新的token替换它,通过这种方式我们将得到一个压缩的训练数据集,以及一个用于将任意序列编码的算法,并使用这个词汇表进行解码,将其解码回字符串。

如下例子所示,文本从一篇博客中截取的

# text from https://www.reedbeta.com/blog/programmers-intro-to-unicode/
text = "Unicode! 声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop】
推荐阅读
相关标签