序列模型(1)—— 难处理的序列数据

  • 参考:《动手学深度学习》8.1 节

1. 序列数据

  • 机器学习中常考虑 “判断图像内容” 这样的分类任务或 “根据地区面积预测房价” 这样的回归任务,这时我们通常把数据简单地看作相互独立的 “样本-标记” 二元组,且所有样本都是 i.i.d 的
  • 生活中除了上面那些情况外,还有很多序列性质的数据,比如一段文字、一段音乐、语音数据、股价趋势、DNA 片段等等,这些序列数据样本间具有相关性,一个样本对应的标记是由其自身和其他样本共同决定的,样本数据不再具有 i.i.d 性质,这种独特的性质使得序列数据任务形式非常多样,比如
    1. 从序列整体考虑:语音识别(分类任务);文本情感分析、轨迹评分(回归任务)
    2. 从序列组成考虑:“完形填空”(分类或回归);序列生成(基于序列尾部数据反复进行分类或回归预测)
    3. 从序列表示考虑:表示学习(无监督任务)
  • 考察生活中最常见的序列数据,如股价曲线等时序数据、文本音视频等结构化数据,不难发现,序列中的所有样本都基于某种规律被精细地组织在一起,反过来说,序列数据体现了某种样本间的潜在规律和关系,基于这种考虑,处理序列数据任务时我们不仅仅对 序列样本与标记的关系 感兴趣,更对 序列样本之间的关系 感兴趣。事实上,我们常常要借助后者分析和处理前者
  • 数学中 随机过程 定义为一族与时间相关的随机变量,而序列恰好可以看做一堆相互关联的随机变量,因此序列都可以看做某种随机过程的轨道,从这个角度看,随机过程就能描述序列背后的 “动力学关系”(样本之间的关系)。实际应用中,我们通常不直接尝试学习随机过程,一方面对序列所服从的随机过程类型进行假设很困难,另一方面自然语言等复杂序列所对应的随机过程往往过于复杂以至于无法进行数学建模。尽管如此,如果能合理地假设序列数据满足某种随机过程,还是可以对序列数据的分布形式进行确定和简化,从而提高数据驱动学习的效率,比如在序列生成任务中,常常假设序列数据服从某种马尔可夫过程
  • MLP、SVM 等传统机器学习模型没有考虑对于样本间关系进行建模,因此处理序列数据需要新的统计工具新的深度神经网络架构,下面第 2 节介绍适用于序列建模的统计工具,第 3 节通过实验说明用传统网络架构处理序列数据的问题

2. 统计工具

  • 考虑一个简单的股价序列数据(30年的 FTSE 100 指数)
    在这里插入图片描述
    设用 x t x_t xt 表示 t t t 时刻观察到的指数值,那么一个简单的基于过去数据预测新数据的序列预测问题形如
    x t ∼ P ( x t ∣ x t − 1 , . . . , x 1 ) x_t \sim P(x_t|x_{t-1},...,x_1) xtP(xtxt1,...,x1)

