到目前为止,我们遇到过两种类型的数据:表格数据和图像数据。对于图像数据,我们设计了专门的卷积神经网络架构(cnn)来为这类特殊的数据结构建模。换句话说,如果我们拥有一张图像,我们 需要有效地利用其像素位置,假若我们对图像中的像素位置进行重排,就会对图像中内容的推断造成极大的困难。
最重要的是,到目前为止我们默认数据都来自于某种分布,并且 所有样本都是独立同分布 的(independently and identically distributed)。然而,大多数的数据并非如此。例如,文章中的单词是按顺序写的,如 果顺序被随机地重排,就很难理解文章原始的意思。同样,视频中的图像帧、对话中的音频信号以及网站上 的浏览行为都是有顺序的。因此,针对此类数据而设计特定模型,可能效果会更好。 另一个问题来自这样一个事实:我们不仅仅可以接收一个序列作为输入,而是还可能期望继续猜测这个序列的后续。例如,一个任务可以是继续预测2, 4, 6, 8, 10, . . .。这在时间序列分析中是相当常见的,可以用来预测 股市的波动、患者的体温曲线或者赛车所需的加速度。同理,我们需要能够处理这些数据的特定模型。
简言之,如果说 卷积神经网络可以有效地处理空间信息,那么本章的循环神经网络(recurrent neural network, RNN)则可以更好地处理序列信息。循环神经网络通过引入状态变量存储过去的信息和当前的输入,从而可 以确定当前的输出。 许多使用循环网络的例子都是基于文本数据的,因此我们将在本章中重点介绍语言模型。在对序列数据进行 更详细的回顾之后,我们将介绍文本预处理的实用技术。然后,我们将讨论语言模型的基本概念,并将此讨论作为循环神经网络设计的灵感。
一 序列模型
处理序列数据需要统计工具和新的深度神经网络架构。
1.1 自回归模型
以 预测股票价格为例。
为了实现这个预测,交易员可以使用回归模型。仅有一个主要问题:输入数据的 数量,输入xt−1, . . . , x1本身因t而异。也就是说,输入数据的数量这个数字将会随着我们遇到的数据量的增加 而增加,因此需要一个近似方法来使这个计算变得容易处理。本章后面的大部分内容将围绕着如何有效估计 P(xt | xt−1, . . . , x1)展开。简单地说,它归结为以下两种策略。
第一种策略,假设在现实情况下相当长的序列 xt−1, . . . , x1可能是不必要的,因此我们只需要满足某个长度 为τ的时间跨度,即使用观测序列xt−1, . . . , xt−τ。当下获得的最直接的好处就是参数的数量总是不变的,至少 在t > τ时如此,这就使我们能够训练一个上面提及的深度网络。这种模型被称为自回归模型(autoregressive models),因为它们是对自己执行回归。
第二种策略,如 下图所示,是保留一些对过去观测的总结ht,并且同时更新预测xˆt和总结ht。这就产生了 基于xˆt = P(xt | ht)估计xt,以及公式ht = g(ht−1, xt−1)更新的模型。由于ht从未被观测到,这类模型也被称 为 隐变量自回归模型(latent autoregressive models)。
这两种情况都有一个显而易见的问题:如何生成训练数据?一个经典方法是使用历史观测来预测下一个未来观测。显然,我们并不指望时间会停滞不前。然而,一个常见的假设是虽然特定值xt可能会改变,但是序列 本身的动力学不会改变。这样的假设是合理的,因为新的动力学一定受新的数据影响,而我们不可能用目前 所掌握的数据来预测新的动力学。统计学家称 不变的动力学为静止的(stationary)。
注意,如果我们处理的是离散的对象(如单词),而不是连续的数字,则上述的考虑仍然有效。唯一的差别是, 对于离散的对象,我们需要使用分类器而不是回归模型来估计P(xt | xt−1, . . . , x1)。
1.2 马尔可夫模型
回想一下,在自回归模型的近似法中,我们使用xt−1, . . . , xt−τ 而不是xt−1, . . . , x1来估计xt。只要这种是近似精确的,我们就说序列满足马尔可夫条件(Markov condition)。
当假设xt仅是离散值时,这样的模型特别棒,因为在这种情况下,使用动态规划可以沿着马尔可夫链精确地 计算结果。
利用这一事实,我们只需要考虑过去观察中的一个非常短的历史:P(xt+1 | xt, xt−1) = P(xt+1 | xt)。隐马尔 可夫模型中的动态规划超出了本节的范围,而动态规划这些计算工具已经在控制算法和强化学习算法广泛使用。
1.3 模型训练
在了解了上述统计工具后,让我们在实践中尝试一下!首先,我们生成一些数据:使用 正弦函数和一些可加 性噪声来生成序列数据,时间步为1, 2, . . . , 1000。
%matplotlib inline
import torch
from torch import nn
from d2l import torch as d2l
T = 1000
time = torch.arange(1, T + 1, dtype=torch.float32)
x = torch.sin(0.01 * time) + torch.normal(0, 0.2, (T,))
d2l.plot(time, [x], 'time', 'x', xlim=[1, 1000], figsize=(6, 3))