minGPT bpe.py
示例运行
bpe.py 直接可以运行,提供了一个示例:
原始输入文本为:Hello!! I'm Andrej Karpathy. It's 2022. w00t :D 🤗
首先文本会经过预处理(分词),得到如下结果:
['Hello', '!!', ' I', "'m", ' Andrej', ' Karpathy', '.', ' It', "'s", ' 2022', '.', ' w', '00', 't', ' :', 'D', ' �'']
之后会针对每个单词进行编码:
token | token_bytes | token_translated | token_merged | token_ix |
---|---|---|---|---|
'Hello' | b'Hello' | 'Hello' | ['Hello'] | [15496] |
'!!' | b'!!' | '!!' | ['!!'] | [3228] |
' I' | b' I' | 'ĠI' | ['ĠI'] | [314] |
"'m" | b"'m" | "'m" | ["'m"] | [1101] |
' Andrej' | b' Andrej' | 'ĠAndrej' | ['ĠAndre', 'j'] | [10948, 73] |
'.' | b'.' | '.' | ['.'] | [13] |
' It' | b' It' | 'ĠIt' | ['ĠIt'] | [632] |
"'s" | b"'s" | "'s" | ["'s"] | [338] |
' 2022' | b' 2022' | 'Ġ2022' | ['Ġ2022'] | [33160] |
'.' | b'.' | '.' | ['.'] | [13] |
' w' | b' w' | 'Ġw' | ['Ġw'] | [266] |
'00' | b'00' | '00' | ['00'] | [405] |
't' | b't' | 't' | ['t'] | [83] |
' :' | b' :' | 'Ġ:' | ['Ġ:'] | [1058] |
'D' | b'D' | 'D' | ['D'] | [35] |
' 🤗' | b' \xf0\x9f\xa4\x97' | 'ĠðŁ¤Ĺ' | ['ĠðŁ', '¤', 'Ĺ'] | [12520, 97, 245] |
最后源文本编码后的输出为:
[15496, 3228, 314, 1101, 10948, 73, 509, 5117, 10036, 13, 632, 338, 33160, 13, 266, 405, 83, 1058, 35, 12520, 97, 245]
这个结果可以传入 Transformer 了!
get_encoder
该函数用于初始化编码器。
首先需要下载 GPT-2 的 encoder.json。encoder.json 的作用是什么呢?
encoder.json 是一个包含原始映射关系的 JSON 文件,它将 token 映射到 BPE(Byte Pair Encoding)索引。在这个文件中,共有 50257 个映射关系,包括 256 个单字节 token,50,000 个合并后的 token,以及一个特殊的 <|endoftext|>
。
接下来下载 GPT-2 的 vocab.bpe。vocab.bpe 的作用是什么呢?
vocab.bpe 是一个包含 BPE(Byte Pair Encoding)合并操作的文件。在这个文件中,存储了一个 BPE 树结构,用于表示如何将一对字符(a, b)合并为一个新的字符 ab。在这个代码片段中,vocab.bpe 文件被下载并读取,然后将其中的合并操作转换为一个包含元组的列表 bpe_merges。
BPE 的作用是将文本分割成更小的子单元,这样可以在保留语义信息的同时减少词汇表的大小。在 NLP 任务中,这有助于提高模型的性能和泛化能力。
最终用这两份数据创建编码器:
enc = Encoder(encoder, bpe_merges)
这两个文件:
- 一个是 token(单词+子词)到序号的映射,1018KB
- 一个是 bigram 是否可以合并的规则,446KB
Encoder
encode_and_show_work
对传入字符串进行编码,并保存编码后的结果返回,结果用于展示目的。
首先,初始化数据结构:
# 初始化bpe_idx列表
bpe_idx = []
# 初始化parts列表
parts = []
使用正则从文本中提取出所有单词:
# 使用正则表达式从文本中提取所有单词
tokens = re.findall(self.pat, text)
对单词进行遍历:
for token in tokens:
对每个单词执行操作:
# 将单词编码为字节
token_bytes = token.encode('utf-8')
# 将所有字节转换为它们的Unicode字符串表示形式并拼接
# 其中byte_encoder保存了一个Unicode映射表
token_translated = ''.join(self.byte_encoder[b] for b in token_bytes)
# 执行所有适用的BPE合并,根据self.bpe_ranks
token_merged = self.bpe(token_translated).split(' ')
# 将所有BPE令牌转换为整数
token_ix = [self.encoder[bpe_token] for bpe_token in token_merged]
# 将所有整数扩展到bpe_idx列表中
bpe_idx.extend(token_ix)
token_translated 的具体编码过程是:
self.byte_encoder = bytes_to_unicode()
bytes_to_unicode
将 byte 值映射成漂亮的 unicode 字符。- byte 的取值范围是 0~255
- 有 188 个取值可直接展示(bs 变量部分)
- 有 68 个需要添加位移(256),映射到一个更好看的 unicode 字符
- 最终返回一个字典
- key 是 byte 值 0~255
- value 是对应的好看的 unicode 字符
- 所以 token_translated 是,对 text 按字节通过
bytes_to_unicode
映射到好看的 unicode 字符
token_merged 的具体过程是:
- bpe 方法详见笔记的 bpe 小节。
bpe
bpe 流程调试如下。
以 token 为 Hello 为例:
将 token 打散得到 word=('H', 'e', 'l', 'l', 'o')。
根据 word 建立 pairs=
进入循环迭代过程:
- 找到可以合并的下一个最低等级的 bigram=('l', 'l')
- 解包 first, l
- 解包 second, l
- 开始循环对 word 进行处理, word 长度 5
- 当前 i 值 i=0
- 找到当前单词序列中下一个first的出现位置 j=2
- 扩展后的
new_word=['H', 'e']
- 令 i = j = 2
- 如果该出现位置后面也跟着second,则将它们合并为一个令牌
- 引入 first+second 后的
['H', 'e', 'll']
- 然后将索引 i 增加 2,以跳过已合并的字符对, i=4
- 当前 i 值 i=4
- word 没找到 first,将剩余的单词添加到new_word中并跳出循环
- 扩展后的
new_word=['H', 'e', 'll', 'o']
- 当前 i 值 i=0
- 所有(first, second)的出现位置都已经合并为first_second
- 此时的 new_word=('H', 'e', 'll', 'o')
- word = ('H', 'e', 'll', 'o')
- 继续获取word中的 bigrams(字符对),并重复上述过程
- 新的 pairs=
- 找到可以合并的下一个最低等级的 bigram=('e', 'll')
- 解包 first, e
- 解包 second, ll
- 开始循环对 word 进行处理, word 长度 4
- 当前 i 值 i=0
- 找到当前单词序列中下一个first的出现位置 j=1
- 扩展后的
new_word=['H']
- 令 i = j = 1
- 如果该出现位置后面也跟着second,则将它们合并为一个令牌
- 引入 first+second 后的
['H', 'ell']
- 然后将索引 i 增加 2,以跳过已合并的字符对, i=3
- 当前 i 值 i=3
- word 没找到 first,将剩余的单词添加到new_word中并跳出循环
- 扩展后的
new_word=['H', 'ell', 'o']
- 当前 i 值 i=0
- 所有(first, second)的出现位置都已经合并为first_second
- 此时的 new_word=('H', 'ell', 'o')
- word = ('H', 'ell', 'o')
- 继续获取word中的 bigrams(字符对),并重复上述过程
- 新的
- 找到可以合并的下一个最低等级的 bigram=('ell', 'o')
- 解包 first, ell
- 解包 second, o
- 开始循环对 word 进行处理, word 长度 3
- ……
- 所有(first, second)的出现位置都已经合并为first_second
- 此时的 new_word=('H', 'ello')
- word = ('H', 'ello')
- 继续获取word中的 bigrams(字符对),并重复上述过程
- 所有(first, second)的出现位置都已经合并为first_second
- 此时的 new_word=('Hello',)
- word = ('Hello',)
- word的长度为 1,说明已经完成了合并过程,跳出循环。
- 跳出循环
- 合并后的 Hello
- 将 Hello 加入缓存
本文作者:Maeiee
本文链接:minGPT bpe.py
版权声明:如无特别声明,本文即为原创文章,版权归 Maeiee 所有,未经允许不得转载!
喜欢我文章的朋友请随缘打赏,鼓励我创作更多更好的作品!