Transformer 学习笔记

1 为什么要用 Transformer ?(创新点)

1.1 问题来源

最开始机器翻译使用的 seq2seq 所存在的问题:

  • encoder 和 decoder 之间的上下文向量长度固定,但输入文本的长度是不固定的,长度不对称
  • 固定长度的上下文向量无法对长语句做有效编码,会遇到信息瓶颈,产生信息丢失的情况

为了解决上述问题,基于 attention 的 seq2seq 随即被提出(这个 attention 跟 transformer 中的 self-attention 是不一样的),它能很好地利用权重编码上下文信息,但是它依然有缺陷:

  • encoder/decoder 的单元体依然采用 RNN/LSTM/GRU,只能从左到右或从右到左依次计算,无法并行
  • 顺序计算中信息丢失问题有缓解,但未彻底解决

所以,可以说 transformer 所需要解决的主要问题:一个是并行计算,另一个是长依赖的信息丢失

1.2 创新点

transformer 摒弃了 RNN/LSTM/GRU 的单元结构,将 Attention 的思想发挥到极致,核心创新点包括:

  • self-attention,全新的一种结构
  • sin-cos,位置编码,论文中只是很简单的提了几句,但是很多人对它做了深入解读,相当巧妙
  • mask,并行化计算手段之一

2 什么是 Transformer ?(网络结构)

2.1 全局结构

论文:《Attention Is All You Need》

代码:[pytorch]

Transformer 的整体结构如上左图所示,它包含 Encoder/Decoder 两部分,分别由六个重复单元组成,上一级单元的输出作为下一级单元的输入,Encoder 的输出作为 Decoder 的输入;上右图是单元内部的构成细节,核心是 input embedding、positional encoding、multi-head attention、masked multi-head attention 等。

2.2 流程分析
2.2.1 编码器输入数据处理
  • 词嵌入:对输入句子的每个单词进行向量化,input embedding

    • 方式:Word2Vec
    • 效果:每个单词转换成一个 512 维向量,假设句子有 3 个单词,则得到一个 3*512 维度向量
    • 注意:每个句子的单词数量不一样,所以需要提前统计训练句子中长度最长的单词个数 N N N,其它句子以此为标准,不足的以特定标志编码填充
  • 位置编码:摈弃掉RNN循环结构后,单词间的位置信息需要重新编码,positional encoding

  • 方式:

    • 网络自动学习:给每个句子定义一个 ( 1 , N , 512 ) (1,N,512) (1,N,512) 的可学习向量,加入到位置编码中即可
      • 自定义规则:sin-cos 编码,原文就采用的这种方式
    • 效果:不同位置的单词都能拥有一个与其绝对位置相关联的 512 维编码向量
    • 注意:位置编码和词嵌入向量是元素相加的关系,其结果作为第一个编码器的输入,该输入的 shape 是 ( b , N , 512 ) (b,N,512) (b,N,512)
2.2.2 编码器前向运行
  • 注意力层:每个单词的编码输出通过 Attention 机制引入其余单词的编码信息
    • 方式:Multi Head Attention
    • 维度: ( b , N , 512 ) (b,N,512) (b,N,512) ( b , N , 512 ) (b,N,512) (b,N,512)
    • 效果:不同单词之间的关联更强,模型对上下文信息把握更准,同时 multi head 增强了模型专注于不同位置的能力
    • 注意:论文中采用 8 个 head,并加入dropout、残差(add)和归一化(normal)操作,目的是防止梯度消失、加快收敛
  • 前馈神经网络
    • 方式:两个全连接层
    • 维度: ( b , N , 512 ) (b,N,512) (b,N,512) ( b , N , 512 ) (b,N,512) (b,N,512)
    • 效果:增加非线性变换
    • 注意:前馈网络中除了全连接层以外,还有 relu、dropout、残差、归一化;整个编码器会将 “attention-forward” 的过程重复 6 次
2.2.3 解码器输入数据处理

第一个解码器输入:最后一个编码器的输出 + 目标词嵌入向量

