编码器堆栈
原始 Transformer 模型的编码器和解码器的层都是层叠的层。编码器堆栈的每一层都有以下结构:
图 2.3:Transformer 编码器堆栈的一层
原始编码器层结构对于 Transformer 模型的所有N=6 层保持不变。每一层包含两个主要的子层:一个多头注意力机制和一个完全连接的位置逐层前馈网络。
请注意,在 Transformer 模型中,每个主要子层子层(x)周围都有一个残差连接。这些连接将子层的未处理输入x传输到层标准化函数。这样,我们可以确定诸如位置编码之类的关键信息在传输过程中不会丢失。因此,每层的标准化输出如下:
层标准化 (x + 子层(x))
尽管编码器的N=6 层的结构是相同的,但每层的内容并不严格与上一层相同。
例如,嵌入子层仅出现在堆栈的底层。其他五层不包含嵌入层,这保证了经过所有层的编码输入是稳定的。
此外,多头注意力机制从第 1 层到第 6 层执行相同的功能。但它们并不执行相同的任务。每一层都从前一层学习,并探索关联序列中标记的不同方式。它寻找单词的不同关联,就像我们在解决填字游戏时寻找字母和单词的不同关联一样。
Transformer 的设计者引入了一个非常高效的约束。模型的每个子层的输出都具有恒定的维度,包括嵌入层和残差连接。该维度为d[model],可以根据您的目标设置为另一个值。在原始的 Transformer 架构中,d[model] = 512。
d[model]具有强大的影响。几乎所有关键操作都是点积。因此,维度保持稳定,这减少了计算操作的数量,减少了机器的消耗,并使跟踪信息在模型中流动变得更容易。
编码器的这个全局视图展示了 Transformer 的高度优化的架构。在接下来的几节中,我们将深入研究每个子层和机制。
我们将从嵌入子层开始。
输入嵌入
输入嵌入子层使用原始 Transformer 模型中学习的嵌入将输入标记转换为维度为d[model] = 512 的向量。输入嵌入的结构是经典的:
图 2.4:Transformer 的输入嵌入子层
嵌入子层的工作方式类似于其他标准的转换模型。标记器将一个句子转换为标记。每个标记器都有其方法,如 BPE、单词片段和句子片段方法。Transformer 最初使用了 BPE,但其他模型使用其他方法。
目标是相似的,选择取决于选择的策略。例如,应用于序列 Transformer is an innovative NLP model!
的标记器将在一种模型中产生以下标记:
['the', 'transform', 'er', 'is', 'an', 'innovative', 'n', 'l', 'p', 'model', '!']
您会注意到这个分词器将字符串标准化为小写字母并将其截断为子部分。分词器通常会提供一个用于嵌入过程的整数表示。例如:
text = "The cat slept on the couch.It was too tired to get up." tokenized text= [1996, 4937, 7771, 2006, 1996, 6411, 1012, 2009, 2001, 2205, 5458, 2000, 2131, 2039, 1012]
此时,标记化文本中没有足够的信息进行更深入的分析。标记化文本必须被嵌入。
Transformer 包含了一个学习到的嵌入子层。许多嵌入方法可以应用到标记化的输入中。
我选择了word2vec
嵌入方法中谷歌在 2013 年发布的 skip-gram 架构来说明 Transformer 的嵌入子层。skip-gram 会关注窗口中的中心词,并预测上下文词。例如,如果 word(i)是一个两步窗口中的中心词,skip-gram 模型会分析 word(i-2),word(i-1),word(i+1),和 word(i+2)。然后窗口会滑动并重复这个过程。skip-gram 模型通常包含输入层,权重,隐藏层,以及包含标记化输入词的词嵌入的输出。
假设我们需要为以下句子进行嵌入:
The black cat sat on the couch and the brown dog slept on the rug.
我们将专注于两个词,black
和brown
。这两个词的词嵌入向量应该是相似的。
由于我们必须为每个词生成一个大小为d[model] = 512 的向量,我们将为每个词获得大小为512
的向量嵌入:
black=[[-0.01206071 0.11632373 0.06206119 0.01403395 0.09541149 0.10695464 0.02560172 0.00185677 -0.04284821 0.06146432 0.09466285 0.04642421 0.08680347 0.05684567 -0.00717266 -0.03163519 0.03292002 -0.11397766 0.01304929 0.01964396 0.01902409 0.02831945 0.05870414 0.03390711 -0.06204525 0.06173197 -0.08613958 -0.04654748 0.02728105 -0.07830904 … 0.04340003 -0.13192849 -0.00945092 -0.00835463 -0.06487109 0.05862355 -0.03407936 -0.00059001 -0.01640179 0.04123065 -0.04756588 0.08812257 0.00200338 -0.0931043 -0.03507337 0.02153351 -0.02621627 -0.02492662 -0.05771535 -0.01164199 -0.03879078 -0.05506947 0.01693138 -0.04124579 -0.03779858 -0.01950983 -0.05398201 0.07582296 0.00038318 -0.04639162 -0.06819214 0.01366171 0.01411388 0.00853774 0.02183574 -0.03016279 -0.03184025 -0.04273562]]
单词black
现在用512
维度表示。其他嵌入方法可以被使用,d[model]可以有更多维度。
brown
的词嵌入也用512
维度表示:
brown=[[ 1.35794589e-02 -2.18823571e-02 1.34526128e-02 6.74355254e-02 1.04376070e-01 1.09921647e-02 -5.46298288e-02 -1.18385479e-02 4.41223830e-02 -1.84863899e-02 -6.84073642e-02 3.21860164e-02 4.09143828e-02 -2.74433400e-02 -2.47369967e-02 7.74542615e-02 9.80964210e-03 2.94299088e-02 2.93895267e-02 -3.29437815e-02 … 7.20389187e-02 1.57317147e-02 -3.10291946e-02 -5.51304631e-02 -7.03861639e-02 7.40829483e-02 1.04319192e-02 -2.01565702e-03 2.43322570e-02 1.92969330e-02 2.57341694e-02 -1.13280728e-01 8.45847875e-02 4.90090018e-03 5.33546880e-02 -2.31553353e-02 3.87288055e-05 3.31782512e-02 -4.00604047e-02 -1.02028981e-01 3.49597558e-02 -1.71501152e-02 3.55573371e-02 -1.77437533e-02 -5.94457164e-02 2.21221056e-02 9.73121971e-02 -4.90022525e-02]]
为了验证这两个词产生的词嵌入,我们可以使用余弦相似度来查看单词black
和brown
的词嵌入是否相似。
余弦相似度使用欧几里得(L2)范数在一个单位球中创建向量。我们比较的向量的点积是这两个向量之间的余弦。更多关于余弦相似度理论的内容,您可以查阅 scikit-learn 的文档,以及其他很多来源:scikit-learn.org/stable/modules/metrics.html#cosine-similarity
。
在示例的嵌入中,大小为d[model] = 512 的黑色向量与大小为d[model] = 512 的棕色向量之间的余弦相似度是:
cosine_similarity(black, brown)= [[0.9998901]]
skip-gram 产生了两个彼此接近的向量。它检测到 black 和 brown 形成了一个颜色子集的词典。
Transformer 的后续层并不是空手起步。它们已经学会了词嵌入,这些词嵌入已经提供了有关如何关联这些词的信息。
然而,由于没有额外的向量或信息指示序列中单词的位置,很多信息都缺失了。
Transformer 的设计者提出了另一个创新特性:位置编码。
让我们看看位置编码是如何工作的。
位置编码
我们进入 Transformer 的这个位置编码函数时并不知道词在序列中的位置:
图 2.5:位置编码
我们不能创建独立的位置向量,这将对 Transformer 的训练速度产生较高的成本,并使注意力子层过于复杂。这个想法是向输入嵌入中添加一个位置编码值,而不是添加额外的向量来描述序列中一个标记的位置。
工业 4.0 是实用的,且不受模型限制。原始 Transformer 模型只有一个包含词嵌入和位置编码的向量。我们将在 第十五章,从自然语言处理到任务不可知的 Transformer 模型 中探索使用一个独立的矩阵来进行位置编码的分离注意力。
Transformer 期望输出的每个向量都具有固定大小 d[model] = 512(或模型的其他常量值)。
如果我们回到我们在词嵌入子层中使用的句子,我们可以看到黑色和棕色可能在语义上相似,但在句子中相距甚远:
The `black` cat sat on the couch and the `brown` dog slept on the rug.
单词 black
处于位置 2,pos=2
,而单词 brown
处于位置 10,pos=10
。
我们的问题是找到一种方法,向每个单词的词嵌入中添加一个值,以便它具有该信息。但是,我们需要向 d[model] = 512 维度添加一个值!对于每个单词嵌入向量,我们需要找到一种方法,为 black
和 brown
的词嵌入向量的 range(0,512)
维度中的 i
提供信息。
有许多方法可以实现位置编码。本节将重点介绍设计者巧妙地使用单位球来表示位置编码,使用正弦和余弦值,因此保持小但有用。
Vaswani 等人(2017 年)提供了正弦和余弦函数,以便我们可以为每个位置和 d[model] = 512 的词嵌入向量的每个维度 i 生成不同的频率来生成位置编码 (PE):
如果我们从词嵌入向量的开头开始,我们将从一个常数开始 (512
),i=0
,并以 i=511
结束。这意味着正弦函数将应用于偶数,余弦函数将应用于奇数。一些实现方式可能不同。在这种情况下,正弦函数的定义域可能是 ,余弦函数的定义域可能是 。这将产生类似的结果。
在本节中,我们将使用 Vaswani 等人(2017 年)描述的函数方式。将其直译为 Python 伪代码产生了以下代码,用于表示位置向量 pe[0][i]
的位置 pos
:
def positional_encoding(pos,pe): for i in range(0, 512,2): pe[0][i] = math.sin(pos / (10000 ** ((2 * i)/d_model))) pe[0][i+1] = math.cos(pos / (10000 ** ((2 * i)/d_model))) return pe
Google Brain Trax 和 Hugging Face 等公司提供了用于单词嵌入部分和现在位置编码部分的即用型库。因此,你不需要运行我在本节中分享的代码。但是,如果你想探索代码,你可以在 Google Colaboratory 的 positional_encoding.ipynb
笔记本和本章的 GitHub 仓库中的 text.txt
文件中找到它。
在继续之前,你可能想看一下正弦函数的图表,例如 pos=2
。
例如,你可以谷歌以下图表:
plot y=sin(2/10000^(2*x/512))
只需输入绘图请求:
图 2.6:使用 Google 绘图
你将获得以下图表:
图 2.7:图
如果我们回到这一部分正在解析的句子,我们可以看到 black
位于位置 pos=2
,而 brown
位于位置 pos=10
:
The black cat sat on the couch and the brown dog slept on the rug.
如果我们将正弦和余弦函数直接应用于 pos=2
,我们将获得大小为512
的位置编码向量:
PE(2)= [[ 9.09297407e-01 -4.16146845e-01 9.58144367e-01 -2.86285430e-01 9.87046242e-01 -1.60435960e-01 9.99164224e-01 -4.08766568e-02 9.97479975e-01 7.09482506e-02 9.84703004e-01 1.74241230e-01 9.63226616e-01 2.68690288e-01 9.35118318e-01 3.54335666e-01 9.02130723e-01 4.31462824e-01 8.65725577e-01 5.00518918e-01 8.27103794e-01 5.62049210e-01 7.87237823e-01 6.16649508e-01 7.46903539e-01 6.64932430e-01 7.06710517e-01 7.07502782e-01 … 5.47683925e-08 1.00000000e+00 5.09659337e-08 1.00000000e+00 4.74274735e-08 1.00000000e+00 4.41346799e-08 1.00000000e+00 4.10704999e-08 1.00000000e+00 3.82190599e-08 1.00000000e+00 3.55655878e-08 1.00000000e+00 3.30963417e-08 1.00000000e+00 3.07985317e-08 1.00000000e+00 2.86602511e-08 1.00000000e+00 2.66704294e-08 1.00000000e+00 2.48187551e-08 1.00000000e+00 2.30956392e-08 1.00000000e+00 2.14921574e-08 1.00000000e+00]]
我们还为位置 10 获得了大小为512
的位置编码向量,pos=10:
PE(10)= [[-5.44021130e-01 -8.39071512e-01 1.18776485e-01 -9.92920995e-01 6.92634165e-01 -7.21289039e-01 9.79174793e-01 -2.03019097e-01 9.37632740e-01 3.47627431e-01 6.40478015e-01 7.67976522e-01 2.09077001e-01 9.77899194e-01 -2.37917677e-01 9.71285343e-01 -6.12936735e-01 7.90131986e-01 -8.67519796e-01 4.97402608e-01 -9.87655997e-01 1.56638563e-01 -9.83699203e-01 -1.79821849e-01 … 2.73841977e-07 1.00000000e+00 2.54829672e-07 1.00000000e+00 2.37137371e-07 1.00000000e+00 2.20673414e-07 1.00000000e+00 2.05352507e-07 1.00000000e+00 1.91095296e-07 1.00000000e+00 1.77827943e-07 1.00000000e+00 1.65481708e-07 1.00000000e+00 1.53992659e-07 1.00000000e+00 1.43301250e-07 1.00000000e+00 1.33352145e-07 1.00000000e+00 1.24093773e-07 1.00000000e+00 1.15478201e-07 1.00000000e+00 1.07460785e-07 1.00000000e+00]]
当我们直观地将 Vaswani 等人(2017 年)的函数翻译成 Python 并查看结果时,我们希望检查结果是否有意义。
用于单词嵌入的余弦相似度函数对于更好地可视化位置的接近度非常方便:
cosine_similarity(pos(2), pos(10))= [[0.8600013]]
单词 black
和 brown
的位置之间的相似度以及词汇领域(一起使用的单词组)的相似度是不同的:
cosine_similarity(black, brown)= [[0.9998901]]
位置的编码显示出比单词嵌入相似度更低的相似度值。
位置编码已经将这些词分开。请记住,单词嵌入会随用于训练它们的语料库而变化。现在的问题是如何将位置编码添加到单词嵌入向量中。
将位置编码添加到嵌入向量中
Transformer 的作者们发现了一种简单的方法,只需将位置编码向量简单地添加到单词嵌入向量中:
图 2.8:位置编码
如果我们回过头来,例如,提取 black
的单词嵌入,然后将其命名为 y[1] = black,我们就可以将其添加到通过位置编码函数获得的位置向量 pe(2)中。我们将获得输入单词 black
的位置编码 pc(black):
pc(black) = y[1] + pe(2)
解决方案很简单。然而,如果我们按照所示应用它,我们可能会丢失单词嵌入的信息,这将被位置编码向量最小化。
有许多可能性来增加 y[1] 的价值,以确保单词嵌入层的信息可以在后续层中有效使用。
许多可能性之一是向 y[1],即 black
的单词嵌入添加一个任意值:
y[1] * math.sqrt(d_model)
现在我们可以将单词black
的位置向量加到其嵌入向量中,它们都是相同的大小(512
):
for i in range(0, 512,2): pe[0][i] = math.sin(pos / (10000 ** ((2 * i)/d_model))) pc[0][i] = (y[0][i]*math.sqrt(d_model))+ pe[0][i] pe[0][i+1] = math.cos(pos / (10000 ** ((2 * i)/d_model))) pc[0][i+1] = (y[0][i+1]*math.sqrt(d_model))+ pe[0][i+1]
得到的结果是维度为d[model] = 512*的最终位置编码向量:
pc(black)= [[ 9.09297407e-01 -4.16146845e-01 9.58144367e-01 -2.86285430e-01 9.87046242e-01 -1.60435960e-01 9.99164224e-01 -4.08766568e-02 … 4.74274735e-08 1.00000000e+00 4.41346799e-08 1.00000000e+00 4.10704999e-08 1.00000000e+00 3.82190599e-08 1.00000000e+00 2.66704294e-08 1.00000000e+00 2.48187551e-08 1.00000000e+00 2.30956392e-08 1.00000000e+00 2.14921574e-08 1.00000000e+00]]
对单词brown
和序列中的所有其他单词应用相同的操作。
我们可以将余弦相似性函数应用于black
和brown
的位置编码向量:
cosine_similarity(pc(black), pc(brown))= [[0.9627094]]
现在,通过我们应用的三个表示单词black
和brown
的余弦相似性函数,我们对位置编码过程有了清晰的认识:
[[0.99987495]] word similarity [[0.8600013]] positional encoding vector similarity [[0.9627094]] final positional encoding similarity
我们看到初始单词嵌入的相似性较高,值为0.99
。然后我们看到位置编码向量的位置 2 和 10 使这两个单词的相似性值降低为0.86
。
最后,我们将每个单词的单词嵌入向量添加到其相应的位置编码向量中。我们发现,这使得两个单词的余弦相似度为0.96
。
每个单词的位置编码现在包含初始单词嵌入信息和位置编码值。
位置编码的输出导致了多头注意力子层。
子层 1:多头注意力
多头注意力子层包含八个头,并带有后层规范化,将在子层输出中添加残差连接并对其进行规范化:
图 2.9:多头注意力子层
本节开始讲解注意力层的架构。接着,以 Python 中的一个小模块实现了多头注意力的示例。最后,描述了后层规范化。
让我们从多头注意力的架构开始。
多头注意力的架构
编码器堆叠的第一层的多头注意力子层的输入是包含每个单词的嵌入和位置编码的向量。堆叠的下一层不会重新开始这些操作。
输入序列每个单词x[n]的向量维度是 d[model] = 512:
pe(x[n])=[d[1]=9.09297407e^-01, d[2]=-4.16146845e^-01, …, d[512]=1.00000000e+00]
每个单词x[n]的表示现在已经变成了512维的向量 d[model] = 512。
每个单词都映射到所有其他单词,以确定它在序列中的位置。
在下面的句子中,我们可以看到它可能与序列中的cat
和rug
相关:
Sequence =The cat sat on the rug and it was dry-cleaned.
模型将训练以确定it
是与cat
还是rug
相关联。我们可以通过使用当前的512维度训练该模型进行大量的计算。
但是,通过分析一个d[model]块的序列,我们只能得到一个观点。此外,使用现在的512维度将需要相当长的计算时间来找到其他观点。
一个更好的方法是将每个单词x[n]的512维度划分为8个64维度。
然后我们可以并行运行 8 个“头”来加速训练,并获得每个单词如何与另一个相关的 8 个不同表示子空间:
图 2.10:多头表示
现在可以看到有8
个并行运行的头。其中一个头可能认为it
和cat
很合适,另一个认为it
和rug
很合适,另一个认为rug
和dry-cleaned
很合适。
每个头的输出是形状为x * d[k]的矩阵Z[i]。多头注意力输出Z定义为:
Z = (Z[0], Z[1], Z[2], Z[3], Z[4], Z[5], Z[6], Z[7])
然而,Z必须被连接,这样多头子层的输出不是尺寸的序列,而是xm * d[model]矩阵的一行。
在退出多头注意力子层之前,Z的元素被连接:
MultiHead(output) = Concat(Z[0], Z[1], Z[2], Z[3], Z[4], Z[5], Z[6], Z[7]) = x,d[model]
请注意,每个头都被连接成一个具有维度d[model] = 512 的z。多头层的输出遵循原始 Transformer 模型的约束。
在注意机制的每个头h[n]内,“单词”矩阵有三种表示:
- 一个查询矩阵(Q)的维度为d[q] = 64,它寻求所有“单词”矩阵的键-值对。
- 一个键矩阵(K)的维度为d[k] = 64,它将被训练以提供一个注意力值。
- 一个值矩阵(V)的维度为d[v] = 64,它将被训练以提供另一个注意力值。
注意力被定义为“缩放点积注意力”,它在下面的方程中表示,我们将Q、K和V代入其中:
所有矩阵都具有相同的维度,这样可以相对简单地使用缩放点积来获得每个头的注意力值,然后连接 8 个头的输出Z。
要获得Q、K和V,我们必须使用它们的权重矩阵Q[w]、K[w]和V[w]训练模型,它们具有d[k] = 64 列和d[model] = 512 行。例如,Q是通过x和Q[w]的点积获得的。Q将具有d[k] = 64 的维度。
您可以修改所有参数,例如层数、头部、d[model]、d[k]和 Transformer 的其他变量,以适应您的模型。本章描述了由Vaswani等人(2017 年)提出的原始 Transformer 参数。在修改或探索其他人设计的原始模型变体之前,了解原始架构是至关重要的。
Google Brain Trax、OpenAI 和 Hugging Face 等提供了可供我们在本书中使用的即用型库。
然而,让我们打开 Transformer 模型的机制,并在 Python 中动手实现来说明我们刚刚探索的架构,以便可视化该模型的代码并用中间图形表示出来。
我们将使用基本的 Python 代码,只用numpy
和一个softmax
函数在 10 个步骤中运行注意力机制的关键方面。
请记住,工业 4.0 开发者将面临同一算法的多种架构挑战。
现在让我们开始构建我们模型的Step 1以表示输入。
步骤 1:表示输入
将Multi_Head_Attention_Sub_Layer.ipynb
保存到你的 Google Drive(确保你有一个 Gmail 帐户),然后在 Google Colaboratory 中打开它。笔记本在本章的 GitHub 存储库中。
我们将从仅使用最小的 Python 函数开始,以低层次理解 Transformer 的工作原理。我们将使用基本代码探索多头注意力子层的内部工作:
import numpy as np from scipy.special import softmax
我们正在构建的注意力机制的输入被缩小到d[模型]==4,而不是d[模型]=512。这将输入x的向量维度缩小到d[模型]=4,更容易可视化。
x包含每个具有 4 个维度的3
个输入,而不是512
个:
print("Step 1: Input : 3 inputs, d_model=4") x =np.array([[1.0, 0.0, 1.0, 0.0], # Input 1 [0.0, 2.0, 0.0, 2.0], # Input 2 [1.0, 1.0, 1.0, 1.0]]) # Input 3 print(x)
输出显示我们有 3 个d[模型]=4 的向量:
Step 1: Input : 3 inputs, d_model=4 [[1\. 0\. 1\. 0.] [0\. 2\. 0\. 2.] [1\. 1\. 1\. 1.]]
我们模型的第一步准备好了:
图 2.11:多头注意力子层的输入
现在我们将权重矩阵添加到我们的模型中。
步骤 2:初始化权重矩阵
每个输入都有 3 个权重矩阵:
- Q[w]用来训练查询
- K[w]用来训练键
- V[w]用来训练值
这三个权重矩阵将应用于模型中的所有输入。
Vaswani等人(2017)描述的权重矩阵是d[K]==64 维。但是,让我们将矩阵缩小到d[K]==3。维度被缩小到3*4
权重矩阵以更容易可视化中间结果并与输入x执行点积。
这个教育笔记本中矩阵的大小和形状是任意的。目标是通过注意力机制的整个过程。
三个权重矩阵是从查询权重矩阵开始初始化的:
print("Step 2: weights 3 dimensions x d_model=4") print("w_query") w_query =np.array([[1, 0, 1], [1, 0, 0], [0, 0, 1], [0, 1, 1]]) print(w_query)
输出是w_query
权重矩阵:
w_query [[1 0 1] [1 0 0] [0 0 1] [0 1 1]]
现在我们将初始化关键权重矩阵:
print("w_key") w_key =np.array([[0, 0, 1], [1, 1, 0], [0, 1, 0], [1, 1, 0]]) print(w_key)
输出是关键权重矩阵:
w_key [[0 0 1] [1 1 0] [0 1 0] [1 1 0]]
最后,我们初始化值权重矩阵:
print("w_value") w_value = np.array([[0, 2, 0], [0, 3, 0], [1, 0, 3], [1, 1, 0]]) print(w_value)
输出是值权重矩阵:
w_value [[0 2 0] [0 3 0] [1 0 3] [1 1 0]]
我们模型的第二步准备好了:
图 2.12:添加到模型中的权重矩阵
现在我们将通过输入向量乘以权重矩阵来获得Q、K和V。
步骤 3:矩阵乘法以获得 Q、K 和 V
我们现在将输入向量乘以权重矩阵,以获得每个输入的查询、键和值向量。
在这个模型中,我们假设所有输入都有一个w_query
、w_key
和w_value
权重矩阵。其他方法也是可能的。
让我们首先将输入向量乘以w_query
权重矩阵:
print("Step 3: Matrix multiplication to obtain Q,K,V") print("Query: x * w_query") Q=np.matmul(x,w_query) print(Q)
输出是Q[1]==64 的向量=[1, 0, 2],Q[2]=[2,2, 2],Q[3]=[2,1, 3]:
Step 3: Matrix multiplication to obtain Q,K,V Query: x * w_query [[1\. 0\. 2.] [2\. 2\. 2.] [2\. 1\. 3.]]
现在我们将输入向量乘以w_key
权重矩阵:
print("Key: x * w_key") K=np.matmul(x,w_key) print(K)
我们得到K[1]= [0, 1, 1],K[2]= [4, 4, 0],以及K[3]= [2 ,3, 1]的向量:
Key: x * w_key [[0\. 1\. 1.] [4\. 4\. 0.] [2\. 3\. 1.]]
最后,我们将输入向量乘以w_value
权重矩阵:
print("Value: x * w_value") V=np.matmul(x,w_value) print(V)
我们得到V[1]= [1, 2, 3],V[2]= [2, 8, 0],以及V[3]= [2 ,6, 3]的向量:
Value: x * w_value [[1\. 2\. 3.] [2\. 8\. 0.] [2\. 6\. 3.]]
我们模型的第三步准备好了:
图 2.13:生成了Q、K和V
我们有了需要计算注意力分数的Q、K和V值。
步骤 4:缩放注意力分数
注意力头现在实现了原始的 Transformer 方程:
步骤 4 关注Q和K:
对于这个模型,我们将四舍五入 = = 1.75 为 1,并将值代入方程的Q和K部分:
print("Step 4: Scaled Attention Scores") k_d=1 #square root of k_d=3 rounded down to 1 for this example attention_scores = (Q @ K.transpose())/k_d print(attention_scores)
中间结果显示为:
Step 4: Scaled Attention Scores [[ 2\. 4\. 4.] [ 4\. 16\. 12.] [ 4\. 12\. 10.]]
步骤 4 现在已完成。例如,x[1]的分数为[2,4,4]跨越了K向量的头部显示为:
图 2.14:输入#1 的缩放注意力分数
现在注意力方程将为每个向量的中间分数应用 softmax。
步骤 5:每个向量的缩放 softmax 注意力分数
现在我们对每个中间注意力分数应用 softmax 函数。与进行矩阵乘法不同,让我们放大到每个单独的向量:
print("Step 5: Scaled softmax attention_scores for each vector") attention_scores[0]=softmax(attention_scores[0]) attention_scores[1]=softmax(attention_scores[1]) attention_scores[2]=softmax(attention_scores[2]) print(attention_scores[0]) print(attention_scores[1]) print(attention_scores[2])
我们为每个向量得到了缩放的 softmax 注意力分数:
Step 5: Scaled softmax attention_scores for each vector [0.06337894 0.46831053 0.46831053] [6.03366485e-06 9.82007865e-01 1.79861014e-02] [2.95387223e-04 8.80536902e-01 1.19167711e-01]
步骤 5 现在已完成。例如,所有键的x[1]的分数的 softmax 是:
图 2.15:所有键的输入#1 的 softmax 分数
现在我们可以用完整的方程计算最终的注意力值。
步骤 6:最终的注意力表示
现在我们可以通过将V代入来完成注意力方程:
我们首先计算输入x[1]对步骤 6 和 7 的注意力分数。我们为一个词向量计算一个注意力值。当我们到达步骤 8 时,我们将将注意力计算推广到另外两个输入向量。
为了获得x[1]的 Attention(Q,K,V),我们将中间注意力分数逐个与 3 个值向量相乘,以放大方程的内部工作:
print("Step 6: attention value obtained by score1/k_d * V") print(V[0]) print(V[1]) print(V[2]) print("Attention 1") attention1=attention_scores[0].reshape(-1,1) attention1=attention_scores[0][0]*V[0] print(attention1) print("Attention 2") attention2=attention_scores[0][1]*V[1] print(attention2) print("Attention 3") attention3=attention_scores[0][2]*V[2] print(attention3) Step 6: attention value obtained by score1/k_d * V [1\. 2\. 3.] [2\. 8\. 0.] [2\. 6\. 3.] Attention 1 [0.06337894 0.12675788 0.19013681] Attention 2 [0.93662106 3.74648425 0\. ] Attention 3 [0.93662106 2.80986319 1.40493159]
步骤 6 完成。例如,已计算了每个输入的x[1]的 3 个注意力值:
图 2.16:注意力表示
现在需要将注意力值相加。
Transformers 自然语言处理(一)(2)https://developer.aliyun.com/article/1514346