Transformer 架构概览
2017 年,谷歌的研究团队发表了具有里程碑意义的论文《Attention is All You Need》,首次提出了 Transformer 模型。这一创新架构极大地推动了自然语言处理(NLP)技术的发展,成为后续如 Generative Pre-trained Transformer(GPT),Pathways Language Model(PaLM)等大型语言模型(LLM)开发的基石,彻底改变了之前依赖传统神经网络,比如 Recurrent Neural Network(RNN)及其变种 Long Short-Term Memory(LSTM)和 Gated Recurrent Unit(GRU)的研究方向。
RNN 面临的挑战
循环神经网络(RNN)是神经网络(NN)的一种特别设计,专门用于处理按顺序排列的数据,比如文本、音频、时间序列等。它的独到之处在于引入了'记忆'功能,让网络能记住之前输入的信息。这种记忆功能在处理需要理解上下文的任务时显得尤为重要,比如在 NLP 中,语义的理解和生成过程。
如果从视觉角度描述,一个标准的 RNN 结构看起来就像是一个计算单元,它通过一个自连接的隐藏状态进行信息循环,让信息能够跨时间步(St)传递:

随着数据在 RNN 中的流动,之前时间步的激活状态会作为输入参与到当前数据的处理中,让模型能够动态地融合时间上下文和序列的历史信息。这一点对于很多序列到序列(Seq2Seq)的预测任务尤为关键。
RNN 及其变种 LSTM 和 GRU 曾是序列模型的核心,专为顺序处理数据和捕捉时间序列依赖而设计。不过,它们面临几个关键挑战,这些挑战限制了其效能和效率:
难以理解长期关联:
- 梯度消失问题:在反向传播时,RNN 面临梯度逐渐减小直至消失的问题,这使得模型难以学习序列中远距离元素间的关系。
- 梯度爆炸问题:另一方面,梯度可能会急剧增加,引发梯度爆炸问题,这会破坏学习过程的稳定性。
顺序处理的局限:
- 固有的顺序处理机制限制了并行处理的可能性,导致处理长序列时训练和推理速度缓慢。
计算和内存负担:
- 高计算需求:RNN,LSTM 和 GRU 因其复杂结构而计算量巨大,这些结构旨在解决梯度消失问题。
- 内存限制:维护长序列的隐藏状态需要大量内存,这对扩展性构成了挑战。
想象一个简单的任务,RNN 需要预测句子中的下一个单词。如果 RNN 在尝试预测之前只看到了一个词,那么它猜对的可能性不高。如果我们通过让它观察更多之前的词来提高其预测能力,我们就需要更多的计算资源。但即使有了更多资源和数据,RNN 依然面临困难,因为它需要完整理解整个句子乃至整篇文章来准确预测。仅依赖观察前几个词是不够的,它需要全面理解上下文。
Transformer 架构优势
Transformer 模型的推出,正是 Vaswani 在其里程碑式的论文《Attention is All You Need》中所做的工作,这一创新不仅突破了传统限制,还为解决 RNN 面临的问题提供了解决方案。

