Transformer模型学习笔记

Transformer模型学习笔记

  • 前言
  • 回顾
  • 参考资料
  • 解读
    • 1.High-level的看一下,transformer大致是个什么样子
    • 2.输入语句中词的顺序(Positional Encoding)
    • 3.进入Encoder层
    • 4.Self-Attention层
    • 5.多头机制 Multi-head
    • 6.Multi-head具体在代码里怎么实现
    • 7.Add&Norm以及Feed Forward
    • 8.Attention Mask机制
    • 9.完整解码部分
    • 10.最后输出层

前言

Google研究菌曰: 在transformer模型之前,我们做机器翻译等事情(论文原话: 我们做转录模型(transduction model)) 都是用循环神经网络(RNN)或者卷积神经网络(CNN)作为基本单元,搭建一个包含encoder和decoder的模型. 虽然效果不错,但是显然还有很多进步空间. 既然拿那么多钱,上班又不是996,不如整点新的东西? 于是有了transformer模型.

回顾

回顾下整体流程, 为啥会想到要去创造transformer这个东西.
做机器翻译?
–>那咱们搭一个具有encoder-decoder结构的模型. 其中seq2seq是最常用的encoder-decoder模型–>模型里的小单元用基本结构的RNN或者基本结构的CNN.
训练完发现对长句记忆效果不理想,模型记不住之前的信息?发生梯度消失?
–>采用RNN的变体结构LSTM
翻译效果不太好?不同输入单词对后面的影响以及重要程度没有体现? (这种情况被称为分心模型)
–>加入注意力机制(attention),让每个单词都关注它最应该关心的信息,即从"分心模型"变成了"带注意力模型"(打比方: 听老师上课时,要有目的的开小差,而不是肆无忌惮的开小差或者从头认真听到尾)
比起seq2seq有什么改进?
–> 原始的seq2seq模型,decoder部分用的信息全来自encoder部分最后一个时间片产生的向量, 显然向量长度有限->导致记录的信息也有限. 加入attention后, encoder部分每个单词都会产生额外信息,decoder部分模型会加入这个额外信息.

那现在效果还可以,但是attention机制依旧无法并行运算, 所以训练速度仍然很慢, 咋滴办? (且attention忽略了输入句中文字间和目标句中文字间的关系)
–>Transformer模型!! Transformer模型里的self-attention机制可以实现并行计算,并且代替seq2seq结构.

参考资料

论文原文: Attention is all you need
英文解读–> https://jalammar.github.io/illustrated-transformer/
解读的中文翻译–> https://blog.csdn.net/yujianmin1990/article/details/85221271

解读

这是原论文里Transformer的结构图(该图我以后会重画, 里面标注有些不是很正确, 容易引起误解)
Transformer模型学习笔记_第1张图片

1.High-level的看一下,transformer大致是个什么样子

就是下面这个样子. 整个模型包含encoder部分和decoder部分,依旧是encoder输入,decoder输出.
Transformer模型学习笔记_第2张图片
稍微画仔细点,长这样
Transformer模型学习笔记_第3张图片
需要注意的是:
6个encoder结构互相相同,但是不共享参数.
6个decoder结构互相相同,但是也不共享参数.

看完了,那看看每个encoder编码器里的具体结构. 长这样↓
Transformer模型学习笔记_第4张图片
encoder里的第一个结构叫做 self-attention 层. 所有向量进入encoder后,都会进入self-attention层. self-attention层帮助encoder编码器在编码(处理)某个特定单词时,同时关注同一个输入句子里其他单词的信息.
然后self-attention层的输出流向一个前向网络层(Feed Forward Neural Network),每个输入位置对应的前向网络是独立互不干扰的.

decoder和encoder长得差不多,结构如下
Transformer模型学习笔记_第5张图片
OK,到这里,整个Transformer最粗略的结构讲完了.
下一节深入每个结构的细节

2.输入语句中词的顺序(Positional Encoding)

我们先来看一下, 送到Encoder之前, 模型对输入的X是怎么处理的.

