Attention Is All You Need论文精读笔记

Attention Is All You Need论文精读笔记

沐神讲解Transformer论文的相关笔记。

部分内容参考了Transformer论文逐段精读【论文精读】 - 哔哩哔哩 (bilibili.com)

相关博文:

《动手学深度学习》Seq2Seq、attention、transformer相关问题的总结与反思_QIzikk的博客-CSDN博客

《动手学深度学习》Seq2Seq代码可能出错的原因及适当分析_QIzikk的博客-CSDN博客

相关视频:

Transformer论文逐段精读【论文精读】_哔哩哔哩_bilibili

相关课程:《动手学深度学习》
在这里插入图片描述

0、FAQ

1 Transformer为何使用多头注意力机制?(为什么不使用一个头)
2.Transformer为什么Q和K使用不同的权重矩阵生成,为何不能使用同一个值进行自身的点乘? (注意和第一个问题的区别)
3.Transformer计算attention的时候为何选择点乘而不是加法?两者计算复杂度和效果上有什么区别?
4.为什么在进行softmax之前需要对attention进行scaled(为什么除以dk的平方根),并使用公式推导进行讲解
5.在计算attention score的时候如何对padding做mask操作?
6.为什么在进行多头注意力的时候需要对每个head进行降维?(可以参考上面一个问题)
7.大概讲一下Transformer的Encoder模块?
8.为何在获取输入词向量之后需要对矩阵乘以embedding size的开方?意义是什么?
9.简单介绍一下Transformer的位置编码?有什么意义和优缺点?
10.你还了解哪些关于位置编码的技术,各自的优缺点是什么?
11.简单讲一下Transformer中的残差结构以及意义。
12.为什么transformer块使用LayerNorm而不是BatchNorm?LayerNorm 在Transformer的位置是哪里?
13.简答讲一下BatchNorm技术,以及它的优缺点。
14.简单描述一下Transformer中的前馈神经网络?使用了什么激活函数?相关优缺点?
15.Encoder端和Decoder端是如何进行交互的?(在这里可以问一下关于seq2seq的attention知识)
16.Decoder阶段的多头自注意力和encoder的多头自注意力有什么区别?(为什么需要decoder自注意力需要进行 sequence mask)
17.Transformer的并行化提现在哪个地方?Decoder端可以做并行化吗?
19.Transformer训练的时候学习率是如何设定的?Dropout是如何设定的,位置在哪里?Dropout 在测试的需要有什么需要注意的吗?
20.解码端的残差结构有没有把后续未被看见的mask信息添加进来,造成信息的泄露。

1、导言部分

RNN有两个致命缺点:

A . ht的计算是依赖ht-1和当前输入X的,所以想要得到时间步t的隐藏状态,就必须要计算ht-1的隐藏状态,这使得RNN的并行度非常差

B. 过早的历史信息可能被丢掉。时序信息是一步一步往后传递的.

2、相关工作

考虑使用CNN替代RNN,但是CNN也有问题:CNN对比较长的序列难以建模。因为卷积做计算的时候,每次是看一个比较小的窗口(例如3*3的像素块),如果两个像素相隔比较远,那需要很多个卷积层一层层把两个像素联系起来

但是CNN有优势在于,可以有多个输出通道,每一个输出通道可以认为是识别不一样的模式。==> 想要CNN的这个优点,所以提出了Multi-Head Attention,模拟CNN多通道的效果。

3、模型架构

编码器-解码器模型

编码器会将X = (x1, x2 … xn) 【长度为n。如果是一个句子,那xi就表示句子中的第i个词】转化成Z = (z1, z2 … zn) 【长度也是n,每一个zi对应xi的一个向量表示,将原始的输入变成机器学习可以理解的一些向量】;

解码器会得到编码器的输出Z。与编码器不同之处在于,编码器的词是一个个生成的,无法一下看到整个句子。【 auto-regressive in decoder,即:在解码器中,输入又是自己的输出】

Transformer使用了Encoder-Decoder架构

在这里插入图片描述

Transformer编码器

