大模型数据预处理---词元化(分词)

发布于:2024-05-23 ⋅ 阅读:(218) ⋅ 点赞:(0)

    词元化(Tokenization)是数据预处理中的一个关键步骤,旨在将原始文本分割成模型可识别和建模的词元序列,作为大语言模型的输入数据。传统自然语言处理研究(如基于条件随机场的序列标注)主要使用基于词汇的分词方法,这种方法更符合人类的语言认知。然而,基于词汇的分词在某些语言(如中文分词)中可能对于相同的输入产生不同的分词结果,导致生成包含海量低频词的庞大词表,还可能存在未登录词(Out-of-vocabulary, OOV)等问题。因此,一些语言模型开始采用字符作为最小单位来分词。例如,ELMo 采用了 CNN 词编码器。最近,子词分词器(Subword Tokenizer)被广泛应用于基于 Transformer 的语言模型中,包括 BPE 分词、WordPiece 分词和 Unigram 分词三种常见方法。

BPE 分词

    在 1994 年,BPE 算法被提出,最早用于通用的数据压缩。随后,自然语言处理领域的研究人员将其进行适配,并应用于文本分词。BPE 算法从一组基本符号(例如字母和边界字符)开始,迭代地寻找语料库中的两个相邻词元,并将它们替换为新的词元,这一过程被称为合并。合并的选择标准是计算两个连续词元的共现频率,也就是每次迭代中,最频繁出现的一对词元会被选择与合并。合并过程将一直持续达到预定义的词表大小。

1 import re
2 from collections import defaultdict
3
4
5 def extract_frequencies(sequence):
6 """
7 给定一个字符串,计算字符串中的单词出现的频率,并返回词表(一个词到频率的映射
↩→ 字典)。
8 """
9 token_counter = Counter()
10 for item in sequence:
11 tokens = ' '.join(list(item)) + ' </w>'
12 token_counter[tokens] += 1
13 return token_counter
14
15 def frequency_of_pairs(frequencies):
16 """
17 给定一个词频字典,返回一个从字符对到频率的映射字典。
18 """
19 pairs_count = Counter()
20 for token, count in frequencies.items():
21 chars = token.split()
22 for i in range(len(chars) - 1):
23 pair = (chars[i], chars[i+1])
24 pairs_count[pair] += count
25 return pairs_count
26
27 def merge_vocab(merge_pair, vocab):
28 """
29 给定一对相邻词元和一个词频字典,将相邻词元合并为新的词元,并返回新的词表。
30 """
31 re_pattern = re.escape(' '.join(merge_pair))
32 pattern = re.compile(r'(?<!\S)' + re_pattern + r'(?!\S)')
33 updated_tokens = {pattern.sub(''.join(merge_pair), token): freq for
↩→ token, freq in vocab.items()}
34 return updated_tokens
35
36 def encode_with_bpe(texts, iterations):
37 """
38 给定待分词的数据以及最大合并次数,返回合并后的词表。
39 """
40 vocab_map = extract_frequencies(texts)
41 for _ in range(iterations):
42 pair_freqs = frequency_of_pairs(vocab_map)
43 if not pair_freqs:
44 break
45 most_common_pair = pair_freqs.most_common(1)[0][0]
46 vocab_map = merge_vocab(most_common_pair, vocab_map)
47 return vocab_map
48
49 num_merges = 1000
50 bpe_pairs = encode_with_bpe(data, num_merges)

    字节级别的 BPE(Byte-level BPE, B-BPE)是 BPE 算法的一种拓展。它将字节视为合并操作的基本符号,从而可以实现更细粒度的分割,且解决了未登录词问题。采用这种词元化方法的代表性语言模型包括 GPT-2 、BART 和 LLaMA 。具体来说,如果将所有 Unicode 字符都视为基本字符,那么包含所有可能基本字符的基本词表会非常庞大(例如将中文的每个汉字当作一个基本字符)。而将字节作为基本词表可以设置基本词库的大小为 256,同时确保每个基本字符都包含在词汇中。例如,GPT-2 的词表大小为 50,257 ,包括 256 个字节的基本词元、一个特殊的文末词元以及通过 50,000 次合并学习到的词元。通过使用一些处理标点符号的附加规则,GPT2 的分词器可以对文本进行分词,不再需要使用 “” 符号。需要注意的是,由于 Unicode 中存在重复编码的特殊字符,可以使用标准化方法(例如NFKC)来预处理我们的语料。但 NFKC 并不是无损的,可能会降低分词性能。

