
多元时间序列预测一直是数学建模竞赛里的重头戏。不管是华为杯还是研究生数学建模碰到交通流量预测、空气质量预报、电力负荷预估这类题目时最大的难点往往不是模型不够复杂而是变量之间的关系压根不是静止的。你用一个固定矩阵描述变量关联结果新数据进来就失效你说变量之间相互独立又把真实场景里“PM2.5浓度影响能见度风速反过来稀释PM2.5”这种强耦合关系全丢了。我最近深入研究了一个叫FACT的方法——用细粒度跨变量卷积建模动态变量交互把跨变量依赖从静态变成了动态处理和这类问题很顺手。这篇文章不聊虚的直接拆解这个方法的原理然后结合建模竞赛实战场景讲清楚怎么落地用起来。1. 先搞明白动态变量交互到底难在哪建模竞赛里大家最常用的一套预测思路是把每个变量当成一条时间序列然后用LSTM、GRU或者Transformer去拟合时间依赖。这个方法在变量少、关系简单的时候效果还行一旦变量数量上来比如一个城市空气质量数据集里有PM2.5、PM10、O3、NO2、SO2、CO、风速、风向、气温、湿度你就会发现单变量建模有一个致命伤——变量之间隔着一条信息鸿沟。这里我拿一个生活化场景来类比。你把一组传感器当成一家公司里的员工每个员工都记录自己的KPI。单变量建模就相当于每个员工只看着自己的历史业绩做下季度规划完全不关心其他人手里的资源、市场动态、协作进度。这种模式下遇到系统性风险时所有员工都会同时误判。在空气污染场景里某天突然刮起大风如果只看PM2.5的历史趋势模型会给你报一个“持续累积”的预测但事实上风速这个变量已经悄悄把污染物稀释了。这个信息就被漏掉了。跨变量卷积建模解决的就是这个信息孤岛问题。FACT的核心思路是把变量维度和时间维度分开处理然后用卷积的方式让变量之间互相“看见”对方。这个方向不只FACT在做Crossformer、DLinear、PatchTST这些模型都往这个方向探索过但每个方法都有自己的局限。Crossformer用全连接层把所有变量一次性融合参数规模随着变量数量平方级增长且学到的是静态关联PatchTST在时间维度上做得极其精美却在跨变量维度上选择了一条清晰的“channel independent”路线主动放弃变量交互DLinear更是直接用线性层摊平一切交互深度不足。我用一个表格把主流方案和FACT的差异梳理了一下这样看更直观模型跨变量建模方式动态交互能力信息瓶颈适用场景LSTM/GRU单变量建模无跨变量交互无漏掉变量耦合关系独立序列Transformer类全局注意力全变量两两关联弱静态权重为主计算量随变量数平方增长小规模变量Crossformer全连接跨变量嵌入较弱矩阵固定参数冗余难以捕捉突变的交互关系变量关系稳定场景DLinear单层线性映射无过度简化非线性关系基准对比FACT细粒度跨变量卷积强逐时间步动态生成卷积核计算量可控无显著瓶颈高维动态关联的多元时间序列这个对比最关键的落点是FACT的“动态”这个属性。传统方法做跨变量交互时通常学习一个固定的权重矩阵比如变量A对变量B的影响系数是0.5那就是0.5不管今天空气质量是优还是重度污染这个关系都不变。但现实世界不是这样的晴天的时候太阳辐射对臭氧生成的影响极其显著阴天的时候这条链路几乎失效工作日早高峰的交通流量和事故率高度相关凌晨两点这个相关性就完全不存在。这就是动态变量交互的真实含义——变量之间的耦合强度会随着时间、随着外部状态的变化而改变。FACT针对这个痛点把跨变量建模拆成了“细粒度”的操作不是一次性在所有变量上做一个粗粒度融合而是先把变量的特征分解成共享特征和变量特定特征再按时间步动态更新卷积核。这东西听起来抽象但本质上就是你在看数据的时候不要问“A和B整体上是什么关系”而是问“在t这一刻A和B之间正在发生什么关系”。后面我详细拆这个机制。还有一点需要提醒建模竞赛中很多人碰到变量多的问题第一反应就是做个特征选择或者PCA降维删掉几个“不太重要”的变量。这个思路在处理线性关系时有效但碰上非线性耦合场景很可能删掉的那几个“次要变量”恰恰在某个时间段里占据了主导地位。风速这个变量在空气污染预测中经常被当成弱特征可一旦发生沙尘暴或者强对流天气风速就成了决定性因子。FACT这类模型的好处是它可以完整保留变量然后在训练过程中自行决定每条变量交互路径的强弱。数据本身会告诉你哪条交互通道在这个时间窗口里变强了哪个变弱了。2. 细粒度跨变量卷积的核心机制拆解2.1 共享特征与变量特定特征的分离FACT一开始做的不是直接卷积而是对输入数据的特征空间做了一个划分一部分是共享特征一部分是变量特定特征。为什么非得这么做我实际跑实验时的理解是这样的如果直接把原始变量扔进卷积核卷积核会被每个变量自身的量纲和分布搞乱。风速的数值可能是0到20PM2.5的数值可能是0到500CO的数值可能只有0到5——这几种特征混在一起生成卷积核模型得花大量参数去区分量纲真正学习交互关系的参数反而被挤占了。共享特征的设定是在t这个时间截面上各个变量之间共有的那部分信息比如整体趋势、共同的周期波动、同源驱动的变化。这个思路跟时序分解里的Seasonal-Trend Decomposition很像只不过FACT把它嵌入到了特征提取层内部。变量特定特征则是每一个变量自己独有的信息比如PM2.5自身的高频抖动、某个传感器特有的噪声模式。我在处理竞赛数据时发现一个有用的小操作在进入模型之前先手动把原始数据做一次标准化和趋势对齐。共享特征部分最好使用同一套scale进行标准化变量特定特征则各自单独标准化。这个预处理方式能让FGCC的分解模块更早收敛你在训练前期就能看到一个相当平滑的损失下降曲线。很多同学喜欢“一把梭”直接喂原始数据结果损失曲线抖得跟心电图一样这往往是量纲问题没有预先处理到位。2.2 动态卷积核的生成逻辑FGCC最核心的操作为什么叫“细粒度”这个细体现在它生成卷积核的粒度上。传统卷积网络在图像处理里的逻辑是一个卷积核扫遍全图但FGCC不这么干。FGCC是为每一个时间步、每一组变量组合动态生成对应的卷积核。打个比方传统跨变量建模像是你拍了一张全景照片然后用一个固定的滤镜去处理整张图FGCC的做法是把照片按时间和区域切成很多小块每一块重新计算当前光线下应该用什么参数去调整。这个动态生成的过程在代码层面可以拆成这样几个步骤第一步输入特征先经过一个共享嵌入层得到一个中间表示H。这个H包含了当前时间步所有变量的信息压缩。第二步通过一个核生成网络把H映射为一组卷积核参数。这个网络本身不复杂通常就是两层全连接加非线性激活复杂之处在于输出的卷积核参数数量随变量数变化但它是动态生成的所以能实时反映当前各变量的状态。第三步用生成出来的卷积核跟变量特征做卷积操作。此刻的卷积不是全变量的大融合而是细粒度地在相关的变量子集上做局部卷积单个注意力集中到了“这个时间点哪些变量在协同变化”上。核生成网络的参数是固定的网络本身是静态训练的但输入到网络里的特征是变化的输出卷积核因此也是逐时间步变化的。用口语一点说就是网络学了一个“如何快速计算卷积核”的函数而不是直接学一个固定的卷积核。我刚理解这个概念的时候想到了一个特别贴切的比喻你把卷积核想象成做饭时的菜谱。FACT不是给你一本固定的菜谱让你天天照着做而是每次根据冰箱里有什么食材当前变量的状态、当前天气和环境时间步状态、吃饭人的心情上游特征临时帮你写一道新菜谱。所以每次做出来的菜都能贴合当下情况。2.3 卷积如何“跨变量”而不丢失时间信息跨变量卷积最容易出现的毛病是做变量融合的时候把时间顺序打乱了导致模型只记住了变量间的空间关系忘了时间上的先后依赖。FACT在设计上避开了这个坑——它的FGCC模块只负责处理变量维度的交互时间依赖的建模是由另一个模块TCCTemporal Convolution Component单独负责的。这些设计理念落实到竞赛工程里的意义是你在实现的时候可以把两个模块串在一起先让FGCC对每个时间截面的变量做交互建模把融合后的特征按时间顺序排列再交给TCC做时间维度的卷积。特征先横向融合再纵向积累。这个串联结构保证了信息在两个维度上的流动是有序的不会混在一起。实际操作过程中可以留意一下两个模块的嵌入维度设置。我试过几组组合发现FGCC的输出维度保持与输入维度一致或者略大一点然后TCC的卷积核尺寸设置成覆盖2到4个时间步整体效果最稳定。如果你把TCC的卷积核设得太大比如覆盖10个时间步时间依赖会被过度平滑曲线的尖峰和突变细节会丢失。这点和图像卷积的直觉是一样的卷积核越大捕捉的越偏向全局趋势细节分辨越低。3. 建模竞赛场景下的FACT落地实操3.1 数据预处理的四个关键动作不管是华为杯还是其他建模竞赛拿到数据的第一件事情永远是清理和结构梳理。我在处理多元时间序列数据时基本会固定走四个动作缺失值处理、异常值识别、序列平稳化、滑窗构建。缺失值处理这块多元时间序列和单变量不一样你不仅要看单个变量自己的缺失还要看变量之间的缺失模式。比如风速和PM2.5同时缺失和只有风速缺失处理策略完全不同。前者可能指向传感器停机或者通信故障补值方案要谨慎后者可能只是随机丢失线性插值就够了。FACT模型对缺失值敏感程度低于树模型但也不能指望它自动处理一切建议在进模型之前就把缺失补好。序列平稳化上常见的做法是差分或者取对数。在做完平稳化之后需要把原始统计量记录下来因为最后模型输出的预测值必须恢复成原尺度否则你写论文的时候会发现预测结果对不上真实数据的单位。这个反变换步骤在竞赛论文的模型评估环节很容易被忽略但评委用rmse一算就能发现你数据对不上。滑窗构建是最影响训练效率的一步。窗口长度决定了模型能看到多长的历史信息我一般用两个原则来确定窗口长度第一需要覆盖数据里最长的周期成分比如交通流量数据有24小时周期那窗口至少得涵盖24个时间点第二窗口长度宁长勿短长了模型可以通过注意力机制自己决定关注哪段历史短了想关注也关注不了。不过在FACT里窗口也不能太长因为FGCC逐时间步生成卷积核窗口越长单次前向计算就要生成越多的卷积核显存消耗会显著增长。我通常设置在24到96之间具体看数据采样频率。3.2 模型配置和训练细节FACT在训练过程中有几个关键参数直接影响最终效果。嵌入维度是第一个要定的这个值决定了特征表示的容量。我在小规模数据集上习惯用64中等规模用128如果数据量大且变量数超过20个会考虑256。需要留意的是嵌入维度过大在FGCC的核生成网络里会带来更多的参数但收敛速度会变慢所以不要在比赛初期一上来就开256这种大维度先用128跑通全流程再考虑要不要加。第二个参数是核生成网络的隐层维度。按我的经验这个值设置在嵌入维度的1.5倍到2倍之间。它是学习和生成动态卷积核的主力如果容量设置太小生成的卷积核会缺少多样性不同时间步之间卷积核的差异不明显动态交互就退化成静态交互了。学习率调度我用的是余弦退火加线性warmup。刚开始训练的前5个epoch用很小的学习率做warmup让核生成网络先稳定下来然后进入余弦退火阶段。这个做法的收益在FACT上体现得很明显——因为这个模型分两段式生成卷积核信息流动链比较长如果不做warmup前期的梯度震荡会特别剧烈。还有一个值得专门说的点损失函数的选择。FACT原论文使用MAE或者MSE作为基础损失但在竞赛场景里我更建议使用Huber Loss或者平滑L1 Loss。原因很实在因为竞赛数据里经常包含一些由于传感器故障、突发极端天气产生的离群值MSE会对这些离群值给过高权重导致模型的注意力被少数异常点吸引动态卷积核的生成也被干扰。Huber Loss在误差接近零时表现为L2损失保证平滑误差超过阈值后切换为L1损失限制梯度大小对离群值鲁棒很多。我自己常用阈值为1.0效果比较稳定。3.3 评价指标和验证方式数学建模竞赛里预测类题目的评价指标通常给的是RMSE、MAPE或者皮尔逊相关系数。用FACT做预测时我强烈建议你同时跟踪这三种指标因为它们三个观察的角度完全不同RMSE对大幅度误差敏感MAPE看的是相对误差但真实值为零的时候会爆皮尔逊相关系数看的是趋势一致性但完全不关心数值偏差。一个模型完全可能RMSE很高但皮尔逊相关系数接近0.9说明趋势对了但幅值偏了也可能反过来。写论文的时候同时报告这三个指标也能让陈述显得更严密。验证方式上时间序列预测不能照搬普通机器学习的K折交叉验证——随机打乱就会让时间顺序错乱造成严重的数据泄漏。推荐用滚动时间窗口验证法用前70%-80%的数据训练后面20%-30%的数据验证然后在验证集上按时间步逐步前移每移动一步就做一次预测和评估。这个方法最贴近真实预测场景也是竞赛评委期望看到的验证方式。4. 典型竞赛题目场景实战案例4.1 场景一空气质量预测里的强动态耦合空气质量预测是多元时间序列建模竞赛里特别经典的一类题。题目一般会给过去几十天的多站点污染物浓度和气象数据让你预测未来几天的PM2.5峰值浓度。这类题目的跨变量交互真实存在而且变化剧烈白天太阳辐射强NO2和挥发性有机物在光照下发生光化学反应生成臭氧所以NO2和O3的交互关系在白天是正相关主导到了夜间光化学反应停止O3会与NO反应重新消耗交互关系就反转了。你要是用静态权重建模这种日内反转的交互模式是学不出来的。用FACT处理这个场景时流程这样走先把所有变量按15分钟频率重采样做缺失插值然后构造滑窗。FGCC模块会重点捕捉“当前时段臭氧浓度上升时哪些变量正在协同变化”这类信息。我在实际训练中发现动态卷积核在这个场景里很自然地聚焦到了NO2、太阳辐射、温度这组变量上而在夜间时间步上卷积核的关注重心会自动转移到NO、相对湿度上。不需要有人告诉模型你这个场景的化学机理模型通过数据学出来的交互模式与大气化学常识相当吻合。这个案例给竞赛选手的启发很直接别一开始就想着把手头所有变量都塞进模型也别因为怕复杂就删变量。动态交互建模本身就是让数据自己告诉你交互结构。如果题目给了30个变量直接全部交给FACT让FGCC自己挖掘结构然后你在论文里把学到的交互权重可视化出来呈现效果和物理含义阐释都会好很多。4.2 场景二交通流量预测中的周期性交互转移交通流量预测类的题目典型的变量有各路段的流量、平均车速、车道占有率、天气状况、事件信息。周期性很强但周期性本身不行交互会发生转移早高峰期间主干道流量和匝道汇入流量的交互最强因为匝道拥堵直接拖垮主干道平峰时段这个链路就弱化甚至消失流量和车速的交互变成主导。这类数据有一个特点交互模式在一天之内会发生多次切换而切换的时刻和强度在不同星期几还不同。用FACT来处理FGCC的核心优势被充分放大——它可以为上午8点生成一组卷积核体现主干道和匝道的强关联再为上午11点生成另一组卷积核把交互重心转移到流量和车速上。模型通过整个数据集的训练自动学习到了这套“按时间段切换交互模式”的逻辑。竞赛写论文时我习惯把动态卷积核在不同时间段的差异可视化画一个热力图横轴是时间步纵轴是变量对组合颜色深浅表示卷积核权重强度。这种图放在论文里非常直观评委一看就明白你的模型确实建模了动态交互而不是套了一个黑盒了事。热度图能显著提高模型的说服力尤其是你还能指着图上说“早高峰这一块颜色明显变深了说明模型捕捉到了主干道和匝道的协同”。4.3 代码框架速写一个最小可用的FACT结构纯说理论容易飘我自己整理了一个最小可用的PyTorch风格代码框架帮你把FACT的核心结构串起来。这个代码不是完整的训练脚本但你可以拿它当骨架往里面填自己的数据读取和训练逻辑。import torch import torch.nn as nn import torch.nn.functional as F class DynamicConv(nn.Module): 动态卷积核生成与跨变量卷积 def __init__(self, input_dim, hidden_dim): super().__init__() self.input_dim input_dim # 核生成网络从共享特征映射到卷积核参数 self.kernel_fc1 nn.Linear(input_dim, hidden_dim) self.kernel_fc2 nn.Linear(hidden_dim, input_dim * input_dim) def forward(self, x): batch, ts, dim x.shape out [] for t in range(ts): xt x[:, t, :].unsqueeze(-1) # (batch, dim, 1) # 动态生成当前时间步的卷积核权重 kernel torch.tanh(self.kernel_fc1(xt.squeeze(-1))) kernel self.kernel_fc2(kernel) # (batch, dim * dim) kernel kernel.view(batch, dim, dim) # 使用矩阵乘法实现跨变量卷积 yt torch.bmm(kernel, xt) # (batch, dim, 1) out.append(yt) return torch.stack(out, dim1).squeeze(-1)这段代码要重点理解三个地方一个是卷积核参数的映射方式。kernel_fc2把隐层向量映射到input_dim乘以input_dim的维度这就是一个完整的二维卷积核展开。对每一组变量对都有对应的权重。这个权重的数值不是固定的它由当前时间步的输入特征动态决定。另一个是矩阵乘法的运算逻辑。torch.bmm(kernel, xt)利用二维卷积核作用于当前时间步的变量向量相当于把变量之间的线性耦合关系应用了一遍。如果你想要非线性交互可以在这层之后加一个激活函数或者把卷积核的生成过程加深。第三是for循环明显效率偏低。完整实现会用2D卷积或者矩阵批量乘法来替代显式循环但这里用for循环是为了让你看清楚“逐时间步动态生成”到底是怎么发生的。竞赛场景里如果时间步很长建议用torch.einsum做批量计算能快很多。4.4 在竞赛论文里如何讲清楚这个方法竞赛论文和学术论文不同不需要太厚的数学推导但需要把“你用了什么方法为什么管用效果如何验证”讲明白。FACT这部分内容我建议在论文里按这样组织模型设计模块先引出问题——变量间结构是动态变化的固定权重矩阵表达不了这种变化。中间放架构图核心展示FGCC模块的输入输出流程。然后单独开一小节写动态卷积核生成机制用一句话说明卷积核由核生成网络基于当前时间步的状态实时计算得到因此不同时间步的交互权重是可以瞬变的。再用前面那个热力图做可视化证据最后给出消融实验去掉FGCC换成静态全连接计算预测误差上升多少。这个组织方式的逻辑传导很顺畅问题-方案-机制-证据-效果。评委顺着这个结构读下来不用额外花精力去“猜”你这个模型blocks到底怎么工作的印象分会高不少。5. 常见问题与实操避坑经验5.1 变量数量太大导致核生成网络参数爆炸核生成网络把特征映射到input_dim的平方维度如果变量数是50那最后就要生成2500个权重的卷积核参数规模一下就上去了。处理方式有两个一个是对变量做分组先按业务含义分组气象组、污染组、交通组组内用FGCC组间用简单的全局融合这样单次卷积核的规模会小很多另一个是使用低秩分解把卷积核矩阵分解成两个低秩矩阵的乘积大幅压缩参数量。竞赛里如果变量在30个以内不做低秩分解问题不大超过30个建议先分组。5.2 动态卷积核训练不收敛核生成网络离输入层比较近收敛速度会直接影响整体模型的稳定性。我在调试时遇到过的典型困境损失下降几个epoch后进入平台期无论怎么调学习率都下不去。后来排查原因是卷积核的初始化方式。核生成网络的输出在初始阶段如果数值太大矩阵乘法就会放大特征导致梯度爆炸。解决方法有两种把kernel_fc2的权重初始化设成均值为0、方差极小的分布或者在生成卷积核后加一层LayerNorm约束输出范围。后者效果更好但需要多调一个归一化层的参数。我在代码框架里没有加这个归一化属于刻意留白。你真训练时如果发现前期损失锯齿状跳动第一反应就往这里查基本能解决。这个优化操作很值得记住因为我换过好几种初始化方式最后真正把稳定性拉起来的就是这个LayerNorm。5.3 长序列预测误差累积这个问题在竞赛预测题里极其常见模型训练时用的是单步预测解码验证时却让你预测未来24个时间点。单步预测每一步的误差都会喂给下一步误差像滚雪球一样越来越大。FACT的全流程里有一个分层渐进预测的设计把预测拆成多个层级每层只预测一个较短的片段然后用预测出的片段拼接成完整未来序列。这个层级结构对缓解误差累积效果很明显。竞赛里如果遇到长时间预测的题目建议在FACT骨干上加一个自回归修正模块预测每一段之后将预测值反馈到输入窗口末尾让模型看到自己的预测结果再做下一步决策。这会在训练和推理阶段增加一些时间成本但预测精度提升是值得的。5.4 竞赛数据噪声大导致动态卷积核抖动剧烈竞赛数据的噪声水平通常比公开数据集更高。FGCC动态生成卷积核的特性在噪声大的时候容易出问题卷积核随着噪声来回抖动导致预测波动剧烈、方差变大。我的处理经验是给核生成网络加一个平滑约束把相邻时间步生成的卷积核差值加入损失函数做正则项。具体操作是取出相邻时间步的卷积核矩阵计算Frobenius范数差值乘一个较小的权重系数加到总损失上。这个正则项能压制卷积核的高频抖动让动态交互的变化更平滑、更贴近真实物理过程的连续性。毕竟真实世界里变量交互模式的切换通常不是阶跃的而是渐变的。5.5 快速判断FACT是否适合当前赛题不是所有预测题都适合直接上FACT。我用朴素的标准做判断如果题目给的变量少于5个而且变量之间相关性本身很弱那动态交互建模就是杀鸡用牛刀直接上LSTM或者GRU结果可能更好更稳如果变量在5个以上而且你观察到数据里有明显的联动现象或不同时间段的交互转移比如交通、气象、能源这些场景那FACT就处在能发力的范围内。还有一个判断信号值得留意先做一个快速的Granger因果检验看看变量之间是否存在双向因果或者随时间变化的因果结构。如果检验结果显示滞后变量之间有显著的交叉预测能力用FACT的动态交互建模会如鱼得水。如果因果结构很微弱说明变量可能基本独立FACT的优势就不明显。这个检验在动手训练模型之前花半个小时做一下能给你省一整天的无效训练时间。6. 写在最后关于这个方法我的一些实践感受研究FACT的过程中给我最大触动的点在于它把“跨变量交互”这个被很多人用一句“注意力机制”带过的问题重新拉回到一个可解释、可操作的粒度上。传统注意力机制确实能看到变量间的关联但它给的是一个点乘相似度缺乏空间结构的建模能力FGCC直接生成卷积核来操作变量向量交互不再是相似度分数而是真正改变了特征向量的组合方式。这种区别在竞赛数据上的表现就是预测曲线在突变时刻的响应更快不再拖着长长的滞后。建模竞赛不比拼谁的模型看起来更炫拼的就是谁能更准确地还原数据背后的生成机制。FACT的思路之所以适合竞赛场景本质上是它承认了一个事实变量之间的不是固定不变的而是会随着时间、随着外部条件不断重组的。把这个动态性建模出来预测精度和解释力都会上一个台阶。如果大家在实际复现过程中遇到问题欢迎随时找我讨论我也很想知道这个方法在不同竞赛题目上的表现差异。