Attention机制学习

42 篇文章 2 订阅
42 篇文章 1 订阅

Attention机制

回顾RNN结构

  • 讲attention之前先回顾一下RNN的各种结构

N to N

完全图解RNN、RNN变体、Seq2Seq、Attention机制

  • 如:语音处理,时间序列处理

N to 1

完全图解RNN、RNN变体、Seq2Seq、Attention机制

  • 如:情感分析,输入一段视频判断类型

1 to N

完全图解RNN、RNN变体、Seq2Seq、Attention机制

完全图解RNN、RNN变体、Seq2Seq、Attention机制

  • 如:从图像生成文字,从类别生成语音或音乐

N to M

  • 这种就够又叫encoder-decoder模型,或Seq2Seq模型

完全图解RNN、RNN变体、Seq2Seq、Attention机制

完全图解RNN、RNN变体、Seq2Seq、Attention机制

  • 如:机器翻译,文本摘要,阅读理解,语音识别…

回归正题Attention

  • encoder-decoder结构中,显然当要处理的信息长度很长的时候,一个c存储不了那么多信息,导致处理精度下降

  • 所以我们打算计算很多个c

    在这里插入图片描述

    • 这样翻译不同的y的时候,对于x就有不同的权重

    完全图解RNN、RNN变体、Seq2Seq、Attention机制

    • 计算结构如下

    • img

    • 每一个c会去选取和当前所要输出的y最合适的上下文信息。

      • 具体的,我们用 a i j a_{ij} aij来衡量encoder中第j阶段的 h j h_j hj(hidden state),和当前decoder中第i阶段的相关性

      • 以机器翻译为例

        完全图解RNN、RNN变体、Seq2Seq、Attention机制

        • 上图标有红色的地方就是和decoder当前阶段最相关的地方,对应的值较大;其他的地方对应的值较小。这里就是attention的精髓所在了——每个decoder的状态对于每个encoder的状态分配注意力(当然, ∑ j = 1 L x a i j = 1 \sum_{j=1}^{L_x}a_{ij}=1 j=1Lxaij=1

          h t = R N N e n c ( x t , h t − 1 ) h t ′ = R N N d e c ( c t , h t − 1 ′ ) c i = ∑ j = 1 L x a i j h j ( T x 是 x 总 长 ) h_t=RNN_{enc}(x_t,h_{t-1})\\ h'_t=RNN_{dec}{(c_t,h'_{t-1})}\\ c_i=\sum_{j=1}^{L_x} a_{ij}h_j\qquad (T_x是x总长) ht=RNNenc(xt,ht1)ht=RNNdec(ct,ht1)ci=j=1Lxaijhj(Txx)

    • 接下来就是求 a i j a_{ij} aij

      • b i j = s c o r e ( h i − 1 ′ , h j ) ( 我 们 的 h ′ 是 从 0 开 始 的 ) a i j = e b i j ∑ e b i k b_{ij}=score(h'_{i-1},h_j)\qquad (我们的h'是从0开始的)\\ a_{ij}=\frac{e^{b_{ij}}}{\sum e^{b_{ik}}} bij=score(hi1,hj)(h0)aij=ebikebij
    • 那么我们的score是怎么计算的呢

      • img
      • 最简单的方方法就是直接计算点乘,点积类似计算相似度。
      • 完全图解RNN、RNN变体、Seq2Seq、Attention机制
  • 而,attention极值就是来解决这个问题的,定义如下

    • 给定一组向量集合value,以及一个向量集合query,attention机制就是根据query计算value的加权求和机制

attention的本质思想

img

Query(Q),Key(K),Value(V),Source是有<Key,Value>的数据对构成

  • 1、给定Target中的某个元素Query,通过计算Query和各个Key的相似性
  • 2、得到每个Key对应Value的权重系数
  • 3、然后对Value进行加权求和,即得到了最终的Attention数值

本质上Attention机制是对Source中元素的Value值进行加权求和,而Query和Key用来计算对应Value的权重系数,可写为如下公式
A t t e n t i o n ( Q , < K , V > ) = ∑ i = 1 L x S i m i l a r i t y ( Q , K i ) ∗ V i = s o f t m a x ( s i m ( Q , K ) ) V Attention(Q,<K,V>)=\sum_{i=1}^{L_x}Similarity(Q,K_i)*V_i\\ =softmax(sim(Q,K))V Attention(Q,<K,V>)=i=1LxSimilarity(Q,Ki)Vi=softmax(sim(Q,K))V

具体计算可分为两个过程:

  • 1、根据Query和Key计算权重系数(可分为如下两个阶段)
    • 一(阶段一)、根据Query和Key计算两者的相似性
    • 二(阶段二)、对第一阶段的原始分值进行归一化处理
  • 2、根据权重系数对Value进行加权求和

img

attention 的效果

img

  • 上图为英语-德语翻译的注意力概率分布(可视化地展示了在英语-德语翻译系统中加入Attention机制后,Source和Target两个句子每个单词对应的注意力分配概率分布。)