一些先验知识:
在NLP里,拿到单词,比较常见的情况下,我们都会把它转成向量. 这个操作称为embedding. 这个向量的长度我们称为embedding size, 它是一个超参数.
我们假设每个单词已经转换成1*4的一个向量(或者叫Tensor), 比如下图 (当然实际场景里尺寸没有那么小,通常尺寸是1*512或者1*1024)
在这里插入图片描述
那么对于一个句子,就是由一系列向量组成的一个list. [x1,x2,x3…], list的长度就是句子长度(对于上图这个例子, 那么这个list的size就是3*4)
当然由于我们都是进行矩阵运算,需要每个list长度都相同. 此时我们会设定一个统一长度(即max_len), 这个长度是一个超参数, 通常有两种方法:
1.使用训练集(或者每个batch里)的最长句子的长度. 长度不足的句子会在后面补[PAD]. 这个就是我们通常说的padding操作.
2.设定为文本最长长度的80%~90%. 同样,长度不足的补上[PAD]. 超出长度的句子,超出部分裁减掉. 这个情况通常用在文本摘要之类句子长度非常长的情况下,或者模型对句子长度输入有限制.
先验知识完毕.


开始正式讲Positional Encoding.
由于本模型没有使用循环或者卷积神经网络,我们需要额外的信息来表达词的顺序.
Transformer模型对每个输入词向量(就是下图中深绿色格子的小x矩阵Embedding) 额外加入了一个向量,这些向量遵循模型学习的指定模式. 这样可以帮助词定位自己的位置,或者在句子中不同词之间的距离. 直觉上看,在词嵌入中加入这些向量,可以让他们之后各种处理过程中,提供有意义的词嵌入间的距离.
说人话就是, 在词向量送入encoder之前,再加上一个位置编码向量(positional encoding),使得词向量具有一定的位置信息.
Transformer模型学习笔记_第6张图片
这个位置编码大概长这样↓*(图里数字存在疑问, 按照公式算出来并不对,也可能我算错了)*
Transformer模型学习笔记_第7张图片
那么这个东西怎么产生?
在论文里, 用的是这样的公式:
P E ( p o s , 2 i ) = s i n ( p o s 1000 0 2 i / d m o d e l ) PE_{(pos,2i)} = sin(\frac{pos}{10000^{2i/d_{model}}} ) PE(pos,2i)=sin(100002i/dmodelpos)
P E ( p o s , 2 i + 1 ) = c o s ( p o s 1000 0 2 i / d m o d e l ) PE_{(pos,2i+1)} = cos(\frac{pos}{10000^{2i/d_{model}}} ) PE(pos,2i+1)=cos(100002i/dmodelpos)

公式里, pos就是词在输入句子(或者说输入的sequence)里的位置, i是词向量位置,即 2i 表示偶数位置,2i+1 表示奇数位置. dmodel的值就是单词embedding的大小(在上图中,就是4)
这样PE是一个二维矩阵, 列数=词向量(embedding)大小, 行数=输入句子中词的个数(或者说输入sequence的长度). 比如我句子/sequence长度是748,单词embedding大小是128, 那我的PE就是一个748*128大小的二维矩阵. PE(0,3)就是句子里第一个单词的第4个向量位置.
这样, 上述公式表示在每个词语的词向量的偶数位置添加sin变量,奇数位置添加cos变量. 注意i是从0开始的.

再举个,如果pos=3(即句子/sequence里第4个单词),单词embedding大小是128, 即d_model=128,那么这个单词的位置向量就是
[ s i n ( 3 1000 0 0 / 128 ) , c o s ( 3 1000 0 0 / 128 ) , s i n ( 3 1000 0 2 / 128 ) , c o s ( 3 1000 0 2 / 128 ) , s i n ( 3 1000 0 4 / 128 ) , c o s ( 3 1000 0 4 / 128 ) , … . ] [sin(\frac{3}{10000^{0/128}}), cos(\frac{3}{10000^{0/128}}),sin(\frac{3}{10000^{2/128}}), cos(\frac{3}{10000^{2/128}}),sin(\frac{3}{10000^{4/128}}), cos(\frac{3}{10000^{4/128}}), ….] [sin(100000/1283),cos(100000/1283),sin(100002/1283),cos(100002/1283),sin(100004/1283),cos(100004/1283),.]

解说参考: https://blog.csdn.net/Flying_sfeng/article/details/100996524
代码实现

