前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >对patch深入理解下篇:Patch+LSTM实现以及改进策略整理

对patch深入理解下篇:Patch+LSTM实现以及改进策略整理

作者头像
科学最Top
发布2025-03-11 21:10:50
发布2025-03-11 21:10:50
20600
代码可运行
举报
文章被收录于专栏:科学最Top科学最Top
运行总次数:0
代码可运行

我在去年11月份写了patch深入理解的上篇,主要介绍patch的原理和代码实现过程。文章发布后很多朋友催更下篇,其实一直在积累素材,因为介绍完原理和实现之后,下一步肯定是要考虑如何改进。在这之前,首先,我们接着上一篇的内容,实现了一个LSTM+patch的例子,结果表明加上Patch之后确实对LSTM在各指标和预测长度上均有明显的效果提升。然后,本篇文章还重点介绍了最近一段时间我读到的对patch的几种改进策略,以及自己的一些思考。

这里再挖个坑吧,下一篇我计划写一下如何在LSTM+Patch的基础上,通过傅立叶进行降噪,并探索实现动态切分patch的方法。欢迎关注~

实现LSTM+Patch

我的这份代码是在Are Transformers Effective for Time Series Forecasting? (AAAI 2023)这篇文章代码的基础上写的。主要在model文件夹添加了LSTM和patch_LSTM两个类,大家复制下面的代码,把文件放入到model文件夹即可运行。此外,为了简化,没有做patch切分时候的补齐。包括hidden_size等一些参数也是直接写到了模型里面,所以严格来说,代码并不是很规范,以实现效果为主。

01 LSTM 代码实现

下面是原始LSTM模型的代码实现,实现过程已经重复过多次,直接看代码,就不在展开细致讲解。

代码语言:javascript
代码运行次数:0
运行
复制
class Model(nn.Module):
    """
    Just one Linear layer
    """
    def __init__(self, configs):
        super(Model, self).__init__()
        self.seq_len = configs.seq_len
        self.pred_len = configs.pred_len
        self.hidden_size = 64
        self.num_layers = 2
        self.enc_in = configs.enc_in
        self.lstm = nn.LSTM(self.enc_in, self.hidden_size, self.num_layers, batch_first=True)
        self.fc = nn.Linear(self.hidden_size, self.pred_len)
        self.fc2 = nn.Linear(self.seq_len, self.enc_in)
    def forward(self, x):
        # x: [Batch, Input length, Channel]
        h0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size).to(x.device)  # 初始化隐藏状态h0
        c0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size).to(x.device) # 初始化记忆状态c0
        out, _ = self.lstm(x, (h0, c0)) # out:[bs,seq,hid]
        out = self.fc(out)  # out:[bs,seq,pred_len]
        out = self.fc2(out.permute(0, 2, 1)) # out: [bs, pred_len, channel]
        return out  # [Batch, Output length, Channel]
代码语言:javascript
代码运行次数:0
运行
复制

02 LSTM+patch

下面是代码实现的LSTM+patch的实现过程。这里我设置了步长为12、切分长度也为12,这样相当于没有重复的切分。此外,为了简化很多超参数也没有特意调参寻优,我们还是把重点放到patch实现上,特别要关注数据维度的变化:

  • 进入forward函数时x的维度: [Batch, seq_len, Channel]
  • 置换后两个维度,并用unfold函数切分:[bs, ch, sql]=>[bs, ch, pum, plen],pnum是切分后块的数量。
  • 之前讲过,放回到LSTM、Transformer时,数据还是要变回三维,因此这里我们把batch和channel合并到一起,数据维度变成了:[(bs*ch), pnum, plen]
  • 此时已经可以放入到模型建模,LSTM输出结果的维度是:[(bs*ch), plen, hidden],然后我们通过线性层和reshape操作,把维度调整回[(bs,pred_len, ch]

代码如下,其实就是维度拆分、合并。另外欢迎大家纠错

代码语言:javascript
代码运行次数:0
运行
复制
class Model(nn.Module):
    """
    Just one Linear layer
    """
    def __init__(self, configs):
        super(Model, self).__init__()
        self.seq_len = configs.seq_len #336
        self.pred_len = configs.pred_len
        self.hidden_size = 64
        self.num_layers = 2
        self.enc_in = configs.enc_in
        #patch
        self.plen = 12
        self.pnum = 28 # seq_len/plen
        # LSTM 这里 self.enc_in => self.pnum
        self.lstm = nn.LSTM(self.pnum, self.hidden_size, self.num_layers, batch_first=True)
        self.fc = nn.Linear(self.hidden_size, self.pnum)
        self.fc2 = nn.Linear(self.seq_len, self.pred_len)
        # self.fc3 = nn.Linear(self.seq_len, self.enc_in)
    def forward(self, x): # x: [Batch, seq_len, Channel]
        x_shape = x.size()
        # patching
        if x.size(1) == self.seq_len: # 这里为了简单,就直接定义patch的切分步长为12,不重合,注意336恰好能整除12
            x = x.permute(0, 2, 1)  # [bs, ch, sql]=>[16,21,336]
            x = x.unfold(dimension=-1, size=12, step=12) # [bs, ch, pum, plen]=>[16,21,28,12]
            x = torch.reshape(x,(x.shape[0]*x.shape[1], x.shape[2], x.shape[3])) # [(bs*ch), pnum, plen]=>[336,28,12] 相当于原来的[bs, ch, sql]
            x = x.permute(0, 2, 1)
        h0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size).to(x.device)  # 初始化隐藏状态h0
        c0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size).to(x.device) # 初始化记忆状态c0
        out, _ = self.lstm(x, (h0, c0)) # out:[(bs*ch), plen, hidden]
        out = self.fc(out)
        out = torch.reshape(out, (x_shape[0], x_shape[1], -1)) # out: [bs, ch, ()]
        out = self.fc2(out.permute(0, 2, 1)) # out: []
        return out.permute(0, 2, 1)  # [Batch, Output length, Channel]   