后续解码器输入:最后一个编码器的输出 + 前面解码器的输出

  • 目标单词嵌入
    • 方式:Word2Vec
    • 效果:将目标句子中的单词转换成一个 512 维向量
    • 注意:解码器的单词嵌入需要将单词右移一位,在头部加入一个预定义好的解码开始标志位 BOS_WORD
  • 位置编码
    • 方式:sin-cos 编码
    • 效果:不同位置的单词获得一个与其绝对位置关联的 512 维编码向量
    • 注意:其位置同样要跟上面的目标单词嵌入相对应,多一位 BOS_WORD
2.2.4 解码器前向运行

假设现在要预测的目标句子是:i am a student,那么解码器是如何完成解码的?

首先要知道,推理和训练的解码过程是不同的,先看推理的过程:

  1. 输入BOS_WORD,计算词向量,送入解码器,解码器输出 i
  2. 输入已经解码的 BOS_WORD 和 i,计算词向量,送入解码器,解码器输出 am
  3. 输入已经解码的 BOS_WORD、i 和 am,计算词向量,送入解码器,解码器输出 a
  4. 输入已经解码的 BOS_WORD、i 、am 和 a,计算词向量,送入解码器,解码器输出 student
  5. 输入已经解码的 BOS_WORD、i、am、a 和 student,计算词向量,送入解码器,解码器输出解码结束标志位 EOS_WORD
  6. 解码结束

上面的动图省略了第一步过程,但也清晰地展示了推理时的解码过程。再来看训练的过程:

  1. 推理的时候每次预测要依赖上一次推理的结果来做词嵌入,而训练的时候上一次推理的结果实际上已经知道了,因为我们有 label
  2. 此时就可以把多个词向量做成一个矩阵,然后乘以一个下三角矩阵,也就是 mask,这样就能掩盖后面的目标单词信息,做到一次解码

着重看下模型解码器的结构组成:

  • Masked注意力层
    • 方式:Masked Multi-Head Attention
    • 输入:qkv 向量的输入 x 来自目标单词嵌入或上一个解码器输出(它们共用同一个输入),另外还有一个 mask 下三角矩阵
    • 注意:mask 作用在 softmax 归一化之前,其维度是 ( N , N ) (N,N) (N,N)
  • 注意力层
    • 方式:Multi-Head Attention
    • 输入:q 向量的输入 x 来自 masked 注意力层的输出,kv 向量的输入 x 来自最后一个编码器的输出(这两个共用一个输入)
    • 注意:这个在结构上跟编码器的注意力层是完全一致的
  • 前馈神经网络
    • 方式:两个全连接层
    • 效果:将解码器输出的特征做分类,例如解码器输出是 ( b , N , 512 ) (b,N,512) (b,N,512),经过前馈神经网络后变成 ( b , N , c l s ) (b,N,cls) (b,N,cls),cls 表示整个训练集中单词的数量
    • 注意:全连接层之后接 softmax 做分类,最后使用 ce loss 损失函数
2.3 细节理解
2.3.1 sin-cos embedding

位置编码需要得到单词的位置信息,其输入是单词在句子中的位置 pos,输出是 512 维向量。具体计算公式为:
P E ( p o s , 2 i ) = s i n ( p o s / 1000 0 2 i / d ) P E ( p o s , 2 i + 1 ) = c o s ( p o s / 1000 0 2 i / d ) PE_{(pos,2i)}=sin(pos/10000^{2i/d}) \\ PE_{(pos,2i+1)}=cos(pos/10000^{2i/d}) PE(pos,2i)=sin(pos/100002i/d)PE(pos,2i+1)=cos(pos/100002i/d)
其中 2 i , 2 i + 1 2i,2i+1 2i,2i+1 表示输出向量的元素位置, d d d 表示向量的长度(也就是 512);该计算方式将输出向量 512 个维度切分为偶数行和奇数行,偶数行采用 sin 函数编码,奇数行采用 cos 函数编码,然后按照原行号进行拼接。

image-20201019163927533