Transformer 架构包括编码器和解码器两部分,每部分都含有多个层,这些层集成了多头自注意力机制和前馈神经网络,共同工作以提升处理效率和性能。
Transformer 的工作方式是同时处理句子中的全部词序,而不是像 RNN 那样,一次只处理一个词。这种处理机制让 Transformer 在捕捉句子内词语之间的上下文关系和相互作用方面更为出色,对于理解人类语言来说,这一点极为关键。
此外,Transformer 采用了一种名为自注意力(self-attention)的技术,能够对句中各词赋予不同的权重,并集中关注对完成特定任务最关键的词语。正是这种机制,使得 Transformer 能够灵活应对各种任务,并且达到非常高的准确度。
RNN vs Transformer
Transformer 解决了 RNN 的几个关键问题,并提供了更高效和更强大的解决方案:
并行处理与效率提升:
- 自注意力机制:与 RNN 不同,Transformer 通过自注意力机制评估输入数据各部分的重要性,实现对序列更加精细的理解。
- 并行化能力:Transformer 架构支持数据的并行处理,大幅提高了训练和推理速度。
解决长期依赖问题:
- 全局上下文感知:得益于自注意力机制,Transformer 能够同时处理整个序列,有效地捕捉长期依赖,避免了顺序处理的限制。
可伸缩性与灵活性:
- 降低内存需求:通过去除循环连接的需求,Transformer 减少了内存使用,提高了模型的可伸缩性和效率。
- 高度适应性:Transformer 的架构包含了堆叠的编解码器,这种设计使其不仅在自然语言处理领域,在计算机视觉和语音识别等多个领域也能发挥出色的适应性。
| 特性 | RNN | Transformer |
|---|
| 处理方式 | 顺序处理 | 并行处理 |
| 长上下文理解 | 难以捕捉长依赖 | 通过自注意力机制捕捉长依赖 |
| 可伸缩性 | 差 | 好 |
| 应用领域 | 自然语言处理 | 自然语言处理、计算机视觉、语音识别等 |
| 训练速度 | 慢 | 快 |
自注意力机制
注意力机制的精髓在于,它让模型能够在执行任务时专注于输入序列的不同部分,正如人们在处理信息时会更加关注某些特定的词或物体。这一机制极大地提升了模型把握数据上下文和内在联系的能力。
注意力机制基于查询(Queries),键(Keys),值(Values)三个向量运作,这些向量源自输入数据,查询和键的相互作用决定了模型对输入的各个部分的关注程度,而值则承载了实际要处理的信息。
注意力权重用来评估输入数据各部分之间的相关性,指导模型应更多关注哪些部分。
自注意力,作为一种特殊的注意力机制,让模型能够评估输入数据各部分之间的相对重要性。它是 Transformer 架构的基础,使得模型能够高效地并行处理数据序列,这是之前按顺序处理数据的模型所不具备的。

自注意力实现了输入数据所有部分的同时处理,显著提升了训练速度和效率。而且,它能够无视位置距离,捕捉序列元素间的关系,克服了如 RNN 和 LSTM 这类早期模型的主要局限。
模型不仅关注每个单词,还会评估每个单词与其他单词之间的关系,并对这些关系赋予注意力权重,从而学习每个单词与其他单词之间的相关性。
注意力及其映射
假设你在看一张公园里有许多狗和人的图片,如果要你找出所有黄色的球,你的大脑会自动聚焦于可能有黄色球的地方,忽略掉大部分狗和人。这种聚焦机制类似于机器学习中的注意力机制,它帮助模型专注于对当前任务(本例中为黄色球)重要的数据部分,而忽略不相关的信息(如狗和人)。
注意力图类似于一个展示你在寻找黄色球时关注点的地图,它会突出显示黄色球的区域并淡化其他部分。在机器学习中,注意力图直观地显示了模型在数据中的关注焦点,以做出决策或预测。因此,在上述示例中,注意力图会突出显示模型认为对找到黄色球重要的输入部分,帮助我们理解模型做出决策的逻辑。
机器学习模型把数字视为语言,但不直接理解文字,因此我们需要通过标记化过程将文字转换为数字,即为每个单词分配一个基于模型已知的所有单词列表的唯一数字,使模型能够理解和处理文本。
文本输入被数字化后,就可以传递给嵌入层,将每个单词(或 Token)转换为一个称为向量的数字列表。这些向量通过训练过程调整,随着模型从数据中学习,调整这些数字以提高其任务执行能力,这个过程称为'可训练的向量嵌入(Embedding)',是使计算机能够理解和从中学习的文字表示方式。

一旦得到嵌入,就可以将其传递给自注意力层,该层分析输入序列中标记之间的关系,优化模型对数据的处理和理解能力。
多头自注意力

