Transformer介绍(二):注意力机制

时间:2024-11-12 17:43:20

transformer是大语言模型的核心技术之一,它首次出现于2017年的一篇著名论文《Attention Is All You Need》,论文原文:https://arxiv.org/abs/1706.03762

看过我上篇文章《Transformer介绍(一)》的都知道,模型的目标是接收一段文本,预测下一个词。输入的文本被切分成很多小块,每个小块是一个token,通常是单词、单词片段或符号。

为了便于理解,我们简化一下,将token视为单词。transformer的第一步是将每个token关联到高维向量,即embedding(嵌入向量)。

 在所有可能的嵌入向量构成的高维空间中,方向可以对应语义。例如,方向对应性别,在该空间中,加上一个方向,就能将男性名词嵌入,转到对应女性名词的嵌入。

在高维空间中,还有很多的方向可以对应多种词义。transformer的目标是逐步调整这些嵌入(embedding),使它们不仅编码(encode)每个词,还能融入更丰富的上下文含义。

比如,mole在不同的语境有不同的含义。transformer的第一步切分文本,并将每个token对应一个向量,此时三个mole对应的都是同一个向量,因为初始的token嵌入没有参照上下文查找表。

接下来周围的信息传递到该嵌入向量。嵌入空间中有多个方向,编码了mole这个词多种不同的含义。

 

训练的好的attention blocks能计算出给generic embedding加什么向量,才能把它移动到上下文对应的具体方向上。

attention blocks不仅精细化了一个词的含义,还让模型相互传递这些嵌入向量所包含的信息。

所有的向量流经网络后,经过了很多层的attention blocks,此时预测下一个token的计算过程,完全基于序列中最后一个向量。

每个词的初始嵌入是一个高维向量,只编码了该单词的含义,没有关联上下文。然后还编码了词的位置信息,现在可以知道词是什么,以及在文中的什么位置。

用E表示这些嵌入向量,目标是通过一系列的计算产生一组新的更精确的嵌入向量。比如那些经过形容词修饰的名词,所对应的向量。

在深度学习中,我们希望多数计算都是矩阵向量乘法,其中矩阵填满了可调的参数,需要模型基于数据学习后调整。模型的实际行为往往很难解释,因为它是通过调整大量参数来最小化代价函数。

当逐一分析这些带有参数的矩阵时,为了帮助理解,我们设想出一个具体的行为。

第一步,可以想成是每个名词(如creature),问,我前面有形容词吗?然后前面的 fluffy 和 blue 回答,我就是形容词。

这样的提问被编码为另一个向量,即另一组数字,我们称它为这个词的查询。查询向量的维度比嵌入向量小很多,只有128维。

 要计算查询向量Q,先取一个矩阵,记为W_Q,再乘以嵌入向量E。

把查询矩阵分别与文中的所有embedding(嵌入向量)相乘 ,即给每个token算出一个查询矩阵。这个矩阵内部的数值都是模型的参数,具体的行为模式都是从数据中学习的。

这个矩阵在某个注意力头中的作用,有点难以理解。为了方便起见,想象一个它能学会的例子。假设这个查询矩阵,将嵌入空间中的名词,映射到较小的查询空间中的某个方向,用向量来编码”寻找前置形容词“的概念。至于非名词的嵌入会怎样,谁知道呢?现在我们只关注名词。

同时我们还需要第二个矩阵,即键矩阵(key matrix),也会与每个嵌入向量(embedding)相乘,产生第二个向量序列,称为键(key)。从概念上讲,可以把 视为想要回答 查询

 这个键矩阵也充满了可调参数,跟查询矩阵一样,它也会将嵌入向量映射到相同的低维度空间。 当键与查询的方向对齐时,可以认为它们相匹配。

就本例而言,键矩阵会将形容词fluffy和blue映射到名词creature对应的查询向量高度对齐的方向上。为了衡量每个键与每个查询的匹配程度,我们要计算所有可能的【键-查询】之间的点积。可以想象成充满圆点的网格,圆点越大,点积就越大,键与查询就越对齐。

如果由fluffy和blue生成的键,确实与creature所产生的查询高度对齐,那么这2个位置的点积就会是较大的正数,用机器学习中的术语说,fluffy和blue的嵌入(embedding)注意(attention)到了creature的嵌入(embedding)。反过来,the等词的键与creature的查询之间的点积是较小值或负值,也就是这两个词互不相关。网格中的值,可以是负无穷到正无穷的任何实数。表示的是每个词与其他词含义有多相关。这些分数的用法,是对每一列进行加权求和。权重为相关性,这样数值就不能是负无穷到正无穷,而是要介于0到1之间,并且每列总和为1,就像概率分布一样。

