注意力机制Attention Mechanism及论文

注意力机制Attention Mechanism


注意力机制源于对人类视觉的研究。在认知科学中,由于信息处理的瓶颈,人类会选择性地关注所有信息的一部分,同时忽略其他可见的信息,上述机制通常被称为注意力机制。
例如在NLP中,当要捕捉一句话中词与词的关系时,如:

在这里插入图片描述
当看到eating这个词时,我们更希望看到食物apple,而对颜色green并不在意,此时我们就可以使用注意力机制来衡量词之间的相关性。相比于序列模型,它能够通过矩阵运算实现并行化,提高效率。

Q,K,V

注意力机制中有三个要素:Q(query),K(key),V(value),它们均由张量表示,在NLP中,Q可以理解成要输入的某个词(即要研究那个词与其他词的相关性),K可以理解为所有的词或者规定的词(即要与Q计算相关性的词),V可以理解为K对应的值(最后需要再和Q与K出来的结果做运算)。K和V可以是一个词,可以都有相同的向量表示,但意义不一样,比如上图中的apple,它本身的属性可以理解成K,但是它对最后的输出的属性可以理解成V(虽然它们是一样的)。大致如下图:
在这里插入图片描述
图选自沐神的动手学深度学习,应该放在下面,但要先展示一下注意力机制的流程就贴这了,可以先不看注意力评分函数。
虽然注意力机制是这几年提出的,但早期就有这种思想,如经典的Nadaraya-Watson核回归
f ( x ) = ∑ i = 1 n K ( x − x i ) ∑ j = 1 n K ( x − x j ) y i f(x)=\sum_{i=1}^n\frac{K(x-x_i)}{\sum_{j=1}^nK(x-x_j)}y_i f(x)=i=1nj=1nK(xxj)K(xxi)yi
这是一种回归方法,基本思想就是使用已知样本的加权平均作为新样本的预测,其中n为样本数,K为核函数(可以选用高斯核等),用来度量查询x与键xi的相关性,函数值越大说明查询与键相关性越大,也为对应的值yi赋予了更大的权重。

注意力分数

注意力分数实际就是Q与K相关性的大小,在Nadaraya-Watson核回归可以类比为yi的权重。一般化中,假定query为长度为q的向量,ki为长度为k的向量,vi为长度为v的向量,则:
f ( q u e r y , ( k 1 , v 1 ) , . . . , ( k m , v m ) ) = ∑ i = 1 m a ( q u e r y , k i ) v i ∈ R v f(query,(k_1,v_1),...,(k_m,v_m))=\sum_{i=1}^ma(query,k_i)v_i\in R^v f(query,(k1,v1),...,(km,vm))=i=1ma(query,ki)viRv
这里的a(query,ki)即为注意力分数,a表示计算注意力分数的方式,结果还需要softmax归一化。
方式1:additive attention
若query与ki的长度不一样
a ( q u e r y , k i ) = v T t a n h ( W k k i + W q q u e r y ) a(query,k_i)=v^{T}tanh(W_kk_i+W_qquery) a(query,ki)=vTtanh(Wkki+Wqquery)
其中 W k , W q W_k,W_q Wk,Wq分别为hxk,hxq形状的矩阵,v为长度为h的向量,这里算出来的是一个数。
多维并行见代码,沐神在将这个代码时没有很清楚(也可能是个人理解不够),经过一段时间琢磨,一下为一点思考(可以照着上面公式看):

class AdditiveAttention(nn.Module):
    """加性注意力"""
    def __init__(self, key_size, query_size, num_hiddens, dropout, **kwargs):
        super(AdditiveAttention, self).__init__(**kwargs)
        self.W_k = nn.Linear(key_size, num_hiddens, bias=False)
        self.W_q = nn.Linear(query_size, num_hiddens, bias=False)
        self.w_v = nn.Linear(num_hiddens, 1, bias=False)
        self.dropout = nn.Dropout(dropout)

    def forward(self, queries, keys, values, valid_lens):
        queries, keys = self.W_q(queries), self.W_k(keys)
        # 在维度扩展后,
        # queries的形状:(batch_size,查询的个数,1,num_hiddens)
        # key的形状:(batch_size,1,“键-值”对的个数,num_hiddens)
        # 使用广播方式进行求和, 求和之后的形状是:(batch_size,查询的个数,键-值对的个数,num_hiddens)
        features = queries.unsqueeze(2) + keys.unsqueeze(1) 
        features = torch.tanh(features)
        # self.w_v仅有一个输出,因此从形状中移除最后那个维度(使num_hiddens)为1。
        # scores的形状:(batch_size,查询的个数,“键-值”对的个数)
        scores = self.w_v(features).squeeze(-1)
        self.attention_weights = masked_softmax(scores, valid_lens)
        # values的形状:(batch_size,“键-值”对的个数,值的维度)
        return torch.bmm(self.dropout(self.attention_weights), values)

