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

VMD+Attention+LSTM:时间序列预测的分解注意力混合模型实战

发布时间:2026/9/13 18:54:12

资讯中心
01
ARTICLE

VMD+Attention+LSTM:时间序列预测的分解注意力混合模型实战

VMD+Attention+LSTM:时间序列预测的分解注意力混合模型实战
简介基于VMD-Attention-LSTM的时间序列预测模型Python实现面向深度学习与时间序列方向初学者及课程设计、毕业设计场景。方案融合变分模态分解与注意力机制给出从数据处理、模型构建到训练预测的完整参考流程内含详细代码注释与项目说明便于按步骤理解算法细节。压缩包共28个文件、约5.26MB包含6个Python源码、9份Excel原始数据、模型权重文件、处理后的CSV数据、图文说明与项目报告等覆盖数据拆分、窗口构造、模型搭建、训练评估与预测等环节。已有635人学习/下载。代码采用前30天数据预测第31天的滑动窗口设计内置128单元LSTM、点积注意力与Dropout防过拟合机制并提供训练好的模型权重与单序列预测脚本用户可直接运行结果也可基于小数据集快速验证算法便于后续扩展。1. 为什么时间序列预测要叠 VMD、Attention 和 LSTM做时间序列预测的人十有八九会遇到同一个尴尬数据明明是干净的LSTM 也跑起来了可 loss 下降得像个假模型val loss 却一动不动的——典型过拟合。这个资源给了一个很实在的解法不是把网络堆得更深而是在进 LSTM 之前先用 VMD 把序列拆开再用 Attention 把拆出来的模态按重要程度加权。思路很直接VMD 负责把非线性、非平稳的信号分解成有限带宽的模态分量Attention 负责让模型自己决定看哪几个时间步、哪几个特征更重要LSTM 再负责把这些信息按时间顺序递推下去。整套代码用 Python 写有训练脚本、预测脚本、训练好的权重和一份湖北数据的预测结果结构清楚得可以直接抄作业。适合正在做课程设计、毕业设计或者刚开始接触深度学习时序预测的从业者也适合那些已经跑通基础 LSTM、想看看加分解和加注意力到底能带来什么变化的人。2. 数据准备与 VMD 分解从原始序列到 (840, 30, 15) 的训练张量2.1 数据源与滚动窗口的构造逻辑压缩包里放了北京、湖北、天津、上海、深圳、广东、重庆、福建等多个省市的时间序列表格式是 xlsx处理后的数据被整理成了一份统一的 CSV。第一次打开处理后的数据表.csv时你看到的应该是按日期排序的数值型序列每一列是一个地区或一个指标没有缺失值也不用再清洗。真正麻烦的是怎么把一维序列变成 LSTM 能吃进去的三维张量。代码里给出的答案是用前 30 天的数据预测第 31 天窗口每次向后滑动一天。也就是说一个样本的输入形状是(30, 特征个数)标签是第 31 天的值。连续滑动得到样本后再按 8:2 左右切分训练集和测试集于是就有了代码里写死的(840, 30, 15)和(205, 30, 15)这两个张量形状。第一步要理解 30 这个时间步的含义它对应的是循环核时间展开步数太小了抓不住趋势周期太大了训练样本数会骤减代码选择 30 属于时序预测里比较稳妥的起点。import pandas as pd import numpy as np def build_samples(series, time_step30): X, y [], [] for i in range(len(series) - time_step): X.append(series[i : i time_step]) y.append(series[i time_step]) return np.array(X), np.array(y) # series 是某省市经过 VMD 分解并构造好的特征矩阵形状为 (总天数, 15) X, y build_samples(series, time_step30) # 手动切分保持时间顺序不乱 train_X, train_y X[:840], y[:840] test_X, test_y X[840:1045], y[840:1045] print(train_X.shape, test_X.shape) # (840, 30, 15) / (205, 30, 15)这段代码里的build_samples是标准的滑窗函数time_step30决定循环核展开多少步X的第二个维度永远是时间步第三个维度是每个时间步上的特征数。切分时我特意没有用train_test_split因为时间序列一旦随机打乱未来信息就会漏进训练集验证结果会虚高。后面那个840和205不是拍脑袋定的而是总样本数减去时间步后再按比例切出来的你换数据集的时候应该按int(len(X) * 0.8)这种方式计算别把边界写死。2.2 VMD 分解的代码实现与 K 值怎么定VMD变分模态分解和 EMD 最大的区别是 VMD 把分解问题放到变分框架里求解每个模态被约束在中心频率附近不容易出现 EMD 那种模态混叠。在这个资源里VMD 承担的任务不是直接预测而是先把原始序列拆成若干个平稳性更好的子序列再把这些子序列作为 LSTM 的特征输入。常见实现是用vmdpy这个库安装方式就是pip install vmdpy核心参数就四个alpha是惩罚项K是模态个数tau是噪声容忍度DC决定是否把直流分量单独拆出来。from vmdpy import VMD import numpy as np def vmd_decompose(series, K5, alpha2000, tau0.0, DC0, init1, tol1e-7): # series: 一维数组长度为 T # 返回 u形状为 (K, T)每一行是一个模态分量 u, u_hat, omega VMD(series, K, alpha, tau, DC, init, tol) return u # 示例对湖北原始序列做 5 模态分解 raw pd.read_excel(14-22.7 湖北.xlsx).iloc[:, 1].values modes vmd_decompose(raw, K5) print(modes.shape) # (5, 总天数)K是最值得调的参数。K太小分解不充分趋势和噪声还搅在一起K太大会出现虚假模态相邻两个分量的中心频率重叠。判断办法很朴素画出各模态的频谱如果两个模态的中心频率几乎重合说明K过大如果某个模态里还明显残留高频毛刺说明K偏小。项目里默认用了 5 个模态并且只取部分模态构成特征背后还有一层考虑后面展开。2.3 15 维特征是怎么构造出来的训练数据特征维度是 15很多人拿到代码后第一反应是VMD 分解不是只有 5 个模态吗15 从哪来的注意项目说明里那句关键的话每个时间段只对应五个原数据的 VMD 分解特征而不是把所有分解结果全部丢进去。实验也证明了如果把全部模态一股脑全送进网络特征被压缩得太厉害模型会严重过拟合按当前方式截取五个原始序列的对应分解特征预测结果基本不受影响过拟合问题却明显缓解。我按资源里的思路给一个可落地的构造方法选 5 条原始序列比如 5 个省市的数据每条序列做 K3 的 VMD 分解取每条的 3 个模态5×315正好凑成每个时间步的 15 维特征。你也可以把 K 设为 5每条取前 3 个主能量模态同样能得到 15 维。关键不在于具体怎么拼而在于每个时间步上的 15 个特征必须在时间上对齐不能前 30 天用的是 A 序列的分解结果第 31 天却混进了 B 序列的分解结果。def build_feature_matrix(series_list, K3): # series_list: 5 个一维序列组成的 list feature_list [] for s in series_list: modes vmd_decompose(s, KK) feature_list.append(modes[:3]) # 取前 3 个主模态 # 拼接成 (T, 15) feature_matrix np.vstack(feature_list).T return feature_matrix拼接时注意np.vstack的方向modes[:3]得到的是(3, T)五个序列拼起来是(15, T)转置后才是(T, 15)对应 LSTM 要求的「每个时间步一个 15 维向量」。这个 15 维特征里既有不同序列自身的模态信息又有它们之间的横向关联Attention 层后面做的事就是在这些特征里挑出当前预测点最该看的那些维度。3. Attention-LSTM 模型结构点积注意力与双层 LSTM 怎么组合3.1 点积注意力为什么适用在这个场景时序预测里加 Attention最朴素的理解是LSTM 虽然能记住历史但对第 1 天和第 29 天的信息权重是均等的而现实中往往是某几个关键时间点决定了第 31 天的值比如节假日前后的突变、政策发布的当天。点积注意力通过计算 Query 和 Key 之间的相似度把这种「哪个时间步更重要」变成可训练的权重。资源里实现了attention_3d_block输入形状是(batch_size, time_steps, features)它做的事情就是典型的 scaled dot-product attention先生成 Q、K、V然后算 Q 和 K 的点积除以特征维度的平方根做缩放再 softmax 得到权重最后用权重加权 V。import tensorflow as tf from tensorflow.keras import layers def attention_3d_block(inputs): # inputs shape: (batch_size, time_steps, features) features inputs.shape[-1] # 用三个独立的全连接层生成 Q、K、V q layers.Dense(features, activationrelu)(inputs) k layers.Dense(features, activationrelu)(inputs) v layers.Dense(features, activationrelu)(inputs) # 点积相似度 缩放 score tf.matmul(q, k, transpose_bTrue) score score / tf.sqrt(tf.cast(features, tf.float32)) # softmax 归一化成权重 weight tf.nn.softmax(score, axis-1) # 加权求和得到上下文向量 context tf.matmul(weight, v) return context注意score矩阵的形状是(batch, timesteps, timesteps)score[i][j]表示第 i 个时间步应该给第 j 个时间步分配多少注意力。Dense(features)的作用不是改变维度而是让模型学习从原始特征到 Q/K/V 空间的映射。这里没有用多头因为数据集规模只有一千多条样本多头会把参数量撑得过大单头点积注意力已经够用。3.2 双 LSTM 层加 Dropout 的完整模型Attention_lstm的组装顺序是Attention 模块 → 两个 128 单元的 LSTM 层 → 每个 LSTM 后接 Dropout(0.5) → Flatten 展平 → 全连接输出层。输入形状在代码里写死为(30, 15)其中 30 是时间步15 是特征维度。两层 LSTM 都设置return_sequencesTrue这很关键——第一层输出的仍然是完整的时间步序列这样第二层 LSTM 才能继续按时间递推如果第一层只返回最后一步序列的时序信息到第二层就断了。from tensorflow.keras import Model, Input from tensorflow.keras import layers def Attention_lstm(input_shape(30, 15)): inputs Input(shapeinput_shape) # 注意力层先处理原始输入 att attention_3d_block(inputs) # 第一层 LSTM返回完整序列 x layers.LSTM(128, return_sequencesTrue, activationtanh)(att) x layers.Dropout(0.5)(x) # 第二层 LSTM同样返回序列 x layers.LSTM(128, return_sequencesTrue, activationtanh)(x) x layers.Dropout(0.5)(x) # 展平后接全连接输出第 31 天的预测值 x layers.Flatten()(x) outputs layers.Dense(1)(x) model Model(inputs, outputs) return model model Attention_lstm() model.summary()两个 LSTM 单元数都取 128不是随便定的。项目说明里明确提到经过测试 128 神经元数量在精度和训练成本上最均衡。activationtanh是 LSTM 默认的门控激活内部状态用 tanh 压缩到 [-1,1] 可以避免梯度爆炸。Dropout(0.5)放在 LSTM 输出之后而不是内部循环之间这种是标准 dropout如果要在时间步维度上做变体 dropout需要用LSTM(..., dropout0.5)参数两种位置作用不同不要混淆。3.3 为什么输出层只有 1 个神经元整个任务的标签是第 31 天的值一个连续数值所以输出层是一个无激活函数的全连接层等价于线性回归头。这里有个新手容易踩的坑以为是回归问题就在输出层加sigmoid或者tanh结果预测值全被限制在 [-1,1] 或 [0,1] 区间里完全对不上原始量纲。正确的做法是不加激活函数让输出层的线性组合直接映射到目标值范围如果你做了归一化记得预测完再做反归一化还原。4. 训练细节损失函数、回调与过拟合控制4.1 超参数表与选型理由训练阶段的设计比模型结构更能看出作者有没有实际调过参。学习率取1e-4Batch Size 取 128优化器用 Adam损失函数用 Huber最大迭代次数 500。这套组合放在小数据集上非常稳下面是完整的超参数表超参数取值选择说明LSTM 单元数128测试后精度与训练速度折中学习率1e-4小数据集上偏保守避免前期震荡Batch Size128与 840 条训练样本配合约 7 个 batch/epochDropout0.5抑制过拟合力度较大但要配合 Huber损失函数Huber对异常值不敏感比 MSE 稳优化器Adam自适应学习率免去手动衰减Epochs500配合回调取最优权重不需要手动卡 epochBatch Size 128 看起来和 840 条训练样本不匹配一个 epoch 只有 6 个 batch反而成了一种隐式正则化——梯度更新次数少、噪声大不容易陷入局部最优。1e-4的学习率在这种 batch 数量下不会跳得太猛。Huber 损失是新引入的关键点MSE 对离群点会给出平方级别的惩罚一旦某天数据出现尖峰梯度会被拉得很大Huber 在误差小于阈值delta时表现如 MSE大于阈值时退化为 MAE既保留了对正常样本的敏感度又限制异常样本的破坏力。4.2 Callback 保存最优模型权重500 个 epoch 不代表最终模型用的是最后一个 epoch 的权重。很多情况下训练集 loss 还在降验证集 loss 早就开始回升了这就是过拟合的信号。资源里用了 CallBatch 机制里的 ModelCheckpoint 来监控验证集指标只把最优点保存下来。具体做法是让回调监控验证集 Huber 损失modemin一旦验证损失创新低就把当前权重写到 checkpoint 文件里。from tensorflow.keras.callbacks import ModelCheckpoint, EarlyStopping from tensorflow.keras.optimizers import Adam from tensorflow.keras.losses import Huber model.compile(optimizerAdam(learning_rate1e-4), lossHuber(delta1.0), metrics[mae]) checkpoint ModelCheckpoint( checkpoint/my_model.ckpt, monitorval_loss, modemin, save_best_onlyTrue, save_weights_onlyTrue, verbose1 ) early_stop EarlyStopping( monitorval_loss, patience30, restore_best_weightsTrue ) history model.fit( train_X, train_y, validation_data(test_X, test_y), batch_size128, epochs500, callbacks[checkpoint, early_stop] )逻辑说明save_best_onlyTrue保证磁盘上始终只有最优权重一组文件不会像默认行为那样每个 epoch 都存一遍。save_weights_onlyTrue只存权重不存结构文件小加载时用代码里重新定义的模型结构再load_weights就行。EarlyStopping的patience30说直白点就是验证损失连续 30 个 epoch 不创新低就提前终止400 个 epoch 跑完的情况其实很少见资源里标注 500 是上限。4.3 为什么 VMD 特征能缓解过拟合回到项目说明里那个实验现象直接用所有时间原数据 VMD 分解后的特征作为输入数据量被压得过小网络严重过拟合改成五个原始序列的对应模态后过拟合明显缓解。这个现象背后的原理是特征维度从「全量分解的高维拼接」降到 15 维之后模型的自由度被迫减小LSTM 和 Attention 只能学习有限特征里最稳定的映射关系。相当于给模型加了一个隐式的特征选择器——VMD 分解本身也滤掉了一部分高频噪声LSTM 拿到的是相对干净的模态输入不会花大量参数去拟合噪声细节。验证过拟合是否被控制住直接看训练集 loss 和验证集 loss 的差距。我一般会画一个双纵轴图左边是训练 Huber loss右边是验证 Huber loss如果两条曲线都在下降且末端差值小于 20%说明模型在泛化如果训练 loss 一路冲到 0.1 以下而验证 loss 停在 0.5 上下说明 Attention 的权重没有学到有意义的分布多半是特征没对齐或者 K 值不对。5. 预测应用与扩展技巧加载最优权重后的三件事5.1 用后 100 个时间点做预测验证资源里把湖北原数据预处理后的后 100 个时间点专门拿来测试目的是模拟「模型没见过的最新鲜数据」。加载权重后输入形状必须是(100, 30, 15)而不是(100, 15)——因为每个预测点都需要前面 30 个时间步的历史特征。一个常见的做法是保留原始序列最后 130 个时间步前 30 个作为首个预测窗口的输入预测出第 31 个点后把这个点拼到序列末尾作为新的历史再切出 30 个时间步预测下一个点这样滚动下去直到生成 100 个预测值。import numpy as np def rolling_predict(model, history, steps100, time_step30): # history: (T, 15) 特征矩阵 preds [] tmp history.copy() for _ in range(steps): # 取最后 30 个时间步作为输入 x tmp[-time_step:].reshape(1, time_step, 15) y_pred model.predict(x, verbose0)[0, 0] # 构造下一时间步的特征向量并拼回历史 next_feature build_next_feature(y_pred) # 生成 15 维特征 tmp np.vstack([tmp, next_feature.reshape(1, 15)]) preds.append(y_pred) return np.array(preds)这里build_next_feature是特征构造函数的反向过程把预测出的标量值还原成当天的 15 维特征再拼回历史。注意如果训练前做了归一化预测出的y_pred是归一化区间内的值计算误差指标前必须反归一化到原始量纲否则 RMSE 和 MAPE 完全没有可解释性。资源里的pred_hubei_set.npy就是这组预测结果的 Numpy 数组你可以直接和真实值相减算一下最大误差出现在哪个区域往往能发现 Attention 权重在那些位置确实会重新分配注意力。5.2 三个实战级调优建议第一个建议是改大数据集再谈模型能力。这份代码只用了一个较小数据集训练结构上是完整的但 840 条样本对双 LSTM 加 Attention 来说还是偏少有能力的可以通过文件里的13-21.6北京.xlsx、13-22.8天津.xlsx等多省市数据做多序列联合训练把样本量扩到几千条过拟合会进一步缓解。第二个建议是调K值时要配合模态能量看。VMD 分解后计算每个模态的方差贡献率去掉累计贡献率低于 90% 的模态再进特征矩阵比盲目固定 K3 或者 K5 更合理。实现时可以在vmd_decompose函数里加一行variance_ratio np.var(u, axis1) / np.sum(np.var(u, axis1))然后根据这个比值选择保留前几个模态。第三个建议是不要迷信测试集指标。pred_hubei_set.npy里的 100 个点是滚动预测出来的误差会随着步数增加而累积所以只看第 1 步的预测误差是不够的。多步预测的误差曲线如果呈现单调上升说明模型把历史预测值当作真实值回传时误差在扩散这时候应该把time_step加大到 60 或者改用 seq2seq 结构在解码端重新生成特征而不是继续加大训练轮数。本文还有配套的精品资源点击获取
02
RELATED NEWS

相关资讯

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

03
WHY YAOTU

想打造同款高转化官网?

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

场景化定制

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

营销型架构

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

全周期服务

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

免费获取你的建站方案

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