简介:这份基于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分钟能跑完的对照实验:同样的数据与相同的units,LSTM与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_cols=None, target_col='close', seq_len=20): # 读取本地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], axis=0) X_test, y_test = make_samples(test_seq) return X_train, y_train, X_test, y_test, scaler, target_idx这段代码有几个关键约定。seq_len=20表示用过去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(units=64, return_sequences=True, input_shape=(seq_len, n_features)), Dropout(0.2), LSTM(units=32, return_sequences=False), Dropout(0.2), Dense(units=1) ]) model.compile( optimizer=tf.keras.optimizers.Adam(learning_rate=0.001), loss='mse', metrics=['mae'] )参数这里要说明清楚。第一层units=64是隐状态维度,可以理解为模型同时维护64个独立的“记忆单元”,并不是越大大越好,股票数据噪声多,隐维度过大会在训练集上记住具体价位而非规律。return_sequences=True是堆叠两层LSTM时的必需品,它让第一层把每个时间步的隐藏状态都传给下一层;最后一层LSTM取最后一个时间步输出,经过Dense(1)压缩成下一日收盘价预测值。input_shape里的(seq_len, n_features)必须与第2章prepare_data的返回值保持一致,否则跑数据时会直接报维度不匹配错误。
| 参数 | 示例值 | 影响与调整方向 |
|---|---|---|
| units | 64/32 | 增大则容量提升,但股票数据容易过拟合;首选32~128 |
| return_sequences | True/False | 堆叠LSTM时首层要True,末层要False |
| dropout | 0.2 | 抑制过拟合,期末作业建议0.2~0.4之间 |
| 学习率 | 0.001 | Adam默认可用;loss震荡时降到0.0005 |
3.2 训练与早停:epoch、batch_size与验证集
训练LSTM回归模型要尽量让epoch上限设大,同时用早停兜底,因为不同股票序列的收敛速度差别很大,固定epochs=50要么欠拟合要么过拟合。
early_stop = EarlyStopping( monitor='val_loss', patience=10, restore_best_weights=True ) history = model.fit( X_train, y_train, validation_split=0.1, epochs=100, batch_size=32, callbacks=[early_stop], verbose=1 )这里使用validation_split=0.1会从训练序列尾部自动切出最近10%数据作为验证集,天然符合时间顺序。不要额外设置shuffle=True,Keras的fit默认会在训练时洗牌,但这个洗牌发生在样本层面,对序列值本身没有影响,验证集仍然按尾部截取,所以不必担心样本顺序被打乱导致数据泄漏。batch_size=32在几千个滑窗样本量级下是稳定选择,显存紧张时改成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, label='real') plt.plot(pred_close, label='lstm 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(f"RMSE={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, steps=5, target_idx=0): """输入最后一个历史窗口,递归预测未来steps天""" cur = last_window.reshape(1, seq_len, n_features) # 保证三维 results = [] for _ in range(steps): next_val = model.predict(cur, verbose=0)[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)], axis=1) return np.array(results)注意这里的n_features是在模型定义时就固定了的,如果只有close单特征,target_idx=0没问题;如果后续加了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预测,右边误差随步数变化曲线,这组图比任何调参论述都有说服力。
本文还有配套的精品资源,点击获取