赞
踩
详解语言模型以及LLM(大语言模型)的Tokenization分词方法(WordPiece,Byte-Pair Encoding (BPE),Byte-level BPE(BBPE)代码实现)全文阅读和实现可能需要45分钟,建议收藏~如果觉得对你有帮助,那就点个赞吧 。
Tokenization(分词) 在自然语言处理(NLP)的任务中是最基本的一步,把文本内容处理为最小基本单元即token用于后续的处理,如何把文本处理成token呢?有一系列的方法,基本思想是构建一个词表通过词表一一映射进行分词,但如何构建合适的词表呢?以下以分词粒度为角度进行介绍:
1.word(词)粒度
在英文语系中,word(词)级别分词实现很简单,因为有天然的分隔符。在中文里面word(词)粒度,需要一些分词工具比如jieba,以下是中文和英文的例子:
中文句子:我喜欢看电影和读书。
分词结果:我 | 喜欢 | 看 | 电影 | 和 | 读书。
英文句子:I enjoy watching movies and reading books.
分词结果:I | enjoy | watching | movies | and | reading | books.
word(词)粒度的优点有:
缺点:
2.char(字符)粒度
以字符为单位进行分词,即将文本拆分成一个个单独的字符作为最小基本单元,这种字符粒度的分词方法适用于多种语言,无论是英文、中文还是其他不同语言,都能够一致地使用字符粒度进行处理,因为英文就26个字母以及其他的一些符号,中文常见字就6000个左右。
中文句子:我喜欢看电影和读书。
分词结果:我 | 喜 | 欢 | 看 | 电 | 影 | 和 | 读 | 书 | 。
英文句子:I enjoy watching movies and reading books.
分词结果:I | | e | n | j | o | y | | w | a | t | c | h | i | n | g | | m | o | v | i | e | s | | a | n | d | | r | e | a | d | i | n | g | | b | o | o | k | s | .
char(字符)粒度的优点有:
缺点:
3.subword(子词)粒度
在很多情况下,既不希望将文本切分成单独的词(太大),也不想将其切分成单个字符(太小),而是希望得到介于词和字符之间的子词单元。这就引入了 subword(子词)粒度的分词方法。
在BERT时代,WordPiece 分词方法被广泛应用[1],比如 BERT、DistilBERT等。WordPiece 分词方法是 subword(子词)粒度的一种方法。
3.1 WordPiece
WordPiece核心思想是将单词拆分成多个前缀符号(比如BERT中的##)最小单元,再通过子词合并规则将最小单元进行合并为子词级别。例如对于单词"word",拆分如下:
w ##o ##r ##d
然后通过合并规则进行合并,从而循环迭代构建出一个词汇表,以下是核心步骤:
简单举例[2]:
我们有以下的训练语料中的样例,括号中第2位为在训练语料中出现的频率:
("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5)
我们对其进行拆分为带前缀的形式:
("h" "##u" "##g", 10), ("p" "##u" "##g", 5), ("p" "##u" "##n", 12), ("b" "##u" "##n", 4), ("h" "##u" "##g" "##s", 5)
所以这些样例的初始词表将会是:
["b", "h", "p", "##g", "##n", "##s", "##u"]
接下来重要的一步进行计算合并分数,也称作互信息(信息论中衡量两个变量之间的关联程度[3]),简单来说就是以下公式来计算
score=(freq_of_pair)/(freq_of_first_element×freq_of_second_element)
分数 = 合并pair候选的频率 / (第一个元素的频率 × 第二个元素的频率)
对于上述样例中这个pair(“##u”, “##g”)出现的频率是最高的20次,但是"##u"出现的频率是36次, “##g"出现的频率是20次,所以这个pair(”##u", “##g”)的分数是(20)/(36*20) = 1/36,同理计算这个pair(“##g”, “##s”)的分数为(5)/(20*5) = 1/20,所以最先合并的pair是(“##g”, “##s”)→(“##gs”)。此时词表和拆分后的的频率将变成以下:
Vocabulary: ["b", "h", "p", "##g", "##n", "##s", "##u", "##gs"]
Corpus: ("h" "##u" "##g", 10), ("p" "##u" "##g", 5), ("p" "##u" "##n", 12), ("b" "##u" "##n", 4), ("h" "##u" "##gs", 5)
重复上述的操作,直到达到你想要的词表的大小
Vocabulary: ["b", "h", "p", "##g", "##n", "##s", "##u", "##gs", "hu", "hug"]
Corpus: ("hug", 10), ("p" "##u" "##g", 5), ("p" "##u" "##n", 12), ("b" "##u" "##n", 4), ("hu" "##gs", 5)
简单的代码实现[2]:
用一些包含中英文的文本作为训练语料,因为英文有天然的分隔符,所以在这个例子中,中文已经进行了分词:
sentences = [
"我",
"喜欢",
"吃",
"苹果",
"他",
"不",
"喜欢",
"吃",
"苹果派",
"I like to eat apples",
"She has a cute cat",
"you are very cute",
"give you a hug",
]
统计每个词出现的频率并初始化初始词表:
from collections import defaultdict # 构建频率统计 def build_stats(sentences): stats = defaultdict(int) for sentence in sentences: symbols = sentence.split() for symbol in symbols: stats[symbol] += 1 return stats stats = build_stats(sentences) print("stats:", stats) alphabet = [] for word in stats.keys(): if word[0] not in alphabet: alphabet.append(word[0]) for letter in word[1:]: if f"##{letter}" not in alphabet: alphabet.append(f"##{letter}") alphabet.sort() # 初始词表 vocab = alphabet.copy() print("alphabet:", alphabet) # 结果 stats: defaultdict(<class 'int'>, {'我': 1, '喜欢': 2, '吃': 2, '苹果': 1, '他': 1, '不': 1, '苹果派': 1, 'I': 1, 'like': 1, 'to': 1, 'eat': 1, 'apples': 1, 'She': 1, 'has': 1, 'a': 2, 'cute': 2, 'cat': 1, 'you': 2, 'are': 1, 'very': 1, 'give': 1, 'hug': 1}) # 初始词表 alphabet: ['##a', '##e', '##g', '##h', '##i', '##k', '##l', '##o', '##p', '##r', '##s', '##t', '##u', '##v', '##y', '##果', '##欢', '##派', 'I', 'S', 'a', 'c', 'e', 'g', 'h', 'l', 't', 'v', 'y', '不', '他', '吃', '喜', '我', '苹']
根据初始词表拆分每个词:
splits = {
word: [c if i == 0 else f"##{c}" for i, c in enumerate(word)]
for word in stats.keys()
}
print("splits:", splits)
# 结果
splits: {'我': ['我'], '喜欢': ['喜', '##欢'], '吃': ['吃'], '苹果': ['苹', '##果'], '他': ['他'], '不': ['不'], '苹果派': ['苹', '##果', '##派'], 'I': ['I'], 'like': ['l', '##i', '##k', '##e'], 'to': ['t', '##o'], 'eat': ['e', '##a', '##t'], 'apples': ['a', '##p', '##p', '##l', '##e', '##s'], 'She': ['S', '##h', '##e'], 'has': ['h', '##a', '##s'], 'a': ['a'], 'cute': ['c', '##u', '##t', '##e'], 'cat': ['c', '##a', '##t'], 'you': ['y', '##o', '##u'], 'are': ['a', '##r', '##e'], 'very': ['v', '##e', '##r', '##y'], 'give': ['g', '##i', '##v', '##e'], 'hug': ['h', '##u', '##g']}
根据上述提到的计算互信息的分数公式进行计算:
def compute_pair_scores(splits): letter_freqs = defaultdict(int) pair_freqs = defaultdict(int) for word, freq in stats.items(): split = splits[word] if len(split) == 1: letter_freqs[split[0]] += freq continue for i in range(len(split) - 1): pair = (split[i], split[i + 1]) letter_freqs[split[i]] += freq pair_freqs[pair] += freq letter_freqs[split[-1]] += freq scores = { pair: freq / (letter_freqs[pair[0]] * letter_freqs[pair[1]]) for pair, freq in pair_freqs.items() } return scores pair_scores = compute_pair_scores(splits) for i, key in enumerate(pair_scores.keys()): print(f"{key}: {pair_scores[key]}") if i >= 5: break
一些结果:
('喜', '##欢'): 0.5
('苹', '##果'): 0.5
('##果', '##派'): 0.5
('l', '##i'): 0.5
('##i', '##k'): 0.5
('##k', '##e'): 0.125
我们需要的是将分数最高的进行合并然后开始循环迭代,看一看分数最高的pair(子词对):
best_pair = ""
max_score = None
for pair, score in pair_scores.items():
if max_score is None or max_score < score:
best_pair = pair
max_score = score
print(best_pair, max_score)
# 结果
('S', '##h') 1.0
结果为(‘S’, ‘##h’) 1.0,所以最先合成的就是(‘S’, ‘##h’)→’##Sh’,合并的函数如下:
def merge_pair(a, b, splits):
for word in stats:
split = splits[word]
if len(split) == 1:
continue
i = 0
while i < len(split) - 1:
if split[i] == a and split[i + 1] == b:
merge = a + b[2:] if b.startswith("##") else a + b
split = split[:i] + [merge] + split[i + 2 :]
else:
i += 1
splits[word] = split
return splits
最后就是一直进行循环迭代,直到vocab达到了我们想要的数量
vocab_size = 50 while len(vocab) < vocab_size: scores = compute_pair_scores(splits) best_pair, max_score = "", None for pair, score in scores.items(): if max_score is None or max_score < score: best_pair = pair max_score = score splits = merge_pair(*best_pair, splits) new_token = ( best_pair[0] + best_pair[1][2:] if best_pair[1].startswith("##") else best_pair[0] + best_pair[1] ) vocab.append(new_token) print("vocab:", vocab) # 结果 vocab: ['##a', '##e', '##g', '##h', '##i', '##k', '##l', '##o', '##p', '##r', '##s', '##t', '##u', '##v', '##y', '##果', '##欢', '##派', 'I', 'S', 'a', 'c', 'e', 'g', 'h', 'l', 't', 'v', 'y', '不', '他', '吃', '喜', '我', '苹', 'Sh', '喜欢', '苹果', '苹果派', 'li', 'lik', 'gi', 'giv', '##pl', '##ppl', '##ry', 'to', 'yo', 'ea', 'eat']
上述就是WordPiece分词方法的代码实现,一般来说最后会在词表中加上一些特殊词汇,以及英文中26个字母加上各种符号以及常见中文字符,不过如果训练语料比较大以及词表比较大那这些应该也是已经包括了,只需要添加特殊词汇:
all_vocab = vocab + ["[PAD]", "[UNK]", "[CLS]", "[SEP]", "[MASK]"] + other_alphabet
在大语言模型时代,最常用的分词方法是Byte-Pair Encoding (BPE)和Byte-level BPE(BBPE),Byte-Pair Encoding (BPE)最初是一种文本压缩算法[4],在训练 GPT 时被 OpenAI 用于tokenization,后续好多模型GPT,RoBERTa等都采用了这种分词方法。Byte-level BPE(BBPE)是于19年在BPE的基础上提出以Byte-level(字节)为粒度的分词方法[5],目前BLOOM,Llama,Falcon等采用的是该分词方法。
3.2 Byte-Pair Encoding (BPE)
Byte-Pair Encoding (BPE)核心思想是逐步合并出现频率最高的子词对而不是像Wordpiece计算合并分数,从而构建出一个词汇表,以下是核心步骤:
简单的代码实现[2]:
用一些包含中英文的文本作为训练语料和上面相同,因为英文有天然的分隔符,所以在这个例子中,中文已经进行了分词:
sentences = [
"我",
"喜欢",
"吃",
"苹果",
"他",
"不",
"喜欢",
"吃",
"苹果派",
"I like to eat apples",
"She has a cute cat",
"you are very cute",
"give you a hug",
]
统计每个词出现的频率并初始化初始词表:
# 构建频率统计 def build_stats(sentences): stats = defaultdict(int) for sentence in sentences: symbols = sentence.split() for symbol in symbols: stats[symbol] += 1 return stats stats = build_stats(sentences) print("stats:", stats) alphabet = [] for word in stats.keys(): for letter in word: if letter not in alphabet: alphabet.append(letter) alphabet.sort() # 初始词表 vocab = alphabet.copy() print("alphabet:", alphabet) # 结果 stats: defaultdict(<class 'int'>, {'我': 1, '喜欢': 2, '吃': 2, '苹果': 1, '他': 1, '不': 1, '苹果派': 1, 'I': 1, 'like': 1, 'to': 1, 'eat': 1, 'apples': 1, 'She': 1, 'has': 1, 'a': 2, 'cute': 2, 'cat': 1, 'you': 2, 'are': 1, 'very': 1, 'give': 1, 'hug': 1}) # 初始词表 alphabet: ['I', 'S', 'a', 'c', 'e', 'g', 'h', 'i', 'k', 'l', 'o', 'p', 'r', 's', 't', 'u', 'v', 'y', '不', '他', '吃', '喜', '我', '果', '欢', '派', '苹']
根据初始词表拆分每个词,计算左右pair(子词对)出现的频率
splits = {word: [c for c in word] for word in stats.keys()} print("splits:", splits) def compute_pair_freqs(splits): pair_freqs = defaultdict(int) for word, freq in stats.items(): split = splits[word] if len(split) == 1: continue for i in range(len(split) - 1): pair = (split[i], split[i + 1]) pair_freqs[pair] += freq return pair_freqs pair_freqs = compute_pair_freqs(splits) for i, key in enumerate(pair_freqs.keys()): print(f"{key}: {pair_freqs[key]}") if i >= 5: break # 结果 splits: {'我': ['我'], '喜欢': ['喜', '欢'], '吃': ['吃'], '苹果': ['苹', '果'], '他': ['他'], '不': ['不'], '苹果派': ['苹', '果', '派'], 'I': ['I'], 'like': ['l', 'i', 'k', 'e'], 'to': ['t', 'o'], 'eat': ['e', 'a', 't'], 'apples': ['a', 'p', 'p', 'l', 'e', 's'], 'She': ['S', 'h', 'e'], 'has': ['h', 'a', 's'], 'a': ['a'], 'cute': ['c', 'u', 't', 'e'], 'cat': ['c', 'a', 't'], 'you': ['y', 'o', 'u'], 'are': ['a', 'r', 'e'], 'very': ['v', 'e', 'r', 'y'], 'give': ['g', 'i', 'v', 'e'], 'hug': ['h', 'u', 'g']} ('喜', '欢'): 2 ('苹', '果'): 2 ('果', '派'): 1 ('l', 'i'): 1 ('i', 'k'): 1 ('k', 'e'): 1
然后开始循环迭代找到出现频率最高的pair(子词对):
best_pair = ""
max_freq = None
for pair, freq in pair_freqs.items():
if max_freq is None or max_freq < freq:
best_pair = pair
max_freq = freq
print(best_pair, max_freq)
结果为【(‘喜’, ‘欢’) 2】,所以最先合成的就是(‘喜’, ‘欢’)→’喜欢’,然后合并的函数如下:
def merge_pair(a, b, splits): for word in stats: split = splits[word] if len(split) == 1: continue i = 0 while i < len(split) - 1: if split[i] == a and split[i + 1] == b: split = split[:i] + [a + b] + split[i + 2 :] else: i += 1 splits[word] = split return splits
最后就是一直进行循环直到vocab达到了我们想要的数量:
# 假设我们想要的词典为50 merges = {} vocab_size = 50 while len(vocab) < vocab_size: pair_freqs = compute_pair_freqs(splits) best_pair = "" max_freq = None for pair, freq in pair_freqs.items(): if max_freq is None or max_freq < freq: best_pair = pair max_freq = freq splits = merge_pair(*best_pair, splits) merges[best_pair] = best_pair[0] + best_pair[1] vocab.append(best_pair[0] + best_pair[1]) print("merges:", merges) print("vocab:", vocab) # 结果 merges: {('喜', '欢'): '喜欢', ('苹', '果'): '苹果', ('a', 't'): 'at', ('c', 'u'): 'cu', ('cu', 't'): 'cut', ('cut', 'e'): 'cute', ('y', 'o'): 'yo', ('yo', 'u'): 'you', ('v', 'e'): 've', ('苹果', '派'): '苹果派', ('l', 'i'): 'li', ('li', 'k'): 'lik', ('lik', 'e'): 'like', ('t', 'o'): 'to', ('e', 'at'): 'eat', ('a', 'p'): 'ap', ('ap', 'p'): 'app', ('app', 'l'): 'appl', ('appl', 'e'): 'apple', ('apple', 's'): 'apples', ('S', 'h'): 'Sh', ('Sh', 'e'): 'She', ('h', 'a'): 'ha'} vocab: ['I', 'S', 'a', 'c', 'e', 'g', 'h', 'i', 'k', 'l', 'o', 'p', 'r', 's', 't', 'u', 'v', 'y', '不', '他', '吃', '喜', '我', '果', '欢', '派', '苹', '喜欢', '苹果', 'at', 'cu', 'cut', 'cute', 'yo', 'you', 've', '苹果派', 'li', 'lik', 'like', 'to', 'eat', 'ap', 'app', 'appl', 'apple', 'apples', 'Sh', 'She', 'ha']
再加上一些特殊词汇和其他词汇:
all_vocab = vocab + ["[PAD]", "[UNK]", "[BOS]", "[EOS]"] + other_alphabet
上述就是BPE的代码实现,BPE理论上还是会出现OOV的,当词汇表的大小受限时,一些较少频繁出现的子词和没有在训练过程中见过的子词,就会无法进入词汇表出现OOV,而Byte-level BPE(BBPE)理论上是不会出现这个情况的。
3.3 Byte-level BPE(BBPE)
基础知识:
Unicode: Unicode 是一种字符集,旨在涵盖地球上几乎所有的书写系统和字符。它为每个字符分配了一个唯一的代码点(code point)用于标识字符。Unicode 不关注字符在计算机内部的具体表示方式,而只是提供了一种字符到代码点的映射。Unicode 的出现解决了字符集的碎片化问题,使得不同的语言和字符能够在一个共同的标准下共存。然而,Unicode 并没有规定如何在计算机内存中存储和传输这些字符。
UTF-8: UTF-8(Unicode Transformation Format-8)是一种变长的字符编码方案,它将 Unicode 中的代码点转换为字节序列。UTF-8 的一个重要特点是它是向后兼容 ASCII 的,这意味着标准的 ASCII 字符在 UTF-8 中使用相同的字节表示,从而确保现有的 ASCII 文本可以无缝地与 UTF-8 共存。在 UTF-8 编码中,字符的表示长度可以是1到4个字节,不同范围的 Unicode 代码点使用不同长度的字节序列表示,这样可以高效地表示整个 Unicode 字符集。UTF-8 的编码规则是:
例如,英文字母 “A” 的 Unicode 代码点是U+0041,在 UTF-8 中表示为 0x41(与 ASCII 编码相同);而中文汉字 “你” 的 Unicode 代码点是U+4F60,在 UTF-8 中表示为0xE4 0xBD 0xA0三个字节的序列。
所以简单的来说:
Byte(字节):计算机存储和数据处理时,字节是最小的单位。一个字节包含8个(Bit)二进制位,每个位可以是0或1,每位的不同排列和组合可以表示不同的数据,所以一个字节能表示的范围是256个
言归正传:
Byte-level BPE(BBPE)和Byte-Pair Encoding (BPE)区别就是BPE是最小词汇是字符级别,而BBPE是字节级别的,通过UTF-8的编码方式这一个字节的256的范围,理论上可以表示这个世界上的所有字符。
所以实现的步骤和BPE就是实现的粒度不一样,其他的都是一样的
简单代码实现,不做赘述,读者朋友们可以自己实现一下
from collections import defaultdict sentences = [ "我", "喜欢", "吃", "苹果", "他", "不", "喜欢", "吃", "苹果派", "I like to eat apples", "She has a cute cat", "you are very cute", "give you a hug", ] # 构建初始词汇表,包含一个字节的256个表示 initial_vocab = [bytes([byte]) for byte in range(256)] vocab = initial_vocab.copy() print("initial_vocab:", initial_vocab) # 构建频率统计 def build_stats(sentences): stats = defaultdict(int) for sentence in sentences: symbols = sentence.split() for symbol in symbols: stats[symbol.encode("utf-8")] += 1 return stats stats = build_stats(sentences) splits = {word: [byte for byte in word] for word in stats.keys()} def compute_pair_freqs(splits): pair_freqs = defaultdict(int) for word, freq in stats.items(): split = splits[word] if len(split) == 1: continue for i in range(len(split) - 1): pair = (split[i], split[i + 1]) pair_freqs[pair] += freq return pair_freqs pair_freqs = compute_pair_freqs(splits) def merge_pair(pair, splits): merged_byte = bytes(pair) for word in stats: split = splits[word] if len(split) == 1: continue i = 0 while i < len(split) - 1: if split[i:i+2] == pair: # 检查分割中是否有这对字节 split = split[:i] + [merged_byte] + split[i + 2 :] else: i += 1 splits[word] = split return splits vocab_size = 50 while len(vocab) < vocab_size: pair_freqs = compute_pair_freqs(splits) best_pair = () max_freq = None for pair, freq in pair_freqs.items(): if max_freq is None or max_freq < freq: best_pair = pair max_freq = freq splits = merge_pair(best_pair, splits) merged_byte = bytes(best_pair) print("vocab:", vocab)
着重解释一下为什么Byte-level BPE(BBPE)不会出现OOV问题,初始的词表里有256个表示如下:
[b'\x00', b'\x01', b'\x02', b'\x03', b'\x04', b'\x05', b'\x06', b'\x07', b'\x08', b'\t', b'\n', b'\x0b', b'\x0c', b'\r', b'\x0e', b'\x0f', b'\x10', b'\x11', b'\x12', b'\x13', b'\x14', b'\x15', b'\x16', b'\x17', b'\x18', b'\x19', b'\x1a', b'\x1b', b'\x1c', b'\x1d', b'\x1e', b'\x1f', b' ', b'!', b'"', b'#', b'$', b'%', b'&', b"'", b'(', b')', b'*', b'+', b',', b'-', b'.', b'/', b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b':', b';', b'<', b'=', b'>', b'?', b'@', b'A', b'B', b'C', b'D', b'E', b'F', b'G', b'H', b'I', b'J', b'K', b'L', b'M', b'N', b'O', b'P', b'Q', b'R', b'S', b'T', b'U', b'V', b'W', b'X', b'Y', b'Z', b'[', b'\\', b']', b'^', b'_', b'`', b'a', b'b', b'c', b'd', b'e', b'f', b'g', b'h', b'i', b'j', b'k', b'l', b'm', b'n', b'o', b'p', b'q', b'r', b's', b't', b'u', b'v', b'w', b'x', b'y', b'z', b'{', b'|', b'}', b'~', b'\x7f', b'\x80', b'\x81', b'\x82', b'\x83', b'\x84', b'\x85', b'\x86', b'\x87', b'\x88', b'\x89', b'\x8a', b'\x8b', b'\x8c', b'\x8d', b'\x8e', b'\x8f', b'\x90', b'\x91', b'\x92', b'\x93', b'\x94', b'\x95', b'\x96', b'\x97', b'\x98', b'\x99', b'\x9a', b'\x9b', b'\x9c', b'\x9d', b'\x9e', b'\x9f', b'\xa0', b'\xa1', b'\xa2', b'\xa3', b'\xa4', b'\xa5', b'\xa6', b'\xa7', b'\xa8', b'\xa9', b'\xaa', b'\xab', b'\xac', b'\xad', b'\xae', b'\xaf', b'\xb0', b'\xb1', b'\xb2', b'\xb3', b'\xb4', b'\xb5', b'\xb6', b'\xb7', b'\xb8', b'\xb9', b'\xba', b'\xbb', b'\xbc', b'\xbd', b'\xbe', b'\xbf', b'\xc0', b'\xc1', b'\xc2', b'\xc3', b'\xc4', b'\xc5', b'\xc6', b'\xc7', b'\xc8', b'\xc9', b'\xca', b'\xcb', b'\xcc', b'\xcd', b'\xce', b'\xcf', b'\xd0', b'\xd1', b'\xd2', b'\xd3', b'\xd4', b'\xd5', b'\xd6', b'\xd7', b'\xd8', b'\xd9', b'\xda', b'\xdb', b'\xdc', b'\xdd', b'\xde', b'\xdf', b'\xe0', b'\xe1', b'\xe2', b'\xe3', b'\xe4', b'\xe5', b'\xe6', b'\xe7', b'\xe8', b'\xe9', b'\xea', b'\xeb', b'\xec', b'\xed', b'\xee', b'\xef', b'\xf0', b'\xf1', b'\xf2', b'\xf3', b'\xf4', b'\xf5', b'\xf6', b'\xf7', b'\xf8', b'\xf9', b'\xfa', b'\xfb', b'\xfc', b'\xfd', b'\xfe', b'\xff']
通过上述的方式其实是在一直根据训练语料循环迭代合成子词或者词,最后形成词表,比如“苹果”通过UTF-8进行编码后为“\xe8\x8b\xb9\xe6\x9e\x9c”,如果词表里面有,那“苹果”就通过词表映射成了1个表示,准确来说是1个token;如果词表里没有,那就用256中的“\xe8+\x8b+\xb9+\xe6+\x9e+\x9c”来表示“苹果”这个词,那就是6个token。在先前的各种分词方法中,如果词典里没有”苹果“这个词,也没有”苹“,”果“这样的子词的话,那就变成了[UNK]。所以在现在的大模型中,以Byte-level BPE(BBPE)这种方式进行分词是不会出现OOV,但词表中如果没有word级别的词的话,一些中英文就会分词分的很细碎,比如Llama在中文上就会把一些词分成多个token其实就是UTF-8后的中文编码,对编码效率以及语义会有影响,于是出现了一些扩充Llama中文词表的工作。
上述分词算法在工程上实现一般使用sentencpiece工具包[6],谷歌在这个包中实现了上述的一系列算法,扩充Llama中文词表的工作也都是在此上面实现的。后续我也会写一篇文章进行详细的讲解。欢迎关注~
参考
ab[1] https://huggingface.co/learn/nlp-course/chapter6/6?fw=pt
[2] https://zh.wikipedia.org/zh-hans/%E4%BA%92%E4%BF%A1%E6%81%AF
[3] https://arxiv.org/abs/1508.07909
[4] https://arxiv.org/abs/1909.03341
[5] https://huggingface.co/learn/nlp-course/chapter6/5?fw=pt
[6] https://github.com/google/sentencepiece
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。