news 2026/10/9 3:01:15

PyTorch实现LSTM股价预测:从数据处理到模型评估的完整指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
PyTorch实现LSTM股价预测:从数据处理到模型评估的完整指南

简介:基于Python与PyTorch框架实现LSTM对股票价格预测的完整源码项目,面向正在完成期末大作业、课程设计或毕业设计的计算机专业学生,也适合希望动手实践深度学习时序预测的初学者。压缩包共14个文件,包含5个Python脚本、3个pyc缓存、2张示意图片、CSV数据文件、Markdown说明及pkl模型文件,整体仅359KB,结构紧凑,下载后即可解压运行。项目源码经导师指导审定,评审分达98分,所有代码均经过本地编译与严格调试,覆盖数据加载、数据集构建、LSTM模型定义、训练、评估及结果可视化等完整流程,并配有依赖清单与配套说明文档,能够帮助读者快速理解LSTM在股价预测中的应用逻辑与调参思路。该资源已有90人学习下载,难度较为适中,尤其适合作为深度学习课程设计参考、毕业设计模块或入门实战项目。

1. 用LSTM预测股价:不是玄学,但也不是印钞机

LSTM做股票价格预测,是Python学习路线里最常被点名的期末大作业题目之一。这个基于PyTorch的LSTM股票预测项目,包含从数据解析、数据集构造、模型训练到评估可视化的完整脚本,解压后装好依赖,依次运行train.py和evaluate.py,就能看到预测曲线与评估指标。

它解决的核心问题是:给定一段历史收盘价序列,让LSTM把序列中的规律学出来,预测下一天的价格。项目经过导师指导和助教审定,评审分98分,源码做过严格调试,不是解压后跑不起来的半成品。

适合正在做期末大作业、课程设计的计算机专业学生,也适合想完整走一遍数据处理到结果评估流程的Python学习者。难度适中,先把代码跑通,再对照参数理解每一步在干什么,是性价比很高的练手资源。

2. 项目骨架与数据流:六个文件各自管什么

2.1 文件清单与职责边界

解压后是一个Stock_Prediction文件夹,文件不多,但每个都有明确分工。先把职责边界梳理清楚,后面跑起来才不会慌。

文件职责
parser_my.py解析原始数据,提取收盘价成numpy数组
dataset.py把价格序列切成滑窗样本,输出Tensor
LSTMModel.py定义LSTM网络结构
train.py训练入口,集中参数设置、数据切分、训练循环
evaluate.py加载模型做预测,输出MAE/RMSE指标和对比曲线
requirements.txt依赖清单,安装环境用

另外,data存放原始CSV数据,model存放训练好的权重,img存放评估图片,__pycache__是Python自动生成的字节码缓存,不用管。README.md里有运行说明,建议先读一遍。

这个结构是典型的"数据解析→数据集→模型→训练→评估"五段式,和多数PyTorch入门项目的组织方式一致。理解这个骨架的收益在于:以后换自己的数据,只需要改parser_my.py和dataset.py两个文件,模型和训练脚本基本不用动。

2.2 数据从CSV到Tensor:parser_my.py与dataset.py的配合

parser_my.py做的事很单纯:从CSV里把价格列取出来,转成float32数组。

# parser_my.py 核心逻辑 import pandas as pd import numpy as np def parse_close_price(csv_path): df = pd.read_csv(csv_path) # 列名以实际数据为准,常见的有close/Close/收盘价 close = df['close'].values.astype(np.float32) return close

逻辑说明:pandas读入CSV后取close列,转成numpy数组。转成float32是有讲究的:PyTorch默认浮点类型就是float32,提前转好可以避免后续Tensor转换时报错或产生额外拷贝;float32比float64省一半内存,数据量大时体感明显。

列名的坑在真实数据里最常见,不同渠道下载的数据,列名可能是close、Close、收盘价,甚至带前后空格。第一次跑之前先print(df.columns)看一眼,否则KeyError很难定位。如果原始数据是Excel或接口返回的JSON,只需改这个函数的读取方式,保持"返回一维float数组"的约定,后面代码不用动。换数据时我一般会先打印close数组的长度和头尾几条数据,确认量级和区间正常,再进入下一步,免得数据源是空的或全重复,训练出完全没意义的模型。

dataset.py负责把一维价格序列切成"用过去N天预测下一天"的样本对:

# dataset.py 核心逻辑 import torch from torch.utils.data import Dataset class StockDataset(Dataset): def __init__(self, data, seq_len=10): self.data = data self.seq_len = seq_len def __len__(self): return len(self.data) - self.seq_len def __getitem__(self, index): x = self.data[index:index + self.seq_len] # 输入:过去seq_len天 y = self.data[index + self.seq_len] # 标签:后一天价格 x = torch.tensor(x, dtype=torch.float32).unsqueeze(-1) y = torch.tensor(y, dtype=torch.float32) return x, y

参数说明:seq_len是滑窗长度,取10表示用10个交易日的收盘价预测第11天。__getitem__里unsqueeze(-1)把x从(seq_len,)变成(seq_len, 1),因为PyTorch的LSTM要求输入形状是(batch, seq_len, input_size),这里input_size=1表示只用收盘价这一个特征。后面要加成交量的话,就把多列拼进滑窗,input_size对应变成2或更大。样本总数是len(data) - seq_len,1000天数据配seq_len=10,样本数是990个,这个边界条件算错会导致最后一个样本越界。

2.3 归一化:必须在dataset外面做

train.py里有一段MinMaxScaler归一化,把价格压到0到1之间。价格数据动辄几十到几百,不归一化直接喂LSTM,梯度很容易爆炸,loss飘到nan是家常便饭。

# train.py 数据归一化片段 from sklearn.preprocessing import MinMaxScaler scaler = MinMaxScaler() train_data_normalized = scaler.fit_transform(train_data.reshape(-1, 1)) test_data_normalized = scaler.transform(test_data.reshape(-1, 1))

这里有一个新手最容易踩的坑:归一化的min和max只能用训练集计算。如果先fit全量数据再切分,测试集的最高最低点信息在训练阶段就泄露给了模型,评估指标虚高,答辩时被问一句就露馅。顺序必须是:先按时间切分,再fit训练集,最后transform测试集,之后用同一个scaler做inverse_transform还原预测值。

requirements.txt里包含pandas、numpy、torch、scikit-learn、matplotlib这几项,装环境时在项目目录下执行pip install -r requirements.txt。如果本机Python环境还没配好,先把Python装好并确认pip可用,再装依赖,顺序别反,否则会出现import时ModuleNotFoundError的连环报错。

3. 模型定义与训练参数:把LSTM网络结构拆开看

3.1 LSTMModel.py里的网络结构

LSTMModel.py定义的网络非常经典:双层LSTM加一个全连接输出层,整个类不到二十行。

# LSTMModel.py 核心逻辑 import torch.nn as nn class LSTMModel(nn.Module): def __init__(self, input_size=1, hidden_size=64, num_layers=2, output_size=1): super(LSTMModel, self).__init__() self.lstm = nn.LSTM( input_size=input_size, hidden_size=hidden_size, num_layers=num_layers, batch_first=True ) self.fc = nn.Linear(hidden_size, output_size) def forward(self, x): out, (hidden, cell) = self.lstm(x) out = self.fc(out[:, -1, :]) # 取最后一个时间步输出 return out

参数说明:input_size=1对应dataset里每个时间步只喂一个价格;hidden_size=64是隐状态维度,相当于网络的记忆容量,调大能增强表达能力,但也更容易过拟合;num_layers=2是堆叠两层LSTM,第一层提取短期模式,第二层在其输出上再抽象一层;batch_first=True让输入形状是(batch, seq_len, input_size),正好和dataset.py生成的张量对齐。

forward里out[:, -1, :]是取序列最后一个时间步的隐状态输出,这是单步预测的标准写法。LSTM每个时间步都有输出,但做单步预测只关心最后一步的状态。有一个常见误区:hidden_size不是层数,而是每一层记忆单元的宽度,它和num_layers是两个独立维度。改参数时建议一次只动一个变量并记录效果,两个一起改,出了问题根本定位不到是哪个改动引入的。

3.2 train.py的训练循环与关键超参

train.py是整个项目的主入口,流程是:读数据→归一化→构造dataset→构造DataLoader→定义模型、loss、优化器→循环训练→保存权重到model目录。

# train.py 核心训练片段 import torch import torch.nn as nn from torch.utils.data import DataLoader from dataset import StockDataset from LSTMModel import LSTMModel SEQ_LEN = 10 BATCH_SIZE = 32 EPOCHS = 150 LEARNING_RATE = 0.001 TRAIN_RATIO = 0.8 model = LSTMModel(input_size=1, hidden_size=64, num_layers=2, output_size=1) criterion = nn.MSELoss() optimizer = torch.optim.Adam(model.parameters(), lr=LEARNING_RATE) for epoch in range(EPOCHS): model.train() total_loss = 0 for x_batch, y_batch in train_loader: optimizer.zero_grad() pred = model(x_batch) loss = criterion(pred.squeeze(-1), y_batch) loss.backward() optimizer.step() total_loss += loss.item() print(f"Epoch {epoch+1}/{EPOCHS}, Loss: {total_loss / len(train_loader):.6f}")