为什么要采用这种方式编码位置信息?(参考

  • 它能为每个时间步输出一个独一无二的编码;
  • 不同长度的句子之间,任何两个时间步之间的距离保持一致;
  • 模型能毫不费力地泛化到更长的句子,且它的值是有界的;
  • 它是确定性的。
2.3.2 self-attention

计算步骤:

  • 计算三个向量:计算输入句子中每个单词的查询向量 q i q_i qi、键向量 k i k_i ki、值向量 v i v_i vi,计算方式是用输入词向量 X i X_i Xi 乘以相应的矩阵 W Q , W K , W V W^Q,W^K,W^V WQ,WK,WV,这里的词向量实际上是词嵌入加上位置编码之后的向量,图中没有表示出来;
  • 计算单词间得分:计算句子中其它单词对当前单词的得分,这个得分也就代表了当前单词有多重视句子的其他部分,计算公式: q i ⋅ k j q_i\cdot k_j qikj;然后将得分除以8,8是论文中使用的键向量的维数 64 的平方根,这会让梯度更稳定。这里也可以使用其它值,8只是默认值;
  • 得分归一化:对得分向量做 softmax,使当前单词在其它所有单词上的得分趋于归一化,得到的分数都是正值且和为1;它给出了句子中其他位置的单词对编码当前单词的贡献,因为当前单词对自己的贡献是最大的,所以将获得最高的 softmax 分数;
  • 加权求和:将每个单词的值向量 v j v_j vj 乘以 softmax 分数,这样可以关注语义上相关的单词,并弱化不相关的单词;最后对加权值向量求和。

具体计算公式:
Q = X × W Q K = X × W K V = X × W V A t t e n t i o n ( Q , K , V ) = s o f t m a x ( Q × K T d k ) V Q=X \times W^Q \\ K=X \times W^K \\ V=X \times W^V \\ Attention(Q,K,V)=softmax(\frac{Q \times K^T}{\sqrt{d_k}})V Q=X×WQK=X×WKV=X×WVAttention(Q,K,V)=softmax(dk Q×KT)V
每一个自注意力层需要定义三个可学习矩阵 W Q , W K , W V W^Q,W^K,W^V WQ,WK,WV ,其 shape 为 ( 512 , M ) (512,M) (512,M),为了保证经过注意力层后维度不发生变化,一般 M M M 设置为 512。 Q , K , V Q,K,V Q,K,V 就是查询向量、键向量、值向量组成的矩阵, Q Q Q K T K^T KT 矩阵相乘可以计算向量相似性(也就是单词之间的贡献度),采用 softmax 转换为概率分布后,同值向量矩阵 V V V 做矩阵相乘即可实现加权求和。

具体代码实现如下,输出内容有两个:输出向量,注意力矩阵(也就是贡献度)

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,d_k=64,防止梯度过大
        # QK/sqrt(dk),计算单词相互之间的贡献度,attn的维度是(b,N,N)
        attn = torch.matmul(q / self.temperature, k.transpose(2, 3))

        # 加入 mask,掩盖后面单词的影响
        if mask is not None:
            attn = attn.masked_fill(mask == 0, -1e9)
            
        # softmax+dropout
        attn = self.dropout(F.softmax(attn, dim=-1))
        
        # 概率分布xV,加权求和,output的维度是(b,N,512)
        output = torch.matmul(attn, v)

        return output, attn
2.3.3 multi-head attention

论文中使用 8 头,每一头都需要一组可学习矩阵 W i Q , W i K , W i V W^Q_i,W^K_i,W^V_i WiQ,WiK,WiV,都需要做一次 self-attention 的计算,最后将计算结果做一个 concat。单独看每一头 Q i , K i , V i Q_i,K_i,V_i Qi,Ki,Vi的维度,一般是 ( b , N , 64 ) (b,N,64) (b,N,64),也可以有变化,但是要保证三个维度满足以下数字关系:

  • Q i Q_i Qi 的 shape: ( b , L , E ) (b,L,E) (b,L,E)
  • K i K_i Ki 的 shape: ( b , S , E ) (b,S,E) (b,S,E)
  • V i V_i Vi 的 shape: ( b , S , E ) (b,S,E) (b,S,E)

具体代码实现时,并不是对每个头单独做 attention 计算,而是以组合矩阵的方式统一处理;最终返回的内容,除了编码器的输出以外,还会返回注意力矩阵。具体内容可以参考 transformer 的实现

class MultiHeadAttention(nn.Module):
    ''' Multi-Head Attention module '''

    def __init__(self, n_head, d_model, d_k, d_v, dropout=0.1):
        # n_head: 头的数量,默认值为 8
        # d_model: 编码向量的长度,一般是 512
        # d_k: 键向量的维度
        # d_v: 值向量的维度
        super().__init__()

        self.n_head = n_head
        self.d_k = d_k
        self.d_v = d_v

        # QKV可学习矩阵参数初始化
        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是相等的,都是编码器的输入X
        d_k, d_v, n_head = self.d_k, self.d_v, self.n_head
        sz_b, len_q, len_k, len_v = q.size(0), q.size(1), k.size(1), v.size(1)

        residual = q

        # 将输入X也就是传入的qkv分别与可学习矩阵相乘,然后切分成 n_head 个头
        # 矩阵维度变为: b x lq x n x dv
        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 x n x lq x dv
        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.
            
		# 全部 n_head 头一起做 attention 计算
        q, attn = self.attention(q, k, v, mask=mask)

        # 转换维度,变回原始状态: b x lq x n x dv
        q = q.transpose(1, 2).contiguous().view(sz_b, len_q, -1)
        q = self.dropout(self.fc(q))
        
        # 残差计算,层归一化
        q += residual
        q = self.layer_norm(q)

        return q, attn

pytorch 对于多头注意力做了实现,作为一个 API 提供给用户,相关参数含义可以查看 nn.MultiheadAttention

2.3.4 masked multi-head attention

之所以要在训练的时候使用 mask 矩阵,是因为:

  1. 让解码器在解码第 i 个单词的时候,只能看到前面 i-1 个单词的信息,避免作弊;
  2. 利用矩阵一次性完成所有单词的解码,不必挨个做计算,后续单词的解码也无需等待前面单词解码的结果;

image-20210320152549530

masked multi-head attention 的代码实现同 multi-head attention 是一致的,只不过传入的参数 mask 不再是 None。具体起作用的代码如下:

if mask is not None:
    attn = attn.masked_fill(mask == 0, -1e9)

这在 self-attention 的实现中也能看到!

3 怎么用 Transformer ?(应用拓展与变形)

image-20210322164037350

3.1 NLP 中的 Transformer

(TODO)

3.2 CV 中的 Transformer
3.2.1 图像分类 ViT
(1)论文代码

《An Image is Worth 16x16 Words:Transformers for Image Recognition at Scale》[code]

(2)网络结构

image-20210322164454974

把图像分成固定大小的 patchs,把 patchs 看成 words 送入 transformer 的 encoder,中间没有任何卷积操作;最后利用一个 FC 层来预测分类。

看一下具体的操作:

  • 分块
    • H × W × C H \times W \times C H×W×C 的图片分割成 N N N 个分辨率为 P × P × C P \times P \times C P×P×C 的小块,把每一个小块看成是一个 word
    • 原本是 ( B , C , H , W ) (B, C, H, W) (B,C,H,W) 的输入变成 ( B , N , P 2 C ) (B, N, P^2C) (B,N,P2C) 的词嵌入,其中 N = H W P 2 N=\frac{HW}{P^2} N=P2HW,每一个小块像素展平就是一个词嵌入向量
    • 对应于分块后词嵌入的位置编码,作者采用的是“随机初始化、在训练中学习”的方式
    • 这里除了位置编码外,还有一个可选项 Class Embedding,也是通过一个可学习矩阵实现的
    • 原文中 P = 32 P=32 P=32,导致词嵌入向量维度 P 2 C = 3072 P^2C=3072 P2C=3072,太大了,所以作者先进行降维操作,降到 1024
    • 原文中切成了 9 块,但是 N = 10 N=10 N=10,原因是 ViT 只用了 Transformer 的编码器,没用它的解码器,编码器之后直接开始分类预测,所以就需要额外添加一个开启解码的标志,也就是上图中的 0 号 word。假设现在没有这个 0 号 word,那么应该取哪一个 patch 的输出向量进行后续的分类呢?因为每一个 patch 都是图像的一部分,单独取哪一个都不合适,所以要添加这个 0 号 word,取它的输出向量做分类预测。
  • encoder
    • 这里的 encoder 跟 transformer 中没有什么区别,结构是一模一样的
  • MLP Head
    • 一个全连接做分类,要注意的是:输入取的是编码器的第 0 维输出向量

CRNN 可以理解为每四个像素作为一个序列点,其实也是将图像分割成 patch,然后一个 patch 就是一个 word;所以这部分完全可以使用 transformer 做替换。

(3)模型性能

image-20210326105629144

作者使用微调的方式做了大量试验,得出结论:CV 领域应用 transformer 需要大量数据进行预训练,在同等数据量的情况下性能不如 CNN;一旦数据量上来了,对应的训练时间也会加长很多,那么就可以轻松超越 CNN

(4)评价
  1. 这里将完整的图像强行分割成 patches,实际上就是为了迎合 transformer,patch 之间的空间信息将会丢失;
3.2.2 目标检测 DETR
(1)论文代码

《End-to-End Object Detection with Transformers》[code]

(2)网络结构

image-20210322165449894

先用 CNN 提取特征,然后把特征图的每个点看成 word,将特征序列送入 transformer 的 encoder-decoder 结构,最后输出指定长度为 N 的无序集合,集合中每个元素包含物体类别和坐标。其中 N 表示整个数据集中图片上最多物体的数目,因为整个训练和测试都 Batch 进行,如果不设置最大输出集合数,无法进行batch训练;如果图片中物体不够 N 个,那么就采用 no object 填充,表示该元素是背景。

(3)模型性能

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-F2wn85X7-1652862847859)(https://raw.githubusercontent.com/dagongji10/FigureBed/master/img/image-20210326110614753.png)]

3.2.3 图像分割 SETR
(1)论文代码

《Rethinking Semantic Segmentation from a Sequence-to-Sequence Perspective with Transformers》[code]

(2)网络结构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qhd8V87A-1652862847859)(https://raw.githubusercontent.com/dagongji10/FigureBed/master/img/image-20210322170258155.png)]

用 ViT 作为的图像的 encoder,然后加一个CNN的 decoder 来完成语义图的预测。

(3)模型性能

(TODO)

3.3 Pytorch中Transformer的使用
3.3.1 torch.nn.Transformer

一个 Transformer 层,包含多个编码器、解码器。

# 构造
torch.nn.Transformer(
    d_model: int = 512,						# 词嵌入特征向量的维度,一般都是 512
    nhead: int = 8,							# 多头注意力的个数,一般是 8
    num_encoder_layers: int = 6,			# 编码器的重叠次数
    num_decoder_layers: int = 6,			# 解码器的重叠次数
    dim_feedforward: int = 2048,			# 编码器、解码器中前向计算的维度
    dropout: float = 0.1,					# dropout 正则化比例
    activation: str = 'relu',				# 编码器、解码器中激活函数的类型,可以是 relu/gelu
    custom_encoder: Optional[Any] = None,
    custom_decoder: Optional[Any] = None,
    layer_norm_eps=1e-05, 
    batch_first=False, 
    norm_first=False, 
    device=None, 
    dtype=None
)
# 前向
forward(
    src, 							# (S,N,E),必需,词嵌入之后的编码器输入,S 输入序列数,N 批量数,E 特征维度
    tgt, 							# (T,N,E),必需,词嵌入之后的解码器输入,T 输出解码数,N 批量数,E 特征维度
    src_mask=None, 					# (S,S),编码器输入的掩码
    tgt_mask=None, 					# (T,T),解码器输入的掩码
    memory_mask=None, 				# (T,S),编码器输出的掩码
    src_key_padding_mask=None, 
    tgt_key_padding_mask=None, 
    memory_key_padding_mask=None
)
# 使用
transformer_model = nn.Transformer(nhead=16, num_encoder_layers=12)
src = torch.rand((10, 32, 512))
tgt = torch.rand((20, 32, 512))
out = transformer_model(src, tgt)
3.3.2 torch.nn.TransformerEncoderLayer

一个编码器层(类似于卷积层),由 self-attention、forward 两部分构成。

# 构造
torch.nn.TransformerEncoderLayer(
    d_model, 							# 词嵌入特征向量的维度
    nhead, 								# 多头注意力的个数
    dim_feedforward=2048, 				# 前向计算的维度
    dropout=0.1, 						# dropout 正则化比例
    activation=<function relu>, 		# 激活函数的类型,可以是 relu/gelu
    layer_norm_eps=1e-05, 				# 层归一化参数
    batch_first=False, 
    norm_first=False, 
    device=None, 
    dtype=None
)
# 前向
forward(
    src, 						# 必需,词嵌入之后的编码器输入
    src_mask=None, 				# 输入的掩码
    src_key_padding_mask=None
)
# 使用
encoder_layer = nn.TransformerEncoderLayer(d_model=512, nhead=8)
src = torch.rand(10, 32, 512)
out = encoder_layer(src)
3.3.3 torch.nn.TransformerEncoder

多个编码器组成的一个 stack。

# 构造
torch.nn.TransformerEncoder(
    encoder_layer, 				# 编码器层
    num_layers, 				# 编码器层的重复数量
    norm=None					# 层归一化
)
# 前向
forward(
    src, 						# 必需,词嵌入之后的编码器输入
    src_mask=None, 				# 输入的掩码
    src_key_padding_mask=None
)
# 使用
encoder_layer = nn.TransformerEncoderLayer(d_model=512, nhead=8)
transformer_encoder = nn.TransformerEncoder(encoder_layer, num_layers=6)
src = torch.rand(10, 32, 512)
out = transformer_encoder(src)
3.3.4 torch.nn.TransformerDecoderLayer

一个解码器层,由 self-attention、multi-head-attention、forward 构成。

# 构造
torch.nn.TransformerDecoderLayer(
    d_model, 
    nhead, 
    dim_feedforward=2048, 
    dropout=0.1, 
    activation=<function relu>, 
    layer_norm_eps=1e-05, 
    batch_first=False, 
    norm_first=False, 
    device=None, 
    dtype=None
)
# 前向
forward(
    tgt, 
    memory, 
    tgt_mask=None, 
    memory_mask=None, 
    tgt_key_padding_mask=None, 
    memory_key_padding_mask=None
)
# 使用
decoder_layer = nn.TransformerDecoderLayer(d_model=512, nhead=8)
memory = torch.rand(10, 32, 512)
tgt = torch.rand(20, 32, 512)
out = decoder_layer(tgt, memory)
3.3.5 torch.nn.TransformerDecoder

多个解码器重叠组成的 stack。

# 构造
torch.nn.TransformerDecoder(
    decoder_layer, 
    num_layers, 
    norm=None
)
# 前向
forward(
    tgt, 
    memory, 
    tgt_mask=None, 
    memory_mask=None, 
    tgt_key_padding_mask=None, 
    memory_key_padding_mask=None
)
# 使用
decoder_layer = nn.TransformerDecoderLayer(d_model=512, nhead=8)
transformer_decoder = nn.TransformerDecoder(decoder_layer, num_layers=6)
memory = torch.rand(10, 32, 512)
tgt = torch.rand(20, 32, 512)
out = transformer_decoder(tgt, memory)
3.3.6 torch.nn.MultiheadAttention

多头注意力层。

# 构造
torch.nn.MultiheadAttention(
    embed_dim, 				# 词嵌入后特征维度
    num_heads, 				# 多头注意力的个数
    dropout=0.0, 
    bias=True, 
    add_bias_kv=False, 
    add_zero_attn=False, 
    kdim=None, 
    vdim=None, 
    batch_first=False, 
    device=None, 
    dtype=None
)
# 前向
forward(
    query, 						# 查询向量,(L,N,E),L 目标序列长度
    key, 						# 键向量,(S,N,E),S 源序列长度
    value, 						# 值向量,(S,N,E)
    key_padding_mask=None, 
    need_weights=True, 
    attn_mask=None, 
    average_attn_weights=True
)
# 使用
multihead_attn = nn.MultiheadAttention(embed_dim, num_heads)
attn_output, attn_output_weights = multihead_attn(query, key, value)

4 常见问题

4.1 编码器

(1)词嵌入的时候,训练集中句子长度不等,如何解决?

答:找到训练集中长度最长的句子,将词嵌入长度设置为该句子长度,一般设置为 100。

(2)为什么词嵌入做完向量化了还要加一个位置编码?

答:transformer 跟 RNN 不同,它不是像后者那样按照顺序将单词依次输入,因此也就失去了单词之间的位置信息,所以需要位置编码将此部分内容加进去。

(3)位置编码为什么要设置成 sin-cos 的方式?

答:

(4)self-attention 中的 QKV 分别有什么含义?

答:Q 表示查询矩阵,就好比是你想搜索查询的问题;K 是键矩阵,就好比是搜索引擎中文章的标题或者摘要;V 是值矩阵,就好比是搜索引擎中的所有文章。Q 和 K 做点乘就是将你的问题同搜索引擎中所有文章的标题做匹配,得到每篇文章跟你问题的一个关联程度值,然后用这些关联值跟所有文章做一个加权平均,关联值大的文章融合信息就多,关联值小的文章融合信息就少(这就是注意力机制,权重大的部分重点关注,权重小的部分略微关注),得到最终的答案。(参考

(5)self-attention 中计算 attention-score 的时候为什么使用点乘,而不是用加法?

答:因为 attention-score 的意义是表征每一个单词对其它单词的影响力,也就是向量的相似度,计算向量相似度的方式就是点乘,利用点乘来产生在不同空间上的投影,形成 self-attention 的表达能力;如果用加法的话最终得到的还是一个同等维度的向量,而不是一个代表相似度的浮点值!

(6)self-attention 中为什么 Q 和 K 要使用不同的权重矩阵做初始化,为什么不能使用同一个值做自身的点乘?

答:Q 和 K 相乘的目的是为了计算注意力矩阵(也称为相似度矩阵),它代表着不同单词之间的相互影响程度,这也是 self-attention 中的核心 attention 所在!使用不同的权重矩阵可以理解为,它是在不同的空间上做投影,增加了表达能力,让注意力矩阵拥有更强的泛化能力。反之,如果使用同一个值做自身的点乘,那么得到的注意力矩阵就是一个对称矩阵,就有 d ( x 1 , x 2 ) = d ( x 2 , x 1 ) d(x_1, x_2)=d(x_2, x_1) d(x1,x2)=d(x2,x1),但是在 self-attention 中这种对称性是不必要的,想象一下“我是一个男孩”这句话,“男孩”修饰“我”的重要性应该要高于“我”修饰“男孩”的重要性,所以同一个值自身点乘的方式会降低注意力矩阵的表达泛化能力。(参考

(7)transformer 为什么要用残差结构?

答:反向传播的时候不会造成梯度消失。

(8)transformer 为什么使用 LayerNorm 而不是 BatchNorm?

答:CV 领域认为 channel 维度的信息是很重要的,如果对 channel 维度也进行归一化,则会造成重要信息的丢失;所以 BN 层对于一个 ( b , w , h , c ) (b,w,h,c) (b,w,h,c) 的特征图,计算的是 ( b , w , h ) (b,w,h) (b,w,h) 的均值方差,对每一个 channel(特征)来做这样一个计算。但在 NLP 领域中,一个 batch 中的句子长度不一,并且这些句子之间没有什么关联,它们之间的差异性需要被保留,所以在一个 batch 内部做归一化是不合适的;因此,transformer 中的归一化采用 LayerNorm 形式,对一句话的每个单词向量做归一化,也就是在 ( b , N , M ) (b,N,M) (b,N,M) M M M 维度上做归一化。(参考

(9)为什么使用 multi-head 而不是单头?

答:多头可以在不增加计算量的情况下,使参数矩阵形成多个子空间,使矩阵对多方面信息进行学习,矩阵整体的size不变,改变的只是每个head的维度大小。

(10)transformer 的并行化体现在哪里?

答:主要体现在编码器的 self-attention 部分,它能够以矩阵的形式并行处理整个输入序列,RNN 则只能按先后顺序依次处理。

4.2 解码器

(1)推理和训练中解码的流程有何不同?解码器可以做并行化吗?

答:训练时输入可以通过矩阵的形式输入,能够做并行化处理;推理时只能依次输入向量,不能并行处理。

(2)decoder 中多头注意力层的 qkv 来自于哪里?mask 多头注意力层的 qkv 又来自于哪里?

答:多头注意力层的 q 来自于 mask 多头注意力层的输出,kv 来自于最后一个编码器的输出;mask 多头注意力层的 qkv 来自目标单词嵌入或者上一个解码器的输出。

(3)mask 下三角矩阵作业的位置是哪儿?假设编码器输入 qkv 的维度是 ( b , N , 512 ) (b,N,512) (b,N,512),那么 mask 下三角矩阵的维度是多少?

答:mask 作用在 attention-score 之后,但是在 softmax 之前,它将 mask 掩码值为 0、看不见的位置置为负极大值,如此一来 softmax 之后那些看不见的值其影响力就变为 0。mask 的维度跟句子长度有关,有 N N N 个单词,则它的维度就是 ( N , N ) (N,N) (N,N)

5 参考

初级理解:

《3万字长文带你轻松入门视觉Transformer》:推荐仔细阅读,里面有些小笔误,但是问题不大

《Transformer升级之路:Sinusoidal位置编码追根溯源》:关于位置编码的深入解析与推导

《The Annotated Transformer》:从头开始实现 transformer

高层次理解:

《How Transformers work in deep learning and NLP: an intuitive introduction》:高层次理解

《The Illustrated Transformer》:图解 transformer

《The Transformer Family》:transformer 的技术摘要,最新发展的总结

应用:

《Transformers》:transformer 在不同领域的应用,GitHub 项目,star 高达 42k

《搞懂视觉Transformer原理和代码,看这篇技术综述就够了!》:transformer 在不同领域的几个具体应用

分析:

《如何看待Transformer在CV上的应用前景,未来有可能替代CNN吗?》:大家对于 transformer 在 CV 中所扮演角色的一些看法

  • 2
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
李宏毅是一位著名的机器学习深度学习专家,他在教学视频中也提到了Transformer模型。下面是一些关于李宏毅关于Transformer笔记总结: 1. Transformer 是一种基于注意力机制(attention mechanism)的序列到序列(sequence-to-sequence)模型。它在自然语言处理任务中取得了很大的成功。 2. Transformer 模型的核心思想是完全摒弃了传统的循环神经网络(RNN)结构,而是采用了自注意力机制(self-attention mechanism)来建模输入序列之间的依赖关系。 3. 自注意力机制能够将输入序列中的每个位置与其他位置建立联系,从而捕捉到全局上下文的信息。它能够解决传统的RNN模型在处理长序列时的梯度消失和梯度爆炸问题。 4. Transformer 模型由编码器(Encoder)和解码器(Decoder)两部分组成。编码器负责将输入序列表示为高维向量,解码器则根据编码器的输出生成目标序列。 5. 编码器和解码器由多个层堆叠而成,每一层都包含了多头自注意力机制和前馈神经网络。多头自注意力机制可以并行地学习输入序列中不同位置之间的关系。 6. Transformer 模型还引入了残差连接(residual connection)和层归一化(layer normalization)来帮助模型更好地进行训练和优化。 这些是李宏毅关于Transformer的一些主要笔记总结,希望对你有所帮助。注意,这些总结仅代表了我对李宏毅在其教学视频中所讲述内容的理解,如有误差请以李宏毅本人的观点为准。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值