对每列应用softmax函数进行归一化,然后填入网格,此时就能将每一列看作权重,表示左侧的键与顶部的查询的相关度。我们称这个网格为注意力模式(attention pattern)。

 

原版的transformer论文里用了一种非常简洁的写法。这里的Q和K分别包含了所有的查询向量和键向量。即通过将嵌入向量与查询矩阵和键矩阵相乘,得到小向量。

 

分子中的表达式非常简洁的表示了所有可能的键-查询 之间点积的网格。为了数值稳定性,将所有点积除以 键-查询 空间维度的平方根。然后整个表达式外层的softmax函数,应理解为逐列的计算。

 

在训练过程中,对给定文本运行模型时,模型会根据正确预测出下一个词的概率高低,来进行奖惩,并稍微调整各个权重。 而效率更高的一种方法是,让它同时预测,每个初始token子序列之后, 所有可能的下一个token。比如之前这个短语 the fluffy blue creature,它可以同时预测creature的下一个词,以及the的下一个词。

这样,一个训练样本就能提供多次训练机会。就注意力模式而言,这意味着不能让后词影响前词,不然就会泄露接下来的答案。所以希望这些代表着后面token影响前面的位置,能被强制变为0。你可能会想将它们直接设为0,但这样,每列总和就不是1了,不再是归一化了。所以常见的方法是,在应用softmax之前,先把它们设为负无穷,这样应用softmax后,它们就都会变为0,且列仍保持归一化。这一过程称为掩码(masking)。

也有的注意力机制,不应用掩码,在我们的GPT示例中,尽管掩码在训练阶段比运行阶段更重要。例如当作聊天机器人运行时,有两个阶段都会采用掩码,以避免后方token影响到前方的token。

注意力模式,其大小等于上下文长度的平方,这就是为什么上下文长度会成为大语言模型的巨大瓶颈。而扩大上下文长度,也绝非易事。可想而知,出于对上下文窗口的渴求,近来年,注意力机制出现了一些变体。Sparse Attention Mechanisms 、Blockwise Attention 、Linformer、Reformer、Ring Attention 、Longformer、Adaptive Attention Span等,旨在使上下文更具扩展性。

计算注意力模式,就能让模型推断出,每个词与其他哪些词相关;然后再去更新嵌入向量,把各个词的信息传递给与之相关的其他词。例如,让fluffy的embedding改变creature的embedding,使其移动到12000多维嵌入空间的另一个方向,从而更具体的编码(encode)fluffy creature。

多头注意力(Mutil-headed attention)

对于多头注意力,方法略有不同,会用到第三个矩阵,我们称之为值矩阵(Value Matrix)。将它乘以前面词的嵌入,得到的结果是值向量。这也是你要给后词的嵌入,所加的向量。因此,这个值向量与嵌入向量处于同一个高维空间。

值矩阵乘以一个词的嵌入向量,可以理解为,如果这个词要调整目标词的含义,那么要反应这一点,得对目标词的嵌入加上什么向量呢?

   值向量可以认为是与对应的键向量相关联,对于网格中的每一列,给每个值向量乘以该列的对应权重。例如,对于creature一词的嵌入,要加上fluffy和blue的值向量的大部分比例,而其他词的值向量的比例等于0或趋近于0。

最后,为了更新该列对应的嵌入向量,也就是最初没有上下文含义的creature的词嵌入,要做的是,将该列中所有带权值向量加和,得到想要引入的变化量,记为\Delta E,然后把它加到原始嵌入向量上,预期是能得到一个更精准的向量,编码了更丰富的上下文信息。比如a fluffy blue creature

当然,不能只对一个嵌入进行处理,而是对所有列分别进行加权求和,得到一系列变化量,将所有的变化量加到对应的嵌入向量上,从而通过注意力模块,得到一系列更精准的嵌入向量。整个过程就是单头注意力机制(single head of attention)。

这个过程由三种充满了可调参数的矩阵实现,即Query、Key和Value。

上篇文章中讲到GPT-3模型本身的总参数量,键矩阵和查询矩阵各有12288列对应嵌入维度,128行对应较小的 键-查询 空间维度,这样就各有约150万个参数。而对于值矩阵,它是一个12288列和12288行的方阵,因为它的输入和输出都存在高维的嵌入空间,12,288x12,288=150,994,994,也就是增加约1.5亿个参数。

 

设计上,值矩阵的参数量可以比键矩阵和查询矩阵多几个数量级,但实际上更为高效的做法是,让值矩阵所需要的参数量,等于键矩阵和查询矩阵的参数量之和。 对于并行运行多个注意力头来说,这点非常重要。

具体做法是,将值矩阵分解为2个小矩阵相乘;从概念上讲,建议把它整体视为一个线性映射。输入和输出都在这个高维的嵌入空间。

