0
点赞
收藏
分享

微信扫一扫

注意力机制

夹胡碰 2022-02-13 阅读 93

注意力机制概述

注意力评分

1、加性注意力Additive Attention

2、缩放点积注意力Scaled Dot-Product Attention

在seq2seq中应用Attention(Bahdanau注意力)

在seq2seq中使用attention的动机:

加了attention后的区别

 具体的实现过程:

Bahdanau注意力的代码实现


注意力机制概述

心理学定义:在复杂的情况下关注值得注意的点

“是否包含自主性提示”将注意力机制与全连接层或汇聚层区别开来:

卷积、全连接、pooling都是只考虑非意志线索;

注意力机制则考虑意志线索:意志线索(自主性提示)称为查询(query)。 给定任何查询,注意力机制通过注意力汇聚(attention pooling) 将选择引导至感官输入(sensory inputs,例如中间特征表示)。 在注意力机制中,这些感官输入被称为(value)。 更通俗的解释,每个值都与一个(key)配对, 这可以想象为感官输入的非自主提示。 如 图10.1.3所示,我们可以设计注意力汇聚, 以便给定的查询(自主性提示)可以与键(非自主性提示)进行匹配, 这将引导得出最匹配的值(感官输入)。

 是注意力权重 

意志线索query就是f(x),式子中的x就是非意志线索key简单来说,query就是对key做了一个加权算术,其中的权重就是query对key的偏向性选择。


注意力评分

注意力分数是query和key的相似度(举个栗子,去公司求职的时候,猜测自己的薪水,应该是看和自己专业相仿,工作内容和时间差不多的人的薪水,这些人的注意力分数就会高),没有Normalized的alpha就是注意力分数a。

查询q和键ki的注意力权重(标量) 是通过注意力评分函数a 将两个向量映射成标量, 再经过softmax运算得到的。。选择不同的注意力评分函数a会导致不同的注意力汇聚操作。

1、加性注意力Additive Attention

给定查询q∈Rq和 键k∈Rk, 加性注意力(additive attention)的评分函数为

 将query和key连结起来后输入到一个多层感知机(MLP)中, 感知机包含一个隐藏层,其隐藏单元数是一个超参数h。 通过使用tanh作为激活函数,并且禁用偏置项。query和key可以是任意长度,通过矩阵乘法,最后得到的a是一个值,因为(1*h)*(h*1).

#@save
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_hidden)
        # key的形状:(batch_size,1,“键-值”对的个数,num_hiddens)
        # 使用广播方式进行求和
        features = queries.unsqueeze(2) + keys.unsqueeze(1)
        features = torch.tanh(features)
        # self.w_v仅有一个输出,因此从形状中移除最后那个维度。
        # 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)

2、缩放点积注意力Scaled Dot-Product Attention

当n个query和m个Key的长度都是d时

#@save
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)

在seq2seq中应用Attention(Bahdanau注意力)

注意在这个模型里什么是value\key\query

key——编码器中每一个词对应RNN的输出。

query——解码器对上一个词的预测输出(用来匹配相关的key)。

value——上下文的语义?

在seq2seq中使用attention的动机:

机器翻译中,每个生成的词可能相关于源句子中不同的词。但是seq2seq不能直接对此建模:decoder的输入是最后一个H, 虽然包含了前面的信息,但是我们要还原出位置信息。

加了attention后的区别

原来的输入永远都是encoder的RNN最后一个隐藏状态作为上下文context和embedding一起传入解码器的RNN。

 在原来的基础上加上attention(用最后一个时刻的状态作为输入不好,应该根据翻译的词看前面几个时刻的隐藏状态)。相当于对前面的隐藏状态作了一个加权平均,取最相关的state.

 具体的实现过程:

编译器encoder把每个词的输出作为Key和value(两者是一样的)放进attention里面,然后当decoder预测出hello的时候,把输出的预测作为query放入attention寻找hello周围的词(匹配match的Key)。

“用编译器创建索引,用解码器的预测来定位关注点”

最后的输入是attention的输出和embedding


Bahdanau注意力的代码实现

加了attention_weight函数

class Seq2SeqAttentionDecoder(AttentionDecoder):
    def __init__(self, vocab_size, embed_size, num_hiddens, num_layers,
                 dropout=0, **kwargs):
        super(Seq2SeqAttentionDecoder, self).__init__(**kwargs)
        self.attention = d2l.AdditiveAttention(
            num_hiddens, num_hiddens, num_hiddens, dropout)
        self.embedding = nn.Embedding(vocab_size, embed_size)
        self.rnn = nn.GRU(
            embed_size + num_hiddens, num_hiddens, num_layers,
            dropout=dropout)
        self.dense = nn.Linear(num_hiddens, vocab_size)

    def init_state(self, enc_outputs, enc_valid_lens, *args):
        # outputs的形状为(batch_size,num_steps,num_hiddens).
        # hidden_state的形状为(num_layers,batch_size,num_hiddens)
        outputs, hidden_state = enc_outputs
        return (outputs.permute(1, 0, 2), hidden_state, enc_valid_lens)

    def forward(self, X, state):
        # enc_outputs的形状为(batch_size,num_steps,num_hiddens).
        # hidden_state的形状为(num_layers,batch_size,
        # num_hiddens)
        enc_outputs, hidden_state, enc_valid_lens = state
        # 输出X的形状为(num_steps,batch_size,embed_size)
        X = self.embedding(X).permute(1, 0, 2)
        outputs, self._attention_weights = [], []
        for x in X:
            # query的形状为(batch_size,1,num_hiddens)
            query = torch.unsqueeze(hidden_state[-1], dim=1)
            # context的形状为(batch_size,1,num_hiddens)
            context = self.attention(
                query, enc_outputs, enc_outputs, enc_valid_lens)
            # 在特征维度上连结
            x = torch.cat((context, torch.unsqueeze(x, dim=1)), dim=-1)
            # 将x变形为(1,batch_size,embed_size+num_hiddens)
            out, hidden_state = self.rnn(x.permute(1, 0, 2), hidden_state)
            outputs.append(out)
            self._attention_weights.append(self.attention.attention_weights)
        # 全连接层变换后,outputs的形状为
        # (num_steps,batch_size,vocab_size)
        outputs = self.dense(torch.cat(outputs, dim=0))
        return outputs.permute(1, 0, 2), [enc_outputs, hidden_state,
                                          enc_valid_lens]

    @property
    def attention_weights(self):
        return self._attention_weights
举报

相关推荐

0 条评论