长时间序列预测算法---Informer
目录
- 一、传统的 Transformer 模型
- 二、Informer原理
- 2.1 Attention计算
- 2.2 “积极”的Q筛选
- 2.2.1 KL散度
- 2.2.2 “懒惰”的q处理
- 2.3 Encoder结构
- 2.4 Decoder结构
- 2.4.1 Transformer的Decoder操作
- 2.4.2 Informer的Decoder操作
- 2.5 Informer模型的改进
- 三、模型应用
时间序列相关参考文章:
时间序列预测算法—ARIMA
时间序列预测算法—Prophet
时间序列预测算法—LSTM
长时间序列预测算法—Informer
时间序列分类任务—tsfresh
有季节效应的非平稳序列分析
python时间序列处理
时间序列异常值检测方法
时间序列异常值处理方法
Informer 模型是近年来在时间序列预测领域中比较新颖的一种模型,它基于 Transformer 架构,专门为长时间序列预测任务进行了优化。LSTM模型应用长序列时间预测问题性能不佳的原因:LSTM是串行网络,难以并行化,序列越长反向传播的速度越慢,从而预测速度越慢。
Informer论文
Github原代码库
一、传统的 Transformer 模型
老牌统计学模型:ARIMA和Prophet;传统深度学习模型:LSTM。相关文章可以参考以上给出的链接。
Transformer 是一种深度学习模型,最初应用于自然语言处理(NLP)任务,尤其是在机器翻译、文本生成等任务中表现出色。它的核心思想是通过自注意力机制(Self-Attention) 来捕捉序列中不同位置之间的关系。然而,Transformer 在处理长时间序列数据时会遇到一些困难。因为 Transformer 的计算复杂度是随着序列长度的平方增长的,这在处理非常长的时间序列时会变得非常慢,甚至无法处理。
传统Transformer 模型详解
二、Informer原理
2.1 Attention计算
Transformer计算QKV时,从QK点积的热力图看出,越亮部分表示QK相关性越高。热力图中大部分为黑色,实验发现对于每个Q只有一小部分的K和它有较强的关系。就下图来看,8000个样本,相关性较高的可能只有2000个不到。大部分时候QK的关系都接近于0。
下图纵坐标为Q,横坐标为K。每一行即为一个Q与所有K相关性的结果。红色部分就是一个“积极”的Q,从图中看出它和哪个K相关性较高。绿色部分是一个“懒惰”的Q,它和所有的K关系都很“一般”。
在实际计算中,这些“懒惰”的Q不仅无法提供有效的价值,而且在Q里大部分都是这些“懒惰”的家伙。只选取“积极”的Q来计算注意力机制,而舍弃掉“懒惰”的Q的想法随之诞生。这就是Informer论文的核心:ProbSparse Attention(概率稀疏注意力机制)。
2.2 “积极”的Q筛选
ProbSparse Self-attention 采取了和均匀分布相比较的思路。均匀分布就像上图中的虚线部分,是一条直线。对于每个Q计算它和均匀分布的差异,差异越大就越“活跃”。
2.2.1 KL散度
注意力的概率分布:
p
(
k
j
∣
q
i
)
=
k
(
q
i
,
k
j
)
∑
l
k
(
q
i
,
k
l
)
p(k_{j}|q_{i})=\frac{k(q_{i},k_{j})}{\sum_{l}k(q_{i},k_{l})}
p(kj∣qi)=∑lk(qi,kl)k(qi,kj)
均匀分布:
q
(
k
j
∣
q
i
)
=
1
L
k
q(k_{j}|q_{i})=\frac{1}{L_{k}}
q(kj∣qi)=Lk1
这里
k
(
q
i
,
k
l
)
k(q_{i},k_{l})
k(qi,kl)选用的计算方式是
e
q
i
k
l
T
d
e^{\frac{q_{i}k_{l}^T}{\sqrt{d}}}
edqiklT,
L
k
L_k
Lk 就是query查询向量的长度。度量两种分布的距离—KL散度(描述两个概率分布P和Q差异的一种方法,是非对称的)
离散的KL散度定义:
D
(
P
∣
∣
Q
)
=
∑
i
∈
x
P
(
i
)
∗
[
l
o
g
(
P
(
i
)
Q
(
i
)
)
]
D(P||Q)=\sum_{i\in{x}}P(i)*[log(\frac{P(i)}{Q(i)})]
D(P∣∣Q)=∑i∈xP(i)∗[log(Q(i)P(i))]
连续的KL散度定义:
D
(
P
∣
∣
Q
)
=
∫
x
P
(
x
)
∗
[
l
o
g
(
P
(
i
)
Q
(
i
)
)
]
d
x
D(P||Q)=\int_{x}P(x)*[log(\frac{P(i)}{Q(i)})]dx
D(P∣∣Q)=∫xP(x)∗[log(Q(i)P(i))]dx
将上面的两种分布带入KL散度的计算公式,舍弃常数项,最终定义第i个query的稀疏性度量如下:
M
(
q
i
,
K
)
=
l
n
∑
j
=
1
L
k
e
x
p
q
i
k
j
T
d
−
1
L
k
∗
∑
j
=
1
L
k
q
i
k
j
T
d
M(q_{i},K)={ln\sum_{j=1}^{L_{k}}exp\frac{q_{i}k_{j}^T}{\sqrt{d}}}-\frac{1}{L_{k}}*\sum_{j=1}^{L_{k}}\frac{q_{i}k_{j}^T}{\sqrt{d}}
M(qi,K)=lnj=1∑LkexpdqikjT−Lk1∗j=1∑LkdqikjT
公式中第一项是
q
i
q_{i}
qi和所有k内积的Log-Sum-Exp(LSE),第二项是算术平均数,散度越大,概率分布越多样化,越可能存在关注重点。
上述公式会有两个问题:点积对的计算复杂度是
O
(
L
Q
∗
L
K
)
O(L_{Q}*L_{K})
O(LQ∗LK);LSE计算存在数值不稳定的风险,因为
e
x
e^x
ex形式下,可能会数据溢出报错。
为了解决这两个问题,作者采用了以下的方式:
● 随机采样:随机选择其中的top u (
U
=
L
Q
l
n
L
K
U = L_Q ln L_K
U=LQlnLK ) 个点积对计算
M
ˉ
(
q
i
,
K
)
\bar M(q_i,K)
Mˉ(qi,K) 。这样使复杂度降低到
O
(
L
l
n
L
)
O(LlnL)
O(LlnL)。(代码的默认参数中U=25)
● 用
m
a
x
(
q
i
k
j
T
d
)
max(\frac{q_{i}k_{j}^T}{\sqrt{d}})
max(dqikjT)替换
l
n
∑
j
=
1
L
k
e
x
p
q
i
k
j
T
d
ln\sum_{j=1}^{L_{k}}exp\frac{q_{i}k_{j}^T}{\sqrt{d}}
ln∑j=1LkexpdqikjT 。直接选择最大值与均匀分布算差异可以进一步加速计算过程。
由此第i个query的“稀疏性度量”表达式改写为:
M
ˉ
(
q
i
,
K
)
=
m
a
x
(
q
i
k
j
T
d
)
−
1
L
k
∗
∑
j
=
1
L
k
q
i
k
j
T
d
\bar M(q_{i},K)={max(\frac{q_{i}k_{j}^T}{\sqrt{d}}})-\frac{1}{L_{k}}*\sum_{j=1}^{L_{k}}\frac{q_{i}k_{j}^T}{\sqrt{d}}
Mˉ(qi,K)=max(dqikjT)−Lk1∗j=1∑LkdqikjT
这样可以得到每个q的
M
ˉ
\bar M
Mˉ得分,得分越大这个q越“积极”。然后,在全部的q里选择
M
ˉ
\bar M
Mˉ得分较大的前U个,定义为“积极”的q。来进行QKV矩阵乘法的计算。(U取值根据实际情况来定,原论文中序列长度为96,作者定义U=25,即选择得分较大的25个Q)
思考:
● 为了求解这个度量得分
M
ˉ
\bar M
Mˉ,还是要计算全部的QK点积,这样难道不是更“复杂”了吗?并没有减少计算量或者加快计算速度。
● 而且只计算“积极”的Q,“懒惰”的q就完全抛弃吗?矩阵的形状不就发生了变化吗?这会影响后续的计算吗?
答:度量得分
M
ˉ
\bar M
Mˉ 的计算,只是想用这个得分去筛选“积极”的q,用所有的k参与计算,计算量确实太大。实际上并没有计算全部的QK点积,而是进行了一个抽样。 将每个“积极”的Q和所有k(原论文中是96个k)计算,但在论文源码的实践中,在计算前会随机选取一部分k(原论文中是25个k)来计算,也可以作为它的分布。
2.2.2 “懒惰”的q处理
ProbSparse Self-attention根据上述的内容,允许每个k仅关注U个“积极”的q来获得ProbSparse自注意力:
A
(
Q
,
K
,
V
)
=
S
o
f
t
m
a
x
(
Q
ˉ
K
T
d
)
V
A(Q,K,V)=Softmax(\frac{\bar{Q} K^T}{\sqrt{d}})V
A(Q,K,V)=Softmax(dQˉKT)V
这里的
Q
ˉ
\bar{Q}
Qˉ就是top u个queries选拔后的。对于剩余的“懒惰”的q,作者采取的办法是,用V向量的平均来代替剩余的 Lazy queries 对应的时间点的向量。
通过判定得到的 Lazy queries 的概率分布本来就接近均匀向量,也就是说这个时间点对96个时间点的注意力是均衡的,每个都差不多,所以他们atteniton计算后的向量也应当接近于全部V向量的平均。通过这样的“填充”我们最后的矩阵Z依旧是96*64的维度,但因为是在计算完“积极”Q和K点积后再“填充,计算的时间和空间复杂度都大大降低了。
在理解公式的基础上再梳理一下源码的实现步骤:
(1)输入序列长为96,在K中进行随机采样,随机选取25个K来进行后续计算。
(2)选择“积极”的Q进行计算。 (正常情况下如上推导,将每个“积极”的Q和96个k计算,但在论文源码的实践中,不需要计算那么多,用我们上一步随机选取的25个k来计算,也可以作为它的分布)
(3)每个Q有25个得分,选择25个得分里最大的作为这个q的最终得分
M
ˉ
(
q
i
,
K
)
\bar M(q_{i},K)
Mˉ(qi,K),在96个Q中选择最终得分
M
ˉ
\bar M
Mˉ最大的25个q。
(4)计算这25个q的QK内积,其余位置直接用V的均值来代替,得到最终的矩阵Z。
2.3 Encoder结构
论文中提出了一种新的EncoderStack结构,由多个Encoder和蒸馏层组合而成。左边绿色的部分,是Encoder的输入。由上面深绿色的scalar和下面浅绿色的stamp组成。
- 深绿色的scalar就相当于我们之前Transformer里的input-embedding 是我们原始输入的向量。
- 浅绿色的stamp包含之前Transformer里的 Positional Ecoding(位置编码)来表示输入序列的相对位置。在时序预测任务中,这个stamp其实由LocalTimeStamp(也就是位置编码)和GobalTimeStamp(与时序相关的编码)共同构成。
Encoder的作用是Self-attention Distilling,由于ProbSparse自相关机制有很多都是用V的mean填充的,所以天然就存在冗余的attention sorce ,因此在相邻的Attention Block之间应用卷积与池化来对特征进行下采样,所以作者在设计Encoder时,采用蒸馏的操作不断抽取重点特征,从而得到值得重点关注的特征图。从图中看到每个Encoder有3个Attention Block;每个Attention Block内有n个头权重矩阵。
Informer的架构图并没有像Transformer一样在Encoder的左边标注“
N
x
N_x
Nx ”来表示N个Encoder的堆叠,而是一大一小两个梯形。
作者为了提高encoder鲁棒性,还提出了一个strick。上面的Encoder是主stack,输入整个embedding后经过了三个Attention Block,最终得到Feature Map。还可以再复制一份具有一半输入的embedding(直接取96个时间点的后48个),让它经过两个Attention Block,最终会得到和上面维度相同的Feature Map,然后把两个Feature Map拼接。作者认为这种方式对短周期的数据可能更有效一些。
2.4 Decoder结构
2.4.1 Transformer的Decoder操作
由1.1的transformer架构图可以得出:Decoder输出后经过Linear+Softmax,Linear将输出扩展到与vocabulary size一样的维度,再经过softmax,就可以选择最高概率的词作为预测结果。
梳理一下训练好模型执行预测的过程:
Decoder:Encoder对embedding操作后的KV+开始符号=预测结果i
Decoder:Encoder对embedding操作后的KV+“i”=预测结果am
Decoder:Encoder对embedding操作后的KV+“i am”=预测结果a
Decoder:Encoder对embedding操作后的KV+“i am a”=预测结果student
Decoder:Encoder对embedding操作后的KV+“i am a student”=预测结果 结尾符号
不难看出Decoder每一步的输出都需要前一步的结果作为输入才可以,这就是step-by-step动态解码的过程。
2.4.2 Informer的Decoder操作
Informer的Decoder由2层相同的多头Attention堆叠而成。Decoder的输入如下:
X
d
e
=
{
X
t
o
k
e
n
,
X
0
}
X_{de}=\{X_{token},X_0\}
Xde={Xtoken,X0}
输入序列是由两部分拼接而来的,
X
t
o
k
e
n
∈
R
L
t
o
k
e
n
∗
d
m
o
d
e
l
X_{token} \in R^{L_{token}*d_{model}}
Xtoken∈RLtoken∗dmodel是开始的token,
X
0
∈
R
L
y
∗
d
m
o
d
e
l
X_{0} \in R^{L_{y}*d_{model}}
X0∈RLy∗dmodel是用0填充预测序列。假设如果我们想要预测7天的温度,decoder就需要输入前面1-7天的温度,后面用0填充8-14天需要预测的温度的位置,这就是一种Mask的机制。在源码中Decoder输入长度为72,其中前48是真实值,后24是预测值。
第一步是加上mask后做自身的ProbAttention
第二步是自身计算完Attention后与encoder计算Attention
Decoder输入时,将所有需要预测的部分都用0来填充作为掩码。然后,增加掩码Mask后的embedding经过了ProbSparse-Attention,需要注意的一个细节是这里筛选“积极”的q之后,对于“懒惰”的q的填充方式,不同于encoder部分用V的均值(mean)填充,而是用 Cumsum,也就是每一个queries之前所有时间点V向量的累加,来防止模型关注未来的信息。得到来自Encoder的KV和Decoder第一步attention的Q之后,进行的是传统Transformer里的“Multi-head Attention”计算。在结构图中也可以看到这部分是一个“矩形”。
Generative Style Decoder,简而言之,在Decoder部分的改进就是不同于Transformer原始架构的“一个个”预测,而是“一块块”预测。通过上图我们可以直观看出,一个forward就可以得到所有的24个预测结果。
2.5 Informer模型的改进
Motivation (Transformer的不足) | Contribution (Informer的改进) |
---|---|
self-attention平方级的计算复杂度 | 提出ProbSparse Self-attention筛选出重要的query,降低计算的复杂度 |
堆叠多层网络,内存占用遇到瓶颈 | 提出Self-attention Distilling 减少维度和网络参数 |
step-by-step解码预测,速度较慢 | 提出Generative Style Decoder,一步得到所有预测结果 |
Informer解决Transformer 难以直接应用于长时间序列预测的三个重要问题;针对Transformer的三点不足,Informer分别在Attention的计算方式,Encoder的蒸馏堆叠和Decoder的生成式预测做出了改进,使得它更适用于长序列的时间序列预测问题。
三、模型应用
Github原代码库
main_informer.py主程序。帮助定义informer所需的所有参数、并执行实际的训练和预测流程。main_informer.py里的全部参数如下:
参数名称 | 参数类型 | 参数讲解 |
---|---|---|
model | str | 这是一个用于实验的参数设置,其中包含了三个选项: informer, informerstack, informerlight根据实验需求,可以选择其中之一来进行实验,默认是使用informer模型。 |
data | str | 数据,这个并不是数据集文件,而是你想要用官方定义的方法还是你自己的数据集进行定义数据加载器,如果是自己的数据集就输入custom(这也是这个开源项目做的比较好的地方,项目本身可以选择用自己的数据集) |
root_path | str | 这个是文件的路径,不要到具体的文件,到目录级别即可。 |
data_path | str | 这个填写文件的名称。 |
features | str | 这个是特征有三个选项M,MS,S。分别是多元预测多元,多元预测单元,单元预测单元。 |
target | str | 这个是数据集中想要预测那一列数据,假设预测的是油温OT列就输入OT即可。 |
freq | str | 时间的间隔,数据集每一条数据之间的时间间隔。 |
checkpoints | str | 训练出来的模型保存路径 |
seq_len | int | 用过去的多少条数据来预测未来的数据 |
label_len | int | 可以裂解为更高的权重占比的部分,要小于seq_len |
pred_len | int | 预测未来多少个时间点的数据 |
enc_in | int | 数据有多少列,要减去时间那一列 |
dec_in | int | 数据有多少列,要减去时间那一列 |
c_out | int | 如果features填写的是M那么和上面就一样,是数据列数,如果填写的MS那么这里要输入1因为你的输出只有一列数据。 |
d_model | int | 用于设置模型的维度,默认值为512。可以根据需要调整该参数的数值来改变模型的维度 |
n_heads | int | 用于设置模型中的注意力头数。默认值为8,表示模型会使用8个注意力头.(有时也会用数据有多少列作为头数,可以根据自己实际数据集情况设定) |
e_layers | int | 用于设置编码器的层数 |
d_layers | int | 用于设置解码器的层数 |
s_layers | str | 用于设置堆叠编码器的层数 |
d_ff | int | 模型中全连接网络(FCN)的维度,默认值为2048 |
factor | in | ProbSparse自注意力中的因子,默认值为5 |
padding | int | 填充类型,默认值为0,如果不够数据就填写0. |
distil | bool | 是否在编码器中使用蒸馏操作。使用–distil参数表示不使用蒸馏操作,默认为True也是我们的论文中比较重要的一个改进。 |
dropout | float | 丢弃的概率,防止过拟合 |
attn | str | 编码器中使用的注意力类型,默认为"prob"论文的主要改进点,提出的注意力机制。 |
embed | str | 时间特征的编码方式,默认为"timeF" |
activation | str | 激活函数 |
output_attention | bool | 是否在编码器中输出注意力,默认为False |
do_predict | bool | 是否进行预测 |
mix | bool | 在生成式解码器中是否使用混合注意力,默认为True |
cols | str | 从数据文件中选择特定的列作为输入特征,不常用 |
num_workers | int | 线程(windows最好设置成0否则会报线程错误,linux系统随便设置) |
itr | int | 实验运行的次数,默认为2 |
train_epochs | int | 训练的次数 |
batch_size | int | 一次往模型力输入多少条数据 |
patience | int | 早停机制,如果损失多少个epochs没有改变就停止训练 |
learning_rate | float | 学习率 |
des | str | 实验描述,默认为"test" |
loss | str | 损失函数,默认为"mse" |
lradj | str | 学习率的调整方式,默认为"type1" |
use_amp | bool | 混合精度训练, |
inverse | bool | 是否将归一化后的数据转换为原始值,这里默认为False,如果你想要转换为原来的数据改成True。 |
use_gpu | bool | 是否使用GPU训练,根据自身来选择 |
gpu | int | GPU的编号 |
use_multi_gpu | bool | 是否使用多个GPU训练。 |
devices | str | GPU的编号 |
data:包含一切和数据有关,存放数据集和数据集加载相关代码,核心代码为data_loader.py,既包括处理数据的代码,也包括导入数据的代码。
exp:实验相关,数据加载,模型训练、测试、预测的过程代码,包括了定义损失函数、定义网络结构、进行训练等神经网络的基本流程。
models:Informer模型相关内容,包含词向量编码(embed.py)、注意力机制(attn.py)、encoder编码器(encoder.py)、decoder(decoder.py)解码器和模型定义(model.py)几部分。
utils:与项目相关的辅助代码,这里包含掩码相关(masking.py)、算法衡量指标(metrics.py)、时间特征(timefreatures.py)和其它工具(tools.py)几部分。