2.1 自回归模型(Autoregressive)

  • 注意到指数预测其实是一个回归任务,主要的问题在于输入样本 x t − 1 , . . . , x 1 x_{t-1},...,x_1 xt1,...,x1 是随 t t t 变化的,输入的数据量随时间增长线性增加,我们需要能处理这种变长输入问题的模型,这时有两种选择
    在这里插入图片描述

    1. 自回归模型autoregressive models:这种方法简单粗暴,既然变长数据不好处理,那么我们可以只考虑其中最近的固定长度的一个片段,从而转换为定长输入。这样的简化有一定的合理性,因为序列中越近邻的样本往往相关性也越大。设考虑的片段长度为 τ \tau τ,则 x t x_t xt 仅由 x t − 1 , . . . , x t − τ x_{t-1},...,x_{t-\tau} xt1,...,xtτ 决定,从数学上讲,我们这样做是假设序列具有 τ \tau τ 阶马尔可夫性(见下文 2.2 节)。由于输入长度固定了,这时理论上我们可以像普通回归任务那样训练一个 MLP 来做回归,注意到模型在对自己的组成部分进行回归,因此它被称为 “自回归模型”。上图(a)显示了考虑前两步历史序列的自回归模型的预测过程
    2. 隐变量自回归模型latent autoregressive models:考虑人类是如何做股价预测的,我们会尝试从过去的数据中提取一些规律,比如股市中 “缠论”、“金叉、死叉、上叉、下叉” 等等技术面指标,然后基于当前走势形态运用这些规律进行预测。从数学角度看,我们用一个隐变量 h t − 1 h_{t-1} ht1 描述截止到 t − 1 t-1 t1 时刻的总结规律。任意时刻 t t t,首先结合当前观测 x t − 1 x_{t-1} xt1 更新总结 h t = g ( h t − 1 , x t − 1 ) h_t=g(h_{t-1}, x_{t-1}) ht=g(ht1,xt1) 然后基于最新的隐变量得到预测结果 x ^ t ∼ P ( x t ∣ h t ) \hat{x}_t\sim P(x_t|h_t) x^tP(xtht)。上图(b)显示了隐变量自回归模型的预测过程,相比普通自回归模型,它允许历史序列长度随着预测进行自然地增长
  • 传统的机器学习任务可以自然地划分样本和标记,而序列预测任务中只有一条历史轨迹,我们还要将其转换为一组 “样本-标记” 二元组才能训练,一个直观且经典的方法就是截取一段历史观测来预测下一个观测,从而组成监督学习训练集。依第1节最后所分析,我们合理地假设序列样本之间服从某种静态(stationary)规律,在这个指数预测问题中,它可以表现为关于任意长度历史数据的条件概率分布 P ( x t ∣ x t − 1 , . . . , x 1 ) P(x_t|x_{t-1},...,x_1) P(xtxt1,...,x1),截取的历史序列 x t − 1 , . . . , x 1 x_{t-1},...,x_1 xt1,...,x1 越长,就越能体现这个规律,这样整个轨迹的概率分布可以表示为
    P ( x 1 , . . . , x T ) = ∏ t = 1 T P ( x t ∣ x t − 1 , . . . , x 1 ) P(x_1,...,x_T) = \prod_{t=1}^T P(x_t|x_{t-1},...,x_1) P(x1,...,xT)=t=1TP(xtxt1,...,x1) 我们构造的所有训练数据,都可以看做这个潜在规律所诱导的变长度历史条件分布 P P P 的采样,我们希望学到它进行预测。从这个角度看

    1. 自回归模型通过裁剪将分布的条件部分变为给定长度,相当于用 P ( x t ∣ x t − 1 , . . . , x t − τ ) P(x_t|x_{t-1},...,x_{t-\tau}) P(xtxt1,...,xtτ) 估计 P ( x t ∣ x t − 1 , . . . , x 1 ) P(x_t|x_{t-1},...,x_1) P(xtxt1,...,x1)
    2. 隐变量自回归模型将分布条件的历史部分,即 x t − 2 , . . . , x 1 x_{t-2},...,x_1 xt2,...,x1 部分归纳到隐变量中,当前样本观测 x t − 1 x_{t-1} xt1 作为输入
  • 注意, P ( x t ∣ x t − 1 , . . . , x 1 ) P(x_t|x_{t-1},...,x_1) P(xtxt1,...,x1) 应基于序列样本离散或连续,使用分类器或回归器进行估计,这个指数预测问题中使用回归

