- tips:观前提示,配合着论文一起看可能会更加有效哦~
前置知识
在学习Transformer之前,我们先来回顾一下神经网络一些相关的基础概念
- 激活函数:为了引入非线性从而设计的函数,典型的有Sigmod、ReLU等等
- 损失函数:用于衡量真实值和模型预测值之间的差距,是模型训练的基础,对于不同的任务一般会有不同的损失函数,经典的有MSE、交叉熵
- 梯度下降:由于导数能够体现函数是在增加还是减少及其幅度,所以可以用求导的方法调整参数让损失函数的值降低,定义公式 $\theta_{new}$ = $\theta_{old}$ - $f(\theta_{old})$,让 $f(\theta)$ 值不断减小,从而达到训练模型的目的
- tips:这里的f在神经网络中指的是损失函数
- 学习率:因为有些时候模型的预测函数求导后的值过大,导致一直达不到最低点,所以需要添加学习率在上述公式的 $f(\theta)$ 前面以调整 $f(\theta)$ 值变换速度,从而便于模型收敛
神经网络发展史
在此之后,我们了解一下神经网络发展的历史,这能帮助我们更好地理解为什么需要Transformer架构
- FNN:Feedforward Neutral Network,前馈神经网络,最简单的一种神经网络,由输入层、隐藏层、输出层三部分构成,它的输入维度是固定的,没有时序概念,也无法理解时间上的先后关系,对于时间上分先后的任务几乎没有处理能力,现在很少使用
- RNN:Recurrent Neutral Network,循环神经网络,和FNN不同的是,在隐藏层增加了一个循环,即上一层的输出会作为下一层的一个输入,公式为 $$ h_t = \tanh(W_h h_{t-1}+W_x x_t+b_h) $$ 其中tanh是一种激活函数,它能够处理时序问题,但是当输入维度和输出维度不相等的情况下它也无能为力了
- 编码器-解码器结构:将RNN结构拆成两个部分,即输入层和输出层,编码器结构通过多个RNN循环得到最终隐藏状态h_t,这个h_t也被成为上下文向量,对于解码器,更加普遍的方法是,在每次rnn循环都有两个输入(上一次算出来的隐藏状态和上下文向量本身(这个上下文向量的作用是为了保证在后面经过了多次处理之后信息不会丢失))和两个输出(预测结果和下一步的隐藏状态量),其中后者暂记作C
注意力机制
但是即便是编码器-解码器结构依然存在着一些问题,当我们输入一个非常长的一句话时,token的数量会增加很多,所以随之而来的计算也会多很多,到后面很可能模型就忘记了前面的token(不断地计算下前面的token的权重无限降低),而我们更加希望的是模型输出的东西是全局性的,所以就出现了Attention Mechanism,注意力机制。
- tips:值得一提的是,注意力机制在Transformer出现之前就已经出现了,Transformer只是基于这一机制提出的一种架构。
原理部分
为了解决以上问题,学术界提出的办法是对于每个token的第二个输入(也就是原本是上下文向量的那个位置)设计一个单独的"上下文向量",即每个token有一个对于每个其他不同token的隐藏状态量都有不同权重的$C_t$,权重的大小和token之间的相关度成正相关。
为什么我们需要Transformer
当GPU的算力越来越强,大家发现传统的注意力机制增强的RNN神经网络无法完全利用GPU的算力,正如当初设计计算机的时候的发展路径一样,大家更希望能够解锁串行化计算,但是显然RNN中每个隐藏量的计算都依赖于上一个隐藏量就导致了并行计算是不可能的。
引入
于是,在这种情况下,有的人选择把CNN卷积神经网络重新引入,但是这样等于又将之前注意力机制解决的问题重新引入,而Transformer架构直接完全抛弃了RNN/CNN,完全基于自注意力机制。
原理
对于监督学习来说,我们对于模型训练时的输入其实是由两部分组成的,一部分是原始输入,还有一部分是标准输出,基于此,我们就可以发现其实上述需要等待其他token编码完的过程就没有必要,其实我们可以直接用标准输出来代替这一部分,从而达到一开始每个位置的token都能编码进其他位置的token的时序信息和位置信息的目的。
在Tansformer中,这常常由一个512维的向量实现,在编码之后形成一个带有完整的上下文的矩阵,而在解码器部分,我们采用先将前文的标准答案和编码器已经编码好的信息输入给模型,剩余部分不输入,这样就达到了每个token都可以独立进行训练的目的。
具体过程解析

图 1:论文中的Transformer架构
预处理
Embedding
我们以翻译任务为例,当我们输入一句话的时候(例如,“世界你好”),首先经过Embedding把这一句话映射成模型能够理解的向量形式,一般是512维。
- tips:Embedding的方式一般由两种,一个是你用专门的已经训练好的Embedding模型,这种方法比较适合自己做一些小模型的训练的时候用;另一种就是现在大语言模型训练中更常用的,让Embedding和整个Transformer一起训练,这样效果更好。(当然训练成本不可避免地也会增加)
位置编码(Postional Encoding)
这之后,我们要对于每个词的位置也编码一个512维的向量,公式如下

