在基于词语的语言模型中,我们使用了循环神经网络。它的输入是一段不定长的序列,输出却是定长的,例如输入:They are,输出可能是 watching 或者 sleeping。然而,很多问题的输出是不定长的序列。以机器翻译为例,输入是一段英文,输出是一段法语,输入和输出皆不定长,例如
英语:The are watching
法语:lls regardent
当输入输出序列都是不定长时,我们可以使用编码器 - 解码器(encoder-decoder)或者 seq2seq。它们分别是基于 2014 年的两个工作:
- Cho et al., Learning Phrase Representations using RNN Encoder-Decoder for Statistical Machine Translation
- Sutskever et al., Sequence to Sequence Leaerning with Neural Networks
以上两个工作本质上都用到了两个循环神经网络结构,分别叫做编码器和解码器。编码器对应输入序列,解码器对应输出序列
编码器 — 解码器
编码器和解码器分别对应输入序列和输出序列的两个循环神经网络。我们通常会在输入序列和输出序列后面分别附上一个特殊字符 '<eos>'(end of sequence)表示序列的终止。在测试模型时,一旦输出 '<eos>' 就终止当前的输出序列
编码器
编码器的作用是把一个不定长的输入序列转化成一个定长的背景词向量 $\boldsymbol {c}$。该背景词向量包含了输入序列的信息。常用的编码器是循环神经网络
首先回顾以下循环神经网络的知识。假设循环神经网络单元为 $f$,在 $t$ 时刻的输入为 $x_t,t=1,...,T$。假设 $\boldsymbol {x}_t$ 是单个输出在嵌入层的结果,例如 $\boldsymbol {x}_t$ 对应的 ont-hot 向量 $\boldsymbol {o}\in \mathbb {R}^x$ 与嵌入层参数矩阵 $\boldsymbol {E}\in \mathbb {R}^{x\times h}$ 的乘积 $\boldsymbol {o}^\top\boldsymbol {E}$。隐藏层变量
$$ \boldsymbol{h}_t = f(\boldsymbol{x}_t, \boldsymbol{h}_{t-1}) $$
编码器的背景向量
$$ \boldsymbol{c} = q(\boldsymbol{h}_1, \ldots, \boldsymbol{h}_T) $$
一个简单的背景向量可以认为是该网络最终时刻的隐藏层变量 $\boldsymbol {h}_T$。我们将这里的循环神经网络叫做编码器
双向循环神经网络
编码器的输入既可以是正向传递,也可以是反向传递的。如果输入序列是 $\boldsymbol {x}_1,\boldsymbol {x}_2,...,\boldsymbol {x}_T$,在正向传递中,隐藏层变量
$$ \overrightarrow {\boldsymbol{h}}_t = f(\boldsymbol{x}_t,\overrightarrow {\boldsymbol{h}}_{t-1}) $$
而反向传递过程中,隐藏层变量的计算变为
$$ \overleftarrow {\boldsymbol{h}}_t = f(\boldsymbol{x}_t,\overleftarrow {\boldsymbol{h}}_{t-1}) $$
当我们希望编码器的输入既包含正向传递信息又包含反向传递信息时,我们可以使用双向循环神经网络。例如,给定输入序列 $\boldsymbol {x}_1,\boldsymbol {x}_2,...,\boldsymbol {x}_T$,按正向传递,它们在循环神经网络中的隐藏层变量分别是 $\vec {\boldsymbol {h}}_1,\vec {\boldsymbol {h}}_2,...,\vec {\boldsymbol {h}}_T$;按反向传播,它们在循环神经网络中的隐藏层变量分别是 $\overleftarrow {\boldsymbol {h}}_1,\overleftarrow {\boldsymbol {h}}_2,...,\overleftarrow {\boldsymbol {h}}_T$。在双向循环神经网络中,时刻 $i$ 的隐藏层变量是将 $\vec {\boldsymbol {h}}_i$ 和 $\overleftarrow {\boldsymbol {h}}_i$ 拼接起来,例如
- import numpy as np
- h_forward = np.array([1, 2])
- h_backward = np.array([3, 4])
- h_bi = np.concat(h_forward, h_backward, dim=0)
- # [1, 2, 3, 4]
解码器
编码器最终输出了一个背景向量 $\boldsymbol {c}$,该背景向量整合了输入序列 $\boldsymbol {x}_1,\boldsymbol {x}_2,...,\boldsymbol {x}_T$
假设训练数据中的输出序列是 $\boldsymbol {y}_1,\boldsymbol {y}_2,...,\boldsymbol {y}_{T'}$,我们希望表示每个 $t'$ 时刻输出的向量,既取决于之前的输出又取决于背景向量。因为,我们可以最大化输出序列的联合概率
$$ P(\boldsymbol{y}_1,\boldsymbol{y}_2,...,\boldsymbol{y}_{T'})=\prod_{t'=1}^{T'}P(\boldsymbol{y}_{t'}\mid \boldsymbol{y}_1,...,\boldsymbol{y}_{t'-1},\boldsymbol{c}) $$
并得到该输出序列的损失函数
$$ -\log P(\boldsymbol{y}_1,...,\boldsymbol{y}_{T'}) $$
为此,我们使用另一个循环神经网络作为解码器。解码器使用函数 $p$ 来表示单个输出 $\boldsymbol {y}_{t'}$ 的概率
$$ P(\boldsymbol{y}_{t'}\mid \boldsymbol{y}_1,...,\boldsymbol{y}_{t'-1},\boldsymbol{c})=p(\boldsymbol{y}_{t'-1},\boldsymbol{s}_{t'},\boldsymbol{c}) $$
其中的 $\boldsymbol {s}_{t'}$ 为 $t'$ 时刻的解码器的隐藏层变量。该隐藏层变量
$$ \boldsymbol{s}_{t'}=g(\boldsymbol{y}_{t'-1},\boldsymbol{c},\boldsymbol{s}_{t'-1}) $$
其中函数 g 是循环神经网络单元
需要注意的是,编码器和解码器通常会使用多层循环神经网络
注意力机制
在以上的解码器设计中,各个时刻使用了相同的背景向量 $\boldsymbol {c}$。如果解码器的不同时刻可以使用不同的背景向量呢?
以英语 - 法语翻译为例,给定一对输入序列 "They are watching" 和输出序列 "lls regardent",解码器在时刻 1 可以使用更多的编码了 "They are" 信息的背景向量来生成 "lls",而在时刻 2 可以使用更多编码了 "watching" 信息的背景向量来生成 "regardent"。这看上去就像是在解码器的每一时刻对输入序列中不同时刻分配不同的注意力。这也是注意力机制的由来
现在,对上面的解码器稍作修改。我们假设时刻 $t'$ 的背景向量为 $\boldsymbol {c}_{t'}$。那么解码器在时刻 $t'$ 的隐藏层变量
$$ \boldsymbol{s}_{t'}=g(\boldsymbol{y}_{t'-1},\boldsymbol{c}_{t'},\boldsymbol{s}_{t'-1}) $$
令编码器在 $t$ 时刻的隐藏层变量为 $\boldsymbol {h}_t$,解码器在 $t'$ 时刻的背景向量为
$$ \boldsymbol{c}_{t'}=\sum_{t=1}^{T}\alpha_{t't}\boldsymbol{h}_t $$
也就是说,给定解码器的当前时刻 $t'$,我们需要对编码器中不同时刻的隐藏层变量求加权平均。而权值也称注意力权值。它的计算公式是
$$ \alpha_{t' t} = \frac{\exp(e_{t' t})}{ \sum_{k=1}^T \exp(e_{t' k}) } $$
而 $e_{t' t}\in \mathbb {R}$ 的计算公式为
$$ e_{t' t} = a(\boldsymbol{s}_{t' - 1}, \boldsymbol{h}_t) $$
其中函数 $a$ 有多种设计方法。在 Bahdanau 的论文中
$$ \boldsymbol{e}_{t't} = \boldsymbol{v}^\top \tanh(\boldsymbol{W}_s \boldsymbol{s}_{t'-1} + \boldsymbol{W}_h \boldsymbol{h}_t), $$
其中 $\boldsymbol {v}$、$\boldsymbol {W}_s$、$\boldsymbol {W}_h$ 和编码器与解码器两个循环神经网络中的各个权重与偏移项以及嵌入层参数等都是需要同时学习的模型参数
在 Bahdanau 的论文的论文中,编码器和解码器均使用了 GRU
在解码器中,我们需要对 GRU 的设计稍作修改,假设 $\boldsymbol {y}_t$ 是单个输出在嵌入层的结果,例如 $\boldsymbol {y}_t$ 对应的 ont-hot 向量 $\boldsymbol {o}\in \mathbb {R}^y$ 与嵌入层参数矩阵 $\boldsymbol {B}\in \mathbb {R}^{y\times s}$ 的乘积 $\boldsymbol {o}^\top\boldsymbol {B}$。假设时刻 $t'$ 的背景向量为 $\boldsymbol {c}_{t'}$。那么解码器在时刻 $t'$ 的单个隐藏层变量
$$ \boldsymbol{s}_{t'} = \boldsymbol{z}_{t'} \odot \boldsymbol{s}_{t'-1} + (1 - \boldsymbol{z}_{t'}) \odot \tilde{\boldsymbol{s}}_{t'} $$
其中,重置门、更新门和候选隐藏状态分别为
$$ \begin{split}\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'} &= \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}\end{split} $$
总结
- 编码器 - 解码器(seq2seq)的输入和输出可以都是不定长序列
- 在解码器上应用注意力机制可以使解码器的每个时刻使用不同的背景向量。每个背景向量相当于对输入序列的不同部分分配了不同的注意力
发现新大陆,写得清楚明了
有些地方 输入 输出 弄反了。
也就是说,给定解码器的当前时刻 ,我们需要对解码器中不同时刻的隐藏层变量求加权平均。
这里应该是对编码器中不同时刻的隐藏层变量求加权平均吧
已修改,感谢提醒
什么是背景向量?
以及它的作用是什么呢
一个简单的背景向量可以认为是该网络最终时刻的隐藏层变量 h_t
好的
h_bi = np.concat(h_forward, h_backward, dim=0)
'numpy' has no attribute 'concat'
想确认下,这里是不是有误?
这只是例子
h_bi = np.concat (h_forward, h_backward, dim=0) 可以改为:h_bi = np.hstack((h_forward, h_backward))
"它的输入时一段不定长的序列,输入却是定长的,"。开头第二句有笔误 @(乖)
已修改,感谢提醒
所以对于常规的 seq2seq 模型,encoder 和 decoder 的 rnn 网络大小,层数,每层的神经元个数都应该保持一致?如果是这样,还有什么其他的编码形式吗
[Math Processing Error] 公式都显示不出来欸 #(期待)
ctrl f5 刷新
尴尬了,刷新一下好了 @(惊哭)
谢谢