ChatGPT使用了另一种复杂的分词方案,叫做“字节对编码”(Byte Pair Encoding,简称BPE),最初这是一种数据压缩算法,让我们来看看它的过程。BPE在多次迭代中合并频繁使用的字符对。例如,给定字符串:
“low low low low low lower lower newest newest newest newest newest newest widest widest widest”
单词频率统计如下:
{ “low”: 5, “lower”: 2, “newest”: 6, “widest”: 3, }
现在,我们将单词拆分成字符,字符集将是初始词汇表: {l, o, w, e, r, n, s, t, i, d}
每个单词拆分成字符集合如下:
{ “l o w”: 5, “l o w e r”: 2, “n e w e s t”: 6, “w i d e s t”: 3, }
现在,我们来看每对相邻字符对。例如,“l o w”有两个相邻字符对“lo”和“ow”,由于“lo”出现在“l o w”和“l o w e r”中,前者的频率为5,后者的频率为2,那么字符对“lo”的频率为7。通过这种方式,我们得到以下统计:
{ “l o”: 7, “o w”: 7, “w e”: 8, “e r”: 2, “n e”: 6, “e w”: 6, “e s”: 9, “s t”: 9, “w i”: 3, “i d”: 3, “d e”: 3, }
现在我们可以看到最频繁的字符对是“e s”和“s t”,然后我们将“e s”合并为一个单位,命名为“es”,并将“es”添加到词汇表中: {l, o, w, e, r, n, s, t, i, d, es} 每个单词的字符集合变为:
{ “l o w”: 5, “l o w e r”: 2, “n e w es t”: 6, “w i d es t”: 3, }
此时最频繁的字符对是“es”和“t”,然后我们将它们合并为“est”,并将其添加到词汇表中: {l, o, w, e, r, n, s, t, i, d, es, est} 每个单词的字符集合变为:
{ “l o w”: 5, “l o w e r”: 2, “n e w est”: 6, “w i d est”: 3 }
这时最频繁的字符对是“l o”,我们将它们添加到词汇表中:
{l, o, w, e, r, n, s, t, i, d, es, est, lo}
单词集合为:
{ “lo w”: 5, “lo w e r”: 2, “n e w est”: 6, “w i d est”: 3 }
现在很容易看出,最频繁的字符对是“lo”和“w”,将它们合并并添加到词汇表中: {l, o, w, e, r, n, s, t, i, d, es, est, lo, low}
单词字符集合为: { “low”: 5, “low e r”: 2, “n e w est”: 6, “w i d est”: 3 }
通过这种方式,我们可以继续迭代,直到达到预设的次数或者词汇表达到预期的大小。接下来我们来看如何使用代码实现这个过程:
from collections import defaultdict
# 计算单词频率并将单词拆分成字符集合
def get_vocab(data):vocab = defaultdict(int)for word in data.split():vocab[' '.join(list(word))] += 1return vocabvocab = get_vocab("low low low low low lower lower newest newest newest newest newest newest widest widest widest")
print(vocab)
运行上面的代码,我们得到以下结果:
defaultdict(<class 'int'>, {'l o w': 5, 'l o w e r': 2, 'n e w e s t': 6, 'w i d e s t': 3})
接下来我们可以计算相邻字符对的频率:
# 计算相邻字符对的频率
from collections import Counter
def get_stats(vocab):pairs = Counter()for word, freq in vocab.items(<