循环神经网络
写在前面
参考书籍
Aston Zhang, Zachary C. Lipton, Mu Li, Alexander J. Smola. Dive into Deep Learning. 2020.
简介 - Dive-into-DL-PyTorch (tangshusen.me)
循环神经网络前导
source code: NJU-ymhui/DeepLearning: Deep Learning with pytorch (github.com)
use git to clone: https://github.com/NJU-ymhui/DeepLearning.git
/RNN
markov.py text_preprocess.py nl_statistics.py random_sampling.py sequential_partition.py util.py rnn_self.py rnn_lib.py
到目前为止,我们一直都默认数据来自于某种分布,并且所有数据都是i.i.d(独立同分布)的,但是现实并非总是如此。比如一篇文章的文字是按某种顺序出现的,视频中的图像帧也按照特定顺序出现,网站的浏览行为也是有规律可循的…因此我们需要一个全新的模型来刻画这种现象。
本文介绍的循环神经网络可以很好地处理序列信息,通过引入状态变量存储过去的信息和当前的输入,可以给出当前的输出。
由于许多循环神经网络的例子都基于文本数据,因此本文着重介绍语言模型。
序列模型
处理序列数据需要统计工具和新的深度神经网络架构。
我们以股市交易数据为例入门,不妨用x~t~表示在t时间步(time step)时观测到的价格(注:t通常是离散的并在整数或其子集上变化);如果希望在t日时较为准确地预测当日价格x~t~,应当有x~t~ ~ P(x~t~|x~t-1~…x~1~), 即在已知前t - 1日结果的前提下预测当日结果。
自回归模型
第一种策略,假设在现实情况下相当长的序列x~t−1~, . . . , x~1~可能是不必要的,因此我们只需要满足某个长度为τ的时间跨度,即使用观测序列x~t−1~, . . . , x~t−τ~ 。当下获得的最直接的好处就是参数的数量总是不变的,至少在t > τ时如此,这就使我们能够训练一个上面提及的深度网络。这种模型被称为自回归模型,因为它们是对自己执行回归。
第二种策略,如图8.1.2所示,是保留一些对过去观测的总结ht,并且同时更新预测ˆx~t~和总结ht。这就产生了基于ˆx~t~ = P(x~t~ | h~t~)估计x~t~,以及公式h~t~ = g(h~t−1~, x~t−1~)更新的模型。由于ht从未被观测到,这类模型也被称为隐变量自回归模型

现在遇到一个新的问题,如何生成训练数据?一个常见的假设是,虽然特定值x~t~会改变,但序列本身的动力学不会改变,因为新的动力学一定受新数据的影响,而我们不可能用现有的数据预测出新的动力学。因此,整个序列的估计值都将通过以下的方式获得:
当处理对象离散时(比如单词)上述公式仍有效,只不过要用分类器而不是回归模型来估计P
马尔可夫模型Markov
理论部分
简单来说我们在上述公式的基础上,取τ = 1,得到一个一阶马尔可夫模型;再考虑*x~t~*仅是离散值,使用动态规划沿着马尔科夫链精确地计算结果。
详见9.1. Working with Sequences — Dive into Deep Learning 1.0.3 documentation (d2l.ai)
训练
接下来我们使用正弦函数在背景噪声下生成一些序列数据。
然后基于这些数据做一些预测
code
import torch |
output




文本预处理
序列数据的另一种常见形式是文本;例如,一篇文章可以被看作单词甚至是字符序列。文本预处理通常采用以下步骤:
- 将文本作为字符串加载到内存中。
- 将字符串拆分为词元(如单词和字符)。
- 建立一个词表,将拆分的词元映射到数字索引。(因为词元的类型是字符/字符串,而模型需要的是数字)
- 将文本转换为数字索引序列,方便模型操作。
code
import torch |
output
3221 |
语言模型和数据集
模型原理部分参考9.3. Language Models — Dive into Deep Learning 1.0.3 documentation (d2l.ai)
自然语言统计
code
import torch |
output
[('the', 2261), ('i', 1267), ('and', 1245), ('of', 1155), ('a', 816), ('to', 695), ('was', 552), ('in', 541), ('that', 443), ('my', 440)] |


读取长序列数据
因为序列数据本质上是连续的,因此我们在处理数据时需要解决读取长序列数据的问题。
一种处理办法是:当序列过长而不能被模型一次性处理时,拆分这样的序列方便模型读取。假设我们将使用神经网络来训练语言模型,模型中的网络一次处
理具有预定义长度(例如n个时间步)的一个小批量序列。现在的问题是如何随机生成一个小批量数据的特征和标签以供读取。
首先,由于文本序列可以是任意长的,例如整本《时光机器》,于是任意长的序列可以被我们划分为具有相同时间步数的子序列。当训练我们的神经网络时,这样的小批量子序列将被输入到模型中。假设网络一次只处理具有n个时间步的子序列。

上图画出了从原始文本序列获得子序列的所有不同的方式,其中n = 5,并且每个时间步的词元对应于一个字符。
那么,我们应该选择哪一个呢?如果我们只选择一个偏移量,那么用于训练网络的、所有可能的子序列的覆盖范围将是有限的;因此,我们可以从随机偏移量开始划分序列,以同时获得覆盖性(coverage)和随机性(randomness)。下面介绍两个策略:随机采样,顺序分区
随机采样
在此策略下,每个样本都是在原始的长序列上任意捕获的子序列。在迭代过程中,来自两个相邻的、随机的、小批量中的子序列不一定在原始序列上相邻。对于语言建模,目标是基于到目前为止我们看到的词元来预测下一个词元,因此标签是移位了一个词元的原始序列。
code
import torch |
output
每个小批量中有两个子序列对: |
顺序分区
在迭代过程中,除了对原始序列可以随机抽样外,我们还可以保证两个相邻的小批量中的子序列在原始序列上也是相邻的。这种策略在基于小批量的迭代过程中保留了拆分的子序列的顺序,因此称为顺序分区。
code
import torch |
output
X: tensor([[ 2, 3, 4, 5, 6], |
将两种策略整合出辅助类
util.py
from d2l import torch as d2l |
循环神经网络
下面我们开始正式介绍循环神经网络!
理论部分详见9.4. Recurrent Neural Networks — Dive into Deep Learning 1.0.3 documentation (d2l.ai)
无隐状态的神经网络:比如一个有单隐藏层的多层感知机
有隐状态的循环神经网络:…
基于循环神经网络的字符级语言模型、困惑度
详见9.4. Recurrent Neural Networks — Dive into Deep Learning 1.0.3 documentation (d2l.ai)
从零实现循环神经网络
此处将从头开始基于循环神经网络实现字符级语言模型,在时光机器数据集上训练。
code
import math |
output
tensor([[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |

random sample: |

简洁实现的循环神经网络
此节中我们将使用深度学习框架中的高级API实现循环神经网络
code
import torch |
output
torch.Size([1, 32, 256]) |

通过时间反向传播
本部分为纯理论介绍,详见9.7. Backpropagation Through Time — Dive into Deep Learning 1.0.3 documentation (d2l.ai)
(•‿•)