简介面向高校机器学习与金融数据分析类课程的LSTM股票价格预测Python完整项目尤其适合课程设计、期末大作业或毕业设计参考。项目覆盖行情数据读取、时间序列预处理、LSTM模型构建与训练、预测结果对比可视化等核心环节并附有使用说明文档按文档配置环境即可直接运行复现实验效果。压缩包共89个文件以Python脚本、txt说明文档、网页前端样式与交互脚本、Excel及SQLite数据文件等类型为主整体约8.89MB目录结构清晰便于按模块阅读、调试和二次改造。已有738人浏览学习适合需要快速完成高分课程设计同时想深入掌握LSTM建模流程、股票序列特征与预测评估细节的读者。1. 基于LSTM的股票价格预测课程设计里最值得下的一份源码基于LSTM的股票价格预测是课程设计和期末大作业里出现频率极高的题目。原因很直白题目自带热度LSTM是时序建模主力代码量不大可视化效果好答辩时好讲。这份源码就是冲这个场景准备的解压后code.py是核心训练代码django目录提供网页展示README.md写清了运行步骤和参数说明属于拿到手就能跑的那一类。它解决的事很具体用过去一段时间的股价序列训练LSTM预测下一交易日的收盘价再把预测曲线和真实价格画在同一张图上。适合三类人——要做课程设计但不想从零写代码的学生想把LSTM时序预测完整跑通一遍的入门开发者以及在找可复现参考实现的从业者。这里多说一句不是所有股票预测代码都能下载即用数据接口失效、归一化还原报错、PyTorch版本不匹配都是常见坑这份源码带使用说明文档按README准备数据就能跑。2. 源码结构与数据准备从CSV到训练集的完整流水线2.1 源码包结构code.py、django目录和README.md怎么分工解压后是一个标准的Git仓库结构顶层目录是stock_prediction。先看文件清单路径作用stock_prediction/.gitattributesGit属性配置文件不影响运行stock_prediction/README.md使用说明文档含环境安装、运行步骤、参数说明stock_prediction/code.py核心训练代码数据预处理、模型构建、训练、预测、绘图全在这里stock_prediction/django/基于Django的网页展示端用于答辩演示code.py是主程序整个流程可以拆成九个步骤# code.py 主流程 import numpy as np import pandas as pd import torch import torch.nn as nn from sklearn.preprocessing import MinMaxScaler # 1. 读取数据 # 2. 提取收盘价并归一化 # 3. 按 time_step 构造监督学习样本 # 4. 按时间顺序切分训练集/测试集 # 5. 定义 LSTM 模型 # 6. 训练并保存模型 # 7. 预测、还原、绘图这个结构几乎是所有LSTM价格预测项目的标准骨架。code.py偏训练django目录偏展示README.md是说明书。如果你只想要预测结果跑code.py就够了如果要答辩演示django端可以把曲线和预测结果放到网页上效果比终端输出直观得多。2.2 数据准备读取CSV、归一化与滑动窗口股票数据的原始格式一般是date, open, high, low, close, volume。做价格预测时最常用的是close收盘价原因有两个一是收盘价是当天交易的最终共识噪声相对小二是单变量序列最容易把LSTM跑通后续要加成交量、开盘价做多特征只需把input_size改成对应维数。常见做法是拿tushare或akshare拉数据但课程设计阶段更省事的是直接读本地CSV。CSV列名必须对得上否则pandas读出来全是空值。归一化这步很多人不理解为什么必须做。LSTM内部用的是tanh和sigmoid激活函数输入数值太大时梯度会饱和训练直接卡死把价格压到0到1之间梯度才稳定。MinMaxScaler是按列找min和max再线性映射还原时用inverse_transform就能回到原始价格量纲。import pandas as pd from sklearn.preprocessing import MinMaxScaler df pd.read_csv(stock_data.csv) # 列: date, open, high, low, close, volume prices df[close].values.reshape(-1, 1) # 转成二维列向量 scaler MinMaxScaler(feature_range(0, 1)) scaled scaler.fit_transform(prices) # 归一化到 [0, 1] print(f原始价格范围: {prices.min():.2f} ~ {prices.max():.2f}) print(f归一化后范围: {scaled.min():.2f} ~ {scaled.max():.2f})参数说明reshape(-1, 1)是必须的sklearn的Scaler要求输入是二维数组(n_samples, n_features)feature_range设为(0, 1)是为了配合tanh和sigmoid的敏感区间。如果股票价格本身波动不大也可以直接标准化StandardScaler但MinMaxScaler在还原预测值时更直观因为inverse_transform直接回到价格量纲。接着构造监督学习样本。LSTM的单步预测逻辑是用第t天往前60天的收盘价预测第t天的价格。这个60就是time_step也叫滑动窗口。窗口太短模型看不到趋势窗口太长计算量上去了但收益有限。课程设计里60是常见配置对应约三个月的交易日。def create_sequences(data, time_step60): X, y [], [] for i in range(len(data) - time_step - 1): X.append(data[i:i time_step, 0]) # 窗口内60天 y.append(data[i time_step, 0]) # 第61天 return np.array(X), np.array(y) time_step 60 X, y create_sequences(scaled, time_step) split int(len(X) * 0.8) X_train, X_test X[:split], X[split:] y_train, y_test y[:split], y[split:] X_train X_train.reshape(-1, time_step, 1) X_test X_test.reshape(-1, time_step, 1) print(fX_train: {X_train.shape}, X_test: {X_test.shape}) print(fy_train: {y_train.shape}, y_test: {y_test.shape})逻辑说明create_sequences返回的X是二维(time_step,)的集合最后再reshape成(batch, time_step, 1)这是PyTorch LSTM的标准输入格式——第一维是batch第二维是时间步第三维是特征数。划分比例0.8/0.2是课程设计常见配置如果数据量小可以改成0.7/0.3让测试曲线更完整。注意这里划分训练/测试集不能用random.shuffle时间序列一旦打乱训练集就混进了未来数据测试集指标会虚高。必须按时间顺序硬切。3. LSTM模型构建与参数选型网络结构、超参和设计理由3.1 为什么选LSTM梯度问题与门控机制RNN在处理长序列时有个致命问题梯度在反向传播过程中会反复相乘序列一长梯度就指数级衰减或爆炸前面的价格信息根本传不到后层模型只能记住最近几天。LSTM在循环单元里加了三道门——遗忘门决定过去信息丢多少输入门决定新信息记多少输出门决定输出什么。三道门让梯度有了一条相对平缓的高速通道所以能记住几十天前的趋势。对股票价格来说这很有用今天的价格既受昨天影响又受一个月前的趋势区间影响LSTM能同时抓住这两个尺度。ARIMA和线性回归也能做价格预测但它们对非线性关系建模能力弱课程设计里用LSTM在答辩时也更有说头。这也是为什么网上搜lstm模型代码和lstm神经网络出来最多的是价格预测方向的demo。3.2 模型定义与超参表import torch.nn as nn class LSTMPredictor(nn.Module): def __init__(self, input_size1, hidden_size64, num_layers2, output_size1, dropout0.2): super(LSTMPredictor, self).__init__() self.lstm nn.LSTM( input_sizeinput_size, hidden_sizehidden_size, num_layersnum_layers, batch_firstTrue, dropoutdropout ) self.fc nn.Linear(hidden_size, output_size) def forward(self, x): # x 形状: (batch, time_step, input_size) out, _ self.lstm(x) out out[:, -1, :] # 取最后一个时间步的隐状态 out self.fc(out) # 全连接层映射到价格 return out逻辑说明关键在out[:, -1, :]。LSTM返回所有时间步的隐状态序列但预测任务只需要最后一步的输出因为最后一步的隐状态已经包含了整个窗口的信息。中间所有隐状态在序列标注或编码器场景里才用得上这里直接丢弃。参数默认值作用建议范围input_size1每步输入的特征数只用收盘价时填1hidden_size64隐层记忆维度32~128num_layers2LSTM层数1~3dropout0.2随机丢弃比例防过拟合0~0.5output_size1输出维度回归任务固定1hidden_size是记忆容量64起步数据量小就32数据量大可以试128。num_layers超过3层对几百条价格序列几乎没有收益反而训练更慢、更容易过拟合。dropout只在训练时生效模型eval()模式下会自动关闭。3.3 损失函数、优化器与梯度裁剪回归任务用MSELoss它把预测和真实的平方差作为损失大误差会被放大促使模型优先修正偏离大的点。优化器选Adam原因是它对学习率不敏感课程设计场景不需要精细调lr0.001一般就能收敛。import torch model LSTMPredictor() criterion nn.MSELoss() optimizer torch.optim.Adam(model.parameters(), lr0.001) print(f模型参数量: {sum(p.numel() for p in model.parameters())})训练时一个容易被忽略的细节是梯度裁剪。LSTM在长序列上反向传播时梯度范数可能暴涨loss直接变NaN多步预测值变成天文数字。我一般会在每次backward之后加一行nn.utils.clip_grad_norm_把梯度范数钳制在1.0训练会稳很多。这个技巧在数据本身波动大、序列长的时候尤其重要。4. 训练、预测与评估指标让预测结果从数字变成对比曲线4.1 训练循环DataLoader、epochs与loss监控用TensorDataset和DataLoader把训练集包装成可以按批次取的组织形式。这里有个常见的认知偏差很多人习惯直接把整个数组扔给模型训练数据量小还行数据量上来之后显存吃不消而且分批次训练本身就有一定的正则化效果。from torch.utils.data import TensorDataset, DataLoader epochs 100 batch_size 32 train_dataset TensorDataset( torch.FloatTensor(X_train), torch.FloatTensor(y_train).view(-1, 1) ) train_loader DataLoader(train_dataset, batch_sizebatch_size, shuffleFalse) for epoch in range(epochs): model.train() total_loss 0.0 for batch_x, batch_y in train_loader: optimizer.zero_grad() pred model(batch_x) loss criterion(pred, batch_y) loss.backward() nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0) optimizer.step() total_loss loss.item() if (epoch 1) % 20 0: print(fEpoch {epoch1}/{epochs}, Loss: {total_loss/len(train_loader):.6f})参数说明shuffleFalse是刻意的时间序列一旦打乱就破坏了时间依赖关系模型会学到错误的规律。epochs选100是保守值判断依据是看最后20轮loss是否还在明显下降是就加到150已经平缓就保持100。batch_size32在价格预测场景里够用如果数据只有几百条batch_size可以降到16让每次参数更新更频繁。4.2 预测与还原inverse_transform和两条曲线训练完成后模型输出的是归一化值必须用训练时fit的scaler还原成原始价格。还原这一步是报错重灾区因为scaler在训练时输入的是二维列向量还原时如果传一维数组就直接抛异常。model.eval() with torch.no_grad(): train_predict model(torch.FloatTensor(X_train)).numpy() test_predict model(torch.FloatTensor(X_test)).numpy() # 还原归一化预测值和真实值都映射回原始价格 train_predict scaler.inverse_transform(train_predict) y_train_orig scaler.inverse_transform(y_train.reshape(-1, 1)) test_predict scaler.inverse_transform(test_predict) y_test_orig scaler.inverse_transform(y_test.reshape(-1, 1)) from sklearn.metrics import mean_squared_error, mean_absolute_error rmse np.sqrt(mean_squared_error(y_test_orig, test_predict)) mae mean_absolute_error(y_test_orig, test_predict) print(f测试集 RMSE: {rmse:.2f}, MAE: {mae:.2f})逻辑说明train_predict和test_predict已经是(n, 1)的二维形状可以直接inverse_transformy_train和y_test是create_sequences时留下的一维数组必须先reshape(-1, 1)再还原。最后用matplotlib把test_predict和y_test_orig画成两条曲线一眼就能看出拟合程度。4.3 评估指标怎么读指标含义对股票预测的参考价值RMSE均方根误差对大误差敏感能暴露极端偏差MAE平均绝对误差单位是价格直观看偏差多少元MAPE平均绝对百分比误差适合对比不同价位的股票课程设计报告里通常写RMSE和MAE两个就够。如果RMSE明显大于MAE说明预测在少数几天出现了较大偏差这在股票数据里很正常因为偶发暴涨暴跌很难被模型捕捉。5. 避坑课程设计里最常翻车的五个细节5.1 inverse_transform报维度错误现象还原预测值时抛出ValueError: Expected 2D array, got 1D array instead。原因scaler拟合时输入的是(n, 1)二维数组还原时如果传入一维(n,)数组sklearn直接拒绝执行。这是LSTM预测项目里出现频率最高的报错。解决预测结果先reshape(-1, 1)再inverse_transform还原后如果需要一维数组再调用ravel()。记住一个原则scaler训练时输入了什么形状还原时就必须传什么形状。5.2 预测曲线整体滞后一天现象训练loss很低但预测曲线和真实曲线几乎平行整体向右平移了一个周期。原因time_step过小时模型学到的最优策略就是「今天的价格约等于昨天的价格」本质上是在拟合恒等映射根本没有学到趋势。网上不少lstm预测demo都有这个毛病只是没被指出来。解决把time_step加大到40到60同时用第6章的滚动多步预测验证模型是否真的有外推能力。单步预测的滞后现象是这个任务里最难摆脱的通病答辩时被老师追问的可能性极高。5.3 两次运行结果差异大现象同一份代码连续跑两遍loss曲线和预测图都不一样。原因模型初始化、DataLoader顺序、dropout都有随机性没有固定随机种子结果自然漂移。这在课程设计里特别容易被质疑实验结果不可复现。解决文件开头统一固定种子。import random import numpy as np import torch random.seed(42) np.random.seed(42) torch.manual_seed(42)5.4 测试集数据泄漏现象测试集RMSE异常低但换一段新数据预测效果崩了。原因对全部数据执行了scaler.fit_transformscaler提前看到了测试集的min和max相当于考试前看了答案。这是最隐蔽的一个坑因为训练曲线看起来一切正常。解决scaler只在训练集上fit测试集只用transform。数据划分必须发生在归一化之前或者至少保证fit_transform只作用在训练集切片上。5.5 CSV中文路径或中文列名报错现象pd.read_csv读文件时报UnicodeDecodeError或者列名取不到。原因Windows默认编码是GBK而CSV文件通常是UTF-8保存的路径带中文也可能导致某些环境下解码失败。解决路径和文件名全部用英文read_csv时指定encodingutf-8。这是环境问题跟模型本身无关但每年都有人在这个上面浪费半天时间。6. 进阶多步预测与滚动回测验证要证明LSTM模型真的有预测能力单看单步拟合曲线不够。单步预测本质是拿真实历史去预测下一天很多情况下模型学到的只是「今天的价格和昨天差不多」曲线看起来拟合得很好实际上没有外推能力。答辩时最经典的问题就是「你预测的是哪一段未来数据你能预测吗」这时候需要做多步预测。多步预测的思路是滚动把当前窗口的最后一天预测值拼到序列尾部同时丢掉最旧的一天让窗口整体往前挪一步接着预测下一天。这样连续预测出未来N天的价格。def multi_step_predict(model, initial_seq, steps5, scalerNone): model.eval() seq initial_seq.copy() # shape: (time_step, 1) predictions [] with torch.no_grad(): for _ in range(steps): input_tensor torch.FloatTensor(seq).view(1, seq.shape[0], 1) next_val model(input_tensor).item() # 单日预测 predictions.append(next_val) # 滚动窗口删除最旧一天拼接预测值 seq np.vstack([seq[1:], [[next_val]]]) if scaler is not None: predictions scaler.inverse_transform( np.array(predictions).reshape(-1, 1) ).ravel() return predictions参数说明initial_seq取测试集最后time_step个真实值steps是要预测的天数scaler传训练时那个scaler输出就是原始价格单位。steps设得越大预测误差会累积得越明显这是递归多步预测的固有特性课程设计里建议预测5天以内。有了这个函数回测就很容易写从测试集第一个点开始滑动取窗口预测未来5天与真实5天对比画一条虚线扩展。如果模型的预测曲线在5步之后仍然能大致跟随趋势方向说明模型确实学到了价格动量而不是单步滞后拟合。模型训练完记得保存torch.save(model.state_dict(), lstm_model.pth) # 加载 model LSTMPredictor() model.load_state_dict(torch.load(lstm_model.pth)) model.eval()加载模型时必须重新实例化同一个LSTMPredictor类再load_state_dict否则结构对不上会报错。保存state_dict而不是整个模型兼容性更好换PyTorch版本也不容易出问题。我以前栽过一个大跟头训练时单步拟合漂亮就没做多步验证答辩时被老师追问「你模型外推能力怎么样」当场答不上来。从那以后我每次跑股票预测类项目都会强制自己把多步回测和数据泄漏检查走一遍确认曲线不是延迟复制再下结论。希望帮到你。本文还有配套的精品资源点击获取