引言
想象一下,你是一名软件工程师,正在参与一个令人兴奋的项目,并且正在寻找一种编程语言来帮助你快速高效地创建软件。你听说了一种革命性的新编程语言,它被称为编程语言中的瑞士军刀:这种语言在创建机器学习(ML)模型时最为高效——此外,它还能比其他Web开发框架更快地创建出色的网站,并且支持硬件编程。此外,它在网络编程和其他相关任务中的表现也非常出色。是不是很有兴趣了解这种强大的编程语言呢?
在机器学习框架的世界中,也可以观察到类似的进展。Transformer架构是一种非常多才多艺的机器学习架构。Transformer最初是为自然语言处理(NLP)开发的。由于其卓越的表现,这种架构使得其他NLP架构,如递归神经网络(RNN)和长短时记忆网络(LSTM),变得过时。最近,Transformer架构开始对其他机器学习领域产生影响。根据SUPERB(superbbenchmark.org/leaderboard)的数据,语音处理的最佳基础模型也基于Transformer架构。此外,Transformer在计算机视觉和其他机器学习领域也展现出了出色的表现。因此,Transformer有潜力将所有人工智能框架融合成一个单一的、高度适应性的架构。
在本章中,我们将深入探讨这种多功能机器学习架构的基础结构。具体来说,本章将重点讲解Vaswani等人(2017年)提出的原始Transformer架构。由于Transformer最初是为NLP任务设计的,我们将理解重要的NLP模型,并探讨Transformer如何受这些模型的影响。
结构
本章涵盖以下内容:
- 自然语言处理(NLP)模型的发展历程
- Transformer架构
- Transformer的训练过程
- Transformer的推理过程
- Transformer的类型及其应用
目标
本章旨在为读者提供一个广泛的理解,涉及NLP模型发展的演变及其重要里程碑,特别强调Transformer架构。通过对不同NLP模型的深入分析,比较并突显Transformer模型如何解决其前辈模型的局限性。本章将重点探讨组成Transformer架构的关键组件。此外,本章还将介绍Transformer模型的不同变种,展示它们在NLP领域的广泛应用。本章的核心主题是追溯NLP模型发展的历程,最终揭示Transformer作为语言处理技术中的一项突破性创新的崛起。
自然语言处理模型的发展历程
Transformer最初是由Vaswani等人在2017年为自然语言处理(NLP)任务,特别是机器翻译,提出的。目前,它是NLP领域以及其他广泛任务(如语音处理、计算机视觉等)中最受欢迎和最有效的模型。然而,Transformer的出现并非突如其来。实际上,它是NLP模型多年研究和发展的成果,每个新模型都建立在前一个模型的基础上。了解这一点非常重要,因为在理解Transformer架构时,我们将能够将其置于NLP模型的历史发展背景中,了解其局限性,以及Transformer如何表现出独特的多功能性。
在接下来的部分,我们将探索NLP模型演变的时间线,并对各种NLP模型进行对比。图1.1展示了NLP研究的时间轴:
Transformer模型是所有先前研究成果的集大成者。Vaswani等人引用了一些原始研究,尤其是以下几项研究,Transformer模型似乎深受其影响。
接下来的部分,我们将讨论一些最重要的NLP模型,它们的优点和不足之处。
循环神经网络(RNN)
首先,让我们讨论一下下一个词预测的概念。例如,假设我们有一个句子:“The color of the sky is ….”。根据我们大脑已经处理的信息,我们可以预测这个句子中的下一个词会是“blue”(蓝色)。然而,这个预测不仅仅是基于前一个词,而是基于多个前面的词。
传统的机器学习算法,如线性回归和多层感知机,并没有能力存储之前的信息并将其用于预测。这些算法无法保留先前输入的信息。在这种情况下,循环神经网络(RNN)就派上了用场,它能够保留先前的信息,并利用这些信息进行准确的预测。
图1.2展示了RNN的结构。每个RNN单元将前一个单元的输出作为输入。这使得网络能够保留来自前一个时间步的信息,并将其融入到随后的每次迭代计算中:
RNN的局限性
让我们考虑以下例子:“England is my hometown. I spent my whole life there. I just moved to Spain two days ago. I can speak only one language, which is ….” 在这个例子中,下一个词是“English”(英语)。最重要的上下文词在这个句子中是“England”,它出现在句子的开头。然而,在某些情况下,相关信息可能会被RNN放置得离需要它的地方非常远。例如,在这个例子中,相关信息和预测词之间的间隔大约是26个时间步,即“England”出现在时间步1,而预测词出现在时间步27。这么大的间隔对于RNN来说可能是一个问题,因为它们可能无法在如此长的序列中保留上下文信息,或者与这些信息相关的权重可能变得非常小。这是由于RNN的结构所致,在RNN中,梯度会不断乘以网络中的权重矩阵,这样梯度可能变得非常小,甚至为零。这会使得网络难以学习,从而导致训练变慢,甚至完全失败。
LSTM
为了解决梯度消失问题,引入了长短期记忆网络(LSTM)。
与RNN不同,LSTM具有一个“记忆门”(memory gate),它允许网络存储数据中的长期依赖关系。此外,LSTM还具有一个“忘记门”(forget gate),可以帮助过滤掉来自前一状态的不必要信息。
LSTM的另一个优势是它们遇到梯度消失问题的概率较低。梯度消失问题发生在反向传播过程中,梯度变得非常小,甚至为零,这使得网络难以学习。LSTM通过采用控制信息流的门控机制来解决这个问题,从而能够保留相关的细节,并丢弃无关的信息。图1.3展示了RNN和LSTM结构的比较。与RNN相比,LSTM结构更加复杂:
LSTM的局限性
- 处理长序列的能力有限:尽管LSTM具有记忆门,它们仍然在处理长序列时遇到困难。这是因为它们使用固定长度的隐藏状态,如果输入序列非常长,这就可能成为一个问题。
- 处理速度较慢:LSTM处理序列是按顺序进行的,这可能比较慢,并限制了跨多个处理器并行计算的能力。
Cho(2014)的RNN编码器-解码器
RNN编码器-解码器模型是一种序列到序列的算法,主要包含三个核心组件。让我们通过一个英法翻译的例子来探索RNN编码器-解码器模型的组成部分:
- 编码器:这是一个RNN,用于将变长的输入序列(在此例中为英语句子)编码为一个固定长度的向量。
- 编码向量:编码器输出的固定长度向量。
- 解码器:这也是一个RNN,接收编码向量作为输入,并生成一个变长的输出序列(在此例中为英语输入序列的法语翻译)。
编码器-解码器模型尤其适用于诸如机器翻译和语音识别等任务,在这些任务中,输入序列和输出序列的长度可能不同。图1.4展示了RNN编码器-解码器模型的简化表示:
局限性:主要局限性是梯度消失问题。该模型使用编码器RNN的最终隐藏状态生成输入序列的固定长度向量表示,这可能会导致早期时间步的重要信息丢失。
Bahdanau(2014)的注意力机制
Bahdanau在2014年的论文中提出了注意力机制,扩展了RNN编码器-解码器模型。它实际上是一个加入了注意力机制的编码器-解码器模型。让我们来讨论一下注意力机制是什么:
- 注意力机制:它允许模型有选择地关注输入序列中与输出更相关的部分,同时忽略那些不那么相关的部分。
例如,在机器翻译中——注意力机制使得模型能够专注于最重要的单词或短语,从而准确地预测翻译结果。
从本质上讲,注意力机制模仿了人类的认知行为,通过聚焦于最重要的词汇,并过滤掉噪音,来优化任务的处理。
局限性:Bahdanau的注意力机制是一个局部注意力机制,它一次只查看输入序列的一个子集。对于较短的句子,这种机制可以很好地工作。然而,如果输入句子较长,性能会显著降低。
- 编码器-解码器方法:它有效的原因是可以处理不同长度的输入和输出序列,这在机器翻译和其他NLP任务中尤为常见,因为输入和输出序列中的单词数可能不同。
- 注意力机制:这是这种方法中的一个关键组件,因为它使神经网络能够集中关注输入数据中对于当前任务最重要的部分。这样可以帮助网络更有效地捕捉相关信息,从而提高在各种NLP任务中的表现。
在接下来的部分,我们将讨论Transformer架构,并理解编码器-解码器架构和注意力机制是Transformer架构的主要组成部分。
Transformer架构
Transformer有许多变种,但在本节中,我们将讨论Vaswani等人(2017)提出的原始Transformer架构。他们提出该架构是为了机器翻译(例如,从英语到法语)。在详细讨论之前,让我们先突出Transformer架构的几个重要方面:
- 编码器-解码器架构:Transformer使用编码器-解码器架构来进行机器翻译。
- 编码器:编码器将输入序列转换为一个序列向量,向量的长度等于输入序列的长度。编码器由多个编码器块组成。
- 解码器:解码器也由多个解码器块组成,编码器的输出(序列向量)会传递给所有解码器块。
- 多头注意力:多头注意力是编码器和解码器的核心组成部分。
- 位置编码:位置编码是Transformer架构中引入的一个新概念,它对每个输入token的位置信息进行编码,表示其在输入序列中的位置。
图1.5展示了Transformer架构:
嵌入(Embedding)
如图1.5所示,Transformer中的输入序列通过嵌入向量表示。嵌入是将单词或token表示为固定长度的向量的过程。
在深入讨论嵌入之前,我们先了解一下文本在NLP中传统的表示方式。这将帮助我们理解为什么使用嵌入。传统上,机器学习中的文本数据是通过n-gram模型来表示的。以1-gram为例:如果总样本中有50,000个唯一的单词,每个输入序列将被表示为一个50,000维的向量。我们将这些维度填充为每个单词在特定输入序列中出现的次数。然而,这种方法存在几个问题:
- 即使是小的输入序列(例如,只有两个token的序列),我们也需要一个高维的向量(50,000维),这会导致向量高度稀疏。
- 对于这些高维向量表示,无法进行有意义的数学操作。
嵌入技术解决了这些问题。嵌入是一种技术,用于通过一个实数向量表示单词或序列,这个向量捕捉了单词或短语的含义和上下文。
一个非常简单的嵌入示例是:我们取一组单词,如[cabbage, rabbit, eggplant, elephant, dog, cauliflower],并将每个单词表示为二维空间中的一个向量,其中捕捉到动物和颜色等特征。嵌入示例如图1.6所示。最终的嵌入向量可能如下所示:
[cabbage, cauliflower, eggplant, dog, rabbit, elephant] = [[0.2, 0.1], [0.2, 0.3], [0.2, 0.8], [0.8, 0.4], [0.75, 0.6], [0.9, 0.7]]
我们可以看到,cabbage(甘蓝)和cauliflower(花椰菜)的第一个维度几乎相同,因为它们都是蔬菜。这使得它们在第一个维度上接近。此外,我们可以对这些嵌入向量进行加法和减法操作,因为每个维度表示特定的概念,而如果两个token表示相似的概念,它们的向量会更接近。
有趣的是,在实际应用中,我们通常使用像BERT或word2vec这样的预训练模型,这些模型通过数十亿个样本进行训练,并提取大量维度的特征(例如,BERT使用768维)。与n-gram方法相比,嵌入技术具有更高的准确性,并且在NLP中提供了更大的灵活性。
位置编码(Positional Encoding)
Transformer中的位置编码用于向模型提供关于输入序列中每个单词位置的信息。与之前的架构(如LSTM)不同,在LSTM中每个token是按顺序处理的(一个接一个);而Transformer是并行处理输入tokens的。这意味着每个token也需要具有位置相关的信息。
让我们了解一下位置编码是如何实现的。在《Attention is All You Need》论文中,作者使用了一个特定的公式来计算位置编码。该公式如下:
PE(pos, 2i) 和 PE(pos, 2i + 1) 分别是位置 pos 在输入序列中的位置编码向量的第 i 维和第 (i + 1) 维。
- pos 是单词在输入序列中的位置,从 0 开始。
- i 是位置编码向量中的维度索引,从 0 开始。
- d 是嵌入的维度(在原始架构中为 512)。
这个公式生成了一组位置编码,每个位置在输入序列中都是唯一的,且随着位置变化平滑变化。
需要理解的是,存在 256 对正弦和余弦值(512/2)。因此,i 的值从 0 到 255。
接下来让我们解析这个公式:
第一个单词(位置=0)的编码将是:
因此,第一个单词的位置信息编码将是 [0, 1, 0, 1, …, 1]。第二个单词的位置信息编码将是 [0.8414, 0.5403, 0.8218, …]。如果嵌入向量是 512 维的,位置信息编码向量将如下所示:
模型输入
如图 1.7 所示,模型输入是位置信息编码和嵌入向量的逐点相加。让我们理解一下这是如何实现的。
为了表示“I Live In New York”,假设它的标记化长度为5,我们添加了1个标记。
[‘I’, ‘Live’, ‘In’, ‘NewYork’, <pad>]
首先,每个标记都会被表示为整数。在这个例子中,单词“I”被表示为8667,‘Live’被表示为1362,‘In’被表示为1300,‘NewYork’被表示为1301,<pad>被表示为0。结果是:
IntegerRepresentation = [8667, 1362, 1300, 1301, 0]
然后,我们将这些标记化序列传递到嵌入层。每个标记的嵌入是由512维向量表示的。在下面的例子中,向量 [embeddingtoken_8667] 的维度为512。
Embedding = [[embeddingtoken_8667], [embeddingtoken_1362], [embeddingtoken_1300], [embeddingtoken_1301], [embeddingtoken_0]]
最后,我们将嵌入向量和位置信息编码逐点相加,然后输入模型。
PositionalEncodingVector = [[size=512], [size=512], [size=512], [size=512], [size=512]] +
Embedding = [[embeddingtoken_8667], [embeddingtoken_1362], [embeddingtoken_1300], [embeddingtoken_1301], [embeddingtoken_0]] =
ModelInput = [[size=512], [size=512], [size=512], [size=512], [size=512]]
编码层
编码层是Transformer架构中的一个关键组件,负责处理和编码输入序列为向量表示。请参考下图:
让我们详细了解编码层的每个子组件:
- 输入到编码器:编码器的第一层输入是嵌入向量和位置编码的逐元素求和。
- 多头自注意力:Transformer编码块中的一个关键组件是多头自注意力机制。该机制允许模型在进行预测时,根据输入的不同部分的重要性来进行加权。在后续部分,我们将详细讨论多头自注意力机制的原理。
- 加法与归一化层:加法层,也叫做残差连接,用于将输入与前一层的输出相加,然后再传递给下一层。这允许模型学习残差函数,即输入和输出之间的差异,而不是直接学习实际的函数。这有助于改善模型的性能,特别是在层数较多时。归一化层则对每一层的激活值进行标准化,确保所有隐藏单元的激活值都保持在一个合理的范围内。这样可以帮助稳定模型的训练,防止输入过大或过小,从而避免梯度消失或梯度爆炸等问题。
- 前馈层:多头自注意力机制的输出将作为输入传递给前馈层,并应用非线性激活函数。前馈层用于提取数据中的更高层次特征。前馈层之后同样有加法和归一化层,这一输出将传递给下一个编码块。
- 编码器输出:编码器的最后一个块产生一个序列向量,然后将其作为特征传递给解码器块。
注意力机制
注意力机制已经成为一种多功能且强大的神经网络组件,使得模型能够根据给定的上下文加权和优先考虑相关信息。其核心概念——自注意力和多头注意力,极大地推动了 Transformer 架构的卓越表现。让我们深入探讨这些概念。
自注意力
自注意力机制是 Transformer 性能的关键。让我们理解它是如何工作的。考虑以下两个例子:
- Rabbit ate the carrot because it was hungry.
- Rabbit ate the carrot because it was tasty.
在这两个句子中,"it" 指的到底是什么?仅通过句子的结构和位置,我们无法确定。"It" 在每个句子中的指代是不一样的,需要根据上下文来理解。正如 Vaswani 等人(2017年)所说: “意义是事物之间关系的结果,自注意力是学习关系的一种通用方式。”
自注意力计算输入句子中每个词语之间的关系权重。通过这种机制,模型能够理解输入句子的含义。
我们来看一下在这两个句子中,“it”的注意力计算。如图1.9所示,在处理第一个句子中的“it”时,模型会赋予“rabbit”更高的权重,而在第二个句子中,模型则赋予“tasty”更高的权重。这表明模型通过自注意力机制能够理解“it”在不同上下文中的不同含义。
多头注意力
与只使用一个注意力头不同,自注意力块使用多个注意力头。每个注意力头使用不同的参数,并专注于从输入中提取不同的特征。
图1.10再次展示了相同的例子,但这次使用了两个注意力头。图中,Head1 用红色表示,Head2 用黄色表示。我们可以看到,不同的注意力头捕获了不同的上下文关系:
解码器层
解码器的结构与编码器相似,但它包含一个额外的组件——掩蔽自注意力机制。让我们详细了解解码器的架构:
解码器输入
在训练过程中,解码器第一层的输入是通过逐点相加获得的:
- 目标序列的嵌入(Embeddings)
- 目标序列的位置信息(Positional encoding)
掩蔽多头注意力
掩蔽多头注意力和常规的多头注意力的主要区别在于,掩蔽多头注意力会对输入序列的某些部分进行掩蔽或屏蔽,使得解码器在生成输出序列时无法看到这些部分。具体来说,输入序列中与未来目标标记(即尚未生成的标记)对应的位置会被掩蔽。这是必需的,因为:
- 解码器是逐词生成的。
- 我们只显示当前生成位置的单词;因此,解码器不能看到未来需要生成的目标标记。
如图所示,掩蔽多头注意力的输入是步骤1生成的向量——即目标序列的嵌入和位置信息的逐点相加。
多头注意力
如图所示,解码器中的多头注意力机制的输入通常是编码器的输出和之前在输出序列中生成的标记。与仅使用第一个解码器块不同,所有的解码器块都接收来自编码器的输出,因为:
- 模型能够确保输入序列中的信息在整个解码过程中都被传播。
- 模型得到了有效的正则化,因为每个解码器块都可以访问相同的信息,这有助于防止过拟合,并提升模型的泛化性能。
前馈层
多头自注意力机制的输出会传递到前馈层的输入。此时,还会应用非线性激活函数。前馈层对于从数据中提取更高层次的特征至关重要。
线性层
在变换器架构中,解码器中的线性层是用来生成解码器最终输出的组件。解码器线性层的输入是解码器最后一层的输出。此外,还会应用SoftMax激活函数来生成序列中下一个单词的概率分布。
Transformer的训练过程
Transformer的训练过程通常包括以下几个步骤:
- 数据预处理和生成输入与目标序列的位置信息(Positional Encoding)
- 通过编码器和解码器层
- 损失计算:将生成的输出序列与目标输出序列进行比较,并使用交叉熵等损失函数计算损失值。
- 反向传播:使用反向传播算法计算损失对模型参数的梯度。
- 优化:使用优化算法(如Adam)更新模型参数,以最小化损失值。
- 重复步骤3-7:执行多个epoch,直到模型在验证集上的表现稳定或达到满意的水平。
需要注意的是,在训练过程中,模型会接触大量平行文本数据,其中输入序列和输出序列已对齐,模型通过注意力机制和线性层学习将输入序列映射到输出序列。
Transformer的推理过程
Transformer的推理过程通常包括以下几个步骤:
- 数据预处理和生成输入序列的位置信息(Positional Encoding) 。需要注意的是,在推理过程中,我们不会有目标序列。
- 通过编码器和解码器块。需要注意的是,解码器的输入在训练和推理过程中有些许不同。在训练时,我们将实际的目标序列传递给第一个解码器块;而在推理时,我们不会使用目标序列,而是将已经推断出的标记传递给解码器,直到当前状态。原因在于推理过程中没有目标序列可用。
Transformer的类型及其应用
到目前为止,我们已经讲解了用于机器翻译的Transformer架构。然而,Transformer有许多变种。让我们来回顾一下这些变种。
仅编码器模型
仅编码器模型只包含Transformer模型的编码器层。注意力层可以访问输入句子中的所有单词。仅编码器模型通常具有双向注意力,称为自编码模型。以下是一些仅编码器模型的例子及应用。
一些已提出的仅编码器模型例子包括:
- Bidirectional Encoder Representations from Transformers (BERT) :BERT是一个预训练的仅编码器模型,已在大量文本语料库上进行训练,已证明在广泛的自然语言处理任务中有效,包括情感分析、文本分类和问答系统等。
- A Lite BERT (ALBERT) :ALBERT是BERT的轻量版本,在使用较少计算资源的情况下,能达到与BERT相似的性能。
应用:
- 情感分析:编码器可以训练提取给定文本的特征,并预测其情感(正面、负面或中性)。
- 文本分类:编码器可以训练将给定文本分类为不同类别,如新闻、体育、政治等。
- 命名实体识别:编码器可以训练识别给定文本中的实体,如人名、组织名和地名等。
- 语言建模:编码器可以训练预测一个序列中的下一个标记。
仅解码器模型
仅解码器模型仅使用Transformer架构中的解码器层。解码器的注意力层只能访问当前标记之前的序列。因此,这种模型通常称为自回归模型,因为它们被训练基于当前序列中的前一个标记预测下一个标记。
例子:
- GPT(由OpenAI提出)
- 可控生成的条件Transformer语言模型(CTRL)
应用:
- 文本生成:仅解码器模型在文本生成和自然语言生成(NLG)中有广泛应用。
编码器-解码器模型
也称为序列到序列模型,使用编码器和解码器。最初提出Transformer的论文使用的是编码器-解码器模型。编码器的注意力层可以访问输入序列中的所有标记,而解码器的注意力层只能查看当前和之前的标记。未来的标记会被遮蔽,不会被解码器的注意力层看到。
例子:
- BART(用于序列生成任务的去噪自动编码器预训练)
应用:
- 机器翻译
总结
总之,本章提供了对自然语言处理(NLP)模型发展历程及其重要里程碑的深入分析。我们欣赏Transformer架构如何融合了前代NLP模型的最佳特性。此外,我们还深入探讨了Transformer的显著特点,包括自注意力机制和位置编码。最后,我们探讨了各种Transformer变体及其应用,并认识到它在多个机器学习任务中的广泛影响。
在下一章,我们将实现Transformer模型的基础架构,使用PyTorch框架。这将为我们打下坚实的基础,帮助我们理解每个组件如何运作,并为我们深入应用Transformer模型到自然语言处理、计算机视觉、语音处理和表格数据处理等各个领域提供关键支持。