序列样本与标记的关系
感兴趣,更对 序列样本之间的关系
感兴趣。事实上,我们常常要借助后者分析和处理前者随机过程
定义为一族与时间相关的随机变量,而序列恰好可以看做一堆相互关联的随机变量,因此序列都可以看做某种随机过程的轨道,从这个角度看,随机过程就能描述序列背后的 “动力学关系”(样本之间的关系)。但是实际应用中,我们通常不直接尝试学习随机过程,因为对序列服从的随机过程类型进行假设有时是很困难的,通常的做法还是尝试直接拟合序列中每个样本标记的分布,这样就和传统的监督学习差不多了,只是输入模型的样本除了被预测的样本外,还要包含序列中的其他相关样本。另外,有些过程比如马尔可夫过程可以这些分布的数学形式进行简化,因此有时也会假设序列服从这些特殊过程新的统计工具
和新的深度神经网络架构
,下面第 2 节介绍适用于序列建模的统计工具,第 3 节通过实验说明用传统网络架构处理序列数据的问题注意到指数预测其实是一个回归任务,主要的问题在于输入样本 x t − 1 , . . . , x 1 x_{t-1},...,x_1 xt−1,...,x1 是随 t t t 变化的,输入的数据量随时间增长线性增加,我们需要能处理这种变长输入问题的模型,这时有两种选择
自回归模型autoregressive models
:这种方法简单粗暴,既然变长数据不好处理,那么我们可以只考虑其中最近的固定长度的一个片段,从而转换为定长输入。这样的简化有一定的合理性,因为序列中越近邻的样本往往相关性也越大。设考虑的片段长度为 τ \tau τ,则 x t x_t xt 仅由 x t − 1 , . . . , x t − τ x_{t-1},...,x_{t-\tau} xt−1,...,xt−τ 决定,从数学上讲,我们这样做是假设序列具有 τ \tau τ 阶马尔可夫性(见下文 2.2 节)。由于输入长度固定了,这时理论上我们可以像普通回归任务那样训练一个 MLP 来做回归,注意到模型在对自己的组成部分进行回归,因此它被称为 “自回归模型”隐变量自回归模型latent autoregressive models
:考虑人类是如何做股价预测的,我们会尝试从过去的数据中提取一些规律,比如股市中 “缠论”、“金叉、死叉、上叉、下叉” 等等技术面指标,然后基于当前走势形态运用这些规律进行预测。从数学角度看,我们用一个隐变量 h t − 1 h_{t-1} ht−1 描述截止到 t − 1 t-1 t−1 时刻的总结规律,首先结合当前观测 x t − 1 x_{t-1} xt−1 更新总结 h t = g ( h t − 1 , x t − 1 ) h_t=g(h_{t-1}, x_{t-1}) ht=g(ht−1,xt−1) 然后基于最新的隐变量得到预测结果 x ^ t ∼ P ( x t ∣ h t ) \hat{x}_t\sim P(x_t|h_t) x^t∼P(xt∣ht) 这个方法的结构图如下传统的机器学习任务可以自然地划分样本和标记,而序列预测任务中只有一条历史轨迹,我们还要将其转换为一组 “样本-标记” 二元组才能训练,一个直观且经典的方法就是截取一段历史观测来预测下一个观测,从而组成监督学习训练集。依第1节最后所分析,我们合理地假设序列样本之间服从某种静态(stationary)规律,在这个指数预测问题中,它可以表现为关于任意长度历史数据的条件概率分布 P ( x t ∣ x t − 1 , . . . , x 1 ) P(x_t|x_{t-1},...,x_1) P(xt∣xt−1,...,x1),截取的历史序列 x t − 1 , . . . , x 1 x_{t-1},...,x_1 xt−1,...,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=1∏TP(xt∣xt−1,...,x1) 我们构造的所有训练数据,都可以看做这个潜在规律所诱导的变长度历史条件分布 P P P 的采样,我们希望学到它进行预测。从这个角度看
注意, P ( x t ∣ x t − 1 , . . . , x 1 ) P(x_t|x_{t-1},...,x_1) P(xt∣xt−1,...,x1) 应基于序列样本离散或连续,使用分类器或回归器进行估计,这个指数预测问题中使用回归
前面提到,自回归模型使用 P ( x t ∣ x t − 1 , . . . , x t − τ ) P(x_t|x_{t-1},...,x_{t-\tau}) P(xt∣xt−1,...,xt−τ) 估计 P ( x t ∣ x t − 1 , . . . , x 1 ) P(x_t|x_{t-1},...,x_1) P(xt∣xt−1,...,x1),如果这种估计是近似精确的,我们就称该序列满足 τ阶马尔可夫条件
,即下一个状态仅由前 τ \tau τ 个状态决定。特别是 τ = 1 \tau=1 τ=1 得到的 一阶马尔可夫模型
可以使得计算大大简化,这时我们可以利用动态规划方法直接沿着马尔可夫链进行简单而精确的计算 ,例如计算 P ( x t + 1 ∣ x t − 1 ) P(x_{t+1}|x_{t-1}) P(xt+1∣xt−1)
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+1∣xt−1)=P(xt−1)∑xtP(xt+1,xt,xt−1)=P(xt−1)∑xtP(xt+1∣xt,xt−1)P(xt,xt−1)=xt∑P(xt+1∣xt)P(xt∣xt−1)
通过假设序列服从马尔可夫性条件(生成该序列的模型是一个马尔可夫模型),我们构造的监督学习样本可以只考虑一定长度的历史序列作为样本,在 τ = 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(xt∣xt−1,...,x1)=P(xt∣xt−1),只须考虑当前数据
由于马尔可夫性质对于简化序列分布具有重要意义,常常假设序列生成过程具有马尔可夫性,从而得到各种马尔可夫模型
不考虑动作 | 考虑动作 | |
---|---|---|
状态完全可见 /完全可观测 | 马尔可夫过程(MP) | 马尔可夫决策过程(MDP) |
状态不完全可见/部分可观测 | 隐马尔可夫模型(HMM) | 不完全可观测马尔可夫决策过程(POMDP) |
原则上,将完整序列分布 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=T∏1P(xt∣xt+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+1∣xt) 会比解释 P ( x t ∣ x t + 1 ) P(x_t|x_{t+1}) P(xt∣xt+1) 更容易
因此,当我们展开完整序列分布时,应当考虑问题性质,这决定了我们的模型输入
%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()
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')
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)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
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()
具体而言,这个网络的隐藏层学到了任意 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} xt−1,xt−2,...,xt−τ 的 τ \tau τ 个关系,输出层学到了对这些关系的重视比例,从而得到预测结果,而如 x t − i x_{t-i} xt−i 和 x t − j x_{t-j} xt−j 之间的关系无法学到
k步预测
,这里我们希望预测 x ^ 604 + k \hat{x}_{604+k} x^604+k。理论上我们确实可以反复使用之前训练的单步预测模型得到超出训练时刻的预测结果,这时模型的预测会建立在之前的预测结果之上,如下(注意 hat 上标的位置)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()
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)
这里的矩阵操作直接看可能特有点难懂,其实 features
的成分如下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()
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(xt∣xt−1,...,xt−τ) 连乘所得,只是 “N-gram模型” 中的 P ( x t ∣ x t − 1 , . . . , x t − τ ) P(x_t|x_{t-1},...,x_{t-\tau}) P(xt∣xt−1,...,xt−τ) 是通过概率统计而非神经网络拟合计算的
- 关于 N-gram 模型,可以参考 自然语言处理NLP中的N-gram模型
NNLM模型
完全一致,二者的做法也几乎相同,只是 NNLM 还要训练一个词投影层得到序列样本(单词)更好的特征表示,另外由于 NNLM 是做分类任务,所以其损失设计为交叉熵(论文中使用的最大似然等价于最小化交叉熵),并在最后加上 softmax 层以便进行分类
- 关于 NNLM 模型,可以参考 语言模型(二)—— 神经网络语言模型(NNLM)
- 关于为什么最大似然等价于最小化交叉熵,可以参考 信息论概念详细梳理:信息量、信息熵、条件熵、互信息、交叉熵、KL散度、JS散度 4.1 节
k步预测
。随着 k k k 增加,会造成误差的快速累积和预测质量的极速下降