2.2 马尔可夫模型

  • 前面提到,自回归模型使用 P ( x t ∣ x t − 1 , . . . , x t − τ ) P(x_t|x_{t-1},...,x_{t-\tau}) P(xtxt1,...,xtτ) 估计 P ( x t ∣ x t − 1 , . . . , x 1 ) P(x_t|x_{t-1},...,x_1) P(xtxt1,...,x1)如果这种估计是近似精确的,我们就称该序列满足 τ阶马尔可夫条件,即下一个状态仅由前 τ \tau τ 个状态决定。特别是 τ = 1 \tau=1 τ=1 得到的 一阶马尔可夫模型 可以使得计算大大简化,这时我们可以利用动态规划方法直接沿着马尔可夫链进行简单而精确的计算 ,例如计算 P ( x t + 1 ∣ x t − 1 ) P(x_{t+1}|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} P\left(x_{t+1} \mid x_{t-1}\right) &=\frac{\sum_{x_{t}} P\left(x_{t+1}, x_{t}, x_{t-1}\right)}{P\left(x_{t-1}\right)} \\ &=\frac{\sum_{x_{t}} P\left(x_{t+1} \mid x_{t}, x_{t-1}\right) P\left(x_{t}, x_{t-1}\right)}{P\left(x_{t-1}\right)} \\ &=\sum_{x_{t}} P\left(x_{t+1} \mid x_{t}\right) P\left(x_{t} \mid x_{t-1}\right) \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)

  • 通过假设序列服从马尔可夫性条件(生成该序列的模型是一个马尔可夫模型),我们构造的监督学习样本可以只考虑一定长度的历史序列作为样本,在 τ = 1 \tau=1 τ=1 的极限情况下 P ( x t ∣ x t − 1 , . . . , x 1 ) = P ( x t ∣ x t − 1 ) P(x_t|x_{t-1},...,x_1)=P(x_t|x_{t-1}) P(xtxt1,...,x1)=P(xtxt1),只须考虑当前数据

  • 由于马尔可夫性质对于简化序列分布具有重要意义,常常假设序列生成过程具有马尔可夫性,从而得到各种马尔可夫模型

    不考虑动作考虑动作
    状态完全可见 /完全可观测马尔可夫过程(MP)马尔可夫决策过程(MDP)
    状态不完全可见/部分可观测隐马尔可夫模型(HMM)不完全可观测马尔可夫决策过程(POMDP)
    1. MP 可以简单理解为由马尔可夫链采样生成轨迹的随机过程。马尔可夫链是由一阶马尔可夫关系 P ( x t ∣ x t − 1 ) P(x_t|x_{t-1}) P(xtxt1) 在状态 x x x 之间构成的概率图,利用它不但能简单且自然地生成状态序列,甚至还可以从初始状态预测系统未来任意时刻可能处于的状态概率分布
    2. MDP 和 POMDP 引入了激发状态转移的 “动作” 概念,广泛应用于解序列决策问题的强化学习领域。详见 强化学习笔记(3)—— 有限马尔可夫决策过程(finite MDP)
    3. HMM 假设有一个马尔可夫过程和一个观测概率矩阵,先用 MP 生成不可观测的状态序列,再由各个状态基于观测概率矩阵生成一个观测从而产生观测随机序列。关于这个模型的任务比较多,包括概率计算问题(给定模型和观测序列,计算观测序列出现概率)、学习问题(给定观测序列,估计模型参数)、预测问题(给定模型和观测序列,估计状态序列)三类