图 2:论文中的PE公式
这个公式中有一个非常巧妙地Mathematical Trick,他同时使用了sin和cos两个函数,结合sin(x + k)和cos(x + k)的展开公式,如果我知道位置 pos 的 sin 和 cos,那么我可以通过一个线性变换得到位置 pos+k 的 sin 和 cos,也就是作者所说的对于固定的$offset_k$,$PE_{pos+k}$可以表示为$PE_{pos}$的线性函数。
而公式中的10000则是为了控制正弦波和余弦波的变化频率,即前面的维度高频变化快,后面的维度低频变化慢。
这一步之后,每个token变成了X=Embedding+PE
- tips:有的人可能会说,为什么不直接从1开始编码,也就是PE(pos) = sin(pos),这样来说会更加简单不是吗?但是这样会丢失很多语义信息,因为transformer是并行化处理的,为了让位置编码中携带更多的信息,也更加符合注意力机制中的点积等计算,我们需要用这种复杂的方式让模型能够理解
- tips:另外,有必要提一嘴的是,现代的Transformer发展出了更多的位置编码,例如现在应用最广泛的RoPE。
注意力模块
这一部分是重点,我将会分成经典注意力机制和多头注意力机制两个模块进行讲解。
经典注意力机制(Q、K、V)
这里输入被分成了三个部分,每个部分对应一个权重矩阵$W_q$、$W_k$、$W_v$(这里的q意思是query,也就是查询,k是key,v是value分别是键和值),经过位置编码的输入矩阵分别乘以这三个权重矩阵,由矩阵乘法规则可知结果还是三个1 * 512的矩阵。
为什么要进行这个分类呢?对于自然语言处理等任务中,每个token的含义是会随着上下文发生一定量的变化的,所以对于我们之前embedding的向量空间不一定对于每篇上下文都是准确无误的,我们需要对其进行一定的调整。这也就是上下文信息的编码过程。
那么Q、K、V究竟分别代表着什么呢?对于Q,你可以简单地理解成模型在处理这个词的时候,向其他的词询问“我和你之间的关系有多近”(通过后面的公式我们也能看到论文中使用的方法是将Q和K一起做点积),具体细分下来,Q可以是在问很多问题,例如寻找代词的指代对象、寻找执行动作的主体和承受者、寻找主谓之间的关系等等,K就相当于给Q准备的一个答案库,他其中储存了它对应的token的各种特征,例如“World”这个token的Q来说“你和世界之间的关系是什么”,然后“Hello”中储存了“开始、亲切、打招呼”等特质,此时匹配度不高的话就对于原token没有多大影响,但是如果像是“瓜摊”和“价格”这两个token之间的Q和K相乘,权重就会比较大。而V则代表着这个词真正的含义,当经过Q和K之后这个token发现我两好像是知音,关系非常近,于是就从V当中取出这个token的真正信息,把自己的位置往他那边靠一点(也就是在向量空间中往对方token的向量靠近一点),自此,上下文信息也就被编码进去了。

图 3:注意力机制计算公式
- tips:这里公式中V前面的部分(也就是Q和K相乘之后经过放缩和激活函数处理之后得到的矩阵,我们称之为权重)不仅包含了当前token对于其他token的权重,同时也包含了自身对于自身的权重,就比如世界这个词本身的意思对于它在这个上下文中肯定也是占一部分权重的。
权重和代表着实际信息的V相乘,最终就得到了这个token真正的语义信息。
多头注意力机制
上面所述的过程是单头注意力,而多头注意力则是将权重矩阵的维度改成64 * 512,这样一来,通过矩阵的乘法规则,我们将会得到一个1 * 64的矩阵,然而这样的过程将会发生八次,最终得到的八个1 * 64矩阵直接拼接起来,就又变回了一个1 * 512的矩阵,然后在经过一个线性层,最终就得到了这一层的输出。
至于要理解为什么我们要分这么多头去进行计算,又要主动进行一个先降维再升维的过程,还是要回到向量空间去理解。
我们知道向量空间都是有很多维度的,每一个维度我们可以看成是当前token的一个特征,比如描述一个人,可以从他的身高、性别、长相等等方面来看,而在这个方向的投影长度就可以看作是这个人在这个特征上的程度或者说打分之类的东西。
而降维的过程则是主动创造了一个更侧重于某些特征的语义子空间,比如只从性别角度来看这个人,这样去操作的话,模型原本需要用同一个注意力分布来表达,这样的话,这个token一次只能评估一个方面自己与其他的token关系如何,而不是从多个角度分别关联到更多的token,后者是在训练过程中显然更加高效的,而且让不同的信息具有不同的Attention权重,模型也该学习到的特征将会更加丰富,具有更多的细节。
那为什么还要经过一个线性层乘以一个$W_o$呢,因为单纯的拼接本质上还是不同特征的简单相加,就像是一个人的头+身子+腿,并没有组合成各种各样的系统,也没有血管连接等等,自然也不是完整的人,而线性层就是将这个人真正组合起来了,成为了一个真正的在原本的512维的向量空间中的一个token。