class PositionalEncoding(nn.Module):
    "Implement the PE function."


    def __init__(self, d_model, dropout, max_len=5000):
        super(PositionalEncoding, self).__init__()
        self.dropout = nn.Dropout(p=dropout)


        # Compute the positional encodings once in log space.
        pe = torch.zeros(max_len, d_model)
        position = torch.arange(0, max_len).unsqueeze(1)
        div_term = torch.exp(torch.arange(0.0, d_model, 2) *
                             -(math.log(10000.0) / d_model))
        pe[:, 0::2] = torch.sin(position * div_term)
        pe[:, 1::2] = torch.cos(position * div_term)
        pe = pe.unsqueeze(0)
        self.register_buffer('pe', pe)


    def forward(self, x):
        x = x + Variable(self.pe[:, :x.size(1)],
                         requires_grad=False)
        return self.dropout(x)

总结一下这步就做了这么一件事, 其中position_embedding是直接由公式算出来的,而不是训练出来的.

x_embedding = x_embedding + position_embedding

3.进入Encoder层

Transformer模型学习笔记_第8张图片
这里能看到Transformer的一个关键特性,每个位置的词仅仅流过它自己的编码器路径。在self-attention层中,这些路径两两之间是相互依赖的。重点之一来了!!! 前向网络层没有这些依赖性,但这些路径在流经前向网络时可以并行执行. (记忆前文所说的,为什么transformer能够实现并行计算)

Transformer模型学习笔记_第9张图片

4.Self-Attention层

先回忆下attention, attention本质是啥?
解释参考:https://www.jianshu.com/p/d7f50cc5560e
Attention函数的本质可以被描述为一个查询(Query)到一系列(键Key-值Value)对的映射:
Transformer模型学习笔记_第10张图片
其计算方式是先计算Query和各个Key的相似性或者相关性(这里用点乘),得到每个Key对应Value的权重系数,然后对Value进行加权求和,即得到了最终的Attention数值。所以本质上Attention机制是对Source中元素的Value值进行加权求和,而Query和Key用来计算对应Value的权重系数:
Transformer模型学习笔记_第11张图片

self-attention比普通attention有什么改进. 举个别人的例子, 这句话

The animal didn’t cross the street because it was too tired

普通attention无法获取it和animal之间的关联. 因为decoder部分取用encoder部分的信息. 但encoder部分内部之间没有作关联处理.
而self-attention, encoder可以取用来源于自己的句子信息, 即可以取到it和animal之前的信息(我取用了我自己的信息)

Self-Attention是Attention的特殊形式。自注意模型其实就是我们前面的query、key和value这三个输入是相等的, 即Q=K=V,例如输入一个句子,那么里面的每个词都要和该句子中的所有词进行attention计算。目的是学习句子内部的词依赖关系,捕获句子的内部结构。

这里就再强调一下为什么需要self-attention: 实现了seq2seq不能做到的并行计算,并且从结构上代替了seq2seq.

普通self-attention具体如何计算===
上文里我们提到的就是普通的self-attention, Q,K,V的值其实就是输入X的值(即Q=X,K=X,V=X). 用X去查询X的键值(自己查自己)
联想sequence to sequence里加入attention, 此时是Q=Y,K=X,V=X. 我们用Y去查询X的键值.
计算公式就是
s o f t m a x ( X × X T ) × X softmax(X×X^{T}) ×X softmax(X×XT)×X
等你读完下面这部分你会发现就是最后那张softmax图,只是把QKV全部换成了X.

带线性层的self-attention具体如何计算===
带线性层的self-attention, 就是输入X会经过q,k,v三个矩阵. 这三个矩阵都是独立的,单独初始化的.
我们先看下理论上如何计算单个向量,再看下实际操作过程中,如何以矩阵方式计算。

简单向量计算:
第一步, 我们初始化三个权重向量: query-vec(查询向量), key-vec(键向量), value-vec(值向量),他们都分别独立的被初始化, 并且这些矩阵的值在训练过程中也会被更新。【注意:不是每个词向量独享3个矩阵,而是所有输入共享3个转换矩阵;权重矩阵是基于输入位置的转换矩阵;有个可以尝试的点,如果每个词独享一组转换矩阵,会不会效果更厉害呢?】
注意到这些新向量的维度比输入词向量的维度要小(512–>64),并不是必须要小的,是为了让多头attention的计算更稳定。

