提交 4e2e1b7f 编写于 作者: A Aston Zhang

add seq2seq, revise rnn

上级 01108c79
......@@ -133,6 +133,9 @@ $$f(x + \epsilon) \approx f(x) + f'(x) \epsilon.$$
## 概率
## 概率和统计
### 全概率和条件概率
### 最大似然估计
# 注意力机制
在以上的解码器设计中,各个时刻使用了相同的背景向量。如果解码器的不同时刻可以使用不同的背景向量呢?
在以上的解码器设计中,各个时间步使用了相同的背景变量。如果解码器的不同时间步可以使用不同的背景变量呢?
## 设计
以英语-法语翻译为例,给定一对输入序列“they are watching”和输出序列“Ils regardent”,解码器在时刻1可以使用更多编码了“they are”信息的背景向量来生成“Ils”,而在时刻2可以使用更多编码了“watching”信息的背景向量来生成“regardent”。这看上去就像是在解码器的每一时刻对输入序列中不同时刻分配不同的注意力。这也是注意力机制的由来。它最早[由Bahanau等在2015年提出](https://arxiv.org/abs/1409.0473)
以英语-法语翻译为例,给定一对输入序列“they are watching”和输出序列“Ils regardent”,解码器在时间步1可以使用更多编码了“they are”信息的背景变量来生成“Ils”,而在时间步2可以使用更多编码了“watching”信息的背景变量来生成“regardent”。这看上去就像是在解码器的每一时间步对输入序列中不同时间步分配不同的注意力。这也是注意力机制的由来。它最早[由Bahanau等在2015年提出](https://arxiv.org/abs/1409.0473)
现在,对上面的解码器稍作修改。我们假设时刻$t^\prime$的背景向量为$\boldsymbol{c}_{t^\prime}$。那么解码器在$t^\prime$时刻的隐含层变量
现在,对上面的解码器稍作修改。我们假设时间步$t^\prime$的背景变量为$\boldsymbol{c}_{t^\prime}$。那么解码器在$t^\prime$时间步的隐含层变量
$$\boldsymbol{s}_{t^\prime} = g(\boldsymbol{y}_{t^\prime-1}, \boldsymbol{c}_{t^\prime}, \boldsymbol{s}_{t^\prime-1})$$
令编码器在$t$时刻的隐含变量为$\boldsymbol{h}_t$,解码器在$t^\prime$时刻的背景向量为
令编码器在$t$时间步的隐含变量为$\boldsymbol{h}_t$,解码器在$t^\prime$时间步的背景变量为
$$\boldsymbol{c}_{t^\prime} = \sum_{t=1}^T \alpha_{t^\prime t} \boldsymbol{h}_t$$
也就是说,给定解码器的当前时刻$t^\prime$,我们需要对编码器中不同时刻$t$的隐含层变量求加权平均。而权值也称注意力权重。它的计算公式是
也就是说,给定解码器的当前时间步$t^\prime$,我们需要对编码器中不同时间步$t$的隐含层变量求加权平均。而权值也称注意力权重。它的计算公式是
$$\alpha_{t^\prime t} = \frac{\exp(e_{t^\prime t})}{ \sum_{k=1}^T \exp(e_{t^\prime k}) } $$
......@@ -35,7 +35,7 @@ $$e_{t^\prime t} = \boldsymbol{v}^\top \tanh(\boldsymbol{W}_s \boldsymbol{s}_{t^
在解码器中,我们需要对GRU的设计稍作修改。
假设$\boldsymbol{y}_t$是单个输出$y_t$在嵌入层的结果,例如$y_t$对应的one-hot向量$\boldsymbol{o} \in \mathbb{R}^y$与嵌入层参数矩阵$\boldsymbol{B} \in \mathbb{R}^{y \times s}$的乘积$\boldsymbol{o}^\top \boldsymbol{B}$。
假设时刻$t^\prime$的背景向量为$\boldsymbol{c}_{t^\prime}$。那么解码器在$t^\prime$时刻的单个隐含层变量
假设时间步$t^\prime$的背景变量为$\boldsymbol{c}_{t^\prime}$。那么解码器在$t^\prime$时间步的单个隐含层变量
$$\boldsymbol{s}_{t^\prime} = \boldsymbol{z}_{t^\prime} \odot \boldsymbol{s}_{t^\prime-1} + (1 - \boldsymbol{z}_{t^\prime}) \odot \tilde{\boldsymbol{s}}_{t^\prime}$$
......@@ -48,9 +48,12 @@ $$\boldsymbol{z}_{t^\prime} = \sigma(\boldsymbol{W}_{yz} \boldsymbol{y}_{t^\prim
$$\tilde{\boldsymbol{s}}_{t^\prime} = \text{tanh}(\boldsymbol{W}_{ys} \boldsymbol{y}_{t^\prime-1} + \boldsymbol{W}_{ss} (\boldsymbol{s}_{t^\prime - 1} \odot \boldsymbol{r}_{t^\prime}) + \boldsymbol{W}_{cs} \boldsymbol{c}_{t^\prime} + \boldsymbol{b}_s)$$
我们将在本章稍后的[“机器翻译”](nmt.md)一节中实现含深度循环神经网络的编码器和解码器。
## 小结
* 在解码器上应用注意力机制可以在解码器的每个时刻使用不同的背景向量。每个背景向量相当于对输入序列的不同部分分配了不同的注意力。
* 在解码器上应用注意力机制可以在解码器的每个时间步使用不同的背景变量。每个背景变量相当于对输入序列的不同部分分配了不同的注意力。
## 练习
......@@ -66,3 +69,10 @@ $$\tilde{\boldsymbol{s}}_{t^\prime} = \text{tanh}(\boldsymbol{W}_{ys} \boldsymbo
## 扫码直达[讨论区](https://discuss.gluon.ai/t/topic/6759)
![](../img/qr_seq2seq-attention.svg)
## 参考文献
[1] Bahdanau, Dzmitry, Kyunghyun Cho, and Yoshua Bengio. “Neural machine translation by jointly learning to align and translate.” arXiv:1409.0473 (2014).
[2] Luong, Minh-Thang, Hieu Pham, and Christopher D. Manning. “Effective approaches to attention-based neural machine translation.” arXiv:1508.04025 (2015).
......@@ -4,7 +4,6 @@
本章中,我们将先介绍如何用向量表示词,并应用这些词向量求近义词和类比词。接着,在文本分类任务中,我们进一步应用词向量分析文本情感。此外,自然语言处理任务中很多输出是不定长的,例如任意长度的句子。我们将描述应对这类问题的编码器—解码器模型以及注意力机制,并将它们应用于机器翻译中。
本章介绍的自然语言处理技术大多是2013年后提出的,并有着广泛的应用。
```eval_rst
......
......@@ -39,6 +39,7 @@ encoder_num_layers = 1
decoder_num_layers = 2
encoder_drop_prob = 0.1
decoder_drop_prob = 0.1
encoder_embed_size = 256
encoder_num_hiddens = 256
decoder_num_hiddens = 256
alignment_size = 25
......@@ -51,6 +52,10 @@ ctx = mx.cpu(0)
这里使用了[之前章节](pretrained-embedding.md)介绍的`mxnet.contrib.text`来创建法语和英语的词典。需要注意的是,我们会在句末附上EOS符号,并可能通过添加PAD符号使每个序列等长。
我们通常会在输入序列和输出序列后面分别附上一个特殊字符“<eos>”(end of sequence)表示序列的终止。在测试模型时,一旦输出“<eos>”就终止当前的输出序列。
```{.python .input}
def read_data(max_seq_len):
input_tokens = []
......@@ -103,14 +108,14 @@ dataset = gdata.ArrayDataset(fr, en)
```{.python .input}
class Encoder(nn.Block):
"""编码器。"""
def __init__(self, num_inputs, num_hiddens, num_layers, drop_prob,
**kwargs):
def __init__(self, num_inputs, embed_size, num_hiddens, num_layers,
drop_prob, **kwargs):
super(Encoder, self).__init__(**kwargs)
with self.name_scope():
self.embedding = nn.Embedding(num_inputs, num_hiddens)
self.embedding = nn.Embedding(num_inputs, embed_size)
self.dropout = nn.Dropout(drop_prob)
self.rnn = rnn.GRU(num_hiddens, num_layers, dropout=drop_prob,
input_size=num_hiddens)
input_size=embed_size)
def forward(self, inputs, state):
embedding = self.embedding(inputs).swapaxes(0, 1)
......@@ -304,8 +309,8 @@ def train(encoder, decoder, decoder_init_state, max_seq_len, ctx,
以下分别实例化编码器、解码器和解码器初始隐藏状态网络。
```{.python .input}
encoder = Encoder(len(input_vocab), encoder_num_hiddens, encoder_num_layers,
encoder_drop_prob)
encoder = Encoder(len(input_vocab), encoder_embed_size, encoder_num_hiddens,
encoder_num_layers, encoder_drop_prob)
decoder = Decoder(decoder_num_hiddens, len(output_vocab),
decoder_num_layers, max_seq_len, decoder_drop_prob,
alignment_size, encoder_num_hiddens)
......@@ -323,7 +328,7 @@ train(encoder, decoder, decoder_init_state, max_seq_len, ctx, eval_fr_ens)
## 束搜索
在上一节里,我们提到编码器最终输出了一个背景向量$\boldsymbol{c}$,该背景向量编码了输入序列$x_1, x_2, \ldots, x_T$的信息。假设训练数据中的输出序列是$y_1, y_2, \ldots, y_{T^\prime}$,输出序列的生成概率是
在上一节里,我们提到编码器最终输出了一个背景变量$\boldsymbol{c}$,该背景变量编码了输入序列$x_1, x_2, \ldots, x_T$的信息。假设训练数据中的输出序列是$y_1, y_2, \ldots, y_{T^\prime}$,输出序列的生成概率是
$$\mathbb{P}(y_1, \ldots, y_{T^\prime}) = \prod_{t^\prime=1}^{T^\prime} \mathbb{P}(y_{t^\prime} \mid y_1, \ldots, y_{t^\prime-1}, \boldsymbol{c}).$$
......
# 求近似词和类比词
本节介绍如何应用预训练的词向量,例如求近似词和类比词。这里使用的预训练的GloVe和fastText词向量分别来自它们的项目网站 [1,2]。
本节介绍如何应用在大规模语料上预训练的词向量,例如求近似词和类比词。这里使用的预训练的GloVe和fastText词向量分别来自它们的项目网站 [1,2]。
首先导入实验所需的包或模块。
......@@ -12,10 +12,10 @@ from mxnet.gluon import nn
## 由数据集建立词典和载入词向量
下面,我们以fastText为例,由数据集建立词典并载入词向量。fastText提供了基于不同语言的多套预训练的词向量。以下打印了其中的5套。
下面,我们以fastText为例,由数据集建立词典并载入词向量。fastText提供了基于不同语言的多套预训练的词向量。这些词向量是在大规模语料上训练得到的,例如维基百科语料。以下打印了其中的10套。
```{.python .input n=34}
text.embedding.get_pretrained_file_names('fasttext')[:5]
print(text.embedding.get_pretrained_file_names('fasttext')[:10])
```
### 访问词向量
......@@ -85,10 +85,10 @@ layer(nd.array([2, 1]))[:, :5]
除了使用数据集建立词典外,我们还可以直接由预训练词向量建立词典。
这一次我们使用GloVe的预训练词向量。以下打印了GloVe提供的预训练词向量中的5套
这一次我们使用GloVe的预训练词向量。以下打印了GloVe提供的各套预训练词向量。这些词向量是在大规模语料上训练得到的,例如维基百科语料和推特语料
```{.python .input n=35}
text.embedding.get_pretrained_file_names('glove')[:5]
print(text.embedding.get_pretrained_file_names('glove'))
```
我们使用50维的词向量。和之前不同,这里不再传入根据数据集建立的词典,而是直接使用预训练词向量中的词建立词典。
......
# 编码器—解码器(seq2seq)
在很多应用中,输入和输出都可以是不定长序列。以机器翻译为例,输入是可以是一段不定长的英语文本序列,输出可以是一段不定长的法语文本序列,例如
在基于词语的语言模型中,我们使用了[循环神经网络](../chapter_recurrent-neural-networks/rnn-gluon.md)。它的输入是一段不定长的序列,输出却是定长的,例如一个词语。然而,很多问题的输出也是不定长的序列。以机器翻译为例,输入是可以是英语的一段话,输出可以是法语的一段话,输入和输出皆不定长,例如
> 英语:“They”、“are”、“watching”、“.”
> 英语:They are watching.
> 法语:“Ils”、“regardent”、“.”
> 法语:Ils regardent.
当输入输出都是不定长序列时,我们可以使用编码器—解码器(encoder-decoder)[1] 或者seq2seq模型 [2]。这两个模型本质上都用到了两个循环神经网络,分别叫做编码器和解码器。编码器对应输入序列,解码器对应输出序列。下面我们来介绍编码器—解码器的设计。
当输入输出都是不定长序列时,我们可以使用编码器—解码器(encoder-decoder)或者seq2seq。它们分别基于2014年的两个工作:
* Cho et al., [Learning Phrase Representations using RNN Encoder-Decoder for Statistical Machine Translation](https://www.aclweb.org/anthology/D14-1179)
* Sutskever et al., [Sequence to Sequence Learning with Neural Networks](https://papers.nips.cc/paper/5346-sequence-to-sequence-learning-with-neural-networks.pdf)
## 编码器
以上两个工作本质上都用到了两个循环神经网络,分别叫做编码器和解码器。编码器对应输入序列,解码器对应输出序列。下面我们来介绍编码器—解码器的设计
编码器的把一个不定长的输入序列变换成一个定长的背景变量$\boldsymbol{c}$,并在该背景变量中编码输入序列信息。常用的编码器是循环神经网络
让我们考虑批量大小为1的时序数据样本。
在时间步$t$,循环神经网络将输入$x_t$的特征向量$\boldsymbol{x}_t$和上个时间步的隐藏状态$\boldsymbol{h}_{t-1}$变换为当前时间步的隐藏状态$\boldsymbol{h}_t$。因此,我们可以用函数$f$表达循环神经网络隐藏层的变换:
## 编码器—解码器
$$\boldsymbol{h}_t = f(\boldsymbol{x}_t, \boldsymbol{h}_{t-1}). $$
编码器和解码器是分别对应输入序列和输出序列的两个循环神经网络。我们通常会在输入序列和输出序列后面分别附上一个特殊字符“<eos>”(end of sequence)表示序列的终止。在测试模型时,一旦输出“<eos>”就终止当前的输出序列。
假设输入序列的时间步数为$T$。编码器通过自定义函数$q$将各个时间步的隐藏状态变换为背景变量
### 编码器
$$\boldsymbol{c} = q(\boldsymbol{h}_1, \ldots, \boldsymbol{h}_T).$$
编码器的作用是把一个不定长的输入序列转化成一个定长的背景向量$\boldsymbol{c}$。该背景向量包含了输入序列的信息。常用的编码器是循环神经网络。
我们回顾一下[循环神经网络](../chapter_recurrent-neural-networks/rnn-scratch.md)知识。假设循环神经网络单元为$f$,在$t$时刻的输入为$x_t, t=1, \ldots, T$。
假设$\boldsymbol{x}_t$是单个输出$x_t$在嵌入层的结果,例如$x_t$对应的one-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$。
例如,当选择$q(\boldsymbol{h}_1, \ldots, \boldsymbol{h}_T) = \boldsymbol{h}_T$时,背景变量是输入序列最终时间步的隐藏状态$\boldsymbol{h}_T$。
我们将这里的循环神经网络叫做编码器。
#### 双向循环神经网络
编码器的输入既可以是正向传递,也可以是反向传递。如果输入序列是$x_1, x_2, \ldots, 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}) $$
以上描述的编码器是一个单向的循环神经网络,每个时间步的隐藏状态只取决于该时间步及之前的输入子序列。我们也可以使用双向循环神经网络构造编码器。这种情况下,编码器每个时间步的隐藏状态同时取决于该时间步之前和之后的子序列(包括当前时间步的输入),并编码了整个序列的信息。
## 解码器
当我们希望编码器的输入既包含正向传递信息又包含反向传递信息时,我们可以使用双向循环神经网络。例如,给定输入序列$x_1, x_2, \ldots, x_T$,按正向传递,它们在循环神经网络的隐含层变量分别是$\overrightarrow{\boldsymbol{h}}_1, \overrightarrow{\boldsymbol{h}}_2, \ldots, \overrightarrow{\boldsymbol{h}}_T$;按反向传递,它们在循环神经网络的隐含层变量分别是$\overleftarrow{\boldsymbol{h}}_1, \overleftarrow{\boldsymbol{h}}_2, \ldots, \overleftarrow{\boldsymbol{h}}_T$。在双向循环神经网络中,时刻$i$的隐含层变量可以把$\overrightarrow{\boldsymbol{h}}_i$和$\overleftarrow{\boldsymbol{h}}_i$连结起来。
刚刚已经介绍,假设输入序列的时间步数为$T$,编码器输出的背景变量$\boldsymbol{c}$编码了整个输入序列$x_1, \ldots, x_T$的信息。给定训练样本中的输出序列$y_1, y_2, \ldots, y_{T^\prime}$。假设其中每个时间步$t^\prime$的输出同时取决于该时间步之前的输出序列和背景变量。那么,根据最大似然估计,我们可以最大化输出序列基于输入序列的条件概率
### 解码器
$$
\begin{aligned}
\mathbb{P}(y_1, \ldots, y_{T^\prime} \mid x_1, \ldots, x_T)
&= \prod_{t^\prime=1}^{T^\prime} \mathbb{P}(y_{t^\prime} \mid y_1, \ldots, y_{t^\prime-1}, x_1, \ldots, x_T)\\
&= \prod_{t^\prime=1}^{T^\prime} \mathbb{P}(y_{t^\prime} \mid y_1, \ldots, y_{t^\prime-1}, \boldsymbol{c}),
\end{aligned}
$$
编码器最终输出了一个背景向量$\boldsymbol{c}$,该背景向量编码了输入序列$x_1, x_2, \ldots, x_T$的信息。
假设训练数据中的输出序列是$y_1, y_2, \ldots, y_{T^\prime}$,我们希望表示每个$t$时刻输出的既取决于之前的输出又取决于背景向量。之后,我们就可以最大化输出序列的联合概率
并得到该输出序列的损失
$$\mathbb{P}(y_1, \ldots, y_{T^\prime}) = \prod_{t^\prime=1}^{T^\prime} \mathbb{P}(y_{t^\prime} \mid y_1, \ldots, y_{t^\prime-1}, \boldsymbol{c})$$
$$- \log\mathbb{P}(y_1, \ldots, y_{T^\prime}).$$
为此,我们可以使用另一个循环神经网络作为解码器。
在输出序列的时间步$t^\prime$,解码器将上一时间步的输出$y_{t^\prime-1}$以及背景变量$\boldsymbol{c}$作为输入,并将它们与上一时间步的隐藏状态$\boldsymbol{s}_{t^\prime-1}$变换为当前时间步的隐藏状态$\boldsymbol{s}_{t^\prime}$。因此,我们可以用函数$g$表达解码器隐藏层的变换:
并得到该输出序列的损失函数
$$\boldsymbol{s}_{t^\prime} = g(y_{t^\prime-1}, \boldsymbol{c}, \boldsymbol{s}_{t^\prime-1}).$$
$$- \log\mathbb{P}(y_1, \ldots, y_{T^\prime})$$
有了解码器的隐藏状态后,我们可以自定义输出层来计算损失中的$\mathbb{P}(y_{t^\prime} \mid y_1, \ldots, y_{t^\prime-1}, \boldsymbol{c})$,例如基于当前时间步的解码器隐藏状态 $\boldsymbol{s}_{t^\prime}$、上一时间步的输出$y_{t^\prime-1}$以及背景变量$\boldsymbol{c}$来计算当前时间步输出$y_{t^\prime}$的概率分布。
为此,我们使用另一个循环神经网络作为解码器。解码器使用函数$p$来表示单个输出$y_{t^\prime}$的概率
$$\mathbb{P}(y_{t^\prime} \mid y_1, \ldots, y_{t^\prime-1}, \boldsymbol{c}) = p(y_{t^\prime-1}, \boldsymbol{s}_{t^\prime}, \boldsymbol{c})$$
其中的$\boldsymbol{s}_t$为$t^\prime$时刻的解码器的隐含层变量。该隐含层变量
$$\boldsymbol{s}_{t^\prime} = g(y_{t^\prime-1}, \boldsymbol{c}, \boldsymbol{s}_{t^\prime-1})$$
其中函数$g$是循环神经网络单元。
需要注意的是,编码器和解码器通常会使用[多层循环神经网络](../chapter_recurrent-neural-networks/rnn-gluon.md)
在实际中,我们常使用深度循环神经网络作为编码器和解码器。我们将在本章稍后的[“机器翻译”](nmt.md)一节中实现含深度循环神经网络的编码器和解码器。
## 小结
* 编码器-解码器(seq2seq)的输入和输出可以都是不定长序列。
* 编码器-解码器(seq2seq)可以输入并输出不定长的序列。
* 编码器—解码器使用了两个循环神经网络。
## 练习
......@@ -95,3 +75,10 @@ $$\boldsymbol{s}_{t^\prime} = g(y_{t^\prime-1}, \boldsymbol{c}, \boldsymbol{s}_{
## 扫码直达[讨论区](https://discuss.gluon.ai/t/topic/4523)
![](../img/qr_seq2seq.svg)
## 参考文献
[1] Cho, Kyunghyun, et al. “Learning phrase representations using RNN encoder-decoder for statistical machine translation.” arXiv:1406.1078 (2014).
[2] Sutskever, Ilya, Oriol Vinyals, and Quoc V. Le. "Sequence to sequence learning with neural networks." NIPS. 2014.
......@@ -21,7 +21,7 @@ $$\boldsymbol{O}_t = \boldsymbol{H}_t \boldsymbol{W}_{hy} + \boldsymbol{b}_y,$$
其中权重$\boldsymbol{W}_{hy} \in \mathbb{R}^{2h \times y}$和偏差$\boldsymbol{b}_y \in \mathbb{R}^{1 \times y}$为输出层的模型参数。
双向循环神经网络架构如图6.4所示。和前面介绍的单向循环神经网络不同,给定一段时间序列,双向循环神经网络在每个时间步的隐藏状态同时取决于该时间步之前和之后的子序列(包括当前时间步的输入),并可能捕捉了整个序列的信息。
双向循环神经网络架构如图6.4所示。和前面介绍的单向循环神经网络不同,给定一段时间序列,双向循环神经网络在每个时间步的隐藏状态同时取决于该时间步之前和之后的子序列(包括当前时间步的输入),并编码了整个序列的信息。
![双向循环神经网络架构。](../img/birnn.svg)
......@@ -31,7 +31,7 @@ $$\boldsymbol{O}_t = \boldsymbol{H}_t \boldsymbol{W}_{hy} + \boldsymbol{b}_y,$$
## 小结
* 双向循环神经网络在每个时间步的隐藏状态可能捕捉了整个序列的信息
* 双向循环神经网络在每个时间步的隐藏状态同时取决于该时间步之前和之后的子序列(包括当前时间步的输入)
## 练习
......
......@@ -6,7 +6,7 @@
## 长短期记忆
我们先介绍长短期记忆的设计。它修改了循环神经网络隐藏状态的计算方式。长短期记忆的隐藏状态包括形状相同的隐藏层变量和细胞(也称记忆细胞)。
我们先介绍长短期记忆的设计。它修改了循环神经网络隐藏状态的计算方式,并引入了与隐藏状态形状相同的记忆细胞(某些文献把记忆细胞当成一种特殊的隐藏状态)。
### 输入门、遗忘门和输出门
......@@ -27,37 +27,37 @@ $$
和门控循环单元中的重置门和更新门一样,这里的输入门、遗忘门和输出门中每个元素的值域都是$[0, 1]$。
### 候选细胞
### 候选记忆细胞
和门控循环单元中的候选隐藏状态一样,时间步$t$的的候选细胞$\tilde{\boldsymbol{C}}_t \in \mathbb{R}^{n \times h}$也使用了值域在$[-1, 1]$的tanh函数做激活函数。它的计算和不带门控的循环神经网络的隐藏状态的计算没什么区别:
和门控循环单元中的候选隐藏状态一样,时间步$t$的的候选记忆细胞$\tilde{\boldsymbol{C}}_t \in \mathbb{R}^{n \times h}$也使用了值域在$[-1, 1]$的tanh函数做激活函数。它的计算和不带门控的循环神经网络的隐藏状态的计算没什么区别:
$$\tilde{\boldsymbol{C}}_t = \text{tanh}(\boldsymbol{X}_t \boldsymbol{W}_{xc} + \boldsymbol{H}_{t-1} \boldsymbol{W}_{hc} + \boldsymbol{b}_c).$$
其中的$\boldsymbol{W}_{xc} \in \mathbb{R}^{x \times h}$和$\boldsymbol{W}_{hc} \in \mathbb{R}^{h \times h}$是权重参数,$\boldsymbol{b}_c \in \mathbb{R}^{1 \times h}$是偏移参数。
### 细胞
### 记忆细胞
我们可以通过元素值域在$[0, 1]$的输入门、遗忘门和输出门来控制隐藏状态中信息的流动:这通常可以应用按元素乘法符$\odot$。当前时间步细胞$\boldsymbol{C}_t \in \mathbb{R}^{n \times h}$的计算组合了上一时间步细胞和当前时间步候选细胞的信息,并通过遗忘门和输入门来控制信息的流动:
我们可以通过元素值域在$[0, 1]$的输入门、遗忘门和输出门来控制隐藏状态中信息的流动:这通常可以应用按元素乘法符$\odot$。当前时间步记忆细胞$\boldsymbol{C}_t \in \mathbb{R}^{n \times h}$的计算组合了上一时间步记忆细胞和当前时间步候选记忆细胞的信息,并通过遗忘门和输入门来控制信息的流动:
$$\boldsymbol{C}_t = \boldsymbol{F}_t \odot \boldsymbol{C}_{t-1} + \boldsymbol{I}_t \odot \tilde{\boldsymbol{C}}_t.$$
需要注意的是,如果遗忘门一直近似1且输入门一直近似0,过去的细胞将一直通过时间保存并传递至当前时间步。
需要注意的是,如果遗忘门一直近似1且输入门一直近似0,过去的记忆细胞将一直通过时间保存并传递至当前时间步。
这个设计可以应对循环神经网络中的梯度衰减问题,并更好地捕捉时序数据中间隔较大的依赖关系。
### 隐藏层变量
### 隐藏状态
有了细胞以后,接下来我们还可以通过输出门来控制从细胞到隐藏层变量$\boldsymbol{H}_t \in \mathbb{R}^{n \times h}$的信息的流动:
有了记忆细胞以后,接下来我们还可以通过输出门来控制从记忆细胞到隐藏状态$\boldsymbol{H}_t \in \mathbb{R}^{n \times h}$的信息的流动:
$$\boldsymbol{H}_t = \boldsymbol{O}_t \odot \text{tanh}(\boldsymbol{C}_t).$$
这里的tanh函数确保隐藏层变量元素值在-1到1之间。需要注意的是,当输出门近似1,细胞信息将传递到隐藏层变量供输出层使用;当输出门近似0,细胞信息只自己保留。
这里的tanh函数确保隐藏状态元素值在-1到1之间。需要注意的是,当输出门近似1,记忆细胞信息将传递到隐藏状态供输出层使用;当输出门近似0,记忆细胞信息只自己保留。
### 输出层
在时间步$t$,长短期记忆的输出层计算和之前描述的循环神经网络输出层计算一样:我们只需将该时刻的隐藏层变量$\boldsymbol{H}_t$传递进输出层,从而计算时间步$t$的输出。
在时间步$t$,长短期记忆的输出层计算和之前描述的循环神经网络输出层计算一样:我们只需将该时刻的隐藏状态$\boldsymbol{H}_t$传递进输出层,从而计算时间步$t$的输出。
## 实验
......@@ -182,7 +182,7 @@ gb.train_and_predict_rnn(lstm_rnn, False, num_epochs, num_steps, num_hiddens,
## 小结
* 长短期记忆的隐藏状态包括隐藏层变量和细胞。只有隐藏层变量会传递进输出层。
* 长短期记忆的隐藏层输出包括隐藏状态和记忆细胞。只有隐藏状态会传递进输出层。
* 长短期记忆的输入门、遗忘门和输出门可以控制信息的流动。
* 长短期记忆的可以应对循环神经网络中的梯度衰减问题,并更好地捕捉时序数据中间隔较大的依赖关系。
......@@ -190,8 +190,8 @@ gb.train_and_predict_rnn(lstm_rnn, False, num_epochs, num_steps, num_hiddens,
## 练习
* 调调超参数,观察并分析对运行时间、困惑度以及创作歌词的结果造成的影响。
* 在相同条件下,比较长短期记忆、门控循环单元和循环神经网络的运行时间。
* 既然候选细胞已通过使用tanh函数确保值域在-1到1之间,为什么隐藏层变量还需再次使用tanh函数来确保输出值域在-1到1之间?
* 在相同条件下,比较长短期记忆、门控循环单元和不带门控的循环神经网络的运行时间。
* 既然候选记忆细胞已通过使用tanh函数确保值域在-1到1之间,为什么隐藏状态还需再次使用tanh函数来确保输出值域在-1到1之间?
## 扫码直达[讨论区](https://discuss.gluon.ai/t/topic/4042)
......
......@@ -195,10 +195,10 @@ def detach(state):
def eval_rnn(data_source):
l_sum = nd.array([0], ctx=ctx)
n = 0
hidden = model.begin_state(func=nd.zeros, batch_size=batch_size, ctx=ctx)
state = model.begin_state(func=nd.zeros, batch_size=batch_size, ctx=ctx)
for i in range(0, data_source.shape[0] - 1, num_steps):
X, y = get_batch(data_source, i)
output, hidden = model(X, hidden)
output, state = model(X, state)
l = loss(output, y)
l_sum += l.sum()
n += l.size
......@@ -212,15 +212,15 @@ def train_rnn():
for epoch in range(1, num_epochs + 1):
train_l_sum = nd.array([0], ctx=ctx)
start_time = time.time()
hidden = model.begin_state(func=nd.zeros, batch_size=batch_size,
state = model.begin_state(func=nd.zeros, batch_size=batch_size,
ctx=ctx)
for batch_i, idx in enumerate(range(0, train_data.shape[0] - 1,
num_steps)):
X, y = get_batch(train_data, idx)
# 从计算图分离隐藏状态。
hidden = detach(hidden)
# 从计算图分离隐藏状态变量(包括 LSTM 的记忆细胞)
state = detach(state)
with autograd.record():
output, hidden = model(X, hidden)
output, state = model(X, state)
# l 形状:(batch_size * num_steps,)。
l = loss(output, y).sum() / (batch_size * num_steps)
l.backward()
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册