深度学习入门(五十三)循环神经网络——序列模型

深度学习入门(五十三)循环神经网络——序列模型

  • 前言
  • 循环神经网络——序列模型
    • 课件
      • 序列数据
      • 序列数据:更多例子
      • 统计工具
      • 序列模型
      • 方案A-马尔科夫假设
      • 方案B-潜变量模型
      • 总结
    • 教材
      • 1 统计工具
        • 1.1 自回归模型
        • 1.2 马尔可夫模型
        • 1.3 因果关系
      • 2 训练
      • 3 预测
      • 4 小结

前言

核心内容来自博客链接1博客连接2希望大家多多支持作者
本文记录用,防止遗忘

循环神经网络——序列模型

课件

序列数据

实际中很多数据是有时序结构的
比如:电影的评价随时间变化而变化

  • 拿奖后评分上升,直到奖项被忘记
  • 看了很多好电影后,人们的期望变高
  • 季节性:贺岁片、暑期档
  • 导演、演员的负面报道导致评分变低

序列数据:更多例子

音乐、语言、文本、和视频都是连续的

  • 标题“狗咬人”远没有“人咬狗”那么令人惊讶
  • 大地震发生后,很可能会有几次较小的余震
  • 人的互动是连续的,从网上吵架可以看出
  • 预测明天的股价要比填补昨天遗失的股价的更困难

统计工具

  • 在时间 t t t观察到 x t x_t xt,那么得到 T T T个不独立的随机变量 ( x 1 , x 2 , . . . , x T ) ∼ p ( x ) (x_1,x_2,...,x_T) ∼ p(\mathbf{x}) (x1,x2,...,xT)p(x)
  • 使用条件概率展开
    p ( a , b ) = p ( a ) p ( b ∣ a ) = p ( b ) p ( a ∣ b ) p(a,b)= p(a)p(b|a)=p(b)p(a | b) p(a,b)=p(a)p(ba)=p(b)p(ab)

p ( x ) = p ( x 1 ) ⋅ p ( x 2 ∣ x 1 ) ⋅ p ( x 3 ∣ x 1 , x 2 ) ⋅ … p ( x T ∣ x 1 , … x T − 1 ) p(\mathbf{x}) = p(x_1) ⋅ p(x_2| x_1) ⋅ p(x_3| x_1, x_2) ⋅ …p(x_T| x_1, …x_{T−1}) p(x)=p(x1)p(x2x1)p(x3x1,x2)p(xTx1,xT1)

深度学习入门(五十三)循环神经网络——序列模型_第1张图片

p ( x ) = p ( x T ) ⋅ p ( x T − 1 ∣ x T ) ⋅ p ( x T − 2 ∣ x T − 1 , x T ) ⋅ … p ( x 1 ∣ x 2 , … x T ) p(\mathbf{x}) = p(x_T) ⋅ p(x_{T-1}| x_T) ⋅ p(x_{T-2}| x_{T-1}, x_{T}) ⋅ …p(x_1| x_{2}, …x_{T}) p(x)=p(xT)p(xT1xT)p(xT2xT1,xT)p(x1x2,xT)

深度学习入门(五十三)循环神经网络——序列模型_第2张图片

序列模型

