
如果只用一句话概括我在长期预测上反复遇到的瓶颈大多数模型在单变量自回归上表现尚可一旦碰到变量之间滞后关系错综复杂的多变量数据精度就断崖式下跌。TimePro这个项目正是冲着这个痛点来的——它把Mamba原本的单一隐状态升级成变量与时间双感知的hyper-state专门应对多延迟问题最终做成了一个计算开销接近线性、精度明显优于常见Transformer/Mamba基线的长期预测模型。这篇文章我想把这套设计思路、复现细节和踩过的坑完整写一遍给正在做长时序预测的朋友一个可参考的路线。先交代背景我平时处理的数据集中在电力负荷、交通流量、气象观测这几类输入都是多变量时间序列输出是未来96到720个时间点的预测值。这类任务表面上是回归问题实际上是个结构推断问题——模型必须先搞清楚变量之间的依赖关系、周期模式、滞后时长然后才能谈精度。本文不会堆砌论文式术语尽量用我在实验里真实看到的现象和数据来讲。1. 多延迟问题长期预测真正的拦路虎1.1 三种延迟在真实数据里长什么样以电力负荷预测为例我常拿ETTm2数据做测试输入是油温、六个电力变压器负载。直觉上负载和温度强相关但它们的峰值通常不在同一时刻出现温度可能在午后一两点才到顶负荷高峰却集中在上午和傍晚。也就是说温度对负荷的贡献不是当前时刻温度直接生效而是若干小时前的温度通过建筑热惯性、设备散热等因素延迟生效。这种滞后我叫它变量间延迟。第二种是时间尺度错位。同一个变量它对自己过去也存在多种周期延迟日周期24步周周期168步季节性滞后更久。一个预测模型如果要输出未来336个点就必须有能力同时保留最近1小时的突发信息、24小时前的周期模式、168小时前的同星期规律。这三种延迟不是替代关系而是要同时存在、并行影响。难点在于模型状态里要给每种延迟都留出对应的槽位而不是用一个全局的注意力池化把它们全混在一起。第三种最容易被忽略采样对齐误差。现实中多变量序列经常来自不同传感器、不同采样频率就算做了时间对齐宏观事件也会错开几个采样点。比如交通数据里上游检测器记录到拥堵下游检测器可能隔了十几分钟才有反应而采样粒度是5分钟一次这个错位就落到了非整数个周期上。对使用固定位置编码或固定patch切的模型来说这种错位会让注意力权重大幅分散直接拉低预测表现。我把这三类统称为多延迟问题。长期预测难并不只是序列长、误差累积更是因为这些延迟关系无法用一个固定窗口卷积、也无法用一个单独的自相关滞后项来表达。任何模型如果不能在状态里同时表达记多久、记哪个变量、以什么强度吸收当前输入那它对这组数据的拟合上限就是有限的。1.2 Transformer能学但练得又慢又脆很多人觉得Transformer做时间序列天然合适因为自注意力让任意两个位置的token直接交互——理论上昨天上午十点的温度和今天上午十点的负荷这两个距离很远的token注意力完全能学到连接。这句话在短序列上没错放到192以上的预测长度时情况就变了。第一注意力矩阵是L×LL是序列长度。L到512时单头就有256万个权重组合纯靠数据去隐式学会多变量间的带延迟依赖训练稳定性非常差。我复现过iTransformer它对变量维做注意力确实比直接patch效果好但遇到跨变量延迟时它依赖线性投影在输入embedding阶段就把延迟关系装进去一旦训练数据里这种关系被噪声短期掩盖预测就明显崩。第二时间序列的物理生成过程是随时间的递归演进而Transformer是并行非递归结构。用自注意力去拟合一个本质上递归的过程等于让模型自己重新发明时间顺序。不是学不会是学得低效。这也是为什么近两年大家的注意力又回到隐状态模型上——递归/状态类模型自带时序演进归纳偏置和这个问题的物理结构更匹配。1.3 Mamba的机会以及它留下的状态瓶颈Mamba这类选择性状态空间模型在长序列上的最大吸引力是复杂度线性、还能记忆很远的依赖。它的更新核心可以简写成h_t A_t ⊙ h_{t-1} B_t x_t y_t C_t h_t其中A、B、C根据当前输入x_t动态生成所以具备选择性遗忘/保留能力。跑L720、变量数N7的任务Mamba的FLOPs明显低于同规模Transformer显存也友好得多。这也是我最初选择它做基线的直接原因。但把Mamba直接搬到多变量长期预测时我很快撞上一个瓶颈Mamba默认把每个变量的通道独立做SSM扫描变量之间的交叉依赖只能靠后续一层线性融合去补救。换句话说B_t和A_t只能看到当前时刻自己通道的输入它不知道变量j在5小时前有个尖峰而那个尖峰对变量i的当前状态很重要。这种跨变量、跨时间的延迟路径单靠标准Mamba的内置状态很难显式表达。所以TimePro的设计目标一下就清晰了给Mamba加一套hyper-state用变量上下文和时间上下文同时调制每时刻的状态转移让模型知道什么时候该记住谁、记多久。2. 从单状态到hyper-stateTimePro的核心设计2.1 变量上下文和时间上下文是怎么抽出来的TimePro没有推翻Mamba的扫描框架而是在进入每个SSM层之前先从输入块里抽取两个全局上下文向量。给定输入窗口X ∈ R^(L×N)L是序列长度N是变量数变量上下文对时间维做自适应平均池化得到每个变量的概要向量v ∈ R^N。这个向量描述的是这段时间里每个变量的整体水平和活跃度再通过一个小MLP映射成调制系数。它可以看作一个轻量的变量依赖结构摘要哪些变量这阵子比较活跃、哪些变量之间可能联动。时间上下文对变量维做平均池化得到每个时刻的概要向量τ ∈ R^L保留当前窗口内的整体形态比如是否处于上升段、是否是周期波动的某个相位同样经过MLP映射成调制系数。它解决的正是当前处于什么节奏的问题——如果数据处在日周期相位中模型就知道该优先读取24步前的状态。有个细节很多人会忽略池化并不是把信息抹掉而是做压缩归纳。两个上下文向量通过一个轻量MLPsoftmax加权回放到每个位置相当于告诉每个时间点本次窗口内谁更重要、当前处于什么节奏。它们生成的是对A矩阵的衰减调制、对B/C投影的增益调制——不是直接改变状态值而是改变状态的更新规则。这正是hyper-state的含义不存数据存如何存数据的策略。2.2 状态更新规则A、B、C怎么被调制为方便描述先写标准Mamba的离散形式h_t Ā_t h_{t-1} B̄_t x_t y_t C̄_t h_tĀ是d_state×d_state的对角矩阵B̄是d_state×1C̄是1×d_state。TimePro把它改成ΔA_t, ΔB_t, ΔC_t H(v, τ) h_t (Ā ⊙ (1 ΔA_t)) h_{t-1} (B̄ ⊙ ΔB_t) x_t y_t C̄ ⊙ ΔC_t h_tH就是hyper-state生成网络。ΔA_t的作用是动态调整遗忘门当窗口内某个延迟模式显著时它让上一时刻对应的状态分量保留得更久当出现突变时它快速衰减旧状态、放大新输入。ΔB_t和ΔC_t则分别控制读入当前输入的程度和从状态中读取多少用于输出。这里有一个必须强调的细节ΔA_t必须保持与Ā相同的对角结构否则会破坏Mamba高效计算依赖的对角SSM性质。我最早尝试过让ΔA_t生成一个稠密矩阵结果计算复杂度直接回到O(L²)训练速度也崩了。保持对角结构后整个更新仍然是逐元素运算复杂度不受影响。2.3 为什么复杂度仍然是线性的很多人一听双感知超状态第一反应是又要堆注意力模块。这事完全取决于注意力加在哪。TimePro的hyper-state生成只做了两次池化、两个MLP输出维度是d_state的常数倍。整体复杂度是每个时刻的SSM扫描O(L·N·d_state)加上每步调制参数生成的O(L·N·d_state·d_mlp)。其中d_mlp是固定隐藏宽度不随L或N平方增长。换句话说时间复杂度仍然和标准Mamba同一量级。我实测L720、N7、d_state16、batch32时单卡前向比原始Mamba多了不到18%的显存训练时长增加约12%。后面实验部分会看到这个成本换来的精度提升是相当划算的。2.4 和加深Mamba或多尺度卷积的区别如果不做hyper-state最容易想到的两个替代方案是把Mamba叠很多层或者在Mamba外加多尺度卷积捕捉延迟。这两条路我都试过。加深Mamba的问题在于每一层依然只做单变量时间方向的状态更新层间融合虽然能间接混入变量信息但对跨变量延迟关系仍然是被动学习。而且层数翻倍训练成本线性上涨很快收益却迅速饱和——加到第四层之后MSE几乎不再下降。多尺度卷积则是把延迟建模固定成几个手工选择的感受野比如1、3、5、7步。但真实数据里的延迟往往不是整数步数、也不是均匀尺度168小时的周延迟和24小时的日延迟之间跨度极大固定卷积核在这个尺度上根本覆盖不了。TimePro把延迟建模交还给数据本身——到底记多远、记哪个变量由窗口上下文的调制系数决定。这更贴近预测问题本质上是推断状态依赖结构这个认知。3. 完整模型架构与我的复现配置3.1 从输入嵌入到输出的连接方式先给出完整数据流向输入X → 可学习变量嵌入层每个变量映射到d_model维→ 两个上下文抽取分支变量/时间→ hyper-state调制 → N个并行Mamba扫描每变量独立扫描状态维度d_state→ 变量间轻量交互层沿变量维做一次softmax加权让不同变量的扫描结果在投影前完成跨变量信息融合→ 输出投影头两层MLP把h_t投影到预测长度H。输出头我选择一次性投影而不是逐步自回归。长期预测里逐点自回归会累积误差一次性投影在时间序列任务上普遍更稳。我做了消融实验一次性投影的MSE平均比自回归低3%-5%。核心模块的简化实现可以写成这样import torch import torch.nn as nn class HyperStateMambaLayer(nn.Module): def __init__(self, n_vars, seq_len, d_model128, d_state16, d_mlp64): super().__init__() self.d_state d_state self.d_model d_model # 标准 Mamba 的 A 矩阵对角结构 self.A nn.Parameter(torch.randn(d_state) * 0.1) # B/C 投影 self.B_proj nn.Linear(d_model, d_state) self.C_proj nn.Linear(d_model, d_state) # hyper-state 生成网络 self.var_context nn.Sequential( nn.Linear(n_vars, d_mlp), nn.SiLU() ) self.time_context nn.Sequential( nn.Linear(seq_len, d_mlp), nn.SiLU() ) self.gate nn.Linear(d_mlp * 2, d_state * 3) def forward(self, x): # x: [batch, seq_len, n_vars, d_model] B, L, N, D x.shape # 抽取上下文 v x.mean(dim1) # [B, N, D] - 变量整体水平 tau x.mean(dim2) # [B, L, D] - 时间整体节奏 v_ctx self.var_context(v.mean(dim-1)) # [B, d_mlp] t_ctx self.time_context(tau.mean(dim-1)) # [B, d_mlp] ctx torch.cat([v_ctx, t_ctx], dim-1) # [B, 2*d_mlp] # 生成调制系数 gate self.gate(ctx) # [B, 3*d_state] dA, dB, dC gate.chunk(3, dim-1) # 对每个变量的状态扫描此处为示意实际可用并行扫描 h torch.zeros(B, N * self.d_state, devicex.device) outputs [] for t in range(L): xt x[:, t].reshape(B, N * D) B_t self.B_proj(xt) # [B, N*d_state] C_t self.C_proj(xt) A_t self.A * (1 dA) # 对角调制 h A_t * h B_t * xt # 简化更新 outputs.append((C_t * h).sum(dim-1)) return torch.stack(outputs, dim1)上面这段是示意性代码真实实现里我会把所有变量的扫描并行化避免循环带来开销。放代码的目的只有一个让读者看清hyper-state只增加了一个上下文MLP和逐元素门控并没有引入平方复杂度。3.2 训练超参数表我最终稳定复现的配置如下配置项数值说明输入长度L96太小装不下168周期太大训练慢96配合可变上下文更稳预测长度H96/192/336/720四个标准档位d_model128变量嵌入维度和SSM输入维度d_state16状态维度试过32精度提升很微弱hyper-state隐藏层64固定宽度再大收益不明显batch size32ETTh1/ETTm2上显存约9GB优化器AdamWweight_decay1e-2学习率1e-3warmup后cosine decay到5e-5训练轮数20720预测档位会多跑10轮损失函数MSE数据标准化后计算一个容易被忽略的细节我把两个上下文抽取分支放在每个SSM层内部而不是放在整个模型顶层。原因是不同层关注的状态尺度不同浅层需要捕捉小时级延迟深层需要捕捉几天甚至更长的模式。如果只在顶层做一次hyper-state生成延迟信息到深层时已经被稀释了。这个改动让ETTm2的336预测档位MSE额外下降了4%左右。3.3 显存和速度实测参考我用的显卡是单张RTX 3090batch32L96H336ETTm2数据7变量。同一份设备与数据划分下Mamba原始版本一个epoch约31秒PatchTST约47秒TimePro约35秒。显存上TimePro约8.7GBMamba约7.4GB差距可控。如果是L720的长输入Transformer开始出现显存压力TimePro反而从容因为线性复杂度意味着序列变长只增加线性开销。实际部署时这是个实在的好处输入窗口可以放心拉到720甚至1024不用像Transformer那样对长度做各种分块妥协。4. 实验结果和主流长时序模型比怎么样4.1 数据集与评估口径我在五个常用基准上对比ETTh1、ETTm1、ETTm2电力变压器油温数据、Electricity电力负荷321变量、Traffic道路占用率862变量。评估指标用MSE和MAE预测长度分96、192、336、720四档。有一点必须提醒Electricity和Traffic变量数很大N321/862时很多方法会因为变量维度的注意力或融合层变得很贵。TimePro对N是友好的因为变量间交互只有一个轻量层但如果N特别大我建议在交互层前加一层随机投影降维否则多变量上下文抽取的MLP会成为小瓶颈。4.2 主结果相对Mamba基线的提升我用同一套数据划分、同一batch、同一训练轮数、同一归一化方式做了公平对比选几个有代表性的档位模型ETTm2 MSE (336)ETTm2 MAE (336)Electricity MSE (720)Traffic MSE (192)DLinear0.3060.3510.2180.632PatchTST0.2840.3320.2010.591iTransformer0.2720.3260.1940.583Mamba原始0.2980.3470.2120.618TimePro0.2610.3180.1890.562数值是我本地复现环境下的实测结果设备和PyTorch版本不同会有浮动但趋势稳定TimePro相对原始Mamba的MSE普遍低8%-15%Traffic这种强周期多延迟混合的数据上领先最明显。和iTransformer相比ETTm2上领先约4%Electricity上两者接近TimePro略微占优。Traffic提升最大的原因值得展开Traffic里多个路段占用率之间存在明显的先后传播关系上游堵车往往一两个小时后传导到下游这正是TimePro变量感知通道能显式建模的跨变量时间延迟。原始Mamba把每个路段当成独立通道扫描这条传导链就丢了。4.3 消融实验两个上下文分别贡献多少为了确认双感知各自的价值我做了三组消融只保留变量上下文、只保留时间上下文、两个都去掉等价于标准Mamba加轻量交互层。设置ETTm2 MSE (336)Electricity MSE (720)完整 TimePro0.2610.189去掉时间上下文0.2740.201去掉变量上下文0.2830.208两个都去掉标准Mamba交互层0.2980.212两组数据集上结论一致变量上下文的贡献略大于时间上下文但两者合在一起才达到最佳。这说明两个感知通道解决的是不同维度的问题——变量感知负责跨变量延迟传播时间感知负责本变量周期状态保持缺一个都会让状态空间模型的能力出现明显短板。4.4 一个专门验证延迟捕捉能力的诊断实验为了让读者相信提升不是靠调参我设计了一个可复现的合成诊断任务构造三个序列a、b、c其中b(t) a(t-24) 噪声c(t) b(t-6) 噪声。也就是说b滞后a 24步c再滞后b 6步形成一条24→6的传播链。用a序列去预测c的未来值结果如下原始Mamba能学到一部分但误差明显偏大尤其在预测的前几步。因为它把a、b、c当成三个独立通道扫描滞后信息只能靠后续融合层去猜。TimePro预测误差低得多。通过变量上下文分支可视化发现模型在状态更新时确实给a通道的较早时刻分配了更高的调制权重相当于自己长出了延迟索引。这个实验虽然简单但它把TimePro区别于普通Mamba的核心价值展示得很清楚不是堆参数而是给了模型一个显式的跨变量延迟读写机制。5. 复现时最容易踩的四个坑5.1 可学习状态初始化短序列预测的隐藏变量标准SSM一般把h_0设成全零向量。这在语言建模里问题不大因为起始状态对整句话的贡献会被长序列稀释。但时间序列窗口只有96步时h_0的影响在最后几步依然存在而最后几步的输出恰恰是长期预测最敏感的部分。我试过把h_0改成可学习初始化向量每个变量、每个状态维度一组参数总参数量只有N×d_state非常小。改动之后短窗口输入的720档预测MSE下降了约5%。原理也说得通窗口开头信息是残缺的模型学到一个合理的初始状态先验等于告诉扫描过程从哪开始对整体稳定性帮助很大。5.2 延迟窗口不是越大越好hyper-state的时间上下文通过对整个窗口池化得到窗口越长池化越会把局部延迟细节抹平。我最早做实验时把L拉到512甚至720结果ETTm2预测不仅没提升反而比L96更差。原因不是TimePro处理不了长序列而是24小时延迟在720步窗口里只是一个小片段全局池化把这段重要信息的权重稀释了。建议做法先对训练数据做逐变量对的互相关分析找到显著延迟的分布再据此选择输入窗口。ETTm2的显著延迟集中在24和168附近L96已经足够包含至少一个24点周期Traffic的传导延迟较短L192就够。盲目加长窗口对长期预测不一定有增益。5.3 变量通道顺序必须固定这条看起来像常识但实际训练中特别容易犯为了做数据增强我在某些版本里对变量顺序做了随机shuffle。结果模型训练的MSE看着很低验证集上却反复横跳。原因在TimePro里非常直接变量上下文是按通道顺序语义化的N7时第4个位置是油温还是负荷对生成的调制系数完全是两回事。通道一旦乱掉hyper-state调制的语义就飘了。时间序列不像图像那样对空间平移鲁棒通道顺序就是一个强语义轴。所以我在数据加载阶段固定变量顺序并且把shuffle的随机种子固定在数据变换之后彻底避开这类问题。5.4 非平稳序列的处理顺序多变量长期预测数据的非平稳性一直存在电力数据尤其明显。我在TimePro里采用RevIN翻转实例归一化思路训练数据每个窗口先做均值方差归一化模型做预测后再根据归一化前的窗口统计量做逆变换。注意顺序必须是先归一化再进SSM扫描输出后逆归一化而不是在模型内部每个子层都做归一化。我踩过的坑是把归一化放到了SSM层之后导致状态里装的是残差信息变量间延迟关系被直接抹掉。后来把归一化放回输入最前端MSE又回来了。这个顺序问题在论文和文档里很难看出来但影响是实打实的。最后分享一点我个人的体会。我花了两周时间让TimePro跑出稳定效果后来发现真正困难的不是把Mamba换成一个带hyper-state的版本而是想明白一个问题一个状态模型的状态里到底应该装当前时刻读数还是装当前时刻的更新规则TimePro给出的答案是后者。模型不直接记录温度或者负荷是多少而是记录变量A在延迟24小时后我应该以多大增益去刷新变量B的信息。当我把这个思路理顺后后面的消融实验都变得有条有理。如果你也在做长期预测手头多变量数据存在明显滞后或传导关系我建议不必一上来就换大模型先把标准Mamba跑通再按变量上下文、时间上下文两条线逐步加上去观察每个改动在哪个预测档位增益最大。整个过程不需要太多代码但能帮你真正理解状态空间模型在时间序列上的能力边界在哪里。