Skip to content

从零学transformer

encoding和decoding

在hugging face中,有对transformer的入门介绍:https://huggingface.co/learn/llm-course/zh-CN/chapter1/4

个人理解中,encoding模型将语句映射为高维空间,比如分类;decoding模型可以把语句在当前维度空间中进行扩展,比如续写。

还有encoding-decoding模型,结合了两者的特性,应用为翻译,问答这种场景。

embedding

虽然中文的翻译是词嵌入,但是挺不知所云的。embedding可以理解为将人能看懂的词,转换为机器能看懂的向量。毕竟机器只能处理数字。

self attention

如果抛弃一些AI相关的知识,一个句子由多个词组成,每个词都有自己的含义。但是词和词的组合是有某种联系的,一个段子是“要断章取义”,取自《不要断章取义》。

再引入一些基本的AI知识,词不是模型处理的基本单元,token才是。token和token之间的逻辑关系,就是attention来解决的问题。

在已经简单理解embeding后,用什么方法来建立向量和向量之间的逻辑关系?需要抽象出来另外几个向量,毕竟向量只能和向量进行计算。这里出现的就是Q,K,V向量。

那么什么是Q,K,V向量?

Q, query,查询向量。 K, key,键向量。 V, value,值向量。

attention如何计算呢?根据第一性原理,如果要计算一个词的attention,肯定要和这个词的所有其他词进行计算,这样才能知道这个词和其它词之间的联系。虽然思路是这样,但是实现上另有独到之处。

第一步,每个输入的词向量都会计算出对应的Q,K,V向量。计算方法是将词向量分别与权重矩阵WQ,WK,WV进行矩阵乘法。WQ, WK, WV是训练过程中学习到的参数。

第二步,就可以计算self attention了。将一个词的查询向量q和包括它自身的所有k向量进行点积,可以得到一系列表示相关性的值。相关性越高,值越大。那么显然,一个词和它自身的相关性最高,所以点积值也最高。(但实际上这不是所期望的,有其它方法可以避免词只关注它自身。)

第三步和第四步,可以认为是归一化的过程。这里再引入一点知识,softmax函数,可以将一系列表示相关性的值,转换为一个概率分布。

第五步,将每个值向量v,乘以上面得到的softmax的概率分布。

第六步,将第五步的加权后的向量相加,就得到了这个词的attention向量z。这个z就可以交给feed forward层了。

矩阵化attention计算

一个词的attention计算是上面的流程,但是真算的时候不会这么串行算的。 将所有的词向量打包成矩阵X(X的每一行是一个词向量),分别与权重矩阵WQ,WK,WV进行矩阵乘法,就可以得到所有的词的Q,K,V向量。

将步骤二到六合并为一个公式,softmax(Q × K^T / √d_k)V = Z

multi attention

根据上文,注意力和k和q点积大小相关,这种计算方法下,一个词肯定和它自身最相关。但是,对于“小明太累了,他想休息”这个句子,“他”如何和“他”最相关显然对于理解语义是没作用的。应该让“他”注意到“小明”上。

上文中,只有一组WQ,WK,WV。只能表示一个维度的“注意力”。大模型很难有解释性,这个维度可能表示时间,空间,情感,语法或任意什么维度的一种,那么最终得到的注意力可能不那么准确。引入多头后,就有多组WQ,WK,WV(文中说transformer有8个注意力头),所以最终会得到8个Z矩阵。

但是feed forward层只接收一个Z矩阵,所以需要将8个Z矩阵变成一个。

文中的方法是,将8个Z矩阵拼接起来,然后再乘以一个额外的权重矩阵W_O,得到的一个最终的Z。

position encoding

在矩阵化attention计算中,流程如下:

  1. 将所有的词向量打包成了矩阵X
  2. 将X分别与权重矩阵WQ,WK,WV进行矩阵乘法,得到Q,K,V
  3. Q和K_T进行点积,得到一系列表示相关性的值,然后使用softmax归一化
  4. 上一步得到的矩阵乘以V,得到最终的Z矩阵
  5. 多头合并,然后传递给feed forward层

从上面步骤中,矩阵乘来乘去,虽然词向量已经被变换了数次,但是都是并行计算的,也就是传递给模型的“我爱你”和“你爱我”再模型的视角下是一个东西。如何保留并行计算的高效率,并且还要保留语序的信息?

解决办法是在词向量中引入位置相关的信息,那么在后续的注意力中,位置也会成为一个维度信息被考虑。当然,位置编码也是一个深入的话题,这里不再展开。

residuals

残差实际上是一个数学问题。大模型的训练简单来说就是在找一个函数的参数,使得函数的输出和目标之间的差最小。寻找这些参数的方法是梯度下降,梯度下降通过链式法则计算,如果每一层的偏导数都很小,最终的梯度就会被计算成0了,毕竟计算机只能处理有限的精度,这时就出现了梯度消失。避免梯度消失的方法就是残差。transformer中每一个子层后都会跟一个残差链接(进行ADD & Norm操作)。

decoder side

上面的内容涵盖了encoder的大部分内容。encoder处理输入序列后,会得到一组注意力向量K和V。每个decoder在attention层中都会使用这些注意力向量。最后经过linear和softmax后,输出内容。

后续步骤会重复这个过程,直到decoder输出完毕。解码器中也会嵌入位置编码,来保留语序的信息。

解码器的attention层和编码器行为不同,解码器只能关注输出序列中较早的位置,这个行为就像你不能用测试集来训练模型一样。

linear and softmax layer

解码器最终给出的还是一个浮点向量,最后一步是“翻译”成人话了。

linear 层是一个全连接层,将解码器的输出向量映射到一个高维空间,称为logits向量。如果模型学会了1000个词,那么logits向量的维度就是1000。softmax层会将logits向量转换为一个概率分布,每个元素表示对应词的概率。最后,将概率分布中的最大值对应的词作为模型的输出。

参考

https://jalammar.github.io/illustrated-transformer/