动手深度学习PyTorch(十)Seq2Seq、Attention

1. 编码器—解码器(seq2seq)

在自然语言处理的很多应用中,输入和输出都可以是不定长序列。以机器翻译为例,输入可以是一段不定长的英语文本序列,输出可以是一段不定长的法语文本序列,例如

英语输入:“They”、“are”、“watching”、“.”

法语输出:“Ils”、“regardent”、“.”

当输入和输出都是不定长序列时,我们可以使用编码器—解码器(encoder-decoder)[1] 或者seq2seq模型 [2]。这两个模型本质上都用到了两个循环神经网络,分别叫做编码器和解码器。编码器用来分析输入序列,解码器用来生成输出序列。

下图描述了使用编码器—解码器将上述英语句子翻译成法语句子的一种方法。在训练数据集中,我们可以在每个句子后附上特殊符号“<eos>”(end of sequence)以表示序列的终止。编码器每个时间步的输入依次为英语句子中的单词、标点和特殊符号“<eos>”。图中使用了编码器在最终时间步的隐藏状态作为输入句子的表征或编码信息。解码器在各个时间步中使用输入句子的编码信息和上个时间步的输出以及隐藏状态作为输入。我们希望解码器在各个时间步能正确依次输出翻译后的法语单词、标点和特殊符号"<eos>"。需要注意的是,解码器在最初时间步的输入用到了一个表示序列开始的特殊符号"<bos>"(beginning of sequence)。
图10.8 使用编码器—解码器将句子由英语翻译成法语。编码器和解码器分别为循环神经网络

接下来,我们分别介绍编码器和解码器的定义。

编码器

编码器的作用是把一个不定长的输入序列变换成一个定长的背景变量 c \boldsymbol{c} c,并在该背景变量中编码输入序列信息。常用的编码器是循环神经网络。

让我们考虑批量大小为1的时序数据样本。假设输入序列是 x 1 , … , x T x_1,\ldots,x_T x1,,xT,例如 x i x_i xi是输入句子中的第 i i i个词。在时间步 t t t,循环神经网络将输入 x t x_t xt的特征向量 x t \boldsymbol{x}_t xt和上个时间步的隐藏状态 h t − 1 \boldsymbol{h}_{t-1} ht1变换为当前时间步的隐藏状态 h t \boldsymbol{h}_t ht。我们可以用函数 f f f表达循环神经网络隐藏层的变换:

h t = f ( x t , h t − 1 ) . \boldsymbol{h}_t = f(\boldsymbol{x}_t, \boldsymbol{h}_{t-1}). ht=f(xt,ht1).

接下来,编码器通过自定义函数 q q q将各个时间步的隐藏状态变换为背景变量

c = q ( h 1 , … , h T ) . \boldsymbol{c} = q(\boldsymbol{h}_1, \ldots, \boldsymbol{h}_T). c=q(h1,,hT).

例如,当选择 q ( h 1 , … , h T ) = h T q(\boldsymbol{h}_1, \ldots, \boldsymbol{h}_T) = \boldsymbol{h}_T q(h1,,hT)=hT时,背景变量是输入序列最终时间步的隐藏状态 h T \boldsymbol{h}_T hT

以上描述的编码器是一个单向的循环神经网络,每个时间步的隐藏状态只取决于该时间步及之前的输入子序列。我们也可以使用双向循环神经网络构造编码器。在这种情况下,编码器每个时间步的隐藏状态同时取决于该时间步之前和之后的子序列(包括当前时间步的输入),并编码了整个序列的信息。

解码器