参数说明:MSELoss是回归任务的标准选择,对较大误差施加平方惩罚,适合连续的股价预测。Adam加lr=0.001是这类小项目最稳妥的起点,不需要自己设计学习率衰减。BATCH_SIZE=32在几百到几千条数据的范围内都合理,数据量小可以降到16。EPOCHS=150不是越大越好,要观察loss是否进入平台期,如果50轮就过拟合,跑完150轮纯属浪费时间。

两个容易忽略的细节:一是pred和y_batch的形状要对齐,LSTM输出是(batch, 1),y_batch是(batch,),所以要pred.squeeze(-1)削掉末尾维度再算loss;二是model.train()不能省,这个模型虽然没用Dropout和BatchNorm,但养成切换模式的好习惯,以后换带这些层的模型结构时不会翻车。

3.3 训练集测试集切分:时序数据不能乱切

train.py里TRAIN_RATIO=0.8,按时间顺序取前80%做训练、后20%做测试。关键是"按时间顺序"——股价是强时序数据,如果像图像分类那样随机切分,测试样本会混在训练样本中间,等于让模型"穿越"回去预测,评估结果完全失真。

这个项目没有显式切验证集,靠训练loss趋势来观察是否过拟合。训练loss持续下降而测试指标很差,基本就是过拟合,常见应对是调小hidden_size、增大seq_len,或者在全连接层前加Dropout。更严谨的做法是从训练集末尾再切10%出来做验证集,用来挑epoch和调参,测试集留到最后只评估一次。

4. 跑通评估与可视化:evaluate.py的指标和曲线怎么读

4.1 MAE与RMSE:两个指标背后的含义差异

evaluate.py加载model目录下的权重,对测试集做预测,输出两个核心指标:平均绝对误差MAE和均方根误差RMSE。

MAE是所有预测误差绝对值的平均,单位就是价格单位,误差5元代表平均每天偏差5元。RMSE先把误差平方再取平均再开方,对大误差更敏感。如果MAE和RMSE差距明显,说明测试集里存在少数偏差特别大的点,通常对应数据里的暴涨暴跌段。比如MAE=5、RMSE=9,意味着大部分日子偏差在5元以内,但某些极端行情下偏差可能到十几二十元。

什么算好的MAE?要看股价量级。一只20元的股票MAE=0.5元是很好的结果,一只200元的股票MAE=5元也还可以。更实用的判断标准是算相对误差:MAE除以真实价格均值,10%以内算能看,5%以内算优秀。跑完evaluate.py,我一般会顺手算一个方向准确率——预测值比前一天高且真实值也比前一天高,记为预测正确。方向准确率低于50%基本等于瞎猜,哪怕MAE再好看也不能用。把这个指标写进报告,比只放MAE更有说服力。

4.2 逆归一化与画图:把预测拉回真实价格

模型输出是0到1之间的归一化值,画图前必须用训练时那个scaler做inverse_transform,把预测值和真实值一起还原成原始价格,否则两条曲线不在一个量级,画出来根本没法看。

# evaluate.py 核心评估片段 import matplotlib.pyplot as plt import torch model.eval() with torch.no_grad(): test_pred = model(test_x_tensor) test_pred = test_pred.numpy().reshape(-1, 1) pred_price = scaler.inverse_transform(test_pred) real_price = scaler.inverse_transform(test_y.numpy().reshape(-1, 1)) plt.figure(figsize=(12, 5)) plt.plot(real_price, label='Real Price', color='blue') plt.plot(pred_price, label='Predicted Price', color='red', linestyle='--') plt.legend() plt.savefig('img/prediction_result.png', dpi=150) plt.show()

逻辑说明:model.eval()把模型切到推理模式,torch.no_grad()停止梯度追踪,这两个搭配运行时更快、省内存,更重要的是防止意外更新模型参数。逆归一化必须复用训练时的scaler实例,里面存着训练集的min和max;如果重新new一个MinMaxScaler,没fit过,inverse_transform会直接报错。

画图时还有个小技巧:数据量大时全局曲线太密,细节看不清,可以只画最后200个点的局部放大图。evaluate.py保存的img里可以同时输出全局图和局部图,课程设计报告里用局部图更清楚。

4.3 预测曲线的三种典型形态

