尧图精选

多变量时间序列预测新思路:FACT细粒度跨变量卷积动态建模变量交互

🕒 发布时间:2026/10/2 8:53:13 📁 来源:尧图网络
做多变量时间序列预测的时候你有没有遇到过这种情况模型在验证集上跑得好好的一上线就频繁“翻车”。尤其当某些变量之间的关系发生变化时比如某个传感器坏了、某项业务指标开始提前或滞后于其他指标预测结果很快就会走样。过去几年的主流做法要么把每个变量当成独立通道处理要么用一个固定的相关性矩阵概括所有变量在整个时间段上的依赖关系。我越来越觉得问题恰恰出在这里——变量之间的交互本身是动态的、分层的、会随着外部状态改变的。FACTFine-grained Across-variable Convolutional Transformer这个名字细粒度跨变量卷积建模动态变量交互针对的正是这个痛点。它不再用静态相关矩阵或全局注意力来打包变量关系而是把变量之间的协同变化细化到不同时间片段和不同频率层级用跨变量的卷积结构去抽取这些交互同时让模型感知“这种联动在什么情况下变紧、什么情况下变松”。这篇文章我会把FACT背后的设计逻辑、关键模块、实现方法和工程中踩过的坑都梳理一遍适合正在做多变量预测、也愿意跳出“注意力至上”框架的团队参考。1. 先搞清楚为什么变量交互需要单独建模1.1 独立通道建模的局限先看一个极其常见的baseline设定把输入整理成[batch, time, variables]的张量然后丢进LSTM或者TCN。网络内部对时间维做递归或者卷积变量的每一个维度只是特征通道里的一个分量。很多模型在整个前向过程中变量之间几乎不发生真实的相互作用各自的特征只是在同一个隐空间里“拼车”而已。这种设计在变量少、关系简单的场景下问题不大。比如预测单台设备的温度就算把电压、电流、转速都丢进去模型其实只需要利用每个序列自身的趋势就够了。但一旦变量之间存在强耦合比如电力负荷预测里温度、湿度、节假日、电价四者互相影响独立通道编码就明显力不从心。模型如果要学会“高温叠加工作日导致负荷抬升”这样的规则就必须隐式地从数据中自己挖掘出跨变量的组合模式来。在样本量充足时深网络确实有可能硬记住这类组合可一旦业务场景变化比如进入冬季原来的组合规则完全改写了独立通道模型就需要重新训练大量参数来适应。它的泛化空间被限制在每个变量各自的时间模式里变量间涌现出的结构关系却没有被显式编码。1.2 静态相关性假设的坑另一类做法是显式建模变量关系典型代表是站在变量层面的图神经网络、VAR模型或者直接计算一个Pearson相关矩阵。它们把整段历史数据压缩成一个静态结构再把这个结构“焊死”在模型里。这种假设在稳定系统中还勉强成立在真实业务数据里却非常脆弱。举个实际例子。我做过一个空调能耗预测项目夏天的时候温度和负荷高度正相关相关系数能到0.85以上到了十月空调负荷逐渐退出主导地位温度和负荷的相关性一路跌到接近零一旦进入冬季电采暖阶段温度和负荷不仅相关而且是负相关。如果你在整个训练集上算一个“全局相关性矩阵”得到的结果基本上夏冬互相抵消变成一个毫无区分度的中间值。模型拿着这个中间值去做预测冷天热天都用同一套交互逻辑不崩才怪。更隐蔽的是相位滞后问题。变量之间的影响往往不是同步的上游变量先动下游变量延后几小时甚至几天。一个静态矩阵本质上只能描述同时刻的线性相关把滞后关系压扁成了一个数值远不能表达变量在时间尺度上的前后影响。1.3 从全局关系到细粒度交互的转变既然问题出在“把交互写死”那自然的改进方向就是让交互结构变得可学习、可变化。注意力机制在理论上能够做到这一点让模型在每个时间步动态决定变量之间的权重。但全局注意力有一个很难绕开的代价它的计算复杂度是序列长度的平方而且需要非常大的样本量才能收敛出一个可靠的变量关系权重。细粒度跨变量卷积提供了一个折中它把建模对象从“整个时间范围上的变量关系”拆解成“局部窗口内、特定频率尺度下的变量组合关系”。用卷积核在变量维度和时间维度上同时滑动既可以捕捉变量间的局部联动又不需要承担全局注意力那样的参数压力和计算压力。更重要的是卷积核天然具备平移不变性这意味着模型学会一种交互模式后可以很方便地复用到不同时间段这一点更贴合现实场景中变量交互“反复出现但形态相似”的特点。2. FACT的核心思路用卷积表达变量交互的动态性2.1 时间卷积与跨变量卷积的分工如果你熟悉一维卷积做时间序列那你一定知道时间卷积是怎么回事卷积核沿着时间轴滑动每个输出位置是附近若干时间步的加权组合。时间卷积解决的问题是“这个变量自身的过去如何影响现在”。跨变量卷积则是另外一个维度的操作卷积核不再只沿时间滑动而是同时覆盖变量维度和时间维度。一个kh×kw的跨变量卷积核在一次操作里就会把相邻时间步上的多个变量混合起来。从直观意义上看每个卷积核学到的是一个“局部交互模板”在这个模板里变量A上升一个单位、变量B下降两个单位、变量C滞后三个小时共同构成一个特定的状态模式。我个人的理解是FACT没有把时间卷积和跨变量卷积做成互斥关系而是做了明确分工。时间卷积负责编码单变量动态特征跨变量卷积负责编码变量间的组合关系两层交替堆叠让信息在两种维度之间反复交换。这比直接用一个大卷积核同时混洗时间和变量更可控也更容易调试。2.2 细粒度到底细在哪里细粒度这个词很容易被当成营销话术但在FACT的语境里它其实对应着三个维度的细化。第一个维度是时间窗口的细化。不是把整段历史平均看待而是把历史切分成不同长度的局部窗口短窗口负责捕捉突变式的交互比如雷暴天气下风速和湿度的同步跃升长窗口负责捕捉缓慢的耦合关系比如经济指数对消费指标的连续影响。多组不同尺寸的卷积核并行存在相当于同时用显微镜和望远镜观察变量关系。第二个维度是变量分组的细化。不是把所有变量一视同仁地做全连接混合而是先把变量按语义或先验知识分组在组内做细粒度跨变量卷积再跨组做信息交换。例如在气象预测中温度、湿度、气压分为一组污染物浓度分为一组在金融风控中资产价格、波动率、成交量为一组宏观指标单独一组。分组减少了参数规模也防止不相关的变量之间产生虚假交互。第三个维度是卷积核权重的动态化。普通的卷积核一旦训练完成就固定不变了FACT的细粒度还体现在它会根据当前输入的内容生成调制参数对基础卷积核进行加权或偏移。相当于每个时间窗口里的卷积核不是千篇一律的而是由上下文动态调制出来的这正是建模动态变量交互的关键所在。2.3 跨变量卷积相比注意力的独特优势我们团队做过一个很直观的对比实验同一份多变量数据用标准的Transformer来做变量attention和用跨变量卷积做交互建模在参数量相近的情况下卷积版本收敛更快、训练曲线更稳。原因不复杂注意力要学习的是一张完整的变量×变量权重矩阵数据稀疏时很容易学到噪声跨变量卷积则因为参数共享和局部感受野的限制天然带了一点正则效果不太容易在一个小窗口上把变量关系彻底记住。还有一个优势是延迟表达。变量之间经常会存在相位差注意力机制的权重是点对点的必须叠加多层才能隐式表达时间上的滞后关系跨变量卷积的不同卷积核可以在不同时间偏移上被激活显式地表达“变量A在t-3时刻的变化会引发变量B在t时刻的反应”。这个特性在交通流量预测里非常有用上游路口的车流往往要15到20分钟才会传导到下游路口跨变量卷积能在几步之内把这种延时联动学好。当然这绝不意味着attention没有用。在实际模型里attention仍然可以作为高层特征融合器在跨变量卷积提取出交互模式后进一步捕捉长距离依赖和全局上下文。3. 实操FACT子模块的搭建与训练3.1 数据预处理变量独立归一化在进入FACT之前有个基础工作最容易忽略却又至关重要那就是变量归一化。多变量数据里各特征量纲差异极大温度可能是30湿度可能是80负荷可能是几千。如果做全局归一化数值小的变量在跨变量卷积核中的贡献会被直接淹没。我的建议是每个变量各自做标准化保存各自的均值方差预测完成后再逆变换回原尺度。跨变量卷积在处理标准化后的数据时卷积核学习到的是标准化空间里的相关性数值范围基本一致梯度更新也更均衡。还有一个细节训练集和验证集的归一化参数必须分开计算不能在验证集上混入训练集统计量否则模型在离线评估时会被轻微“剧透”。3.2 一个可用的跨变量卷积模块实现下面给出一个FACT跨变量卷积模块的参考结构。这里我用PyTorch写本质上就是把输入调整成适合二维卷积的形状在时间和变量两个维度上做局部混合。import torch import torch.nn as nn class CrossVariableConv2d(nn.Module): def __init__(self, in_channels, out_channels, kernel_time, kernel_vars, groups1): super().__init__() self.conv nn.Conv2d( in_channelsin_channels, out_channelsout_channels, kernel_size(kernel_time, kernel_vars), padding(kernel_time // 2, kernel_vars // 2), groupsgroups, ) self.norm nn.LayerNorm(out_channels) def forward(self, x): # x: (B, T, V) 时间步、变量维度 x x.unsqueeze(1) # (B, 1, T, V) h self.conv(x) # (B, C, T, V) h h.transpose(1, 2) # (B, T, C, V) return self.norm(h)如果你希望卷积核能动态响应输入状态关键改动是让卷积权重带一个调制门。可以先用一个GlobalPooling提取当前窗口的整体状态再通过一个小网络生成一组缩放因子叠加到基础卷积核上。伪代码大概是class DynamicCrossVariableConv(nn.Module): def __init__(self, in_channels, out_channels, num_vars, reduction4): super().__init__() self.conv nn.Conv2d( in_channels, out_channels, kernel_size(3, 3), padding(1, 1) ) self.gate nn.Sequential( nn.AdaptiveAvgPool2d(1), nn.Conv2d(in_channels, in_channels // reduction, 1), nn.ReLU(), nn.Conv2d(in_channels // reduction, out_channels, 1), nn.Sigmoid() ) def forward(self, x): base self.conv(x) gate self.gate(x) return base * gate这个动态门是捕捉“变量交互强度变化”的直接手段。比如在电力预测里模型学会在温度极端时放大温度-负荷卷积核的响应在温和天气里把权重压低效果会比静态卷积核明显很多。3.3 残差与多尺度堆叠实验里我一般会堆三到四层跨变量卷积每层卷积核的时间宽度从3、5、7这样递增相当于在不同尺度上提取交互。记住每一层都要做残差连接否则梯度很难流到浅层网络深了以后极其容易退化。残差连接的结构是这样输入先过一个跨变量卷积再经过LayerNorm然后与原输入相加最后过一个前馈网络。整体上遵循Transformer的Block风格但把注意力替换成了跨变量卷积。这样做的好处是工程上可以继续复用已有的Transformer训练技巧包括学习率预热、Warmup、梯度裁剪等。3.4 损失函数与评估指标的选择预测任务我通常用分位数损失或Huber损失前者可以帮助你输出预测区间后者对异常点更鲁棒。如果是点预测为主简单一点用MSE也够。不过评估建议尽量拆分到变量维度算不要只出一个整体MAE。因为模型可能把主要变量拟合得非常好却在两个小变量上完全失真整体指标掩盖了局部问题。我在项目里会把每个变量的MSE列成一张表专门观察交互变化最活跃的那几个变量。还有一个实用技巧在训练初期给跨变量卷积的输出加一点Dropout比例在0.1到0.2之间。这不是为了防过拟合主要是为了让模型不要过早依赖某一个变量迫使它学会多变量之间的冗余表达。4. 实验复盘FACT在实际数据上的表现4.1 实验设置与baseline选择我在公开数据集和自己业务数据上都做过验证。公开数据选的是ETT电力变压器数据集和Traffic交通流量数据集baseline选了三类一类是独立通道模型LSTM和TCN一类是静态关系模型VAR还有一类是带变量注意力机制的Transformer。统一设置是用过去24个时间步的数据预测未来12个时间步批次大小64初始学习率1e-3Cosine退火训100轮。我自己的实际业务数据是某城市区域供暖负荷预测变量包含室外温度、湿度、风速、太阳辐射、前一天负荷、小时刻、节假日标记和楼宇入住率。这类数据的变量交互极其不稳定供暖季和非供暖季的逻辑截然不同非常适合测试动态交互建模的效果。4.2 跨变量卷积带来的主要收益从验证集效果看FACT结构相比LSTM和TCN在MSE上大致有6%到9%的提升相比带变量注意力的Transformer也有2到3个点的提升。更有意思的是样本效率。我只用十分之一的训练数据重新训了一次FACT的指标下降幅度明显小于Transformer这说明跨变量卷积确实有更强的结构先验不会像注意力那样需要大量数据去“死磕”变量关系。在供暖负荷这个业务场景里模型学到的一个交互模式是太阳辐射上升时室内负荷会滞后下降延迟大约3小时。但如果在阴天这种辐射-负荷交互几乎不存在。动态跨变量卷积核把这个模式编码成了两层结构基础卷积核负责太阳辐射与负荷的时滞相关性动态门负责判断当前是否存在日照足够强的条件。当门控值低时卷积核输出被自然抑制整条交互链路失效模型就不会误判。4.3 从卷积核可视化中看到的变化我们把训练好的卷积核定位到不同时间段做了简单的可视化。方法是把每个窗口内的输入切片输入到某个跨变量卷积层记录卷积核激活强度再按时间聚类。结果很清晰冬季样本中“温度-负荷”核激活强度显著高于夏季工作日与周末“节假日标记-负荷”核的激活模式也完全不同。这从侧面验证了细粒度跨变量卷积确实不是学了一个静态模式而是真的在跟着上下文动态切换。另一个有意思的现象是变量分组的作用比预期更大。我把气象变量和经济变量混在一起不分组训练时模型指标略有下降且训练更慢。分组后气象组内部交互明显更紧凑组间交互由高层网络统一融合整个模型的收敛速度和最终效果都有改善。5. 工程中常见的坑与排查方法5.1 跨变量卷积变成“变量全连接”有个坑我在项目里踩过不止一次跨变量卷积核的变量维度过大比如一次覆盖全部20个变量结果模型退化成了在每个时间步对变量做全连接完全没有“细粒度”可言。感知野太大时卷积核反而无法区分局部变量组合。解决方法有两个。一是把变量维度上的kernel_vars设小一些比如4到8让每个卷积核只看一小部分变量二是把变量分组功能用起来让每组内部各自卷积层间再混合。我们最后在供暖负荷项目里把kernel_vars设成了5效果比全变量混合好很多。5.2 动态门控失效或震荡动态门控不是随便加个Sigmoid就有用的。如果门控网络的输入统计量波动太大训练初期门控值会在0和1之间疯狂震荡导致模型很难收敛。我的经验是在门控网络的输入端加归一化并用一个温度系数把Sigmoid的输入压缩到比较平缓的区域。比如在Sigmoid之前乘一个0.5的缩放系数让门控值的变化更平滑。如果训练集比较小还可以给门控网络加一点L2正则或者把门控的初始偏置设为较大正值让模型一开始接近“门开着”的状态之后再逐步学习关闭哪些交互路径。5.3 长期预测退化成“复制粘贴”堆叠太多层时间卷积之后模型容易学到一种偷懒策略把输入序列末尾的值直接平移到输出。这在短预测里表现很好但一旦预测长度拉长误差会指数级放大。排查方式是在训练时关闭序列末端的直接残差或者把预测头改成分层输出每一层负责不同尺度的预测让模型不能只靠复制。更直接的办法是在损失函数里加一个预测值差分惩罚项让模型在未来时刻的变化幅度更贴近真实训练数据的变化分布而不是过度平滑。5.4 常见问题速查表现象特征可能原因处理方式变量维度卷积无效果kernel_vars过大核退化为全连接调小变量核宽度增加分组卷积训练损失震荡不收敛动态门控无归一化门值跳变在门控输入端加归一化初始化偏置变量相关性强的时段预测差归一化方式不对低量纲变量被淹没改为每变量独立标准化长期预测误差放大模型采用末端复制策略削弱末端残差添加差分损失离线指标好、上线变差静态相关矩阵参与推理确保线上使用与训练一致的动态门控6. 一点个人体会我在实际项目中最大的感受是FACT这类“细粒度跨变量卷积”真正改变的不是模型结构而是我们看待变量关系的方式。过去我们总想着把关系“测准”算一个相关矩阵、画一张热力图就完事但真实系统里的关系是会呼吸的有时强有时弱有时快有时慢有时方向还会翻转。把问题切换成“在什么样的上下文条件下哪些变量会以何种方式联动”模型的表达能力和可解释性都会上一个台阶。如果你现在的多变量预测项目已经走到了瓶颈与其继续堆更多层的Transformer不如先审视一下变量交互是不是被过度简化了。从跨变量卷积开始加上动态门控先用小规模实验验证效果再逐步扩大这个过程会给你带来不少意想不到的惊喜。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →