BPE Tokenizer 讲解
AI摘要: 本文介绍了Byte Pair Encoding(BPE)算法在分词场景中的应用。传统对文本的分词方式包括基于单词和字符两种方式,而针对中文还有基于常用词的分词方式。在LLM时代,BPE算法因其适合海量数据的特性而被采用。文章通过对比不同级别的分词方法,如char level、word level以及sub word level等,指出了各自的优缺点及适用场景。
传统对文本的分词方式无非是基于单词、字符两种方式(char level 、word level),针对中文还有基于常用词的分词方式(如jieba)。在LLM时代,Byte Pair Encoding算法更加适合海量的数据的分词场景。
背景
举个例子,对于i am highest句子。
-
char level分词:
['i', 'a', 'm', ' ' , 'h', 'i', 'g', 'h', 'e', 's', 't']
, 这种级别的分词会导致一个句子产生非常长的token,如果是更长的句子,token的长度会更加爆炸,训练成本太高; -
word level分词:
['i', 'am', 'highest']
,看样子还行。如果遇到hight/higher/highest,这种分词方式会将这三个词当成不同的词,这不太好,因为他们其实本质是一个差不都的意思。此外,当词表不够丰富时,容易导致OOV问题; -
sub word level分词:这是上述两种方式的折中方案,将一个词分割成多个字词的表示。hightest会被分割为
['high', 'est']
原理
Byte Pair Encoding是sub word level分词的具体实现方式的一种。BPE
是通过迭代地挖掘频繁词对和替换来得到分词结果,首先将词分成单个字符,然后依次用另一个字符替换频率最高的一对字符 ,直到循环次数结束。有点像霍夫曼编码,压缩常见项。
听起来有点绕,但是看过程就很清晰:
corpus = ["highest", "higher", "lower", "lowest", "cooler", "coolest"]
{
"highest": ["h", "i", "g", "h", "e", "s", "t", "</w>"],
"higher": ["h", "i", "g", "h", "e", "r", "</w>"],
"lower": ["l", "o", "w", "e", "r", "</w>"],
"lowest": ["l", "o", "w", "e", "s", "t", "</w>"],
"cooler": ["c", "o", "o", "l", "e", "r", "</w>"],
"collest": ["c", "o", "o", "l", "e", "s", "t", "</w>"],
}
{
"highest": ["h", "i", "g", "h", "es", "t", "</w>"],
"higher": ["h", "i", "g", "h", "e", "r", "</w>"],
"lower": ["l", "o", "w", "e", "r", "</w>"],
"lowest": ["l", "o", "w", "es", "t", "</w>"],
"cooler": ["c", "o", "o", "l", "e", "r", "</w>"],
"collest": ["c", "o", "o", "l", "es", "t", "</w>"],
}
{
"highest": ["h", "i", "g", "h", "est", "</w>"],
"higher": ["h", "i", "g", "h", "e", "r", "</w>"],
"lower": ["l", "o", "w", "e", "r", "</w>"],
"lowest": ["l", "o", "w", "est", "</w>"],
"cooler": ["c", "o", "o", "l", "e", "r", "</w>"],
"collest": ["c", "o", "o", "l", "est", "</w>"],
}
{
"highest": ["h", "i", "g", "h", "est</w>"],
"higher": ["h", "i", "g", "h", "e", "r", "</w>"],
"lower": ["l", "o", "w", "e", "r", "</w>"],
"lowest": ["l", "o", "w", "est</w>"],
"cooler": ["c", "o", "o", "l", "e", "r", "</w>"],
"collest": ["c", "o", "o", "l", "est</w>"],
}
{
"highest": ["h", "i", "g", "h", "est</w>"],
"higher": ["h", "i", "g", "h", "er", "</w>"],
"lower": ["l", "o", "w", "er", "</w>"],
"lowest": ["l", "o", "w", "est</w>"],
"cooler": ["c", "o", "o", "l", "er", "</w>"],
"collest": ["c", "o", "o", "l", "est</w>"],
}
{
"highest": ["h", "i", "g", "h", "est</w>"],
"higher": ["h", "i", "g", "h", "er</w>"],
"lower": ["l", "o", "w", "er</w>"],
"lowest": ["l", "o", "w", "est</w>"],
"cooler": ["c", "o", "o", "l", "er</w>"],
"collest": ["c", "o", "o", "l", "est</w>"],
}
...
代码实现
调包侠最爱的环节:
import sentencepiece as sp
sp.SentencePieceTrainer.train(input="tlbb.txt", # 支持txt和tsv格式
model_prefix="tlbb", # 保存模型文件名的前缀
vocab_size=5000,
model_type='bpe')
针对中文的处理
BPE
的第一步是按照char level进行分词,在英文场景中是26个英文字母,加上特殊字符也不会太多,还是比较适用的。在中文场景中,单个中文字就有上千个,BPE
就不是很适合了。BPE
通过按照 ascii
进行拆分出最小单位, 所以一个中文字会被拆出乱码的情况。