当我们在10.7 节遇到机器翻译时,我们设计了一个基于两个 RNN 的序列到序列 (seq2seq) 学习的编码器-解码器架构 ( Sutskever et al. , 2014 )。具体来说,RNN 编码器将可变长度序列转换为固定形状的上下文变量。然后,RNN 解码器根据生成的标记和上下文变量逐个标记地生成输出(目标)序列标记。
回想一下我们在下面重印的图 10.7.2 (图 11.4.1)以及一些额外的细节。通常,在 RNN 中,有关源序列的所有相关信息都由编码器转换为某种内部固定维状态表示。正是这种状态被解码器用作生成翻译序列的完整和唯一的信息源。换句话说,seq2seq 机制将中间状态视为可能作为输入的任何字符串的充分统计。
虽然这对于短序列来说是相当合理的,但很明显这对于长序列来说是不可行的,比如一本书的章节,甚至只是一个很长的句子。毕竟,一段时间后,中间表示中将根本没有足够的“空间”来存储源序列中所有重要的内容。因此,解码器将无法翻译又长又复杂的句子。第一个遇到的人是 格雷夫斯 ( 2013 )当他们试图设计一个 RNN 来生成手写文本时。由于源文本具有任意长度,他们设计了一个可区分的注意力模型来将文本字符与更长的笔迹对齐,其中对齐仅在一个方向上移动。这反过来又利用了语音识别中的解码算法,例如隐马尔可夫模型 (Rabiner 和 Juang,1993 年)。
受到学习对齐的想法的启发, Bahdanau等人。( 2014 )提出了一种没有单向对齐限制的可区分注意力模型。在预测标记时,如果并非所有输入标记都相关,则模型仅对齐(或关注)输入序列中被认为与当前预测相关的部分。然后,这用于在生成下一个令牌之前更新当前状态。虽然在其描述中相当无伤大雅,但这种Bahdanau 注意力机制可以说已经成为过去十年深度学习中最有影响力的想法之一,并催生了 Transformers (Vaswani等人,2017 年)以及许多相关的新架构。
11.4.1。模型
我们遵循第 10.7 节的 seq2seq 架构引入的符号 ,特别是(10.7.3)。关键思想是,而不是保持状态,即上下文变量c将源句子总结为固定的,我们动态更新它,作为原始文本(编码器隐藏状态)的函数ht) 和已经生成的文本(解码器隐藏状态st′−1). 这产生 ct′, 在任何解码时间步后更新 t′. 假设输入序列的长度T. 在这种情况下,上下文变量是注意力池的输出:
我们用了st′−1作为查询,和 ht作为键和值。注意 ct′然后用于生成状态 st′并生成一个新令牌(参见 (10.7.3))。特别是注意力权重 α使用由 ( 11.3.7 )定义的附加注意评分函数按照 (11.3.3)计算。这种使用注意力的 RNN 编码器-解码器架构如图 11.4.2所示。请注意,后来对该模型进行了修改,例如在解码器中包含已经生成的标记作为进一步的上下文(即,注意力总和确实停止在T而是它继续进行t′−1). 例如,参见Chan等人。( 2015 )描述了这种应用于语音识别的策略。
11.4.2。用注意力定义解码器
要实现带有注意力的 RNN 编码器-解码器,我们只需要重新定义解码器(从注意力函数中省略生成的符号可以简化设计)。让我们通过定义一个意料之中的命名类来开始具有注意力的解码器的基本接口 AttentionDecoder
。
我们需要在Seq2SeqAttentionDecoder
类中实现 RNN 解码器。解码器的状态初始化为(i)编码器最后一层在所有时间步的隐藏状态,用作注意力的键和值;(ii) 编码器在最后一步的所有层的隐藏状态。这用于初始化解码器的隐藏状态;(iii) 编码器的有效长度,以排除注意力池中的填充标记。在每个解码时间步,解码器最后一层的隐藏状态,在前一个时间步获得,用作注意机制的查询。注意机制的输出和输入嵌入都被连接起来作为 RNN 解码器的输入。
class Seq2SeqAttentionDecoder(AttentionDecoder):
def __init__(self, vocab_size, embed_size<