设想你在一个拥挤的公交车上,努力听朋友讲故事,你的大脑能自动关注到他所说的关键词,同时留意周围的环境声,譬如有人叫你的名字或是公交车到站播报声。
多头自注意力机制在计算机中实现了类似的功能。它使得模型能够同时关注句子或图像的多个部分,不仅抓住主旨,同时也能从多角度捕捉上下文和微妙之处。
这种技术仿佛为机器学习模型装配了一系列专业的'透镜',每个'透镜'或'头'关注数据的不同维度。因此,在 Transformer 模型中,多头自注意力赋予了模型深入理解输入信息的能力。
这项技术的作用远不止于识别句子中的下一个词汇;它涉及到把握整个句子的含义,理解各个词汇间的关联,甚至识别讽刺或强调等语言细节。这让 Transformer 在执行语言翻译、文章摘要编写或生成逼真文本等任务上展现出了强大的能力。
在训练期间,模型通过学习并储存在各层的自注意力权重来识别输入序列中每个词对于序列中其他所有词的重要性。这个过程不止进行一次。实际上,模型会并行地学习多组自注意力权重,也就是所谓的'头',而这些头彼此之间是独立的。
预测过程
在机器学习模型中,自注意力机制负责捕捉语言的各种特性,每个部分关注不同的语言特征。比如,某部分可能探寻句子中字符间的关系,另一部分关注动作发生的情况,还有的部分可能识别单词的发音相似性。有趣的是,这些自注意力'头'的关注点并非预设,而是从随机开始,通过处理大量数据并自我学习,自然而然地识别出各种语言特征。它们学习到的一些特征我们能够理解,如前所述的例子,但有些则更加难以捉摸。
模型对输入数据应用了所有这些注意力机制后,接下来会通过一个全连接层进行处理,生成一个与模型词汇表中每个词作为下一词出现的可能性相关的数值列表(即 logits)。随后,这些数值通过 softmax 层转换成概率,给模型词汇表中的每个单词一个表示其作为下一词出现概率的分数。虽然会有成千上万个这样的分数,但通常情况下,某个单词的分数会比其他单词高,成为模型预测下一词的首选。
词嵌入技术
词嵌入(Word embedding)也有人叫做词向量,本文统一称做词嵌入。它是 Transformer 的初始输入形式。
词嵌入介绍
众所周知,计算机是用数字来思考的,无法自动掌握单词和句子的意义,如果我们想让计算机理解自然语言,就需要把这些信息转换成计算机能够处理的格式,即数字向量。
人类很早以前就学会了怎样把文本转换为机器能理解的格式,其中最早的一种格式是 ASCII,这种方式虽然在文本渲染和传输方面有效,但却无法传递词汇的深层含义,那个时代,标准的搜索技术是基于关键词的搜索,即查找包含特定单词或词组的所有文档。
词嵌入技术的出现彻底改变了这一格局,通过将单词、句子甚至图像转化为数字向量,它不仅仅改善了文本的表示方式,更重要的是,它捕捉到了语言的本质和丰富的语义;这一创新使得语义搜索成为可能,让我们能够精准地理解和分析不同语言的文档,通过探索这些高级的数值表示形式,我们能够洞察计算机是如何开始理解人类语言的细微差别的,这一进步正在改变我们在数字时代处理信息的方式。
今天,词嵌入技术也是 LLM 的核心技术之一,也是 Transformer 的初始输入形式。

词嵌入的实现方法
稀疏表示法(Sparse Representations)
词袋模型
文本转换成向量的一种基础方法是词袋模型,它将文档视为一组不考虑顺序的单词集合。
词袋模型中,每个单词都被视为一个'词元',文档中的每个词元都被赋予一个唯一的数字 ID,然后,我们可以使用一个向量来表示文档,其中向量的每个维度代表一个词元,向量的每个元素表示该词元在文档中出现的次数。
我们可以利用 NLTK Python 库来完成这一任务,如下是费曼的一句著名引语生成的词袋模型的词嵌入,'We are lucky to live in an age in which we are still making discoveries'(我们很幸运生活在一个仍在不断发现新事物的时代)。

