基于字节对编码(Byte Pair Encoding,BPE)概念的分词方案较之前的方案更复杂。BPE 分词器曾被用于训练像 GPT-2、GPT-3 这样的大语言模型,以及 ChatGPT 中使用的原始模型。
由于实现 BPE 可能相对复杂,我们将使用一个现有的 Python 开源库,名为 tiktoken(https://github.com/openai/tiktoken),它基于 Rust 源代码非常高效地实现了 BPE 算法。与其他 Python 库类似,使用 Python 的 pip 安装 tiktoken 库:
pip install tiktoken
以下代码基于 tiktoken 0.7.0:
from importlib.metadata import version
import tiktoken
print("tiktoken version:", version("tiktoken"))
安装完成后,按照如下方式创建一个 tiktoken 中的 BPE 分词器实例:
tokenizer = tiktoken.get_encoding("gpt2")
该分词器的用法类似于我们之前通过 encode
方法实现的 SimpleTokenizerV2
:
text = (
"Hello, do you like tea? <|endoftext|> In the sunlit terraces"
"of someunknownPlace."
)
integers = tokenizer.encode(text, allowed_special={"<|endoftext|>"})
print(integers)
代码会打印以下 token ID:
[15496, 11, 466, 345, 588, 8887, 30, 220, 50256, 554, 262, 4252, 18250, 8812, 2114, 286, 617, 34680, 27271, 13]
然后,可以使用 decode
方法将这些 token ID 转换回文本,类似于前一篇中的 SimpleTokenizerV2
:
strings = tokenizer.decode(integers)
print(strings)
打印输出:
Hello, do you like tea? <|endoftext|> In the sunlit terraces of someunknownPlace.
从以上的 token ID 和对应的文本,可以观察到:
<|endoftext|>
的 token 是一个值相对较大的 token ID,即 50256。实际上,用于训练 GPT-2、GPT-3 以及 ChatGPT 中使用的原始模型的 BPE 分词器的词汇量是 50,257 ,其中<|endoftext|>
的 token ID 是最大的数。- BPE 分词器能够正确地编码和解码未知单词,例如“someunknownPlace”。BPE 分词器可以处理任何未知单词,而无需使用
<|unk|>
的 token 替换。这是如何实现的呢?
BPE 算法的基础在于将不在其预定义词汇表中的词分解为更小的子词单元甚至是单个字符,这使得它能够处理这类单词。如图 2.11 所示。这种方式允许模型灵活且有效地应对未曾见过的词汇,提高了模型处理语言的多样性和复杂性的能力。
图 2.11 BPE 分词器将未知单词分解为子词和单个字符。通过这种方式,BPE 分词器可以解析任何单词,并且不需要用特殊标记(如 <|unk|>
)替换未知单词。
以下是用代码实现图 2.11 所示的过程。
import tiktoken
# 加载 GPT-2 的 BPE 分词器
tokenizer = tiktoken.get_encoding("gpt2")
# 输入未知单词
text = "Akwirw ier"
# 使用 encode 方法生成 token IDs
integers = tokenizer.encode(text)
print("Token IDs:", integers)
# 对每个 token ID 调用 decode 方法
decoded_tokens = [tokenizer.decode([token_id]) for token_id in integers]
print("Decoded tokens:", decoded_tokens)
# 检查是否可以重建原始输入
reconstructed_text = tokenizer.decode(integers)
print("Reconstructed text:", reconstructed_text)
运行上述代码,可能会得到以下输出(具体结果取决于分词器的实现):
Token IDs [33901, 86, 343, 86, 220, 959]
Decoded tokens: ['Ak', 'w', 'ir', 'w', ' ', 'ier']
Reconstructed text: Akwirw ier
从输出中可以看到:
- 原始文本 “Akwirw ier” 被分解为多个子词或字符。
- 通过
decode
方法,我们可以将这些子词或字符重新组合成原始输入。
将未知单词分解为单个字符的能力确保了分词器以及使用它训练的大型语言模型(LLM)能够处理任何文本,即使这些文本包含训练数据中未出现过的单词。
关于 BPE 的详细讨论和实现超出了本文的范围,但简而言之,BPE 通过迭代地将频繁出现的字符合并为子词,再将频繁出现的子词合并为完整单词来构建其词汇表。例如,BPE 首先将其词汇表初始化为所有单个字符(如 “a,” “b” 等)。在下一阶段,它将经常一起出现的字符组合合并为子词。例如,“d” 和 “e” 可能会被合并为子词 “de”,这在许多英语单词中都很常见,比如 “define”、“depend”、“made” 和 “hidden”。这些合并是由频率阈值决定的。
原文:Sebastian Raschka. Build a Large Language Model(From Scratch),此处为原文的中文翻译,为了阅读方便,有适当修改。