什么是 Transformer 模型输入层使用的词元化方法
笔者最近在学习 Transformer 模型的实现细节。
在下面这本书的输入嵌入
章节,提到了词元化方法的概念。
词元化方法简介
词元化(Tokenization)是自然语言处理中的一个核心步骤,涉及将文本数据转换为模型可以理解的基本单元。这个步骤的重要性毋庸置疑,因为文本数据本质上是字符串,无法直接输入到机器学习模型中,尤其是神经网络模型中。通过词元化,文本数据可以被分解为更小的单位(如词、子词或字符),这些单位最终被表示为数值向量,从而使模型能够有效处理和理解语言。
在深入解释词元化的主流方法之前,我们先理解什么是词元(Token)。词元是文本的最小组成单位,可以是一个单词、一个字符,或者一个子词,取决于具体的词元化方法。不同的词元化方法会对文本进行不同程度的分解。一个好的词元化方法应根据应用的目标、语言的特性、数据的稀疏性和模型复杂度,平衡对文本的拆分颗粒度。
主流词元化方法及详细解释
在现代自然语言处理任务中,存在多种主流词元化方法,每种方法都有其优缺点,适用于不同的场景和任务。接下来,我将详细介绍几种常见的词元化方法,包括:基于空格的词元化、字典词元化、子词词元化(如 BPE 和 WordPiece)、字符级词元化以及最近比较流行的 SentencePiece。我们会使用代码和示例来演示这些方法的实际应用。
基于空格的词元化
基于空格的词元化方法是最简单、最直观的一种方法。在这种方法中,文本被简单地按照空格进行拆分,每个词被认为是一个独立的词元。对于英语这样的语言,这种方法非常直观,因为单词之间通常有明确的分隔符。
举个简单的例子:
文本:"GPT is amazing!"
使用基于空格的词元化方法,这段文本会被拆分为:
["GPT", "is", "amazing!"]
这种方法的主要优点是简单直观,易于实现。Python 中可以很容易地使用 split()
函数来实现:
text = "GPT is amazing!"
tokens = text.split(" ")
print(tokens)
输出:
['GPT', 'is', 'amazing!']
然而,这种方法的缺点也非常明显,尤其是对于词形变化丰富的语言,或者对于包含标点符号的句子。标点符号通常会和单词黏在一起,如上例中的 "amazing!"
,这可能导致模型无法准确理解词语的含义。此外,空格分词对中文等语言并不适用,因为中文的词语之间没有空格分隔符。
字典词元化
字典词元化(Word Tokenization)是通过建立词汇表,将文本中的词与词汇表中的词一一对应。这种方法在构建模型时需要一个包含所有可能单词的词汇表,然后将每个单词映射到词汇表中的索引。
举个例子:假设我们有如下文本:
文本:"I love machine learning."
词汇表可能是:
{"I": 1, "love": 2, "machine": 3, "learning": 4}
根据这个词汇表,词元化的结果是:
[1, 2, 3, 4]
然而,构建完整的词汇表有一个巨大的缺点,即词汇表需要包含所有可能的单词,而这在实际应用中几乎是不可能的,尤其是对于开放域的应用场景,可能会遇到许多词汇表中不存在的新词。
子词词元化方法
子词词元化是一种有效处理词汇表爆炸问题的方法,它将单词拆分为更小的子单元。它能够通过自动学习来平衡词汇表的大小和词元的表达能力。两种常见的子词词元化方法是 BPE(Byte-Pair Encoding)和 WordPiece。
BPE (Byte-Pair Encoding)
BPE 是一种基于合并频率的子词词元化方法,最早用于数据压缩,后来被引入到 NLP 中。BPE 的思想是,从每个字符开始,逐渐合并最常见的字符对,从而形成子词单元。
举个例子:
假设我们有以下单词:
文本:"lower, lowest"
初始词元是单个字符:
[l, o, w, e, r], [l, o, w, e, s, t]
BPE 算法会重复合并频率最高的字符对,例如合并 "l"
和 "o"
,得到新的词元 "lo"
,继续合并直到达到预定的词汇量大小。
在 Python 中可以使用 tokenizers
库实现 BPE 分词:
from tokenizers import Tokenizer
from tokenizers.models import BPE
from tokenizers.trainers import BpeTrainer
# 初始化分词器
tokenizer = Tokenizer(BPE())
trainer = BpeTrainer(vocab_size=100, min_frequency=2)
tokenizer.train(trainer, ["lower lowest"])
# 对文本进行词元化
tokens = tokenizer.encode("lowest")
print(tokens.tokens)
WordPiece
WordPiece 是一种类似于 BPE 的子词词元化方法,最初由谷歌用于其神经机器翻译系统,后来被 BERT 等模型采用。WordPiece 的核心思想是通过最大化语言模型的似然函数来学习词汇表。在实际使用中,WordPiece 会将低频词拆分为常见的子词单元,这样可以保持词汇表的合理大小,同时能够有效应对未登录词。
一个简单例子是将单词 "unhappiness"
拆分为:
["un", "##happiness"]
这里的 ##
表示这是一个子词,而不是一个独立的词。
Hugging Face 的 transformers
库中可以使用内置的分词器对文本进行 WordPiece 分词:
from transformers import BertTokenizer
tokenizer = BertTokenizer.from_pretrained("bert-base-uncased")
text = "unhappiness"
tokens = tokenizer.tokenize(text)
print(tokens)
输出:
['un', '##happiness']
字符级词元化
字符级词元化是最细粒度的词元化方法,将文本中的每个字符作为一个词元。与其他词元化方法相比,字符级词元化的粒度非常小,因此可以处理 OOV(Out-of-Vocabulary)问题,因为所有词汇最终都可以分解为字符。然而,字符级词元化的方法也有其缺点,主要是产生的序列会非常长,这可能使模型训练变得困难。
举个例子:
文本:"GPT"
字符级词元化的结果是:
["G", "P", "T"]
在 Python 中可以通过简单的列表表达式实现:
text = "GPT"
tokens = [char for char in text]
print(tokens)
输出:
['G', 'P', 'T']
SentencePiece
SentencePiece 是一种无需空格的词元化方法,特别适用于无明确空格分隔符的语言(例如中文和日文)。SentencePiece 可以看作是 BPE 和 WordPiece 的结合。它通过直接对原始文本(包括空格)进行建模,不需要任何预处理步骤(如空格分词)。
例如,对于句子 "I saw a girl"
,SentencePiece 可能会将其分为:
['▁I', '▁saw', '▁a', '▁girl']
这里的 ▁
表示空格,代表词的开头。通过这种方式,SentencePiece 能够同时处理包含空格和不包含空格的语言。
可以使用 sentencepiece
库进行词元化:
import sentencepiece as spm
# 训练模型
spm.SentencePieceTrainer.train(input='data.txt', model_prefix='m', vocab_size=5000)
# 加载模型并分词
sp = spm.SentencePieceProcessor(model_file='m.model')
text = "I saw a girl"
tokens = sp.encode(text, out_type=str)
print(tokens)
词元化方法的优缺点总结
不同的词元化方法各有优缺点。在选择词元化方法时,需要根据具体的应用场景和模型的特性来进行权衡。
-
基于空格的词元化:简单易懂,适用于标点符号较少且语言结构清晰的文本,但在多语言环境中表现不佳,尤其是无法处理未登录词。
-
字典词元化:适合有一个明确词汇表的小型应用,但难以应对开放域任务和新词的问题。
-
BPE 和 WordPiece:通过子词分解有效控制词汇表大小,并能够处理未登录词问题。BPE 更强调频率合并,而 WordPiece 强调最大化语言模型的概率。
-
字符级词元化:能够完全解决未登录词问题,但由于粒度过小,导致序列长度过长,增加了模型训练的复杂度。
-
SentencePiece:结合了 BPE 和 WordPiece 的优点,适合对包含空格和非空格语言同时建模,具有较强的通用性。
真实案例研究:GPT 模型中的词元化
为了更好地理解词元化在实际应用中的重要性,我们可以回顾 GPT 模型是如何利用词元化方法的。GPT-3 模型使用了一种结合 BPE 和自定义词汇表的词元化方法,使得它能够高效处理多种语言和文本类型。
例如,GPT-3 的词汇表大小为 50257 个词元,这些词元包含了常见的单词和子词,因此模型在面对大量未登录词时依然能够表现出色。这种词元化方法的优势在于可以灵活处理不同的词形变化和稀有词,避免了因 OOV 词汇导致的模型性能下降。
在 GPT-3 的训练中,如果遇到非常长且复杂的单词,这个单词会被拆分为若干子词,而这些子词的组合仍然可以保持原始单词的含义。例如,单词 "unbelievably"
可能被分为 "un"
, "believ"
, 和 "ably"
。这种词元化方式能够有效减少词汇表的大小,同时确保模型可以理解罕见词汇的组成部分。
代码示例:实现一个简单的 BPE 分词器
为了更好地理解 BPE 词元化的原理,我们可以实现一个简单的 BPE 分词器。下面的 Python 代码演示了如何通过 BPE 来对文本进行词元化:
from collections import Counter, defaultdict
def get_vocab(corpus):
vocab = Counter()
for word in corpus:
vocab[' '.join(word) + ' </w>'] += 1
return vocab
def get_stats(vocab):
pairs = defaultdict(int)
for word, freq in vocab.items():
symbols = word.split()
for i in range(len(symbols) - 1):
pairs[(symbols[i], symbols[i + 1])] += freq
return pairs
def merge_vocab(pair, vocab):
bigram = ' '.join(pair)
new_vocab = {}
for word in vocab:
new_word = word.replace(bigram, ''.join(pair))
new_vocab[new_word] = vocab[word]
return new_vocab
# 示例语料库
corpus = ["low", "lowest", "newer", "wider"]
vocab = get_vocab(corpus)
# BPE 合并步骤
num_merges = 10
for i in range(num_merges):
pairs = get_stats(vocab)
if not pairs:
break
best = max(pairs, key=pairs.get)
vocab = merge_vocab(best, vocab)
print(f"Step {i+1}: {best}")
print(vocab)
这个代码展示了 BPE 的基本工作流程:从每个字符开始,通过合并频率最高的字符对,逐渐生成新的子词单元。在实际应用中,BPE 的计算过程可能会更加复杂,但这个示例有助于理解其核心思想。
省流版
词元化是自然语言处理任务中不可或缺的步骤,其重要性体现在如何将复杂的文本信息转化为模型可以理解和处理的数值表示。不同的词元化方法适用于不同的场景和需求,从简单的基于空格的方法,到复杂的子词方法如 BPE、WordPiece,再到无空格的 SentencePiece,各有其特点和应用场景。
在未来的 NLP 应用中,词元化方法可能会继续演变,以更好地适应多语言环境和开放域的需求。
- 点赞
- 收藏
- 关注作者
评论(0)