序列模型

2 minute read

Published:

本文介绍具有时间结构的序列数据,讨论自回归模型、隐变量自回归模型和马尔可夫假设,并通过时间序列预测代码比较不同预测方式。

🎦 本节课程视频地址 👇

Bilibil

序列模型

  • 实际中很多数据是有时序结构的
    • 如电影的评分
    • 音乐、语言、文本和视频都是连续的
    • 股票的价格
    • 人与人之间的互动

统计工具

  • 在时间 \(t\) 观察到 \(x_t\),那么得到 \(T\) 个不独立的随机变量 \((x_1,...,x_T)~p(\bf x)\)
  • 使用条件概率展开(贝叶斯公式) \(p(a,b)=p(a)p(b\vert a)=p(b)p(a\vert b)\)

推广到这一系列事件同时发生的概率,换言之,前 \(T-1\) 个事件已经发生的条件下,第 \(T\) 个事件发生的概率:

正向:

\[p({\bf x})=p(x_1)\cdot p(x_2\vert x_1)\cdot p(x_3\vert x_1,x_2)\cdot ...\cdot p(x_T\vert x_1,...,x_{T-1})\]

反向:

\[p({\bf x})=p(x_T)\cdot p(x_{T-1}\vert x_T)\cdot p(x_{T-2}\vert x_{T-1},x_T)\cdot ...\cdot p(x_1\vert x_{T-1},...,x_2)\]

反向从未来推广到过去,并不完全有物理意义

对条件概率建模:

\[p(x_t\vert x_1,...,x_{t-1})=p(x_t\vert f(x_1,...,x_{t-1}))\]

对见过的数据建模,也称自回归模型(Autoregressive Model)。

040-01

马尔科夫假设

假设当前数据只跟 \(\tau\) 个过去数据点相关

\[p(x_t\vert x_1,...,x_{t-1})=p(x_t\vert x_{t-\tau},...,x_{t-1})=p(x_t\vert f(x_{t-\tau},...,x_{t-1}))\]

潜变量模型

引入潜变量 \(h_t\) 来表示过去信息 \(h_t=f(x_1,...,x_{t-1})\)

  • 这样 \(x_t=p(x_t\vert h_t)\)

总结

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

代码模拟马尔可夫算法

创建数据集

%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,))
#torch.normal(mean, std, size)
d2l.plot(time, [x], 'time', 'x', xlim=[1, 1000], figsize=(6, 3))
tau = 4
features = torch.zeros((T - tau, tau))
#(996, 4)全零
for i in range(tau):
    features[:, i] = x[i: T - tau + i]
    #对每行的第i列赋值,996行
    #按顺序把每四个点作为一个样本
    #每次向前一个点
labels = x[tau:].reshape((-1, 1))
#预测点实值x(4:1000),转化为列向量
batch_size, n_train = 16, 600
#把前600个样本用于训练
train_iter = d2l.load_array((features[:n_train], labels[:n_train]),
                            batch_size, is_train=True) #x[599:603]
#Construct a PyTorch data iterator.
040-02

网络和训练

#定义MLP网络
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(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)
#在这里已经训练好了网络的权重,所以在后面直接调用就可以了

预测

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))
#分别对于实际和预测绘图
040-03
multistep_preds = torch.zeros(T)
multistep_preds[: n_train + tau] = x[: n_train + tau]
#(0:604)
for i in range(n_train + tau, T):
    #(604,1000)
    multistep_preds[i] = net(
        multistep_preds[i - tau:i].reshape((1, -1)))
    #[600,604]用于预测604并赋值
    #实际上前599个并没有起作用

#从前603个预测后面的396个
#所以很离谱

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))
040-04
max_steps = 64

features = torch.zeros((T - tau - max_steps + 1, tau + max_steps))
#(993, tau+max_steps)
for i in range(tau):
    features[:, i] = x[i: i + T - tau - max_steps + 1]
    #给前tau个赋值

for i in range(tau, tau + max_steps):
    features[:, i] = net(features[:, i - tau:i]).reshape(-1)
    #预测后max_steps个

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))
    # steps = 1
    # time[4: 937]
    # features[:, 4] 画每行第五个元素
    # steps = 4 
    # time[7: 937]
    # features[:, 7] 画每行第八个元素,或者说5-8个元素是预测出来的,只选择第8个
    
#实际上这个算法的底层逻辑都是用前4个预测第5个,然后依次类推,直到max_step处
#画图是为了把第step处的预测表示出来
#也就是表示出预测到不同位置的拟合程度
040-05

一点感悟

逻辑比代码本身更重要。

体现在代码里,就是通过简单的切片代替复杂的循环,实现想要达到的效果,这非常了不起。