2.3 因果关系

  • 原则上,将完整序列分布 P ( x 1 , . . . , x T ) P(x_1,...,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 可能受到影响,但反过来则不存在这种影响,这时解释 P ( x t + 1 ∣ x t ) P(x_{t+1}|x_t) P(xt+1xt) 会比解释 P ( x t ∣ x t + 1 ) P(x_t|x_{t+1}) P(xtxt+1) 更容易

  • 因此,当我们展开完整序列分布时,应当考虑问题性质,这决定了我们的模型输入

    1. 有时数据存在一个自然的方向,比如本例这样的时序数据,这时要注意未来的事件不能影响过去,我们的模型应该是根据过去的序列预测未来的。这种时序关系在论文中常称为因果(causal)关系
    2. 有时数据不存在方向,比如文本 “完形填空”,这时模型应当考虑两个方向的序列内容进行预测

3. 传统网络架构难以处理序列数据

  • 即使有了适用于序列数据的统计工具,如果不对网络结构进行改进,还是有很多序列任务难以处理。以简单的自回归模型为例,我们总是使用前 τ \tau τ 个样本值预测下 n n n 个样本值,即用给定序列学习 P ( x t + n − 1 , . . . , x t ∣ x t − 1 , . . . , x t − τ ) P(x_{t+n-1},...,x_t|x_{t-1},...,x_{t-\tau}) P(xt+n1,...,xtxt1,...,xtτ),这样模型确实能学到从连续的 τ \tau τ 个给定序列值到下一个序列样本值的映射,但是其缺陷也很明显
    1. 如果输入模型的都是给定序列数据,那么预测应该是比较准确的。固定 n n n,准确性随 τ \tau τ 增加慢慢上升;固定 τ \tau τ,准确性随 n n n 增加慢慢下降
    2. 关键的问题在于这样的模型没法脱离给定序列的输入进行预测,换句话说,让他 bootstrap 地反复基于自己之前的预测进行预测,这样 “生成” 的序列很快就会崩掉。这是因为每一步预测都会累计微小误差,这些误差会在 bootstrap 过程中不断累计,最终导致 distribution shift。在更高的角度上讲,传统的 MLP 网络架构不适合处理序列数据,因为他只能考察有限长度内的序列信息,而且无法记住之前的序列信息。就像图像处理适合用 CNN 那样,我们需要对序列数据更有归纳偏置(model bias)的网络架构
  • 下面进行实验验证上述想法,以下内容由 jupyter notebook 文档转换而来,部分代码可能无法直接复制运行!

3.1 生成训练序列

  • 使用正弦函数和一些可加噪声来生成长度为 1000 的序列数据,这里正弦函数就是序列背后的 “动力学模型”,决定 “序列样本之间的关系”,可见噪声来模拟数据收集过程中的采样噪声
    %matplotlib inline
    import torch
    import matplotlib.pyplot as plt
    from torch import nn
    from torch.utils import data
    import numpy as np
    
    T = 1000                 # 轨迹总长度 1000
    time = torch.arange(T)   
    x = torch.sin(0.01 * time) + torch.normal(0, 0.2, (T,)) 
    
    fig = plt.figure(figsize=(8,4))
    plt.plot(time, x, linewidth=1, label='data')
    plt.xlabel('time')
    plt.ylabel('x')
    plt.grid(True)
    plt.legend()
    
    在这里插入图片描述

3.2 使用 MLP 建立自回归模型

  • 下面我们尝试用连续的四个样本值预测下一个样本值,即 τ = 4 , n = 1 \tau=4, n=1 τ=4,n=1,训练一个简单的含一个隐藏层的多层感知机进行回归,输入层单元数为 4,隐藏层单元数为 10 使用 Relu 激活函数,输出层单元数为 1,损失使用适于回归任务的 MSE Loss
  • 下面定义模型、训练方法、数据加载方法等必要组件
    def load_array(data_arrays, batch_size, is_train=True):
        dataset = data.TensorDataset(*data_arrays)
        return data.DataLoader(dataset, batch_size, shuffle=is_train)
    
    def evaluate_loss(net, data_iter, loss):
        loss_sum = 0
        data_num = 0
        for X, y in data_iter:
            out = net(X)
            y = y.reshape(out.shape)
            l = loss(out, y)
            loss_sum += l.sum()
            data_num += l.numel()
        return loss_sum / data_num
    
    # 初始化网络权重的函数
    def init_weights(m):
        if type(m) == nn.Linear:
            # 这种参数初始化方式可以维持 relu 激活线性网络在前向过程中的参数方差尽量不变,避免梯度爆炸和梯度消失
            nn.init.kaiming_normal_(m.weight, a=0, mode='fan_in', nonlinearity='relu') 
            
    # 一个简单的多层感知机
    def get_net():
        net = nn.Sequential(nn.Linear(4, 10),
                            nn.ReLU(),
                            nn.Linear(10, 1))
        
        # .apply 方法会先逐层对 Module 的子 Module 应用 init_weights 方法,最后对 Module 自身应用
        # 通过 init_weights 中的限制初始化线性层参数
        net.apply(init_weights) 
        return net
    
    # 平方损失。注意:MSELoss计算平方误差时不带系数1/2
    loss = nn.MSELoss(reduction='none')
    

3.3 进行自回归训练

  • 如 2.1 节自回归模型介绍,基于嵌入维度 τ \tau τ,将给定序列映射为一系列标记 y t = x t y_t=x_t yt=xt 和样本特征 x t = [ x t − τ , . . . , x t − 1 ] x_t=[x_{t-\tau},...,x_{t-1}] xt=[xtτ,...,xt1] 数据对。可见这时标记比样本多出 τ \tau τ 个,因为最初的 τ \tau τ 的标记找不到足够长的序列作为特征,这时我们可以用 0 填充不足的部分(padding),也可以像下面一样直接丢弃这部分
    tau = 4                                # 输入模型的序列长度(嵌入维度),这里设为根据前四个样本值预测下一个
    features = torch.zeros((T - tau, tau)) # 一共 T-tau 个样本,每个样本长 tau
    for i in range(tau):                   # 每列错一位填入,每行就是连续 tau 个样本值,作为特征 
        features[:, i] = x[i: T - tau + i]
    labels = x[tau:].reshape((-1, 1))      # 序列从 tau 索引开始,作为标记
    
    这里的矩阵操作直接看可能有点难懂,其实我们就是想一列一列地组成如下效果(这里数字指序列元素的 index)
    在这里插入图片描述
    然后这里的每一行都能构成一个训练使用的 “特征-标记” 对
  • 直接用 3.2 节定义的方法进行训练 5 个 epoch,batch size 设为 16,这里只用前 600 个 “特征-标记” 对进行训练
    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}, loss: {evaluate_loss(net, train_iter, loss):f}')
    
    batch_size = 16	
    n_train = 600     # 只有前n_train个样本用于训练,这里是索引 ([0,1,2,3],4) 到索引 ([599,600,601,602],603)
    train_iter = load_array((features[:n_train], labels[:n_train]), batch_size, is_train=True)
    
    net = get_net()
    train(net, train_iter, loss, 5, 0.01)
    
    epoch 1, loss: 0.176262
    epoch 2, loss: 0.117714
    epoch 3, loss: 0.082789
    epoch 4, loss: 0.065136
    epoch 5, loss: 0.056665
    

