其他分享
首页 > 其他分享> > 万字长文带你轻松入门了解视觉Transformer

万字长文带你轻松入门了解视觉Transformer

作者:互联网

一导读

Transformer整个网络结构完全由Attention机制组成,其出色的性能在多个任务上都取得了非常好的效果。本文从Transformer的结构出发,结合视觉中的成果进行了分析,能够帮助初学者们快速入门。

0 摘要
transformer结构是google在17年的Attention Is All You Need论文中提出,在NLP的多个任务上取得了非常好的效果,可以说目前NLP发展都离不开transformer。最大特点是抛弃了传统的CNN和RNN,整个网络结构完全是由Attention机制组成。由于其出色性能以及对下游任务的友好性或者说下游任务仅仅微调即可得到不错效果,在计算机视觉领域不断有人尝试将transformer引入,近期也出现了一些效果不错的尝试,典型的如目标检测领域的detr和可变形detr,分类领域的vision transformer等等。本文从transformer结构出发,结合视觉中的transformer成果(具体是vision transformer和detr)进行分析,希望能够帮助cv领域想了解transformer的初学者快速入门。由于本人接触transformer时间也不长,也算初学者,故如果有描述或者理解错误的地方欢迎指正。

本文的大部分图来自论文、国外博客和国内翻译博客,在此一并感谢前人工作,具体链接见参考资料。本文特别长,大概有3w字,请先点赞收藏然后慢慢看…

1 transformer介绍
一般讲解transformer都会以机器翻译任务为例子讲解,机器翻译任务是指将一种语言转换得到另一种语言,例如英语翻译为中文任务。从最上层来看,如下所示:

1.1 早期seq2seq
机器翻译是一个历史悠久的问题,本质可以理解为序列转序列问题,也就是我们常说的seq2seq结构,也可以称为encoder-decoder结构,如下所示:

encoder和decoder在早期一般是RNN模块(因为其可以捕获时序信息),后来引入了LSTM或者GRU模块,不管内部组件是啥,其核心思想都是通过Encoder编码成一个表示向量,即上下文编码向量,然后交给Decoder来进行解码,翻译成目标语言。一个采用典型RNN进行编码码翻译的可视化图如下:

可以看出,其解码过程是顺序进行,每次仅解码出一个单词。对于CV领域初学者来说,RNN模块构建的seq2seq算法,理解到这个程度就可以了,不需要深入探讨如何进行训练。但是上述结构其实有缺陷,具体来说是:

不论输入和输出的语句长度是什么,中间的上下文向量长度都是固定的,一旦长度过长,仅仅靠一个固定长度的上下文向量明显不合理
仅仅利用上下文向量解码,会有信息瓶颈,长度过长时候信息可能会丢失
通俗理解是编码器与解码器的连接点仅仅是编码单元输出的隐含向量,其包含的信息有限,对于一些复杂任务可能信息不够,如要翻译的句子较长时,一个上下文向量可能存不下那么多信息,就会造成翻译精度的下降。

1.2 基于attention的seq2seq
基于上述缺陷进而提出带有注意力机制Attention的seq2seq,同样可以应用于RNN、LSTM或者GRU模块中。注意力机制Attention对人类来说非常好理解,假设给定一张图片,我们会自动聚焦到一些关键信息位置,而不需要逐行扫描全图。此处的attention也是同一个意思,其本质是对输入的自适应加权,结合cv领域的senet中的se模块就能够理解了。

se模块最终是学习出一个1x1xc的向量,然后逐通道乘以原始输入,从而对特征图的每个通道进行加权即通道注意力,对attention进行抽象,不管啥领域其机制都可以归纳为下图:

将Query(通常是向量)和4个Key(和Q长度相同的向量)分别计算相似性,然后经过softmax得到q和4个key相似性的概率权重分布,然后对应权重乘以Value(和Q长度相同的向量),最后相加即可得到包含注意力的attention值输出,理解上应该不难。 举个简单例子说明:

假设世界上所有小吃都可以被标签化,例如微辣、特辣、变态辣、微甜、有嚼劲…,总共有1000个标签,现在我想要吃的小吃是[微辣、微甜、有嚼劲],这三个单词就是我的Query
来到东门老街一共100家小吃店,每个店铺卖的东西不一样,但是肯定可以被标签化,例如第一家小吃被标签化后是[微辣、微咸],第二家小吃被标签化后是[特辣、微臭、特咸],第三家小吃被标签化后是[特辣、微甜、特咸、有嚼劲],其余店铺都可以被标签化,每个店铺的标签就是Keys,但是每家店铺由于卖的东西不一样,单品种类也不一样,所以被标签化后每一家的标签List不一样长
Values就是每家店铺对应的单品,例如第一家小吃的Values是[烤羊肉串、炒花生]
将Query和所有的Keys进行一一比对,相当于计算相似性,此时就可以知道我想买的小吃和每一家店铺的匹配情况,最后有了匹配列表,就可以去店铺里面买东西了(Values和相似性加权求和)。最终的情况可能是,我在第一家店铺买了烤羊肉串,然后在第10家店铺买了个玉米,最后在第15家店铺买了个烤面筋
以上就是完整的注意力机制,采用我心中的标准Query去和被标签化的所有店铺Keys一一比对,此时就可以得到我的Query在每个店铺中的匹配情况,最终去不同店铺买不同东西的过程就是权重和Values加权求和过程。简要代码如下:

复制代码

假设q是(1,N,512),N就是最大标签化后的list长度,k是(1,M,512),M可以等于N,也可以不相等

(1,N,512) x (1,512,M)–>(1,N,M)

attn = torch.matmul(q, k.transpose(2, 3))

softmax转化为概率,输出(1,N,M),表示q中每个n和每个m的相关性

attn=F.softmax(attn, dim=-1)

(1,N,M) x (1,M,512)–>(1,N,512),V和k的shape相同

output = torch.matmul(attn, v)
复制代码
带有attention的RNN模块组成的ser2seq,解码时候可视化如下:

在没有attention时候,不同解码阶段都仅仅利用了同一个编码层的最后一个隐含输出,加入attention后可以通过在每个解码时间步输入的都是不同的上下文向量,以上图为例,解码阶段会将第一个开启解码标志(也就是Q)与编码器的每一个时间步的隐含状态(一系列Key和Value)进行点乘计算相似性得到每一时间步的相似性分数,然后通过softmax转化为概率分布,然后将概率分布和对应位置向量进行加权求和得到新的上下文向量,最后输入解码器中进行解码输出,其详细解码可视化如下:

通过上述简单的attention引入,可以将机器翻译性能大幅提升,引入attention有以下几个好处:

注意力显著提高了机器翻译性能
注意力允许解码器以不同程度的权重利用到编码器的所有信息,可以绕过瓶颈
通过检查注意力分布,可以看到解码器在关注什么,可解释性强
1.3 基于transformer的seq2seq
基于attention的seq2seq的结构虽然说解决了很多问题,但是其依然存在不足:

不管是采用RNN、LSTM还是GRU都不利于并行训练和推理,因为相关算法只能从左向右依次计算或者从右向左依次计算
长依赖信息丢失问题,顺序计算过程中信息会丢失,虽然LSTM号称有缓解,但是无法彻底解决
最大问题应该是无法并行训练,不利于大规模快速训练和部署,也不利于整个算法领域发展,故在Attention Is All You Need论文中抛弃了传统的CNN和RNN,将attention机制发挥到底,整个网络结构完全是由Attention机制组成,这是一个比较大的进步。

google所提基于transformer的seq2seq整体结构如下所示:

其包括6个结构完全相同的编码器,和6个结构完全相同的解码器,其中每个编码器和解码器设计思想完全相同,只不过由于任务不同而有些许区别,整体详细结构如下所示:

第一眼看有点复杂,其中N=6,由于基于transformer的翻译任务已经转化为分类任务(目标翻译句子有多长,那么就有多少个分类样本),故在解码器最后会引入fc+softmax层进行概率输出,训练也比较简单,直接采用ce loss即可,对于采用大量数据训练好的预训练模型,下游任务仅仅需要训练fc层即可。上述结构看起来有点复杂,一个稍微抽象点的图示如下:

看起来比基于RNN或者其余结构构建的seq2seq简单很多。下面结合代码和原理进行深入分析。

1.4 transformer深入分析
前面写了一大堆,没有理解没有关系,对于cv初学者来说其实只需要理解QKV的含义和注意力机制的三个计算步骤:Q和所有K计算相似性;对相似性采用softmax转化为概率分布;将概率分布和V进行一一对应相乘,最后相加得到新的和Q一样长的向量输出即可,重点是下面要讲的transformer结构。

下面按照 编码器输入数据处理->编码器运行->解码器输入数据处理->解码器运行->分类head 的实际运行流程进行讲解。

1.4.1 编码器输入数据处理
(1) 源单词嵌入

以上面翻译任务为例,原始待翻译输入是三个单词:

输入是三个单词,为了能够将文本内容输入到网络中肯定需要进行向量化(不然单词如何计算?),具体是采用nlp领域的embedding算法进行词嵌入,也就是常说的Word2Vec。对于cv来说知道是干嘛的就行,不必了解细节。假设每个单词都可以嵌入成512个长度的向量,故此时输入即为3x512,注意Word2Vec操作只会输入到第一个编码器中,后面的编码器接受的输入是前一个编码器输出。

为了便于组成batch(不同训练句子单词个数肯定不一样)进行训练,可以简单统计所有训练句子的单词个数,取最大即可,假设统计后发现待翻译句子最长是10个单词,那么编码器输入是10x512,额外填充的512维向量可以采用固定的标志编码得到,例如$$。

(2) 位置编码positional encoding

采用经过单词嵌入后的向量输入到编码器中还不够,因为transformer内部没有类似RNN的循环结构,没有捕捉顺序序列的能力,或者说无论句子结构怎么打乱,transformer都会得到类似的结果。为了解决这个问题,在编码词向量时会额外引入了位置编码position encoding向量表示两个单词i和j之间的距离,简单来说就是在词向量中加入了单词的位置信息。

加入位置信息的方式非常多,最简单的可以是直接将绝对坐标0,1,2编码成512个长度向量即可。作者实际上提出了两种方式:

网络自动学习
自己定义规则
提前假设单词嵌入并且组成batch后,shape为(b,N,512),N是序列最大长度,512是每个单词的嵌入向量长度,b是batch

(a) 网络自动学习

self.pos_embedding = nn.Parameter(torch.randn(1, N, 512))
比较简单,因为位置编码向量需要和输入嵌入(b,N,512)相加,所以其shape为(1,N,512)表示N个位置,每个位置采用512长度向量进行编码

(b) 自己定义规则

自定义规则做法非常多,论文中采用的是sin-cos规则,具体做法是:

将向量(N,512)采用如下函数进行处理

pos即0~N,i是0-511