主要也就一块代码

class AttDecoder_RNN(nn.Module):
    def __init__(self, word_num, hidden_dim, dropp=config.drop_p, max_length=config.max_length):
        super(AttDecoder_RNN, self).__init__()
        self.word_num = word_num
        self.hidden_dim = hidden_dim
        self.embed = nn.Embedding(word_num, hidden_dim)
        self.gru = nn.GRU(hidden_dim, hidden_dim)
        self.dropout = nn.Dropout(dropp)

        self.attn = nn.Linear(2 * hidden_dim, max_length)
        self.attn_C = nn.Linear(2 * hidden_dim, self.hidden_dim)

        self.out = nn.Linear(self.hidden_dim, self.word_num)

    def forward(self, encoder_state, input, hidden=None):
        batch_size = input.size(0)
        if hidden is None:
            hidden = t.rand(1, batch_size, self.hidden_dim)

        emb = self.embed(input)
        emb = self.dropout(emb)

        att_w = F.softmax(self.attn(t.cat((emb, hidden[0]), 1)), dim=1)  #先用上一层hidden与input拼接,然后通过网络映射到max_length得到权重a,得到权重再softmax
        att_c = t.bmm(att_w.unsqueeze(0).permute(1, 0, 2), encoder_state.permute(1, 0, 2))  #用权重a与encoder的hidden相乘得到c,(permute是pytorch调整维度)

        output = t.cat((emb, att_c.permute(1, 0, 2)[0]), 1)    #再把input与c拼接
        output = self.attn_C(output).unsqueeze(0)   #将长度映射还原,拼接之后hidden长度会加倍

        output, hidden = self.gru(output, hidden)
        output = F.log_softmax(self.out(output[0]), dim=1)
        return output, hidden, att_w

self-attention模块——扔掉RNN

  • 一般任务的Encoder-Decoder框架中,输入Source和输出Target内容是不一样的,比如一边英文一边中文,Attention机制发生在Target的元素Query和Source中的所有元素之间

  • 而self-attention机制,Attention机制发生在Source内部元素之间或者Target内部元素之间,也可以理解为Target=Source这种特殊情况下的注意力计算机制(就是计算对象发生了变化,Q=K=V)

    • 具体的,比如翻译“I arrived at the bank after crossing the river”,要翻译“bank”是银行还是河岸,需要结合river这个词,所以我们需要在翻译“bank”的时候,river的Attention Score就有较高的值;
    • 而普通的RNN在两词相距较远的时候效果较差,而且顺序处理效果较低
    • 步骤效果如下,思路很简单,就这样就把RNN扔掉了
      在这里插入图片描述
  • transformer模型中的相似性使用的是缩放点积模型(scaled dot-product attention):
    s i m ( K i , Q ) = K i T Q d k sim(K_i,Q)=\frac{K_i^TQ}{\sqrt{d_k}} sim(Ki,Q)=dk KiTQ

  • 流程图如下

img

  • 不过没有了RNN,encoder过程就没有了hidden state,那怎么办?

    • 我们对每一个word做embedding,然后用embedding代替hidden state来self-attention
    • 所以Q矩阵装的都是word embedding(K,V也一样)
    • v i v_i vi是第i个word的embedding,对 v i v_i vi的attention为下面第一个img
  • 注意,在decoder中的self-attention的流程和encoder中的差不多,不过encoder中的word是一次性全部输入进去的,decoder中的word是从一遍生成的(如从左到右),那么对于 v i v_i vi是没有机会和 v j ( j ≥ i ) v_j(j\geq i) vj(ji)做attention的,这个时候我们需要使 s c o r e ( v i , v j ) = − ∞ score(v_i,v_j)=-∞ score(vi,vj)=,有 a i j = s o f t m a x ( s c o r e ( v i , v j ) ) = s o f t m a x ( − ∞ ) = 0 a_{ij}=softmax(score(v_i,v_j))=softmax(-∞)=0 aij=softmax(score(vi,vj))=softmax()=0

    • 这个其实相当于一个masked的操作,就是transformer模型中的masked muti-head attention中在decoder中的操作

优点

  • 优点
    • 引入Self Attention后会更容易捕获句子中长距离的相互依赖的特征
    • Self Attention对于增加计算的并行性也有直接帮助作用

Multi-head Attention模块

  • muti-head attention就是多个self-attention结构的结合,每个head学到不同表示空间中的特征。

    • 将模型分为多个头,形成多个子空间,可以让模型关注不同方面的信息
  • 如下图,两个head学到的特征可能会不同img

  • 其实就是把self-attention进行stacking,把每个word的embedding拆分成几块分别作self-attention最后拼接起来

img

  • 步骤大致如下

    • img

    • img

