LLM大语言模型之Tokenization分词方法(WordPiece,Byte-Pair Encoding (BPE),Byte-level BPE(BBPE)原理及其代码实现)

本文详细介绍了大语言模型(LLM)中常用的分词方法,包括WordPiece、Byte-Pair Encoding (BPE)和Byte-level BPE (BBPE)的原理及代码实现。通过比较它们的优缺点,如WordPiece的语义明确与长尾问题,BPE的简单合并策略,以及BBPE的无OOV问题,阐述了在自然语言处理中如何选择合适的分词策略。
摘要由CSDN通过智能技术生成

LLM大语言模型之Tokenization分词方法(WordPiece,Byte-Pair Encoding (BPE),Byte-level BPE(BBPE)原理及其代码实现)

详解语言模型以及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(词)粒度的优点有:

  • 语义明确:以词为单位进行分词可以更好地保留每个词的语义,使得文本在后续处理中能够更准确地表达含义。
  • 上下文理解:以词为粒度进行分词有助于保留词语之间的关联性和上下文信息,从而在语义分析和理解时能够更好地捕捉句子的意图。

缺点:

  • 长尾效应和稀有词问题: 词表可能变得巨大,包含很多不常见的词汇,增加存储和训练成本,稀有词的训练数据有限,难以获得准确的表示。
  • OOV(Out-of-Vocabulary): 词粒度分词模型只能使用词表中的词来进行处理,无法处理词表之外的词汇,这就是所谓的OOV问题。
  • 形态关系和词缀关系: 无法捕捉同一词的不同形态,也无法有效学习词缀在不同词汇之间的共通性,限制了模型的语言理解能力,比如love和loves在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(字符)粒度的优点有:

  • 统一处理方式:字符粒度分词方法适用于不同语言,无需针对每种语言设计不同的分词规则或工具,具有通用性。
  • 解决OOV问题:由于字符粒度分词可以处理任何字符,无需维护词表,因此可以很好地处理一些新创词汇、专有名词等问题。

缺点:

  • 语义信息不明确:字符粒度分词无法直接表达词的语义,可能导致在一些语义分析任务中效果较差。
  • 处理效率低:由于文本被拆分为字符,处理的粒度较小,增加后续处理的计算成本和时间。

3.subword(子词)粒度

在很多情况下,既不希望将文本切分成单独的词(太大),也不想将其切分成单个字符(太小),而是希望得到介于词和字符之间的子词单元。这就引入了 subword(子词)粒度的分词方法。

在BERT时代,WordPiece 分词方法被广泛应用[1],比如 BERT、DistilBERT等。WordPiece 分词方法是 subword(子词)粒度的一种方法。

3.1 WordPiece

WordPiece核心思想是将单词拆分成多个前缀符号(比如BERT中的##)最小单元,再通过子词合并规则将最小单元进行合并为子词级别。例如对于单词"word",拆分如下:

w ##o ##r ##d

然后通过合并规则进行合并,从而循环迭代构建出一个词汇表,以下是核心步骤:

  1. 计算初始词表:通过训练语料获得或者最初的英文中26个字母加上各种符号以及常见中文字符,这些作为初始词表。
  2. 计算合并分数:对训练语料拆分的多个子词单元通过合拼规则计算合并分数。
  3. 合并分数最高的子词对:选择分数最高的子词对,将它们合并成一个新的子词单元,并更新词表。
  4. 重复合并步骤:不断重复步骤 2 和步骤 3,直到达到预定的词汇表大小、合并次数,或者直到不再有有意义的合并(即,进一步合并不会显著提高词汇表的效益)。
  5. 分词:使用最终得到的词汇表对文本进行分词。

简单举例[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: {
   '我': ['我'], '喜欢': ['喜', 
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值