文章目录
前言
学习大模型的朋友肯定听说过大模型接口按token,自己编写代码的时候也经常看到token这个词,那它究竟是什么呢,我们一起来探究一下
一、token是什么
在大模型中,“token”通常指代文本中的最小单位,可以是一个单词、一个字符或其他子字符串。对于英语文本,通常以空格分隔的单词作为token。
下面是一个伪代码示例,描述了如何生成和计数token:
# 假设有一个字符串
text = "Hello, how are you?"
# 初始化一个空的token计数器
token_count = 0
# 将文本按空格分割成单词列表
words = text.split(" ")
# 遍历每个单词
for word in words:
# 去除标点符号等非字母字符
word = word.strip(",.?!")
# 如果单词不为空,则增加token计数器
if word:
token_count += 1
# 可以在这里对每个单词进行其他处理,例如进行词性标注等等
# 输出token计数结果
print("Token count:", token_count)
在上述伪代码中,文本被分割成单词列表,然后每个单词经过处理后计入token计数器。可以根据需要对每个token进行额外的处理或标注。最终输出token的数量。
在大模型(如GPT、BERT等)中,token
是一个更广泛的概念,不仅限于一个词。在这些模型中,token
可以是一个单词、一部分单词或一个标点符号。具体的划分方式依赖于模型使用的分词(tokenization)算法。
二、常用分词方法
-
空格分词(Whitespace Tokenization):
- 最简单的方法,每个单词和标点符号之间的空格作为分隔符。
- 例子:“Hello, world!” -> [“Hello,”, “world!”]
-
词汇分词(Word Tokenization):
- 基于词汇表进行分词,每个词在词汇表中都有对应的token。
- 例子:“Hello, world!” -> [“Hello”, “,”, “world”, “!”]
-
子词分词(Subword Tokenization):
- 将单词分解为更小的子词或字符序列,常用的方法包括BPE(Byte Pair Encoding)和WordPiece。
- 例子(BPE):“Hello, world!” -> [“He”, “##llo”, “,”, “world”, “!”]
- 例子(WordPiece):“unhappiness” -> [“un”, “##happiness”]
-
字符分词(Character Tokenization):
- 将每个字符作为一个token。
- 例子:“Hello, world!” -> [“H”, “e”, “l”, “l”, “o”, “,”, " ", “w”, “o”, “r”, “l”, “d”, “!”]
三、GPT-3的分词方式
1. 代码示例
GPT-3 使用一种基于 BPE 的分词算法,能够将文本拆分为子词单位。这样可以更有效地处理常见词和罕见词,并在处理未知词时表现得更好。
from transformers import GPT2Tokenizer
# 使用 GPT-2 的分词器(GPT-3 使用类似的分词器)
tokenizer = GPT2Tokenizer.from_pretrained('gpt2')
# 输入文本
text = "Hello, world!"
# 分词
tokens = tokenizer.tokenize(text)
print(tokens)
输出示例:
['Hello', ',', 'Ġworld', '!']
在这个例子中,'Hello'
和 ','
是单独的token,而 Ġworld
表示前面有一个空格的 world
。
2. Ġworld
和 world
的区别
大家肯定有疑问,上面的分词里有一个特别表述前缀是空格的用法Ġworld
。
Ġworld
和 world
在后续计算中是有区别的。在使用子词分词方法(如 BPE 或 WordPiece)时,分词器会将带有前缀的子词(如 Ġworld
)和不带前缀的子词(如 world
)视为不同的token。
1) 分词中的空格前缀
在 GPT-2 和 GPT-3 等模型中,子词分词器会用特殊的前缀(例如 Ġ
)来表示子词的边界或空格。具体来说:
Ġworld
表示前面有一个空格的world
。world
表示没有空格的world
。
示例:
from transformers import GPT2Tokenizer
# 使用 GPT-2 的分词器
tokenizer = GPT2Tokenizer.from_pretrained('gpt2')
# 分词示例
text1 = "Hello world"
text2 = "Helloworld"
tokens1 = tokenizer.tokenize(text1)
tokens2 = tokenizer.tokenize(text2)
print(tokens1) # ['Hello', 'Ġworld']
print(tokens2) # ['Hello', 'world']
在这个例子中,'Hello world'
被分成了 ['Hello', 'Ġworld']
,而 'Helloworld'
被分成了 ['Hello', 'world']
。虽然看起来 world
和 Ġworld
都包含 world
,但在模型内部,它们被视为不同的token。
2) 后续计算中的区别
在后续计算中(如词嵌入、模型输入等),Ġworld
和 world
会有不同的嵌入表示。这种区分有几个重要的作用:
- 捕捉上下文信息:带有空格前缀的token可以帮助模型捕捉词语之间的边界和上下文信息。例如,
Ġworld
表示它前面有一个空格,通常表示是一个新词的开始。 - 处理连贯文本:当处理连续文本时,空格前缀可以帮助模型区分连续的词和单词内部的子词。例如,
Helloworld
(一个单词)和Hello world
(两个单词)在模型中会有不同的表示。 - 词汇一致性:在生成文本或进行预测时,模型可以根据上下文更准确地选择带有或不带有空格前缀的token,以保持生成文本的连贯性和可读性。
示例:计算嵌入
在模型内部,每个token都会被映射到一个高维向量空间中,称为词嵌入。以下是如何在 PyTorch 中查看这些词嵌入的示例:
import torch
from transformers import GPT2Tokenizer, GPT2Model
# 初始化 GPT-2 模型和分词器
tokenizer = GPT2Tokenizer.from_pretrained('gpt2')
model = GPT2Model.from_pretrained('gpt2')
# 示例文本
text1 = "Hello world"
text2 = "Helloworld"
# 分词
tokens1 = tokenizer(text1, return_tensors='pt')
tokens2 = tokenizer(text2, return_tensors='pt')
# 获取词嵌入
with torch.no_grad():
embeddings1 = model(**tokens1).last_hidden_state
embeddings2 = model(**tokens2).last_hidden_state
print("Embeddings for 'Hello world':", embeddings1)
print("Embeddings for 'Helloworld':", embeddings2)
在这个示例中,'Hello world'
和 'Helloworld'
的词嵌入会有所不同,因为它们的token序列不同。
3. 为什么使用子词分词
- 处理未登录词:词汇表中没有的新词或拼写错误可以通过组合已有的子词来表示。
- 减小词汇表大小:相比单词分词,子词分词可以大幅减小词汇表的大小,同时覆盖更多的词形变化。
- 提高模型效率:子词分词平衡了模型在处理常见词和罕见词时的效率和性能。
总结
在大模型中,token
不一定是一个完整的词。它可以是一个词、一部分词、字符甚至是标点符号。这取决于所使用的分词方法。子词分词方法(如 BPE 和 WordPiece)在现代 NLP 模型中非常常见,因为它们能够高效地处理各种语言现象,同时保持词汇表的紧凑性。
带有空格前缀(如 Ġworld
)和不带空格前缀(如 world
)的token在模型中会被视为不同的实体,并且会有不同的词嵌入表示。这种区分有助于模型捕捉词语边界和上下文信息,从而提高对连续文本的处理能力和生成文本的质量。