代码实现:
from nltk.stem import SnowballStemmer
from nltk.tokenize import word_tokenize
import collections
text = 'We are lucky to live in an age in which we are still making discoveries'
words = word_tokenize(text)
print(words)
stemmer = SnowballStemmer(language = "english")
stemmed_words = list(map(lambda x: stemmer.stem(x), words))
print(stemmed_words)
bag_of_words = collections.Counter(stemmed_words)
print(bag_of_words)
词频 - 逆文档频率(TF-IDF)
相比词袋方法,TF-IDF(词频 - 逆文档频率)是一种略有改进的版本,它通过两个指标的乘积来实现。

词频(TF)是衡量一个词在文档中出现频率的基本指标,它是评价单词在文档内重要性的简单方法;计算词频时,我们会计算特定词汇在文档中出现的次数,并将其除以文档中的总词数,这样做的目的是为了消除不同文档长度带来的影响。

逆文档频率(IDF)是一个用来评估一个词在整个文档集或语料库中重要性的指标,它帮助我们了解一个词在所有文档中是常见的还是罕见的。
计算这个指标时,我们会用语料库中的文档总数除以含有该词的文档数,然后取这个比值的对数;这样的计算方法降低了那些在众多文档中频繁出现的词的重要性,认为这些词相对不太重要;这个计算过程有助于我们更好地理解和评估不同词汇在文档集中的独特性和重要性。

代码实现:
from sklearn.feature_extraction.text import TfidfVectorizer
def compute_tfidf(documents):
vectorizer = TfidfVectorizer()
return vectorizer.fit_transform(raw_documents=documents)
if __name__ == "__main__":
docs = [
"I love natural language processing",
"In natural language processing, the sentences are represented as embeddings or vectors",
"The distance between the embedding vectors gives the contextual meaning between them"
]
result = compute_tfidf(documents=docs)
print(result.toarray())
密集向量表示法(Dense Vector Representations)
Word2Vec
Word2Vec 是一种划时代的技术,它通过神经网络生成词嵌入,能够捕捉单词间的语义关系,更真实地反映出单词在语言中的使用和意义。
它通过在大规模文本语料库上的训练,能够理解单词间复杂的关系,如同义词、反义词和关联词,这些都是通过向量空间的几何属性来实现的。
Word2Vec 使用一个简单的双层神经网络来从大量文本中学习单词之间的联系,其设计基于假设出现在相似语境中的单词在语义上是相似的,这一原则主要通过两种训练算法实现:连续词袋(CBOW)和 Skip-Gram,它们主要在处理单词上下文的方法上有所区别。

CBOW 通过给定的上下文来预测目标单词。上下文是指目标单词周围的单词窗口,例如,在句子'The cat sits on the mat'中,若以'sits'为目标词,并选择周围各 2 个单词作为窗口,上下文就是['The', 'cat', 'on', 'the', 'mat']。
它以上下文中的单词为输入,合并它们的向量,然后使用这个综合向量来预测目标单词,并通过调整单词向量来减少预测目标单词时的误差。
Skip-Gram 采取与 CBOW 相反的逻辑,它使用目标单词来预测周围的上下文单词,以'sits'为目标单词,其目标是预测['The', 'cat', 'on', 'the', 'mat']这样的上下文单词。
它针对每一个目标单词,使用它的向量来预测特定范围内的上下文单词向量,其目标是调整单词向量,以提高上下文单词预测的准确率。
从性能方面来说,CBOW 运行更快,对常见词的准确性略优于 Skip-Gram;而 Skip-Gram 虽然运行较慢,但对于不常见的词汇和小型数据集来说,表现更佳,因为它为每个单词提供了更多的训练样本。
从训练目标来说,CBOW 通过平均上下文单词向量,模糊了一些分布信息,这对于表示高频词汇较为有利;而 Skip-Gram 将每一对上下文 - 目标视为独立的观察,因此它能够更好地捕捉各种关系,尤其是对于低频词汇。
代码实现:
from gensim.utils import simple_preprocess
from gensim.models import Word2Vec
from sklearn.manifold import TSNE
import matplotlib.pyplot as plt
def compute(documents):
processed_docs = [simple_preprocess(document) for document in documents]
model_cbow = Word2Vec(sentences=processed_docs, window=5, vector_size=100, workers=5, min_count=1, sg=0)
model_skip_gram = Word2Vec(sentences=processed_docs, window=5, vector_size=100, workers=5, min_count=1, sg=1)
vector_cbow = model_cbow.wv['language']
vector_skipgram = model_skip_gram.wv['language']
return model_cbow, model_skip_gram
def visualize(model: Word2Vec):
word_vectors = model.wv.vectors
words = model.wv.index_to_key
tsne = TSNE(n_components=2, random_state=)
word_vectors_2d = tsne.fit_transform(word_vectors)
plt.figure(figsize=(, ))
i, word (words):
plt.scatter(word_vectors_2d[i, ], word_vectors_2d[i, ])
plt.text(word_vectors_2d[i, ] + , word_vectors_2d[i, ] + , word, fontsize=)
plt.show()
__name__ == :
sentences = [
,
,
,
,
,
,
,
,
,
]
cbow_model, skip_gram_model = compute(documents=sentences)
visualize(cbow_model)
词嵌入可视化:

GloVe
GloVe(全局词向量表示)是斯坦福大学研究人员开发的一种无监督学习模型,专门用于创建单词的向量表示;不同于传统模型那样仅仅基于单词共现次数,GloVe 模型通过考虑这些次数的比率来揭示单词之间的语义联系,这一点使其能够同时捕捉语言的局部和全局统计特性。
GloVe 的核心优势在于它能够通过分析词共现的概率来识别语义关系,采用一种混合方法结合了全局矩阵分解和局部上下文窗口技术,为词汇提供全面的表示。
此外,其可扩展性强,能够处理大规模语料库和庞大的词汇量,非常适合于分析网络级别的数据集。
代码实现:
from gensim.scripts.glove2word2vec import glove2word2vec
from gensim.models import KeyedVectors
glove_input_file = 'glove.6B.100d.txt'
word2vec_output_file = 'glove.6B.100d.word2vec.txt'
glove2word2vec(glove_input_file, word2vec_output_file)
model = KeyedVectors.load_word2vec_format(word2vec_output_file, binary=False)
word_vector = model['language']
print(word_vector)
print(model.most_similar('language'))
OpenAI Transformer 嵌入
OpenAI 能够生成信息丰富的密集向量(Dense Vector),并成为现代语言模型的主流技术。
OpenAI 允许你使用同一个'核心'模型,并根据不同的使用案例进行微调,无需重新训练核心模型(这会耗费大量时间和成本),这促成了预训练模型的兴起;这些模型属于 GPT 系列,包括 GPT-3 及其最新迭代,这些都可以通过 OpenAI 的 API 获得。
Google AI 的 BERT(Bidirectional Encoder Representations from Transformers)是首批流行模型之一,text-embedding-3-small 和 text-embedding-3-large 是最新也是性能最强的嵌入模型,它们引入新的参数,允许用户控制模型的整体大小。
代码实现:
from openai import OpenAI
client = OpenAI()
def get_embedding(text, model="text-embedding-3-small"):
text = text.replace("\n", " ")
return client.embeddings.create(input = [text], model=model)
.data[0].embedding
get_embedding("We are lucky to live in an age in which we are still making discoveries.")
词嵌入相似度计算方法
词嵌入本质上是向量,因此,如果我们想了解两个单词或句子在语义上的接近程度,我们可以计算向量之间的距离,距离越小,语义意义上越接近。
有几种不同的度量方法可以用来衡量两个向量之间的距离:
- 欧几里得距离(L2)
- 曼哈顿距离(L1)
- 点积
- 余弦距离
下面我们将讨论这些方法,作为一个简单的例子,我们将使用两个二维向量 A=(1,4) 和 B=(2,2)。
欧几里得距离(L2)
定义两点(或向量)之间距离的最标准方法是欧几里得距离,也称为 L2 范数,它是我们日常生活中最常用的度量方法,例如,当我们谈论两个城镇之间的距离时。

