LSTM股票价格预测实战:从滑动窗口到Keras模型完整方案

发布时间:2026/9/13 2:09:13
LSTM股票价格预测实战:从滑动窗口到Keras模型完整方案
简介这份基于LSTM的股票价格预测项目源码主要面向计算机相关专业学生或从业者可作为期末大作业、课程设计的完整参考解决金融时序数据建模与预测的实战需求。压缩包共包含89个文件核心为24个Python脚本与19个编译缓存文件涵盖数据预处理、模型构建、训练评估等环节另有13个txt说明文档、4个HTML页面及配套CSS/JS前端资源以及xls、xlsx、sqlite3等数据文件整体仅8.89MB结构清晰便于快速提取使用。项目由个人大作业打磨而成评审分达到95分以上并经过严格调试下载后即可运行。学习该源码可掌握LSTM在股票价格预测中的完整流程包括数据清洗、特征构造、模型调参与结果可视化同时可参考作者在项目组织、文件划分和问题排查方面的思路对完成同类课设或提升实战能力具有较高价值。目前已有200人学习下载适合中高级Python学习者深入研读。 股票价格预测是深度学习期末作业里出现频率最高的课题之一但也是“看起来在跑、实际上没学对”的重灾区。用LSTM做这题时最典型的现象是loss持续下降打开预测图却发现蓝色预测线总比红色真实线慢半拍——这不是调大epoch能解决的根因在于滑动窗口的构造方式、归一化的作用边界和模型输出目标三处没对齐。这里不打算用某份现成源码的说明书糊弄过去而是按期末大作业的验收标准数据可查、代码可跑、答案可解释展开一套结构完整的LSTM股票价格预测源码方案从序列样本怎么切、特征怎么归一化到Keras模型怎么搭、训练完怎么评估每个步骤都有对应代码和参数解释。适合要交课设作业的在校学生也适合快速套用一个LSTM回归任务模板的工程师。2. 时间序列预测的选型逻辑LSTM的门控机制与滑动窗口构造2.1 为什么股票序列预测优先LSTM而不是普通RNN股票的收盘价、成交量本质上是一个长依赖时间序列今天是否站上均线、近20个交易日的趋势是否延续都会对明天的价格产生作用。普通RNN在反向传播时梯度经过多个时间步连乘序列长度只要超过20步就会出现明显的梯度消失隐状态里留存的主要是最近一步的信息模型退化成“拿昨天的价格当预测结果”。这也是很多用SimpleRNN做股价预测的同学发现数据拟合尚可、但测试集上几乎无泛化能力的原因。LSTM是在RNN基础上引入细胞状态Ct的改进结构。细胞状态沿时间轴穿过整个序列门控只做乘性修改梯度因此拥有一条直通的“高速公路”信息可以在几十个时间步内保持。你可以在Keras里做一个5分钟能跑完的对照实验同样的数据与相同的unitsLSTM与SimpleRNN各训练50轮留意验证集loss的下降速度差异。这个对照组写进期末作业报告里比空谈“LSTM优势”更有说服力。2.2 LSTM遗忘门的输入是哪些数据h(t-1)与x(t)的拼接网上经常有人搜“lstm遗忘门的输入是什么数据”背后的坑是维度理解。遗忘门计算式为f_t sigmoid(W_f * concat(h(t-1), x(t)) b_f)输入由两个部分拼接而成上一时刻的隐藏状态h(t-1)和当前时刻的特征向量x(t)。在股票数据里h(t-1)不是收盘价本身而是模型对“截至上一个交易日”市场信息的压缩隐藏向量x(t)则是当日输入特征比如收盘价归一化值、成交量、5日涨幅这些原始观测。两者列维度不同必须用tf.concat或numpy的concatenate拼在一起再经过线性变换和sigmoid得到0到1之间的向量逐元素乘到细胞状态上决定哪些历史信息要保留。门控名称输入作用股票预测中的直觉理解遗忘门h(t-1), x(t)决定细胞状态哪一位要被归零是否丢弃昨天积累的平台支撑或趋势信号输入门h(t-1), x(t)决定候选值哪些写入细胞状态当前价格与成交量是否构成新的上涨结构输出门h(t-1), x(t)过滤后的细胞状态映射为隐藏状态h(t)模型对外表达的“综合判断”过滤后输出2.3 数据准备的第一个雷区归一化、滑动窗口与训练集切分数据准备阶段最常见的错误是把乱序样本直接喂进LSTM。股票序列一旦随机打乱模型会“偷看未来”训练时指标非常漂亮一到真实滚动预测就彻底失灵。正确做法是保留时间顺序并在窗口内组织样本。import pandas as pd import numpy as np from sklearn.preprocessing import MinMaxScaler def prepare_data(csv_path, feature_colsNone, target_colclose, seq_len20): # 读取本地CSV按日期升序排好 df pd.read_csv(csv_path, parse_dates[date]).sort_values(date) if feature_cols is None: feature_cols [target_col] target_idx feature_cols.index(target_col) data df[feature_cols].values.astype(np.float32) # 按时间顺序取前80%训练后20%测试 train_size int(len(data) * 0.8) train_raw, test_raw data[:train_size], data[train_size:] # 只对训练区拟合scaler测试区仅transform避免归一化泄漏 scaler MinMaxScaler(feature_range(0, 1)) train_norm scaler.fit_transform(train_raw) test_norm scaler.transform(test_raw) def make_samples(seq): X, y [], [] for i in range(len(seq) - seq_len): X.append(seq[i:i seq_len]) y.append(seq[i seq_len, target_idx]) return np.array(X), np.array(y) X_train, y_train make_samples(train_norm) # 用训练集尾部seq_len个点接上测试区保证窗口不断档 test_seq np.concatenate([train_norm[-seq_len:], test_norm], axis0) X_test, y_test make_samples(test_seq) return X_train, y_train, X_test, y_test, scaler, target_idx这段代码有几个关键约定。seq_len20表示用过去20个交易日预测第21天的收盘价周期上对应A股一个月的交易日数量。scaler只对训练区调用fit_transform测试区只能调用transform如果对全量数据fit测试集信息会经归一化参数泄漏进训练过程答辩时一问一个准。make_samples里的滑动窗口会产生重叠样本这是时间序列增广的常见做法但注意重叠带来的自相关性会影响误差分布评估时要有心理预期。3. 用Keras搭建LSTM模型源码结构与必调参数3.1 构建模型Sequential接口下的LSTM层参数用Python做LSTM时间序列预测时最常出错的环节不是模型结构而是样本维度没对齐。下面先给一个可运行的Keras模型定义import tensorflow as tf from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout from tensorflow.keras.callbacks import EarlyStopping seq_len 20 n_features 1 # 演示场景只用收盘价单特征多特征改这里 model Sequential([ LSTM(units64, return_sequencesTrue, input_shape(seq_len, n_features)), Dropout(0.2), LSTM(units32, return_sequencesFalse), Dropout(0.2), Dense(units1) ]) model.compile( optimizertf.keras.optimizers.Adam(learning_rate0.001), lossmse, metrics[mae] )参数这里要说明清楚。第一层units64是隐状态维度可以理解为模型同时维护64个独立的“记忆单元”并不是越大大越好股票数据噪声多隐维度过大会在训练集上记住具体价位而非规律。return_sequencesTrue是堆叠两层LSTM时的必需品它让第一层把每个时间步的隐藏状态都传给下一层最后一层LSTM取最后一个时间步输出经过Dense(1)压缩成下一日收盘价预测值。input_shape里的(seq_len, n_features)必须与第2章prepare_data的返回值保持一致否则跑数据时会直接报维度不匹配错误。参数示例值影响与调整方向units64/32增大则容量提升但股票数据容易过拟合首选32~128return_sequencesTrue/False堆叠LSTM时首层要True末层要Falsedropout0.2抑制过拟合期末作业建议0.2~0.4之间学习率0.001Adam默认可用loss震荡时降到0.00053.2 训练与早停epoch、batch_size与验证集训练LSTM回归模型要尽量让epoch上限设大同时用早停兜底因为不同股票序列的收敛速度差别很大固定epochs50要么欠拟合要么过拟合。early_stop EarlyStopping( monitorval_loss, patience10, restore_best_weightsTrue ) history model.fit( X_train, y_train, validation_split0.1, epochs100, batch_size32, callbacks[early_stop], verbose1 )这里使用validation_split0.1会从训练序列尾部自动切出最近10%数据作为验证集天然符合时间顺序。不要额外设置shuffleTrueKeras的fit默认会在训练时洗牌但这个洗牌发生在样本层面对序列值本身没有影响验证集仍然按尾部截取所以不必担心样本顺序被打乱导致数据泄漏。batch_size32在几千个滑窗样本量级下是稳定选择显存紧张时改成16收敛更慢但更稳。3.3 预测与价格还原从归一化空间回到真实股价模型的输出是归一化后的数值要画对比图或计算误差指标必须把预测值和真实值同时还原到原始价格区间。单特征场景下直接逆变换即可import matplotlib.pyplot as plt y_pred model.predict(X_test) # shape: (样本数, 1) pred_close scaler.inverse_transform(y_pred) true_close scaler.inverse_transform(y_test.reshape(-1, 1)) plt.figure(figsize(10, 4)) plt.plot(true_close, labelreal) plt.plot(pred_close, labellstm predict) plt.legend() plt.title(stock close price prediction) plt.show()这段代码只有一个注意点y_test在生成时是(n,)的一维数组inverse_transform要求二维输入所以要用reshape(-1, 1)。如果第2章里把feature_cols扩展成包含成交量在内的多个特征就不能再这样逆变换需要把预测结果拼接成完整特征行、逆变换后再取目标列我在第5章会展开这个细节。4. 训练完成后的评估与排错时间切分、评估指标与三个高频翻车点4.1 时间序列验证必须遵守的顺序边界train_test_split这类随机切分工具不能用于股票预测。序列样本之间高度自相关随机切分会让训练集和测试集出现大量时间交叠模型相当于“开卷考试”在答辩演示时经不起推敲。推荐做法是按时间点切分前80%训练、后20%测试第2章代码已经按这个思路实现。要检查自己的切分是否靠谱可以把X_test里每个样本的窗口起点和终点打出来确认最小起点时间大于训练集最大时间。实际做的时候我还建议把train_norm[-seq_len:]这一段当作跨边界衔接单独说明因为很多同学在构造测试集时直接对test_raw做滑窗丢掉了前面seq_len个样本的上下文导致测试预测前20个点断档。4.2 评估指标选哪个RMSE、MAPE与方向准确率期末作业不能只贴一张下降的loss曲线评估指标要能解释模型在真实数据上的水平。from sklearn.metrics import mean_squared_error, mean_absolute_error def evaluate_metrics(y_true, y_pred): y_true y_true.flatten() y_pred y_pred.flatten() rmse np.sqrt(mean_squared_error(y_true, y_pred)) mae mean_absolute_error(y_true, y_pred) # MAPE对低价股异常敏感价格无限接近0时数值会爆炸 mape np.mean(np.abs((y_true - y_pred) / y_true)) * 100 # 方向准确率比较相邻两日价格差的方向是否相同 diff_true np.diff(y_true) diff_pred np.diff(y_pred) direction_acc np.mean(np.sign(diff_true) np.sign(diff_pred)) return rmse, mae, mape, direction_acc rmse, mae, mape, direction_acc evaluate_metrics(true_close, pred_close) print(fRMSE{rmse:.4f}, MAE{mae:.4f}, MAPE{mape:.4f}%, direction_acc{direction_acc:.2%})指标关注点局限RMSE大误差的惩罚更重受价格绝对值影响不同股票间不可直接对比MAE平均绝对偏差更容易理解对极端行情不敏感MAPE相对误差百分比便于横向比较股价接近0时失效方向准确率涨跌方向是否判断正确忽略涨跌幅度但适合展示LSTM的实用价值4.3 高频翻车点与其背后的低层原因第一个翻车点是预测曲线滞后一拍。模型输出几乎等于上一个真实收盘价肉眼看着拟合很好实际上它学的不是趋势规律而是“复制前值”。检测方法很简单把diff(true_close)和diff(pred_close)画在同一个坐标系里如果预测值的diff基本贴近0说明模型退化了。缓解手段是缩短seq_len并加入成交量、涨跌幅等额外特征迫使模型依赖更多信息。第二个翻车点是归一化泄漏。一定要确保scaler.fit()只见过训练集否则测试区间的最小最大值提前被模型通过数据管线“学过”测试指标虚高。第三个翻车点是滑窗时序列补位错误。很多源码在测试阶段不拼接train_norm[-seq_len:]导致X_test[0]不是训练集尾巴后紧接着的那一天语义上形成了一个跳跃窗口。提示期末作业答辩时老师最爱问“为什么你的训练loss一直在降测试曲线还是滞后”。回复的起点就是这三个翻车点而不是“再训练50轮”。5. 从源码包到期末作业多步预测、特征增强与答辩交付技巧5.1 从单步预测升级为多步递归预测单步预测在答辩里容易被追问“你只能预测明天能预测未来一周吗”。多步预测有递归法、直接法和seq2seq三种路线期末作业性价比最高的是递归法用模型预测次日再把预测值拼进窗口末尾滚动预测后续天数。def recursive_forecast(model, last_window, steps5, target_idx0): 输入最后一个历史窗口递归预测未来steps天 cur last_window.reshape(1, seq_len, n_features) # 保证三维 results [] for _ in range(steps): next_val model.predict(cur, verbose0)[0, 0] results.append(next_val) # 构造新窗口丢弃窗口最旧一天加入预测值 new_last cur[0, -1, :].copy() new_last[target_idx] next_val cur np.concatenate([cur[:, 1:, :], new_last.reshape(1, 1, -1)], axis1) return np.array(results)注意这里的n_features是在模型定义时就固定了的如果只有close单特征target_idx0没问题如果后续加了volume就要把被预测的列替换为next_val其余列可以沿用昨天数值也可以分别递归预测。误差会随着预测步数累积这是LSTM自身无法消除的展示结果时要诚实说明“第五天预测明显劣于第一天”。5.2 加入成交量与涨跌幅特征时的收益与边界把feature_cols扩展成[close, volume]或加上5日涨跌幅模型能从量价配合中捕捉到更多状态。但要注意两个问题不同特征的量纲差异大统一进MinMaxScaler是可行的而归一化后的inverse_transform必须基于完整特征矩阵才能还原价格列不能只对单列操作。特征数量从1加到3时样本量不变而输入维度变大模型参数量随之上升几百条股票数据很容易过拟合建议同时把units降到32并提高dropout到0.3。5.3 源码包结构与README交付清单期末大作业的源码包要让人拿到手里就能复现一份合格的目录结构是lstm_stock/ ├── data/ # 原始CSV列名包含date/close/volume ├── prepare.py # 归一化、滑动窗口、时间切分 ├── train.py # LSTM模型定义、训练、早停 ├── evaluate.py # RMSE/MAE/MAPE/方向准确率与绘图 └── README.md # 运行步骤、依赖、结果图与结论README里要把运行命令写到可直接复制的程度python train.py python evaluate.py。结果图命名要带指标比如prediction_rmse_12.34.png评审打开目录一眼就能看到效果。答辩时把单步预测图和多步递归预测误差累积图画在一张A4报告里左边真实vs预测右边误差随步数变化曲线这组图比任何调参论述都有说服力。本文还有配套的精品资源点击获取