使用了N = 6个完全一样层。每一个层有两个子层:Multi-Head Attention 和 FFN(FFN其实就是一个MLP)

对于每一个子层,我们使用一个残差连接,在最后使用层归一化,即:LayerNorm(X + sublayer(X))

论文中说,为了维度计算方便,我们这里设置每一层的维度都是512【与CNN不同。CNN一般是每经过一层,空间维度往下减少,channel维度往上增加

Transformer解码器

使用了N = 6个完全一样层。每一个层有三个子层:Multi-Head Attention、与编码器连接的Multi-Head Attention 和 FFN(FFN其实就是一个MLP)。由于预测的时候是看不到当前当前时间之后的输出,所以第一个Multi-Head层是Masked-Multi-Head self-Attention【保证训练和预测的时候行为是一致的。btw,沐神之前seq2seq代码出错就是因为训练和预测的行为不一致,详情:《动手学深度学习》Seq2Seq代码可能出错的原因及适当分析_QIzikk的博客-CSDN博客.

Layer-Norm

与Batch-Norm的区别:BN是每一列均值变0方差变1,而LN是每一行均值变0方差变1。【列是特征,行是样本】

下图中蓝色是BN,黄色是LN。

在NLP,一般输入都是3维的(因为一个样本往往有序列长度,seq(n)就表示序列长度是n, 序列中的每一个元素都用一个向量表示)。

在NLP,一般输入的长度是不一样的(看似一样是因为我们做过padding了),所以BN的话,每次小批量算出的均值和方差的抖动相差是比较大的; 而且回忆下:BN在预测的时候,使用的均值和方差是滑动指数平均计算之后的结果,如果预测的长度在训练时出现很少,那BN可能不是那么好用;

但是LN就不存在这个问题,因为是每个样本自己算均值和方差,也不需要存储一个全局的均值和方差(因为样本自己可以算)。

LayerNorm 和 BatchNorm 的例子理解:n 本书

BatchNorm:n本书,每本书的第一页拿出来,根据 n 本书的第一页的字数均值 做 Norm

LayerNorm:针对某一本书,这本书的每一页拿出来,根据次数每页的字数均值,自己做 Norm

在这里插入图片描述

Attention

注意力函数是 一个将一个query 和一些 key - value 对映射成一个输出的函数,其中所有的 query、key、value 和 output 都是一些向量。

具体来说,output 是 value 的一个加权和 --> 输出的维度 == value 的维度【具体到代码中是 (batch_size, num_queries, value的维度)】。output 中 value 的权重(加权的“权”是怎么得到的?) --> 查询 query 和该value对应的 key 的相似度

在这里插入图片描述

以这个小图为例,如果黄色的query和前两个key相似度比较大,那前两个value就会得到较大的权重;绿色的query和最后一个key相似度比较大,那么最后一个value就会得到较大的权重。虽然key-value没变,但是query不同会导致权重分配不同,最后的输出结果也不同

由于有不同Attention的计算方法,这里论文只详细写了在Transformer中使用到的——

Scaled Dot-Product Attention

query、key是等长的,都是dk,value长度是dv(所以输出的长度也是dv)。我们直接把query和key做内积(因为维度相同),

如果内积的值越大,就表示两个向量的相似度就越高;如果内积是0,两个向量正交,则这两个向量没有相似度

【这里的内积是可以为负的,但是最后做完softmax就会变成正数0-1之间

一个个算效率很低,往往是用矩阵运算

Q矩阵:(num_queries, d); K矩阵:(num_keys, d) V矩阵:(num_keys, dv)【维度肯定一样,才能做内积】

query和key的个数可能是不一样的!

矩阵的每一行是一个query对所有key的内积值

在这里插入图片描述

其实论文中使用的和普通的点积注意力机制几乎一样,所谓 “Scaled"就是除了一个sqrt(dk).

为什么要除一个sqrt(dk) —— 防止softmax函数的梯度消失

dk不是很大的时候,除不除均可。dk 比较大时 (2 个向量的长度比较长的时候),点积的值会比较大或者比较小