masked_softmax是为了去掉冗余信息,加了mask的softmax函数,可以先不管,若想了解可以去看https://zh-v2.d2l.ai/chapter_attention-mechanisms/attention-scoring-functions.html,代码主要难顶的是forward里面的广播机制(看懂之后我直呼NB)。
开始时查询queries的形状是(batch_szie,查询的个数,query_size),keys的形状是(batch_size,“键-值”对的个数,key_size),首先通过线性层使它们的query_size与key_size都变成了num_hiddens。然后,也是最重要的一点,因为现在queries与keys的形状还不能相加,使用了unsqueeze扩展了维度,queries的形状变成(batch_size,查询的个数,1,num_hiddens),keys的形状变成(batch_size,1,“键-值”对的个数,num_hiddens),都成了四维张量,先不看batch_size(或者假设batch_size为1),这里的广播就是用每一个查询(形状为1xnum_hiddens)去加形状为(“键-值”对的个数,num_hiddens)的矩阵,得到每一个形状为(“键-值”对的个数,num_hiddens)的矩阵,然后加入查询个数这个维度,代码中features的形状就变成(batch_size,查询的个数,键-值对的个数,num_hiddens)。最后再由一个线性层将features形状变成(batch_size,查询的个数,键-值对的个数,1),去掉最后的维度,最后形状就是(batch_size,查询的个数,键-值对的个数),其中每个元素意义就是查询的词与key的注意力分数。
方式2:scaled dot-product attention
若query与ki均为d维向量
a ( q u e r y , k i ) = q u e r y × k i T d a(query,k_i)=\frac{query\times{k_i^{T}}}{\sqrt{d}} a(query,ki)=d query×kiT
这算出来的也是一个数。
若query为二维张量Q(表示查询多个词),此时K,V均为二维张量,假定 Q ∈ R n × d , K ∈ R m × d , V ∈ R m × v Q\in R^{n\times{d}},K\in R^{m\times{d}},V\in R^{m\times{v}} QRn×d,KRm×d,VRm×v,可以实现并行计算:
f = s o f t m a x ( Q K T d ) V ∈ R n × v f=softmax(\frac{QK^{T}}{\sqrt{d}})V\in R^{n\times{v}} f=softmax(d QKT)VRn×v
这里softmax里面出来的形状是(n,m),n表示查询的个数,m表示key-value键值对的个数,最后得到输出的长度由V部分决定,输出长度为nxv的矩阵(v是由实际任务定义,如在机器翻译中,v就是可供选择的翻译出的词的数量)。
代码:

class DotProductAttention(nn.Module):
    """缩放点积注意力"""
    def __init__(self, dropout, **kwargs):
        super(DotProductAttention, self).__init__(**kwargs)
        self.dropout = nn.Dropout(dropout)

    # queries的形状:(batch_size,查询的个数,d)
    # keys的形状:(batch_size,“键-值”对的个数,d)
    # values的形状:(batch_size,“键-值”对的个数,值的维度)
    # valid_lens的形状:(batch_size,)或者(batch_size,查询的个数)
    def forward(self, queries, keys, values, valid_lens=None):
        d = queries.shape[-1]
        # 设置transpose_b=True为了交换keys的最后两个维度
        scores = torch.bmm(queries, keys.transpose(1,2)) / math.sqrt(d)
        self.attention_weights = masked_softmax(scores, valid_lens)
        return torch.bmm(self.dropout(self.attention_weights), values)

由于queries与keys的最后一个维度相同,就直接转置相乘得到形状为(batch_size,查询的个数,“键-值”对的个数)即(batch_size,n,m)的张量,softmax出来的结果也是这个形状,这个就是第二种方式的注意力分数,与mxv的张量V相乘得到nxv的张量,这个是最后的输出了。
scaled dot-product attention的自注意力机制版本:
在这里插入图片描述

seq2seq中注意力机制的应用

seq2seq原论文:Sequence to Sequence Learning with Neural Networks
加入attention的seq2seq原论文:NEURAL MACHINE TRANSLATION BY JOINTLY LEARNING TO ALIGN AND TRANSLATE
在没有引入注意力机制的seq2seq模型中,解码器接受的输入是编码器的输出,即编码器最后时刻的隐藏状态,但这在机器翻译任务中不太可取。
在这里插入图片描述
因为每个生成的词可能源于句子中不同的词,比如上图,“她”与句子中的“She”关系最大,“苹果”与“apple”关系最大,但原始的seq2seq的Decoder只接受Encoder的最后状态,即句子的压缩信息,所以可能会使生成的词捕捉不到关键的针对性的信息,在翻译长句子时效果很差,就需要加入注意力机制。
在这里插入图片描述
加入注意力就是使解码器在任何时刻的输出都考虑全部的key,每个key会有一个注意力分数,根据分数的大小分配权重,下图出自吴恩达老师https://www.bilibili.com/video/BV1FT4y1E74V?p=180&spm_id_from=333.1007.top_right_bar_window_history.content.click
在这里插入图片描述

上图下方是Encoder,上方是Decoder,下方使用的是双向的RNN得到每一个句子单词的隐状态(特征向量)。在训练时是实现知道输出的y,所以可以得到每个翻译输出的query与keys的注意力分数,使用注意力机制得到Decoder每个时刻的输入C,由于Decoder也是RNN结构,输出时考虑上一时刻的隐状态与上一时刻的输出与Encoder由注意力机制得到的C。


参考资料:
李沐—动手学深度学习v2
https://lilianweng.github.io/lil-log/2018/06/24/attention-attention.html?spm=a2c4e.10696291.0.0.4c5619a4PZdBIF


深度学习笔记 2022/01/04

  • 2
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值