从序列模型到 Transformer(PyTorch 版)(1)|Python 主题月

826 阅读4分钟

本文正在参加「Python主题月」,详情查看活动链接

想象一下,你正在西瓜视频网站上看电影,首先我们假设每一个用户给出评分都是他对这部电影真实的评分。,你想看更多的电影,对吗?事实证明,事情并不那么简单。人们对不同类型电影的兴趣可以随着时间的推移而发生相当大的变化。事实上,心理学家甚至对其中的一些影响有命名。

内容参考《深度学习》的 8.1. Sequence Models 章节,对内容进行解读整理融入了自己的认识。

028.jpeg

  • 时效性,我们评分也会收到大众影响,在奥斯卡颁奖之后,相应电影的收视率会上升,即使是同一部电影。这种效应持续了几个月,直到奖项被遗忘。有研究表明,这种效应将评分提高了半个百分点以上。

  • 而且我们会有这种感觉,就是同期电影都比较优秀,对于同一部电影如果放在竞争激烈寒暑假时,当观众看了许多优秀作品后再看这个电影就会感觉很一般,不过如果在其他时期可能同一部电影会在一些平平庸庸作品中脱颖而出

  • 有季节性,在暑假或者寒假期间一些电影票房会上升,如果把贺岁片放在寒假或者暑假来上映,效果就没有预期那么好,因为很多产品是有季节性的。

  • 在某些情况下,电影由于导演或演员在制作过程中的不当行为而变得不受欢迎。

让我们用𝑥𝑡表示价格,即在时间步骤𝑡∈ℤ+我们观察到价格𝑥𝑡。请注意,对于本文中的序列,𝑡通常是离散的,在整数或其子集上变化。假设一个希望在𝑡日在股市中表现良好的交易者通过以下方式预测𝑥𝑡

回归模型

为了实现这一点,我们的交易员可以使用一个回归模型。不过这里有重要的问题,就是输入的数量不是常数而是变量,𝑥𝑡-1,...,𝑥1 是随机变量,取决于 t。这里怎么理解这些数据,也就是即使在同一个时刻 t 所看的数据也不是不同的,听起来有点,当然采样数据不可能回到过去,这里假设可以归到过去 t 时刻,因为 xtx_t 是一个随机变量,就是重复在 t 时刻观察该数据你也无法得到形同值,但是这个 xtx_t 又不是孤立存在的,可能与其之前时刻值有一定关系,例如今天商品价格浮动虽然是随机的但是也是在上一个时刻价格基础上涨价或者降价,而且可能也会受到前一段时间,也就是时间跨度内价格走势所影响,所以我们可以认为这些点联合分布也就是近似为 p(xtxt1,,x1)p(x_t|x_{t-1},\cdots,x_1)

自回归模型

首先,对于当前 t 时刻的 x 也没有必要考虑相当长的序列 xt1,,x1x_{t-1},\cdots,x_1 。在这种情况下,可以选择适当序列长度为 𝜏 的时间跨度即可,所以也就考虑xt1,xtτx_{t-1} \cdots, x_{t-\tau} 的观察值作为对于当前 xtx_t 预测。这样可以避免参数过多,而且参数的数量是固定的,𝑡>𝜏。这使得我们可以训练一个如上所述的深度网络。这种模型将被称为自回归模型

潜在自回归模型

第二种策略,如图8.1.2所示,是保留过去观察值都更新到 hth_t 这是因状态单元,在每一个时刻在预测 x^\hat{x}同时也会更新 hth_t。估计 xtx_tx)t=P(xtht),然后通过x)t = P(x_t|h_t),然后通过 h_t = g(h_{t-1},x_{t-1})来更新 来更新h_t。由于。由于 h_t$ 是隐含模型内部用于保存状态的,所以这些模型也被称为潜在自回归模型

027.svg

这两种情况都提出了一个明显的问题:如何产生训练数据。人们通常使用历史观察来预测下一个观察,而这些观察是到现在为止的。显然,我们并不期望时间静止。然而,一个常见的假设是,虽然𝑥的具体数值可能会改变,但至少序列本身的动态(也就是变化规律、例如趋势、季节性这些特性是不会改变。这是合理的,因为新的动态就是这样,是新的,因此用我们目前掌握的数据是无法预测的。统计学家把不发生变化的动态称为静止的动态。不管我们怎么做,我们将通过以下方式得到整个序列的估计值。

以上两个问题都是围绕如何产生训练数据集,我们都是基于过去观测值来预测下一个值,每个数据都是随机变量,这些随机变量并不是静止数据,这些和我们之前遇到图像数据集、房价数据集这些静止不变数据不同,这也是随机过程中随机变量的魅力,那么我们有应该如何在这些动态数据中学到知识以便进行推理,数据虽然是动态随机变量,但是他们在时间轴会呈现出一些规律供我们模型学习。统计学家把不发生变化的动态称为静止的动态。我们要做到就是通过序列给出预测值

P(x1,,xT)=i=1TP(xtxt1,,x1)P(x_1, \cdots, x_T) = \prod_{i=1}^T P(x_t|x_{t-1},\cdots, x_1)

马尔科夫链模型

回顾一下近似,在自回归模型中,只使用 xt1,,xtτx_{t-1},\cdots, x_{t-\tau},而不是 xt1,,x1x_{t-1},\cdots,x_1 数据来估计 xtx_t。只要这种近似是准确的,也就是大小为 tautau 时间窗口在数据上滑动,就说该序列满足马尔科夫条件,如果τ=1\tau = 1,就有一个一阶Markov模型,𝑃(𝑥)由以下公式给出

P(x1,,xT)=t=1TP(xtxt1) where P(x1x0)=P(x1)P(x_1, \ldots, x_T) = \prod_{t=1}^T P(x_t \mid x_{t-1}) \text{ where } P(x_1 \mid x_0) = P(x_1)

xtx_t 就便于计算,因为我们近似考虑只有与当前值最近的值对当前值做的贡献最大,我们就可以有效地计算P(xt+1xt1)P(x_{t+1} \mid x_{t-1})

P(xt+1xt1)=xtP(xt+1,xt,xt1)P(xt1)=xtP(xt+1xt,xt1)P(xt,xt1)P(xt1)=xtP(xt+1xt)P(xtxt1)\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}

代码实现

%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))

output_1_0.svg

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
train_iter = d2l.load_array((features[:n_train], labels[:n_train]), batch_size, is_train=True)
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
loss = nn.MSELoss()
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.backward()
            trainer.step()
            
        print(f'epoch {epoch + 1}, loss:{d2l.evaluate_loss(net, train_iter, loss):f}')
net = get_net()
train(net, train_iter, loss, 5, 0.01)
epoch 1, loss:0.064925
epoch 2, loss:0.052238
epoch 3, loss:0.054105
epoch 4, loss:0.052671
epoch 5, loss:0.049820

预测

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))

由于训练损失很小,查看模型表现如何。看看这在实践中意味着什么,要检查的是模型对下一步发生的事情的预测能力,即一步到位的预测。

output_5_0.svg

multisetp_preds = torch.zeros(T)
multisetp_preds[:n_train + tau] = x[:n_train + tau]
for i in range(n_train + tau, T):
    multisetp_preds[i] = net(multisetp_preds[i - tau:i].reshape((1,-1)))
d2l.plot([time, time[tau:], time[n_train + tau:]], [
    x.detach().numpy(),
    onestep_preds.detach().numpy(), multisetp_preds[n_train + tau:].detach().numpy()],
        'time','x',legend=['data','1-step preds','multistep preds'],
        xlim=[1,1000],figsize=(6,3))

output_6_0.svg