刚刚已经介绍,编码器输出的背景变量 c \boldsymbol{c} c编码了整个输入序列 x 1 , … , x T x_1, \ldots, x_T x1,,xT的信息。给定训练样本中的输出序列 y 1 , y 2 , … , y T ′ y_1, y_2, \ldots, y_{T'} y1,y2,,yT,对每个时间步 t ′ t' t(符号与输入序列或编码器的时间步 t t t有区别),解码器输出 y t ′ y_{t'} yt的条件概率将基于之前的输出序列 y 1 , … , y t ′ − 1 y_1,\ldots,y_{t'-1} y1,,yt1和背景变量 c \boldsymbol{c} c,即 P ( y t ′ ∣ y 1 , … , y t ′ − 1 , c ) P(y_{t'} \mid y_1, \ldots, y_{t'-1}, \boldsymbol{c}) P(yty1,,yt1,c)

为此,我们可以使用另一个循环神经网络作为解码器。在输出序列的时间步 t ′ t^\prime t,解码器将上一时间步的输出 y t ′ − 1 y_{t^\prime-1} yt1以及背景变量 c \boldsymbol{c} c作为输入,并将它们与上一时间步的隐藏状态 s t ′ − 1 \boldsymbol{s}_{t^\prime-1} st1变换为当前时间步的隐藏状态 s t ′ \boldsymbol{s}_{t^\prime} st。因此,我们可以用函数 g g g表达解码器隐藏层的变换:

s t ′ = g ( y t ′ − 1 , c , s t ′ − 1 ) . \boldsymbol{s}_{t^\prime} = g(y_{t^\prime-1}, \boldsymbol{c}, \boldsymbol{s}_{t^\prime-1}). st=g(yt1,c,st1).

有了解码器的隐藏状态后,我们可以使用自定义的输出层和softmax运算来计算 P ( y t ′ ∣ y 1 , … , y t ′ − 1 , c ) P(y_{t^\prime} \mid y_1, \ldots, y_{t^\prime-1}, \boldsymbol{c}) P(yty1,,yt1,c),例如,基于当前时间步的解码器隐藏状态 s t ′ \boldsymbol{s}_{t^\prime} st、上一时间步的输出 y t ′ − 1 y_{t^\prime-1} yt1以及背景变量 c \boldsymbol{c} c来计算当前时间步输出 y t ′ y_{t^\prime} yt的概率分布。

训练模型

根据最大似然估计,我们可以最大化输出序列基于输入序列的条件概率

P ( y 1 , … , y T ′ ∣ x 1 , … , x T ) = ∏ t ′ = 1 T ′ P ( y t ′ ∣ y 1 , … , y t ′ − 1 , x 1 , … , x T ) = ∏ t ′ = 1 T ′ P ( y t ′ ∣ y 1 , … , y t ′ − 1 , c ) , \begin{aligned} P(y_1, \ldots, y_{T'} \mid x_1, \ldots, x_T) &= \prod_{t'=1}^{T'} P(y_{t'} \mid y_1, \ldots, y_{t'-1}, x_1, \ldots, x_T)\\ &= \prod_{t'=1}^{T'} P(y_{t'} \mid y_1, \ldots, y_{t'-1}, \boldsymbol{c}), \end{aligned} P(y1,,yTx1,,xT)=t=1TP(yty1,,yt1,x1,,xT)=t=1TP(yty1,,yt1,c),

并得到该输出序列的损失

− log ⁡ P ( y 1 , … , y T ′ ∣ x 1 , … , x T ) = − ∑ t ′ = 1 T ′ log ⁡ P ( y t ′ ∣ y 1 , … , y t ′ − 1 , c ) , -\log P(y_1, \ldots, y_{T'} \mid x_1, \ldots, x_T) = -\sum_{t'=1}^{T'} \log P(y_{t'} \mid y_1, \ldots, y_{t'-1}, \boldsymbol{c}), logP(y1,,yTx1,,xT)=t=1TlogP(yty1,,yt1,c),

在模型训练中,所有输出序列损失的均值通常作为需要最小化的损失函数。在上图所描述的模型预测中,我们需要将解码器在上一个时间步的输出作为当前时间步的输入。与此不同,在训练中我们也可以将标签序列(训练集的真实输出序列)在上一个时间步的标签作为解码器在当前时间步的输入。这叫作强制教学(teacher forcing)。

2. 注意力机制

在上一节(编码器—解码器(seq2seq))里,解码器在各个时间步依赖相同的背景变量来获取输入序列信息。当编码器为循环神经网络时,背景变量来自它最终时间步的隐藏状态。

现在,让我们再次思考那一节提到的翻译例子:输入为英语序列“They”“are”“watching”“.”,输出为法语序列“Ils”“regardent”“.”。不难想到,解码器在生成输出序列中的每一个词时可能只需利用输入序列某一部分的信息。例如,在输出序列的时间步1,解码器可以主要依赖“They”“are”的信息来生成“Ils”,在时间步2则主要使用来自“watching”的编码信息生成“regardent”,最后在时间步3则直接映射句号“.”。这看上去就像是在解码器的每一时间步对输入序列中不同时间步的表征或编码信息分配不同的注意力一样。这也是注意力机制的由来。

仍然以循环神经网络为例,注意力机制通过对编码器所有时间步的隐藏状态做加权平均来得到背景变量。解码器在每一时间步调整这些权重,即注意力权重,从而能够在不同时间步分别关注输入序列中的不同部分并编码进相应时间步的背景变量。本节我们将讨论注意力机制是怎么工作的。

在上一节(编码器—解码器(seq2seq))里我们区分了输入序列或编码器的索引 t t t与输出序列或解码器的索引 t ′ t' t。该节中,解码器在时间步 t ′ t' t的隐藏状态 s t ′ = g ( y t ′ − 1 , c , s t ′ − 1 ) \boldsymbol{s}_{t'} = g(\boldsymbol{y}_{t'-1}, \boldsymbol{c}, \boldsymbol{s}_{t'-1}) st=g(yt1,c,st1),其中 y t ′ − 1 \boldsymbol{y}_{t'-1} yt1是上一时间步 t ′ − 1 t'-1 t1的输出 y t ′ − 1 y_{t'-1} yt1的表征,且任一时间步 t ′ t' t使用相同的背景变量 c \boldsymbol{c} c。但在注意力机制中,解码器的每一时间步将使用可变的背景变量。记 c t ′ \boldsymbol{c}_{t'} ct是解码器在时间步 t ′ t' t的背景变量,那么解码器在该时间步的隐藏状态可以改写为

s t ′ = g ( y t ′ − 1 , c t ′ , s t ′ − 1 ) . \boldsymbol{s}_{t'} = g(\boldsymbol{y}_{t'-1}, \boldsymbol{c}_{t'}, \boldsymbol{s}_{t'-1}). st=g(yt1,ct,st1).

这里的关键是如何计算背景变量 c t ′ \boldsymbol{c}_{t'} ct和如何利用它来更新隐藏状态 s t ′ \boldsymbol{s}_{t'} st。下面将分别描述这两个关键点。

计算背景变量

我们先描述第一个关键点,即计算背景变量。下图描绘了注意力机制如何为解码器在时间步2计算背景变量。首先,函数 a a a根据解码器在时间步1的隐藏状态和编码器在各个时间步的隐藏状态计算softmax运算的输入。softmax运算输出概率分布并对编码器各个时间步的隐藏状态做加权平均,从而得到背景变量。
在这里插入图片描述

具体来说,令编码器在时间步 t t t的隐藏状态为 h t \boldsymbol{h}_t ht,且总时间步数为 T T T。那么解码器在时间步 t ′ t' t的背景变量为所有编码器隐藏状态的加权平均:

c t ′ = ∑ t = 1 T α t ′ t h t , \boldsymbol{c}_{t'} = \sum_{t=1}^T \alpha_{t' t} \boldsymbol{h}_t, ct=t=1Tαttht,

其中给定 t ′ t' t时,权重 α t ′ t \alpha_{t' t} αtt t = 1 , … , T t=1,\ldots,T t=1,,T的值是一个概率分布。为了得到概率分布,我们可以使用softmax运算:

α t ′ t = exp ⁡ ( e t ′ t ) ∑ k = 1 T exp ⁡ ( e t ′ k ) , t = 1 , … , T . \alpha_{t' t} = \frac{\exp(e_{t' t})}{ \sum_{k=1}^T \exp(e_{t' k}) },\quad t=1,\ldots,T. αtt=k=1Texp(etk)exp(ett),t=1,,T.

现在,我们需要定义如何计算上式中softmax运算的输入 e t ′ t e_{t' t} ett。由于 e t ′ t e_{t' t} ett同时取决于解码器的时间步 t ′ t' t和编码器的时间步 t t t,我们不妨以解码器在时间步 t ′ − 1 t'-1 t1的隐藏状态 s t ′ − 1 \boldsymbol{s}_{t' - 1} st1与编码器在时间步 t t t的隐藏状态 h t \boldsymbol{h}_t ht为输入,并通过函数 a a a计算 e t ′ t e_{t' t} ett

e t ′ t = a ( s t ′ − 1 , h t ) . e_{t' t} = a(\boldsymbol{s}_{t' - 1}, \boldsymbol{h}_t). ett=a(st1,ht).

这里函数 a a a有多种选择,如果两个输入向量长度相同,一个简单的选择是计算它们的内积 a ( s , h ) = s ⊤ h a(\boldsymbol{s}, \boldsymbol{h})=\boldsymbol{s}^\top \boldsymbol{h} a(s,h)=sh。而最早提出注意力机制的论文则将输入连结后通过含单隐藏层的多层感知机变换 [1]:

a ( s , h ) = v ⊤ tanh ⁡ ( W s s + W h h ) , a(\boldsymbol{s}, \boldsymbol{h}) = \boldsymbol{v}^\top \tanh(\boldsymbol{W}_s \boldsymbol{s} + \boldsymbol{W}_h \boldsymbol{h}), a(s,h)=vtanh(Wss+Whh),

其中 v \boldsymbol{v} v W s \boldsymbol{W}_s Ws W h \boldsymbol{W}_h Wh都是可以学习的模型参数。

矢量化计算

我们还可以对注意力机制采用更高效的矢量化计算。广义上,注意力机制的输入包括查询项以及一一对应的键项和值项,其中值项是需要加权平均的一组项。在加权平均中,值项的权重来自查询项以及与该值项对应的键项的计算。

在上面的例子中,查询项为解码器的隐藏状态,键项和值项均为编码器的隐藏状态。
让我们考虑一个常见的简单情形,即编码器和解码器的隐藏单元个数均为 h h h,且函数 a ( s , h ) = s ⊤ h a(\boldsymbol{s}, \boldsymbol{h})=\boldsymbol{s}^\top \boldsymbol{h} a(s,h)=sh。假设我们希望根据解码器单个隐藏状态 s t ′ − 1 ∈ R h \boldsymbol{s}_{t' - 1} \in \mathbb{R}^{h} st1Rh和编码器所有隐藏状态 h t ∈ R h , t = 1 , … , T \boldsymbol{h}_t \in \mathbb{R}^{h}, t = 1,\ldots,T htRh,t=1,,T来计算背景向量 c t ′ ∈ R h \boldsymbol{c}_{t'}\in \mathbb{R}^{h} ctRh
我们可以将查询项矩阵 Q ∈ R 1 × h \boldsymbol{Q} \in \mathbb{R}^{1 \times h} QR1×h设为 s t ′ − 1 ⊤ \boldsymbol{s}_{t' - 1}^\top st1,并令键项矩阵 K ∈ R T × h \boldsymbol{K} \in \mathbb{R}^{T \times h} KRT×h和值项矩阵 V ∈ R T × h \boldsymbol{V} \in \mathbb{R}^{T \times h} VRT×h相同且第 t t t行均为 h t ⊤ \boldsymbol{h}_t^\top ht。此时,我们只需要通过矢量化计算

softmax ( Q K ⊤ ) V \text{softmax}(\boldsymbol{Q}\boldsymbol{K}^\top)\boldsymbol{V} softmax(QK)V

即可算出转置后的背景向量 c t ′ ⊤ \boldsymbol{c}_{t'}^\top ct。当查询项矩阵 Q \boldsymbol{Q} Q的行数为 n n n时,上式将得到 n n n行的输出矩阵。输出矩阵与查询项矩阵在相同行上一一对应。

更新隐藏状态

现在我们描述第二个关键点,即更新隐藏状态。以门控循环单元为例,在解码器中我们可以对6.7节(门控循环单元(GRU))中门控循环单元的设计稍作修改,从而变换上一时间步 t ′ − 1 t'-1 t1的输出 y t ′ − 1 \boldsymbol{y}_{t'-1} yt1、隐藏状态 s t ′ − 1 \boldsymbol{s}_{t' - 1} st1和当前时间步 t ′ t' t的含注意力机制的背景变量 c t ′ \boldsymbol{c}_{t'} ct [1]。解码器在时间步 t ′ t' t的隐藏状态为

s t ′ = z t ′ ⊙ s t ′ − 1 + ( 1 − z t ′ ) ⊙ s ~ t ′ , \boldsymbol{s}_{t'} = \boldsymbol{z}_{t'} \odot \boldsymbol{s}_{t'-1} + (1 - \boldsymbol{z}_{t'}) \odot \tilde{\boldsymbol{s}}_{t'}, st=ztst1+(1zt)s~t,

其中的重置门、更新门和候选隐藏状态分别为

r t ′ = σ ( W y r y t ′ − 1 + W s r s t ′ − 1 + W c r c t ′ + b r ) , z t ′ = σ ( W y z y t ′ − 1 + W s z s t ′ − 1 + W c z c t ′ + b z ) , s ~ t ′ = tanh ( W y s y t ′ − 1 + W s s ( s t ′ − 1 ⊙ r t ′ ) + W c s c t ′ + b s ) , \begin{aligned} \boldsymbol{r}_{t'} &= \sigma(\boldsymbol{W}_{yr} \boldsymbol{y}_{t'-1} + \boldsymbol{W}_{sr} \boldsymbol{s}_{t' - 1} + \boldsymbol{W}_{cr} \boldsymbol{c}_{t'} + \boldsymbol{b}_r),\\ \boldsymbol{z}_{t'} &= \sigma(\boldsymbol{W}_{yz} \boldsymbol{y}_{t'-1} + \boldsymbol{W}_{sz} \boldsymbol{s}_{t' - 1} + \boldsymbol{W}_{cz} \boldsymbol{c}_{t'} + \boldsymbol{b}_z),\\ \tilde{\boldsymbol{s}}_{t'} &= \text{tanh}(\boldsymbol{W}_{ys} \boldsymbol{y}_{t'-1} + \boldsymbol{W}_{ss} (\boldsymbol{s}_{t' - 1} \odot \boldsymbol{r}_{t'}) + \boldsymbol{W}_{cs} \boldsymbol{c}_{t'} + \boldsymbol{b}_s), \end{aligned} rtzts~t=σ(Wyryt1+Wsrst1+Wcrct+br),=σ(Wyzyt1+Wszst1+Wczct+bz),=tanh(Wysyt1+Wss(st1rt)+Wcsct+bs),

其中含下标的 W \boldsymbol{W} W b \boldsymbol{b} b分别为门控循环单元的权重参数和偏差参数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值