前言
翻了自己的笔记,我是2023年11月份因为想学量化开始了解时序研究,又因为学时序偶然看到了Patch TST这篇文章。于是在11月23号读了论文并记下笔记,后来又做了粗浅的代码解读,当时就感觉时序任务中,Patch未来将像位置编码一样成为Transformer架构不可缺少的一部分。
到今天整一年过去了,重新阅读Patch TST的论文和代码,发现自己以前对patch的理解是比较想当然的。当时有些地方是没有彻底搞明白,大家也可以思考一下,比如:patch TST如何做到在没有修改transformer框架结构的情况下,实现从输入数据点到数据块的转变?我认为这就是patch TST的精髓。还有对于刚入门的同学而言,可以思考时序数据和NLP数据的在进入模型处理时有何异同?数据维度的对应关系是怎么样的?为什么patch TST往通道独立方向走?
我相信这些问题也会给不少刚入门、刚开始读论文的同学不少困惑,所以我打算写一篇新手友好向的总结。由于patch TST以transformer为基础,而Transformer本身也有一定理解门槛,同时,如果把Transformer、LSTM作为一种建模手段,实际上它们的输入维度是一样的。所以我的思路是这样:从LSTM讲起,梳理清楚LSTM数据的维度流转过程、代码实现过程;然后过渡到Transformer,理解Transformer的数据流转和Patch的实现,重点学习Patch本身。
这篇文章主讲理论,下面主讲代码(LSTM/Linear+patch)文章会比较长~~~
RNN误区解释
让我们先从LSTM的结构说起,长久以来,我们学习RNN、LSTM模型都是看的上面这个图。尽管字符用黑体表示,但我认为仍有一定误导性:首先,隐藏层通常不是一个神经元,这是因为我们做预测的时候特征一般不会只有一个!其次,在标准的 LSTM 架构中,不同时间步的隐藏层参数是一样的。这是因为 LSTM 的权重矩阵(包括输入门、遗忘门、输出门和细胞状态更新相关的权重)在整个序列处理过程中是共享的!我在学RNN时候就没有学仔细,上面两点理解都有偏差。
图片来源:知乎master苏
实际上,RNN的立体结构如上图所示,你会发现每个时间步都是有深度的。那么思考这样一个问题:使用LSTM处理文本任务以及时间序列任务,它们的特征维度是如何与图中的模型结构对应?有何区别?
在文本任务中,我们通常有“句子、字/词、词向量”三方面特征,把字或词做词嵌入,得到词向量,所以输入层(input_size)的维度实际就是词向量的维度。同时隐藏层还可以定义层数(num_layers),图中有两个隐藏层。句子的长度就是时间步的长度,图中长度为4。这样随着时间步的推移,逐个字建模。
在时序任务中,以天气数据为例,我们通常有“温度、湿度、气压、风速”等特征,输入层对应的是上述多个特征在不同时间点的值,input_size就等于特征个数fea_size。如果我们把batch考虑进去,那么LSTM输入的维度就可以表示为:[batch_size, seq_len, fea_size],这和Transformer输入的维度是一致的,同时也解释了为什么说Transformer在做预测的时候,用的是时间点。
时序中的Patch
好了,现在我们理解了NLP和时序任务输入的差异,知道了LSTM和Transformer的输入维度都是[batch_size, seq_len, fea_size],以及为什么说它们都是以时间点为输入。现在我们重点从代码的角度理解patch操作的全流程,同时讨论为什么patch 后,模型结构没有变,但却以时间块为输入?
step1 生成数据
我们构造一个[batch_size, seq_len, fea_size]维度分别为[2,6,4]的tensor,每个batch中的数据和csv表格是一样的,四列六行,表示有四个特征,每个特征6个时间步长。
import numpy as np
import torchx = torch.randn(2,6,4) #[batch_size, seq_len, fea_size]
x
step2 做patch切分
论文中使用unfold函数,对每个特征的按照块大小为2,步长为2进行切分,同时指定切分最后一个维度。这时,我们发现原本三维的tensor变为了四维,即[batch_size, fea_size, patch_num, patch_len]
x = x.permute(0, 2, 1) #[batch_size, fea_size, seq_len]
x = x.unfold(dimension=-1,size=2, step = 2)
print(x.shape)
x #[batch_size, fea_size, patch_num, patch_len]
step3 维度调整
经过上一步骤,我们已经把原始数据集做了patch切分,但是数据维度变成了四维,四维数据显然无法放入到LSTM或Transformer中的,那么这里就要进行维度的调整和合并。patch TST中,作者是把batch_size和fea_size进行了合并。具体怎么做的看如下的代码:
x = torch.reshape(x, (x.shape[0]*x.shape[1],x.shape[2],x.shape[3]))
x.shape #[(batch_size*fea_size),patch_num,patch_len]
原文中作者还对patch_len进行线性变换,使其长度由patch_len增加到d_model,不过对我们理解维度变换没有影响。
现在回到开头我们看发生了什么,处理之前我们输入到LSTM/Transformer的特征维度是
[batch_size, seq_len, fea_size]
。具体到每一个时间步,前面我们说过,是把每个特征在当前时刻的数值输入到模型,所以从时间维度来说,是对时间点进行建模。而经过上面处理后,特征维度变为了
[(batch_size*fea_size),patch_num,patch_len]
,
我们观察到此时batch_size和fea_size已经合并,而且输入到模型的真实数据变为patch_len,也就是数据块。所以说patchTST在未改变模型架构的前提下,实现了以上功能,同时由于切块输入的原因,计算量大大减少。
如何实现LSTM+Patch
有了上面的基础,我们不妨自己先在LSTM模型尝试添加Patch,看看效果如何。下篇基础知识点讲解来分享代码~
大家可以关注我【科学最top】,第一时间follow时序高水平论文解读!!!