LSTM时序预测实战:从数据预处理到收益率预测的全流程解析

📅 发布时间:2026/10/12 16:18:18
LSTM时序预测实战:从数据预处理到收益率预测的全流程解析
简介基于 LSTM 的时序收益预测系统代码包适合 Python 与深度学习初学者以及希望快速上手金融时序建模的开发者。项目围绕大宗商品化工、有色、贵金属价格数据展开覆盖数据预处理、模型构建、训练、预测与效果评估操作文档中介绍了 LSTM 输入门、遗忘门、输出门等门控机制并涉及 Adam 优化器、均方误差损失、MAE/RMSE 评估指标能够帮助读者理解 RNN/LSTM 建模流程和时序数据切分方法。资源共 55 个文件除 Python 脚本外还包含训练好的 TensorFlow 模型文件、NumPy 数据、Excel 行情数据、训练日志、实验截图与操作说明文档压缩包大小约 2.27 MB。目前已有 2912 人学习。借助这套代码读者可以拿到可复现的完整建模过程既能跟随文档逐步搭建实验也能参考图表快速对比化工、有色、贵金属三类品种的拟合效果为后续调整参数或扩展到其他序列预测任务提供基线对金融时序预测实验和课程设计具有直接参考价值。1. LSTM时序收益预测这套代码包解决的不只是股价预测问题拆开这份「基于LSTM的股票预测系统.rar」里面不是一堆孤立的模型文件而是一条按数据、训练、预测三脚本串起来的完整流水线。它的预测对象是化工、有色、贵金属三个行业的大宗商品收益率而不是直接预测价格这个设计本身就绕开了金融序列非平稳的坑。适合两类人一类是做LSTM时序预测相关毕设、需要完整工程参考的学生另一类是正在摸索时间序列预测、想直接拿到可复现项目的从业者。对我来说它最有价值的地方在于数据准备、模型保存、反归一化这些环节的工程处理——正是这些细节决定了模型训练完能不能真正落地使用。2. 数据与预处理从xlsx到npy1data_create.py到底做了什么2.1 数据形态与预测目标为什么用收益率而不是原始价格拿到资源后先看data目录里面有三份Exceldata_化工.xlsx、data_有色.xlsx、data_贵金属.xlsx另外还有一份大宗商品指数.xlsx。这说明数据源是三个行业各自的指数或价格序列不是单只股票。核心设计在于预测目标——收益率而不是价格序列。金融价格序列通常是非平稳的直接拿原始价格喂给LSTM模型很容易学到「昨天价格高今天预测价格也高」这种伪规律训练集loss很低测试集一测就露馅。收益率序列则相对平稳更接近LSTM擅长的模式学习范畴。这份资源里三个行业的数据是分开存放的y_.npy和test_y_.npy分别对应三个行业的标签数据。1data_create.py这个名字暗示了它的职责把xlsx原始数据转成模型能吃的npy格式。整个预处理流程我在复现时重构了核心逻辑大致是读取Excel、计算收益率、做滑动窗口、归一化、切分训练测试集、保存npy。# 1data_create.py 核心逻辑按资源内流程重构 import numpy as np import pandas as pd from sklearn.preprocessing import MinMaxScaler import pickle def load_and_preprocess(excel_path, column_nameclose): # 读取原始数据这里以收盘价序列为例 df pd.read_excel(excel_path) # 计算收益率当前价格相对前一期的变化率 df[return] df[column_name].pct_change() # 删除首行空值 df df.dropna().reset_index(dropTrue) return df[return].values.reshape(-1, 1) def create_sequences(data, window20): X, y [], [] for i in range(len(data) - window): # 用过去window期收益率预测下一期收益率 X.append(data[i:iwindow]) y.append(data[iwindow]) return np.array(X), np.array(y) # 以化工行业为例 returns load_and_preprocess(data_化工.xlsx) # 归一化注意只能对训练集fit测试集用transform scaler MinMaxScaler(feature_range(0, 1)) returns_scaled scaler.fit_transform(returns) X, y create_sequences(returns_scaled, window20) # 按时间顺序切分不能随机打乱 split_idx int(len(X) * 0.8) X_train, X_test X[:split_idx], X[split_idx:] y_train, y_test y[:split_idx], y[split_idx:] # 保存npy与scaler np.save(X_train.npy, X_train) np.save(y_train.npy, y_train) with open(MinMaxScaler_model.pkl, wb) as f: pickle.dump(scaler, f)这段代码有三个关键点需要说明。第一pct_change()计算收益率时首行会产生NaN必须dropna否则后面构造窗口会带进空值。第二MinMaxScaler的fit_transform只做在训练集上测试集只用transform——这里如果偷懒对全量数据一起归一化就造成了数据泄漏测试集的信息提前进入了训练流程。第三切分时严格按时间顺序绝对不能用train_test_split的默认shuffle时序数据一旦打乱顺序LSTM学到的时序依赖关系就全废了。window参数我用了20也就是过去20个交易周期预测下一个周期的收益率这个值对日频数据比较常见对更低频的数据可以适当调小。2.2 滑动窗口构造与归一化两个影响训练结果的细节滑动窗口是LSTM处理时序数据的基本形态。窗口长度window决定了模型能「回看」多长的历史。窗口太小模型看不够历史趋势窗口太大样本数量减少还容易把过多噪声带进来。我一般会先用20做个基线再对比10和30的效果看验证集loss再定。注意每个样本的shape是(window, 1)1是收益率这一个特征维度。另一个细节是MinMaxScaler的feature_range我设成了(0,1)也就是把收益率序列压缩到0到1之间。LSTM内部用tanh和sigmoid激活函数输入范围在0到1附近梯度传播最稳定。如果原始数据是收益率数值通常比较小百分之几的量级不归一化直接训练也能收敛但归一化后收敛更快、更稳。训练完的scaler一定要pickle保存下来预测时要靠它把模型输出还原成真实收益率这个对象在后面的3pred_test.py里还会用到。2.3 数据去向与文件对应关系npy、csv各自扮演什么角色跑完1data_create.pydata目录下会生成或保留多组npy文件y_化工.npy这类是完整标签集test_y_*.npy是测试集标签。pre_data.csv这个文件我推测是最终预测结果的汇总表把三个行业的真实收益率和预测收益率按时间对齐后落盘方便后续画图和复盘。这种命名习惯在工程里很常见原始数据不动预处理产物用npy保存可读性强的结果用csv导出。3. 模型构建与训练2train.py里的LSTM结构与训练策略3.1 模型结构LSTM层、Dense层与参数怎么搭配2train.py负责模型的构建和训练。LSTM是RNN的一种变体通过输入门、遗忘门、输出门三个门控机制解决传统RNN的梯度消失和梯度爆炸问题。在收益率预测这个场景里我们关心的是序列的长期依赖——比如过去几期的趋势是否延续LSTM的门控结构恰好擅长捕捉这类模式。模型结构我按资源内可能的实现重构如下# 2train.py 模型定义核心部分 import tensorflow as tf from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint def build_lstm_model(input_shape, units64): model Sequential() # 第一层LSTMreturn_sequencesTrue表示返回完整序列方便堆叠第二层LSTM model.add(LSTM(unitsunits, return_sequencesTrue, input_shapeinput_shape)) model.add(Dropout(0.2)) # 第二层LSTM不返回序列只返回最后一个时间步的输出 model.add(LSTM(unitsunits // 2, return_sequencesFalse)) model.add(Dropout(0.2)) # 全连接层输出一个值下一期收益率 model.add(Dense(1)) # Adam是时序预测里最常用的优化器学习率从1e-3起步 model.compile(optimizertf.keras.optimizers.Adam(learning_rate0.001), lossmse, metrics[mae]) return model model build_lstm_model(input_shape(20, 1), units64) model.summary()两层LSTM加Dropout的结构比单层LSTM稍重但拟合能力更强。第一层返回完整序列是因为第二层还需要按时间步展开计算。Dense(1)输出层没有激活函数因为这是一个回归任务输出范围不限制。损失函数用mse均方误差它对大的误差惩罚更重适合金融收益这种希望避免大偏差的场景。units64是我起步的推荐值三个行业如果数据量差异大可以分别试32和64看验证集表现再定。3.2 训练过程fit参数、早停与模型保存训练部分的代码涉及几个关键回调。ModelCheckpoint按验证集loss保存最优模型EarlyStopping在验证集loss连续多轮不下降时提前终止训练避免过拟合。# 2train.py 训练部分 checkpoint ModelCheckpoint( models/model_lstm_ys.tf, # 保存为TF格式目录 monitorval_loss, # 以验证集loss为准 save_best_onlyTrue, # 只保存最优epoch verbose1 ) early_stop EarlyStopping( monitorval_loss, patience15, # 连续15轮不改善就停 restore_best_weightsTrue # 恢复最优权重 ) # 训练epochs设100实际由early stop控制 history model.fit( X_train, y_train, validation_data(X_test, y_test), epochs100, batch_size32, callbacks[checkpoint, early_stop], verbose1 )batch_size32是个稳妥默认值数据量大可以调64提速数据小用16防止梯度震荡。patience15是早停的核心参数——如果验证集loss连续15个epoch都没有创新低训练就停下。这里有个容易被忽略的细节restore_best_weightsTrue必须开否则early stop触发时模型停留在最后一个epoch的权重而不是验证集最优的那个epoch。资源里models目录下有三个行业的tf格式模型文件和checkpoint文件正是ModelCheckpoint保存的结果。日志目录logs下每个行业有train和validation两个子目录说明训练时还挂了TensorBoard回调我建议有条件也加上loss曲线动态看比事后看截图直观得多。3.3 训练曲线怎么看模型收敛的三个信号资源配了多张模型训练曲线截图覆盖三个行业。看这些图重点看三件事一是训练loss和验证loss是否同步下降如果训练降而验证不降说明过拟合已经开始二是下降是否平滑剧烈震荡说明学习率可能偏大三是最终验证loss是否收敛到平台期。理想状态是两条曲线在几十个epoch后都走平间隔不大。如果验证loss一直下不去优先怀疑窗口长度和units其次是数据质量问题——比如某段时期市场波动异常放大模型会把噪声当规律学进去。4. 预测与评估3pred_test.py如何还原真实收益率4.1 加载模型与反归一化顺序出错预测值就全偏了3pred_test.py是整个流水线的最后一步。它要做的事不是把模型输出直接当作预测值而是先反归一化。这是一个非常容易翻车的环节。模型在归一化后的数值空间里做预测输出的0到1之间的数必须经过scaler.inverse_transform才能还原成真实收益率。常见的错误是重新new一个MinMaxScaler再fit一遍测试集——这得到的scaler和训练时的scaler完全不是一回事反归一化出来的值会整体偏移。# 3pred_test.py 预测与反归一化核心逻辑 import numpy as np import pickle from tensorflow.keras.models import load_model # 加载训练好的模型与归一化器 model load_model(models/model_lstm_ys.tf) with open(MinMaxScaler_model.pkl, rb) as f: scaler pickle.load(f) # 加载测试集这里是示例脚本里可循环处理三个行业 X_test np.load(X_test_ys.npy) y_test np.load(test_y_ys.npy) # 模型预测输出仍是归一化空间的值 pred_scaled model.predict(X_test) # 关键步骤用训练时保存的scaler反归一化 pred scaler.inverse_transform(pred_scaled.reshape(-1, 1)) y_true scaler.inverse_transform(y_test.reshape(-1, 1)) # 计算评估指标 mae np.mean(np.abs(pred - y_true)) rmse np.sqrt(np.mean((pred - y_true) ** 2)) print(fMAE: {mae:.6f}, RMSE: {rmse:.6f}) # 保存为csv便于对比和绘图 import pandas as pd result_df pd.DataFrame({ 真实收益率: y_true.flatten(), 预测收益率: pred.flatten() }) result_df.to_csv(pre_data.csv, indexFalse)这段代码里model.predict的输出shape是(num_samples, 1)reshape(-1,1)是为了匹配scaler.inverse_transform的输入要求。MAE和RMSE这两个指标的意义在于MAE告诉你平均偏差多少RMSE因为对误差平方求和会更放大大的偏差如果RMSE明显大于MAE说明存在少数预测偏差特别大的点需要警惕。资源里的pre_data.csv应该就是这一步生成的结果汇总。4.2 拟合曲线怎么看贴合是好事但别只看贴合资源里有收益率拟合曲线_化工、收益率拟合曲线_有色、收益率拟合曲线_贵金属三张图。这些图通常是测试集上真实收益率和预测收益率的时序曲线对比。看图时有一个规律要知道LSTM在金融时序上的预测值往往比真实值平滑且存在滞后。如果图里预测曲线比真实曲线整体右移或更平缓这不是代码bug而是模型倾向输出接近前值或均值的保守预测。判断模型好坏不能只看曲线重合度还要看方向——预测的涨跌方向和真实涨跌方向一致的比例有多少。4.3 多行业结果对比为什么有的行业拟合好有的差三个行业的拟合效果大概率是有差异的。化工、有色、贵金属受不同的供需逻辑驱动波动特征不同。波动率越小的品种收益率序列越平稳LSTM拟合起来越容易波动率大的品种比如某段时间受消息面冲击的有色金属测试集上出现极端值时预测曲线往往会跟不上。资源里三个行业的训练曲线分开放就是方便分别排查问题。5. 避坑时序预测项目的五个常见坑与排查方法5.1 反归一化结果整体偏移现象预测曲线形状和真实曲线几乎一致但整体上移或下移数值明显不在一个量级。 原因预测时新建了一个MinMaxScaler并对测试集重新fit导致缩放参数和训练时不一致。 解决训练时把scaler用pickle保存预测时只load不重新fit。也就是我前面说的——fit_transform只用训练集测试集和预测集一律只用transform。5.2 数据泄漏归一化时混入了测试集现象训练时验证集loss很低模型看起来收敛得很好但真正上线预测时效果大打折扣。 原因预处理时先对全量数据fit了scaler再切分训练测试集测试集分布信息提前进入了训练环节。 解决先切分再归一化或者先fit_transform训练集再transform测试集。我强烈建议在1data_create.py里就把这一步的顺序固化下来不要每次写完再改。5.3 预测值严重滞后看起来像「昨天的预报」现象预测曲线几乎就是真实曲线向右平移了一个周期滞后感明显。 原因LSTM在金融数据的保守预测倾向。当序列随机性较强时模型学到的最优策略往往是输出接近最近观测值导致预测值滞后一拍。 解决方向准确率才是这类模型的更重要指标。我还会额外计算「预测方向准确率」也就是预测值相对上一期真实值的方向是否和真实方向一致以此判断模型是否学到了有效规律。滞后本身不是灾难但如果你拿预测值做交易决策滞后就是致命问题。5.4 训练loss震荡不降甚至出现NaN现象训练到中途loss开始上下剧烈震荡或者直接变成NaN。 原因最常见的是学习率过大导致梯度爆炸另一种可能是数据里有异常大或异常小的值比如极端行情下的收益率归一化后依然影响梯度。 解决先把学习率从1e-3降到1e-4试跑50轮同时检查收益率序列里有没有超出合理范围的异常值比如超过50%的单期变动。5.5 模型文件损坏或格式不兼容现象load_model时报错提示无法识别模型文件格式。 原因TF的SavedModel格式在版本间存在兼容性问题。资源里的model_lstm_ys.tf.index和data-00000-of-00001是标准的TensorFlow SavedModel结构但如果本机TensorFlow版本和训练时差异过大加载可能报错。 解决用与训练时相近的TensorFlow版本或改用只保存权重的.h5格式重新存储。我通常同时保存tf格式和h5格式一个保兼容一个保灵活。6. 进阶方向准确率验证与三行业模型对比6.1 方向准确率比拟合曲线更诚实的指标对这个场景方向准确率比MAE更有参考价值——它直接回答「模型能不能判断收益率是涨还是跌」这个投资决策层面的问题。我通常会把测试集里真实收益率绝对变化很小比如小于0.001的样本过滤掉因为接近0的涨跌方向本身没有意义。# 方向准确率计算示例 import numpy as np def direction_accuracy(y_true, y_pred, threshold0.001): # 过滤掉变动幅度过小的样本 mask np.abs(y_true) threshold y_true_f y_true[mask].flatten() y_pred_f y_pred[mask].flatten() # 方向一致预测方向和真实方向相同 correct np.sign(y_true_f) np.sign(y_pred_f) return np.mean(correct) # 假设pred和y_true是反归一化后的结果 acc direction_accuracy(y_true, pred) print(f方向准确率: {acc:.2%})代码里np.sign比较的是正负号也就是涨跌方向。threshold过滤的作用是排除那些收益率几乎为零的样本这些样本方向判断本身就是随机游戏。方向准确率大于50%说明模型比抛硬币强对金融时序模型来说稳定在55%以上就已经有参考价值了。6.2 三行业对比与模型选型做完三个行业的预测后把MAE、RMSE、方向准确率汇总成一张表。以我的经验波动率低的品种方向准确率往往更高这是数据本身的性质不代表某个行业的模型结构更好。对比的价值在于如果某个行业效果特别差可以单独给它调window或units而不是三个行业共用一个超参数。用这份资源做毕设或项目落地时真正值得花时间打磨的是数据预处理和评估体系——模型结构本身已被研究得很成熟工程上的细节才是拉开差距的地方。我把这套流程跑通后最大的教训是永远不要让测试集的信息提前泄漏到训练里也永远不要只看loss曲线就判断模型可用。从那以后我每次跑时序模型都强制先检查一遍数据切分顺序、归一化方式、反归一化对象再训练、再预测、再算方向准确率。希望帮到你。本文还有配套的精品资源点击获取