代码实现:
import numpy as np
sum(list(map(lambda x, y: (x - y) ** 2, vector1, vector2))) ** 0.5
np.linalg.norm((np.array(vector1) - np.array(vector2)), ord = 2)
曼哈顿距离(L1)
曼哈顿距离,也称为 L1 范数。这个名称来源于纽约的曼哈顿,因为这个岛有一个街道网格布局,而在曼哈顿两点之间的最短路线将是 L1 距离,因为你需要遵循街道网格。

代码实现:
sum(list(map(lambda x, y: abs(x - y), vector1, vector2)))
np.linalg.norm((np.array(vector1) - np.array(vector2)), ord = 1)
点积
点积或标量积,它的公式如下。

这个度量有点难以解释,一方面,它表明了向量是否指向同一方向;另一方面,结果很大程度上依赖于向量的大小;
例如,让我们计算两对向量之间的点积:
对于向量 A=(1,1) 和 B=(1,1),它们的点积计算结果是 1∗1+1∗1=2。
当我们考虑另一对向量 C=(1,1) 和 D=(10,10) 时,点积为 1∗10+1∗10=20。
尽管这两对向量在方向上保持一致,但由于它们的大小存在差异,导致它们的点积有很大的不同。这就解释了为什么在进行向量比较时,点积并非总是最合适的选择。
代码实现:
sum(list(map(lambda x, y: x*y, vector1, vector2)))
np.dot(vector1, vector2)
余弦距离

余弦距离,不同于点积,它衡量的是两个向量之间的角度差异,而非它们的长度,这使得它特别适用于比较文档或句子的相似性,因为它不会受到文档长度的影响。
余弦相似度的值范围是从 -1 到 1,值越接近 1,表示两个向量的方向越相似;值为 0,则表示它们是正交的;而值为 -1,则表示它们方向完全相反。
通过计算向量之间的余弦相似度,我们可以更精确地评估它们在语义上的相似度,而不仅仅是比较它们的数值大小或直线距离。
代码实现:
dot_product = sum(list(map(lambda x, y: x*y, vector1, vector2)))
norm_vector1 = sum(list(map(lambda x: x ** 2, vector1))) ** 0.5
norm_vector2 = sum(list(map(lambda x: x ** 2, vector2))) ** 0.5
dot_product/norm_vector1/norm_vector2
from sklearn.metrics.pairwise import cosine_similarity
cosine_similarity(
np.array(vector1).reshape(1, -1),
np.array(vector2).reshape(1, -1))[0][0]
分词(Tokenizer)
分词是 Transformer 架构中 Embedding 之前非常重要的一步。

它将文本拆分为模型或机器可以处理的小单位,并分配一个唯一的标识符 (Token);就像我们学习外语时,首先要学习字母和单词一样,分词是模型或机器学习语言的第一步。


分词可视化工具,请访问 https://tiktokenizer.vercel.app/
分词流程
分词的流程通常包括 Normalization,Pre-tokenization,Model 和 Post-tokenization。

