1. 从一次翻车经历说起为什么我要折腾FreqCycle去年下半年我接手了一个工业设备振动信号的预测任务。数据是每秒采样的加速度传感器读数目标是提前预测未来一段时间内的振动趋势用来做异常预警。一开始我走的是最常规的路子——LSTM加滑动窗口Python里几行代码就能搭起来训练也快MSE看着还挺漂亮。但部署到现场之后问题就来了模型对低频的整体趋势拟合得不错可一旦设备出现早期磨损那种高频的、细微的周期性抖动模型几乎完全捕捉不到。等到它终于“看见”异常时往往已经错过了最佳干预窗口。这个坑让我意识到一个很本质的问题传统时间序列预测模型尤其是基于LSTM的这类循环网络天然存在“低频偏好”。它们对平滑的、缓慢变化的趋势敏感但对中高频成分的建模能力很弱。原因也不复杂——MSE损失函数会倾向于让模型优先拟合幅值大的低频分量高频分量幅值小对总损失的贡献微乎其微梯度信号被淹没模型自然就“懒得学”。后来我翻了不少资料也试过一些改进方案比如在损失函数里加权、做差分预处理、引入注意力机制等效果有提升但都不够系统。直到我把目光转向频域开始认真思考“时频联合建模”这件事才慢慢摸索出一套相对完整的框架。这套框架我给它起了个名字叫FreqCycle核心思想就是显式地把中高频成分补齐用多尺度的方式在时域和频域同时做预测。这篇文章就是把这套框架的设计思路、核心细节、实操过程和踩过的坑完整地分享出来。如果你也在做时间序列预测尤其是遇到过高频信号丢失、多尺度特征难以兼顾的问题那这篇内容应该能给你一些直接能用的参考。文章会涉及Python代码、参数选择、损失函数设计、频域变换的实操细节适合有一定深度学习基础、做过LSTM时间序列预测的读者。当然我也会尽量把原理讲得通俗一些新手跟着思路走也能理解个大概。2. 核心设计思路为什么是“显式补齐”而不是“让模型自己学”2.1 传统时域预测的先天短板先说说为什么常规做法会丢掉中高频。假设你有一段时序信号它可以分解成不同频率成分的叠加。低频部分对应长期趋势中频对应周期性波动高频对应噪声或突变。用LSTM做预测时输入是历史窗口输出是未来窗口损失函数通常是MSE。MSE对误差是平方惩罚幅值大的低频分量一旦预测偏一点损失就很大而高频分量幅值可能只有低频的十分之一即使预测完全错误对总损失的贡献也很小。这就导致一个结果模型会优先把低频预测准高频部分直接“摆烂”。你去看预测曲线整体形状是对的但放大看细节高频抖动全被抹平了。这在很多场景下是致命的——工业故障诊断、金融高频波动、气象突变预测关键信息恰恰藏在中高频里。有人会说那我给高频加权不就行了理论上可以但实操中权重很难调。权重太小没效果权重太大模型会去拟合噪声反而把低频也带偏。而且加权只是在损失层面做文章模型结构本身并没有专门处理高频的通道治标不治本。2.2 频域视角带来的转机换个角度如果把信号变换到频域用傅里叶变换或者小波变换不同频率成分就变成了不同频段的系数。这时候你可以很直观地看到低频系数幅值大、数量少高频系数幅值小、数量多。在频域里做预测相当于把不同频段“拉平”到同一个尺度上模型可以更均衡地学习每个频段的规律。但纯频域预测也有问题它丢失了时间信息。你预测出未来一段时间的频谱但不知道每个频率成分在哪个时刻出现。所以最合理的方案是时频联合建模——既保留时域的时序结构又引入频域的频段分解让模型在两个域里同时学习。FreqCycle的核心设计就是基于这个思路把输入信号分解成多个尺度每个尺度对应不同的频段然后对每个尺度分别做时域预测同时显式地补齐中高频部分的预测结果。注意这里的“显式补齐”是关键——不是让模型自己去学高频而是通过结构设计强制模型对中高频分量进行单独建模和预测最后再融合回整体。2.3 多尺度分解的具体选型多尺度分解的方法有很多我试过几种主流的分解方法优点缺点适用场景小波变换时频局部化好多分辨率分析需要选小波基边界效应非平稳信号经验模态分解自适应强无需基函数模态混叠计算量大非线性非平稳傅里叶变换实现简单频段清晰无时间信息全局变换平稳信号多尺度滑动平均计算极快易于实现频段划分粗糙快速原型我最终选了小波变换作为主力分解方法原因是它在时频局部化上表现最好而且可以通过选择不同的小波基和分解层数灵活控制频段划分。具体用的是PyWavelets这个库分解层数根据信号长度和采样率来定一般取log2(N)左右N是信号长度。比如信号长度1024分解层数取6到8层比较合适。分解之后每个尺度的子序列就对应一个特定的频段。低频尺度对应趋势中频尺度对应周期波动高频尺度对应细节和突变。然后每个尺度单独送进预测模型最后把各尺度的预测结果重构回原始信号。这样一来中高频成分就有了专门的建模通道不会再被低频淹没。3. 核心细节解析从分解到重构的每一步3.1 小波分解的实操细节与参数选择小波分解这一步有几个参数直接决定后续效果我一个个说。小波基的选择。常用的有db4、sym8、coif5等。db4Daubechies 4是我用得最多的它在时频局部化和计算效率之间平衡得比较好。如果你的信号突变比较多可以试试sym8对称性更好重构时相位失真小。选基函数没有绝对标准建议拿一段典型数据做几次试验看哪个基函数分解后的子序列最“干净”。分解层数。层数越多频段划分越细但计算量也越大而且高层低频部分可能只剩几个点不利于建模。我的经验是分解层数L满足N / 2^L 16N是信号长度。比如N1024L最大取6因为1024/6416。再往上分最低频那层就只剩十几个点了预测意义不大。边界处理。小波分解在边界处会有失真这是不可避免的。PyWavelets提供了几种边界模式默认是symmetric我一般用periodization因为它在重构时能保证完美重建。如果你的信号本身是周期性的periodization效果最好如果不是zero或symmetric也可以但要注意边界几个点可能不准后续建模时可以裁掉。代码上大概是这样import pywt import numpy as np def wavelet_decompose(signal, waveletdb4, level6): coeffs pywt.wavedec(signal, wavelet, levellevel, modeperiodization) # coeffs[0]是低频近似coeffs[1:]是各层细节 return coeffs def wavelet_reconstruct(coeffs, waveletdb4): return pywt.waverec(coeffs, wavelet, modeperiodization)分解完你会得到level1个子序列第一个是近似分量低频后面是细节分量从高频到低频排列。每个子序列的长度和原始信号一样因为用了periodization模式可以直接送进模型。3.2 多尺度预测模型的结构设计每个尺度的子序列我用的预测模型是LSTM 全连接输出的结构。为什么还用LSTM因为它在时域建模上确实成熟稳定而且PyTorch里实现起来快。但这里有个关键改动每个尺度用独立的LSTM而不是共享一个模型。原因是不同频段的统计特性差异很大低频平滑、高频抖动共享参数会让模型难以兼顾。具体结构输入是历史窗口[t-T, t]输出是未来窗口[t, tH]。T是输入长度H是预测步长。LSTM层数一般2层隐藏单元数根据尺度调整——低频尺度可以用64或128高频尺度用32或64就够了因为高频分量本身信息量少太大会过拟合。全连接层输出H维直接对应未来H步的预测值。损失函数方面每个尺度单独算MSE然后加权求和。权重怎么定我的做法是按各尺度子序列的方差来定方差大的尺度权重大方差小的尺度权重小。这样既保证了低频趋势的准确性又不会让高频部分被完全忽略。具体公式total_loss sum(w_i * MSE_i) / sum(w_i) w_i var(subseries_i)这个权重是自动计算的不需要手动调省了不少事。3.3 “显式补齐”到底补的是什么这是FreqCycle最核心的一步。前面说了传统模型会丢掉中高频那FreqCycle怎么“补”具体做法是在预测阶段对中高频尺度的预测结果做一次额外的修正。修正的依据是历史数据中中高频分量的统计规律。比如高频分量在历史窗口内的能量占比、过零率、峰值间隔等特征这些特征可以提取出来送进一个小的回归网络预测未来窗口内高频分量的“强度包络”。然后用这个包络去调制LSTM输出的高频预测让高频部分的幅值更合理。听起来有点绕举个例子。假设历史窗口内高频分量的平均幅值是0.1但LSTM预测出来的未来高频幅值只有0.02明显偏小。这时候修正网络会根据历史统计预测未来高频幅值应该在0.08到0.12之间然后把LSTM的输出按比例放大。这样既保留了LSTM学到的时序模式又补上了幅值信息。修正网络的输入特征包括历史窗口内各尺度的能量比、均值、方差、过零率、自相关系数等。输出是未来窗口内各尺度的缩放因子。这个网络很小两层全连接就够训练时和主模型一起端到端优化。注意修正网络只对中高频尺度生效低频尺度不做修正因为低频本身预测已经比较准修正反而可能引入误差。3.4 重构与后处理各尺度预测完之后用pywt.waverec把系数重构回时域。重构前要注意预测出来的子序列长度是H但小波重构需要和原始分解时相同的长度结构。我的做法是先把预测的子序列拼接到历史子序列后面形成完整长度的序列再做重构。这样边界效应最小。重构之后可能还会有一些小的偏差比如整体幅值偏大或偏小。这时候可以做一个简单的幅值校准计算历史窗口内原始信号和重构信号的幅值比用这个比值去缩放预测结果。这一步不是必须的但在某些数据上能提升几个百分点的精度。4. 完整实操流程从数据到预测的每一步4.1 数据准备与预处理数据这块我拿一个公开的振动数据集做例子采样率1kHz信号长度1024。实际项目中你的数据可能更长可以切分成多个1024长度的片段每个片段作为一个样本。预处理步骤去均值每个片段减去自己的均值消除直流分量。归一化用全局均值和标准差做标准化注意不要用每个片段自己的统计量否则会引入信息泄漏。切分训练/验证/测试集按时间顺序切不要随机打乱时序数据随机打乱会破坏时序依赖。def preprocess(signal, global_mean, global_std): signal signal - np.mean(signal) signal (signal - global_mean) / global_std return signal4.2 小波分解与子序列构建对每个片段做小波分解得到level1个子序列。然后对每个子序列做滑动窗口切分构造输入输出对。输入长度T取128预测步长H取64。这样每个片段可以切出1024-128-641833个样本数据量足够。def create_samples(subseries, T128, H64): X, y [], [] for i in range(len(subseries) - T - H 1): X.append(subseries[i:iT]) y.append(subseries[iT:iTH]) return np.array(X), np.array(y)每个尺度都这样处理得到多组训练数据。4.3 模型训练与参数配置训练时每个尺度的LSTM独立训练但修正网络是共享的因为修正网络输入的是统计特征和具体尺度无关。优化器用Adam学习率1e-3batch size 64训练100个epoch早停patience设15。关键参数汇总参数取值说明小波基db4平衡局部化与效率分解层数6N1024时输入长度T128覆盖足够历史预测步长H64根据需求调整LSTM层数2低频尺度可加层隐藏单元64/32低频/高频学习率1e-3Adam默认batch size64根据显存调整epoch100早停patience15训练过程中每个尺度的损失单独记录方便排查哪个尺度学得不好。如果某个高频尺度的损失一直不降可能是该尺度信噪比太低可以考虑对该尺度做平滑或降采样。4.4 预测与重构的完整代码预测阶段把测试集的输入送进各尺度模型得到各尺度预测然后拼接历史子序列做小波重构。def predict_and_reconstruct(models, corrector, history_signal, T128, H64): coeffs wavelet_decompose(history_signal) pred_coeffs [] for i, (coeff, model) in enumerate(zip(coeffs, models)): x coeff[-T:].reshape(1, T, 1) pred model.predict(x).flatten() if i 0: # 中高频尺度做修正 stats extract_stats(coeff[-T:]) scale corrector.predict(stats.reshape(1, -1)).flatten() pred pred * scale full np.concatenate([coeff, pred]) pred_coeffs.append(full) reconstructed wavelet_reconstruct(pred_coeffs) return reconstructed[-H:]重构出来的就是未来H步的预测信号。5. 常见问题与排查技巧实录5.1 高频尺度预测全是噪声怎么办这是最常见的问题。高频子序列本身信噪比低LSTM很容易学成“输出均值”或者“输出噪声”。我的排查思路先看高频子序列的方差如果方差极小比如小于全局方差的1%说明这个尺度本身信息量很少可以考虑直接置零不参与预测。如果方差正常但预测效果差检查输入长度T是否足够。高频分量的周期短T太大反而会引入无关历史。可以试试把T减小到64或32。还可以对高频子序列做一次低通滤波去掉极端噪声后再送进模型。5.2 重构后信号幅值偏大或偏小这通常是因为各尺度预测的误差在重构时累积了。解决办法在重构前对每个尺度的预测做幅值校准用历史窗口内该尺度的幅值比来缩放。重构后做一次全局幅值校准计算历史窗口内原始信号和重构信号的RMS比用这个比值缩放预测。如果偏差很大检查小波分解和重构的模式是否一致periodization模式必须前后一致。5.3 训练损失震荡不收敛多尺度模型训练时各尺度损失量级差异大可能导致总损失震荡。排查方向检查各尺度损失的权重是否合理。如果某个尺度权重过大会主导梯度。可以试试用方差归一化后的权重。学习率可能偏大试试降到5e-4或1e-4。修正网络的输出没有做范围限制可能输出极端值。可以在修正网络最后加一个tanh或sigmoid把缩放因子限制在合理范围比如0.5到2之间。5.4 预测步长H变大后精度骤降H增大意味着预测难度指数上升。如果H从64增到128后精度掉得厉害可以采用自回归预测每次只预测一步然后把预测值作为输入滚动预测。这样模型只需要学一步映射难度低但误差会累积。采用多模型策略不同H用不同模型H小的模型负责短期H大的模型负责长期最后加权融合。检查数据本身是否具有长程可预测性。有些信号本质上就是混沌的H太大谁也预测不准。5.5 常见问题速查表问题现象可能原因排查方法解决技巧高频预测全为均值信噪比低梯度弱看高频方差置零或降T重构幅值偏差大误差累积对比RMS幅值校准损失震荡权重失衡看各尺度损失方差归一化权重H大精度降长程难预测看自相关自回归或多模型边界失真小波边界效应看首尾点裁掉边界或换模式实操心得小波分解的层数不要贪多。我一开始分了10层结果最低频那层只剩1个点根本没法建模。后来改成6层效果反而更好。层数选择的核心原则是每个子序列至少要有几十个点否则预测没有统计意义。6. 这套框架还能怎么扩展FreqCycle目前是我在振动信号上验证的但它的思路可以迁移到很多场景。比如金融时序预测可以把价格序列分解成趋势、周期、波动三个尺度分别建模气象预测里温度、湿度、风速也可以做多尺度分解。核心逻辑是一样的不要让一个模型去同时学所有频段而是分而治之再显式补齐容易被忽略的中高频。如果你用的是Transformer而不是LSTM也完全可以套这个框架。把每个尺度的子序列送进独立的Transformer编码器修正网络换成基于注意力的统计特征融合效果可能更好。我最近在试的一个变体是用Informer替换LSTM在长序列预测上表现更稳但计算量也上去了需要根据实际资源权衡。代码层面我建议把分解、建模、修正、重构四个模块解耦每个模块单独测试。这样出问题的时候能快速定位是哪个环节的锅。比如你可以先用真实子序列做重构验证重构模块没问题再逐步替换成预测子序列看误差从哪一步开始放大。最后分享一个小技巧在训练初期先把修正网络的权重冻结只训练各尺度的LSTM。等LSTM收敛得差不多了再解冻修正网络一起微调。这样训练更稳定收敛也更快。我试过直接端到端训练前期损失震荡很厉害分阶段训练后曲线平滑多了。