当的值比较大的时候,相对的差距会变大,导致最大值 softmax会更加靠近于1,剩下那些值就会更加靠近于0。值就会更加向两端靠拢,算梯度的时候,梯度比较小;而softmax会让大的数据更大,小的更小

因为 softmax 最后的结果是希望 softmax 的预测值,置信的地方尽量靠近1,不置信的地方尽量靠近0,以保证收敛差不多了。这时候梯度就会变得比较小,那就会跑不动

trasformer 里面一般用的 dk 比较大 (本文 512),所以除 sqrt(dk) 是一个不错的选择

在这里插入图片描述

Mask

这里的Mask主要是指masked-self-attention,所以query和key是等长的,均为n.

对于t时间的qt,我们应该只看k1, k2… kt-1,而不应该看之后的东西(因为还没有);但是在注意力的时候,qt其实是能看到所有的k的

解决方法:算还是一起算的【!误区!其实kt, kt+1 … kn都是一起参与运算的,并不是不算了】,只是需要保证在算权重的时候,我们只算前面的部分即可,后面的不看(做masked-softmax, 把后面的值都换成一个很小的负数,这样做softmax就会变成0,具体见《动手学深度学习》里面的代码)。

#@save
def masked_softmax(X, valid_lens):
    """通过在最后一个轴上掩蔽元素来执行softmax操作"""
    # X:3D张量,valid_lens:1D或2D张量
    if valid_lens is None:
        return nn.functional.softmax(X, dim=-1)
    else:
        shape = X.shape
        if valid_lens.dim() == 1:
            valid_lens = torch.repeat_interleave(valid_lens, shape[1])
            # print(valid_lens)
        else:
            # 如果是挨个指定的,拉开
            # print(valid_lens)
            valid_lens = valid_lens.reshape(-1)
            # print(valid_lens)
        # 最后一轴上被掩蔽的元素使用一个非常大的负值替换,从而其softmax输出为0
        X = d2l.sequence_mask(X.reshape(-1, shape[-1]), valid_lens,
                              value=-1e6)
        return nn.functional.softmax(X.reshape(shape), dim=-1)

Multi-Head

与其做一个单个的注意力函数,不如说把整个 query、key、value 整个project(投影)到一个低维,投影 h 次。然后再做 h 次的注意力函数,把每一个函数的输出 拼接在一起,得到最终的输出。

在这里插入图片描述

将原始的K,Q,V进入一个Linear线性层(将K,Q,V投影到一个更低的维度),做h次Scaled Dot-Product Attention,会得到h个输出,将他们concat起来,做一次线性的投影。

为什么要用Mukti-Head?

因为Scaled Dot-Product Attention中没有什么可以学习的参数,但是我们还是希望能学习到不同的模式。(如果用加性Attention, 还有一些参数可以学习)。所以先投影到低维,这个投影的W矩阵是可以学习的【理解:有h次的学习机会,使得可以在投影到的度量空间里面匹配不同的模式,类似CNN中的多个输出通道

因为原论文中的d=512,用来8个head,为了保证输入输出维度是一样的,所以每次单个头投影到的维度是64(512/8)

实际上的实现是可以使用一次大矩阵乘法实现的,就不需要8个for-loop。

*具体方法:

class MultiHeadAttention(nn.Module):
    def __init__(self, key_size, query_size, value_size, num_hiddens, num_heads, dropout, bias=False, **kwargs):
        super(MultiHeadAttention, self).__init__(**kwargs)
        self.num_heads = num_heads
        self.attention = DotProductAttention(dropout)
        self.W_q = nn.Linear(query_size, num_hiddens, bias=bias)
        self.W_k = nn.Linear(key_size, num_hiddens, bias=bias)
        self.W_v = nn.Linear(value_size, num_hiddens, bias=bias)
        self.W_o = nn.Linear(num_hiddens, num_hiddens, bias=bias)
    
    def forward(self, queries, keys, values, valid_lens):
        '''
        queries,keys,values的形状:
        (batch_size,查询或者“键-值”对的个数,num_hiddens)
        valid_lens 的形状:
        (batch_size,)或(batch_size,查询的个数)
        经过变换后,输出的queries,keys,values 的形状:
        (batch_size*num_heads,查询或者“键-值”对的个数,num_hiddens/num_heads)
        pq = pk = pv = num_hiddens/num_heads
        '''
        queries = transpose_qkv(self.W_q(queries), self.num_heads)
        keys = transpose_qkv(self.W_k(keys), self.num_heads)
        values = transpose_qkv(self.W_v(values), self.num_heads)
        
        if valid_lens is not None:
            # 在轴0,将第一项(标量或者矢量)复制num_heads次,然后如此复制第二项,然后诸如此类。
            valid_lens = torch.repeat_interleave(valid_lens, repeats=self.num_heads, dim=0)
        
        # output的形状:(batch_size*num_heads,查询的个数,num_hiddens/num_heads)
        # [最后一个维度应该是值的维度,都是值的维度这里已经是num_hiddens/num_heads了]
        output = self.attention(queries, keys, values, valid_lens)
        
        # output_concat的形状:(batch_size,查询的个数,num_hiddens)
        output_concat = transpose_output(output, self.num_heads)
        return self.W_o(output_concat)
        
 def transpose_qkv(X, num_heads):
    """为了多注意力头的并行计算而变换形状"""
    # 输入X的形状:(batch_size,查询或者“键-值”对的个数,num_hiddens)
    # 输出X的形状:(batch_size,查询或者“键-值”对的个数,num_heads,num_hiddens/num_heads)
    X = X.reshape(X.shape[0], X.shape[1], num_heads, -1)
    # 调整维度顺序,输出X的形状:(batch_size,num_heads,查询或者“键-值”对的个数,num_hiddens/num_heads)
    X = X.permute(0, 2, 1, 3)
    # 最终输出的形状:(batch_size*num_heads,查询或者“键-值”对的个数, num_hiddens/num_heads)
    return X.reshape(-1, X.shape[2], X.shape[3]) 

def transpose_output(X, num_heads):
    # X(output的形状:(batch_size*num_heads,查询的个数,num_hiddens/num_heads) )
    # 需要变成:(batch_size,查询的个数,num_hiddens)
    X = X.reshape(-1, num_heads, X.shape[1], X.shape[2])
    X = X.permute(0, 2, 1, 3)
    return X.reshape(X.shape[0], X.shape[1], -1)

Transformer中的3种Attention

编码器self-attention

在这里插入图片描述

输入是 (batch_size, num_step, num_hiddens) 的,这里假设b=1.

因为做self-attention, qkv都是相同的,所以最后的输出维度和输入维度相同

解码器self-attention

与编码器类似,唯一不同在于要做Mask, 理由见上。

编码器-解码器“交互”的attention

这里的key,value不再是自己,而是来自编码器的输出(num_step个长为d的向量);query来自之前解码器的输出(m个长为d的向量)

在这里插入图片描述

Feed Forward

在这里插入图片描述

本质就是一个MLP。max其实就是ReLU激活函数,xW1+b1是第一层,max()W2+b2是第二层

x是query后的输出,维度是d=512.W1会投影到2048,W2又把2048投影回了512.

其实是个单隐藏层的MLP,中间隐藏层将输入扩大到原来的4倍。

对比RNN——MLP起到了什么作用?

我们考虑最简单的情况:没有residual、没有 layernorm、 attention 单头。

attention起到的作用:把序列中的信息抓取出来,做一次汇聚, 对输入做一个加权和,加权和进入 point-wise MLP 。

【图中虽然画了多个红色方块MLP,但是每一个方块其实是一个,权重都是一样的(类似RNN展开的画法)】

在这里插入图片描述

所以图中高亮的部分已经有了序列中“感兴趣”的东西,信息已经抓取出来了,以至于在用MLP做映射,将信息映射到更想要的语义空间的时候,MLP只需要独立对每个输出做就行了。【理解:高亮部分是每个query加权得到的attention结果,已经包含了整个序列的信息

RNN呢