Normalization
Normalization 主要包括以下几个方面:
- 文本清洗:
- 去除无用字符:移除文本中的特殊字符、非打印字符等,只保留对分词和模型训练有意义的内容。
- 去除额外空白:消除文本中多余的空格、制表符、换行符等,统一文本格式。
- 标准化写法:
- 统一大小写:将所有文本转换为小写或大写,减少大小写变体带来的影响。
- 数字标准化:将数字统一格式化,有时候会将所有数字替换为一个占位符或特定的标记,以减少模型需要处理的变量数量。
- 编码一致性:
- 字符标准化:确保文本采用统一的字符编码(如 UTF-8),处理或转换特殊字符和符号。
- 语言规范化:
- 词形还原(Lemmatization):将单词还原为基本形式(lemma),例如将动词的过去式还原为一般现在时。
- 词干提取(Stemming):去除单词的词缀,提取词干,这是一种更粗糙的词形还原方式。
Pre-tokenization
Pre-tokenization 是基于一些简单的规则(如空格和标点)进行初步的文本分割,这一步骤是为了将文本初步拆解为更小的单元,如句子和词语;对于英文等使用空格分隔的语言来说,这一步相对直接,但对于中文等无空格分隔的语言,则可能直接进入下一步。
Model
Model 是分词的核心部分,在 Pre-tokenization 的基础上,根据选定的模型或算法(BPE,WordPiece,Unigram 语言模型(LM)或 SentencePiece 等)进行更细致的处理,包括通过大量文本数据,根据算法规则生成词汇表 (Vocabulary),然后依据词汇表,将文本拆分为 Token。
在自然语言处理模型中,确定合适的词汇表大小是一个关键步骤,它直接影响模型的性能、效率以及适应性;理想的词汇表应该在保证模型性能和效率的同时,满足特定任务和数据集的需求。
较大的词汇表意味着模型需要更多的计算资源来处理和存储分词嵌入,在资源有限的环境下,过大的词汇表可能导致训练和推理过程变得低效。
词汇表的大小也会影响模型的处理速度,较大的词汇表可能增加模型在进行词嵌入查找和生成输出时的计算负担,从而减慢处理速度。
较大的词汇表可以提高模型覆盖不同词汇和表达的能力,有助于模型更好地理解和生成文本;然而,过大的词汇表也可能导致模型在某些 Token 上的训练不足,影响其泛化能力。
一个庞大的词汇表可能导致词嵌入空间的稀疏性问题,使得模型难以有效学习某些较少见的 Token 的表示。
不同的自然语言处理任务可能需要不同大小的词汇表。例如,精细的文本生成任务可能需要较大的词汇量以覆盖更多细节,而一些分类任务则可能只需较小的词汇表即可达到较高性能。
特定任务可能需要引入特殊 Token(如控制代码,隐私标记等,这些在 LLM 如何通过监督学习使用 Tools 中有应用到),这也需要在设置词汇表时考虑。
不同语言的结构差异意味着对词汇表的需求也不同。例如,拼接语(如德语)可能需要更大的词汇量来覆盖其丰富的复合词形态。
数据集中文本的复杂性和多样性也影响词汇表的设置,丰富多变的数据集可能需要更大的词汇量来捕获文本的多样性。
如上所述,在实际应用中,可能需要通过实验和调整来找到最适合特定模型和任务的词汇表大小,下面是各大 LLM 词汇表的大小和性能对比:


Post-tokenization
Post-tokenization 主要包括:
- 序列填充与截断:为保证输入序列的长度一致,对过长的序列进行截断,对过短的序列进行填充。
- 特殊 Token 添加:根据模型需求,在序列的适当位置添加特殊 Token(如 [CLS], [SEP])。
- 构建注意力掩码:对于需要的模型,构建注意力掩码以区分实际 Token 和填充 Token。
分词算法
Tokenizer 按粒度分,常见的有:
- 按单词划分(word base):按照词进行分词,如英文 Today is sunday. 则根据空格或标点进行分割 [today, is, sunday, .]
- 按字符划分(character base):按照单字符进行分词,就是以 char 为最小粒度,如英文 Today is sunday. 则会分割成 [t, o, d, a, y, i, s, s, u, n, d, a, y, .]
- 按子单词划分(subword tokenization):按照词的 subword 进行分词,如英文 Today is sunday. 则会分割成 [to, day, is , s, un, day, .]
按单词划分简单易理解,每个 word 都分配一个 ID,所需的词汇表会根据语料库大小而不同,而且这种分词方式,会将两个本身意思一致的词分成两个毫不同的 ID,在英文中尤为明显,如 cat,cats。
按字符划分,此种现象会有所减缓,而且词汇表相对小的多,但分词后的每个 char 字符是毫无意义的,而且输入的长度变长不少,只有合并后才有意义,这种分词在模型的初始 character embedding 是无意义的,英文中尤为明显,但中文是较为合理的,在中文中用得比较多。
最后,为了平衡以上两种方法,提出了 subword tokenization,典型的有 Byte Pair Encoding (BPE),Wordpiece,Unigram 和 SentencePiece。
BPE
BPE 最初是作为一种数据压缩技术提出的,后来被应用于自然语言处理领域,特别是在 LLM 的 Tokenizer 过程中;BPE 的核心思想是通过迭代合并文本数据中最频繁出现的字符或字符序列来动态构建词汇表;这个过程从字符级别开始,逐渐构建出更长的词汇或短语表示,直到达到预设的词汇表大小或合并次数为止。
BPE 标记化的主要步骤如下:
- 初始化:以单个字符初始化词汇表的元素。
- 统计共现频率:计算所有相邻字符对的共现频率。
- 合并操作:选择最频繁出现的字符对,将它们合并为新的词汇表项。
- 重复:重复上述过程,直到达到预设的合并次数或词汇表大小。
OpenAI 从 GPT2 开始就是使用的这种分词方式。
WordPiece
WordPiece 在选择要合并的字符或词对时采取了更为复杂的策略;它不仅计算这些组合的频率,还考虑了合并后带来的概率增益。
这意味着 WordPiece 在构建词汇表时,会更倾向于选择那些能够显著提高文本表示准确性的单元。Google 的 BERT 模型就是采用了 WordPiece 作为其分词器,这也是 BERT 在多种语言任务上表现出色的原因之一。
Unigram
Unigram 方法采用的是一种概率统计的方式,它会预测每个单词作为独立单元出现的概率(基于朴素贝叶斯统计),并基于这个概率来进行分词。这个过程中,某些词可能会被拆分成更小的单元,以便模型可以更灵活地处理语言中的变化和新词。
Unigram 的一个关键优势是其能够自动适应不同语言的特性,使得模型在处理多语言文本时更加高效,它在 GPT-1 中被使用。
总结一下:
- BPE 是在每次迭代中只使用出现频率来识别最佳匹配,直到达到预定义的词汇量大小。
- WordPiece 类似于 BPE,使用频率出现来识别潜在的合并,但根据合并词前后分别出现的可能性概率大小,进行是否合并。
- Unigram 没有频率出现的概率模型进行直接分词,相反,它使用概率模型训练语言模型(LM),移除使得最大似然概率减小最小的子词,然后进行反复计算,达到最大似然概率。

SentencePiece
SentencePiece 是 Google 推出的 subword 开源工具包,集成了 BPE 和 Unigram;除此之外,SentencePiece 还能支持字符和词级别的分词。
SentencePiece 主要解决了以下三点问题:
- 以 unicode 方式编码字符,将所有的输入(英文、中文等不同语言)都转化为 unicode 字符,解决了多语言编码方式不同的问题。
- 将空格编码为'',如'New York' 会转化为['', 'New', '_York'],这也是为了能够处理多语言问题,比如英文解码时有空格,而中文没有,这种语言区别。
- 优化了速度,如果您实时处理输入并对原始输入执行标记化,则速度会太慢。SentencePiece 通过使用 BPE 算法的优先级队列来加速它来解决这个问题,以便您可以将它用作端到端解决方案的一部分。
如果任务涉及到多语言处理,特别是包含无空格分隔的语言,SentencePiece 可能是一个好的选择。对于希望在词汇丰富度和处理未知词上取得平衡的项目,BPE 或 WordPiece 可能更加合适。而如果项目特别注重于统计特性和语言模型的精确性,Unigram 或许能提供更好的支持。
总结
分词是 Transformer 架构或者 NLP 中非常重要的一步,选择合适的分词方法对于模型非常重要。
随着 NLP 技术的不断进步,Tokenizer 技术也在持续发展中,未来可能将不仅仅是要将文本转换为模型可理解格式,而是要扩展到语音和图像特征级别的数据,将更加智能化,包括细粒度,跨模态,自适应和可学习的 Tokenizer 技术。
同时,随着对 AI 伦理和隐私保护的重视,未来的 Tokenizer 技术将更加注重安全性和隐私保护;这可能包括开发新的技术来匿名化敏感信息、防止数据泄露,并确保在模型训练和推理过程中保护用户隐私。