例如,把blue的嵌入向量,映射到相加后,把名词变蓝 的向量。实践上分两步,下图中第二个矩阵的行数较少,通常等于键-查询 空间的维度,可以看做是将较大的嵌入向量降维到较小的空间,暂记为Value矩阵。下图中的第一个矩阵,则是从小空间映射回嵌入空间,得到的是用于实际更新的向量,暂记为Value↑矩阵。在线性代数中,这种操作的实质是,对最大矩阵进行低秩分解。

全加起来,一个注意力头包含约630万个参数。

 目前讨论的都是自注意力(self-attention)的 头,与其他模型中的变体 交叉注意力(cross- attention)头有所不同。交叉注意力涉及的模型会处理两种不同类型的数据,比如原文与正在被翻译出来的原文,或是语音音频与正在被转录出来的文字。

交叉注意力和自注意力几乎相同,唯一的区别是,键和查询矩阵作用于不同的数据集。 例如,文本翻译模型中,键可能来自于一种语言,而查询则来自另一种语言,这个注意力模式就可以描述,一种语言中的哪些词对应另一种语言中的哪些词。在这种情况下,通常不会用到掩码,因为不存在后面token影响前面token的问题。

接下来,就是多次重复这个过程。在本文的例子中,关注的是形容词更新名称的含义,但实际上下文影响词义的方式多种多样,例如,【他撞毁了车】,他们撞毁了出现在车之前,这意味着对车的形状和结构发生了改变。

还有很多联系不是语法层面上的,例如,【巫师】和【Harry】出现在同一段话中,就表明它很有可能是指【哈利·波特】,而如果这段话中出现了【女王】、【萨塞克斯】、【威廉】等词, 那么【Harry】的词嵌入也许该更新到【哈里王子】方向。

对于你能想象到的每一种不同的上下文更新方式,键矩阵和查询矩阵的参数都会有所改变,以捕获不同的注意力模式,而值矩阵的参数也会因嵌入的更新值不同而改变。

这些矩阵实际情况下的真实情况难以解释,无论如何,权重已被训练完成,能让模型高效预测下一个token。

Transformer内完整的注意力模块,是由多头注意力组成,大量并行的执行这些操作,而每个头都有不同的键、查询、值矩阵。例如,GPT-3每个模块内使用96个注意力头,这意味着有96个不同的键和查询矩阵,产生96种不同的注意力模式,然后每个注意力头都有独特的值矩阵,用来产生96个值向量序列,全部都将以对应注意力模式作为权重,分别进行加权求和。即对应上下文中的每个位置,也就是每个token,每个头都会给出一个,要加入到该位置的嵌入中的变化量。

而咱们要做的就是,把各个头给出的变化量加起来,然后给该位置的初始嵌入加上这个加和。这个总和,就是多头注意力模块输出的一列,也就是这个通过模块,能够得到一个更精准的嵌入。

 总的来说,通过并行多头运算,模型能学习到根据上下文来改变语义的多种方式。

继续统计参数量,一共有96个头,每个头都包含这4个矩阵,每个多头注意力模块最终会有约6亿个参数。

前面说到,值矩阵被分解为2个小矩阵相乘,我们暂且记为Value↑矩阵和Value↓矩阵。每个注意力头都会有这对矩阵,这种设计是可行的。在一些论文或实际的实现方式中会有些不同。这些Value↑矩阵会合在一起被称作输出矩阵,与整个多头注意力模块相关联。而单个注意力头的值矩阵,则单指第一步的矩阵,即将嵌入向量投影到低维度空间的Value↓矩阵。

 抛开技术上的细微差别,我们知道,流经transformer的数据,并不只是经过单个注意力模块。

首先,它之后还会经过多层感知器模块;

其次,它还会多次重复这两种操作。这意味着,某些词吸收了上下文信息后,含义细致的嵌入向量还有更多机会被周围含义细致的词语所影响。 越是接近网络的深处,每个嵌入就会从其他嵌入中,吸收越多的含义,使得嵌入本身也越来越丰富细致。 对于输入的内容,这样能提炼出更高级、更抽象的概念,而不仅仅只是修饰和语法结构,也许还包括情感、语气和诗意,以及话题涉及的科学原理等。

再次回到计数表,GPT-3包含96个不同的层,因此,键、查询、值的参数总数还得再乘以96,使得所有注意力头的参数总数约580亿个。

这约占网络总参数量1750亿的三分之一,尽管注意力机制吸引了所有的注意力,但实际上更多的参数来自于其间的模块。 

 注意力机制成功的主要原因,并不在于它能实现什么特定的行为,而是它的可并行性,这样就能让GPU在短时间内进行大量计算。

在过去一二十年里,深度学习的一个重要经验是,仅靠扩大模型规模就能为模型性能带来质的飞跃;而可并行架构的巨大优势,就在于能轻易扩大规模。