假设语料中包含了五个英文单词:
“loop”,“pool”,“loot”,“tool”,“loots”
在这种情况下,BPE 假设的初始词汇表即为:
[“l”, “o”, “p”, “t”, “s”]
在实践中,基础词汇表可以包含所有 ASCII 字符,也可能包含一些 Unicode 字符(比如中文的汉字)。如果正在进行分词的文本中包含了训练语料库中没有的字符,则该字符将被转换为未知词元(如 “”)。
假设单词在语料库中的频率如下:
(“loop”,15),(“pool”,10),(“loot”,10),(“tool”,5),(“loots”,8)
其中,出现频率最高的是 “oo”,出现了 48 次,因此,学习到的第一条合并规则是(“o”, “o”)→ “oo”,这意味着“oo”将被添加到词汇表中,并且应用这一合并规则到语料库的所有词汇。在这一阶段结束时,词汇和语料库如下所示:
词汇:[“l”, “o”, “p”, “t”, “s”, “oo”]
语料库:(“l”“oo”“p”,15),(“p”“oo”“l”,10),(“l”“oo”“t”,10),(“t”“oo”“l”,5),(“l”“oo”“t”“s”,8)
此时,出现频率最高的配对是(“l”,“oo”),在语料库中出现了 33 次,因此学习到的第二条合并规则是(“l”,“oo”)→“loo”。将其添加到词汇表中并应用到所有现有的单词,可以得到:
词汇:[“l”, “o”, “p”, “t”, “s”, “oo”, “loo”]
语料库:(“loo”“p”,15),(“p”“oo”“l”,10),(“loo”“t”,10),(“t”“oo”“l”,5),(“loo”“t”“s”,8)现在,最常出现的词对是(“loo”, “t”),因此可以学习合并规则(“loo”, “t”)→ “loot”,这样就得到了第一个三个字母的词元:
词汇:[“l”, “o”, “p”, “t”, “s”, “oo”, “loo”, “loot”]
语料库:(“loo”“p”,15),(“p”“oo”“l”,10),(“loot”,10),(“t”“oo”“l”,5),(“loot”“s”,8)
可以重复上述过程,直到达到所设置的终止词汇量。

WordPiece 分词

    WordPiece 是谷歌内部非公开的分词算法,最初是由谷歌研究人员在开发语音搜索系统时提出的。随后,在 2016 年被用于机器翻译系统,并于 2018年被 BERT 采用作为分词器。WordPiece 分词和 BPE 分词的想法非常相似,都是通过迭代合并连续的词元,但是合并的选择标准略有不同。在合并前,WordPiece分词算法会首先训练一个语言模型,并用这个语言模型对所有可能的词元对进行评分。然后,在每次合并时,它都会选择使得训练数据的似然性增加最多的词元 对。

    由于谷歌并未发布 WordPiece 分词算法的官方实现,这里我们参考了 Hugging Face 在线自然语言课程中给出的 WordPiece 算法的一种实现。与 BPE 类似,Word Piece 分词算法也是从一个小的词汇表开始,其中包括模型使用的特殊词元和初始词汇表。由于它是通过添加前缀(如 BERT 的##)来识别子词的,因此每个词的初始拆分都是将前缀添加到词内的所有字符上。举例来说,“word”会被拆分为:“w##o ##r ##d”。与 BPE 方法的另一个不同点在于,WordPiece 分词算法并不选择最频繁的词对,而是使用下面的公式为每个词对计算分数:得分 = 词对出现的频率/(第一个词出现的频率 × 第二个词出现的频率)。

Unigram 分词

    与 BPE 分词和 WordPiece 分词不同,Unigram 分词方法从语料库的一组足够大的字符串或词元初始集合开始,迭代地删除其中的词元,直到达到预期的词表大小。它假设从当前词表中删除某个词元,并计算训练语料的似然增加情况,以此来作为选择标准。这个步骤是基于一个训练好的一元语言模型来进行的。为估计一元语言模型,它采用期望最大化(Expectation–Maximization, EM)算法:在每次迭代中,首先基于旧的语言模型找到当前最优的分词方式,然后重新估计一元概率从而更新语言模型。这个过程中一般使用动态规划算法(即维特比算法,Viterbi Algorithm)来高效地找到语言模型对词汇的最优分词方式。采用这种分词方法的代表性模型包括 T5 和 mBART。

分词器的选用

    虽然直接使用已有的分词器较为方便(例如 OPT 和 GPT-3 使用了GPT-2 的分词器),但是使用为预训练语料专门训练或设计的分词器会更加有效,尤其是对于那些混合了多领域、多语言和多种格式的语料。最近的大语言模型通常使用 SentencePiece 代码库为预训练语料训练定制化的分词器,这一代码库支持字节级别的 BPE 分词和 Unigram 分词。

    为了训练出高效的分词器,我们应重点关注以下几个因素。首先,分词器必须具备无损重构的特性,即其分词结果能够准确无误地还原为原始输入文本。其次,分词器应具有高压缩率,即在给定文本数据的情况下,经过分词处理后的词元数量应尽可能少,从而实现更为高效的文本编码和存储。具体来说,压缩比可以通过将原始文本的 UTF-8 字节数除以分词器生成的词元数(即每个词元的平均字节数)来计算:压缩率 =UTF-8 字节数/词元数。例如,给定一段大小为 1MB(1,048,576 字节)的文本,如果它被分词为 200,000个词元,其压缩率即为1,048,576 /200,000 = 5.24。

    值得注意的是,在扩展现有的大语言模型(如继续预训练或指令微调)的同时,还需要意识到原始分词器可能无法较好地适配实际需求。以 LLaMA 为例,它基于主要包含英语文本的预训练语料训练了 BPE 分词器。因此,当处理中文等非英语数据时,该分词器可能表现不佳,甚至可能导致推理延迟的增加。此外,为进一步提高某些特定能力(如数学能力),还可能需要针对性地设计分词器。例如,BPE 分词器可能将整数 7,481 分词为“7 481”,而将整数 74,815 分词为“748 15”。这导致相同的数字被分割成不同的子串,降低了解决相关数学问题的能力。相比之下,专门设计基于数字的分词方式可以避免这种不一致性,从而提升大语言模型的数值计算能力。综上所述,在设计和训练分词器时,我们需要综合考虑多种因素,以确保其在实际应用中能够发挥最佳效果。


网站公告

今日签到

点亮在社区的每一天
去签到