Rethink AI【4】:Transformer模型

前言

作为NLP领域的最重要模型之一,Transformer有着强大的性能。

为什么要引入Transformer

Transformer这一模型与称呼,来源于2017年的一篇论文——《Attention is All You Need》。在2017年左右,NLP领域的代表性模型无非是RNN系列和CNN系列。

RNN系列的模型的计算过程是以迭代整个序列为基础的,想要在其基础上进行并行计算是很困难的,处理长序列所需的时间很多。除此之外,RNN也具有梯度爆炸/梯度消失现象,难以捕捉长距离的词元依赖关系。

CNN系列的模型固然可以很好捕捉长序列信息,但其有一个十分致命的缺点,即其卷积核的大小是固定的,难以自适应捕捉不同序列的语义距离。对于简单句来说,卷积核的大小可能太大了——而相同大小的卷积核,对于长难句来说,又太小。

Transformer架构就解决了这两类模型的问题——通过注意力机制解决了难以并行计算、梯度爆炸/消失的问题,通过注意力机制中的权重设置,能够自适应捕捉不同序列的语义距离。简单来说,就是将RNN的迭代过程,变成了矩阵的乘法运算;将CNN的卷积过程,化作了注意力机制模块中,矩阵的参数。除此之外,Transformer也具有架构统一、易于拓展的优点。

Transformer的首要任务其实和RNN和CNN无异,那就是学习到语言的特征。

注意力机制

广义的注意力机制,其本质就是矩阵的乘法,其通用结构是:
$$
\text{Score}([x_1,x_2,\cdots,x_n])=\mathbf{W}[x_1,x_2,\cdots,x_n]
$$
其中,$\mathbf{W}$是注意力矩阵,是一个可学习的参数。通过数据集训练这个矩阵,从而得到向量中每一个元素的重要性。

Transformer的结构

Transformer的结构如下图所示:

嵌入模块

首先,这里的Input Embedding和Output Embedding都是“词嵌入”的技术,即将自然语言转化为向量的过程。这一步是NLP最基础的技术之一,因为计算机没有办法直接理解自然语言,需要将之转化为数字表示才可理解。有关这个技术,我会在后续的文章中讨论。

编码器

我们从左往右看,左边的灰色框框被称作“编码器”(Encoder)。其目的是将自然语言的向量表示,转化为模型内部的表示。这个模块接收一个输入,吐出一个输出。实际上,第一个编码器模块的输入就是自然语言的向量。当向量进入编码器后,首先经过一个多头注意力机制模块(Multi-Head Attention Block)。多头注意力机制模块,实际上就是若干个注意力机制模块进行拼接。每一个注意力机制模块都应用下面的公式进行计算:
$$
\text{Attention}(\mathbf{Q},\mathbf{K},\mathbf{V})=\text{Softmax}(\frac{\mathbf{Q}\mathbf{K}^T}{\sqrt{d_k}})\mathbf{V}
$$
我们按照每一部分字母来解释。

首先,$\mathbf{Q},\mathbf{K},\mathbf{V}$是由输入$\mathbf{I}$经过运算得到的矩阵:
$$
\mathbf{Q}=\mathbf{W}_Q \mathbf{I},\mathbf{Q}\in\mathbb{R}^{n\times d_k}
$$

$$
\mathbf{K}=\mathbf{W}_K \mathbf{I},\mathbf{K}\in\mathbb{R}^{m\times d_k}
$$

$$
\mathbf{V}=\mathbf{W}_V \mathbf{I},\mathbf{V}\in\mathbb{R}^{m\times d_v}
$$

这三个字母分别代表query、key和value,是作者借用的搜索领域的名词。我们不妨考虑一下搜索引擎是怎么运作的,在搜索引擎中输入一个关键字(query),然后蹦出来一堆页面的标题(key),找到符合心意的标题,点进去,就得到了相应的内容(value)。回到我们的任务上,我们的任务是对语言进行建模,给定一个自然语言的句子,我们不妨让key和这个句子一致,那么我们的内容也需要和key一致。输入是什么,输出就是什么,这样才能捕捉到语言的内部规律——当捕捉到语言内部规律时,给定一个自然语言句子,其输出必须要和源端一致,否则就是偏差了。输入所乘的矩阵,实际上是一个投影操作,将输入分别投影到query、key和value空间。

