模型内部 · 分词器
- 基础符号
- 256 个字节
- 合并规则
- 50,000
- 词表
- 50,257 256 + 50,000 + <|endoftext|>
- 这段文本
- 18 个字节 → 5 个词元
全部步骤
预分词
为什么用子词:按单个字符切,序列会非常长;按整词切,词表会非常大,而且仍然会遗漏新词;BPE 介于两者之间。首先,一个正则表达式把文本切成单词、数字和标点,每一段都保留它前面的空格。在下方输入你自己的文本试试。
5 个片段字节
每个片段变成它的 UTF-8 字节。GPT-2 为每个字节值指定一个看得见的替身字符,所以空格写作 Ġ。
18 个字节BPE 合并
在每个片段内部,查出每一对相邻符号的序号,把序号最小的一对融合。如此重复,直到没有任何一对在表中;罕见的词最后会变成好几个词元。
13 次合并生效词表 id
最终的每个符号都是词表中的一项。合并得到的词元,其 id 等于 256 加上它的合并序号。id 0–255 是 256 个字节符号,按 GPT-2 自己的顺序排列,而不是按字节值(“.” 是 13,Ġ 是 220),50256 是 <|endoftext|>。
ids [464, 3797, 3332, 319, 262]解码
解码是反过来的:每个 id 查出它的字节串,把字节拼起来,按 UTF-8 读取。一个词元可能在一个字符的中间结束(试试 “café 你好”),所以流式输出会先扣住这些字节,直到字符完整。
id → 字节 → 文本
代码
import regex as re # pip install regex (\p{L} needs it)
pat = re.compile(r"""'s|'t|'re|'ve|'m|'ll|'d| ?\p{L}+| ?\p{N}+| ?[^\s\p{L}\p{N}]+|\s+(?!\S)|\s+""")
def encode(text):
ids = []
for piece in pat.findall(text):
word = [byte_char[b] for b in piece.encode("utf-8")] # " " → "Ġ"
while len(word) > 1:
pairs = list(zip(word, word[1:]))
best = min(pairs, key=lambda p: ranks.get(p, float("inf")))
if best not in ranks: break # no rule left
word = merge(word, best) # every a, b → ab
ids += [vocab[s] for s in word] # 256 + merge rank
return ids
def decode(ids):
data = b"".join(token_bytes[i] for i in ids) # a lookup and a join
return data.decode("utf-8", errors="replace")