第一种是理想形态:预测曲线贴着真实曲线走,方向转折基本同步,MAE在真实价格均值的5%以内,这种结果可以直接写进报告。

第二种是滞后形态:预测曲线形状对,但整体右移一两个时间步,转折点慢半拍。这是单步预测框架的固有现象,下面避坑章节第一条展开讲。

第三种是平坦形态:预测曲线几乎是一条水平线,只在整体均值附近小幅波动,真实曲线的起伏完全没学到。这通常说明模型容量不足、数据特征太少,或者seq_len设置不合理。看到平坦形态,优先调大hidden_size和seq_len,再看loss是否真的降了下去。

如果三种形态都不是,预测曲线和真实曲线毫无关联、形状完全不同,先检查训练是否真正收敛:把train.py打印的loss曲线拿出来看,如果loss还在持续下降,说明训练轮数不够,加大EPOCHS再跑。写报告时建议把原始价格曲线、预测曲线、归一化后的loss曲线三张图放在一起,分别对应数据、模型收敛、预测效果三层内容,导师看起来一目了然。

5. LSTM股价预测避坑:五个高频问题实测记录

5.1 预测曲线整体右移:单步预测的滞后困局

现象:预测曲线形状和真实曲线一致,但整体右移一两个时间步,转折点总慢半拍。evaluate.py画出的图上,红色虚线明显比蓝色实线晚一拍。

原因:模型用过去10天预测下一天,平稳行情里学到的近似最优解是"明天的价格约等于今天的价格"。这个平滑解在大部分日子误差很小,但在趋势转折处必然滞后,因为转折信息还没进入输入窗口。这是单步预测框架的固有问题,不是代码bug。

解决:三个方向,一是把seq_len从10调大到20或30,让模型看到更长的上下文;二是改成多步预测,一次输出未来多天的价格,后续进阶章节有具体实现;三是评估时不要死盯逐日偏差,同时统计方向准确率,避免被滞后指标误导。

5.2 loss变成NaN,训练直接报废

现象:训练到某个epoch后loss突然变成nan,之后所有epoch全是nan,模型权重全部失效,此前的训练时间全白费。

原因:最常见是学习率太大导致梯度爆炸,在数据没归一化时尤其明显,价格动辄几百上千,梯度数值极易溢出。另一个高频原因是原始数据里有缺失值或无穷值,parser_my.py没做清洗,NaN一路传导到loss。

解决:先检查数据,用np.isnan(close).sum()确认没有缺失值;再把lr从0.001降到0.0005甚至0.0001;最后确认归一化真的执行了。我一般会在训练循环开头加一行assert not torch.isnan(x_batch).any(),把脏数据拦在模型之前,这个习惯帮我省了很多排错时间。

5.3 时序数据被shuffle,训练Loss好看但预测全废

现象:训练loss下降很漂亮,但evaluate.py在测试集上的预测效果一塌糊涂,方向对不上,曲线完全失真。

原因:DataLoader里设了shuffle=True,把时间顺序彻底打乱了。LSTM学的是时间依赖关系,训练样本顺序乱了,模型看到的前后关联全是错乱的,学到的规律迁移不到真实的时序上。这是时序任务最隐蔽的坑,因为训练loss完全正常,只有到评估阶段才暴露出来。

解决:训练和测试的DataLoader全部设置shuffle=False。如果数据量大、确实想借shuffle提升泛化,可以按时间窗口分组后只shuffle窗口之间的顺序,窗口内部保持时间序列,但本项目的数据量完全不需要这个操作。

5.4 归一化偷看未来数据,评估分虚高

现象:测试集MAE小得离谱,模型看起来神预测,但换一段新数据后表现崩盘,完全不可用。

原因:归一化时用了全量数据的min和max,测试集的最高点和最低点信息在训练阶段就泄露给了模型。这是特征工程层面的数据泄露,属于时序预测项目里最容易被答辩老师抓到的概念错误。

提示:判断数据是否泄露的快速方法——把测试区间换到训练数据没有覆盖的新时间段,指标明显下降,就说明之前的评估存在泄露。

解决:严格执行"先按时间切分、再fit训练集、最后transform测试集"的顺序。代码里MinMaxScaler只对训练集调用fit_transform,测试集只调用transform。验证模型真实性时,把测试时间区间拉到训练数据覆盖范围之外,如果指标明显变差,说明之前的高分是泄露造成的虚高。

5.5 重新加载模型,评估结果和训练时对不上

现象:训练结束当场评估一切正常,隔天重新运行evaluate.py,指标变差或者曲线方向对不上,像是换了个模型。

