简介面向时间序列预测学习者的一份LSTM与Transformer混合建模实战资源定位于帮助读者掌握用深度学习组合模型处理非线性时序数据的完整流程。压缩包共13个文件包含Python训练脚本、CSV格式的数据集、需求说明文档、预测结果对比图以及IDE辅助配置等包体仅1.74MB轻量但内容链完整。已有1100人学习下载适合具备一定Python基础、希望尝试混合模型建模的初中级玩家。资源覆盖数据读取与预处理、LSTM捕捉局部依赖、Transformer建模全局模式、模型训练与评估等关键环节并通过真实值与预测值的可视化对比帮助判断效果需求文档则对项目目标、字段结构和预期输出做了说明便于快速上手实验。对金融、气象或销量预测等场景的时序建模实践具有直接参考价值。1. LSTMTransformer混合模型这份压缩包为什么值得动手跑一遍时间序列预测做到后期大家基本都会碰到同一个瓶颈LSTM 能记住短期波动但序列一长前面的全局规律就顾不过来Transformer 的注意力机制能看全局却又容易把噪声当规律。混合模型 LSTMTransformer 的核心思路就是把两者串起来——先用 LSTM 提炼局部时序特征再让 Transformer 在全局层面捕捉依赖。压缩包“混合模型时间序列预测实战-讲了.rar”里那套代码正好就是这么设计的。这份资源适合两类人一类是刚学完 LSTM 或 Transformer 基础、想看看两个模型怎么协同工作的新手另一类是在做销售预测、流量预测或负荷预测需要一份能直接改数据跑起来的参照工程。包里除了lstmtransformer.py完整代码还有data.csv、ceshi.csv两个数据集、需求文档和数据读取测试脚本连训练结果的可视化真实值预测值画图.png都一并打包了属于拿到手就能复现的那种实战项目不是只有概念讲稿的“空壳”。2. 拆包看结构需求文档、数据脚本与训练脚本怎么协作拿到 rar 包第一件事不是急着运行而是先把文件清单过一遍。这个包里的.idea是 PyCharm 的工程配置目录里面是workspace.xml、misc.xml、modules.xml这些 IDE 索引文件跟训练逻辑没关系可以直接忽略。真正要关注的是这几个文件各自的分工它们构成了一个完整的训练-验证链路。文件作用优先级需求文档.txt说明项目目标、数据集结构和预期结果先读data.csv训练数据模型学习规律用的样本核心ceshi.csv测试数据评估模型在未知样本上的表现核心数据读取测试.py验证数据能否被正确读取和预处理先跑lstmtransformer.py混合模型定义、训练与预测的完整代码核心真实值预测值画图.png模型预测值与真实值的对比可视化参考这个顺序也是我拆包的习惯先看需求文档把边界搞清楚再用数据读取脚本确认数据没坑最后才打开训练代码。2.1 需求文档.txt先确定目标、评估口径和数据约束很多初学者拿到项目包直接开跑结果训练完发现评估指标选错了白费半天。需求文档.txt这个文件虽然不起眼但它定义了整个任务的边界。通常它会写明三样东西目标变量是单列还是多列、预测未来多少个时间点、用 MAE 还是 RMSE 作为评估口径。这个包里的需求文档描述的重点是“混合模型在时间序列上的表现”并结合 LSTM 的序列建模能力和 Transformer 的全局注意力机制。实操时你需要确认的点是训练集和测试集是不是按时间顺序切分的data.csv与ceshi.csv的时间范围有没有重叠。如果有重叠那“测试”就名不副实模型看到的其实是已经见过的数据。我一般会先在需求文档里圈出两个关键词一个是数据频率是小时级、日级还是分钟级这决定了后面窗口长度怎么设另一个是预测步长是单步预测还是多步预测这直接决定了模型的输出维度和评估方式。这两点如果在需求文档里没写清楚就打开 CSV 文件自己看时间列。2.2 data.csv 与 ceshi.csv训练集和测试集的分工逻辑时间序列的数据集划分和普通机器学习不一样不能随机打乱必须按时间前后切。data.csv作为训练集通常占据序列前 70% 到 80% 的时间范围ceshi.csv是序列最后一段用来模拟“未来”。这个包里把两个文件分开存放就是为了避免你手动切片时不小心把顺序搞乱。用 pandas 打开先看列名和头几行确认时间列是标准格式还是纯字符串。实用做法是写两行快速的读取代码直接看数据结构import pandas as pd df_train pd.read_csv(data.csv) df_test pd.read_csv(ceshi.csv) print(df_train.head()) print(df_train.info()) print(df_test.head()) print(df_test.info())这段代码的作用很直接head()看前 5 行数据结构info()看有没有缺失值、列类型对不对。如果时间列被解析成了 object 类型而不是 datetime后面做时序序列化时会报错所以这一步是给后续建模清路障的。还有一个细节值得检查两个 CSV 的时间间隔是否均匀。比如data.csv是每 5 分钟一条结果中间缺了一段模型在训练时会学到不规律的时间间隔预测效果会恶化。快速检查方式是对时间列做diff()看间隔是否是固定值。如果发现缺失常见的做法是向前填充或用插值补上但前提是你得先发现这个坑。2.3 数据读取测试.py正式训练前的冒烟测试包里单独放一个数据读取测试.py这个习惯很实用。它相当于深度学习的冒烟测试——先确认数据链路是通的再跑大模型。深度学习模型训练动辄几十分钟如果在读取阶段就翻车浪费的时间成本非常高。这个脚本的核心逻辑通常是这样import pandas as pd import numpy as np # 读取数据并解析时间列 df pd.read_csv(data.csv, parse_dates[time]) df df.set_index(time) # 检查缺失值和基本统计信息 print(缺失值数量:, df.isnull().sum().values[0]) print(数据统计信息:) print(df.describe()) # 归一化处理这一步是为模型训练做准备 from sklearn.preprocessing import MinMaxScaler scaler MinMaxScaler(feature_range(0, 1)) scaled_data scaler.fit_transform(df.values) print(归一化后的数据形状:, scaled_data.shape)这里最关键的是parse_dates参数它把时间列自动解析成 pandas 的 datetime 类型。scaler.fit_transform在数据读取测试.py里出现是为了验证数据能否顺利转换成 0 到 1 区间的数值神经网络对这种缩放非常敏感不做的话训练很容易不收敛。如果这个脚本跑完没有报错、输出里的数据形状是(样本数, 特征数)说明数据链路是通的可以放心进入模型构建环节。如果这一段就挂了后面再怎么调模型结构都是白搭。3. 混合模型建模核心LSTM 与 Transformer 的衔接代码逐段拆解lstmtransformer.py是整个压缩包的核心文件。这个文件的代码结构一般是先做序列化把一维时间序列切成带标签的滑动窗口样本然后定义 LSTM 层负责局部特征提取再接 Transformer 编码器做全局依赖建模最后经过全连接层输出预测结果。理解这段代码的关键在于一个设计决策LSTM 输出的每个时间步的隐藏状态会被送入 Transformer 的注意力层——这是混合模型的价值所在。下面按代码逻辑顺序拆开讲。3.1 序列化滑动窗口长度怎么定直接决定“看多远”时间序列建模的第一步是把一维序列变成窗口样本。窗口长度通常叫look_back或window_size是一开始就要拍板的参数它在混合模型里既影响 LSTM 看到的局部范围也影响 Transformer 注意力矩阵的大小。def create_sequences(data, window_size): X, y [], [] for i in range(len(data) - window_size): X.append(data[i:i window_size]) y.append(data[i window_size]) return np.array(X), np.array(y) # 以24个时间步为窗口预测下一个时间步 window_size 24 X_train, y_train create_sequences(scaled_data, window_size) print(训练样本形状 X:, X_train.shape, y:, y_train.shape)这段代码做的事情就是把长度L的序列切成L - window_size个样本每个样本用前 24 个点预测第 25 个点。X的形状是(样本数, 24, 特征数)这正好符合 LSTM 的输入要求(batch, time_steps, features)。窗口大小怎么选如果是日粒度数据24 就代表看过去一天适合捕捉日周期如果是分钟级数据可能要看 1440一天才能捕捉到完整周期。一个常见做法是先跑一个快速实验对比不同窗口下的验证集误差选误差最小的值。窗口太小模型只看到局部噪声窗口太大则计算量上去而信息冗余这属于需要根据数据集本身反复试的参数。3.2 LSTM 层为什么要设置 return_sequencesTrue不少人第一次写 LSTM 接 Transformer 时在return_sequences这个参数上翻车。LSTM 默认只返回最后一个时间步的输出形状是(batch, units)但 Transformer 的注意力层需要的是完整的时间步序列形状是(batch, time_steps, units)所以必须设置return_sequencesTrue。from tensorflow.keras import layers, models # 定义 LSTM 模块提取局部时序特征 lstm_layer layers.LSTM( units64, return_sequencesTrue, dropout0.2, return_stateFalse )units64是隐藏状态维度控制模型容量。64 这个数值在很多中小规模时间序列项目里是性价比比较高的选择太小拟合能力不够太大容易过拟合且训练变慢。dropout0.2是在 LSTM 内部引入的正则化防止模型把训练集里的噪声也记住。注意这里没有加return_stateTrue因为我们要的是完整的隐藏状态序列作为 Transformer 的输入而不是最后一个细胞状态。如果只想用 LSTM 做特征提取然后接全连接层用return_sequencesFalse就够了但在这里不行——它会把时间维度吞掉Transformer 就没东西可以“注意”了。3.3 Transformer 编码器自注意力机制如何捕捉全局依赖LSTM 的输出经过一个可选的投影层后进入 Transformer 编码器。编码器的核心是多头自注意力MultiHeadAttention它让序列中每一个时间步都能直接看到其他所有时间步不管它们在时间上隔多远。这正是 LSTM 做不到的——LSTM 的隐藏状态传递是递归的信息要一步一步传过去。def transformer_encoder(inputs, num_heads, ff_dim, dropout_rate0.1): # 多头自注意力层让每个时间步能看到序列中所有时间步 attention_output layers.MultiHeadAttention( num_headsnum_heads, key_diminputs.shape[-1] )(inputs, inputs) # 残差连接 层归一化这是 Transformer 稳定训练的基石 attention_output layers.Dropout(dropout_rate)(attention_output) out1 layers.LayerNormalization(epsilon1e-6)(inputs attention_output) # 前馈网络对每个时间步独立做非线性变换 ffn_output layers.Dense(ff_dim, activationrelu)(out1) ffn_output layers.Dense(inputs.shape[-1])(ffn_output) ffn_output layers.Dropout(dropout_rate)(ffn_output) return layers.LayerNormalization(epsilon1e-6)(out1 ffn_output)这段代码的结构是标准的 Transformer 编码器块。第一行MultiHeadAttention里的key_dim设置成输入特征维度这样注意力矩阵的规模是(time_steps, time_steps)也就是每两个时间步之间的相关权重。后面的两个残差连接是重点——如果没有残差连接深层网络训练时梯度容易消失或爆炸加了残差连接和层归一化之后模型能稳定收敛。一个常见问题是时间序列输入需不需要像 NLP 那样加位置编码我的习惯是当窗口长度不超过 50 时LSTM 已经把时间顺序信息编码进了输出特征里Transformer 层不需要额外的位置编码但如果窗口很长比如 200 步以上建议在输入 Transformer 前把位置编码加到 LSTM 的输出上不然注意力机制会把不同时间步当成无序集合。3.4 整体模型拼装与训练配置早停、断点续训、学习率有了 LSTM 和 Transformer 编码器就可以把模型拼起来了。注意输出层用 Dense(1) 是因为这个项目做的是单变量单步预测输出的是一个归一化后的标量如果是多变量或多步预测输出维度要相应调整。import tensorflow as tf from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint # 组装 LSTM Transformer 混合模型 def build_mixed_model(window_size, num_features, lstm_units64, num_heads4, ff_dim128): inputs layers.Input(shape(window_size, num_features)) # 第一阶段LSTM 提取局部依赖 x layers.LSTM(lstm_units, return_sequencesTrue, dropout0.2)(inputs) # 第二阶段Transformer 编码器捕捉全局模式 x transformer_encoder(x, num_headsnum_heads, ff_dimff_dim) # 第三阶段展平并输出预测 x layers.Flatten()(x) x layers.Dropout(0.2)(x) output layers.Dense(1, activationlinear)(x) model models.Model(inputsinputs, outputsoutput) return model # 实例化并配置训练 model build_mixed_model(window_size24, num_features1) model.compile( optimizertf.keras.optimizers.Adam(learning_rate0.001), lossmse, metrics[mae] ) # 早停与模型保存防止过拟合的最好手段 early_stop EarlyStopping(monitorval_loss, patience20, restore_best_weightsTrue) checkpoint ModelCheckpoint(best_model.keras, monitorval_loss, save_best_onlyTrue)这段组装逻辑是输入经 LSTM 编码后获得带局部上下文的状态序列再由 Transformer 编码器对整条序列做全局注意力加权最后展平接一个线性输出。num_heads4意味着把特征分成 4 个子空间分别计算注意力每个子空间关注不同的依赖模式——有的关注周期规律有的关注突变点。ff_dim128是前馈网络的中间维度通常设成 LSTM 单元的 2 倍。EarlyStopping的patience20表示如果验证集 loss 连续 20 个 epoch 不下降就停止训练restore_best_weightsTrue会把模型权重回滚到验证集最优的那一步这等于给模型上了双保险。ModelCheckpoint则保证即使训练中途断了也有一个当前最优模型存盘不用从头再来——这就是我常说的“后悔药”。4. 避坑排查训练与评估阶段最容易翻车的四个细节混合模型结构相对复杂坑也比普通单模型多。这个项目包跑下来我复盘了自己和身边人常踩的四个问题每一条都是真实场景按“现象 → 原因 → 解决”的方式写在这里。4.1 归一化泄漏验证集精度虚高一上真实数据就崩现象训练时验证集 loss 一路走低但把ceshi.csv输入模型后预测图跟真实值差了一个量级。原因在数据读取测试.py里直接对整个数据集fit_transform包括测试集的部分。这样测试数据的信息提前被scaler记住了相当于考试前把答案翻了一遍训练时表现当然好。这是时间序列预测最典型的“数据泄漏”案例。解决严格按时间切分后再做归一化。只对训练集调用scaler.fit()然后分别对训练集和测试集用同一个 scaler 做transform()scaler MinMaxScaler(feature_range(0, 1)) scaled_train scaler.fit_transform(df_train.values) # 注意测试集只能用 transform不能用 fit_transform scaled_test scaler.transform(df_test.values)从那以后我每次跑时间序列项目都会强制检查一句全流程里fit_transform是不是只出现了一次且只在训练集上。4.2 随机种子不固定同一个代码跑两遍结果完全不同现象同一个lstmtransformer.py上午跑和下午跑训练曲线几乎对不上最后 MAPE 差了两个百分点。原因LSTM 的权重初始化、Dropout 的随机失活、训练数据加载时的 shuffle 都涉及随机数。不固定种子等于每次训练都是个新模型。对于工程复现来说这等于没有结果。解决在代码最开头固定所有随机源import random import numpy as np import tensorflow as tf random.seed(42) np.random.seed(42) tf.random.set_seed(42)同时把训练数据加载里的shuffleFalse或设置固定shuffleTrue且配上随机种子。注意 TensorFlow 2.x 的tf.random.set_seed不能完全保证跨平台一致但至少保证同一台机器上可以复现。混合模型训练成本高每次跑都要能回到同一个结果这是底线。4.3 预测值整体滞后曲线形状对但像一个时间步的“慢放”现象看图时发现预测曲线跟真实值曲线整体右移了一个节拍也就是模型预测值总是滞后于真实值这几乎是时间序列预测中最常见的挫败感来源。原因单步预测模型本质是拟合序列的局部惯性——如果序列变化平缓最优策略就是“预测值 ≈ 上一个真实值”模型学到的不是规律而是这个惰性逻辑。Transformer 的注意力机制有时会加剧这个现象因为它会把注意力集中到相邻时间步。解决先不要把滞后背锅给模型结构。第一招是看数据是否做过差分或趋势平稳化第二招是改用多步预测预测未来 N 个点而不是一个点在输出层把Dense(1)改成Dense(horizon)第三招是评估时用多步滚动预测而不是每一步都输入真实值“喂”给模型这样暴露模型真实推演能力。4.4 画图对比时坐标不一致真实值和预测值“比不齐”现象真实值预测值画图.png里两条曲线各自趋势都对但横轴对不上或者在纵轴上差了一个偏移量。原因一种是归一化后直接画图忘了inverse_transform还原到原始量纲另一种是训练集和测试集的索引没对齐画图时用各自的iloc位置就错位了。解决画图前务必先反归一化# 用训练时保存的 scaler 把预测值还原回原始范围 pred_original scaler.inverse_transform(pred_scaled.reshape(-1, 1)) true_original scaler.inverse_transform(y_test_scaled.reshape(-1, 1))然后统一用时间列作为横轴不要用行号。建议把画图逻辑单独放进一个函数里每次出图前检查三个东西横轴是时间格式不是整型、两条曲线的数组长度相等、量纲一致。这是我花过最不值当的一个晚上总结出来的血泪经验。5. 进阶验证滚动多步预测与残差分析把模型的底细摸清楚最后一个环节说一个具体技巧——用滚动预测来验证模型的稳定性。很多人在训练完模型后仅仅在测试集上跑一次单步预测画一张图感觉效果不错就收工了。但单步预测有信息优势每一步都用真实值作为输入误差不会累积。真实业务场景是连续的——上一步的预测误差会带入下一步所以必须做滚动预测把模型“放养”到未知序列上看它能撑多久。滚动预测的实现思路不复杂核心是用上一步的预测值替代真实值作为下一步的输入def rolling_predict(model, scaler, initial_window, future_steps): 滚动多步预测每步用上一步预测值作为输入 initial_window: 历史窗口数据长度等于 window_size future_steps: 要预测的未来时间步数 current_window initial_window.copy() predictions [] for _ in range(future_steps): # 当前窗口归一化后输入模型 current_window_scaled scaler.transform(current_window.reshape(-1, 1)) input_seq current_window_scaled.reshape(1, window_size, 1) # 预测下一个点并反归一化 pred_scaled model.predict(input_seq, verbose0) pred_value scaler.inverse_transform(pred_scaled)[0, 0] predictions.append(pred_value) # 更新窗口丢掉最旧的点加入刚预测出的点 current_window np.append(current_window[1:], pred_value) return np.array(predictions)这个函数的关键动作是每循环一次就把窗口向前推一格np.append(current_window[1:], pred_value)中的current_window[1:]是去掉窗口最旧的一个真实点pred_value是上一个预测值用来顶替最新的位置。滚动预测跑出来的结果如果和真实值在 5 步内还能保持趋势一致说明模型学到的是真规律不是“背题”。有了滚动预测结果后下一步是残差分析。把真实值与预测值的差值画成散点图观察残差是否随机分布在 0 轴两侧。如果残差呈现明显的周期性波动说明模型漏掉了某个周期成分如果残差在某个区间内系统性为正或为负说明模型有偏。最后用一组量化指标收尾不要让“效果不错”停留在肉眼层面from sklearn.metrics import mean_squared_error, mean_absolute_error import numpy as np mae mean_absolute_error(true_series, pred_series) rmse np.sqrt(mean_squared_error(true_series, pred_series)) mape np.mean(np.abs((true_series - pred_series) / true_series)) * 100 print(fMAE{mae:.4f}, RMSE{rmse:.4f}, MAPE{mape:.2f}%)RMSE 对大误差敏感如果 RMSE 比 MAE 大很多说明存在个别偏差极大的预测点要去检查是不是输入窗口里混入了异常值MAPE 适合用来横向对比不同模型的业务效果但数据里有接近 0 的值时它会爆炸需要配合看 MAE。这个压缩包的价值不在于代码能直接套用到所有场景而在于它把混合模型从“听说过”变成了“能上手跑”。你可以先把data.csv换成自己的数据调window_size和lstm_units观察结果变化再进一步改成多步预测比较单步与多步的误差差异。从那以后我每次做一个新的时间序列项目都强制按这个流程走一遍先读需求文档再跑数据脚本然后固定随机种子、排查归一化泄漏最后做滚动预测和残差分析。这套顺序帮我挡掉了不少训练到一半才发现数据有问题的情况希望帮到你。本文还有配套的精品资源点击获取