绿色代表上一次的输出,其实就是:上一个时刻的信息输出传入下一个时候做输入

在这里插入图片描述

所以总结如下:

RNN 跟 transformer 异:如何传递序列的信息——

RNN 是把上一个时刻的信息输出传入下一个时候做输入。Transformer 通过一个 attention 层,去全局的拿到整个序列里面信息,再用 MLP 做语义的转换

RNN 跟 transformer 同:语义空间的转换 + 关注点—— 用一个 MLP 来做语义空间的转换

Embedding

embedding:将输入的一个词、词语 token 映射成为一个长为 d = 512的向量。

论文中提到模型的编码器、解码器、最后 softmax 之前的 3 个 embedding 共享权重。–> 训练更简单【注意这里是把英语和德语放到一个字典中了,不再分开做两个字典,所以才能共享权重】。

注意Embedding之后乘了一个sqrt(d)

原因:权重 * sqrt(dmodel = 512) ,学 embedding 的时候,会把每一个向量的 L2 Norm 学的比较小。维度大的化,学到的一些权重值就会变小,但之后还需要加上 positional encoding(不会随着维度的增加而变化,是一个-1, 1之间的数)。

multiply weights by sqrt(dmodel) 使得 embedding 和 positional encosing 的 scale 差不多,可以做加法

Positional Encoding

RNN中,上一个时刻的输出作为下一个时刻的输入,本来就有时序信息在内(做在模型内部)

但是attention是在输入中加入时序信息的(将输入的位置i, i = 1,2,3…加入到输入里面)

在这里插入图片描述

论文中一个 512 维的向量来表示一个数字,该数字体现了位置信息 1 2 3 4 5 6 7 8…。

周期不一样的 sin 和 cos 函数计算 --> 任何一个值可以用一个长为 512 的向量来表示。

这个长为 512 、记录了时序信息的一个positional encoding,与嵌入层相加 ,便可把时序信息加进数据。

4、Why Attention?

在这里插入图片描述

论文对比了三种数据。其中n是num_step(序列长度),d是向量的长度(维度)

self-attention: O(n^2 * d),Q矩阵、K矩阵都是n * d的,所以相乘复杂度是n^2 * d的。

所谓“Senquential Operations”其实就是“做下一步操作之前需要等多长时间”,也就是**“并行度”**的体现。

"M-P-L"表示信息从一个点到另一个点最多需要走多少步

“Restricted”表示做attention操作的时候只和邻近的r个邻居做。

5、实验

数据集

WMT 2014 数据集 。使用byte-pair encoding, BPE,可以提取词根,可以处理一个词的多种变化 -ing -ed(如果为每一个不同形式的词都构建一个token,那样字典会过大,而且不容易发现词之间的关系)。37000 tokens(英语德语共享字典 --> encoder 和 decoder 用一个东西、模型更简单、Embedding 共享权重,不再分开做两个字典)。

优化器

使用Adam优化器。

学习率不固定,而是通过公式计算。(warm-up, 从一个小的值慢慢爬到一个高的值,到一定值之后,根据 step_num ^ 0.5衰减)。

在这里插入图片描述

正则项

Residual Dropout——在经过多头注意力和MLP,进入残差连接之前和进入 layernorm 之前,使用 dropout,drop = 0.1,即

10%的元素 重置为 0,剩下的值 * 1.1

在输入做Embedding + Positional Encoding的时候,也用了Dropout.

【在模型中带权重的层,输出都使用了 dropout ,使用了大量的dropout层】。

另外,模型使用了Label Smoothing——

我们在用softmax作预测的时候,希望正确的分类softmax逼近1,剩下错误的逼近0。但是softmax很难趋近为1,使得训练比较难。所以往往考虑将标准降低一点,比如0.9。但这里降得比较狠,直接将标准降到了0.1,即正确的词,softmax的输出0.1(置信度达到0.1) 即可,剩下的值是 0.9 / 字典大小.

这样做会使得困惑度变大,但是可以有效提升BLEU分数

6、标准参数

在这里插入图片描述

0.1是Label Smoothing, PPL是困惑度。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值