我们观察Softmax函数的分母,表面上看是一个矩阵相乘,实际上,这是一个求向量间相似度的操作,还记得余弦相似度怎么算吗:
$$
\text{CosSim}(\vec{a},\vec{b})=\frac{ab^T}{||a||||b||}
$$
分母其实也是这个含义,根据矩阵乘法的规则,我们知道,结果矩阵中的每一个元素,都是乘子两个矩阵的行与列的积,实际上,分子所计算的,就是query矩阵和key矩阵中每一行元素之间的相似度。分子结果矩阵的第一行第二列元素,就是query矩阵的第一行向量和key矩阵第二行向量之间的相似度。至于分母,其实它就是一个scaling factor,用于控制元素的缩放,求每一行向量的开销过大,不妨用一个现有的常数做scaling。

这个结果做一个Softmax,实际上是一个normalization(归一化)操作,将数据都缩放到0-1这个量级,便于统一处理。

因为key和value是一一对应的关系,最后的结果和value相乘,就得到了每一个位置上,每个value单元的重要性。还是按照矩阵乘法的规则,Softmax的函数结果矩阵,第一行便是query的第一行向量和所有key之间的相似度关系,这个时候和value的列向量相乘,就得到了这个相似度关系权重下,每一个value单元的权重。

这是一个注意力机制模块的工作方法,多头注意力机制就是并行处理很多很多个注意力机制模块,每个模块的参数都是独立的,通过不同的参数学习到不同的注意力信息。

在得到了这个注意力矩阵后,我们发现,这个结果要经历一个Add and Norm层,广义来看,这是一个十分著名的结构——ResNet:
$$
\text{Output}(x)=x+f(x)
$$
这一结构能够缓解梯度爆炸和梯度消失的现象,其中的Norm其实上是Layer Norm,层归一化操作。对于每一个头的每一个输出矩阵,都按照行向量进行归一化,这能够稳定同一value下,不同元素所占的比重。Add and Norm层,实际上就是为了稳定输出矩阵中,每一行的比重。

在这之后,进入了一个FFN模块,对刚刚得到的矩阵进行进一步的信息提取,在实际的模型架构中,这一模块先做了升维操作,随后做了降维操作,先提供更丰富的信息表达程度,然后进行凝练——这就像是一个先把书读厚,再读薄的过程。

解码器

当输入进入层层Encoder,最终得到一个输出之后,它会被输入解码器链中。具体来说,这个输出会输入解码器链的每一层

Decoder的输出,除了这个编码器计算出来的结果外,还有一个右移的输出值,这个输出值——用机器翻译来说——便是目标语言的句子了。

这里,这个目标端的句子首先进入了一个掩码注意力机制模块中。何为掩码,这可得好好解释一番。

Transformer的预测过程是一个通过序列$[x_1,x_2,\cdots,x_n]$预测$[x_1,x_2,\cdots,x_n,x_{n+1}]$的过程。这就要求,每一个词元的状态,只和它之前的词元有关。所以,在计算注意力的时候,需要和它前面的矩阵计算注意力。所以,注意力机制计算的时候,需要用到掩码。

随后,将这个注意力矩阵作为实际的内容——value矩阵,按照之前的方法运算即可。

位置编码

到这里,结束了吗?并没有,我们剩下一个模块还没有介绍——位置编码模块(Positional Encoding)。因为到目前为止,我们没有办法捕捉到位置信息,也就是说,交换两个词元的位置,最终的结果似乎并没有太大变化。“我爱任天堂”和“任天堂爱我”,不加入位置编码,没法区分这两个句子的差异。

在原论文中,所使用的位置编码是:
$$
\text{PE}_{(\text{pos}, 2i)}=\sin(\frac{\text{pos}}{10000^{\frac{2i}{d_{model}}}})
$$

$$
\text{PE}_{(\text{pos}, 2i+1)}=\cos(\frac{\text{pos}}{10000^{\frac{2i}{d_{model}}}})
$$

其中,$\text{pos}$是位置,$i$是维度数。这样的好处是,可以获得相对的位置信息,这是因为,正余弦函数的加和性质:
$$
\sin(a+b)=\sin(a)\cos(b)+\cos(a)\sin(b)
$$

$$
\cos(a+b)=\cos(a)\cos(b)-\sin(a)\sin(b)
$$

后记

着实花了我一大番功夫缕清各种细节。不得不说,Transformer算是我心目中最伟大的模型之一了,简单、有效。

文章作者:
文章链接: https://www.coderlock.site/2025/08/14/Rethink-AI【4】:Transformer模型/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 寒夜雨