看图, queries就是我们的查询向量, keys是键向量, values是值向量, q1,k1,v1都并不相同.
Transformer模型学习笔记_第12张图片
第二步,计算attention就是计算一个分值。对“Thinking Matchines”这句话,对“Thinking”计算attention 分值。我们需要计算每个词与“Thinking”的评估分,这个分决定着编码“Thinking”时(某个固定位置时),每个输入词需要集中多少关注度。
这个分,通过“Thing”对应query-vector与所有词的key-vec依次做点积得到。所以当我们处理位置#1时,第一个分值是q1和k1的点积,第二个分值是q1和k2的点积。
Transformer模型学习笔记_第13张图片
第三步和第四步,除以8(√64=8, 64来源于原论文里键向量key-vec的维数)
这样做目的, 按照论文作者说梯度会更稳定. 然后加上softmax操作,归一化.
Transformer模型学习笔记_第14张图片
softmax分值决定着在这个位置,每个词的表达程度(关注度)。很明显,这个位置的词应该有最高的归一化分数,但大部分时候总是有助于关注该词的相关的词。
第五步,将softmax分值与value-vec按位相乘。保留关注词的value值,削弱非相关词的value值。
第六步,将所有加权向量加和,产生该位置的self-attention的输出结果。
Transformer模型学习笔记_第15张图片
即z1=v1*0.88+v2*0.12

实际操作中的运算:
实际操作中,就变成了矩阵运算.
首先, 我们独立的,初始化三个矩阵: 查询矩阵(WQ),键矩阵(WK)和值矩阵(WV), 将所有输入词向量合并成输入矩阵X, 并且将其分别乘以权重矩阵WQ,WK,WV,得到Q,K,V三个新的矩阵. 这样一个操作相当于让X过了三个线性层.

但是要注意! 由于WQ,WK,WV并不相同,所以下图里最右边的Q,K,V也不相同! 你会有疑问, 不是说在self-attention里, Q=K=V吗? 其实更合理的说法是: “在self-attention里, 输入的Q,K,V是相等的” . 这里输入的QKV的确是相等的(都是X), 但是在经过不同的线性变换后,他们已经变得不同了.
你可能接着会有疑问, 那为什么会要做这么一次线性变换? 答案是为了实现multi-head机制. 该机制下文会讲.
在这里插入图片描述
然后, 将步骤2~6合并成一个计算self-attention层输出的公式
在这里插入图片描述
这里 Q(查询矩阵)*KT(键矩阵的转置)计算结果为一个2*2矩阵, 2*2 乘以v(2*3形状的值矩阵)= z(2*3形状的输出), 在和z相乘的过程中,已经自动完成了上一小节所说的加权向量加和, z的第一行就是上一小节里的z1, 第二行就是上一小节里的z2.

5.多头机制 Multi-head

多头机制有什么好处?

  1. 提高了模型关注不同位置的能力.
    一般情况下,一个注意力机制下,模型可能只能关注到一两个单词, 多头注意力机制可以使模型有机会关注到其他更多的单词.

比如说"我在外滩肯德基吃饭".
提问:“你在哪里吃饭?” “你在吃什么?” 显然这两个问题需要关注到句子里不同的部分.

2.提供了多种"子空间表达方式(representation subspaces)". 如下图,我们会设定8组查询矩阵,键矩阵和值矩阵(8*3=共计24个). 这24个矩阵, 每个都会单独初始化. 经过训练之后,输入向量可以被映射到不同的子表达空间中. 这里就是为什么X要过线性层的原因, 我们希望得到8组不同的QKV以加强模型的表达能力. (回忆: 不带线性层的self-attention是不是只能获得一组QKV?)
Transformer模型学习笔记_第16张图片

计算self-attention的方法和上一节提到的一样,只是做了8次, 得到8个z, 即z0,z1,z2,…z7
Transformer模型学习笔记_第17张图片
因为前向网络并不能接收八个矩阵,而是希望输入是一个矩阵,所以就把八个矩阵合并成一个矩阵(concat操作)
1) 八个矩阵连在一起
2) 然后和一个权重矩阵w0相乘. 这个w0也是跟随者着模型一起训练的.
3) 按照下图尺寸, 2*24 矩阵 乘以 24*4矩阵=Z矩阵 (2*4矩阵), 这个Z矩阵会被送入下一步的"前向网络层"
Transformer模型学习笔记_第18张图片
下图是完整流程
Transformer模型学习笔记_第19张图片
注意, 其实八组QKV有可能训练到最后长一样.

6.Multi-head具体在代码里怎么实现

刚刚看了半天,是不是觉得一顿操作猛如虎, 代码应该三百五?
实际上我们是这么写Multi-head self-attention的:

# 假定x是我们的输入
# 假设它的size是[batch_size, seq_len, hidden_size],
# 其中hidden_size你也可以认为是embedding size,反正就是每个词的向量表示.
x=inputs

# 定义8个头
num_of_head=8  
# 定义每个头的size是多少. 
# 这里我们应该保证设定的hidden_size一定要是num_of_head的整数倍.
size_per_head=hidden_size//num_of_head 

# 定义Q,K,V, 你会发现其实我们没有定义8次.
q=nn.Linear(hidden_size,hidden_size)
k=nn.Linear(hidden_size,hidden_size)
v=nn.Linear(hidden_size,hidden_size)

# 我们的输入x经过Q,K,V三个线性层
# qw,kw,vw的size仍然是[batch_size, seq_len, hidden_size]
qw=q(x)
kw=k(x)
vw=v(x)

# 这一步做变化, 把qw,kw,vw的size变成
# [batch_size, seq_len, num_of_head, size_per_head]
# 回忆: hidden_size = num_of_head * size_per_head
qw=qw.view(new_size)
kw=kw.view(new_size)
vw=vw.view(new_size)

# 中间两维度调换[batch_size, num_of_head, seq_len, size_per_head]
qw.permute()
kw.permute()
vw.permute()

# q和k点乘除以根号64,再过softmax
# 此时size是[batch_size, num_of_head, seq_len, seq_len]
attention_scores = torch.matmul(qw, kw)/sqrt(size_per_head)
attention_scores = softmax(attention_scores)

# 再和v做点乘
# 结果的size是[batch_size, num_of_head, seq_len, size_per_head]
z=torch.matmul(attention_scores,vw)

# 最后维度调回来
# [batch_size, seq_len, num_of_head, size_per_head]
z.permute()
# 把最后两个维度拼接起来
# [batch_size, seq_len, hidden_size]
z.reshape()

=======================================================
说到这里需要总结一下, 我们讲完了multi-head机制, 讲完了Self-attention机制. Encoder里用的是Multi-head Self-attention. 这个和Decoder里是有些许区别的. 下文Decoder里会继续介绍.

7.Add&Norm以及Feed Forward

做完attention之后的操作都比较简单.
Transformer模型学习笔记_第20张图片
Add&Normalize这层长这样,将X和Z相加后,过一个layer normalization即可.
Transformer模型学习笔记_第21张图片
而Feed Forward其实就是一个线性层+激活函数. 上图我写了一个伪代码, 我相信看完你们会更清楚:

# x依旧是我们的输入
x = inputs
x = x + positional_encoding

# z是x过完attention之后的值
z = multi_head_self_attention(x)  

# 把z和x相加过一个layer normalization,这步就是Add&Norm
new_z = LayerNorm(x + z)

# 这步就是Feed Forward
new_new_z = nn.Linear(new_z)  # 过一个线性层, keras里就是Dense()
new_new_z = gelu(new_new_z)   # 我随便写了一个激活函数

# 第二次Add&Norm
output = LayerNorm(new_z + new_new_z)

解码器中也是一样,所以解码器里我就不重复了:
Transformer模型学习笔记_第22张图片

编码器(Encoder)部分到此为止================================




下面开始是解码器(Decoder部分)
解码器部分和编码器大同小异, 唯一需要注意的是attention操作

8.Attention Mask机制

Decoder部分每个单元格的Attention和Encoder部分稍有不同. 其他输入时的Positional embedding, Feed forward,Add&Norm之类结构都和Encoder一模一样.

Encoder单元格有一个Multi-Head Attention:
1.Multi-head Self-Attention(图里左下角那个)

Decoder单元格有两个Multi-Head Attention:
1.Multi-head Attention(图里右上面那个)
2.Masked Multi-head Self-Attention(图里下面画红框的那个)

Transformer模型学习笔记_第23张图片
请注意他们名称上的区别. 他们实际上是有细微区别的.
我们先讲一下第一个Multi-Head Attention和encoder里Multi-head Self-Attention有什么区别.
没错,区别很简单, 一个有self一个没self. 具体怎么实现的? 就是在输入时的区别. 回想一下, 我们self-attention的输入是不是三个X? Q,K,V都是X. 这里只要把Q换成Y即可. K,V仍然是X.
我们看图上的箭头,decoder部分的Multi-Head Attention下面是不是有三个箭头? 两个箭头来源于Encoder? 这个就是我们的X, 另外一个箭头来源于上一层Add&Norm的输出,他就是我们的Y.
再理解一下, Q换成Y, 就意味着Y在对X的键值进行查询.