3.4 预测

3.4.1 单步预测

  • 注意到 3.3 节的训练误差很小,我们希望模型能有良好的工作效果。首先检查它预测下一个时间步的能力,这时模型不进行任何 bootstrap 形式的预测
    onestep_preds = net(features)
    
    fig = plt.figure(figsize=(8,4))
    plt.plot(time, x, linewidth=1, label='data')
    plt.plot(time[tau:], onestep_preds.detach().numpy(), linewidth=1, label='1-step preds')
    plt.xlabel('time')
    plt.ylabel('x')
    plt.grid(True)
    plt.legend()
    
    在这里插入图片描述
  • 可见,单步预测效果不错。即使这些预测的时间步超过了用于训练的部分,其结果看起来仍然是可信的,这说明简单的 MLP 网络有能力根据之前的一段序列预测出下一时刻的序列值。但是这样做的问题也很明显,从网络结构看,这个预测只考虑了输入序列片段中所有时刻和被预测时刻的关系,这样的网络结构没有能力学习 “序列样本之间的关系”

    具体而言,这个网络的隐藏层学到了任意 t t t 时刻样本 x t x_t xt 和其前时刻样本 x t − 1 , x t − 2 , . . . , x t − τ x_{t-1}, x_{t-2},...,x_{t-\tau} xt1,xt2,...,xtτ τ \tau τ 个关系,输出层学到了对这些关系的重视比例,从而得到预测结果,而 x t − i x_{t-i} xti x t − j x_{t-j} xtj 之间的关系无法学到