下面是我跑的几组实验,验证了在不同的预测长度下,原始LSTM和Patch+LSTM的实验结果对比。首先大家能看到,添加Patch后,各项指标在不同的预测长度上提升都是非常明显的。其次,线性模型比LSTM效果好很多~

你肯定也能想到现在自己定义patch的长度为12,滑动步长为12,是没有依据的,没错!这些都是可以改进的地方。这就接到下文了,我们来阅读近期三篇对patch的一些改进工作。

原始Patch的不足

还是先回顾一下patch的具体操作,是把一条序列切分成片段,然后按照片段进行建模。好处在于:1、保留更长的前后信息;2、节约transformer计算成本。

那原始的patch是否有不足呢?有的,首先原始patch是对所有的数据集按照相同的片段长度进行切分。但实际情况是不同数据集的采样频率有巨大差距。如下图所示,像汇率数据集、疾病数据集、交通数据集的数据模式显然是不一样的,那么采样频率是分钟的和小时都用同样的长度切显然不合理。

所以对patch的第一种改进策略就是:按照数据集的差异进行动态切分,学术一点的名字就是自适应切分🙂。

对Patch的改进

这里我挑选出三个比较有代表性的改进策略,分别是LPTM、PatchMLP、Medformer。这三个工作

都看到了原始Patch的不足,我们都做过论文解读,感兴趣的可以翻看之前的文章,这里我们逐一看一下他们是怎么做的。

01 LPTM

论文题目:Large Pre-trained time series models for cross-domain Time series analysis tasks(NIPS24)

论文思路如果用一句话来概括那就是暴力枚举,但GitHub代码我没跑通。主要还是学思路。

LPTM的patch切分可以分为四个步骤:首先是切分,通过双层循环,枚举从i到j的所有切分方式,切分完之后进行打分;然后,挑选以i为起点的所有片段中,得分最高的片段;这样做能保证序列是连续的,但带来一个问题,切分的片段大概率是有重复的。所以接着从上步得到的序列集合中,逐个剔除评分最低的片段,直到集合不连续。最后,通过注意力机制解决序列不连续的问题。

这里有个问题其实论文是有所回避的,模型变量间是否应该建模,既是通道独立还是通道依赖,这个问题在下一篇论文会给出一个方案。

02 PatchMLP

论文题目:Unlocking the Power of Patch: Patch-Based MLP for Long-Term Time Series Forecasting(AAAI25)

Patch+MLP这个思路估计很多人都想到过,但本文做了两点创新,其一是通过滑动平均来提取平滑分量和含有噪声的残差。请注意,无论是滑动平均还是傅立叶变换去噪声,很多论文的结果都表明去噪声确实是一种加点的方法,如果自己模型提点到了瓶颈,不妨试一下去噪声😊。

其二就是多尺度、跨通道的patch切分和嵌入方法。对于特定的尺度 p∈P,时间序列首先被分割成非重叠的patch,每个patch的长度对应于 p。这种分割过程生成了patch序列 x_p∈R^(N×p),其中 N 是patch的数量。切分完成后,在进行线性嵌入,每个patch序列 x_p通过单层线性层进行嵌入,生成潜在向量 x_e∈R^(N×d),其中 d 是嵌入维度,不同尺度的patch可以有不同的嵌入维度。最后,展开这些潜在向量获得最终的嵌入向量 X∈R^(1×d_model) ,其中 d_model是输入模型的最终嵌入维度。以上策略允许模型在不同时间跨度上灵活学习代表性的特征,从而提高预测的准确性和模型的泛化能力。

看核心代码,默认的尺度是[48,24,12,6],其实这个粒度的选择本身就有问题,枚举太暴力,但本文这样定义好像也没有依据。

03 Medformer

论文题目:Medformer: A Multi-Granularity Patching Transformer for Medical Time-Series Classification

还是先看论文做patch的思路,大体来说就是跨通道多粒度patch切分,然后通过注意力机制融合。

首先,使用跨通道patch嵌入来有效捕捉多时间戳和跨通道特征。将多变量时间序列样本分割成多个跨通道的非重叠patch片段,这些patch被映射到潜在嵌入空间并进行数据增强。公式如下:

其实还是做线性投影,注意这里的最大的不同就是跨通道

其次,采用多粒度patch嵌入而不是固定长度,使模型能够以不同尺度捕捉通道特征。每个patch长度代表一个独特的粒度,多粒度patch嵌入自动对应不同的采样频率,从而模拟不同的频带并捕捉特定频带的特征。

最后,引入了两级多粒度自注意力机制,分别进行粒内自注意力和粒间自注意力。粒内自注意力在同一粒度内捕捉特征,而粒间自注意力在不同粒度之间捕捉相关性。

不过简单翻看了代码,发现还是依赖于自定义的粒度。

个人理解

从Patch TST出来有快两年了,围绕patch的改进工作有很多,用一位好友的话说现在的改进与实现并不够“优雅”。你会发现实际上模型并不能做到自适应的进行patch切分。多数模型依赖于枚举、自定义的方式实现。因此作者认为围绕Patch的改进仍然有改进空间。挖个坑,下一篇准备在LSTM+Patch的基础上,通过傅立叶进行降噪,并探索实现动态切分patch的方法。欢迎关注~

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-03-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 科学最Top 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 实现LSTM+Patch
  • 原始Patch的不足
  • 对Patch的改进
  • 个人理解
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档