彻底理解GPT tokenizers
你可能已经听说过GPT这个词,它是一种人工智能模型,可以生成各种各样的文本,比如小说、诗歌、对话、新闻等等。GPT的全称是Generative Pre-trained Transformer,意思是生成式预训练变换器。生成式表示它可以根据一些输入(比如一个单词或一句话)来创造新的内容,预训练表示它在使用之前已经在大量的文本数据上进行了学习,变换器表示它使用了一种叫做Transformer的神经网络结构。
要理解GPT的工作原理,我们需要先了解一个重要的概念:token。token是文本的最小单位,可以是一个字母、一个单词、一个标点符号或者一个符号。比如,这句话:
Hello, world!
可以被分成五个token:
Hello , world !
GPT模型在处理文本时,需要先把文本分割成token,然后把每个token转换成一个数字,这个数字就代表了这个token的含义。这个数字叫做token ID。比如,我们可以用下面的表格来表示每个token和它对应的token ID:
token | token ID |
Hello | 1 |
, | 2 |
world | 3 |
! | 4 |
那么,这句话就可以被转换成一个数字序列:
1 2 3 4
GPT模型就是通过学习大量的这样的数字序列,来掌握文本的规律和语义。
然后,当我们给它一个输入(比如一个token ID或者一个数字序列),它就可以根据它学到的知识,来生成一个合理的输出(比如一个新的token ID或者一个新的数字序列)。
但是,如果我们只用单个字母或单词作为token,会有一些问题。首先,不同的语言有不同的词汇量,有些语言可能有几万个单词,有些语言可能有几十万甚至几百万个单词。如果我们要给每个单词分配一个唯一的token ID,那么我们需要很大的内存空间来存储这些ID。其次,有些单词可能很少出现在文本中,或者有些单词可能是新造出来的,比如一些专有名词、缩写、网络用语等等。如果我们要让GPT模型能够处理这些单词,那么我们需要不断地更新我们的token ID表格,并且重新训练模型。
为了解决这些问题,GPT模型使用了一种叫做BPE(Byte Pair Encoding)的方法来分割文本。BPE是一种数据压缩技术,它可以把一段文本分割成更小的子单元(subword),这些子单元可以是单个字母、字母组合、部分单词或完整单词。
BPE的原理是基于统计频率来合并最常见的字母对或子单元对。比如,如果我们有下面这四个单词:
lowlowernewestwidest
我们可以先把它们分割成单个字母:
l o wl o w e rn e w e s tw i d e s t
然后,我们可以统计每个字母对出现的次数,比如:
pair | count |
l o | 2 |
o w | 2 |
w e | 2 |
e r | 1 |
n e | 1 |
e w | 1 |
w i | 1 |
i d | 1 |
d e | 1 |
e s | 1 |
s t | 1 |
我们可以看到,l o,o w和w e都出现了两次,是最常见的字母对。我们可以把它们合并成一个新的子单元,比如:
lowlow ern e westw i dest
这样,我们就减少了一些token的数量。我们可以重复这个过程,直到达到我们想要的token的数量或者没有更多的可合并的字母对。比如,我们可以继续合并e r,n e,e w等等,得到:
lowlowernewestwidest
这样,我们就把四个单词分割成了六个子单元:
lowernewestwidest
这些子单元就是BPE的token。我们可以给它们分配token ID,比如:
token | token ID |
low | 5 |
er | 6 |
new | 7 |
est | 8 |
wid | 9 |
那么,这四个单词就可以被转换成下面的数字序列:
55 67 89 8
你可能会问,为什么要用BPE来分割文本呢?有什么好处呢?其实,BPE有以下几个优点:
- 它可以减少token的数量,从而节省内存空间和计算资源。
- 它可以处理未知或罕见的单词,只要把它们分割成已知的子单元就行了。比如,如果我们遇到一个新单词lowerest,我们可以把它分割成low er est,然后用对应的token ID表示它。
- 它可以捕捉单词的形态变化,比如复数、时态、派生等等。比如,如果我们遇到一个单词lowering,我们可以把它分割成low er ing,然后用对应的token ID表示它。这样,GPT模型就可以学习到这个单词和其他形式的关系。
当然,BPE也有一些缺点,比如:
- 它可能会破坏一些有意义的子单元,比如把一个完整的单词分割成两个或多个部分。比如,如果我们遇到一个单词tower,我们可能会把它分割成t ow er,而不是保留它作为一个整体。
- 它可能会导致一些歧义或混淆,比如把两个不同的单词分割成相同的子单元序列。比如,如果我们遇到两个单词tow er和tower,我们可能会把它们都分割成t ow er,而不是区分它们。
- 它可能会影响一些特殊的符号或标记的处理,比如HTML标签、URL、邮箱地址等等。比如,如果我们遇到一个URLhttps://www.bing.com/, 我们可能会把它分割成多个子单元,比如:
https : / / www . bing . com /
这样,可能会丢失一些原本的含义或格式。
所以,BPE并不是一种完美的方法,它只是一种权衡的方法,它在减少token数量和保留token含义之间寻找一个平衡点。不同的BPE方法可能会有不同的分割规则和结果,比如,我们可以设置一个最大的token数量,或者一个最小的合并频率,来影响BPE的过程和输出。
那么,GPT模型是如何使用BPE来分割文本的呢?实际上,GPT模型并不是直接使用BPE来分割文本,而是使用了一种叫做GPT-2 tokenizer的工具,这个工具是基于BPE的一种改进版本。GPT-2 tokenizer有以下几个特点:
- 它使用了Unicode编码来表示每个字符,而不是ASCII编码。这样,它可以支持更多的语言和符号,比如中文、日文、阿拉伯文、表情符号等等。
- 它使用了一个固定的token数量,即50257个。这个数字是根据GPT-2模型的输入层的大小来确定的,每个输入层可以容纳50257个不同的token ID。
- 它使用了一个预先训练好的BPE模型来分割文本,这个BPE模型是在一个大规模的文本数据集上训练得到的,它包含了各种各样的文本类型和语言。
上手实践
如果你想使用GPT-2 tokenizer来分割文本,你可以参考以下的步骤:
- 首先,你需要安装和导入transformers库,这是一个提供了各种预训练模型和工具的开源库¹²。
- 然后,你需要从预训练的gpt2模型中加载tokenizer和model,你可以使用AutoTokenizer和GPT2DoubleHeadsModel类来实现这一功能¹²。
- 接着,你需要给tokenizer添加一些特殊的token,比如[CLS]和[SEP],这些token可以帮助模型识别文本的开始和结束¹²。
- 最后,你可以使用tokenizer的encode或encode_plus方法来把文本转换成token ID的序列,并且使用model的forward方法来得到模型的输出¹²³。
下面是一个简单的Python代码示例:
# 导入transformers库
from transformers import AutoTokenizer, GPT2DoubleHeadsModel
import torch
# 加载tokenizer和model
tokenizer = AutoTokenizer.from_pretrained("gpt2")
model = GPT2DoubleHeadsModel.from_pretrained("gpt2")
# 添加特殊的token
num_added_tokens = tokenizer.add_special_tokens({"cls_token": "[CLS]", "sep_token": "[SEP]"})
# 分割文本
text = "Hello, my dog is cute"
inputs = tokenizer.encode_plus(text, add_special_tokens=True, return_tensors="pt")
# 得到模型的输出
outputs = model(**inputs)
last_hidden_states = outputs.last_hidden_state
一旦您了解了令牌,GPT 工具生成文本的方式就会变得更加有意义。
特别是,观看 GPT-4 将其输出作为独立令牌流式传输回很有趣(GPT-4 比 3.5 略慢,因此更容易看到发生了什么)。
这是我得到的 - 使用我的 llm CLI 工具从 GPT-4 生成文本:llm -s 'Five names for a pet pelican' -4。
字典中不存在的“Pelly” 占用了多个token,而字典中存在的“Captain Gulliver”则能一次性输出。
本文转载自 AI小智,作者: AI小智