原因:常见三类,一是模型权重路径不对或加载了旧文件;二是加载后没有调用model.eval(),BatchNorm在训练和推理模式下行为不同,导致输出漂移;三是model目录下权重文件被覆盖或写了一半,加载时不报错但内容是残缺的。

解决:torch.load时加map_location='cpu',避免GPU/CPU设备不匹配;加载后强制model.eval();保存模型时同时存一份带超参数和训练指标的meta.json,加载后先对比指标,对不上就直接重新训练。我的习惯是每次实验跑完复制一份带时间戳的权重文件,避免model目录被后续实验覆盖。

6. 让预测更稳:多步预测、外部特征与早停三个调整方向

6.1 多步预测替代单步滚动

单步预测滞后是框架问题,改成多步预测能明显缓解,因为模型没法靠"照抄上一个值"糊弄过去。

class LSTMMultiStep(nn.Module): def __init__(self, input_size=1, hidden_size=64, num_layers=2, output_steps=5): super().__init__() self.lstm = nn.LSTM(input_size, hidden_size, num_layers, batch_first=True) self.fc = nn.Linear(hidden_size, output_steps) def forward(self, x): out, _ = self.lstm(x) return self.fc(out[:, -1, :])

标签y改成未来K天的价格序列,dataset.py里y取index之后K个连续值,MSE对K个输出一起算,滞后会有肉眼可见的改善。

6.2 加入成交量和开盘价等外部特征

input_size从1扩到2或更多,把成交量、开盘价、最高价、最低价拼进滑窗。成交量能反映市场活跃度,对趋势判断有实打实的帮助。所有新特征都要归一化,量纲差异大的特征混在一起会让LSTM训练很不稳定。

6.3 用早停法挑epoch

固定150轮对某些数据可能早就过拟合。加一个提前停止:验证集loss连续N轮不降就停,同时保存最优权重。

best_loss = float('inf') patience = 10 no_improve = 0 for epoch in range(EPOCHS): val_loss = evaluate_val_loss() if val_loss < best_loss: best_loss = val_loss no_improve = 0 torch.save(model.state_dict(), 'model/best_model.pt') else: no_improve += 1 if no_improve >= patience: break

从那以后我每次跑序列预测项目,都会强制走一遍"先检查归一化切分顺序、再确认shuffle关闭、最后看loss趋势"这条流程。希望帮到你——把parser_my.py里的CSV路径换成你的数据,改一下列名和seq_len,按顺序跑通train.py和evaluate.py,整套流程走下来,你对LSTM做时序预测的理解,会远比看十篇教程来得扎实。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/9 3:00:30

Hadoop和Spark大数据项目案例:集群搭建、参数调优与数据倾斜处理

简介&#xff1a;这是一份面向大数据架构师、解决方案人员及技术学习者的Hadoop与Spark项目案例分析文档。文档从实际工作常见场景出发&#xff0c;梳理了数据整合、专业分析、Hadoop作为一种服务、流分析、复杂事件处理、ETL流、更换或增加SAS七类典型大数据项目&#xff0c;逐…

作者头像 李华
网站建设 2026/10/9 3:00:17

企业AI大模型数字底座方案:架构拆解与落地避坑指南

简介&#xff1a;这份文档面向企业数字化负责人、架构师与技术规划人员&#xff0c;围绕AI大模型数字底座建设提供一套完整设计方案&#xff0c;帮助解决转型路径不清、技术选型与业务需求脱节等问题。资源包共1个docx文件&#xff0c;约342KB&#xff0c;内容按项目概述、业务…

作者头像 李华
网站建设 2026/10/9 3:00:17

低代码配置指南:医疗机构DeepSeek辅助诊断模型训练技巧

简介&#xff1a;这份PDF文档面向医疗信息技术开发人员、数据分析师与机器学习工程师&#xff0c;聚焦如何在低代码平台上完成DeepSeek辅助诊断模型的训练与落地。内容从低代码开发与医疗机构需求切入&#xff0c;依次讲解DeepSeek模型原理与医疗应用场景、低代码环境搭建、医疗…

作者头像 李华
网站建设 2026/10/9 2:59:23

近红外光谱深度学习回归建模:从预处理到1D-CNN的完整工程实践

简介&#xff1a;面向近红外光谱&#xff08;NIR&#xff09;数据回归分析场景的深度学习模型代码包&#xff0c;适合化学、生物医学、食品及农业等领域需要利用光谱数据预测组分含量的研究人员与学生使用。该压缩包共9个文件&#xff0c;含8个Python脚本和1个Markdown说明文档…

作者头像 李华