图 4:多头注意力机制公式计算
残差连接与归一化
这是通过一个公式实现的:
$$ \mathrm{Output} = \mathrm{LayerNorm}\left(x + \mathrm{Sublayer}(x)\right) $$
简单来讲,就是相当于把原始输入给加到后续的输出里面
通过这样一种方式,我们可以做到即便我在深度学习中间遇到一个训练很差的模块,或者说学习太深了导致原始信息丢失得太多,这个时候我们还可以通过这样一个残差去保留到原始输入的信息,不至于导致信息完全丢失。
同时你可能还会注意到,公式中还有一个LayerNorm,这代表着归一化,是为了防止网络中的数值分布随着层数不断变化,让后面的网络更容易学习,也让模型更容易收敛。
- tips:在Attention is all you need里面,作者用的是一种叫做Post-LN的结构,所以展示的公式是这样的,但是很多现代的Transformer会用Pre-LN,公式会变成另一种形式。
FFN层
这一层的原理其实和之前讲的神经网络发展史中的FNN部分差不多,主要的区别在于Transformer进行了一个升维的处理,假设输入的512维变量为x,经过$xW_1$之后,变成一个2048维的变量,然后在经过ReLU激活,最后再映射成512维的变量,这一层的主要目的就是增强每个token内部的非线性特征变换能力,这样这个"人"就变得更加立体了。

图 5:论文中的FNN层公式
解码器部分略述
因为解码器的原理部分和编码器差别不大,于是这里不做详细表述,只对于Masked Multi-Head Attention以及解码过程进行一个概述。
Masked Multi-Head Attention
Masked在这里的意思是掩码,如果有学过密码学的朋友对于这个应该是比较熟悉的。不知道你看到这里还记不记得我们一开始说过,解码器部分我们需要让模型看到上文,预测这个位置的token,而不能让模型看到下文,因为我们要把时序信息编码进去,而显然我们无法预测未来的结果,但是我们至少知道在此之前我们说了什么。
掩码做的就是这样一个事情,让模型看不到下文,只能看到上文,经过这样的处理之后,我们就得到了一个Q(后文有详细解释)。
解码过程
在掩码多头注意力之前呢,模型还是会经过Embedding编码位置信息,而在这之后模型又将上下文信息编码进去了,在经过残差连接和归一化,我们又进入了下一个多头注意力处理层。
这一层的输入是我们刚刚提到的Q,还有编码层的结果,也就是K和V,在之前我们已经详细叙述过什么是K、Q和V了,其实在这里起到的是类似的作用,事实上,这个Q相当于是上文的信息,现在我想用这个模型从上文的信息中预测出来我下一个词应该是什么,然后解码器就告诉你了,根据你这个上文信息,和你匹配的应该是这个token的K,它的具体含义就在我的V里面,你赶紧过来取吧,然后模型就知道了我这里应该是哪个词。
- tips:如果从一开始就认真看到这里的小伙伴一定会发现编码器部分以及解码器的第一个注意力模块用的Q、K、V都是token自己的,但是解码器的第二个注意力模块的Q来自我提前准备好的答案,K和V都来自于编码器,这两者是有着显著区别的,因而在学术上前者被称为自注意力机制,后者被称为交叉注意力机制。
最后还是和编码器一样的,Add&Norm+FNN+Add&Norm。
最后变成概率输出
(这一节实在想不出用什么标题了,随便写一下吧)
经过这上面一系列操作,我们最后终于得到了一个1 * 512维的向量,而这个向量就是我们一开始想预测的哪个向量,但问题是,这个向量怎么重新转换成自然语言呢?
那么在这里,我的理解是相当于进行了Embedding的一个逆向操作,我们Embedding的时候不是用到了一个巨大的向量空间吗,现在我们还需要把那个向量空间拿过来,但是这次是用向量拿到对应的词,这就是论文中那个线性层的作用,将这个向量映射成另一个大的向量,比如说向量空间有500000000个词,就得到了一个1 * 500000000的向量,每一列都是对于这一列对应的词的概率打分,最后通过softmax归一化之后,成功变成了你在营销号嘴里经常听到模型对于这个位置上每个词出现的概率了。
总结
所以,最后回到为什么我们需要Transformer,首先它的架构使得训练成本很低,其次它的并行计算能力让它比RNN的训练快多了,最后,就是长距离依赖能力,由于Transformer的这个自注意力机制,导致无论上下文有多长,它都可以注意到。

图 6:Transformer模型的架构复杂度对比
- tips:本文章大部分灵感来自于https://www.bilibili.com/video/BV1xoJwzDESD/,特此做出感谢,也推荐大家去看看这个视频加深理解。