Q i = Q W i Q , K i = K W i K , V i = V W i V i = 1 , . . . , h s h e a d i = A t t e n t i o n ( Q i , K i , V i ) i = 1 , . . . , h s M u l t i H e a d ( Q , K , V ) = C o n c a t ( h e a d 1 , . . . , h e a d h s ) W O ( h i d d e n d i m = h s ∗ h e a d d i m ) Q_i=QW_i^Q,K_i=KW_i^K,V_i=VW_i^V\quad i=1,...,h_s\\ head_i=Attention(Q_i,K_i,V_i)\quad i=1,...,h_s\\ MultiHead(Q,K,V)=Concat(head_1,...,head_{h_s})W^O\\ (hidden_{dim}=h_s*head_{dim}) Qi=QWiQ,Ki=KWiK,Vi=VWiVi=1,...,hsheadi=Attention(Qi,Ki,Vi)i=1,...,hsMultiHead(Q,K,V)=Concat(head1,...,headhs)WOhiddendim=hsheaddim

  • 上面那几个 W W W映射矩阵可以用 h i d d e n d i m ∗ h i d d e n d i m hidden_{dim}*hidden_{dim} hiddendimhiddendim的linear layer实现

Layer Normalization与残差连接

在这里插入图片描述可以发现在“Multi-Head Attention”旁还有一条直连的边,这里用的即是ResNet中的残差(F(X)+X)。

  • 原文写的公式是:LayerNorm(X + SubLayer(X)),然后attention的部分就是我们现在要学习的部分SubLayer(X)(即F(x))

LN放的位置探究

就是这么简单的改变,可以大大提升模型的调参难度与学习效率。

  • 在Post-LN中,transfomer对参数的变化十分敏感,需要仔细的调参以及使用warm-up的学习策略,非常的慢。主要的问题再LN的位置,导致layer的梯度范数级增长

简要代码如下

if self.pre_lnorm:
      pre = self.self_attn_norm(src)
      src = src + self.self_attn(pre, pre, pre, src_mask) # residual connection

      pre = self.pff_norm(src)
      src = src + self.pff(pre) # residual connection
else:
      src = self.self_attn_norm(src + self.self_attn(src, src, src, src_mask)) # residual connection + layerNorm
      src = self.pff_norm(src + self.pff(src)) # residual connection + layerNorm

简要实现如下

class MultiHeadAttentionLayer(nn.Module):
    def __init__(self, hid_dim, n_heads, dropout, device):
        super().__init__()
        
        assert hid_dim % n_heads == 0
        
        self.hid_dim = hid_dim   # in paper, 512
        self.n_heads = n_heads   # in paper, 8
        self.head_dim = hid_dim // n_heads  # in paper, 512 // 8 = 64
         
        self.fc_q = nn.Linear(hid_dim, hid_dim)
        self.fc_k = nn.Linear(hid_dim, hid_dim)
        self.fc_v = nn.Linear(hid_dim, hid_dim)
        
        self.fc_o = nn.Linear(hid_dim, hid_dim)
        
        self.dropout = nn.Dropout(dropout)
        
        self.scale = torch.sqrt(torch.FloatTensor([self.head_dim])).to(device)  # sqrt(64)
        
    def forward(self, query, key, value, mask = None):
        
        batch_size = query.shape[0]
        
        #query = [batch size, query len, hid dim]
        #key = [batch size, key len, hid dim]
        #value = [batch size, value len, hid dim]
                
        Q = self.fc_q(query)
        K = self.fc_k(key)
        V = self.fc_v(value)
        
        #Q = [batch size, query len, hid dim]
        #K = [batch size, key len, hid dim]
        #V = [batch size, value len, hid dim]
                
        Q = Q.view(batch_size, -1, self.n_heads, self.head_dim).permute(0, 2, 1, 3)
        K = K.view(batch_size, -1, self.n_heads, self.head_dim).permute(0, 2, 1, 3)
        V = V.view(batch_size, -1, self.n_heads, self.head_dim).permute(0, 2, 1, 3)
        
        #Q = [batch size, n heads, query len, head dim]
        #K = [batch size, n heads, key len, head dim]
        #V = [batch size, n heads, value len, head dim]
                
        energy = torch.matmul(Q, K.permute(0, 1, 3, 2)) / self.scale
        
        #energy = [batch size, n heads, query len, key len]
        
        if mask is not None:
            energy = energy.masked_fill(mask == 0, -1e10)
        
        attention = torch.softmax(energy, dim = -1)
                
        #attention = [batch size, n heads, query len, key len]
                
        x = torch.matmul(self.dropout(attention), V)  #x = [batch size, n heads, query len, head dim]
        
        # 将x还原成linear layer可以process的size
        x = x.permute(0, 2, 1, 3).contiguous() 
        # contiguous 返回一个内存连续的有相同数据的tensor,如果原tensor内存连续,则返回原tensor. 一般与transpose,permute, view搭配使用
        # transpose、permute等维度变换操作后,tensor在内存中不再是连续存储的,而view操作要求tensor的内存连续存储,所以需要contiguous来返回一个contiguous copy
        
        
        x = x.view(batch_size, -1, self.hid_dim) #x = [batch size, query len, n heads, head dim]
        
        x = self.fc_o(x) #x = [batch size, query len, hid dim]
                
        return x, attention
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值