讲完第一个Multi-Head Attention,我们讲一下第二个Masked Multi-head Self-Attention.
这个Masked Multi-head Self-Attention和Encoder部分的区别是多了一个mask, 其他的都一样. 我们先来看一下为什么需要这个mask, 然后看看这个mask应该加在哪里.

Attention的机制缺点是, 模型会看到下一部分, 比如这里的 I, 可以看到后面have, a, dream 三个单词,我们不希望前面的单词可以看到后面的信息.
Transformer模型学习笔记_第24张图片
而之前seq2seq 机制是: 模型永远是接受上一次输入的结果,不会看到下一部分. 回想一下seq2seq结构, 我当前的输入的一部分是来源于上一次的输出.
那么这个问题如何解决?
答案: 采用mask机制.
不同于padding机制的mask, 这里我称为Attention Mask. 方法是设置一个下三角mask, 把不希望模型看到的部分设置成 -inf (实际写代码里我们就设置一个非常大的负数,比如 -1010 即可).

我想有同学会说mask不是也有设置成0的吗?
那么什么时候设置成0,什么时候设置成-inf呢?
设置成0:
如果是要先做对位相乘操作,我们设置成0,这一步通常用在padding的mask操作上. 对于一句句子,pad部分的mask被设置成0.
举例: 对于一个长度为3,max_len为7的句子,它的mask长这样:[1,1,1,0,0,0,0]. 因为把句子embedding送入线性层计算后,0的部分也会变的有值(即pad也参与了计算),我们不希望pad部分也拥有值,所以在经过计算后,再过一下mask保证pad部分始终为0.
设置成-inf:
如果马上要做softmax操作,我们设置成-inf.因为如果直接把0送入softmax,这一部分在经过softmax计算后,也会享有一部分权重,然而我们不想0值的部分拥有权重,所以设置成-inf,这样softmax在计算时他们的权重都会变成0.
所以, 由于我们在做完attention mask后需要做softmax操作, 所以这里设置成 -inf.

Attention Mask矩阵长这样,是一个左下部分都为1,右上部分全为-inf的矩阵.
当然你也可以设置左下角全为0, 右上部分全为-inf. 区别就是设为0时,和原矩阵做相加运算, 设为1时和原矩阵点对点相乘.

I have a dream
I 1 -inf -inf -inf
have 1 1 -inf -inf
a 1 1 1 -inf
dream 1 1 1 1

原矩阵过了attention mask之后的效果:Transformer模型学习笔记_第25张图片

所以理所当然,在代码里, 这一步也是加在softmax之前.
回忆一下第5节里讲的Multi-head Attention怎么实现, 我们有这样一段代码

# q和k点乘除以根号64,再过softmax
# 此时size是[batch_size, num_of_head, seq_len, seq_len]
attention_scores = torch.matmul(qw, kw)/sqrt(size_per_head)
attention_scores = softmax(attention_scores)

而这个attention mask就是加在softmax之前,变成

# q和k点乘除以根号64,再过softmax
# 此时size是[batch_size, num_of_head, seq_len, seq_len]
attention_scores = torch.matmul(qw, kw)/sqrt(size_per_head)
attention_scores = attention_scores + attention_mask
attention_scores = softmax(attention_scores)

看一下图可能更形象:
首先我们做完点乘torch.matmul后, 矩阵可能长这样
Transformer模型学习笔记_第26张图片
它的size是[seq_len, seq_len].
我们把它过一个attention mask, 就变成了下图, 右上部分是不是全部都被mask了? :
Transformer模型学习笔记_第27张图片

9.完整解码部分

好的,看完了mask机制,我们看下完整解码部分,基本都很简单了. 先来看个GIF. 这个GIF已经画的很清楚了, 你们可以看到Encoder部分的K,V参与了Decoder部分Multi-head Attention的计算(再强调一下是Multi-head Attention, 而不是Multi-head Self-Attention!!! 是Decoder里第二次做的Attention!!!)

在编码之后,是解码过程;解码的每一步输出一个元素作输出序列,动画已经蛮清楚了.

10.最后输出层

decoder出来后会经过一个线性层(linear)和softmax层. 线性层就是一个FC(全连接层), 用softmax看哪个词概率最高就输出哪个.

你可能感兴趣的:(知识总结)