3.4.2 多步预测

  • 考虑更复杂的问题,假设我们只观测到了序列的前 604 个时刻( t = 0 , 1 , 2... , 603 t=0,1,2...,603 t=0,1,2...,603),能否预测出 t > = 604 t>=604 t>=604 时刻的样本值,或者说能否生成出可能的后续序列
  • 对于直到 x t x_t xt 的观测序列,其在 t + k t+k t+k 处的预测输出 x ^ t + k \hat{x}_{t+k} x^t+k 称为 k步预测,这里我们希望预测 x ^ 604 + k \hat{x}_{604+k} x^604+k。理论上我们确实可以反复使用之前训练的单步预测模型得到超出训练时刻的预测结果,这时模型的预测会建立在之前的预测结果之上,如下(注意 hat 上标的位置)
    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{split}\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{split} 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),
  • 让我们看看效果如何
    multistep_preds = torch.zeros(T)
    multistep_preds[: n_train] = x[: n_train] # 填入训练模型使用的原始序列部分
    for i in range(n_train + tau, T):         # 利用在前 n_train 个样本训练的模型,自回归地预测未见序列的标记 
        multistep_preds[i] = net(multistep_preds[i - tau:i].reshape((1, -1)))
        
    fig = plt.figure(figsize=(8,4))
    plt.plot(time, x, linewidth=1, label='data')
    plt.plot(time[tau:], onestep_preds.detach().numpy(), linewidth=1, label='1-step preds')
    plt.plot(time[n_train+tau:], multistep_preds[n_train+tau:].detach().numpy(), linewidth=1, label='multistep preds')
    plt.xlabel('time')
    plt.ylabel('x')
    plt.grid(True)
    plt.legend()
    
    在这里插入图片描述
    可见绿线的预测显然并不理想,预测结果经过几个预测步骤之后很快就会收敛到一个常数。就像本节开头处分析的,这是由于预测误差的累计所导致的,具体而言
    1. 假设步骤 1 的预测后积累了一些误差 ϵ 1 = ϵ ˉ \epsilon_1=\bar{\epsilon} ϵ1=ϵˉ
    2. 预测步骤 2 的输入就被扰动了 ϵ 1 \epsilon_1 ϵ1,这些扰动会被累计到其预测结果的误差中, ϵ 2 = ϵ ˉ + c ϵ 1 \epsilon_2 = \bar{\epsilon}+c\epsilon_1 ϵ2=ϵˉ+cϵ1(这里 c c c 是个常数)
    3. 后续预测情况以此类推,预测误差会快速累计,模型的输入很快会偏移训练时的输入空间,导致 distribution shift
  • 随着k步预测中 k k k 的增加,即基于过去预测进行新预测的步数越多,累计误差会越大,预测结果会越糟糕。下面我们观察随着预测结果是如何随 k k k 增大而崩溃的。我们设 k = 1 , 4 , 16 , 64 k=1,4,16,64 k=1,4,16,64,在整个序列上进行预测
    max_steps = 64
    features = torch.zeros((T - tau - max_steps + 1, tau + max_steps))
    # 列i(i<tau)是来自原始序列 x 的观测,其时间步从(i)到(i+T-tau-max_steps+1)
    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)
    
    这里的矩阵操作直接看可能特有点难懂,其实 features 的成分如下
    在这里插入图片描述
    最左边 0/1/2/3 列就是原始序列截取的部分错开一个时刻,这样每一行取 4 个就是连续 4 时刻的轨迹片段,可以直接输入前面训练的网络,网络输出就是在整个轨迹上的 k = 1 k=1 k=1 步预测,把它放到第 4 列,然后再取 1/2/3/4 列重复以上过程,再取 2/3/4/5 列重复以上过程,不停循环,最后就能得到整个轨迹上的 k = 2 , 3 , . . . , 64 k=2,3,...,64 k=2,3,...,64 步预测了。完成这些后,取其中 k = 1 , 4 , 16 , 64 k=1,4,16,64 k=1,4,16,64 列绘图
    fig = plt.figure(figsize=(8,4))
    steps = (1, 4, 16, 64)
    for step in steps:
        t = time[tau + step - 1: T - max_steps + step]
        pred = features[:, (tau + step - 1)].detach().numpy()
        
        plt.plot(t, pred, linewidth=1, label=f'{step}-step preds')
        plt.xlabel('time')
        plt.ylabel('x')
        plt.grid(True)
        plt.legend()
    
    在这里插入图片描述
    这清楚地体现了当我们试图基于 MLP 进行 Autoregressive,基于 bootstrap 思想预测更远的未来时,预测的质量是如何变化的。虽然 “4步预测” 看起来仍然不错,但超过这个跨度的任何预测几乎都是无用的

