简介:本资源是一份基于LSTM神经网络的股票价格预测模型Python实现,专为高校计算机、金融工程或人工智能方向学生设计,适用于期末大作业、课程设计及毕业设计参考。项目已获97分高分评价,完整复现了数据预处理、序列建模、训练调优与可视化预测全流程,具备较强工程落地性与教学示范性。压缩包共12个文件,含3个核心Python源码(含主训练脚本与预测模块)、2个CSV历史行情数据(上证指数与单支股票)、1个Word版详细说明文档、1个模型检查点及配套meta/index文件,整体仅961KB,轻量易部署。已有1082人学习下载,内容结构清晰:从原始数据加载、滑动窗口构造、LSTM模型定义到结果绘图与误差评估一应俱全,附带可直接运行的demo脚本与编译缓存文件,显著降低复现门槛,是理解时序预测实战的优质入门范例。 先把这个期末大作业当项目做,别当“交差”的作业做。同样是LSTM股票预测,有人拿95分,有人拿85分,差距不在模型本身,而在于你有没有把“预测”这件事讲透。
这篇内容我按完整的项目复盘来写,包含了我自己当时踩过的坑、查过的资料、最后总结出的高分套路,你可以直接照着复现,也可以在此基础上加自己的东西冲更高分。
1. 项目整体设计与评分点拆解
1.1 拿到题目后首先想清楚的事
LSTM股票预测这个题目,每年都有人做,但大多数人只是跑通了一个模型就交上去了。要想拿95分以上,你需要理解评分老师在看什么。期末大作业的评分维度通常分为以下几块:选题意义与问题定义(约15分)、数据获取与预处理规范性(约20分)、模型设计与参数解释(约25分)、实验对比与结果分析(约25分)、代码规范与报告撰写(约15分)。
很多人在第一块就吃了亏,开篇就写“股市风险大,预测很重要”,这话没错,但太空了。你要做的是把问题定义清楚:是预测收盘价,还是预测涨跌方向?是用前N天数据预测后一天,还是预测未来一周趋势?问题定义不同,模型设计完全不同,评分老师一眼就能看出你有没有想清楚。
1.2 技术选型:为什么选LSTM
时间序列预测的模型有不少选择,ARIMA、Prophet、XGBoost都能做,但LSTM在这个题目里有天然优势:
- 股票数据是典型的时间序列,存在长期依赖关系,LSTM的门控机制天然适合捕获这种依赖
- LSTM能处理变长序列,不像ARIMA有严格的平稳性要求
- 相比普通RNN,LSTM解决了梯度消失问题,训练更稳定
- 深度学习模型的“黑盒”特性反而给了你更多分析空间(可视化、注意力等)
但要注意,用LSTM不意味着可以忽略传统方法。一个能拿高分的大作业,通常还包含一个对比实验:LSTM vs ARIMA vs 线性回归。哪怕ARIMA效果更好,你也需要通过分析来解释“为什么在这个数据集上LSTM没打赢传统模型”,这本身就是深度学习的价值体现。
1.3 项目目录与交付物的规划
先把目录结构规划好,避免中途写乱了:
stock_prediction_project/ ├── data/ # 原始数据与处理后的数据 │ ├── raw/ # 从tushare或yfinance下载的原始数据 │ └── processed/ # 归一化、滑窗后的数据 ├── src/ # 源代码 │ ├── data_fetcher.py # 数据下载模块 │ ├── preprocess.py # 数据预处理模块 │ ├── model.py # LSTM模型定义 │ ├── train.py # 训练脚本 │ ├── evaluate.py # 评估与可视化 │ └── utils.py # 工具函数 ├── checkpoints/ # 模型权重保存 ├── figures/ # 生成的图表(论文/报告用) ├── requirements.txt # 依赖清单 └── README.md # 项目说明这个结构向老师传递了一个信息:你是按工程化标准做的,不是把代码堆在一个.ipynb里。这也是加分项。
2. 环境配置与数据获取:把这些基础做到滴水不漏
2.1 Python环境配置的坑
环境配置是最容易翻车的地方,也是很多同学一开始就卡住的地方。建议直接用Anaconda创建独立环境,不要用系统全局的Python,因为后面装TensorFlow/PyTorch时会因为依赖冲突整得焦头烂额。
conda create -n stock_lstm python=3.9 conda activate stock_lstm pip install tensorflow pandas numpy matplotlib scikit-learn tushare这里说一下版本选择的问题,TensorFlow的版本不要太新,很多老教程用的还是2.4/2.5,如果你装了2.10以上版本,有些API接口会不一样(比如tf.keras的使用方式),但from tensorflow.keras这种写法在2.x都兼容。如果你用PyTorch,也可以,但LSTM的搭建代码要相应调整。
提示:如果你的网络环境访问外网有问题,下载TensorFlow可能很慢,镜像源是最快的解决方案。用清华或阿里云的镜像源:
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple tensorflow
2.2 数据源选择:免费且合规的获取方式
股票数据获取必须注意合规性问题。几个常见的数据源:
- Tushare:国内最常用的免费数据源,注册即用,积分够的话可以获取很细粒度的数据
- AKShare:完全免费,无需注册,但接口变动比较频繁
- yfinance(Yahoo Finance):需要能访问外网,不推荐
- baostock:国内免费数据源,接口稳定,适合做期末作业
我的建议是用Tushare或者AKShare,因为数据是A股的真实行情,报告里可以写清楚“数据来源:Tushare Pro API”,导师看了觉得规范。
Tushare的代码示例:
import tushare as ts import pandas as pd # 需要先在tushare.pro注册获取token ts.set_token('你的token') pro = ts.pro_api() # 获取平安银行2020-2023年的日线数据 df = pro.daily(ts_code='000001.SZ', start_date='20200101', end_date='20231231') df = df.sort_values('trade_date').reset_index(drop=True) print(df.head())如果你不想注册,也可以用AKShare:
import akshare as ak # 获取贵州茅台历史数据 df = ak.stock_zh_a_hist(symbol="600519", period="daily", start_date="20200101", end_date="20231231", adjust="qfq") df = df.rename(columns={'日期': 'date', '收盘': 'close'}) print(df.head())2.3 股票代码选择的思路
选择哪只股票也是有讲究的。建议选流动性好、数据量大、趋势相对明显的股票,比如贵州茅台(600519)、平安银行(000001)、招商银行(600036)。不要选ST股或次新股,因为数据可能有异常。
我当时选的是平安银行,原因很简单:数据平稳、受大盘影响明显,LSTM能学到一些规律。更重要的是,平安银行的历史数据里包含了几次明显的涨跌周期,这让预测结果可视化时看起来“有故事可讲”。
3. 数据预处理:这步决定了你模型的上限
3.1 缺失值与异常值处理
获取到的原始数据通常不会直接可用。我拿到数据后第一件事是做数据检查:
# 检查缺失值 print(df.isnull().sum()) # 检查数据量 print(f"数据量: {len(df)}") print(f"时间范围: {df['trade_date'].min()} ~ {df['trade_date'].max()}")缺失值的处理方式:如果缺失的是非交易日(周末、节假日),不需要填充,因为股票数据本身就是按交易日排列的;如果是交易日缺失,用前向填充(ffill)或插值。
3.2 为什么一定要做归一化
这是很多初学者的知识盲区,但在报告里写清楚了是加分项。
LSTM使用tanh和sigmoid作为激活函数,它们的输出范围是[-1, 1]或[0, 1]。如果输入数据不归一化,股价动辄几十、几百,梯度会非常大,训练过程很容易震荡甚至发散。归一化让所有特征处在同一量纲下,模型收敛更快,训练更稳定。
常用的归一化方法有两种:
from sklearn.preprocessing import MinMaxScaler, StandardScaler # 方法1:MinMaxScaler,将数据缩放到[0,1] scaler = MinMaxScaler(feature_range=(0, 1)) scaled_data = scaler.fit_transform(df[['close']].values) # 方法2:StandardScaler,标准化为均值为0,方差为1 # scaler = StandardScaler() # scaled_data = scaler.fit_transform(df[['close']].values)对于股票数据,我推荐用MinMaxScaler,因为股票价格没有负值,而且MinMaxScaler在反归一化时更直观,预测结果能直接转换回真实价格。StandardScaler在做对比实验时也可以试一试,但在报告中要说明两种方法的差异。
注意:这里的Scaler必须只在训练集上
fit,然后用同一个Scaler去transform测试集,严禁在整个数据集上fit。否则会造成数据泄漏,测试集的分布信息提前混进了训练过程,实验结果虚高,这在学术上是站不住脚的。
3.3 滑窗机制:构造有监督学习样本
LSTM不能直接吃一长串股价序列,需要把序列切割成“输入样本-标签”对。假设我们用过去look_back=60天的数据预测下一天的价格,具体做法是把数据做成:
X[0] = [t0, t1, ..., t59] -> y[0] = t60 X[1] = [t1, t2, ..., t60] -> y[1] = t61 ...def create_sequences(data, look_back=60): X, y = [], [] for i in range(len(data) - look_back): X.append(data[i:i+look_back, 0]) y.append(data[i+look_back, 0]) return np.array(X), np.array(y) look_back = 60 X, y = create_sequences(scaled_data, look_back) print(f"X shape: {X.shape}, y shape: {y.shape}")look_back是超参数中非常关键的一个。太小(比如5天),模型只看得到短期波动,学不到中期趋势;太大(比如250天,约一年交易日),训练数据的长度会不够,而且引入了太多历史噪声。我实测下来,60(约三个月交易日)是一个不错的平衡点。但你完全可以在报告里做一个look_back敏感性分析,每次改参数重新训练,画N张预测图对比,这种踏实劲导师不可能不给分。
3.4 数据集切分:训练集/验证集/测试集的划分原则
时间序列数据的划分和普通机器学习数据不一样,不能随机打乱,必须按时间顺序划分,否则就破坏了时间相关性,相当于作弊。
我采用的划分方式是:前80%做训练集,中间10%做验证集,最后10%做测试集。
train_size = int(len(X) * 0.8) val_size = int(len(X) * 0.1) X_train, y_train = X[:train_size], y[:train_size] X_val, y_val = X[train_size:train_size+val_size], y[train_size:train_size+val_size] X_test, y_test = X[train_size+val_size:], y[train_size+val_size:] # 调整shape为LSTM输入格式:(样本数, 时间步长, 特征数) X_train = X_train.reshape((X_train.shape[0], X_train.shape[1], 1)) X_val = X_val.reshape((X_val.shape[0], X_val.shape[1], 1)) X_test = X_test.reshape((X_test.shape[0], X_test.shape[1], 1))这里有一个容易被忽略的细节——只用收盘价做特征,还是加入其它特征?
只用收盘价,模型学到的模式相对有限,但思路清晰,便于解释。如果你想拿更高的分,可以尝试加入开盘价、最高价、最低价、成交量,甚至是一些技术指标(如MACD、RSI)。特征多了,模型输入维度从1变成N,需要调整模型结构,但报告的内容会更丰富。
我当时报告里做了一个对比:“单特征 vs 多特征”,多特征模型的RMSE略高(即更准),但训练时间多了将近一倍。这个实验极大的丰富了报告的深度,帮你拉开和同班同学的差距。
4. LSTM模型设计与训练调优
4.1 网络结构设计:如何确定层数和神经元数量
LSTM网络的结构设计是整个项目的核心环节。我的目标是要在“表达能力”和“过拟合风险”之间找到平衡点。
一个标准的LSTM回归模型长这样:
import tensorflow as tf from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout from tensorflow.keras.optimizers import Adam from tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau def build_lstm_model(look_back=60, n_features=1): model = Sequential([ LSTM(units=128, return_sequences=True, input_shape=(look_back, n_features)), Dropout(0.2), LSTM(units=64, return_sequences=False), Dropout(0.2), Dense(units=32, activation='relu'), Dense(units=1) ]) model.compile(optimizer=Adam(learning_rate=0.001), loss='mean_squared_error', metrics=['mae']) return model model = build_lstm_model() model.summary()结构的关键点:
- 第一层LSTM需要
return_sequences=True,因为要输出完整的序列到下一层LSTM - Dropout层防止过拟合,0.2的含义是在训练时随机丢弃20%的神经元连接
- 最后一层是Dense(1),因为我们要输出一个连续值(下一天的股价),不是分类,所以没有激活函数
- 损失函数用MSE,因为这个任务本质是回归问题,MSE对大的预测误差敏感,能有效指导模型优化
关于神经元数量,没有绝对的公式,但有一个经验法则:输入特征的8~16倍。单特征下用32~128都合理。神经元翻倍计算量会翻4倍(因为LSTM的矩阵乘法是输入维度和单元数同时影响参数量),所以不要盲目选大的。
4.2 训练参数的配置与理由
训练的核心参数包括batch size、epochs、学习率和早停机制。我推荐的配置如下:
- batch size = 64:比较中庸的选择,在训练速度和梯度稳定性之间折中。太大(如256)会让梯度方向过于“平均”,错过最优解;太小(如16)训练不稳定、震荡大
- epochs = 100:训练100轮,同时配合早停
- learning rate = 0.001:Adam优化器的默认学习率,通常不需要动
- EarlyStopping patience=15:如果验证集loss连续15个epoch没有下降,就提前停止,防止过拟合
early_stopping = EarlyStopping(monitor='val_loss', patience=15, restore_best_weights=True) reduce_lr = ReduceLROnPlateau(monitor='val_loss', factor=0.5, patience=5, min_lr=1e-6) history = model.fit( X_train, y_train, validation_data=(X_val, y_val), epochs=100, batch_size=64, callbacks=[early_stopping, reduce_lr], verbose=1 )ReduceLROnPlateau是个很实用的调参技巧。当验证损失进入平台期,学习率自动减半,让模型在小步长下继续精细搜索。这个细节写进报告里,能显得你很懂调参。
4.3 训练过程中的损失曲线解读
训练完成后,第一件事就是画损失曲线。这是报告中最基础也是最重要的图之一。
import matplotlib.pyplot as plt plt.figure(figsize=(12, 5)) plt.plot(history.history['loss'], label='Training Loss') plt.plot(history.history['val_loss'], label='Validation Loss') plt.title('Model Loss') plt.xlabel('Epochs') plt.ylabel('Loss') plt.legend() plt.savefig('figures/loss_curve.png', dpi=300, bbox_inches='tight') plt.show()那到底怎么解读这张图呢?
- 如果训练损失和验证损失都持续下降并趋于平稳,说明模型健康,没有过拟合
- 如果训练损失还在下降,但验证损失已经开始上升,说明过拟合了,需要加大Dropout、减小模型规模、或者增加早停的力度
- 如果两条曲线都不降,或者震荡得非常厉害,说明学习率可能太高,或者数据归一化没做好
我当时训练到第50轮左右早停就触发了,模型恢复到了best weights。整个训练过程不到3分钟(GTX 1660显卡),CPU也不慢。这个训练成本对学生党很友好。
4.4 预测与反归一化
训练完成后做预测,注意一个细节:预测时也要做滑窗,不能一次性把整个测试集丢进去(那是作弊)。需要像“滚动预测”一样,用已知的真实数据点逐个生成预测结果。
不过,为了简单且有效,大部分期末作业的做法是一次性把测试集X_test丢进模型,得到每个样本点的预测序列,再反归一化回真实股价。我这个阶段也是这么做的。如果你追求更高阶的玩法,可以尝试“多步递归预测”:预测出下一天的股价后,把预测值拼进输入序列,继续迭代预测未来5天。这样更像真实的交易场景,但误差会累积。
# 模型预测 predicted_prices = model.predict(X_test) # 反归一化:把预测值和真实值都还原到原始价格尺度 predicted_prices = scaler.inverse_transform(predicted_prices.reshape(-1, 1)) real_prices = scaler.inverse_transform(y_test.reshape(-1, 1))值得说明的是,scaler.inverse_transform的输入必须和fit时的shape一致,所以这里都会reshape(-1, 1)一下。这个细节在报告里可以提一下,因为很多同学代码报错就在这种小地方。
5. 评估指标与结果可视化
5.1 回归预测的核心指标:MAE、RMSE、MAPE
评估模型性能时,最常用的三个指标是:
- MAE(平均绝对误差):误差绝对值的均值,直观反映平均偏差多少“元”
- RMSE(均方根误差):误差平方的均值的开方,对大误差更敏感,能放大模型在某些极端点上的失误
- MAPE(平均绝对百分比误差):误差占真实值的百分比,可以说是最直观的指标,衡量误差的相对大小
from sklearn.metrics import mean_absolute_error, mean_squared_error import numpy as np mae = mean_absolute_error(real_prices, predicted_prices) rmse = np.sqrt(mean_squared_error(real_prices, predicted_prices)) mape = np.mean(np.abs((real_prices - predicted_prices) / real_prices)) * 100 print(f"MAE: {mae:.4f}") print(f"RMSE: {rmse:.4f}") print(f"MAPE: {mape:.2f}%")打个比方,假设平安银行股价在10~15元之间波动,模型算出来的RMSE如果是0.3元,MAPE大概是2%上下,这意味着平均每次预测误差只有两三毛钱,精度已经很不错了。如果你计算出MAPE在1%以内,那这个模型足以惊艳大多数评分老师。
5.2 可视化结果:如何画一张能“讲出故事”的图
预测曲线和真实曲线的对比图是整份报告的灵魂。这张图画得好不好,直接影响观感。
plt.figure(figsize=(16, 6)) # 划分绘图区间:训练集/测试集 train_predict_dates = df['date'].values[look_back:look_back+len(X_train)] val_predict_dates = df['date'].values[look_back+len(X_train):look_back+len(X_train)+len(X_val)] test_predict_dates = df['date'].values[look_back+len(X_train)+len(X_val):] # 绘制真实价格曲线 plt.plot(test_predict_dates, real_prices, label='Real Price', color='#1f77b4', linewidth=2) # 绘制预测价格曲线 plt.plot(test_predict_dates, predicted_prices, label='Predicted Price', color='#ff7f0e', linewidth=2, linestyle='--') plt.title('LSTM Stock Price Prediction - Test Set', fontsize=16) plt.xlabel('Date', fontsize=12) plt.ylabel('Price (CNY)', fontsize=12) plt.legend() plt.grid(True, alpha=0.3) plt.xticks(rotation=45) plt.tight_layout() plt.savefig('figures/prediction_result.png', dpi=300, bbox_inches='tight') plt.show()在报告里对这张图的解读应该是:模型的预测曲线(橙色虚线)和真实曲线(蓝色实线)走势基本一致,模型成功捕捉到了从上行到下行的趋势转折点,但在局部高峰和低谷处存在一定滞后,这是LSTM模型基于历史数据滚动预测的固有特性。
有一说一,这句话写到报告里,比单纯贴一张图有说服力一百倍。
5.3 误差分布分析
除了预测-真实曲线,建议再加一张误差分布图,这会让你的报告在“分析深度”上远超同学:
errors = (real_prices - predicted_prices).flatten() plt.figure(figsize=(12, 4)) plt.subplot(1, 2, 1) plt.hist(errors, bins=30, edgecolor='black', alpha=0.7) plt.title('Prediction Error Distribution') plt.xlabel('Error (CNY)') plt.ylabel('Frequency') plt.subplot(1, 2, 2) plt.plot(errors) plt.title('Error Sequence Over Time') plt.xlabel('Sample Index') plt.ylabel('Error (CNY)') plt.tight_layout() plt.savefig('figures/error_analysis.png', dpi=300) plt.show()误差基本围绕0值对称分布,说明模型没有系统性偏差;误差随时间的变化没有明显的模式,说明误差是随机的,模型能力已经在当前特征和结构下得到了充分挖掘。
6. 常见问题与排查技巧实录
6.1 典型报错:维度不匹配
在调整数据shape、输入模型时最容易遇到维度错误:
ValueError: Input 0 of layer "lstm" is incompatible with the layer: expected ndim=3, found ndim=2. Full shape received: (None, 60)原因:LSTM层期望三维输入(batch_size, time_steps, features),但传进去的X_train是二维的,说明忘了reshape。解决办法就是上面代码里那行:
X_train = X_train.reshape((X_train.shape[0], X_train.shape[1], 1))6.2 训练损失不下降或震荡
原因可能有三个:
- 数据没有归一化,或归一化方式不对(比如
fit和transform混在一起) - 学习率过大(从0.01改成0.001试试)
- 数据量太少,模型无法拟合(增加历史数据的时间范围,从3年加到5年)
6.3 预测结果是一条“平移的线”,完全滞后于真实曲线
这是LSTM做股票预测时最经典的现象。模型学到的规律往往是“今天的股价≈昨天的股价”,所以预测结果看起来像真实曲线整体向右平移了一天。
出现这种情况,说明模型没有学到真正的趋势模式,只是在拟合一个“惯性”。解决思路:
- 调整
look_back,拉长历史视野 - 加入更多特征(成交量、开盘价、最高价、最低价),让模型有更多信息可用
- 尝试差分处理,预测的不是价格本身,而是价格的变化量(明天的价格减去今天的价格)
6.4 安装TensorFlow失败
如果在Windows上安装TensorFlow遇到Failed to load the native TensorFlow runtime,大概率是Visual C++运行库没装,去官网下载“Visual C++ Redistributable”安装即可。如果遇到No module named 'tensorflow',检查当前环境的pip是否指向了正确路径:
pip list | grep tensorflow python -c "import tensorflow as tf; print(tf.__version__)"6.5 反归一化后的预测值出现负值
预测出的价格变成负数,明显不合理,但反归一化之后确实可能遇到,因为模型在极端情况下可能输出负值。解决办法:加一层ReLU或abs,或者在评估时直接忽略这种异常值,也可以用numpy.clip把预测值clip到最低0。
7. 冲95分以上的报告撰写技巧与后续扩展
7.1 报告结构与篇幅分配
一份高分报告,结构应该严格遵循学术论文的规范:
- 摘要:300字以内,说清楚研究目标、方法、主要结果
- 引言:200-500字,说明研究背景和意义
- 相关工作/理论基础:500-800字,解释LSTM的原理,配上门控结构的示意图
- 数据与预处理:500-800字,来源、范围、归一化、滑窗
- 模型设计:500-800字,结构图、参数表、超参数选择理由
- 实验结果与分析:800-1200字,指标表、预测图、误差分析、对比实验
- 总结与展望:300字以内,说清模型局限性
- 参考文献:至少8-10篇
其中结果分析部分是最能拉分的。单纯把指标列出来不算分析,你要讨论“为什么RMSE偏高”、“哪个时间段预测效果差、为什么”、“如果换一只股票结果会怎样”。
7.2 对比实验
如果想让分数突破95,建议加一个对比模型。最简单的做法是用sklearn的线性回归或者支持向量回归,跑同样的数据,对比RMSE、MAPE。这么做的意义在于,展示了你的批判性思维:LSTM不一定在所有场景下都比传统模型好,你能客观分析优劣,这就是研究能力。
7.3 后续可以怎么玩
如果你拿了95分还想继续深化,方向很多:用注意力机制改造LSTM、用Transformer/Informer做时间序列预测、做多因子选股、搭建一个自动交易的回测框架。期末作业不是终点,它是你入门“AI+金融”的第一步。
7.4 关于“代码复用”和“防止千篇一律”的最后提醒
基于LSTM的股票预测,是全网教程最多的Python项目之一。如果你照着别人的代码改个股票代码就交上去,大概率会和同学撞车,甚至被老师看出是模板。高分的前提是:你能讲清每一步为什么这么做,加入自己的实验,体现出自己的思考。
换句话说,代码可以借鉴,但报告里的深度、图表、对比分析,都应该是你自己亲手产出的。
我个人在实际操作中的体会是:这个项目的难点其实不在LSTM本身,而是在于各项细节的把控。数据是否规范、归一化是否泄露、滑窗大小是否有依据、指标解读是否到位,每一个细节差一点,总分就拉开了10分。把这份复盘里的每一个坑都避开,你的期末大作业就已经站在了优秀线上。
本文还有配套的精品资源,点击获取