一、背景
1、机器翻译中,每个生成的词可能相关于源句子中不同的词,但是之前用的是最后一个RNN层出来的context。
2、加入注意力
(1)假设输入序列中有𝑇个词元, 解码时间步𝑡′的上下文变量是注意力集中的输出
时间步𝑡′−1时的解码器隐状态𝑠_𝑡′−1是查询, 编码器隐状态ℎ_𝑡既是键,也是值, 注意力权重𝛼是使用加性注意力打分函数计算的
(2)Bahdanau注意力的架构
a、要预测下一个词的时候,将当前预测出的词作为query,编码器各个状态作为(key,value),进行attention,来找到对预测下一个词有用的原文
b、编码器对每次词的输出作为key和value(一样的)
c、解码器RNN对上一词的输出是query
d、注意力的输出和下一词的词嵌入合并进入
二、定义注意力解码器
1、对于Seqseq,只需要重新定义解码器
#@save class AttentionDecoder(d2l.Decoder): """带有注意力机制解码器的基本接口""" def __init__(self, **kwargs): super(AttentionDecoder, self).__init__(**kwargs) @property def attention_weights(self): raise NotImplementedError
2、初始化解码器的状态,需要下面的输入:
(1)编码器在所有时间步的最终层隐状态,将作为注意力的键和值;
(2)上一时间步的编码器全层隐状态,将作为初始化解码器的隐状态;
(3)编码器有效长度(排除在注意力池中填充词元)。
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
二、总结
1、在预测词元时,如果不是所有输入词元都是相关的,那么具有Bahdanau注意力的循环神经网络编码器-解码器会有选择地统计输入序列的不同部分。这是通过将上下文变量视为加性注意力池化的输出来实现的。
2、在循环神经网络编码器-解码器中,Bahdanau注意力将上一时间步的解码器隐状态视为查询,在所有时间步的编码器隐状态同时视为键和值。