
1. 从一次翻车的预测任务说起去年下半年我接手了一个工业设备振动信号的预测项目目标是根据过去若干小时的传感器读数预测未来一段时间的振动趋势。数据采样率不低每秒几十个点信号里既有缓慢漂移的低频趋势也有设备轴承摩擦产生的高频抖动。我一开始用的是经典的LSTM时间序列预测方案堆了两层调了调学习率训练损失降得挺漂亮验证集上的MSE看着也还行。结果上线之后现场工程师反馈说预测曲线“太肉了”——低频的大趋势跟得还行但那些高频的、快速变化的抖动几乎全被抹平了。设备一旦出现异常的高频冲击模型根本反应不过来等预测值开始抬头的时候实际故障已经发生了。这个坑让我重新审视了一个老问题时间序列预测里中高频成分到底该怎么处理。低频趋势好预测因为它平滑、周期长、信噪比高高频成分难预测因为它变化快、噪声大、容易被模型当成“不可学习的随机扰动”给忽略掉。但恰恰是这些中高频成分在很多场景里才是真正有价值的信息——设备故障的早期征兆、金融数据的短期波动、电力负荷的尖峰变化都藏在中高频段里。后来我接触到FreqCycle这个思路它做的事情说白了就是显式地把中高频成分补齐而不是指望模型自己去学。这个“显式”两个字是关键也是我想在这篇博文里重点聊的东西。如果你正在做时间序列预测尤其是用LSTM或者Transformer这类模型发现高频部分总是预测不准那这篇内容应该能给你一些可以直接抄作业的思路。2. FreqCycle到底在解决什么问题2.1 传统时频预测框架的“高频失明”现象先说清楚问题出在哪。绝大多数时间序列预测模型不管是LSTM、GRU还是Transformer本质上都是在时域上做文章。它们把序列当成一串数字然后学习从过去到未来的映射关系。这个思路对低频趋势很友好因为低频信号在时域上就是缓慢变化的模型有足够多的历史点去捕捉规律。但高频成分不一样。高频信号在时域上的表现是快速振荡相邻两个点之间可能就发生剧烈变化。模型在学习的时候会面临一个尴尬的局面高频部分的模式太复杂参数空间里很难找到一个稳定的映射同时高频部分往往夹杂噪声模型很容易把它当成噪声一起忽略掉。结果就是模型学会了“画大趋势”但把高频细节全丢了。我做过一个简单的实验用同一份数据分别训练LSTM和FreqCycle然后把预测结果做FFT变换看频谱。LSTM的预测结果在低频段能量跟真实值基本吻合但到了中高频段能量直接掉了一个数量级。这就是典型的“高频失明”。2.2 为什么“显式补齐”比“让模型自己学”更靠谱FreqCycle的核心思路是不指望模型从零开始学会高频模式而是先把中高频成分从原始信号里分离出来单独处理再显式地补回去。这个思路背后的逻辑其实很朴素高频成分的预测难度和低频成分不在一个量级上用同一套参数去拟合两者必然顾此失彼。打个比方你要画一幅山水画远山的轮廓低频用大笔触慢慢描近处的树叶纹理高频得用小笔触细细勾。如果你只用一支大笔树叶就糊成一团只用小笔远山又画得太慢。FreqCycle的做法相当于准备两支笔一支画大轮廓一支补细节最后拼在一起。具体到技术实现上FreqCycle通常会做这么几件事先用某种时频分解方法比如小波变换、经验模态分解或者简单的滑动窗口FFT把原始序列拆成低频、中频、高频几个分量然后对低频分量用常规的时序模型预测对中高频分量用专门设计的模块处理最后把各分量的预测结果重构回原始信号。这个“分解-分别预测-重构”的流程就是“显式补齐”的核心。2.3 多尺度时频预测的适用场景这套框架不是万能的它最适合的场景有几个特征信号本身有明显的多尺度结构低频和高频成分都有实际意义高频成分虽然难预测但并非完全随机存在一定的可学习模式对预测精度要求高尤其是对突变和尖峰的捕捉有硬性需求。工业设备监测、电力负荷预测、金融高频数据、气象时序、交通流量预测这些场景都符合上述特征。反过来如果你的数据本身就是平滑的或者高频部分纯粹是噪声没有任何信息量那用FreqCycle就是杀鸡用牛刀常规LSTM足够了。3. 核心模块拆解与实操要点3.1 时频分解选小波还是选EMD时频分解是FreqCycle的第一步也是决定后续效果的关键一步。常见的选择有两大类小波变换Wavelet Transform和经验模态分解EMD及其变体。小波变换的优势是数学基础扎实分解结果稳定可以通过选择不同的小波基和分解层数来精确控制频段划分。缺点是基函数一旦选定就固定了对非平稳信号的适应性稍弱。EMD的优势是自适应性强不需要预设基函数分解出来的分量IMF直接对应信号本身的振荡模式。缺点是容易出现模态混叠而且计算量比小波大。我个人的经验是如果信号相对平稳或者你对频段划分有明确要求优先用小波如果信号非平稳特征明显且你愿意花时间调EMD的参数可以试试EMD。在实际项目中我更多用的是小波因为它的可控性更好调试起来心里有底。小波分解的关键参数有两个小波基和分解层数。小波基常用的有db4、sym8、coif5等db4适合捕捉突变sym8适合平滑信号coif5在两者之间比较均衡。分解层数决定了频段划分的粒度层数越多高频段分得越细但计算量也越大。一般来说分解层数取3到5层就够了再多了高频段的信息量太少反而不好建模。import pywt import numpy as np # 假设signal是原始时间序列长度至少为2^5 wavelet db4 level 4 # 小波分解 coeffs pywt.wavedec(signal, wavelet, levellevel) # coeffs是一个列表coeffs[0]是低频近似系数coeffs[1:]是各层细节系数 # 从高频到低频依次为coeffs[-1], coeffs[-2], ..., coeffs[1] # 重构各分量 # 低频分量 low_freq pywt.waverec([coeffs[0]] [np.zeros_like(c) for c in coeffs[1:]], wavelet) # 中频分量取中间两层 mid_coeffs [np.zeros_like(coeffs[0])] [np.zeros_like(c) for c in coeffs[1:-2]] coeffs[-2:] mid_freq pywt.waverec(mid_coeffs, wavelet) # 高频分量取最高两层 high_coeffs [np.zeros_like(coeffs[0])] [np.zeros_like(c) for c in coeffs[1:-1]] [coeffs[-1]] high_freq pywt.waverec(high_coeffs, wavelet)上面这段代码展示了如何用PyWavelets做小波分解并重构出低频、中频、高频三个分量。注意重构的时候要把不需要的系数置零这样每个分量就只包含对应频段的信息。注意小波分解要求信号长度是2的整数次幂如果不是需要先做padding。padding的方式有零填充、对称填充、周期填充等我一般用对称填充因为它在边界处的伪影最小。3.2 低频预测常规时序模型的用武之地低频分量拿到手之后就可以用常规的时序预测模型来处理了。LSTM、GRU、TCN、Transformer都可以选哪个取决于你的数据量和计算资源。数据量小的时候LSTM就够了数据量大且追求精度可以上Transformer。低频分量的特点是平滑、周期性强、信噪比高所以模型很容易学到规律。我在实际项目里通常用两层LSTMhidden size设64到128dropout设0.2左右训练个几十个epoch就能收敛得不错。损失函数用MSE或者MAE都行如果对尖峰更敏感可以用Huber损失。这里有一个细节值得注意低频分量的预测窗口和中高频分量的预测窗口可以不一样。低频分量变化慢可以用更长的历史窗口来预测更长的未来中高频分量变化快历史窗口太长反而引入噪声通常用较短的历史窗口预测较短的未来。这个多尺度窗口的设计也是FreqCycle“多尺度”三个字的体现。3.3 中高频补齐FreqCycle的核心创新点中高频分量的处理是FreqCycle最核心的部分也是跟传统方法拉开差距的地方。传统方法要么直接忽略中高频要么用同一个模型硬拟合效果都不好。FreqCycle的做法是设计专门的模块来“补齐”中高频成分。具体来说中高频补齐模块通常包含两个部分一个是模式提取网络用来从中高频分量的历史数据里学习可重复的振荡模式另一个是残差补偿机制用来处理那些无法被模式提取网络捕捉的随机成分。模式提取网络可以用一维卷积神经网络1D-CNN来实现因为卷积核天然适合捕捉局部振荡模式。卷积核的大小对应振荡的周期长度多个不同大小的卷积核可以同时捕捉不同频率的振荡。比如卷积核大小取3、5、7就能分别捕捉高频、中频、中低频的局部模式。残差补偿机制则是一个轻量级的全连接网络或者简单的自回归模型它的输入是模式提取网络的输出和真实值之间的残差输出是对残差的预测。这个机制的作用是兜底防止模式提取网络漏掉某些重要的高频成分。import torch import torch.nn as nn class MidHighFreqComplement(nn.Module): def __init__(self, input_dim, hidden_dim64): super().__init__() # 多尺度卷积核提取局部振荡模式 self.conv3 nn.Conv1d(1, 16, kernel_size3, padding1) self.conv5 nn.Conv1d(1, 16, kernel_size5, padding2) self.conv7 nn.Conv1d(1, 16, kernel_size7, padding3) self.relu nn.ReLU() # 残差补偿网络 self.residual_fc nn.Sequential( nn.Linear(48, hidden_dim), nn.ReLU(), nn.Linear(hidden_dim, 1) ) # 输出层 self.output_fc nn.Linear(48, 1) def forward(self, x): # x shape: (batch, seq_len, 1) x x.permute(0, 2, 1) # (batch, 1, seq_len) c3 self.relu(self.conv3(x)) c5 self.relu(self.conv5(x)) c7 self.relu(self.conv7(x)) # 拼接多尺度特征 multi_scale torch.cat([c3, c5, c7], dim1) # (batch, 48, seq_len) multi_scale multi_scale.permute(0, 2, 1) # (batch, seq_len, 48) # 模式提取输出 pattern_out self.output_fc(multi_scale) # 残差补偿 residual self.residual_fc(multi_scale) return pattern_out residual这个模块的设计思路是多尺度卷积负责捕捉可重复的振荡模式残差补偿负责兜底那些模式之外的成分。两者相加得到最终的中高频预测。实际用的时候这个模块的参数量不大训练起来很快但效果比直接用LSTM拟合中高频要好不少。3.4 重构与融合把分量拼回完整信号各分量预测完之后最后一步是重构。最简单的做法是直接把低频、中频、高频的预测值相加。但这样有个问题各分量是独立预测的预测误差会累积直接相加可能导致重构后的信号在某些点上出现不自然的跳变。我的做法是加一个融合层用一个轻量级的网络学习各分量的加权系数。这个融合层的输入是各分量的预测值输出是最终的预测值。训练的时候融合层和各分量预测模型可以联合微调这样各分量之间的协调性会更好。class FusionLayer(nn.Module): def __init__(self, num_components3): super().__init__() self.weights nn.Parameter(torch.ones(num_components) / num_components) self.bias nn.Parameter(torch.zeros(1)) def forward(self, components): # components: list of tensors, each shape (batch, seq_len, 1) stacked torch.cat(components, dim-1) # (batch, seq_len, num_components) weights torch.softmax(self.weights, dim0) fused torch.sum(stacked * weights, dim-1, keepdimTrue) self.bias return fused用softmax对权重做归一化保证权重非负且和为1这样融合层的输出就是各分量的凸组合不会出现权重为负导致的信号反转问题。4. 完整实操流程与参数选择4.1 数据预处理与频段划分拿到原始数据之后第一步是预处理。预处理包括去趋势、归一化、异常值处理。去趋势可以用差分或者减去滑动平均归一化用Min-Max或者Z-Score都行异常值用3-sigma或者IQR方法检测并替换。频段划分是预处理之后的关键步骤。用小波分解的话分解层数决定了频段边界。假设采样频率是fs分解层数为L那么第j层细节系数对应的频段是[fs/2^(j1), fs/2^j]。比如fs100HzL4那么分量频段范围对应物理意义低频近似系数0-3.125 Hz趋势、缓慢漂移中频第3、4层细节3.125-12.5 Hz周期性波动高频第1、2层细节12.5-50 Hz快速振荡、冲击这个划分不是固定的要根据你的数据特点来调。如果高频成分特别重要可以增加分解层数把高频段分得更细。4.2 各分量模型的训练策略各分量的模型可以独立训练也可以联合训练。独立训练的好处是每个模型可以单独调参互不干扰联合训练的好处是各分量之间的协调性更好但调参难度大。我一般先用独立训练快速验证各分量的效果然后再用联合训练做微调。独立训练的时候低频模型用MSE损失中高频模型用MSE加上一个频谱一致性损失。频谱一致性损失的作用是保证预测信号在频域上的能量分布跟真实信号接近这对中高频预测特别重要。def spectral_consistency_loss(pred, target): # 对预测和真实值做FFT计算频谱能量的差异 pred_fft torch.fft.rfft(pred, dim1) target_fft torch.fft.rfft(target, dim1) pred_energy torch.abs(pred_fft) ** 2 target_energy torch.abs(target_fft) ** 2 # 归一化后计算MSE pred_energy pred_energy / (pred_energy.sum(dim1, keepdimTrue) 1e-8) target_energy target_energy / (target_energy.sum(dim1, keepdimTrue) 1e-8) return torch.mean((pred_energy - target_energy) ** 2)这个损失函数的核心思想是不要求预测值在时域上逐点匹配但要求它在频域上的能量分布跟真实值一致。对于中高频预测来说逐点匹配太难了但能量分布匹配是可行的而且对实际应用来说能量分布匹配往往比逐点匹配更有意义。4.3 联合微调与推理部署独立训练完之后把所有分量模型和融合层拼在一起做联合微调。联合微调的损失函数是各分量损失加上融合后的总损失。学习率要调小一般是独立训练时的十分之一避免破坏已经学好的分量模型。推理部署的时候整个流程是原始信号 - 小波分解 - 各分量模型预测 - 融合层 - 重构信号。这个流程可以封装成一个端到端的模型输入历史序列输出未来序列。推理速度主要取决于小波分解和各分量模型的计算量在GPU上跑的话一般能做到毫秒级。实操心得联合微调的时候我习惯先把融合层的学习率设得比分量模型大一点让融合层先适应各分量的输出然后再统一学习率。这样收敛更稳定不容易出现某个分量被过度压制的情况。5. 常见问题与排查技巧实录5.1 高频预测总是滞后怎么办高频预测滞后是最常见的问题。表现是预测曲线在突变点之后才开始变化跟不上真实值的快速变化。原因通常是模型的历史窗口太长或者模型的感受野太大导致它对最近的变化不敏感。解决办法有两个一是缩短中高频模型的历史窗口让它只关注最近的变化二是在模型里加入注意力机制让模型自动关注最近几个时间步。我试过在1D-CNN后面加一个轻量级的自注意力层滞后问题改善很明显。5.2 分解层数怎么选才不翻车分解层数选多了高频段的信息量太少模型学不到东西选少了高频和中频混在一起还是不好处理。我的经验是先看信号的频谱找到主要的能量集中频段然后让分解层数保证每个主要频段都能被单独分出来。具体操作上可以先做一次FFT画出频谱图看看能量主要集中在哪几个频段。然后根据频段边界反推分解层数。如果频谱上能量分布比较连续没有明显的分界那就取3到4层先把低频和中高频分开中高频内部再细分。5.3 重构信号出现跳变怎么排查重构信号出现跳变通常是各分量预测值在边界处不连续导致的。排查思路是先单独看每个分量的预测曲线看跳变出现在哪个分量上然后检查该分量的模型在边界处的输出是否平滑最后检查融合层的权重是否合理。如果跳变出现在高频分量上可能是高频模型的输出噪声太大可以加一个平滑滤波或者增大残差补偿网络的权重。如果跳变出现在融合后的信号上可能是融合层的权重学习不充分可以增加融合层的训练轮数或者调整学习率。问题现象可能原因排查方法解决方案高频预测滞后历史窗口太长检查窗口长度与突变时间差缩短窗口加注意力机制分解层数不当频段划分不合理画频谱图看能量分布根据频谱边界调整层数重构信号跳变分量边界不连续单独查看各分量预测曲线加平滑滤波调融合层权重高频能量丢失模型忽略高频对比预测与真实的频谱加频谱一致性损失训练不收敛联合微调学习率太大观察损失曲线降低学习率分阶段训练5.4 计算资源不够怎么精简FreqCycle的完整版计算量不小尤其是小波分解和多尺度卷积。如果计算资源有限可以做几个精简一是减少小波分解层数只分低频和高频两层二是把多尺度卷积的卷积核数量减少比如从16减到8三是用GRU代替LSTM参数量更少四是融合层用固定的等权重不学习。这些精简会损失一些精度但在资源受限的场景下是值得的。我在一个嵌入式设备上部署的时候就是把分解层数降到2层卷积核数量降到8推理速度提升了将近一倍精度只掉了不到5%。6. 一些踩坑之后的个人体会FreqCycle这套框架我用了一年多踩过的坑不少有几个体会比较深。第一个体会是不要迷信分解层数越多越好。我一开始觉得分得越细越好直接上了6层小波分解结果高频段的信息量太少模型根本学不到东西预测出来的高频分量基本就是噪声。后来降到4层效果反而好了很多。分解层数要跟信号的采样率和实际频段分布匹配不是越多越好。第二个体会是中高频补齐模块的参数量要控制。中高频成分本身信息量就少如果模型参数量太大很容易过拟合。我试过用一个大卷积网络去拟合高频分量训练集上损失降得很低但验证集上高频预测一塌糊涂。后来把参数量砍到原来的三分之一泛化能力反而提升了。第三个体会是频谱一致性损失比时域损失更适合中高频。时域上逐点匹配对高频来说太苛刻了模型为了降低时域损失会倾向于输出平滑的预测值结果就是高频被抹平。换成频谱一致性损失之后模型更关注频域上的能量分布高频预测的细节明显丰富了很多。最后分享一个小技巧如果你手头的数据量不大可以先在公开数据集上预训练各分量模型然后用自己的数据做微调。预训练的时候可以用一些通用的时序数据集比如电力负荷、交通流量这些模型学到的振荡模式有一定的迁移性。微调的时候只需要少量数据就能达到不错的效果这对数据稀缺的场景特别有用。这套框架后续还可以往几个方向扩展一是把时频分解换成可学习的分解让模型自己学最优的频段划分二是把中高频补齐模块换成基于扩散模型的生成式方法进一步提升高频细节的丰富度三是把框架扩展到多变量预测利用变量之间的频域相关性来互相补全。这些方向我还在摸索有进展了再跟大家分享。