p ( x ) = p ( x 1 ) ⋅ p ( x 2 ∣ x 1 ) ⋅ p ( x 3 ∣ x 1 , x 2 ) ⋅ … p ( x T ∣ x 1 , … x T − 1 ) p(\mathbf{x}) = p(x_1) ⋅ p(x_2| x_1) ⋅ p(x_3| x_1, x_2) ⋅ …p(x_T| x_1, …x_{T−1}) p(x)=p(x1)p(x2x1)p(x3x1,x2)p(xTx1,xT1)
深度学习入门(五十三)循环神经网络——序列模型_第3张图片
对条件概率建模
p ( x t ∣ x 1 , … x t − 1 ) = p ( x t ∣ f ( x 1 , … x t − 1 ) p(x_t| x_1, …x_{t−1}) = p(x_t| f(x_1, …x_{t−1}) p(xtx1,xt1)=p(xtf(x1,xt1)

对见过的数据建模,也称自回归模型

方案A-马尔科夫假设

p ( x ) = p ( x 1 ) ⋅ p ( x 2 ∣ x 1 ) ⋅ p ( x 3 ∣ x 1 , x 2 ) ⋅ … p ( x T ∣ x 1 , … x T − 1 ) p(\mathbf{x}) = p(x_1) ⋅ p(x_2| x_1) ⋅ p(x_3| x_1, x_2) ⋅ …p(x_T| x_1, …x_{T−1}) p(x)=p(x1)p(x2x1)p(x3x1,x2)p(xTx1,xT1)
深度学习入门(五十三)循环神经网络——序列模型_第4张图片

  • 假设当前数据只跟 τ τ τ个过去数据点相关
    深度学习入门(五十三)循环神经网络——序列模型_第5张图片
    p ( x t ∣ x 1 , … x t − 1 ) = p ( x t ∣ x t − τ , … x t − 1 ) = p ( x t ∣ f ( x t − τ , … x t − 1 ) ) p(x_t| x_1, …x_{t−1}) = p(x_t| x_{t−τ}, …x_{t−1}) = p(x_t| f(x_{t−τ}, …x_{t−1})) p(xtx1,xt1)=p(xtxtτ,xt1)=p(xtf(xtτ,xt1))

方案B-潜变量模型

p ( x ) = p ( x 1 ) ⋅ p ( x 2 ∣ x 1 ) ⋅ p ( x 3 ∣ x 1 , x 2 ) ⋅ … p ( x T ∣ x 1 , … x T − 1 ) p(\mathbf{x}) = p(x_1) ⋅ p(x_2| x_1) ⋅ p(x_3| x_1, x_2) ⋅ …p(x_T| x_1, …x_{T−1}) p(x)=p(x1)p(x2x1)p(x3x1,x2)p(xTx1,xT1)
深度学习入门(五十三)循环神经网络——序列模型_第6张图片

引入潜变量 h t h_t ht,来表示过去信息 h t = f ( x 1 , . . . , x t − 1 ) h_t =f(x_1,...,x_{t-1}) ht=f(x1,...,xt1)
这样 x t = p ( x t ∣ h t ) x_t= p(x_t | h_t) xt=p(xtht)
深度学习入门(五十三)循环神经网络——序列模型_第7张图片

总结

  • 时序模型中,当前数据跟之前观察到的数据相关
  • 自回归模型使用自身过去数据来预测未来
  • 马尔科夫模型假设当前只跟最近少数数据相关,从而简化模型
  • 潜变量模型使用潜变量来概括历史信息

教材

1 统计工具

处理序列数据需要统计工具和新的深度神经网络架构。 为了简单起见,我们以下图所示的股票价格为例。
深度学习入门(五十三)循环神经网络——序列模型_第8张图片
其中,用 x t x_t xt表示价格,即在时间步(time step) t ∈ Z + t \in \mathbb{Z}^+ tZ+时,观察到的价格 x t x_t xt。 请注意, t t t对于本文中的序列通常是离散的,并在整数或其子集上变化。 假设一个交易员想在%t%日的股市中表现良好,于是通过以下途径预测 x t x_t xt
x t ∼ P ( x t ∣ x t − 1 , … , x 1 ) . x_t \sim P(x_t \mid x_{t-1}, \ldots, x_1). xtP(xtxt1,,x1).

1.1 自回归模型

为了实现这个预测,交易员可以使用回归模型, 例如在线性回归一节中训练的模型。 仅有一个主要问题:输入数据的数量, 输入 x t − 1 , … , x 1 x_{t-1}, \ldots, x_1 xt1,,x1本身因 t t t而异。 也就是说,输入数据的数量这个数字将会随着我们遇到的数据量的增加而增加, 因此需要一个近似方法来使这个计算变得容易处理。 本章后面的大部分内容将围绕着如何有效估计 P ( x t ∣ x t − 1 , … , x 1 ) P(x_t \mid x_{t-1}, \ldots, x_1) P(xtxt1,,x1)展开。 简单地说,它归结为以下两种策略。

  • 第一种策略,假设在现实情况下相当长的序列 x t − 1 , … , x 1 x_{t-1}, \ldots, x_1 xt1,,x1可能是不必要的, 因此我们只需要满足某个长度为 τ \tau τ的时间跨度, 即使用观测序列 x t − 1 , … , x t − τ x_{t-1}, \ldots, x_{t-\tau} xt1,,xtτ。 当下获得的最直接的好处就是参数的数量总是不变的, 至少在 t > τ t > \tau t>τ时如此,这就使我们能够训练一个上面提及的深度网络。 这种模型被称为自回归模型(autoregressive models), 因为它们是对自己执行回归。

  • 第二种策略,如图所示, 是保留一些对过去观测的总结 h t h_t ht, 并且同时更新预测 x ^ t \hat{x}_t x^t和总结 h t h_t ht。 这就产生了基于 x ^ t = P ( x t ∣ h t ) \hat{x}_t = P(x_t \mid h_{t}) x^t=P(xtht)估计 x t x_t xt, 以及公式 h t = g ( h t − 1 , x t − 1 ) h_t = g(h_{t-1}, x_{t-1}) ht=g(ht1,xt1)更新的模型。 由于 h t h_t ht从未被观测到,这类模型也被称为隐变量自回归模型(latent autoregressive models)
    深度学习入门(五十三)循环神经网络——序列模型_第9张图片

这两种情况都有一个显而易见的问题:如何生成训练数据? 一个经典方法是使用历史观测来预测下一个未来观测。 显然,我们并不指望时间会停滞不前。 然而,一个常见的假设是虽然特定值 x t x_t xt可能会改变, 但是序列本身的动力学不会改变。 这样的假设是合理的,因为新的动力学一定受新的数据影响, 而我们不可能用目前所掌握的数据来预测新的动力学。 统计学家称不变的动力学为静止的(stationary)。 因此,整个序列的估计值都将通过以下的方式获得:
P ( x 1 , … , x T ) = ∏ t = 1 T P ( x t ∣ x t − 1 , … , x 1 ) . P(x_1, \ldots, x_T) = \prod_{t=1}^T P(x_t \mid x_{t-1}, \ldots, x_1). P(x1,,xT)=t=1TP(xtxt1,,x1).
注意,如果我们处理的是离散的对象(如单词), 而不是连续的数字,则上述的考虑仍然有效。 唯一的差别是,对于离散的对象, 我们需要使用分类器而不是回归模型来估计 P ( x t ∣ x t − 1 , … , x 1 ) P(x_t \mid x_{t-1}, \ldots, x_1) P(xtxt1,,x1)

1.2 马尔可夫模型

回想一下,在自回归模型的近似法中, 我们使用 x t − 1 , … , x t − τ x_{t-1}, \ldots, x_{t-\tau} xt1,,xtτ而不是 x t − 1 , … , x 1 x_{t-1}, \ldots, x_1 xt1,,x1来估计 x t x_t xt。 只要这种是近似精确的,我们就说序列满足马尔可夫条件(Markov condition)。 特别是,如果 τ = 1 \tau = 1 τ=1,得到一个一阶马尔可夫模型(first-order Markov model) P ( x ) P(x) P(x)由下式给出: P ( x 1 , … , x T ) = ∏ t = 1 T P ( x t ∣ x t − 1 )  当  P ( x 1 ∣ x 0 ) = P ( x 1 ) . P(x_1, \ldots, x_T) = \prod_{t=1}^T P(x_t \mid x_{t-1}) \text{ 当 } P(x_1 \mid x_0) = P(x_1). P(x1,,xT)=t=1TP(xtxt1)  P(x1x0)=P(x1).

当假设 x t x_t xt仅是离散值时,这样的模型特别棒, 因为在这种情况下,使用动态规划可以沿着马尔可夫链精确地计算结果。 例如,我们可以高效地计算 P ( x t + 1 ∣ x t − 1 ) P(x_{t+1} \mid x_{t-1}) P(xt+1xt1)

P ( x t + 1 ∣ x t − 1 ) = ∑ x t P ( x t + 1 , x t , x t − 1 ) P ( x t − 1 ) = ∑ x t P ( x t + 1 ∣ x t , x t − 1 ) P ( x t , x t − 1 ) P ( x t − 1 ) = ∑ x t P ( x t + 1 ∣ x t ) P ( x t ∣ x t − 1 ) \begin{aligned}\begin{aligned} P(x_{t+1} \mid x_{t-1}) &= \frac{\sum_{x_t} P(x_{t+1}, x_t, x_{t-1})}{P(x_{t-1})}\\ &= \frac{\sum_{x_t} P(x_{t+1} \mid x_t, x_{t-1}) P(x_t, x_{t-1})}{P(x_{t-1})}\\ &= \sum_{x_t} P(x_{t+1} \mid x_t) P(x_t \mid x_{t-1}) \end{aligned}\end{aligned} P(xt+1xt1)=P(xt1)xtP(xt+1,xt,xt1)=P(xt1)xtP(xt+1xt,xt1)P(xt,xt1)=xtP(xt+1xt)P(xtxt1)

利用这一事实,我们只需要考虑过去观察中的一个非常短的历史: P ( x t + 1 ∣ x t , x t − 1 ) = P ( x t + 1 ∣ x t ) P(x_{t+1} \mid x_t, x_{t-1}) = P(x_{t+1} \mid x_t) P(xt+1xt,xt1)=P(xt+1xt)。 隐马尔可夫模型中的动态规划超出了本节的范围, 而动态规划这些计算工具已经在控制算法和强化学习算法广泛使用。

1.3 因果关系

原则上,将 P ( x 1 , … , x T ) P(x_1, \ldots, x_T) P(x1,,xT)倒序展开也没什么问题。 毕竟,基于条件概率公式,我们总是可以写出:

P ( x 1 , … , x T ) = ∏ t = T 1 P ( x t ∣ x t + 1 , … , x T ) . P(x_1, \ldots, x_T) = \prod_{t=T}^1 P(x_t \mid x_{t+1}, \ldots, x_T). P(x1,,xT)=t=T1P(xtxt+1,,xT).

事实上,如果基于一个马尔可夫模型, 我们还可以得到一个反向的条件概率分布。 然而,在许多情况下,数据存在一个自然的方向,即在时间上是前进的。 很明显,未来的事件不能影响过去。 因此,如果我们改变 x t x_t xt,可能会影响未来发生的事情 x t + 1 x_{t+1} xt+1,但不能反过来。 也就是说,如果我们改变 x t x_t xt,基于过去事件得到的分布不会改变。 因此,解释 P ( x t + 1 ∣ x t ) P(x_{t+1} \mid x_t) P(xt+1xt)应该比解释 P ( x t ∣ x t + 1 ) P(x_t \mid x_{t+1}) P(xtxt+1)更容易。 例如,在某些情况下,对于某些可加性噪声 ϵ \epsilon ϵ, 显然我们可以找到 x t + 1 = f ( x t ) + ϵ x_{t+1} = f(x_t) + \epsilon xt+1=f(xt)+ϵ, 而反之则不行。 而这个向前推进的方向恰好也是我们通常感兴趣的方向。

2 训练

在了解了上述统计工具后,让我们在实践中尝试一下! 首先,我们生成一些数据:使用正弦函数和一些可加性噪声来生成序列数据, 时间步为 1 , 2 , … , 1000 1, 2, \ldots, 1000 1,2,,1000

%matplotlib inline
import torch
from torch import nn
from d2l import torch as d2l

T = 1000  # 总共产生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))

深度学习入门(五十三)循环神经网络——序列模型_第10张图片
接下来,我们将这个序列转换为模型的“特征-标签”(feature-label)对。 基于嵌入维度 τ \tau τ,我们将数据映射为数据对 y t = x t y_t = x_t yt=xt x t = [ x t − τ , … , x t − 1 ] \mathbf{x}_t = [x_{t-\tau}, \ldots, x_{t-1}] xt=[xtτ,,xt1]。 你可能已经注意到,这比我们提供的数据样本少了 τ \tau τ个, 因为我们没有足够的历史记录来描述前 τ \tau τ个数据样本。 一个简单的解决办法是:如果拥有足够长的序列就丢弃这几项; 另一个方法是用零填充序列。 在这里,我们仅使用前600个“特征-标签”对进行训练。

tau = 4
features = torch.zeros((T - tau, tau))
for i in range(tau):
    features[:, i] = x[i: T - tau + i]
labels = x[tau:].reshape((-1, 1))

batch_size, n_train = 16, 600
# 只有前n_train个样本用于训练
train_iter = d2l.load_array((features[:n_train], labels[:n_train]),
                            batch_size, is_train=True)

在这里,我们使用一个相当简单的架构训练模型: 一个拥有两个全连接层的多层感知机,ReLU激活函数和平方损失。

# 初始化网络权重的函数
def init_weights(m):
    if type(m) == nn.Linear:
        nn.init.xavier_uniform_(m.weight)

# 一个简单的多层感知机
def get_net():
    net = nn.Sequential(nn.Linear(4, 10),
                        nn.ReLU(),
                        nn.Linear(10, 1))
    net.apply(init_weights)
    return net

# 平方损失。注意:MSELoss计算平方误差时不带系数1/2
loss = nn.MSELoss(reduction='none')

现在,准备训练模型了。实现下面的训练代码的方式与前面中的循环训练基本相同。因此,我们不会深入探讨太多细节。

def train(net, train_iter, loss, epochs, lr):
    trainer = torch.optim.Adam(net.parameters(), lr)
    for epoch in range(epochs):
        for X, y in train_iter:
            trainer.zero_grad()
            l = loss(net(X), y)
            l.sum().backward()
            trainer.step()
        print(f'epoch {epoch + 1}, '
              f'loss: {d2l.evaluate_loss(net, train_iter, loss):f}')

net = get_net()
train(net, train_iter, loss, 5, 0.01)

输出:

epoch 1, loss: 0.092109
epoch 2, loss: 0.061592
epoch 3, loss: 0.057409
epoch 4, loss: 0.055409
epoch 5, loss: 0.054001

3 预测

由于训练损失很小,因此我们期望模型能有很好的工作效果。 让我们看看这在实践中意味着什么。 首先是检查模型预测下一个时间步的能力, 也就是单步预测(one-step-ahead prediction)

onestep_preds = net(features)
d2l.plot([time, time[tau:]],
         [x.detach().numpy(), onestep_preds.detach().numpy()], 'time',
         'x', legend=['data', '1-step preds'], xlim=[1, 1000],
         figsize=(6, 3))

深度学习入门(五十三)循环神经网络——序列模型_第11张图片

正如我们所料,单步预测效果不错。 即使这些预测的时间步超过了 600 + 4 600+4 600+4(n_train + tau), 其结果看起来仍然是可信的。 然而有一个小问题:如果数据观察序列的时间步只到 604 604 604, 我们需要一步一步地向前迈进:

x ^ 605 = f ( x 601 , x 602 , x 603 , x 604 ) , x ^ 606 = f ( x 602 , x 603 , x 604 , x ^ 605 ) , x ^ 607 = f ( x 603 , x 604 , x ^ 605 , x ^ 606 ) , x ^ 608 = f ( x 604 , x ^ 605 , x ^ 606 , x ^ 607 ) , x ^ 609 = f ( x ^ 605 , x ^ 606 , x ^ 607 , x ^ 608 ) , … \begin{aligned}\hat{x}_{605} = f(x_{601}, x_{602}, x_{603}, x_{604}), \\ \hat{x}_{606} = f(x_{602}, x_{603}, x_{604}, \hat{x}_{605}), \\ \hat{x}_{607} = f(x_{603}, x_{604}, \hat{x}_{605}, \hat{x}_{606}),\\ \hat{x}_{608} = f(x_{604}, \hat{x}_{605}, \hat{x}_{606}, \hat{x}_{607}),\\ \hat{x}_{609} = f(\hat{x}_{605}, \hat{x}_{606}, \hat{x}_{607}, \hat{x}_{608}),\\ \ldots\end{aligned} x^605=f(x601,x602,x603,x604),x^606=f(x602,x603,x604,x^605),x^607=f(x603,x604,x^605,x^606),x^608=f(x604,x^605,x^606,x^607),x^609=f(x^605,x^606,x^607,x^608),
通常,对于直到 x t x_t xt的观测序列,其在时间步 t + k t+k t+k处的预测输出 x ^ t + k \hat{x}_{t+k} x^t+k称为k步预测(k-step-ahead-prediction)。 由于我们的观察已经到了 x 604 x_{604} x604,它的k步预测是 x ^ 604 + k \hat{x}_{604+k} x^604+k。 换句话说,我们必须使用我们自己的预测(而不是原始数据)来进行多步预测。 让我们看看效果如何。

multistep_preds = torch.zeros(T)
multistep_preds[: n_train + tau] = x[: n_train + tau]
for i in range(n_train + tau, T):
    multistep_preds[i] = net(
        multistep_preds[i - tau:i].reshape((1, -1)))

d2l.plot([time, time[tau:], time[n_train + tau:]],
         [x.detach().numpy(), onestep_preds.detach().numpy(),
          multistep_preds[n_train + tau:].detach().numpy()], 'time',
         'x', legend=['data', '1-step preds', 'multistep preds'],
         xlim=[1, 1000], figsize=(6, 3))

深度学习入门(五十三)循环神经网络——序列模型_第12张图片
如上面的例子所示,绿线的预测显然并不理想。 经过几个预测步骤之后,预测的结果很快就会衰减到一个常数。 为什么这个算法效果这么差呢?事实是由于错误的累积: 假设在步骤1之后,我们积累了一些错误
ϵ 1 = ϵ ˉ \epsilon_1 = \bar\epsilon ϵ1=ϵˉ。 于是,步骤2的输入被扰动了 ϵ 1 \epsilon_1 ϵ1, 结果积累的误差是依照次序的 ϵ 2 = ϵ ˉ + c ϵ 1 \epsilon_2 = \bar\epsilon + c \epsilon_1 ϵ2=ϵˉ+cϵ1, 其中 c c c为某个常数,后面的预测误差依此类推。 因此误差可能会相当快地偏离真实的观测结果。 例如,未来24小时的天气预报往往相当准确, 但超过这一点,精度就会迅速下降。 我们将在本章及后续章节中讨论如何改进这一点。

基于 k = 1 , 4 , 16 , 64 k = 1, 4, 16, 64 k=1,4,16,64,通过对整个序列预测的计算, 让我们更仔细地看一下k步预测的困难。

max_steps = 64

features = torch.zeros((T - tau - max_steps + 1, tau + max_steps))
# 列i(i
for i in range(tau):
    features[:, i] = x[i: i + T - tau - max_steps + 1]

# 列i(i>=tau)是来自(i-tau+1)步的预测,其时间步从(i)到(i+T-tau-max_steps+1)
for i in range(tau, tau + max_steps):
    features[:, i] = net(features[:, i - tau:i]).reshape(-1)

steps = (1, 4, 16, 64)
d2l.plot([time[tau + i - 1: T - max_steps + i] for i in steps],
         [features[:, (tau + i - 1)].detach().numpy() for i in steps], 'time', 'x',
         legend=[f'{i}-step preds' for i in steps], xlim=[5, 1000],
         figsize=(6, 3))

深度学习入门(五十三)循环神经网络——序列模型_第13张图片
以上例子清楚地说明了当我们试图预测更远的未来时,预测的质量是如何变化的。 虽然“4步预测”看起来仍然不错,但超过这个跨度的任何预测几乎都是无用的。

4 小结

  • 内插法(在现有观测值之间进行估计)和外推法(对超出已知观测范围进行预测)在实践的难度上差别很大。因此,对于你所拥有的序列数据,在训练时始终要尊重其时间顺序,即最好不要基于未来的数据进行训练。

  • 序列模型的估计需要专门的统计工具,两种较流行的选择是自回归模型和隐变量自回归模型。

  • 对于时间是向前推进的因果模型,正向估计通常比反向估计更容易。

  • 对于直到时间步 t t t的观测序列,其在时间步 t + k t+k t+k的预测输出是“k步预测”。随着我们对预测时间k值的增加,会造成误差的快速累积和预测质量的极速下降。

你可能感兴趣的:(深度学习,深度学习,rnn)