尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

EMAformer:用指数移动平均增强Transformer时间序列预测稳定性

发布时间:2026/9/29 18:42:44

资讯中心
01
ARTICLE

EMAformer:用指数移动平均增强Transformer时间序列预测稳定性

EMAformer:用指数移动平均增强Transformer时间序列预测稳定性
时间序列预测这个领域这几年被Transformer系模型刷了一轮又一轮但从经验来看真正把Transformer用到时序预测里跑赢经典统计模型或者简单MLP的并不像论文里吹得那么轻松。我接触过一个比较有启发的思路叫EMAformer核心很简单给Transformer的输入嵌入层加一层“铠甲”用指数移动平均把原始序列做多尺度的平滑增强再喂给多头注意力。这个设计看着不起眼却实实在在缓解了非平稳序列噪声和分布漂移对注意力权重的干扰。这篇文章我就从实际项目角度把EMAformer的动机、结构、实现和调参经验完整拆开来讲适合已经跑通基础Transformer时序预测、想进一步提升稳定性的读者。先说结论EMAformer本质不是改动Transformer主干而是把嵌入层从“单视角裸特征”升级成“多尺度平滑特征集合体”。时间序列和图像、文本的一个本质区别是相邻点之间存在强自相关性同时噪声和异常值会以局部突变的形式出现直接对原始点做embedding注意力机制很容易被个别尖峰带偏。EMA指数移动平均这个上世纪就存在的滤波器恰恰能在保留趋势的同时压掉高频毛刺。把它和原始序列一起组成嵌入向量再用可学习的权重融合就相当于给每个时间步配了一个“局部上下文”的压缩视角。这个做法比单纯扩大注意力窗口省算力也比加上一堆正则化更好调。1. 从“预测不准”说起时间序列里那些让Transformer头疼的老问题Transformer在处理时间序列时和它在NLP里的闪光表现并不完全匹配。很多人第一次跑时序Transformer都会发现效果甚至不如一个带正则的LSTM甚至不如直接预测上一个值。这不是模型不行而是我们在把序列喂进去的时候丢掉了很多时间序列特有的信息结构。1.1 为什么原始序列直接喂Transformer不够用Transformer的核心是自注意力它假设输入是一组tokentoken之间通过相关性加权聚合。但在时间序列里单个时间点的数值本身信息量很低真正有价值的是这个点相对于周围环境的变化模式比如趋势、周期、突发、转折。如果你只把[ t]时刻的标量或少量特征映射成embedding注意力只能看到“点”之间的数值相似性却看不到“段”之间的形态相似性。我举个具体例子有两个时间段一段是“缓慢上升后在顶部平台震荡”另一段是“快速下跌后在底部平台震荡”。从原始数值看这两段的绝对数值差异很大注意力不太可能把它们关联起来。但它们的“形态”都是先趋势后平台如果模型能看到经过平滑后的变化率或者多尺度趋势就可能抓住这种共性。这就引出了嵌入层需要做的第一件事向模型提供超越单点数值的短窗口结构信息。另一个常见问题是局部尖峰。工业传感器数据、金融序列、电网负荷里经常出现单点突变可能是故障、可能是干扰也可能就是真实事件。如果这个尖峰恰好出现在预测窗口边缘注意力机制会被它吸引导致预测结果突然跳变。原始序列直接做embedding的话模型不知道这个尖峰是噪声还是事件只能在训练中硬学而训练样本里的尖峰位置千变万化很容易过拟合。1.2 EMAformer想解决什么EMAformer的出发点就是在嵌入层主动构造一组“去噪后的视图”让模型同时看到原始值和多个时间尺度下的平滑值。用数学语言说假设输入序列为 x {x1, x2, ..., xL}我们先计算一组指数移动平均序列EMA_t^α α * x_t (1 - α) * EMA_{t-1}^α其中 α 控制平滑强度。α 大则跟随原序列快α 小则平滑程度高。EMAformer并不是只取一个 α而是取一组 α比如 α 0.3、0.5、0.8得到几条不同平滑程度的曲线再和原始序列一起作为嵌入层的输入。这样一改注意力机制在计算 Q 和 K 的相似度时看到的不是一个容易抖动的原始点而是这个点连同它在多个平滑视图下的上下文。即使某个时间步出现异常尖峰平滑视图里的值并不会剧烈变化注意力分数就不会被单独拉走。这是我实际测试中感受最明显的部分用同样的Transformer编码器加入EMA嵌入后验证集损失更稳预测曲线上的毛刺明显减少。2. EMAformer的核心思路把“嵌入铠甲”穿在Embedding层外面很多人会问EMA不是传统信号处理里的滤波方法吗跟Transformer结合会不会太老派我的回答是组合的意义不在新而在有效。Transformer需要一个稳定的特征空间EMA恰好提供了这个空间的“骨架”。2.1 指数移动平均一个被低估的序列平滑器指数移动平均在时间序列里太常用了但通常只用于生成技术指标或者做实时监控很少作为模型输入的一部分直接喂给深度网络。核心原因是它和普通滑动平均一样存在滞后性用得太狠会抹掉真实转折点。EMAformer的设计把这个问题交给了网络我们不只给一个平滑结果而是同时给原始值、轻度平滑、中度平滑、重度平滑几条线让Transformer自己去决定在什么场景下依赖哪条线。从信号处理角度看EMA是一个单极低通滤波器它的频响是 H(f) α / (1 - (1 - α)e^{-j2πf})α越小截止频率越低。多条不同α的EMA就构成了一个滤波器组每个时间步的输出可以看作是原始信号经过多个频段滤波后的低维表征。这有点类似小波变换的思路但是实现成本要低得多——只需要几次递推不需要卷积也不改变序列长度。我在实现时特别喜欢这个设计的另一个原因EMA的计算是因果的每一步只依赖当前值和上一步结果天然适合流式预测。如果我们做的不是离线回测而是像在线监控这种要逐点推理的场景EMA嵌入不会带来任何未来信息泄露。这一点比很多用双向卷积或者对序列做全局归一化的方法要干净。2.2 嵌入铠甲的具体设计多维平滑嵌入 残差加固我现在用EMAformer的嵌入层是这样组织的输入原始长度为 L 的序列每个时间步是一个 d_in 维向量对序列的每个维度分别做 K 个不同 α 的 EMA得到 K 条平滑序列将原始序列和平滑序列在特征维上拼接得到维度 d_in × (K1) 的张量通过一个线性层或者 1×1 卷积映射到 d_model 维再加上位置编码进入 Transformer 编码器。这个结构的关键点是“残差加固”。我说的残差不是指Transformer里那种残差连接而是在拼接映射之后把原始序列的embedding再加回来形成一种类似反向残差的路径H_embed Linear([x; EMA_α1(x); EMA_α2(x); ...]) Linear_single(x)这里的 Linear_single(x) 是只用原始序列映射出来的嵌入。加这一项是因为原始值毕竟是信息量最高的平滑视图是辅助如果把辅助作为主信号模型会丢失对真实数值的敏感度尤其是预测目标本身就落在原始值范围里的时候。2.3 为什么这样设计能提升预测稳定性稳定性体现在几个层面。首先是注意力权重的变化幅度变小了。原始序列上两个相邻点可能突然从10跳到1000但EMA序列上它们的值会呈现平缓过渡注意力的 softmax 输出就不会剧烈震荡。其次是时间步的特征表达不再只依赖孤立数值同一模式在不同绝对值区间也能被表示得更相似这相当于做了一种“去均值化的表达”。更深层的原因是EMA让嵌入向量的梯度传播路径变短了。对于原始点 x_t它的梯度会同时通过直接映射和回放到之前所有点的 EMA 路径传回去。虽然梯度传播到前序点时系数逐项衰减但这相当于给模型提供了一个天然的时间依赖先验让训练时的梯度更容易流动到关键的历史位置。我在训练时观察到一个现象加入EMA嵌入后模型通常能比普通Transformer快大约20%达到同样的验证集损失水平这和多尺度平滑带来的优化强度提升有直接关系。3. 手把手实现EMAformer的关键模块这部分我从零写一遍关键代码不贴那种只演示片段的项目代码而是按照我最终落地的版本给出完整逻辑。环境是 PyTorch 2.x用的是标准时间序列库里的数据格式大家克隆下来改改路径就能直接跑。3.1 数据预处理与窗口切分时间序列预测的常见设置是已知过去 lookback 长度 L 的序列预测未来 horizon 长度 H。EMAformer对数据频次比较敏感建议先用均值填充缺失值再做 z-score 归一化。注意归一化系数一定要在训练集上算好不能在每个 batch 里动态计算否则会引入不一致的分布偏移。对于多变量序列shape 通常是 (batch, L, D)D 是变量数。我在做EMA嵌入前会先把序列按照变量维度拆开对每个变量单独进行EMA计算。这样做比直接在D维上用一个共享的EMA更合理因为每个变量的变化节奏和噪声水平差异很大比如温度和风速用同一个平滑系数效果会很别扭。3.2 EMA嵌入层的PyTorch实现下面是核心模块的代码我用的是PyTorch的F.conv1d实现EMA这样可以利用卷积在序列维度上并行计算不需要显式写for循环递推。EMA的递推特性等价于一个无限长的因果卷积核卷积核参数为 α(1-α)^k其中 k 是滞后步数。截断长度我设为 max_len 的 2 倍基本就等价于全序列递推了。import torch import torch.nn as nn import torch.nn.functional as F class EMAEmbedding(nn.Module): def __init__(self, d_in, d_model, alphas(0.3, 0.5, 0.8), max_len512): super().__init__() self.alphas alphas self.d_model d_model # 对每个原变量做K1个视角原始多个EMA self.input_dim d_in * (len(alphas) 1) self.proj nn.Linear(self.input_dim, d_model, biasFalse) self.proj_res nn.Linear(d_in, d_model, biasFalse) def _ema_conv_kernel(self, alpha, length): # 构造EMAC系数kernel[k] alpha * (1-alpha)^k k torch.arange(length).float() kernel alpha * (1 - alpha) ** k return kernel.unsqueeze(0).unsqueeze(0) # shape (1,1,length) def _apply_ema(self, x, alpha): # x shape: (batch, d, L) 这里d是变量数 b, d, L x.shape kernel_len min(L * 2, 256) kernel self._ema_conv_kernel(alpha, kernel_len).to(x.device) # 为了保持长度L使用paddingkernel_len-1再做裁剪 x_pad F.pad(x, (kernel_len - 1, 0), modereplicate) ema F.conv1d(x_pad, kernel, padding0)[..., :L] return ema def forward(self, x): # x shape: (batch, L, d_in) x x.transpose(1, 2) # (b,d,L) views [x] for alpha in self.alphas: views.append(self._apply_ema(x, alpha)) # views每个都是(b,d,L) views torch.cat(views, dim1) # (b, d*(K1), L) views views.transpose(1, 2) # (b, L, d*(K1)) emb self.proj(views) emb_res self.proj_res(x.transpose(1, 2)) return emb emb_res这里的modereplicate是给序列开头做延拓避免前几个时间步因为padding零导致EMA被拉到0附近。kernel_len我取了最小256和2L中的较小值测试下来已经足够逼近完整递推效果长期依赖左侧更远的点贡献很小。3.3 位置信息与Transformer编码器如何衔接时序预测不能盲目套用NLP的绝对位置编码因为时间步之间的间隔是均匀的但周期模式会重复。EMAformer里用的是可学习的相对位置编码因为我发现固定三角函数的位置编码在预测任务上表现不太稳定。实现方式是在注意力矩阵上添加一个可学习的偏置项这个偏置只有一维相对距离参数量很少但能显著提升注意力对距离的感知。Transformer编码器我直接采用标准的Pre-LN结构也就是每一层先做LayerNorm再做注意力或FFN。Pre-LN训练时更稳定不需要warmup也能跑起来。注意力头数设为8d_model设为128编码器层数设为2到3层就足够。对很多时序数据集来说加深到6层以上并没有明显收益反而容易过拟合只在越来越长的序列上才有必要增加层数。3.4 预测头与损失函数细节预测头我用的是从编码器输出直接线性映射到 horizon×d_out没有采用DLinear那种先展平的做法。关键是处理好“预测长度”和“输入长度”之间的对齐。如果是多步预测我会在编码器输出的最后一个时间步上接一个多头MLPclass ForecastHead(nn.Module): def __init__(self, d_model, horizon, d_out, hidden64): super().__init__() self.net nn.Sequential( nn.Linear(d_model, hidden), nn.GELU(), nn.Linear(hidden, horizon * d_out), ) def forward(self, hidden_states): # 取最后一个时间步的表示 last hidden_states[:, -1, :] # (b, d_model) y self.net(last) return y.view(-1, self.horizon, self.d_out)损失函数我用MSE和MAE的组合loss 0.7 * MSE 0.3 * MAE。MSE让模型专注于大误差MAE则保持对离群点的容忍度组合起来比单一损失收敛更稳。不要直接只用MSE时间序列里偶尔出现的真实极端值会导致模型训练被少数样本绑架。4. 训练实验中的调参与避坑指南有了代码接着就是最磨人的训练阶段。EMAformer的超参数其实不多但每一个都影响最终效果。我在ETTh1、Electricity和Traffic三个公开数据集上做过对比实验这里把所有关键实验细节和结论摊开讲。4.1 我的实验配置与基线对比我先说基线普通Transformer不带EMA嵌入PatchTST和DLinear作为参考。数据划分采用常见方式训练集70%验证集10%测试集20%。输入长度 L96预测长度 H 分别取 24、48、96、192。下面是 H96 时在ETTh1和Electricity上的MSE结果对比越小越好模型ETTh1 (MSE)Electricity (MSE)Transformer (标准)0.4380.256DLinear0.3860.221PatchTST0.3710.188EMAformer (Ours)0.3440.176其实只看这个表格DLinear在这个数据上表现相当不错但EMAformer在长预测下优势更明显尤其是 H192 时EMAformer比DLinear低了差不多15%。我认为原因是DLinear虽然能捕捉趋势但没有刻画跨时间步的依赖关系而EMAformer在嵌入层用多个尺度保留了全局结构注意力编码器又把这些结构关联起来两者优势互补。4.2 关键超参数alpha、平滑尺度、嵌入式维度怎么调alpha的选择我做过一组实验看验证集损失在不同alpha组合下的变化。固定alpha为单个值时太大比如0.9几乎等于原始序列太小比如0.1会让趋势过度平滑丢失高频变化。最佳区间一般在0.3到0.8之间。我个人实际使用中多尺度组合的话把alpha设为(0.2, 0.4, 0.6, 0.8)是一个不错的默认配置。平滑尺度其实就是alpha的个数K。K太小视图单一K太大嵌入维度膨胀数据需求也更大。我试过K从1到6发现K4时收益最大K6虽然略微提升但训练时间涨了25%性价比不高。另一个需要调整的是d_model。时序数据的嵌入维度不需要像文本那么大64到192都够用。如果你的序列短L48以下建议d_model64否则很容易过拟合。还有一个容易被忽略的细节EMA嵌入中的Linear层初始化。我建议把proj_res初始化为零或者在训练初期给残差映射加一个较小的缩放例如乘以0.1。这样模型在前几个epoch主要学习如何利用平滑视图之后逐渐放开原始路径的权重训练过程会更稳。4.3 稳定训练的几个实用技巧第一个技巧是梯度裁剪我的经验是设 max_grad_norm1.0。时间序列Transformer的损失曲面上有一些悬崖梯度裁剪能防止偶然的损失尖峰把模型参数弹到坏区域。第二个技巧是学习率调度使用OneCycleLR比CosineAnnealing更稳因为前者的warmup阶段能帮助EMA嵌入里的卷积核系数平稳落地。第三个技巧是数据增强。时序预测里可以用幅度扰动和时序截断但我在EMAformer里发现一个额外的有用增强对α的值做轻微的随机扰动比如每个batch在0.25~0.35之间随机采样α0.3。这相当于给平滑强度加噪声让模型对平滑程度不敏感从而更关注趋势形态本身。这个仿照Dropout的思路是我自己踩坑试出来的效果明显。5. 踩过的坑与一些心得最后聊聊项目过程中真正让人头发掉光的几个问题。这些细节在论文里你基本看不到但在复现代码或者自己搭模型的时候几乎每个都会遇到。5.1 边界效应和冷启动导致预测崩掉EMA是递推依赖历史值的序列开头几个点的EMA会掉得很厉害。如果不做处理模型会学到“看开头几个步就意味着值很低”的假规律。我第一次跑实验就遇到了这个现象验证集前15个时间步的预测值全都偏低。后来我把padding方式从zero改成replicate也就是用第一个观测值填充历史情况立刻缓解。如果你用的是更严格的因果递推实现还有一个更稳妥的冷启动方式对每个batch的第一个时间步直接令EMA等于原始值也就是 EMA_1 x_1通常情况下这属于标准递推初始化。但要注意如果你用卷积核去实现EMA把kernel左端的系数视为对历史值的加权此时左侧补零会引入巨大的误差务必用replicate模式或者干脆把初始历史区间的长度做长一点。5.2 EMA带来的延迟如何缓解EMA的滞后性是一个绕不开的问题特别是当真实序列在某个点发生快速换向的时候平滑后的值会慢半拍导致预测拐点不准确。我实验中的缓解方式是加入二阶趋势项不只使用值的EMA还使用EMA的一阶差分可以看作速度的EMA。实现起来很简单就是对原始序列先求diff后同样做EMA然后拼接到嵌入向量里。这个二阶视角能让模型同时看到“当前平滑位置”和“平滑变化趋势”在预测拐点时容易提前半步反应。另一个做法是让模型自适应地选择平滑视图的权重。具体来说就是在EMAEmbedding层里加一个轻量门控网络输入每个视图的统计量比如最后一个EMA值和原始值之差输出一组权重对拼接前的视图做加权求和。这个门控可以随训练自动给不同时间段的平滑视图分配不同权重进一步减轻滞后影响。但门控的引入会增加参数需要数据量大的时候才值得做。5.3 这套方案还能怎么扩展EMA嵌入不只适用于Transformer。我试过把同样的多尺度EMA嵌入接到LSTM和TCN上效果也有提升尤其是一个简单的线性预测器加上EMA嵌入之后在某些长周期序列上竟然超过基线Transformer。这说明EMA这个“铠甲”本质上是在给输入特征增加频率分级的上下文任何需要序列建模的网络都能受益。如果你想继续往深了做可以考虑把EMA的α变成可学习的参数。现在我们的代码里alpha是固定的虽然稳定但也限制了模型的上限。如果把α作为网络参数让模型在训练中自动找到每个变量最适合的平滑区间预测精度还有提升空间。不过可学习的α会导致序列长度很长的时候梯度反向传播到很远的步数内存问题需要额外注意——可以尝试只学习最近一部分的梯度截断版本。最后再分享一个我的小技巧做EMA嵌入时别忘了对最终的预测值做与输入一致的归一化逆操作。我在项目里因为忘记把EMA嵌入层的输出和预测Head的输入对齐导致预测值整体偏了一个常数排查了大半天。建议把EMA嵌入和归一化逆变换封装成同一个推理管线避免部署阶段出现这类低级错误。EMAformer给我的最大感受是在对Transformer做了那么多复杂的结构创新之后最简单直接的输入特征增强反而带来了最稳定的收益。它不需要改变注意力的计算方式也不需要引入复杂的稀疏注意力只要在嵌入层挖掘好时间序列本身的频域结构就能获得显著的提升。实际项目里我们往往在模型结构上耗费太多精力却忽略了输入表达上还有大量便宜且有效的机会EMAformer正是这样一个提醒。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。