将向量的512维度切分为奇数行和偶数行
偶数行采用sin函数编码,奇数行采用cos函数编码
然后按照原始行号拼接
复制代码
def get_position_angle_vec(position):
# hid_j是0-511,d_hid是512,position表示单词位置0~N-1
return [position / np.power(10000, 2 * (hid_j // 2) / d_hid) for hid_j in range(d_hid)]

每个单词位置0~N-1都可以编码得到512长度的向量

sinusoid_table = np.array([get_position_angle_vec(pos_i) for pos_i in range(n_position)])

偶数列进行sin

sinusoid_table[:, 0::2] = np.sin(sinusoid_table[:, 0::2]) # dim 2i

奇数列进行cos

sinusoid_table[:, 1::2] = np.cos(sinusoid_table[:, 1::2]) # dim 2i+1
复制代码
上面例子的可视化如下:

如此编码的优点是能够扩展到未知的序列长度,例如前向时候有特别长的句子,其可视化如下:

作者为啥要设计如此复杂的编码规则?原因是sin和cos的如下特性:

可以将PE(pos+k)用PE(pos)进行线性表出:

假设k=1,那么下一个位置的编码向量可以由前面的编码向量线性表示,等价于以一种非常容易学会的方式告诉了网络单词之间的绝对位置,让模型能够轻松学习到相对位置信息。 注意编码方式不是唯一的,将单词嵌入向量和位置编码向量相加就可以得到编码器的真正输入了,其输出shape是(b,N,512)。

1.4.2 编码器前向过程
编码器由两部分组成:自注意力层和前馈神经网络层。

其前向可视化如下:

注意上图没有绘制出单词嵌入向量和位置编码向量相加过程,但是是存在的。

(1) 自注意力层

通过前面分析我们知道自注意力层其实就是attention操作,并且由于其QKV来自同一个输入,故称为自注意力层。我想大家应该能想到这里attention层作用,在参考资料1博客里面举了个简单例子来说明attention的作用:假设我们想要翻译的输入句子为The animal didn’t cross the street because it was too tired,这个“it”在这个句子是指什么呢?它指的是street还是这个animal呢?这对于人类来说是一个简单的问题,但是对于算法则不是。当模型处理这个单词“it”的时候,自注意力机制会允许“it”与“animal”建立联系即随着模型处理输入序列的每个单词,自注意力会关注整个输入序列的所有单词,帮助模型对本单词更好地进行编码。 实际上训练完成后确实如此,google提供了可视化工具,如下所示:

上述是从宏观角度思考,如果从输入输出流角度思考,也比较容易:

假设我们现在要翻译上述两个单词,首先将单词进行编码,和位置编码向量相加,得到自注意力层输入X,其shape为(b,N,512);然后定义三个可学习矩阵 [公式] (通过nn.Linear实现),其shape为(512,M),一般M等于前面维度512,从而计算后维度不变;将X和矩阵 [公式] 相乘,得到QKV输出,shape为(b,N,M);然后将Q和K进行点乘计算向量相似性;采用softmax转换为概率分布;将概率分布和V进行加权求和即可。其可视化如下:

上述绘制的不是矩阵形式,更好理解而已。对于第一个单词的编码过程是:将q1和所有的k进行相似性计算,然后除以维度的平方根(论文中是64,本文可以认为是512)使得梯度更加稳定,然后通过softmax传递结果,这个softmax分数决定了每个单词对编码当下位置(“Thinking”)的贡献,最后对加权值向量求和得到z1。

这个计算很明显就是前面说的注意力机制计算过程,每个输入单词的编码输出都会通过注意力机制引入其余单词的编码信息。

上述为了方便理解才拆分这么细致,实际上代码层面采用矩阵实现非常简单:

上面的操作很不错,但是还有改进空间,论文中又增加一种叫做“多头”注意力(“multi-headed” attention)的机制进一步完善了自注意力层,并在两方面提高了注意力层的性能:

它扩展了模型专注于不同位置的能力。在上面的例子中,虽然每个编码都在z1中有或多或少的体现,但是它可能被实际的单词本身所支配。如果我们翻译一个句子,比如“The animal didn’t cross the street because it was too tired”,我们会想知道“it”指的是哪个词,这时模型的“多头”注意机制会起到作用。
它给出了注意力层的多个“表示子空间",对于“多头”注意机制,有多个查询/键/值权重矩阵集(Transformer使用8个注意力头,因此我们对于每个编码器/解码器有8个矩阵集合)。

简单来说就是类似于分组操作,将输入X分别输入到8个attention层中,得到8个Z矩阵输出,最后对结果concat即可。论文图示如下:

先忽略Mask的作用,左边是单头attention操作,右边是n个单头attention构成的多头自注意力层。

代码层面非常简单,单头attention操作如下:

复制代码
class ScaledDotProductAttention(nn.Module):
‘’’ Scaled Dot-Product Attention ‘’’

def __init__(self, temperature, attn_dropout=0.1):
    super().__init__()
    self.temperature = temperature
    self.dropout = nn.Dropout(attn_dropout)

def forward(self, q, k, v, mask=None):
    # self.temperature是论文中的d_k ** 0.5,防止梯度过大
    # QxK/sqrt(dk)
    attn = torch.matmul(q / self.temperature, k.transpose(2, 3))

    if mask is not None:
        # 屏蔽不想要的输出
        attn = attn.masked_fill(mask == 0, -1e9)
    # softmax+dropout
    attn = self.dropout(F.softmax(attn, dim=-1))
    # 概率分布xV
    output = torch.matmul(attn, v)

    return output, attn

复制代码
再次复习下Multi-Head Attention层的图示,可以发现在前面讲的内容基础上还加入了残差设计和层归一化操作,目的是为了防止梯度消失,加快收敛。

Multi-Head Attention实现在ScaledDotProductAttention基础上构建:

复制代码
class MultiHeadAttention(nn.Module):
‘’’ Multi-Head Attention module ‘’’

# n_head头的个数,默认是8
# d_model编码向量长度,例如本文说的512
# d_k, d_v的值一般会设置为 n_head * d_k=d_model,
# 此时concat后正好和原始输入一样,当然不相同也可以,因为后面有fc层
# 相当于将可学习矩阵分成独立的n_head份
def __init__(self, n_head, d_model, d_k, d_v, dropout=0.1):
    super().__init__()
    # 假设n_head=8,d_k=64
    self.n_head = n_head
    self.d_k = d_k
    self.d_v = d_v
    # d_model输入向量,n_head * d_k输出向量
    # 可学习W^Q,W^K,W^V矩阵参数初始化
    self.w_qs = nn.Linear(d_model, n_head * d_k, bias=False)
    self.w_ks = nn.Linear(d_model, n_head * d_k, bias=False)
    self.w_vs = nn.Linear(d_model, n_head * d_v, bias=False)
    # 最后的输出维度变换操作
    self.fc = nn.Linear(n_head * d_v, d_model, bias=False)
    # 单头自注意力
    self.attention = ScaledDotProductAttention(temperature=d_k ** 0.5)
    self.dropout = nn.Dropout(dropout)
    # 层归一化
    self.layer_norm = nn.LayerNorm(d_model, eps=1e-6)

def forward(self, q, k, v, mask=None):
    # 假设qkv输入是(b,100,512),100是训练每个样本最大单词个数
    # 一般qkv相等,即自注意力
    residual = q
    # 将输入x和可学习矩阵相乘,得到(b,100,512)输出
    # 其中512的含义其实是8x64,8个head,每个head的可学习矩阵为64维度
    # q的输出是(b,100,8,64),kv也是一样
    q = self.w_qs(q).view(sz_b, len_q, n_head, d_k)
    k = self.w_ks(k).view(sz_b, len_k, n_head, d_k)
    v = self.w_vs(v).view(sz_b, len_v, n_head, d_v)

    # 变成(b,8,100,64),方便后面计算,也就是8个头单独计算
    q, k, v = q.transpose(1, 2), k.transpose(1, 2), v.transpose(1, 2)

    if mask is not None:
        mask = mask.unsqueeze(1)   # For head axis broadcasting.
    # 输出q是(b,8,100,64),维持不变,内部计算流程是:
    # q*k转置,除以d_k ** 0.5,输出维度是b,8,100,100即单词和单词直接的相似性
    # 对最后一个维度进行softmax操作得到b,8,100,100
    # 最后乘上V,得到b,8,100,64输出
    q, attn = self.attention(q, k, v, mask=mask)

    # b,100,8,64-->b,100,512
    q = q.transpose(1, 2).contiguous().view(sz_b, len_q, -1)
    q = self.dropout(self.fc(q))
    # 残差计算
    q += residual
    # 层归一化,在512维度计算均值和方差,进行层归一化
    q = self.layer_norm(q)

    return q, attn

复制代码
现在pytorch新版本已经把MultiHeadAttention当做nn中的一个类了,可以直接调用。

(2) 前馈神经网络层

这个层就没啥说的了,非常简单:

复制代码
class PositionwiseFeedForward(nn.Module):
‘’’ A two-feed-forward-layer module ‘’’

def __init__(self, d_in, d_hid, dropout=0.1):
    super().__init__()
    # 两个fc层,对最后的512维度进行变换
    self.w_1 = nn.Linear(d_in, d_hid) # position-wise
    self.w_2 = nn.Linear(d_hid, d_in) # position-wise
    self.layer_norm = nn.LayerNorm(d_in, eps=1e-6)
    self.dropout = nn.Dropout(dropout)

def forward(self, x):
    residual = x

    x = self.w_2(F.relu(self.w_1(x)))
    x = self.dropout(x)
    x += residual

    x = self.layer_norm(x)

    return x

复制代码
(3) 编码层操作整体流程

可视化如下所示:

单个编码层代码如下所示:

复制代码
class EncoderLayer(nn.Module):
def init(self, d_model, d_inner, n_head, d_k, d_v, dropout=0.1):
super(EncoderLayer, self).init()
self.slf_attn = MultiHeadAttention(n_head, d_model, d_k, d_v, dropout=dropout)
self.pos_ffn = PositionwiseFeedForward(d_model, d_inner, dropout=dropout)

def forward(self, enc_input, slf_attn_mask=None):
    # Q K V是同一个,自注意力
    # enc_input来自源单词嵌入向量或者前一个编码器输出
    enc_output, enc_slf_attn = self.slf_attn(
        enc_input, enc_input, enc_input, mask=slf_attn_mask)
    enc_output = self.pos_ffn(enc_output)
    return enc_output, enc_slf_attn

复制代码
将上述编码过程重复n遍即可,除了第一个模块输入是单词嵌入向量与位置编码的和外,其余编码层输入是上一个编码器输出即后面的编码器输入不需要位置编码向量。如果考虑n个编码器的运行过程,如下所示:

复制代码
class Encoder(nn.Module):
def init(
self, n_src_vocab, d_word_vec, n_layers, n_head, d_k, d_v,
d_model, d_inner, pad_idx, dropout=0.1, n_position=200):
# nlp领域的词嵌入向量生成过程(单词在词表里面的索引idx–>d_word_vec长度的向量)
self.src_word_emb = nn.Embedding(n_src_vocab, d_word_vec, padding_idx=pad_idx)
# 位置编码
self.position_enc = PositionalEncoding(d_word_vec, n_position=n_position)
self.dropout = nn.Dropout(p=dropout)
# n个编码器层
self.layer_stack = nn.ModuleList([
EncoderLayer(d_model, d_inner, n_head, d_k, d_v, dropout=dropout)
for _ in range(n_layers)])
# 层归一化
self.layer_norm = nn.LayerNorm(d_model, eps=1e-6)

def forward(self, src_seq, src_mask, return_attns=False):
    # 对输入序列进行词嵌入,加上位置编码
    enc_output = self.dropout(self.position_enc(self.src_word_emb(src_seq)))
    enc_output = self.layer_norm(enc_output)
    # 作为编码器层输入
    for enc_layer in self.layer_stack:
        enc_output, _ = enc_layer(enc_output, slf_attn_mask=src_mask)
    return enc_output

复制代码
到目前为止我们就讲完了编码部分的全部流程和代码细节。现在再来看整个transformer算法就会感觉亲切很多了:

1.4.3 解码器输入数据处理
在分析解码器结构前先看下解码器整体结构,方便理解:

其输入数据处理也要区分第一个解码器和后续解码器,和编码器类似,第一个解码器输入不仅包括最后一个编码器输出,还需要额外的输出嵌入向量,而后续解码器输入是来自最后一个编码器输出和前面解码器输出。

(1) 目标单词嵌入

这个操作和源单词嵌入过程完全相同,维度也是512,假设输出是i am a student,那么需要对这4个单词也利用word2vec算法转化为4x512的矩阵,作为第一个解码器的单词嵌入输入。

(2) 位置编码

同样的也需要对解码器输入引入位置编码,做法和编码器部分完全相同,且将目标单词嵌入向量和位置编码向量相加即可作为第一个解码器输入。

和编码器单词嵌入不同的地方是在进行目标单词嵌入前,还需要将目标单词即是i am a student右移动一位,新增加的一个位置采用提前定义好的标志位BOS_WORD代替,现在就变成[BOS_WORD,i,am,a,student],为啥要右移?因为解码过程和seq2seq一样是顺序解码的,需要提供一个开始解码标志,。不然第一个时间步的解码单词i是如何输出的呢?具体解码过程其实是:输入BOS_WORD,解码器输出i;输入前面已经解码的BOS_WORD和i,解码器输出am…,输入已经解码的BOS_WORD、i、am、a和student,解码器输出解码结束标志位EOS_WORD,每次解码都会利用前面已经解码输出的所有单词嵌入信息

下面有个非常清晰的gif图,一目了然:

上图没有绘制BOS_WORD嵌入向量输入,然后解码出i单词的过程。

1.4.4 解码器前向过程
仔细观察解码器结构,其包括:带有mask的MultiHeadAttention、MultiHeadAttention和前馈神经网络层三个组件,带有mask的MultiHeadAttention和MultiHeadAttention结构和代码写法是完全相同,唯一区别是是否输入了mask。

为啥要mask?原因依然是顺序解码导致的。试想模型训练好了,开始进行翻译(测试),其流程就是上面写的:输入BOS_WORD,解码器输出i;输入前面已经解码的BOS_WORD和i,解码器输出am…,输入已经解码的BOS_WORD、i、am、a和student,解码器输出解码结束标志位EOS_WORD,每次解码都会利用前面已经解码输出的所有单词嵌入信息,这个测试过程是没有问题,但是训练时候我肯定不想采用上述顺序解码类似rnn即一个一个目标单词嵌入向量顺序输入训练,肯定想采用类似编码器中的矩阵并行算法,一步就把所有目标单词预测出来。要实现这个功能就可以参考编码器的操作,把目标单词嵌入向量组成矩阵一次输入即可,但是在解码am时候,不能利用到后面单词a和student的目标单词嵌入向量信息,否则这就是作弊(测试时候不可能能未卜先知)。为此引入mask,目的是构成下三角矩阵,右上角全部设置为负无穷(相当于忽略),从而实现当解码第一个字的时候,第一个字只能与第一个字计算相关性,当解出第二个字的时候,只能计算出第二个字与第一个字和第二个字的相关性。具体是:在解码器中,自注意力层只被允许处理输出序列中更靠前的那些位置,在softmax步骤前,它会把后面的位置给隐去(把它们设为-inf)。

还有个非常重要点需要知道(看图示可以发现):解码器内部的带有mask的MultiHeadAttention的qkv向量输入来自目标单词嵌入或者前一个解码器输出,三者是相同的,但是后面的MultiHeadAttention的qkv向量中的kv来自最后一层编码器的输入,而q来自带有mask的MultiHeadAttention模块的输出。
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷
腾讯问卷

标签:Transformer,self,单词,文带,解码器,腾讯,字长,向量,问卷
来源: https://blog.csdn.net/XJS_1980/article/details/110347437