3.5 扩展内容

  • 事实上,第 3 节实验的这个模型的思想和统计语言模型中的 N-gram模型 十分类似。“N-gram模型” 中的 N N N 就相当于这里向前看的长度 τ \tau τ,它用于从 “是否符合自然语言” 的角度评估一个句子的质量,也就是利用语料库(历史序列)判读一个句子(新序列)出现的概率高不高,如果概率高就认为句子质量不错,而句子概率就是用这里 P ( x t ∣ x t − 1 , . . . , x t − τ ) P(x_t|x_{t-1},...,x_{t-\tau}) P(xtxt1,...,xtτ) 连乘所得,只是 “N-gram模型” 中的 P ( x t ∣ x t − 1 , . . . , x t − τ ) P(x_t|x_{t-1},...,x_{t-\tau}) P(xtxt1,...,xtτ) 是通过概率统计而非神经网络拟合计算的
  • 另外,这里通过序列中前 τ \tau τ 个样本值预测下一个样本值这个任务和统计语言模型中的 NNLM模型 完全一致,二者的做法也几乎相同,只是 NNLM 还要训练一个词投影层得到序列样本(单词)更好的特征表示,另外由于 NNLM 是做分类任务,所以其损失设计为交叉熵(论文中使用的最大似然等价于最小化交叉熵),并在最后加上 softmax 层以便进行分类
  • “N-gram模型” 和 “NNLM” 都仅适用于概率预测而非轨迹生成,这也能解释 3.4.1 和 3.4.2 节的实验结果

4. 小结

  • 内插法(在现有观测值之间进行估计)和外推法(对超出已知观测范围进行预测)在实践的难度上差别很大。因此,对于你所拥有的序列数据,在训练时始终要尊重其自然顺序,即最好不要基于未见的数据进行训练(如第 3 节的时序序列,不要基于未来的数据进行训练)
  • 序列模型的估计需要专门的统计工具,两种较流行的选择是自回归模型和隐变量自回归模型
  • MLP 等传统的网络架构不适用于序列模型的统计工具,需要新的网络结构
  • 对于时间是向前推进的因果模型,正向估计通常比反向估计更容易(更合理更有解释性)
  • 对于直到时间步 t t t 的观测序列,其在时间步 t + k t+k t+k 的预测输出称为 k步预测随着 k k k 增加,会造成误差的快速累积和预测质量的极速下降
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

云端FFF

所有博文免费阅读,求打赏鼓励~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值